Skip to content

Latest commit

 

History

History
818 lines (602 loc) · 28.7 KB

File metadata and controls

818 lines (602 loc) · 28.7 KB
title Getting Started with AG-UI
description Step-by-step tutorial to build your first AG-UI server and client with Agent Framework
zone_pivot_groups programming-languages
author moonbox3
ms.topic tutorial
ms.author evmattso
ms.date 04/01/2026
ms.service agent-framework

Getting Started with AG-UI

This tutorial demonstrates how to build both server and client applications using the AG-UI protocol with .NET or Python and Agent Framework. You'll learn how to create an AG-UI server that hosts an AI agent and a client that connects to it for interactive conversations.

What You'll Build

By the end of this tutorial, you'll have:

  • An AG-UI server hosting an AI agent accessible via HTTP
  • A client application that connects to the server and streams responses
  • Understanding of how the AG-UI protocol works with Agent Framework

::: zone pivot="programming-language-csharp"

Prerequisites

Before you begin, ensure you have the following:

Note

These samples use Azure OpenAI models. For more information, see how to deploy Azure OpenAI models with Microsoft Foundry.

Note

These samples use DefaultAzureCredential for authentication. Make sure you're authenticated with Azure (e.g., via az login). For more information, see the Azure Identity documentation.

Warning

The AG-UI protocol is still under development and subject to change. We will keep these samples updated as the protocol evolves.

Step 1: Creating an AG-UI Server

The AG-UI server hosts your AI agent and exposes it via HTTP endpoints using ASP.NET Core.

Note

The server project requires the Microsoft.NET.Sdk.Web SDK. If you're creating a new project from scratch, use dotnet new web or ensure your .csproj file uses <Project Sdk="Microsoft.NET.Sdk.Web"> instead of Microsoft.NET.Sdk.

Install Required Packages

Install the necessary packages for the server:

dotnet add package Microsoft.Agents.AI.Hosting.AGUI.AspNetCore --prerelease
dotnet add package Azure.AI.Projects --prerelease
dotnet add package Azure.Identity
dotnet add package Microsoft.Agents.AI.Foundry --prerelease

Note

The Microsoft.Agents.AI.Foundry package is required for the AsAIAgent() extension method that creates an Agent Framework agent from an AIProjectClient.

Server Code

Create a file named Program.cs:

// Copyright (c) Microsoft. All rights reserved.

using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient().AddLogging();
builder.Services.AddAGUI();

WebApplication app = builder.Build();

string endpoint = builder.Configuration["AZURE_OPENAI_ENDPOINT"]
    ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
string deploymentName = builder.Configuration["AZURE_OPENAI_DEPLOYMENT_NAME"]
    ?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set.");

// Create the AI agent
AIAgent agent = new AIProjectClient(
        new Uri(endpoint),
        new DefaultAzureCredential())
    .AsAIAgent(
        model: deploymentName,
        name: "AGUIAssistant",
        instructions: "You are a helpful assistant.");

// Map the AG-UI agent endpoint
app.MapAGUI("/", agent);

await app.RunAsync();

Warning

DefaultAzureCredential is convenient for development but requires careful consideration in production. In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid latency issues, unintended credential probing, and potential security risks from fallback mechanisms.

Key Concepts

  • AddAGUI: Registers AG-UI services with the dependency injection container
  • MapAGUI: Extension method that registers the AG-UI endpoint with automatic request/response handling and SSE streaming
  • AsAIAgent: Creates an Agent Framework agent from an AIProjectClient with a specified model and instructions
  • ASP.NET Core Integration: Uses ASP.NET Core's native async support for streaming responses
  • Instructions: The agent is created with default instructions, which can be overridden by client messages
  • Configuration: AIProjectClient with DefaultAzureCredential provides secure authentication

Configure and Run the Server

Set the required environment variables:

export AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"

Run the server:

dotnet run --urls http://localhost:8888

The server will start listening on http://localhost:8888.

Note

Keep this server running while you set up and run the client in Step 2. Both the server and client need to run simultaneously for the complete system to work.

Step 2: Creating an AG-UI Client

The AG-UI client connects to the remote server and displays streaming responses.

Important

Before running the client, ensure the AG-UI server from Step 1 is running at http://localhost:8888.

Install Required Packages

Install the AG-UI client library:

dotnet add package Microsoft.Agents.AI.AGUI --prerelease
dotnet add package Microsoft.Agents.AI --prerelease

Note

The Microsoft.Agents.AI package provides the AsAIAgent() extension method.

Client Code

Create a file named Program.cs:

// Copyright (c) Microsoft. All rights reserved.

using Microsoft.Agents.AI;
using Microsoft.Agents.AI.AGUI;
using Microsoft.Extensions.AI;

string serverUrl = Environment.GetEnvironmentVariable("AGUI_SERVER_URL") ?? "http://localhost:8888";

Console.WriteLine($"Connecting to AG-UI server at: {serverUrl}\n");

// Create the AG-UI client agent
using HttpClient httpClient = new()
{
    Timeout = TimeSpan.FromSeconds(60)
};

AGUIChatClient chatClient = new(httpClient, serverUrl);

AIAgent agent = chatClient.AsAIAgent(
    name: "agui-client",
    description: "AG-UI Client Agent");

AgentSession session = await agent.CreateSessionAsync();
List<ChatMessage> messages =
[
    new(ChatRole.System, "You are a helpful assistant.")
];

try
{
    while (true)
    {
        // Get user input
        Console.Write("\nUser (:q or quit to exit): ");
        string? message = Console.ReadLine();

        if (string.IsNullOrWhiteSpace(message))
        {
            Console.WriteLine("Request cannot be empty.");
            continue;
        }

        if (message is ":q" or "quit")
        {
            break;
        }

        messages.Add(new ChatMessage(ChatRole.User, message));

        // Stream the response
        bool isFirstUpdate = true;
        string? threadId = null;

        await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, session))
        {
            ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate();

            // First update indicates run started
            if (isFirstUpdate)
            {
                threadId = chatUpdate.ConversationId;
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine($"\n[Run Started - Thread: {chatUpdate.ConversationId}, Run: {chatUpdate.ResponseId}]");
                Console.ResetColor();
                isFirstUpdate = false;
            }

            // Display streaming text content
            foreach (AIContent content in update.Contents)
            {
                if (content is TextContent textContent)
                {
                    Console.ForegroundColor = ConsoleColor.Cyan;
                    Console.Write(textContent.Text);
                    Console.ResetColor();
                }
                else if (content is ErrorContent errorContent)
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine($"\n[Error: {errorContent.Message}]");
                    Console.ResetColor();
                }
            }
        }

        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine($"\n[Run Finished - Thread: {threadId}]");
        Console.ResetColor();
    }
}
catch (Exception ex)
{
    Console.WriteLine($"\nAn error occurred: {ex.Message}");
}

Key Concepts

  • Server-Sent Events (SSE): The protocol uses SSE for streaming responses
  • AGUIChatClient: Client class that connects to AG-UI servers and implements IChatClient
  • AsAIAgent: Extension method on AGUIChatClient to create an agent from the client
  • RunStreamingAsync: Streams responses as AgentResponseUpdate objects
  • AsChatResponseUpdate: Extension method to access chat-specific properties like ConversationId and ResponseId
  • Session Management: The AgentSession maintains conversation context across requests
  • Content Types: Responses include TextContent for messages and ErrorContent for errors

Configure and Run the Client

Optionally set a custom server URL:

export AGUI_SERVER_URL="http://localhost:8888"

Run the client in a separate terminal (ensure the server from Step 1 is running):

dotnet run

Step 3: Testing the Complete System

With both the server and client running, you can now test the complete system.

Expected Output

$ dotnet run
Connecting to AG-UI server at: http://localhost:8888

User (:q or quit to exit): What is 2 + 2?

[Run Started - Thread: thread_abc123, Run: run_xyz789]
2 + 2 equals 4.
[Run Finished - Thread: thread_abc123]

User (:q or quit to exit): Tell me a fun fact about space

[Run Started - Thread: thread_abc123, Run: run_def456]
Here's a fun fact: A day on Venus is longer than its year! Venus takes
about 243 Earth days to rotate once on its axis, but only about 225 Earth
days to orbit the Sun.
[Run Finished - Thread: thread_abc123]

User (:q or quit to exit): :q

Color-Coded Output

The client displays different content types with distinct colors:

  • Yellow: Run started notifications
  • Cyan: Agent text responses (streamed in real-time)
  • Green: Run completion notifications
  • Red: Error messages

How It Works

Server-Side Flow

  1. Client sends HTTP POST request with messages
  2. ASP.NET Core endpoint receives the request via MapAGUI
  3. Agent processes the messages using Agent Framework
  4. Responses are converted to AG-UI events
  5. Events are streamed back as Server-Sent Events (SSE)
  6. Connection closes when the run completes

Client-Side Flow

  1. AGUIChatClient sends HTTP POST request to server endpoint
  2. Server responds with SSE stream
  3. Client parses incoming events into AgentResponseUpdate objects
  4. Each update is displayed based on its content type
  5. ConversationId is captured for conversation continuity
  6. Stream completes when run finishes

Protocol Details

The AG-UI protocol uses:

  • HTTP POST for sending requests
  • Server-Sent Events (SSE) for streaming responses
  • JSON for event serialization
  • Thread IDs (as ConversationId) for maintaining conversation context
  • Run IDs (as ResponseId) for tracking individual executions

Next Steps

Now that you understand the basics of AG-UI, you can:

Additional Resources

::: zone-end

::: zone pivot="programming-language-python"

Prerequisites

Before you begin, ensure you have the following:

Note

These samples use Azure OpenAI models. For more information, see how to deploy Azure OpenAI models with Foundry.

Note

These samples use AzureCliCredential for authentication. Make sure you're authenticated with Azure (e.g., via az login). For more information, see the Azure Identity documentation.

Warning

The AG-UI protocol is still under development and subject to change. We will keep these samples updated as the protocol evolves.

This quickstart takes the shortest path to a running app: install the packages, expose an agent over AG-UI with FastAPI, and point a CopilotKit React frontend at it. For a language-agnostic client (a Python AGUIChatClient or curl), see Backend Tool Rendering.

Step 1: Install the Packages

Install the AG-UI integration on the agent (server) side:

pip install agent-framework-ag-ui --pre

Or using uv:

uv pip install agent-framework-ag-ui --prerelease=allow

This installs agent-framework-core, fastapi, and uvicorn as dependencies.

Install the CopilotKit packages on the frontend side (from your React or Next.js app):

npm install @copilotkit/react-core @copilotkit/react-ui @copilotkit/runtime @ag-ui/client

Step 2: Define and Expose an Agent

Create a file named server.py. Define an Agent Framework agent, wrap it in an AgentFrameworkAgent, and register it as an AG-UI endpoint on a FastAPI app.

"""AG-UI server exposed to a CopilotKit frontend."""

import os

from agent_framework import Agent
from agent_framework.openai import OpenAIChatCompletionClient
from agent_framework_ag_ui import AgentFrameworkAgent, add_agent_framework_fastapi_endpoint
from azure.identity import AzureCliCredential
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

endpoint = os.environ["AZURE_OPENAI_ENDPOINT"]
deployment_name = os.environ["AZURE_OPENAI_CHAT_COMPLETION_MODEL"]

chat_client = OpenAIChatCompletionClient(
    model=deployment_name,
    azure_endpoint=endpoint,
    api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
    credential=AzureCliCredential(),
)

# The base agent handles reasoning and tool calls.
base_agent = Agent(
    name="AGUIAssistant",
    instructions="You are a helpful assistant.",
    client=chat_client,
)

# AgentFrameworkAgent adapts the agent to the AG-UI protocol and is the
# entry point for generative UI features (tools, shared state, and more).
agent = AgentFrameworkAgent(
    agent=base_agent,
    name="MyAgent",
    description="A helpful assistant exposed over AG-UI.",
)

app = FastAPI(title="AG-UI Server")

# Allow the frontend's browser requests during local development.
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

# Register the agent at the root path.
add_agent_framework_fastapi_endpoint(app, agent, "/")

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="127.0.0.1", port=8000)

Key Concepts

  • Agent: The Agent Framework agent that reasons over messages and calls tools.
  • AgentFrameworkAgent: A lightweight wrapper that adapts the agent to the AG-UI protocol. It's also where you enable generative UI features such as state_schema and predict_state_config.
  • add_agent_framework_fastapi_endpoint: Registers the agent as a FastAPI endpoint with automatic request handling and SSE streaming.

Set the required environment variables and run the server:

export AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
export AZURE_OPENAI_CHAT_COMPLETION_MODEL="gpt-4o-mini"

python server.py

The server listens on http://127.0.0.1:8000. Keep it running while you set up the frontend.

Step 3: Point a CopilotKit Frontend at the Agent

CopilotKit connects to your AG-UI endpoint through a runtime route, then renders a chat UI with a provider and a chat component.

First, add a runtime route that proxies to your agent. In a Next.js App Router project, create app/api/copilotkit/route.ts:

import { CopilotRuntime, copilotRuntimeNextJSAppRouterEndpoint, ExperimentalEmptyAdapter } from "@copilotkit/runtime";
import { HttpAgent } from "@ag-ui/client";
import { NextRequest } from "next/server";

// Point the runtime at the FastAPI endpoint from Step 2.
const runtime = new CopilotRuntime({
  agents: {
    my_agent: new HttpAgent({ url: "http://127.0.0.1:8000/" }),
  },
});

export const POST = async (req: NextRequest) => {
  const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
    runtime,
    serviceAdapter: new ExperimentalEmptyAdapter(),
    endpoint: "/api/copilotkit",
  });
  return handleRequest(req);
};

Then render the chat UI. In app/page.tsx:

"use client";

import { CopilotKit, CopilotChat } from "@copilotkit/react-core/v2";

export default function Page() {
  return (
    <CopilotKit runtimeUrl="/api/copilotkit" agent="my_agent">
      <CopilotChat agentId="my_agent" className="h-screen" />
    </CopilotKit>
  );
}

The agent prop, the agents key in the runtime, and each hook's agentId all use the same identifier (my_agent here).

Tip

Frontend tools and UI renderers you register in the browser are forwarded to your agent automatically over the AG-UI protocol, so all the generative UI patterns below work without extra server wiring.

Step 4: Run It

With the agent server running from Step 2, start your frontend in a separate terminal:

npm run dev

Open the app, ask the assistant a question, and watch the response stream in token by token. You now have a working AG-UI + Agent Framework app with a CopilotKit UI.

Dive Deeper

The quickstart gets you a streaming chat. From here, add capabilities as you need them:

Generative UI: A Spectrum of UI Control

Generative UI is the ability to render agent activity as real interface, not just text. AG-UI and CopilotKit support a spectrum of approaches, from the agent driving everything with zero frontend code to your app composing UI freely. Pick the lowest rung that meets your need; each one gives your app more control at the cost of more frontend code.

Rung Pattern Who designs the UI Frontend code
1 Automatic tool rendering Built-in default renderer None
2 Custom tool rendering Your app, per named tool useRenderTool
3 Catch-all rendering Your app, one renderer for all tools useDefaultRenderTool
4 Frontend tools Your app runs browser actions useFrontendTool
5 State-driven rendering Your app renders live agent state useAgent
6 Your components Your app's React components useComponent
7 Open generative UI The agent composes UI openGenerativeUI

All snippets below build on the server.py and frontend from the quickstart. On the agent side you add tools or state; on the frontend you add a hook. This spectrum intentionally omits the fully declarative (A2UI) approach.

1. Automatic Tool Rendering

Define a tool on your agent and the default chat UI renders each call (arguments, status, and result) with no frontend code. Use this when you want zero-effort feedback about what the agent is doing.

Add a backend tool to the agent in server.py:

from typing import Annotated

from agent_framework import Agent, tool
from pydantic import Field


@tool
def get_weather(
    location: Annotated[str, Field(description="The location to get weather for")],
) -> str:
    return f"The weather for {location} is 70 degrees."


base_agent = Agent(
    name="AGUIAssistant",
    instructions="You are a helpful assistant. Use tools when relevant.",
    client=chat_client,
    tools=[get_weather],
)

No frontend changes are needed; <CopilotChat /> renders the tool call with its built-in default renderer.

2. Custom Tool Rendering

Supply a branded component for a specific tool. The renderer's name must match the tool name. Use this when a tool deserves a purpose-built card instead of the generic view.

The agent keeps the same get_weather tool from rung 1. On the frontend, register a renderer with useRenderTool:

import { useRenderTool } from "@copilotkit/react-core/v2";
import { z } from "zod";

useRenderTool({
  name: "get_weather",
  parameters: z.object({ location: z.string() }),
  render: ({ status, parameters }) => (
    <div className="rounded-lg border p-3">
      {status !== "complete"
        ? `Checking weather for ${parameters.location}…`
        : `Weather for ${parameters.location} is ready.`}
    </div>
  ),
});

3. Catch-all Rendering

Register a single renderer that paints every tool call not claimed by a named renderer. Use this for a consistent look across many tools without writing one component per tool.

import { useDefaultRenderTool } from "@copilotkit/react-core/v2";

// A convenience wrapper around useRenderTool({ name: "*", ... }).
useDefaultRenderTool({
  render: ({ name, status, parameters, result }) => (
    <div className="rounded-lg border p-3">
      <strong>{name}</strong>: {status}
      {result ? <pre>{JSON.stringify(result, null, 2)}</pre> : null}
    </div>
  ),
});

4. Frontend Tools

Let the agent call an action that runs in the user's browser, such as reading component state, calling a browser API, or triggering an animation. The handler can be async. Register it with useFrontendTool; CopilotKit forwards it to the agent over AG-UI, so no backend tool definition is required.

import { useFrontendTool } from "@copilotkit/react-core/v2";
import { z } from "zod";

useFrontendTool({
  name: "change_background",
  description: "Change the page background to any valid CSS background value.",
  parameters: z.object({ background: z.string() }),
  handler: async ({ background }) => {
    document.body.style.background = background;
    return { status: "success" };
  },
});

5. State-driven Rendering

Render the agent's live state (a plan, a progress list, a draft document) as it updates. Declare a state_schema, update state from a tool, and subscribe to it on the frontend.

On the agent side, use state_update to publish state after each transition and pass state_schema to the wrapper:

from typing import Annotated

from agent_framework import Agent, tool
from agent_framework_ag_ui import AgentFrameworkAgent, state_update
from pydantic import Field

STATE_SCHEMA: dict[str, object] = {
    "steps": {
        "type": "array",
        "items": {
            "type": "object",
            "properties": {
                "title": {"type": "string"},
                "status": {"type": "string", "enum": ["pending", "in_progress", "completed"]},
            },
        },
        "description": "Ordered plan steps with live status.",
    }
}


@tool(name="set_steps", description="Publish the full plan and step statuses. Call on every transition.")
def set_steps(
    steps: Annotated[list[dict], Field(description="The complete list of steps with title and status.")],
):
    return state_update(text=f"Published {len(steps)} step(s).", state={"steps": steps})


base_agent = Agent(
    name="Planner",
    instructions="Plan the work as steps, then walk each step pending → in_progress → completed by calling set_steps.",
    client=chat_client,
    tools=[set_steps],
)

agent = AgentFrameworkAgent(
    agent=base_agent,
    name="PlannerAgent",
    description="Streams a live plan to the UI.",
    state_schema=STATE_SCHEMA,
)

On the frontend, read the state with useAgent:

import { useAgent } from "@copilotkit/react-core/v2";

// For type safety, define a type matching your agent's state snapshot.
type AgentState = { steps: { title: string; status: string }[] };

const { agent } = useAgent({ agentId: "my_agent" });

return (
  <ul>
    {agent.state?.steps?.map((step, i) => (
      <li key={i}>{step.status === "completed" ? "✅" : "⏳"} {step.title}</li>
    ))}
  </ul>
);

6. Your Components

Register your own React components and let the agent decide when to render them. Start display-only, then make them interactive by letting the component send a value back to the agent. Register components with useComponent; CopilotKit forwards them to the agent as callable tools, so instruct the agent to call them by name.

base_agent = Agent(
    name="ChartAssistant",
    instructions=(
        "When the user asks for a chart, call render_bar_chart with a title and a "
        "data array of {label, value} items."
    ),
    client=chat_client,
    # No backend tools; the components are registered on the frontend and
    # forwarded to the agent as tools it can call by name.
    tools=[],
)

Display-only: the agent supplies the data, the component renders it:

import { useComponent } from "@copilotkit/react-core/v2";
import { z } from "zod";

useComponent({
  name: "render_bar_chart",
  description: "Display a bar chart with labeled numeric values.",
  parameters: z.object({
    title: z.string(),
    data: z.array(z.object({ label: z.string(), value: z.number() })),
  }),
  render: BarChart, // your component
});

Interactive: the component collects input and calls respond to return it to the agent:

useComponent({
  name: "pick_meeting_time",
  description: "Ask the user to choose a meeting time.",
  parameters: z.object({ options: z.array(z.string()) }),
  render: ({ respond, status, args }) => (
    <MeetingTimePicker status={status} respond={respond} {...args} />
  ),
});

7. Open Generative UI

Let the agent compose UI on the fly. CopilotKit auto-registers a generateSandboxedUi frontend tool; when the agent calls it, the streamed HTML and CSS is mounted inside a sandboxed iframe. Enable it on both the runtime and the provider, with no per-component code required.

In the runtime route, list the agents that may generate UI:

const runtime = new CopilotRuntime({
  agents: { my_agent: new HttpAgent({ url: "http://127.0.0.1:8000/" }) },
  openGenerativeUI: { agents: ["my_agent"] },
});

In the provider, turn it on:

<CopilotKit runtimeUrl="/api/copilotkit" agent="my_agent" openGenerativeUI={{}}>
  <CopilotChat agentId="my_agent" className="h-screen" />
</CopilotKit>

On the agent side, instruct it to call the tool:

base_agent = Agent(
    name="UIAssistant",
    instructions="On each turn, call generateSandboxedUi once to build a self-contained HTML + CSS widget that answers the user.",
    client=chat_client,
    tools=[],
)

For an advanced variant where the generated UI calls back into host functions you define, pass those functions to the provider with openGenerativeUI={{ sandboxFunctions }}. See Open Generative UI in the CopilotKit docs.

Additional Resources

::: zone-end