Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
A aprovação da ferramenta MAF permanece responsável por decidir se uma ferramenta requer aprovação. AG-UI transporta a solicitação de aprovação para o cliente e a decisão do cliente de volta para o servidor.
Para políticas de aprovação, regras condicionais e diretrizes gerais de segurança, consulte Usar ferramentas de função com aprovações humanas no loop.
Exigir aprovação
Envolva a função MAF com ApprovalRequiredAIFunction e exponha o 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);
Quando o modelo chama a ferramenta, o adaptador AG-UI termina a execução com uma interrupção de chamada de ferramenta em vez de executar a função.
Resolver a interrupção de um cliente .NET
AGUIChatClient exibe a interrupção como ToolApprovalRequestContent. Crie e envie uma resposta usando os tipos normais de aprovação do 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.
}
}
Reutilize o mesmo AgentSession ao enviar a resposta para que o cliente possa continuar a execução interrompida. Use approved: false para rejeitar a chamada. O adaptador converte a resposta MAF para o payload canônico de retomada do AG-UI.
Próximas Etapas
Este tutorial mostra como implementar fluxos de trabalho humanos no loop com a interface do usuário do AG, em que os usuários devem aprovar execuções de ferramentas antes de serem executadas. Isso é essencial para operações confidenciais, como transações financeiras, modificações de dados ou ações que têm consequências significativas.
Prerequisites
Antes de começar, verifique se você concluiu o tutorial de Renderização da Ferramenta de Back-end e entenda:
- Como criar ferramentas de função
- Como AG-UI transmite eventos da ferramenta
- Configuração básica do servidor e do cliente
O que é Human-in-the-Loop?
HITL (Human-in-the-Loop) é um padrão em que o agente solicita a aprovação do usuário antes de executar determinadas operações. Com AG-UI:
- O agente gera chamadas de ferramenta como de costume
- Em vez de executar imediatamente, o servidor envia solicitações de aprovação para o cliente
- O cliente exibe a solicitação e solicita uma ação do usuário
- O usuário aprova ou rejeita a ação
- O servidor recebe a resposta e continua de acordo
Benefits
- Segurança: impedir que ações não intencionais sejam executadas
- Transparência: os usuários veem exatamente o que o agente quer fazer
- Controle: os usuários têm a palavra final sobre operações confidenciais
- Conformidade: atender aos requisitos regulatórios para supervisão humana
Ferramentas de marcação para aprovação
Para requerer aprovação de uma ferramenta, use o parâmetro approval_mode no 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 aprovação
-
always_require: sempre solicite aprovação antes da execução -
never_require: nunca solicite aprovação (comportamento padrão) -
conditional: solicitar aprovação com base em determinadas condições (lógica personalizada)
Criando um servidor com Human-in-the-Loop
Aqui está uma implementação completa do servidor com ferramentas necessárias para aprovação:
"""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)
Conceitos-chave
-
AgentFrameworkAgentwrapper: habilita recursos de protocolo AG-UI, como human-in-the-loop -
require_confirmation=True: ativa o fluxo de trabalho de aprovação para ferramentas marcadas -
Controle no nível da ferramenta: Somente as ferramentas marcadas com
approval_mode="always_require"solicitarão a aprovação
Noções básicas sobre interrupções de aprovação
Quando uma ferramenta requer aprovação, a execução termina com uma interrupção canônica AG-UI.
Interrupção de aprovação
{
"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"
}
}
}
}
}
]
}
}
As interrupções de aprovação da ferramenta usam reason: "tool_call" e incluem um toolCallId. O ChatResponseUpdate final de AGUIChatClient preserva os valores de outcome e interrupts em additional_properties.
Interrupt e ResumeEntry são tipos de protocolo de ag_ui.core, não modelos específicos do Agent Framework.
Formato do currículo
Retome o mesmo thread com uma matriz canônica resume . Use accepted: false para rejeitar a operação, permitindo que o agente continue. Use status: "cancelled" sem uma carga para cancelar a execução interrompida.
{
"threadId": "thread-1",
"messages": [],
"resume": [
{
"interruptId": "approval-1",
"status": "resolved",
"payload": {
"accepted": true
}
}
]
}
Cliente com suporte de aprovação
Aqui está um cliente usando AGUIChatClient que lida com solicitações de aprovação:
"""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())
Interação de exemplo
Com o servidor e o cliente em execução:
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]
Se o usuário rejeitar:
Approve this action? (yes/no): no
[Sending approval response: False]
I understand. The transfer has been cancelled and no money was moved.
[Run Finished]
Mensagens de confirmação personalizadas
Personalize as mensagens de aprovação e confirmação na interface do usuário do seu cliente AG-UI ao exibir interrupções de aprovação enviadas pelo servidor. A biblioteca Python AgentFrameworkAgent expõe solicitações de aprovação e metadados de interrupção; ela não aceita um objeto de estratégia de confirmação no servidor.
Práticas recomendadas
Limpar descrições de ferramentas
Forneça descrições detalhadas para que os usuários entendam o que estão aprovando:
@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
Aprovação granular
Solicite aprovação para ações confidenciais individuais em vez de envio em lote:
# 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 nomes de parâmetro descritivos e forneça 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
Gerenciamento de tempo limite
Defina os tempos limite apropriados para solicitações de aprovação:
# Client side
async with httpx.AsyncClient(timeout=120.0) as client: # 2 minutes for user to respond
# Handle approval
pass
Aprovação seletiva
Você pode misturar ferramentas que exigem aprovação com aquelas que não:
# 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
Aprovações em lote e cancelamento
Uma resposta do modelo pode conter tanto ferramentas que exigem aprovação quanto ferramentas que não exigem aprovação. Ao solucionar a interrupção visível, as outras chamadas de ferramenta desse lote também são concluídas de acordo com as respectivas decisões de aprovação. Por exemplo, um ramo irmão never_require é executado, e seu TOOL_CALL_RESULT é transmitido na execução retomada mesmo quando o ramo irmão que exige aprovação é rejeitado.
Cancelar com status: "cancelled" interrompe a retomada da aprovação e limpa o estado de aprovação enfileirado da thread.
Solicitações posteriores não podem ressurgir ou executar chamadas de ferramenta obsoletas do lote cancelado.
Próximas Etapas
Recursos adicionais
O Go oferece suporte a fluxos AG-UI com intervenção humana, com ferramentas que exigem aprovação. Encapsule uma ferramenta de função com tool.ApprovalRequiredFunc e, em seguida, hospede o agente por meio 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 o exemplo AG-UI com humano no ciclo para obter um exemplo completo executável.