跳转至

第 7 章:OpenAI 兼容 API 与远程 Provider —— 统一入口的另一面

7.1 为什么 Jan 自己还要做一层 proxy

Jan 既要让 Web UI 调云端模型,也要向其他客户端暴露本地 API。Rust core/server 提供一个 Hyper HTTP server,把本地 llama.cpp、macOS MLX 和远程 Provider 统一到 OpenAI 风格的入口。

flowchart LR
  Client[OpenAI SDK / curl / Jan Web UI]
  Proxy[Hyper proxy\ntrusted hosts + api key]
  Local[llama.cpp router]
  MLX[MLX session]
  Remote[Anthropic / Google / OpenAI / custom]
  Conv[converters.rs\nwire format translation]
  Client --> Proxy
  Proxy --> Local
  Proxy --> MLX
  Proxy --> Conv --> Remote

7.2 server command 的生命周期

start_server 接收 host、port、prefix、api_key、trusted_hosts、timeout 和是否允许 server-side tool execution,拿到 AppState 中的 provider configs、model defaults、MCP server map 后调用 proxy::start_server

服务器 handle 存在 AppState.server_handle 中;stop_serverget_server_status 通过同一个共享 handle 管理。这样的生命周期集中在 Rust,避免 WebView 刷新后丢失 server 状态。

7.3 ProviderConfig 是远程模型的运行时注册表

ProviderConfig 保存 provider、API key chain、base URL、custom headers、models 和 api_type。Web 的 DataProvider 启动时从 keyring 重新 seed keys,再调用 register_provider_config 写入 Rust 的 AppState.provider_configs

关键设计是 key chain:当上游返回 401/403/429 时可以尝试后备 key,而不是把一个临时配额错误暴露成“模型不可用”。密钥值本身走 OS keyring;配置和模型列表可以持久化,secret 不应该跟着 localStorage 导出。

7.4 converters:把“OpenAI 形状”翻译成上游协议

converters.rs 定义 UpstreamConverterconverter_forSseAccumulator 等。默认 Provider 可以 verbatim passthrough;anthropicgoogleopenai-responses 等 api_type 选择对应转换器。

Jan request: OpenAI ChatCompletionRequest
  ├─ messages / tool calls / images
  ├─ sampling params
  └─ stream=true
        ↓ converter
upstream native request
        ↓ SSE accumulator
OpenAI-shaped streamed response

流式翻译尤其难:Anthropic 的 content blocks、Google 的 candidates、OpenAI Responses 的 event taxonomy 都不一样;转换器必须在“上游事件尚未结束”时维护累积状态,再输出下游能理解的 delta。

7.5 安全检查不应被兼容性掩盖

proxy 在转发前使用 is_valid_host、CORS/origin 检查、prefix 移除和 API key 验证。trusted_hosts 控制允许的来源。开放本地端口不是天然安全:同机其他进程、浏览器网页和局域网设备都可能尝试访问,因此 host、origin、token、CORS 和 tool execution 必须一起看。

7.6 server-side tool execution 是一个危险开关

server 配置中的 enable_server_tool_execution 决定 proxy 是否可以在 Rust 侧把 MCP 工具加入远程请求链。默认关闭是合理的:如果一个外部客户端仅仅调用 /v1/chat/completions,不应该无意间获得本机文件、网络或 MCP 子进程能力。

开启后,工具权限从“Jan UI 的审批弹窗”扩展到“任何拿到 API key 的客户端”,这应当被视为更高风险模式。

7.7 参数清洗与错误翻译

前端 model-factory 会先过滤 provider 不支持的参数;Rust proxy/converter 还要承担第二道边界,因为外部客户端可以发送任意 JSON。错误翻译要做到:

  • 保留 HTTP status 与上游可操作原因。
  • 不回显 API key、完整 Authorization header 或内部文件路径。
  • 对 llama.cpp Jinja trace 做清洗,把最终 Error: ... 作为用户可读原因。
  • 把 DNS/TLS/timeout 等 transport error 转成行动建议。

这条链路是“统一 API”带来的代价:对调用方隐藏差异,就必须由 Jan 负责解释差异。