AI 智能体

2026 DeepSeek V4 多轮 400:最小请求怎么复现?

MacHTML Lab2026.08.18 约6分钟阅读
2026 DeepSeek V4 多轮 400:最小请求怎么复现?

第一轮返回了 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_contentcontenttool_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 消息,与该消息的 contenttool_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 是否一致;
  • 响应中的 idfinish_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_contentcontenttool_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 中单独记录 reasoningreasoning_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,快速获得可用资源,把时间用在问题复现与修复上。

租用云端 Mac mini
Apple Silicon 云端 Mac