Skip to content

Response Healing plugin does not strip Markdown code fences from JSON output #204

@dj-fiorex

Description

@dj-fiorex

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: false
    • response_format is provided
    • response-healing plugin is enabled
  • The issue persists when using both:

    • OpenRouter TypeScript SDK (v0.10.x)
    • Raw fetch API
  • Observed with model: anthropic/claude-sonnet-4

  • Response sometimes routed via different providers (e.g. Google)


Questions

  1. Is this expected behavior, or a bug in the Response Healing plugin?
  2. Does the plugin depend on specific providers or models to function correctly?
  3. 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!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions