# DeepSeek V4 не вызывает инструменты в локальном coding agent: DSML, parser, quant или harness?

> Диагностика tool calling по границам: сырой DSML, parser vLLM, квантованный артефакт, streaming, dispatch и история следующего запроса.

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

Локальная DeepSeek V4 может нормально обсуждать репозиторий и при этом не открыть ни одного файла. Это не один дефект. Модель должна выразить намерение в DSML, serving stack — преобразовать его в `tool_calls`, клиент — запустить нужную функцию, а следующий запрос — сохранить результат и состояние assistant.

Поэтому полезный вопрос звучит не «какой quant лучше», а **на какой границе впервые пропала структурированная команда**. Ответ находится в одной полной трассе, если не менять пять компонентов одновременно.

![Границы tool call DeepSeek V4 между моделью, parser, runtime и agent harness](https://www.aifreeapi.com/posts/ru/deepseek-v4-tool-calling-local-agent-troubleshooting/img/cover.webp)

## Один симптом скрывает разные причины

Для безопасного теста объявите простой tool без побочного эффекта, например чтение временного файла. Сохраните сырой completion до parser, JSON-ответ сервера, событие после нормализации клиентом, dispatch, результат и следующий outbound request. Перед передачей логов удалите ключи, реальные пути, аргументы и код.

| Наблюдение | Первая рабочая гипотеза | Что сравнить |
|---|---|---|
| В raw output нет invoke или теги повреждены | генерация модели, rendering prompt или обрыв | короткий context, обязательный tool, официальный artifact |
| Invoke полный, но `tool_calls` отсутствует | server parser | non-streaming, версия vLLM, V4 parser/tokenizer flags |
| Только один artifact не загружается или меняет структуру | quant/runtime compatibility | тот же request и parser, другой artifact |
| `tool_calls` есть, но tool не запускается | agent harness | нормализованное событие, permissions, schema, call ID |
| Первый tool работает, второй turn падает | replay истории | возвращённый assistant message против реально добавленного |

Такой порядок не позволяет обвинить quant в parser-only ошибке, которая воспроизводится без весов, и не позволяет «чинить» parser, если сама модель не закрыла DSML-блок.

## Что именно сообщает DSML

В [официальной карточке DeepSeek V4](https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash) указано, что релиз использует отдельную реализацию `encoding`, а не обычный Jinja chat template. Она кодирует OpenAI-подобные сообщения и разбирает текст completion. Tool intent внутри сырого вывода выглядит примерно так:

```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>
```

Клиенту coding agent обычно нужен уже структурированный объект, например имя функции и JSON arguments. Если DSML попал в видимый `content`, сначала проверьте две вещи: был ли блок корректен до parser и должен ли установленный parser распознавать именно эту ревизию encoding.

Issue vLLM [#48931](https://github.com/vllm-project/vllm/issues/48931) показывает parser-level случай без GPU: полный `invoke`, но без внешнего start wrapper, возвращается как обычный текст, а `tool_calls` остаётся пустым. Отсюда следуют две отдельные задачи. Модель или rendering могут пропустить wrapper; parser может либо безопасно восстановить однозначный блок, либо отказать. Один факт не доказывает другой.

В [#51914](https://github.com/vllm-project/vllm/issues/51914) описан другой вариант: повреждённое написание start wrapper у V4-Flash-0731 на vLLM 0.27.1 с DSpark. Автор прямо отмечает, что причина в DSpark не доказана. Корректная проверка — тот же набор запросов с DSpark ON и OFF, а не общий запрет speculative decoding.

## Parser можно проверить отдельно от агента

Сначала отключите streaming. Затем на том же запросе сравните `tool_choice="required"` и `tool_choice="auto"`. В отчёте vLLM [#40801](https://github.com/vllm-project/vllm/issues/40801) raw DSML чаще просачивался при `auto + stream`, тогда как required или non-streaming уменьшали проблему в той среде. Issue закрыт и код изменился: эти переключатели нужны для локализации ветки, а не как вечная production-настройка.

Проверьте фактическую версию внутри контейнера. Текущий vLLM обрабатывает reasoning и DSML в [специальном state machine](https://github.com/vllm-project/vllm/blob/main/vllm/parser/deepseek_v4.py). Ранее исправлялись типы параметров, вложенные `arguments` и буфер в конце stream; контекст этих исправлений сохранён в [#41240](https://github.com/vllm-project/vllm/issues/41240). Новые launch flags не превращают старый image в новую реализацию.

Минимальный gate перед запуском большого клиента может быть совсем простым:

```python
def boundary(raw, message):
    has_dsml = "<｜DSML｜invoke" in raw
    has_calls = bool(message.get("tool_calls"))
    if has_dsml and not has_calls:
        return "parser boundary failed"
    if has_calls:
        return "inspect client dispatch"
    return "inspect generation and prompt rendering"
```

Это не production parser и не механизм ремонта. Его задача — не дать UI скрыть первую потерю структуры.

![Таблица поиска первой границы, где raw DSML перестаёт быть структурированным tool call](https://www.aifreeapi.com/posts/ru/deepseek-v4-tool-calling-local-agent-troubleshooting/img/parser-boundary.webp)

## Quantization проверяют после структуры

Официальный V4-Flash уже распространяется в смешанной точности: FP4 для MoE experts и FP8 для большинства остальных параметров. Само слово «quantized» не означает неофициальный или негодный artifact.

Есть как минимум два класса проблем:

- **модель не стартует**: в [#41604](https://github.com/vllm-project/vllm/issues/41604) неканонические V4 quantizations падали при инициализации vLLM из-за отсутствующей metadata `scale_fmt`; tool loop ещё не начался;
- **меняется сгенерированная структура**: конкретный artifact при одинаковом prompt может чаще терять delimiter. Это видно только в raw output при контролируемом A/B.

Зафиксируйте model revision, encoding/tokenizer, runtime build, parser flags, sampling, context, tool schema, prompt и seed, если он поддерживается. Меняйте только artifact. Если оба варианта выдают одинаковый корректный DSML, а структура теряется после него, причиной ранней границы не является bit count.

## Почему harness проверяется на следующем turn

После появления `tool_calls` запишите, что именно получил adapter клиента, какое имя ушло в dispatcher, совпали ли arguments и call ID, разрешён ли tool политикой, и как результат попал в history. Агент может молча отфильтровать неизвестный finish reason, ожидать другую оболочку arguments или потерять связь между вызовом и результатом.

Отдельная ловушка — `reasoning_content`. В документации [hosted DeepSeek thinking mode](https://api-docs.deepseek.com/guides/thinking_mode/) сказано, что assistant turn с tool call должен полностью участвовать в последующих запросах; потеря `reasoning_content` может дать HTTP 400. Локальный OpenAI-compatible server не обязан валидировать историю точно так же. Сравнивайте поля ответа и следующего request, а не добавляйте их вслепую.

Harness считается исправным только после завершения цикла: tool получен, выполнен один раз, результат связан с тем же call, следующий запрос содержит ожидаемое состояние, а модель продолжает работу. Если минимальный SDK-клиент завершает два turn на том же endpoint, а большой coding agent нет, замена quant почти наверняка исследует не ту границу.

![Контролируемая трасса DeepSeek V4 от raw output до dispatch и следующего turn](https://www.aifreeapi.com/posts/ru/deepseek-v4-tool-calling-local-agent-troubleshooting/img/controlled-loop.webp)

## Матрица, которую можно приложить к issue

Проведите последовательные однофакторные сравнения: stream off/on, required/auto, короткий/длинный context, concurrency 1/рабочая нагрузка, официальный artifact/кандидат, минимальный loop/полный agent. Для каждого сохраняйте raw и parsed response.

Concurrency важна отдельно. В [#48089](https://github.com/vllm-project/vllm/issues/48089) один стенд vLLM 0.24.0 был чистым при последовательном запуске, но давал повреждённые структуры под нагрузкой, в том числе без streaming. Это не универсальная частота отказов, а причина всегда воспроизводить проблему сначала при concurrency 1.

В хороший отчёт входят репозиторий модели и revision, имя и hash quant, revision encoding/tokenizer, runtime, launch flags, обезличенный request, raw completion, parsed message и следующий request. Фраза «Claude Code не вызывает tools» не локализует ни одного слоя.

Текущий model/API contract разобран в [руководстве по DeepSeek V4 Pro](/ru/posts/deepseek-v4-pro). Если задача — выбрать модель для ограниченного локального железа, используйте [сравнение для local agentic coding](/ru/posts/qwen3-8-27b-local-agentic-coding). Здесь критерий уже: один вызов остаётся структурированным от текста модели до исполнения и следующего turn.
