# Life Echo API Life Echo 后端服务,基于 FastAPI 构建的实时语音对话回忆录生成系统。 ## 项目简介 Life Echo API 是一个智能对话系统,通过 WebSocket 实时连接,使用 LangChain Agent 引导用户进行回忆录访谈对话,并将口语内容自动整理为结构化的书面章节,最终生成回忆录 PDF。 ### 架构要点(多 Agent 收敛) - **会话真源**:`conversation_messages`(DB)+ Redis 缓存;**实时编排入口**:`ChatOrchestrator`。 - **图像管线**:正文主图 `generate_story_image`;章节封面 `try_enqueue_generate_chapter_cover` → `generate_chapter_cover`。 - **回忆录批次**:`MemoirOrchestrator.prepare_batches` 显式分桶后,`process_memoir_phase1` 派发 Phase 2 按类别调用 `run_story_pipeline_for_category_batch`(含 `StoryRouteAgent.plan_batch` 多 unit 写入)。 ### LLM 与记忆(约定文档) - **JSON 模式**:结构化抽取/路由/叙事 JSON 使用 `app/core/langchain_llm.py` 的 `bind_json_object_mode`(与 [DeepSeek JSON Output](https://api-docs.deepseek.com/guides/json_mode) 一致);详见 [`docs/llm-json-mode.md`](docs/llm-json-mode.md)。适配器说明见 [`app/adapters/llm/deepseek.py`](app/adapters/llm/deepseek.py)。 - **记忆检索**:异步与 Celery 均使用 **向量(pgvector)** chunks,见 [`docs/memory-retrieval.md`](docs/memory-retrieval.md)(含 async/sync **行为矩阵**)。 - **AI 相关代码扫描**:`uv run python scripts/ai_touchpoints_scan.py --markdown docs/ai-touchpoints.md`(在 `api/` 目录下执行)生成带标签的触点列表,见 [`docs/ai-touchpoints.md`](docs/ai-touchpoints.md)。 - **与 AI 强相关的配置项**:产品调参 SSOT 为 [`config/*.toml`](config/default.toml)(经 `app/features/*/constants.py` 与 `app/core/runtime_constants.py` re-export);密钥见 [`.env.example`](.env.example)。详见 [`docs/configuration.md`](docs/configuration.md)。 - **Memory compaction**:默认在 `config/default.toml` → `[memory]` 中开启。须运行 **Celery worker** 与 **celery-beat**([`docker-compose.yml`](docker-compose.yml) 已包含 `celery-beat`,用于定期 `memory_compaction_sweep`)。 - **Memory LLM enrichment(单次 LLM:会话摘要 + 事实)**:任务路由到 **`memory_idle`** 队列(`config/default.toml` → `[celery] memory_enrichment_queue`)。本地与 compose 内 worker 已使用 `-Q celery,memory_idle`;生产可单独起低并发 worker 只消费 `memory_idle`,与主队列隔离。 ## 技术栈 - **Web 框架**: FastAPI 0.115.0 - **WebSocket**: websockets 14.1 - **AI 框架**: LangChain 0.3.7 + DeepSeek/兼容 OpenAI 的 LLM - **数据库**: PostgreSQL 17 + SQLAlchemy 2.0.36 (asyncpg) - **缓存/队列**: Redis 7 + Celery 5.3 - **PDF 生成**: ReportLab 4.2.2 + WeasyPrint 62.3 - **ASR/TTS**: OpenAI Whisper API - **认证**: JWT (python-jose) + bcrypt - **其他**: Pydantic, python-dotenv - **可观测性**: OpenTelemetry → Grafana LGTM(Tempo / Prometheus / Loki),见 [`docs/observability.md`](docs/observability.md) ## 可观测性(本地) ```bash docker compose -f docker-compose.dev.yml -f docker-compose.observability.yml up -d # Grafana: http://127.0.0.1:48300 ``` 在 `config/*.toml` 的 `[deploy]` 中配置 `otel_enabled` 与 `otel_exporter_otlp_endpoint`;采样策略等细项见 `[otel]` section 与 [`docs/observability.md`](docs/observability.md)。 ## 项目结构 ``` api/ ├── main.py # 应用入口(uvicorn 启动) ├── app/ # 应用主包 │ ├── main.py # FastAPI 应用定义 │ ├── core/ # 核心基础设施 │ │ ├── config.py # secrets / bootstrap(Settings + facade) │ │ ├── app_config*.py # TOML 加载与 AppConfig │ │ ├── runtime_constants.py # re-export config/*.toml runtime sections │ │ ├── db.py # 数据库连接 │ │ ├── redis.py # Redis 服务 │ │ ├── security.py # JWT、密码哈希 │ │ └── task_tracker.py # 任务状态追踪 │ ├── features/ # 功能模块(各模块含 router、service、repo) │ │ ├── auth/ # 认证(注册、登录、短信验证码) │ │ ├── user/ # 用户信息 │ │ ├── conversation/ # 对话、WebSocket │ │ ├── memory/ # 记忆检索 │ │ ├── memoir/ # 回忆录、章节、PDF、图像生成 │ │ ├── payment/ # 支付 │ │ ├── plan/ # 套餐 │ │ ├── quota/ # 配额 │ │ ├── tasks/ # 任务状态 API │ │ └── content/ # 内容(TTS 等) │ ├── adapters/ # 外部能力适配器(ASR、TTS、LLM、短信、存储等) │ ├── ports/ # 能力契约(Protocol) │ └── agents/ # LangChain Agent ├── tasks/ # Celery 后台任务 │ └── memoir_tasks.py # 回忆录处理任务 └── docs/ # 详细文档 ``` ## 环境配置 ### 1. 安装依赖 ```bash cd api pip install -r requirements.txt ``` ### 2. 配置(TOML + .env) 配置分两层 SSOT,详见 **[docs/configuration.md](docs/configuration.md)**。 | 层 | 来源 | 内容 | |----|------|------| | **Secrets / bootstrap** | [`.env.example`](.env.example) | `DATABASE_URL`、`SECRET_KEY`、API/支付/Liblib 密钥 | | **非密钥** | [`config/default.toml`](config/default.toml) + `config/{APP_ENV}.toml` | 功能开关、SMS 模板、Chat/Memoir/Memory/Eval 调参、OTel 等 | 本地开发:`.env.development`(密钥)+ `config/development.toml`(行为);`development.sh` 将前者同步为 `.env`。预发/生产:`.env.staging` / `.env.production` + 对应 `config/*.toml`。 最小 `.env` 示例: ```env APP_ENV=development DATABASE_URL=postgresql://postgres:postgres@localhost:48291/life_echo REDIS_URL=redis://localhost:48307/0 SECRET_KEY=your-secret-key-here DEEPSEEK_API_KEY=sk-... ZHIPU_API_KEY=... TENCENT_SECRET_ID=... TENCENT_SECRET_KEY=... ``` **腾讯云**:凭证仍在 env(`TENCENT_SECRET_ID/KEY`);短信模板 ID、COS 桶名等在 `config/*.toml` 的 `[deploy]` section。 业务代码读取 TOML 值仍可用原有 import(re-export): | 模块 | 路径 | |------|------| | 访谈 / 聊天 | `app/features/conversation/constants.py` → `chat` | | 回忆录流水线 | `app/features/memoir/constants.py` → `memoir` | | Story / 章节 | `app/features/story/constants.py` → `story` | | 记忆富化 / compaction | `app/features/memory/constants.py` → `memory` | | 内网评测 | `app/features/evaluation/constants.py` → `eval_cfg` | | ASR/TTS/LLM/Celery 等 | `app/core/runtime_constants.py` | ### 3. 数据库迁移 数据库 schema 由 Alembic 管理。**`app/main.py` 启动时会在线程中执行 `alembic upgrade head`**(见 `app/core/alembic_startup.py`):对连接类错误自动重试;生产环境建议设置 `ALEMBIC_STARTUP_FAIL_FAST=true`,迁移失败则进程退出。 规范与跨环境排障见 **[docs/alembic-migrations.md](docs/alembic-migrations.md)**(禁止改已部署 revision id、老库用显式 `0019` 补列等)。 ```bash cd api uv run alembic upgrade head uv run pytest tests/test_alembic_migration_policy.py -q ``` 若库中仍为已撤回的 `0020_*` revision,部署前先执行 `uv run python scripts/repair_alembic_version_after_withdrawn_0020.py`(见上文文档)。 ## 快速启动 ### 本地开发 推荐使用一键脚本(会自动启动 PostgreSQL/Redis、检查 `.venv`、安装依赖并拉起 FastAPI + Celery): ```bash cd api ./dev-up.sh ``` 可选环境变量: - `SKIP_INSTALL=1`:跳过依赖安装 - `API_HOST` / `API_PORT`:覆盖 API 启动地址和端口 - `CELERY_POOL`:覆盖 Celery 池类型(macOS 推荐 `solo`) 也可以使用手动方式: ```bash cd api # 1. 启动 PostgreSQL + Redis docker compose -f docker-compose.dev.yml up -d # 2. 安装依赖 pip install -r requirements.txt # 3. 配置环境变量(与 docker-compose.dev.yml 固定宿主端口一致:Postgres 48291、Redis 48307) export DATABASE_URL=postgresql://postgres:postgres@localhost:48291/life_echo export REDIS_URL=redis://localhost:48307/0 # 4. 启动 API(终端 1) uvicorn main:app --reload --host 0.0.0.0 --port 8000 # 5. 启动 Celery Worker(终端 2) # macOS 使用 solo 池避免 fork 崩溃问题;须同时消费 memory_idle(Memory 富化) celery -A app.tasks.celery_app worker --loglevel=info --pool=solo -Q celery,memory_idle # Linux/生产环境可以使用 prefork 池 # celery -A app.tasks.celery_app worker --loglevel=info --concurrency=4 -Q celery,memory_idle ``` ### 验证服务 ```bash # 检查 PostgreSQL docker exec life-echo-postgres-dev psql -U postgres -c "SELECT 1" # 检查 Redis docker exec life-echo-redis-dev redis-cli ping ``` ### 生产部署(一键) ```bash cd api # 创建生产配置 cp .env .env.prod # 编辑 .env.prod # 启动所有服务 docker compose up -d # 查看日志 docker compose logs -f ``` 服务启动后,访问: - API 文档: http://localhost:8000/docs - 健康检查: http://localhost:8000/health ## 📚 详细文档 更多详细文档请参考 [docs/README.md](docs/README.md): - **[本地开发环境配置](docs/本地开发环境配置.md)** - 开发环境搭建指南 - **[WebSocket 快速测试指南](docs/WebSocket快速测试指南.md)** - WebSocket 快速测试 - **[WebSocket 测试文档](docs/WebSocket测试文档.md)** - WebSocket 详细接口文档 - **[文字交流模式说明](docs/文字交流模式说明.md)** - 文字对话模式功能说明 - **[测试脚本使用说明](docs/测试脚本使用说明.md)** - 自动化测试脚本指南 ## 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": "new-refresh-token-string", "token_type": "bearer" } ``` 每次刷新会轮换 refresh token(返回新的 refresh token,旧 token 立即失效)。在 `REFRESH_TOKEN_REUSE_GRACE_SECONDS`(默认 30 秒)窗口内重复使用已轮换的旧 token 视为幂等重试,返回新 access token 与当前 replacement refresh token;grace 窗口外再次使用则吊销该用户全部会话并返回 `REFRESH_TOKEN_REUSE`。 ##### 用户登出 ```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} ``` ### HTTP 错误契约 所有 HTTP 错误响应均为 `application/json`,统一格式: ```json { "error_code": "NOT_FOUND", "message": "资源不存在", "request_id": "550e8400-e29b-41d4-a716-446655440000" } ``` - `error_code`:机器可读错误码(见 OpenAPI `ErrorResponse` / `ErrorCode` 组件) - `message`:面向用户的说明 - `request_id`:与响应头 `X-Request-Id` 一致,便于排查 **429 状态码语义**:HTTP 429 被两种错误码共用,客户端必须根据 `error_code` 分支,不能只看 status: | error_code | 含义 | |------------|------| | `QUOTA_EXCEEDED` | 配额已用尽(如对话次数) | | `RATE_LIMITED` | 请求频率超限(如 SMS 发送冷却) | 遗留 `HTTPException(status_code=429)` 默认映射为 `RATE_LIMITED`。 **CORS 与 credentials**:`api_cors_origins` 留空时,服务端使用 `allow_origins=["*"]` 且 `allow_credentials=False`;生产/staging 必须在 `config/staging.toml` / `config/production.toml` 的 `[deploy]` 中配置逗号分隔的前端域名。 ### 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" } ``` **WebSocket 错误消息**(与 HTTP 错误契约不同,勿混用 `parseApiError`): 服务端 → 客户端(配额不足等): ```json { "type": "error", "data": { "message": "本月对话次数已用尽", "code": "QUOTA_EXCEEDED" }, "timestamp": "2024-01-15T10:00:00Z" } ``` - WS 帧使用 `data.code`(如 `QUOTA_EXCEEDED`),**不是** HTTP 的 `error_code` 字段 - HTTP 客户端错误解析器(`parseApiError`)不适用于 WebSocket 消息 ## 数据库模型 ### 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 - `user_input_text`: 用户输入正文(语音 ASR 或文字输入;历史列名 `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. 在对应 feature 的 `app/features//models.py` 中定义模型类 2. 继承 `Base`(从 `app.core.db` 导入) 3. 在 `app/main.py` 中 import 该 models 模块以注册到 Base.metadata 4. 运行 Alembic 迁移 ### 添加新的服务 1. 在 `app/features//service.py` 中实现业务逻辑 2. 通过 `deps.py` 提供依赖注入 3. 在对应 feature 的 router 中通过 `Depends(get_xxx_service)` 使用 ## 安全注意事项 1. **CORS 配置**: 本地开发默认可用 `allow_origins=["*"]`(`deploy.api_cors_origins` 留空);生产/staging 必须在 `config/staging.toml` / `config/production.toml` 的 `[deploy]` 中设置逗号分隔前端域名 2. **API Key 安全**: 确保 `.env` 文件不被提交到版本控制 3. **SECRET_KEY 安全**: 使用强随机字符串作为 JWT 签名密钥,生产环境必须更换 4. **密码安全**: 密码使用 bcrypt 哈希存储,不会以明文形式保存 5. **令牌安全**: - 访问令牌短期有效(2小时),降低泄露风险 - 刷新令牌存储在数据库中,支持撤销;每次 `/api/auth/refresh` 会轮换 refresh token - 已轮换的 refresh token 被再次使用时,服务端吊销全部会话并返回 `REFRESH_TOKEN_REUSE` - 令牌过期后必须使用刷新令牌重新获取 6. **数据库备份**: 定期备份 PostgreSQL 数据库 7. **错误处理**: 所有 API 都包含适当的错误处理和权限验证 8. **日志记录**: 建议添加日志记录功能以便调试和监控 ## 许可证 MIT License