When a tool fails
Your tool talks to a database, and the database is down. The interesting question is what the agent does next.
@function_tool
def lookup_order(order_id: str) -> str:
"""Look up the status of an order by its id."""
raise ValueError(f"{order_id} is not a real order")Run it. The run does not crash. You still get three items and a sensible answer.
result = await Runner.run(agent, "Where is order ZZZ?")
for item in result.new_items:
print(type(item).__name__)
print()
print("answer:", result.final_output)What the model was told
for item in result.new_items:
if item.type == "tool_call_output_item":
print(item.output)That sentence is the SDK's own wording, and it is all the model got. Your exception message is in there, which is why a tool that raises ValueError("ZZZ is not a real order") leads to a useful reply and one that raises ValueError("error") does not. The text you raise is a prompt.
What the SDK did
It caught the exception, turned it into a message, and handed that message back to the model as the tool's answer. The model then had a choice, exactly as it would with a successful answer, and it chose to tell the customer it could not find the order.
That is the default and it is usually what you want. An agent that dies because one lookup failed is worse than an agent that says it could not find something.
When you want it to stop instead
Some failures should end the run rather than be explained away. The decorator takes a handler for that.
@function_tool(failure_error_function=None)
def charge_card(amount: int) -> str:
"""Charge the customer's card."""
...Passing None means the exception is raised instead of being handed to the model. Use it where a quiet retry would be dangerous, and leave the default everywhere else.
- Change the exception message and see it reach the model's answer.
- Add a second reply that tries the tool again with a different id.
- Print
item.outputon the tool output item and read what the model was told.
Slow is fine. Stopping is the only problem.