更新文档:修复 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
parent
a3ef0809d9
commit
a69f3fb75b
|
|
@ -23,3 +23,4 @@ build/
|
||||||
|
|
||||||
# OS
|
# OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.claude
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
# Claude Code — 儿童心理陪伴玩偶项目
|
||||||
|
|
||||||
|
## 项目背景
|
||||||
|
|
||||||
|
基于小智 AI 生态的儿童心理陪伴玩偶项目。由客座教授(大头/胡老师)指导团队开发。
|
||||||
|
- 仓库:http://xmhome.ow-my.com:3000/sileya-ai/child-psycho-companion
|
||||||
|
- 技术栈:Python、MCP(Model 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-server(8000/8003)、mcp-endpoint-server(8004)
|
||||||
|
- 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
|
||||||
|
```
|
||||||
|
|
@ -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 Plan(sk-cp- 开头)
|
||||||
|
- LLM 端点:`https://api.minimaxi.com/v1`
|
||||||
|
- Anthropic 端点:`https://api.minimaxi.com/anthropic`
|
||||||
|
|
||||||
|
### mcp-endpoint-server
|
||||||
|
- 端口:8004
|
||||||
|
- Token(已 URL 编码):`usxQ%2BQ7GkvPsndgGVZDZBCE1%2Bcp6w1dJKkmoj7EJCqM%3D`
|
||||||
|
|
@ -108,8 +108,19 @@ async def run_single(text: str):
|
||||||
print(f"发送: {text}")
|
print(f"发送: {text}")
|
||||||
print(f"{'='*60}\n")
|
print(f"{'='*60}\n")
|
||||||
|
|
||||||
url = f"{WS_URL}?device-id={DEVICE_ID}&authorization=cli"
|
# Query param device-id kept for server-side routing/parsing;
|
||||||
async with websockets.connect(url, ping_interval=None, max_size=10 * 1024 * 1024) as ws:
|
# 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(含工具列表)
|
# 1. 发 hello,等 welcome(含工具列表)
|
||||||
await ws.send(json.dumps({
|
await ws.send(json.dumps({
|
||||||
"type": "hello", "version": 1,
|
"type": "hello", "version": 1,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue