# DeepSeek V4 本地编码 Agent 不调用工具：DSML、解析器、量化还是 Harness？

> 从原始 DSML、vLLM 解析、量化权重、流式响应到 Agent 消息回放，逐层定位 DeepSeek V4 本地工具调用失效的第一个断点。

- Source: https://www.aifreeapi.com/zh/posts/deepseek-v4-tool-calling-local-agent-troubleshooting
- Language: zh
- Published: 2026-08-21
- Updated: 2026-08-21
- Publisher: AI Free API (https://www.aifreeapi.com)

本地 DeepSeek V4 能解释代码，却不肯读文件、跑命令或提交补丁，未必是模型“变笨了”。同一个界面症状，可能发生在完全不同的位置：模型没生成正确 DSML，服务端没有把 DSML 转成 `tool_calls`，量化或推理路径改变了结构 token，客户端没有派发工具，或者第二轮请求丢了必要的 assistant 状态。

排障的关键不是先换模型，而是**固定一条最小工具请求，找到结构第一次出错的边界**。一次改变多个变量，只会得到另一个无法解释的结果。

![DeepSeek V4 工具调用经过模型、解析器、运行时和 Agent harness 的故障边界](https://www.aifreeapi.com/posts/zh/deepseek-v4-tool-calling-local-agent-troubleshooting/img/cover.webp)

## 先保存一轮完整证据

选择一个无副作用、参数简单的工具，例如读取一个临时文本文件或返回固定日期。为同一次请求保存以下六项，并在分享前删除密钥、真实路径、工具参数和仓库内容：

1. 发给模型的最终 prompt 或渲染后的消息摘要；
2. parser 处理前的原始 completion text；
3. OpenAI 兼容接口返回的完整 message；
4. Agent 客户端归一化后的工具事件；
5. 实际派发的工具名、call ID 和结果；
6. 带着工具结果发出的下一次请求。

这六项能把“没有调用工具”拆成可验证的问题：

| 第一个异常位置 | 看到的现象 | 优先检查 |
|---|---|---|
| 模型原始输出 | 没有 invoke，或 DSML 标签、工具名、参数不完整 | prompt 渲染、上下文长度、采样、模型 artifact |
| 服务端 parser | 原始输出有完整 invoke，API 却只有普通 `content` | V4 tokenizer/parser 配置与 runtime 版本 |
| 量化/推理运行时 | 某 artifact 无法加载，或只有某一推理路径产生坏标签 | 保持其他条件不变做 artifact 或特性 A/B |
| Agent harness | API 已有 `tool_calls`，客户端仍不执行 | 事件归一化、权限、参数校验、派发逻辑 |
| 消息回放 | 第一轮执行成功，第二轮 400、重复调用或失忆 | assistant message、call ID、tool result 的完整性 |

## 原始 DSML 能先排除一半猜测

DeepSeek 的 [V4 官方模型卡](https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash) 明确说明，这一代没有随模型提供普通 Jinja chat template，而是通过专用 `encoding` 实现编码 OpenAI 风格消息、解析 completion。工具意图在模型文本里使用 DSML 表达，结构大致如下：

```text
<｜DSML｜tool_calls>
<｜DSML｜invoke name="read_file">
<｜DSML｜parameter name="path" string="true">src/app.ts</｜DSML｜parameter>
</｜DSML｜invoke>
</｜DSML｜tool_calls>
```

这不是让编码 Agent 自己解析的公共格式。正常链路应由服务端把它转换为结构化 `tool_calls`。因此，判断方法非常直接：

- 原始文本就没有 invoke：先查模型生成和 prompt rendering；
- 原始文本含完整 invoke，但 API message 没有 `tool_calls`：先查 parser；
- API 已有正确 `tool_calls`，客户端没动作：转向 harness；
- 工具执行后下一轮才坏：检查历史与协议回放。

vLLM issue [#48931](https://github.com/vllm-project/vllm/issues/48931) 给出了一个不需要 GPU 的 parser 级复现：当完整 invoke 缺少外层开始标记时，相关路径会把整段 DSML 当成普通内容，没有生成 `tool_calls`。这同时揭示两个不同问题——模型或渲染可能漏了 wrapper，而 parser 是否应该保守恢复是另一个决定。不能把两者合并成“量化毁了工具调用”。

另一份 [#51914](https://github.com/vllm-project/vllm/issues/51914) 报告在 V4-Flash-0731、vLLM 0.27.1 和 DSpark 环境里看到开始 wrapper 拼写异常。报告者明确写明 DSpark 因果尚未成立，正确动作是保持输入不变做 ON/OFF 对照，而不是把 speculative decoding 直接定罪。

## Parser 排查可以不经过编码 Agent

先用 `stream=false` 请求一次，再打开 streaming。对确实需要工具的同一 prompt，再比较 `tool_choice="required"` 与 `tool_choice="auto"`。vLLM 的 [#40801](https://github.com/vllm-project/vllm/issues/40801) 曾在特定环境中报告 `auto + stream` 更容易泄漏 DSML，而 required 或非流式明显改善。该 issue 已关闭，版本也在变化；这些开关适合定位分支，不应永久写成“最佳配置”。

还要核对实际容器版本，而不是只看启动命令。当前 vLLM 把 DeepSeek V4 的 reasoning 和 DSML 放进[专用 parser 状态机](https://github.com/vllm-project/vllm/blob/main/vllm/parser/deepseek_v4.py)，早期路径则经历过类型转换、`arguments` wrapper、流结束缓冲等修复，[#41240](https://github.com/vllm-project/vllm/issues/41240) 记录了这些边界。给旧镜像复制新 flag，并不会把新 parser 代码复制进去。

可以在进入复杂客户端前放一个极小的观测门：

```python
def first_bad_boundary(raw_text, api_message):
    raw_has_invoke = "<｜DSML｜invoke" in raw_text
    calls = api_message.get("tool_calls") or []
    if raw_has_invoke and not calls:
        return "server parser"
    if calls:
        return "inspect client dispatch and replay"
    return "inspect generation and prompt rendering"
```

它不是容错 DSML parser，也不要放进生产链路“修复”模型输出；它只帮助记录第一个断点。

![对照原始 DSML、结构化 tool_calls 与后续检查动作的分层诊断表](https://www.aifreeapi.com/posts/zh/deepseek-v4-tool-calling-local-agent-troubleshooting/img/parser-boundary.webp)

## 什么时候才轮到量化

官方模型卡列出的 V4-Flash 本身就是混合精度：MoE expert 使用 FP4，多数其他参数使用 FP8。因此，“出现量化”不等于“使用了不正确的第三方版本”。真正的问题是 artifact、tokenizer/encoding revision 与 runtime 是否匹配。

量化相关现象至少分两类：

- **加载兼容性**：vLLM [#41604](https://github.com/vllm-project/vllm/issues/41604) 报告某些非 canonical V4 quant 缺少 `scale_fmt` metadata，初始化时直接失败。此时尚未进入工具循环。
- **生成差异**：某个 artifact 在相同输入下更容易漏掉 DSML delimiter。这必须比较 parser 前的原始输出，不能从 Q4、AWQ、GGUF 等名字推断。

做量化 A/B 时，固定模型 revision、encoding/tokenizer、runtime build、parser flag、采样、上下文、tool schema、prompt，以及支持时的 seed。只替换 artifact。若两个 artifact 都产生相同的有效 DSML，只有其中一次 API message 丢了 `tool_calls`，最早异常仍在 parser 或其运行路径，而不是权重。

## Harness 要到第二轮成功后才能过关

服务端返回 `tool_calls` 只证明结构到达客户端。继续检查：客户端是否接受 finish reason，工具别名是否被改写，arguments 是否被二次包裹，call ID 是否保持，权限是否阻止执行，tool result 是否匹配原调用。

如果第一轮工具执行成功、下一轮才返回 400，还要分清托管 API 和本地 OpenAI 兼容服务的合同。DeepSeek 的[托管 thinking mode 文档](https://api-docs.deepseek.com/guides/thinking_mode/)要求：含工具调用的 assistant turn 需要在后续请求中完整回传 `reasoning_content`，漏掉可能得到 HTTP 400。本地服务不一定执行完全相同的校验。正确做法不是盲目补字段，而是逐字段比较服务端返回的 assistant message 与 harness 实际追加的历史。

一个最小 SDK 循环能完成两轮、完整编码 Agent 却失败时，继续换 quant 的信息价值已经很低。差异通常落在客户端事件适配、工具权限、派发或 history reconstruction。

![从模型原始输出到工具执行和下一轮请求的一次完整受控链路](https://www.aifreeapi.com/posts/zh/deepseek-v4-tool-calling-local-agent-troubleshooting/img/controlled-loop.webp)

## 用小矩阵得到可行动的结论

建议按下列顺序做单变量测试：非流式/流式、required/auto、短/长上下文、并发 1/生产并发、官方 artifact/候选 quant、最小客户端/完整 Agent。每一轮都保存原始文本与解析后 message。

并发也不能省略。vLLM [#48089](https://github.com/vllm-project/vllm/issues/48089) 在一个 0.24.0 环境中报告顺序基线正常、并发下出现结构损坏，而且非流式也有异常。这只能证明该环境存在负载相关性，不能把报告中的比例当成你的故障率；它提醒你先把并发降到 1，再判断 streaming 是否真是根因。

最终 bug report 至少包含模型仓库与 revision、quant 文件及 hash、encoding/tokenizer revision、runtime 版本、启动 flag、脱敏请求、原始 completion、解析后响应和下一轮历史。只有“Claude Code 不调用工具”无法区分任何一层。

如果你要核对模型 ID、托管 API 和 thinking 合同，转到 [DeepSeek V4 Pro 接入指南](/zh/posts/deepseek-v4-pro)；如果问题其实是有限显存下该选哪种本地 Agent 模型，参考[本地 Agent 编码模型选择](/zh/posts/qwen3-8-27b-local-agentic-coding)。当前故障的完成标准更窄：一次工具调用从模型文本到执行，再到下一轮，结构始终没有丢失。
