Ajuste fino de Ministral-3 en vLLM

Guía práctica para respuestas en texto plano y formato JSON

Jeopardy-Game-Benchmark
Benchmark del Juego de Jeopardy

Lecciones aprendidas al construir una simulación de Jeopardy con IA para 12 jugadores impulsada por Ministral-3-14B-Instruct-2512.


Introducción

Ejecutar Ministral-3 en vLLM resulta sorprendentemente potente. El modelo es rápido, creativo y capaz de generar respuestas de alta calidad incluso bajo cargas de trabajo intensivas.

Pero una vez que pasas de simples indicaciones en formato chat a salidas estructuradas, automatización o uso programático, las cosas se complican rápidamente.

Durante el desarrollo de un juego de Jeopardy impulsado por IA con 12 jugadores simultáneos y cientos de llamadas al modelo, nos encontramos con varios problemas prácticos:

Esta guía resume las lecciones prácticas aprendidas al resolver estos problemas, junto con patrones concretos que puedes reutilizar en tus propios proyectos.


El Escenario del Mundo Real

Nuestro proyecto de referencia simula un juego completo de Jeopardy! donde:

Una sola ejecución del juego puede superar fácilmente 800 llamadas de API.

Este entorno expuso casos límite que rara vez aparecen en demostraciones simples — convirtiéndolo en una excelente cama de pruebas para entender cómo Ministral se comporta bajo cargas reales de producción.


1. Ejecutando Ministral-3 en vLLM

Los modelos Ministral no utilizan la configuración estándar del tokenizador de HuggingFace

Esto significa que el comando de inicio debe habilitar explícitamente el formato del tokenizador de Mistral.

bash
vllm serve mistralai/Ministral-3-14B-Instruct-2512 \
  --tokenizer_mode mistral \
  --config_format mistral \
  --load_format mistral

Si su aplicación depende de llamadas a funciones, agregue las banderas de herramientas:

bash
--enable-auto-tool-choice
--tool-call-parser mistral

Limitación importante

A diferencia de otros modelos, Ministral no admite chat_template_kwargs

Si envías una solicitud como esta:

JSON
{
  "chat_template_kwargs": {
    "enable_thinking": false
  }
}

vLLM devuelve:

Código
HTTP 400: chat_template is not supported for Mistral tokenizers

Eso significa que funciones como el ajuste explícito del modo "pensamiento" (utilizado en modelos como Qwen o DeepSeek) simplemente no están disponibles

Afortunadamente, esto rara vez es necesario porque Ministral ya genera salidas concisas por defecto.


2. Temperatura: El parámetro más importante

La documentación oficial de vLLM utiliza consistentemente el siguiente valor con Ministral-3:

Código
temperature = 0.15

A primera vista esto parece extremadamente bajo. Sin embargo, resulta ser crítico para tareas estructuradas.

¿Qué ocurre con temperaturas más altas?

Usando el valor predeterminado estilo OpenAI:

JavaScript
temperature: 0.7

el modelo se vuelve demasiado creativo con la estructura

Una solicitud sencilla como:

JSON
{ "expertise": "2-3 topics they know best" }

podría devolver algo como:

JSON
{
  "expertise": [
    {
      "category": "Gourmet Pizza Alchemy",
      "detail": "Can transform random ingredients into Michelin-star pizza"
    },
    {
      "category": "Sumo Wrestling Physics",
      "detail": "Understands body mechanics and center-of-gravity combat"
    }
  ]
}

Aunque es JSON técnicamente válido, no cumple con lo que el esquema solicitó.

El resultado:


¿Por qué 0.15 funciona mejor

A baja temperatura el modelo se vuelve estructuralmente disciplinado.

JavaScript
temperature: 0.15

Ventajas:

Incluso la generación creativa de texto sigue siendo sólida; el modelo simplemente deja de improvisar en la estructura.

Recomendación: Usa temperatura: 0.15 como valor predeterminado para Ministral-3.


3. Obtener Respuestas en JSON Limpio

Generar JSON legible por máquina desde modelos de lenguaje es más difícil de lo que parece.

Ministral tiende a interpretar los campos del esquema de manera semántica en lugar de estructuralmente, lo que genera salidas profundamente anidadas.


El enfoque ingenuo

Un prompt como:

Código
Return JSON with these fields.

genera estructuras verbosas con frecuencia.

Ejemplo de solicitud:

JSON
{ "expertise": "2-3 topics they know best" }

Respuesta típica:

JSON
{
  "expertise": [
    {
      "category": "Ancient Roman Engineering",
      "detail": "Knows aqueduct systems in surprising detail"
    },
    {
      "category": "Pizza Dough Chemistry",
      "detail": "Obsessed with yeast fermentation dynamics"
    }
  ]
}

Esto consume tres veces los tokens esperados.


La solución confiable: Prompting de dos capas

La solución más fiable combina dos instrucciones.

Capa 1 — Instrucción del sistema

Código
Respond with ONLY valid JSON.
No markdown, no explanation, no text before or after the JSON.
Keep values as short plain strings — never use nested objects or arrays.

Capa 2 — Restricción de esquema

Justo al lado de la definición del esquema:

Código
Every value MUST be a short plain string — NO arrays, NO nested objects.

Combinado con temperatura 0.15, esto genera JSON plano predecible


Presupuesto de tokens

Incluso con restricciones, Ministral tiende a producir valores más largos que otros modelos

Observación de ejemplo en nuestro benchmark:

Modelo Tokens necesarios
GPT-4o ~512
Qwen ~512
Ministral-3 ~1024

Una regla segura:

Presupuesta 1.5–2 veces los tokens para salidas en formato JSON.


Parsing defensivo de JSON

Incluso con indicaciones perfectas, los modelos generan ocasionalmente JSON mal formado.

Una buena estrategia es añadir capas de análisis defensivo.


1) Extraer JSON de formato Markdown

JavaScript
function extractJSON(raw, shape) {
  var text = raw.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/i, '').trim();
  if (shape === 'array') {
    var m = text.match(/\[[\s\S]*\]/);
    if (m) text = m[0];
  } else {
    var m = text.match(/\{[\s\S]*\}/);
    if (m) text = m[0];
  }
  return text;
}

2) Reparar salida truncada

Rastrear corchetes abiertos y cerrarlos automáticamente:

JavaScript
var stack = [];
var inStr = false, esc = false;

for (var i = 0; i < text.length; i++) {
  var ch = text[i];

  if (esc) { esc = false; continue; }
  if (ch === '\\') { esc = true; continue; }
  if (ch === '"') { inStr = !inStr; continue; }
  if (inStr) continue;

  if (ch === '{') stack.push('}');
  else if (ch === '[') stack.push(']');
  else if (ch === '}' || ch === ']') stack.pop();
}

text = text.replace(/,\s*$/, '');

while (stack.length > 0)
  text += stack.pop();

3) Aplanar valores anidados

Si el modelo sigue devolviendo estructuras anidadas:

JavaScript
if (Array.isArray(value)) {
  flat = value.map(function(item) {
    if (typeof item === 'string') return item;
    if (typeof item === 'object') return Object.values(item).join(' — ');
    return String(item);
  }).join(', ');
}

4) Reintentar solicitudes fallidas

Un bucle de reintento sencillo aumenta drásticamente la fiabilidad.

Porque Ministral se comporta de manera consistente a baja temperatura, los reintentos suelen tener éxito.

Recomendado:

Código
2–3 retry attempts

4. Obtener respuestas de texto plano limpio

Ministral adora el formato.

Incluso al solicitar texto plano, suele generar:

  • texto en negrita
  • énfasis en cursiva
  • encabezados
  • formato de código en línea

Esto ocurre porque el modelo incluye una instrucción de sistema que fomenta el formato Markdown enriquecido


¿Por qué esto importa

Muchos pipelines dependen de comprobaciones simples en cadenas.

Ejemplo:

JavaScript
verdict.toUpperCase().startsWith('CORRECT')

Pero si el modelo devuelve:

Código
**CORRECT**

la verificación falla.


Solución: Siempre eliminar el formato Markdown

El enfoque más seguro es normalizar todas las salidas antes del procesamiento.

JavaScript
function stripMarkdown(text) {
  if (!text) return text;

  var s = text.replace(/\*\*([^*]+)\*\*/g, '$1');
  s = s.replace(/__([^_]+)__/g, '$1');
  s = s.replace(/\*([^*]+)\*/g, '$1');
  s = s.replace(/^#{1,6}\s+/gm, '');
  s = s.replace(/`([^`]+)`/g, '$1');
  s = s.replace(/^```[a-z]*\s*$/gm, '');

  return s.trim();
}

Aplica esto a cada respuesta del modelo, no solo para Ministral.

Evita la ramificación específica por modelo y mantiene los flujos de trabajo consistentes.


5. Centralización de Comportamientos Específicos del Modelo

Si su sistema admite múltiples familias de modelos (como Mistral, Qwen, DeepSeek, Llama, etc.), el diseño más mantenible es centralizar el comportamiento del modelo en un solo lugar.

Ejemplo:

JavaScript
function buildModelProfile(modelName) {
  var lower = modelName.toLowerCase();
  var isMistral = lower.includes('mistral') || lower.includes('ministral');

  return {
    family: isMistral ? 'Mistral' : 'Generic',

    jsonSystemInstruction: isMistral
      ? 'Respond with ONLY valid JSON. No markdown. Keep values as short plain strings.'
      : 'You output only valid JSON. No markdown fences, no explanation.',

    jsonSchemaHint: isMistral
      ? ' Every value MUST be a short plain string — NO arrays, NO nested objects.'
      : '',

    jsonTemperature: isMistral ? 0.15 : 0.7,
    defaultTemperature: isMistral ? 0.15 : 0.7,

    plainTextInstruction: ' Do not use markdown formatting.'
  };
}

Esto permite que el resto de su sistema siga siendo model-agnostic

Añadir un nuevo modelo más adelante se vuelve trivial.


Hoja de Trucos de Mistral-3

Configuración Valor recomendado Razón
tokenizer_mode ministral Requerido para el tokenizador correcto
config_format ministral Obligatorio
load_format ministral Obligatorio
chat_template_kwargs No enviar No soportado
temperatura 0.15 Evita alucinaciones estructurales
instrucción en formato JSON Valores explícitos y planos Evitar objetos anidados
max_tokens 1.5–2× lo típico El modelo es prolijo
Eliminación de formato Markdown Siempre Evitar errores de formato
reintentos en formato JSON 2–3 intentos Recuperación confiable

Conclusiones finales

Mistral-3 rinde de manera excepcional cuando está correctamente ajustado.

Una vez que tú:

  • reducir la temperatura
  • restringir estructuras de JSON
  • normalizar la salida de Markdown
  • agregar análisis defensivo de JSON

el modelo se vuelve notablemente predecible y listo para producción

En nuestro benchmark de Jeopardy, esta configuración soportó:

  • 12 participantes de IA concurrentes
  • Más de 800 llamadas por sesión a la API
  • Más de 2,000 tokens/seg rendimiento
  • salida estructurada consistente

Todo ejecutándose localmente en la infraestructura de GPU de Trooper.AI.


Comienza

Prueba la plantilla de despliegue completo de vLLM aquí: Servidor Compatible con OpenAI de vLLM