# Discourse AI: structured output response was not valid JSON

**URL:** https://meta.discourse.org/t/discourse-ai-structured-output-response-was-not-valid-json/408821
**Category:** Support
**Tags:** ai
**Created:** [July 29, 2026, 2:11pm UTC](https://meta.discourse.org/t/discourse-ai-structured-output-response-was-not-valid-json/408821 "2026-07-29T14:11:05Z")
**Posts on this page:** 11
**Page:** 1

<div class="post-metadata">

### Author: ![lilydjwg](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/lilydjwg/32/515838_2.png) [@lilydjwg](https://meta.discourse.org/u/lilydjwg)
#### Post date: [July 29, 2026, 2:11pm UTC](https://meta.discourse.org/t/discourse-ai-structured-output-response-was-not-valid-json/408821/1 "2026-07-29T14:11:05Z")

</div>

I updated yesterday\[1\] and now there are a lot of errors in the logs:

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

```

I inspected the HTTP response and it had no issue. Then I tried the text with `rails console`:

```plaintext
...
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)

```

it turns out that Discourse escaped line feed characters before parsing\[2\], causing the error.

* * *

1. 

> **[Commits · discourse/discourse](https://github.com/discourse/discourse/commits/aa3c347a7478e8a95f060c9b8e6a8dcfbcb52d83)**
>
> A platform for community discussion. Free, open, simple. - Commits · discourse/discourse

2. [code](https://github.com/discourse/discourse/blob/e1980faff446c0062b07c7bf03069feb579d2cb5/plugins/discourse-ai/lib/completions/json_streaming_tracker.rb#L28), introduced by [pr#41716](https://github.com/discourse/discourse/pull/41716)

---

<div class="post-metadata">

### Author: ![Falco](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/falco/32/179432_2.png) [@Falco](https://meta.discourse.org/u/Falco)
#### Post date: [July 29, 2026, 3:00pm UTC](https://meta.discourse.org/t/discourse-ai-structured-output-response-was-not-valid-json/408821/2 "2026-07-29T15:00:11Z")

</div>

> [@lilydjwg](#):
>
> and now there are a lot of errors in the logs:

That is a warning, not an error.

That said, I’m moving that to INFO log level, as people keep getting worried about these 😮‍💨

---

<div class="post-metadata">

### Author: ![lilydjwg](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/lilydjwg/32/515838_2.png) [@lilydjwg](https://meta.discourse.org/u/lilydjwg)
#### Post date: [July 29, 2026, 3:48pm UTC](https://meta.discourse.org/t/discourse-ai-structured-output-response-was-not-valid-json/408821/3 "2026-07-29T15:48:01Z")

</div>

> [@Falco](#):
>
> That is a warning, not an error.

Well, technically true, but it indicates a bug in the code. The fallback works so no error is raised, but it is better to fix the source instead of moving it to INFO level to cover up, isn’t it?

JSON allows line feed charaters as whitespace but doesn’t allow escape sequences outside strings; the escape function however escapes it. To fix it, you can change to escape only ASCII characters 0x0 to 0x1f inclusive according to the JSON spec. (I don’t know how to write it in Ruby so no PR.)

---

<div class="post-metadata">

### Author: ![lilydjwg](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/lilydjwg/32/515838_2.png) [@lilydjwg](https://meta.discourse.org/u/lilydjwg)
#### Post date: [July 29, 2026, 3:53pm UTC](https://meta.discourse.org/t/discourse-ai-structured-output-response-was-not-valid-json/408821/4 "2026-07-29T15:53:28Z")

</div>

To illustrate, the following _is_ valid JSON:

```json
{
  "key": "value"
}

```

The following is an escaped one, which is invalid and causing the issue:

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

```

---

<div class="post-metadata">

### Author: ![sam](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sam/32/102149_2.png) [@sam](https://meta.discourse.org/u/sam)
#### Post date: [August 5, 2026, 5:27am UTC](https://meta.discourse.org/t/discourse-ai-structured-output-response-was-not-valid-json/408821/5 "2026-08-05T05:27:16Z")

</div>

which LLM are you using?

---

<div class="post-metadata">

### Author: ![lilydjwg](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/lilydjwg/32/515838_2.png) [@lilydjwg](https://meta.discourse.org/u/lilydjwg)
#### Post date: [August 5, 2026, 5:45am UTC](https://meta.discourse.org/t/discourse-ai-structured-output-response-was-not-valid-json/408821/6 "2026-08-05T05:45:36Z")

</div>

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

---

<div class="post-metadata">

### Author: ![sam](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sam/32/102149_2.png) [@sam](https://meta.discourse.org/u/sam)
#### Post date: [August 5, 2026, 5:49am UTC](https://meta.discourse.org/t/discourse-ai-structured-output-response-was-not-valid-json/408821/7 "2026-08-05T05:49:27Z")

</div>

A 4B model is going to struggle to comply, they are getting better, but this is certainly on the older side.

If you are going 4B I probably would recommend something more recent … try Qwen 3.5-4B or Gemma 4 E4B.

---

<div class="post-metadata">

### Author: ![lilydjwg](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/lilydjwg/32/515838_2.png) [@lilydjwg](https://meta.discourse.org/u/lilydjwg)
#### Post date: [August 5, 2026, 5:54am UTC](https://meta.discourse.org/t/discourse-ai-structured-output-response-was-not-valid-json/408821/8 "2026-08-05T05:54:04Z")

</div>

I believe that llama.cpp forces the schema by picking only tokens that conforms to it. I also captured a response with tcpdump and checked that the returned response is valid JSON.

It contained spaces and line breaks outside strings however (i.e. it is a pretty-printed JSON), and those line breaks were escaped by Discourse, making it invalid (I checked it in rails console).

I’m using the spam detection feature btw.

---

<div class="post-metadata">

### Author: ![sam](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sam/32/102149_2.png) [@sam](https://meta.discourse.org/u/sam)
#### Post date: [August 5, 2026, 6:39am UTC](https://meta.discourse.org/t/discourse-ai-structured-output-response-was-not-valid-json/408821/9 "2026-08-05T06:39:51Z")

</div>

Legit, working on a fix.

---

<div class="post-metadata">

### Author: ![sam](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sam/32/102149_2.png) [@sam](https://meta.discourse.org/u/sam)
#### Post date: [August 5, 2026, 7:12am UTC](https://meta.discourse.org/t/discourse-ai-structured-output-response-was-not-valid-json/408821/10 "2026-08-05T07:12:42Z")

</div>

Fix is here:

> <https://github.com/discourse/discourse/pull/42339>
>
> Escape control characters only within JSON string values so pretty-printed forma…tting remains intact. Retain lexical state across streaming chunks to correctly handle split strings, escapes, and control characters.

---

<div class="post-metadata">

### Author: ![system](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/system/32/443519_2.png) [@system](https://meta.discourse.org/u/system)
#### Post date: [September 4, 2026, 7:13am UTC](https://meta.discourse.org/t/discourse-ai-structured-output-response-was-not-valid-json/408821/11 "2026-09-04T07:13:21Z")

</div>

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.
