AI Agents ยท ยท 9 min read

How to Build Your First AI Agent in TypeScript with ADK-TS

Learn to build your first AI agent in TypeScript with ADK-TS. Create multi-agent systems with subagents and tools using external APIs. Complete tutorial for developers.

How to Build Your First AI Agent in TypeScript with ADK-TS

As TypeScript developers, weโ€™re always looking for ways to make our applications smarter and more interactive. While integrating AI into our apps has traditionally meant writing complex Python code or managing large language models, thatโ€™s changing. Thanks to frameworks like ADK-TS, we can now build intelligent AI agents right in our TypeScript projects.

In this tutorial, weโ€™ll build a simple multi-agent system that can handle weather queries and tell jokes. Youโ€™ll learn how to create AI agents that can:

By the end of this tutorial, youโ€™ll have a working AI agent system and understand how to extend it for your own projects. Letโ€™s get started.

What are AI Agents?

When we talk about AI agents, weโ€™re talking about making our applications smarter in how they handle user requests. Instead of just responding to specific commands, an AI agent can understand what a user wants and figure out how to help them.

Think about a weather application. A traditional app might require users to click specific buttons or type exact city names. An AI agent, on the other hand, can understand natural questions like โ€œDo I need an umbrella today?โ€ or โ€œWhatโ€™s the weather like in London?โ€ and know how to get that information.

What makes AI agents different from regular AI integrations? Itโ€™s their ability to:

Why Build AI Agents in TypeScript?

As TypeScript developers, weโ€™re used to building well-structured, type-safe applications. While Python has been the go-to language for AI development, TypeScript offers some real advantages:

Plus, with frameworks like ADK-TS, we donโ€™t need to learn complex AI concepts or deal with model training. The framework handles the heavy lifting for youโ€”model integrations, built-in session and memory management, tool handling, observability, and more. We can just focus on building useful features using familiar TypeScript patterns.

What Makes ADK-TS Different

ADK-TS is a TypeScript-first framework that simplifies building AI agents and multi-agent systems. It provides type-safe APIs using TypeScript and Zod, supports multiple LLM providers (OpenAI, Anthropic, Gemini, and more), enables streaming responses for real-time interactions, and includes built-in memory management for stateful conversations.

You can also orchestrate multiple agents to work together, create reusable tools for external integrations, and deploy agents as HTTP servers or integrate them into existing applications. The framework uses familiar patterns, such as fluent builders and factory functions, that fit naturally into your TypeScript workflow.

Building Our First AI Agent System

Weโ€™ll build a multi-agent system that has one root agent and coordinates two specialized sub-agents:

Each sub-agent has its own tools and logic, while the root agent focuses on routing requests. This structure gives us separation of concerns (changes to weather logic wonโ€™t affect jokes), easier testing (test tools and agents can run in isolation), and the ability to add new agents without disrupting existing ones.

Letโ€™s set up our project.

Project Folder Structure

Hereโ€™s a recommended structure for organizing your project:

my-first-ai-agent/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ agents/
โ”‚   โ”‚   โ”œโ”€โ”€ weather-agent/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ tools.ts
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ agent.ts
โ”‚   โ”‚   โ”œโ”€โ”€ joke-agent/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ tools.ts
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ agent.ts
โ”‚   โ”‚   โ””โ”€โ”€ agent.ts          # Root agent
โ”‚   โ”œโ”€โ”€ env.ts
โ”‚   โ””โ”€โ”€ index.ts
โ”œโ”€โ”€ .env
โ”œโ”€โ”€ tsconfig.json
โ””โ”€โ”€ package.json

This structure helps keep your code organized by separating each agent into its own folder with its tools. You can adapt this layout to fit your preferences, but this approach makes it easier to maintain and scale as you add more agents.

Setting Up the Project

Next, open your terminal and run the following commands:

mkdir my-first-ai-agent
cd my-first-ai-agent
pnpm init

Install the necessary dependencies:

pnpm add @iqai/adk dotenv zod
pnpm add -D typescript @types/node tsx

Create a basic TypeScript configuration in tsconfig.json:

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true
  }
}

Create a .env file to store your environment variables:

# .env
GOOGLE_API_KEY=your_google_api_key_here
LLM_MODEL=gemini-2.5-flash # Default model

You can get a Google API key from the Google AI Studio.

ADK-TS supports multiple LLM providers, but we will be using Gemini, which is the default provider. To learn more about configuring different LLM providers, check out the ADK-TS documentation.

Lastly, create a .env.ts file to load the environment variables:

// src/env.ts
import { config } from "dotenv";
import { z } from "zod";

config();

export const envSchema = z.object({
 GOOGLE_API_KEY: z.string(),
 LLM_MODEL: z.string().default("gemini-2.5-flash"),
});

export const env = envSchema.parse(process.env);

This sets up our project structure and configuration. Next, weโ€™ll start building our tools and agents.

Agent Interaction Flow

Before we dive into the code, letโ€™s visualize how our multi-agent system works:

A comprehensive diagram showing how the root agent coordinates with specialized sub-agents to handle user requests through external APIs.

The diagram shows the complete flow of a user request through our system:

  1. User makes a request - The user asks a natural-language question, such as โ€œWhatโ€™s the weather in London?โ€
  2. Root agent analyzes - The root agent receives the request and determines which specialized agent should handle it
  3. Root agent delegates - Based on the analysis, the root agent routes the task to the appropriate sub-agent (Weather Agent in this example)
  4. Selected sub-agent executes - Only the chosen sub-agent activates and uses its tools to fetch data from external APIs
  5. Response delivered - The sub-agent returns data through the root agent back to the user

If the user had asked for a joke instead, the Joke Agent would be selected, while the Weather Agent would remain inactive. This selective activation ensures efficiency and clarity in handling requests.

Now, letโ€™s start building our tools and agents.

Creating Tools for Our Agents

Before we build the agents, we need to create the tools they will use. Tools are reusable components that encapsulate specific functionality, making it easier for agents to perform tasks. They extend the agentโ€™s capabilities by enabling it to interact with external APIs or services in a structured way.

We will create two tools: one for fetching weather information and another for fetching jokes. These tools will wrap external APIs and provide a clean interface for our agents to use.

Create a new file src/agents/weather-agent/tools.ts:

// src/agents/weather-agent/tools.ts
import { createTool } from "@iqai/adk";
import * as z from "zod";

export const weatherTool = createTool({
  name: "get_weather",
  description: "Get current weather for a city",
  schema: z.object({
    city: z.string().describe("City name"),
  }),
  fn: async ({ city }) => {
    try {
      const response = await fetch(
        `https://wttr.in/${encodeURIComponent(city)}?format=3`
      );
      return await response.text();
    } catch {
      return `Weather unavailable for ${city}`;
    }
  },
});

Next, create src/agents/joke-agent/tools.ts:

// src/agents/joke-agent/tools.ts
import { createTool } from "@iqai/adk";

export const jokeTool = createTool({
 name: "get_joke",
 description: "Fetches a random joke",
 fn: async () => {
  try {
   const response = await fetch(
    "<https://official-joke-api.appspot.com/random_joke>",
   );
   return await response.text();
  } catch {
   return "Joke unavailable right now.";
  }
 },
});

These tools provide the basic capabilities our agents will need. The weather tool can fetch weather information for any city, while the joke tool retrieves random jokes from an API.

Creating Our Specialized Agents

Now that we have our tools ready, letโ€™s create the agents that will use them. Each agent will focus on one specific task - either providing weather information or telling jokes.

In src/agents/weather-agent/agent.ts, create the weather agent:

// src/agents/weather-agent/agent.ts
import { LlmAgent } from "@iqai/adk";
import { env } from "../../env";
import { weatherTool } from "./tools";

export const getWeatherAgent = () => {
  const weatherAgent = new LlmAgent({
    name: "weather_agent",
    description: "provides weather for a given city",
    model: env.LLM_MODEL,
    tools: [weatherTool],
  });

  return weatherAgent;
};

Similarly, in src/agents/joke-agent/agent.ts, create the joke agent:

// src/agents/joke-agent/agent.ts
import { LlmAgent } from "@iqai/adk";
import { env } from "../../env";
import { jokeTool } from "./tools";

export const getJokeAgent = () => {
  const jokeAgent = new LlmAgent({
    name: "joke_agent",
    description: "provides a random joke",
    model: env.LLM_MODEL,
    tools: [jokeTool],
  });

  return jokeAgent;
};

Each agent is configured with a specific purpose and the tools it needs to do its job. The weather agent knows about weather-related queries, while the joke agent handles requests for humor.

Creating the Root Agent

The root agent acts as a coordinator, directing user requests to the appropriate specialized agent. When a user asks a question like โ€œWhatโ€™s the weather in Paris?โ€ or โ€œTell me a joke,โ€ the root agent determines which sub-agent should handle the request. If the user asks a question that doesnโ€™t fit either category, the root agent can respond with a default message or handle it gracefully.

Hereโ€™s how we set it up in src/agents/agent.ts:

// src/agents/agent.ts
import { AgentBuilder } from "@iqai/adk";
import { env } from "../env";
import { getJokeAgent } from "./joke-agent/agent";
import { getWeatherAgent } from "./weather-agent/agent";

export const getRootAgent = () => {
  const jokeAgent = getJokeAgent();
  const weatherAgent = getWeatherAgent();

  return AgentBuilder.create("root_agent")
    .withDescription(
      "Root agent that delegates tasks to sub-agents for telling jokes and providing weather information."
    )
    .withInstruction(
      `
     You are a routing agent. Analyze the user's request and delegate to the appropriate sub-agent:

    - For ANY request about weather, weather conditions, temperature, or location-based weather queries: delegate to weather_agent
    - For ANY request about jokes, humor, comedy, or asking to tell a joke: delegate to joke_agent

    Do not answer questions yourself. Always delegate to the appropriate sub-agent based on the request type.`
    )
    .withModel(env.LLM_MODEL)
    .withSubAgents([jokeAgent, weatherAgent])
    .build();
};

Putting It All Together

Finally, letโ€™s create our main application file that uses our agent system. In src/index.ts:

// src/index.ts
import * as dotenv from "dotenv";
import { getRootAgent } from "./agents/agent";

dotenv.config();

async function main() {
	const questions = ["how is weather in london?", "tell me a random joke"];

	const { runner } = await getRootAgent();

	for (const question of questions) {
		console.log(`๐Ÿ“ Question: ${question}`);
		const response = await runner.ask(question);
		console.log(`๐Ÿค– Response: ${response}`);
	}
}

main().catch(console.error);

This code creates our agent system and tests it with a couple of simple questions. The root agent automatically figures out which specialized agent should handle each request.

Running Your Agent

To run your agent system, add these scripts to your package.json:

{
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js"
  }
}

And run your agent using:

pnpm dev

You should see output like:

๐Ÿ“ Question: how is weather in london?
๐Ÿค– Response: The weather in London is โ›…๏ธ +14ยฐC.
๐Ÿ“ Question: tell me a random joke
๐Ÿค– Response: Why do programmers prefer dark mode? Because light attracts bugs!
Sample output from running the agent test script

Using the CLI for Interactive Testing

Instead of writing test scripts, you can interact with your agent directly using the ADK-TS CLI. First, install the CLI:

# Install globally
pnpm add -g @iqai/adk-cli

# Or use npx without global installation
npx @iqai/adk-cli

The CLI provides two ways to test your agents:

Terminal-Based Chat (adk run): Start an interactive chat session in your terminal for quick testing and experimentation.

Web-Based Interface (adk web): Launch a local web server with a visual chat interface for a more user-friendly experience.

Both methods auto-discover your agents from the src/agents directory and let you test without writing any scripts. For detailed installation and usage instructions, check out the CLI documentation.

Using ADK-TS Built-in Tools

ADK-TS also includes several built-in tools you can use right away without creating custom implementations. For example, if you want to add web search capabilities to your agent, you can use the built-in GoogleSearch tool:

import { GoogleSearch, LlmAgent } from "@iqai/adk";
import { env } from "../env";

export const getSearchAgent = () => {
  return new LlmAgent({
    name: "search_agent",
    description: "searches the web for information",
    model: env.LLM_MODEL,
    tools: [new GoogleSearch()], // Use the built-in tool
  });
};

Check out the tools documentation to see whatโ€™s available and how to use them.

Taking Your Agent Further

While our weather and joke example is straightforward, you can apply these same patterns to build more sophisticated agents. Here are some real-world applications:

Customer Service Agent

You could extend this pattern to create a customer service agent that answers product questions using a product database, handles order status inquiries by connecting to your order system, escalates complex issues to human support when needed, and maintains conversation context across multiple questions.

Development Assistant

Create an agent that helps with coding tasks by analyzing pull requests and suggesting improvements, helping debug code by running tests and analyzing logs, generating documentation from code comments, and reviewing code for security issues.

Blockchain Portfolio Agent

Build an agent that helps users manage their cryptocurrency investments by tracking portfolio performance across multiple exchanges, analyzing market trends using price data APIs, sending alerts for significant price changes, and providing insights on potential investment opportunities based on historical data and current market conditions.

Conclusion

In this tutorial, weโ€™ve built a multi-agent system using TypeScript and ADK-TS. Weโ€™ve seen how to create specialized agents that handle specific tasks, equip our agents with tools to give them real capabilities, coordinate multiple agents through a root agent, and structure our code in a maintainable way.

This is just the beginning of what you can do with AI agents in TypeScript. Whether youโ€™re building a customer service system, a blockchain portfolio manager, or something entirely new, the patterns weโ€™ve covered here will help you create organized and effective agent-based applications.

Next Steps

To view the complete code used in this tutorial, you can:

Ready to build your own AI agent? Check out the ADK-TS documentation for more examples and advanced features.

Read next