Files
semantic-kernel/dotnet/samples/KernelSyntaxExamples/Example59_OpenAIFunctionCalling.cs
T
ChrisandGil LaHaye 47b879435d .Net: Experimental Feature - Assistants API (#3446)
### 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]>
2023-11-15 23:03:53 +00:00

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.");
}
}
}
}
}