Discourse AI: структурированный вывод не является валидным JSON

Я обновился вчера[1], и теперь в логах появилось множество ошибок:

Discourse AI: structured output response was not valid JSON, falling back to best-effort parsing (311 bytes) 

Я проверил HTTP-ответ, и с ним всё было в порядке. Затем я попробовал обработать текст через 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>': unexpected token "\\" (JsonCompleter::ParseError)

Оказалось, что Discourse экранирует символы перевода строки перед парсингом[2], что и вызывает ошибку.


  1. ↩︎

  2. code, добавлено в pr#41716 ↩︎

Это предупреждение, а не ошибка.

Тем не менее, я понижаю уровень этого сообщения до INFO, так как люди постоянно волнуются из-за них :face_exhaling:

Ну, технически верно, но это указывает на ошибку в коде. Фоллбэк работает, поэтому ошибка не возникает, но лучше исправить источник, а не переносить сообщение на уровень INFO, чтобы скрыть проблему, верно?

JSON позволяет использовать символы перевода строки как пробельные символы, но не допускает escape-последовательности вне строк; однако функция экранирования экранирует их. Чтобы исправить это, вы можете изменить код так, чтобы экранировались только ASCII-символы от 0x0 до 0x1f включительно, согласно спецификации JSON. (Я не знаю, как это написать на Ruby, поэтому PR не предоставляю.)

Для иллюстрации, следующее является валидным JSON:

{
  "key": "value"
}

Следующий вариант является экранированным, что делает его невалидным и вызывает проблему:

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

какую LLM вы используете?

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

Модели с 4 миллиардами параметров будут испытывать трудности с соблюдением требований. Хотя они становятся лучше, это определённо более старые решения.

Если вы всё же выбираете модель на 4 миллиарда параметров, я бы порекомендовал что-то более свежее… попробуйте Qwen 3.5-4B или Gemma 4 E4B.

Я полагаю, что llama.cpp принудительно применяет схему, выбирая только токены, соответствующие ей. Я также перехватил ответ с помощью tcpdump и проверил, что возвращаемый ответ является валидным JSON.

Однако он содержал пробелы и переносы строк вне строк (т.е. это был JSON с форматированием), и эти переносы строк были экранированы Discourse, что сделало его невалидным (я проверил это в консоли Rails).

Кстати, я использую функцию обнаружения спама.

Понятно, работаю над исправлением.

Исправление здесь: