Hi all,
I am facing a strange behaviour when setting a pydantic model as a response model for an agent vs team:
When calling arun(stream=True)
, Team
and Agent
return a valid response model.
However, when calling arun(stream=False)
, Team
fails to follow the response model.
Here’s the code to replicate the error:
import asyncio
from textwrap import dedent
from typing import Literal, Union, Optional
from agno.agent import Agent
from agno.models.aws import Claude
from agno.team import Team
from agno.tools.thinking import ThinkingTools
from pydantic import Field, BaseModel
MODEL_ID = "eu.anthropic.claude-3-7-sonnet-20250219-v1:0"
class GeneralKnowledgeSource(BaseModel):
title: str = Field(
description=dedent(
"""
The title of the source.
"""
)
)
author: str = Field(
description=dedent(
"""
The author(s) of the source.
"""
)
)
type: str = Field(
description=dedent(
"""
The type of the source.
Choose from "book", "article" or "website".
If none of these are fitting, assign your own type.
"""
)
)
response_schema: Literal["GeneralSource"] = "GeneralSource"
class GeneralKnowledgeAnswerSchema(BaseModel):
sources: list[GeneralKnowledgeSource] = Field(
description=dedent(
"""
Sources that back up the answer.
"""
)
)
answer: str = Field(
description=dedent(
"""
Your answer to the query.
It should be truthful and factual.
Format it as a well-readable Markdown.
Use list elements, bold fonts and tables where useful.
"""
)
)
response_schema: Literal["answer"] = "GeneralKnowledgeAnswer"
class FollowUpSchema(BaseModel):
content: str = Field(
description=dedent(
"""
Follow-up questions to ask the customer for clarification.
Ask questions if query:
- lacks context or details
- is vague or is subjective
- Query is aimed at a topic that might or might not be related to your area of expertise.
It is always best to ask the user for clarifications rather than to make assumptions,
especially if there is a need for disambiguation.
"""
)
)
reason: Optional[str] = Field(
description="Why the clarification is needed.", default=None
)
response_schema: Literal["follow-up"] = "FollowUp"
class DeclineSchema(BaseModel):
"""Schema for agent answers to user queries that are not supported by the agent."""
content: str = Field(
description=dedent(
"""
If a user query clearly falls outside of your expertise, decline the query and explain why.
Be concise and informative.
Mention what types of question the agent can answer and propose some questions
that are related to the user's query, whenever possible.
"""
)
)
response_schema: Literal["decline"] = "Decline"
RouterSchema = Union[GeneralKnowledgeAnswerSchema, FollowUpSchema, DeclineSchema]
general_knowledge_agent = Agent(
name="General Knowledge Agent for Biology",
role="Provide answers to general questions about biology.",
model=Claude(MODEL_ID),
show_tool_calls=True,
response_model=GeneralKnowledgeAnswerSchema,
tool_call_limit=10,
tools=[ThinkingTools(add_instructions=True)],
instructions=[
"Use your intrinsic knowledge to answer the user's question.",
"Find relevant sources to support your answer.",
"Provide a succinct and factually correct answer to the user's question.",
"Format your answer as a well-readable Markdown.",
"Decline questions that are not related to biology."
],
add_datetime_to_instructions=True,
respond_directly=False,
monitoring=False,
)
assistant = Team(
name="Assistant",
mode="route",
model=Claude(MODEL_ID),
enable_team_history=True,
members=[general_knowledge_agent],
# show_tool_calls=True,
debug_mode=False,
# show_members_responses=True,
instructions=[
"You are an assistant responsible for answering user inquiries.",
"Carefully analyze each user message and determine if it is within your area of expertise",
"Your area of expertise is biology.",
"If the message is outside your area of expertise, decline it.",
"If the message is within your area of expertise, route it to the appropriate agent:",
"\t- For general questions, route to the general_knowledge_agent who will use its intrinsic knowledge to answer the question.",
"Always provide a clear explanation of why you're routing the inquiry to a specific agent.",
"After receiving a response from the appropriate agent, relay that information back to the user in a professional and helpful manner.",
"Ensure a seamless experience for the user by maintaining context throughout the conversation.",
],
response_model=RouterSchema,
parse_response=True
)
async def main(_query: str):
async for event in await general_knowledge_agent.arun(_query, stream=True):
pass
print(event)
# Returns a correct response model
"""
RunResponseContentEvent(created_at=1756223944, event='RunResponseContent', agent_id='69e5deef-3b0b-4331-bce6-c665858335fd', agent_name='General Knowledge Agent for Biology', run_id='756e0541-dbb6-492d-9a23-5ce030aa7d0f', session_id='39a29de8-e585-440e-9d9a-cf986b693755', team_session_id=None, tools=None, content=GeneralKnowledgeAnswerSchema(sources=[GeneralKnowledgeSource(title='Molecular Biology of the Cell', ...
"""
async for event in await assistant.arun(_query, stream=True):
pass
print(event)
# Returns correct response model
# But raises WARNING Failed to convert response to output model: model_validate_json
"""
ToolCallCompletedEvent(..., content='forward_task_to_member(member_id=general-knowledge-agent-for-biology, expected_output=...) completed in 0.0008s.', tool=ToolExecution(tool_call_id='toolu_bdrk_01KabeSTygGYvZznhbXtVLav', tool_name='forward_task_to_member', tool_args={'member_id': 'general-knowledge-agent-for-biology', 'expected_output': 'A comprehensive explanation of what mitochondria are, including their structure, function, and significance in cells.'}, tool_call_error=False, result='{"sources":[{"title":"Molecular Biology of the Cell","author":"Bruce Alberts, Alexander Johnson, Julian Lewis, David Morgan, Martin Raff, Keith Roberts, Peter Walter","type":"book","response_schema":"GeneralSource"}
"""
response = await assistant.arun(_query, stream=False)
print(response)
# Returns wrong response model (seems not to have verified the response)
"""
TeamRunResponse(content='# Mitochondria: The Powerhouse of the Cell\n\nMitochondria are membrane-bound organelles found in the cytoplasm of most eukaryotic cells. Often referred to as the "powerhouse of the cell," these remarkable structures play a crucial role in cellular energy
"""
response = await general_knowledge_agent.arun(_query, stream=False)
print(response)
# Returns correct response model
"""
RunResponse(content=GeneralAnswerSchema(sources=[GeneralSource(title='Molecular Biology of the Cell', author='Bruce Alberts, Alexander Johnson, Julian Lewis, David Morgan, Martin Raff, Keith Roberts, and Peter Walter', type='book', response_schema='GeneralSource'), GeneralSource(title='The Cell: A Molecular Approach', author='Geoffrey Cooper', type='book', response_schema='GeneralSource')],
"""
if __name__ == "__main__":
query = "What is the mitochondria?"
asyncio.run(main(query))