Spring AI Alibaba 多Agent协作

下面的代码基于前面的内容: https://blog.csdn.net/DavidSoCool/article/details/160857790

文档:https://java2ai.com/docs/frameworks/agent-framework/advanced/multi-agent

下面内容分为四大块:顺序执行\并发执行\路由

systemPrompt 定义路由决策的整体框架,instruction 提供具体的路由指导。

一 顺序执行(Sequential Agent)

功能描述:A根据用户的输入生成谜语,B根据A出的谜语猜答案.

代码:

1.新增SequentialAgentConfig.java

package com.david.springalibabareactagentdemo.config;

import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.agent.flow.agent.SequentialAgent;
import com.alibaba.cloud.ai.graph.exception.GraphRunnerException;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.ai.deepseek.api.DeepSeekApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
public class SequentialAgentConfig {

    @Value("${spring.ai.deepseek.api-key}")
    private String apiKey;

    @Bean
    public SequentialAgent sequentialAgent() {

        // 创建 ChatModel
        DeepSeekApi deepSeekApi = DeepSeekApi.builder()
                .apiKey(apiKey)
                .build();

        ChatModel chatModel = DeepSeekChatModel.builder()
                .deepSeekApi(deepSeekApi)
                .build();

        // 创建专业化的子Agent
        ReactAgent riddleAgent = ReactAgent.builder()
                .name("riddle_agent")
                .model(chatModel)
                .description("出谜语Agent")
                .instruction("""
                你是一个谜语专家,擅长出谜语。请根据用户的输入出谜语:{input}。
                最终只返回谜语,不要包含答案信息。
                """)
                // 设置为 false 可以让子 Agent 专注于自己的任务,不受父流程复杂上下文的影响。
                .includeContents(false)
                .outputKey("riddle")
                .build();

        ReactAgent guessAgent = ReactAgent.builder()
                .name("guess_agent")
                .model(chatModel)
                .description("猜谜语Agent")
                .instruction("""
                                你是一个猜谜语专家,擅长对猜谜语,待猜的谜语:
                                {riddle}
                                最终只返回谜语答案,不要包含任何信息。
                        """)
                // 设置为 false 可以让子 Agent 专注于自己的任务,不受父流程复杂上下文的影响。
                .includeContents(false)
                .outputKey("riddle_answer")
                .build();

        // 创建顺序Agent
        SequentialAgent sequentialAgent = SequentialAgent.builder()
                .name("blog_agent")
                .description("根据用户给定的描述出一个谜语,然后让猜谜语的Agent进行猜谜")
                .subAgents(List.of(riddleAgent, guessAgent))
                .build();
        return sequentialAgent;
    }
}

2.新增接口及调用

/**
     * 测试顺序执行多个Agent
     *
     * @param message
     * @return
     * @throws GraphRunnerException
     */
    @RequestMapping("/sequentialAgentChat")
    public Map<String, String> sequentialAgentChat(@RequestParam(value = "message") String message) throws GraphRunnerException {
        Map<String, String> map = new HashMap<>();
        // 使用
        Optional<OverAllState> result = sequentialAgent.invoke(message);

        if (result.isPresent()) {
            OverAllState state = result.get();

            // 访问第一个Agent的输出
            state.value("riddle").ifPresent(riddle -> {
                if (riddle instanceof AssistantMessage) {
                    String riddleStr = ((AssistantMessage) riddle).getText();
                    System.out.println("谜语: " + riddleStr);
                    map.put("谜语", riddleStr);
                }
            });

            // 访问第二个Agent的输出
            state.value("riddle_answer").ifPresent(riddleAnswer -> {
                if (riddleAnswer instanceof AssistantMessage) {
                    String riddleAnswerStr = ((AssistantMessage) riddleAnswer).getText();
                    System.out.println("答案: " + riddleAnswerStr);
                    map.put("猜的答案", riddleAnswerStr);
                }
            });
        }
        return map;
    }

3.启动服务并测试

这里可以看到日志输出的结果,大部分还是可以才对的,从猜奇异果有多种叫法的结果可以看出includeContents =false也是生效的,子Agent的上下文是隔离的

二 并行执行(Parallel Agent)

功能描述:A根据用户的输入生成菜名,B根据用户输入生成植物学名.

代码:

1.新增ParallelAgentConfig.java

package com.david.springalibabareactagentdemo.config;

import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.agent.flow.agent.ParallelAgent;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.ai.deepseek.api.DeepSeekApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
public class ParallelAgentConfig {

    @Value("${spring.ai.deepseek.api-key}")
    private String apiKey;

    @Bean
    public ParallelAgent parallelAgent() {

        // 创建 ChatModel
        DeepSeekApi deepSeekApi = DeepSeekApi.builder()
                .apiKey(apiKey)
                .build();

        ChatModel chatModel = DeepSeekChatModel.builder()
                .deepSeekApi(deepSeekApi)
                .build();

        // 创建多个专业化Agent
        ReactAgent cookAgent = ReactAgent.builder()
                .name("cook_agent")
                .model(chatModel)
                .description("专业厨师的AI助手")
                .instruction("""
                        你是一个擅长做饭的厨师,擅长根据食材输出可以做的菜。
                        用户会给你食材:{input},你只需根据食材输出可以做的菜名,不要包含任何信息。
                        """)
                .outputKey("cook_result")
                // 设置为 false 可以让子 Agent 专注于自己的任务,不受父流程复杂上下文的影响。
                .includeContents(false)
                .build();

        ReactAgent plantAgent = ReactAgent.builder()
                .name("plant_agent")
                .model(chatModel)
                .description("专业植物学家的AI助手")
                .instruction("""
                        你是一个专业的植物学家,擅长植物学。
                        "用户会给你的植物是:{input},你只需要输出植物学名,不要包含任何信息。
                        """)
                .outputKey("plant_result")
                // 设置为 false 可以让子 Agent 专注于自己的任务,不受父流程复杂上下文的影响。
                .includeContents(false)
                .build();

        // 创建并行Agent
        ParallelAgent parallelAgent = ParallelAgent.builder()
                .name("parallel_creative_agent")
                .description("并行执行多个任务,包括输出菜名和植物学名")
                .mergeOutputKey("merged_results")
                .subAgents(List.of(cookAgent, plantAgent))
                .mergeStrategy(new ParallelAgent.DefaultMergeStrategy())
                .build();

        return parallelAgent;
    }
}

2.新增接口及调用

/**
     * 测试并行执行多个Agent
     *
     * @param message
     * @return
     * @throws GraphRunnerException
     */
    @RequestMapping("/parallelAgentChat")
    public Map<String, String> parallelAgentChat(@RequestParam(value = "message") String message) throws GraphRunnerException {
        Map<String, String> map = new HashMap<>();
        // 使用
        Optional<OverAllState> result = parallelAgent.invoke(message);
        System.out.println("输入: " + message);
        map.put("输入", message);

        if (result.isPresent()) {
            OverAllState state = result.get();

            // 访问各个Agent的输出
            state.value("cook_result").ifPresent(r -> {
                if (r instanceof AssistantMessage) {
                    String text = ((AssistantMessage) r).getText();
                    System.out.println("cook_result: " + text);
                    map.put("cook_result", text);
                }
            });
            state.value("plant_result").ifPresent(r ->{
                if (r instanceof AssistantMessage) {
                    String text = ((AssistantMessage) r).getText();
                    System.out.println("plant_result: " + text);
                    map.put("plant_result", text);
                }
            });

            // 访问合并后的结果
            state.value("merged_results").ifPresent(r -> {
                System.out.println("merged_results: " + r);
                map.put("merged_results", r.toString());
            });
        }
        return map;
    }

3.启动服务并测试

三 路由(LlmRoutingAgent)

功能描述: 根据用户的输入路由到不同职责的Agent

为了提高路由的准确性,需要注意以下几点:

        a. 提供清晰明确的Agent描述, 子Agent的 description 非常重要,它告诉LLM何时应该选择该Agent

        b.明确Agent的职责边界

        c. 使用不同领域的Agent避免重叠

        d. 使用systemPrompt 用于设置路由决策的系统提示

代码:

1.新增RoutingAgentConfig.java

package com.david.springalibabareactagentdemo.config;

import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.agent.flow.agent.LlmRoutingAgent;
import com.alibaba.cloud.ai.graph.agent.flow.agent.ParallelAgent;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.ai.deepseek.api.DeepSeekApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
public class RoutingAgentConfig {

    @Value("${spring.ai.deepseek.api-key}")

    private String apiKey;

    final String ROUTING_SYSTEM_PROMPT = """
            你是一个智能的内容路由Agent,负责根据用户需求将任务路由到最合适的专家Agent。
            
            ## 你的职责
            1. 仔细分析用户输入的意图和需求
            2. 根据任务特性,选择最合适的专家Agent
            3. 确保路由决策准确、高效
            
            ## 可用的子Agent及其职责
            
            ### cook_agent
            - **功能**: 擅长做饭的厨师,擅长根据蔬菜输出可以做的菜品
            - **适用场景**:
            * 用户需要知道如何烹饪
            * 用户需要知道如何食用
            - **输出**: cook_result
            
            ### plant_agent
            - **功能**: 专业的植物学家,擅长植物学
            - **适用场景**:
            * 用户需要了解这个植物
            - **输出**: plant_result
            
            ## 决策规则
            
            1. **烹饪任务**: 如果用户需要知道如何烹饪,选择 cook_agent
            2. **植物任务**: 如果用户需要了解植物,选择 plant_agent
            
            ## 响应格式
            只返回Agent名称(cook_agent、plant_agent),不要包含其他解释。
            """;


    @Bean
    public LlmRoutingAgent llmRoutingAgent() {

        // 创建 ChatModel
        DeepSeekApi deepSeekApi = DeepSeekApi.builder()
                .apiKey(apiKey)
                .build();

        ChatModel chatModel = DeepSeekChatModel.builder()
                .deepSeekApi(deepSeekApi)
                .build();

        // 创建专业化的子Agent
        ReactAgent cookAgent = ReactAgent.builder()
                .name("cook_agent")
                .model(chatModel)
                .description("专业厨师的AI助手")
                .instruction("""
                        你是一个擅长做饭的厨师,擅长根据蔬菜输出可以做的菜品。
                        用户会给你蔬菜:{input},你只需根据蔬菜输出制作的菜品,不要包含任何信息。
                        """)
                .outputKey("cook_result")
                // 设置为 false 可以让子 Agent 专注于自己的任务,不受父流程复杂上下文的影响。
                .includeContents(false)
                .build();

        ReactAgent plantAgent = ReactAgent.builder()
                .name("plant_agent")
                .model(chatModel)
                .description("专业植物学家的AI助手")
                .instruction("""
                        你是一个专业的植物学家,擅长植物学。
                        "用户会给你的植物是:{input},你只需要输出植物简介,不要包含任何信息。
                        """)
                .outputKey("plant_result")
                // 设置为 false 可以让子 Agent 专注于自己的任务,不受父流程复杂上下文的影响。
                .includeContents(false)
                .build();

        // 创建路由Agent
        LlmRoutingAgent routingAgent = LlmRoutingAgent.builder()
                .name("content_routing_agent")
                .description("根据用户需求智能路由到合适的专家Agent")
                .model(chatModel)
                .subAgents(List.of(cookAgent, plantAgent))
                .systemPrompt(ROUTING_SYSTEM_PROMPT)
                .build();

        return routingAgent;
    }
}

2.新增接口及调用

/**
     * 测试路由Agent
     *
     * @param message
     * @return
     * @throws GraphRunnerException
     */
    @RequestMapping("/llmRoutingAgentChat")
    public Map<String, String> llmRoutingAgentChat(@RequestParam(value = "message") String message) throws GraphRunnerException {
        Map<String, String> map = new HashMap<>();
        // 使用
        Optional<OverAllState> result = llmRoutingAgent.invoke(message);
        log.info("输入: " + message);
        map.put("输入", message);
        if (result.isPresent()) {
            OverAllState state = result.get();
            List<AbstractMessage> messageList = (List<AbstractMessage>) state.data().get("messages");
            // messageList不为空获取最后一条消息
            if (messageList != null && !messageList.isEmpty()) {
                AbstractMessage lastMessage = messageList.get(messageList.size() - 1);
                String response = lastMessage.getText();
                log.info("result: " + response);
                map.put("result", response);
            }
        }
        return map;
    }

3.启动服务并测试

从控制台的日志可以看出,是正确路由到了指定的Agent

输入"介绍下西兰花"

输入"西兰花怎么吃"

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值