Files
life-echo/api/docs/internal-eval.md
Kevin 71fbd39e32 feat(api)!: memory single chain — async MemoryService, strict eval closure
Route all memory ingest/retrieve/enrichment/compaction through async MemoryService.
Remove legacy sync memory implementations (ingest/retrieve/compaction); Celery and
memoir Phase2 call asyncio.run into MemoryService-backed helpers.

Memoir Phase1 batch ingest uses MemoryService.ingest_transcripts_batch; drop chapters.
evidence_bundle_json mirror (Alembic 0015). Evaluation uses snapshot/link-only bundles;
raise EvidenceClosureMissing instead of partial/fallback lineage tiers.

Split memoir state into NarrativeCoverageState and InterviewControlState; delete the
_interview_meta_store adapter layer. Remove rolling-query and recent-fact fallback
settings from config and evidence assembly.

Update judges, docs, tests, and PlaygroundPage alignment.

Made-with: Cursor
2026-04-30 14:11:50 +08:00

152 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 内部回归评测平台
与主 API`app/main.py`)隔离进程部署,避免评测候选链路透出给消费者 App。
## 启动
**推荐一条命令**`internal-eval.sh` 实际调用 `development.sh`,在同一进程树里启动主站 `main:app`**8000**)、**一份** Celery、内部评测 `internal_app`(默认 **8001**)以及 `app-eval-web`(默认 **5174**)。不需要再并行执行两份启动脚本。
| | 单一命令 `./internal-eval.sh` |
|---|-------------------------------|
| HTTP | 主站 **8000** + internal **8001** |
| Celery | 仅 **一个** worker与主站共用队列 |
| 前端 | 默认启动 `app-eval-web``START_EVAL_WEB=0` 可关) |
**主站 + Celery 已在其他终端**`./development.sh` 跑起来了,只在同一台机器上多开评测 HTTP 与前端、且 **不再起第二份 Worker**
```bash
cd api
# 确保 .env.development / .env 含 INTERNAL_EVAL_API_KEY:8000 已被主站监听
SKIP_INFRA=1 SKIP_INSTALL=1 EVAL_ATTACH_ONLY=1 ./internal-eval.sh
```
兼容旧写法:`SKIP_CELERY=1` 会映射为 `EVAL_ATTACH_ONLY=1`(仍要求 **8000 已在监听**)。
仅主业务、不要评测台时照旧:`./development.sh`(不设置 `LIFE_ECHO_WITH_INTERNAL_EVAL`)。
若你只需要 **8001**、刻意不启主站 **8000**,请用下文「手动 uvicorn」配合既有 Celery不要用 `./internal-eval.sh`(一键脚本会顺带拉起主站)。
**默认会起 `app-eval-web`,并用 Vite `--open` 尝试打开浏览器**`http://127.0.0.1:5174/`)。不要前端时设 `START_EVAL_WEB=0`;只要前端但不要弹窗时设 `OPEN_EVAL_WEB=0`
数据库与主服务共用;需配置环境变量后启动专用进程:
```bash
cd api
export INTERNAL_EVAL_API_KEY='your-long-random-secret'
export INTERNAL_EVAL_ENABLE_DOCS=1 # 可选,开 /docs
# 评测评审Playground / Memoir 手动的对话与成稿打分)
# 智谱:默认 EVAL_JUDGE_API_KEY否则回退 ZHIPU_API_KEY
export EVAL_JUDGE_API_KEY='...' # 可选
export EVAL_JUDGE_MODEL='glm-5'
# DeepSeekAPI 模型名 deepseek-reasoner 即 R1与访谈主链路密钥一致独立默认模型名
export DEEPSEEK_API_KEY='...' # 选用 DeepSeek 评审时必填(或回退 LLM_API_KEY
export EVAL_JUDGE_DEEPSEEK_MODEL='deepseek-reasoner' # 可选
export EVAL_JUDGE_DEEPSEEK_CONTEXT_WINDOW_TOKENS='64000' # 可选;用于 transcript 截断,避免按 GLM 200K 估长
uv run uvicorn app.internal_main:internal_app --host 0.0.0.0 --port 8001
```
Celery worker 与主站共用(`celery_app``include` 回忆录等任务;**不再**包含已下线的 `evaluation_tasks` 实验批量跑批)。需 Phase1 / 叙事推进时请启动 worker
```bash
uv run celery -A app.tasks.celery_app worker -l info -Q celery,memory_idle
```
## 前端(`app-eval-web`
```bash
cd app-eval-web
npm install
VITE_EVAL_API_BASE=http://127.0.0.1:8001 VITE_EVAL_API_KEY=与上同 npm run dev
```
或使用仓库根目录 `npm run eval-web`(需本地已 `npm install``app-eval-web`)。
**部署约定**`app-eval-web` **不提供**生产/预发 Docker 镜像,也 **不**纳入 Staging/Production 的 `api/docker-compose.yml` 部署。评测 UI 仅在开发阶段于本机通过 Vite `npm run dev`(或 `./internal-eval.sh` 拉起)使用;不要在预发/生产环境编译 `dist/` 或挂载独立容器对外提供服务。
## 流式评审
`POST /internal/api/evaluation/judge/conversation-stream` 使用 **fetch 读取 SSE**chunk请求头携带 `X-Internal-Eval-Key` 即可;不要求浏览器 `EventSource`。Body 可选 **`judge_provider`**`zhipu`(默认)| `deepseek`,以及 **`judge_model`**(空则用该供应商环境默认)。首轮 `meta` 事件会回显 `judge_provider` / `judge_model`
新增事件:
- `compare_summary`:结构化 A/B 对比摘要,包含 `group_deltas`、关键回落维度、是否出现重复盘问风险,以及 transcript 截断提示。
- `compare_delta`:原有自由文案流,适合人读;不替代结构化结论。
## 评测 Web`app-eval-web`
- **Playground · 分步测评**:选用户导出 MD 为基线 → `eval-sandbox` + 逐轮 `replay/conversation`**`skip_memoir: true`** 时只做对话)→ **`memoir-submit`** 再可选轮询 **`memoir-phase1-ready`** → 跳转 **Memoir / Stories** 看成稿;支持 **智谱 / DeepSeek R1** 对话流式评分(工具栏「评审模型」)。
- **Memoir**:按 `user_id` 拉库中章节快照与基线对照评审。
- **Stories**:故事列表与评审。
- **实机联调**(侧栏「实机联调」,哈希路由 `#live`):用与 **消费者主站**相同的 REST / WebSocket 测核心聊天与回忆录。需主站 `main:app` 已启动(默认 **:8000**),并在主站环境启用 Mock 登录(见下)。开发时 `app-eval-web`**`/api`** 与 **`/ws`** 代理到主站(`VITE_MAIN_API_PROXY_TARGET`,默认 `http://127.0.0.1:8000`);也可设 `VITE_MAIN_API_BASE` 直连完整主站 URL。
### Mock 登录(仅非 production
在主站 `.env` / `.env.development` 中设置 **`MOCK_SMS_LOGIN_ENABLED=1`**(或 `true`)。`APP_ENV=production`**`POST /api/auth/mock/sms-login` 始终返回 404**。请求体:`phone`11 位)、`agreed_to_terms: true`,可选 `nickname`(新用户);响应与正式短信登录相同(`access_token` + `refresh_token`)。**切勿在生产环境开启。**
## 真实链路透传回放(与 App 一致)
| 方法 | 路径 | 说明 |
|------|------|------|
| `POST` | `/internal/api/evaluation/sessions/eval-sandbox` | 无 body新建**临时用户**`eval_` 伪手机号)+ 空白 `conversation_id` |
| `POST` | `/internal/api/evaluation/sessions/replay-bootstrap` | body`{ "user_id" }`,在已有用户下返回新 `conversation_id` |
| `POST` | `/internal/api/evaluation/replay/conversation` | body`conversation_id``fixture_filename` **或** `user_utterances`;可选 **`skip_memoir`**(默认 false为 true 时不 `queue_message`、且不会仅因 `flush_memoir_after``flush_pending`)、`flush_memoir_after`(默认 true`skip_tts`(默认 true。响应含 `segment_ids`(本批创建的用户 segment |
| `POST` | `/internal/api/evaluation/sessions/{conversation_id}/memoir-submit` | 无 body收集本会话内 `topic_category IS NULL``processed` 为 false 的 segment调用 `flush_pending(user_id, extra_segment_ids=…)`;返回 `segment_ids``celery_task_id` |
| `GET` | `/internal/api/evaluation/sessions/{conversation_id}/memoir-phase1-ready` | query`segment_ids` 可重复。所列 segment 均已写入 `topic_category``ready: true` |
**默认(`skip_memoir: false`**:每轮仍相当于主站路径:`create_user_segment``process_user_message``background_runner.queue_message`;末尾可 `flush_pending`
**Playground 分步(`skip_memoir: true` + `flush_memoir_after: false`**:只做 `create_user_segment``process_user_message`**不**入回忆录队列;对话结束后再调 **`memoir-submit`** 统一 flush。
- **TTS**:回放默认 `skip_tts: true`
- **Celery**Phase1 / 叙事仍依赖 worker仅起 HTTP 未起 worker 时,`memoir-submit` 后任务会堆积。
- **Playground**:第 2 步可选轮询 `memoir-phase1-ready`(前端默认最长约 **10 分钟**`VITE_MEMOIR_PHASE1_WAIT_MAX_MS` 可覆盖)。中断时本地草稿可「继续未完成重放」接续同一 `conversation_id`(仅对话进度;旧版「每轮等待 Phase1」草稿会被跳过并提示改走 `memoir-submit`)。
## A/B 发布口径(追平 A / 超过 A
Playground 的结构化摘要里,后端会给出一份 `gate`
- `regressed`:仍明显落后 A`context_memory` / `emotion_carry` 等关键项明显回落,或再次出现“重复盘问 / 忽略已答信息”。
- `parity`:总分基本追平 A且关键维度未明显退步。
- `surpass`:总分显著高于 A同时 `context_memory`、人物建模等关键项不退步,且未出现重复盘问风险。
建议发布前不要只看单个 case
1. 先固定一组 **黄金样本 fixture**(覆盖童年、求学、职业、家庭、价值观,以及长对话样本)。
2. 每次 prompt / state / anti-repeat 改动后,用同一组 fixture 全量重放。
3. 要求整组样本里:
- 不得出现 `regressed` 的受保护样本;
- 大多数样本至少达到 `parity`
- 目标样本才以 `surpass` 作为升级完成标志。
如果 `compare_summary.truncation.*_truncated_for_compare = true`,说明 A/B 对比所用 transcript 仍超过合计预算(`compare_cap_total_chars`)后做了裁切;单侧较短时会先占满「合计字符池」再裁较长一侧尾部。若仍截断,可略调高 `EVAL_JUDGE_CONTEXT_WINDOW_TOKENS` / 降低 `EVAL_JUDGE_APPROX_TOKENS_PER_CHAR`,或见 `EVAL_JUDGE_MAX_COMPARE_TRANSCRIPT_CHARS_EACH`。结论仍应结合逐轮评分与关键样本人工复核。
## 手动 GLM-5不写 `eval_runs` 表)
| 方法 | 路径 | 说明 |
|------|------|------|
| `POST` | `/internal/api/evaluation/judge/conversation` | body`{ "conversation_id" }`,返回轮次分 + 全文对话分 |
| `POST` | `/internal/api/evaluation/judge/memoir-chapters` | body`{ "user_id", "baseline_sections"? }`Chapter/Story 分项 |
| `GET` | `/internal/api/evaluation/users/{user_id}/memoir-snapshot` | 只读章节与故事正文快照 |
## 回忆录评审可追溯证据闭包lineage
**严格闭包口径、synthetic vs library 分表** 见同目录 **[traceable-memoir-lineage.md](./traceable-memoir-lineage.md)**。
手动 `/judge/memoir-chapters` 与历史自动化 run 的 `judge_bundle_json` 已按 **artifact 绑定证据** 组 prompt而不再默认拼接「最近 N 个会话全文」:
- **`evidence_trace`**bundle 完整 JSONsegment / conversation / chunk / fact / timeline / summary、`notes` 等)。内审计一般够用;若需按类型深链 UI 再排期。
- **`format_meta`**`truncated``dropped_sections``included_token_estimate`区分「prompt 裁掉」与「库中无 lineage」。
- **生产侧**:叙事流水线在每次 Story 写入后覆盖 `story_evidence_links`,并在当前 `story_versions.prompt_meta.memoir_retrieval` 写入本轮检索到的稳定 id`story_pipeline_sync._persist_story_lineage_sync`)。
- **章节快照 Phase C**`chapter_evidence_snapshots` + `chapter_evidence_links``chapters.current_evidence_snapshot_id` 指向当前版本;评测只读当前快照。刷新见 `memoir/chapter_evidence_snapshot.py`
- **对话 memory tracePhase 八)**:访谈路由下,`conversation_messages.memory_retrieval_trace_json` 在配对 **AI** 消息上写入本轮 `HybridRetriever` 命中的 chunk/fact/timeline/summary/story 等 id`memory/retrieval_trace.py`)。
历史数据缺 link / snapshot 时不可评测,需先通过正式流水线重新物化。
## Fixture 详情扩展
`GET /internal/api/evaluation/fixtures/user-exports/{filename}` 在原有 `turns` 外增加:
- `source_user_id`:导出抬头中的 User ID
- `memoir_sections``## 回忆录章节(生成正文)` 下按标题切分的基线正文(已去掉 `{{IMAGE:...}}` 占位)