第24章 高级案例分析

第24章 高级案例分析

本章深入分析AgentScope-Java的高级示例,包括路由分发、自动记忆、可观测性集成和Human-in-the-Loop完整实现。这些高级模式展示了如何构建生产级的智能应用。


24.1 高级示例概览

24.1.1 示例分类

agentscope-examples/
├── advanced/                        # 高级特性示例
│   ├── RoutingByToolCallsExample.java  # 路由分发模式
│   ├── AutoMemoryExample.java          # 长期记忆集成
│   ├── LangfuseExample.java            # 可观测性集成
│   └── RAGExample.java                 # RAG高级用法
├── hitl-chat/                       # 完整HITL应用
│   ├── service/AgentService.java       # Agent服务层
│   ├── hook/ToolConfirmationHook.java  # 确认Hook
│   └── controller/ChatController.java  # Web控制器
└── quickstart/
    └── HookStopAgentExample.java    # Hook停止示例

24.1.2 高级特性对照表

示例核心特性应用场景技术难度
RoutingByToolCallsExampleAgent路由任务分发、专家系统高级
AutoMemoryExample长期记忆个性化助手、知识积累高级
LangfuseExample可观测性生产监控、调试分析中级
HookStopAgentExampleHITL核心安全审批、人机协作中级
AgentService完整HITL企业级Web应用高级

24.2 路由分发模式深入分析

24.2.1 模式概述

路由分发模式使用一个"路由Agent"分析用户请求,然后将任务分发给专门的"专家Agent"处理。这种模式实现了关注点分离和专业化处理。

24.2.2 架构设计

┌──────────────────────────────────────────────────────────────┐
│                    RoutingByToolCallsExample                  │
├──────────────────────────────────────────────────────────────┤
│                                                               │
│  用户请求: "Generate a quick sort function in Python"         │
│              │                                                │
│              ▼                                                │
│  ┌─────────────────────────────────────────────────────────┐ │
│  │                  RouterAgent (路由Agent)                  │ │
│  │  ┌─────────────────────────────────────────────────────┐│ │
│  │  │ 分析请求类型,选择合适的专家工具                    ││ │
│  │  │ - generate_Python_code → PythonAgent                 ││ │
│  │  │ - generate_poem → PoemAgent                         ││ │
│  │  └─────────────────────────────────────────────────────┘│ │
│  └──────────────────────────┬──────────────────────────────┘ │
│                             │                                 │
│            ┌────────────────┴────────────────┐               │
│            ▼                                 ▼               │
│  ┌──────────────────┐              ┌──────────────────┐     │
│  │   PythonAgent    │              │    PoemAgent     │     │
│  │  Python代码专家  │              │    诗歌创作专家  │     │
│  │                  │              │                  │     │
│  │ qwen-max         │              │ qwen-max         │     │
│  │ enableThinking   │              │ enableThinking   │     │
│  │ thinkingBudget   │              │ thinkingBudget   │     │
│  └──────────────────┘              └──────────────────┘     │
│                                                               │
│  最终响应: 完整的Python快速排序代码                          │
└──────────────────────────────────────────────────────────────┘

24.2.3 路由Agent实现

public class RoutingByToolCallsExample {

    public static void main(String[] args) {
        // 创建工具包并注册路由工具
        Toolkit toolkit = new Toolkit();
        toolkit.registerTool(new SimpleTools());
        
        // 创建路由Agent
        // 关键:sysPrompt专注于路由决策
        ReActAgent routerImplicit = ReActAgent.builder()
            .name("RouterImplicit")
            .sysPrompt(
                "You're a routing agent. Your target is to route the user query "
                + "to the right follow-up task.")
            .model(
                DashScopeChatModel.builder()
                    .apiKey(ExampleUtils.getDashScopeApiKey())
                    .modelName("qwen-max")
                    .stream(true)
                    .enableThinking(true)  // 启用深度思考
                    .formatter(new DashScopeChatFormatter())
                    .defaultOptions(
                        GenerateOptions.builder()
                            .thinkingBudget(512)  // 思考预算
                            .build())
                    .build())
            .memory(new InMemoryMemory())
            .toolkit(toolkit)
            .build();

        // 用户请求
        Msg userMsg = Msg.builder()
            .role(MsgRole.USER)
            .content(TextBlock.builder()
                .text("Help me to generate a quick sort function in Python")
                .build())
            .build();
        
        // 执行路由
        Msg response = routerImplicit.call(userMsg).block();
        System.out.println("Agent> " + MsgUtils.getTextContent(response));
    }
}

24.2.4 专家工具定义

public static class SimpleTools {
    
    /**
     * Python代码生成工具
     * 内部创建专用的PythonAgent处理请求
     */
    @Tool(
        name = "generate_Python_code",
        description = "Generate Python code based on the demand")
    public Msg generatePython(
        @ToolParam(name = "demand", description = "The demand for the Python code")
        String demand) {
        
        System.out.println("I am PythonAgent, now generating Python code for: " + demand);
        
        // 创建Python专家Agent
        ReActAgent agent = ReActAgent.builder()
            .name("PythonAgent")
            .sysPrompt(
                "You're a Python expert, your target is to generate Python code "
                + "based on the demand.")
            .model(
                DashScopeChatModel.builder()
                    .apiKey(ExampleUtils.getDashScopeApiKey())
                    .modelName("qwen-max")
                    .stream(true)
                    .enableThinking(true)
                    .formatter(new DashScopeChatFormatter())
                    .defaultOptions(
                        GenerateOptions.builder()
                            .thinkingBudget(1024)  // 更高的思考预算
                            .build())
                    .build())
            .memory(new InMemoryMemory())
            .toolkit(new Toolkit())
            .build();

        Msg userMsg = Msg.builder()
            .role(MsgRole.USER)
            .content(TextBlock.builder().text(demand).build())
            .build();
        
        return agent.call(userMsg).block();
    }

    /**
     * 诗歌生成工具
     */
    @Tool(name = "generate_poem", description = "Generate a poem based on the demand")
    public Msg generatePoem(
        @ToolParam(name = "demand", description = "The demand for the poem")
        String demand) {
        
        System.out.println("I am PoemAgent, now generating a poem for: " + demand);
        
        ReActAgent agent = ReActAgent.builder()
            .name("PoemAgent")
            .sysPrompt(
                "You're a poet, your target is to generate poems based on the demand.")
            .model(
                DashScopeChatModel.builder()
                    .apiKey(ExampleUtils.getDashScopeApiKey())
                    .modelName("qwen-max")
                    .stream(true)
                    .enableThinking(true)
                    .formatter(new DashScopeChatFormatter())
                    .defaultOptions(
                        GenerateOptions.builder()
                            .thinkingBudget(1024)
                            .build())
                    .build())
            .memory(new InMemoryMemory())
            .toolkit(new Toolkit())
            .build();

        Msg userMsg = Msg.builder()
            .role(MsgRole.USER)
            .content(TextBlock.builder().text(demand).build())
            .build();
        
        return agent.call(userMsg).block();
    }
}

24.2.5 路由执行流程

1. 用户输入: "Help me to generate a quick sort function in Python"
              │
              ▼
2. RouterAgent分析请求
   ┌────────────────────────────────────────────────────────┐
   │ Thinking (512 tokens):                                  │
   │ - 用户需要Python代码                                    │
   │ - 应该调用generate_Python_code工具                      │
   │ - 参数: "quick sort function"                          │
   └────────────────────────────────────────────────────────┘
              │
              ▼
3. 调用generate_Python_code工具
   ┌────────────────────────────────────────────────────────┐
   │ PythonAgent处理请求:                                    │
   │ - 深度思考(1024 tokens)                                 │
   │ - 生成完整的快速排序代码                                │
   │ - 添加注释和使用示例                                    │
   └────────────────────────────────────────────────────────┘
              │
              ▼
4. 返回最终结果给用户
   ┌────────────────────────────────────────────────────────┐
   │ def quick_sort(arr):                                    │
   │     if len(arr) <= 1:                                   │
   │         return arr                                      │
   │     pivot = arr[len(arr) // 2]                         │
   │     ...                                                │
   └────────────────────────────────────────────────────────┘

24.2.6 路由模式最佳实践

实践说明
路由Agent轻量化专注于分析和路由,不执行具体任务
专家Agent专业化每个专家只处理特定领域的任务
工具描述精确帮助路由Agent准确选择工具
思考预算分配路由Agent少,专家Agent多
错误处理专家失败时路由Agent应能fallback

24.3 长期记忆集成深入分析

24.3.1 模式概述

AutoMemoryExample展示了如何集成长期记忆服务(Mem0),使Agent能够跨会话记住用户偏好和历史信息。

24.3.2 架构设计

┌──────────────────────────────────────────────────────────────┐
│                      AutoMemoryExample                        │
├──────────────────────────────────────────────────────────────┤
│                                                               │
│  ┌────────────────────────────────────────────────────────┐  │
│  │                     ReActAgent                          │  │
│  │  ┌──────────────────────────────────────────────────┐  │  │
│  │  │              AutoContextMemory                    │  │  │
│  │  │  ┌────────────┐  ┌────────────────────────────┐  │  │  │
│  │  │  │ tokenRatio │  │ 自动上下文压缩              │  │  │  │
│  │  │  │ = 0.1      │  │ 保留最近20条消息            │  │  │  │
│  │  │  └────────────┘  └────────────────────────────┘  │  │  │
│  │  └──────────────────────────────────────────────────┘  │  │
│  │                            │                            │  │
│  │  ┌──────────────────────────────────────────────────┐  │  │
│  │  │            Mem0LongTermMemory                     │  │  │
│  │  │  ┌────────────┐  ┌────────────────────────────┐  │  │  │
│  │  │  │ userId     │  │ 云端存储用户记忆            │  │  │  │
│  │  │  │ apiKey     │  │ 跨会话持久化                │  │  │  │
│  │  │  └────────────┘  └────────────────────────────┘  │  │  │
│  │  └──────────────────────────────────────────────────┘  │  │
│  │                                                         │  │
│  │  ┌──────────────────────────────────────────────────┐  │  │
│  │  │              AutoContextHook                      │  │  │
│  │  │  自动管理上下文长度,触发记忆保存               │  │  │
│  │  └──────────────────────────────────────────────────┘  │  │
│  └─────────────────────────────────────────────────────────┘  │
│                                                               │
│  ┌────────────────────────────────────────────────────────┐  │
│  │                  JsonSession (本地)                     │  │
│  │  ~/.agentscope/examples/sessions/123453344.json        │  │
│  └────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────┘

24.3.3 完整实现

public class AutoMemoryExample {

    public static void main(String[] args) {
        String apiKey = ExampleUtils.getDashScopeApiKey();

        // 步骤1:创建聊天模型
        DashScopeChatModel chatModel = DashScopeChatModel.builder()
            .apiKey(apiKey)
            .modelName("qwen3-max-preview")
            .stream(true)
            .enableThinking(true)
            .formatter(new DashScopeChatFormatter())
            .defaultOptions(GenerateOptions.builder()
                .thinkingBudget(1024)
                .build())
            .build();

        // 步骤2:创建长期记忆(Mem0云服务)
        // 访问 https://app.mem0.ai/dashboard/settings?tab=api-keys 获取API密钥
        Mem0LongTermMemory longTermMemory = Mem0LongTermMemory.builder()
            .apiKey(ExampleUtils.getMem0ApiKey())
            .userId("example-user")  // 用户标识
            .apiBaseUrl("https://api.mem0.ai")
            .build();
        
        // 步骤3:配置自动上下文记忆
        AutoContextConfig autoContextConfig = AutoContextConfig.builder()
            .tokenRatio(0.1)   // 使用10%的token用于上下文
            .lastKeep(20)      // 保留最近20条消息
            .build();
        
        AutoContextMemory memory = new AutoContextMemory(autoContextConfig, chatModel);

        // 步骤4:注册工具
        Toolkit toolkit = new Toolkit();
        toolkit.registerTool(new ReadFileTool());
        toolkit.registerTool(new WriteFileTool());

        // 步骤5:构建Agent
        ReActAgent agent = ReActAgent.builder()
            .name("Assistant")
            .sysPrompt(
                "You are a helpful AI assistant. Be friendly and concise. "
                + "Respond to user using the language that user asks.")
            .model(chatModel)
            .memory(memory)
            .maxIters(50)
            // 长期记忆配置
            .longTermMemory(longTermMemory)
            .longTermMemoryMode(LongTermMemoryMode.STATIC_CONTROL)
            .enablePlan()
            // 注册自动上下文Hook
            .hook(new AutoContextHook())
            .toolkit(toolkit)
            .build();
        
        // 步骤6:设置会话持久化
        String sessionId = "123453344";
        Path sessionPath = Paths.get(
            System.getProperty("user.home"), 
            ".agentscope", 
            "examples", 
            "sessions"
        );
        Session session = new JsonSession(sessionPath);

        // 加载已有会话
        agent.loadIfExists(session, sessionId);

        // 步骤7:交互循环
        Scanner scanner = new Scanner(System.in);
        System.out.println("Auto Memory Example Started!");
        
        try {
            while (true) {
                System.out.print("You: ");
                String query = scanner.nextLine().trim();

                if ("exit".equalsIgnoreCase(query)) {
                    System.out.println("Goodbye!");
                    break;
                }

                if (query.isEmpty()) {
                    continue;
                }

                Msg userMsg = Msg.builder()
                    .role(MsgRole.USER)
                    .content(TextBlock.builder().text(query).build())
                    .build();

                System.out.println("\nProcessing...\n");
                Msg response = agent.call(userMsg).block();
                System.out.println("Assistant: " + response.getTextContent() + "\n");
                
                // 每次交互后保存会话
                agent.saveTo(session, sessionId);
            }
        } finally {
            // 确保会话被保存
            agent.saveTo(session, sessionId);
        }
        scanner.close();
    }
}

24.3.4 记忆模式详解

LongTermMemoryMode选项

模式行为适用场景
STATIC_CONTROL手动控制记忆存储精确控制记忆内容
AUTO_SAVE自动保存重要信息通用场景
PROMPT_BASED基于提示词触发需要用户确认

AutoContextConfig参数

参数说明推荐值
tokenRatio上下文占用token比例0.1-0.3
lastKeep保留的最近消息数10-50
compressionStrategy压缩策略SUMMARIZE

24.3.5 记忆流程图

用户会话开始
      │
      ▼
┌─────────────────────────────────────────────────────────────┐
│ 1. 加载本地会话 (JsonSession)                                │
│    - 恢复短期记忆 (InMemoryMemory)                          │
│    - 恢复Agent状态                                          │
└─────────────────────────────────────────────────────────────┘
      │
      ▼
┌─────────────────────────────────────────────────────────────┐
│ 2. 查询长期记忆 (Mem0)                                       │
│    - 获取用户历史偏好                                        │
│    - 获取相关历史信息                                        │
└─────────────────────────────────────────────────────────────┘
      │
      ▼
┌─────────────────────────────────────────────────────────────┐
│ 3. 对话处理                                                  │
│    - AutoContextHook自动管理上下文                          │
│    - 超过tokenRatio时自动压缩历史                           │
└─────────────────────────────────────────────────────────────┘
      │
      ▼
┌─────────────────────────────────────────────────────────────┐
│ 4. 保存记忆                                                  │
│    - 短期记忆 → JsonSession (本地)                          │
│    - 重要信息 → Mem0 (云端)                                 │
└─────────────────────────────────────────────────────────────┘

24.4 可观测性集成深入分析

24.4.1 模式概述

LangfuseExample展示了如何将Agent的执行过程发送到Langfuse进行追踪、分析和评估。这对于生产环境的监控和调试至关重要。

24.4.2 架构设计

┌──────────────────────────────────────────────────────────────┐
│                      LangfuseExample                          │
├──────────────────────────────────────────────────────────────┤
│                                                               │
│  ┌─────────────────────────────────────────────────────────┐ │
│  │                      ReActAgent                          │ │
│  │                          │                                │ │
│  │                          │ 执行事件                       │ │
│  │                          ▼                                │ │
│  │  ┌───────────────────────────────────────────────────┐   │ │
│  │  │               TracerRegistry                       │   │ │
│  │  │                    │                               │   │ │
│  │  │                    ▼                               │   │ │
│  │  │  ┌─────────────────────────────────────────────┐  │   │ │
│  │  │  │            TelemetryTracer                   │  │   │ │
│  │  │  │  endpoint: langfuse.com/api/otel/traces     │  │   │ │
│  │  │  │  auth: Basic(publicKey:secretKey)           │  │   │ │
│  │  │  └─────────────────────────────────────────────┘  │   │ │
│  │  └───────────────────────────────────────────────────┘   │ │
│  └─────────────────────────────────────────────────────────┘ │
│                              │                                │
│                              │ OTLP Protocol                  │
│                              ▼                                │
│  ┌─────────────────────────────────────────────────────────┐ │
│  │                    Langfuse Cloud                        │ │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐   │ │
│  │  │  Traces      │  │  Metrics     │  │  Evaluations │   │ │
│  │  │  - Agent调用 │  │  - 延迟      │  │  - 质量评估  │   │ │
│  │  │  - LLM调用   │  │  - Token用量 │  │  - 反馈收集  │   │ │
│  │  │  - 工具执行  │  │  - 成功率    │  │              │   │ │
│  │  └──────────────┘  └──────────────┘  └──────────────┘   │ │
│  └─────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘

24.4.3 完整实现

public class LangfuseExample {

    // Langfuse OTLP endpoints
    private static final String LANGFUSE_US_ENDPOINT =
            "https://us.cloud.langfuse.com/api/public/otel/v1/traces";
    private static final String LANGFUSE_EU_ENDPOINT =
            "https://cloud.langfuse.com/api/public/otel/v1/traces";

    public static void main(String[] args) {
        // 获取配置
        String dashScopeApiKey = ExampleUtils.getDashScopeApiKey();
        String langfusePublicKey = getLangfusePublicKey();
        String langfuseSecretKey = getLangfuseSecretKey();
        String langfuseEndpoint = getLangfuseEndpoint();

        System.out.println("Starting Langfuse Tracing Example...\n");

        // 步骤1:初始化Langfuse追踪
        initLangfuseTracing(langfusePublicKey, langfuseSecretKey, langfuseEndpoint);

        // 步骤2:创建Agent
        ReActAgent agent = ReActAgent.builder()
            .name("Assistant")
            .sysPrompt(
                "You are a helpful AI assistant. Be concise and informative "
                + "in your responses.")
            .model(
                DashScopeChatModel.builder()
                    .apiKey(dashScopeApiKey)
                    .modelName("qwen-plus")
                    .formatter(new DashScopeChatFormatter())
                    .build())
            .build();

        System.out.println("Agent created with Langfuse tracing\n");

        // 步骤3:多轮对话循环
        try (Scanner scanner = new Scanner(System.in)) {
            int turn = 1;

            while (true) {
                System.out.print("[Turn " + turn + "] You: ");
                String userInput = scanner.nextLine().trim();

                if (userInput.isEmpty()) continue;
                if ("exit".equalsIgnoreCase(userInput)) break;

                Msg userMsg = Msg.builder()
                    .role(MsgRole.USER)
                    .content(TextBlock.builder().text(userInput).build())
                    .build();

                // 调用Agent - 自动追踪到Langfuse
                long startTime = System.currentTimeMillis();
                Msg response = agent.call(userMsg).block();
                long elapsed = System.currentTimeMillis() - startTime;

                if (response != null) {
                    System.out.println("[Turn " + turn + "] Agent (" 
                        + elapsed + "ms): " + response.getTextContent());
                }

                turn++;
            }
        }
    }

    /**
     * 初始化Langfuse追踪
     */
    private static void initLangfuseTracing(
            String publicKey, String secretKey, String endpoint) {
        
        // 构建Basic Auth头
        String credentials = publicKey + ":" + secretKey;
        String authHeader = "Basic " + 
            Base64.getEncoder().encodeToString(credentials.getBytes());

        // 创建TelemetryTracer并配置Langfuse
        TelemetryTracer langfuseTracer = TelemetryTracer.builder()
            .endpoint(endpoint)
            .addHeader("Authorization", authHeader)
            .build();

        // 注册到全局TracerRegistry
        TracerRegistry.register(langfuseTracer);

        System.out.println("Langfuse tracing initialized");
        System.out.println("  Endpoint: " + endpoint);
        System.out.println("  Public Key: " + publicKey.substring(0, 10) + "...");
    }

    // 辅助方法:获取环境变量
    private static String getLangfusePublicKey() {
        String key = System.getenv("LANGFUSE_PUBLIC_KEY");
        if (key == null || key.isBlank()) {
            throw new IllegalStateException(
                "LANGFUSE_PUBLIC_KEY not set. Get keys from https://cloud.langfuse.com");
        }
        return key;
    }

    private static String getLangfuseSecretKey() {
        String key = System.getenv("LANGFUSE_SECRET_KEY");
        if (key == null || key.isBlank()) {
            throw new IllegalStateException("LANGFUSE_SECRET_KEY not set");
        }
        return key;
    }

    private static String getLangfuseEndpoint() {
        String endpoint = System.getenv("LANGFUSE_ENDPOINT");
        return (endpoint != null && !endpoint.isBlank()) ? endpoint : LANGFUSE_US_ENDPOINT;
    }
}

24.4.4 追踪数据结构

Langfuse Trace (一次用户会话)
│
├── Span: agent.call
│   ├── 开始时间: 2026-01-15T10:30:00Z
│   ├── 结束时间: 2026-01-15T10:30:05Z
│   ├── 属性:
│   │   ├── agent.name: "Assistant"
│   │   ├── input: "What is AgentScope?"
│   │   └── output: "AgentScope is..."
│   │
│   ├── Span: model.generate
│   │   ├── 开始时间: 2026-01-15T10:30:01Z
│   │   ├── 结束时间: 2026-01-15T10:30:04Z
│   │   ├── 属性:
│   │   │   ├── model: "qwen-plus"
│   │   │   ├── input_tokens: 150
│   │   │   ├── output_tokens: 200
│   │   │   └── latency_ms: 3000
│   │   │
│   │   └── Span: tool.execute (如果有工具调用)
│   │       ├── tool.name: "search"
│   │       ├── tool.input: {"query": "AgentScope"}
│   │       └── tool.output: "Search results..."
│   │
│   └── 结果: SUCCESS
│
└── 元数据:
    ├── session_id: "abc123"
    ├── user_id: "user456"
    └── environment: "production"

24.4.5 Langfuse控制台功能

功能描述
Traces查看完整的调用链和时间线
Generations分析LLM生成的详细信息
Scores添加质量评分和反馈
Datasets创建评估数据集
Experiments运行A/B测试
Cost AnalysisToken使用和成本分析

24.5 Human-in-the-Loop完整实现

24.5.1 模式概述

HookStopAgentExample和AgentService展示了完整的Human-in-the-Loop实现,允许用户在敏感操作执行前进行审批。

24.5.2 核心架构

┌──────────────────────────────────────────────────────────────┐
│                   HITL Complete Architecture                  │
├──────────────────────────────────────────────────────────────┤
│                                                               │
│  ┌─────────────────────────────────────────────────────────┐ │
│  │                    Web Layer (SSE)                       │ │
│  │  ChatController                                          │ │
│  │    ↓ POST /chat                                          │ │
│  │    ↓ POST /confirm                                       │ │
│  │    ↓ POST /interrupt                                     │ │
│  └─────────────────────────────────────────────────────────┘ │
│                             │                                 │
│                             ▼                                 │
│  ┌─────────────────────────────────────────────────────────┐ │
│  │                   AgentService                           │ │
│  │  ┌──────────────────────────────────────────────────┐   │ │
│  │  │              ReActAgent                           │   │ │
│  │  │  ┌────────────────────────────────────────────┐  │   │ │
│  │  │  │         ToolConfirmationHook               │  │   │ │
│  │  │  │  - 检测危险工具                             │  │   │ │
│  │  │  │  - 调用stopAgent()暂停                     │  │   │ │
│  │  │  │  - 等待用户确认                            │  │   │ │
│  │  │  └────────────────────────────────────────────┘  │   │ │
│  │  └──────────────────────────────────────────────────┘   │ │
│  │                                                          │ │
│  │  ┌──────────────────────────────────────────────────┐   │ │
│  │  │              InMemorySession                      │   │ │
│  │  │  会话状态持久化,支持恢复                         │   │ │
│  │  └──────────────────────────────────────────────────┘   │ │
│  └─────────────────────────────────────────────────────────┘ │
│                                                               │
│  事件流:                                                      │
│  REASONING → 检测危险工具 → TOOL_CONFIRM →                    │
│  用户确认 → TOOL_RESULT → AGENT_RESULT                       │
└──────────────────────────────────────────────────────────────┘

24.5.3 ToolConfirmationHook实现

/**
 * 工具确认Hook
 * 在PostReasoningEvent中检测敏感工具并暂停执行
 */
public class ToolConfirmationHook implements Hook {

    // 需要确认的敏感工具列表
    private static final List<String> SENSITIVE_TOOLS = 
        List.of("delete_file", "send_email");

    @Override
    public <T extends HookEvent> Mono<T> onEvent(T event) {
        // 只处理PostReasoningEvent
        if (event instanceof PostReasoningEvent postReasoning) {
            Msg reasoningMsg = postReasoning.getReasoningMessage();
            if (reasoningMsg == null) {
                return Mono.just(event);
            }

            // 检查是否有敏感工具调用
            List<ToolUseBlock> toolCalls = 
                reasoningMsg.getContentBlocks(ToolUseBlock.class);
            boolean hasSensitiveTool = toolCalls.stream()
                .anyMatch(tool -> SENSITIVE_TOOLS.contains(tool.getName()));

            if (hasSensitiveTool) {
                System.out.println("Sensitive tool detected, requesting confirmation...");
                // 关键:调用stopAgent()暂停执行
                postReasoning.stopAgent();
            }
        }
        return Mono.just(event);
    }
}

24.5.4 交互循环实现

/**
 * 带确认功能的交互循环
 */
static void startChatWithConfirmation(ReActAgent agent) {
    Scanner scanner = new Scanner(System.in);

    while (true) {
        System.out.print("\nYou: ");
        String input = scanner.nextLine().trim();

        if (input.equalsIgnoreCase("exit")) break;
        if (input.isEmpty()) continue;

        // 创建用户消息
        Msg userMsg = Msg.builder()
            .name("user")
            .role(MsgRole.USER)
            .content(TextBlock.builder().text(input).build())
            .build();

        // 调用Agent
        Msg response = agent.call(userMsg).block();

        // 检查是否有待确认的工具调用
        while (response != null && hasPendingToolUse(response)) {
            // 显示待确认的工具
            System.out.println("\n  Agent paused for confirmation");
            displayPendingToolCalls(response);

            System.out.print("\nConfirm execution? (yes/no): ");
            String confirmation = scanner.nextLine().trim().toLowerCase();

            if (confirmation.equals("yes") || confirmation.equals("y")) {
                // 用户确认,继续执行
                System.out.println("Resuming execution...\n");
                response = agent.call().block();  // 无参数调用继续执行
            } else if (confirmation.equals("no") || confirmation.equals("n")) {
                // 用户拒绝,发送取消结果
                System.out.println("Operation cancelled by user.\n");
                Msg cancelResult = createCancelledToolResults(response, agent.getName());
                response = agent.call(cancelResult).block();
            }
        }

        // 输出最终响应
        if (response != null) {
            System.out.println("\nAgent: " + response.getTextContent());
        }
    }
}

/**
 * 检查消息是否包含待执行的工具调用
 */
static boolean hasPendingToolUse(Msg msg) {
    return msg.hasContentBlocks(ToolUseBlock.class);
}

/**
 * 显示待确认的工具调用详情
 */
static void displayPendingToolCalls(Msg msg) {
    List<ToolUseBlock> toolCalls = msg.getContentBlocks(ToolUseBlock.class);
    for (ToolUseBlock toolCall : toolCalls) {
        System.out.println("  Tool: " + toolCall.getName());
        System.out.println("  Input: " + toolCall.getInput());
    }
}

/**
 * 创建取消的工具结果消息
 */
static Msg createCancelledToolResults(Msg toolUseMsg, String agentName) {
    List<ToolUseBlock> toolCalls = toolUseMsg.getContentBlocks(ToolUseBlock.class);
    
    List<ToolResultBlock> results = toolCalls.stream()
        .map(tool -> ToolResultBlock.of(
            tool.getId(),
            tool.getName(),
            TextBlock.builder()
                .text("Operation cancelled by user. Please try a different approach.")
                .build()))
        .toList();

    return Msg.builder()
        .name(agentName)
        .role(MsgRole.TOOL)
        .content(results.toArray(new ToolResultBlock[0]))
        .build();
}

24.5.5 企业级AgentService实现

@Service
public class AgentService {

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

    private final McpService mcpService;
    private Toolkit sharedToolkit;
    private ToolConfirmationHook confirmationHook;
    
    // 会话存储
    private final Session session = new InMemorySession();
    
    // 运行中的Agent缓存
    private final ConcurrentHashMap<String, ReActAgent> runningAgents = 
        new ConcurrentHashMap<>();

    @PostConstruct
    public void init() {
        sharedToolkit = new Toolkit();
        sharedToolkit.registerTool(new BuiltinTools());
        sharedToolkit.registerTool(new ReadFileTool());
        
        // 配置危险工具列表
        Set<String> dangerousTools = new HashSet<>();
        dangerousTools.add("view_text_file");
        dangerousTools.add("list_directory");
        confirmationHook = new ToolConfirmationHook(dangerousTools);
    }

    /**
     * 创建Agent并加载会话状态
     */
    private ReActAgent createAgent(String sessionId) {
        Toolkit sessionToolkit = sharedToolkit.copy();
        
        ReActAgent agent = ReActAgent.builder()
            .name("Assistant")
            .sysPrompt("You are a helpful assistant with access to various tools.")
            .model(
                DashScopeChatModel.builder()
                    .apiKey(apiKey)
                    .modelName(modelName)
                    .stream(true)
                    .enableThinking(false)
                    .formatter(new DashScopeChatFormatter())
                    .build())
            .toolkit(sessionToolkit)
            .memory(new InMemoryMemory())
            .hook(confirmationHook)
            .build();
        
        // 加载已有会话状态
        agent.loadIfExists(session, sessionId);
        return agent;
    }

    /**
     * 处理聊天请求(返回SSE事件流)
     */
    public Flux<ChatEvent> chat(String sessionId, String message) {
        ReActAgent agent = createAgent(sessionId);
        runningAgents.put(sessionId, agent);

        Msg userMsg = Msg.builder()
            .name("User")
            .role(MsgRole.USER)
            .content(TextBlock.builder().text(message).build())
            .build();

        return agent.stream(userMsg)
            .flatMap(this::convertEventToChatEvents)
            .concatWith(Flux.just(ChatEvent.complete()))
            .doFinally(signal -> {
                runningAgents.remove(sessionId);
                agent.saveTo(session, sessionId);
            })
            .onErrorResume(error -> 
                Flux.just(ChatEvent.error(error.getMessage()), ChatEvent.complete()));
    }

    /**
     * 确认或拒绝工具执行
     */
    public Flux<ChatEvent> confirmTool(
            String sessionId, 
            boolean confirmed, 
            String reason,
            List<ToolCallInfo> toolCalls) {
        
        ReActAgent agent = createAgent(sessionId);
        runningAgents.put(sessionId, agent);

        if (confirmed) {
            // 用户确认,继续执行
            return agent.stream(StreamOptions.defaults())
                .flatMap(this::convertEventToChatEvents)
                .concatWith(Flux.just(ChatEvent.complete()))
                .doFinally(signal -> {
                    runningAgents.remove(sessionId);
                    agent.saveTo(session, sessionId);
                });
        } else {
            // 用户拒绝,发送取消结果
            List<ToolResultBlock> results = new ArrayList<>();
            String cancelMessage = reason != null ? reason : "Operation cancelled";
            
            for (ToolCallInfo tool : toolCalls) {
                results.add(ToolResultBlock.of(
                    tool.getId(),
                    tool.getName(),
                    TextBlock.builder().text(cancelMessage).build()));
            }
            
            Msg cancelResult = Msg.builder()
                .name("Assistant")
                .role(MsgRole.TOOL)
                .content(results.toArray(new ToolResultBlock[0]))
                .build();

            return agent.stream(cancelResult)
                .flatMap(this::convertEventToChatEvents)
                .concatWith(Flux.just(ChatEvent.complete()))
                .doFinally(signal -> {
                    runningAgents.remove(sessionId);
                    agent.saveTo(session, sessionId);
                });
        }
    }

    /**
     * 中断正在运行的Agent
     */
    public boolean interrupt(String sessionId) {
        ReActAgent agent = runningAgents.get(sessionId);
        if (agent != null) {
            agent.interrupt();
            return true;
        }
        return false;
    }
}

24.5.6 事件流转换

/**
 * 将Agent事件转换为前端SSE事件
 */
private Flux<ChatEvent> convertEventToChatEvents(Event event) {
    List<ChatEvent> events = new ArrayList<>();
    Msg msg = event.getMessage();
    
    switch (event.getType()) {
        case REASONING -> {
            if (event.isLast() && msg.hasContentBlocks(ToolUseBlock.class)) {
                // 检测工具调用
                List<ToolUseBlock> toolCalls = msg.getContentBlocks(ToolUseBlock.class);
                boolean hasDangerous = toolCalls.stream()
                    .anyMatch(t -> confirmationHook.isDangerous(t.getName()));
                
                if (hasDangerous) {
                    // 需要确认的工具调用
                    List<PendingToolCall> pending = toolCalls.stream()
                        .map(tool -> new PendingToolCall(
                            tool.getId(),
                            tool.getName(),
                            convertInput(tool.getInput()),
                            confirmationHook.isDangerous(tool.getName())))
                        .toList();
                    events.add(ChatEvent.toolConfirm(pending));
                } else {
                    // 安全工具,直接显示
                    for (ToolUseBlock tool : toolCalls) {
                        events.add(ChatEvent.toolUse(
                            tool.getId(),
                            tool.getName(),
                            convertInput(tool.getInput())));
                    }
                }
            } else {
                // 普通文本响应
                String text = extractText(msg);
                if (text != null && !text.isEmpty()) {
                    events.add(ChatEvent.text(text, !event.isLast()));
                }
            }
        }
        case TOOL_RESULT -> {
            // 工具执行结果
            for (ToolResultBlock result : msg.getContentBlocks(ToolResultBlock.class)) {
                events.add(ChatEvent.toolResult(
                    result.getId(),
                    result.getName(),
                    extractToolOutput(result)));
            }
        }
        case AGENT_RESULT -> {
            // 最终响应
            String text = msg.getTextContent();
            if (text != null && !text.isEmpty()) {
                events.add(ChatEvent.text(text, false));
            }
        }
    }
    
    return Flux.fromIterable(events);
}

24.6 本章小结

24.6.1 高级模式对比

模式核心技术适用场景复杂度
路由分发Agent作为工具、嵌套调用专家系统、任务分发
长期记忆Mem0、AutoContext个性化、知识积累
可观测性TelemetryTracer、OTLP生产监控、调试
HITLstopAgent、Hook安全审批、人机协作

24.6.2 关键API总结

API用途所属模块
GenerateOptions.thinkingBudget()控制思考深度模型配置
Mem0LongTermMemory云端长期记忆记忆系统
AutoContextHook自动上下文管理Hook系统
TelemetryTracer追踪数据发送可观测性
TracerRegistry.register()注册全局追踪器可观测性
PostReasoningEvent.stopAgent()暂停Agent执行HITL
agent.call()恢复执行HITL

24.6.3 设计原则总结

  1. 关注点分离:路由Agent和专家Agent职责明确
  2. 状态持久化:短期记忆+长期记忆双层架构
  3. 可观测性优先:生产环境必须有完整追踪
  4. 安全第一:敏感操作必须有人工审批
  5. 响应式设计:使用Flux支持流式处理和SSE

24.6.4 下一步学习

掌握这些高级模式后,建议继续学习:

  • 第25章:狼人杀游戏案例(完整多智能体博弈)
  • 第26章:奶茶店系统(分布式多Agent协作)

这些综合案例将展示如何将本章的高级模式组合成复杂的生产级应用。

← 返回目录