资讯动态

基于Spring AI Alibaba构建生产级Java AI Agent:状态管理、Graph编排与工程化实践

发布时间:2026/8/14 9:17:25 来源:尧图企业网站定制
1. 从“玩具”到“生产级”一个Java程序员的Agent执念作为一个在Java生态里泡了十几年的老码农这两年看着AI Agent的风口心里其实挺痒的。网上铺天盖地的教程十个有九个半是Python的用的不是LangChain就是LangGraph张口闭口就是OpenAI的API。不是说Python不好但对于我们这些后端服务、企业级应用开发的主力军——Java程序员来说总感觉隔了一层。我们关心的是这东西怎么集成到现有的Spring Boot微服务里怎么处理高并发下的上下文管理怎么保证链路的可观测性和稳定性换句话说我们需要的不是一个能跑起来的“玩具”而是一个能扛住生产环境流量的“工程化AI Agent”。这就是我决定用Spring AI Alibaba 1.1.2.0从零开始手搓一个生产级AI Agent的初衷。Spring AI Alibaba这个项目可以看作是Spring生态对AI应用开发的一次“官方”回应它试图把AI能力特别是与阿里云百炼、灵积等模型的交互像集成一个数据库比如Redis或者消息队列一样无缝地融入到Spring的应用上下文中。而1.1.2.0版本在我看来是一个里程碑它引入了对Graph图编程模型更成熟的支持这让构建复杂的、多步骤的AI工作流也就是Agent的核心变得前所未有的清晰和可控。所以这篇“完结篇”我想和你分享的不是又一个“Hello World”式的Demo而是如何站在一个Java后端工程师的视角利用Spring AI Alibaba提供的“基础设施”去设计和实现一个具备生产级潜力的AI Agent。我们会聚焦于几个核心的生产级考量状态管理用Redis持久化对话历史与Agent运行状态、流程编排用Graph和SnapGraphBuilder构建健壮的工作流、以及工程化封装设计清晰的分层与接口。你会发现当AI能力被Spring的IoC容器和熟悉的注解所管理时一切都会变得那么“Java”那么“Spring”。2. 生产级Agent的基石状态管理与Graph编排在开始敲代码之前我们必须想清楚两个问题Agent的“记忆”存在哪里Agent的“行为逻辑”如何组织这两个问题直接决定了Agent的可靠性、扩展性和性能。2.1 为什么生产环境必须用外部存储管理状态很多入门教程里Agent的对话历史Memory和运行状态State直接放在内存里服务一重启全没了。这在生产环境是绝对不可接受的。想象一个客服Agent用户问了三个问题在回答第四个问题时服务重启了Agent“失忆”了这体验得多糟糕因此我们需要一个外部、持久化、高性能的存储来管理状态。Redis几乎是这个场景下的不二之选。原因如下高性能与低延迟Agent的每次交互都可能需要读写上下文Redis的内存读写特性完美匹配。丰富的数据结构我们可以用String存序列化的完整状态用List存对话历史用Hash存一些元信息非常灵活。过期策略可以很方便地通过TTLTime-To-Live来控制会话数据的生命周期避免数据无限膨胀。高可用Redis Cluster或哨兵模式能提供生产级的高可用保障这是内存存储无法比拟的。在Spring AI Alibaba的语境下我们需要实现自己的ChatMemory或State存储。核心是定义一个RedisChatMemoryStore这样的Bean它实现ChatMemoryStore接口内部利用RedisTemplate进行CRUD操作。关键点在于序列化Agent的Message或State对象通常结构复杂建议使用Jackson进行JSON序列化并在Redis中存储为String类型。Component public class RedisChatMemoryStore implements ChatMemoryStore { Autowired private RedisTemplateString, String redisTemplate; private final ObjectMapper objectMapper new ObjectMapper(); Override public void store(String conversationId, ListMessage messages) { try { String key chat:memory: conversationId; String value objectMapper.writeValueAsString(messages); // 设置24小时过期 redisTemplate.opsForValue().set(key, value, 24, TimeUnit.HOURS); } catch (JsonProcessingException e) { throw new RuntimeException(Failed to serialize messages, e); } } Override public ListMessage retrieve(String conversationId) { String key chat:memory: conversationId; String value redisTemplate.opsForValue().get(key); if (value null) { return new ArrayList(); } try { // 注意处理泛型类型 return objectMapper.readValue(value, new TypeReferenceListMessage() {}); } catch (JsonProcessingException e) { throw new RuntimeException(Failed to deserialize messages, e); } } Override public void remove(String conversationId) { String key chat:memory: conversationId; redisTemplate.delete(key); } }注意这里有一个实际踩过的坑。Message及其子类如SystemMessage,UserMessage,AiMessage可能包含一些内部字段或循环引用直接序列化可能会失败或产生巨大JSON。建议在ObjectMapper中配置忽略未知属性FAIL_ON_UNKNOWN_PROPERTIES设为false和仅序列化非空字段。2.2 Graph编排用“流程图”思维构建Agent工作流Agent的核心是其决策和执行逻辑。一个复杂的Agent可能包含理解用户意图、调用工具查询信息、进行多轮思考、格式化输出等多个步骤。如果用传统的if-else或责任链模式来写代码会很快变得难以维护和调试。Spring AI Alibaba 1.1.2.0 引入了对Graph模型的增强支持其核心是SnapGraphBuilder。你可以把它理解成画流程图的工具。每个节点Node是一个执行单元比如调用LLM、执行一个工具方法边Edge定义了节点之间的流转条件。这种方式的巨大优势在于可视化与可调试性工作流可以被直观地“画”出来执行路径一目了然。在出现问题时你可以清晰地追踪到是哪个节点出了错。灵活性通过动态增删节点或修改边条件可以轻松调整Agent的行为而无需重写大量业务代码。与Spring生态集成Graph中的节点可以是普通的Spring Bean方便依赖注入和管理。一个典型的生产级Agent Graph可能包含以下节点路由节点Router根据用户输入或当前状态决定下一步进入哪个专业处理分支。工具执行节点Tool Node调用一个具体的工具比如查询数据库、调用外部API。LLM调用节点LLM Node请求大模型进行思考、总结或生成文本。条件判断节点Conditional检查某个条件如工具执行结果是否为空决定后续流向。使用SnapGraphBuilder构建这样一个Graph的代码结构非常清晰Configuration public class CustomerServiceGraphConfig { Autowired private ChatClient chatClient; // Spring AI Alibaba 封装的聊天客户端 Autowired private ProductQueryTool productQueryTool; Autowired private OrderStatusTool orderStatusTool; Bean public Graph customerServiceGraph() { SnapGraphBuilder builder new SnapGraphBuilder(); // 1. 初始节点解析用户意图 Node intentNode new Node(intentNode, (state) - { String userInput state.get(input, String.class); // 这里可以调用一个简单的分类模型或规则引擎 if (userInput.contains(产品) || userInput.contains(买)) { return Map.of(intent, QUERY_PRODUCT); } else if (userInput.contains(订单) || userInput.contains(物流)) { return Map.of(intent, QUERY_ORDER); } else { return Map.of(intent, GENERAL_CHAT); } }); // 2. 产品查询节点 Node productNode new Node(productNode, (state) - { String query state.get(input, String.class); ListProduct products productQueryTool.execute(query); state.put(productResult, products); return state; }); // 3. 订单查询节点 Node orderNode ... // 类似 // 4. LLM总结回答节点 Node llmResponseNode new Node(llmResponseNode, (state) - { String intent state.get(intent, String.class); String prompt; if (QUERY_PRODUCT.equals(intent)) { ListProduct products state.get(productResult, List.class); prompt 根据以下产品列表生成一段推荐给用户的友好回复 products.toString(); } else { prompt 请以客服身份友好地回答用户 state.get(input, String.class); } AiMessage response chatClient.call(new Prompt(prompt)).getResult().getOutput(); state.put(finalResponse, response.getContent()); return state; }); // 构建图定义节点和边 builder.addNode(intentNode) .addNode(productNode) .addNode(orderNode) .addNode(llmResponseNode) .addEdge(intentNode, productNode, (state) - QUERY_PRODUCT.equals(state.get(intent))) .addEdge(intentNode, orderNode, (state) - QUERY_ORDER.equals(state.get(intent))) .addEdge(intentNode, llmResponseNode, (state) - GENERAL_CHAT.equals(state.get(intent))) .addEdge(productNode, llmResponseNode) // 产品查询后总是进入LLM总结 .addEdge(orderNode, llmResponseNode); // 订单查询后总是进入LLM总结 return builder.build(); } }这个Graph定义了一个简单的客服Agent流程先判断意图然后根据意图分支执行不同的工具查询最后汇总信息交由LLM生成友好回复。State对象在整个Graph中流转携带了所有必要的信息。3. 工程化封装定义清晰的领域模型与服务层有了状态存储和流程引擎我们还需要良好的代码结构来组织业务逻辑。直接在所有地方操作Graph和RedisTemplate会让代码迅速腐化。我们应该遵循经典的分层架构。3.1 定义领域模型Conversation, Turn, AgentState首先定义核心领域对象让业务语义更清晰。// 一次完整的对话会话 Data public class Conversation { private String id; // 会话ID可用UUID生成 private String userId; // 关联的用户ID private LocalDateTime createdAt; private LocalDateTime updatedAt; private ListTurn turns; // 对话轮次列表 private MapString, Object persistentState; // 需要跨轮次持久化的状态如用户偏好 } // 单次交互轮次用户输入Agent响应 Data public class Turn { private String turnId; private Message userMessage; private Message agentMessage; private MapString, Object turnState; // 本轮次产生的临时状态 private LocalDateTime timestamp; private String graphExecutionId; // 关联本次执行的Graph实例ID用于调试溯源 } // Agent运行时的快照状态对应Graph中的State Data public class AgentState { private String currentGraphNode; // 当前所在节点 private MapString, Object variables; // 状态变量池 private String conversationId; private String lastError; }3.2 构建服务层AgentExecutionService服务层负责协调资源是业务逻辑的核心。它依赖Graph、ChatMemoryStore以及可能的其他服务如用户服务、工具服务。Service Slf4j public class AgentExecutionService { Autowired private Graph customerServiceGraph; Autowired private RedisChatMemoryStore memoryStore; Autowired private ConversationRepository conversationRepo; Transactional // 考虑事务性确保状态存储和业务更新的一致性 public AgentResponse execute(String conversationId, String userInput) { // 1. 加载或创建会话 Conversation conversation conversationRepo.findById(conversationId) .orElseGet(() - createNewConversation(conversationId)); // 2. 从Redis加载历史消息构建本次执行的初始State ListMessage history memoryStore.retrieve(conversationId); MapString, Object initialState new HashMap(); initialState.put(input, userInput); initialState.put(history, history); initialState.put(conversationId, conversationId); // 可以注入一些业务上下文如用户等级、产品目录等 initialState.put(userContext, loadUserContext(conversation.getUserId())); // 3. 执行Graph ExecutionResult result; try { result customerServiceGraph.execute(initialState); } catch (Exception e) { log.error(Graph execution failed for conversation: {}, conversationId, e); // 生产环境应有降级策略例如返回一个预设的友好错误信息 return AgentResponse.fail(系统正在思考请稍后再试。); } // 4. 处理执行结果 MapString, Object finalState result.getState(); String agentOutput (String) finalState.get(finalResponse); // 5. 保存本轮交互 Turn newTurn new Turn(); newTurn.setUserMessage(new UserMessage(userInput)); newTurn.setAgentMessage(new AiMessage(agentOutput)); newTurn.setTurnState(finalState); conversation.getTurns().add(newTurn); conversationRepo.save(conversation); // 6. 更新记忆将本轮对话加入历史并存回Redis history.add(newTurn.getUserMessage()); history.add(newTurn.getAgentMessage()); // 生产环境需考虑历史消息截断策略防止token超限 memoryStore.store(conversationId, history); // 7. 返回响应 return AgentResponse.success(agentOutput, newTurn.getTurnId()); } private Conversation createNewConversation(String id) { Conversation conv new Conversation(); conv.setId(id); conv.setCreatedAt(LocalDateTime.now()); conv.setTurns(new ArrayList()); // ... 其他初始化逻辑 return conversationRepo.save(conv); } }这个AgentExecutionService是一个典型的模板它处理了会话生命周期、状态加载/保存、Graph执行、异常处理和持久化。这里的关键经验是将AI Agent的执行看作一个标准的、有状态的业务流程用处理其他业务事务一样的态度来对待它包括事务管理、日志记录和异常降级。3.3 工具Tool的标准化封装Agent的能力很大程度上取决于其可用的工具。在Spring AI Alibaba中工具通常被定义为实现了特定接口的Spring Bean。为了便于管理和扩展我们可以建立一个工具注册中心。public interface AgentTool { String getName(); String getDescription(); Class? getInputSchema(); // 定义工具输入参数的JSON Schema用于让LLM理解 Object execute(MapString, Object parameters); } Service public class ProductQueryTool implements AgentTool { Autowired private ProductService productService; Override public String getName() { return query_products; } Override public String getDescription() { return 根据关键词查询产品信息返回产品列表。; } Override public Class? getInputSchema() { // 返回一个定义输入参数如keyword, category的Java Bean类 return ProductQueryInput.class; } Override public Object execute(MapString, Object parameters) { // 参数校验和转换 ProductQueryInput input convertParams(parameters); // 调用真正的业务服务 return productService.searchProducts(input.getKeyword(), input.getCategory()); } }然后我们可以创建一个ToolRegistry在应用启动时扫描所有AgentTool的实现并注册。当Graph中的工具节点需要调用工具时就从ToolRegistry中按名称查找并执行。这种模式使得增加新工具变得非常容易只需要实现接口并声明为Bean即可。4. 生产环境部署与运维考量代码写完了本地也跑通了但离“生产级”还有最后一道也是最重要的一道坎部署和运维。这部分往往被教程忽略但却是决定项目成败的关键。4.1 配置管理与多环境隔离AI应用涉及大量配置不同模型的API Key、Base URL、超时时间、Redis连接信息、Graph的调试开关等。绝不能硬编码在代码里。最佳实践是使用Spring Cloud Config、Nacos或Apollo等配置中心实现配置的集中管理、动态刷新和多环境隔离开发、测试、生产。对于敏感信息如API Key务必使用加密存储。在application.yml中我们可以进行分层配置spring: ai: alibaba: chat: # 默认使用阿里云灵积的qwen-plus模型 options: api-key: ${AI_API_KEY:default_key} # 从环境变量或配置中心读取 endpoint: https://dashscope.aliyuncs.com/compatible-mode/v1 model: qwen-plus temperature: 0.8 max-tokens: 2000 # Graph执行配置 graph: execution: mode: ASYNC # 对于耗时长的Graph考虑异步执行 timeout-ms: 30000 # 超时设置防止僵尸任务 # 自定义配置 agent: memory: redis: ttl-hours: 24 max-history-turns: 50 # 历史对话最大轮次防止token爆炸 graph: customer-service: enable-debug-log: false # 生产环境关闭详细节点日志4.2 可观测性日志、指标与链路追踪一个黑盒的Agent在生产环境是可怕的。我们必须知道它处理每个请求花了多长时间Graph的哪个节点最慢工具调用失败率是多少用户都在问什么问题结构化日志使用SLF4JLogback在AgentExecutionService、各个工具节点、Graph引擎的关键位置打点日志。日志内容要包含conversationId、graphExecutionId、userId等关键字段方便后续聚合查询。log.info(Graph execution started. [conversationId{}, graph{}], conversationId, graphName); log.warn(Tool execution failed. [tool{}, error{}, conversationId{}], toolName, errorMsg, conversationId);监控指标Metrics集成Micrometer将关键指标暴露给Prometheus。agent.execution.durationAgent请求处理耗时直方图。agent.graph.node.duration每个Graph节点的执行耗时。agent.tool.invocation.count工具调用次数按成功/失败分类。agent.llm.token.usage大模型Token消耗如果API支持返回。 这些指标能帮你快速定位性能瓶颈和异常。分布式链路追踪如果Agent是微服务架构中的一环集成SkyWalking、Jaeger或Zipkin。确保从Web入口到Agent服务内部Graph执行的完整调用链都被追踪。当某个用户请求响应慢时你可以清晰地看到时间消耗在了数据库查询、外部API调用还是LLM等待上。4.3 弹性与容错设计生产环境没有100%可用的外部服务。LLM API可能超时数据库可能抖动工具依赖的第三方接口可能挂掉。重试与退避对于暂时性故障如网络超时使用Spring Retry或Resilience4j为关键操作如LLM调用、工具查询添加带指数退避的重试机制。Retryable(value {ResourceAccessException.class}, maxAttempts 3, backoff Backoff(delay 1000, multiplier 2)) public AiMessage callChatModelWithRetry(Prompt prompt) { return chatClient.call(prompt).getResult().getOutput(); }熔断与降级使用Resilience4j或Sentinel为LLM服务或核心工具设置熔断器。当失败率超过阈值时快速失败并执行降级逻辑。例如当产品查询工具不可用时Agent可以降级为回复“暂时无法查询产品详情但您可以先浏览我们的网站主页或稍后再试。”异步与超时对于可能耗时的复杂Graph考虑将其提交到线程池异步执行并通过Future或消息队列返回结果。同时必须在Graph层面和HTTP请求层面设置合理的超时避免资源被长时间占用。4.4 安全与权限控制AI Agent能调用工具这意味着它可能拥有操作数据库、发送邮件甚至执行系统命令的能力。必须实施严格的安全措施。输入校验与净化对所有用户输入进行严格的校验防止Prompt注入攻击。例如用户输入中如果包含“忽略之前的指令”等可能劫持Agent行为的文本应进行过滤或特殊处理。工具权限沙箱不是所有用户都能使用所有工具。可以在AgentExecutionService中根据conversationId关联的userId查询用户权限动态决定本次执行可以加载哪些工具到Graph中。或者在工具执行方法内部进行权限校验。输出内容过滤对LLM生成的内容进行安全审查过滤不当言论、敏感信息或虚假内容。可以集成内容安全API或者在最终输出前进行一层规则匹配。5. 从Demo到上线持续迭代与效果评估将第一个版本的Agent部署到预生产环境后工作才刚刚开始。你需要建立一个闭环持续监控、评估和优化它。5.1 建立效果评估体系如何判断你的Agent是“智能”还是“智障”需要定义一些可量化的指标任务完成率用户明确意图的任务如查订单、退换货中有多少被成功解决用户满意度通过对话结束后的评分如1-5星或情感分析来收集。平均对话轮次解决一个问题平均需要多少轮对话轮次越少通常效率越高。人工接管率有多少对话最终需要转接给真人客服这个比率需要持续降低。建立一个简单的评估后台定期如每天抽样一些对话日志由运营或产品同学进行标注成功/失败、原因分类。这些数据将成为优化Agent的最宝贵输入。5.2 基于反馈的迭代优化根据评估结果和用户反馈优化路径通常是Prompt工程优化如果Agent经常误解意图优化你的路由节点或初始Prompt的指令。让系统指令更清晰提供更丰富的示例Few-shot。工具增强如果用户常问某个问题但现有工具无法回答考虑开发新工具。例如用户总问“我的积分有多少”那就开发一个queryUserPoints工具。Graph流程调整如果某个决策分支总是走错调整Graph中边的判断条件。或者增加一个“澄清”节点在意图模糊时主动询问用户。模型调优或切换如果生成的内容质量不佳可以尝试调整温度temperature、top_p等参数或者在成本允许的情况下切换为更强大的模型。5.3 A/B测试与渐进式发布对于重大的Agent逻辑修改比如全新的Graph设计不要直接全量发布。采用A/B测试策略将一小部分流量比如5%导向新版本的AgentB组。对比B组和原有版本A组的核心指标任务完成率、用户满意度等。如果B组数据显著优于A组再逐步扩大流量比例直至全量。Spring Cloud Gateway或你的API网关可以很方便地实现这种基于用户ID或请求比例的流量染色和路由。6. 回顾与心路Java程序员的AI Agent实践感悟走完从零到一的整个流程再回头看用Spring AI Alibaba开发AI Agent本质上是一场“传统后端工程思维”与“新兴AI应用范式”的碰撞与融合。我们并没有去发明新的轮子而是用Java和Spring最擅长的方式——分层、抽象、依赖注入、外部化配置、声明式事务——去管理和驯服AI能力。最大的体会是“生产级”三个字重不在“AI”而在“生产”。它考验的是你如何将一项不稳定、有延迟、结果不确定的新技术LLM封装成一个稳定、可观测、可运维、可扩展的标准化服务。Redis解决了状态持久化Graph解决了流程可视化与可控Spring的生态解决了配置、监控、安全等一系列脏活累活。在这个过程中Spring AI Alibaba 1.1.2.0扮演了一个优秀的“粘合剂”和“脚手架”角色。它可能没有LangChain那么庞大的工具生态但它提供了最符合Java开发者直觉的编程模型。你不需要去学习一套全新的、充满动态类型和魔术方法的框架你还是在写你熟悉的Service、Bean和Autowired只不过这些Bean现在能和大模型对话了。最后给想尝试的Java同行几个实在的建议从小场景切入比如先做一个自动生成周报总结的Agent而不是一上来就挑战全自动客服。拥抱迭代第一个版本肯定很笨没关系建立反馈闭环慢慢调。基础设施先行在写第一行Agent业务代码前先把日志、监控、配置中心搭好这会让你后续的调试和运维轻松十倍。AI Agent的开发是一场马拉松不是百米冲刺。用你最熟悉的Java和Spring一步一个脚印把每个生产环节的坑都踩实你就能构建出真正可靠、有用的智能体。这条路我已经跑通了第一部分希望这篇实录能成为你起点处的一块路标。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价