5.1 IContextProvider 概述
`IContextProvider` 是 RAF 最强大的扩展机制。它让开发者在 Agent 的每次 `run()` 调用前后插入自定义逻辑——注入指令、历史消息、动态工具,甚至压缩上下文。
IContextProvider trait 定义
/// 上下文提供器 trait — Agent 调用生命周期的核心扩展点
///
/// 对标 MAF 的 `AIContextProvider` 抽象类。
/// Provider 可按注册顺序执行,靠后的 Provider 可设置 `replace_messages = true`
/// 来替换前面 Provider 累积的消息。这天然支持压缩策略(截断/摘要等)。
#[async_trait]
pub trait IContextProvider: Send + Sync {
fn name(&self) -> &str;
/// 提供器分类——与 ContextProviderDecl 的 kind 标签对应。
/// "memory" | "skills" | "mcp" | "workspace" | "knowledge" | "wiki"
fn kind(&self) -> &str { "unknown" }
async fn on_invoking(
&self,
agent: &dyn IAgent,
session: &dyn ISession,
messages: &[ChatMessage],
options: &AgentRunOptions,
) -> Result<ContextResult>;
async fn on_invoked(
&self,
agent: &dyn IAgent,
session: &dyn ISession,
request_messages: &[ChatMessage],
response: Option<&AgentResponse>,
error: Option<&AgentError>,
) -> Result<()>;
}
方法详解
| 方法 | 调用时机 | 传入参数 | 用途 | |------|---------|----------|------| | `on_invoking()` | LLM 调用**之前** | Agent、Session、当前消息列表、运行选项 | 返回 `ContextResult` 注入上下文 | | `on_invoked()` | LLM 调用**之后**(无论成功或失败) | Agent、Session、请求消息、响应或错误 | 持久化状态、日志记录、清理 |
**`on_invoked()` 的 `error` 参数**:当 LLM 调用抛出 `AgentError` 时,`response` 为 `None`,`error` 为 `Some(err)`。这使得提供器可以记录失败、回滚状态。
ContextResult 载体
/// 上下文注入载体 — Provider 在 Pre-invocation 阶段返回的上下文增强内容
#[derive(Default)]
pub struct ContextResult {
/// 追加到 system prompt 的指令文本
pub instructions: Option<String>,
/// 注入到消息列表的消息
pub messages: Vec<ChatMessage>,
/// 本次调用可用的动态工具
pub tools: Vec<Arc<dyn ITool>>,
/// 若为 true,则替换已累积的消息;默认 false(追加)
pub replace_messages: bool,
}
| 字段 | 类型 | 含义 | 典型使用方 | |------|------|------|-----------| | `instructions` | `Option<String>` | 追加到 system prompt 的指令,多个提供器的指令用换行拼接 | WorkspaceContextProvider、AgentSkillsProvider | | `messages` | `Vec<ChatMessage>` | 注入的消息列表,追加到 LLM 消息历史之前 | InMemoryHistoryProvider | | `tools` | `Vec<Arc<dyn ITool>>` | 动态注册的工具,与 AgentBuilder 注册的工具合并 | AgentSkillsProvider(load_skill / read_skill_resource) | | `replace_messages` | `bool` | 设为 `true` 时,当前提供器的 messages 替换之前所有提供器累积的消息 | 压缩策略提供器 |
提供器链:顺序执行与合并
sequenceDiagram
participant Engine as Agent 引擎
participant P1 as Provider 1<br/>(History)
participant P2 as Provider 2<br/>(Skills)
participant P3 as Provider 3<br/>(Workspace)
participant LLM
Engine->>P1: on_invoking(agent, session, messages, options)
P1-->>Engine: ContextResult { instructions: None, messages: history, tools: [] }
Engine->>P2: on_invoking(agent, session, messages, options)
P2-->>Engine: ContextResult { instructions: "## Skills\n...", messages: [], tools: [load_skill, ...] }
Engine->>P3: on_invoking(agent, session, messages, options)
P3-->>Engine: ContextResult { instructions: "## Workspace\n...", messages: [], tools: [ReadFile, WriteFile, ...] }
Note over Engine: 合并 ContextResult<br/>instructions = P1 + P2 + P3<br/>messages = P1.messages (replace_messages 均为 false)<br/>tools = P1 + P2 + P3
Note over Engine: 构建最终上下文
Engine->>LLM: system_prompt + messages + tools 定义
LLM-->>Engine: AgentResponse
Engine->>P3: on_invoked(agent, session, req_msgs, Some(response), None)
Engine->>P2: on_invoked(agent, session, req_msgs, Some(response), None)
Engine->>P1: on_invoked(agent, session, req_msgs, Some(response), None)
Note over P1: 持久化 user 消息到 session
replace_messages:消息替换语义
正常模式下,所有提供器的 `messages` 字段被**追加**到累积列表中。但当某个提供器设置 `replace_messages = true` 时:
1. 框架丢弃之前所有提供器累积的 messages 2. 使用当前提供器的 messages 作为新的消息列表 3. 后续提供器在此基础上继续追加
**典型场景:上下文压缩**
struct CompressionProvider {
strategy: Box<dyn ICompressionStrategy>,
}
impl IContextProvider for CompressionProvider {
fn name(&self) -> &str { "CompressionProvider" }
fn kind(&self) -> &str { "compression" }
async fn on_invoking(&self, agent: &dyn IAgent, session: &dyn ISession, ...) -> Result<ContextResult> {
// 加载完整历史
let history = session.get_messages().await?;
// 压缩(保留最近 N 条 + 摘要)
let compressed = self.strategy.compress(&history);
Ok(ContextResult {
messages: compressed,
replace_messages: true, // ← 替换前面的历史消息
..Default::default()
})
}
async fn on_invoked(&self, ...) -> Result<()> { Ok(()) }
}
**提供器注册顺序决定了压缩优先级**:将 `CompressionProvider` 放在提供器链靠后位置,它就能压缩前面所有提供器累积的消息。
完整的 Agent run() 生命周期中提供器的位置
flowchart TD
START["agent.run(messages, options)"] --> TOUCH["session.touch_request_hash(messages)"]
TOUCH --> PRE_PROV["按顺序调用所有 Provider.on_invoking()"]
PRE_PROV --> MERGE["合并 ContextResult"]
MERGE --> BUILD["构建最终上下文:<br/>- system prompt + instructions<br/>- injected messages + request messages<br/>- registered tools + injected tools"]
BUILD --> CHAT["chat_client.complete(messages, options)"]
CHAT -->|成功| RESP["AgentResponse"]
CHAT -->|失败| ERR["AgentError"]
RESP --> POST_PROV["按顺序调用所有 Provider.on_invoked()"]
ERR --> POST_PROV
POST_PROV --> END["返回 AgentResponse 或 Err"]
关键要点
1. **提供器按注册顺序执行**——前面的提供器先注入,后面的提供器可以覆盖(通过 `replace_messages`) 2. **`ContextResult.tools` 是动态工具**——与 `AgentBuilder::with_tool()` 注册的工具合并 3. **`instructions` 用换行拼接**——多个提供器的指令在 system prompt 中连续显示 4. **`on_invoked` 总被调用**——无论 LLM 调用成功或失败,`error` 参数区分两种情况 5. **提供器链天然支持压缩**——靠后的提供器设置 `replace_messages = true` 即可替换前面的消息