Files
jerryandClaude Fable 5 8467dec3a8 fix(bff): 断流崩溃根治——SSE 取消链模块明文化 + 团队文档归档
客户端中途断开流时 Cython 协程 __Pyx_Coroutine_SendEx 断言 abort 整个 BFF。
证据链:3.2.8 崩 db.c / 3.3.0 仍崩(升级无效) / db 明文后迁移 sse_relay.c,
证明取消链上任意 Cython 协程均可触发。最终把 db.py/qa.py/sse_relay.py
加入编译白名单(明文),隔离实例断流探针 10/10 存活,生产同构建实测通过。
代价:3 模块失去编译硬化(无凭据内容),编译模块 30->27。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-03 21:13:56 +08:00

9.7 KiB
Raw Permalink Blame History

DEV1 P1 自测报告(2026-09-03)

完成模块清单

A. vstore 独立包(仓库根 vstore/,端口 8094 独立服务)

文件 职责
pyproject.toml 包定义(fastapi/uvicorn/httpx/cryptography/faiss-cpu/numpy/python-docx/pymupdf/PyYAML/python-multipart)
settings.py VstorePaths dataclass + 限额常量(MAX_ATTACH_COUNT=5、MAX_ATTACH_BYTES=20MB、MAX_ATTACH_CHARS=60000、MAX_SECTIONS=12、MAX_OUTLINE_CHUNKS=3);VSTORE_KEY 必填(缺失抛异常拒绝启动);VSTORE_ROOT/VAULT_PATH/INDEX_PATH(本地或 http(s)://)/EMBED_URL/EMBED_MODEL
crypto.py Fernet 封装 encrypt_bytes/decrypt_bytes;.enc 原子落盘;secure_delete 3 遍覆写
parsing.py extract_docx→{outline,paragraphs,tables,style_hints}、extract_pdf(pymupdf ≤300 页)、extract_text_file(utf-8→gb18030)、outline_to_markdown、outline_to_embedding_chunks(>7500 字符按节切块 ≤3)
embed.py embed_batch:httpx 线程池并发 ≤8 调 ollama /api/embed(url/model 由 settings);单条失败降级零向量不整体失败
backend.py parse_backend:http(s):// → RemoteBackend(HTTP 转译,接口语义与本地一致),其余(含 file://)→ LocalBackend;混合形态拒绝;启动握手(health+空检索)失败抛异常拒绝启动
vault.py 单一主库 add/get_decrypted/delete/search/rebuild_index;明文纪律(原文不出函数末尾 del,磁盘只有 .enc);检索 meta 不含正文(keywords 只用用户 meta)
retrieval.py faiss IndexFlatIP + index.json 单一事实源(行号==json 下标);三档降级(向量→meta 关键词→最新 8 条);向量档分数阈值(零向量查询正确降级);增量写锁;rebuild 临时文件+os.replace 原子切换
api.py FastAPI:/api/vstore/{ingest,search,documents/{id},documents/{id}/style,rebuild-index,health};错误信封 {ok,error:{code,message,degraded}} 与 BFF 一致

B. sdk/app/services/docgen/(BFF 侧服务层)+ services/test_docgen_*.py

文件 职责
settings.py DocgenPaths + 限额常量(与 vstore 对齐);DOCGEN_ROOT
client.py httpx.AsyncClient 客户端;重试(指数退避 0.5/1/2s);ingest/search/get_document/get_style/delete/update_metadata/rebuild/health;失败抛 DocgenUnavailable(router 兜底 UPSTREAM_DOWN)或返回错误结构,不抛未捕获异常
parsing.py extract_docx_style(bytes)→style_hints(python-docx,导出对齐)
llm.py complete_text/complete_json(response_format json_object+一次 JSON 修复重试);3 次指数退避 1.5s/3s/6s(429/5xx/超时);httpx 裸调不用 openai SDK;env 键 DOCGEN_LLM_BASE_URL/API_KEY/MODEL/TIMEOUT=300
intent.py route(text,has_attachments,has_active_artifact)→qa|docgen|edit|export,词典与判定顺序严格按 cases_intent.md;漏判安全(不命中一律 qa);is_docgen_intent(async)供 dev2 护栏
md2docx.py 行级状态机→Word 内建样式(Normal/Heading 1-3/List Bullet/List Number/Table Grid/Quote);中文正文宋体/标题黑体,font.name 与 w:eastAsia 同时设置;表头加粗灰底
md2pdf.py markdown-it-py(commonmark) 出 HTML→PyMuPDF Story→PDF;字体用 /usr/share/fonts 真实文件(NotoSerifCJK 正文/NotoSansCJK 标题,禁用内置 CJK);Story 失败/空文本自动降级自写排版器(TextWriter 逐块排版)
pipeline.py generate_document(description,*,documents,template)→{markdown,md_path}:资料明文拉取(失败占位不阻断)→模版大纲(裁剪≤12 节)→无模版 complete_json(失败重试 1 次→六段式默认)→逐节生成(单节重试 2 次仍失败占位)→humanize 按行保护(标题/表格/列表行原样,只 humanize 正文段)→md 落盘 DOCGEN_ROOT/artifacts/
editor.py apply_edit(md,instruction)→{markdown}(长文档分段改,段失败保留原段);smart_export(md,format,style)→Path
converter.py(新增适配层) docx_from_markdown(markdown,title)→bytes / pdf_from_markdown→bytes(async;dev2 桩实际 import 的签名;内调 md2docx/md2pdf 核心,bytes 不落中间文件)

测试文件与用例数

测试文件 用例数 通过 说明
vstore/tests/test_settings.py 6 6 限额常量/路径/密钥必填
vstore/tests/test_crypto.py 7 7 加解密/落盘原子/secure_delete
vstore/tests/test_parsing.py 12 12 docx/pdf/文本/大纲派生
vstore/tests/test_embed.py 5 5 httpx.Client 注入,不碰真 ollama
vstore/tests/test_backend.py 9 9 三形态解析+远程握手失败拒绝
vstore/tests/test_retrieval.py 12 12 faiss 三档降级/原子 rebuild
vstore/tests/test_vault.py 13 13 加密落盘无明文断言/增删检索
vstore/tests/test_api.py 8 8 FastAPI TestClient
sdk/app/services/test_docgen_intent.py 30+3 33 cases_intent.md 冻结 30 条全过
sdk/app/services/test_docgen_client.py 12 12 MockTransport,不连真 vstore
sdk/app/services/test_docgen_llm.py 8 8 假 httpx:重试/JSON 修复/认证头
sdk/app/services/test_docgen_md2docx.py 5 5 打开验证段落/样式/中文字体
sdk/app/services/test_docgen_md2pdf.py 3 3 tmpdir 冒烟,验证中文可提取
sdk/app/services/test_docgen_pipeline.py 6 6 monkeypatch llm+client 全流程
sdk/app/services/test_docgen_editor.py 6 6 mock LLM
sdk/app/services/test_docgen_parsing.py 2 2 样式提取
sdk/app/services/test_docgen_converter.py 2 2 dev2 桩签名 bytes 产物
合计 145 145 全部通过

自测命令:cd /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new && /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-venv/bin/python -m pytest vstore/tests/ sdk/app/services/test_docgen_*.py -q → 145 passed

与 dev2 桩签名对齐确认(TL 契约通知核对)

已读 sdk/app/routers/docgen.py 桩段并逐函数核对,验证 router 实际 import 的是本包真实模块(非桩):

  • client.py:ingest_bytes(filename, content, *, name, category, tags, description, as_template) / search_documents(query, *, as_template, top_k) / delete_document / update_metadata / rebuild_index + DocgenUnavailable —— 签名一致,均为 async
  • pipeline.generate_document(description, *, documents, template) -> {markdown, md_path} —— 一致;artifact 落库由 dev2 router 负责(pipeline 拿不到 artifact_id,md 先写 DOCGEN_ROOT/artifacts/.md,router 再按 artifact_id 落一次)
  • editor.apply_edit(markdown, instruction) -> {markdown} —— 一致
  • intent.is_docgen_intent(text) —— 已按 dev2 await 方式实现为 async 并返回 bool;route 四分类保留
  • 转换器:dev2 实际 import 的是 services/docgen.converter 的 docx_from_markdown(markdown, title) -> bytes(async,两参),与 TL 通知的 md2docx.py 导出 (markdown, out_path) -> Path 不一致 —— 已双层满足:md2docx/md2pdf 同步核心按 TL 签名提供,新增 converter.py 适配 dev2 桩
  • ingest 返回含 document_id/name/category/tags/description/as_template/enc_path/embed_sha/embed_chunks —— 与 dev2 落库字段对齐

已知限制(TL 已裁决,P1 正式验收通过)

以下为 TL 对报告 1-9 条的裁决结果(全部通过,P1 验收完成):

  1. converter.py 保留(裁决通过):dev2 桩 import 的是 converter 的 bytes 签名,保留适配层正确,不改 dev2。「双层满足」方案即最终形态。
  2. update_metadata 维持现状:BFF doc_library 落库为准;vstore PATCH 端点列入 V1.5 演进(本期不加)。
  3. wps-office 缺失:以 Noto 为实际方案(NotoSerifCJK 正文 / NotoSansCJK 标题),记录为已知限制,不算缺陷。
  4. commonmark preset:维持现状,V1.5 可考虑 linkify-it-py。
  5. 远程后端联测:R1 轮测 DC-CONF-03/04 会真实起受控假远端验证,若暴露问题由 dev1 修复。
  6. startup deprecation:与 BFF 现有风格一致,不改。
  7. vstore library 列表端点:BFF 走 doc_library 表,不需要。
  8. 测试位置:符合先例,通过。
  9. vstore_restart.sh:TL 已创建(deploy/vstore_restart.sh),无需 dev1 处理。

状态:P1 已交付并验收通过,dev1 收工待命;R1 轮测若暴露 vstore/services 侧 bug,TL 指名修复。

R1 修复记录(2026-09-03)

FAILOVER-02 节标题重复(已修复)

  • 问题:pipeline 逐节合并时无脑插入 ## 节标题,LLM 输出首节自带同文本标题时出现重复标题。
  • 修复:新增 _merge_section(section_title, section_md) 辅助函数——输出首行若是标题行(# 1-6 级)且文本与节标题相同(忽略前后空格与标题层级差异),保留 LLM 自带标题不重复插入;否则正常插入 ## 节标题。H1 总标题仍只加一次(主流程不变)。
  • 变更文件:sdk/app/services/docgen/pipeline.py(合并循环改走 _merge_section;written 拼接改用合并后的 block);sdk/app/services/test_docgen_pipeline.py(新增 3 用例)。
  • 新增用例:
    1. test_generate_merge_no_duplicate_title_when_llm_output_has_heading——mock 按节输出自带标题(## 与 ### 两个层级变体),断言每节标题行恰 1 条、节名全文恰 1 次;
    2. test_generate_merge_inserts_title_when_llm_output_has_no_heading——mock 输出不带标题,断言正常插入且恰 1 次;
    3. test_merge_section_helper_rules——_merge_section 单元规则(同文本去重/异文本插入/无标题插入/前后空格容忍)。
  • 自测:pytest sdk/app/services/test_docgen_pipeline.py -q → 9 passed;全量 pytest vstore/tests/ sdk/app/services/test_docgen_*.py -q → 148 passed(无回归)。