最近在技术社区交流时,发现很多开发者对AI应用开发,特别是结合Spring AI这类框架构建智能代理(AI Agent)系统,表现出浓厚的兴趣。然而,从环境搭建、模型集成到最终部署,整个过程常会遇到各种“拦路虎”,比如依赖冲突、本地模型加载失败、代理逻辑不清晰等。本文将以一个实战项目为例,手把手带你从零构建一个具备本地模型能力的AI代理助手,涵盖Spring AI集成、Ollama本地模型调用、Agent核心逻辑设计以及前后端交互,并提供完整的可运行代码和避坑指南。无论你是想入门AI应用开发,还是希望将大模型能力集成到现有Java项目中,都能从中获得一套可直接复用的解决方案。
1. 背景与核心概念:为什么需要AI代理助手?
在深入代码之前,我们有必要厘清几个关键概念。AI技术的快速发展,尤其是大语言模型(LLM)的普及,催生了“AI代理”(AI Agent)这一重要范式。它不再是简单的问答接口,而是一个能够感知环境、进行规划、使用工具并执行任务以达成目标的自主或半自主系统。
1.1 什么是AI代理(AI Agent)?你可以将其理解为一个“数字员工”。给定一个目标(如“分析这份财报”),AI代理会自主拆解步骤:调用工具获取数据、使用模型进行分析、生成报告。其核心在于**工具使用(Tool Calling)和任务规划(Planning)**能力。这与传统的仅能完成单轮对话的Chatbot有本质区别。
1.2 Spring AI 与 本地模型Spring AI是Spring官方社区提供的项目,旨在为基于JVM的应用程序(如Spring Boot)集成人工智能功能提供便捷的抽象和接口。它统一了对接不同AI提供商(如OpenAI、Azure OpenAI、Ollama等)的方式,让开发者能以熟悉的Spring风格(如AiClient、PromptTemplate)来调用AI能力。
“本地模型”指的是在开发者自己的机器或服务器上部署运行的大模型,例如通过Ollama拉取和运行的Llama 3、Qwen等开源模型。使用本地模型的好处是数据隐私性强、无网络延迟、调用成本低(无需API费用),非常适合开发测试、内部工具或对数据安全要求高的场景。
1.3 本项目的目标我们将构建一个“AI代理助手”,它能够:
- 基础对话:回答用户的一般性问题。
- 工具调用:根据用户需求,动态选择并调用预定义的工具(如获取天气、计算器、查询数据库)。
- 本地运行:核心模型调用基于本地部署的Ollama,保障隐私和可控性。
- Web服务:提供简单的HTTP API,方便前端或其他系统集成。
接下来,我们将从环境准备开始,一步步实现这个系统。
2. 环境准备与版本说明
工欲善其事,必先利其器。以下是构建本项目所需的环境和工具清单。请务必注意版本兼容性,这是后续步骤能顺利运行的基础。
2.1 基础开发环境
- 操作系统:macOS / Linux / Windows (WSL2推荐)。本文演示环境为 macOS。
- Java:JDK 17 或 21。Spring AI 对版本有要求,推荐使用LTS版本。可通过
java -version验证。 - 构建工具:Maven 3.6+ 或 Gradle 7.x+。本文使用 Maven。
- IDE:IntelliJ IDEA(推荐)、VS Code 或 Eclipse。
2.2 核心服务:OllamaOllama 是运行本地大模型的利器。我们需要先安装并启动它。
- 安装:访问 Ollama官网 下载对应系统的安装包,或使用命令行安装(Linux/macOS)。
- 拉取模型:安装后,打开终端,拉取一个适合你电脑配置的模型。例如,拉取轻量级的
llama3.2:1b模型(约1B参数):
如果你的硬件性能较强,可以尝试ollama pull llama3.2:1bllama3.2:3b或qwen2.5:3b。 - 运行与验证:运行该模型,并测试其基础对话能力。
在出现的提示符后输入ollama run llama3.2:1bHello,看是否能得到正常回复。按Ctrl+D退出交互模式。关键点:Ollama服务默认会在http://localhost:11434启动一个API服务。我们的Spring Boot应用将通过这个地址与模型通信。
2.3 初始化Spring Boot项目使用 Spring Initializr 快速生成项目骨架。
- Project: Maven
- Language: Java
- Spring Boot: 3.2.x (确保与Spring AI版本兼容,当前推荐3.2.5+)
- Dependencies:
Spring Web(构建Web API)Spring AI(核心AI依赖)Lombok(简化代码,可选但推荐)
生成并下载项目,用IDE打开。接下来,我们需要细化pom.xml中的依赖。
3. 核心依赖与配置详解
Spring AI是一个相对较新的项目,其依赖配置是第一步,也是容易出错的一步。
3.1 完善pom.xml依赖打开pom.xml文件,确保包含以下关键依赖。特别注意Spring AI的BOM(物料清单)管理,这是统一版本、避免冲突的关键。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.5</version> <!-- 使用确定的Boot版本 --> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>ai-agent-assistant</artifactId> <version>0.0.1-SNAPSHOT</version> <name>ai-agent-assistant</name> <description>Demo project for Spring AI Agent with Local Model</description> <properties> <java.version>17</java.version> <!-- 定义Spring AI版本 --> <spring-ai.version>0.8.1</spring-ai.version> </properties> <dependencyManagement> <dependencies> <!-- 引入Spring AI BOM,管理所有Spring AI组件版本 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>${spring-ai.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- Spring Boot 基础依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring AI Ollama 集成 --> <!-- 这是连接本地Ollama服务的核心依赖 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-ollama-spring-boot-starter</artifactId> </dependency> <!-- 开发工具 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>配置解析:
dependencyManagement中引入spring-ai-bom:这是最佳实践,确保所有Spring AI相关依赖(如未来可能添加的redis、vector-store等)版本一致。spring-ai-ollama-spring-boot-starter:这个starter包封装了与Ollama API交互的客户端OllamaChatClient,开箱即用。
3.2 配置Ollama连接在src/main/resources/application.yml(或application.properties) 中配置Ollama服务地址和默认使用的模型。
# application.yml spring: ai: ollama: # Ollama服务的基础URL,默认即本地11434端口 base-url: http://localhost:11434 # 默认使用的聊天模型,需与Ollama中拉取的模型名一致 chat: options: model: llama3.2:1b # 可调节模型创造性,0.0更确定,1.0更多样 temperature: 0.7 # 可选:设置应用端口 server: port: 8080至此,基础环境与配置已完成。你可以启动Spring Boot应用 (AiAgentAssistantApplication),如果控制台没有报错,并且能看到类似OllamaChatClient initialized的日志,说明连接本地模型成功。
4. 构建AI代理助手:从基础对话到工具调用
现在进入核心开发阶段。我们将分步实现:1) 基础对话API;2) 自定义工具;3) 代理执行器。
4.1 实现基础对话控制器首先,创建一个简单的REST接口,验证Spring AI能否通过Ollama正常工作。
// 文件路径:src/main/java/com/example/aiagent/controller/ChatController.java package com.example.aiagent.controller; import org.springframework.ai.chat.client.ChatClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import lombok.RequiredArgsConstructor; @RestController @RequiredArgsConstructor public class ChatController { // Spring AI 会自动注入一个配置好的ChatClient Bean private final ChatClient chatClient; @GetMapping("/chat") public String chat(@RequestParam(value = "message", defaultValue = "Hello") String message) { // 调用ChatClient进行单轮对话 String response = chatClient.prompt() .user(message) .call() .content(); return "AI Agent 回复: " + response; } }启动应用,访问http://localhost:8080/chat?message=介绍一下你自己。如果一切正常,你将看到来自本地Llama模型的回复。这证明了基础通路是畅通的。
4.2 定义自定义工具(Tool)AI代理的“智能”很大程度上体现在它能调用哪些工具。我们来定义两个简单的工具:一个计算器和一个模拟的天气查询工具。
首先,创建一个工具类,其中的方法将被AI代理识别和调用。
// 文件路径:src/main/java/com/example/aiagent/tool/CalculatorTool.java package com.example.aiagent.tool; import org.springframework.stereotype.Component; import java.util.function.Function; @Component public class CalculatorTool implements Function<CalculatorTool.Request, CalculatorTool.Response> { // 定义工具的输入参数结构 public record Request(double a, double b, String operator) {} // 定义工具的输出结构 public record Response(double result) {} @Override public Response apply(Request request) { double result; switch (request.operator()) { case "+": result = request.a() + request.b(); break; case "-": result = request.a() - request.b(); break; case "*": result = request.a() * request.b(); break; case "/": if (request.b() == 0) throw new IllegalArgumentException("除数不能为零"); result = request.a() / request.b(); break; default: throw new IllegalArgumentException("不支持的运算符: " + request.operator()); } return new Response(result); } // 工具的描述,对于AI理解工具功能至关重要 public String getDescription() { return """ 一个简单的计算器工具,用于执行基础算术运算。 输入参数: - a: 第一个数字 (double) - b: 第二个数字 (double) - operator: 运算符,支持 '+', '-', '*', '/' 输出: 计算结果 (double) """; } }// 文件路径:src/main/java/com/example/aiagent/tool/WeatherTool.java package com.example.aiagent.tool; import org.springframework.stereotype.Component; import java.util.function.Function; @Component public class WeatherTool implements Function<WeatherTool.Request, WeatherTool.Response> { public record Request(String city) {} public record Response(String city, String weather, int temperature) {} // 模拟天气数据 private static final java.util.Map<String, Response> WEATHER_DATA = java.util.Map.of( "北京", new Response("北京", "晴", 25), "上海", new Response("上海", "多云", 28), "深圳", new Response("深圳", "阵雨", 30) ); @Override public Response apply(Request request) { // 模拟查询,实际项目中可替换为调用真实API return WEATHER_DATA.getOrDefault(request.city(), new Response(request.city(), "未知", 0)); } public String getDescription() { return """ 查询指定城市的天气情况。 输入参数: - city: 城市名称,例如 '北京'、'上海' 输出: 包含城市、天气状况和温度(摄氏度)的对象。 """; } }关键点:工具类必须是一个Spring Bean (@Component),并实现Function接口。getDescription()方法提供的清晰描述,是AI模型能否正确理解和使用该工具的关键。
4.3 配置并启用AI代理(Agent)Spring AI提供了强大的ChatClient来构建代理。我们需要将定义的工具注册进去,并配置代理的行为。
// 文件路径:src/main/java/com/example/aiagent/config/AgentConfig.java package com.example.aiagent.config; import com.example.aiagent.tool.CalculatorTool; import com.example.aiagent.tool.WeatherTool; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; import org.springframework.ai.chat.memory.InMemoryChatMemory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; @Configuration public class AgentConfig { @Bean public ChatClient aiAgent(ChatClient.Builder chatClientBuilder, CalculatorTool calculatorTool, WeatherTool weatherTool) { // 1. 构建工具列表,并转换为Spring AI可识别的格式 var tools = List.of( ChatClient.AdvisorFunctionCall.advisorFunctionTool( "calculator", calculatorTool.getDescription(), calculatorTool ), ChatClient.AdvisorFunctionCall.advisorFunctionTool( "weather", weatherTool.getDescription(), weatherTool ) ); // 2. 构建并返回一个具备工具调用和记忆能力的ChatClient (即我们的Agent) return chatClientBuilder .defaultAdvisors( // 工具调用顾问:让Agent学会在需要时使用我们注册的工具 ChatClient.AdvisorFunctionCall.builder() .functionTools(tools) .build(), // 简单日志顾问:在控制台输出交互过程,便于调试 new SimpleLoggerAdvisor(), // 记忆顾问:为对话提供短期记忆,使Agent能联系上下文 new MessageChatMemoryAdvisor(new InMemoryChatMemory()) ) .build(); } }配置解析:
ChatClient.AdvisorFunctionCall:这是实现工具调用的核心。它将我们的工具函数包装成模型能理解的格式。SimpleLoggerAdvisor:在控制台打印详细的请求、响应、工具调用信息,调试神器。MessageChatMemoryAdvisor:为对话提供简单的内存,使得Agent能记住当前会话中的历史消息,实现多轮对话。
4.4 创建高级代理控制器现在,创建一个新的控制器,使用我们配置好的、具备工具调用能力的ChatClient(即AI代理)。
// 文件路径:src/main/java/com/example/aiagent/controller/AgentController.java package com.example.aiagent.controller; import org.springframework.ai.chat.client.ChatClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import lombok.RequiredArgsConstructor; import lombok.Data; @RestController @RequiredArgsConstructor public class AgentController { // 注入我们配置的、具备工具调用能力的AI代理 private final ChatClient aiAgent; @PostMapping("/agent/chat") public AgentResponse chatWithAgent(@RequestBody UserRequest request) { // 调用代理处理用户请求 String agentResponse = aiAgent.prompt() .user(request.getMessage()) .call() .content(); return new AgentResponse(agentResponse); } // 内部请求/响应对象 @Data public static class UserRequest { private String message; } @Data public static class AgentResponse { private final String response; public AgentResponse(String response) { this.response = response; } } }5. 运行、测试与效果验证
完成所有代码编写后,让我们启动项目并进行全面测试。
5.1 启动应用
- 确保Ollama服务正在运行(终端执行
ollama serve或确保服务已启动)。 - 在IDE中运行
AiAgentAssistantApplication的main方法,或使用命令mvn spring-boot:run。
观察控制台日志,应无错误,并看到Spring AI和Ollama相关的初始化信息。
5.2 测试基础对话使用curl、Postman或浏览器测试基础对话接口:
GET http://localhost:8080/chat?message=你好,世界!预期返回一个由本地模型生成的问候回复。
5.3 测试AI代理工具调用这是核心测试。我们向代理发送需要计算或查询的请求。
# 使用curl测试代理接口 curl -X POST http://localhost:8080/agent/chat \ -H "Content-Type: application/json" \ -d '{"message": "请计算一下125加上37等于多少?"}'观察控制台!你会看到SimpleLoggerAdvisor打印的详细日志,类似:
User: 请计算一下125加上37等于多少? AI (思考): 我需要使用计算器工具。我将调用`calculator`工具,参数为 a=125, b=37, operator=‘+’。 Function Call: calculator({“a”:125, “b”:37, “operator”:“+”}) Function Response: {“result”:162.0} AI (最终回复): 125加上37等于162。最终API返回的响应内容将是:“125加上37等于162。”
再测试天气查询:
curl -X POST http://localhost:8080/agent/chat \ -H "Content-Type: application/json" \ -d '{"message": "今天北京的天气怎么样?"}'代理会识别出需要调用weather工具,查询模拟数据后返回:“今天北京天气晴,气温25摄氏度。”
5.4 测试多轮对话(记忆能力)发送连续请求:
# 第一轮 curl ... -d '{"message": "我叫小明"}' # 预期回复可能是问候或确认。 # 第二轮 curl ... -d '{"message": "我的名字是什么?"}'由于配置了MessageChatMemoryAdvisor,代理有很大概率能回答“你叫小明”。这证明了其基础的会话记忆能力。
6. 常见问题与排查思路(FAQ)
在实际搭建过程中,你可能会遇到以下问题。这里提供系统的排查思路。
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
应用启动失败,报OllamaConnectionException或ConnectException | 1. Ollama服务未启动。 2. application.yml中配置的base-url错误。3. 防火墙/端口被占用。 | 1. 终端执行ollama serve并确保无报错。2. 浏览器访问 http://localhost:11434,应看到Ollama的欢迎页。3. 检查 spring.ai.ollama.base-url配置是否正确。4. 使用 lsof -i:11434查看端口监听情况。 |
调用/chat接口超时或返回空 | 1. 本地模型首次响应慢或配置的模型不存在。 2. 模型参数(如 temperature)导致生成异常。3. 硬件资源(内存、CPU)不足。 | 1. 直接在终端用ollama run llama3.2:1b测试模型是否正常。2. 检查 application.yml中的model名称是否与Ollama中的完全一致。3. 尝试调低 temperature(如0.1) 或减少输入长度。4. 监控系统资源,考虑换用更小的模型(如 tinyllama)。 |
| 代理不调用工具,而是直接回答“我不会”或胡言乱语 | 1. 工具描述 (getDescription) 不够清晰,模型无法理解。2. 模型能力不足,无法进行有效的工具调用规划。 3. 请求的Prompt不够明确。 | 1.重点检查:优化工具描述,使用清晰、结构化的英文或中文描述输入输出格式。 2. 升级本地模型到能力更强的版本(如 llama3.2:3b)。3. 在用户请求中给予更明确的指令,如“请使用计算器工具计算...”。 4. 查看 SimpleLoggerAdvisor日志,观察模型在收到请求后的“思考”过程。 |
| 工具调用参数错误,如类型不匹配 | 1. 工具函数apply的输入输出类型与描述不符。2. 模型生成的参数格式错误。 | 1. 确保Request记录(Record)的字段名和类型与描述一致。2. 在 AdvisorFunctionCall中,Spring AI会尝试进行类型转换,确保它是可序列化的POJO。3. 在工具函数内部增加日志,打印接收到的参数。 |
| 多轮对话记忆失效 | 1.InMemoryChatMemory是会话级,可能未正确关联。2. 默认记忆长度有限。 | 1. 确保每次对话来自同一HTTP会话(对于简单测试,快速连续调用可能被视为同一会话)。 2. 考虑实现更稳定的记忆存储,如基于会话ID的 Map或集成Spring AI Redis Chat Memory。 |
7. 最佳实践与工程建议
将Demo提升到可工程化水平,需要注意以下几点:
7.1 工具设计与描述优化
- 单一职责:每个工具应只做一件事。不要设计一个“万能工具”。
- 描述即契约:工具描述是AI理解工具的“说明书”。务必清晰、准确、结构化。建议包含:工具名称、功能简述、输入参数(名称、类型、说明)、输出格式示例。
- 错误处理:在工具的
apply方法中做好健壮的错误处理(如参数校验、异常捕获),并返回友好的错误信息,让AI能将其反馈给用户。
7.2 代理(Agent)的提示词工程
- 系统提示词(System Prompt):可以通过
ChatClient.Builder的defaultSystem()方法为代理设定角色和能力边界。例如:
一个清晰的系统提示能极大提升代理行为的可控性和准确性。return chatClientBuilder .defaultSystem(""" 你是一个专业的AI助手,擅长使用计算器和查询天气。 你必须遵循以下规则: 1. 当用户涉及数学计算时,必须使用计算器工具。 2. 当用户询问天气时,必须使用天气查询工具。 3. 其他问题请友好、简洁地回答。 """) .defaultAdvisors(...) .build();
7.3 性能与稳定性
- 模型选择:在生产环境,根据业务需求权衡模型大小、速度和精度。可考虑使用量化模型。
- 超时与重试:在
application.yml中配置Ollama客户端的超时和重试策略。spring: ai: ollama: chat: options: model: qwen2.5:7b # 客户端配置 client: connect-timeout: 30s read-timeout: 60s - 异步处理:对于耗时的AI生成或工具调用,考虑使用
@Async或消息队列进行异步处理,避免阻塞HTTP请求。
7.4 可观测性与监控
- 日志:充分利用
SimpleLoggerAdvisor进行调试。生产环境可将其替换为自定义的Advisor,将交互日志写入ELK等系统。 - 指标:集成Micrometer,监控AI调用延迟、工具调用次数、Token使用量(如果模型支持)等关键指标。
7.5 安全与权限
- 输入校验:对所有用户输入进行严格的校验和清理,防止Prompt注入攻击。
- 工具权限:不是所有工具都应被任意调用。可以根据用户角色或上下文,动态地注册或禁用某些工具。
- 输出过滤:对AI生成的内容进行必要的安全过滤,避免产生不当言论。
通过以上步骤,你已经成功搭建了一个基于Spring AI和本地Ollama模型的AI代理助手原型。这个项目具备了核心的对话、工具调用和记忆能力。你可以在此基础上,继续扩展更多工具(如数据库查询、API调用、文件处理),优化代理的推理逻辑,或为其添加一个简单的前端界面,从而打造出一个真正实用的内部智能助手。