StructFix

Schema-aware structured output recovery for LLMs and agent workflows.

  • Recovers invalid structured outputs
  • Repairs missing required fields
  • Fixes enum violations
  • Validates and repairs tool-call payloads
  • Handles markdown-wrapped or text-wrapped JSON
  • Lightweight: 220M parameters

91.9% schema success on unseen schemas with randomized field names.

StructFix is a CodeT5+ 220M model fine-tuned to repair broken structured outputs using ConstraintDSL, a compact schema representation designed for small language models.

StructFix recovery flow

Problem

LLM and agent outputs often look almost correct but fail validation.

Input:

{
  "priority": "urgent"
}

Constraint:

priority must be one of: low | medium | high

Output:

{
  "priority": "high"
}

Quick Start

Install:

pip install transformers torch

Run inference:

from transformers import AutoModelForSeq2SeqLM, AutoTokenizer

model_id = "ottema/structfix-codet5p-220m"

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForSeq2SeqLM.from_pretrained(model_id)

dsl = """FIELD priority TYPE string VALUES low|medium|high REQUIRED yes
FIELD description TYPE string REQUIRED yes"""

broken_output = """{
  "priority": "urgent"
}"""

prompt = f"""TASK repair_structured_output

SPEC
{dsl}

BROKEN_OUTPUT
{broken_output}"""

inputs = tokenizer(prompt, return_tensors="pt", max_length=512, truncation=True)
outputs = model.generate(
    **inputs,
    max_length=256,
    num_beams=1,
    do_sample=False,
)

repaired = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(repaired)

Example output:

{"priority":"high","description":""}

Developer Examples

Reusable repair helper

import json
from transformers import AutoModelForSeq2SeqLM, AutoTokenizer

model_id = "ottema/structfix-codet5p-220m"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForSeq2SeqLM.from_pretrained(model_id)


def repair_structured_output(dsl: str, broken_output: str) -> dict:
    prompt = f"""TASK repair_structured_output

SPEC
{dsl}

BROKEN_OUTPUT
{broken_output}"""

    inputs = tokenizer(prompt, return_tensors="pt", max_length=512, truncation=True)
    outputs = model.generate(
        **inputs,
        max_length=256,
        num_beams=1,
        do_sample=False,
    )
    text = tokenizer.decode(outputs[0], skip_special_tokens=True)
    return json.loads(text)

Usage:

dsl = """FIELD status TYPE string VALUES success|error|pending REQUIRED yes
FIELD result TYPE string REQUIRED yes"""

payload = '{"result":"Found 3 items"}'

print(repair_structured_output(dsl, payload))

Example output:

{"status":"success","result":"Found 3 items"}

Repair and validate against JSON Schema

Use StructFix as a recovery step, then validate with your normal validator.

pip install transformers torch jsonschema
import jsonschema

schema = {
    "type": "object",
    "properties": {
        "priority": {"type": "string", "enum": ["low", "medium", "high"]},
        "description": {"type": "string"},
    },
    "required": ["priority", "description"],
}

dsl = """FIELD priority TYPE string VALUES low|medium|high REQUIRED yes
FIELD description TYPE string REQUIRED yes"""

broken = '{"priority":"urgent"}'
repaired = repair_structured_output(dsl, broken)

jsonschema.validate(instance=repaired, schema=schema)
print(repaired)

Example output:

{"priority":"high","description":""}

Repair an OpenAI-style tool call payload

dsl = """TOOL create_ticket
ARG priority TYPE string VALUES low|medium|high REQUIRED yes
ARG description TYPE string REQUIRED yes
ARG customer_id TYPE integer REQUIRED no"""

broken_tool_call = """
create_ticket(priority="urgent", customer_id="42")
"""

print(repair_structured_output(dsl, broken_tool_call))

Example output:

{"priority":"high","description":"","customer_id":42}

Strip markdown and extra assistant text

dsl = """FIELD user_id TYPE integer REQUIRED yes
FIELD username TYPE string REQUIRED yes
FIELD active TYPE boolean REQUIRED no"""

assistant_output = """Here is the JSON:

```json
{"user_id": "42", "username": "jdoe", "active": "true"}
```
"""

print(repair_structured_output(dsl, assistant_output))

Example output:

{"user_id":42,"username":"jdoe","active":true}

Compile JSON Schema to ConstraintDSL

This repository includes a reference compiler in schema_compiler.py. The core mapping is straightforward:

def json_schema_to_dsl(schema: dict) -> str:
    required = set(schema.get("required", []))
    lines = []

    for name, prop in schema.get("properties", {}).items():
        typ = prop.get("type", "string")
        enum = ""
        if "enum" in prop:
            enum = " VALUES " + "|".join(prop["enum"])
        req = "yes" if name in required else "no"
        lines.append(f"FIELD {name} TYPE {typ}{enum} REQUIRED {req}")

    return "\n".join(lines)

Example:

schema = {
    "type": "object",
    "properties": {
        "priority": {"type": "string", "enum": ["low", "medium", "high"]},
        "description": {"type": "string"},
    },
    "required": ["priority", "description"],
}

print(json_schema_to_dsl(schema))

Output:

FIELD priority TYPE string VALUES low|medium|high REQUIRED yes
FIELD description TYPE string REQUIRED yes

What It Repairs

Category Support
Missing required fields Yes
Invalid enums Yes
Wrong types Yes
Partial tool calls Yes
Markdown-wrapped JSON Yes
Extra text before or after JSON Yes
Truncated objects and arrays Yes
Python-like tool calls Yes

When To Use It

Use StructFix when you have a schema or tool definition and need to recover a structured payload from an LLM, agent, ETL, or integration workflow.

Good fits:

  • Agent tool-call argument repair
  • JSON payload recovery before validation
  • Enum and required-field correction
  • Recovering JSON from assistant responses with prose or markdown
  • Lightweight local repair before retrying an expensive model call

Not a good fit:

  • Arbitrary data cleaning without a schema
  • High-stakes financial, medical, legal, or regulatory corrections without human validation
  • Inputs longer than the model context window
  • Tasks where preserving every original field name is mandatory without post-validation

ConstraintDSL

StructFix does not use raw JSON Schema directly at inference time. It expects a compact line-oriented schema format called ConstraintDSL.

Example:

FIELD priority TYPE string VALUES low|medium|high REQUIRED yes
FIELD description TYPE string REQUIRED yes
FIELD customer_id TYPE integer REQUIRED no

Tool-call example:

TOOL create_ticket
ARG priority TYPE string VALUES low|medium|high REQUIRED yes
ARG description TYPE string REQUIRED yes
ARG customer_id TYPE integer REQUIRED no

Model input format:

TASK repair_structured_output

SPEC
FIELD priority TYPE string VALUES low|medium|high REQUIRED yes
FIELD description TYPE string REQUIRED yes

BROKEN_OUTPUT
{"priority":"urgent"}

ConstraintDSL exists because raw JSON Schema generalized poorly in this setup. With the same base model, data, and training procedure, ConstraintDSL improved unseen-schema schema success from 55.0% to 96.3%.

See ConstraintDSL for the DSL specification and compiler references.

Results

Main benchmark

Method Schema Success
json-repair 65.2%
CodeT5+ + raw JSON Schema 55.0%
StructFix + ConstraintDSL 96.3%
StructFix + randomized fields 91.9%

Schema representation ablation

Test Schema Success
Raw JSON Schema 55.0%
ConstraintDSL 96.3%
Randomized field names 91.9%

Per-corruption performance

Unseen schemas with random hex field names:

Corruption StructFix json-repair
invalid_enum 96.4% 0%
missing_required 92.2% 0%
null_required 97.9% 2.9%
wrong_type 92.0% 0%
tool_call_partial_args 90.9% 0%
tool_call_python_syntax 90.0% 0%
tool_call_wrong_param 93.8% 51.2%
agent_chain 87.2% 40.5%

Latency in the benchmark was about 690 ms/example for StructFix and 0.13 ms/example for json-repair.

Known Limitations

  • Field names unseen during training may be substituted by semantically similar names.
  • Synonym enum repair depends on lexical similarity and field-name semantics.
  • The model is English-oriented in the current version.
  • Maximum input length is 512 tokens.
  • Always validate the output against your schema after inference.
  • Not recommended for financial, medical, legal, or regulatory corrections without human review.

Example field-name substitutions observed in showcase validation:

DSL field name Model output
action active
records_processed items_processed
contract_id consign_id
to strand

Research Findings

  • Raw JSON Schema generalized poorly for this 220M model: 55.0% schema success.
  • ConstraintDSL improved unseen-schema performance to 96.3%.
  • Randomized field names still achieved 91.9%, suggesting the model uses explicit constraints rather than only memorized field semantics.
  • Field names remain the most important DSL component in ablations.

Full benchmark details are available in StructFix-Bench.

Training Details

Item Value
Base model Salesforce/codet5p-220m
Parameters 220M
Training data 200K synthetic examples
Format ConstraintDSL
Epochs 3
Effective batch size 32
Learning rate 2e-4
Final eval loss 0.056
Field-name shuffling 50% of training examples
Synthetic enums 50% of training examples

Related Repositories

Citation

@software{structfix_codet5p_220m,
  title = {StructFix: Schema-Aware Structured Output Recovery with ConstraintDSL},
  author = {Ottema},
  year = {2026},
  url = {https://huggingface.co/ottema/structfix-codet5p-220m}
}

License

Apache-2.0. Check the Salesforce CodeT5+ base model license for compatibility with your use case.

Downloads last month
32
Safetensors
Model size
0.2B params
Tensor type
F32
ยท
Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support

Model tree for ottema/structfix-codet5p-220m

Finetuned
(97)
this model

Space using ottema/structfix-codet5p-220m 1

Collection including ottema/structfix-codet5p-220m