同一批模型有兩種 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 指南裡。
常見失敗
這些在匆忙遷移的整合裡反覆出現:
- 把
response_format發給 Responses 形態的端點,然後斷定該端點不支援結構化輸出。 - 仍然在
choices的響應物件裡讀取outputitems。 - 假設
n仍然有效,結果產品期望多份生成時卻靜默只拿到一份。 - 把
previous_response_id當成“API 記住了一切”,包括它並不會帶過來的 instructions。 - 假設兩種形態在其他方面完全相同,然後在生產裡發現工具呼叫的差異。
- 把“文件說支援這個”讀成“我的端點支援這個”。文件描述的是官方 API;你的端點是另一份實現。
一條工程提醒
API 形態不是網路細節。它會滲進你的型別定義、日誌欄位名、測試夾具和值班手冊。在寫客戶端之前就定下形態,記錄你依賴它的哪些部分,當第二個端點進入畫面時,重跑一次 payload 測試,而不是假設第一個答案依然成立。
清單
- 用真實 payload 確認你的端點接受哪些形態,而不是靠相容性宣告。
- 每種形態固定一套型別和欄位名,不要混用。
- 對齊結構化輸出欄位:
response_format或text.format,絕不在同一個客戶端裡兩者都用。 - 對齊工具定義與工具呼叫的形態,請求側和響應側都要。
- 選定狀態策略,並寫下它不會帶過來什麼。
- 如果你做流式,對齊 streaming 事件名。
- 把每個真實 payload 用兩種形態各發一次,並把結果留作整合測試。
- 從生產環境裡真正會呼叫端點的那個環境跑測試。
這些都不會讓任一形態成為普遍正確的答案;它讓你的選擇成為關於你的端點的答案。本站所記錄平臺在端點層面的具體細節在其文件裡,並且這裡引用的每個欄位名、引數和事件名,都應在構建時以當時的官方頁面核對。