2026-01-18 15:58:05 +08:00
|
|
|
|
# Life Echo API
|
|
|
|
|
|
|
|
|
|
|
|
Life Echo 后端服务,基于 FastAPI 构建的实时语音对话回忆录生成系统。
|
|
|
|
|
|
|
|
|
|
|
|
## 项目简介
|
|
|
|
|
|
|
|
|
|
|
|
Life Echo API 是一个智能对话系统,通过 WebSocket 实时连接,使用 LangChain Agent 引导用户进行回忆录访谈对话,并将口语内容自动整理为结构化的书面章节,最终生成回忆录 PDF。
|
|
|
|
|
|
|
2026-03-22 16:45:57 +08:00
|
|
|
|
### 架构要点(多 Agent 收敛)
|
|
|
|
|
|
|
|
|
|
|
|
- **会话真源**:`conversation_messages`(DB)+ Redis 缓存;**实时编排入口**:`ChatOrchestrator`。
|
|
|
|
|
|
- **图像管线**:正文主图 `generate_story_image`;章节封面 `try_enqueue_generate_chapter_cover` → `generate_chapter_cover`。
|
2026-05-06 13:18:02 +08:00
|
|
|
|
- **回忆录批次**:`MemoirOrchestrator.prepare_batches` 显式分桶后,`process_memoir_phase1` 派发 Phase 2 按类别调用 `run_story_pipeline_for_category_batch`(含 `StoryRouteAgent.plan_batch` 多 unit 写入)。
|
2026-03-22 16:45:57 +08:00
|
|
|
|
|
2026-03-26 12:13:36 +08:00
|
|
|
|
### 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)。
|
2026-04-30 09:17:01 +08:00
|
|
|
|
- **记忆检索**:异步与 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)。
|
2026-05-22 13:44:50 +08:00
|
|
|
|
- **与 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`,与主队列隔离。
|
2026-03-26 12:13:36 +08:00
|
|
|
|
|
2026-01-18 15:58:05 +08:00
|
|
|
|
## 技术栈
|
|
|
|
|
|
|
|
|
|
|
|
- **Web 框架**: FastAPI 0.115.0
|
|
|
|
|
|
- **WebSocket**: websockets 14.1
|
2026-01-21 23:21:36 +01:00
|
|
|
|
- **AI 框架**: LangChain 0.3.7 + DeepSeek/兼容 OpenAI 的 LLM
|
|
|
|
|
|
- **数据库**: PostgreSQL 17 + SQLAlchemy 2.0.36 (asyncpg)
|
|
|
|
|
|
- **缓存/队列**: Redis 7 + Celery 5.3
|
2026-01-18 15:58:05 +08:00
|
|
|
|
- **PDF 生成**: ReportLab 4.2.2 + WeasyPrint 62.3
|
|
|
|
|
|
- **ASR/TTS**: OpenAI Whisper API
|
2026-01-21 23:21:36 +01:00
|
|
|
|
- **认证**: JWT (python-jose) + bcrypt
|
2026-01-18 15:58:05 +08:00
|
|
|
|
- **其他**: Pydantic, python-dotenv
|
feat: OpenTelemetry LGTM observability, dev tooling, and memoir UX fixes (#31) (#32)
* add staging ios app build script
* feat(api): add OpenTelemetry LGTM stack for local observability
Wire OTel traces, metrics, and logs through a collector to Tempo,
Prometheus, and Loki, with custom LLM instrumentation, dev compose overlay,
Grafana provisioning, env templates, and development.sh auto-start.
* feat: expand observability, harden dev tooling, and fix expo staging UX
Add business and LLM Prometheus metrics with Grafana dashboards, alerting,
and a metrics verification script. Wire telemetry through adapters and core
LLM paths, and document the local LGTM workflow.
Fix development.sh for macOS bash 3.2, open Grafana and eval-web in Chrome,
and repair eval-web auto-open (unbound EVAL_WEB_BROWSER_SCHEDULED). Merge
internal-eval into the main dev script with improved compose handling.
Require EXPO_PUBLIC_* at build time, improve iOS HTTP ATS for staging IPs,
show memoir empty state instead of load errors when no chapters exist, and
add jest env setup plus chapter list response normalization.
* chore: enable Grafana Assistant Cursor plugin
* fix: memoir empty state and repair withdrawn 0020_chapters_book_id stamp
Show empty memoir UI when the chapter list succeeds with no items; treat auth/404 as non-fatal. Extend alembic revision repair so local dev DBs stamped with the removed 0020_chapters_book_id migration can roll back and upgrade to 0019.
---------
Co-authored-by: Kevin <kevin@brighteng.org>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 15:14:13 +08:00
|
|
|
|
- **可观测性**: 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
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-22 13:44:50 +08:00
|
|
|
|
在 `config/*.toml` 的 `[deploy]` 中配置 `otel_enabled` 与 `otel_exporter_otlp_endpoint`;采样策略等细项见 `[otel]` section 与 [`docs/observability.md`](docs/observability.md)。
|
2026-01-18 15:58:05 +08:00
|
|
|
|
|
|
|
|
|
|
## 项目结构
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
api/
|
2026-03-18 17:18:23 +08:00
|
|
|
|
├── main.py # 应用入口(uvicorn 启动)
|
|
|
|
|
|
├── app/ # 应用主包
|
|
|
|
|
|
│ ├── main.py # FastAPI 应用定义
|
|
|
|
|
|
│ ├── core/ # 核心基础设施
|
2026-05-22 13:44:50 +08:00
|
|
|
|
│ │ ├── config.py # secrets / bootstrap(Settings + facade)
|
|
|
|
|
|
│ │ ├── app_config*.py # TOML 加载与 AppConfig
|
|
|
|
|
|
│ │ ├── runtime_constants.py # re-export config/*.toml runtime sections
|
2026-03-18 17:18:23 +08:00
|
|
|
|
│ │ ├── 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
|
2026-01-26 11:54:03 +08:00
|
|
|
|
├── tasks/ # Celery 后台任务
|
|
|
|
|
|
│ └── memoir_tasks.py # 回忆录处理任务
|
|
|
|
|
|
└── docs/ # 详细文档
|
2026-01-18 15:58:05 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 环境配置
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 安装依赖
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
cd api
|
|
|
|
|
|
pip install -r requirements.txt
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-22 13:44:50 +08:00
|
|
|
|
### 2. 配置(TOML + .env)
|
2026-01-18 15:58:05 +08:00
|
|
|
|
|
2026-05-22 13:44:50 +08:00
|
|
|
|
配置分两层 SSOT,详见 **[docs/configuration.md](docs/configuration.md)**。
|
2026-01-18 15:58:05 +08:00
|
|
|
|
|
2026-05-22 13:44:50 +08:00
|
|
|
|
| 层 | 来源 | 内容 |
|
|
|
|
|
|
|----|------|------|
|
|
|
|
|
|
| **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 等 |
|
2026-01-21 23:21:36 +01:00
|
|
|
|
|
2026-05-22 13:44:50 +08:00
|
|
|
|
本地开发:`.env.development`(密钥)+ `config/development.toml`(行为);`development.sh` 将前者同步为 `.env`。预发/生产:`.env.staging` / `.env.production` + 对应 `config/*.toml`。
|
2026-01-18 15:58:05 +08:00
|
|
|
|
|
2026-05-22 13:44:50 +08:00
|
|
|
|
最小 `.env` 示例:
|
2026-01-18 15:58:05 +08:00
|
|
|
|
|
2026-05-22 13:44:50 +08:00
|
|
|
|
```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=...
|
2026-01-18 15:58:05 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-22 13:44:50 +08:00
|
|
|
|
**腾讯云**:凭证仍在 env(`TENCENT_SECRET_ID/KEY`);短信模板 ID、COS 桶名等在 `config/*.toml` 的 `[deploy]` section。
|
2026-01-18 15:58:05 +08:00
|
|
|
|
|
2026-05-22 13:44:50 +08:00
|
|
|
|
业务代码读取 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` |
|
2026-01-18 15:58:05 +08:00
|
|
|
|
|
2026-03-18 17:18:23 +08:00
|
|
|
|
### 3. 数据库迁移
|
2026-01-18 15:58:05 +08:00
|
|
|
|
|
2026-05-19 16:40:45 +08:00
|
|
|
|
数据库 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` 补列等)。
|
2026-01-18 15:58:05 +08:00
|
|
|
|
|
2026-03-18 17:18:23 +08:00
|
|
|
|
```bash
|
|
|
|
|
|
cd api
|
|
|
|
|
|
uv run alembic upgrade head
|
2026-05-19 16:40:45 +08:00
|
|
|
|
uv run pytest tests/test_alembic_migration_policy.py -q
|
2026-01-18 15:58:05 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-19 16:40:45 +08:00
|
|
|
|
若库中仍为已撤回的 `0020_*` revision,部署前先执行 `uv run python scripts/repair_alembic_version_after_withdrawn_0020.py`(见上文文档)。
|
|
|
|
|
|
|
2026-01-21 23:06:47 +01:00
|
|
|
|
## 快速启动
|
2026-01-18 15:58:05 +08:00
|
|
|
|
|
2026-01-21 23:06:47 +01:00
|
|
|
|
### 本地开发
|
2026-01-18 15:58:05 +08:00
|
|
|
|
|
2026-03-09 15:30:18 +08:00
|
|
|
|
推荐使用一键脚本(会自动启动 PostgreSQL/Redis、检查 `.venv`、安装依赖并拉起 FastAPI + Celery):
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
cd api
|
|
|
|
|
|
./dev-up.sh
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
可选环境变量:
|
|
|
|
|
|
- `SKIP_INSTALL=1`:跳过依赖安装
|
|
|
|
|
|
- `API_HOST` / `API_PORT`:覆盖 API 启动地址和端口
|
|
|
|
|
|
- `CELERY_POOL`:覆盖 Celery 池类型(macOS 推荐 `solo`)
|
|
|
|
|
|
|
|
|
|
|
|
也可以使用手动方式:
|
|
|
|
|
|
|
2026-01-18 15:58:05 +08:00
|
|
|
|
```bash
|
2026-01-21 23:06:47 +01:00
|
|
|
|
cd api
|
|
|
|
|
|
|
2026-01-21 23:21:36 +01:00
|
|
|
|
# 1. 启动 PostgreSQL + Redis
|
2026-03-25 17:40:04 +08:00
|
|
|
|
docker compose -f docker-compose.dev.yml up -d
|
2026-01-21 23:06:47 +01:00
|
|
|
|
|
|
|
|
|
|
# 2. 安装依赖
|
|
|
|
|
|
pip install -r requirements.txt
|
|
|
|
|
|
|
2026-05-08 17:28:31 +08:00
|
|
|
|
# 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
|
2026-01-21 23:21:36 +01:00
|
|
|
|
|
|
|
|
|
|
# 4. 启动 API(终端 1)
|
2026-01-18 15:58:05 +08:00
|
|
|
|
uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
2026-01-21 23:06:47 +01:00
|
|
|
|
|
2026-01-21 23:21:36 +01:00
|
|
|
|
# 5. 启动 Celery Worker(终端 2)
|
feat(eval): memoir A/B chapter judging and eval-web parity with dialogue
- Judge baseline excerpt and library chapter separately; build_memoir_compare_summary for gate, nine-dim and leaf deltas.
- Memoir SSE chapter payload: baseline_judge, compare_summary, baseline_judge_error.
- MemoirJudgeOutput: loose score coercion and post-validate clamp; memoir judge prompt caps from settings.
- app-eval-web: two-column MemoirScoreCard layout, MemoirCompareSummary, chapter blocks and CSS.
- Add memoir_compare_summary, log_events, celery_log_context, memoir_pipeline_progress; tests and migration 0014.
- Misc: memory/evidence and enrichment paths, task/orchestrator updates, internal-eval docs, env examples.
2026-04-10 10:23:43 +08:00
|
|
|
|
# macOS 使用 solo 池避免 fork 崩溃问题;须同时消费 memory_idle(Memory 富化)
|
|
|
|
|
|
celery -A app.tasks.celery_app worker --loglevel=info --pool=solo -Q celery,memory_idle
|
2026-01-21 23:06:47 +01:00
|
|
|
|
|
|
|
|
|
|
# Linux/生产环境可以使用 prefork 池
|
feat(eval): memoir A/B chapter judging and eval-web parity with dialogue
- Judge baseline excerpt and library chapter separately; build_memoir_compare_summary for gate, nine-dim and leaf deltas.
- Memoir SSE chapter payload: baseline_judge, compare_summary, baseline_judge_error.
- MemoirJudgeOutput: loose score coercion and post-validate clamp; memoir judge prompt caps from settings.
- app-eval-web: two-column MemoirScoreCard layout, MemoirCompareSummary, chapter blocks and CSS.
- Add memoir_compare_summary, log_events, celery_log_context, memoir_pipeline_progress; tests and migration 0014.
- Misc: memory/evidence and enrichment paths, task/orchestrator updates, internal-eval docs, env examples.
2026-04-10 10:23:43 +08:00
|
|
|
|
# celery -A app.tasks.celery_app worker --loglevel=info --concurrency=4 -Q celery,memory_idle
|
2026-01-18 15:58:05 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-21 23:21:36 +01:00
|
|
|
|
### 验证服务
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 检查 PostgreSQL
|
|
|
|
|
|
docker exec life-echo-postgres-dev psql -U postgres -c "SELECT 1"
|
|
|
|
|
|
|
|
|
|
|
|
# 检查 Redis
|
|
|
|
|
|
docker exec life-echo-redis-dev redis-cli ping
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-21 23:06:47 +01:00
|
|
|
|
### 生产部署(一键)
|
2026-01-18 15:58:05 +08:00
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-01-21 23:06:47 +01:00
|
|
|
|
cd api
|
|
|
|
|
|
|
|
|
|
|
|
# 创建生产配置
|
|
|
|
|
|
cp .env .env.prod
|
|
|
|
|
|
# 编辑 .env.prod
|
|
|
|
|
|
|
|
|
|
|
|
# 启动所有服务
|
2026-03-25 17:40:04 +08:00
|
|
|
|
docker compose up -d
|
2026-01-21 23:06:47 +01:00
|
|
|
|
|
|
|
|
|
|
# 查看日志
|
2026-03-25 17:40:04 +08:00
|
|
|
|
docker compose logs -f
|
2026-01-18 15:58:05 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
服务启动后,访问:
|
|
|
|
|
|
- API 文档: http://localhost:8000/docs
|
|
|
|
|
|
- 健康检查: http://localhost:8000/health
|
|
|
|
|
|
|
2026-01-26 11:54:03 +08:00
|
|
|
|
## 📚 详细文档
|
|
|
|
|
|
|
|
|
|
|
|
更多详细文档请参考 [docs/README.md](docs/README.md):
|
|
|
|
|
|
|
|
|
|
|
|
- **[本地开发环境配置](docs/本地开发环境配置.md)** - 开发环境搭建指南
|
|
|
|
|
|
- **[WebSocket 快速测试指南](docs/WebSocket快速测试指南.md)** - WebSocket 快速测试
|
|
|
|
|
|
- **[WebSocket 测试文档](docs/WebSocket测试文档.md)** - WebSocket 详细接口文档
|
|
|
|
|
|
- **[文字交流模式说明](docs/文字交流模式说明.md)** - 文字对话模式功能说明
|
|
|
|
|
|
- **[测试脚本使用说明](docs/测试脚本使用说明.md)** - 自动化测试脚本指南
|
|
|
|
|
|
|
2026-01-18 15:58:05 +08:00
|
|
|
|
## 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",
|
2026-05-22 13:44:50 +08:00
|
|
|
|
"refresh_token": "new-refresh-token-string",
|
2026-01-18 15:58:05 +08:00
|
|
|
|
"token_type": "bearer"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-22 13:44:50 +08:00
|
|
|
|
每次刷新会轮换 refresh token(返回新的 refresh token,旧 token 立即失效)。在 `REFRESH_TOKEN_REUSE_GRACE_SECONDS`(默认 30 秒)窗口内重复使用已轮换的旧 token 视为幂等重试,返回新 access token 与当前 replacement refresh token;grace 窗口外再次使用则吊销该用户全部会话并返回 `REFRESH_TOKEN_REUSE`。
|
|
|
|
|
|
|
2026-01-18 15:58:05 +08:00
|
|
|
|
##### 用户登出
|
|
|
|
|
|
```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}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-22 13:44:50 +08:00
|
|
|
|
### 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]` 中配置逗号分隔的前端域名。
|
|
|
|
|
|
|
2026-01-18 15:58:05 +08:00
|
|
|
|
### 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"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-22 13:44:50 +08:00
|
|
|
|
**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 消息
|
|
|
|
|
|
|
2026-01-18 15:58:05 +08:00
|
|
|
|
## 数据库模型
|
|
|
|
|
|
|
|
|
|
|
|
### 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
|
2026-03-26 12:13:36 +08:00
|
|
|
|
- `user_input_text`: 用户输入正文(语音 ASR 或文字输入;历史列名 `transcript_text`)
|
2026-01-18 15:58:05 +08:00
|
|
|
|
- `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)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 添加新的数据库模型
|
|
|
|
|
|
|
2026-03-18 17:18:23 +08:00
|
|
|
|
1. 在对应 feature 的 `app/features/<feature_name>/models.py` 中定义模型类
|
|
|
|
|
|
2. 继承 `Base`(从 `app.core.db` 导入)
|
|
|
|
|
|
3. 在 `app/main.py` 中 import 该 models 模块以注册到 Base.metadata
|
|
|
|
|
|
4. 运行 Alembic 迁移
|
2026-01-18 15:58:05 +08:00
|
|
|
|
|
|
|
|
|
|
### 添加新的服务
|
|
|
|
|
|
|
2026-03-18 17:18:23 +08:00
|
|
|
|
1. 在 `app/features/<feature_name>/service.py` 中实现业务逻辑
|
|
|
|
|
|
2. 通过 `deps.py` 提供依赖注入
|
|
|
|
|
|
3. 在对应 feature 的 router 中通过 `Depends(get_xxx_service)` 使用
|
2026-01-18 15:58:05 +08:00
|
|
|
|
|
|
|
|
|
|
## 安全注意事项
|
|
|
|
|
|
|
2026-05-22 13:44:50 +08:00
|
|
|
|
1. **CORS 配置**: 本地开发默认可用 `allow_origins=["*"]`(`deploy.api_cors_origins` 留空);生产/staging 必须在 `config/staging.toml` / `config/production.toml` 的 `[deploy]` 中设置逗号分隔前端域名
|
2026-01-18 15:58:05 +08:00
|
|
|
|
2. **API Key 安全**: 确保 `.env` 文件不被提交到版本控制
|
|
|
|
|
|
3. **SECRET_KEY 安全**: 使用强随机字符串作为 JWT 签名密钥,生产环境必须更换
|
|
|
|
|
|
4. **密码安全**: 密码使用 bcrypt 哈希存储,不会以明文形式保存
|
|
|
|
|
|
5. **令牌安全**:
|
|
|
|
|
|
- 访问令牌短期有效(2小时),降低泄露风险
|
2026-05-22 13:44:50 +08:00
|
|
|
|
- 刷新令牌存储在数据库中,支持撤销;每次 `/api/auth/refresh` 会轮换 refresh token
|
|
|
|
|
|
- 已轮换的 refresh token 被再次使用时,服务端吊销全部会话并返回 `REFRESH_TOKEN_REUSE`
|
2026-01-18 15:58:05 +08:00
|
|
|
|
- 令牌过期后必须使用刷新令牌重新获取
|
2026-02-12 13:33:19 +08:00
|
|
|
|
6. **数据库备份**: 定期备份 PostgreSQL 数据库
|
2026-01-18 15:58:05 +08:00
|
|
|
|
7. **错误处理**: 所有 API 都包含适当的错误处理和权限验证
|
|
|
|
|
|
8. **日志记录**: 建议添加日志记录功能以便调试和监控
|
|
|
|
|
|
|
|
|
|
|
|
## 许可证
|
|
|
|
|
|
|
|
|
|
|
|
MIT License
|