An AI tool integration needs an explicit boundary between a proposed action and application execution. This example accepts a normalized tool-call envelope and dispatches one named operation: looking up the available quantity of an allowed product. The lookup really runs; the proposed model calls are fixtures.
The application owns the operation name, argument contract and data source. A model-provided name never becomes a Python expression, shell command or dynamically imported function. This tutorial focuses on dispatch and execution rather than merely checking generated classification JSON.
Prerequisites
Use Python 3.12 or another supported Python 3 release. Save the complete program as tools.py. No SDK, package installation, server or API key is required.
import json
STOCK = {'PEN': 12, 'BOOK': 3}
def dispatch(raw):
if len(raw.encode('utf-8')) > 2048:
raise ValueError('request too large')
def unique(pairs):
result = {}
for key, value in pairs:
if key in result:
raise ValueError('duplicate key')
result[key] = value
return result
call = json.loads(raw, object_pairs_hook=unique)
if type(call) is not dict or set(call) != {'name', 'arguments'}:
raise ValueError('invalid envelope')
if call['name'] != 'stock_lookup':
raise ValueError('unknown tool')
args = call['arguments']
if type(args) is not dict or set(args) != {'sku'}:
raise ValueError('invalid arguments')
sku = args['sku']
if type(sku) is not str or sku not in STOCK:
raise ValueError('unknown sku')
return {'sku': sku, 'available': STOCK[sku]}
fixture = '{"name":"stock_lookup","arguments":{"sku":"PEN"}}'
assert dispatch(fixture) == {'sku':'PEN', 'available':12}
for bad in ['[]', '{', fixture.replace('stock_lookup','shell'),
fixture.replace('"PEN"','true'),
fixture.replace('"sku":"PEN"','"sku":"PEN","sku":"BOOK"'),
fixture.replace('"sku":"PEN"','"sku":"PEN","admin":true'),
' ' * 2049]:
try:
dispatch(bad)
except ValueError:
pass
else:
raise AssertionError('bad call accepted')
print(json.dumps(dispatch(fixture), sort_keys=True))
print('7 rejected fixtures')
Run and expected output
python3 tools.py
{"available": 12, "sku": "PEN"}
7 rejected fixtures
How execution stays explicit
The parser rejects duplicate object keys before dictionary construction could hide them. The envelope must contain exactly name and arguments. Only stock_lookup reaches the application lookup, and only known string SKUs reach the dictionary. Extra fields do not acquire meaning accidentally.
The seven rejection fixtures exercise a list envelope, malformed JSON, an unknown operation, a Boolean SKU, duplicate keys, an unexpected argument and an oversized request. These tests verify the boundary around the actual lookup; they do not demonstrate that a model will choose the correct tool.
Integrating and troubleshooting
For a real provider, first adapt its documented tool-call response into this small envelope. Provider-specific response parsing and returning tool results to a conversation are intentionally absent here. Keep that adapter separate so transport changes do not alter the dispatcher contract.
The byte-size check happens after the caller has supplied a string. A network service must also limit its incoming body before allocating it. This dispatcher provides no user identity or authorization: a production stock service must scope the lookup to the authenticated caller's permitted inventory. For operations that modify state, add application authorization, replay handling and transaction rules before execution.
If a previously accepted fixture fails after changing the contract, update the producer and dispatcher together. Do not relax the unknown-tool branch to execute arbitrary names. Keep tests for forbidden operations even when adding another legitimate tool.
Primary references
Anthropic's tool-design guide discusses tool boundaries and testing. Python's JSON reference documents object_pairs_hook. Browse Python tutorials and AI tutorials.