diff --git a/dotnet/samples/Concepts/ChatCompletion/Anthropic_ChatCompletion.cs b/dotnet/samples/Concepts/ChatCompletion/Anthropic_ChatCompletion.cs new file mode 100644 index 000000000000..c3bf9a0a19d8 --- /dev/null +++ b/dotnet/samples/Concepts/ChatCompletion/Anthropic_ChatCompletion.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; + +namespace ChatCompletion; + +public sealed class Anthropic_ChatCompletion(ITestOutputHelper output) : BaseTest(output) +{ + [Fact] + public async Task SampleAsync() + { + Console.WriteLine("============= Anthropic - Claude Chat Completion ============="); + + string apiKey = TestConfiguration.AnthropicAI.ApiKey; + string modelId = TestConfiguration.AnthropicAI.ModelId; + + Assert.NotNull(apiKey); + Assert.NotNull(modelId); + + Kernel kernel = Kernel.CreateBuilder() + .AddAnthropicChatCompletion( + modelId: modelId, + apiKey: apiKey) + .Build(); + + await SimpleChatAsync(kernel); + } + + private async Task SimpleChatAsync(Kernel kernel) + { + Console.WriteLine("======== Simple Chat ========"); + + var chatHistory = new ChatHistory("You are an expert in the tool shop."); + var chat = kernel.GetRequiredService(); + + // First user message + chatHistory.AddUserMessage("Hi, I'm looking for new power tools, any suggestion?"); + await MessageOutputAsync(chatHistory); + + // First bot 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 bot assistant message + reply = await chat.GetChatMessageContentAsync(chatHistory); + chatHistory.Add(reply); + await MessageOutputAsync(chatHistory); + } + + /// + /// Outputs the last message of the chat history + /// + private Task MessageOutputAsync(ChatHistory chatHistory) + { + var message = chatHistory.Last(); + + Console.WriteLine($"{message.Role}: {message.Content}"); + Console.WriteLine("------------------------"); + + return Task.CompletedTask; + } +} diff --git a/dotnet/samples/Concepts/ChatCompletion/Anthropic_ChatCompletionStreaming.cs b/dotnet/samples/Concepts/ChatCompletion/Anthropic_ChatCompletionStreaming.cs new file mode 100644 index 000000000000..471107d8281b --- /dev/null +++ b/dotnet/samples/Concepts/ChatCompletion/Anthropic_ChatCompletionStreaming.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Text; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; + +namespace ChatCompletion; + +public sealed class Anthropic_ChatCompletionStreaming(ITestOutputHelper output) : BaseTest(output) +{ + [Fact] + public async Task SampleAsync() + { + Console.WriteLine("============= Anthropic - Claude Chat Streaming ============="); + + string apiKey = TestConfiguration.AnthropicAI.ApiKey; + string modelId = TestConfiguration.AnthropicAI.ModelId; + + Assert.NotNull(apiKey); + Assert.NotNull(modelId); + + Kernel kernel = Kernel.CreateBuilder() + .AddAnthropicChatCompletion( + modelId: modelId, + apiKey: apiKey) + .Build(); + + await this.StreamingChatAsync(kernel); + } + + private async Task StreamingChatAsync(Kernel kernel) + { + Console.WriteLine("======== Streaming Chat ========"); + + var chatHistory = new ChatHistory("You are an expert in the tool shop."); + var chat = kernel.GetRequiredService(); + + // First user message + chatHistory.AddUserMessage("Hi, I'm looking for alternative coffee brew methods, can you help me?"); + await MessageOutputAsync(chatHistory); + + // First bot 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 bot assistant message + streamingChat = chat.GetStreamingChatMessageContentsAsync(chatHistory); + reply = await MessageOutputAsync(streamingChat); + chatHistory.Add(reply); + } + + /// + /// Outputs the last message of the chat history + /// + private Task MessageOutputAsync(ChatHistory chatHistory) + { + var message = chatHistory.Last(); + + Console.WriteLine($"{message.Role}: {message.Content}"); + Console.WriteLine("------------------------"); + + return Task.CompletedTask; + } + + private async Task MessageOutputAsync(IAsyncEnumerable 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()); + } +} diff --git a/dotnet/samples/Concepts/ChatCompletion/Anthropic_ProvidersSetup.cs b/dotnet/samples/Concepts/ChatCompletion/Anthropic_ProvidersSetup.cs new file mode 100644 index 000000000000..753d7af61c79 --- /dev/null +++ b/dotnet/samples/Concepts/ChatCompletion/Anthropic_ProvidersSetup.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.SemanticKernel; + +namespace ChatCompletion; + +/// +/// This sample shows how to setup different providers for anthropic. +/// +public sealed class Anthropic_ProvidersSetup(ITestOutputHelper output) : BaseTest(output) +{ + public void AnthropicProvider() + { + var kernel = Kernel.CreateBuilder() + .AddAnthropicChatCompletion( + modelId: "modelId", + apiKey: "apiKey") + .Build(); + } + + /// + /// For more information on how to setup the Vertex AI provider, go to sample. + /// + public void VertexAiProvider() + { + var kernel = Kernel.CreateBuilder() + .AddAnthropicVertextAIChatCompletion( + modelId: "modelId", + bearerTokenProvider: () => ValueTask.FromResult("bearer"), + endpoint: new Uri("https://your-endpoint")) + .Build(); + } +} diff --git a/dotnet/samples/Concepts/ChatCompletion/Anthropic_Vision.cs b/dotnet/samples/Concepts/ChatCompletion/Anthropic_Vision.cs new file mode 100644 index 000000000000..324992ed17df --- /dev/null +++ b/dotnet/samples/Concepts/ChatCompletion/Anthropic_Vision.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; +using Resources; + +namespace ChatCompletion; + +public sealed class Anthropic_Vision(ITestOutputHelper output) : BaseTest(output) +{ + [Fact] + public async Task SampleAsync() + { + Console.WriteLine("============= Anthropic - Claude Chat Completion ============="); + + string apiKey = TestConfiguration.AnthropicAI.ApiKey; + string modelId = TestConfiguration.AnthropicAI.ModelId; + + Assert.NotNull(apiKey); + Assert.NotNull(modelId); + + Kernel kernel = Kernel.CreateBuilder() + .AddAnthropicChatCompletion( + modelId: modelId, + apiKey: apiKey) + .Build(); + + var chatHistory = new ChatHistory("Your job is describing images."); + var chatCompletionService = kernel.GetRequiredService(); + + // 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 { { "mimeType", "image/jpeg" } }) + ]); + + var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory); + + Console.WriteLine(reply.Content); + } +} diff --git a/dotnet/samples/Concepts/Concepts.csproj b/dotnet/samples/Concepts/Concepts.csproj index 25724b822243..eacb90f66d35 100644 --- a/dotnet/samples/Concepts/Concepts.csproj +++ b/dotnet/samples/Concepts/Concepts.csproj @@ -55,6 +55,7 @@ + diff --git a/dotnet/samples/Concepts/README.md b/dotnet/samples/Concepts/README.md index cbbcc2539288..05876bd0dc5e 100644 --- a/dotnet/samples/Concepts/README.md +++ b/dotnet/samples/Concepts/README.md @@ -59,6 +59,10 @@ dotnet test -l "console;verbosity=detailed" --filter "FullyQualifiedName=ChatCom - [Google_GeminiChatCompletionStreaming](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/Google_GeminiChatCompletionStreaming.cs) - [Google_GeminiGetModelResult](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/Google_GeminiGetModelResult.cs) - [Google_GeminiVision](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/Google_GeminiVision.cs) +- [Anthropic_ChatCompletion](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/Anthropic_ChatCompletion.cs) +- [Anthropic_ChatCompletionStreaming](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/Anthropic_ChatCompletionStreaming.cs) +- [Anthropic_Vision](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/Anthropic_Vision.cs) +- [Anthropic_ProvidersSetup](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/Anthropic_ProvidersSetup.cs) - [OpenAI_ChatCompletion](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/OpenAI_ChatCompletion.cs) - [OpenAI_ChatCompletionMultipleChoices](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/OpenAI_ChatCompletionMultipleChoices.cs) - [OpenAI_ChatCompletionStreaming](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/OpenAI_ChatCompletionStreaming.cs) diff --git a/dotnet/samples/Demos/AIModelRouter/AIModelRouter.csproj b/dotnet/samples/Demos/AIModelRouter/AIModelRouter.csproj index 4ce04e354cc8..e53aa55a2a79 100644 --- a/dotnet/samples/Demos/AIModelRouter/AIModelRouter.csproj +++ b/dotnet/samples/Demos/AIModelRouter/AIModelRouter.csproj @@ -15,6 +15,7 @@ + diff --git a/dotnet/samples/Demos/AIModelRouter/Program.cs b/dotnet/samples/Demos/AIModelRouter/Program.cs index 9d3631dbcb90..612c13b251c3 100644 --- a/dotnet/samples/Demos/AIModelRouter/Program.cs +++ b/dotnet/samples/Demos/AIModelRouter/Program.cs @@ -74,6 +74,16 @@ private static async Task Main(string[] args) Console.WriteLine("• Azure AI Inference Added - Use \"azureai\" in the prompt."); } + if (config["Anthropic:ApiKey"] is not null) + { + services.AddAnthropicChatCompletion( + serviceId: "anthropic", + modelId: config["Anthropic:ModelId"] ?? "claude-3-5-sonnet-20240620", + apiKey: config["Anthropic:ApiKey"]!); + + Console.WriteLine("• Anthropic Added - Use \"anthropic\" in the prompt."); + } + // Adding a custom filter to capture router selected service id services.AddSingleton(new SelectedServiceFilter()); @@ -92,7 +102,7 @@ private static async Task Main(string[] args) // Find the best service to use based on the user's input KernelArguments arguments = new(new PromptExecutionSettings() { - ServiceId = router.FindService(userMessage, ["lmstudio", "ollama", "openai", "onnx", "azureai"]) + ServiceId = router.FindService(userMessage, ["lmstudio", "ollama", "openai", "onnx", "azureai", "anthropic"]) }); // Invoke the prompt and print the response diff --git a/dotnet/samples/Demos/AIModelRouter/README.md b/dotnet/samples/Demos/AIModelRouter/README.md index afb061ced3c2..7d0269977a3a 100644 --- a/dotnet/samples/Demos/AIModelRouter/README.md +++ b/dotnet/samples/Demos/AIModelRouter/README.md @@ -22,11 +22,17 @@ The sample can be configured by using the command line with .NET [Secret Manager ```powershell dotnet user-secrets set "OpenAI:ApiKey" "... your api key ... " -dotnet user-secrets set "OpenAI:ModelId" ".. Openai model .. " (default: gpt-4o) +dotnet user-secrets set "OpenAI:ModelId" ".. Openai model id .. " (default: gpt-4o) + +dotnet user-secrets set "Anthropic:ApiKey" "... your api key ... " +dotnet user-secrets set "Anthropic:ModelId" "... Anthropic model id .. " (default: claude-3-5-sonnet-20240620) + dotnet user-secrets set "Ollama:ModelId" ".. Ollama model id .. " dotnet user-secrets set "Ollama:Endpoint" ".. Ollama endpoint .. " (default: http://localhost:11434) + dotnet user-secrets set "LMStudio:Endpoint" ".. LM Studio endpoint .. " (default: http://localhost:1234) -dotnet user-secrets set "Onnx:ModelId" ".. Onnx model id" + +dotnet user-secrets set "Onnx:ModelId" ".. Onnx model id .. " dotnet user-secrets set "Onnx:ModelPath" ".. your Onnx model folder path .." ``` @@ -53,4 +59,16 @@ dotnet run > **User** > LMStudio, what is Jupiter? Keep it simple. -> **Assistant** > Jupiter is the fifth planet from the Sun in our Solar System and one of its gas giants alongside Saturn, Uranus, and Neptune. It's famous for having a massive storm called the Great Red Spot that has been raging for hundreds of years. \ No newline at end of file +> **Assistant** > Jupiter is the fifth planet from the Sun in our Solar System and one of its gas giants alongside Saturn, Uranus, and Neptune. It's famous for having a massive storm called the Great Red Spot that has been raging for hundreds of years. + +> **User** > AzureAI, what is Jupiter? Keep it simple. + +> **Assistant** > Jupiter is the fifth planet from the Sun in our Solar System and one of its gas giants alongside Saturn, Uranus, and Neptune. It's famous for having a massive storm called the Great Red Spot that has been raging for hundreds of years. + +> **User** > Anthropic, what is Jupiter? Keep it simple. + +> **Assistant** > Jupiter is the fifth planet from the Sun in our Solar System and one of its gas giants alongside Saturn, Uranus, and Neptune. It's famous for having a massive storm called the Great Red Spot that has been raging for hundreds of years. + +> **User** > ONNX, what is Jupiter? Keep it simple. + +> **Assistant** > Jupiter is the fifth planet from the Sun in our Solar System and one of its gas giants alongside Saturn, Uranus, and Neptune. It's famous for having a massive storm called the Great Red Spot that has been raging for hundreds of years. diff --git a/dotnet/src/InternalUtilities/samples/InternalUtilities/TestConfiguration.cs b/dotnet/src/InternalUtilities/samples/InternalUtilities/TestConfiguration.cs index 01b60b08c9cb..07bdaf313ef2 100644 --- a/dotnet/src/InternalUtilities/samples/InternalUtilities/TestConfiguration.cs +++ b/dotnet/src/InternalUtilities/samples/InternalUtilities/TestConfiguration.cs @@ -44,6 +44,7 @@ public static void Initialize(IConfigurationRoot configRoot) public static ChatGPTRetrievalPluginConfig ChatGPTRetrievalPlugin => LoadSection(); public static MsGraphConfiguration MSGraph => LoadSection(); public static MistralAIConfig MistralAI => LoadSection(); + public static AnthropicAIConfig AnthropicAI => LoadSection(); public static GoogleAIConfig GoogleAI => LoadSection(); public static VertexAIConfig VertexAI => LoadSection(); public static AzureCosmosDbMongoDbConfig AzureCosmosDbMongoDb => LoadSection(); @@ -213,6 +214,12 @@ public class MistralAIConfig public string EmbeddingModelId { get; set; } } + public class AnthropicAIConfig + { + public string ApiKey { get; set; } + public string ModelId { get; set; } + } + public class GoogleAIConfig { public string ApiKey { get; set; }