Renderização de ferramenta de back-end com AG-UI

As ferramentas de back-end usam o pipeline de ferramentas MAF normal. AG-UI adiciona eventos de transporte para que um cliente possa observar a chamada e o resultado; ele não introduz uma abstração de ferramenta separada.

Adicionar uma ferramenta de back-end

Defina e registre a ferramenta como faria para qualquer agente do MAF:

using System.ComponentModel;
using Microsoft.Extensions.AI;

[Description("Get the weather for a location.")]
static string GetWeather(
    [Description("The city to look up.")] string location) =>
    $"The weather in {location} is sunny.";

AITool getWeather = AIFunctionFactory.Create(GetWeather, name: "get_weather");
AIAgent agent = chatClient.AsAIAgent(tools: [getWeather]);

app.MapAGUIServer("/", agent);

Para tipos de solicitação ou resposta complexos, configure o mesmo JsonSerializerOptions para ASP.NET Core e AIFunctionFactory.Create.

Dica

Consulte o .NET exemplo de ferramentas de back-end para obter uma implementação completa.

Para esquemas de ferramentas, injeção de dependência, tratamento de erros e design geral de ferramentas, consulte Usar ferramentas de função com um agente.

mapeamento de eventos AG-UI

Quando o agente chama a ferramenta:

  • FunctionCallContenté emitido como AG-UI TOOL_CALL_STARTe TOOL_CALL_ARGSTOOL_CALL_END eventos.
  • FunctionResultContent é emitido como um TOOL_CALL_RESULT evento.
  • O texto e outro conteúdo do agente continuam a ser transmitidos normalmente.

Um cliente .NET recebe o conteúdo traduzido como FunctionCallContent eFunctionResultContent:

await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, session))
{
    foreach (AIContent content in update.Contents)
    {
        if (content is FunctionCallContent call)
        {
            Console.WriteLine($"Calling {call.Name}");
        }
        else if (content is FunctionResultContent result)
        {
            Console.WriteLine($"Result: {result.Result}");
        }
    }
}

Os resultados da ferramenta são valores voltados para o modelo que AG-UI também expõe ao cliente. Para emitir o estado da interface do usuário compartilhado além de um resultado de ferramenta, use os mapeamentos explícitos descritos no gerenciamento de Estado.

Próximas Etapas 

Este tutorial mostra como adicionar ferramentas de função aos seus agentes de AG-UI. As ferramentas de função são funções personalizadas do Python que o agente pode chamar para executar tarefas específicas, como recuperar dados, executar cálculos ou interagir com sistemas externos. Com o AG-UI, essas ferramentas são executadas no back-end e seus resultados são transmitidos automaticamente para o cliente.

Pré-requisitos

Antes de começar, verifique se você concluiu o tutorial de Introdução e tenha:

  • Python 3.10 ou posterior
  • agent-framework-ag-ui Instalado
  • Serviço Azure OpenAI configurado
  • Noções básicas sobre AG-UI configuração do servidor e do cliente

Note

Esses exemplos usam DefaultAzureCredential para autenticação. Verifique se você está autenticado com o Azure (por exemplo, via az login). Para obter mais informações, consulte a documentação da Identidade do Azure.

O que é renderização de ferramenta de back-end?

A renderização realizada por ferramentas de back-end significa:

  • As ferramentas de função são definidas no servidor
  • O agente de IA decide quando chamar essas ferramentas
  • Ferramentas executadas no back-end (lado do servidor)
  • Os eventos e os resultados da chamada de ferramenta são transmitidos para o cliente em tempo real
  • O cliente recebe atualizações sobre o progresso da execução da ferramenta

Esta abordagem proporciona:

  • Segurança: as operações confidenciais permanecem no servidor
  • Consistência: todos os clientes usam as mesmas implementações de ferramenta
  • Transparência: os clientes podem exibir o progresso da execução da ferramenta
  • Flexibilidade: atualizar ferramentas sem alterar o código do cliente

Criando ferramentas de funções

Ferramenta de Função Básica

Você pode transformar qualquer função python em uma ferramenta usando o @tool decorador:

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


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

Conceitos-chave

  • @tool decorador: marca uma função como disponível para o agente
  • Anotações de tipo: fornecer informações de tipo para parâmetros
  • Annotated e Field: adicionar descrições para ajudar o agente a entender os parâmetros
  • Docstring: descreve o que a função faz (ajuda o agente a decidir quando usá-la)
  • Valor retornado: o resultado retornado ao agente (e transmitido para o cliente)

Ferramentas Multifuncionais

Você pode fornecer várias ferramentas para dar ao agente mais funcionalidades:

from typing import Any
from agent_framework import tool


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


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

Criando um servidor AG-UI com ferramentas de função

Aqui está uma implementação completa do servidor com ferramentas de função:

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

import os
from typing import Annotated, Any

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


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


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


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

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

chat_client = OpenAIChatCompletionClient(
    model=deployment_name,
    azure_endpoint=endpoint,
    api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
    credential=AzureCliCredential(),
)

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

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

if __name__ == "__main__":
    import uvicorn

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

Compreender Eventos da Ferramenta

Quando o agente chama uma ferramenta, o cliente recebe vários eventos:

Eventos de ativação de ferramenta

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

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

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

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

Cliente aprimorado para eventos de ferramenta

Aqui está um cliente aprimorado usando AGUIChatClient que exibe a execução da ferramenta:

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

import asyncio
import os

from agent_framework import Agent
from agent_framework_ag_ui import AGUIChatClient


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

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

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

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

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

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

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

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

            print("\n")

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


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

Interação de exemplo

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

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

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

Práticas recomendadas de implementação de ferramentas

Tratamento de erros

Manipule erros de forma elegante em suas ferramentas.

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

Tipos de retorno avançados

Retornar dados estruturados quando apropriado:

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

Documentação descritiva

Forneça descrições claras para ajudar o agente a entender quando usar ferramentas:

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

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

Organização de ferramentas com classes

Para ferramentas relacionadas, organize-as em uma classe:

from agent_framework import tool


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

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

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

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


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

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

Próximas etapas

Agora que você entende a renderização da ferramenta de back-end, você pode:

Recursos adicionais

Os servidores go AG-UI podem expor ferramentas de função normais do Agent Framework. Crie ferramentas com tool/functool, anexe-as ao agente hospedado e atenda ao agente com aguiprovider.NewJSONHTTPHandler.

searchRestaurants := functool.MustNew(functool.Config{
    Name:        "search_restaurants",
    Description: "Search for restaurants in a location.",
}, func(ctx context.Context, in restaurantSearchRequest) (restaurantSearchResponse, error) {
    return restaurantSearchResponse{
        Location: in.Location,
        Cuisine:  in.Cuisine,
        Results:  []restaurantInfo{{Name: "The Golden Fork", Cuisine: in.Cuisine}},
    }, nil
})

a := foundryprovider.NewAgent(endpoint, token, foundryprovider.ModelDeployment(model), foundryprovider.AgentConfig{
    Config: agent.Config{
        Tools: []tool.Tool{searchRestaurants},
    },
})

Dica

Consulte o exemplo das ferramentas de back-end do AG-UI para obter um exemplo completo e executável.