← JSONSuture

How to fix malformed LLM JSON without a retry loop

Structured-output features reduce JSON failures, but application boundaries still see code fences, trailing commas, unquoted keys, truncated containers, and provider-specific tool-call damage. The safe response depends on whether the failure is syntactic or semantic.

1. Preserve the original output

Do not overwrite the raw response before diagnosis. Repair should produce a separate value and a list of transformations. This keeps failures auditable without storing payloads in a third-party logging system.

2. Repair only deterministic syntax

Good candidates include removing markdown fences, quoting object keys, removing trailing commas, normalizing Python literals, and closing clearly truncated containers. Missing business facts are not syntax and must never be guessed.

{name: 'Ada', active: True,}

→

{"name":"Ada","active":true}

3. Validate against the contract

A parser can turn plain prose into a valid JSON string. That is valid JSON, but it is not a valid tool argument when your application expects an object. JSON Schema provides the necessary semantic boundary.

{
  "type": "object",
  "required": ["name", "active"],
  "properties": {
    "name": { "type": "string" },
    "active": { "type": "boolean" }
  },
  "additionalProperties": false
}

4. Retry only when information is missing

Retry the model when output is semantically incomplete, contradictory, or cut off before required content exists. Do not spend another model call merely to repair a comma or code fence. A retry can change an otherwise correct answer.

5. Fail closed around tools

Before executing a tool call, require schema validity, reject unsafe keys, cap input size and depth, and block remote schema references. Treat repaired output as untrusted input, not as trusted model intent.

When not to use JSONSuture: it is a batch request/response API, not a token-by-token streaming proxy. It also does not reconstruct missing facts. Use a streaming-specific system or model retry when those are the actual requirements.

Copy-paste boundary call

const response = await fetch(
  "https://vesper-3159a405.base44.app/functions/v1RepairJson",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.JSONSUTURE_API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ text: modelOutput, schema: toolSchema })
  }
);

const { result } = await response.json();
if (!result.schema_valid) throw new Error("Unsafe tool arguments");
await executeTool(result.data);

The reproducible benchmark documents 14 fixed repair, schema, and guardrail cases. The core adapter and tests are source-visible.

Create a free key and run a schema-validating sample

Need private implementation guidance? Email darkstorm13@gmail.com with your stack and expected JSON shape—never API keys, credentials, or private payloads.