-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Expand file tree
/
Copy pathProgram.cs
More file actions
249 lines (217 loc) · 10.4 KB
/
Program.cs
File metadata and controls
249 lines (217 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
// Copyright (c) Microsoft. All rights reserved.
#pragma warning disable MAAIW001 // Experimental: HandoffWorkflowBuilder
using System.ComponentModel;
using System.Text.Json;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.Handoff;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
using Microsoft.SemanticKernel.ChatCompletion;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// Queries to simulate user input during the interactive orchestration
List<string> Queries = [
"I'd like to track the status of my first order 123.",
"I want to return another order of mine whose ID is 456 because it arrived damaged.",
];
// This sample compares running handoff orchestrations using
// Semantic Kernel and the Agent Framework.
Console.WriteLine("=== Semantic Kernel Handoff Orchestration ===");
// State to help format the streaming output
bool newAgentTurn = true;
string previousFunctionCallId = string.Empty;
await SKHandoffOrchestration();
Console.WriteLine("\n=== Agent Framework Handoff Agent Workflow ===");
await AFHandoffAgentWorkflow();
# region SKHandoffOrchestration
[KernelFunction]
string SKCheckOrderStatus(string orderId) => $"Order {orderId} is shipped and will arrive in 2-3 days.";
[KernelFunction]
string SKProcessReturn(string orderId, string reason) => $"Return for order {orderId} has been processed successfully.";
[KernelFunction]
string SKProcessRefund(string orderId, string reason) => $"Refund for order {orderId} has been processed successfully.";
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
async Task SKHandoffOrchestration()
{
// Create agents
var triageAgent = GetSKAgent(
instructions: "You are a customer support agent that triages issues.",
name: "TriageAgent",
description: "Handle customer requests.");
var statusAgent = GetSKAgent(
instructions: "You are a customer support agent that checks order status.",
name: "OrderStatusAgent",
description: "Handle order status requests.");
statusAgent.Kernel.Plugins.AddFromFunctions("OrderStatusPlugin", [KernelFunctionFactory.CreateFromMethod(SKCheckOrderStatus)]);
var returnAgent = GetSKAgent(
instructions: "You are a customer support agent that handles order returns.",
name: "OrderReturnAgent",
description: "Handle order return requests.");
returnAgent.Kernel.Plugins.AddFromFunctions("OrderReturnPlugin", [KernelFunctionFactory.CreateFromMethod(SKProcessReturn)]);
var refundAgent = GetSKAgent(
instructions: "You are a customer support agent that handles order refunds.",
name: "OrderRefundAgent",
description: "Handle order refund requests.");
refundAgent.Kernel.Plugins.AddFromFunctions("OrderRefundPlugin", [KernelFunctionFactory.CreateFromMethod(SKProcessRefund)]);
Queue<string> queries = new(Queries);
// Create orchestration with handoffs
HandoffOrchestration orchestration =
new(OrchestrationHandoffs
.StartWith(triageAgent)
.Add(triageAgent, statusAgent, returnAgent, refundAgent)
.Add(statusAgent, triageAgent, "Transfer to this agent if the issue is not status related")
.Add(returnAgent, triageAgent, "Transfer to this agent if the issue is not return related")
.Add(refundAgent, triageAgent, "Transfer to this agent if the issue is not refund related"),
triageAgent,
statusAgent,
returnAgent,
refundAgent)
{
InteractiveCallback = () =>
{
string input = queries.Count > 0 ? queries.Dequeue() : "exit";
Console.WriteLine($"\nUser: {input}");
return ValueTask.FromResult(new ChatMessageContent(AuthorRole.User, input));
},
StreamingResponseCallback = StreamingResultCallback,
};
InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
OrchestrationResult<string> result = await orchestration.InvokeAsync(
"I am a customer that needs help with my two orders",
runtime);
string text = await result.GetValueAsync();
Console.WriteLine($"\nFinal Result: {text}");
await runtime.RunUntilIdleAsync();
}
#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
ChatCompletionAgent GetSKAgent(string instructions, string name, string description)
{
var kernel = Kernel.CreateBuilder().AddAzureOpenAIChatCompletion(deploymentName, endpoint, new AzureCliCredential()).Build();
return new ChatCompletionAgent()
{
Kernel = kernel,
Instructions = instructions,
Description = description,
Name = name
};
}
ValueTask StreamingResultCallback(StreamingChatMessageContent streamedResponse, bool isFinal)
{
if (newAgentTurn)
{
Console.Write($"\n{streamedResponse.AuthorName}: ");
newAgentTurn = false;
}
Console.Write(streamedResponse.Content);
if (streamedResponse.Items.OfType<StreamingFunctionCallUpdateContent>().FirstOrDefault()
is StreamingFunctionCallUpdateContent call)
{
if (call.CallId is not null && previousFunctionCallId != call.CallId)
{
Console.Write($"\nCalling function '{call.Name}' with arguments: ");
previousFunctionCallId = call.CallId;
}
if (!string.IsNullOrEmpty(call.Arguments))
{
Console.Write($"{call.Arguments}");
}
}
if (isFinal)
{
newAgentTurn = true;
previousFunctionCallId = string.Empty;
Console.WriteLine();
}
return ValueTask.CompletedTask;
}
# endregion
# region AFHandoffAgentWorkflow
[Description("Get the order status for a given order ID.")]
static string AFCheckOrderStatus([Description("The order ID to check the status for.")] string orderId)
=> $"Order {orderId} is shipped and will arrive in 2-3 days.";
[Description("Process a return for a given order ID.")]
static string AFProcessReturn(
[Description("The order ID to process the return for.")] string orderId,
[Description("The reason for the return.")] string reason)
=> $"Return for order {orderId} has been processed successfully for the following reason: {reason}.";
[Description("Process a refund for a given order ID.")]
static string AFProcessRefund([Description("The order ID to process the refund for.")] string orderId)
=> $"Refund for order {orderId} has been processed successfully.";
async Task AFHandoffAgentWorkflow()
{
// Create agents
var client = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient();
ChatClientAgent triageAgent = new(client,
instructions: "A customer support agent that triages issues.",
name: "TriageAgent",
description: "Handle customer requests.");
ChatClientAgent statusAgent = new(client,
name: "OrderStatusAgent",
instructions: "Handle order status requests.",
description: "A customer support agent that checks order status.",
tools: [AIFunctionFactory.Create(AFCheckOrderStatus)]);
ChatClientAgent returnAgent = new(client,
name: "OrderReturnAgent",
instructions: "Handle order return requests.",
description: "A customer support agent that handles order returns.",
tools: [AIFunctionFactory.Create(AFProcessReturn)]);
ChatClientAgent refundAgent = new(client,
name: "OrderRefundAgent",
instructions: "Handle order refund requests.",
description: "A customer support agent that handles order refund.",
tools: [AIFunctionFactory.Create(AFProcessRefund)]);
// Create workflow with handoffs
var handoffAgentWorkflow = AgentWorkflowBuilder.CreateHandoffBuilderWith(triageAgent)
.WithHandoffs(triageAgent, [statusAgent, returnAgent, refundAgent])
.WithHandoff(statusAgent, triageAgent, "Transfer to this agent if the issue is not status related")
.WithHandoff(returnAgent, triageAgent, "Transfer to this agent if the issue is not return related")
.WithHandoff(refundAgent, triageAgent, "Transfer to this agent if the issue is not refund related")
.Build();
// Run the workflow
List<ChatMessage> messages = [];
foreach (var query in Queries)
{
Console.WriteLine($"User: {query}");
messages.Add(new(ChatRole.User, query));
await using var run = await InProcessExecution.RunStreamingAsync(handoffAgentWorkflow, messages);
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
string? lastExecutorId = null;
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
{
if (evt is AgentResponseUpdateEvent e)
{
if (string.IsNullOrEmpty(e.Update.Text) && e.Update.Contents.Count == 0)
{
continue;
}
if (e.ExecutorId != lastExecutorId)
{
lastExecutorId = e.ExecutorId;
Console.WriteLine();
Console.Write($"{e.Update.AuthorName}: ");
}
Console.Write(e.Update.Text);
if (e.Update.Contents.OfType<Microsoft.Extensions.AI.FunctionCallContent>().FirstOrDefault()
is Microsoft.Extensions.AI.FunctionCallContent call)
{
Console.WriteLine();
Console.WriteLine($"Calling function '{call.Name}' with arguments: {JsonSerializer.Serialize(call.Arguments)}");
}
}
else if (evt is WorkflowOutputEvent output)
{
Console.WriteLine("\n");
messages.AddRange(output.As<List<ChatMessage>>()!);
}
}
}
}
# endregion