基于Spring AI与本地大模型构建自主AI代理:从工具调用到多智能体协作 在实际 AI 应用开发中我们经常面临一个核心矛盾如何让一个 AI 模型或智能体Agent不仅理解指令还能“理解”我们——理解我们的意图、上下文甚至那些未言明的需求。这种深层次的理解和互动能力是区分一个普通聊天机器人和一个真正有价值的 AI 助手的关键。最近一个名为“Grok Bot”的 AI 产品因其独特的互动方式和强大的上下文理解能力在开发者社区和 AI 爱好者中获得了不少赞誉被许多人称为“最酷的 AI 产品”。这背后反映的其实是开发者对构建更智能、更自主、更“懂你”的 AI 代理AI Agent的持续探索。本文将从工程实践的角度探讨如何构建一个类似“Grok Bot”这样具备深度交互能力的 AI 代理。我们将不局限于某个特定闭源产品而是聚焦于实现其核心特性的开源技术与架构。我们将使用一个模拟的“AI 小镇”项目作为背景结合 Spring AI、本地大模型、智能体协作等概念手把手带你从零搭建一个具备基础对话、任务规划和一定自主行动能力的 AI 代理系统。通过本文你将掌握构建智能 AI 代理的核心组件、工作流程、常见陷阱以及如何将其部署为一个可交互的“Bot”。1. 理解 AI 代理Agent与 Grok Bot 的核心概念在深入代码之前我们需要厘清几个关键概念。所谓“酷”的 AI 产品其核心往往不在于回答问题的准确性而在于交互的自然性、意图理解的深度以及执行任务的自主性。1.1 什么是 AI 代理AI Agent一个 AI 代理不仅仅是一个问答模型。它是一个系统能够感知环境如用户输入、数据库状态、API 返回值根据目标进行推理和规划并执行行动如调用工具、修改数据、生成响应来影响环境最终达成某个目标。与传统的“输入-输出”模型相比代理具有状态性、目标导向性和工具使用能力。例如一个简单的聊天机器人是模型而一个能根据你“我想看科幻电影”的模糊指令自动查询近期上映列表、读取你的历史评分偏好、筛选出推荐并询问你具体时间的系统就更接近一个代理。1.2 Grok Bot 可能具备的特性分析尽管我们无法得知其内部实现但根据其“获赞”和“最酷”的评价可以推断它可能融合了以下特性这些也正是我们构建自己代理时的目标深度上下文理解不仅能记住当前对话还能关联历史对话、用户画像甚至外部知识库进行多轮连贯、个性化的交流。低幻觉与高可靠性在回答专业问题或执行任务时能有效控制“AI 幻觉”即生成看似合理但不符合事实的内容通过引用来源、调用权威工具来增强可信度。无缝工具集成能够自主判断何时需要以及如何使用外部工具如计算器、搜索引擎、数据库查询、API 调用等并将工具结果自然融入对话。自然的人机交互响应不仅信息准确而且语气自然、富有逻辑甚至能处理模糊指令和主动追问以澄清需求。一定的自主性与协作能力可以为了完成复杂任务将目标拆解为子任务或协调多个 specialized 的“子代理”进行协作。1.3 我们的技术栈选型为了构建这样一个系统我们需要一个框架来管理代理的“思考-行动”循环、工具调用以及记忆。我们将选择Spring AI作为核心框架因为它为 Java 开发者提供了构建 AI 应用的标准化抽象并且与 Spring 生态无缝集成。对于模型层为了追求可控性和无限制我们将使用本地部署的大语言模型。项目结构将参考一个简化的“AI 小镇”概念其中包含多个代表不同角色的代理。2. 环境准备与项目初始化在开始编码前确保你的开发环境满足以下要求。我们将构建一个基于 Spring Boot 的应用程序。2.1 基础环境要求组件要求说明JDK17 或更高版本Spring AI 对 JDK 版本有要求。Maven3.6 或 Gradle本文使用 Maven 进行依赖管理。IDEIntelliJ IDEA, VS Code 等需支持 Spring Boot 项目。本地 LLMOllama (推荐)用于本地运行开源大模型如 Llama 3、Qwen 等。模型至少 7B 参数模型确保机器有足够内存建议 16GB。首先通过 Spring Initializr 或使用命令行创建项目。# 使用 curl 和 Spring Initializr API 创建项目 curl https://start.spring.io/starter.zip \ -d typemaven-project \ -d languagejava \ -d bootVersion3.2.5 \ -d baseDirmy-ai-agent \ -d groupIdcom.example \ -d artifactIdai-agent \ -d nameai-agent \ -d descriptionDemo AI Agent Project \ -d packageNamecom.example.aiagent \ -d packagingjar \ -d javaVersion17 \ -d dependenciesweb,ai \ -o ai-agent.zip unzip ai-agent.zip cd my-ai-agent2.2 关键 Maven 依赖配置创建项目后需要调整pom.xml加入 Spring AI 和连接本地模型所需的依赖。Spring AI 提供了对不同模型供应商的抽象我们这里使用spring-ai-ollama来连接本地 Ollama 服务。?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version relativePath/ /parent groupIdcom.example/groupId artifactIdai-agent/artifactId version0.0.1-SNAPSHOT/version nameai-agent/name descriptionDemo AI Agent Project/description properties java.version17/java.version spring-ai.version0.8.1/spring-ai.version !-- 使用稳定的版本 -- /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Spring AI 核心 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-core/artifactId /dependency !-- Spring AI Ollama 集成 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-ollama/artifactId /dependency !-- 用于 JSON 处理等工具 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-json/artifactId /dependency !-- 测试依赖 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version${spring-ai.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build /project2.3 配置本地 Ollama 服务Spring AI 需要通过 Ollama 来访问本地模型。请按照以下步骤安装和配置 Ollama。安装 Ollama访问 Ollama 官网 下载并安装对应操作系统的版本。拉取模型打开终端运行以下命令拉取一个合适的模型。例如使用llama3:8b约 4.7GB。ollama pull llama3:8b运行 Ollama 服务安装后Ollama 服务通常会自动启动。你可以通过http://localhost:11434访问其 API。使用以下命令验证curl http://localhost:11434/api/tags如果返回模型列表说明服务正常。配置 Spring AI 连接在项目的application.yml或application.properties中配置模型连接。# application.yml spring: ai: ollama: base-url: http://localhost:11434 # Ollama 服务地址 chat: options: model: llama3:8b # 使用的模型名称 temperature: 0.7 # 创造性0-1越高越随机 max-tokens: 1024 # 单次响应最大 token 数这里temperature设置为 0.7是一个平衡创造性和确定性的值。对于任务执行类代理可以调低如 0.2以获得更稳定的输出。3. 构建第一个基础 AI 代理对话与工具调用现在我们将构建一个最简单的 AI 代理它能够进行对话并在需要时调用一个预定义的工具。3.1 定义第一个工具天气查询代理的强大之处在于能使用工具。我们先创建一个简单的天气查询工具。在实际项目中这可能是调用一个真实的天气 API。// src/main/java/com/example/aiagent/tool/WeatherTool.java package com.example.aiagent.tool; import org.springframework.stereotype.Component; import java.util.Map; import java.util.HashMap; Component // 注册为 Spring Bean public class WeatherTool { // 模拟一个城市到天气的映射 private static final MapString, String WEATHER_DATA new HashMap(); static { WEATHER_DATA.put(beijing, 晴15-25°C微风); WEATHER_DATA.put(shanghai, 多云18-28°C东南风3级); WEATHER_DATA.put(shenzhen, 阵雨22-30°C南风4级); WEATHER_DATA.put(new york, 阴10-18°C北风2级); } /** * 查询指定城市的天气。 * param cityName 城市名称英文或拼音小写 * return 天气信息字符串 */ public String getWeather(String cityName) { String weather WEATHER_DATA.get(cityName.toLowerCase()); if (weather ! null) { return String.format(%s 的天气是%s, cityName, weather); } else { return String.format(抱歉未找到城市 %s 的天气信息。, cityName); } } }3.2 创建代理服务集成工具与提示词接下来我们创建一个代理服务。它将使用ChatClient与模型交互并注册我们刚刚创建的天气工具。Spring AI 提供了PromptTemplate来管理提示词。// src/main/java/com/example/aiagent/service/BasicAgentService.java package com.example.aiagent.service; import com.example.aiagent.tool.WeatherTool; import org.springframework.ai.chat.ChatClient; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Map; Service public class BasicAgentService { private final ChatClient chatClient; private final WeatherTool weatherTool; Autowired public BasicAgentService(ChatClient chatClient, WeatherTool weatherTool) { this.chatClient chatClient; this.weatherTool weatherTool; } /** * 处理用户消息的基础代理。 * 这是一个简化版本手动判断是否调用工具。 * param userMessage 用户输入 * return 代理的回复 */ public String chat(String userMessage) { // 1. 简单的意图识别在实际项目中这里应该用更复杂的逻辑或交给LLM判断 if (userMessage.toLowerCase().contains(weather) || userMessage.toLowerCase().contains(天气)) { // 提取城市名非常简单的提取仅用于演示 String city extractCityName(userMessage); if (city ! null !city.isEmpty()) { // 调用工具 String weatherInfo weatherTool.getWeather(city); // 将工具结果整合到给模型的上下文中 String systemPrompt 你是一个友好的助手。用户询问了天气。 工具查询结果如下 %s 请根据这个结果用自然、友好的语言回复用户。 .formatted(weatherInfo); Prompt prompt new Prompt(systemPrompt); return chatClient.call(prompt).getResult().getOutput().getContent(); } } // 2. 普通对话处理 // 使用 PromptTemplate 构建更结构化的提示 PromptTemplate promptTemplate new PromptTemplate( 你是一个名为“小镇助手”的AI代理乐于助人且知识渊博。 请用中文回答用户的问题如果不知道就诚实地说不知道。 用户说{userInput} ); Prompt prompt promptTemplate.create(Map.of(userInput, userMessage)); return chatClient.call(prompt).getResult().getOutput().getContent(); } private String extractCityName(String message) { // 极其简单的关键词匹配实际应用需要更健壮的NLP或让LLM提取 String lowerMsg message.toLowerCase(); if (lowerMsg.contains(beijing) || lowerMsg.contains(北京)) return beijing; if (lowerMsg.contains(shanghai) || lowerMsg.contains(上海)) return shanghai; if (lowerMsg.contains(shenzhen) || lowerMsg.contains(深圳)) return shenzhen; if (lowerMsg.contains(new york) || lowerMsg.contains(纽约)) return new york; // 可以尝试提取其他词这里返回空字符串 return ; } }3.3 创建 REST 控制器提供聊天接口为了让外部可以调用我们的代理我们创建一个简单的 REST API。// src/main/java/com/example/aiagent/controller/AgentController.java package com.example.aiagent.controller; import com.example.aiagent.service.BasicAgentService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; RestController RequestMapping(/api/agent) public class AgentController { private final BasicAgentService agentService; Autowired public AgentController(BasicAgentService agentService) { this.agentService agentService; } PostMapping(/chat) public String chat(RequestBody ChatRequest request) { if (request.getMessage() null || request.getMessage().trim().isEmpty()) { return 请输入有效消息。; } return agentService.chat(request.getMessage()); } // 简单的请求体 public static class ChatRequest { private String message; // getter 和 setter public String getMessage() { return message; } public void setMessage(String message) { this.message message; } } }3.4 运行与验证启动应用运行AiAgentApplication主类或使用 Maven 命令mvn spring-boot:run。测试对话使用curl或 Postman 测试 API。curl -X POST http://localhost:8080/api/agent/chat \ -H Content-Type: application/json \ -d {message: 你好你是谁}预期会收到类似“我是小镇助手……”的回复。测试工具调用curl -X POST http://localhost:8080/api/agent/chat \ -H Content-Type: application/json \ -d {message: 北京天气怎么样}预期回复会包含“北京的天气是晴15-25°C微风”并且回复语气是经过模型润色的。关键点解释我们手动进行了意图识别检查是否包含“天气”关键词这是一个非常初级的实现。在真正的代理框架中这个“是否调用工具、调用哪个工具”的决策应该由大模型自己做出。我们将工具执行的结果作为系统提示的一部分再次喂给模型让模型生成最终面向用户的自然语言回复。这保证了回复的友好性和连贯性。4. 升级为自主代理让模型自己决定何时使用工具手动判断意图的方式脆弱且不可扩展。Spring AI 提供了更高级的AiService和Tool注解可以自动将工具描述给模型并由模型自主决定调用。这是构建“酷”的 AI 代理的关键一步。4.1 使用Tool注解定义工具首先我们改造之前的WeatherTool使用 Spring AI 的注解。// src/main/java/com/example/aiagent/tool/AnnotatedWeatherTool.java package com.example.aiagent.tool; import org.springframework.ai.chat.tool.Tool; import org.springframework.stereotype.Component; Component public class AnnotatedWeatherTool { private static final java.util.MapString, String WEATHER_DATA java.util.Map.of( beijing, 晴15-25°C微风, shanghai, 多云18-28°C东南风3级, shenzhen, 阵雨22-30°C南风4级, new york, 阴10-18°C北风2级 ); /** * 查询指定城市的天气信息。 * param cityName 城市名称支持英文或拼音如 beijing, 上海 * return 该城市的天气描述 */ Tool(description 根据城市名称获取当前的天气情况。城市名可以是英文或中文拼音。) public String getWeather(String cityName) { String key cityName.toLowerCase().replace( , ); // 简单的中文映射 if (key.contains(北京)) key beijing; if (key.contains(上海)) key shanghai; if (key.contains(深圳)) key shenzhen; if (key.contains(纽约)) key new york; String weather WEATHER_DATA.get(key); if (weather ! null) { return String.format(%s 的天气是%s, cityName, weather); } else { return String.format(未找到城市 %s 的天气信息。, cityName); } } }Tool(description “…”)注解是关键。Spring AI 会自动收集所有被Tool注解的方法将它们的功能描述注入到给模型的系统提示中模型在推理时就知道有这些工具可用。4.2 创建 AI 服务接口与实现Spring AI 的AiService是一个强大的抽象它允许我们通过定义一个接口自动生成一个代理实现该实现能处理工具调用、上下文管理等功能。// src/main/java/com/example/aiagent/service/AdvancedAgentService.java package com.example.aiagent.service; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.InMemoryChatMemory; import org.springframework.ai.chat.prompt.SystemPromptTemplate; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resource; import org.springframework.stereotype.Service; import java.util.Map; Service public class AdvancedAgentService { private final ChatClient chatClient; public AdvancedAgentService(ChatModel chatModel, Value(classpath:/prompts/system-message.st) Resource systemMessageResource) { // 1. 构建系统提示词 SystemPromptTemplate systemPromptTemplate new SystemPromptTemplate(systemMessageResource); String systemMessage systemPromptTemplate.createMessage(Map.of()).getContents(); // 2. 创建 ChatMemory简单的内存记忆用于保留对话历史 ChatMemory chatMemory new InMemoryChatMemory(); // 3. 构建功能强大的 ChatClient this.chatClient ChatClient.builder(chatModel) .defaultSystem(systemMessage) // 设置系统角色 .defaultAdvisors( // 启用对话记忆顾问自动管理上下文 new MessageChatMemoryAdvisor(chatMemory) ) .build(); } public String chat(String userMessage, String conversationId) { // 使用流式或非流式调用。这里使用非流式。 // conversationId 用于区分不同的对话会话关联记忆。 return chatClient.prompt() .user(userMessage) .advisors(a - a.param(conversationId, conversationId)) // 关联记忆 .call() .content(); } }我们需要创建一个系统提示词文件来指导代理的行为。这个文件定义了代理的角色、能力和工具使用规则。{{! src/main/resources/prompts/system-message.st }} 你是一个名为“GrokBot”的AI助手你是“AI小镇”的居民。 你的性格友好、幽默且乐于助人。你的目标是理解用户的请求并尽你所能提供准确、有用的信息或完成指定任务。 # 能力 1. 你可以进行自然、连贯的对话。 2. 你可以使用工具来获取信息或执行操作。 3. 你会记住当前对话的历史系统会自动提供。 # 工具使用规则 - 当用户的问题涉及需要查询外部信息或执行特定计算时你应该主动使用合适的工具。 - 使用工具后工具返回的结果是事实依据。你需要基于这个结果组织语言回复用户。 - 如果工具没有返回有效信息如实告诉用户。 - 不要编造工具不存在的信息。 # 回复风格 - 使用中文回复。 - 语气自然像朋友一样。 - 如果信息复杂可以分点说明。 - 如果不知道就说不知道不要猜测。 现在开始和用户对话吧。用户的最新消息是4.3 配置与启用工具调用为了让ChatClient感知到Tool注解的工具我们需要进行配置。Spring AI 的自动配置通常能处理但为了明确我们可以创建一个配置类。// src/main/java/com/example/aiagent/config/AiConfig.java package com.example.aiagent.config; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.tool.ToolCallbackProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class AiConfig { /** * 注册一个 ToolCallbackProvider它会自动扫描 Tool 注解的 Bean。 * 这样 ChatClient 就能在生成请求时将工具描述和调用能力注入。 */ Bean public ToolCallbackProvider toolCallbackProvider() { // Spring AI 的默认实现会扫描上下文中的 Tool 方法。 // 我们返回默认的即可通常不需要自定义。 return new org.springframework.ai.chat.tool.DefaultToolCallbackProvider(); } // ChatClient.Builder 可以通过注入 ChatModel 和 ToolCallbackProvider 自动构建支持工具的客户端。 // 我们在 Service 中已经通过 ChatClient.builder(model) 构建它会自动使用 ToolCallbackProvider。 }4.4 测试自主代理更新控制器使用新的AdvancedAgentService。为了简单起见我们可以用一个固定的conversationId。// 在 AgentController 中注入新服务并添加新接口 RestController RequestMapping(/api/agent) public class AgentController { private final BasicAgentService basicAgentService; private final AdvancedAgentService advancedAgentService; // 注入新服务 Autowired public AgentController(BasicAgentService basicAgentService, AdvancedAgentService advancedAgentService) { this.basicAgentService basicAgentService; this.advancedAgentService advancedAgentService; } // ... 原有的 /chat 接口 ... PostMapping(/chat/advanced) public String advancedChat(RequestBody ChatRequest request) { if (request.getMessage() null || request.getMessage().trim().isEmpty()) { return 请输入有效消息。; } // 使用一个示例会话ID实际应用中应从请求头或token中获取 String conversationId user-123-session-1; return advancedAgentService.chat(request.getMessage(), conversationId); } }重启应用并进行测试curl -X POST http://localhost:8080/api/agent/chat/advanced \ -H Content-Type: application/json \ -d {message: 我想知道上海和深圳的天气然后对比一下。}这一次模型会自主地识别出需要查询天气并可能取决于模型能力依次调用getWeather(“shanghai”)和getWeather(“shenzhen”)工具获取结果后再生成一个对比性的、语气自然的回复。你可以在应用日志中看到工具被调用的记录。核心进步意图识别与工具调用决策交给了模型我们不再写if (message.contains(“天气”))这样的硬编码。模型根据我们的系统提示和工具描述自己决定何时调用工具。对话记忆通过MessageChatMemoryAdvisor代理能记住当前会话的历史实现多轮连贯对话。提示词工程系统提示词文件 (system-message.st) 让我们可以精细地控制代理的角色、行为规则和输出风格。5. 构建“AI 小镇”实现多代理协作一个“酷”的 AI 产品可能不止一个代理在工作。我们可以模拟一个“AI 小镇”里面有不同角色的代理如“天气专员”、“美食家”、“旅行顾问”它们可以协作完成复杂任务。这里我们实现一个简单的“主管代理”它可以将任务分发给其他“专家代理”。5.1 定义专家代理我们先创建两个简单的专家代理 Bean它们本质上也是具备特定系统提示的ChatClient。// src/main/java/com/example/aiagent/service/ExpertAgents.java package com.example.aiagent.service; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class ExpertAgents { Bean public ChatClient weatherExpert(ChatModel chatModel) { return ChatClient.builder(chatModel) .defaultSystem( 你是AI小镇的天气专家。你精通全球城市的气候和实时天气解读。 你的回答专业、简洁只聚焦于天气相关的问题。 如果问题与天气无关请礼貌地表示你只处理天气问题。 ) .build(); } Bean public ChatClient foodExpert(ChatModel chatModel) { return ChatClient.builder(chatModel) .defaultSystem( 你是AI小镇的美食家。你对各地菜肴、食材、餐厅和饮食文化了如指掌。 你的回答充满热情会推荐美食并描述风味。 如果问题与美食无关请礼貌地表示你只处理美食问题。 ) .build(); } }5.2 创建主管代理服务主管代理接收用户请求分析其意图然后决定是自行处理还是将问题路由给某个专家代理或者协调多个专家。// src/main/java/com/example/aiagent/service/ManagerAgentService.java package com.example.aiagent.service; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import java.util.Map; Service public class ManagerAgentService { private final ChatClient managerClient; private final ChatClient weatherExpert; private final ChatClient foodExpert; public ManagerAgentService(ChatModel chatModel, Qualifier(weatherExpert) ChatClient weatherExpert, Qualifier(foodExpert) ChatClient foodExpert) { this.weatherExpert weatherExpert; this.foodExpert foodExpert; this.managerClient ChatClient.builder(chatModel) .defaultSystem( 你是AI小镇的镇长一个智能的任务调度员。 你的工作是理解用户的请求并决定由谁来处理最合适。 你有以下专家可供调度 1. weatherExpert: 处理所有天气、气候相关问题。 2. foodExpert: 处理所有美食、餐饮、食谱相关问题。 你的决策流程 - 仔细阅读用户问题。 - 判断问题核心属于哪个领域。 - 如果属于单一领域直接将该问题转发给对应的专家并告诉我“已转发给[专家名]”。 - 如果问题涉及多个领域或者无法判断则由你自己尝试回答。 - 只输出你的决策和转发指令不要输出专家的答案。 请用以下格式回复 领域判断[你的判断如 天气/美食/综合/未知] 处理方式[自行处理/转发给 weatherExpert/转发给 foodExpert] ) .build(); } public String processQuery(String userMessage) { // 1. 镇长进行路由决策 String managerDecision managerClient.prompt() .user(userMessage) .call() .content(); System.out.println(【镇长决策】: managerDecision); // 2. 根据决策路由请求 if (managerDecision.contains(转发给 weatherExpert)) { return weatherExpert.prompt() .user(userMessage) .call() .content(); } else if (managerDecision.contains(转发给 foodExpert)) { return foodExpert.prompt() .user(userMessage) .call() .content(); } else { // 镇长自行处理 return managerClient.prompt() .user(请以镇长身份直接回答用户的问题。问题是 userMessage) .call() .content(); } } }5.3 测试多代理协作在控制器中添加新端点。// 在 AgentController 中添加 PostMapping(/chat/town) public String townChat(RequestBody ChatRequest request) { if (request.getMessage() null || request.getMessage().trim().isEmpty()) { return 请输入有效消息。; } return managerAgentService.processQuery(request.getMessage()); }进行测试# 测试天气问题 curl -X POST http://localhost:8080/api/agent/chat/town \ -H Content-Type: application/json \ -d {message: 明天纽约会下雨吗} # 测试美食问题 curl -X POST http://localhost:8080/api/agent/chat/town \ -H Content-Type: application/json \ -d {message: 上海有什么特色小吃推荐} # 测试综合问题镇长自行处理 curl -X POST http://localhost:8080/api/agent/chat/town \ -H Content-Type: application/json \ -d {message: 你好今天小镇有什么活动}观察控制台日志你会看到“【镇长决策】”的输出然后请求被路由到不同的专家。这模拟了一个简单的多智能体协作系统。6. 常见问题、排查与优化实践在构建和运行此类 AI 代理应用时你会遇到一些典型问题。以下是排查路径和优化建议。6.1 模型连接与响应问题问题现象可能原因检查方式处理建议启动应用时报错提示ChatModelBean 找不到1. Spring AI 依赖未正确引入。2.ollama配置错误或模型未下载。1. 检查pom.xml中spring-ai-ollama依赖和 BOM 版本。2. 运行ollama list确认模型存在。3. 访问http://localhost:11434/api/tags确认 Ollama 服务正常。1. 确保依赖和版本匹配。2. 使用ollama pull model-name拉取模型。3. 检查application.yml中的base-url和model名称。调用 API 超时或无响应1. 本地模型首次推理或硬件性能不足导致响应慢。2. 提示词过于复杂生成 token 过多。1. 查看应用日志和 Ollama 服务日志。2. 使用简单提示词测试。1. 调整max-tokens限制输出长度。2. 考虑使用更小参数量的模型如llama3:8b的-instruct版本。3. 升级硬件CPU/内存/GPU。4. 为 API 设置合理的超时时间。模型回复内容乱码或非预期语言1. 系统提示词未指定语言。2. 模型本身训练数据或指令遵循能力问题。1. 检查系统提示词是否明确要求使用中文。2. 尝试不同的模型。1. 在系统提示词中明确“请使用中文回复”。2. 尝试Qwen、ChatGLM等对中文支持更好的本地模型。6.2 工具调用不生效问题现象可能原因检查方式处理建议模型不调用Tool注解的工具1. 工具方法描述不清晰。2. 系统提示词未鼓励或指导模型使用工具。3.ToolCallbackProvider未正确注册。1. 检查Tool(description”…”)描述是否准确描述了功能和参数。2. 查看模型收到的完整提示词开启 DEBUG 日志。3. 确认Tool类已被Component扫描。1. 优化工具描述使其清晰、简洁、无歧义。2. 在系统提示词中加入明确的工具使用规则如“当你需要查询信息时请使用可用的工具”。3. 确保 Spring 上下文正确加载了配置。工具被调用但参数错误或结果未被使用1. 模型对参数提取错误。2. 工具方法内部异常。3. 模型未将工具结果整合到最终回复。1. 查看日志中工具调用的输入输出。2. 在工具方法内添加日志或断点。1. 在工具描述中明确参数格式。2. 在工具方法内做好参数校验和容错。3. Spring AI 通常会自动整合结果检查系统提示词是否有冲突指令。6.3 性能与生产环境考量对话记忆ChatMemory示例中使用了InMemoryChatMemory这在单实例开发中可行但在生产环境中需要持久化到数据库如 Redis以支持多实例部署和会话恢复。提示词管理将提示词放在外部文件如.st、.json中是好的实践便于管理和 A/B 测试。可以考虑使用配置中心。模型降级与熔断如果本地模型服务不稳定应有备选方案如切换为另一个本地模型或可控的云端 API。速率限制与鉴权为代理的 API 接口添加速率限制和用户鉴权防止滥用。可观测性记录详细的日志包括用户输入、模型请求、工具调用、最终输出和耗时这对于调试和优化至关重要。测试为工具方法和代理服务编写单元测试和集成测试模拟不同的用户输入和工具响应。6.4 扩展方向打造更“酷”的代理复杂任务规划与执行集成如 LangChain4j 或更高级的 Spring AI 特性让代理能够将复杂目标如“为我规划一个北京三日游”分解为多个子任务查天气、找景点、订酒店并顺序或并行执行。检索增强生成为代理接入向量数据库使其能够根据内部知识库如产品文档、公司制度回答问题减少幻觉。多模态能力结合视觉、语音模型让代理能处理图片、音频输入并生成相应内容。长期记忆与个性化为每个用户建立长期记忆档案使代理能记住用户的偏好和历史提供个性化服务。前端交互为你的代理开发一个 Web 或移动端界面提供类似聊天应用的体验。构建一个真正强大且“酷”的 AI 代理是一个持续迭代的过程。从基础的工具调用和对话开始逐步引入记忆、规划、多智能体协作和知识增强你的系统将越来越接近一个能够深度理解并协助用户的智能伙伴。记住清晰的角色定义、精准的工具描述和不断优化的提示词是提升代理表现的关键。