28ca43ccb2
将 note、pdd、prd、roadmap 四类文档分别归入 `design/` 下对应子目录中,并新增 `.gitkeep` 占位文件
4.7 KiB
4.7 KiB
边界情况
本文档从
9-llm-provider-unified-interface.md拆分而来,包含 §9 边界情况。相关文件:
- 9b-ir-type-system.md — IR 类型定义(ContentBlock、ThinkingConfig、MessageRequest/Response 等)
- 9c-llm-provider-trait.md — StreamEvent 类型定义(ThinkingDelta 等)
- 9d-provider-implementations.md — Anthropic 流式状态机(与 Thinking signature 强相关)
- 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
✅ 推演结论(2026-06-17):方案 C —— MessageComplete 兜底 + finalize 统一回填
决策: Thinking signature 不在 event 级传递,而是通过
MessageComplete.thinking_signature携带, 由PartialMessageResponse::finalize()统一回填到最后一个 Thinking block。具体路径:
Anthropic message_delta → AnthropicProvider 提取 thinking.signature → 发出 MessageComplete { stop_reason, thinking_signature: Some("0x...") } PartialMessageResponse: ContentBlockEnd(thinking_idx) ← signature 还没到,builder 中 signature = None ... MessageComplete { thinking_signature: Some("0x...") } → state.thinking_signature = Some("0x...") finalize(): 遍历 blocks → 找到 Thinking builder → builder.signature 为 None,用 state.thinking_signature 回填 → ContentBlock::Thinking { text, signature: Some("0x...") }理由:
- 不新增独立事件类型(保持 StreamEvent 简洁)
- signature 在 message_delta 中下发(晚于 content_block_stop),MessageComplete 是该时刻的天然载体
- Anthropic 同一响应中最多一个 thinking block,不存在"多 thinking block 归属"的歧义
- 非流式响应中,thinking block 的 signature 直接通过 IR→原生映射填充,路径不变
影响范围:
StreamEvent::MessageComplete增加thinking_signature: Option<String>字段PartialMessageResponse增加thinking_signature: Option<String>暂存字段PartialMessageResponse::finalize()增加回填逻辑(约 3 行代码)- AnthropicProvider 流式状态机在
message_delta处理中提取 thinking.signature何时实现: Phase 4 AnthropicProvider 流式状态机实现时一并完成
9.3 Multiple ContentBlock 的处理
消息的 content 为 Vec<ContentBlock>,可包含多种类型的混合:
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 传递:
// 请求
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 支持:
// 配置内置工具
request.set_extra("built_in_tools", json!(["web_search", "file_search"]));
// Response API 返回的搜索结果
// → ContentBlock::Extension { kind: "web_search_result", data: {...} }
如果未来内置工具成为跨 Provider 通用特性,将 BuiltInToolsCapable 升级为核心 trait。