最近在写AgentScope Java实战系列,前两篇把对话层和记忆层过了一遍,模型终于能记住上下文了。但说实话,光会聊天还远远不够——用户要的是Agent能查订单、能改配置、能读文档。这篇“实战03”正好切入整个链路里最出效果的一环:知识与工具层。
一句话解释这层是干什么的:给Agent装上“手”和“书架”。手,是工具调用能力,让Agent能真正操作外部系统;书架,是知识库,让Agent能利用私有文档、实时数据,而不是只靠模型肚子里那点训练语料。这篇我会从整体设计思路讲起,拆工具注册与执行全流程,再讲知识库怎么切分、向量化、召回,最后给出一套完整的实战走查和问题排查清单。内容偏实操,适合正在用AgentScope做Java Agent开发的工程师,也适合刚接触Agent架构、想理解“函数调用+RAG”到底在解决什么问题的新手。
1. 为什么需要知识与工具层——先搞懂Agent的三层结构
1.1 AgentScope里的三层模型:对话、知识与工具、编排
AgentScope在架构上其实是很典型的三层分工:对话层负责“怎么说”,知识与工具层负责“拿什么说、凭什么做”,编排层负责“接下来做什么”。前两层是素材和手段,第三层是流程控制。我们这次卡在中间这一层,是因为它直接决定了Agent是“能聊”还是“能用”。
对话层处理的是模型输入输出、上下文窗口、多轮记忆。这块如果没做好,模型会“失忆”,但即便不失忆,它也回答不了私有业务问题。比如用户问“我这个订单为什么还没发货”,模型脑子里根本没有你系统的订单数据。这个时候就需要两种能力补齐:要么给它一本书查(知识库),要么给它一只手去问(工具调用)。前者解决“不知道”,后者解决“做不到”。
编排层则是在拿到知识和工具结果之后,决定下一步该调用哪个工具、要不要继续追问用户、什么时候该终止。如果没有知识与工具层,编排层会变得很空——模型只能反复生成文本,没法触发真实动作。所以我习惯把这一层理解为Agent的“地基”。地基不牢,后面聊什么多智能体协作、自动规划,都是空中楼阁。
1.2 “手”和“书架”到底指什么
拿人打个比方。你招了一个实习生,脑子很聪明、表达很流畅,但坐在工位上没有任何资料、不能碰内部系统。他能干什么?顶多陪你聊天。Agent也是一样,大模型是“大脑”,但它没有被接入任何外部资源。工具层就是给这个实习生开权限、教他操作ERP系统;知识层就是给他一个档案柜,遇到不懂的规则可以去翻。
工具层的核心是函数调用(Function Calling),也就是让模型输出“我想调用某个方法、参数是什么”,再由框架路由到真实Java方法去执行。知识层则是RAG(检索增强生成),先把文档切成小块、做向量化,用户提问时先检索最相关的片段,再把片段塞进上下文让模型回答。
这两个东西经常被放在一起讲,因为它们都是对模型能力的“外挂式扩展”。但它们的本质完全不同:工具是“动词”,用来改变世界状态;知识是“名词”,用来提供事实依据。明白了这一点,你就知道什么信息该放书架、什么动作该用手做,后面才不会混。
2. 工具层:给Agent装上一双能干活的“手”
2.1 工具的本质:从LLM的函数调用到Java方法暴露
深挖一层,函数调用没那么玄乎。模型本身只会“输出文字”,但我们可以诱导它输出结构化的调用意图,例如一段JSON,里面写好“工具名=queryOrder,参数=orderId:10086”。框架接到这段JSON后,解析参数、反射调用那个Java方法,再把返回结果作为新的上下文喂回给模型。
这中间AgentScope做了两件关键事情:一是把模型输出的自由文本解析成符合Java方法签名的参数,二是把Java方法的返回结果序列化成模型能理解的文本。你想想,如果没有这个框架层,你需要自己写JSON解析、类型转换、异常处理、结果回填……每接一个工具都要重复一遍,工程量很大。框架存在的意义,就是把“模型输出文本”和“代码执行”之间这条断路接上。
我在实战中看到很多人忽略了一点:模型选择工具,靠的是“工具描述”而不是工具名本身。模型并不“理解”你的Java方法逻辑,它只看你提供的描述和参数schema。所以工具层的第一个门槛不是写代码,而是把每个工具的方法名、描述、参数约束写得足够清楚。
2.2 AgentScope Java工具注册的三种主流方式
AgentScope Java的工具注册方式,跟Spring Bean的暴露思路很像,无非就是换了个壳。我整理下来,常用的是以下三种。
- 注解式:在Java方法上打一个框架注解,声明工具名称、描述、参数说明,框架在启动时自动扫描并注册。这种最省事,适合大多数业务方法。
- 声明式:在配置类里通过Builder或注册器显式添加工具描述,再把实际执行逻辑的类传进去。适合源码不方便加注解的第三方SDK方法。
- 自动扫描+手动补充:框架扫描指定包路径下的注解方法,再手动补充一些动态生成的工具(比如把数据库里配置的API列表批量注册上去)。
三种方式的差异主要体现在灵活性和可维护性上。注解式最快但散落在代码里,声明式集中但代码量稍多,动态补充适合数据驱动的场景。我一般建议小团队、业务方法固定的项目用注解式,中大型项目把公共组件做成声明式工具库,便于测试和复用。
2.3 一个工具“从定义到被调用”的全过程
用一个“查询订单状态”工具来串一遍完整路径,你就能感受到这个环节有多少细节。
第一步是定义:确定方法入参是订单ID,返回值是订单状态、物流信息、时间节点等字段。第二步是注册:框架把工具名、描述、参数约束登记到模型可见的“工具清单”里。第三步是模型决策:用户说“查一下我的10086订单”,模型根据描述判断该调queryOrder,并生成参数。第四步是执行:AgentScope解析参数,调用你的Java方法,执行过程中可能要查数据库、调下游接口。第五步是回填:结果转成文本,连同原始上下文一起再交给模型,模型据此组织最终回复。
这五个步骤里,最容易被低估的是第四步。执行可能失败,可能超时,可能返回空。如果你不在这一步做好异常兜底,模型拿到的是一个Exception堆栈,它就只能硬着头皮编一个答案。做工具层,本质上是在做“边界管理”:哪些错误可以直接告诉用户,哪些错误要转换成语义化的信息,哪些情况该触发重试。
2.4 工具层设计中的几个关键决策
参数校验是第一个关键点。LLM生成的参数不一定合法,可能类型对但数据不对。所以工具方法入口不要信任模型,要像对待外部API请求一样做校验。第二个是超时与熔断,一个下游接口如果三秒没返回,Agent没必要干等,直接返回“查询超时,请稍后再试”,比拖着整个链路强得多。第三个是权限控制,一个Agent能调用的工具必须能收敛到具体账号和角色,不能让模型通过工具操作越权数据。
我在写AgentScope里的Tools层时,最常做的一件事是“幂等设计”。很多工具调用是模型试错触发的,同一个查询可能被执行两次,这没大碍;但如果是退款、发消息、改订单这种写操作,一旦不幂等,用户就会收到两条通知。所以写操作工具一定要支持幂等键或状态机判断,这是做Agent工具层最容易踩的坑。
3. 知识层:给Agent配一个随取随用的“书架”
3.1 LLM的知识局限与RAG的出现
先说说模型自身的问题。大模型的训练数据有截止时间,它不知道你的公司昨天发布的新规则;它也没见过你的内部产品文档、私有系统操作手册。更麻烦的是,它的“知识”是概率性记忆,你问它某个冷门配置项,它可能自信地给出一个完全错误但听起来很合理的答案,这就是幻觉。
RAG的思路很简单:别让模型凭空想,先给它资料,再让它照着组织语言。就像考试时允许你带参考书,你翻到相关章节再作答,准确率自然高很多。这也是为什么近两年“RAG架构”几乎成了企业Agent落地的标配——它不改变模型本身,只是改变了模型回答问题时依赖的信息源。
需要强调一点,RAG不是一劳永逸的银弹。它把“模型不知道”的问题,转换成了“知识库有没有、检索能不能找得到”的问题。如果你的书架本身是乱的,检索结果不相关,模型基于错误片段回答,反而比不接知识库更危险。所以知识层的工作,一半在做“入库”,另一半在做“召回质量控制”。
3.2 知识库的构建流程:切分、向量化、召回
知识库的构建可以拆成三条流水线:文档处理、向量化入库、在线召回。
文档处理的第一步是切分。原始文档是一个几百页的PDF,不可能整本塞进上下文。切分策略很关键。按固定长度切最容易,但会把语义拦腰截断;按标题、段落切更贴合语义。我常用的是“滑动窗口”:每段控制在二三百字左右、相邻窗口重叠三十到五十字,这样既能控制上下文长度,又能保住段落之间的衔接信息。
第二步是向量化。把文本片段通过Embedding模型转成一个高维向量。这个向量的意义是语义位置:相似的文字会被映射到相近的位置。这样用户问“怎么退款”,即使知识库里写的词是“取消订单并退款”,也能通过向量相似度被检索出来。选Embedding模型时主要看三点:语义效果、向量维度、服务化成本。
第三步是召回。用户提问向量化之后,去向量库做最近邻搜索,取最相近的TopK片段。实际工程里我很少只用纯向量召回,因为向量检索对专有名词、订单号、ID类文本并不友好。我一般会叠加一个关键词检索(BM25或直接数据库LIKE),最后用重排序模型把两路结果合并打分,效果会明显好一截。
3.3 AgentScope Java里如何挂载知识库
AgentScope Java并没有强制你绑定某个向量数据库,它在知识层做的是“可插拔”设计:你负责把文档切分、向量化、存储,框架负责提供统一的检索入口。实际落地时我们会用Milvus、Elasticsearch或者小型项目直接用内存向量库。关键是面向Agent暴露的接口语义要统一:入参是用户问题,出参是命中的文档片段列表。
我建议在项目里单独建一个“知识库服务模块”,把数据源连接、向量化模型调用、检索逻辑都隔离起来。Agent只依赖你提供的检索接口,不关心背后存的是ES还是Milvus。这样做的好处是,未来换向量库或者升级Embedding模型,Agent层完全不用动。
有一个实操细节值得说:入库时一定要保留“来源”和“元数据”。比如每个片段同时存文档名、章节路径、更新时间。这样模型引用知识库内容时,你能在回复里带上出处。我现在做Agent都会在回答底部附上“参考文档X第Y节”,用户看到出处,信任感完全不一样。
3.4 知识质量的把控:召回率、准确率与人工兜底
知识库上线后,最怕的不是模型笨,而是架子上的书放错了位置。如果入库时没做清洗,一段FAQ正文里混着营销文案,检索命中后模型就会输出一堆不相干的话。所以入库前必须做一遍清洗,把页眉页脚、目录、重复段落、表格碎片等噪声去掉。
我会用一套“人工标注+离线评测”的方法控制质量:挑出200个有代表性的用户问题,逐个跑检索,人工看Top3命中是否相关,算出召回率指标。低于一定水平就继续调切分参数或换Embedding模型。上线之后还要加一层兜底:当检索结果相似度低于阈值时,Agent应该老实回答“知识库中没有找到相关内容”,而不是硬找一个最像的片段来编。敢说“不知道”是知识层设计成熟的标志。
4. 手和书架是怎么配合的——一个完整任务走查
4.1 场景设定:售后客服Agent
我们用一个售后客服场景串一下整个知识+工具层。用户发来一句话:“我10086订单都付款三天了还没发货,能不能帮我改个地址?”
模型看到这句话,先需要判断两件事:这个用户是不是在问规则,还是需要操作数据。规则性的内容,比如“大件商品一般付款后48小时内发货”,在知识库里有;但“10086订单当前到哪一步了”,知识库里没有,必须调工具去业务系统查。这就是知识和工具的分工。
Agent会把问题拆成两路:先查知识库回答发货时效的疑问,同时调用订单查询工具拿到真实的订单状态。如果系统显示订单已经出货、物流单号都生成了,那改地址就不是客服规则能解决的了,Agent要直接说明“已经出库,无法修改地址,建议联系物流拦截”。这一个回复里,知识和工具是交织使用的。
4.2 知识库与工具的分工边界
分工说得再清晰一点:静态知识放书架,动态数据必用工具。产品说明书、售后政策、操作手册、QA库,这类内容更新频率低,放知识库没问题。但用户的订单状态、库存余量、账户余额、实时价格,这类数据必须调接口拿。如果你把这些动态数据同步进知识库,那数据永远是迟到的,检索出来的也是过期信息。
还有一种内容是“半静态”的:比如历史工单、历史故障记录。这种数据量很大,不太适合全量同步到知识库。我见过比较合适的做法是,定期把这类记录做向量化存入知识库,作为辅助检索;但最终要以工具查询的结果为准。知识库负责“参考经验”,工具负责“事实数据”。
有时候Agent会陷入“该翻书还是该动手”的犹豫,这时候我建议在设计工具描述时写清楚适用条件。工具描述里明确“此工具仅用于查询订单详情,延迟发货相关规则请参考知识库”,模型看到描述就知道边界在哪里。
4.3 失效场景:知识库没查到、工具调用失败怎么办
这套系统跑久了,你一定会遇到失败场景。知识库没检索到相关内容时,不能硬答。AgentScope可以配置“低置信度兜底话术”,让模型承认不了解,并引导用户转人工或提供客服入口。这比编一个答案好一百倍,因为客服场景最伤信任的就是“一本正经胡说八道”。
工具调用失败则是另一种处理逻辑。超时、返回码异常、参数不合法,这些信息不建议直接抛给用户。框架可以把异常转换成一句“系统查询暂时不可用,请稍后再试”,同时在日志里记录完整调用链。我在工具层会加一层“重试+降级”:查询类工具可以自动重试一次,写操作工具绝对不自动重试,防止重复扣款或重复发货。
还有一个容易被忽略的场景:工具返回结果为空。比如“这个订单根本不存在”。模型拿到空结果,很容易顺着用户的思路编一个解释。所以工具设计时就应该约定:查不到也要返回结构化状态,比如“status: NOT_FOUND, message: 订单不存在”,让模型有据可说,而不是凭空猜测。
5. 实战中的配置与参数选择——这些坑我替你们踩过了
5.1 工具方法的命名与描述怎么写
工具层的描述文本,直接影响模型选工具的准确率。命名规则我总结了三句话:动词开头、业务语义完整、避免抽象缩写。queryOrderById比getOrderInfo好,因为“ById”点明了入参是什么;sendRefundNotification比notice好,因为“Refund”点明了业务动作。
描述里要写两件事:什么情况下该调用,什么情况下不该调用。比如“查询订单详细信息,当用户询问订单状态、物流进度、支付结果时使用;当用户只是询问发货规则时不要使用”。这段描述就是模型的“使用说明书”,写清楚了,模型就不会一碰到相关词汇就胡调。
参数约束也要写细。不求多,但必填参数一定要标明,枚举值要列全。比如状态参数“只有PENDING、SHIPPED、DELIVERED、CANCELED四种取值”,如果不在描述里说明,模型可能传一个“完成”过来。框架层一般有参数解析兜底,但源头约束比事后纠错更省事。
5.2 分块大小、向量维度与TopK怎么定
知识库的几个关键参数,我给一组经验起步值,之后按效果微调。文本分块建议200到500字一段,重叠区间取20%左右;太长检索定位不准,太短会丢失语境。向量维度取决于你选的Embedding模型,常见是768维或1536维,这不是你可以随意改的,选模型时留意存储成本就好。
TopK的选择要结合大模型上下文窗口来定。我在多数场景里用TopK=5,也就是每次检索取5个片段拼进上下文。片段越短TopK可以越大,片段越长对应减少;总的原则是“检索到的内容占模型输入控制在可读范围内”。如果你同时开了关键词检索和向量检索,那么至少需要“多路召回”,最后再一起重排。
调整这个过程,我建议不要靠感觉。先把用户的提问日志导出来,离线跑一遍检索,把“模型最终是否用到了正确的片段”作为评估标准。这比看几个孤立的例子靠谱得多。上线之后还要监控一个指标:每次回答里知识库片段的引用率。如果Agent几乎不引用知识库内容,很可能模型觉得你的知识库没用,或者是检索结果本身太差。
5.3 工具执行的安全边界与权限控制
Agent工具层涉及真实系统操作,安全边界不能靠模型自觉。工具执行前要做“身份校验”,确认当前对话所代表的用户是否有权限操作这个对象。一个普通用户通过Agent查自己的订单没问题,但查别人订单或者发起退款,就必须在工具方法内部再次鉴权,不能只依赖前端或编排层。
写入类工具要加审批流或二次确认。开发时我会把一个“工具”分成“查询类”和“写操作类”,AgentScope在编排时可以给写工具增加“需要用户确认”的前置条件。让Agent先说出“我将为您执行退款操作”,等用户明确同意后再真正调底层方法。这层防护尽可能不做让步。
敏感字段的脱敏也在工具层做。工具返回给模型的文本不能出现身份证号、手机号、支付卡号等明文。我会在工具返回前统一走一个“脱敏器”,手机号只显示前三位和后四位。模型不需要知道完整信息,需要的时候由工具做定向查询。这个原则一定要贯彻到底。
5.4 工具层的可观测性与日志记录
工具层的调试难度比普通接口高,因为你不能确定模型这次为什么会调用某个工具。我的做法是“每次工具调用都记录快照”:原始用户提问、模型生成的调用意图、实际执行参数、方法返回结果、耗时、是否重试。这组日志能帮你还原现场,也能用来做工具效果复盘。
日志里至少要存模型输入输出全文,这很重要。模型没调用工具,可能是因为工具描述不清楚;调用了错误工具,可能是描述有歧义。有了输入输出快照,你才能定位是“模型理解错”还是“工具定义错”。我一般会专门建一个“AgentTrace”表,按会话归档。
线上问题排查时,我习惯先看工具层的调用漏斗:用户问题总数、触发工具的问题占比、工具成功执行占比、工具结果被引用占比。每掉一档,就说明某个环节有流失。这个漏斗比单看接口错误率直观得多。
6. 常见问题与排查技巧实录
6.1 问题速查表
我把在实际落地中见过的高频问题整理成一个速查表,按现象、原因、排查方向排列,方便直接对照。
| 现象 | 常见原因 | 排查方向 |
|---|---|---|
| 模型始终不调用工具 | 工具描述不清晰或没进入工具清单 | 检查注册是否成功、描述是否写清适用场景 |
| 模型调用了错误的工具 | 多个工具描述语义重叠 | 给工具差异化描述,必要时增加否定条件 |
| 工具参数解析失败 | 描述里参数约束不完整 | 补齐必填项、枚举值、类型说明 |
| 知识库检索结果不相关 | 切分不合理或Embedding模型不合适 | 调整分块策略,离线评测召回效果 |
| 知识库命中但回答还是错 | 上下文里知识片段被淹没或冲突 | 控制片段数量,限定模型只能参考片段回答 |
| 工具执行超时拖垮整个会话 | 下游接口无超时保护 | 给工具调用加超时、重试、降级策略 |
| 写操作被重复执行 | 模型重试或用户重复提交 | 写工具增加幂等键和状态校验 |
| 回答显示过期的知识 | 知识库更新不及时 | 建立文档更新机制,按版本维护知识片 |
6.2 怎么调试工具层和知识层
调试工具层,我推荐“单测优先”。每个工具方法先独立跑一遍,确认入参出参没毛病,再接入Agent链路。如果链路有问题,至少能排除工具本身的Bug。对于模型侧,我习惯用最小复现法:把某一轮的用户问题、工具清单、知识片段固定住,多次执行看模型输出是否稳定,不稳定说明描述或知识片段有问题。
知识层的调试更像离线实验。我会写一个评测脚本,输入一批用户问题,跑一遍“向量检索+重排”,把Top3结果打印出来人工看。只看命中率不够,还要看“命中的片段是否真的包含答案”。有时候检索命中了,但命中的是FAQ的问题部分而不是答案部分,这就要调整入库策略,把问题和答案作为一个整体切块。
6.3 实测下来最值得做的三个优化
最后分享三个实测下来收益最高的优化。
第一个是“多路召回+重排”,纯向量检索在业务Query里很容易偏,但它有上限。第二个是“工具结果格式化”,把所有工具返回值统一成一层“status+message+data结构”,模型拿到之后总结答案省力很多。第三个是“动态更新知识库”,别让知识片库吃老本,每日定时扫描源文档,有变更就重新切分入库并标记旧版本下线。
这三个优化投入都不大,但对Agent的可用性提升很明显。尤其是第2个建议,我刚开始做工具层时,工具返回的很随意,导致模型经常自己“脑补”遗漏字段;改成统一结构后,回复质量一下子稳定了。你如果现在刚搭完手和书架,可以先从这三点入手继续打磨。
我个人在实际操作中的体会是:知识与工具层是最能“立竿见影”的一层。前面调提示词、调模型参数,效果提升都是渐进的;但一旦接上几个好用的工具和一套可靠的知识库,Agent的能力是肉眼可见地变实。后面的系列里,我会继续沿着编排层往下聊,看看多Agent之间怎么通过这一层协作。如果你也正在做AgentScope Java项目,欢迎按这篇的路线先把“手”和“书架”搭起来,再回头优化细节。