Skip to content

Tools & Function Calling

Tools allow LLMs to call functions you define, enabling them to fetch data, perform calculations, or interact with external systems.

A tool has a name, description, parameter schema, and a run function. You can define parameters using JSON Schema or Zod schemas.

import { llm } from '@providerprotocol/ai';
import { anthropic } from '@providerprotocol/ai/anthropic';
import type { ToolInput } from '@providerprotocol/ai';
const getWeather: ToolInput = {
name: 'get_weather',
description: 'Get the current weather for a location',
parameters: {
type: 'object',
properties: {
location: {
type: 'string',
description: 'City name (e.g., "San Francisco")',
},
unit: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
description: 'Temperature unit',
},
},
required: ['location'],
},
run: async ({ location, unit = 'fahrenheit' }) => {
// Your implementation here
const data = await fetchWeatherAPI(location);
return {
location,
temperature: unit === 'celsius' ? data.tempC : data.tempF,
conditions: data.conditions,
};
},
};

Pass tools to the llm() options:

const claude = llm({
model: anthropic('claude-sonnet-4-20250514'),
system: 'You are a helpful assistant with access to weather data.',
tools: [getWeather],
});
const turn = await claude.generate('What is the weather in Tokyo?');
console.log(turn.response.text);
// "The weather in Tokyo is currently 72°F and sunny."

UPP automatically:

  1. Sends tools to the model
  2. Executes tool calls when the model requests them
  3. Sends results back to the model
  4. Continues until the model produces a final response
User: "What's the weather in Tokyo?"
│
▼
┌─────────────────────────────┐
│ Model decides to call tool │
│ get_weather({location: │
│ "Tokyo"}) │
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ UPP executes tool.run() │
│ Result: {temp: 72, ...} │
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ Model receives result │
│ Generates final response │
└─────────────────────────────┘
│
▼
"The weather in Tokyo is 72°F and sunny."

Provide multiple tools for complex tasks:

const tools: Tool[] = [
{
name: 'get_weather',
description: 'Get current weather for a location',
parameters: {
type: 'object',
properties: {
location: { type: 'string' },
},
required: ['location'],
},
run: async ({ location }) => fetchWeather(location),
},
{
name: 'search_web',
description: 'Search the web for information',
parameters: {
type: 'object',
properties: {
query: { type: 'string' },
},
required: ['query'],
},
run: async ({ query }) => searchWeb(query),
},
{
name: 'calculate',
description: 'Perform mathematical calculations',
parameters: {
type: 'object',
properties: {
expression: { type: 'string', description: 'Math expression to evaluate' },
},
required: ['expression'],
},
run: async ({ expression }) => eval(expression), // Use a safe math parser in production
},
];
const claude = llm({
model: anthropic('claude-sonnet-4-20250514'),
tools,
});

Control tool execution with toolStrategy:

const claude = llm({
model: anthropic('claude-sonnet-4-20250514'),
tools: [getWeather, searchWeb],
toolStrategy: {
// Maximum tool execution rounds (default: 10)
maxIterations: 5,
// Called before each tool execution
onBeforeCall: (tool, params) => {
console.log(`Calling ${tool.name} with`, params);
return true; // Return false to block execution
},
// Called after each tool execution
onAfterCall: (tool, params, result) => {
console.log(`${tool.name} returned:`, result);
},
// Called when a tool throws an error
onError: (tool, params, error) => {
console.error(`${tool.name} failed:`, error);
},
// Called when max iterations reached
onMaxIterations: (iterations) => {
console.warn(`Reached max iterations: ${iterations}`);
},
},
});

Require approval before executing sensitive tools:

const deleteFile: Tool = {
name: 'delete_file',
description: 'Delete a file from the filesystem',
parameters: {
type: 'object',
properties: {
path: { type: 'string' },
},
required: ['path'],
},
// Approval function - return true to allow, false to block
approval: async ({ path }) => {
const answer = await prompt(`Delete ${path}? (y/n) `);
return answer.toLowerCase() === 'y';
},
run: async ({ path }) => {
await fs.unlink(path);
return { success: true };
},
};

The Turn contains information about all tool executions:

const turn = await claude.generate('What is the weather in NYC and LA?');
// All tool executions
for (const execution of turn.toolExecutions) {
console.log('Tool:', execution.toolName);
console.log('Args:', execution.arguments);
console.log('Result:', execution.result);
console.log('Duration:', execution.duration, 'ms');
console.log('Error:', execution.isError);
}
// Number of inference cycles (1 + tool rounds)
console.log('Cycles:', turn.cycles);

When the model requests multiple tools simultaneously, they execute in parallel:

// User asks: "What's the weather in NYC, LA, and Chicago?"
// Model calls get_weather 3 times in parallel
const turn = await claude.generate(
'What is the weather in NYC, LA, and Chicago?'
);
// All three executed concurrently
console.log(turn.toolExecutions.length); // 3

Tool execution events appear in the stream:

const stream = claude.stream('What is the weather in Tokyo?');
for await (const event of stream) {
switch (event.type) {
case 'text_delta':
process.stdout.write(event.delta.text ?? '');
break;
case 'tool_call_delta':
console.log('\n[Tool call]', event.delta.toolName);
break;
case 'tool_execution_start':
console.log(`[Executing ${event.delta.toolName}...]`);
break;
case 'tool_execution_end':
console.log(`[Result: ${JSON.stringify(event.delta.result)}]`);
break;
}
}

Add provider-specific metadata to tools:

const tool: Tool = {
name: 'search',
description: 'Search for information',
parameters: { type: 'object', properties: {} },
metadata: {
// Anthropic-specific: enable prompt caching
anthropic: {
cache_control: { type: 'ephemeral' },
},
},
run: async () => ({ results: [] }),
};

Tool parameters use JSON Schema:

const createUser: Tool = {
name: 'create_user',
description: 'Create a new user account',
parameters: {
type: 'object',
properties: {
name: {
type: 'string',
minLength: 1,
maxLength: 100,
},
email: {
type: 'string',
format: 'email',
},
age: {
type: 'integer',
minimum: 0,
maximum: 150,
},
role: {
type: 'string',
enum: ['admin', 'user', 'guest'],
default: 'user',
},
tags: {
type: 'array',
items: { type: 'string' },
maxItems: 10,
},
},
required: ['name', 'email'],
},
run: async (params) => createUserInDB(params),
};

Return errors gracefully or let them propagate:

const riskyTool: Tool = {
name: 'risky_operation',
description: 'An operation that might fail',
parameters: { type: 'object', properties: {} },
run: async () => {
try {
return await performRiskyOperation();
} catch (error) {
// Return error as result (model sees error message)
return { error: error.message };
}
},
};

If a tool throws an exception, UPP catches it and reports it to the model as an error result.

import { llm, Tool, Thread } from '@providerprotocol/ai';
import { openai } from '@providerprotocol/ai/openai';
// Define tools
const tools: Tool[] = [
{
name: 'get_stock_price',
description: 'Get the current stock price for a ticker symbol',
parameters: {
type: 'object',
properties: {
symbol: {
type: 'string',
description: 'Stock ticker (e.g., AAPL)',
},
},
required: ['symbol'],
},
run: async ({ symbol }) => {
const price = await fetchStockPrice(symbol);
return { symbol, price, currency: 'USD' };
},
},
{
name: 'calculate_return',
description: 'Calculate investment return',
parameters: {
type: 'object',
properties: {
principal: { type: 'number' },
currentValue: { type: 'number' },
},
required: ['principal', 'currentValue'],
},
run: async ({ principal, currentValue }) => {
const returnPct = ((currentValue - principal) / principal) * 100;
return { returnPercentage: returnPct.toFixed(2) + '%' };
},
},
];
// Create LLM with tools
const gpt = llm({
model: openai('gpt-4o'),
system: 'You are a financial assistant.',
tools,
toolStrategy: {
maxIterations: 5,
onBeforeCall: (tool, params) => {
console.log(`→ ${tool.name}(${JSON.stringify(params)})`);
return true;
},
},
});
// Use in conversation
const thread = new Thread();
const turn = await gpt.generate(
thread,
'I bought 100 shares of AAPL at $150 each. What is my return?'
);
thread.append(turn);
console.log(turn.response.text);