Discourse AI: la respuesta de salida estructurada no era JSON válido

Actualicé ayer[1] y ahora hay muchos errores en los registros:

Discourse AI: la respuesta de salida estructurada no era JSON válido, recurriendo a análisis por mejor esfuerzo (311 bytes) 

Inspeccioné la respuesta HTTP y no presentaba ningún problema. Luego probé el texto con rails console:

...
discourse(prod)> p = JsonCompleter.new
=>
#<JsonCompleter:0x00007f48763b0270
...
discourse(prod)> p.parse text
=>
{"spam" => false,
 "reason" =>
  "..."}
discourse(prod)> p.parse DiscourseAi::Utils::BestEffortJsonParser.escape_control_characters(text)
(discourse):14:in '<main>': token inesperado "\\" (JsonCompleter::ParseError)

Resulta que Discourse escapaba los caracteres de salto de línea antes del análisis[2], lo cual provocaba el error.


  1. ↩︎

  2. código, introducido por pr#41716 ↩︎

Eso es una advertencia, no un error.

Dicho esto, voy a cambiarlo al nivel de registro INFO, porque la gente sigue preocupándose por esto :face_exhaling:

Bueno, técnicamente es cierto, pero indica un error en el código. El mecanismo de respaldo funciona, por lo que no se genera ningún error, pero es mejor corregir la causa raíz en lugar de cambiarlo al nivel INFO para encubrirlo, ¿verdad?

JSON permite caracteres de salto de línea como espacios en blanco, pero no permite secuencias de escape fuera de las cadenas; sin embargo, la función de escape las codifica. Para solucionarlo, puedes modificarla para que solo escape los caracteres ASCII de 0x0 a 0x1f inclusive, según la especificación de JSON. (No sé cómo escribirlo en Ruby, así que no haré una solicitud de extracción.)

Para ilustrarlo, lo siguiente es JSON válido:

{
  "key": "value"
}

Lo siguiente es una cadena escapada, la cual es inválida y está causando el problema:

{\u000a  "key": "value"\u000a}

¿Qué LLM estás utilizando?

llama-server -hf mradermacher/Qwen3-4B-Instruct-2507-GGUF:Q4_K_M

Un modelo de 4B tendrá dificultades para cumplir, aunque están mejorando, pero este está claramente en el lado más antiguo.

Si vas a usar uno de 4B, probablemente te recomendaría algo más reciente… prueba con Qwen 3.5-4B o Gemma 4 E4B.

Creo que llama.cpp fuerza el esquema seleccionando únicamente los tokens que lo cumplen. También capturé una respuesta con tcpdump y verifiqué que la respuesta devuelta fuera JSON válido.

Sin embargo, contenía espacios y saltos de línea fuera de las cadenas de texto (es decir, era un JSON con formato legible), y Discourse escapaba esos saltos de línea, lo que lo hacía inválido (lo verifiqué en la consola de Rails).

Por cierto, estoy utilizando la función de detección de spam.

Válido, trabajando en una solución.

La corrección está aquí: