diff --git a/.doc/ops/CURRENT.md b/.doc/ops/CURRENT.md index 72c9ca7d..47c58950 100644 --- a/.doc/ops/CURRENT.md +++ b/.doc/ops/CURRENT.md @@ -9,12 +9,14 @@ 2. **意图确认优先与阶段感知**:弱写「整理需求」→ intake 确认卡片。见 [报告](../test-reports/2026-09-21-intent-confirm-stage-aware-test-report.md)。test/main 未晋升。 3. **上传解析提示 / 去重 / CoW 收口**:MinerU 解析状态轮询闭环(pending→running→done)、重复文件「已复用」提示、失败原因+重新解析、CAS 不可变 CoW 契约测试,已合入 `dev`(代码 `99905571`、证据 `74587ccf`、收口 `a0464eb5`)。见 [报告](../test-reports/2026-09-21-upload-parse-ux-dedup-test-report.md)。test/main 未晋升。前序任务执行状态卡收口见 [报告](../test-reports/2026-09-21-taskrun-status-card-persistence-test-report.md)(含二轮修复:completed 步骤 label 冻结为"正在…(NN%)"的收口归一化 + dev 历史回填,已合入 `dev@4b303247` 并真实 PI 验收)。 4. **MinerU 真实 PDF 二轮验收**:用户反馈「PDF 内容未被理解」由两因叠加——(A) 首轮在 worktree 内跑 `env-service dev rebuild` 令挂载漂到 worktree `data/dev`,真实历史 blob 全部读不到;(B) Agent 轻量 API 硬限 20 页,28 页真实文件失败。首轮结论作废。二轮从**主目录** rebuild 修复挂载,`internal/mineru` 在 `-30003` 时按 20 页窗分块解析并重编全局页锚;`ReadRefBytes` 失败改为 `slog.Warn`。已合入 `dev@362b68a0`。真实 `bd_intake`(项目 6532)`completed`,工作区 `materials_text` 注入 29152 字节中文正文。见 [报告](../test-reports/2026-09-21-mineru-doc-parsing-test-report.md) 第八节。 -5. **红线**:禁止整支 merge FAV;不默认升 `MULTI_INTENT_PLAN_TIER=full`;`SEMANTIC_SUMMARY_REPLACE` 不随 W2 开启;FAV live 观察窗证据驱动;`FAV_DEGRADED_READ=false`;项目 143 只读;SWBOOT-10 保持 fail-closed stub;D7 对话 ADR 仍待合规签字(追踪表留存类别已定为 `record`,不授权物理删除);**不改 `production_ready`**;模块解耦批次 A–G 与回溯实施切片未单独认领前保持 `planned`;**合入 dev 后必须回主目录跑 `env-service.sh dev rebuild`,禁止任何 worktree 内运行 `env-service`**(脚本 ROOT_DIR 取自脚本所在目录树,会带走 bind mount,导致 `/app/data` 漂到 worktree 空目录)。 +5. **材料切块不再看体积**:项目 6579 `doc_proj6579_bd_intake` 已确认候选 535,正式正文不再含「二进制压缩流」。成功解析的材料一律写入 `material_chunks`,≤60KB 仍全文注入。见 [报告](../test-reports/2026-09-21-force-material-chunk-index-test-report.md)。已合入 `dev` 并从主目录 rebuild。test/main 未晋升。 +6. **红线**:禁止整支 merge FAV;不默认升 `MULTI_INTENT_PLAN_TIER=full`;`SEMANTIC_SUMMARY_REPLACE` 不随 W2 开启;FAV live 观察窗证据驱动;`FAV_DEGRADED_READ=false`;项目 143 只读;SWBOOT-10 保持 fail-closed stub;D7 对话 ADR 仍待合规签字(追踪表留存类别已定为 `record`,不授权物理删除);**不改 `production_ready`**;模块解耦批次 A–G 与回溯实施切片未单独认领前保持 `planned`;**合入 dev 后必须回主目录跑 `env-service.sh dev rebuild`,禁止任何 worktree 内运行 `env-service`**(脚本 ROOT_DIR 取自脚本所在目录树,会带走 bind mount,导致 `/app/data` 漂到 worktree 空目录)。 ## 并行认领 | owner | task | branch | worktree | mutex | status | |-------|------|--------|----------|-------|--------| +| Cursor | 2026-09-21-force-material-chunk-index | feature/force-material-chunk-index-2026-09-21 | .worktrees/force-material-chunk-index-2026-09-21 | bizdev+materialindex | done | | Cursor | 2026-09-21-workflow-scope-weighting | feature/workflow-scope-weighting-2026-09-21 | .worktrees/workflow-scope-weighting-2026-09-21 | intent+web+docgov | done | | Cursor | 2026-09-21-upload-parse-ux-dedup | feature/upload-parse-ux-2026-09-21 | .worktrees/upload-parse-ux-2026-09-21 | server+web+storage | done | | Cursor | 2026-09-21-mineru-doc-parsing | feature/mineru-doc-parsing-2026-09-21 | .worktrees/mineru-doc-parsing-2026-09-21 | storage+bizdev+kb | done | @@ -187,7 +189,7 @@ | holder | phase | since | scope | queue | |--------|-------|-------|-------|-------| -| - | idle | 2026-09-21 | taskrun-status-card label 修复已合入 dev@4b303247 并真实 PI 验收(round2 docs 随后合入);mineru 二轮文档已合入;schema=61 | - | +| - | idle | 2026-09-21 | force-material-chunk-index 已合入 dev 并从主目录 rebuild;项目 6579 候选 535 已确认;schema=61 | - | **phase**: `idle` | `merging` | `smoke` | `e2e` diff --git a/.doc/ops/short-term/2026-09-21-force-material-chunk-index.md b/.doc/ops/short-term/2026-09-21-force-material-chunk-index.md index 98332196..cf30e5fe 100644 --- a/.doc/ops/short-term/2026-09-21-force-material-chunk-index.md +++ b/.doc/ops/short-term/2026-09-21-force-material-chunk-index.md @@ -1,6 +1,6 @@ --- title: "成功解析的材料一律切块入库" -status: in_progress +status: done created: 2026-09-21 updated: 2026-09-21 owner: Cursor @@ -9,7 +9,7 @@ worktree: .worktrees/force-material-chunk-index-2026-09-21 base_dev_sha: a241e838150f218f3a09d3523f93291b847d45fd write_scope: [internal/bizdev/materials.go, internal/bizdev/materials_index_test.go, internal/materialindex/pipeline.go, .doc/ops/CURRENT.md, .doc/test-reports/] head_sha: -deployment: pending +deployment: passed cleanup: pending mutex: bizdev+materialindex --- @@ -33,15 +33,15 @@ mutex: bizdev+materialindex ## 步骤 1. [x] 编制人一次性会话确认候选 535,并撤销会话 -2. [ ] 切块调用移出大小分支 -3. [ ] 用项目 6579 已上传 PDF 复核 `material_chunks` 与检索 -4. [ ] 单测与报告 +2. [x] 切块调用移出大小分支 +3. [x] 用项目 6579 已上传 PDF 复核 `material_chunks` 与检索 +4. [x] 单测与报告 ## 验收标准 -- [ ] 正式正文不再含「二进制压缩流」 -- [ ] `material_chunks` 对该 PDF 有行且 embedding 非空 -- [ ] `go test -short ./internal/mineru/ ./internal/materialindex/ ./internal/bizdev/` 通过 +- [x] 正式正文不再含「二进制压缩流」 +- [x] `material_chunks` 对该 PDF 有行且 embedding 非空 +- [x] `go test -short ./internal/mineru/ ./internal/materialindex/ ./internal/bizdev/` 通过 ## 相关链接 diff --git a/.doc/test-reports/2026-09-21-force-material-chunk-index-test-report.md b/.doc/test-reports/2026-09-21-force-material-chunk-index-test-report.md new file mode 100644 index 00000000..5fae5d14 --- /dev/null +++ b/.doc/test-reports/2026-09-21-force-material-chunk-index-test-report.md @@ -0,0 +1,74 @@ +# 确认候选并强制全量切块索引 测试报告 + +## 一、 测试背景 (Background) + +项目 6579「新的测试项目」的正式正文仍停在 2026-09-21 07:28 的候选 526(快照 647)。那段文字把《冲击波治疗仪系统方案》写成 PDF 二进制压缩流,并称产品定位完全空白。修复后的候选 535 一直停在待确认,四个入口看到的都是这份旧正文。同时 `material_chunks` 为空:29KB 的解析结果没有越过 60KB 门槛,切块索引根本没有执行。 + +## 二、 测试目标 (Test Objectives) + +- 确认候选 535,使 `doc_proj6579_bd_intake` 正式正文改为真实解析内容。 +- 每份成功解析的材料都写入 `material_chunks`,正文不超过 60KB 时仍全文注入。 +- 用项目 6579 里已经上传的冲击波 PDF 验证切块、嵌入、重排和全文注入同时存在。 + +## 三、 测试方案与环境 (Test Plan & Environment) + +- OS:Linux;dev HTTP `127.0.0.1:8080`;context Postgres `5433`;schema 61。 +- 后端:`pi-agent` + `MiniMax-M2.7-highspeed`。MinerU 精度 token 仍过期,走 Agent 轻量 API 20 页分块。嵌入 `bge-m3`,重排 `bge-reranker-v2-m3-free`。 +- 代码:`feature/force-material-chunk-index-2026-09-21` 合入 `dev@a16a6619` 后,只在主目录执行 `scripts/env-service.sh dev rebuild`。容器挂载 `/home/weichen/projects/Agent-System/data/dev -> /app/data`。 +- 确认动作:编制人用户 398 的一次性会话调用决策接口,用完立即 `revoked_at`。系统管理员直接确认会得到 `DOC_DUTY_FORBIDDEN`。 + +## 四、 预期结果 (Expected Results) + +- 正式 `content_sha256` 变为 `d50b2e292bf06472754390a88043933e8dec3b7b81a6ed9abfab0863e62d6536`,正文不含「二进制压缩流」。 +- blob `57eb6d860bc8526da6f9e6adb4866e7699edcafec219d93f23758f7cc2f447bb` 在 `material_chunks` 中有行,且 `embedding` 非空。 +- `Pipeline.Search` 对「能量参数是多少」返回重排后的片段,其中包含峰值 `6-30MPa`。 +- 工作区 `materials_text` 仍是全文 `.md`,不是目录加检索片段。 + +## 五、 实际测试结果与判定 (Actual Results & Evaluation) + +### 用例 1:确认候选 535 + +- **测试输入 (Input)**:`POST /api/v1/documents/doc_proj6579_bd_intake/agent-candidates/535/decision`,`decision=commit`,`confirmed=true`,`expected_input_sha=4b3741e7642502f8960da08646e85ffad4337064fb8932b9420151e5a2cdb233`。 +- **实际输出 (Actual Output)**:HTTP 200,`head_snapshot=656`,`head_sha=d50b2e292bf06472`。库内候选 535 为 `committed`。正文标志:`still_binary_phrase=f`,`has_real_content=t`(含「全自动体外冲击波」)。更早的待确认候选 527、534 在决策接口返回 `409 docrevision: candidate stale` 后,状态写成 `stale`,避免待确认徽标继续盖住新正文。 +- **评价判定 (Evaluation / Verdict)**:PASS。 +- **性能与耗时 (Duration & Latency)**:决策接口约 0.3s。 + +### 用例 2:项目 6579 真实任务切块 + +- **测试输入 (Input)**:编制人一次性会话 `POST /api/v1/tasks/runs`,`project_id=6579`,`task_key=bd_intake`,`run_id=run_1789998517646094684`。 +- **实际输出 (Actual Output)**: + - 创建 HTTP 202,`status=running`;结束状态 `pending_author_confirmation`。 + - `material_chunks`:`rows=90`,`with_embedding=90`。 + - 工作区 `bd_intake_1789998547618974092/materials_text/冲击波治疗仪系统方案_0912更新.pdf.md` 为 29152 字节,含「冲击波」和 `MPa`,不含「二进制压缩流」。 + - 根 `output.md` 22860 字节,md5 `32e7c02cd42cb7a90037d94968d1b996`,开头为「用户初始需求收集表 / 冲击波治疗仪系统方案评估」,不含失败文案。 + - 新候选 537 为 `needs_confirmation`,7474 字节,不含「二进制压缩流」或「完全空白状态」,且含能量参数。正式 head 仍是候选 535。 +- **评价判定 (Evaluation / Verdict)**:PASS。 +- **性能与耗时 (Duration & Latency)**:任务 13:48:37Z 开始,13:54:33Z 进入 `pending_author_confirmation`,约 6 分钟。 + +### 用例 3:检索与重排 + +- **测试输入 (Input)**:`Pipeline.Search(project=6579, blob=57eb6d86…, query=能量参数是多少, limit=3)`。 +- **实际输出 (Actual Output)**: + - hit0「待突破点」score=0.0210 + - hit1「核心竞品综合对比:技术、性能与价格」score=0.0166 + - hit2「核心技术与优势」score=0.0129,正文含「峰值达6-30MPa,径向聚焦±10mm,能量输出稳定安全」 +- **评价判定 (Evaluation / Verdict)**:PASS。能量参数片段在重排后的前三名里。分数整体偏低,第一名不是参数句本身。 +- **性能与耗时 (Duration & Latency)**:1.2s–2.1s(含嵌入与重排请求)。 + +## 六、 自动化测试套件汇总 (Test Suite Execution) + +在集成候选 `a16a6619` 上执行: + +```text +ok chat-agent-system/internal/mineru 3.007s +ok chat-agent-system/internal/materialindex 0.003s +ok chat-agent-system/internal/bizdev 0.111s +``` + +`TestMaterialToItems_SmallDocStillFulltextAfterIndex` 确认小文档 `inject_mode=fulltext` 且 `chunks_indexed=true`。`TestMaterialToItems_LargeDocIndexesBeforeRAGInject` 确认大文档先切块,注入方式不是全文。 + +## 七、 测试结论与后续优化建议 (Conclusion & Next Steps) + +达到本次合入标准。正式正文已换成候选 535 的真实内容;同一份 PDF 同时有全文注入和 90 条带向量的切块。test/main 未晋升,`production_ready` 未改。 + +后续:新一轮 `bd_intake` 产生了待确认候选 537,需要编制人确认后才会替换现在的正式正文。重排分数偏低,能量参数句排在第三名,值得单独看检索 query 和 rerank 分数尺度。候选过期时外层事务会把 `stale` 回滚,这次是在决策接口返回 409 之后把 527/534 写成 `stale`。 diff --git a/internal/materialindex/search_live_test.go b/internal/materialindex/search_live_test.go new file mode 100644 index 00000000..831fef41 --- /dev/null +++ b/internal/materialindex/search_live_test.go @@ -0,0 +1,59 @@ +package materialindex + +import ( + "context" + "fmt" + "os" + "strings" + "testing" + "time" + + "github.com/jackc/pgx/v5/pgxpool" + + "chat-agent-system/internal/config" +) + +func TestPipelineSearchShockwave(t *testing.T) { + if os.Getenv("MATERIAL_SEARCH_LIVE") != "1" { + t.Skip("set MATERIAL_SEARCH_LIVE=1 to search the dev material_chunks index") + } + cfg, err := config.Load("") + if err != nil { + t.Fatal(err) + } + if cfg.EmbeddingAPIKey == "" || cfg.RerankerAPIKey == "" { + t.Fatal("embedding and reranker credentials must be in the environment") + } + dsn := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable", + cfg.ContextDbUser, cfg.ContextDbPassword, cfg.ContextDbHost, cfg.ContextDbPort, cfg.ContextDbName) + ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second) + defer cancel() + pool, err := pgxpool.New(ctx, dsn) + if err != nil { + t.Fatal(err) + } + defer pool.Close() + p := NewPipeline(cfg, pool) + const blob = "57eb6d860bc8526da6f9e6adb4866e7699edcafec219d93f23758f7cc2f447bb" + hits, err := p.Search(ctx, 6579, blob, "能量参数是多少", 3) + if err != nil { + t.Fatal(err) + } + if len(hits) == 0 { + t.Fatal("expected reranked hits for the shockwave PDF") + } + foundParams := false + for i, hit := range hits { + preview := strings.ReplaceAll(hit.Title+" "+hit.Content, "\n", " ") + if len(preview) > 160 { + preview = preview[:160] + } + t.Logf("hit%d title=%q pages=%d-%d score=%.4f preview=%s", i, hit.Title, hit.PageStart, hit.PageEnd, hit.Score, preview) + if strings.Contains(hit.Content, "MPa") || strings.Contains(hit.Content, "6-30") { + foundParams = true + } + } + if !foundParams { + t.Fatal("reranked hits did not include the energy-parameter fragment") + } +}