Skip to content

第9章 Docker 部署分布式 Agent 搞定旅游规划

9-1 AgentScope搭建工程化的分布式Agent协同

9-2 分布式Agent自主旅游规划的架构思路

9-3 SpringBoot 4和AgentScope 的整合

开始项目级整合,SpringBoot 和AgentScope 整合使用agentscope-spring-boot-starter依赖包

xml
<properties>
    <!--  SpringBoot 4      -->
    <spring-boot.version>4.0.2</spring-boot.version>
    <!--   AgentScope     -->
    <AgentScope.version>1.0.8</AgentScope.version>
    <logback.version>1.5.25</logback.version>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<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>
    </dependencies>
</dependencyManagement>
<dependencies>
    <!--  AgentScope 和 SpringBoot 的集成     -->
    <dependency>
        <groupId>io.agentscope</groupId>
        <artifactId>agentscope-spring-boot-starter</artifactId>
        <version>${AgentScope.version}</version>
    </dependency>
    <!--   实现slf4j接口,不然日志打印不出来    -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>${logback.version}</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

补充测试用例

java
@SpringBootApplication
public class ManagerAgentApplication {
    public static void main(String[] args) {
        String apiKey = System.getenv("DASHSCOPE_API_KEY");
        if (apiKey == null || apiKey.isEmpty()) {
            System.err.println("环境变量 DASHSCOPE_API_KEY未设置");
            System.exit(1);
        }
        ReActAgent agent =
                ReActAgent.builder()
                        .name("HelloAgent")
                        .description("AgentScope ReActAgent Hello World!")
                        .model(DashScopeChatModel.builder()
                                //请求语言大模型的apikey
                                .apiKey(apiKey)//所使用的语言大模型
                                .modelName("qwen3-max")
                                .build())
                        .sysPrompt("你是一个AI助手。")
                        .build();
        System.out.println("############等待响应。。.\n");
        //运行Agent
        agent.stream(
                        //Prompt
                        Msg.builder()
                                //消息角色
                                .role(MsgRole.USER)
                                //消息内容 (Prompt)
                                .content(List.of(
                                        TextBlock.builder()
                                                .text("你好").build()
                                ))
                                //消息内容(发送文字形式的Prompt)
                                // .textContent("")
                                .build()
                )
                //把响应结打印出来
                .doOnNext(msg -> System.out.println(msg.getMessage().getContent()))
                //阻塞直到结束
                .blockLast();
        SpringApplication.run(ManagerAgentApplication.class, args);
    }
}

9-4 创建不同节点的ReAct Agent

创建四个模块

xml
<!--   主管Agent 负责整体执行计划的制定    -->
<module>manager_agent</module>
<!--   路线制定Agent     -->
<module>routeMaking_agent</module>
<!--   行程规划Agent     -->
<module>tripPlanner_agent</module>
<!--   公共模块    -->
<module>commons</module>

在公共模块创建Agent工具类和流式响应返回类

java
/**
 * description: ReActAgent 工具类
 */
public class AgentUtils {
    /**
     * description: 创建ReAct Agent Builder
     */
    public static ReActAgent.Builder getReActAgentBuilder(String name, String description) {
        return ReActAgent.builder()
                        .name(name)
                        .description(description)
                        .model(DashScopeChatModel.builder()
                                //请求语言大模型的apikey
                                .apiKey("你的API_KEY")
                                //所使用的语言大模型
                                .modelName("qwen3-max")
                                .stream(true)
                                .build());
    }

    /**
     * description: ReAct Agent 流式响应
     */
    public static Flux<Event> streamResponse(AgentBase agent, String prompt) {
       return agent.stream(
                        //Prompt
                        Msg.builder()
                                //消息角色
                                .role(MsgRole.USER)
                                //消息内容 (Prompt)
                                .content(List.of(
                                        TextBlock.builder()
                                                .text(prompt)
                                                .build()
                                ))
                                .build()
                );
    }
}

还有工具Tools类

java
/**
 * description: Agent Tool 工具类
 */
public class ToolUtils {
    private final Toolkit toolkit ;
    public ToolUtils() {
        //创建工具包
        toolkit = new Toolkit();
    }
    /**
     * description: 获取工具包
     */
    public Toolkit getToolkit(Object tool) {
        //把工具添加到工具包,能自动扫描@Tool所注释的方法,作为Agent的工具
        toolkit.registerTool(tool);
        return toolkit;
    }
}

创建主管Agent

java
package cn.yuan.agents;

import cn.yuan.util.AgentUtils;
import io.agentscope.core.ReActAgent;

/**
 * 主管Agent
 */
public class ManagerAgent {
    private final ReActAgent agent;
    public ManagerAgent() {
        this.agent = AgentUtils.getReActAgentBuilder("ManagerAgent", "主管Agent").build();
    }
}

9-5 主管Agent自主分解复杂任务

Agent 运行

java
/**
 * description: Agent 运行
 */
public void run() {
    String prompt ="帮我制定2026年元旦,深圳到惠州3日游自驾游计划,请包含吃住行,天气,酒店,餐饮美食。 ";
    Flux<Event> stream = AgentUtils.streamResponse(agent,prompt);
    //把响应结打印出来
    stream.doOnNext(msg->System.out.println(msg.getMessage().getTextContent()))
            //阻塞直到结束
            .blockLast();
}

主方法中测试使用

java
public static void main(String[] args) {
    ManagerAgent manager = new ManagerAgent();
    manager.run();
}

ReActAgent 能自主分解复杂任务, 并且会自动生成计划步骤:

  1. .enablePlan()
  2. .planNotebook()

.enablePlan() 内部调用了 PlanNotebook的Builder 构造方法是采用默认的 PlanNotebook 的属性,当开启这个属性,此时启动打印到日志

java
Registered tool 'create_plan' in group 'ungrouped'
Registered tool 'view_historical_plans' in group 'ungrouped'
Registered tool 'recover_historical_plan' in group 'ungrouped'
Registered tool 'finish_subtask' in group 'ungrouped'
Registered tool 'update_subtask_state' in group 'ungrouped'
Registered tool 'revise_current_plan' in group 'ungrouped'
Registered tool 'update_plan_info' in group 'ungrouped'
Registered tool 'finish_plan' in group 'ungrouped'
Registered tool 'view_subtasks' in group 'ungrouped'
Registered tool 'get_subtask_count' in group 'ungrouped'

.planNotebook() 它是传入 PlanNotebook的 实例,可以对 PlanNotebook 进行自定义

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

    /**
     * description: 自定义 PlanNotebook 实例
     */
    public PlanNotebook getPlan() {
        return PlanNotebook.builder()
                .build();
    }
}

更新主管Agent

java
public ManagerAgent() {
    //PlanNotebook
    TripPlan plan = new TripPlan();
    //计划对象
    PlanNotebook planNotebook = plan.getPlan();
    this.agent  = AgentUtils.getReActAgentBuilder("ManagerAgent", "主管Agent")
            // .enablePlan()
            .planNotebook(planNotebook)
            .build();
}

9-6 自主分解任务的关键:PlanNotebook

Agent自主规划和自主决策的工作模式

PlanNotebook对象是Agent能自主分解任务和步骤执行的核心

PlanNotebook整个流程:

  1. 复杂任务分解
  2. 生成执行步骤
  3. 状态跟踪
  4. 动态调整
  5. 任务完成

PlanNotebook对象:自主规划 (PlanAct) + 自主决策 (ReAct)

PlanNotebook自定义属性值有: needUserConfirm/maxSubtasks/storage...

java
PlanNotebook.builder()
        //计划步骤是否需要用户确认
        .needUserConfirm(true)
        //分解出来的子任务数量限制
        .maxSubtasks(5)
        //计划存储器
        //.storage(new PlanStorage())
        .build();

9-7 计划和执行中的事件拦截:Hook

Hook 是对 HookEvent事件 的拦截 HookEvent事件:

PreReasoningEvent:用户的输入事件 PostReasoningEvent: Agent推理思考过程事件 PreActingEvent: Agent执行过程准备调用工具的事件 PostActingEvent:Agent执行过程调用工具完成的事件

java
/**
 * description:  计划拦截器
 */
@Slf4j
public class PlanHook implements Hook {
    @Override
    public <T extends HookEvent> 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);
        } else if (event instanceof PostActingEvent) {
            //调用工具事件
            PostActingEvent e = (PostActingEvent) event;
            String toolName = e.getToolUse().getName();
            log.info("##### 调用工具:" + toolName);
        } else {
            // 其他事件忽略

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

主管Agent添加拦截器

java
public ManagerAgent() {
    //PlanNotebook
    TripPlan plan = new TripPlan();
    //计划对象
    PlanNotebook planNotebook = plan.getPlan();
    this.agent  = AgentUtils.getReActAgentBuilder("ManagerAgent", "主管Agent")
            // .enablePlan()
            .planNotebook(planNotebook)
            .hook(new PlanHook(planNotebook))
            .build();
}

9-8 主管Agent分发任务给相应Agent

修改主管Agent的prompt

java
String prompt = """
        帮我制定2026年元旦,深圳到惠州3日游自驾游计划, 请包含吃住行,天气,酒店,餐饮美食。
    
        你可以调用以下Agent处理子任务:
        - routeMaking Agent:擅长处理自驾游路线制定
        - tripPlanner Agent:擅长处理景点行程规划
   
        - 每个子任务要注明调用的Agent
        """;

此时在执行步骤中返回的有调用这两个Agent的步骤说明

9-9 团队成员的智能体卡片注册到Nacos

远程的行程规划Agent

需要引入A2A协议的依赖和Nacos整合

xml
<dependency>
    <groupId>io.agentscope</groupId>
    <artifactId>agentscope-a2a-spring-boot-starter</artifactId>
    <version>${AgentScope.version}</version>
</dependency>
<!-- 额外添加 Nacos Spring Boot starter 的依赖 -->
 <dependency>
     <groupId>io.agentscope</groupId>
     <artifactId>agentscope-nacos-spring-boot-starter</artifactId>
     <version>${AgentScope.version}</version>
 </dependency>

在团队成员的智能体卡片Agent服务中配置文件,参考官方文档A2A配置:https://java.agentscope.io/zh/task/a2a.html

服务端配置文件

yaml
# application.yml
agentscope:
  dashscope:
    api-key: your-api-key
  agent:
    name: my-assistant
  a2a:
    server:
      enabled: true
      card:
        name: My Assistant
        description: An intelligent assistant based on AgentScope
    # 在 `agentscope.a2a` 下添加Nacos相关配置
    nacos:
      server-addr: ${NACOS_SERVER_ADDRESS:127.0.0.1:8848}
      username: ${NACOS_USERNAME:nacos}
      password: ${NACOS_PASSWORD:nacos}

AgentScope框架自带了注册中心: AgentScopeA2aServer

AgentScope框架将智能体卡片注册到注册中心,有2种方案:

  • a. 通过SpringBoot, 以Bean的形式自动注入

  • b. 手动写入注册中心, 主要针对于AgentScopeA2aServer

手动写入注册中心,项目不用种方式

java
/**
 * description: 行程规划Agent
 */
@Component
public class TripPlannerAgent {
    @Bean
    public ReActAgent getTripPlannerAgent() {
        //行程规划Agent Builder
        ReActAgent.Builder builder = AgentUtils.getReActAgentBuilder("TripPlannerAgent", "擅长处理景点行程规划");
        return builder.build();
        //=========== 手动写入注册中心,项目不用种方式 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 ====
    }
}

9-10 远程Agent封装为工具执行子任务

主管智能体如何获取远程Agent

参考文档:https://java.agentscope.io/zh/task/a2a.html,在客户端:A2aAgent的方式获取。

先创建NacosUtil工具类

java
public class NacosUtil {
    public static AiService getNacosClient() throws NacosException {
        // 设置 Nacos 地址
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.SERVER_ADDR, "localhost:8848");
        // 创建 Nacos Client
        return AiFactory.createAiService(properties);
    }
}

将它注册到工具类中

java
/**
 * description: Agent Tool 工具类
 */
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封装为工具

java
/**
 * description: 将远程Agent封装为工具
 */
@Slf4j
public class RemoteAgentTool {

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

    /**
     * description: 基于A2A协议获取行程规划Agent
     */
    @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();
    }
}

将远程Agent封装为工具的封装注册到工具包

java
public ManagerAgent() {
    //PlanNotebook
    TripPlan plan = new TripPlan();
    //Toolkit
    ToolUtils toolUtils = new ToolUtils();
    //将远程Agent封装为工具的封装注册到工具包
    Toolkit toolkit = toolUtils.getToolkit(new RemoteAgentTool());
    //计划对象
    PlanNotebook planNotebook = plan.getPlan();
    agent = AgentUtils.getReActAgentBuilder("ManagerAgent", "主管Agent")
            // .enablePlan()
            .planNotebook(planNotebook)
            //拦截器
            .hook(new planHook(planNotebook))
            //工具包
            .toolkit(toolkit)
            .build();
}