From ebfd705b93700ffb195185827459c7c0280dcfb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E5=9C=A8=E5=9D=A4?= Date: Sun, 18 Jan 2026 15:58:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0Docker=E5=92=8C?= =?UTF-8?q?=E9=83=A8=E7=BD=B2=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加Dockerfile用于容器化部署 - 添加docker-compose.yml用于本地开发环境 - 添加build.sh构建脚本 - 添加README.md项目文档 - 添加API文档 --- api/Dockerfile | 46 +++ api/README.md | 442 +++++++++++++++++++++ api/build.sh | 139 +++++++ api/docker-compose.yml | 38 ++ api/docs/WebSocket快速测试指南.md | 103 +++++ api/docs/WebSocket测试文档.md | 618 ++++++++++++++++++++++++++++++ api/docs/文字交流模式说明.md | 147 +++++++ 7 files changed, 1533 insertions(+) create mode 100644 api/Dockerfile create mode 100644 api/README.md create mode 100644 api/build.sh create mode 100644 api/docker-compose.yml create mode 100644 api/docs/WebSocket快速测试指南.md create mode 100644 api/docs/WebSocket测试文档.md create mode 100644 api/docs/文字交流模式说明.md diff --git a/api/Dockerfile b/api/Dockerfile new file mode 100644 index 0000000..f2fd6e0 --- /dev/null +++ b/api/Dockerfile @@ -0,0 +1,46 @@ +# Life Echo API Dockerfile +FROM python:3.11-slim + +# 设置工作目录 +WORKDIR /app + +# 安装系统依赖 +RUN apt-get update && apt-get install -y \ + gcc \ + g++ \ + libc6-dev \ + libffi-dev \ + libxml2-dev \ + libxslt1-dev \ + libjpeg-dev \ + zlib1g-dev \ + libpng-dev \ + libfreetype6-dev \ + && rm -rf /var/lib/apt/lists/* + +# 复制依赖文件 +COPY requirements.txt . + +# 安装Python依赖 +RUN pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir -r requirements.txt + +# 复制应用代码 +COPY . . + +# 创建非root用户 +RUN useradd -m -u 1000 appuser && \ + chown -R appuser:appuser /app + +# 切换到非root用户 +USER appuser + +# 暴露端口 +EXPOSE 8000 + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD python -c "import requests; requests.get('http://localhost:8000/health')" || exit 1 + +# 启动命令 +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/api/README.md b/api/README.md new file mode 100644 index 0000000..775fa6c --- /dev/null +++ b/api/README.md @@ -0,0 +1,442 @@ +# Life Echo API + +Life Echo 后端服务,基于 FastAPI 构建的实时语音对话回忆录生成系统。 + +## 项目简介 + +Life Echo API 是一个智能对话系统,通过 WebSocket 实时连接,使用 LangChain Agent 引导用户进行回忆录访谈对话,并将口语内容自动整理为结构化的书面章节,最终生成回忆录 PDF。 + +## 技术栈 + +- **Web 框架**: FastAPI 0.115.0 +- **WebSocket**: websockets 14.1 +- **AI 框架**: LangChain 0.3.0 + DeepSeek/兼容 OpenAI 的 LLM +- **数据库**: SQLAlchemy 2.0.36 + SQLite (aiosqlite) +- **PDF 生成**: ReportLab 4.2.2 + WeasyPrint 62.3 +- **ASR/TTS**: OpenAI Whisper API +- **认证**: JWT (python-jose) + bcrypt (passlib) +- **其他**: Pydantic, python-dotenv + +## 项目结构 + +``` +api/ +├── main.py # FastAPI 应用入口 +├── requirements.txt # Python 依赖 +├── agents/ # LangChain Agent +│ ├── conversation_agent.py # 对话引导 Agent +│ ├── memory_agent.py # 记忆整理 Agent +│ └── prompts/ # Agent 提示词 +│ ├── conversation_prompts.py +│ └── memory_prompts.py +├── database/ # 数据库模块 +│ ├── database.py # 数据库连接和初始化 +│ └── models.py # SQLAlchemy 数据模型 +├── routers/ # API 路由 +│ ├── auth.py # 认证 API(注册、登录、刷新令牌) +│ ├── conversations.py # 对话管理 API +│ ├── chapters.py # 章节管理 API +│ ├── books.py # 回忆录管理 API +│ └── websocket.py # WebSocket 端点 +├── dependencies/ # 依赖注入 +│ └── auth.py # 认证依赖(获取当前用户) +└── services/ # 业务服务 + ├── auth_service.py # 认证服务(密码哈希、JWT) + ├── asr_service.py # 语音识别服务 + ├── tts_service.py # 语音合成服务 + ├── pdf_service.py # PDF 生成服务 + └── llm_service.py # LLM 服务(DeepSeek/兼容 OpenAI 的 LLM) +``` + +## 环境配置 + +### 1. 安装依赖 + +```bash +cd api +pip install -r requirements.txt +``` + +### 2. 环境变量配置 + +创建 `.env` 文件(在 `api/` 目录下): + +```env +# DeepSeek API 配置(推荐,优先使用) +DEEPSEEK_API_KEY=your_deepseek_api_key_here +DEEPSEEK_BASE_URL=https://api.deepseek.com # 可选,默认值 +DEEPSEEK_MODEL=deepseek-chat # 可选,默认值 + +# 或使用通用 LLM 配置(支持其他兼容 OpenAI 的 LLM) +LLM_API_KEY=your_llm_api_key_here +LLM_BASE_URL=https://api.your-llm-provider.com # 可选 +LLM_MODEL=your-model-name # 可选,默认 deepseek-chat +LLM_TEMPERATURE=0.7 # 可选,默认 0.7 + +# 数据库配置(可选,默认使用 SQLite) +DATABASE_URL=sqlite+aiosqlite:///./life_echo.db + +# 认证配置 +SECRET_KEY=your-secret-key-here # JWT签名密钥(建议使用随机字符串) +ALGORITHM=HS256 # JWT算法(默认HS256) +ACCESS_TOKEN_EXPIRE_MINUTES=120 # 访问令牌过期时间(分钟,默认120即2小时) + +# 服务器配置(可选) +HOST=0.0.0.0 +PORT=8000 +``` + +**LLM 配置优先级**: +1. `DEEPSEEK_API_KEY` - 优先使用 DeepSeek(推荐) +2. `LLM_API_KEY` - 通用 LLM 配置(支持其他兼容 OpenAI 格式的 LLM) + +**DeepSeek 配置示例**: +```env +DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxx +DEEPSEEK_MODEL=deepseek-chat +``` + +### 3. 初始化数据库 + +数据库会在首次启动时自动初始化,或手动初始化: + +```python +from database import init_db +init_db() +``` + +## 运行服务 + +### 开发模式 + +```bash +uvicorn main:app --reload --host 0.0.0.0 --port 8000 +``` + +### 生产模式 + +```bash +uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4 +``` + +服务启动后,访问: +- API 文档: http://localhost:8000/docs +- 健康检查: http://localhost:8000/health + +## API 文档 + +### 认证系统 + +系统使用 JWT(JSON Web Token)进行认证,采用访问令牌(Access Token)+ 刷新令牌(Refresh Token)机制: + +- **访问令牌**:有效期 2 小时,用于 API 请求认证 +- **刷新令牌**:有效期 30 天,用于刷新访问令牌 + +#### 认证流程 + +1. **用户注册**:`POST /api/auth/register` +2. **用户登录**:`POST /api/auth/login` → 返回 `access_token` 和 `refresh_token` +3. **API 请求**:在 Header 中携带 `Authorization: Bearer {access_token}` +4. **刷新令牌**:`POST /api/auth/refresh` → 使用 `refresh_token` 获取新的 `access_token` +5. **用户登出**:`POST /api/auth/logout` → 撤销 `refresh_token` + +#### 认证 API (`/api/auth`) + +##### 用户注册 +```http +POST /api/auth/register +Content-Type: application/json + +{ + "phone": "13800138000", + "password": "password123", + "nickname": "用户昵称", + "email": "user@example.com" // 可选 +} +``` + +**响应**: +```json +{ + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "refresh_token": "random-refresh-token-string", + "token_type": "bearer" +} +``` + +##### 用户登录 +```http +POST /api/auth/login +Content-Type: application/json + +{ + "phone": "13800138000", + "password": "password123" +} +``` + +**响应**:同注册接口 + +##### 刷新访问令牌 +```http +POST /api/auth/refresh +Content-Type: application/json + +{ + "refresh_token": "your-refresh-token" +} +``` + +**响应**: +```json +{ + "access_token": "new-access-token", + "refresh_token": "same-refresh-token", + "token_type": "bearer" +} +``` + +##### 用户登出 +```http +POST /api/auth/logout +Authorization: Bearer {access_token} +Content-Type: application/json + +{ + "refresh_token": "your-refresh-token" +} +``` + +##### 获取当前用户信息 +```http +GET /api/auth/me +Authorization: Bearer {access_token} +``` + +**响应**: +```json +{ + "id": "user-id", + "phone": "13800138000", + "email": "user@example.com", + "nickname": "用户昵称", + "avatar_url": null, + "subscription_type": "free", + "created_at": "2024-01-15T10:00:00Z" +} +``` + +#### 使用认证令牌 + +所有需要认证的 API 请求都需要在 Header 中携带访问令牌: + +```http +Authorization: Bearer {access_token} +``` + +### REST API + +#### 对话管理 (`/api/conversations`) + +**注意**:所有对话相关接口都需要认证。 + +- `POST /api/conversations` - 创建新对话(需要认证) +- `GET /api/conversations/{conversation_id}` - 获取对话详情(需要认证,只能访问自己的对话) +- `POST /api/conversations/{conversation_id}/end` - 结束对话(需要认证,只能结束自己的对话) + +#### 章节管理 (`/api/chapters`) + +**注意**:所有章节相关接口都需要认证。 + +- `GET /api/chapters` - 获取用户所有章节(需要认证) +- `GET /api/chapters/{chapter_id}` - 获取章节详情(需要认证,只能访问自己的章节) +- `POST /api/chapters/{chapter_id}/regenerate` - 重新整理章节(需要认证,只能操作自己的章节) + +#### 回忆录管理 (`/api/books`) + +**注意**:所有回忆录相关接口都需要认证。 + +- `GET /api/books/current` - 获取当前回忆录(需要认证) +- `POST /api/books/export-pdf` - 导出 PDF(需要认证,只能导出自己的回忆录) + +### WebSocket API + +#### 对话 WebSocket (`/ws/conversation/{conversation_id}`) + +**注意**:WebSocket 连接需要认证,通过查询参数传递访问令牌。 + +**连接地址**: +``` +ws://localhost:8000/ws/conversation/{conversation_id}?token={access_token} +``` + +实时双向通信,支持: +- 接收客户端音频数据 +- 发送 Agent 响应文本 +- 实时语音识别(ASR) +- 实时语音合成(TTS) + +**认证要求**: +- 必须在查询参数中提供有效的 `access_token` +- 只能连接属于当前用户的对话 +- 如果对话不存在,将自动创建并关联到当前用户 + +**消息格式**: + +客户端 → 服务端: +```json +{ + "type": "audio", + "data": "base64_encoded_audio_data" +} +``` + +服务端 → 客户端: +```json +{ + "type": "transcript", + "text": "识别出的文本", + "agent_response": "Agent 的回复" +} +``` + +```json +{ + "type": "audio", + "data": "base64_encoded_tts_audio" +} +``` + +## 数据库模型 + +### User(用户) +- `id`: 用户 ID +- `phone`: 手机号(唯一,必填) +- `password_hash`: 密码哈希 +- `email`: 邮箱(可选) +- `openid`: 微信 OpenID(可选) +- `nickname`: 昵称 +- `avatar_url`: 头像 URL +- `subscription_type`: 订阅类型(free/premium) +- `created_at`: 创建时间 + +### RefreshToken(刷新令牌) +- `id`: 令牌 ID +- `user_id`: 用户 ID(外键) +- `token`: 刷新令牌(唯一) +- `expires_at`: 过期时间(30天后) +- `created_at`: 创建时间 +- `is_revoked`: 是否已撤销 + +### Conversation(对话) +- `id`: 对话 ID +- `user_id`: 用户 ID +- `started_at`: 开始时间 +- `ended_at`: 结束时间 +- `duration_seconds`: 持续时间(秒) +- `summary`: 对话摘要 +- `status`: 状态(active/ended/processing) +- `current_topic`: 当前话题 +- `conversation_stage`: 对话阶段(childhood/education/career/family/beliefs/summary) + +### Segment(对话段落) +- `id`: 段落 ID +- `conversation_id`: 对话 ID +- `audio_url`: 音频 URL +- `transcript_text`: 转录文本 +- `created_at`: 创建时间 +- `processed`: 是否已处理 +- `topic_category`: 话题分类 +- `agent_response`: Agent 响应 + +### Chapter(章节) +- `id`: 章节 ID +- `user_id`: 用户 ID +- `title`: 标题 +- `content`: 内容 +- `order_index`: 排序索引 +- `status`: 状态(draft/completed) +- `images`: 图片 URL 列表(JSON) +- `updated_at`: 更新时间 +- `category`: 章节分类 + +### Book(回忆录) +- `id`: 回忆录 ID +- `user_id`: 用户 ID +- `title`: 标题 +- `total_pages`: 总页数 +- `total_words`: 总字数 +- `cover_image_url`: 封面图片 URL +- `updated_at`: 更新时间 + +## 核心功能 + +### 1. 对话引导 Agent + +使用 LangChain 构建的对话 Agent,根据传记结构引导用户回忆: +- 童年时光 +- 教育经历 +- 职业生涯 +- 家庭生活 +- 人生信念 +- 总结回顾 + +### 2. 记忆整理 Agent + +将口语对话内容整理为结构化的书面章节: +- 口语转书面语 +- 内容结构化 +- 章节分类 +- 自动生成标题 + +### 3. 语音服务 + +- **ASR (语音识别)**: 使用 OpenAI Whisper API 将音频转为文本 +- **TTS (语音合成)**: 使用 OpenAI TTS API 将文本转为语音 + +### 4. PDF 生成 + +使用 ReportLab 和 WeasyPrint 生成精美的回忆录 PDF 文档。 + +## 开发指南 + +### 添加新的 API 路由 + +1. 在 `routers/` 目录创建新的路由文件 +2. 定义路由函数 +3. 在 `main.py` 中注册路由: + +```python +from routers import your_router +app.include_router(your_router.router) +``` + +### 添加新的数据库模型 + +1. 在 `database/models.py` 中定义模型类 +2. 继承 `Base` +3. 运行数据库迁移(或重新初始化) + +### 添加新的服务 + +1. 在 `services/` 目录创建服务文件 +2. 实现服务类和方法 +3. 在路由或其他服务中导入使用 + +## 安全注意事项 + +1. **CORS 配置**: 当前允许所有来源,生产环境应限制为特定域名 +2. **API Key 安全**: 确保 `.env` 文件不被提交到版本控制 +3. **SECRET_KEY 安全**: 使用强随机字符串作为 JWT 签名密钥,生产环境必须更换 +4. **密码安全**: 密码使用 bcrypt 哈希存储,不会以明文形式保存 +5. **令牌安全**: + - 访问令牌短期有效(2小时),降低泄露风险 + - 刷新令牌存储在数据库中,支持撤销 + - 令牌过期后必须使用刷新令牌重新获取 +6. **数据库备份**: 定期备份 SQLite 数据库文件 +7. **错误处理**: 所有 API 都包含适当的错误处理和权限验证 +8. **日志记录**: 建议添加日志记录功能以便调试和监控 + +## 许可证 + +MIT License diff --git a/api/build.sh b/api/build.sh new file mode 100644 index 0000000..05bd434 --- /dev/null +++ b/api/build.sh @@ -0,0 +1,139 @@ +#!/bin/bash + +# Life Echo API Docker 镜像构建脚本 + +set -e + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# 配置 +IMAGE_NAME="life-echo-api" +VERSION="${1:-latest}" +REGISTRY="${REGISTRY:-}" +DOCKERFILE="${DOCKERFILE:-Dockerfile}" +BUILD_CONTEXT="${BUILD_CONTEXT:-.}" + +# 显示帮助信息 +show_help() { + echo "用法: $0 [选项] [版本]" + echo "" + echo "选项:" + echo " -h, --help 显示帮助信息" + echo " -v, --version VER 指定镜像版本(默认: latest)" + echo " -r, --registry REG 指定镜像仓库(可选)" + echo " -f, --file FILE 指定Dockerfile路径(默认: Dockerfile)" + echo " -p, --push 构建后推送到仓库" + echo " -t, --tag TAG 添加额外的标签" + echo "" + echo "示例:" + echo " $0 # 构建 latest 版本" + echo " $0 -v v1.0.0 # 构建 v1.0.0 版本" + echo " $0 -r registry.example.com # 指定镜像仓库" + echo " $0 -v v1.0.0 -p # 构建并推送" + echo "" +} + +# 解析参数 +PUSH=false +EXTRA_TAGS=() + +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_help + exit 0 + ;; + -v|--version) + VERSION="$2" + shift 2 + ;; + -r|--registry) + REGISTRY="$2" + shift 2 + ;; + -f|--file) + DOCKERFILE="$2" + shift 2 + ;; + -p|--push) + PUSH=true + shift + ;; + -t|--tag) + EXTRA_TAGS+=("$2") + shift 2 + ;; + *) + if [[ -z "$VERSION" || "$VERSION" == "latest" ]]; then + VERSION="$1" + fi + shift + ;; + esac +done + +# 构建镜像名称 +if [[ -n "$REGISTRY" ]]; then + FULL_IMAGE_NAME="${REGISTRY}/${IMAGE_NAME}:${VERSION}" +else + FULL_IMAGE_NAME="${IMAGE_NAME}:${VERSION}" +fi + +echo -e "${GREEN}开始构建 Docker 镜像...${NC}" +echo -e "镜像名称: ${YELLOW}${FULL_IMAGE_NAME}${NC}" +echo -e "Dockerfile: ${YELLOW}${DOCKERFILE}${NC}" +echo -e "构建上下文: ${YELLOW}${BUILD_CONTEXT}${NC}" +echo "" + +# 构建镜像 +echo -e "${GREEN}执行构建命令...${NC}" +docker build \ + -f "${DOCKERFILE}" \ + -t "${FULL_IMAGE_NAME}" \ + "${BUILD_CONTEXT}" + +# 添加额外标签 +for tag in "${EXTRA_TAGS[@]}"; do + if [[ -n "$REGISTRY" ]]; then + TAG_NAME="${REGISTRY}/${IMAGE_NAME}:${tag}" + else + TAG_NAME="${IMAGE_NAME}:${tag}" + fi + echo -e "${GREEN}添加标签: ${TAG_NAME}${NC}" + docker tag "${FULL_IMAGE_NAME}" "${TAG_NAME}" +done + +# 显示构建结果 +echo "" +echo -e "${GREEN}构建完成!${NC}" +echo -e "镜像: ${YELLOW}${FULL_IMAGE_NAME}${NC}" + +# 显示镜像信息 +echo "" +echo -e "${GREEN}镜像信息:${NC}" +docker images "${FULL_IMAGE_NAME}" --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}" + +# 推送到仓库 +if [[ "$PUSH" == true ]]; then + echo "" + echo -e "${GREEN}推送镜像到仓库...${NC}" + docker push "${FULL_IMAGE_NAME}" + + for tag in "${EXTRA_TAGS[@]}"; do + if [[ -n "$REGISTRY" ]]; then + TAG_NAME="${REGISTRY}/${IMAGE_NAME}:${tag}" + else + TAG_NAME="${IMAGE_NAME}:${tag}" + fi + docker push "${TAG_NAME}" + done + + echo -e "${GREEN}推送完成!${NC}" +fi + +echo "" +echo -e "${GREEN}完成!${NC}" diff --git a/api/docker-compose.yml b/api/docker-compose.yml new file mode 100644 index 0000000..277b613 --- /dev/null +++ b/api/docker-compose.yml @@ -0,0 +1,38 @@ +version: '3.8' + +services: + api: + build: + context: . + dockerfile: Dockerfile + image: life-echo-api:latest + container_name: life-echo-api-prod + ports: + - "8000:8000" + env_file: + - .env.prod + volumes: + # 持久化数据库 + - ./data:/app/data + restart: always + healthcheck: + test: ["CMD", "python", "-c", "import requests; requests.get('http://localhost:8000/health')"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + networks: + - life-echo-network + # 资源限制 + deploy: + resources: + limits: + cpus: '2' + memory: 2G + reservations: + cpus: '1' + memory: 1G + +networks: + life-echo-network: + driver: bridge diff --git a/api/docs/WebSocket快速测试指南.md b/api/docs/WebSocket快速测试指南.md new file mode 100644 index 0000000..8735779 --- /dev/null +++ b/api/docs/WebSocket快速测试指南.md @@ -0,0 +1,103 @@ +# Life Echo WebSocket 快速测试指南 + +## 🚀 快速开始 + +### 1. 连接地址 +``` +ws://localhost:8000/ws/conversation/{conversation_id} +``` + +### 2. 生成对话ID +```bash +# Python +python -c "import uuid; print(uuid.uuid4())" + +# 或使用在线工具:https://www.uuidgenerator.net/ +``` + +### 3. 在 Apifox 中操作 + +1. **新建 WebSocket 请求** + - 协议:WebSocket + - URL:`ws://localhost:8000/ws/conversation/你的对话ID` + +2. **点击连接** + - 应该立即收到 `connect` 消息 + +3. **发送音频块** + ```json + { + "type": "audio_chunk", + "conversation_id": "你的对话ID", + "data": { + "audio_base64": "UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAAB9AAACABAAZGF0YQAAAAA=" + } + } + ``` + +4. **接收响应**(按顺序) + - `transcript` - 语音转文字 + - `agent_response` - Agent 回应 + - `tts_audio` - TTS 音频 + +5. **结束对话** + ```json + { + "type": "end_conversation", + "conversation_id": "你的对话ID" + } + ``` + +--- + +## 📋 消息类型速查 + +### 客户端发送 + +| 类型 | 说明 | JSON 示例 | +|------|------|-----------| +| `audio_chunk` | 发送音频 | `{"type":"audio_chunk","data":{"audio_base64":"..."}}` | +| `end_conversation` | 结束对话 | `{"type":"end_conversation"}` | + +### 服务端返回 + +| 类型 | 说明 | 包含字段 | +|------|------|---------| +| `connect` | 连接确认 | `data.status` | +| `transcript` | 语音转文字 | `data.text` | +| `agent_response` | Agent 回应 | `data.text` | +| `tts_audio` | TTS 音频 | `data.audio_base64` | +| `end_conversation` | 结束确认 | `data.status` | +| `error` | 错误信息 | `data.message` | + +--- + +## 🧪 测试场景 + +### 场景1:基础对话 +1. 连接 → 2. 发送音频 → 3. 接收3条响应 → 4. 结束对话 + +### 场景2:多轮对话 +重复场景1的步骤2-3多次 + +### 场景3:错误处理 +发送无效的 `audio_base64` 数据,查看 `error` 消息 + +--- + +## ⚠️ 常见问题 + +**Q: 连接失败?** +A: 检查后端服务是否运行:`curl http://localhost:8000/health` + +**Q: 收不到响应?** +A: 检查消息格式和必需字段,查看后端日志 + +**Q: 音频处理失败?** +A: 检查音频格式和 Base64 编码,确保 ASR/TTS 服务配置正确 + +--- + +## 📝 完整文档 + +详细测试文档请参考:`WebSocket测试文档.md` diff --git a/api/docs/WebSocket测试文档.md b/api/docs/WebSocket测试文档.md new file mode 100644 index 0000000..e009bc2 --- /dev/null +++ b/api/docs/WebSocket测试文档.md @@ -0,0 +1,618 @@ +# Life Echo WebSocket 对话接口测试文档 + +## 1. 接口信息 + +### 1.1 连接地址 +``` +ws://localhost:8000/ws/conversation/{conversation_id} +``` + +**参数说明:** +- `conversation_id`: 对话ID(UUID格式,例如:`550e8400-e29b-41d4-a716-446655440000`) + +**示例:** +``` +ws://localhost:8000/ws/conversation/550e8400-e29b-41d4-a716-446655440000 +``` + +### 1.2 协议 +- 协议类型:WebSocket (WS) +- 消息格式:JSON +- 编码:UTF-8 + +--- + +## 2. 消息类型定义 + +### 2.1 客户端 → 服务端消息类型 + +| 消息类型 | 说明 | 必需字段 | +|---------|------|---------| +| `audio_chunk` | 发送音频数据块 | `type`, `data.audio_base64` | +| `end_conversation` | 结束对话 | `type` | + +### 2.2 服务端 → 客户端消息类型 + +| 消息类型 | 说明 | 包含字段 | +|---------|------|---------| +| `connect` | 连接确认 | `type`, `conversation_id`, `data.status`, `timestamp` | +| `transcript` | 语音转文字结果 | `type`, `conversation_id`, `data.text`, `timestamp` | +| `agent_response` | Agent 回应文本 | `type`, `conversation_id`, `data.text`, `timestamp` | +| `tts_audio` | TTS 生成的音频 | `type`, `conversation_id`, `data.audio_base64`, `timestamp` | +| `end_conversation` | 对话结束确认 | `type`, `conversation_id`, `data.status`, `timestamp` | +| `error` | 错误信息 | `type`, `data.message`, `timestamp` | + +--- + +## 3. 消息格式规范 + +### 3.1 客户端消息格式 + +#### 3.1.1 发送音频块 (audio_chunk) +```json +{ + "type": "audio_chunk", + "conversation_id": "550e8400-e29b-41d4-a716-446655440000", + "data": { + "audio_base64": "UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAAB9AAACABAAZGF0YQAAAAA=" + }, + "timestamp": "2024-01-15T10:30:00.000Z" +} +``` + +**字段说明:** +- `type`: 固定值 `"audio_chunk"` +- `conversation_id`: 对话ID(可选,建议包含) +- `data.audio_base64`: Base64编码的音频数据(必需) +- `timestamp`: ISO 8601格式的时间戳(可选) + +#### 3.1.2 结束对话 (end_conversation) +```json +{ + "type": "end_conversation", + "conversation_id": "550e8400-e29b-41d4-a716-446655440000", + "timestamp": "2024-01-15T10:35:00.000Z" +} +``` + +### 3.2 服务端消息格式 + +#### 3.2.1 连接确认 (connect) +```json +{ + "type": "connect", + "conversation_id": "550e8400-e29b-41d4-a716-446655440000", + "data": { + "status": "connected" + }, + "timestamp": "2024-01-15T10:30:00.000Z" +} +``` + +#### 3.2.2 语音转文字结果 (transcript) +```json +{ + "type": "transcript", + "conversation_id": "550e8400-e29b-41d4-a716-446655440000", + "data": { + "text": "我小时候住在北京" + }, + "timestamp": "2024-01-15T10:30:05.000Z" +} +``` + +#### 3.2.3 Agent 回应 (agent_response) +```json +{ + "type": "agent_response", + "conversation_id": "550e8400-e29b-41d4-a716-446655440000", + "data": { + "text": "听起来很有趣!能告诉我更多关于你在北京的生活吗?比如你最喜欢那里的什么地方?" + }, + "timestamp": "2024-01-15T10:30:06.000Z" +} +``` + +#### 3.2.4 TTS 音频 (tts_audio) +```json +{ + "type": "tts_audio", + "conversation_id": "550e8400-e29b-41d4-a716-446655440000", + "data": { + "audio_base64": "UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAAB9AAACABAAZGF0YQAAAAA=" + }, + "timestamp": "2024-01-15T10:30:07.000Z" +} +``` + +#### 3.2.5 对话结束 (end_conversation) +```json +{ + "type": "end_conversation", + "conversation_id": "550e8400-e29b-41d4-a716-446655440000", + "data": { + "status": "ended" + }, + "timestamp": "2024-01-15T10:35:00.000Z" +} +``` + +#### 3.2.6 错误信息 (error) +```json +{ + "type": "error", + "data": { + "message": "ASR服务暂时不可用,请稍后重试" + }, + "timestamp": "2024-01-15T10:30:05.000Z" +} +``` + +--- + +## 4. 在 Apifox 中测试步骤 + +### 4.1 准备工作 + +1. **启动后端服务** + ```bash + cd api + uvicorn main:app --reload --host 0.0.0.0 --port 8000 + ``` + +2. **准备测试数据** + - 生成一个 UUID 作为 `conversation_id`(可以使用在线工具或命令行) + - 准备一段 Base64 编码的音频数据(用于测试) + +### 4.2 创建 WebSocket 连接 + +1. **打开 Apifox** + - 创建新请求 + - 选择协议类型:**WebSocket** + +2. **配置连接地址** + ``` + ws://localhost:8000/ws/conversation/550e8400-e29b-41d4-a716-446655440000 + ``` + - 将 `550e8400-e29b-41d4-a716-446655440000` 替换为你的对话ID + +3. **点击"连接"按钮** + - 连接成功后,应该立即收到 `connect` 类型的消息 + +### 4.3 测试场景 + +#### 场景 1:完整对话流程测试 + +**步骤:** + +1. **连接 WebSocket** + - 地址:`ws://localhost:8000/ws/conversation/{conversation_id}` + - 预期收到:`connect` 消息 + +2. **发送音频块** + ```json + { + "type": "audio_chunk", + "conversation_id": "550e8400-e29b-41d4-a716-446655440000", + "data": { + "audio_base64": "UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAAB9AAACABAAZGF0YQAAAAA=" + } + } + ``` + - 预期收到(按顺序): + 1. `transcript` - 语音转文字结果 + 2. `agent_response` - Agent 回应文本 + 3. `tts_audio` - TTS 生成的音频 + +3. **继续发送多个音频块** + - 重复步骤 2,模拟连续对话 + +4. **结束对话** + ```json + { + "type": "end_conversation", + "conversation_id": "550e8400-e29b-41d4-a716-446655440000" + } + ``` + - 预期收到:`end_conversation` 确认消息 + - 连接自动断开 + +#### 场景 2:错误处理测试 + +**测试无效的音频数据:** +```json +{ + "type": "audio_chunk", + "data": { + "audio_base64": "invalid_base64_data" + } +} +``` +- 预期收到:`error` 消息,包含错误描述 + +**测试缺少必需字段:** +```json +{ + "type": "audio_chunk" +} +``` +- 预期收到:`error` 消息或连接断开 + +#### 场景 3:连接断开测试 + +1. **正常断开** + - 发送 `end_conversation` 消息 + - 连接正常关闭 + +2. **异常断开** + - 直接关闭连接(不发送 `end_conversation`) + - 服务端应清理资源 + +#### 场景 4:多轮对话测试 + +**测试对话阶段切换:** + +1. **发送童年相关话题** + ```json + { + "type": "audio_chunk", + "data": { + "audio_base64": "{童年相关音频的base64}" + } + } + ``` + - 预期:Agent 回应关于童年的话题 + +2. **发送教育相关话题** + ```json + { + "type": "audio_chunk", + "data": { + "audio_base64": "{教育相关音频的base64}" + } + } + ``` + - 预期:Agent 自动切换到教育阶段,回应教育相关话题 + +3. **发送工作相关话题** + - 预期:Agent 切换到职业阶段 + +--- + +## 5. 测试用例清单 + +### 5.1 基础功能测试 + +| 用例ID | 测试场景 | 输入 | 预期结果 | 状态 | +|--------|---------|------|---------|------| +| TC-001 | 建立连接 | 连接 WebSocket | 收到 `connect` 消息 | ⬜ | +| TC-002 | 发送音频块 | 发送 `audio_chunk` | 收到 `transcript`、`agent_response`、`tts_audio` | ⬜ | +| TC-003 | 结束对话 | 发送 `end_conversation` | 收到 `end_conversation` 确认,连接断开 | ⬜ | +| TC-004 | 多轮对话 | 连续发送多个音频块 | 每轮都收到完整响应 | ⬜ | + +### 5.2 异常场景测试 + +| 用例ID | 测试场景 | 输入 | 预期结果 | 状态 | +|--------|---------|------|---------|------| +| TC-101 | 无效音频数据 | 发送无效 base64 | 收到 `error` 消息 | ⬜ | +| TC-102 | 缺少必需字段 | 发送不完整的消息 | 收到 `error` 或连接断开 | ⬜ | +| TC-103 | 无效对话ID | 使用不存在的 conversation_id | 连接成功但创建新对话 | ⬜ | +| TC-104 | 连接超时 | 长时间不发送消息 | 连接保持或超时断开 | ⬜ | +| TC-105 | 异常断开 | 直接关闭连接 | 服务端清理资源 | ⬜ | + +### 5.3 性能测试 + +| 用例ID | 测试场景 | 输入 | 预期结果 | 状态 | +|--------|---------|------|---------|------| +| TC-201 | 快速连续发送 | 1秒内发送10个音频块 | 所有请求都能正常处理 | ⬜ | +| TC-202 | 大音频块 | 发送较大的音频数据 | 正常处理,响应时间在可接受范围 | ⬜ | +| TC-203 | 长时间连接 | 保持连接30分钟 | 连接稳定,无内存泄漏 | ⬜ | + +--- + +## 6. 测试数据准备 + +### 6.1 生成对话ID + +**方法1:使用在线工具** +- 访问:https://www.uuidgenerator.net/ +- 生成 UUID v4 + +**方法2:使用命令行** +```bash +# Python +python -c "import uuid; print(uuid.uuid4())" + +# Node.js +node -e "console.log(require('crypto').randomUUID())" +``` + +### 6.2 准备测试音频 + +**方法1:使用文本转Base64(模拟)** +```bash +# 创建一个简单的测试音频文件(WAV格式) +# 然后转换为Base64 +base64 -i test_audio.wav +``` + +**方法2:使用在线工具** +- 访问:https://base64.guru/converter/encode/audio +- 上传音频文件,获取Base64编码 + +**方法3:使用Python脚本** +```python +import base64 + +with open("test_audio.wav", "rb") as f: + audio_base64 = base64.b64encode(f.read()).decode('utf-8') + print(audio_base64) +``` + +### 6.3 测试音频建议 + +- **格式**:WAV、MP3、M4A +- **时长**:3-10秒 +- **内容**:清晰的中文语音 +- **示例话题**: + - 童年:"我小时候住在北京" + - 教育:"我在清华大学读书" + - 工作:"我在一家科技公司工作" + - 家庭:"我有一个幸福的家庭" + +--- + +## 7. 在 Apifox 中的详细操作 + +### 7.1 创建 WebSocket 请求 + +1. **新建请求** + - 点击"新建" → "WebSocket" + - 或使用快捷键 `Ctrl+N` (Windows) / `Cmd+N` (Mac) + +2. **配置连接** + - **名称**:Life Echo 对话测试 + - **URL**:`ws://localhost:8000/ws/conversation/{conversation_id}` + - **方法**:WebSocket (自动识别) + +3. **添加参数** + - 在 URL 中直接替换 `{conversation_id}` 为实际值 + - 或使用变量:`ws://localhost:8000/ws/conversation/{{conversation_id}}` + +### 7.2 发送消息 + +1. **连接成功后** + - 在消息输入框中输入 JSON 格式的消息 + - 或使用"消息模板" + +2. **创建消息模板** + ```json + { + "type": "audio_chunk", + "conversation_id": "{{conversation_id}}", + "data": { + "audio_base64": "{{audio_base64}}" + }, + "timestamp": "{{$timestamp}}" + } + ``` + +3. **发送消息** + - 点击"发送"按钮 + - 或使用快捷键 `Ctrl+Enter` + +### 7.3 查看响应 + +1. **实时消息** + - 在"消息"面板查看收到的消息 + - 消息按时间顺序显示 + +2. **消息详情** + - 点击消息查看详细信息 + - 可以查看 JSON 格式、时间戳等 + +3. **保存响应** + - 右键消息 → "保存为示例" + - 用于后续对比测试 + +### 7.4 使用环境变量 + +1. **创建环境** + - 点击"环境" → "新建环境" + - 名称:Local Development + +2. **添加变量** + ```json + { + "base_url": "ws://localhost:8000", + "conversation_id": "550e8400-e29b-41d4-a716-446655440000", + "audio_base64": "UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAAB9AAACABAAZGF0YQAAAAA=" + } + ``` + +3. **在请求中使用** + - URL: `{{base_url}}/ws/conversation/{{conversation_id}}` + - 消息中使用 `{{audio_base64}}` + +--- + +## 8. 常见问题排查 + +### 8.1 连接失败 + +**问题**:无法建立 WebSocket 连接 + +**排查步骤:** +1. 检查后端服务是否运行:`curl http://localhost:8000/health` +2. 检查端口是否正确:默认 8000 +3. 检查防火墙设置 +4. 检查 URL 格式是否正确(`ws://` 不是 `http://`) + +### 8.2 收不到响应 + +**问题**:发送消息后没有收到响应 + +**排查步骤:** +1. 检查消息格式是否正确(JSON 格式) +2. 检查必需字段是否包含(`type`, `data.audio_base64`) +3. 检查后端日志是否有错误 +4. 检查 LLM 服务是否配置(DEEPSEEK_API_KEY 或 LLM_API_KEY) + +### 8.3 收到错误消息 + +**问题**:收到 `error` 类型的消息 + +**排查步骤:** +1. 查看错误消息内容:`data.message` +2. 检查 ASR 服务配置(OPENAI_API_KEY) +3. 检查 TTS 服务配置(OPENAI_API_KEY) +4. 检查 LLM 服务配置(DEEPSEEK_API_KEY 或 LLM_API_KEY) + +### 8.4 音频处理失败 + +**问题**:音频转文字失败 + +**排查步骤:** +1. 检查音频格式是否支持(WAV、MP3、M4A) +2. 检查 Base64 编码是否正确 +3. 检查音频文件大小(建议 < 10MB) +4. 检查 ASR 服务是否可用 + +--- + +## 9. 测试检查清单 + +### 9.1 连接测试 +- [ ] 能够成功建立 WebSocket 连接 +- [ ] 连接后立即收到 `connect` 消息 +- [ ] 连接信息包含正确的 `conversation_id` + +### 9.2 消息发送测试 +- [ ] 能够发送 `audio_chunk` 消息 +- [ ] 能够发送 `end_conversation` 消息 +- [ ] 消息格式符合规范 + +### 9.3 消息接收测试 +- [ ] 发送音频后收到 `transcript` 消息 +- [ ] 收到 `agent_response` 消息 +- [ ] 收到 `tts_audio` 消息 +- [ ] 消息顺序正确(transcript → agent_response → tts_audio) + +### 9.4 功能测试 +- [ ] 语音转文字功能正常 +- [ ] Agent 回应内容合理 +- [ ] TTS 音频生成正常 +- [ ] 对话阶段自动切换 + +### 9.5 异常处理测试 +- [ ] 无效消息返回错误 +- [ ] 连接断开时资源清理 +- [ ] 错误消息格式正确 + +--- + +## 10. 性能指标 + +### 10.1 响应时间 + +| 操作 | 目标响应时间 | 实际响应时间 | 状态 | +|------|------------|------------|------| +| 连接建立 | < 1s | - | ⬜ | +| 音频转文字 | < 3s | - | ⬜ | +| Agent 回应 | < 5s | - | ⬜ | +| TTS 生成 | < 3s | - | ⬜ | +| 完整流程 | < 15s | - | ⬜ | + +### 10.2 并发测试 + +- [ ] 支持 10 个并发连接 +- [ ] 支持 50 个并发连接 +- [ ] 支持 100 个并发连接 + +--- + +## 11. 测试报告模板 + +### 测试信息 +- **测试日期**:2024-XX-XX +- **测试人员**:XXX +- **测试环境**:Local Development +- **后端版本**:1.0.0 + +### 测试结果 +- **总用例数**:XX +- **通过数**:XX +- **失败数**:XX +- **通过率**:XX% + +### 问题记录 +1. **问题描述**:XXX + - **严重程度**:高/中/低 + - **复现步骤**:XXX + - **预期结果**:XXX + - **实际结果**:XXX + +--- + +## 12. 附录 + +### 12.1 快速测试脚本 + +**Python 测试脚本示例:** +```python +import asyncio +import websockets +import json +import uuid +import base64 + +async def test_websocket(): + conversation_id = str(uuid.uuid4()) + uri = f"ws://localhost:8000/ws/conversation/{conversation_id}" + + async with websockets.connect(uri) as websocket: + # 接收连接确认 + response = await websocket.recv() + print("收到连接确认:", response) + + # 发送音频块(模拟) + message = { + "type": "audio_chunk", + "conversation_id": conversation_id, + "data": { + "audio_base64": "UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAAB9AAACABAAZGF0YQAAAAA=" + } + } + await websocket.send(json.dumps(message)) + + # 接收响应 + for i in range(3): + response = await websocket.recv() + print(f"收到响应 {i+1}:", response) + + # 结束对话 + end_message = { + "type": "end_conversation", + "conversation_id": conversation_id + } + await websocket.send(json.dumps(end_message)) + + # 接收结束确认 + response = await websocket.recv() + print("收到结束确认:", response) + +asyncio.run(test_websocket()) +``` + +### 12.2 参考链接 + +- FastAPI WebSocket 文档:https://fastapi.tiangolo.com/advanced/websockets/ +- Apifox WebSocket 测试:https://apifox.com/help/app/web-socket +- UUID 生成器:https://www.uuidgenerator.net/ +- Base64 编码工具:https://base64.guru/converter/encode/audio + +--- + +**文档版本**:v1.0 +**最后更新**:2024-01-15 +**维护人员**:Life Echo 开发团队 diff --git a/api/docs/文字交流模式说明.md b/api/docs/文字交流模式说明.md new file mode 100644 index 0000000..f4445d7 --- /dev/null +++ b/api/docs/文字交流模式说明.md @@ -0,0 +1,147 @@ +# 文字交流模式说明 + +## 概述 + +当前系统已配置为**纯文字交流模式**,暂时不接通语音模块(ASR/TTS)。用户可以通过WebSocket发送文字消息与AI进行对话,系统会自动将对话记录整理成书的多个章节。 + +## 功能特性 + +### 1. 文字对话 +- 用户通过WebSocket发送 `TEXT` 类型的消息 +- AI Agent 生成文字回应 +- 所有对话记录保存到数据库 + +### 2. 章节自动整理 +- 对话结束后,系统自动调用 Memory Agent +- 将对话段落整理成结构化的章节 +- 章节按类别分类(童年、教育、职业、家庭、信念、总结等) + +### 3. 用户认证 +- 所有操作需要用户登录 +- 使用JWT访问令牌进行认证 +- WebSocket连接需要传递token参数 + +## WebSocket 消息格式 + +### 客户端发送 + +#### 文字消息 +```json +{ + "type": "text", + "conversation_id": "conversation-id", + "data": { + "text": "我小时候住在北京" + }, + "timestamp": "2024-01-15T10:30:00.000Z" +} +``` + +#### 结束对话 +```json +{ + "type": "end_conversation", + "conversation_id": "conversation-id" +} +``` + +### 服务端返回 + +#### 连接确认 +```json +{ + "type": "connect", + "conversation_id": "conversation-id", + "data": { + "status": "connected" + }, + "timestamp": "2024-01-15T10:30:00.000Z" +} +``` + +#### Agent 回应 +```json +{ + "type": "agent_response", + "conversation_id": "conversation-id", + "data": { + "text": "听起来很有趣!能告诉我更多关于你在北京的生活吗?" + }, + "timestamp": "2024-01-15T10:30:05.000Z" +} +``` + +#### 对话结束确认 +```json +{ + "type": "end_conversation", + "conversation_id": "conversation-id", + "data": { + "status": "ended" + }, + "timestamp": "2024-01-15T10:35:00.000Z" +} +``` + +## 使用流程 + +### 1. 用户注册/登录 +```bash +POST /api/auth/register +{ + "phone": "13800138000", + "password": "password123", + "nickname": "用户昵称" +} + +# 返回 access_token 和 refresh_token +``` + +### 2. 创建对话 +```bash +POST /api/conversations +Authorization: Bearer {access_token} + +# 返回 conversation_id +``` + +### 3. 连接 WebSocket +``` +ws://localhost:8000/ws/conversation/{conversation_id}?token={access_token} +``` + +### 4. 发送文字消息 +发送 `TEXT` 类型消息,接收 `AGENT_RESPONSE` 回应 + +### 5. 结束对话 +发送 `END_CONVERSATION` 消息,系统自动整理章节 + +### 6. 查看章节 +```bash +GET /api/chapters +Authorization: Bearer {access_token} +``` + +## 已移除的功能 + +- ❌ 音频块处理(`AUDIO_CHUNK`) +- ❌ 语音转文字(ASR) +- ❌ 文字转语音(TTS) +- ❌ 转写结果消息(`TRANSCRIPT`) +- ❌ TTS音频消息(`TTS_AUDIO`) + +## 保留的功能 + +- ✅ 文字消息处理(`TEXT`) +- ✅ Agent 文字回应(`AGENT_RESPONSE`) +- ✅ 对话阶段检测 +- ✅ 章节自动整理 +- ✅ 用户认证 +- ✅ 对话管理 + +## 注意事项 + +1. **WebSocket 连接必须提供 token**:`ws://.../ws/conversation/{id}?token={access_token}` +2. **所有对话记录都会保存**:用于后续章节整理 +3. **章节整理在对话结束时触发**:发送 `END_CONVERSATION` 消息 +4. **章节按类别自动分类**:系统会根据内容自动判断章节类别