Skip to content

第11章 MCP+A2A,助力旅游规划团队协作

11-1 回顾AgentScope旅游规划的整体架构

旅游规划项目整体架构的回顾,涵盖基于AgentScope与Spring Boot框架的模块化设计、主管智能体的任务分解与分发机制、A2A协议下通过Nexus注册中心实现的远程智能体发现与调用,以及工具挂载、PlanLowBook规划能力、Skills专业流程嵌入等核心组件。

目标与架构更新背景

  • 回顾旅游规划项目整体架构。
  • 因AI新功能“skills”(专业固定流程)推出,新增深度讲解章节,并将skills嵌入项目。

项目基础框架与模块划分

  • 基于两个框架:AgentScope 1.0Spring Boot(采用最新版)。
  • 采用Docker部署多节点Agent,故进行模块化设计,共四个模块:
    • 公共模块(common) :存放公共工具类。
    • 主管智能体模块(manager):负责整体旅游规划需求的接收、自主任务分解、执行计划制定与任务分发。
    • 路线制定智能体模块(route-planner):专职执行路线规划任务。
    • 行程规划智能体模块(itinerary-planner):专职执行行程安排任务。

主管智能体的核心能力

  • 自主分解用户提交的复杂Prompt需求。
  • 集成 Hook插件机制 :作为可挂载插件,支持日志输出、执行过程可视化等扩展能力。
  • 依赖 PlanLowBook对象 :实现任务分解、步骤生成、状态跟踪与动态调整,赋予主管智能体自主规划与决策能力;该对象支持自定义,包括人工确认开关、计划存储方式及状态跟踪策略 。

A2A协议与Nacos注册中心机制

  • 主管智能体通过 A2A(Agent-to-Agent)协议 发现并调用远程智能体。
  • 采用 Nacos注册中心 作为A2A协议注册点 。Nacos
  • 路线制定与行程规划智能体向Nacos注册其 智能体卡片 ,包含所持技能(skills)、专长领域等元数据。
  • 主管智能体从Nacos查询并识别可用远程智能体及其能力,据此精准分发任务 。

**任务执行流程与思考范式 **

  • 主管智能体分发任务后,真正执行业务逻辑的是远程智能体)。

  • 各智能体均遵循

    思考-执行-反馈自主执行任务模式:

    • Think:理解任务目标与上下文;
    • Act:执行具体操作;
    • Reflect:反思执行结果是否符合预定目标,不一致则自我修正 。
  • 执行过程中将嵌入 skills专业流程 :skills非单一工具,而是一整套标准化、固定化的专业工作流程 。

公共模块工具类详解

  • 共三个工具类:
    • AgentBuilder工具类 :封装AgentScope框架下智能体的构建逻辑。
    • NacosUtil工具类 :用于生成Nacos服务客户端,支撑A2A注册与发现。
    • ToolUtils工具类 :封装工具挂载(tool loading)与工具注册逻辑。

**工具挂载(Tool Loading)机制 **

  • 智能体通过 builder.toolKey() 方法挂载工具包 ;
  • 工具包内注册MCP等具体工具 ;
  • 挂载后智能体即具备对应工具调用能力 ;
  • 公共模块已将工具挂载与注册逻辑封装为复用工具类。

**远程智能体调用实现 **

  • 主管智能体将获取到的远程智能体作为自身工具进行调用 ;
  • 在主管模块的 @Tool 方法中实现远程智能体获取逻辑 ;
  • 通过A2A协议,使主管智能体可识别并调用 ;
  • 调用本质是:主管智能体将任务委托给已注册至Nacos的远程智能体执行,后者作为其“可执行工具” 。

**已实现三大核心功能总结 **

  • 主管智能体对复杂用户需求的自主分解能力
  • 基于A2A协议与Nacos注册中心的远程智能体发现机制
  • 主管智能体将远程智能体作为工具挂载并调度执行任务的能力

11-2 测试团队成员基于A2A协议注册到Nacos

旅游规划项目中主管智能体与远程智能体协同机制的实现原理及A2A协议注册中心的测试验证过程,重点解析了AgentScope框架自带注册中心与Nacos注册中心的区别、Bean自动注入机制、依赖管理优化等关键技术点,并通过实际运行演示了行程规划智能体成功注册至Nacos注册中心的全过程。

**项目已实现功能回顾 **

  • **主管智能体的任务分解功能 **:将复杂需求自主分割为多个简单子任务。
  • 基于Nacos的A2A协议注册中心功能:远程智能体可将自身智能体卡片注册至Nacos注册中心;主管智能体可从中获取卡片并分发任务。
  • **主管智能体工具化注册功能 **:主管智能体将获取的远程智能体作为工具注册至自身注册中心,并在任务分发时调用该工具执行子任务。

**核心目标:注册中心功能测试 **

  • 测试内容:验证远程智能体能否成功将智能体卡片注册到Nacos注册中心,以及主管智能体能否从中正确获取卡片。
  • 测试前提:仅需导入agentscope-a2a-spHookEventring-boot-starteragentscope-nacos-spring-boot-starter两个依赖,即可支持A2A协议及Nacos注册中心。

启动报错分析与根本原因

  • 错误现象:行程规划智能体模块启动时报“缺少bean”错误。

  • **关键知识点一:AgentScope自带注册中心 **:AgentScope框架内置注册中心对象(agent-code-a2a-server),无需强制使用Nacos。

  • 关键知识点二:智能体卡片注册的两种方式

    • 自动注入方式(Spring Boot Bean):通过@Component声明Bean,由Spring Boot自动注入至指定注册中心。

      java
      @Component
      public class TripPlannerAgent {
          @Bean
          public ReActAgent getTripPlannerAgent() {
              //行程规划Agent Builder
              ReActAgent.Builder builder = AgentUtils.getReActAgentBuilder("TripPlannerAgent", "擅长处理景点行程规划");
              return builder.build();
          }
      }
    • 手动写入方式:直接调用注册中心对象的方法,传入自定义智能体卡片。

      java
      //=========== 手动写入注册中心,项目不用种方式 START ====
          //行程规划Agent 智能体卡片
          ConfigurableAgentCard agentCard =  new ConfigurableAgentCard.Builder()
                  .name("TripPlannerAgent")
                  .description("行程规划Agent")
                  .build();
          //将智能体卡片写入到AgentScope自带的注册中心
          AgentScopeA2aServer.builder(builder)
                  .agentCard(agentCard)
                  .deploymentProperties(new DeploymentProperties("localhost",8080))
                  .build();
          //还需要AgentScopeA2aServer启动
          //======== 手动写入注册中心,项目不用种方式 END ====

**手动注册方式详解 **

  • 注册中心构建:通过DeploymentProperties配置注册中心地址与端口:new DeploymentProperties("localhost", 8080)
  • 智能体卡片写入:调用agentCard()方法传入自定义智能体卡片对象,支持完全自定义卡片字段。
  • 注册中心启动要求:手动注册前必须显式启动AgentScope自带注册中心实例。
  • 项目适配性说明:因本项目为分布式架构,不采用手动注册方式,故注释相关代码段。

自动注入方式实现

  • Bean定义规范
    • 添加@Component注解标识为Spring组件;
    • 方法返回类型必须为ReactAgent对象(如withAnAgent);
    • 返回值即为待注册的智能体实例。
  • 注册中心定位机制:Spring Boot依据配置文件中Nacos配置项(如nacos.address=localhost:8080)自动识别目标注册中心。
  • 智能体卡片生成逻辑:框架自动从ReactAgent对象中提取namedescription等元信息,构造标准智能体卡片,无需手动编写卡片类。

依赖管理优化

  • 问题根源agentscope-a2a-spring-boot-starter依赖被置于根pom.xml<dependencies>中,导致所有模块(含无需注册的主管智能体模块)均强制引入该依赖,但主管模块未提供对应Bean而报错
  • 解决方案: 将依赖移至<dependencyManagement>标签内,仅在需注册功能的模块(行程规划、路线制定)中显式声明该依赖。
  • 效果验证: 避免无关模块因缺失Bean而启动失败,实现按需依赖注入。

Nacos注册中心验证流程

  • 前置准备:启动Nacos服务(执行文档中提供的启动命令),访问管理页面确认Agent列表为空。
  • 模块启动与日志观察:启动行程规划模块,日志显示Spring Boot successfully registered agent card to Nacos
  • 页面验证结果
    • 刷新Nacos管理界面Agent列表,出现行程规划智能体卡片;
    • 点击详情页,description字段值为“形成规划智能体”,name字段与代码中设置一致;
    • 证实智能体卡片信息由框架自动从ReactAgent对象中抽取并持久化至Nacos。

**总结与关键结论 **

  • AgentScope框架同时支持内置注册中心与第三方注册中心(如Nacos),二者互不冲突。
  • 自动注入方式依赖Spring Boot生命周期与配置驱动,简洁高效;手动方式灵活但需额外维护注册中心实例。
  • 分布式项目中应严格遵循“谁需要谁引入”原则,通过<dependencyManagement>实现精细化依赖管控。
  • Nacos注册中心成功接收并展示智能体卡片,标志着A2A协议远程协同能力已具备工程落地基础。

11-3 测试主管Agent基于A2A协议获取团队成员

主管智能体如何通过Nacos注册中心发现并调用远程智能体(路线制定智能体)的完整流程,包括智能体卡片注册一致性、@Tool注解工具方法定义、A2aAgent对象获取与call()阻塞调用、日志验证机制及多模块协同启动实践。

主管智能体调用远程智能体的整体目标

  • 内容:课聚焦主管智能体通过Nacos注册中心发现并调用已注册的远程智能体。
  • 前置基础确认:行程规划智能体与路线制定智能体均已按相同方式注册至Nacos注册中心,其智能体卡片具备唯一name属性。

远程智能体注册机制复述

  • **行程规划智能体注册状态 **:已完成行程规划智能体卡片写入Nacos注册中心。

  • 路线制定智能体注册状态

    • 路线制定智能体(即路线规划智能体)已按相同流程注册至Nacos注册中心。

    • 注册方法与行程规划智能体完全一致,即通过自动注入智能体卡片实现 。

      java
      @Component
      @Slf4j
      public class RouteMakingAgent {
          @Bean
          public ReActAgent getRouteMakingAgent() {
           //注入到Nacos
              return AgentUtils.getReActAgentBuilder("RouteMakingAgent", "擅长处理自驾游路线制定").build();
        }
      }
  • **注册关键配置 **:仅需将对应组件设为Bean即可完成注册配置。

**主管智能体模块结构与工具方法定义 **

  • 智能体工具基础规范:主管智能体模块中,智能体工具需使用@Tool注解修饰。

  • 已定义的两个远程智能体获取工具

    • 工具一:获取远程路线制定智能体。

      java
      @Tool(description = "从Nacos注册中心获取路线制定Agent")
      public void callRouteMakingAgent() throws NacosException {
          log.info("工具方法:路线制定智能体...正在调用中");
          A2aAgent agent = A2aAgent.builder()
                  .name("RouteMakingAgent")
                  .agentCardResolver(
                          //创建 Nacos 的 AgentCardResolver
                          new NacosAgentCardResolver(NacosUtil.getNacosClient()))
                  .build();
          log.info("获取到的远程Agent描述:"+agent.getDescription());
          //远程Agent运行
          agent.call().block();
      }
    • 工具二:获取远程行程规划智能体 。

      java
      @Tool(description = "从Nacos注册中心获取行程规划Agent")
      public void callTripPlannerAgent() throws NacosException {
          A2aAgent agent = A2aAgent.builder()
                  .name("TripPlannerAgent")
                  .agentCardResolver(
                          //创建 Nacos 的 AgentCardResolver
                          new NacosAgentCardResolver(NacosUtil.getNacosClient()))
                  .build();
          //远程Agent运行
          agent.call().block();
      }
  • 工具执行逻辑 : 两工具均通过Nacos注册中心读取对应智能体卡片 ),进而获取远程智能体实例 )。

  • **功能完成状态说明 **:此前所写功能尚未进行实际测试 。

测试目标与验证路径

  • 核心验证目标:测试主管智能体能否成功从注册中心Nacos读取智能体卡片 。
  • 延伸验证目标:验证主管智能体能否驱动远程智能体执行具体任务。

远程智能体获取技术实现

  • 获取对象类型:通过A2aAgent对象获取远程智能体。
  • 获取参数配置
    • 传入Nacos注册中心地址作为方法参数 。
    • 依据name属性匹配注册中心内智能体卡片。
    • name值必须与注册中心Nacos中卡片的name属性严格一致,方可成功获取。
  • **返回对象确认 **:成功获取后返回A2aAgent对象实例。

**远程智能体调用与运行机制 **

  • 调用入口方法:通过call()方法运行A2aAgent对象 。
  • **执行模式特性 **:call()方法为阻塞式同步执行 。

日志验证方案设计(在@Tool中)

  • **日志注解配置 **: 使用@Slf4j注解启用日志功能 。
  • **非空校验逻辑 **: 若A2aAgent对象不为空,则证明远程智能体获取成功,并打印日志 。
  • 描述属性验证逻辑
    • 打印A2aAgent对象的getDescription()返回值 。
    • 通过比对描述内容(如“路线制定智能体”)确认获取对象准确性 。

主管智能体启动方式与Prompt调整

  • 启动类运行模式
    • 前期测试采用main函数直接运行,未启动Spring容器。
    • 因当前需依赖Spring上下文注入,后续需切换为Spring容器启动模式 。
  • Prompt驱动调用逻辑
    • 主管智能体需接收Prompt指令以触发对应@Tool方法 。
    • 当前测试目标为调用路线制定智能体,故Prompt需明确指示“调用路线制定智能体”。
    • 方法描述(description)与Prompt语义匹配是触发成功的前提。

11-4 测试主管Agent调动团队成员执行任务

路线智能体调用MCP服务配置以及主管智能体调用路线智能体的业务流程

路线智能体配置MCP服务,先不实现MCP中具体业务,创建mcp模块,构建MCP服务

java
@Slf4j
public class BaiduMapMCP {
    /**
     * description: 创建百度地图MCP客户端
     */
    @Tool(description = "百度地图MCP Server")
    public void getBaiduMapMCP() {
       log.info("==================");
       log.info("正在调用百度地图MCP....");
       log.info("==================");
    }
}

将MCP服务工具注册到路线规划智能体中

java
@Component
@Slf4j
public class RouteMakingAgent {
    @Bean
    public ReActAgent getRouteMakingAgent() {
        ToolUtils toolUtils = new ToolUtils();
        Toolkit toolkit = toolUtils.getToolkit(new BaiduMapMCP());
        //打印挂载的工具
        Set<String> toolNames = toolkit.getToolNames();
        toolNames.stream().forEach(value -> log.info("挂载的工具名称:"+value));
        //注入到Nacos
        return AgentUtils.getReActAgentBuilder("RouteMakingAgent", "擅长处理自驾游路线制定")
                //工具包
                .toolkit(toolkit)
                .build();
    }
}

再修改主管智能体中的基于A2A协议获取路线制定Agent的工具

java
@Slf4j
public class RemoteAgentTool {

    /**
     * description: 基于A2A协议获取路线制定Agent
     */
    @Tool(description = "从Nacos注册中心获取路线制定Agent")
    public void callRouteMakingAgent() throws NacosException {
        log.info("工具方法:路线制定智能体...正在调用中");
        A2aAgent agent = A2aAgent.builder()
                .name("RouteMakingAgent")
                .agentCardResolver(
                        //创建 Nacos 的 AgentCardResolver
                        new NacosAgentCardResolver(NacosUtil.getNacosClient()))
                .build();
        log.info("获取到的远程Agent描述:"+agent.getDescription());
        //远程Agent运行
//        agent.call().block();
        Flux<Event> stream = AgentUtils.streamResponse(agent, "调用百度地图MCP");
        stream.doOnNext(msg->System.out.println(msg.getMessage().getTextContent()))
                //阻塞直到结束
                .blockLast();
    }
}

11-5 获取百度地图MCP服务端的工具列表

具体实现百度MCP的业务逻辑,通过魔塔社区mcp文档https://modelscope.cn/mcp/servers/@baidu-maps/mcp获取百度地图的mcp获取

java
@Slf4j
public class BaiduMapMCP {
    //MCP 客户端
    private McpClientWrapper baiduMapMCP = null;
    //MCP 客户端初始化
    private boolean mcpInitialized = false;
    /**
     * description: 创建百度地图MCP客户端
     */
    public void getBaiduMapMCP() {
        //创建MCP客户端
        baiduMapMCP = McpClientBuilder.create("BaiduMap-mcp")
                //和MCP Server以SSE方式进行通信
                .sseTransport("你的MCP地址")
                //请求超时
                .timeout(Duration.ofSeconds(120))
                //异步请求
                .buildAsync()
                .block();

    }
    /**
     * description: 初始化百度地图MCP客户端
     */
    public McpClientWrapper initBaiduMapMCP() {
        //通过Optional判断百度MCP客户端是否为null
        Optional<McpClientWrapper> mcpClientWrapper = Optional.ofNullable(baiduMapMCP);
        if(mcpClientWrapper.isPresent()) {
            log.info("百度MCP客户端已经创建");
            if(!mcpInitialized) {
                synchronized (this) {
                    if (!mcpInitialized) {
                        //MCP客户端初始化
                        baiduMapMCP.initialize().block();
                        //获取MCP服务端工具列表
                        if(baiduMapMCP.isInitialized()) {
                            log.info("百度地图MCP 客户端初始化成功!");
                            baiduMapMCP.listTools().block().forEach(tool -> {
                                log.info("百度地图MCP工具列表:" + tool.name());
                            });
                            mcpInitialized=true;
                        }
                    }
                }
            }
        }
        return baiduMapMCP;
    }
}

然后在远程的行程智能体的启动类中测试临时调用

java
public class RouteMakingAgentApplication {
    public static void main(String[] args) {
        BaiduMapMCP mcp = new BaiduMapMCP();
        mcp.getBaiduMapMCP();
        mcp.initBaiduMapMCP();
    }
}

11-6 路线制定专员Agent挂载百度地图MCP

在工具类ToolUtils中添加同mcp获取工具包的方法

java
public class ToolUtils {
    private final Toolkit toolkit ;
    public ToolUtils() {
        //创建工具包
        toolkit = new Toolkit();
    }
    /**
     * description: 获取工具包
     */
    public Toolkit getToolkit(McpClientWrapper mcp) {
        //把MCP服务端的所有工具添加到工具包
        toolkit.registerMcpClient(mcp).block();
        return toolkit;
    }
}

再在路线制定专员Agent上挂载百度地图MCP

java
@Component
@Slf4j
public class RouteMakingAgent {
    @Bean
    public ReActAgent getRouteMakingAgent() {
        BaiduMapMCP mcp = new BaiduMapMCP();
        //创建百度地图MCP客户端
        mcp.getBaiduMapMCP();
        //初始化百度地图MCP客户端
        McpClientWrapper mcpClient = mcp.initBaiduMapMCP();
        //Toolkit
        ToolUtils toolUtils = new ToolUtils();
        Toolkit toolkit = toolUtils.getToolkit(mcpClient);
        //打印挂载的工具
        Set<String> toolNames = toolkit.getToolNames();
        toolNames.stream().forEach(value -> log.info("挂载的工具名称:"+value));
        //注入到Nacos
        return AgentUtils.getReActAgentBuilder("RouteMakingAgent", "擅长处理自驾游路线制定")
                //工具包
                .toolkit(toolkit)
                .build();
    }
}

11-7 主管Agent自主分发任务给远程相应的成员

两个远程智能体要对其描述功能:

java
AgentUtils.getReActAgentBuilder("RouteMakingAgent","擅长处理自驾游路线制定")
AgentUtils.getReActAgentBuilder("TripPlannerAgent", "擅长处理景点行程规划")

修改promptm,定义从Nacos注册中心获取远程的Agent

java
String prompt =
        """
           帮我制定2026年元旦,
           深圳到惠州3日游自驾游计划,
           请包含吃住行,天气,酒店,餐饮美食。
           
          - 从Nacos注册中心获取远程的Agent,
          - 把子任务分发给擅长处理相关任务的Agent
          - 每个子任务要注明调用的Agent                
        """;

再将工具类Tools中描写修改为从Nacos中获取

java
@Tool(description = "从Nacos注册中心获取路线制定Agent")
public void callRouteMakingAgent() 
 
@Tool(description = "从Nacos注册中心获取行程规划Agent")
public void callTripPlannerAgent()

11-8 人工介入修改主管Agent制定的计划

人工介入的话,需要在自定义 Agent自主分解旅游规划任务中开启

java
/**
 * description: 自定义 Agent自主分解旅游规划任务
 */
public class TripPlan {

    /**
     * description: 自定义 PlanNotebook 实例
     */
    public PlanNotebook getPlan() {
        return PlanNotebook.builder()
                //计划步骤是否需要用户确认
                .needUserConfirm(true)
                //分解出来的子任务数量限制
                .maxSubtasks(5)
                .build();
    }
}

同时需要一个UserAgent来实现将用户输入的传入agent中,可以在main中测试,通过用户输入看打印

java
public static void main(String[] args) {
    UserAgent user = UserAgent.builder().name("User").build();
    Msg userInput = user.call().block();
    System.out.println("用户说:"+ userInput.getTextContent());
}

在项目中,通过Hook来传入人工介入

java
@Slf4j
public class PlanHook implements Hook {
    //监听用户输入
    private final UserAgent user;
    //计划步骤
    private final PlanNotebook plan;

    public PlanHook(PlanNotebook planNotebook) {
        this.user = UserAgent.builder().name("User").build();
        this.plan = planNotebook;
    }
    @Override
    public <T extends Hook@Slf4j
public class PlanHook implements Hook {
    //监听用户输入
    private final UserAgent user;
    //计划步骤
    private final PlanNotebook plan;

    public PlanHook(PlanNotebook planNotebook) {
        this.user = UserAgent.builder().name("User").build();
        this.plan = planNotebook;
    }
    @Override
    public <T extends HookEvent> Mono<T> onEvent(T event) {Event> Mono<T> onEvent(T event) {
        //匹配不同的事件
        if (event instanceof PreReasoningEvent) {
            //用户输入事件
            PreReasoningEvent e = (PreReasoningEvent) event;
            String reason = e.getInputMessages().get(0).getTextContent();
            log.info("#### 用户的Prompt:#######");
            log.info(reason);
        } else if (event instanceof PostReasoningEvent) {
            //推理思考事件
            PostReasoningEvent e = (PostReasoningEvent) event;
            String reason = e.getReasoningMessage().getTextContent();
            log.info("#### 思考过程:#######");
            log.info(reason);
            //当计划列表已生成
            Plan currentPlan = plan.getCurrentPlan();
            if (currentPlan != null) {
                System.out.println("请输入修改意见: ");
                user.call().block();
            }
        } else if (event instanceof PostActingEvent) {
            //调用工具事件
            PostActingEvent e = (PostActingEvent) event;
            String toolName = e.getToolUse().getName();
            log.info("##### 调用工具:" + toolName);
        } else {
            // 其他事件忽略

        }
        // 返回原事件
        return Mono.just(event);
    }
}

Docker部署nacos 3.x

sh
下载
sudo docker pull nacos/nacos-server:v3.1.0

运行
sudo docker run --rm --name nacos -e MODE=standalone -e NACOS_AUTH_ENABLE=false -e NACOS_AUTH_TOKEN=MjM1ZmU4NjAxMzU1NTQyYWU0MTEyYWU4ZDg3YTZiNGUK -e NACOS_AUTH_IDENTITY_KEY=nacos -e NACOS_AUTH_IDENTITY_VALUE=nacos -p 8848:8848 -p 9848:9848 -p 8088:8080 nacos/nacos-server:v3.1.0

控制台页面:http://127.0.0.1:8088
控制台登录username: nacos
控制台登录password: nacos

说明
8848:HTTP端口
9848:RPC端口
8088:控制台页面端口