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

FunctionModel: write a stand-in model

FunctionModel turns a Python function into a model. The agent sends it the same messages a real model gets, and your function writes the response.

Example
from pydantic_ai import Agent, ModelResponse, TextPart
from pydantic_ai.models.function import AgentInfo, FunctionModel


def reply(messages, info: AgentInfo) -> ModelResponse:
    ticket = messages[-1].parts[-1].content
    return ModelResponse(parts=[TextPart(f"You wrote {len(ticket.split())} words.")])


agent = Agent(FunctionModel(reply))
print(agent.run_sync("I was charged twice for one order").output)

A model function takes two arguments. messages is the list from lesson 2, so messages[-1].parts[-1] is the newest part: the ticket. info describes what the agent offers the model. The function returns a ModelResponse with the parts a model would send, here one TextPart.

Example
def peek(messages, info: AgentInfo) -> ModelResponse:
    print("instructions:", info.instructions)
    print("tools:", [tool.name for tool in info.function_tools])
    print("text allowed:", info.allow_text_output)
    return ModelResponse(parts=[TextPart("ok")])


agent = Agent(FunctionModel(peek), instructions="Be short.")
agent.run_sync("hello")

info is how a stand-in model knows what the agent wants. A real model gets the same things, as JSON in the API request.

The stand-in for this course

The rest of the course uses one model function, in shop_model.py. It is a few keyword rules, not a language model, but it answers through the same parts a real model would:

Exampleshop_model.py
import re

from pydantic_ai import ModelResponse, TextPart, ToolCallPart
from pydantic_ai.models.function import AgentInfo, FunctionModel


def sort_ticket(text):
    text = text.lower()
    if "charged" in text or "refund" in text:
        return "billing", 4
    if "parcel" in text or "arrived" in text:
        return "shipping", 3
    return "other", 1


def shop_reply(messages, info: AgentInfo) -> ModelResponse:
    prompts = [p.content for m in messages for p in m.parts if p.part_kind == "user-prompt"]
    ticket = prompts[-1]
    last = messages[-1].parts[-1]
    order = re.search(r"A-\d{4}", ticket)

    # 1. The ticket names an order and the agent has a tool: ask for it.
    if order and info.function_tools and last.part_kind == "user-prompt":
        tool = info.function_tools[0].name
        return ModelResponse(parts=[ToolCallPart(tool, {"order_id": order.group()})])

    # 2. A tool answered: write the reply from what it said.
    if last.part_kind == "tool-return" and info.allow_text_output:
        return ModelResponse(parts=[TextPart(f"Order {order.group()}: {last.content}.")])

    # 3. The agent wants a typed answer: fill in its output tool.
    category, priority = sort_ticket(ticket)
    if info.output_tools:
        args = {"category": category, "priority": priority}
        return ModelResponse(parts=[ToolCallPart(info.output_tools[0].name, args)])

    # 4. Otherwise, plain text.
    return ModelResponse(parts=[TextPart(f"Sorted as {category}.")])


shop_model = FunctionModel(shop_reply, model_name="shop")
  • sort_ticket guesses a category and a priority from keywords.
  • Rule 1: a ticket with an order id like A-1001, sent to an agent that has a tool, gets a ToolCallPart asking for that tool.
  • Rule 2: when the newest part is a tool-return, it writes the answer from the tool's result.
  • Rule 3: an agent that wants typed output, lesson 6, gets its output tool called with the category and priority.
  • Rule 4: anything else gets a sentence.
Example
from pydantic_ai import Agent

from shop_model import shop_model

agent = Agent(shop_model)
for ticket in ["I was charged twice for one order", "My parcel never arrived", "How do I change my email?"]:
    print(agent.run_sync(ticket).output)
What the stand-in is for
A keyword rule gets "I want my money back" wrong, and lesson 21 catches it doing so. What you learn here is the code around the model: tools, types, retries, approval and tests. That code stays the same when a real model replaces the function.
Try it yourself
  • Add a rule to sort_ticket for tickets about passwords, with the category account.
  • Make reply answer in capital letters.
  • In peek, print len(messages).

Slow is fine. Stopping is the only problem.