AI项目博客
记录 AI 工程实践与大模型应用

Spring AI Alibaba 86|Graph Core · 多智能体监督者模式 · 实现示例

多智能体监督者模式是一种常见的多智能体系统架构,其中有一个监督者智能体负责协调和路由任务到不同的工作智能体。

在多智能体监督者模式中:

  • Supervisor Agent(监督者智能体):负责路由到不同的 worker agents
  • Worker Agents(工作智能体):执行具体任务的智能体,如 Researcher Agent、Coder Agent 等
import com.alibaba.cloud.ai.graph.CompiledGraph;
import com.alibaba.cloud.ai.graph.KeyStrategy;
import com.alibaba.cloud.ai.graph.KeyStrategyFactory;
import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.StateGraph;
import com.alibaba.cloud.ai.graph.action.NodeAction;
import com.alibaba.cloud.ai.graph.exception.GraphStateException;
import com.alibaba.cloud.ai.graph.state.strategy.AppendStrategy;
import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.function.FunctionToolCallback;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;

import static com.alibaba.cloud.ai.graph.StateGraph.END;
import static com.alibaba.cloud.ai.graph.StateGraph.START;
import static com.alibaba.cloud.ai.graph.action.AsyncEdgeAction.edge_async;
import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async;

public class MultiAgentSupervisorExample {

  private final ChatModel chatModel;
  private final ChatModel chatModelWithTool;

  public MultiAgentSupervisorExample(ChatModel chatModel, ChatModel chatModelWithTool) {
      this.chatModel = chatModel;
      this.chatModelWithTool = chatModelWithTool;
  }

  /**
   * 创建 Multi-Agent Supervisor Graph
   */
  public CompiledGraph createSupervisorGraph() throws GraphStateException {
      // 定义状态管理策略
      KeyStrategyFactory keyStrategyFactory = () -> {
          HashMap<String, KeyStrategy> strategies = new HashMap<>();
          strategies.put("messages", new AppendStrategy());
          strategies.put("next", new ReplaceStrategy());
          return strategies;
      };

      // 创建 agents
      String[] members = {"researcher", "coder"};
      SupervisorNode supervisor = new SupervisorNode(chatModel, members);
      ResearcherNode researcher = new ResearcherNode(chatModelWithTool);
      CoderNode coder = new CoderNode(chatModelWithTool);

      // 构建 StateGraph
      StateGraph workflow = new StateGraph(keyStrategyFactory)
              .addNode("supervisor", node_async(supervisor))
              .addNode("researcher", node_async(researcher))
              .addNode("coder", node_async(coder))
              .addEdge(START, "supervisor")
              .addConditionalEdges(
                      "supervisor",
                      edge_async(state -> {
                          String next = (String) state.value("next").orElse("FINISH");
                          return next;
                      }),
                      Map.of(
                              "FINISH", END,
                              "researcher", "researcher",
                              "coder", "coder"
                      )
              )
              .addEdge("researcher", "supervisor")
              .addEdge("coder", "supervisor");

      return workflow.compile();
  }

  /**
   * Supervisor Agent Node
   * 负责决定将任务路由到哪个 worker
   */
  public static class SupervisorNode implements NodeAction {
      private final ChatClient chatClient;
      private final String[] members;

      public SupervisorNode(ChatModel model, String[] members) {
          this.chatClient = ChatClient.builder(model).build();
          this.members = members;
      }

      @Override
      public Map<String, Object> apply(OverAllState state) throws Exception {
          // 获取最后一条消息
          List<Object> messages = (List<Object>) state.value("messages").orElse(List.of());
          if (messages.isEmpty()) {
              throw new IllegalStateException("No messages in state");
          }

          // 获取最后一条消息的文本内容
          String lastMessageText = extractTextFromMessage(messages.get(messages.size() - 1));

          // 构建系统提示
          String membersList = String.join(", ", members);
          String systemPrompt = String.format(
                  "你是一个 supervisor,负责管理以下 workers 之间的对话:%s。
" +
                          "根据以下用户请求,响应应该由哪个 worker 来处理。
" +
                          "每个 worker 将执行任务并返回结果和状态。
" +
                          "当任务完成时,响应 FINISH。
" +
                          "只返回 worker 名称(%s)或 FINISH,不要返回其他内容。",
                  membersList, membersList
          );

          // 调用 LLM 决定路由
          String result = chatClient.prompt()
                  .system(systemPrompt)
                  .user("用户消息: " + lastMessageText)
                  .call()
                  .content();

          // 清理结果,确保只返回 worker 名称或 FINISH
          String next = normalizeRoute(result, members);

          return Map.of("next", next);
      }

      /**
       * 规范化路由结果
       */
      private String normalizeRoute(String result, String[] members) {
          if (result == null || result.trim().isEmpty()) {
              return "FINISH";
          }

          String normalized = result.trim().toLowerCase();

          // 检查是否是 FINISH
          if (normalized.equals("finish") || normalized.contains("finish")) {
              return "FINISH";
          }

          // 检查是否匹配任何成员
          for (String member : members) {
              if (normalized.equals(member.toLowerCase()) ||
                      normalized.contains(member.toLowerCase())) {
                  return member;
              }
          }

          // 如果无法确定,根据消息内容推断
          return members.length > 0 ? members[0] : "FINISH";
      }

      private String extractTextFromMessage(Object message) {
          if (message instanceof Map) {
              Map<?, ?> msgMap = (Map<?, ?>) message;
              Object content = msgMap.get("content");
              if (content != null) {
                  return content.toString();
              }
          }
          return message.toString();
      }
  }

  /**
   * Researcher Agent Node
   * 负责执行研究任务
   */
  public static class ResearcherNode implements NodeAction {
      private final ChatClient chatClient;

      public ResearcherNode(ChatModel model) {
          ToolCallback searchTool = FunctionToolCallback.builder("search", new SearchTool())
                  .description(SearchTool.DESCRIPTION)
                  .inputType(SearchTool.SearchRequest.class)
                  .build();

          this.chatClient = ChatClient.builder(model)
                  .defaultTools(searchTool)
                  .build();
      }

      @Override
      public Map<String, Object> apply(OverAllState state) throws Exception {
          // 获取最后一条消息
          List<Object> messages = (List<Object>) state.value("messages").orElse(List.of());
          if (messages.isEmpty()) {
              throw new IllegalStateException("No messages in state");
          }

          String lastMessageText = extractTextFromMessage(messages.get(messages.size() - 1));

          // 使用 ChatClient 调用 LLM,LLM 可能会调用搜索工具
          String result = chatClient.prompt()
                  .user(lastMessageText)
                  .call()
                  .content();

          // 返回结果消息
          return Map.of("messages", List.of(
                  Map.of("role", "assistant", "content", result)
          ));
      }

      private String extractTextFromMessage(Object message) {
          if (message instanceof Map) {
              Map<?, ?> msgMap = (Map<?, ?>) message;
              Object content = msgMap.get("content");
              if (content != null) {
                  return content.toString();
              }
          }
          return message.toString();
      }
  }

  /**
   * Coder Agent Node
   * 负责执行代码任务
   */
  public static class CoderNode implements NodeAction {
      private final ChatClient chatClient;

      public CoderNode(ChatModel model) {
          ToolCallback coderTool = FunctionToolCallback.builder("executeCode", new CoderTool())
                  .description(CoderTool.DESCRIPTION)
                  .inputType(CoderTool.CodeRequest.class)
                  .build();

          this.chatClient = ChatClient.builder(model)
                  .defaultTools(coderTool)
                  .build();
      }

      @Override
      public Map<String, Object> apply(OverAllState state) throws Exception {
          // 获取最后一条消息
          List<Object> messages = (List<Object>) state.value("messages").orElse(List.of());
          if (messages.isEmpty()) {
              throw new IllegalStateException("No messages in state");
          }

          String lastMessageText = extractTextFromMessage(messages.get(messages.size() - 1));

          // 使用 ChatClient 调用 LLM,LLM 可能会调用代码执行工具
          String result = chatClient.prompt()
                  .user(lastMessageText)
                  .call()
                  .content();

          // 返回结果消息
          return Map.of("messages", List.of(
                  Map.of("role", "assistant", "content", result)
          ));
      }

      private String extractTextFromMessage(Object message) {
          if (message instanceof Map) {
              Map<?, ?> msgMap = (Map<?, ?>) message;
              Object content = msgMap.get("content");
              if (content != null) {
                  return content.toString();
              }
          }
          return message.toString();
      }
  }

  /**
   * 搜索工具(模拟实现)
   */
  public static class SearchTool implements BiFunction<SearchTool.SearchRequest, ToolContext, String> {

      public static final String DESCRIPTION = """
              使用此工具在互联网上执行搜索。

              Usage:
              - query 参数是要搜索的查询字符串
              - 工具会执行搜索并返回搜索结果
              - 这是一个模拟实现,返回固定的搜索结果
              """;

      @Override
      public String apply(SearchRequest request, ToolContext toolContext) {
          System.out.println("搜索查询: '" + request.query + "'");
          // 模拟搜索结果
          return "下一届冬奥会将在意大利的科尔蒂纳举行,时间是2026年";
      }

      /**
       * 搜索请求结构
       */
      public static class SearchRequest {
          @JsonProperty(required = true)
          @JsonPropertyDescription("要搜索的查询字符串")
          public String query;

          public SearchRequest() {
          }

          public SearchRequest(String query) {
              this.query = query;
          }
      }
  }

  /**
   * 代码执行工具(模拟实现)
   */
  public static class CoderTool implements BiFunction<CoderTool.CodeRequest, ToolContext, String> {

      public static final String DESCRIPTION = """
              使用此工具执行 Java 代码并进行数学计算。

              Usage:
              - request 参数是要执行的代码请求
              - 如果你想查看某个值的输出,应该使用 System.out.println(...); 打印出来
              - 这对用户可见
              - 这是一个模拟实现,返回固定的执行结果
              """;

      @Override
      public String apply(CodeRequest request, ToolContext toolContext) {
          System.out.println("代码执行请求: '" + request.request + "'");
          // 模拟代码执行结果
          return "2";
      }

      /**
       * 代码执行请求结构
       */
      public static class CodeRequest {
          @JsonProperty(required = true)
          @JsonPropertyDescription("要执行的代码请求")
          public String request;

          public CodeRequest() {
          }

          public CodeRequest(String request) {
              this.request = request;
          }
      }
  }
}

本文整理自 Spring AI Alibaba 官方文档《多智能体监督者模式》:https://java2ai.com/docs/frameworks/graph-core/examples/multi-agent-supervisor

赞(0)
未经允许不得转载:AI项目博客 » Spring AI Alibaba 86|Graph Core · 多智能体监督者模式 · 实现示例

评论 抢沙发

AI项目博客

专注 AI 工程化落地 —— 覆盖智能体、大模型、Java AI 与 Python 的学习笔记,以及 AI 平台的项目实战记录。

关于本站文章归档

登录

找回密码

注册