Constrain the output format instead of parsing the mess
A team wrote weeks of fragile parsers to pull fields out of free-form model text. Asking for structured output in the first place deleted most of that code.
Constrain the output format instead of parsing the mess
A backend lead at a fintech company showed me a file he was quietly embarrassed by. It was a parser. It had grown to a few hundred lines, and it existed to pull four fields out of whatever prose the model felt like writing that day.
The model was asked to read a document and return a name, an amount, a date, and a category. It did that well. The trouble was the packaging. Some days the answer came back as a tidy sentence. Some days it came back as a bulleted list. Some days the model added a friendly preamble before getting to the point. The parser had to survive all of it.
The parser grows to match the model’s moods
Every new phrasing the model produced became a new branch in the parser. A user fed in a document that made the model write “the total appears to be” instead of “the total is,” and the extraction missed. So someone added a case for that. Then another phrasing showed up. Another case.
This is how these files grow. Each fix is small and reasonable. Nobody decides to write a fragile parser. You write one line to handle a real output you saw, and then the model shows you an output you did not see, and you write another line. The file is a record of every surprise the model handed you, and the list is never finished because the surface it draws from is natural language.
The team read this as their own sloppiness. It was not. They were trying to clean up a shape after the fact that they could have requested up front.
Shape the output at generation time
The fix was to stop asking for an answer and start asking for a shape. Instead of “read this document and tell me the total,” the request became a structured output call with a defined format: four named fields, each with a type. The model now returns those fields directly. There is no preamble to strip, no sentence to pattern-match, no list to walk.
The parser did not get better. It got deleted. What replaced it was a small validation step that checks the four fields are present and the types are right, which is real work worth keeping. The hundreds of lines that guessed at the model’s phrasing went away, because the model was no longer phrasing anything.
Here is what that shift looks like in practice:
- Define the fields, not the sentence. Name each value you need and give it a type. Ask for those, not for prose you will mine later.
- Use real structured-output constraints. Most current models support a schema or structured mode that shapes generation. Reach for that before you reach for a regex.
- Keep validation, drop extraction. You still check the fields are present and sane. You stop trying to reconstruct them from free text.
- Treat a shape violation as an error. If a required field is missing, catch it and retry or flag it. Do not paper over it with another parsing branch.
The general principle is simple. Cleaning up output after generation is a losing game, because the input to your cleanup is the full range of a language model’s phrasing. Constraining the output during generation is a winning one, because you decide the shape and the model fills it.
How we approach it at Density Labs
When we look at an AI feature in the AI Opportunity Assessment, our fixed two-week, $2,500 engagement that scopes the real production work before you build, a parser like this one is a signal. It usually means the format contract was never set, so the burden of structure landed downstream in code that guesses. We move that contract upstream. We define the shape the feature needs, request it at generation time, and add one validation gate. The pile of extraction logic tends to collapse into a handful of lines, and the feature stops breaking every time a user phrases something new.
Do not parse what you can request. Ask for the shape, and there is nothing to clean up.