AgentScope Java新手村16 从RAG到多路检索
时间:2026-07-25 | 作者:318050 | 阅读:0很多朋友可能已经注意到了,AgentScope 2.0 在某些核心设计上来了个大转弯。尤其是在知识检索这块,曾经大家熟悉的 SimpleRAGKnowledge、BgeRAGKnowledge 这些 1.x 的 API,在最新的 RC2 版本中已经被正式标记为“过去式”了。
为什么要把看似成熟、好用的“文档分块-嵌入-检索”黑盒管道拿掉?简单来说,框架的思路变了。1.x 把这件事做成了一个固定的、你只管配置参数的“体质”。而 2.0 的思路更激进:把检索的决策权,真正交还给大模型(LLM)本身。
新方案把“检索”拆解成“subagent + 文件检索 + Skill 仓库”。
你的文档不再需要切片、向量化,直接放在 ./docs 目录里就行。Subagent 可以像人一样,用 grep_files 搜关键词找文件,再用 read_file 读原文。搜什么词、读哪段,都由 LLM 自己权衡,这比硬编码一个检索管道要灵活得多。
为了更好地承接与迁移,本章我们先回顾一下 1.x 的旧 API 作为参考(主要是为了维护老项目的同学),然后详细展开 2.0 推荐的新做法。
16.1 1.x RAG 旧 API 回顾
先看一个 1.x 的典型用法,算是留个念想:
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(...) 方法上已经被标记了 @Deprecated(forRemoval = true),编译会告警。所以,新项目就不要再走回头路了。
16.2 2.0 推荐的"subagent + 文件检索"
为什么不用 1.x 的 RAG 了?
我们掰开揉碎来看看 1.x 那条固定管道到底哪里别扭:
文档 → 切片 → 嵌入(向量化) → 存向量库
↓
用户提问 → 同样嵌入 → 余弦相似度搜索 → 取 top-k 切片 → 拼进 prompt → LLM 回答
这条路走下来,至少有三大痛点:
第一,黑盒检索,查无对症。 向量相似度高,不代表语义就相关。搜出来的片段可能“长得像”,但跟你的问题可能驴唇不对马嘴。最要命的是,你根本没地方去 debug 为什么它找到了这段,而不是那段。
第二,管道路径固定,模型是“局外人”。 怎么切片、怎么嵌入、怎么搜,都被管道定死了。LLM 在整个检索过程中就是个旁观者,完全没有参与决策。它只能被动接受喂到 prompt 里的东西。
第三,依赖的“外援”太多。 需要 Embedding 模型(比如 DashScope / Qwen Embedding),还得搭一个向量数据库(如 Milvus、Pinecone),凭空多了两个微服务需要维护,复杂度一下就上去了。
2.0 怎么做?
核心思路就是把“选择权”交还给 LLM。Agent 自带 grep_files(关键词搜索)和 read_file(读文件)两个利器,让 LLM 自己决定该搜什么关键词、该读哪个文件、该读多少内容。
整个工作流就变成了:
用户提问 → LLM 判断应该用什么关键词去 grep → 打开读相关文件 → 综合信息给出答案
对比之下,差距就非常明显了:
- 检索决策者:1.x RAG — 管道(程序);2.0 subagent 文件检索 — LLM(agent)
- 检索方式:1.x RAG — 向量相似度;2.0 subagent 文件检索 —
grep_files关键词搜索 +read_file精读 - 外部依赖:1.x RAG — Embedding 模型 + 向量库;2.0 subagent 文件检索 — 无,全依赖 agent 内置工具
- 可调试性:1.x RAG — 差(不知道管道为什么找了那段);2.0 subagent 文件检索 — 好(agent 日志会显示 grep 了什么词、读了哪个文件)
- 灵活性:1.x RAG — 固定管道,一成不变;2.0 subagent 文件检索 — LLM 可以自主分步:先 grep 找文件名 → 再 read 读内容 → 线索不够再 grep 扩大范围
这个思想的本质就是:与其让程序去猜“哪段文本和用户问题长得最像”,不如让 LLM 自己去想“我应该搜什么词、读哪个文件”。
在代码实现上,HarnessAgent 已经自带了 read_file、grep_files、glob_files 三个内置工具。Subagent 可以直接使用它们:
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 看到用户问题里带有“文档”字眼,就会主动 spawn 刚刚注册的 doc_reader subagent,后者再用 grep 和 read 工具自己决定怎么查。
16.3 进阶:用 Skill 仓库做"结构化 RAG"
如果你的文档量很大,并且希望按照主题进行“切分和管理”,直接把每份文档做成一个 Skill 是更明智的做法(具体可参考第 18 章)。你的目录结构会是这样的:
workspace/
└── skills/
├── product-faq/
│ └── SKILL.md
├── engineering-handbook/
│ └── SKILL.md
└── legal-policies/
└── SKILL.md
每个 SKILL.md 就像一份说明书,描述“这个 skill 是干什么的”:
# 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。
@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);
}
写好这个普通 Java 工具,然后注册到你的 Agent 或 Subagent 里就行。这才是 2.0 框架推崇的核心理念:该用什么工具,就用什么工具,不必再拘泥于一个叫 RAGKnowledge 的抽象层。
16.5 最小迁移清单(1.x RAG → 2.0)
最后,如果你正在从 1.x 迁移过来,下面这张对照表应该能帮你快速找到“新路”:
- 1.x:调
RAGKnowledge.retrieve(...)自动检索 → 2.0:Subagent 用内置grep_files+read_file检索 - 1.x:用
SimpleRAGKnowledge等内置分块、嵌入 → 2.0:框架已去掉内置管道。如需嵌入,自己写@Tool调向量库 - 1.x:配置分块策略、嵌入模型 → 2.0:在自定义
@Tool里自由实现,框架不再限制 - 1.x:
agent.knowledge(knowledge)→ 2.0:.subagent(retriever)或.toolkit(toolkit) - 1.x:
agent.call(..., retriever=knowledge)→ 2.0:拆成 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 中。
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自由实现,框架不再限制你的手脚。
来源:整理自互联网
免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。
相关文章
更多-
- 高德开放平台世界地图升级有哪些新变化
- 时间:2026-07-25
-
- 转炉炼钢终点静态控制预测模型概述
- 时间:2026-07-25
-
- CSS 3D从布局到立方体实现技巧全解析
- 时间:2026-07-25
-
- Starlette版本过高导致Jinja2 500错误与unhashable dict
- 时间:2026-07-25
-
- LangGraph边详解:智能体图道路系统
- 时间:2026-07-25
-
- ES与ADP打通成为企业级知识库存储首选
- 时间:2026-07-25
-
- HTAP是噱头还是标配?2026年混合负载数据库技术真相
- 时间:2026-07-25
-
- 从Demo到全球上线,开发者只需这一步
- 时间:2026-07-25
精选合集
更多大家都在玩
热门话题
大家都在看
更多-
- iOS 13.5.1电池续航差是电池耗电问题吗
- 时间:2026-07-25
-
- 苹果教育优惠开启 附购买攻略
- 时间:2026-07-25
-
- 苹果iOS 14 beta 2 测试版主要更新内容:除细节变化外修复多项Bug
- 时间:2026-07-25
-
- iOS 14 beta 2 是否解决内存占用过多问题?
- 时间:2026-07-25
-
- 受欢迎的奥特曼游戏有哪些
- 时间:2026-07-25
-
- iOS 14信息应用5大更新变化
- 时间:2026-07-25
-
- iOS 14正式版上线时间公布 官方全新介绍
- 时间:2026-07-25
-
- 最新苹果iOS 14 Beta 2版本更新内容全解析与升级教程
- 时间:2026-07-25