Compartilhar via


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

Este tutorial mostra como adicionar ferramentas de função de front-end aos seus clientes AG-UI. As ferramentas de front-end são funções executadas no lado do cliente, permitindo que o agente de IA interaja com o ambiente local do usuário, acesse dados específicos do cliente ou execute operações de interface do usuário. O servidor orquestra quando chamar essas ferramentas, mas a execução ocorre inteiramente no cliente.

Pré-requisitos

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

  • .NET 8.0 ou posterior
  • Microsoft.Agents.AI.AGUI pacote instalado
  • Microsoft.Agents.AI pacote instalado
  • Noções básicas sobre a configuração do cliente AG-UI

O que são ferramentas de front-end?

As ferramentas de front-end são ferramentas de função que:

  • São definidos e registrados no cliente
  • Executar no ambiente do cliente (não no servidor)
  • Permitir que o agente de IA interaja com recursos específicos do cliente
  • Fornecer resultados de volta ao servidor para que o agente incorpore em respostas
  • Habilitar experiências personalizadas e com reconhecimento de contexto

Casos de uso comuns:

  • Lendo dados do sensor local (GPS, temperatura etc.)
  • Acessando o armazenamento ou as preferências do lado do cliente
  • Executando operações de interface do usuário (alterando temas, exibindo notificações)
  • Interagindo com recursos específicos do dispositivo (câmera, microfone)

Registrando ferramentas de front-end no cliente

A principal diferença do tutorial de introdução é registrar ferramentas com o agente cliente. Veja o que muda:

// Define a frontend function tool
[Description("Get the user's current location from GPS.")]
static string GetUserLocation()
{
    // Access client-side GPS
    return "Amsterdam, Netherlands (52.37°N, 4.90°E)";
}

// Create frontend tools
AITool[] frontendTools = [AIFunctionFactory.Create(GetUserLocation)];

// Pass tools when creating the agent
AIAgent agent = chatClient.AsAIAgent(
    name: "agui-client",
    description: "AG-UI Client Agent",
    tools: frontendTools);

O restante do código do cliente permanece o mesmo mostrado no tutorial de Introdução.

Como as ferramentas são enviadas para o servidor

Quando você registra ferramentas com AsAIAgent(), o AGUIChatClient automaticamente:

  1. Captura as definições de ferramenta (nomes, descrições, esquemas de parâmetro)
  2. Envia as ferramentas com cada solicitação para o agente do servidor que as mapeia para ChatAgentRunOptions.ChatOptions.Tools

O servidor recebe as declarações da ferramenta de cliente e o modelo de IA pode decidir quando chamá-las.

Inspecionando e modificando ferramentas com middleware

Você pode usar o middleware do agente para inspecionar ou modificar a execução do agente, incluindo o acesso às ferramentas:

// Create agent with middleware that inspects tools
AIAgent inspectableAgent = baseAgent
    .AsBuilder()
    .Use(runFunc: null, runStreamingFunc: InspectToolsMiddleware)
    .Build();

static async IAsyncEnumerable<AgentResponseUpdate> InspectToolsMiddleware(
    IEnumerable<ChatMessage> messages,
    AgentSession? session,
    AgentRunOptions? options,
    AIAgent innerAgent,
    CancellationToken cancellationToken)
{
    // Access the tools from ChatClientAgentRunOptions
    if (options is ChatClientAgentRunOptions chatOptions)
    {
        IList<AITool>? tools = chatOptions.ChatOptions?.Tools;
        if (tools != null)
        {
            Console.WriteLine($"Tools available for this run: {tools.Count}");
            foreach (AITool tool in tools)
            {
                if (tool is AIFunction function)
                {
                    Console.WriteLine($"  - {function.Metadata.Name}: {function.Metadata.Description}");
                }
            }
        }
    }

    await foreach (AgentResponseUpdate update in innerAgent.RunStreamingAsync(messages, session, options, cancellationToken))
    {
        yield return update;
    }
}

Esse padrão de middleware permite que você:

  • Validar definições de ferramenta antes da execução

Conceitos Principais

Veja a seguir novos conceitos para ferramentas de front-end:

  • Registro do lado do cliente: as ferramentas são registradas no cliente usando AIFunctionFactory.Create() e passadas para AsAIAgent()
  • Captura automática: as ferramentas são capturadas e enviadas automaticamente por meio de ChatAgentRunOptions.ChatOptions.Tools

Como funcionam as ferramentas de front-end

Fluxo do Lado do Servidor

O servidor não sabe os detalhes de implementação das ferramentas de front-end. Ele só sabe:

  1. Nomes e descrições de ferramentas (do registro do cliente)
  2. Esquemas de parâmetro
  3. Quando solicitar a execução da ferramenta

Quando o agente de IA decide chamar uma ferramenta de front-end:

  1. O servidor envia uma solicitação de chamada de ferramenta para o cliente por meio da SSE
  2. O servidor aguarda que o cliente execute a ferramenta e retorne os resultados
  3. O servidor incorpora os resultados no contexto do agente
  4. O agente continua o processamento com os resultados da ferramenta

Fluxo de Client-Side

O cliente manipula a execução da ferramenta de front-end:

  1. Recebe FunctionCallContent do servidor indicando uma solicitação de ferramenta
  2. Corresponde o nome da ferramenta a uma função registrada localmente
  3. Desserializa parâmetros da solicitação
  4. Executa a função localmente
  5. Serializa o resultado
  6. Envia FunctionResultContent de volta para o servidor
  7. Continua recebendo respostas do agente

Saída esperada com ferramentas de front-end

Quando o agente chama as ferramentas de front-end, você verá a chamada da ferramenta e resultará na saída de streaming:

User (:q or quit to exit): Where am I located?

[Client Tool Call - Name: GetUserLocation]
[Client Tool Result: Amsterdam, Netherlands (52.37°N, 4.90°E)]

You are currently in Amsterdam, Netherlands, at coordinates 52.37°N, 4.90°E.

Configuração do servidor para ferramentas de front-end

O servidor não precisa de configuração especial para dar suporte a ferramentas de front-end. Use o servidor padrão AG-UI do tutorial Começando - ele automaticamente:

  • Recebe declarações de ferramenta de front-end durante a conexão do cliente
  • Solicita a execução da ferramenta quando o agente de IA precisa delas
  • Aguarda os resultados do cliente
  • Incorpora resultados na tomada de decisão do agente

Próximas etapas

Agora que você entende as ferramentas de front-end, você pode:

Recursos adicionais

Este tutorial mostra como adicionar ferramentas de função de front-end aos seus clientes AG-UI. As ferramentas de front-end são funções executadas no lado do cliente, permitindo que o agente de IA interaja com o ambiente local do usuário, acesse dados específicos do cliente ou execute operações de interface do usuário.

Pré-requisitos

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

  • Python 3.10 ou posterior
  • httpx instalado para a funcionalidade do cliente HTTP
  • Noções básicas sobre a configuração do cliente AG-UI
  • Serviço Azure OpenAI configurado

O que são ferramentas de front-end?

As ferramentas de front-end são ferramentas de função que:

  • São definidos e registrados no cliente
  • Executar no ambiente do cliente (não no servidor)
  • Permitir que o agente de IA interaja com recursos específicos do cliente
  • Fornecer resultados de volta ao servidor para que o agente incorpore em respostas

Casos de uso comuns:

  • Lendo dados do sensor local
  • Acessando o armazenamento ou as preferências do lado do cliente
  • Executando operações de interface do usuário
  • Interagindo com recursos específicos do dispositivo

Criando ferramentas de front-end

As ferramentas de front-end no Python são definidas de forma semelhante às ferramentas de back-end, mas são registradas com o cliente:

from typing import Annotated
from pydantic import BaseModel, Field


class SensorReading(BaseModel):
    """Sensor reading from client device."""
    temperature: float
    humidity: float
    air_quality_index: int


def read_climate_sensors(
    include_temperature: Annotated[bool, Field(description="Include temperature reading")] = True,
    include_humidity: Annotated[bool, Field(description="Include humidity reading")] = True,
) -> SensorReading:
    """Read climate sensor data from the client device."""
    # Simulate reading from local sensors
    return SensorReading(
        temperature=22.5 if include_temperature else 0.0,
        humidity=45.0 if include_humidity else 0.0,
        air_quality_index=75,
    )


def change_background_color(color: Annotated[str, Field(description="Color name")] = "blue") -> str:
    """Change the console background color."""
    # Simulate UI change
    print(f"\n🎨 Background color changed to {color}")
    return f"Background changed to {color}"

Criando um cliente AG-UI com ferramentas de front-end

Aqui está uma implementação completa do cliente com ferramentas de front-end:

"""AG-UI client with frontend tools."""

import asyncio
import json
import os
from typing import Annotated, AsyncIterator

import httpx
from pydantic import BaseModel, Field


class SensorReading(BaseModel):
    """Sensor reading from client device."""
    temperature: float
    humidity: float
    air_quality_index: int


# Define frontend tools
def read_climate_sensors(
    include_temperature: Annotated[bool, Field(description="Include temperature")] = True,
    include_humidity: Annotated[bool, Field(description="Include humidity")] = True,
) -> SensorReading:
    """Read climate sensor data from the client device."""
    return SensorReading(
        temperature=22.5 if include_temperature else 0.0,
        humidity=45.0 if include_humidity else 0.0,
        air_quality_index=75,
    )


def get_user_location() -> dict:
    """Get the user's current GPS location."""
    # Simulate GPS reading
    return {
        "latitude": 52.3676,
        "longitude": 4.9041,
        "accuracy": 10.0,
        "city": "Amsterdam",
    }


# Tool registry maps tool names to functions
FRONTEND_TOOLS = {
    "read_climate_sensors": read_climate_sensors,
    "get_user_location": get_user_location,
}


class AGUIClientWithTools:
    """AG-UI client with frontend tool support."""

    def __init__(self, server_url: str, tools: dict):
        self.server_url = server_url
        self.tools = tools
        self.thread_id: str | None = None

    async def send_message(self, message: str) -> AsyncIterator[dict]:
        """Send a message and handle streaming response with tool execution."""
        # Prepare tool declarations for the server
        tool_declarations = []
        for name, func in self.tools.items():
            tool_declarations.append({
                "name": name,
                "description": func.__doc__ or "",
                # Add parameter schema from function signature
            })

        request_data = {
            "messages": [
                {"role": "system", "content": "You are a helpful assistant with access to client tools."},
                {"role": "user", "content": message},
            ],
            "tools": tool_declarations,  # Send tool declarations to server
        }

        if self.thread_id:
            request_data["thread_id"] = self.thread_id

        async with httpx.AsyncClient(timeout=60.0) as client:
            async with client.stream(
                "POST",
                self.server_url,
                json=request_data,
                headers={"Accept": "text/event-stream"},
            ) as response:
                response.raise_for_status()

                async for line in response.aiter_lines():
                    if line.startswith("data: "):
                        data = line[6:]
                        try:
                            event = json.loads(data)

                            # Handle tool call requests from server
                            if event.get("type") == "TOOL_CALL_REQUEST":
                                await self._handle_tool_call(event, client)
                            else:
                                yield event

                            # Capture thread_id
                            if event.get("type") == "RUN_STARTED" and not self.thread_id:
                                self.thread_id = event.get("threadId")

                        except json.JSONDecodeError:
                            continue

    async def _handle_tool_call(self, event: dict, client: httpx.AsyncClient):
        """Execute frontend tool and send result back to server."""
        tool_name = event.get("toolName")
        tool_call_id = event.get("toolCallId")
        arguments = event.get("arguments", {})

        print(f"\n\033[95m[Client Tool Call: {tool_name}]\033[0m")
        print(f"  Arguments: {arguments}")

        try:
            # Execute the tool
            tool_func = self.tools.get(tool_name)
            if not tool_func:
                raise ValueError(f"Unknown tool: {tool_name}")

            result = tool_func(**arguments)

            # Convert Pydantic models to dict
            if hasattr(result, "model_dump"):
                result = result.model_dump()

            print(f"\033[94m[Client Tool Result: {result}]\033[0m")

            # Send result back to server
            await client.post(
                f"{self.server_url}/tool_result",
                json={
                    "tool_call_id": tool_call_id,
                    "result": result,
                },
            )

        except Exception as e:
            print(f"\033[91m[Tool Error: {e}]\033[0m")
            # Send error back to server
            await client.post(
                f"{self.server_url}/tool_result",
                json={
                    "tool_call_id": tool_call_id,
                    "error": str(e),
                },
            )


async def main():
    """Main client loop with frontend tools."""
    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")

    client = AGUIClientWithTools(server_url, FRONTEND_TOOLS)

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

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

            print()
            async for event in client.send_message(message):
                event_type = event.get("type", "")

                if event_type == "RUN_STARTED":
                    print(f"\033[93m[Run Started]\033[0m")

                elif event_type == "TEXT_MESSAGE_CONTENT":
                    print(f"\033[96m{event.get('delta', '')}\033[0m", end="", flush=True)

                elif event_type == "RUN_FINISHED":
                    print(f"\n\033[92m[Run Finished]\033[0m")

                elif event_type == "RUN_ERROR":
                    error_msg = event.get("message", "Unknown error")
                    print(f"\n\033[91m[Error: {error_msg}]\033[0m")

            print()

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


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

Como funcionam as ferramentas de front-end

Fluxo de protocolo

  1. Registro do cliente: o cliente envia declarações de ferramenta (nomes, descrições, parâmetros) para o servidor
  2. Orquestração de Servidor: o agente de IA decide quando chamar ferramentas de front-end com base na solicitação do usuário
  3. Solicitação de Chamada de Ferramenta: Servidor envia TOOL_CALL_REQUEST evento ao cliente via SSE
  4. Execução do cliente: o cliente executa a ferramenta localmente
  5. Envio de resultados: cliente envia resultado de volta ao servidor por meio de solicitação POST
  6. Processamento do agente: o servidor incorpora o resultado e continua a resposta

Principais eventos

  • TOOL_CALL_REQUEST: servidor solicita a execução da ferramenta de front-end
  • TOOL_CALL_RESULT: o cliente envia o resultado da execução (via HTTP POST)

Saída esperada

User (:q or quit to exit): What's the temperature reading from my sensors?

[Run Started]

[Client Tool Call: read_climate_sensors]
  Arguments: {'include_temperature': True, 'include_humidity': True}
[Client Tool Result: {'temperature': 22.5, 'humidity': 45.0, 'air_quality_index': 75}]

Based on your sensor readings, the current temperature is 22.5°C and the 
humidity is at 45%. These are comfortable conditions!
[Run Finished]

Instalação do servidor

O servidor AG-UI da série de tutoriais de introdução suporta automaticamente ferramentas de interface de usuário. Nenhuma alteração necessária no lado do servidor – ele manipula a orquestração de ferramentas automaticamente.

Práticas recomendadas

Segurança

def access_sensitive_data() -> str:
    """Access user's sensitive data."""
    # Always check permissions first
    if not has_permission():
        return "Error: Permission denied"

    try:
        # Access data
        return "Data retrieved"
    except Exception as e:
        # Don't expose internal errors
        return "Unable to access data"

Tratamento de erros

def read_file(path: str) -> str:
    """Read a local file."""
    try:
        with open(path, "r") as f:
            return f.read()
    except FileNotFoundError:
        return f"Error: File not found: {path}"
    except PermissionError:
        return f"Error: Permission denied: {path}"
    except Exception as e:
        return f"Error reading file: {str(e)}"

Operações assíncronas

async def capture_photo() -> str:
    """Capture a photo from device camera."""
    # Simulate camera access
    await asyncio.sleep(1)
    return "photo_12345.jpg"

Resolução de problemas

Ferramentas que não estão sendo chamadas

  1. Garantir que as declarações da ferramenta sejam enviadas ao servidor
  2. Verifique se as descrições das ferramentas indicam claramente a finalidade.
  3. Verificar logs do servidor para o registro da ferramenta

Erros de Execução

  1. Adicionar tratamento abrangente de erros
  2. Validar parâmetros antes do processamento
  3. Retornar mensagens de erro amigáveis ao usuário
  4. Registrar erros para depuração

Problemas de tipo

  1. Usar modelos Pydantic para tipos complexos
  2. Converter modelos em ditados antes da serialização
  3. Manipular as conversões de tipo de forma explícita

Próximas etapas

Recursos adicionais