
ReAct 循环会推理但推理不能凭空产生事实。模型训练截止后的产品文档、企业内部的接口约定、用户上传的 PDF都不在参数记忆里。02 篇结尾说过一句话工具是 Agent 的手检索是 Agent 的书架。本篇拆开看这两样东西在 AgentScope Java 里的实现——Tool 注解驱动的工具系统三层 RAG 检索链路MCP 三种传输以及子 Agent 的两种声明方式。dream-scope 的知识与工具体系也在这篇完整亮相。先看一条贯穿全文的主线框架给能力模块定边界。AgentScope 的 Toolkit、SimpleKnowledge、McpClientBuilder 都是框架侧的积木dream-scope 要回答的是另一个问题——这些积木放在六边形架构的哪一层谁组装它们失败时按什么顺序降级。能力清单可以抄官方文档边界取舍才是工程文章该写的。一、工具系统Tool 注解驱动的函数调用AgentScope v2 的工具体系围绕两个类型展开Tool注解标在普通 Java 方法上Toolkit是注册中心。一个最小的工具长这样public class WeatherTools { Tool(name get_weather, description 查询指定城市的天气返回温度与天气状况) public String getWeather( ToolParam(name city, description 城市名称例如深圳, required true) String city, ToolParam(name unit, description 温度单位celsius 或 fahrenheit, required false) String unit) { String u (unit null) ? celsius : unit; return {\city\:\ city \,\temp\:26,\condition\:\多云\}; } }框架侧发生了三件事。第一Toolkit.registerTool(new WeatherTools())反射扫描对象上所有 Tool 方法逐个登记。第二方法签名加 ToolParam 的描述被组装成 OpenAPI 风格的 JSON Schema随每次推理发给模型——模型看到的不是 Java 方法而是一份工具目录。第三模型决定调用时框架把 JSON 参数反序列化成方法入参并反射执行返回值转回消息块进入下一轮推理。Tool 注解的属性值得过一遍每个都对应一个运行期行为属性默认作用name为空时回退方法名模型侧可见的工具标识description空模型决定是否调用的首要依据strictfalse强约束参数 JSON SchemareadOnlyfalse只读标记权限与沙箱会参考concurrencySafetrue并发安全标记externalToolfalse模型只生成调用由外部系统执行stateInjectedfalse向工具注入 Agent 状态有一个设计细节值得对照 Spring AIAgentScope 强制每个 ToolParam 写 name。原因是 Java 编译产物默认不含方法参数名框架拿不到 city 还是 unit 这种信息显式声明后schema 生成不依赖-parameters编译参数构建环境少一个隐性前提。工程上这属于把不确定性消灭在注解里的做法。1.1 别把业务上下文交给模型工具经常需要 userId、sessionId 这类调用上下文。常见的错误做法是让模型传——把 userId 写进工具参数模型可能填错、可能被提示词注入诱导填别人的。AgentScope 的约定是方法参数里不带 ToolParam 的参数框架按类型从 RuntimeContext 注入模型全程看不到public record UserContext(String tenantId, String userId) {} Tool(name list_my_orders, description 查询当前用户的订单) public String listOrders(UserContext ctx, ToolParam(name status, required false) String status) { return query(ctx.tenantId(), ctx.userId(), status); }这个机制和 02 篇的 RuntimeContext 一脉相承调用身份从 HTTP 层构建穿过中间件最终在工具执行点被消费。模型负责做什么业务上下文由框架注入两条信道物理分开。1.2 异步返回与工具组工具方法可以直接返回MonoString框架自动适配配合Schedulers.boundedElastic()把阻塞查询挪出事件循环。对无法改造的阻塞工具HarnessAgent 还提供异步 offload把执行丢到独立调度器。工具数量上来之后AgentScope 提供 ToolGroup把一批工具打包成组默认不激活再注册一个 meta tool 让模型按需装备/卸载工具组。价值在于上下文预算——几十个工具的 schema 全量常驻每次推理都占 token按组激活让模型只在需要时看到那批目录。二、dream-scope 的工具面三个演示工具与一个检索工具框架能力看完回到项目。dream-scope 的 chat 工具集中在一个纯 POJO 类 ChatTools无 Spring 依赖由 Toolkit 扫描注册Tool(name getCurrentTime, description 返回服务器当前日期时间含时区偏移) public String getCurrentTime() { return OffsetDateTime.now().toString(); } Tool(name calculate, description 计算四则运算表达式支持括号与小数例如 12*3) public String calculate( ToolParam(name expression, description 数学表达式仅允许数字、小数点、 - * / 与括号, required true) String expression) { ... } Tool(name httpGet, description 对 http/https URL 发起 GET返回响应体前 N 个字符) public String httpGet(...) { ... }三个工具都是演示位时间、计算、HTTP GET。真正的主角是第四个——retrieveTool(name retrieve, description 从知识库检索带编号的参考资料。回答产品、架构或调用方式时先调用再按 [1][2] 引用) public String retrieve( ToolParam(name query, description 检索问句尽量包含专有名词, required true) String query, ToolParam(name topK, description 返回条数默认 3上限 8, required false) Integer topK) { if (retrievePort null) { return 知识库未配置; } int limit topK null || topK 0 ? DEFAULT_TOP_K : Math.min(topK, HARD_MAX_TOP_K); var hits retrievePort.retrieve(query, limit); return RetrieveCitations.format(hits); }三个细节description 写的是使用时机不是功能罗列。回答产品、架构或调用方式时先调用再按 [1][2] 引用——前半句给模型的触发条件后半句直接规定引用格式。工具描述是模型路由工具的依据把使用规范写进描述比指望系统提示词约束更近。topK 有硬上限。模型传 100 也只取 8防御性钳制在工具边界完成不信任模型输出。retrievePort 为 null 时返回提示语而不是抛异常。知识库未配置是合法运行态本地无 Key 演示不是错误。片段出处dream-scope-adapter/src/main/java/com/zhu/scope/adapter/tool/ChatTools.java权限方面呼应 02 篇chat 主路径 PermissionMode.BYPASS文件与 Shell 工具关闭工具面收敛到这四个演示工具加 MCP 挂载。工具越少模型路由越稳这是刻意的减法。三、三层 RAG一条跨模块的检索链路retrieve 工具背后的 retrievePort 是整条 RAG 链路的门面但它只是接口。实现分布在三个模块各管一段HTTP / ChatTools.retrieve → RetrievePort再包 Hybrid Advanced → SimpleKnowledgeRetrievePort ← adapter 模块 → SimpleKnowledgeembed search → InMemoryStore 或 PgVectorStore ← 向量库二选一层类职责adapterSimpleKnowledgeRetrievePort适配官方 SimpleKnowledge管 embed、入库、切块、向量检索knowledgeHybridRetrievePort向量与关键词双路召回RRF 融合knowledgeAdvancedRetrievePort叠加问句改写与精排失败退回内层webRagPortsConfig组合根按配置装配唯一 RetrievePort Bean分层的依据是依赖方向。adapter 里的类才允许 import io.agentscope六边形边界01 篇的 enforcer 在构建期强制knowledge 模块定义自己的 RetrievePort 接口写 RRF 融合时看不到 AgentScope 的任何类型web 的 RagPortsConfig 是组合根决定生产环境用哪条实现链。检索逻辑因此可以脱离框架单测——给 HybridRetrievePort 注入假端口RRF 融合的正确性不依赖任何向量库。3.1 adapter 层SimpleKnowledgeRetrievePort这一层做的是官方组件的工程化包装。SimpleKnowledge 是 AgentScope 官方的知识抽象管 embed 和相似度检索适配器补齐生产要用的部分向量库二选一不传 store 按 embedding 维度建 InMemoryStore单测、无 PG 的本地演示传配置则建官方 PgVectorStorebuild()自动 CREATE EXTENSION vector 并建表文件入库分批大 PDF 整本一次 embed 容易超时按每批 8 块写入进度经 ThreadLocal 回调上报reading / embedding 两个阶段稳定 id 与来源索引AgentScope Reader 自带的块 id 不可靠入库前统一盖自有 docIdpayload 写入 source 与 docType——按来源列表、按来源覆盖删除、重启后从 pg 重建索引都靠这套约定失败时保正文embedding 失败抛 EmbeddingIngestException把已经从 PDF 抽出的正文带上——额度不足时整份文档的解析成果不白费上层拿正文降级写关键词索引。切片参数也在这层生效chunkSize 2000、chunkOverlap 200配置项dream-scope.rag.*只影响文件入库addText 整段一块不切。3.2 knowledge 层HybridRetrievePort 的 RRF 融合纯向量检索的短板很老专有名词、型号、代号这类强字面信号embedding 不一定拉开距离。HybridRetrievePort 的答案是双路召回 RRFReciprocal Rank Fusionint fetch Math.min(topK * 3, Math.max(topK, 32)); ListRetrieveHit vector safeRetrieve(primary, query, fetch, source); ListRetrieveHit kw safeRetrieve(keyword, query, fetch, source);每路多取约 3 倍候选RRF 用排名倒数融合常数 K 取 60——行业里验证过的默认值让两路的排名平滑互补而不是互相碾压。双路不是摆设降级逻辑内建在检索流程里向量这条路空了就用关键词结果关键词空了就用向量结果两路全空才返回空。关键词索引是进程内的 InMemoryKeywordIndex由 Hybrid 自己维护向量检索成功后把返回正文镜像进关键词索引embedding 失败则只写关键词。向量端口不反向持有索引写权集中在 Hybrid 一处不会出现两处各写一半的状态。3.3 knowledge 层AdvancedRetrievePort 的改写与精排Hybrid 之上还可以再包一层 AdvancedRetrievePort叠加两个可选增强问句改写DashScopeQueryRewritePort用便宜的小模型默认 qwen-turbo把口语问句扩成检索友好的表述。口语和文档用词经常对不上——怎么部署扩成部署 安装 启动 配置召回率立刻不同精排DashScopeRerankPort召回负责别漏精排负责排对。粗排出 top 24精排模型逐对打分重排取前 topK。默认模型 gte-rerank-v2配置默认开启。两步都遵循同一条纪律失败退回内层结果。改写服务挂了用原句检索精排挂了用粗排顺序——增强能力必须表现为增益而不是单点故障。这也解释了配置里 rewrite 默认关、rerank 默认开的取舍改写收益依赖问句风格精排收益对文档问答几乎稳定默认值按风险不对称来定。四、组合根与降级链provider 四档RagPortsConfig 是 web 模块的组合根决定生产环境检索栈的形态。核心是一个 provider 配置dream-scope.rag.provider四档语义provider行为auto默认有 jdbc embedding Key → pg仅 Key → 内存向量否则或失败 → 关键词pg / pgvector强制 pg缺 jdbc 或 Key 直接抛错不静默降级simple强制内存向量缺 Key 抛错keyword进程内关键词不调 embeddingauto 档的降级链值得展开pg 失败 → 试内存向量 → 再失败 → 关键词。每一档成功后都过 enhance按开关挂精排与改写。运行期的 embedding 额度类错误如免费额度用尽走另一条路关闭半开的向量端口切换到关键词索引检索继续可用——用户搜到的是关键词命中而不是一条 500。降级不是无脑兜底。显式指定 pg 却缺配置时直接抛错不启动——配置写明了意图静默降级反而掩盖问题只有 auto 档才允许悄悄往下走。自动降级是给没表态的容错显式配置要的是说到做到两种语义分开处理。还有一个重启恢复的细节sourceIds来源 → 块 id 索引在内存里进程重启即空。组合根启动时检查 pg 表有数据则从 payload 重建 source 索引、镜像关键词列表/删除/混合检索照常可用表为空才灌演示语料避免重复 upsert。检索链路整体如下片段出处dream-scope-web/src/main/java/com/zhu/scope/web/config/RagPortsConfig.java五、刻意不挂 knowledge()单通道检索的取舍框架其实内置了一条更自动的路ReActAgent.knowledge()把知识源挂到 Agent 上每轮推理前自动检索注入旧版 GenericRAGHook已标 Deprecated。dream-scope 刻意不用原因写在两处源码注释里避免与 retrieve 工具各搜一次、引用两套。双通道的实际问题不是多花一次检索而是一致性自动注入走一套检索配置工具调用走另一套同一轮里模型可能看到两份来源不同、格式不同的资料引用出处无法归一。收敛成单通道后检索只发生在 retrieve 工具里配置、降级、引用格式只有一份模型看到的资料永远带着 [1][2] 编号。这条取舍顺带回答了Agentic RAG 还是 Application RAGAgentScope 官方也在文档里把工具化检索列为推荐形态——RAG 退化为普通工具调用后权限、压缩、子 Agent 隔离这些机制全部免费复用框架不需要为检索单开一条隐式注入路径。5.1 knowledge Agent只检索不调模型单通道之外dream-scope 还内置了一个特殊的 Agentid 为 knowledge 的 ScopeKnowledgeAgent。它不走模型handle 方法就是一次检索Override public AgentInvokeResult handle(AgentInvokeRequest request) { String query request null ? : request.input(); var hits retrievePort.retrieve(query, DEFAULT_TOP_K); // topK 5 String formatted RetrieveCitations.format(hits); return new AgentInvokeResult(id(), formatted); }它实现的是与 chat Agent 同一个 StreamingAgentHandler 接口HTTP 侧看来两者无异——请求进、事件出。差别在于没有模型调用不推理、不消耗 token检索 top 5 直接返回带编号结果。定位是给前端或调用方一个纯检索出口搜索页面、调试知识库内容、给第三方系统供数都不该为此烧一次 LLM。它与 chat 工具共用同一个 RetrievePort Bean检索口径完全一致。片段出处dream-scope-adapter/src/main/java/com/zhu/scope/adapter/ScopeKnowledgeAgent.java六、MCP三传输与运行态收敛工具的第二来源是 MCPModel Context Protocol——把外部 MCP Server 的工具挂进 Toolkit。AgentScope 在 core 内置了 MCP 客户端McpClientBuilder 提供三种传输传输方法场景stdiostdioTransport(command, args...)本地拉起子进程npx filesystem 等SSEsseTransport(url)旧版远端服务Streamable HTTPstreamableHttpTransport(url)新版远端服务主流方向接入只要两步builder 建连拿到 McpClientWrapperToolkit.registerMcpClient 注册全部工具McpClientWrapper mcp McpClientBuilder.create(demo) .streamableHttpTransport(http://127.0.0.1:8093/mcp) .timeout(Duration.ofSeconds(30)) .buildAsync() .block(); // 异步建连block 等握手完成 toolkit.registerMcpClient(mcp).block(); // 拉取工具 schema 并注册之后 MCP 工具与本地 Tool 在模型侧无差别。生命周期上 McpClientWrapper 持有连接与子进程应用关闭时逐个 close——dream-scope 的 ChatMcp.closeQuietly 按打开的逆序回收失败不掩盖主异常。6.1 web 为什么拒绝 stdio框架三传输都支持dream-scope 的 web 模块却在运行态把 stdio 拒了。写 tools.json 时直接抛错if (stdio.equals(normalized)) { throw new IllegalArgumentException(mcp stdio transport is not allowed); }理由是部署形态。stdio 传输要在宿主机上拉起子进程适合开发者本机web 模块的目标环境是容器与服务器npx 拉子进程意味着镜像里要预装 Node、子进程逃逸出 JVM 的资源管理、故障排查多一层进程树。运行态只留 sse 与 streamableHttp 两种 HTTP 传输工具来源全部走网络边界干净。值得注意的是拒绝的位置不是在 MCP 建连时才报错而是启动装配写 tools.json 时就抛——配置错误在启动期暴露不带病运行。这与 02 篇条件分支只出现在装配点的取舍同源。片段出处dream-scope-web/src/main/java/com/zhu/scope/web/util/WorkspaceSubagentSeed.java6.2 工具名用服务端原名dream-scope 自带一个演示 MCP ServerDemoMcpServer随 web 端口起在 8093提供一个 echo 工具。模型看到的工具名是mcp__demo__echo——这不是框架加的前缀而是服务端公布的原名AgentScope 原样注册。这个细节的工程含义工具名冲突的治理责任在服务端命名客户端不做二次加工。多 Server 挂载时各自的名字空间靠服务端自己保证模型侧看到的目录与 MCP 协议层完全一致排查问题时不用在框架改名和服务端起名之间来回对。七、Subagent两路径声明与委派工具第三个能力是子 Agent。AgentScope 的 Subagent 体系解决三件事上下文隔离子任务的过程内容不污染主上下文、专职化独立提示词与工具面、后台并行。声明方式有两条路径编程式builder 里直接构造 SubagentDeclaration。dream-scope 只放了一个 summarizerstatic SubagentDeclaration summarizer() { return SubagentDeclaration.builder() .name(SUMMARIZER_ID) .description(把用户给出的文本缩成不超过三句的中文摘要。) .inlineAgentsBody(你是摘要子 Agent。禁止调用任何工具。只输出摘要正文不要标题或前缀。) .maxIters(4) .build(); }声明式在 workspace 的subagents/目录写 Markdown文件名即 agent_id。weather-agent 与 flight-agent 两个演示子 Agent 就是这种形态——YAML front matter 写 name、description、maxIters、工具白名单正文写系统提示词含固定的假数据格式不调外部 API。两路在 HarnessAgent.build() 时合并注册。规则只有一条硬约束同一个 agent_id 不能既出现在代码里又出现在文件里——重复声明直接冲突不留文件覆盖代码的隐式优先级。子 Agent 的运行靠五个内置工具模型自主调用工具作用agent_spawn创建/复用子 Agent 并派任务timeout_seconds0 转后台agent_send向已有子 Agent 实例继续发消息task_output按 task_id 取后台任务结果wait_async_resultsbarrier 等待多个后台任务结果task_cancel / task_list取消/列举后台任务分配取舍的思路和 MCP 拒 stdio 一致确定性高的放代码内容型的放文件。summarizer 是系统行为的一部分摘要出口参数稳定代码声明便于重构时一起评审天气与航班是演示位提示词与数据格式会经常调整Markdown 文件改完重启即生效不需要碰 Java。声明式路径的文件还天然是给用户/部署方留的配置位——不写代码也能加子 Agent。片段出处dream-scope-adapter/src/main/java/com/zhu/scope/adapter/subagent/ChatSubagents.java、dream-scope-web/src/main/resources/workspace/subagents/weather-agent.md八、本篇学到了什么与 04 预告框架侧本篇完成了知识与工具层的四个认知Tool 注解驱动 schema 自动生成无 ToolParam 的参数由 RuntimeContext 按类型注入——业务上下文不过模型的手Toolkit 是统一注册中心本地工具、工具组、MCP 工具在模型侧无差别RAG 的推荐形态是工具化检索框架的 knowledge() 自动注入让位于单通道的工具调用MCP 三传输由 McpClientBuilder 统一提供stdio 适合本机、HTTP 传输适合服务端。工程侧dream-scope 给出了三层检索链路的完整样板adapter 适配官方组件、knowledge 做 RRF 融合与改写精排、web 组合根按 provider 四档装配——pg、内存向量、关键词逐级降级失败路径全部有归宿检索只有一个入口knowledge Agent 提供不调模型的纯检索出口MCP 运行态拒绝 stdio、工具名用服务端原名子 Agent 两路声明、build 合并、同 id 冲突即报错。工具、检索、子 Agent 三块的共同点是能力全部来自框架边界全部自己划。下一篇是主干最后一篇进入互通层A2A 协议与 Agent Card——服务怎么被别的 Agent 发现与调用Nacos 提示词热更新的接线方式与版本兼容的现实限制。项目信息dream-scope 开源地址https://github.com/logosssss/dream-scope 觉得有帮助欢迎 starDream-SaaS 项目地址https://dream-saas.com有问题评论区见欢迎交流~