Files
agcore/docs/9c-llm-provider-trait.md
T

5.7 KiB
Raw Blame History

LlmProvider Trait 设计

本文档从 9-llm-provider-unified-interface.md 拆分而来,包含 §4 LlmProvider Trait 设计。

相关文件:

4. LlmProvider Trait 设计

4.1 核心接口

#[async_trait]
pub trait LlmProvider: Send + Sync {
    /// 发送消息请求,获取完整响应。
    async fn chat(&self, request: MessageRequest) -> Result<MessageResponse, LlmError>;

    /// 流式消息请求,返回语义化事件流。
    async fn chat_stream(
        &self,
        request: MessageRequest,
    ) -> Result<
        Pin<Box<dyn Stream<Item = Result<StreamEvent, LlmError>> + Send>>,
        LlmError,
    >;

    /// 返回 Provider 的能力描述。
    fn capabilities(&self) -> ProviderCapabilities;
}

4.2 ProviderCapabilities

#[derive(Debug, Clone)]
pub struct ProviderCapabilities {
    /// Provider 标识名称
    pub provider_name: &'static str,
    /// 支持的模型列表(None = 不限制)
    pub supported_models: Option<Vec<String>>,
    /// 特性标记
    pub features: ProviderFeatures,
}

#[derive(Debug, Clone, Default)]
pub struct ProviderFeatures {
    pub streaming: bool,
    pub thinking: bool,
    pub vision: bool,
    pub audio_input: bool,
    pub tool_use: bool,
    pub parallel_tool_calls: bool,
    /// true=OpenAI风格(system in messages), false=Anthropic风格(system as param)
    pub system_prompt_in_messages: bool,
    pub max_context_window: u32,
}

capabilities() 的使用场景

  1. LlmCycle:根据 features.thinking 决定是否启用 thinking 模式
  2. AgentBuilder:在构建时校验模型是否支持所需特性
  3. UI/CLI:展示 Provider 的能力矩阵
  4. 智能路由:根据能力自动选择最佳 Provider

4.3 流式事件 StreamEvent

/// 流式事件 —— 流式响应的语义化增量构建过程。
/// 一组 StreamEvent 最终可汇聚为一个完整的 MessageResponse。
#[derive(Debug, Clone)]
pub enum StreamEvent {
    // ── Meta ──
    /// 消息开始
    MessageStart { id: String, model: String },

    // ── Content 增量 ──
    /// 文本增量
    TextDelta { text: String },
    /// 思考增量(Anthropic thinking block
    ThinkingDelta { text: String },
    /// 拒绝增量(OpenAI refusal
    RefusalDelta { text: String },

    // ── Tool Call ──
    /// 工具调用开始
    ToolCallStart { index: u32, id: String, name: String },
    /// 工具参数增量
    ToolCallArgumentsDelta { index: u32, arguments: String },
    /// 工具调用结束
    ToolCallEnd { index: u32 },

    // ── 汇总 ──
    /// Token 用量
    CostUpdate { usage: Usage },
    /// 消息完成
    MessageComplete { stop_reason: StopReason },

    // ── 错误 ──
    Error { message: String },
}

🔄 待深入推演:StreamEvent 汇聚为 MessageResponse 的算法 文档提到"一组 StreamEvent 最终可汇聚为一个完整的 MessageResponse",但没有给出具体实现。 这是上层做"既流式又完整"双模处理的关键基础设施。 需要推演:

  1. PartialMessageResponse 结构体设计(累积的状态:id, model, text_buffer, tool_calls 字典, usage, stop_reason 等)
  2. StreamEvent::apply_to(&self, state: &mut PartialMessageResponse) -> bool 算法——每个事件类型如何更新状态
  3. PartialMessageResponse::finalize(self) -> MessageResponse 的完成逻辑
  4. 边界情况:MessageStart 到达前就收到 TextDelta 怎么办?ToolCallArgumentsDelta 到达顺序乱序? 多次 CostUpdate 是叠加还是覆盖? 优先级:高(Phase 3 流式功能依赖此设计)

🔄 待深入推演:ToolCallStart 的 index 来源与归一化 ToolCallStart.index 在不同 Provider 流式协议中的含义不同:

  • OpenAI SSE chunk 中 delta.tool_calls[i].index该次 delta 的局部索引(只含变动的 tool call), 需要调用方累积状态来跟踪每个 tool call 的完整索引
  • Anthropic 的 content_block_start 中的 index全局的 content block 索引(直接映射到最终 content 数组位置) 需要推演:
  1. IR 的 index 使用哪种语义?建议用"全局 tool call 序号"OpenAI Provider 内部从局部 index 映射到全局序号)
  2. 当 OpenAI 并行返回多个 tool_calls(同一 chunk 含多个 delta.tool_calls 条目),如何分配 index? 优先级:中(Phase 2 实现 OpenAI 流式转换时需解决)

与当前 StreamEvent 的对比

当前 StreamEvent 新 StreamEvent 理由
AssistantTextDelta { text } TextDelta { text } 简洁化
ThinkingDelta { text } Anthropic 需要
RefusalDelta { text } OpenAI 需要
ToolExecutionStarted { tool_name, input, tool_call_id } ToolCallStart { index, id, name } + ToolCallArgumentsDelta { index, arguments } 支持增量参数(Anthropic 的 tool_use 流式需要)
ToolExecutionCompleted 移除 这是 LlmCycle 层的事件,非 Provider 层
CostUpdate { usage } 保留
TurnComplete { reason } MessageComplete { stop_reason } 语义更准确
Error { message } 保留
MessageStart { id, model } Anthropic 需要
ToolCallEnd { index } 明确参数完整时间点