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.
- cURL / shell
- Python
- JavaScript
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 .
from json import loads
from re import compile
FENCE_SHAPED = compile(
r"\A(?:```json|json)[ \t]*\r?\n(?P<body>[\s\S]*?)\r?\n```[ \t]*(?:\r?\n)?\Z"
)
def strip_json_fence(content: str) -> str:
match = FENCE_SHAPED.fullmatch(content)
return match.group("body") if match else content
value = loads(strip_json_fence(content))
const fenceShaped = /^(?:```json|json)[ \t]*\r?\n([\s\S]*?)\r?\n```[ \t]*(?:\r?\n)?$/;
function stripJsonFence(content) {
const match = content.match(fenceShaped);
return match ? match[1] : content;
}
const value = JSON.parse(stripJsonFence(content));
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.
- Python
- JavaScript
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
let reasoning = "";
let answer = "";
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta;
if (!delta) continue; // the include_usage final chunk carries no choices
reasoning += delta.reasoning ?? "";
answer += delta.content ?? "";
}
if (reasoning.trim() === answer.trim()) {
reasoning = ""; // the working was replayed as the answer; render content only
}