第 11 章:兼容层——OpenAI、Anthropic 与 Cloud Passthrough¶
一、兼容的目标不是模拟所有细节¶
Ollama 选择一个内部规范:api.ChatRequest / api.Message / api.Tool / api.ChatResponse。OpenAI 和 Anthropic 兼容层只承诺把常见调用语义映射到这个规范,再把结果翻译回对方的传输格式。
外部协议
→ 请求 adapter
→ Ollama internal API
→ common inference path
→ 响应 writer/adapter
→ 外部协议
这样做的边界也很清楚:如果外部协议有 Ollama 内部没有的语义,就必须在 middleware、openai 或 Anthropic 包中明确降级,而不是偷偷丢字段。
二、OpenAI Chat 的关键映射¶
OpenAI middleware 要处理:
- messages 的 role/content 结构。
- tools 与 tool calls。
stream、stream options、usage。- response format/JSON schema。
- model list/retrieve。
- embeddings 的 encoding format。
- audio transcription 的兼容入口。
响应侧的 ChatWriter 保存 toolCallSent 状态,因为 tool call 的 SSE chunk 与普通文本 chunk 的边界不同;结束时再根据 IncludeUsage 追加 usage event。
三、Anthropic Messages 的特殊性¶
Anthropic /v1/messages 与 OpenAI 最大的差别不是 URL,而是消息 block、thinking、tool use/tool result 和 streaming event 命名。middleware/anthropic.go 负责把这些 block 转成内部 api.Message,再把 Ollama response 重新包装成 Anthropic event stream。
这也是为什么 Anthropic 兼容不能简单复用 OpenAI middleware:两个协议都“像 chat”,但其消息和 stream 状态机不同。
四、Cloud passthrough 的两种情况¶
显式 cloud model¶
当模型引用带 cloud source 时,ChatHandler 会把模型名归一化,再通过 cloud proxy 转发。请求不应尝试在本地 GetModel 后加载不存在的权重。
本地 manifest 指向 remote model¶
某些 local model metadata 可能包含 RemoteHost 与 RemoteModel。handler 会检查 remote hostname 是否在 envconfig.Remotes() allowlist 中,云被禁用时拒绝请求。
model ref / manifest
→ local? → scheduler
→ explicit cloud? → signed cloud proxy
→ remote config? → allowlist + cloud status + proxy
五、签名与安全边界¶
server/cloud_proxy.go 会构造 challenge,对请求进行签名,并复制允许的请求/响应 headers;同时过滤 hop-by-hop headers,维护 JSONL framing。它还会处理 cloud unauthorized,使客户端拿到 sign-in URL。
这里的原则是:
- 本地 server 不把任意 URL 当作 remote target。
- cloud 请求需要明确身份与签名。
- 代理不能盲目转发连接级 headers。
- 流式响应要维持原协议的 framing。
六、为什么兼容层仍共享 ChatHandler¶
如果 OpenAI/Anthropic 各自实现 prompt、scheduler、工具 parser,会导致:
- 同一个模型三种协议行为不同。
- OOM、keep-alive、thinking 支持不一致。
- bug 修复要复制三次。
共享 handler 把差异收敛到“协议进出口”;代价是 internal API 必须足够表达多协议能力,middleware 也会变复杂。
七、边界案例¶
兼容层需要特别关注:
| 场景 | 风险 |
|---|---|
| streaming + tool call | chunk 顺序和终止事件错误 |
| usage | 非 streaming 与 streaming 位置不同 |
| thinking | 不能把隐藏思考误当普通 content |
| cloud model | 本地找不到模型不应返回普通 local 404 |
| remote host | SSRF/任意转发风险 |
| 压缩请求体 | 解压后大小限制,避免资源耗尽 |
八、设计取舍¶
兼容层是一种“语义翻译器”,而不是第二套产品。它尽量复用内部生命周期,明确在 protocol adapter 里承担差异,并将 remote/cloud 作为显式的安全分支处理。