更新文档:修复 WebSocket 认证、记录 MiniMax 401 根因、添加 STATUS.md

- xiaozhi_cli_client.py: 改用 additional_headers 传递 Device-ID
- .gitignore: 添加 .claude
- 新增 STATUS.md: 完整项目状态报告
- 更新 CLAUDE.md: 最新问题排查记录和下一步计划

已知问题:MiniMax LLM 401 根因为 manager-api 配置覆盖,
解决方向为升级到全模块安装智控台
main
Sileya 2026-04-06 21:30:50 +08:00
parent a3ef0809d9
commit a69f3fb75b
4 changed files with 278 additions and 2 deletions

1
.gitignore vendored
View File

@ -23,3 +23,4 @@ build/
# OS
.DS_Store
.claude

102
CLAUDE.md Normal file
View File

@ -0,0 +1,102 @@
# Claude Code — 儿童心理陪伴玩偶项目
## 项目背景
基于小智 AI 生态的儿童心理陪伴玩偶项目。由客座教授(大头/胡老师)指导团队开发。
- 仓库http://xmhome.ow-my.com:3000/sileya-ai/child-psycho-companion
- 技术栈Python、MCPModel Context Protocol、MiniMax API、Docker
## 技术架构
### 核心模块
- `src/psycho_screener/screener.py` — 心理问题筛查bullying/depression/anxiety 等7类
- `src/psycho_screener/mcp_tool.py` — fastmcp 协议版本
- `src/psycho_screener/mcp_tool_native.py` — 原生 mcp SDK 版本
### 工具
- `mcp_simulator.py` — MCP 协议模拟器
- `xiaozhi_cli_client.py` — xiaozhi-esp32-server CLI 调试工具
- `test_xiaozhi_client.py` — pytest 测试套件
### 环境
- Docker 容器xiaozhi-esp32-server8000/8003、mcp-endpoint-server8004
- Python 3.x + .venv 虚拟环境
---
## 当前状态2026-04-06
### 已解决的问题
#### 1. WebSocket 认证问题 ✅
- **问题**:服务端从 HTTP header 读 `device-id`,客户端从 URL query param 传
- **根因**xiaozhi-server `connection.py` 第 227 行用 `self.headers.get("device-id")` 读 header客户端用 `?device-id=xxx` query 参数
- **修复**`xiaozhi_cli_client.py` 改用 `additional_headers={"Device-ID": DEVICE_ID, "Client-ID": DEVICE_ID}` 传递
- **状态**已修复并验证WebSocket 连接成功
#### 2. MCP Endpoint Token URL 编码 ✅
- **问题**token 中的 `+` 在 URL 中被 FastAPI 解码为空格,导致 base64 解码失败
- **根因**`api.minimaxi.com/v1` 返回 1008 policy violation
- **修复**`.config.yaml` 中 token 的 `+``%2B``=` → `%3D`
- **状态**:已修复,`MCP接入点连接成功`
#### 3. MCP 工具注册 ✅ 不需要真机
- **澄清**`psycho-screener` 是服务端 MCP通过 stdio 子进程运行,不需要 ESP32 真机
- **状态**`psycho_screen` 工具已在函数列表中注册
### 待解决问题
#### P0 — MiniMax LLM 401 错误(根因已定位)
- **现象**xiaozhi-server LLM 调用返回 401 "令牌已过期或验证不正确"
- **根因**:配置被 xinnan-tech manager-api 远程覆盖
- 本地 `.config.yaml` 配置了 MiniMax-M2.5
- 但 `config_loader.py` 检测到 manager-api 配置后,调用 `get_server_config()` 从 xinnan-tech API 获取设备配置
- API 返回的默认模型是 `glm-4-flash`,覆盖了本地配置
- `glm-4-flash` 的 API key 与 MiniMax 不匹配,导致 401
- **解决方向**:升级到 xiaozhi-esp32-server 全模块安装(智控台),在智控台配置 MiniMax API Key
- **详见**`STATUS.md`
#### P1 — `screen_from_messages()` 未实现
- `screener.py` 的消息格式接口未完成
- 需要从 messages 数组提取儿童对话
---
## 下一步计划
### 近期(升级智控台)
1. 将 xiaozhi-esp32-server 从最简化安装升级为全模块安装(智控台)
2. 在智控台配置 MiniMax LLM + TTS
3. 验证玩偶对话功能完整链路
### 中期(功能完善)
1. 实现 `screen_from_messages()` 方法
2. WebSocket 认证调试
3. 案例库设计
详见 `STATUS.md`
---
## 运行命令
```bash
# 激活虚拟环境
cd /Users/bigemon/WorkSpace/child-psycho-companion
source .venv/bin/activate
# 测试玩偶对话
python xiaozhi_cli_client.py "今天小朋友打我,我好害怕"
# Docker 容器状态
docker ps | grep xiaozhi
# 查看 xiaozhi-server 日志
docker logs xiaozhi-esp32-server 2>&1 | tail -20
```
## Git 操作
```bash
git add . && git commit -m "描述" && git push origin main
```

162
STATUS.md Normal file
View File

@ -0,0 +1,162 @@
# 项目状态报告
_最后更新2026-04-06_
---
## 一、当前部署架构
```
┌─────────────────────────────────────────────────────┐
│ MacBook Pro (本地) │
│ │
│ ┌─ xiaozhi-esp32-server (Docker) ─────────────┐ │
│ │ 端口: 8000 (WS) / 8003 (HTTP) │ │
│ │ config: /opt/xiaozhi-esp32-server/data/ │ │
│ │ │ │
│ │ ⚠️ 当前: manager-api → xinnan-tech 云端 │ │
│ │ 云端配置覆盖本地 .config.yaml │ │
│ │ 导致 LLM 型号: glm-4-flash (应为MiniMax) │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌─ xiaozhi_cli_client.py ──────────────────────┐ │
│ │ WebSocket CLI 调试工具 │ │
│ │ ✅ WebSocket 认证问题已修复 │ │
│ │ ⚠️ TTS 音频跳过(无播放),文本回复正常 │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌─ child-psycho-companion ─────────────────────┐ │
│ │ psycho-screener MCP 工具 │ │
│ │ ✅ 已注册到 xiaozhi-server │ │
│ │ ✅ psycho_screen 工具在工具列表中 │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
```
---
## 二、问题排查记录
### 2.1 WebSocket 连接 ✅ 已解决
| 项目 | 详情 |
|------|------|
| **症状** | 连接成功但 `device-id` 为 None |
| **根因** | 服务端从 HTTP header 读 `device-id`,客户端从 URL query param 传 |
| **文件** | `xiaozhi_cli_client.py` |
| **修复** | `additional_headers={"Device-ID": DEVICE_ID, "Client-ID": DEVICE_ID}` |
### 2.2 MCP Endpoint Token ✅ 已解决
| 项目 | 详情 |
|------|------|
| **症状** | `ERROR: token解密失败 1008` |
| **根因** | URL 中 `+` 被解码为空格base64 解析失败 |
| **文件** | `xiaozhi-server/data/.config.yaml` |
| **修复** | `+``%2B``=` → `%3D` |
### 2.3 MiniMax LLM 401 ❌ 未解决(根因已定位)
| 项目 | 详情 |
|------|------|
| **症状** | LLM 返回 401 "令牌已过期或验证不正确" |
| **根因** | `config_loader.py` 优先从 manager-api 获取配置,云端返回 `glm-4-flash`,覆盖本地 MiniMax 配置 |
| **证据** | 请求日志中 `model: "glm-4-flash"`(而非 MiniMax-M2.5 |
| **解法** | 升级到全模块安装,在智控台配置 MiniMax |
---
## 三、MiniMax LLM 401 深度分析
### 当前配置加载逻辑
```
1. load_config() 读取 .config.yaml
2. 检测到 manager-api 有配置url + secret
3. 调用 get_server_config() 从 xinnan-tech API 获取设备配置
4. API 返回配置的 model: "glm-4-flash"
5. 覆盖本地 LLM 配置
6. xiaozhi-server 用 glm-4-flash + GLM API key 调 LLM → 401
```
### MiniMax API 验证结果
| 端点 | 状态 | 备注 |
|------|------|------|
| `api.minimaxi.com/v1/chat/completions` | ✅ 200 | MiniMax key 有效 |
| `api.minimaxi.com/anthropic` | ✅ 200 | MiniMax Token Plan |
| 容器内 urllib 直接调用 | ✅ 成功 | key 和网络均正常 |
| 容器内 httpx 直接调用 | ✅ 成功 | key 和网络均正常 |
| xiaozhi-server openai 库调用 | ❌ 401 | 因用的是 glm-4-flash 模型 |
---
## 四、下一步计划
### 方案:升级到 xiaozhi-esp32-server 全模块安装(智控台)
#### 为什么选这个方案
1. **路径最短**:当前已是最简化安装,升级到全模块架构改动最小
2. **MiniMax 原生支持**智控台有原生日志配置页TTS 有 `MinimaxTTS` 选项
3. **社区最大**8k ⭐,文档完善,有中文社区支持
4. **功能完整**:智控台提供完整 Web UI设备管理、模型配置、OTA 升级等
#### 部署要求
| 项目 | 最低配置 | 推荐配置 |
|------|---------|---------|
| CPU | 2核 | 4核 |
| 内存 | 4GB | 8GB |
| Docker | ✅ | ✅ |
| 数据库 | 自动拉起 MySQL + Redis | 同左 |
#### 部署步骤
1. 下载 `docker-compose_all.yml``config_from_api.yaml`
2. 配置 MySQL + Redis 容器
3. 启动 xiaozhi-esp32-server-web智控台端口 8002
4. 访问 `http://<IP>:8002` 注册超级管理员
5. 从智控台复制 `server.secret` 到本地 `.config.yaml`
6. **在智控台配置 MiniMax API Key**LLM + TTS
7. 重启容器验证
#### MiniMax 集成方式
**LLM**:智控台 → 模型配置 → 新增
```yaml
type: openai
model_name: MiniMax-M2.5
url: https://api.minimaxi.com/v1
api_key: sk-cp-Sd2G0paJUZWdQhKrISIICVqQnuiE4qvT-yMszahI7s0Sau02Pa1XZCXNsj2Z91n-xNV8hIG-xL8lENaEgFNQBZr7S6Y8_R7OASOScenpJIxxWOb6vc7sF38
```
**TTS**:智控台 → 模型配置 → 语音合成 → 选择 `MinimaxTTS`
---
## 五、备选方案(备查)
| 方案 | 特点 | MiniMax 难度 | 备注 |
|------|------|------------|------|
| **xinnan-tech 官方全模块** | 8k⭐功能最全 | ⭐ 极易 | **推荐首选** |
| **joey-zhou Java 版** | Web UI 最完整716⭐ | ⭐⭐⭐ 中等 | 3 容器Java 技术栈 |
| **MiniMax-OpenPlatform 版** | MiniMax 官方深度定制 | ⭐ 零配置 | 强耦合 MiniMax灵活性差 |
---
## 六、依赖信息
### Docker 容器
```
xiaozhi-esp32-server ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:server_latest
```
### MiniMax API
- Key 类型Token Plansk-cp- 开头)
- LLM 端点:`https://api.minimaxi.com/v1`
- Anthropic 端点:`https://api.minimaxi.com/anthropic`
### mcp-endpoint-server
- 端口8004
- Token已 URL 编码):`usxQ%2BQ7GkvPsndgGVZDZBCE1%2Bcp6w1dJKkmoj7EJCqM%3D`

View File

@ -108,8 +108,19 @@ async def run_single(text: str):
print(f"发送: {text}")
print(f"{'='*60}\n")
url = f"{WS_URL}?device-id={DEVICE_ID}&authorization=cli"
async with websockets.connect(url, ping_interval=None, max_size=10 * 1024 * 1024) as ws:
# Query param device-id kept for server-side routing/parsing;
# HTTP headers (Device-ID, Client-ID) are what the server's
# connection.py actually reads (line 227: self.headers.get("device-id")).
url = f"{WS_URL}?device-id={DEVICE_ID}"
async with websockets.connect(
url,
additional_headers={
"Device-ID": DEVICE_ID,
"Client-ID": DEVICE_ID,
},
ping_interval=None,
max_size=10 * 1024 * 1024,
) as ws:
# 1. 发 hello等 welcome含工具列表
await ws.send(json.dumps({
"type": "hello", "version": 1,