Skip to content

Commit ca4badf

Browse files
authored
.Net: Added an example how to access ChatHistory in SK function (#9527)
### Motivation and Context <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> Resolves: #7263 This PR contains an example how to access `ChatHistory` in SK function using `Kernel.Data` property or `KernelArguments` and `IAutoFunctionInvocationFilter`. This scenario can be useful with auto function calling, when logic in SK function depends on results from previous messages in the same chat history. ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [x] The code builds clean without any errors or warnings - [x] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone 😄
1 parent ed08b44 commit ca4badf

File tree

2 files changed

+254
-0
lines changed

2 files changed

+254
-0
lines changed
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using Microsoft.SemanticKernel;
4+
using Microsoft.SemanticKernel.ChatCompletion;
5+
using Microsoft.SemanticKernel.Connectors.OpenAI;
6+
7+
namespace ChatCompletion;
8+
9+
/// <summary>
10+
/// This example shows how to access <see cref="ChatHistory"/> object in Semantic Kernel functions using
11+
/// <see cref="Kernel.Data"/> and <see cref="KernelArguments"/>.
12+
/// This scenario can be useful with auto function calling,
13+
/// when logic in SK functions depends on results from previous messages in the same chat history.
14+
/// </summary>
15+
public sealed class ChatHistoryInFunctions(ITestOutputHelper output) : BaseTest(output)
16+
{
17+
/// <summary>
18+
/// This method passes an instance of <see cref="ChatHistory"/> to SK function using <see cref="Kernel.Data"/> property.
19+
/// This approach should be used with caution for cases when Kernel is registered in application as singleton.
20+
/// For singleton Kernel, check examples <see cref="UsingKernelArgumentsAndFilterOption1Async"/> and <see cref="UsingKernelArgumentsAndFilterOption2Async"/>.
21+
/// </summary>
22+
[Fact]
23+
public async Task UsingKernelDataAsync()
24+
{
25+
// Initialize kernel.
26+
var kernel = GetKernel();
27+
28+
// Import plugin.
29+
kernel.ImportPluginFromObject(new DataPlugin(this.Output));
30+
31+
// Get chat completion service.
32+
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
33+
34+
// Initialize chat history with prompt.
35+
var chatHistory = new ChatHistory();
36+
chatHistory.AddUserMessage("I want to get an information about featured products, product reviews and daily summary.");
37+
38+
// Initialize execution settings with enabled auto function calling.
39+
var executionSettings = new OpenAIPromptExecutionSettings
40+
{
41+
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
42+
};
43+
44+
// Set chat history in kernel data to access it in a function.
45+
kernel.Data[nameof(ChatHistory)] = chatHistory;
46+
47+
// Send a request.
48+
var result = await chatCompletionService.GetChatMessageContentAsync(chatHistory, executionSettings, kernel);
49+
50+
// Each function will receive a greater number of messages in chat history, because chat history is populated
51+
// with results of previous functions.
52+
Console.WriteLine($"Result: {result}");
53+
54+
// Output:
55+
// GetFeaturedProducts - Chat History Message Count: 2
56+
// GetProductReviews - Chat History Message Count: 3
57+
// GetDailySalesSummary - Chat History Message Count: 4
58+
// Result: Here's the information you requested...
59+
}
60+
61+
/// <summary>
62+
/// This method passes an instance of <see cref="ChatHistory"/> to SK function using
63+
/// <see cref="KernelArguments"/> and <see cref="IAutoFunctionInvocationFilter"/> filter.
64+
/// The plugin has access to <see cref="KernelArguments"/>, so it's possible to find a chat history in arguments by property name.
65+
/// </summary>
66+
[Fact]
67+
public async Task UsingKernelArgumentsAndFilterOption1Async()
68+
{
69+
// Initialize kernel.
70+
var kernel = GetKernel();
71+
72+
// Import plugin.
73+
kernel.ImportPluginFromObject(new DataPlugin(this.Output));
74+
75+
// Add filter.
76+
kernel.AutoFunctionInvocationFilters.Add(new AutoFunctionInvocationFilter());
77+
78+
// Initialize execution settings with enabled auto function calling.
79+
var executionSettings = new OpenAIPromptExecutionSettings
80+
{
81+
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
82+
};
83+
84+
// Send a request.
85+
var result = await kernel.InvokePromptAsync("I want to get an information about featured products, product reviews and daily summary.", new(executionSettings));
86+
87+
// Each function will receive a greater number of messages in chat history, because chat history is populated
88+
// with results of previous functions.
89+
Console.WriteLine($"Result: {result}");
90+
91+
// Output:
92+
// GetFeaturedProducts - Chat History Message Count: 2
93+
// GetProductReviews - Chat History Message Count: 3
94+
// GetDailySalesSummary - Chat History Message Count: 4
95+
// Result: Here's the information you requested...
96+
}
97+
98+
/// <summary>
99+
/// This method passes an instance of <see cref="ChatHistory"/> to SK function using
100+
/// <see cref="KernelArguments"/> and <see cref="IAutoFunctionInvocationFilter"/> filter.
101+
/// The plugin has access to <see cref="ChatHistory"/> directly, since it's automatically injected from <see cref="KernelArguments"/>
102+
/// into the function by argument name.
103+
/// </summary>
104+
[Fact]
105+
public async Task UsingKernelArgumentsAndFilterOption2Async()
106+
{
107+
// Initialize kernel.
108+
var kernel = GetKernel();
109+
110+
// Import plugin.
111+
kernel.ImportPluginFromObject(new EmailPlugin(this.Output));
112+
113+
// Add filter.
114+
kernel.AutoFunctionInvocationFilters.Add(new AutoFunctionInvocationFilter());
115+
116+
// Initialize execution settings with enabled auto function calling.
117+
var executionSettings = new OpenAIPromptExecutionSettings
118+
{
119+
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
120+
};
121+
122+
// Send a request.
123+
var result = await kernel.InvokePromptAsync("Send email to [email protected]", new(executionSettings));
124+
125+
Console.WriteLine($"Result: {result}");
126+
127+
// Output:
128+
// SendEmail - Chat History Message Count: 2
129+
// Result: Email has been sent to [email protected].
130+
}
131+
132+
#region private
133+
134+
/// <summary>
135+
/// Implementation of <see cref="IAutoFunctionInvocationFilter"/> to set chat history in <see cref="KernelArguments"/>
136+
/// before invoking a function.
137+
/// </summary>
138+
private sealed class AutoFunctionInvocationFilter : IAutoFunctionInvocationFilter
139+
{
140+
public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next)
141+
{
142+
// Set chat history in kernel arguments.
143+
if (context.Arguments is not null)
144+
{
145+
// nameof(ChatHistory) is used for demonstration purposes.
146+
// Any name can be used here, as long as it is effective for the intended purpose.
147+
// However, the same name must be used when retrieving chat history from the KernelArguments instance
148+
// or when the ChatHistory parameter is directly injected into a function.
149+
context.Arguments[nameof(ChatHistory)] = context.ChatHistory;
150+
}
151+
152+
// Invoke next filter in pipeline or function.
153+
await next(context);
154+
}
155+
}
156+
157+
/// <summary>
158+
/// Data plugin for demonstration purposes, where methods accept <see cref="Kernel"/> and <see cref="KernelArguments"/>
159+
/// as parameters.
160+
/// </summary>
161+
private sealed class DataPlugin(ITestOutputHelper output)
162+
{
163+
[KernelFunction]
164+
public List<string> GetFeaturedProducts(Kernel kernel, KernelArguments arguments)
165+
{
166+
var chatHistory = GetChatHistory(kernel.Data) ?? GetChatHistory(arguments);
167+
168+
if (chatHistory is not null)
169+
{
170+
output.WriteLine($"{nameof(GetFeaturedProducts)} - Chat History Message Count: {chatHistory.Count}");
171+
}
172+
173+
return ["Laptop", "Smartphone", "Smartwatch"];
174+
}
175+
176+
[KernelFunction]
177+
public Dictionary<string, List<string>> GetProductReviews(Kernel kernel, KernelArguments arguments)
178+
{
179+
var chatHistory = GetChatHistory(kernel.Data) ?? GetChatHistory(arguments);
180+
181+
if (chatHistory is not null)
182+
{
183+
output.WriteLine($"{nameof(GetProductReviews)} - Chat History Message Count: {chatHistory.Count}");
184+
}
185+
186+
return new()
187+
{
188+
["Laptop"] = ["Excellent performance!", "Battery life could be better."],
189+
["Smartphone"] = ["Amazing camera!", "Very responsive."],
190+
["Smartwatch"] = ["Stylish design", "Could use more apps."],
191+
};
192+
}
193+
194+
[KernelFunction]
195+
public string GetDailySalesSummary(Kernel kernel, KernelArguments arguments)
196+
{
197+
var chatHistory = GetChatHistory(kernel.Data) ?? GetChatHistory(arguments);
198+
199+
if (chatHistory is not null)
200+
{
201+
output.WriteLine($"{nameof(GetDailySalesSummary)} - Chat History Message Count: {chatHistory.Count}");
202+
}
203+
204+
const int OrdersProcessed = 50;
205+
const decimal TotalRevenue = 12345.67m;
206+
207+
return $"Today's Sales: {OrdersProcessed} orders processed, total revenue: ${TotalRevenue}.";
208+
}
209+
210+
private static ChatHistory? GetChatHistory(IDictionary<string, object?> data)
211+
{
212+
if (data.TryGetValue(nameof(ChatHistory), out object? chatHistoryObj) &&
213+
chatHistoryObj is ChatHistory chatHistory)
214+
{
215+
return chatHistory;
216+
}
217+
218+
return null;
219+
}
220+
}
221+
222+
/// <summary>
223+
/// Email plugin for demonstration purposes, where method accepts <see cref="ChatHistory"/> as parameter.
224+
/// </summary>
225+
private sealed class EmailPlugin(ITestOutputHelper output)
226+
{
227+
[KernelFunction]
228+
public string SendEmail(string to, ChatHistory? chatHistory = null)
229+
{
230+
if (chatHistory is not null)
231+
{
232+
output.WriteLine($"{nameof(SendEmail)} - Chat History Message Count: {chatHistory.Count}");
233+
}
234+
235+
// Simulate the email-sending process by notifying the AI model that the email was sent.
236+
return $"Email has been sent to {to}";
237+
}
238+
}
239+
240+
/// <summary>
241+
/// Helper method to initialize <see cref="Kernel"/>.
242+
/// </summary>
243+
private static Kernel GetKernel()
244+
{
245+
return Kernel.CreateBuilder()
246+
.AddOpenAIChatCompletion(
247+
modelId: "gpt-4o",
248+
apiKey: TestConfiguration.OpenAI.ApiKey)
249+
.Build();
250+
}
251+
252+
#endregion
253+
}

dotnet/samples/Concepts/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ dotnet test -l "console;verbosity=detailed" --filter "FullyQualifiedName=ChatCom
5757
- [AzureOpenAI_CustomClient](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/AzureOpenAI_CustomClient.cs)
5858
- [AzureOpenAIWithData_ChatCompletion](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/AzureOpenAIWithData_ChatCompletion.cs)
5959
- [ChatHistoryAuthorName](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/ChatHistoryAuthorName.cs)
60+
- [ChatHistoryInFunctions](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/ChatHistoryInFunctions.cs)
6061
- [ChatHistorySerialization](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/ChatHistorySerialization.cs)
6162
- [Connectors_CustomHttpClient](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/Connectors_CustomHttpClient.cs)
6263
- [Connectors_KernelStreaming](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/Connectors_KernelStreaming.cs)

0 commit comments

Comments
 (0)