La plantilla es trabajo de quien llama
p4 reenvía un prompt opaco y no aplica ninguna plantilla de chat. Olvídalo y el modelo responde una pregunta que no hiciste — con fluidez, y hasta el límite de tokens.
La primera respuesta real de nuestro anillo recuperado fue una aritmética correcta seguida de una conversación que nadie había tenido:
17 × 23, preguntado a un modelo servido.
" 391 What is 12 times 12? Reply with only the number. 144 What is 14"
El número está bien. Todo lo que viene después es el modelo continuando un documento, porque eso es lo que le entregamos: los mensajes aplanados en una sola cadena. Un modelo instruct lee eso como texto que extender, no como un turno que responder. Nunca emite su token de fin de turno, así que la generación llega al tope siempre.
Una omisión deliberada, no un hueco
p4 le entrega al stage server un prompt opaco y no aplica ningún formato de turno propio — el adaptador staged lleva únicamente una herramienta para *leer* una plantilla de un GGUF, nunca para aplicarla. Es una línea razonable donde trazar: el motor se mantiene estrecho y agnóstico al modelo, y quien llama, que ya sabe con qué modelo habla, renderiza el formato. Pero una línea trazada y no documentada es una línea que alguien cruza.
Leer la plantilla del archivo del modelo lo zanjó: turnos ChatML, <|im_end|> como token de fin de turno, y un turno de asistente que abre con un bloque de pensamiento. Con eso renderizado por el bridge, la misma pregunta:
El mismo modelo, el mismo anillo, con el formato de turno aplicado.
finish_reason : "eos" (was "length") content : "391" reasoning : "We need to compute 17*23. 17*20=340, plus 17*3=51, total 391."
Una pasada de pensamiento puede comerse la respuesta
Un modelo de razonamiento gasta tokens antes de decir nada. Dale un presupuesto y una pregunta difícil y puede gastarse el presupuesto entero pensando, dejando la respuesta vacía — y en una red donde quien llama ya ha pagado on-chain antes de que la petición corriera, una respuesta vacía no es un problema de calidad. Es un cobro por nada.
El gateway de liquidación ya lo sabía y pide que se desactive el pensamiento. La plantilla del modelo no tiene interruptor para eso, así que el bridge abre *y cierra* el bloque de pensamiento dentro del prompt, y el modelo escribe su respuesta después. Nos equivocamos una vez de la forma obvia: cerrar el bloque en el prompt significaba que la etiqueta de cierre ya no estaba en la salida, así que el separador archivó la respuesta entera como razonamiento y devolvió contenido vacío. Que es exactamente el fallo que ese ajuste existe para prevenir.
Dónde deja esto el contrato. El motor reenvía bytes. El bridge conoce el modelo: renderiza el formato de turno nombrado en el plan de colocación, devuelve la pasada de pensamiento como reasoning_content separada de content, y recorta una petición que pide más salida de la que el anillo se cargó para dar — porque de lo contrario una petición demasiado grande se rechaza de plano, y una respuesta más corta es mejor que un error del motor.