Deep AgentsDeep Agents 0.7 · Python 3.11+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
28 small wins to finish your pathNext lesson →

Subagent structured output with response_format

Structured output is a reply that fills a schema you define, such as a Pydantic class; a subagent with response_format hands its result back to the main agent as JSON in that shape instead of free text.

Last updated: 29 Sep, 2026 · Deep Agents 0.7

Structured output with subagents · from the Complete Deep Agents Course With LangChain · 149:32 to 154:05
The video passes the schema class directly on OpenAI. On Groq a subagent with tools needs ToolStrategy(...) around the class, as the next section explains.

The video's ResearchFindings schema

The video's second subagent example returns structured output. It defines a Pydantic class, ResearchFindings, with three fields: a summary, a confidence score from 0 to 1, and a list of sources. The subagent dictionary gets one more key, response_format, set to that class. Asked to research recent advances in quantum computing, the main agent calls task with subagent_type researcher, the tool message that comes back holds the summary, confidence and source URLs, and the main agent writes its answer from them.

The run below uses web_search, the Tavily tool from Tools: a travel search the agent can call, which needs TAVILY_API_KEY. The agent runs on Gemini, so it also needs GOOGLE_API_KEY, the free key from Installation and setup; with the Groq line it needs GROQ_API_KEY instead. Start findings.py with it.

python
import os
from tavily import TavilyClient
from typing import Literal

tavily_client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))

def web_search(query: str, max_results: int = 5,
               topic: Literal["general", "sports", "news", "finance"] = "general"):
    """Run a web search"""
    return tavily_client.search(query, max_results=min(max_results, 5), topic=topic)

Add the video's code under web_search in findings.py, with four changes: the model is google_genai:gemini-2.5-flash, Gemini's free key, which leaves Groq's daily limit for the other lessons (the Groq line works the same); the schema goes in ToolStrategy, as the next section explains; the subagent's prompt names web_search, so it searches instead of answering from memory; and invoke replaces await agent.ainvoke, which runs only inside a notebook or an async function.

ExampleAPI keyFrom the video, run on Gemini
from deepagents import create_deep_agent
from langchain.agents.structured_output import ToolStrategy
from langchain.chat_models import init_chat_model
from pydantic import BaseModel, Field

class ResearchFindings(BaseModel):
    """Structured findings from a research task."""
    summary: str = Field(description="Summary of findings")
    confidence: float = Field(description="Confidence score from 0 to 1")
    sources: list[str] = Field(description="List of source URLs")

research_subagent = {
    "name": "researcher",
    "description": "Researches topics and returns structured findings",
    "system_prompt": "Research the given topic thoroughly with web_search. Return your findings.",
    "tools": [web_search],
    "response_format": ToolStrategy(ResearchFindings),
}
model = init_chat_model("google_genai:gemini-2.5-flash", temperature=0, max_retries=6)
agent = create_deep_agent(model=model, subagents=[research_subagent])
result = agent.invoke({"messages": [{"role": "user", "content": "Research recent advances in quantum computing"}]})

for message in result["messages"]:
    if message.type == "tool":
        print("researcher returned:", message.text)
print("final:", result["messages"][-1].text[:600])

The tool message is the subagent's reply as JSON in the ResearchFindings shape: a summary, a confidence of 0.95 and a list of source URLs. The final answer is the main agent rewriting that summary under headings, starting with Algorithms and Hardware. Search results change from day to day, so your run will find other sources.

Passing the schema on Groq with ToolStrategy

The video passes the class directly, and on OpenAI the model's built-in JSON mode returns it. Groq cannot use JSON mode and tools in the same request, so a subagent with tools fails with "json mode cannot be combined with tool/function calling". Wrapping the class in ToolStrategy asks for the schema as one more tool call instead, which works on any model that can call tools.

python
from langchain.agents.structured_output import ToolStrategy

subagent = {..., "response_format": ToolStrategy(HotelPick)}   # the reply fills HotelPick

The HotelPick schema

Start trip.py with search_travel, the catalog tool from Tools: a travel search the agent can call. Everything below goes in the same file, under it.

python
from langchain.tools import tool

CATALOG = {
    "paris": {
        "flight": ["Return flight Delhi to Paris: 42,000 rupees"],
        "hotel": ["Seine Budget Inn, Latin Quarter: 5,200 rupees a night",
                  "Hotel Lumiere, Montmartre: 7,500 rupees a night",
                  "Le Grand Opera Hotel: 16,000 rupees a night"],
        "sight": ["Eiffel Tower summit: 3,100 rupees", "Louvre Museum: 2,000 rupees",
                  "Seine river cruise: 1,500 rupees", "Versailles day trip: 2,600 rupees",
                  "Montmartre walking tour: free"],
        "food": ["Cafe breakfast and bistro dinner: 3,000 rupees a day"],
    },
}


@tool
def search_travel(city: str, kind: str) -> str:
    """Search the travel catalog. kind is "flight", "hotel", "sight" or "food". Prices are in rupees."""
    entries = CATALOG.get(city.lower(), {}).get(kind)
    return "\n".join(entries) if entries else f"The catalog has no {kind} entries for {city}."
python
from deepagents import create_deep_agent
from langchain.chat_models import init_chat_model

model = init_chat_model("groq:openai/gpt-oss-120b", temperature=0, max_retries=6)
python
from langchain.agents.structured_output import ToolStrategy
from pydantic import BaseModel, Field


class HotelPick(BaseModel):
    """One hotel chosen from the catalog."""
    name: str = Field(description="Hotel name")
    price_per_night: int = Field(description="Price per night in rupees")
    reason: str = Field(description="One short reason for the pick")

A hotel-scout that returns HotelPick

python
hotel_scout = {
    "name": "hotel-scout",
    "description": "Finds a hotel in a city that fits a nightly budget.",
    "system_prompt": "Look up hotels with search_travel and pick one. Use only catalog data.",
    "tools": [search_travel],
    "response_format": ToolStrategy(HotelPick),   # the subagent returns this schema
}

The main agent

python
agent = create_deep_agent(
    model=model,
    subagents=[hotel_scout],
    system_prompt="Give every hotel question to hotel-scout with the task tool, then answer in one sentence.",
)

Printing what hotel-scout handed back

ExampleAPI keytrip.py, continued
result = agent.invoke({"messages": [{"role": "user", "content": "Find me a Paris hotel under 8,000 rupees a night."}]})

for message in result["messages"]:
    if message.type == "tool":
        print("hotel-scout returned:", message.text)
print("final:", result["messages"][-1].text)

What the structured reply shows

  • The tool message is JSON with exactly the three fields of HotelPick.
  • price_per_night is a number, 5200, not the text "5,200 rupees", because the schema says int.
  • The main agent read the JSON and wrote the one-sentence answer from it.

Free text vs structured output

Free-text reportresponse_format
What comes backAny wording the subagent choosesJSON that fits the schema
Reading it in codeParse text yourselfLoad the JSON, fields are named
Good forExplanations, summariesPrices, scores, lists another step uses

The main agent can take a response_format too; its parsed result is in result["structured_response"].

Where structured subagent output helps

  • A subagent whose numbers the main agent adds up, like a price per night.
  • Research findings with a confidence score and sources, as in the video.
  • Passing results to code after the agent finishes.
Watch out. Field descriptions are instructions. price_per_night: int with "Price per night in rupees" gets 5200; without the unit, a model may return a price in euros.
Try it yourself
  • Add a district: str field to HotelPick and run it again.
  • Pass HotelPick without ToolStrategy and read the error Groq returns.
  • Give the main agent response_format=ToolStrategy(HotelPick) and print result["structured_response"].

Slow is fine. Stopping is the only problem.