# This page will use the following imports:
from lasagna import Model, EventCallback, AgentRun
from lasagna import (
extract_last_message,
extraction,
flat_messages,
noop_callback,
easy_extract,
)from lasagna import known_models
from lasagna.tui import tui_input_loop
import os
from pydantic import BaseModel, Field
from dotenv import load_dotenv
Structured Output
Structured output (aka “extraction”) is the most powerful way to leverage generative AI. 💪
We need to set up our “binder” (see the quickstart guide for what this is).
load_dotenv()
if os.environ.get('OPENAI_API_KEY'):
print('Using OpenAI')
= known_models.openai_gpt_5_mini_binder
binder
elif os.environ.get('ANTHROPIC_API_KEY'):
print('Using Anthropic')
= known_models.anthropic_claude_sonnet_4_binder
binder
else:
assert False, "Neither OPENAI_API_KEY nor ANTHROPIC_API_KEY is set! We need at least one to do this demo."
Using OpenAI
The Power of Structured Output
Consider this…
- The most popular agentic use-case right now is Retrieval Augmented Generation (RAG).
- RAG is just an example of tool calling.
- Tool calling is just an example of structured output.
Structured output is the real hero behind the agentic revolution that is to come. 🤯🤯🤯
About Grammars
Generative models that support grammar-restricted generation are the best at doing structured output. Such models guarantee that your specified output schema* will be adhered to.
While formal grammars guarantee that output schemas are followed, that should not be confused with correct output.
For example, if the schema says that age
should be extracted as an integer, then you can be sure you’ll get an age
as an integer. But it may not be the correct value!
Still, grammars are much better than no grammars, so you’ll want to favor generative AI models that can follow a grammar.
Structured Output in Lasagna AI
In Lasagna AI, you specify your desired output schema as a combination of Pydantic types and TypedDict
types.
Here’s an example, using Pydantic types:
class LinguisticConstruction(BaseModel):
str = Field(description='the linguistic subject of the construction')
subject: str = Field(description='the linguistic verb of the construction')
verb: object: str = Field(description='the linguistic object of the construction')
class ExtractionModel(BaseModel):
str
summary: list[LinguisticConstruction] constructions:
Pydantic types are preferred over TypedDict
, because they let you pass string descriptions to each parameter (which, in turn, go into the model’s prompt).
Then, inside your agent, you pass your desired output type to model.extract(...)
.
See an example agent below:
async def linguistic_extraction_agent(
model: Model,
event_callback: EventCallback,list[AgentRun],
prev_runs: -> AgentRun:
) # Get **ONLY** the last message from the user:
= extract_last_message(prev_runs, from_tools=False, from_extraction=False)
last_message assert last_message['role'] == 'human'
# Do structured output over the user's message:
= await model.extract(
new_message, result
event_callback,= [last_message],
messages = ExtractionModel,
extraction_type
)assert isinstance(result, ExtractionModel)
# Wrap the new messages into an `AgentRun` result:
return extraction('linguistic_extraction_agent', [new_message], result)
= """
PROMPT Hey diddle diddle,
The cat and the fiddle,
The cow jumped over the moon;
The little dog laughed
To see such sport,
And the dish ran away with the spoon.
""".strip()
list[AgentRun] = [
prev_runs:
flat_messages('input',
[
{'role': 'human',
'text': PROMPT,
},
],
),
]
= binder(linguistic_extraction_agent)
bound_agent
= await bound_agent(noop_callback, prev_runs) # type: ignore[top-level-await]
agent_run
assert agent_run['type'] == 'extraction'
= agent_run['result']
result assert isinstance(result, ExtractionModel)
print(type(result))
print(result.summary)
for construction in result.constructions:
print(' ', construction)
<class '__main__.ExtractionModel'>
Extracted main clauses and simple subject-verb-object (or equivalent) constructions from the nursery rhyme 'Hey Diddle Diddle'. Identifies interjection, elliptical phrases, finite clauses, intransitive verbs, an infinitival clause, and a prepositional complement.
subject='(interjection)' verb='hey diddle (exclamation)' object=''
subject='the cat and the fiddle' verb='(elliptical/none)' object=''
subject='the cow' verb='jumped' object='over the moon'
subject='the little dog' verb='laughed' object=''
subject='(implicit subject for infinitive)' verb='to see' object='such sport'
subject='the dish' verb='ran away' object='with the spoon'
Easy Extraction
The steps above are designed such that you can layer agents. There’s a lot of “wrapping” and “unwrapping” that goes on.
However, that can be overkill if you only want a simple (“easy”) extraction method.
Here’s an “easy” way to do it, if you don’t care about building complex layered agents:
= await easy_extract(binder, PROMPT, ExtractionModel) # type: ignore[top-level-await]
result
assert isinstance(result, ExtractionModel)
print(type(result))
print(result.summary)
for construction in result.constructions:
print(' ', construction)
<class '__main__.ExtractionModel'>
Extract basic subject-verb-object and notable clause constructions from the nursery rhyme 'Hey Diddle Diddle'.
subject="(exclamation) 'Hey'" verb='diddle (vocative/nonsense)' object=''
subject='The cat and the fiddle' verb='' object=''
subject='The cow' verb='jumped' object='over the moon'
subject='The little dog' verb='laughed' object=''
subject='(implicit: the little dog)' verb='to see / see' object='such sport'
subject='The dish' verb='ran away' object='with the spoon'
More Opining on Structured Output
Robust software often starts with sane data formats, from which logic flows naturally. Think SQL schemas. Think algebraic data types.
Structured output is similar in spirit. You begin with the output schema, and you build your prompt around that.