diff --git a/mvp/README.md b/mvp/README.md index 1defdd8..21c3117 100644 --- a/mvp/README.md +++ b/mvp/README.md @@ -14,7 +14,7 @@ | [architecture/agent-orchestration.md](architecture/agent-orchestration.md) | Agent 编排架构 | | [architecture/harness-quality-gates.md](architecture/harness-quality-gates.md) | Harness 与质量门禁 | | [architecture/session-trace-lifecycle.md](architecture/session-trace-lifecycle.md) | 会话与 Trace 生命周期 | -| [architecture/RAG知识检索架构.md](architecture/RAG知识检索架构.md) | 当前 hybrid 检索架构 | +| [architecture/RAG知识检索架构.md](architecture/RAG知识检索架构.md) | 当前检索架构(py-rag 知识服务接入) | | [issues/README.md](issues/README.md) | MVP issue 索引 | | [tables/README.md](tables/README.md) | 当前 MySQL 表说明 | | [demo/README.md](demo/README.md) | Demo 运行和演示材料 | diff --git a/mvp/architecture/RAG检索可观测性与审计.md b/mvp/architecture/RAG检索可观测性与审计.md index 493b4f1..b6ec017 100644 --- a/mvp/architecture/RAG检索可观测性与审计.md +++ b/mvp/architecture/RAG检索可观测性与审计.md @@ -1,8 +1,14 @@ # RAG 检索可观测性、审计与 Trace(现行) -**更新日期**:2026-07-28 +**更新日期**:2026-09-29 **状态**:当前可运行 -**关联**:`lookup_knowledge`、Harness `ToolBoundary`、`tool_invocation`、`DiagnosisTraceService`、离线 eval +**关联**:`lookup_knowledge`、Harness `ToolBoundary`、`tool_invocation`、`DiagnosisTraceService`、离线 eval + +> **2026-09-29 RAG 抽离影响**:检索后端切换为 py-rag 服务(见 [RAG知识检索架构.md](./RAG知识检索架构.md))。 +> Trace / 审计的三层边界与读写接口**不变**;变化仅在内容语义: +> L0 已下沉(`queryHints` 恒为空结构、`categoryFilter` 恒为 null、attempt 只剩 `UNFILTERED_VECTOR`), +> 质量分统一为 py-rag rerank 绝对分(scoreLabel=RERANK)。 +> 文中涉及 FILTERED/RETRY attempt 的示例为历史数据读法,保留供回放旧 Run。 --- @@ -119,21 +125,21 @@ flowchart TB | 字段 | 含义 | |------|------| | `originalQuery` | 原始查询 | -| `rewrittenQuery` | L0/变换后用于检索的 query | -| `categoryFilter` | 首次过滤的 category(可 null) | +| `rewrittenQuery` | 用于检索的 query(L0 下沉后恒等于 originalQuery) | +| `categoryFilter` | 首次过滤的 category(L0 下沉后恒为 null) | | `selectedAttempt` | 最终采用的 attempt 名 | | `fallbackReason` | 如 `filtered_vector_low_quality`;未降级为 null | | `evidenceStatus` | 内部:`supported` / `no_evidence` 等 | -| `queryHints` | L0:domains、keywords、entities、l0_match_count… | +| `queryHints` | L0 提示(下沉后恒为空 domains/keywords/entities 与 l0_match_count=0) | | `attempts[]` | 每次检索尝试快照 | -**常见 `selectedAttempt`:** +**`selectedAttempt` 取值:** | 值 | 含义 | |----|------| -| `FILTERED_VECTOR` | 带 category 的首次检索即采用 | -| `UNFILTERED_VECTOR` | 无 category,直接全库检索 | -| `UNFILTERED_VECTOR_RETRY` | filtered 低质/无证据后去掉 category 重试 | +| `UNFILTERED_VECTOR` | **当前唯一会出现**:无 category,直传 py-rag 检索 | +| `FILTERED_VECTOR` | (历史)带 category 的首次检索即采用;L0 下沉后不再产生 | +| `UNFILTERED_VECTOR_RETRY` | (历史)filtered 低质/无证据后去掉 category 重试;分支保留但不可达,仅见于旧 Run 回放 | **单次 `attempts[]` 元素:** @@ -148,21 +154,18 @@ flowchart TB | `durationMs` | 耗时 | | `errorMessage` | 失败时 | -### 3.3 一次典型路径(含 filter fallback) +### 3.3 一次典型路径(当前:单 attempt 直传) ```mermaid flowchart TB - Q[query] --> L0[L0 hint → 可选 categoryFilter] - L0 --> A1[attempt FILTERED_VECTOR] - A1 --> PQ{isLowQuality?} - PQ -->|否| USE1[selectedAttempt = FILTERED_VECTOR] - PQ -->|是| A2[attempt UNFILTERED_VECTOR_RETRY] - A2 --> USE2[selectedAttempt = RETRY
fallbackReason = low_quality / no_evidence] - USE1 --> POST[PostProcess · evidenceBlocks · relevanceLevel] - USE2 --> POST + Q[query 原始句直传] --> A1[attempt UNFILTERED_VECTOR
PyRagKnowledgeSearchAdapter → py-rag] + A1 --> POST[PostProcess · evidenceBlocks · relevanceLevel] POST --> LR[LookupResult 完整 Trace] ``` +> 历史 filter fallback 路径(L0 → FILTERED_VECTOR → 低质 → UNFILTERED_VECTOR_RETRY)的流程图已随 L0 下沉移除; +> 旧 Run 的 Trace 回放仍可见该结构,字段含义见 §3.2。 + ### 3.4 与 Agent 投影的关系 ```mermaid @@ -208,35 +211,27 @@ flowchart LR | `output_preview` | level/attempt 摘要 | status=… | | `duration_ms` / `success` | 有 | 有 | -### 4.3 `retrieval_details`(rag_lookup_v1)示例 +### 4.3 `retrieval_details`(rag_lookup_v1)示例(当前形态) ```json { "audit_schema": "rag_lookup_v1", "search_mode": "hybrid", - "selected_attempt": "UNFILTERED_VECTOR_RETRY", - "fallback_reason": "filtered_vector_low_quality", - "category_filter": "overfilter-decoy", - "evidence_keys": ["doc#chunk-0"], - "sources": ["doc"], - "evidence_candidate_count": 8, + "selected_attempt": "UNFILTERED_VECTOR", + "fallback_reason": null, + "category_filter": null, + "evidence_keys": ["e2e-gateway-b9c1fa12-md-34223174#chunk-1"], + "sources": ["e2e-gateway-b9c1fa12-md"], + "evidence_candidate_count": 5, "evidence_block_count": 2, - "l0_hints": { "domains": ["mysql"], "matched_keywords": ["pool"] }, + "l0_hints": { "domains": [], "matched_keywords": [] }, "attempts": [ { - "name": "FILTERED_VECTOR", - "category_filter": "overfilter-decoy", - "candidate_count": 2, - "usable": false, - "top_similarity": 0.3, - "duration_ms": 12 - }, - { - "name": "UNFILTERED_VECTOR_RETRY", + "name": "UNFILTERED_VECTOR", "candidate_count": 5, "usable": true, - "top_similarity": 0.9, - "duration_ms": 20 + "top_similarity": 0.91, + "duration_ms": 640 } ], "truncated": false, @@ -247,7 +242,8 @@ flowchart LR } ``` -**默认不落库:** 原始 query 全文、chunk 正文 excerpt、完整 rerankTrace(体积与隐私)。 +**默认不落库:** 原始 query 全文、chunk 正文 excerpt、完整 rerankTrace(体积与隐私)。 +历史 Run 中 `selected_attempt=FILTERED_VECTOR` / `UNFILTERED_VECTOR_RETRY` 与非空 `l0_hints` 为 L0 下沉前的旧数据形态。 ### 4.4 Trace API:人怎么读 RAG @@ -304,12 +300,12 @@ flowchart TB | 现象 | 优先看 | |------|--------| -| 为何走了 retry | `fallback_reason` + 两次 `attempts` | -| 是否 hybrid | `search_mode` | -| 滤错域 | `category_filter` + L0 domains | +| 是否 hybrid | `search_mode`(hybrid/semantic 对应 py-rag 融合/纯向量) | +| 滤错域 | (历史)`category_filter` + L0 domains;L0 下沉后恒为 null | | 相关度档 | 列 `relevanceLevel`(PRECISE/REFERENCE) | | 返回了哪些块 | `evidence_keys` / `sources`(无正文) | | Agent 是否被截断 | `truncated` / `returned_count` | +| 为何走了 retry | (历史)`fallback_reason` + 两次 `attempts`;L0 下沉后单 attempt,不再产生 | ### 4.6 diagnosis_trace 事件 vs tool_invocation 行 @@ -352,10 +348,11 @@ flowchart LR | 旧(archive `retrieval-observability`) | 现 | |----------------------------------------|-----| -| `vector-store.mode` 多后端 | `search_mode` dense\|hybrid,单一 V2 store | +| `vector-store.mode` 多后端 | `search_mode` dense\|hybrid(现映射 py-rag semantic\|hybrid) | | sink 理想化未落地 | `RagLookupAuditEnricher` + 列回填 | | `relevance_level` 混用 evidence_status | **列仅 RAG 等级**;契约状态在 details | | 未写清 Trace API 读法 | 本文 §4.4–4.5 | +| L0 hint / FILTERED-RETRY attempt(2026-07-28 形态) | 2026-09-29 L0 下沉 py-rag:单 attempt、queryHints 恒空、质量分 RERANK 直传 | --- diff --git a/mvp/architecture/RAG知识检索架构.md b/mvp/architecture/RAG知识检索架构.md index b0c6b91..69c7028 100644 --- a/mvp/architecture/RAG知识检索架构.md +++ b/mvp/architecture/RAG知识检索架构.md @@ -1,13 +1,16 @@ # RAG 知识检索架构 -**更新日期**:2026-07-28 +**更新日期**:2026-09-29 **状态**:当前可运行架构 -**关联实现**:`lookup_knowledge`、`MilvusHybridKnowledgeStore`、`KnowledgeSearchPort` -**关联运维**:`scripts/rebuild_hybrid_knowledge.py`、`POST /api/knowledge/rebuild-hybrid` +**关联实现**:`lookup_knowledge`、`KnowledgeSearchPort`、`PyRagKnowledgeSearchAdapter`、`PyRagClient` +**关联契约**:py-rag 仓库 `docs/Java接入文档.md`(API v1,冻结面) +**关联运维**:py-rag `/api/v1/collections:rebuild`(全量重建)、py-rag `/api/v1/documents:ingest`(单文档入库) ## 1. 定位 知识检索是 Diagnosis Agent 的显式证据工具,不是隐式 Advisor。 +检索算法(dense+BM25 融合、rerank、判级)与文档入库(解析、frontmatter、分块、向量化) +**全部由独立的 py-rag 知识服务承担**;Java 侧只保留 harness 消费面与 HTTP 客户端。 ```text Diagnosis Agent @@ -19,7 +22,7 @@ Diagnosis Agent 目标: - 保留 Agent 可见的工具调用与证据边界 -- 用单一向量后端完成 dense + BM25 hybrid 检索 +- 检索/入库基础设施外置为独立服务,Java 侧不感知引擎细节 - 用 chunk 级证据身份保证同文档多片段可同时进入上下文 - 检索行为可配置、可重建、可审计 @@ -41,336 +44,193 @@ flowchart LR subgraph RetrievalBoundary["Retrieval boundary"] Backend["LookupKnowledgeTool"] Port["KnowledgeSearchPort"] - Store["MilvusHybridKnowledgeStore"] + Remote["PyRagKnowledgeSearchAdapter"] + Service["py-rag 知识服务 (HTTP /api/v1)"] end Agent --> Tool Tool --> Adapter Adapter --> Backend Backend --> Port - Port --> Store + Port --> Remote + Remote -->|HTTP| Service Adapter --> Projector Adapter --> Canonical ``` | 边界 | 职责 | 不负责 | |---|---|---| -| Agent | 决定何时检索、如何用证据写报告 | 不直接访问 Milvus / MySQL 元数据表 | +| Agent | 决定何时检索、如何用证据写报告 | 不直接访问 py-rag / MySQL 元数据表 | | Harness | Tool 校验、投影裁剪、canonical 存证 | 不改写检索排序算法 | -| Retrieval | L0 hint、dense/BM25 召回、后处理、打包 | 不绕过 ACI 直接给 Agent 原始库响应 | +| Retrieval | 请求映射、后处理、打包;py-rag 承担召回/融合/rerank/判级 | 不绕过 ACI 直接给 Agent 原始库响应 | ## 3. 当前主链路 ```mermaid flowchart TD - A["lookup_knowledge(query)"] --> B["KnowledgeQueryTransformer"] - B --> C["L0 hint: domain / keywords / categoryFilter"] - C --> D["KnowledgeDocumentRetriever"] - D --> E["KnowledgeSearchPort"] - E --> F["VectorSearchService"] - F --> G{"retrieval.search.mode"} - G -->|dense| H["MilvusHybridKnowledgeStore.searchDense"] - G -->|hybrid| I["MilvusHybridKnowledgeStore.searchHybrid"] - H --> J["candidates + chunk identity"] - I --> J - J --> K["KnowledgeEvidencePostProcessor"] - K --> L["evidenceKey dedup / maxChunksPerDocument / return-n"] - L --> M{"filtered low quality?"} - M -->|yes and had categoryFilter| N["unfiltered retry"] - N --> K - M -->|no| O["KnowledgeContextPacker"] - O --> P["LookupResultAssembler"] - P --> Q["RagResultProjector"] - Q --> R["Agent-facing RagToolResult"] + A["lookup_knowledge(query)"] --> B["原始 query 直传(L0 已下沉 py-rag)"] + B --> C["KnowledgeDocumentRetriever"] + C --> D["KnowledgeSearchPort"] + D --> E["PyRagKnowledgeSearchAdapter"] + E -->|POST /api/v1/search| F["py-rag: dense+BM25 融合 / rerank / 判级"] + F --> G["hits + evidenceKey(docId#chunk-N)"] + G --> H["KnowledgeEvidencePostProcessor"] + H --> I["evidenceKey dedup / maxChunksPerDocument / return-n"] + I --> J["KnowledgeContextPacker"] + J --> K["LookupResultAssembler"] + K --> L["RagResultProjector"] + L --> M["Agent-facing RagToolResult"] ``` 对应代码: | 阶段 | 类 | 职责 | |---|---|---| -| Tool 编排 | `LookupKnowledgeTool` | 串联 transform / retrieve / post / pack | -| Query 理解 | `KnowledgeQueryTransformer` + `KnowledgeIndexService` | L0 只产 hint 与可选 category filter | -| 检索端口 | `KnowledgeSearchPort` / `VectorKnowledgeSearchAdapter` | 屏蔽底层存储细节 | -| 检索门面 | `VectorSearchService` | `dense` 或 `hybrid` 路由 | -| 向量后端 | `MilvusHybridKnowledgeStore` | 唯一知识库读写后端(MilvusClientV2) | -| 后处理 | `KnowledgeEvidencePostProcessor` | 归一化、规则 boost、chunk 去重、相关度等级 | +| Tool 编排 | `LookupKnowledgeTool` | 串联 retrieve / post / pack;UNFILTERED 单 attempt 主路径 | +| 检索端口 | `KnowledgeSearchPort` / `PyRagKnowledgeSearchAdapter` | 防腐层;请求映射 + 命中归一化 | +| HTTP 客户端 | `PyRagClient` | API v1 调用、错误信封(`E_*`)、`X-Request-ID`、分端点超时 | +| 后处理 | `KnowledgeEvidencePostProcessor` | qualityScore(RERANK 直传)、chunk 去重、相关度等级 | | 打包 | `KnowledgeContextPacker` | 有界 context pack | | 投影 | `RagResultProjector` | 只暴露 Agent 可见 evidence 字段 | -## 4. 唯一向量后端:MilvusClientV2 +2026-09-29 抽离时删除的 Java 侧组件:`MilvusHybridKnowledgeStore`、`VectorSearchService`、 +`VectorKnowledgeSearchAdapter`、`RrfFusion` / `LexicalRanker`(融合评分下沉)、 +`KnowledgeIndexService` / `KnowledgeDomainService`(L0 索引)、 +`DocumentChunkService` / `FrontmatterParser` / `TextExtractorService`(入库解析)、 +`KnowledgeQueryTransformer`(L0 query 理解)。 -### 4.1 已废弃路径 +## 4. py-rag 检索契约映射 -以下路径**不再**用于 `lookup_knowledge`: +请求映射(`PyRagKnowledgeSearchAdapter`): -- legacy `MilvusServiceClient` search / insert -- `retrieval.vector-store.mode=sdk|spring|auto` -- Spring AI `VectorStore` 作为知识检索主路径 +| Java(KnowledgeSearchRequest) | py-rag(/api/v1/search) | 说明 | +|---|---|---| +| `query` | `query` | 原始检索句直传,服务端自行处理边界与精排 | +| `mode=DENSE` | `mode=semantic` | 纯向量,对照/排障用 | +| `mode=HYBRID` | `mode=hybrid` | dense+BM25 融合,线上主路径(`retrieval.search.mode: hybrid`) | +| `topK` | `retrieve_k` / `return_n` / `max_chunks_per_document` | 三者同置 topK:chunk 去重截断由 Java 后处理器统一负责,避免服务端预截断 | +| `categoryFilter` | `category` | L0 下沉后恒为 null(不过滤) | +| — | `kb_scope` | 不传,由 py-rag 部署配置决定 | -### 4.2 当前后端 +响应映射: + +| py-rag | Java(KnowledgeSearchHit) | 说明 | +|---|---|---| +| `evidence_key` | `evidenceKey` / `docId` / `chunkIndex` | `docId#chunk-N`,与 EvidenceGuard 验真约定一致 | +| `excerpt` | `content` | 进入 context pack 的正文 | +| `quality_score` | `score` / `rawScore` | rerank 绝对相关分 [0,1],越大越好 | +| — | `scoreLabel=RERANK` | `RetrievalScoreNormalizer` 对 RERANK 分支 quality 原样 clamp,不走 L2/rank 归一化 | +| `relevance_level` | (参考值) | Java 后处理按同阈值(0.75/0.5)独立判级,语义一致 | +| `evidence_status=no_evidence` | 空列表 | 正常业务响应(服务端保证 hits=[]),Agent 侧按"无知识可用"处理 | + +超时与重试矩阵见 py-rag 仓库 `docs/Java接入文档.md` 第 6 节;Java 侧由 `pyrag.*` 配置承载。 + +## 5. 入库与重建 + +### 5.1 日常写入 ```text -写入: - VectorIndexService - -> MilvusHybridKnowledgeStore.upsertChunk +业务上传(DocumentController /api/documents/upload) + -> MySQL api_document(业务元数据:faultSource 等)+ 本地原件保存 + -> PyRagClient.ingest(multipart 透传原件 + category) + -> py-rag:解析 / frontmatter 校验 / 分块 / 向量化 / 索引 -读取: - VectorSearchService - -> MilvusHybridKnowledgeStore.searchDense - -> MilvusHybridKnowledgeStore.searchHybrid +简单上传(FileUploadController /api/upload) + -> 本地保存 + PyRagClient.ingest(category 可选参数,缺省 default;入库失败不影响上传成功语义) ``` -默认 collection: +MySQL `api_document.docId` 取 py-rag 返回的 `doc_id`(路径 slug + 内容 SHA-256 前 8 位), +与检索 `evidence_key` 的 docId 段对齐;`chunk_count` 取 ingest 响应。 + +### 5.2 全量重建 + +```text +POST /api/v1/collections:rebuild?confirm=REBUILD (py-rag 服务端) +GET /api/v1/tasks/{task_id} (任务状态) +``` + +- rebuild 为 py-rag 异步任务;执行期间 ingest 返回 409(`E_REBUILD_IN_PROGRESS`),search 不受影响 +- 原料为 py-rag 服务端 `data/knowledge_base/` 下历次 ingest 落盘的 md +- 旧 Java 侧 `POST /api/knowledge/rebuild-hybrid` 与 `scripts/rebuild_hybrid_knowledge.py` 已删除 + +### 5.3 单文档删除 + +py-rag API v1 没有单文档删除端点。`DELETE /api/documents/{docId}` 只删 MySQL 元数据与本地原件; +py-rag 侧已入库内容需全量重建后才会消失(见 `DocumentManagementService.deleteDocument` 注释)。 + +## 6. 部署与配置 ```yaml -milvus: - collection: biz +pyrag: + base-url: ${PYRAG_BASE_URL:http://localhost:8000} + connect-timeout-ms: 3000 + search-read-timeout-ms: 5000 # 正常 300–800ms(含 rerank 外呼) + ingest-read-timeout-ms: 30000 + default-read-timeout-ms: 10000 ``` -重建时会 drop + recreate 该 collection,并按 dense + BM25 schema 重建。 +- Milvus / etcd / MinIO 随 py-rag 部署,不再由本仓库 `docker-compose.yml` 管理(compose 仅剩 MySQL/Redis) +- `vector-database.yml` 已删除;Makefile 的 up/down/status 只管 MySQL/Redis +- `retrieval.search.mode: hybrid` 语义保留:映射 py-rag 的 `hybrid` / `semantic` -## 5. Collection Schema - -`biz`(可配置)逻辑字段: - -| 字段 | 类型 | 用途 | -|---|---|---| -| `id` | VarChar PK | chunk 级主键 | -| `content` | VarChar | 返回给 Agent 的原文片段 | -| `search_text` | VarChar + analyzer | BM25 输入文本 | -| `sparse_vector` | SparseFloatVector | BM25 Function 输出 | -| `vector` | FloatVector | dense embedding | -| `metadata` | JSON | docId / chunkIndex / category / kb_scope / title / breadcrumb 等 | - -Function: +## 7. 证据身份与去重(不变) ```text -BM25(search_text -> sparse_vector) -``` - -索引: - -```text -vector -> IVF_FLAT + L2 -sparse_vector -> SPARSE_INVERTED_INDEX + BM25 -``` - -写入时: - -- `content` 保存原始 chunk 正文 -- `search_text` / dense embedding 使用 `Title + Path + Content` 拼装文本 -- metadata 必须带 `docId`、`chunkIndex`,供 chunk 级证据身份使用 - -## 6. 检索模式 - -配置: - -```yaml -retrieval: - search: - mode: hybrid # dense | hybrid(见 6.0 用途约定) - hybrid: - rrf-k: 60 - kb-scope: "" -rag: - retrieve-k: 20 - return-n: 5 - max-chunks-per-document: 2 -``` - -### 6.0 模式用途约定(保留双 mode 的原因) - -知识库 **只维护一套** dense + BM25 schema 数据(默认 collection `biz`)。 -`retrieval.search.mode` 切换的是**同库上的查询算法**,不是两套互斥索引、也不是两套写入路径。 - -| 模式 | 定位 | 说明 | -|---|---|---| -| **hybrid** | **线上主路径 / 默认** | dense ANN + 服务端 BM25 + RRF;`lookup_knowledge` 正式召回只认此模式 | -| **dense** | **对照 / 评测 / 排障** | 仅 dense ANN,用于和 hybrid 对比召回效果(命中文档/chunk、排名差异等) | - -约定: - -1. 生产配置保持 `mode: hybrid`;不要把 dense 当成第二套长期并行的线上策略。 -2. 需要看「去掉 BM25+RRF 后召回差在哪」时,临时切 `mode: dense`,其它参数(`retrieve-k`、`return-n`、category filter、query 集)尽量固定,再切回 hybrid。 -3. hybrid 入库的数据 **完全适用于** dense-only 查询:每条 chunk 都写了 `vector`;dense 模式只是不使用 `sparse_vector` / BM25 子路。 -4. 代码里 `@Value` 在配置缺失时的兜底仍可能是 `dense`(历史兼容);**以 `application.yml` 的 hybrid 为准**。若做回归,确认运行配置而不是只看注解默认值。 - -不建议的用法: - -- 按请求/按租户在 dense 与 hybrid 之间当产品功能随意切换(当前也无稳定的 per-call mode 覆盖)。 -- 把 dense 模式的相关度表现直接当成 hybrid 的最终质量结论(hybrid 排序信 RRF,后处理分数仍多 L2 兼容,见下节)。 - -### 6.1 dense(对照基线) - -```text -query - -> embedding - -> dense ANN on vector - -> topK -``` - -仅走 `vector` 字段的 L2 ANN。用于基线对比,不作为正式主路径。 - -### 6.2 hybrid(当前默认 / 主路径) - -```text -query - -> path A: dense ANN(query embedding) - -> path B: BM25 sparse ANN(raw query text) - -> Milvus hybridSearch + RRFRanker(k) - -> topK fused hits -``` - -说明:hybrid **内部**的 dense 子路是融合的一部分,与配置项 mode=dense(整次检索只跑单路 ANN)不是同一概念。 - -分数与后处理(quality 统一,2026-07-28): - -- 一级 scoreLabel 仅 **dense | hybrid**(旧别名 canonicalize)。 -- **dense**:score = L2;qualityScore = 1 - clamp(L2)/maxL2Distance。 -- **hybrid**:返回序 = RRF 序;qualityScore 由 **本轮 rank 线性映射**(不把 RRF 原分当 L2;不做 dense L2 回填覆盖主分;无 m25_only_* 一级 label)。 -- 后处理:**统一**消费 qualityScore;排序主序 = originalRank;**不做** L0 关键词/domain contains 加分改序(重叠仅可写 hitReasons 解释)。 -- -elevance_level / category 低质 unfiltered retry:只看 top qualityScore 与阈值。 -- 实现:RetrievalScoreNormalizer、KnowledgeEvidencePostProcessor;详见 OpenSpec -ag-quality-score-unify。 - -### 6.3 category filter 与降级 - -```text -if L0 给出唯一 domain: - 先 filtered 检索 - if 无证据或 topSimilarity < referenceThreshold: - 再 unfiltered retry -else: - 直接 unfiltered -``` - -这里的 filter 是 metadata category / kb_scope 约束,不是第二套向量库。 - -## 7. 证据身份与去重 - -Delivery 1 已落地: - -```text -evidenceKey = - docId#chunk-{chunkIndex} +evidenceKey = docId#chunk-{chunkIndex} fallback: vector:{id} fallback: rank:{n} ``` -规则: +- 去重按 `evidenceKey`,同文档不同 chunk 可同时保留 +- `rag.max-chunks-per-document` / `rag.return-n` 在 Java 后处理器生效 +- Agent 投影中的 `document_id` 使用 chunk 级 evidenceKey,EvidenceGuard 据此验真 -- 去重按 `evidenceKey`,不是按 source 文档路径 -- 同文档不同 chunk 可同时保留 -- `rag.max-chunks-per-document` 限制单文档最多进入结果的 chunk 数 -- `rag.return-n` 限制后处理后最多返回条数 -- Agent 投影中的 `document_id` 使用 chunk 级 evidenceKey - -这保证 hybrid 召回的多片段不会在后处理/投影阶段被文档级折叠吞掉。 - -## 8. L0 / L1 职责 - -| 层 | 做什么 | 不做什么 | -|---|---|---| -| L0 | domain/keyword hint、可选 category filter、trace 解释、轻规则 boost | 不直接当事实 evidence | -| L1 dense/BM25 | 事实证据召回 | 不依赖 frontmatter 关键词命中才返回正文 | - -L0 命中文档正文不会在 L1 失败时兜底成 evidence。 - -## 9. Agent 可见契约 +## 8. Agent 可见契约(不变) Agent 只看到有界 `RagToolResult`: -- `evidence_status` -- `tool_call_id` -- `query` +- `evidence_status` / `tool_call_id` / `query` - `evidence[]`:`document_id` / `source` / `title` / `breadcrumb` / `excerpt` -- `relevance_level` +- `relevance_level`(PRECISE / REFERENCE;`RagRelevanceLevel.HIGHLY_RELEVANT` 为保留档) - `truncated` / `returned_count` -不暴露: +不暴露:raw score、retrievalTrace / rerankTrace、contextPack 全文、py-rag 地址与凭据。 +完整内部结果仍在 `LookupResult` 中,供审计与调试使用。Trace / 审计读法见 +[RAG检索可观测性与审计.md](./RAG检索可观测性与审计.md)。 -- raw score / fused score -- retrievalTrace / rerankTrace -- contextPack 全文 -- Milvus 内部字段与凭据 +## 9. L0 下沉 -完整内部结果仍在 `LookupResult` 中,供审计与调试使用。 +L0 query 理解(domain/keyword hint → 可选 categoryFilter)随本次抽离**整体下沉 py-rag**: -### 9.1 Trace 与审计(现行入口) +- `KnowledgeQueryTransformer` / `KnowledgeIndexService.analyzeQuery` 已删除 +- `lookup_knowledge` 直传原始 query;`categoryFilter` 恒为 null,走 UNFILTERED 单 attempt +- `LookupKnowledgeTool` 中 filtered→unfiltered 降级分支保留但不可达(作为未来 Java 侧过滤策略的兜底骨架) +- `RetrievalTrace.queryHints` 恒为空结构;`attempt` 命名只剩 `UNFILTERED_VECTOR` -请求内 `retrievalTrace` / 落库 `tool_invocation` / Trace API 读法见: +## 10. 与旧文档的差异 -**[RAG检索可观测性与审计.md](./RAG检索可观测性与审计.md)** - -要点: - -- Agent **看不到**完整 retrievalTrace;人通过 `GET /api/diagnosis/{sessionId}/trace` 的 `toolInvocations[].retrievalDetails` 回放。 -- `relevance_level` 列存 RAG 等级(PRECISE/REFERENCE);`evidence_status` 在 details JSON。 -- 默认审计不落原始 query 全文与 excerpt 正文。 - -## 10. 写入与重建 - -### 10.1 日常写入 - -文档上传 / 知识库初始化: - -```text -markdown - -> frontmatter + body - -> DocumentChunkService - -> dense embedding + search_text - -> MilvusHybridKnowledgeStore.upsertChunk - -> MySQL api_document + L0 memory index -``` - -### 10.2 全量重建 - -危险操作,需显式确认: - -```bash -python scripts/rebuild_hybrid_knowledge.py --confirm REBUILD -``` - -等价 API: - -```text -POST /api/knowledge/rebuild-hybrid?confirm=REBUILD -``` - -服务端顺序: - -1. drop + recreate `milvus.collection`(默认 `biz`) -2. 清空 MySQL `api_document` -3. 清空内存 L0 -4. 扫描 `knowledge_base/**/*.md`(跳过 `README.md`)force 导入 - -不会修改磁盘上的 `knowledge_base/` 源文件。 - -## 11. 与旧文档的差异 - -| 旧描述(已归档) | 当前实现 | +| 旧描述(2026-07-28 版) | 当前实现(2026-09-29 抽离后) | |---|---| -| Spring AI VectorStore 主路径 + SDK fallback | 单一 MilvusClientV2 后端 | -| `retrieval.vector-store.mode=auto/sdk/spring` | 已移除;改为 `retrieval.search.mode=dense/hybrid` | -| source 级 evidence 去重 | chunk 级 `evidenceKey` 去重 | -| 应用层 sparse-lite lexical 伪 hybrid | 库内 dense ANN + BM25 + RRFRanker | -| 新建 `biz_hybrid` 过渡 collection | 默认使用并重建 `biz` | +| 进程内 MilvusClientV2,dense+BM25+RRF | py-rag 服务端承担;Java 经 `KnowledgeSearchPort` → HTTP | +| `retrieval.search.mode` 切 Milvus 查询算法 | 同名配置映射 py-rag `hybrid` / `semantic` | +| scoreLabel 仅 dense \| hybrid,L2/rank 归一化 | 新增 `RERANK`:py-rag rerank 绝对分直传 | +| L0 hint + categoryFilter + filtered/unfiltered retry | L0 下沉;原始 query 直传,单 attempt | +| Java 侧 frontmatter 解析 / 分块 / embedding 入库 | py-rag `documents:ingest`;Java 只做 MySQL 元数据 + 原件保存 | +| `POST /api/knowledge/rebuild-hybrid` 重建 | py-rag `POST /api/v1/collections:rebuild` | +| `GET /milvus/health` 健康检查 | py-rag `GET /api/v1/health`(含 milvus/embedding/rerank 探针) | +| 删除文档同步删向量索引 | 仅删 MySQL+本地文件;py-rag 侧靠全量重建生效 | 历史材料见: -- `mvp/architecture/archive/2026-07-22-legacy/rag-architecture.md` -- `mvp/architecture/archive/2026-07-22-legacy/modular-rag-pipeline.md` +- `mvp/architecture/archive/2026-07-22-legacy/rag-architecture.md`(Milvus 前身) +- `mvp/engineering/rag/Milvus-Hybrid接入清单.md`(进程内 Milvus hybrid 接入纪要,已过时) +- 本仓库 git 历史:`refactor/extract-rag-module` 分支,71 文件 / 约 -8000 行 -## 12. 当前已知边界 +## 11. 当前已知边界 -- hybrid 依赖云端/实例支持 BM25 Function 与 sparse index -- 全量重建受 embedding API 与 Milvus 写入延迟影响,可能较慢 -- L0 关键词匹配仍较粗,只作 hint,不作主召回 -- 尚未做邻块上下文自动扩展、cross-encoder rerank、真 query rewrite -- `totalVectors` 统计接口仍可能返回 0,不代表 collection 为空;以 rebuild/init 结果与检索命中为准 -- `retrieval.search.mode=dense` 仅作召回对照,不是第二套主路径 -- 同一 hybrid schema 数据可被 dense / hybrid 两种查询复用;从纯旧 dense-only collection 升级必须 rebuild -- hybrid 质量闸门优先用 `denseDistance` 绝对 L2;无 dense 时 rank 回退;排序仍跟 RRF -- 后处理不再用 L0 关键词 boost 改序;词面信号以库内 BM25+RRF 为准 +- py-rag 判级阈值(0.75/0.5/0.3)未校准,`quality_score` 仅供排序与展示参考(契约已知边界) +- frontmatter 的 keywords/summary/covers/when_to_retrieve 当前仅上传方提供;Java 侧 LLM 补全(原 `DocumentFieldEnricher`)已随抽离移除,待 py-rag 开放 +- `knowledge_base/` 历史原料需在 py-rag 侧完成一次性 ingest 迁移后方可检索 +- 无单文档删除;下线文档靠 py-rag 全量重建 +- eval/rag-retrieval 离线基线基于旧 L0/scoreLabel 语义构建,抽离后需重新校准(见 `mvp/engineering/rag/RAG离线评测-基线设计.md` 顶部说明) - Trace/审计细节与限制见 [RAG检索可观测性与审计.md](./RAG检索可观测性与审计.md) diff --git a/mvp/architecture/README.md b/mvp/architecture/README.md index 966bd05..2fba919 100644 --- a/mvp/architecture/README.md +++ b/mvp/architecture/README.md @@ -1,6 +1,6 @@ # MVP 架构文档 -**更新日期**:2026-07-29 +**更新日期**:2026-09-29 **状态**:当前单 Diagnosis Agent + Harness 架构 当前文档入口: @@ -12,12 +12,19 @@ | [harness-quality-gates.md](harness-quality-gates.md) | Run、Tool、Evidence、Semantic、Release 与 Trace Recorder 门禁 | | [session-trace-lifecycle.md](session-trace-lifecycle.md) | sessionId/runId、SSE、统一 Timeline 和 reasoning audit 生命周期 | | [diagnosis-information-gain-stop-architecture.md](diagnosis-information-gain-stop-architecture.md) | 已实施的信息增益评价、Harness 饱和检测、Draft 合同失败降级与证据不足停止设计 | -| [RAG知识检索架构.md](RAG知识检索架构.md) | 当前 `lookup_knowledge` 检索:MilvusClientV2 dense+BM25 hybrid、chunk 证据身份、重建运维 | +| [RAG知识检索架构.md](RAG知识检索架构.md) | 当前 `lookup_knowledge` 检索:py-rag 知识服务接入、契约映射、chunk 证据身份、入库与重建运维 | | [RAG检索可观测性与审计.md](RAG检索可观测性与审计.md) | RAG Trace / 审计:请求内 retrievalTrace、tool_invocation 富字段、Trace API 读法 | **工程纪要**(问题 / 决策 / E2E,非架构规范正文)见 [../engineering/README.md](../engineering/README.md)。 -2026-07-22 前的多角色编排、双入口和旧证据链文档已移动到 `archive/2026-07-22-legacy/`,仅用于历史决策追溯,不代表当前运行时。其中旧 RAG 描述(Spring AI VectorStore 主路径 + Milvus SDK fallback)已被当前 hybrid 实现取代,请以 [RAG知识检索架构.md](RAG知识检索架构.md) 为准。检索可观测与 Trace 以 [RAG检索可观测性与审计.md](RAG检索可观测性与审计.md) 为准(勿再依赖 archive 内旧 retrieval-observability)。 +2026-07-22 前的多角色编排、双入口和旧证据链文档已移动到 `archive/2026-07-22-legacy/`,仅用于历史决策追溯,不代表当前运行时。 + +RAG 架构经历两次更替,均以 [RAG知识检索架构.md](RAG知识检索架构.md) 为准: + +1. 2026-07-28:进程内 MilvusClientV2 hybrid(取代更早的 Spring AI VectorStore 主路径 + Milvus SDK fallback); +2. **2026-09-29(当前)**:RAG 模块抽离为独立 py-rag 知识服务,Java 经 `KnowledgeSearchPort` → `PyRagKnowledgeSearchAdapter` → HTTP `/api/v1` 调用;进程内 Milvus/embedding/L0/分块全部移除。 + +检索可观测与 Trace 以 [RAG检索可观测性与审计.md](RAG检索可观测性与审计.md) 为准(勿再依赖 archive 内旧 retrieval-observability)。 当前普通 Trace 与 LLM 步骤审计(`agent_reasoning_audit`:`reasoning_content` + `assistant_text`)使用独立存储和独立接口。 DeepSeek thinking 捕获路径与 V015–V017 字段已 live 验证(2026-07-28)。Reasoning 访问控制、保留期限、加密要求仍由 ISS-015 跟踪,不能把“数据已分表 + 能抓到 thinking”理解为“治理已经完成”。 diff --git a/mvp/architecture/current-mvp-architecture.md b/mvp/architecture/current-mvp-architecture.md index ef85baa..23c86de 100644 --- a/mvp/architecture/current-mvp-architecture.md +++ b/mvp/architecture/current-mvp-architecture.md @@ -1,13 +1,13 @@ # 当前 MVP 架构 -**更新日期**:2026-07-28 +**更新日期**:2026-09-29 **状态**:当前可运行架构 ## 1. 系统定位 SuperBizAgent 是面向故障诊断的可追踪 Agent 应用。当前系统只保留一个拥有 Tool loop 的 `Diagnosis Agent`;Harness 负责确定性的预算、取消、工具边界、证据验真、语义审查和安全发布。 -知识检索当前为显式 `lookup_knowledge` 工具 + 单一 MilvusClientV2 后端(dense / dense+BM25 hybrid)。详细链路见 [RAG知识检索架构.md](RAG知识检索架构.md)。 +知识检索当前为显式 `lookup_knowledge` 工具 + 独立 py-rag 知识服务(HTTP `/api/v1`);检索算法(dense+BM25 融合、rerank、判级)与文档入库全部在 py-rag 侧,Java 只保留 harness 消费面与 HTTP 客户端。详细链路见 [RAG知识检索架构.md](RAG知识检索架构.md)。 ## 2. 分层 @@ -71,22 +71,22 @@ Agent 只看到三个固定 Tool: Agent -> RagToolAdapter / ToolBoundary -> LookupKnowledgeTool - -> L0 hint(可选 category filter) + -> 原始 query 直传(L0 已下沉 py-rag) -> KnowledgeSearchPort - -> VectorSearchService - -> MilvusHybridKnowledgeStore # 唯一知识向量后端 - -> RagResultProjector # 有界 Agent 投影 + -> PyRagKnowledgeSearchAdapter # HTTP 客户端(PyRagClient) + -> py-rag 知识服务 /api/v1/search # 融合 / rerank / 判级 + -> KnowledgeEvidencePostProcessor # chunk 去重 / return-n / 判级(Java 侧) + -> RagResultProjector # 有界 Agent 投影 ``` 要点: -- 默认 `retrieval.search.mode=hybrid`:dense ANN + BM25 sparse ANN + RRFRanker。 -- 也可切 `dense`:仅 dense ANN。 -- 已移除知识路径上的 legacy `MilvusServiceClient` search 与 `vector-store.mode=sdk|spring|auto` 路由。 +- 默认 `retrieval.search.mode=hybrid`:映射 py-rag `hybrid`(dense+BM25 融合);`dense` 映射 `semantic` 作对照。 - 证据按 chunk 级 `evidenceKey` 去重;Agent 侧 `document_id` 为 chunk 级身份。 -- 知识库全量重建:`python scripts/rebuild_hybrid_knowledge.py --confirm REBUILD`,默认操作 collection `biz`。 +- py-rag `evidence_status=no_evidence` 按正常"无知识可用"处理,不是错误。 +- 知识库全量重建:py-rag `POST /api/v1/collections:rebuild?confirm=REBUILD`(异步任务)。 -完整 schema、模式、重建与历史差异见 [RAG知识检索架构.md](RAG知识检索架构.md)。 +完整契约映射、入库、重建与历史差异见 [RAG知识检索架构.md](RAG知识检索架构.md)。 ## 5. Trace 与持久化 @@ -117,10 +117,12 @@ Reasoning endpoint 是敏感审计面,不属于普通业务 API。数据和查 与知识检索相关的独立 API: -- `POST /api/knowledge/init`:导入/增量初始化 `knowledge_base` -- `POST /api/knowledge/rebuild-hybrid?confirm=REBUILD`:清空并重建 dense+BM25 collection(默认 `biz`) -- `GET /api/knowledge/stats`:文档元数据统计 -- `GET /milvus/health`:MilvusClientV2 健康检查与 knowledge collection 名 +- `POST /api/documents/upload`:文档上传(MySQL 业务元数据 + 本地原件 + py-rag ingest) +- `POST /api/upload`:简单上传(本地保存 + py-rag ingest,可选 `category`) +- `GET /api/documents/{docId}` / `GET /api/documents/status/{status}` / `GET /api/documents/faultSource/{faultSource}`:文档元数据查询 +- `DELETE /api/documents/{docId}`:删除 MySQL 元数据与本地原件(py-rag 侧索引需全量重建生效) + +知识库检索/入库/重建的服务端健康与统计由 py-rag 提供:`GET /api/v1/health`、`GET /api/v1/stats`(`{pyrag.base-url}`)。 ## 7. 安全边界 diff --git a/mvp/engineering/README.md b/mvp/engineering/README.md index 1fe7812..1ef5105 100644 --- a/mvp/engineering/README.md +++ b/mvp/engineering/README.md @@ -20,14 +20,19 @@ ## RAG +> 2026-09-29 RAG 模块已抽离为独立 py-rag 知识服务(架构见 +> [../architecture/RAG知识检索架构.md](../architecture/RAG知识检索架构.md))。 +> 下列纪要保留决策过程价值,涉及进程内 Milvus / L0 的实现细节以各文顶部说明为准。 + | 文档 | 内容 | |---|---| -| [rag/RAG排序-多路召回与RRF.md](rag/RAG排序-多路召回与RRF.md) | K、多路融合、RRF、L0 边界 | -| [rag/RAG-Hybrid质量分与后处理.md](rag/RAG-Hybrid质量分与后处理.md) | qualityScore 统一、L2 伪装废止、后处理 | -| [rag/RAG-Agent如何读relevance_level.md](rag/RAG-Agent如何读relevance_level.md) | Agent 侧相关度标签含义与误读 | -| [rag/RAG离线评测-基线设计.md](rag/RAG离线评测-基线设计.md) | Golden/Fixture、hybrid 评测与闸门 | -| [rag/Milvus-Hybrid接入清单.md](rag/Milvus-Hybrid接入清单.md) | Hybrid 交付拆分与接入清单 | -| [rag/RAG审计补丁-stepid-query-E2E验收.md](rag/RAG审计补丁-stepid-query-E2E验收.md) | step_id / query 审计 live 验收 | +| [rag/RAG证据链探索笔记-从py-rag响应到引用验真.md](rag/RAG证据链探索笔记-从py-rag响应到引用验真.md) | **抽离后链路首发导读**:数据逐站形态、关键字段、设计哲学与化石清单 | +| [rag/RAG排序-多路召回与RRF.md](rag/RAG排序-多路召回与RRF.md) | K、多路融合、RRF、L0 边界(实现已下沉 py-rag,判断框架仍有效) | +| [rag/RAG-Hybrid质量分与后处理.md](rag/RAG-Hybrid质量分与后处理.md) | qualityScore 统一、后处理(分数语义现为 RERANK 直传) | +| [rag/RAG-Agent如何读relevance_level.md](rag/RAG-Agent如何读relevance_level.md) | Agent 侧相关度标签含义与误读(现行) | +| [rag/RAG离线评测-基线设计.md](rag/RAG离线评测-基线设计.md) | Golden/Fixture、hybrid 评测与闸门(需按新语义重新校准) | +| [rag/Milvus-Hybrid接入清单.md](rag/Milvus-Hybrid接入清单.md) | Hybrid 交付拆分与接入清单(已过时,仅历史追溯) | +| [rag/RAG审计补丁-stepid-query-E2E验收.md](rag/RAG审计补丁-stepid-query-E2E验收.md) | step_id / query 审计 live 验收(现行) | 架构对照: @@ -36,7 +41,7 @@ 相关 Issue: -- [ISS-017 L0 过滤收窄与 Fallback 加固](../issues/active/ISS-017-rag-l0-filter-fallback-hardening.md)(暂缓,保持现网) +- [ISS-017 L0 过滤收窄与 Fallback 加固](../issues/active/ISS-017-rag-l0-filter-fallback-hardening.md)(已失效:L0 下沉 py-rag,问题前提不复存在) --- diff --git a/mvp/engineering/diagnosis/一次诊断全流程-E2E导读.md b/mvp/engineering/diagnosis/一次诊断全流程-E2E导读.md index 101637d..7ed8745 100644 --- a/mvp/engineering/diagnosis/一次诊断全流程-E2E导读.md +++ b/mvp/engineering/diagnosis/一次诊断全流程-E2E导读.md @@ -4,6 +4,10 @@ **状态**:SUCCESS 完整诊断主文档;工具阶段按**现行**审计能力(`step_id` / `query`)说明 **文档路径**:`mvp/engineering/diagnosis/一次诊断全流程-E2E导读.md` +> **现状说明(2026-09-29)**:本文基于 2026-07 的 live Run 编写,图中 `VectorSearchService` / +> `MilvusHybridKnowledgeStore`(进程内 Milvus)已替换为 py-rag 服务调用;审计/Trace 结构不变。 +> 当前检索链路见 [../architecture/RAG知识检索架构.md](../architecture/RAG知识检索架构.md)。 + ### 主样本(正文数值与 timeline 来源) | 项 | 值 | diff --git a/mvp/engineering/harness/Harness RAG 检索体系学习笔记-从 query 到可验证证据.md b/mvp/engineering/harness/Harness RAG 检索体系学习笔记-从 query 到可验证证据.md index 5f592ad..7fec7cc 100644 --- a/mvp/engineering/harness/Harness RAG 检索体系学习笔记-从 query 到可验证证据.md +++ b/mvp/engineering/harness/Harness RAG 检索体系学习笔记-从 query 到可验证证据.md @@ -1,5 +1,10 @@ # Harness RAG 检索体系学习笔记:从 query 到可验证证据 +> **现状说明(2026-09-29)**:RAG 模块抽离后,本文「检索前 L0 导航」与 +> `MilvusHybridKnowledgeStore` / `KnowledgeQueryTransformer` 相关章节描述的类已删除 +> (L0 与向量检索下沉 py-rag 服务端);检索后处理/打包/投影/审计部分仍然现行。 +> 当前架构见 [../../architecture/RAG知识检索架构.md](../../architecture/RAG知识检索架构.md)。 + **更新日期**:2026-08-03 **主题**:lookup_knowledge 完整后端链路——检索前/检索/检索后/打包/组装/降级/契约/验证 **设计文档**:`mvp/engineering/rag/`(RAG 排序、Hybrid 质量分、relevance_level 等) diff --git a/mvp/engineering/harness/Harness 整体架构学习笔记-从装配到入口到记忆到知识库写入.md b/mvp/engineering/harness/Harness 整体架构学习笔记-从装配到入口到记忆到知识库写入.md index b3f7108..fefffaf 100644 --- a/mvp/engineering/harness/Harness 整体架构学习笔记-从装配到入口到记忆到知识库写入.md +++ b/mvp/engineering/harness/Harness 整体架构学习笔记-从装配到入口到记忆到知识库写入.md @@ -1,5 +1,10 @@ # Harness 整体架构学习笔记:从装配到入口到记忆到知识库写入 +> **现状说明(2026-09-29)**:RAG 模块抽离后,本文「知识库写入链路」章节描述的 +> `KnowledgeIndexService` / Milvus 写入路径已删除(入库下沉 py-rag `documents:ingest`); +> 其余装配/入口/记忆章节仍现行。当前架构见 +> [../../architecture/RAG知识检索架构.md](../../architecture/RAG知识检索架构.md)。 + **更新日期**:2026-08-04 **主题**:整体架构五块补充(了解层面)——配置装配中心 / HTTP 入口层 / 会话系统与记忆体系 / 知识库写入链路 **配套**:九域主线笔记(core/retry/progress/tool/guard/release/agent/audit/contract 全 ✅) diff --git a/mvp/engineering/rag/Milvus-Hybrid接入清单.md b/mvp/engineering/rag/Milvus-Hybrid接入清单.md index 560cb0d..0b336a9 100644 --- a/mvp/engineering/rag/Milvus-Hybrid接入清单.md +++ b/mvp/engineering/rag/Milvus-Hybrid接入清单.md @@ -1,5 +1,10 @@ # Milvus Hybrid Search 接入对照清单 +> **已过时(2026-09-29)**:RAG 模块已抽离为独立 py-rag 知识服务,进程内 Milvus +> (`MilvusHybridKnowledgeStore` / `VectorSearchService`)与本文描述的接入路径已整体删除。 +> 当前检索架构与契约映射见 [../../architecture/RAG知识检索架构.md](../../architecture/RAG知识检索架构.md)。 +> 本文仅作历史决策追溯。 + **日期**:2026-07-27 **前提**:旧 Milvus SDK 直连检索路径后续废弃,不作为长期实现基础 **目标**:在现有 `lookup_knowledge` pipeline 上接入 dense + sparse/BM25 混合检索,融合优先走服务端 RRF diff --git a/mvp/engineering/rag/RAG-Hybrid质量分与后处理.md b/mvp/engineering/rag/RAG-Hybrid质量分与后处理.md index fc1371c..10d3742 100644 --- a/mvp/engineering/rag/RAG-Hybrid质量分与后处理.md +++ b/mvp/engineering/rag/RAG-Hybrid质量分与后处理.md @@ -1,5 +1,12 @@ # 混合检索上线之后:为什么还要统一 qualityScore,以及上一代后处理错在哪 +> **现状说明(2026-09-29)**:本文讨论的后处理排序/去重/判级仍在 Java 侧 +> (`KnowledgeEvidencePostProcessor` / `RetrievalScoreNormalizer`); +> 但分数语义已变:py-rag 服务端返回 rerank 绝对分(scoreLabel=`RERANK`,[0,1] 越大越好), +> quality 直传,不再走本文所述 dense L2 / hybrid rank 归一化分支(分支保留作兼容)。 +> 判级阈值 0.75/0.5 与 py-rag 契约一致。当前架构见 +> [../../architecture/RAG知识检索架构.md](../../architecture/RAG知识检索架构.md)。 + **日期**:2026-07-28 **范围**:hybrid 检索后的分数语义、后处理排序、相关度闸门、scoreLabel 约定 **读者**:已经(或准备)上 dense+BM25+RRF,却发现「召回变了、质量判断还拧着」的工程同学 diff --git a/mvp/engineering/rag/RAG排序-多路召回与RRF.md b/mvp/engineering/rag/RAG排序-多路召回与RRF.md index d740707..35b0557 100644 --- a/mvp/engineering/rag/RAG排序-多路召回与RRF.md +++ b/mvp/engineering/rag/RAG排序-多路召回与RRF.md @@ -1,5 +1,10 @@ # 诊断 Agent 场景下的 RAG 排序:从 K=3 规则加分,到多路召回与 RRF +> **现状说明(2026-09-29)**:本文的排序判断框架(多路召回、RRF、Rerank 选型)仍是理解 +> py-rag 服务端检索设计的背景材料;但 RRF 融合、BM25、rerank 的**实现**已下沉 py-rag 服务端, +> Java 侧不再有 `RrfFusion` / `MilvusHybridKnowledgeStore`。 +> 当前架构见 [../../architecture/RAG知识检索架构.md](../../architecture/RAG知识检索架构.md)。 + **日期**:2026-07-27 **范围**:知识检索排序、多路召回、分数融合、Rerank 选型 **读者**:需要在 Agent 系统里落地 RAG,而不是只做 Demo 问答的工程同学 diff --git a/mvp/engineering/rag/RAG离线评测-基线设计.md b/mvp/engineering/rag/RAG离线评测-基线设计.md index d2bacbe..676e1e3 100644 --- a/mvp/engineering/rag/RAG离线评测-基线设计.md +++ b/mvp/engineering/rag/RAG离线评测-基线设计.md @@ -1,5 +1,10 @@ # RAG 离线评测:讨论、设计与落地 +> **需重新校准(2026-09-29)**:RAG 模块抽离后,L0 hint / categoryFilter 已下沉 py-rag、 +> 质量分改为 RERANK 直传、attempt 只剩 `UNFILTERED_VECTOR`;本文设计的 fixture/baseline +> 基于旧 L0/scoreLabel 语义构建,回归评测需按新语义重新生成 fixture 并校准闸门。 +> 当前架构见 [../../architecture/RAG知识检索架构.md](../../architecture/RAG知识检索架构.md)。 + **日期**:2026-07-28 **范围**:`eval/rag-retrieval` 离线 baseline、fixture 生成、与 hybrid/quality 主路径对齐 **读者**:要维护或扩展知识库回归评测的工程同学 diff --git a/mvp/engineering/rag/RAG证据链探索笔记-从py-rag响应到引用验真.md b/mvp/engineering/rag/RAG证据链探索笔记-从py-rag响应到引用验真.md new file mode 100644 index 0000000..438e327 --- /dev/null +++ b/mvp/engineering/rag/RAG证据链探索笔记-从py-rag响应到引用验真.md @@ -0,0 +1,336 @@ +# RAG 证据链探索笔记:从 py-rag 响应到引用验真 + +**日期**:2026-09-29 +**范围**:RAG 模块抽离后的完整数据链路——每个站点"数据长什么样、字段怎么来、为什么这样设计" +**读者**:要理解或维护 `lookup_knowledge` 证据链的工程同学 +**关联文档**: + +- 架构规范:[../../architecture/RAG知识检索架构.md](../../architecture/RAG知识检索架构.md)(本次抽离后的权威口径) +- Trace / 审计:[../../architecture/RAG检索可观测性与审计.md](../../architecture/RAG检索可观测性与审计.md) +- 契约原文:py-rag 仓库 `docs/Java接入文档.md`(API v1 冻结面) +- 前置知识:`RAG-Hybrid质量分与后处理.md`(分数语义演进史) + +> 本文按一次真实代码探索的顺序组织:从 py-rag 返回的 JSON 出发,沿着数据走过的每一站, +> 讲清关键字段、加工规则与设计取舍,最后到 Agent 引用验真收口。 + +--- + +## 0. 全景路线图 + +```text +py-rag JSON ──► KnowledgeSearchHit 站点1-2:数据形态与防腐层映射 + │ + ① 后处理 ──► EvidencePostprocessResult 站点3:去重/限流/判级 + │ + ② 打包 ──► ContextPack 站点4:文本储备(非 Agent 口粮) + │ + ③ 组装 ──► LookupResult 站点5:内部真相全集 + │ + ④ 投影 ──► RagToolResult ──► Agent 站点6-7:冻结契约与 Agent 视图 + │ + ⑤ Agent 写报告引用 toolCallId + │ + ⑥ EvidenceGuard 对账验真 ──► 语义审查 ──► 发布 站点8:证据安全链 + (旁路:每站关键字段 ──► tool_invocation 审计表,供人排障) +``` + +一句话定位:**py-rag 负责"把对的片段找出来",Java 侧负责"把找到的证据管起来"**; +检索质量可以整体外换,证据治理一寸不动——这次抽离(71 文件 / 约 -8000 行)本身就是证明。 + +--- + +## 1. 站点一:py-rag 返回的数据结构 + +响应 JSON 按职责分四块,四块数据两条去路(①②③喂后处理,④喂审计): + +```json +{ + "query": "网关超时怎么排查", // ① 入参回显 + "mode": "hybrid", // ① 查询模式(hybrid | semantic) + "hits": [ // ② 命中数组(检索的基本单位是 chunk) + { + "evidence_key": "e2e-gateway-b9c1fa12-md-34223174#chunk-1", // chunk 级身份 + "document_id": "e2e-gateway-b9c1fa12-md-34223174", // 所属文档 + "source": "e2e-gateway-b9c1fa12-md", // 来源路径 + "title": "网关超时排查", + "breadcrumb": "网关超时排查 > 处理步骤", + "excerpt": "网关超时先检查 upstream 配置…", // 正文 + "quality_score": 0.9147, // rerank 绝对分 + "relevance_level": "PRECISE" // py-rag 自己的判级(Java 不用) + } + ], + "relevance_level": "PRECISE", // ③ 顶层判级(top1) + "evidence_status": "supported", // ③ 业务状态 + "retrieval_trace": { // ④ 过程记录(不参与后处理,进审计) + "mode": "hybrid", "filters": {"category": "gateway"}, + "recall_count": 20, "rerank_model": "BAAI/bge-reranker-v2-m3", + "no_evidence_basis": null + } +} +``` + +关键语义: + +- **`evidence_status` 只有两类**:`supported`(正常)/ `no_evidence`(查到了但都是垃圾或无候选, + 此时 `hits` 恒为空)。它是 **200 正常业务响应**,Java 侧直接走"无知识可用"分支,不重试不报错。 +- **`relevance_level` 是分数的档位化**:`quality_score ≥0.75 → PRECISE`,`≥0.5 → REFERENCE`, + `<0.5` 不输出。阈值当前未校准(契约已知边界)。**它是给模型的,分数是给系统的**—— + 同一信息两种表达,服务两种消费者。 +- **命中没有独立 metadata map**(旧 Milvus 方案遗留概念),文档归属信息就是那几个平铺字段。 + +--- + +## 2. 站点二:防腐层映射——`KnowledgeSearchHit` + +`PyRagKnowledgeSearchAdapter` 把每个 hit 映射成可移植结构。从此全 Java 侧只认这个类型, +py-rag 字段再怎么变只改 adapter 一处。后处理实际只消费其中 **7 个字段**: + +```text +evidenceKey → 去重键(docId#chunk-N,原样采纳) +docId / chunkIndex → 文档分桶(单文档上限);chunkIndex 从 "#chunk-N" 解析 +content ← excerpt,最终证据正文 +score + scoreLabel ← quality_score + 常量 "rerank"(quality 直传) +originalRank ← 数组下标 +1,排序的权威 +source / title / breadcrumb → 透传展示 +``` + +`retrieval_trace` 不进这条链——它走审计旁路。**进入后处理时,每个 hit 被浓缩成 +"身份 + 分数 + 名次 + 正文"四组信息,四步加工全部围绕它们转。** + +--- + +## 3. 站点三:后处理四步——`EvidencePostprocessResult` + +```text +① 打分 score + scoreLabel → qualityScore(RERANK 分支直传,clamp [0,1]) +② 排序 只按 originalRank —— ★ 分数不参与排序,只用于判级 +③ 去重截断 evidenceKey 去重 → 单文档 chunk ≤2 → 总数 ≤5(return-n) +④ 判级 顶分 ≥0.75 PRECISE / ≥0.5 REFERENCE / 否则无档 +``` + +输出结构逐字段(7 条命中进、5 块存出的例子): + +| 字段 | 规则 | 例值 | +|---|---|---| +| `candidateCount` | 输入候选数 | 7 | +| `evidenceBlockCount` | 存活块数(差值 = 被治理掉的) | 5 | +| `topSimilarity` | 排序后第一名的 qualityScore | 0.91 | +| `relevanceLevel` | Java 按本地阈值独立判定(**不用 py-rag 返回的档位**,那个字段映射时已丢弃) | PRECISE | +| `completenessHint` | 档位绑定的天花板提示文案 | "知识库中不存在比上述结果更精准的文档" | +| `rerankTrace[]` | 每个**存活**块的最终序账本 | finalRank 1~5 | + +### 3.1 去重辨析:三条规则别混淆 + +| 层 | 键 | 规则 | 目的 | +|---|---|---|---| +| 去重 | `evidenceKey = docId#chunk-N` | 同一块再现 → **合并**(hitReasons 取并集) | 同一片段只出现一次 | +| 单文档上限 | `docId`(分桶) | 同文档**不同** chunk 可共存,最多 2 | 防单文档刷屏 | +| 总条数 | — | 最多 5 | 上下文预算 | + +**合并键是 evidenceKey 而不是 docId**——同文档的第 0 段和第 1 段是两份不同证据,按 docId +合并会把多片段证据文档级折叠掉。单次检索正常不会返回同一个 chunk 两次(每个 chunk 是唯一 +索引条目),合并分支是给"索引脏数据 / 未来多路归并"准备的**防御性兜底**,成本一次 map 查询。 + +### 3.2 为什么会同文档多块命中——根源在分块 + +入库时文档切成多个 chunk,每块独立 embedding、独立索引;检索按 chunk 算相似度。 +这是刻意的颗粒度选择:整篇文档一个向量会稀释语义,且上下文也塞不下全文。 +**切块是为了检索得准、取得少;同文档多块命中是切块的自然结果; +"chunk 级身份 + 单文档上限"就是为管理这个现象而生的。** + +### 3.3 `rerankTrace`:最终序账本 + +```java +Item { finalRank; source; baseScore; finalScore; boostReasons } +``` + +- 条数 = 存活块数(finalRank 连续编号);被合并/截断的块不产生条目; +- `baseScore == finalScore` **恒相等**——双字段是规则加分时代的化石(当年关键词加分, + 现已废除防操纵);`boostReasons` 同理,装的已是纯解释标签; +- 只记到**文档级**(无 evidenceKey),chunk 身份要看 `evidenceBlocks[]`; +- Agent 看不到它,审计默认不落库——活在内存 LookupResult 与评测快照里。 + +### 3.4 附带闸门 + +`isLowQuality()`:无可用块或顶分 <0.5 即低质。原触发 filtered→unfiltered 重试, +L0 下沉后分支休眠、闸门保留——将来 Java 侧重引过滤策略可直接接上。 + +--- + +## 4. 站点四:打包——`ContextPack`(不是 Agent 口粮) + +`KnowledgeContextPacker` 把证据块压成**一段有字符预算的文本**(默认 4000 字符): + +```text +策略 ranked_evidence_char_budget:名次即优先级,先到先得 + [Evidence 1] + source: gateway-timeout.md + breadcrumb: 网关超时排查 > 处理步骤 + reasons: semantic_rank:1, attempt:UNFILTERED_VECTOR ← 召回溯源标签 + content: + 网关超时先检查 upstream 配置… + 预算见底:装得下 header → 正文截断加 "...";连 header 都放不下 → 整条进 omittedSources +``` + +```java +ContextPack { packedText; strategy; charBudget; usedChars; includedSources; omittedSources } +``` + +**必须澄清的定位**:主诊断链路里 **Agent 不消费 packedText**(主代码零调用)——Agent 拿的是 +站点六投影后的结构化列表。它的价值:人工回放可读、评测快照存证、以及将来任何 +"证据进 prompt"路径的现成格式化出口(字符预算是与后处理"块数预算"互补的物理闸门)。 + +`reasons:` 行是证据的"简历":`semantic_rank:N`(本次检索第几名)+ `attempt:X`(哪次尝试产出, +当前只剩 `UNFILTERED_VECTOR`;历史值 `FILTERED_VECTOR` / `UNFILTERED_VECTOR_RETRY` 已随 +L0 下沉绝迹)。 + +> 小化石:`ContextPack` 的 javadoc 仍写 "Agent-facing",与 packer 侧注释矛盾—— +> 它诞生时确实面向 Agent,投影路线成为主路径后退居内部,注释没跟上身份变化。 + +--- + +## 5. 站点五:组装——`LookupResult` 真相全集 + +`LookupResultAssembler` 把三样东西合体成内部契约出口: + +```text +EvidencePostprocessResult(证据集+档位+trace)┐ +ContextPack(打包文本) ├─► LookupResult +RetrievalTrace(检索路径) ┘ +``` + +只有两个字段是"算"出来的:`found = hasUsableEvidence()`(false 时附固定兜底文案 +"知识库未检索到可用证据,请结合日志、指标、告警继续排查");两个 count 把"进多少/出多少" +带给审计。其余字段一一搬运。 + +--- + +## 6. 站点六:投影——`RagResultProjector`(加工链最后一站) + +**输入是 LookupResult 的 JSON 字符串而非对象**——内部结构随便演化,冻结契约纹丝不动, +解耦的关键就是这层"字符串边界"(字段名还带防御性别名对:`evidenceBlocks`/`evidence_blocks`)。 + +```text +① query 截断(≤500 字) 动了 → truncated +② 逐块过三道闸: + 条数 ≤8(超出 truncated+停止) + 身份去重(evidenceKey → document_id → docId#chunk-N → 序号兜底 的回退链) + 摘录 ≤1200 字(截断 → truncated) +③ evidence_status 客观判定:数组空不空(EVIDENCE_FOUND / NO_EVIDENCE) +④ relevance_level:有证据才读,解析不出 → null +⑤ fitBudget 总字节兜底:整个 JSON 超 16KB → 从尾部逐条裁 + 裁到空 → 诚实降级 NO_EVIDENCE;还超 → 抛异常(fail closed) +``` + +**三道递进预算闸**(条数管语义 / 片段管局部 / 字节管整体)集中在 `ToolProjectionLimits` +一个 record(8 条 / 1200 字 / 16KB,与日志、MySQL 工具共用)。**`truncated` 只要任何一处 +动过就置位——Agent 永远知道"看到的可能不全"**,不会把截断结果当全量。 + +--- + +## 7. 站点七:Agent 视图——模型实际看到的 JSON + +```json +{ + "evidence_status": "EVIDENCE_FOUND", + "tool_call_id": "call_x1", + "query": "网关超时怎么排查", + "evidence": [ + { "document_id": "e2e-gateway-…-34223174#chunk-1", + "source": "e2e-gateway-b9c1fa12-md", + "title": "网关超时排查", + "breadcrumb": "网关超时排查 > 处理步骤", + "excerpt": "网关超时先检查 upstream 配置…" } + ], + "returned_count": 5, + "relevance_level": "PRECISE", + "truncated": false +} +``` + +| 字段 | 模型的正确用法 | +|---|---| +| `evidence_status` | `NO_EVIDENCE` → 老实换工具(日志/指标/MySQL),不许编 | +| `evidence[].excerpt` | 结论唯一的内容依据 | +| `evidence[].document_id` | **引用坐标**——报告里逐字引用它,EvidenceGuard 只认这个 | +| `relevance_level` | PRECISE 可放心下结论;REFERENCE 结合上下文判断,必要时说清还缺什么维度 | +| `truncated` | true → 看到的可能不全,可收窄 query 重搜 | + +**三个"看不到"**:分数(防未校准数字诱导过度自信)、过程(attempt/trace 留给人)、 +其他站的内部字段。一句话:**留坐标、留内容、留诚实,剥掉一切会误导或撑爆上下文的东西。** + +--- + +## 8. 站点八:验真——`EvidenceGuard`(证据安全链第一道闸) + +Agent 写完诊断产出结构化草稿 `DiagnosisDraft`(analysis 带引用的 toolCallIds, +conclusion/action_plan 带 basedOnAnalysisIds,limitations 必填)。EvidenceGuard 纯规则验真: + +```text +A 结构校验 id 唯一、正文非空、报告引用必须指向已登记分析、limitations 必填 +B 引用验真 runId+toolCallId → Redis 账本可查 → READY 状态 → 同 Run(防跨 Run 挪用) + → kind 语义匹配:NORMAL↔EVIDENCE_FOUND / NEGATIVE_OBSERVATION↔NO_EVIDENCE +C 重读重建 从账本严格反序列化投影(多一个字段都违规)→ 用账本内容重建证据快照 +``` + +四个设计点: + +1. **证据内容从账本重读,不信模型复述**——模型转述的"检索结果"进不了快照; +2. **kind 匹配堵两头撒谎**——"查到了装没查到"与"没查到装查到了"都过不去; +3. **runId 绑定 + READY**——别的 Run 的证据、失败/过期的调用不可引用; +4. **纯规则、20 个违规码全枚举**——便宜、确定、可审计;"结论是否夸大"留给下一道 + SemanticGuard,这里只回答"引用是否真实、结构是否合法"。 + +--- + +## 9. 附:模型怎么知道要输出那份 JSON + +四层合力,没有一刻依赖模型"自觉": + +| 层 | 机制 | +|---|---| +| 格式 | `DiagnosisDraftOutputSchema`(BeanOutputConverter)从 Java 类自动生成 JSON Schema 注入 prompt;解析失败抛异常 | +| 语义 | `diagnosis-agent-prompt.md`:证据充分 → 全填并建立完整引用链;证据不足 → `conclusion=null` 是合法成功(schema 层把 conclusion 类型改成 `object\|null`);limitations 无条件必填 | +| 时机 | 模型自主停止(无有效查询范围即停)+ Harness 强制(连续 NO_GAIN / 预算耗尽 → STOP_REQUIRED 后必须直接交卷) | +| 兜底 | 格式错/引用违规 → evidence-repair-prompt 修复重试 → 仍败 → 固定降级 FALLBACK | + +--- + +## 10. 设计思路总结(五条哲学) + +1. **防腐层:换引擎不换证据链**——`KnowledgeSearchPort` 是接缝,抽离时消费面零改动、 + 250 个测试原样通过,这是接口设计价值的最硬证明。 +2. **分数给系统,档位给模型**——未校准的连续分数会诱导过度自信;`quality_score` 在 + Java 侧做闸门和审计,Agent 只见 PRECISE/REFERENCE。 +3. **chunk 级身份贯穿始终**——入库分块、检索按块、去重按 `docId#chunk-N`、引用坐标到块、 + 验真对块。颗粒度统一,才有多片段证据共存与伪引用无处遁形。 +4. **诚实标记**——`truncated`、`no_evidence`、`unchanged`、`no_evidence_basis`: + 系统从不假装"看到的即全部",每个不完整/为空都有显式信号与原因。 +5. **所见即所证**——投影结果是"模型看到的"与"Redis 存证的"同一份;EvidenceGuard 对账 + 没有翻译损耗,伪引用无处遁形。 + +### 化石清单(读代码时的辨认指南) + +| 化石 | 现状 | +|---|---| +| `RerankTrace.baseScore/finalScore` 双字段 | 恒相等(规则加分已废),保留兼容旧审计格式 | +| `boostReasons` 字段名 | 装的是纯解释标签,不再加分 | +| `ContextPack` javadoc "Agent-facing" | 身份已变(内部/储备),注释未跟上 | +| `LookupResultAssembler.deduped()` | 会话级去重回包的历史占位,主链路不再调用 | +| `FILTERED_VECTOR` / `UNFILTERED_VECTOR_RETRY` attempt | L0 下沉后不可达,仅存于旧 Run 回放 | +| `KnowledgeQuery` 的 L0 hint 字段 | 恒空结构,供后处理与 trace 兼容保留 | + +--- + +## 11. 关键字段速查 + +| 字段 | 哪一站 | 一句话 | +|---|---|---| +| `evidence_key` | py-rag → 全程 | chunk 级身份 `docId#chunk-N`,去重与验真的锚 | +| `quality_score` | py-rag → 后处理 | rerank 绝对分 [0,1],quality 直传 | +| `evidence_status` | py-rag / 投影 | 两类:supported / no_evidence(正常业务响应) | +| `relevance_level` | 后处理 → Agent | PRECISE/REFERENCE 档位,Java 按本地阈值独立判定 | +| `originalRank` | adapter → 后处理 | 排序唯一权威,分数不动序 | +| `candidateCount / evidenceBlockCount` | 后处理 | 进多少 / 出多少,差值即治理幅度 | +| `truncated` | 投影 | 任何截断都置位,诚实标记 | +| `tool_call_ids` | Draft → EvidenceGuard | 引用验真的入口,runId 绑定 + READY 校验 | diff --git a/mvp/issues/active/ISS-017-rag-l0-filter-fallback-hardening.md b/mvp/issues/active/ISS-017-rag-l0-filter-fallback-hardening.md index 6eb18e0..755c8be 100644 --- a/mvp/issues/active/ISS-017-rag-l0-filter-fallback-hardening.md +++ b/mvp/issues/active/ISS-017-rag-l0-filter-fallback-hardening.md @@ -1,11 +1,18 @@ # ISS-017 RAG L0 过滤收窄与 Fallback 加固 -**状态**:开放,暂缓实施(保持现网行为) +**状态**:已失效(2026-09-29 RAG 抽离,L0 整体下沉 py-rag,问题前提不复存在) **严重程度**:中 **发现时间**:2026-07-28 -**更新日期**:2026-07-28 +**更新日期**:2026-09-29 **来源**:hybrid + qualityScore 收口后的评测/设计讨论;`chat-l0-filter-fallback` golden case -**关联**:`LookupKnowledgeTool`、`KnowledgeQueryTransformer`、`KnowledgeEvidencePostProcessor`、`eval/rag-retrieval`、历史 `rag/rag-l0-domain-entity-hint.md` +**关联**:`LookupKnowledgeTool`、`KnowledgeQueryTransformer`(已删除)、`KnowledgeEvidencePostProcessor`、`eval/rag-retrieval`、历史 `rag/rag-l0-domain-entity-hint.md` + +> **处理记录(2026-09-29)**:RAG 模块抽离为 py-rag 知识服务时,L0 query 理解 +> (`KnowledgeQueryTransformer` / `KnowledgeIndexService.analyzeQuery`)整体下沉服务端, +> `categoryFilter` 恒为 null,`FILTERED_VECTOR` / `UNFILTERED_VECTOR_RETRY` 分支不再可达。 +> 本 issue 讨论的「硬过滤赌一把 + 失败整页替换」路径已不存在,无需加固,关闭。 +> 若未来在 Java 侧重新引入检索过滤策略,filtered→unfiltered 的降级骨架仍在 +> `LookupKnowledgeTool` 中保留,届时可参考本文的改进方向与回归动机。 --- diff --git a/mvp/tables/知识域表-knowledge_domain.md b/mvp/tables/知识域表-knowledge_domain.md index 5242371..02f327f 100644 --- a/mvp/tables/知识域表-knowledge_domain.md +++ b/mvp/tables/知识域表-knowledge_domain.md @@ -1,8 +1,12 @@ # 知识域表:knowledge_domain -**状态**:当前表 +**状态**:孤儿表(2026-09-29 RAG 抽离后无写入方) **来源**:`V009__add_knowledge_domain.sql`、`KnowledgeDomain` +> **2026-09-29 变更**:本表的写入方 `KnowledgeDomainService` 已随 RAG 模块抽离删除 +> (L0 domain 分析下沉 py-rag)。表结构由 Flyway 保留(`ddl-auto: validate`),当前无读写方, +> 待后续迁移清理。历史数据仅供追溯。 + ## 定位 `knowledge_domain` 保存知识库领域级元数据,为 RAG backend 的 domain hint、检索选择和可观测性提供基础信息;它不是 Agent-facing Tool contract。