新闻详情

新闻详情

首页 / 资讯中心 / 详情

用Java构建Agent智能体:从ReAct循环到工具调用的工程实践

发布时间:2026/9/28 9:41:31来源:尧图网络
用Java构建Agent智能体:从ReAct循环到工具调用的工程实践
如果现在做一个“用什么语言写Agent智能体”的投票Java大概率排不进前三。过去这一年我偏偏反着来用Java把一个名叫lucky_agent的Agent智能体完整打样出来能接大模型、能自己调工具、能多轮对话还塞进了既有的Java微服务链路里。这篇文章把从选型、架构、核心实现到踩坑的记录全摊开适合两类人看一类是Java后端想搞清楚Agent到底怎么回事另一类是自己已经写过一些Agent但想要更体系化经验的人。顺便说一句最近半年Java面试题里Agent相关的内容多起来了这篇也能当个速查底稿。lucky_agent这个名字起得比较随意当时只希望项目的运气能好一点迭代别太频繁。但做着做着它从一个Demo变得越来越像样最后我在真实业务里跑了一轮拿到了不少一手数据。下面这些内容全部来自实操不是那种“Hello Agent”的泛泛教程。1. 为什么偏偏是Java选型背后的逻辑1.1 团队技术栈与“省事”原则很多人一想到Agent默认就是Python加LangChain。这没错Python在AI生态里确实无敌但落到真实团队里问题就变成你的服务跑在什么技术上代码要交给谁维护监控告警是不是已经有了一套现成体系。我当时所在团队的核心服务全部是JavaSpring Boot 3.x注册中心、配置中心、链路追踪、日志平台全都是围绕Java生态搭好的。如果为了Agent单独弄一套Python服务意味着要额外维护一套部署流水线、一套依赖体系还要培养团队里所有人从Java切到Python的习惯。这个隐性成本比写代码本身高得多。而lucky_agent的选择是把Agent能力直接做成Java体系里一个普通模块。它和大模型通信走HTTP和记忆存储走Redis和业务系统走内部API。对运维来说这就是一个再正常不过的Java服务日志、监控、发版流程全部复用。现实一点讲选型很多时候不是选“最好的语言”而是选“最省事的路径”。1.2 Agent不是只有一条技术路线我见过不少人把LangChain之类框架当成了Agent本身不引入一个大框架就觉得心里没底。但Agent的本质其实非常朴素模型推理决定下一步行动执行行动把结果拿回来再推理再看下一步直到能给出最终答案。这套循环逻辑跟语言没有强绑定关系。真正分水岭在于你的模型能力和工具能力是怎么提供的。lucky_agent核心的大模型调用是通过HTTP API完成的不管背后是商用接口还是开源模型部署的服务对Java来说都是一个带鉴权的REST接口。API返回的JSON里既可能有自然语言回复也可能有工具调用指令剩下的就是Java侧怎么解释和执行这段指令。所以Agent开发的难点从来不是“用什么语言”而是“这套循环在你的工程环境里怎么转得稳、控制得住”。Java在类型约束、编译期检查、线程模型、监控体系上的积累反而在稳定性和工程化上很有优势。1.3 lucky_agent的能力边界与组件选型动手写之前我给lucky_agent画了一条能力边界清单不贪多先把最核心的几件事做扎实支持OpenAI兼容协议的多家模型接口切换模型只需要改配置支持Tool Calling和Function CallingAgent能调用外部工具支持多轮对话带短期会话记忆和长期用户记忆支持简单的任务编排比如把“查订单”和“算到货时间”串联起来工具调用支持并发执行、超时控制、权限白名单组件选型方面我尽量不引入重型依赖。应用框架用Spring Boot 3.2JDK直接上Java 21HTTP客户端用OkHttpJSON处理用Jackson会话记忆存Redis向量检索没有上Milvus那种重型库而是用远程Embedding接口加本地余弦相似度计算模型调用支持流式和非流式两种模式。这套组合跑下来依赖非常清爽。多人协作时新同学看代码的认知负担很小因为他不需要先学会一个几百页文档的Agent框架才能改代码。2. 先看主循环Agent的骨架就是一段while循环2.1 ReAct范式如何在Java里落地ReAct是Reason加Act的简称翻译成大白话就是模型先想一步然后做一步看看结果再想下一步。lucky_agent的主循环用伪代码写出来就这么点东西。while (!agentLoop.isFinished()) { Message response llm.chat(history); if (response.isToolCall()) { ToolResult result toolExecutor.execute(response.getToolCall()); history.addToolResult(result); } else { return response.getContent(); } }这段代码看着简单但把它写稳的过程并不简单。首先要防止无限循环所以我在AgentLoop里加了最大轮数限制默认12轮超过就直接终止并告知用户“任务未完成步骤过多”。其次每一次工具调用的结果都要写回历史消息因为大模型是无状态的它必须看到上一步的工具结果才能继续推理。Java很适合做这种显式状态管理。比起Python里靠字典和魔法参数自由传参Java的强类型帮我躲过了很多运行时字段拼错的低级问题。每个Message有明确的role、content、toolCallId编译器就能拦掉一批低级错误。2.2 一次完整对话到底流转了几次我用一个实际例子说明。用户说“帮我查一下订单OD10086的物流信息然后推算大概哪天能到。”第一轮lucky_agent把这句话拼进系统提示词发给大模型。模型判断需要调用查物流工具于是返回一个Tool Calling指令参数是订单号OD10086但注意模型可能不会立刻返回最终答案。第二轮Agent执行物流查询工具拿到真实的运输轨迹。比如快件已经离开中转站、当前在某个城市、距离收件地址还有400公里。第三轮Agent把工具结果拼回历史再次调用大模型。模型这次不需要再调工具它基于轨迹数据推算给出“预计3天内送达最大可能是后天下午”的答复并以普通消息形式返回。这只是一次简单任务实际业务里反复调用五六个工具的情况很常见比如查询库存、算运费、查优惠券、生成订单。每一步之间都可能有数据依赖所以主循环必须等上一步的结果不能一股脑并行。如何判断哪些工具能并行、哪些必须串行我放在工具调度那一章详细说。2.3 主循环留在业务侧框架只做半成品这是lucky_agent架构里我最坚持的一个决定。很多Agent框架喜欢把主循环完全封装掉业务方只能通过回调接口塞逻辑。但实测下来真实业务里的Agent循环总是要穿插权限校验、审计日志、用户取消操作、灰度开关检查。如果这些逻辑都要通过框架的扩展点来实现很快就会被框架限制住。所以lucky_agent把主循环拆成一个AgentLoop接口默认实现是标准的ReAct循环但业务侧可以完全替换掉默认实现。比如有的场景要求“必须先查权限再调工具”那就可以直接在自定义循环的executeTool之前插入权限判断。框架层面只保证消息协议、工具注册、历史管理这些基础设施决策流程交给业务。这一点对于把Agent接入老旧系统特别重要。你永远不知道业务方在哪个环节有自己的合规要求把主循环开放出去留给扩展的余地比什么都替用户决定要好。3. 让Agent学会“用工具”Tool Calling的设计与实现3.1 注解加反射五秒钟增加一个新工具lucky_agent里定义一个新工具只需要在一个Spring Bean的方法上打个注解。Component public class OrderTools { AgentTool(name query_order_logistics, desc 根据订单号查询物流轨迹) public String queryLogistics( ToolParam(value orderId, desc 订单号) String orderId, ToolParam(value timeoutMs, desc 超时时间默认8000, required false) Integer timeoutMs) { // 调内部物流系统返回JSON字符串 return logisticsClient.query(orderId); } }启动时lucky_agent通过Spring的ApplicationContext拿到所有Bean扫描方法上的AgentTool注解把方法名、描述、参数Schema组合成工具描述列表注册到ToolRegistry里并最终在请求大模型时以function列表的形式发给模型。模型就知道系统有哪些工具可以用、每个工具长什么样。这个方案的好处是极度低侵入。新增能力完全不用动Agent核心代码团队里任何人想给Agent加一个查询能力只需要写一个普通方法加上注解完事。我甚至把工具描述写在了注解的desc里要求团队写成“在什么场景下用、输入是什么、输出是什么”的格式因为大模型对描述越清晰选错工具的概率越低。3.2 从JSON Schema到参数绑定和校验大模型返回的Tool Calling本质上是一个JSON结构工具名加一个参数字符串。Java这边要做的事情是把参数字符串绑定成方法入参。{ name: query_order_logistics, arguments: {\orderId\:\OD10086\} }我用Jackson处理这块但很快遇到一个经典坑Java的泛型擦除让TypeReference在反射场景下非常容易写错。后来我直接在参数绑定这一步统一用JsonNode接收再按ToolParam里声明的参数名和类型逐个取值。这样绕开了复杂泛型嵌套每个参数单独转换出错信息也更精准。参数校验必须自己来。大模型不是严谨的编译器它可能在arguments里漏字段也可能给一个语法不完整但能部分解析的JSON。我在绑定后做三个检查必填参数是否存在、类型是否正确、字符串格式是否符合预定义规则。失败的参数会形成一条错误信息返回给大模型比如“orderId格式应为字母加数字”让模型有机会修正参数重新调用。实测下来这种“让模型自己纠错”的机制非常管用能把工具调用成功率提升不少。3.3 多工具并发执行与超时兜底复杂任务里模型可能会一次要求并行调用多个工具比如同时查天气、查航班、查酒店。lucky_agent用一个批量执行器接收工具调用列表按配置决定是否并行。并行实现用的是CompletableFuture加自定义线程池在Java 21上也可以换成虚拟线程IO密集型的工具调用能获得很高的吞吐。每个工具都有独立的超时时间默认5秒长任务可以写到ToolParam里覆盖。超时之后工具执行器不会干等而是立刻把一个错误结果写回给大模型“tool query_order_logistics timed out after 5000ms”。模型看到这个错误通常会换个思路比如简化查询条件或者通报用户稍后再试。这个兜底动作非常关键否则整个Agent循环会卡在某个工具上连带拖垮整个用户会话。并发场景还要注意一个细节多个工具结果要区分对应哪一次调用。lucky_agent用toolCallId做关联每个Tool Calling带上唯一ID工具结果也带上这个ID再写回历史。否则模型同时调两个工具结果回来串了推理就会彻底错乱。4. 让Agent“有记性”短期记忆与长期记忆的两层设计4.1 短期会话记忆怎么存、怎么剪没有记忆的Agent每轮对话都是重新开始产品上根本没法用。lucky_agent的短期记忆用Redis实现每个会话以sessionId为key存一个消息列表。消息结构是一个简化的JSONrole、content、timestamp、toolCallId。默认保留最近20轮超过之后按照FIFO策略挤出窗口。单靠轮数剪枝还不够因为20轮可能包含大量长文本工具结果。我在写入前会估算每条消息的Token占用整体超过上限时优先丢弃最早的、超过一定长度阈值的工具结果。比如查订单详情返回了2000字我会先截断到600字再存防止一条大结果把整个上下文挤爆。反正模型真正需要的只是关键状态字段完整数据已经落库需要时再查一次就行。短期记忆的实际效果很明显用户在上一轮说了“我是黑钻会员”这一轮再问“我订的货什么时候到”模型会知道自动加上黑钻会员的优先发货备注而不需要用户重复说明。4.2 长期记忆与向量检索的轻量实现长期记忆解决的是跨会话的用户偏好问题。lucky_agent的做法是在每次对话结束时把对话中提取出的关键用户信息做Embedding存入一个长期记忆库下一次会话开始时在用户消息之外向量召回topK条相关记忆拼进系统提示词。向量库没有引入重型组件而是用一个远程Embedding服务把文本转成float数组然后存在本地服务里做余弦相似度计算。3000条记忆规模的检索耗时在几十毫秒量级对这个项目完全够用。如果将来量上来再平滑替换成专业向量库。这里有个经验召回的记忆不是越多越好。topK我默认设3因为这些记忆会占用输入Token还会干扰模型对当前问题的注意力。召回结果还会做一个时间衰减加权半年前的偏好权重低最近三天的偏好权重高这样模型不会被过时的习惯带偏。4.3 上下文窗口不够时先摘要在前多轮长对话最容易遇到的问题就是上下文窗口被塞满。lucky_agent在写入每条新消息前都会检查剩余Token预算如果不足触发记忆压缩。压缩逻辑是把窗口里最早的一部分历史消息发给大模型让它生成一段摘要然后用这段摘要替换掉那些原始消息。比如20轮对话被摘成3句话“用户咨询过电脑型号对比过三款倾向ThinkPad X1预算在9000左右”。原始消息释放出大量上下文空间但关键信息没有丢。别频繁触发压缩因为摘要本身也要消耗Token来回压缩几次成本反而更高。我设置了一个冷却机制同一个会话在5分钟之内最多触发一次压缩。实测中一次20轮对话通常触发一次摘要就够了模型对整体语义的把握比直接丢弃历史要好很多。5. 实测踩坑记录稳定性这块儿没什么捷径5.1 流式输出与SSE的连接管理lucky_agent对外提供流式接口通过SSE把大模型的输出一段一段推给前端。听起来简单但Agent循环里可能穿插多次工具调用前端拿到的不是一个纯粹的答案流而是“调工具了”“工具结果回来了”“正在写回答”这些中间状态。我的做法是把SSE事件分成几类通过事件名区分thinking事件用于模型推理过程tool_run事件标记某个工具开始执行tool_result事件携带工具结果摘要message事件才是最终回复内容done事件表示整个流程结束。前端可以据此渲染出Agent的“思考过程”体验非常直观。连接管理是踩坑重灾区。Agent循环里一次完整任务可能耗时十几秒中间还有多轮模型往返。在这期间SSE连接可能因为网关超时或网络波动断开。我给SSE加了心跳机制Redis缓存会话状态前端断线后可以用同一个sessionId恢复上下文重新请求Agent继续执行。这一套做完线上掉线率才降下来。5.2 模型输出解析失败这件事真的会发生网上很多Demo代码默认大模型会返回规规矩矩的JSON实际生产环境完全不是这样。有的模型会在Tool Calling的JSON外面包一层Markdown代码块有的会夹杂几句自然语言解释有的干脆把JSON截断了一截。lucky_agent第一次遇到这种输出时控制台直接抛异常整个会话崩掉。我当时的第一反应不是写正则去硬抠因为正则在模型输出的各种变体面前不堪一击。最终方案是用Jackson的JsonParser从头开始逐段扫描找到一个合法的JSON结构就停如果整段都不是合法JSON再做一次轻量清理去掉Markdown标记、去掉首尾空白再试一次。最多重试两次还不行就终止本轮工具调用把错误状态返回给用户。这个兜底方案上线后解析失败率从百分之几降到千分之一以内。别指望模型永远规范把解析逻辑做得足够耐磨才是稳定性的关键。5.3 Token预算、成本控制与工具结果截断Agent单次任务调用的Token数比普通对话多很多因为每一轮工具调用都会增加往返。如果不加控制一次任务烧掉几万Token很正常。lucky_agent在发送请求前会做一次Token预算估算规则很朴素中文按1.5到2个字符估1个Token英文按3.5到4个字符估1个Token。虽然不如专业tokenizer精确但作为预算控制已经够用。工具结果在写回历史前也必须截断。我的默认策略是单条工具结果最大2000字符超出部分截断尾部并追加一行“结果过长已被截断”。比如一个订单接口返回了完整的字段列表加操作日志模型真正关心的可能只有状态和预计时间截断不会损害推理。还有一个小建议给每次会话设置总Token上限比如50万Token超过之后强制开启记忆压缩并提醒用户。成本失控往往不是某一次调用超了而是几十轮对话长期没有收敛。6. 权限、安全与并发调优6.1 工具权限边界Agent能调的东西必须分级Agent能调用工具也就意味着模型能间接访问系统资源。权限设计如果不到位一个提示词注入就可能让Agent去执行危险操作。lucky_agent从三个层面做了控制。第一层是工具注册分级。每个工具注解里可以标注权限级别比如anonymous、user、admin。模型发起工具调用后执行器会校验当前会话的用户身份是否满足级别要求不满足就直接拒绝并把拒绝原因返回给模型。第二层是参数级校验。凡是涉及文件路径、URL地址、IP地址的工具入参我都加了一道统一过滤器。比如内部文件查询工具会拒绝包含..的路径参数防止路径穿越HTTP请求类工具会拒绝访问内网网段和云元数据地址规避SSRF。第三层是敏感操作二次确认。支付、删除、修改核心配置这类工具调用前必须返回一个待确认状态由用户在客户端点确认后才会真正执行。另外每次工具调用的入参、出参、耗时都会落审计日志。测试时看起来多此一举但真正出了安全问题这些日志就是定位的唯一线索。6.2 线程池与虚拟线程的并发配置Agent并发有一个明显特征一部分线程是阻塞等待大模型返回另一部分线程在执行业务工具调用整体以IO密集型为主。lucky_agent的线程池最初按CPU核数配置结果吞吐很差后来发现瓶颈在等待大模型响应的线程全部被占住了。调整后的策略分两个池接收用户请求的线程池用有界队列核心线程数按接口QPS估算拒绝策略返回“系统繁忙”提示工具执行线程池则允许更高并发因为每个工具调用时间短且有超时上限一个会话最多同时占用3个工具执行线程。Java 21虚拟线程上线后工具执行池实际上可以做得更激进因为虚拟线程的内存占用远小于平台线程。并发场景还有一个隐藏坑同一个用户同时开多个会话时短期记忆会串。lucky_agent的会话上游加了一个用户级互斥锁同一个userId同一时间只允许一个活跃会话。真有多并发需求可以开放只读会话写会话保持单路避免Agent状态错乱。7. 后续演进我觉得Java系Agent还有三件事值得做7.1 多模型切换与失败重路由lucky_agent目前只接了单模型配置。线上最难受的是模型接口偶尔超时或返回448这类限流错误用户只能重试。下一步想做一个模型路由层主模型超时或连续失败时自动切换到备用的另一家模型同时保持上下文不丢失。因为各家模型的system tokenizer不完全一致切换后历史消息可能需要做一次适配但这个工程价值非常大能直接提升可用性。7.2 多Agent协作让一个团队更像一个团队现在的lucky_agent是单智能体包打天下。遇到跨领域任务时比如用户要同时查商品、比价格、评估优惠券单Agent的上下文会迅速膨胀工具选择也开始混乱。更合理的方案是拆成多个小Agent每个Agent只负责一个领域由一个调度Agent统一分派需求。Java在这块的天然优势是有成熟的线程模型和消息机制多Agent之间的通信可以完全基于内部消息队列不依赖额外中间件。7.3 把AgentLoop沉淀成可独立复用的Java组件lucky_agent目前还是单体模块工具注册、记忆管理、主循环这些代码都混在一起。等接口稳定后我计划把AgentLoop核心逻辑抽成独立组件Spring Boot通过自动配置来装配提供可插拔的工具注册、记忆存储、模型适配扩展点。这样其他Java项目想引入Agent能力只需要引入一个依赖、实现几个接口就能复用lucky_agent沉淀下来的稳定性方案。最后说一点个人体会。做lucky_agent最大的收获不是“把LangChain用Java写了一遍”而是想明白了Agent开发的工程重心模型推理占三成会话长稳占三成工具链路的安全和控制占四成。只要把工具调度、记忆管理、超时兜底这几块做扎实任何语言都能当好Agent的地基。Java系开发者不需要羡慕Python系的原生AI生态我们手里这套成熟到骨子里的工程基建本身就是写Agent的重要底气。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

快速搭建网站的工具怎么选?3个方案省下5万冤枉钱 2026/9/28 9:42:31

快速搭建网站的工具怎么选?3个方案省下5万冤枉钱

快速搭建网站的工具怎么选?3个方案省下5万冤枉钱 网站做好了没人访问,这是很多老板最头疼的事。你花大价钱做的官网,设计精美、功能齐全,但打开一看,流量为零,咨询为零。这时候你才意识到,问题不在“做没做”,而在“怎么快速做出来并推向市场”。面…

阅读更多 →
昇腾910B多机分布式推理:从HCCL到MindIE的DeepSeek部署实践 2026/9/28 9:42:24

昇腾910B多机分布式推理:从HCCL到MindIE的DeepSeek部署实践

昇腾910B上跑DeepSeek多机分布式推理,很多人卡在第一眼:MindIE、HCCL、ranktable、hccn_tool,每个词都眼熟,串起来就不是那么回事。实际踩过一圈之后你会发现,真正决定能不能跑起来的不是模型代码,而是通信…

阅读更多 →
从CANoe到TSMaster:车载总线测试工具链迁移实战指南 2026/9/28 9:42:24

从CANoe到TSMaster:车载总线测试工具链迁移实战指南

搞车载总线测试的工程师,电脑里大概率都装着一套CANoe。我最早接触CANoe是刚入行那会儿,跟着前辈在项目里做网络测试,从报文发送、DBC解析到UDS诊断,基本全是靠Vector这套工具撑起来的。说实话,CANoe确实是这个行业的标…

阅读更多 →
从刷榜到用榜:GitHub Trending 的增量逻辑、项目筛选与高效落地 2026/9/28 9:42:23

从刷榜到用榜:GitHub Trending 的增量逻辑、项目筛选与高效落地

1. 日榜的"热度"到底是怎么算出来的先别急着收藏仓库。每天打开 GitHub 的 Trending 页面,你看到的是过去 24 小时内 Star 增量最高的仓库,周榜和月榜则分别看一周、一个月内的增量。官方没有公开完整排序算法,但用久了会发现&…

阅读更多 →
【Java开发MCP】SSE模式开发并集成MCP:TaoToken统一Key接入与SpringAI WebFlux配置骨架 2026/9/28 9:42:23

【Java开发MCP】SSE模式开发并集成MCP:TaoToken统一Key接入与SpringAI WebFlux配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
OpenCompass 高效评测:Partitioner 任务切分与 Runner 执行后端实战指南 2026/9/28 9:42:23

OpenCompass 高效评测:Partitioner 任务切分与 Runner 执行后端实战指南

模型评测人工智能大模型AI 评测 【免费下载链接】opencompass OpenCompass is an LLM evaluation platform, supporting a wide range of models from OpenAI, Anthropic, Gemini, Qwen, GLM, DeepSeek, etc, across 100 datasets covering knowledge, reasoning, coding, scie…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉