LlamaIndex Tutorial (TypeScript): building custom tools for advanced developers
This tutorial shows you how to build custom LlamaIndex tools in TypeScript and wire them into an agent that can call your own business logic. You need this when the built-in tools are too generic and you want the agent to query internal systems, validate inputs, or trigger controlled actions.
What You'll Need
- •Node.js 18+
- •A TypeScript project with
ts-nodeor a build step - •
@llamaindex/core - •
@llamaindex/openai - •An OpenAI API key in
OPENAI_API_KEY - •Basic familiarity with async/await and TypeScript types
- •A local
.envfile or shell environment for secrets
Step-by-Step
- •Start by installing the packages and setting up your environment. I’m using the current LlamaIndex TypeScript packages directly, so you can run this in a clean project without extra wrappers.
npm init -y
npm install @llamaindex/core @llamaindex/openai dotenv
npm install -D typescript tsx @types/node
- •Create a custom tool around a real function. The important part is the
ToolMetadataname/description pair, because that is what the agent uses to decide when to call your tool.
import "dotenv/config";
import { FunctionTool } from "@llamaindex/core/tools";
type Account = {
id: string;
status: "active" | "frozen";
balance: number;
};
const accounts: Record<string, Account> = {
"acc_1001": { id: "acc_1001", status: "active", balance: 2450 },
"acc_1002": { id: "acc_1002", status: "frozen", balance: 120 },
};
const getAccountSummary = async ({ accountId }: { accountId: string }) => {
const account = accounts[accountId];
if (!account) return `Account ${accountId} not found`;
return JSON.stringify(account);
};
export const accountTool = FunctionTool.from(
getAccountSummary,
{
name: "get_account_summary",
description: "Fetches a bank account summary by account ID.",
}
);
- •Add a second tool that performs validation instead of retrieval. In production systems, this is where you keep the agent honest by forcing it through deterministic checks before it can continue.
import { FunctionTool } from "@llamaindex/core/tools";
const validateTransfer = async ({
fromAccountId,
toAccountId,
amount,
}: {
fromAccountId: string;
toAccountId: string;
amount: number;
}) => {
if (fromAccountId === toAccountId) return "Invalid transfer: same source and destination";
if (amount <= 0) return "Invalid transfer: amount must be greater than zero";
if (amount > 1000) return "Transfer blocked: amount exceeds approval threshold";
return "Transfer validated";
};
export const transferValidationTool = FunctionTool.from(
validateTransfer,
{
name: "validate_transfer",
description:
"Validates whether a transfer request meets basic policy rules.",
}
);
- •Put the tools into an agent and let the model decide when to use them. This is the part that turns plain functions into agent capabilities.
import { OpenAI } from "@llamaindex/openai";
import { ReActAgent } from "@llamaindex/core/agent";
import { accountTool } from "./accountTool";
import { transferValidationTool } from "./transferValidationTool";
const llm = new OpenAI({
model: "gpt-4o-mini",
});
async function main() {
const agent = new ReActAgent({
llm,
tools: [accountTool, transferValidationTool],
verbose: true,
});
const response = await agent.chat({
message:
"Check account acc_1001 and tell me whether a transfer of $500 to acc_1002 is valid.",
});
console.log(response.message.content);
}
main().catch(console.error);
- •If you want tighter control, wrap your tool output in structured JSON and keep the descriptions specific. That makes it easier to parse downstream and reduces vague tool calls.
import { FunctionTool } from "@llamaindex/core/tools";
const fraudCheck = async ({ customerId }: { customerId: string }) => {
const riskScore = customerId === "cust_high_risk" ? 92 : 14;
return JSON.stringify({
customerId,
riskScore,
decision: riskScore > 80 ? "review" : "allow",
reason:
riskScore > 80 ? "Customer exceeded risk threshold" : "Customer within policy",
});
};
export const fraudCheckTool = FunctionTool.from(fraudCheck, {
name: "fraud_check",
description:
"Returns a risk score and decision for a customer before approval.",
});
Testing It
Run the file with npx tsx src/index.ts and watch the verbose trace. You should see the agent select one or more tools before producing its final answer.
Test with prompts that force different paths:
- •“Summarize acc_1001”
- •“Validate a $50 transfer from acc_1001 to acc_1002”
- •“Validate a $5000 transfer”
If the tool descriptions are good, the model should choose the right function without extra prompting. If it doesn’t, tighten the tool names and make the descriptions more operational.
Next Steps
- •Add Zod validation around tool inputs so bad arguments fail fast before hitting business logic
- •Replace in-memory mock data with real service calls to your internal APIs
- •Build multi-step workflows where one tool’s output becomes another tool’s input
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
By Cyprian Aarons, AI Consultant at Topiax.
Want the complete 8-step roadmap?
Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.
Get the Starter Kit