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가 파싱 전에 줄바꿈 문자를 이스케이프 처리하고 있는 것으로 드러났습니다^[code. 이는 pr#41716에서 도입된 변경 사항으로, 이로 인해 오류가 발생하고 있습니다.


  1. ↩︎

그것은 에러가 아니라 경고입니다.

그럼에도 불구하고, 사람들이 계속 이 문제로 걱정하고 있으니 :face_exhaling: INFO 로그 레벨로 변경하겠습니다.

기술적으로는 맞지만, 이는 코드에 버그가 있음을 나타냅니다. 폴백이 작동하므로 오류가 발생하지는 않지만, INFO 레벨로 변경하여 가리는 것보다 원인을 수정하는 것이 더 좋지 않나요?

JSON은 줄바꿈 문자를 공백으로 허용하지만, 문자열 외부의 이스케이프 시퀀스는 허용하지 않습니다. 그러나 이스케이프 함수는 이를 이스케이프 처리합니다. 이를 수정하려면 JSON 사양에 따라 ASCII 문자 0x0에서 0x1f까지만 이스케이프하도록 변경할 수 있습니다. (Ruby로 어떻게 작성해야 할지 모르므로 PR은 없습니다.)

예를 들어, 아래는 유효한 JSON입니다:

{
  "key": "value"
}

아래는 이스케이프된 것으로, 유효하지 않아 문제를 일으키고 있습니다:

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

어떤 LLM을 사용하고 있나요?

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

4B 모델은 준수하기 어려울 것입니다. 성능이 좋아지고는 있지만, 이 모델은 확실히 오래된 편에 속합니다.

4B 모델을 사용할 계획이라면 좀 더 최신 모델을 권장합니다. Qwen 3.5-4B이나 Gemma 4 E4B를 시도해 보세요.

llama.cpp가 스키마에 부합하는 토큰만 선택함으로써 스키마를 강제한다고 생각합니다. 또한 tcpdump로 응답을 캡처하여 반환된 응답이 유효한 JSON인지 확인했습니다.

그러나 문자열 밖의 공백과 줄바꿈이 포함되어 있었습니다(즉, pretty-printed JSON입니다). Discourse는 이 줄바꿈을 이스케이프 처리하여 JSON을 비유효하게 만들었습니다(rails console에서 확인했습니다).

참고로 스팸 감지 기능을 사용하고 있습니다.

알겠습니다. 수정 작업 중입니다.

수정 사항이 반영되었습니다: