Function Calling (Tools)
Transform your AI from a passive chatbot into an active assistant that can execute actions in your application.
What are Tools?
Tools (also called function calling) allow the AI to perform actions like:
- Creating or updating records
- Triggering workflows
- Filtering or sorting data
- Navigating between pages
- Executing business logic
How It Works
- You define tools - Specify what functions are available and their parameters
- AI decides when to call - Based on user intent, AI selects the appropriate tool
- Handler executes - Your JavaScript function runs
- AI uses result - AI incorporates the result into its response
Multi-Step Tool Execution
When the AI needs to perform multiple actions, InAppAI automatically handles iterative tool calling:
- AI returns tool calls — One or more tools per round
- All tools execute in parallel — Results collected via
Promise.all - Results sent back — AI evaluates and may call more tools
- Loop until done — Continues until AI responds with text or
maxToolRoundsis reached
This means requests like “Add 3 tasks” work automatically — the AI calls addTodo multiple times across rounds until all tasks are created.
Best Practices for Multi-Step Tools
- Use unique IDs: Use
crypto.randomUUID()instead ofDate.now()to avoid ID collisions when tools execute in parallel - Use functional state updates:
setItems(prev => [...prev, newItem])instead ofsetItems([...items, newItem])to ensure parallel updates chain correctly
Tool Action Recording
Every tool call is automatically recorded in the assistant message’s toolActions array. This gives the AI a structured memory of what it did, enabling it to recall its own actions in later turns.
Each entry is a ToolAction:
interface ToolAction {
tool: string; // Tool name (e.g., "addTodo")
args: Record<string, any>; // Parameters passed to the handler
result: any; // Return value from the handler
}
Example: After the AI adds a todo, the assistant message includes:
{
id: '123-assistant',
role: 'assistant',
content: 'I\'ve added "Buy groceries" to your list!',
toolActions: [
{
tool: 'addTodo',
args: { text: 'Buy groceries', priority: 'high' },
result: { success: true, todo: { id: 'abc', text: 'Buy groceries' } },
},
],
}
This means if the user later asks “What did you just do?”, the AI can answer accurately because the full tool execution history is part of the conversation.
Tip: Return meaningful data from your tool handlers — the return value is stored in result and sent to the AI as context in subsequent turns. The more informative the result, the better the AI can recall its actions.
Basic Example
import { InAppAI } from '@inappai/react';
import { useState } from 'react';
function CounterApp() {
const [count, setCount] = useState(0);
return (
<div>
<p>Counter: {count}</p>
<InAppAI
agentId="your-agent-id"
tools={[
{
name: 'incrementCounter',
description: 'Increment the counter by a specified amount',
parameters: {
type: 'object',
properties: {
amount: {
type: 'number',
description: 'Amount to increment (default: 1)',
},
},
required: [],
},
handler: async (params) => {
const amount = params.amount || 1;
setCount(prev => prev + amount);
return { success: true, newCount: count + amount };
},
},
]}
/>
</div>
);
}
User: “Increase the counter by 5” AI: [Calls incrementCounter({ amount: 5 })] “I’ve incremented the counter by 5. It’s now at 5.”
Tool Interface
interface Tool {
name: string; // Unique identifier
description: string; // When and how to use this tool
parameters: { // JSON Schema definition
type: 'object';
properties: Record<string, {
type: string; // 'string', 'number', 'boolean', 'array', 'object'
description: string; // Parameter explanation
enum?: string[]; // Optional: allowed values
}>;
required: string[]; // Required parameter names
};
handler: (params: any) => Promise<any>; // Async function
}
Complete Example: Todo App
Let the AI manage a todo list:
import { InAppAI } from '@inappai/react';
import { useState } from 'react';
function TodoApp() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Buy groceries', completed: false },
{ id: 2, text: 'Call dentist', completed: false },
]);
return (
<InAppAI
agentId="your-agent-id"
tools={[
{
name: 'addTodo',
description: 'Create a new todo item',
parameters: {
type: 'object',
properties: {
text: {
type: 'string',
description: 'The task description',
},
},
required: ['text'],
},
handler: async (params) => {
const newTodo = {
id: Date.now(),
text: params.text,
completed: false,
};
setTodos([...todos, newTodo]);
return { success: true, todo: newTodo };
},
},
{
name: 'completeTodo',
description: 'Mark a todo as completed',
parameters: {
type: 'object',
properties: {
id: {
type: 'number',
description: 'The ID of the todo to complete',
},
},
required: ['id'],
},
handler: async (params) => {
const todo = todos.find(t => t.id === params.id);
if (!todo) {
return { success: false, message: 'Todo not found' };
}
setTodos(todos.map(t =>
t.id === params.id ? { ...t, completed: true } : t
));
return { success: true, message: `Completed: ${todo.text}` };
},
},
{
name: 'deleteTodo',
description: 'Delete a todo item',
parameters: {
type: 'object',
properties: {
id: {
type: 'number',
description: 'The ID of the todo to delete',
},
},
required: ['id'],
},
handler: async (params) => {
setTodos(todos.filter(t => t.id !== params.id));
return { success: true, message: 'Todo deleted' };
},
},
]}
/>
);
}
Conversation Example:
- User: “Add a task to finish the report”
- AI: [Calls addTodo({ text: ‘Finish the report’ })] “I’ve added ‘Finish the report’ to your todo list.”
- User: “Mark the groceries task as done”
- AI: [Calls completeTodo({ id: 1 })] “I’ve marked ‘Buy groceries’ as completed.”
Multiple Tools
You can provide multiple tools for different actions:
<InAppAI
agentId="your-agent-id"
tools={[
{
name: 'searchProducts',
description: 'Search for products by name or category',
parameters: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search query' },
},
required: ['query'],
},
handler: async ({ query }) => {
const results = await fetch(`/api/products?q=${query}`);
return await results.json();
},
},
{
name: 'addToCart',
description: 'Add a product to the shopping cart',
parameters: {
type: 'object',
properties: {
productId: { type: 'string', description: 'Product ID' },
quantity: { type: 'number', description: 'Quantity to add' },
},
required: ['productId'],
},
handler: async ({ productId, quantity = 1 }) => {
// Add to cart logic
return { success: true, cartTotal: newTotal };
},
},
{
name: 'getCart',
description: 'Get current shopping cart contents',
parameters: {
type: 'object',
properties: {},
},
handler: async () => {
return {
items: cart,
total: calculateTotal(cart),
};
},
},
]}
/>
The AI will automatically choose the right tool based on user intent.
Parameter Types
String Parameters
{
name: 'sendEmail',
parameters: {
type: 'object',
properties: {
to: { type: 'string', description: 'Recipient email address' },
subject: { type: 'string', description: 'Email subject' },
body: { type: 'string', description: 'Email body content' },
},
required: ['to', 'subject', 'body'],
},
handler: async ({ to, subject, body }) => {
// Send email logic
},
}
Number Parameters
{
name: 'updateQuantity',
parameters: {
type: 'object',
properties: {
itemId: { type: 'number', description: 'Item ID' },
quantity: { type: 'number', description: 'New quantity' },
},
required: ['itemId', 'quantity'],
},
handler: async ({ itemId, quantity }) => {
// Update quantity logic
},
}
Enum Parameters (Constrained Values)
{
name: 'filterProducts',
parameters: {
type: 'object',
properties: {
category: {
type: 'string',
enum: ['electronics', 'clothing', 'books', 'home'],
description: 'Product category',
},
sortBy: {
type: 'string',
enum: ['price-asc', 'price-desc', 'name', 'rating'],
description: 'Sort order',
},
},
required: ['category'],
},
handler: async ({ category, sortBy = 'name' }) => {
// Filter and sort logic
},
}
Boolean Parameters
{
name: 'toggleFeature',
parameters: {
type: 'object',
properties: {
featureName: { type: 'string', description: 'Feature identifier' },
enabled: { type: 'boolean', description: 'Enable or disable' },
},
required: ['featureName', 'enabled'],
},
handler: async ({ featureName, enabled }) => {
// Toggle feature logic
},
}
Best Practices
1. Clear Descriptions
Write descriptions that explain when and why to use the tool:
// ❌ Vague
description: 'Updates a todo'
// ✅ Clear
description: 'Mark a todo as completed when the user indicates they finished a task'
2. Error Handling
Always handle errors gracefully:
handler: async (params) => {
try {
const result = await updateDatabase(params);
return { success: true, data: result };
} catch (error) {
console.error('Tool error:', error);
return {
success: false,
message: 'Failed to update. Please try again.',
};
}
}
3. Return Meaningful Data
Return information the AI can use in its response:
// ❌ Minimal
return { success: true };
// ✅ Informative
return {
success: true,
message: 'Todo added successfully',
todo: newTodo,
totalTodos: todos.length + 1,
};
4. Validate Inputs
Check parameters before executing:
handler: async ({ email, amount }) => {
if (!email || !email.includes('@')) {
return { success: false, message: 'Invalid email address' };
}
if (amount <= 0) {
return { success: false, message: 'Amount must be positive' };
}
// Proceed with logic
}
5. Async Operations
Use async/await for API calls or database operations:
handler: async ({ userId }) => {
const user = await fetch(`/api/users/${userId}`).then(r => r.json());
const orders = await fetch(`/api/users/${userId}/orders`).then(r => r.json());
return { user, orders };
}
Security Considerations
1. Validate User Permissions
Check if the user is authorized:
handler: async ({ itemId }) => {
const user = getCurrentUser();
if (!user.isAdmin) {
return { success: false, message: 'Permission denied' };
}
// Admin-only logic
}
2. Sanitize Inputs
Never trust tool parameters directly:
handler: async ({ query }) => {
// Sanitize query to prevent injection
const sanitized = query.replace(/[^\w\s]/gi, '');
const results = await searchDatabase(sanitized);
return results;
}
3. Rate Limiting
Prevent abuse by limiting calls:
const callCounts = new Map();
handler: async ({ action }) => {
const userId = getCurrentUser().id;
const count = callCounts.get(userId) || 0;
if (count > 10) {
return { success: false, message: 'Rate limit exceeded' };
}
callCounts.set(userId, count + 1);
// Execute action
}
Advanced Patterns
Tool Chaining
Tools can call other tools:
const tools = [
{
name: 'processOrder',
handler: async ({ orderId }) => {
// Validate order
const order = await validateOrder(orderId);
// Charge payment (could be another tool)
const payment = await chargePayment(order);
// Send confirmation (could be another tool)
await sendConfirmation(order.email);
return { success: true, order, payment };
},
},
];
Stateful Tools
Access and modify component state:
const [filter, setFilter] = useState('all');
const [sortBy, setSortBy] = useState('date');
tools={[
{
name: 'updateView',
description: 'Update the current view filter and sort order',
parameters: {
type: 'object',
properties: {
filter: { type: 'string', enum: ['all', 'active', 'completed'] },
sortBy: { type: 'string', enum: ['date', 'priority', 'name'] },
},
required: [],
},
handler: async ({ filter: newFilter, sortBy: newSort }) => {
if (newFilter) setFilter(newFilter);
if (newSort) setSortBy(newSort);
return {
success: true,
filter: newFilter || filter,
sortBy: newSort || sortBy,
};
},
},
]}
Debugging Tools
Use console logging to debug tool execution:
handler: async (params) => {
console.log('Tool called:', params);
const result = await executeLogic(params);
console.log('Tool result:', result);
return result;
}
Next Steps
- Context - Pass application state to AI
- Backend Settings - Configure AI provider and model
- Knowledge Base - Add custom documentation
- API Reference - Complete Tool type definition
- Live Demo - See function calling in action