14 KiB
Essence Report: SuperBizAgent-java — RAG 实现
Lens: mechanical(机械论——结构、接口、数据流)
Design analyzed: RAG 管道——从文档上传到 Agent 辅助检索的完整写入/读取双路径
Files examined: 12
Pattern: Pipeline-as-Services + Agent-Mediated Retrieval
Status: complete
Phase 1: 定位 — 设计目标确认
来自 /explore 报告的「设计二:完整的 RAG 管道(5 级流水线)」。用户指定深入 RAG 实现部分。
涉及 12 个核心文件,跨越 controller → service → client → constant 四层。
Phase 2: Deep Dive — 逐文件追踪
核心文件清单
| # | 文件 | 角色 | 暴露接口 |
|---|---|---|---|
| 1 | constant/MilvusConstants.java |
Schema 契约常量 | VECTOR_DIM=1024, COLLECTION_NAME="biz" |
| 2 | client/MilvusClientFactory.java |
数据库初始化 | createClient() → 自动建表+建索引 |
| 3 | config/DocumentChunkConfig.java |
分块参数 | maxSize=800, overlap=100 |
| 4 | dto/DocumentChunk.java |
分块实体 | content, startIndex/endIndex, chunkIndex, title |
| 5 | service/DocumentChunkService.java |
智能分块器 | chunkDocument(content, filePath) → List<DocumentChunk> |
| 6 | service/VectorEmbeddingService.java |
向量化网关 | generateEmbedding(text) → List<Float> (1024-dim) |
| 7 | service/VectorIndexService.java |
写入管道编排 | indexSingleFile(path) → 读→删旧→分块→向量化→写 |
| 8 | service/VectorSearchService.java |
语义检索 | searchSimilarDocuments(query, topK) → List<SearchResult> |
| 9 | service/RagService.java |
全栈 RAG 问答 | queryStream(question, history, callback) → SSE流式 |
| 10 | agent/tool/InternalDocsTools.java |
Agent 工具桥 | queryInternalDocs(query) → JSON(仅检索,不生文) |
| 11 | controller/FileUploadController.java |
写入入口 | POST /api/upload → 文件存储 + 自动索引 |
| 12 | controller/ChatController.java |
读取入口 | POST /api/chat(_stream) → ReactAgent + 工具调用 |
完整的调用链(双路径)
写入路径(索引管道)
POST /api/upload
└─ FileUploadController.upload() [L34]
├─ Files.copy() → 保存文件到 uploadPath
└─ VectorIndexService.indexSingleFile() [L124]
├─ Files.readString() [L135]
├─ deleteExistingData() [L173]
│ └─ milvusClient.delete() [L198]
│ expr: metadata["_source"] == "/path/to/file"
├─ chunkService.chunkDocument() [L142]
│ ├─ splitByHeadings() [L61]
│ │ └─ 正则: ^(#{1,6})\s+(.+)$
│ ├─ chunkSection() × N [L104]
│ │ ├─ splitByParagraphs() [L174]
│ │ └─ getOverlapText() [L193]
│ └─ → List<DocumentChunk>
└─ for each chunk: [L146]
├─ embeddingService.generateEmbedding() [L76]
│ └─ DashScope TextEmbedding API → List<Float>[1024]
└─ insertToMilvus() [L255]
└─ UUID(source+chunkIndex) + vector + content + metadata(JSON)
读取路径(Agent 中介检索)
POST /api/chat_stream
└─ ChatController.chatStream() [L143]
└─ chatService.createReactAgent() [L183]
└─ tools: [DateTimeTools, InternalDocsTools, QueryMetricsTools, QueryLogsTools]
└─ agent.stream(question) [L189]
└─ Agent 自主决策 → 调用 queryInternalDocs
└─ InternalDocsTools.queryInternalDocs() [L53]
└─ VectorSearchService.searchSimilarDocuments() [L42]
├─ embeddingService.generateQueryVector() [L47]
├─ milvusClient.search() [L51]
│ └─ L2距离, IVF_FLAT, nprobe=10
└─ → List<SearchResult>{id, content, score, metadata}
└─ return JSON to Agent
└─ Agent 融合检索结果 + LLM推理 → 最终回答
架构图
graph TB
subgraph 写入路径
UPLOAD[POST /api/upload]
FC[FileUploadController]
VIS[VectorIndexService]
DCS[DocumentChunkService]
VES[VectorEmbeddingService]
MV_W[(Milvus)]
end
subgraph 读取路径
CHAT[POST /api/chat_stream]
CC[ChatController]
AGENT[ReactAgent]
IDT[InternalDocsTools<br/>@Tool注解]
VSS[VectorSearchService]
MV_R[(Milvus)]
LLM[DashScope LLM]
end
UPLOAD --> FC
FC --> VIS
VIS --> DCS --> VIS
VIS --> VES --> VIS
VIS --> MV_W
CHAT --> CC
CC --> AGENT
AGENT -->|自主决策调用| IDT
IDT --> VSS
VSS --> VES --> VSS
VSS --> MV_R
IDT -->|JSON结果| AGENT
AGENT --> LLM
LLM -->|SSE流式| CC
关键设计决策(代码证据)
1. 幂等上传——元数据驱动的去重策略
// VectorIndexService.java:138-139
// 删除该文件的旧数据(如果存在)
deleteExistingData(path.toString());
deleteExistingData() (L173-215) 使用 metadata["_source"] == filePath 作为删除表达式。每次上传同一文件时,先清空旧向量再写入新数据,保证数据一致性。
2. 路径标准化——跨平台一致性
// VectorIndexService.java:176-178
Path path = Paths.get(filePath).normalize();
String normalizedPath = path.toString().replace(File.separator, "/");
Windows \ 和 Unix / 统一为正斜杠,避免 Milvus 表达式解析错误。在 deleteExistingData() 和 buildMetadata() 中均有应用。
3. 重叠窗口 + 句子边界感知
// DocumentChunkService.java:132-148
if (currentChunk.length() > 0 &&
currentChunk.length() + paragraph.length() > chunkConfig.getMaxSize()) {
// 保存当前分片
String overlap = getOverlapText(chunkContent); // 提取重叠文本
currentChunk = new StringBuilder(overlap); // 新分片以重叠文本开头
getOverlapText() (L193-213) 更进一步:在重叠文本中寻找句子边界(。?!),避免在句子中间截断。当句子边界超过 overlapSize/2 时才使用,否则退回原始重叠策略。
4. 检索与生成分离
InternalDocsTools.queryInternalDocs() 只做检索,不做生成。它将搜索结果序列化为 JSON 返回给 Agent,由 Agent 的 LLM 自行判断如何使用这些信息。
// InternalDocsTools.java:68
String resultJson = objectMapper.writeValueAsString(searchResults);
return resultJson;
对比 RagService.queryStream() 则完整执行「检索→构建上下文→LLM 生成」三步,是一个独立的全栈 RAG 备用路径。
5. Milvus Schema 设计
// MilvusClientFactory.java:109-142
// 四个字段:
// id VarChar(256) 主键 — UUID(source + chunkIndex)
// vector FloatVector(1024) — text-embedding-v4 输出
// content VarChar(8192) — 分块后的文本内容
// metadata JSON — {_source, _extension, _file_name, chunkIndex, totalChunks, title}
// 索引: IVF_FLAT, L2距离, nlist=128
Phase 3: Extract Pattern
设计模式:Pipeline-as-Services + Agent-Mediated Retrieval
问题: 如何将知识库文档转化为 AI Agent 可检索、可利用的语义记忆?
传统方案的问题:
- 关键词检索:无法理解语义相似的查询
- 硬编码 FAQ:无法应对未见过的问题
- 直接向量检索 + 固定提示词:所有问题都触发检索,浪费资源
本项目的方案:两阶段架构
┌──────────────────────────────────────────────────┐
│ STAGE 1: 写入管道 (离线/上传时触发) │
│ │
│ 文档 ──→ 智能分块 ──→ 向量化 ──→ Milvus存储 │
│ (标题+段落 (text-embedding (IVF_FLAT │
│ 边界感知) -v4, 1024-dim) L2索引) │
│ │
│ 接口契约: │
│ IN: File → OUT: N × (vector + content + meta) │
└──────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────┐
│ STAGE 2: 读取管道 (Agent 决策时触发) │
│ │
│ 用户问题 ──→ Agent 思考 ──→ 决定查知识库 │
│ │ │
│ ▼ │
│ 向量检索 (L2距离) ──→ Top-K 文档片段 │
│ │ │
│ ▼ │
│ Agent 融合检索结果 + LLM推理 → 回答 │
│ │
│ 接口契约: │
│ IN: query(自然语言) → OUT: JSON(检索结果) │
│ Agent 自主决定: 是否调用 / 如何使用结果 │
└──────────────────────────────────────────────────┘
接口契约(隐式——通过 Spring DI 实现)
| 契约 | 生产者 | 消费者 | 数据形状 |
|---|---|---|---|
List<DocumentChunk> |
DocumentChunkService | VectorIndexService | {content, startIndex, endIndex, chunkIndex, title} |
List<Float>[1024] |
VectorEmbeddingService | VectorIndexService, VectorSearchService | DashScope text-embedding-v4 输出 |
List<SearchResult> |
VectorSearchService | InternalDocsTools, RagService | {id, content, score, metadata} |
StreamCallback |
RagService | (外部调用者) | {onSearchResults, onContentChunk, onComplete, onError} |
替代方案对比
| 方案 | 本项目 | LangChain4j | 纯 DashScope API |
|---|---|---|---|
| 分块策略 | 标题感知 + 段落边界 + 句子重叠 | 多种内置 Splitter | 无,需自建 |
| 向量库 | Milvus (IVF_FLAT) | 多后端支持 | 无 |
| Agent 集成 | Spring AI @Tool 注解,Agent 自主决策 | AiServices + @Tool | 无 Agent 框架 |
| 去重 | metadata["_source"] 匹配删除 | 需自定义 | 不适用 |
为什么选择这种设计?
- 「检索」和「生成」分离:
InternalDocsTools只返回检索结果,生成由 Agent 的 LLM 完成。Agent 可以选择不使用检索结果(如果检索质量不高),或者交叉验证多次检索的结果 - 工具化 RAG:将 RAG 暴露为 Agent 工具而非独立 API,让 Agent 在合适的时机触发检索——而非对所有问题都做 RAG
- 5 个独立 Service:每个阶段可单独替换。想换分块策略?只改
DocumentChunkService。想换向量库?只改VectorSearchService+VectorIndexService
Phase 4: Migrate — 可迁移的设计
可迁移性评估
这个 RAG 设计高度可迁移到任何需要「知识库 + AI Agent」的 Java 项目。核心依赖是 Spring AI 生态 + 一个向量数据库。
Steal-it 示例(12 行)
// 核心思想:Pipeline-as-Services + Agent Tool Bridge
// 以下骨架可直接用于任何 Spring Boot 项目
// 1. 分块器:语义感知分割
public List<Chunk> chunk(String doc) {
return splitByHeadings(doc).stream()
.flatMap(s -> splitToFit(s, MAX_SIZE, OVERLAP))
.toList();
}
// 2. Agent 工具桥:检索但不生文
@Component
class KnowledgeBaseTool {
@Tool(description = "搜索内部知识库获取相关信息")
public String search(@ToolParam(description="查询内容") String query) {
List<Float> qv = embedder.embed(query); // 向量化
var results = vectorDB.search(qv, TOP_K); // 语义检索
return toJson(results); // 返回给Agent
}
}
落地陷阱
| 陷阱 | 说明 | 本项目如何规避 |
|---|---|---|
| 路径分隔符不一致 | Windows \ vs Unix / 导致 Milvus 表达式解析失败 |
VectorIndexService.java:177 强制 replace(File.separator, "/") |
| 重复上传污染数据 | 同一文件多次上传产生重复向量 | VectorIndexService.java:138-139 delete-before-insert |
| 分块边界截断语义 | 固定长度切割可能切断句子 | DocumentChunkService.java:203-206 在重叠区找句子边界 |
| Agent 未触发工具 | Agent 不知道何时该查知识库 | InternalDocsTools.java:49-52 @Tool description 用英文详细描述触发场景 |
| 向量维度不匹配 | embedding 模型输出维度与 Milvus schema 不一致 | MilvusConstants.java:18 集中管理 VECTOR_DIM=1024 |
| API Key 未初始化 | 静态 Constants 被其他线程覆盖 | VectorEmbeddingService.java:86-89 每次调用前检查并修复 |
Self-review
- 设计真实存在 — 每个声明均有文件+行号证据
- 分析深度足够 — 完整追踪了写入/读取两条全路径
- 迁移示例≤20行 — 仅提取 Pipeline + Tool Bridge 骨架
- 陷阱具体 — 每个都有代码规避证据
- 可解释为什么优于替代方案 — Agent 自主决策 vs 强制 RAG
Essence Report: SuperBizAgent-java
Lens: mechanical
Design analyzed: RAG 管道 — Pipeline-as-Services + Agent-Mediated Retrieval
Files examined: 12
Pattern: Pipeline-as-Services + Agent Tool Bridge
Migration: 12-line steal-it skeleton
HTML generated: no
Status: complete