Appendix: Provider Adapter - DeepSeek / Anthropic tool_use vs OpenAI function_call
learn-workbuddy 教的是桌面 agent harness,不是某一家模型厂商的 API。理想状态下,agent loop 不应该关心模型来自 DeepSeek、Anthropic、OpenAI,还是离线 mock。模型协议可以不同,但 harness 的工具执行、权限、审计、转录和外部化逻辑应该保持一致。
这就是 mini_workbuddy/providers.py 的作用:把不同 provider 的工具调用协议归一成同一个内部形状。
两种协议的差异
| 关注点 | DeepSeek / Anthropic-compatible Messages API | OpenAI Responses API |
|---|---|---|
| 工具定义 | {name, description, input_schema} | {type:"function", name, description, parameters} |
| 模型请求工具 | tool_use content block,含 id、name、input | function_call output item,含 call_id、name、arguments |
| 参数编码 | 已经是 dict | JSON 字符串,需要 json.loads |
| 返回工具结果 | tool_result block,引用 tool_use_id | function_call_output item,引用 call_id |
| 结果放置位置 | user message 的 content list | 顶层 typed input item |
| 系统提示 | system= | instructions= |
| assistant turn | {role:"assistant", content: response.content} | response.output item list |
最容易踩坑的两点:OpenAI 的工具参数是 JSON 字符串,DeepSeek / Anthropic-compatible 是 dict;两边工具调用 ID 字段也不同。adapter 的意义就是把这些差异从主 loop 里拿掉。
归一后的内部契约
harness loop 只看三个中性类型:
ToolSpec(name, description, parameters)
ToolCall(id, name, arguments)
ModelTurn(text, tool_calls, raw_assistant)loop 形状因此保持稳定:
turn = provider.create(ProviderRequest(system, messages, tools))
messages.append(turn.raw_assistant)
if not turn.wants_tools:
break
results = [(call, run_tool(call)) for call in turn.tool_calls]
messages.append(provider.format_tool_results(results))主 loop 不读取 tool_use_id,不直接 json.loads provider 参数,也不需要知道哪家模型返回了答案。权限、审计、转录、大输出外部化都运行在归一后的 ToolCall 上。
为什么用 Responses API,而不是 Agents SDK
OpenAI 有两层抽象:
- Responses API:适合你自己拥有 agent loop,自己管理工具执行、消息历史和状态。
- Agents SDK:适合让 SDK 管 turns、tools、handoffs、sessions 和 tracing。
这个教程的目标是“从 0 复刻 harness”,所以主线应该保留自己的 loop。否则读者会学会调用框架,却看不到框架内部到底在解决什么问题。Agents SDK 可以作为未来附录:“什么时候应该直接用框架”,但不应该替代核心教学实现。
这个判断也和 OpenAI 官方文档一致:Responses API 支持让模型调用你的函数和内置工具;工具调用流程本质上是“模型请求工具 -> 应用执行工具 -> 把 function_call_output 带回下一轮”。Agents SDK 则在 Responses API 之上提供更高层 runtime,让 SDK 管 turns、tools、guardrails、handoffs、sessions 和 tracing。
Provider 选择
.env 配置:
PROVIDER=deepseek|anthropic|openai|openai-chat|offline
DEEPSEEK_API_KEY=...
DEEPSEEK_MODEL=deepseek-v4-pro
ANTHROPIC_API_KEY=...
MODEL_ID=...
OPENAI_API_KEY=...
OPENAI_MODEL=gpt-4.1
OPENAI_CHAT_BASE_URL=https://your-openai-compatible-gateway.example/v1
OPENAI_CHAT_API_KEY=...
OPENAI_CHAT_MODEL=gpt-5.5auto 逻辑是:优先 Anthropic key,其次 DeepSeek key,再其次 OpenAI Responses key,再其次 OpenAI-compatible Chat key,都没有则回退离线 mock。离线 mock 是一个脚本化工具调用 agent,用来让无 key 读者和 CI 也能跑完整 harness。
运行
# 离线,无 key,确定性
python examples/mini_workbuddy_demo/code.py --mode real --provider offline
# 真实 DeepSeek,推荐学习路径
python examples/mini_workbuddy_demo/code.py --mode real --provider deepseek
python s01_agent_loop/code.py --provider deepseek
# 章节脚本默认写入 ~/.learn_workbuddy/,避免和真实 WorkBuddy 的 ~/.workbuddy/ 撞库。
# 需要临时目录时可加:WORKBUDDY_HOME=/tmp/learn-workbuddy
# 真实 Anthropic
python examples/mini_workbuddy_demo/code.py --mode real --provider anthropic
# 真实 OpenAI
python examples/mini_workbuddy_demo/code.py --mode real --provider openai
# OpenAI-compatible 网关
python examples/mini_workbuddy_demo/code.py --mode real --provider openai-chat
python examples/full_tour/code.py --provider openai-chat
python s03_deferred_loading/code.py --eval --provider openai-chat
# 一键真实 API 冒烟,需 key
python scripts/run_real_smoke.py --provider openai --targets mini full
python scripts/run_real_smoke.py --provider openai-chat --targets mini full all-lessons
python scripts/run_real_smoke.py --provider deepseek --targets all-lessonsfull_tour 会做 provider probe:第一轮强制 provider 调用 tool_search,然后把工具结果通过同一个 adapter 回传给模型。这用于证明真实 provider 不只是初始化成功,而是经过了 model -> tool -> result 的 harness 回路。
对应代码
mini_workbuddy/providers.py:三个 provider 和select_provider。examples/mini_workbuddy_demo/code.py:同一个 real loop 跑多个 provider。tests/test_providers.py:provider 选择、schema 翻译、fake-client 解析、离线完整 loop。scripts/run_real_smoke.py:手动真实 API 冒烟,all-lessons使用每章统一--eval入口。
官方资料
- OpenAI Responses API overview: https://developers.openai.com/api/reference/responses/overview/
- OpenAI function calling guide: https://developers.openai.com/api/docs/guides/function-calling
- OpenAI Agents SDK: https://openai.github.io/openai-agents-python/
- DeepSeek API docs: https://api-docs.deepseek.com/
- DeepSeek Anthropic-compatible guide: https://api-docs.deepseek.com/guides/anthropic_api