AI ์์ด์ ํธ๋ฅผ ๋ง๋ค๋ค๋ณด๋ฉด, ์ง๊ธ์ ํฉ์ด์ง ์ฌ๋ฌ ๋๊ตฌ๋ค์ ๋ชจ์ผ๊ฑฐ๋, ์ผ๋ฐ ํ๋ก ํธ์๋ ์ ํ๋ฆฌ์ผ์ด์
์์ ์ผ์ผ์ด AI ์์ด์ ํธ๊ฐ ๋ ๋ค์ด๋๋ ์๋ต์ ์ด๋ป๊ฒ ํ์ํ ๊ฒ์ธ์ง์ ๋ํด์ ๋๋ฌด ์ธ๋ถ์ ์ธ ์ฌํญ๋ค์ ๊ณ ๋ฏผํด์ผํ๋ค๋ณด๋ ์ค์ฉ์ ์ด๊ฑฐ๋ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ฝ๋๋ฅผ ๋ง๋๋ ๋๋์ด ์ ํ ๋ค์ง ์์์ต๋๋ค. ํนํ ๋ท๋ท์ ๊ฒฝ์ฐ์๋ ์ํ๋ ๋ชฉ์ ์ง๊น์ง ๊ฐ๋ ๊ณผ์ ์์์ ceremony๋ boilerplate๊ฐ ๋๋ฌด ๋ง๊ธฐ๋ ํ๊ณ ์ ![]()
์ค๋ซ๋ง์ MS Agent Framework ๋ฌธ์๋ฅผ ๋ณด๋ค๊ฐ AG-UI ๋ผ๋ ๊ฒ์ ์ฐพ๊ฒ ๋์ ์ข ์ดํด๋ณด๋ ๊ทธ๊ฐ ์ ๊ฐ ๋ต๋ตํ๋ค๊ณ ๋๊ผ๋ ๋ถ๋ถ๋ค์ ์ ๋ถ ํด์ํด์ฃผ๋ ๋๋์ด๋ผ ๋ฐ๊ฐ์ด ๋ง์์ ์ฝ๋ ์ํ์ ๊ณต์ ํด๋ด ๋๋ค.
์ผ๋จ AG-UI๋ ๊ธฐ์กด์ ์ผ์ผ์ด ์์ผ๋ก ๊ตฌํํด์ผ ํ๋ ๋ง์ ๋ถ๋ถ๋ค์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ๊ฒ ํฌ์ฅํด์ฃผ๋ ๋๊ตฌ์ด๊ณ , ๋ท๋ท๋ง์ ๊ธฐ์ ์ด ์๋๋ผ AI ์์ด์ ํธ ๊ธฐ์ ์ ์ฐธ์ฌํ๋ ๊ณณ์ด๋ผ๋ฉด ๋๋ํ ๊ฒ์์ด ํ์ค์ผ๋ก ์ ๊ณตํ๋ ํ๋กํ ์ฝ์ด๋ผ ์ ๋ง ์ฌ์ฉํ๊ธฐ ์ข์ต๋๋ค. ์ฆ, ๋ญ์ฒด์ธ์ผ๋ก AG-UI ์๋ฒ๋ฅผ ๋ง๋ค๊ณ , ๋ท๋ท ๋ฐ์คํฌํฑ ํด๋ผ์ด์ธํธ๋ ๋ธ๋ ์ด์ WASM ์ฑ์ด ๋ถ๋ ๊ตฌ์ฑ์ด ์ถฉ๋ถํ ๊ฐ๋ฅํฉ๋๋ค.
๊ทธ๋ฆฌ๊ณ AG-UI๋ฅผ ์ฌ์ฉํ๋ฉด, (1) ์๋ฒ๊ฐ ์ ๊ณตํ๋ ๋๊ตฌ ํธ์ถ์ ๋ฌผ๋ก , (2) ์๋ฒ๊ฐ ์ง์ ์ฒ๋ฆฌํ ์ ์๊ณ ํด๋ผ์ด์ธํธ์๊ฒ ์์ํด์ผ ํ๋ ๋๊ตฌ ํธ์ถ๊น์ง ํ ๋ฒ์ ๊ตฌ์ฑํ ์ ์์ต๋๋ค.
AG-UI ์๋ฒ ์ธก ๊ตฌํ (ASP .NET Core Kestrel)
์ฌ์ฉํ๊ธฐ ์ํ๋ IChatClient ๊ตฌํ์ฒด์ tool calling์ ์ง์ํ๋ ์ ์ ํ ๋ชจ๋ธ์ ์ฐพ์ ์ฐ๊ฒฐํ๊ธฐ๋ง ํ๋ฉด, ์์ด์ ํธ๋ฅผ ์ํ ์์คํ ํ๋กฌํํธ๋ฅผ ํ๋ค๊ฒ ๊ณ ๋ฏผํ์ง ์์๋ ๋๋จธ์ง๋ ํ๋ ์์ํฌ ๋ ๋ฒจ์์ ์ ๋ถ ๋ํํด์ค๋๋ค.
๊ทธ๋ฆฌ๊ณ SSE ๋ฐฉ์์ ์ถ๋ ฅ์ Kestrel์ ์ง์ ํตํฉํ ์ ์์ด์, ์ํ๋ subpath ์ฃผ์๋ฅผ agent ์ ์ฉ์ผ๋ก ํ ๋นํ๊ณ ํด๋ผ์ด์ธํธ๊ฐ ์ด ์ฃผ์๋ฅผ ์ฐพ์์ ์ฐ๊ฒฐํ ์ ์๊ฒ ํฌ์ฅํ ์ ์์ต๋๋ค. (์ด ๋ถ๋ถ์ด ์ ๋ง ์ข์ ๋ถ๋ถ์ ๋๋ค.)
#:sdk Microsoft.NET.Sdk.Web
#:package Microsoft.Agents.AI.Hosting.AGUI.AspNetCore@1.0.0-preview.251114.1
#:package Microsoft.Extensions.AI.OpenAI@10.0.0-preview.1.25560.10
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using OpenAI;
using System.ClientModel;
using Microsoft.Extensions.Logging;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<MathTools>();
builder.Services.AddChatClient(
new OpenAIClient(
new ApiKeyCredential(Util.GetPassword("openrouter-key")),
new OpenAIClientOptions { Endpoint = new Uri("https://openrouter.ai/api/v1", UriKind.Absolute), }
).GetChatClient("x-ai/grok-4.1-fast").AsIChatClient());
using var app = builder.Build();
var mathTool = app.Services.GetRequiredService<MathTools>();
var agent = app.Services.GetRequiredService<IChatClient>().CreateAIAgent(
name: "AGUIAssistant",
instructions: "You are a helpful assistant.",
tools: [
AIFunctionFactory.Create(mathTool.Add, nameof(mathTool.Add), "Add two numbers"),
AIFunctionFactory.Create(mathTool.Add, nameof(mathTool.Sub), "Subtract two numbers"),
]);
app.MapAGUI("/aguitest", agent);
app.Run();
public class MathTools(ILogger<MathTools> logger)
{
public int Add(int a, int b)
{
logger.LogInformation("Add tool called: {a}, {b}", a, b);
return a + b;
}
public int Sub(int a, int b)
{
logger.LogInformation("Subtool called: {a}, {b}", a, b);
return a - b;
}
}
ํด๋ผ์ด์ธํธ
ํด๋ผ์ด์ธํธ์ ๊ฒฝ์ฐ์ ์๋ฒ์ ์ฌ์ ์ ์ฝ์ํ ์ฃผ์๋ก ์ฐ๊ฒฐํ๋ฉด ๋ฉ๋๋ค. AG-UI ์๋ฒ์๊ฒ โ๋ ์ด๋ฐ ํด ๊ฐ์ง๊ณ ์์ดโ๋ผ๊ณ ์๋ฆฌ๊ณ ์ฌ์ฉํ๊ฒ๋ ์ ๋ํ๊ณ ์ถ๋ค๋ฉด, tools ํ๋ผ๋ฏธํฐ์ ์๋ฒ์์ ๋ง๋ค์๋ ๊ฒ๊ณผ ๋๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ๋๊ตฌ๋ฅผ ๋ฑ๋กํ๋ฉด, ํ๋กฌํํธ์ ๋ฐ๋ผ์ ์๋์ผ๋ก ํธ์ถ ๋ํ์ด ์ด๋ฃจ์ด์ง๊ฒ ๋ฉ๋๋ค.
ํนํ ์ด ํด๋ผ์ด์ธํธ ๋ฐฉ์์ ์๋ฌด๋ฆฌ ๋ ๊ฑฐ์ ๊ธฐ์ ์ด๋ผ ํ ์ง๋ผ๋ (WinForm, WPF๋ผ ํ ์ง๋ผ๋) AI ๊ธฐ๋ฅ์ ๊ฐ๋ณ๊ฒ ํตํฉ๋๋ค๋ ์ ์ด ํฐ ๋ฌด๊ธฐ์ ๋๋ค.
#:package Microsoft.Agents.AI.AGUI@1.0.0-preview.251114.1
#:package Microsoft.Agents.AI@1.0.0-preview.251114.1
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.AGUI;
using Microsoft.Extensions.AI;
var serverUrl = "http://localhost:5000/aguitest";
Console.WriteLine($"Connecting to AG-UI server at: {serverUrl}\n");
using var httpClient = new HttpClient()
{
Timeout = TimeSpan.FromSeconds(60)
};
AGUIChatClient chatClient = new(httpClient, serverUrl);
AIAgent agent = chatClient.CreateAIAgent(
name: "agui-client",
description: "AG-UI Client Agent",
tools: []);
AgentThread thread = agent.GetNewThread();
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 (AgentRunResponseUpdate update in agent.RunStreamingAsync(messages, thread))
{
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}");
}
๋ ์์ธํ ๋ด์ฉ์ ์ด๊ณณ์์ ๋ณด์ค ์ ์์ต๋๋ค. ์์ง์ ํ๋ฆฌ๋ทฐ ๋ฒ์ ์ด์ง๋ง, ์ถฉ๋ถํ ํ
์คํธํด๋ณผ ๊ฐ์น๊ฐ ์๋ ๊ธฐ์ ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค. ![]()