1、定义数据模型

1.1、定义 Agen؜t 的状态枚举

/**  
 * 代理执行状态的枚举类  
 */  
public enum AgentState {  
  
    /**  
     * 空闲状态  
     */  
    IDLE,  
  
    /**  
     * 运行中状态  
     */  
    RUNNING,  
  
    /**  
     * 已完成状态  
     */  
    FINISHED,  
  
    /**  
     * 错误状态  
     */  
    ERROR  
}

2、核心架构开发

2.1、开发基础 Agent 类

/**  
 * 抽象基础代理类,用于管理代理状态和执行流程。  
 *   
 * 提供状态转换、内存管理和基于步骤的执行循环的基础功能。  
 * 子类必须实现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<Message> 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<String> 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() {  
        // 子类可以重写此方法来清理资源  
    }  
}

BaseAgent 是一个 AI 智能体的抽象基类,它的职责是:

  1. 提供 代理状态管理(比如:空闲、运行中、完成、错误)
  2. 提供 基于步骤的执行循环(支持多轮交互或链式思维)
  3. 提供会话上下文管理(保存用户消息、模型响应)
  4. 提供 执行流程控制(如最大步数、状态转换、异常处理)
  5. 为子类封装公共逻辑,只要求子类实现具体的step() 方法(即每一步怎么做)

2.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();  
        }  
    }  
}

2.3、开发 ToolCallAgent 类

ToolCa‏llAgent 负责实现؜工具调用能力,继承自 R​eActAgent,具体‌实现了 think 和 ‏act 两个抽象方法。

但是 Spring 的 ChatClient 已经支持选择工具进行调用(内部完成了 think、act、observe),所以这里我们要自主实现,可以使用 Spring AI 提供的 手动控制工具执行。

/**
 * 处理工具调用的基础代理类,具体实现了 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();  
    }

    @Override
    public boolean think() {
        if (getNextStepPrompt() != null && !getNextStepPrompt().isEmpty()) {
            UserMessage userMessage = new UserMessage(getNextStepPrompt());
            getMessageList().add(userMessage);
        }
        List<Message> 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();
            List<AssistantMessage.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;
        }
    }
	
	  /**  
	 * 执行工具调用并处理结果  
	 *  
	 * @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;  
	}
}

2.3.1、think() 方法逻辑流程

作用: 让 AI 智能体去思考下一步该干啥,生成响应,看需不需要工具调用。

1、如果有 nextStepPrompt(下一步提示),
那么构造 UserMessage 加入消息列表。

2、用当前消息列表 + chatOptions 构造 Prompt。

3、调用 chatClient 执行 Prompt:

chatClient.prompt(prompt)
    .system(getSystemPrompt())
    .tools(availableTools)
    .call()
    .chatResponse();

生成 AI 响应(包含文本和工具调用请求)

4、拿到助手响应:

AssistantMessage assistantMessage = chatResponse.getResult().getOutput();
String result = assistantMessage.getText();
List<AssistantMessage.ToolCall> toolCallList = assistantMessage.getToolCalls();

提取思考文本和工具调用信息。

5、日志输出:

  1. 打印 AI 思考的文本。
  2. 打印工具调用个数、名字、参数。

6、根据工具调用情况:

没有工具调用:

  • 记录助手消息到消息列表。
  • 返回 false(不需要 act,思考结束)

有工具调用:

  • 返回 true(说明需要 act 去调用工具)

7、如果出错:

  • 记录错误消息到消息列表。
  • 返回 false。

2.3.2、act() 方法逻辑流程

1、检查 toolCallChatResponse 有没有工具调用:

没有:直接返回 “没有工具调用”

2、调用工具:

toolCallingManager.executeToolCalls(prompt, toolCallChatResponse);

执行所有工具调用
拿到执行结果和更新后的会话历史

3、更新消息列表

setMessageList(toolExecutionResult.conversationHistory());

确保上下文完整(包含工具响应)

4、格式化结果:
拼接每个工具的执行结果:

工具 xxx 完成了它的任务!结果: xxx、

打印到日志。

5、检查是否有调用 doTerminate 工具:
如果有,设置 AgentState.FINISHED

2.3.3、总结

think 方法:

ChatResponse chatResponse = getChatClient().prompt(prompt)
    .system(getSystemPrompt())
    .tools(availableTools)
    .call()
    .chatResponse();

这里调用了大模型,具体过程是:

  • 构建 Prompt(包含历史对话、system prompt、可用工具等)
  • 调用大模型(可能是 OpenAI / DashScope / 其他 LLM 提供方)
  • 大模型返回响应,封装成 ChatResponse,包括:①AI 生成的文本,②工具调用请求(如果有)

act 方法:

ToolExecutionResult toolExecutionResult = toolCallingManager.executeToolCalls(prompt, toolCallChatResponse);

这里 没有再次调用大模型,而是:

  • 根据 think 阶段大模型返回的工具调用请求
  • 真正去执行这些工具(你定义的业务逻辑、函数、API 调用等)
  • 把工具执行结果封装起来,更新消息历史
方法 是否调用大模型 作用
think() ✅ 调用了大模型 生成响应 + 决定要不要用工具
act() ❌ 没有调用大模型 执行工具,处理工具响应

think 是“问大模型”,act 是“用工具干活”,大模型只在 think 阶段被调用了一次。

3.1、在 tools 包下新建 TerminateTool

public class TerminateTool {  
  
    @Tool(description = """  
            Terminate the interaction when the request is met OR if the assistant cannot proceed further with the task.  
            "When you have finished all the tasks, call this tool to end the work.  
            """)  
    public String doTerminate() {  
        return "任务结束";  
    }  
}

2.4、修改 ToolRegistration,注册中止工具

@Configuration
public class ToolRegistration {
    @Bean
    public ToolCallback[] allTools() {
        FileOperationTool fileOperationTool = new FileOperationTool();
        WebScrapingTool webScrapingTool = new WebScrapingTool();
        ResourceDownloadTool resourceDownloadTool = new ResourceDownloadTool();
        TerminalOperationTool terminalOperationTool = new TerminalOperationTool();
        PDFGenerationTool pdfGenerationTool = new PDFGenerationTool();
        TerminateTool terminateTool = new TerminateTool();

        return ToolCallbacks.from(
                fileOperationTool,
                webScrapingTool,
                resourceDownloadTool,
                terminalOperationTool,
                pdfGenerationTool,
                terminateTool
        );
    }
}

2.5、完善 ‏act 方法

当调؜用了终止工具时,修​改 agent 的‌状态为 “已结束”‏:

@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"));
        boolean terminateToolCalled = toolResponseMessage.getResponses().stream()
                .anyMatch(response -> "doTerminate".equals(response.name()));
        if (terminateToolCalled) {
            setState(AgentState.FINISHED);
        }
        log.info(results);
        return results;
    }

2.6、开发 YuAnManus 类

YuAnManus 是‏可以直接提供给其他方法调用的 AI؜ 超级智能体实例,继承自 Tool​CallAgent,需要给智能体设‌置各种参数,比如对话客户端 cha‏tClient、工具调用列表等。

@Component
public class YuAnManus extends ToolCallAgent {
  
    public YuAnManus(ToolCallback[] allTools, ChatModel dashscopeChatModel) {
        super(allTools);  
        this.setName("yuAnManus");
        String SYSTEM_PROMPT = """  
                You are yuAnManus, 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);  
    }  
}

2.7、测试智能体

@SpringBootTest  
class YuManusTest {  
  
    @Resource  
    private YuManus yuManus;  
  
    @Test  
    void run() {  
        String userPrompt = """  
                我的另一半居住在上海静安区,请帮我找到 5 公里内合适的约会地点,  
                并结合一些网络图片,制定一份详细的约会计划,  
                并以 PDF 格式输出""";  
        String answer = yuManus.run(userPrompt);  
        Assertions.assertNotNull(answer);  
    }  
}

结果展示:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
生成的 PDF 文档
在这里插入图片描述

Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐