Skip to content

.NET: Approval requests handling in workflows #1973

@jozkee

Description

@jozkee

I couldn't find a way to handle user input requests (human in the loop) in the workflow event loop. I was expecting this could be as simple as just type checking for a type extending WorkflowEvent but I don't see one being returned.

If I just let the loop run, I see an ExecutorFailedEvent with the error:

[Event: ExecutorFailedEvent(Executor = 4c39304655fc445bbee8b75ab7d09ca9, Data: System.InvalidOperationException = System.InvalidOperationException: FunctionApprovalRequestContent found with FunctionCall.CallId(s) 'call_tzJTA2K8mNGedRAc4aSjDxst' that have no matching FunctionApprovalResponseContent.

I tried to send the approval response with TrySendMessageAsync but it didn't reach the IChatClient, aditionally, in a non-streaming case, this is not available. I'm not sure if I'm missing a piece in the puzzle but I think it should have a better UX if that's the case.

using Microsoft.Extensions.AI;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
using Azure.Identity;
using Azure.AI.OpenAI;
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

var endpoint = "https://<my-endpoint>.openai.azure.com";
var deploymentName = "gpt-4o-mini";
IChatClient client = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
    .GetOpenAIResponseClient(deploymentName)
    .AsIChatClient();

AIAgent agentA = new ChatClientAgent(client, options:
    new ChatClientAgentOptions
    {
        ChatOptions = new ChatOptions
        {
            Tools = [new HostedMcpServerTool("deep-wiki", "https://mcp.deepwiki.com/sse")
            {
                // hosted MCP requires tool approval by default,
                // uncommenting this would workaround the approval shortcoming
                // but that's not the point of the sample
                // ApprovalMode = HostedMcpServerToolApprovalMode.NeverRequire
            },
            // Similarly, using below line would avoid approval requirement for client functions.
            //AIFunctionFactory.Create((object e) => $"Echo {e}", "echo_tool")
            new ApprovalRequiredAIFunction(AIFunctionFactory.Create((object e) => $"Echo {e}", "echo_tool")),
        ]},
        Instructions = "Use your tools to address user petitions, truncate the response to 20 words."
    });

AIAgent agentB = new ChatClientAgent(client, options:
    new ChatClientAgentOptions
    {
        Instructions = "You are an expert translator, translate the input text to Spanish and only the text provided, truncate the response to 20 words."
    });

WorkflowBuilder builder = new(agentA);
builder.AddEdge(agentA, agentB);
Workflow workflow = builder.Build();

await using StreamingRun run = await InProcessExecution.StreamAsync(workflow,
    new List<ChatMessage> { new ChatMessage(ChatRole.User,
    "Use the echo_tool to echo the value 42") });
//"Where's the README file for Microsoft.Extensions.AI package located in the dotnet/extensions repository?") }); // for MCP testing

await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
    Console.WriteLine($"[Event: {evt}]");
    if (evt is AgentRunUpdateEvent runUpd)
    {
        foreach (var userInputRequest in runUpd.Update.UserInputRequests)
        {
            Console.WriteLine($"Approval request id: {userInputRequest.Id}");
            if (userInputRequest is FunctionApprovalRequestContent farc)
            {
                // Create the approval response
                FunctionApprovalResponseContent response = farc.CreateResponse(approved: true);
                await run.TrySendMessageAsync(new ChatMessage(ChatRole.User, [response])); // didn't work.
            }
            else
            {
                // Handle MCP approval request
            }
        }
    }
}

Console.WriteLine("Done.");

cc @lokitoth @westey-m @stephentoub @jeffhandley

Related: #938, although that issue focuses on orchestration (AgentWorkflowBuilder).

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

Status

Planned

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions