Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
L’approbation des outils MAF reste chargée de déterminer si un outil nécessite une approbation. AG-UI transporte la demande d’approbation au client et la décision du client vers le serveur.
Pour les stratégies d’approbation, les règles conditionnelles et les consignes générales de sécurité, consultez Utiliser les outils de fonction avec approbations via une intervention humaine.
Exiger une approbation
Encapsulez la fonction MAF avec ApprovalRequiredAIFunction et exposez l’agent normalement :
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);
Lorsque le modèle appelle l’outil, l’adaptateur AG-UI termine l’exécution avec une interruption d’appel d’outil au lieu d’exécuter la fonction.
Résoudre l’interruption d’un client .NET
AGUIChatClient expose l’interruption sous la forme de ToolApprovalRequestContent. Créez et envoyez une réponse à l’aide des types d’approbation MAF normaux :
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.
}
}
Réutilisez le même AgentSession lors de l’envoi de la réponse afin que le client puisse reprendre l’exécution interrompue. Utilisez approved: false pour rejeter l’appel. L’adaptateur convertit la réponse MAF en charge utile de reprise AG-UI canonique.
Étapes suivantes
Ce tutoriel vous montre comment implémenter des workflows avec intervention humaine avec AG-UI, où les utilisateurs doivent approuver la mise en œuvre des outils avant leur mise en œuvre. Cela est essentiel pour les opérations sensibles telles que les transactions financières, les modifications de données ou les actions qui ont des conséquences significatives.
Prerequisites
Avant de commencer, vérifiez que vous avez terminé le didacticiel De rendu de l’outil principal et comprenez :
- Guide pratique pour créer des outils de fonction
- Comment AG-UI transmet les événements liés aux outils
- Configuration du serveur et du client de base
Qu’est-ce que Human-in-the-Loop ?
Human-in-the-Loop (HITL) est un modèle où l’agent demande l’approbation de l’utilisateur avant d’exécuter certaines opérations. Avec AG-UI :
- L’assistant génère des appels de fonction comme d’habitude
- Au lieu d’exécuter immédiatement, le serveur envoie des demandes d’approbation au client
- Le client affiche la demande et invite l’utilisateur
- L’utilisateur approuve ou rejette l’action
- Le serveur reçoit la réponse et se poursuit en conséquence
Benefits
- Sécurité : empêcher l’exécution d’actions involontaires
- Transparence : les utilisateurs voient exactement ce que l’agent veut faire
- Contrôle : les utilisateurs ont le dernier mot sur les opérations sensibles
- Conformité : Répondre aux exigences réglementaires en matière de surveillance humaine
Outils de marquage pour approbation
Pour exiger l’approbation d’un outil, utilisez le approval_mode paramètre dans le @tool décorateur :
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"
Modes d’approbation
-
always_require: Toujours demander l’approbation avant l’exécution -
never_require: Ne jamais demander d’approbation (comportement par défaut) -
conditional: Demander une approbation basée sur certaines conditions (logique personnalisée)
Création d’un serveur avec Human-in-the-Loop
Voici une implémentation complète du serveur avec les outils nécessaires à l’approbation :
"""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)
Concepts clés
-
AgentFrameworkAgentwrapper : active les fonctionnalités de protocole AG-UI comme human-in-the-loop -
require_confirmation=True: active le flux de travail d’approbation pour les outils marqués -
Contrôle au niveau de l’outil : seuls les outils marqués avec
approval_mode="always_require"devront demander l’approbation
Comprendre les interruptions d’approbation
Lorsqu’un outil nécessite une approbation, l’exécution se termine par une interruption de AG-UI canonique.
Interruption de validation
{
"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"
}
}
}
}
}
]
}
}
L’approbation de l’outil interrompt l’utilisation reason: "tool_call" et inclut un toolCallId. Le ChatResponseUpdate final de AGUIChatClient conserve les valeurs de outcome et de interrupts dans additional_properties.
Interrupt et ResumeEntry sont des types de protocole issus de ag_ui.core, et non des modèles spécifiques à Agent Framework.
Format de CV
Reprendre le même thread avec un tableau canonique resume . Permet accepted: false de rejeter l’opération tout en permettant à l’agent de continuer. Utilisez status: "cancelled" sans charge utile pour annuler l’exécution interrompue.
{
"threadId": "thread-1",
"messages": [],
"resume": [
{
"interruptId": "approval-1",
"status": "resolved",
"payload": {
"accepted": true
}
}
]
}
Client avec fonctionnalité d'approbation
Voici un client AGUIChatClient qui gère les demandes d’approbation :
"""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())
Exemple d’interaction
Avec le serveur et le client en cours d’exécution :
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 l’utilisateur rejette :
Approve this action? (yes/no): no
[Sending approval response: False]
I understand. The transfer has been cancelled and no money was moved.
[Run Finished]
Messages de confirmation personnalisés
Personnalisez les messages d’approbation et de confirmation dans votre interface utilisateur du client AG-UI lors du rendu des interruptions d’approbation du serveur. Le SDK Python AgentFrameworkAgent expose les demandes d’approbation et les métadonnées d’interruption ; il n’accepte pas d’objet de stratégie de confirmation côté serveur.
Meilleures pratiques
Effacer les descriptions des outils
Fournissez des descriptions détaillées afin que les utilisateurs comprennent ce qu’ils approuvent :
@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
Approbation granulaire
Demandez l’approbation pour des actions sensibles individuelles plutôt que par lot :
# 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
Arguments informatifs
Utilisez des noms de paramètres descriptifs et fournissez un contexte :
@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
Gestion du délai d’expiration
Définissez les délais d’expiration appropriés pour les demandes d’approbation :
# Client side
async with httpx.AsyncClient(timeout=120.0) as client: # 2 minutes for user to respond
# Handle approval
pass
Approbation sélective
Vous pouvez combiner des outils qui nécessitent une approbation avec ceux qui ne le font pas :
# 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
Approbations par lots et annulation
Une réponse de modèle peut contenir à la fois des outils et des outils requis par approbation qui ne nécessitent pas d’approbation. Le traitement de l’interruption affichée finalise également les autres appels d’outils de ce lot, selon leurs décisions d’approbation. Par exemple, un élément pair never_require s’exécute et son TOOL_CALL_RESULT est transmis en continu lors de l’exécution reprise, même lorsque l’élément pair nécessitant une approbation est rejeté.
L’annulation avec status: "cancelled" interrompt la reprise de l’approbation et efface l’état d’approbation en file d’attente pour le fil.
Les requêtes ultérieures ne peuvent pas faire réapparaître ni exécuter des appels d’outils obsolètes provenant du lot annulé.
Étapes suivantes
Ressources additionnelles
Go prend en charge les flux avec intervention humaine d’AG-UI, avec des outils nécessitant une approbation. Encapsulez un outil de fonction avec tool.ApprovalRequiredFunc, puis hébergez l’agent via 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
Consultez l’exemple AG-UI human-in-the-loop pour un exemple complet et exécutable.