# RAG 知识检索架构 **更新日期**:2026-09-29 **状态**:当前可运行架构 **关联实现**:`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 -> lookup_knowledge(query) -> Harness ToolBoundary / ACI projection -> EvidenceGuard 只认当前 Run 的 READY canonical 证据 ``` 目标: - 保留 Agent 可见的工具调用与证据边界 - 检索/入库基础设施外置为独立服务,Java 侧不感知引擎细节 - 用 chunk 级证据身份保证同文档多片段可同时进入上下文 - 检索行为可配置、可重建、可审计 ## 2. 稳定边界 ```mermaid flowchart LR subgraph AgentBoundary["Agent boundary"] Agent["Diagnosis Agent"] Tool["lookup_knowledge"] end subgraph HarnessBoundary["Harness boundary"] Adapter["RagToolAdapter"] Projector["RagResultProjector"] Canonical["Redis canonical invocation"] end subgraph RetrievalBoundary["Retrieval boundary"] Backend["LookupKnowledgeTool"] Port["KnowledgeSearchPort"] Remote["PyRagKnowledgeSearchAdapter"] Service["py-rag 知识服务 (HTTP /api/v1)"] end Agent --> Tool Tool --> Adapter Adapter --> Backend Backend --> Port Port --> Remote Remote -->|HTTP| Service Adapter --> Projector Adapter --> Canonical ``` | 边界 | 职责 | 不负责 | |---|---|---| | Agent | 决定何时检索、如何用证据写报告 | 不直接访问 py-rag / MySQL 元数据表 | | Harness | Tool 校验、投影裁剪、canonical 存证 | 不改写检索排序算法 | | Retrieval | 请求映射、后处理、打包;py-rag 承担召回/融合/rerank/判级 | 不绕过 ACI 直接给 Agent 原始库响应 | ## 3. 当前主链路 ```mermaid flowchart TD 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` | 串联 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 字段 | 2026-09-29 抽离时删除的 Java 侧组件:`MilvusHybridKnowledgeStore`、`VectorSearchService`、 `VectorKnowledgeSearchAdapter`、`RrfFusion` / `LexicalRanker`(融合评分下沉)、 `KnowledgeIndexService` / `KnowledgeDomainService`(L0 索引)、 `DocumentChunkService` / `FrontmatterParser` / `TextExtractorService`(入库解析)、 `KnowledgeQueryTransformer`(L0 query 理解)。 ## 4. py-rag 检索契约映射 请求映射(`PyRagKnowledgeSearchAdapter`): | 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 部署配置决定 | 响应映射: | 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 业务上传(DocumentController /api/documents/upload) -> MySQL api_document(业务元数据:faultSource 等)+ 本地原件保存 -> PyRagClient.ingest(multipart 透传原件 + category) -> py-rag:解析 / frontmatter 校验 / 分块 / 向量化 / 索引 简单上传(FileUploadController /api/upload) -> 本地保存 + PyRagClient.ingest(category 可选参数,缺省 default;入库失败不影响上传成功语义) ``` 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 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 ``` - 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` ## 7. 证据身份与去重(不变) ```text 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 据此验真 ## 8. Agent 可见契约(不变) Agent 只看到有界 `RagToolResult`: - `evidence_status` / `tool_call_id` / `query` - `evidence[]`:`document_id` / `source` / `title` / `breadcrumb` / `excerpt` - `relevance_level`(PRECISE / REFERENCE;`RagRelevanceLevel.HIGHLY_RELEVANT` 为保留档) - `truncated` / `returned_count` 不暴露:raw score、retrievalTrace / rerankTrace、contextPack 全文、py-rag 地址与凭据。 完整内部结果仍在 `LookupResult` 中,供审计与调试使用。Trace / 审计读法见 [RAG检索可观测性与审计.md](./RAG检索可观测性与审计.md)。 ## 9. L0 下沉 L0 query 理解(domain/keyword hint → 可选 categoryFilter)随本次抽离**整体下沉 py-rag**: - `KnowledgeQueryTransformer` / `KnowledgeIndexService.analyzeQuery` 已删除 - `lookup_knowledge` 直传原始 query;`categoryFilter` 恒为 null,走 UNFILTERED 单 attempt - `LookupKnowledgeTool` 中 filtered→unfiltered 降级分支保留但不可达(作为未来 Java 侧过滤策略的兜底骨架) - `RetrievalTrace.queryHints` 恒为空结构;`attempt` 命名只剩 `UNFILTERED_VECTOR` ## 10. 与旧文档的差异 | 旧描述(2026-07-28 版) | 当前实现(2026-09-29 抽离后) | |---|---| | 进程内 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`(Milvus 前身) - `mvp/engineering/rag/Milvus-Hybrid接入清单.md`(进程内 Milvus hybrid 接入纪要,已过时) - 本仓库 git 历史:`refactor/extract-rag-module` 分支,71 文件 / 约 -8000 行 ## 11. 当前已知边界 - 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)