第26章 奶茶店多智能体系统

第26章 奶茶店多智能体系统

本章深入分析AgentScope-Java中的奶茶店(Boba Tea Shop)多智能体系统案例。这是一个企业级分布式系统,展示了如何使用A2A协议、MCP工具服务、Nacos服务发现等技术构建生产级的多Agent协作系统。


26.1 项目概述

26.1.1 系统特性

特性描述
分布式架构多个独立微服务组成的Agent系统
A2A协议Agent到Agent的标准通信协议
MCP集成通过MCP协议调用外部工具服务
Nacos服务发现使用Nacos进行服务注册与发现
长期记忆集成Mem0实现用户偏好记忆
RAG支持知识库检索增强咨询能力
业务工具订单管理、库存查询、用户反馈

26.1.2 业务场景

┌──────────────────────────────────────────────────────────────────┐
│                     云原生奶茶店业务场景                          │
├──────────────────────────────────────────────────────────────────┤
│                                                                   │
│  客户端                                                           │
│    │                                                              │
│    ▼                                                              │
│  ┌──────────────────────────────────────────────────────────────┐│
│  │                    Supervisor Agent                          ││
│  │                    (主管Agent)                               ││
│  │  职责:                                                        ││
│  │  - 接收用户请求                                               ││
│  │  - 路由到专业Agent                                            ││
│  │  - 生成日报/周报                                              ││
│  │  - 发送钉钉通知                                               ││
│  └──────────────────────────────┬───────────────────────────────┘│
│                                 │                                 │
│             ┌───────────────────┴───────────────────┐            │
│             │                                       │            │
│             ▼                                       ▼            │
│  ┌─────────────────────────┐         ┌─────────────────────────┐│
│  │    Consult Agent        │         │    Business Agent       ││
│  │    (咨询Agent)          │         │    (业务Agent)          ││
│  │  职责:                  │         │  职责:                  ││
│  │  - 产品咨询             │         │  - 订单创建             ││
│  │  - 菜单推荐             │         │  - 订单查询             ││
│  │  - RAG知识检索          │         │  - 库存检查             ││
│  │  - 用户偏好记忆         │         │  - 用户反馈             ││
│  └─────────────────────────┘         └──────────┬──────────────┘│
│                                                  │               │
│                                                  ▼               │
│                                      ┌─────────────────────────┐│
│                                      │    MCP Tool Server      ││
│                                      │    (工具服务)           ││
│                                      │  - 订单API             ││
│                                      │  - 库存API             ││
│                                      │  - 反馈API             ││
│                                      └─────────────────────────┘│
└──────────────────────────────────────────────────────────────────┘

26.1.3 项目结构

boba-tea-shop/
├── supervisor-agent/              # 主管Agent服务
│   ├── src/main/java/.../
│   │   ├── tools/
│   │   │   └── ScheduleAgentTools.java  # 报表工具
│   │   ├── config/
│   │   │   └── AgentScopeModelConfig.java
│   │   ├── entity/
│   │   │   ├── Order.java
│   │   │   ├── Product.java
│   │   │   └── Feedback.java
│   │   └── mapper/
│   │       ├── OrderMapper.java
│   │       └── FeedbackMapper.java
│   └── pom.xml
│
├── consult-sub-agent/             # 咨询Agent服务
│   ├── src/main/java/.../
│   │   ├── config/
│   │   │   └── AgentScopeRunner.java
│   │   ├── tools/
│   │   │   └── ConsultTools.java
│   │   └── utils/
│   │       └── MonitoringHook.java
│   └── pom.xml
│
├── business-sub-agent/            # 业务Agent服务
│   ├── src/main/java/.../
│   │   ├── config/
│   │   │   └── AgentScopeRunner.java
│   │   └── utils/
│   │       └── MonitoringHook.java
│   └── pom.xml
│
├── business-mcp-server/           # MCP工具服务
│   ├── src/main/java/.../
│   │   └── config/
│   │       └── McpToolDefinitions.java
│   └── pom.xml
│
└── docker-compose.yml             # 容器编排

26.2 系统架构

26.2.1 服务架构图

┌──────────────────────────────────────────────────────────────────────┐
│                         Boba Tea Shop Architecture                    │
├──────────────────────────────────────────────────────────────────────┤
│                                                                       │
│  ┌─────────────────────────────────────────────────────────────────┐ │
│  │                        用户入口层                                │ │
│  │  ┌────────────┐  ┌────────────┐  ┌────────────┐                │ │
│  │  │ Web Client │  │ Mobile App │  │ 钉钉Bot    │                │ │
│  │  └──────┬─────┘  └──────┬─────┘  └──────┬─────┘                │ │
│  └─────────┼───────────────┼───────────────┼───────────────────────┘ │
│            │               │               │                          │
│            └───────────────┼───────────────┘                          │
│                            │                                          │
│                            ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────┐ │
│  │                    Supervisor Agent                              │ │
│  │  ┌──────────────────────────────────────────────────────────┐   │ │
│  │  │ ReActAgent + A2A Client                                   │   │ │
│  │  │  - ScheduleAgentTools (报表生成)                          │   │ │
│  │  │  - DingTalkNotification (通知)                            │   │ │
│  │  │  - A2A调用子Agent                                         │   │ │
│  │  └──────────────────────────────────────────────────────────┘   │ │
│  │                            │                                     │ │
│  │            ┌───────────────┴───────────────┐                    │ │
│  │            │ A2A Protocol                  │                    │ │
│  └────────────┼───────────────────────────────┼────────────────────┘ │
│               │                               │                       │
│               ▼                               ▼                       │
│  ┌─────────────────────────┐   ┌─────────────────────────┐          │
│  │   Consult Sub-Agent     │   │   Business Sub-Agent    │          │
│  │   (咨询服务)            │   │   (业务服务)            │          │
│  │  ┌───────────────────┐  │   │  ┌───────────────────┐  │          │
│  │  │ AgentRunner       │  │   │  │ AgentRunner       │  │          │
│  │  │ - NacosToolkit    │  │   │  │ - NacosToolkit    │  │          │
│  │  │ - RAG Knowledge   │  │   │  │ - MCP Client      │  │          │
│  │  │ - Mem0 Memory     │  │   │  │ - Mem0 Memory     │  │          │
│  │  └───────────────────┘  │   │  └─────────┬─────────┘  │          │
│  └─────────────────────────┘   └────────────┼────────────┘          │
│                                             │                        │
│                                             │ MCP Protocol           │
│                                             ▼                        │
│                              ┌─────────────────────────────┐        │
│                              │   Business MCP Server        │        │
│                              │   (工具服务)                 │        │
│                              │  ┌────────────────────────┐ │        │
│                              │  │ McpToolDefinitions     │ │        │
│                              │  │ - order-create-order   │ │        │
│                              │  │ - order-get-order      │ │        │
│                              │  │ - order-check-stock    │ │        │
│                              │  │ - feedback-create      │ │        │
│                              │  └────────────────────────┘ │        │
│                              └─────────────────────────────┘        │
│                                             │                        │
│                                             ▼                        │
│  ┌─────────────────────────────────────────────────────────────────┐│
│  │                         数据层                                   ││
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐             ││
│  │  │  MySQL      │  │  Nacos      │  │  Mem0 Cloud │             ││
│  │  │  (业务数据) │  │  (服务发现) │  │  (长期记忆) │             ││
│  │  └─────────────┘  └─────────────┘  └─────────────┘             ││
│  └─────────────────────────────────────────────────────────────────┘│
└──────────────────────────────────────────────────────────────────────┘

26.2.2 技术栈

层级技术用途
Agent框架AgentScope-Java核心Agent实现
通信协议A2A (Agent-to-Agent)Agent间通信
工具协议MCP (Model Context Protocol)工具调用
服务发现Nacos服务注册与发现
长期记忆Mem0用户偏好持久化
知识库RAG + 向量存储产品知识检索
数据库MySQL订单、产品数据
容器化Docker Compose服务编排

26.3 咨询Agent实现

26.3.1 AgentRunner配置

@Configuration
public class AgentScopeRunner {

    @Value("${agentscope.dashscope.api-key}")
    String apiKey;

    @Value("${agentscope.dashscope.model-name}")
    String modelName;

    @Bean
    public AgentRunner agentRunner(
            AgentPromptConfig promptConfig,
            ConsultTools consultTools,
            Knowledge knowledge,
            Model model) {

        // 使用Nacos工具包(支持服务发现)
        Toolkit toolkit = new NacosToolkit();
        toolkit.registerTool(consultTools);

        // 自动上下文压缩配置
        AutoContextConfig autoContextConfig = AutoContextConfig.builder()
            .tokenRatio(0.4)   // 40%用于上下文
            .lastKeep(10)      // 保留最近10条
            .build();
        
        // 支持自动压缩的记忆
        AutoContextMemory memory = new AutoContextMemory(autoContextConfig, model);

        // 构建Agent
        ReActAgent.Builder builder = ReActAgent.builder()
            .name("consult_agent")
            .sysPrompt(promptConfig.getConsultAgentInstruction())
            .memory(memory)
            .hooks(List.of(new MonitoringHook()))
            .model(model)
            .toolkit(toolkit)
            .knowledge(knowledge)         // RAG知识库
            .ragMode(RAGMode.AGENTIC);    // Agent控制的RAG

        return new CustomAgentRunner(builder);
    }
}

26.3.2 自定义AgentRunner

/**
 * 自定义AgentRunner
 * 支持用户级长期记忆和任务管理
 */
private static class CustomAgentRunner implements AgentRunner {

    private static final Pattern USER_ID_PATTERN = 
        Pattern.compile("<userId>(.+?)</userId>");

    private final ReActAgent.Builder agentBuilder;
    private final Map<String, ReActAgent> agentCache;

    private CustomAgentRunner(ReActAgent.Builder agentBuilder) {
        this.agentBuilder = agentBuilder;
        this.agentCache = new ConcurrentHashMap<>();
    }

    /**
     * 构建带长期记忆的Agent
     * 每个用户有独立的记忆空间
     */
    private ReActAgent buildReActAgent(String userId) {
        Mem0LongTermMemory longTermMemory = Mem0LongTermMemory.builder()
            .agentName("ConsultAgent")
            .userId(userId)
            .apiBaseUrl("https://api.mem0.ai")
            .apiKey(System.getenv("MEM0_API_KEY"))
            .build();
        
        return agentBuilder
            .longTermMemory(longTermMemory)
            .build();
    }

    @Override
    public String getAgentName() {
        return "consult_agent";
    }

    @Override
    public String getAgentDescription() {
        return "Boba tea shop consultation agent";
    }

    /**
     * 处理A2A请求
     * 为每个任务创建独立的Agent实例
     */
    @Override
    public Flux<Event> stream(List<Msg> requestMessages, AgentRequestOptions options) {
        if (agentCache.containsKey(options.getTaskId())) {
            throw new IllegalStateException(
                "Agent already exists for taskId: " + options.getTaskId());
        }
        
        // 从消息中解析用户ID
        String userId = parseUserIdFromMessages(requestMessages);
        
        // 创建用户专属Agent
        ReActAgent agent = buildReActAgent(userId);
        agentCache.put(options.getTaskId(), agent);
        
        // 将用户ID添加到记忆
        agent.getMemory().addMessage(
            Msg.builder()
                .role(MsgRole.USER)
                .content(TextBlock.builder()
                    .text("<userId>" + userId + "</userId>")
                    .build())
                .build());
        
        // 执行并在完成后清理
        return agent.stream(requestMessages)
            .doFinally(signal -> agentCache.remove(options.getTaskId()));
    }

    /**
     * 从消息中解析用户ID
     */
    private String parseUserIdFromMessages(List<Msg> requestMessages) {
        for (Msg msg : requestMessages) {
            if (msg.getContent() == null) continue;
            
            for (var block : msg.getContent()) {
                if (block instanceof TextBlock textBlock) {
                    String text = textBlock.getText();
                    if (text != null) {
                        Matcher matcher = USER_ID_PATTERN.matcher(text);
                        if (matcher.find()) {
                            return matcher.group(1).trim();
                        }
                    }
                }
            }
        }
        return "default_userId";
    }

    /**
     * 停止任务
     */
    @Override
    public void stop(String taskId) {
        ReActAgent agent = agentCache.remove(taskId);
        if (agent != null) {
            agent.interrupt();
        }
    }
}

26.4 业务Agent与MCP集成

26.4.1 MCP客户端初始化

@Configuration
public class AgentScopeRunner {

    @Bean
    public AgentRunner agentRunner(
            AgentPromptConfig promptConfig, 
            AiService aiService, 
            Model model) {

        Toolkit toolkit = new NacosToolkit();

        AutoContextConfig autoContextConfig = AutoContextConfig.builder()
            .tokenRatio(0.4)
            .lastKeep(10)
            .build();
        
        AutoContextMemory memory = new AutoContextMemory(autoContextConfig, model);

        ReActAgent.Builder builder = ReActAgent.builder()
            .name("business_agent")
            .sysPrompt(promptConfig.getBusinessAgentInstruction())
            .memory(memory)
            .hooks(List.of(new MonitoringHook()))
            .model(model)
            .toolkit(toolkit);

        return new CustomAgentRunner(builder, aiService, toolkit);
    }
}

26.4.2 MCP工具注册

private static class CustomAgentRunner implements AgentRunner {

    private final ReActAgent.Builder agentBuilder;
    private final AiService aiService;
    private final Toolkit toolkit;
    private final Map<String, ReActAgent> agentCache;
    private volatile boolean mcpInitialized = false;

    /**
     * 延迟初始化MCP客户端
     * 确保MCP服务可用后才注册工具
     */
    private void initializeMcpOnce() {
        if (!mcpInitialized) {
            synchronized (this) {
                if (!mcpInitialized) {
                    try {
                        // 创建Nacos MCP服务管理器
                        NacosMcpServerManager mcpServerManager = 
                            new NacosMcpServerManager(aiService);
                        
                        // 通过Nacos发现并连接MCP服务
                        NacosMcpClientWrapper mcpClientWrapper = 
                            NacosMcpClientBuilder.create(
                                "business-mcp-server",  // 服务名
                                mcpServerManager
                            ).build();
                        
                        // 注册MCP客户端到工具包
                        toolkit.registerMcpClient(mcpClientWrapper).block();
                        
                        mcpInitialized = true;
                    } catch (Exception e) {
                        logger.warn("Failed to initialize MCP client: " 
                            + e.getMessage() + ", will try later.");
                    }
                }
            }
        }
    }

    private ReActAgent buildReActAgent(String userId) {
        // 初始化MCP
        initializeMcpOnce();
        
        // 创建长期记忆
        Mem0LongTermMemory longTermMemory = Mem0LongTermMemory.builder()
            .agentName("BusinessAgent")
            .userId(userId)
            .apiBaseUrl("https://api.mem0.ai")
            .apiKey(System.getenv("MEM0_API_KEY"))
            .build();
        
        return agentBuilder
            .longTermMemory(longTermMemory)
            .build();
    }
}

26.5 MCP工具服务

26.5.1 工具定义

/**
 * MCP工具定义
 * 定义所有可通过MCP协议调用的工具
 */
public class McpToolDefinitions {

    /**
     * 工具定义记录
     */
    public record ToolDefinition(
        String name, 
        String description, 
        Map<String, Object> schema) {

        /**
         * 转换为MCP JSON Schema格式
         */
        public JsonSchema jsonSchema() {
            String type = (String) schema.getOrDefault("type", "object");
            Map<String, Object> properties = (Map<String, Object>) schema.get("properties");
            List<String> required = (List<String>) schema.get("required");
            return new JsonSchema(type, properties, required, null, null, null);
        }
    }

    // ===================== 订单工具 =====================

    /**
     * 创建订单工具
     */
    public static final ToolDefinition ORDER_CREATE_ORDER_WITH_USER =
        new ToolDefinition(
            "order-create-order-with-user",
            "Create a new boba tea order for user. Supports all products "
                + "from Cloud Edge Boba Tea Shop.",
            createOrderWithUserSchema());

    /**
     * 查询订单工具
     */
    public static final ToolDefinition ORDER_GET_ORDER =
        new ToolDefinition(
            "order-get-order",
            "Query order details by order ID, including product name, "
                + "sweetness, ice level, quantity, price and creation time.",
            createGetOrderSchema());

    /**
     * 按用户查询订单
     */
    public static final ToolDefinition ORDER_GET_ORDER_BY_USER =
        new ToolDefinition(
            "order-get-order-by-user",
            "Query order details by user ID and order ID.",
            createGetOrderByUserSchema());

    /**
     * 库存检查工具
     */
    public static final ToolDefinition ORDER_CHECK_STOCK =
        new ToolDefinition(
            "order-check-stock",
            "Check if the specified product has sufficient stock.",
            createCheckStockSchema());

    // ===================== 反馈工具 =====================

    /**
     * 创建反馈工具
     */
    public static final ToolDefinition FEEDBACK_CREATE_FEEDBACK =
        new ToolDefinition(
            "feedback-create-feedback",
            "Create user feedback record, userId is required",
            createFeedbackSchema());

    /**
     * 按用户查询反馈
     */
    public static final ToolDefinition FEEDBACK_GET_FEEDBACK_BY_USER =
        new ToolDefinition(
            "feedback-get-feedback-by-user",
            "Query feedback records by user ID",
            createUserIdSchema());

    // Schema创建辅助方法
    private static Map<String, Object> createOrderWithUserSchema() {
        Map<String, Object> schema = new HashMap<>();
        schema.put("type", "object");
        
        Map<String, Object> properties = new HashMap<>();
        properties.put("userId", Map.of(
            "type", "string",
            "description", "User ID"));
        properties.put("productName", Map.of(
            "type", "string",
            "description", "Product name"));
        properties.put("quantity", Map.of(
            "type", "integer",
            "description", "Order quantity"));
        properties.put("sweetness", Map.of(
            "type", "string",
            "description", "Sweetness level: full/half/light/none"));
        properties.put("iceLevel", Map.of(
            "type", "string",
            "description", "Ice level: full/half/light/none"));
        
        schema.put("properties", properties);
        schema.put("required", List.of("userId", "productName", "quantity"));
        
        return schema;
    }
}

26.6 主管Agent与报表生成

26.6.1 报表工具

@Component
public class ScheduleAgentTools {

    @Value("${agent.dingtalk.access-token}")
    private String accessToken;

    @Autowired 
    private FeedbackMapper feedbackMapper;
    
    @Autowired 
    private OrderMapper orderMapper;
    
    @Autowired 
    private ProductMapper productMapper;

    /**
     * 获取业务日报数据
     */
    @Tool(description = "Get business report data information")
    public Map<String, Object> getDailyReportInfo() {
        // 获取时间范围
        String maxMonth = orderMapper.selectMaxCreatedMonth();
        Date startTime, endTime;
        
        if (maxMonth != null && !maxMonth.isEmpty()) {
            YearMonth yearMonth = YearMonth.parse(maxMonth);
            LocalDate firstDayOfMonth = yearMonth.atDay(1);
            startTime = Date.from(firstDayOfMonth
                .atStartOfDay(ZoneId.systemDefault())
                .toInstant());
        } else {
            startTime = new Date(System.currentTimeMillis() - 365L * 24 * 60 * 60 * 1000);
        }
        endTime = new Date();

        Map<String, Object> templateData = new HashMap<>();
        templateData.put("store_name", "Cloud Native Store #1");

        // ========== 订单销售数据 ==========
        List<Order> todayOrders = orderMapper.findOrdersByTimeRange(startTime, endTime);
        int todayOrderCount = todayOrders.size();
        BigDecimal totalRevenue = todayOrders.stream()
            .map(Order::getTotalPrice)
            .reduce(BigDecimal.ZERO, BigDecimal::add);

        // 昨日数据对比
        Date yesterdayStartTime = new Date(startTime.getTime() - 365L * 24 * 60 * 60 * 1000);
        List<Order> yesterdayOrders = orderMapper.findOrdersByTimeRange(
            yesterdayStartTime, startTime);
        int yesterdayOrderCount = yesterdayOrders.size();
        BigDecimal yesterdayTotalRevenue = yesterdayOrders.stream()
            .map(Order::getTotalPrice)
            .reduce(BigDecimal.ZERO, BigDecimal::add);

        templateData.put("total_sales", todayOrderCount);
        templateData.put("yesterday_total_sales", yesterdayOrderCount);
        templateData.put("total_revenue", String.format("%.2f", totalRevenue));
        templateData.put("avg_price", totalRevenue
            .divide(new BigDecimal(todayOrderCount), 2, RoundingMode.HALF_UP)
            .doubleValue());

        // 增长率计算
        double salesGrowth = (totalRevenue.doubleValue() - yesterdayTotalRevenue.doubleValue())
            / yesterdayTotalRevenue.doubleValue() * 100;
        templateData.put("sales_growth", 
            String.format("%s %.2f%%", salesGrowth >= 0 ? "📈" : "📉", Math.abs(salesGrowth)));

        // ========== 用户反馈数据 ==========
        List<Feedback> validFeedbacks = feedbackMapper.selectByTimeRange(startTime, endTime);
        templateData.put("feedbacks", validFeedbacks.stream()
            .map(Feedback::toFormattedString)
            .toList());

        // 评分统计
        int totalValidFeedbacks = validFeedbacks.size();
        long positiveCount = validFeedbacks.stream().filter(f -> f.getRating() == 5).count();
        long neutralCount = validFeedbacks.stream()
            .filter(f -> f.getRating() >= 3 && f.getRating() <= 4).count();
        long negativeCount = validFeedbacks.stream().filter(f -> f.getRating() < 3).count();

        templateData.put("positive_rate", 
            totalValidFeedbacks > 0 ? positiveCount * 100.0 / totalValidFeedbacks : 0);
        templateData.put("neutral_rate", 
            totalValidFeedbacks > 0 ? neutralCount * 100.0 / totalValidFeedbacks : 0);
        templateData.put("negative_rate", 
            totalValidFeedbacks > 0 ? negativeCount * 100.0 / totalValidFeedbacks : 0);

        return templateData;
    }

    /**
     * 发送钉钉通知
     */
    @Tool(description = "Send notification to DingTalk")
    public String sendDingTalkNotification(
            @ToolParam(name = "content", description = "Notification content")
            String content) {
        
        String webhookUrl = String.format(DEFAULT_WEBHOOK_URL_TEMPLATE, accessToken);
        
        Map<String, Object> payload = new HashMap<>();
        payload.put("msgtype", "markdown");
        
        Map<String, String> markdown = new HashMap<>();
        markdown.put("title", "Daily Report");
        markdown.put("text", content);
        payload.put("markdown", markdown);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        
        HttpEntity<String> request = new HttpEntity<>(
            JsonUtils.toJsonString(payload), headers);
        
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> response = restTemplate.postForEntity(
            webhookUrl, request, String.class);

        return response.getBody();
    }
}

26.7 服务发现与A2A通信

26.7.1 Nacos集成

/**
 * Nacos工具包
 * 支持通过Nacos发现MCP服务
 */
public class NacosToolkit extends Toolkit {

    private final AiService aiService;

    public NacosToolkit() {
        this.aiService = NacosFactory.createAiService();
    }

    /**
     * 注册通过Nacos发现的MCP客户端
     */
    public Mono<Void> registerNacosMcpClient(String serviceName) {
        NacosMcpServerManager mcpServerManager = new NacosMcpServerManager(aiService);
        
        NacosMcpClientWrapper mcpClientWrapper = NacosMcpClientBuilder
            .create(serviceName, mcpServerManager)
            .build();
        
        return registerMcpClient(mcpClientWrapper);
    }
}

26.7.2 A2A配置

/**
 * A2A Agent配置
 */
@Configuration
public class A2aAgentConfiguration {

    @Bean
    public A2aClientAgent consultAgent(A2aClientConfig config) {
        return A2aClientAgent.builder()
            .name("consult_agent")
            .serverUrl(config.getConsultAgentUrl())
            .build();
    }

    @Bean
    public A2aClientAgent businessAgent(A2aClientConfig config) {
        return A2aClientAgent.builder()
            .name("business_agent")
            .serverUrl(config.getBusinessAgentUrl())
            .build();
    }
}

26.8 监控Hook

26.8.1 监控Hook实现

/**
 * 监控Hook
 * 记录Agent执行的各个阶段
 */
public class MonitoringHook implements Hook {

    private static final Logger logger = LoggerFactory.getLogger(MonitoringHook.class);

    @Override
    public <T extends HookEvent> Mono<T> onEvent(T event) {
        if (event instanceof PreReasoningEvent pre) {
            logger.info("[Pre-Reasoning] Agent: {}, Messages: {}", 
                pre.getAgentName(), 
                pre.getMessages().size());
        } else if (event instanceof PostReasoningEvent post) {
            logger.info("[Post-Reasoning] Agent: {}, Response: {}", 
                post.getAgentName(),
                truncate(post.getReasoningMessage().getTextContent(), 100));
        } else if (event instanceof PreActingEvent preAct) {
            logger.info("[Pre-Acting] Agent: {}, Tools: {}", 
                preAct.getAgentName(),
                preAct.getToolUseBlocks().stream()
                    .map(ToolUseBlock::getName)
                    .collect(Collectors.joining(", ")));
        } else if (event instanceof PostActingEvent postAct) {
            logger.info("[Post-Acting] Agent: {}, Results: {}", 
                postAct.getAgentName(),
                postAct.getToolResults().size());
        }
        
        return Mono.just(event);
    }

    private String truncate(String text, int maxLength) {
        if (text == null) return null;
        return text.length() > maxLength 
            ? text.substring(0, maxLength) + "..." 
            : text;
    }
}

26.9 部署配置

26.9.1 Docker Compose

version: '3.8'

services:
  # Nacos服务发现
  nacos:
    image: nacos/nacos-server:v2.3.0
    ports:
      - "8848:8848"
    environment:
      - MODE=standalone

  # MySQL数据库
  mysql:
    image: mysql:8.0
    ports:
      - "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=bobatea

  # 主管Agent
  supervisor-agent:
    build: ./supervisor-agent
    ports:
      - "8080:8080"
    depends_on:
      - nacos
      - mysql
    environment:
      - NACOS_SERVER_ADDR=nacos:8848
      - DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY}
      - MEM0_API_KEY=${MEM0_API_KEY}

  # 咨询Agent
  consult-sub-agent:
    build: ./consult-sub-agent
    ports:
      - "8081:8081"
    depends_on:
      - nacos
    environment:
      - NACOS_SERVER_ADDR=nacos:8848
      - DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY}
      - MEM0_API_KEY=${MEM0_API_KEY}

  # 业务Agent
  business-sub-agent:
    build: ./business-sub-agent
    ports:
      - "8082:8082"
    depends_on:
      - nacos
      - business-mcp-server
    environment:
      - NACOS_SERVER_ADDR=nacos:8848
      - DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY}
      - MEM0_API_KEY=${MEM0_API_KEY}

  # MCP工具服务
  business-mcp-server:
    build: ./business-mcp-server
    ports:
      - "8083:8083"
    depends_on:
      - nacos
      - mysql
    environment:
      - NACOS_SERVER_ADDR=nacos:8848
      - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/bobatea

26.10 本章小结

26.10.1 技术要点

技术应用场景
A2A协议Agent间通信
MCP协议工具服务调用
Nacos服务发现与注册
NacosToolkitNacos集成工具包
Mem0用户级长期记忆
AutoContextMemory上下文自动压缩
AgentRunnerA2A服务端实现
MonitoringHook执行监控

26.10.2 架构模式

  1. 微服务架构:每个Agent作为独立服务部署
  2. 服务发现:通过Nacos实现动态服务发现
  3. 协议标准化:使用A2A和MCP标准协议
  4. 用户隔离:每个用户有独立的记忆空间
  5. 懒加载:MCP客户端按需初始化
  6. 监控集成:Hook系统实现执行追踪

26.10.3 扩展能力

奶茶店案例展示了企业级多Agent系统的构建方法:

  • 新增Agent:注册到Nacos,实现AgentRunner接口
  • 新增工具:在MCP服务中定义ToolDefinition
  • 新增业务:添加新的业务实体和数据访问层
  • 水平扩展:通过Nacos实现多实例负载均衡

26.10.4 完整教程回顾

通过第六部分的四个实战案例,我们学习了:

  1. 第23章:基础示例的核心用法
  2. 第24章:高级特性的生产级应用
  3. 第25章:多智能体博弈游戏系统
  4. 第26章:企业级分布式多Agent架构

这些案例从简单到复杂,从单机到分布式,完整展示了AgentScope-Java在不同场景下的应用方式。

← 返回目录