AutoGen Tutorial (TypeScript): building custom tools for advanced developers
This tutorial shows how to build custom tools in AutoGen with TypeScript, wire them into an agent, and make the tool layer safe enough for real workflows. You need this when the built-in examples stop being enough and you want your agent to call your own APIs, enforce validation, and return structured results.
What You'll Need
- •Node.js 18+ installed
- •A TypeScript project with
ts-nodeor a build step - •
npmorpnpm - •An OpenAI API key in
OPENAI_API_KEY - •Packages:
- •
@autogenai/autogen - •
zod - •
dotenv - •
typescript - •
ts-node
- •
Install them like this:
npm install @autogenai/autogen zod dotenv
npm install -D typescript ts-node @types/node
Step-by-Step
- •Create a minimal TypeScript entry point and load your API key from
.env. Keep the bootstrap boring; tool work should be isolated from config work.
import "dotenv/config";
import { AssistantAgent } from "@autogenai/autogen";
if (!process.env.OPENAI_API_KEY) {
throw new Error("OPENAI_API_KEY is missing");
}
const agent = new AssistantAgent({
name: "tool-agent",
model: "gpt-4o-mini",
});
- •Define a custom tool with strict input validation. For advanced developers, the important part is not “can the model call it,” but “can you trust the payload before it hits your system.”
import { z } from "zod";
const AccountLookupInput = z.object({
accountId: z.string().min(3),
});
type AccountLookupArgs = z.infer<typeof AccountLookupInput>;
async function lookupAccount(args: AccountLookupArgs) {
const parsed = AccountLookupInput.parse(args);
return {
accountId: parsed.accountId,
status: "active",
balance: 18420.55,
currency: "USD",
};
}
- •Wrap the function in AutoGen tool metadata so the agent can discover it. The schema needs to be explicit, because weak schemas lead to vague calls and bad retries.
import { FunctionTool } from "@autogenai/autogen";
const lookupAccountTool = FunctionTool.from(
lookupAccount,
{
name: "lookup_account",
description: "Fetch account status and balance by account ID.",
parameters: {
type: "object",
properties: {
accountId: {
type: "string",
minLength: 3,
description: "Internal account identifier.",
},
},
required: ["accountId"],
additionalProperties: false,
},
}
);
- •Register the tool on the agent and run a prompt that forces tool usage. This is where most teams stop using plain chat completions and start getting deterministic behavior out of agents.
agent.registerTools([lookupAccountTool]);
const result = await agent.run({
task: "Check account ACC-10291 and summarize its status for a relationship manager.",
});
console.log(result.messages.at(-1)?.content);
- •Add a second tool for write operations, but keep side effects behind explicit guards. In production, read tools and write tools should not share the same trust level.
const UpdateLimitInput = z.object({
accountId: z.string().min(3),
newLimit: z.number().positive().max(1000000),
});
async function updateCreditLimit(args: z.infer<typeof UpdateLimitInput>) {
const parsed = UpdateLimitInput.parse(args);
return {
accountId: parsed.accountId,
creditLimitUpdatedTo: parsed.newLimit,
approvedByPolicy: true,
};
}
const updateCreditLimitTool = FunctionTool.from(updateCreditLimit, {
name: "update_credit_limit",
description: "Update an account credit limit after policy checks.",
parameters: {
type: "object",
properties: {
accountId: { type: "string", minLength: 3 },
newLimit: { type: "number", minimum: 0, maximum: 1000000 },
},
required: ["accountId", "newLimit"],
additionalProperties: false,
},
});
- •Register both tools and keep the final orchestration in one place. This makes it easy to swap models, add observability, or route high-risk calls through human approval later.
agent.registerTools([lookupAccountTool, updateCreditLimitTool]);
const response = await agent.run({
task:
"If ACC-10291 has a balance above 15000, raise its credit limit to 25000 and explain why.",
});
for (const message of response.messages) {
console.log(`${message.role}: ${message.content}`);
}
Testing It
Run the script with npx ts-node src/index.ts or compile it first if your project uses tsc. Start by checking that the agent actually calls lookup_account before answering; if it skips the tool, your schema or prompt is too loose.
Then test invalid inputs by changing accountId to an empty string or passing a negative number into newLimit. The Zod parse should fail before any downstream logic runs.
Finally, inspect the final assistant output for two things:
- •It should reflect tool-returned data rather than hallucinated values.
- •It should stay consistent across repeated runs with the same prompt.
If you want stronger verification, log every tool invocation with timestamps and request IDs so you can trace model behavior in production.
Next Steps
- •Add retry logic and timeout handling around each tool call.
- •Wrap high-risk tools with approval gates before execution.
- •Learn how to stream intermediate AutoGen messages so you can observe planning and tool selection in real time.
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