AG-UI ile Durum Yönetimi

Bu öğreticide AG-UI ile durum yönetiminin nasıl uygulanıp istemci ile sunucu arasında çift yönlü durum eşitlemesi gerçekleştirilmesi gösterilmektedir. Bu, üretken kullanıcı arabirimi, gerçek zamanlı panolar veya işbirliğine dayalı deneyimler gibi etkileşimli uygulamalar oluşturmak için gereklidir.

Önkoşullar

Başlamadan önce şunları anladığınızdan emin olun:

Durum Yönetimi nedir?

AG-UI'da durum yönetimi şunları sağlar:

  • Paylaşılan Durum: Hem istemci hem de sunucu, uygulama durumunun eşitlenmiş bir görünümünü korur
  • Çift Yönlü Eşitleme: Durum istemciden veya sunucudan güncelleştirilebilir
  • Gerçek Zamanlı Güncelleştirmeler: Değişiklikler durum olayları kullanılarak hemen akışla gönderilir
  • Öngörücü Güncellemeler: LLM araç bağımsız değişkenlerini oluştururken, durum güncellemelerini akış halinde sunar (optimizm odaklı kullanıcı arabirimi)
  • Yapılandırılmış Veriler: Durum, doğrulama için bir JSON şemasını izler

Kullanım Örnekleri

Durum yönetimi şunlar için değerlidir:

  • Üretken UI: Yazılım aracı tarafından kontrol edilen durum temelinde kullanıcı arabirimi bileşenlerini oluşturma
  • Form Oluşturma: Aracı, bilgi toplayan form alanlarını doldurur
  • İlerleme İzleme: Çok adımlı işlemlerin gerçek zamanlı ilerleme durumunu gösterme
  • Etkileşimli Panolar: Aracı işledikçe güncelleştirilen verileri görüntüleme
  • İşbirliğine Dayalı Düzenleme: Birden çok kullanıcı tutarlı durum güncelleştirmeleri görüyor

C#'de Durumdan Haberdar Etmenleri Oluşturma

Durum Modelinizi Tanımlama

İlk olarak, durum yapınız için sınıfları tanımlayın:

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;

Durum Yönetimi için Ara Yazılım Uygulamak

İstemcinin durumu ne zaman gönderdiğini algılayarak ve aracının yanıtlarını koordine ederek durum yönetimini işleyen ara yazılım oluşturun:

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

Aracıyı Durum Yönetimi ile Yapılandırma

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

Uyarı

DefaultAzureCredential geliştirme için uygundur ancak üretimde dikkatli bir şekilde dikkate alınması gerekir. Üretimde gecikme sorunları, istenmeyen kimlik bilgisi yoklama ve geri dönüş mekanizmalarından kaynaklanan olası güvenlik risklerini önlemek için belirli bir kimlik bilgisi (ör ManagedIdentityCredential. ) kullanmayı göz önünde bulundurun.

Aracı Uç Noktasını Eşleme

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

Önemli Kavramlar

  • Durum Algılama: Ara yazılım, istemcinin durum yönetimi isteğinde ag_ui_state bulunup bulunmadığını algılamak için ChatOptions.AdditionalProperties içinde denetim yapar.
  • İki Aşamalı Tepki: İlk olarak yapılandırılmış durumu (JSON şeması) oluşturur, ardından kullanıcı dostu bir özet sunar.
  • Yapılandırılmış Durum Modelleri: Durum yapınız için C# sınıflarını JSON özellik adlarıyla tanımlayın
  • JSON Şema Yanıt Biçimi: Yapılandırılmış çıktıyı sağlamak için kullanın ChatResponseFormat.ForJsonSchema<T>()
  • STATE_SNAPSHOT Olayları: AG-UI çerçevesinin otomatik olarak STATE_SNAPSHOT olaylara dönüştürdüğü medya türüyle DataContent olarak application/json yayılır
  • Durum Bağlamı: Geçerli durum, aracıya bağlam sağlamak için sistem iletisi olarak eklenir

Nasıl Çalışır?

  1. İstemci şu durumda istek gönderir: ChatOptions.AdditionalProperties["ag_ui_state"]
  2. Ara yazılım durumu algılar ve JSON şema yanıt biçimiyle ilk çalıştırmayı gerçekleştirir
  3. Ara yazılım, sistem iletisine bağlam olarak geçerli durumu ekler
  4. Aracı, durum modelinizle eşleşen yapılandırılmış durum güncelleştirmesi oluşturur
  5. Ara yazılım durumu serileştirir ve DataContent olarak yayınlar (STATE_SNAPSHOT etkinlik olarak gerçekleşir)
  6. Ara yazılım, kullanıcı dostu özet oluşturmak için ikinci çalıştırmayı gerçekleştirir
  7. İstemci hem durum anlık görüntüsünü hem de doğal dil özetini alır

Tavsiye

İki aşamalı yaklaşım, durum yönetimini kullanıcı iletişiminden ayırır. İlk aşama yapılandırılmış, güvenilir durum güncelleştirmeleri sağlarken ikinci aşama kullanıcıya doğal dil geri bildirimi sağlar.

İstemci Uygulaması (C#)

Önemli

C# istemci uygulaması bu öğreticiye dahil değildir. Sunucu tarafı durum yönetimi tamamlandı, ancak istemcilerin şunları gerçekleştirmesi gerekir:

  1. Durumu boş bir nesneyle başlatın (null değil): RecipeState? currentState = new RecipeState();
  2. Durumu, bir DataContent mesajında ChatRole.System olarak gönderin.
  3. DataContent ile birlikte mediaType = "application/json" olarak durum anlık görüntülerini alma

AG-UI barındırma katmanı DataContent elemanından durumu otomatik olarak ayıklar ve ChatOptions.AdditionalProperties["ag_ui_state"] olarak JsonElement elemanına yerleştirir.

Tam bir istemci uygulama örneği için aşağıdaki tam çift yönlü durum akışını gösteren Python istemci desenini inceleyin.

Durum Modellerini Tanımlama

İlk olarak, durum yapınız için Pydantik modelleri tanımlayın. Bu, tür güvenliğini ve doğrulamasını sağlar:

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

Durum Şeması

Durumunuzun yapısını ve türlerini belirtmek için bir durum şeması tanımlayın:

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

Uyarı

Durum şeması, type ve isteğe bağlı description ile basit bir biçim kullanır. Gerçek yapı, Pydantic modelleriniz tarafından tanımlanır.

Tahmine Dayalı Durum Güncelleştirmeleri

Tahmine dayalı durum güncellemeleri, LLM'nin oluşturduğu akış aracı argümanlarını duruma aktarır ve gerçek zamanlı kullanıcı arabirimi güncellemelerini etkinleştirir.

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

Bu yapılandırma, recipe durum alanını recipe aracının update_recipe bağımsız değişkeniyle eşler. Aracı çağırdığında, parametreler LLM tarafından oluşturulurken gerçek zamanlı olarak duruma akar.

Durum Güncelleştirme Aracı Tanımlama

Pydantic modelinizi kabul eden bir araç işlevi oluşturun:

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

Önemli

Araç işlevinin parametre adı (recipe), tool_argument içinde bulunan predict_state_config ile eşleşmelidir.

Durum Yönetimi ile Aracı Oluşturma

Durum yönetimiyle tam bir sunucu uygulaması aşağıda belirtilmiştir:

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

Önemli Kavramlar

  • Pydantik Modeller: Tür güvenliği ve doğrulama ile yapılandırılmış durumu tanımlama
  • Durum Şeması: Durum alanı türlerini belirten basit biçim
  • Tahmin Durum Yapılandırması: Akış güncellemeleri için durum alanlarını yazılım araçlarının parametreleri ile eşleştirir
  • Durum Ekleme: Geçerli durum, bağlam sağlamak için sistem iletileri olarak otomatik olarak eklenir
  • Güncelleştirmeleri Tamamlama: Araçlar yalnızca deltaları değil, tam durumu yazmalıdır
  • Onay Stratejisi: Etki alanınız için onay iletilerini özelleştirme (tarif, belge, görev planlaması vb.)

Durum Olaylarını Anlama

Durum Anlık Görüntü Olayı

Araç tamamlandığında gösterilen geçerli durumun tam anlık görüntüsü:

{
    "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"
            ]
        }
    }
}

Durum Delta Olayı

Aşamalı durum güncellemeleri, JSON Düzeltme Eki biçimi kullanarak LLM akış aracının bağımsız değişkenleri olarak yayımlanır.

{
    "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"]
            }
        }
    ]
}

Uyarı

LLM, araç parametrelerini oluşturarak iyimser kullanıcı arayüzü güncellemeleri sağlarken, durum delta olaylarını gerçek zamanlı olarak iletir. Araç yürütmeyi tamamladığında son durum anlık görüntüsü oluşturulur.

İstemci Uygulaması

Paket, AG-UI sunucularına bağlanma imkanı sağlar ve Python istemci deneyimini .NET ile eşdeğerlilik seviyesine getirir.

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

Önemli Avantajlar

aşağıdakileri AGUIChatClient sağlar:

  • Basitleştirilmiş Bağlantı: HTTP/SSE iletişiminin otomatik olarak işlenmesi
  • İş Parçacığı Yönetimi: Konuşma sürekliliği için yerleşik iş parçacığı kimliği izleme
  • Aracı Tümleştirmesi: Agent ile tanıdık API için sorunsuz çalışır
  • Durum İşleme: Sunucudan durum olaylarının otomatik ayrıştırılması
  • .NET ile eşlik: Diller arasında tutarlı deneyim

Tavsiye

Tam olarak aracı çerçevesinin konuşma geçmişi, araç yürütme ve ara yazılım desteği gibi özelliklerinden yararlanmak için AGUIChatClient ve Agent ile kullanın.

Onay Stratejilerini Kullanma

parametresi, confirmation_strategy etki alanınız için onay iletilerini özelleştirmenize olanak tanır:

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

Kullanılabilir stratejiler:

  • DefaultConfirmationStrategy() - Herhangi bir aracı için genel iletiler
  • RecipeConfirmationStrategy() - Tarife özgü iletiler
  • DocumentWriterConfirmationStrategy() - Belge düzenleme mesajları
  • TaskPlannerConfirmationStrategy() - Görev planlama iletileri

Ayrıca, öğesinden ConfirmationStrategy devralarak ve gerekli yöntemleri uygulayarak özel stratejiler oluşturabilirsiniz.

Örnek Etkileşim

Sunucu ve istemci çalışırken:

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

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

Tavsiye

:state Konuşma sırasında istediğiniz zaman geçerli durumu görüntülemek için komutunu kullanın.

Öngörü Durum Güncellemeleri Uygulamada

Tahmine dayalı durum güncelleştirmeleri predict_state_config ile kullanılırken, LLM araç yürütülmeden önce gerçek zamanlı olarak araç bağımsız değişkenleri oluştururken istemci STATE_DELTA olaylarını alır:

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

Bu, istemcinin aracı düşünürken iyimser kullanıcı arabirimi güncelleştirmelerini gerçek zamanlı olarak göstermesine olanak tanır ve kullanıcılara anında geri bildirim sağlar.

İnsan Müdahalesiyle Durum

ayarını yaparak require_confirmation=Truedurum yönetimini onay iş akışlarıyla birleştirebilirsiniz:

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

Etkinleştirildiğinde:

  1. Aracı araç bağımsız değişkenleri oluştururken durum güncelleştirmeleri akışı (olaylar aracılığıyla STATE_DELTA tahmine dayalı güncelleştirmeler)
  2. Aracı, aracı yürütmeden önce (olay aracılığıyla FUNCTION_APPROVAL_REQUEST) onay ister.
  3. Onaylanırsa, araç yürütülür ve son durum, olay STATE_SNAPSHOT aracılığıyla yayınlanır.
  4. Reddedilirse, öngörülen durum değişiklikleri atılır

Gelişmiş Durum Desenleri

Birden Çok Alan içeren Karmaşık Durum

Farklı araçlarla birden çok durum alanını yönetebilirsiniz:

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

Wildcard Aracı Bağımsız Değişkenlerini Kullanma

Bir araç karmaşık iç içe veri döndürdüğünde, "*"'yu tüm araç argümanlarını duruma eşlemek için kullanın.

@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": "*"}
}

Bu, araç çağrısının tamamını (tüm bağımsız değişkenler) document durum alanına eşler.

En İyi Yöntemler

Pydantic Modellerini Kullanma

Tür güvenliği için yapılandırılmış modeller tanımlayın:

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

Avantajlar:

  • Tür Güvenliği: Veri türlerinin otomatik doğrulanması
  • Belgeler: Alan açıklamaları belge görevi görür
  • IDE Desteği: Otomatik tamamlama ve tür denetimi
  • Serileştirme: Otomatik JSON dönüştürme

Durum Güncelleştirmelerini Tamamla

Yalnızca deltaları değil, her zaman tam durumu yazın:

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

Bu, durum tutarlılığını ve uygun tahmine dayalı güncelleştirmeleri sağlar.

Parametre Adlarını Eşleştir

Araç parametre adlarının yapılandırmayla eşleştiğinden tool_argument emin olun:

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

Yönergelerde Bağlam Sağlama

Durum yönetimi hakkında net yönergeler ekleyin:

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

Onay Stratejilerini Kullanma

Etki alanınız için onay iletilerini özelleştirin:

from agent_framework_ag_ui import RecipeConfirmationStrategy

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

Sonraki Adımlar

Şimdi tüm temel AG-UI özelliklerini öğrendiniz! Bundan sonra yapabileceklerin:

  • Agent Framework belgelerini keşfedin
  • Tüm AG-UI özelliklerini birleştiren eksiksiz bir uygulama oluşturma
  • AG-UI hizmetinizi üretim ortamına dağıtma

Ek Kaynaklar