AIFreeAPI Logo

DeepSeek V4 no llama herramientas en agentes de código locales: DSML, parser, quant o harness

A
7 min readGuías para desarrolladores

Que el modelo conteste no demuestra que el bucle de herramientas funcione. Guarda raw output, respuesta estructurada, evento del cliente y siguiente request.

Fronteras de una llamada de DeepSeek V4 entre modelo, parser, runtime y agent harness

Un DeepSeek V4 servido en local puede comentar un repositorio con soltura y, aun así, no leer un archivo ni ejecutar un comando. El síntoma visible es único —el agente no actúa—, pero la avería puede estar en cinco sitios distintos.

El modelo genera intención de herramienta en DSML. El servidor debe convertirla en tool_calls. El cliente debe aceptar el evento, ejecutar la función y devolver el resultado. Después, el siguiente request debe conservar el estado que espera el endpoint. La manera más rápida de encontrar la causa es seguir una sola llamada y marcar la primera representación incorrecta.

La traza mínima que evita cambiarlo todo

Declara una herramienta sin efectos secundarios y con un argumento sencillo, por ejemplo leer un fichero temporal. Para el mismo intento conserva:

  1. el input final que llegó al modelo o un resumen verificable de su renderizado;
  2. el completion crudo antes del parser;
  3. el mensaje JSON que devolvió el servidor;
  4. el evento normalizado por el adaptador del agente;
  5. el dispatch, call ID y resultado reales;
  6. el siguiente request con su historial.

Elimina claves, rutas, argumentos sensibles y contenido del repositorio antes de compartir la traza.

Primera señal incorrectaCandidato principalPrueba siguiente
No aparece invoke o el DSML está rotogeneración o renderizado del promptcontexto corto, tool obligatorio, artefacto oficial
El invoke es completo pero no hay tool_callsparser del servidornon-streaming, versión y flags V4 de vLLM
Solo falla un artefacto o una ruta de inferenciaquant/runtimemantener request y parser; cambiar una sola pieza
El servidor entrega tool_calls pero no se ejecutanagent harnessinspeccionar adapter, permisos, schema y call ID
La primera herramienta funciona y el turno siguiente fallareconstrucción del historialcomparar el mensaje devuelto con el mensaje reenviado

Esta clasificación impide atribuir a la cuantización un fallo que se reproduce en una unidad de parser sin pesos ni GPU. También evita esperar que un parser repare una estructura que el modelo nunca generó.

DSML permite separar generación y parsing

La ficha oficial de DeepSeek V4 explica que el release no incluye una plantilla Jinja convencional. Usa una implementación encoding dedicada para convertir mensajes compatibles con OpenAI en input y para interpretar el texto de salida. Una llamada cruda tiene una forma parecida a esta:

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>

El agente de código no debería consumir ese marcado como texto. La capa de serving debe exponer un nombre de función y arguments estructurados. Si el bloque es correcto en raw output pero aparece dentro de content, el parser es el primer sospechoso. Si falta el wrapper de apertura o el invoke está truncado, primero hay que revisar generación, contexto y renderizado.

El issue de vLLM #48931 incluye una reproducción a nivel de parser sin GPU: un invoke completo sin el wrapper inicial se devuelve como contenido y no produce tool_calls. Esto no convierte modelo y parser en una sola causa. La omisión del wrapper sucede antes; la decisión de recuperar o rechazar un bloque huérfano pertenece al parser.

El issue #51914 describe otro caso en V4-Flash-0731 con vLLM 0.27.1 y DSpark: el wrapper inicial aparece mal escrito. El autor indica que no hay pruebas suficientes para culpar a DSpark. La comparación útil conserva los mismos requests y alterna DSpark ON/OFF.

Aísla el parser sin el cliente completo

Envía primero una respuesta con stream=false. Después, sin tocar nada más, activa streaming. En una tarea que realmente exige la herramienta, compara además tool_choice="required" con "auto".

vLLM #40801 informó de fragmentos DSML que escapaban a content sobre todo con auto + stream, y de una mejora al usar required o non-streaming en aquel entorno. El issue está cerrado y las versiones han cambiado. Esos switches sirven para localizar una rama; no son una receta permanente.

Obtén la versión real dentro del contenedor. El vLLM actual procesa reasoning y DSML de V4 con una máquina de estados específica. Rutas anteriores recibieron correcciones sobre tipos de parámetros, wrappers arguments y buffers al final del stream, documentadas en #41240. Copiar flags modernos en una imagen antigua no actualiza su código.

Un gate de observación puede ser pequeño:

python
def frontera(raw, message): hay_invoke = "<|DSML|invoke" in raw calls = message.get("tool_calls") or [] if hay_invoke and not calls: return "parser del servidor" if calls: return "dispatch o replay del cliente" return "generación o renderizado"

No lo conviertas en un parser tolerante de producción. Ejecutar marcado corrupto de forma permisiva puede aceptar nombres o arguments no declarados.

Comparación del DSML crudo y tool_calls estructurados para localizar la primera frontera rota
Comparación del DSML crudo y tool_calls estructurados para localizar la primera frontera rota

Cuándo tiene sentido comparar quants

El artefacto oficial de V4-Flash ya usa precisión mixta: FP4 en los expertos MoE y FP8 en la mayoría del resto. Por tanto, «está cuantizado» no es un diagnóstico. Hay que verificar el artefacto exacto, la revisión de encoding/tokenizer y la compatibilidad del runtime.

El issue de vLLM #41604 informa de cuantizaciones no canónicas que no arrancan porque falta metadata scale_fmt. Es un fallo de carga/runtime anterior a cualquier tool call. Otro caso distinto sería que un artefacto ya cargado cambiara la probabilidad de emitir todos los delimitadores DSML.

Para probar esto último, fija model revision, encoding y tokenizer, build del runtime, flags del parser, sampling, contexto, tool schema, prompt y seed si existe. Cambia solo el artefacto y compara raw output. Si ambos producen el mismo DSML válido pero solo una respuesta pierde tool_calls, los bits del quant no son la primera frontera rota.

El harness no pasa hasta completar el segundo turno

Cuando el servidor ya entrega una llamada estructurada, registra el nombre y los arguments que recibió el adapter, la decisión de permisos, el dispatch, el call ID y el resultado. Un cliente puede descartar un finish reason desconocido, cambiar un alias, envolver dos veces los arguments o perder la identidad de la llamada.

Si el primer tool se ejecuta y después aparece HTTP 400, separa el contrato hosted del local. La documentación del thinking mode alojado por DeepSeek exige reenviar reasoning_content del assistant turn que hizo la llamada; omitirlo puede causar 400. Un servidor local compatible con OpenAI puede validar otra forma. Compara campo por campo el mensaje recibido y el que el harness añadió al historial.

El harness solo queda descartado como causa cuando la herramienta se ejecuta una vez, el resultado conserva la identidad de la llamada y el modelo continúa en el siguiente turno. Si un bucle mínimo con SDK completa dos turnos contra el mismo endpoint y el agente completo no, el diferencial está en normalización, permisos, dispatch o history construction.

Bucle controlado desde la salida de DeepSeek V4 hasta la ejecución y el siguiente request
Bucle controlado desde la salida de DeepSeek V4 hasta la ejecución y el siguiente request

Una matriz pequeña produce un issue útil

Cambia una variable por fila: non-streaming/streaming, required/auto, contexto corto/largo, concurrencia 1/carga real, artefacto oficial/quant candidato y cliente mínimo/agente completo. Guarda raw y parsed response en cada celda.

La carga merece una prueba propia. vLLM #48089 describió un entorno 0.24.0 limpio en secuencial y con estructuras dañadas bajo concurrencia, incluso en algunos casos non-streaming. No uses sus porcentajes como tasa universal; úsalo como motivo para reproducir primero con concurrency 1.

Un reporte accionable incluye repositorio y revision del modelo, nombre y hash del quant, revision de encoding/tokenizer, versión del runtime, flags, request sin secretos, raw completion, respuesta parseada y siguiente request. «Claude Code no llama herramientas» no permite corregir ninguna capa.

Para el contrato general del modelo y la API, consulta la guía de DeepSeek V4 Pro. Si la decisión real es escoger un modelo para hardware local limitado, usa la guía de modelos para agentic coding local. Aquí el criterio es más concreto: la estructura de una llamada sobrevive desde el texto del modelo hasta la ejecución y el turno siguiente.