Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
MAF-verktygsgodkännande ansvarar fortfarande för att avgöra om ett verktyg kräver godkännande. AG-UI skickar godkännandebegäran till klienten och klientens beslut tillbaka till servern.
För godkännandeprinciper, villkorsregler och allmän säkerhetsvägledning, se Använda funktionsverktyg med godkännanden från människor i loopen.
Kräv godkännande
Omslut MAF-funktionen med ApprovalRequiredAIFunction och exponera agenten normalt:
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);
När modellen anropar verktyget slutför AG-UI-adaptern körningen med ett avbrott i verktygsanropet i stället för att köra funktionen.
Lösa avbrott från en .NET-klient
AGUIChatClient visar avbrottet som ToolApprovalRequestContent. Skapa och skicka ett svar med de vanliga MAF-godkännandetyperna:
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.
}
}
Återanvänd samma AgentSession när du skickar svaret så att klienten kan fortsätta den avbrutna körningen. Använd approved: false för att avvisa anropet. Adaptern konverterar MAF-svaret till den kanoniska AG-UI-återupptagningsdatan.
Nästa steg
Den här handledningen visar hur du implementerar arbetsflöden för människa i processen med AG-UI, där användarna måste godkänna verktygsexekveringar innan de utförs. Detta är viktigt för känsliga åtgärder som finansiella transaktioner, dataändringar eller åtgärder som har betydande konsekvenser.
Förutsättningar
Innan du börjar bör du se till att du har slutfört handledningen för Backend Tool Rendering och förstått:
- Så här skapar du funktionsverktyg
- Så här AG-UI strömmar verktygshändelser
- Grundläggande server- och klientkonfiguration
Vad är Human-in-the-Loop?
Human-in-the-Loop (HITL) är ett mönster där agenten begär användargodkännande innan vissa åtgärder körs. Med AG-UI:
- Agenten genererar verktygsanrop som vanligt
- I stället för att köra omedelbart skickar servern begäranden om godkännande till klienten
- Klienten visar begäran och uppmanar användaren
- Användaren godkänner eller avvisar åtgärden
- Servern tar emot svaret och fortsätter därefter
Benefits
- Säkerhet: Förhindra att oavsiktliga åtgärder utförs
- Transparens: Användarna ser exakt vad agenten vill göra
- Kontroll: Användarna har sista ord om känsliga åtgärder
- Efterlevnad: Uppfylla regulatoriska krav för mänsklig tillsyn
Märkningsverktyg för godkännande
Om du vill kräva godkännande för ett verktyg använder du parametern approval_mode i dekoratören @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"
Godkännandelägen
-
always_require: Begär alltid godkännande före körning -
never_require: Begär aldrig godkännande (standardbeteende) -
conditional: Begär godkännande baserat på vissa villkor (anpassad logik)
Skapa en server med Human-in-the-Loop
Här är en fullständig serverimplementering med verktyg som krävs för godkännande:
"""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)
Viktiga begrepp
-
AgentFrameworkAgentwrapper: Aktiverar AG-UI protokollfunktioner som human-in-the-loop -
require_confirmation=True: Aktiverar arbetsflöde för godkännande för markerade verktyg -
Kontroll på verktygsnivå: Endast verktyg som markerats med
approval_mode="always_require"begär godkännande
Förstå avbrott i godkännanden
När ett verktyg kräver godkännande, avslutas körningen med ett standardiserat AG-UI-avbrott.
Avbrott i godkännande
{
"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"
}
}
}
}
}
]
}
}
Verktygsgodkännande avbryter användningen av reason: "tool_call" och inkluderar en toolCallId. Den slutliga ChatResponseUpdate från AGUIChatClient bevarar värdena för outcome och interrupts i additional_properties.
Interrupt och ResumeEntry är protokolltyper från ag_ui.core, inte Agent Framework-specifika modeller.
CV-format
Återuppta samma tråd med en kanonisk resume matris. Använd accepted: false för att avvisa åtgärden samtidigt som agenten kan fortsätta. Använd status: "cancelled" utan nyttolast för att avbryta den avbrutna körningen.
{
"threadId": "thread-1",
"messages": [],
"resume": [
{
"interruptId": "approval-1",
"status": "resolved",
"payload": {
"accepted": true
}
}
]
}
Klient med godkännande stöd
Här är en klient som använder AGUIChatClient som hanterar begäranden om godkännande:
"""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())
Exempelinteraktion
När servern och klienten körs:
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]
Om användaren avvisar:
Approve this action? (yes/no): no
[Sending approval response: False]
I understand. The transfer has been cancelled and no money was moved.
[Run Finished]
Anpassade bekräftelsemeddelanden
Anpassa godkännande- och bekräftelsemeddelanden i ditt AG-UI-klientgränssnitt vid rendering av godkännandeavbrott från servern. Python AgentFrameworkAgent exponerar begäranden om godkännande och avbryter metadata. Det krävs inte ett bekräftelsestrategiobjekt på serversidan.
Metodtips
Rensa verktygsbeskrivningar
Ange detaljerade beskrivningar så att användarna förstår vad de godkänner:
@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
Finkornigt godkännande
Begär godkännande för enskilda känsliga åtgärder i stället för batchbearbetning:
# 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
Informativa argumenten
Använd beskrivande parameternamn och ange kontext:
@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
Timeouthantering
Ange lämpliga tidsgränser för godkännandebegäranden:
# Client side
async with httpx.AsyncClient(timeout=120.0) as client: # 2 minutes for user to respond
# Handle approval
pass
Selektivt godkännande
Du kan blanda verktyg som kräver godkännande med de som inte gör det:
# 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
Gruppvisa godkännanden och avbrytande
Ett modellsvar kan innehålla både verktyg som krävs för godkännande och verktyg som inte kräver godkännande. Om du löser det synliga avbrottet slutförs även de andra verktygsanropen från den batchen enligt deras godkännandebeslut. Till exempel körs en never_require parallell gren och dess TOOL_CALL_RESULT strömmas vidare i den återupptagna körningen även när den parallella gren som kräver godkännande avvisas.
Att avbryta med status: "cancelled" avbryter återupptagningen av godkännandet och rensar det köade godkännandetillståndet för tråden.
Senare begäranden kan inte dyka upp igen eller köra inaktuella verktygsanrop från den avbrutna batchen.
Nästa steg
Ytterligare resurser
Go stöder AG-UI-arbetsflöden med mänsklig medverkan och verktyg som kräver godkännande. Omslut ett funktionsverktyg med tool.ApprovalRequiredFuncoch var sedan värd för agenten 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
Se AG-UI human-in-the-loop-exemplet för ett fullständigt körbart exempel.