首页 / 文章

利用Spring AI将AI代理添加到Spring Boot微服务中。

使用Spring AI将AI代理添加到Spring Boot服务中:将工具作为Bean使用,在现有代码中实现断路器、令牌指标以及异步代理循环。

发布者:REACTAPP.TOP阅读约 17 分钟3863 词

来源: How to Integrate AI Agents Into Spring Boot Microservices | 2026 Java AI Guide

想象这样一种场景:有三个已经承担生产流量的Spring Boot微服务,需要六周时间为其添加智能代理功能,其中两个成功部署,而第三个则演变成一场漫长且代价高昂的教训。这一情景反映了众多Java团队所经历的情况,也为后续内容奠定了背景。本文旨在为熟悉Spring Boot的Java后端工程师提供实用指导,帮助他们了解如何将智能代理流程集成到那些存在延迟限制、涉及多个依赖团队以及服务真实用户的现有服务中,同时还能在不破坏现有架构的前提下实现AI功能。

接下来的章节将介绍在Spring Boot环境中AI智能代理的真正含义与局限性、各团队采用它们的原因、如何使用Spring AI实现工具调用、通常在正式上线后才出现的三种故障模式,以及为何应将令牌消耗视为一项重要的运营指标。

什么是智能体,什么不是

在编写任何代码之前,必须先确立正确的思维模型。那些在 Java 中实现智能体集成时遇到困难的工程师,并非被框架所限制,而是使用了错误的模型。

智能体并非简单的 API 调用,而是一个推理循环:语言模型接收任务,决定调用哪些工具,获取结果后,结合已掌握的所有信息进行推理,最终要么给出答案,要么进入下一轮循环。

这个循环由四个要素构成:

  • 模型即推理引擎,可以是 GPT-4o、Claude、Gemini,或是通过 Ollama 提供的开源模型。
  • 工具则是模型被允许调用的函数、API 或服务。
  • 循环是指将智能体与单次LLM调用分隔开的多轮迭代过程。
  • 上下文则是不断累积的对话历史、工具输出及指令,这些内容为每一轮推理提供输入。
  • 在Spring Boot微服务中,这意味着你无需在AI接口上额外添加类似RestTemplate的封装层。你需要将多步骤、有状态且非确定性的工作流嵌入到原本为有限、同步且可预测的执行而设计的服务中。正是这种不匹配导致了大多数Java AI集成出现问题。一个简单的记忆方法:工具并非智能体,循环才是;只要不明白这一点,你最终构建的只会是接口而非智能体。

    在Spring Boot服务中采用智能体的理由

    教程往往跳过这一部分。它们只展示如何集成智能体,却很少解释企业为何应该这么做、它能解决什么问题,或是过早采用会带来什么代价。

    超越固定规则

    传统的微服务用于自动化已知的流程,即输入A经过规则集B处理后产生输出C,这种模式非常适合确定性流程。而智能体则将这一理念扩展到那些步骤复杂、且下一步操作需依据中间结果来确定的任务中。以下是一些适用于现有Spring Boot架构的示例:

    • 订单处理。一个智能体可在单个请求中同时检查库存、评估定价规则、应用针对客户的折扣,并给出发货建议,这样前端就不必依次发起四次调用。
    • 文档理解。智能体可读取上传的PDF文件,提取结构化字段,根据业务规则对这些字段进行验证,然后确认结果或将案例转交给人工审核员,且无需固定的提取规则。
    • 客户支持。在现有的支持服务中,智能体能够获取账户状态、订单历史及政策文件信息,从而撰写符合上下文的准确回复,这样既缩短了处理时间,又不会让人工完全脱离处理流程。
    • 代码审查与持续集成辅助。在平台工程流程中,智能体可分析失败的测试用例,查阅内部文档,并提出针对性的修复方案。

    在每种情况下,其值都不是直接的“AI”。它用能够根据实际输入情况进行调整的推理层取代了脆弱的手动编排逻辑,从而避免在输入偏离预期路径时出现故障。

    为何 Spring Boot 适合这项工作

    Python 在 AI 教程中占据主导地位,而 Java 则主导着企业级生产系统,两者之间的差距正是巨大的机遇所在。Spring Boot 拥有轻量级 Python 框架无法直接提供的结构优势:

    • 依赖注入功能可让工具类Bean得到妥善管理、便于测试,并且无需额外配置即可实现组合使用。
    • Actuator 和 Micrometer可将令牌使用情况、代理轮询延迟以及工具错误率转化为常规的运营指标。
  • 异步原语如@Async和CompletableFuture能够很好地处理运行时间不固定的代理循环。
  • Resilience4j通过电路断路器和重试机制封装工具调用,防止故障级联。
  • Spring Security使得面向代理的工具端点的身份验证与授权功能能够与现有的访问控制体系集成。
  • 成熟的运维工具意味着像Prometheus、Grafana或Datadog这样的现有工具栈无需新增基础设施即可扩展用于收集代理指标。
  • 除此之外,Spring AI在同一生态系统中提供了LLM客户端、向量存储集成以及基于@Tool注解的函数调用功能,因此构建生产级代理无需脱离Spring的工作方式。

    何时应该使用代理

    代理在以下情况下能够发挥作用:

    • 工作流需要根据执行过程中发现的信息来做出决策,而非仅依据请求到达时已有的信息。
    • 当前的实现方式要求前端或协调服务来管理多个后端调用,而代理可以在服务器端承担这一协调工作。
    • 输入为结构可变的自然语言,分类、提取、总结或路由等功能由内容本身决定,而非依赖预定义的架构。
    • 现有系统会以“结果错误但不会崩溃”的方式出问题,此时可以设计代理来揭示不确定性,而非默默返回最可能的错误答案。

    当工作流程完全确定、输入数据结构化且规则逻辑易于维护时,使用智能体反而不合适。智能体会增加复杂性,只有当其他方案更差时才值得为此付出代价。

    用通俗的话解释商业理由

    对于工程负责人和产品相关方而言,这一理由主要基于三个方面:

    1. 更快地实现新功能。过去需要数周时间编写后端协调代码的功能,如今在智能体处理多步骤协调后往往几天就能完成原型开发。团队在首次成功集成后通常会报告这种速度提升,不过具体数值会有所不同。
    2. 更不易出错的规则维护。服务中的每条硬编码规则都是一种债务,当业务逻辑发生变化时这种债务会不断增加,而用自然语言编写的指令通常比用命令式代码表达相同逻辑更容易更新。
    3. 复合型人工智能系统的基础。一个集成良好的智能体是多智能体工作流、检索增强生成流程以及人工智能原生产品功能的基础。

    为何如此紧迫,以及执行模式的转变

    在2023年及2024年期间,Java中的人工智能功能基本上仅限于向大语言模型接口发送请求并获取返回的文本:通过WebClient实现简单的HTTP请求,设置超时时间后便不再做进一步处理。

    那种模型已不再适用。Spring AI的功能集(工具与函数调用、向量存储集成,以及专为代理工作流设计的ChatClient API)表明执行模型本身已经发生了变化。

    • 旧模型是单个请求对应一次LLM调用并产生一个响应。
    • 新模型则是单个请求会分解为N次工具调用和M个推理步骤,最终才产生一个响应。

    仍有许多Spring Boot工程师沿用旧模型,将ChatClient当作RestTemplate来使用。这种分类错误会在系统上线数周后引发生产问题,而这些问题出现时往往并不明显。

    循环即代理

    初次接触智能体集成的工程师通常会设想用一个服务来替代控制器:一个@Service Bean,注入一个ChatClient,就大功告成了。那其实只是个自动完成接口,而非真正的智能体。真正的智能体会不断循环执行接收任务、选择工具、执行工具、接收结果、对新状态进行推理,然后再决定是继续处理还是给出回复。这个循环中的推理引擎就是模型,而整个循环本身就是智能体。

    为何常规设计会高估成本

    人们普遍认为,调用大语言模型才是昂贵且复杂的部分,剩下的工作可以由Spring的@Service包装器来处理。但实际上,“剩下的工作”如今已变得分布式:工具的响应速度、上下文在多轮对话中的状态变化、错误在多步骤流程中的传播方式,以及需要处理未完成工作的重试机制,都影响着整体成本。

    设想一个假设的订单分类服务。它最初只有一个 POST /orders/classify 接口,通过一次大型语言模型调用来完成任务。后来该服务被重构为一个智能体,它能对订单进行分类,调用库存管理工具,根据库存情况调用定价工具,最终给出配送建议。每一次工具调用都是真实的业务请求,存在实际的延迟。

    假设对于80%的请求,该智能体能在两秒内完成处理;而对于剩余的20%请求,由于上游的库存服务响应缓慢,处理时间会延长到8到14秒。这并非人工智能的问题,而是智能体循环设计所暴露出的分布式系统问题。

    常被忽视的权衡

    智能体循环并非无状态的。每个工具的运行结果都会被添加到对话中,并在下一轮再次发送给模型。在处理速度较高的情况下,每次请求所需的内存占用会随着智能体处理的轮次增加而上升,而原始请求数量的影响则相对较小。一个每分钟处理1,000次请求、平均每轮处理4次请求的服务,实际上相当于进行了4,000次大语言模型调用,且上下文大小还会持续累积。

    因此在编写任何智能体代码之前,应先列出所有的工具调用,估算每个调用的p95延迟时间,并计算在最大轮次数下的最坏情况上下文大小。在设计循环结构之前就要先规划好上下文容量。在Java后端中,大多数智能体故障实际上都是伪装成模型故障的超时故障。

    Spring AI提供的是实现框架,而非完整方案

    Spring AI是一个严肃的、面向生产环境的抽象层。它提供了ChatClient、ToolCallback、VectorStore以及代理执行API,支持依赖注入、通过application.properties切换模型,还能通过Flux实现响应式处理。作为在Spring Boot中集成大语言模型的起点,它无疑是最佳选择。

    但它并未提供代理流水线的实现策略,而这正是原型与生产级服务之间的差距所在。该框架仅负责管理单个工具调用和模型交互,无法处理以下内容:

    • 代理与下游服务之间的服务边界契约。
    • 跨请求的代理状态持久化,即会话内存管理。
    • 针对涉及多步骤故障的断路器逻辑。
  • 工作流层面的可观测性,例如了解智能体为何选择特定的工具序列。
  • 编写工具故障矩阵

    在正式上线之前,需为每个@Tool方法定义三种情况下的处理方式:

    • 工具返回预期输出。智能体将进入下一个推理步骤。
    • 工具返回错误。智能体接收到错误信号后,需决定是重试、使用备用方案还是通知用户。
    • 工具超时。此时会触发断路器机制,返回备用值,并告知智能体数据不可用。

    如果任何工具的第二种和第三种情况均未定义,说明集成工作尚未完成。正常流程并非服务本身。Spring AI负责处理具体实现方式,而原因、时机以及故障时的应对策略仍需由您自行决定。

    上下文窗口如同未书面的契约

    在普通的 Spring Boot 服务中,违反契约会引发异常。而在智能体中,超出上下文窗口限制不会产生异常、警报或错误日志。模型会悄悄丢弃对话中最旧的内容,而那些内容往往包含着业务规则的系统提示。当违反 REST API 的契约时,它会返回 400 错误码;而上下文窗口则只是开始遗忘内容,这种错误更难被发现。

    一个示例事件

    想象一下这样一个文档处理服务,其系统提示语长度约为800个标记。在测试中,该工具每次响应的平均长度约为1,600个标记,远在模型的处理范围内。但在实际应用中,某个特定的工作流会触发三次带有大量数据的工具调用,其中就包括一个长度约为1,800个标记的嵌套订单历史响应。总体上下文长度超过了6,000个标记,导致系统提示语被悄悄截断,而智能体给出的答案虽然看似合理,却遗漏了重要的业务规则。

    既没有异常抛出,也没有警报触发。直到三天后的每周审计时,这个漏洞才被发现。

    解决方案:从一开始就关注标记数量指标

    在部署之前需监控模型上下文使用情况。使用 Micrometer 时,需从响应元数据的 usage 对象中读取提示词数量和生成令牌数量,并以指标的形式展示出来,例如 agent.context.input_tokens 和 agent.context.output_tokens 这样的名称。不同版本的 Spring AI 其访问器名称有所差异,因此请查看所使用版本的 usage API 文档。当上下文使用量达到模型上限的 80% 左右时应当发出警报。这通常需要两个 Bean 以及一个配置属性,且必须在正式上线前完成设置。如果部署的智能体没有令牌使用指标,就如同运行数据库却未设置查询时间监控,最终会导致系统在凌晨 2 点停止运行。

    逐步构建

    前提条件:Spring Boot 3.2 或更高版本,Spring AI 1.0 或更高版本以及对应模型提供方的启动器(本例中为 OpenAI 启动器),对 @Async 和 CompletableFuture 的熟练掌握,一个可作为工具暴露的内部服务,以及已集成到可观测性框架中的 Micrometer。不同版本的 Spring AI 中,启动器文件的名称会有所变化,因此请在参考文档中确认当前使用的版本。

    步骤 1:添加依赖项

    在 pom.xml 中,为对应的服务添加 Spring AI 启动器,如果需要为工具调用启用断路器功能,则还需添加 resilience4j-spring-boot3 组件。模型凭证应存放在 application.properties 中。通过 spring.ai.openai.api-key 属性从 OPENAI_API_KEY 等环境变量中读取 API 密钥,选择聊天模型(示例中使用的是 gpt-4o),并将温度值设定在较低的水平,约为 0.2,这样才能保证工具使用的稳定性。

    步骤 2:在配置智能体之前定义工具

    顺序很重要,因为工具的设计决定了智能体的行为。系统提示语应明确提及具体的工具功能,如果在工具尚未创建时就编写提示语,会导致指令模糊,从而使模型产生困惑。

    在示例中,OrderTools组件通过其构造函数接收一个InventoryService和一个PricingService,并公开了两个带有注解的方法。checkInventory(String sku)会返回可用数量,若SKU未知则返回-1。getPrice(String sku, String customerTier)则返回以美分表示的价格。每个方法都带有包含描述信息的@Tool注解。

    常见的错误是让这些描述过于模糊。模型会读取这些描述来决定何时调用相应的工具,因此准确且完整的描述能减少不必要的调用并提升答案的准确性。应像上面的示例那样,明确写出单位、返回值以及未找到时的处理方式。

    步骤3:为ChatClient提供简短的系统提示

    AgentConfig 类通过注入的构建器以及 OrderTools 来定义 ChatClient 对象。默认的系统提示语简短且带有指令性:充当订单处理助手,确认库存可用性前必须先检查库存,报价前必须先获取价格信息,需返回一个 JSON 对象,其中包含一个表示是否可用的布尔值、以美分表示的整数 price 以及 recommendation 字符串,绝不能虚构库存数量或价格。这些工具会被注册到 defaultTools 中。

    在这里,对令牌数量的控制至关重要。系统提示语中的每个令牌都会减少用于工具结果的容量,因此请尽量将令牌数控制在300以下,并毫不留情地进行优化。

    第4步:异步运行智能体并记录令牌使用情况

    OrderAgentService负责管理ChatClient和MeterRegistry。其processOrder方法会返回一个CompletableFuture<String>,并在supplyAsync中执行代理调用:首先发送用户输入,调用模型,获取聊天响应,读取输入和输出的token数量并将其记录为指标,最后返回结果文本。

    为何要使用异步?代理的运行时间取决于工具调用的次数,而非请求的表面复杂度。在繁忙的服务中,如果同步控制器线程被阻塞8到14秒,就会耗尽线程池。从一开始就应将代理执行视为后台任务来处理。

    第5步:为每个工具添加断路器

    使用 Resilience4j 的 @CircuitBreaker 注解为每个工具方法添加保护,需指定断路器名称(如 inventoryTool)以及备用方法。库存工具的备用方法会记录包含 SKU 的警告信息并返回 -1,以此告知代理库存数据不可用。阈值应设置在 application.properties 中:10 次调用的滑动窗口、50% 的失败率阈值,以及断路器处于开启状态时的 10 秒等待时间。

    如果断路器似乎从未触发,需记住 Spring AOP 仅在调用通过 Spring 代理时才会起作用。请正确注入工具组件,切勿直接实例化它们。

    步骤 6:创建采用异步响应模式的接口

    映射到 /agent 的 OrderAgentController 会接收发往 /orders 的 POST 请求,生成一个请求 ID,启动对应的代理进程,并将该请求的未来状态存储在并发映射结构中,随后返回带有该 ID 的 202 Accepted 响应。对 /orders/{requestId}/result 的 GET 请求会查询该请求的未来状态:若 ID 未知则返回 404,若请求仍在处理中则返回带有“Processing”提示的 202 响应,处理完成后返回包含结果的 200 响应,而执行失败时则返回带有错误信息的 500 响应。在演示之外,应使用能够在重启后依然存在且能自动删除过期条目的存储机制来替代内存中的映射结构。

    决策分支适用于工具本身。对于延迟较低的内部服务调用,应保持工具同步,并在服务层处理异步操作;而对于延迟不可预测的外部 API,则需让工具以异步方式运行,配备专用线程池和严格的超时设置,这样单个处理缓慢的依赖项就不会导致无限制的延迟影响代理的轮次预算。

    值得了解的工具

    • Spring AI 是 Spring Boot 的核心代理框架。
    • LangChain4j 是一种原生 Java 的替代方案,常被认为在多代理协调方面表现更佳。
    • Resilience4j 可为工具调用提供断路器与重试功能。
    • Micrometer 负责统计令牌使用情况以及代理的各类指标。
    • Ollama 可在本地运行开源模型,用于开发和测试。

    会浪费时间并损害可信度的三个错误

    初学者的陷阱:仅使用理想路径的输入进行测试。 教程中使用的是干净且易于处理的测试数据,但真实用户提交的输入往往处理时间较长、含义模糊,或者会引发仅在生产环境中才会出现的异常工具行为。应在每次发布前,使用匿名化的真实生产请求样本(而非合成数据)在测试环境中对相关组件进行测试,至少要使用超出预期上下文限制的恶意输入来进行测试。

    专家的盲点:在产品发布前过度设计。那些深谙多智能体相关理论的工程师,有时会在单个智能体甚至还未接触到真实用户之前,就先设计出协调器、子智能体、记忆层以及RAG处理流程。这就好比在验证单体架构之前就先构建服务网格,属于“复合型AI”领域的类似错误。应先在一个服务中部署一个智能体及其配套工具,对其进行测试,只有在数据需求出现时才增加复杂性。那些能最快将产品投入使用的团队,都避免在还没有产品时就着手设计平台架构。

    系统性缺陷:无人对智能体的行为负责。常规故障只会留下日志记录、指标数据及警报信息。而那些看似合理但实际上错误的智能体回复则不会留下任何痕迹:既没有异常提示,也没有警报,只有通过结构验证却违反业务逻辑的回复。应当为智能体的输出指定责任人,而不仅仅是判断其是否做出了响应;同时应每周审查一部分样本回复,而不仅仅是关注错误率。这更多是组织层面的决策,而非技术问题。

    入门指南未提及的内容

    据多数人所说,Spring Boot智能体集成中最困难的部分并非人工智能本身。模型行为、提示工程以及工具调用接口大约一周时间就能掌握。真正的难点在于Java的同步处理机制与智能体执行过程中的异步、多轮交互特性之间的冲突。

    Spring Boot的请求-响应模型假定执行时间是有上限的,而智能体并非如此。它会根据任务需求进行尽可能多的工具调用,并执行必要的推理步骤,完全不受30秒网关超时限制的影响。智能体流水线并不适合请求-响应模式,而更适合事件驱动模式;越早接受这一事实,集成就能越快完成。

    那些进展最慢的团队试图让整个处理过程保持同步,直到收到智能体的回复。而那些能最快完成整合的团队则将智能体作为后台任务运行,按需启动它,通过轮询或WebSocket传递结果,并据此设计服务契约。Spring AI的入门资料中缺少这种思维方式的阐述,不过或许也不必包含。

    需要延续的思维模式

    以上内容可概括为一句话:AI智能体是一种内部拥有非确定性执行引擎的分布式系统。

    Java工程师已熟悉的分布式系统原则(隔离故障、观察行为、限制执行时间、明确故障契约)在这里同样适用,且作用更为重要,因为这里的故障更为隐蔽,出现时间也更长。令人欣慰的是,Spring Boot非常适合这项工作。依赖注入、基于Actuator的可观测性功能、异步处理机制以及成熟的运维生态系统都是巨大优势,而Spring AI也是一个相当成熟的框架。问题在于思维模式,而非工具本身。

    实际操作规则:

    • 将智能体视为一个推理循环,并围绕该循环而非模型调用来设计服务契约。
    • 当工作流依赖于执行过程中的信息,或现有编排机制过于脆弱且难以维护时,应采用智能体。而如果工作流是确定性的且基于规则的版本能够正常运行,则无需使用智能体。
    • Spring AI提供框架,而你需要自行制定工具故障处理、上下文资源管理以及可观测性相关的策略。
    • 应将上下文窗口视为契约,并在正式上线前就对令牌消耗情况进行监控,而非等到首次出现故障之后。
    • 从一开始就采用异步方式运行智能体,因为事件驱动的设计在处理请求响应式架构带来的问题时更具优势。

    常见问题

    Spring Boot中适合使用哪种框架来开发智能体? Spring AI是Spring Boot应用最合适的选项。LangChain4j也是一个不错的选择,它对多智能体协作的支持更为全面,且两者都处于活跃维护状态。

    现有的微服务必须重写吗?不必。该方法通过 Spring AI 的 @Tool 注解、ChatClient Bean 以及 @Async 执行方式来扩展服务,而不会改动现有的服务逻辑。

    在生产环境中应如何处理智能体故障?需将每个 @Tool 方法封装在 Resilience4j 的断路器中,通过 Micrometer 监控令牌使用情况,在正式上线前定义智能体故障处理矩阵,并指定专人负责评估响应质量而不仅仅是错误率。

    智能体与普通的 LLM 调用有何不同?普通调用仅包含一个请求和一个响应,而智能体则是一个循环结构,模型可以在其中多次调用工具、读取结果、进行分析,进而决定是继续操作还是给出最终答案。

    Spring Boot 能够应对生产环境中的智能体工作负载吗?只要做出正确的架构选择,答案是肯定的。通过现有的稳定库,即可实现异步执行、断路器机制、令牌度量以及显式的上下文窗口管理等功能。

    RAG 的作用是什么? RAG 只是众多工具模式中的一种:智能体会从向量存储中检索相关文档,以丰富其推理前的上下文信息。当智能体需要庞大的知识库时,这种机制能提供帮助,但并非所有集成场景都必需,而且只有在上下文窗口限制开始引发实际问题时才建议引入,以避免增加不必要的复杂性。