Pydantic AIPydantic AI 2.51 · Python 3.10+
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
29 small wins to finish your pathNext lesson →

Requests and responses in a run

A run is a conversation between the agent and the model, kept as a list of messages you can print to see exactly what was sent and returned.

Last updated: 28 Sep, 2026 · Pydantic AI 2.51

The run in Agent basics and a first run returned an answer. That answer came from a message exchange, and result.all_messages() hands you the whole list.

Listing the messages of a run

Example
for message in result.all_messages():
    print(message.kind)
    for part in message.parts:
        print("   ", part.part_kind)

What the two messages hold

  • A request goes from the agent to the model; a response comes back.
  • Each message holds parts. The request has one user-prompt part, your ticket; the response has one text part, the answer.
  • kind and part_kind are the two labels you read to follow a run.

Watching a run grow with a tool

A tool is a Python function the model can ask the agent to call. Function tools in Pydantic AI covers tools in full; one here is enough to see the message list grow:

Example
@agent.tool_plain
def lookup_order(order_id: str) -> str:
    """Look up an order's status."""
    return f"Order {order_id} has shipped."


result = agent.run_sync("Where is my order A-1001?")
for message in result.all_messages():
    for part in message.parts:
        content = getattr(part, "content", None) or getattr(part, "args", None)
        print(f"{message.kind:8} {part.part_kind:12} {content}")
print(result.usage)

Reading the four messages

  • The first response is a tool-call part with arguments, not the answer.
  • The agent runs the tool and sends the result back as a tool-return part in a new request.
  • The model then writes its text answer, so the run made two requests and usage shows tool_calls=1.
  • The test model called the tool with order_id='a': it calls every tool once with made-up arguments that fit the types. A real model would read A-1001 from the ticket.
The agent loop, as messages
to the modelthe agent runs itto the modelround againRequestuser-promptResponsetool-callRequesttool-returnResponsetext
Hover or tap a piece to see what it is and which lesson built it.
Trace a run

Pick one to watch it run, step by step.

A request vs a response

requestresponse
DirectionAgent to modelModel to agent
Common partsuser-prompt, tool-return, retry-prompttext, tool-call
Who fills itYour prompt and tool resultsThe model

When you read the message list

  • Working out why an agent called a tool, or which arguments it sent.
  • Storing a conversation to continue it later, covered in the message-history part.
  • Debugging a run that made more requests than you expected.
Watch out. The test model's tool arguments are invented to fit the types, not read from the ticket, so never treat them as a real answer. Its job here is to show the run, not to sort the ticket.
Try it yourself
  • Add a second tool, refund_order(order_id: str) -> str, and print the parts again.
  • Print result.new_messages(). How does it differ from all_messages() here?
  • Print result.response, the model's last response.

Slow is fine. Stopping is the only problem.