1 · Where you encounter this
ChatGPT can connect to apps, and the OpenAI API documents function calling as a multi-step interaction. The application executes a tool and returns its result. Our example accepts a prewritten call list and runs a local order lookup. It demonstrates execution controls, not autonomous planning or a ChatGPT connector implementation.
Official product / API documentation ↗Source checked 2026-10-25. Product behavior is publicly documented; the code below is an independent teaching implementation.
2 · The engineering decision
A model-produced tool name and arguments are a proposal, not permission. Validate against an allowlist and argument contract before dispatch. Bound actual executions, cache results by call ID and reject ID reuse with different arguments. A trace should distinguish blocked, invalid, replayed and executed calls. Read-only fixtures let you test this boundary without sending messages or mutating real systems.
- call-1 uses lookup_order with A17 and executes once.
- The duplicate call-1 returns the cached result and consumes no additional execution budget.
- delete_order is absent from the allowlist and is blocked.
- Final executed count is 1. Change budget to 0 and the lookup is stopped before execution.
3 · Run and change the input
Runs entirely in this browser. No model call, network request, API key or upload. Input is JSON; this is a deterministic component demo, not a generative assistant.
{
"executed": 1,
"trace": [
{
"id": "call-1",
"status": "executed",
"result": {
"status": "shipped",
"tracking": "DEMO-123"
}
},
{
"id": "call-1",
"status": "replayed",
"result": {
"status": "shipped",
"tracking": "DEMO-123"
}
},
{
"id": "call-2",
"status": "blocked_tool"
}
]
}Initial output above is the result of the example, computed during the build. Run after editing to calculate your result.
4 · Complete source code
Requires a modern browser or Node.js 18+. Download the module, save this runner beside it and execute node demo.mjs. No npm dependencies.
Download source ↓// Execute a supplied tool plan against a local read-only fixture. No agent/LLM.
export const example = {budget: 2, calls: [
{id: 'call-1', name: 'lookup_order', arguments: {order_id: 'A17'}},
{id: 'call-1', name: 'lookup_order', arguments: {order_id: 'A17'}},
{id: 'call-2', name: 'delete_order', arguments: {order_id: 'A17'}}
]};
export function run({budget, calls}) {
if (!Number.isInteger(budget) || budget < 0 || budget > 10) throw Error('budget must be 0–10');
if (!Array.isArray(calls) || calls.length > 20) throw Error('At most 20 calls');
const orders = new Map([['A17', {status: 'shipped', tracking: 'DEMO-123'}]]);
const cache = new Map(); let executed = 0;
const trace = calls.map(call => {
if (!call || typeof call.id !== 'string' || typeof call.name !== 'string') return {status: 'invalid_call'};
if (call.name !== 'lookup_order') return {id: call.id, status: 'blocked_tool'};
const args = call.arguments;
if (!args || typeof args !== 'object' || Array.isArray(args) ||
Object.keys(args).length !== 1 || typeof args.order_id !== 'string') return {id: call.id, status: 'invalid_arguments'};
const fingerprint = JSON.stringify([call.name, args.order_id]);
if (cache.has(call.id)) {
const prior = cache.get(call.id);
return {id: call.id, status: prior.fingerprint === fingerprint ? 'replayed' : 'id_conflict',
...(prior.fingerprint === fingerprint ? {result: prior.result} : {})};
}
if (executed >= budget) return {id: call.id, status: 'budget_exhausted'};
executed++;
const result = orders.get(args.order_id) || {status: 'not_found'};
cache.set(call.id, {fingerprint, result});
return {id: call.id, status: 'executed', result};
});
return {executed, trace};
}
import {run, example} from './bounded-tools.mjs';
console.log(JSON.stringify(run(example), null, 2));5 · Failure cases and production gaps
This cache lasts only for one run. Real side-effecting tools need durable idempotency, authorization, timeouts, cancellation and an audit trail. Replaying an identical ID is different from repeating a request with a new ID. A read-only status response should never be presented as evidence that an order was changed.
6 · Your implementation exercise
Test unknown tools, extra argument keys, reused IDs with different orders and exhausted budgets. Then design a timeout and approval state machine before adding any write operation.
Deliver changed code, at least one passing and one failing case, and an explanation of what the test cannot prove.
Continue with the underlying concept →