第9章 Docker 部署分布式 Agent 搞定旅游规划
9-1 AgentScope搭建工程化的分布式Agent协同

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


9-3 SpringBoot 4和AgentScope 的整合
开始项目级整合,SpringBoot 和AgentScope 整合使用agentscope-spring-boot-starter依赖包
<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>补充测试用例
@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

创建四个模块
<!-- 主管Agent 负责整体执行计划的制定 -->
<module>manager_agent</module>
<!-- 路线制定Agent -->
<module>routeMaking_agent</module>
<!-- 行程规划Agent -->
<module>tripPlanner_agent</module>
<!-- 公共模块 -->
<module>commons</module>在公共模块创建Agent工具类和流式响应返回类
/**
* 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类
/**
* 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
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 运行
/**
* 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();
}主方法中测试使用
public static void main(String[] args) {
ManagerAgent manager = new ManagerAgent();
manager.run();
}ReActAgent 能自主分解复杂任务, 并且会自动生成计划步骤:
- .enablePlan()
- .planNotebook()
.enablePlan() 内部调用了 PlanNotebook的Builder 构造方法是采用默认的 PlanNotebook 的属性,当开启这个属性,此时启动打印到日志
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自主分解旅游规划任务
/**
* description: 自定义 Agent自主分解旅游规划任务
*/
public class TripPlan {
/**
* description: 自定义 PlanNotebook 实例
*/
public PlanNotebook getPlan() {
return PlanNotebook.builder()
.build();
}
}更新主管Agent
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整个流程:
- 复杂任务分解
- 生成执行步骤
- 状态跟踪
- 动态调整
- 任务完成
PlanNotebook对象:自主规划 (PlanAct) + 自主决策 (ReAct)
PlanNotebook自定义属性值有: needUserConfirm/maxSubtasks/storage...
PlanNotebook.builder()
//计划步骤是否需要用户确认
.needUserConfirm(true)
//分解出来的子任务数量限制
.maxSubtasks(5)
//计划存储器
//.storage(new PlanStorage())
.build();9-7 计划和执行中的事件拦截:Hook
Hook 是对 HookEvent事件 的拦截 HookEvent事件:
PreReasoningEvent:用户的输入事件 PostReasoningEvent: Agent推理思考过程事件 PreActingEvent: Agent执行过程准备调用工具的事件 PostActingEvent:Agent执行过程调用工具完成的事件
/**
* 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添加拦截器
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
String prompt = """
帮我制定2026年元旦,深圳到惠州3日游自驾游计划, 请包含吃住行,天气,酒店,餐饮美食。
你可以调用以下Agent处理子任务:
- routeMaking Agent:擅长处理自驾游路线制定
- tripPlanner Agent:擅长处理景点行程规划
- 每个子任务要注明调用的Agent
""";此时在执行步骤中返回的有调用这两个Agent的步骤说明
9-9 团队成员的智能体卡片注册到Nacos
远程的行程规划Agent
需要引入A2A协议的依赖和Nacos整合
<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
服务端配置文件
# 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
手动写入注册中心,项目不用种方式
/**
* 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工具类
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);
}
}将它注册到工具类中
/**
* 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封装为工具
/**
* 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封装为工具的封装注册到工具包
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();
}