16.1 1.x RAG 旧 API 回顾
java
代码解读
复制代码
import io.agentscope.core.rag.SimpleRAGKnowledge; import io.agentscope.core.rag.Knowledge; import io.agentscope.core.rag.loader.LocalFileLoader; import io.agentscope.core.ReActAgent; import io.agentscope.core.model.DashScopeChatModel; public class Chapter16_LegacyRag { public static void main(String[] args) { // 1. 准备知识库 Knowledge knowledge = new SimpleRAGKnowledge( new LocalFileLoader("./docs"), new DashScopeEmbeddingModel()); // 2. agent 装上 knowledge ReActAgent agent = ReActAgent.builder() .name("doc_qa") .sysPrompt("你是文档问答助手。") .model(DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen-plus") .build()) .knowledge(knowledge) // 1.x .build(); // 3. 调用时 agent 内部会先 retrieve 再回答 } }
2.0 仍可编译:ReActAgent.knowledge(...)在 RC2 中标记为@Deprecated(forRemoval = true),编译通过但会告警。新项目请不要使用。
16.2 2.0 推荐的"subagent + 文件检索"
为什么不用 1.x 的 RAG 了?
1.x 的 RAG 是一条固定管道:
css
代码解读
复制代码
文档 → 切片 → 嵌入(向量化) → 存向量库 ↓ 用户提问 → 同样嵌入 → 余弦相似度搜索 → 取 top-k 切片 → 拼进 prompt → LLM 回答
这条管道有三个问题:
| 问题 | 影响 |
|---|---|
| 黑盒检索 | 向量相似度 ≠ 语义相关。搜出来的切片可能"看起来像但内容不相关",你没地方 debug |
| 管道路径固定 | 先切片、再嵌入、再搜索。LLM 全程不参与检索决策,搜什么、怎么搜全由管道定 |
| 依赖外部组件 | 需要 embedding 模型(DashScope/Qwen Embedding)+ 向量库(Milvus/Pinecone),多两个微服务 |
2.0 怎么做?
把检索权交给 LLM。agent 自带grep_files(关键词搜索)和read_file(读文件),LLM 自己决定搜什么关键词、读哪个文件、读多少内容。不切片、不嵌入、不向量库。
perl
代码解读
复制代码
2.0 工作流: 用户提问 → LLM 自己决定用什么关键词 grep → 自己读相关文件 → 自己综合答案
对比一下:
| 1.x RAG | 2.0 subagent + 文件检索 | |
|---|---|---|
| 检索决策者 | 管道(程序) | LLM(agent) |
| 检索方式 | 向量相似度 | grep_files关键词 +read_file精读 |
| 外部依赖 | embedding 模型 + 向量库 | 无,全是 agent 内置工具 |
| 可调试性 | 差(你不知道为啥搜到这个切片) | 好(agent 日志里能看到 grep 了什么、读了什么) |
| 灵活性 | 固定管道 | LLM 可以分步检索:先 grep 找文件名 → 再 read 读内容 → 不够再 grep 扩大范围 |
核心思想:与其让程序猜"哪段和用户问题最像",不如让 LLM 自己想"我应该搜什么词、读哪个文件"。HarnessAgent自带read_file/grep_files/glob_files三个内置工具,subagent 可以直接用它们:
typescript
代码解读
复制代码
import io.agentscope.harness.agent.subagent.SubagentDeclaration; import io.agentscope.harness.HarnessAgent; public class Chapter16_NewRag { public static void main(String[] args) { // 文档检索 subagent:用 grep_files + read_file 代替传统向量检索 SubagentDeclaration docReader = SubagentDeclaration.builder() .name("doc_reader") .description(""" 文档检索 subagent。 拿到问题后: 1. 先用 grep_files 在 ./docs 找关键词 2. 用 read_file 读最相关的 1-2 个文件 3. 综合内容回答 """) // LLM 根据 description 决定何时 spawn 它 .inlineAgentsBody("你是一个文档检索员," + "只用 read_file / grep_files 找答案。") // subagent 自己的系统提示词 .build(); HarnessAgent host = HarnessAgent.builder() .name("qa") .sysPrompt("你是问答助理,需要查文档时 spawn doc_reader。") .model(model()) .workspace(Path.of("./workspace")) .subagent(docReader) // 注册 subagent .build(); } }
跑host.call(...)时,LLM 看到用户的问题含"文档",会主动 spawndoc_reader,后者用 grep + read 自己决定查什么。
16.3 进阶:用 Skill 仓库做"结构化 RAG"
如果文档量很大、希望"按主题切分",可以直接把每份文档做成一个Skill(详见第 18 章):
objectivec
代码解读
复制代码
workspace/ └── skills/ ├── product-faq/ │ └── SKILL.md ├── engineering-handbook/ │ └── SKILL.md └── legal-policies/ └── SKILL.md
每份SKILL.md描述"这个 skill 解决什么问题":
makefile
代码解读
复制代码
# product-faq/SKILL.md name: product-faq description: | 产品 FAQ:当用户问"如何退款 / 如何开发票 / 如何修改地址"时优先用。 allowed-tools: [read_file, grep_files]
主 agent 在 prompt 里被告知"先看 SKILL.md 决定用哪个 skill"。LLM 按 description 路由到对应 skill,再读 SKILL.md 里手动维护的文档链接。
这种方式优势:
- 路由可读:产品经理也能改
SKILL.md调整路由 - token 省:一次只载入相关 skill 的元信息,不一次性塞进 prompt
- 可管理:文档更新时只改对应 skill 的内容
16.4 何时仍用真正的"嵌入 + 向量检索"
如果你的检索需求满足下面任一条件,仍可保留传统 RAG:
- 文档量 > 10 万条,subagent 用 grep_files 检索太慢
- 检索要求"语义相似"(用户写"心情不好"要找到"沮丧"段)
- 需要 hybrid search(同时跑 BM25 和向量搜索,把两边的结果按权重凑成一份最终排名)
此时推荐在 2.0 中手写一个Tool:
less
代码解读
复制代码
@Tool(name = "vector_search", description = "向量检索") public String vectorSearch( @ToolParam(name = "query") String query, @ToolParam(name = "topK", required = false) Integer topK) { // 调你自己的向量库(Milvus / Elasticsearch / PGVector) return vectorStore.search(query, topK == null ? 5 : topK); }
把工具注册到 agent / subagent 即可。这就是 2.0 推荐的"该用什么工具就用什么工具",不必拘泥于RAGKnowledge抽象。
16.5 最小迁移清单(1.x RAG → 2.0)
| 1.x 你做了什么 | 2.0 怎么做 |
|---|---|
调RAGKnowledge.retrieve(...)自动检索 | subagent 用内置grep_files+read_file检索 |
用SimpleRAGKnowledge等内置分块+嵌入 | 框架已去掉内置管道。如需嵌入,自己写@Tool调向量库 |
| 配置分块策略、嵌入模型 | 在自定义@Tool里自由实现,框架不再限制 |
agent.knowledge(knowledge) | .subagent(retriever)或.toolkit(toolkit) |
agent.call(..., retriever=knowledge) | 拆成 subagent + 工具,LLM 自主决定何时检索 |
核心变化:1.x 的 RAG 是框架内置管道(你只管配参数)。2.0 框架不再内置分块/嵌入/向量搜索,但你可以用
@Tool自由实现任意检索逻辑。检索决策从管道转移到 LLM。
16.6 完整可运行示例
这个例子在演示什么?
一个 QA agent 配了两种检索方式,LLM 根据问题类型自己决定用哪个:
doc_readersubagent:用内置grep_files+read_file在./docs目录做文件级关键词检索。适合"退货政策是什么""怎么开发票"这类事实性问题,零外部依赖。vector_search工具:调 Milvus/ES 做语义级向量检索。适合用户写"心情不好"要找"沮丧相关文档"这类模糊匹配。这不是冗余,是互补:subagent 查文件快但只能精确匹配,向量检索慢但能理解语义。LLM 看到问题类型后自己选路:事实问题 spawn subagent,模糊问题调 vector_search。两个可以共存于同一个 agent。
typescript
代码解读
复制代码
public class Chapter16_Full { public static void main(String[] args) { // 文件检索 subagent:用内置工具,不额外写 Java 代码 SubagentDeclaration docReader = SubagentDeclaration.builder() .name("doc_reader") .description("文档检索;输入问题,输出从 ./docs 找出的相关段落") .inlineAgentsBody(""" 你是一个文档检索员。 1. 用 grep_files 在 ./docs 下找关键词 2. 用 read_file 读最相关 2 份文件 3. 把内容整理成 200 字以内回答 """) .build(); // 语义检索工具:业务方自己接向量库(可选) Toolkit toolkit = new Toolkit(); toolkit.registerTool(new VectorSearchTool("http://localhost:19530")); HarnessAgent host = HarnessAgent.builder() .name("qa") .sysPrompt(""" 你是问答助理。 优先 spawn doc_reader 查本地文档;如果用户问模糊的语义类问题,调 vector_search 工具。 """) .model(model()) .workspace(Path.of("./workspace")) .subagent(docReader) // 文件检索(关键词) .toolkit(toolkit) // 向量检索(语义) .build(); // 事实性问题 → LLM 会 spawn doc_reader host.call( List.of(new UserMessage("user", "退款政策是什么?")), RuntimeContext.empty()) .block(); // 模糊问题 → LLM 会调 vector_search host.call( List.of(new UserMessage("user", "有哪些和用户不满意相关的政策?")), RuntimeContext.empty()) .block(); } }
16.7 本章小结
- 1.x
RAGKnowledge在 2.0 中被标记为弃用,未来会移除。 - 2.0 推荐"subagent + 文件检索"或"业务方手写向量检索
@Tool"。 - 大量结构化文档可以做成 Skill 仓库,用 description 做路由。
- 真正需要嵌入 + 向量库时,业务方用
@Tool自由实现即可。