Skip to content

fix: strip markdown code fences before JSON parsing in structured output#1037

Open
VibhorGautam wants to merge 3 commits intoItzCrazyKns:masterfrom
VibhorGautam:fix/json-parse-markdown-fences-959
Open

fix: strip markdown code fences before JSON parsing in structured output#1037
VibhorGautam wants to merge 3 commits intoItzCrazyKns:masterfrom
VibhorGautam:fix/json-parse-markdown-fences-959

Conversation

@VibhorGautam
Copy link

@VibhorGautam VibhorGautam commented Mar 8, 2026

Summary

Fixes #959

Some LLM providers (notably Claude via OpenAI-compatible APIs and certain Ollama models) wrap their structured output in markdown code fences:

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

This causes JSON.parse and partial-json to throw parse errors, breaking generateObject() and streamObject() calls with these models.

Changes

  • Added a stripMarkdownFences() helper that removes ```json ... ``` wrappers before the content reaches repairJson / parse
  • Applied to both OpenAI and Ollama provider implementations
  • Covers both generateObject() (full response) and streamObject() (streaming partial + final)
  • Other providers (Groq, Gemini, Anthropic, LMStudio, Lemonade) extend OpenAILLM so they inherit the fix automatically

Why repairJson({ extractJson: true }) isn't enough

The existing repairJson with extractJson handles some cases but doesn't reliably strip markdown fences in all edge cases — particularly when the fence includes a language tag (json) or has varying whitespace. The explicit strip before repair makes the pipeline more robust.

How to test

  1. npm install && npm run dev, open http://localhost:3000
  2. In settings, pick a model that's known to wrap structured output in code fences (e.g. Claude via an OpenAI-compatible proxy, or an Ollama model like deepseek-r1)
  3. Send a query that triggers the search pipeline (e.g. "latest news about TypeScript") — this exercises generateObject() in the classifier
  4. Confirm the search completes without a JSON parse error in the terminal logs
  5. Also test with a standard OpenAI model (e.g. GPT-4o) to make sure the no-fence path still works — stripMarkdownFences should be a no-op when there are no fences

Some LLM providers (notably Claude via compatible APIs and certain Ollama
models) wrap their structured output in markdown code fences like
```json ... ```. This causes JSON.parse and partial-json to fail with
a parse error, breaking generateObject and streamObject calls.

Added a stripMarkdownFences helper that removes these fences before the
content reaches repairJson / parse. Applied to both OpenAI and Ollama
provider implementations in generateObject() and streamObject().

Fixes ItzCrazyKns#959
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/lib/models/providers/ollama/ollamaLLM.ts">

<violation number="1" location="src/lib/models/providers/ollama/ollamaLLM.ts:23">
P2: Fence stripping is too strict for streamed partials and misses common fence header variants, causing repeated parse failures / empty partial outputs.</violation>
</file>

<file name="src/lib/models/providers/openai/openaiLLM.ts">

<violation number="1" location="src/lib/models/providers/openai/openaiLLM.ts:272">
P2: Streaming partial parsing still breaks for fenced JSON because fence stripping only works after a closing fence is present, so most deltas parse as invalid and fall back to `{}`.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

The regex previously required a closing ``` to match, so during
streaming the opening fence stayed in the text and broke partial
JSON parsing. Now we also strip a leading-only fence when no
closing fence is present yet.
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/lib/models/providers/openai/openaiLLM.ts">

<violation number="1" location="src/lib/models/providers/openai/openaiLLM.ts:32">
P2: Regex change regressed fence stripping by requiring a newline after opening backticks, so same-line fenced JSON is no longer handled.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

The previous commit required \n after the opening backticks, which
missed cases like ```json{"key":"value"}``` where there's no newline.
Changed \n to \n? in both the full-pair and leading-only patterns.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] v1.12.0: JSON parse error with Claude models via OpenAI-compatible endpoints (LiteLLM/OpenRouter)

1 participant