Megosztás:


Backend eszköz renderelése AG-UI-vel

Ez az oktatóanyag bemutatja, hogyan adhat hozzá függvényeszközöket a AG-UI-ügynökökhöz. A függvényeszközök egyéni C# metódusok, amelyeket az ügynök meghívhat bizonyos feladatok végrehajtására, például adatok beolvasására, számítások elvégzésére vagy külső rendszerekkel való interakcióra. Az AG-UI használatával ezek az eszközök a háttérrendszeren futnak, és az eredmények automatikusan streamelhetők az ügyfélnek.

Előfeltételek

Mielőtt hozzákezdene, győződjön meg arról, hogy elvégezte az Első lépések oktatóanyagot, és az alábbiakat:

  • .NET 8.0 vagy újabb
  • Microsoft.Agents.AI.Hosting.AGUI.AspNetCore telepített csomag
  • Azure OpenAI-szolgáltatás konfigurálva
  • A AG-UI kiszolgáló- és ügyfélbeállítás alapszintű ismerete

Mi az a háttéreszköz-renderelés?

A háttéreszköz renderelése a következőt jelenti:

  • A függvényeszközök a kiszolgálón vannak definiálva
  • Az AI-ügynök dönti el, hogy mikor hívja meg ezeket az eszközöket
  • A háttérrendszeren futó eszközök (kiszolgálóoldali)
  • Az eszközhívási események és eredmények valós időben streamelhetők az ügyfélhez
  • Az ügyfél frissítéseket kap az eszköz végrehajtási folyamatáról

AG-UI-kiszolgáló létrehozása függvényeszközökkel

Az alábbiakban egy teljes kiszolgálói implementációt mutatunk be, amely bemutatja, hogyan regisztrálhatók az eszközök összetett paramétertípusokkal:

// 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();

Alapfogalmak

  • Kiszolgálóoldali végrehajtás: A kiszolgálói folyamatban végrehajtott eszközök
  • Automatikus streamelés: Az eszközhívások és az eredmények valós időben streamelhetők az ügyfeleknek

Fontos

Amikor összetett paramétertípusokkal (objektumokkal, tömbökkel stb.) rendelkező eszközöket hoz létre, meg kell adnia a serializerOptions paramétert AIFunctionFactory.Create() paraméterhez. A szerializáló opciókat az alkalmazás konfigurált JsonOptions-ból IOptions<Microsoft.AspNetCore.Http.Json.JsonOptions> kell beszerezni, hogy biztosítsuk a JSON-szerializálás konzisztenciáját az alkalmazáson belül.

A kiszolgáló futtatása

Környezeti változók beállítása és futtatása:

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

Eszközhívások megfigyelése a kliensben

Az Első lépések oktatóanyag alapszintű kliensprogramja megjeleníti az ügynök végleges szöveges válaszát. Kiterjesztheti azonban, hogy megfigyelje az eszközhívásokat és az eredményeket, miközben azokat a kiszolgálóról streameli.

Eszközvégrehajtás részleteinek megjelenítése

Az eszközhívások és az eredmények valós idejű megtekintéséhez bővítse ki az ügyfél streamelési ciklusát a FunctionCallContent és FunctionResultContent kezelésére.

// 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;
        }
    }
}

Várt kimenet eszközhívásokkal

Amikor az ügynök meghívja a háttéreszközöket, a következőt fogja látni:

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]

Alapfogalmak

  • FunctionCallContent: Egy meghívandó eszközt jelöl annak Name és Arguments (paraméterkulcs-érték párokkal)
  • FunctionResultContent: Az eszköz Result vagy Exception, amelyet CallId azonosít

Következő lépések

Miután hozzáadhatja a függvényeszközöket, a következőt teheti:

További források

Ez az oktatóanyag bemutatja, hogyan adhat hozzá függvényeszközöket a AG-UI-ügynökökhöz. A függvényeszközök egyéni Python-függvények, amelyeket az ügynök meghívhat bizonyos feladatok végrehajtására, például adatok beolvasására, számítások elvégzésére vagy külső rendszerekkel való interakcióra. Az AG-UI használatával ezek az eszközök a háttérrendszeren futnak, és az eredmények automatikusan streamelhetők az ügyfélnek.

Előfeltételek

Mielőtt hozzákezdene, győződjön meg arról, hogy elvégezte az Első lépések oktatóanyagot, és az alábbiakat:

  • Python 3.10 vagy újabb
  • agent-framework-ag-ui Telepített
  • Azure OpenAI-szolgáltatás konfigurálva
  • A AG-UI kiszolgáló- és ügyfélbeállítás alapszintű ismerete

Megjegyzés:

Ezek a minták hitelesítéshez használatosak DefaultAzureCredential . Győződjön meg arról, hogy hitelesítve van az Azure-ral (pl. keresztül az login). További információkért tekintse meg az Azure Identity dokumentációját.

Mi az a háttéreszköz-renderelés?

A háttéreszköz renderelése a következőt jelenti:

  • A függvényeszközök a kiszolgálón vannak definiálva
  • Az AI-ügynök dönti el, hogy mikor hívja meg ezeket az eszközöket
  • A háttérrendszeren futó eszközök (kiszolgálóoldali)
  • Az eszközhívási események és eredmények valós időben streamelhetők az ügyfélhez
  • Az ügyfél frissítéseket kap az eszköz végrehajtási folyamatáról

Ez a megközelítés a következő lehetőségeket nyújtja:

  • Biztonság: A bizalmas műveletek a kiszolgálón maradnak
  • Konzisztencia: Minden ügyfél ugyanazokat az eszköz-implementációkat használja
  • Átlátszóság: Az ügyfelek megjeleníthetik az eszközök végrehajtási állapotát
  • Rugalmasság: Eszközök frissítése az ügyfélkód módosítása nélkül

Függvényeszközök létrehozása

Alapszintű funkcióeszköz

Bármely Python-függvényt eszközré alakíthat a @tool dekoratőrrel:

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."

Alapfogalmak

  • @tool dekorátor: Az ügynök számára elérhetőként jelöli meg a függvényt
  • Típusjegyzetek: Adja meg a paraméterek típusadatait
  • Annotated és Field: Leírások hozzáadása az ügynöknek a paraméterek megértéséhez
  • Docstring: A függvény működésének leírása (segít az ügynöknek eldönteni, hogy mikor használja)
  • Visszatérési érték: Az ügynöknek visszaadott eredmény (és streamelve az ügyfélnek)

Több függvényeszköz

Több eszközt is biztosíthat az ügynök további képességeinek biztosításához:

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},
        ],
    }

AG-UI-kiszolgáló létrehozása függvényeszközökkel

Az alábbiakban egy teljes kiszolgálói implementációt talál függvényeszközökkel:

"""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)

Eszközesemények ismertetése

Amikor az ügynök meghív egy eszközt, az ügyfél több eseményt is kap:

Eszközhívási események

# 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."
}

Továbbfejlesztett kliens a szerszámeseményekhez

Íme egy továbbfejlesztett kliens AGUIChatClient, amely eszközvégrehajtást jelenít meg.

"""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())

Példa interakcióra

A továbbfejlesztett kiszolgáló és ügyfél futtatásával:

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]

Az eszköz implementálásának ajánlott eljárásai

Hibakezelés

Az eszközeid hibáinak elegáns kezelése:

@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)}"

Gazdag visszatérési típusok

Strukturált adatokat ad vissza, ha szükséges:

@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,
        },
    }

Leíró dokumentáció

Adjon meg egyértelmű leírásokat, amelyek segítenek az ügynöknek megérteni, hogy mikor érdemes eszközöket használnia:

@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

Osztályokkal történő eszközszervezés

A kapcsolódó eszközök esetében rendezze őket egy osztályba:

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,
    ],
)

Következő lépések

Most, hogy megismerte a háttéreszközök renderelését, a következőt teheti:

További források