-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Closed
Labels
questionFurther information is requestedFurther information is requested
Description
Question
Hi,
I’m working with pydantic_ai and noticed that when running a multi-step agent workflow (using Graph, Agent, BaseNode, etc.), the output includes not just the final text (e.g., the generated email), but also intermediate parts like ToolCallPart and ToolReturnPart in the ModelRequest(parts=[...]) or ModelResponse(parts=[...]) sequence.
Sample output:
ModelRequest(parts=[ToolReturnPart(tool_name='final_result', ...)])and
ModelResponse(parts=[
ThinkingPart(...),
TextPart(...),
ToolCallPart(tool_name='final_result', ...),
])My question:
- Is it expected that these tool invocation parts appear in the message history by default?
- What is the recommended way to access only the final user-facing output (e.g., the actual generated email body/subject), and ignore these tool-related parts?
- Are these parts mainly for debugging, or do they play a role in downstream nodes/agents?
Additional Context
from __future__ import annotations as _annotations
from dataclasses import dataclass, field
import asyncio
from rich import print
from pydantic import BaseModel, EmailStr
from pydantic_ai import Agent, format_as_xml
from pydantic_ai.messages import ModelMessage
from pydantic_graph import BaseNode, End, Graph, GraphRunContext,GraphRunResult
from pydantic_ai.models.openai import OpenAIModel
from pydantic_ai.providers.openai import OpenAIProvider
ollama_model = OpenAIModel(
model_name='qwen3:4b',
provider=OpenAIProvider(base_url='http://xxx:11434/v1'),
)
@dataclass
class User:
name: str
email: EmailStr
interests: list[str]
@dataclass
class Email:
subject: str
body: str
@dataclass
class State:
user: User
write_agent_messages: list[ModelMessage] = field(default_factory=list)
email_writer_agent = Agent(
ollama_model,
output_type=Email,
system_prompt='Write a welcome email to our tech blog.',
)
@dataclass
class WriteEmail(BaseNode[State]):
email_feedback: str | None = None
async def run(self, ctx: GraphRunContext[State]) -> Feedback:
if self.email_feedback:
prompt = (
f'Rewrite the email for the user:\n'
f'{format_as_xml(ctx.state.user)}\n'
f'Feedback: {self.email_feedback}'
)
else:
prompt = (
f'Write a welcome email for the user:\n'
f'{format_as_xml(ctx.state.user)}'
)
result = await email_writer_agent.run(
prompt,
message_history=ctx.state.write_agent_messages,
)
ctx.state.write_agent_messages += result.new_messages()
return Feedback(result.output)
class EmailRequiresWrite(BaseModel):
feedback: str
class EmailOk(BaseModel):
pass
feedback_agent = Agent[None, EmailRequiresWrite | EmailOk](
ollama_model,
output_type=EmailRequiresWrite | EmailOk, # type: ignore
system_prompt=(
'Review the email and provide feedback, email must reference the users specific interests.'
),
)
@dataclass
class Feedback(BaseNode[State, None, Email]):
email: Email
async def run(
self,
ctx: GraphRunContext[State],
) -> WriteEmail | End[Email]:
prompt = format_as_xml({'user': ctx.state.user, 'email': self.email})
result = await feedback_agent.run(prompt)
if isinstance(result.output, EmailRequiresWrite):
return WriteEmail(email_feedback=result.output.feedback)
else:
return End(self.email)
async def main():
user = User(
name='John Doe',
email='[email protected]',
interests=['Haskel', 'Lisp', 'Fortran'],
)
state = State(user)
feedback_graph = Graph(nodes=(WriteEmail, Feedback))
result = await feedback_graph.run(WriteEmail(), state=state)
# print(result.persistence)
print(result.output)
print(state.write_agent_messages)
"""
Email(
subject='Welcome to our tech blog!',
body='Hello John, Welcome to our tech blog! ...',
)
"""
if __name__ == "__main__":
asyncio.run(main())output:
Email(
subject='Welcome to the Functional Programming Blog!',
body="Dear John Doe,\n\nWelcome to the Functional Programming Blog! We're thrilled to have you join our community of enthusiasts who share a passion for Haskell, Lisp, and Fortran.\n\nAs someone deeply interested
in Haskell's elegant type system, Lisp's versatility, and Fortran's foundational role in computing, you'll find tailored content that resonates with your expertise. Explore our tutorials on advanced Haskell patterns,
historical insights into Lisp's evolution, and deep dives into Fortran's impact on early programming.\n\nWe invite you to engage with our community, share your insights, and discover how these languages shape modern
software engineering. Let's explore the beauty of functional programming together!\n\nBest regards,\nThe Functional Programming Team"
)
[
ModelRequest(parts=[SystemPromptPart(content='Write a welcome email to our tech blog.', timestamp=datetime.datetime(2025, 8, 2, 4, 14, 50, 136008, tzinfo=datetime.timezone.utc)), UserPromptPart(content='Write a
welcome email for the user:\n<name>John Doe</name>\n<email>[email protected]</email>\n<interests>\n <item>Haskel</item>\n <item>Lisp</item>\n <item>Fortran</item>\n</interests>',
timestamp=datetime.datetime(2025, 8, 2, 4, 14, 50, 136010, tzinfo=datetime.timezone.utc))]),
ModelResponse(parts=[ThinkingPart(content='\nOkay, I need to write a welcome email for John Doe. Let me start by looking at the details provided. His email is [email protected], and his interests are Haskell,
Lisp, and Fortran. \n\nFirst, the subject line should be friendly and welcome. Maybe something like "Welcome to Our Tech Blog, John Doe!" That sounds good.\n\nNow the body. I should mention his interests to make it
personal. Start with a greeting, then acknowledge his interests in those programming languages. Maybe mention that the blog covers topics related to those, like functional programming and historical languages.
Encourage him to explore the blog and engage with the community. Also, include a call to action, like clicking a link to explore more. End with a friendly sign-off.\n\nWait, I should check if the blog\'s content
aligns with his interests. Haskell and Lisp are functional programming languages, while Fortran is an older language. The blog might have posts on functional programming, maybe some history of programming languages,
or tutorials. So the body should reflect that. \n\nMake sure the tone is welcoming and enthusiastic. Avoid jargon but show that the blog is tailored to his interests. Maybe mention specific sections or articles that
might interest him. Also, include a link to the blog\'s homepage or a "Read More" section. \n\nDouble-check the email structure: subject, greeting, body paragraphs, closing. Ensure the email is concise but
informative. Personalize it with his name and interests. Alright, that should cover it.\n'), TextPart(content='\n\n'), ToolCallPart(tool_name='final_result', args='{"body":"Hi John Doe,\\n\\nWelcome to our tech blog!
We\'re thrilled to have you join our community of tech enthusiasts. With your interests in Haskell, Lisp, and Fortran, you\'ll find a wealth of content tailored to your passion for functional programming and
historical language design.\\n\\nWhether you\'re exploring advanced topics in functional programming, diving into the legacy of Fortran, or discovering the elegance of Lisp, our blog is your go-to resource. Don\'t
forget to check out our latest posts and engage with the community!\\n\\nBest regards,\\nThe Tech Blog Team\\n\\nP.S. Click [here](https://www.techblog.com) to explore our latest articles!","subject":"Welcome to Our
Tech Blog, John Doe!"}', tool_call_id='call_5flb5qm2')], usage=Usage(requests=1, request_tokens=219, response_tokens=475, total_tokens=694, details={}), model_name='qwen3:4b', timestamp=datetime.datetime(2025, 8, 2,
12, 14, 39, tzinfo=TzInfo(UTC)), vendor_id='chatcmpl-28'),
ModelRequest(parts=[ToolReturnPart(tool_name='final_result', content='Final result processed.', tool_call_id='call_5flb5qm2', timestamp=datetime.datetime(2025, 8, 2, 4, 14, 54, 13486,
tzinfo=datetime.timezone.utc))]),
ModelRequest(parts=[UserPromptPart(content='Rewrite the email for the user:\n<name>John Doe</name>\n<email>[email protected]</email>\n<interests>\n <item>Haskel</item>\n <item>Lisp</item>\n
<item>Fortran</item>\n</interests>\nFeedback: The email effectively highlights your interests in Haskell, Lisp, and Fortran, showcasing a personalized approach to functional programming and historical language design.
The tone is welcoming and engaging, making it clear how the blog aligns with your passions.', timestamp=datetime.datetime(2025, 8, 2, 4, 14, 57, 822005, tzinfo=datetime.timezone.utc))]),
ModelResponse(parts=[ThinkingPart(content='\nOkay, the user wants me to rewrite the welcome email for John Doe, who\'s interested in Haskell, Lisp, and Fortran. Let me start by understanding the original email.
The previous version was praised for highlighting his interests and the tone was welcoming. The feedback says it\'s good, so maybe the user wants a slightly different version but still keeping the same positive
aspects.\n\nFirst, I need to make sure the subject line is catchy. The original might have been something like "Welcome to Our Tech Blog!" Maybe I can make it more specific to his interests. Perhaps "Welcome to Our
Tech Blog: Exploring the World of Functional Programming!" That includes his interests and sets the tone.\n\nNow the body. The original might have mentioned his interests and how the blog aligns with them. I should
keep that personal touch. Start with a friendly greeting, mention his interests in Haskell, Lisp, and Fortran, and explain how the blog covers those areas. Maybe add something about the blog\'s focus on functional
programming and historical languages. Also, include a call to action, like subscribing or exploring the blog.\n\nWait, the user mentioned "historical language design" in the feedback. So I should make sure to mention
that. Also, perhaps add a line about the blog\'s community or resources. Maybe something like "Join a community of like-minded developers and explore the latest in functional programming and historical language
design."\n\nCheck the email structure. Subject line, greeting, body paragraphs, closing. Make sure it\'s concise but informative. Avoid jargon but keep it engaging. Maybe add an emoji related to programming, like a
computer or brain emoji to make it friendlier.\n\nWait, the user provided the interests as Haskell, Lisp, Fortran. Fortran is a historical language, so highlighting that could be good. Maybe mention how the blog
covers both modern functional programming and historical languages.\n\nAlso, ensure the email is personalized with his name and email. The previous version had that, so I should keep that. Maybe add a line about期待
(look forward to) him joining the community.\n\nLet me put it all together. Start with the subject, then the body. Make sure each paragraph flows naturally, connecting his interests to the blog\'s content. End with a
friendly sign-off. Check for any typos or awkward phrases. Make sure the tone remains welcoming and engaging as per the feedback.\n'), TextPart(content='\n\n'), ToolCallPart(tool_name='final_result', args='{"body":"Hi
John Doe,\\n\\nWelcome to our tech blog, where we celebrate the beauty of functional programming and the legacy of pioneering languages like Haskell, Lisp, and Fortran. Your passion for these domains aligns perfectly
with our mission to explore both modern functional programming innovations and the historical design principles that shaped them.\\n\\nWhether you\'re diving into Haskell\'s type system, Lisp\'s flexibility, or
Fortran\'s foundational impact, you\'ll find insightful content, tutorials, and discussions that resonate with your expertise. Join a community of like-minded developers and explore the latest in functional
programming and historical language design.\\n\\nLooking forward to seeing your contributions and discoveries!\\n\\nBest regards,\\nThe Tech Blog Team","subject":"Welcome to Our Tech Blog: Exploring the World of
Functional Programming!"}', tool_call_id='call_dd85q8bt')], usage=Usage(requests=1, request_tokens=350, response_tokens=660, total_tokens=1010, details={}), model_name='qwen3:4b', timestamp=datetime.datetime(2025, 8,
2, 12, 14, 48, tzinfo=TzInfo(UTC)), vendor_id='chatcmpl-721'),
ModelRequest(parts=[ToolReturnPart(tool_name='final_result', content='Final result processed.', tool_call_id='call_dd85q8bt', timestamp=datetime.datetime(2025, 8, 2, 4, 15, 2, 792036,
tzinfo=datetime.timezone.utc))]),
ModelRequest(parts=[UserPromptPart(content="Rewrite the email for the user:\n<name>John Doe</name>\n<email>[email protected]</email>\n<interests>\n <item>Haskel</item>\n <item>Lisp</item>\n
<item>Fortran</item>\n</interests>\nFeedback: The email effectively highlights the user's interests in Haskell, Lisp, and Fortran by aligning the blog's focus with their expertise. It emphasizes Haskell's type system,
Lisp's flexibility, and Fortran's foundational impact, while inviting engagement with the community. A minor suggestion: incorporating specific tutorials or historical contexts for each language could deepen the
connection to the user's passion.", timestamp=datetime.datetime(2025, 8, 2, 4, 15, 6, 173277, tzinfo=datetime.timezone.utc))]),
ModelResponse(parts=[ThinkingPart(content="\nOkay, let's see. The user wants me to rewrite the welcome email for John Doe, who's interested in Haskell, Lisp, and Fortran. The previous feedback mentioned that the
email effectively highlights his interests but suggests adding specific tutorials or historical contexts for each language to deepen the connection.\n\nFirst, I need to make sure the email starts with a friendly
greeting. Then, address his interests in each language. For Haskell, I should mention the type system and maybe a tutorial. For Lisp, talk about flexibility and perhaps a historical context. Fortran's foundational
impact is important, so maybe include something about its role in early computing.\n\nI should structure each language section to not only highlight their strengths but also suggest specific content on the blog. That
way, John feels like there's targeted content for him. Also, the tone should remain welcoming and engaging, encouraging him to explore and engage with the community.\n\nWait, the user's feedback mentioned a minor
suggestion. So I need to incorporate that. Maybe add a sentence about specific tutorials or historical contexts. Let me check the previous version to ensure I'm not repeating the same points. Also, make sure the email
flows well, each paragraph transitions smoothly to the next.\n\nDouble-check the email details: name, email, interests. Make sure all three languages are covered with specific mentions. Avoid jargon but keep it
technical enough for someone interested in those languages. End with a call to action, maybe inviting him to explore the blog and engage with the community.\n\nOkay, that should cover it. Let me put it all together in
a friendly, personalized email.\n"), TextPart(content='\n\n'), ToolCallPart(tool_name='final_result', args='{"body":"Dear John Doe,\\n\\nWelcome to the Functional Programming Blog! We\'re thrilled to have you join our
community of enthusiasts who share a passion for Haskell, Lisp, and Fortran.\\n\\nAs someone deeply interested in Haskell\'s elegant type system, Lisp\'s versatility, and Fortran\'s foundational role in computing,
you\'ll find tailored content that resonates with your expertise. Explore our tutorials on advanced Haskell patterns, historical insights into Lisp\'s evolution, and deep dives into Fortran\'s impact on early
programming.\\n\\nWe invite you to engage with our community, share your insights, and discover how these languages shape modern software engineering. Let\'s explore the beauty of functional programming
together!\\n\\nBest regards,\\nThe Functional Programming Team","subject":"Welcome to the Functional Programming Blog!"}', tool_call_id='call_62j9u8ch')], usage=Usage(requests=1, request_tokens=510,
response_tokens=501, total_tokens=1011, details={}), model_name='qwen3:4b', timestamp=datetime.datetime(2025, 8, 2, 12, 14, 55, tzinfo=TzInfo(UTC)), vendor_id='chatcmpl-429'),
ModelRequest(parts=[ToolReturnPart(tool_name='final_result', content='Final result processed.', tool_call_id='call_62j9u8ch', timestamp=datetime.datetime(2025, 8, 2, 4, 15, 10, 50672,
tzinfo=datetime.timezone.utc))])
]
Metadata
Metadata
Assignees
Labels
questionFurther information is requestedFurther information is requested