| 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 |
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.
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"
Before you begin, ensure you have the following:
- .NET 8.0 or later
- Azure OpenAI service endpoint and deployment configured
- Azure CLI installed and authenticated
- User has the
Cognitive Services OpenAI Contributorrole for the Azure OpenAI resource
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.
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 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 --prereleaseNote
The Microsoft.Agents.AI.Foundry package is required for the AsAIAgent() extension method that creates an Agent Framework agent from an AIProjectClient.
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.
AddAGUI: Registers AG-UI services with the dependency injection containerMapAGUI: Extension method that registers the AG-UI endpoint with automatic request/response handling and SSE streamingAsAIAgent: Creates an Agent Framework agent from anAIProjectClientwith 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:
AIProjectClientwithDefaultAzureCredentialprovides secure authentication
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:8888The 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.
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 the AG-UI client library:
dotnet add package Microsoft.Agents.AI.AGUI --prerelease
dotnet add package Microsoft.Agents.AI --prereleaseNote
The Microsoft.Agents.AI package provides the AsAIAgent() extension method.
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}");
}- 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
AGUIChatClientto create an agent from the client - RunStreamingAsync: Streams responses as
AgentResponseUpdateobjects - AsChatResponseUpdate: Extension method to access chat-specific properties like
ConversationIdandResponseId - Session Management: The
AgentSessionmaintains conversation context across requests - Content Types: Responses include
TextContentfor messages andErrorContentfor errors
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 runWith both the server and client running, you can now test the complete system.
$ 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
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
- Client sends HTTP POST request with messages
- ASP.NET Core endpoint receives the request via
MapAGUI - Agent processes the messages using Agent Framework
- Responses are converted to AG-UI events
- Events are streamed back as Server-Sent Events (SSE)
- Connection closes when the run completes
AGUIChatClientsends HTTP POST request to server endpoint- Server responds with SSE stream
- Client parses incoming events into
AgentResponseUpdateobjects - Each update is displayed based on its content type
ConversationIdis captured for conversation continuity- Stream completes when run finishes
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
Now that you understand the basics of AG-UI, you can:
- Add Backend Tools: Create custom function tools for your domain
::: zone-end
::: zone pivot="programming-language-python"
Before you begin, ensure you have the following:
- Python 3.10 or later
- Node.js 18 or later (for the CopilotKit frontend)
- Azure OpenAI service endpoint and deployment configured
- Azure CLI installed and authenticated
- User has the
Cognitive Services OpenAI Contributorrole for the Azure OpenAI resource
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.
Install the AG-UI integration on the agent (server) side:
pip install agent-framework-ag-ui --preOr using uv:
uv pip install agent-framework-ag-ui --prerelease=allowThis 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/clientCreate 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)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 asstate_schemaandpredict_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.pyThe server listens on http://127.0.0.1:8000. Keep it running while you set up the frontend.
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.
With the agent server running from Step 2, start your frontend in a separate terminal:
npm run devOpen 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.
The quickstart gets you a streaming chat. From here, add capabilities as you need them:
- Want tools your agent runs on the server? See Backend Tool Rendering.
- Want the agent to call actions that run in the browser? See Frontend Tools.
- Want approval workflows before an action runs? See Human-in-the-Loop.
- Want to synchronize state between the agent and the UI? See State Management.
- Want to render rich, custom UI from tool calls and agent state? See Generative UI: A Spectrum of UI Control below.
- Want the full CopilotKit React setup, hooks reference, and components? See the Microsoft Agent Framework integration for CopilotKit.
- Want to explore features interactively? See Testing with the AG-UI Dojo.
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.
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.
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>
),
});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>
),
});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" };
},
});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>
);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} />
),
});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.
- AG-UI Overview
- Agent Framework Documentation
- Microsoft Agent Framework integration for CopilotKit
- AG-UI Protocol Specification
::: zone-end