How to Fix 'duplicate tool calls' in LlamaIndex (TypeScript)
If you’re seeing Error: duplicate tool calls in LlamaIndex TypeScript, it usually means the agent tried to register or execute the same tool twice in a single workflow. In practice, this shows up when you wire tools into both the agent and the underlying runner, or when your code reuses a tool list across multiple invocations.
This is not a model problem. It’s almost always a wiring issue in your TypeScript setup.
The Most Common Cause
The #1 cause is registering the same tool in two places: once in the agent constructor and again in the chat/run call, or building the tools array in a way that duplicates entries.
Here’s the broken pattern:
import { OpenAI } from "llamaindex";
import { FunctionTool, ReActAgent } from "llamaindex";
const getBalanceTool = FunctionTool.from(async () => {
return "Balance: $1,250";
}, {
name: "get_balance",
description: "Get account balance",
});
const llm = new OpenAI({ model: "gpt-4o-mini" });
const agent = new ReActAgent({
llm,
tools: [getBalanceTool],
});
// ❌ Broken: passing the same tool again during execution
const response = await agent.chat({
message: "What's my balance?",
tools: [getBalanceTool],
});
console.log(response.response);
And here’s the fixed pattern:
import { OpenAI } from "llamaindex";
import { FunctionTool, ReActAgent } from "llamaindex";
const getBalanceTool = FunctionTool.from(async () => {
return "Balance: $1,250";
}, {
name: "get_balance",
description: "Get account balance",
});
const llm = new OpenAI({ model: "gpt-4o-mini" });
const agent = new ReActAgent({
llm,
tools: [getBalanceTool],
});
// ✅ Fixed: tools are defined once at construction time
const response = await agent.chat({
message: "What's my balance?",
});
console.log(response.response);
The key rule is simple:
- •Define tools once
- •Pass them once
- •Don’t rebuild or append the same tool list per request unless you dedupe it first
If you’re using FunctionTool, QueryEngineTool, or custom BaseTool implementations, this mistake is easy to make because the code looks harmless.
Other Possible Causes
Here are the other failure modes I see most often.
1. Duplicate tool names
LlamaIndex uses tool names as identifiers. If two tools share the same name, you can get collisions that surface as duplicate tool execution errors.
// ❌ Broken
const t1 = FunctionTool.from(fnA, { name: "lookup_customer", description: "A" });
const t2 = FunctionTool.from(fnB, { name: "lookup_customer", description: "B" });
// ✅ Fixed
const t1 = FunctionTool.from(fnA, { name: "lookup_customer_profile", description: "A" });
const t2 = FunctionTool.from(fnB, { name: "lookup_policy", description: "B" });
2. Reusing and mutating a shared tools array
If you keep a module-level array and push into it on every request, you’ll accumulate duplicates over time.
// ❌ Broken
const tools = [];
export function buildTools(extraTool) {
tools.push(extraTool);
return tools;
}
// ✅ Fixed
export function buildTools(extraTool) {
return [extraTool];
}
If you need composition, dedupe by name before returning:
function uniqueTools(tools) {
const seen = new Set<string>();
return tools.filter((tool) => {
if (seen.has(tool.metadata.name)) return false;
seen.add(tool.metadata.name);
return true;
});
}
3. Creating a new agent inside a loop with shared state
This often happens in server handlers or batch jobs where each iteration reuses cached objects incorrectly.
// ❌ Broken
for (const request of requests) {
const agent = new ReActAgent({ llm, tools });
await agent.chat({ message: request.prompt, tools });
}
// ✅ Fixed
for (const request of requests) {
const agent = new ReActAgent({ llm, tools });
await agent.chat({ message: request.prompt });
}
4. Tool wrappers that register themselves twice
Some teams wrap FunctionTool inside their own factory and accidentally call registration logic twice.
// ❌ Broken
function makeCustomerTools() {
const t = FunctionTool.from(getCustomer, {
name: "get_customer",
description: "Fetch customer",
});
registerGlobalTool(t); // first registration
return [t];
}
registerGlobalTool(t); // second registration elsewhere
// ✅ Fixed
function makeCustomerTools() {
return [
FunctionTool.from(getCustomer, {
name: "get_customer",
description: "Fetch customer",
}),
];
}
How to Debug It
- •
Log every tool name before constructing the agent
You want to see exactly what LlamaIndex receives.
console.log(tools.map((t) => t.metadata.name));If you see repeated names like
["get_customer", "get_customer"], that’s your bug. - •
Search for all
tools:assignmentsCheck whether you pass tools into:
- •
new ReActAgent({ tools }) - •
agent.chat({ tools }) - •workflow nodes or runners
The error often comes from supplying them in more than one layer.
- •
- •
Check for shared mutable arrays
Look for:
- •module-level arrays
- •
.push()on cached lists - •singleton registries
If your tool list grows between requests, you’ve found it.
- •
Print stack traces around tool creation
Wrap your factory code so you can see where each tool instance is created.
const tool = FunctionTool.from(fn, { name: "lookup_policy", description: "" }); console.trace("Created tool:", tool.metadata.name);If the trace appears twice per request, your factory is being called twice.
Prevention
- •Keep tool definitions pure and immutable.
- •Use a single source of truth for each tool list per agent.
- •Enforce unique names with a small helper before building agents:
function assertUniqueToolNames(tools) {
const names = tools.map((t) => t.metadata.name);
const dupes = names.filter((name, i) => names.indexOf(name) !== i);
if (dupes.length > 0) {
throw new Error(`Duplicate tool names found: ${[...new Set(dupes)].join(", ")}`);
}
}
If you’re using LlamaIndex TypeScript and hit duplicate tool calls, assume duplication in your wiring first. In most cases, fixing how ReActAgent, FunctionTool, and your per-request state are assembled will clear it immediately.
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