104 lines
4.4 KiB
Markdown
104 lines
4.4 KiB
Markdown
# 边界情况
|
||
|
||
> 本文档从 `9-llm-provider-unified-interface.md` 拆分而来,包含 §9 边界情况。
|
||
>
|
||
> **相关文件:**
|
||
> - [9b-ir-type-system.md](9b-ir-type-system.md) — IR 类型定义(ContentBlock、ThinkingConfig、MessageRequest/Response 等)
|
||
> - [9c-llm-provider-trait.md](9c-llm-provider-trait.md) — StreamEvent 类型定义(ThinkingDelta 等)
|
||
> - [9d-provider-implementations.md](9d-provider-implementations.md) — Anthropic 流式状态机(与 Thinking signature 强相关)
|
||
> - [9e-llm-cycle-and-upstream.md](9e-llm-cycle-and-upstream.md) — LlmCycle 的 tool 循环与 compact 逻辑
|
||
|
||
## 9. 边界情况
|
||
|
||
### 9.1 工具定义的传递路径变化
|
||
|
||
| 阶段 | 路径 |
|
||
|------|------|
|
||
| **当前** | `ToolRegistry.definitions()` → `Vec<ToolDefinition>` → `build_request()` 包装为 `Vec<OpenaiTool>` → `ChatRequest.tools` |
|
||
| **方案 C** | `ToolRegistry.definitions()` → `Vec<ToolDefinition>` → `build_request()` 直接放入 → `MessageRequest.tools` → **Provider 内部**包装为原生格式 |
|
||
|
||
工具定义的抽象层次从 LlmCycle 下移到 Provider 内部,更合理。
|
||
|
||
### 9.2 Thinking 的端到端流程
|
||
|
||
```
|
||
Agent 启用 thinking:
|
||
config.thinking = Some(ThinkingConfig { budget_tokens: 16000 })
|
||
|
||
LlmCycle.build_request():
|
||
→ MessageRequest { thinking: config.thinking, ... }
|
||
|
||
OpenaiProvider:
|
||
→ capabilities().features.thinking == false
|
||
→ 忽略 thinking 字段(或转为 extra.reasoning_tokens)
|
||
|
||
AnthropicProvider:
|
||
→ capabilities().features.thinking == true
|
||
→ 将 thinking 写入 Anthropic 请求参数
|
||
→ 流式响应中返回 StreamEvent::ThinkingDelta
|
||
→ 最终消息的 content 中包含 ContentBlock::Thinking
|
||
```
|
||
|
||
> **🔄 待深入推演:Thinking signature 的端到端传递**
|
||
> 当前设计中,Anthropic 的 thinking block signature 在 `ContentBlock::Thinking.signature` 中存储,
|
||
> 但从流式事件到最终 ContentBlock 的传递路径没有闭环:
|
||
> 1. `content_block_start`(针对 thinking block)下发的是 `{ type: "thinking" }` 没有 signature
|
||
> 2. `content_block_delta` 下发 `thinking` delta + **signature**(在 delta 中,不是独立的字段)
|
||
> 3. `StreamEvent::ThinkingDelta { text }` 当前只有 text,缺少 signature 字段
|
||
> 4. 最终需要将 signature 附着到 `ContentBlock::Thinking { text, signature: Some(...) }` 上
|
||
>
|
||
> **需要推演:**
|
||
> 1. `StreamEvent::ThinkingDelta` 是否需要增加 `signature: Option<String>` 字段?
|
||
> 2. 或者改为每个 block 结束时发一个独立的 `ContentBlockSealed { index, block: ContentBlock }` 事件?
|
||
> 3. 非流式响应中,Anthropic 的 thinking block 是完整的(含 text 和 signature 在同一 block 中),
|
||
> 映射没有问题。但流式响应中,signature 的附着时机需要在 AnthropicProvider 的状态机中准确定义。
|
||
> 4. `PartialMessageResponse`(§4.3 待推演)需要能按 index 追踪 thinking block 的 text 累积和 signature 暂存。
|
||
>
|
||
> 此问题与 [9d-provider-implementations.md](9d-provider-implementations.md) §5.2 的"Anthropic 流式状态机设计"直接相关,
|
||
> 建议在推演状态机时一并解决。
|
||
> 优先级:高(Phase 4 AnthropicProvider 实现的前提条件)
|
||
|
||
### 9.3 Multiple ContentBlock 的处理
|
||
|
||
消息的 `content` 为 `Vec<ContentBlock>`,可包含多种类型的混合:
|
||
|
||
```rust
|
||
Message::Assistant {
|
||
content: vec![
|
||
ContentBlock::Text { text: "让我思考一下..." },
|
||
ContentBlock::Thinking { text: "先用加法工具...", signature: None },
|
||
ContentBlock::Text { text: "答案是 3" },
|
||
ContentBlock::ToolUse { id: "call_1", name: "add", input: json!({"a":1,"b":2}) },
|
||
],
|
||
}
|
||
```
|
||
|
||
LlmCycle 处理 tool 循环时只关心 `ToolUse` block,其他 block 按原样传递给消息历史。
|
||
|
||
### 9.4 多 Choice 场景
|
||
|
||
OpenAI 的 `n > 1` 参数在 IR 中通过 `extra` 传递:
|
||
|
||
```rust
|
||
// 请求
|
||
request.set_extra("n", json!(3));
|
||
// 响应
|
||
response.extra["all_choices"] = json!([...choices 2..n]);
|
||
```
|
||
|
||
`MessageResponse` 只承载 `choices[0]`(主消息),其他 choices 放 `extra`。
|
||
|
||
### 9.5 OpenAI Response API 的内置工具
|
||
|
||
通过 `extra` + 可选能力 trait 支持:
|
||
|
||
```rust
|
||
// 配置内置工具
|
||
request.set_extra("built_in_tools", json!(["web_search", "file_search"]));
|
||
|
||
// Response API 返回的搜索结果
|
||
// → ContentBlock::Extension { kind: "web_search_result", data: {...} }
|
||
```
|
||
|
||
如果未来内置工具成为跨 Provider 通用特性,将 `BuiltInToolsCapable` 升级为核心 trait。
|