Bagikan melalui


Visualisasi Peralatan Backend dengan AG-UI

Tutorial ini menunjukkan kepada Anda cara menambahkan alat fungsi ke agen AG-UI Anda. Alat fungsi adalah metode C# kustom yang dapat dipanggil agen untuk melakukan tugas tertentu seperti mengambil data, melakukan penghitungan, atau berinteraksi dengan sistem eksternal. Dengan AG-UI, alat-alat ini dijalankan pada backend dan hasilnya secara otomatis dialirkan ke klien.

Prasyarat

Sebelum memulai, pastikan Anda telah menyelesaikan tutorial Memulai dan memiliki:

  • .NET 8.0 atau yang lebih baru
  • Microsoft.Agents.AI.Hosting.AGUI.AspNetCore paket terinstal
  • Layanan Azure OpenAI dikonfigurasi
  • Pemahaman dasar tentang server AG-UI dan penyiapan klien

Apa itu Backend Tool Rendering?

Penyajian alat backend berarti:

  • Alat fungsi ditentukan di server
  • Agen AI memutuskan kapan harus memanggil alat-alat ini
  • Alat dijalankan di backend (sisi server)
  • Peristiwa dan hasil panggilan alat dialirkan ke klien secara real time
  • Klien menerima pembaruan tentang kemajuan eksekusi alat

Membuat Server AG-UI dengan Alat Fungsi

Berikut adalah implementasi server lengkap yang menunjukkan cara mendaftarkan alat dengan jenis parameter kompleks:

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

using System.ComponentModel;
using System.Text.Json.Serialization;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Options;
using OpenAI.Chat;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient().AddLogging();
builder.Services.ConfigureHttpJsonOptions(options =>
    options.SerializerOptions.TypeInfoResolverChain.Add(SampleJsonSerializerContext.Default));
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.");

// Define request/response types for the tool
internal sealed class RestaurantSearchRequest
{
    public string Location { get; set; } = string.Empty;
    public string Cuisine { get; set; } = "any";
}

internal sealed class RestaurantSearchResponse
{
    public string Location { get; set; } = string.Empty;
    public string Cuisine { get; set; } = string.Empty;
    public RestaurantInfo[] Results { get; set; } = [];
}

internal sealed class RestaurantInfo
{
    public string Name { get; set; } = string.Empty;
    public string Cuisine { get; set; } = string.Empty;
    public double Rating { get; set; }
    public string Address { get; set; } = string.Empty;
}

// JSON serialization context for source generation
[JsonSerializable(typeof(RestaurantSearchRequest))]
[JsonSerializable(typeof(RestaurantSearchResponse))]
internal sealed partial class SampleJsonSerializerContext : JsonSerializerContext;

// Define the function tool
[Description("Search for restaurants in a location.")]
static RestaurantSearchResponse SearchRestaurants(
    [Description("The restaurant search request")] RestaurantSearchRequest request)
{
    // Simulated restaurant data
    string cuisine = request.Cuisine == "any" ? "Italian" : request.Cuisine;

    return new RestaurantSearchResponse
    {
        Location = request.Location,
        Cuisine = request.Cuisine,
        Results =
        [
            new RestaurantInfo
            {
                Name = "The Golden Fork",
                Cuisine = cuisine,
                Rating = 4.5,
                Address = $"123 Main St, {request.Location}"
            },
            new RestaurantInfo
            {
                Name = "Spice Haven",
                Cuisine = cuisine == "Italian" ? "Indian" : cuisine,
                Rating = 4.7,
                Address = $"456 Oak Ave, {request.Location}"
            },
            new RestaurantInfo
            {
                Name = "Green Leaf",
                Cuisine = "Vegetarian",
                Rating = 4.3,
                Address = $"789 Elm Rd, {request.Location}"
            }
        ]
    };
}

// Get JsonSerializerOptions from the configured HTTP JSON options
Microsoft.AspNetCore.Http.Json.JsonOptions jsonOptions = app.Services.GetRequiredService<IOptions<Microsoft.AspNetCore.Http.Json.JsonOptions>>().Value;

// Create tool with serializer options
AITool[] tools =
[
    AIFunctionFactory.Create(
        SearchRestaurants,
        serializerOptions: jsonOptions.SerializerOptions)
];

// Create the AI agent with tools
ChatClient chatClient = new AzureOpenAIClient(
        new Uri(endpoint),
        new DefaultAzureCredential())
    .GetChatClient(deploymentName);

ChatClientAgent agent = chatClient.AsIChatClient().AsAIAgent(
    name: "AGUIAssistant",
    instructions: "You are a helpful assistant with access to restaurant information.",
    tools: tools);

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

await app.RunAsync();

Konsep utama

  • Eksekusi di sisi server: Alat dijalankan dalam proses server
  • Streaming otomatis: Panggilan dan hasil alat dialirkan ke klien secara real time

Penting

Saat membuat alat dengan jenis parameter kompleks (objek, array, dll.), Anda harus memberikan serializerOptions parameter ke AIFunctionFactory.Create(). Opsi serializer harus diperoleh dari aplikasi yang dikonfigurasi JsonOptions melalui IOptions<Microsoft.AspNetCore.Http.Json.JsonOptions> untuk memastikan konsistensi dengan serialisasi JSON aplikasi lainnya.

Menjalankan Server

Atur variabel lingkungan dan jalankan:

export AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"
dotnet run --urls http://localhost:8888

Mengamati Panggilan Alat di Klien

Klien dasar dari tutorial Getting Started menampilkan respons teks akhir agen. Namun, Anda dapat mengoptimalkan fungsinya untuk mengamati panggilan alat dan hasilnya saat ditransmisikan dari server.

Menampilkan Detail Eksekusi Alat

Untuk melihat panggilan alat dan hasilnya secara real-time, perluas perulangan streaming klien untuk menangani FunctionCallContent dan FunctionResultContent:

// Inside the streaming loop from getting-started.md
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, session))
{
    ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate();

    // ... existing run started code ...

    // Display streaming content
    foreach (AIContent content in update.Contents)
    {
        switch (content)
        {
            case TextContent textContent:
                Console.ForegroundColor = ConsoleColor.Cyan;
                Console.Write(textContent.Text);
                Console.ResetColor();
                break;

            case FunctionCallContent functionCallContent:
                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine($"\n[Function Call - Name: {functionCallContent.Name}]");

                // Display individual parameters
                if (functionCallContent.Arguments != null)
                {
                    foreach (var kvp in functionCallContent.Arguments)
                    {
                        Console.WriteLine($"  Parameter: {kvp.Key} = {kvp.Value}");
                    }
                }
                Console.ResetColor();
                break;

            case FunctionResultContent functionResultContent:
                Console.ForegroundColor = ConsoleColor.Magenta;
                Console.WriteLine($"\n[Function Result - CallId: {functionResultContent.CallId}]");

                if (functionResultContent.Exception != null)
                {
                    Console.WriteLine($"  Exception: {functionResultContent.Exception}");
                }
                else
                {
                    Console.WriteLine($"  Result: {functionResultContent.Result}");
                }
                Console.ResetColor();
                break;

            case ErrorContent errorContent:
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"\n[Error: {errorContent.Message}]");
                Console.ResetColor();
                break;
        }
    }
}

Output yang Diharapkan dengan Panggilan Alat

Saat agen memanggil alat backend, Anda akan melihat:

User (:q or quit to exit): What's the weather like in Amsterdam?

[Run Started - Thread: thread_abc123, Run: run_xyz789]

[Function Call - Name: SearchRestaurants]
  Parameter: Location = Amsterdam
  Parameter: Cuisine = any

[Function Result - CallId: call_def456]
  Result: {"Location":"Amsterdam","Cuisine":"any","Results":[...]}

The weather in Amsterdam is sunny with a temperature of 22°C. Here are some 
great restaurants in the area: The Golden Fork (Italian, 4.5 stars)...
[Run Finished - Thread: thread_abc123]

Konsep utama

  • FunctionCallContent: Mewakili alat yang dipanggil dengan Name dan Arguments (pasangan nilai kunci parameter)
  • FunctionResultContent: Berisi alat Result atau Exception, yang diidentifikasi oleh CallId

Langkah Selanjutnya

Sekarang setelah Anda dapat menambahkan alat fungsi, Anda dapat:

Sumber Daya Tambahan

Tutorial ini menunjukkan kepada Anda cara menambahkan alat fungsi ke agen AG-UI Anda. Alat fungsi adalah fungsi Python kustom yang dapat dipanggil agen untuk melakukan tugas tertentu seperti mengambil data, melakukan penghitungan, atau berinteraksi dengan sistem eksternal. Dengan AG-UI, alat-alat ini dijalankan pada backend dan hasilnya secara otomatis dialirkan ke klien.

Prasyarat

Sebelum memulai, pastikan Anda telah menyelesaikan tutorial Memulai dan memiliki:

  • Python 3.10 atau yang lebih baru
  • agent-framework-ag-ui Diinstal
  • Layanan Azure OpenAI dikonfigurasi
  • Pemahaman dasar tentang server AG-UI dan penyiapan klien

Nota

Sampel ini digunakan DefaultAzureCredential untuk autentikasi. Pastikan Anda diautentikasi dengan Azure (misalnya, melalui az login). Untuk informasi selengkapnya, lihat dokumentasi Azure Identity.

Apa itu Backend Tool Rendering?

Penyajian alat backend berarti:

  • Alat fungsi ditentukan di server
  • Agen AI memutuskan kapan harus memanggil alat-alat ini
  • Alat dijalankan di backend (sisi server)
  • Peristiwa dan hasil panggilan alat dialirkan ke klien secara real time
  • Klien menerima pembaruan tentang kemajuan eksekusi alat

Pendekatan ini menyediakan:

  • Keamanan: Operasi sensitif tetap berada di server
  • Konsistensi: Semua klien menggunakan implementasi alat yang sama
  • Transparansi: Klien dapat menampilkan kemajuan eksekusi alat
  • Fleksibilitas: Memperbarui alat tanpa mengubah kode klien

Membuat Alat Fungsional

Alat Fungsi Dasar

Anda dapat mengubah fungsi Python apa pun menjadi alat menggunakan @tool dekorator:

from typing import Annotated
from pydantic import Field
from agent_framework import tool


@tool
def get_weather(
    location: Annotated[str, Field(description="The city")],
) -> str:
    """Get the current weather for a location."""
    # In a real application, you would call a weather API
    return f"The weather in {location} is sunny with a temperature of 22°C."

Konsep utama

  • @tool decorator: Menandai fungsi yang tersedia untuk agen
  • Anotasi tipe: Berikan informasi tipe untuk parameter
  • Annotated dan Field: Tambahkan deskripsi untuk membantu agen memahami parameter
  • Docstring: Menjelaskan apa yang dilakukan fungsi (membantu agen memutuskan kapan harus menggunakannya)
  • Nilai Kembali: Hasil yang dikembalikan ke agen serta dialirkan ke klien

Alat Serbaguna

Anda dapat menyediakan beberapa alat untuk memberi agen lebih banyak kemampuan:

from typing import Any
from agent_framework import tool


@tool
def get_weather(
    location: Annotated[str, Field(description="The city.")],
) -> str:
    """Get the current weather for a location."""
    return f"The weather in {location} is sunny with a temperature of 22°C."


@tool
def get_forecast(
    location: Annotated[str, Field(description="The city.")],
    days: Annotated[int, Field(description="Number of days to forecast")] = 3,
) -> dict[str, Any]:
    """Get the weather forecast for a location."""
    return {
        "location": location,
        "days": days,
        "forecast": [
            {"day": 1, "weather": "Sunny", "high": 24, "low": 18},
            {"day": 2, "weather": "Partly cloudy", "high": 22, "low": 17},
            {"day": 3, "weather": "Rainy", "high": 19, "low": 15},
        ],
    }

Membuat Server AG-UI dengan Alat Fungsi

Berikut adalah implementasi server lengkap dengan alat fungsi:

"""AG-UI server with backend tool rendering."""

import os
from typing import Annotated, Any

from agent_framework import Agent, tool
from agent_framework.azure import AzureOpenAIChatClient
from agent_framework_ag_ui import add_agent_framework_fastapi_endpoint
from azure.identity import AzureCliCredential
from fastapi import FastAPI
from pydantic import Field


# Define function tools
@tool
def get_weather(
    location: Annotated[str, Field(description="The city")],
) -> str:
    """Get the current weather for a location."""
    # Simulated weather data
    return f"The weather in {location} is sunny with a temperature of 22°C."


@tool
def search_restaurants(
    location: Annotated[str, Field(description="The city to search in")],
    cuisine: Annotated[str, Field(description="Type of cuisine")] = "any",
) -> dict[str, Any]:
    """Search for restaurants in a location."""
    # Simulated restaurant data
    return {
        "location": location,
        "cuisine": cuisine,
        "results": [
            {"name": "The Golden Fork", "rating": 4.5, "price": "$$"},
            {"name": "Bella Italia", "rating": 4.2, "price": "$$$"},
            {"name": "Spice Garden", "rating": 4.7, "price": "$$"},
        ],
    }


# Read required configuration
endpoint = os.environ.get("AZURE_OPENAI_ENDPOINT")
deployment_name = os.environ.get("AZURE_OPENAI_DEPLOYMENT_NAME")

if not endpoint:
    raise ValueError("AZURE_OPENAI_ENDPOINT environment variable is required")
if not deployment_name:
    raise ValueError("AZURE_OPENAI_DEPLOYMENT_NAME environment variable is required")

chat_client = AzureOpenAIChatClient(
    credential=AzureCliCredential(),
    endpoint=endpoint,
    deployment_name=deployment_name,    
)

# Create agent with tools
agent = Agent(
    name="TravelAssistant",
    instructions="You are a helpful travel assistant. Use the available tools to help users plan their trips.",
    chat_client=chat_client,
    tools=[get_weather, search_restaurants],
)

# Create FastAPI app
app = FastAPI(title="AG-UI Travel Assistant")
add_agent_framework_fastapi_endpoint(app, agent, "/")

if __name__ == "__main__":
    import uvicorn

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

Memahami Peristiwa Perangkat

Saat agen memanggil alat, klien menerima beberapa peristiwa:

Peristiwa Panggilan Perangkat

# 1. TOOL_CALL_START - Tool execution begins
{
    "type": "TOOL_CALL_START",
    "toolCallId": "call_abc123",
    "toolCallName": "get_weather"
}

# 2. TOOL_CALL_ARGS - Tool arguments (may stream in chunks)
{
    "type": "TOOL_CALL_ARGS",
    "toolCallId": "call_abc123",
    "delta": "{\"location\": \"Paris, France\"}"
}

# 3. TOOL_CALL_END - Arguments complete
{
    "type": "TOOL_CALL_END",
    "toolCallId": "call_abc123"
}

# 4. TOOL_CALL_RESULT - Tool execution result
{
    "type": "TOOL_CALL_RESULT",
    "toolCallId": "call_abc123",
    "content": "The weather in Paris, France is sunny with a temperature of 22°C."
}

Klien yang Disempurnakan untuk Acara Perangkat

Berikut adalah klien yang ditingkatkan menggunakan AGUIChatClient yang menampilkan eksekusi alat:

"""AG-UI client with tool event handling."""

import asyncio
import os

from agent_framework import Agent, ToolCallContent, ToolResultContent
from agent_framework_ag_ui import AGUIChatClient


async def main():
    """Main client loop with tool event display."""
    server_url = os.environ.get("AGUI_SERVER_URL", "http://127.0.0.1:8888/")
    print(f"Connecting to AG-UI server at: {server_url}\n")

    # Create AG-UI chat client
    chat_client = AGUIChatClient(server_url=server_url)

    # Create agent with the chat client
    agent = Agent(
        name="ClientAgent",
        chat_client=chat_client,
        instructions="You are a helpful assistant.",
    )

    # Get a thread for conversation continuity
    thread = agent.create_session()

    try:
        while True:
            message = input("\nUser (:q or quit to exit): ")
            if not message.strip():
                continue

            if message.lower() in (":q", "quit"):
                break

            print("\nAssistant: ", end="", flush=True)
            async for update in agent.run(message, session=thread, stream=True):
                # Display text content
                if update.text:
                    print(f"\033[96m{update.text}\033[0m", end="", flush=True)

                # Display tool calls and results
                for content in update.contents:
                    if isinstance(content, ToolCallContent):
                        print(f"\n\033[95m[Calling tool: {content.name}]\033[0m")
                    elif isinstance(content, ToolResultContent):
                        result_text = content.result if isinstance(content.result, str) else str(content.result)
                        print(f"\033[94m[Tool result: {result_text}]\033[0m")

            print("\n")

    except KeyboardInterrupt:
        print("\n\nExiting...")
    except Exception as e:
        print(f"\n\033[91mError: {e}\033[0m")


if __name__ == "__main__":
    asyncio.run(main())

Contoh Interaksi

Dengan server dan klien yang disempurnakan aktif:

User (:q or quit to exit): What's the weather like in Paris and suggest some Italian restaurants?

[Run Started]
[Tool Call: get_weather]
[Tool Result: The weather in Paris, France is sunny with a temperature of 22°C.]
[Tool Call: search_restaurants]
[Tool Result: {"location": "Paris", "cuisine": "Italian", "results": [...]}]
Based on the current weather in Paris (sunny, 22°C) and your interest in Italian cuisine,
I'd recommend visiting Bella Italia, which has a 4.2 rating. The weather is perfect for
outdoor dining!
[Run Finished]

Praktik Terbaik Implementasi Alat

Penanganan Kesalahan

Tangani kesalahan dengan lancar pada perangkat Anda:

@tool
def get_weather(
    location: Annotated[str, Field(description="The city.")],
) -> str:
    """Get the current weather for a location."""
    try:
        # Call weather API
        result = call_weather_api(location)
        return f"The weather in {location} is {result['condition']} with temperature {result['temp']}°C."
    except Exception as e:
        return f"Unable to retrieve weather for {location}. Error: {str(e)}"

Jenis Pengembalian Lengkap

Mengembalikan data terstruktur jika sesuai:

@tool
def analyze_sentiment(
    text: Annotated[str, Field(description="The text to analyze")],
) -> dict[str, Any]:
    """Analyze the sentiment of text."""
    # Perform sentiment analysis
    return {
        "text": text,
        "sentiment": "positive",
        "confidence": 0.87,
        "scores": {
            "positive": 0.87,
            "neutral": 0.10,
            "negative": 0.03,
        },
    }

Dokumentasi Deskriptif

Berikan deskripsi yang jelas untuk membantu agen memahami kapan harus menggunakan alat:

@tool
def book_flight(
    origin: Annotated[str, Field(description="Departure city and airport code, e.g., 'New York, JFK'")],
    destination: Annotated[str, Field(description="Arrival city and airport code, e.g., 'London, LHR'")],
    date: Annotated[str, Field(description="Departure date in YYYY-MM-DD format")],
    passengers: Annotated[int, Field(description="Number of passengers")] = 1,
) -> dict[str, Any]:
    """
    Book a flight for specified passengers from origin to destination.

    This tool should be used when the user wants to book or reserve airline tickets.
    Do not use this for searching flights - use search_flights instead.
    """
    # Implementation
    pass

Organisasi Alat dengan Kelas

Untuk alat terkait, atur di kelas:

from agent_framework import tool


class WeatherTools:
    """Collection of weather-related tools."""

    def __init__(self, api_key: str):
        self.api_key = api_key

    @tool
    def get_current_weather(
        self,
        location: Annotated[str, Field(description="The city.")],
    ) -> str:
        """Get current weather for a location."""
        # Use self.api_key to call API
        return f"Current weather in {location}: Sunny, 22°C"

    @tool
    def get_forecast(
        self,
        location: Annotated[str, Field(description="The city.")],
        days: Annotated[int, Field(description="Number of days")] = 3,
    ) -> dict[str, Any]:
        """Get weather forecast for a location."""
        # Use self.api_key to call API
        return {"location": location, "forecast": [...]}


# Create tools instance
weather_tools = WeatherTools(api_key="your-api-key")

# Create agent with class-based tools
agent = Agent(
    name="WeatherAgent",
    instructions="You are a weather assistant.",
    chat_client=AzureOpenAIChatClient(...),
    tools=[
        weather_tools.get_current_weather,
        weather_tools.get_forecast,
    ],
)

Langkah Selanjutnya

Sekarang setelah Anda memahami penyajian alat backend, Anda dapat:

Sumber Daya Tambahan