Skip to content

Quick Start: Build Your First Agent in 10 Minutes

This tutorial walks you through building a working AI agent with a tool, an HTTP endpoint, and a React chat UI. By the end, you will have a complete full-stack agent application.

Prerequisites

Make sure you have completed the Installation steps and have a valid GOOGLE_AI_STUDIO_KEY environment variable set.


Step 1 -- Install packages

Create a new project and install the required dependencies:

mkdir my-first-agent && cd my-first-agent
npm init -y
npm install @modernpath/agent-framework @google/genai yaml zod
npm install -D typescript tsx @types/node

Create a minimal tsconfig.json:

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "strict": true,
    "esModuleInterop": true,
    "outDir": "dist",
    "declaration": true,
    "experimentalDecorators": true
  },
  "include": ["src"]
}

Step 2 -- Configure GeminiClient

The GeminiClient wraps the @google/genai SDK with retry logic, document validation, and streaming support.

src/gemini.ts
import { GeminiClient } from "@modernpath/agent-framework";

export const gemini = new GeminiClient({
  apiKey: process.env.GOOGLE_AI_STUDIO_KEY!, // (1)!
  model: "gemini-2.5-flash",                 // (2)!
  maxOutputTokens: 4096,
  temperature: 0.7,
});
  1. The API key is loaded from an environment variable. Never hard-code keys in source.
  2. See Environment Setup for available models.

Step 3 -- Create a ToolRegistry and register a tool

Tools extend what your agent can do. The @Tool decorator attaches metadata (name, description, parameter schema) that the framework uses for validation and LLM function calling.

src/tools/weather.ts
import { Tool, ITool, ToolMetadata } from "@modernpath/agent-framework";

@Tool({
  name: "get_weather",
  description: "Get the current weather for a city",
  category: "utilities",
  parameters: {
    city: {
      type: "string",
      description: "City name, e.g. 'Helsinki'",
      required: true,
    },
  },
})
export class WeatherTool implements ITool {
  async execute(params: Record<string, any>): Promise<any> {
    const city = params.city as string;
    // In production, call a real weather API here
    return {
      city,
      temperature: 22,
      condition: "Partly cloudy",
      humidity: 65,
    };
  }
}

Register the tool in a ToolRegistry:

src/registry.ts
import { ToolRegistry } from "@modernpath/agent-framework";
import { WeatherTool } from "./tools/weather";

export function createRegistry(): ToolRegistry {
  const registry = new ToolRegistry();
  registry.register("get_weather", new WeatherTool());
  return registry;
}

Dynamic tools

You can also register tools without decorators using createTool():

import { createTool, ToolRegistry } from "@modernpath/agent-framework";

const greetTool = createTool(
  { name: "greet", description: "Say hello", parameters: {
    name: { type: "string", description: "Name to greet", required: true },
  }},
  async (params) => `Hello, ${params.name}!`,
);

const registry = new ToolRegistry();
registry.register("greet", greetTool);

Step 4 -- Create an agent

For this tutorial, we will build a simple custom agent that uses GeminiClient directly. The framework also ships built-in agents like QAChatAgent for RAG workloads (see Built-in Agents).

src/agents/my-agent.ts
import { BaseAgent, AgentContext, ToolRegistry } from "@modernpath/agent-framework";
import { GeminiClient } from "@modernpath/agent-framework";

export class MyFirstAgent extends BaseAgent {
  public description = "A simple agent that answers questions and can check the weather.";

  constructor(
    toolRegistry: ToolRegistry,
    private readonly gemini: GeminiClient,
  ) {
    super("MyFirstAgent", "1.0.0", toolRegistry);
  }

  protected async executeInternal(context: AgentContext): Promise<any> {
    // Check if the user is asking about weather
    const prompt = context.prompt.toLowerCase();
    if (prompt.includes("weather")) {
      // Extract city (simple heuristic for demo)
      const cityMatch = context.prompt.match(/weather (?:in|for|at) (\w+)/i);
      const city = cityMatch?.[1] || "Helsinki";

      const weatherData = await this.useTool("get_weather", { city });

      const answer = await this.gemini.generateContent(
        `The user asked: "${context.prompt}"\n\n` +
        `Weather data: ${JSON.stringify(weatherData)}\n\n` +
        `Write a helpful response about the weather.`,
        { temperature: 0.5 },
      );

      return { answer: answer.text, weatherData };
    }

    // General question -- just use Gemini directly
    const answer = await this.gemini.generateContent(context.prompt, {
      systemPrompt: "You are a helpful assistant. Be concise and accurate.",
      temperature: 0.7,
    });

    return { answer: answer.text };
  }

  clone(toolRegistry: ToolRegistry): BaseAgent {
    return new MyFirstAgent(toolRegistry, this.gemini);
  }
}

Step 5 -- Create an HTTP endpoint

The framework provides handler factories that produce platform-agnostic request handlers. The gcpHttp adapter wraps them for GCP Cloud Functions (Express-compatible).

src/server.ts
import {
  createAgentExecuteHandler,
  gcpHttp,
} from "@modernpath/agent-framework";
import { gemini } from "./gemini";
import { createRegistry } from "./registry";
import { MyFirstAgent } from "./agents/my-agent";

const registry = createRegistry();
const agent = new MyFirstAgent(registry, gemini);

// Create the handler
const handler = createAgentExecuteHandler({
  resolveAgent: (agentType: string) => {
    if (agentType === "my-first-agent") return agent;
    throw new Error(`Unknown agentType: ${agentType}`);
  },
  getUserId: () => 1, // (1)!
});

// Wrap for GCP Cloud Functions / Express
export const agentExecute = gcpHttp(handler);
  1. In production, extract the user ID from an authentication token in the request headers.

You can test this locally with a lightweight Express server:

src/local-server.ts
import express from "express";
import { agentExecute } from "./server";

const app = express();
app.use(express.json());

// Mount the agent endpoint
app.post("/api/agents/:auditingId/execute", agentExecute as any);

app.listen(3001, () => {
  console.log("Agent server running at http://localhost:3001");
});
npm install express @types/express
GOOGLE_AI_STUDIO_KEY=your-key-here npx tsx src/local-server.ts

Test it with curl:

curl -X POST http://localhost:3001/api/agents/1/execute \
  -H "Content-Type: application/json" \
  -d '{"agentType": "my-first-agent", "prompt": "What is the weather in Helsinki?"}'

Step 6 -- Add a React UI with AgentChat

Now let's connect a React frontend. In a separate directory (or workspace), set up a React app:

npm create vite@latest my-agent-ui -- --template react-ts
cd my-agent-ui
npm install @modernpath/agent-ui-react

Wire up the AgentChat component with a backend factory:

src/App.tsx
import { AgentChat, createSseAgentBackend } from "@modernpath/agent-ui-react";

// For non-streaming, use createHttpAgentBackend instead
const backend = createSseAgentBackend({
  baseUrl: "http://localhost:3001",
  buildPath: (auditingId) => `/api/agents/${auditingId}/execute/stream`,
});

function App() {
  return (
    <div style={{ height: "100vh", padding: 24 }}>
      <AgentChat
        backend={backend}
        auditingId={1}
        agentType="my-first-agent"
        title="My First Agent"
        placeholder="Ask me anything..."
        stream={true}
      />
    </div>
  );
}

export default App;

Streaming requires an SSE endpoint

The createSseAgentBackend expects your server to expose a streaming SSE endpoint. Use createAgentExecuteStreamHandler on the backend to provide this. For the simplest setup, use createHttpAgentBackend with stream={false} instead:

import { AgentChat, createHttpAgentBackend } from "@modernpath/agent-ui-react";

const backend = createHttpAgentBackend({
  baseUrl: "http://localhost:3001",
  buildPath: (auditingId) => `/api/agents/${auditingId}/execute`,
});

<AgentChat
  backend={backend}
  auditingId={1}
  agentType="my-first-agent"
  title="My First Agent"
/>

Start the UI:

npm run dev

Open http://localhost:5173 in your browser. Type a question and the agent will respond through the chat interface.


What you have built

You now have a working full-stack agent application:

graph LR
    Browser["React UI<br/><code>AgentChat</code>"] -- "POST /api/agents/1/execute" --> Server["Express Server<br/><code>gcpHttp(handler)</code>"]
    Server --> Agent["MyFirstAgent<br/><code>executeInternal()</code>"]
    Agent --> Gemini["GeminiClient<br/><code>gemini-2.5-flash</code>"]
    Agent --> Tool["WeatherTool<br/><code>get_weather</code>"]

Next steps

  • Environment Setup -- Configure API keys, models, and feature flags.
  • Project Structure -- Organize for production with workspaces.
  • Built-in Agents -- Use QAChatAgent for RAG, OrchestratorAgent for multi-step tasks.
  • Tracing -- Add observability with setTraceStore().
  • Deployment -- Deploy to GCP Cloud Functions, Azure, or Cloud Run.