mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET Workflows - Support agent level function invocation for declarative workflow (#1442)
* Checkpoint * Checkpoint * Checkpoint * Good * Namespace * Namespace * Dun * Async Test * AgentId * Portable pattern * Portable2 * Portable3 * Respond to comments * Namespace * Function call selection * ToHashSet * ToHashSet * Updated * Parameter name * Final * Tests
This commit is contained in:
+73
-4
@@ -1,10 +1,13 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Events;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Kit;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
|
||||
using Microsoft.Bot.ObjectModel;
|
||||
using Microsoft.Bot.ObjectModel.Abstractions;
|
||||
@@ -16,23 +19,67 @@ namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
|
||||
internal sealed class InvokeAzureAgentExecutor(InvokeAzureAgent model, WorkflowAgentProvider agentProvider, WorkflowFormulaState state) :
|
||||
DeclarativeActionExecutor<InvokeAzureAgent>(model, state)
|
||||
{
|
||||
public static class Steps
|
||||
{
|
||||
public static string Input(string id) => $"{id}_{nameof(Input)}";
|
||||
public static string Resume(string id) => $"{id}_{nameof(Resume)}";
|
||||
}
|
||||
|
||||
// Input is requested by a message other than ActionExecutorResult.
|
||||
public static bool RequiresInput(object? message) => message is not ActionExecutorResult;
|
||||
|
||||
private AzureAgentUsage AgentUsage => Throw.IfNull(this.Model.Agent, $"{nameof(this.Model)}.{nameof(this.Model.Agent)}");
|
||||
private AzureAgentInput? AgentInput => this.Model.Input;
|
||||
private AzureAgentOutput? AgentOutput => this.Model.Output;
|
||||
|
||||
protected override bool EmitResultEvent => false;
|
||||
protected override bool IsDiscreteAction => false;
|
||||
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await this.InvokeAgentAsync(context, this.GetInputMessages(), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public ValueTask ResumeAsync(IWorkflowContext context, AgentToolResponse message, CancellationToken cancellationToken) =>
|
||||
this.InvokeAgentAsync(context, [message.FunctionResults.ToChatMessage()], cancellationToken);
|
||||
|
||||
public async ValueTask CompleteAsync(IWorkflowContext context, ActionExecutorResult message, CancellationToken cancellationToken)
|
||||
{
|
||||
await context.RaiseCompletionEventAsync(this.Model, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async ValueTask InvokeAgentAsync(IWorkflowContext context, IEnumerable<ChatMessage>? messages, CancellationToken cancellationToken)
|
||||
{
|
||||
string? conversationId = this.GetConversationId();
|
||||
string agentName = this.GetAgentName();
|
||||
string? additionalInstructions = this.GetAdditionalInstructions();
|
||||
bool autoSend = this.GetAutoSendValue();
|
||||
IEnumerable<ChatMessage>? inputMessages = this.GetInputMessages();
|
||||
|
||||
AgentRunResponse agentResponse = await agentProvider.InvokeAgentAsync(this.Id, context, agentName, conversationId, autoSend, additionalInstructions, inputMessages, cancellationToken).ConfigureAwait(false);
|
||||
bool isComplete = true;
|
||||
|
||||
AgentRunResponse agentResponse = await agentProvider.InvokeAgentAsync(this.Id, context, agentName, conversationId, autoSend, additionalInstructions, messages, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (string.IsNullOrEmpty(agentResponse.Text))
|
||||
{
|
||||
// Identify function calls that have no associated result.
|
||||
List<FunctionCallContent> functionCalls = this.GetOrphanedFunctionCalls(agentResponse);
|
||||
isComplete = functionCalls.Count == 0;
|
||||
|
||||
if (!isComplete)
|
||||
{
|
||||
AgentToolRequest toolRequest = new(agentName, functionCalls);
|
||||
await context.SendMessageAsync(toolRequest, targetId: null, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (isComplete)
|
||||
{
|
||||
await context.SendResultMessageAsync(this.Id, result: null, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await this.AssignAsync(this.AgentOutput?.Messages?.Path, agentResponse.Messages.ToTable(), context).ConfigureAwait(false);
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private IEnumerable<ChatMessage>? GetInputMessages()
|
||||
@@ -48,6 +95,28 @@ internal sealed class InvokeAzureAgentExecutor(InvokeAzureAgent model, WorkflowA
|
||||
return userInput?.ToChatMessages();
|
||||
}
|
||||
|
||||
private List<FunctionCallContent> GetOrphanedFunctionCalls(AgentRunResponse agentResponse)
|
||||
{
|
||||
HashSet<string> functionResultIds =
|
||||
[.. agentResponse.Messages
|
||||
.SelectMany(
|
||||
m =>
|
||||
m.Contents
|
||||
.OfType<FunctionResultContent>()
|
||||
.Select(functionCall => functionCall.CallId))];
|
||||
|
||||
List<FunctionCallContent> functionCalls = [];
|
||||
foreach (FunctionCallContent functionCall in agentResponse.Messages.SelectMany(m => m.Contents.OfType<FunctionCallContent>()))
|
||||
{
|
||||
if (!functionResultIds.Contains(functionCall.CallId))
|
||||
{
|
||||
functionCalls.Add(functionCall);
|
||||
}
|
||||
}
|
||||
|
||||
return functionCalls;
|
||||
}
|
||||
|
||||
private string? GetConversationId()
|
||||
{
|
||||
if (this.Model.ConversationId is null)
|
||||
|
||||
Reference in New Issue
Block a user