From 6672e914eb189aa91caf289701f8383d280d1196 Mon Sep 17 00:00:00 2001 From: builder-ujaladi Date: Sat, 18 Apr 2026 15:09:07 -0400 Subject: [PATCH] feat: add structured output tutorial (#14) Add a step-by-step tutorial covering structured output with Strands Agents: - Flat Pydantic models (sync + async) - Complex schemas (nested, lists, optional, enums, constraints) - Validation & self-correction (retry loop, exception handling, custom forcing prompt) - Tools + structured output (calculator integration) - Streaming with structured output --- .../01-learn/14-structured-output/README.md | 98 +++ .../images/architecture.png | Bin 0 -> 76259 bytes .../14-structured-output/requirements.txt | 3 + .../structured-output.ipynb | 632 ++++++++++++++++++ 4 files changed, 733 insertions(+) create mode 100644 python/01-learn/14-structured-output/README.md create mode 100644 python/01-learn/14-structured-output/images/architecture.png create mode 100644 python/01-learn/14-structured-output/requirements.txt create mode 100644 python/01-learn/14-structured-output/structured-output.ipynb diff --git a/python/01-learn/14-structured-output/README.md b/python/01-learn/14-structured-output/README.md new file mode 100644 index 00000000..e724a2a1 --- /dev/null +++ b/python/01-learn/14-structured-output/README.md @@ -0,0 +1,98 @@ +# Structured Output with Strands Agents + +This tutorial teaches you how to get reliable, typed, validated data from your Strands agents using structured output. Instead of parsing free-form text, you define a Pydantic model and the agent returns a validated Python object — ready for downstream code, APIs, and workflows. + +## Architecture + +![Structured Output Architecture](images/architecture.png) + +The agent loop registers your Pydantic model as a dynamic tool. The LLM uses regular tools first, then calls the structured output tool last. Pydantic validates the output — if validation fails, the error is sent back to the LLM for self-correction. + +## Tutorial Details + +| Information | Details | +|------------------------|----------------------------------------------------------------------| +| **Strands Features** | Structured Output, Pydantic Validation, Streaming, Tool Integration | +| **Agent Pattern** | Single agent with structured output | +| **Tools** | `calculator` (from strands-agents-tools) | +| **Model** | Claude Sonnet 4.5 on Amazon Bedrock | + +## How It Works + +1. Developer defines a Pydantic `BaseModel` describing the desired output schema +2. The model is passed to the agent via `structured_output_model=MyModel` +3. The SDK converts the Pydantic model into a tool specification and registers it as a dynamic tool +4. The LLM processes the prompt, optionally using regular tools to gather information +5. The LLM calls the structured output tool with data matching the schema +6. Pydantic validates the output — on failure, the error is sent back and the LLM self-corrects +7. On success, the validated object is available at `result.structured_output` + +## Prerequisites + +- Python 3.10 or later +- AWS account with [Amazon Bedrock](https://aws.amazon.com/bedrock/) model access configured +- [Model access](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access-modify.html) enabled for Claude Sonnet 4.5 +- Basic understanding of Python and [Pydantic](https://docs.pydantic.dev/) +- Familiarity with Strands Agents basics [(see Tutorial 01)](../01-first-agent/) + +## Tutorial Structure + +``` +14-structured-output/ +├── README.md +├── requirements.txt +├── structured-output.ipynb +└── images/ + └── architecture.png +``` + +| File | Description | +|------|-------------| +| [structured-output.ipynb](./structured-output.ipynb) | Interactive notebook covering all 5 structured output patterns | + +## What You'll Learn + +- **Your First Structured Output**: Define a flat Pydantic model, pass it to an agent, and access typed results — both sync and async +- **Complex Schemas**: Build nested models with `List`, `Optional`, field validators, and enum constraints +- **Validation & Self-Correction**: Understand the automatic retry loop when validation fails, handle `StructuredOutputException`, and customize the forcing prompt +- **Tools + Structured Output**: Combine regular tools with structured output so agents can gather data and return structured results +- **Streaming Behavior**: Use `stream_async()` and understand that structured output appears only in the final event + +## Installation + +Install the required dependencies: + +```bash +pip install -r requirements.txt +``` + +## Running the Examples + +1. Open the notebook: [structured-output.ipynb](./structured-output.ipynb) +2. Run cells sequentially — each section builds on the previous one +3. Observe the agent's structured output in each example + +> **Note:** The exact field values will vary between runs since the LLM generates content dynamically. The structure and types will always match your Pydantic model. + +## Key Concepts + +- **Structured Output**: A feature that makes agents return validated Pydantic objects instead of free-form text +- **`structured_output_model`**: The parameter (on Agent constructor or per-call) that specifies the Pydantic model to use +- **Structured Output Tool**: A dynamic tool the SDK auto-registers from your Pydantic model — the LLM calls it to produce structured data +- **Validation & Self-Correction**: When the LLM's output fails Pydantic validation, the SDK sends the error back and the LLM retries automatically +- **Forcing**: If the LLM ignores the structured output tool, the SDK forces it by restricting available tools and setting `tool_choice` +- **`StructuredOutputException`**: Raised when the LLM fails to produce valid structured output even after forcing +- **`structured_output_prompt`**: A customizable message sent to the LLM when forcing is triggered + +## Additional Resources + +- [Strands Agents Documentation](https://strandsagents.com/) +- [Structured Output User Guide](https://strandsagents.com/latest/user-guide/concepts/agents/structured-output/) +- [Pydantic Documentation](https://docs.pydantic.dev/) +- [Strands Tools Repository](https://github.com/strands-agents/tools) + +## Next Steps + +- Learn about [Memory](../06-memory/) to persist agent memory across sessions +- Explore [Agents as Tools](../10-agents-as-tools/) to compose structured output agents into larger systems +- Try [Graph Workflows](../12-graph/) to build multi-step pipelines with structured data flowing between nodes diff --git a/python/01-learn/14-structured-output/images/architecture.png b/python/01-learn/14-structured-output/images/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..df4c4c13912efe0ce979f213ecfbb5dc060ceb91 GIT binary patch literal 76259 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 g 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\n", + " \n", + "\n", + "\n", + "| Feature | Description |\n", + "|---------|-------------|\n", + "| **Flat Pydantic Models** | Define a model, pass it to the agent, and access typed results |\n", + "| **Complex Schemas** | Nested models, lists, optional fields, validators, and enums |\n", + "| **Validation & Self-Correction** | Automatic retry when validation fails, exception handling, custom forcing prompt |\n", + "| **Tools + Structured Output** | Combine tool use with structured results |\n", + "| **Streaming** | Understand where structured output fits in a streaming workflow |" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup and prerequisites\n", + "\n", + "### Prerequisites\n", + "* Python 3.10+\n", + "* AWS account\n", + "* Anthropic Claude Sonnet 4.5 enabled on Amazon Bedrock, [guide](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access-modify.html)\n", + "* Familiarity with Strands Agents basics [(see Tutorial 01)](../01-first-agent/)\n", + "\n", + "Let's now install the required packages" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!pip install -r requirements.txt -q" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Importing dependency packages\n", + "\n", + "Now let's import the dependency packages" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from enum import Enum\n", + "from typing import List, Literal, Optional\n", + "\n", + "from pydantic import BaseModel, Field, field_validator\n", + "\n", + "from strands import Agent\n", + "from strands.types.exceptions import StructuredOutputException" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Model used throughout this tutorial\n", + "MODEL_ID = \"us.anthropic.claude-sonnet-4-5-20250929-v1:0\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Your First Structured Output\n", + "\n", + "Let's start with the simplest use case: you want the agent to return data in a specific shape instead of free-form text.\n", + "\n", + "**The pattern:**\n", + "1. Define a Pydantic `BaseModel` with the fields you need\n", + "2. Pass it as `structured_output_model` when calling the agent\n", + "3. Access the typed result via `result.structured_output`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Defining a Pydantic Model\n", + "\n", + "This is a plain Pydantic model — the same kind you'd use for an API schema or database record. The `Field(description=...)` helps the LLM understand what each field expects." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class MovieReview(BaseModel):\n", + " \"\"\"A structured movie review.\"\"\"\n", + "\n", + " title: str = Field(description=\"The movie title\")\n", + " rating: int = Field(description=\"Rating from 1 to 10\")\n", + " summary: str = Field(description=\"A brief one-sentence summary of the review\")\n", + " recommend: bool = Field(description=\"Whether you would recommend this movie\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Synchronous call\n", + "\n", + "Let's pass `structured_output_model` when calling the agent. The result is a validated `MovieReview` instance — not a string, not a dict.\n", + "\n", + "> **Note:** The exact field values in the output will vary between runs since the LLM generates content dynamically. The structure and types will always match your Pydantic model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "agent = Agent(model=MODEL_ID)\n", + "\n", + "result = agent(\"Review the movie Inception by Christopher Nolan\", structured_output_model=MovieReview)\n", + "\n", + "# Access the typed result\n", + "review = result.structured_output\n", + "print(f\"Type: {type(review).__name__}\")\n", + "print(f\"Title: {review.title}\")\n", + "print(f\"Rating: {review.rating}/10\")\n", + "print(f\"Summary: {review.summary}\")\n", + "print(f\"Recommend: {review.recommend}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Asynchronous call\n", + "\n", + "The same pattern works with `invoke_async()` for async workflows." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "async def get_review_async():\n", + " agent = Agent(model=MODEL_ID)\n", + " result = await agent.invoke_async(\n", + " \"Review the movie The Matrix\",\n", + " structured_output_model=MovieReview,\n", + " )\n", + " review = result.structured_output\n", + " print(f\"[Async] {review.title}: {review.rating}/10 — {review.summary}\")\n", + "\n", + "\n", + "await get_review_async()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Complex Schemas\n", + "\n", + "Real-world data isn't flat. Let's see how structured output handles the same Pydantic features you'd use in production: nested models, lists, optional fields, constrained values, and enums." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Nested models and lists\n", + "\n", + "Let's define sub-models and compose them. The SDK converts the full schema — including nested `$ref` types — into a tool specification the LLM can understand." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class Address(BaseModel):\n", + " \"\"\"A physical address.\"\"\"\n", + "\n", + " street: str = Field(description=\"Street address\")\n", + " city: str = Field(description=\"City name\")\n", + " state: str = Field(description=\"State or province\")\n", + " country: str = Field(description=\"Country\")\n", + "\n", + "\n", + "class Skill(BaseModel):\n", + " \"\"\"A professional skill with proficiency level.\"\"\"\n", + "\n", + " name: str = Field(description=\"Skill name\")\n", + " years_experience: int = Field(description=\"Years of experience\", ge=0)\n", + "\n", + "\n", + "class Candidate(BaseModel):\n", + " \"\"\"A job candidate profile.\"\"\"\n", + "\n", + " name: str = Field(description=\"Full name\")\n", + " address: Address = Field(description=\"Home address\")\n", + " skills: List[Skill] = Field(description=\"List of professional skills\")\n", + " bio: Optional[str] = Field(default=None, description=\"Short bio, if available\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "agent = Agent(model=MODEL_ID)\n", + "\n", + "result = agent(\n", + " \"Create a profile for a fictional senior software engineer based in Seattle with 3 skills\",\n", + " structured_output_model=Candidate,\n", + ")\n", + "\n", + "candidate = result.structured_output\n", + "print(f\"Name: {candidate.name}\")\n", + "print(f\"Location: {candidate.address.city}, {candidate.address.state}\")\n", + "print(f\"Bio: {candidate.bio}\")\n", + "print(\"Skills:\")\n", + "for skill in candidate.skills:\n", + " print(f\" - {skill.name}: {skill.years_experience} years\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Field constraints and enums\n", + "\n", + "We can use Pydantic's `Field` constraints (`ge`, `le`, `min_length`, etc.) and `Literal`/`Enum` types to restrict valid values. The LLM sees these constraints in the tool schema and respects them." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class Priority(str, Enum):\n", + " \"\"\"Task priority levels.\"\"\"\n", + "\n", + " LOW = \"low\"\n", + " MEDIUM = \"medium\"\n", + " HIGH = \"high\"\n", + " CRITICAL = \"critical\"\n", + "\n", + "\n", + "class TicketAnalysis(BaseModel):\n", + " \"\"\"Analysis of a customer support ticket.\"\"\"\n", + "\n", + " category: str = Field(description=\"Issue category\", min_length=1)\n", + " sentiment: Literal[\"positive\", \"negative\", \"neutral\"] = Field(description=\"Customer sentiment\")\n", + " priority: Priority = Field(description=\"Ticket priority\")\n", + " confidence: float = Field(description=\"Confidence score\", ge=0.0, le=1.0)\n", + " summary: str = Field(description=\"Brief summary of the issue\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "agent = Agent(model=MODEL_ID)\n", + "\n", + "ticket_text = \"\"\"\n", + "I've been trying to reset my password for 3 days now and the reset email never arrives.\n", + "I've checked spam. This is blocking my entire team from accessing the dashboard.\n", + "We're paying for the enterprise plan and this level of service is unacceptable.\n", + "\"\"\"\n", + "\n", + "result = agent(\n", + " f\"Analyze this support ticket:\\n{ticket_text}\",\n", + " structured_output_model=TicketAnalysis,\n", + ")\n", + "\n", + "analysis = result.structured_output\n", + "print(f\"Category: {analysis.category}\")\n", + "print(f\"Sentiment: {analysis.sentiment}\")\n", + "print(f\"Priority: {analysis.priority.value}\")\n", + "print(f\"Confidence: {analysis.confidence:.0%}\")\n", + "print(f\"Summary: {analysis.summary}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Validation & Self-Correction\n", + "\n", + "What happens when the LLM returns data that doesn't pass Pydantic validation?\n", + "\n", + "The SDK handles this automatically:\n", + "1. The LLM calls the structured output tool with its data\n", + "2. Pydantic validates the data — if it fails, the SDK formats a per-field error message\n", + "3. The error is sent back to the LLM as a tool result\n", + "4. The LLM reads the error and self-corrects on the next attempt\n", + "\n", + "This happens within the normal agent loop — no extra code needed." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Triggering the retry loop\n", + "\n", + "Let's define a model with a strict `@field_validator` that the LLM is likely to violate on its first attempt. This lets us observe the self-correction cycle." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class StrictCode(BaseModel):\n", + " \"\"\"A product code that must follow a strict format.\"\"\"\n", + "\n", + " product_name: str = Field(description=\"Name of the product\")\n", + " product_code: str = Field(description=\"Product code in format: 3 uppercase letters, dash, 4 digits (e.g., ABC-1234)\")\n", + "\n", + " @field_validator(\"product_code\")\n", + " @classmethod\n", + " def validate_code_format(cls, v: str) -> str:\n", + " \"\"\"Enforce the exact format: AAA-0000.\"\"\"\n", + " import re\n", + "\n", + " if not re.match(r\"^[A-Z]{3}-\\d{4}$\", v):\n", + " raise ValueError(f\"Product code '{v}' must match format AAA-0000 (3 uppercase letters, dash, 4 digits)\")\n", + " return v" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "agent = Agent(model=MODEL_ID)\n", + "\n", + "# The LLM may initially produce a code like \"laptop-001\" — the validator will reject it,\n", + "# the error goes back to the LLM, and it self-corrects to something like \"LAP-0001\".\n", + "# You can enable debug logging (logging.getLogger(\"strands\").setLevel(logging.DEBUG))\n", + "# to see the validation error and retry in the agent's logs.\n", + "result = agent(\n", + " \"Generate a product entry for a wireless keyboard\",\n", + " structured_output_model=StrictCode,\n", + ")\n", + "\n", + "product = result.structured_output\n", + "print(f\"Product: {product.product_name}\")\n", + "print(f\"Code: {product.product_code}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Handling StructuredOutputException\n", + "\n", + "If the LLM fails to produce valid output even after the SDK forces it, a `StructuredOutputException` is raised. Always wrap structured output calls in a try/except for production code." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "agent = Agent(model=MODEL_ID)\n", + "\n", + "try:\n", + " result = agent(\n", + " \"Generate a product entry for a USB hub\",\n", + " structured_output_model=StrictCode,\n", + " )\n", + " product = result.structured_output\n", + " print(f\"Success: {product.product_name} — {product.product_code}\")\n", + "except StructuredOutputException as e:\n", + " print(f\"Structured output failed: {e}\")\n", + " print(\"Fallback: use the raw text response or retry with a different prompt\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Custom forcing prompt\n", + "\n", + "When the LLM doesn't call the structured output tool on its own, the SDK sends a forcing prompt to nudge it. The default is:\n", + "\n", + "> *\"You must format the previous response as structured output.\"*\n", + "\n", + "You can customize this with `structured_output_prompt` to give the LLM more specific guidance for your schema." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "agent = Agent(model=MODEL_ID)\n", + "\n", + "result = agent(\n", + " \"Generate a product entry for a mechanical keyboard\",\n", + " structured_output_model=StrictCode,\n", + " structured_output_prompt=(\n", + " \"Format your response using the structured output tool. \"\n", + " \"The product_code MUST be exactly 3 uppercase letters, a dash, then 4 digits (e.g., MKB-0001).\"\n", + " ),\n", + ")\n", + "\n", + "product = result.structured_output\n", + "print(f\"Product: {product.product_name}\")\n", + "print(f\"Code: {product.product_code}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Combining Tools with Structured Output\n", + "\n", + "In practice, agents don't just format text — they use tools to gather information first, then structure the result. The structured output tool coexists with regular tools. The LLM uses regular tools to do work, then calls the structured output tool last to return the final answer.\n", + "\n", + "Let's use the `calculator` tool from `strands-agents-tools` so the agent can perform calculations before returning a structured result." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from strands_tools import calculator\n", + "\n", + "\n", + "class InvestmentAnalysis(BaseModel):\n", + " \"\"\"Analysis of an investment calculation.\"\"\"\n", + "\n", + " initial_amount: float = Field(description=\"Initial investment amount in dollars\")\n", + " annual_rate: float = Field(description=\"Annual interest rate as a percentage\")\n", + " years: int = Field(description=\"Investment period in years\")\n", + " final_amount: float = Field(description=\"Calculated final amount after compound interest\")\n", + " total_return: float = Field(description=\"Total return as a percentage\")\n", + " recommendation: str = Field(description=\"Brief investment recommendation\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "agent = Agent(\n", + " model=MODEL_ID,\n", + " tools=[calculator],\n", + " system_prompt=\"You are a financial analyst. Use the calculator tool for all math operations.\",\n", + ")\n", + "\n", + "result = agent(\n", + " \"If I invest $10,000 at 7% annual compound interest for 15 years, what will I have? Analyze this investment.\",\n", + " structured_output_model=InvestmentAnalysis,\n", + ")\n", + "\n", + "analysis = result.structured_output\n", + "print(f\"Initial: ${analysis.initial_amount:,.2f}\")\n", + "print(f\"Rate: {analysis.annual_rate}%\")\n", + "print(f\"Period: {analysis.years} years\")\n", + "print(f\"Final: ${analysis.final_amount:,.2f}\")\n", + "print(f\"Return: {analysis.total_return:.1f}%\")\n", + "print(f\"Recommendation: {analysis.recommendation}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The agent used the `calculator` tool to compute compound interest, then called the structured output tool to return the result as a validated `InvestmentAnalysis` object. The tools and structured output work together seamlessly." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Streaming with Structured Output\n", + "\n", + "When building streaming UIs, you want to show progress while the agent works. With structured output:\n", + "\n", + "* **Text chunks may stream** as the agent reasons and uses tools\n", + "* **Structured output is NOT available incrementally** — it appears only in the **final event**\n", + "\n", + "This is because the structured output must be fully validated before it's returned.\n", + "\n", + "> **Note:** When structured output is active, the LLM primarily interacts via tool calls (the structured output tool). You may see minimal or no streaming text before the final structured result — this is expected behavior." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class BookSummary(BaseModel):\n", + " \"\"\"A structured book summary.\"\"\"\n", + "\n", + " title: str = Field(description=\"Book title\")\n", + " author: str = Field(description=\"Author name\")\n", + " genre: str = Field(description=\"Primary genre\")\n", + " themes: List[str] = Field(description=\"Key themes\")\n", + " one_liner: str = Field(description=\"One-sentence summary\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "async def stream_structured_output():\n", + " agent = Agent(model=MODEL_ID)\n", + "\n", + " print(\"Streaming events:\")\n", + " print(\"-\" * 40)\n", + "\n", + " async for event in agent.stream_async(\n", + " \"Summarize the book 'The Hitchhiker's Guide to the Galaxy' by Douglas Adams\",\n", + " structured_output_model=BookSummary,\n", + " ):\n", + " # Text chunks arrive during processing\n", + " if \"data\" in event:\n", + " print(f\"[stream] {event['data']}\", end=\"\", flush=True)\n", + "\n", + " # Structured output arrives only in the final result\n", + " if \"result\" in event:\n", + " print(\"\\n\" + \"-\" * 40)\n", + " print(\"Final structured output:\")\n", + " summary = event[\"result\"].structured_output\n", + " print(f\" Title: {summary.title}\")\n", + " print(f\" Author: {summary.author}\")\n", + " print(f\" Genre: {summary.genre}\")\n", + " print(f\" Themes: {', '.join(summary.themes)}\")\n", + " print(f\" Summary: {summary.one_liner}\")\n", + "\n", + "\n", + "await stream_structured_output()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The validated `BookSummary` object only appears in the final `result` event. Keep this in mind when designing your UI — you can show a loading indicator for the structured data while streaming text progress." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Key Takeaways\n", + "\n", + "You've learned how to get reliable, typed data from Strands agents:\n", + "\n", + "1. **Basic structured output** — Define a Pydantic model, pass it to the agent, get a validated object back\n", + "2. **Complex schemas** — Nested models, lists, optional fields, field constraints, and enums all work\n", + "3. **Validation & self-correction** — The SDK automatically retries when validation fails, and you can catch `StructuredOutputException` as a fallback\n", + "4. **Tools + structured output** — Agents use tools to gather data, then return structured results\n", + "5. **Streaming** — Structured output appears in the final event, not incrementally\n", + "\n", + "### Tips for production use\n", + "\n", + "* Use `structured_output_model=` (not the deprecated `agent.structured_output()` method)\n", + "* Add `Field(description=...)` to help the LLM understand your schema\n", + "* Always wrap production calls in `try/except StructuredOutputException`\n", + "* Use `structured_output_prompt` to improve success rates for strict schemas\n", + "* Structured output works with both sync (`agent()`) and async (`invoke_async()`, `stream_async()`)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbformat_minor": 5, + "pygments_lexer": "ipython3", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}