你如果在寫 AI Agent 或聊天機器人,一定寫過這種程式碼:在本地端宣告一個 messages 陣列,每次使用者說話,就把舊的歷史紀錄、系統提示詞、再加上最新的問題,通通塞進去送給 API。
這種 Stateless(無狀態) 的設計,在只有單次生成時很方便,但當對話變長,或者你需要頻繁呼叫工具時,事情就會變得非常麻煩。
你需要自己維護對話歷史資料庫、自己設計截斷(Truncation)演算法防止 Context Window 爆掉,還要承受因為歷史訊息些微變動導致 Prompt Cache 失效、Token 費用暴增的代價。
OpenAI 推出的 Responses API (/v1/responses),就是為了解決這件事。這不是單純的 API 升級,而是把對話狀態收攏到伺服器端的 Stateful(有狀態) 設計。
為什麼無狀態會是個負擔?
在舊的 Chat Completions API (/v1/chat/completions) 裡,每一次 API 請求都是獨立的。伺服器處理完就忘記,所以你得被迫把所有的對話歷史重新發送。
這會帶來幾個顯而易見的缺點。
首先是 Token 數呈二次方增長。第三輪對話你要傳送第一、二輪的所有內容,第五輪要傳送前面四輪的所有內容。網路傳輸與 Token 費用會隨著對話輪數急遽上升。
再來是快取形同虛設。OpenAI 的 Prompt Caching(提示詞快取) 是根據前綴文字來比對的。只要你為了帶入最新時間戳記、或者在中間插入了暫時性資料,整串快取就會失效,你得付全額的輸入 Token 費用。
最後是工具呼叫的來回繁瑣。模型說要呼叫工具,API 回傳 tool_calls;你得在本地跑完程式碼,再把結果包裝成 tool 角色的訊息送回去。這中間的狀態控制邏輯全得由你手動維護。
有狀態的 Responses API 是怎麼運作的?
Responses API 引進了伺服器端儲存(透過 store: true 參數)。
當你發送請求時,OpenAI 會在雲端保存這段對話,並回傳一個唯一的 response_id(例如 resp_123)。
下一輪對話時,你不需要再送整串 messages,只要送最新的一條訊息,並用 previous_response_id 指向上一輪的 ID 就好。
我們直接用 Python 程式碼來看兩者的差別。
舊寫法:Chat Completions API
你必須自己宣告並維護歷史陣列:
import os
from openai import OpenAI
client = OpenAI(api_key=os.environ.get("OPENAI_KEY"))
# 用戶端必須自行維護歷史紀錄
conversation_history = [
{"role": "system", "content": "你是一個幽默的助手。"}
]
# 第一輪對話
user_msg_1 = "你好,我是 Albert。我養了一隻狗叫 Pocky。"
conversation_history.append({"role": "user", "content": user_msg_1})
response_1 = client.chat.completions.create(
model="gpt-4o",
messages=conversation_history
)
assistant_msg_1 = response_1.choices[0].message.content
print(f"Assistant: {assistant_msg_1}")
# 手動將模型回覆加入歷史
conversation_history.append({"role": "assistant", "content": assistant_msg_1})
# 第二輪對話:必須發送完整的歷史
user_msg_2 = "我剛才說我養的寵物叫什麼名字?"
conversation_history.append({"role": "user", "content": user_msg_2})
response_2 = client.chat.completions.create(
model="gpt-4o",
messages=conversation_history
)
assistant_msg_2 = response_2.choices[0].message.content
print(f"Assistant: {assistant_msg_2}")
新寫法:Responses API
你不必宣告歷史陣列,系統提示直接寫在頂層的 instructions,對話透過 previous_response_id 串接:
import os
from openai import OpenAI
client = OpenAI(api_key=os.environ.get("OPENAI_KEY"))
# 第一輪對話:開啟 store 儲存狀態
response_1 = client.responses.create(
model="gpt-5.5",
instructions="你是一個幽默的助手。",
input="你好,我是 Albert。我養了一隻狗叫 Pocky。",
store=True
)
# 透過 output 陣列取得文字
assistant_msg_1 = response_1.output[0].content[0].text
print(f"Assistant: {assistant_msg_1}")
# 記錄此時的 Response ID
last_response_id = response_1.id
# 第二輪對話:僅需發送最新問題與前一輪的 ID
response_2 = client.responses.create(
model="gpt-5.5",
input="我剛才說我養的寵物叫什麼名字?",
previous_response_id=last_response_id
)
assistant_msg_2 = response_2.output[0].content[0].text
print(f"Assistant: {assistant_msg_2}")
你的用戶端不再需要保留那一長串隨時間膨脹的陣列,OpenAI 在雲端會自動幫你還原上下文。
輸出結構的翻轉:多型 output 陣列
如果你仔細看剛才的 Responses API 程式碼,會發現解析回覆的地方變了。
舊的 API 依靠 choices 陣列返回結果,而 Responses API 則使用頂層的 output 陣列。
這個 output 陣列是多型(Polymorphic)的。它不只會吐出最終的對話文字,還會把思維鏈推理數據、工具呼叫等,拆分成不同的「項目」存在同一個陣列裡。
例如,一次包含推理的輸出結構會像這樣:
{
"id": "resp_001",
"object": "response",
"status": "completed",
"output": [
{
"id": "item_cot_001",
"type": "reasoning",
"summary": "分析用戶意圖並比對台積電最新財務報表"
},
{
"id": "item_msg_002",
"type": "message",
"status": "completed",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "台積電最新一季營收達..."
}
]
}
]
}
這個設計解決了以前「思考過程與回答混在一起」的髒亂感。你可以輕鬆在前端分離「模型思考過程」與「最終對話呈現」,不需要再用正則表達式或字串分割去濾掉思維鏈。
推理模型與原生工具的底層優化
如果只是幫你存歷史紀錄,這套 API 還不夠看。Responses API 最強大的地方,在於它對推理模型(如 o 系列)與原生工具的底層優化。
內建工具的雲端閉環
以前要讓模型上網搜尋或執行程式碼,你需要在本地自己開發搜尋或沙箱環境,並處理複雜的工具呼叫循環。
在 Responses API 中,OpenAI 原生支援了三種內建工具:
web_search:讓模型自己上網搜尋最新資料。code_interpreter:提供安全的雲端 Python 執行沙箱,處理數學運算或資料分析。file_search:內建 RAG 向量檢索。
你只需要在參數中宣告,OpenAI 伺服器就會在背景自動完成「模型思考 $\rightarrow$ 呼叫工具 $\rightarrow$ 取得結果 $\rightarrow$ 產出回覆」的完整閉環,你的用戶端甚至不需要寫任何 Callback。
# 啟用內建工具
response = client.responses.create(
model="gpt-5.5",
input="請幫我查詢 2026 年最新公佈的諾貝爾物理學獎得主是誰?並用 Python 計算出他的年齡。",
tools=[
{"type": "web_search"},
{"type": "code_interpreter"}
]
)
避免推理 Token 的浪費
像 o1 或 o3-mini 這樣的推理模型,在回答前需要消耗大量的推理 Token(Reasoning Tokens)。
在無狀態的 API 裡,如果你進行多輪對話,模型為了在後續對話維持相同的推理邏輯,往往需要重新推理,這會重複扣除昂貴的推理 Token。
而在 Responses API 裡,因為對話狀態保留在雲端,下一輪對話會直接利用已快取的推理狀態,省下重複推理的成本。
遷移至 Responses API 的抉擇
我們把這兩代 API 的關鍵差異整理如下:
| 比較維度 | Chat Completions API | Responses API |
|---|---|---|
| 設計哲學 | Stateless(無狀態)、純對話補全 | Stateful(有狀態)、代理人與推理導向 |
| 狀態管理 | 用戶端必須完全維護歷史訊息 | 伺服器端儲存,透過 previous_response_id 串接 |
| 頂層輸入 | messages 陣列 | input (最新訊息) + instructions (系統提示) |
| 頂層輸出 | choices[i].message.content | output[i] (多型項目:message、reasoning) |
| 快取效能 | 只要歷史紀錄一變,快取立刻失效 | 雲端狀態不變,快取利用率高達 80% |
| 原生工具 | 無。僅支援自定義工具的 Schema 聲明 | 原生內建 web_search、code_interpreter 等 |
| 推理模型 | 多輪對話容易重複扣推理 Token | 快取推理狀態,避免重複扣 Token |
這並不代表你必須立刻把所有的專案都重寫。
如果你只是在做簡單的單次任務(例如文章摘要、文字分類),或者你的系統需要非常精細地手動控制每一次 Context 視窗的大小,繼續使用 Chat Completions API 仍然是合適的選擇。
但如果你正在開發一個具備多輪複雜對話、頻繁使用工具的 AI Agent,或者大量使用 o 系列推理模型,轉用 Responses API 能幫上你很多忙。
小結
從 Chat Completions 到 Responses API 的演進,揭示了 LLM 應用開發的範式轉移——我們不再只是向一個聰明的文字生成引擎丟出 Prompt,而是將狀態與工具託管給雲端的 AI Agent,由它來幫我們維護整個對話的來龍去脈。