mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET Workflows - WIP Declarative action update (#1761)
* WIP * Fixed build errors (#1638) Comment and nullable type alignment * Sync to SDK update * Checkpoint * Checkpoint: Tests passing * Checkpoint: EndWorkflow * Add trace * .NET: Azure.AI.Agents Package Split + Initial Extensions (#1657) * Move packages * Update nuget.config * Address Xmldoc * Remove format from branches checks * Address Xmldocs * Add more details to the implementation * Moving Agent logic to ChatClient * Adding Name and Id overrides to AzureAIAgent * Updating extensions * Add GetAiAgent extensions * Adding support for version as name can conflict 409 using the Agents API with same name * Addressing more updates to the extensions * More improvements * Remove debugging code from sample * Address copilot feedback * Apply suggestions from co-pilot code review * Checkpoint * Update Directory.Packages.props Fix package version rollback: Azure.AI.Agents.Persistent (beta-6 => beta-7) * Add project reference * .NET: Add comprehensive unit tests for Microsoft.Agents.AI.AzureAIAgents extension methods (#1786) * Initial plan * Add comprehensive unit test project for Microsoft.Agents.AI.AzureAIAgents Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com> * Add README documenting test project and package dependency requirements Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com> * Fix documentation URL to use learn.microsoft.com Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com> * Bump back AAAP 1.2.0-beta.7 * Address AI generated UT's * Remove UT Readme * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * .NET: Change model to be required just for prompt agent definition specific extensions (#1812) * Remove unneeded model from extensions * Add noop justification * Update Package Nameing: V1 -> AzureAI.Persistent / V2 -> AzureAI (#1829) * Checkpoint for merge * No build errors * .NET: Update Extensions for Strict Agent Definitions + Improvements (#1892) * Update Package Nameing: V1 -> AzureAI.Persistent / V2 -> AzureAI * Update agents and extensions to comply with strict agent definitions * More static updates * Address UT, and ResponseTool support * Improving reusability extensions * Addressing ResponseTools Unit Tests and extension setup * Adapted workaround on breaking AAA with OpenAI 2.6.0 * Small updates * Remove strictness when retrieving agents, improved XmlDocs * Improve sample comments * Update dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/AgentsClientExtensionsTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Address PR comments * Address UT failing * Address Copilot feedback * Address Copilot feedback * Address comment typo * Address PR feedback * Address typo * Add missing Extensions with ChatClientAgentOptions * Address comments --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Updated package version (#1897) * Version update (#1901) * Checkpoint * Updated package version (#1906) * Checkpoint * Checkpoint * Checkpoint * Align with azure ai agent * Update dotnet/samples/GettingStarted/Workflows/Declarative/StudentTeacher/Program.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update dotnet/samples/GettingStarted/Workflows/Declarative/MCPToolApproval/Program.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update dotnet/samples/GettingStarted/Workflows/Declarative/DeepResearch/Program.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Refactored external input * Update dotnet/samples/GettingStarted/Workflows/Declarative/MCPToolApproval/Program.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Agent tools patch * Demos validated * Checkpoint * Hygiene * Checkpoint - Samples * Update dotnet/samples/GettingStarted/Workflows/Declarative/StudentTeacher/Program.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update dotnet/samples/GettingStarted/Workflows/Declarative/StudentTeacher/Program.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Checkpoint * Checkpoint - Deep Research * Update baseline * Update * Typo * Checkpoint * Typos * Sample cleanup * Update dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/AzureAgentProvider.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update dotnet/src/Microsoft.Agents.AI.AzureAI/AgentsClientExtensions.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update dotnet/samples/GettingStarted/Workflows/Declarative/FunctionTools/Program.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update dotnet/samples/GettingStarted/Workflows/Declarative/StudentTeacher/Program.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update dotnet/samples/GettingStarted/Workflows/Declarative/ToolApproval/Program.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update dotnet/samples/GettingStarted/Workflows/Declarative/DeepResearch/Program.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Typo * Typo * Fix input loop * Sample - Function Calling / External Input * Typo * Finessed * Checkpoint * Fix feed * Checkpoint - so close * Ding dong! * "there" *** * Fixup comments * Fix sample * Code analysis * Header * Typo (variableName) * Remove dead code * Skip test (agent api ratchet) * Comment * Update dotnet/samples/GettingStarted/Workflows/Declarative/StudentTeacher/Program.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Typo --------- Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com>
This commit is contained in:
+3
-1
@@ -11,7 +11,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<InjectSharedThrow>true</InjectSharedThrow>
|
||||
<InjectIsExternalInitOnLegacy>true</InjectIsExternalInitOnLegacy>
|
||||
<InjectSharedWorkflowsExecution>true</InjectSharedWorkflowsExecution>
|
||||
<InjectSharedWorkflowsSettings>true</InjectSharedWorkflowsSettings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -5,18 +5,12 @@
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using Azure.AI.Agents.Persistent;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
#if CHECKPOINT_JSON
|
||||
using Microsoft.Agents.AI.Workflows.Checkpointing;
|
||||
#endif
|
||||
using Microsoft.Agents.AI.Workflows.Declarative;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Events;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Kit;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Shared.Workflows;
|
||||
|
||||
namespace Demo.DeclarativeWorkflow;
|
||||
|
||||
@@ -62,52 +56,13 @@ internal sealed class Program
|
||||
|
||||
Notify("\nWORKFLOW: Starting...");
|
||||
|
||||
// Run the workflow, just like any other workflow
|
||||
string input = this.GetWorkflowInput();
|
||||
|
||||
#if CHECKPOINT_JSON
|
||||
// Use a file-system based JSON checkpoint store to persist checkpoints to disk.
|
||||
DirectoryInfo checkpointFolder = Directory.CreateDirectory(Path.Combine(".", $"chk-{DateTime.Now:yyMMdd-hhmmss-ff}"));
|
||||
CheckpointManager checkpointManager = CheckpointManager.CreateJson(new FileSystemJsonCheckpointStore(checkpointFolder));
|
||||
#else
|
||||
// Use an in-memory checkpoint store that will not persist checkpoints beyond the lifetime of the process.
|
||||
CheckpointManager checkpointManager = CheckpointManager.CreateInMemory();
|
||||
#endif
|
||||
|
||||
Checkpointed<StreamingRun> run = await InProcessExecution.StreamAsync(workflow, input, checkpointManager);
|
||||
|
||||
bool isComplete = false;
|
||||
object? response = null;
|
||||
do
|
||||
{
|
||||
ExternalRequest? externalRequest = await this.MonitorAndDisposeWorkflowRunAsync(run, response);
|
||||
if (externalRequest is not null)
|
||||
{
|
||||
Notify("\nWORKFLOW: Yield");
|
||||
|
||||
if (this.LastCheckpoint is null)
|
||||
{
|
||||
throw new InvalidOperationException("Checkpoint information missing after external request.");
|
||||
}
|
||||
|
||||
// Process the external request.
|
||||
response = await this.HandleExternalRequestAsync(externalRequest);
|
||||
|
||||
// Let's resume on an entirely new workflow instance to demonstrate checkpoint portability.
|
||||
workflow = this.CreateWorkflow();
|
||||
|
||||
// Restore the latest checkpoint.
|
||||
Debug.WriteLine($"RESTORE #{this.LastCheckpoint.CheckpointId}");
|
||||
Notify("\nWORKFLOW: Restore");
|
||||
|
||||
run = await InProcessExecution.ResumeStreamAsync(workflow, this.LastCheckpoint, checkpointManager, run.Run.RunId);
|
||||
}
|
||||
else
|
||||
{
|
||||
isComplete = true;
|
||||
}
|
||||
}
|
||||
while (!isComplete);
|
||||
// Execute the workflow: The WorkflowRunner demonstrates how to execute
|
||||
// a workflow, handle the workflow events, and providing external input.
|
||||
// This also includes the ability to checkpoint workflow state and how to
|
||||
// resume execution.
|
||||
await this.Runner.ExecuteAsync(this.CreateWorkflow, input);
|
||||
|
||||
Notify("\nWORKFLOW: Done!\n");
|
||||
}
|
||||
@@ -116,19 +71,16 @@ internal sealed class Program
|
||||
/// Create the workflow from the declarative YAML. Includes definition of the
|
||||
/// <see cref="DeclarativeWorkflowOptions" /> and the associated <see cref="WorkflowAgentProvider"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value assigned to <see cref="IncludeFunctions" /> controls on whether the function
|
||||
/// tools (<see cref="AIFunction"/>) initialized in the constructor are included for auto-invocation.
|
||||
/// </remarks>
|
||||
private Workflow CreateWorkflow()
|
||||
{
|
||||
// Use DeclarativeWorkflowBuilder to build a workflow based on a YAML file.
|
||||
AzureAgentProvider agentProvider = new(this.FoundryEndpoint, new AzureCliCredential())
|
||||
// Create the agent provider that will service agent requests within the workflow.
|
||||
AzureAgentProvider agentProvider = new(new Uri(this.FoundryEndpoint), new AzureCliCredential())
|
||||
{
|
||||
// Functions included here will be auto-executed by the framework.
|
||||
Functions = IncludeFunctions ? this.FunctionMap.Values : null,
|
||||
Functions = this.Functions
|
||||
};
|
||||
|
||||
// Define the workflow options.
|
||||
DeclarativeWorkflowOptions options =
|
||||
new(agentProvider)
|
||||
{
|
||||
@@ -137,31 +89,16 @@ internal sealed class Program
|
||||
//LoggerFactory = null, // Assign to enable logging
|
||||
};
|
||||
|
||||
// Use DeclarativeWorkflowBuilder to build a workflow based on a YAML file.
|
||||
return DeclarativeWorkflowBuilder.Build<string>(this.WorkflowFile, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration key used to identify the Foundry project endpoint.
|
||||
/// </summary>
|
||||
private const string ConfigKeyFoundryEndpoint = "FOUNDRY_PROJECT_ENDPOINT";
|
||||
|
||||
/// <summary>
|
||||
/// Controls on whether the function tools (<see cref="AIFunction"/>) initialized
|
||||
/// in the constructor are included for auto-invocation.
|
||||
/// NOTE: By default, no functions exist as part of this sample.
|
||||
/// </summary>
|
||||
private const bool IncludeFunctions = true;
|
||||
|
||||
private static Dictionary<string, string> NameCache { get; } = [];
|
||||
private static HashSet<string> FileCache { get; } = [];
|
||||
|
||||
private string WorkflowFile { get; }
|
||||
private string? WorkflowInput { get; }
|
||||
private string FoundryEndpoint { get; }
|
||||
private PersistentAgentsClient FoundryClient { get; }
|
||||
private IConfiguration Configuration { get; }
|
||||
private CheckpointInfo? LastCheckpoint { get; set; }
|
||||
private Dictionary<string, AIFunction> FunctionMap { get; }
|
||||
private WorkflowRunner Runner { get; }
|
||||
private IList<AIFunction> Functions { get; }
|
||||
|
||||
private Program(string workflowFile, string? workflowInput)
|
||||
{
|
||||
@@ -170,246 +107,26 @@ internal sealed class Program
|
||||
|
||||
this.Configuration = InitializeConfig();
|
||||
|
||||
this.FoundryEndpoint = this.Configuration[ConfigKeyFoundryEndpoint] ?? throw new InvalidOperationException($"Undefined configuration setting: {ConfigKeyFoundryEndpoint}");
|
||||
this.FoundryClient = new PersistentAgentsClient(this.FoundryEndpoint, new AzureCliCredential());
|
||||
this.FoundryEndpoint = this.Configuration[Application.Settings.FoundryEndpoint] ?? throw new InvalidOperationException($"Undefined configuration setting: {Application.Settings.FoundryEndpoint}");
|
||||
|
||||
List<AIFunction> functions =
|
||||
this.Functions =
|
||||
[
|
||||
// Manually define any custom functions that may be required by agents within the workflow.
|
||||
// By default, this sample does not include any functions.
|
||||
//AIFunctionFactory.Create(),
|
||||
];
|
||||
this.FunctionMap = functions.ToDictionary(f => f.Name);
|
||||
}
|
||||
|
||||
private async Task<ExternalRequest?> MonitorAndDisposeWorkflowRunAsync(Checkpointed<StreamingRun> run, object? response = null)
|
||||
{
|
||||
// Always dispose the run when done.
|
||||
await using IAsyncDisposable disposeRun = run;
|
||||
|
||||
bool hasStreamed = false;
|
||||
string? messageId = null;
|
||||
|
||||
await foreach (WorkflowEvent workflowEvent in run.Run.WatchStreamAsync())
|
||||
{
|
||||
switch (workflowEvent)
|
||||
this.Runner =
|
||||
new(this.Functions)
|
||||
{
|
||||
case ExecutorInvokedEvent executorInvoked:
|
||||
Debug.WriteLine($"EXECUTOR ENTER #{executorInvoked.ExecutorId}");
|
||||
break;
|
||||
|
||||
case ExecutorCompletedEvent executorCompleted:
|
||||
Debug.WriteLine($"EXECUTOR EXIT #{executorCompleted.ExecutorId}");
|
||||
break;
|
||||
|
||||
case DeclarativeActionInvokedEvent actionInvoked:
|
||||
Debug.WriteLine($"ACTION ENTER #{actionInvoked.ActionId} [{actionInvoked.ActionType}]");
|
||||
break;
|
||||
|
||||
case DeclarativeActionCompletedEvent actionComplete:
|
||||
Debug.WriteLine($"ACTION EXIT #{actionComplete.ActionId} [{actionComplete.ActionType}]");
|
||||
break;
|
||||
|
||||
case ExecutorFailedEvent executorFailure:
|
||||
Debug.WriteLine($"STEP ERROR #{executorFailure.ExecutorId}: {executorFailure.Data?.Message ?? "Unknown"}");
|
||||
break;
|
||||
|
||||
case WorkflowErrorEvent workflowError:
|
||||
throw workflowError.Data as Exception ?? new InvalidOperationException("Unexpected failure...");
|
||||
|
||||
case SuperStepCompletedEvent checkpointCompleted:
|
||||
this.LastCheckpoint = checkpointCompleted.CompletionInfo?.Checkpoint;
|
||||
Debug.WriteLine($"CHECKPOINT x{checkpointCompleted.StepNumber} [{this.LastCheckpoint?.CheckpointId ?? "(none)"}]");
|
||||
break;
|
||||
|
||||
case RequestInfoEvent requestInfo:
|
||||
Debug.WriteLine($"REQUEST #{requestInfo.Request.RequestId}");
|
||||
if (response is not null)
|
||||
{
|
||||
ExternalResponse requestResponse = requestInfo.Request.CreateResponse(response);
|
||||
await run.Run.SendResponseAsync(requestResponse);
|
||||
response = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Yield to handle the external request
|
||||
return requestInfo.Request;
|
||||
}
|
||||
break;
|
||||
|
||||
case ConversationUpdateEvent invokeEvent:
|
||||
Debug.WriteLine($"CONVERSATION: {invokeEvent.Data}");
|
||||
break;
|
||||
|
||||
case MessageActivityEvent activityEvent:
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.WriteLine("\nACTIVITY:");
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine(activityEvent.Message.Trim());
|
||||
break;
|
||||
|
||||
case AgentRunUpdateEvent streamEvent:
|
||||
if (!string.Equals(messageId, streamEvent.Update.MessageId, StringComparison.Ordinal))
|
||||
{
|
||||
hasStreamed = false;
|
||||
messageId = streamEvent.Update.MessageId;
|
||||
|
||||
if (messageId is not null)
|
||||
{
|
||||
string? agentId = streamEvent.Update.AgentId;
|
||||
if (agentId is not null)
|
||||
{
|
||||
if (!NameCache.TryGetValue(agentId, out string? realName))
|
||||
{
|
||||
PersistentAgent agent = await this.FoundryClient.Administration.GetAgentAsync(agentId);
|
||||
NameCache[agentId] = agent.Name;
|
||||
realName = agent.Name;
|
||||
}
|
||||
agentId = realName;
|
||||
}
|
||||
agentId ??= nameof(ChatRole.Assistant);
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.Write($"\n{agentId.ToUpperInvariant()}:");
|
||||
Console.ForegroundColor = ConsoleColor.DarkGray;
|
||||
Console.WriteLine($" [{messageId}]");
|
||||
}
|
||||
}
|
||||
|
||||
ChatResponseUpdate? chatUpdate = streamEvent.Update.RawRepresentation as ChatResponseUpdate;
|
||||
switch (chatUpdate?.RawRepresentation)
|
||||
{
|
||||
case MessageContentUpdate messageUpdate:
|
||||
string? fileId = messageUpdate.ImageFileId ?? messageUpdate.TextAnnotation?.OutputFileId;
|
||||
if (fileId is not null && FileCache.Add(fileId))
|
||||
{
|
||||
BinaryData content = await this.FoundryClient.Files.GetFileContentAsync(fileId);
|
||||
await DownloadFileContentAsync(Path.GetFileName(messageUpdate.TextAnnotation?.TextToReplace ?? "response.png"), content);
|
||||
}
|
||||
break;
|
||||
case RequiredActionUpdate actionUpdate:
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.Write($"Calling tool: {actionUpdate.FunctionName}");
|
||||
Console.ForegroundColor = ConsoleColor.DarkGray;
|
||||
Console.WriteLine($" [{actionUpdate.ToolCallId}]");
|
||||
break;
|
||||
}
|
||||
try
|
||||
{
|
||||
Console.ResetColor();
|
||||
Console.Write(streamEvent.Update.Text);
|
||||
hasStreamed |= !string.IsNullOrEmpty(streamEvent.Update.Text);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.ResetColor();
|
||||
}
|
||||
break;
|
||||
|
||||
case AgentRunResponseEvent messageEvent:
|
||||
try
|
||||
{
|
||||
if (hasStreamed)
|
||||
{
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
if (messageEvent.Response.Usage is not null)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.DarkGray;
|
||||
Console.WriteLine($"[Tokens Total: {messageEvent.Response.Usage.TotalTokenCount}, Input: {messageEvent.Response.Usage.InputTokenCount}, Output: {messageEvent.Response.Usage.OutputTokenCount}]");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.ResetColor();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null; // No request to handle
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle request for external input, either from a human or a function tool invocation.
|
||||
/// </summary>
|
||||
private async ValueTask<object> HandleExternalRequestAsync(ExternalRequest request) =>
|
||||
request.Data.TypeId.TypeName switch
|
||||
{
|
||||
// Request for human input
|
||||
_ when request.Data.TypeId.IsMatch<AnswerRequest>() => HandleUserMessageRequest(request.DataAs<AnswerRequest>()!),
|
||||
// Request for function tool invocation. (Only active when functions are defined and IncludeFunctions is true.)
|
||||
_ when request.Data.TypeId.IsMatch<AgentFunctionToolRequest>() => await this.HandleToolRequestAsync(request.DataAs<AgentFunctionToolRequest>()!),
|
||||
// Request for user input, such as function or mcp tool approval
|
||||
_ when request.Data.TypeId.IsMatch<UserInputRequest>() => HandleUserInputRequest(request.DataAs<UserInputRequest>()!),
|
||||
// Unknown request type.
|
||||
_ => throw new InvalidOperationException($"Unsupported external request type: {request.GetType().Name}."),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handle request for human input.
|
||||
/// </summary>
|
||||
private static AnswerResponse HandleUserMessageRequest(AnswerRequest request)
|
||||
{
|
||||
string? userInput;
|
||||
do
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.DarkGreen;
|
||||
Console.Write($"\n{request.Prompt ?? "INPUT:"} ");
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
userInput = Console.ReadLine();
|
||||
}
|
||||
while (string.IsNullOrWhiteSpace(userInput));
|
||||
|
||||
return new AnswerResponse(userInput);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle a function tool request by invoking the specified tools and returning the results.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This handler is only active when <see cref="IncludeFunctions"/> is set to true and
|
||||
/// one or more <see cref="AIFunction"/> instances are defined in the constructor.
|
||||
/// </remarks>
|
||||
private async ValueTask<AgentFunctionToolResponse> HandleToolRequestAsync(AgentFunctionToolRequest request)
|
||||
{
|
||||
Task<FunctionResultContent>[] functionTasks = request.FunctionCalls.Select(functionCall => InvokesToolAsync(functionCall)).ToArray();
|
||||
|
||||
await Task.WhenAll(functionTasks);
|
||||
|
||||
return AgentFunctionToolResponse.Create(request, functionTasks.Select(task => task.Result));
|
||||
|
||||
async Task<FunctionResultContent> InvokesToolAsync(FunctionCallContent functionCall)
|
||||
{
|
||||
AIFunction functionTool = this.FunctionMap[functionCall.Name];
|
||||
AIFunctionArguments? functionArguments = functionCall.Arguments is null ? null : new(functionCall.Arguments.NormalizePortableValues());
|
||||
object? result = await functionTool.InvokeAsync(functionArguments);
|
||||
return new FunctionResultContent(functionCall.CallId, JsonSerializer.Serialize(result));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle request for user input for mcp and function tool approval.
|
||||
/// </summary>
|
||||
private static UserInputResponse HandleUserInputRequest(UserInputRequest request)
|
||||
{
|
||||
return UserInputResponse.Create(request, ProcessRequests());
|
||||
|
||||
IEnumerable<UserInputResponseContent> ProcessRequests()
|
||||
{
|
||||
foreach (UserInputRequestContent approvalRequest in request.InputRequests)
|
||||
{
|
||||
// Here we are explicitly approving all requests.
|
||||
// In a real-world scenario, you would replace this logic to either solicit user approval or implement a more complex approval process.
|
||||
yield return
|
||||
approvalRequest switch
|
||||
{
|
||||
McpServerToolApprovalRequestContent mcpApprovalRequest => mcpApprovalRequest.CreateResponse(approved: true),
|
||||
FunctionApprovalRequestContent functionApprovalRequest => functionApprovalRequest.CreateResponse(approved: true),
|
||||
_ => throw new NotSupportedException($"Unsupported request of type {approvalRequest.GetType().Name}"),
|
||||
};
|
||||
}
|
||||
}
|
||||
#if CHECKPOINT_JSON
|
||||
// Use an json file checkpoint store that will persist checkpoints to the local file system.
|
||||
UseJsonCheckpoints = true
|
||||
#else
|
||||
// Use an in-memory checkpoint store that will not persist checkpoints beyond the lifetime of the process.
|
||||
UseJsonCheckpoints = false
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
private static string? ParseWorkflowFile(string[] args)
|
||||
@@ -516,19 +233,4 @@ internal sealed class Program
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
|
||||
private static async ValueTask DownloadFileContentAsync(string filename, BinaryData content)
|
||||
{
|
||||
string filePath = Path.Combine(Path.GetTempPath(), Path.GetFileName(filename));
|
||||
filePath = Path.ChangeExtension(filePath, ".png");
|
||||
|
||||
await File.WriteAllBytesAsync(filePath, content.ToArray());
|
||||
|
||||
Process.Start(
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "cmd.exe",
|
||||
Arguments = $"/C start {filePath}"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user