TypeScript AI Tool Calling
A complete guide to registering type-safe tools, executing local functions, and coordinating multi-step agentic execution loops in TypeScript.
TL;DR
- Define tool argument schemas using standard
parametersJSON Schema objects. - Dispatch model
tool_callsrequests to local TypeScript functions. - Append returned tool execution results with the
toolrole.
Tool Definition and Schemas
Tool definition objectDeclare function signature and parameters for OpenAI.
const weatherTool = {
type: 'function' as const,
function: {
name: 'getWeather',
description: 'Get weather',
parameters: {
type: 'object',
properties: { city: { type: 'string' } },
required: ['city'],
},
},
};interface ToolDefinitionDefine reusable TypeScript interface for custom tools.
interface ToolDefinition {
name: string;
description: string;
parameters: Record<string, unknown>;
}tool_choice parameterControl whether model must execute a tool or answer freely.
const cfg = {
tool_choice: 'auto' as const,
// or: { type: 'function', function: { name: 'fn' } }
};Executing Local Functions
Typed tool registryMap tool names to typed execution handlers safely.
type ToolFn = (args: any) => Promise<string>;
const handlers: Record<string, ToolFn> = {
getWeather: async ({ city }) => {
return JSON.stringify({ temp: 72, city });
},
};Execute tool call safelyParse JSON arguments and invoke target handler.
async function executeCall(call: any) {
const fn = handlers[call.function.name];
if (!fn) throw new Error('Unknown tool');
const args = JSON.parse(call.function.arguments);
return await fn(args);
}Tool result messageFormat function execution output for conversation thread.
function makeToolMsg(id: string, output: string) {
return {
role: 'tool' as const,
tool_call_id: id,
content: output,
};
}Multi-Step Agentic Loops
Agent loop conditionRun agent loop until model emits standard text response.
let step = 0;
const maxSteps = 5;
while (step < maxSteps) {
const res = await getCompletion(messages);
const msg = res.choices[0].message;
messages.push(msg);
if (!msg.tool_calls?.length) break;
step++;
}Parallel tool dispatchExecute multiple concurrent tool calls with Promise.all.
const toolResults = await Promise.all(
msg.tool_calls.map(async (call) => ({
role: 'tool' as const,
tool_call_id: call.id,
content: await executeCall(call),
}))
);
messages.push(...toolResults);Agent step guardThrow descriptive error when maximum step threshold hit.
if (step >= maxSteps) {
throw new Error('Agent exceeded maximum steps');
}Anthropic Claude Tools
Anthropic tool schemaFormat tool definitions for Claude messages API.
const claudeTools = [{
name: 'lookup_user',
description: 'Lookup user record',
input_schema: {
type: 'object',
properties: { id: { type: 'string' } },
required: ['id'],
},
}];Claude tool_use blockDetect tool invocation blocks within Claude response.
const toolUse = res.content.find(
(block) => block.type === 'tool_use'
);
if (toolUse) {
const input = toolUse.input;
console.log('Tool called:', toolUse.name);
}Claude tool_result blockSend tool response block back to Anthropic API.
const reply = {
role: 'user' as const,
content: [{
type: 'tool_result' as const,
tool_use_id: toolUse.id,
content: 'User: active',
}],
};Tips
- Always map your tool definitions to a strongly typed TypeScript
Recordof handler functions to ensure complete compile-time routing safety. - Set a conservative
maxStepsloop counter when orchestrating autonomous agent loops to prevent unbounded execution and runaway token billing.
Warnings
- Never execute arbitrary system shell commands or raw database queries without validating tool arguments against a strict
z.object()schema. - Remember to return a matching
tool_call_idin every tool response message or provider APIs will reject the conversation thread.
In Practice
An autonomous TypeScript agent that resolves user queries by executing typed weather tool functions.
- Register the tool schema with OpenAI chat completions.
- Inspect the assistant response for requested tool calls.
- Execute the local TypeScript weather handler with parsed arguments.
- Append the tool message and request the final synthesis.
import { OpenAI } from 'openai';
export async function runAgent(prompt: string) {
const openai = new OpenAI();
const tools = [{
type: 'function' as const,
function: {
name: 'getWeather',
parameters: { type: 'object', properties: {} },
},
}];
const res = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: prompt }],
tools,
});
const call = res.choices[0].message.tool_calls?.[0];
if (!call) return res.choices[0].message.content;
return `Executed ${call.function.name} successfully`;
}FAQ
The application sends user messages alongside tool definitions. If the model determines a tool is needed, it responds with tool_calls containing JSON arguments. Your code parses the arguments, executes the local TypeScript function, and appends a message with role: 'tool' containing the result before calling the model again.
Declare an interface where keys match tool names and values are typed async functions. You can use Record<string, (args: any) => Promise<unknown>> or map each handler to its specific Zod-inferred argument type for strict typing.
Maintain an integer counter inside your while loop that increments on each tool execution step. Compare it against a configured maxSteps threshold, such as 5 steps, throwing an error or prompting the user if exceeded.