Spring AI alibaba 智能体构建
本文基于开源项目Open Manus的源码分析,整理了一个Java版智能体应用的开发框架。主要内容包括: 架构分析:Open Manus采用分层设计,核心模块包括Agent基类、工具层、沙箱环境等 BaseAgent实现:定义了智能体基本属性和运行流程,包括状态管理、记忆机制和核心run方法 ReActAgent扩展:继承BaseAgent并实现ReAct模式,通过think-act分离决策与执行
本文为个人学习笔记整理,仅供交流参考,非专业教学资料,内容请自行甄别。
文章目录
前言
本篇将参照开源的智能体Open Manus源码,学习源码的编程思想,构建一个Java版本的智能体应用。
一、Open Manus源码总体架构
  git hub Open Manus的代码仓库首先去下载源码,并且解压:
    源码的目录层级:
- app: 核心代码 
  - agent: 各类智能体实现与基类(如 base.py、浏览器/数据分析/SWE 等)
- prompt: 系统提示词与模板(如 planning.py、browser.py)
- tool: 工具层(终端、浏览器、搜索、可视化、Python 执行、文件操作等)
- sandbox: 沙箱执行环境客户端与管理(隔离执行、终端等)
- 其他:llm.py(模型封装)、config.py、logger.py、schema.py(数据结构)等
 
- assets: 资源文件(图片、Logo 等)
- config: 示例配置(模型、Ollama/Azure/Google、Daytona、MCP 等)与 mcp.example.json
- examples: 使用示例与用例(如旅行计划 demo)
- protocol: A2A 协议相关实现(多智能体执行器、入口、说明文档)
- tests\sandbox: 与沙箱相关的测试用例
- workspace: 运行过程中的工作区示例或临时文件
  重点需要关注,app包下的agent。其中BaseAgent,ReActAgent,ToolCallAgent和Manus是重点要学习的分层思想的体现:
- BaseAgent:主要定义了最基本的属性,以及step抽象方法(子类实现,在这里写“每一步要干什么”),run(主循环执行)的方法。- name/description: 智能体名字和描述。
- system_prompt/next_step_prompt: 系统级提示词、下一步提示词(指导智能体如何行动)。
- llm: 语言模型实例(负责生成内容)。
- memory: 记忆(保存消息历史)。
- state: 当前状态(空闲、运行中、完成、错误等,来自 AgentState 枚举)。
- max_steps/current_step: 最大步数与当前步数(防止无限循环)。
- duplicate_threshold: 判断“卡住”的阈值(重复回答次数的上限)
 
- ReActAgent:是前篇中提到的ReAct的体现,继承了BaseAgent,定义了think(思考,判断是否需要调用工具)和act(行动,调用工具)两个抽象方法,并且在这一层实现了父类的step方法。
- ToolCallAgent:是调用工具的类,继承了ReActAgent,实现了父类的think和act方法。
- Manus:用于初始化工具和MCP
二、BaseAgent编写
  参照Open Manus的BaseAgent,首先完成主要功能,即定义属性,run方法和step抽象方法,首先前期准备,需要创建一个AgentState的枚举,参照源码:
/**
 * 代理执行状态的枚举类  
 */  
public enum AgentState {  
  
    /**  
     * 空闲状态  
     */  
    IDLE,  
  
    /**  
     * 运行中状态  
     */  
    RUNNING,  
  
    /**  
     * 已完成状态  
     */  
    FINISHED,  
  
    /**  
     * 错误状态  
     */  
    ERROR  
}
2.1、定义属性和step抽象方法
/**
 * agent代理基础层
 */
@Data
@Slf4j
public abstract class BaseAgent {
    /**
     * 名称
     */
    private String name;
    /**
     * 系统提示词
     */
    private String systemPrompt;
    /**
     * 下一步的提示词
     */
    private String nextStepPrompt;
    /**
     * 状态,默认是空闲状态
     */
    private AgentState agentState = AgentState.IDLE;
    /**
     * 当前步骤,用于多轮推论
     */
    private Integer currentStep = 1;
    /**
     * 最大轮次
     */
    private Integer maxStep = 10;
    /**
     * 大模型客户端
     */
    private ChatClient chatClient;
    /**
     * 自主维护上下文记忆
     */
    private List<Message> memoryMessages = new ArrayList<>();
    /**
     * 定义每次循环中执行的步骤,子类实现
     *
     * @return
     */
    public abstract String step();
    /**
     * 清理资源
     */
    public void clearUp() {
        log.info("清理资源....");
    }
}
2.2、run方法
  run方法是BaseAgent的核心,其整体的工作流程:
- 检测当前状态必须是“空闲”。
- 如果传入了 request,会先把它作为用户消息写入记忆。
- 切换到“运行中”,然后循环执行步骤:
 - 步数自增,记录日志
 - 调用 step() 执行一步(这是留给子类实现的核心逻辑)
 - 检查是否“卡住”,若卡住则调整策略提示
 - 记录每一步的结果
 - 若达到最大步数,会停止并把状态还原为“空闲”
 - 最后清理沙箱资源(SANDBOX_CLIENT.cleanup())
- 返回每一步的结果汇总文本
  我们先实现一个最基本的,不考虑源码中的is_stuck检查是否“卡住”,后续再进行优化:
   /**
     * 运行代理
     *
     * @param text
     * @return
     */
    public String run(String text) {
        if (!agentState.equals(AgentState.IDLE)) {
            throw new RuntimeException("当前代理 非空闲状态!");
        }
        if (StrUtil.isBlank(text)) {
            throw new RuntimeException("用户输入信息不能为空!");
        }
        //修改状态
        agentState = AgentState.RUNNING;
        //记录上下文信息(用户提示词)
        memoryMessages.add(new UserMessage(text));
        ArrayList<String> list = new ArrayList<>();
        try {
            //循环条件 小于最大轮次,并且状态不为结束
            for (int i = 0; i < maxStep && agentState != AgentState.FINISHED; i++) {
                //执行具体的操作,子类实现
                String stepResult = step();
                stepResult = stepResult + "当前轮次" + currentStep;
                log.info("当前轮次:{}", currentStep);
                //当前轮次 + 1
                currentStep = currentStep + 1;
                list.add(stepResult);
                //检查是否超过了步数限制
                if (currentStep >= maxStep) {
                    agentState = AgentState.FINISHED;
                    list.add("终止,到达最大步数(" + maxStep + ")");
                }
            }
            return String.join("/n", list);
        } catch (Exception e) {
            agentState = AgentState.ERROR;
            log.error("执行错误,原因:", e);
            return "执行错误" + e.getMessage();
        } finally {
            this.clearUp();
        }
    }
三、ReActAgent编写
  ReActAgent相对比较简单,需要继承BaseAgent,并且重写step方法,然后还需要定义think和act方法,参照源码,其中在step方法中,如果think方法返回的为false,则直接return掉,不会再去调用工具。
@EqualsAndHashCode(callSuper = true)
@Data
@Slf4j
public abstract class ReActAgent extends BaseAgent{
    /**
     * 定义每次循环中执行的步骤,子类实现
     *
     * @return
     */
    @Override
    public String step() {
        try {
            if (!think()){
                return "思考完成 - 无需行动";
            }
            return act();
        } catch (Exception e) {
            log.error("步骤执行失败,原因:",e);
            return "步骤执行失败:" + e.getMessage();
        }
    }
    /**
     * 思考 判断是否需要调用工具
     * @return
     */
    public abstract boolean think();
    /**
     * 行动 调用工具
     * @return
     */
    public abstract String act();
}
四、ToolCallAgent编写
  ToolCallAgent需要继承ReActAgent,重写think和act两个方法,首先定义属性和构造方法:
@EqualsAndHashCode(callSuper = true)
@Data
@Slf4j
public class ToolCallAgent extends ReActAgent {
    /**
     * 定义可用的工具
     */
    private ToolCallback[] availableTools;
    /**
     * 定义可用的工具,这里使用mcp
      */
//    private ToolCallbackProvider toolCallbackProvider;
    /**
     * 定义保存工具调用信息的响应结果
     */
    private ChatResponse toolCallChatResponse;
    /**
     * 定义工具调用的管理者(调用工具,由用户手动完成,非依赖Spring AI框架)
     */
    private ToolCallingManager toolCallingManager;
    /**
     * 禁用框架自动调用工具的上下文
     */
    private ChatOptions chatOptions;
    public ToolCallAgent(ToolCallback[] availableTools) {
        super();
        this.availableTools = availableTools;
        this.toolCallingManager = ToolCallingManager.builder().build();
        //自己维护上下文
        this.chatOptions = DashScopeChatOptions.builder().withProxyToolCalls(true).build();
    }
}
4.1、实现think方法
 &emsp同样可以参照源码,源码中的think方法,主要做了以下的步骤:
    如果有next_step_prompt(下一步提示词),就将其存入自己维护的对话记忆中,作为用户提示词:

    然后调用大模型,并且处理结果,这里对于异常的处理,如果是重试异常 - token 超出限制,则特殊处理,记录上下文,改状态,返回:
    对于响应进行解析,主要是获取返回的结果和工具列表(大模型认为是否需要使用工具),这里会记录各种日志:
  源码中think分支判断较为全面,参照源码实现think的主要逻辑:
    /**
     * 思考 判断是否需要调用工具
     *
     * @return
     */
    @Override
    public boolean think() {
        //下一步提示词不为空
        if (StrUtil.isNotBlank(getNextStepPrompt())) {
            //作为用户提示词,加入到上下文记忆中
            getMemoryMessages().add(new UserMessage(getNextStepPrompt()));
        }
        //得到最新的上下文
        List<Message> memoryMessages = getMemoryMessages();
        Prompt prompt = new Prompt(memoryMessages, chatOptions);
        try {
            //获取带有工具选项的响应
            toolCallChatResponse = getChatClient().
                    //预设prompt
                            prompt(prompt).
                    //系统提示词
                            system(getSystemPrompt())
                    //设置工具
                    .tools(availableTools)
                    .call()
                    .chatResponse();
            //得到助手提示词
            AssistantMessage assistantMessage = toolCallChatResponse.getResult().getOutput();
            String text = assistantMessage.getText();
            List<AssistantMessage.ToolCall> toolCalls = assistantMessage.getToolCalls();
            log.info("{}的思考:{}", getName(), text);
            log.info("{}选择了{}个工具使用", getName(), toolCalls.size());
            String toolInfo = toolCalls.stream().map(toolCall -> "工具名称" + toolCall.name() + "工具参数" + toolCall.arguments()).collect(Collectors.joining("/n"));
            log.info(toolInfo);
            if (CollUtil.isEmpty(toolCalls)) {
                log.info("{}本次无需使用工具", getName());
                getMemoryMessages().add(assistantMessage);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("{}的思考过程遇到了问题:",getName(),e);
            //记录上下文
            getMemoryMessages().add(new AssistantMessage("处理时遇到错误" + e.getMessage()));
            return false;
        }
    }
4.2、实现act方法
  在源码中,act方法就比think方法简单的多,这里比我们自己的实现多了一个策略的枚举,表示是否需要使用工具:
    首先进行判断,检查是否有工具要执行:
- 没有 tool_calls 且策略是 REQUIRED:抛出“必须工具调用但没有提供”的错误。
- 没有 tool_calls 且非 REQUIRED:直接返回最后一条消息的文本(或默认提示)。

  然后执行所有的工具,拼接上下文,然后返回:
  在我们自己的实现中,不需要这样做,因为在框架中,已经帮我们拼接好了上下文,如果有多个工具的场景下,只要获取最新的一条聊天记录即可,包含了之前所有的上下文记录。
    自己实现的act方法:
    /**
     * 行动 调用工具
     *
     * @return
     */
    @Override
    public String act() {
        if (!toolCallChatResponse.hasToolCalls()){
            return "没有工具调用";
        }
        //准备调用工具
        Prompt prompt = new Prompt(getMemoryMessages(), chatOptions);
        //调用工具
        ToolExecutionResult toolExecutionResult = toolCallingManager.executeToolCalls(prompt, toolCallChatResponse);
        //记录消息上下文 conversationHistory中已经包含了历史的消息
        setMemoryMessages(toolExecutionResult.conversationHistory());
        //返回当前工具调用的结果
        ToolResponseMessage toolResponseMessage = (ToolResponseMessage) CollUtil.getLast(toolExecutionResult.conversationHistory());
        String resp = toolResponseMessage.getResponses().stream().map(toolResponse -> "工具" + toolResponse.name() + "完成了工作,结果" + toolResponse.responseData()).collect(Collectors.joining());
        //判断是否调用了终止工具
        boolean aniedMatch = toolResponseMessage.getResponses().stream().anyMatch(toolResponse -> "doTerminate".equals(toolResponse.name()));
        if (aniedMatch){
            //修改状态为终止
            setAgentState(AgentState.FINISHED);
        }
        log.info(resp);
        return resp;
    }
五、Manus编写
源码中的Manus,主要是完成了初始化本地工具和MCP,这五个工具分别是:
- PythonExecute - Python 代码执行工具:在隔离环境中执行 Python 代码,支持超时控制和安全限制。
- BrowserUseTool - 浏览器自动化工具:可以控制网页操作和提取信息。
- StrReplaceEditor - 文件编辑工具:文件查看、创建和编辑工具,支持沙箱环境。
- AskHuman - 人机交互工具:让智能体向人类用户询问信息或确认。
- Terminate - 任务结束工具:当任务完成或无法继续时,结束智能体的执行。

    在工具使用这一篇中,我们已经自定义了一些工具,还需要补充定义一个Terminate - 任务结束工具,描述也是参照源码的实现:
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 "任务结束";  
    }  
}
完整的Manus:
@Component
public class RagdollCatManus extends ToolCallAgent {
    public RagdollCatManus(ToolCallback[] availableTools, ChatModel dashscopeChatModel) {
        super(availableTools);
        //设置应用名称
        this.setName("RagdollCatManus");
        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);
        // 初始化客户端
        ChatClient chatClient = ChatClient.builder(dashscopeChatModel)
                .defaultAdvisors(new MyLogAdvisor())
                .build();
        this.setChatClient(chatClient);
    }
}
六、进行测试
@SpringBootTest
class RagdollCatManusTest {
    @Resource
    private RagdollCatManus ragdollCatManus;
    @Test
    public void test(){
        String result = ragdollCatManus.run("帮我生成一份java学习路线,以pdf的格式输出");
        Assertions.assertNotNull(result);
    }
}
观察日志打印,第一轮(结果部分是抓取的网页,内容过多截取掉了)
2025-10-06T09:58:00.517+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.advisor.MyLogAdvisor   : 自定义日志拦截器,AI request: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.
2025-10-06T09:58:01.386+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.advisor.MyLogAdvisor   : 自定义日志拦截器,AI response:
2025-10-06T09:58:01.387+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.agent.ToolCallAgent    : RagdollCatManus的思考:
2025-10-06T09:58:01.387+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.agent.ToolCallAgent    : RagdollCatManus选择了1个工具使用
2025-10-06T09:58:01.387+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.agent.ToolCallAgent    : 工具名称scrapeWebPage工具参数{"url": "https://roadmap.sh/java"}
2025-10-06T09:58:01.716+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.agent.ToolCallAgent    : 工具scrapeWebPage完成了工作,结果""
2025-10-06T09:58:01.726+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.agent.BaseAgent        : 当前轮次:1
  第二轮,调用自定义的generatePDF生成pdf:
2025-10-06T09:58:01.726+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.advisor.MyLogAdvisor   : 自定义日志拦截器,AI request: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.
2025-10-06T09:58:26.119+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.advisor.MyLogAdvisor   : 自定义日志拦截器,AI response:我已成功从网页 `https://roadmap.sh/java` 抓取了 Java 学习路线的内容。该内容包括成为现代 Java 开发者的分步指南,涵盖了学习步骤、资源、面试问题和测验等。
接下来,我将整理这些信息并生成一份详细的 Java 学习路线 PDF 文件。现在我将使用 `generatePDF` 工具来创建这个文件。
2025-10-06T09:58:26.119+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.agent.ToolCallAgent    : RagdollCatManus的思考:我已成功从网页 `https://roadmap.sh/java` 抓取了 Java 学习路线的内容。该内容包括成为现代 Java 开发者的分步指南,涵盖了学习步骤、资源、面试问题和测验等。
接下来,我将整理这些信息并生成一份详细的 Java 学习路线 PDF 文件。现在我将使用 `generatePDF` 工具来创建这个文件。
2025-10-06T09:58:26.119+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.agent.ToolCallAgent    : RagdollCatManus选择了1个工具使用
2025-10-06T09:58:26.120+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.agent.ToolCallAgent    : 工具名称generatePDF工具参数{"content": "<h1>Java Developer 学习路线</h1><p>这是一份成为现代 Java 开发者的分步指南。</p><h2>什么是 Java 开发者?</h2><p>Java 开发者是专门使用 Java 语言开发应用程序的软件工程师。他们的工作包括构建 Web 应用(如 AEM 或 Liferay)、桌面应用(如 Eclipse)和企业系统(如 ERP 和 CRM 系统)。他们的职责包括编写代码、解决技术问题以及确保软件的性能和可靠性。</p><h2>Java 开发者做什么?</h2><p>Java 开发者大部分时间都在为各种应用程序创建 Java 程序,包括 Web 应用、桌面应用和大规模企业系统。他们负责设计、编写、测试和维护代码以确保性能、功能和安全性。</p><h2>需要什么技能才能成为 Java 开发者?</h2><p>要成为一名 Java 开发者,你需要掌握多种技能。首先,你需要对 Java 编程语言及其生态系统有扎实的基础,并理解软件工程原理。深入学习 JVM、并发和内存管理对于在大型企业系统上工作至关重要。如果你正在处理 RESTful API,了解 Hibernate 技术和拥有 Oracle(或其他类型的 SQL)数据库经验对后端 Java 开发者来说非常重要。</p><h2>如何成为 Java 开发者?</h2><p>要成为 Java 开发者,强烈建议遵循结构化的学习过程(例如,这份 <a href='https://roadmap.sh/java'>Java 路线图</a> 是一个很好的起点)。首先关注学习 Java 编程语言,理解其核心概念、类型系统和一些面向对象编程(鉴于 Java 高度基于此)。一旦熟悉了 Java,开始练习开发小型 Java 程序;它们会给你机会面对现实世界的问题并找到创造性的解决方案。在此过程中,尝试研究最佳实践并理解开发方法论,以帮助你建立专业知识。</p>", "fileName": "java_learning_roadmap.pdf"}
2025-10-06T09:58:26.470+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.agent.ToolCallAgent    : 工具generatePDF完成了工作,结果"PDF generated successfully to: D:\\Idea_workspace\\2025\\second-ai-agent/tmp/pdf/java_learning_roadmap.pdf"
2025-10-06T09:58:26.470+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.agent.BaseAgent        : 当前轮次:2
  第三轮,生成完成,调用TerminateTool,结束任务。
2025-10-06T09:58:26.471+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.advisor.MyLogAdvisor   : 自定义日志拦截器,AI request: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.
2025-10-06T09:58:44.105+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.advisor.MyLogAdvisor   : 自定义日志拦截器,AI response:我已成功生成了 Java 学习路线的 PDF 文件。文件已保存到 `D:\Idea_workspace\2025\second-ai-agent\tmp\pdf\java_learning_roadmap.pdf`。
这个 PDF 包含了成为现代 Java 开发者的分步指南,涵盖了:
- 什么是 Java 开发者
- Java 开发者的工作内容
- 成为 Java 开发者所需的技能
- 如何成为 Java 开发者的建议路径
现在,我已经完成了您请求的任务:生成一份 Java 学习路线并以 PDF 格式输出。
2025-10-06T09:58:44.105+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.agent.ToolCallAgent    : RagdollCatManus的思考:我已成功生成了 Java 学习路线的 PDF 文件。文件已保存到 `D:\Idea_workspace\2025\second-ai-agent\tmp\pdf\java_learning_roadmap.pdf`。
这个 PDF 包含了成为现代 Java 开发者的分步指南,涵盖了:
- 什么是 Java 开发者
- Java 开发者的工作内容
- 成为 Java 开发者所需的技能
- 如何成为 Java 开发者的建议路径
现在,我已经完成了您请求的任务:生成一份 Java 学习路线并以 PDF 格式输出。
2025-10-06T09:58:44.105+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.agent.ToolCallAgent    : RagdollCatManus选择了1个工具使用
2025-10-06T09:58:44.105+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.agent.ToolCallAgent    : 工具名称doTerminate工具参数{}
2025-10-06T09:58:44.106+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.agent.ToolCallAgent    : 工具doTerminate完成了工作,结果"任务结束"
2025-10-06T09:58:44.106+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.agent.BaseAgent        : 当前轮次:3
2025-10-06T09:58:44.106+08:00  INFO 14576 --- [second-ai-agent] [           main] o.r.secondaiagent.agent.BaseAgent        : 清理资源....

更多推荐
 
 



所有评论(0)