9.1 装饰器模式与管道架构

概述

RAF 的 ChatClient 管道采用**装饰器模式(Decorator Pattern)** 构建。`IChatClient` 作为统一接口,`DelegatingChatClient` 作为装饰器基类,`ChatClientBuilder` 按注册顺序组装装饰器链。这个设计将横切关注点(工具调用、审批、持久化)从 Agent 层解耦到管道层,实现了高度可复用的模块化架构。

为什么选择装饰器模式

传统的 Agent 层工具循环

在早期的 Agent 框架中,工具调用循环通常在 Agent 层实现:

传统模式:
  Agent.run()
    → 组装消息
    → 调用 LLM
    → 收到 tool_calls
    → 执行工具
    → 追加结果到消息
    → 再次调用 LLM
    → ... 循环直到停止

**问题**: - 工具循环逻辑与 Agent 逻辑紧密耦合 - 难以在不同的 LLM 提供商之间复用 - 添加新功能(如审批、持久化)需要修改 Agent 代码 - 难以进行单元测试(需要 mock 整个 Agent)

RAF 的装饰器模式

RAF 将工具调用循环下沉到 `IChatClient` 装饰器层:

RAF 模式:
  Agent.run()
    → 组装消息(纯组装,不关心工具)
    → IChatClient.run(messages)
      → FunctionInvokingChatClient.run()
        → 调用 inner client → LLM 响应
        → 如果有 tool_calls → 执行工具 → 循环
        → 返回最终流

**优势**: - **关注点分离**:Agent 负责"调度",装饰器负责"执行" - **可组合性**:通过 `ChatClientBuilder` 自由组合装饰器 - **可测试性**:可以单独 mock `IChatClient` 来测试装饰器行为 - **提供商无关**:同一个 `FunctionInvokingChatClient` 可以包裹任何 `IChatClient` 实现

IChatClient — 统一接口

// crates/core/src/chat_client.rs

#[async_trait]
pub trait IChatClient: Send + Sync {
    /// 执行聊天补全,产生更新增量的流式响应
    async fn run(
        &self,
        messages: &[ChatMessage],
        options: ChatClientRunOptions,
    ) -> Result<BoxStream<'static, Result<AgentResponseUpdate>>>;

    /// 返回此客户端使用的模型标识符
    fn model_id(&self) -> &str;

    /// 返回模型元数据,描述能力边界
    fn model_metadata(&self) -> Option<&ModelMetadata> {
        None
    }

    /// 返回装饰器链中的内部客户端
    fn inner_client(&self) -> Option<&Arc<dyn IChatClient>> {
        None
    }
}

`IChatClient` 的 `run()` 方法签名是统一的关键: - **输入**:`&[ChatMessage]`(消息列表)和 `ChatClientRunOptions`(运行选项) - **输出**:`BoxStream<'static, Result<AgentResponseUpdate>>`(流式响应)

这个签名使得任何 `IChatClient` 实现都可以被另一个 `IChatClient` 包装,形成装饰器链。

DelegatingChatClient — 装饰器基类

// crates/core/src/chat_client.rs

pub struct DelegatingChatClient {
    inner: Arc<dyn IChatClient>,
}

impl DelegatingChatClient {
    pub fn new(inner: Arc<dyn IChatClient>) -> Self {
        Self { inner }
    }

    pub fn inner(&self) -> &Arc<dyn IChatClient> {
        &self.inner
    }
}

#[async_trait]
impl IChatClient for DelegatingChatClient {
    async fn run(
        &self,
        messages: &[ChatMessage],
        options: ChatClientRunOptions,
    ) -> Result<BoxStream<'static, Result<AgentResponseUpdate>>> {
        self.inner.run(messages, options).await
    }

    fn model_id(&self) -> &str {
        self.inner.model_id()
    }

    fn model_metadata(&self) -> Option<&ModelMetadata> {
        self.inner.model_metadata()
    }

    fn inner_client(&self) -> Option<&Arc<dyn IChatClient>> {
        Some(&self.inner)
    }
}

`DelegatingChatClient` 提供了**透传(passthrough)** 的默认行为:所有方法直接委托给 `inner`。自定义装饰器(如 `FunctionInvokingChatClient`)重写需要拦截的方法。

自定义装饰器示例

struct LoggingDecorator {
    inner: Arc<dyn IChatClient>,
}

#[async_trait]
impl IChatClient for LoggingDecorator {
    async fn run(
        &self,
        messages: &[ChatMessage],
        options: ChatClientRunOptions,
    ) -> Result<BoxStream<'static, Result<AgentResponseUpdate>>> {
        tracing::info!(
            msg_count = messages.len(),
            model = self.inner.model_id(),
            "ChatClient::run called"
        );
        self.inner.run(messages, options).await
    }

    fn model_id(&self) -> &str {
        self.inner.model_id()
    }
}

ChatClientBuilder — 管道构建器

`ChatClientBuilder` 是组装装饰器链的工厂:

// crates/core/src/chat_client.rs

pub struct ChatClientBuilder {
    decorators: Vec<Box<dyn Fn(Arc<dyn IChatClient>) -> Arc<dyn IChatClient> + Send + Sync>>,
    leaf: Option<Arc<dyn IChatClient>>,
}

impl ChatClientBuilder {
    pub fn new() -> Self { /* ... */ }

    /// 设置叶子 ChatClient(实际的 LLM 服务客户端)
    pub fn leaf(mut self, client: Arc<dyn IChatClient>) -> Self {
        self.leaf = Some(client);
        self
    }

    /// 添加装饰器工厂
    /// 装饰器按注册顺序包装:先注册的装饰器在最外层
    pub fn use_decorator(
        mut self,
        factory: Box<dyn Fn(Arc<dyn IChatClient>) -> Arc<dyn IChatClient> + Send + Sync>,
    ) -> Self {
        self.decorators.push(factory);
        self
    }

    /// 构建管道
    pub fn build(self) -> Result<Arc<dyn IChatClient>> {
        let mut client = self.leaf.ok_or_else(|| {
            AgentError::ConfigError("leaf IChatClient is required".into())
        })?;
        for factory in self.decorators {
            client = factory(client);
        }
        Ok(client)
    }
}

管道组装示意

graph LR
    subgraph "ChatClientBuilder.build()"
        direction LR
        A[leaf: DeepSeekChatClient] --> B[factory[0]: PerServiceCallPersisting]
        B --> C[factory[1]: FunctionInvokingChatClient]
    end

    subgraph "运行时调用链"
        direction RL
        D[Agent.run()] -->|IChatClient::run| C
        C -->|IChatClient::run| B
        B -->|IChatClient::run| A
    end

    style C fill:#4CAF50,color:white
    style A fill:#2196F3,color:white

注册顺序决定了包装顺序: - **先注册的装饰器在最外层**(最先被调用) - **叶子客户端在最内层**(最后被调用)

let pipeline = ChatClientBuilder::new()
    .leaf(Arc::new(DeepSeekChatClient::new(llm_options)?))
    .use_decorator(Box::new(|inner| {
        Arc::new(PerServiceCallPersistingChatClient::new(inner, session))
    }))
    .use_decorator(Box::new(|inner| {
        Arc::new(FunctionInvokingChatClient::new(inner, tools))
    }))
    .build()?;

// 运行时调用顺序:
// Agent → FunctionInvoking → PerServiceCallPersisting → DeepSeek → HTTP

架构对比:RAF vs Agent 层工具循环

graph TB
    subgraph "传统模式:Agent 层循环"
        AG[Agent] -->|1. 调用 LLM| LLM1[LLM]
        LLM1 -->|2. 返回 tool_calls| AG
        AG -->|3. 执行工具| T1[Tools]
        T1 -->|4. 返回结果| AG
        AG -->|5. 追加消息,再次调用| LLM1
    end

    subgraph "RAF 模式:装饰器层循环"
        AG2[Agent<br/>纯消息组装] -->|调用| FICC[FunctionInvoking<br/>ChatClient]
        FICC -->|1. 调用 LLM| LLM2[LLM]
        LLM2 -->|2. 返回 tool_calls| FICC
        FICC -->|3. 执行工具| T2[Tools]
        T2 -->|4. 返回结果| FICC
        FICC -->|5. 追加消息,再次调用| LLM2
        FICC -->|6. 最终流| AG2
    end

    style AG fill:#ff9800,color:white
    style FICC fill:#4CAF50,color:white

使用示例:完整管道构建

use std::sync::Arc;
use rust_agent_core::{ChatClientBuilder, ChatClientRunOptions, IChatClient};
use rust_agent_client::{DeepSeekChatClient, ChatClientOptions};
use rust_agent_framework::FunctionInvokingChatClient;

// 1. 创建叶子客户端
let llm_options = ChatClientOptions::deepseek(
    "deepseek-chat",
    std::env::var("DEEPSEEK_API_KEY").unwrap(),
);
let leaf = DeepSeekChatClient::new(llm_options).unwrap();

// 2. 注册工具
let tools: Vec<Arc<dyn ITool>> = vec![
    Arc::new(ReadFile { scope: None }),
    Arc::new(WriteFile { scope: None }),
];

// 3. 构建管道
let pipeline = ChatClientBuilder::new()
    .leaf(Arc::new(leaf))
    .use_decorator(Box::new(move |inner| {
        Arc::new(FunctionInvokingChatClient::new(inner, tools.clone())
            .with_max_rounds(5))
    }))
    .build()
    .unwrap();

// 4. 使用管道 — 装饰器透明地处理工具调用
let stream = pipeline.run(
    &[ChatMessage::user("读取 Cargo.toml 的内容")],
    ChatClientRunOptions::default(),
).await.unwrap();

inner_client() 链式访问

`inner_client()` 方法允许在运行时穿透装饰器链,访问内部客户端:

// 获取最内层的叶子客户端
fn get_leaf_client(client: &Arc<dyn IChatClient>) -> &Arc<dyn IChatClient> {
    let mut current = client;
    while let Some(inner) = current.inner_client() {
        current = inner;
    }
    current
}

这在 `SkillMemoryContextProvider` 等场景中至关重要——它需要访问原始 API 客户端来创建子 Agent(如 `MemoryAgent`)。

归纳

装饰器模式为 RAF 带来了以下架构收益:

| 收益 | 实现方式 | |------|---------| | 关注点分离 | Agent 负责调度,装饰器负责执行 | | 可组合性 | `ChatClientBuilder` 按序组装任意装饰器链 | | 可测试性 | 每个装饰器可独立测试,只需 mock 内部 `IChatClient` | | 提供商无关 | 任何实现 `IChatClient` 的提供商都可被装饰 | | 横切关注点 | 工具调用、审批、持久化作为独立装饰器实现 |

这个设计直接参照了 MAF (Microsoft Agent Framework) 的 `FunctionInvokingChatClient` + `DelegatingChatClient` 模式,但使用 Rust 的 trait 系统和 `Arc<dyn>` 替代了 C# 的接口继承,保留了 Rust 生态的惯用表达。