-
Notifications
You must be signed in to change notification settings - Fork 36
Description
Description
When using OpenRouter with:
response_format: { type: "json_schema" }plugins: [{ id: "response-healing" }]stream: false
the model still returns JSON wrapped in Markdown code fences (e.g. json ... ), even though the Response Healing plugin documentation states that it should extract JSON from Markdown code blocks.
This behavior appears inconsistent with the documented functionality.
Expected behavior
The response-healing plugin should return clean JSON in:
choices[0].message.content
without any Markdown wrapping, so it can be directly parsed via:
JSON.parse(content)Actual behavior
The response still contains Markdown fences:
```json
{
"some_key": {
"factId": "...",
"value": "..."
}
}
This requires manual post-processing before parsing.
---
### Reproduction (raw API request)
```ts
const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "anthropic/claude-sonnet-4",
stream: false,
messages: [
{
role: "system",
content: "Return only valid JSON. Do not use markdown.",
},
{
role: "user",
content: "Extract structured data and return JSON.",
},
],
response_format: {
type: "json_schema",
json_schema: {
name: "test",
strict: true,
schema: {
type: "object",
additionalProperties: true,
},
},
},
plugins: [
{ id: "response-healing" }
],
}),
});
const data = await res.json();
console.log(data.choices?.[0]?.message?.content);
Notes
-
This happens even when:
stream: falseresponse_formatis providedresponse-healingplugin is enabled
-
The issue persists when using both:
- OpenRouter TypeScript SDK (v0.10.x)
- Raw
fetchAPI
-
Observed with model:
anthropic/claude-sonnet-4 -
Response sometimes routed via different providers (e.g. Google)
Questions
- Is this expected behavior, or a bug in the Response Healing plugin?
- Does the plugin depend on specific providers or models to function correctly?
- Are there additional parameters required to enforce JSON extraction?
Workaround
Currently, the only reliable solution is to manually strip Markdown fences:
function stripJsonFence(text: string): string {
const match = text.trim().match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
return match ? match[1].trim() : text;
}Impact
This prevents safe direct usage of structured outputs and requires extra parsing logic, reducing reliability of the plugin in production pipelines.
Thanks!