ARTICLE DETAIL

资讯详情

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

AgentScope Java实战:知识层与工具层的架构设计与落地

AgentScope Java实战:知识层与工具层的架构设计与落地 AgentScope Java 系列写到第三篇前面我们已经把 Agent 本身聊通了能说话、能按流程走、能被编排。但真到了生产环境你会发现一个特别尴尬的问题——如果只给 Agent 一张嘴它什么都敢说尤其敢编答案。你问它内部系统里的订单状态它一本正经回你一句“根据系统记录订单已发货”其实它根本没见过系统。你让它查产品文档里某个参数的含义它可能凭训练数据中的记忆胡诌一段。想让 Agent 真正变成生产力光有大脑还不够得给它配上“书架”知识层和“手”工具层。这篇“AgentScope Java 实战 03”要解决的就是这件事知识与工具层怎么设计、怎么落地以及我在实际项目中踩过的那些坑。这篇适合已经了解 AgentScope Java 基础概念的读者尤其是正在用 Java 做 RAG 问答、Function Calling、企业内部助手这类场景的后端开发者。如果你还用过 LangChain 或者自己写过裸的 function calling那看看 AgentScope 的做法也会有收获。1. 先理清楚知识层和工具层在 AgentScope 里到底管什么1.1 一句话说清知识层与工具层的差异很多人把知识层和工具层混在一起觉得都是“给模型喂额外信息”。其实这两者的机制完全不同可以打个比方知识层是书架。它存放的是只读的、相对稳定的信息——产品文档、FAQ、历史工单、专家经验。模型需要某个事实时通过检索把相关片段临时取出来放到上下文里。工具层是手。它做的是可执行、有副作用的动作——查数据库、调用下游系统、发邮件、改配置。模型自己不会执行这些操作只能发出“我想调用这个工具参数是这些”的请求由框架帮你真正执行。如果只装书架Agent 能“知道”更多但依然什么也做不了如果只装手Agent 能做事但缺少背景知识容易乱动。两者缺一不可。1.2 知识与工具层挂在编排流程的哪个位置要理解这两个层得看 AgentScope Java 运行时的消息循环大致是怎么走的模型层大脑负责推理和生成文本知识层在模型生成前介入把检索结果作为额外上下文塞进 prompt工具层在模型生成后介入解析模型输出的 tool_calls执行对应方法再把执行结果作为一条工具消息喂回给模型。也就是说知识层不是直接压到模型输入里的工具层也不是直接跟业务代码写在一起的。它们都是围绕“模型输入/输出”这个核心循环做扩展。在 AgentScope Java 里这套链路通常长这样用户输入 → Agent 组装消息 → 查询知识库注入相关知识 → 调用大模型 → 模型返回文本或工具调用请求 → 工具管理器执行 → 结果回填 → 再次调用模型 → 直到模型给出最终答复。我见过很多刚开始做 Agent 的代码把知识检索塞到一个巨大的 service 里把工具调用写在 if-else 里最后模型一问就乱。之所以先画这个位置图就是想说知识层和工具层在架构上是正交的千万别揉成一把梭。1.3 判断边界什么该进书架什么该做成手这块是我在实际拆需求时最常被问到的“这个数据放知识库还是做成工具”我一般用三个问题来判断第一这个信息是静态的还是动态的静态的、低频变化的如产品规格、操作手册、企业制度优先进知识库。动态的、实时查询的如库存、订单状态、账户余额必须做成工具。第二这个动作是“查”还是“改”查且结果可以被文档化描述进知识库改或产生外部副作用必须做成工具并且要加权限和审计。第三这个信息是一次性被完整消费还是需要按相关性筛选一个几百页的手册不可能全塞给模型就需要切分、索引、检索这就是知识库该干的事。一个接口一次能返回完整结果做成工具更直接。拿我最常做的电商售后场景举例退货政策、保修条款 → 书架查订单物流、修改退款金额、创建售后单 → 手客服话术模板 → 书架查询用户是否在黑名单 → 手边界清楚了后面设计就不会颠三倒四。2. 书架侧知识接入、召回与记忆落盘的完整链路2.1 书架的第一层考验知识从什么格式进入知识层不是简单地把一堆文件丢给 Agent。Agent 的上下文窗口有限就算支持 200k 上下文你也不可能每次调用都把全部公司文档塞进去。书架必须先解决“怎么进”的问题。我处理过的知识源主要有三类。第一类是结构化数据比如数据库里的商品信息、问答对。这类最省事可以直接按行转成文档片段注意保留主键和来源字段方便溯源。第二类是半结构化文档比如 Markdown、Wiki 页面。这类需要按标题层级和段落结构做切分而不是按固定字符数硬切否则语义会被拦腰斩断。第三类是纯文本比如 PDF、邮件、聊天记录。这类需要先做格式清洗去掉页眉页脚、换行符、乱码字符再切分。实操中很多团队把功夫全花在向量检索上结果源文档一团糟召回自然一塌糊涂。我习惯在接入前定义一个统一的知识文档模型至少包含文档ID、标题、正文切片、元数据作者、更新时间、来源链接、权限标签。后续检索、引用、权限过滤全靠这套模型。2.2 切分与向量化召回质量的起点知识切分没有标准答案但有经验值。我踩过的情况是切得太碎比如每 100 个字一刀检索出来的片段往往只有孤立的几句话上下文不完整模型回答时只能靠猜切得太长比如一整章一个向量召回时语义不够聚焦还可能因为太长把模型上下文撑爆。比较稳妥的做法是语义边界优先、长度兜底。先把文档按 Markdown 标题、自然段等边界切块再把单块大小控制在 300800 token 左右如果某段明显过长再按句子继续拆分并允许相邻块之间保留少量重叠避免关键信息正好落在切缝处丢掉。切完之后就是向量化。这里的核心是 Embedding 模型的选择。中文场景和英文场景要分开考虑不能用通用向量模型硬扛中文业务词汇选模型时先在你自己的数据集上抽样测一下相似度排序不要只看榜单分数。同时embedding 模型最好和 Agent 使用的 LLM 在语义风格上匹配不然检索出来的“相关”结果模型看起来并不相关。这里还需要注意向量化服务的部署位置。Java 服务端调用 Python 侧 embedding 服务是常态中间走 HTTP务必做好超时和降级。如果 embedding 接口挂了整个知识问答就直接不可用这个风险在设计时要提前暴露出来。2.3 召回策略不是 TopK 越大越好向量检索只是召回的手段不是终点。我在项目里吃过 TopK 的亏最初为了“不遗漏”TopK 拉到 10甚至 20结果模型收到的上下文里混进大量弱相关片段回答起来反而瞻前顾后还疯狂消耗 token。后来我固定了一套组合策略先做向量召回取候选结果 TopK 30 左右再做重排或过滤用基于规则的打分或 rerank 模型把真正的 TopN 压到 35 个设定相似度阈值低于阈值的直接丢弃让模型坦率说“不知道”而不是拿不相关内容硬编。更关键的是检索回来不要直接把原始切片拼进 prompt。每个切片要带上标题、章节路径、来源页码这些元信息让模型知道这段内容的“出身”。我在实践里看到带来源元信息的检索片段回答的上下文连贯性和可信度明显更高还方便最终回答中给用户展示引用来源。至于重排怎么做小项目可以用 BM25 和向量得分做加权融合大项目再上 rerank 模型。先用简单的别一上来就造重排服务。2.4 记忆是书架的另一种藏书会话记忆怎么落盘知识库解决的是“不知道”的问题记忆解决的是“忘了”的问题。同一个用户多轮对话里Agent 必须能记住前面说过的话但不能什么都长期留着。我会把记忆分成两层短期会话记忆通常存在进程内或 Redis 里过期时间就设在会话生命周期内保存的是最近几轮消息压缩后的摘要长期个性化记忆存用户偏好、历史事实类信息落库或落向量库跨会话复用。AgentScope Java 为这种分层记忆提供了接入点但你要想清楚每层放什么。短期记忆别搞复杂滚动窗口加摘要就行长期记忆一定要带用户维度的隔离否则 A 用户的偏好会污染 B 用户的回答这在多租户系统里是严重事故。另外记忆和知识库在检索时最好不要混成一个结果集。知识库检索结果代表“事实”记忆检索结果代表“个性化偏好”在 prompt 里要分成不同的 section 提供给模型模型才知道哪些是通用规章、哪些是针对这个用户的专属信息。3. 手侧工具注册、函数声明到调用闭环的实现细节3.1 工具的本质给模型一个可控的“副作用入口”大模型本质是文本生成模型它不具备执行能力。所谓 Function Calling其实是模型在生成文本时额外输出一段结构化的“函数调用意图”。AgentScope Java 要做的事情是把这个意图和 Java 世界里真实的方法调用对应起来。工具不是越强越好而是越可控越好。给模型一把刀可以用来切菜但也可能伤人。所以工具注册时就要考虑哪些工具对哪些用户可见、哪些操作需要二次确认、哪些工具根本不该暴露给自主调用链只能由人工触发。这个边界注册阶段就要定死不能等运行时再拦。3.2 Tool 声明怎么写模型才看得懂模型不像人它看不到你的 Java 方法体只能看你提供的工具描述和 JSON Schema。所以工具声明有几个怨种点写不好会直接导致模型瞎调或者不调。name 要短且语义唯一比如 query_stock而不是 StockQueryServiceImpl。description 要写清楚这个工具“什么时候用、什么时候不用”。我见过只写“查询库存”的模型连用户问物流也去调库存就是因为没写“仅当用户询问商品实时库存或数量时使用不适用于物流信息”。参数 schema 要严格。参数名、类型、是否必填、取值范围都要写死。枚举值如果不写全模型就会自由发挥传一个你根本没处理过的值进来。返回值结构也要描述最好说明返回的是 JSON 字符串还是纯文本模型好判断怎么解析。如果你用 AgentScope Java 的注解方式注册工具核心工作其实是写好那个描述字符串。我经常把工具描述当成接口文档来评审而不是随便填写因为工具描述就是模型的“说明书”。下面是我习惯的声明方式示意具体注解名会随版本略有出入机制基本一致AgentTool( name query_stock, description 按商品SKU查询实时库存数量。仅当用户询问某个商品是否有货、剩余多少件时使用不适用于查询订单物流、价格和促销信息。, params { ToolParam(name skuId, type string, description 商品SKU编码形如SKU2024-011, required true) } ) public String queryStock(String skuId) { StockInfo stock stockService.getBySku(skuId); if (stock null) { return {\found\: false}; } return {\found\: true, \stock\: stock.getAvailable() }; }3.3 工具调用的完整闭环模型发话、框架跑腿、结果回填工具调用不是一步到位的。完整闭环是用户提问模型根据工具描述判断需要调用某工具输出 tool_callsAgentScope Java 捕获到 tool_calls解析参数框架按注册表找到对应方法并执行执行结果包装成 tool role 消息重新拼进对话上下文模型拿到工具结果整理成最终回复。这里有个细节第 4 步执行完不能直接把原始对象返回给模型。最好统一序列化成字符串或 JSON同时包含成功/失败状态、错误信息。如果工具执行抛异常了别让异常把整个 Agent 循环搞崩把异常信息折叠成一句“工具调用失败原因xxx”交给模型决定是重试、换个工具、还是如实告诉用户失败。如果一次模型返回里包含多个工具调用比如同时查库存和查价格框架要考虑是串行还是并行执行。串行简单可控并行快但要小心共享状态。我一般默认串行只有确认工具间完全无依赖时才允许并行。3.4 工具执行的治理超时、幂等、权限、审计工具层的风控比功能本身更重要。我把这一块称为“工具治理”这几条是我做任何 Agent 工具层都强制要有的超时。每个工具执行必须设置超时时间建议默认 35 秒。Agent 是面向用户的实时交互一个下游接口慢 30 秒体验直接毁了。超时后要能返回可理解的错误而不是空等。幂等。工具若会产生副作用改库、发单、转账必须让下游支持幂等键。模型可能会因为上游重试而重复调用同一个工具如果没有幂等用户就会被重复扣款、重复下单。这种事故我见过不止一次。权限。工具注册表里每个工具标注“用户级”“管理员级”“系统级”Agent 在选择工具时要根据当前会话主体做过滤不能让普通用户通过 Agent 调用管理员工具。审计。所有工具调用都记录入参、出参、耗时、由哪个用户触发、由模型哪一次决策触发。出了事可以回溯不然连锅都找不到。我用一个简单的 HTTP 风格表格总结治理维度方便你对照检查自己的工具层维度要求常见事故超时3~5秒强制中断下游慢拖垮整个Agent幂等带幂等键重试重复下单、重复扣款权限按会话主体过滤越权调用管理工具审计全量留痕可追溯事故无法定位限流按用户/工具限速恶意刷调用耗尽资源4. Java 工程落地用 Spring 管理工具用本地检索库搭知识层4.1 知识层选型从 Lucene 到专门向量库各取所需Java 后端做知识库选型比 Python 生态更“重”但可选路径也很明确。小规模、单机、想快速跑通用 Lucene 或者带向量检索能力的本地索引最合适。Lucene 的 BM25 做关键词召回很成熟近几个版本也支持向量字段对几十万级别的文档量完全够用部署还省心不用额外引一个服务。中等规模、已有 ES 集群直接把文档索引和向量索引都放在 Elasticsearch 里。ES 的向量检索能力虽然 HNSW 性能一般但胜在复用基础设施、查询语法统一。大规模、高 QPS、向量检索是核心路径上 Milvus、Qdrant 这类专用向量库或者云厂商的向量检索服务。此时工程复杂度会明显上升集合管理、标量过滤、索引构建、多副本每一项都要有人专门盯。我给一个保守的选择依据场景推荐方案理由Demo、个人项目Lucene / 本地向量文件零运维、直接内嵌中小团队内网知识库Elasticsearch复用已有基础设施高并发对外智能助手专用向量库性能与扩展性更稳另外如果文档量很小几千条 FAQ 以下别硬上向量库。直接一个 HashMap 加载到内存里、用关键词匹配和简单的 TF-IDF效果甚至更好因为你可以在召回后直接用规则做精确匹配。玩具场景不要用战术的勤奋掩盖战略的懒惰。4.2 工具侧工程化把 Agent 工具注册到 Spring 容器在 Java 工程里工具不应该散落在乱七八糟的静态方法里。我推荐的做法是把工具按业务域拆成不同的 ToolProvider Bean统一交给 AgentScope 的工具管理器扫描注册。Component public class OrderToolProvider { private final OrderService orderService; public OrderToolProvider(OrderService orderService) { this.orderService orderService; } AgentTool(name query_order_status, description ...) public String queryOrderStatus(ToolParam(name orderId, ...) String orderId) { Order order orderService.getOrder(orderId); // 返回统一JSON } }这样做的好处很明显工具能像普通 Spring Bean 一样依赖注入数据库连接、配置中心、Redis 都直接可用工具生命周期由容器管理方便加 AOP 做审计和限流测试时可以注入 mock不真的调下游。工具注册表的设计我建议至少包含工具名、方法句柄、参数 Schema、工具类型只读/写操作、需要的权限级别、超时时间。运行时按会话主体的权限做过滤而不是把所有工具一股脑都暴露给模型。4.3 一个最小可跑的骨架Agent 知识检索工具 业务工具把知识层和工具层拼起来最小的骨架大概是这样public class AssistantDemo { public static void main(String[] args) { LLM model LLMs.getModel(your-model-config); // 1. 知识检索作为一个普通工具暴露给模型 ToolManager toolManager new ToolManager(); toolManager.register(new KnowledgeSearchTool(ragService, 3)); // 2. 业务工具 toolManager.register(new OrderToolProvider(orderService)); // 3. 组装Agent这里以ReActAgent示例 ReActAgent agent ReActAgent.builder() .model(model) .tools(toolManager) .maxIterations(5) .build(); // 4. 提问 AgentResponse response agent.run(我们公司投影仪的保修期是多久另外 SKU2024-011 现在有货吗); System.out.println(response.getContent()); } }这个骨架里知识检索被包装成工具是因为模型可能需要在回答过程中按需检索。有的场景你也可以把知识检索放在 agent 消息组装阶段主动注入区别在于主动注入适合“每轮都必须先查”按需检索适合“模型自己判断要不要查”。我见过很多初版代码把知识检索写死在下游服务里模型只能被动接受。真正留出“模型自主决定检索时机”之后逻辑反而更贴近真实使用方式——用户没说之前模型不一定需要先翻书。5. 一个跑通的生产级例子文档问答加实时库存查询5.1 需求拆解一次对话里同时用到书架和手我拿一个相对完整的需求演示用户问“我们公司新项目的投影仪保修期多久这台投影仪有货吗能给我报个价吗”这个场景里保修期属于知识库库存和价格属于实时业务工具。如果只装书架第三个问题答不了如果只装手第一个问题没有工具能答模型只能凭训练记忆瞎编。这种“知识 工具混合提问”在真实业务里非常常见。Agent 需要先检索内部文档定位保修规则再调用库存接口查实时数量最后调用报价工具计算价格。一个正确的实现应该对这三种请求分别处理。5.2 知识检索工具的返回值设计知识检索工具我通常会返回包含多个候选片段的结构化文本而不是只返回一个最佳匹配{ query: 投影仪保修期, hits: [ { score: 0.92, title: 商用投影仪售后政策 2024版, content: 所有商用投影仪整机保修三年核心部件保修五年..., source: https://wiki.internal/projector/warranty } ] }返回给模型时要强调如果检索结果不能直接回答问题必须明确告知用户“未在资料中找到相关信息”不允许编造。这个约束写在系统提示词里还不够最好在知识检索工具的 description 里也写一遍双重约束才有效果。我踩过的一个教训是知识检索结果里只有 content 没有 title 和 source模型就算答对了也不知道出处引用来源全靠瞎编。加上了明确的 title 和 source 字段之后模型回答时会把出处说清楚。5.3 结合工具的完整对话流实际跑出来的一次对话流大致如下用户提问模型判断需要知识检索发起工具调用 search_knowledge_base(query: 投影仪保修期)框架执行返回保修政策片段模型继续判断需要查询库存发起 query_stock(skuId: SKU2024-011)框架执行返回 found: true, stock: 36模型继续调用 quote_price(skuId: SKU2024-011)框架执行返回价格模型综合所有信息生成最终答复。如果你用 AgentScope Java 的调试工具或自行打印消息序列会看到这一串 msg 交替user → assistant(tool_call) → tool → assistant(tool_call) → tool → assistant(final)。只要能看到这种模式说明工具闭环是通的。5.4 这类场景的收益和注意点内容安全再强调一下企业内部知识往往有保密等级知识检索工具在执行时必须过滤当前用户无权限的文档。我经历过一次事故低权限用户通过 Agent 检索到了内部高权限政策文档内容直接查了聊天记录才发现忘记做权限过滤。技术上的收益很直接不再需要为每个问答场景单独维护庞大的提示词模板知识更新时只需更新索引不用改代码新增业务操作只需新增一个工具方法不改 Agent 主流程。这个收益需要跑过一个完整迭代周期才会真正体会到。6. 实测清单知识层和工具层最容易翻车的六个环节6.1 工具描述写得太“文科”模型就会乱摸手最初我把工具描述写得很像人话比如“这个工具可以查询订单如果你想知道用户买了什么的话”。模型根本抓不住边界用户问一句“我什么时候到货”它也会去调订单查询工具。后来改成“仅当用户询问订单配送状态时调用不得用于售后投诉”误调率立刻降下来了。工具描述的正确格式应该是这个工具做什么 什么时候调用 什么时候不调用 关键参数解释。行业里很多团队已经开始用“触发条件 非触发条件”的方式写工具描述这对模型消歧义是有效的值得借鉴。6.2 切分不合理向量检索召回落入“近而不准”知识切分直接决定召回质量。我调试过一个客户案例他们把产品文档按每 200 个字符固定切分结果很多关键知识点被截成两半检索时还总能命中但下半句是上一段的尾巴模型拿到的片段不完整回答自然差。后来改为按章节段落切分并把切分单元设为 500 token、重叠 50 token召回质量立刻上了两个台阶。我的建议是任何一次“检索质量差”的抱怨都先去看源文档的切分和清洗不要一上来就换向量模型。向量模型再强也救不了烂掉的切分结果。6.3 上下文被知识堆满成本和效果双输TopK 太大、召回片段太长都会把上下文字数顶得非常高。我见到不少人被账单吓到过明明只是简单问答一次请求却输入了好几万 token。解决办法知识检索结果按相关性排序后只取前三每个片段在拼入 prompt 前做压缩只保留命中句和前后少量上下文重排阶段如果关键句重复出现只保留信息量最高的那一个。分层记忆也只放必要信息长期记忆经过摘要之后再入库而不是把原始会话一字不差存进去。下文提示词里的知识 section 也不该和普通历史消息混在一起我会专门用明显的标记区分“以下是检索到的资料片段”让模型知道这些内容属于资料而不是用户原话。6.4 并发与重复执行对模型的不确定性要有预期模型输出并不稳定同样的 prompt 可能这次输出 tool_call下次直接输出一段“我好帮你查到”的假话。我们在生产里遇到的另一个问题是模型把同一次提问拆出多个工具调用但参数完全一样或者因为框架层面超时重试把同一个写操作执行了两次。应对办法工具注册时预设 idempotencyKey每次工具调用的会话生成唯一键下游接口支持基于该键的幂等重复参数的工具调用在框架层做去重相同工具相同参数的并发调用直接合并对写操作工具强制要求人工确认不允许模型自主二次尝试。6.5 日志链路怎么搭观测模型到底要做什么工具层一旦复杂定位问题就成了一场灾难。我强烈建议从上线第一天就把链路日志打全每次模型调用记录 tool_calls 原始输出每次工具执行记录入参出参、耗时、是否幂等命中每次知识检索记录查询词、召回列表、最终选用的片段。我把这些日志统一打到独立的索引里用 traceId 贯穿一次对话。出了问题先看模型想调什么再看框架实际执行了什么最后看工具返回了什么。三步一对80% 的问题都能定位。6.6 版本升级与工具契约别让新模型毁掉旧方法框架和模型都会升级。模型切换后工具描述不一定还适用于新模型的 function calling 格式。我在一次模型升级后发现新模型对 description 里“绝不”这类否定词的理解偏弱导致不该调的工具频繁被调。说明工具描述也需要针对模型做回归测试。每次升级模型或 AgentScope 版本都要跑一遍工具调用回归用例固定 50 条问题人工判定每条是否应调用工具、参数是否正确、最终回答是否合理。这个是苦功夫但能避免线上翻车。我对知识与工具层最重要的一个体会是Agent 的能力上限其实不是你模型选得多强而是你的“书架”整理得有多清晰、你的“手”管得有多稳。工具乱、知识脏再好的模型也只是个反应快但缺乏常识的机器人。下一篇我可能会写编排层讲讲多个带手带书架的 Agent 怎么合作——那又是另一个值得掰开揉碎的话题。
返回列表