客户端中途断开流时 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>
8.2 KiB
8.2 KiB
任务书:开发 agent-1 —— P1 后端服务层(vstore 独立包 + BFF services/docgen)
前置必读
- docs/docgen_team_context.md(服务器环境事实 + 团队纪律,全部遵守)
- 本任务书 + 模块职责边界
职责范围(只改这些文件,不越界)
A. 新增 vstore 独立包(仓库根 /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new/vstore/):
- pyproject.toml(依赖:fastapi/uvicorn/httpx/cryptography/faiss-cpu/numpy/python-docx/pymupdf/PyYAML,版本对齐 venv 已装)
- vstore/settings.py —— VstorePaths dataclass + 限额常量(MAX_ATTACH_COUNT=5、MAX_ATTACH_BYTES=20MB、MAX_ATTACH_CHARS=60000、MAX_SECTIONS=12、MAX_OUTLINE_CHUNKS=3);env 键:VSTORE_KEY(Fernet key,必填)、VSTORE_ROOT、VSTORE_VAULT_PATH、VSTORE_INDEX_PATH(可覆盖:本地路径或 http(s):// URL)、VSTORE_EMBED_URL(默认 http://127.0.0.1:11434)、VSTORE_EMBED_MODEL(默认 bge-m3:567m)
- vstore/crypto.py —— Fernet 封装 encrypt_bytes/decrypt_bytes;.enc 落盘;secure_delete
- vstore/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)
- vstore/embed.py —— embed_batch(texts):httpx 并发≤8 调 ollama /api/embed(url/model 由 settings)
- vstore/backend.py —— parse_backend(spec)->LocalBackend|RemoteBackend:含 :// 且 scheme http/https → RemoteBackend(vault/index 操作转 HTTP 指向远程根,接口语义与本地一致);其余(含 file://)→ LocalBackend(本机磁盘);启动握手校验(/health + 空检索),失败日志告警并拒绝(抛异常阻止启动)
- vstore/vault.py —— 单一主库:add(file_bytes, filename, meta, as_template)->id(解析→提取→嵌入→加密落盘→索引;原文不出函数,末尾丢弃);get_decrypted(id);delete(id);list/search(meta)
- vstore/retrieval.py —— faiss IndexFlatIP + index.json 单一事实源(行号==json 下标);search(q, top_k, category, as_template) 三档降级(向量→meta 关键词→最新8条);增量写锁 + rebuild 临时索引 os.replace 原子切换
- vstore/api.py —— FastAPI app:POST /api/vstore/ingest(multipart: file+name/category/description/tags/as_template)→{ok,item:{id,name,as_template,embed_chunks}};GET /api/vstore/search?q&top_k&category&as_template→{ok,mode,items[]};GET /api/vstore/documents/{id}(明文全文+outline+meta,仅生成时取);GET /api/vstore/documents/{id}/style(docx 样式提取);DELETE /api/vstore/documents/{id};POST /api/vstore/rebuild-index;GET /api/vstore/health
- vstore/tests/ —— 单元测试:crypto 加解密、vault 加密落盘无明文断言(tmpdir + 假 embed,不碰真 ollama)、retrieval 假 embed、backend 路径解析三形态(裸路径/file:///http(s)://)+ 远程握手失败拒绝、api 端点(FastAPI TestClient) B. 新增 sdk/app/services/docgen/(BFF 侧纯生成/导出,不含存储检索):
- settings.py —— DocgenPaths + 限额常量(与 vstore 对齐)
- client.py —— vstore HTTP 客户端(httpx.AsyncClient,超时/重试;方法 ingest/search/get_document/get_style/delete/rebuild/health;所有失败路径不抛未捕获异常,返回错误结构供上层兜底)
- parsing.py —— BFF 侧样式提取 extract_docx_style(bytes)->style_hints(python-docx,供导出对齐)
- llm.py —— complete_text(system,user,*,temperature=0.3,max_tokens=8000)->str;complete_json(...)(response_format json_object + 一次 JSON 修复重试);3 次指数退避 1.5s/3s/6s(429/5xx/超时);httpx 裸调(不用 openai SDK——阻塞事件循环);env 键 DOCGEN_LLM_BASE_URL/DOCGEN_LLM_API_KEY/DOCGEN_LLM_MODEL/DOCGEN_LLM_TIMEOUT=300
- intent.py —— 服务端意图路由 route(text, has_attachments, has_active_artifact)->qa|docgen|edit|export。规则词典法:漏判安全(不命中一律 qa)。优先级:导出词(导出/转成/下载+格式词 pdf/docx/word)→ 编辑词(改/加/删/润色+当前有活动文档)→ 写文档意图(写类动词:写/拟/起草/生成/撰写/帮我写/给我写 + 文档类型词词典 + 量词 一篇/一份/一个/一则 等)→ 附件信号(has_attachments + 写类动词)→ qa。文档类型词:论文/报告/通知/通告/公告/请示/批复/函/申请书/申报书/标书/合同/协议/方案/计划/总结/汇报/讲话稿/致辞/证明/纪要/简报/材料/说明书/指南/申请书等。词典与判定规则要和前端一致(同一条用例表两边测)
- md2docx.py —— 行级状态机→Word 内建样式(Normal/Heading 1-3/List Bullet/List Number/Table Grid/Quote);中文:正文宋体/标题黑体,font.name 与 w:eastAsia 同时设置;表格表头加粗灰底
- md2pdf.py —— markdown-it-py 出 HTML → PyMuPDF Story→PDF;字体用 /usr/share/fonts 真实文件(NotoSansCJK/NotoSerifCJK/wps-office),禁用内置 CJK;若 Story 路线中文异常,降级自写排版器(标题/段落/表格画线)
- pipeline.py —— run_generate(db, description, library_ids, template_id)->dict:经 client 取明文(vstore 解密)→ 选模版(指定>检索 as_template=true>跳过)→ 大纲(模版 outline 裁剪≤12 节;无模版 complete_json 出 {title,sections[]},失败重试1次再失败六段式默认)→ 逐节生成合并(单节失败重试2次仍失败占位)→ humanize 按行保护(标题/表格/列表行原样,只 humanize 正文段;humanize 现成函数见 sdk/app/services/,只调用不修改)→ md+docx 落盘 → 写 doc_artifacts 表(表结构由 dev2 定,用 CREATE IF NOT EXISTS + 宽松字段,dev1 用 dict 方式按约定字段读写,先跟 dev2 对齐字段名,见下方【跨模块契约】)
- editor.py —— apply_edit(md, instruction)->str(当前文档全文 + 只输出完整新 md;长文档分段改);smart_export(md, format, template_style)->Path
- 单元测试 sdk/app/services/test_docgen_*.py —— intent 路由 20-30 用例(与前端同用例表)、client 假 vstore(httpx MockTransport 或 monkeypatch)、md2docx(打开验证段落/样式/中文字体)、md2pdf 冒烟(tmpdir)、pipeline mock LLM 全流程(monkeypatch llm + client)、editor(mock LLM)
【跨模块契约】(与 dev2 对齐,dev2 会在 sdk/app/db.py 建表)
- doc_library 表字段:id TEXT PK、name、category、tags、description、as_template INTEGER、enc_path、embed_sha、active、created_at
- doc_artifacts 表字段:id TEXT PK、session_id、status、title、template_name、md_path、docx_path、pdf_path、error、created_at
- BFF->vstore 全部经 client.py;router 层(dev2)只 import client.py 与 pipeline/editor
- 错误信封沿用现有格式(见 sdk/app/routers/ 现有代码,dev2 负责)
【纪律再强调】
- 不碰生产运行中的服务(8090/8091 等);不自行部署;单元测试只用 tmpdir/mock/fixture,不依赖 ollama/LLM 真调用
- 只动本任务书列出的文件;发现需要改其他文件 → 记入报告交 TL 裁决
- 不用 pydantic 模型(服务层只用 dataclass/dict)——Cython 编译纪律
- 不 commit、不 push、不 touch 部署树
【自测要求】
- 每个模块写完即写对应单元测试并跑通: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(排除依赖 ollama/LLM 的用例,用 mock)
- 自测结果写入 docs/team/report_dev1.md(列出:完成模块清单、每个测试文件与用例数、通过数、已知限制)
【完成标准】
报告写入 docs/team/report_dev1.md 后,通过 /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new/docs/team/report_dev1.md 的方式让 TL 知道(在你的最终回复中明确写「DEV1 P1 完成」+ 报告路径 + 自测通过数)
【重要补充】意图路由的规范词典与 30 条用例表已冻结于 docs/team/cases_intent.md(唯一事实源)——intent 实现与测试一律以它为准,不得自造词典/自造用例。