Function Tools
Create custom tools from TypeScript functions
Function tools are the simplest way to give your AI agents custom capabilities. When you need your agent to perform specific actions—like fetching data from your database, calling an API, or performing calculations—you create a TypeScript function and ADK-TS automatically converts it into a tool the agent can use.
Unlike pre-built tools, function tools let you implement exactly the logic you need. ADK-TS handles all the complexity of making your function work with the LLM: extracting parameter information from your code, generating schemas, validating inputs, and converting data types.
ADK-TS offers multiple approaches for building function-based tools:
- Standard Function Tools - Convert regular TypeScript functions into AI-compatible tools with three creation methods
- Long Running Function Tools - Tools for operations that take time, like file processing or human approval workflows
- Agents-as-a-Tool - Use specialized agents as reusable tools within other agents
Quick Start
Here's how to create and use a function tool with your agent:
import { AgentBuilder, createFunctionTool, LlmAgent } from "@iqai/adk";
import * as dotenv from "dotenv";
dotenv.config();
// Provides weather information for a specified city.
function getWeather(city: string, unit: string = "celsius") {
// Your implementation goes here
// In a real app, you'd call a weather API
return {
status: "success",
city,
temperature: 72,
unit,
conditions: "sunny",
humidity: 65,
};
}
// Create a function tool with explicit description
const weatherTool = createFunctionTool(getWeather, {
description:
"Fetches current weather information for a city. Use this when the user asks about weather conditions, temperature, or forecasts.",
name: "get_weather_tool",
});
// Use the function tool in your agent
const weatherAgent = new LlmAgent({
name: "weather_agent",
description: "Provides weather information",
tools: [weatherTool],
});
const rootAgent = () => {
return AgentBuilder.create("root_agent")
.withModel("gemini-2.0-flash")
.withDescription("You are a general purpose assistant.")
.withSubAgents([weatherAgent])
.build();
};
async function main() {
const { runner } = await rootAgent();
const response = await runner.ask(
"What's the weather in New York in fahrenheit?"
);
console.log(`🤖 Response: ${response}`);
}
main().catch(console.error);That's it! You can now use createFunctionTool to wrap your functions with explicit descriptions, or pass functions directly to the tools array (see Creating Function Tools for all options).
Standard Function Tools
Standard function tools make it easy to turn regular TypeScript functions into AI-compatible tools. You have three ways to create them:
- Direct Function - Pass your function directly to the
toolsarray (simplest approach, requires JSDoc) - createFunctionTool - Wrap with
createFunctionToolutility for explicit control (recommended) - FunctionTool Class - Use the
FunctionToolclass directly for maximum flexibility
How Standard Function Tools Work
When you create a standard function tool, ADK-TS:
- Analyzes your function's signature and JSDoc comments
- Extracts parameter names, types, and default values
- Creates a JSON schema the LLM can understand
- Handles type conversion between the LLM and your function
- Provides automatic access to
ToolContextwhen needed
Defining Standard Function Signatures
ADK-TS automatically converts TypeScript function signatures into tool schemas that LLMs can understand. This involves analyzing parameters, return types, and JSDoc comments to create a clear contract for how the tool should be used.
Parameters
The framework analyzes your function's parameters to determine which are required and which are optional. This is interpreted as follows:
- Required parameters: Parameters without default values or
?syntax become required fields. The LLM must provide values for these. - Optional parameters: Parameters with default values or
?syntax become optional fields. The LLM can omit them. - Rest parameters (
...args): Completely ignored by ADK-TS (not included in the schema)
Example: Required and Optional Parameters
/**
* Searches for flights with flexible date options.
*
* @param destination - The destination city
* @param departureDate - The desired departure date
* @param flexibleDays - Number of flexible days to search
* @param notes - Optional travel notes
*/
function searchFlights(
destination: string, // Required - no default value
departureDate: string, // Required - no default value
flexibleDays: number = 0, // Optional - has default value
notes?: string // Optional - uses ? syntax
) {
return {
status: "success",
flights: [],
searchedDays: flexibleDays,
notes: notes || "No special requests",
};
}In this example:
destinationanddepartureDateare required - the LLM must always provide valuesflexibleDaysis optional with default0- used if LLM doesn't provide a valuenotesis optional with?syntax - can be undefined if not provided
Parameter Detection
ADK-TS determines parameter requirements by analyzing your TypeScript function signature. It only looks at the parameter definitions—it doesn't execute your code or check runtime behavior.
Return Type
Standard function tools work best when they return structured objects rather than simple values. This gives the LLM context about what each piece of returned data means. ADK-TS automatically handles different return types:
- Object returns: Used as-is, preserving all keys and values
- Simple values: Wrapped in an object with a
resultkey - Undefined/null: Converted to an empty object
{}
For best results, include a status field in your return object to indicate success or failure. This helps the LLM understand whether the operation worked and how to respond.
Example: Structured Return Values
// ❌ Poor - Not descriptive
function validateEmail(email: string): boolean {
return email.includes("@");
}
// ✅ Good - Descriptive with status
function validateEmail(email: string): {
status: string;
isValid: boolean;
message: string;
} {
const isValid = email.includes("@") && email.includes(".");
return {
status: isValid ? "success" : "error",
isValid,
message: isValid ? "Email format is valid" : "Email format is invalid",
};
}JSDoc Comments
JSDoc comments are crucial for function tools. ADK-TS extracts these comments to:
- Create the tool's description that helps the LLM understand when to use it
- Generate parameter descriptions from
@paramtags - Extract type information when TypeScript types aren't specific enough
Write clear JSDoc comments that explain your tool's purpose, when to use it, parameter meanings, and what it returns.
Example: Comprehensive JSDoc
/**
* Calculates the total price of an order including tax and shipping.
* Use this when you need to compute the final cost that a customer will pay.
*
* @param subtotal - The sum of all item prices before tax
* @param taxRate - The tax percentage to apply (e.g., 0.08 for 8%)
* @param shippingCost - The cost of shipping, defaults to free shipping if not specified
* @returns An object with the itemized breakdown and total amount due
*/
function calculateOrderTotal(
subtotal: number,
taxRate: number,
shippingCost: number = 0
): {
subtotal: number;
tax: number;
shipping: number;
total: number;
} {
const tax = subtotal * taxRate;
const total = subtotal + tax + shippingCost;
return { subtotal, tax, shipping: shippingCost, total };
}Accessing Context with ToolContext
Your function tools can access the ToolContext to interact with session state, memory, artifacts, and more. Simply add a toolContext parameter to your function:
import { ToolContext, LlmAgent } from "@iqai/adk";
/**
* Saves user preferences to session state
* @param theme - The theme preference ('light' or 'dark')
* @param language - The language preference
*/
function savePreferences(
theme: string,
language: string,
toolContext: ToolContext
) {
// Store preferences in session state
toolContext.state.set("userPreferences", { theme, language });
return {
status: "success",
message: "Preferences saved successfully",
};
}
/**
* Gets user preferences from session state
*/
function getPreferences(toolContext: ToolContext) {
const preferences = toolContext.state.get("userPreferences");
return {
status: "success",
preferences: preferences || { theme: "light", language: "en" },
};
}
const preferencesAgent = new LlmAgent({
name: "preferences_agent",
description: "Manages user preferences",
tools: [savePreferences, getPreferences],
});ADK-TS automatically detects when your function has a toolContext parameter and injects it at runtime. For more details on what you can do with ToolContext, see the ToolContext documentation.
Creating Standard Function Tools
You have three options for creating standard function tools, each with different levels of control:
Option 1: Direct Function Assignment
The simplest approach is to pass your function directly to the tools array. ADK-TS automatically extracts the description from JSDoc comments:
import { LlmAgent } from "@iqai/adk";
/**
* Calculates the distance between two geographic points.
* Use this when you need to compute distance between coordinates.
*
* @param lat1 - Latitude of the first point
* @param lon1 - Longitude of the first point
* @param lat2 - Latitude of the second point
* @param lon2 - Longitude of the second point
*/
function calcDistanceTool(
lat1: number,
lon1: number,
lat2: number,
lon2: number
): number {
const distance = Math.sqrt(
Math.pow(lat2 - lat1, 2) + Math.pow(lon2 - lon1, 2)
);
return distance;
}
// The function is automatically converted to a FunctionTool
const mapAgent = new LlmAgent({
name: "map_agent",
description: "Helps with map-related requests",
instruction: "Help with map-related requests",
tools: [calcDistanceTool],
});JSDoc Required for Direct Functions
When passing functions directly, you must include a JSDoc comment with at
least 3 characters. The first paragraph (before any @param tags) becomes the
tool description. You can optionally use @description to explicitly mark the
description section.
Option 2: Using createFunctionTool (Recommended)
Use createFunctionTool to wrap your function with an explicit description. This gives you control over the tool's metadata:
import { LlmAgent, createFunctionTool } from "@iqai/adk";
// Function to calculate distance between two geographic coordinates
function calculateDistance(
lat1: number,
lon1: number,
lat2: number,
lon2: number
): number {
const distance = Math.sqrt(
Math.pow(lat2 - lat1, 2) + Math.pow(lon2 - lon1, 2)
);
return distance;
}
// Wrap with explicit configuration
const calcDistanceTool = createFunctionTool(calculateDistance, {
name: "calc_distance_tool",
description:
"Calculates the distance between two geographic points. Use this when you need to compute distance between coordinates.",
});
const mapAgent = new LlmAgent({
name: "map_agent",
description: "Helps with map-related requests",
instruction: "Help with map-related requests",
tools: [calcDistanceTool],
});Configuration Options:
| Option | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Override the function name |
description | string | No | Explicit tool description (if not provided, falls back to JSDoc) |
isLongRunning | boolean | No | Mark as long-running operation |
shouldRetryOnFailure | boolean | No | Enable automatic retries |
maxRetryAttempts | number | No | Maximum retry attempts |
Description Sources
createFunctionTool tries to get the description in this order:
- The
descriptionparameter you provide - JSDoc comment on the function (if no description parameter)
- Empty string (will fail validation)
So you must provide either an explicit description OR a JSDoc comment.
Option 3: Using FunctionTool Class
For maximum flexibility and advanced use cases, use the FunctionTool class directly:
import { LlmAgent, FunctionTool } from "@iqai/adk";
// Function to calculate distance between two geographic coordinates
function calculateDistance(
lat1: number,
lon1: number,
lat2: number,
lon2: number
): number {
const distance = Math.sqrt(
Math.pow(lat2 - lat1, 2) + Math.pow(lon2 - lon1, 2)
);
return distance;
}
// Define the FunctionTool for distance calculation
const calcDistanceTool = new FunctionTool(calculateDistance, {
name: "calc_distance_tool",
description: "Calculate distance between geographic coordinates",
isLongRunning: false,
shouldRetryOnFailure: true,
maxRetryAttempts: 2,
parameterTypes: {
lat1: "number",
lon1: "number",
lat2: "number",
lon2: "number",
},
});
const mapAgent = new LlmAgent({
name: "map_agent",
description: "Helps with map-related requests",
instruction: "Help with map-related requests",
tools: [calcDistanceTool],
});When to Use Each Approach:
| Approach | Best For | Description Source | Pros | Cons |
|---|---|---|---|---|
| Direct Function | Quick prototypes | JSDoc only | Minimal code, automatic | Must have JSDoc |
| createFunctionTool | Production code | Explicit OR JSDoc (fallback) | Clean, flexible, explicit control | Slightly more code |
| FunctionTool Class | Advanced cases | Explicit OR JSDoc (fallback) | Maximum control, parameter type override | Most verbose |
Function Tool Best Practices
To create effective function tools:
- Keep It Simple: Fewer parameters make tools easier for the LLM to use correctly
- Use Clear Types: Primitive types (
string,number,boolean) work best - Descriptive Names: Use action verbs for function names (
getWeather, notweather) - Use Async: Async functions allow parallel tool execution for better performance
- Handle Errors: Return meaningful error messages, not just error codes
- Validate Input: Check parameters before processing to avoid runtime errors
Long Running Function Tools
ADK-TS supports long-running operations through the isLongRunning option. This is perfect for operations that take time to complete, like processing large files or getting human approval.
Creating Long-Running Tools
You can create long-running tools using FunctionTool or createFunctionTool by setting isLongRunning: true:
import { LlmAgent, FunctionTool } from "@iqai/adk";
/**
* Requests human approval for an expense.
*
* @param purpose - The reason for the expense
* @param amount - The expense amount in dollars
*/
function requestHumanApproval(purpose: string, amount: number) {
return {
status: "pending",
requestId: `req-${Date.now()}`,
message: `Approval requested for ${purpose} ($${amount})`,
};
}
// Create a long-running function tool
const approvalTool = new FunctionTool(requestHumanApproval, {
name: "request_approval_tool",
description: "Requests human approval for an expense",
isLongRunning: true,
});
const approvalAgent = new LlmAgent({
name: "approval_agent",
description: "Handles expense approval requests",
tools: [approvalTool],
});How Long-Running Tools Work
When an agent executes a long-running tool:
- The tool initiates the operation and returns immediately with a status like
{ status: "pending" } - The agent pauses execution until the operation completes
- Your application updates the tool status when the operation finishes
- The agent continues with the updated result
Here's a complete example:
import { LlmAgent, FunctionTool, AgentBuilder } from "@iqai/adk";
import * as dotenv from "dotenv";
dotenv.config();
// Requests human approval for an expense.
function requestHumanApproval(purpose: string, amount: number) {
return {
status: "pending",
requestId: `req-${Date.now()}`,
message: `Approval requested for ${purpose} ($${amount})`,
};
}
// Create a long-running function tool
const approvalTool = new FunctionTool(requestHumanApproval, {
name: "request_approval_tool",
description: "Requests human approval for an expense",
isLongRunning: true,
});
const approvalAgent = new LlmAgent({
name: "approval_agent",
description: "Handles expense approval requests",
tools: [approvalTool],
});
const rootAgent = () => {
return AgentBuilder.create("root_agent")
.withModel("gemini-2.5-flash")
.withDescription("You are a general purpose assistant.")
.withSubAgents([approvalAgent])
.build();
};
async function main() {
const { runner } = await rootAgent();
const response = await runner.ask(
"Request approval for a business lunch with a client for $45."
);
console.log(`🤖 Response: ${response}`);
}
main().catch(console.error);When to Use Long-Running Tools
Long-running tools are ideal for:
- Human-in-the-Loop Processes: Get human approval or input during agent execution
- Large File Processing: Handle document analysis, image processing, or data transformation
- External API Integration: Work with APIs that may take time to respond
- Background Processing: Run intensive calculations without blocking user interaction
Best Practices for Long-Running Tools
For effective long-running tools:
- Provide Clear Status: Return status indicators (
pending,completed,failed) - Include Reference IDs: Return ticket/request IDs so users can track operations
- Handle Timeouts: Consider what happens if operations take too long
- Give Progress Updates: When possible, report progress percentage or step completion
ADK-TS Implementation Note
The long-running tool itself should return quickly - it only initiates the long-running process. The actual time-consuming work should happen in a separate system (database job, external API, etc.). Your application is responsible for updating the operation status and resuming the agent when complete.
Agents-as-a-Tool
ADK-TS lets you use agents as tools, creating powerful combinations where specialized agents handle specific tasks. This is perfect for building modular, reusable AI systems.
With the AgentTool class, you can:
- Create specialized agents for specific tasks (summarization, analysis, translation)
- Make those agents available as tools to other agents for delegation
- Build hierarchical agent systems where the main agent retains control
- Easily combine and reuse agent capabilities across different workflows
- Leverage specialized knowledge in focused, domain-specific agents
Configuration Options
When creating an agent as a tool, the AgentTool class accepts these configuration options:
| Option | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | The name of the tool (required) |
| agent | BaseAgent | Yes | The agent instance to use as a tool (required) |
| description | string | No | Description of what the tool does (defaults to agent's description) |
| skipSummarization | boolean | No | If true, returns the agent's response directly without additional LLM processing. If false (default), the main agent's LLM processes the response before including it in output |
Example: Summarization Agent-as-Tool
Here's a complete example of creating and using an agent as a tool:
import { LlmAgent, AgentTool } from "@iqai/adk";
// Create a specialized summarization agent
const summaryAgent = new LlmAgent({
name: "summary_agent",
description: "Summarizes long text into concise summaries",
instruction: `You are a text summarization expert.
When given text, create a clear, concise summary that captures the key points.
Keep summaries to 2-3 paragraphs maximum.
Maintain objectivity and accuracy.`,
});
// Create a main agent that uses the summary agent as a tool
const mainAgent = new LlmAgent({
name: "main_agent",
description: "Main agent that delegates summarization tasks",
instruction: `You are a helpful assistant.
When users provide long documents or text, use the 'summarize' tool to create summaries.
For short texts, answer directly without using the tool.`,
tools: [
new AgentTool({
name: "summarize_tool",
agent: summaryAgent,
description: "Summarizes long text into concise summaries",
skipSummarization: false, // LLM will process summary before responding
}),
],
});How It Works
When the main agent uses an agent-as-tool:
- Recognition: The main agent recognizes when a tool call should be routed to an
AgentTool - Delegation: The main agent calls the tool agent with the request
- Processing: The tool agent processes the request according to its instructions
- Response: The tool agent generates and returns its response
- Post-Processing: If
skip_summarizationisfalse, the main agent's LLM processes the response; otherwise it's returned directly - Integration: The response is incorporated into the main agent's output to the user
Agent-as-Tool vs Sub-Agents
Agent-as-Tool: When Agent A calls Agent B as a tool, Agent B's response is passed back to Agent A, which retains control and continues processing. Agent A remains the primary agent.
Sub-Agent: When Agent A delegates to Agent B as a sub-agent, control is completely transferred to Agent B. Agent A is effectively out of the loop.
Related Topics
✨ createTool Utility
Create custom tools with Zod schema validation
⚡ Built-in Tools
Ready-to-use tools for search, file operations, and user interaction
🔐 Authentication
Secure your tools and agents with authentication strategies
💾 Sessions & State
Manage data flow and persistence across agent interactions
How is this guide?