-
Notifications
You must be signed in to change notification settings - Fork 3.4k
/
Copy pathStep01_Processes.cs
192 lines (167 loc) · 7.79 KB
/
Step01_Processes.cs
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
// Copyright (c) Microsoft. All rights reserved.
using Events;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using SharedSteps;
namespace Step01;
/// <summary>
/// Demonstrate creation of <see cref="KernelProcess"/> and
/// eliciting its response to three explicit user messages.
/// </summary>
public class Step01_Processes(ITestOutputHelper output) : BaseTest(output, redirectSystemConsoleOutput: true)
{
/// <summary>
/// Demonstrates the creation of a simple process that has multiple steps, takes
/// user input, interacts with the chat completion service, and demonstrates cycles
/// in the process.
/// </summary>
/// <returns>A <see cref="Task"/></returns>
[Fact]
public async Task UseSimpleProcessAsync()
{
// Create a kernel with a chat completion service
Kernel kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: TestConfiguration.OpenAI.ChatModelId,
apiKey: TestConfiguration.OpenAI.ApiKey)
.Build();
// Create a process that will interact with the chat completion service
ProcessBuilder process = new("ChatBot");
var introStep = process.AddStepFromType<IntroStep>();
var userInputStep = process.AddStepFromType<ChatUserInputStep>();
var responseStep = process.AddStepFromType<ChatBotResponseStep>();
// Define the behavior when the process receives an external event
process
.OnInputEvent(ChatBotEvents.StartProcess)
.SendEventTo(new ProcessFunctionTargetBuilder(introStep));
// When the intro is complete, notify the userInput step
introStep
.OnFunctionResult(nameof(IntroStep.PrintIntroMessage))
.SendEventTo(new ProcessFunctionTargetBuilder(userInputStep));
// When the userInput step emits an exit event, send it to the end step
userInputStep
.OnEvent(ChatBotEvents.Exit)
.StopProcess();
// When the userInput step emits a user input event, send it to the assistantResponse step
userInputStep
.OnEvent(CommonEvents.UserInputReceived)
.SendEventTo(new ProcessFunctionTargetBuilder(responseStep, parameterName: "userMessage"));
// When the assistantResponse step emits a response, send it to the userInput step
responseStep
.OnEvent(ChatBotEvents.AssistantResponseGenerated)
.SendEventTo(new ProcessFunctionTargetBuilder(userInputStep));
// Build the process to get a handle that can be started
KernelProcess kernelProcess = process.Build();
// Start the process with an initial external event
using var runningProcess = await kernelProcess.StartAsync(kernel, new KernelProcessEvent() { Id = ChatBotEvents.StartProcess, Data = null });
}
/// <summary>
/// The simplest implementation of a process step. IntroStep
/// </summary>
private sealed class IntroStep : KernelProcessStep
{
/// <summary>
/// Prints an introduction message to the console.
/// </summary>
[KernelFunction]
public void PrintIntroMessage()
{
System.Console.WriteLine("Welcome to Processes in Semantic Kernel.\n");
}
}
/// <summary>
/// A step that elicits user input.
/// </summary>
private sealed class ChatUserInputStep : ScriptedUserInputStep
{
public override void PopulateUserInputs(UserInputState state)
{
state.UserInputs.Add("Hello");
state.UserInputs.Add("How tall is the tallest mountain?");
state.UserInputs.Add("How low is the lowest valley?");
state.UserInputs.Add("How wide is the widest river?");
state.UserInputs.Add("exit");
state.UserInputs.Add("This text will be ignored because exit process condition was already met at this point.");
}
public override async ValueTask GetUserInputAsync(KernelProcessStepContext context)
{
var userMessage = this.GetNextUserMessage();
if (string.Equals(userMessage, "exit", StringComparison.OrdinalIgnoreCase))
{
// exit condition met, emitting exit event
await context.EmitEventAsync(new() { Id = ChatBotEvents.Exit, Data = userMessage });
return;
}
// emitting userInputReceived event
await context.EmitEventAsync(new() { Id = CommonEvents.UserInputReceived, Data = userMessage });
}
}
/// <summary>
/// A step that takes the user input from a previous step and generates a response from the chat completion service.
/// </summary>
private sealed class ChatBotResponseStep : KernelProcessStep<ChatBotState>
{
public static class Functions
{
public const string GetChatResponse = nameof(GetChatResponse);
}
/// <summary>
/// The internal state object for the chat bot response step.
/// </summary>
internal ChatBotState? _state;
/// <summary>
/// ActivateAsync is the place to initialize the state object for the step.
/// </summary>
/// <param name="state">An instance of <see cref="ChatBotState"/></param>
/// <returns>A <see cref="ValueTask"/></returns>
public override ValueTask ActivateAsync(KernelProcessStepState<ChatBotState> state)
{
_state = state.State;
return ValueTask.CompletedTask;
}
/// <summary>
/// Generates a response from the chat completion service.
/// </summary>
/// <param name="context">The context for the current step and process. <see cref="KernelProcessStepContext"/></param>
/// <param name="userMessage">The user message from a previous step.</param>
/// <param name="_kernel">A <see cref="Kernel"/> instance.</param>
/// <returns></returns>
[KernelFunction(Functions.GetChatResponse)]
public async Task GetChatResponseAsync(KernelProcessStepContext context, string userMessage, Kernel _kernel)
{
_state!.ChatMessages.Add(new(AuthorRole.User, userMessage));
IChatCompletionService chatService = _kernel.Services.GetRequiredService<IChatCompletionService>();
ChatMessageContent response = await chatService.GetChatMessageContentAsync(_state.ChatMessages).ConfigureAwait(false);
if (response == null)
{
throw new InvalidOperationException("Failed to get a response from the chat completion service.");
}
System.Console.ForegroundColor = ConsoleColor.Yellow;
System.Console.WriteLine($"ASSISTANT: {response.Content}");
System.Console.ResetColor();
// Update state with the response
_state.ChatMessages.Add(response);
// emit event: assistantResponse
await context.EmitEventAsync(new KernelProcessEvent { Id = ChatBotEvents.AssistantResponseGenerated, Data = response });
}
}
/// <summary>
/// The state object for the <see cref="ChatBotResponseStep"/>.
/// </summary>
private sealed class ChatBotState
{
internal ChatHistory ChatMessages { get; } = new();
}
/// <summary>
/// A class that defines the events that can be emitted by the chat bot process. This is
/// not required but used to ensure that the event names are consistent.
/// </summary>
private static class ChatBotEvents
{
public const string StartProcess = "startProcess";
public const string IntroComplete = "introComplete";
public const string AssistantResponseGenerated = "assistantResponseGenerated";
public const string Exit = "exit";
}
}