当前位置: 首页 > news >正文

AI Agent 开发实战(六):用 Spring AI 搭建你的第一个 Agent

AI Agent 开发实战(六):用 Spring AI 搭建你的第一个 Agent

这是「AI Agent 开发实战」系列的第 6 篇。前 5 篇我们聊了概念、LLM、记忆、工具和框架横评,今天终于要动手了——用 Spring AI 从零搭建一个能调用工具的 Agent,跑通 ReAct 循环。

读完本文,你会得到一个可运行的 Spring AI Agent 项目,它能让 LLM 自主决定何时调用工具、何时直接回答。


一、Spring AI 是什么,不是什么

先对齐预期:

维度Spring AILangChain4j
定位Spring 官方 AI 抽象层Java 版 LangChain
风格Spring Boot 自动配置 + Bean 注入Builder 模式 + 链式调用
上手成本Spring 开发者几乎零成本需要学一套新 API
灵活性通过 Adapter 扩展内置组件丰富
生态Spring 全家桶天然集成多框架适配

Spring AI 的核心理念:把 LLM 当作 Spring 生态的一等公民——像用JdbcTemplate访问数据库一样用ChatClient访问大模型。

Spring AI 架构全景 │ ├── Model 层 │ ├── ChatModel(对话模型抽象) │ ├── EmbeddingModel(向量模型抽象) │ ├── ImageModel(图像模型抽象) │ └── AudioModel(语音模型抽象) │ ├── API 层(各厂商适配器) │ ├── OpenAI / Azure OpenAI │ ├── Anthropic Claude │ ├── 阿里通义 / 百度文心 / 智谱 │ └── Ollama(本地模型) │ ├── 功能层 │ ├── ChatClient(统一调用入口) │ ├── Function Calling(工具调用) │ ├── RAG(检索增强) │ ├── Advisors(拦截器链) │ └── Memory(对话记忆) │ └── 集成层 ├── Vector Store(向量存储) ├── Spring Boot AutoConfiguration └── Observability(Micrometer + Zipkin)

二、项目搭建:5 分钟跑起来

2.1 创建项目

使用 Spring Initializr(https://start.spring.io),选:

  • Java 17+
  • Spring Boot 3.3+
  • 依赖:Spring Web + Spring AI

pom.xml关键依赖:

<dependencyManagement><dependencies><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-bom</artifactId><version>1.0.0</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><!-- Spring AI 核心 --><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai-spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies>

2.2 配置模型

application.yml

spring:ai:openai:api-key:${OPENAI_API_KEY}base-url:https://api.openai.com# 或你的代理地址chat:options:model:gpt-4o-minitemperature:0.7

如果用国内模型,换对应的 starter。比如用智谱:spring-ai-zhipuai-spring-boot-starter,配置spring.ai.zhipuai.api-key

2.3 验证连通

@RestController@RequestMapping("/api/agent")publicclassAgentController{privatefinalChatClientchatClient;publicAgentController(ChatClient.Builderbuilder){this.chatClient=builder.build();}@GetMapping("/chat")publicStringchat(@RequestParamStringmessage){returnchatClient.prompt().user(message).call().content();}}

启动后访问http://localhost:8080/api/agent/chat?message=你好,能返回回复就说明通了。


三、给 Agent 加上"手脚":Function Calling

没有工具的 Agent 只是个 Chatbot。Spring AI 通过@Tool注解(或函数式注册)让 LLM 能调用你的 Java 方法。

3.1 定义工具

@ComponentpublicclassWeatherTools{@Tool(description="查询指定城市的当前天气信息,返回温度、天气状况和风力")publicStringgetWeather(@ToolParam(description="城市名称,如:北京、上海")Stringcity){// 实际项目中调用天气 API// 这里模拟返回Map<String,String>mockData=Map.of("北京","晴天,温度 28°C,北风 3 级","上海","多云,温度 31°C,东南风 2 级","深圳","雷阵雨,温度 33°C,南风 4 级");returnmockData.getOrDefault(city,city+":暂无天气数据");}@Tool(description="计算两个城市之间的距离(单位:公里)")publicStringgetDistance(@ToolParam(description="出发城市")StringfromCity,@ToolParam(description="目的城市")StringtoCity){// 模拟距离计算returnfromCity+" 到 "+toCity+" 约 1200 公里";}}

3.2 注册工具并调用

Spring AI 1.0 的方式——在 ChatClient 调用时指定工具:

@RestController@RequestMapping("/api/agent")publicclassAgentController{privatefinalChatClientchatClient;privatefinalWeatherToolsweatherTools;publicAgentController(ChatClient.Builderbuilder,WeatherToolsweatherTools){this.chatClient=builder.build();this.weatherTools=weatherTools;}@GetMapping("/chat")publicStringchat(@RequestParamStringmessage){returnchatClient.prompt().user(message).tools(weatherTools)// 注册工具.call().content();}}

3.3 测试

用户:北京今天天气怎么样?适合出门吗? LLM 思考 → 需要查询天气 → 调用 getWeather("北京") → 拿到结果:"晴天,温度 28°C,北风 3 级" → 综合回答:"北京今天晴天,28°C,北风3级,非常适合出门!"

关键点:你不需要写任何 if-else 来判断"用户问的是不是天气"。LLM 自己根据工具描述决定调不调、传什么参数。这就是 Agent 的"自主"。


四、加记忆:让 Agent 记住上下文

上面的 Agent 每次对话都是失忆的。加上对话记忆:

4.1 对话记忆(Chat Memory)

@ConfigurationpublicclassAgentConfig{@BeanpublicChatClientchatClient(ChatClient.Builderbuilder){returnbuilder.defaultAdvisors(MessageChatMemoryAdvisor.builder(newInMemoryChatMemory()).build()).build();}}

4.2 区分会话

多用户场景下,需要按会话 ID 隔离记忆:

@GetMapping("/chat")publicStringchat(@RequestParamStringmessage,@RequestParam(defaultValue="default")StringsessionId){returnchatClient.prompt().user(message).advisors(a->a.param(CHAT_MEMORY_CONVERSATION_ID_KEY,sessionId)).tools(weatherTools).call().content();}

记忆内部结构:

InMemoryChatMemory │ ├── session "user-001" │ ├── [UserMessage] "北京天气怎么样?" │ ├── [AssistantMessage] "北京今天晴天,28°C..." │ └── [UserMessage] "那上海呢?" │ ├── session "user-002" │ └── [UserMessage] "深圳到北京多远?" │ └── ...

五、完整 Agent 示例:出行助手

把工具、记忆、提示词模板组合起来,搭一个"出行助手" Agent:

5.1 Agent 服务层

@ServicepublicclassTravelAssistant{privatefinalChatClientchatClient;privatefinalWeatherToolsweatherTools;publicTravelAssistant(ChatClient.Builderbuilder,WeatherToolsweatherTools){this.weatherTools=weatherTools;this.chatClient=builder.defaultSystem(""" 你是一个出行助手。用户会问你天气、距离等出行相关问题。 你需要根据问题自主决定调用哪些工具,综合信息后给出建议。 回答时: 1. 先给出事实数据(天气、距离等) 2. 再给出出行建议(穿什么、带伞、交通方式等) 3. 语气友好,像一个懂行的朋友 """).defaultAdvisors(MessageChatMemoryAdvisor.builder(newInMemoryChatMemory()).build()).build();}publicStringchat(StringsessionId,StringuserMessage){returnchatClient.prompt().user(userMessage).advisors(a->a.param(MessageChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY,sessionId)).tools(weatherTools).call().content();}}

5.2 多轮对话效果

第 1 轮: 用户:我明天要从北京去上海出差,帮我看看两边的天气 Agent → 调用 getWeather("北京") + getWeather("北京") Agent:北京明天晴天28°C,上海多云31°C。建议北京穿薄外套,上海比较闷热穿短袖即可,不用带伞。 第 2 轮: 用户:北京到上海多远? Agent → 调用 getDistance("北京", "上海") Agent:北京到上海约1200公里。高铁大约4.5小时,飞机约2小时。出差的话高铁更稳当,不受天气影响。 第 3 轮: 用户:那我还是坐高铁吧 Agent:好选择!高铁准点率高,而且市中心到市中心,比飞机省了机场往返时间。记得提前买票,热门时段容易售罄。

注意第 3 轮——Agent 没有调用任何工具,纯靠记忆上下文就给出了合理建议。这就是 LLM 的自主决策:它知道什么时候需要工具、什么时候不需要。


六、Advisor 机制:Agent 的中间件

Spring AI 的 Advisor 机制类似 Servlet Filter,可以在 LLM 调用的前后插入逻辑:

用户请求 │ ▼ Advisor 链 │ ├── 1. MessageChatMemoryAdvisor(注入对话记忆) ├── 2. 你的自定义 Advisor(日志/限流/兜底) ├── ... │ ▼ ChatModel.call() → LLM API │ ▼ Advisor 链(逆向) │ ├── 2. 你的自定义 Advisor(处理响应) ├── 1. MessageChatMemoryAdvisor(保存本轮对话到记忆) │ ▼ 返回给用户

自定义 Advisor 示例:调用日志

publicclassLoggingAdvisorimplementsCallAroundAdvisor{@OverridepublicStringgetName(){return"LoggingAdvisor";}@OverridepublicAdvisedResponsearoundCall(AdvisedRequestrequest,CallAroundAdvisorChainchain){longstart=System.currentTimeMillis();// 打印用户输入log.info("用户输入: {}",request.userText());// 继续调用链AdvisedResponseresponse=chain.nextAroundCall(request);// 打印 LLM 输出和耗时longcost=System.currentTimeMillis()-start;log.info("LLM 输出: {}, 耗时: {}ms",response.response().getResult().getOutput().getText(),cost);returnresponse;}}

注册:

@BeanpublicChatClientchatClient(ChatClient.Builderbuilder){returnbuilder.defaultAdvisors(newLoggingAdvisor(),MessageChatMemoryAdvisor.builder(newInMemoryChatMemory()).build()).build();}

七、生产环境的注意事项

7.1 超时与重试

@BeanpublicChatClientchatClient(ChatClient.Builderbuilder,WeatherToolsweatherTools){returnbuilder.defaultOptions(ChatOptionsBuilder.builder().withMaxTokens(2048).build()).build();}// 外层用 Spring Retry 包裹@Retryable(maxAttempts=3,backoff=@Backoff(delay=1000))publicStringchat(StringsessionId,Stringmessage){// ...}

7.2 工具调用的安全边界

@ComponentpublicclassSafeWeatherTools{privatestaticfinalSet<String>ALLOWED_CITIES=Set.of("北京","上海","深圳","广州");@Tool(description="查询指定城市的当前天气")publicStringgetWeather(@ToolParam(description="城市名称")Stringcity){// 防止 LLM 幻觉出非法城市名if(!ALLOWED_CITIES.contains(city)){return"不支持查询该城市的天气";}returndoQueryWeather(city);}privateStringdoQueryWeather(Stringcity){// 调用真实 APIreturn"...";}}

7.3 记忆持久化

InMemoryChatMemory重启就丢。生产环境用持久化实现:

// 基于 Redis 的记忆存储publicclassRedisChatMemoryimplementsChatMemory{privatefinalRedisTemplate<String,String>redisTemplate;@Overridepublicvoidadd(StringconversationId,List<Message>messages){Stringkey="chat:memory:"+conversationId;// 序列化并存储,设置过期时间messages.forEach(msg->redisTemplate.opsForList().rightPush(key,serialize(msg)));redisTemplate.expire(key,Duration.ofHours(24));}@OverridepublicList<Message>get(StringconversationId,intlastN){Stringkey="chat:memory:"+conversationId;// 取最近 N 条List<String>raw=redisTemplate.opsForList().range(key,-lastN,-1);returnraw.stream().map(this::deserialize).toList();}// ... 序列化/反序列化省略}

八、Spring AI vs 手写 Agent:什么时候用框架

场景推荐理由
快速验证 Agent 概念手写 MiniAgent理解原理,没有黑盒
Spring 技术栈团队Spring AI零学习成本,Bean 注入
需要丰富的内置组件LangChain4jRAG 管道、文档加载等开箱即用
生产级 Agent 服务Spring AI + 自定义 Advisor既有框架支撑,又能灵活扩展
需要精细控制 Agent 行为手写 + 框架混合核心循环自己写,工具调用用框架

我的建议:先用 Spring AI 跑通一个 Agent(就是本文的做法),理解ChatClient + Tool + Advisor + Memory这四件套怎么协作。遇到框架限制再手写核心部分,不要一上来就造轮子。


九、小结

Spring AI Agent 四件套 │ ├── ChatClient → 统一调用入口,替代手写 HTTP │ ├── @Tool → 声明式工具注册,LLM 自主选择调用 │ ├── Advisor → 拦截器链,记忆/日志/限流/兜底 │ └── ChatMemory → 对话记忆,支持按会话隔离
要点说明
工具注册@Tool+@ToolParam声明式,零配置
LLM 自主决策不需要写 if-else,LLM 根据 Tool 描述自动选择
记忆隔离通过 sessionId 区分不同用户的对话上下文
Advisor 机制类似 Filter 的中间件,可组合扩展
安全边界工具方法内部做参数校验,防止 LLM 幻觉注入

下一篇我们聊Harness Engineering 与约束管理:怎么用 Plan 模板、工具注册约束、输出 Schema 让 Agent 的行为更可控——毕竟"自由"和"可控"之间需要找到一个平衡。


这是「AI Agent 开发实战」系列第 6 篇,系列目录:

  1. 别再把 LLM 当聊天机器人了,这才是 Agent 的正确打开方式
  2. 三大基石之 LLM 调用与 Prompt 工程
  3. 三大基石之记忆系统
  4. 三大基石之工具调用
  5. Java 生态 Agent 框架横评
  6. 本文:用 Spring AI 搭建你的第一个 Agent
http://www.jsqmd.com/news/1289590/

相关文章:

  • 单片机毕业设计-基于 STM32F103 的多传感器水位水温管控系统设计 基于嵌入式单片机的水箱恒温与液位分级预警系统(011801)
  • 2026年在广东做兼职人员结算咨询和落地服务的靠谱企业有哪些:爱灵活专业合规值得信赖 - 米諾
  • NLP第一阶段练习 简单词性标注
  • 南昌朋友聚餐火锅店推荐|全场景适配本地觅食实用指南 - 品牌2026推荐
  • 需求驱动的快速开发思维
  • 如何解决Docker Registry部署瓶颈?Docket分布式传输架构全解析
  • SPARTA核心插件解析:Ambisonic编码与解码的完整指南
  • 带父母吃火锅去哪?南昌本地多元火锅门店觅食指南 - 品牌2026推荐
  • Oracle性能诊断从未如此简单:TPT-Oracle脚本实战案例大全
  • 江门公司监事变更公司口碑好排名实测:江门服务机构榜单与挑选攻略 - 米諾
  • douyin-downloader:抖音内容批量下载与管理的完整解决方案
  • NIKON 4S020-140 印刷电路板
  • 宝鸡黄金回收避坑指南!全域区县实体门店,免费上门、实价实收不压价 - 淘淇黄金回收
  • 罗技鼠标宏终极指南:5分钟搞定PUBG完美压枪
  • 暗黑破坏神2存档编辑器d2s-editor:可视化修改工具全面解析与实战指南
  • 南昌团队聚餐火锅推荐|多场景适配本地火锅觅食指南 - 品牌2026推荐
  • SPARTA插件完全手册:从AmbiBIN到TrackerTest的19个实用工具
  • 2026佛山知识产权版权专利申请流程步骤,佛山优质知识产权代理商哪家专业权威? - GrowthUME
  • 英雄联盟Akari助手:终极LCU自动化工具完整指南,让你的游戏效率提升300%
  • Termux包管理完全攻略:Termux Command Handbook教你玩转pkg命令
  • 2026年寄快递想便宜必看:散户寄件低价渠道与优惠技巧大全(附寄半折实测攻略) - 快递物流资讯
  • Midscene.js终极指南:如何用自然语言重塑你的UI自动化测试体验
  • mini seq2seq模型评估:困惑度计算与翻译质量提升方法
  • ESP32 partitions csv 分区表文件完整讲解
  • 双相情感障碍的脑功能成像:连接性和网络
  • 突破上传限制:gh_mirrors/fi/file-upload断点续传原理与实现
  • 玻璃钢管道玻璃钢罐厂家山东明驰:喷衬缠绕工艺,出厂合格率≥99% 质保 1 年 - 速递信息
  • 告别龟速合成:用Matcha-TTS实现实时语音生成的技术实践
  • 专属钉钉定制开发需要多少钱?2026年费用构成方案
  • Docker 镜像与容器核心概念详解