mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
### 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. --> This is an experimental feature that integrates Open AI assistants into Semantic Kernel: https://devblogs.microsoft.com/semantic-kernel/assistants-the-future-of-semantic-kernel/ ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> This change introduces an assistant framework that integrates with semantic-kernel patterns, particularly around function calling. https://platform.openai.com/docs/api-reference/assistants Assistant usage is showcased in two `KernelSyntaxExamples`. This initial version does not support: - Files - Retrieval - Code Interpreter <img width="308" alt="image" src="https://github.com/microsoft/semantic-kernel/assets/66376200/fc221f53-1717-4767-828c-4f90fd5d0129"> ### 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 😄 --------- Co-authored-by: Gil LaHaye <[email protected]>
207 lines
9.2 KiB
C#
207 lines
9.2 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.SemanticKernel;
|
|
using Microsoft.SemanticKernel.AI.ChatCompletion;
|
|
using Microsoft.SemanticKernel.Connectors.AI.OpenAI;
|
|
using Microsoft.SemanticKernel.Connectors.AI.OpenAI.AzureSdk;
|
|
using Microsoft.SemanticKernel.Functions.OpenAPI.Model;
|
|
using Microsoft.SemanticKernel.Functions.OpenAPI.OpenAI;
|
|
using Microsoft.SemanticKernel.Orchestration;
|
|
using Microsoft.SemanticKernel.Plugins.Core;
|
|
using RepoUtils;
|
|
|
|
/**
|
|
* This example shows how to use OpenAI's function calling capability via the chat completions interface.
|
|
* For more information, see https://platform.openai.com/docs/guides/gpt/function-calling.
|
|
*/
|
|
// ReSharper disable once InconsistentNaming
|
|
public static class Example59_OpenAIFunctionCalling
|
|
{
|
|
public static async Task RunAsync()
|
|
{
|
|
IKernel kernel = await InitializeKernelAsync();
|
|
var chatCompletion = kernel.GetService<IChatCompletion>();
|
|
var chatHistory = chatCompletion.CreateNewChat();
|
|
|
|
OpenAIRequestSettings requestSettings = new()
|
|
{
|
|
// Include all functions registered with the kernel.
|
|
// Alternatively, you can provide your own list of OpenAIFunctions to include.
|
|
Functions = kernel.Functions.GetFunctionViews().Select(f => f.ToOpenAIFunction()).ToList(),
|
|
FunctionCall = "TimePlugin-Date",
|
|
};
|
|
|
|
// Set FunctionCall to the name of a specific function to force the model to use that function.
|
|
await CompleteChatWithFunctionsAsync("What day is today?", chatHistory, chatCompletion, kernel, requestSettings);
|
|
|
|
// Uncomment the samples and run them one at a time
|
|
//await StreamingCompleteChatWithFunctionsAsync("What day is today?", chatHistory, chatCompletion, kernel, requestSettings);
|
|
|
|
// Set FunctionCall to auto to let the model choose the best function to use.
|
|
requestSettings.FunctionCall = OpenAIRequestSettings.FunctionCallAuto;
|
|
//await CompleteChatWithFunctionsAsync("What computer tablets are available for under $200?", chatHistory, chatCompletion, kernel, requestSettings);
|
|
//await StreamingCompleteChatWithFunctionsAsync("What computer tablets are available for under $200?", chatHistory, chatCompletion, kernel, requestSettings);
|
|
}
|
|
|
|
private static async Task<IKernel> InitializeKernelAsync()
|
|
{
|
|
// Create kernel with chat completions service
|
|
IKernel kernel = new KernelBuilder()
|
|
.WithLoggerFactory(ConsoleLogger.LoggerFactory)
|
|
.WithOpenAIChatCompletionService(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey, serviceId: "chat")
|
|
//.WithAzureOpenAIChatCompletionService(TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey, serviceId: "chat")
|
|
.Build();
|
|
|
|
// Load functions to kernel
|
|
kernel.ImportFunctions(new TimePlugin(), "TimePlugin");
|
|
await kernel.ImportOpenAIPluginFunctionsAsync("KlarnaShoppingPlugin", new Uri("https://www.klarna.com/.well-known/ai-plugin.json"), new OpenAIFunctionExecutionParameters());
|
|
|
|
return kernel;
|
|
}
|
|
|
|
private static async Task CompleteChatWithFunctionsAsync(string ask, ChatHistory chatHistory, IChatCompletion chatCompletion, IKernel kernel, OpenAIRequestSettings requestSettings)
|
|
{
|
|
Console.WriteLine($"User message: {ask}");
|
|
chatHistory.AddUserMessage(ask);
|
|
|
|
// Send request and add response to chat history
|
|
var chatResult = (await chatCompletion.GetChatCompletionsAsync(chatHistory, requestSettings))[0];
|
|
chatHistory.AddAssistantMessage(chatResult);
|
|
|
|
await PrintChatResultAsync(chatResult);
|
|
|
|
// Check for function response
|
|
OpenAIFunctionResponse? functionResponse = chatResult.GetOpenAIFunctionResponse();
|
|
if (functionResponse is not null)
|
|
{
|
|
// If the function returned by OpenAI is an SKFunction registered with the kernel,
|
|
// you can invoke it using the following code.
|
|
if (kernel.Functions.TryGetFunctionAndContext(functionResponse, out ISKFunction? func, out ContextVariables? context))
|
|
{
|
|
var result = (await kernel.RunAsync(func, context)).GetValue<object>();
|
|
|
|
string? resultContent = null;
|
|
if (result is RestApiOperationResponse apiResponse)
|
|
{
|
|
resultContent = apiResponse.Content?.ToString();
|
|
}
|
|
else if (result is string str)
|
|
{
|
|
resultContent = str;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(resultContent))
|
|
{
|
|
// Add the function result to chat history
|
|
chatHistory.AddFunctionMessage(resultContent, functionResponse.FullyQualifiedName);
|
|
|
|
// Get another completion
|
|
requestSettings.FunctionCall = OpenAIRequestSettings.FunctionCallNone;
|
|
chatResult = (await chatCompletion.GetChatCompletionsAsync(chatHistory, requestSettings))[0];
|
|
chatHistory.AddAssistantMessage(chatResult);
|
|
|
|
await PrintChatResultAsync(chatResult);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine($"Error: Function {functionResponse.PluginName}.{functionResponse.FunctionName} not found.");
|
|
}
|
|
}
|
|
}
|
|
|
|
private static async Task PrintChatResultAsync(IChatResult chatResult)
|
|
{
|
|
// Check for message response
|
|
var chatMessage = await chatResult.GetChatMessageAsync();
|
|
if (!string.IsNullOrEmpty(chatMessage.Content))
|
|
{
|
|
Console.WriteLine($"Assistant response: {chatMessage.Content}");
|
|
}
|
|
|
|
// Check for function response
|
|
OpenAIFunctionResponse? functionResponse = chatResult.GetOpenAIFunctionResponse();
|
|
if (functionResponse is not null)
|
|
{
|
|
// Print function response details
|
|
Console.WriteLine("Function name: " + functionResponse.FunctionName);
|
|
Console.WriteLine("Plugin name: " + functionResponse.PluginName);
|
|
Console.WriteLine("Arguments: ");
|
|
foreach (var parameter in functionResponse.Parameters)
|
|
{
|
|
Console.WriteLine($"- {parameter.Key}: {parameter.Value}");
|
|
}
|
|
}
|
|
}
|
|
|
|
private static async Task StreamingCompleteChatWithFunctionsAsync(string ask, ChatHistory chatHistory, IChatCompletion chatCompletion, IKernel kernel, OpenAIRequestSettings requestSettings)
|
|
{
|
|
Console.WriteLine($"User message: {ask}");
|
|
chatHistory.AddUserMessage(ask);
|
|
|
|
// Send request
|
|
await foreach (var chatResult in chatCompletion.GetStreamingChatCompletionsAsync(chatHistory, requestSettings))
|
|
{
|
|
StringBuilder chatContent = new();
|
|
await foreach (var message in chatResult.GetStreamingChatMessageAsync())
|
|
{
|
|
if (message.Content is not null)
|
|
{
|
|
Console.Write(message.Content);
|
|
chatContent.Append(message.Content);
|
|
}
|
|
}
|
|
chatHistory.AddAssistantMessage(chatContent.ToString());
|
|
|
|
var functionResponse = await chatResult.GetOpenAIStreamingFunctionResponseAsync();
|
|
|
|
if (functionResponse is not null)
|
|
{
|
|
// Print function response details
|
|
Console.WriteLine("Function name: " + functionResponse.FunctionName);
|
|
Console.WriteLine("Plugin name: " + functionResponse.PluginName);
|
|
Console.WriteLine("Arguments: ");
|
|
foreach (var parameter in functionResponse.Parameters)
|
|
{
|
|
Console.WriteLine($"- {parameter.Key}: {parameter.Value}");
|
|
}
|
|
|
|
// If the function returned by OpenAI is an SKFunction registered with the kernel,
|
|
// you can invoke it using the following code.
|
|
if (kernel.Functions.TryGetFunctionAndContext(functionResponse, out ISKFunction? func, out ContextVariables? context))
|
|
{
|
|
var kernelResult = await kernel.RunAsync(func, context);
|
|
|
|
var result = kernelResult.GetValue<object>();
|
|
|
|
string? resultMessage = null;
|
|
if (result is RestApiOperationResponse apiResponse)
|
|
{
|
|
resultMessage = apiResponse.Content?.ToString();
|
|
}
|
|
else if (result is string str)
|
|
{
|
|
resultMessage = str;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(resultMessage))
|
|
{
|
|
Console.WriteLine(resultMessage);
|
|
|
|
// Add the function result to chat history
|
|
chatHistory.AddAssistantMessage(resultMessage);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine($"Error: Function {functionResponse.PluginName}.{functionResponse.FunctionName} not found.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|