阿里开源AgentScope多智能体框架解析系列(八)第8章:Hook 系统与事件机制

127 阅读12分钟

本章导读

本章将深入讲解AgentScope的Hook系统。Hook是责任链模式的实现,它允许你在Agent执行的各个阶段插入自定义逻辑,实现日志记录、性能监控、内容增强、安全检查等功能。Hook系统是AgentScope可扩展性的核心。

  • 理解Hook系统的设计理念和工作原理
  • 掌握Hook接口和事件类型
  • 学会实现自定义Hook
  • 了解Hook的实际应用场景
  • 掌握Hook的最佳实践

8.1 Hook系统设计理念

8.1.1 什么是Hook

在AgentScope中,Hook是一种事件拦截机制,允许你在Agent执行的关键节点插入自定义逻辑。

类比:钓鱼的鱼钩

事件流 ────→ PreReasoningEvent ────→ 推理阶段
             ↓
           Hook1捕获
             ↓
           Hook2捕获
             ↓
           Hook3捕获
             ↓
           继续流动 ────→

每个Hook都有机会:
- 观察事件(日志记录)
- 修改事件(内容增强)
- 阻止事件(安全检查)

8.1.2 Hook的工作原理

Hook采用责任链模式(Chain of Responsibility),多个Hook按顺序处理同一个事件。

Agent执行流程与Hook触发点:

用户输入
  ↓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
第N轮推理-行动循环
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  ↓
[1] PreReasoningEvent触发
  ├─ Hook1.onEvent(PreReasoningEvent)
  ├─ Hook2.onEvent(PreReasoningEvent)
  └─ Hook3.onEvent(PreReasoningEvent)
  ↓
推理阶段执行(调用LLM)
  ├─ ReasoningChunkEvent触发(流式,多次)
  │   ├─ Hook1.onEvent(ReasoningChunkEvent)
  │   ├─ Hook2.onEvent(ReasoningChunkEvent)
  │   └─ Hook3.onEvent(ReasoningChunkEvent)
  ↓
[2] PostReasoningEvent触发
  ├─ Hook1.onEvent(PostReasoningEvent)
  ├─ Hook2.onEvent(PostReasoningEvent)
  └─ Hook3.onEvent(PostReasoningEvent)
  ↓
检查是否需要执行工具
  ↓
[3] PreActingEvent触发(如果有工具调用)
  ├─ Hook1.onEvent(PreActingEvent)
  ├─ Hook2.onEvent(PreActingEvent)
  └─ Hook3.onEvent(PreActingEvent)
  ↓
执行工具
  ├─ ActingChunkEvent触发(可能有)
  ↓
[4] PostActingEvent触发
  ├─ Hook1.onEvent(PostActingEvent)
  ├─ Hook2.onEvent(PostActingEvent)
  └─ Hook3.onEvent(PostActingEvent)
  ↓
继续下一轮或完成

8.1.3 Hook接口设计

package io.agentscope.core.hook;

import reactor.core.publisher.Mono;

/**
 * Hook接口定义了事件处理的规范
 */
public interface Hook {
    
    /**
     * 处理事件
     * 
     * @param event 事件对象(可能是PreReasoningEvent、PostReasoningEvent等)
     * @param <T> 事件类型
     * @return 处理后的事件(可以修改事件内容)
     */
    <T extends HookEvent> Mono<T> onEvent(T event);
    
    /**
     * Hook的优先级
     * 数字越小,优先级越高(越早执行)
     * 
     * @return 优先级值(默认100)
     */
    default int getPriority() {
        return 100;
    }
}

Hook的特点

1. 泛型设计
   - 一个Hook可以处理多种事件类型
   - 通过instanceof判断事件类型

2. 响应式
   - 返回Mono<T>,支持异步处理
   - 可以与其他异步操作组合

3. 链式调用
   - 多个Hook按优先级顺序执行
   - 每个Hook都可以修改事件

4. 非侵入式
   - 不修改Agent核心代码
   - 通过配置添加/移除Hook

8.2 核心Hook事件(Why - 为什么需要这些事件)

8.2.1 事件类型总览

AgentScope定义了6种核心事件:

推理阶段(3种):
├─ PreReasoningEvent      # 推理前
├─ ReasoningChunkEvent    # 推理流式内容
└─ PostReasoningEvent     # 推理后

行动阶段(3种):
├─ PreActingEvent         # 行动前(工具执行前)
├─ ActingChunkEvent       # 行动流式内容
└─ PostActingEvent        # 行动后(工具执行后)

8.2.2 推理阶段的事件

PreReasoningEvent - 推理前事件

/**
 * 推理前触发的事件
 * 可以修改输入消息、系统提示词、工具列表
 */
public class PreReasoningEvent extends HookEvent {
    
    // ===== 只读字段 =====
    private final String agentName;
    private final int iterationCount;        // 第几轮推理
    private final long timestamp;
    
    // ===== 可修改字段 =====
    private List<Msg> inputMessages;         // 输入消息(可修改)
    private String systemPrompt;             // 系统提示词(可修改)
    private List<ToolSchema> availableTools; // 可用工具(可修改)
    
    // Getter和Setter...
    public void setInputMessages(List<Msg> messages) {
        this.inputMessages = messages;
    }
}

典型应用

// 应用1:RAG增强
Hook ragHook = new Hook() {
    @Override
    public <T extends HookEvent> Mono<T> onEvent(T event) {
        if (event instanceof PreReasoningEvent pre) {
            // 检索相关文档
            List<Document> docs = knowledge.retrieve(
                extractQuery(pre.getInputMessages())
            );
            
            // 添加到输入消息
            List<Msg> enhanced = new ArrayList<>(pre.getInputMessages());
            enhanced.add(0, createRAGMessage(docs));
            pre.setInputMessages(enhanced);
        }
        return Mono.just(event);
    }
};

// 应用2:动态调整系统提示词
Hook promptHook = new Hook() {
    @Override
    public <T extends HookEvent> Mono<T> onEvent(T event) {
        if (event instanceof PreReasoningEvent pre) {
            String enhancedPrompt = pre.getSystemPrompt() + 
                "\n当前时间: " + LocalDateTime.now() +
                "\n用户偏好: " + getUserPreferences();
            pre.setSystemPrompt(enhancedPrompt);
        }
        return Mono.just(event);
    }
};

ReasoningChunkEvent - 推理流式内容

/**
 * 推理过程中的流式内容
 * 实时接收LLM生成的每个Token或块
 */
public class ReasoningChunkEvent extends HookEvent {
    private final String delta;              // 增量内容(一个Token或一个块)
    private final ContentBlock contentBlock; // 内容块(可能是TextBlock、ToolUseBlock等)
    private final String modelName;
    private final long timestamp;
    
    // 只读,不可修改
}

典型应用

// 应用:实时展示推理过程
Hook streamingHook = new Hook() {
    @Override
    public <T extends HookEvent> Mono<T> onEvent(T event) {
        if (event instanceof ReasoningChunkEvent chunk) {
            // 实时打印到控制台
            System.out.print(chunk.getDelta());
            
            // 或推送到WebSocket
            webSocketSession.send(chunk.getDelta());
            
            // 或更新UI
            Platform.runLater(() -> textArea.appendText(chunk.getDelta()));
        }
        return Mono.just(event);
    }
};

PostReasoningEvent - 推理后事件

/**
 * 推理完成后触发的事件
 * 包含推理结果和性能指标
 */
public class PostReasoningEvent extends HookEvent {
    private final Msg reasoningResult;       // 推理结果
    private final long executionTime;        // 推理耗时(毫秒)
    private final ChatUsage usage;           // Token使用情况
    private final String modelName;
    
    // 只读,不可修改
}

典型应用

// 应用1:性能监控
Hook monitoringHook = new Hook() {
    @Override
    public <T extends HookEvent> Mono<T> onEvent(T event) {
        if (event instanceof PostReasoningEvent post) {
            // 记录指标
            metrics.record("reasoning_duration_ms", post.getExecutionTime());
            metrics.record("tokens_used", post.getUsage().getTotalTokens());
            
            // 如果超过阈值,发送告警
            if (post.getExecutionTime() > 10000) {
                alertService.send("推理耗时过长: " + post.getExecutionTime() + "ms");
            }
        }
        return Mono.just(event);
    }
};

// 应用2:日志记录
Hook loggingHook = new Hook() {
    @Override
    public <T extends HookEvent> Mono<T> onEvent(T event) {
        if (event instanceof PostReasoningEvent post) {
            logger.info(
                "推理完成 - 耗时: {}ms, Tokens: {}, 是否调用工具: {}",
                post.getExecutionTime(),
                post.getUsage().getTotalTokens(),
                post.getReasoningResult().getContent().stream()
                    .anyMatch(c -> c instanceof ToolUseBlock)
            );
        }
        return Mono.just(event);
    }
};

8.2.3 行动阶段的事件

PreActingEvent - 工具执行前事件

/**
 * 工具执行前触发的事件
 */
public class PreActingEvent extends HookEvent {
    private final String toolName;               // 工具名称
    private final Map<String, Object> toolArgs;  // 工具参数
    private final String toolCallId;             // 工具调用ID
    private final long timestamp;
    
    // 只读,不可修改
}

典型应用

// 应用:工具调用审计
Hook auditHook = new Hook() {
    @Override
    public <T extends HookEvent> Mono<T> onEvent(T event) {
        if (event instanceof PreActingEvent pre) {
            auditLog.log(
                "工具调用 - 名称: {}, 参数: {}",
                pre.getToolName(),
                pre.getToolArgs()
            );
        }
        return Mono.just(event);
    }
};

PostActingEvent - 工具执行后事件

/**
 * 工具执行后触发的事件
 */
public class PostActingEvent extends HookEvent {
    private final String toolName;           // 工具名称
    private final String toolResult;         // 工具执行结果
    private final boolean isError;           // 是否出错
    private final long executionTime;        // 执行耗时
    private final String errorMessage;       // 错误信息(如果有)
    
    // 只读,不可修改
}

典型应用

// 应用:工具性能监控
Hook toolMonitoringHook = new Hook() {
    @Override
    public <T extends HookEvent> Mono<T> onEvent(T event) {
        if (event instanceof PostActingEvent post) {
            metrics.record(
                "tool_execution_time",
                post.getExecutionTime(),
                Map.of("tool_name", post.getToolName())
            );
            
            if (post.isError()) {
                errorLog.error(
                    "工具执行失败 - 工具: {}, 错误: {}",
                    post.getToolName(),
                    post.getErrorMessage()
                );
            }
        }
        return Mono.just(event);
    }
};

8.3 Hook的实际应用(How - 如何使用)

8.3.1 应用1:日志记录Hook

完整的日志记录Hook,记录Agent执行的每个阶段。

import io.agentscope.core.hook.Hook;
import io.agentscope.core.hook.HookEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;

/**
 * 日志记录Hook
 * 记录Agent执行的所有关键事件
 */
public class LoggingHook implements Hook {
    
    private static final Logger logger = LoggerFactory.getLogger(LoggingHook.class);
    
    @Override
    public <T extends HookEvent> Mono<T> onEvent(T event) {
        return Mono.just(event).doOnNext(e -> logEvent(e));
    }
    
    private void logEvent(HookEvent event) {
        switch(event) {
            case PreReasoningEvent pre -> {
                logger.info("━━━━━ 推理开始 ━━━━━");
                logger.info("Agent: {}", pre.getAgentName());
                logger.info("轮次: {}", pre.getIterationCount());
                logger.info("输入消息数: {}", pre.getInputMessages().size());
                logger.info("可用工具数: {}", pre.getAvailableTools().size());
            }
            
            case ReasoningChunkEvent chunk -> {
                logger.debug("推理流: {}", chunk.getDelta());
            }
            
            case PostReasoningEvent post -> {
                logger.info("━━━━━ 推理完成 ━━━━━");
                logger.info("耗时: {}ms", post.getExecutionTime());
                logger.info("Token使用: {} (输入: {}, 输出: {})",
                    post.getUsage().getTotalTokens(),
                    post.getUsage().getPromptTokens(),
                    post.getUsage().getCompletionTokens()
                );
                
                boolean hasToolCall = post.getReasoningResult().getContent().stream()
                    .anyMatch(c -> c instanceof ToolUseBlock);
                logger.info("是否调用工具: {}", hasToolCall);
            }
            
            case PreActingEvent pre -> {
                logger.info("━━━━━ 工具执行开始 ━━━━━");
                logger.info("工具名称: {}", pre.getToolName());
                logger.info("工具参数: {}", pre.getToolArgs());
            }
            
            case PostActingEvent post -> {
                logger.info("━━━━━ 工具执行完成 ━━━━━");
                logger.info("工具名称: {}", post.getToolName());
                logger.info("耗时: {}ms", post.getExecutionTime());
                logger.info("是否出错: {}", post.isError());
                if (post.isError()) {
                    logger.error("错误信息: {}", post.getErrorMessage());
                } else {
                    logger.info("执行结果: {}", 
                        post.getToolResult().substring(0, Math.min(100, post.getToolResult().length())));
                }
            }
            
            default -> {}
        }
    }
    
    @Override
    public int getPriority() {
        return 10;  // 高优先级,最先执行
    }
}

// 使用示例
ReActAgent agent = ReActAgent.builder()
    .name("Agent")
    .model(model)
    .toolkit(toolkit)
    .hooks(List.of(new LoggingHook()))
    .build();

日志输出示例

INFO  - ━━━━━ 推理开始 ━━━━━
INFO  - Agent: Agent
INFO  - 轮次: 1
INFO  - 输入消息数: 1
INFO  - 可用工具数: 5

INFO  - ━━━━━ 推理完成 ━━━━━
INFO  - 耗时: 1250ms
INFO  - Token使用: 450 (输入: 320, 输出: 130)
INFO  - 是否调用工具: true

INFO  - ━━━━━ 工具执行开始 ━━━━━
INFO  - 工具名称: query_database
INFO  - 工具参数: {sql=SELECT * FROM users WHERE id = 123}

INFO  - ━━━━━ 工具执行完成 ━━━━━
INFO  - 工具名称: query_database
INFO  - 耗时: 85ms
INFO  - 是否出错: false
INFO  - 执行结果: {"id":123,"name":"张三","age":28}

8.3.2 应用2:性能监控Hook

监控Agent的性能指标,发送到监控系统。

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;

/**
 * 性能监控Hook
 * 集成Prometheus、DataDog等监控系统
 */
public class MetricsHook implements Hook {
    
    private final MeterRegistry registry;
    
    public MetricsHook(MeterRegistry registry) {
        this.registry = registry;
    }
    
    @Override
    public <T extends HookEvent> Mono<T> onEvent(T event) {
        return switch(event) {
            case PostReasoningEvent post -> 
                Mono.just(event).doOnNext(e -> recordReasoningMetrics(post));
            
            case PostActingEvent post -> 
                Mono.just(event).doOnNext(e -> recordToolMetrics(post));
            
            default -> Mono.just(event);
        };
    }
    
    private void recordReasoningMetrics(PostReasoningEvent event) {
        // 记录推理耗时
        Timer.builder("agent.reasoning.duration")
            .tag("agent", event.getAgentName())
            .tag("model", event.getModelName())
            .register(registry)
            .record(event.getExecutionTime(), TimeUnit.MILLISECONDS);
        
        // 记录Token使用
        registry.counter("agent.tokens.total",
            "agent", event.getAgentName(),
            "type", "total"
        ).increment(event.getUsage().getTotalTokens());
        
        registry.counter("agent.tokens.prompt",
            "agent", event.getAgentName()
        ).increment(event.getUsage().getPromptTokens());
        
        registry.counter("agent.tokens.completion",
            "agent", event.getAgentName()
        ).increment(event.getUsage().getCompletionTokens());
        
        // 记录推理次数
        registry.counter("agent.reasoning.count",
            "agent", event.getAgentName()
        ).increment();
    }
    
    private void recordToolMetrics(PostActingEvent event) {
        // 记录工具执行耗时
        Timer.builder("agent.tool.duration")
            .tag("tool", event.getToolName())
            .tag("status", event.isError() ? "error" : "success")
            .register(registry)
            .record(event.getExecutionTime(), TimeUnit.MILLISECONDS);
        
        // 记录工具调用次数
        registry.counter("agent.tool.calls",
            "tool", event.getToolName(),
            "status", event.isError() ? "error" : "success"
        ).increment();
        
        // 记录工具错误
        if (event.isError()) {
            registry.counter("agent.tool.errors",
                "tool", event.getToolName()
            ).increment();
        }
    }
}

// 使用示例
MeterRegistry registry = new SimpleMeterRegistry();

ReActAgent agent = ReActAgent.builder()
    .name("MonitoredAgent")
    .hooks(List.of(new MetricsHook(registry)))
    .build();

// 指标会自动发送到Prometheus等监控系统

8.3.3 应用3:RAG集成Hook

在推理前自动检索相关知识,增强Agent的回答能力。

import io.agentscope.core.rag.Knowledge;
import io.agentscope.core.rag.RetrieveConfig;

/**
 * RAG增强Hook
 * 自动检索相关文档并添加到输入消息
 */
public class RAGHook implements Hook {
    
    private final Knowledge knowledge;
    private final int topK;
    private final double scoreThreshold;
    
    public RAGHook(Knowledge knowledge, int topK, double scoreThreshold) {
        this.knowledge = knowledge;
        this.topK = topK;
        this.scoreThreshold = scoreThreshold;
    }
    
    @Override
    public <T extends HookEvent> Mono<T> onEvent(T event) {
        if (!(event instanceof PreReasoningEvent pre)) {
            return Mono.just(event);
        }
        
        // 从用户消息提取查询
        String query = extractQuery(pre.getInputMessages());
        
        // 检索相关文档
        return knowledge.retrieve(query, RetrieveConfig.builder()
            .limit(topK)
            .scoreThreshold(scoreThreshold)
            .build())
        
        .doOnNext(documents -> {
            if (documents.isEmpty()) {
                return;
            }
            
            // 格式化检索结果
            String ragContext = formatDocuments(documents);
            
            // 创建RAG消息
            Msg ragMsg = Msg.builder()
                .role(MsgRole.SYSTEM)
                .textContent(
                    "以下是与用户问题相关的背景信息,请参考这些信息回答用户:\n\n" +
                    ragContext
                )
                .metadata("source", "rag")
                .build();
            
            // 添加到输入消息(放在最前面)
            List<Msg> enhanced = new ArrayList<>();
            enhanced.add(ragMsg);
            enhanced.addAll(pre.getInputMessages());
            
            pre.setInputMessages(enhanced);
        })
        
        .thenReturn(event);
    }
    
    private String extractQuery(List<Msg> messages) {
        // 提取最近的用户消息作为查询
        return messages.stream()
            .filter(m -> m.getRole() == MsgRole.USER)
            .map(Msg::getTextContent)
            .reduce((first, second) -> second)  // 取最后一条
            .orElse("");
    }
    
    private String formatDocuments(List<Document> documents) {
        StringBuilder sb = new StringBuilder();
        
        for (int i = 0; i < documents.size(); i++) {
            Document doc = documents.get(i);
            sb.append(String.format(
                "[文档%d] (相关度: %.2f)\n%s\n\n",
                i + 1,
                doc.getScore(),
                doc.getContent()
            ));
        }
        
        return sb.toString();
    }
    
    @Override
    public int getPriority() {
        return 50;  // 中等优先级
    }
}

// 使用示例
Knowledge knowledge = createKnowledge();  // 创建知识库

ReActAgent agent = ReActAgent.builder()
    .name("RAGAgent")
    .model(model)
    .hooks(List.of(
        new RAGHook(knowledge, 5, 0.7)  // 检索top 5,相似度>0.7
    ))
    .build();

// 用户提问时,自动检索相关文档
agent.call("AgentScope支持哪些LLM?").block();

// Hook自动:
// 1. 检索相关文档
// 2. 添加到输入消息
// 3. LLM基于检索结果回答

8.3.4 应用4:安全检查Hook

在推理前检查输入安全性,防止注入攻击。

/**
 * 安全检查Hook
 * 检测和过滤危险输入
 */
public class SecurityHook implements Hook {
    
    private final List<String> blockedPatterns = List.of(
        "ignore previous instructions",
        "disregard",
        "system prompt",
        "你现在是",
        "忽略之前的指令"
    );
    
    @Override
    public <T extends HookEvent> Mono<T> onEvent(T event) {
        if (!(event instanceof PreReasoningEvent pre)) {
            return Mono.just(event);
        }
        
        // 检查用户输入
        List<Msg> messages = pre.getInputMessages();
        
        for (Msg msg : messages) {
            if (msg.getRole() != MsgRole.USER) {
                continue;
            }
            
            String content = msg.getTextContent().toLowerCase();
            
            // 检测危险模式
            for (String pattern : blockedPatterns) {
                if (content.contains(pattern)) {
                    logger.warn("检测到可疑输入: {}", content);
                    
                    // 可以选择:
                    // 1. 阻止请求
                    return Mono.error(new SecurityException(
                        "检测到不安全的输入,请求被拒绝"));
                    
                    // 2. 或者清理输入
                    // msg.setTextContent(sanitize(content));
                }
            }
        }
        
        return Mono.just(event);
    }
    
    @Override
    public int getPriority() {
        return 1;  // 最高优先级,最先执行
    }
}

8.3.5 应用5:成本控制Hook

监控和限制Token使用,控制成本。

/**
 * 成本控制Hook
 * 限制Token使用,防止成本超标
 */
public class CostControlHook implements Hook {
    
    private final AtomicLong totalTokens = new AtomicLong(0);
    private final long maxTokensPerSession;
    private final double costPerToken;
    
    public CostControlHook(long maxTokensPerSession, double costPerToken) {
        this.maxTokensPerSession = maxTokensPerSession;
        this.costPerToken = costPerToken;
    }
    
    @Override
    public <T extends HookEvent> Mono<T> onEvent(T event) {
        if (event instanceof PostReasoningEvent post) {
            long tokens = post.getUsage().getTotalTokens();
            long total = totalTokens.addAndGet(tokens);
            
            logger.info("Token使用: {} (总计: {}/{})",
                tokens, total, maxTokensPerSession);
            
            // 检查是否超限
            if (total > maxTokensPerSession) {
                logger.error("Token使用超限!");
                return Mono.error(new CostLimitExceededException(
                    "Token使用超过限制: " + total + "/" + maxTokensPerSession
                ));
            }
            
            // 计算成本
            double cost = total * costPerToken;
            logger.info("当前成本: ${}", String.format("%.4f", cost));
        }
        
        return Mono.just(event);
    }
    
    public void resetCounter() {
        totalTokens.set(0);
    }
    
    public long getTotalTokens() {
        return totalTokens.get();
    }
    
    public double getTotalCost() {
        return totalTokens.get() * costPerToken;
    }
}

// 使用示例
CostControlHook costHook = new CostControlHook(
    100000,  // 每个会话最多10万Token
    0.002    // 每个Token成本$0.002
);

ReActAgent agent = ReActAgent.builder()
    .name("CostControlledAgent")
    .hooks(List.of(costHook))
    .build();

// 查询成本
System.out.println("总Token: " + costHook.getTotalTokens());
System.out.println("总成本: $" + costHook.getTotalCost());

8.4 Hook的最佳实践

8.4.1 Hook设计原则

原则1:单一职责
✓ 每个Hook只做一件事
✓ 日志Hook只记录日志,监控Hook只记录指标

原则2:最小副作用
✓ 尽量不修改事件内容
✓ 如果必须修改,记录日志

原则3:性能优先
✓ Hook会在每次调用时执行,必须高效
✓ 避免阻塞操作
✓ 使用异步API

原则4:错误隔离
✓ Hook出错不应影响Agent执行
✓ 捕获所有异常
✓ 使用doOnError处理错误

8.4.2 Hook优先级管理

/**
 * Hook执行顺序示例
 */
public class HookPriorityExample {
    
    public static void main(String[] args) {
        Hook securityHook = new SecurityHook();    // priority = 1
        Hook loggingHook = new LoggingHook();      // priority = 10
        Hook ragHook = new RAGHook(...);           // priority = 50
        Hook metricsHook = new MetricsHook(...);   // priority = 100
        
        // 执行顺序(从小到大):
        // 1. securityHook (priority=1)     - 最先执行,检查安全
        // 2. loggingHook (priority=10)     - 记录日志
        // 3. ragHook (priority=50)         - RAG增强
        // 4. metricsHook (priority=100)    - 记录指标
        
        ReActAgent agent = ReActAgent.builder()
            .hooks(List.of(securityHook, loggingHook, ragHook, metricsHook))
            .build();
        
        // AgentScope会自动按优先级排序
    }
}

8.4.3 异步Hook的实现

/**
 * 异步Hook示例
 * 不阻塞Agent执行
 */
public class AsyncMetricsHook implements Hook {
    
    private final WebClient metricsClient;
    
    @Override
    public <T extends HookEvent> Mono<T> onEvent(T event) {
        if (event instanceof PostReasoningEvent post) {
            // 异步发送指标,不阻塞
            sendMetricsAsync(post)
                .subscribeOn(Schedulers.boundedElastic())
                .subscribe(
                    result -> logger.debug("指标已发送"),
                    error -> logger.error("指标发送失败", error)
                );
        }
        
        // 立即返回,不等待异步操作完成
        return Mono.just(event);
    }
    
    private Mono<Void> sendMetricsAsync(PostReasoningEvent event) {
        return metricsClient.post()
            .uri("/metrics")
            .bodyValue(Map.of(
                "duration", event.getExecutionTime(),
                "tokens", event.getUsage().getTotalTokens()
            ))
            .retrieve()
            .bodyToMono(Void.class)
            .timeout(Duration.ofSeconds(5))
            .onErrorResume(error -> {
                logger.error("发送指标失败", error);
                return Mono.empty();
            });
    }
}

8.5 生产场景:完整的Hook系统

让我们通过一个完整的生产级示例,展示多个Hook协同工作。

/**
 * 生产级Hook配置
 * 集成日志、监控、RAG、安全等功能
 */
public class ProductionHookConfiguration {
    
    public static List<Hook> createProductionHooks(
            MeterRegistry registry,
            Knowledge knowledge) {
        
        return List.of(
            // 1. 安全检查(最高优先级)
            new SecurityHook(),
            
            // 2. 日志记录
            new LoggingHook(),
            
            // 3. 成本控制
            new CostControlHook(100000, 0.002),
            
            // 4. RAG增强
            new RAGHook(knowledge, 5, 0.7),
            
            // 5. 性能监控
            new MetricsHook(registry),
            
            // 6. 错误追踪
            new ErrorTrackingHook()
        );
    }
}

// 使用示例
public class ProductionAgentSetup {
    
    public static void main(String[] args) {
        // 创建监控注册表
        MeterRegistry registry = new PrometheusMeterRegistry(
            PrometheusConfig.DEFAULT
        );
        
        // 创建知识库
        Knowledge knowledge = createKnowledge();
        
        // 创建生产级Agent
        ReActAgent agent = ReActAgent.builder()
            .name("ProductionAgent")
            .model(model)
            .toolkit(toolkit)
            .memory(new InMemoryMemory())
            .hooks(ProductionHookConfiguration.createProductionHooks(
                registry, knowledge
            ))
            .build();
        
        // Agent现在具备:
        // ✓ 安全检查
        // ✓ 完整日志
        // ✓ 成本控制
        // ✓ RAG增强
        // ✓ 性能监控
        // ✓ 错误追踪
    }
}

8.6 本章总结

关键要点

  1. Hook系统设计

    • 责任链模式实现
    • 6种核心事件类型
    • 可修改和只读事件
  2. Hook接口

    • onEvent()方法处理事件
    • getPriority()定义优先级
    • 响应式编程支持
  3. 实际应用

    • 日志记录:LoggingHook
    • 性能监控:MetricsHook
    • RAG增强:RAGHook
    • 安全检查:SecurityHook
    • 成本控制:CostControlHook
  4. 最佳实践

    • 单一职责原则
    • 最小副作用
    • 异步执行
    • 错误隔离

Hook开发检查清单

设计阶段:
☐ 明确Hook的单一职责
☐ 确定需要处理的事件类型
☐ 设计Hook的优先级

实现阶段:
☐ 实现Hook接口
☐ 使用switch表达式处理不同事件
☐ 避免阻塞操作
☐ 添加错误处理

测试阶段:
☐ 测试Hook对各种事件的处理
☐ 测试Hook的性能影响
☐ 测试Hook的错误处理
☐ 测试Hook的优先级

部署阶段:
☐ 配置合适的优先级
☐ 添加监控和告警
☐ 文档化Hook的作用

下一章预告

第9章将深入讲解Formatter与多模型适配,探讨如何自动转换消息格式以适配不同的LLM厂商,实现真正的多模型支持。