{
"Text": "coffee liqueur",
"Type": "Liqueur",
"Liquor_type": "Liqueur",
"Name_brand": null,
"Unit_of_measure": "ounce",
"Measurement_or_unit_count": "3/4"
},
but you expected a
{
Text: string,
Type: IngredientType,
Liquor_type: LiquorType or null,
Name_brand: string or null,
Unit_of_measure: string,
Measurement_or_unit_count: string,
} class Recipe {
name string
ingredients Ingredient[]
num_ingredients int
...
// add a constraint on the type
@@assert(counts_match, {{ this.ingredients|length == this.num_ingredients }})
}
And then if you want to be very wild, put this in your prompt: {{ ctx.output_format }}
No quotes around strings
And it'll do some cool stuff class Job { company: string[] }
And the LLM happens to output: { "company": "Amazon" }
We can upcast "Amazon" -> ["Amazon"] since you indicated that in your schema. tb = TypeBuilder()
tb.Person.add_property("last_name", tb.string().list())
tb.Person.add_property("height", tb.float().optional()).description(
"Height in meters"
)
tb.Hobby.add_value("chess")
for name, val in tb.Hobby.list_values():
val.alias(name.lower())
tb.Person.add_property("hobbies", tb.Hobby.type().list()).description(
"Some suggested hobbies they might be good at"
)
# no_tb_res = await b.ExtractPeople("My name is Harrison. My hair is black and I'm 6 feet tall.")
tb_res = await b.ExtractPeople(
"My name is Harrison. My hair is black and I'm 6 feet tall. I'm pretty good around the hoop.",
{"tb": tb},
)
assert len(tb_res) > 0, "Expected non-empty result but got empty."
for r in tb_res:
print(r.model_dump()) "Existing solutions like response_format: 'json' and function calling often disappoint."
"The article compares multiple frameworks designed to handle structured output."
"Handling and preventing malformed JSON is a critical concern."
"Two main techniques for this: parsing malformed JSON or constraining LLM token generation."
"Framework comparison includes details on language support, JSON handling, prompt building, control, model providers, API flavors, type definitions, and test frameworks."
"BAML is noted for its robust handling of malformed JSON using a new Rust-based parser."
"Instructor supports multiple LLM providers but has limitations on prompt control."
"Guidance, Outlines, and others apply LLM token constraints but have limitations with models like OpenAI's."
]
that workaround we've found works quite well, but the problem is that its not sufficient to just retry in the case of failed schema matches (its both inefficient and also imo incorrect).
Take these two scenarios for example:
Scenario 1. My system is designed to output receipts, but the user does something malicious and gives me an invoice. during step 2, it fails to fit the schema, but then you try with step 3, and now you have a receipt! Its close, but your business logic is not expecting that. Often when schema alignment fails, its usually because the schema was ambiguous or the input was not valid.
Scenario 2. I ask the LLM to produce this schema:
However the person only has ever worked at 1 job. so the LLM outputs: { "name": "Vaibhav", "past_jobs": "Google" }. Technically since you know you expect an array, you could just transform the string -> string[].
thats the algorithm we created: schema-aligned parsing. More here if you're interested: https://boundaryml.com/blog/schema-aligned-parsing
Benchmark wise, when we tested last, it seems to help on top of every model (especially the smaller ones) https://www.reddit.com/r/LocalLLaMA/comments/1esd9xc/beating...
Hope this helps with some of the ambiguities in the post :)