AIFreeAPI Logo

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

A
5 min readРуководства для разработчиков

Если агент печатает план, но не запускает tool, сначала сохраните raw output, API message, client event и следующий request. Они покажут, где исчезла структура.

Границы tool call DeepSeek V4 между моделью, parser, runtime и agent harness

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

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

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

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

НаблюдениеПервая рабочая гипотезаЧто сравнить
В raw output нет invoke или теги поврежденыгенерация модели, rendering prompt или обрывкороткий context, обязательный tool, официальный artifact
Invoke полный, но tool_calls отсутствуетserver parsernon-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 указано, что релиз использует отдельную реализацию 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 показывает parser-level случай без GPU: полный invoke, но без внешнего start wrapper, возвращается как обычный текст, а tool_calls остаётся пустым. Отсюда следуют две отдельные задачи. Модель или rendering могут пропустить wrapper; parser может либо безопасно восстановить однозначный блок, либо отказать. Один факт не доказывает другой.

В #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 raw DSML чаще просачивался при auto + stream, тогда как required или non-streaming уменьшали проблему в той среде. Issue закрыт и код изменился: эти переключатели нужны для локализации ветки, а не как вечная production-настройка.

Проверьте фактическую версию внутри контейнера. Текущий vLLM обрабатывает reasoning и DSML в специальном state machine. Ранее исправлялись типы параметров, вложенные arguments и буфер в конце stream; контекст этих исправлений сохранён в #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
Таблица поиска первой границы, где raw DSML перестаёт быть структурированным tool call

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

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

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

  • модель не стартует: в #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 сказано, что 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
Контролируемая трасса DeepSeek V4 от raw output до dispatch и следующего turn

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

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

Concurrency важна отдельно. В #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. Если задача — выбрать модель для ограниченного локального железа, используйте сравнение для local agentic coding. Здесь критерий уже: один вызов остаётся структурированным от текста модели до исполнения и следующего turn.