Many tools, one agent
Tools are a list. Adding a second one is adding a second function, and the model picks between them by reading their descriptions.
@function_tool
def refund(amount: int) -> str:
"""Refund an amount in rupees to the customer."""
return f"Refunded {amount} rupees."An integer argument this time, not a string. The type hint travels into the schema, so the model is told to send a number. The SDK then checks what came back against that schema before your function is called, and converts where it can: a model that sends "500" as text still arrives in your function as the integer 500. Something it cannot convert is an error before a line of your code runs.
agent = Agent(
name="Support",
instructions="Use the right tool for the job.",
model=PretendModel([call("refund", amount=500), "Your refund is on its way."]),
tools=[lookup_order, refund],
)The model is scripted here so the lesson is about the wiring rather than about whether a fake model can tell a refund from a lookup. It asks for refund, the SDK runs it, and the second reply ends the loop.
result = await Runner.run(agent, "Please refund 500 rupees")
print("tools it had:", [t.name for t in agent.tools])
print("answer: ", result.final_output)How a real model chooses
It has the name, the description and the arguments of every tool, and the conversation so far. Nothing else. Which means the way to make it choose better is not a longer prompt, it is a clearer set of tool descriptions.
- One job per tool. A tool that looks up an order and sometimes refunds it will be called for both and get one of them wrong.
- Say when, not just what. "Refund an amount in rupees to the customer" beats "handles refunds".
- Fewer is better. Twenty tools is twenty descriptions to weigh up on every turn.
- Add a third tool and print all three names.
- Change the scripted call to
lookup_orderand see the other path. - Give two tools nearly identical descriptions and think about what a real model would do.
You understood something today that you didn't yesterday.