AutoGen Tutorial (TypeScript): handling async tools for advanced developers

By Cyprian AaronsUpdated 2026-04-21
autogenhandling-async-tools-for-advanced-developerstypescript

This tutorial shows you how to wire async tools into an AutoGen TypeScript agent without blocking the event loop or breaking tool call flow. You need this when your agent has to wait on real I/O like database queries, HTTP APIs, queues, or internal services that return promises.

What You'll Need

  • Node.js 18+ and npm
  • A TypeScript project with ts-node or a build step
  • autogen-core and autogen-ext
  • An OpenAI API key set in OPENAI_API_KEY
  • Basic familiarity with AutoGen agents, messages, and tool registration
  • A terminal for running the example

Step-by-Step

  1. First, install the packages and set up a minimal TypeScript project. The important part here is using the AutoGen core/runtime packages plus the OpenAI model client extension.
npm init -y
npm install autogen-core autogen-ext openai
npm install -D typescript ts-node @types/node
npx tsc --init
  1. Define an async tool as a normal async function. In production, this is where you wrap your HTTP client, database driver, or internal SDK; the key is that the function returns a Promise and can safely await I/O.
export async function getAccountBalance(accountId: string): Promise<string> {
  await new Promise((resolve) => setTimeout(resolve, 500));

  const balances: Record<string, number> = {
    "acct-1001": 1240.55,
    "acct-2002": 87.12,
  };

  const balance = balances[accountId];
  if (balance === undefined) {
    throw new Error(`Unknown account: ${accountId}`);
  }

  return `Account ${accountId} balance is $${balance.toFixed(2)}`;
}
  1. Register that async function as an AutoGen tool and expose it to the model. Use a proper schema so the model knows exactly what arguments it can pass; that reduces malformed tool calls and makes the tool usable in real workflows.
import { z } from "zod";
import { FunctionTool } from "autogen-core";

export const getAccountBalanceTool = FunctionTool.from(
  async ({ accountId }: { accountId: string }) => {
    return await getAccountBalance(accountId);
  },
  {
    name: "get_account_balance",
    description: "Fetches the current balance for a bank account.",
    schema: z.object({
      accountId: z.string().describe("The bank account identifier"),
    }),
  }
);
  1. Create an assistant agent and attach the tool. This example uses an OpenAI chat completion client through autogen-ext, then runs a single request where the agent decides whether to call the async tool.
import { AssistantAgent } from "autogen-core";
import { OpenAIChatCompletionClient } from "autogen-ext/models/openai";

const modelClient = new OpenAIChatCompletionClient({
  model: "gpt-4o-mini",
});

const agent = new AssistantAgent({
  name: "banking_assistant",
  modelClient,
  tools: [getAccountBalanceTool],
});

const result = await agent.run(
  [{ role: "user", content: "What is the balance for acct-1001?" }],
);

console.log(result.messages.at(-1)?.content);
  1. If your async tool talks to external systems, keep it deterministic at the boundary. Add timeouts, explicit errors, and typed outputs so your agent can recover cleanly when a service is slow or unavailable.
async function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
  const timeout = new Promise<never>((_, reject) =>
    setTimeout(() => reject(new Error(`Timed out after ${ms}ms`)), ms)
  );
  return await Promise.race([promise, timeout]);
}

export const safeBalanceTool = FunctionTool.from(
  async ({ accountId }: { accountId: string }) => {
    try {
      return await withTimeout(getAccountBalance(accountId), 2000);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      throw new Error(`balance lookup failed for ${accountId}: ${message}`);
    }
  },
  {
    name: "safe_get_account_balance",
    description: "Fetches account balance with timeout handling.",
    schema: z.object({
      accountId: z.string(),
    }),
  }
);

Testing It

Run the script with OPENAI_API_KEY exported in your shell and confirm you get a natural-language answer that includes the balance returned by the tool. Then test an unknown account ID like acct-9999 and verify the agent surfaces a controlled error instead of hanging or crashing.

If you want to validate concurrency behavior, fire two separate requests in parallel and make sure both complete independently. That catches accidental shared-state bugs in async tools, which show up fast once you put agents behind real traffic.

A good sanity check is to add temporary logging inside getAccountBalance and confirm you see one tool invocation per user request. If you see repeated calls or no calls at all, inspect your tool schema and whether the model is actually enabled to use tools.

Next Steps

  • Add retries with exponential backoff around flaky upstream APIs.
  • Wrap tools with auth context so each request uses tenant-specific credentials.
  • Move from single-tool examples to multi-tool routing across accounts, claims, and policy systems

Keep learning

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

Related Guides