介绍:通过Spring AI自由调度工具,完成Agent的搭建

概念解说

智能体:基于对话的AI项目,它通过对话方式接收用户的输入,由大模型自动调用工具执行用户指定的业务流程,并生成回复。如Dify、Coze等。

JDK版本:17SpringBoot版本:3.4.5Spring AI版本:1.0.0

步骤一:引入依赖

版本管理

<spring-ai.version>1.0.0</spring-ai.version><spring-boot.version>3.4.5</spring-boot.version>
 <dependencyManagement>        <dependencies>            <dependency>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-dependencies</artifactId>                <version>${spring-boot.version}</version>                <type>pom</type>                <scope>import</scope>            </dependency>
            <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>

具体依赖

	 <dependency>            <groupId>org.springframework.ai</groupId>            <artifactId>spring-ai-starter-model-ollama</artifactId>        </dependency>
        <dependency>            <groupId>org.springframework.ai</groupId>            <artifactId>spring-ai-starter-model-openai</artifactId>        </dependency>

步骤二:配置信息

application.yml

server:  port: 端口spring:  application:    name: Agent-Demo  ai:    ollama:      base-url: http://localhost:11434      chat:        model: qwen2.5:3b    openai:      base-url: https://dashscope.aliyuncs.com/compatible-mode      api-key: 个人key      chat:        options:          model: qwen-plus

这里使用的ollama模型,如已安装,请使用cmd命令查看已有模型

ollama list

图片

模型可自定义,聊天模型即可,同时需要申请阿里云百炼账号

阿里云百炼地址:https://bailian.console.aliyun.com/console?tab=model#/model-market

图片

步骤三:智能体代码

[1]BaseAgent

@Component@Data@Slf4jpublic abstract class BaseAgent {    // 名称    protected String name;    protected String description;    protected String systemPrompt;    protected String nextStepPrompt;
    // llm 客户端    @Resource(name = "agentAssist")    protected ChatClient chatClient;
    // 智能体状态    protected AgentState state = AgentState.IDLE;
    // 消息列表    protected List<Message> msg = new ArrayList<>();
    // 最大步数    protected int maxSteps = 10;
    // 初始步数    protected int currentStep = 0;
    /**     * 执行     *     * @param message     * @param emitter     */    public void run(String message, SseEmitter emitter) {        log.info("智能体状态: {}", state.getState());
        addMessage("user", message);        setState(AgentState.RUNNING);
        log.info("current step: {}", currentStep);        try {            while (currentStep < maxSteps && state != AgentState.FINISHED) {                log.info("test info..");                currentStep++;                String stepResult = step();                String resMessage = String.format("{\"step\": %d, \"result\": %s}", currentStep, stepResult);                try {                    emitter.send(SseEmitter.event()                            .name("step" + currentStep)                            .data(resMessage)                            .id(String.valueOf(currentStep))                    );                    emitter.send("\n\n");                } catch (IOException e) {                    setState(AgentState.ERROR);                    emitter.send(SseEmitter.event()                            .name("error")                            .data("{\"error\": \"发送数据失败: " + e.getMessage() + "\"}")                            .id("error")                    );                    emitter.completeWithError(e);                    return;                }            }            emitter.complete();        } catch (Exception e) {            setState(AgentState.ERROR);            try {                emitter.send(SseEmitter.event()                        .name("error")                        .data("{\"error\": \"发送数据失败: " + e.getMessage() + "\"}")                        .id("error")                );                emitter.completeWithError(e);            } catch (IOException ex) {                log.info("执行异常: {}", ex.getMessage());            }        } finally {            cleanup();        }    }
    protected abstract String step();
    protected abstract void cleanup();
}

通过基底抽象类,定义核心执行流程,方便继承并使用

[2]ReActAgent

@Datapublic abstract class ReActAgent extends BaseAgent {    @Override    protected String step() {        boolean shouldAct = think();        System.out.println("shouldAct --> " + shouldAct);        if (!shouldAct) {            return "执行完成,无需再调用方法";        }        return act();    }
    protected abstract boolean think();
    protected abstract String act();}

定义执行抽象类,通过继承基底类,使其自定义功能和方法,实现功能扩展

[3]ToolCallAgent

@Component@Slf4jpublic class ToolCallAgent extends ReActAgent {    @Autowired    protected ToolCallbackProvider toolCallbackProvider;    protected List<AssistantMessage.ToolCall> toolCalls = new ArrayList<>();
    @Override    protected boolean think() {        ChatOptions options = ToolCallingChatOptions.builder()                .toolCallbacks(toolCallbackProvider.getToolCallbacks())                .internalToolExecutionEnabled(false)                .build();        ChatResponse chatResponse = chatClient.prompt(systemPrompt)                .system(nextStepPrompt)                .messages(msg)                .options(options)                .call()                .chatResponse();
        AssistantMessage output = chatResponse.getResult().getOutput();        toolCalls.addAll(output.getToolCalls());        log.info("---> 本轮挑选的工具:{}", toolCalls);        String content = output.getText();        addMessage("assistant", content);
        // 检查是否为终止请求        if (toolCalls.stream().anyMatch(toolCall -> "terminate".equals(toolCall.name()))) {            System.out.println("检测到终止请求,结束交互");            setState(AgentState.FINISHED);            toolCalls.clear();            return false;        }
        System.out.println("测试是否返回..");        return true;    }
    @Override    protected String act() {        // 技术群获取    }
    @Override    protected void cleanup() {       // 技术群获取    }}

通过工具类继承执行抽象类,实现具体的业务方法

[4]ManusAgent

@Componentpublic class ManusAgent extends ToolCallAgent {    @Resource(name = "agentAssist")    private ChatClient chatClient;
    public ManusAgent() {        this.name = "Java Manus";        this.description = "解决多个任务的多功能智能体";
        this.setSystemPrompt("""                您必须对所有用户交互以中文进行响应.你是Java Manus,一个先进的人工智能助手,旨在通过利用各种工具有效地解决任何用户任务.您的功能包括运行计算、处理文本、与远程服务交互以及在必要时终止任务.遵循这些步骤来处理任务:
                1.任务分析与分解                2.工具选择                3.参数格式化                4.异常处理                5.任务完成与终止
                例子:                - 用户任务:“查找最近的新闻并进行简单的计算。”                    1. 任务1:检索最近的新闻                     - 思考:这需要从网上获取信息                     - 执行:使用网页浏览工具,输入“最近的新闻”之类的查询关键字                     - 输出:总结出一些文章摘要                    2. 任务2:执行一个简单的计算                     - 思考:这需要执行一个数学表达式                     - 执行:使用代码执行工具,如使用“2+2”                     - 输出:结果为4                    3. 合并结果:用中文回复新闻摘要和计算结果                    4. 终止:如果所有子任务都已完成,则调用终止工具
                即使内部推理是用英语,也要用中文回答.如果对任务不确定,请与用户澄清或使用终止工具以避免无限循环.            """);        this.setNextStepPrompt("""                根据用户的请求和当前上下文,确定下一个操作.遵循以下步骤:
                1. 分析上下文                 - 查看用户的原始请求和最近的对话历史.                 - 标识当前子任务或任务计划中的下一步.                 - 检查之前的工具输出以获取相关信息.                2. 计划并行动                 - 确定当前子任务是否需要工具、进一步思考或终止.                 - 如果需要工具,请根据其功能选择最合适的工具.                 - 指定准确的参数,确保它们符合工具的需求(例如,字符串,JSON).                3. 执行或终止                 - 如果调用工具,请提供清晰的参数并预测输出.                 - 如果所有子任务都完成了(例如:检索到的信息、保存的文件),调用终止工具来完成任务.                 - 如果卡住了(例如:重复的错误,重复的操作,比如保存相同的文件),调用终止工具并用中文解释,例如:“任务已完成,无需重复操作”.                4. 日志推理                 - 简要解释你的思考过程(内部可以用英语,但用中文回答).                 - 注意在计划期间所做的任何挑战或假设.
                例子:                 - 上下文:用户询问最近的新闻和计算;检索到的新闻,正在进行计算.                    1.分析:下一个子任务是执行计算.                    2.计划:用一个数学表达式作为字符串来使用代码执行工具.                    3.执行:使用“2 + 2”之类的参数调用工具.                    4.推理:计算简单明了,期望得到数值结果.                    5.请用中文回答:"正在执行计算...".                    6.终止检查:如果所有子任务都已完成,则调用终止工具.
                如果调用工具失败,请分析错误,调整参数或工具后重试.如果多次尝试仍无进展或任务已完成,则调用终止工具并用中文进行解释.            """);        this.setMaxSteps(10);        this.setChatClient(chatClient);    }}

通过继承工具类并编写提示词,满足业务需求,通过@Component注解,方便Spring容器管理

步骤四:核心代码

[1]ManusController

@Validated@RequiredArgsConstructor@RestController@RequestMapping("/ai/agent/manus")public class ManusController {    @Autowired    private ManusAgent manusAgent;
    /**     * 执行     *     * @param message     * @return     */    @GetMapping(value = "/execute", produces = "text/html;charset=UTF-8")    public SseEmitter execute(@RequestParam(name = "message") String message) {        SseEmitter sseEmitter = new SseEmitter(-1L);        manusAgent.run(message, sseEmitter);        return sseEmitter;    }}

通过注入ManusAgent,实现智能体run方法调用,根据用户请求自动调用工具完成任务

[2]MathTool

@Servicepublic class MathTool {    @Tool(name = "add", description = "加法运算")    public String add(@ToolParam(description = "第一个数字") int a, @ToolParam(description = "第二个数字") int b) {        System.out.println("加法运算");        return String.valueOf((a + b));    }
    @Tool(name = "substract", description = "减法运算")    public String substract(@ToolParam(description = "第一个数字") int a, @ToolParam(description = "第二个数字") int b) {        System.out.println("减法运算");        return String.valueOf((a - b));    }
    @Tool(name = "multiply", description = "乘法运算")    public String multiply(@ToolParam(description = "第一个数字") int a, @ToolParam(description = "第二个数字") int b) {        System.out.println("乘法运算");        return String.valueOf((a * b));    }
    @Tool(name = "divide", description = "除法运算")    public String divide(@ToolParam(description = "第一个数字") int a, @ToolParam(description = "第二个数字") int b) {        System.out.println("除法运算");        return String.valueOf((a / b));    }}

业务工具,可以根据需求自定义,通过@Service交给Spring容器管理

[3]AgentConfig

@Configurationpublic class AgentConfig {    @Bean("toolCallbackProvider")    public ToolCallbackProvider toolCallbackProvider(MathTool mathTool) {        return MethodToolCallbackProvider                .builder()                .toolObjects(mathTool)                .build();    }}

将工具类告诉Spring AI,方便调用,如有多个工具,也是支持的

源码解析

图片

步骤五:测试

启动项目

图片

接口测试

图片

日志打印

图片

步骤六:架构解析

设计模式应用

1. 模板方法模式(Template Method Pattern)

public abstract class BaseAgent {
    public void run(String message, SseEmitter emitter) {}
    protected abstract String step(); // 模板方法
    protected abstract void cleanup();}

并且每个层级Agent都能扩展模板

# 模板方法模式概念定义一个操作中算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变算法的结构即可重定义该算法的某些特定步骤.

2. 责任链模式(Chain of Responsibility Pattern)

通过继承链形成处理责任传递:

BaseAgent -> ReActAgent -> ToolCallAgent -> ManusAgent

每个层级都增强了智能体的能力,上级不知道下级的具体表现,但共同完成智能体的功能

# 责任链模式概念如果有多个对象有机会处理请求,责任链可使请求的发送者和接受者解耦,请求沿着责任链传递,直到有一个对象处理了它为止.

3. 策略模式(Strategy Pattern)

体现在不同层级的智能体可以有不同的行为策略:

protected abstract boolean think();
protected abstract String act();

不同的Agent子类可以提供不同的think()和act()实现策略

# 策略模式概念策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户.

4.工厂方法模式(Factory Method Pattern)

通过Spring的@Component注解,这些智能体可以被Spring容器作为bean创建和管理

@Componentpublic class ManusAgent extends ToolCallAgent {// Spring容器会负责创建这个bean}

设计原则应用

  1. 开闭原则(Open Close Principle)

  2. 单一职责原则(Single Responsibility Principle)

  3. 依赖倒置原则(Dependence Inversion Principle)

# 开闭原则:对扩展开放,对修改关闭,抽象思维搭建结构,具体实现扩展细节.- 对扩展开放:新增智能体类型只需扩展基类- 对修改关闭:无需修改现有代码即可添加新功能
# 单一职责原则:要求一个接口或类只有一个原因引起变化,也就是说一个接口或一个类只有一个原则,它就只负责一件事.每个类都有明确的职责- BaseAgent:基础流程控制- ReActAgent:ReAct模式实现- ToolCallAgent:工具调用功能- ManusAgent:具体业务逻辑
# 依赖倒置原则:上层模块不应该依赖底层模块,它们都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象;面向接口编程;- 高层模块(BaseAgent)不依赖低层模块,都依赖抽象- 通过抽象方法(step(),think(),act())定义契约

至此,基于Spring AI搭建智能体Demo版结束啦,需要资料可以加技术群获取!

图片

本人正在打造技术交流群,欢迎志同道合的朋友一起探讨,一起努力,通过自己的努力,在技术岗位这条道路上走得更远。QQ群号:925317809 备注:技术交流 即可通过!

加入

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐