Sections in this lesson
Tool Calling
Treat a model tool request as untrusted input and execute it through an application policy.
Reviewed: · Content owner: Interview Preparator
What you will learn
- Separate a proposed action from execution.
- Validate arguments and authorize resource access.
- Bound retries, time and side effects.
Before you start
The flow
- 1.Model proposes
- 2.Validate
- 3.Authorize
- 4.Execute
- 5.Return observation
A request is not permission
The model chooses a tool name and arguments from exposed capabilities. The application resolves the tool, validates its schema and checks the authenticated user against the specific resource. Never accept a role, tenant or permission claimed in generated arguments.
Expose narrow, task-oriented tools such as lookupOrder instead of arbitrary SQL or shell execution. Clear descriptions improve tool selection but do not replace enforcement.
Design for retries and failure
Read operations can still expose private data. Write operations additionally need confirmation rules, idempotency keys and an audit trail. A timeout does not prove that a write failed; a blind retry may execute it twice.
Set per-tool deadlines, maximum calls and payload limits. Return a safe, structured error so the model can recover without receiving stack traces, secrets or hidden policy details.
Evaluate the action trace
A good final answer can hide an unauthorized intermediate call. Check tool selection, argument validity, access denial and side effects separately. The example proves that denial happens before the execution counter increases.
Try it
import assert from 'node:assert/strict';let executions = 0;function executeTool(request, session) { if (request.name !== 'lookupOrder') throw new Error('Unknown tool'); const args = request.arguments; if (!args || typeof args !== 'object' || Array.isArray(args) || Object.keys(args).join(',') !== 'orderId' || typeof args.orderId !== 'string' || !/^ORD-\d+$/.test(args.orderId)) { throw new Error('Invalid arguments'); } // In production, query by authenticated tenant AND order ID. if (!session.allowedOrders.includes(args.orderId)) throw new Error('Forbidden'); executions++; return { orderId: args.orderId, status: 'SHIPPED' };}const request = { name: 'lookupOrder', arguments: { orderId: 'ORD-42' } };assert.throws(() => executeTool(request, { allowedOrders: [] }), /Forbidden/);assert.equal(executions, 0);assert.equal(executeTool(request, { allowedOrders: ['ORD-42'] }).status, 'SHIPPED');assert.throws(() => executeTool({ name: 'deleteAll', arguments: {} }, { allowedOrders: [] }));console.log({ executions });Run with Node.js 22: node examples/generative-ai/tool-calling.mjs. This is a deterministic teaching example, not a live model call.
Interview questions
Does a tool schema authorize an action?
No. It constrains structure; identity and resource permissions are enforced by the service.
Why is retrying a write dangerous?
A lost response can follow a successful write. Use idempotency and inspect operation state before retrying.
What makes a tool easy to use safely?
A narrow contract, explicit parameters, bounded results, clear errors and server-side permissions.
Practice
Add a cancellation tool requiring explicit confirmation and an idempotency key.
Check your solution
- Denied calls produce no side effect.
- Repeating an approved key does not repeat cancellation.
- Unknown tools and extra arguments are rejected.
Official references
Continue learning
Category sequence
Generative AI
Lesson 7 / 8
Was this lesson useful?
Change notes: Initial bilingual lesson and executable example.