第 3 章:HTTP API——一套内部模型,多个外部协议¶
一、路由表就是产品边界¶
server.GenerateRoutes 是读 Ollama 的第二个入口。它先创建 Gin router、配置 CORS 和 allowed-host middleware,再按责任注册路由。核心实现见 server/routes.go。
原生 API¶
| 类别 | 端点 | 作用 |
|---|---|---|
| 服务 | GET /、GET /api/version、GET /api/status |
心跳、版本、云状态 |
| 模型 | POST /api/pull、POST /api/push、GET /api/tags、POST /api/show |
模型缓存和元数据 |
| 管理 | DELETE /api/delete、POST /api/create、POST /api/copy |
模型引用和 Modelfile |
| 推理 | POST /api/generate、POST /api/chat |
completion/chat |
| 向量 | POST /api/embed、POST /api/embeddings |
embedding |
| 运行态 | GET /api/ps |
当前加载的模型和 runner |
| Blob | HEAD/POST /api/blobs/:digest |
分块传输和去重 |
OpenAI 兼容 API¶
/v1/chat/completions
/v1/completions
/v1/embeddings
/v1/models
/v1/models/:model
/v1/responses
/v1/audio/transcriptions
Anthropic 兼容 API¶
/v1/messages
关键观察:兼容层没有复制一套 inference handler,而是通过 middleware 把外部请求转换成 Ollama 内部结构,再调用 ChatHandler 或 GenerateHandler。
二、为什么同一个 handler 可以服务多种协议¶
内部 API 类型位于 api/types.go:ChatRequest、Message、Tool、Options、ChatResponse。外部协议的字段差异在 middleware 与 openai 包吸收。
flowchart LR
O[Ollama Client] --> N[Native JSON]
P[OpenAI Client] --> OM[openai middleware]
A[Anthropic Client] --> AM[anthropic middleware]
N --> CR[api.ChatRequest]
OM --> CR
AM --> CR
CR --> CH[server.ChatHandler]
CH --> RES[api.ChatResponse]
RES --> NOUT[NDJSON]
RES --> SSE[OpenAI SSE / Anthropic SSE]
这种设计的核心是先归一化,再执行:模型加载、prompt、工具 parser 和 scheduler 不需要分别理解 OpenAI/Anthropic 的请求字段。
三、OpenAI 的 writer 为什么重要¶
middleware/openai.go 没有只做 JSON 字段映射,还实现了写响应的协议状态机:
- 非 streaming:把
api.ChatResponse转成 OpenAI completion JSON。 - streaming:把每个响应 chunk 转成
data: {...}\n\n。 - 结束时发送可选 usage chunk 和
data: [DONE]。 - 错误时把内部
api.StatusError包成 OpenAI error shape。
这说明兼容层的难点不是“字段改名”,而是传输语义:NDJSON 是一行一个完整对象,SSE 是事件帧;tool call、usage、done 的边界也不同。实现入口见 middleware/openai.go。
四、路由 middleware 的顺序¶
以 /v1/chat/completions 为例,注册顺序大致是:
Gin 全局:CORS → allowed host
↓
inference request logging
↓
cloud passthrough
↓
OpenAI ChatMiddleware
↓
ChatHandler
↓
writer 将内部流翻译为 OpenAI SSE/JSON
顺序有明确含义:
- 先做安全和跨域边界。
- 再决定
:cloud模型是否需要被代理。 - 再将协议转换为内部
api类型。 - 最后进入与原生 API 相同的业务流程。
五、模型引用是 API 与云的分叉点¶
ChatHandler 先调用 parseAndValidateModelRef,再按 modelSourceCloud、local 或 remote model 分支。对 cloud 模型,handler 会在本地做最少的模型字段归一化,然后将请求转给云端;对 local model,才会继续 GetModel → scheduleRunner。
这是一种“本地优先、显式远端”的安全边界:普通本地模型不会因为兼容层存在就自动变成远端请求;只有模型引用或模型配置明确指向 cloud/remote 才走代理。
六、错误模型¶
API 层的错误通常经历:
输入 JSON / 模型名错误
→ handler 直接返回 400/404
runner 加载/推理错误
→ stream 中返回带 error 字段的 JSON line
兼容层
→ middleware writer 转成 OpenAI/Anthropic error event
api.Client.stream 会对每一行先检查 error 和 HTTP 状态,再交给具体 callback;这让 CLI 不需要为每个 endpoint 重复写错误解析。
七、设计取舍¶
- 内部结构稳定,外部协议可变:长期维护时只扩展适配器。
- middleware 拦截而非重写 handler:避免 native/OpenAI/Anthropic 三条逻辑漂移。
- 流式 writer 持有状态:tool call 是否已发送、usage 是否应追加,都不能只靠一行 JSON 决定。
- allowed host 与 CORS 同时存在:CORS 解决浏览器跨域,allowed host 解决服务端接受哪些 Host,两者不是替代关系。