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

141 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LlmProvider Trait 设计
> 本文档从 `9-llm-provider-unified-interface.md` 拆分而来,包含 §4 LlmProvider Trait 设计。
>
> **相关文件:**
> - [9b-ir-type-system.md](9b-ir-type-system.md) — IR 类型定义(MessageRequest、MessageResponse、StreamEvent 等)
> - [9d-provider-implementations.md](9d-provider-implementations.md) — 各 Provider 的具体实现策略
> - [9f-edge-cases.md](9f-edge-cases.md) — Thinking signature 等边界情况(与 StreamEvent 设计关联)
## 4. LlmProvider Trait 设计
### 4.1 核心接口
```rust
#[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
```rust
#[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
```rust
/// 流式事件 —— 流式响应的语义化增量构建过程。
/// 一组 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 }` | 明确参数完整时间点 |