17 KiB
17 KiB
report_bioflow_dev2 —— P1 生信服务地基(开发 agent A2)
任务书:docs/team/brief_bioflow_dev2_p1.md 工作分支:feature/bioflow-conversation-orchestration(保持,不切换) 纪律:不 commit/push/部署;不碰生产服务(8090/8091);服务层零 pydantic;零硬编码
1. 交付概览
| 模块 | 说明 |
|---|---|
| bioflow_service/(新增,独立部署树) | 生信服务 8098:FastAPI + 独立 SQLite + docker 执行器 + 状态机 + 规划器 |
| sdk/app/routers/bioflow_proxy.py(新增) | BFF 流式反代 + 报告登记端点 |
| sdk/app/main.py(2 行改动) | BIOFLOW_ENABLED 开关注册反代路由 |
| sdk/app/config.py(2 键) | bioflow_enabled(默认 false)/ bioflow_service_url |
| docs/team/report_bioflow_dev2.md | 本报告 |
2. 模块清单(bioflow_service/app/)
明文白名单(编译管线保留 .py)
config.py—— BIOFLOW_ 前缀配置(pydantic-settings):image_map_path(仓库内 bioflow_kernel/stage_image_map.json,按 file 推导 repo 根)、data_root/ jobs_dir/db_path/archives_dir/flow_docs_dir(全部可覆盖,无写死宿主路径)、 concurrency=3、stage_timeout_sec、host_uid_env=BIOFLOW_HOST_UID(默认服务进程 os.getuid() 推导)、container_data_dir=/bioflow-data、upload 白名单、DOCGEN_LLM_* 沿用 docgen 直连模式;bba_env 属性从配置推导 BBA_PFAM_DIR/BBA_RAG_MAPPING_FILE。schemas.py—— pydantic 请求/响应模型(ChatStreamRequest/JobCreateRequest/ StageConfirmRequest/ArchiveCreateRequest 等 10 个;STAGE/JOB 状态 Literal)。db.py—— 独立 SQLite(WAL)三表 + 幂等迁移(CREATE IF NOT EXISTS + PRAGMA table_info 增量列):bio_jobs/bio_stages/bio_archives(字段与实施计划 §六对齐); bio_stages UNIQUE(job_id, stage),job 删除级联。main.py—— FastAPI 组装 + lifespan(init_db→init_context→recover→shutdown); health 端点 /api/bioflow/health(db 连通状态字段,FR-BIO-025 探活用)。
服务层(全部 dataclass/dict/stdlib,无 pydantic,Cython 编译)
services/bioflow/kernel.py—— Docker 执行器:读 A1 stage_image_map.json (75 阶段/8 镜像),按阶段→镜像拼 docker run(--user uid/三目录挂载/BIOFLOW_DATA 只读/-e BBA_*/--cpus/--memory/--rm +python -B -m cli.run_{stage},list 无 shell); cpu/memory 白名单回落;超时 terminate→kill;日志落任务 logs/ + 尾 80 行截断; 跨环境 note 透传(编排层 P2 拆分依据)。services/bioflow/runner.py—— 任务状态机:created→await_inputs→running→ paused/done/failed/archived;阶段 waiting→await_files→waiting_confirm→queued→ running→done/failed/skipped;输入检查(FR-BIO-005)→确认门(FR-BIO-008,confirm_confirmed旁路防二次确认门)→并发信号量(BIOFLOW_CONCURRENCY 默认 3, FIFO 排队)→docker→manifest 判成败(no_manifest/manifest_failed/timeout/ exit_code 四因);pause 停阶段边界、resume 不跳过确认、rerun/skip 状态校验; 重启恢复 recover():running 阶段按 manifest 判成败(有 success manifest→done 否则重跑)、done 不重跑(attempt 不变)、archived 跳过。services/bioflow/catalog.py—— 阶段目录(映射表驱动 + STAGE_INFO/STAGE_DEPENDENCIES 静态快照,不运行时反射);73 阶段(真实表 75 去重复校验)、基因家族链 21 阶段、 手写参数 schema(identify 11 参数含 default/source/help,source 标注 contract/user/runtime 供表单默认值来源可见);validate_chain 未知阶段拒绝 + auto_fill_prerequisites 前置补全(拓扑排序依赖在前)。services/bioflow/planner.py—— 路径规划:复用句式(「用 XXX 存档任务的方法…」) → archive 直接装载;否则知识库检索(vstore/flow.py FlowStore 本地实例混合检索, 失败降级 llm_only)+ LLM 候选(DOCGEN_LLM_* 直连,JSON 模式 + 修复重试)→ catalog 硬校验(未知阶段拒绝、前置补全);PLAN_LLM_TIMEOUT/PLAN_UNAVAILABLE 降级内置兜底链。services/bioflow/manifests.py—— manifest 解析校验(manifest_v1.schema 必需字段; 定位 final/{prefix}.{stage}.manifest.json,无兜底防跨阶段误配);跨阶段输入定位 (artifacts full_path 解析 + 存在性检查)。services/bioflow/events.py—— 事件总线(12 类事件全集 + job_recovered): 进程内 pub/sub(SSE 订阅)+ 内存环形缓冲 + events/{job_id}.jsonl 持久 (重启回放)+ snapshot 首帧 + replay(after_seq) 断线回放。services/bioflow/llm.py—— LLM 直连(docgen 同款:指数退避 + JSON 修复 + LLMTimeout/LLMUnavailable 分型)。services/bioflow/params.py—— 参数预填(契约文档参数解析 + BBA_* 默认值标注 + 存档参数预填)。services/bioflow/reports.py—— 报告登记(报告 HTML 定位/fig:// 图清单提取/ 登记载荷(job_id/标题/md/图清单)+ 内部 HTTP 调 BFF 反代端点,BFF 不可达降级)。services/bioflow/archive.py—— 存档打包(final/结果+logs/过程+events.jsonl 过程记录+input 原始文件;include_intermediate 开关含中间文件;zip 下载)。services/bioflow/store.py—— 三表 CRUD(upsert/list/get/delete;归档精确优先 模糊匹配、reused_count 计数)。services/bioflow/intent.py—— 意图分流(cases_bioflow_intent.md 冻结词典同表: export→edit→docgen→附件 docgen→bioflow_qa(FLOW_QA 命中即拦)→bioflow (动词+家族语境词/分析对象)→qa 漏判安全;复用方法名提取 3-20 字符;2000 字符截断判定)。
路由
routers/bioflow.py—— health/catalog(?stage=/?chain=gene_family)/ chat/stream(SSE:意图分流→plan_ready 候选→心跳;plan seq 编码 data 载荷直出)/ ask(知识库引用块:source/title_path/path/cite/line/text)。routers/biojobs.py—— jobs 全套(POST 201/列表 rowid 倒序分页过滤/详情/删除 running 409);files(multipart 流式逐块写盘 + 扩展名白名单 + 大小上限 413 + server_path 登记限 data_root/任务目录白名单 403);stages confirm(参数校验 未知 422)/rerun(带参数→waiting_confirm)/skip/pause/resume/manifest(manifest 对象 + 日志尾 80 行);events SSE(snapshot 首帧 + 回放 + 实时订阅 + 任务终态 关闭 TC-BIO-API-081);report revise(最小接口:无图目标 422/绘图阶段定位)。routers/bioarchives.py—— archives 全套(存档 201 重名 409/列表/详情/删除 磁盘+行/run 复用/按名匹配 404 ARCHIVE_NOT_FOUND/download zip/report HTML); reports/register(归一化 + 图清单回填,BFF 反代落 doc_artifacts)。
构建与运维
build.sh—— 复制 tooling/compile/compile_sdk.sh 改白名单(明文:main.py/ config.py/schemas.py/db.py + __init__.py 规则;schemas= pydantic cyfunction 教训、 db= 断流崩溃根治教训);16 模块 Cython+GCC 全过(实测 /tmp 输出 16 .so, 编译树 import OK)。restart.sh—— 8098 重启(零硬编码:路径脚本位置推导 + 环境变量覆盖)。tests/—— pytest(-m bioflow_unit 127 用例 / -m bioflow_api 39 用例)。
3. 自测结果(2026-09-06,服务器 venv 3.12.13)
| 命令 | 结果 |
|---|---|
python -m pytest tests/ -q |
166 passed(15.7s,0 warnings) |
python -m pytest tests/ -q -m bioflow_unit |
127 passed |
python -m pytest tests/ -q -m bioflow_api |
39 passed |
| build.sh → /tmp 编译 + 编译树 import | 16 模块 .so,import OK |
| BFF BIOFLOW_ENABLED=false/true 双态 import | 均 OK(反代路由 2 条) |
| 真实 stage_image_map.json 加载 | 75 阶段/8 镜像/identify 11 参数/命令拼装正确 |
| /home/xzq 硬编码扫描(app+tests) | 0 处 |
全部 mock docker/LLM/vstore(tmpdir + fake 执行器),不连任何生产服务 (8090/8091/8094/8098 从未探测)。
4. 已知限制(交 TL 裁决)
- SSE 传输层测试法:本环境 fastapi TestClient 的 sync stream 包装与 httpx ASGITransport 对永不结束的 SSE 流均存在首帧不达/整流缓冲问题(dbg3/dbg6 最小 复现实证),SSE 用例改为端点直调迭代 body_iterator + 模块级共享 event loop (anyio portal 绑定首个 loop,跨 loop 死锁实证)。生产部署(uvicorn 真实网络 SSE)不受影响;如 TL 要求 TestClient 原生流测试,需另行排查 fastapi/anyio 版本组合。
- catalog 阶段数口径:任务书口径 73;A1 交付 stage_image_map.json 实为 75 阶段(含 module=null 的 synteny_plot_revise 与 executable=false 的 msa/ enrichment)。kernel 对 module=null 阶段拼命令前置拦截(UnknownStageError), catalog 全量返回 75。以 A1 交付物为准,口径差异记录备查。
- gene_family 链为 21 阶段(A1 family_chain 实测 21,任务书口径约 18); 以 stage_image_map.json family_chain 字段为唯一事实源。
- FlowStore 生产数据未入库:vstore/data/flow_docs 目录当前不存在(A4 自测 在 tmpdir 完成;真实语料 106 文件/724 块需 TL 部署时跑 vstore/scripts/ingest_flow_docs.py 入库)。planner 已按本地实例对接(加载成功 零块时检索空 → LLM 候选照常,降级链完整);生产前入库即可获得 KB 增强 (llm_rag source)。
- chat/stream 是规划入口:plan_ready 后保持心跳连接,任务创建走 POST /jobs(P1 契约);会话级任务事件经 /jobs/{id}/events 订阅。若 TL 要求 chat/stream 单流贯穿任务事件(A3 前端形态),需 P2 加会话级订阅联动。
- 报告登记 BFF 侧 doc_artifacts 落库:bioflow_proxy.register_report 字段 (job_id/session_id/标题/md_path/图清单)已按约定实现;图清单字段当前存于 请求体(doc_artifacts 表无 figures 列,如需持久化图清单需 TL 裁决加列——已 在 payload 透传中保留)。与 A3 report 模板的 fig:// 渲染协调点:报告 HTML 内 fig://id 引用清单由生信服务 reports.extract_figure_refs 提供。
- 意图词典判定为启发式快照:与 cases_bioflow_intent.md 冻结表逐条对齐 (40 用例表全过);家族名语境词规则(「SPL 会议」不命中)为词典实现注意第 1/4 条的工程化解读,最终同表双跑一致性以 TC-BIO-INTENT-047(L2 用例)验收。
- 事件持久化目录:events/{job_id}.jsonl 与任务目录分离(bioflow_data/ jobs/events/);归档时打包进档案根。任务删除不清理事件文件(留审计), 如需一并删除交 TL 裁决。
- 镜像 tag 后缀:docker_image_tag_suffix 默认 "dev"(A1 交付物 :dev); 发布流水线(P2 FR-BIO-032 compile_bioflow.sh)需按终态 tag 覆盖环境变量。
- pytest.ini python_files 陷阱:本仓库 pytest 9.1.1 下
python_files = ["test_*.py"](列表形式)会导致目录级收集 0 用例(单文件显式传参正常), 已用字符串形式python_files = test_*.py规避并实证;写报告备案防踩。
5. 交付物清单
| 文件 | 说明 |
|---|---|
| bioflow_service/app/{config,schemas,db,main,context}.py | 应用骨架(4 明文 + context) |
| bioflow_service/app/services/bioflow/*.py | 服务层 12 模块(零 pydantic) |
| bioflow_service/app/routers/{bioflow,biojobs,bioarchives}.py | 端点三层 |
| bioflow_service/build.sh / restart.sh | 编译白名单管线 / 重启脚本 |
| bioflow_service/pytest.ini | markers + python_files(字符串形式陷阱规避) |
| bioflow_service/tests/*.py | 10 文件 166 用例 |
| sdk/app/routers/bioflow_proxy.py | BFF 流式反代 + 报告登记 |
| sdk/app/main.py(+2 行)/ sdk/app/config.py(+2 键) | BFF 最小改动 |
| docs/team/report_bioflow_dev2.md | 本报告 |
6. R1 缺陷修复记录(T2 轮次测试,2026-09-06)
BUG-R1-01(阻断)BFF 反代大响应体 502 —— 已修复
- 根因:
sdk/app/routers/bioflow_proxy.py_proxy()中upstream.aread()在async with httpx.AsyncClient上下文退出后才调用;生产事件循环(uvloop)下 连接已随 client 关闭,大响应体(全量 catalog 55,905B)首读未缓冲 → httpx.ReadError → 502;小响应体(health 80B 等)因首读已缓冲幸存。 - 修法:client 生命周期手工管理(非 async with)——非 SSE 分支在 client 存活期内
完成
aread(),随后upstream.aclose()+client.aclose();SSE 分支在 stream() 的 finally 中一并关闭两对象(client 活到流消费完毕)。send 失败/aread 失败均 aclose 后返回 503/502 信封。 - 验证:修复前 uvloop_probe 复现 5/5 = 502(含 AREAD EXC 堆栈);修复后同一 脚本 catalog 5/5 = 200(bodylen=55905)、health 200,无异常。
BUG-R1-03(严重)flow_store 未接线 —— 已修复
- 根因:
init_flow_store已实现但init_context()调build_context时不传 flow_store(恒 None)→ 部署态 ask/chat_qa 引用块恒空。L2 测试注入 FakeFlowStore 掩盖了生产链路缺口。 - 修法:
init_context()接线flow_store = init_flow_store(get_settings() .flow_docs_dir_abs)并传入build_context(flow_store=...);缺失/失败照旧降级 None 不阻断启动。 - 新增测试:
tests/test_context_wiring.pytest_init_context_wires_flow_store(临时 flow_docs + 1024 维假嵌入 → 接线后 context.flow_store 非 None 且真实 FlowStore.search 检索非空)test_flow_ask_returns_citations_after_wiring(接线后 ask 端点返回 citations 非空且 degraded=false)test_init_flow_store_missing_dir_degrades(缺失目录降级不阻断)
- E2E 验证:真实生产 flow_docs(724 块)经 init_flow_store 加载成功,检索 "identify 需要什么输入" 4 hits(stage_contract/skill 引用块正常)。
BUG-R1-04(一般)events snapshot 脏结构 —— 已修复
- 根因:
snapshot()对 job_created 载荷 stages 元素直接str(s);runner 实际 发布{stage, status}dict → 快照首帧阶段名变成 dict repr 串(前端进度卡乱串), 直到后续回放事件覆盖。单测用字符串列表 stages 未触发。 - 修法:新增
_stage_key()/_stage_entry()——dict 元素保真透传为干净{stage, status}(取 stage 字段为键),字符串元素照旧兼容;空键跳过。 - 新增测试:
tests/test_events.py::test_snapshot_dict_stages_clean(dict stages 发布 → 快照逐阶段 {stage, status} 干净结构,阶段名非 dict repr 串)。
自测结果(修复后)
| 命令 | 结果 |
|---|---|
python -m pytest tests/ -q |
170 passed(15.8s,0 failed) |
-m bioflow_unit |
131 passed |
-m bioflow_api |
39 passed |
修复前基线 166 passed;新增 4 条回归用例(wiring 3 + dict-stages 1)后 170 全绿。
7. P7 缺陷修复记录(终验,2026-09-06)
BUG-P7-01(严重)上传文件后任务不自动续跑 —— 已修复
- 根因:任务首阶段缺输入停在 await_files 时
run_job已 "await" 返回 (job=await_inputs);上传落盘后无任何重推机制——confirm 端点对 await_files 状态抛 409 STAGE_NOT_CONFIRMABLE。文件引导(FR-BIO-005/006)端到端断裂。 - 修法(TL 指定,按此实现):
runner.py新增retry_after_files(record):仅当 record.status == JOB_AWAIT_INPUTS 时生效(其余状态 no-op 防重复 spawn)——置 JOB_RUNNING、 pause_requested=False、persist_job、spawn(_resume_loop)(_resume_loop 跳过 done 阶段;await_files 阶段经 _run_stage 重新输入检查:文件齐备则 auto_continue 直跑或发 stage_await_confirm)。biojobs.py的 upload_file 与 _register_path 两个成功分支末尾调用await ctx.runner.retry_after_files(record)。
- 新增测试(4 条):
test_runner.py::test_retry_after_files_auto_continue_runs_to_done(① auto_continue 上传后自动续跑到 done,fake executor 记录执行调用)test_runner.py::test_retry_after_files_manual_mode_goes_confirm(②手动 模式上传后进入 waiting_confirm 并收到 stage_await_confirm 事件)test_runner.py::test_retry_after_files_non_await_state_is_noop(③非 await_inputs 状态调用为 no-op:不重复执行、不新增任务)test_api.py::test_upload_resumes_await_inputs_job(端点全链路复现路径: await_inputs → 上传 → 自动续跑 done;api_context 注入 check_inputs runner)
自测结果(修复后)
| 命令 | 结果 |
|---|---|
python -m pytest tests/ -q |
174 passed(16.3s,0 failed) |
-m bioflow_unit |
134 passed |
-m bioflow_api |
40 passed |
修复前基线 170 passed;新增 4 条回归用例后 174 全绿。