ChatClientAgent

`ChatClientAgent` 是 `IAgent` trait 的核心实现,对齐 MAF 的 `ChatClientAgent` 类。它是一个自包含的 Agent 运行时,持有 LLM 客户端、工具注册表、上下文提供器链和压缩策略。

结构体字段

pub struct ChatClientAgent {
    id: AgentId,                                       // Agent 唯一标识
    metadata: AgentMetadata,                           // 静态元数据
    chat_client: Arc<dyn IChatClient>,                 // LLM 客户端管道
    instructions: String,                              // 系统指令
    tools: Arc<tokio::sync::RwLock<ToolRegistry>>,     // 工具注册表(读写锁)
    context_providers: Vec<Arc<dyn IContextProvider>>,  // Provider 链
    compression_strategy: Option<Arc<dyn ICompressionStrategy>>,  // 压缩策略
    token_counter: Option<Arc<dyn ITokenCounter>>,     // Token 计数器
}

字段详解

| 字段 | 类型 | 说明 | |------|------|------| | `id` | `AgentId` | 构造时从 `name` 参数生成。 | | `metadata` | `AgentMetadata` | `agent_type` 固定为 `"ChatClientAgent"`;`key` 同 `id`;`description`、`tool_names` 等通过方法设置。 | | `chat_client` | `Arc<dyn IChatClient>` | 可以是管道包装后的客户端(`FunctionInvokingChatClient` → `DeepSeekChatClient`)。 | | `instructions` | `String` | 系统指令文本。在 `run()` 中被组装为 `system` 消息。 | | `tools` | `Arc<RwLock<ToolRegistry>>` | 使用 `RwLock` 支持并发读取工具列表。`run()` 中每次 LLM 调用前读取以生成工具定义。 | | `context_providers` | `Vec<Arc<dyn IContextProvider>>` | 按注册顺序执行的 Provider 链。`AgentBuilder` 默认注入 `InMemoryHistoryProvider`。 | | `compression_strategy` | `Option<Arc<dyn ICompressionStrategy>>` | 压缩策略(可选)。需要同时配置 `token_counter` 才生效。 | | `token_counter` | `Option<Arc<dyn ITokenCounter>>` | Token 计数(可选)。配合压缩策略使用。 |

构造方法

`new()`

pub fn new(name: impl Into<String>, chat_client: Arc<dyn IChatClient>) -> Self

创建最小化 Agent,仅设置 `id`、`metadata` 和 `chat_client`,其余字段使用默认值。

let client = Arc::new(DeepSeekChatClient::from_key("sk-...", "deepseek-chat")?);
let agent = ChatClientAgent::new("my-agent", client);

链式配置方法

// 设置指令
agent = agent.with_instructions("You are a helpful assistant.");

// 设置工具注册表
let mut registry = ToolRegistry::new();
registry.register(ReadFile { scope: None });
agent = agent.with_tools(registry);

// 设置描述
agent = agent.with_description("文件管理助手");

// 设置 ContextProvider 链(覆盖原有链)
agent = agent.with_context_providers(vec![
    Arc::new(InMemoryHistoryProvider::new()),
    Arc::new(WorkspaceContextProvider::new(scope)),
]);

// 设置压缩策略
agent = agent.with_compression_strategy(Arc::new(
    TokenBudgetStrategy::new()
));

// 设置 Token 计数器
agent = agent.with_token_counter(Arc::new(EstimateCounter::new()));

运行时访问

// 并发读取工具注册表
let tools_guard = agent.tools().await;
for tool in tools_guard.list() {
    println!("Tool: {}", tool.name());
}
// tools_guard 自动释放

IAgent 实现

#[async_trait]
impl IAgent for ChatClientAgent {
    fn id(&self) -> &AgentId { &self.id }
    fn metadata(&self) -> &AgentMetadata { &self.metadata }

    async fn run(
        &self,
        messages: Vec<ChatMessage>,
        session: Option<Arc<dyn ISession>>,
        options: Option<AgentRunOptions>,
    ) -> Result<BoxStream<'static, Result<AgentResponseResult>>> {
        // 完整的三阶段生命周期(详见 run-lifecycle.md)
    }

    async fn reset(&self) -> Result<()> { Ok(()) }

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

`chat_client()` 暴露

`ChatClientAgent` 重写 `chat_client()` 返回 `Some(&self.chat_client)`。这使得 ContextProvider 能够通过 `agent.chat_client()` 发现底层 LLM 客户端。例如,`SkillMemoryContextProvider` 使用它创建 `MemoryAgent` 进行后台记忆整合。

AgentProxy 辅助类型

在 Phase 3(Post-invocation)中,`ChatClientAgent` 创建一个 `AgentProxy` 实例传递给 Provider:

struct AgentProxy {
    id: AgentId,
    metadata: AgentMetadata,
    chat_client: Arc<dyn IChatClient>,
}

`AgentProxy` 的 `run()` 方法固定返回 `ConfigError`——它仅用于 `on_invoked()` 回调中提供 Agent 身份信息,不允许 Provider 通过它触发新的 LLM 调用。

ChatClientAgent 与 AgentBuilder 的关系

`AgentBuilder` 是 `ChatClientAgent` 的构造器门面(Facade),处理了以下复杂性:

1. **默认 Provider**:自动注入 `InMemoryHistoryProvider` 2. **管道组装**:当注册工具时,用 `FunctionInvokingChatClient` 包裹叶子客户端 3. **ToolRegistry 初始化**:自动创建并注册工具的 `ToolRegistry` 4. **参数校验**:确保 `chat_client` 已设置

// AgentBuilder::build() 内部逻辑(简化版)
pub fn build(self) -> Result<Arc<dyn IAgent>> {
    let chat_client = self.chat_client
        .ok_or_else(|| AgentError::ConfigError("chat_client is required".into()))?;

    // 管道组装:如果有工具,包裹 FunctionInvokingChatClient
    let pipeline_client = if !self.tools.is_empty() {
        ChatClientBuilder::new()
            .leaf(Arc::new(chat_client))
            .use_decorator(Box::new(move |inner| {
                Arc::new(FunctionInvokingChatClient::new(inner, tools).with_max_rounds(max_rounds))
            }))
            .build()?
    } else {
        Arc::new(chat_client)
    };

    // 构造 ChatClientAgent 并链式配置
    let mut agent = ChatClientAgent::new(&self.agent_id, pipeline_client)
        .with_instructions(&self.instructions)
        .with_context_providers(self.context_providers);

    if let Some(strategy) = self.compression_strategy {
        agent = agent.with_compression_strategy(strategy);
    }
    if let Some(counter) = self.token_counter {
        agent = agent.with_token_counter(counter);
    }

    // 返回 trait object
    Ok(Arc::new(agent))
}

内部模块引用

`ChatClientAgent` 使用以下内部模块:

| 模块 | 用途 | |------|------| | `converter::AgentResponseConverter` | SSE 增量 → 结构化 Content/Event | | `memory::memory_context::build_turn_transcript` | 构建本轮对话完整记录 |

方法提取:`spawn_post_invocation_handler`

`run()` 方法原本约 330 行,Phase 3(Post-invocation)的 channel 分叉逻辑已提取为独立方法 `spawn_post_invocation_handler`(位于 `impl ChatClientAgent` 块中):

impl ChatClientAgent {
    fn spawn_post_invocation_handler(
        &self,
        converted: impl Stream<Item = Result<AgentResponseResult>> + Send + 'static,
        session: Option<Arc<dyn ISession>>,
        request_messages: Vec<ChatMessage>,
        caller_messages: Vec<ChatMessage>,
    ) -> impl Stream<Item = Result<AgentResponseResult>> + Send + 'static
}

该方法负责: 1. 通过 `tokio::sync::mpsc::unbounded_channel` 分叉流式响应 2. 后台收集完整响应后调用所有 `IContextProvider::on_invoked` 钩子 3. 将 assistant/tool 消息持久化到 session

提取后 `run()` 方法缩减至约 150 行,职责更清晰:Phase 1(预调用)→ Phase 1.5(压缩)→ Phase 2(LLM 调用)→ Phase 3(委托给 `spawn_post_invocation_handler`)。

下一步

理解 `ChatClientAgent` 的结构后,请阅读 **[AgentBuilder 构建器](./agent-builder.md)** 了解流畅构建器模式的完整使用指南。