AI 智能體

2026 DeepSeek V4 多輪 400:最小請求怎麼重現?

MacHTML Lab2026.08.18 約5分鐘閱讀
2026 DeepSeek V4 多輪 400:最小請求怎麼重現?

症狀:第一輪 function calling 成功,加入工具結果後,下一次請求卻收到 400,錯誤指向推理內容未回傳。

最快解法:先把 DeepSeek 官方 API 與 vLLM 拆成兩條原始 HTTP 復現鏈;官方 API 的工具呼叫回合完整保留 reasoning_content,vLLM 則依目前部署版本確認 reasoning 欄位,禁止在同一份 payload 中混用兩者。

最後更新於 2026 年 8 月 18 日;資料核實自 DeepSeek 思考模式文件DeepSeek Chat Completions 欄位定義vLLM reasoning outputs 文件

這篇適合三類人:需要向模型或框架維護者提交可重現證據的 AI Agent 開發者;要判斷問題在客戶端、閘道還是推理端點的後端工程師;以及準備為官方 API 與 vLLM 建立雙端點回歸環境的平台團隊。

先把兩種端點分開,否則所有錯誤都會混在一起

你不能只看 model 名稱判斷請求去了哪裡。先記錄實際的 base_url、HTTP Host、部署版本與啟用的思考模式:

  • DeepSeek 官方 API:例如 https://api.deepseek.com
  • vLLM 自託管端點:例如內部的 /v1/chat/completions
  • 轉發閘道:即使外觀相容 OpenAI 格式,也要確認它是否改寫了 assistant 訊息。

官方思考模式把 reasoning_contentcontent 放在 assistant 訊息的同一層。涉及工具呼叫時,該欄位必須在後續請求中完整回傳,否則 API 會回傳 400官方工具呼叫範例也直接展示了 assistant 訊息、tool_calls 與工具結果的連續回放。

目前 vLLM 文件把推理輸出欄位寫成 reasoning,並說明它曾使用 reasoning_content 這個名稱。vLLM reasoning outputs 文件的這項變更,只能證明目前輸出契約的命名方向,不能推導出 DeepSeek 官方 API 會接受 reasoning 作為輸入,也不能證明你的 vLLM 部署版本已支援相同的回放格式。

這裡有三個常見限制:

  • 欄位契約不同:同樣是 OpenAI 相容格式,輸出欄位與輸入回放規則仍可能不同。
  • 中介層會丟欄位:物件轉字典、JSON 過濾、紀錄脫敏或訊息正規化,都可能移除 reasoning_contenttool_callstool_call_id
  • 工具結果不是獨立證據:只看到 tool 訊息正確,不代表前一則 assistant 訊息仍保留完整推理欄位。

用最小首輪請求先證明 function calling 能否觸發

先不要接入 Agent 框架,也不要使用多個工具。工具只保留一個無副作用函式,參數維持最小,方便你確認錯誤究竟來自欄位還是工具本身。

以下請求只展示定位必要內容,沒有金鑰、完整推理文字或真實工具結果:

curl "$DEEPSEEK_BASE_URL/chat/completions" \
  -H "Authorization: Bearer $DEEPSEEK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-v4-pro",
    "messages": [
      {
        "role": "user",
        "content": "請查詢測試城市的天氣。"
      }
    ],
    "tools": [
      {
        "type": "function",
        "function": {
          "name": "get_test_weather",
          "description": "回傳固定的測試天氣資料",
          "parameters": {
            "type": "object",
            "properties": {
              "city": { "type": "string" }
            },
            "required": ["city"]
          }
        }
      }
    ],
    "thinking": { "type": "enabled" }
  }'

保存完整回應,但除錯紀錄只需要抽取:

{
  "id": "chatcmpl-redacted",
  "message": {
    "content": "[保留結構,不保留真實文字]",
    "reasoning_content": "[REDACTED]",
    "tool_calls": [
      {
        "id": "call-redacted",
        "type": "function",
        "function": {
          "name": "get_test_weather",
          "arguments": "{\"city\":\"test\"}"
        }
      }
    ]
  },
  "finish_reason": "tool_calls"
}

這裡要分開記錄 reasoning_content 與 vLLM 的 reasoning。不要在紀錄層先把欄位統一成 thinkinganalysis,否則你失去向端點證明原始欄位的能力。官方 Chat Completions schema 對 assistant、tool 與工具呼叫 ID 的欄位都有明確定義,可對照官方 API schema

第一輪成功,為什麼下一輪仍然會報 400?

最常見的原因不是工具執行失敗,而是你把首輪 assistant 訊息保存成了不完整版本。

DeepSeek 官方 API 的正確回放骨架如下:

{
  "messages": [
    {
      "role": "user",
      "content": "請查詢測試城市的天氣。"
    },
    {
      "role": "assistant",
      "content": "[首輪 content;可為空字串]",
      "reasoning_content": "[完整回傳值,除錯展示時脫敏]",
      "tool_calls": [
        {
          "id": "call-redacted",
          "type": "function",
          "function": {
            "name": "get_test_weather",
            "arguments": "{\"city\":\"test\"}"
          }
        }
      ]
    },
    {
      "role": "tool",
      "tool_call_id": "call-redacted",
      "content": "[固定測試結果]"
    }
  ]
}

reasoning_content 應放在首輪模型輸出的 assistant 訊息裡,與 contenttool_calls 同級。工具結果則是另一則 role: tool 訊息,透過相同的 tool_call_id 對應。不能把推理內容塞進 tool 訊息,也不能只把它寫入外部紀錄而不放回 messages

你可以在送出後續請求前加入斷言:

assistant = first_response["choices"][0]["message"]

assert assistant.get("tool_calls")
assert assistant.get("reasoning_content") is not None
assert assistant["tool_calls"][0]["id"]

tool_call_id = assistant["tool_calls"][0]["id"]

messages.append({
    "role": "assistant",
    "content": assistant.get("content"),
    "reasoning_content": assistant["reasoning_content"],
    "tool_calls": assistant["tool_calls"],
})

messages.append({
    "role": "tool",
    "tool_call_id": tool_call_id,
    "content": "[固定測試結果]",
})

不要把「物件有這個欄位」誤認為「序列化後仍有這個欄位」。至少檢查三個層次:原始 HTTP 回應、程式內部物件、真正送出的 JSON。任何一層缺失,都要把它標記為客戶端或中介層問題,而不是先歸咎於模型。

提醒: 不要把完整推理文字放進公開 issue、共享紀錄或測試報告。保留欄位存在性、字串長度區間、工具呼叫 ID 與脫敏後的結構即可;這足以證明回放是否完整。

用失敗樣本與成功樣本形成一對證據

先故意移除官方 API 所要求的推理欄位,建立穩定失敗樣本:

{
  "role": "assistant",
  "content": "[首輪內容]",
  "tool_calls": [
    {
      "id": "call-redacted",
      "type": "function",
      "function": {
        "name": "get_test_weather",
        "arguments": "{\"city\":\"test\"}"
      }
    }
  ]
}

接著只補回 reasoning_content,其他訊息保持不變,再送出第二次請求。你要保存以下證據:

  • HTTP 狀態碼與錯誤體。
  • 失敗請求中的 base_url
  • assistant 訊息是否含 reasoning_content
  • tool_call_id 是否與首輪 ID 完全一致。
  • 閘道或中介層前後的 JSON 差異。

「補回欄位後成功」仍不足以宣告修復完成。你還要繼續送出下一次工具結果,再確認模型能從工具回應進入最終回答。連續工具呼叫時,每一個產生工具呼叫的 assistant 回合都要保留自己的推理欄位;不能只保存第一回合。

不用 OpenAI SDK 時,最可靠的方法就是保留兩份 .json:一份是首輪回應的脫敏原文,一份是後續請求的最終 body。使用 curl、Node.js fetch 或 Python requests 都可以,重點是不要讓 SDK 的訊息類別替你隱藏欄位生命週期。

官方 API 與 vLLM:同一骨架,不同出口

你可以共用內部訊息模型,但不能把端點欄位當成雙向通用替換。建議在出口層做單向映射:

內部模型:
assistant.reasoning
assistant.content
assistant.tool_calls

DeepSeek 官方 API 出口:
reasoning -> reasoning_content

vLLM 出口:
依目標版本協議確認使用 reasoning

vLLM 的文件指出,推理輸出欄位目前使用 reasoning,而舊文件與部分相容層仍可能出現 reasoning_content。此外,vLLM 的工具解析只會從 content 解析函式呼叫,不應把工具 JSON 放進推理欄位。vLLM 工具呼叫與 reasoning 說明可用來確認這個邊界。

檢查項目 DeepSeek 官方 API vLLM 自託管端點
推理輸出欄位 reasoning_content 目前文件使用 reasoning
工具呼叫欄位 tool_calls 依 OpenAI 相容層與部署版本確認
後續回放 工具回合必須完整回傳 reasoning_content 不能直接推定接受官方欄位
主要證據 官方 API 文件與實際 HTTP 回應 目標版本文件、協議 schema、實際 HTTP 回應

因此,vLLM 返回 reasoning 後,不能直接原樣回傳給 DeepSeek API。你必須先判斷這段訊息的來源,再在 DeepSeek 出口轉成 reasoning_content。反過來,也不要把 DeepSeek 的欄位名稱全域改成 reasoning,因為這可能破壞官方 API 的工具回放。

版本差異要以部署版本為準。除了一般文件,也應檢查目前 vLLM reasoning API 參考與目標部署所使用的 parser、Chat Completions schema 及協議原始碼。若文件與實際回應不同,以實際請求和版本化 schema 建立暫時契約,並在升級時重新驗證。

把最小復現鏈固化成回歸用例

完成一次手動排障後,立即把它變成可重跑的測試。不要只斷言 HTTP 狀態碼,因為某些錯誤可能在閘道被改寫或被錯誤地吞掉。

  • [ ] 記錄實際 base_url,並斷言請求沒有誤送到另一個端點。
  • [ ] 以無工具呼叫請求確認一般思考模式訊息能正常完成。
  • [ ] 以單次工具呼叫確認 tool_callstool_call_id 與工具結果能對應。
  • [ ] 以連續工具呼叫確認每一則 assistant 回合的推理欄位都能回放。
  • [ ] 對官方 API 斷言工具回合存在 reasoning_content
  • [ ] 對 vLLM 斷言實際輸出欄位符合目標部署版本,而非只符合某份舊文件。
  • [ ] 保留一份缺少推理欄位的失敗 body,確保錯誤仍能穩定重現。
  • [ ] 測試端點切換後,內部訊息模型仍能正確完成單向欄位映射。
  • [ ] 脫敏保存 request、response、錯誤體與工具呼叫 ID。

下表可直接作為測試矩陣。欄位名稱是契約檢查項,不代表兩個端點可以互換:

用例 官方 API 應檢查 vLLM 應檢查 修復完成條件
無工具呼叫 content、思考欄位可選回放 依版本確認 reasoning 是否出現 最終回答可取得
單次工具呼叫 reasoning_contenttool_calls、工具 ID parser 與工具解析結果 工具結果能觸發下一輪
連續工具呼叫 每輪完整保留推理與工具 ID 每輪輸出與輸入契約一致 最終回答不再出現 400
端點切換 出口轉成 reasoning_content 出口依版本保留 reasoning 同一內部訊息不被全域改名
錯誤回放 缺欄位時穩定得到 400 依實際協議產生可辨識錯誤 錯誤可定位到責任邊界

如果本地開發機無法長期保留兩套乾淨端點,或每次框架升級都會改寫會話狀態,租用一套隔離的雲端 Mac 測試環境會比在同一台機器上反覆切換設定更容易留下證據。你可以先查看 MacHTML 的雲端 Mac 方案支援說明,確認是否符合你的測試週期與遠端連線方式;若只是短期復現,不必為了這個問題先購買長期硬體。

目前方案若是單一本地開發機,常見缺點是端點環境互相污染、版本回退不容易、SSH 或遠端協作時資料不一致;若改用臨時共用伺服器,又可能缺少穩定的圖形化工具鏈與隔離權限。對需要反覆驗證官方 API、vLLM、閘道與框架升級的團隊,租用 MacHTML 的隔離 Mac 環境,能把同一份復現腳本、脫敏請求與回歸結果固定下來,較適合作為短期修復與測試用途;但若你需要長期滿載推理、特殊實體介面或永久保存大量模型資料,自購硬體仍可能更合理。

為 AI Agent 測試建立穩定的遠端 Mac 環境

使用 MacHTML 遠端 Mac,快速建立可重現的測試環境,協助您核對請求、回應與多輪流程。 透過瀏覽器即可連線及管理 Mac,無需另外維護實體設備,方便團隊整理完整的除錯證據。 按需租用 Mac 與算力節點,讓後端工程師及推理平台團隊彈性配置測試資源。 立即選擇合適的 MacHTML 方案,將端點回歸與工具呼叫測試納入穩定、可控的工作流程。

租用雲端 Mac mini
Apple Silicon 雲端 Mac