2026 DeepSeek V4 第二輪 400:請求日誌怎麼查?

獲勝者:逐跳比對同一會話的請求資料,而不是先重試。 如果首輪 tool call 正常、加入工具結果後第二輪才報 400,先檢查歷史訊息是否遺失 reasoning_content,再確認 vLLM 端點是否應使用 reasoning;只有在欄位與端點契約都正確後,才排查訊息順序和額外參數。
這篇適合維護多輪 function calling 循環、負責 API 閘道、會話資料庫或 SDK 封裝的工程師,也適合同時接入官方 API 與 vLLM、需要保留端點切換能力的推理平台團隊。
最後更新於 2026 年 8 月 15 日;資料核實自官方 thinking mode 說明、官方 tool calls 文件、Chat Completion 欄位定義及vLLM reasoning outputs 文件。
先看故障簽名:首輪成功,第二輪才失敗
最小脫敏日誌應保留同一個 conversation_id 下的三段內容:
| 位置 | 必須保留的欄位 | 用途 |
|---|---|---|
| 首個 assistant 回應 | role、content、reasoning_content 或 reasoning、tool_calls |
確認模型是否真的產生工具呼叫 |
| 工具回應 | role、tool_call_id、content |
確認工具結果是否回到正確的呼叫 |
| 下一次請求 | 完整訊息索引、欄位集合、端點類型、HTTP 狀態 | 定位欄位在哪一跳消失或改名 |
如果首輪連工具定義都無法解析,方向才是 schema、tools 或 tool_choice。但首輪已經成功產生 tool_calls,而第二輪才出現 400,故障範圍通常已縮小到「歷史訊息如何被重新組裝」。
官方文件明確指出,thinking mode 中一旦發生工具呼叫,後續請求必須完整回傳該 assistant 輪次的 reasoning_content;缺少這個欄位可能直接得到 400。
常見限制有三個:
- SDK 回應物件只保存
content和tool_calls,額外欄位在轉成字典時被忽略。 - API 閘道只允許白名單欄位,未知的
reasoning_content或reasoning被刪除。 - 資料庫 schema 沒有推理欄位,消費者重新建立 assistant 訊息時只取固定欄位。
因此,重試通常沒有幫助。重試只會把同一份缺欄位的歷史訊息再次送出。
沒有推理欄位:先查保存與序列化,不要改工具定義
若最終失敗請求中的 assistant 訊息完全沒有推理欄位,先不要調整工具 JSON Schema。排查重點是資料流:
assistant = {
"role": response["role"],
"content": response.get("content"),
"tool_calls": response.get("tool_calls"),
"reasoning_content": response.get("reasoning_content"),
}
上段只代表「應保存哪些欄位」,不代表兩類端點可以共用同一個出站物件。對官方 API 的 thinking tool loop,發生工具呼叫的 assistant 訊息需要保留 reasoning_content。官方範例也直接把模型回應中的完整 assistant 訊息加入 messages,其中包含 content、reasoning_content 和 tool_calls。
建議在四個位置列印「欄位集合」,而不是列印完整推理內容:
| 取樣節點 | 建議記錄 | 看到甚麼才算正常 |
|---|---|---|
| SDK 原始回應 | sorted(message.keys()) |
有工具呼叫時包含推理欄位 |
| 內部會話物件 | 欄位集合與訊息索引 | assistant 索引沒有被重建遺失 |
| 資料庫或佇列輸出 | schema 欄位、序列化後欄位 | 推理欄位仍存在,空值策略一致 |
| 最終 HTTP body | 端點、版本、欄位集合 | 與目標端點契約一致 |
實務上,以下幾種程式碼最容易製造第二輪 400:
messages.append({
"role": "assistant",
"content": message.content,
"tool_calls": message.tool_calls,
})
這段程式主動丟掉了 reasoning_content。若上游是官方 thinking API,下一輪就不再是原本的會話上下文。
另外,串流模式需要注意增量合併。reasoning_content 可能分散在多個 delta;若合併器只累積 content,首輪畫面看似正常,實際保存的 assistant 訊息卻沒有完整推理欄位。
有推理內容但送錯欄位:官方 API 與 vLLM 必須分開處理
這是 reasoning_content 與 vLLM reasoning 最容易混淆的地方。
| 端點 | 主要推理欄位 | 排查重點 | 不應直接假設 |
|---|---|---|---|
| DeepSeek 官方 API | reasoning_content |
thinking tool loop 是否完整回傳 | vLLM 的 reasoning 可直接代替 |
| 當前 vLLM 文件與介面 | reasoning |
實際部署版本、reasoning parser、chat template | 所有版本都接受舊欄位 |
| 內部訊息模型 | 建議使用中立欄位,例如 reasoning_text |
進入出站適配器前保持一致 | 內部物件可直接當 HTTP body |
vLLM 文件已說明,推理輸出欄位由舊名稱 reasoning_content 改為 reasoning,遷移方式是替換欄位名稱。文件同時展示了工具呼叫與 reasoning 並存的回應形式。(vLLM reasoning outputs 文件)
但這不代表 reasoning 可以不經轉換地發給官方 API。較穩妥的做法是保留一個內部欄位,再依端點映射:
def to_outbound_message(message, endpoint):
out = {
"role": message["role"],
"content": message.get("content"),
}
if message.get("tool_calls") is not None:
out["tool_calls"] = message["tool_calls"]
if endpoint == "deepseek-api":
out["reasoning_content"] = message.get("reasoning_text")
elif endpoint == "vllm":
out["reasoning"] = message.get("reasoning_text")
return out
這個適配層有兩個優點:
- 不需要在全域訊息物件同時複製兩個欄位。
- 端點切換時,欄位轉換集中在一處,容易配合版本測試。
vLLM 的實際輸入兼容行為仍應按部署版本驗證。文件中的欄位名稱、parser 和 chat template 是協議線索,不是所有歷史版本的永久保證。社群 issue 可用來找復現方向,但不能直接推論所有 vLLM 版本都會丟棄舊欄位。(vLLM 社群 issue 復現報告)
欄位在記憶體裡,經過閘道或資料庫後消失
若 SDK 原始回應有推理欄位,內部物件也有,但最終 body 沒有,故障已不在模型端。可按以下順序逐跳取證:
1. 檢查 SDK 物件轉換
部分 SDK 的訊息物件不是普通字典。使用 .dict()、model_dump() 或自訂 JSON encoder 時,未宣告欄位可能被排除。日誌只記錄欄位名稱即可:
assert "tool_calls" in assistant
assert "reasoning_content" in assistant or "reasoning" in assistant
2. 檢查閘道白名單
閘道常見設定包括:
| 風險位置 | 可能造成的結果 | 證據 |
|---|---|---|
| Request schema | 未知欄位被拒絕或刪除 | 閘道前後欄位集合不同 |
| 空值清理器 | 空字串或 null 被移除 |
原始欄位存在,出站欄位消失 |
| JSON 重建器 | 只複製固定欄位 | 欄位順序或索引重新生成 |
| 流式合併器 | 只合併 content |
reasoning 增量沒有進入完整訊息 |
3. 檢查資料庫與佇列
若 schema 只有 role、content、tool_calls,即使應用程式暫時保存了推理內容,寫入資料庫後仍會遺失。消費者服務也可能使用舊版資料模型,把新增欄位當成未知欄位丟棄。
最有價值的證據不是完整 payload,而是同一個訊息索引在各節點的差異:
message[3] sdk: role, content, reasoning_content, tool_calls
message[3] memory: role, content, reasoning_content, tool_calls
message[3] database: role, content, tool_calls
message[3] outbound: role, content, tool_calls
此時應修資料庫 schema 或消費者映射,不應透過重試掩蓋問題。若需要固定環境保存失敗會話,可先參考 ProxyMac 的說明資源,將日誌欄位、端點版本和回放方式固定下來。
推理欄位正確:再查訊息鏈與額外參數
當最終請求已含正確推理欄位,400 仍可能來自訊息鏈,而不是欄位名稱。
應逐項核對:
- assistant 工具呼叫是否排在對應的 tool 訊息之前。
tool_call_id是否與 assistant 的呼叫 ID 完全一致。- tool 訊息是否只回應實際存在的工具呼叫。
- assistant 的
content是否被框架改成不符合端點要求的空值型別。 - 是否在 thinking mode 中帶入端點不支援的額外參數。
官方 thinking mode 文件列出,思考模式不支援 temperature、top_p、presence_penalty 和 frequency_penalty;相容行為可能讓參數不一定立即報錯,但不應把所有 400 都歸因於推理欄位。
tool_choice 也要單獨核對。某些整合文件已提示,DeepSeek V4 thinking mode 對 tool_choice 有限制;若錯誤訊息明確指向參數,就應依該端點文件移除或改寫,而不是繼續複製推理欄位。(DeepSeek thinking mode 參數說明)
按條件選擇修復層:不要用全域雙欄位掩蓋問題
可用以下分支決定修復位置:
- 若 SDK 原始回應已缺欄位:先檢查 SDK、串流合併器和模型回應解析器;不要修改資料庫。
- 若記憶體有欄位、資料庫沒有:修 schema、序列化規則或佇列消費者。
- 若資料庫有欄位、官方 API 最終請求沒有:修官方 API 出站適配器,映射到
reasoning_content。 - 若 vLLM 回應只有
reasoning:內部先統一成中立欄位,再按版本和端點映射。 - 若欄位、順序和 ID 都正確但仍 400:檢查
tool_choice、thinking mode 參數、chat template 和明確錯誤訊息。 - 若無法判斷是 API 還是 vLLM 問題:用同一份最小會話分別直連兩個端點,再回放完整業務鏈路。
不建議在所有訊息中同時加入 reasoning_content 和 reasoning。這可能暫時讓某些鏈路「看似恢復」,卻把端點契約錯誤藏在資料模型內,之後升級 parser 或閘道時更難定位。
用同一會話回放,確認修復真正生效
最小回放應固定 3 個測試入口:
| 回放入口 | 目的 | 必須記錄 |
|---|---|---|
| 直連官方 API | 確認 reasoning_content 與 tool loop 契約 |
端點、模型、thinking 設定、HTTP 回應 |
| 直連目標 vLLM | 確認 reasoning、parser 與版本行為 |
vLLM 版本、啟動參數、chat template |
| 經完整業務鏈路 | 確認閘道、資料庫、佇列沒有丟欄位 | 每跳欄位集合、訊息索引、前後差異 |
回放時不要只測新的單輪請求。應保留:
- 使用者訊息。
- 首個 assistant 工具呼叫。
- 對應 tool 結果。
- 下一次請求。
- 最終回應或 400 body。
修復完成後,版本控制中至少留下以下紀錄:
- 修改前後的出站 JSON 欄位差異。
- 端點類型:官方 API 或 vLLM。
- 實際 vLLM 版本與 parser 設定。
tool_call_id和訊息索引是否保持一致。- 明確錯誤訊息及回放結果。
這份基線能在 DeepSeek 或 vLLM 更改欄位名稱、兼容策略或工具參數限制後,快速判斷是端點變更,還是自家資料鏈路再次丟欄位。
常見問題
DeepSeek V4 為甚麼第一次工具呼叫成功,第二次才報 400?
首輪成功表示工具定義和初次解析可能沒有問題。第二輪加入 tool 結果後,框架必須重送先前的 assistant 訊息;若 reasoning_content 在保存、序列化或出站轉換時消失,官方 API 便可能拒絕該歷史上下文。
reasoning_content 已經保存,為甚麼多輪請求仍然失敗?
保存位置不一定是出錯位置。需要比較 SDK、記憶體物件、資料庫、閘道和最終 body 的欄位集合。即使 reasoning_content 存在,tool_call_id 不匹配、訊息順序錯誤、空值型別不相容或額外參數受限,也可能造成 400。
vLLM 返回 reasoning 後能否直接發給 DeepSeek 官方 API?
不應直接發送。當前 vLLM 文件使用 reasoning,官方 thinking tool loop 要求 reasoning_content。兩者應由適配層轉換,並以實際 vLLM 部署版本做直連回放;社群報告只能作為特定版本的排查線索。
怎麼抓取多輪 tool call 的最小請求日誌定位丟欄位環節?
只保留同一會話的 assistant 工具呼叫、tool 結果和下一次失敗請求,逐跳記錄欄位集合、訊息索引、端點與狀態碼。推理內容、工具結果和金鑰全部脫敏。若前後欄位集合不同,差異所在的節點就是第一個應修復的位置。
如果目前方案是把兩類端點共用一份訊息 JSON,常見缺點是欄位契約混雜、閘道白名單難以維護、vLLM 升級後兼容行為不易驗證,還會讓失敗會話難以穩定重播。先把適配層和回放環境固定,比單純增加伺服器或重試次數更可靠。需要持續保留固定測試環境時,可進一步查看 ProxyMac 的雲端環境說明;若專案還依賴 macOS 客戶端、Xcode 自動化或長時間在線任務,再評估 ProxyMac 的方案頁面,讓同一套回歸流程有可重複的 Mac 執行環境。