Session 5· 16· 25 min
Errors, Refusals & Mini Assistant
What you'll learn
- ▸Handle tool execution errors gracefully
- ▸Detect model refusals inside a tool-using conversation
- ▸Put it all together in a complete mini assistant
The Phase 2 capstone. Combine: tool definitions, a generic router, a loop, parallel handling, error catching, and refusal detection. This is the closest thing to a real production agent you have built so far.
16_tool_errors_refusal_final_mini_assistant.py
def call_tool_safe(name, args):
try: ①
return route(name, args)
except Exception as e:
return {"error": str(e)} ②
for _ in range(MAX_ITERS): ③
response = client.chat.completions.create(...)
msg = response.choices[0].message
messages.append(msg)
if msg.refusal: ④
print("Refusal:", msg.refusal); break
if not msg.tool_calls:
print(msg.content); break
for call in msg.tool_calls:
result = call_tool_safe(call.function.name, json.loads(call.function.arguments))
messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})①Wrap the tool call in try/except. Errors are normal — log them and send to the model as an error result.
②Return the error as a tool result so the model can apologise or retry.
③Capped loop — NEVER use while True in production.
④Refusal check happens first. If set, stop the loop immediately.
$ python 16_tool_errors_refusal_final_mini_assistant.py
Phase 2 complete
You can now define tools, execute them in a loop, handle parallel calls, handle errors, and handle refusals. This is the full tool-calling skill set. Phase 3 revisits structured output with the modern Pydantic-based parse methods.
Knowledge Check
When a tool raises an exception, what should you do?
Recap — what you just learned
- ✓Wrap tool calls in try/except — return errors as results, not exceptions
- ✓Always cap iterations (MAX_ITERS) in production
- ✓Check msg.refusal before tool_calls/content
- ✓This is the pattern every real agent uses