博客

Chat Completions 還是 Responses:為你的相容端點選擇它真正支援的 API 形態

遷移指南把差異歸結為三處變化:端點、如何讀取輸出,以及如何攜帶狀態。當你的端點聲稱相容時,這些差異各自意味著什麼。

Chat Completions 還是 Responses:為你的相容端點選擇它真正支援的 API 形態

同一批模型有兩種 API 形態在流通,而對呼叫相容端點的團隊來說,有意思的問題不是紙面上哪個更勝一籌。而是你的端點實際實現了每種形態的哪些部分。官方的遷移指南把差異壓縮成三處相關變化:把請求發到不同的端點、從不同的結構讀取輸出、選擇應用如何在輪次之間攜帶狀態。其餘的一切都從這三處推出,而廠商落地頁上的“compatible”並不能告訴你其中任何一處是否被忠實實現。

指南自己框定的三處變化

指南的總結值得引用,因為它界定了整個遷移的範圍:

把遷移視為三處相關變化:把請求發到/v1/responses,從型別化的output陣列讀取輸出,並選擇你的應用如何在不同輪次之間攜帶狀態。

第一處變化是機械性的。第二處是大多數程式碼崩掉的地方,因為這兩種響應物件並不是同一主題的變體:在 Chat Completions 裡你會收到一個choices陣列,每個包含一個message,而 Responses API 返回一個型別化的response物件,帶自己的id和一個由 Item 組成的output陣列。有一個便捷輔助函式output_text,在只需要文本時用它抽取文本。

逐元素來看這些差異

遷移指南直接列出了差異:Chat Completions 對 Responses,下面不加評判地總結,構建時應以當時的官方頁面為準:

| 關注點 | Chat Completions | Responses |
| --- | --- | --- |
| 端點 | POST /v1/chat/completions | POST /v1/responses |
| 輸出 | choices,每個包含一個message | 型別化的 response物件,帶有outputItems,外加一個output_text輔助函式 |
| 多次生成 | n把並行生成作為choices返回 | 沒有n;每個請求只生成一份,需要候選就發多個請求 |
| 系統指引 | 訊息列表裡的一個 system訊息 | 頂層的instructions,與input分開 |
| 輸入 | 訊息列表 | 一個字串或一個訊息列表 |
| 輪次之間的狀態 | 你儲存對話記錄並重發 messages | previous_response_id、之前的輸出 Items,或使用 Conversations API |
| 結構化輸出 | response_format | text.format |
| 工具呼叫 | function 配置和返回的呼叫是同一種形態 | 請求配置與回傳的呼叫都不同 |

有兩行值得展開。關於狀態,指南指出,store: true在輪次之間維護狀態,同時保留 reasoning 和工具上下文,並且兩個 API 的儲存預設值不同,store: false可以在任一邊把它關掉。關於多次生成,指南說得很明確:Chat Completions 可以返回多份並行生成,作為choices,使用n引數,而 Responses API 省略了這個引數,只返回一份生成。如果你的產品讓使用者在一次呼叫中從候選草稿裡挑選,那這是對形態選擇的約束,而不是細節。

一處限制,精確引用

有一個 tool calling 的限制在轉述中常被扭曲,所以這裡按遷移指南的原話給出:

從 GPT-5.4 起,Chat Completions 不支援使用reasoning_effort之外的取值none進行 tool calling。

按字面讀這句話:它是一個關於 Chat Completions 從某一代模型開始的陳述,關於 tool calling 與非nonereasoning effort 組合的陳述。它不是“到處都不支援 tool calling”的斷言,不是關於每個模型的斷言,也根本不是關於相容端點的斷言。如果你的整合同時使用工具和 reasoning effort,這句話決定了你在 OpenAI 這一側能用哪種形態,值得在圍繞它設計之前檢視當前頁面。

相容端點實際實現了什麼

相容端點通常先實現 Chat Completions,而覆蓋 Responses 形態的程度各不相同,這就是為什麼唯一可靠的答案來自測試,而不是相容性對照表。方法並不光鮮:構造一個真實的 payload,覆蓋你依賴的功能,包括一個 tool 定義和一個結構化輸出欄位,然後用兩種形態各發一次。然後檢查三件事。

第一,請求是否被接受。端點路徑上的 404、欄位名上的校驗錯誤,或引數被靜默丟棄,對你的規劃而言含義相同,儘管只有其中一部分會明確報出來。

第二,響應結構是否與官方一致。欄位名、陣列巢狀,以及流式場景下的事件名。一個把文本放在與文件不同的鍵下的形態,就是會讓你在最糟糕的時刻花一週除錯的形態。

第三,錯誤是否可讀。在每種形態裡故意發一個無效欄位,看錯誤是指出了這個欄位,還是塌縮成一個籠統的失敗。這就是五分鐘修好和花一下午二分定位的區別。

本站對相容整合普遍採用的那套清單紀律,在整合清單裡。並且要從真正會呼叫端點的那個環境跑測試:在公司代理或 TLS 檢查層之後,路徑重寫和響應頭處理會改變你實際在跟誰說話,正如企業網路指南針對一類相關問題所描述的那樣。

按約束選,而不是按偏好選

誠實的選法是列出你的產品真正需要什麼,再看哪種形態能提供:

  • 需要一次呼叫拿到多個候選?那就是n引數,按指南它存在於 Chat Completions,而不在 Responses。留在 Chat Completions,或者改動產品互動。
  • 需要配合previous_response_id的託管狀態?那就是 Responses。但要把指南的提醒一起帶上:previous_response_id不會帶上上一個響應的頂層instructions,所以穩定的 instructions 必須在每次請求時重發。
  • 需要text.format結構化輸出如文件所述?那就是 Responses;Chat Completions 的欄位是response_format。兩個欄位名不能互換,結構化輸出指南覆蓋了不同相容廠商之間形態的差異。
  • 需要在端點之間最大可移植性?使用兩種形態都支援的子集,並自己管理狀態,而不是依賴任一 API 的記憶。

有一條能拯救程式碼庫的規則:不要在同一個客戶端裡混用兩種形態的欄位名。response_format發給 Responses,或text.format發給 Chat Completions,不是聰明的相容技巧;而是一個在你碰到的每個端點上表現都不同的 bug。

遷移,三步

每一步都有一個驗收點,而驗收點正是防止半遷移客戶端上線的關鍵。

第一步:端點。把路徑改成/v1/responses並拿到響應物件。驗收標準:一個純文本請求返回型別化響應,帶有id,並且output_text包含預期文本。

第二步:如何讀取輸出。把choices[0].message訪問替換為逐項讀取output陣列,或在純文本場景下用output_text。驗收標準:你的抽取函式對純文本產出與之前相同的最終文本,並在涉及工具或 reasoning 時正確識別非文本項。工具呼叫是最鋒利的一角:請求側的 function 配置和響應側返回的呼叫在兩個 API 之間形態都不同,function calling 指南端到端走了一遍;執行工具迴圈時生產側的紀律在tool calling 指南裡。

第三步:狀態。明確決定:自己重發之前的 items、使用previous_response_id並重發 instructions,還是用持久的 conversation 物件。驗收標準:多輪對話產生的後續行為與你的 Chat Completions 基線一致,包括程序重啟之後。如果你做流式響應,同時要對齊事件名;事件序列的差異在streaming 指南裡。

常見失敗

這些在匆忙遷移的整合裡反覆出現:

  1. 把response_format發給 Responses 形態的端點,然後斷定該端點不支援結構化輸出。
  2. 仍然在choices的響應物件裡讀取outputitems。
  3. 假設n仍然有效,結果產品期望多份生成時卻靜默只拿到一份。
  4. 把previous_response_id當成“API 記住了一切”,包括它並不會帶過來的 instructions。
  5. 假設兩種形態在其他方面完全相同,然後在生產裡發現工具呼叫的差異。
  6. 把“文件說支援這個”讀成“我的端點支援這個”。文件描述的是官方 API;你的端點是另一份實現。

一條工程提醒

API 形態不是網路細節。它會滲進你的型別定義、日誌欄位名、測試夾具和值班手冊。在寫客戶端之前就定下形態,記錄你依賴它的哪些部分,當第二個端點進入畫面時,重跑一次 payload 測試,而不是假設第一個答案依然成立。

清單

  1. 用真實 payload 確認你的端點接受哪些形態,而不是靠相容性宣告。
  2. 每種形態固定一套型別和欄位名,不要混用。
  3. 對齊結構化輸出欄位:response_format或text.format,絕不在同一個客戶端裡兩者都用。
  4. 對齊工具定義與工具呼叫的形態,請求側和響應側都要。
  5. 選定狀態策略,並寫下它不會帶過來什麼。
  6. 如果你做流式,對齊 streaming 事件名。
  7. 把每個真實 payload 用兩種形態各發一次,並把結果留作整合測試。
  8. 從生產環境裡真正會呼叫端點的那個環境跑測試。

這些都不會讓任一形態成為普遍正確的答案;它讓你的選擇成為關於你的端點的答案。本站所記錄平臺在端點層面的具體細節在其文件裡,並且這裡引用的每個欄位名、引數和事件名,都應在構建時以當時的官方頁面核對。