docs(mvp): update RAG docs for py-rag extraction
- rewrite RAG architecture doc for py-rag service contract mapping, ingest and rebuild ops - refresh observability/trace doc for post-L0 single-attempt semantics - add py-rag chain exploration note; mark superseded Milvus/L0 notes with status banners - close ISS-017 (superseded by L0 sinking); mark knowledge_domain table orphaned - refresh architecture/mvp/engineering indexes
This commit is contained in:
@@ -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<br/>fallbackReason = low_quality / no_evidence]
|
||||
USE1 --> POST[PostProcess · evidenceBlocks · relevanceLevel]
|
||||
USE2 --> POST
|
||||
Q[query 原始句直传] --> A1[attempt UNFILTERED_VECTOR<br/>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 直传 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+131
-271
@@ -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)
|
||||
|
||||
@@ -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”理解为“治理已经完成”。
|
||||
|
||||
@@ -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. 安全边界
|
||||
|
||||
|
||||
Reference in New Issue
Block a user