第一轮返回了 tool_calls,追加工具结果后,DeepSeek V4 多轮请求却报 400。
最快解法:先把官方 API 与 vLLM 拆成两条原始 HTTP 复现链;官方 API 的工具调用回合完整回传 reasoning_content,vLLM 则先按部署版本确认 reasoning 的输入输出契约,禁止直接全局改名。
最后更新于 2026 年 8 月 18 日,字段规则核实自 DeepSeek API 文档、Chat Completions Schema 与 vLLM 当前 reasoning 文档。
这篇适合需要提交可复现证据的 AI Agent 开发者、判断故障属于客户端还是网关的后端工程师,以及准备维护官方 API 与自托管推理端点双回归环境的平台团队。
先分清端点:模型名称相同,不代表消息契约相同
排障第一步不是改字段,而是记录请求真正发往哪里。不要只看 model 写的是不是 deepseek-v4-flash,因为官方 API、网关适配层和本地 vLLM 可能使用同一个模型别名,却要求不同的消息字段。
官方 API 使用 OpenAI 兼容的 Chat Completions 入口。思考模式下,模型返回的推理字段是 reasoning_content,并且工具调用产生的 assistant 消息需要在后续请求中完整带回该字段。官方文档明确说明,工具调用回合缺少它会返回 400。可先核对思考模式中的多轮工具调用要求。
vLLM 当前文档把推理输出字段写成 reasoning,并说明它曾经叫作 reasoning_content。这只是 vLLM 当前输出命名和迁移说明,不等于官方 API 会接受 reasoning,也不等于你的目标部署版本一定接受把 reasoning 写回输入消息。具体输入兼容行为,要以部署版本文档、协议模型和原始请求实测为准。(docs.vllm.ai)
你需要先保存以下信息:
base_url,包括是否带/v1。- 端点类型:官方 API、网关,还是 vLLM。
- 实际模型 ID。
- 思考模式开关及其参数位置。
- vLLM 启动时使用的 reasoning parser 和 tool parser。
- 原始响应中究竟出现了
reasoning_content还是reasoning。
这里有三个容易被忽略的隐性成本:
- 字段被中间件过滤。 日志脱敏、对象转字典、JSON Schema 校验器可能把未知字段直接丢掉。
- 端点被错误归类。 适配层只根据模型名判断协议,导致 vLLM 响应被按官方 API 消息回放。
- 工具结果顺序不完整。
tool_call_id、assistant 消息和 tool 消息必须形成对应关系,单独保留工具结果不能重建有效会话。
首轮先求可观察:用一个无副作用工具触发 function calling
下面的请求只保留定位字段。工具不会访问真实系统,也不包含密钥、完整推理文本或用户数据。请求不使用 SDK,直接发送到官方 API。
curl https://api.deepseek.com/chat/completions \
-H 'Authorization: Bearer $DEEPSEEK_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"model": "deepseek-v4-flash",
"messages": [
{
"role": "user",
"content": "请调用 get_demo_date,返回一个演示日期。"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_demo_date",
"description": "返回固定的演示日期,不访问外部系统。",
"parameters": {
"type": "object",
"properties": {},
"additionalProperties": false
}
}
}
],
"thinking": {
"type": "enabled"
},
"reasoning_effort": "high"
}'
不要假设模型一定会调用工具。你的目标是得到一份完整响应,并只提取这些字段:
{
"id": "chatcmpl-redacted",
"message": {
"content": "",
"reasoning_content": "[已脱敏]",
"tool_calls": [
{
"id": "call-redacted",
"type": "function",
"function": {
"name": "get_demo_date",
"arguments": "{}"
}
}
]
},
"finish_reason": "tool_calls"
}
官方 API 的 Chat Completions Schema 将 reasoning_content、content 和 tool_calls 放在 assistant 消息层级;工具结果则必须使用 role: tool 和对应的 tool_call_id。可以对照官方请求与响应字段定义检查序列化结果。(api-docs.deepseek.com)
此时先不要改名。把响应原文保存一份,把脱敏后的结构保存另一份。两者之间如果字段数量不同,问题发生在客户端或中间件,不应归咎于推理端点。
第一轮成功,第二轮仍报 400:缺的通常不是工具结果
这是最常见的 DeepSeek V4 多轮 400 症状:首轮拿到了工具调用,工具函数也正常返回,但第二次请求只追加了工具消息,遗漏了首轮 assistant 消息里的 reasoning_content。
官方 API 的回放骨架应接近下面这样。[REDACTED] 只是占位符,真实测试时必须使用首轮响应返回的完整推理字段;不要把示例占位符发送给端点。
{
"model": "deepseek-v4-flash",
"messages": [
{
"role": "user",
"content": "请调用 get_demo_date,返回一个演示日期。"
},
{
"role": "assistant",
"content": "",
"reasoning_content": "[REDACTED]",
"tool_calls": [
{
"id": "call-redacted",
"type": "function",
"function": {
"name": "get_demo_date",
"arguments": "{}"
}
}
]
},
{
"role": "tool",
"tool_call_id": "call-redacted",
"content": "2026-08-18"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_demo_date",
"description": "返回固定的演示日期,不访问外部系统。",
"parameters": {
"type": "object",
"properties": {},
"additionalProperties": false
}
}
}
],
"thinking": {
"type": "enabled"
},
"reasoning_effort": "high"
}
官方文档的关键点不是“把思考内容放到下一条 user 消息”,而是把它放回上一条 assistant 消息,与该消息的 content、tool_calls 同级。之后再追加 tool 消息。工具调用连续发生时,每一轮产生的 assistant 推理字段都要保留。(api-docs.deepseek.com)
可以先构造一个稳定失败样本:
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call-redacted",
"type": "function",
"function": {
"name": "get_demo_date",
"arguments": "{}"
}
}
]
}
然后只增加 reasoning_content,再次发送。两次请求必须记录:
- HTTP 状态码;
- 完整错误体;
- 实际发送的 messages;
tool_call_id是否一致;base_url是否一致;- 响应中的
id和finish_reason。
如果缺字段样本稳定返回 400,而完整回放样本进入下一轮,才可以把证据指向官方 API 的消息契约。不要把一次偶然成功当成所有回合都修复。
vLLM 的 reasoning 能否直接回传给官方 API?
不能直接这样推断。vLLM 当前文档用 reasoning 表示推理输出,并将 reasoning_content 作为旧名称说明;但官方 API 的工具调用回放契约仍然使用 reasoning_content。因此,vLLM 返回 reasoning 后,不能未经确认就原样发送给官方 API。(docs.vllm.ai)
更稳妥的做法是分三层记录:
端点出口层:
- 官方 API:读取并回放
assistant.reasoning_content。 - vLLM:读取部署版本实际返回的
assistant.reasoning。 - 网关:记录是否进行了字段转换,以及转换前后的 JSON。
内部消息模型层:
{
"role": "assistant",
"content": "",
"reasoning": "[内部统一字段]",
"tool_calls": [
{
"id": "call-redacted",
"name": "get_demo_date",
"arguments": "{}"
}
]
}
请求映射层:
- 发往官方 API 时,将内部
reasoning单向映射为reasoning_content。 - 发往 vLLM 时,按目标部署版本的协议决定是否映射为
reasoning。 - 不要修改已经保存的历史消息,也不要在全局序列化器里把两个字段互相替换。
vLLM 的当前解析器文档还显示,DeepSeek V4 parser 同时处理 <think> 推理片段和 DSML 工具调用。它说明的是 vLLM 如何解析模型输出,不是官方 API 的输入回放规则。(docs.vllm.ai)
两个端点分栏测试:同一骨架,不同字段出口
你可以复用相同的工具定义、用户问题和工具结果,但必须分别保存两份端点配置。
官方 API 侧:
base_url指向官方 Chat Completions 入口;- 观察
reasoning_content; - assistant 消息完整回放
reasoning_content、content和tool_calls; - tool 消息使用对应的
tool_call_id; - 缺少推理字段时,保留 400 错误样本。
vLLM 侧:
base_url指向目标部署的 OpenAI 兼容入口;- 记录 vLLM 版本、启动参数和 parser;
- 观察响应是否提供
reasoning; - 用目标版本的协议定义确认 assistant 输入是否接受该字段;
- 不要因为 OpenAI 兼容就认为所有消息字段完全兼容。
vLLM 的 reasoning 文档说明,推理输出需要使用对应的 --reasoning-parser;当前文档中的输出示例也直接读取 message.reasoning。这与旧版文档曾使用 reasoning_content 的情况不同,所以排障报告必须带版本信息,不能只写“vLLM 返回不兼容”。(docs.vllm.ai)
把一次排障变成可重复回归
当两个端点都能完成一次工具调用后,再建立回归用例。下面这份清单可以直接交给客户端、网关和推理平台负责人分别执行:
- [ ] 记录目标
base_url,并断言请求没有被错误路由到另一端点。 - [ ] 用无工具调用请求确认基础聊天链路正常。
- [ ] 用单次
function calling请求确认工具定义、参数 JSON 和tool_calls能被解析。 - [ ] 保存首轮 assistant 消息的
content、推理字段和tool_calls。 - [ ] 追加工具结果时,断言
tool_call_id与首轮调用 ID 完全一致。 - [ ] 在官方 API 用缺少
reasoning_content的样本稳定复现 400。 - [ ] 在官方 API 用完整
reasoning_content回放样本验证后续请求。 - [ ] 在 vLLM 中单独记录
reasoning与reasoning_content的实际出口。 - [ ] 测试连续两次工具调用,不能只验证首轮成功。
- [ ] 端点切换后重新生成请求,不直接复用另一端点的字段名。
- [ ] 断言消息顺序、字段存在性、调用 ID 和目标 URL,而不只断言状态码。
- [ ] 保存一份删除密钥、推理全文、隐私输入和真实工具结果的复现包。
“修好了”的标准应是:官方 API 的完整字段样本连续通过,缺字段样本仍稳定失败;vLLM 端点按自身版本契约通过;两者之间的转换只发生在出口适配层。这样才能证明修复的是字段错位,而不是偶然换了模型、清空了会话或绕过了工具调用。
如果你还要检查远程环境的权限、端口和 SSH 回放方式,可以把这份请求清单与 MacHTML 帮助文档中的环境操作说明一起使用。需要核算临时测试环境成本时,再查看 MacHTML 方案与价格说明,不要把本地机器上的一次成功测试当成双端点可交付环境。
本地开发机与云端 Mac:差别在复现条件,不在“能不能发 curl”
在本地开发机上排查,优点是启动快、脚本改动直接;但它通常有三个真实缺点:官方 API 与 vLLM 的干净环境难以长期并存,代理或网关配置容易残留,权限和进程状态也可能让第二次测试与第一次不一致。
如果你需要反复验证框架升级、字段映射修复和回滚结果,临时租赁 MacHTML 的云端 Mac 测试环境会更适合做隔离回归:你可以固定一套脚本和消息样本,分别保存两个端点的环境变量与日志,再按同一顺序重放。它不适合需要长期满负载推理、物理 GPU 直通或特殊硬件接口的场景;但对需要临时保留干净工程环境、提交可脱敏复现包的团队,通常比反复清理个人开发机更容易控制变量。
真正值得保留的不是某次 400 的截图,而是能在另一台机器上重新得到同样失败、再用同一字段修复的请求链。你可以从 MacHTML 的 Mac 云端使用入口准备一套隔离环境,把官方 API 与 vLLM 的双端点脚本固定下来,再决定是否纳入团队回归流程。
用 MacHTML,快速搭建稳定的多轮接口调试环境
MacHTML 提供远程 Mac 与算力节点,适合复现 DeepSeek V4 多轮请求和定位 400 错误。 按需开通独立开发环境,无需采购设备,减少本地配置差异对测试结果的干扰。 从原始 HTTP 请求验证到字段适配层调试,都能在同一台远程 Mac 上连续完成。 现在开通 MacHTML,快速获得可用资源,把时间用在问题复现与修复上。