跳转至

第 3 章:HTTP API——一套内部模型,多个外部协议

一、路由表就是产品边界

server.GenerateRoutes 是读 Ollama 的第二个入口。它先创建 Gin router、配置 CORS 和 allowed-host middleware,再按责任注册路由。核心实现见 server/routes.go

原生 API

类别 端点 作用
服务 GET /GET /api/versionGET /api/status 心跳、版本、云状态
模型 POST /api/pullPOST /api/pushGET /api/tagsPOST /api/show 模型缓存和元数据
管理 DELETE /api/deletePOST /api/createPOST /api/copy 模型引用和 Modelfile
推理 POST /api/generatePOST /api/chat completion/chat
向量 POST /api/embedPOST /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 内部结构,再调用 ChatHandlerGenerateHandler

二、为什么同一个 handler 可以服务多种协议

内部 API 类型位于 api/types.goChatRequestMessageToolOptionsChatResponse。外部协议的字段差异在 middlewareopenai 包吸收。

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

顺序有明确含义:

  1. 先做安全和跨域边界。
  2. 再决定 :cloud 模型是否需要被代理。
  3. 再将协议转换为内部 api 类型。
  4. 最后进入与原生 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,两者不是替代关系。