Skip to main content

Overview

Tools allow your agents to perform actions beyond text generation. This example demonstrates:
  • Creating custom tools with Zod schemas
  • Managing state across multiple interactions
  • Using state variables in agent instructions
  • Building practical applications with persistent data
Tools are the bridge between your AI agent and the real world - they can call APIs, modify databases, send emails, and more.

Shopping Cart Example

We’ll build a shopping cart assistant that can:
  • Add items with quantities and prices
  • View cart contents and totals
  • Track state across multiple requests

Complete Example

1
Step 1: Create Custom Tools
2
Define tools using the createTool function:
3
import { createTool } from "@iqai/adk";
import * as z from "zod";

export const addItemTool = createTool({
	name: "add_item",
	description: "Add an item to the shopping cart",
	schema: z.object({
		item: z.string().describe("Item name"),
		quantity: z.number().default(1).describe("Quantity to add"),
		price: z.number().describe("Price per item"),
	}),
	fn: ({ item, quantity, price }, context) => {
		const cart: { item: string; quantity: number; price: number }[] =
			context.state.get("cart", []);
		const existingItemIndex = cart.findIndex(
			(cartItem) => cartItem.item === item,
		);

		let updatedCart: { item: string; quantity: number; price: number }[];
		if (existingItemIndex > -1) {
			updatedCart = cart.map((cartItem, index) => {
				if (index === existingItemIndex) {
					return { ...cartItem, quantity: cartItem.quantity + quantity };
				}
				return cartItem;
			});
		} else {
			updatedCart = [...cart, { item, quantity, price }];
		}

		context.state.set("cart", updatedCart);
		context.state.set("cartCount", updatedCart.length);

		const total = updatedCart.reduce(
			(sum, cartItem) => sum + cartItem.quantity * cartItem.price,
			0,
		);

		return {
			success: true,
			item,
			quantity,
			cartTotal: total,
			message: `Added ${quantity}x ${item} to cart`,
		};
	},
});

export const viewCartTool = createTool({
	name: "view_cart",
	description: "View current shopping cart contents",
	fn: (_, context) => {
		const cart = context.state.get("cart", []);
		const total = cart.reduce(
			(sum, item) => sum + item.quantity * item.price,
			0,
		);

		return {
			cart,
			total,
			itemCount: cart.reduce((sum, item) => sum + item.quantity, 0),
			message:
				cart.length > 0
					? `Cart has ${cart.length} different items`
					: "Cart is empty",
		};
	},
});
4
The context parameter in tool functions provides access to:
  • context.state: Get and set state variables
  • context.session: Access session metadata
  • context.saveArtifact(): Save files and artifacts
  • context.memory: Query long-term memory
5
Step 2: Create an Agent with Tools and State
6
import { AgentBuilder } from "@iqai/adk";
import dedent from "dedent";
import { addItemTool, viewCartTool } from "./tools";

export function getRootAgent() {
	const initialState = {
		cart: [],
		cartCount: 0,
	};

	return AgentBuilder.create("shopping_cart_agent")
		.withModel(process.env.LLM_MODEL || "gemini-3-flash-preview")
		.withInstruction(
			dedent`
			You are a shopping cart assistant. Help users manage their cart.

			Current cart state:
			- Items in cart: {cartCount}
			- Cart contents: {cart}

			You can add items and view the cart. Always be helpful with pricing and quantities.
		`,
		)
		.withTools(addItemTool, viewCartTool)
		.withQuickSession({ state: initialState })
		.build();
}
7
State variables in instructions (like {cartCount} and {cart}) are automatically replaced with their current values at runtime. This gives your agent context about the current state.
8
Step 3: Use the Agent
9
import { ask } from "../utils";
import { getRootAgent } from "./agents/agent";

async function main() {
	const { runner } = await getRootAgent();

	const questions = [
		"Add 2 apples to my cart at $1.50 each",
		"Add 1 banana for $0.75",
		"Show me my complete cart with total",
	];

	for (const question of questions) {
		await ask(runner, question);
	}
}

main().catch(console.error);
10
Step 4: Run the Example
11
node index.ts

Expected Output

👤 User: Add 2 apples to my cart at $1.50 each

🤖 Agent: Added 2x apples to your cart at $1.50 each. 
Your cart total is now $3.00.

---

👤 User: Add 1 banana for $0.75

🤖 Agent: Added 1x banana to your cart for $0.75. 
Your cart now has 2 different items with a total of $3.75.

---

👤 User: Show me my complete cart with total

🤖 Agent: Here's your shopping cart:

1. Apples - Quantity: 2, Price: $1.50 each = $3.00
2. Banana - Quantity: 1, Price: $0.75 each = $0.75

Cart Total: $3.75
Total Items: 3

Key Concepts

Tool Schema Definition

Zod schemas define the input structure for your tools:
schema: z.object({
	item: z.string().describe("Item name"),
	quantity: z.number().default(1).describe("Quantity to add"),
	price: z.number().describe("Price per item"),
})
  • Type Safety: TypeScript knows the exact parameter types
  • Validation: Inputs are validated before the function runs
  • LLM Guidance: Descriptions help the AI understand parameter purposes

State Management

State persists across multiple interactions within a session:
// Get state (with default value)
const cart = context.state.get("cart", []);

// Update state
context.state.set("cart", updatedCart);
context.state.set("cartCount", updatedCart.length);

State in Instructions

Reference state variables in your agent’s instructions:
withInstruction(`
	Current cart state:
	- Items in cart: {cartCount}
	- Cart contents: {cart}
`)
The agent sees the current state values and can respond appropriately.

Advanced Tool Patterns

Async Tools

Tools can be asynchronous for API calls or database operations:
export const fetchWeatherTool = createTool({
	name: "fetch_weather",
	description: "Get current weather for a city",
	schema: z.object({
		city: z.string(),
	}),
	fn: async ({ city }, context) => {
		const response = await fetch(
			`https://api.weather.com/v1/weather?city=${city}`
		);
		const data = await response.json();
		return data;
	},
});

Tools with Multiple State Operations

export const checkoutTool = createTool({
	name: "checkout",
	description: "Complete the purchase",
	fn: async (_, context) => {
		const cart = context.state.get("cart", []);
		const total = calculateTotal(cart);

		// Process payment (async operation)
		const orderId = await processPayment(total);

		// Clear cart
		context.state.set("cart", []);
		context.state.set("cartCount", 0);
		context.state.set("lastOrderId", orderId);

		return { orderId, total };
	},
});

Error Handling in Tools

export const addItemTool = createTool({
	name: "add_item",
	description: "Add item to cart",
	schema: z.object({
		item: z.string(),
		price: z.number(),
	}),
	fn: ({ item, price }, context) => {
		try {
			if (price <= 0) {
				return {
					success: false,
					error: "Price must be greater than 0",
				};
			}

			// Add item logic...
			return { success: true, item };
		} catch (error) {
			return {
				success: false,
				error: error.message,
			};
		}
	},
});

Built-in Tools

ADK-TS provides several built-in tools:
  • RecallMemoryTool: Search long-term memory
  • FileReadTool: Read file contents
  • FileWriteTool: Write to files
  • HttpRequestTool: Make HTTP requests
  • AskUserTool: Prompt for user input
Example using built-in tools:
import { AgentBuilder, RecallMemoryTool, HttpRequestTool } from "@iqai/adk";

const agent = AgentBuilder.create("research_agent")
	.withTools(new RecallMemoryTool(), new HttpRequestTool())
	.build();

Use Cases

E-commerce

Cart management, inventory checks, order processing

Data Processing

ETL operations, data validation, transformations

API Integration

Call external services, webhooks, third-party APIs

File Operations

Read, write, process documents and data files

Next Steps

Database Sessions

Persist state beyond in-memory sessions

Multi-Agent Systems

Coordinate multiple agents with shared tools

Source Code

View the complete example in the repository: apps/examples/src/02-tools-and-state

Build docs developers (and LLMs) love