mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
working e2e orchestration completion.
This commit is contained in:
@@ -26,7 +26,8 @@ AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey)
|
||||
// Set up an AI agent following the standard Microsoft Agent Framework pattern.
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
const string AnalysisInstructions = @"You are a Customer Feedback Analyzer. Your task is to analyze customer survey responses and categorize them accurately.
|
||||
const string AnalysisInstructions = """
|
||||
You are a Customer Feedback Analyzer. Your task is to analyze customer survey responses and categorize them accurately.
|
||||
|
||||
INPUT: You will receive customer feedback text that may include a rating and comments.
|
||||
|
||||
@@ -37,40 +38,37 @@ OUTPUT: Return ONLY ONE category from this list:
|
||||
- Support Incident Status
|
||||
|
||||
CATEGORIZATION RULES:
|
||||
- ""Bug Report"": Technical issues, errors, crashes, features not working as expected
|
||||
- ""General Feedback"": Suggestions, compliments, general comments about the product or service
|
||||
- ""Billing Question"": Payment issues, subscription inquiries, pricing questions, refund requests
|
||||
- ""Support Incident Status"": Follow-ups on existing tickets, status inquiries about previous issues
|
||||
- "Bug Report": Technical issues, errors, crashes, features not working as expected
|
||||
- "General Feedback": Suggestions, compliments, general comments about the product or service
|
||||
- "Billing Question": Payment issues, subscription inquiries, pricing questions, refund requests
|
||||
- "Support Incident Status": Follow-ups on existing tickets, status inquiries about previous issues
|
||||
|
||||
RESPONSE FORMAT: Return only the category name exactly as shown above, with no additional text or explanation.
|
||||
|
||||
Examples:
|
||||
- ""The app crashes when I try to export"" → Bug Report
|
||||
- ""Love the new design! Great work"" → General Feedback
|
||||
- ""Why was I charged twice this month?"" → Billing Question
|
||||
- ""What's the status of ticket #12345?"" → Support Incident Status";
|
||||
- "The app crashes when I try to export" → Bug Report
|
||||
- "Love the new design! Great work" → General Feedback
|
||||
- "Why was I charged twice this month?" → Billing Question
|
||||
- "What's the status of ticket #12345?" → Support Incident Status
|
||||
""";
|
||||
AIAgent agent = client.GetChatClient(deploymentName).CreateAIAgent(JokerInstructions, JokerName);
|
||||
AIAgent agent2 = client.GetChatClient(deploymentName).CreateAIAgent(AnalysisInstructions, "FeedbackAnalysisBot");
|
||||
AIAgent surveyFeedbackAgent = client.GetChatClient(deploymentName).CreateAIAgent(AnalysisInstructions, "FeedbackAnalysisBot");
|
||||
|
||||
SurveyResponseParserExecutor surveyResponseParserExecutor = new();
|
||||
ResponseRouterExecutor responseRouterExecutor = new();
|
||||
|
||||
WorkflowBuilder builder = new(surveyResponseParserExecutor);
|
||||
builder.AddEdge(surveyResponseParserExecutor, agent2);
|
||||
builder.AddEdge(agent2, responseRouterExecutor).WithOutputFrom(responseRouterExecutor);
|
||||
builder.AddEdge(surveyResponseParserExecutor, surveyFeedbackAgent);
|
||||
builder.AddEdge(surveyFeedbackAgent, responseRouterExecutor).WithOutputFrom(responseRouterExecutor);
|
||||
|
||||
var workflow = builder.WithName("HandleSurveyResponse").Build();
|
||||
|
||||
// Configure the function app to host AI agents and workflows in a unified way.
|
||||
// This will automatically generate HTTP API endpoints for agents and workflows.
|
||||
using IHost app = FunctionsApplication
|
||||
.CreateBuilder(args)
|
||||
.ConfigureFunctionsWebApplication()
|
||||
//.ConfigureDurableAgents(op => op.AddAIAgent(agent, timeToLive: TimeSpan.FromHours(1)))
|
||||
.ConfigureDurableOptions(options =>
|
||||
{
|
||||
// Configure workflows
|
||||
options.Workflows.AddWorkflow(workflow);
|
||||
})
|
||||
.Build();
|
||||
app.Run();
|
||||
var functionBuilder = FunctionsApplication.CreateBuilder(args);
|
||||
functionBuilder.ConfigureFunctionsWebApplication().ConfigureDurableOptions(options =>
|
||||
{
|
||||
// Configure workflows
|
||||
options.Workflows.AddWorkflow(workflow);
|
||||
});
|
||||
functionBuilder.Build().Run();
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
POST {{authority}}/api/workflows/HandleSurveyResponse/run
|
||||
Content-Type: text/plain
|
||||
|
||||
Rating: 5. Why was I charged $99 when my plan should be $49? I need a refund for the overcharge
|
||||
Rating: 10. Why was I charged $99 when my plan should be $49? I need a refund for the overcharge
|
||||
|
||||
@@ -68,7 +68,7 @@ internal sealed class BuiltInFunctionExecutor : IFunctionExecutor
|
||||
}
|
||||
}
|
||||
|
||||
if (durableTaskClient is null && context.FunctionDefinition.EntryPoint != BuiltInFunctions.RunWorkflowOrechstrtationFunctionEntryPoint)
|
||||
if (durableTaskClient is null)
|
||||
{
|
||||
// This is not expected to happen since all built-in functions are
|
||||
// expected to have a Durable Task client binding.
|
||||
@@ -117,11 +117,6 @@ internal sealed class BuiltInFunctionExecutor : IFunctionExecutor
|
||||
|
||||
if (context.FunctionDefinition.EntryPoint == BuiltInFunctions.RunWorkflowOrechstrtationHttpFunctionEntryPoint)
|
||||
{
|
||||
//if (httpRequestData == null)
|
||||
//{
|
||||
// throw new InvalidOperationException($"HTTP request data binding is missing for the invocation {context.InvocationId}.");
|
||||
//}
|
||||
|
||||
if (httpRequestData == null)
|
||||
{
|
||||
throw new InvalidOperationException($"HTTP request data binding is missing for the invocation {context.InvocationId}.");
|
||||
|
||||
@@ -23,7 +23,6 @@ internal static class BuiltInFunctions
|
||||
internal static readonly string RunAgentHttpFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(RunAgentHttpAsync)}";
|
||||
internal static readonly string RunAgentEntityFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(InvokeAgentAsync)}";
|
||||
internal static readonly string RunWorkflowOrechstrtationHttpFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(RunWorkflowOrechstrtationHttpTriggerAsync)}";
|
||||
internal static readonly string RunWorkflowOrechstrtationFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(RunWorkflowOrchestratorAsync)}";
|
||||
internal static readonly string InvokeWorkflowActivityFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(InvokeWorkflowActivityAsync)}";
|
||||
internal static readonly string RunAgentMcpToolFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(RunMcpToolAsync)}";
|
||||
|
||||
@@ -42,21 +41,6 @@ internal static class BuiltInFunctions
|
||||
return runner.ExecuteActivityAsync(activityFunctionName, input, functionContext);
|
||||
}
|
||||
|
||||
public static async Task<List<string>> RunWorkflowOrchestratorAsync(string taskOrchestrationContext, FunctionContext functionsContext)
|
||||
{
|
||||
var logger = functionsContext.GetLogger("BuiltInFunctions");
|
||||
var outputs = new List<string>();
|
||||
const string WorkflowName = "MyTestWorkflow"; // to do: get from TaskOrchestrationContext
|
||||
if (logger.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
logger.LogInformation("Orchestrator {WorkflowName} is executing. Input: {Input}", WorkflowName, taskOrchestrationContext);
|
||||
}
|
||||
|
||||
//var runner = functionsContext.InstanceServices.GetService<DurableWorkflowRunner>();
|
||||
//await runner!.RunAsync(null, WorkflowName);
|
||||
return outputs;
|
||||
}
|
||||
|
||||
// Exposed as an entity trigger via AgentFunctionsProvider
|
||||
public static Task<string> InvokeAgentAsync(
|
||||
[DurableClient] DurableTaskClient client,
|
||||
@@ -86,14 +70,12 @@ internal static class BuiltInFunctions
|
||||
[DurableClient] DurableTaskClient client,
|
||||
FunctionContext context)
|
||||
{
|
||||
// to do: Retrieve the workflow and execute it.
|
||||
var workflowName = context.FunctionDefinition.Name.Replace("http-", "");
|
||||
var inputMessage = await req.ReadAsStringAsync();
|
||||
//string instanceId = await client.ScheduleNewOrchestrationInstanceAsync("dafx-MyTestWorkflow");
|
||||
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync("WorkflowRunnerOrchestration", new DuableWorkflowRunRequest { WorkflowName = workflowName, Input = inputMessage! }); //OrchFunction"); // dafx-MyTestWorkflow");
|
||||
|
||||
HttpResponseData response = req.CreateResponse(HttpStatusCode.OK);
|
||||
await response.WriteStringAsync($"InvokeWorkflowOrechstrtationAsync is invoked for {workflowName}.{instanceId}");
|
||||
HttpResponseData response = req.CreateResponse(HttpStatusCode.Accepted);
|
||||
await response.WriteStringAsync($"InvokeWorkflowOrechstrtationAsync is invoked for {workflowName}. Orchestration instanceId: {instanceId}");
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ using Microsoft.DurableTask.Worker;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.Agents.AI.Hosting.AzureFunctions;
|
||||
|
||||
@@ -68,7 +69,6 @@ public static class DurableOptionsExtensions
|
||||
builder.UseWhen<BuiltInFunctionExecutionMiddleware>(static context =>
|
||||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentHttpFunctionEntryPoint, StringComparison.Ordinal) ||
|
||||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentMcpToolFunctionEntryPoint, StringComparison.Ordinal) ||
|
||||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunWorkflowOrechstrtationFunctionEntryPoint, StringComparison.Ordinal) ||
|
||||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunWorkflowOrechstrtationHttpFunctionEntryPoint, StringComparison.Ordinal) ||
|
||||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.InvokeWorkflowActivityFunctionEntryPoint, StringComparison.Ordinal) ||
|
||||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentEntityFunctionEntryPoint, StringComparison.Ordinal));
|
||||
@@ -85,9 +85,15 @@ public static class DurableOptionsExtensions
|
||||
{
|
||||
throw new InvalidOperationException("FunctionContext is not available in the orchestration context.");
|
||||
}
|
||||
|
||||
var logger = tc.CreateReplaySafeLogger("WorkflowRunnerOrchestration");
|
||||
DurableWorkflowRunner runner = functionContext.InstanceServices.GetRequiredService<DurableWorkflowRunner>();
|
||||
return await runner.RunWorkflowOrchestrationAsync(tc, inputBindingData).ConfigureAwait(false);
|
||||
var orchestrationResult = await runner.RunWorkflowOrchestrationAsync(tc, inputBindingData, logger);
|
||||
if (logger.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
logger.LogInformation("Durable workflow orchestration completed. Result:{Result}", string.Join(",", orchestrationResult));
|
||||
}
|
||||
|
||||
return orchestrationResult;
|
||||
}));
|
||||
|
||||
builder.Services.AddSingleton<IFunctionMetadataTransformer, DurableWorkflowFunctionMetadataTransformer>();
|
||||
|
||||
@@ -21,23 +21,23 @@ internal sealed class DurableWorkflowRunner
|
||||
this._options = durableOptions.Workflows;
|
||||
}
|
||||
|
||||
internal async Task<List<string>> RunWorkflowOrchestrationAsync(TaskOrchestrationContext context, DuableWorkflowRunRequest input)
|
||||
internal async Task<List<string>> RunWorkflowOrchestrationAsync(TaskOrchestrationContext taskOrchestrationContext, DuableWorkflowRunRequest input, ILogger logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
ArgumentNullException.ThrowIfNull(taskOrchestrationContext);
|
||||
ArgumentNullException.ThrowIfNull(input);
|
||||
|
||||
string workflowName = input.WorkflowName;
|
||||
|
||||
this._logger.LogAttemptingToRunWorkflow(workflowName);
|
||||
logger.LogAttemptingToRunWorkflow(workflowName);
|
||||
|
||||
if (!this._options.Workflows.TryGetValue(workflowName, out Workflow? wf))
|
||||
{
|
||||
throw new InvalidOperationException($"Workflow '{workflowName}' not found.");
|
||||
}
|
||||
|
||||
this._logger.LogRunningWorkflow(wf.Name);
|
||||
logger.LogRunningWorkflow(wf.Name);
|
||||
|
||||
var result = await this.RunExecutorsInWorkFlowAsync(context, wf, input.Input).ConfigureAwait(false);
|
||||
var result = await this.RunExecutorsInWorkFlowAsync(taskOrchestrationContext, wf, input.Input, logger);
|
||||
|
||||
return [result];
|
||||
}
|
||||
@@ -45,16 +45,17 @@ internal sealed class DurableWorkflowRunner
|
||||
private async Task<string> RunExecutorsInWorkFlowAsync(
|
||||
TaskOrchestrationContext taskOrchestrationContext,
|
||||
Workflow workflow,
|
||||
string initialInput)
|
||||
string initialInput,
|
||||
ILogger logger)
|
||||
{
|
||||
List<string> executorResult = [];
|
||||
|
||||
if (this._logger.IsEnabled(LogLevel.Information))
|
||||
if (logger.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
foreach (WorkflowExecutorInfo executorInfo in WorkflowHelper.GetExecutorsFromWorkflowInOrder(workflow))
|
||||
{
|
||||
string triggerName = this.BuildTriggerName(workflow.Name!, executorInfo.ExecutorId);
|
||||
this._logger.LogInformation(
|
||||
logger.LogInformation(
|
||||
" Scheduling executor '{ExecutorId}' (IsAgentic: {IsAgentic}) with trigger name '{TriggerName}'",
|
||||
executorInfo.ExecutorId,
|
||||
executorInfo.IsAgenticExecutor,
|
||||
@@ -69,7 +70,7 @@ internal sealed class DurableWorkflowRunner
|
||||
else
|
||||
{
|
||||
string AgentName = this.GetAgentNameFromExecutorId(workflow.Name!, executorInfo.ExecutorId);
|
||||
this._logger.LogInformation(
|
||||
logger.LogInformation(
|
||||
" Invoking agentic executor '{ExecutorId}'",
|
||||
AgentName);
|
||||
DurableAIAgent agent = taskOrchestrationContext.GetAgent(AgentName);
|
||||
@@ -78,7 +79,7 @@ internal sealed class DurableWorkflowRunner
|
||||
AgentThread destinationThread = agent.GetNewThread();
|
||||
var agentResponse = await agent.RunAsync(input, destinationThread);
|
||||
executorResult.Add(agentResponse.Text);
|
||||
this._logger.LogInformation(
|
||||
logger.LogInformation(
|
||||
"Agentic executor '{ExecutorId}' completed with response: {AgentResponse}",
|
||||
AgentName,
|
||||
agentResponse);
|
||||
@@ -162,7 +163,7 @@ internal sealed class DurableWorkflowRunner
|
||||
"activity-run",
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
// Create a minimal workflow context for the executor
|
||||
// Create a minimal workflow taskOrchestrationContext for the executor
|
||||
MinimalActivityContext context = new(executorPair.Key);
|
||||
|
||||
// Execute the executor with the input
|
||||
|
||||
+1
-2
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.DurableTask;
|
||||
using Microsoft.Azure.Functions.Worker.Builder;
|
||||
@@ -38,7 +38,6 @@ public static class FunctionsApplicationBuilderExtensions
|
||||
builder.UseWhen<BuiltInFunctionExecutionMiddleware>(static context =>
|
||||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentHttpFunctionEntryPoint, StringComparison.Ordinal) ||
|
||||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentMcpToolFunctionEntryPoint, StringComparison.Ordinal) ||
|
||||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunWorkflowOrechstrtationFunctionEntryPoint, StringComparison.Ordinal) ||
|
||||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunWorkflowOrechstrtationHttpFunctionEntryPoint, StringComparison.Ordinal) ||
|
||||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.InvokeWorkflowActivityFunctionEntryPoint, StringComparison.Ordinal) ||
|
||||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentEntityFunctionEntryPoint, StringComparison.Ordinal));
|
||||
|
||||
Reference in New Issue
Block a user