useAIFrontendTool
The useAIFrontendTool
hook allows you to register functions that the AI can execute in your React components. This enables direct interaction with your UI and state, creating powerful experiences where the AI can manipulate widgets, update data, and trigger actions based on user requests.
Syntax
interface FrontendTool {
name: string;
description: string;
parameters: ZodSchema;
execute: (params: any) => Promise<any> | any;
}
function useAIFrontendTool(tool: FrontendTool): void
Parameters
Property | Type | Required | Description |
---|---|---|---|
name | string | Yes | Unique tool identifier |
description | string | Yes | Clear description for the AI to understand when to use the tool |
parameters | ZodSchema | Yes | Zod schema defining the tool’s parameters |
execute | (params: any) => Promise<any> | any | Yes | Function that executes when the AI calls the tool |
Tool Registration
Basic Example
import { useAIFrontendTool } from "ai-chat-bootstrap";
import { z } from "zod";
function CounterWidget() {
const [counter, setCounter] = useState(0);
useAIFrontendTool({
name: "increment_counter",
description: "Increment the counter by a specified amount",
parameters: z.object({
amount: z.number().default(1).describe("Amount to increment by"),
}),
execute: async (params: { amount: number }) => {
const newValue = counter + params.amount;
setCounter(newValue);
return {
newValue,
amount: params.amount,
message: `Counter incremented by ${params.amount}. New value: ${newValue}`
};
},
});
return <div>Counter: {counter}</div>;
}
Multiple Tools in One Component
function CounterControls() {
const [counter, setCounter] = useState(0);
// Increment tool
useAIFrontendTool({
name: "increment_counter",
description: "Increment the counter by a specified amount",
parameters: z.object({
amount: z.number().default(1).describe("Amount to increment by"),
}),
execute: async (params: { amount: number }) => {
const newValue = counter + params.amount;
setCounter(newValue);
return {
newValue,
amount: params.amount,
message: `Counter incremented by ${params.amount}. New value: ${newValue}`
};
},
});
// Decrement tool
useAIFrontendTool({
name: "decrement_counter",
description: "Decrement the counter by a specified amount",
parameters: z.object({
amount: z.number().default(1).describe("Amount to decrement by"),
}),
execute: async (params: { amount: number }) => {
const newValue = counter - params.amount;
setCounter(newValue);
return {
newValue,
amount: params.amount,
message: `Counter decremented by ${params.amount}. New value: ${newValue}`
};
},
});
// Reset tool
useAIFrontendTool({
name: "reset_counter",
description: "Reset the counter to zero",
parameters: z.object({}),
execute: async () => {
setCounter(0);
return {
newValue: 0,
message: "Counter reset to 0"
};
},
});
return <div>Counter: {counter}</div>;
}
Parameter Validation
Tools use Zod schemas for type-safe parameter validation:
Basic Types
parameters: z.object({
amount: z.number().min(1).max(100).describe("Amount to increment by"),
reason: z.string().optional().describe("Optional reason for the increment"),
enabled: z.boolean().default(true).describe("Whether to enable the feature"),
})
Complex Types
parameters: z.object({
user: z.object({
id: z.string().describe("User ID"),
name: z.string().describe("User name"),
email: z.string().email().describe("User email address"),
}),
settings: z.object({
theme: z.enum(["light", "dark", "auto"]).describe("UI theme"),
notifications: z.boolean().describe("Enable notifications"),
}).optional(),
tags: z.array(z.string()).describe("Array of tags"),
})
Validation with Defaults
parameters: z.object({
amount: z.number().default(1).describe("Amount to change"),
direction: z.enum(["up", "down"]).default("up").describe("Direction to change"),
animate: z.boolean().default(true).describe("Whether to animate the change"),
})
Return Values
Tools can return structured data that the AI uses for context:
Simple Response
execute: async (params) => {
const result = performAction(params);
return {
success: true,
value: result,
message: "Operation completed successfully"
};
}
Detailed Response
execute: async (params) => {
const startTime = Date.now();
const result = await performComplexOperation(params);
const duration = Date.now() - startTime;
return {
success: true,
result: result.data,
metadata: {
duration: `${duration}ms`,
itemsProcessed: result.count,
timestamp: new Date().toISOString(),
},
message: `Processed ${result.count} items in ${duration}ms`,
nextSteps: ["Review results", "Save changes", "Notify users"]
};
}
Error Handling
Tools should handle errors gracefully:
execute: async (params) => {
try {
// Validation
if (params.amount < 0) {
return {
success: false,
error: "Amount cannot be negative",
message: "Please provide a positive amount"
};
}
// Perform operation
const result = await riskyOperation(params);
return {
success: true,
result,
message: "Operation completed successfully"
};
} catch (error) {
console.error("Tool execution failed:", error);
return {
success: false,
error: error.message,
message: "Operation failed, please try again",
details: process.env.NODE_ENV === 'development' ? error.stack : undefined
};
}
}
Complete Integration Example
"use client";
import React, { useState } from "react";
import { ChatContainer, useAIChat, useAIFrontendTool } from "ai-chat-bootstrap";
import { z } from "zod";
export function ChatWithTools() {
const [counter, setCounter] = useState(0);
// Register frontend tools that AI can use
useAIFrontendTool({
name: "increment_counter",
description: "Increment the counter by a specified amount",
parameters: z.object({
amount: z.number().default(1).describe("Amount to increment by"),
}),
execute: async (params: { amount: number }) => {
const newValue = counter + params.amount;
setCounter(newValue);
return {
newValue,
amount: params.amount,
message: `Counter incremented by ${params.amount}. New value: ${newValue}`
};
},
});
const chat = useAIChat({
api: "/api/chat",
systemPrompt: "You are a helpful assistant that can control a counter widget. Use the increment_counter tool when users ask you to change the counter value."
});
return (
<div className="space-y-4">
{/* Counter Display */}
<div className="flex items-center justify-center p-6 border rounded-lg bg-muted/50">
<div className="text-center">
<div className="text-4xl font-bold text-primary mb-2">{counter}</div>
<div className="text-sm text-muted-foreground">Counter Value</div>
</div>
</div>
{/* Chat Interface */}
<div className="h-[420px] w-full">
<ChatContainer
chat={chat}
header={{ title: "AI Assistant with Tools", subtitle: "Can control the counter above" }}
ui={{ placeholder: "Try: 'increment by 3' or 'decrease by 2'" }}
/>
</div>
</div>
);
}
Backend Integration
import { createAzure } from "@ai-sdk/azure";
import { convertToModelMessages, streamText } from "ai";
const azure = createAzure({
resourceName: process.env.AZURE_RESOURCE_NAME!,
apiKey: process.env.AZURE_API_KEY!,
apiVersion: process.env.AZURE_API_VERSION ?? "preview",
});
const model = azure(process.env.AZURE_DEPLOYMENT_ID!);
export async function POST(req: Request) {
const { messages, enrichedSystemPrompt, tools } = await req.json();
const result = await streamText({
model,
messages: [
{ role: "system", content: enrichedSystemPrompt },
...convertToModelMessages(messages),
],
tools,
});
return result.toUIMessageStreamResponse();
}
Advanced Examples
Form Management Tool
function FormManager() {
const [formData, setFormData] = useState({});
const [errors, setErrors] = useState({});
useAIFrontendTool({
name: "update_form_field",
description: "Update a specific field in the form",
parameters: z.object({
field: z.string().describe("Field name to update"),
value: z.any().describe("New value for the field"),
}),
execute: async (params) => {
setFormData(prev => ({ ...prev, [params.field]: params.value }));
// Clear any existing error for this field
if (errors[params.field]) {
setErrors(prev => {
const newErrors = { ...prev };
delete newErrors[params.field];
return newErrors;
});
}
return {
success: true,
field: params.field,
value: params.value,
message: `Updated ${params.field} to: ${params.value}`
};
},
});
return <div>...</div>;
}
Data Fetching Tool
function DataManager() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
useAIFrontendTool({
name: "fetch_user_data",
description: "Fetch user data from the API",
parameters: z.object({
userId: z.string().describe("User ID to fetch data for"),
includeMetadata: z.boolean().default(false).describe("Include metadata in response"),
}),
execute: async (params) => {
try {
setLoading(true);
const response = await fetch(`/api/users/${params.userId}${
params.includeMetadata ? '?include=metadata' : ''
}`);
if (!response.ok) {
throw new Error(`Failed to fetch user: ${response.statusText}`);
}
const userData = await response.json();
setData(prev => [...prev, userData]);
return {
success: true,
user: userData,
message: `Fetched data for user ${userData.name}`,
metadata: params.includeMetadata ? userData.metadata : undefined
};
} catch (error) {
return {
success: false,
error: error.message,
message: `Failed to fetch user data: ${error.message}`
};
} finally {
setLoading(false);
}
},
});
return <div>...</div>;
}
How It Works
- Tool Registration:
useAIFrontendTool
registers tools in a global store - Schema Serialization: Tool schemas are converted to JSON Schema for the backend
- AI Decision: The AI model decides when to call tools based on user input
- Execution: Tools execute in the browser and return results
- Context Update: Tool results are added to the conversation context
- UI Updates: Tools can modify React state, triggering re-renders
Best Practices
- Clear Descriptions: Write detailed tool descriptions so the AI knows when to use them
- Typed Parameters: Use specific Zod schemas for better validation
- Meaningful Returns: Return structured data that helps the AI understand what happened
- Error Handling: Handle failures gracefully and return error information
- State Management: Keep tool state in sync with your component state
- Performance: Avoid heavy computations in tool execution
- Security: Validate all parameters and sanitize inputs
- User Feedback: Provide clear feedback about tool execution results
TypeScript Interface
interface FrontendTool {
name: string;
description: string;
parameters: ZodSchema;
execute: (params: any) => Promise<any> | any;
}
interface ToolDefinition {
type: "function";
function: {
name: string;
description: string;
parameters: JSONSchema;
};
}
Common Tool Patterns
- State Updates: Modify component state based on AI requests
- Form Management: Update form fields and validation
- Data Fetching: Retrieve data from APIs
- UI Actions: Trigger animations, modals, or navigation
- File Operations: Read, write, or process files
- External Integrations: Call external services or APIs
See Also
- Chat with Tools guide - Complete tutorial with live examples
- useAIChat - Main chat hook
- Tool Result Rendering - Custom tool result display
Last updated on