luis-otte commited on
Commit
1de807f
·
verified ·
1 Parent(s): 0309200

Add developer usage examples to model card

Browse files
Files changed (1) hide show
  1. README.md +372 -88
README.md CHANGED
@@ -13,153 +13,437 @@ base_model: Salesforce/codet5p-220m
13
  pipeline_tag: text-generation
14
  ---
15
 
16
- # StructFix V3.1 — CodeT5+ 220M (ConstraintDSL)
17
 
18
- Experimental schema-aware structured output recovery model.
19
 
20
- A 220M CodeT5+ model fine-tuned to repair broken structured outputs from agents, tool calls, and LLM workflows using **ConstraintDSL**.
 
 
 
 
 
21
 
22
- ## Key result
23
 
24
- V3.1 improves external real-case pass rate from **22% (V2) to 88%**, with **100% enum-membership** and **100% tool-format success** across evaluation splits.
25
 
26
- ## Performance
27
 
28
- | Method | External (9 cases) | Schema (unseen) | Enum Membership | Tool Format |
29
- |--------|:------------------:|:---------------:|:---------------:|:-----------:|
30
- | json-repair | — | 65.2% | — | — |
31
- | V2 (ConstraintDSL) | 2/9 (22%) | 96.3% | 0% (nonsense) | 0% |
32
- | **V3.1 (this model)** | **8/9 (88%)** | **99.3%** | **100%** | **100%** |
33
 
34
- ## Evaluation splits
35
 
36
- | Split | Schema Success | Parse | Enum Membership | Field Match | Tool Format |
37
- |-------|:--------------:|:-----:|:---------------:|:-----------:|:-----------:|
38
- | test_seen | 99.4% | 99.9% | 100% | 85.6% | 100% |
39
- | test_unseen | 99.3% | 99.7% | 100% | 85.2% | 100% |
40
- | test_random | 95.7% | 96.1% | 100% | 85.7% | 100% |
41
- | test_enum_diagnostic | 100% | 100% | 100% | 89.7% | — |
42
 
43
- ## What changed from V2 to V3.1
 
 
 
 
44
 
45
- | Problem | V2 | V3.1 |
46
- |---------|-----|------|
47
- | Enum treated as suggestion | `urgent → critical` (outside enum) | `urgent → high` (within enum) |
48
- | TOOL/ARG format unknown | 0% TOOL examples in training | 30% TOOL examples |
49
- | Field name substitution | `action → active` | Mostly resolved (80%+ random names) |
50
- | Enum constraint on nonsense input | 0% membership | 40% (nonsense) / 100% (real enums) |
51
 
52
- ### Training changes
 
 
53
 
54
- - **30% TOOL/ARG format** (was 0%)
55
- - **27% enum enforcement examples** (synonym → enum, nonsense → fallback)
56
- - **71% random hex field names** (forces structural reasoning)
57
- - **200K synthetic examples**, 3 epochs
58
 
59
- ## Validated examples (8/9 external cases pass)
 
 
 
 
 
 
60
 
61
- All examples below pass with correct enum values, field names, and types:
62
 
63
- ### Missing required field + invalid enum
 
 
 
 
64
 
65
  ```python
66
- schema = {"type": "object", "properties": {"priority": {"type": "string", "enum": ["low", "medium", "high"]}, "description": {"type": "string"}}, "required": ["priority", "description"]}
67
- broken = '{"priority":"urgent"}'
68
- # Output: {"priority":"high","description":"..."}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  ```
70
 
71
- ### Wrong types
 
 
72
 
73
  ```python
74
- schema = {"type": "object", "properties": {"priority": {"type": "string", "enum": ["low", "medium", "high"]}, "customer_id": {"type": "integer"}, "active": {"type": "boolean"}}, "required": ["priority", "customer_id", "active"]}
75
- broken = '{"priority":"medium","customer_id":"42","active":"true"}'
76
- # Output: {"priority":"medium","customer_id":42,"active":true}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  ```
78
 
79
- ### Markdown-wrapped with invalid enum
80
 
81
  ```python
82
- schema = {"type": "object", "properties": {"priority": {"type": "string", "enum": ["low", "medium", "high"]}, "description": {"type": "string"}}, "required": ["priority", "description"]}
83
- broken = 'Here is the payload:\n```json\n{"priority":"urgent"}\n```\nThanks!'
84
- # Output: {"priority":"high","description":"..."}
 
 
 
85
  ```
86
 
87
- ### Truncated object
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
  ```python
90
- schema = {"type": "object", "properties": {"status": {"type": "string", "enum": ["open", "closed", "pending"]}, "title": {"type": "string"}, "count": {"type": "integer"}}, "required": ["status", "title", "count"]}
91
- broken = '{"status":"pend", "title":"Ticket A", "count":'
92
- # Output: {"status":"pending","title":"Ticket A","count":...}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  ```
94
 
95
- ### TOOL/ARG Python syntax to JSON
96
 
97
  ```python
98
- broken = "create_ticket(priority='urgent', description='printer broken', customer_id='123')"
99
- # Output: {"priority":"high","description":"printer broken","customer_id":123}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  ```
101
 
102
- ### TOOL/ARG Partial args with missing required
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
  ```python
105
- broken = '{"priority":"urgent"}'
106
- # Output: {"priority":"high","description":"..."}
 
 
 
 
 
 
 
 
 
 
 
107
  ```
108
 
109
- ### Randomized field names with enum typo
110
 
111
  ```python
112
- broken = '{"f7a9":"gren","b31c":"9"}'
113
- # Output: {"f7a9":"green","b31c":9}
 
 
 
 
 
 
 
 
114
  ```
115
 
116
- ## Enum constraint diagnostic
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
- | Experiment | V2 | V3.1 |
119
- |------------|-----|------|
120
- | Nonsense enum + nonsense input | 0% | 40% |
121
- | Nonsense enum + real input | 0% | 30% |
122
- | **Real enum + nonsense input** | **30%** | **100%** |
 
123
 
124
- VALUES is treated as a hard constraint when enum values are recognizable. When enum values are completely foreign to the model's vocabulary, it still struggles to map arbitrary inputs to them.
125
 
126
- ## Known limitations
 
 
 
127
 
128
- - **Nonsense enum mapping**: When enum values are completely outside the model's vocabulary (e.g., `abc|def|ghi`), the model may still output non-enum values (~60% failure rate for nonsense enums).
129
- - **Text-heavy extractions**: When broken output is embedded in substantial natural language, the model may lose track of field names (field_name_match ~85-90%).
130
- - **Post-model validation required**: field_name_exact_match is ~85%, meaning downstream validation is still necessary.
131
- - **Latency**: ~500ms per example (vs 0.13ms for json-repair).
132
- - **English-only** in current version.
133
- - **Max input length**: 512 tokens.
134
 
135
- ## Training details
136
 
137
- - **Base model**: Salesforce/codet5p-220m (220M parameters)
138
- - **Training data**: 200K synthetic examples (StructFix-Bench V3.1)
139
- - **Format**: ConstraintDSL with 70% FIELD / 30% TOOL/ARG
140
- - **Field randomization**: 71% random hex field names
141
- - **Enum enforcement**: 27% of examples are enum-specific corruptions
142
- - **Epochs**: 3, **Final eval loss**: 0.090
143
- - **Batch size**: 8 x 4 gradient accumulation = 32 effective
144
- - **Learning rate**: 2e-4
 
 
 
 
145
 
146
- ## Related
147
 
148
- - [StructFix V2 (research artifact)](https://huggingface.co/ottema/structfix-codet5p-220m) previous version
149
- - [StructFix-Bench](https://huggingface.co/datasets/ottema/structfix-bench) benchmark dataset
150
- - [ConstraintDSL](https://huggingface.co/datasets/ottema/constraint-dsl) DSL specification
151
 
152
  ## Citation
153
 
154
  ```bibtex
155
- @software{structfix_v31,
156
- title = {StructFix V3.1: Schema-Aware Structured Output Recovery with ConstraintDSL},
157
  author = {Ottema},
158
  year = {2026},
159
- url = {https://huggingface.co/ottema/structfix-codet5p-220m-v3}
160
  }
161
  ```
162
 
163
  ## License
164
 
165
- Apache-2.0 (check Salesforce CodeT5+ base model license for compatibility)
 
13
  pipeline_tag: text-generation
14
  ---
15
 
16
+ # StructFix
17
 
18
+ Schema-aware structured output recovery for LLMs and agent workflows.
19
 
20
+ - Recovers invalid structured outputs
21
+ - Repairs missing required fields
22
+ - Fixes enum violations
23
+ - Validates and repairs tool-call payloads
24
+ - Handles markdown-wrapped or text-wrapped JSON
25
+ - Lightweight: 220M parameters
26
 
27
+ **91.9% schema success on unseen schemas with randomized field names.**
28
 
29
+ StructFix is a CodeT5+ 220M model fine-tuned to repair broken structured outputs using **ConstraintDSL**, a compact schema representation designed for small language models.
30
 
31
+ ![StructFix recovery flow](assets/structfix-flow.png)
32
 
33
+ ## Problem
 
 
 
 
34
 
35
+ LLM and agent outputs often look almost correct but fail validation.
36
 
37
+ Input:
 
 
 
 
 
38
 
39
+ ```json
40
+ {
41
+ "priority": "urgent"
42
+ }
43
+ ```
44
 
45
+ Constraint:
 
 
 
 
 
46
 
47
+ ```text
48
+ priority must be one of: low | medium | high
49
+ ```
50
 
51
+ Output:
 
 
 
52
 
53
+ ```json
54
+ {
55
+ "priority": "high"
56
+ }
57
+ ```
58
+
59
+ ## Quick Start
60
 
61
+ Install:
62
 
63
+ ```bash
64
+ pip install transformers torch
65
+ ```
66
+
67
+ Run inference:
68
 
69
  ```python
70
+ from transformers import AutoModelForSeq2SeqLM, AutoTokenizer
71
+
72
+ model_id = "ottema/structfix-codet5p-220m"
73
+
74
+ tokenizer = AutoTokenizer.from_pretrained(model_id)
75
+ model = AutoModelForSeq2SeqLM.from_pretrained(model_id)
76
+
77
+ dsl = """FIELD priority TYPE string VALUES low|medium|high REQUIRED yes
78
+ FIELD description TYPE string REQUIRED yes"""
79
+
80
+ broken_output = """{
81
+ "priority": "urgent"
82
+ }"""
83
+
84
+ prompt = f"""TASK repair_structured_output
85
+
86
+ SPEC
87
+ {dsl}
88
+
89
+ BROKEN_OUTPUT
90
+ {broken_output}"""
91
+
92
+ inputs = tokenizer(prompt, return_tensors="pt", max_length=512, truncation=True)
93
+ outputs = model.generate(
94
+ **inputs,
95
+ max_length=256,
96
+ num_beams=1,
97
+ do_sample=False,
98
+ )
99
+
100
+ repaired = tokenizer.decode(outputs[0], skip_special_tokens=True)
101
+ print(repaired)
102
+ ```
103
+
104
+ Example output:
105
+
106
+ ```json
107
+ {"priority":"high","description":""}
108
  ```
109
 
110
+ ## Developer Examples
111
+
112
+ ### Reusable repair helper
113
 
114
  ```python
115
+ import json
116
+ from transformers import AutoModelForSeq2SeqLM, AutoTokenizer
117
+
118
+ model_id = "ottema/structfix-codet5p-220m"
119
+ tokenizer = AutoTokenizer.from_pretrained(model_id)
120
+ model = AutoModelForSeq2SeqLM.from_pretrained(model_id)
121
+
122
+
123
+ def repair_structured_output(dsl: str, broken_output: str) -> dict:
124
+ prompt = f"""TASK repair_structured_output
125
+
126
+ SPEC
127
+ {dsl}
128
+
129
+ BROKEN_OUTPUT
130
+ {broken_output}"""
131
+
132
+ inputs = tokenizer(prompt, return_tensors="pt", max_length=512, truncation=True)
133
+ outputs = model.generate(
134
+ **inputs,
135
+ max_length=256,
136
+ num_beams=1,
137
+ do_sample=False,
138
+ )
139
+ text = tokenizer.decode(outputs[0], skip_special_tokens=True)
140
+ return json.loads(text)
141
  ```
142
 
143
+ Usage:
144
 
145
  ```python
146
+ dsl = """FIELD status TYPE string VALUES success|error|pending REQUIRED yes
147
+ FIELD result TYPE string REQUIRED yes"""
148
+
149
+ payload = '{"result":"Found 3 items"}'
150
+
151
+ print(repair_structured_output(dsl, payload))
152
  ```
153
 
154
+ Example output:
155
+
156
+ ```json
157
+ {"status":"success","result":"Found 3 items"}
158
+ ```
159
+
160
+ ### Repair and validate against JSON Schema
161
+
162
+ Use StructFix as a recovery step, then validate with your normal validator.
163
+
164
+ ```bash
165
+ pip install transformers torch jsonschema
166
+ ```
167
 
168
  ```python
169
+ import jsonschema
170
+
171
+ schema = {
172
+ "type": "object",
173
+ "properties": {
174
+ "priority": {"type": "string", "enum": ["low", "medium", "high"]},
175
+ "description": {"type": "string"},
176
+ },
177
+ "required": ["priority", "description"],
178
+ }
179
+
180
+ dsl = """FIELD priority TYPE string VALUES low|medium|high REQUIRED yes
181
+ FIELD description TYPE string REQUIRED yes"""
182
+
183
+ broken = '{"priority":"urgent"}'
184
+ repaired = repair_structured_output(dsl, broken)
185
+
186
+ jsonschema.validate(instance=repaired, schema=schema)
187
+ print(repaired)
188
+ ```
189
+
190
+ Example output:
191
+
192
+ ```json
193
+ {"priority":"high","description":""}
194
  ```
195
 
196
+ ### Repair an OpenAI-style tool call payload
197
 
198
  ```python
199
+ dsl = """TOOL create_ticket
200
+ ARG priority TYPE string VALUES low|medium|high REQUIRED yes
201
+ ARG description TYPE string REQUIRED yes
202
+ ARG customer_id TYPE integer REQUIRED no"""
203
+
204
+ broken_tool_call = """
205
+ create_ticket(priority="urgent", customer_id="42")
206
+ """
207
+
208
+ print(repair_structured_output(dsl, broken_tool_call))
209
+ ```
210
+
211
+ Example output:
212
+
213
+ ```json
214
+ {"priority":"high","description":"","customer_id":42}
215
  ```
216
 
217
+ ### Strip markdown and extra assistant text
218
+
219
+ ````python
220
+ dsl = """FIELD user_id TYPE integer REQUIRED yes
221
+ FIELD username TYPE string REQUIRED yes
222
+ FIELD active TYPE boolean REQUIRED no"""
223
+
224
+ assistant_output = """Here is the JSON:
225
+
226
+ ```json
227
+ {"user_id": "42", "username": "jdoe", "active": "true"}
228
+ ```
229
+ """
230
+
231
+ print(repair_structured_output(dsl, assistant_output))
232
+ ````
233
+
234
+ Example output:
235
+
236
+ ```json
237
+ {"user_id":42,"username":"jdoe","active":true}
238
+ ```
239
+
240
+ ### Compile JSON Schema to ConstraintDSL
241
+
242
+ This repository includes a reference compiler in `schema_compiler.py`. The core mapping is straightforward:
243
 
244
  ```python
245
+ def json_schema_to_dsl(schema: dict) -> str:
246
+ required = set(schema.get("required", []))
247
+ lines = []
248
+
249
+ for name, prop in schema.get("properties", {}).items():
250
+ typ = prop.get("type", "string")
251
+ enum = ""
252
+ if "enum" in prop:
253
+ enum = " VALUES " + "|".join(prop["enum"])
254
+ req = "yes" if name in required else "no"
255
+ lines.append(f"FIELD {name} TYPE {typ}{enum} REQUIRED {req}")
256
+
257
+ return "\n".join(lines)
258
  ```
259
 
260
+ Example:
261
 
262
  ```python
263
+ schema = {
264
+ "type": "object",
265
+ "properties": {
266
+ "priority": {"type": "string", "enum": ["low", "medium", "high"]},
267
+ "description": {"type": "string"},
268
+ },
269
+ "required": ["priority", "description"],
270
+ }
271
+
272
+ print(json_schema_to_dsl(schema))
273
  ```
274
 
275
+ Output:
276
+
277
+ ```text
278
+ FIELD priority TYPE string VALUES low|medium|high REQUIRED yes
279
+ FIELD description TYPE string REQUIRED yes
280
+ ```
281
+
282
+ ## What It Repairs
283
+
284
+ | Category | Support |
285
+ | --- | :---: |
286
+ | Missing required fields | Yes |
287
+ | Invalid enums | Yes |
288
+ | Wrong types | Yes |
289
+ | Partial tool calls | Yes |
290
+ | Markdown-wrapped JSON | Yes |
291
+ | Extra text before or after JSON | Yes |
292
+ | Truncated objects and arrays | Yes |
293
+ | Python-like tool calls | Yes |
294
+
295
+ ## When To Use It
296
+
297
+ 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.
298
+
299
+ Good fits:
300
+
301
+ - Agent tool-call argument repair
302
+ - JSON payload recovery before validation
303
+ - Enum and required-field correction
304
+ - Recovering JSON from assistant responses with prose or markdown
305
+ - Lightweight local repair before retrying an expensive model call
306
+
307
+ Not a good fit:
308
+
309
+ - Arbitrary data cleaning without a schema
310
+ - High-stakes financial, medical, legal, or regulatory corrections without human validation
311
+ - Inputs longer than the model context window
312
+ - Tasks where preserving every original field name is mandatory without post-validation
313
+
314
+ ## ConstraintDSL
315
+
316
+ StructFix does not use raw JSON Schema directly at inference time. It expects a compact line-oriented schema format called ConstraintDSL.
317
+
318
+ Example:
319
+
320
+ ```text
321
+ FIELD priority TYPE string VALUES low|medium|high REQUIRED yes
322
+ FIELD description TYPE string REQUIRED yes
323
+ FIELD customer_id TYPE integer REQUIRED no
324
+ ```
325
+
326
+ Tool-call example:
327
+
328
+ ```text
329
+ TOOL create_ticket
330
+ ARG priority TYPE string VALUES low|medium|high REQUIRED yes
331
+ ARG description TYPE string REQUIRED yes
332
+ ARG customer_id TYPE integer REQUIRED no
333
+ ```
334
+
335
+ Model input format:
336
+
337
+ ```text
338
+ TASK repair_structured_output
339
+
340
+ SPEC
341
+ FIELD priority TYPE string VALUES low|medium|high REQUIRED yes
342
+ FIELD description TYPE string REQUIRED yes
343
+
344
+ BROKEN_OUTPUT
345
+ {"priority":"urgent"}
346
+ ```
347
+
348
+ 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%**.
349
+
350
+ See [ConstraintDSL](https://huggingface.co/datasets/ottema/constraint-dsl) for the DSL specification and compiler references.
351
+
352
+ ## Results
353
+
354
+ ### Main benchmark
355
+
356
+ | Method | Schema Success |
357
+ | --- | :---: |
358
+ | json-repair | 65.2% |
359
+ | CodeT5+ + raw JSON Schema | 55.0% |
360
+ | **StructFix + ConstraintDSL** | **96.3%** |
361
+ | **StructFix + randomized fields** | **91.9%** |
362
+
363
+ ### Schema representation ablation
364
+
365
+ | Test | Schema Success |
366
+ | --- | :---: |
367
+ | Raw JSON Schema | 55.0% |
368
+ | ConstraintDSL | 96.3% |
369
+ | Randomized field names | 91.9% |
370
+
371
+ ### Per-corruption performance
372
+
373
+ Unseen schemas with random hex field names:
374
+
375
+ | Corruption | StructFix | json-repair |
376
+ | --- | :---: | :---: |
377
+ | `invalid_enum` | 96.4% | 0% |
378
+ | `missing_required` | 92.2% | 0% |
379
+ | `null_required` | 97.9% | 2.9% |
380
+ | `wrong_type` | 92.0% | 0% |
381
+ | `tool_call_partial_args` | 90.9% | 0% |
382
+ | `tool_call_python_syntax` | 90.0% | 0% |
383
+ | `tool_call_wrong_param` | 93.8% | 51.2% |
384
+ | `agent_chain` | 87.2% | 40.5% |
385
+
386
+ Latency in the benchmark was about **690 ms/example** for StructFix and **0.13 ms/example** for json-repair.
387
+
388
+ ## Known Limitations
389
+
390
+ - Field names unseen during training may be substituted by semantically similar names.
391
+ - Synonym enum repair depends on lexical similarity and field-name semantics.
392
+ - The model is English-oriented in the current version.
393
+ - Maximum input length is 512 tokens.
394
+ - Always validate the output against your schema after inference.
395
+ - Not recommended for financial, medical, legal, or regulatory corrections without human review.
396
+
397
+ Example field-name substitutions observed in showcase validation:
398
 
399
+ | DSL field name | Model output |
400
+ | --- | --- |
401
+ | `action` | `active` |
402
+ | `records_processed` | `items_processed` |
403
+ | `contract_id` | `consign_id` |
404
+ | `to` | `strand` |
405
 
406
+ ## Research Findings
407
 
408
+ - Raw JSON Schema generalized poorly for this 220M model: **55.0%** schema success.
409
+ - ConstraintDSL improved unseen-schema performance to **96.3%**.
410
+ - Randomized field names still achieved **91.9%**, suggesting the model uses explicit constraints rather than only memorized field semantics.
411
+ - Field names remain the most important DSL component in ablations.
412
 
413
+ Full benchmark details are available in [StructFix-Bench](https://huggingface.co/datasets/ottema/structfix-bench).
 
 
 
 
 
414
 
415
+ ## Training Details
416
 
417
+ | Item | Value |
418
+ | --- | --- |
419
+ | Base model | `Salesforce/codet5p-220m` |
420
+ | Parameters | 220M |
421
+ | Training data | 200K synthetic examples |
422
+ | Format | ConstraintDSL |
423
+ | Epochs | 3 |
424
+ | Effective batch size | 32 |
425
+ | Learning rate | 2e-4 |
426
+ | Final eval loss | 0.056 |
427
+ | Field-name shuffling | 50% of training examples |
428
+ | Synthetic enums | 50% of training examples |
429
 
430
+ ## Related Repositories
431
 
432
+ - [StructFix model](https://huggingface.co/ottema/structfix-codet5p-220m): this model card, focused on usage.
433
+ - [StructFix-Bench](https://huggingface.co/datasets/ottema/structfix-bench): dataset, benchmark splits, and ablations.
434
+ - [ConstraintDSL](https://huggingface.co/datasets/ottema/constraint-dsl): DSL specification and compiler references.
435
 
436
  ## Citation
437
 
438
  ```bibtex
439
+ @software{structfix_codet5p_220m,
440
+ title = {StructFix: Schema-Aware Structured Output Recovery with ConstraintDSL},
441
  author = {Ottema},
442
  year = {2026},
443
+ url = {https://huggingface.co/ottema/structfix-codet5p-220m}
444
  }
445
  ```
446
 
447
  ## License
448
 
449
+ Apache-2.0. Check the Salesforce CodeT5+ base model license for compatibility with your use case.