Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
La aprobación de la herramienta MAF sigue siendo responsable de decidir si una herramienta requiere aprobación. AG-UI transporta la solicitud de aprobación al cliente y la decisión del cliente de vuelta al servidor.
Para consultar las políticas de aprobación, las reglas condicionales y la orientación general de seguridad, consulte Uso de herramientas de función con aprobaciones con intervención humana.
Requerimiento de aprobación
Envuelva la función MAF con ApprovalRequiredAIFunction y exponga el agente normalmente:
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
AIFunction deleteFile = AIFunctionFactory.Create(
(string path) => $"Deleted {path}",
name: "delete_file",
description: "Delete a file.");
AITool approvalRequiredTool = new ApprovalRequiredAIFunction(deleteFile);
AIAgent agent = chatClient.AsAIAgent(tools: [approvalRequiredTool]);
app.MapAGUIServer("/", agent);
Cuando el modelo llama a la herramienta, el adaptador de AG-UI finaliza la ejecución con una interrupción de llamada a herramientas en lugar de ejecutar la función.
Resolución de la interrupción desde un cliente de .NET
AGUIChatClient muestra la interrupción como ToolApprovalRequestContent. Cree y envíe una respuesta mediante los tipos de aprobación normales de MAF:
ToolApprovalRequestContent? request = null;
await foreach (AgentResponseUpdate update in
remoteAgent.RunStreamingAsync(messages, session))
{
request ??= update.Contents
.OfType<ToolApprovalRequestContent>()
.FirstOrDefault();
}
if (request is not null)
{
ToolApprovalResponseContent response = request.CreateResponse(approved: true);
ChatMessage resume = new(ChatRole.User, [response]);
await foreach (AgentResponseUpdate update in
remoteAgent.RunStreamingAsync([resume], session))
{
// Process the resumed response.
}
}
Vuelva a usar lo mismo AgentSession al enviar la respuesta para que el cliente pueda continuar la ejecución interrumpida. Use approved: false para rechazar la llamada. El adaptador convierte la respuesta de MAF en la carga útil canónica de reanudación de AG-UI.
Pasos siguientes
En este tutorial se muestra cómo implementar flujos de trabajo humanos en bucle con AG-UI, donde los usuarios deben aprobar las ejecuciones de herramientas antes de que se realicen. Esto es esencial para las operaciones confidenciales, como transacciones financieras, modificaciones de datos o acciones que tienen consecuencias significativas.
Prerequisites
Antes de comenzar, asegúrese de que ha completado el tutorial de representación de la herramienta de back-end y comprenda lo siguiente:
- Cómo crear herramientas de función
- Cómo AG-UI transmite eventos de la herramienta
- Configuración básica del servidor y del cliente
¿Qué es Human-in-the-Loop?
Human-in-the-Loop (HITL) es un patrón en el que el agente solicita la aprobación del usuario antes de ejecutar determinadas operaciones. Con AG-UI:
- El agente genera invocaciones a herramientas como de costumbre.
- En lugar de ejecutarse inmediatamente, el servidor envía solicitudes de aprobación al cliente.
- El cliente muestra la solicitud y solicita al usuario
- El usuario aprueba o rechaza la acción.
- El servidor recibe la respuesta y continúa en consecuencia.
Benefits
- Seguridad: impedir que se ejecuten acciones no deseadas
- Transparencia: los usuarios ven exactamente lo que el agente quiere hacer
- Control: los usuarios tienen la decisión final sobre las operaciones sensibles
- Cumplimiento: cumplir los requisitos normativos para la supervisión humana
Herramientas de Marcado para Aprobación
Para solicitar la aprobación de una herramienta, use el parámetro approval_mode en el decorador @tool.
from agent_framework import tool
from typing import Annotated
from pydantic import Field
@tool(approval_mode="always_require")
def send_email(
to: Annotated[str, Field(description="Email recipient address")],
subject: Annotated[str, Field(description="Email subject line")],
body: Annotated[str, Field(description="Email body content")],
) -> str:
"""Send an email to the specified recipient."""
# Send email logic here
return f"Email sent to {to} with subject '{subject}'"
@tool(approval_mode="always_require")
def delete_file(
filepath: Annotated[str, Field(description="Path to the file to delete")],
) -> str:
"""Delete a file from the filesystem."""
# Delete file logic here
return f"File {filepath} has been deleted"
Modos de aprobación
-
always_require: solicitar siempre la aprobación antes de la ejecución -
never_require: nunca se solicita aprobación (comportamiento predeterminado) -
conditional: solicitud de aprobación basada en determinadas condiciones (lógica personalizada)
Creación de un servidor con human-in-the-loop
Esta es una implementación completa del servidor con herramientas necesarias para aprobación:
"""AG-UI server with human-in-the-loop."""
import os
from typing import Annotated
from agent_framework import Agent, tool
from agent_framework.openai import OpenAIChatCompletionClient
from agent_framework_ag_ui import AgentFrameworkAgent, add_agent_framework_fastapi_endpoint
from azure.identity import AzureCliCredential
from fastapi import FastAPI
from pydantic import Field
# Tools that require approval
@tool(approval_mode="always_require")
def transfer_money(
from_account: Annotated[str, Field(description="Source account number")],
to_account: Annotated[str, Field(description="Destination account number")],
amount: Annotated[float, Field(description="Amount to transfer")],
currency: Annotated[str, Field(description="Currency code")] = "USD",
) -> str:
"""Transfer money between accounts."""
return f"Transferred {amount} {currency} from {from_account} to {to_account}"
@tool(approval_mode="always_require")
def cancel_subscription(
subscription_id: Annotated[str, Field(description="Subscription identifier")],
) -> str:
"""Cancel a subscription."""
return f"Subscription {subscription_id} has been cancelled"
# Regular tools (no approval required)
@tool
def check_balance(
account: Annotated[str, Field(description="Account number")],
) -> str:
"""Check account balance."""
# Simulated balance check
return f"Account {account} balance: $5,432.10 USD"
# 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="BankingAssistant",
instructions="You are a banking assistant. Help users with their banking needs. Always confirm details before performing transfers.",
client=chat_client,
tools=[transfer_money, cancel_subscription, check_balance],
)
# Wrap agent to enable human-in-the-loop
wrapped_agent = AgentFrameworkAgent(
agent=agent,
require_confirmation=True, # Enable human-in-the-loop
)
# Create FastAPI app
app = FastAPI(title="AG-UI Banking Assistant")
add_agent_framework_fastapi_endpoint(app, wrapped_agent, "/")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8888)
Conceptos clave
-
AgentFrameworkAgentcontenedor: habilita características del protocolo AG-UI como human-in-the-loop -
require_confirmation=True: activa el flujo de trabajo de aprobación para las herramientas marcadas. -
Control de nivel de herramienta: solo las herramientas marcadas con
approval_mode="always_require"solicitarán aprobación
Comprender las interrupciones de aprobación
Cuando una herramienta requiere aprobación, la ejecución finaliza con una interrupción de AG-UI canónica.
Interrupción de aprobación
{
"type": "RUN_FINISHED",
"threadId": "thread-1",
"runId": "run-1",
"outcome": {
"type": "interrupt",
"interrupts": [
{
"id": "approval-1",
"reason": "tool_call",
"message": "Approve tool call transfer_money?",
"toolCallId": "call-1",
"responseSchema": {
"type": "object",
"properties": {
"accepted": { "type": "boolean" },
"arguments": { "type": "object" }
},
"required": ["accepted"]
},
"metadata": {
"agent_framework": {
"type": "function_approval_request",
"function_call": {
"call_id": "call-1",
"name": "transfer_money",
"arguments": {
"from_account": "1234567890",
"to_account": "0987654321",
"amount": 500.00,
"currency": "USD"
}
}
}
}
}
]
}
}
Las interrupciones de aprobación de herramientas usan reason: "tool_call" e incluyen una toolCallId. La versión final de ChatResponseUpdate de AGUIChatClient conserva los valores de outcome y interrupts en additional_properties.
Interrupt y ResumeEntry son tipos de protocolo de ag_ui.core, no modelos específicos de Agent Framework.
Formato del currículum
Retome el mismo hilo con una matriz resume canónica. Use accepted: false para rechazar la operación al permitir que el agente continúe. Usa status: "cancelled" sin carga para cancelar la ejecución interrumpida.
{
"threadId": "thread-1",
"messages": [],
"resume": [
{
"interruptId": "approval-1",
"status": "resolved",
"payload": {
"accepted": true
}
}
]
}
Cliente con soporte para aprobación
Este es un cliente que usa AGUIChatClient que controla las solicitudes de aprobación:
"""AG-UI client with human-in-the-loop support."""
import asyncio
import os
from agent_framework import Agent
from agent_framework_ag_ui import AGUIChatClient
def display_approval_request(update) -> None:
"""Display approval request details to the user."""
print("\n\033[93m" + "=" * 60 + "\033[0m")
print("\033[93mAPPROVAL REQUIRED\033[0m")
print("\033[93m" + "=" * 60 + "\033[0m")
# Display tool call details from update contents
for i, content in enumerate(update.contents, 1):
if content.type == "function_approval_request":
function_call = content.function_call
print(f"\nAction {i}:")
print(f" Tool: \033[95m{function_call.name}\033[0m")
print(f" Arguments:")
for key, value in (function_call.arguments or {}).items():
print(f" {key}: {value}")
print("\n\033[93m" + "=" * 60 + "\033[0m")
async def main():
"""Main client loop with approval handling."""
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)
pending_interrupts = []
async for update in agent.run(message, session=thread, stream=True):
# Check if this update carries an approval request.
if any(content.type == "function_approval_request" for content in update.contents):
display_approval_request(update)
if update.text:
print(f"\033[96m{update.text}\033[0m", end="", flush=True)
properties = update.additional_properties or {}
outcome = properties.get("outcome")
if isinstance(outcome, dict) and outcome.get("type") == "interrupt":
pending_interrupts = outcome.get("interrupts", [])
if pending_interrupts:
resume_entries = []
for interrupt in pending_interrupts:
prompt = interrupt.get("message", "Approve this action?")
user_choice = input(f"\n{prompt} (yes/no): ").strip().lower()
resume_entries.append({
"interruptId": interrupt["id"],
"status": "resolved",
"payload": {"accepted": user_choice in ("yes", "y")},
})
print("\nAssistant: ", end="", flush=True)
async for update in agent.run(
[],
session=thread,
stream=True,
options={
"available_interrupts": pending_interrupts,
"resume": resume_entries,
},
):
if update.text:
print(f"\033[96m{update.text}\033[0m", end="", flush=True)
print()
except KeyboardInterrupt:
print("\n\nExiting...")
except Exception as e:
print(f"\n\033[91mError: {e}\033[0m")
if __name__ == "__main__":
asyncio.run(main())
Interacción de ejemplo
Con el servidor y el cliente en ejecución:
User (:q or quit to exit): Transfer $500 from account 1234567890 to account 0987654321
[Run Started]
============================================================
APPROVAL REQUIRED
============================================================
Action 1:
Tool: transfer_money
Arguments:
from_account: 1234567890
to_account: 0987654321
amount: 500.0
currency: USD
============================================================
Approve this action? (yes/no): yes
[Sending approval response: True]
[Tool Result: Transferred 500.0 USD from 1234567890 to 0987654321]
The transfer of $500 from account 1234567890 to account 0987654321 has been completed successfully.
[Run Finished]
Si el usuario rechaza:
Approve this action? (yes/no): no
[Sending approval response: False]
I understand. The transfer has been cancelled and no money was moved.
[Run Finished]
Mensajes de confirmación personalizados
Personalice los mensajes de aprobación y confirmación en la interfaz de usuario de su cliente AG-UI al mostrar las interrupciones de aprobación procedentes del servidor. Python AgentFrameworkAgent pone a disposición las solicitudes de aprobación y los metadatos de interrupción; no acepta un objeto de estrategia de confirmación en el lado del servidor.
Prácticas recomendadas
Borrar descripciones de herramientas
Proporcione descripciones detalladas para que los usuarios comprendan lo que aprueban:
@tool(approval_mode="always_require")
def delete_database(
database_name: Annotated[str, Field(description="Name of the database to permanently delete")],
) -> str:
"""
Permanently delete a database and all its contents.
WARNING: This action cannot be undone. All data in the database will be lost.
Use with extreme caution.
"""
# Implementation
pass
Aprobación detallada
Solicite la aprobación de acciones confidenciales individuales en lugar de procesar por lotes:
# Good: Individual approval per transfer
@tool(approval_mode="always_require")
def transfer_money(...): pass
# Avoid: Batching multiple sensitive operations
# Users should approve each operation separately
Argumentos informativos
Use nombres de parámetro descriptivos y proporcione contexto:
@tool(approval_mode="always_require")
def purchase_item(
item_name: Annotated[str, Field(description="Name of the item to purchase")],
quantity: Annotated[int, Field(description="Number of items to purchase")],
price_per_item: Annotated[float, Field(description="Price per item in USD")],
total_cost: Annotated[float, Field(description="Total cost including tax and shipping")],
) -> str:
"""Purchase items from the store."""
pass
Gestión del tiempo de espera
Establezca los tiempos de espera adecuados para las solicitudes de aprobación:
# Client side
async with httpx.AsyncClient(timeout=120.0) as client: # 2 minutes for user to respond
# Handle approval
pass
Aprobación selectiva
Puede combinar herramientas que requieran aprobación con las que no:
# No approval needed for read-only operations
@tool
def get_account_balance(...): pass
@tool
def list_transactions(...): pass
# Approval required for write operations
@tool(approval_mode="always_require")
def transfer_funds(...): pass
@tool(approval_mode="always_require")
def close_account(...): pass
Aprobaciones y cancelación por lotes
Una respuesta de un modelo puede contener tanto herramientas que requieren aprobación como herramientas que no requieren aprobación. Resolver la interrupción visible también completa las demás llamadas a herramientas de ese mismo lote según sus decisiones de aprobación. Por ejemplo, se ejecuta un elemento hermano never_require y su TOOL_CALL_RESULT se transmite en flujo en la ejecución reanudada incluso cuando se rechaza el elemento hermano que requiere aprobación.
Cancelar con status: "cancelled" interrumpe la reanudación de la aprobación y elimina el estado de aprobación en cola del hilo.
Las solicitudes posteriores no pueden volver a aparecer ni ejecutar llamadas a herramientas obsoletas desde el lote cancelado.
Pasos siguientes
Recursos adicionales
Go admite flujos de AG-UI human-in-the-loop con herramientas que requieren aprobación. Envuelva una herramienta de funciones con tool.ApprovalRequiredFunc, y a continuación aloje el agente a través de aguiprovider.
approveExpense := functool.MustNew(functool.Config{
Name: "approve_expense_report",
Description: "Approve the expense report.",
}, func(ctx context.Context, expenseReportID string) (string, error) {
return fmt.Sprintf("Expense report %s approved", expenseReportID), nil
})
a := foundryprovider.NewAgent(endpoint, token, foundryprovider.ModelDeployment(model), foundryprovider.AgentConfig{
Config: agent.Config{
Tools: []tool.Tool{tool.ApprovalRequiredFunc(approveExpense)},
},
})
Tip
Consulte el ejemplo deAG-UI human-in-the-loop para obtener un ejemplo completo de ejecución.