Files
agcore/design/pdd/9f-edge-cases.md
T
徐涛 28ca43ccb2 chore(docs): 将设计文档从 docs 移至 design 目录
将 note、pdd、prd、roadmap 四类文档分别归入 `design/` 下对应子目录中,并新增 `.gitkeep` 占位文件
2026-07-23 05:45:53 +08:00

4.7 KiB
Raw Blame History

边界情况

本文档从 9-llm-provider-unified-interface.md 拆分而来,包含 §9 边界情况。

相关文件:

9. 边界情况

9.1 工具定义的传递路径变化

阶段 路径
当前 ToolRegistry.definitions()Vec<ToolDefinition>build_request() 包装为 Vec<OpenaiTool>ChatRequest.tools
方案 C ToolRegistry.definitions()Vec<ToolDefinition>build_request() 直接放入 → MessageRequest.toolsProvider 内部包装为原生格式

工具定义的抽象层次从 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...") }

理由:

  1. 不新增独立事件类型(保持 StreamEvent 简洁)
  2. signature 在 message_delta 中下发(晚于 content_block_stop),MessageComplete 是该时刻的天然载体
  3. Anthropic 同一响应中最多一个 thinking block,不存在"多 thinking block 归属"的歧义
  4. 非流式响应中,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 的处理

消息的 contentVec<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。