Skip to main content

Known issues

This page records known behavior that may affect integrations. Each issue includes examples and a mitigation you can apply while handling model output.

JSON output may include a Markdown code fence

Diffusion generation can occasionally wrap an otherwise valid JSON object in a Markdown json code fence. In some responses, the three opening backticks are missing but the json label and closing backticks remain.

Examples

With the opening fence:

```json
{"name":"Priya","intent":"trial"}
```

With the opening backticks missing:

json
{"name":"Priya","intent":"trial"}
```

Mitigation

Request JSON with response_format on a non-streaming /chat/completions request. That path returns a value ready to parse and does not produce a fence, so prefer it whenever it is available.

The stripping below is for the cases where it is not: streaming requests, the Responses API, and requests that ask for JSON in the prompt without response_format.

Before parsing a response that should be JSON, remove the wrapper only when the entire output has one of the two prefixes above and a closing fence on its own final line. Then parse and validate the result against your expected schema. Do not use a generic character trim: that can alter legitimate JSON or string values.

content=$(jq -r '.choices[0].message.content' response.json)

cleaned=$(
printf '%s' "$content" |
perl -0pe 's/\A(?:```json|json)[ \t]*\r?\n(.*)\r?\n```[ \t]*(?:\r?\n)?\z/$1/s'
)

printf '%s\n' "$cleaned" | jq .

If parsing or schema validation still fails, follow the bounded retry and fallback guidance in Prompt engineering.

Reasoning is not separated on every request

Reasoning (enable_thinking: true) is not separated from the answer on every request. Where it is not separated, the request still succeeds and never errors, but the model's working comes back inline in content with protocol markers (a leading thought line, or a <|channel>thought…<channel|> scaffold) instead of in a separate reasoning field.

Mitigation

Leave include_reasoning at its default and read the working from message.reasoning (or a type: "reasoning" output item on the Responses API) when it is present and non-empty. When it is not, content carries the working, its protocol markers, and the answer together. There is no separate answer to read from that reply. (Under include_reasoning: false an empty reasoning is the documented shape, not this issue: check content for the markers above instead.) Whether the working is separated depends on the server that handled the request and can stay the same for long stretches, so bound any retry to one or two attempts and then send the request with thinking off, rather than parsing the markers out of content.

Reasoning after a tool result

On a turn whose last message is a tool result, with thinking on, a reply that would otherwise carry no answer text returns that text in the answer slot instead of nothing: on chat completions in message.content (with message.reasoning null on a non-streamed reply), and on the Responses API as the type: "message" item with no type: "reasoning" item.

While streaming that turn on chat completions, the same text is also emitted as delta.reasoning chunks before it lands in the answer slot, so a client that concatenates both channels renders it twice. This only happens when the working is returned; include_reasoning: false emits it once, as content. The Responses API has no suppression, so its stream also carries both copies: as reasoning events and again in the message item's text.

Mitigation

On a tool-result turn, de-duplicate. On chat completions: if the accumulated delta.reasoning equals the answer text, drop the reasoning copy and keep content. On the Responses API: keep the message item and drop the reasoning events that repeat it. Also handle an empty content on such a turn rather than relying on this recovery. See Reasoning.

reasoning, answer = "", ""
for chunk in stream:
if not chunk.choices: # the include_usage final chunk carries no choices
continue
delta = chunk.choices[0].delta
reasoning += getattr(delta, "reasoning", None) or ""
answer += delta.content or ""

if reasoning.strip() == answer.strip():
reasoning = "" # the working was replayed as the answer; render content only