diff --git a/docs/docgen_team_context.md b/docs/docgen_team_context.md new file mode 100644 index 0000000..86f2e5d --- /dev/null +++ b/docs/docgen_team_context.md @@ -0,0 +1,41 @@ +===== 服务器关键环境事实(doc_writer 实施团队共享上下文,2026-09-03 实测) ===== + +【路径】 +- 服务器仓库: /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new (git main,当前干净,HEAD aab24d2) +- 部署根: /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-deploy-unified/(sdk/ + ui/,另有 legacy-rag/ qa/ methodology/) +- venv: /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-venv/bin/python(py3.12.13) +- UI 源码: /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new/ui/(vite dev 5180,vite.config proxy 8090;生产 dist 由 TL 构建同步) + +【端口】8090 BFF / 8091 QA(金标,零改动)/ 8092-8093 legacy RAG / 8094 vstore(新) / 8099 mock / 5180 vite dev +【版本】venv 已装:python-docx 1.1.2、pymupdf 1.28.2、faiss-cpu 1.14.3、cryptography 50.0.1、markdown-it-py 4.2.0、PyYAML、httpx、numpy;无 pandoc/libreoffice +【LLM/嵌入】ollama 在跑:bge-m3:567m(1024 维,0.37s/条,QA 现役同款);qwen3:8b-q4_K_M 本机可用(保密档 LLM) +【字体】/usr/share/fonts/opentype/noto/NotoSansCJK-*.ttc、NotoSerifCJK-*.ttc、truetype/wps-office/(PDF 必须用真实字体文件,禁用 PyMuPDF 内置 CJK) + +===== 关键代码事实 ===== +- BFF 是 Cython 编译部署:db.py/services/ 编译为 .so;main.py/config.py/schemas.py 是明文白名单(pydantic 模型只能放 schemas.py;服务层只用 dataclass/dict) +- 测试命名先例:sdk/app/services/test_*.py、sdk/app/routers/test_*.py(新增照此) +- PipelineCard.vue:pc-head 含 pc-title + 状态 badge;块结构 {type:pipeline, status, steps:[{name,state,desc,phase}]};PHASE_COLOR_VAR 键:planning/retrieval/evidence/generation/done(在 ui/src/i18n/labels.js:24-30) +- BenchPanel.vue:tabs 数组 + bench.activeTab===key 的 v-if pane 链(retrieval/sources/extract/kg);bench.js setActiveTab(key) 任意 key 可用 +- 前端已有 markdown-it 14.1.0 + dompurify 3.2.3(渲染/消毒现成);上传先例:extraction multipart;http.js 600s 兜底 +- 左栏展开/收起按钮样式:ui/src/styles/base.css:697-711 .side-handle-btn(26×26 圆角、hairline 描边、surface-1 底、跨左栏边缘半嵌、垂直居中) +- BFF 重启:bash /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new/deploy/bff_restart.sh(setsid 脱离 SSH,日志 /tmp/plantai-bff.log) +- vstore 将新增 deploy/vstore_restart.sh(仿 bff_restart.sh:uvicorn vstore.api:app --port 8094) +- 备份目录先例:/home/xzq/backups/-/ + +===== 团队纪律(所有 agent 必须遵守) ===== +1. 所有改码只在服务器仓库 /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new(经 ssh xzq@8.163.92.8 执行) +2. 开发 agent 只动自己模块的文件;不越界改其他模块 +3. 零改动:QA 树(8091)、ui/src/stores/modes.js、ui/src/i18n/labels.js、ui/src/stores/bench.js(store)、ui/src/api/qa.js、ui/src/sse/parser.js;capabilities.py 零改动 +4. 不 push git、不做 commit/tag(由 TL 统一决定) +5. 不改服务器运行中的生产进程;不自行部署(部署/重启由 TL 执行) +6. 不自行跑 ssh、不连生产端口探测(连 curl 127.0.0.1:8090/8091 也不行——别动运行中的服务) +7. 单元测试只跑不依赖生产服务/网络的(可用 mock/fixture/tmpdir);pytest 入口由 TL 统一执行 +8. 每个 agent 有独立工作文件目录(见各任务书),互不写入 +9. 明文安全:不落未加密的模版/附件内容到仓库或日志;测试样例可用占位文本 +10. 产出报告写在本任务书指定的报告文件 + +===== 需要改代码前必读的仓库文件 ===== +- sdk/app/config.py、sdk/app/schemas.py、sdk/app/db.py、sdk/app/main.py(P2 必读全文) +- sdk/app/routers/qa.py(错误信封格式与 multipart 先例)、sdk/app/routers/extraction.py 或类似(上传先例) +- ui/src/stores/chat.js、ui/src/components/chat/Composer.vue、PipelineCard.vue、AssistantMessage.vue、ui/src/components/bench/BenchPanel.vue、ui/src/components/layout/AppShell.vue、ui/src/styles/base.css(P3 必读全文) +- tooling/compile/compile_sdk.sh(编译清单机制——P2 若增删编译白名单文件需 TL 确认) diff --git a/docs/team/acceptance_plan.md b/docs/team/acceptance_plan.md new file mode 100644 index 0000000..323f293 --- /dev/null +++ b/docs/team/acceptance_plan.md @@ -0,0 +1,25 @@ +# P5 验收方案(Team Leader 亲自执行,R2 100% PASS 后开始) + +## 验收范围(计划「验证」章节 1-5 全部) +1. 全量 pytest(sdk 域 + 域 2)——R2 已含,TL 复核数字 +2. curl 端到端全链(含保密断言/路径配置/宕机演练) +3. 意图 30 条前后端双跑——R2 已含,TL 复核 +4. 浏览器人工验收(TL 亲自,经 SSH 隧道): + - 拖文件进聊天框 → chips → 自然语言写文档 → 骨架卡「文档生成」徽标 → 自动切「文档」TAB → 预览渲染(标题/表格/列表)→ 就地编辑保存刷新仍在 → prompt「第2段改正式一点」→ 预览更新 → 导出 PDF/DOCX → 浏览器自动下载且文件可打开 + - QA 流程开启能力时 pipeline 卡显示能力徽标 + - 右栏拖宽生效 + 宽度持久化 + 手柄与左栏一致(跨边缘半嵌/箭头镜像) + - 三模式问答/停止按钮/多会话回归 +5. 批量导入压测(R2 DC-PERF 已覆盖,TL 复核数字) + +## 隧道(TL 本机 Mac) +- ssh -N -L 15180:127.0.0.1:5180 -L 15090:127.0.0.1:8090 xzq@8.163.92.8 +- UI: http://localhost:15180(vite dev 最新前端) + +## 验收留痕 +- 验收结论写入 tests/docgen/test_case_report.md 尾部「最终验收(TL)」区 +- 附:验收日期/执行人/各项 PASS|FAIL/已知问题(断流崩溃既有问题不重复展开) + +## 注意 +- 浏览器验收会真实调用 deepseek(生成耗时 2-6 分钟),选一个短指令(如「帮我写一份简短的测试通知」)控制时长 +- 下载断言:浏览器下载目录出现文件 + python-docx/pymupdf 打开验证(TL 在服务器端开下载产物) +- 停止按钮测试注意:若触发断流崩溃(既有问题),按既有口径重启恢复并记录 diff --git a/docs/team/brief_dev1_p1.md b/docs/team/brief_dev1_p1.md new file mode 100644 index 0000000..6af16b7 --- /dev/null +++ b/docs/team/brief_dev1_p1.md @@ -0,0 +1,50 @@ +# 任务书:开发 agent-1 —— P1 后端服务层(vstore 独立包 + BFF services/docgen) + +## 前置必读 +1. docs/docgen_team_context.md(服务器环境事实 + 团队纪律,全部遵守) +2. 本任务书 + 模块职责边界 + +## 职责范围(只改这些文件,不越界) +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 实现与测试一律以它为准,不得自造词典/自造用例。 diff --git a/docs/team/brief_dev2_p2.md b/docs/team/brief_dev2_p2.md new file mode 100644 index 0000000..8c3b2bd --- /dev/null +++ b/docs/team/brief_dev2_p2.md @@ -0,0 +1,45 @@ +# 任务书:开发 agent-2 —— P2 BFF 集成/API + 批量导入工具 + +## 前置必读 +1. docs/docgen_team_context.md(服务器环境事实 + 团队纪律,全部遵守) +2. 本任务书 + 【跨模块契约】;P1 模块由 dev1 开发,你只按契约 import,不替 dev1 写服务层 + +## 职责范围(只改这些文件,不越界) +A. sdk/app/config.py(改动)—— Settings 加:docgen_root、docgen_artifact_ttl_days=30、docgen_quota_gb=50、vstore_base_url(默认 http://127.0.0.1:8094)、docgen_llm_base_url(默认 deepseek)、docgen_llm_api_key、docgen_llm_model、docgen_llm_timeout=300 +B. sdk/app/schemas.py(改动)—— Block 联合加 ArtifactBlock(type:"artifact"、artifact_id/title/template_name/status);新增 ArtifactOut、DocgenGenerateRequest、DocgenEditRequest、DocgenExportRequest 等 pydantic 模型(schemas.py 是明文白名单,pydantic 只能放这里) +C. sdk/app/db.py(改动)—— _SCHEMA 加两表(幂等 CREATE IF NOT EXISTS,try/except 不阻断启动): + - 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 +D. sdk/app/main.py(改动)—— 挂载 docgen.router +E. 新增 sdk/app/routers/docgen.py —— BFF 端点(向 UI 暴露;对 vstore 全部经 services/docgen/client.py 透传;错误信封沿用现有格式,参考 sdk/app/routers/qa.py 等现有路由写法): + - POST /api/docgen/library(multipart):上传入库(用户拖拽即走此路)→ 透传 vstore /ingest → {ok, items:[{library_id, name, size, as_template}]} + - POST /api/docgen/generate(JSON {description, session_id, library_ids[], template_id?}):同步生成(600s 兜底)→ {ok, artifact:{...}} + - GET /api/docgen/artifacts/{id}/content(md 原文);PATCH …/content(保存编辑 body {markdown}) + - POST /api/docgen/artifacts/{id}/edit:{instruction} → {ok, markdown} + - POST /api/docgen/artifacts/{id}/export:{format:"pdf"|"docx"} → {ok, download_url} + - GET /api/docgen/artifacts/{id}/download?format=:FileResponse attachment(RFC 5987 文件名) + - 主库管理(透传 vstore):GET /api/docgen/library?q=&category=&as_template=、GET/DELETE /api/docgen/library/{id}、PATCH /api/docgen/library/{id}(改 meta/as_template)、POST /api/docgen/library/rebuild-index + - 意图护栏:generate/edit/export 在 BFF 侧跑 services/docgen/intent.py 同规则校验(非 docgen 意图 → 422 提示走 QA) + - vstore 失败兜底:入库失败 → 错误返回(前端 chips 标红可重试);检索失败 → 空模版集合(生成跳过模版);document 拉取失败 → 该资料缺失占位提示(不阻断) +F. 新增 tooling/scripts/docgen_import.py(不进编译链,直连 vstore HTTP)—— 读目录/CSV 批量 ingest(并发 8),as_template=true 默认;rebuild-index 原子切换;--dry-run 先例;导入完成删除源明文目录(--keep-source 可保留) +G. 新增 sdk/app/routers/test_docgen_api.py —— 路由层测试(TestClient + mock pipeline/client,不依赖真实 vstore/LLM) + +## 【跨模块契约】(与 dev1 对齐) +- 服务层只 import:services/docgen/client.py、pipeline.py、editor.py、intent.py(dev1 提供;若 dev1 尚未交付,先用最小接口桩并标记 TODO,交 TL 协调) +- db 两表字段如上;服务层以 dict 读写,不引 pydantic +- BFF->vstore 全部经 client.py;router 不直接 httpx 调 vstore +- 用户消息/会话落库:generate 只负责生成与 artifact 落库;聊天消息落库由前端 chat.js 走现有机制(你不用改 sessions/messages) + +## 【纪律再强调】 +- 不碰生产运行中的服务(8090/8091 等);不自行部署;测试用 TestClient + mock,不连真实 vstore/ollama/LLM +- 只动本任务书列出的文件;发现需要改其他文件(如 capabilities.py、qa.py)→ 记入报告交 TL 裁决 +- 不 commit、不 push、不 touch 部署树 +- db.py 加表只追加 _SCHEMA,不改现有表定义 + +## 【自测要求】 +- cd /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new && /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-venv/bin/python -m pytest sdk/app/routers/test_docgen_api.py -q +- config/schemas 改动能 import 通过;db 加表在 tmp sqlite 上执行验证幂等 +- 自测结果写入 docs/team/report_dev2.md + +## 【完成标准】 +最终回复中明确写「DEV2 P2 完成」+ 报告路径 docs/team/report_dev2.md + 自测通过数 diff --git a/docs/team/brief_dev3_p3.md b/docs/team/brief_dev3_p3.md new file mode 100644 index 0000000..afc61c5 --- /dev/null +++ b/docs/team/brief_dev3_p3.md @@ -0,0 +1,36 @@ +# 任务书:开发 agent-3 —— P3 前端 UI(自然触发 + 文档 TAB + 能力徽标 + 右栏可拖宽) + +## 前置必读 +1. docs/docgen_team_context.md(服务器环境事实 + 团队纪律,全部遵守) +2. 本任务书;改任何文件前先读其全文,理解现有结构再动 + +## 职责范围(只改这些文件,不越界) +A. 新增 ui/src/api/docgen.js —— uploadLibrary/generate/getArtifactContent/saveArtifactContent/editArtifact/exportArtifact/listLibrary/downloadUrl(复用现有 http.js 封装,600s 兜底) +B. 新增 ui/src/i18n/docgen.js —— 全部新文案(含能力徽标「文档生成」) +C. 新增 ui/src/intent.js —— 前端意图路由 route(text, hasAttachments, hasActiveArtifact)->qa|docgen|edit|export。**规则词典与后端 services/docgen/intent.py 完全一致**(同一词典表、同一优先级:导出词→编辑词+有活动文档→写文档意图(写类动词+文档类型词+量词)→附件+写类动词→qa;漏判安全)。导出文档类型词词典与后端一致:论文/报告/通知/通告/公告/请示/批复/函/申请书/申报书/标书/合同/协议/方案/计划/总结/汇报/讲话稿/致辞/证明/纪要/简报/材料/说明书/指南 +D. 新增 ui/src/stores/docgen.js —— 文档 TAB 状态:活动 artifact、md 内容、编辑态、导出态、模版检索候选;不 import bench.js 的状态(只调 setActiveTab) +E. 修改 ui/src/components/chat/Composer.vue —— 拖拽区:dragover 高亮 → drop 收集(前端预检 20MB/5 个)→ 先调 uploadLibrary(入库主向量库,持久可用)→ chips(可移除,移除仅从本次引用拿掉);发送时按 intent.js 路由:docgen → chat.sendDocgen;QA → 原路径(附件注记进消息文本)。**send() 现有逻辑零改动,docgen 走独立分支** +F. 修改 ui/src/stores/chat.js —— 新增 sendDocgen(description, libraryIds):用户消息落库(走现有消息落库机制)→ 助手骨架(kind:docgen,pipeline 块带 capabilities:[{key:doc_writer, label:文档生成}] 徽标,steps 五步:解析资料/检索模版/规划大纲/逐节生成/导出文档,phase 复用 PHASE_COLOR_VAR 键 planning/retrieval/evidence/generation/done)→ generate(600s)→ 成功:blocks=[pipeline(done), artifact 块] + 持久化 + 自动切 bench.setActiveTab(doc);失败 _errorMarker。QA 路径的 pipeline 块注入本次实际启用的会话能力 chips(联网搜索/答案润色/Superpowers,label 取自 settings store 已加载 manifest)。现有 send/_runStream 零改动 +G. 修改 ui/src/components/chat/PipelineCard.vue —— pc-head 加 capabilities 徽标区(v-for 渲染 chip,复用现有 badge 视觉),block.capabilities 缺省时零渲染(既有消息无影响) +H. 新增 ui/src/components/chat/DocCard.vue —— 消息流内文档卡片:标题/模版名/「打开预览」按钮(切到文档 TAB) +I. 新增 ui/src/components/bench/DocPane.vue —— 第 5 TAB 主体:markdown-it 渲染 HTML 预览(复用现有 markdown.js 渲染+消毒);块级就地编辑(标题/正文/列表/表格行 contenteditable,blur 序列化回 md + debounce PATCH 保存);「用 prompt 修改」输入行(调 edit 端点);「导出」按钮组(PDF/DOCX) +J. 修改 ui/src/components/bench/BenchPanel.vue —— tabs 数组加 {key:doc, label:文档} + pane 链加 v-else-if 挂 DocPane(两处模板改动,无逻辑改动) +K. 修改 ui/src/components/chat/AssistantMessage.vue —— 块渲染加 artifact 分支 +L. 修改 ui/src/components/layout/AppShell.vue + ui/src/styles/base.css —— 右栏可拖拽改宽 + 手柄仿左栏:① .bench-wrap 左边缘加 .bench-resizer(宽 8px、cursor:col-resize,mousedown 拖动实时改宽,min 300px / max 60vw,mouseup 持久化 localStorage pa-bench-width,初始 380px,折叠态不拖拽);② 展开/收起按钮改成左栏 .side-handle-btn 同款:26×26 圆角、hairline 描边、surface-1 底、跨右栏左边缘半嵌(left:0 + translate(-50%,-50%))、垂直居中、图标 13px;③ 箭头方向镜像左栏:折叠态朝左(指向内容侧)、展开态朝右;④ 折叠态 28px 竖条保留为点击区,按钮嵌其上(同款式);⑤ 宽度变化与手柄位置联动(transition 0.3s 同左栏) + +## 零改动红线 +ui/src/stores/modes.js、ui/src/i18n/labels.js、ui/src/stores/bench.js(store)、ui/src/api/qa.js、ui/src/sse/parser.js + +## 【纪律再强调】 +- 不碰生产运行中的服务(8090/8091/5180 等);不自行重启 vite、不自行构建 dist +- 只动本任务书列出的文件;发现需要改其他文件 → 记入报告交 TL 裁决 +- 不 commit、不 push、不 touch 部署树 + +## 【自测要求】 +- 前端自测:cd ui && npx vitest run(如仓库已有前端测试惯例则照做;intent.js 必须有单元测试——与后端同用例表 20-30 条);无测试框架先例则至少 node --input-type=module 直接 import intent.js 跑断言脚本(intent.js 保持纯函数无 Vue 依赖以便 node 直测) +- 自测结果写入 docs/team/report_dev3.md(完成模块清单、测试通过数、已知限制) + +## 【完成标准】 +最终回复中明确写「DEV3 P3 完成」+ 报告路径 docs/team/report_dev3.md + 自测通过数 + +【重要补充】意图路由的规范词典与 30 条用例表已冻结于 docs/team/cases_intent.md(唯一事实源)——intent 实现与测试一律以它为准,不得自造词典/自造用例。 diff --git a/docs/team/brief_qa_p0.md b/docs/team/brief_qa_p0.md new file mode 100644 index 0000000..07ecc9e --- /dev/null +++ b/docs/team/brief_qa_p0.md @@ -0,0 +1,36 @@ +# 任务书:测试 agent —— P0 测试治理(编制全部测试用例 + 报告框架) + +## 前置必读 +1. docs/docgen_team_context.md(服务器环境事实 + 团队纪律,全部遵守) +2. 本任务书 + 仓库现有测试先例(sdk/app/services/test_*.py、sdk/app/routers/test_*.py、tests/ 目录)——先读 2-3 个现有测试文件学断言风格 + +## 你的角色 +- 编制【全部】测试用例(目标:覆盖率 100%——每一个计划模块、每一个端点、每一条意图路由规则、每一个 UI 交互点都有对应用例) +- 用例完成后提交 Team Leader 审核;审核过关前【不执行任何测试】(TL 会指令你何时开始轮测) +- 每轮测试逐项执行并逐条留痕;复测与最终结果同样逐条留痕 + +## 交付物(只写这些文件,不越界) +A. tests/docgen/test_case_report.md —— 测试用例总报告(唯一事实源): + - 头部:文档版本、编制人(测试 agent)、编制日期、审核状态(待 TL 审核/已审核通过) + - 用例分组(编号规则 DC-<组>-):DC-UNIT-CRYPTO、DC-UNIT-VAULT、DC-UNIT-BACKEND、DC-UNIT-RETRIEVAL、DC-UNIT-INTENT、DC-UNIT-CONVERT(md2docx/md2pdf)、DC-UNIT-PIPELINE、DC-UNIT-EDITOR、DC-UNIT-CLIENT、DC-API(端点逐条)、DC-INTENT-CASES(20-30 条意图对照,前后端同一张表)、DC-E2E-CURL(端到端脚本步骤)、DC-CONF(路径配置三形态+握手拒绝)、DC-FAILOVER(vstore 宕机演练)、DC-UI(浏览器验收清单逐条)、DC-REGRESSION(三模式问答/停止/多会话)、DC-PERF(百级导入压测) + - 每条用例:编号/模块/前置/步骤/预期/实测/结论(PASS|FAIL)/轮次/日期/执行人(实测列每轮追加留痕,不覆盖历史) + - 报告内建「轮次记录」区:每轮开始记轮次号+日期+执行人,结束记汇总(总条数/PASS/FAIL/FAIL 明细) +B. tests/docgen/run_all.sh + 各分组执行脚本 —— 每条可自动化的用例有对应脚本/命令(curl 场景、pytest 选择器、意图对照表脚本、UI 清单核对表),用例编号与脚本一一对应,可复跑 +C. tests/docgen/cases_intent.md —— 意图对照表(20-30 条,前后端共用:输入文本/附件标志/活动文档标志/期望结果) + +## 用例编写要求 +1. 每条用例:可判定(预期明确)、可复跑、失败可定位到模块 +2. 单元类用例与开发自测互补但不重复造轮子:你的用例是验收口径(黑盒为主),开发的自测是白盒口径 +3. E2E 用例写清 curl 命令序列与每步断言(含 ls vault/ 只见 .enc、明文 grep 无命中等保密断言) +4. UI 用例是浏览器人工/半自动验证清单(每步可核对),写清检查点 +5. 覆盖计划「验证」章节的 5 条全部要求 + 计划中所有功能点(自然触发意图、加密入库、vstore 独立服务、能力徽标、文档 TAB 预览编辑导出下载、右栏拖宽+手柄、模版检索、批量导入、路径配置、降级兜底) + +## 【纪律再强调】 +- 不碰生产运行中的服务;未经 TL 指令【不执行测试】(此刻你只编用例) +- 只写 tests/docgen/ 下文件;不 commit、不 push +- 报告用中文 + +## 【完成标准】 +最终回复中明确写「QA P0 用例编制完成」+ 报告路径 tests/docgen/test_case_report.md + 用例总数 + 覆盖声明(对照计划功能点清单) + +【重要补充】意图路由的规范词典与 30 条用例表已冻结于 docs/team/cases_intent.md(唯一事实源)——intent 实现与测试一律以它为准,不得自造词典/自造用例。 diff --git a/docs/team/cases_intent.md b/docs/team/cases_intent.md new file mode 100644 index 0000000..852d688 --- /dev/null +++ b/docs/team/cases_intent.md @@ -0,0 +1,68 @@ +# 意图路由规范(唯一事实源)——dev1(Python)/dev3(JS)/测试 agent 三方共用 + +## 路由签名 +route(text, has_attachments, has_active_artifact) -> "qa" | "docgen" | "edit" | "export" + +## 判定顺序(按优先级从上到下,命中即返回) + +### 1. export(需 has_active_artifact == True) +条件:text 含任一 EXPORT_WORD 且含任一 FORMAT_WORD +- EXPORT_WORD = [导出, 转成, 转为, 转换成, 下载成, 输出为, 存为, 转pdf, 转word] +- FORMAT_WORD = [pdf, docx, doc, word](英文不区分大小写;"doc" 是 "docx" 的子串,判定时 FORMAT_WORD 逐一 contains 即可) +无活动文档时不命中本规则,继续向下判定。 + +### 2. edit(需 has_active_artifact == True) +条件:text 含任一 EDIT_WORD +- EDIT_WORD = [改, 修改, 调整, 删, 删除, 加一段, 补充, 润色, 缩写, 扩写, 重写, 改写, 优化, 加个, 增加一段] +无活动文档时不命中本规则,继续向下判定。 + +### 3. docgen(写文档意图) +条件:text 含任一 WRITE_VERB 且含任一 DOC_TYPE +- WRITE_VERB = [写, 撰写, 拟, 起草, 编制, 生成, 编写, 拟定, 草拟, 制定] +- DOC_TYPE = [论文, 报告, 通知, 通告, 公告, 请示, 批复, 函, 申请书, 申请, 申报书, 标书, 合同, 协议, 方案, 计划, 总结, 汇报, 讲话稿, 致辞, 证明, 纪要, 简报, 材料, 说明书, 指南, 文档, 申报材料, 工作总结, 调研报告, 可行性报告, 项目书, 策划书, 演讲稿, 新闻稿, 文案, 简历, 制度, 规定, 办法, 细则, 章程, 声明, 邀请函, 感谢信, 慰问信, 介绍信, 推荐信, 倡议书, 述职报告, 开题报告, 结题报告, 实验报告, 分析报告, 会议纪要] +- 明确排除(不属于 DOC_TYPE,防误判):介绍, 内容, 代码, 诗, 作业, 问题, 建议, 想法 + +### 4. docgen(附件信号) +条件:has_attachments == True 且 text 含任一 WRITE_VERB +(兜底:拖入文件 + 写类动词,即使无文档类型词也视为生成意图) + +### 5. 其余一律 "qa"(漏判安全原则:不命中绝不进 docgen) + +## 用例表(30 条,前后端实现与测试共用同一张表) + +| # | 输入 text | 附件 | 活动文档 | 期望 | +|---|---|---|---|---| +| 1 | 给我写一篇关于小麦育种的论文 | 否 | 否 | docgen | +| 2 | 帮我写一个关于疫情防控的通知,参考xxx文档的格式 | 否 | 否 | docgen | +| 3 | 你来帮我起草一份项目总结报告 | 否 | 否 | docgen | +| 4 | 拟一份关于安全生产的通知 | 否 | 否 | docgen | +| 5 | 写一个基金申请文档,参考xxxx和xxxx资料,你来润色 | 否 | 否 | docgen | +| 6 | 根据这个文档,帮我写一篇申报材料 | 是 | 否 | docgen | +| 7 | 写一段水稻基因的介绍 | 否 | 否 | qa | +| 8 | 水稻基因是什么?介绍一下 | 否 | 否 | qa | +| 9 | 帮我总结一下这个问题的要点 | 否 | 否 | qa | +| 10 | 写一首关于秋天的诗 | 否 | 否 | qa | +| 11 | 帮我写代码实现一个排序函数 | 否 | 否 | qa | +| 12 | 这个方案你怎么看? | 否 | 否 | qa | +| 13 | 通知今天下午开会 | 否 | 否 | qa | +| 14 | 导出成PDF | 否 | 是 | export | +| 15 | 把文档转成word发给我 | 否 | 是 | export | +| 16 | 导出成PDF | 否 | 否 | qa | +| 17 | 第2段改正式一点 | 否 | 是 | edit | +| 18 | 把标题改成黑体 | 否 | 是 | edit | +| 19 | 润色一下第二段 | 否 | 是 | edit | +| 20 | 第2段改正式一点 | 否 | 否 | qa | +| 21 | 帮我写个方案,参考上次上传的材料 | 否 | 否 | docgen | +| 22 | 写一份年度工作计划 | 否 | 否 | docgen | +| 23 | 起草会议纪要 | 否 | 否 | docgen | +| 24 | 帮我生成一个市场分析报告 | 否 | 否 | docgen | +| 25 | 写一篇演讲稿,要有感染力 | 否 | 否 | docgen | +| 26 | 帮我写一份劳动合同 | 否 | 否 | docgen | +| 27 | 转成PDF | 否 | 是 | export | +| 28 | 帮我写个会议通知模板 | 否 | 是 | docgen | +| 29 | 给这个通知加个结尾 | 否 | 是 | edit | +| 30 | 增加一段关于风险的描述 | 否 | 是 | edit | + +## 一致性要求 +- dev1 的 sdk/app/services/docgen/intent.py 与 dev3 的 ui/src/intent.js 必须按本表逐条通过 +- 测试 agent 的 DC-INTENT-CASES 组直接引用本表(不得另造规则) diff --git a/docs/team/deploy_plan.md b/docs/team/deploy_plan.md new file mode 100644 index 0000000..f137750 --- /dev/null +++ b/docs/team/deploy_plan.md @@ -0,0 +1,105 @@ +# P4 部署清单(TL 执行,逐步核对) + +> 纪律:备份先行、只动服务器、不 push git、不碰 QA 树(8091)、生产 BFF 8090 只在最后一步重启。 + +## 0. 前置确认 +- [ ] dev1/dev2/dev3/QA v1.1 全部交付且 TL 审核通过 +- [ ] dev1 交付后:TL 统一删除 routers/docgen.py 顶部 _STUB 桩段并核对签名 +- [ ] 意图 30 条双跑(后端 pytest + 前端 node)通过 + +## 1. 备份(不可跳过) +```bash +TS=$(date +%Y%m%d-%H%M); BK=/home/xzq/backups/docgen-$TS; mkdir -p $BK +tar czf $BK/deploy-sdk.tar.gz -C /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-deploy-unified sdk +cp /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-deploy-unified/sdk/data/plantai.db $BK/plantai.db +cp /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-deploy-unified/sdk/.env $BK/env.backup +``` + +## 2. 全量测试(服务器仓库,venv) +```bash +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 sdk/app/routers/test_docgen_api.py \ + tooling/scripts/test_docgen_import.py tests/docgen/ -c tests/docgen/pytest.ini -q +``` + +## 3. BFF 编译 + 替换部署树 +```bash +bash tooling/compile/compile_sdk.sh sdk /tmp/sdk-docgen-build /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-venv/bin/python +rsync -a --delete --exclude data --exclude .env --exclude mock /tmp/sdk-docgen-build/ /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-deploy-unified/sdk/ +# 部署树 .env 追加(不覆盖已有): +# VSTORE_BASE_URL=http://127.0.0.1:8094 +# DOCGEN_LLM_API_KEY / DOCGEN_LLM_BASE_URL / DOCGEN_LLM_MODEL(复制 QA 的 deepseek 值) +# DOCGEN_ROOT +``` + +## 4. vstore 部署 + 起服 +```bash +mkdir -p /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-deploy-unified/vstore +rsync -a REPO/vstore/ /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-deploy-unified/vstore/ +# 部署树 vstore/.env:VSTORE_KEY(取自 /home/xzq/backups/docgen-vstore-key.txt)、 +# VSTORE_ROOT 或 VSTORE_VAULT_PATH/VSTORE_INDEX_PATH(本地路径或 URL)、 +# VSTORE_EMBED_URL=http://127.0.0.1:11434、VSTORE_EMBED_MODEL=bge-m3:567m +bash deploy/vstore_restart.sh +curl -s http://127.0.0.1:8094/api/vstore/health +``` + +## 5. BFF 重启 + 前端构建 +```bash +bash deploy/bff_restart.sh +cd ui && npm run build # 生产 dist → 部署树 ui/dist(按现有 sync 规则) +``` + +## 6. 通知测试 agent 开始 R1(受控端口:vstore 8094 / BFF 8090 / UI 5180 或 dist) + +## 7. 回滚(出问题时) +```bash +rsync -a --delete --exclude data --exclude .env $BK/deploy-sdk.tar.gz 解包覆盖 sdk/ +cp $BK/plantai.db 回原位; bash deploy/bff_restart.sh +``` + +## 附:P4 部署实际记录(2026-09-03 15:05) + +- 备份:/home/xzq/backups/docgen-20260903-1425(deploy-sdk.tar.gz + plantai.db + env.backup) +- 编译:30 模块 Cython 3.2.8 硬化成功(docgen 全链路 .so);rsync 替换部署树(保留 data/.env/mock) +- vstore:8094 已起服,local 后端;部署树 vstore/.env 曾因 key 值二次加前缀报 Fernet 格式错(已修,重写 .env 后 ingest 正常) +- vstore_restart.sh 已修 cd 目标(vstore 是包,须从部署根 -m vstore.api:app) +- BFF:8090 重启后 openapi 确认 8 个 docgen 端点;健康检查五上游 + db 正常 +- 冒烟:vstore ingest 成功、vault 仅 .enc、明文 grep 无命中;BFF 透传入库/list 正常;完整 QA 流(不断流)7 progress + complete 正常 +- vite:5180 已重启(v5.4.11,19s 就绪) + +## 附:既有问题归档(非本次引入,A/B 证据) + +**「客户端中途断流 → BFF 崩溃」**:`__Pyx_Coroutine_SendEx: Assertion f->f_back == NULL`(db.c)。 +- 复现:curl --max-time 20 打 /api/qa/chat/stream 后超时断连 → BFF abort +- A/B:本次构建(db.c:13468)与 9月2日旧构建(db.c:13218,tooling/build/sdk-compiled 备份)**均崩溃**,断言一致 → 既有问题(Cython 3.2.8 协程取消缺陷,与 docgen 无关) +- 影响:用户点「停止」或网络断连可能触发 BFF 进程退出(bff_restart.sh 可快速恢复);前端「停止」按钮走 AbortController 的路径是否触发待 V1.5 专项验证与修复 +- 决策:不纳入本轮 docgen 闭环;记录后另立专项 + +## 附:断流崩溃根治专项(用户指令 2026-09-03,doc_writer 闭环完成后立即开始) + +**用户指令**:记录下来,当前任务完成后立即开始根治断流崩溃。 + +**已知事实(A/B 证据)**:旧构建(9月2日,db.c:13218)与新构建(db.c:13468)断流均崩,Cython 3.2.8 协程取消缺陷;当前无任何自动拉起(无 systemd/supervisor/cron 守护)。 + +**拟修路径(按序验证)**: +1. Cython 3.2.8 → 3.3.0 重编译 → 隔离实例断流探针 10 连发 A/B +2. 仍崩则取消敏感模块(db.py / sse_relay.py)降级明文 Python(compile_sdk.sh 白名单机制支持) +3. 配套加固(可选):BFF 自动守护(systemd Restart=always 或 cron 探测重启) + +**验收**:隔离实例断流探针不崩 + 生产替换后「停止按钮/断网」实测 + 三模式问答回归。 + +## 附:断流崩溃根治完成记录(2026-09-03 19:30) + +**修复方案(最终)**:编译白名单追加 3 个模块(app/db.py、app/routers/qa.py、app/services/sse_relay.py)——SSE 流取消链明文化,取消路径全程走 CPython 原生协程。 + +**证据链**: +- Cython 3.2.8 构建:崩溃 db.c:13468 +- Cython 3.3.0 构建:崩溃 db.c:14416(升级无效,3.3.0 未修复此缺陷) +- db.py 明文:崩溃迁移 sse_relay.c:29531(同一断言)→ 证明取消链上任意 Cython 协程均可能触发 +- db+qa+sse_relay 明文化:隔离实例断流探针 10/10 存活 +- 生产同构建替换后:真实断流探针存活 + 完整 QA 流 7 progress + complete 正常 + 五上游健康 + +**代价与风险**:3 个模块失去 Cython 硬化(源码可读,无敏感凭据——凭据在 .env/环境);编译模块 30→27。白名单理由已写入 compile_sdk.sh 注释。 + +**备份**:/home/xzq/backups/docgen-crashfix-20260903-1922(可回滚) diff --git a/docs/team/report_dev1.md b/docs/team/report_dev1.md new file mode 100644 index 0000000..959a72f --- /dev/null +++ b/docs/team/report_dev1.md @@ -0,0 +1,91 @@ +# 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(无回归)。 diff --git a/docs/team/report_dev2.md b/docs/team/report_dev2.md new file mode 100644 index 0000000..7fd0f20 --- /dev/null +++ b/docs/team/report_dev2.md @@ -0,0 +1,144 @@ +# P2 开发报告 —— 开发 agent-2(BFF 集成/API + 批量导入工具) + +日期:2026-09-03 + +## 一、改动文件清单 + +### 修改(4 个) +| 文件 | 改动 | +|---|---| +| sdk/app/config.py | Settings 追加 docgen 键:docgen_root(默认 data/docgen)、docgen_artifact_ttl_days=30、docgen_quota_gb=50、docgen_llm_base_url(默认 https://api.deepseek.com)、docgen_llm_api_key(默认空)、docgen_llm_model=deepseek-chat、docgen_llm_timeout=300、vstore_base_url(默认 http://127.0.0.1:8094)。全部可被环境变量覆盖 | +| sdk/app/schemas.py | Block 联合追加 ArtifactBlock(type:"artifact"、artifact_id/title/template_name/status);新增 §12 模型:ArtifactOut、LibraryItemOut、LibraryPatch、DocgenGenerateRequest、DocgenEditRequest、ArtifactContentPatch、DocgenExportRequest(format Literal["pdf","docx"])、DocgenImportLine(导入工具 CSV 契约) | +| sdk/app/db.py | _SCHEMA 不动;新增 _DOCGEN_SCHEMA 两表(doc_library:id/name/category/tags/description/as_template/enc_path/embed_sha/active/created_at;doc_artifacts:id/session_id/status/title/template_name/md_path/docx_path/pdf_path/error/created_at)+ 4 个索引。connect() 内单独 executescript,try/except 失败只记日志不阻断启动;CREATE IF NOT EXISTS 幂等 | +| sdk/app/main.py | 挂载 docgen.router(import 加入 routers 包导入行 + include_router) | + +### 新增(4 个) +| 文件 | 内容 | +|---|---| +| sdk/app/routers/docgen.py | BFF 全部 docgen 端点(见 §二);错误信封沿用 app.upstream.envelope | +| sdk/app/routers/test_docgen_api.py | 路由层测试:TestClient + monkeypatch mock 服务层 + 临时 SQLite,不连真实 vstore/LLM | +| tooling/scripts/docgen_import.py | 批量导入工具:并发 8、--dry-run、--keep-source、metadata.csv 映射、成功才删源明文目录、失败不删不重建、rebuild-index | +| tooling/scripts/test_docgen_import.py | 导入工具单测:httpx.MockTransport 假 vstore | + +## 二、端点与行为(routers/docgen.py) + +- POST /api/docgen/library(multipart,100MB 护栏)→ client.ingest_bytes → {ok, items:[{library_id,name,size,as_template}]};ingest 失败不落库,VALIDATION 信封(chips 标红可重试);vstore 不可达 → 503 UPSTREAM_DOWN +- POST /api/docgen/generate:description 必填 ≤2000 字;意图护栏 → 模版检索(失败→空模版集不阻断)→ 资料落库行(library_ids 空=全部 active)→ pipeline.generate_document → artifact 落库 {ok, artifact};服务未就绪 → 503 + 落 error 行(前端可查状态) +- GET/PATCH /artifacts/{id}/content(md 原文/保存编辑) +- POST /artifacts/{id}/edit:{instruction} → {ok, markdown},编辑结果同步落盘 +- POST /artifacts/{id}/export:{format:"pdf"|"docx"} → {ok, download_url},同步转换(内容取最新编辑版) +- GET /artifacts/{id}/download?format=:FileResponse attachment(RFC 5987 文件名) +- 主库:GET /library(q/category/as_template 落库过滤)、GET/PATCH/DELETE /library/{id}(PATCH/DELETE 的 vstore 透传失败容错)、POST /library/rebuild-index(透传,不可达 → 503) +- 意图护栏:generate/edit/export 先跑 intent.is_docgen_intent,非 docgen 意图 → 422 INTENT_REJECTED(提示走 QA) +- 路径穿越防护:_safe_rel 拒绝绝对路径与 "..";制品落盘在 docgen_root 下 + +## 三、dev1 依赖与接口桩(重要) + +services/docgen/ 当时尚未交付,docgen.py 按任务书契约做了最小接口桩(try/except ImportError 回落,签名与契约一致): +- client.py:DocgenUnavailable、ingest_bytes、search_documents、delete_document、update_metadata、rebuild_index +- pipeline.py:generate_document(description, *, documents, template) +- editor.py:apply_edit(markdown, instruction) +- intent.py:is_docgen_intent(text) +- converter.py:docx_from_markdown / pdf_from_markdown(任务书未列但 export 必需,桩同签) + +**待 TL 联动联测项:** +1. dev1 交付后删掉 docgen.py 顶部桩段即可(router 主体零改动),但需对齐桩签名与 dev1 真实签名(尤其 client 函数名/参数、generate_document 返回结构 {markdown, md_path}、edit 返回 {markdown, md_path?}) +2. 任务书兜底要求「document 拉取失败 → 缺失占位提示(不阻断)」:资料全文拉取属服务层职责(router 只传 doc_library 落库行 dict 给 pipeline),具体占位形态待 dev1 服务层实现后联测 +3. export 的转换实现(docx/pdf 生成)由 dev1 converter.py 负责;契约未在任务书列明,本报告提请 TL 确认归属 + +## 四、自测结果 + +- 路由层:pytest sdk/app/routers/test_docgen_api.py -q → **29 passed**(上传 3、生成 4、content/edit/export/download 12、主库 8、块联合 2;含 422/404/503/204 与 vstore 容错路径) +- 导入工具:pytest tooling/scripts/test_docgen_import.py -q → **7 passed**(dry-run、成功删源、keep-source、部分失败不删不重建、并发 ingest) +- 回归抽查(只跑目标相关,未跑全量):sdk/app/test_db.py 5 passed;sdk/app/test_sessions_api.py 13 passed(Block 联合扩展无回归) +- config/schemas:import + 默认值断言通过(含 DocgenExportRequest 非法格式 422 校验) +- db:tmp sqlite 验证两表字段齐全 + 二次 connect 幂等通过 +- main.py:openapi 确认 8 个 docgen 路径挂载 +- 合计:**54 passed,0 failed**(29+7+5+13) + +## 五、已知限制 / 需 TL 裁决 + +1. **QUOTA_EXCEEDED 信封未实现**:docgen_quota_gb 配置键已就位,但 quota 校验逻辑(制品目录体积扫描)属服务层/运维职责,任务书端点清单未包含对应入口——是否需要 BFF 侧实现,请 TL 裁决 +2. **docgen_artifact_ttl_days 暂无消费者**:配置键已就位,TTL 清理循环建议放在 vstore 或运维侧,任务书未列对应端点 +3. **INTENT_REJECTED / QUOTA_EXCEEDED 是 envelope 新 code**:upstream.py 的 _ERROR_MESSAGES/status 映射表未含这两个 code,docgen.py 调用时显式传 status=422/507 与 message,未改 upstream.py(不在任务书文件清单) +4. **legacy.py 既有告警**:openapi 生成时 Duplicate Operation ID 告警(legacy 路由,非本次改动引入) +5. **编译链**:docgen.py / test_docgen_api.py 属新增文件,compile_sdk.sh 白名单机制(main/config/schemas 明文)无需改动;docgen_import.py 在 tooling/ 不进编译链——无需 TL 确认 +6. 服务层以 dict 读写落库(不引 pydantic),与契约一致 + +## 六、纪律自检 + +- 未碰 capabilities.py/qa.py 等非清单文件;未连生产端口(8090/8091 等);未 commit/push;未重启任何服务 +- tests/docgen/、deploy/vstore_restart.sh、docs/ 下其他文件为他家产物,未动 + +## 七、补丁记录(2026-09-03,TL 裁决后追加) + +TL 对报告 §五 3 个问题的裁决:1 QUOTA_EXCEEDED 由 BFF 实现;2 TTL 由 BFF 惰性清理;3 新错误码维持现状(不改 upstream.py);4 converter 归属 dev1(交付后 TL 统一删桩,本 agent 不处理)。 + +### 补丁内容(只改 sdk/app/routers/docgen.py 与 test_docgen_api.py) +1. **配额 _check_quota()**(routers/docgen.py): + - artifacts/ 目录体积扫描(rglob + st_size,跳过符号链接,不抛异常) + - 超 docgen_quota_gb → 507 QUOTA_EXCEEDED 信封(degraded=true,含已用/配额信息) + - 拒绝点:generate(意图护栏之后、模版检索之前)、export(护栏之后) + - generate 成功后顺带复查一次(写盘后体积更准;超限仅告警日志,artifact 已存在仍返回成功) +2. **TTL 惰性清理 _prune_expired_artifacts()**(routers/docgen.py): + - created_at 超 docgen_artifact_ttl_days 的 artifact:删除 md/docx/pdf 文件(unlink missing_ok,路径穿越防护 _safe_rel)+ DB 行 status 置 expired、路径列清空 + - 在 generate 开始前调用;清理异常只记日志不阻断生成 +3. 模块 docstring 更新(配额/TTL 语义说明) + +### 新增测试用例(test_docgen_api.py,+5) +- test_generate_quota_exceeded_507:quota=0 设置下 generate 返回 507 QUOTA_EXCEEDED(degraded=true) +- test_export_quota_exceeded_507:同设置下 export 返回 507 +- test_check_quota_passes_when_under_limit:默认设置下 generate/export 不被误拦 +- test_generate_prunes_expired_artifacts:过期行(31 天前)文件被删 + status 置 expired;新鲜行(1 天前)不受影响 +- test_prune_expired_missing_files_tolerated:过期行文件缺失时清理容错(不抛异常,行仍置 expired) + +### 补丁后自测 +- 路由层:`pytest sdk/app/routers/test_docgen_api.py -q` → **34 passed**(原 29 + 新 5) +- 导入工具:`pytest tooling/scripts/test_docgen_import.py -q` → 7 passed(无回归) +- 合计:**41 passed,0 failed** + +## 八、测试污染修复记录(2026-09-03,TL 联测诊断后) + +### 现象 +单独跑 test_docgen_api.py 34 全过;与任何其他测试文件合跑 → 32 ERROR。 +错误链:`'test_library_upload_ok' requested an async fixture 'db', with no plugin or hook that handled it` → fixture 链 `assert not self._finalizers` 崩溃。 + +### 根因(TL 诊断,已采纳) +pytest-asyncio 1.4.0 的 event_loop fixture 跨文件复用 bug:async fixture(db)与 sync 测试(client/测试函数)混用,多文件合跑时 loop finalizers 跨文件残留,async fixture 链崩溃。本仓库可靠模式是 QA 验收脚本 tests/docgen/test_docgen_api_acceptance.py 的纯 sync fixture + 手动事件循环。 + +### 修复(方案 A,只改 sdk/app/routers/test_docgen_api.py) +db fixture 由 async 改为纯 sync:connect/close 分别经 `_run()`(asyncio.new_event_loop().run_until_complete)执行;fixture 链(db/client/_reset_mocks)全 sync;测试函数内 db 访问维持 `_run(async def inner(): ...)` 闭包模式(QA 验收脚本同款)。文件头部注释记录诊断原因,防回归。 + +### 三组合跑结果(验收标准全过) +1. `pytest sdk/app/routers/test_docgen_api.py tooling/scripts/test_docgen_import.py -q` → **41 passed**(无 ERROR) +2. `pytest sdk/app/routers/test_docgen_api.py -q` → **34 passed** +3. `pytest sdk/app/routers/test_docgen_api.py tooling/scripts/test_docgen_import.py sdk/app/services/test_docgen_*.py vstore/tests/ -q` → **186 passed**(与 TL 预计一致) + +未动被测代码 docgen.py(dev1 删桩替换后由 TL 管理)。 + +## 九、R1 修复记录(2026-09-03,TL 审阅 FAIL 1:护栏结构性矛盾) + +### 根因(TL 定性) +_intent_guard 对所有端点统一调 is_docgen_intent(text)(= route(text, False, False) == "docgen"): +- generate 传 description → 正确 +- edit 传 instruction(如「润色一下第一段」)→ route 判 edit(has_active_artifact 硬编码 False)→ 恒 422 +- export 传合成文本 → 恒 422 + +### 修复(按 TL 要求,只改 sdk/app/routers/docgen.py 护栏相关行 + test_docgen_api.py) +1. import 增加 route:`from ..services.docgen.intent import is_docgen_intent, route`(is_docgen_intent 布尔接口未动) +2. _intent_guard 改为 `_intent_guard(text, *, has_active_artifact: bool)`,内部直接 route 三参判定: + - generate:has_active_artifact=False → route(...) == "docgen" 放行(维持原语义) + - edit:has_active_artifact=True → route(...) in ("docgen", "edit") 放行(编辑指令判 edit) + - export:取消护栏(format 已有 DocgenExportRequest Literal 白名单;DC-API-09 format 校验继续生效) + - 路由异常 → 放行(漏判安全,护栏不阻断) +3. 测试适配真实 route:generate 各用例 description 改为含写动词+文档类型(「写一份…」);edit 拒绝用例 monkeypatch 目标改为 route(机制验证);新增 4 个验收用例: + - edit「润色一下第一段」→ 200(route 判 edit 放行) + - export 合法 format → 200(无护栏) + - export 非法 format → 422 VALIDATION(白名单仍生效) + - edit 问答类指令「帮我查个文献」→ 422 INTENT_REJECTED(护栏仍生效) + - generate「今天天气怎么样」→ 422 INTENT_REJECTED(真实 route 判定) + +### 自测结果 +- `pytest sdk/app/routers/test_docgen_api.py sdk/app/services/test_docgen_*.py sdk/app/test_db.py sdk/app/test_sessions_api.py -q` → **135 passed** +- 污染回归组合:`test_docgen_api.py + tooling/scripts/test_docgen_import.py` → 45 passed;`+ sdk/app/services/test_docgen_*.py + vstore/tests/` → 193 passed +- 均无 ERROR、无 FAILED diff --git a/docs/team/report_dev3.md b/docs/team/report_dev3.md new file mode 100644 index 0000000..1dd9d7d --- /dev/null +++ b/docs/team/report_dev3.md @@ -0,0 +1,87 @@ +# DEV3 P3 自测报告 —— 前端 UI(自然触发 + 文档 TAB + 能力徽标 + 右栏可拖宽) + +- 日期:2026-09-03 +- 归属:开发 agent-3(P3 前端 UI) +- 事实源:docs/team/brief_dev3_p3.md、docs/team/cases_intent.md、docs/docgen_team_context.md + +## 一、完成模块清单(按任务书 A–L) + +| 编号 | 文件 | 状态 | 说明 | +|---|---|---|---| +| A | ui/src/api/docgen.js | 新增 | uploadLibrary / generate / getArtifactContent / saveArtifactContent / editArtifact / exportArtifact / listLibrary / downloadUrl,全部经 http.js 封装(默认 600s 读超时兜底);端点契约按 P2 任务书 docgen.py 约定(dev2 开发中,联调后生效) | +| B | ui/src/i18n/docgen.js | 新增 | DOCGEN_STEPS 五步(解析资料/检索模版/规划大纲/逐节生成/导出文档,相位复用 PHASE_COLOR_VAR 键 planning/retrieval/evidence/generation/done)、CAP_BADGES 能力徽标文案、DG 全量新文案 | +| C | ui/src/intent.js | 新增 | 纯函数无 Vue 依赖,与后端 intent.py 同词典同规则(EXPORT_WORD/FORMAT_WORD/EDIT_WORD/WRITE_VERB/DOC_TYPE/DOC_TYPE_EXCLUDE 与 cases_intent.md 逐词一致);漏判安全(不命中一律 qa) | +| D | ui/src/stores/docgen.js | 新增 | 文档 TAB 状态:activeArtifact/md/editing/exporting/templateCandidates;不 import bench.js 状态,只调 bench.setActiveTab(动态 import 防循环依赖) | +| E | ui/src/components/chat/Composer.vue | 修改 | 拖拽上传(dragover 高亮遮罩 → drop 预检 20MB×5 → uploadLibrary 入库 → chips 可移除/失败可重试);发送按 intent.js 分流:docgen → chat.sendDocgen、edit → docgen store 预填指令+切 TAB、export → 导出请求、qa → 原 emit('send') 路径(附件注记进消息文本)。send() 原逻辑零改动 | +| F | ui/src/stores/chat.js | 修改 | 新增 newDocgenSkeletonMessage(kind:docgen,pipeline 块 capabilities:[{key:doc_writer,label:文档生成}],steps 五步)、collectQaCaps/injectQaCaps(QA 路径注入本次启用能力 chips:联网搜索/答案润色/Superpowers,label 取 settings store manifest)、sendDocgen(description, libraryIds)(用户消息落库→骨架→generate 600s→成功 pipeline done + artifact 块 + 持久化 + 自动切 bench.setActiveTab('doc'),失败 _errorMarker 标注可重试)、retry 对 docgen 消息同位置替换走文档生成路径。send/_runStream 零改动(仅骨架落库后一行 injectQaCaps) | +| G | ui/src/components/chat/PipelineCard.vue | 修改 | pc-head 加 capabilities 徽标区(v-for chip,复用 badge 视觉,渐变描边);block.capabilities 缺省时零渲染;标题支持 block.title 覆盖(docgen 显示「文档生成流水线」) | +| H | ui/src/components/chat/DocCard.vue | 新增 | 消息流内文档卡片:标题/模版名/「打开预览」(自足闭环:docgen store 加载 + bench 切文档 TAB,不向上游抛事件) | +| I | ui/src/components/bench/DocPane.vue | 新增 | 第 5 TAB 主体:markdown-it 渲染预览(复用 markdown.js 消毒);块级就地编辑(标题/段落/列表/表格行 contenteditable,blur 序列化回 md + debounce 800ms PATCH);「用 prompt 修改」输入行(edit 端点);导出按钮组(PDF/DOCX → download_url 下载,手动兜底) | +| J | ui/src/components/bench/BenchPanel.vue | 修改 | tabs 数组 = labels.BENCH_TABS + DOCGEN_TAB{key:doc,label:文档}(不碰 labels.js 红线);pane 链 v-else-if 挂 DocPane(两处模板改动,无逻辑改动) | +| K | ui/src/components/chat/AssistantMessage.vue | 修改 | 块渲染加 artifact 分支(DocCard) | +| L | ui/src/components/layout/AppShell.vue + ui/src/styles/base.css | 修改 | ① .bench-resizer 左边缘 8px col-resize 拖拽实时改宽(min 300px / max 60vw,mouseup 持久化 localStorage pa-bench-width,初始 380px,折叠态隐藏不拖拽,拖拽中禁用过渡跟手);② 展开/收起按钮改左栏 .side-handle-btn 同款(26×26 圆角、hairline 描边、surface-1 底、跨右栏左边缘半嵌 left:0+translate(-50%,-50%)、垂直居中、图标 13px);③ 箭头镜像左栏:折叠态朝左(指向内容侧)、展开态朝右;④ 折叠态 28px 竖条保留为点击区(bench-wrap 条身点击展开,按钮嵌其上同款式);⑤ 宽度变化与手柄位置联动(transition 0.3s 同左栏,.bench-resizing 时禁用过渡) | + +## 二、自测结果 + +1. **intent.js 30 条用例(docs/team/cases_intent.md 表)**:30 / 30 通过。 + 测试方式:node --input-type=module 直接 import ui/src/intent.js 跑断言脚本(intent.js 保持纯函数无 Vue 依赖,脚本存于 /tmp/test_intent_dev3.mjs,未落仓库)。英文格式词已按事实源要求做大小写不敏感判定(PDF→pdf)。 +2. **新 JS 文件语法**:node --check 全部通过(intent.js / api/docgen.js / i18n/docgen.js / stores/docgen.js / stores/chat.js)。 +3. **.vue 单文件组件编译**:@vue/compiler-sfc 逐文件编译(script setup + template)7 个改动/新增 .vue 全部通过(Composer / DocCard / DocPane / PipelineCard / AssistantMessage / BenchPanel / AppShell)。检查脚本在 /tmp/vue_sfc_check.mjs,未落仓库。 +4. **既有前端单测回归**:`npx vitest run` 全量 172 条 —— 171 通过,1 失败: + - 失败项:tests/frontend/unit/b3/benchpanel.test.js「四 tab 来自 labels.BENCH_TABS」期望 4 个 tab,实际 5 个(P3 按任务书 J 新增「文档」TAB 的必然结果,属任务书要求的行为变化)。 + - b1 appshell.test.js 7/7 通过(旧 .bench-collapse-btn 保留在 DOM 仅 CSS 退役,折叠断言未破坏); + - b2 chat.store.test.js 19/19、b2 components.test.js 20/20 通过(send/_runStream 零改动 + 徽标区缺省零渲染已回归验证)。 + +## 三、已知限制 + +1. **后端联调未做**:docgen 端点契约按 P2 任务书编写(dev2 开发中)。api/docgen.js 的路径/字段(items[].library_id、artifact.artifact_id、download_url 等)待 BFF 联调后可能微调。 +2. **前端到后端的 artifact 字段别名**:generate 响应可能返回 artifact_id 或 id,api 层已容错。 +3. **pipeline 中间状态是简化模拟**:sendDocgen 等待 600s 同步 generate 期间,仅把 planning 步置 running(无法获得后端实时阶段);生成成功/失败后整体置 done/error。若后端后续支持进度回调,可在此扩展。 +4. **附件 chips 的引用持久性**:用户消息落库仅 text(现有 messages 接口只有 text 字段),library_ids 放在前端消息 meta 上,会话刷新后不保留。重试时退化为无附件生成(不阻塞)。 +5. **DocPane 就地编辑不支持全部 markdown**:序列化覆盖标题/段落/列表/表格/引用/代码块,其它行内格式(加粗/斜体/链接)在编辑序列化后会丢失(预览完整保留)。 +6. **intent.js 的 exclude 过滤逻辑**:按事实源表的 30 条用例全部通过,但事实源未指定「exclude 词命中时若同时有 doc_type 应如何处理」的精确算法(现为:排除词优先否决,即便同时含文档类型词)。后端 dev1 实现若与我对齐无碍;若有差异需按用例表裁决。 +7. **session_caps 未加载时能力徽标不显示**:QA 能力徽标依赖 settings store 已加载 manifest(现状是只有打开设置面板才 load)。若希望问答时徽标始终准确,需在会话创建/发送时触发 settings.load——涉及 settings store 调用时机,交 TL 裁决。 + +## 四、需要 TL 裁决/联动的问题 + +1. **b3 benchpanel.test.js 需更新**(测试 agent 归属):第 5 个「文档」TAB 是任务书 J 要求的行为变化,该断言「tabs 长度为 4」需改为 5 并追加文档 TAB 用例。 +2. **B1 的 .bench-collapse-btn 保留方案确认**:旧折叠按钮保留 DOM 但 CSS display:none(为了不破坏 b1 appshell.test.js 的折叠断言)。这是「视觉退役但保留 DOM」的折中,若 TL 希望彻底移除并同步更新 b1 测试,请告知。 +3. **右栏拖宽覆盖 laptop 档(1024–1279px)固定 340px 列宽**:P3 用 .app-shell 内联 grid-template-columns 覆盖了 responsive.css 的 laptop 档固定列宽(用户拖宽优先于档位默认收窄);B1 原「laptop 自动收窄 340px」的行为被替换为「保持 380px/用户宽度」。如需保留档位收窄行为,需 B1 联动确认。 +4. **能力徽标显示时机**(见限制 7):settings store 的 load 时机是否要与会话创建/发送联动。 +5. **artifact 块落库的持久化结构**:前端落库 artifact 块用 {type:'artifact', artifact_id, title, template_name, status};P2 的 schemas.py 定义了 ArtifactBlock 字段(artifact_id/title/template_name/status),前端已按此对齐,请 dev2 联调时确认无出入。 +6. **api/docgen.js 端点契约确认**:uploadLibrary 返回 items[].library_id(P2 契约);若 dev2 实际实现返回结构不同,需按实际调整 docgen.js 与 Composer 的 chip 解析(集中在 api 层与 Composer.uploadOne 两处)。 + +## 五、TL 裁决跟进(2026-09-03 微补丁) + +- 裁决 1/2/3/5/6:无需改动(测试 agent 更新 b3 测试、保留 DOM 折中接受、拖宽优先于档位收窄列入 R1 已知行为变化、契约已核实对齐)。 +- 裁决 4 已实施:Composer.vue onMounted 增加「manifest 为空时静默 load 一次」(manifest 为空时 settings.load(sessionId),sessionId 取自 chat store,失败静默;不碰 send/_runStream 内部)。自测:@vue/compiler-sfc 编译 7/7 通过;b2 components.test.js + b1 appshell.test.js 27/27 通过。 + +## 六、R1 修复记录(2026-09-03) + +TL 审阅 R1 结果定性 2 个 bug,已修复并自测: + +1. **BUG-2(FAIL,已修)**:DocPane 编辑模式 contenteditable 未接线——原实现误以为 CSS 能加 HTML 属性(注释「由 :deep 样式承担」不成立),编辑模式实际不可编辑。修复:DocPane.vue 新增 `wireContenteditable()`,`enterEdit` 与 `onEditBlur` 重建后在 nextTick 遍历 editRoot.children 程序化设置 contenteditable="true"(h1-h6/p/pre/blockquote 给元素本身,ul/ol 给 li,table 给 tr),不改 renderMarkdown 全局。附带:onEditBlur 加 30ms 防抖,避免 blur→重建→focus 迁移的二次 blur 重复序列化。 +2. **BUG-3(契约缝隙,已修)**:后端 ArtifactOut 无 markdown 字段,生成成功切 TAB 显示「内容不可用」。修复:docgen store 的 adoptArtifact 改为 `adoptArtifact(artifact, {initialMd})`——无本地 md 时自动调 getArtifactContent 拉取后再渲染;chat.js sendDocgen 成功路径把 payload.markdown(若有)作为 initialMd 传入。 +3. **BUG-4(顺带修)**:open 同 id 短路不拉取。修复:同 id 且有本地内容且非 force 才复用;切换不同文档或 force 时清旧 md 再拉取(避免旧内容残留与误判)。删除废弃的 setContent(由 initialMd 取代)。 + +自测结果: +- node --check:chat.js / docgen.js / intent.js / api 层全部通过 +- @vue/compiler-sfc 编译检查:7 个 .vue 全部通过 +- vitest 全量:**173 / 173 通过**(测试 agent 更新后的 b3 用例含文档 TAB) +- R1 临时 mount 级自测(不落仓库,跑完即删):DocPane 编辑接线(h1/p/li contenteditable=true)、blur 序列化重建后仍接线、adoptArtifact 自动拉取、open 短路语义 4 条全部通过 + +## 七、R2 修复记录(2026-09-03) + +TL 审阅 R2 结果定性 DC-UI-08 为修复不完整(真实 bug):根 div 挂 @blur 收不到事件——**blur 不冒泡**,子 contenteditable 块失焦时 onEditBlur 永远不触发(真实用户流程 PATCH 次数=0)。已修复并自测: + +1. **事件监听**:`@blur` → `@focusout`(focusout 冒泡,根 div 可收到所有子块失焦)。onEditBlur 增加 relatedTarget 判断:`editRoot.contains(e.relatedTarget)` 时 = 块间焦点迁移,不序列化(防重建打断输入)。 +2. **焦点恢复**:失焦块在顶层 children 的索引被记录,序列化重建后 nextTick 恢复焦点到同索引块(体验:焦点不丢失)。 +3. **退出编辑 flush**:点「预览」退出时 focusout 定时器会因 editing=false 被跳过——exitEdit 现在同步序列化 flush 最后一次未保存编辑(不 flush 会丢字)。 + +自测结果: +- @vue/compiler-sfc 编译检查:7 个 .vue 全部通过 +- vitest 全量:**173 / 173 通过** +- R2 临时行为测试(mount 级,不落仓库,跑完即删)3/3: + - 块间焦点切换(relatedTarget 在编辑区内)不触发序列化保存 + - 失焦到编辑区外触发一次序列化 + PATCH(debounce 合并),重建后仍接线 + - 退出编辑 flush 最后一次未保存编辑 diff --git a/tooling/compile/compile_sdk.sh b/tooling/compile/compile_sdk.sh index 659af6b..9becc45 100755 --- a/tooling/compile/compile_sdk.sh +++ b/tooling/compile/compile_sdk.sh @@ -31,10 +31,14 @@ rsync -a --exclude '__pycache__' --exclude 'test_*.py' --exclude 'data' --exclud "$SOURCE_ROOT/" "$OUTPUT_DIR/" # 白名单明文:main.py + config.py + __init__.py(编译前从发现集排除) -WHITELIST_PY=("app/main.py" "app/config.py" "app/schemas.py") +WHITELIST_PY=("app/main.py" "app/config.py" "app/schemas.py" "app/db.py" "app/routers/qa.py" "app/services/sse_relay.py") # 白名单理由:main.py=uvicorn 入口;config.py=运维可读面; # schemas.py=pydantic v2 模型(validator 方法被 Cython 转 cyfunction 触发 -# PydanticUserError,实测编译后无法导入——纯数据契约面,无敏感逻辑,明文保留) +# PydanticUserError,实测编译后无法导入——纯数据契约面,无敏感逻辑,明文保留); +# db.py=2026-09-03 断流崩溃根治:客户端中途断开流时 Cython 协程 +# __Pyx_Coroutine_SendEx 断言 abort 整个 BFF(Cython 3.2.8/3.3.0 均复现, +# A/B 证据见 docs/team/deploy_plan.md 附档)。db 是崩溃协程热点, +# 降级明文 Python 走 CPython 原生协程取消,根治断言崩溃 _is_whitelisted() { local rel="$1" for w in "${WHITELIST_PY[@]}"; do [[ "$rel" == "$w" ]] && return 0; done