Compartir a través de


Representación de herramientas de front-end con AG-UI

En este tutorial se muestra cómo agregar herramientas de función de front-end a los clientes de AG-UI. Las herramientas de front-end son funciones que se ejecutan en el lado cliente, lo que permite que el agente de IA interactúe con el entorno local del usuario, acceda a datos específicos del cliente o realice operaciones de interfaz de usuario. El servidor organiza cuándo llamar a estas herramientas, pero la ejecución se produce completamente en el cliente.

Prerrequisitos

Antes de comenzar, asegúrese de que ha completado el tutorial de introducción y que tiene:

  • .NET 8.0 o posterior
  • Microsoft.Agents.AI.AGUI paquete instalado
  • Microsoft.Agents.AI paquete instalado
  • Conocimientos básicos de la configuración del cliente de AG-UI

¿Qué son las herramientas de front-end?

Las herramientas de front-end son herramientas de función que:

  • Se definen y registran en el cliente
  • Ejecutar en el entorno del cliente (no en el servidor)
  • Permitir que el agente de IA interactúe con recursos específicos del cliente
  • Proporcionar los resultados al servidor para que el agente los incorpore en las respuestas.
  • Habilitación de experiencias personalizadas y compatibles con el contexto

Casos de uso comunes:

  • Lectura de datos del sensor local (GPS, temperatura, etc.)
  • Acceso al almacenamiento o preferencias del lado cliente
  • Realizar operaciones de interfaz de usuario (cambiar temas, mostrar notificaciones)
  • Interacción con características específicas del dispositivo (cámara, micrófono)

Registro de herramientas de front-end en el cliente

La diferencia clave del tutorial de introducción es registrar herramientas con el agente del cliente. Estos son los cambios:

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

El resto del código de cliente sigue siendo el mismo que se muestra en el tutorial Introducción.

Cómo se envían las herramientas al servidor

Al registrar herramientas con AsAIAgent(), AGUIChatClient automáticamente:

  1. Captura las definiciones de herramientas (nombres, descripciones, esquemas de parámetros)
  2. Envía las herramientas con cada solicitud al agente de servidor que los asigna a ChatAgentRunOptions.ChatOptions.Tools

El servidor recibe las declaraciones de la herramienta cliente y el modelo de IA puede decidir cuándo llamarlas.

Inspección y modificación de herramientas con middleware

Puede usar el middleware del agente para inspeccionar o modificar la ejecución del agente, incluido el acceso a las herramientas:

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

Este patrón de middleware le permite:

  • Validación de definiciones de herramientas antes de la ejecución

Conceptos clave

A continuación se muestran nuevos conceptos para las herramientas de front-end:

  • Registro del lado cliente: las herramientas se registran en el cliente mediante AIFunctionFactory.Create() y se pasan a AsAIAgent()
  • Captura automática: las herramientas se capturan y envían automáticamente a través de ChatAgentRunOptions.ChatOptions.Tools

Cómo funcionan las herramientas de front-end

flujo del lado del servidor

El servidor no conoce los detalles de implementación de las herramientas de front-end. Solo sabe lo siguiente:

  1. Nombres y descripciones de herramientas (desde el registro de cliente)
  2. Esquemas de parámetros
  3. Cuándo solicitar la ejecución de la herramienta

Cuando el agente de IA decide llamar a una herramienta de front-end:

  1. El servidor envía una solicitud de llamada de herramienta al cliente a través de SSE
  2. El servidor espera a que el cliente ejecute la herramienta y devuelva resultados
  3. El servidor incorpora los resultados en el contexto del agente
  4. El agente continúa procesando los resultados de la herramienta

Flujo del lado del cliente

El cliente controla la ejecución de la herramienta de front-end:

  1. Recibe FunctionCallContent desde el servidor que indica una solicitud para llamar a la herramienta
  2. Corresponde el nombre de la herramienta a una función registrada localmente.
  3. Deserializa los parámetros de la petición.
  4. Ejecuta la función localmente.
  5. Serializa el resultado
  6. Devuelve FunctionResultContent al servidor.
  7. Continúa recibiendo respuestas del agente

Salida esperada con herramientas de front-end

Cuando el agente llame a las herramientas de front-end, verá la llamada a la herramienta y dará como resultado la salida 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.

Configuración del servidor para las herramientas de front-end

El servidor no necesita una configuración especial para admitir herramientas de front-end. Utiliza el servidor AG-UI estándar del tutorial Guía de inicio: este se configura automáticamente.

  • Recibe declaraciones de herramientas de front-end durante la conexión de cliente.
  • Solicita la ejecución de la herramienta cuando el agente de IA los necesita.
  • Espera los resultados del cliente
  • Incorpora resultados en la toma de decisiones del agente

Pasos siguientes

Ahora que comprende las herramientas de front-end, puede hacer lo siguiente:

Recursos adicionales

En este tutorial se muestra cómo agregar herramientas de función de front-end a los clientes de AG-UI. Las herramientas de front-end son funciones que se ejecutan en el lado cliente, lo que permite que el agente de IA interactúe con el entorno local del usuario, acceda a datos específicos del cliente o realice operaciones de interfaz de usuario.

Prerrequisitos

Antes de comenzar, asegúrese de que ha completado el tutorial de introducción y que tiene:

  • Python 3.10 o posterior
  • httpx instalado para la funcionalidad del cliente HTTP
  • Conocimientos básicos de la configuración del cliente de AG-UI
  • Servicio OpenAI de Azure configurado

¿Qué son las herramientas de front-end?

Las herramientas de front-end son herramientas de función que:

  • Se definen y registran en el cliente
  • Ejecutar en el entorno del cliente (no en el servidor)
  • Permitir que el agente de IA interactúe con recursos específicos del cliente
  • Proporcionar los resultados al servidor para que el agente los incorpore en las respuestas.

Casos de uso comunes:

  • Lectura de datos del sensor local
  • Acceso al almacenamiento o preferencias del lado cliente
  • Realización de operaciones de interfaz de usuario
  • Interacción con características específicas del dispositivo

Creación de herramientas de front-end

Las herramientas de front-end de Python se definen de forma similar a las herramientas de back-end, pero se registran con el 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}"

Creación de un cliente de AG-UI con herramientas de front-end

Esta es una implementación de cliente completa con herramientas 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())

Cómo funcionan las herramientas de front-end

Flujo de protocolo

  1. Registro de cliente: el cliente envía declaraciones de herramientas (nombres, descripciones, parámetros) al servidor
  2. Orquestación del servidor: el agente de IA decide cuándo llamar a las herramientas de front-end en función de la solicitud de usuario
  3. Solicitud de llamada de herramienta: el servidor envía TOOL_CALL_REQUEST evento al cliente a través de SSE
  4. Ejecución del cliente: el cliente ejecuta la herramienta localmente.
  5. Envío de resultados: el cliente devuelve el resultado al servidor a través de la solicitud POST.
  6. Procesamiento del agente: el servidor incorpora el resultado y continúa la respuesta

Eventos clave

  • TOOL_CALL_REQUEST: El servidor solicita la ejecución de la herramienta frontend
  • TOOL_CALL_RESULT: el cliente envía el resultado de la ejecución (a través de HTTP POST)

Salida 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]

Configuración del servidor

El servidor AG-UI estándar del tutorial de introducción admite automáticamente las herramientas de frontend. No se necesitan cambios en el lado servidor: controla automáticamente la orquestación de herramientas.

Procedimientos recomendados

Security

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"

Tratamiento de errores

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

Operaciones asincrónicas

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

Solución de problemas

Herramientas a las que no se llama

  1. Asegurarse de que las declaraciones de herramientas se envían al servidor
  2. Verificar que las descripciones de la herramienta indiquen claramente el propósito
  3. Comprobación de los registros del servidor para el registro de herramientas

Errores de ejecución

  1. Incluir una gestión de errores completa.
  2. Validación de parámetros antes del procesamiento
  3. Devolver mensajes de error fáciles de entender
  4. Registro de errores para la depuración

Problemas de tipo

  1. Uso de modelos Pydantic para tipos complejos
  2. Conversión de modelos en dicts antes de la serialización
  3. Maneje las conversiones de tipo explícitamente

Pasos siguientes

Recursos adicionales