Spring AI 入门级学习 6:怎么构建 AI 智能体

张开发
2026/4/15 20:58:34 15 分钟阅读

分享文章

Spring AI 入门级学习 6:怎么构建 AI 智能体
简单来说智能体 Agent 也有人叫它为 AI 代理以大语言模型为核心集成 记忆、知识库和工具 等能力为一体构造了完整的决策能力、执行能力和记忆能力就像一个有主观能动性的人类一样我们前面学习的RAG、工具调用、MCP 等组合起来其实就算是一个简单的智能体虽然还不具备完整‍‍的‍自主规划和执行‎‎能力‎ChatResponse response chatClient .prompt() .user(message) // 记忆 .advisors(spec - spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId) .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10)) // 开启日志便于观察效果 .advisors(new MyLoggerAdvisor()) // 知识库 .advisors(new QuestionAnswerAdvisor(loveAppVectorStore)) // 工具调用 .tools(toolCallbackProvider) .call() .chatResponse();智能体实现关键技术CoT 思维链CoTChain of‏‏‏ Thought思维链是一种让 AI 像人类一‍‍‍样 “思考” 的技术帮助 AI 在处理复杂问题‎‎‎时能够按步骤思考你会发现有很多大模型都有 thinking 版本来支持思考模式对于复杂的推理类问题先思考后‏‏‏执行效果往往更好在之前是通过输入 Prompt 时给模型提供额外的提示或‍‍‍引导比如 “让我们一步一步思考这个问题”让模型以逐‎‎‎步推理的方式生成回答Agent Loop 执行循环Agent‏‏‏ Loop 是智能体‍‍‍最核心的工作机制指‎‎‎智能体在没有用户输入‏‏‏的情况下自主重复执⁡⁡⁡行推理和工具调用的过程平常像是在网页端使用时每次用户提问后AI 回复一次就结束‍‍‍了。但在智能体中AI 回复后可能会继续自主‎‎‎执行后续动作如调用工具、处理结果、继续‏‏‏推理形成一个自主执行的循环直到任务⁡⁡⁡完成ReAct 模式ReActReasoning Acting是一种结合推理和行动的智能体架构它模仿人类解决问题时 ”思考 - 行动 - 观察” 的循环目的是通过交互式决策解决复杂任务是目前最常用的智能体工作模式之一总结上面三个方式就是CoT先想清楚Agent Loop不停地做ReAct每一轮都按“想一下 → 做一下 → 看结果”来推进像是在 cursor 中的使用话不多说我们直接进入实践来一步一步了解这些概念自主实现 Manus 智能体智能体中的调用大模型、会话记忆、工具调用能力等使用 ‎‎‎AI 开发框架这些能力都不需要我们自己实现代码量会简‏‏‏单很多首先定义数据模型新建 agent.model 包将所有用到的数据模型实体类、枚举类等都放到该包下目前我们只‏‏‏需要定义 Age‍‍n‍t 的状态枚举‎‎用‎于控制智能体的‏‏执行/** 代理执行状态的枚举类 */ public enum AgentState { /** 空闲状态 */ IDLE, /** 运行中状态 */ RUNNING, /** 已完成状态 */ FINISHED, /** 错误状态 */ ERROR }这个就是一次对话任务的 agent 运行状态核心架构开发智能体的核心架构包括以下类BaseAgent智能体基类定义基本信息和多步骤执行流程ReActAgent实现思考和行动两个步骤的智能体ToolCallAgent实现工具调用能力的智能体YuManus最终可使用的 Manus 实例1、开发基础 Agent 类 BaseAgent/** 抽象基础代理类用于管理代理状态和执行流程。 提供状态转换、内存管理和基于步骤的执行循环的基础功能。 子类必须实现step方法。 */ Data Slf4j public abstract class BaseAgent { // 核心属性 private String name; // 提示 private String systemPrompt; private String nextStepPrompt; // 状态 private AgentState state AgentState.IDLE; // 执行控制 private int maxSteps 10; private int currentStep 0; // LLM private ChatClient chatClient; // Memory需要自主维护会话上下文 private List messageList new ArrayList(); /** - 运行代理 param userPrompt 用户提示词 return 执行结果 */ public String run(String userPrompt) { if (this.state ! AgentState.IDLE) { throw new RuntimeException(Cannot run agent from state: this.state); } if (StringUtil.isBlank(userPrompt)) { throw new RuntimeException(Cannot run agent with empty user prompt); } // 更改状态 state AgentState.RUNNING; // 记录消息上下文 messageList.add(new UserMessage(userPrompt)); // 保存结果列表 List results new ArrayList(); try { for (int i 0; i maxSteps state ! AgentState.FINISHED; i) { int stepNumber i 1; currentStep stepNumber; log.info(Executing step stepNumber / maxSteps); // 单步执行 String stepResult step(); String result Step stepNumber : stepResult; results.add(result); } // 检查是否超出步骤限制 if (currentStep maxSteps) { state AgentState.FINISHED; results.add(Terminated: Reached max steps ( maxSteps )); } return String.join(\n, results); } catch (Exception e) { state AgentState.ERROR; log.error(Error executing agent, e); return 执行错误 e.getMessage(); } finally { // 清理资源 this.cleanup(); } } /** - 执行单个步骤 return 步骤执行结果 */ public abstract String step(); /** 清理资源 */ protected void cleanup() { // 子类可以重写此方法来清理资源 } }上述代码中我们可以看到这里的 cleanup() 应该运行“结束本次运行后的收尾”像是chatClient 属性由调用方传入具体调用大模型的对象而不是写死使用的大模型更灵活messageList 属性用于维护消息上下文列表state 属性来控制智能体的执行流程这里的 cleanup() 应该运行“结束本次运行后的收尾”像是重置运行时状态比如当前步骤数、临时标记、执行中的状态位释放资源比如浏览器句柄、文件句柄、网络连接、临时对象清理这次任务的中间变量或缓存2、开发 ReActAgent 类继承自 ‍‍‍BaseAgent并且将 s‎‎‎tep 方法分解为 think‏‏‏ 和 act 两个抽象方法/** ReAct (Reasoning and Acting) 模式的代理抽象类 实现了思考-行动的循环模式 */ EqualsAndHashCode(callSuper true) Data public abstract class ReActAgent extends BaseAgent { /** 处理当前状态并决定下一步行动 return 是否需要执行行动true表示需要执行false表示不需要执行 */ public abstract boolean think(); /** - 执行决定的行动 return 行动执行结果 */ public abstract String act(); /** - 执行单个步骤思考和行动 return 步骤执行结果 */ Override public String step() { try { boolean shouldAct think(); if (!shouldAct) { return 思考完成 - 无需行动; } return act(); } catch (Exception e) { // 记录异常日志 e.printStackTrace(); return 步骤执行失败: e.getMessage(); } } }3、开发 ToolCallAgent 类ToolCa‏‏‏llAgent 负责实现‍‍‍工具调用能力继承自 R‎‎‎eActAgent具体‏‏‏实现了 think 和 ⁡⁡⁡act 两个抽象方法基于 ‏‏‏Spring ‍A‍I‍ 的工具调‎用能‎力‎手动控‏制工‏具执行先‏定义所‏需⁡的属性和⁡构造方法/** 处理工具调用的基础代理类具体实现了 think 和 act 方法可以用作创建实例的父类 */ EqualsAndHashCode(callSuper true) Data Slf4j public class ToolCallAgent extends ReActAgent { // 可用的工具 private final ToolCallback[] availableTools; // 保存了工具调用信息的响应 private ChatResponse toolCallChatResponse; // 工具调用管理者 private final ToolCallingManager toolCallingManager; // 禁用内置的工具调用机制自己维护上下文 private final ChatOptions chatOptions; public ToolCallAgent(ToolCallback[] availableTools) { super(); this.availableTools availableTools; this.toolCallingManager ToolCallingManager.builder().build(); // 禁用 Spring AI 内置的工具调用机制自己维护选项和消息上下文 this.chatOptions DashScopeChatOptions.builder() .withProxyToolCalls(true) .build(); } }注意在上述代码中‏‏‏我们通过将 Ch‍‍‍atOptions 的 withPr‎‎‎oxyToolCalls 选项设置为‏‏‏ true来禁止 Spring A⁡⁡⁡I 托管工具调用而是我们自主实现下面我们实‏‏‏现 think‍ ‍方‍法传入工‎具列‎表并‎调用大‏模型‏得到需‏要⁡调用的工⁡具列表/** 处理当前状态并决定下一步行动 return 是否需要执行行动 */ Override public boolean think() { if (getNextStepPrompt() ! null !getNextStepPrompt().isEmpty()) { UserMessage userMessage new UserMessage(getNextStepPrompt()); getMessageList().add(userMessage); } List messageList getMessageList(); Prompt prompt new Prompt(messageList, chatOptions); try { // 获取带工具选项的响应 ChatResponse chatResponse getChatClient().prompt(prompt) .system(getSystemPrompt()) .tools(availableTools) .call() .chatResponse(); // 记录响应用于 Act this.toolCallChatResponse chatResponse; AssistantMessage assistantMessage chatResponse.getResult().getOutput(); // 输出提示信息 String result assistantMessage.getText(); ListAssistantMessage.ToolCall toolCallList assistantMessage.getToolCalls(); log.info(getName() 的思考: result); log.info(getName() 选择了 toolCallList.size() 个工具来使用); String toolCallInfo toolCallList.stream() .map(toolCall - String.format(工具名称%s参数%s, toolCall.name(), toolCall.arguments()) ) .collect(Collectors.joining(\n)); log.info(toolCallInfo); if (toolCallList.isEmpty()) { // 只有不调用工具时才记录助手消息 getMessageList().add(assistantMessage); return false; } else { // 需要调用工具时无需记录助手消息因为调用工具时会自动记录 return true; } } catch (Exception e) { log.error(getName() 的思考过程遇到了问题: e.getMessage()); getMessageList().add( new AssistantMessage(处理时遇到错误: e.getMessage())); return false; } }最后实现 ac‏‏‏t 方法执行工具调用列表‍‍‍得到返回结果并将工具的响应‎‎‎添加到消息列表中/** 执行工具调用并处理结果 return 执行结果 */ Override public String act() { if (!toolCallChatResponse.hasToolCalls()) { return 没有工具调用; } // 调用工具 Prompt prompt new Prompt(getMessageList(), chatOptions); ToolExecutionResult toolExecutionResult toolCallingManager.executeToolCalls(prompt, toolCallChatResponse); // 记录消息上下文conversationHistory 已经包含了助手消息和工具调用返回的结果 setMessageList(toolExecutionResult.conversationHistory()); // 当前工具调用的结果 ToolResponseMessage toolResponseMessage (ToolResponseMessage) CollUtil.getLast(toolExecutionResult.conversationHistory()); String results toolResponseMessage.getResponses().stream() .map(response - 工具 response.name() 完成了它的任务结果: response.responseData()) .collect(Collectors.joining(\n)); log.info(results); return results; }4、开发 YuManus 类YuManus 是‏‏‏可以直接提供给其他方法调用的 AI‍‍‍ 超级智能体实例继承自 Tool‎‎‎CallAgent需要给智能体设‏‏‏置各种参数比如对话客户端 cha⁡⁡⁡tClient、工具调用列表等Component public class YuManus extends ToolCallAgent { public YuManus(ToolCallback[] allTools, ChatModel dashscopeChatModel) { super(allTools); this.setName(yuManus); String SYSTEM_PROMPT You are YuManus, an all-capable AI assistant, aimed at solving any task presented by the user. You have various tools at your disposal that you can call upon to efficiently complete complex requests. ; this.setSystemPrompt(SYSTEM_PROMPT); String NEXT_STEP_PROMPT Based on user needs, proactively select the most appropriate tool or combination of tools. For complex tasks, you can break down the problem and use different tools step by step to solve it. After using each tool, clearly explain the execution results and suggest the next steps. If you want to stop the interaction at any point, use the terminate tool/function call. ; this.setNextStepPrompt(NEXT_STEP_PROMPT); this.setMaxSteps(20); // 初始化客户端 ChatClient chatClient ChatClient.builder(dashscopeChatModel) .defaultAdvisors(new MyLoggerAdvisor()) .build(); this.setChatClient(chatClient); } }看代码是不是非常的不清晰我们拿对话历史上下文messlist的变化来梳理一下流程run(userPrompt) 开始 BaseAgent.run() 先把本次用户输入加进去messageList.add(new UserMessage(userPrompt));第一轮 think() 如果有 nextStepPromptYuManus中自定义的在每一轮循环里额外塞给大模型的“下一步行动指导词”还会再加一个用户消息getMessageList().add(new UserMessage(getNextStepPrompt()));然后拿这整段历史去问模型Prompt prompt new Prompt(messageList, this.chatOptions);1.如果模型不调工具模型返回普通 assistantMessage即这次 think 的对话代码会手动加进去getMessageList().add(assistantMessage);然后这一轮结束不进 act()。2.如果模型要调工具这时 think() 不会手动加 assistantMessage而是把响应先存到this.toolCallChatResponse chatResponse;因为下一步 act() 会统一处理由它来保存工具调用的对话进入 act()这里执行ToolExecutionResult toolExecutionResult toolCallingManager.executeToolCalls(prompt, toolCallChatResponse);setMessageList(toolExecutionResult.conversationHistory())此时 conversationHistory() 一般就会变成一整段更新后的历史包含 之前的用户消息 nextStepPrompt assistant 发出的 tool call 消息 tool 返回消息然后整段历史直接覆盖回 messageList下一轮继续 think()下一轮又会基于更新后的 messageList 继续推理。 所以模型能“看到”前面已经调过什么工具、返回了什么结果再决定下一步。补充智能体工作流当我们面对复杂任‏‏‏务时单一智能体可能无法满足需‍‍‍求。因此智能体工作流Agen‎‎‎t Workflow应运而生‏‏‏通过简单的编排允许多个专业⁡⁡⁡智能体协同工作各司其职。像我们刚刚做的就是单个 agent自己循环思考、调工具、继续执行智能体工作流里可能有多个不同 agent比如Planner Agent负责拆任务Search Agent负责搜索资料Code Agent负责写代码Review Agent负责检查结果调用执行可以是一个 agent 的多个实例比如开 3 个 Search Agent同时查不同来源多个不同类型的agent一个负责规划一个负责执行一个负责审核同时运行对独立子任务并行执行当然执行顺序也要规划好串行A 做完结果给 BB 再做并行A 和 B 同时做不同子任务混合先 Planner 拆任务再多个执行 agent 并行最后 Reviewer 汇总

更多文章