Állapotkezelés AG-UI

Ez az oktatóanyag bemutatja, hogyan implementálhatja az állapotkezelést az AG-UI-vel, lehetővé téve az állapot kétirányú szinkronizálását az ügyfél és a kiszolgáló között. Ez elengedhetetlen az olyan interaktív alkalmazások létrehozásához, mint a generatív felhasználói felület, a valós idejű irányítópultok vagy az együttműködésen alapuló élmények.

Előfeltételek

Mielőtt hozzákezdene, győződjön meg arról, hogy megérti:

Mi az állapotkezelés?

A AG-UI állapotkezelése a következőket teszi lehetővé:

  • Megosztott állapot: Az ügyfél és a kiszolgáló is szinkronizált nézetet tart fenn az alkalmazásállapotról
  • Kétirányú szinkronizálás: Az állapot ügyfélről vagy kiszolgálóról is frissíthető
  • Valós idejű frissítések: A módosítások azonnal streamelhetők állapotesemények használatával
  • Prediktív frissítések: Állapotfrissítések streamelése az LLM által eszközargumentumok létrehozásakor (optimista felhasználói felület)
  • Strukturált adatok: Az állapot egy JSON-sémát követ az ellenőrzéshez

Használati esetek

Az állapotkezelés értékes a következő célokra:

  • Generatív felhasználói felület: Felhasználói felület összetevőinek létrehozása ügynök által ellenőrzött állapot alapján
  • Űrlapkészítés: Az ügynök kitölti az űrlapmezőket, miközben adatokat gyűjt
  • Folyamatkövetés: A többlépéses műveletek valós idejű előrehaladásának megjelenítése
  • Interaktív irányítópultok: Az ügynök által feldolgozva frissülő adatok megjelenítése
  • Együttműködésen alapuló szerkesztés: Több felhasználó egységes állapotfrissítéseket lát

Állapotérzékeny ügynökök létrehozása C# nyelven

Az állapotmodell definiálása

Először határozza meg az állapotstruktúra osztályait:

using System.Text.Json.Serialization;

namespace RecipeAssistant;

// State response wrapper
internal sealed class RecipeResponse
{
    [JsonPropertyName("recipe")]
    public RecipeState Recipe { get; set; } = new();
}

// Recipe state model
internal sealed class RecipeState
{
    [JsonPropertyName("title")]
    public string Title { get; set; } = string.Empty;

    [JsonPropertyName("cuisine")]
    public string Cuisine { get; set; } = string.Empty;

    [JsonPropertyName("ingredients")]
    public List<string> Ingredients { get; set; } = [];

    [JsonPropertyName("steps")]
    public List<string> Steps { get; set; } = [];

    [JsonPropertyName("prep_time_minutes")]
    public int PrepTimeMinutes { get; set; }

    [JsonPropertyName("cook_time_minutes")]
    public int CookTimeMinutes { get; set; }

    [JsonPropertyName("skill_level")]
    public string SkillLevel { get; set; } = string.Empty;
}

// JSON serialization context
[JsonSerializable(typeof(RecipeResponse))]
[JsonSerializable(typeof(RecipeState))]
[JsonSerializable(typeof(System.Text.Json.JsonElement))]
internal sealed partial class RecipeSerializerContext : JsonSerializerContext;

State Management Middleware implementálása

Készítsen olyan köztes szoftvert, amely az ügyfél által küldött állapotot észleli, és összehangolja az ügynök válaszait a megfelelő állapotkezelés érdekében.

using System.Runtime.CompilerServices;
using System.Text.Json;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

internal sealed class SharedStateAgent : DelegatingAIAgent
{
    private readonly JsonSerializerOptions _jsonSerializerOptions;

    public SharedStateAgent(AIAgent innerAgent, JsonSerializerOptions jsonSerializerOptions)
        : base(innerAgent)
    {
        this._jsonSerializerOptions = jsonSerializerOptions;
    }

    protected override Task<AgentResponse> RunCoreAsync(
        IEnumerable<ChatMessage> messages,
        AgentSession? session = null,
        AgentRunOptions? options = null,
        CancellationToken cancellationToken = default)
    {
        return this.RunStreamingAsync(messages, session, options, cancellationToken)
            .ToAgentResponseAsync(cancellationToken);
    }

    protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
        IEnumerable<ChatMessage> messages,
        AgentSession? session = null,
        AgentRunOptions? options = null,
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        // Check if the client sent state in the request
        if (options is not ChatClientAgentRunOptions { ChatOptions.AdditionalProperties: { } properties } chatRunOptions ||
            !properties.TryGetValue("ag_ui_state", out object? stateObj) ||
            stateObj is not JsonElement state ||
            state.ValueKind != JsonValueKind.Object)
        {
            // No state management requested, pass through to inner agent
            await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, session, options, cancellationToken).ConfigureAwait(false))
            {
                yield return update;
            }
            yield break;
        }

        // Check if state has properties (not empty {})
        bool hasProperties = false;
        foreach (JsonProperty _ in state.EnumerateObject())
        {
            hasProperties = true;
            break;
        }

        if (!hasProperties)
        {
            // Empty state - treat as no state
            await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, session, options, cancellationToken).ConfigureAwait(false))
            {
                yield return update;
            }
            yield break;
        }

        // First run: Generate structured state update
        var firstRunOptions = new ChatClientAgentRunOptions
        {
            ChatOptions = chatRunOptions.ChatOptions.Clone(),
            AllowBackgroundResponses = chatRunOptions.AllowBackgroundResponses,
            ContinuationToken = chatRunOptions.ContinuationToken,
            ChatClientFactory = chatRunOptions.ChatClientFactory,
        };

        // Configure JSON schema response format for structured state output
        firstRunOptions.ChatOptions.ResponseFormat = ChatResponseFormat.ForJsonSchema<RecipeResponse>(
            schemaName: "RecipeResponse",
            schemaDescription: "A response containing a recipe with title, skill level, cooking time, preferences, ingredients, and instructions");

        // Add current state to the conversation - state is already a JsonElement
        ChatMessage stateUpdateMessage = new(
            ChatRole.System,
            [
                new TextContent("Here is the current state in JSON format:"),
                new TextContent(JsonSerializer.Serialize(state, this._jsonSerializerOptions.GetTypeInfo(typeof(JsonElement)))),
                new TextContent("The new state is:")
            ]);

        var firstRunMessages = messages.Append(stateUpdateMessage);

        // Collect all updates from first run
        var allUpdates = new List<AgentResponseUpdate>();
        await foreach (var update in this.InnerAgent.RunStreamingAsync(firstRunMessages, session, firstRunOptions, cancellationToken).ConfigureAwait(false))
        {
            allUpdates.Add(update);

            // Yield all non-text updates (tool calls, etc.)
            bool hasNonTextContent = update.Contents.Any(c => c is not TextContent);
            if (hasNonTextContent)
            {
                yield return update;
            }
        }

        var response = allUpdates.ToAgentResponse();

        // Try to deserialize the structured state response
        JsonElement stateSnapshot;
        try
        {
            stateSnapshot = JsonSerializer.Deserialize<JsonElement>(response.Text, this._jsonSerializerOptions);
        }
        catch (JsonException)
        {
            yield break;
        }

        // Serialize and emit as STATE_SNAPSHOT via DataContent
        byte[] stateBytes = JsonSerializer.SerializeToUtf8Bytes(
            stateSnapshot,
            this._jsonSerializerOptions.GetTypeInfo(typeof(JsonElement)));
        yield return new AgentResponseUpdate
        {
            Contents = [new DataContent(stateBytes, "application/json")]
        };

        // Second run: Generate user-friendly summary
        var secondRunMessages = messages.Concat(response.Messages).Append(
            new ChatMessage(
                ChatRole.System,
                [new TextContent("Please provide a concise summary of the state changes in at most two sentences.")]));

        await foreach (var update in this.InnerAgent.RunStreamingAsync(secondRunMessages, session, options, cancellationToken).ConfigureAwait(false))
        {
            yield return update;
        }
    }
}

Az ügynök konfigurálása állapotkezeléssel

using Microsoft.Agents.AI;
using Azure.AI.Projects;
using Azure.Identity;

AIAgent CreateRecipeAgent(JsonSerializerOptions jsonSerializerOptions)
{
    string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
        ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
    string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME")
        ?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set.");

    // Create base agent
    AIAgent baseAgent = new AIProjectClient(
        new Uri(endpoint),
        new DefaultAzureCredential())
        .AsAIAgent(
            model: deploymentName,
            name: "RecipeAgent",
        instructions: """
            You are a helpful recipe assistant. When users ask you to create or suggest a recipe,
            respond with a complete RecipeResponse JSON object that includes:
            - recipe.title: The recipe name
            - recipe.cuisine: Type of cuisine (e.g., Italian, Mexican, Japanese)
            - recipe.ingredients: Array of ingredient strings with quantities
            - recipe.steps: Array of cooking instruction strings
            - recipe.prep_time_minutes: Preparation time in minutes
            - recipe.cook_time_minutes: Cooking time in minutes
            - recipe.skill_level: One of "beginner", "intermediate", or "advanced"

            Always include all fields in the response. Be creative and helpful.
            """);

    // Wrap with state management middleware
    return new SharedStateAgent(baseAgent, jsonSerializerOptions);
}

Figyelmeztetés

DefaultAzureCredential a fejlesztéshez kényelmes, de a termelési környezetben gondos megfontolást igényel. Éles környezetben fontolja meg egy adott hitelesítő adat (pl. ManagedIdentityCredential) használatát a késési problémák elkerülése, a hitelesítő adatok nem szándékos próbálgatásának és a tartalék mechanizmusokból eredő esetleges biztonsági kockázatok elkerülése érdekében.

Az ügynökvégpont térképezése

using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient().AddLogging();
builder.Services.ConfigureHttpJsonOptions(options =>
    options.SerializerOptions.TypeInfoResolverChain.Add(RecipeSerializerContext.Default));
builder.Services.AddAGUI();

WebApplication app = builder.Build();

var jsonOptions = app.Services.GetRequiredService<IOptions<Microsoft.AspNetCore.Http.Json.JsonOptions>>().Value;
AIAgent recipeAgent = CreateRecipeAgent(jsonOptions.SerializerOptions);
app.MapAGUI("/", recipeAgent);

await app.RunAsync();

Alapfogalmak

  • Állapotészlelés: A köztes szoftver ellenőrzi a ag_ui_stateChatOptions.AdditionalProperties-ben, hogy mikor kéri az ügyfél az állapotkezelést
  • Kétfázisú Válasz: Először struktúrált állapotot (JSON séma) hoz létre, majd egy felhasználóbarát összefoglalót készít
  • Strukturált állapotmodellek: Az állapotstruktúra C#-osztályainak definiálása JSON-tulajdonságnevekkel
  • JSON-séma válaszformátuma: Strukturált ChatResponseFormat.ForJsonSchema<T>() kimenet biztosítása
  • STATE_SNAPSHOT Események: Kibocsátva DataContentapplication/json médiatípusként, amelyet az AG-UI-keretrendszer automatikusan átalakít STATE_SNAPSHOT eseményekké
  • Állapotkörnyezet: A rendszer rendszerüzenetként injektálja az aktuális állapotot, hogy kontextust biztosítson az ügynöknek

Hogyan működik?

  1. Az ügyfél a következő állapotú kérést küldi: ChatOptions.AdditionalProperties["ag_ui_state"]
  2. A Middleware észleli az állapotot, és első futtatáskor JSON-séma válaszformátummal hajt végre
  3. A Köztes szoftver az aktuális állapotot adja hozzá környezetként egy rendszerüzenetben
  4. Az ügynök az állapotmodellnek megfelelő strukturált állapotfrissítést hoz létre
  5. A köztes szoftver szerializálja az állapotot, és a következőképpen bocsát ki DataContent (STATE_SNAPSHOT esemény lesz)
  6. A Middleware második futtatáskor felhasználóbarát összegzést hoz létre
  7. Az ügyfél megkapja az állapot pillanatképét és a természetes nyelvi összefoglalást is

Jótanács

A kétfázisú megközelítés elválasztja az állapotkezelést a felhasználói kommunikációtól. Az első fázis strukturált, megbízható állapotfrissítéseket biztosít, míg a második fázis természetes nyelvi visszajelzést ad a felhasználónak.

Ügyfél-implementáció (C#)

Fontos

Az oktatóanyag nem tartalmazza a C#-ügyfél implementálását. A kiszolgálóoldali állapotkezelés befejeződött, de az ügyfeleknek a következőkre van szükségük:

  1. Állapot inicializálása üres objektummal (nem null): RecipeState? currentState = new RecipeState();
  2. Az DataContent állapot elküldése ChatRole.System üzenetben
  3. Állapot-pillanatképek fogadása a következőképpen:DataContentmediaType = "application/json"

Az AG-UI üzemeltetési réteg automatikusan kinyeri az állapotot DataContent, és elhelyezi azt ChatOptions.AdditionalProperties["ag_ui_state"], mint JsonElement.

A teljes ügyfél-implementálási példáért tekintse meg az alábbi Python-ügyfélmintát, amely a teljes kétirányú állapotfolyamatot mutatja be.

Állapotmodellek definiálása

Először is definiáljon Pydantic-modelleket az állapotstruktúrához. Ez biztosítja a típus biztonságát és érvényesítését:

from enum import Enum
from pydantic import BaseModel, Field


class SkillLevel(str, Enum):
    """The skill level required for the recipe."""
    BEGINNER = "Beginner"
    INTERMEDIATE = "Intermediate"
    ADVANCED = "Advanced"


class CookingTime(str, Enum):
    """The cooking time of the recipe."""
    FIVE_MIN = "5 min"
    FIFTEEN_MIN = "15 min"
    THIRTY_MIN = "30 min"
    FORTY_FIVE_MIN = "45 min"
    SIXTY_PLUS_MIN = "60+ min"


class Ingredient(BaseModel):
    """An ingredient with its details."""
    icon: str = Field(..., description="Emoji icon representing the ingredient (e.g., 🥕)")
    name: str = Field(..., description="Name of the ingredient")
    amount: str = Field(..., description="Amount or quantity of the ingredient")


class Recipe(BaseModel):
    """A complete recipe."""
    title: str = Field(..., description="The title of the recipe")
    skill_level: SkillLevel = Field(..., description="The skill level required")
    special_preferences: list[str] = Field(
        default_factory=list, description="Dietary preferences (e.g., Vegetarian, Gluten-free)"
    )
    cooking_time: CookingTime = Field(..., description="The estimated cooking time")
    ingredients: list[Ingredient] = Field(..., description="Complete list of ingredients")
    instructions: list[str] = Field(..., description="Step-by-step cooking instructions")

Állapotséma

Adjon meg egy állapotsémát az állapot szerkezetének és típusainak megadásához:

state_schema = {
    "recipe": {"type": "object", "description": "The current recipe"},
}

Megjegyzés:

Az állapotséma egy egyszerű formátumot használ, amely type és nem kötelező description elemeket tartalmaz. A tényleges struktúrát a Pydantic-modellek határozzák meg.

Prediktív állapotfrissítések

A prediktív állapotfrissítések streamelik az eszköz argumentumait az állapotba az LLM generálása során, így optimista felhasználói felületi frissítéseket tesz lehetővé:

predict_state_config = {
    "recipe": {"tool": "update_recipe", "tool_argument": "recipe"},
}

Ez a konfiguráció megfelelteti az recipe állapotmezőt az recipeupdate_recipe eszköz argumentumának. Amikor az ügynök meghívja az eszközt, az argumentumok valós időben streamelnek az állapotba, miközben az LLM azokat generálja.

Állapotfrissítési eszköz definiálása

Hozzon létre egy olyan eszközfüggvényt, amely elfogadja a Pydantic modellet:

from agent_framework import tool


@tool
def update_recipe(recipe: Recipe) -> str:
    """Update the recipe with new or modified content.

    You MUST write the complete recipe with ALL fields, even when changing only a few items.
    When modifying an existing recipe, include ALL existing ingredients and instructions plus your changes.
    NEVER delete existing data - only add or modify.

    Args:
        recipe: The complete recipe object with all details

    Returns:
        Confirmation that the recipe was updated
    """
    return "Recipe updated."

Fontos

Az eszközfüggvény paraméternevének (recipe) meg kell egyeznie a tool_argument-vel a saját predict_state_config-jában.

Az ügynök létrehozása állapotkezeléssel

Íme egy teljes kiszolgáló-implementáció az állapotkezeléssel:

"""AG-UI server with state management."""

from agent_framework import Agent
from agent_framework.openai import OpenAIChatCompletionClient
from agent_framework_ag_ui import (
    AgentFrameworkAgent,
    RecipeConfirmationStrategy,
    add_agent_framework_fastapi_endpoint,
)
from azure.identity import AzureCliCredential
from fastapi import FastAPI

# Create the chat agent with tools
agent = Agent(
    name="recipe_agent",
    instructions="""You are a helpful recipe assistant that creates and modifies recipes.

    CRITICAL RULES:
    1. You will receive the current recipe state in the system context
    2. To update the recipe, you MUST use the update_recipe tool
    3. When modifying a recipe, ALWAYS include ALL existing data plus your changes in the tool call
    4. NEVER delete existing ingredients or instructions - only add or modify
    5. After calling the tool, provide a brief conversational message (1-2 sentences)

    When creating a NEW recipe:
    - Provide all required fields: title, skill_level, cooking_time, ingredients, instructions
    - Use actual emojis for ingredient icons (🥕 🧄 🧅 🍅 🌿 🍗 🥩 🧀)
    - Leave special_preferences empty unless specified
    - Message: "Here's your recipe!" or similar

    When MODIFYING or IMPROVING an existing recipe:
    - Include ALL existing ingredients + any new ones
    - Include ALL existing instructions + any new/modified ones
    - Update other fields as needed
    - Message: Explain what you improved (e.g., "I upgraded the ingredients to premium quality")
    - When asked to "improve", enhance with:
      * Better ingredients (upgrade quality, add complementary flavors)
      * More detailed instructions
      * Professional techniques
      * Adjust skill_level if complexity changes
      * Add relevant special_preferences

    Example improvements:
    - Upgrade "chicken" → "organic free-range chicken breast"
    - Add herbs: basil, oregano, thyme
    - Add aromatics: garlic, shallots
    - Add finishing touches: lemon zest, fresh parsley
    - Make instructions more detailed and professional
    """,
    client=OpenAIChatCompletionClient(
        model=deployment_name,
        azure_endpoint=endpoint,
        api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
        credential=AzureCliCredential(),
    ),
    tools=[update_recipe],
)

# Wrap agent with state management
recipe_agent = AgentFrameworkAgent(
    agent=agent,
    name="RecipeAgent",
    description="Creates and modifies recipes with streaming state updates",
    state_schema={
        "recipe": {"type": "object", "description": "The current recipe"},
    },
    predict_state_config={
        "recipe": {"tool": "update_recipe", "tool_argument": "recipe"},
    },
    confirmation_strategy=RecipeConfirmationStrategy(),
)

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

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=8888)

Alapfogalmak

  • Pydantic Models: Strukturált állapot definiálása típusbiztonsággal és ellenőrzéssel
  • Állapotséma: Állapotmezőtípusok egyszerű formátuma
  • Prediktív állapotkonfiguráció: Állapotmezők leképezése a streamelési frissítések eszközargumentumaihoz
  • Állapotinjektálás: A rendszer automatikusan injektálja az aktuális állapotot rendszerüzenetként a környezet biztosítása érdekében
  • Teljes frissítések: Az eszközöknek meg kell írniuk a teljes állapotot, nem csak az eltéréseket.
  • Megerősítési stratégia: Az Ön területéhez tartozó jóváhagyási üzenetek testreszabása (recept, dokumentum, feladattervezés stb.)

Az állapotesemények ismertetése

Esemény állapot-pillanatképe

Az eszköz befejezésekor kibocsátott aktuális állapot teljes pillanatképe:

{
    "type": "STATE_SNAPSHOT",
    "snapshot": {
        "recipe": {
            "title": "Classic Pasta Carbonara",
            "skill_level": "Intermediate",
            "special_preferences": ["Authentic Italian"],
            "cooking_time": "30 min",
            "ingredients": [
                {"icon": "🍝", "name": "Spaghetti", "amount": "400g"},
                {"icon": "🥓", "name": "Guanciale or bacon", "amount": "200g"},
                {"icon": "🥚", "name": "Egg yolks", "amount": "4"},
                {"icon": "🧀", "name": "Pecorino Romano", "amount": "100g grated"},
                {"icon": "🧂", "name": "Black pepper", "amount": "To taste"}
            ],
            "instructions": [
                "Bring a large pot of salted water to boil",
                "Cut guanciale into small strips and fry until crispy",
                "Beat egg yolks with grated Pecorino and black pepper",
                "Cook spaghetti until al dente",
                "Reserve 1 cup pasta water, then drain pasta",
                "Remove pan from heat, add hot pasta to guanciale",
                "Quickly stir in egg mixture, adding pasta water to create creamy sauce",
                "Serve immediately with extra Pecorino and black pepper"
            ]
        }
    }
}

State Delta Event

Növekményes állapotfrissítések a JSON Patch formátummal, az LLM streameken keresztül eszközargumentumokként kibocsátva.

{
    "type": "STATE_DELTA",
    "delta": [
        {
            "op": "replace",
            "path": "/recipe",
            "value": {
                "title": "Classic Pasta Carbonara",
                "skill_level": "Intermediate",
                "cooking_time": "30 min",
                "ingredients": [
                    {"icon": "🍝", "name": "Spaghetti", "amount": "400g"}
                ],
                "instructions": ["Bring a large pot of salted water to boil"]
            }
        }
    ]
}

Megjegyzés:

Az állapotváltozási események valós időben áramlanak, miközben az LLM létrehozza az eszközargumentumokat, optimista felhasználói felületi frissítéseket kínálva. A végső állapot pillanatképe akkor lesz kibocsátva, amikor az eszköz befejezi a végrehajtást.

Ügyfél-implementáció

A agent_framework_ag_ui csomag lehetővé teszi AGUIChatClient AG-UI kiszolgálókhoz való csatlakozást, így a Python ügyfélélménye paritásos lesz a .NET-tel:

"""AG-UI client with state management."""

import asyncio
import json
import os
from typing import Any

import jsonpatch
from agent_framework import Agent, Message, Role
from agent_framework_ag_ui import AGUIChatClient


async def main():
    """Example client with state tracking."""
    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)

    # Wrap with Agent for convenient API
    agent = Agent(
        name="ClientAgent",
        client=chat_client,
        instructions="You are a helpful assistant.",
    )

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

    # Track state locally
    state: dict[str, Any] = {}

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

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

            if message.lower() == ":state":
                print(f"\nCurrent state: {json.dumps(state, indent=2)}")
                continue

            print()
            # Stream the agent response with state
            async for update in agent.run(message, session=thread, stream=True):
                # Handle text content
                if update.text:
                    print(update.text, end="", flush=True)

                # Handle state updates
                for content in update.contents:
                    # STATE_SNAPSHOT events come as DataContent with application/json
                    if hasattr(content, 'media_type') and content.media_type == 'application/json':
                        # Parse state snapshot
                        state_data = json.loads(content.data.decode() if isinstance(content.data, bytes) else content.data)
                        state = state_data
                        print("\n[State Snapshot Received]")

                    # STATE_DELTA events are handled similarly
                    # Apply JSON Patch deltas to maintain state
                    if hasattr(content, 'delta') and content.delta:
                        patch = jsonpatch.JsonPatch(content.delta)
                        state = patch.apply(state)
                        print("\n[State Delta Applied]")

            print(f"\n\nCurrent state: {json.dumps(state, indent=2)}")
            print()

    except KeyboardInterrupt:
        print("\n\nExiting...")


if __name__ == "__main__":
    # Install dependencies: pip install agent-framework-ag-ui jsonpatch --pre
    asyncio.run(main())

Főbb előnyök

A AGUIChatClient következő lehetőségeket nyújtja:

  • Egyszerűsített kapcsolat: HTTP/SSE-kommunikáció automatikus kezelése
  • Szálkezelés: Beépített szálazonosító-nyomon követés a beszélgetés folytonosságához
  • Ügynökintegráció: Zökkenőmentesen működik a jól ismert API-val Agent
  • Állapotkezelés: Az állapotesemények automatikus elemzése a kiszolgálóról
  • Paritás a .NET-tel: Egységes felhasználói élmény a nyelvek között

Jótanács

Az ügynök-keretrendszer funkcióinak maximális kihasználása érdekében használja a AGUIChatClient és Agent elemeket, mint például a beszélgetési előzmények, az eszközök futtatása és a middleware támogatás.

Megerősítési stratégiák használata

A confirmation_strategy paraméterrel testre szabhatja a tartomány jóváhagyási üzeneteit:

from agent_framework_ag_ui import RecipeConfirmationStrategy

recipe_agent = AgentFrameworkAgent(
    agent=agent,
    state_schema={"recipe": {"type": "object", "description": "The current recipe"}},
    predict_state_config={"recipe": {"tool": "update_recipe", "tool_argument": "recipe"}},
    confirmation_strategy=RecipeConfirmationStrategy(),
)

Elérhető stratégiák:

  • DefaultConfirmationStrategy() – Általános üzenetek bármely ügynökhöz
  • RecipeConfirmationStrategy() - Receptspecifikus üzenetek
  • DocumentWriterConfirmationStrategy() – Dokumentumszerkesztési üzenetek
  • TaskPlannerConfirmationStrategy() - Tevékenységtervezési üzenetek

Egyéni stratégiákat is létrehozhat a szükséges metódusok ConfirmationStrategy öröklésével és implementálásával.

Példa interakcióra

A kiszolgáló és az ügyfél futása közben:

User (:q to quit, :state to show state): I want to make a classic Italian pasta carbonara

[Run Started]
[Calling Tool: update_recipe]
[State Updated]
[State Updated]
[State Updated]
[Tool Result: Recipe updated.]
Here's your recipe!
[Run Finished]

============================================================
CURRENT STATE
============================================================

recipe:
  title: Classic Pasta Carbonara
  skill_level: Intermediate
  special_preferences: ['Authentic Italian']
  cooking_time: 30 min
  ingredients:
    - 🍝 Spaghetti: 400g
    - 🥓 Guanciale or bacon: 200g
    - 🥚 Egg yolks: 4
    - 🧀 Pecorino Romano: 100g grated
    - 🧂 Black pepper: To taste
  instructions:
    1. Bring a large pot of salted water to boil
    2. Cut guanciale into small strips and fry until crispy
    3. Beat egg yolks with grated Pecorino and black pepper
    4. Cook spaghetti until al dente
    5. Reserve 1 cup pasta water, then drain pasta
    6. Remove pan from heat, add hot pasta to guanciale
    7. Quickly stir in egg mixture, adding pasta water to create creamy sauce
    8. Serve immediately with extra Pecorino and black pepper

============================================================

Jótanács

:state A parancs használatával bármikor megtekintheti az aktuális állapotot a beszélgetés során.

Prediktív állapotfrissítések működés közben

Prediktív állapotfrissítések predict_state_config használatakor az ügyfél STATE_DELTA eseményeket fogad, amikor az LLM valós időben generálja az eszközargumentumokat, mielőtt az eszköz végrehajtja őket.

// Agent starts generating tool call for update_recipe
// Client receives STATE_DELTA events as the recipe argument streams:

// First delta - partial recipe with title
{
  "type": "STATE_DELTA",
  "delta": [{"op": "replace", "path": "/recipe", "value": {"title": "Classic Pasta"}}]
}

// Second delta - title complete with more fields
{
  "type": "STATE_DELTA",
  "delta": [{"op": "replace", "path": "/recipe", "value": {
    "title": "Classic Pasta Carbonara",
    "skill_level": "Intermediate"
  }}]
}

// Third delta - ingredients starting to appear
{
  "type": "STATE_DELTA",
  "delta": [{"op": "replace", "path": "/recipe", "value": {
    "title": "Classic Pasta Carbonara",
    "skill_level": "Intermediate",
    "cooking_time": "30 min",
    "ingredients": [
      {"icon": "🍝", "name": "Spaghetti", "amount": "400g"}
    ]
  }}]
}

// ... more deltas as the LLM generates the complete recipe

Ez lehetővé teszi, hogy az ügyfél valós időben jelenítsen meg optimista felhasználói felületi frissítéseket, ahogy az ügynök gondolkodik, és azonnali visszajelzést ad a felhasználóknak.

Emberi beavatkozással vezérelt állapot

Az állapotkezelést a jóváhagyási munkafolyamatokkal kombinálhatja a következő beállítással require_confirmation=True:

recipe_agent = AgentFrameworkAgent(
    agent=agent,
    state_schema={"recipe": {"type": "object", "description": "The current recipe"}},
    predict_state_config={"recipe": {"tool": "update_recipe", "tool_argument": "recipe"}},
    require_confirmation=True,  # Require approval for state changes
    confirmation_strategy=RecipeConfirmationStrategy(),
)

Ha engedélyezve van:

  1. Állapotfrissítések streamelése, ahogy az ügynök eszközargumentumokat generál (prediktív frissítések STATE_DELTA eseményeken keresztül)
  2. Az ügynök jóváhagyást kér az eszköz végrehajtása előtt (eseményen keresztül FUNCTION_APPROVAL_REQUEST )
  3. Jóváhagyás esetén az eszköz végrehajtja a elemet, és a végleges állapot ki lesz bocsátva (eseményen keresztül STATE_SNAPSHOT )
  4. Ha elutasítják, a prediktív állapot módosításai el lesznek vetve

Speciális állapotminták

Összetett állapot több mezővel

Több állapotmezőt is kezelhet különböző eszközökkel:

from pydantic import BaseModel


class TaskStep(BaseModel):
    """A single task step."""
    description: str
    status: str = "pending"
    estimated_duration: str = "5 min"


@tool
def generate_task_steps(steps: list[TaskStep]) -> str:
    """Generate task steps for a given task."""
    return f"Generated {len(steps)} steps."


@tool
def update_preferences(preferences: dict[str, Any]) -> str:
    """Update user preferences."""
    return "Preferences updated."


# Configure with multiple state fields
agent_with_multiple_state = AgentFrameworkAgent(
    agent=agent,
    state_schema={
        "steps": {"type": "array", "description": "List of task steps"},
        "preferences": {"type": "object", "description": "User preferences"},
    },
    predict_state_config={
        "steps": {"tool": "generate_task_steps", "tool_argument": "steps"},
        "preferences": {"tool": "update_preferences", "tool_argument": "preferences"},
    },
)

Helyettesítő karakteres eszköz argumentumainak használata

Amikor egy eszköz összetett beágyazott adatokat ad vissza, használja az "*"-t az összes eszközargumentum állapotba való leképezéséhez.

@tool
def create_document(title: str, content: str, metadata: dict[str, Any]) -> str:
    """Create a document with title, content, and metadata."""
    return "Document created."


# Map all tool arguments to document state
predict_state_config = {
    "document": {"tool": "create_document", "tool_argument": "*"}
}

Ez leképozza a teljes eszközhívást (az összes argumentumot) az document állapotmezőre.

Ajánlott eljárások

Pydantic-modellek használata

Strukturált modellek definiálása típusbiztonsághoz:

class Recipe(BaseModel):
    """Use Pydantic models for structured, validated state."""
    title: str
    skill_level: SkillLevel
    ingredients: list[Ingredient]
    instructions: list[str]

Előnyök:

  • Típusbiztonság: Adattípusok automatikus ellenőrzése
  • Dokumentáció: A mezőleírások dokumentációként szolgálnak
  • IDE-támogatás: Automatikus kiegészítés és típusellenőrzés
  • Szerializálás: Automatikus JSON-átalakítás

Állapotfrissítések befejezése

Mindig írja meg a teljes állapotot, nem csak a delta értékeket.

@tool
def update_recipe(recipe: Recipe) -> str:
    """
    You MUST write the complete recipe with ALL fields.
    When modifying a recipe, include ALL existing ingredients and
    instructions plus your changes. NEVER delete existing data.
    """
    return "Recipe updated."

Ez biztosítja az állapotkonzisztenciát és a megfelelő prediktív frissítéseket.

Paraméternevek egyeztetése

Ellenőrizze, hogy az eszközparaméterek neve megegyezik-e a konfigurációval tool_argument :

# Tool parameter name
def update_recipe(recipe: Recipe) -> str:  # Parameter name: 'recipe'
    ...

# Must match in predict_state_config
predict_state_config = {
    "recipe": {"tool": "update_recipe", "tool_argument": "recipe"}  # Same name
}

Környezet megadása az utasításokban

Adjon meg egyértelmű utasításokat az állapotkezeléssel kapcsolatban:

agent = Agent(
    instructions="""
    CRITICAL RULES:
    1. You will receive the current recipe state in the system context
    2. To update the recipe, you MUST use the update_recipe tool
    3. When modifying a recipe, ALWAYS include ALL existing data plus your changes
    4. NEVER delete existing ingredients or instructions - only add or modify
    """,
    ...
)

Megerősítő stratégiák használata

A tartomány jóváhagyási üzeneteinek testreszabása:

from agent_framework_ag_ui import RecipeConfirmationStrategy

recipe_agent = AgentFrameworkAgent(
    agent=agent,
    confirmation_strategy=RecipeConfirmationStrategy(),  # Domain-specific messages
)

Következő lépések

Most már megtanulta az összes alapvető AG-UI funkciót! Ezután a következőkre van lehetőség:

  • Az Agent Framework dokumentációjának megismerése
  • Az összes AG-UI funkciót kombináló teljes alkalmazás létrehozása
  • A AG-UI szolgáltatás üzembe helyezése éles környezetben

További források