* 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>
11 KiB
内部回归评测平台
与主 API(app/main.py)隔离进程部署,避免评测候选链路透出给消费者 App。
启动
推荐一条命令:./development.sh 默认启动主站(8000)、Celery、内部评测 API(默认 7999)、评测 Web(5174);.env 中 OTEL_ENABLED=true 时并起 Grafana 且自动打开浏览器。./internal-eval.sh 仅为兼容转发。
./development.sh(默认) |
|
|---|---|
| HTTP | 主站 8000 + internal 7999 |
| Celery | 仅 一个 worker |
| 评测 UI | open → http://127.0.0.1:5174/(OPEN_EVAL_WEB=0 可关) |
| 可观测性 | Grafana :48300(OPEN_OBSERVABILITY_UI=0 可关) |
若 主站 + Celery 已在其他终端 由 ./development.sh 跑起来了,只在同一台机器上多开评测 HTTP 与前端、且 不再起第二份 Worker:
cd api
# 确保 .env.development / .env 含 INTERNAL_EVAL_API_KEY;:8000 已被主站监听
SKIP_INFRA=1 SKIP_INSTALL=1 EVAL_ATTACH_ONLY=1 ./development.sh
兼容旧写法:SKIP_CELERY=1 会映射为 EVAL_ATTACH_ONLY=1(仍要求 8000 已在监听)。
仅主业务、不要评测台:LIFE_ECHO_WITH_INTERNAL_EVAL=0 ./development.sh。
若只需 7999、不启主站 8000,见下文「手动 uvicorn」;不要用一键脚本。
默认会起 app-eval-web,并用系统浏览器打开评测台(http://127.0.0.1:5174/,与 Grafana 同为 open)。不要前端时设 START_EVAL_WEB=0;只要前端但不要弹窗时设 OPEN_EVAL_WEB=0。
数据库与主服务共用;需配置环境变量后启动专用进程:
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'
# DeepSeek(API 模型名 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:
uv run celery -A app.tasks.celery_app worker -l info -Q celery,memory_idle
前端(app-eval-web)
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:
- 先固定一组 黄金样本 fixture(覆盖童年、求学、职业、家庭、价值观,以及长对话样本)。
- 每次 prompt / state / anti-repeat 改动后,用同一组 fixture 全量重放。
- 要求整组样本里:
- 不得出现
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。
手动 /judge/memoir-chapters 与历史自动化 run 的 judge_bundle_json 已按 artifact 绑定证据 组 prompt,而不再默认拼接「最近 N 个会话全文」:
evidence_trace:bundle 完整 JSON(segment / 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 trace(Phase 八):访谈路由下,
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 IDmemoir_sections:## 回忆录章节(生成正文)下按标题切分的基线正文(已去掉{{IMAGE:...}}占位)