chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text;
|
||||
using Azure.AI.Inference;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// These examples demonstrate different ways of using chat completion with Azure Foundry or GitHub models.
|
||||
/// Azure AI Foundry: https://ai.azure.com/explore/models
|
||||
/// GitHub Models: https://github.com/marketplace?type=models
|
||||
/// </summary>
|
||||
public class AzureAIInference_ChatCompletion(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task ServicePromptAsync()
|
||||
{
|
||||
Console.WriteLine("======== Azure AI Inference - Chat Completion ========");
|
||||
|
||||
Assert.NotNull(TestConfiguration.AzureAIInference.ApiKey);
|
||||
|
||||
var chatService = new ChatCompletionsClient(
|
||||
endpoint: new Uri(TestConfiguration.AzureAIInference.Endpoint),
|
||||
credential: new Azure.AzureKeyCredential(TestConfiguration.AzureAIInference.ApiKey))
|
||||
.AsIChatClient(TestConfiguration.AzureAIInference.ChatModelId)
|
||||
.AsChatCompletionService();
|
||||
|
||||
Console.WriteLine("Chat content:");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
var chatHistory = new ChatHistory("You are a librarian, expert about books");
|
||||
|
||||
// First user message
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// First assistant message
|
||||
var reply = await chatService.GetChatMessageContentAsync(chatHistory);
|
||||
chatHistory.Add(reply);
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// Second user message
|
||||
chatHistory.AddUserMessage("I love history and philosophy, I'd like to learn something new about Greece, any suggestion");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// Second assistant message
|
||||
reply = await chatService.GetChatMessageContentAsync(chatHistory);
|
||||
chatHistory.Add(reply);
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
/* Output:
|
||||
|
||||
Chat content:
|
||||
------------------------
|
||||
System: You are a librarian, expert about books
|
||||
------------------------
|
||||
User: Hi, I'm looking for book suggestions
|
||||
------------------------
|
||||
Assistant: Sure, I'd be happy to help! What kind of books are you interested in? Fiction or non-fiction? Any particular genre?
|
||||
------------------------
|
||||
User: I love history and philosophy, I'd like to learn something new about Greece, any suggestion?
|
||||
------------------------
|
||||
Assistant: Great! For history and philosophy books about Greece, here are a few suggestions:
|
||||
|
||||
1. "The Greeks" by H.D.F. Kitto - This is a classic book that provides an overview of ancient Greek history and culture, including their philosophy, literature, and art.
|
||||
|
||||
2. "The Republic" by Plato - This is one of the most famous works of philosophy in the Western world, and it explores the nature of justice and the ideal society.
|
||||
|
||||
3. "The Peloponnesian War" by Thucydides - This is a detailed account of the war between Athens and Sparta in the 5th century BCE, and it provides insight into the political and military strategies of the time.
|
||||
|
||||
4. "The Iliad" by Homer - This epic poem tells the story of the Trojan War and is considered one of the greatest works of literature in the Western canon.
|
||||
|
||||
5. "The Histories" by Herodotus - This is a comprehensive account of the Persian Wars and provides a wealth of information about ancient Greek culture and society.
|
||||
|
||||
I hope these suggestions are helpful!
|
||||
------------------------
|
||||
*/
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChatPromptAsync()
|
||||
{
|
||||
StringBuilder chatPrompt = new("""
|
||||
<message role="system">You are a librarian, expert about books</message>
|
||||
<message role="user">Hi, I'm looking for book suggestions</message>
|
||||
""");
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddAzureAIInferenceChatCompletion(
|
||||
modelId: TestConfiguration.AzureAIInference.ChatModelId,
|
||||
endpoint: new Uri(TestConfiguration.AzureAIInference.Endpoint),
|
||||
apiKey: TestConfiguration.AzureAIInference.ApiKey)
|
||||
.Build();
|
||||
|
||||
var reply = await kernel.InvokePromptAsync(chatPrompt.ToString());
|
||||
|
||||
chatPrompt.AppendLine($"<message role=\"assistant\"><![CDATA[{reply}]]></message>");
|
||||
chatPrompt.AppendLine("<message role=\"user\">I love history and philosophy, I'd like to learn something new about Greece, any suggestion</message>");
|
||||
|
||||
reply = await kernel.InvokePromptAsync(chatPrompt.ToString());
|
||||
|
||||
Console.WriteLine(reply);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text;
|
||||
using Azure.AI.Inference;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// These examples demonstrate different ways of using streaming chat completion with Azure Foundry or GitHub models.
|
||||
/// Azure AI Foundry: https://ai.azure.com/explore/models
|
||||
/// GitHub Models: https://github.com/marketplace?type=models
|
||||
/// </summary>
|
||||
public class AzureAIInference_ChatCompletionStreaming(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// This example demonstrates chat completion streaming using OpenAI.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public Task StreamChatAsync()
|
||||
{
|
||||
Console.WriteLine("======== Azure AI Inference - Chat Completion Streaming ========");
|
||||
|
||||
var chatService = new ChatCompletionsClient(
|
||||
endpoint: new Uri(TestConfiguration.AzureAIInference.Endpoint),
|
||||
credential: new Azure.AzureKeyCredential(TestConfiguration.AzureAIInference.ApiKey!))
|
||||
.AsIChatClient(TestConfiguration.AzureAIInference.ChatModelId)
|
||||
.AsChatCompletionService();
|
||||
|
||||
return this.StartStreamingChatAsync(chatService);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates chat completion streaming using OpenAI via the kernel.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task StreamChatPromptAsync()
|
||||
{
|
||||
Console.WriteLine("======== Azure AI Inference - Chat Prompt Completion Streaming ========");
|
||||
|
||||
StringBuilder chatPrompt = new("""
|
||||
<message role="system">You are a librarian, expert about books</message>
|
||||
<message role="user">Hi, I'm looking for book suggestions</message>
|
||||
""");
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddAzureAIInferenceChatCompletion(
|
||||
modelId: TestConfiguration.AzureAIInference.ChatModelId,
|
||||
endpoint: new Uri(TestConfiguration.AzureAIInference.Endpoint),
|
||||
apiKey: TestConfiguration.AzureAIInference.ApiKey)
|
||||
.Build();
|
||||
|
||||
var reply = await StreamMessageOutputFromKernelAsync(kernel, chatPrompt.ToString());
|
||||
|
||||
chatPrompt.AppendLine($"<message role=\"assistant\"><![CDATA[{reply}]]></message>");
|
||||
chatPrompt.AppendLine("<message role=\"user\">I love history and philosophy, I'd like to learn something new about Greece, any suggestion</message>");
|
||||
|
||||
reply = await StreamMessageOutputFromKernelAsync(kernel, chatPrompt.ToString());
|
||||
|
||||
Console.WriteLine(reply);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates how the chat completion service streams text content.
|
||||
/// It shows how to access the response update via StreamingChatMessageContent.Content property
|
||||
/// and alternatively via the StreamingChatMessageContent.Items property.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task StreamTextFromChatAsync()
|
||||
{
|
||||
Console.WriteLine("======== Stream Text from Chat Content ========");
|
||||
|
||||
// Create chat completion service
|
||||
var chatService = new ChatCompletionsClient(
|
||||
endpoint: new Uri(TestConfiguration.AzureAIInference.Endpoint),
|
||||
credential: new Azure.AzureKeyCredential(TestConfiguration.AzureAIInference.ApiKey!))
|
||||
.AsIChatClient(TestConfiguration.AzureAIInference.ChatModelId)
|
||||
.AsChatCompletionService();
|
||||
|
||||
// Create chat history with initial system and user messages
|
||||
ChatHistory chatHistory = new("You are a librarian, an expert on books.");
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions.");
|
||||
chatHistory.AddUserMessage("I love history and philosophy. I'd like to learn something new about Greece, any suggestion?");
|
||||
|
||||
// Start streaming chat based on the chat history
|
||||
await foreach (StreamingChatMessageContent chatUpdate in chatService.GetStreamingChatMessageContentsAsync(chatHistory))
|
||||
{
|
||||
// Access the response update via StreamingChatMessageContent.Content property
|
||||
Console.Write(chatUpdate.Content);
|
||||
|
||||
// Alternatively, the response update can be accessed via the StreamingChatMessageContent.Items property
|
||||
Console.Write(chatUpdate.Items.OfType<StreamingTextContent>().FirstOrDefault());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts streaming chat with the chat completion service.
|
||||
/// </summary>
|
||||
/// <param name="chatCompletionService">The chat completion service instance.</param>
|
||||
private async Task StartStreamingChatAsync(IChatCompletionService chatCompletionService)
|
||||
{
|
||||
Console.WriteLine("Chat content:");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
var chatHistory = new ChatHistory("You are a librarian, expert about books");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// First user message
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// First assistant message
|
||||
await StreamMessageOutputAsync(chatCompletionService, chatHistory, AuthorRole.Assistant);
|
||||
|
||||
// Second user message
|
||||
chatHistory.AddUserMessage("I love history and philosophy, I'd like to learn something new about Greece, any suggestion?");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// Second assistant message
|
||||
await StreamMessageOutputAsync(chatCompletionService, chatHistory, AuthorRole.Assistant);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs the chat history by streaming the message output from the kernel.
|
||||
/// </summary>
|
||||
/// <param name="kernel">The kernel instance.</param>
|
||||
/// <param name="prompt">The prompt message.</param>
|
||||
/// <returns>The full message output from the kernel.</returns>
|
||||
private async Task<string> StreamMessageOutputFromKernelAsync(Kernel kernel, string prompt)
|
||||
{
|
||||
bool roleWritten = false;
|
||||
string fullMessage = string.Empty;
|
||||
|
||||
await foreach (var chatUpdate in kernel.InvokePromptStreamingAsync<StreamingChatMessageContent>(prompt))
|
||||
{
|
||||
if (!roleWritten && chatUpdate.Role.HasValue)
|
||||
{
|
||||
Console.Write($"{chatUpdate.Role.Value}: {chatUpdate.Content}");
|
||||
roleWritten = true;
|
||||
}
|
||||
|
||||
if (chatUpdate.Content is { Length: > 0 })
|
||||
{
|
||||
fullMessage += chatUpdate.Content;
|
||||
Console.Write(chatUpdate.Content);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("\n------------------------");
|
||||
return fullMessage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,414 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json;
|
||||
using Azure.AI.OpenAI.Chat;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
|
||||
using xRetry;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates how to use Azure OpenAI Chat Completion with data.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Set-up instructions:
|
||||
/// <para>1. Upload the following content in Azure Blob Storage in a .txt file.</para>
|
||||
/// <para>You can follow the steps here: <see href="https://learn.microsoft.com/en-us/azure/ai-services/openai/use-your-data-quickstart"/></para>
|
||||
/// <para>
|
||||
/// Emily and David, two passionate scientists, met during a research expedition to Antarctica.
|
||||
/// Bonded by their love for the natural world and shared curiosity,
|
||||
/// they uncovered a groundbreaking phenomenon in glaciology that could
|
||||
/// potentially reshape our understanding of climate change.
|
||||
/// </para>
|
||||
/// 2. Set your secrets:
|
||||
/// <para> dotnet user-secrets set "AzureAISearch:Endpoint" "https://... .search.windows.net"</para>
|
||||
/// <para> dotnet user-secrets set "AzureAISearch:ApiKey" "{Key from your Search service resource}"</para>
|
||||
/// <para> dotnet user-secrets set "AzureAISearch:IndexName" "..."</para>
|
||||
/// </value>
|
||||
public class AzureOpenAIWithData_ChatCompletion(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[RetryFact(typeof(HttpOperationException))]
|
||||
public async Task ExampleWithChatCompletionAsync()
|
||||
{
|
||||
Console.WriteLine("=== Example with Chat Completion ===");
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddAzureOpenAIChatCompletion(
|
||||
TestConfiguration.AzureOpenAI.ChatDeploymentName,
|
||||
TestConfiguration.AzureOpenAI.Endpoint,
|
||||
TestConfiguration.AzureOpenAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
var chatHistory = new ChatHistory();
|
||||
|
||||
// First question without previous context based on uploaded content.
|
||||
var ask = "How did Emily and David meet?";
|
||||
chatHistory.AddUserMessage(ask);
|
||||
|
||||
// Chat Completion example
|
||||
var dataSource = GetAzureSearchDataSource();
|
||||
var promptExecutionSettings = new AzureOpenAIPromptExecutionSettings { AzureChatDataSource = dataSource };
|
||||
|
||||
var chatCompletion = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
var chatMessage = await chatCompletion.GetChatMessageContentAsync(chatHistory, promptExecutionSettings);
|
||||
|
||||
var response = chatMessage.Content!;
|
||||
|
||||
// Output
|
||||
// Ask: How did Emily and David meet?
|
||||
// Response: Emily and David, both passionate scientists, met during a research expedition to Antarctica.
|
||||
Console.WriteLine($"Ask: {ask}");
|
||||
Console.WriteLine($"Response: {response}");
|
||||
|
||||
var citations = GetCitations(chatMessage);
|
||||
|
||||
OutputCitations(citations);
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
// Chat history maintenance
|
||||
chatHistory.AddAssistantMessage(response);
|
||||
|
||||
// Second question based on uploaded content.
|
||||
ask = "What are Emily and David studying?";
|
||||
chatHistory.AddUserMessage(ask);
|
||||
|
||||
// Chat Completion Streaming example
|
||||
Console.WriteLine($"Ask: {ask}");
|
||||
Console.WriteLine("Response: ");
|
||||
|
||||
await foreach (var update in chatCompletion.GetStreamingChatMessageContentsAsync(chatHistory, promptExecutionSettings))
|
||||
{
|
||||
Console.Write(update);
|
||||
|
||||
var streamingCitations = GetCitations(update);
|
||||
|
||||
OutputCitations(streamingCitations);
|
||||
}
|
||||
|
||||
Console.WriteLine(Environment.NewLine);
|
||||
}
|
||||
|
||||
[RetryFact(typeof(HttpOperationException))]
|
||||
public async Task ExampleWithKernelAsync()
|
||||
{
|
||||
Console.WriteLine("=== Example with Kernel ===");
|
||||
|
||||
var ask = "How did Emily and David meet?";
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddAzureOpenAIChatCompletion(
|
||||
TestConfiguration.AzureOpenAI.ChatDeploymentName,
|
||||
TestConfiguration.AzureOpenAI.Endpoint,
|
||||
TestConfiguration.AzureOpenAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
var function = kernel.CreateFunctionFromPrompt("Question: {{$input}}");
|
||||
|
||||
var dataSource = GetAzureSearchDataSource();
|
||||
var promptExecutionSettings = new AzureOpenAIPromptExecutionSettings { AzureChatDataSource = dataSource };
|
||||
|
||||
// First question without previous context based on uploaded content.
|
||||
var response = await kernel.InvokeAsync(function, new(promptExecutionSettings) { ["input"] = ask });
|
||||
|
||||
// Output
|
||||
// Ask: How did Emily and David meet?
|
||||
// Response: Emily and David, both passionate scientists, met during a research expedition to Antarctica.
|
||||
Console.WriteLine($"Ask: {ask}");
|
||||
Console.WriteLine($"Response: {response.GetValue<string>()}");
|
||||
Console.WriteLine();
|
||||
|
||||
// Second question based on uploaded content.
|
||||
ask = "What are Emily and David studying?";
|
||||
response = await kernel.InvokeAsync(function, new(promptExecutionSettings) { ["input"] = ask });
|
||||
|
||||
// Output
|
||||
// Ask: What are Emily and David studying?
|
||||
// Response: They are passionate scientists who study glaciology,
|
||||
// a branch of geology that deals with the study of ice and its effects.
|
||||
Console.WriteLine($"Ask: {ask}");
|
||||
Console.WriteLine($"Response: {response.GetValue<string>()}");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example shows how to use Azure OpenAI Chat Completion with data and function calling.
|
||||
/// Note: Using a data source and function calling is currently not supported in a single request. Enabling both features
|
||||
/// will result in the function calling information being ignored and the operation behaving as if only the data source was provided.
|
||||
/// More information about this limitation here: <see href="https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/openai/Azure.AI.OpenAI/README.md#use-your-own-data-with-azure-openai"/>.
|
||||
/// To address this limitation, consider separating function calling and data source across multiple requests in your solution design.
|
||||
/// The example demonstrates how to implement a retry mechanism for unanswered queries. If the current request uses an Azure Data Source, the logic retries using function calling, and vice versa.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ExampleWithFunctionCallingAsync()
|
||||
{
|
||||
Console.WriteLine("=== Example with Function Calling ===");
|
||||
|
||||
var builder = Kernel.CreateBuilder()
|
||||
.AddAzureOpenAIChatCompletion(
|
||||
TestConfiguration.AzureOpenAI.ChatDeploymentName,
|
||||
TestConfiguration.AzureOpenAI.Endpoint,
|
||||
TestConfiguration.AzureOpenAI.ApiKey);
|
||||
|
||||
// Add retry filter.
|
||||
// This filter will evaluate if the model provided the answer to user's question.
|
||||
// If yes, it will return the result. Otherwise it will try to use Azure Data Source and function calling sequentially until
|
||||
// the requested information is provided. If both sources doesn't contain the requested information, the model will explain that in response.
|
||||
builder.Services.AddSingleton<IFunctionInvocationFilter, FunctionInvocationRetryFilter>();
|
||||
|
||||
var kernel = builder.Build();
|
||||
|
||||
// Import plugin.
|
||||
kernel.ImportPluginFromType<DataPlugin>();
|
||||
|
||||
// Define response schema.
|
||||
// The model evaluates its own answer and provides a boolean flag,
|
||||
// which allows to understand whether the user's question was actually answered or not.
|
||||
// Based on that, it's possible to make a decision whether the source of information should be changed or the response
|
||||
// should be provided back to the user.
|
||||
var responseSchema =
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Message": { "type": "string" },
|
||||
"IsAnswered": { "type": "boolean" },
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
// Define execution settings with response format and initial instructions.
|
||||
var promptExecutionSettings = new AzureOpenAIPromptExecutionSettings
|
||||
{
|
||||
ResponseFormat = "json_object",
|
||||
ChatSystemPrompt =
|
||||
"Provide concrete answers to user questions. " +
|
||||
"If you don't have the information - do not generate it, but respond accordingly. " +
|
||||
$"Use following JSON schema for all the responses: {responseSchema}. "
|
||||
};
|
||||
|
||||
// First question without previous context based on uploaded content.
|
||||
var ask = "How did Emily and David meet?";
|
||||
|
||||
// The answer to the first question is expected to be fetched from Azure Data Source (in this example Azure AI Search).
|
||||
// Azure Data Source is not enabled in initial execution settings, but is configured in retry filter.
|
||||
var response = await kernel.InvokePromptAsync(ask, new(promptExecutionSettings));
|
||||
var modelResult = ModelResult.Parse(response.ToString());
|
||||
|
||||
// Output
|
||||
// Ask: How did Emily and David meet?
|
||||
// Response: Emily and David, both passionate scientists, met during a research expedition to Antarctica [doc1].
|
||||
Console.WriteLine($"Ask: {ask}");
|
||||
Console.WriteLine($"Response: {modelResult?.Message}");
|
||||
|
||||
ask = "Can I have Emily's and David's emails?";
|
||||
|
||||
// The answer to the second question is expected to be fetched from DataPlugin-GetEmails function using function calling.
|
||||
// Function calling is not enabled in initial execution settings, but is configured in retry filter.
|
||||
response = await kernel.InvokePromptAsync(ask, new(promptExecutionSettings));
|
||||
modelResult = ModelResult.Parse(response.ToString());
|
||||
|
||||
// Output
|
||||
// Ask: Can I have their emails?
|
||||
// Response: Emily's email is emily@contoso.com and David's email is david@contoso.com.
|
||||
Console.WriteLine($"Ask: {ask}");
|
||||
Console.WriteLine($"Response: {modelResult?.Message}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AzureSearchChatDataSource"/> class.
|
||||
/// </summary>
|
||||
#pragma warning disable AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
|
||||
private static AzureSearchChatDataSource GetAzureSearchDataSource()
|
||||
{
|
||||
return new AzureSearchChatDataSource
|
||||
{
|
||||
Endpoint = new Uri(TestConfiguration.AzureAISearch.Endpoint),
|
||||
Authentication = DataSourceAuthentication.FromApiKey(TestConfiguration.AzureAISearch.ApiKey),
|
||||
IndexName = TestConfiguration.AzureAISearch.IndexName
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a collection of <see cref="ChatCitation"/>.
|
||||
/// </summary>
|
||||
private static IList<ChatCitation> GetCitations(ChatMessageContent chatMessageContent)
|
||||
{
|
||||
var message = chatMessageContent.InnerContent as OpenAI.Chat.ChatCompletion;
|
||||
var messageContext = message.GetMessageContext();
|
||||
|
||||
return messageContext.Citations;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a collection of <see cref="ChatCitation"/>.
|
||||
/// </summary>
|
||||
private static IList<ChatCitation>? GetCitations(StreamingChatMessageContent streamingContent)
|
||||
{
|
||||
var message = streamingContent.InnerContent as OpenAI.Chat.StreamingChatCompletionUpdate;
|
||||
var messageContext = message?.GetMessageContext();
|
||||
|
||||
return messageContext?.Citations;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs a collection of <see cref="ChatCitation"/>.
|
||||
/// </summary>
|
||||
private void OutputCitations(IList<ChatCitation>? citations)
|
||||
{
|
||||
if (citations is not null)
|
||||
{
|
||||
Console.WriteLine("Citations:");
|
||||
|
||||
foreach (var citation in citations)
|
||||
{
|
||||
Console.WriteLine($"Chunk ID: {citation.ChunkId}");
|
||||
Console.WriteLine($"Title: {citation.Title}");
|
||||
Console.WriteLine($"File path: {citation.FilePath}");
|
||||
Console.WriteLine($"URL: {citation.Url}");
|
||||
Console.WriteLine($"Content: {citation.Content}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter which performs the retry logic to answer user's question using different sources.
|
||||
/// Initially, if the model doesn't provide an answer, the filter will enable Azure Data Source and retry the same request.
|
||||
/// If Azure Data Source doesn't contain the requested information, the filter will disable it and enable function calling instead.
|
||||
/// If the answer is provided from the model itself or any source, it is returned back to the user.
|
||||
/// </summary>
|
||||
private sealed class FunctionInvocationRetryFilter : IFunctionInvocationFilter
|
||||
{
|
||||
public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next)
|
||||
{
|
||||
// Retry logic for Azure Data Source and function calling is enabled only for Azure OpenAI prompt execution settings.
|
||||
if (context.Arguments.ExecutionSettings is not null &&
|
||||
context.Arguments.ExecutionSettings.TryGetValue(PromptExecutionSettings.DefaultServiceId, out var executionSettings) &&
|
||||
executionSettings is AzureOpenAIPromptExecutionSettings azureOpenAIPromptExecutionSettings)
|
||||
{
|
||||
// Store the initial data source and function calling configuration to reset it after filter execution.
|
||||
var initialAzureChatDataSource = azureOpenAIPromptExecutionSettings.AzureChatDataSource;
|
||||
var initialFunctionChoiceBehavior = azureOpenAIPromptExecutionSettings.FunctionChoiceBehavior;
|
||||
|
||||
// Track which source of information was used during the execution to try both sources sequentially.
|
||||
var dataSourceUsed = initialAzureChatDataSource is not null;
|
||||
var functionCallingUsed = initialFunctionChoiceBehavior is not null;
|
||||
|
||||
// Perform a request.
|
||||
await next(context);
|
||||
|
||||
// Get and parse the result.
|
||||
var result = context.Result.GetValue<string>();
|
||||
var modelResult = ModelResult.Parse(result);
|
||||
|
||||
// If the model could not answer the question, then retry the request using an alternate technique:
|
||||
// - If the Azure Data Source was used then disable it and enable function calling.
|
||||
// - If function calling was used then disable it and enable the Azure Data Source.
|
||||
while (modelResult?.IsAnswered is false || (!dataSourceUsed && !functionCallingUsed))
|
||||
{
|
||||
// If Azure Data Source wasn't used - enable it.
|
||||
if (azureOpenAIPromptExecutionSettings.AzureChatDataSource is null)
|
||||
{
|
||||
var dataSource = GetAzureSearchDataSource();
|
||||
|
||||
// Since Azure Data Source is enabled, the function calling should be disabled,
|
||||
// because they are not supported together.
|
||||
azureOpenAIPromptExecutionSettings.AzureChatDataSource = dataSource;
|
||||
azureOpenAIPromptExecutionSettings.FunctionChoiceBehavior = null;
|
||||
|
||||
dataSourceUsed = true;
|
||||
}
|
||||
// Otherwise, if function calling wasn't used - enable it.
|
||||
else if (azureOpenAIPromptExecutionSettings.FunctionChoiceBehavior is null)
|
||||
{
|
||||
// Since function calling is enabled, the Azure Data Source should be disabled,
|
||||
// because they are not supported together.
|
||||
azureOpenAIPromptExecutionSettings.AzureChatDataSource = null;
|
||||
azureOpenAIPromptExecutionSettings.FunctionChoiceBehavior = FunctionChoiceBehavior.Auto();
|
||||
|
||||
functionCallingUsed = true;
|
||||
}
|
||||
|
||||
// Perform a request.
|
||||
await next(context);
|
||||
|
||||
// Get and parse the result.
|
||||
result = context.Result.GetValue<string>();
|
||||
modelResult = ModelResult.Parse(result);
|
||||
}
|
||||
|
||||
// Reset prompt execution setting properties to the initial state.
|
||||
azureOpenAIPromptExecutionSettings.AzureChatDataSource = initialAzureChatDataSource;
|
||||
azureOpenAIPromptExecutionSettings.FunctionChoiceBehavior = initialFunctionChoiceBehavior;
|
||||
}
|
||||
// Otherwise, perform a default function invocation.
|
||||
else
|
||||
{
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a model result with actual message and boolean flag which shows if user's question was answered or not.
|
||||
/// </summary>
|
||||
private sealed class ModelResult
|
||||
{
|
||||
public string Message { get; set; }
|
||||
|
||||
public bool IsAnswered { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parses model result.
|
||||
/// </summary>
|
||||
public static ModelResult? Parse(string? result)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(result))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// With response format as "json_object", sometimes the JSON response string is coming together with annotation.
|
||||
// The following line normalizes the response string in order to deserialize it later.
|
||||
var normalized = result
|
||||
.Replace("```json", string.Empty)
|
||||
.Replace("```", string.Empty);
|
||||
|
||||
return JsonSerializer.Deserialize<ModelResult>(normalized);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Example of data plugin that provides a user information for demonstration purposes.
|
||||
/// </summary>
|
||||
private sealed class DataPlugin
|
||||
{
|
||||
private readonly Dictionary<string, string> _emails = new()
|
||||
{
|
||||
["Emily"] = "emily@contoso.com",
|
||||
["David"] = "david@contoso.com",
|
||||
};
|
||||
|
||||
[KernelFunction]
|
||||
public List<string> GetEmails(List<string> users)
|
||||
{
|
||||
var emails = new List<string>();
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
if (this._emails.TryGetValue(user, out var email))
|
||||
{
|
||||
emails.Add(email);
|
||||
}
|
||||
}
|
||||
|
||||
return emails;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text;
|
||||
using Azure.Identity;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// These examples demonstrate different ways of using chat completion with Azure OpenAI API.
|
||||
/// </summary>
|
||||
public class AzureOpenAI_ChatCompletion(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// Sample showing how to use <see cref="Kernel"/> with chat completion and chat prompt syntax.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ChatPromptAsync()
|
||||
{
|
||||
Console.WriteLine("======== Azure Open AI - Chat Completion ========");
|
||||
|
||||
Assert.NotNull(TestConfiguration.AzureOpenAI.ChatDeploymentName);
|
||||
Assert.NotNull(TestConfiguration.AzureOpenAI.Endpoint);
|
||||
|
||||
StringBuilder chatPrompt = new("""
|
||||
<message role="system">You are a librarian, expert about books</message>
|
||||
<message role="user">Hi, I'm looking for book suggestions</message>
|
||||
""");
|
||||
|
||||
var kernelBuilder = Kernel.CreateBuilder();
|
||||
if (string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.ApiKey))
|
||||
{
|
||||
kernelBuilder.AddAzureOpenAIChatCompletion(
|
||||
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
|
||||
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
|
||||
credentials: new DefaultAzureCredential(),
|
||||
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
|
||||
}
|
||||
else
|
||||
{
|
||||
kernelBuilder.AddAzureOpenAIChatCompletion(
|
||||
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
|
||||
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
|
||||
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
|
||||
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
|
||||
}
|
||||
|
||||
var kernel = kernelBuilder.Build();
|
||||
var reply = await kernel.InvokePromptAsync(chatPrompt.ToString());
|
||||
|
||||
chatPrompt.AppendLine($"<message role=\"assistant\"><![CDATA[{reply}]]></message>");
|
||||
chatPrompt.AppendLine("<message role=\"user\">I love history and philosophy, I'd like to learn something new about Greece, any suggestion</message>");
|
||||
|
||||
reply = await kernel.InvokePromptAsync(chatPrompt.ToString());
|
||||
|
||||
Console.WriteLine(reply);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sample showing how to use <see cref="IChatCompletionService"/> directly with a <see cref="ChatHistory"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ServicePromptAsync()
|
||||
{
|
||||
Console.WriteLine("======== Azure Open AI - Chat Completion ========");
|
||||
|
||||
Assert.NotNull(TestConfiguration.AzureOpenAI.ChatDeploymentName);
|
||||
Assert.NotNull(TestConfiguration.AzureOpenAI.Endpoint);
|
||||
|
||||
AzureOpenAIChatCompletionService chatCompletionService =
|
||||
string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.ApiKey)
|
||||
? new(
|
||||
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
|
||||
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
|
||||
credentials: new DefaultAzureCredential(),
|
||||
modelId: TestConfiguration.AzureOpenAI.ChatModelId)
|
||||
: new(
|
||||
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
|
||||
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
|
||||
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
|
||||
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
|
||||
|
||||
Console.WriteLine("Chat content:");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
var chatHistory = new ChatHistory("You are a librarian, expert about books");
|
||||
|
||||
// First user message
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// First assistant message
|
||||
var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
|
||||
chatHistory.Add(reply);
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// Second user message
|
||||
chatHistory.AddUserMessage("I love history and philosophy, I'd like to learn something new about Greece, any suggestion");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// Second assistant message
|
||||
reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
|
||||
chatHistory.Add(reply);
|
||||
OutputLastMessage(chatHistory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// These examples demonstrate different ways of using streaming chat completion with Azure OpenAI API.
|
||||
/// </summary>
|
||||
public class AzureOpenAI_ChatCompletionStreaming(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// This example demonstrates chat completion streaming using Azure OpenAI.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public Task StreamServicePromptAsync()
|
||||
{
|
||||
Console.WriteLine("======== Azure Open AI Chat Completion Streaming ========");
|
||||
|
||||
AzureOpenAIChatCompletionService chatCompletionService = new(
|
||||
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
|
||||
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
|
||||
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
|
||||
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
|
||||
|
||||
return this.StartStreamingChatAsync(chatCompletionService);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates how the chat completion service streams text content.
|
||||
/// It shows how to access the response update via StreamingChatMessageContent.Content property
|
||||
/// and alternatively via the StreamingChatMessageContent.Items property.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task StreamServicePromptTextAsync()
|
||||
{
|
||||
Console.WriteLine("======== Azure Open AI Streaming Text ========");
|
||||
|
||||
// Create chat completion service
|
||||
AzureOpenAIChatCompletionService chatCompletionService = new(
|
||||
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
|
||||
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
|
||||
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
|
||||
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
|
||||
|
||||
// Create chat history with initial system and user messages
|
||||
ChatHistory chatHistory = new("You are a librarian, an expert on books.");
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions.");
|
||||
chatHistory.AddUserMessage("I love history and philosophy. I'd like to learn something new about Greece, any suggestion?");
|
||||
|
||||
// Start streaming chat based on the chat history
|
||||
await foreach (StreamingChatMessageContent chatUpdate in chatCompletionService.GetStreamingChatMessageContentsAsync(chatHistory))
|
||||
{
|
||||
// Access the response update via StreamingChatMessageContent.Content property
|
||||
Console.Write(chatUpdate.Content);
|
||||
|
||||
// Alternatively, the response update can be accessed via the StreamingChatMessageContent.Items property
|
||||
Console.Write(chatUpdate.Items.OfType<StreamingTextContent>().FirstOrDefault());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates how the chat completion service streams raw function call content.
|
||||
/// See <see cref="FunctionCalling.FunctionCalling.RunStreamingChatCompletionApiWithManualFunctionCallingAsync"/> for a sample demonstrating how to simplify
|
||||
/// function call content building out of streamed function call updates using the <see cref="FunctionCallContentBuilder"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task StreamFunctionCallContentAsync()
|
||||
{
|
||||
Console.WriteLine("======== Stream Function Call Content ========");
|
||||
|
||||
// Create chat completion service
|
||||
AzureOpenAIChatCompletionService chatCompletionService = new(deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
|
||||
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
|
||||
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
|
||||
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
|
||||
|
||||
// Create kernel with helper plugin.
|
||||
Kernel kernel = new();
|
||||
kernel.ImportPluginFromFunctions("HelperFunctions",
|
||||
[
|
||||
kernel.CreateFunctionFromMethod((string longTestString) => DateTime.UtcNow.ToString("R"), "GetCurrentUtcTime", "Retrieves the current time in UTC."),
|
||||
]);
|
||||
|
||||
// Create execution settings with manual function calling
|
||||
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(autoInvoke: false) };
|
||||
|
||||
// Create chat history with initial user question
|
||||
ChatHistory chatHistory = [];
|
||||
chatHistory.AddUserMessage("Hi, what is the current time?");
|
||||
|
||||
// Start streaming chat based on the chat history
|
||||
await foreach (StreamingChatMessageContent chatUpdate in chatCompletionService.GetStreamingChatMessageContentsAsync(chatHistory, settings, kernel))
|
||||
{
|
||||
// Getting list of function call updates requested by LLM
|
||||
var streamingFunctionCallUpdates = chatUpdate.Items.OfType<StreamingFunctionCallUpdateContent>();
|
||||
|
||||
// Iterating over function call updates. Please use the unctionCallContentBuilder to simplify function call content building.
|
||||
foreach (StreamingFunctionCallUpdateContent update in streamingFunctionCallUpdates)
|
||||
{
|
||||
Console.WriteLine($"Function call update: callId={update.CallId}, name={update.Name}, arguments={update.Arguments?.Replace("\n", "\\n")}, functionCallIndex={update.FunctionCallIndex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartStreamingChatAsync(IChatCompletionService chatCompletionService)
|
||||
{
|
||||
Console.WriteLine("Chat content:");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
var chatHistory = new ChatHistory("You are a librarian, expert about books");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// First user message
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// First assistant message
|
||||
await StreamMessageOutputAsync(chatCompletionService, chatHistory, AuthorRole.Assistant);
|
||||
|
||||
// Second user message
|
||||
chatHistory.AddUserMessage("I love history and philosophy, I'd like to learn something new about Greece, any suggestion?");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// Second assistant message
|
||||
await StreamMessageOutputAsync(chatCompletionService, chatHistory, AuthorRole.Assistant);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// These examples demonstrate different ways of using chat completion reasoning models with Azure OpenAI API.
|
||||
/// </summary>
|
||||
public class AzureOpenAI_ChatCompletionWithReasoning(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// Sample showing how to use <see cref="Kernel"/> with chat completion and chat prompt syntax.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ChatPromptWithReasoningAsync()
|
||||
{
|
||||
Console.WriteLine("======== Azure Open AI - Chat Completion with Reasoning ========");
|
||||
|
||||
Assert.NotNull(TestConfiguration.AzureOpenAI.ChatDeploymentName);
|
||||
Assert.NotNull(TestConfiguration.AzureOpenAI.Endpoint);
|
||||
Assert.NotNull(TestConfiguration.AzureOpenAI.ApiKey);
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddAzureOpenAIChatCompletion(
|
||||
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
|
||||
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
|
||||
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
|
||||
modelId: TestConfiguration.AzureOpenAI.ChatModelId)
|
||||
.Build();
|
||||
|
||||
// Create execution settings with high reasoning effort.
|
||||
var executionSettings = new AzureOpenAIPromptExecutionSettings //OpenAIPromptExecutionSettings
|
||||
{
|
||||
// Flags Azure SDK to use the new token property.
|
||||
SetNewMaxCompletionTokensEnabled = true,
|
||||
MaxTokens = 2000,
|
||||
// Note: reasoning effort is only available for reasoning models (at this moment o3-mini & o1 models)
|
||||
ReasoningEffort = ChatReasoningEffortLevel.Low
|
||||
};
|
||||
|
||||
// Create KernelArguments using the execution settings.
|
||||
var kernelArgs = new KernelArguments(executionSettings);
|
||||
|
||||
StringBuilder chatPrompt = new("""
|
||||
<message role="developer">You are an expert software engineer, specialized in the Semantic Kernel SDK and NET framework</message>
|
||||
<message role="user">Hi, Please craft me an example code in .NET using Semantic Kernel that implements a chat loop .</message>
|
||||
""");
|
||||
|
||||
// Invoke the prompt with high reasoning effort.
|
||||
var reply = await kernel.InvokePromptAsync(chatPrompt.ToString(), kernelArgs);
|
||||
|
||||
Console.WriteLine(reply);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sample showing how to use <see cref="IChatCompletionService"/> directly with a <see cref="ChatHistory"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ServicePromptWithReasoningAsync()
|
||||
{
|
||||
Console.WriteLine("======== Azure Open AI - Chat Completion with Azure Default Credential with Reasoning ========");
|
||||
|
||||
Assert.NotNull(TestConfiguration.AzureOpenAI.ChatDeploymentName);
|
||||
Assert.NotNull(TestConfiguration.AzureOpenAI.Endpoint);
|
||||
Assert.NotNull(TestConfiguration.AzureOpenAI.ApiKey);
|
||||
|
||||
IChatCompletionService chatCompletionService = new AzureOpenAIChatCompletionService(
|
||||
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
|
||||
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
|
||||
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
|
||||
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
|
||||
|
||||
// Create execution settings with high reasoning effort.
|
||||
var executionSettings = new AzureOpenAIPromptExecutionSettings
|
||||
{
|
||||
// Flags Azure SDK to use the new token property.
|
||||
SetNewMaxCompletionTokensEnabled = true,
|
||||
MaxTokens = 2000,
|
||||
// Note: reasoning effort is only available for reasoning models (at this moment o3-mini & o1 models)
|
||||
ReasoningEffort = ChatReasoningEffortLevel.Low
|
||||
};
|
||||
|
||||
// Create a ChatHistory and add messages.
|
||||
var chatHistory = new ChatHistory();
|
||||
chatHistory.AddDeveloperMessage(
|
||||
"You are an expert software engineer, specialized in the Semantic Kernel SDK and .NET framework.");
|
||||
chatHistory.AddUserMessage(
|
||||
"Hi, Please craft me an example code in .NET using Semantic Kernel that implements a chat loop.");
|
||||
|
||||
// Instead of a prompt string, call GetChatMessageContentAsync with the chat history.
|
||||
var reply = await chatCompletionService.GetChatMessageContentAsync(
|
||||
chatHistory: chatHistory,
|
||||
executionSettings: executionSettings);
|
||||
|
||||
Console.WriteLine(reply);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ClientModel;
|
||||
using System.ClientModel.Primitives;
|
||||
using Azure.AI.OpenAI;
|
||||
using Microsoft.SemanticKernel;
|
||||
|
||||
#pragma warning disable CA5399 // HttpClient is created without enabling CheckCertificateRevocationList
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// This example shows a way of using a Custom HttpClient and HttpHandler with Azure OpenAI Connector to capture
|
||||
/// the request Uri and Headers for each request.
|
||||
/// </summary>
|
||||
public sealed class AzureOpenAI_CustomClient(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task UsingCustomHttpClientWithAzureOpenAI()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.AzureOpenAI.Endpoint);
|
||||
Assert.NotNull(TestConfiguration.AzureOpenAI.ChatDeploymentName);
|
||||
Assert.NotNull(TestConfiguration.AzureOpenAI.ApiKey);
|
||||
|
||||
Console.WriteLine($"======== Azure Open AI - {nameof(UsingCustomHttpClientWithAzureOpenAI)} ========");
|
||||
|
||||
// Create an HttpClient and include your custom header(s)
|
||||
using var myCustomHttpHandler = new MyCustomClientHttpHandler(Output);
|
||||
using var myCustomClient = new HttpClient(handler: myCustomHttpHandler);
|
||||
myCustomClient.DefaultRequestHeaders.Add("My-Custom-Header", "My Custom Value");
|
||||
|
||||
// Configure AzureOpenAIClient to use the customized HttpClient
|
||||
var clientOptions = new AzureOpenAIClientOptions
|
||||
{
|
||||
Transport = new HttpClientPipelineTransport(myCustomClient),
|
||||
NetworkTimeout = TimeSpan.FromSeconds(30),
|
||||
RetryPolicy = new ClientRetryPolicy()
|
||||
};
|
||||
|
||||
var customClient = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAI.Endpoint), new ApiKeyCredential(TestConfiguration.AzureOpenAI.ApiKey), clientOptions);
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddAzureOpenAIChatCompletion(TestConfiguration.AzureOpenAI.ChatDeploymentName, customClient)
|
||||
.Build();
|
||||
|
||||
// Load semantic plugin defined with prompt templates
|
||||
string folder = RepoFiles.SamplePluginsPath();
|
||||
|
||||
kernel.ImportPluginFromPromptDirectory(Path.Combine(folder, "FunPlugin"));
|
||||
|
||||
// Run
|
||||
var result = await kernel.InvokeAsync(
|
||||
kernel.Plugins["FunPlugin"]["Excuses"],
|
||||
new() { ["input"] = "I have no homework" }
|
||||
);
|
||||
Console.WriteLine(result.GetValue<string>());
|
||||
|
||||
myCustomClient.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normally you would use a custom HttpClientHandler to add custom logic to your custom http client
|
||||
/// This uses the ITestOutputHelper to write the requested URI to the test output
|
||||
/// </summary>
|
||||
/// <param name="output">The <see cref="ITestOutputHelper"/> to write the requested URI to the test output </param>
|
||||
private sealed class MyCustomClientHttpHandler(ITestOutputHelper output) : HttpClientHandler
|
||||
{
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
output.WriteLine($"Requested URI: {request.RequestUri}");
|
||||
|
||||
request.Headers.Where(h => h.Key != "Authorization")
|
||||
.ToList()
|
||||
.ForEach(h => output.WriteLine($"{h.Key}: {string.Join(", ", h.Value)}"));
|
||||
output.WriteLine("--------------------------------");
|
||||
|
||||
// Add custom logic here
|
||||
return await base.SendAsync(request, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
// The following example shows how to use Chat History with Author identity associated with each chat message.
|
||||
public class ChatHistoryAuthorName(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// Flag to force usage of OpenAI configuration if both <see cref="TestConfiguration.OpenAI"/>
|
||||
/// and <see cref="TestConfiguration.AzureOpenAI"/> are defined.
|
||||
/// If 'false', Azure takes precedence.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// NOTE: Retrieval tools is not currently available on Azure.
|
||||
/// </remarks>
|
||||
private new const bool ForceOpenAI = true;
|
||||
|
||||
private static readonly OpenAIPromptExecutionSettings s_executionSettings =
|
||||
new()
|
||||
{
|
||||
FrequencyPenalty = 0,
|
||||
PresencePenalty = 0,
|
||||
Temperature = 1,
|
||||
TopP = 0.5,
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task CompletionIdentityAsync(bool withName)
|
||||
{
|
||||
Console.WriteLine("======== Completion Identity ========");
|
||||
|
||||
IChatCompletionService chatService = CreateCompletionService();
|
||||
|
||||
ChatHistory chatHistory = CreateHistory(withName);
|
||||
|
||||
WriteMessages(chatHistory);
|
||||
|
||||
WriteMessages(await chatService.GetChatMessageContentsAsync(chatHistory, s_executionSettings), chatHistory);
|
||||
|
||||
ValidateMessages(chatHistory, withName);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task StreamingIdentityAsync(bool withName)
|
||||
{
|
||||
Console.WriteLine("======== Completion Identity ========");
|
||||
|
||||
IChatCompletionService chatService = CreateCompletionService();
|
||||
|
||||
ChatHistory chatHistory = CreateHistory(withName);
|
||||
|
||||
var content = await chatHistory.AddStreamingMessageAsync(chatService.GetStreamingChatMessageContentsAsync(chatHistory, s_executionSettings).Cast<OpenAIStreamingChatMessageContent>()).ToArrayAsync();
|
||||
|
||||
WriteMessages(chatHistory);
|
||||
|
||||
ValidateMessages(chatHistory, withName);
|
||||
}
|
||||
|
||||
private static ChatHistory CreateHistory(bool withName)
|
||||
{
|
||||
return
|
||||
[
|
||||
new ChatMessageContent(AuthorRole.System, "Write one paragraph in response to the user that rhymes") { AuthorName = withName ? "Echo" : null },
|
||||
new ChatMessageContent(AuthorRole.User, "Why is AI awesome") { AuthorName = withName ? "Ralph" : null },
|
||||
];
|
||||
}
|
||||
|
||||
private void ValidateMessages(ChatHistory chatHistory, bool expectName)
|
||||
{
|
||||
foreach (var message in chatHistory)
|
||||
{
|
||||
if (expectName && message.Role != AuthorRole.Assistant)
|
||||
{
|
||||
Assert.NotNull(message.AuthorName);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Null(message.AuthorName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteMessages(IReadOnlyList<ChatMessageContent> messages, ChatHistory? history = null)
|
||||
{
|
||||
foreach (var message in messages)
|
||||
{
|
||||
Console.WriteLine($"# {message.Role}:{message.AuthorName ?? "?"} - {message.Content ?? "-"}");
|
||||
}
|
||||
|
||||
history?.AddRange(messages);
|
||||
}
|
||||
|
||||
private static IChatCompletionService CreateCompletionService()
|
||||
{
|
||||
return
|
||||
ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ?
|
||||
new OpenAIChatCompletionService(
|
||||
TestConfiguration.OpenAI.ChatModelId,
|
||||
TestConfiguration.OpenAI.ApiKey) :
|
||||
new AzureOpenAIChatCompletionService(
|
||||
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
|
||||
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
|
||||
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
|
||||
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// This example shows how to access <see cref="ChatHistory"/> object in Semantic Kernel functions using
|
||||
/// <see cref="Kernel.Data"/> and <see cref="KernelArguments"/>.
|
||||
/// This scenario can be useful with auto function calling,
|
||||
/// when logic in SK functions depends on results from previous messages in the same chat history.
|
||||
/// </summary>
|
||||
public sealed class ChatHistoryInFunctions(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// This method passes an instance of <see cref="ChatHistory"/> to SK function using <see cref="Kernel.Data"/> property.
|
||||
/// This approach should be used with caution for cases when Kernel is registered in application as singleton.
|
||||
/// For singleton Kernel, check examples <see cref="UsingKernelArgumentsAndFilterOption1Async"/> and <see cref="UsingKernelArgumentsAndFilterOption2Async"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingKernelDataAsync()
|
||||
{
|
||||
// Initialize kernel.
|
||||
var kernel = GetKernel();
|
||||
|
||||
// Import plugin.
|
||||
kernel.ImportPluginFromObject(new DataPlugin(this.Output));
|
||||
|
||||
// Get chat completion service.
|
||||
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
// Initialize chat history with prompt.
|
||||
var chatHistory = new ChatHistory();
|
||||
chatHistory.AddUserMessage("I want to get an information about featured products, product reviews and daily summary.");
|
||||
|
||||
// Initialize execution settings with enabled auto function calling.
|
||||
var executionSettings = new OpenAIPromptExecutionSettings
|
||||
{
|
||||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
|
||||
};
|
||||
|
||||
// Set chat history in kernel data to access it in a function.
|
||||
kernel.Data[nameof(ChatHistory)] = chatHistory;
|
||||
|
||||
// Send a request.
|
||||
var result = await chatCompletionService.GetChatMessageContentAsync(chatHistory, executionSettings, kernel);
|
||||
|
||||
// Each function will receive a greater number of messages in chat history, because chat history is populated
|
||||
// with results of previous functions.
|
||||
Console.WriteLine($"Result: {result}");
|
||||
|
||||
// Output:
|
||||
// GetFeaturedProducts - Chat History Message Count: 2
|
||||
// GetProductReviews - Chat History Message Count: 3
|
||||
// GetDailySalesSummary - Chat History Message Count: 4
|
||||
// Result: Here's the information you requested...
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method passes an instance of <see cref="ChatHistory"/> to SK function using
|
||||
/// <see cref="KernelArguments"/> and <see cref="IAutoFunctionInvocationFilter"/> filter.
|
||||
/// The plugin has access to <see cref="KernelArguments"/>, so it's possible to find a chat history in arguments by property name.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingKernelArgumentsAndFilterOption1Async()
|
||||
{
|
||||
// Initialize kernel.
|
||||
var kernel = GetKernel();
|
||||
|
||||
// Import plugin.
|
||||
kernel.ImportPluginFromObject(new DataPlugin(this.Output));
|
||||
|
||||
// Add filter.
|
||||
kernel.AutoFunctionInvocationFilters.Add(new AutoFunctionInvocationFilter());
|
||||
|
||||
// Initialize execution settings with enabled auto function calling.
|
||||
var executionSettings = new OpenAIPromptExecutionSettings
|
||||
{
|
||||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
|
||||
};
|
||||
|
||||
// Send a request.
|
||||
var result = await kernel.InvokePromptAsync("I want to get an information about featured products, product reviews and daily summary.", new(executionSettings));
|
||||
|
||||
// Each function will receive a greater number of messages in chat history, because chat history is populated
|
||||
// with results of previous functions.
|
||||
Console.WriteLine($"Result: {result}");
|
||||
|
||||
// Output:
|
||||
// GetFeaturedProducts - Chat History Message Count: 2
|
||||
// GetProductReviews - Chat History Message Count: 3
|
||||
// GetDailySalesSummary - Chat History Message Count: 4
|
||||
// Result: Here's the information you requested...
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method passes an instance of <see cref="ChatHistory"/> to SK function using
|
||||
/// <see cref="KernelArguments"/> and <see cref="IAutoFunctionInvocationFilter"/> filter.
|
||||
/// The plugin has access to <see cref="ChatHistory"/> directly, since it's automatically injected from <see cref="KernelArguments"/>
|
||||
/// into the function by argument name.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingKernelArgumentsAndFilterOption2Async()
|
||||
{
|
||||
// Initialize kernel.
|
||||
var kernel = GetKernel();
|
||||
|
||||
// Import plugin.
|
||||
kernel.ImportPluginFromObject(new EmailPlugin(this.Output));
|
||||
|
||||
// Add filter.
|
||||
kernel.AutoFunctionInvocationFilters.Add(new AutoFunctionInvocationFilter());
|
||||
|
||||
// Initialize execution settings with enabled auto function calling.
|
||||
var executionSettings = new OpenAIPromptExecutionSettings
|
||||
{
|
||||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
|
||||
};
|
||||
|
||||
// Send a request.
|
||||
var result = await kernel.InvokePromptAsync("Send email to test@contoso.com", new(executionSettings));
|
||||
|
||||
Console.WriteLine($"Result: {result}");
|
||||
|
||||
// Output:
|
||||
// SendEmail - Chat History Message Count: 2
|
||||
// Result: Email has been sent to test@contoso.com.
|
||||
}
|
||||
|
||||
#region private
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IAutoFunctionInvocationFilter"/> to set chat history in <see cref="KernelArguments"/>
|
||||
/// before invoking a function.
|
||||
/// </summary>
|
||||
private sealed class AutoFunctionInvocationFilter : IAutoFunctionInvocationFilter
|
||||
{
|
||||
public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next)
|
||||
{
|
||||
// Set chat history in kernel arguments.
|
||||
if (context.Arguments is not null)
|
||||
{
|
||||
// nameof(ChatHistory) is used for demonstration purposes.
|
||||
// Any name can be used here, as long as it is effective for the intended purpose.
|
||||
// However, the same name must be used when retrieving chat history from the KernelArguments instance
|
||||
// or when the ChatHistory parameter is directly injected into a function.
|
||||
context.Arguments[nameof(ChatHistory)] = context.ChatHistory;
|
||||
}
|
||||
|
||||
// Invoke next filter in pipeline or function.
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data plugin for demonstration purposes, where methods accept <see cref="Kernel"/> and <see cref="KernelArguments"/>
|
||||
/// as parameters.
|
||||
/// </summary>
|
||||
private sealed class DataPlugin(ITestOutputHelper output)
|
||||
{
|
||||
[KernelFunction]
|
||||
public List<string> GetFeaturedProducts(Kernel kernel, KernelArguments arguments)
|
||||
{
|
||||
var chatHistory = GetChatHistory(kernel.Data) ?? GetChatHistory(arguments);
|
||||
|
||||
if (chatHistory is not null)
|
||||
{
|
||||
output.WriteLine($"{nameof(GetFeaturedProducts)} - Chat History Message Count: {chatHistory.Count}");
|
||||
}
|
||||
|
||||
return ["Laptop", "Smartphone", "Smartwatch"];
|
||||
}
|
||||
|
||||
[KernelFunction]
|
||||
public Dictionary<string, List<string>> GetProductReviews(Kernel kernel, KernelArguments arguments)
|
||||
{
|
||||
var chatHistory = GetChatHistory(kernel.Data) ?? GetChatHistory(arguments);
|
||||
|
||||
if (chatHistory is not null)
|
||||
{
|
||||
output.WriteLine($"{nameof(GetProductReviews)} - Chat History Message Count: {chatHistory.Count}");
|
||||
}
|
||||
|
||||
return new()
|
||||
{
|
||||
["Laptop"] = ["Excellent performance!", "Battery life could be better."],
|
||||
["Smartphone"] = ["Amazing camera!", "Very responsive."],
|
||||
["Smartwatch"] = ["Stylish design", "Could use more apps."],
|
||||
};
|
||||
}
|
||||
|
||||
[KernelFunction]
|
||||
public string GetDailySalesSummary(Kernel kernel, KernelArguments arguments)
|
||||
{
|
||||
var chatHistory = GetChatHistory(kernel.Data) ?? GetChatHistory(arguments);
|
||||
|
||||
if (chatHistory is not null)
|
||||
{
|
||||
output.WriteLine($"{nameof(GetDailySalesSummary)} - Chat History Message Count: {chatHistory.Count}");
|
||||
}
|
||||
|
||||
const int OrdersProcessed = 50;
|
||||
const decimal TotalRevenue = 12345.67m;
|
||||
|
||||
return $"Today's Sales: {OrdersProcessed} orders processed, total revenue: ${TotalRevenue}.";
|
||||
}
|
||||
|
||||
private static ChatHistory? GetChatHistory(IDictionary<string, object?> data)
|
||||
{
|
||||
if (data.TryGetValue(nameof(ChatHistory), out object? chatHistoryObj) &&
|
||||
chatHistoryObj is ChatHistory chatHistory)
|
||||
{
|
||||
return chatHistory;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Email plugin for demonstration purposes, where method accepts <see cref="ChatHistory"/> as parameter.
|
||||
/// </summary>
|
||||
private sealed class EmailPlugin(ITestOutputHelper output)
|
||||
{
|
||||
[KernelFunction]
|
||||
public string SendEmail(string to, ChatHistory? chatHistory = null)
|
||||
{
|
||||
if (chatHistory is not null)
|
||||
{
|
||||
output.WriteLine($"{nameof(SendEmail)} - Chat History Message Count: {chatHistory.Count}");
|
||||
}
|
||||
|
||||
// Simulate the email-sending process by notifying the AI model that the email was sent.
|
||||
return $"Email has been sent to {to}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to initialize <see cref="Kernel"/>.
|
||||
/// </summary>
|
||||
private static Kernel GetKernel()
|
||||
{
|
||||
return Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion(
|
||||
modelId: "gpt-4o",
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey)
|
||||
.Build();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// Extensions methods for <see cref="IChatCompletionService"/>
|
||||
/// </summary>
|
||||
internal static class ChatCompletionServiceExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds an wrapper to an instance of <see cref="IChatCompletionService"/> which will use
|
||||
/// the provided instance of <see cref="IChatHistoryReducer"/> to reduce the size of
|
||||
/// the <see cref="ChatHistory"/> before sending it to the model.
|
||||
/// </summary>
|
||||
/// <param name="service">Instance of <see cref="IChatCompletionService"/></param>
|
||||
/// <param name="reducer">Instance of <see cref="IChatHistoryReducer"/></param>
|
||||
public static IChatCompletionService UsingChatHistoryReducer(this IChatCompletionService service, IChatHistoryReducer reducer)
|
||||
{
|
||||
return new ChatCompletionServiceWithReducer(service, reducer);
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// Instance of <see cref="IChatCompletionService"/> which will invoke a delegate
|
||||
/// to reduce the size of the <see cref="ChatHistory"/> before sending it to the model.
|
||||
/// </summary>
|
||||
public sealed class ChatCompletionServiceWithReducer(IChatCompletionService service, IChatHistoryReducer reducer) : IChatCompletionService
|
||||
{
|
||||
private static IReadOnlyDictionary<string, object?> EmptyAttributes { get; } = new Dictionary<string, object?>();
|
||||
|
||||
public IReadOnlyDictionary<string, object?> Attributes => EmptyAttributes;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IReadOnlyList<ChatMessageContent>> GetChatMessageContentsAsync(
|
||||
ChatHistory chatHistory,
|
||||
PromptExecutionSettings? executionSettings = null,
|
||||
Kernel? kernel = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var reducedMessages = await reducer.ReduceAsync(chatHistory, cancellationToken).ConfigureAwait(false);
|
||||
var reducedHistory = reducedMessages is null ? chatHistory : new ChatHistory(reducedMessages);
|
||||
|
||||
return await service.GetChatMessageContentsAsync(reducedHistory, executionSettings, kernel, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<StreamingChatMessageContent> GetStreamingChatMessageContentsAsync(
|
||||
ChatHistory chatHistory,
|
||||
PromptExecutionSettings? executionSettings = null,
|
||||
Kernel? kernel = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
var reducedMessages = await reducer.ReduceAsync(chatHistory, cancellationToken).ConfigureAwait(false);
|
||||
var history = reducedMessages is null ? chatHistory : new ChatHistory(reducedMessages);
|
||||
|
||||
var messages = service.GetStreamingChatMessageContentsAsync(history, executionSettings, kernel, cancellationToken);
|
||||
await foreach (var message in messages)
|
||||
{
|
||||
yield return message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.ML.Tokenizers;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="ChatHistory"/>."/>
|
||||
/// </summary>
|
||||
internal static class ChatHistoryExtensions
|
||||
{
|
||||
private static readonly Tokenizer s_tokenizer = TiktokenTokenizer.CreateForModel("gpt-4");
|
||||
|
||||
/// <summary>
|
||||
/// Returns the system prompt from the chat history.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For simplicity only a single system message is supported in these examples.
|
||||
/// </remarks>
|
||||
internal static ChatMessageContent? GetSystemMessage(this IReadOnlyList<ChatMessageContent> chatHistory)
|
||||
{
|
||||
return chatHistory.FirstOrDefault(m => m.Role == AuthorRole.System);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract a range of messages from the provided <see cref="ChatHistory"/>.
|
||||
/// </summary>
|
||||
/// <param name="chatHistory">The source history</param>
|
||||
/// <param name="startIndex">The index of the first messageContent to extract</param>
|
||||
/// <param name="endIndex">The index of the first messageContent to extract, if null extract up to the end of the chat history</param>
|
||||
/// <param name="systemMessage">An optional system messageContent to include</param>
|
||||
/// <param name="summaryMessage">An optional summary messageContent to include</param>
|
||||
/// <param name="messageFilter">An optional message filter</param>
|
||||
public static IEnumerable<ChatMessageContent> Extract(
|
||||
this IReadOnlyList<ChatMessageContent> chatHistory,
|
||||
int startIndex,
|
||||
int? endIndex = null,
|
||||
ChatMessageContent? systemMessage = null,
|
||||
ChatMessageContent? summaryMessage = null,
|
||||
Func<ChatMessageContent, bool>? messageFilter = null)
|
||||
{
|
||||
endIndex ??= chatHistory.Count - 1;
|
||||
if (startIndex > endIndex)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (systemMessage is not null)
|
||||
{
|
||||
yield return systemMessage;
|
||||
}
|
||||
|
||||
if (summaryMessage is not null)
|
||||
{
|
||||
yield return summaryMessage;
|
||||
}
|
||||
|
||||
for (int index = startIndex; index <= endIndex; ++index)
|
||||
{
|
||||
var messageContent = chatHistory[index];
|
||||
|
||||
if (messageFilter?.Invoke(messageContent) ?? false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return messageContent;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute the index truncation where truncation should begin using the current truncation threshold.
|
||||
/// </summary>
|
||||
/// <param name="chatHistory">The source history.</param>
|
||||
/// <param name="truncatedSize">Truncated size.</param>
|
||||
/// <param name="truncationThreshold">Truncation threshold.</param>
|
||||
/// <param name="hasSystemMessage">Flag indicating whether or not the chat history contains a system messageContent</param>
|
||||
public static int ComputeTruncationIndex(this IReadOnlyList<ChatMessageContent> chatHistory, int truncatedSize, int truncationThreshold, bool hasSystemMessage)
|
||||
{
|
||||
if (chatHistory.Count <= truncationThreshold)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Compute the index of truncation target
|
||||
var truncationIndex = chatHistory.Count - (truncatedSize - (hasSystemMessage ? 1 : 0));
|
||||
|
||||
// Skip function related content
|
||||
while (truncationIndex < chatHistory.Count)
|
||||
{
|
||||
if (chatHistory[truncationIndex].Items.Any(i => i is FunctionCallContent or FunctionResultContent))
|
||||
{
|
||||
truncationIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return truncationIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a system messageContent to the chat history
|
||||
/// </summary>
|
||||
/// <param name="chatHistory">Chat history instance</param>
|
||||
/// <param name="content">Message content</param>
|
||||
public static void AddSystemMessageWithTokenCount(this ChatHistory chatHistory, string content)
|
||||
{
|
||||
IReadOnlyDictionary<string, object?> metadata = new Dictionary<string, object?>
|
||||
{
|
||||
["TokenCount"] = s_tokenizer.CountTokens(content)
|
||||
};
|
||||
chatHistory.AddMessage(AuthorRole.System, content, metadata: metadata);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a user messageContent to the chat history
|
||||
/// </summary>
|
||||
/// <param name="chatHistory">Chat history instance</param>
|
||||
/// <param name="content">Message content</param>
|
||||
public static void AddUserMessageWithTokenCount(this ChatHistory chatHistory, string content)
|
||||
{
|
||||
IReadOnlyDictionary<string, object?> metadata = new Dictionary<string, object?>
|
||||
{
|
||||
["TokenCount"] = s_tokenizer.CountTokens(content)
|
||||
};
|
||||
chatHistory.AddMessage(AuthorRole.User, content, metadata: metadata);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a assistant messageContent to the chat history
|
||||
/// </summary>
|
||||
/// <param name="chatHistory">Chat history instance</param>
|
||||
/// <param name="content">Message content</param>
|
||||
public static void AddAssistantMessageWithTokenCount(this ChatHistory chatHistory, string content)
|
||||
{
|
||||
IReadOnlyDictionary<string, object?> metadata = new Dictionary<string, object?>
|
||||
{
|
||||
["TokenCount"] = s_tokenizer.CountTokens(content)
|
||||
};
|
||||
chatHistory.AddMessage(AuthorRole.Assistant, content, metadata: metadata);
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IChatHistoryReducer"/> which trim to the specified max token count.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This reducer requires that the ChatMessageContent.MetaData contains a TokenCount property.
|
||||
/// </remarks>
|
||||
public sealed class ChatHistoryMaxTokensReducer : IChatHistoryReducer
|
||||
{
|
||||
private readonly int _maxTokenCount;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="ChatHistoryMaxTokensReducer"/>.
|
||||
/// </summary>
|
||||
/// <param name="maxTokenCount">Max token count to send to the model.</param>
|
||||
public ChatHistoryMaxTokensReducer(int maxTokenCount)
|
||||
{
|
||||
if (maxTokenCount <= 0)
|
||||
{
|
||||
throw new ArgumentException("Maximum token count must be greater than zero.", nameof(maxTokenCount));
|
||||
}
|
||||
|
||||
this._maxTokenCount = maxTokenCount;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<IEnumerable<ChatMessageContent>?> ReduceAsync(IReadOnlyList<ChatMessageContent> chatHistory, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var systemMessage = chatHistory.GetSystemMessage();
|
||||
|
||||
var truncationIndex = ComputeTruncationIndex(chatHistory, systemMessage);
|
||||
|
||||
IEnumerable<ChatMessageContent>? truncatedHistory = null;
|
||||
|
||||
if (truncationIndex > 0)
|
||||
{
|
||||
truncatedHistory = chatHistory.Extract(truncationIndex, systemMessage: systemMessage);
|
||||
}
|
||||
|
||||
return Task.FromResult<IEnumerable<ChatMessageContent>?>(truncatedHistory);
|
||||
}
|
||||
|
||||
#region private
|
||||
|
||||
/// <summary>
|
||||
/// Compute the index truncation where truncation should begin using the current truncation threshold.
|
||||
/// </summary>
|
||||
/// <param name="chatHistory">Chat history to be truncated.</param>
|
||||
/// <param name="systemMessage">The system message</param>
|
||||
private int ComputeTruncationIndex(IReadOnlyList<ChatMessageContent> chatHistory, ChatMessageContent? systemMessage)
|
||||
{
|
||||
var truncationIndex = -1;
|
||||
|
||||
var totalTokenCount = (int)(systemMessage?.Metadata?["TokenCount"] ?? 0);
|
||||
for (int i = chatHistory.Count - 1; i >= 0; i--)
|
||||
{
|
||||
truncationIndex = i;
|
||||
var tokenCount = (int)(chatHistory[i].Metadata?["TokenCount"] ?? 0);
|
||||
if (tokenCount + totalTokenCount > this._maxTokenCount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
totalTokenCount += tokenCount;
|
||||
}
|
||||
|
||||
// Skip function related content
|
||||
while (truncationIndex < chatHistory.Count)
|
||||
{
|
||||
if (chatHistory[truncationIndex].Items.Any(i => i is FunctionCallContent or FunctionResultContent))
|
||||
{
|
||||
truncationIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return truncationIndex;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for <see cref="IChatHistoryReducer"/> implementations.
|
||||
/// </summary>
|
||||
public class ChatHistoryReducerTests(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(3, null, null, 100, 0)]
|
||||
[InlineData(3, "SystemMessage", null, 100, 0)]
|
||||
[InlineData(6, null, null, 100, 4)]
|
||||
[InlineData(6, "SystemMessage", null, 100, 5)]
|
||||
[InlineData(6, null, new int[] { 1 }, 100, 4)]
|
||||
[InlineData(6, "SystemMessage", new int[] { 2 }, 100, 4)]
|
||||
public async Task VerifyMaxTokensChatHistoryReducerAsync(int messageCount, string? systemMessage, int[]? functionCallIndexes, int maxTokens, int expectedSize)
|
||||
{
|
||||
// Arrange
|
||||
var chatHistory = CreateHistoryWithUserInput(messageCount, systemMessage, functionCallIndexes, true);
|
||||
var reducer = new ChatHistoryMaxTokensReducer(maxTokens);
|
||||
|
||||
// Act
|
||||
var reducedHistory = await reducer.ReduceAsync(chatHistory);
|
||||
|
||||
// Assert
|
||||
VerifyReducedHistory(reducedHistory, ComputeExpectedMessages(chatHistory, expectedSize));
|
||||
}
|
||||
|
||||
private static void VerifyReducedHistory(IEnumerable<ChatMessageContent>? reducedHistory, ChatMessageContent[]? expectedMessages)
|
||||
{
|
||||
if (expectedMessages is null)
|
||||
{
|
||||
Assert.Null(reducedHistory);
|
||||
return;
|
||||
}
|
||||
Assert.NotNull(reducedHistory);
|
||||
ChatMessageContent[] messages = reducedHistory.ToArray();
|
||||
Assert.Equal(expectedMessages.Length, messages.Length);
|
||||
Assert.Equal(expectedMessages, messages);
|
||||
}
|
||||
|
||||
private static ChatMessageContent[]? ComputeExpectedMessages(ChatHistory chatHistory, int expectedSize)
|
||||
{
|
||||
if (expectedSize == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var systemMessage = chatHistory.GetSystemMessage();
|
||||
var count = expectedSize - ((systemMessage is null) ? 0 : 1);
|
||||
var expectedMessages = chatHistory.TakeLast<ChatMessageContent>(count).ToArray();
|
||||
if (systemMessage is not null)
|
||||
{
|
||||
expectedMessages = [systemMessage, .. expectedMessages];
|
||||
}
|
||||
return expectedMessages;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an alternating list of user and assistant messages.
|
||||
/// Function content is optionally injected at specified indexes.
|
||||
/// </summary>
|
||||
private static ChatHistory CreateHistoryWithUserInput(int messageCount, string? systemMessage = null, int[]? functionCallIndexes = null, bool includeTokenCount = false)
|
||||
{
|
||||
var text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
|
||||
var chatHistory = new ChatHistory();
|
||||
if (systemMessage is not null)
|
||||
{
|
||||
chatHistory.AddSystemMessageWithTokenCount(systemMessage);
|
||||
}
|
||||
for (int index = 0; index < messageCount; ++index)
|
||||
{
|
||||
if (index % 2 == 1)
|
||||
{
|
||||
if (includeTokenCount)
|
||||
{
|
||||
chatHistory.AddAssistantMessageWithTokenCount($"Assistant response:{index} - {text}");
|
||||
}
|
||||
else
|
||||
{
|
||||
chatHistory.AddAssistantMessage($"Assistant response:{index} - {text}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (includeTokenCount)
|
||||
{
|
||||
chatHistory.AddUserMessageWithTokenCount($"User input:{index} - {text}");
|
||||
}
|
||||
else
|
||||
{
|
||||
chatHistory.AddUserMessageWithTokenCount($"User input:{index} - {text}");
|
||||
}
|
||||
}
|
||||
|
||||
if (functionCallIndexes is not null && functionCallIndexes.Contains(index))
|
||||
{
|
||||
IReadOnlyDictionary<string, object?> metadata = new Dictionary<string, object?>
|
||||
{
|
||||
["TokenCount"] = 10
|
||||
};
|
||||
|
||||
chatHistory.Add(new ChatMessageContent(AuthorRole.Assistant, [new FunctionCallContent($"Function call 1: {index}")], metadata: metadata));
|
||||
chatHistory.Add(new ChatMessageContent(AuthorRole.Tool, [new FunctionResultContent($"Function call 1: {index}")], metadata: metadata));
|
||||
chatHistory.Add(new ChatMessageContent(AuthorRole.Assistant, [new FunctionCallContent($"Function call 2: {index}")], metadata: metadata));
|
||||
chatHistory.Add(new ChatMessageContent(AuthorRole.Tool, [new FunctionResultContent($"Function call 2: {index}")], metadata: metadata));
|
||||
}
|
||||
}
|
||||
return chatHistory;
|
||||
}
|
||||
|
||||
private sealed class FakeChatCompletionService(string result) : IChatCompletionService
|
||||
{
|
||||
public IReadOnlyDictionary<string, object?> Attributes { get; } = new Dictionary<string, object?>();
|
||||
|
||||
public Task<IReadOnlyList<ChatMessageContent>> GetChatMessageContentsAsync(ChatHistory chatHistory, PromptExecutionSettings? executionSettings = null, Kernel? kernel = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult<IReadOnlyList<ChatMessageContent>>([new(AuthorRole.Assistant, result)]);
|
||||
}
|
||||
|
||||
#pragma warning disable IDE0036 // Order modifiers
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
public async IAsyncEnumerable<StreamingChatMessageContent> GetStreamingChatMessageContentsAsync(ChatHistory chatHistory, PromptExecutionSettings? executionSettings = null, Kernel? kernel = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
#pragma warning restore IDE0036 // Order modifiers
|
||||
{
|
||||
yield return new StreamingChatMessageContent(AuthorRole.Assistant, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
public class ChatHistorySerialization(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
private static readonly JsonSerializerOptions s_options = new() { WriteIndented = true };
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates how to serialize and deserialize <see cref="ChatHistory"/> class
|
||||
/// with <see cref="ChatMessageContent"/> having SK various content types as items.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task SerializeChatHistoryWithSKContentTypesAsync()
|
||||
{
|
||||
int[] data = [1, 2, 3];
|
||||
|
||||
var message = new ChatMessageContent(AuthorRole.User, "Describe the factors contributing to climate change.")
|
||||
{
|
||||
Items =
|
||||
[
|
||||
new TextContent("Discuss the potential long-term consequences for the Earth's ecosystem as well."),
|
||||
new ImageContent(new Uri("https://fake-random-test-host:123")),
|
||||
new BinaryContent(new BinaryData(data), "application/octet-stream"),
|
||||
new AudioContent(new BinaryData(data), "application/octet-stream")
|
||||
]
|
||||
};
|
||||
|
||||
var chatHistory = new ChatHistory([message]);
|
||||
|
||||
var chatHistoryJson = JsonSerializer.Serialize(chatHistory, s_options);
|
||||
|
||||
var deserializedHistory = JsonSerializer.Deserialize<ChatHistory>(chatHistoryJson);
|
||||
|
||||
var deserializedMessage = deserializedHistory!.Single();
|
||||
|
||||
Console.WriteLine($"Content: {deserializedMessage.Content}");
|
||||
Console.WriteLine($"Role: {deserializedMessage.Role.Label}");
|
||||
|
||||
Console.WriteLine($"Text content: {(deserializedMessage.Items![0]! as TextContent)!.Text}");
|
||||
|
||||
Console.WriteLine($"Image content: {(deserializedMessage.Items![1]! as ImageContent)!.Uri}");
|
||||
|
||||
Console.WriteLine($"Binary content: {Encoding.UTF8.GetString((deserializedMessage.Items![2]! as BinaryContent)!.Data!.Value.Span)}");
|
||||
|
||||
Console.WriteLine($"Audio content: {Encoding.UTF8.GetString((deserializedMessage.Items![3]! as AudioContent)!.Data!.Value.Span)}");
|
||||
|
||||
Console.WriteLine($"JSON:\n{chatHistoryJson}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows how to serialize and deserialize <see cref="ChatHistory"/> class with <see cref="ChatMessageContent"/> having custom content type as item.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SerializeChatWithHistoryWithCustomContentType()
|
||||
{
|
||||
var message = new ChatMessageContent(AuthorRole.User, "Describe the factors contributing to climate change.")
|
||||
{
|
||||
Items =
|
||||
[
|
||||
new TextContent("Discuss the potential long-term consequences for the Earth's ecosystem as well."),
|
||||
new CustomContent("Some custom content"),
|
||||
]
|
||||
};
|
||||
|
||||
var chatHistory = new ChatHistory([message]);
|
||||
|
||||
// The custom resolver should be used to serialize and deserialize the chat history with custom .
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
TypeInfoResolver = new CustomResolver(),
|
||||
WriteIndented = true,
|
||||
};
|
||||
|
||||
var chatHistoryJson = JsonSerializer.Serialize(chatHistory, options);
|
||||
|
||||
var deserializedHistory = JsonSerializer.Deserialize<ChatHistory>(chatHistoryJson, options);
|
||||
|
||||
var deserializedMessage = deserializedHistory!.Single();
|
||||
|
||||
Console.WriteLine($"Content: {deserializedMessage.Content}");
|
||||
Console.WriteLine($"Role: {deserializedMessage.Role.Label}");
|
||||
|
||||
Console.WriteLine($"Text content: {(deserializedMessage.Items![0]! as TextContent)!.Text}");
|
||||
|
||||
Console.WriteLine($"Custom content: {(deserializedMessage.Items![1]! as CustomContent)!.Content}");
|
||||
Console.WriteLine($"JSON:\n{chatHistoryJson}");
|
||||
}
|
||||
|
||||
private sealed class CustomContent(string content) : KernelContent(content)
|
||||
{
|
||||
public string Content { get; } = content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The TypeResolver is used to serialize and deserialize custom content types polymorphically.
|
||||
/// For more details, refer to the <see href="https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/polymorphism?pivots=dotnet-8-0"/> article.
|
||||
/// </summary>
|
||||
private sealed class CustomResolver : DefaultJsonTypeInfoResolver
|
||||
{
|
||||
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
|
||||
{
|
||||
var jsonTypeInfo = base.GetTypeInfo(type, options);
|
||||
|
||||
if (jsonTypeInfo.Type != typeof(KernelContent))
|
||||
{
|
||||
return jsonTypeInfo;
|
||||
}
|
||||
|
||||
// It's possible to completely override the polymorphic configuration specified in the KernelContent class
|
||||
// by using the '=' assignment operator instead of the ??= compound assignment one in the line below.
|
||||
jsonTypeInfo.PolymorphismOptions ??= new JsonPolymorphismOptions();
|
||||
|
||||
// Add custom content type to the list of derived types declared on KernelContent class.
|
||||
jsonTypeInfo.PolymorphismOptions.DerivedTypes.Add(new JsonDerivedType(typeof(CustomContent), "customContent"));
|
||||
|
||||
// Override type discriminator declared on KernelContent class as "$type", if needed.
|
||||
jsonTypeInfo.PolymorphismOptions.TypeDiscriminatorPropertyName = "name";
|
||||
|
||||
return jsonTypeInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
// These examples show how to use a custom HttpClient with SK connectors.
|
||||
public class Connectors_CustomHttpClient(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// Demonstrates the usage of the default HttpClient provided by the SK SDK.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void UseDefaultHttpClient()
|
||||
{
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion(
|
||||
modelId: TestConfiguration.OpenAI.ChatModelId,
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey) // If you need to use the default HttpClient from the SK SDK, simply omit the argument for the httpMessageInvoker parameter.
|
||||
.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates the usage of a custom HttpClient.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void UseCustomHttpClient()
|
||||
{
|
||||
using var httpClient = new HttpClient();
|
||||
|
||||
// If you need to use a custom HttpClient, simply pass it as an argument for the httpClient parameter.
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion(
|
||||
modelId: TestConfiguration.OpenAI.ModelId,
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey,
|
||||
httpClient: httpClient)
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// This example shows how you can use Streaming with Kernel.
|
||||
/// </summary>
|
||||
/// <param name="output"></param>
|
||||
public class Connectors_KernelStreaming(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task RunAsync()
|
||||
{
|
||||
string apiKey = TestConfiguration.AzureOpenAI.ApiKey;
|
||||
string chatDeploymentName = TestConfiguration.AzureOpenAI.ChatDeploymentName;
|
||||
string chatModelId = TestConfiguration.AzureOpenAI.ChatModelId;
|
||||
string endpoint = TestConfiguration.AzureOpenAI.Endpoint;
|
||||
|
||||
if (apiKey is null || chatDeploymentName is null || chatModelId is null || endpoint is null)
|
||||
{
|
||||
Console.WriteLine("Azure endpoint, apiKey, deploymentName or modelId not found. Skipping example.");
|
||||
return;
|
||||
}
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddAzureOpenAIChatCompletion(
|
||||
deploymentName: chatDeploymentName,
|
||||
endpoint: endpoint,
|
||||
serviceId: "AzureOpenAIChat",
|
||||
apiKey: apiKey,
|
||||
modelId: chatModelId)
|
||||
.Build();
|
||||
|
||||
var funnyParagraphFunction = kernel.CreateFunctionFromPrompt("Write a funny paragraph about streaming", new OpenAIPromptExecutionSettings() { MaxTokens = 100, Temperature = 0.4, TopP = 1 });
|
||||
|
||||
var roleDisplayed = false;
|
||||
|
||||
Console.WriteLine("\n=== Prompt Function - Streaming ===\n");
|
||||
|
||||
string fullContent = string.Empty;
|
||||
// Streaming can be of any type depending on the underlying service the function is using.
|
||||
await foreach (var update in kernel.InvokeStreamingAsync<OpenAIStreamingChatMessageContent>(funnyParagraphFunction))
|
||||
{
|
||||
// You will be always able to know the type of the update by checking the Type property.
|
||||
if (!roleDisplayed && update.Role.HasValue)
|
||||
{
|
||||
Console.WriteLine($"Role: {update.Role}");
|
||||
fullContent += $"Role: {update.Role}\n";
|
||||
roleDisplayed = true;
|
||||
}
|
||||
|
||||
if (update.Content is { Length: > 0 })
|
||||
{
|
||||
fullContent += update.Content;
|
||||
Console.Write(update.Content);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("\n------ Streamed Content ------\n");
|
||||
Console.WriteLine(fullContent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
public class Connectors_WithMultipleLLMs(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
private const string ChatPrompt = "Hello AI, what can you do for me?";
|
||||
|
||||
private static Kernel BuildKernel()
|
||||
{
|
||||
return Kernel.CreateBuilder()
|
||||
.AddAzureOpenAIChatCompletion(
|
||||
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
|
||||
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
|
||||
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
|
||||
serviceId: "AzureOpenAIChat",
|
||||
modelId: TestConfiguration.AzureOpenAI.ChatModelId)
|
||||
.AddOpenAIChatCompletion(
|
||||
modelId: TestConfiguration.OpenAI.ChatModelId,
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey,
|
||||
serviceId: "OpenAIChat")
|
||||
.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows how to invoke a prompt and specify the service id of the preferred AI service. When the prompt is executed the AI Service with the matching service id will be selected.
|
||||
/// </summary>
|
||||
/// <param name="serviceId">Service Id</param>
|
||||
[Theory]
|
||||
[InlineData("AzureOpenAIChat")]
|
||||
public async Task InvokePromptByServiceIdAsync(string serviceId)
|
||||
{
|
||||
var kernel = BuildKernel();
|
||||
Console.WriteLine($"======== Service Id: {serviceId} ========");
|
||||
|
||||
var result = await kernel.InvokePromptAsync(ChatPrompt, new(new PromptExecutionSettings { ServiceId = serviceId }));
|
||||
|
||||
Console.WriteLine(result.GetValue<string>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows how to invoke a prompt and specify the model id of the preferred AI service. When the prompt is executed the AI Service with the matching model id will be selected.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
private async Task InvokePromptByModelIdAsync()
|
||||
{
|
||||
var modelId = TestConfiguration.OpenAI.ChatModelId;
|
||||
var kernel = BuildKernel();
|
||||
Console.WriteLine($"======== Model Id: {modelId} ========");
|
||||
|
||||
var result = await kernel.InvokePromptAsync(ChatPrompt, new(new PromptExecutionSettings() { ModelId = modelId }));
|
||||
|
||||
Console.WriteLine(result.GetValue<string>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows how to invoke a prompt and specify the service ids of the preferred AI services.
|
||||
/// When the prompt is executed the AI Service will be selected based on the order of the provided service ids.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task InvokePromptFunctionWithFirstMatchingServiceIdAsync()
|
||||
{
|
||||
string[] serviceIds = ["NotFound", "AzureOpenAIChat", "OpenAIChat"];
|
||||
var kernel = BuildKernel();
|
||||
Console.WriteLine($"======== Service Ids: {string.Join(", ", serviceIds)} ========");
|
||||
|
||||
var result = await kernel.InvokePromptAsync(ChatPrompt, new(serviceIds.Select(serviceId => new PromptExecutionSettings { ServiceId = serviceId })));
|
||||
|
||||
Console.WriteLine(result.GetValue<string>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows how to invoke a prompt and specify the model ids of the preferred AI services.
|
||||
/// When the prompt is executed the AI Service will be selected based on the order of the provided model ids.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task InvokePromptFunctionWithFirstMatchingModelIdAsync()
|
||||
{
|
||||
string[] modelIds = ["gpt-4-1106-preview", TestConfiguration.AzureOpenAI.ChatModelId, TestConfiguration.OpenAI.ChatModelId];
|
||||
var kernel = BuildKernel();
|
||||
Console.WriteLine($"======== Model Ids: {string.Join(", ", modelIds)} ========");
|
||||
|
||||
var result = await kernel.InvokePromptAsync(ChatPrompt, new(modelIds.Select((modelId, index) => new PromptExecutionSettings { ServiceId = $"service-{index}", ModelId = modelId })));
|
||||
|
||||
Console.WriteLine(result.GetValue<string>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows how to create a KernelFunction from a prompt and specify the service ids of the preferred AI services.
|
||||
/// When the function is invoked the AI Service will be selected based on the order of the provided service ids.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task InvokePreconfiguredFunctionWithFirstMatchingServiceIdAsync()
|
||||
{
|
||||
string[] serviceIds = ["NotFound", "AzureOpenAIChat", "OpenAIChat"];
|
||||
var kernel = BuildKernel();
|
||||
Console.WriteLine($"======== Service Ids: {string.Join(", ", serviceIds)} ========");
|
||||
|
||||
var function = kernel.CreateFunctionFromPrompt(ChatPrompt, serviceIds.Select(serviceId => new PromptExecutionSettings { ServiceId = serviceId }));
|
||||
var result = await kernel.InvokeAsync(function);
|
||||
|
||||
Console.WriteLine(result.GetValue<string>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows how to create a KernelFunction from a prompt and specify the model ids of the preferred AI services.
|
||||
/// When the function is invoked the AI Service will be selected based on the order of the provided model ids.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task InvokePreconfiguredFunctionWithFirstMatchingModelIdAsync()
|
||||
{
|
||||
string[] modelIds = ["gpt-4-1106-preview", TestConfiguration.AzureOpenAI.ChatModelId, TestConfiguration.OpenAI.ChatModelId];
|
||||
var kernel = BuildKernel();
|
||||
|
||||
Console.WriteLine($"======== Model Ids: {string.Join(", ", modelIds)} ========");
|
||||
|
||||
var function = kernel.CreateFunctionFromPrompt(ChatPrompt, modelIds.Select((modelId, index) => new PromptExecutionSettings { ServiceId = $"service-{index}", ModelId = modelId }));
|
||||
var result = await kernel.InvokeAsync(function);
|
||||
|
||||
Console.WriteLine(result.GetValue<string>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows how to invoke a KernelFunction and specify the model id of the AI Service the function will use.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task InvokePreconfiguredFunctionByModelIdAsync()
|
||||
{
|
||||
var modelId = TestConfiguration.OpenAI.ChatModelId;
|
||||
var kernel = BuildKernel();
|
||||
Console.WriteLine($"======== Model Id: {modelId} ========");
|
||||
|
||||
var function = kernel.CreateFunctionFromPrompt(ChatPrompt);
|
||||
var result = await kernel.InvokeAsync(function, new(new PromptExecutionSettings { ModelId = modelId }));
|
||||
|
||||
Console.WriteLine(result.GetValue<string>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows how to invoke a KernelFunction and specify the service id of the AI Service the function will use.
|
||||
/// </summary>
|
||||
/// <param name="serviceId">Service Id</param>
|
||||
[Theory]
|
||||
[InlineData("AzureOpenAIChat")]
|
||||
public async Task InvokePreconfiguredFunctionByServiceIdAsync(string serviceId)
|
||||
{
|
||||
var kernel = BuildKernel();
|
||||
Console.WriteLine($"======== Service Id: {serviceId} ========");
|
||||
|
||||
var function = kernel.CreateFunctionFromPrompt(ChatPrompt);
|
||||
var result = await kernel.InvokeAsync(function, new(new PromptExecutionSettings { ServiceId = serviceId }));
|
||||
|
||||
Console.WriteLine(result.GetValue<string>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows when specifying a non-existent ServiceId the kernel throws an exception.
|
||||
/// </summary>
|
||||
/// <param name="serviceId">Service Id</param>
|
||||
[Theory]
|
||||
[InlineData("NotFound")]
|
||||
public async Task InvokePromptByNonExistingServiceIdThrowsExceptionAsync(string serviceId)
|
||||
{
|
||||
var kernel = BuildKernel();
|
||||
Console.WriteLine($"======== Service Id: {serviceId} ========");
|
||||
|
||||
await Assert.ThrowsAsync<KernelException>(async () => await kernel.InvokePromptAsync(ChatPrompt, new(new PromptExecutionSettings { ServiceId = serviceId })));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows how in the execution settings when no model id is found it falls back to the default service.
|
||||
/// </summary>
|
||||
/// <param name="modelId">Model Id</param>
|
||||
[Theory]
|
||||
[InlineData("NotFound")]
|
||||
public async Task InvokePromptByNonExistingModelIdUsesDefaultServiceAsync(string modelId)
|
||||
{
|
||||
var kernel = BuildKernel();
|
||||
Console.WriteLine($"======== Model Id: {modelId} ========");
|
||||
|
||||
await kernel.InvokePromptAsync(ChatPrompt, new(new PromptExecutionSettings { ModelId = modelId }));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Google.Apis.Auth.OAuth2;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// These examples demonstrate different ways of using chat completion with Google VertexAI and GoogleAI APIs.
|
||||
/// </summary>
|
||||
public sealed class Google_GeminiChatCompletion(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task GoogleAIUsingChatCompletion()
|
||||
{
|
||||
Console.WriteLine("============= Google AI - Gemini Chat Completion =============");
|
||||
|
||||
string geminiApiKey = TestConfiguration.GoogleAI.ApiKey;
|
||||
string geminiModelId = TestConfiguration.GoogleAI.Gemini.ModelId;
|
||||
|
||||
if (geminiApiKey is null || geminiModelId is null)
|
||||
{
|
||||
Console.WriteLine("Gemini credentials not found. Skipping example.");
|
||||
return;
|
||||
}
|
||||
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddGoogleAIGeminiChatCompletion(
|
||||
modelId: geminiModelId,
|
||||
apiKey: geminiApiKey)
|
||||
.Build();
|
||||
|
||||
await this.ProcessChatAsync(kernel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VertexAIUsingChatCompletion()
|
||||
{
|
||||
Console.WriteLine("============= Vertex AI - Gemini Chat Completion =============");
|
||||
|
||||
string? bearerToken = null;
|
||||
Assert.NotNull(TestConfiguration.VertexAI.ClientId);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.ClientSecret);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.Location);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.ProjectId);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.Gemini.ModelId);
|
||||
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddVertexAIGeminiChatCompletion(
|
||||
modelId: TestConfiguration.VertexAI.Gemini.ModelId,
|
||||
bearerTokenProvider: GetBearerToken,
|
||||
location: TestConfiguration.VertexAI.Location,
|
||||
projectId: TestConfiguration.VertexAI.ProjectId)
|
||||
.Build();
|
||||
|
||||
// To generate bearer key, you need installed google sdk or use google web console with command:
|
||||
//
|
||||
// gcloud auth print-access-token
|
||||
//
|
||||
// Above code pass bearer key as string, it is not recommended way in production code,
|
||||
// especially if IChatCompletionService will be long lived, tokens generated by google sdk lives for 1 hour.
|
||||
// You should use bearer key provider, which will be used to generate token on demand:
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// Kernel kernel = Kernel.CreateBuilder()
|
||||
// .AddVertexAIGeminiChatCompletion(
|
||||
// modelId: TestConfiguration.VertexAI.Gemini.ModelId,
|
||||
// bearerKeyProvider: () =>
|
||||
// {
|
||||
// // This is just example, in production we recommend using Google SDK to generate your BearerKey token.
|
||||
// // This delegate will be called on every request,
|
||||
// // when providing the token consider using caching strategy and refresh token logic when it is expired or close to expiration.
|
||||
// return GetBearerToken();
|
||||
// },
|
||||
// location: TestConfiguration.VertexAI.Location,
|
||||
// projectId: TestConfiguration.VertexAI.ProjectId);
|
||||
|
||||
async ValueTask<string> GetBearerToken()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(bearerToken))
|
||||
{
|
||||
return bearerToken;
|
||||
}
|
||||
|
||||
var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
|
||||
new ClientSecrets
|
||||
{
|
||||
ClientId = TestConfiguration.VertexAI.ClientId,
|
||||
ClientSecret = TestConfiguration.VertexAI.ClientSecret
|
||||
},
|
||||
["https://www.googleapis.com/auth/cloud-platform"],
|
||||
"user",
|
||||
CancellationToken.None);
|
||||
|
||||
var userCredential = await credential.WaitAsync(CancellationToken.None);
|
||||
bearerToken = userCredential.Token.AccessToken;
|
||||
|
||||
return bearerToken;
|
||||
}
|
||||
|
||||
await this.ProcessChatAsync(kernel);
|
||||
}
|
||||
|
||||
private async Task ProcessChatAsync(Kernel kernel)
|
||||
{
|
||||
var chatHistory = new ChatHistory("You are an expert in the tool shop.");
|
||||
var chat = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
// First user message
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for new power tools, any suggestion?");
|
||||
await MessageOutputAsync(chatHistory);
|
||||
|
||||
// First assistant message
|
||||
var reply = await chat.GetChatMessageContentAsync(chatHistory);
|
||||
chatHistory.Add(reply);
|
||||
await MessageOutputAsync(chatHistory);
|
||||
|
||||
// Second user message
|
||||
chatHistory.AddUserMessage("I'm looking for a drill, a screwdriver and a hammer.");
|
||||
await MessageOutputAsync(chatHistory);
|
||||
|
||||
// Second assistant message
|
||||
reply = await chat.GetChatMessageContentAsync(chatHistory);
|
||||
chatHistory.Add(reply);
|
||||
await MessageOutputAsync(chatHistory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs the last message of the chat history
|
||||
/// </summary>
|
||||
private Task MessageOutputAsync(ChatHistory chatHistory)
|
||||
{
|
||||
var message = chatHistory.Last();
|
||||
|
||||
Console.WriteLine($"{message.Role}: {message.Content}");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text;
|
||||
using Google.Apis.Auth.OAuth2;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// These examples demonstrate different ways of using chat completion with Google VertexAI and GoogleAI APIs.
|
||||
/// </summary>
|
||||
public sealed class Google_GeminiChatCompletionStreaming(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task GoogleAIUsingStreamingChatCompletion()
|
||||
{
|
||||
Console.WriteLine("============= Google AI - Gemini Chat Completion =============");
|
||||
|
||||
string geminiApiKey = TestConfiguration.GoogleAI.ApiKey;
|
||||
string geminiModelId = TestConfiguration.GoogleAI.Gemini.ModelId;
|
||||
|
||||
if (geminiApiKey is null || geminiModelId is null)
|
||||
{
|
||||
Console.WriteLine("Gemini credentials not found. Skipping example.");
|
||||
return;
|
||||
}
|
||||
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddGoogleAIGeminiChatCompletion(
|
||||
modelId: geminiModelId,
|
||||
apiKey: geminiApiKey)
|
||||
.Build();
|
||||
|
||||
await this.ProcessStreamingChatAsync(kernel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VertexAIUsingStreamingChatCompletion()
|
||||
{
|
||||
Console.WriteLine("============= Vertex AI - Gemini Chat Completion =============");
|
||||
|
||||
string? bearerToken = null;
|
||||
Assert.NotNull(TestConfiguration.VertexAI.ClientId);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.ClientSecret);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.Location);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.ProjectId);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.Gemini.ModelId);
|
||||
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddVertexAIGeminiChatCompletion(
|
||||
modelId: TestConfiguration.VertexAI.Gemini.ModelId,
|
||||
bearerTokenProvider: GetBearerToken,
|
||||
location: TestConfiguration.VertexAI.Location,
|
||||
projectId: TestConfiguration.VertexAI.ProjectId)
|
||||
.Build();
|
||||
|
||||
// To generate bearer key, you need installed google sdk or use google web console with command:
|
||||
//
|
||||
// gcloud auth print-access-token
|
||||
//
|
||||
// Above code pass bearer key as string, it is not recommended way in production code,
|
||||
// especially if IChatCompletionService will be long lived, tokens generated by google sdk lives for 1 hour.
|
||||
// You should use bearer key provider, which will be used to generate token on demand:
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// Kernel kernel = Kernel.CreateBuilder()
|
||||
// .AddVertexAIGeminiChatCompletion(
|
||||
// modelId: TestConfiguration.VertexAI.Gemini.ModelId,
|
||||
// bearerKeyProvider: () =>
|
||||
// {
|
||||
// // This is just example, in production we recommend using Google SDK to generate your BearerKey token.
|
||||
// // This delegate will be called on every request,
|
||||
// // when providing the token consider using caching strategy and refresh token logic when it is expired or close to expiration.
|
||||
// return GetBearerKey();
|
||||
// },
|
||||
// location: TestConfiguration.VertexAI.Location,
|
||||
// projectId: TestConfiguration.VertexAI.ProjectId);
|
||||
|
||||
async ValueTask<string> GetBearerToken()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(bearerToken))
|
||||
{
|
||||
return bearerToken;
|
||||
}
|
||||
|
||||
var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
|
||||
new ClientSecrets
|
||||
{
|
||||
ClientId = TestConfiguration.VertexAI.ClientId,
|
||||
ClientSecret = TestConfiguration.VertexAI.ClientSecret
|
||||
},
|
||||
["https://www.googleapis.com/auth/cloud-platform"],
|
||||
"user",
|
||||
CancellationToken.None);
|
||||
|
||||
var userCredential = await credential.WaitAsync(CancellationToken.None);
|
||||
bearerToken = userCredential.Token.AccessToken;
|
||||
|
||||
return bearerToken;
|
||||
}
|
||||
|
||||
await this.ProcessStreamingChatAsync(kernel);
|
||||
}
|
||||
|
||||
private async Task ProcessStreamingChatAsync(Kernel kernel)
|
||||
{
|
||||
var chatHistory = new ChatHistory("You are an expert in the tool shop.");
|
||||
var chat = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
// First user message
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for alternative coffee brew methods, can you help me?");
|
||||
await MessageOutputAsync(chatHistory);
|
||||
|
||||
// First assistant message
|
||||
var streamingChat = chat.GetStreamingChatMessageContentsAsync(chatHistory);
|
||||
var reply = await MessageOutputAsync(streamingChat);
|
||||
chatHistory.Add(reply);
|
||||
|
||||
// Second user message
|
||||
chatHistory.AddUserMessage("Give me the best speciality coffee roasters.");
|
||||
await MessageOutputAsync(chatHistory);
|
||||
|
||||
// Second assistant message
|
||||
streamingChat = chat.GetStreamingChatMessageContentsAsync(chatHistory);
|
||||
reply = await MessageOutputAsync(streamingChat);
|
||||
chatHistory.Add(reply);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs the last message of the chat history
|
||||
/// </summary>
|
||||
private Task MessageOutputAsync(ChatHistory chatHistory)
|
||||
{
|
||||
var message = chatHistory.Last();
|
||||
|
||||
Console.WriteLine($"{message.Role}: {message.Content}");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task<ChatMessageContent> MessageOutputAsync(IAsyncEnumerable<StreamingChatMessageContent> streamingChat)
|
||||
{
|
||||
bool first = true;
|
||||
StringBuilder messageBuilder = new();
|
||||
await foreach (var chatMessage in streamingChat)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
Console.Write($"{chatMessage.Role}: ");
|
||||
first = false;
|
||||
}
|
||||
|
||||
Console.Write(chatMessage.Content);
|
||||
messageBuilder.Append(chatMessage.Content);
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("------------------------");
|
||||
return new ChatMessageContent(AuthorRole.Assistant, messageBuilder.ToString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Resources;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// This sample shows how to use binary file and inline Base64 inputs, like PDFs, with Google Gemini's chat completion.
|
||||
/// </summary>
|
||||
public class Google_GeminiChatCompletionWithFile(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task GoogleAIChatCompletionWithLocalFile()
|
||||
{
|
||||
Console.WriteLine("============= Google AI - Gemini Chat Completion With Local File =============");
|
||||
|
||||
Assert.NotNull(TestConfiguration.GoogleAI.ApiKey);
|
||||
Assert.NotNull(TestConfiguration.GoogleAI.Gemini.ModelId);
|
||||
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddGoogleAIGeminiChatCompletion(TestConfiguration.GoogleAI.Gemini.ModelId, TestConfiguration.GoogleAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
var fileBytes = await EmbeddedResource.ReadAllAsync("employees.pdf");
|
||||
|
||||
var chatHistory = new ChatHistory("You are a friendly assistant.");
|
||||
chatHistory.AddUserMessage(
|
||||
[
|
||||
new TextContent("What's in this file?"),
|
||||
new BinaryContent(fileBytes, "application/pdf")
|
||||
]);
|
||||
|
||||
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
|
||||
|
||||
Console.WriteLine(reply.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VertexAIChatCompletionWithLocalFile()
|
||||
{
|
||||
Console.WriteLine("============= Vertex AI - Gemini Chat Completion With Local File =============");
|
||||
|
||||
Assert.NotNull(TestConfiguration.VertexAI.BearerKey);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.Location);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.ProjectId);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.Gemini.ModelId);
|
||||
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddVertexAIGeminiChatCompletion(
|
||||
modelId: TestConfiguration.VertexAI.Gemini.ModelId,
|
||||
bearerKey: TestConfiguration.VertexAI.BearerKey,
|
||||
location: TestConfiguration.VertexAI.Location,
|
||||
projectId: TestConfiguration.VertexAI.ProjectId)
|
||||
.Build();
|
||||
|
||||
var fileBytes = await EmbeddedResource.ReadAllAsync("employees.pdf");
|
||||
|
||||
var chatHistory = new ChatHistory("You are a friendly assistant.");
|
||||
chatHistory.AddUserMessage(
|
||||
[
|
||||
new TextContent("What's in this file?"),
|
||||
new BinaryContent(fileBytes, "application/pdf"),
|
||||
]);
|
||||
|
||||
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
|
||||
|
||||
Console.WriteLine(reply.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GoogleAIChatCompletionWithBase64DataUri()
|
||||
{
|
||||
Console.WriteLine("============= Google AI - Gemini Chat Completion With Base64 Data Uri =============");
|
||||
|
||||
Assert.NotNull(TestConfiguration.GoogleAI.ApiKey);
|
||||
Assert.NotNull(TestConfiguration.GoogleAI.Gemini.ModelId);
|
||||
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddGoogleAIGeminiChatCompletion(TestConfiguration.GoogleAI.Gemini.ModelId, TestConfiguration.GoogleAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
var fileBytes = await EmbeddedResource.ReadAllAsync("employees.pdf");
|
||||
var fileBase64 = Convert.ToBase64String(fileBytes.ToArray());
|
||||
var dataUri = $"data:application/pdf;base64,{fileBase64}";
|
||||
|
||||
var chatHistory = new ChatHistory("You are a friendly assistant.");
|
||||
chatHistory.AddUserMessage(
|
||||
[
|
||||
new TextContent("What's in this file?"),
|
||||
new BinaryContent(dataUri)
|
||||
// Google AI Gemini AI does not support arbitrary URIs but we can convert a Base64 URI into InlineData with the correct mimeType.
|
||||
]);
|
||||
|
||||
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
|
||||
|
||||
Console.WriteLine(reply.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VertexAIChatCompletionWithBase64DataUri()
|
||||
{
|
||||
Console.WriteLine("============= Vertex AI - Gemini Chat Completion With Base64 Data Uri =============");
|
||||
|
||||
Assert.NotNull(TestConfiguration.VertexAI.BearerKey);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.Location);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.ProjectId);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.Gemini.ModelId);
|
||||
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddVertexAIGeminiChatCompletion(
|
||||
modelId: TestConfiguration.VertexAI.Gemini.ModelId,
|
||||
bearerKey: TestConfiguration.VertexAI.BearerKey,
|
||||
location: TestConfiguration.VertexAI.Location,
|
||||
projectId: TestConfiguration.VertexAI.ProjectId)
|
||||
.Build();
|
||||
|
||||
var fileBytes = await EmbeddedResource.ReadAllAsync("employees.pdf");
|
||||
var fileBase64 = Convert.ToBase64String(fileBytes.ToArray());
|
||||
var dataUri = $"data:application/pdf;base64,{fileBase64}";
|
||||
|
||||
var chatHistory = new ChatHistory("You are a friendly assistant.");
|
||||
chatHistory.AddUserMessage(
|
||||
[
|
||||
new TextContent("What's in this file?"),
|
||||
new BinaryContent(dataUri)
|
||||
// Vertex AI API does not support URIs outside of inline Base64 or GCS buckets within the same project. The bucket that stores the file must be in the same Google Cloud project that's sending the request. You must always provide the mimeType via the metadata property.
|
||||
// var content = new BinaryContent(gs://generativeai-downloads/files/employees.pdf);
|
||||
// content.Metadata = new Dictionary<string, object?> { { "mimeType", "application/pdf" } };
|
||||
]);
|
||||
|
||||
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
|
||||
|
||||
Console.WriteLine(reply.Content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.Google;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// These examples demonstrate different ways of using chat completion with Google AI API.
|
||||
/// <para>
|
||||
/// Currently thinking is only supported in Google AI Gemini 2.5+ models.
|
||||
/// For Gemini 2.5 models, use ThinkingBudget. For Gemini 3.0 and later, use ThinkingLevel.
|
||||
/// See: https://developers.googleblog.com/en/start-building-with-gemini-25-flash/#:~:text=thinking%20budgets
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class Google_GeminiChatCompletionWithThinking(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task GoogleAIChatCompletionUsingThinkingBudget()
|
||||
{
|
||||
Console.WriteLine("============= Google AI - Gemini 2.5 Chat Completion using Thinking Budget =============");
|
||||
|
||||
Assert.NotNull(TestConfiguration.GoogleAI.ApiKey);
|
||||
string geminiModelId = "gemini-2.5-pro-exp-03-25";
|
||||
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddGoogleAIGeminiChatCompletion(
|
||||
modelId: geminiModelId,
|
||||
apiKey: TestConfiguration.GoogleAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
var chatHistory = new ChatHistory("You are an expert in the tool shop.");
|
||||
var chat = kernel.GetRequiredService<IChatCompletionService>();
|
||||
var executionSettings = new GeminiPromptExecutionSettings
|
||||
{
|
||||
// This parameter gives the model how much tokens it can use during the thinking process.
|
||||
ThinkingConfig = new() { ThinkingBudget = 2000 }
|
||||
};
|
||||
|
||||
// First user message
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for new power tools, any suggestion?");
|
||||
await MessageOutputAsync(chatHistory);
|
||||
|
||||
// First assistant message
|
||||
var reply = await chat.GetChatMessageContentAsync(chatHistory, executionSettings);
|
||||
chatHistory.Add(reply);
|
||||
await MessageOutputAsync(chatHistory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GoogleAIChatCompletionUsingThinkingLevel()
|
||||
{
|
||||
Console.WriteLine("============= Google AI - Gemini 3.0 Chat Completion using Thinking Level =============");
|
||||
|
||||
Assert.NotNull(TestConfiguration.GoogleAI.ApiKey);
|
||||
string geminiModelId = "gemini-3.0-flash";
|
||||
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddGoogleAIGeminiChatCompletion(
|
||||
modelId: geminiModelId,
|
||||
apiKey: TestConfiguration.GoogleAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
var chatHistory = new ChatHistory("You are an expert in the tool shop.");
|
||||
var chat = kernel.GetRequiredService<IChatCompletionService>();
|
||||
var executionSettings = new GeminiPromptExecutionSettings
|
||||
{
|
||||
// This parameter specifies the thinking level for Gemini 3.0+ models.
|
||||
// Possible values are "none", "low", "medium", and "high".
|
||||
ThinkingConfig = new() { ThinkingLevel = "high" }
|
||||
};
|
||||
|
||||
// First user message
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for new power tools, any suggestion?");
|
||||
await MessageOutputAsync(chatHistory);
|
||||
|
||||
// First assistant message
|
||||
var reply = await chat.GetChatMessageContentAsync(chatHistory, executionSettings);
|
||||
chatHistory.Add(reply);
|
||||
await MessageOutputAsync(chatHistory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs the last message of the chat history
|
||||
/// </summary>
|
||||
private Task MessageOutputAsync(ChatHistory chatHistory)
|
||||
{
|
||||
var message = chatHistory.Last();
|
||||
|
||||
Console.WriteLine($"{message.Role}: {message.Content}");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Connectors.Google;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an example class for Gemini Embedding Generation with volatile memory store.
|
||||
/// </summary>
|
||||
public sealed class Google_GeminiGetModelResult(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task GetTokenUsageMetadata()
|
||||
{
|
||||
Console.WriteLine("======== Inline Function Definition + Invocation ========");
|
||||
|
||||
Assert.NotNull(TestConfiguration.VertexAI.BearerKey);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.Location);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.ProjectId);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.Gemini.ModelId);
|
||||
|
||||
// Create kernel
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddVertexAIGeminiChatCompletion(
|
||||
modelId: TestConfiguration.VertexAI.Gemini.ModelId,
|
||||
bearerKey: TestConfiguration.VertexAI.BearerKey,
|
||||
location: TestConfiguration.VertexAI.Location,
|
||||
projectId: TestConfiguration.VertexAI.ProjectId)
|
||||
.Build();
|
||||
|
||||
// To generate bearer key, you need installed google sdk or use google web console with command:
|
||||
//
|
||||
// gcloud auth print-access-token
|
||||
//
|
||||
// Above code pass bearer key as string, it is not recommended way in production code,
|
||||
// especially if IChatCompletionService will be long lived, tokens generated by google sdk lives for 1 hour.
|
||||
// You should use bearer key provider, which will be used to generate token on demand:
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// Kernel kernel = Kernel.CreateBuilder()
|
||||
// .AddVertexAIGeminiChatCompletion(
|
||||
// modelId: TestConfiguration.VertexAI.Gemini.ModelId,
|
||||
// bearerKeyProvider: () =>
|
||||
// {
|
||||
// // This is just example, in production we recommend using Google SDK to generate your BearerKey token.
|
||||
// // This delegate will be called on every request,
|
||||
// // when providing the token consider using caching strategy and refresh token logic when it is expired or close to expiration.
|
||||
// return GetBearerKey();
|
||||
// },
|
||||
// location: TestConfiguration.VertexAI.Location,
|
||||
// projectId: TestConfiguration.VertexAI.ProjectId)
|
||||
|
||||
string prompt = "Hi, give me 5 book suggestions about: travel";
|
||||
|
||||
// Invoke function through kernel
|
||||
FunctionResult result = await kernel.InvokePromptAsync(prompt);
|
||||
|
||||
// Display results
|
||||
var geminiMetadata = result.Metadata as GeminiMetadata;
|
||||
Console.WriteLine(result.GetValue<string>());
|
||||
Console.WriteLine(geminiMetadata?.AsJson());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Google.Apis.Auth.OAuth2;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Connectors.Google;
|
||||
using OpenAI.Chat;
|
||||
using Directory = System.IO.Directory;
|
||||
using File = System.IO.File;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// Structured Outputs is a feature in Vertex API that ensures the model will always generate responses based on provided JSON Schema.
|
||||
/// This gives more control over model responses, allows to avoid model hallucinations and write simpler prompts without a need to be specific about response format.
|
||||
/// More information here: <see href="https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/control-generated-output#model_behavior_and_response_schema"/>.
|
||||
/// </summary>
|
||||
public class Google_GeminiStructuredOutputs(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// This method shows how to enable Structured Outputs feature with <see cref="ChatResponseFormat"/> object by providing
|
||||
/// JSON schema of desired response format.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task StructuredOutputsWithTypeInExecutionSettings(bool useGoogleAI)
|
||||
{
|
||||
var kernel = this.InitializeKernel(useGoogleAI);
|
||||
|
||||
GeminiPromptExecutionSettings executionSettings = new()
|
||||
{
|
||||
ResponseMimeType = "application/json",
|
||||
// Send a request and pass prompt execution settings with desired response schema.
|
||||
ResponseSchema = typeof(User)
|
||||
};
|
||||
|
||||
var result = await kernel.InvokePromptAsync("Extract the data from the following text: My name is Praveen", new(executionSettings));
|
||||
|
||||
var user = JsonSerializer.Deserialize<User>(result.ToString())!;
|
||||
this.OutputResult(user);
|
||||
|
||||
// Send a request and pass prompt execution settings with desired response schema.
|
||||
executionSettings.ResponseSchema = typeof(MovieResult);
|
||||
result = await kernel.InvokePromptAsync("What are the top 10 movies of all time?", new(executionSettings));
|
||||
|
||||
// Deserialize string response to a strong type to access type properties.
|
||||
// At this point, the deserialization logic won't fail, because MovieResult type was described using JSON schema.
|
||||
// This ensures that response string is a serialized version of MovieResult type.
|
||||
var movieResult = JsonSerializer.Deserialize<MovieResult>(result.ToString())!;
|
||||
|
||||
// Output the result.
|
||||
this.OutputResult(movieResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method shows how to use Structured Outputs feature in combination with Function Calling and Gemini models.
|
||||
/// <see cref="EmailPlugin.GetEmails"/> function returns a <see cref="List{T}"/> of email bodies.
|
||||
/// As for final result, the desired response format should be <see cref="Email"/>, which contains additional <see cref="Email.Category"/> property.
|
||||
/// This shows how the data can be transformed with AI using strong types without additional instructions in the prompt.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task StructuredOutputsWithFunctionCalling(bool useGoogleAI)
|
||||
{
|
||||
// Initialize kernel.
|
||||
var kernel = this.InitializeKernel(useGoogleAI);
|
||||
|
||||
kernel.ImportPluginFromType<EmailPlugin>();
|
||||
|
||||
// Specify response format by setting Type object in prompt execution settings and enable automatic function calling.
|
||||
var executionSettings = new GeminiPromptExecutionSettings
|
||||
{
|
||||
ResponseSchema = typeof(EmailResult),
|
||||
ResponseMimeType = "application/json",
|
||||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
|
||||
};
|
||||
|
||||
// Send a request and pass prompt execution settings with desired response format.
|
||||
var result = await kernel.InvokePromptAsync("Process the emails.", new(executionSettings));
|
||||
|
||||
// Deserialize string response to a strong type to access type properties.
|
||||
// At this point, the deserialization logic won't fail, because EmailResult type was specified as desired response format.
|
||||
// This ensures that response string is a serialized version of EmailResult type.
|
||||
var emailResult = JsonSerializer.Deserialize<EmailResult>(result.ToString())!;
|
||||
|
||||
// Output the result.
|
||||
this.OutputResult(emailResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method shows how to enable Structured Outputs feature with Semantic Kernel functions from prompt
|
||||
/// using Semantic Kernel template engine.
|
||||
/// In this scenario, JSON Schema for response is specified in a prompt configuration file.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task StructuredOutputsWithFunctionsFromPrompt(bool useGoogleAI)
|
||||
{
|
||||
// Initialize kernel.
|
||||
var kernel = this.InitializeKernel(useGoogleAI);
|
||||
|
||||
// Initialize a path to plugin directory: Resources/Plugins/MoviePlugins/MoviePluginPrompt.
|
||||
var pluginDirectoryPath = Path.Combine(Directory.GetCurrentDirectory(), "Resources", "Plugins", "MoviePlugins", "MoviePluginPrompt");
|
||||
|
||||
// Create a function from prompt.
|
||||
kernel.ImportPluginFromPromptDirectory(pluginDirectoryPath, pluginName: "MoviePlugin");
|
||||
|
||||
var executionSettings = new GeminiPromptExecutionSettings
|
||||
{
|
||||
ResponseSchema = typeof(MovieResult),
|
||||
ResponseMimeType = "application/json",
|
||||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
|
||||
};
|
||||
|
||||
var result = await kernel.InvokeAsync("MoviePlugin", "TopMovies", new(executionSettings));
|
||||
|
||||
// Deserialize string response to a strong type to access type properties.
|
||||
// At this point, the deserialization logic won't fail, because MovieResult type was specified as desired response format.
|
||||
// This ensures that response string is a serialized version of MovieResult type.
|
||||
var movieResult = JsonSerializer.Deserialize<MovieResult>(result.ToString())!;
|
||||
|
||||
// Output the result.
|
||||
this.OutputResult(movieResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method shows how to enable Structured Outputs feature with Semantic Kernel functions from YAML
|
||||
/// using Semantic Kernel template engine.
|
||||
/// In this scenario, JSON Schema for response is specified in YAML prompt file.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task StructuredOutputsWithFunctionsFromYaml(bool useGoogleAI)
|
||||
{
|
||||
// Initialize kernel.
|
||||
var kernel = this.InitializeKernel(useGoogleAI);
|
||||
|
||||
// Initialize a path to YAML function: Resources/Plugins/MoviePlugins/MoviePluginYaml.
|
||||
var functionPath = Path.Combine(Directory.GetCurrentDirectory(), "Resources", "Plugins", "MoviePlugins", "MoviePluginYaml", "TopMovies.yaml");
|
||||
|
||||
// Load YAML prompt.
|
||||
var topMoviesYaml = File.ReadAllText(functionPath);
|
||||
|
||||
// Import a function from YAML.
|
||||
var function = kernel.CreateFunctionFromPromptYaml(topMoviesYaml);
|
||||
kernel.ImportPluginFromFunctions("MoviePlugin", [function]);
|
||||
|
||||
var executionSettings = new GeminiPromptExecutionSettings
|
||||
{
|
||||
ResponseSchema = typeof(MovieResult),
|
||||
ResponseMimeType = "application/json",
|
||||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
|
||||
};
|
||||
|
||||
var result = await kernel.InvokeAsync("MoviePlugin", "TopMovies", new(executionSettings));
|
||||
|
||||
// Deserialize string response to a strong type to access type properties.
|
||||
// At this point, the deserialization logic won't fail, because MovieResult type was specified as desired response format.
|
||||
// This ensures that response string is a serialized version of MovieResult type.
|
||||
var movieResult = JsonSerializer.Deserialize<MovieResult>(result.ToString())!;
|
||||
|
||||
// Output the result.
|
||||
this.OutputResult(movieResult);
|
||||
}
|
||||
|
||||
#region private
|
||||
|
||||
/// <summary>Movie result struct that will be used as desired chat completion response format (structured output).</summary>
|
||||
private struct MovieResult
|
||||
{
|
||||
public List<Movie> Movies { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>Movie struct that will be used as desired chat completion response format (structured output).</summary>
|
||||
private struct Movie
|
||||
{
|
||||
public string Title { get; set; }
|
||||
|
||||
public string Director { get; set; }
|
||||
|
||||
public int ReleaseYear { get; set; }
|
||||
|
||||
public double Rating { get; set; }
|
||||
|
||||
public bool IsAvailableOnStreaming { get; set; }
|
||||
|
||||
public MovieGenre? Genre { get; set; }
|
||||
|
||||
public List<string> Tags { get; set; }
|
||||
}
|
||||
|
||||
private enum MovieGenre
|
||||
{
|
||||
Action,
|
||||
Adventure,
|
||||
Comedy,
|
||||
Drama,
|
||||
Fantasy,
|
||||
Horror,
|
||||
Mystery,
|
||||
Romance,
|
||||
SciFi,
|
||||
Thriller,
|
||||
Western
|
||||
}
|
||||
|
||||
private sealed class EmailResult
|
||||
{
|
||||
public List<Email> Emails { get; set; }
|
||||
}
|
||||
|
||||
private sealed class Email
|
||||
{
|
||||
public string Body { get; set; }
|
||||
|
||||
public string Category { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>Plugin to simulate RAG scenario and return collection of data.</summary>
|
||||
private sealed class EmailPlugin
|
||||
{
|
||||
/// <summary>Function to simulate RAG scenario and return collection of data.</summary>
|
||||
[KernelFunction]
|
||||
private List<string> GetEmails()
|
||||
{
|
||||
return
|
||||
[
|
||||
"Hey, just checking in to see how you're doing!",
|
||||
"Can you pick up some groceries on your way back home? We need milk and bread.",
|
||||
"Happy Birthday! Wishing you a fantastic day filled with love and joy.",
|
||||
"Let's catch up over coffee this Saturday. It's been too long!",
|
||||
"Please review the attached document and provide your feedback by EOD.",
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
[Description("User")]
|
||||
private sealed class User
|
||||
{
|
||||
[Description("This field contains name of user")]
|
||||
[JsonPropertyName("name")]
|
||||
[AllowNull]
|
||||
public string? Name { get; set; }
|
||||
|
||||
[Description("This field contains user email")]
|
||||
[JsonPropertyName("email")]
|
||||
[AllowNull]
|
||||
public string? Email { get; set; }
|
||||
|
||||
[Description("This field contains user age")]
|
||||
[JsonPropertyName("age")]
|
||||
[AllowNull]
|
||||
public int? Age { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>Helper method to output <see cref="MovieResult"/> object content.</summary>
|
||||
private void OutputResult(MovieResult movieResult)
|
||||
{
|
||||
for (var i = 0; i < movieResult.Movies.Count; i++)
|
||||
{
|
||||
var movie = movieResult.Movies[i];
|
||||
|
||||
this.Output.WriteLine($"""
|
||||
- Movie #{i + 1}
|
||||
Title: {movie.Title}
|
||||
Director: {movie.Director}
|
||||
Release year: {movie.ReleaseYear}
|
||||
Rating: {movie.Rating}
|
||||
Genre: {movie.Genre}
|
||||
Is available on streaming: {movie.IsAvailableOnStreaming}
|
||||
Tags: {string.Join(",", movie.Tags ?? [])}
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Helper method to output <see cref="EmailResult"/> object content.</summary>
|
||||
private void OutputResult(EmailResult emailResult)
|
||||
{
|
||||
for (var i = 0; i < emailResult.Emails.Count; i++)
|
||||
{
|
||||
var email = emailResult.Emails[i];
|
||||
|
||||
this.Output.WriteLine($"""
|
||||
- Email #{i + 1}
|
||||
Body: {email.Body}
|
||||
Category: {email.Category}
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
||||
private void OutputResult(User user)
|
||||
{
|
||||
this.Output.WriteLine($"""
|
||||
- User
|
||||
Name: {user.Name}
|
||||
Email: {user.Email}
|
||||
Age: {user.Age}
|
||||
""");
|
||||
}
|
||||
|
||||
private Kernel InitializeKernel(bool useGoogleAI)
|
||||
{
|
||||
Kernel kernel;
|
||||
if (useGoogleAI)
|
||||
{
|
||||
this.Console.WriteLine("============= Google AI - Gemini Chat Completion Structured Outputs =============");
|
||||
|
||||
Assert.NotNull(TestConfiguration.GoogleAI.ApiKey);
|
||||
Assert.NotNull(TestConfiguration.GoogleAI.Gemini.ModelId);
|
||||
|
||||
kernel = Kernel.CreateBuilder()
|
||||
.AddGoogleAIGeminiChatCompletion(
|
||||
modelId: TestConfiguration.GoogleAI.Gemini.ModelId,
|
||||
apiKey: TestConfiguration.GoogleAI.ApiKey)
|
||||
.Build();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Console.WriteLine("============= Vertex AI - Gemini Chat Completion Structured Outputs =============");
|
||||
|
||||
Assert.NotNull(TestConfiguration.VertexAI.ClientId);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.ClientSecret);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.Location);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.ProjectId);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.Gemini.ModelId);
|
||||
|
||||
string? bearerToken = TestConfiguration.VertexAI.BearerKey;
|
||||
kernel = Kernel.CreateBuilder()
|
||||
.AddVertexAIGeminiChatCompletion(
|
||||
modelId: TestConfiguration.VertexAI.Gemini.ModelId,
|
||||
bearerTokenProvider: GetBearerToken,
|
||||
location: TestConfiguration.VertexAI.Location,
|
||||
projectId: TestConfiguration.VertexAI.ProjectId)
|
||||
.Build();
|
||||
|
||||
// To generate bearer key, you need installed google sdk or use google web console with command:
|
||||
//
|
||||
// gcloud auth print-access-token
|
||||
//
|
||||
// Above code pass bearer key as string, it is not recommended way in production code,
|
||||
// especially if IChatCompletionService will be long lived, tokens generated by google sdk lives for 1 hour.
|
||||
// You should use bearer key provider, which will be used to generate token on demand:
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// Kernel kernel = Kernel.CreateBuilder()
|
||||
// .AddVertexAIGeminiChatCompletion(
|
||||
// modelId: TestConfiguration.VertexAI.Gemini.ModelId,
|
||||
// bearerKeyProvider: () =>
|
||||
// {
|
||||
// // This is just example, in production we recommend using Google SDK to generate your BearerKey token.
|
||||
// // This delegate will be called on every request,
|
||||
// // when providing the token consider using caching strategy and refresh token logic when it is expired or close to expiration.
|
||||
// return GetBearerToken();
|
||||
// },
|
||||
// location: TestConfiguration.VertexAI.Location,
|
||||
// projectId: TestConfiguration.VertexAI.ProjectId);
|
||||
|
||||
async ValueTask<string> GetBearerToken()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(bearerToken))
|
||||
{
|
||||
return bearerToken;
|
||||
}
|
||||
|
||||
var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
|
||||
new ClientSecrets
|
||||
{
|
||||
ClientId = TestConfiguration.VertexAI.ClientId,
|
||||
ClientSecret = TestConfiguration.VertexAI.ClientSecret
|
||||
},
|
||||
["https://www.googleapis.com/auth/cloud-platform"],
|
||||
"user",
|
||||
CancellationToken.None);
|
||||
|
||||
var userCredential = await credential.WaitAsync(CancellationToken.None);
|
||||
bearerToken = userCredential.Token.AccessToken;
|
||||
|
||||
return bearerToken;
|
||||
}
|
||||
}
|
||||
|
||||
return kernel;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Resources;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// This sample shows how to use Google's Gemini Chat Completion model with vision using VertexAI and GoogleAI APIs.
|
||||
/// </summary>
|
||||
public sealed class Google_GeminiVision(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task GoogleAIChatCompletionWithVision()
|
||||
{
|
||||
Console.WriteLine("============= Google AI - Gemini Chat Completion with vision =============");
|
||||
|
||||
string geminiApiKey = TestConfiguration.GoogleAI.ApiKey;
|
||||
string geminiModelId = TestConfiguration.GoogleAI.Gemini.ModelId;
|
||||
|
||||
if (geminiApiKey is null)
|
||||
{
|
||||
Console.WriteLine("Gemini credentials not found. Skipping example.");
|
||||
return;
|
||||
}
|
||||
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddGoogleAIGeminiChatCompletion(
|
||||
modelId: geminiModelId,
|
||||
apiKey: geminiApiKey)
|
||||
.Build();
|
||||
|
||||
var chatHistory = new ChatHistory("Your job is describing images.");
|
||||
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
// Load the image from the resources
|
||||
await using var stream = EmbeddedResource.ReadStream("sample_image.jpg")!;
|
||||
using var binaryReader = new BinaryReader(stream);
|
||||
var bytes = binaryReader.ReadBytes((int)stream.Length);
|
||||
|
||||
chatHistory.AddUserMessage(
|
||||
[
|
||||
new TextContent("What’s in this image?"),
|
||||
// Google AI Gemini API requires the image to be in base64 format, doesn't support URI
|
||||
// You have to always provide the mimeType for the image
|
||||
new ImageContent(bytes, "image/jpeg"),
|
||||
]);
|
||||
|
||||
var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
|
||||
|
||||
Console.WriteLine(reply.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VertexAIChatCompletionWithVision()
|
||||
{
|
||||
Console.WriteLine("============= Vertex AI - Gemini Chat Completion with vision =============");
|
||||
|
||||
Assert.NotNull(TestConfiguration.VertexAI.BearerKey);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.Location);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.ProjectId);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.Gemini.ModelId);
|
||||
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddVertexAIGeminiChatCompletion(
|
||||
modelId: TestConfiguration.VertexAI.Gemini.ModelId,
|
||||
bearerKey: TestConfiguration.VertexAI.BearerKey,
|
||||
location: TestConfiguration.VertexAI.Location,
|
||||
projectId: TestConfiguration.VertexAI.ProjectId)
|
||||
.Build();
|
||||
|
||||
// To generate bearer key, you need installed google sdk or use google web console with command:
|
||||
//
|
||||
// gcloud auth print-access-token
|
||||
//
|
||||
// Above code pass bearer key as string, it is not recommended way in production code,
|
||||
// especially if IChatCompletionService will be long lived, tokens generated by google sdk lives for 1 hour.
|
||||
// You should use bearer key provider, which will be used to generate token on demand:
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// Kernel kernel = Kernel.CreateBuilder()
|
||||
// .AddVertexAIGeminiChatCompletion(
|
||||
// modelId: TestConfiguration.VertexAI.Gemini.ModelId,
|
||||
// bearerKeyProvider: () =>
|
||||
// {
|
||||
// // This is just example, in production we recommend using Google SDK to generate your BearerKey token.
|
||||
// // This delegate will be called on every request,
|
||||
// // when providing the token consider using caching strategy and refresh token logic when it is expired or close to expiration.
|
||||
// return GetBearerKey();
|
||||
// },
|
||||
// location: TestConfiguration.VertexAI.Location,
|
||||
// projectId: TestConfiguration.VertexAI.ProjectId);
|
||||
|
||||
var chatHistory = new ChatHistory("Your job is describing images.");
|
||||
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
// Load the image from the resources
|
||||
await using var stream = EmbeddedResource.ReadStream("sample_image.jpg")!;
|
||||
using var binaryReader = new BinaryReader(stream);
|
||||
var bytes = binaryReader.ReadBytes((int)stream.Length);
|
||||
|
||||
chatHistory.AddUserMessage(
|
||||
[
|
||||
new TextContent("What’s in this image?"),
|
||||
// Vertex AI Gemini API supports both base64 and URI format
|
||||
// You have to always provide the mimeType for the image
|
||||
new ImageContent(bytes, "image/jpeg"),
|
||||
// The Cloud Storage URI of the image to include in the prompt.
|
||||
// The bucket that stores the file must be in the same Google Cloud project that's sending the request.
|
||||
// new ImageContent(new Uri("gs://generativeai-downloads/images/scones.jpg"),
|
||||
// metadata: new Dictionary<string, object?> { { "mimeType", "image/jpeg" } })
|
||||
]);
|
||||
|
||||
var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
|
||||
|
||||
Console.WriteLine(reply.Content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.HuggingFace;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// This example shows a way of using Hugging Face connector with HuggingFace Text Generation Inference (TGI) API.
|
||||
/// Follow steps in <see href="https://huggingface.co/docs/text-generation-inference/main/en/quicktour"/> to setup HuggingFace local Text Generation Inference HTTP server.
|
||||
/// <list type="number">
|
||||
/// <item>Install HuggingFace TGI via docker</item>
|
||||
/// <item><c>docker run -d --gpus all --shm-size 1g -p 8080:80 -v "c:\temp\huggingface:/data" ghcr.io/huggingface/text-generation-inference:latest --model-id teknium/OpenHermes-2.5-Mistral-7B</c></item>
|
||||
/// <item>Run the examples</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public class HuggingFace_ChatCompletion(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// This example shows how to setup LMStudio to use with the <see cref="Kernel"/> InvokeAsync (Non-Streaming).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
#pragma warning restore CS0419 // Ambiguous reference in cref attribute
|
||||
public async Task UsingKernelNonStreamingWithHuggingFace()
|
||||
{
|
||||
Console.WriteLine($"======== HuggingFace - Chat Completion - {nameof(UsingKernelNonStreamingWithHuggingFace)} ========");
|
||||
|
||||
var endpoint = new Uri("http://localhost:8080"); // Update the endpoint if you chose a different port. (defaults to 8080)
|
||||
var modelId = "teknium/OpenHermes-2.5-Mistral-7B"; // Update the modelId if you chose a different model.
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddHuggingFaceChatCompletion(
|
||||
model: modelId,
|
||||
apiKey: null,
|
||||
endpoint: endpoint)
|
||||
.Build();
|
||||
|
||||
var prompt = @"Rewrite the text between triple backticks into a business mail. Use a professional tone, be clear and concise.
|
||||
Sign the mail as AI Assistant.
|
||||
|
||||
Text: ```{{$input}}```";
|
||||
|
||||
var mailFunction = kernel.CreateFunctionFromPrompt(prompt, new HuggingFacePromptExecutionSettings
|
||||
{
|
||||
TopP = 0.5f,
|
||||
MaxTokens = 1000,
|
||||
});
|
||||
|
||||
var response = await kernel.InvokeAsync(mailFunction, new() { ["input"] = "Tell David that I'm going to finish the business plan by the end of the week." });
|
||||
Console.WriteLine(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sample showing how to use <see cref="IChatCompletionService"/> directly with a <see cref="ChatHistory"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingServiceNonStreamingWithHuggingFace()
|
||||
{
|
||||
Console.WriteLine($"======== HuggingFace - Chat Completion - {nameof(UsingServiceNonStreamingWithHuggingFace)} ========");
|
||||
|
||||
// HuggingFace local HTTP server endpoint
|
||||
var endpoint = new Uri("http://localhost:8080"); // Update the endpoint if you chose a different port. (defaults to 8080)
|
||||
var modelId = "teknium/OpenHermes-2.5-Mistral-7B"; // Update the modelId if you chose a different model.
|
||||
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddHuggingFaceChatCompletion(
|
||||
model: modelId,
|
||||
endpoint: endpoint)
|
||||
.Build();
|
||||
|
||||
var chatService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
Console.WriteLine("Chat content:");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
var chatHistory = new ChatHistory("You are a librarian, expert about books");
|
||||
|
||||
// First user message
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// First assistant message
|
||||
var reply = await chatService.GetChatMessageContentAsync(chatHistory);
|
||||
chatHistory.Add(reply);
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// Second user message
|
||||
chatHistory.AddUserMessage("I love history and philosophy, I'd like to learn something new about Greece, any suggestion");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// Second assistant message
|
||||
reply = await chatService.GetChatMessageContentAsync(chatHistory);
|
||||
chatHistory.Add(reply);
|
||||
OutputLastMessage(chatHistory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.HuggingFace;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// This example shows a way of using Hugging Face connector with HuggingFace Text Generation Inference (TGI) API.
|
||||
/// Follow steps in <see href="https://huggingface.co/docs/text-generation-inference/main/en/quicktour"/> to setup HuggingFace local Text Generation Inference HTTP server.
|
||||
/// <list type="number">
|
||||
/// <item>Install HuggingFace TGI via docker</item>
|
||||
/// <item><c>docker run -d --gpus all --shm-size 1g -p 8080:80 -v "c:\temp\huggingface:/data" ghcr.io/huggingface/text-generation-inference:latest --model-id teknium/OpenHermes-2.5-Mistral-7B</c></item>
|
||||
/// <item>Run the examples</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public class HuggingFace_ChatCompletionStreaming(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// Sample showing how to use <see cref="IChatCompletionService"/> directly with a <see cref="ChatHistory"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingServiceStreamingWithHuggingFace()
|
||||
{
|
||||
Console.WriteLine($"======== HuggingFace - Chat Completion - {nameof(UsingServiceStreamingWithHuggingFace)} ========");
|
||||
|
||||
// HuggingFace local HTTP server endpoint
|
||||
var endpoint = new Uri("http://localhost:8080"); // Update the endpoint if you chose a different port. (defaults to 8080)
|
||||
var modelId = "teknium/OpenHermes-2.5-Mistral-7B"; // Update the modelId if you chose a different model.
|
||||
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddHuggingFaceChatCompletion(
|
||||
model: modelId,
|
||||
endpoint: endpoint)
|
||||
.Build();
|
||||
|
||||
var chatService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
Console.WriteLine("Chat content:");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
var chatHistory = new ChatHistory("You are a librarian, expert about books");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// First user message
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// First assistant message
|
||||
await StreamMessageOutputAsync(chatService, chatHistory, AuthorRole.Assistant);
|
||||
|
||||
// Second user message
|
||||
chatHistory.AddUserMessage("I love history and philosophy, I'd like to learn something new about Greece, any suggestion?");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// Second assistant message
|
||||
await StreamMessageOutputAsync(chatService, chatHistory, AuthorRole.Assistant);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example shows how to setup LMStudio to use with the <see cref="Kernel"/> InvokeAsync (Non-Streaming).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingKernelStreamingWithHuggingFace()
|
||||
{
|
||||
Console.WriteLine($"======== HuggingFace - Chat Completion - {nameof(UsingKernelStreamingWithHuggingFace)} ========");
|
||||
|
||||
var endpoint = new Uri("http://localhost:8080"); // Update the endpoint if you chose a different port. (defaults to 8080)
|
||||
var modelId = "teknium/OpenHermes-2.5-Mistral-7B"; // Update the modelId if you chose a different model.
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddHuggingFaceChatCompletion(
|
||||
model: modelId,
|
||||
apiKey: null,
|
||||
endpoint: endpoint)
|
||||
.Build();
|
||||
|
||||
var prompt = @"Rewrite the text between triple backticks into a business mail. Use a professional tone, be clear and concise.
|
||||
Sign the mail as AI Assistant.
|
||||
|
||||
Text: ```{{$input}}```";
|
||||
|
||||
var mailFunction = kernel.CreateFunctionFromPrompt(prompt, new HuggingFacePromptExecutionSettings
|
||||
{
|
||||
TopP = 0.5f,
|
||||
MaxTokens = 1000,
|
||||
});
|
||||
|
||||
await foreach (var word in kernel.InvokeStreamingAsync(mailFunction, new() { ["input"] = "Tell David that I'm going to finish the business plan by the end of the week." }))
|
||||
{
|
||||
Console.WriteLine(word);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ClientModel;
|
||||
using System.ClientModel.Primitives;
|
||||
using System.Net;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates how an AI application can use code to attempt inference with the first available chat client in the list, falling back to the next client if the previous one fails.
|
||||
/// The <see cref="FallbackChatClient"/> class handles all the fallback complexities, abstracting them away from the application code.
|
||||
/// Since the <see cref="FallbackChatClient"/> class implements the <see cref="IChatClient"/> interface, the chat client used for inference the application can be easily replaced with the <see cref="FallbackChatClient"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The <see cref="FallbackChatClient"/> class is useful when an application utilizes multiple models and needs to switch between them based on the situation.
|
||||
/// For example, the application may use a cloud-based model by default and seamlessly fall back to a local model when the cloud model is unavailable (e.g., in offline mode), and vice versa.
|
||||
/// Additionally, the application can enhance resilience by employing several cloud models, falling back to the next one if the previous model fails.
|
||||
/// </remarks>
|
||||
public class HybridCompletion_Fallback(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// This example demonstrates how to perform completion using the <see cref="FallbackChatClient"/>, which falls back to an available model when the primary model is unavailable.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task FallbackToAvailableModelAsync()
|
||||
{
|
||||
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
|
||||
|
||||
// Create and register an unavailable chat client that fails with 503 Service Unavailable HTTP status code
|
||||
kernelBuilder.Services.AddSingleton<IChatClient>(CreateUnavailableOpenAIChatClient());
|
||||
|
||||
// Create and register a cloud available chat client
|
||||
kernelBuilder.Services.AddSingleton<IChatClient>(CreateAzureOpenAIChatClient());
|
||||
|
||||
// Create and register fallback chat client that will fallback to the available chat client when unavailable chat client fails
|
||||
kernelBuilder.Services.AddSingleton<IChatCompletionService>((sp) =>
|
||||
{
|
||||
IEnumerable<IChatClient> chatClients = sp.GetServices<IChatClient>();
|
||||
|
||||
return new FallbackChatClient(chatClients.ToList()).AsChatCompletionService();
|
||||
});
|
||||
|
||||
Kernel kernel = kernelBuilder.Build();
|
||||
kernel.ImportPluginFromFunctions("Weather", [KernelFunctionFactory.CreateFromMethod(() => "It's sunny", "GetWeather")]);
|
||||
|
||||
AzureOpenAIPromptExecutionSettings settings = new()
|
||||
{
|
||||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
|
||||
};
|
||||
|
||||
FunctionResult result = await kernel.InvokePromptAsync("Do I need an umbrella?", new(settings));
|
||||
|
||||
Output.WriteLine(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates how to perform streaming completion using the <see cref="FallbackChatClient"/>, which falls back to an available model when the primary model is unavailable.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task FallbackToAvailableModelStreamingAsync()
|
||||
{
|
||||
// Create an unavailable chat client that fails with 503 Service Unavailable HTTP status code
|
||||
IChatClient unavailableChatClient = CreateUnavailableOpenAIChatClient();
|
||||
|
||||
// Create a cloud available chat client
|
||||
IChatClient availableChatClient = CreateAzureOpenAIChatClient();
|
||||
|
||||
// Create a fallback chat client that will fallback to the available chat client when unavailable chat client fails
|
||||
IChatCompletionService fallbackCompletionService = new FallbackChatClient([unavailableChatClient, availableChatClient]).AsChatCompletionService();
|
||||
|
||||
Kernel kernel = new();
|
||||
kernel.ImportPluginFromFunctions("Weather", [KernelFunctionFactory.CreateFromMethod(() => "It's sunny", "GetWeather")]);
|
||||
|
||||
AzureOpenAIPromptExecutionSettings settings = new()
|
||||
{
|
||||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
|
||||
};
|
||||
|
||||
IAsyncEnumerable<StreamingChatMessageContent> result = fallbackCompletionService.GetStreamingChatMessageContentsAsync("Do I need an umbrella?", settings, kernel);
|
||||
|
||||
await foreach (var update in result)
|
||||
{
|
||||
Output.WriteLine(update);
|
||||
}
|
||||
}
|
||||
|
||||
private static IChatClient CreateUnavailableOpenAIChatClient()
|
||||
{
|
||||
AzureOpenAIClientOptions options = new()
|
||||
{
|
||||
Transport = new HttpClientPipelineTransport(
|
||||
new HttpClient
|
||||
(
|
||||
new StubHandler(new HttpClientHandler(), async (response) => { response.StatusCode = System.Net.HttpStatusCode.ServiceUnavailable; })
|
||||
)
|
||||
)
|
||||
};
|
||||
|
||||
IChatClient openAiClient = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAI.Endpoint), new AzureCliCredential(), options).GetChatClient(TestConfiguration.AzureOpenAI.ChatDeploymentName).AsIChatClient();
|
||||
|
||||
return new ChatClientBuilder(openAiClient)
|
||||
.UseFunctionInvocation()
|
||||
.Build();
|
||||
}
|
||||
|
||||
private static IChatClient CreateAzureOpenAIChatClient()
|
||||
{
|
||||
IChatClient chatClient = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAI.Endpoint), new AzureCliCredential()).GetChatClient(TestConfiguration.AzureOpenAI.ChatDeploymentName).AsIChatClient();
|
||||
|
||||
return new ChatClientBuilder(chatClient)
|
||||
.UseFunctionInvocation()
|
||||
.Build();
|
||||
}
|
||||
|
||||
protected sealed class StubHandler(HttpMessageHandler innerHandler, Func<HttpResponseMessage, Task> handler) : DelegatingHandler(innerHandler)
|
||||
{
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await base.SendAsync(request, cancellationToken);
|
||||
|
||||
await handler(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a chat client that performs inference using the first available chat client in the list, falling back to the next one if the previous client fails.
|
||||
/// </summary>
|
||||
internal sealed class FallbackChatClient : IChatClient
|
||||
{
|
||||
private readonly IList<IChatClient> _chatClients;
|
||||
private static readonly List<HttpStatusCode> s_defaultFallbackStatusCodes =
|
||||
[
|
||||
HttpStatusCode.InternalServerError,
|
||||
HttpStatusCode.NotImplemented,
|
||||
HttpStatusCode.BadGateway,
|
||||
HttpStatusCode.ServiceUnavailable,
|
||||
HttpStatusCode.GatewayTimeout
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FallbackChatClient"/> class.
|
||||
/// </summary>
|
||||
/// <param name="chatClients">The chat clients to fallback to.</param>
|
||||
public FallbackChatClient(IList<IChatClient> chatClients)
|
||||
{
|
||||
this._chatClients = chatClients?.Any() == true ? chatClients : throw new ArgumentException("At least one chat client must be provided.", nameof(chatClients));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the HTTP status codes that will trigger the fallback to the next chat client.
|
||||
/// </summary>
|
||||
public List<HttpStatusCode>? FallbackStatusCodes { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChatClientMetadata Metadata => new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Microsoft.Extensions.AI.ChatResponse> GetResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
for (int i = 0; i < this._chatClients.Count; i++)
|
||||
{
|
||||
var chatClient = this._chatClients.ElementAt(i);
|
||||
|
||||
try
|
||||
{
|
||||
return await chatClient.GetResponseAsync(messages, options, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (this.ShouldFallbackToNextClient(ex, i, this._chatClients.Count))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// If all clients fail, throw an exception or return a default value
|
||||
throw new InvalidOperationException("Neither of the chat clients could complete the inference.");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
for (int i = 0; i < this._chatClients.Count; i++)
|
||||
{
|
||||
var chatClient = this._chatClients.ElementAt(i);
|
||||
|
||||
IAsyncEnumerable<ChatResponseUpdate> completionStream = chatClient.GetStreamingResponseAsync(messages, options, cancellationToken);
|
||||
|
||||
ConfiguredCancelableAsyncEnumerable<ChatResponseUpdate>.Enumerator enumerator = completionStream.ConfigureAwait(false).GetAsyncEnumerator();
|
||||
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
// Move to the first update to reveal any exceptions.
|
||||
if (!await enumerator.MoveNextAsync())
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (this.ShouldFallbackToNextClient(ex, i, this._chatClients.Count))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Yield the first update.
|
||||
yield return enumerator.Current;
|
||||
|
||||
// Yield the rest of the updates.
|
||||
while (await enumerator.MoveNextAsync())
|
||||
{
|
||||
yield return enumerator.Current;
|
||||
}
|
||||
|
||||
// The stream has ended so break the while loop.
|
||||
break;
|
||||
}
|
||||
finally
|
||||
{
|
||||
await enumerator.DisposeAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldFallbackToNextClient(Exception ex, int clientIndex, int numberOfClients)
|
||||
{
|
||||
// If the exception is thrown by the last client then don't fallback.
|
||||
if (clientIndex == numberOfClients - 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
HttpStatusCode? statusCode = ex switch
|
||||
{
|
||||
HttpOperationException operationException => operationException.StatusCode,
|
||||
HttpRequestException httpRequestException => httpRequestException.StatusCode,
|
||||
ClientResultException clientResultException => (HttpStatusCode?)clientResultException.Status,
|
||||
_ => throw new InvalidOperationException($"Unsupported exception type: {ex.GetType()}."),
|
||||
};
|
||||
|
||||
if (statusCode is null)
|
||||
{
|
||||
throw new InvalidOperationException("The exception does not contain an HTTP status code.");
|
||||
}
|
||||
|
||||
return (this.FallbackStatusCodes ?? s_defaultFallbackStatusCodes).Contains(statusCode!.Value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
// We don't own the chat clients so we don't dispose them.
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object? GetService(Type serviceType, object? serviceKey = null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// This example shows a way of using OpenAI connector with other APIs that supports the same ChatCompletion API standard from OpenAI.
|
||||
/// <list type="number">
|
||||
/// <item>Install LMStudio Platform in your environment (As of now: 0.3.10)</item>
|
||||
/// <item>Open LM Studio</item>
|
||||
/// <item>Search and Download Llama2 model or any other</item>
|
||||
/// <item>Update the modelId parameter with the model llm name loaded (i.e: llama-2-7b-chat)</item>
|
||||
/// <item>Start the Local Server on http://localhost:1234</item>
|
||||
/// <item>Run the examples</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public class LMStudio_ChatCompletion(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// This example shows how to setup LMStudio to use with the <see cref="Kernel"/> InvokeAsync (Non-Streaming).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
#pragma warning restore CS0419 // Ambiguous reference in cref attribute
|
||||
public async Task UsingKernelStreamingWithLMStudio()
|
||||
{
|
||||
Console.WriteLine($"======== LM Studio - Chat Completion - {nameof(UsingKernelStreamingWithLMStudio)} ========");
|
||||
|
||||
var modelId = "llama-2-7b-chat"; // Update the modelId if you chose a different model.
|
||||
var endpoint = new Uri("http://localhost:1234/v1"); // Update the endpoint if you chose a different port.
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion(
|
||||
modelId: modelId,
|
||||
apiKey: null,
|
||||
endpoint: endpoint)
|
||||
.Build();
|
||||
|
||||
var prompt = @"Rewrite the text between triple backticks into a business mail. Use a professional tone, be clear and concise.
|
||||
Sign the mail as AI Assistant.
|
||||
|
||||
Text: ```{{$input}}```";
|
||||
|
||||
var mailFunction = kernel.CreateFunctionFromPrompt(prompt, new OpenAIPromptExecutionSettings
|
||||
{
|
||||
TopP = 0.5,
|
||||
MaxTokens = 1000,
|
||||
});
|
||||
|
||||
var response = await kernel.InvokeAsync(mailFunction, new() { ["input"] = "Tell David that I'm going to finish the business plan by the end of the week." });
|
||||
Console.WriteLine(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sample showing how to use <see cref="IChatCompletionService"/> directly with a <see cref="ChatHistory"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingServiceNonStreamingWithLMStudio()
|
||||
{
|
||||
Console.WriteLine($"======== LM Studio - Chat Completion - {nameof(UsingServiceNonStreamingWithLMStudio)} ========");
|
||||
|
||||
var modelId = "llama-2-7b-chat"; // Update the modelId if you chose a different model.
|
||||
var endpoint = new Uri("http://localhost:1234/v1"); // Update the endpoint if you chose a different port.
|
||||
|
||||
OpenAIChatCompletionService chatService = new(modelId: modelId, apiKey: null, endpoint: endpoint);
|
||||
|
||||
Console.WriteLine("Chat content:");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
var chatHistory = new ChatHistory("You are a librarian, expert about books");
|
||||
|
||||
// First user message
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// First assistant message
|
||||
var reply = await chatService.GetChatMessageContentAsync(chatHistory);
|
||||
chatHistory.Add(reply);
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// Second user message
|
||||
chatHistory.AddUserMessage("I love history and philosophy, I'd like to learn something new about Greece, any suggestion");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// Second assistant message
|
||||
reply = await chatService.GetChatMessageContentAsync(chatHistory);
|
||||
chatHistory.Add(reply);
|
||||
OutputLastMessage(chatHistory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// This example shows a way of using OpenAI connector with other APIs that supports the same ChatCompletion API standard from OpenAI.
|
||||
/// <list type="number">
|
||||
/// <item>Install LMStudio Platform in your environment (As of now: 0.3.10)</item>
|
||||
/// <item>Open LM Studio</item>
|
||||
/// <item>Search and Download Llama2 model or any other</item>
|
||||
/// <item>Update the modelId parameter with the model llm name loaded (i.e: llama-2-7b-chat)</item>
|
||||
/// <item>Start the Local Server on http://localhost:1234</item>
|
||||
/// <item>Run the examples</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public class LMStudio_ChatCompletionStreaming(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// Sample showing how to use <see cref="IChatCompletionService"/> streaming directly with a <see cref="ChatHistory"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingServiceStreamingWithLMStudio()
|
||||
{
|
||||
Console.WriteLine($"======== LM Studio - Chat Completion - {nameof(UsingServiceStreamingWithLMStudio)} ========");
|
||||
|
||||
var modelId = "llama-2-7b-chat"; // Update the modelId if you chose a different model.
|
||||
var endpoint = new Uri("http://localhost:1234/v1"); // Update the endpoint if you chose a different port.
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion(
|
||||
modelId: modelId,
|
||||
apiKey: null,
|
||||
endpoint: endpoint)
|
||||
.Build();
|
||||
|
||||
OpenAIChatCompletionService chatCompletionService = new(modelId: modelId, apiKey: null, endpoint: endpoint);
|
||||
|
||||
Console.WriteLine("Chat content:");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
var chatHistory = new ChatHistory("You are a librarian, expert about books");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// First user message
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// First assistant message
|
||||
await StreamMessageOutputAsync(chatCompletionService, chatHistory, AuthorRole.Assistant);
|
||||
|
||||
// Second user message
|
||||
chatHistory.AddUserMessage("I love history and philosophy, I'd like to learn something new about Greece, any suggestion?");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// Second assistant message
|
||||
await StreamMessageOutputAsync(chatCompletionService, chatHistory, AuthorRole.Assistant);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example shows how to setup LMStudio to use with the Kernel InvokeAsync (Streaming).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingKernelStreamingWithLMStudio()
|
||||
{
|
||||
Console.WriteLine($"======== LM Studio - Chat Completion - {nameof(UsingKernelStreamingWithLMStudio)} ========");
|
||||
|
||||
var modelId = "llama-2-7b-chat"; // Update the modelId if you chose a different model.
|
||||
var endpoint = new Uri("http://localhost:1234/v1"); // Update the endpoint if you chose a different port.
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion(
|
||||
modelId: modelId,
|
||||
apiKey: null,
|
||||
endpoint: endpoint)
|
||||
.Build();
|
||||
|
||||
var prompt = @"Rewrite the text between triple backticks into a business mail. Use a professional tone, be clear and concise.
|
||||
Sign the mail as AI Assistant.
|
||||
|
||||
Text: ```{{$input}}```";
|
||||
|
||||
var mailFunction = kernel.CreateFunctionFromPrompt(prompt, new OpenAIPromptExecutionSettings
|
||||
{
|
||||
TopP = 0.5,
|
||||
MaxTokens = 1000,
|
||||
});
|
||||
|
||||
await foreach (var word in kernel.InvokeStreamingAsync(mailFunction, new() { ["input"] = "Tell David that I'm going to finish the business plan by the end of the week." }))
|
||||
{
|
||||
Console.WriteLine(word);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.MistralAI;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
// The following example shows how to use Semantic Kernel with MistralAI API
|
||||
public class MistralAI_ChatCompletion(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task GetChatMessageContentAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.MistralAI.ChatModelId);
|
||||
Assert.NotNull(TestConfiguration.MistralAI.ApiKey);
|
||||
|
||||
MistralAIChatCompletionService chatService = new(
|
||||
modelId: TestConfiguration.MistralAI.ChatModelId,
|
||||
apiKey: TestConfiguration.MistralAI.ApiKey);
|
||||
|
||||
var chatHistory = new ChatHistory("You are a librarian, expert about books");
|
||||
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
|
||||
this.OutputLastMessage(chatHistory);
|
||||
|
||||
var reply = await chatService.GetChatMessageContentAsync(chatHistory, new MistralAIPromptExecutionSettings { MaxTokens = 200 });
|
||||
Console.WriteLine(reply);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetChatMessageContentUsingImageContentAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.MistralAI.ImageModelId);
|
||||
Assert.NotNull(TestConfiguration.MistralAI.ApiKey);
|
||||
|
||||
// Create a logging handler to output HTTP requests and responses
|
||||
var handler = new LoggingHandler(new HttpClientHandler(), this.Output);
|
||||
var httpClient = new HttpClient(handler);
|
||||
|
||||
MistralAIChatCompletionService chatService = new(
|
||||
modelId: TestConfiguration.MistralAI.ImageModelId,
|
||||
apiKey: TestConfiguration.MistralAI.ApiKey,
|
||||
httpClient: httpClient);
|
||||
|
||||
var chatHistory = new ChatHistory();
|
||||
|
||||
var chatMessage = new ChatMessageContent(AuthorRole.User, "What's in this image?");
|
||||
chatMessage.Items.Add(new ImageContent("data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEA2ADYAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAAQABADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD5rooor8DP9oD/2Q=="));
|
||||
|
||||
chatHistory.Add(chatMessage);
|
||||
this.OutputLastMessage(chatHistory);
|
||||
|
||||
var reply = await chatService.GetChatMessageContentAsync(chatHistory, new MistralAIPromptExecutionSettings { MaxTokens = 200 });
|
||||
Console.WriteLine(reply);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.MistralAI;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates the use of chat prompts with MistralAI.
|
||||
/// </summary>
|
||||
public sealed class MistralAI_ChatPrompt(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task GetChatMessageContentsAsync()
|
||||
{
|
||||
var service = new MistralAIChatCompletionService(
|
||||
TestConfiguration.MistralAI.ChatModelId!,
|
||||
TestConfiguration.MistralAI.ApiKey!
|
||||
);
|
||||
|
||||
var chatHistory = new ChatHistory
|
||||
{
|
||||
new ChatMessageContent(AuthorRole.System, "Respond in French."),
|
||||
new ChatMessageContent(AuthorRole.User, "What is the best French cheese?")
|
||||
};
|
||||
var response = await service.GetChatMessageContentsAsync(
|
||||
chatHistory, new MistralAIPromptExecutionSettings { MaxTokens = 500 });
|
||||
|
||||
foreach (var message in response)
|
||||
{
|
||||
Console.WriteLine(message.Content);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetStreamingChatMessageContentsAsync()
|
||||
{
|
||||
var service = new MistralAIChatCompletionService(
|
||||
TestConfiguration.MistralAI.ChatModelId!,
|
||||
TestConfiguration.MistralAI.ApiKey!
|
||||
);
|
||||
|
||||
var chatHistory = new ChatHistory
|
||||
{
|
||||
new ChatMessageContent(AuthorRole.System, "Respond in French."),
|
||||
new ChatMessageContent(AuthorRole.User, "What is the best French cheese?")
|
||||
};
|
||||
var streamingChat = service.GetStreamingChatMessageContentsAsync(
|
||||
chatHistory, new MistralAIPromptExecutionSettings { MaxTokens = 500 });
|
||||
|
||||
await foreach (var update in streamingChat)
|
||||
{
|
||||
Console.Write(update);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChatPromptAsync()
|
||||
{
|
||||
const string ChatPrompt = """
|
||||
<message role="system">Respond in French.</message>
|
||||
<message role="user">What is the best French cheese?</message>
|
||||
""";
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddMistralChatCompletion(
|
||||
modelId: TestConfiguration.MistralAI.ChatModelId,
|
||||
apiKey: TestConfiguration.MistralAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
var chatSemanticFunction = kernel.CreateFunctionFromPrompt(
|
||||
ChatPrompt, new MistralAIPromptExecutionSettings { MaxTokens = 500 });
|
||||
var chatPromptResult = await kernel.InvokeAsync(chatSemanticFunction);
|
||||
|
||||
Console.WriteLine(chatPromptResult);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.OpenApi.Extensions;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.MistralAI;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates the use of function calling with MistralAI.
|
||||
/// </summary>
|
||||
public sealed class MistralAI_FunctionCalling(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task AutoInvokeKernelFunctionsAsync()
|
||||
{
|
||||
// Create a kernel with MistralAI chat completion and WeatherPlugin
|
||||
Kernel kernel = this.CreateKernelWithWeatherPlugin();
|
||||
|
||||
// Invoke chat prompt with auto invocation of functions enabled
|
||||
const string ChatPrompt = """
|
||||
<message role="user">What is the weather like in Paris?</message>
|
||||
""";
|
||||
var executionSettings = new MistralAIPromptExecutionSettings { ToolCallBehavior = MistralAIToolCallBehavior.AutoInvokeKernelFunctions };
|
||||
var chatSemanticFunction = kernel.CreateFunctionFromPrompt(
|
||||
ChatPrompt, executionSettings);
|
||||
var chatPromptResult = await kernel.InvokeAsync(chatSemanticFunction);
|
||||
|
||||
Console.WriteLine(chatPromptResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AutoInvokeKernelFunctionsMultipleCallsAsync()
|
||||
{
|
||||
// Create a kernel with MistralAI chat completion and WeatherPlugin
|
||||
Kernel kernel = this.CreateKernelWithWeatherPlugin();
|
||||
var service = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
// Invoke chat prompt with auto invocation of functions enabled
|
||||
var chatHistory = new ChatHistory
|
||||
{
|
||||
new ChatMessageContent(AuthorRole.User, "What is the weather like in Paris?")
|
||||
};
|
||||
var executionSettings = new MistralAIPromptExecutionSettings { ToolCallBehavior = MistralAIToolCallBehavior.AutoInvokeKernelFunctions };
|
||||
var chatPromptResult1 = await service.GetChatMessageContentsAsync(chatHistory, executionSettings, kernel);
|
||||
chatHistory.AddRange(chatPromptResult1);
|
||||
|
||||
chatHistory.Add(new ChatMessageContent(AuthorRole.User, "What is the weather like in Marseille?"));
|
||||
var chatPromptResult2 = await service.GetChatMessageContentsAsync(chatHistory, executionSettings, kernel);
|
||||
|
||||
Console.WriteLine(chatPromptResult1[0].Content);
|
||||
Console.WriteLine(chatPromptResult2[0].Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RequiredKernelFunctionsAsync()
|
||||
{
|
||||
// Create a kernel with MistralAI chat completion and WeatherPlugin
|
||||
Kernel kernel = this.CreateKernelWithWeatherPlugin();
|
||||
var plugin = kernel.Plugins.First();
|
||||
|
||||
// Invoke chat prompt with auto invocation of functions enabled
|
||||
const string ChatPrompt = """
|
||||
<message role="user">What is the weather like in Paris?</message>
|
||||
""";
|
||||
var executionSettings = new MistralAIPromptExecutionSettings
|
||||
{
|
||||
ToolCallBehavior = MistralAIToolCallBehavior.RequiredFunctions(plugin, true)
|
||||
};
|
||||
var chatSemanticFunction = kernel.CreateFunctionFromPrompt(
|
||||
ChatPrompt, executionSettings);
|
||||
var chatPromptResult = await kernel.InvokeAsync(chatSemanticFunction);
|
||||
|
||||
Console.WriteLine(chatPromptResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NoKernelFunctionsAsync()
|
||||
{
|
||||
// Create a kernel with MistralAI chat completion and WeatherPlugin
|
||||
Kernel kernel = this.CreateKernelWithWeatherPlugin();
|
||||
|
||||
// Invoke chat prompt with auto invocation of functions enabled
|
||||
const string ChatPrompt = """
|
||||
<message role="user">What is the weather like in Paris?</message>
|
||||
""";
|
||||
var executionSettings = new MistralAIPromptExecutionSettings
|
||||
{
|
||||
ToolCallBehavior = MistralAIToolCallBehavior.NoKernelFunctions
|
||||
};
|
||||
var chatSemanticFunction = kernel.CreateFunctionFromPrompt(
|
||||
ChatPrompt, executionSettings);
|
||||
var chatPromptResult = await kernel.InvokeAsync(chatSemanticFunction);
|
||||
|
||||
Console.WriteLine(chatPromptResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AutoInvokeKernelFunctionsMultiplePluginsAsync()
|
||||
{
|
||||
// Create a kernel with MistralAI chat completion and WeatherPlugin and WidgetPlugin
|
||||
Kernel kernel = this.CreateKernelWithWeatherPlugin();
|
||||
kernel.Plugins.AddFromType<WidgetPlugin>();
|
||||
|
||||
// Invoke chat prompt with auto invocation of functions enabled
|
||||
const string ChatPrompt = """
|
||||
<message role="user">Create a lime and scarlet colored widget for me.</message>
|
||||
""";
|
||||
var executionSettings = new MistralAIPromptExecutionSettings { ToolCallBehavior = MistralAIToolCallBehavior.AutoInvokeKernelFunctions };
|
||||
var chatSemanticFunction = kernel.CreateFunctionFromPrompt(
|
||||
ChatPrompt, executionSettings);
|
||||
var chatPromptResult = await kernel.InvokeAsync(chatSemanticFunction);
|
||||
|
||||
Console.WriteLine(chatPromptResult);
|
||||
}
|
||||
|
||||
public sealed class WeatherPlugin
|
||||
{
|
||||
[KernelFunction]
|
||||
[Description("Get the current weather in a given location.")]
|
||||
public string GetWeather(
|
||||
[Description("The city and department, e.g. Marseille, 13")] string location
|
||||
) => "12°C\nWind: 11 KMPH\nHumidity: 48%\nMostly cloudy";
|
||||
}
|
||||
|
||||
public sealed class WidgetPlugin
|
||||
{
|
||||
[KernelFunction]
|
||||
[Description("Creates a new widget of the specified type and colors")]
|
||||
public string CreateWidget([Description("The colors of the widget to be created")] WidgetColor[] widgetColors)
|
||||
{
|
||||
var colors = string.Join('-', widgetColors.Select(c => c.GetDisplayName()).ToArray());
|
||||
return $"Widget created with colors: {colors}";
|
||||
}
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum WidgetColor
|
||||
{
|
||||
[Description("Use when creating a red item.")]
|
||||
Red,
|
||||
|
||||
[Description("Use when creating a green item.")]
|
||||
Green,
|
||||
|
||||
[Description("Use when creating a blue item.")]
|
||||
Blue
|
||||
}
|
||||
|
||||
private Kernel CreateKernelWithWeatherPlugin()
|
||||
{
|
||||
// Create a logging handler to output HTTP requests and responses
|
||||
var handler = new LoggingHandler(new HttpClientHandler(), this.Output);
|
||||
HttpClient httpClient = new(handler);
|
||||
|
||||
// Create a kernel with MistralAI chat completion and WeatherPlugin
|
||||
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
|
||||
kernelBuilder.AddMistralChatCompletion(
|
||||
modelId: TestConfiguration.MistralAI.ChatModelId!,
|
||||
apiKey: TestConfiguration.MistralAI.ApiKey!,
|
||||
httpClient: httpClient);
|
||||
kernelBuilder.Plugins.AddFromType<WeatherPlugin>();
|
||||
Kernel kernel = kernelBuilder.Build();
|
||||
return kernel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.MistralAI;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates the use of function calling and streaming with MistralAI.
|
||||
/// </summary>
|
||||
public sealed class MistralAI_StreamingFunctionCalling(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task GetChatMessageContentsAsync()
|
||||
{
|
||||
// Create a kernel with MistralAI chat completion and WeatherPlugin
|
||||
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
|
||||
kernelBuilder.AddMistralChatCompletion(
|
||||
modelId: TestConfiguration.MistralAI.ChatModelId!,
|
||||
apiKey: TestConfiguration.MistralAI.ApiKey!);
|
||||
kernelBuilder.Plugins.AddFromType<WeatherPlugin>();
|
||||
Kernel kernel = kernelBuilder.Build();
|
||||
|
||||
// Get the chat completion service
|
||||
var chat = kernel.GetRequiredService<IChatCompletionService>();
|
||||
var chatHistory = new ChatHistory();
|
||||
chatHistory.AddUserMessage("What is the weather like in Paris?");
|
||||
|
||||
// Get the streaming chat message contents
|
||||
var streamingChat = chat.GetStreamingChatMessageContentsAsync(
|
||||
chatHistory, new MistralAIPromptExecutionSettings { ToolCallBehavior = MistralAIToolCallBehavior.AutoInvokeKernelFunctions }, kernel);
|
||||
|
||||
await foreach (var update in streamingChat)
|
||||
{
|
||||
Console.Write(update);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class WeatherPlugin
|
||||
{
|
||||
[KernelFunction]
|
||||
[Description("Get the current weather in a given location.")]
|
||||
public string GetWeather(
|
||||
[Description("The city and department, e.g. Marseille, 13")] string location
|
||||
) => "17°C\nWind: 23 KMPH\nHumidity: 59%\nMostly cloudy";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// The sample show how to add a chat history reducer which only sends the last two messages in <see cref="ChatHistory"/> to the model.
|
||||
/// </summary>
|
||||
public class MultipleProviders_ChatHistoryReducer(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task ShowTotalTokenCountAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ChatModelId);
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
OpenAIChatCompletionService openAiChatService = new(
|
||||
modelId: TestConfiguration.OpenAI.ChatModelId,
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
var chatHistory = new ChatHistory("You are a librarian and expert on books about cities");
|
||||
|
||||
string[] userMessages = [
|
||||
"Recommend a list of books about Seattle",
|
||||
"Recommend a list of books about Dublin",
|
||||
"Recommend a list of books about Amsterdam",
|
||||
"Recommend a list of books about Paris",
|
||||
"Recommend a list of books about London"
|
||||
];
|
||||
|
||||
int totalTokenCount = 0;
|
||||
foreach (var userMessage in userMessages)
|
||||
{
|
||||
chatHistory.AddUserMessage(userMessage);
|
||||
|
||||
var response = await openAiChatService.GetChatMessageContentAsync(chatHistory);
|
||||
chatHistory.AddAssistantMessage(response.Content!);
|
||||
Console.WriteLine($"\n>>> Assistant:\n{response.Content!}");
|
||||
|
||||
if (response.InnerContent is OpenAI.Chat.ChatCompletion chatCompletion)
|
||||
{
|
||||
totalTokenCount += chatCompletion.Usage?.TotalTokenCount ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Example total token usage is approximately: 10000
|
||||
Console.WriteLine($"Total Token Count: {totalTokenCount}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ShowHowToReduceChatHistoryToLastMessageAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ChatModelId);
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
OpenAIChatCompletionService openAiChatService = new(
|
||||
modelId: TestConfiguration.OpenAI.ChatModelId,
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
var truncatedSize = 2; // keep system message and last user message only
|
||||
IChatCompletionService chatService = openAiChatService.UsingChatHistoryReducer(new ChatHistoryTruncationReducer(truncatedSize));
|
||||
|
||||
var chatHistory = new ChatHistory("You are a librarian and expert on books about cities");
|
||||
|
||||
string[] userMessages = [
|
||||
"Recommend a list of books about Seattle",
|
||||
"Recommend a list of books about Dublin",
|
||||
"Recommend a list of books about Amsterdam",
|
||||
"Recommend a list of books about Paris",
|
||||
"Recommend a list of books about London"
|
||||
];
|
||||
|
||||
int totalTokenCount = 0;
|
||||
foreach (var userMessage in userMessages)
|
||||
{
|
||||
chatHistory.AddUserMessage(userMessage);
|
||||
Console.WriteLine($"\n>>> User:\n{userMessage}");
|
||||
|
||||
var response = await chatService.GetChatMessageContentAsync(chatHistory);
|
||||
chatHistory.AddAssistantMessage(response.Content!);
|
||||
Console.WriteLine($"\n>>> Assistant:\n{response.Content!}");
|
||||
|
||||
if (response.InnerContent is OpenAI.Chat.ChatCompletion chatCompletion)
|
||||
{
|
||||
totalTokenCount += chatCompletion.Usage?.TotalTokenCount ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Example total token usage is approximately: 3000
|
||||
Console.WriteLine($"Total Token Count: {totalTokenCount}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ShowHowToReduceChatHistoryToLastMessageStreamingAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ChatModelId);
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
OpenAIChatCompletionService openAiChatService = new(
|
||||
modelId: TestConfiguration.OpenAI.ChatModelId,
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
var truncatedSize = 2; // keep system message and last user message only
|
||||
IChatCompletionService chatService = openAiChatService.UsingChatHistoryReducer(new ChatHistoryTruncationReducer(truncatedSize));
|
||||
|
||||
var chatHistory = new ChatHistory("You are a librarian and expert on books about cities");
|
||||
|
||||
string[] userMessages = [
|
||||
"Recommend a list of books about Seattle",
|
||||
"Recommend a list of books about Dublin",
|
||||
"Recommend a list of books about Amsterdam",
|
||||
"Recommend a list of books about Paris",
|
||||
"Recommend a list of books about London"
|
||||
];
|
||||
|
||||
int totalTokenCount = 0;
|
||||
foreach (var userMessage in userMessages)
|
||||
{
|
||||
chatHistory.AddUserMessage(userMessage);
|
||||
Console.WriteLine($"\n>>> User:\n{userMessage}");
|
||||
|
||||
var response = new StringBuilder();
|
||||
var chatUpdates = chatService.GetStreamingChatMessageContentsAsync(chatHistory);
|
||||
await foreach (var chatUpdate in chatUpdates)
|
||||
{
|
||||
response.Append((string?)chatUpdate.Content);
|
||||
|
||||
if (chatUpdate.InnerContent is StreamingChatCompletionUpdate openAiChatUpdate)
|
||||
{
|
||||
totalTokenCount += openAiChatUpdate.Usage?.TotalTokenCount ?? 0;
|
||||
}
|
||||
}
|
||||
chatHistory.AddAssistantMessage(response.ToString());
|
||||
Console.WriteLine($"\n>>> Assistant:\n{response}");
|
||||
}
|
||||
|
||||
// Example total token usage is approximately: 3000
|
||||
Console.WriteLine($"Total Token Count: {totalTokenCount}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ShowHowToReduceChatHistoryToMaxTokensAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ChatModelId);
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
OpenAIChatCompletionService openAiChatService = new(
|
||||
modelId: TestConfiguration.OpenAI.ChatModelId,
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey);
|
||||
IChatCompletionService chatService = openAiChatService.UsingChatHistoryReducer(new ChatHistoryMaxTokensReducer(100));
|
||||
|
||||
var chatHistory = new ChatHistory();
|
||||
chatHistory.AddSystemMessageWithTokenCount("You are an expert on the best restaurants in the world. Keep responses short.");
|
||||
|
||||
string[] userMessages = [
|
||||
"Recommend restaurants in Seattle",
|
||||
"What is the best Italian restaurant?",
|
||||
"What is the best Korean restaurant?",
|
||||
"Recommend restaurants in Dublin",
|
||||
"What is the best Indian restaurant?",
|
||||
"What is the best Japanese restaurant?",
|
||||
];
|
||||
|
||||
int totalTokenCount = 0;
|
||||
foreach (var userMessage in userMessages)
|
||||
{
|
||||
chatHistory.AddUserMessageWithTokenCount(userMessage);
|
||||
Console.WriteLine($"\n>>> User:\n{userMessage}");
|
||||
|
||||
var response = await chatService.GetChatMessageContentAsync(chatHistory);
|
||||
chatHistory.AddAssistantMessageWithTokenCount(response.Content!);
|
||||
Console.WriteLine($"\n>>> Assistant:\n{response.Content!}");
|
||||
|
||||
if (response.InnerContent is OpenAI.Chat.ChatCompletion chatCompletion)
|
||||
{
|
||||
totalTokenCount += chatCompletion.Usage?.TotalTokenCount ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Example total token usage is approximately: 3000
|
||||
Console.WriteLine($"Total Token Count: {totalTokenCount}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ShowHowToReduceChatHistoryWithSummarizationAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ChatModelId);
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
OpenAIChatCompletionService openAiChatService = new(
|
||||
modelId: TestConfiguration.OpenAI.ChatModelId,
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey);
|
||||
IChatCompletionService chatService = openAiChatService.UsingChatHistoryReducer(new ChatHistorySummarizationReducer(openAiChatService, 2, 4));
|
||||
|
||||
var chatHistory = new ChatHistory("You are an expert on the best restaurants in every city. Answer for the city the user has asked about.");
|
||||
|
||||
string[] userMessages = [
|
||||
"Recommend restaurants in Seattle",
|
||||
"What is the best Italian restaurant?",
|
||||
"What is the best Korean restaurant?",
|
||||
"What is the best Brazilian restaurant?",
|
||||
"Recommend restaurants in Dublin",
|
||||
"What is the best Indian restaurant?",
|
||||
"What is the best Japanese restaurant?",
|
||||
"What is the best French restaurant?",
|
||||
|
||||
];
|
||||
|
||||
int totalTokenCount = 0;
|
||||
foreach (var userMessage in userMessages)
|
||||
{
|
||||
chatHistory.AddUserMessage(userMessage);
|
||||
Console.WriteLine($"\n>>> User:\n{userMessage}");
|
||||
|
||||
var response = await chatService.GetChatMessageContentAsync(chatHistory);
|
||||
chatHistory.AddAssistantMessage(response.Content!);
|
||||
Console.WriteLine($"\n>>> Assistant:\n{response.Content!}");
|
||||
|
||||
if (response.InnerContent is OpenAI.Chat.ChatCompletion chatCompletion)
|
||||
{
|
||||
totalTokenCount += chatCompletion.Usage?.TotalTokenCount ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Example total token usage is approximately: 3000
|
||||
Console.WriteLine($"Total Token Count: {totalTokenCount}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using OllamaSharp;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// These examples demonstrate different ways of using chat completion with Ollama API.
|
||||
/// </summary>
|
||||
public class Ollama_ChatCompletion(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// Demonstrates how you can use the chat completion service directly.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingChatClientPromptAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.Ollama.ModelId);
|
||||
|
||||
Console.WriteLine("======== Ollama - Chat Completion ========");
|
||||
|
||||
using IChatClient ollamaClient = new OllamaApiClient(
|
||||
uriString: TestConfiguration.Ollama.Endpoint,
|
||||
defaultModel: TestConfiguration.Ollama.ModelId);
|
||||
|
||||
Console.WriteLine("Chat content:");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
List<ChatMessage> chatHistory = [new ChatMessage(ChatRole.System, "You are a librarian, expert about books")];
|
||||
|
||||
// First user message
|
||||
chatHistory.Add(new(ChatRole.User, "Hi, I'm looking for book suggestions"));
|
||||
this.OutputLastMessage(chatHistory);
|
||||
|
||||
// First assistant message
|
||||
var reply = await ollamaClient.GetResponseAsync(chatHistory);
|
||||
chatHistory.AddRange(reply.Messages);
|
||||
this.OutputLastMessage(chatHistory);
|
||||
|
||||
// Second user message
|
||||
chatHistory.Add(new(ChatRole.User, "I love history and philosophy, I'd like to learn something new about Greece, any suggestion"));
|
||||
this.OutputLastMessage(chatHistory);
|
||||
|
||||
// Second assistant message
|
||||
reply = await ollamaClient.GetResponseAsync(chatHistory);
|
||||
chatHistory.AddRange(reply.Messages);
|
||||
this.OutputLastMessage(chatHistory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates how you can get extra information from the service response, using the underlying inner content.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a breaking glass scenario, any attempt on running with different versions of OllamaSharp library that introduces breaking changes
|
||||
/// may cause breaking changes in the code below.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task UsingChatCompletionServicePromptWithInnerContentAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.Ollama.ModelId);
|
||||
|
||||
Console.WriteLine("======== Ollama - Chat Completion ========");
|
||||
|
||||
using var ollamaClient = new OllamaApiClient(
|
||||
uriString: TestConfiguration.Ollama.Endpoint,
|
||||
defaultModel: TestConfiguration.Ollama.ModelId);
|
||||
|
||||
var chatService = ollamaClient.AsChatCompletionService();
|
||||
|
||||
Console.WriteLine("Chat content:");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
var chatHistory = new ChatHistory("You are a librarian, expert about books");
|
||||
|
||||
// First user message
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
|
||||
this.OutputLastMessage(chatHistory);
|
||||
|
||||
// First assistant message
|
||||
var reply = await chatService.GetChatMessageContentAsync(chatHistory);
|
||||
|
||||
// Assistant message details
|
||||
// Ollama Sharp does not support non-streaming and always perform streaming calls, for this reason, the inner content is always a list of chunks.
|
||||
var ollamaSharpInnerContent = reply.InnerContent as OllamaSharp.Models.Chat.ChatDoneResponseStream;
|
||||
|
||||
OutputOllamaSharpContent(ollamaSharpInnerContent!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates how you can template a chat history call using the kernel for invocation.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ChatPromptAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.Ollama.ModelId);
|
||||
|
||||
StringBuilder chatPrompt = new("""
|
||||
<message role="system">You are a librarian, expert about books</message>
|
||||
<message role="user">Hi, I'm looking for book suggestions</message>
|
||||
""");
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOllamaChatClient(
|
||||
endpoint: new Uri(TestConfiguration.Ollama.Endpoint ?? "http://localhost:11434"),
|
||||
modelId: TestConfiguration.Ollama.ModelId)
|
||||
.Build();
|
||||
|
||||
var reply = await kernel.InvokePromptAsync(chatPrompt.ToString());
|
||||
|
||||
chatPrompt.AppendLine($"<message role=\"assistant\"><![CDATA[{reply}]]></message>");
|
||||
chatPrompt.AppendLine("<message role=\"user\">I love history and philosophy, I'd like to learn something new about Greece, any suggestion</message>");
|
||||
|
||||
reply = await kernel.InvokePromptAsync(chatPrompt.ToString());
|
||||
|
||||
Console.WriteLine(reply);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates how you can template a chat history call and get extra information from the response while using the kernel for invocation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a breaking glass scenario, any attempt on running with different versions of OllamaSharp library that introduces breaking changes
|
||||
/// may cause breaking changes in the code below.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task ChatPromptWithInnerContentAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.Ollama.ModelId);
|
||||
|
||||
StringBuilder chatPrompt = new("""
|
||||
<message role="system">You are a librarian, expert about books</message>
|
||||
<message role="user">Hi, I'm looking for book suggestions</message>
|
||||
""");
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOllamaChatClient(
|
||||
endpoint: new Uri(TestConfiguration.Ollama.Endpoint ?? "http://localhost:11434"),
|
||||
modelId: TestConfiguration.Ollama.ModelId)
|
||||
.Build();
|
||||
|
||||
var functionResult = await kernel.InvokePromptAsync(chatPrompt.ToString());
|
||||
|
||||
// Ollama Sharp does not support non-streaming and always perform streaming calls, for this reason, the inner content of a non-streaming result is a list of the generated chunks.
|
||||
var messageContent = functionResult.GetValue<ChatResponse>(); // Retrieves underlying chat message content from FunctionResult.
|
||||
var ollamaSharpRawRepresentation = messageContent!.RawRepresentation as OllamaSharp.Models.Chat.ChatDoneResponseStream; // Retrieves inner content from ChatMessageContent.
|
||||
|
||||
OutputOllamaSharpContent(ollamaSharpRawRepresentation!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve extra information from the final response.
|
||||
/// </summary>
|
||||
/// <param name="innerContent">The complete OllamaSharp response provided as inner content of a chat message</param>
|
||||
/// <remarks>
|
||||
/// This is a breaking glass scenario, any attempt on running with different versions of OllamaSharp library that introduces breaking changes
|
||||
/// may cause breaking changes in the code below.
|
||||
/// </remarks>
|
||||
private void OutputOllamaSharpContent(OllamaSharp.Models.Chat.ChatDoneResponseStream innerContent)
|
||||
{
|
||||
Console.WriteLine($$"""
|
||||
Model: {{innerContent.Model}}
|
||||
Message role: {{innerContent.Message.Role}}
|
||||
Message content: {{innerContent.Message.Content}}
|
||||
Created at: {{innerContent.CreatedAt}}
|
||||
Done: {{innerContent.Done}}
|
||||
Done Reason: {{innerContent.DoneReason}}
|
||||
Eval count: {{innerContent.EvalCount}}
|
||||
Eval duration: {{innerContent.EvalDuration}}
|
||||
Load duration: {{innerContent.LoadDuration}}
|
||||
Total duration: {{innerContent.TotalDuration}}
|
||||
Prompt eval count: {{innerContent.PromptEvalCount}}
|
||||
Prompt eval duration: {{innerContent.PromptEvalDuration}}
|
||||
------------------------
|
||||
""");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using OllamaSharp;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// These examples demonstrate different ways of using chat completion with Ollama API.
|
||||
/// </summary>
|
||||
public class Ollama_ChatCompletionStreaming(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// This example demonstrates chat completion streaming using <see cref="IChatClient"/> directly.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingChatClientStreaming()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.Ollama.ModelId);
|
||||
|
||||
Console.WriteLine($"======== Ollama - Chat Completion - {nameof(UsingChatClientStreaming)} ========");
|
||||
|
||||
using IChatClient ollamaClient = new OllamaApiClient(
|
||||
uriString: TestConfiguration.Ollama.Endpoint,
|
||||
defaultModel: TestConfiguration.Ollama.ModelId);
|
||||
|
||||
Console.WriteLine("Chat content:");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
List<ChatMessage> chatHistory = [new ChatMessage(ChatRole.System, "You are a librarian, expert about books")];
|
||||
this.OutputLastMessage(chatHistory);
|
||||
|
||||
// First user message
|
||||
chatHistory.Add(new(ChatRole.User, "Hi, I'm looking for book suggestions"));
|
||||
this.OutputLastMessage(chatHistory);
|
||||
|
||||
// First assistant message
|
||||
await StreamChatClientMessageOutputAsync(ollamaClient, chatHistory);
|
||||
|
||||
// Second user message
|
||||
chatHistory.Add(new(Microsoft.Extensions.AI.ChatRole.User, "I love history and philosophy, I'd like to learn something new about Greece, any suggestion?"));
|
||||
this.OutputLastMessage(chatHistory);
|
||||
|
||||
// Second assistant message
|
||||
await StreamChatClientMessageOutputAsync(ollamaClient, chatHistory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates chat completion streaming using <see cref="IChatCompletionService"/> directly.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingChatCompletionServiceStreamingWithOllama()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.Ollama.ModelId);
|
||||
|
||||
Console.WriteLine($"======== Ollama - Chat Completion - {nameof(UsingChatCompletionServiceStreamingWithOllama)} ========");
|
||||
|
||||
using var ollamaClient = new OllamaApiClient(
|
||||
uriString: TestConfiguration.Ollama.Endpoint,
|
||||
defaultModel: TestConfiguration.Ollama.ModelId);
|
||||
|
||||
var chatService = ollamaClient.AsChatCompletionService();
|
||||
|
||||
Console.WriteLine("Chat content:");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
var chatHistory = new ChatHistory("You are a librarian, expert about books");
|
||||
this.OutputLastMessage(chatHistory);
|
||||
|
||||
// First user message
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
|
||||
this.OutputLastMessage(chatHistory);
|
||||
|
||||
// First assistant message
|
||||
await StreamMessageOutputAsync(chatService, chatHistory, AuthorRole.Assistant);
|
||||
|
||||
// Second user message
|
||||
chatHistory.AddUserMessage("I love history and philosophy, I'd like to learn something new about Greece, any suggestion?");
|
||||
this.OutputLastMessage(chatHistory);
|
||||
|
||||
// Second assistant message
|
||||
await StreamMessageOutputAsync(chatService, chatHistory, AuthorRole.Assistant);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates retrieving underlying OllamaSharp library information through <see cref="IChatClient" /> streaming raw representation (breaking glass) approach.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a breaking glass scenario and is more susceptible to break on newer versions of OllamaSharp library.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task UsingChatClientStreamingRawContentsWithOllama()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.Ollama.ModelId);
|
||||
|
||||
Console.WriteLine($"======== Ollama - Chat Completion - {nameof(UsingChatClientStreamingRawContentsWithOllama)} ========");
|
||||
|
||||
using IChatClient ollamaClient = new OllamaApiClient(
|
||||
uriString: TestConfiguration.Ollama.Endpoint,
|
||||
defaultModel: TestConfiguration.Ollama.ModelId);
|
||||
|
||||
Console.WriteLine("Chat content:");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
List<ChatMessage> chatHistory = [new ChatMessage(ChatRole.System, "You are a librarian, expert about books")];
|
||||
this.OutputLastMessage(chatHistory);
|
||||
|
||||
// First user message
|
||||
chatHistory.Add(new(ChatRole.User, "Hi, I'm looking for book suggestions"));
|
||||
this.OutputLastMessage(chatHistory);
|
||||
|
||||
await foreach (var chatUpdate in ollamaClient.GetStreamingResponseAsync(chatHistory))
|
||||
{
|
||||
var rawRepresentation = chatUpdate.RawRepresentation as OllamaSharp.Models.Chat.ChatResponseStream;
|
||||
OutputOllamaSharpContent(rawRepresentation!);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates how you can template a chat history call while using the <see cref="Kernel"/> for invocation.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingKernelChatPromptStreaming()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.Ollama.ModelId);
|
||||
|
||||
Console.WriteLine($"======== Ollama - Chat Completion - {nameof(UsingKernelChatPromptStreaming)} ========");
|
||||
|
||||
StringBuilder chatPrompt = new("""
|
||||
<message role="system">You are a librarian, expert about books</message>
|
||||
<message role="user">Hi, I'm looking for book suggestions</message>
|
||||
""");
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOllamaChatClient(
|
||||
endpoint: new Uri(TestConfiguration.Ollama.Endpoint),
|
||||
modelId: TestConfiguration.Ollama.ModelId)
|
||||
.Build();
|
||||
|
||||
var reply = await StreamMessageOutputFromKernelAsync(kernel, chatPrompt.ToString());
|
||||
|
||||
chatPrompt.AppendLine($"<message role=\"assistant\"><![CDATA[{reply}]]></message>");
|
||||
chatPrompt.AppendLine("<message role=\"user\">I love history and philosophy, I'd like to learn something new about Greece, any suggestion</message>");
|
||||
|
||||
reply = await StreamMessageOutputFromKernelAsync(kernel, chatPrompt.ToString());
|
||||
|
||||
Console.WriteLine(reply);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates retrieving underlying library information through chat completion streaming inner contents.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a breaking glass scenario and is more susceptible to break on newer versions of OllamaSharp library.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task UsingKernelChatPromptStreamingRawRepresentation()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.Ollama.ModelId);
|
||||
|
||||
Console.WriteLine($"======== Ollama - Chat Completion - {nameof(UsingKernelChatPromptStreamingRawRepresentation)} ========");
|
||||
|
||||
StringBuilder chatPrompt = new("""
|
||||
<message role="system">You are a librarian, expert about books</message>
|
||||
<message role="user">Hi, I'm looking for book suggestions</message>
|
||||
""");
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOllamaChatClient(
|
||||
endpoint: new Uri(TestConfiguration.Ollama.Endpoint),
|
||||
modelId: TestConfiguration.Ollama.ModelId)
|
||||
.Build();
|
||||
|
||||
var reply = await StreamMessageOutputFromKernelAsync(kernel, chatPrompt.ToString());
|
||||
|
||||
chatPrompt.AppendLine($"<message role=\"assistant\"><![CDATA[{reply}]]></message>");
|
||||
chatPrompt.AppendLine("<message role=\"user\">I love history and philosophy, I'd like to learn something new about Greece, any suggestion</message>");
|
||||
|
||||
await foreach (var chatUpdate in kernel.InvokePromptStreamingAsync<StreamingChatMessageContent>(chatPrompt.ToString()))
|
||||
{
|
||||
var innerContent = chatUpdate.InnerContent as OllamaSharp.Models.Chat.ChatResponseStream;
|
||||
OutputOllamaSharpContent(innerContent!);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates how the chat completion service streams text content.
|
||||
/// It shows how to access the response update via StreamingChatMessageContent.Content property
|
||||
/// and alternatively via the StreamingChatMessageContent.Items property.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingStreamingTextFromChatCompletion()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.Ollama.ModelId);
|
||||
|
||||
Console.WriteLine($"======== Ollama - Chat Completion - {nameof(UsingStreamingTextFromChatCompletion)} ========");
|
||||
|
||||
using var ollamaClient = new OllamaApiClient(
|
||||
uriString: TestConfiguration.Ollama.Endpoint,
|
||||
defaultModel: TestConfiguration.Ollama.ModelId);
|
||||
|
||||
// Create chat completion service
|
||||
var chatService = ollamaClient.AsChatCompletionService();
|
||||
|
||||
// Create chat history with initial system and user messages
|
||||
ChatHistory chatHistory = new("You are a librarian, an expert on books.");
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions.");
|
||||
chatHistory.AddUserMessage("I love history and philosophy. I'd like to learn something new about Greece, any suggestion?");
|
||||
|
||||
// Start streaming chat based on the chat history
|
||||
await foreach (StreamingChatMessageContent chatUpdate in chatService.GetStreamingChatMessageContentsAsync(chatHistory))
|
||||
{
|
||||
// Access the response update via StreamingChatMessageContent.Content property
|
||||
Console.Write(chatUpdate.Content);
|
||||
|
||||
// Alternatively, the response update can be accessed via the StreamingChatMessageContent.Items property
|
||||
Console.Write(chatUpdate.Items.OfType<StreamingTextContent>().FirstOrDefault());
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> StreamMessageOutputFromKernelAsync(Kernel kernel, string prompt)
|
||||
{
|
||||
bool roleWritten = false;
|
||||
string fullMessage = string.Empty;
|
||||
|
||||
await foreach (var chatUpdate in kernel.InvokePromptStreamingAsync<StreamingChatMessageContent>(prompt))
|
||||
{
|
||||
if (!roleWritten && chatUpdate.Role.HasValue)
|
||||
{
|
||||
Console.Write($"{chatUpdate.Role.Value}: {chatUpdate.Content}");
|
||||
roleWritten = true;
|
||||
}
|
||||
|
||||
if (chatUpdate.Content is { Length: > 0 })
|
||||
{
|
||||
fullMessage += chatUpdate.Content;
|
||||
Console.Write(chatUpdate.Content);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("\n------------------------");
|
||||
return fullMessage;
|
||||
}
|
||||
|
||||
private async Task StreamChatClientMessageOutputAsync(IChatClient chatClient, List<ChatMessage> chatHistory)
|
||||
{
|
||||
bool roleWritten = false;
|
||||
string fullMessage = string.Empty;
|
||||
List<ChatResponseUpdate> chatUpdates = [];
|
||||
await foreach (var chatUpdate in chatClient.GetStreamingResponseAsync(chatHistory))
|
||||
{
|
||||
chatUpdates.Add(chatUpdate);
|
||||
if (!roleWritten && !string.IsNullOrEmpty(chatUpdate.Text))
|
||||
{
|
||||
Console.Write($"Assistant: {chatUpdate.Text}");
|
||||
roleWritten = true;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(chatUpdate.Text))
|
||||
{
|
||||
Console.Write(chatUpdate.Text);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("\n------------------------");
|
||||
chatHistory.AddRange(chatUpdates.ToChatResponse().Messages);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve extra information from each streaming chunk response.
|
||||
/// </summary>
|
||||
/// <param name="streamChunk">Streaming chunk provided as inner content of a streaming chat message</param>
|
||||
/// <remarks>
|
||||
/// This is a breaking glass scenario, any attempt on running with different versions of OllamaSharp library that introduces breaking changes
|
||||
/// may cause breaking changes in the code below.
|
||||
/// </remarks>
|
||||
private void OutputOllamaSharpContent(OllamaSharp.Models.Chat.ChatResponseStream streamChunk)
|
||||
{
|
||||
Console.WriteLine($$"""
|
||||
Model: {{streamChunk.Model}}
|
||||
Message role: {{streamChunk.Message.Role}}
|
||||
Message content: {{streamChunk.Message.Content}}
|
||||
Created at: {{streamChunk.CreatedAt}}
|
||||
Done: {{streamChunk.Done}}
|
||||
""");
|
||||
|
||||
/// The last message in the chunk is a <see cref="OllamaSharp.Models.Chat.ChatDoneResponseStream"/> type with additional metadata.
|
||||
if (streamChunk is OllamaSharp.Models.Chat.ChatDoneResponseStream doneStream)
|
||||
{
|
||||
Console.WriteLine($$"""
|
||||
Done Reason: {{doneStream.DoneReason}}
|
||||
Eval count: {{doneStream.EvalCount}}
|
||||
Eval duration: {{doneStream.EvalDuration}}
|
||||
Load duration: {{doneStream.LoadDuration}}
|
||||
Total duration: {{doneStream.TotalDuration}}
|
||||
Prompt eval count: {{doneStream.PromptEvalCount}}
|
||||
Prompt eval duration: {{doneStream.PromptEvalDuration}}
|
||||
""");
|
||||
}
|
||||
Console.WriteLine("------------------------");
|
||||
}
|
||||
|
||||
private void OutputLastMessage(List<ChatMessage> chatHistory)
|
||||
{
|
||||
var message = chatHistory.Last();
|
||||
Console.WriteLine($"{message.Role}: {message.Text}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Resources;
|
||||
using TextContent = Microsoft.SemanticKernel.TextContent;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// This sample shows how to use llama3.2-vision model with different content types (text and image).
|
||||
/// </summary>
|
||||
public class Ollama_ChatCompletionWithVision(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// This sample uses IChatClient directly with a local image file and sends it to the model along
|
||||
/// with a text message to get the description of the image.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task GetLocalImageDescriptionUsingChatClient()
|
||||
{
|
||||
Console.WriteLine($"======== Ollama - {nameof(GetLocalImageDescriptionUsingChatClient)} ========");
|
||||
|
||||
var imageBytes = await EmbeddedResource.ReadAllAsync("sample_image.jpg");
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOllamaChatClient(modelId: "llama3.2-vision", endpoint: new Uri(TestConfiguration.Ollama.Endpoint))
|
||||
.Build();
|
||||
|
||||
var chatClient = kernel.GetRequiredService<IChatClient>();
|
||||
|
||||
List<ChatMessage> chatHistory = [
|
||||
new(ChatRole.System, "You are a friendly assistant."),
|
||||
new(ChatRole.User, [
|
||||
new Microsoft.Extensions.AI.TextContent("What's in this image?"),
|
||||
new Microsoft.Extensions.AI.DataContent(imageBytes, "image/jpg")
|
||||
])
|
||||
];
|
||||
|
||||
var response = await chatClient.GetResponseAsync(chatHistory);
|
||||
|
||||
Console.WriteLine(response.Text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This sample uses a local image file and sends it to the model along
|
||||
/// with a text message the get the description of the image.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task GetLocalImageDescription()
|
||||
{
|
||||
Console.WriteLine($"======== Ollama - {nameof(GetLocalImageDescription)} ========");
|
||||
|
||||
var imageBytes = await EmbeddedResource.ReadAllAsync("sample_image.jpg");
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOllamaChatCompletion(modelId: "llama3.2-vision", endpoint: new Uri(TestConfiguration.Ollama.Endpoint))
|
||||
.Build();
|
||||
|
||||
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
var chatHistory = new ChatHistory("You are a friendly assistant.");
|
||||
|
||||
chatHistory.AddUserMessage(
|
||||
[
|
||||
new TextContent("What’s in this image?"),
|
||||
new ImageContent(imageBytes, "image/jpg")
|
||||
]);
|
||||
|
||||
var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
|
||||
|
||||
Console.WriteLine(reply.Content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.Onnx;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
// The following example shows how to use Semantic Kernel with Onnx Gen AI Chat Completion API
|
||||
public class Onnx_ChatCompletion(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// Example using the service directly to get chat message content
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Configuration example:
|
||||
/// <list type="table">
|
||||
/// <item>
|
||||
/// <term>ModelId:</term>
|
||||
/// <description>phi-3</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>ModelPath:</term>
|
||||
/// <description>D:\huggingface\Phi-3-mini-4k-instruct-onnx\cpu_and_mobile\cpu-int4-rtn-block-32</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task ServicePromptAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.Onnx.ModelId); // dotnet user-secrets set "Onnx:ModelId" "<model-id>"
|
||||
Assert.NotNull(TestConfiguration.Onnx.ModelPath); // dotnet user-secrets set "Onnx:ModelPath" "<model-folder-path>"
|
||||
|
||||
Console.WriteLine("======== Onnx - Chat Completion ========");
|
||||
|
||||
using var chatService = new OnnxRuntimeGenAIChatCompletionService(
|
||||
modelId: TestConfiguration.Onnx.ModelId,
|
||||
modelPath: TestConfiguration.Onnx.ModelPath);
|
||||
|
||||
Console.WriteLine("Chat content:");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
var chatHistory = new ChatHistory("You are a librarian, expert about books");
|
||||
|
||||
// First user message
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// First assistant message
|
||||
var reply = await chatService.GetChatMessageContentAsync(chatHistory);
|
||||
chatHistory.Add(reply);
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// Second user message
|
||||
chatHistory.AddUserMessage("I love history and philosophy, I'd like to learn something new about Greece, any suggestion");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// Second assistant message
|
||||
reply = await chatService.GetChatMessageContentAsync(chatHistory);
|
||||
chatHistory.Add(reply);
|
||||
OutputLastMessage(chatHistory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Example using the kernel to send a chat history and get a chat message content
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Configuration example:
|
||||
/// <list type="table">
|
||||
/// <item>
|
||||
/// <term>ModelId:</term>
|
||||
/// <description>phi-3</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>ModelPath:</term>
|
||||
/// <description>D:\huggingface\Phi-3-mini-4k-instruct-onnx\cpu_and_mobile\cpu-int4-rtn-block-32</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task ChatPromptAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.Onnx.ModelId); // dotnet user-secrets set "Onnx:ModelId" "<model-id>"
|
||||
Assert.NotNull(TestConfiguration.Onnx.ModelPath); // dotnet user-secrets set "Onnx:ModelPath" "<model-folder-path>"
|
||||
|
||||
Console.WriteLine("======== Onnx - Chat Prompt Completion ========");
|
||||
|
||||
StringBuilder chatPrompt = new("""
|
||||
<message role="system">You are a librarian, expert about books</message>
|
||||
<message role="user">Hi, I'm looking for book suggestions</message>
|
||||
""");
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOnnxRuntimeGenAIChatCompletion(
|
||||
modelId: TestConfiguration.Onnx.ModelId,
|
||||
modelPath: TestConfiguration.Onnx.ModelPath)
|
||||
.Build();
|
||||
|
||||
var reply = await kernel.InvokePromptAsync(chatPrompt.ToString());
|
||||
|
||||
chatPrompt.AppendLine($"<message role=\"assistant\"><![CDATA[{reply}]]></message>");
|
||||
chatPrompt.AppendLine("<message role=\"user\">I love history and philosophy, I'd like to learn something new about Greece, any suggestion</message>");
|
||||
|
||||
reply = await kernel.InvokePromptAsync(chatPrompt.ToString());
|
||||
|
||||
Console.WriteLine(reply);
|
||||
|
||||
DisposeServices(kernel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To avoid any potential memory leak all disposable services created by the kernel are disposed.
|
||||
/// </summary>
|
||||
/// <param name="kernel">Target kernel</param>
|
||||
private static void DisposeServices(Kernel kernel)
|
||||
{
|
||||
foreach (var target in kernel
|
||||
.GetAllServices<IChatCompletionService>()
|
||||
.OfType<IDisposable>())
|
||||
{
|
||||
target.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.Onnx;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// These examples demonstrate the ways different content types are streamed by Onnx GenAI via the chat completion service.
|
||||
/// </summary>
|
||||
public class Onnx_ChatCompletionStreaming(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// Streaming chat completion streaming using the service directly.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Configuration example:
|
||||
/// <list type="table">
|
||||
/// <item>
|
||||
/// <term>ModelId:</term>
|
||||
/// <description>phi-3</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>ModelPath:</term>
|
||||
/// <description>D:\huggingface\Phi-3-mini-4k-instruct-onnx\cpu_and_mobile\cpu-int4-rtn-block-32</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task StreamChatAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.Onnx.ModelId); // dotnet user-secrets set "Onnx:ModelId" "<model-id>"
|
||||
Assert.NotNull(TestConfiguration.Onnx.ModelPath); // dotnet user-secrets set "Onnx:ModelPath" "<model-folder-path>"
|
||||
|
||||
Console.WriteLine("======== Onnx - Chat Completion Streaming ========");
|
||||
|
||||
using var chatService = new OnnxRuntimeGenAIChatCompletionService(
|
||||
modelId: TestConfiguration.Onnx.ModelId,
|
||||
modelPath: TestConfiguration.Onnx.ModelPath);
|
||||
|
||||
Console.WriteLine("Chat content:");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
var chatHistory = new ChatHistory("You are a librarian, expert about books");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// First user message
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// First assistant message
|
||||
await StreamMessageOutputAsync(chatService, chatHistory, AuthorRole.Assistant);
|
||||
|
||||
// Second user message
|
||||
chatHistory.AddUserMessage("I love history and philosophy, I'd like to learn something new about Greece, any suggestion?");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// Second assistant message
|
||||
await StreamMessageOutputAsync(chatService, chatHistory, AuthorRole.Assistant);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Streaming chat completion using the kernel.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Configuration example:
|
||||
/// <list type="table">
|
||||
/// <item>
|
||||
/// <term>ModelId:</term>
|
||||
/// <description>phi-3</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>ModelPath:</term>
|
||||
/// <description>D:\huggingface\Phi-3-mini-4k-instruct-onnx\cpu_and_mobile\cpu-int4-rtn-block-32</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task StreamChatPromptAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.Onnx.ModelId); // dotnet user-secrets set "Onnx:ModelId" "<model-id>"
|
||||
Assert.NotNull(TestConfiguration.Onnx.ModelPath); // dotnet user-secrets set "Onnx:ModelPath" "<model-folder-path>"
|
||||
|
||||
StringBuilder chatPrompt = new("""
|
||||
<message role="system">You are a librarian, expert about books</message>
|
||||
<message role="user">Hi, I'm looking for book suggestions</message>
|
||||
""");
|
||||
|
||||
Console.WriteLine("======== Onnx - Chat Completion Streaming ========");
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOnnxRuntimeGenAIChatCompletion(
|
||||
modelId: TestConfiguration.Onnx.ModelId,
|
||||
modelPath: TestConfiguration.Onnx.ModelPath)
|
||||
.Build();
|
||||
|
||||
var reply = await StreamMessageOutputFromKernelAsync(kernel, chatPrompt.ToString());
|
||||
|
||||
chatPrompt.AppendLine($"<message role=\"assistant\"><![CDATA[{reply}]]></message>");
|
||||
chatPrompt.AppendLine("<message role=\"user\">I love history and philosophy, I'd like to learn something new about Greece, any suggestion</message>");
|
||||
|
||||
reply = await StreamMessageOutputFromKernelAsync(kernel, chatPrompt.ToString());
|
||||
|
||||
Console.WriteLine(reply);
|
||||
|
||||
DisposeServices(kernel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates how the chat completion service streams text content.
|
||||
/// It shows how to access the response update via StreamingChatMessageContent.Content property
|
||||
/// and alternatively via the StreamingChatMessageContent.Items property.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Configuration example:
|
||||
/// <list type="table">
|
||||
/// <item>
|
||||
/// <term>ModelId:</term>
|
||||
/// <description>phi-3</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>ModelPath:</term>
|
||||
/// <description>D:\huggingface\Phi-3-mini-4k-instruct-onnx\cpu_and_mobile\cpu-int4-rtn-block-32</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task StreamTextFromChatAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.Onnx.ModelId); // dotnet user-secrets set "Onnx:ModelId" "<model-id>"
|
||||
Assert.NotNull(TestConfiguration.Onnx.ModelPath); // dotnet user-secrets set "Onnx:ModelPath" "<model-folder-path>"
|
||||
|
||||
Console.WriteLine("======== Stream Text from Chat Content ========");
|
||||
|
||||
// Create chat completion service
|
||||
using var chatService = new OnnxRuntimeGenAIChatCompletionService(
|
||||
modelId: TestConfiguration.Onnx.ModelId,
|
||||
modelPath: TestConfiguration.Onnx.ModelPath);
|
||||
|
||||
// Create chat history with initial system and user messages
|
||||
ChatHistory chatHistory = new("You are a librarian, an expert on books.");
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions.");
|
||||
chatHistory.AddUserMessage("I love history and philosophy. I'd like to learn something new about Greece, any suggestion?");
|
||||
|
||||
// Start streaming chat based on the chat history
|
||||
await foreach (StreamingChatMessageContent chatUpdate in chatService.GetStreamingChatMessageContentsAsync(chatHistory))
|
||||
{
|
||||
// Access the response update via StreamingChatMessageContent.Content property
|
||||
Console.Write(chatUpdate.Content);
|
||||
|
||||
// Alternatively, the response update can be accessed via the StreamingChatMessageContent.Items property
|
||||
Console.Write(chatUpdate.Items.OfType<StreamingTextContent>().FirstOrDefault());
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StreamMessageOutputAsync(OnnxRuntimeGenAIChatCompletionService chatCompletionService, ChatHistory chatHistory, AuthorRole authorRole)
|
||||
{
|
||||
bool roleWritten = false;
|
||||
string fullMessage = string.Empty;
|
||||
|
||||
await foreach (var chatUpdate in chatCompletionService.GetStreamingChatMessageContentsAsync(chatHistory))
|
||||
{
|
||||
if (!roleWritten && chatUpdate.Role.HasValue)
|
||||
{
|
||||
Console.Write($"{chatUpdate.Role.Value}: {chatUpdate.Content}");
|
||||
roleWritten = true;
|
||||
}
|
||||
|
||||
if (chatUpdate.Content is { Length: > 0 })
|
||||
{
|
||||
fullMessage += chatUpdate.Content;
|
||||
Console.Write(chatUpdate.Content);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("\n------------------------");
|
||||
chatHistory.AddMessage(authorRole, fullMessage);
|
||||
}
|
||||
|
||||
private async Task<string> StreamMessageOutputFromKernelAsync(Kernel kernel, string prompt)
|
||||
{
|
||||
bool roleWritten = false;
|
||||
string fullMessage = string.Empty;
|
||||
|
||||
await foreach (var chatUpdate in kernel.InvokePromptStreamingAsync<StreamingChatMessageContent>(prompt))
|
||||
{
|
||||
if (!roleWritten && chatUpdate.Role.HasValue)
|
||||
{
|
||||
Console.Write($"{chatUpdate.Role.Value}: {chatUpdate.Content}");
|
||||
roleWritten = true;
|
||||
}
|
||||
|
||||
if (chatUpdate.Content is { Length: > 0 })
|
||||
{
|
||||
fullMessage += chatUpdate.Content;
|
||||
Console.Write(chatUpdate.Content);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("\n------------------------");
|
||||
return fullMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To avoid any potential memory leak all disposable services created by the kernel are disposed.
|
||||
/// </summary>
|
||||
/// <param name="kernel">Target kernel</param>
|
||||
private static void DisposeServices(Kernel kernel)
|
||||
{
|
||||
foreach (var target in kernel
|
||||
.GetAllServices<IChatCompletionService>()
|
||||
.OfType<IDisposable>())
|
||||
{
|
||||
target.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// These examples demonstrate different ways of using chat completion with OpenAI API.
|
||||
/// </summary>
|
||||
public class OpenAI_ChatCompletion(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// Sample showing how to use <see cref="IChatCompletionService"/> directly with a <see cref="ChatHistory"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ServicePromptAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ChatModelId);
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
Console.WriteLine("======== Open AI - Chat Completion ========");
|
||||
|
||||
OpenAIChatCompletionService chatService = new(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
Console.WriteLine("Chat content:");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
var chatHistory = new ChatHistory("You are a librarian, expert about books");
|
||||
|
||||
// First user message
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// First assistant message
|
||||
var reply = await chatService.GetChatMessageContentAsync(chatHistory);
|
||||
chatHistory.Add(reply);
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// Second user message
|
||||
chatHistory.AddUserMessage("I love history and philosophy, I'd like to learn something new about Greece, any suggestion");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// Second assistant message
|
||||
reply = await chatService.GetChatMessageContentAsync(chatHistory);
|
||||
chatHistory.Add(reply);
|
||||
OutputLastMessage(chatHistory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sample showing how to use <see cref="IChatCompletionService"/> directly with a <see cref="ChatHistory"/> also exploring the
|
||||
/// breaking glass approach capturing the underlying <see cref="OpenAI.Chat.ChatCompletion"/> instance via <see cref="KernelContent.InnerContent"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ServicePromptWithInnerContentAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ChatModelId);
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
Console.WriteLine("======== Open AI - Chat Completion ========");
|
||||
|
||||
OpenAIChatCompletionService chatService = new(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
Console.WriteLine("Chat content:");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
var chatHistory = new ChatHistory("You are a librarian, expert about books");
|
||||
|
||||
// First user message
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
|
||||
this.OutputLastMessage(chatHistory);
|
||||
|
||||
// First assistant message
|
||||
var reply = await chatService.GetChatMessageContentAsync(chatHistory, new OpenAIPromptExecutionSettings { Logprobs = true, TopLogprobs = 3 });
|
||||
|
||||
// Assistant message details
|
||||
var replyInnerContent = reply.InnerContent as OpenAI.Chat.ChatCompletion;
|
||||
|
||||
OutputInnerContent(replyInnerContent!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sample showing how to use <see cref="Kernel"/> with chat completion and chat prompt syntax.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ChatPromptAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ChatModelId);
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
StringBuilder chatPrompt = new("""
|
||||
<message role="system">You are a librarian, expert about books</message>
|
||||
<message role="user">Hi, I'm looking for book suggestions</message>
|
||||
""");
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
var reply = await kernel.InvokePromptAsync(chatPrompt.ToString());
|
||||
|
||||
chatPrompt.AppendLine($"<message role=\"assistant\"><![CDATA[{reply}]]></message>");
|
||||
chatPrompt.AppendLine("<message role=\"user\">I love history and philosophy, I'd like to learn something new about Greece, any suggestion</message>");
|
||||
|
||||
reply = await kernel.InvokePromptAsync(chatPrompt.ToString());
|
||||
|
||||
Console.WriteLine(reply);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates how you can template a chat history call and get extra information from the response while using the kernel for invocation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a breaking glass scenario, any attempt on running with different versions of OpenAI SDK that introduces breaking changes
|
||||
/// may cause breaking changes in the code below.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task ChatPromptWithInnerContentAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ChatModelId);
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
StringBuilder chatPrompt = new("""
|
||||
<message role="system">You are a librarian, expert about books</message>
|
||||
<message role="user">Hi, I'm looking for book suggestions</message>
|
||||
""");
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
var functionResult = await kernel.InvokePromptAsync(chatPrompt.ToString(),
|
||||
new(new OpenAIPromptExecutionSettings { Logprobs = true, TopLogprobs = 3 }));
|
||||
|
||||
var messageContent = functionResult.GetValue<ChatMessageContent>(); // Retrieves underlying chat message content from FunctionResult.
|
||||
var replyInnerContent = messageContent!.InnerContent as OpenAI.Chat.ChatCompletion; // Retrieves inner content from ChatMessageContent.
|
||||
|
||||
OutputInnerContent(replyInnerContent!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates how you can store the output of a chat completion request for use in the OpenAI model distillation or evals products.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This sample adds metadata to the chat completion request which allows the requests to be filtered in the OpenAI dashboard.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task ChatPromptStoreWithMetadataAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ChatModelId);
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
StringBuilder chatPrompt = new("""
|
||||
<message role="system">You are a librarian, expert about books</message>
|
||||
<message role="user">Hi, I'm looking for book suggestions about Artificial Intelligence</message>
|
||||
""");
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
var functionResult = await kernel.InvokePromptAsync(chatPrompt.ToString(),
|
||||
new(new OpenAIPromptExecutionSettings { Store = true, Metadata = new Dictionary<string, string>() { { "concept", "chatcompletion" } } }));
|
||||
|
||||
var messageContent = functionResult.GetValue<ChatMessageContent>(); // Retrieves underlying chat message content from FunctionResult.
|
||||
var replyInnerContent = messageContent!.InnerContent as OpenAI.Chat.ChatCompletion; // Retrieves inner content from ChatMessageContent.
|
||||
|
||||
OutputInnerContent(replyInnerContent!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve extra information from a <see cref="ChatMessageContent"/> inner content of type <see cref="OpenAI.Chat.ChatCompletion"/>.
|
||||
/// </summary>
|
||||
/// <param name="innerContent">An instance of <see cref="OpenAI.Chat.ChatCompletion"/> retrieved as an inner content of <see cref="ChatMessageContent"/>.</param>
|
||||
/// <remarks>
|
||||
/// This is a breaking glass scenario, any attempt on running with different versions of OpenAI SDK that introduces breaking changes
|
||||
/// may break the code below.
|
||||
/// </remarks>
|
||||
private void OutputInnerContent(OpenAI.Chat.ChatCompletion innerContent)
|
||||
{
|
||||
Console.WriteLine($$"""
|
||||
Message role: {{innerContent.Role}} // Available as a property of ChatMessageContent
|
||||
Message content: {{innerContent.Content[0].Text}} // Available as a property of ChatMessageContent
|
||||
|
||||
Model: {{innerContent.Model}} // Model doesn't change per chunk, so we can get it from the first chunk only
|
||||
Created At: {{innerContent.CreatedAt}}
|
||||
|
||||
Finish reason: {{innerContent.FinishReason}}
|
||||
Input tokens usage: {{innerContent.Usage.InputTokenCount}}
|
||||
Output tokens usage: {{innerContent.Usage.OutputTokenCount}}
|
||||
Total tokens usage: {{innerContent.Usage.TotalTokenCount}}
|
||||
Refusal: {{innerContent.Refusal}}
|
||||
Id: {{innerContent.Id}}
|
||||
System fingerprint: {{innerContent.SystemFingerprint}}
|
||||
""");
|
||||
|
||||
if (innerContent.ContentTokenLogProbabilities.Count > 0)
|
||||
{
|
||||
Console.WriteLine("Content token log probabilities:");
|
||||
foreach (var contentTokenLogProbability in innerContent.ContentTokenLogProbabilities)
|
||||
{
|
||||
Console.WriteLine($"Token: {contentTokenLogProbability.Token}");
|
||||
Console.WriteLine($"Log probability: {contentTokenLogProbability.LogProbability}");
|
||||
|
||||
Console.WriteLine(" Top log probabilities for this token:");
|
||||
foreach (var topLogProbability in contentTokenLogProbability.TopLogProbabilities)
|
||||
{
|
||||
Console.WriteLine($" Token: {topLogProbability.Token}");
|
||||
Console.WriteLine($" Log probability: {topLogProbability.LogProbability}");
|
||||
Console.WriteLine(" =======");
|
||||
}
|
||||
|
||||
Console.WriteLine("--------------");
|
||||
}
|
||||
}
|
||||
|
||||
if (innerContent.RefusalTokenLogProbabilities.Count > 0)
|
||||
{
|
||||
Console.WriteLine("Refusal token log probabilities:");
|
||||
foreach (var refusalTokenLogProbability in innerContent.RefusalTokenLogProbabilities)
|
||||
{
|
||||
Console.WriteLine($"Token: {refusalTokenLogProbability.Token}");
|
||||
Console.WriteLine($"Log probability: {refusalTokenLogProbability.LogProbability}");
|
||||
|
||||
Console.WriteLine(" Refusal top log probabilities for this token:");
|
||||
foreach (var topLogProbability in refusalTokenLogProbability.TopLogProbabilities)
|
||||
{
|
||||
Console.WriteLine($" Token: {topLogProbability.Token}");
|
||||
Console.WriteLine($" Log probability: {topLogProbability.LogProbability}");
|
||||
Console.WriteLine(" =======");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
#pragma warning disable SKEXP0010 // OpenAIPromptExecutionSettings.ExtraBody is experimental.
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="OpenAIPromptExecutionSettings.ExtraBody"/> is an escape hatch that injects additional fields
|
||||
/// into the request body sent to the OpenAI-compatible endpoint. Use it to pass vendor-specific or preview
|
||||
/// parameters that are not modeled by <see cref="OpenAIPromptExecutionSettings"/> (for example,
|
||||
/// Qwen3's <c>enable_thinking</c> flag, ChatGLM thinking modes, or NVIDIA NIM custom knobs).
|
||||
///
|
||||
/// <para>Key syntax:</para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>A plain key (without leading <c>$.</c>) is treated as a literal top-level field name.</description></item>
|
||||
/// <item><description>A key starting with <c>$.</c> is interpreted as a JSONPath expression and applied as a deep patch onto the request body.</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public class OpenAI_ChatCompletionExtraBody(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a flat top-level field to the outgoing chat completion request:
|
||||
/// <code>{ "messages": [...], "model": "qwen-plus", "enable_thinking": false, ... }</code>
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task FlatFieldExampleAsync()
|
||||
{
|
||||
OpenAIChatCompletionService chatCompletionService = new(
|
||||
TestConfiguration.OpenAI.ChatModelId,
|
||||
TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
var settings = new OpenAIPromptExecutionSettings
|
||||
{
|
||||
ExtraBody = new Dictionary<string, object?>
|
||||
{
|
||||
// Required by Qwen3 open-source models for non-streaming calls.
|
||||
["enable_thinking"] = false,
|
||||
},
|
||||
};
|
||||
|
||||
var chatHistory = new ChatHistory("You are a helpful assistant.");
|
||||
chatHistory.AddUserMessage("Who are you?");
|
||||
|
||||
var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory, settings);
|
||||
|
||||
Console.WriteLine(reply.Content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses a <c>$.</c>-prefixed JSONPath key to surgically patch a nested field in the request body
|
||||
/// (for example, into <c>thinking.enabled</c>). This avoids having to specify the entire nested object.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task DeepPatchExampleAsync()
|
||||
{
|
||||
OpenAIChatCompletionService chatCompletionService = new(
|
||||
TestConfiguration.OpenAI.ChatModelId,
|
||||
TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
var settings = new OpenAIPromptExecutionSettings
|
||||
{
|
||||
ExtraBody = new Dictionary<string, object?>
|
||||
{
|
||||
// Emits { "thinking": { "enabled": false } } in the request body.
|
||||
["$.thinking.enabled"] = false,
|
||||
},
|
||||
};
|
||||
|
||||
var chatHistory = new ChatHistory("You are a helpful assistant.");
|
||||
chatHistory.AddUserMessage("Summarize the plot of Hamlet in one sentence.");
|
||||
|
||||
var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory, settings);
|
||||
|
||||
Console.WriteLine(reply.Content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// These examples demonstrate different ways of using streaming chat completion with OpenAI API.
|
||||
/// </summary>
|
||||
public class OpenAI_ChatCompletionStreaming(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// This example demonstrates chat completion streaming using OpenAI.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task StreamServicePromptAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ChatModelId);
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
Console.WriteLine("======== Open AI Chat Completion Streaming ========");
|
||||
|
||||
OpenAIChatCompletionService chatCompletionService = new(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
Console.WriteLine("Chat content:");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
var chatHistory = new ChatHistory("You are a librarian, expert about books");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// First user message
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// First assistant message
|
||||
await StreamMessageOutputAsync(chatCompletionService, chatHistory, AuthorRole.Assistant);
|
||||
|
||||
// Second user message
|
||||
chatHistory.AddUserMessage("I love history and philosophy, I'd like to learn something new about Greece, any suggestion?");
|
||||
OutputLastMessage(chatHistory);
|
||||
|
||||
// Second assistant message
|
||||
await StreamMessageOutputAsync(chatCompletionService, chatHistory, AuthorRole.Assistant);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates how the chat completion service streams text content.
|
||||
/// It shows how to access the response update via StreamingChatMessageContent.Content property
|
||||
/// and alternatively via the StreamingChatMessageContent.Items property.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task StreamServicePromptTextAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ChatModelId);
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
Console.WriteLine("======== Stream Text Content ========");
|
||||
|
||||
// Create chat completion service
|
||||
OpenAIChatCompletionService chatCompletionService = new(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
// Create chat history with initial system and user messages
|
||||
ChatHistory chatHistory = new("You are a librarian, an expert on books.");
|
||||
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions.");
|
||||
chatHistory.AddUserMessage("I love history and philosophy. I'd like to learn something new about Greece, any suggestion?");
|
||||
|
||||
// Start streaming chat based on the chat history
|
||||
await foreach (StreamingChatMessageContent chatUpdate in chatCompletionService.GetStreamingChatMessageContentsAsync(chatHistory))
|
||||
{
|
||||
// Access the response update via StreamingChatMessageContent.Content property
|
||||
Console.Write(chatUpdate.Content);
|
||||
|
||||
// Alternatively, the response update can be accessed via the StreamingChatMessageContent.Items property
|
||||
Console.Write(chatUpdate.Items.OfType<StreamingTextContent>().FirstOrDefault());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates retrieving extra information chat completion streaming using OpenAI.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a breaking glass scenario, any attempt on running with different versions of OpenAI SDK that introduces breaking changes
|
||||
/// may break the code below.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task StreamServicePromptWithInnerContentAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ChatModelId);
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
Console.WriteLine("======== OpenAI - Chat Completion Streaming (InnerContent) ========");
|
||||
|
||||
var chatService = new OpenAIChatCompletionService(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
Console.WriteLine("Chat content:");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
var chatHistory = new ChatHistory("Answer straight, do not explain your answer");
|
||||
this.OutputLastMessage(chatHistory);
|
||||
|
||||
// First user message
|
||||
chatHistory.AddUserMessage("How many natural satellites are around Earth?");
|
||||
this.OutputLastMessage(chatHistory);
|
||||
|
||||
await foreach (var chatUpdate in chatService.GetStreamingChatMessageContentsAsync(chatHistory))
|
||||
{
|
||||
var innerContent = chatUpdate.InnerContent as OpenAI.Chat.StreamingChatCompletionUpdate;
|
||||
OutputInnerContent(innerContent!);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates how you can template a chat history call while using the kernel for invocation.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task StreamChatPromptAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ChatModelId);
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
Console.WriteLine("======== OpenAI - Chat Prompt Completion Streaming ========");
|
||||
|
||||
StringBuilder chatPrompt = new("""
|
||||
<message role="system">You are a librarian, expert about books</message>
|
||||
<message role="user">Hi, I'm looking for book suggestions</message>
|
||||
""");
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
var reply = await StreamMessageOutputFromKernelAsync(kernel, chatPrompt.ToString());
|
||||
chatPrompt.AppendLine($"<message role=\"assistant\"><![CDATA[{reply}]]></message>");
|
||||
chatPrompt.AppendLine("<message role=\"user\">I love history and philosophy, I'd like to learn something new about Greece, any suggestion</message>");
|
||||
reply = await StreamMessageOutputFromKernelAsync(kernel, chatPrompt.ToString());
|
||||
Console.WriteLine(reply);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates how you can template a chat history call and get extra information from the response while using the kernel for invocation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a breaking glass scenario, any attempt on running with different versions of OllamaSharp library that introduces breaking changes
|
||||
/// may cause breaking changes in the code below.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task StreamChatPromptWithInnerContentAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ChatModelId);
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
Console.WriteLine("======== OpenAI - Chat Prompt Completion Streaming (InnerContent) ========");
|
||||
|
||||
StringBuilder chatPrompt = new("""
|
||||
<message role="system">Answer straight, do not explain your answer</message>
|
||||
<message role="user">How many natural satellites are around Earth?</message>
|
||||
""");
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
await foreach (var chatUpdate in kernel.InvokePromptStreamingAsync<StreamingChatMessageContent>(chatPrompt.ToString()))
|
||||
{
|
||||
var innerContent = chatUpdate.InnerContent as OpenAI.Chat.StreamingChatCompletionUpdate;
|
||||
OutputInnerContent(innerContent!);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates how the chat completion service streams raw function call content.
|
||||
/// See <see cref="FunctionCalling.FunctionCalling.RunStreamingChatCompletionApiWithManualFunctionCallingAsync"/> for a sample demonstrating how to simplify
|
||||
/// function call content building out of streamed function call updates using the <see cref="FunctionCallContentBuilder"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task StreamFunctionCallContentAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ChatModelId);
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
Console.WriteLine("======== Stream Function Call Content ========");
|
||||
|
||||
// Create chat completion service
|
||||
OpenAIChatCompletionService chatCompletionService = new(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
// Create kernel with helper plugin.
|
||||
Kernel kernel = new();
|
||||
kernel.ImportPluginFromFunctions("HelperFunctions",
|
||||
[
|
||||
kernel.CreateFunctionFromMethod((string longTestString) => DateTime.UtcNow.ToString("R"), "GetCurrentUtcTime", "Retrieves the current time in UTC."),
|
||||
]);
|
||||
|
||||
// Create execution settings with manual function calling
|
||||
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(autoInvoke: false) };
|
||||
|
||||
// Create chat history with initial user question
|
||||
ChatHistory chatHistory = [];
|
||||
chatHistory.AddUserMessage("Hi, what is the current time?");
|
||||
|
||||
// Start streaming chat based on the chat history
|
||||
await foreach (StreamingChatMessageContent chatUpdate in chatCompletionService.GetStreamingChatMessageContentsAsync(chatHistory, settings, kernel))
|
||||
{
|
||||
// Getting list of function call updates requested by LLM
|
||||
var streamingFunctionCallUpdates = chatUpdate.Items.OfType<StreamingFunctionCallUpdateContent>();
|
||||
|
||||
// Iterating over function call updates. Please use the unctionCallContentBuilder to simplify function call content building.
|
||||
foreach (StreamingFunctionCallUpdateContent update in streamingFunctionCallUpdates)
|
||||
{
|
||||
Console.WriteLine($"Function call update: callId={update.CallId}, name={update.Name}, arguments={update.Arguments?.Replace("\n", "\\n")}, functionCallIndex={update.FunctionCallIndex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> StreamMessageOutputFromKernelAsync(Kernel kernel, string prompt)
|
||||
{
|
||||
bool roleWritten = false;
|
||||
string fullMessage = string.Empty;
|
||||
await foreach (var chatUpdate in kernel.InvokePromptStreamingAsync<StreamingChatMessageContent>(prompt))
|
||||
{
|
||||
if (!roleWritten && chatUpdate.Role.HasValue)
|
||||
{
|
||||
Console.Write($"{chatUpdate.Role.Value}: {chatUpdate.Content}");
|
||||
roleWritten = true;
|
||||
}
|
||||
if (chatUpdate.Content is { Length: > 0 })
|
||||
{
|
||||
fullMessage += chatUpdate.Content;
|
||||
Console.Write(chatUpdate.Content);
|
||||
}
|
||||
|
||||
// The last message in the chunk has the usage metadata.
|
||||
// https://platform.openai.com/docs/api-reference/chat/create#chat-create-stream_options
|
||||
if (chatUpdate.Metadata?["Usage"] is not null)
|
||||
{
|
||||
Console.WriteLine(chatUpdate.Metadata["Usage"]?.AsJson());
|
||||
}
|
||||
}
|
||||
Console.WriteLine("\n------------------------");
|
||||
return fullMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve extra information from a <see cref="StreamingChatMessageContent"/> inner content of type <see cref="OpenAI.Chat.StreamingChatCompletionUpdate"/>.
|
||||
/// </summary>
|
||||
/// <param name="streamChunk">An instance of <see cref="OpenAI.Chat.StreamingChatCompletionUpdate"/> retrieved as an inner content of <see cref="StreamingChatMessageContent"/>.</param>
|
||||
/// <remarks>
|
||||
/// This is a breaking glass scenario, any attempt on running with different versions of OpenAI SDK that introduces breaking changes
|
||||
/// may break the code below.
|
||||
/// </remarks>
|
||||
private void OutputInnerContent(OpenAI.Chat.StreamingChatCompletionUpdate streamChunk)
|
||||
{
|
||||
Console.WriteLine($"Id: {streamChunk.CompletionId}");
|
||||
Console.WriteLine($"Model: {streamChunk.Model}");
|
||||
Console.WriteLine($"Created at: {streamChunk.CreatedAt}");
|
||||
Console.WriteLine($"Finish reason: {(streamChunk.FinishReason?.ToString() ?? "--")}");
|
||||
Console.WriteLine($"System fingerprint: {streamChunk.SystemFingerprint}");
|
||||
|
||||
Console.WriteLine($"Content updates: {streamChunk.ContentUpdate.Count}");
|
||||
foreach (var contentUpdate in streamChunk.ContentUpdate)
|
||||
{
|
||||
Console.WriteLine($" Kind: {contentUpdate.Kind}");
|
||||
if (contentUpdate.Kind == OpenAI.Chat.ChatMessageContentPartKind.Text)
|
||||
{
|
||||
Console.WriteLine($" Text: {contentUpdate.Text}"); // Available as a properties of StreamingChatMessageContent.Items
|
||||
Console.WriteLine(" =======");
|
||||
}
|
||||
else if (contentUpdate.Kind == OpenAI.Chat.ChatMessageContentPartKind.Image)
|
||||
{
|
||||
Console.WriteLine($" Image uri: {contentUpdate.ImageUri}");
|
||||
Console.WriteLine($" Image media type: {contentUpdate.ImageBytesMediaType}");
|
||||
Console.WriteLine($" Image detail: {contentUpdate.ImageDetailLevel}");
|
||||
Console.WriteLine($" Image bytes: {contentUpdate.ImageBytes}");
|
||||
Console.WriteLine(" =======");
|
||||
}
|
||||
else if (contentUpdate.Kind == OpenAI.Chat.ChatMessageContentPartKind.Refusal)
|
||||
{
|
||||
Console.WriteLine($" Refusal: {contentUpdate.Refusal}");
|
||||
Console.WriteLine(" =======");
|
||||
}
|
||||
}
|
||||
|
||||
if (streamChunk.ContentTokenLogProbabilities.Count > 0)
|
||||
{
|
||||
Console.WriteLine("Content token log probabilities:");
|
||||
foreach (var contentTokenLogProbability in streamChunk.ContentTokenLogProbabilities)
|
||||
{
|
||||
Console.WriteLine($"Token: {contentTokenLogProbability.Token}");
|
||||
Console.WriteLine($"Log probability: {contentTokenLogProbability.LogProbability}");
|
||||
|
||||
Console.WriteLine(" Top log probabilities for this token:");
|
||||
foreach (var topLogProbability in contentTokenLogProbability.TopLogProbabilities)
|
||||
{
|
||||
Console.WriteLine($" Token: {topLogProbability.Token}");
|
||||
Console.WriteLine($" Log probability: {topLogProbability.LogProbability}");
|
||||
Console.WriteLine(" =======");
|
||||
}
|
||||
|
||||
Console.WriteLine("--------------");
|
||||
}
|
||||
}
|
||||
|
||||
if (streamChunk.RefusalTokenLogProbabilities.Count > 0)
|
||||
{
|
||||
Console.WriteLine("Refusal token log probabilities:");
|
||||
foreach (var refusalTokenLogProbability in streamChunk.RefusalTokenLogProbabilities)
|
||||
{
|
||||
Console.WriteLine($"Token: {refusalTokenLogProbability.Token}");
|
||||
Console.WriteLine($"Log probability: {refusalTokenLogProbability.LogProbability}");
|
||||
|
||||
Console.WriteLine(" Refusal top log probabilities for this token:");
|
||||
foreach (var topLogProbability in refusalTokenLogProbability.TopLogProbabilities)
|
||||
{
|
||||
Console.WriteLine($" Token: {topLogProbability.Token}");
|
||||
Console.WriteLine($" Log probability: {topLogProbability.LogProbability}");
|
||||
Console.WriteLine(" =======");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The last message in the chunk has the usage metadata.
|
||||
// https://platform.openai.com/docs/api-reference/chat/create#chat-create-stream_options
|
||||
if (streamChunk.Usage is not null)
|
||||
{
|
||||
Console.WriteLine($"Usage input tokens: {streamChunk.Usage.InputTokenCount}");
|
||||
Console.WriteLine($"Usage output tokens: {streamChunk.Usage.OutputTokenCount}");
|
||||
Console.WriteLine($"Usage total tokens: {streamChunk.Usage.TotalTokenCount}");
|
||||
}
|
||||
Console.WriteLine("------------------------");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// These examples demonstrate how to do web search with OpenAI Chat Completion
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Currently, web search is only supported with the following models:
|
||||
/// <list type="bullet">
|
||||
/// <item>gpt-4o-search-preview</item>
|
||||
/// <item>gpt-4o-mini-search-preview</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public class OpenAI_ChatCompletioWebSearch(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task UsingChatCompletionWithWebSearchEnabled()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
// Ensure you use a supported model
|
||||
var modelId = "gpt-4o-mini-search-preview";
|
||||
var settings = new OpenAIPromptExecutionSettings
|
||||
{
|
||||
WebSearchOptions = new ChatWebSearchOptions()
|
||||
};
|
||||
|
||||
Console.WriteLine($"======== Open AI - {nameof(UsingChatCompletionWithWebSearchEnabled)} ========");
|
||||
|
||||
OpenAIChatCompletionService chatService = new(modelId, TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
Console.WriteLine("Chat content:");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
var result = await chatService.GetChatMessageContentAsync("What are the top 3 trending news currently", settings);
|
||||
|
||||
// To retrieve the new annotations property from the result we need to use access the OpenAI.Chat.ChatCompletion directly
|
||||
var chatCompletion = result.InnerContent as OpenAI.Chat.ChatCompletion;
|
||||
|
||||
for (var i = 0; i < chatCompletion!.Annotations.Count; i++)
|
||||
{
|
||||
var annotation = chatCompletion!.Annotations[i];
|
||||
Console.WriteLine($"--- Annotation [{i + 1}] ---");
|
||||
Console.WriteLine($"Title: {annotation.WebResourceTitle}");
|
||||
Console.WriteLine($"Uri: {annotation.WebResourceUri}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Reflection;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
using OpenAI.Chat;
|
||||
using Resources;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// These examples demonstrate how to use audio input and output with OpenAI Chat Completion
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Currently, audio input and output is only supported with the following models:
|
||||
/// <list type="bullet">
|
||||
/// <item>gpt-4o-audio-preview</item>
|
||||
/// </list>
|
||||
/// The sample demonstrates:
|
||||
/// <list type="bullet">
|
||||
/// <item>How to send audio input to the model</item>
|
||||
/// <item>How to receive both text and audio output from the model</item>
|
||||
/// <item>How to save and process the audio response</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public class OpenAI_ChatCompletionWithAudio(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// This example demonstrates how to use audio input and receive both text and audio output from the model.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This sample shows:
|
||||
/// <list type="bullet">
|
||||
/// <item>Loading audio data from a resource file</item>
|
||||
/// <item>Configuring the chat completion service with audio options</item>
|
||||
/// <item>Enabling both text and audio response modalities</item>
|
||||
/// <item>Extracting and saving the audio response to a file</item>
|
||||
/// <item>Accessing the transcript metadata from the audio response</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task UsingChatCompletionWithLocalInputAudioAndOutputAudio()
|
||||
{
|
||||
Console.WriteLine($"======== Open AI - {nameof(UsingChatCompletionWithLocalInputAudioAndOutputAudio)} ========\n");
|
||||
|
||||
var audioBytes = await EmbeddedResource.ReadAllAsync("test_audio.wav");
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion("gpt-4o-audio-preview", TestConfiguration.OpenAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
var settings = new OpenAIPromptExecutionSettings
|
||||
{
|
||||
Audio = new ChatAudioOptions(ChatOutputAudioVoice.Shimmer, ChatOutputAudioFormat.Mp3),
|
||||
Modalities = ChatResponseModalities.Text | ChatResponseModalities.Audio
|
||||
};
|
||||
|
||||
var chatHistory = new ChatHistory("You are a friendly assistant.");
|
||||
|
||||
chatHistory.AddUserMessage([new AudioContent(audioBytes, "audio/wav")]);
|
||||
|
||||
var result = await chatCompletionService.GetChatMessageContentAsync(chatHistory, settings);
|
||||
|
||||
// Now we need to get the audio content from the result
|
||||
var audioReply = result.Items.First(i => i is AudioContent) as AudioContent;
|
||||
|
||||
var currentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
|
||||
var audioFile = Path.Combine(currentDirectory, "audio_output.mp3");
|
||||
if (File.Exists(audioFile))
|
||||
{
|
||||
File.Delete(audioFile);
|
||||
}
|
||||
File.WriteAllBytes(audioFile, audioReply!.Data!.Value.ToArray());
|
||||
|
||||
Console.WriteLine($"Generated audio: {new Uri(audioFile).AbsoluteUri}");
|
||||
Console.WriteLine($"Transcript: {audioReply.Metadata!["Transcript"]}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates how to use audio input and receive only text output from the model.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This sample shows:
|
||||
/// <list type="bullet">
|
||||
/// <item>Loading audio data from a resource file</item>
|
||||
/// <item>Configuring the chat completion service with audio options</item>
|
||||
/// <item>Setting response modalities to Text only</item>
|
||||
/// <item>Processing the text response from the model</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task UsingChatCompletionWithLocalInputAudioAndTextOutput()
|
||||
{
|
||||
Console.WriteLine($"======== Open AI - {nameof(UsingChatCompletionWithLocalInputAudioAndTextOutput)} ========\n");
|
||||
|
||||
var audioBytes = await EmbeddedResource.ReadAllAsync("test_audio.wav");
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion("gpt-4o-audio-preview", TestConfiguration.OpenAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
var settings = new OpenAIPromptExecutionSettings
|
||||
{
|
||||
Audio = new ChatAudioOptions(ChatOutputAudioVoice.Shimmer, ChatOutputAudioFormat.Mp3),
|
||||
Modalities = ChatResponseModalities.Text
|
||||
};
|
||||
|
||||
var chatHistory = new ChatHistory("You are a friendly assistant.");
|
||||
|
||||
chatHistory.AddUserMessage([new AudioContent(audioBytes, "audio/wav")]);
|
||||
|
||||
var result = await chatCompletionService.GetChatMessageContentAsync(chatHistory, settings);
|
||||
|
||||
// Now we need to get the audio content from the result
|
||||
Console.WriteLine($"Assistant > {result}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Resources;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// This example shows how to use binary files input with OpenAI's chat completion.
|
||||
/// </summary>
|
||||
public class OpenAI_ChatCompletionWithFile(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// This uses a local file as input for your chat
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingLocalFileInChatCompletion()
|
||||
{
|
||||
var fileBytes = await EmbeddedResource.ReadAllAsync("employees.pdf");
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
var chatHistory = new ChatHistory("You are a friendly assistant.");
|
||||
|
||||
chatHistory.AddUserMessage(
|
||||
[
|
||||
new TextContent("What's in this file?"),
|
||||
new BinaryContent(fileBytes, "application/pdf")
|
||||
]);
|
||||
|
||||
var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
|
||||
|
||||
Console.WriteLine(reply.Content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This uses a Base64 data URI as a binary file input for your chat
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingBase64DataUriInChatCompletion()
|
||||
{
|
||||
var fileBytes = await EmbeddedResource.ReadAllAsync("employees.pdf");
|
||||
var fileBase64 = Convert.ToBase64String(fileBytes.ToArray());
|
||||
var dataUri = $"data:application/pdf;base64,{fileBase64}";
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
var chatHistory = new ChatHistory("You are a friendly assistant.");
|
||||
|
||||
chatHistory.AddUserMessage(
|
||||
[
|
||||
new TextContent("What's in this file?"),
|
||||
new BinaryContent(dataUri)
|
||||
]);
|
||||
|
||||
var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
|
||||
|
||||
Console.WriteLine(reply.Content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
// The following example shows how to use Semantic Kernel with OpenAI API
|
||||
public class OpenAI_ChatCompletionWithReasoning(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// Sample showing how to use <see cref="Kernel"/> with chat completion and chat prompt syntax.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ChatPromptWithReasoningAsync()
|
||||
{
|
||||
Console.WriteLine("======== Open AI - Chat Completion with Reasoning ========");
|
||||
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ChatModelId);
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion(
|
||||
modelId: TestConfiguration.OpenAI.ChatModelId,
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
// Create execution settings with low reasoning effort.
|
||||
var executionSettings = new OpenAIPromptExecutionSettings //OpenAIPromptExecutionSettings
|
||||
{
|
||||
MaxTokens = 2000,
|
||||
ReasoningEffort = ChatReasoningEffortLevel.Low // Only available for reasoning models (i.e: o3-mini, o1, ...)
|
||||
};
|
||||
|
||||
// Create KernelArguments using the execution settings.
|
||||
var kernelArgs = new KernelArguments(executionSettings);
|
||||
|
||||
StringBuilder chatPrompt = new("""
|
||||
<message role="developer">You are an expert software engineer, specialized in the Semantic Kernel SDK and NET framework</message>
|
||||
<message role="user">Hi, Please craft me an example code in .NET using Semantic Kernel that implements a chat loop .</message>
|
||||
""");
|
||||
|
||||
// Invoke the prompt with high reasoning effort.
|
||||
var reply = await kernel.InvokePromptAsync(chatPrompt.ToString(), kernelArgs);
|
||||
|
||||
Console.WriteLine(reply);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sample showing how to use <see cref="IChatCompletionService"/> directly with a <see cref="ChatHistory"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ServicePromptWithReasoningAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ChatModelId);
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
Console.WriteLine("======== Open AI - Chat Completion with Reasoning ========");
|
||||
|
||||
OpenAIChatCompletionService chatCompletionService = new(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
// Create execution settings with low reasoning effort.
|
||||
var executionSettings = new OpenAIPromptExecutionSettings
|
||||
{
|
||||
MaxTokens = 2000,
|
||||
ReasoningEffort = ChatReasoningEffortLevel.Low // Only available for reasoning models (i.e: o3-mini, o1, ...)
|
||||
};
|
||||
|
||||
// Create a ChatHistory and add messages.
|
||||
var chatHistory = new ChatHistory();
|
||||
chatHistory.AddDeveloperMessage(
|
||||
"You are an expert software engineer, specialized in the Semantic Kernel SDK and .NET framework.");
|
||||
chatHistory.AddUserMessage(
|
||||
"Hi, Please craft me an example code in .NET using Semantic Kernel that implements a chat loop.");
|
||||
|
||||
// Instead of a prompt string, call GetChatMessageContentAsync with the chat history.
|
||||
var reply = await chatCompletionService.GetChatMessageContentAsync(
|
||||
chatHistory: chatHistory,
|
||||
executionSettings: executionSettings);
|
||||
|
||||
Console.WriteLine(reply);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Resources;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
// This example shows how to use GPT Vision model with different content types (text and image).
|
||||
public class OpenAI_ChatCompletionWithVision(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task RemoteImageAsync()
|
||||
{
|
||||
const string ImageUri = "https://upload.wikimedia.org/wikipedia/commons/d/d5/Half-timbered_mansion%2C_Zirkel%2C_East_view.jpg";
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion("gpt-4-vision-preview", TestConfiguration.OpenAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
var chatHistory = new ChatHistory("You are a friendly assistant.");
|
||||
|
||||
chatHistory.AddUserMessage(
|
||||
[
|
||||
new TextContent("What’s in this image?"),
|
||||
new ImageContent(new Uri(ImageUri))
|
||||
]);
|
||||
|
||||
var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
|
||||
|
||||
Console.WriteLine(reply.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LocalImageAsync()
|
||||
{
|
||||
var imageBytes = await EmbeddedResource.ReadAllAsync("sample_image.jpg");
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion("gpt-4-vision-preview", TestConfiguration.OpenAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
var chatHistory = new ChatHistory("You are a friendly assistant.");
|
||||
|
||||
chatHistory.AddUserMessage(
|
||||
[
|
||||
new TextContent("What’s in this image?"),
|
||||
new ImageContent(imageBytes, "image/jpg")
|
||||
]);
|
||||
|
||||
var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
|
||||
|
||||
Console.WriteLine(reply.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LocalImageWithImageDetailInMetadataAsync()
|
||||
{
|
||||
var imageBytes = await EmbeddedResource.ReadAllAsync("sample_image.jpg");
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion("gpt-4-vision-preview", TestConfiguration.OpenAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
var chatHistory = new ChatHistory("You are a friendly assistant.");
|
||||
|
||||
chatHistory.AddUserMessage(
|
||||
[
|
||||
new TextContent("What’s in this image?"),
|
||||
new ImageContent(imageBytes, "image/jpg") { Metadata = new Dictionary<string, object?> { ["ChatImageDetailLevel"] = "high" } }
|
||||
]);
|
||||
|
||||
var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
|
||||
|
||||
Console.WriteLine(reply.Content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ClientModel;
|
||||
using System.ClientModel.Primitives;
|
||||
using Microsoft.SemanticKernel;
|
||||
using OpenAI;
|
||||
|
||||
#pragma warning disable CA5399 // HttpClient is created without enabling CheckCertificateRevocationList
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// This example shows a way of using a Custom HttpClient and HttpHandler with OpenAI Connector to capture
|
||||
/// the request Uri and Headers for each request.
|
||||
/// </summary>
|
||||
public sealed class OpenAI_CustomClient(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task UsingCustomHttpClientWithOpenAI()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ChatModelId);
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
Console.WriteLine($"======== Open AI - {nameof(UsingCustomHttpClientWithOpenAI)} ========");
|
||||
|
||||
// Create an HttpClient and include your custom header(s)
|
||||
using var myCustomHttpHandler = new MyCustomClientHttpHandler(Output);
|
||||
using var myCustomClient = new HttpClient(handler: myCustomHttpHandler);
|
||||
myCustomClient.DefaultRequestHeaders.Add("My-Custom-Header", "My Custom Value");
|
||||
|
||||
// Configure AzureOpenAIClient to use the customized HttpClient
|
||||
var clientOptions = new OpenAIClientOptions
|
||||
{
|
||||
Transport = new HttpClientPipelineTransport(myCustomClient),
|
||||
NetworkTimeout = TimeSpan.FromSeconds(30),
|
||||
RetryPolicy = new ClientRetryPolicy()
|
||||
};
|
||||
|
||||
var customClient = new OpenAIClient(new ApiKeyCredential(TestConfiguration.OpenAI.ApiKey), clientOptions);
|
||||
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, customClient)
|
||||
.Build();
|
||||
|
||||
// Load semantic plugin defined with prompt templates
|
||||
string folder = RepoFiles.SamplePluginsPath();
|
||||
|
||||
kernel.ImportPluginFromPromptDirectory(Path.Combine(folder, "FunPlugin"));
|
||||
|
||||
// Run
|
||||
var result = await kernel.InvokeAsync(
|
||||
kernel.Plugins["FunPlugin"]["Excuses"],
|
||||
new() { ["input"] = "I have no homework" }
|
||||
);
|
||||
|
||||
Console.WriteLine(result.GetValue<string>());
|
||||
|
||||
myCustomClient.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normally you would use a custom HttpClientHandler to add custom logic to your custom http client
|
||||
/// This uses the ITestOutputHelper to write the requested URI to the test output
|
||||
/// </summary>
|
||||
/// <param name="output">The <see cref="ITestOutputHelper"/> to write the requested URI to the test output </param>
|
||||
private sealed class MyCustomClientHttpHandler(ITestOutputHelper output) : HttpClientHandler
|
||||
{
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
output.WriteLine($"Requested URI: {request.RequestUri}");
|
||||
|
||||
request.Headers.Where(h => h.Key != "Authorization")
|
||||
.ToList()
|
||||
.ForEach(h => output.WriteLine($"{h.Key}: {string.Join(", ", h.Value)}"));
|
||||
output.WriteLine("--------------------------------");
|
||||
|
||||
// Add custom logic here
|
||||
return await base.SendAsync(request, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
public sealed class OpenAI_FunctionCalling(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task AutoInvokeKernelFunctionsAsync()
|
||||
{
|
||||
// Create a kernel with MistralAI chat completion and WeatherPlugin
|
||||
Kernel kernel = CreateKernelWithPlugin<WeatherPlugin>();
|
||||
|
||||
// Invoke chat prompt with auto invocation of functions enabled
|
||||
const string ChatPrompt = """
|
||||
<message role="user">What is the weather like in Paris?</message>
|
||||
""";
|
||||
var executionSettings = new OpenAIPromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
|
||||
var chatSemanticFunction = kernel.CreateFunctionFromPrompt(
|
||||
ChatPrompt, executionSettings);
|
||||
var chatPromptResult = await kernel.InvokeAsync(chatSemanticFunction);
|
||||
|
||||
Console.WriteLine(chatPromptResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AutoInvokeKernelFunctionsMultipleCallsAsync()
|
||||
{
|
||||
// Create a kernel with MistralAI chat completion and WeatherPlugin
|
||||
Kernel kernel = CreateKernelWithPlugin<WeatherPlugin>();
|
||||
var service = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
// Invoke chat prompt with auto invocation of functions enabled
|
||||
var chatHistory = new ChatHistory
|
||||
{
|
||||
new ChatMessageContent(AuthorRole.User, "What is the weather like in Paris?")
|
||||
};
|
||||
var executionSettings = new OpenAIPromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
|
||||
var result1 = await service.GetChatMessageContentAsync(chatHistory, executionSettings, kernel);
|
||||
chatHistory.Add(result1);
|
||||
|
||||
chatHistory.Add(new ChatMessageContent(AuthorRole.User, "What is the weather like in Marseille?"));
|
||||
var result2 = await service.GetChatMessageContentAsync(chatHistory, executionSettings, kernel);
|
||||
|
||||
Console.WriteLine(result1);
|
||||
Console.WriteLine(result2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AutoInvokeKernelFunctionsWithComplexParameterAsync()
|
||||
{
|
||||
// Create a kernel with MistralAI chat completion and HolidayPlugin
|
||||
Kernel kernel = CreateKernelWithPlugin<HolidayPlugin>();
|
||||
|
||||
// Invoke chat prompt with auto invocation of functions enabled
|
||||
const string ChatPrompt = """
|
||||
<message role="user">Book a holiday for me from 6th June 2025 to 20th June 2025?</message>
|
||||
""";
|
||||
var executionSettings = new OpenAIPromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
|
||||
var chatSemanticFunction = kernel.CreateFunctionFromPrompt(
|
||||
ChatPrompt, executionSettings);
|
||||
var chatPromptResult = await kernel.InvokeAsync(chatSemanticFunction);
|
||||
|
||||
Console.WriteLine(chatPromptResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AutoInvokeLightPluginAsync()
|
||||
{
|
||||
// Create a kernel with OpenAI chat completion and LightPlugin
|
||||
Kernel kernel = CreateKernelWithPlugin<LightPlugin>();
|
||||
kernel.FunctionInvocationFilters.Add(new FunctionFilterExample(this.Output));
|
||||
|
||||
// Invoke chat prompt with auto invocation of functions enabled
|
||||
const string ChatPrompt = """
|
||||
<message role="user">Turn on the light?</message>
|
||||
""";
|
||||
var executionSettings = new OpenAIPromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
|
||||
var chatSemanticFunction = kernel.CreateFunctionFromPrompt(
|
||||
ChatPrompt, executionSettings);
|
||||
var chatPromptResult = await kernel.InvokeAsync(chatSemanticFunction);
|
||||
|
||||
Console.WriteLine(chatPromptResult);
|
||||
}
|
||||
|
||||
private sealed class WeatherPlugin
|
||||
{
|
||||
[KernelFunction]
|
||||
[Description("Get the current weather in a given location.")]
|
||||
public string GetWeather(
|
||||
[Description("The city and department, e.g. Marseille, 13")] string location
|
||||
) => $"12°C\nWind: 11 KMPH\nHumidity: 48%\nMostly cloudy\nLocation: {location}";
|
||||
}
|
||||
|
||||
private sealed class HolidayPlugin
|
||||
{
|
||||
[KernelFunction]
|
||||
[Description("Book a holiday for a specified time period.")]
|
||||
public string BookHoliday(
|
||||
[Description("Holiday time period")] HolidayRequest holidayRequest
|
||||
) => $"Holiday booked, starting {holidayRequest.StartDate} and ending {holidayRequest.EndDate}";
|
||||
}
|
||||
|
||||
private sealed class HolidayRequest
|
||||
{
|
||||
[Description("The date when the holiday period starts in ISO 8601 format")]
|
||||
public string StartDate { get; set; } = string.Empty;
|
||||
|
||||
[Description("The date when the holiday period ends in ISO 8601 format")]
|
||||
public string EndDate { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
private sealed class LightPlugin
|
||||
{
|
||||
public bool IsOn { get; set; } = false;
|
||||
|
||||
[KernelFunction]
|
||||
[Description("Gets the state of the light.")]
|
||||
public string GetState() => IsOn ? "on" : "off";
|
||||
|
||||
[KernelFunction]
|
||||
[Description("Changes the state of the light.'")]
|
||||
public string ChangeState(bool newState)
|
||||
{
|
||||
this.IsOn = newState;
|
||||
var state = GetState();
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
private Kernel CreateKernelWithPlugin<T>()
|
||||
{
|
||||
// Create a logging handler to output HTTP requests and responses
|
||||
var handler = new LoggingHandler(new HttpClientHandler(), this.Output);
|
||||
HttpClient httpClient = new(handler);
|
||||
|
||||
// Create a kernel with OpenAI chat completion and WeatherPlugin
|
||||
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
|
||||
kernelBuilder.AddOpenAIChatCompletion(
|
||||
modelId: TestConfiguration.OpenAI.ChatModelId!,
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey!,
|
||||
httpClient: httpClient);
|
||||
kernelBuilder.Plugins.AddFromType<T>();
|
||||
Kernel kernel = kernelBuilder.Build();
|
||||
return kernel;
|
||||
}
|
||||
|
||||
private sealed class FunctionFilterExample(ITestOutputHelper output) : IFunctionInvocationFilter
|
||||
{
|
||||
public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next)
|
||||
{
|
||||
output.WriteLine($"Function {context.Function.Name} is being invoked with arguments: {JsonSerializer.Serialize(context.Arguments)}");
|
||||
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
using Microsoft.SemanticKernel.Memory;
|
||||
using Microsoft.SemanticKernel.Plugins.Memory;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// Samples show how to use <see cref="TextMemoryPlugin"/> with OpenAI chat completion.
|
||||
/// </summary>
|
||||
public class OpenAI_FunctionCallingWithMemoryPlugin(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// This sample demonstrates how to use a function to retrieve useful information from the memory.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The old <see cref="VolatileMemoryStore"/> and <see cref="SemanticTextMemory"/> classes are used to store and retrieve information.
|
||||
/// These implementations will be replaced soon and this sample will be updated to demonstrate the new (much improved) pattern.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task UseFunctionCallingToRetrieveMemoriesAsync()
|
||||
{
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ChatModelId);
|
||||
Assert.NotNull(TestConfiguration.OpenAI.EmbeddingModelId);
|
||||
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
// Create a kernel with OpenAI chat completion and text embedding generation
|
||||
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
|
||||
kernelBuilder.AddOpenAIChatCompletion(
|
||||
modelId: TestConfiguration.OpenAI.ChatModelId!,
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey!);
|
||||
kernelBuilder.AddOpenAIEmbeddingGenerator(
|
||||
modelId: TestConfiguration.OpenAI.EmbeddingModelId!,
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey!);
|
||||
kernelBuilder.Services.AddSingleton<ITestOutputHelper>(this.Output);
|
||||
kernelBuilder.Services.AddSingleton<IFunctionInvocationFilter, FunctionInvocationFilter>();
|
||||
Kernel kernel = kernelBuilder.Build();
|
||||
|
||||
// Create a text memory store and populate it with sample data
|
||||
var embeddingGenerator = kernel.GetRequiredService<IEmbeddingGenerator<string, Embedding<float>>>();
|
||||
VolatileMemoryStore memoryStore = new();
|
||||
SemanticTextMemory textMemory = new(memoryStore, embeddingGenerator);
|
||||
string collectionName = "SemanticKernel";
|
||||
await PopulateMemoryAsync(collectionName, textMemory);
|
||||
|
||||
// Add the text memory plugin to the kernel
|
||||
MemoryPlugin memoryPlugin = new(collectionName, textMemory);
|
||||
kernel.Plugins.AddFromObject(memoryPlugin, "Memory");
|
||||
|
||||
// Invoke chat prompt with auto invocation of functions enabled
|
||||
var executionSettings = new OpenAIPromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
|
||||
var chatPrompt =
|
||||
"""
|
||||
<message role="user">What is Semantic Kernel?</message>
|
||||
""";
|
||||
var response = await kernel.InvokePromptAsync(chatPrompt, new(executionSettings));
|
||||
|
||||
Console.WriteLine(response);
|
||||
}
|
||||
|
||||
#region private
|
||||
/// <summary>
|
||||
/// Utility to populate a text memory store with sample data.
|
||||
/// </summary>
|
||||
private static async Task PopulateMemoryAsync(string collection, SemanticTextMemory textMemory)
|
||||
{
|
||||
string[] entries =
|
||||
[
|
||||
"Semantic Kernel is a lightweight, open-source development kit that lets you easily build AI agents and integrate the latest AI models into your C#, Python, or Java codebase. It serves as an efficient middleware that enables rapid delivery of enterprise-grade solutions.",
|
||||
"Semantic Kernel is a new AI SDK, and a simple and yet powerful programming model that lets you add large language capabilities to your app in just a matter of minutes. It uses natural language prompting to create and execute semantic kernel AI tasks across multiple languages and platforms.",
|
||||
"In this guide, you learned how to quickly get started with Semantic Kernel by building a simple AI agent that can interact with an AI service and run your code. To see more examples and learn how to build more complex AI agents, check out our in-depth samples.",
|
||||
"The Semantic Kernel extension for Visual Studio Code makes it easy to design and test semantic functions.The extension provides an interface for designing semantic functions and allows you to test them with the push of a button with your existing models and data.",
|
||||
"The kernel is the central component of Semantic Kernel.At its simplest, the kernel is a Dependency Injection container that manages all of the services and plugins necessary to run your AI application."
|
||||
];
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
await textMemory.SaveInformationAsync(
|
||||
collection: collection,
|
||||
text: entry,
|
||||
id: Guid.NewGuid().ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin that provides a function to retrieve useful information from the memory.
|
||||
/// </summary>
|
||||
private sealed class MemoryPlugin(string collection, ISemanticTextMemory memory)
|
||||
{
|
||||
[KernelFunction]
|
||||
[Description("Retrieve useful information to help answer a question.")]
|
||||
public async Task<string> GetUsefulInformationAsync(
|
||||
[Description("The question being asked")] string question)
|
||||
{
|
||||
List<MemoryQueryResult> memories = await memory
|
||||
.SearchAsync(collection, question)
|
||||
.ToListAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return JsonSerializer.Serialize(memories.Select(x => x.Metadata.Text));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IFunctionInvocationFilter"/> that logs the function invocation.
|
||||
/// </summary>
|
||||
private sealed class FunctionInvocationFilter(ITestOutputHelper output) : IFunctionInvocationFilter
|
||||
{
|
||||
private readonly ITestOutputHelper _output = output;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task OnFunctionInvocationAsync(Microsoft.SemanticKernel.FunctionInvocationContext context, Func<Microsoft.SemanticKernel.FunctionInvocationContext, Task> next)
|
||||
{
|
||||
this._output.WriteLine($"Function Invocation - {context.Function.Name}");
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// Samples showing how to get the LLM to provide the reason it is calling a function
|
||||
/// when using automatic function calling.
|
||||
/// </summary>
|
||||
public sealed class OpenAI_ReasonedFunctionCalling(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// Shows how to ask the model to explain function calls after execution.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Asking the model to explain function calls after execution works well but may be too late depending on your use case.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task AskAssistantToExplainFunctionCallsAfterExecutionAsync()
|
||||
{
|
||||
// Create a kernel with OpenAI chat completion and WeatherPlugin
|
||||
Kernel kernel = CreateKernelWithPlugin<WeatherPlugin>();
|
||||
var service = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
// Invoke chat prompt with auto invocation of functions enabled
|
||||
var chatHistory = new ChatHistory
|
||||
{
|
||||
new ChatMessageContent(AuthorRole.User, "What is the weather like in Paris?")
|
||||
};
|
||||
var executionSettings = new OpenAIPromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
|
||||
var result1 = await service.GetChatMessageContentAsync(chatHistory, executionSettings, kernel);
|
||||
chatHistory.Add(result1);
|
||||
Console.WriteLine(result1);
|
||||
|
||||
chatHistory.Add(new ChatMessageContent(AuthorRole.User, "Explain why you called those functions?"));
|
||||
var result2 = await service.GetChatMessageContentAsync(chatHistory, executionSettings, kernel);
|
||||
Console.WriteLine(result2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows how to use a function that has been decorated with an extra parameter which must be set by the model
|
||||
/// with the reason this function needs to be called.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UseDecoratedFunctionAsync()
|
||||
{
|
||||
// Create a kernel with OpenAI chat completion and WeatherPlugin
|
||||
Kernel kernel = CreateKernelWithPlugin<DecoratedWeatherPlugin>();
|
||||
var service = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
// Invoke chat prompt with auto invocation of functions enabled
|
||||
var chatHistory = new ChatHistory
|
||||
{
|
||||
new ChatMessageContent(AuthorRole.User, "What is the weather like in Paris?")
|
||||
};
|
||||
var executionSettings = new OpenAIPromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
|
||||
var result = await service.GetChatMessageContentAsync(chatHistory, executionSettings, kernel);
|
||||
chatHistory.Add(result);
|
||||
Console.WriteLine(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows how to use a function that has been decorated with an extra parameter which must be set by the model
|
||||
/// with the reason this function needs to be called.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UseDecoratedFunctionWithPromptAsync()
|
||||
{
|
||||
// Create a kernel with OpenAI chat completion and WeatherPlugin
|
||||
Kernel kernel = CreateKernelWithPlugin<DecoratedWeatherPlugin>();
|
||||
var service = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
// Invoke chat prompt with auto invocation of functions enabled
|
||||
string chatPrompt = """
|
||||
<message role="user">What is the weather like in Paris?</message>
|
||||
""";
|
||||
var executionSettings = new OpenAIPromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
|
||||
var result = await kernel.InvokePromptAsync(chatPrompt, new(executionSettings));
|
||||
Console.WriteLine(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asking the model to explain function calls in response to each function call can work but the model may also
|
||||
/// get confused and treat the request to explain the function calls as an error response from the function calls.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task AskAssistantToExplainFunctionCallsBeforeExecutionAsync()
|
||||
{
|
||||
// Create a kernel with OpenAI chat completion and WeatherPlugin
|
||||
Kernel kernel = CreateKernelWithPlugin<WeatherPlugin>();
|
||||
kernel.AutoFunctionInvocationFilters.Add(new RespondExplainFunctionInvocationFilter());
|
||||
var service = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
// Invoke chat prompt with auto invocation of functions enabled
|
||||
var chatHistory = new ChatHistory
|
||||
{
|
||||
new ChatMessageContent(AuthorRole.User, "What is the weather like in Paris?")
|
||||
};
|
||||
var executionSettings = new OpenAIPromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
|
||||
var result = await service.GetChatMessageContentAsync(chatHistory, executionSettings, kernel);
|
||||
chatHistory.Add(result);
|
||||
Console.WriteLine(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asking to the model to explain function calls using a separate conversation i.e. chat history seems to provide the
|
||||
/// best results. This may be because the model can focus on explaining the function calls without being confused by other
|
||||
/// messages in the chat history.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task QueryAssistantToExplainFunctionCallsBeforeExecutionAsync()
|
||||
{
|
||||
// Create a kernel with OpenAI chat completion and WeatherPlugin
|
||||
Kernel kernel = CreateKernelWithPlugin<WeatherPlugin>();
|
||||
kernel.AutoFunctionInvocationFilters.Add(new QueryExplainFunctionInvocationFilter(this.Output));
|
||||
var service = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
// Invoke chat prompt with auto invocation of functions enabled
|
||||
var chatHistory = new ChatHistory
|
||||
{
|
||||
new ChatMessageContent(AuthorRole.User, "What is the weather like in Paris?")
|
||||
};
|
||||
var executionSettings = new OpenAIPromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
|
||||
var result = await service.GetChatMessageContentAsync(chatHistory, executionSettings, kernel);
|
||||
chatHistory.Add(result);
|
||||
Console.WriteLine(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This <see cref="IAutoFunctionInvocationFilter"/> will respond to function call requests and ask the model to explain why it is
|
||||
/// calling the function(s). This filter must be registered transiently because it maintains state for the functions that have been
|
||||
/// called for a single chat history.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This filter implementation is not intended for production use. It is a demonstration of how to use filters to interact with the
|
||||
/// model during automatic function invocation so that the model explains why it is calling a function.
|
||||
/// </remarks>
|
||||
private sealed class RespondExplainFunctionInvocationFilter : IAutoFunctionInvocationFilter
|
||||
{
|
||||
private readonly HashSet<string> _functionNames = [];
|
||||
|
||||
public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next)
|
||||
{
|
||||
// Get the function calls for which we need an explanation
|
||||
var functionCalls = FunctionCallContent.GetFunctionCalls(context.ChatHistory.Last());
|
||||
var needExplanation = 0;
|
||||
foreach (var functionCall in functionCalls)
|
||||
{
|
||||
var functionName = $"{functionCall.PluginName}-{functionCall.FunctionName}";
|
||||
if (_functionNames.Add(functionName))
|
||||
{
|
||||
needExplanation++;
|
||||
}
|
||||
}
|
||||
|
||||
if (needExplanation > 0)
|
||||
{
|
||||
// Create a response asking why these functions are being called
|
||||
context.Result = new FunctionResult(context.Result, $"Provide an explanation why you are calling function {string.Join(',', _functionNames)} and try again");
|
||||
return;
|
||||
}
|
||||
|
||||
// Invoke the functions
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This <see cref="IAutoFunctionInvocationFilter"/> uses the currently available <see cref="IChatCompletionService"/> to query the model
|
||||
/// to find out what certain functions are being called.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This filter implementation is not intended for production use. It is a demonstration of how to use filters to interact with the
|
||||
/// model during automatic function invocation so that the model explains why it is calling a function.
|
||||
/// </remarks>
|
||||
private sealed class QueryExplainFunctionInvocationFilter(ITestOutputHelper output) : IAutoFunctionInvocationFilter
|
||||
{
|
||||
private readonly ITestOutputHelper _output = output;
|
||||
|
||||
public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next)
|
||||
{
|
||||
// Invoke the model to explain why the functions are being called
|
||||
var message = context.ChatHistory[^2];
|
||||
var functionCalls = FunctionCallContent.GetFunctionCalls(context.ChatHistory.Last());
|
||||
var functionNames = functionCalls.Select(fc => $"{fc.PluginName}-{fc.FunctionName}").ToList();
|
||||
var service = context.Kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
var chatHistory = new ChatHistory
|
||||
{
|
||||
new ChatMessageContent(AuthorRole.User, $"Provide an explanation why these functions: {string.Join(',', functionNames)} need to be called to answer this query: {message.Content}")
|
||||
};
|
||||
var executionSettings = new OpenAIPromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(autoInvoke: false) };
|
||||
var result = await service.GetChatMessageContentAsync(chatHistory, executionSettings, context.Kernel);
|
||||
this._output.WriteLine(result);
|
||||
|
||||
// Invoke the functions
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
private sealed class WeatherPlugin
|
||||
{
|
||||
[KernelFunction]
|
||||
[Description("Get the current weather in a given location.")]
|
||||
public string GetWeather(
|
||||
[Description("The city and department, e.g. Marseille, 13")] string location
|
||||
) => $"12°C\nWind: 11 KMPH\nHumidity: 48%\nMostly cloudy\nLocation: {location}";
|
||||
}
|
||||
|
||||
private sealed class DecoratedWeatherPlugin
|
||||
{
|
||||
private readonly WeatherPlugin _weatherPlugin = new();
|
||||
|
||||
[KernelFunction]
|
||||
[Description("Get the current weather in a given location.")]
|
||||
public string GetWeather(
|
||||
[Description("A detailed explanation why this function is being called")] string explanation,
|
||||
[Description("The city and department, e.g. Marseille, 13")] string location
|
||||
) => this._weatherPlugin.GetWeather(location);
|
||||
}
|
||||
|
||||
private Kernel CreateKernelWithPlugin<T>()
|
||||
{
|
||||
// Create a logging handler to output HTTP requests and responses
|
||||
var handler = new LoggingHandler(new HttpClientHandler(), this.Output);
|
||||
HttpClient httpClient = new(handler);
|
||||
|
||||
// Create a kernel with OpenAI chat completion and WeatherPlugin
|
||||
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
|
||||
kernelBuilder.AddOpenAIChatCompletion(
|
||||
modelId: TestConfiguration.OpenAI.ChatModelId!,
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey!,
|
||||
httpClient: httpClient);
|
||||
kernelBuilder.Plugins.AddFromType<T>();
|
||||
Kernel kernel = kernelBuilder.Build();
|
||||
return kernel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// Sample shows how to the model will reuse a function result from the chat history.
|
||||
/// </summary>
|
||||
public sealed class OpenAI_RepeatedFunctionCalling(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// Sample shows a chat history where each ask requires a function to be called but when
|
||||
/// an ask is repeated the model will reuse the previous function result.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ReuseFunctionResultExecutionAsync()
|
||||
{
|
||||
// Create a kernel with OpenAI chat completion and WeatherPlugin
|
||||
Kernel kernel = CreateKernelWithPlugin<WeatherPlugin>();
|
||||
var service = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
// Invoke chat prompt with auto invocation of functions enabled
|
||||
var chatHistory = new ChatHistory
|
||||
{
|
||||
new ChatMessageContent(AuthorRole.User, "What is the weather like in Boston?")
|
||||
};
|
||||
var executionSettings = new OpenAIPromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
|
||||
var result1 = await service.GetChatMessageContentAsync(chatHistory, executionSettings, kernel);
|
||||
chatHistory.Add(result1);
|
||||
Console.WriteLine(result1);
|
||||
|
||||
chatHistory.Add(new ChatMessageContent(AuthorRole.User, "What is the weather like in Paris?"));
|
||||
var result2 = await service.GetChatMessageContentAsync(chatHistory, executionSettings, kernel);
|
||||
chatHistory.Add(result2);
|
||||
Console.WriteLine(result2);
|
||||
|
||||
chatHistory.Add(new ChatMessageContent(AuthorRole.User, "What is the weather like in Dublin?"));
|
||||
var result3 = await service.GetChatMessageContentAsync(chatHistory, executionSettings, kernel);
|
||||
chatHistory.Add(result3);
|
||||
Console.WriteLine(result3);
|
||||
|
||||
chatHistory.Add(new ChatMessageContent(AuthorRole.User, "What is the weather like in Boston?"));
|
||||
var result4 = await service.GetChatMessageContentAsync(chatHistory, executionSettings, kernel);
|
||||
chatHistory.Add(result4);
|
||||
Console.WriteLine(result4);
|
||||
}
|
||||
private sealed class WeatherPlugin
|
||||
{
|
||||
[KernelFunction]
|
||||
[Description("Get the current weather in a given location.")]
|
||||
public string GetWeather(
|
||||
[Description("The city and department, e.g. Marseille, 13")] string location
|
||||
) => $"12°C\nWind: 11 KMPH\nHumidity: 48%\nMostly cloudy\nLocation: {location}";
|
||||
}
|
||||
|
||||
private Kernel CreateKernelWithPlugin<T>()
|
||||
{
|
||||
// Create a logging handler to output HTTP requests and responses
|
||||
var handler = new LoggingHandler(new HttpClientHandler(), this.Output);
|
||||
HttpClient httpClient = new(handler);
|
||||
|
||||
// Create a kernel with OpenAI chat completion and WeatherPlugin
|
||||
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
|
||||
kernelBuilder.AddOpenAIChatCompletion(
|
||||
modelId: TestConfiguration.OpenAI.ChatModelId!,
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey!,
|
||||
httpClient: httpClient);
|
||||
kernelBuilder.Plugins.AddFromType<T>();
|
||||
Kernel kernel = kernelBuilder.Build();
|
||||
return kernel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,465 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json;
|
||||
using Azure.Identity;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// Structured Outputs is a feature in OpenAI API that ensures the model will always generate responses based on provided JSON Schema.
|
||||
/// This gives more control over model responses, allows to avoid model hallucinations and write simpler prompts without a need to be specific about response format.
|
||||
/// More information here: <see href="https://platform.openai.com/docs/guides/structured-outputs/structured-outputs"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// OpenAI Structured Outputs feature is available only in latest large language models, starting with GPT-4o.
|
||||
/// More information here: <see href="https://platform.openai.com/docs/guides/structured-outputs/supported-models"/>.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// Some keywords from JSON Schema are not supported in OpenAI Structured Outputs yet. For example, "format" keyword for strings is not supported.
|
||||
/// It means that properties with types <see cref="DateTime"/>, <see cref="DateTimeOffset"/>, <see cref="DateOnly"/>, <see cref="TimeSpan"/>,
|
||||
/// <see cref="TimeOnly"/>, <see cref="Uri"/> are not supported.
|
||||
/// This information should be taken into consideration during response format type design.
|
||||
/// More information here: <see href="https://platform.openai.com/docs/guides/structured-outputs/some-type-specific-keywords-are-not-yet-supported"/>.
|
||||
/// </remarks>
|
||||
public class OpenAI_StructuredOutputs(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// This method shows how to enable Structured Outputs feature with <see cref="ChatResponseFormat"/> object by providing
|
||||
/// JSON schema of desired response format.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task StructuredOutputsWithChatResponseFormatAsync()
|
||||
{
|
||||
// Initialize kernel.
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion(
|
||||
modelId: "gpt-4o-2024-08-06",
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
// Initialize ChatResponseFormat object with JSON schema of desired response format.
|
||||
ChatResponseFormat chatResponseFormat = ChatResponseFormat.CreateJsonSchemaFormat(
|
||||
jsonSchemaFormatName: "movie_result",
|
||||
jsonSchema: BinaryData.FromString("""
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Movies": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Title": { "type": "string" },
|
||||
"Director": { "type": "string" },
|
||||
"ReleaseYear": { "type": "integer" },
|
||||
"Rating": { "type": "number" },
|
||||
"IsAvailableOnStreaming": { "type": "boolean" },
|
||||
"Tags": { "type": "array", "items": { "type": "string" } }
|
||||
},
|
||||
"required": ["Title", "Director", "ReleaseYear", "Rating", "IsAvailableOnStreaming", "Tags"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["Movies"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
"""),
|
||||
jsonSchemaIsStrict: true);
|
||||
|
||||
// Specify response format by setting ChatResponseFormat object in prompt execution settings.
|
||||
var executionSettings = new OpenAIPromptExecutionSettings
|
||||
{
|
||||
ResponseFormat = chatResponseFormat
|
||||
};
|
||||
|
||||
// Send a request and pass prompt execution settings with desired response format.
|
||||
var result = await kernel.InvokePromptAsync("What are the top 10 movies of all time?", new(executionSettings));
|
||||
|
||||
// Deserialize string response to a strong type to access type properties.
|
||||
// At this point, the deserialization logic won't fail, because MovieResult type was described using JSON schema.
|
||||
// This ensures that response string is a serialized version of MovieResult type.
|
||||
var movieResult = JsonSerializer.Deserialize<MovieResult>(result.ToString())!;
|
||||
|
||||
// Output the result.
|
||||
this.OutputResult(movieResult);
|
||||
|
||||
// Output:
|
||||
|
||||
// Title: The Lord of the Rings: The Fellowship of the Ring
|
||||
// Director: Peter Jackson
|
||||
// Release year: 2001
|
||||
// Rating: 8.8
|
||||
// Is available on streaming: True
|
||||
// Tags: Adventure,Drama,Fantasy
|
||||
|
||||
// ...and more...
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method shows how to enable Structured Outputs feature with <see cref="Type"/> object by providing
|
||||
/// the type of desired response format. In this scenario, JSON schema will be created automatically based on provided type.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task StructuredOutputsWithTypeInExecutionSettingsAsync()
|
||||
{
|
||||
// Initialize kernel.
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion(
|
||||
modelId: "gpt-4o-2024-08-06",
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
// Specify response format by setting Type object in prompt execution settings.
|
||||
var executionSettings = new OpenAIPromptExecutionSettings
|
||||
{
|
||||
ResponseFormat = typeof(MovieResult)
|
||||
};
|
||||
|
||||
// Send a request and pass prompt execution settings with desired response format.
|
||||
var result = await kernel.InvokePromptAsync("What are the top 10 movies of all time?", new(executionSettings));
|
||||
|
||||
// Deserialize string response to a strong type to access type properties.
|
||||
// At this point, the deserialization logic won't fail, because MovieResult type was specified as desired response format.
|
||||
// This ensures that response string is a serialized version of MovieResult type.
|
||||
var movieResult = JsonSerializer.Deserialize<MovieResult>(result.ToString())!;
|
||||
|
||||
// Output the result.
|
||||
this.OutputResult(movieResult);
|
||||
|
||||
// Output:
|
||||
|
||||
// Title: The Lord of the Rings: The Fellowship of the Ring
|
||||
// Director: Peter Jackson
|
||||
// Release year: 2001
|
||||
// Rating: 8.8
|
||||
// Is available on streaming: True
|
||||
// Tags: Adventure,Drama,Fantasy
|
||||
|
||||
// ...and more...
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method shows how to use Structured Outputs feature in combination with Function Calling and OpenAI models.
|
||||
/// <see cref="EmailPlugin.GetEmails"/> function returns a <see cref="List{T}"/> of email bodies.
|
||||
/// As for final result, the desired response format should be <see cref="Email"/>, which contains additional <see cref="Email.Category"/> property.
|
||||
/// This shows how the data can be transformed with AI using strong types without additional instructions in the prompt.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task StructuredOutputsWithFunctionCallingOpenAIAsync()
|
||||
{
|
||||
// Initialize kernel.
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion(
|
||||
modelId: "gpt-4o-2024-08-06",
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
kernel.ImportPluginFromType<EmailPlugin>();
|
||||
|
||||
// Specify response format by setting Type object in prompt execution settings and enable automatic function calling.
|
||||
var executionSettings = new OpenAIPromptExecutionSettings
|
||||
{
|
||||
ResponseFormat = typeof(EmailResult),
|
||||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
|
||||
};
|
||||
|
||||
// Send a request and pass prompt execution settings with desired response format.
|
||||
var result = await kernel.InvokePromptAsync("Process the emails.", new(executionSettings));
|
||||
|
||||
// Deserialize string response to a strong type to access type properties.
|
||||
// At this point, the deserialization logic won't fail, because EmailResult type was specified as desired response format.
|
||||
// This ensures that response string is a serialized version of EmailResult type.
|
||||
var emailResult = JsonSerializer.Deserialize<EmailResult>(result.ToString())!;
|
||||
|
||||
// Output the result.
|
||||
this.OutputResult(emailResult);
|
||||
|
||||
// Output:
|
||||
|
||||
// Email #1
|
||||
// Body: Let's catch up over coffee this Saturday. It's been too long!
|
||||
// Category: Social
|
||||
|
||||
// Email #2
|
||||
// Body: Please review the attached document and provide your feedback by EOD.
|
||||
// Category: Work
|
||||
|
||||
// ...and more...
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method shows how to use Structured Outputs feature in combination with Function Calling and Azure OpenAI models.
|
||||
/// <see cref="EmailPlugin.GetEmails"/> function returns a <see cref="List{T}"/> of email bodies.
|
||||
/// As for final result, the desired response format should be <see cref="Email"/>, which contains additional <see cref="Email.Category"/> property.
|
||||
/// This shows how the data can be transformed with AI using strong types without additional instructions in the prompt.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task StructuredOutputsWithFunctionCallingAzureOpenAIAsync()
|
||||
{
|
||||
// Initialize kernel.
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddAzureOpenAIChatCompletion(
|
||||
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
|
||||
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
|
||||
credentials: new AzureCliCredential())
|
||||
.Build();
|
||||
|
||||
kernel.ImportPluginFromType<EmailPlugin>();
|
||||
|
||||
// Specify response format by setting Type object in prompt execution settings and enable automatic function calling.
|
||||
var executionSettings = new AzureOpenAIPromptExecutionSettings
|
||||
{
|
||||
ResponseFormat = typeof(EmailResult),
|
||||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
|
||||
};
|
||||
|
||||
// Send a request and pass prompt execution settings with desired response format.
|
||||
var result = await kernel.InvokePromptAsync("Process the emails.", new(executionSettings));
|
||||
|
||||
// Deserialize string response to a strong type to access type properties.
|
||||
// At this point, the deserialization logic won't fail, because EmailResult type was specified as desired response format.
|
||||
// This ensures that response string is a serialized version of EmailResult type.
|
||||
var emailResult = JsonSerializer.Deserialize<EmailResult>(result.ToString())!;
|
||||
|
||||
// Output the result.
|
||||
this.OutputResult(emailResult);
|
||||
|
||||
// Output:
|
||||
|
||||
// Email #1
|
||||
// Body: Let's catch up over coffee this Saturday. It's been too long!
|
||||
// Category: Social
|
||||
|
||||
// Email #2
|
||||
// Body: Please review the attached document and provide your feedback by EOD.
|
||||
// Category: Work
|
||||
|
||||
// ...and more...
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method shows how to enable Structured Outputs feature with Azure OpenAI chat completion service.
|
||||
/// Model should be gpt-4o with version 2024-08-06 or later.
|
||||
/// Azure OpenAI chat completion API version should be 2024-08-01-preview or later.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task StructuredOutputsWithAzureOpenAIAsync()
|
||||
{
|
||||
// Initialize kernel.
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddAzureOpenAIChatCompletion(
|
||||
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
|
||||
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
|
||||
credentials: new AzureCliCredential())
|
||||
.Build();
|
||||
|
||||
// Specify response format by setting Type object in prompt execution settings.
|
||||
var executionSettings = new AzureOpenAIPromptExecutionSettings
|
||||
{
|
||||
ResponseFormat = typeof(MovieResult)
|
||||
};
|
||||
|
||||
// Send a request and pass prompt execution settings with desired response format.
|
||||
var result = await kernel.InvokePromptAsync("What are the top 10 movies of all time?", new(executionSettings));
|
||||
|
||||
// Deserialize string response to a strong type to access type properties.
|
||||
// At this point, the deserialization logic won't fail, because MovieResult type was specified as desired response format.
|
||||
// This ensures that response string is a serialized version of MovieResult type.
|
||||
var movieResult = JsonSerializer.Deserialize<MovieResult>(result.ToString())!;
|
||||
|
||||
// Output the result.
|
||||
this.OutputResult(movieResult);
|
||||
|
||||
// Output:
|
||||
|
||||
// Title: The Lord of the Rings: The Fellowship of the Ring
|
||||
// Director: Peter Jackson
|
||||
// Release year: 2001
|
||||
// Rating: 8.8
|
||||
// Is available on streaming: True
|
||||
// Tags: Adventure,Drama,Fantasy
|
||||
|
||||
// ...and more...
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method shows how to enable Structured Outputs feature with Semantic Kernel functions from prompt
|
||||
/// using Semantic Kernel template engine.
|
||||
/// In this scenario, JSON Schema for response is specified in a prompt configuration file.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task StructuredOutputsWithFunctionsFromPromptAsync()
|
||||
{
|
||||
// Initialize kernel.
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion(
|
||||
modelId: "gpt-4o-2024-08-06",
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
// Initialize a path to plugin directory: Resources/Plugins/MoviePlugins/MoviePluginPrompt.
|
||||
var pluginDirectoryPath = Path.Combine(Directory.GetCurrentDirectory(), "Resources", "Plugins", "MoviePlugins", "MoviePluginPrompt");
|
||||
|
||||
// Create a function from prompt.
|
||||
kernel.ImportPluginFromPromptDirectory(pluginDirectoryPath, pluginName: "MoviePlugin");
|
||||
|
||||
var result = await kernel.InvokeAsync("MoviePlugin", "TopMovies");
|
||||
|
||||
// Deserialize string response to a strong type to access type properties.
|
||||
// At this point, the deserialization logic won't fail, because MovieResult type was specified as desired response format.
|
||||
// This ensures that response string is a serialized version of MovieResult type.
|
||||
var movieResult = JsonSerializer.Deserialize<MovieResult>(result.ToString())!;
|
||||
|
||||
// Output the result.
|
||||
this.OutputResult(movieResult);
|
||||
|
||||
// Output:
|
||||
|
||||
// Title: The Lord of the Rings: The Fellowship of the Ring
|
||||
// Director: Peter Jackson
|
||||
// Release year: 2001
|
||||
// Rating: 8.8
|
||||
// Is available on streaming: True
|
||||
// Tags: Adventure,Drama,Fantasy
|
||||
|
||||
// ...and more...
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method shows how to enable Structured Outputs feature with Semantic Kernel functions from YAML
|
||||
/// using Semantic Kernel template engine.
|
||||
/// In this scenario, JSON Schema for response is specified in YAML prompt file.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task StructuredOutputsWithFunctionsFromYamlAsync()
|
||||
{
|
||||
// Initialize kernel.
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion(
|
||||
modelId: "gpt-4o-2024-08-06",
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
// Initialize a path to YAML function: Resources/Plugins/MoviePlugins/MoviePluginYaml.
|
||||
var functionPath = Path.Combine(Directory.GetCurrentDirectory(), "Resources", "Plugins", "MoviePlugins", "MoviePluginYaml", "TopMovies.yaml");
|
||||
|
||||
// Load YAML prompt.
|
||||
var topMoviesYaml = File.ReadAllText(functionPath);
|
||||
|
||||
// Import a function from YAML.
|
||||
var function = kernel.CreateFunctionFromPromptYaml(topMoviesYaml);
|
||||
kernel.ImportPluginFromFunctions("MoviePlugin", [function]);
|
||||
|
||||
var result = await kernel.InvokeAsync("MoviePlugin", "TopMovies");
|
||||
|
||||
// Deserialize string response to a strong type to access type properties.
|
||||
// At this point, the deserialization logic won't fail, because MovieResult type was specified as desired response format.
|
||||
// This ensures that response string is a serialized version of MovieResult type.
|
||||
var movieResult = JsonSerializer.Deserialize<MovieResult>(result.ToString())!;
|
||||
|
||||
// Output the result.
|
||||
this.OutputResult(movieResult);
|
||||
|
||||
// Output:
|
||||
|
||||
// Title: The Lord of the Rings: The Fellowship of the Ring
|
||||
// Director: Peter Jackson
|
||||
// Release year: 2001
|
||||
// Rating: 8.8
|
||||
// Is available on streaming: True
|
||||
// Tags: Adventure,Drama,Fantasy
|
||||
|
||||
// ...and more...
|
||||
}
|
||||
|
||||
#region private
|
||||
|
||||
/// <summary>Movie result struct that will be used as desired chat completion response format (structured output).</summary>
|
||||
private struct MovieResult
|
||||
{
|
||||
public List<Movie> Movies { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>Movie struct that will be used as desired chat completion response format (structured output).</summary>
|
||||
private struct Movie
|
||||
{
|
||||
public string Title { get; set; }
|
||||
|
||||
public string Director { get; set; }
|
||||
|
||||
public int ReleaseYear { get; set; }
|
||||
|
||||
public double Rating { get; set; }
|
||||
|
||||
public bool IsAvailableOnStreaming { get; set; }
|
||||
|
||||
public List<string> Tags { get; set; }
|
||||
}
|
||||
|
||||
private sealed class EmailResult
|
||||
{
|
||||
public List<Email> Emails { get; set; }
|
||||
}
|
||||
|
||||
private sealed class Email
|
||||
{
|
||||
public string Body { get; set; }
|
||||
|
||||
public string Category { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>Plugin to simulate RAG scenario and return collection of data.</summary>
|
||||
private sealed class EmailPlugin
|
||||
{
|
||||
/// <summary>Function to simulate RAG scenario and return collection of data.</summary>
|
||||
[KernelFunction]
|
||||
private List<string> GetEmails()
|
||||
{
|
||||
return
|
||||
[
|
||||
"Hey, just checking in to see how you're doing!",
|
||||
"Can you pick up some groceries on your way back home? We need milk and bread.",
|
||||
"Happy Birthday! Wishing you a fantastic day filled with love and joy.",
|
||||
"Let's catch up over coffee this Saturday. It's been too long!",
|
||||
"Please review the attached document and provide your feedback by EOD.",
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Helper method to output <see cref="MovieResult"/> object content.</summary>
|
||||
private void OutputResult(MovieResult movieResult)
|
||||
{
|
||||
for (var i = 0; i < movieResult.Movies.Count; i++)
|
||||
{
|
||||
var movie = movieResult.Movies[i];
|
||||
|
||||
this.Output.WriteLine($"Movie #{i + 1}");
|
||||
this.Output.WriteLine($"Title: {movie.Title}");
|
||||
this.Output.WriteLine($"Director: {movie.Director}");
|
||||
this.Output.WriteLine($"Release year: {movie.ReleaseYear}");
|
||||
this.Output.WriteLine($"Rating: {movie.Rating}");
|
||||
this.Output.WriteLine($"Is available on streaming: {movie.IsAvailableOnStreaming}");
|
||||
this.Output.WriteLine($"Tags: {string.Join(",", movie.Tags)}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Helper method to output <see cref="EmailResult"/> object content.</summary>
|
||||
private void OutputResult(EmailResult emailResult)
|
||||
{
|
||||
for (var i = 0; i < emailResult.Emails.Count; i++)
|
||||
{
|
||||
var email = emailResult.Emails[i];
|
||||
|
||||
this.Output.WriteLine($"Email #{i + 1}");
|
||||
this.Output.WriteLine($"Body: {email.Body}");
|
||||
this.Output.WriteLine($"Category: {email.Category}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
|
||||
namespace ChatCompletion;
|
||||
|
||||
/**
|
||||
* Logit_bias is an optional parameter that modifies the likelihood of specified tokens appearing in a Completion.
|
||||
* When using the Token Selection Biases parameter, the bias is added to the logits generated by the model prior to sampling.
|
||||
*/
|
||||
public class OpenAI_UsingLogitBias(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task RunAsync()
|
||||
{
|
||||
OpenAIChatCompletionService chatCompletionService = new(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
// To use Logit Bias you will need to know the token ids of the words you want to use.
|
||||
// Getting the token ids using the GPT Tokenizer: https://platform.openai.com/tokenizer
|
||||
|
||||
// The following text is the tokenized version of the book related tokens
|
||||
// "novel literature reading author library story chapter paperback hardcover ebook publishing fiction nonfiction manuscript textbook bestseller bookstore reading list bookworm"
|
||||
int[] keys = [3919, 626, 17201, 1300, 25782, 9800, 32016, 13571, 43582, 20189, 1891, 10424, 9631, 16497, 12984, 20020, 24046, 13159, 805, 15817, 5239, 2070, 13466, 32932, 8095, 1351, 25323];
|
||||
|
||||
var settings = new OpenAIPromptExecutionSettings
|
||||
{
|
||||
// This will make the model try its best to avoid any of the above related words.
|
||||
//-100 to potentially ban all the tokens from the list.
|
||||
TokenSelectionBiases = keys.ToDictionary(key => key, key => -100)
|
||||
};
|
||||
|
||||
Console.WriteLine("Chat content:");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
var chatHistory = new ChatHistory("You are a librarian expert");
|
||||
|
||||
// First user message
|
||||
chatHistory.AddUserMessage("Hi, I'm looking some suggestions");
|
||||
await MessageOutputAsync(chatHistory);
|
||||
|
||||
var replyMessage = await chatCompletionService.GetChatMessageContentAsync(chatHistory, settings);
|
||||
chatHistory.AddAssistantMessage(replyMessage.Content!);
|
||||
await MessageOutputAsync(chatHistory);
|
||||
|
||||
chatHistory.AddUserMessage("I love history and philosophy, I'd like to learn something new about Greece, any suggestion");
|
||||
await MessageOutputAsync(chatHistory);
|
||||
|
||||
replyMessage = await chatCompletionService.GetChatMessageContentAsync(chatHistory, settings);
|
||||
chatHistory.AddAssistantMessage(replyMessage.Content!);
|
||||
await MessageOutputAsync(chatHistory);
|
||||
|
||||
/* Output:
|
||||
Chat content:
|
||||
------------------------
|
||||
User: Hi, I'm looking some suggestions
|
||||
------------------------
|
||||
Assistant: Sure, what kind of suggestions are you looking for?
|
||||
------------------------
|
||||
User: I love history and philosophy, I'd like to learn something new about Greece, any suggestion?
|
||||
------------------------
|
||||
Assistant: If you're interested in learning about ancient Greece, I would recommend the book "The Histories" by Herodotus. It's a fascinating account of the Persian Wars and provides a lot of insight into ancient Greek culture and society. For philosophy, you might enjoy reading the works of Plato, particularly "The Republic" and "The Symposium." These texts explore ideas about justice, morality, and the nature of love.
|
||||
------------------------
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs the last message of the chat history
|
||||
/// </summary>
|
||||
private Task MessageOutputAsync(ChatHistory chatHistory)
|
||||
{
|
||||
var message = chatHistory.Last();
|
||||
|
||||
Console.WriteLine($"{message.Role}: {message.Content}");
|
||||
Console.WriteLine("------------------------");
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user