消息模型

RAF 的消息模型是框架数据流的核心——从用户输入到 LLM 流式输出,再到结构化的最终响应,所有数据都通过消息模型承载。

消息体系总览

flowchart LR
    subgraph "输入层"
        CM[ChatMessage]
        MR[MessageRole: System | User | Assistant | Tool]
        MS[MessageSource: External | ChatHistory | ContextProvider | ToolResult]
    end

    subgraph "传输层"
        ARU[AgentResponseUpdate<br/>13 变体]
    end

    subgraph "输出层"
        Content[Content<br/>12 变体]
        Event[Event<br/>3 变体]
        ARR[AgentResponseResult]
        AR[AgentResponse]
    end

    CM --> ARU
    ARU --> Content
    ARU --> Event
    Content --> ARR
    Event --> ARR
    ARR --> AR

ChatMessage

扩展的聊天消息结构,兼容 OpenAI 的 `tool_calls` 和 `tool_call_id`。

pub struct ChatMessage {
    pub role: MessageRole,              // 消息角色
    pub content: String,                // 消息内容
    pub name: Option<String>,           // 可选名称标签
    pub tool_calls: Option<Vec<ToolCall>>,    // 助手消息的工具调用列表
    pub tool_call_id: Option<String>,         // 工具消息的关联 ID
    pub source: Option<MessageSource>,        // 消息来源标记
}

MessageRole

pub enum MessageRole {
    System,     // 系统指令,设定 Agent 行为
    User,       // 用户输入
    Assistant,  // Agent 回复
    Tool,       // 工具执行结果
}

便捷构造方法

// 系统指令
let sys = ChatMessage::system("你是一个代码助手,可以使用文件工具。");

// 用户消息
let user = ChatMessage::user("请读取 src/main.rs 的内容");

// 助手回复(纯文本)
let reply = ChatMessage::assistant("好的,我来读取文件。");

// 助手回复(含工具调用)
let tool_calls = vec![ToolCall {
    id: "call_001".into(),
    name: "read_file".into(),
    arguments: json!({"path": "src/main.rs"}),
}];
let with_call = ChatMessage::assistant_with_tools("正在读取文件...", tool_calls);

// 工具执行结果
let result = ChatMessage::tool("文件内容: fn main() {} ...", "call_001");

MessageSource

用于追踪消息起源,防止重复持久化。

pub enum MessageSource {
    External,         // 外部用户输入
    ChatHistory,      // 从聊天历史加载
    ContextProvider,  // 由 ContextProvider 注入
    ToolResult,       // 工具执行结果
}

`InMemoryHistoryProvider` 使用此标记过滤消息,避免在保存时重复存储历史消息。

Content 枚举:12 个变体

`Content` 是公开 API 的内容载体,通过 `AgentResponseConverter` 从 `AgentResponseUpdate` 转换而来。

pub enum Content {
    Text(TextContent),                          // 文本增量
    Reasoning(ReasoningContent),                // 推理文本增量 (DeepSeek thinking)
    Uri(UriContent),                            // URI 引用
    // ── 工具调用生命周期 ──
    ToolCallStart(ToolCallStartContent),        // ① 开始
    ToolCallArgs(ToolCallArgsContent),          // ② 参数流式到达
    ToolCallArgsParsed(ToolCallArgsParsedContent),  // ②b 参数已解析
    ToolCallArgsProgress(ToolCallArgsProgressContent), // ②c 参数接收中
    ToolCallEnd(ToolCallEndContent),            // ③ 参数完毕
    ToolCalling(ToolCallingContent),            // ④ 完整调用(汇总)
    ToolCalled(ToolCalledContent),              // ⑤ 执行结果
    Usage(UsageContent),                        // 用量统计
    Error(ErrorContent),                        // 错误信息
}

工具调用生命周期

工具调用从 LLM 到执行完成经历完整的 5 阶段生命周期:

stateDiagram-v2
    [*] --> ToolCallStart: LLM 开始生成工具调用
    ToolCallStart --> ToolCallArgs: 流式输出参数
    ToolCallArgs --> ToolCallArgs: 多个 delta 片段
    ToolCallArgs --> ToolCallEnd: 参数流完毕
    ToolCallEnd --> ToolCalling: 汇总解析完成
    ToolCalling --> ToolCalled: 执行工具完成
    ToolCalled --> [*]

    state ToolCallArgs {
        ToolCallArgsContent: 原始 delta
        ToolCallArgsParsedContent: 实时解析的 K-V 对
        ToolCallArgsProgressContent: 字符串值进度
    }

各阶段详解:

#### ① `ToolCallStart` — 开始

pub struct ToolCallStartContent {
    pub meta: ResponseMetadata,
    pub call_id: String,   // 工具调用唯一 ID
    pub name: String,       // 工具名称
}

LLM 刚开始生成新的工具调用时发出,不携带参数。UI 可显示"正在调用 XX 工具"。

#### ② `ToolCallArgs` — 参数流式到达

pub struct ToolCallArgsContent {
    pub meta: ResponseMetadata,
    pub call_id: String,
    pub args_delta: String,  // JSON 参数片段
}

SSE 流中每个 JSON delta 对应一次事件。消费方需自行拼接。

#### ②b `ToolCallArgsParsed` — 参数实时解析

pub struct ToolCallArgsParsedContent {
    pub meta: ResponseMetadata,
    pub call_id: String,
    pub name: String,        // 参数名
    pub value: serde_json::Value,  // 参数值
}

`StreamingArgsParser` 在 JSON 参数流中检测到完整的键值对时立即发出,无需等待整个 JSON 对象。UI 可据此展示已完成的参数。

#### ②c `ToolCallArgsProgress` — 参数接收进度

pub struct ToolCallArgsProgressContent {
    pub meta: ResponseMetadata,
    pub call_id: String,
    pub name: String,
    pub received: usize,      // 已接收字节数
    pub value: serde_json::Value,  // 当前值片段
}

当 LLM 正在生成长文本参数(如代码内容)时,此事件持续发出,携带接收进度和内容预览。

#### ③ `ToolCallEnd` — 参数完毕

pub struct ToolCallEndContent {
    pub meta: ResponseMetadata,
    pub call_id: String,
}

参数流式传输完成,但尚未被解析为结构化 JSON。消费方可标记"参数接收完成,准备执行"。

#### ④ `ToolCalling` — 完整调用

pub struct ToolCallingContent {
    pub meta: ResponseMetadata,
    pub call_id: String,
    pub name: String,
    pub arguments: serde_json::Value,  // 已解析的完整参数
}

流结束后由 `AgentResponseConverter::flush_tool_calls()` 生成,是所有流式阶段的汇总,**可直接传给工具执行**。

#### ⑤ `ToolCalled` — 执行结果

pub struct ToolCalledContent {
    pub meta: ResponseMetadata,
    pub call_id: String,
    pub result: Option<String>,  // 成功结果
    pub error: Option<String>,   // 错误信息
}

工具执行完成后发出。`result` 和 `error` 互斥。

AgentResponseUpdate:13 个变体

内部格式,对应 SSE 事件粒度。`Converter` 将其转换为 `Content` 和 `Event`。

pub enum AgentResponseUpdate {
    TextDelta { delta: String },
    ReasoningDelta { delta: String },
    ToolCallDelta { index: usize, id: Option<String>, name: Option<String>, arguments_delta: String },
    ToolCallStart { id: String, name: String },
    ToolCallArgs { id: String, args_delta: String },
    ToolCallEnd { id: String },
    ToolCalled { id: String, result: Option<String>, error: Option<String> },
    ToolApprovalRequest { call_id: String, name: String, arguments: Value, description: String },
    Usage { usage: Usage },
    Finish { finish_reason: FinishReason, usage: Option<Usage> },
    Error { message: String },
    ResponseMetadata { id: Option<String>, model: Option<String> },
}

**注意**:`ToolCallDelta` 是旧版格式(flat tool call delta),当 SSE 格式不区分 start/args/end 时使用。`Converter` 会将其分解为 `ToolCallStart`、`ToolCallArgs`、`ToolCallEnd` 序列。

AgentResponseResult

公开 API 的流式块类型,每个 yield 产生一个结果。

pub struct AgentResponseResult {
    pub id: Option<String>,
    pub model: Option<String>,
    pub finish_reason: Option<FinishReason>,
    pub contents: Vec<Content>,
    pub events: Vec<Event>,
}

AgentResponse

非流式的聚合响应,通过 `collect_agent_response()` 收集。

pub struct AgentResponse {
    pub id: Option<String>,
    pub model: Option<String>,
    pub text: String,                        // 完整文本回复
    pub reasoning_text: Option<String>,      // 推理文本(thinking 模式)
    pub tool_calls: Vec<ToolCall>,           // 工具调用列表
    pub tool_messages: Vec<ChatMessage>,     // 工具结果消息(用于持久化)
    pub turn_transcript: Vec<ChatMessage>,   // 本轮完整对话记录
    pub finish_reason: Option<FinishReason>,
    pub usage: Option<Usage>,
    pub source_agent_id: Option<AgentId>,
}

流式管道全景

┌────────────────────────────┐
│  LLM API (SSE Stream)      │
│  data: {"choices":[{"delta"│  HTTP SSE
│  :{"content":"Hello"}}]}   │
└──────────┬─────────────────┘
           │ SseStream 解析
           ▼
┌────────────────────────────┐
│  AgentResponseUpdate       │  13 个变体,内部格式
│  TextDelta{delta:"Hello"}  │
└──────────┬─────────────────┘
           │ AgentResponseConverter
           ▼
┌────────────────────────────┐
│  Content + Event           │  12 + 3 个变体
│  Text(TextContent{...})    │  公开 API
│  ToolCallStart{...}        │
│  ToolCalling{...}          │
│  ...                       │
└──────────┬─────────────────┘
           │ StreamExt::fold / inspect
           ▼
┌────────────────────────────┐
│  用户代码                   │
│  while let Some(chunk) ...  │
└────────────────────────────┘

并行工具调用支持

`AgentResponseConverter` 使用 `HashMap<String, ToolCallAccumulator>` 按 `call_id` 管理状态,天然支持并行工具调用。每个 `call_id` 独立跟踪:

- `name`:工具名称 - `args`:累积的参数字符串 - `start_emitted`:防止重复发出 `ToolCallStart`

sequenceDiagram
    participant LLM
    participant Conv as Converter
    participant User

    LLM->>Conv: ToolCallStart{id:"A", name:"read"}
    Conv->>User: Content::ToolCallStart{id:"A"}
    LLM->>Conv: ToolCallStart{id:"B", name:"write"}
    Conv->>User: Content::ToolCallStart{id:"B"}
    LLM->>Conv: ToolCallArgs{id:"A", delta:'{"path"'}}
    Conv->>User: Content::ToolCallArgs{id:"A"}
    LLM->>Conv: ToolCallArgs{id:"B", delta:'{"path"'}}
    Conv->>User: Content::ToolCallArgs{id:"B"}
    Note over LLM,User: 两个工具调用交替进行

下一步

掌握消息模型后,请阅读 **[错误处理](./error-handling.md)**,了解框架的错误类型和传播机制。