agent_with_tool gave the agent one tool — a word counter. What happens when a prompt could reasonably call either of two different tools?
In agent_with_tool the agent had no choice — there was only one tool. With two it has to read the prompt and pick. I'm guessing I just register both with @agent.tool_plain and the model figures out which to invoke?
Exactly. You define both tools on the same agent and do nothing else — the model's tool-calling mechanism reads the docstrings, matches them to the prompt, and fires the right function. Two decorators, one agent:
agent = Agent(model)
@agent.tool_plain
def add(a: int, b: int) -> int:
"""Add two integers."""
return a + b
@agent.tool_plain
def multiply(a: int, b: int) -> int:
"""Multiply two integers."""
return a * bWhat makes the model pick multiply over add for "What is 3 times 5?" — is it just the word "times" in the prompt?
The model maps natural language to tool docstrings. "Times" and "product" both route to multiply; "plus" and "sum" route to add. The docstring is your interface contract — keep it short, precise, and verb-first. The agent then calls run_sync and returns its output as a string:
def agent_two_tools(prompt: str) -> str:
agent = Agent(model)
@agent.tool_plain
def add(a: int, b: int) -> int:
"""Add two integers."""
return a + b
@agent.tool_plain
def multiply(a: int, b: int) -> int:
"""Multiply two integers."""
return a * b
result = agent.run_sync(prompt)
return result.outputThe agent is picking the tool — I just defined them. I wrote add and multiply as plain Python and the model decides which to call at runtime.
That's the tool-calling contract. Your job is to define clean tools with accurate docstrings; the model's job is dispatch. If the docstring is vague — "Do math" — the model will guess wrong. Precise verbs in the docstring make the routing reliable.
So when my manager asks "can you multiply the headcount by the average salary?", I'll have an agent that could actually do that — no copy-paste into a calculator.
And you can add a divide or subtract tool later without touching the dispatch logic. Each new @agent.tool_plain function is a new capability the agent picks up automatically on the next run.
Register both tools on one agent — the model picks which to call:
agent = Agent(model)
@agent.tool_plain
def add(a: int, b: int) -> int:
"""Add two integers."""
return a + b
@agent.tool_plain
def multiply(a: int, b: int) -> int:
"""Multiply two integers."""
return a * b
result = agent.run_sync(prompt)
return result.outputHow dispatch works: the model receives both tool schemas (name + docstring + parameter types) alongside the prompt, selects the best match, calls it with extracted arguments, and folds the return value into the final response.
Docstring quality matters: the docstring is the model's only signal. One clear verb phrase — "Add two integers" — outperforms vague labels.
tool_plain vs tool: tool_plain gets only the typed arguments you declared. Use it for pure functions with no shared state.
agent_with_tool gave the agent one tool — a word counter. What happens when a prompt could reasonably call either of two different tools?
In agent_with_tool the agent had no choice — there was only one tool. With two it has to read the prompt and pick. I'm guessing I just register both with @agent.tool_plain and the model figures out which to invoke?
Exactly. You define both tools on the same agent and do nothing else — the model's tool-calling mechanism reads the docstrings, matches them to the prompt, and fires the right function. Two decorators, one agent:
agent = Agent(model)
@agent.tool_plain
def add(a: int, b: int) -> int:
"""Add two integers."""
return a + b
@agent.tool_plain
def multiply(a: int, b: int) -> int:
"""Multiply two integers."""
return a * bWhat makes the model pick multiply over add for "What is 3 times 5?" — is it just the word "times" in the prompt?
The model maps natural language to tool docstrings. "Times" and "product" both route to multiply; "plus" and "sum" route to add. The docstring is your interface contract — keep it short, precise, and verb-first. The agent then calls run_sync and returns its output as a string:
def agent_two_tools(prompt: str) -> str:
agent = Agent(model)
@agent.tool_plain
def add(a: int, b: int) -> int:
"""Add two integers."""
return a + b
@agent.tool_plain
def multiply(a: int, b: int) -> int:
"""Multiply two integers."""
return a * b
result = agent.run_sync(prompt)
return result.outputThe agent is picking the tool — I just defined them. I wrote add and multiply as plain Python and the model decides which to call at runtime.
That's the tool-calling contract. Your job is to define clean tools with accurate docstrings; the model's job is dispatch. If the docstring is vague — "Do math" — the model will guess wrong. Precise verbs in the docstring make the routing reliable.
So when my manager asks "can you multiply the headcount by the average salary?", I'll have an agent that could actually do that — no copy-paste into a calculator.
And you can add a divide or subtract tool later without touching the dispatch logic. Each new @agent.tool_plain function is a new capability the agent picks up automatically on the next run.
Register both tools on one agent — the model picks which to call:
agent = Agent(model)
@agent.tool_plain
def add(a: int, b: int) -> int:
"""Add two integers."""
return a + b
@agent.tool_plain
def multiply(a: int, b: int) -> int:
"""Multiply two integers."""
return a * b
result = agent.run_sync(prompt)
return result.outputHow dispatch works: the model receives both tool schemas (name + docstring + parameter types) alongside the prompt, selects the best match, calls it with extracted arguments, and folds the return value into the final response.
Docstring quality matters: the docstring is the model's only signal. One clear verb phrase — "Add two integers" — outperforms vague labels.
tool_plain vs tool: tool_plain gets only the typed arguments you declared. Use it for pure functions with no shared state.
Create a free account to get started. Paid plans unlock all tracks.