ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AgentScope Java 实战:工具调用与知识检索,给 Agent 装上“手”和“书架”

AgentScope Java 实战:工具调用与知识检索,给 Agent 装上“手”和“书架” 很多 Agent 项目跑不起来不是因为模型不够聪明而是因为没有手和书架——模型很会说话但既不能真的去查数据、改状态也没有一份可以随时查阅的内部资料库结果一问就露馅。之前用 AgentScope Java 搭好了能跑通对话的 Agent 基础骨架但真要往业务上放卡住的往往是这一层知识与工具层。这篇实战 03我把在 AgentScope Java 里给 Agent 装手工具调用和书架知识检索的完整做法、落地细节和踩坑记录整理出来适合已经把 AgentScope Java 基础跑通、准备接真实业务的开发者也适合刚接触 Agent 开发、想知道工具和知识到底怎么落到代码里的朋友。1. 先搞清楚工具层和知识层到底在解决什么问题1.1 工具层把会说话变成能办事先说工具层。大模型本质是一个文本生成器你给它一段 prompt它给你一段回复。但业务系统要求的是什么是帮我查一下订单状态把这个工单状态改成已处理根据报价规则算一下运费。这些操作不可能靠模型吐一段文字就完成——它没有权限也不应该让它有直接操作数据库的权限。工具层就是在这中间架一座桥模型负责任务理解与决策输出我想调用哪个工具、传什么参数真正执行的是你写在 Java 类里的方法。这个机制在行业内通常叫 Function Calling / Tool CallingAgentScope Java 天然支持我们只需要把方法声明清楚框架负责让模型看到这些工具并在模型决定调用时路由到对应的方法上执行。我见过不少第一次接触 Agent 开发的同事总觉得工具层很高深。其实说白了就是三件事把你的方法注册给模型看、模型按需选择、框架帮你执行并把结果喂回给模型。难点从来不在概念上而在如何把方法声明得让模型看得懂、用得上。1.2 知识层解决一本正经地胡说八道知识层解决的问题更朴素。模型的训练数据是有截止时间的它不可能知道你公司内部的产品手册、报价规则、售后流程。你直接问它我们的退款政策是什么它大概率会编一个听起来很像那么回事的答案——这就是所谓的幻觉。知识层的标准做法是 RAG检索增强生成先把资料切分成片段做向量化存入知识库用户提问时先从知识库检索出相关内容再把这些内容拼进 prompt 给模型参考。相当于给模型配了一个可随时翻阅的书架问什么先翻书翻到了再回答不凭记忆硬编。不过这里要提醒一句书架不只是静态文档。一个真正能用的 Agent知识来源至少有三类静态知识库产品手册、规章制度、结构化业务数据订单、库存、动态对话记忆用户之前说过什么、偏好什么。这三类在 AgentScope Java 里接入方式不太一样下一节我会细讲。1.3 两个层在 AgentScope Java 里的整体位置我觉得先把这两个层在 Agent 运行链路里的位置画清楚后面写代码才不会晕。一次完整的 Agent 请求大概是这样的用户提问 → 会话记忆整理 → 意图理解 → 知识检索如果需要 → 模型决策用哪个工具要不要查资料 → 执行工具/读取检索结果 → 模型组织最终回复 → 返回给用户工具层和知识层的位置在意图理解之后、模型决策前后。知识检索为模型提供事实依据工具调用为模型提供执行能力。在 AgentScope Java 的组件结构里工具会注册到工具管理器Tools知识库和记忆则对接上下文组装器——最终它们都会被拼装成模型可见的内容或可调用的函数列表。理解了位置接下来就可以动手了。2. 装手从普通 Java 方法到可被 Agent 调用的工具2.1 最省事的声明方式在方法上加描述在 AgentScope Java 里注册一个工具最直观的方式是把一个普通 Java 方法标记为可被 Agent 调用的工具。我这边项目经验的写法大概是这样的不同版本注解名可能稍有差异思路是通用的public class LogisticsTools { Tool( name calculate_freight, description 根据重量和距离计算物流运费只用于国内快递报价。, paramDescription { 货物重量单位千克支持小数。, 配送距离单位公里整数即可。 } ) public String calculateFreight(double weightKg, int distanceKm) { double base 8.0; double total base weightKg * 1.5 distanceKm * 0.02; return String.format(运费计算结果%.2f 元, total); } }然后把这个工具类注册到 Agent 上Agent agent Agent.builder() .model(model) .tools(new LogisticsTools()) .build();核心就这几行。AgentScope Java 会自动把你标记过的方法提取出来生成模型可读的工具清单并处理参数映射和调用路由。这里有个主要建议工具方法尽量用基础类型做参数String、int、double 这类返回结果也尽量用简明文本。原因后文会讲——模型对复杂对象结构的天生弱视远比你想象的严重。2.2 工具描述就是给模型的说明书决定它会不会用我踩过最直接的坑工具声明得没问题签名也没错但 Agent 就是死活不调用或者调用了错误的工具。查到最后问题出在描述上。模型决定是否调用一个工具依据的是你写的 name 和 description。它先看描述再看参数。描述写得含糊模型就只能靠猜。举个反面案例。有个工具方法命名为calcPrice描述写的是计算价格。听起来没毛病但 Agent 面对两个工具时——一个是calcPrice计算运费一个是calcTax计算税费用户问运费多少钱模型犹豫半天选了calcTax。改成下面这样之后问题立刻消失Tool( name calc_freight_price, description 计算国内快递运费。当用户询问寄件费用、运费、快递价格时使用。需要提供货物重量千克和运距公里。 )核心原则是描述要回答三个问题——这个工具做什么、什么时候该用、需要什么关键输入。不要写废话但要把触发条件写清楚。2.3 从用户提问到工具返回完整链路工具调用的完整链路我拆给你看用户问从北京寄 3 公斤的东西到上海多少钱第一步AgentScope Java 把用户问题、历史记录、系统 prompt 一起交给模型同时附上工具清单你注册的那些方法。第二步模型判断这个问题需要计算应该调用calc_freight_price于是返回一个结构化的工具调用请求包含工具名和参数值weightKg3.0, distanceKm1200 左右具体由模型从上下文推理得出。第三步框架拦截到这个请求反射调用你注册的 Java 方法拿到返回结果。第四步框架把工具返回结果拼接到原始对话中再次交给模型让模型基于这个结果组织自然语言回复。这里面有个细节很多人容易忽略工具方法的返回值会作为模型看到的文本再次进入模型。所以返回文本的格式要尽量利于模型理解。我习惯在返回结果里直接带结论而不是丢一堆 JSON——模型从 JSON 里提炼数字再算一遍出错概率会高很多。提示如果工具返回的是结构化的业务对象订单信息、商品列表建议在方法内部先把它转成一段简明的文本再返回。模型更擅长读文字而不是解析结构。3. 装书架知识库接入与检索的落地细节3.1 知识、记忆、上下文三个容易混的东西在接知识库之前我强烈建议先把三个概念分清因为它们在代码里是三种不同的处理方式。静态知识公司手册、操作规范这类长期不变的资料。对应的是知识库 向量检索按需取用。动态记忆用户的历史偏好、上次聊到哪了、最近几次对话摘要。对应的是记忆组件。AgentScope Java 里可以把最近几轮对话自动带上也可以把关键信息抽成记忆片段。上下文当前这一次请求最终组装给模型的所有材料包括用户问题、切出来的记忆、检索到的知识片段、工具说明和结果。我曾经把这三样全塞进同一个向量库结果用户只是换了个说法问同一个问题Agent 检索出来的却是完全不相关的历史对话。后来才把静态资料和对话记忆分库存储问题才消停。在 AgentScope Java 里这三样东西分别由不同的组件管理知识库组件负责静态资料的检索记忆组件负责会话历史的存取上下文组装则由 Agent 的内部逻辑完成。所以你接书架的时候先想清楚接的是哪种知识别一股脑全塞进去。3.2 最小可用的知识库切分、向量化、检索一个最小可用的知识库流程是固定的资料切分 → 文本向量化 → 存入向量数据库 → 检索召回。在 AgentScope Java 里你可以自己组织这条链路也可以直接接入现成的向量库组件。常见的组合是Embedding 模型负责向量化向量数据库如 Milvus、Chroma 或你内部已有的引擎负责存储和检索。这里我只讲一个最容易被忽视的参数切分大小。我一开始图省事把整份产品文档切成 64 个字符的碎片想着这样检索会更精准。实际跑起来发现检索召回的内容经常是残肢断臂——一句完整的话被拦腰截断模型拿着半句话根本没法组织回答。后来改成按语义段落切分一个段落或者几条相关句子组成一个片段我这边实践下来 500~1000 字比较稳妥检索召回时也尽量按片段返回而不是按单句返回。模型拿到的上下文完整了回答质量立刻上一个台阶。3.3 检索参数topK 和相似度阈值不是越大越好知识库检索有两个参数最影响效果topK取几条结果和相似度阈值低于多少分不return。我这边实测的结论是topK 不要一味调大。topK3 时Agent 专注于最相关的资料回答准确率最高调到 topK8 以后大量低相关的片段混进来模型反而开始左右为难甚至引用错误内容。相似度阈值也一样。阈值设太低垃圾内容全进来设太高该召回的内容又漏了。我用过一阵 0.7 的阈值后来发现不同 Embedding 模型的分数尺度差异很大换模型就得重新标定。稳妥的做法是离线准备几十个真实问题跑一遍检索看召回的片段是不是人工也认为相关再定阈值。提示知识库上线后不是一劳永逸的。把用户实际问过的、检索效果差的问题攒起来定期重新切分、调参比反复换模型有效得多。4. 手和书架协同一个查规则再算价的完整场景4.1 场景设定让 Agent 根据内部费率表计算运费工具和知识单独都能跑但真正体现价值的是它们协同工作的时候。我拿近期做的一个物流咨询 Agent 举例。需求是这样客户会问北京到上海 3 公斤多少钱我们发的这批货要走 500 公里首重多少。报价规则存放在一份内部费率文档里有首重价、续重价、按距离的分档系数。这些规则如果写死在代码里改一次费率就得发一次版如果让模型靠训练知识回答那纯属碰运气。于是设计成费率规则放知识库书架运费计算逻辑做成工具手。Agent 先检索知识库拿费率规则再调用工具完成计算最后组织答案。4.2 一次真实请求的链路拆解用户提问北京到上海 3 公斤多少钱AgentScope Java 内部大概走了这么几步第一步知识检索。框架把北京到上海 3 公斤 运费作为查询在知识库里检索到关于计费规则首重与续重说明和北京-上海属于华东区距离系数 1.2的片段。第二步材料拼接。检索到的费率片段和工具清单一起进入模型输入。第三步模型决策。模型发现需要计算决定调用calc_freight_price工具参数为 weightKg3.0、distanceKm1200它从知识片段里推理出这个值。第四步工具执行。你的 Java 方法按规则算出运费。第五步最终回复。模型拿到工具计算结果结合检索到的费率解释回复用户您好北京到上海 3 公斤运费为 35.6 元其中首重 8 元续重按每公斤 1.5 元计算华东区距离系数 1.2。整个过程里工具负责精确计算知识库负责提供计算依据模型负责把两者翻译成用户能懂的话。哪个环节负责什么分得明明白白。4.3 为什么这样设计而不是把规则全塞进 Prompt可能有朋友会问费率规则也不多直接写进 system prompt 不就行了吗短期内可以但有几个实际问题规则一多 prompt 会爆炸每次请求都传全量规则token 成本和响应时间都受不了规则变了还要改代码、重启这不是 Agent 该有的工作方式。知识库的好处在于按需加载用户问上海就只取华东区的规则用户问北京就只取华北区的规则。工具的好处在于计算准确模型天生不擅长算术但把计算交给确定性的 Java 方法结果一定是准的。这就是所谓的手和书架的分工——书架负责知道规则手负责执行计算大脑模型负责协调和解说。5. 实战排错在这个层上最容易踩的几个坑5.1 工具描述含糊Agent 就是不用或者选错前面提到过描述是模型判断的唯一依据。我再补充一个排查方法如果 Agent 不调用你预期的工具先把工具描述打印出来站在一个不知道你代码逻辑的陌生人的角度看一遍看它能不能根据描述判断出这个工具什么时候用、怎么用。我碰到过一个典型case工具方法内部逻辑完全正确但描述里没写单位——重量是千克还是克模型搞不清楚于是不传参数或者传错单位。在描述里加上单位千克调用准确率立刻上去了。5.2 知识库命中率低别急着换 Embedding 模型很多朋友一发现检索结果不对第一反应是换更贵的 Embedding 模型。我的经验是先看切分再看查询最后才是换模型。检查切分是否把完整语义切碎了检查查询词是否需要改写比如用户问运费多少知识库里写的是资费标准检索匹配不好可以试着在检索前做查询改写或同义扩展这些都没问题才轮到换模型。有一回我调了一整天的向量模型最后发现是文档切分工具把表格数据切得面目全非检索召回的内容全是表格碎片。修复切分策略后命中率直接翻倍。5.3 工具方法别持有可变状态并发场景下会翻车如果工具方法内部使用成员变量保存状态比如记录上一次调用的结果在并发场景下会出现串数据的问题——用户 A 的请求把用户 B 的数据覆盖了。AgentScope Java 的 Agent 服务部署后工具实例是可能被多线程复用的。工具方法应该是无状态的参数全从方法入参来结果返回后不保存任何中间状态。如果确实需要状态比如记录上下文把它放到 Agent 的会话记忆里而不是工具类的成员变量里。5.4 一定要能看清 Agent 的内心戏最后这条建议可能最实用Agent 开发最大的难点是看不见。模型为什么选了这个工具知识库到底检索到了什么工具返回了什么整个过程对你来说就是个黑盒出了问题根本无从排查。所以我从第一个 Agent 项目开始就养成了一个习惯把 AgentScope Java 的调试输出打开把模型原始请求、工具调用列表、工具返回结果、最终回复全部打印到日志里。这不是偷懒而是必须——没有这些日志你连是模型决策错了还是知识库检索错了还是工具算错了都分不清。还有一个辅助技巧单独写一个测试入口不经过 Agent直接手动调用工具方法和知识检索方法先确认书架里有货、手能干活再接上 Agent 联调。这样能把问题快速定位在组件本身还是模型决策。说实话把知识与工具层真正做扎实之后Agent 才算从demo 玩具变成了能上生产的工具。我现在的体会是模型这块大家用的都差不多真正拉开差距的恰恰是工具声明得清不清楚、知识库切分得合不合理、日志埋得够不够细这些笨功夫。最后再分享一个小技巧每次新增工具或知识库内容先用一套固定的测试问题跑一遍把结果存档下次改了东西再跑一遍对比。这套回归测试看着土但能帮你兜住九成以上的低级回归问题。
返回列表