Responses API 的回應同時包含文字、生成狀態和可供下一輪使用的上下文。output_text 是顯示文字的便捷屬性;工具呼叫是否仍待處理、回應是否完整,以及哪些項目需要加入對話紀錄,則由 status 與完整 output 判斷。
技術文件問答需要保存文件與對話歷史,才能在追問時提供相同的設定資料。空白回覆、輸出截斷和接續失敗則各有對應的狀態與診斷欄位。既有 Chat Completions 程式的改寫方式可參考端點與欄位遷移對照。
Response、output 與文字內容
Response 具有識別碼、狀態、用量與輸出陣列。output 可以同時包含 reasoning、message、function_call 等項目,訊息的 content 則包含文字或拒絕內容。讀取端依 item 與 content 的型別處理資料,文字回答的位置不固定在 output 的第一項。
| 資料 | 用途 | 程式應做的事 |
|---|---|---|
status、error、incomplete_details | 判斷這次生成的執行結果 | 先區分完成、未完成、失敗與仍在執行;保留失敗原因。 |
output[*].type | 決定下一個處理步驟 | 分流文字、工具與其他項目,不假定第一個 item 就是回答。 |
message.content[*] | 取得文字、拒絕與來源註記 | 檢查內容型別;有引用時保留 annotations 和可追查的來源。 |
output_text | 彙整可顯示的文字 | 作為 SDK 的便捷屬性使用,不把它當成完整的工作狀態。 |
usage | 觀察輸入、輸出與推理用量 | 與模型、功能、response ID 及延遲一起記錄。 |
completed 是本輪模型生成的完成狀態。Output 若仍包含自訂工具呼叫,應用程式會繼續執行工具、回填結果,並等待最終答案;UI 與工作紀錄可將這一階段標記為等待工具,結果保存也須依業務流程是否完成決定。
技術文件問答程式
程式的執行環境為 Python 3.11 以上版本與 OpenAI Python SDK。SDK 從環境變數 OPENAI_API_KEY 讀取金鑰,憑證保留在後端環境。預設模型是 gpt-6-astra,推理程度為 reasoning.effort=low,也能透過 OPENAI_RESPONSES_MODEL 指定相容模型。請求未設定 temperature、top_p,推理強度由 reasoning.effort 控制。
python -m pip install -U openai
python -c "import openai; print(openai.__version__)"
程式中的文件是固定資料,不會讀取部署主機。兩輪問題分別詢問執行環境與命令,以及服務的位址與連接埠;每輪使用相同的產品指令,完整 output 會加入對話歷史。SDK 自動重試已關閉,因此每次應用程式呼叫只對應一次嘗試。重試政策與斷線後的處理可參考串流、背景任務與失敗恢復。
import json
import os
from openai import OpenAI
client = OpenAI(timeout=45.0, max_retries=0)
MODEL = os.getenv("OPENAI_RESPONSES_MODEL", "gpt-6-astra")
INSTRUCTIONS = (
"使用繁體中文回答技術文件問題。只根據提供的文件陳述設定,"
"文件沒有的設定直接說明缺少資料,不補造版本或指令。"
)
COMMON = {
"model": MODEL,
"reasoning": {"effort": "low"},
"instructions": INSTRUCTIONS,
"max_output_tokens": 4096,
}
DOCUMENT = (
"文件ID:asgi-deploy;文件版次:r3;執行介面:ASGI。"
"啟動命令:daphne -b 127.0.0.1 -p 8000 project.asgi:application。"
"HTTP 與 WebSocket 請求由前方反向代理轉送到這個服務。"
)
def observation(response):
details = response.incomplete_details
return {
"response_id": response.id,
"request_id": getattr(response, "_request_id", None),
"status": response.status,
"incomplete_reason": getattr(details, "reason", None),
"output_types": [item.type for item in response.output],
"usage": response.usage.model_dump() if response.usage else None,
}
def read_answer(response):
if response.status != "completed":
raise RuntimeError(
f"生成未完成:{response.status};"
f"{response.error or response.incomplete_details}"
)
texts = []
refusals = []
tool_calls = []
for item in response.output:
if item.type == "function_call":
tool_calls.append(item.call_id)
elif item.type == "message":
for part in item.content:
if part.type == "refusal":
refusals.append(part.refusal)
elif part.type == "output_text":
if not isinstance(part.text, str):
raise TypeError("output_text.text 必須是字串")
texts.append(part.text)
if refusals:
raise RuntimeError("模型拒絕回答:" + ";".join(refusals))
if tool_calls:
raise RuntimeError(f"需要工具執行器處理:{tool_calls}")
answer = "".join(texts)
if not answer.strip():
raise ValueError("這輪沒有可顯示文字,請檢查完整 output")
return answer
history = [{
"role": "user",
"content": f"根據這份文件,列出執行介面與啟動命令:{DOCUMENT}",
}]
questions = [None, "剛剛的服務綁定哪個位址與連接埠?"]
for question in questions:
if question is not None:
history.append({"role": "user", "content": question})
response = client.responses.create(
**COMMON, input=history, store=False,
)
print(json.dumps(observation(response), ensure_ascii=False))
print(read_answer(response))
history.extend(response.output)
read_answer() 負責解析可顯示的回答,對話歷史則保留完整 response.output。第二輪 input 包含原始文件、第一輪的全部輸出項目與新問題。上下文遺失時,這三部分就是兩輪請求之間的比對依據。
文件問答程式沒有定義工具,收到 function_call 時會停止並回報缺少執行器。實際的工具流程需要處理呼叫、執行結果與後續模型回應,空白文字本身無法表示這些工作已完成。
空白回覆、拒絕與未完成狀態
output_text 為空可能來自工具呼叫、拒絕,或尚未產生文字的輸出;各種情況在 output 中有不同型別。狀態為 incomplete 時,未完成原因與用量可用來判斷輸出預算是否足夠,重試條件也應依這些資料決定。
| 觀察到的結果 | 判斷方式 | 後續處理 |
|---|---|---|
completed,有文字 message | 確認 content 中有 output_text | 顯示回答;需要引用時保留 annotations。 |
completed,有 function_call | 檢查完整呼叫項目與 call_id | 進入工具流程,不標記整個任務完成。 |
message 包含 refusal | 依內容型別辨識 | 顯示合適的拒絕結果,不當成 JSON 或一般回答解析。 |
incomplete | 查看 incomplete_details.reason 與 usage | 依原因調整輸出預算、輸入或任務;不把片段當完整結果保存。 |
failed | 查看 error 與 request ID | 交由錯誤處理,區分輸入錯誤、服務失敗及可重試條件。 |
queued/in_progress | 檢查是否使用背景模式 | 依 response ID 繼續查詢,不從尚未完成的資料取答案。 |
completed,沒有文字、拒絕或待處理工具 | 記錄完整 output 型別、模型與 SDK 版本 | 視為產品未取得預期結果,保存可重現資料再定位問題。 |
推理 token 計入模型的輸出預算。預算不足時,生成可能在可見文字出現前結束,對應證據包含 status、incomplete_details 與 usage。工具回填錯誤或歷史遺失則屬於輸入資料問題,調高輸出預算不會修復這兩種錯誤。
SDK issue #2546 記錄了 2025 年 8 月使用 gpt-5-mini、max_output_tokens=50 的結果:output 只有 reasoning item,output_text 為空,SDK 版本欄僅填 Latest。該 issue 已關閉。這份回應沒有可供 helper 彙整的文字,診斷依據仍是當次的狀態、預算與完整 output,個案結果也受其模型及設定限制。API 欄位定義見Create a response。
多輪對話的上下文管理
產品對話 ID 用來關聯使用者、聊天視窗與權限,API 的 response ID 則識別單次回應。兩者需要保留對應關係;後端依使用者授權讀取 response 或 conversation,前端傳入的 API ID 本身不具備存取權限。
| 策略 | 下一輪送出的資料 | 適合的管理方式 |
|---|---|---|
| 自管完整 input/output | 既有 input、完整 output、工具結果及本輪訊息 | 應用程式掌握保存、裁切及重播;主例使用這種方式。 |
previous_response_id | 上一輪 response ID 加上本輪新增 input | 由已保存的回應鏈延續上下文;應用程式仍管理產品對話與請求設定。 |
conversation | Conversation ID 加上本輪新增 input | 需要持久對話項目時,由 Conversations 保存串接;仍須建立使用者與 conversation 的授權對應。 |
previous_response_id 已指向既有回應鏈,同時重送該鏈的完整歷史會重複提供內容。同一請求也不能同時使用 conversation 與 previous_response_id。各種上下文管理方式的請求形狀見官方 Conversation state。
previous_response_id 與請求設定
這個回應鏈片段使用文件問答程式已定義的 client、COMMON 與 read_answer()。第二輪以第一輪的 response ID 接續,input 僅包含新問題,無須再傳完整 history。
first = client.responses.create(
**COMMON, input=f"請整理這份文件:{DOCUMENT}", store=True,
)
print(read_answer(first))
second = client.responses.create(
**COMMON, previous_response_id=first.id,
input="這個服務的連接埠是多少?", store=True,
)
print(read_answer(second))
previous_response_id 會延續上下文,上一輪頂層的 instructions 不會自動帶入下一輪。每次呼叫都展開 COMMON,因此產品指令、模型與推理設定都會重新送出;需要工具的回合也須提供工具定義。
HTTP 接續出現 previous_response_not_found 時,檢查項目包括先前 response 是否已保存或刪除,以及目前的專案與端點。上一輪使用 store=False 時,對話以完整 items 接續,不依賴該 ID 作為持久狀態。WebSocket 另有連線內快取條件,詳見串流與連線恢復。
Conversations 與產品對話
conversation = client.conversations.create()
for question in (
f"請整理這份文件:{DOCUMENT}",
"文件中的啟動命令是什麼?",
):
response = client.responses.create(
**COMMON, conversation=conversation.id,
input=question, store=True,
)
print(read_answer(response))
Conversation 能跨請求保存對話項目。產品的列表、搜尋、分享、授權、對話標題與使用者刪除流程,仍由自己的資料庫與服務管理。Input/output 項目會在回應完成後加入 Conversation;中途取消或工具結果尚未回填時,應以實際保存的項目判斷目前狀態。
對話紀錄的保存與重播
同一程序可以直接將 SDK output 物件接回 history,跨程序則需要序列化為 JSON。str(item) 只是物件的字串表示,單獨保存 message.content 也會遺失其他項目。下面的 JSON 往返片段使用文件問答程式累積的 history,保存各項目的原始結構。
from pathlib import Path
snapshot = [
item if isinstance(item, dict)
else item.model_dump(mode="json", exclude_none=True)
for item in history
]
path = Path("conversation.json")
path.write_text(json.dumps(snapshot, ensure_ascii=False), encoding="utf-8")
restored = json.loads(path.read_text(encoding="utf-8"))
assert restored == snapshot
# 下一輪使用 restored 作為完整 input,再 append 新的 user message。
JSON 往返測試確認序列化前後的資料一致,實際接續仍須符合所用 SDK 版本的 input 型別。這份紀錄來自 responses.create() 的原始 output,不包含 parsed 物件或畫面 DTO。SDK 升級的回歸測試可涵蓋序列化、還原及還原後的下一輪請求。
完整項目包括 reasoning item 的 encrypted_content、工具呼叫與結果的對應,以及 assistant message 的 phase。phase 區分過程訊息與最後回答,會影響下一輪解讀訊息的方式。encrypted_content 是服務延續推理時使用的不透明資料,保存時維持原值。目前 stateless 回應會預設帶回這份資料,不必另外要求 reasoning summary。相關欄位見Reasoning 文件。
對話在保存後的第二輪出現 400,可比較保存前後的 item 型別、順序、ID 與必要欄位,定位資料轉換的差異。Reasoning items 也是重播資料的一部分,修正序列化時仍須保留;顯示文字可以另存,但不足以重建原有工具流程。
上下文長度與成本
previous_response_id 可減少應用程式管理與傳送歷史的工作,但鏈上的歷史輸入仍計入後續 input token。長對話的用量紀錄應包含每輪 input、output、可取得的 reasoning/cache 細項,以及任務的累計成本,計算範圍不限於本輪新輸入的文字。
不同任務使用各自的對話,已不相關的文件也可移出後續 input,讓上下文保留輸出與推理所需的空間。應用程式摘要提供可讀的任務狀態;Responses compaction 則以不透明項目保存後續生成所需的上下文,兩者保存的資料與用途不同。
Server-side compaction
這段請求使用文件問答程式的 COMMON 與 history,並啟用 server-side compaction。200,000 是示範門檻,實際值依模型 context window、輸入量與輸出預算調整;上下文未達門檻時,不會立即壓縮。
history.append({
"role": "user", "content": "請把上述部署設定整理成維護檢查清單。",
})
response = client.responses.create(
**COMMON, input=history, store=False,
context_management=[{
"type": "compaction", "compact_threshold": 200000,
}],
)
print(read_answer(response))
history.extend(response.output)
latest = next(
(i for i in range(len(history) - 1, -1, -1)
if getattr(history[i], "type", None) == "compaction"
or (isinstance(history[i], dict)
and history[i].get("type") == "compaction")),
None,
)
if latest is not None:
history = history[latest:]
Server-side compaction 隨一般 Responses 請求執行,不需要另外呼叫 compact 端點。自管 input 陣列時,最新 compaction item 及其後的內容就是下一輪可保留的範圍;previous_response_id 則由既有鏈延續,無須自行裁切伺服器端歷史。應用程式若要自行決定壓縮時機,可使用獨立的 client.responses.compact() 流程。規則與限制見Compaction。
工具回合的呼叫與結果需要成對保存,因此歷史裁切或壓縮應在待回填工具處理完成後進行。保留 function_call_output 卻刪除對應呼叫,會使下一輪失去配對依據。
圖片與檔案輸入格式
純文字 input 可以使用字串,圖片與文件則放在 user message 的 content 陣列。產品規則由 instructions 或 developer message 提供,附件屬於待處理資料,各種 content part 的名稱也依 Responses 契約設定。
# 接在主例的 client、COMMON 定義之後;document.pdf 是自己的檔案。
with open("document.pdf", "rb") as source:
uploaded = client.files.create(file=source, purpose="user_data")
try:
response = client.responses.create(
**COMMON, store=False,
input=[{"role": "user", "content": [
{"type": "input_file", "file_id": uploaded.id},
{"type": "input_text", "text": "列出這份部署文件的服務與連接埠。"},
]}],
)
print(read_answer(response))
finally:
client.files.delete(uploaded.id)
圖片採用 input_image 與 image_url,檔案採用 input_file 搭配 file_id、file_data 或 file_url。輸入出現 400 時,角色、content type、來源欄位、格式、容量及模型支援範圍都是檢查項目。PDF 會處理文字與頁面影像,一般文件與試算表則有不同的擷取方式,檔案上傳成功並不保證所有版面與圖表都已完整輸入。格式差異見File inputs與模型文件。
| 請求出錯的形狀 | Responses 應採用的形狀 |
|---|---|
把圖片 part 寫成 Chat Completions 的 image_url 型別 | user content 使用 type: input_image,來源放在 image_url 欄位。 |
| 已上傳檔案,卻把 file ID 放進一般文字 | 用 type: input_file 與 file_id 建立附件,再用 input_text 說明問題。 |
附件旁的文字 part 沿用 type: text | 改用 type: input_text 與 text 欄位;先縮成一則 user 附件請求確認形狀。 |
程式在完成請求後刪除示範上傳檔案,檔案的建立與清理由同一段流程負責。持續維護的文件庫則需要自己的保存、刪除與檢索規則;單次附件與 file_search 的使用差異見文件查詢工具。音訊另有端點與模型支援範圍,不能直接以改名的圖片或文件 part 傳入。
store=False 與資料保留
Responses 預設保存回應狀態,store=False 關閉的是 response 這一層的保存。產品資料庫、已建立的 Conversations、上傳檔案與其他服務紀錄各有保留規則;標準濫用監控、ZDR 及 background 暫存資料,也依專案的資料控制設定分別處理。
對話紀錄按使用者與產品對話隔離,並提供相應的存取與刪除流程。觀測紀錄通常保存 request/response ID、狀態、用量、延遲和功能分類;完整輸入、文件與加密狀態則需要額外管理存取權限。各資源的保留政策見API 資料控制文件。
狀態與重播測試
診斷紀錄包含模型、SDK 版本、request/response ID、狀態、output 型別及用量,並保留兩輪之間實際送出的資料供比對。預算不足、工具結果缺失和歷史轉換錯誤各有不同的修正位置,可用固定回應與對話資料驗證下列分支。
| 測試情境 | 應驗證的結果 |
|---|---|
| 完成的文字回答 | 正確讀取文字,完整 output 進入下一輪歷史。 |
| 未完成或拒絕 | 走獨立分支,不把片段當成成功答案。 |
| 有工具但沒有文字 | 保留呼叫並交給執行器,不當作一般空字串。 |
| 保存後重播 | JSON 往返保留型別、順序、reasoning 與 phase,並測一次實際接續。 |
| 長對話壓縮後繼續 | 保留最新 compaction 邊界,不留下孤立的工具結果。 |
工具呼叫、結構化輸出、串流與背景工作的相關實作,可從OpenAI API 系列總覽查找。
