Day 2: Instrument the Tool-Calling Interface

Build a Production-Grade AI Support Agent for ShopStream (Python) ~6 min read 15 min hands-on

Why tools need contracts

On Day 1 the dispatcher ran SQL directly. That is fine while the only caller is your own regex. From Day 3 on, the caller is a model, and a model will eventually send {"order_id": 1005} (a number), "ORD-1" (half an id), an amount of -40, or a tool name it invented. In production the cheapest place to stop that is the boundary between "the model decided" and "the database changed". This lesson builds that boundary: a tool registry where every tool declares its arguments and its result, and nothing runs until both check out.

What you build

supportdesk/tools.py adds three things to the project:

  • A small schema validator. A subset of JSON Schema that the standard library can check in a few lines: type, required, enum, minimum/maximum, pattern and maxLength, plus "no extra fields". A failure raises ToolValidationError with the field name, so the caller (and later the model) knows exactly what to fix.

  • Three tools. get_order_status(order_id) returns status, item, dates and price. check_stock(product) returns stock and price. process_refund(order_id, amount, reason, idempotency_key) writes to a new refunds table.

  • An audit trail. Every call, good or bad, becomes a row in tool_calls: tool, arguments, ok, error, latency.

The registry is the only door:

Validating the result too sounds paranoid until a schema migration renames a column and your agent starts telling customers their order is None.

The HTTP API gets GET /tools (names and argument schemas) and POST /tools/NAME. Bad arguments return 400 with the field; arguments that are valid but break a business rule (refunding an order that hasn't shipped) return 422. The Day 1 /chat endpoint keeps working, now going through the same tools.

Refunds must be safe to repeat

process_refund takes an idempotency_key. The first call with a key creates a refund; any later call with the same key returns the first result with "duplicate": true and changes nothing. The key has a UNIQUE constraint in SQLite, so even two threads racing can't both insert.

Read the full lesson free

This lesson is one of the first 3 in its course, free with an account β€” the full article, implementation guide and scripts. No card needed.

Already a member? Sign in