Partilhar via


Gestão do Estado com AG-UI

Este tutorial mostra como implementar o gerenciamento de estado com AG-UI, permitindo a sincronização bidirecional de estado entre o cliente e o servidor. Isso é essencial para criar aplicativos interativos, como interface do usuário generativa, painéis em tempo real ou experiências colaborativas.

Pré-requisitos

Antes de começar, certifique-se de que compreende:

O que é a Gestão do Estado?

A gestão do Estado em AG-UI permite:

  • Estado compartilhado: o cliente e o servidor mantêm uma exibição sincronizada do estado do aplicativo
  • Sincronização bidirecional: o estado pode ser atualizado a partir do cliente ou do servidor
  • Atualizações em tempo real: as alterações são transmitidas imediatamente usando eventos de estado
  • Atualizações preditivas: fluxo de atualizações de estado à medida que o LLM gera argumentos de ferramenta (interface do usuário otimista)
  • Dados estruturados: o estado segue um esquema JSON para validação

Casos de uso

A gestão do Estado é valiosa para:

  • Interface do usuário generativa: crie componentes da interface do usuário com base no estado controlado pelo agente
  • Criação de formulários: o agente preenche campos de formulário à medida que reúne informações
  • Acompanhamento do progresso: mostre o progresso em tempo real das operações em várias etapas
  • Painéis interativos: exibem dados que são atualizados à medida que o agente os processa
  • Edição colaborativa: vários usuários veem atualizações de estado consistentes

Criando agentes State-Aware em C#

Defina seu modelo de estado

Primeiro, defina classes para sua estrutura de estado:

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;

Implementar middleware de gestão de estado

Crie middleware que lida com o gerenciamento de estado detetando quando o cliente envia o estado e coordenando as respostas do agente:

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

Configurar o agente com gerenciamento de estado

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

Advertência

DefaultAzureCredential é conveniente para o desenvolvimento, mas requer uma consideração cuidadosa na produção. Em produção, considere usar uma credencial específica (por exemplo, ManagedIdentityCredential) para evitar problemas de latência, sondagens não intencionais de credenciais e potenciais riscos de segurança provenientes de mecanismos de recurso.

Mapeie o endpoint do agente

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

Conceitos-chave

  • Deteção de estado: o middleware verifica ag_ui_stateChatOptions.AdditionalProperties para detetar quando o cliente está a solicitar gestão de estado
  • Resposta em Duas Fases: primeiro gera o estado estruturado (esquema JSON) e, em seguida, gera um resumo fácil de usar.
  • Modelos de estado estruturados: defina classes C# para sua estrutura de estado com nomes de propriedade JSON
  • Formato de resposta do esquema JSON: Use ChatResponseFormat.ForJsonSchema<T>() para garantir a saída estruturada
  • STATE_SNAPSHOT eventos: emitidos como DataContent com application/json tipo de mídia, que o framework AG-UI converte em eventos STATE_SNAPSHOT automaticamente
  • Contexto do Estado: O estado atual é injetado como uma mensagem do sistema para fornecer contexto ao agente

Como funciona

  1. Cliente envia solicitação com estado em ChatOptions.AdditionalProperties["ag_ui_state"]
  2. O middleware deteta o estado e executa a primeira execução com o formato de resposta do esquema JSON
  3. O middleware adiciona o estado atual como contexto em uma mensagem do sistema
  4. O agente gera atualização de estado estruturada correspondente ao seu modelo de estado
  5. O middleware serializa o estado e emite-o como DataContent (torna-se o evento STATE_SNAPSHOT)
  6. O middleware executa uma segunda execução para gerar um resumo amigável
  7. O cliente recebe o instantâneo de estado e o resumo em linguagem natural

Sugestão

A abordagem em duas fases separa o gerenciamento de estado da comunicação com o usuário. A primeira fase garante atualizações de estado estruturadas e confiáveis, enquanto a segunda fase fornece feedback em linguagem natural para o usuário.

Implementação do cliente (C#)

Importante

A implementação do cliente C# não está incluída neste tutorial. O gerenciamento de estado do lado do servidor está concluído, mas os clientes precisam:

  1. Inicializar o estado com um objeto vazio (não nulo): RecipeState? currentState = new RecipeState();
  2. Enviar estado como DataContent numa ChatRole.System mensagem
  3. Receba instantâneos de estado como DataContent com mediaType = "application/json"

A camada de hospedagem AG-UI extrai automaticamente o estado de DataContent e coloca-o em ChatOptions.AdditionalProperties["ag_ui_state"] como um JsonElement.

Para obter um exemplo completo de implementação de cliente, consulte o padrão de cliente Python abaixo que demonstra o fluxo de estado bidirecional completo.

Modelos de Estados

Primeiro, defina modelos pidânticos para a sua estrutura de estado. Isso garante a segurança do tipo e a validação:

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

Esquema de estado

Defina um esquema de estado para especificar a estrutura e os tipos do seu estado:

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

Observação

O esquema de estado usa um formato simples com type e opcional description. A estrutura real é definida pelos seus modelos Pydantic.

Atualizações de estado preditivas

As atualizações de estado preditivo transmitem argumentos da ferramenta para o estado à medida que o LLM os gera, permitindo atualizações otimistas da interface do usuário:

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

Esta configuração mapeia o recipe campo de estado para o recipe argumento da update_recipe ferramenta. Quando o agente chama a ferramenta, os argumentos são transmitidos para o estado em tempo real à medida que o modelo de linguagem de máquina os gera.

Definir ferramenta de atualização de estado

Crie uma função de ferramenta que aceite o seu modelo Pydantic:

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

Importante

O nome do parâmetro da função de ferramenta (recipe) deve corresponder ao tool_argument no seu predict_state_config.

Criar agente com gestão de estado

Aqui está uma implementação completa do servidor com gerenciamento de estado:

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

Conceitos-chave

  • Modelos Pydantic: Definir estado estruturado com segurança de tipo e validação
  • Esquema de estado: formato simples especificando tipos de campo de estado
  • Configuração de estado preditivo: mapeia campos de estado para argumentos de ferramenta para atualizações de streaming
  • Injeção de estado: O estado atual é injetado automaticamente como mensagens do sistema para fornecer contexto
  • Atualizações completas: as ferramentas devem gravar o estado completo, não apenas deltas
  • Estratégia de confirmação: Personalize as mensagens de aprovação para o seu domínio (receita, documento, planeamento de tarefas, etc.)

Noções básicas sobre eventos de estado

Evento de instantâneo de estado

Um instantâneo completo do estado atual, emitido quando a ferramenta é finalizada:

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

Evento Delta do Estado

Atualizações de estado incrementais usando o formato JSON Patch, emitidas como os argumentos da ferramenta LLM streams:

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

Observação

Os eventos delta de estado são transmitidos em tempo real à medida que o LLM gera os argumentos da ferramenta, fornecendo atualizações otimistas da interface do usuário. O instantâneo de estado final é emitido quando a ferramenta conclui a execução.

Implementação do Cliente

O agent_framework_ag_ui pacote fornece AGUIChatClient a conexão com servidores AG-UI, trazendo a experiência do cliente Python para a paridade com o .NET:

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

Principais Benefícios

O AGUIChatClient dispõe:

  • Conexão simplificada: manipulação automática de comunicação HTTP/SSE
  • Gerenciamento de threads: rastreamento de ID de thread integrado para continuidade de conversa
  • Integração de agentes: funciona perfeitamente com Agent uma API familiar
  • State Handling: Análise automática de eventos de estado do servidor
  • Paridade com .NET: experiência consistente entre idiomas

Sugestão

Use AGUIChatClient com Agent para obter todos os benefícios dos recursos da estrutura do agente, como histórico de conversas, execução de ferramentas e suporte a middleware.

Usando estratégias de confirmação

O confirmation_strategy parâmetro permite personalizar mensagens de aprovação para o seu domínio:

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

Estratégias disponíveis:

  • DefaultConfirmationStrategy() - Mensagens genéricas para qualquer agente
  • RecipeConfirmationStrategy() - Mensagens específicas da receita
  • DocumentWriterConfirmationStrategy() - Mensagens de edição de documentos
  • TaskPlannerConfirmationStrategy() - Mensagens de planeamento de tarefas

Você também pode criar estratégias personalizadas herdando de ConfirmationStrategy e implementando os métodos necessários.

Exemplo de interação

Com o servidor e o cliente em execução:

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

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

Sugestão

Use o :state comando para exibir o estado atual a qualquer momento durante a conversa.

Atualizações de estado preditivas em ação

Ao usar atualizações de estado preditivas com predict_state_config, o cliente recebe STATE_DELTA eventos à medida que o LLM gera argumentos da ferramenta em tempo real, antes da execução da ferramenta.

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

Isso permite que o cliente mostre atualizações otimistas da interface do usuário em tempo real conforme o agente está pensando, fornecendo feedback imediato aos usuários.

Estado com Human-in-the-Loop

Você pode combinar o gerenciamento de estado com fluxos de trabalho de aprovação definindo 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(),
)

Quando ativado:

  1. Fluxo de atualizações de estado à medida que o agente gera argumentos de ferramenta (atualizações preditivas por meio de STATE_DELTA eventos)
  2. O agente solicita aprovação antes de executar a ferramenta (via FUNCTION_APPROVAL_REQUEST evento)
  3. Se aprovada, a ferramenta é executada e o estado final é emitido (via STATE_SNAPSHOT evento)
  4. Se rejeitadas, as alterações de estado preditivo são descartadas

Padrões de estado avançados

Estado complexo com vários campos

Você pode gerenciar vários campos de estado com diferentes ferramentas:

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

Usando argumentos da ferramenta curinga

Quando uma ferramenta retorna dados aninhados complexos, use "*" para mapear todos os argumentos da ferramenta para afirmar:

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

Isso mapeia toda a chamada de ferramenta (todos os argumentos) para o document campo de estado.

Melhores práticas

Usar modelos pidânticos

Definir modelos estruturados para segurança de tipo:

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

Benefícios:

  • Segurança do tipo: Validação automática de tipos de dados
  • Documentação: As descrições dos campos servem como documentação
  • Suporte IDE: Preenchimento automático e verificação de tipo
  • Serialização: conversão automática de JSON

Atualizações de estado completas

Escreva sempre o estado completo, não apenas deltas:

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

Isso garante a consistência do estado e atualizações preditivas adequadas.

Corresponder nomes de parâmetros

Verifique se os nomes dos parâmetros da ferramenta correspondem à tool_argument configuração:

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

Fornecer contexto nas instruções

Inclua instruções claras sobre a gestão do estado:

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

Usar estratégias de confirmação

Personalize as mensagens de aprovação do seu domínio:

from agent_framework_ag_ui import RecipeConfirmationStrategy

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

Próximas Etapas

Agora você aprendeu todos os principais recursos AG-UI! Em seguida, você pode:

  • Explore a documentação do Agent Framework
  • Crie um aplicativo completo combinando todos os recursos AG-UI
  • Implante seu serviço de AG-UI na produção

Recursos adicionais