diff --git a/.gitignore b/.gitignore index 6f36b5f..7161208 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ build/ # OS .DS_Store +.claude diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..cbe5240 --- /dev/null +++ b/CLAUDE.md @@ -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 +``` diff --git a/STATUS.md b/STATUS.md new file mode 100644 index 0000000..2ccae91 --- /dev/null +++ b/STATUS.md @@ -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://: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` diff --git a/xiaozhi_cli_client.py b/xiaozhi_cli_client.py index c7d7298..24be6db 100644 --- a/xiaozhi_cli_client.py +++ b/xiaozhi_cli_client.py @@ -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,