症狀:第一輪 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_content 與 content 放在 assistant 訊息的同一層。涉及工具呼叫時,該欄位必須在後續請求中完整回傳,否則 API 會回傳 400。官方工具呼叫範例也直接展示了 assistant 訊息、tool_calls 與工具結果的連續回放。
目前 vLLM 文件把推理輸出欄位寫成 reasoning,並說明它曾使用 reasoning_content 這個名稱。vLLM reasoning outputs 文件的這項變更,只能證明目前輸出契約的命名方向,不能推導出 DeepSeek 官方 API 會接受 reasoning 作為輸入,也不能證明你的 vLLM 部署版本已支援相同的回放格式。
這裡有三個常見限制:
- 欄位契約不同:同樣是 OpenAI 相容格式,輸出欄位與輸入回放規則仍可能不同。
- 中介層會丟欄位:物件轉字典、JSON 過濾、紀錄脫敏或訊息正規化,都可能移除
reasoning_content、tool_calls或tool_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。不要在紀錄層先把欄位統一成 thinking 或 analysis,否則你失去向端點證明原始欄位的能力。官方 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 訊息裡,與 content、tool_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_calls、tool_call_id與工具結果能對應。 - [ ] 以連續工具呼叫確認每一則 assistant 回合的推理欄位都能回放。
- [ ] 對官方 API 斷言工具回合存在
reasoning_content。 - [ ] 對 vLLM 斷言實際輸出欄位符合目標部署版本,而非只符合某份舊文件。
- [ ] 保留一份缺少推理欄位的失敗 body,確保錯誤仍能穩定重現。
- [ ] 測試端點切換後,內部訊息模型仍能正確完成單向欄位映射。
- [ ] 脫敏保存 request、response、錯誤體與工具呼叫 ID。
下表可直接作為測試矩陣。欄位名稱是契約檢查項,不代表兩個端點可以互換:
| 用例 | 官方 API 應檢查 | vLLM 應檢查 | 修復完成條件 |
|---|---|---|---|
| 無工具呼叫 | content、思考欄位可選回放 |
依版本確認 reasoning 是否出現 |
最終回答可取得 |
| 單次工具呼叫 | reasoning_content、tool_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 方案,將端點回歸與工具呼叫測試納入穩定、可控的工作流程。