Yesterday the agent returned a plain string answer. What happens when you want an answer with three fields — a summary, a list of sources, and a list of keywords — all at once?
A Pydantic BaseModel with those three fields, passed as result_type? Then result.output is a Brief instance and I read .summary, .sources, .keywords directly?
Exactly. Day 13 gave you the two-field Pydantic pattern; today stretches it to three fields including a list. The class:
from pydantic import BaseModel
class Brief(BaseModel):
summary: str
sources: list
keywords: listThe agent call is identical in shape to any other result_type — Pydantic reads the class annotations, asks the model for JSON that fits, and validates.
So the agent fills in three fields in one call? Not three separate calls, one per field?
One call. The model sees the schema, generates structured JSON, and Pydantic parses it into a Brief instance. You then .model_dump() to convert the instance into a plain dict for the return. Full function:
def structured_research_brief(query: str) -> dict:
results = search(query, count=5)
context = " ".join(r["snippet"] for r in results)
urls = [r["url"] for r in results]
prompt = f"Context: {context} Produce a brief."
agent = Agent(model, result_type=Brief, system_prompt="Summarize and list keywords.")
brief = agent.run_sync(prompt).output
data = brief.model_dump()
data["sources"] = urls
return dataNotice the small twist: the sources field gets overwritten with the actual URLs you retrieved. That grounds the brief in traceable sources instead of whatever the model makes up.
Why not let the agent fill in sources too? It sees the context.
Because URLs are the one thing the agent shouldn't invent. You already have them exactly from search(). Letting the model re-emit URLs risks subtle hallucinations — slightly-wrong domains, made-up paths. Python owns the retrieval; Python owns the provenance.
So summary and keywords come from the model, sources come from my Python, and the three merge into one Brief the caller consumes.
Exactly. Split responsibility: intelligence where it helps (summary, keywords), ground truth where it matters (URLs). That's the right design shape for any structured RAG output.
TL;DR: Pydantic Brief(summary, sources, keywords) as result_type; overwrite sources with real URLs from search().
class Brief(BaseModel) — three typed fields.model_dump() — serialize to dict for mergingsources — keep the ground-truth URLs| Field | Source |
|---|---|
summary | agent |
keywords | agent |
sources | search() URLs |
Let the model write prose; let your Python own the provenance — that split keeps briefs grounded.
Yesterday the agent returned a plain string answer. What happens when you want an answer with three fields — a summary, a list of sources, and a list of keywords — all at once?
A Pydantic BaseModel with those three fields, passed as result_type? Then result.output is a Brief instance and I read .summary, .sources, .keywords directly?
Exactly. Day 13 gave you the two-field Pydantic pattern; today stretches it to three fields including a list. The class:
from pydantic import BaseModel
class Brief(BaseModel):
summary: str
sources: list
keywords: listThe agent call is identical in shape to any other result_type — Pydantic reads the class annotations, asks the model for JSON that fits, and validates.
So the agent fills in three fields in one call? Not three separate calls, one per field?
One call. The model sees the schema, generates structured JSON, and Pydantic parses it into a Brief instance. You then .model_dump() to convert the instance into a plain dict for the return. Full function:
def structured_research_brief(query: str) -> dict:
results = search(query, count=5)
context = " ".join(r["snippet"] for r in results)
urls = [r["url"] for r in results]
prompt = f"Context: {context} Produce a brief."
agent = Agent(model, result_type=Brief, system_prompt="Summarize and list keywords.")
brief = agent.run_sync(prompt).output
data = brief.model_dump()
data["sources"] = urls
return dataNotice the small twist: the sources field gets overwritten with the actual URLs you retrieved. That grounds the brief in traceable sources instead of whatever the model makes up.
Why not let the agent fill in sources too? It sees the context.
Because URLs are the one thing the agent shouldn't invent. You already have them exactly from search(). Letting the model re-emit URLs risks subtle hallucinations — slightly-wrong domains, made-up paths. Python owns the retrieval; Python owns the provenance.
So summary and keywords come from the model, sources come from my Python, and the three merge into one Brief the caller consumes.
Exactly. Split responsibility: intelligence where it helps (summary, keywords), ground truth where it matters (URLs). That's the right design shape for any structured RAG output.
TL;DR: Pydantic Brief(summary, sources, keywords) as result_type; overwrite sources with real URLs from search().
class Brief(BaseModel) — three typed fields.model_dump() — serialize to dict for mergingsources — keep the ground-truth URLs| Field | Source |
|---|---|
summary | agent |
keywords | agent |
sources | search() URLs |
Let the model write prose; let your Python own the provenance — that split keeps briefs grounded.
Create a free account to get started. Paid plans unlock all tracks.