luis-otte commited on
Commit
178b80c
·
verified ·
1 Parent(s): 311464a

Improve developer-focused model card

Browse files
Files changed (1) hide show
  1. README.md +196 -156
README.md CHANGED
@@ -10,214 +10,254 @@ tags:
10
  - code-t5
11
  - constraint-dsl
12
  base_model: Salesforce/codet5p-220m
13
- pipeline_tag: text-generation
14
  ---
15
 
16
- # StructFix — CodeT5+ 220M (ConstraintDSL)
17
 
18
- Experimental schema-aware structured output recovery model for small language models.
19
 
20
- A 220M CodeT5+ model fine-tuned to repair broken structured outputs from agents, tool calls, and LLM workflows using **ConstraintDSL** instead of raw JSON Schema.
 
 
 
 
 
21
 
22
- ## Key result
23
 
24
- Replacing raw JSON Schema with ConstraintDSL improved unseen-schema recovery from **55.0% to 96.3%** same model, same data, same training procedure. Only the schema representation changed.
25
 
26
- With fully randomized field names (`f_8f31a7`, `f_2d88b1`, ...), performance holds at **91.9%**, demonstrating strong structural reasoning with a residual dependency on field name semantics.
27
 
28
- ## Performance
29
 
30
- | Method | Schema Success (unseen) | Params | Latency |
31
- |--------|:----------------------:|:------:|:-------:|
32
- | json-repair | 65.2% | — | 0.13ms |
33
- | CodeT5+ + JSON Schema | 55.0% | 220M | ~600ms |
34
- | **StructFix (ConstraintDSL)** | **96.3%** | 220M | ~690ms |
35
- | **StructFix (random fields)** | **91.9%** | 220M | ~690ms |
36
 
37
- ## Per-corruption performance (unseen schemas, random hex fields)
38
-
39
- | Corruption | StructFix | json-repair |
40
- |-----------|:---------:|:-----------:|
41
- | `invalid_enum` | 96.4% | 0% |
42
- | `missing_required` | 92.2% | 0% |
43
- | `null_required` | 97.9% | 2.9% |
44
- | `wrong_type` | 92.0% | 0% |
45
- | `tool_call_partial_args` | 90.9% | 0% |
46
- | `tool_call_python_syntax` | 90.0% | 0% |
47
- | `tool_call_wrong_param` | 93.8% | 51.2% |
48
- | `agent_chain` | 87.2% | 40.5% |
49
 
50
- ## Validated examples
51
 
52
- The following examples pass 100/100 inferences with 100% schema validity:
 
 
53
 
54
- ### Missing required field
55
 
56
- ```python
57
- schema = {
58
- "type": "object",
59
- "properties": {
60
- "tool": {"type": "string"},
61
- "status": {"type": "string", "enum": ["success", "error", "pending"]},
62
- "result": {"type": "string"},
63
- },
64
- "required": ["tool", "status", "result"],
65
  }
66
- broken = '{"tool":"search","result":"Found 3 items"}'
67
- # Output: {"tool":"search","status":"success","result":"Found 3 items"}
68
  ```
69
 
70
- ### Wrong type from legacy system
71
 
72
- ```python
73
- schema = {
74
- "type": "object",
75
- "properties": {
76
- "pipeline_id": {"type": "string"},
77
- "rows_affected": {"type": "integer"},
78
- "success": {"type": "boolean"},
79
- },
80
- "required": ["pipeline_id", "rows_affected", "success"],
81
- }
82
- broken = '{"pipeline_id":"ETL-42","rows_affected":"1250","success":"yes"}'
83
- # Output: {"pipeline_id":"ETL-42","rows_affected":1250,"success":true}
84
  ```
85
 
86
- ### Markdown-wrapped response
87
 
88
  ```python
89
- schema = {
90
- "type": "object",
91
- "properties": {
92
- "user_id": {"type": "integer"},
93
- "username": {"type": "string"},
94
- "email": {"type": "string"},
95
- },
96
- "required": ["user_id", "username", "email"],
97
- }
98
- broken = '```json\n{"user_id":42,"username":"jdoe","email":"jdoe@example.com"}\n```'
99
- # Output: {"user_id":42,"username":"jdoe","email":"jdoe@example.com"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  ```
101
 
102
- ### Single quotes
103
 
104
- ```python
105
- schema = {
106
- "type": "object",
107
- "properties": {
108
- "title": {"type": "string"},
109
- "year": {"type": "integer"},
110
- "category": {"type": "string", "enum": ["paper", "book", "article"]},
111
- },
112
- "required": ["title", "category"],
113
- }
114
- broken = "{'title':'Attention Is All You Need','year':2017,'category':'paper'}"
115
- # Output: {"title":"Attention Is All You Need","year":2017,"category":"paper"}
116
  ```
117
 
118
- ### Truncated object
119
 
120
- ```python
121
- schema = {
122
- "type": "object",
123
- "properties": {
124
- "product": {"type": "string"},
125
- "price": {"type": "number"},
126
- "currency": {"type": "string", "enum": ["USD", "EUR", "BRL"]},
127
- },
128
- "required": ["product", "price", "currency"],
129
- }
130
- broken = '{"product":"Widget","price":29.99,"currency":"USD"'
131
- # Output: {"product":"Widget","price":29.99,"currency":"USD"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  ```
133
 
134
- ### Thought process before JSON
135
 
136
- ```python
137
- schema = {
138
- "type": "object",
139
- "properties": {
140
- "id": {"type": "integer"},
141
- "name": {"type": "string"},
142
- "role": {"type": "string", "enum": ["admin", "user", "viewer"]},
143
- },
144
- "required": ["id", "name", "role"],
145
- }
146
- broken = 'I need to look up the user.\nLet me query the database.\n{"id":1,"name":"Alice","role":"user"}'
147
- # Output: {"id":1,"name":"Alice","role":"user"}
148
  ```
149
 
150
- ### Extra text before JSON
151
 
152
- ```python
153
- schema = {
154
- "type": "object",
155
- "properties": {
156
- "status_code": {"type": "integer"},
157
- "message": {"type": "string"},
158
- },
159
- "required": ["status_code", "message"],
160
- }
161
- broken = 'Here is the API response:\n{"status_code":200,"message":"OK"}'
162
- # Output: {"status_code":200,"message":"OK"}
163
  ```
164
 
165
- ## Schema ablation study
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
- | DSL variant | Schema Success |
168
- |------------|:--------------:|
169
- | Full ConstraintDSL | 96.3% |
170
- | No DSL at all | 78.4% (-17.9pp) |
171
- | Dummy DSL | 75.0% (-21.3pp) |
172
- | Conflicting DSL | 88.3% (-8.0pp) |
173
- | Shuffled field names | 73.4% (-22.9pp) |
174
- | Shuffled enum values | 89.8% (-6.5pp) |
175
- | Shuffled required flags | 93.2% (-3.1pp) |
176
- | Minimal DSL | 82.5% (-13.8pp) |
177
- | Field names only | 72.2% (-24.1pp) |
178
 
179
- ## Known limitations
 
 
 
 
180
 
181
- ### Field name substitution
182
 
183
- The model exhibits residual dependency on field name semantics. When processing schemas with field names that overlap with its training vocabulary, it may substitute field names with semantically similar alternatives:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
 
185
  | DSL field name | Model output |
186
- |---------------|-------------|
187
  | `action` | `active` |
188
  | `records_processed` | `items_processed` |
189
  | `contract_id` | `consign_id` |
190
  | `to` | `strand` |
191
 
192
- This was discovered through a showcase validation suite (100 inferences per example). Of 25 hand-crafted real-world examples, 7 passed at 100% schema validity and 18 exhibited field name substitution.
193
-
194
- **Root cause**: The model learned strong structural reasoning from the DSL, but retains a lexical bias toward field names seen during training. When a real-world field name like `action` partially matches a common training field like `active`, the model may override the DSL spec.
195
-
196
- **Impact**: Benchmark performance (96.3% on generated test sets) is reliable. Real-world performance depends on how much the target field names overlap with training vocabulary.
197
 
198
- ### Other limitations
 
 
 
199
 
200
- - `synonym_enum` repair: 0% with random fields — requires semantic field name understanding
201
- - Latency: ~690ms per example (vs 0.13ms for json-repair)
202
- - English-only in current version
203
- - Max input length: 512 tokens
204
 
205
- ## Training details
206
 
207
- - **Base model**: Salesforce/codet5p-220m (220M parameters)
208
- - **Training data**: 200K synthetic examples (StructFix-Bench)
209
- - **Format**: ConstraintDSL (not raw JSON Schema)
210
- - **Epochs**: 3
211
- - **Batch size**: 8 × 4 gradient accumulation = 32 effective
212
- - **Learning rate**: 2e-4
213
- - **Final eval loss**: 0.056
214
- - **Field name shuffling**: 50% of training examples use shuffled field names
215
- - **Synthetic enums**: 50% of training examples use synthetic enum values
 
 
 
216
 
217
- ## Related
218
 
219
- - [StructFix-Bench](https://huggingface.co/datasets/ottema/structfix-bench) dataset and benchmark
220
- - [ConstraintDSL](https://huggingface.co/datasets/ottema/constraint-dsl) DSL specification and compilers
 
221
 
222
  ## Citation
223
 
@@ -232,4 +272,4 @@ This was discovered through a showcase validation suite (100 inferences per exam
232
 
233
  ## License
234
 
235
- Apache-2.0 (check Salesforce CodeT5+ base model license for compatibility)
 
10
  - code-t5
11
  - constraint-dsl
12
  base_model: Salesforce/codet5p-220m
13
+ pipeline_tag: text2text-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
+ ## Problem
32
 
33
+ LLM and agent outputs often look almost correct but fail validation.
34
 
35
+ Input:
 
 
 
 
 
36
 
37
+ ```json
38
+ {
39
+ "priority": "urgent"
40
+ }
41
+ ```
 
 
 
 
 
 
 
42
 
43
+ Constraint:
44
 
45
+ ```text
46
+ priority must be one of: low | medium | high
47
+ ```
48
 
49
+ Output:
50
 
51
+ ```json
52
+ {
53
+ "priority": "high"
 
 
 
 
 
 
54
  }
 
 
55
  ```
56
 
57
+ ## Quick Start
58
 
59
+ Install:
60
+
61
+ ```bash
62
+ pip install transformers torch
 
 
 
 
 
 
 
 
63
  ```
64
 
65
+ Run inference:
66
 
67
  ```python
68
+ from transformers import AutoModelForSeq2SeqLM, AutoTokenizer
69
+
70
+ model_id = "ottema/structfix-codet5p-220m"
71
+
72
+ tokenizer = AutoTokenizer.from_pretrained(model_id)
73
+ model = AutoModelForSeq2SeqLM.from_pretrained(model_id)
74
+
75
+ dsl = """FIELD priority TYPE string VALUES low|medium|high REQUIRED yes
76
+ FIELD description TYPE string REQUIRED yes"""
77
+
78
+ broken_output = """{
79
+ "priority": "urgent"
80
+ }"""
81
+
82
+ prompt = f"""TASK repair_structured_output
83
+
84
+ SPEC
85
+ {dsl}
86
+
87
+ BROKEN_OUTPUT
88
+ {broken_output}"""
89
+
90
+ inputs = tokenizer(prompt, return_tensors="pt", max_length=512, truncation=True)
91
+ outputs = model.generate(
92
+ **inputs,
93
+ max_length=256,
94
+ num_beams=1,
95
+ do_sample=False,
96
+ )
97
+
98
+ repaired = tokenizer.decode(outputs[0], skip_special_tokens=True)
99
+ print(repaired)
100
  ```
101
 
102
+ Example output:
103
 
104
+ ```json
105
+ {"priority":"high","description":""}
 
 
 
 
 
 
 
 
 
 
106
  ```
107
 
108
+ ## What It Repairs
109
 
110
+ | Category | Support |
111
+ | --- | :---: |
112
+ | Missing required fields | Yes |
113
+ | Invalid enums | Yes |
114
+ | Wrong types | Yes |
115
+ | Partial tool calls | Yes |
116
+ | Markdown-wrapped JSON | Yes |
117
+ | Extra text before or after JSON | Yes |
118
+ | Truncated objects and arrays | Yes |
119
+ | Python-like tool calls | Yes |
120
+
121
+ ## When To Use It
122
+
123
+ 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.
124
+
125
+ Good fits:
126
+
127
+ - Agent tool-call argument repair
128
+ - JSON payload recovery before validation
129
+ - Enum and required-field correction
130
+ - Recovering JSON from assistant responses with prose or markdown
131
+ - Lightweight local repair before retrying an expensive model call
132
+
133
+ Not a good fit:
134
+
135
+ - Arbitrary data cleaning without a schema
136
+ - High-stakes financial, medical, legal, or regulatory corrections without human validation
137
+ - Inputs longer than the model context window
138
+ - Tasks where preserving every original field name is mandatory without post-validation
139
+
140
+ ## ConstraintDSL
141
+
142
+ StructFix does not use raw JSON Schema directly at inference time. It expects a compact line-oriented schema format called ConstraintDSL.
143
+
144
+ Example:
145
+
146
+ ```text
147
+ FIELD priority TYPE string VALUES low|medium|high REQUIRED yes
148
+ FIELD description TYPE string REQUIRED yes
149
+ FIELD customer_id TYPE integer REQUIRED no
150
  ```
151
 
152
+ Tool-call example:
153
 
154
+ ```text
155
+ TOOL create_ticket
156
+ ARG priority TYPE string VALUES low|medium|high REQUIRED yes
157
+ ARG description TYPE string REQUIRED yes
158
+ ARG customer_id TYPE integer REQUIRED no
 
 
 
 
 
 
 
159
  ```
160
 
161
+ Model input format:
162
 
163
+ ```text
164
+ TASK repair_structured_output
165
+
166
+ SPEC
167
+ FIELD priority TYPE string VALUES low|medium|high REQUIRED yes
168
+ FIELD description TYPE string REQUIRED yes
169
+
170
+ BROKEN_OUTPUT
171
+ {"priority":"urgent"}
 
 
172
  ```
173
 
174
+ 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%**.
175
+
176
+ See [ConstraintDSL](https://huggingface.co/datasets/ottema/constraint-dsl) for the DSL specification and compiler references.
177
+
178
+ ## Results
179
+
180
+ ### Main benchmark
181
+
182
+ | Method | Schema Success |
183
+ | --- | :---: |
184
+ | json-repair | 65.2% |
185
+ | CodeT5+ + raw JSON Schema | 55.0% |
186
+ | **StructFix + ConstraintDSL** | **96.3%** |
187
+ | **StructFix + randomized fields** | **91.9%** |
188
 
189
+ ### Schema representation ablation
 
 
 
 
 
 
 
 
 
 
190
 
191
+ | Test | Schema Success |
192
+ | --- | :---: |
193
+ | Raw JSON Schema | 55.0% |
194
+ | ConstraintDSL | 96.3% |
195
+ | Randomized field names | 91.9% |
196
 
197
+ ### Per-corruption performance
198
 
199
+ Unseen schemas with random hex field names:
200
+
201
+ | Corruption | StructFix | json-repair |
202
+ | --- | :---: | :---: |
203
+ | `invalid_enum` | 96.4% | 0% |
204
+ | `missing_required` | 92.2% | 0% |
205
+ | `null_required` | 97.9% | 2.9% |
206
+ | `wrong_type` | 92.0% | 0% |
207
+ | `tool_call_partial_args` | 90.9% | 0% |
208
+ | `tool_call_python_syntax` | 90.0% | 0% |
209
+ | `tool_call_wrong_param` | 93.8% | 51.2% |
210
+ | `agent_chain` | 87.2% | 40.5% |
211
+
212
+ Latency in the benchmark was about **690 ms/example** for StructFix and **0.13 ms/example** for json-repair.
213
+
214
+ ## Known Limitations
215
+
216
+ - Field names unseen during training may be substituted by semantically similar names.
217
+ - Synonym enum repair depends on lexical similarity and field-name semantics.
218
+ - The model is English-oriented in the current version.
219
+ - Maximum input length is 512 tokens.
220
+ - Always validate the output against your schema after inference.
221
+ - Not recommended for financial, medical, legal, or regulatory corrections without human review.
222
+
223
+ Example field-name substitutions observed in showcase validation:
224
 
225
  | DSL field name | Model output |
226
+ | --- | --- |
227
  | `action` | `active` |
228
  | `records_processed` | `items_processed` |
229
  | `contract_id` | `consign_id` |
230
  | `to` | `strand` |
231
 
232
+ ## Research Findings
 
 
 
 
233
 
234
+ - Raw JSON Schema generalized poorly for this 220M model: **55.0%** schema success.
235
+ - ConstraintDSL improved unseen-schema performance to **96.3%**.
236
+ - Randomized field names still achieved **91.9%**, suggesting the model uses explicit constraints rather than only memorized field semantics.
237
+ - Field names remain the most important DSL component in ablations.
238
 
239
+ Full benchmark details are available in [StructFix-Bench](https://huggingface.co/datasets/ottema/structfix-bench).
 
 
 
240
 
241
+ ## Training Details
242
 
243
+ | Item | Value |
244
+ | --- | --- |
245
+ | Base model | `Salesforce/codet5p-220m` |
246
+ | Parameters | 220M |
247
+ | Training data | 200K synthetic examples |
248
+ | Format | ConstraintDSL |
249
+ | Epochs | 3 |
250
+ | Effective batch size | 32 |
251
+ | Learning rate | 2e-4 |
252
+ | Final eval loss | 0.056 |
253
+ | Field-name shuffling | 50% of training examples |
254
+ | Synthetic enums | 50% of training examples |
255
 
256
+ ## Related Repositories
257
 
258
+ - [StructFix model](https://huggingface.co/ottema/structfix-codet5p-220m): this model card, focused on usage.
259
+ - [StructFix-Bench](https://huggingface.co/datasets/ottema/structfix-bench): dataset, benchmark splits, and ablations.
260
+ - [ConstraintDSL](https://huggingface.co/datasets/ottema/constraint-dsl): DSL specification and compiler references.
261
 
262
  ## Citation
263
 
 
272
 
273
  ## License
274
 
275
+ Apache-2.0. Check the Salesforce CodeT5+ base model license for compatibility with your use case.