博客

Codex CLI 自定義供應商:model_providers、wire_api 與僅限 Responses 的規則

把 Codex 指向另一個供應商只需幾行 config.toml,另加一條決定該供應商能否工作的硬約束:wire_api 只接受 responses。

Codex CLI 自定義供應商:model_providers、wire_api 與僅限 Responses 的規則

把 Codex CLI 指向另一個供應商,只需對 ~/.codex/config.toml 做一點小改動:一行選定供應商,一個表定義它,三個欄位通常就能補全。但有一個硬約束決定該供應商到底能不能用,而它從配置檔案的形態上並不明顯:wire 協議設定只接受一個值,responses,因此你指向 Codex 的端點必須說 Responses API 的形態。只實現了 Chat Completions 的供應商不是配置錯了,而是形態不相容——那是另一個問題,有另一種修法。

以下內容都遵循官方Codex 手冊,它是這裡所引示例、欄位名和預設值的來源。在上線配置之前,請對照當前頁面核實。

最小配置,按官方釋出的樣子

手冊的示例一次定義了三個供應商。歸結為最基本的模式,配置是這樣:

model = "gpt-6.1-sol"
model_provider = "proxy"

[model_providers.proxy]
name = "OpenAI using LLM proxy"
base_url = "http://proxy.example.com"
env_key = "OPENAI_API_KEY"

最上面兩行選定模型和供應商 ID。隨後那個表定義該 ID:顯示名稱、Codex 呼叫的 base URL,以及提供金鑰的環境變數。同一個官方示例中的兩處變化,對理解這個模型很重要:

  • 本地 Ollama 條目使用 base_url = "http://localhost:11434/v1",而完全沒有 env_key,因為沒有金鑰需要提供。
  • Mistral 條目使用 base_url = "https://api.mistral.ai/v1",配合 env_key = "MISTRAL_API_KEY"。

注意這些 base URL:Ollama 示例帶一個 /v1 字尾,代理示例則沒有。這不是需要取平均值的矛盾;它反映的是每個端點如何掛載自己的 API。故障排查一節會詳細講。

欄位表,逐欄位說明

手冊逐個記錄了自定義供應商的欄位。核心幾行重述如下,預設值標註為官方預設值:

| Field | Type | Meaning |
| --- | --- | --- |
| name | string | 自定義供應商的顯示名稱 |
| base_url | string | 模型供應商的 API base URL |
| env_key | string | 提供供應商 API key 的環境變數 |
| wire_api | responses | 供應商使用的協議;responses 是唯一受支援的值,省略時也是預設值 |
| query_params | map | 追加到供應商請求的額外查詢引數 |
| http_headers | map | 新增到供應商請求的靜態 HTTP 頭 |
| env_http_headers | map | 存在時從環境變數填充的 HTTP 頭 |
| request_max_retries | number | 對供應商發起 HTTP 請求的重試次數(官方預設值:4) |
| stream_max_retries | number | SSE 流式中斷的重試次數(官方預設值:5) |
| stream_idle_timeout_ms | number | SSE 流的空閒超時,單位為毫秒(官方預設值:300000) |
| supports_standalone_web_search | boolean | 宣告支援相容的獨立 web search 端點(官方預設值:false) |

在寫第一行配置之前,有兩行值得強調。wire_api 將在下一節講述,因為它決定相容性。而 stream_idle_timeout_ms 的單位是毫秒,所以預設值是三十萬毫秒;照手冊的寫法寫,並且把換算做一次,而不是貼上一個看起來像秒的數字。

硬約束:wire_api 只接受 responses

這就是全文存在所要傳達的那句話,引自手冊的欄位表:

responses 是唯一受支援的值,省略時也是預設值。

沒有 chat 這樣的值可以切換。如果你的端點只實現 Chat Completions 形態,正確的結論不是“調整配置”,而是“這個端點不說 Codex 要求的形態”。修法在端點一側:跑一個暴露 Responses 形態的轉換層,或者選一個原生實現它的供應商。兩種形態之間的差異,以及如何測試某個端點真正實現的是哪一種,見API 形態指南;更完整的相容性清單見整合清單。

這也是為什麼那麼多“相容”端點與其他工具配合良好,卻在 Codex 上失敗:與 Chat Completions 相容,和與 Responses 形態相容是兩個不同的主張,而配置檔案無法彌合這個差距。

保留 ID:不要把供應商命名為 openai

自定義供應商 ID 不能複用內建的。手冊列出三個保留 ID:openai、ollama 和 lmstudio。如果你需要為內建 OpenAI 供應商指定不同的 base URL,手冊指向一個專門的設定 openai_base_url,而不是用覆蓋表,因為內建供應商 ID 不能被覆蓋。至於其他情況,選一個不同的 ID,例如 proxy,或者像官方示例對本地 Ollama 例項那樣用 local_ollama。

這是一條小規則,卻有一個大失敗模式:配置里名為 openai 的供應商不會做出名字暗示的事,由此產生的困惑通常表現為一次莫名其妙的“端點錯了”的除錯過程。

把金鑰和請求頭配置好,而不把秘密寫進檔案

配置檔案描述金鑰,但不包含它。env_key 指明提供 API key 的那個環境變數,Codex 在請求時讀取它。這帶來一個值得圍繞它設計的實際後果:輪換金鑰意味著改環境變數並重啟,而不是編輯一個可能已經被提交到某處的配置檔案。Codex 相關的憑據儲存與登入模式見憑據指南,而把金鑰擋在會被複制、記錄或截圖的檔案之外的更廣泛紀律,見金鑰衛生指南。

請求頭也沿同一條界線劃分。http_headers 存放可以放心寫下來的靜態頭值。env_http_headers 存放值來自環境變數的頭名稱,用於那些實質上是偽裝憑據的請求頭。如果某個頭攜帶 token,它屬於第二個 map,而不是第一個。

按順序排查

當自定義供應商“連不上”時,有用的做法是把這句話變成可判定的檢查項。按它們實際是真正原因的頻率排序:

  1. 端點說的是 Responses 形態嗎?這是壓倒性最常見的成因,而且它在配置裡看不出來。在歸咎 Codex 之前,先用一個 Responses 形態的請求直接測試該端點。
  2. base URL 路徑對嗎?針對各自的目標遵循官方示例,並確認你的端點實際使用的掛載路徑。Ollama 示例帶有 /v1;代理示例沒有。不要不加核對就把某個示例的字尾搬到另一個示例上。
  3. 環境變數在啟動 Codex 的那個 shell 裡匯出了嗎?在另一個終端裡設定的變數,或者寫在未重啟的服務定義裡的變數,對這個程序來說並不存在。
  4. 供應商 ID 與保留名稱衝突了嗎? openai、ollama 和 lmstudio 都已被佔用。
  5. 超時和重試適合你的網路嗎?官方預設值是 4 次請求重試、5 次流重試,以及 300000 毫秒的流空閒超時。如果你的網路慢或不穩定,要有意識地調整,而不是一味堆疊重試。
  6. 路上有企業 TLS 代理嗎?證書信任問題會產生看起來像配置錯誤的連線失敗;證書與代理配置見企業網路指南。

表裡最後一個欄位很容易被過度解讀。手冊說該設定用於宣告支援相容的獨立 web search 端點,預設值為 false,並指出獨立搜尋仍在開發中、預設關閉;僅有供應商相容並不能啟用它。把該能力設為 true 也不能啟用搜索:供應商必須支援相容端點,所選模型和執行時也必須支援獨立搜尋,同時配置好的 web search 模式和管理限制仍然生效。

實踐中:不要把該標誌當作讓 web search 生效的開關。把它當作關於你端點的一項誠實宣告,除非你已驗證端點一側,否則保持 false。

清單

  1. 首先確認端點說的是 Responses 形態。
  2. 選一個不屬於保留名稱的供應商 ID。
  3. 為供應商寫 name、base_url 和 env_key。
  4. 在啟動 Codex 的 shell 裡匯出金鑰變數。
  5. 把靜態頭放進 http_headers,把攜帶秘密的頭放進 env_http_headers。
  6. 對照你這類端點對應的官方示例,確認 base URL 路徑。
  7. 通過 Codex 發一個真實請求,如果有錯誤,按形態、路徑、憑據的順序去解讀它。
  8. 記住官方重試預設值(4、5 和 300000 毫秒),這樣偏離它們才是有意為之;並通過改環境變數來輪換金鑰。

自定義供應商就是一張附帶嚴格形態要求的小表。形態對了,其餘都是記賬;形態錯了,再怎麼調重試也沒用。本網站所記錄 API 的平臺側細節在其 文件 中,而這裡引用的每個欄位名和預設值都應在構建時對照當時的官方手冊重新核對。