Demo 4 — Structured Output (Getting typed output with response_format)
Reference:
- https://learn.microsoft.com/en-us/agent-framework/tutorials/agents/structured-output?pivots=programming-language-python
Objectives
- Define a Pydantic model and run with
response_format=<Model>specified - Handle the output safely as
response.value(= a Pydantic instance) - Continue the story from Demo 2 (venue search) and structure the information gathered via Web Search
Prerequisites
- Demo 2 is complete (Microsoft Foundry Agents env vars are configured)
pydanticis installed (already included in the Dev Container)az logincompleted
Additional required env vars:
FOUNDRY_PROJECT_ENDPOINTFOUNDRY_MODEL
This demo uses Web Search, so a Bing connection is also required (same as Demo 2):
BING_CONNECTION_ID(orBING_PROJECT_CONNECTION_ID)- Or
BING_CUSTOM_CONNECTION_ID+BING_CUSTOM_INSTANCE_NAME
- Or
(Recommended)
- Before running Demo 4, verify the following:
az logincompleted (when using Entra ID by default)- The
.envfile (at the repository root) contains the required values
Steps
Step 1. Define the structure (schema) with Pydantic
This repository includes src/demo4_structured_output.py.
(You can edit and extend it as needed)
Below is a minimal example to illustrate the concept.
For production-grade hardening (explicit .env loading, empty-string injection protection, DNS checks, fallback when response.value is empty, etc.), refer to the included src/demo4_structured_output.py.
from pydantic import BaseModel
class VenueInfoModel(BaseModel):
title: str | None = None
description: str | None = None
services: str | None = None
address: str | None = None
estimated_cost_per_person: float = 0.0
class VenueOptionsModel(BaseModel):
options: list[VenueInfoModel]
# In the actual demo, we use Microsoft Foundry Agents + client.get_web_search_tool
# to ask "find venues" and receive the results via response_format=VenueOptionsModel.
Key implementation details (in the included script):
- Explicitly loads the repository root
.envand only fills in unset/empty environment variables- This mitigates the empty-string injection issue in Dev Container / Codespaces
- Includes a fallback that restores the Pydantic model even when
response.valueisNone, as long asresponse.textcontains valid JSON- This handles cases where the value is not populated in non-streaming mode due to backend/version differences
Note:
- Official/sample code may show examples using
client.create_agent(...), but the SDK pinned in this repository usesas_agent(...)as the current API, so we follow that convention
(Additional note)
- The included script runs both non-streaming and streaming modes, allowing you to verify that
PersonInfocan be extracted in either case.
Step 2. Run
python3 -u src/demo4_structured_output.py
Step 3. Expected output
Multiple venue candidates are output with structured fields (title/address/description/…).
- If
response.valueis populated, it is displayed directly as a Pydantic instance - If
response.valueis empty butresponse.textcontains JSON, it is restored and displayed (fallback)
Technical Explanation (Key concepts)
1) response_format makes the “output shape” a contract
- It is not just a JSON string
- It instructs the model to produce output conforming to the specified schema (Pydantic model)
2) response.value is the “type-safe result”
- On success,
response.valuecontains a Pydantic instance - On failure, it can be
None, so always check with an if statement
3) Notes on streaming
This demo first demonstrates structured output in non-streaming mode. Combining it with streaming is an advanced topic best explored alongside the observations in Demo 5/6.
Common Pitfalls
response.value is None in non-streaming mode
The conditions that trigger this depend on the environment (backend/version differences), but the following pattern can occur:
response.textreturns JSON, but it is not populated intoresponse.value(Pydantic)
Resolution:
- The included
src/demo4_structured_output.pyalready has fallback handling (PersonInfo.model_validate_json(...)restores fromresponse.textif it contains JSON) - In your own code, it is also safe to log
response.textwhenresponse.value is Noneand parse it as JSON if valid
Structured output does not work
- Not all agent types/backends support structured output
- First, run the Demo 4 code as-is, then extend it after confirming it works
DNS resolution fails before starting
If the host in FOUNDRY_PROJECT_ENDPOINT cannot be resolved from the execution environment, execution stops at startup.
- Error example:
Cannot resolve FOUNDRY_PROJECT_ENDPOINT host via DNS - Resolution: Review your Private networking / DNS configuration, or run from a network where DNS resolution is possible
Note:
- DNS resolution may succeed on the host environment but fail inside the Dev Container. In that case, check the container’s DNS settings (Corporate DNS / Private Link / Dev Container network settings).
- A workaround using
/etc/hostswith a fixed IP is possible, but it breaks when the IP changes and is not a permanent solution (recommended only as a “last resort” for local testing).
Next Demo
In Demo 5, we split the processing across multiple agents and build a pipeline with guaranteed ordering using Workflow + Edge.
→ Open demo5.md to continue.