尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从零搭建Agent平台:Java Spring AI打造AI同事生产线

从零搭建Agent平台:Java Spring AI打造AI同事生产线 上个月我把团队里散落的十几个Agent脚本收拢成一个统一的Agent平台时有个同事看了一眼控制台半开玩笑地说这不就是给AI建了个工厂批量造同事嘛。 我想了想这个比喻真的很贴切。Agent平台本质上就是一条AI同事生产线——你往里面注册一份Agent定义系统就能把它变成一个有名有姓、有职责、有工具、能对话、能产出结果的数字角色。你像带新人一样给它安排任务、盯它的产出出了问题还能翻记录复盘。这篇文章是我从零搭这个平台的完整复盘。我会讲清楚平台到底在解决什么问题、Agent的核心单元有哪些、我为什么在Java生态里用Spring AI而不是Python那套、关键代码怎么组织以及我实际踩过的那些坑。如果你手头已经写过一两个AI应用想把它从个人脚本升级成团队都能用的平台这篇文章应该能帮你省不少时间。我不预设你有很深的技术背景但你如果用过ChatGPT或者调过大模型API理解起来会非常轻松。1. 为什么要把Agent放进一条生产线1.1 散装Agent的三大痛点先说我自己之前的处境。团队里每个工程师都在用自己的方式造Agent有人用Python调大模型接口有人在自己电脑上跑Jupyter Notebook拼Prompt有人直接把一个工具函数写死在Agent代码里。每个Agent单独看都挺能打有的能写周报有的能查工单有的能做代码审查。但一旦想让大家一起用问题就接二连三地冒出来。第一个痛点是接口不统一。有人用HTTP暴露服务有人用命令行脚本有人直接在本地调试完就不管了。想把这些Agent串起来做一件事你得给每个Agent单独写适配层。第二个痛点是工具重复建设。三个Agent都要查数据库于是数据库查询逻辑被复制了三遍改一个字段名就要同步改三个地方。第三个痛点最致命——不可观测。Agent回答得不对的时候你完全不知道它内部经历了什么是Prompt写得不好还是它调错了一个工具还是模型本身产生了幻觉。没有日志、没有链路追踪复盘基本靠猜。这就像你把一群新人丢进办公室不给他们岗位说明书不记录他们干了什么出了问题只能一个一个问。1.2 平台解决的是多个Agent一起稳定干活平台化的核心价值不是让单个Agent变聪明而是让一群Agent一起稳定干活这件事变成可能。我自己的理解是平台解决了四件事可以记成四统一统一注册、统一运行、统一工具、统一观测。统一注册是说每个Agent都有自己的一份档案——它是干什么的、用哪个模型、有哪些工具、系统Prompt是什么全部以元数据的形式集中管理。统一运行是说所有Agent都在同一个运行时里执行执行方式、超时控制、重试策略都有一套标准。统一工具是把所有Agent可能用到的能力集中收编做权限管理做到一次接入、处处复用。统一观测是说每一次运行都有日志、有轨迹Agent一步步调了什么工具、消耗了多少Token、花了多长时间全部能追溯。这四件事做下来效果就是造同事的成本大幅降低。以前每做一个新Agent都要从零搭一遍架子现在只需要在平台上填一张简历运行时、日志、监控这些基础设施已经有现成的了。有一个类比我一直觉得特别准确单体Agent是你在家里自己做饭平台是把中央厨房建好之后的每一个同事都只是往厨房里加一道新菜谱。1.3 到底谁需要平台谁不需要不过我也得泼一盆冷水。不是所有场景都需要上平台。如果只是想让AI帮你写一封邮件、生成一张图片直接调API就够了上平台是杀鸡用牛刀。平台解决的是量和协作的问题当你要维护几十个Agent当Agent之间需要互相调用当你的Agent要接公司内部的数据和系统当你不希望每个Agent的Prompt散落在不同人的笔记本里——到了这个阶段平台化才真正划算。判断标准很简单你就问自己三个问题这些Agent会不会被别人使用它们需不需要访问公司内部系统它们跑挂了之后需不需要有人能排查只要有一个答案是肯定的就该考虑平台了。我自己当时就是三个答案全中才下定决心动手搭。2. 平台核心概念与技术选型2.1 Agent到底是什么模型、工具、记忆、规划要把平台搭好首先得把Agent这个词拆开。很多人一听到Agent就觉得是个很玄的东西其实它由四块组成模型、工具、记忆、规划。模型就是大语言模型也就是Agent的大脑负责理解和生成。工具是Agent的手脚它让Agent能执行真实动作——查数据库、调接口、发消息、改文件。没有工具的Agent只是一个聊天机器人有了工具它才是一个能干活的同事。记忆是Agent的工作日志和经验本分短期记忆和长期记忆短期记忆就是当前这个任务里的对话上下文长期记忆是对某个用户、某个项目的偏好和历史积累。规划是Agent的工作方法就是它面对一个复杂任务时怎么拆解步骤、先做什么后做什么。把这四块装进一个壳里就是一个完整可跑的Agent。平台做的事情就是把这四块的配置标准化、流程自动化。比如模型可以切换工具可以组合记忆可以持久化规划逻辑可以在Prompt层统一注入。这样你在平台上新增一个Agent本质上就是给四块组件分别填一份配置再绑定到一起。这个思路我一开始没想清楚结果后面改结构改了一次早点想明白可以少走很多弯路。2.2 平台四件套注册、运行时、工具、观测对应Agent的四个组成平台也有自己的四个核心模块我习惯叫它平台四件套。第一个是Agent注册中心相当于同事档案库。每个Agent启动时在这里登记名字、职责描述、使用的模型、绑定哪些工具、系统Prompt是什么。注册中心不仅要存这些元数据还要支持按职责描述去搜索——你想找一个能生成会议纪要的Agent哪怕不知道它的具体名字也应该能搜得到。第二个是Agent运行时这是平台的心脏。它负责把一份Agent配置真正跑起来接收任务、加载配置、构建上下文、调用模型、执行工具调用、返回结果。运行时得处理循环控制比如工具最多调用几次、超时、重试、并发限流。我后面会详细写这部分。第三个是工具市场就是Agent能用的技能库。工具在这里统一注册、统一鉴权Agent只能调用自己被授权的工具。工具市场还需要维护工具的描述信息因为模型的工具调用能力很大程度上取决于工具描述写得清不清楚这个后面我会专门讲。第四个是观测系统这是最容易忽略但最要命的一个。平台需要记录每一次Agent运行的完整轨迹输入是什么、每一步调用了什么工具、工具返回了什么、最终输出是什么、用了多少Token、耗时多久。没有这套记录Agent一旦出错你只能对着空气发懵。这四个模块缺一个平台都不完整。尤其是观测我吃过亏一开始偷懒没做后来出了问题只能加日志重新跑场景白白浪费了几天时间。2.3 技术选型Java Spring AI还是 Python LangChain选型是很多人会纠结的地方。现在大模型应用开发的主流路线有两条一条是Python生态的LangChain / LangGraph另一条是Java生态的Spring AI。两条我都调研过最终选了Java Spring AI的方案。这么选不是因为Python那套不好而是要看你的团队和系统现状。Python那边生态丰富、案例多、迭代快做原型特别顺手。但如果你和我一样团队现有系统用Java核心数据在MySQL和Oracle里中间件是Spring Cloud那一套那引入一个Python技术栈做Agent平台意味着你的平台要跟已有的微服务体系做两套集成一套给Java业务系统一套给Python的Agent。这个双栈维护成本是隐性的但到后期会非常难受。Spring AI的好处是能直接复用Spring生态的成熟能力用Spring Boot管理生命周期用Nacos或Eureka做服务发现用Feign调内部服务用Spring Security做鉴权用Spring Cloud Sleuth做链路追踪。Agent平台不是孤立存在的它要跟你的工单系统、任务系统、用户系统打交道这时候Java生态的顺滑感就体现出来了。LangChain确实有很多高级编排组件但那些能力在Spring AI里可以用Spring的Router、Filter、Service这类老熟人工具替代实现。我把两条路的取舍整理成一张表方便你对照自己的情况维度Python LangChainJava Spring AI原型速度非常快社区案例多稍慢但组件易复用企业系统集成需要额外开发适配层天然融入Spring生态内存与性能默认较低需要优化依托JVM适合长驻服务团队招聘难度偏算法背景偏后端工程背景生产治理能力需自己拼装有成熟监控、配置、治理工具结论就是没有绝对的好与坏只有匹配不匹配。你要是从零做一个独立产品、团队又是Python背景LangChain依然是好选择。但如果你在一个已经有Java技术栈的公司内部搭平台我强烈建议你认真看看Spring AI这条路。3. 从0到1搭建全过程3.1 工程目录与核心数据模型说干就干。我先搭一个最小可用的工程骨架。整个平台拆成五个模块agent-registry负责注册中心agent-runtime负责执行tool-market负责工具管理agent-api是对外的HTTP入口agent-console是一个简单的Web控制台。我特意把注册和运行时拆开是因为这两个东西的扩展方向完全不同——注册中心以后要接数据库和分布式缓存运行时要接消息队列做异步执行拆开改起来互不干扰。agent-platform/ ├── agent-api/ # 对外HTTP入口接收任务 ├── agent-registry/ # Agent注册中心管理Agent定义 ├── agent-runtime/ # Agent运行时执行任务 ├── tool-market/ # 工具市场统一注册和调用工具 └── agent-console/ # 管理控制台查Agent和日志第一个要写的数据模型是AgentSpec也就是Agent的简历。我用Java record来定义简洁且天然不可变。这份数据很重要你宁可字段多写一点也别省后面接控制台、接搜索、接权限管理都用得上public record AgentSpec( String name, // Agent唯一标识比如 meeting-assistant String displayName, // 展示名比如 会议纪要同事 String description, // 职责描述用于搜索和路由 String systemPrompt, // 系统提示词 String modelName, // 绑定的模型 double temperature, // 模型温度参数 ListString toolNames, // 允许调用的工具列表 MapString, String metadata // 扩展字段 ) {}这里最容易被忽略的是description。我后来才明白description不仅要给人类看更要给Agent的路由和搜索用。当你的平台上有上百个Agent用户只说一句帮我整理会议记录系统要能通过description找到那个合适的Agent。description写得像岗位JD明确说清楚这个Agent能干什么、擅长什么、不擅长什么后续做语义匹配才会准。3.2 注册中心给Agent建档案注册中心是这个平台的地基。第一版完全不用上分布式一个ConcurrentHashMap加两个方法就够了。很多人在第一步就想上Redis、上MySQL我劝你先打住——内存版本跑通逻辑后面再替换存储实现接口不变改起来很容易。我实现的注册中心是这样的支持注册、查询、按名字精确查找、按描述关键字搜索。注册时如果名字重复就直接抛异常避免两个Agent抢同一个标识Component public class AgentRegistry { private final MapString, AgentSpec agents new ConcurrentHashMap(); public void register(AgentSpec spec) { if (agents.containsKey(spec.name())) { throw new IllegalArgumentException(Agent已存在: spec.name()); } agents.put(spec.name(), spec); } public OptionalAgentSpec find(String name) { return Optional.ofNullable(agents.get(name)); } public ListAgentSpec search(String keyword) { String k keyword.toLowerCase(); return agents.values().stream() .filter(spec - spec.description().toLowerCase().contains(k)) .toList(); } }这样一个注册中心就完成了一版。它虽然简单但已经满足了统一注册的核心诉求。以后要持久化就在register和find的实现里换成一个Repository接口业务层完全不用动。另外我建议在注册中心上做一个简单的List接口控制台页面直接调用它展示所有Agent这个成本很低但对团队使用体验的提升非常大。3.3 运行时Agent怎么真正跑起来运行时是平台里最核心的模块也是我需要重点解释的部分。Agent一次任务执行的基本流程是这样的拿到任务 - 按Agent名字找到定义 - 初始化上下文消息列表 - 调用模型 - 如果模型返回的是工具调用请求就执行工具并把结果加回消息列表 - 再次调用模型 - 直到模型返回最终文本答案。这个模型和工具交替调用的循环是Agent和普通聊天应用的本质区别。普通聊天是一次请求一次响应Agent可能一轮对话背后跑了三次模型调用、五次工具调用。这个循环逻辑用代码写出来并不复杂关键是控制好迭代次数防止Agent在某个工具上无限循环Component public class AgentRuntime { private final AgentRegistry registry; private final ToolMarket toolMarket; private final ChatModel chatModel; public AgentResult execute(AgentTask task) { AgentSpec spec registry.find(task.agentName()) .orElseThrow(() - new AgentNotFoundException(task.agentName())); ListMessage messages new ArrayList(); messages.add(new SystemMessage(spec.systemPrompt())); messages.add(new UserMessage(task.input())); // 每一次循环都是一次模型思考 可能的工具执行 for (int step 0; step spec.maxIterations(); step) { ChatResponse response chatModel.call( new Prompt(messages, toolMarket.getOptions(spec.toolNames())) ); if (response.hasToolCalls()) { // 模型决定调用工具这里逐个执行 for (ToolCall call : response.toolCalls()) { log.info(Agent {} 调用工具 {}参数 {}, spec.name(), call.name(), call.arguments()); String result toolMarket.execute(call.name(), call.arguments()); messages.add(new ToolMessage(call.id(), result)); } // 带上工具结果回到模型继续生成 continue; } // 模型给出最终文本任务结束 return new AgentResult(spec.name(), response.getText(), step); } throw new MaxIterationException(Agent spec.name() 超过最大迭代次数); } }这个循环是整个平台的心跳。我建议maxIterations一开始就设一个合理值比如8到10太少了复杂任务跑不完太多了有死循环风险。另外一个细节是日志要打在每一条路径上尤其是工具调用这一步。因为后续排查问题靠的就是这些日志还原Agent当时是怎么想的、怎么做的。这里要提醒一点不同版本的Spring AIChatModel和Prompt的API细节会有差异。上面这段代码的核心是流程你实际使用的时候要以你引入的Spring AI版本对应的API为准重点理解消息列表不断增长、模型和工具交替调用这个模式。3.4 工具市场给Agent配好手脚工具市场决定了Agent到底能干多少活。我在tool-market模块里维护一个工具注册表每个工具都有名字、描述、参数定义和真正的执行逻辑。工具的定义和实现我用Spring AI的Tool注解它能自动把方法信息暴露给模型Component public class MeetingTools { Tool(name queryMeetings, description 按日期查询会议列表返回会议主题、时间和参与人) public ListMeeting queryMeetings(String date) { return meetingRepository.findByDate(date); } Tool(name createTodoItem, description 在任务系统创建一个待办事项返回待办ID) public Long createTodoItem(String title, String owner, String dueDate) { return todoService.create(title, owner, dueDate); } }工具描述这件事我踩过很深的坑。最开始我写工具描述特别随意比如queryMeetings就写查询会议结果模型经常在用户问我今天下午的会议取消了吗这种问题时传一个错误的日期格式进去或者不知道该不该返回空列表。后来我把描述改成按日期查询会议列表返回会议主题、时间和参与人日期格式为yyyy-MM-dd当天无会议时返回空列表模型的调用准确率立刻上去了。说穿了工具描述是Prompt工程的一部分。模型看到的是工具名 工具描述 参数说明它靠这些信息决定什么时候调用、传什么参数。描述得越具体、越有边界调用就越准确。这就像给新同事写工具说明书写清楚这个接口接收什么、返回什么、什么时候用人家才不会用错。3.5 全链路示例造一个会议纪要同事理论讲完了我用一个完整的例子把所有东西串起来。我选择造一个会议纪要同事因为会议纪要是办公场景里高频、刚需、效果又非常直观的Agent。这个同事的职责是拿到一份会议录音转写文本核对会议基本信息提炼决策和待办事项并把待办同步到公司的任务系统。它的注册配置长这样Configuration public class MeetingAgentConfig { Bean public AgentSpec meetingAgent(AgentRegistry registry) { AgentSpec spec new AgentSpec( meeting-assistant, // 唯一标识 会议纪要同事, // 展示名 负责把会议转写文本整理成结构化纪要和待办能查询会议信息并创建待办任务, // 描述 你是团队的会议纪要同事。你的工作流程 1. 调用 queryMeetings 核对会议主题、时间和参会人 2. 通读转写全文提取决策、行动项和风险 3. 输出格式固定为会议主题、时间、参会人、决议、待办清单 4. 对每个待办调用 createTodoItem 同步到任务系统。 如果原文缺少信息明确说这里缺失了什么绝对不要编造。 , gpt-4o, // 模型 0.3, // 温度调低让输出更稳定 List.of(queryMeetings, createTodoItem), Map.of(owner, infra-team) ); registry.register(spec); return spec; } }写好配置文件后平台启动时就会自动把这个Agent注册进去。之后通过agent-api提交一个任务传一句请处理今天上午的产品评审会录音运行时就会按照前面那个循环跑起来。这个Agent第一次跑的时候出了一点小问题它调用queryMeetings时传的日期是今天这样的自然语言而不是yyyy-MM-dd格式工具执行直接报错。后来我在工具描述里明确写了日期格式同时在系统Prompt里加了一句调用工具前先想想参数格式问题就解决了。这个小插曲也印证了前面说的工具描述的颗粒度直接决定Agent的稳定性。4. 常见问题与排查技巧实录4.1 最坑的三个问题死循环、上下文爆炸、幻觉平台跑了三周我整理出了三个出现频率最高、也最让人头疼的问题这里逐个说。第一个是工具调用死循环。表现是Agent反复调用同一个工具比如对着日程接口查了一遍又一遍就是不给出最终结论。原因通常有两种工具返回的结果不满足模型的预期模型想换参数再试一次或者是工具本身没有返回有效的终止信号模型觉得信息还不够。排查方法是在运行时把每次工具调用的参数和结果都打日志连续看几次调用就能判断出来。解决方案是两种设置最大迭代次数兜底同时在系统Prompt里加一句如果工具返回了结果不要重复调用同一个工具。第二个是上下文爆炸。Agent跑复杂任务时历史消息越来越多最后直接把模型的上下文窗口撑爆。最明显的症状是前期一切正常越往后回答越乱、越慢最终直接报上下文超长。我当时在平台里加了一个简单的上下文压缩策略当消息列表超过一定条数时把最早的历史对话交给模型做一次摘要用摘要替换原始消息。这个策略实测能大幅延长连续对话能力代价是摘要会丢失一些细节所以只对早期消息做压缩最近的消息保留原文。第三个是幻觉尤其是Agent接了公司数据之后会一本正经地编造不存在的项目、人员和数据。治疗幻觉最有效的手段不是换更强的模型而是给Agent一个可靠的信息边界在系统Prompt里明确告诉它哪些信息必须通过工具获取、哪些情况不许猜测一旦不确定就明确说不知道。我在平台里形成了一条默认规则所有涉及到具体数字、日期、人名的事实必须来自工具调用结果模型自己记忆中的都不算数。4.2 排查方法论可观测性是平台的底线踩过这些坑之后我最深刻的体会就是Agent平台的排查逻辑和传统后端完全不一样。传统后端出错报错栈基本能定位问题Agent出错往往是模型在某个环节误解了工具结果这类模糊问题没有栈只有过程。所以平台的观测系统不是锦上添花而是底线。每个Agent的一次完整运行至少要记录这些维度输入的任务文本、模型生成过程中每一步的工具调用工具名、参数、返回结果截断后的摘要、最终输出、总Token消耗、每步耗时。有这份数据绝大多数问题就能还原出剧本看到底是哪一步出了岔子。我建议把运行日志直接接入公司现有的日志平台而不是自己存文件。因为Agent的日志天然是多行的、结构化的用现成的日志检索系统查起来省力很多。日志里要加一个requestId串起整条链路否则并发跑起来你根本分不清哪个日志属于哪个任务。4.3 避坑速查表我把遇到过的典型问题整理成一个速查表直接复制到你的团队文档里也行排查的时候对照着看能省不少时间现象可能原因排查思路与解决Agent反复调用同一个工具工具结果不满足预期或描述有歧义检查工具调用日志优化工具描述加重复调用限制回答越到后面越乱上下文膨胀挤占了有效信息加上下文压缩策略保留最近消息原文出现编造的工单号/项目名事实类信息没有走工具系统Prompt声明事实必须来自工具不允许猜测工具参数传错工具描述没写清格式在工具描述里给参数示例和边界条件并发一高就大量超时模型调用没有做限流运行时加信号量控制并发超时单独设置同样的任务结果不一致温度太高或Prompt边界模糊把temperature调到0.3以下固化输出格式这个表是我自己平时排查问题的时候对照用的里面每一行都是从真实故障里提炼出来的信价比很高。5. 从单Agent到多智能体工厂怎么升级5.1 多Agent协作的两种基础模式平台单跑一个Agent只是入门真正发挥价值的是让多个Agent协作完成一件复杂的事。我实践下来两种基础模式最常用编排模式和群聊模式。编排模式是一个主Agent当项目经理它不自己干具体活而是把任务拆分后分派给多个子Agent最后汇总结果。比如做一个项目周报Agent主Agent先调用工单统计Agent获取本周工单数据再调用代码提交Agent获取仓库提交量最后自己汇总生成周报。这种模式的好处是边界清晰每个子Agent职责单一主Agent只做调度和汇总出问题好定位。群聊模式是多个Agent共享同一个上下文轮流发言像一个会议室里的讨论组。这种模式适合头脑风暴、方案评审这类场景。我实际体验下来群聊模式对模型的上下文管理要求很高很容易聊着聊着就跑偏所以现在只在小范围实验没有大规模开放。两种模式的选择标准很简单如果任务可以清晰拆分成独立子任务用编排如果需要多角度碰撞观点用群聊。最开始不要同时上两种模式先把编排模式用熟练收益最大。5.2 平台后续演进的三条主线平台跑稳定之后要做好后续演进我觉得有三条主线值得投入。第一条是权限与审计。平台里的Agent能访问真实业务系统和数据所以必须做到Agent能调哪些工具、看哪些数据由权限系统严格控制。每一步工具调用都要留审计记录出问题能追责。这一点在企业内部尤其重要不是你信不信任的问题而是合规底线。第二条是评估体系。没有评估你根本无法判断一个Agent是变好了还是变坏了。我在平台上加了一个简单的回归测试集把过去真实任务作为测试用例每次改完Prompt或工具逻辑之后自动跑一遍对比结果质量。哪怕只是人工抽查也比没有强百倍。这是Agent工程和传统工程最大的不同——传统代码有单元测试兜底Agent只能靠持续评估来兜底。第三条是Agent运营。平台建好之后要像管人一样管Agent谁的调用量高、谁的失败率高、谁的任务总是需要人工兜底、谁的成本消耗最大。我每个月都会拉一遍这些数据把那些入职很久但产出很低的Agent下掉集中精力优化高频高价值的Agent。这跟用人效考核管团队是一个逻辑。最后说两句实在话搭这个平台我最大的体会是Agent的价值从来不在技术demo里而在你把它当成组织里的一员去运营的时候。技术只是让这件事变简单的杠杆。给它明确的职责、给它好用的工具、给它清晰的边界然后像带新同事一样盯它跑几周根据反馈不断调它的Prompt和工具——这套方法论用在哪里都通。最后分享一个最实用的小技巧在平台的默认系统Prompt里统一加一条如果你不确定就明确说你不确定不要编造。这一条规则直接让全平台Agent的无效调用和错误回答减少了将近三分之一。很多时候一个同事靠不靠谱不在于他懂多少而在于他清不清楚自己的边界。Agent也一样。
返回列表