Merge branch 'main' into update-modelid

This commit is contained in:
John Oliver
2024-01-04 12:46:38 +00:00
committed by GitHub
76 changed files with 1800 additions and 1699 deletions
@@ -38,9 +38,9 @@ public static class Example30_ChatWithPrompts
Console.WriteLine("======== Chat with prompts ========");
/* Load 3 files:
* - 28-system-prompt.txt: the system prompt, used to initialize the chat session.
* - 28-user-context.txt: the user context, e.g. a piece of a document the user selected and is asking to process.
* - 28-user-prompt.txt: the user prompt, just for demo purpose showing that one can leverage the same approach also to augment user messages.
* - 30-system-prompt.txt: the system prompt, used to initialize the chat session.
* - 30-user-context.txt: the user context, e.g. a piece of a document the user selected and is asking to process.
* - 30-user-prompt.txt: the user prompt, just for demo purpose showing that one can leverage the same approach also to augment user messages.
*/
var systemPromptTemplate = EmbeddedResource.Read("30-system-prompt.txt");
@@ -58,13 +58,13 @@ public static class Example30_ChatWithPrompts
// Adding required arguments referenced by the prompt templates.
var arguments = new KernelArguments
{
// Put the selected document into the variable used by the system prompt (see 28-system-prompt.txt).
// Put the selected document into the variable used by the system prompt (see 30-system-prompt.txt).
["selectedText"] = selectedText,
// Demo another variable, e.g. when the chat started, used by the system prompt (see 28-system-prompt.txt).
// Demo another variable, e.g. when the chat started, used by the system prompt (see 30-system-prompt.txt).
["startTime"] = DateTimeOffset.Now.ToString("hh:mm:ss tt zz", CultureInfo.CurrentCulture),
// This is the user message, store it in the variable used by 28-user-prompt.txt
// This is the user message, store it in the variable used by 30-user-prompt.txt
["userMessage"] = "extract locations as a bullet point list"
};
@@ -20,7 +20,11 @@ public static class Example59_OpenAIFunctionCalling
{
// Create kernel.
IKernelBuilder builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);
// We recommend the usage of OpenAI latest models for the best experience with tool calling.
// i.e. gpt-3.5-turbo-1106 or gpt-4-1106-preview
builder.AddOpenAIChatCompletion("gpt-3.5-turbo-1106", TestConfiguration.OpenAI.ApiKey);
builder.Services.AddLogging(services => services.AddConsole().SetMinimumLevel(LogLevel.Trace));
Kernel kernel = builder.Build();
@@ -39,7 +43,7 @@ public static class Example59_OpenAIFunctionCalling
"Sydney" => "75 and sunny",
"Tel Aviv" => "80 and sunny",
_ => "31 and snowing",
}, "GetWeatherForCity", "Gets the current weather for the specified city"),
}, "Get_Weather_For_City", "Gets the current weather for the specified city"),
}));
Console.WriteLine("======== Example 1: Use automated function calling with a non-streaming prompt ========");
@@ -106,9 +110,9 @@ public static class Example59_OpenAIFunctionCalling
while (true)
{
Console.Write("Question: ");
Console.Write("Question (Type \"quit\" to leave): ");
string question = Console.ReadLine() ?? string.Empty;
if (question == "done")
if (question == "quit")
{
break;
}
@@ -0,0 +1,39 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
#pragma warning disable CA1812 // Uninstantiated internal types
namespace SemanticKernel.IntegrationTests.Fakes;
internal sealed class ThrowingEmailPluginFake
{
[KernelFunction, Description("Given an email address and message body, send an email")]
public Task<string> SendEmailAsync(
[Description("The body of the email message to send.")] string input = "",
[Description("The email address to send email to.")] string? email_address = "[email protected]")
{
// Throw a non-critical exception for testing
throw new ArgumentException($"Failed to send email to {email_address}");
}
[KernelFunction, Description("Lookup an email address for a person given a name")]
public Task<string> GetEmailAddressAsync(
ILogger logger,
[Description("The name of the person to email.")] string? input = null)
{
// Throw a critical exception for testing
throw new InvalidProgramException();
}
[KernelFunction, Description("Write a short poem for an e-mail")]
public Task<string> WritePoemAsync(
[Description("The topic of the poem.")] string input)
{
return Task.FromResult($"Roses are red, violets are blue, {input} is hard, so is this test.");
}
}
@@ -72,6 +72,42 @@ public sealed class FunctionCallingStepwisePlannerTests : IDisposable
}
}
[Fact]
public async Task DoesNotThrowWhenPluginFunctionThrowsNonCriticalExceptionAsync()
{
Kernel kernel = this.InitializeKernel();
kernel.ImportPluginFromType<ThrowingEmailPluginFake>("Email");
var planner = new FunctionCallingStepwisePlanner(
new FunctionCallingStepwisePlannerConfig() { MaxIterations = 5 });
// Act
var planResult = await planner.ExecuteAsync(kernel, "Email a poem about cats to [email protected]");
// Assert - should contain the expected answer & function calls within the maximum iterations
Assert.NotNull(planResult);
Assert.True(planResult.Iterations > 0);
Assert.True(planResult.Iterations <= 5);
string serializedChatHistory = JsonSerializer.Serialize(planResult.ChatHistory);
Assert.Contains("Email_WritePoem", serializedChatHistory, StringComparison.InvariantCultureIgnoreCase);
Assert.Contains("Email_SendEmail", serializedChatHistory, StringComparison.InvariantCultureIgnoreCase);
}
[Fact]
public async Task ThrowsWhenPluginFunctionThrowsCriticalExceptionAsync()
{
Kernel kernel = this.InitializeKernel();
kernel.ImportPluginFromType<ThrowingEmailPluginFake>("Email");
var planner = new FunctionCallingStepwisePlanner(
new FunctionCallingStepwisePlannerConfig() { MaxIterations = 5 });
// Act & Assert
// Planner should call ThrowingEmailPluginFake.GetEmailAddressAsync, which throws InvalidProgramException
await Assert.ThrowsAsync<InvalidProgramException>(async () => await planner.ExecuteAsync(kernel, "What is Kelly's email address?"));
}
private Kernel InitializeKernel()
{
OpenAIConfiguration? openAIConfiguration = this._configuration.GetSection("Planners:OpenAI").Get<OpenAIConfiguration>();
@@ -1,20 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics.CodeAnalysis;
namespace SemanticKernel.IntegrationTests.TestSettings;
[SuppressMessage("Performance", "CA1812:Internal class that is apparently never instantiated",
Justification = "Configuration classes are instantiated through IConfiguration.")]
internal sealed class PlannerConfiguration
{
public AzureOpenAIConfiguration? AzureOpenAI { get; set; }
public OpenAIConfiguration? OpenAI { get; set; }
public PlannerConfiguration(AzureOpenAIConfiguration? azureOpenAIConfiguration, OpenAIConfiguration? openAIConfiguration)
{
this.AzureOpenAI = azureOpenAIConfiguration;
this.OpenAI = openAIConfiguration;
}
}
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
@@ -135,8 +136,9 @@ public sealed class FunctionCallingStepwisePlanner
var result = (await clonedKernel.InvokeAsync(pluginFunction, arguments, cancellationToken).ConfigureAwait(false)).GetValue<object>();
chatHistoryForSteps.AddMessage(AuthorRole.Tool, ParseObjectAsString(result), metadata: new Dictionary<string, object?>(1) { { OpenAIChatMessageContent.ToolIdProperty, functionResponse.Id } });
}
catch (KernelException)
catch (Exception ex) when (!ex.IsCriticalException())
{
chatHistoryForSteps.AddMessage(AuthorRole.Tool, ex.Message, metadata: new Dictionary<string, object?>(1) { { OpenAIChatMessageContent.ToolIdProperty, functionResponse.Id } });
chatHistoryForSteps.AddUserMessage($"Failed to execute function {functionResponse.FullyQualifiedName}. Try something else!");
}
}
+8 -2
View File
@@ -1,7 +1,12 @@
[run]
source = semantic_kernel
omit =
semantic_kernel/connectors/*
semantic_kernel/connectors/memory/*
semantic_kernel/connectors/openapi/*
semantic_kernel/connectors/search_engine/*
semantic_kernel/connectors/ai/google_palm/*
semantic_kernel/connectors/ai/hugging_face/*
[report]
# Regexes for lines to exclude from consideration
@@ -18,4 +23,5 @@ exclude_lines =
# TYPE_CHECKING and @overload blocks are never executed during pytest run
if TYPE_CHECKING:
@overload
@overload
@abstractmethod
+91 -4
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
[[package]]
name = "aiofiles"
@@ -946,6 +946,73 @@ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.1
[package.extras]
toml = ["tomli"]
[[package]]
name = "coverage"
version = "7.3.3"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "coverage-7.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d874434e0cb7b90f7af2b6e3309b0733cde8ec1476eb47db148ed7deeb2a9494"},
{file = "coverage-7.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee6621dccce8af666b8c4651f9f43467bfbf409607c604b840b78f4ff3619aeb"},
{file = "coverage-7.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1367aa411afb4431ab58fd7ee102adb2665894d047c490649e86219327183134"},
{file = "coverage-7.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f0f8f0c497eb9c9f18f21de0750c8d8b4b9c7000b43996a094290b59d0e7523"},
{file = "coverage-7.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db0338c4b0951d93d547e0ff8d8ea340fecf5885f5b00b23be5aa99549e14cfd"},
{file = "coverage-7.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d31650d313bd90d027f4be7663dfa2241079edd780b56ac416b56eebe0a21aab"},
{file = "coverage-7.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9437a4074b43c177c92c96d051957592afd85ba00d3e92002c8ef45ee75df438"},
{file = "coverage-7.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9e17d9cb06c13b4f2ef570355fa45797d10f19ca71395910b249e3f77942a837"},
{file = "coverage-7.3.3-cp310-cp310-win32.whl", hash = "sha256:eee5e741b43ea1b49d98ab6e40f7e299e97715af2488d1c77a90de4a663a86e2"},
{file = "coverage-7.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:593efa42160c15c59ee9b66c5f27a453ed3968718e6e58431cdfb2d50d5ad284"},
{file = "coverage-7.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8c944cf1775235c0857829c275c777a2c3e33032e544bcef614036f337ac37bb"},
{file = "coverage-7.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eda7f6e92358ac9e1717ce1f0377ed2b9320cea070906ece4e5c11d172a45a39"},
{file = "coverage-7.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c854c1d2c7d3e47f7120b560d1a30c1ca221e207439608d27bc4d08fd4aeae8"},
{file = "coverage-7.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:222b038f08a7ebed1e4e78ccf3c09a1ca4ac3da16de983e66520973443b546bc"},
{file = "coverage-7.3.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff4800783d85bff132f2cc7d007426ec698cdce08c3062c8d501ad3f4ea3d16c"},
{file = "coverage-7.3.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fc200cec654311ca2c3f5ab3ce2220521b3d4732f68e1b1e79bef8fcfc1f2b97"},
{file = "coverage-7.3.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:307aecb65bb77cbfebf2eb6e12009e9034d050c6c69d8a5f3f737b329f4f15fb"},
{file = "coverage-7.3.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ffb0eacbadb705c0a6969b0adf468f126b064f3362411df95f6d4f31c40d31c1"},
{file = "coverage-7.3.3-cp311-cp311-win32.whl", hash = "sha256:79c32f875fd7c0ed8d642b221cf81feba98183d2ff14d1f37a1bbce6b0347d9f"},
{file = "coverage-7.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:243576944f7c1a1205e5cd658533a50eba662c74f9be4c050d51c69bd4532936"},
{file = "coverage-7.3.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a2ac4245f18057dfec3b0074c4eb366953bca6787f1ec397c004c78176a23d56"},
{file = "coverage-7.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f9191be7af41f0b54324ded600e8ddbcabea23e1e8ba419d9a53b241dece821d"},
{file = "coverage-7.3.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31c0b1b8b5a4aebf8fcd227237fc4263aa7fa0ddcd4d288d42f50eff18b0bac4"},
{file = "coverage-7.3.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee453085279df1bac0996bc97004771a4a052b1f1e23f6101213e3796ff3cb85"},
{file = "coverage-7.3.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1191270b06ecd68b1d00897b2daddb98e1719f63750969614ceb3438228c088e"},
{file = "coverage-7.3.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:007a7e49831cfe387473e92e9ff07377f6121120669ddc39674e7244350a6a29"},
{file = "coverage-7.3.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:af75cf83c2d57717a8493ed2246d34b1f3398cb8a92b10fd7a1858cad8e78f59"},
{file = "coverage-7.3.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:811ca7373da32f1ccee2927dc27dc523462fd30674a80102f86c6753d6681bc6"},
{file = "coverage-7.3.3-cp312-cp312-win32.whl", hash = "sha256:733537a182b5d62184f2a72796eb6901299898231a8e4f84c858c68684b25a70"},
{file = "coverage-7.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:e995efb191f04b01ced307dbd7407ebf6e6dc209b528d75583277b10fd1800ee"},
{file = "coverage-7.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fbd8a5fe6c893de21a3c6835071ec116d79334fbdf641743332e442a3466f7ea"},
{file = "coverage-7.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:50c472c1916540f8b2deef10cdc736cd2b3d1464d3945e4da0333862270dcb15"},
{file = "coverage-7.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e9223a18f51d00d3ce239c39fc41410489ec7a248a84fab443fbb39c943616c"},
{file = "coverage-7.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f501e36ac428c1b334c41e196ff6bd550c0353c7314716e80055b1f0a32ba394"},
{file = "coverage-7.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:475de8213ed95a6b6283056d180b2442eee38d5948d735cd3d3b52b86dd65b92"},
{file = "coverage-7.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:afdcc10c01d0db217fc0a64f58c7edd635b8f27787fea0a3054b856a6dff8717"},
{file = "coverage-7.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:fff0b2f249ac642fd735f009b8363c2b46cf406d3caec00e4deeb79b5ff39b40"},
{file = "coverage-7.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a1f76cfc122c9e0f62dbe0460ec9cc7696fc9a0293931a33b8870f78cf83a327"},
{file = "coverage-7.3.3-cp38-cp38-win32.whl", hash = "sha256:757453848c18d7ab5d5b5f1827293d580f156f1c2c8cef45bfc21f37d8681069"},
{file = "coverage-7.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:ad2453b852a1316c8a103c9c970db8fbc262f4f6b930aa6c606df9b2766eee06"},
{file = "coverage-7.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b15e03b8ee6a908db48eccf4e4e42397f146ab1e91c6324da44197a45cb9132"},
{file = "coverage-7.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:89400aa1752e09f666cc48708eaa171eef0ebe3d5f74044b614729231763ae69"},
{file = "coverage-7.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c59a3e59fb95e6d72e71dc915e6d7fa568863fad0a80b33bc7b82d6e9f844973"},
{file = "coverage-7.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ede881c7618f9cf93e2df0421ee127afdfd267d1b5d0c59bcea771cf160ea4a"},
{file = "coverage-7.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3bfd2c2f0e5384276e12b14882bf2c7621f97c35320c3e7132c156ce18436a1"},
{file = "coverage-7.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7f3bad1a9313401ff2964e411ab7d57fb700a2d5478b727e13f156c8f89774a0"},
{file = "coverage-7.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:65d716b736f16e250435473c5ca01285d73c29f20097decdbb12571d5dfb2c94"},
{file = "coverage-7.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a702e66483b1fe602717020a0e90506e759c84a71dbc1616dd55d29d86a9b91f"},
{file = "coverage-7.3.3-cp39-cp39-win32.whl", hash = "sha256:7fbf3f5756e7955174a31fb579307d69ffca91ad163467ed123858ce0f3fd4aa"},
{file = "coverage-7.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cad9afc1644b979211989ec3ff7d82110b2ed52995c2f7263e7841c846a75348"},
{file = "coverage-7.3.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:d299d379b676812e142fb57662a8d0d810b859421412b4d7af996154c00c31bb"},
{file = "coverage-7.3.3.tar.gz", hash = "sha256:df04c64e58df96b4427db8d0559e95e2df3138c9916c96f9f6a4dd220db2fdb7"},
]
[package.dependencies]
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
[package.extras]
toml = ["tomli"]
[[package]]
name = "cryptography"
version = "41.0.7"
@@ -1321,11 +1388,11 @@ files = [
google-auth = ">=2.14.1,<3.0.dev0"
googleapis-common-protos = ">=1.56.2,<2.0.dev0"
grpcio = [
{version = ">=1.33.2,<2.0dev", optional = true, markers = "extra == \"grpc\""},
{version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
{version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""},
]
grpcio-status = [
{version = ">=1.33.2,<2.0.dev0", optional = true, markers = "extra == \"grpc\""},
{version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
{version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""},
]
protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0"
@@ -2150,6 +2217,16 @@ files = [
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
{file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
{file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
{file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"},
{file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"},
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"},
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"},
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"},
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"},
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"},
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"},
{file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"},
{file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"},
@@ -2894,8 +2971,8 @@ files = [
[package.dependencies]
numpy = [
{version = ">=1.20.3", markers = "python_version < \"3.10\""},
{version = ">=1.21.0", markers = "python_version >= \"3.10\""},
{version = ">=1.23.2", markers = "python_version >= \"3.11\""},
{version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""},
]
python-dateutil = ">=2.8.2"
pytz = ">=2020.1"
@@ -4052,6 +4129,7 @@ files = [
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
{file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
{file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
{file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
@@ -4059,8 +4137,15 @@ files = [
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
{file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
{file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
{file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
{file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
@@ -4077,6 +4162,7 @@ files = [
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
{file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
{file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
{file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
@@ -4084,6 +4170,7 @@ files = [
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
{file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
{file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
{file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
+4 -1
View File
@@ -72,8 +72,11 @@ azure-identity = "^1.13.0"
usearch = "1.1.1"
pyarrow = ">=12.0.1,<15.0.0"
[tool.pytest]
[tool.pytest.ini_options]
addopts = "--cov=semantic_kernel --cov-report term-missing"
testpaths = [
"tests"
]
[tool.isort]
profile = "ruff"
@@ -6,6 +6,9 @@ from dotenv import load_dotenv
import semantic_kernel as sk
import semantic_kernel.connectors.ai.open_ai as sk_oai
from semantic_kernel.connectors.ai.chat_completion_client_base import (
ChatCompletionClientBase,
)
from semantic_kernel.utils.settings import azure_openai_settings_from_dot_env_as_dict
load_dotenv()
@@ -26,9 +29,28 @@ chat_service = sk_oai.AzureChatCompletion(
)
kernel.add_chat_service("chat-gpt", chat_service)
prompt_config = sk.PromptTemplateConfig.from_completion_parameters(
max_tokens=2000, temperature=0.7, top_p=0.8
## there are three ways to create the request settings in code: # noqa: E266
## 1. create the request settings from the base class: # noqa: E266
# from semantic_kernel.connectors.ai.chat_completion_client_base import AIRequestSettings
# req_settings = AIRequestSettings(extension_data = { "max_tokens": 2000, "temperature": 0.7, "top_p": 0.8} )
## This method (using the AIRequestSettings base class) is the most generic, and it allows you to store request settings for different services in the same extension_data field. There are two downsides to this approach: the specific request setting class will be created dynamically for each call, this is overhead when using just a single service. and the request settings are not type checked, so you will receive error messages once the dynamic creation of the request settings class fails. # noqa: E501 E266
## 2. create the request settings directly for the service you are using: # noqa: E266
# req_settings = sk_oai.AzureOpenAIChatRequestSettings(max_tokens=2000, temperature=0.7, top_p=0.8)
## The second method is useful when you are using a single service, and you want to have type checking on the request settings or when you are using multiple instances of the same type of service, for instance gpt-35-turbo and gpt-4, both in openai and both for chat. # noqa: E501 E266
## 3. create the request settings from the kernel based on the registered service class: # noqa: E266
req_settings = kernel.get_request_settings_from_service(
ChatCompletionClientBase, "chat-gpt"
)
req_settings.max_tokens = 2000
req_settings.temperature = 0.7
req_settings.top_p = 0.8
## The third method is the most specific as the returned request settings class is the one that is registered for the service and has some fields already filled in, like the service_id and ai_model_id. # noqa: E501 E266
prompt_config = sk.PromptTemplateConfig(completion=req_settings)
prompt_template = sk.ChatPromptTemplate(
"{{$user_input}}", kernel.prompt_template_engine, prompt_config
@@ -61,11 +83,16 @@ async def chat() -> bool:
print("\n\nExiting chat...")
return False
answer = kernel.run_stream_async(chat_function, input_vars=context_vars)
print("Mosscap:> ", end="")
async for message in answer:
print(message, end="")
print("\n")
stream = True
if stream:
answer = kernel.run_stream_async(chat_function, input_vars=context_vars)
print("Mosscap:> ", end="")
async for message in answer:
print(message, end="")
print("\n")
return True
answer = await kernel.run_async(chat_function, input_vars=context_vars)
print(f"Mosscap:> {answer}")
return True
@@ -3,7 +3,7 @@ import os
from dotenv import load_dotenv
import semantic_kernel as sk
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.connectors.search_engine import BingConnector
from semantic_kernel.core_skills import WebSearchEngineSkill
@@ -12,9 +12,17 @@ load_dotenv()
async def main():
kernel = sk.Kernel()
api_key, org_id = sk.openai_settings_from_dot_env()
deployment, key, endpoint, api_version = sk.azure_openai_settings_from_dot_env(
include_api_version=True
)
kernel.add_chat_service(
"chat-gpt", OpenAIChatCompletion("gpt-3.5-turbo", api_key, org_id)
"chat-gpt",
AzureChatCompletion(
deployment_name=deployment,
api_key=key,
endpoint=endpoint,
api_version=api_version,
),
)
connector = BingConnector(api_key=os.getenv("BING_API_KEY"))
web_skill = kernel.import_skill(WebSearchEngineSkill(connector), "WebSearch")
@@ -54,16 +54,21 @@ kernel.import_skill(MathSkill(), skill_name="math")
# if you only want to use a specific function, set the name of that function in this parameter,
# the format for that is 'SkillName-FunctionName', (i.e. 'math-Add').
# if the model or api version do not support this you will get an error.
prompt_config = sk.PromptTemplateConfig.from_completion_parameters(
max_tokens=2000,
temperature=0.7,
top_p=0.8,
function_call="auto",
chat_system_prompt=system_message,
prompt_config = sk.PromptTemplateConfig(
completion=sk_oai.AzureChatRequestSettings(
service_id="chat-gpt",
ai_model_id=deployment_name,
max_tokens=2000,
temperature=0.7,
top_p=0.8,
function_call="auto",
functions=get_function_calling_object(kernel, {"exclude_skill": ["ChatBot"]}),
)
)
prompt_template = OpenAIChatPromptTemplate(
"{{$user_input}}", kernel.prompt_template_engine, prompt_config
)
prompt_template.add_system_message(system_message)
prompt_template.add_user_message("Hi there, who are you?")
prompt_template.add_assistant_message(
"I am Mosscap, a chat bot. I'm trying to figure out what people need."
@@ -72,12 +77,6 @@ prompt_template.add_assistant_message(
function_config = sk.SemanticFunctionConfig(prompt_config, prompt_template)
chat_function = kernel.register_semantic_function("ChatBot", "Chat", function_config)
# calling the chat, you could add a overloaded version of the settings here,
# to enable or disable function calling or set the function calling to a specific skill.
# see the openai_function_calling example for how to use this with a unrelated function definition
filter = {"exclude_skill": ["ChatBot"]}
functions = get_function_calling_object(kernel, filter)
async def chat(context: sk.SKContext) -> Tuple[bool, sk.SKContext]:
try:
@@ -99,7 +98,6 @@ async def chat(context: sk.SKContext) -> Tuple[bool, sk.SKContext]:
chat_skill_name="ChatBot",
chat_function_name="Chat",
context=context,
functions=functions,
)
print(f"Mosscap:> {context.result}")
return True, context
@@ -4,14 +4,16 @@ import asyncio
import semantic_kernel as sk
import semantic_kernel.connectors.ai.google_palm as sk_gp
from semantic_kernel.connectors.ai.chat_request_settings import ChatRequestSettings
from semantic_kernel.connectors.ai.google_palm.gp_request_settings import (
GooglePalmChatRequestSettings,
)
async def chat_request_example(api_key):
palm_chat_completion = sk_gp.GooglePalmChatCompletion(
"models/chat-bison-001", api_key
)
settings = ChatRequestSettings()
settings = GooglePalmChatRequestSettings()
settings.temperature = 1
chat_messages = list()
@@ -4,8 +4,8 @@ import asyncio
import semantic_kernel as sk
import semantic_kernel.connectors.ai.google_palm as sk_gp
from semantic_kernel.connectors.ai.complete_request_settings import (
CompleteRequestSettings,
from semantic_kernel.connectors.ai.google_palm.gp_request_settings import (
GooglePalmRequestSettings,
)
@@ -24,7 +24,7 @@ async def text_completion_example_complete_async(kernel, api_key, user_mssg, set
async def main() -> None:
kernel = sk.Kernel()
apikey = sk.google_palm_settings_from_dot_env()
settings = CompleteRequestSettings()
settings = GooglePalmRequestSettings()
user_mssg1 = (
"Sam has three boxes, each containing a certain number of coins. "
@@ -2,13 +2,12 @@ import asyncio
import logging
import semantic_kernel as sk
from samples.utils import Colors
from semantic_kernel.connectors.ai.open_ai import (
AzureChatCompletion,
OpenAIChatCompletion,
)
from ..utils import Colors
def get_grounding_text():
return """I am by birth a Genevese, and my family is one of the most distinguished of that republic.
@@ -5,6 +5,9 @@ import os
import semantic_kernel as sk
import semantic_kernel.connectors.ai.open_ai as sk_oai
from semantic_kernel.connectors.ai.open_ai.models.chat.open_ai_chat_message import (
OpenAIChatMessage,
)
from semantic_kernel.core_skills import MathSkill
system_message = """
@@ -46,24 +49,6 @@ kernel.import_skill(MathSkill(), skill_name="math")
# if you only want to use a specific function, set the name of that function in this parameter,
# the format for that is 'SkillName-FunctionName', (i.e. 'math-Add').
# if the model or api version do not support this you will get an error.
prompt_config = sk.PromptTemplateConfig.from_completion_parameters(
max_tokens=2000,
temperature=0.7,
top_p=0.8,
function_call="auto",
chat_system_prompt=system_message,
)
prompt_template = sk.ChatPromptTemplate(
"{{$user_input}}", kernel.prompt_template_engine, prompt_config
)
prompt_template.add_user_message("Hi there, who are you?")
prompt_template.add_assistant_message(
"I am Mosscap, a chat bot. I'm trying to figure out what people need."
)
function_config = sk.SemanticFunctionConfig(prompt_config, prompt_template)
chat_function = kernel.register_semantic_function("ChatBot", "Chat", function_config)
# define the functions available
functions = [
{
"name": "search_hotels",
@@ -88,6 +73,25 @@ functions = [
},
}
]
prompt_config = sk.PromptTemplateConfig.from_completion_parameters(
max_tokens=2000,
temperature=0.7,
top_p=0.8,
function_call="auto",
functions=functions,
)
prompt_template = sk.ChatPromptTemplate[OpenAIChatMessage](
"{{$user_input}}", kernel.prompt_template_engine, prompt_config
)
prompt_template.add_system_message(system_message)
prompt_template.add_user_message("Hi there, who are you?")
prompt_template.add_assistant_message(
"I am Mosscap, a chat bot. I'm trying to figure out what people need."
)
function_config = sk.SemanticFunctionConfig(prompt_config, prompt_template)
chat_function = kernel.register_semantic_function("ChatBot", "Chat", function_config)
# define the functions available
async def main() -> None:
@@ -96,7 +100,7 @@ async def main() -> None:
"user_input"
] = "I want to find a hotel in Seattle with free wifi and a pool."
context = await chat_function.invoke_async(context=context, functions=functions)
context = await chat_function.invoke_async(context=context)
if function_call := context.objects.pop("function_call"):
print(f"Function to be called: {function_call.name}")
print(f"Function parameters: \n{function_call.arguments}")
@@ -4,10 +4,6 @@ import asyncio
import semantic_kernel as sk
import semantic_kernel.connectors.ai.open_ai as sk_oai
from semantic_kernel.connectors.ai.chat_request_settings import ChatRequestSettings
from semantic_kernel.connectors.ai.complete_request_settings import (
CompleteRequestSettings,
)
"""
Logit bias enables prioritizing certain tokens within a given output.
@@ -18,9 +14,7 @@ Read more about logit bias and how to configure output: https://help.openai.com/
def _config_ban_tokens(settings_type, keys):
settings = (
ChatRequestSettings() if settings_type == "chat" else CompleteRequestSettings()
)
settings = sk_oai.OpenAIRequestSettings()
# Map each token in the keys list to a bias value from -100 (a potential ban) to 100 (exclusive selection)
for k in keys:
@@ -1,12 +1,9 @@
# Copyright (c) Microsoft. All rights reserved.
from semantic_kernel.connectors.ai.ai_request_settings import AIRequestSettings
from semantic_kernel.connectors.ai.chat_completion_client_base import (
ChatCompletionClientBase,
)
from semantic_kernel.connectors.ai.chat_request_settings import ChatRequestSettings
from semantic_kernel.connectors.ai.complete_request_settings import (
CompleteRequestSettings,
)
from semantic_kernel.connectors.ai.embeddings.embedding_generator_base import (
EmbeddingGeneratorBase,
)
@@ -18,6 +15,5 @@ __all__ = [
"ChatCompletionClientBase",
"TextCompletionClientBase",
"EmbeddingGeneratorBase",
"ChatRequestSettings",
"CompleteRequestSettings",
"AIRequestSettings",
]
@@ -0,0 +1,82 @@
from typing import Any, Dict, Optional
from pydantic import Field
from semantic_kernel.sk_pydantic import SKBaseModel
class AIRequestSettings(SKBaseModel):
"""Base class for AI request settings.
Can be used by itself or as a base class for other request settings. The methods are used to create specific request settings objects based on the keys in the extension_data field, this way you can create a generic AIRequestSettings object in your application, which get's mapped into the keys of the request settings that each services returns by using the service.get_request_settings() method.
Parameters:
service_id (str, optional): The service ID to use for the request. Defaults to None.
extension_data (Dict[str, Any], optional): Any additional data to send with the request. Defaults to None.
kwargs (Any): Additional keyword arguments,
these are attempted to parse into the keys of the specific request settings.
Methods:
prepare_settings_dict: Prepares the settings as a dictionary for sending to the AI service.
update_from_ai_request_settings: Update the keys from another request settings object.
from_ai_request_settings: Create a request settings from another request settings object.
""" # noqa: E501
service_id: Optional[str] = Field(None, min_length=1)
extension_data: Dict[str, Any] = Field(default_factory=dict)
def __init__(self, service_id: Optional[str] = None, **kwargs: Any):
extension_data = kwargs.pop("extension_data", {})
extension_data.update(kwargs)
super().__init__(service_id=service_id, extension_data=extension_data)
self.unpack_extension_data()
@property
def keys(self):
"""Get the keys of the request settings."""
return self.model_fields.keys()
def prepare_settings_dict(self, **kwargs) -> Dict[str, Any]:
return self.model_dump(
exclude={"service_id", "extension_data"},
exclude_none=True,
by_alias=True,
)
def update_from_ai_request_settings(self, config: "AIRequestSettings") -> None:
"""Update the request settings from a completion config."""
if config.service_id is not None:
self.service_id = config.service_id
config.pack_extension_data()
self.extension_data.update(config.extension_data)
self.unpack_extension_data()
@classmethod
def from_ai_request_settings(
cls, config: "AIRequestSettings"
) -> "AIRequestSettings":
"""Create a request settings from a completion config."""
config.pack_extension_data()
return cls(
service_id=config.service_id,
extension_data=config.extension_data,
)
def unpack_extension_data(self) -> None:
"""Update the request settings from extension data.
Does not overwrite existing values with None.
"""
for key, value in self.extension_data.items():
if value is None:
continue
if key in self.keys:
setattr(self, key, value)
def pack_extension_data(self) -> None:
"""Update the extension data from the request settings."""
for key in self.model_fields_set:
if (
key not in ["service_id", "extension_data"]
and getattr(self, key) is not None
):
self.extension_data[key] = getattr(self, key)
@@ -4,6 +4,7 @@ from abc import ABC
from pydantic import constr
from semantic_kernel.connectors.ai.ai_request_settings import AIRequestSettings
from semantic_kernel.sk_pydantic import SKBaseModel
@@ -17,3 +18,7 @@ class AIServiceClientBase(SKBaseModel, ABC):
"""
ai_model_id: constr(strip_whitespace=True, min_length=1)
def get_request_settings_class(self) -> "AIRequestSettings":
"""Create a request settings object."""
return AIRequestSettings # pragma: no cover
@@ -4,7 +4,7 @@ from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, AsyncGenerator, List, Optional, Union
if TYPE_CHECKING:
from semantic_kernel.connectors.ai.chat_request_settings import ChatRequestSettings
from semantic_kernel.connectors.ai.ai_request_settings import AIRequestSettings
from semantic_kernel.models.chat.chat_message import ChatMessage
@@ -13,7 +13,7 @@ class ChatCompletionClientBase(ABC):
async def complete_chat_async(
self,
messages: List["ChatMessage"],
settings: "ChatRequestSettings",
settings: "AIRequestSettings",
logger: Optional[Any] = None,
) -> Union[str, List[str]]:
"""
@@ -22,7 +22,7 @@ class ChatCompletionClientBase(ABC):
Arguments:
messages {List[ChatMessage]} -- A list of chat messages, that can be rendered into a
set of messages, from system, user, assistant and function.
settings {ChatRequestSettings} -- Settings for the request.
settings {AIRequestSettings} -- Settings for the request.
logger {Logger} -- A logger to use for logging. (Deprecated)
Returns:
@@ -34,7 +34,7 @@ class ChatCompletionClientBase(ABC):
async def complete_chat_stream_async(
self,
messages: List["ChatMessage"],
settings: "ChatRequestSettings",
settings: "AIRequestSettings",
logger: Optional[Any] = None,
) -> AsyncGenerator[Union[str, List[str]], None]:
"""
@@ -43,7 +43,7 @@ class ChatCompletionClientBase(ABC):
Arguments:
messages {List[ChatMessage]} -- A list of chat messages, that can be rendered into a
set of messages, from system, user, assistant and function.
settings {ChatRequestSettings} -- Settings for the request.
settings {AIRequestSettings} -- Settings for the request.
logger {Logger} -- A logger to use for logging. (Deprecated)
Yields:
@@ -1,61 +0,0 @@
# Copyright (c) Microsoft. All rights reserved.
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Dict, List, Optional
if TYPE_CHECKING:
from semantic_kernel.connectors.ai.open_ai.semantic_functions.open_ai_chat_prompt_template_with_data_config import (
OpenAIChatPromptTemplateWithDataConfig,
)
from semantic_kernel.semantic_functions.prompt_template_config import (
PromptTemplateConfig,
)
@dataclass
class ChatRequestSettings:
temperature: float = 0.0
top_p: float = 1.0
presence_penalty: float = 0.0
frequency_penalty: float = 0.0
number_of_responses: int = 1
max_tokens: int = 256
token_selection_biases: Dict[int, int] = field(default_factory=dict)
stop_sequences: List[str] = field(default_factory=list)
function_call: Optional[str] = None
data_source_settings: Optional[
"OpenAIChatPromptTemplateWithDataConfig.AzureChatWithDataSettings"
] = None
inputLanguage: Optional[str] = None
outputLanguage: Optional[str] = None
def update_from_completion_config(
self, completion_config: "PromptTemplateConfig.CompletionConfig"
):
self.temperature = completion_config.temperature
self.top_p = completion_config.top_p
self.number_of_responses = completion_config.number_of_responses
self.stop_sequences = completion_config.stop_sequences
self.max_tokens = completion_config.max_tokens
self.presence_penalty = completion_config.presence_penalty
self.frequency_penalty = completion_config.frequency_penalty
self.token_selection_biases = completion_config.token_selection_biases
self.function_call = (
completion_config.function_call
if hasattr(completion_config, "function_call")
else None
)
if hasattr(completion_config, "data_source_settings"):
self.data_source_settings = completion_config.data_source_settings
if hasattr(completion_config, "inputLanguage"):
self.inputLanguage = completion_config.inputLanguage
if hasattr(completion_config, "outputLanguage"):
self.outputLanguage = completion_config.outputLanguage
@staticmethod
def from_completion_config(
completion_config: "PromptTemplateConfig.CompletionConfig",
) -> "ChatRequestSettings":
settings = ChatRequestSettings()
settings.update_from_completion_config(completion_config)
return settings
@@ -1,46 +0,0 @@
# Copyright (c) Microsoft. All rights reserved.
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Dict, List
if TYPE_CHECKING:
from semantic_kernel.semantic_functions.prompt_template_config import (
PromptTemplateConfig,
)
@dataclass
class CompleteRequestSettings:
temperature: float = 0.0
top_p: float = 1.0
presence_penalty: float = 0.0
frequency_penalty: float = 0.0
max_tokens: int = 256
stop_sequences: List[str] = field(default_factory=list)
number_of_responses: int = 1
logprobs: int = 0
token_selection_biases: Dict[int, int] = field(default_factory=dict)
chat_system_prompt: str = "Assistant is a large language model."
def update_from_completion_config(
self, completion_config: "PromptTemplateConfig.CompletionConfig"
):
self.temperature = completion_config.temperature
self.top_p = completion_config.top_p
self.presence_penalty = completion_config.presence_penalty
self.frequency_penalty = completion_config.frequency_penalty
self.max_tokens = completion_config.max_tokens
self.stop_sequences = completion_config.stop_sequences
self.number_of_responses = completion_config.number_of_responses
self.token_selection_biases = completion_config.token_selection_biases
if completion_config.chat_system_prompt:
self.chat_system_prompt = completion_config.chat_system_prompt
@staticmethod
def from_completion_config(
completion_config: "PromptTemplateConfig.CompletionConfig",
) -> "CompleteRequestSettings":
settings = CompleteRequestSettings()
settings.update_from_completion_config(completion_config)
return settings
@@ -1,12 +1,13 @@
# Copyright (c) Microsoft. All rights reserved.
from abc import ABC, abstractmethod
from typing import List
from typing import TYPE_CHECKING, List
from numpy import ndarray
if TYPE_CHECKING:
from numpy import ndarray
class EmbeddingGeneratorBase(ABC):
@abstractmethod
async def generate_embeddings_async(self, texts: List[str]) -> ndarray:
async def generate_embeddings_async(self, texts: List[str]) -> "ndarray":
pass
@@ -1,5 +1,9 @@
# Copyright (c) Microsoft. All rights reserved.
from semantic_kernel.connectors.ai.google_palm.gp_request_settings import (
GooglePalmChatRequestSettings,
GooglePalmTextRequestSettings,
)
from semantic_kernel.connectors.ai.google_palm.services.gp_chat_completion import (
GooglePalmChatCompletion,
)
@@ -14,4 +18,6 @@ __all__ = [
"GooglePalmTextCompletion",
"GooglePalmChatCompletion",
"GooglePalmTextEmbedding",
"GooglePalmChatRequestSettings",
"GooglePalmTextRequestSettings",
]
@@ -0,0 +1,44 @@
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
from pydantic import Field, model_validator
from semantic_kernel.connectors.ai.ai_request_settings import AIRequestSettings
# TODO: replace back with google types once pydantic issue is fixed.
MessageOptions = Union[str, Dict[str, Any], List[Tuple[str, str]]]
MessagesOptions = Union[MessageOptions, Iterable[MessageOptions]]
MessagePromptOption = Union[str, dict]
MessagePromptOptions = Union[MessagePromptOption, Iterable[MessagePromptOption]]
ExampleOptions = Union[Dict[str, Any], Iterable[Dict[str, Any]]]
class GooglePalmRequestSettings(AIRequestSettings):
ai_model_id: Optional[str] = Field(None, serialization_alias="model")
temperature: float = Field(0.0, ge=0.0, le=1.0)
top_p: float = 1.0
top_k: float = 1.0
candidate_count: int = Field(1, ge=1, le=8)
safety_settings: Optional[Dict[str, Any]] = None
prompt: Optional[MessagePromptOptions] = None
class GooglePalmTextRequestSettings(GooglePalmRequestSettings):
max_output_tokens: int = Field(256, gt=0)
stop_sequences: Optional[Union[str, Iterable[str]]] = None
class GooglePalmChatRequestSettings(GooglePalmRequestSettings):
messages: Optional[MessagesOptions] = None
examples: Optional[ExampleOptions] = None
context: Optional[str] = None
token_selection_biases: Dict[int, int] = {}
@model_validator(mode="after")
def validate_input(self):
if self.prompt is not None:
if self.messages or self.context or self.examples:
raise ValueError(
"Prompt cannot be used with messages, context or examples"
)
@@ -4,17 +4,18 @@ import logging
from typing import Any, List, Optional, Tuple, Union
import google.generativeai as palm
from google.generativeai.types import ChatResponse, ExampleOptions, MessagePromptOptions
from google.generativeai.types import ChatResponse
from pydantic import PrivateAttr, constr
from semantic_kernel.connectors.ai.ai_exception import AIException
from semantic_kernel.connectors.ai.ai_request_settings import AIRequestSettings
from semantic_kernel.connectors.ai.ai_service_client_base import AIServiceClientBase
from semantic_kernel.connectors.ai.chat_completion_client_base import (
ChatCompletionClientBase,
)
from semantic_kernel.connectors.ai.chat_request_settings import ChatRequestSettings
from semantic_kernel.connectors.ai.complete_request_settings import (
CompleteRequestSettings,
from semantic_kernel.connectors.ai.google_palm.gp_request_settings import (
GooglePalmChatRequestSettings,
GooglePalmRequestSettings,
)
from semantic_kernel.connectors.ai.text_completion_client_base import (
TextCompletionClientBase,
@@ -60,16 +61,14 @@ class GooglePalmChatCompletion(
async def complete_chat_async(
self,
messages: List[Tuple[str, str]],
request_settings: ChatRequestSettings,
context: Optional[str] = None,
examples: Optional[ExampleOptions] = None,
prompt: Optional[MessagePromptOptions] = None,
settings: GooglePalmRequestSettings,
) -> Union[str, List[str]]:
response = await self._send_chat_request(
messages, request_settings, context, examples, prompt
)
settings.messages = messages
if not settings.ai_model_id:
settings.ai_model_id = self.ai_model_id
response = await self._send_chat_request(settings)
if request_settings.number_of_responses > 1:
if settings.candidate_count > 1:
return [
candidate["output"]
if candidate["output"] is not None
@@ -83,8 +82,7 @@ class GooglePalmChatCompletion(
async def complete_chat_stream_async(
self,
messages: List[Tuple[str, str]],
request_settings: ChatRequestSettings,
context: Optional[str] = None,
settings: GooglePalmRequestSettings,
):
raise NotImplementedError(
"Google Palm API does not currently support streaming"
@@ -93,26 +91,19 @@ class GooglePalmChatCompletion(
async def complete_async(
self,
prompt: str,
request_settings: CompleteRequestSettings,
settings: GooglePalmRequestSettings,
**kwargs,
) -> Union[str, List[str]]:
if kwargs.get("logger"):
logger.warning(
"The `logger` parameter is deprecated. Please use the `logging` module instead."
)
prompt_to_message = [("user", prompt)]
chat_settings = ChatRequestSettings(
temperature=request_settings.temperature,
top_p=request_settings.top_p,
presence_penalty=request_settings.presence_penalty,
frequency_penalty=request_settings.frequency_penalty,
max_tokens=request_settings.max_tokens,
number_of_responses=request_settings.number_of_responses,
token_selection_biases=request_settings.token_selection_biases,
)
response = await self._send_chat_request(prompt_to_message, chat_settings)
settings.messages = [("user", prompt)]
if not settings.ai_model_id:
settings.ai_model_id = self.ai_model_id
response = await self._send_chat_request(settings)
if chat_settings.number_of_responses > 1:
if settings.candidate_count > 1:
return [
candidate["output"]
if candidate["output"] is not None
@@ -126,7 +117,7 @@ class GooglePalmChatCompletion(
async def complete_stream_async(
self,
prompt: str,
request_settings: CompleteRequestSettings,
settings: GooglePalmRequestSettings,
**kwargs,
):
if kwargs.get("logger"):
@@ -139,11 +130,7 @@ class GooglePalmChatCompletion(
async def _send_chat_request(
self,
messages: List[Tuple[str, str]],
request_settings: ChatRequestSettings,
context: Optional[str] = None,
examples: Optional[ExampleOptions] = None,
prompt: Optional[MessagePromptOptions] = None,
settings: GooglePalmRequestSettings,
):
"""
Completes the given user message. If len(messages) > 1, and a
@@ -155,7 +142,7 @@ class GooglePalmChatCompletion(
Arguments:
messages {str} -- The message (from a user) to respond to.
request_settings {ChatRequestSettings} -- The request settings.
settings {GooglePalmRequestSettings} -- The request settings.
context {str} -- Text that should be provided to the model first,
to ground the response. If a system message is provided, it will be
used as context.
@@ -175,23 +162,10 @@ class GooglePalmChatCompletion(
Returns:
str -- The completed text.
"""
if request_settings is None:
if settings is None:
raise ValueError("The request settings cannot be `None`")
if request_settings.max_tokens < 1:
raise AIException(
AIException.ErrorCodes.InvalidRequest,
"The max tokens must be greater than 0, "
f"but was {request_settings.max_tokens}",
)
if len(messages) <= 0:
raise AIException(
AIException.ErrorCodes.InvalidRequest,
"To complete a chat you need at least one message",
)
if messages[-1][0] != "user":
if settings.messages[-1][0] != "user":
raise AIException(
AIException.ErrorCodes.InvalidRequest,
"The last message must be from the user",
@@ -204,12 +178,12 @@ class GooglePalmChatCompletion(
ex,
)
if (
self._message_history is None and context is None
self._message_history is None and settings.context is None
): # If the conversation hasn't started yet and no context is provided
context = ""
if len(messages) > 1: # Check if we need context from messages
for index, (role, message) in enumerate(messages):
if index < len(messages) - 1:
if len(settings.messages) > 1: # Check if we need context from messages
for index, (role, message) in enumerate(settings.messages):
if index < len(settings.messages) - 1:
if role == "system":
context += message + "\n"
else:
@@ -217,18 +191,11 @@ class GooglePalmChatCompletion(
try:
if self._message_history is None:
response = palm.chat( # Start a new conversation
model=self.ai_model_id,
context=context,
examples=examples,
temperature=request_settings.temperature,
candidate_count=request_settings.number_of_responses,
top_p=request_settings.top_p,
prompt=prompt,
messages=messages[-1][1],
**settings.prepare_settings_dict()
)
else:
response = self._message_history.reply( # Continue the conversation
messages[-1][1],
settings.messages[-1][1],
)
self._message_history = response # Store response object for future use
except Exception as ex:
@@ -238,3 +205,7 @@ class GooglePalmChatCompletion(
ex,
)
return response
def get_request_settings_class(self) -> "AIRequestSettings":
"""Create a request settings object."""
return GooglePalmChatRequestSettings
@@ -7,9 +7,10 @@ import google.generativeai as palm
from pydantic import constr
from semantic_kernel.connectors.ai.ai_exception import AIException
from semantic_kernel.connectors.ai.ai_request_settings import AIRequestSettings
from semantic_kernel.connectors.ai.ai_service_client_base import AIServiceClientBase
from semantic_kernel.connectors.ai.complete_request_settings import (
CompleteRequestSettings,
from semantic_kernel.connectors.ai.google_palm.gp_request_settings import (
GooglePalmTextRequestSettings,
)
from semantic_kernel.connectors.ai.text_completion_client_base import (
TextCompletionClientBase,
@@ -41,57 +42,12 @@ class GooglePalmTextCompletion(TextCompletionClientBase, AIServiceClientBase):
async def complete_async(
self,
prompt: str,
request_settings: CompleteRequestSettings,
**kwargs,
request_settings: GooglePalmTextRequestSettings,
logger: Optional[Any] = None,
) -> Union[str, List[str]]:
if kwargs.get("logger"):
logger.warning(
"The `logger` parameter is deprecated. Please use the `logging` module instead."
)
response = await self._send_completion_request(prompt, request_settings)
if request_settings.number_of_responses > 1:
return [candidate["output"] for candidate in response.candidates]
return response.result
async def complete_stream_async(
self,
prompt: str,
request_settings: CompleteRequestSettings,
**kwargs,
):
if kwargs.get("logger"):
logger.warning(
"The `logger` parameter is deprecated. Please use the `logging` module instead."
)
raise NotImplementedError(
"Google Palm API does not currently support streaming"
)
async def _send_completion_request(
self, prompt: str, request_settings: CompleteRequestSettings
):
"""
Completes the given prompt. Returns a single string completion.
Cannot return multiple completions. Cannot return logprobs.
Arguments:
prompt {str} -- The prompt to complete.
request_settings {CompleteRequestSettings} -- The request settings.
Returns:
str -- The completed text.
"""
if not prompt:
raise ValueError("Prompt cannot be `None` or empty")
if request_settings is None:
raise ValueError("Request settings cannot be `None`")
if request_settings.max_tokens < 1:
raise AIException(
AIException.ErrorCodes.InvalidRequest,
"The max tokens must be greater than 0, "
f"but was {request_settings.max_tokens}",
)
request_settings.prompt = prompt
if not request_settings.ai_model_id:
request_settings.ai_model_id = self.ai_model_id
try:
palm.configure(api_key=self.api_key)
except Exception as ex:
@@ -100,24 +56,27 @@ class GooglePalmTextCompletion(TextCompletionClientBase, AIServiceClientBase):
ex,
)
try:
response = palm.generate_text(
model=self.ai_model_id,
prompt=prompt,
temperature=request_settings.temperature,
max_output_tokens=request_settings.max_tokens,
stop_sequences=(
request_settings.stop_sequences
if request_settings.stop_sequences is not None
and len(request_settings.stop_sequences) > 0
else None
),
candidate_count=request_settings.number_of_responses,
top_p=request_settings.top_p,
)
response = palm.generate_text(**request_settings.prepare_settings_dict())
except Exception as ex:
raise AIException(
AIException.ErrorCodes.ServiceError,
"Google PaLM service failed to complete the prompt",
ex,
)
return response
if request_settings.candidate_count > 1:
return [candidate["output"] for candidate in response.candidates]
return response.result
async def complete_stream_async(
self,
prompt: str,
request_settings: GooglePalmTextRequestSettings,
logger: Optional[Any] = None,
):
raise NotImplementedError(
"Google Palm API does not currently support streaming"
)
def get_request_settings_class(self) -> "AIRequestSettings":
"""Create a request settings object."""
return GooglePalmTextRequestSettings
@@ -1,5 +1,8 @@
# Copyright (c) Microsoft. All rights reserved.
from semantic_kernel.connectors.ai.hugging_face.hf_request_settings import (
HuggingFaceRequestSettings,
)
from semantic_kernel.connectors.ai.hugging_face.services.hf_text_completion import (
HuggingFaceTextCompletion,
)
@@ -7,4 +10,8 @@ from semantic_kernel.connectors.ai.hugging_face.services.hf_text_embedding impor
HuggingFaceTextEmbedding,
)
__all__ = ["HuggingFaceTextCompletion", "HuggingFaceTextEmbedding"]
__all__ = [
"HuggingFaceTextCompletion",
"HuggingFaceTextEmbedding",
"HuggingFaceRequestSettings",
]
@@ -0,0 +1,40 @@
from typing import Any, Dict
from transformers import GenerationConfig
from semantic_kernel.connectors.ai.ai_request_settings import AIRequestSettings
class HuggingFaceRequestSettings(AIRequestSettings):
do_sample: bool = True
max_new_tokens: int = 256
num_return_sequences: int = 1
stop_sequences: Any = None
pad_token_id: int = 50256
temperature: float = 0.0
top_p: float = 1.0
def get_generation_config(self) -> GenerationConfig:
return GenerationConfig(
**self.model_dump(
include={"max_new_tokens", "pad_token_id", "temperature", "top_p"},
exclude_unset=True,
exclude_none=True,
by_alias=True,
)
)
def prepare_settings_dict(self, **kwargs) -> Dict[str, Any]:
gen_config = self.get_generation_config()
if "prompt" in kwargs and kwargs["prompt"] is not None:
return {
"prompt": kwargs["prompt"],
"generation_config": gen_config,
"num_return_sequences": self.num_return_sequences,
"do_sample": self.do_sample,
}
return {
"generation_config": gen_config,
"num_return_sequences": self.num_return_sequences,
"do_sample": self.do_sample,
}
@@ -9,8 +9,8 @@ import transformers
from semantic_kernel.connectors.ai.ai_exception import AIException
from semantic_kernel.connectors.ai.ai_service_client_base import AIServiceClientBase
from semantic_kernel.connectors.ai.complete_request_settings import (
CompleteRequestSettings,
from semantic_kernel.connectors.ai.hugging_face.hf_request_settings import (
HuggingFaceRequestSettings,
)
from semantic_kernel.connectors.ai.text_completion_client_base import (
TextCompletionClientBase,
@@ -22,7 +22,7 @@ logger: logging.Logger = logging.getLogger(__name__)
class HuggingFaceTextCompletion(TextCompletionClientBase, AIServiceClientBase):
task: Literal["summarization", "text-generation", "text2text-generation"]
device: str
generator: transformers.Pipeline
generator: Any
def __init__(
self,
@@ -79,7 +79,7 @@ class HuggingFaceTextCompletion(TextCompletionClientBase, AIServiceClientBase):
async def complete_async(
self,
prompt: str,
request_settings: CompleteRequestSettings,
request_settings: HuggingFaceRequestSettings,
**kwargs,
) -> Union[str, List[str]]:
if kwargs.get("logger"):
@@ -87,33 +87,13 @@ class HuggingFaceTextCompletion(TextCompletionClientBase, AIServiceClientBase):
"The `logger` parameter is deprecated. Please use the `logging` module instead."
)
try:
generation_config = transformers.GenerationConfig(
temperature=request_settings.temperature,
top_p=request_settings.top_p,
max_new_tokens=request_settings.max_tokens,
pad_token_id=50256, # EOS token
results = self.generator(**request_settings.prepare_settings_dict(prompt))
result_field_name = (
"summary_text" if self.task == "summarization" else "generated_text"
)
results = self.generator(
prompt,
do_sample=True,
num_return_sequences=request_settings.number_of_responses,
generation_config=generation_config,
)
completions = list()
if self.task == "summarization":
for response in results:
completions.append(response["summary_text"])
if len(completions) == 1:
return completions[0]
return completions
for response in results:
completions.append(response["generated_text"])
if len(completions) == 1:
return completions[0]
return completions
if len(results) == 1:
return results[0][result_field_name]
return [resp[result_field_name] for resp in results]
except Exception as e:
raise AIException("Hugging Face completion failed", e)
@@ -121,7 +101,7 @@ class HuggingFaceTextCompletion(TextCompletionClientBase, AIServiceClientBase):
async def complete_stream_async(
self,
prompt: str,
request_settings: CompleteRequestSettings,
request_settings: HuggingFaceRequestSettings,
**kwargs,
):
"""
@@ -130,7 +110,7 @@ class HuggingFaceTextCompletion(TextCompletionClientBase, AIServiceClientBase):
Arguments:
prompt {str} -- Prompt to complete.
request_settings {CompleteRequestSettings} -- Request settings.
request_settings {HuggingFaceRequestSettings} -- Request settings.
Yields:
str -- Completion result.
@@ -139,28 +119,21 @@ class HuggingFaceTextCompletion(TextCompletionClientBase, AIServiceClientBase):
logger.warning(
"The `logger` parameter is deprecated. Please use the `logging` module instead."
)
if request_settings.number_of_responses > 1:
if request_settings.num_return_sequences > 1:
raise AIException(
AIException.ErrorCodes.InvalidConfiguration,
"HuggingFace TextIteratorStreamer does not stream multiple responses in a parseable format. \
If you need multiple responses, please use the complete_async method.",
)
try:
generation_config = transformers.GenerationConfig(
temperature=request_settings.temperature,
top_p=request_settings.top_p,
max_new_tokens=request_settings.max_tokens,
pad_token_id=50256, # EOS token
)
tokenizer = transformers.AutoTokenizer.from_pretrained(self.ai_model_id)
streamer = transformers.TextIteratorStreamer(tokenizer)
args = {prompt}
kwargs = {
"num_return_sequences": request_settings.number_of_responses,
"generation_config": generation_config,
"num_return_sequences": request_settings.num_return_sequences,
"generation_config": request_settings.get_generation_config(),
"streamer": streamer,
"do_sample": True,
"do_sample": request_settings.do_sample,
}
# See https://github.com/huggingface/transformers/blob/main/src/transformers/generation/streamers.py#L159
@@ -18,7 +18,7 @@ logger: logging.Logger = logging.getLogger(__name__)
class HuggingFaceTextEmbedding(EmbeddingGeneratorBase, AIServiceClientBase):
device: str
generator: sentence_transformers.SentenceTransformer
generator: Any
def __init__(
self,
@@ -1,7 +1,12 @@
# Copyright (c) Microsoft. All rights reserved.
from semantic_kernel.connectors.ai.open_ai.semantic_functions.open_ai_chat_prompt_template_with_data_config import (
OpenAIChatPromptTemplateWithDataConfig,
from semantic_kernel.connectors.ai.open_ai.request_settings.azure_chat_request_settings import (
AzureChatRequestSettings,
)
from semantic_kernel.connectors.ai.open_ai.request_settings.open_ai_request_settings import (
OpenAIChatRequestSettings,
OpenAIRequestSettings,
OpenAITextRequestSettings,
)
from semantic_kernel.connectors.ai.open_ai.services.azure_chat_completion import (
AzureChatCompletion,
@@ -23,11 +28,14 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_embedding impor
)
__all__ = [
"OpenAIRequestSettings",
"OpenAIChatRequestSettings",
"OpenAITextRequestSettings",
"AzureChatRequestSettings",
"OpenAITextCompletion",
"OpenAIChatCompletion",
"OpenAITextEmbedding",
"AzureTextCompletion",
"AzureChatCompletion",
"AzureTextEmbedding",
"OpenAIChatPromptTemplateWithDataConfig",
]
@@ -4,3 +4,4 @@ from typing import final
DEFAULT_AZURE_API_VERSION: final = "2023-05-15"
USER_AGENT: final = "User-Agent"
DEFAULT_CHAT_SYSTEM_PROMPT: final = "Assistant is a large language model."
@@ -5,7 +5,9 @@ from typing import Tuple
from openai import AsyncStream
from openai.types.chat import ChatCompletionChunk
from semantic_kernel.connectors.ai.chat_request_settings import ChatRequestSettings
from semantic_kernel.connectors.ai.open_ai.request_settings.azure_chat_request_settings import (
AzureChatRequestSettings,
)
class AzureChatWithDataStreamResponse:
@@ -13,12 +15,12 @@ class AzureChatWithDataStreamResponse:
_tool_message: str
_assistant_message: AsyncStream[ChatCompletionChunk]
_settings: "ChatRequestSettings"
_settings: "AzureChatRequestSettings"
def __init__(
self,
assistant_message: AsyncStream[ChatCompletionChunk],
settings: "ChatRequestSettings",
settings: "AzureChatRequestSettings",
):
self._assistant_message = assistant_message
self._tool_message = ""
@@ -29,16 +31,14 @@ class AzureChatWithDataStreamResponse:
while True:
try:
message = await self._assistant_message.__anext__()
if message.choices and len(message.choices) > 0:
delta = message.choices[0].delta
if delta:
if delta.model_extra and "context" in delta.model_extra:
for m in delta.model_extra["context"].get("messages", []):
if m["role"] == "tool":
self._tool_message = m.get("content", "")
break
break
if delta and delta.model_extra and "context" in delta.model_extra:
for m in delta.model_extra["context"].get("messages", []):
if m["role"] == "tool":
self._tool_message = m.get("content", "")
break
break
except StopIteration:
break
@@ -22,7 +22,7 @@ class FunctionCall(SKBaseModel):
def to_context_variables(self) -> ContextVariables:
"""Return the arguments as a ContextVariables instance."""
args = self.parse_arguments()
return ContextVariables(variables={k.lower(): v for k, v in args.items()})
return ContextVariables(variables={k.lower(): str(v) for k, v in args.items()})
def split_name(self) -> Tuple[str, str]:
"""Split the name into a skill and function name."""
@@ -0,0 +1,79 @@
import logging
from typing import Any, Dict, List, Literal, Optional, Union
from pydantic import Field
from pydantic.dataclasses import dataclass
from semantic_kernel.connectors.ai.open_ai.request_settings.open_ai_request_settings import (
OpenAIChatRequestSettings,
)
from semantic_kernel.sk_pydantic import SKBaseModel
logger = logging.getLogger(__name__)
@dataclass
class AzureCosmosDBAuthentication:
type: Literal["ConnectionString"] = "ConnectionString"
connectionString: Optional[str] = None
@dataclass
class AzureCosmosDBEmbeddingDependencyType:
type: Literal["DeploymentName"] = "DeploymentName"
deploymentName: Optional[str] = None
@dataclass
class AzureDataSourceParameters:
indexName: str
indexLanguage: Optional[str] = None
fieldsMapping: Dict[str, Any] = Field(default_factory=dict)
inScope: Optional[bool] = True
topNDocuments: Optional[int] = 5
semanticConfiguration: Optional[str] = None
roleInformation: Optional[str] = None
filter: Optional[str] = None
embeddingKey: Optional[str] = None
embeddingEndpoint: Optional[str] = None
embeddingDeploymentName: Optional[str] = None
strictness: int = 3
@dataclass
class AzureCosmosDBDataSource(AzureDataSourceParameters):
authentication: Optional[AzureCosmosDBAuthentication] = None
databaseName: Optional[str] = None
containerName: Optional[str] = None
embeddingDependencyType: Optional[AzureCosmosDBEmbeddingDependencyType] = None
@dataclass
class AzureAISearchDataSources(AzureDataSourceParameters):
endpoint: Optional[str] = None
key: Optional[str] = None
queryType: Literal[
"simple", "semantic", "vector", "vectorSimpleHybrid", "vectorSemanticHybrid"
] = "simple"
@dataclass
class AzureDataSources:
"""Class to hold Azure AI data source parameters."""
type: Literal["AzureCognitiveSearch", "AzureCosmosDB"] = "AzureCognitiveSearch"
parameters: Optional[AzureDataSourceParameters] = None
# @dataclass
class ExtraBody(SKBaseModel):
data_sources: Optional[List[AzureDataSources]] = Field(None, alias="dataSources")
# input_language: Optional[str] = Field(None, serialization_alias="inputLanguage")
# output_language: Optional[str] = Field(None, serialization_alias="outputLanguage")
class AzureChatRequestSettings(OpenAIChatRequestSettings):
"""Specific settings for the Azure OpenAI Chat Completion endpoint."""
response_format: Optional[str] = None
extra_body: Optional[Union[Dict[str, Any], ExtraBody]] = None
@@ -0,0 +1,86 @@
import logging
from typing import Any, Dict, List, Literal, Optional, Union
from pydantic import Field, field_validator, model_validator
from semantic_kernel.connectors.ai.ai_request_settings import AIRequestSettings
from semantic_kernel.connectors.ai.open_ai.const import DEFAULT_CHAT_SYSTEM_PROMPT
logger = logging.getLogger(__name__)
class OpenAIRequestSettings(AIRequestSettings):
"""Common request settings for (Azure) OpenAI services."""
ai_model_id: Optional[str] = Field(None, serialization_alias="model")
frequency_penalty: float = Field(0.0, ge=-2.0, le=2.0)
logit_bias: Dict[str, float] = Field(default_factory=dict)
max_tokens: int = Field(256, gt=0)
number_of_responses: int = Field(1, ge=1, le=128, serialization_alias="n")
presence_penalty: float = Field(0.0, ge=-2.0, le=2.0)
seed: Optional[int] = None
stop: Optional[Union[str, List[str]]] = None
stream: bool = False
temperature: float = Field(0.0, ge=0.0, le=2.0)
top_p: float = Field(1.0, ge=0.0, le=1.0)
user: Optional[str] = None
class OpenAITextRequestSettings(OpenAIRequestSettings):
"""Specific settings for the completions endpoint."""
prompt: Optional[str] = None
best_of: Optional[int] = Field(None, ge=1)
echo: bool = False
logprobs: Optional[int] = Field(None, ge=0, le=5)
suffix: Optional[str] = None
@model_validator(mode="after")
def check_best_of_and_n(self) -> "OpenAITextRequestSettings":
"""Check that the best_of parameter is not greater than the number_of_responses parameter."""
if self.best_of is not None and self.best_of < self.number_of_responses:
raise ValueError(
"When used with number_of_responses, best_of controls the number of candidate completions and n specifies how many to return, therefore best_of must be greater than number_of_responses." # noqa: E501
)
if self.extension_data.get("best_of") is not None and self.extension_data.get(
"best_of"
) < self.extension_data.get("number_of_responses"):
raise ValueError(
"When used with number_of_responses, best_of controls the number of candidate completions and n specifies how many to return, therefore best_of must be greater than number_of_responses." # noqa: E501
)
return self
class OpenAIChatRequestSettings(OpenAIRequestSettings):
"""Specific settings for the Chat Completion endpoint."""
response_format: Optional[Literal["text", "json_object"]] = "text"
tools: Optional[List[Dict[str, Any]]] = None
tool_choice: Optional[str] = None
function_call: Optional[str] = None
functions: Optional[List[Dict[str, Any]]] = None
messages: Optional[List[Dict[str, Any]]] = [
{"role": "system", "content": DEFAULT_CHAT_SYSTEM_PROMPT}
]
@field_validator("functions", "function_call", mode="after")
@classmethod
def validate_function_call(
cls, v: Optional[Union[str, List[Dict[str, Any]]]] = None
):
if v is not None:
logger.warning(
"The function_call and functions parameters are deprecated. Please use the tool_choice and tools parameters instead." # noqa: E501
)
return v
class OpenAIEmbeddingRequestSettings(AIRequestSettings):
input: Optional[Union[str, List[str], List[int], List[List[int]]]] = None
ai_model_id: Optional[str] = Field(None, serialization_alias="model")
encoding_format: Optional[Literal["float", "base64"]] = None
user: Optional[str] = None
extra_headers: Optional[Dict] = None
extra_query: Optional[Dict] = None
extra_body: Optional[Dict] = None
timeout: Optional[float] = None
@@ -3,7 +3,6 @@
import logging
from typing import Any, Dict, List, Optional
from semantic_kernel.connectors.ai.open_ai.models.chat.function_call import FunctionCall
from semantic_kernel.connectors.ai.open_ai.models.chat.open_ai_chat_message import (
OpenAIChatMessage,
)
@@ -19,10 +18,10 @@ from semantic_kernel.template_engine.protocols.prompt_templating_engine import (
logger: logging.Logger = logging.getLogger(__name__)
class OpenAIChatPromptTemplate(ChatPromptTemplate):
class OpenAIChatPromptTemplate(ChatPromptTemplate[OpenAIChatMessage]):
def add_function_response_message(self, name: str, content: Any) -> None:
"""Add a function response message to the chat template."""
self._messages.append(
self.messages.append(
OpenAIChatMessage(role="function", name=name, fixed_content=str(content))
)
@@ -43,19 +42,26 @@ class OpenAIChatPromptTemplate(ChatPromptTemplate):
logger.warning("name is only used with role: function, ignoring")
name = None
function_call = kwargs.get("function_call")
if function_call is not None and role != "assistant":
logger.warning("function_call is only used with role: assistant, ignoring")
function_call = None
if function_call and not isinstance(function_call, FunctionCall):
logger.warning(
"function_call is not a FunctionCall, ignoring: %s", function_call
if function_call is not None:
if role == "assistant":
self.messages.append(
OpenAIChatMessage(
role=role,
fixed_content=message,
name=name,
function_call=function_call,
)
)
function_call = None
self._messages.append(
return
self._log.warning(
"function_call is only used with role: assistant, ignoring"
)
function_call = None
self.messages.append(
OpenAIChatMessage(
role=role,
content_template=PromptTemplate(
message, self._template_engine, self._prompt_config
message, self.template_engine, self.prompt_config
),
name=name,
function_call=function_call,
@@ -1,106 +0,0 @@
# Copyright (c) Microsoft. All rights reserved.
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional
from semantic_kernel.semantic_functions.prompt_template_config import (
PromptTemplateConfig,
)
@dataclass
class OpenAIChatPromptTemplateWithDataConfig(PromptTemplateConfig):
@dataclass
class CompletionConfig(PromptTemplateConfig.CompletionConfig):
inputLanguage: str = None
outputLanguage: str = None
data_source_settings: Optional[
"OpenAIChatPromptTemplateWithDataConfig.AzureChatWithDataSettings"
] = None
@dataclass
class AzureAISearchDataSourceParameters:
"""Class to hold Azure AI Search data source parameters."""
indexName: str
endpoint: str
key: Optional[str] = None
indexLanguage: Optional[str] = None
fieldsMapping: Dict[str, Any] = field(default_factory=dict)
inScope: Optional[bool] = True
topNDocuments: Optional[int] = 5
queryType: Optional[str] = "simple"
semanticConfiguration: Optional[str] = None
roleInformation: Optional[str] = None
embeddingDeploymentName: Optional[str] = None
@dataclass
class AzureAISearchDataSource:
"""Class to hold Azure AI Search data source."""
type: str = "AzureCognitiveSearch"
parameters: "OpenAIChatPromptTemplateWithDataConfig.AzureAISearchDataSourceParameters" = (
None
)
@dataclass
class AzureChatWithDataSettings:
"""Class to hold Azure OpenAI Chat With Data settings,
each data source has a type and parameters."""
dataSources: List[
"OpenAIChatPromptTemplateWithDataConfig.AzureAISearchDataSource"
] = field(default_factory=list)
@staticmethod
def from_dict(data: dict) -> "OpenAIChatPromptTemplateWithDataConfig":
config = super().from_dict(data)
completion_keys = ["inputLanguage", "outputLanguage"]
for comp_key in completion_keys:
if comp_key in data["completion"]:
setattr(config.completion, comp_key, data["completion"][comp_key])
if "data_source_settings" in data["completion"]:
config.completion.data_source_settings = (
OpenAIChatPromptTemplateWithDataConfig.AzureChatWithDataSettings()
)
config.completion.data_source_settings.dataSources = data["completion"][
"data_source_settings"
]["dataSources"]
return config
@staticmethod
def from_completion_parameters(
temperature: float = 0.0,
top_p: float = 1.0,
presence_penalty: float = 0.0,
frequency_penalty: float = 0.0,
max_tokens: int = 256,
number_of_responses: int = 1,
stop_sequences: List[str] = [],
token_selection_biases: Dict[int, int] = {},
chat_system_prompt: str = None,
function_call: Optional[str] = None,
inputLanguage: str = None,
outputLanguage: str = None,
data_source_settings: Optional[
"OpenAIChatPromptTemplateWithDataConfig.AzureChatWithDataSettings"
] = None,
) -> "OpenAIChatPromptTemplateWithDataConfig":
config = PromptTemplateConfig()
config.completion.temperature = temperature
config.completion.top_p = top_p
config.completion.presence_penalty = presence_penalty
config.completion.frequency_penalty = frequency_penalty
config.completion.max_tokens = max_tokens
config.completion.number_of_responses = number_of_responses
config.completion.stop_sequences = stop_sequences
config.completion.token_selection_biases = token_selection_biases
config.completion.chat_system_prompt = chat_system_prompt
config.completion.function_call = function_call
config.completion.inputLanguage = inputLanguage
config.completion.outputLanguage = outputLanguage
config.completion.data_source_settings = data_source_settings
return config
@@ -2,7 +2,6 @@
import logging
from dataclasses import asdict
from typing import (
Any,
AsyncGenerator,
@@ -18,32 +17,37 @@ from typing import (
from openai import AsyncAzureOpenAI
from openai.lib.azure import AsyncAzureADTokenProvider
from semantic_kernel.connectors.ai.chat_request_settings import ChatRequestSettings
from semantic_kernel.connectors.ai.ai_request_settings import AIRequestSettings
from semantic_kernel.connectors.ai.chat_completion_client_base import (
ChatCompletionClientBase,
)
# from semantic_kernel.connectors.ai.chat_request_settings import ChatRequestSettings
from semantic_kernel.connectors.ai.open_ai.const import DEFAULT_AZURE_API_VERSION
from semantic_kernel.connectors.ai.open_ai.models.chat.azure_chat_with_data_response import (
AzureChatWithDataStreamResponse,
)
from semantic_kernel.connectors.ai.open_ai.models.chat.function_call import FunctionCall
from semantic_kernel.connectors.ai.open_ai.request_settings.azure_chat_request_settings import (
AzureChatRequestSettings,
)
from semantic_kernel.connectors.ai.open_ai.services.azure_config_base import (
AzureOpenAIConfigBase,
)
from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion_base import (
OpenAIChatCompletionBase,
)
from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import (
OpenAIModelTypes,
)
from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion_base import (
OpenAITextCompletionBase,
)
from semantic_kernel.connectors.ai.open_ai.utils import _parse_message
from semantic_kernel.connectors.ai.open_ai.utils import _parse_choices, _parse_message
from semantic_kernel.sk_pydantic import HttpsUrl
logger: logging.Logger = logging.getLogger(__name__)
class AzureChatCompletion(
AzureOpenAIConfigBase, OpenAIChatCompletionBase, OpenAITextCompletionBase
AzureOpenAIConfigBase, ChatCompletionClientBase, OpenAITextCompletionBase
):
"""Azure Chat completion class."""
@@ -193,9 +197,9 @@ class AzureChatCompletion(
ad_token: Optional[str] = None,
ad_token_provider: Optional[AsyncAzureADTokenProvider] = None,
default_headers: Optional[Mapping[str, str]] = None,
log: Optional[Any] = None,
async_client: Optional[AsyncAzureOpenAI] = None,
use_extensions: bool = False,
log: Optional[Any] = None,
**kwargs,
) -> None:
"""
@@ -241,16 +245,15 @@ class AzureChatCompletion(
"The 'logger' argument is deprecated. Please use the `logging` module instead."
)
if use_extensions:
base_url = f"{str(endpoint).rstrip('/')}/openai/deployments/{deployment_name}/extensions"
if isinstance(endpoint, str):
endpoint = HttpsUrl(endpoint)
if isinstance(base_url, str):
if base_url and isinstance(base_url, str):
base_url = HttpsUrl(base_url)
if use_extensions and endpoint and deployment_name:
base_url = HttpsUrl(
f"{str(endpoint).rstrip('/')}/openai/deployments/{deployment_name}/extensions"
)
super().__init__(
deployment_name=deployment_name,
endpoint=endpoint,
endpoint=endpoint if not isinstance(endpoint, str) else HttpsUrl(endpoint),
base_url=base_url,
api_version=api_version,
api_key=api_key,
@@ -282,102 +285,87 @@ class AzureChatCompletion(
default_headers=settings.get("default_headers"),
)
async def complete_chat_with_data_async(
async def complete_chat_async(
self,
messages: List[Dict[str, str]],
request_settings: "ChatRequestSettings",
settings: AzureChatRequestSettings,
logger: Optional[Any] = None,
functions: Optional[List[Dict[str, Any]]] = None,
) -> Union[
Tuple[str, str],
List[Tuple[str, str]],
Tuple[Optional[str], Optional[str], Optional[FunctionCall]],
List[Tuple[Optional[str], Optional[str], Optional[FunctionCall]]],
Tuple[Optional[str], Optional[FunctionCall]],
List[Tuple[Optional[str], Optional[FunctionCall]]],
]:
"""Executes a chat completion request with data (and optionally functions) and returns the result.
"""Executes a chat completion request and returns the result.
Arguments:
messages {List[Tuple[str,str]]} -- The messages to use for the chat completion.
request_settings {ChatRequestSettings} -- The settings to use for the chat completion request.
logger -- The logger instance to use. (Optional)
functions {Optional[List[Dict[str, Any]]]} -- The functions to use for the chat completion. (Optional)
Returns:
The completion result(s) in the format (assistant_message, tool_message) or
(assistant_message, tool_message, function_call).
The tool message contains additional information about the data source including citations.
"""
response = await self._send_request(
messages=messages,
request_settings=request_settings,
stream=False,
functions=functions,
)
if len(response.choices) == 1:
return _parse_message(response.choices[0].message, with_data=True)
else:
return [_parse_message(choice.message) for choice in response.choices]
async def complete_chat_stream_with_data_async(
self,
messages: List[Dict[str, str]],
settings: "ChatRequestSettings",
logger: Optional[Any] = None,
) -> AsyncGenerator[Union[str, List[str]], None]:
"""Executes a chat completion request with data and returns the result.
Arguments:
messages {List[Tuple[str,str]]} -- The messages to use for the chat completion.
settings {ChatRequestSettings} -- The settings to use for the chat completion request.
logger -- The logger instance to use. (Optional)
settings {OpenAIRequestSettings} -- The settings to use for the chat completion request.
logger {Optional[Logger]} -- The logger instance to use. (Optional)
Returns:
Union[str, List[str]] -- The completion result(s).
"""
response = await self._send_request(
messages=messages, request_settings=settings, stream=True
)
settings.messages = messages
settings.stream = False
if settings.ai_model_id is None:
settings.ai_model_id = self.ai_model_id
response = await self._send_request(request_settings=settings)
return AzureChatWithDataStreamResponse(response, settings)
if len(response.choices) == 1:
return _parse_message(
response.choices[0].message,
with_data=settings.extra_body is not None,
)
else:
return [
_parse_message(
choice.message,
with_data=settings.extra_body is not None,
)
for choice in response.choices
]
def _create_model_args(
self, request_settings, stream, prompt, messages, functions, chat_mode
):
model_args = super()._create_model_args(
request_settings, stream, prompt, messages, functions, chat_mode
)
async def complete_chat_stream_async(
self,
messages: List[Dict[str, str]],
settings: AzureChatRequestSettings,
logger: Optional[Any] = None,
) -> Union[
AsyncGenerator[Union[str, List[str]], None], AzureChatWithDataStreamResponse
]:
"""Executes a chat completion request and returns the result.
if (
hasattr(request_settings, "data_source_settings")
and request_settings.data_source_settings is not None
):
model_args["extra_body"] = asdict(request_settings.data_source_settings)
Arguments:
messages {List[Tuple[str,str]]} -- The messages to use for the chat completion.
settings {OpenAIRequestSettings} -- The settings to use for the chat completion request.
logger {Optional[Logger]} -- The logger instance to use. (Optional)
# Remove embeddingDeploymentName if not using vector search.
if (
model_args["extra_body"]["dataSources"][0]["parameters"][
"embeddingDeploymentName"
]
is None
):
del model_args["extra_body"]["dataSources"][0]["parameters"][
"embeddingDeploymentName"
]
Returns:
Union[str, List[str]] -- The completion result(s).
"""
settings.messages = messages
settings.stream = True
if settings.ai_model_id is None:
settings.ai_model_id = self.ai_model_id
response = await self._send_request(request_settings=settings)
if settings.extra_body is not None:
yield AzureChatWithDataStreamResponse(response, settings)
else:
# parse the completion text(s) and yield them
async for chunk in response:
if len(chunk.choices) == 0:
continue
# if multiple responses are requested, keep track of them
if settings.number_of_responses > 1:
completions = [""] * settings.number_of_responses
for choice in chunk.choices:
text, index = _parse_choices(choice)
completions[index] = text
yield completions
# if only one response is requested, yield it
else:
text, index = _parse_choices(chunk.choices[0])
yield text
if request_settings.inputLanguage is not None:
model_args["extra_body"][
"inputLanguage"
] = request_settings.inputLanguage
if request_settings.outputLanguage is not None:
model_args["extra_body"][
"outputLanguage"
] = request_settings.outputLanguage
# Remove args that are not supported by the with-data extensions API (yet).
del model_args["n"]
del model_args["logit_bias"]
del model_args["presence_penalty"]
del model_args["frequency_penalty"]
return model_args
def get_request_settings_class(self) -> "AIRequestSettings":
"""Create a request settings object."""
return AzureChatRequestSettings
@@ -90,7 +90,7 @@ class AzureOpenAIConfigBase(OpenAIHandler):
"Please provide either base_url or endpoint",
)
async_client = AsyncAzureOpenAI(
azure_endpoint=endpoint,
azure_endpoint=str(endpoint),
azure_deployment=deployment_name,
api_version=api_version,
api_key=api_key,
@@ -1,12 +1,28 @@
# Copyright (c) Microsoft. All rights reserved.
import logging
from typing import Any, Dict, Mapping, Optional, overload
from typing import (
Any,
AsyncGenerator,
Dict,
List,
Mapping,
Optional,
Tuple,
Union,
overload,
)
from openai import AsyncOpenAI
from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion_base import (
OpenAIChatCompletionBase,
from semantic_kernel.connectors.ai.ai_request_settings import AIRequestSettings
from semantic_kernel.connectors.ai.chat_completion_client_base import (
ChatCompletionClientBase,
)
from semantic_kernel.connectors.ai.open_ai.models.chat.function_call import FunctionCall
from semantic_kernel.connectors.ai.open_ai.request_settings.open_ai_request_settings import (
OpenAIChatRequestSettings,
OpenAIRequestSettings,
)
from semantic_kernel.connectors.ai.open_ai.services.open_ai_config_base import (
OpenAIConfigBase,
@@ -17,12 +33,13 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import (
from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion_base import (
OpenAITextCompletionBase,
)
from semantic_kernel.connectors.ai.open_ai.utils import _parse_choices, _parse_message
logger: logging.Logger = logging.getLogger(__name__)
class OpenAIChatCompletion(
OpenAIConfigBase, OpenAIChatCompletionBase, OpenAITextCompletionBase
OpenAIConfigBase, ChatCompletionClientBase, OpenAITextCompletionBase
):
"""OpenAI Chat completion class."""
@@ -142,3 +159,75 @@ class OpenAIChatCompletion(
org_id=settings.get("org_id"),
default_headers=settings.get("default_headers"),
)
async def complete_chat_async(
self,
messages: List[Dict[str, str]],
settings: OpenAIRequestSettings,
**kwargs,
) -> Union[
Tuple[Optional[str], Optional[FunctionCall]],
List[Tuple[Optional[str], Optional[FunctionCall]]],
]:
"""Executes a chat completion request and returns the result.
Arguments:
messages {List[Tuple[str,str]]} -- The messages to use for the chat completion.
settings {OpenAIRequestSettings} -- The settings to use for the chat completion request.
logger {Optional[Logger]} -- The logger instance to use. (Deprecated)
Returns:
Union[str, List[str]] -- The completion result(s).
"""
settings.messages = messages
settings.stream = False
if settings.ai_model_id is None:
settings.ai_model_id = self.ai_model_id
response = await self._send_request(request_settings=settings)
if len(response.choices) == 1:
return _parse_message(response.choices[0].message)
else:
return [_parse_message(choice.message) for choice in response.choices]
async def complete_chat_stream_async(
self,
messages: List[Dict[str, str]],
settings: OpenAIRequestSettings,
**kwargs,
) -> AsyncGenerator[Union[str, List[str]], None]:
"""Executes a chat completion request and returns the result.
Arguments:
messages {List[Tuple[str,str]]} -- The messages to use for the chat completion.
settings {OpenAIRequestSettings} -- The settings to use for the chat completion request.
logger {Optional[Logger]} -- The logger instance to use. (Deprecated)
Returns:
Union[str, List[str]] -- The completion result(s).
"""
settings.messages = messages
settings.stream = True
if settings.ai_model_id is None:
settings.ai_model_id = self.ai_model_id
response = await self._send_request(request_settings=settings)
# parse the completion text(s) and yield them
async for chunk in response:
if len(chunk.choices) == 0:
continue
# if multiple responses are requested, keep track of them
if settings.number_of_responses > 1:
completions = [""] * settings.number_of_responses
for choice in chunk.choices:
text, index = _parse_choices(choice)
completions[index] = text
yield completions
# if only one response is requested, yield it
else:
text, index = _parse_choices(chunk.choices[0])
yield text
def get_request_settings_class(self) -> "AIRequestSettings":
"""Create a request settings object."""
return OpenAIChatRequestSettings
@@ -1,138 +0,0 @@
# Copyright (c) Microsoft. All rights reserved.
import logging
from typing import (
TYPE_CHECKING,
Any,
AsyncGenerator,
Dict,
List,
Optional,
Tuple,
Union,
)
from semantic_kernel.connectors.ai import ChatCompletionClientBase
from semantic_kernel.connectors.ai.open_ai.models.chat.function_call import FunctionCall
from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import (
OpenAIHandler,
)
from semantic_kernel.connectors.ai.open_ai.utils import _parse_message
if TYPE_CHECKING:
from semantic_kernel.connectors.ai.chat_request_settings import ChatRequestSettings
logger: logging.Logger = logging.getLogger(__name__)
class OpenAIChatCompletionBase(OpenAIHandler, ChatCompletionClientBase):
async def complete_chat_async(
self,
messages: List[Dict[str, str]],
settings: "ChatRequestSettings",
**kwargs,
) -> Union[str, List[str]]:
"""Executes a chat completion request and returns the result.
Arguments:
messages {List[Tuple[str,str]]} -- The messages to use for the chat completion.
settings {ChatRequestSettings} -- The settings to use for the chat completion request.
Returns:
Union[str, List[str]] -- The completion result(s).
"""
if kwargs.get("logger"):
logger.warning(
"The `logger` parameter is deprecated. Please use the `logging` module instead."
)
response = await self._send_request(
messages=messages, request_settings=settings, stream=False
)
if len(response.choices) == 1:
return response.choices[0].message.content
else:
return [choice.message.content for choice in response.choices]
async def complete_chat_with_functions_async(
self,
messages: List[Dict[str, str]],
functions: List[Dict[str, Any]],
request_settings: "ChatRequestSettings",
**kwargs,
) -> Union[
Tuple[Optional[str], Optional[FunctionCall]],
List[Tuple[Optional[str], Optional[FunctionCall]]],
]:
"""Executes a chat completion request and returns the result.
Arguments:
messages {List[Tuple[str,str]]} -- The messages to use for the chat completion.
functions {List[Dict[str, Any]]} -- The functions to use for the chat completion.
settings {ChatRequestSettings} -- The settings to use for the chat completion request.
Returns:
Union[str, List[str]] -- The completion result(s).
"""
if kwargs.get("logger"):
logger.warning(
"The `logger` parameter is deprecated. Please use the `logging` module instead."
)
response = await self._send_request(
messages=messages,
request_settings=request_settings,
stream=False,
functions=functions,
)
if len(response.choices) == 1:
return _parse_message(response.choices[0].message)
else:
return [_parse_message(choice.message) for choice in response.choices]
async def complete_chat_stream_async(
self,
messages: List[Dict[str, str]],
settings: "ChatRequestSettings",
**kwargs,
) -> AsyncGenerator[Union[str, List[str]], None]:
"""Executes a chat completion request and returns the result.
Arguments:
messages {List[Tuple[str,str]]} -- The messages to use for the chat completion.
settings {ChatRequestSettings} -- The settings to use for the chat completion request.
Returns:
Union[str, List[str]] -- The completion result(s).
"""
if kwargs.get("logger"):
logger.warning(
"The `logger` parameter is deprecated. Please use the `logging` module instead."
)
response = await self._send_request(
messages=messages, request_settings=settings, stream=True
)
# parse the completion text(s) and yield them
async for chunk in response:
if len(chunk.choices) == 0:
continue
# if multiple responses are requested, keep track of them
if settings.number_of_responses > 1:
completions = [""] * settings.number_of_responses
for choice in chunk.choices:
text, index = self._parse_choices(choice)
completions[index] = text
yield completions
# if only one response is requested, yield it
else:
text, index = self._parse_choices(chunk.choices[0])
yield text
@staticmethod
def _parse_choices(choice) -> Tuple[str, int]:
message = ""
if choice.delta.content:
message += choice.delta.content
return message, choice.index
@@ -1,20 +1,21 @@
# Copyright (c) Microsoft. All rights reserved.
import logging
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional, Union
from abc import ABC
from typing import List, Union
from numpy import array, ndarray
from numpy import array
from openai import AsyncOpenAI, AsyncStream
from openai.types import Completion
from openai.types.chat import ChatCompletion, ChatCompletionChunk
from pydantic import Field
from semantic_kernel.connectors.ai.ai_exception import AIException
from semantic_kernel.connectors.ai.ai_request_settings import AIRequestSettings
from semantic_kernel.connectors.ai.ai_service_client_base import AIServiceClientBase
from semantic_kernel.connectors.ai.chat_request_settings import ChatRequestSettings
from semantic_kernel.connectors.ai.complete_request_settings import (
CompleteRequestSettings,
from semantic_kernel.connectors.ai.open_ai.request_settings.open_ai_request_settings import (
OpenAIEmbeddingRequestSettings,
OpenAIRequestSettings,
)
from semantic_kernel.connectors.ai.open_ai.services.open_ai_model_types import (
OpenAIModelTypes,
@@ -34,11 +35,7 @@ class OpenAIHandler(AIServiceClientBase, ABC):
async def _send_request(
self,
request_settings: Union[CompleteRequestSettings, ChatRequestSettings],
prompt: Optional[str] = None,
messages: Optional[List[Dict[str, str]]] = None,
stream: bool = False,
functions: Optional[List[Dict[str, Any]]] = None,
request_settings: OpenAIRequestSettings,
) -> Union[
ChatCompletion,
Completion,
@@ -52,146 +49,42 @@ class OpenAIHandler(AIServiceClientBase, ABC):
Arguments:
prompt {str} -- The prompt to complete.
messages {List[Tuple[str, str]]} -- A list of tuples, where each tuple is a role and content set.
request_settings {CompleteRequestSettings} -- The request settings.
request_settings {OpenAIRequestSettings} -- The request settings.
stream {bool} -- Whether to stream the response.
Returns:
ChatCompletion, Completion, AsyncStream[Completion | ChatCompletionChunk] -- The completion response.
"""
chat_mode = self.ai_model_type == OpenAIModelTypes.CHAT
self._validate_request(request_settings, prompt, messages, chat_mode)
model_args = self._create_model_args(
request_settings,
stream,
prompt,
messages,
functions,
chat_mode,
)
try:
response = await (
self.client.chat.completions.create(**model_args)
if chat_mode
else self.client.completions.create(**model_args)
self.client.chat.completions.create(
**request_settings.prepare_settings_dict()
)
if self.ai_model_type == OpenAIModelTypes.CHAT
else self.client.completions.create(
**request_settings.prepare_settings_dict()
)
)
self.store_usage(response)
return response
except Exception as ex:
raise AIException(
AIException.ErrorCodes.ServiceError,
f"{type(self)} service failed to complete the prompt",
ex,
) from ex
if not isinstance(response, AsyncStream):
logger.info(f"OpenAI usage: {response.usage}")
self.prompt_tokens += response.usage.prompt_tokens
self.completion_tokens += response.usage.completion_tokens
self.total_tokens += response.usage.total_tokens
return response
def _validate_request(self, request_settings, prompt, messages, chat_mode):
"""Validate the request, check if the settings are present and valid."""
try:
assert (
self.ai_model_type != OpenAIModelTypes.EMBEDDING
), "The model type is not supported for this operation, please use a text or chat model"
except AssertionError as exc:
raise AIException(
AIException.ErrorCodes.FunctionTypeNotSupported, exc.args[0], exc
) from exc
try:
assert request_settings, "The request settings cannot be `None`"
assert (
request_settings.max_tokens >= 1
), f"The max tokens must be greater than 0, but was {request_settings.max_tokens}"
if chat_mode:
assert (
prompt or messages
), "The messages cannot be `None` or empty, please use either prompt or messages"
if not chat_mode:
assert prompt, "The prompt cannot be `None` or empty"
except AssertionError as exc:
raise AIException(
AIException.ErrorCodes.InvalidRequest, exc.args[0], exc
) from exc
def _create_model_args(
self,
request_settings,
stream,
prompt,
messages,
functions,
chat_mode,
):
model_args = self.get_model_args()
model_args.update(
{
"stream": stream,
"temperature": request_settings.temperature,
"top_p": request_settings.top_p,
"stop": (
request_settings.stop_sequences
if request_settings.stop_sequences is not None
and len(request_settings.stop_sequences) > 0
else None
),
"max_tokens": request_settings.max_tokens,
"presence_penalty": request_settings.presence_penalty,
"frequency_penalty": request_settings.frequency_penalty,
"logit_bias": (
request_settings.token_selection_biases
if request_settings.token_selection_biases is not None
and len(request_settings.token_selection_biases) > 0
else {}
),
"n": request_settings.number_of_responses,
}
)
if not chat_mode:
model_args["prompt"] = prompt
if hasattr(request_settings, "logprobs"):
model_args["logprobs"] = request_settings.logprobs
return model_args
model_args["messages"] = messages or [{"role": "user", "content": prompt}]
if functions and request_settings.function_call is not None:
model_args["function_call"] = request_settings.function_call
if request_settings.function_call != "auto":
model_args["functions"] = [
func
for func in functions
if func["name"] == request_settings.function_call
]
else:
model_args["functions"] = functions
return model_args
async def _send_embedding_request(
self, texts: List[str], batch_size: Optional[int] = None
) -> ndarray:
if self.ai_model_type != OpenAIModelTypes.EMBEDDING:
raise AIException(
AIException.ErrorCodes.FunctionTypeNotSupported,
"The model type is not supported for this operation, please use an embedding model",
)
model_args = self.get_model_args()
self, settings: OpenAIEmbeddingRequestSettings
) -> List[array]:
try:
raw_embeddings = []
batch_size = batch_size or len(texts)
for i in range(0, len(texts), batch_size):
batch = texts[i : i + batch_size] # noqa: E203
response = await self.client.embeddings.create(
input=batch,
**model_args,
)
# make numpy arrays from the response
# TODO: the openai response is cast to a list[float], could be used instead of nparray
raw_embeddings.extend([array(x.embedding) for x in response.data])
if response.usage:
logger.info(f"OpenAI usage: {response.usage}")
self.prompt_tokens += response.usage.prompt_tokens
self.total_tokens += response.usage.total_tokens
return array(raw_embeddings)
response = await self.client.embeddings.create(
**settings.prepare_settings_dict()
)
self.store_usage(response)
# make numpy arrays from the response
# TODO: the openai response is cast to a list[float], could be used instead of nparray
return [array(x.embedding) for x in response.data]
except Exception as ex:
raise AIException(
AIException.ErrorCodes.ServiceError,
@@ -199,6 +92,14 @@ class OpenAIHandler(AIServiceClientBase, ABC):
ex,
) from ex
@abstractmethod
def get_model_args(self) -> Dict[str, Any]:
"""Return the model args for the specific openai api."""
def store_usage(self, response):
if not isinstance(response, AsyncStream):
logger.info(f"OpenAI usage: {response.usage}")
self.prompt_tokens += response.usage.prompt_tokens
self.total_tokens += response.usage.total_tokens
if hasattr(response.usage, "completion_tokens"):
self.completion_tokens += response.usage.completion_tokens
def get_request_settings_class(self) -> "AIRequestSettings":
"""Return the class with the applicable request settings."""
return OpenAIRequestSettings
@@ -6,13 +6,17 @@ from typing import TYPE_CHECKING, AsyncGenerator, List, Union
from openai.types.completion import Completion
from semantic_kernel.connectors.ai import TextCompletionClientBase
from semantic_kernel.connectors.ai.ai_request_settings import AIRequestSettings
from semantic_kernel.connectors.ai.open_ai.request_settings.open_ai_request_settings import (
OpenAITextRequestSettings,
)
from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import (
OpenAIHandler,
)
if TYPE_CHECKING:
from semantic_kernel.connectors.ai.complete_request_settings import (
CompleteRequestSettings,
from semantic_kernel.connectors.ai.open_ai.request_settings.open_ai_request_settings import (
OpenAIRequestSettings,
)
logger: logging.Logger = logging.getLogger(__name__)
@@ -22,25 +26,25 @@ class OpenAITextCompletionBase(TextCompletionClientBase, OpenAIHandler):
async def complete_async(
self,
prompt: str,
settings: "CompleteRequestSettings",
settings: "OpenAIRequestSettings",
**kwargs,
) -> Union[str, List[str]]:
"""Executes a completion request and returns the result.
Arguments:
prompt {str} -- The prompt to use for the completion request.
settings {CompleteRequestSettings} -- The settings to use for the completion request.
settings {OpenAIRequestSettings} -- The settings to use for the completion request.
Returns:
Union[str, List[str]] -- The completion result(s).
"""
if kwargs.get("logger"):
logger.warning(
"The `logger` parameter is deprecated. Please use the `logging` module instead."
)
response = await self._send_request(
prompt=prompt, request_settings=settings, stream=False
)
if isinstance(settings, OpenAITextRequestSettings):
settings.prompt = prompt
else:
settings.messages = [{"role": "user", "content": prompt}]
if settings.ai_model_id is None:
settings.ai_model_id = self.ai_model_id
response = await self._send_request(request_settings=settings)
if isinstance(response, Completion):
if len(response.choices) == 1:
@@ -53,7 +57,7 @@ class OpenAITextCompletionBase(TextCompletionClientBase, OpenAIHandler):
async def complete_stream_async(
self,
prompt: str,
settings: "CompleteRequestSettings",
settings: "OpenAIRequestSettings",
**kwargs,
) -> AsyncGenerator[Union[str, List[str]], None]:
"""
@@ -62,18 +66,21 @@ class OpenAITextCompletionBase(TextCompletionClientBase, OpenAIHandler):
Arguments:
prompt {str} -- The prompt to use for the completion request.
settings {CompleteRequestSettings} -- The settings to use for the completion request.
settings {OpenAIRequestSettings} -- The settings to use for the completion request.
Returns:
Union[str, List[str]] -- The completion result(s).
"""
if kwargs.get("logger"):
logger.warning(
"The `logger` parameter is deprecated. Please use the `logging` module instead."
)
response = await self._send_request(
prompt=prompt, request_settings=settings, stream=True
)
if "prompt" in settings.model_fields:
settings.prompt = prompt
if "messages" in settings.model_fields:
if not settings.messages:
settings.messages = [{"role": "user", "content": prompt}]
else:
settings.messages.append({"role": "user", "content": prompt})
settings.ai_model_id = self.ai_model_id
settings.stream = True
response = await self._send_request(request_settings=settings)
async for partial in response:
if len(partial.choices) == 0:
@@ -101,3 +108,7 @@ class OpenAITextCompletionBase(TextCompletionClientBase, OpenAIHandler):
text = partial.choices[0].text
if text.strip(): # Exclude empty or whitespace-only text
yield text
def get_request_settings_class(self) -> "AIRequestSettings":
"""Create a request settings object."""
return OpenAITextRequestSettings
@@ -1,12 +1,16 @@
# Copyright (c) Microsoft. All rights reserved.
from typing import List, Optional
from typing import Any, Dict, List, Optional
from numpy import ndarray
from numpy import array, ndarray
from semantic_kernel.connectors.ai.ai_request_settings import AIRequestSettings
from semantic_kernel.connectors.ai.embeddings.embedding_generator_base import (
EmbeddingGeneratorBase,
)
from semantic_kernel.connectors.ai.open_ai.request_settings.open_ai_request_settings import (
OpenAIEmbeddingRequestSettings,
)
from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import (
OpenAIHandler,
)
@@ -14,16 +18,37 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import (
class OpenAITextEmbeddingBase(OpenAIHandler, EmbeddingGeneratorBase):
async def generate_embeddings_async(
self, texts: List[str], batch_size: Optional[int] = None
self,
texts: List[str],
batch_size: Optional[int] = None,
**kwargs: Dict[str, Any]
) -> ndarray:
"""Generates embeddings for the given texts.
Arguments:
texts {List[str]} -- The texts to generate embeddings for.
batch_size {Optional[int]} -- The batch size to use for the request.
kwargs {Dict[str, Any]} -- Additional arguments to pass to the request,
see OpenAIEmbeddingRequestSettings for the details.
Returns:
ndarray -- The embeddings for the text.
"""
return await self._send_embedding_request(texts, batch_size)
settings = OpenAIEmbeddingRequestSettings(
ai_model_id=self.ai_model_id,
**kwargs,
)
raw_embeddings = []
batch_size = batch_size or len(texts)
for i in range(0, len(texts), batch_size):
batch = texts[i : i + batch_size] # noqa: E203
settings.input = batch
raw_embedding = await self._send_embedding_request(
settings=settings,
)
raw_embeddings.extend(raw_embedding)
return array(raw_embeddings)
def get_request_settings_class(self) -> AIRequestSettings:
return OpenAIEmbeddingRequestSettings
@@ -1,7 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
import logging
from typing import Any, Dict, List, Optional, Tuple, Union
from typing import Any, Dict, List, Optional, Tuple
from openai.types.chat import ChatCompletion
@@ -120,7 +120,6 @@ async def execute_function_call(
async def chat_completion_with_function_call(
kernel: Kernel,
context: SKContext,
functions: List[Dict[str, str]] = [],
chat_skill_name: Optional[str] = None,
chat_function_name: Optional[str] = None,
chat_function: Optional[SKFunctionBase] = None,
@@ -170,10 +169,13 @@ async def chat_completion_with_function_call(
assert isinstance(
chat_function._chat_prompt_template, OpenAIChatPromptTemplate
), "Please make sure to initialize your chat function with the OpenAIChatPromptTemplate class."
settings = chat_function._chat_prompt_template.prompt_config.completion
if current_call_count >= max_function_calls:
settings.functions = []
context = await chat_function.invoke_async(
context=context,
# when the maximum number of function calls is reached, execute the chat function without Functions.
functions=[] if current_call_count >= max_function_calls else functions,
settings=settings,
)
function_call = context.objects.pop("function_call", None)
# if there is no function_call or if the content is not a FunctionCall object, return the context
@@ -188,7 +190,6 @@ async def chat_completion_with_function_call(
return await chat_completion_with_function_call(
kernel,
chat_function=chat_function,
functions=functions,
context=context,
max_function_calls=max_function_calls,
current_call_count=current_call_count + 1,
@@ -196,11 +197,8 @@ async def chat_completion_with_function_call(
def _parse_message(
message: ChatCompletion, with_data: bool = False, **kwargs
) -> Union[
Tuple[Optional[str], Optional[FunctionCall]],
Tuple[Optional[str], Optional[str], Optional[FunctionCall]],
]:
message: ChatCompletion, with_data: bool = False
) -> Tuple[Optional[str], Optional[str], Optional[FunctionCall]]:
"""
Parses the message.
@@ -210,10 +208,6 @@ def _parse_message(
Returns:
Tuple[Optional[str], Optional[Dict]] -- The parsed message.
"""
if kwargs.get("logger"):
logger.warning(
"The `logger` parameter is deprecated. Please use the `logging` module instead."
)
content = message.content if hasattr(message, "content") else None
function_call = message.function_call if hasattr(message, "function_call") else None
if function_call:
@@ -223,7 +217,7 @@ def _parse_message(
)
if not with_data:
return (content, function_call)
return (content, None, function_call)
else:
tool_content = None
if message.model_extra and "context" in message.model_extra:
@@ -232,3 +226,11 @@ def _parse_message(
tool_content = m.get("content", None)
break
return (content, tool_content, function_call)
def _parse_choices(choice) -> Tuple[str, int]:
message = ""
if choice.delta.content:
message += choice.delta.content
return message, choice.index
@@ -5,9 +5,7 @@ from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, AsyncGenerator, List, Optional, Union
if TYPE_CHECKING:
from semantic_kernel.connectors.ai.complete_request_settings import (
CompleteRequestSettings,
)
from semantic_kernel.connectors.ai.ai_request_settings import AIRequestSettings
class TextCompletionClientBase(ABC):
@@ -17,7 +15,7 @@ class TextCompletionClientBase(ABC):
async def complete_async(
self,
prompt: str,
settings: "CompleteRequestSettings",
settings: "AIRequestSettings",
logger: Optional[Any] = None,
) -> Union[str, List[str]]:
"""
@@ -25,7 +23,8 @@ class TextCompletionClientBase(ABC):
Arguments:
prompt {str} -- The prompt to send to the LLM.
settings {CompleteRequestSettings} -- Settings for the request.
settings {AIRequestSettings} -- Settings for the request.
logger {Logger} -- A logger to use for logging (deprecated).
Returns:
Union[str, List[str]] -- A string or list of strings representing the response(s) from the LLM.
@@ -35,7 +34,7 @@ class TextCompletionClientBase(ABC):
async def complete_stream_async(
self,
prompt: str,
settings: "CompleteRequestSettings",
settings: "AIRequestSettings",
logger: Optional[Any] = None,
) -> AsyncGenerator[Union[str, List[str]], None]:
"""
@@ -43,7 +42,8 @@ class TextCompletionClientBase(ABC):
Arguments:
prompt {str} -- The prompt to send to the LLM.
settings {CompleteRequestSettings} -- Settings for the request.
settings {AIRequestSettings} -- Settings for the request.
logger {Logger} -- A logger to use for logging (deprecated).
Yields:
A stream representing the response(s) from the LLM.
+25 -29
View File
@@ -9,13 +9,10 @@ from typing import Any, Callable, Dict, List, Optional, Type, TypeVar, Union
from uuid import uuid4
from semantic_kernel.connectors.ai.ai_exception import AIException
from semantic_kernel.connectors.ai.ai_request_settings import AIRequestSettings
from semantic_kernel.connectors.ai.chat_completion_client_base import (
ChatCompletionClientBase,
)
from semantic_kernel.connectors.ai.chat_request_settings import ChatRequestSettings
from semantic_kernel.connectors.ai.complete_request_settings import (
CompleteRequestSettings,
)
from semantic_kernel.connectors.ai.embeddings.embedding_generator_base import (
EmbeddingGeneratorBase,
)
@@ -224,11 +221,9 @@ class Kernel:
raise ValueError("No functions passed to run")
try:
completion = ""
async for stream_message in stream_function.invoke_stream_async(
input=None, context=context
):
completion += stream_message
yield stream_message
except Exception as ex:
@@ -483,6 +478,18 @@ class Kernel:
return skill
def get_request_settings_from_service(
self, type: Type[T], service_id: Optional[str] = None
) -> AIRequestSettings:
"""Get the specific request settings from the service, instantiated with the service_id and ai_model_id."""
service = self.get_ai_service(type, service_id)
service_instance = service.__closure__[0].cell_contents
req_settings_type = service_instance.get_request_settings_class()
return req_settings_type(
service_id=service_id,
extension_data={"ai_model_id": service_instance.ai_model_id},
)
def get_ai_service(
self, type: Type[T], service_id: Optional[str] = None
) -> Callable[["Kernel"], T]:
@@ -561,8 +568,6 @@ class Kernel:
if isinstance(service, TextCompletionClientBase):
self.add_text_completion_service(service_id, service)
if self._default_text_completion_service is None:
self._default_text_completion_service = service_id
return self
@@ -728,7 +733,7 @@ class Kernel:
function = SKFunction.from_semantic_config(
skill_name, function_name, function_config
)
function.request_settings.update_from_completion_config(
function.request_settings.update_from_ai_request_settings(
function_config.prompt_template_config.completion
)
@@ -744,9 +749,12 @@ class Kernel:
if len(function_config.prompt_template_config.default_services) > 0
else None,
)
req_settings_type = service.__closure__[
0
].cell_contents.get_request_settings_class()
function.set_chat_configuration(
ChatRequestSettings.from_completion_config(
req_settings_type.from_ai_request_settings(
function_config.prompt_template_config.completion
)
)
@@ -769,9 +777,12 @@ class Kernel:
if len(function_config.prompt_template_config.default_services) > 0
else None,
)
req_settings_type = service.__closure__[
0
].cell_contents.get_request_settings_class()
function.set_ai_configuration(
CompleteRequestSettings.from_completion_config(
req_settings_type.from_ai_request_settings(
function_config.prompt_template_config.completion
)
)
@@ -853,10 +864,9 @@ class Kernel:
if not os.path.exists(prompt_path):
continue
config = PromptTemplateConfig()
config_path = os.path.join(directory, CONFIG_FILE)
with open(config_path, "r") as config_file:
config = config.from_json(config_file.read())
config = PromptTemplateConfig.from_json(config_file.read())
# Load Prompt Template
with open(prompt_path, "r") as prompt_file:
@@ -879,13 +889,7 @@ class Kernel:
function_name: Optional[str] = None,
skill_name: Optional[str] = None,
description: Optional[str] = None,
max_tokens: int = 256,
temperature: float = 0.0,
top_p: float = 1.0,
presence_penalty: float = 0.0,
frequency_penalty: float = 0.0,
number_of_responses: int = 1,
stop_sequences: Optional[List[str]] = None,
**kwargs: Any,
) -> "SKFunctionBase":
function_name = (
function_name
@@ -900,15 +904,7 @@ class Kernel:
else "Generic function, unknown purpose"
),
type="completion",
completion=PromptTemplateConfig.CompletionConfig(
temperature,
top_p,
presence_penalty,
frequency_penalty,
max_tokens,
number_of_responses,
stop_sequences if stop_sequences is not None else [],
),
completion=AIRequestSettings(extension_data=kwargs),
)
validate_function_name(function_name)
@@ -33,7 +33,8 @@ class ChatMessage(SKBaseModel):
Subsequent calls will do nothing.
"""
if self.fixed_content is None:
self.fixed_content = await self.content_template.render_async(context)
if self.content_template is not None:
self.fixed_content = await self.content_template.render_async(context)
def as_dict(self) -> Dict[str, str]:
"""Return the message as a dict.
@@ -6,15 +6,12 @@ import platform
import sys
from concurrent.futures import ThreadPoolExecutor
from enum import Enum
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
from semantic_kernel.connectors.ai.ai_request_settings import AIRequestSettings
from semantic_kernel.connectors.ai.chat_completion_client_base import (
ChatCompletionClientBase,
)
from semantic_kernel.connectors.ai.chat_request_settings import ChatRequestSettings
from semantic_kernel.connectors.ai.complete_request_settings import (
CompleteRequestSettings,
)
from semantic_kernel.connectors.ai.text_completion_client_base import (
TextCompletionClientBase,
)
@@ -55,10 +52,8 @@ class SKFunction(SKFunctionBase):
_delegate_type: DelegateTypes
_function: Callable[..., Any]
_skill_collection: Optional[ReadOnlySkillCollectionBase]
_ai_service: Optional[TextCompletionClientBase]
_ai_request_settings: CompleteRequestSettings
_chat_service: Optional[ChatCompletionClientBase]
_chat_request_settings: ChatRequestSettings
_ai_service: Optional[Union[TextCompletionClientBase, ChatCompletionClientBase]]
_ai_request_settings: AIRequestSettings
_chat_prompt_template: ChatPromptTemplate
@staticmethod
@@ -149,71 +144,27 @@ class SKFunction(SKFunctionBase):
# Similar to non-chat, render prompt (which renders to a
# dict of <role, content, name> messages)
messages = await as_chat_prompt.render_messages_async(context)
functions = (
kwargs.get("functions")
if request_settings.function_call is not None
else None
)
if request_settings.function_call is not None and functions is None:
logger.warning("Function call is not None, but functions is None")
try:
if functions and hasattr(client, "complete_chat_with_functions_async"):
if (
hasattr(client, "complete_chat_with_data_async")
and hasattr(request_settings, "data_source_settings")
and request_settings.data_source_settings is not None
):
(
completion,
tool_message,
function_call,
) = await client.complete_chat_with_data_async(
messages, request_settings, functions=functions
)
if tool_message:
context.objects["tool_message"] = tool_message
as_chat_prompt.add_message(
role="tool", message=tool_message
)
else:
(
completion,
function_call,
) = await client.complete_chat_with_functions_async(
messages, functions, request_settings
)
as_chat_prompt.add_message(
"assistant", message=completion, function_call=function_call
)
if completion is not None:
context.variables.update(completion)
if function_call is not None:
context.objects["function_call"] = function_call
result = await client.complete_chat_async(messages, request_settings)
if isinstance(result, list):
# TODO: handle multiple completions
result = result[0]
if isinstance(result, tuple):
completion, tool_message, function_call = result
else:
if (
hasattr(client, "complete_chat_with_data_async")
and hasattr(request_settings, "data_source_settings")
and request_settings.data_source_settings is not None
):
# third item is function_call, None in this case
(
completion,
tool_message,
_,
) = await client.complete_chat_with_data_async(
messages, request_settings
)
context.objects["tool_message"] = tool_message
as_chat_prompt.add_message(role="tool", message=tool_message)
else:
completion = await client.complete_chat_async(
messages, request_settings
)
as_chat_prompt.add_assistant_message(completion)
completion = result
tool_message = None
function_call = None
if tool_message:
context.objects["tool_message"] = tool_message
as_chat_prompt.add_message(role="tool", message=tool_message)
as_chat_prompt.add_message(
"assistant", message=completion, function_call=function_call
)
if completion is not None:
context.variables.update(completion)
if function_call is not None:
context.objects["function_call"] = function_call
except Exception as exc:
# TODO: "critical exceptions"
context.fail(str(exc), exc)
@@ -232,32 +183,23 @@ class SKFunction(SKFunctionBase):
# list of <role, content> messages)
completion = ""
messages = await chat_prompt.render_messages_async(context)
# With data case - stream and get the tool message for citations
if (
hasattr(client, "complete_chat_with_data_async")
and hasattr(request_settings, "data_source_settings")
and request_settings.data_source_settings is not None
async for partial_content in client.complete_chat_stream_async(
messages=messages, settings=request_settings
):
response = await client.complete_chat_stream_with_data_async(
messages, request_settings
)
# Get the tool message
tool_message = await response.get_tool_message()
if tool_message:
chat_prompt.add_message(role="tool", message=tool_message)
context.objects["tool_message"] = tool_message
# Get the completion
async for partial_content in response:
completion += partial_content
yield partial_content
else:
async for partial_content in client.complete_chat_stream_async(
messages, request_settings
):
if isinstance(partial_content, str):
completion += partial_content
yield partial_content
else:
tool_message = await partial_content.get_tool_message()
if tool_message:
chat_prompt.add_message(
role="tool", message=tool_message
)
context.objects["tool_message"] = tool_message
# Get the completion
async for part in partial_content:
completion += part
yield part
# Use the full completion to update the chat_prompt_template and context
chat_prompt.add_assistant_message(completion)
context.variables.update(completion)
@@ -314,7 +256,7 @@ class SKFunction(SKFunctionBase):
return not self._is_semantic
@property
def request_settings(self) -> CompleteRequestSettings:
def request_settings(self) -> AIRequestSettings:
return self._ai_request_settings
def __init__(
@@ -345,9 +287,7 @@ class SKFunction(SKFunctionBase):
self._stream_function = delegate_stream_function
self._skill_collection = None
self._ai_service = None
self._ai_request_settings = CompleteRequestSettings()
self._chat_service = None
self._chat_request_settings = ChatRequestSettings()
self._ai_request_settings = AIRequestSettings()
self._chat_prompt_template = kwargs.get("chat_prompt_template", None)
def set_default_skill_collection(
@@ -371,21 +311,21 @@ class SKFunction(SKFunctionBase):
if chat_service is None:
raise ValueError("Chat LLM service factory cannot be `None`")
self._verify_is_semantic()
self._chat_service = chat_service()
self._ai_service = chat_service()
return self
def set_ai_configuration(self, settings: CompleteRequestSettings) -> "SKFunction":
def set_ai_configuration(self, settings: AIRequestSettings) -> "SKFunction":
if settings is None:
raise ValueError("AI LLM request settings cannot be `None`")
self._verify_is_semantic()
self._ai_request_settings = settings
return self
def set_chat_configuration(self, settings: ChatRequestSettings) -> "SKFunction":
def set_chat_configuration(self, settings: AIRequestSettings) -> "SKFunction":
if settings is None:
raise ValueError("Chat LLM request settings cannot be `None`")
self._verify_is_semantic()
self._chat_request_settings = settings
self._ai_request_settings = settings
return self
def describe(self) -> FunctionView:
@@ -403,7 +343,7 @@ class SKFunction(SKFunctionBase):
variables: ContextVariables = None,
context: Optional["SKContext"] = None,
memory: Optional[SemanticTextMemoryBase] = None,
settings: Optional[CompleteRequestSettings] = None,
settings: Optional[AIRequestSettings] = None,
log: Optional[Any] = None,
) -> "SKContext":
if log:
@@ -424,7 +364,7 @@ class SKFunction(SKFunctionBase):
variables: ContextVariables = None,
context: Optional["SKContext"] = None,
memory: Optional[SemanticTextMemoryBase] = None,
settings: Optional[CompleteRequestSettings] = None,
settings: Optional[AIRequestSettings] = None,
log: Optional[Any] = None,
) -> "SKContext":
from semantic_kernel.orchestration.sk_context import SKContext
@@ -479,17 +419,11 @@ class SKFunction(SKFunctionBase):
variables: ContextVariables = None,
context: Optional["SKContext"] = None,
memory: Optional[SemanticTextMemoryBase] = None,
settings: Optional[CompleteRequestSettings] = None,
log: Optional[Any] = None,
settings: Optional[AIRequestSettings] = None,
**kwargs: Dict[str, Any],
) -> "SKContext":
from semantic_kernel.orchestration.sk_context import SKContext
if log:
logger.warning(
"The `log` parameter is deprecated. Please use the `logging` module instead."
)
if context is None:
context = SKContext(
variables=ContextVariables("") if variables is None else variables,
@@ -517,27 +451,13 @@ class SKFunction(SKFunctionBase):
context.fail(str(e), e)
return context
async def _invoke_semantic_async(self, context: "SKContext", settings, **kwargs):
async def _invoke_semantic_async(
self, context: "SKContext", settings: AIRequestSettings, **kwargs
):
self._verify_is_semantic()
self._ensure_context_has_skills(context)
if settings is None:
if self._ai_service is not None:
settings = self._ai_request_settings
elif self._chat_service is not None:
settings = self._chat_request_settings
else:
raise KernelException(
KernelException.ErrorCodes.UnknownError,
"Semantic functions must have either an AI service or Chat service",
)
service = (
self._ai_service if self._ai_service is not None else self._chat_service
)
new_context = await self._function(
service, settings, context, functions=kwargs.get("functions", None)
self._ai_service, settings or self._ai_request_settings, context
)
context.variables.merge_or_overwrite(new_context.variables)
return context
@@ -581,7 +501,7 @@ class SKFunction(SKFunctionBase):
variables: ContextVariables = None,
context: Optional["SKContext"] = None,
memory: Optional[SemanticTextMemoryBase] = None,
settings: Optional[CompleteRequestSettings] = None,
settings: Optional[AIRequestSettings] = None,
):
from semantic_kernel.orchestration.sk_context import SKContext
@@ -621,25 +541,10 @@ class SKFunction(SKFunctionBase):
async def _invoke_semantic_stream_async(self, context, settings):
self._verify_is_semantic()
self._ensure_context_has_skills(context)
if settings is None:
if self._ai_service is not None:
settings = self._ai_request_settings
elif self._chat_service is not None:
settings = self._chat_request_settings
else:
raise KernelException(
KernelException.ErrorCodes.UnknownError,
"Semantic functions must have either an AI service or Chat service",
)
service = (
self._ai_service if self._ai_service is not None else self._chat_service
)
async for stream_msg in self._stream_function(service, settings, context):
async for stream_msg in self._stream_function(
self._ai_service, settings or self._ai_request_settings, context
):
yield stream_msg
async def _invoke_native_stream_async(self, context):
@@ -3,9 +3,7 @@
from abc import abstractmethod
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional
from semantic_kernel.connectors.ai.complete_request_settings import (
CompleteRequestSettings,
)
from semantic_kernel.connectors.ai.ai_request_settings import AIRequestSettings
from semantic_kernel.connectors.ai.text_completion_client_base import (
TextCompletionClientBase,
)
@@ -79,7 +77,7 @@ class SKFunctionBase(SKBaseModel):
@property
@abstractmethod
def request_settings(self) -> CompleteRequestSettings:
def request_settings(self) -> AIRequestSettings:
"""AI service settings"""
pass
@@ -101,7 +99,7 @@ class SKFunctionBase(SKBaseModel):
variables: ContextVariables = None,
context: Optional["SKContext"] = None,
memory: Optional[SemanticTextMemoryBase] = None,
settings: Optional[CompleteRequestSettings] = None,
settings: Optional[AIRequestSettings] = None,
) -> "SKContext":
"""
Invokes the function with an explicit string input
@@ -110,7 +108,7 @@ class SKFunctionBase(SKBaseModel):
variables {ContextVariables} -- The custom input
context {SKContext} -- The context to use
memory: {SemanticTextMemoryBase} -- The memory to use
settings {CompleteRequestSettings} -- LLM completion settings
settings {AIRequestSettings} -- LLM completion settings
Returns:
SKContext -- The updated context, potentially a new one if
context switching is implemented.
@@ -124,7 +122,7 @@ class SKFunctionBase(SKBaseModel):
variables: ContextVariables = None,
context: Optional["SKContext"] = None,
memory: Optional[SemanticTextMemoryBase] = None,
settings: Optional[CompleteRequestSettings] = None,
settings: Optional[AIRequestSettings] = None,
**kwargs: Dict[str, Any],
) -> "SKContext":
"""
@@ -134,7 +132,7 @@ class SKFunctionBase(SKBaseModel):
variables {ContextVariables} -- The custom input
context {SKContext} -- The context to use
memory: {SemanticTextMemoryBase} -- The memory to use
settings {CompleteRequestSettings} -- LLM completion settings
settings {AIRequestSettings} -- LLM completion settings
Returns:
SKContext -- The updated context, potentially a new one if
context switching is implemented.
@@ -177,14 +175,12 @@ class SKFunctionBase(SKBaseModel):
pass
@abstractmethod
def set_ai_configuration(
self, settings: CompleteRequestSettings
) -> "SKFunctionBase":
def set_ai_configuration(self, settings: AIRequestSettings) -> "SKFunctionBase":
"""
Sets the AI completion settings used with LLM requests
Arguments:
settings {CompleteRequestSettings} -- LLM completion settings
settings {AIRequestSettings} -- LLM completion settings
Returns:
SKFunctionBase -- The function instance
+6 -6
View File
@@ -9,7 +9,7 @@ from typing import Any, Callable, ClassVar, List, Optional, Union
from pydantic import PrivateAttr
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai import CompleteRequestSettings
from semantic_kernel.connectors.ai import AIRequestSettings
from semantic_kernel.connectors.ai.text_completion_client_base import (
TextCompletionClientBase,
)
@@ -42,7 +42,7 @@ class Plan(SKFunctionBase):
_skill_name: str = PrivateAttr()
_description: str = PrivateAttr()
_is_semantic: bool = PrivateAttr()
_request_settings: CompleteRequestSettings = PrivateAttr()
_request_settings: AIRequestSettings = PrivateAttr()
DEFAULT_RESULT_KEY: ClassVar[str] = "PLAN.RESULT"
@property
@@ -81,7 +81,7 @@ class Plan(SKFunctionBase):
return not self._is_semantic
@property
def request_settings(self) -> CompleteRequestSettings:
def request_settings(self) -> AIRequestSettings:
return self._request_settings
@property
@@ -135,7 +135,7 @@ class Plan(SKFunctionBase):
self,
input: Optional[str] = None,
context: Optional[SKContext] = None,
settings: Optional[CompleteRequestSettings] = None,
settings: Optional[AIRequestSettings] = None,
memory: Optional[SemanticTextMemoryBase] = None,
**kwargs,
# TODO: cancellation_token: CancellationToken,
@@ -180,7 +180,7 @@ class Plan(SKFunctionBase):
self,
input: Optional[str] = None,
context: Optional[SKContext] = None,
settings: Optional[CompleteRequestSettings] = None,
settings: Optional[AIRequestSettings] = None,
memory: Optional[SemanticTextMemoryBase] = None,
**kwargs,
) -> SKContext:
@@ -230,7 +230,7 @@ class Plan(SKFunctionBase):
def set_ai_configuration(
self,
settings: CompleteRequestSettings,
settings: AIRequestSettings,
) -> SKFunctionBase:
if self._function is not None:
self._function.set_ai_configuration(settings)
@@ -68,7 +68,7 @@ class SequentialPlanner:
read_file(PROMPT_CONFIG_FILE_PATH)
)
prompt_template = prompt or read_file(PROMPT_TEMPLATE_FILE_PATH)
prompt_config.completion.max_tokens = self.config.max_tokens
prompt_config.completion.extension_data["max_tokens"] = self.config.max_tokens
prompt_template = PromptTemplate(
template=prompt_template,
@@ -93,7 +93,7 @@ class StepwisePlanner:
read_file(PROMPT_CONFIG_FILE_PATH)
)
prompt_config.completion.max_tokens = self.config.max_tokens
prompt_config.completion.extension_data["max_tokens"] = self.config.max_tokens
self._system_step_function = self.import_semantic_function(
kernel, "StepwiseStep", prompt_template, prompt_config
@@ -4,6 +4,8 @@ import asyncio
import logging
from typing import TYPE_CHECKING, Any, Dict, Generic, List, Optional, TypeVar
from pydantic import Field
from semantic_kernel.models.chat.chat_message import ChatMessage
from semantic_kernel.semantic_functions.prompt_template import PromptTemplate
from semantic_kernel.semantic_functions.prompt_template_config import (
@@ -22,7 +24,7 @@ logger: logging.Logger = logging.getLogger(__name__)
class ChatPromptTemplate(PromptTemplate, Generic[ChatMessageT]):
_messages: List[ChatMessageT]
messages: List[ChatMessageT] = Field(default_factory=list)
def __init__(
self,
@@ -37,8 +39,16 @@ class ChatPromptTemplate(PromptTemplate, Generic[ChatMessageT]):
"The `log` parameter is deprecated. Please use the `logging` module instead."
)
self._messages = []
if self._prompt_config.completion.chat_system_prompt:
self.add_system_message(self._prompt_config.completion.chat_system_prompt)
if self.prompt_config.completion.extension_data.get("chat_system_prompt"):
self.add_system_message(
self.prompt_config.completion.extension_data["chat_system_prompt"]
)
if (
hasattr(self.prompt_config.completion, "messages")
and self.prompt_config.completion.messages
):
for message in self.prompt_config.completion.messages:
self.add_message(message["role"], message["content"])
async def render_async(self, context: "SKContext") -> str:
raise NotImplementedError(
@@ -68,31 +78,37 @@ class ChatPromptTemplate(PromptTemplate, Generic[ChatMessageT]):
message: The message to add, can include templating components.
kwargs: can be used by inherited classes.
"""
self._messages.append(
ChatMessage(
concrete_message = self.model_fields["messages"].annotation.__args__[0]
# When the type is not explicitly set, it is still the typevar, replace with generic ChatMessage
if isinstance(concrete_message, TypeVar):
concrete_message = ChatMessage
self.messages.append(
concrete_message(
role=role,
content_template=PromptTemplate(
message, self._template_engine, self._prompt_config
),
message, self.template_engine, self.prompt_config
)
if message
else None,
**kwargs,
)
)
async def render_messages_async(self, context: "SKContext") -> List[Dict[str, str]]:
"""Render the content of the message in the chat template, based on the context."""
if len(self._messages) == 0 or self._messages[-1].role in [
if len(self.messages) == 0 or self.messages[-1].role in [
"assistant",
"system",
]:
self.add_user_message(message=self._template)
self.add_user_message(message=self.template)
await asyncio.gather(
*[message.render_message_async(context) for message in self._messages]
*[message.render_message_async(context) for message in self.messages]
)
return [message.as_dict() for message in self._messages]
return [message.as_dict() for message in self.messages]
@property
def messages(self) -> List[Dict[str, str]]:
"""Return the messages as a list of dicts with role, content, name."""
return [message.as_dict() for message in self._messages]
def dump_messages(self) -> List[Dict[str, str]]:
"""Return the messages as a list of dicts with role, content, name and function_call."""
return [message.as_dict() for message in self.messages]
@classmethod
def restore(
@@ -21,9 +21,9 @@ logger: logging.Logger = logging.getLogger(__name__)
class PromptTemplate(PromptTemplateBase):
_template: str
_template_engine: PromptTemplatingEngine
_prompt_config: PromptTemplateConfig
template: str
template_engine: PromptTemplatingEngine
prompt_config: PromptTemplateConfig
def __init__(
self,
@@ -36,15 +36,17 @@ class PromptTemplate(PromptTemplateBase):
logger.warning(
"The `log` parameter is deprecated. Please use the `logging` module instead."
)
self._template = template
self._template_engine = template_engine
self._prompt_config = prompt_config
super().__init__(
template=template,
template_engine=template_engine,
prompt_config=prompt_config,
)
def get_parameters(self) -> List[ParameterView]:
seen = set()
result = []
for param in self._prompt_config.input.parameters:
for param in self.prompt_config.parameters:
if param is None:
continue
@@ -58,7 +60,7 @@ class PromptTemplate(PromptTemplateBase):
seen.add(param.name)
blocks = self._template_engine.extract_blocks(self._template)
blocks = self.template_engine.extract_blocks(self.template)
for block in blocks:
if block.type != BlockTypes.VARIABLE:
continue
@@ -78,4 +80,4 @@ class PromptTemplate(PromptTemplateBase):
return result
async def render_async(self, context: "SKContext") -> str:
return await self._template_engine.render_async(self._template, context)
return await self.template_engine.render_async(self.template, context)
@@ -3,12 +3,14 @@
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, List
from semantic_kernel.sk_pydantic import SKBaseModel
if TYPE_CHECKING:
from semantic_kernel.orchestration.sk_context import SKContext
from semantic_kernel.skill_definition.parameter_view import ParameterView
class PromptTemplateBase(ABC):
class PromptTemplateBase(SKBaseModel, ABC):
@abstractmethod
def get_parameters(self) -> List["ParameterView"]:
pass
@@ -1,108 +1,69 @@
# Copyright (c) Microsoft. All rights reserved.
import json
from typing import Generic, List, TypeVar
from dataclasses import dataclass, field
from typing import Dict, List, Optional
from pydantic import Field
from semantic_kernel.connectors.ai.ai_request_settings import AIRequestSettings
from semantic_kernel.sk_pydantic import SKBaseModel
from semantic_kernel.skill_definition.parameter_view import ParameterView
AIRequestSettingsT = TypeVar("AIRequestSettingsT", bound=AIRequestSettings)
@dataclass
class PromptTemplateConfig:
@dataclass
class CompletionConfig:
temperature: float = 0.0
top_p: float = 1.0
presence_penalty: float = 0.0
frequency_penalty: float = 0.0
max_tokens: int = 256
number_of_responses: int = 1
stop_sequences: List[str] = field(default_factory=list)
token_selection_biases: Dict[int, int] = field(default_factory=dict)
chat_system_prompt: str = None
# the function_call should be 'auto' or the name of a specific function in order to leverage function calling
# when not using auto, the format is 'SkillName-FunctionName', e.g. 'Weather-GetWeather'
function_call: Optional[str] = None
@dataclass
class InputParameter:
name: str = ""
description: str = ""
default_value: str = ""
type_: str = "string"
required: bool = True
@dataclass
class InputConfig:
parameters: List["PromptTemplateConfig.InputParameter"] = field(
default_factory=list
)
schema: int = 1
class PromptTemplateConfig(SKBaseModel, Generic[AIRequestSettingsT]):
schema_: int = Field(default=1, alias="schema")
type: str = "completion"
description: str = ""
completion: "PromptTemplateConfig.CompletionConfig" = field(
default_factory=CompletionConfig
)
default_services: List[str] = field(default_factory=list)
input: "PromptTemplateConfig.InputConfig" = field(default_factory=InputConfig)
completion: AIRequestSettingsT = Field(default_factory=AIRequestSettings)
default_services: List[str] = Field(default_factory=list)
parameters: List[ParameterView] = Field(default_factory=list)
@staticmethod
def from_dict(data: dict) -> "PromptTemplateConfig":
config = PromptTemplateConfig()
keys = ["schema", "type", "description"]
for key in keys:
if key in data:
setattr(config, key, data[key])
@classmethod
def from_dict(cls, data: dict) -> "PromptTemplateConfig":
config = {
key: value
for key, value in data.items()
if key in ["schema", "type", "description"]
}
config["parameters"] = []
# Some skills may not have all completion parameters defined
config.completion = PromptTemplateConfig.CompletionConfig()
completion_dict = data["completion"]
completion_keys = [
"temperature",
"top_p",
"presence_penalty",
"frequency_penalty",
"max_tokens",
"number_of_responses",
"stop_sequences",
"token_selection_biases",
"default_services",
"chat_system_prompt",
"function_call",
]
for comp_key in completion_keys:
if comp_key in completion_dict:
setattr(config.completion, comp_key, completion_dict[comp_key])
service_id = completion_dict.pop("service_id", None)
concrete_type = cls.model_fields["completion"].annotation
if isinstance(concrete_type, TypeVar):
concrete_type = AIRequestSettings
config["completion"] = concrete_type(
service_id=service_id, extension_data=completion_dict
)
# Some skills may not have input parameters defined
config.input = PromptTemplateConfig.InputConfig()
config.input.parameters = []
if data.get("input") is not None:
for parameter in data["input"]["parameters"]:
if data.get("parameters") is not None:
for parameter in data["parameters"]:
if "name" in parameter:
name = parameter["name"]
else:
raise Exception(
f"The input parameter doesn't have a name (function: {config.description})"
f"The input parameter doesn't have a name (function: {config['description']})"
)
if "description" in parameter:
description = parameter["description"]
else:
raise Exception(
f"Input parameter '{name}' doesn't have a description (function: {config.description})"
f"Input parameter '{name}' doesn't have a description (function: {config['description']})"
)
if "defaultValue" in parameter:
defaultValue = parameter["defaultValue"]
else:
raise Exception(
f"Input parameter '{name}' doesn't have a default value (function: {config.description})"
f"Input parameter '{name}' doesn't have a default value (function: {config['description']})"
)
type_ = parameter.get("type")
required = parameter.get("required")
config.input.parameters.append(
PromptTemplateConfig.InputParameter(
config["parameters"].append(
ParameterView(
name,
description,
defaultValue,
@@ -110,41 +71,17 @@ class PromptTemplateConfig:
required,
)
)
return config
if "default_services" in data:
config["default_services"] = data["default_services"]
return cls(**config)
@staticmethod
def from_json(json_str: str) -> "PromptTemplateConfig":
import json
@classmethod
def from_json(cls, json_str: str) -> "PromptTemplateConfig":
return cls.from_dict(json.loads(json_str))
def keystoint(d):
return {int(k) if k.isdigit() else k: v for k, v in d.items()}
return PromptTemplateConfig.from_dict(
json.loads(json_str, object_hook=keystoint)
)
@staticmethod
def from_completion_parameters(
temperature: float = 0.0,
top_p: float = 1.0,
presence_penalty: float = 0.0,
frequency_penalty: float = 0.0,
max_tokens: int = 256,
number_of_responses: int = 1,
stop_sequences: List[str] = [],
token_selection_biases: Dict[int, int] = {},
chat_system_prompt: str = None,
function_call: Optional[str] = None,
) -> "PromptTemplateConfig":
config = PromptTemplateConfig()
config.completion.temperature = temperature
config.completion.top_p = top_p
config.completion.presence_penalty = presence_penalty
config.completion.frequency_penalty = frequency_penalty
config.completion.max_tokens = max_tokens
config.completion.number_of_responses = number_of_responses
config.completion.stop_sequences = stop_sequences
config.completion.token_selection_biases = token_selection_biases
config.completion.chat_system_prompt = chat_system_prompt
config.completion.function_call = function_call
return config
@classmethod
def from_completion_parameters(cls, **kwargs) -> "PromptTemplateConfig":
concrete_class = cls.model_fields["completion"].annotation
if isinstance(concrete_class, TypeVar):
concrete_class = AIRequestSettings
return PromptTemplateConfig(completion=concrete_class(extension_data=kwargs))
+3 -1
View File
@@ -14,7 +14,9 @@ HttpsUrl = Annotated[Url, UrlConstraints(max_length=2083, allowed_schemes=["http
class SKBaseModel(BaseModel):
"""Base class for all pydantic models in the SK."""
model_config = ConfigDict(populate_by_name=True, arbitrary_types_allowed=True)
model_config = ConfigDict(
populate_by_name=True, arbitrary_types_allowed=True, validate_assignment=True
)
# TODO: remove these aliases in SK v1
@@ -98,48 +98,3 @@ async def test_azure_e2e_chat_completion_with_skill_and_provided_client(
"human" in output or "Human" in output or "preserve" in output
)
assert len(output) < 100
@pytest.mark.asyncio
async def test_oai_chat_stream_service_with_skills(
setup_tldr_function_for_oai_models, get_aoai_config
):
kernel, sk_prompt, text_to_summarize = setup_tldr_function_for_oai_models
_, api_key, endpoint = get_aoai_config
if "Python_Integration_Tests" in os.environ:
deployment_name = os.environ["AzureOpenAIChat__DeploymentName"]
else:
deployment_name = "gpt-35-turbo"
print("* Service: Azure OpenAI Chat Completion")
print(f"* Endpoint: {endpoint}")
print(f"* Deployment: {deployment_name}")
# Configure LLM service
kernel.add_chat_service(
"chat_completion",
sk_oai.AzureChatCompletion(
deployment_name=deployment_name, endpoint=endpoint, api_key=api_key
),
)
# Create the semantic function
tldr_function = kernel.create_semantic_function(
sk_prompt, max_tokens=200, temperature=0, top_p=0.5
)
result = []
async for message in kernel.run_stream_async(
tldr_function, input_str=text_to_summarize
):
result.append(message)
output = "".join(result).strip()
print(f"TLDR using input string: '{output}'")
assert len(result) > 1
assert "First Law" not in output and (
"human" in output or "Human" in output or "preserve" in output
)
assert len(output) < 100
@@ -9,10 +9,17 @@ import pytest
import semantic_kernel as sk
import semantic_kernel.connectors.ai.open_ai as sk_oai
from semantic_kernel.connectors.ai.open_ai.request_settings.azure_chat_request_settings import (
AzureAISearchDataSources,
AzureChatRequestSettings,
AzureDataSources,
ExtraBody,
)
from semantic_kernel.connectors.memory.azure_cognitive_search.azure_cognitive_search_memory_store import (
AzureCognitiveSearchMemoryStore,
)
from semantic_kernel.memory.memory_record import MemoryRecord
from semantic_kernel.semantic_functions.chat_prompt_template import ChatPromptTemplate
try:
azure_cognitive_search_installed = True
@@ -79,18 +86,17 @@ async def create_with_data_chat_function(
search_endpoint = os.getenv("AZURE_COGNITIVE_SEARCH_ENDPOINT")
search_api_key = os.getenv("AZURE_COGNITIVE_SEARCH_ADMIN_KEY")
azure_aisearch_datasource = sk_oai.OpenAIChatPromptTemplateWithDataConfig.AzureAISearchDataSource(
parameters=sk_oai.OpenAIChatPromptTemplateWithDataConfig.AzureAISearchDataSourceParameters(
indexName=collection,
endpoint=search_endpoint,
key=search_api_key,
)
)
azure_chat_with_data_settings = (
sk_oai.OpenAIChatPromptTemplateWithDataConfig.AzureChatWithDataSettings(
dataSources=[azure_aisearch_datasource]
)
extra = ExtraBody(
data_sources=[
AzureDataSources(
type="AzureCognitiveSearch",
parameters=AzureAISearchDataSources(
indexName=collection,
endpoint=search_endpoint,
key=search_api_key,
),
)
]
)
chat_service = sk_oai.AzureChatCompletion(
@@ -102,12 +108,12 @@ async def create_with_data_chat_function(
)
kernel.add_chat_service("chat-gpt", chat_service)
prompt_config = (
sk_oai.OpenAIChatPromptTemplateWithDataConfig.from_completion_parameters(
prompt_config = ChatPromptTemplate(
completion=AzureChatRequestSettings(
max_tokens=2000,
temperature=0.7,
top_p=0.8,
data_source_settings=azure_chat_with_data_settings,
extra_body=extra,
)
)
@@ -100,50 +100,3 @@ async def test_azure_e2e_text_completion_with_skill_with_provided_client(
"human" in output or "Human" in output or "preserve" in output
)
assert len(output) < 100
@pytest.mark.asyncio
async def test_oai_text_stream_completion_with_skills(
setup_tldr_function_for_oai_models, get_aoai_config
):
kernel, sk_prompt, text_to_summarize = setup_tldr_function_for_oai_models
_, api_key, endpoint = get_aoai_config
if "Python_Integration_Tests" in os.environ:
deployment_name = os.environ["AzureOpenAI__DeploymentName"]
else:
deployment_name = "text-davinci-003"
print("* Service: Azure OpenAI Text Completion")
print(f"* Endpoint: {endpoint}")
print(f"* Deployment: {deployment_name}")
# Configure LLM service
kernel.add_text_completion_service(
"text_completion",
sk_oai.AzureTextCompletion(
deployment_name=deployment_name,
endpoint=endpoint,
api_key=api_key,
),
)
# Create the semantic function
tldr_function = kernel.create_semantic_function(
sk_prompt, max_tokens=200, temperature=0, top_p=0.5
)
result = []
async for message in kernel.run_stream_async(
tldr_function, input_str=text_to_summarize
):
result.append(message)
output = "".join(result).strip()
print(f"TLDR using input string: '{output}'")
assert len(result) > 1
assert "First Law" not in output and (
"human" in output or "Human" in output or "preserve" in output
)
assert len(output) < 100
@@ -1,4 +1,5 @@
# Copyright (c) Microsoft. All rights reserved.
import os
import pytest
from openai import AsyncOpenAI
@@ -81,3 +82,48 @@ async def test_oai_chat_service_with_skills_with_provided_client(
"human" in output or "Human" in output or "preserve" in output
)
assert len(output) < 100
@pytest.mark.asyncio
async def test_oai_chat_stream_service_with_skills(
setup_tldr_function_for_oai_models, get_aoai_config
):
kernel, sk_prompt, text_to_summarize = setup_tldr_function_for_oai_models
_, api_key, endpoint = get_aoai_config
if "Python_Integration_Tests" in os.environ:
deployment_name = os.environ["AzureOpenAIChat__DeploymentName"]
else:
deployment_name = "gpt-35-turbo"
print("* Service: Azure OpenAI Chat Completion")
print(f"* Endpoint: {endpoint}")
print(f"* Deployment: {deployment_name}")
# Configure LLM service
kernel.add_chat_service(
"chat_completion",
sk_oai.AzureChatCompletion(
deployment_name=deployment_name, endpoint=endpoint, api_key=api_key
),
)
# Create the semantic function
tldr_function = kernel.create_semantic_function(
sk_prompt, max_tokens=200, temperature=0, top_p=0.5
)
result = []
async for message in kernel.run_stream_async(
tldr_function, input_str=text_to_summarize
):
result.append(message)
output = "".join(result).strip()
print(f"TLDR using input string: '{output}'")
assert len(result) > 1
assert "First Law" not in output and (
"human" in output or "Human" in output or "preserve" in output
)
assert len(output) < 100
@@ -1,4 +1,5 @@
# Copyright (c) Microsoft. All rights reserved.
import os
import pytest
from openai import AsyncOpenAI
@@ -81,3 +82,50 @@ async def test_oai_text_completion_with_skills_with_provided_client(
"human" in output or "Human" in output or "preserve" in output
)
assert len(output) < 100
@pytest.mark.asyncio
async def test_oai_text_stream_completion_with_skills(
setup_tldr_function_for_oai_models, get_aoai_config
):
kernel, sk_prompt, text_to_summarize = setup_tldr_function_for_oai_models
_, api_key, endpoint = get_aoai_config
if "Python_Integration_Tests" in os.environ:
deployment_name = os.environ["AzureOpenAI__DeploymentName"]
else:
deployment_name = "text-davinci-003"
print("* Service: Azure OpenAI Text Completion")
print(f"* Endpoint: {endpoint}")
print(f"* Deployment: {deployment_name}")
# Configure LLM service
kernel.add_text_completion_service(
"text_completion",
sk_oai.AzureTextCompletion(
deployment_name=deployment_name,
endpoint=endpoint,
api_key=api_key,
),
)
# Create the semantic function
tldr_function = kernel.create_semantic_function(
sk_prompt, max_tokens=200, temperature=0, top_p=0.5
)
result = []
async for message in kernel.run_stream_async(
tldr_function, input_str=text_to_summarize
):
result.append(message)
output = "".join(result).strip()
print(f"TLDR using input string: '{output}'")
assert len(result) > 1
assert "First Law" not in output and (
"human" in output or "Human" in output or "preserve" in output
)
assert len(output) < 100
@@ -150,6 +150,9 @@ async def test_can_create_stepwise_plan(
],
)
@pytest.mark.asyncio
@pytest.mark.xfail(
reason="Test is known to occasionally produce unexpected results.",
)
async def test_can_execute_stepwise_plan(
get_aoai_config,
get_bing_config,
@@ -7,11 +7,10 @@ from unittest.mock import MagicMock, patch
import pytest
from pydantic import ValidationError
from semantic_kernel.connectors.ai.chat_request_settings import (
ChatRequestSettings,
)
if sys.version_info >= (3, 9):
from semantic_kernel.connectors.ai.google_palm import (
GooglePalmChatRequestSettings,
)
from semantic_kernel.connectors.ai.google_palm.services.gp_chat_completion import (
GooglePalmChatCompletion,
)
@@ -65,17 +64,16 @@ async def test_google_palm_text_completion_complete_chat_async_call_with_paramet
ai_model_id=ai_model_id,
api_key=api_key,
)
settings = ChatRequestSettings()
settings = GooglePalmChatRequestSettings()
response = await gp_chat_completion.complete_chat_async(prompt, settings)
assert isinstance(response.result(), str) and len(response.result()) > 0
print(mock_gp.chat)
mock_gp.chat.assert_called_once_with(
model=ai_model_id,
context="",
examples=None,
temperature=settings.temperature,
candidate_count=settings.number_of_responses,
top_p=settings.top_p,
prompt=None,
messages=prompt[-1][1],
top_k=settings.top_k,
candidate_count=settings.candidate_count,
messages=prompt,
token_selection_biases={},
)
@@ -6,11 +6,10 @@ from unittest.mock import MagicMock, patch
import pytest
from pydantic import ValidationError
from semantic_kernel.connectors.ai.complete_request_settings import (
CompleteRequestSettings,
)
if sys.version_info >= (3, 9):
from semantic_kernel.connectors.ai.google_palm import (
GooglePalmTextRequestSettings,
)
from semantic_kernel.connectors.ai.google_palm.services.gp_text_completion import (
GooglePalmTextCompletion,
)
@@ -65,7 +64,7 @@ async def test_google_palm_text_completion_complete_async_call_with_parameters()
ai_model_id=ai_model_id,
api_key=api_key,
)
settings = CompleteRequestSettings()
settings = GooglePalmTextRequestSettings()
response = await gp_text_completion.complete_async(prompt, settings)
assert isinstance(response.result(), str) and len(response.result()) > 0
@@ -73,8 +72,8 @@ async def test_google_palm_text_completion_complete_async_call_with_parameters()
model=ai_model_id,
prompt=prompt,
temperature=settings.temperature,
max_output_tokens=settings.max_tokens,
stop_sequences=None,
candidate_count=settings.number_of_responses,
max_output_tokens=settings.max_output_tokens,
candidate_count=settings.candidate_count,
top_p=settings.top_p,
top_k=settings.top_k,
)
@@ -1,7 +1,5 @@
# Copyright (c) Microsoft. All rights reserved.
from dataclasses import asdict
from logging import Logger
from unittest.mock import AsyncMock, patch
import pytest
@@ -13,15 +11,17 @@ from semantic_kernel.connectors.ai.ai_exception import AIException
from semantic_kernel.connectors.ai.chat_completion_client_base import (
ChatCompletionClientBase,
)
from semantic_kernel.connectors.ai.chat_request_settings import ChatRequestSettings
from semantic_kernel.connectors.ai.open_ai import (
AzureChatCompletion,
)
from semantic_kernel.connectors.ai.open_ai.const import (
USER_AGENT,
)
from semantic_kernel.connectors.ai.open_ai.semantic_functions.open_ai_chat_prompt_template_with_data_config import (
OpenAIChatPromptTemplateWithDataConfig,
)
from semantic_kernel.connectors.ai.open_ai.services.azure_chat_completion import (
AzureChatCompletion,
from semantic_kernel.connectors.ai.open_ai.request_settings.azure_chat_request_settings import (
AzureAISearchDataSources,
AzureChatRequestSettings,
AzureDataSources,
ExtraBody,
)
@@ -153,11 +153,8 @@ async def test_azure_chat_completion_call_with_parameters(mock_create) -> None:
endpoint = "https://test-endpoint.com"
api_key = "test_api_key"
api_version = "2023-03-15-preview"
prompt = "hello world"
messages_in = [{"role": "user", "content": prompt}]
messages_out = [{"role": "user", "content": prompt}]
complete_request_settings = ChatRequestSettings()
messages = [{"role": "user", "content": "hello world"}]
complete_request_settings = AzureChatRequestSettings(service_id="test_service_id")
azure_chat_completion = AzureChatCompletion(
deployment_name=deployment_name,
@@ -166,20 +163,19 @@ async def test_azure_chat_completion_call_with_parameters(mock_create) -> None:
api_key=api_key,
)
await azure_chat_completion.complete_chat_async(
messages=messages_in, settings=complete_request_settings
messages=messages, settings=complete_request_settings
)
mock_create.assert_awaited_once_with(
model=deployment_name,
messages=messages_out,
temperature=complete_request_settings.temperature,
top_p=complete_request_settings.top_p,
n=complete_request_settings.number_of_responses,
stream=False,
stop=None,
max_tokens=complete_request_settings.max_tokens,
presence_penalty=complete_request_settings.presence_penalty,
frequency_penalty=complete_request_settings.frequency_penalty,
logit_bias={},
max_tokens=complete_request_settings.max_tokens,
n=complete_request_settings.number_of_responses,
presence_penalty=complete_request_settings.presence_penalty,
stream=False,
temperature=complete_request_settings.temperature,
top_p=complete_request_settings.top_p,
messages=messages,
)
@@ -195,10 +191,10 @@ async def test_azure_chat_completion_call_with_parameters_and_Logit_Bias_Defined
prompt = "hello world"
messages = [{"role": "user", "content": prompt}]
complete_request_settings = ChatRequestSettings()
complete_request_settings = AzureChatRequestSettings()
token_bias = {1: -100}
complete_request_settings.token_selection_biases = token_bias
token_bias = {"1": -100}
complete_request_settings.logit_bias = token_bias
azure_chat_completion = AzureChatCompletion(
deployment_name=deployment_name,
@@ -218,7 +214,6 @@ async def test_azure_chat_completion_call_with_parameters_and_Logit_Bias_Defined
top_p=complete_request_settings.top_p,
n=complete_request_settings.number_of_responses,
stream=False,
stop=None,
max_tokens=complete_request_settings.max_tokens,
presence_penalty=complete_request_settings.presence_penalty,
frequency_penalty=complete_request_settings.frequency_penalty,
@@ -238,10 +233,10 @@ async def test_azure_chat_completion_call_with_parameters_and_Stop_Defined(
prompt = "hello world"
messages = [{"role": "user", "content": prompt}]
complete_request_settings = ChatRequestSettings()
complete_request_settings = AzureChatRequestSettings()
stop = ["!"]
complete_request_settings.stop_sequences = stop
complete_request_settings.stop = stop
azure_chat_completion = AzureChatCompletion(
deployment_name=deployment_name,
@@ -259,7 +254,7 @@ async def test_azure_chat_completion_call_with_parameters_and_Stop_Defined(
top_p=complete_request_settings.top_p,
n=complete_request_settings.number_of_responses,
stream=False,
stop=complete_request_settings.stop_sequences,
stop=complete_request_settings.stop,
max_tokens=complete_request_settings.max_tokens,
presence_penalty=complete_request_settings.presence_penalty,
frequency_penalty=complete_request_settings.frequency_penalty,
@@ -308,26 +303,25 @@ async def test_azure_chat_completion_with_data_call_with_parameters(
endpoint = "https://test-endpoint.com"
api_key = "test_api_key"
api_version = "2023-03-15-preview"
logger = Logger("test_logger")
prompt = "hello world"
messages_in = [{"role": "user", "content": prompt}]
messages_out = [{"role": "user", "content": prompt}]
azure_aisearch_datasource = OpenAIChatPromptTemplateWithDataConfig.AzureAISearchDataSource(
parameters=OpenAIChatPromptTemplateWithDataConfig.AzureAISearchDataSourceParameters(
indexName="test_index",
endpoint="https://test-endpoint-search.com",
key="test_key",
)
)
azure_chat_with_data_settings = (
OpenAIChatPromptTemplateWithDataConfig.AzureChatWithDataSettings(
dataSources=[azure_aisearch_datasource]
)
)
expected_data_settings = {
"dataSources": [
{
"type": "AzureCognitiveSearch",
"parameters": {
"indexName": "test_index",
"endpoint": "https://test-endpoint-search.com",
"key": "test_key",
},
}
]
}
complete_request_settings = ChatRequestSettings(
data_source_settings=azure_chat_with_data_settings
complete_request_settings = AzureChatRequestSettings(
extra_body=expected_data_settings
)
azure_chat_completion = AzureChatCompletion(
@@ -335,31 +329,23 @@ async def test_azure_chat_completion_with_data_call_with_parameters(
endpoint=endpoint,
api_version=api_version,
api_key=api_key,
log=logger,
)
await azure_chat_completion.complete_chat_with_data_async(
messages=messages_in, request_settings=complete_request_settings
await azure_chat_completion.complete_chat_async(
messages=messages_in, settings=complete_request_settings
)
expected_data_settings = asdict(azure_chat_with_data_settings)
# No embeddingDeploymentName if not using vectors.
del expected_data_settings["dataSources"][0]["parameters"][
"embeddingDeploymentName"
]
mock_create.assert_awaited_once_with(
model=deployment_name,
messages=messages_out,
temperature=complete_request_settings.temperature,
frequency_penalty=complete_request_settings.frequency_penalty,
presence_penalty=complete_request_settings.presence_penalty,
logit_bias={},
top_p=complete_request_settings.top_p,
# n=complete_request_settings.number_of_responses,
n=complete_request_settings.number_of_responses,
stream=False,
stop=None,
max_tokens=complete_request_settings.max_tokens,
# presence_penalty=complete_request_settings.presence_penalty,
# frequency_penalty=complete_request_settings.frequency_penalty,
# logit_bias={},
extra_body=expected_data_settings,
)
@@ -373,21 +359,16 @@ async def test_azure_chat_completion_call_with_data_parameters_and_function_call
endpoint = "https://test-endpoint.com"
api_key = "test_api_key"
api_version = "2023-03-15-preview"
logger = Logger("test_logger")
prompt = "hello world"
messages = [{"role": "user", "content": prompt}]
azure_aisearch_datasource = OpenAIChatPromptTemplateWithDataConfig.AzureAISearchDataSource(
parameters=OpenAIChatPromptTemplateWithDataConfig.AzureAISearchDataSourceParameters(
indexName="test_index",
endpoint="https://test-endpoint-search.com",
key="test_key",
)
ai_source = AzureAISearchDataSources(
indexName="test-index", endpoint="test-endpoint", key="test-key"
)
azure_chat_with_data_settings = (
OpenAIChatPromptTemplateWithDataConfig.AzureChatWithDataSettings(
dataSources=[azure_aisearch_datasource]
)
extra = ExtraBody(
data_sources=[
AzureDataSources(type="AzureCognitiveSearch", parameters=ai_source)
]
)
azure_chat_completion = AzureChatCompletion(
@@ -395,39 +376,33 @@ async def test_azure_chat_completion_call_with_data_parameters_and_function_call
endpoint=endpoint,
api_key=api_key,
api_version=api_version,
log=logger,
)
functions = [{"name": "test-function", "description": "test-description"}]
complete_request_settings = ChatRequestSettings(
complete_request_settings = AzureChatRequestSettings(
function_call="test-function",
data_source_settings=azure_chat_with_data_settings,
)
await azure_chat_completion.complete_chat_with_data_async(
messages=messages,
functions=functions,
request_settings=complete_request_settings,
extra_body=extra,
)
expected_data_settings = asdict(azure_chat_with_data_settings)
# No embeddingDeploymentName if not using vectors.
del expected_data_settings["dataSources"][0]["parameters"][
"embeddingDeploymentName"
]
await azure_chat_completion.complete_chat_async(
messages=messages,
settings=complete_request_settings,
)
expected_data_settings = extra.model_dump(exclude_none=True, by_alias=True)
mock_create.assert_awaited_once_with(
model=deployment_name,
messages=messages,
temperature=complete_request_settings.temperature,
top_p=complete_request_settings.top_p,
# n=complete_request_settings.number_of_responses,
n=complete_request_settings.number_of_responses,
stream=False,
stop=None,
max_tokens=complete_request_settings.max_tokens,
# presence_penalty=complete_request_settings.presence_penalty,
# frequency_penalty=complete_request_settings.frequency_penalty,
# logit_bias=token_bias,
presence_penalty=complete_request_settings.presence_penalty,
frequency_penalty=complete_request_settings.frequency_penalty,
logit_bias=complete_request_settings.logit_bias,
extra_body=expected_data_settings,
functions=functions,
function_call=complete_request_settings.function_call,
@@ -443,55 +418,45 @@ async def test_azure_chat_completion_call_with_data_with_parameters_and_Stop_Def
endpoint = "https://test-endpoint.com"
api_key = "test_api_key"
api_version = "2023-03-15-preview"
logger = Logger("test_logger")
prompt = "hello world"
messages = [{"role": "user", "content": prompt}]
complete_request_settings = ChatRequestSettings()
messages = [{"role": "user", "content": "hello world"}]
complete_request_settings = AzureChatRequestSettings()
stop = ["!"]
complete_request_settings.stop_sequences = stop
complete_request_settings.stop = stop
azure_aisearch_datasource = OpenAIChatPromptTemplateWithDataConfig.AzureAISearchDataSource(
parameters=OpenAIChatPromptTemplateWithDataConfig.AzureAISearchDataSourceParameters(
indexName="test_index",
endpoint="https://test-endpoint-search.com",
key="test_key",
)
ai_source = AzureAISearchDataSources(
indexName="test-index", endpoint="test-endpoint", key="test-key"
)
azure_chat_with_data_settings = (
OpenAIChatPromptTemplateWithDataConfig.AzureChatWithDataSettings(
dataSources=[azure_aisearch_datasource]
)
extra = ExtraBody(
data_sources=[
AzureDataSources(type="AzureCognitiveSearch", parameters=ai_source)
]
)
complete_request_settings.data_source_settings = azure_chat_with_data_settings
complete_request_settings.extra_body = extra
azure_chat_completion = AzureChatCompletion(
deployment_name=deployment_name,
endpoint=endpoint,
api_key=api_key,
api_version=api_version,
log=logger,
)
await azure_chat_completion.complete_chat_async(messages, complete_request_settings)
expected_data_settings = asdict(azure_chat_with_data_settings)
# No embeddingDeploymentName if not using vectors.
del expected_data_settings["dataSources"][0]["parameters"][
"embeddingDeploymentName"
]
expected_data_settings = extra.model_dump(exclude_none=True, by_alias=True)
mock_create.assert_awaited_once_with(
model=deployment_name,
messages=messages,
temperature=complete_request_settings.temperature,
top_p=complete_request_settings.top_p,
# n=complete_request_settings.number_of_responses,
n=complete_request_settings.number_of_responses,
stream=False,
stop=complete_request_settings.stop_sequences,
stop=complete_request_settings.stop,
max_tokens=complete_request_settings.max_tokens,
# presence_penalty=complete_request_settings.presence_penalty,
# frequency_penalty=complete_request_settings.frequency_penalty,
# logit_bias={},
presence_penalty=complete_request_settings.presence_penalty,
frequency_penalty=complete_request_settings.frequency_penalty,
logit_bias={},
extra_body=expected_data_settings,
)
@@ -9,8 +9,8 @@ from pydantic import ValidationError
from semantic_kernel.connectors.ai import TextCompletionClientBase
from semantic_kernel.connectors.ai.ai_exception import AIException
from semantic_kernel.connectors.ai.complete_request_settings import (
CompleteRequestSettings,
from semantic_kernel.connectors.ai.open_ai.request_settings.open_ai_request_settings import (
OpenAITextRequestSettings,
)
from semantic_kernel.connectors.ai.open_ai.services.azure_text_completion import (
AzureTextCompletion,
@@ -133,7 +133,7 @@ async def test_azure_text_completion_call_with_parameters(mock_create) -> None:
api_version = "2023-03-15-preview"
prompt = "hello world"
complete_request_settings = CompleteRequestSettings()
complete_request_settings = OpenAITextRequestSettings()
azure_text_completion = AzureTextCompletion(
deployment_name=deployment_name,
endpoint=endpoint,
@@ -145,17 +145,16 @@ async def test_azure_text_completion_call_with_parameters(mock_create) -> None:
mock_create.assert_awaited_once_with(
model=deployment_name,
prompt=prompt,
temperature=complete_request_settings.temperature,
max_tokens=complete_request_settings.max_tokens,
top_p=complete_request_settings.top_p,
presence_penalty=complete_request_settings.presence_penalty,
frequency_penalty=complete_request_settings.frequency_penalty,
stop=None,
n=complete_request_settings.number_of_responses,
stream=False,
logit_bias={},
logprobs=0,
max_tokens=complete_request_settings.max_tokens,
n=complete_request_settings.number_of_responses,
presence_penalty=complete_request_settings.presence_penalty,
stream=False,
temperature=complete_request_settings.temperature,
top_p=complete_request_settings.top_p,
prompt=prompt,
echo=False,
)
@@ -170,10 +169,10 @@ async def test_azure_text_completion_call_with_parameters_logit_bias_not_none(
api_version = "2023-03-15-preview"
prompt = "hello world"
complete_request_settings = CompleteRequestSettings()
complete_request_settings = OpenAITextRequestSettings()
token_bias = {200: 100}
complete_request_settings.token_selection_biases = token_bias
token_bias = {"200": 100}
complete_request_settings.logit_bias = token_bias
azure_text_completion = AzureTextCompletion(
deployment_name=deployment_name,
@@ -186,17 +185,16 @@ async def test_azure_text_completion_call_with_parameters_logit_bias_not_none(
mock_create.assert_awaited_once_with(
model=deployment_name,
prompt=prompt,
temperature=complete_request_settings.temperature,
max_tokens=complete_request_settings.max_tokens,
top_p=complete_request_settings.top_p,
presence_penalty=complete_request_settings.presence_penalty,
frequency_penalty=complete_request_settings.frequency_penalty,
stop=None,
logit_bias=complete_request_settings.logit_bias,
max_tokens=complete_request_settings.max_tokens,
n=complete_request_settings.number_of_responses,
presence_penalty=complete_request_settings.presence_penalty,
stream=False,
logit_bias=token_bias,
logprobs=0,
temperature=complete_request_settings.temperature,
top_p=complete_request_settings.top_p,
prompt=prompt,
echo=False,
)
@@ -145,8 +145,8 @@ async def test_azure_text_embedding_calls_with_parameters(mock_create) -> None:
await azure_text_embedding.generate_embeddings_async(texts)
mock_create.assert_awaited_once_with(
model=deployment_name,
input=texts,
model=deployment_name,
)
@@ -0,0 +1,243 @@
# Copyright (c) Microsoft. All rights reserved.
import pytest
from pydantic import ValidationError
from semantic_kernel.connectors.ai.ai_request_settings import AIRequestSettings
from semantic_kernel.connectors.ai.open_ai.request_settings.azure_chat_request_settings import (
AzureAISearchDataSources,
AzureChatRequestSettings,
AzureDataSources,
ExtraBody,
)
from semantic_kernel.connectors.ai.open_ai.request_settings.open_ai_request_settings import (
OpenAIChatRequestSettings,
OpenAITextRequestSettings,
)
def test_default_openai_chat_request_settings():
settings = OpenAIChatRequestSettings()
assert settings.temperature == 0.0
assert settings.top_p == 1.0
assert settings.presence_penalty == 0.0
assert settings.frequency_penalty == 0.0
assert settings.max_tokens == 256
assert settings.stop is None
assert settings.number_of_responses == 1
assert settings.logit_bias == {}
assert settings.messages[0]["content"] == "Assistant is a large language model."
assert settings.response_format == "text"
def test_custom_openai_chat_request_settings():
settings = OpenAIChatRequestSettings(
temperature=0.5,
top_p=0.5,
presence_penalty=0.5,
frequency_penalty=0.5,
max_tokens=128,
stop="\n",
number_of_responses=2,
logit_bias={"1": 1},
messages=[{"role": "system", "content": "Hello"}],
)
assert settings.temperature == 0.5
assert settings.top_p == 0.5
assert settings.presence_penalty == 0.5
assert settings.frequency_penalty == 0.5
assert settings.max_tokens == 128
assert settings.stop == "\n"
assert settings.number_of_responses == 2
assert settings.logit_bias == {"1": 1}
assert settings.messages == [{"role": "system", "content": "Hello"}]
def test_openai_chat_request_settings_from_default_completion_config():
settings = AIRequestSettings(service_id="test_service")
chat_settings = OpenAIChatRequestSettings.from_ai_request_settings(settings)
assert chat_settings.service_id == "test_service"
assert chat_settings.temperature == 0.0
assert chat_settings.top_p == 1.0
assert chat_settings.presence_penalty == 0.0
assert chat_settings.frequency_penalty == 0.0
assert chat_settings.max_tokens == 256
assert chat_settings.stop is None
assert chat_settings.number_of_responses == 1
assert chat_settings.logit_bias == {}
def test_openai_chat_request_settings_from_openai_request_settings():
chat_settings = OpenAIChatRequestSettings(
service_id="test_service", temperature=1.0
)
new_settings = OpenAIChatRequestSettings(service_id="test_2", temperature=0.0)
chat_settings.update_from_ai_request_settings(new_settings)
assert chat_settings.service_id == "test_2"
assert chat_settings.temperature == 0.0
def test_openai_text_request_settings_validation():
with pytest.raises(
ValidationError, match="best_of must be greater than number_of_responses"
):
OpenAITextRequestSettings(best_of=1, number_of_responses=2)
def test_openai_text_request_settings_validation_manual():
text_oai = OpenAITextRequestSettings(best_of=1, number_of_responses=1)
with pytest.raises(
ValidationError, match="best_of must be greater than number_of_responses"
):
text_oai.number_of_responses = 2
def test_openai_chat_request_settings_from_custom_completion_config():
settings = AIRequestSettings(
service_id="test_service",
extension_data={
"temperature": 0.5,
"top_p": 0.5,
"presence_penalty": 0.5,
"frequency_penalty": 0.5,
"max_tokens": 128,
"stop": ["\n"],
"number_of_responses": 2,
"logprobs": 1,
"logit_bias": {"1": 1},
"messages": [{"role": "system", "content": "Hello"}],
},
)
chat_settings = OpenAIChatRequestSettings.from_ai_request_settings(settings)
assert chat_settings.temperature == 0.5
assert chat_settings.top_p == 0.5
assert chat_settings.presence_penalty == 0.5
assert chat_settings.frequency_penalty == 0.5
assert chat_settings.max_tokens == 128
assert chat_settings.stop == ["\n"]
assert chat_settings.number_of_responses == 2
assert chat_settings.logit_bias == {"1": 1}
def test_openai_chat_request_settings_from_custom_completion_config_with_none():
settings = AIRequestSettings(
service_id="test_service",
extension_data={
"temperature": 0.5,
"top_p": 0.5,
"presence_penalty": 0.5,
"frequency_penalty": 0.5,
"max_tokens": 128,
"stop": ["\n"],
"number_of_responses": 2,
"functions": None,
"logit_bias": {"1": 1},
"messages": [{"role": "system", "content": "Hello"}],
},
)
chat_settings = OpenAIChatRequestSettings.from_ai_request_settings(settings)
assert chat_settings.temperature == 0.5
assert chat_settings.top_p == 0.5
assert chat_settings.presence_penalty == 0.5
assert chat_settings.frequency_penalty == 0.5
assert chat_settings.max_tokens == 128
assert chat_settings.stop == ["\n"]
assert chat_settings.number_of_responses == 2
assert chat_settings.logit_bias == {"1": 1}
assert chat_settings.functions is None
def test_openai_chat_request_settings_from_custom_completion_config_with_functions():
settings = AIRequestSettings(
service_id="test_service",
extension_data={
"temperature": 0.5,
"top_p": 0.5,
"presence_penalty": 0.5,
"frequency_penalty": 0.5,
"max_tokens": 128,
"stop": ["\n"],
"number_of_responses": 2,
"functions": [{}],
"function_call": "auto",
"logit_bias": {"1": 1},
"messages": [{"role": "system", "content": "Hello"}],
},
)
chat_settings = OpenAIChatRequestSettings.from_ai_request_settings(settings)
assert chat_settings.temperature == 0.5
assert chat_settings.top_p == 0.5
assert chat_settings.presence_penalty == 0.5
assert chat_settings.frequency_penalty == 0.5
assert chat_settings.max_tokens == 128
assert chat_settings.stop == ["\n"]
assert chat_settings.number_of_responses == 2
assert chat_settings.logit_bias == {"1": 1}
assert chat_settings.functions == [{}]
def test_create_options():
settings = OpenAIChatRequestSettings(
temperature=0.5,
top_p=0.5,
presence_penalty=0.5,
frequency_penalty=0.5,
max_tokens=128,
stop=["\n"],
number_of_responses=2,
logit_bias={"1": 1},
messages=[{"role": "system", "content": "Hello"}],
function_call="auto",
)
options = settings.prepare_settings_dict()
assert options["temperature"] == 0.5
assert options["top_p"] == 0.5
assert options["presence_penalty"] == 0.5
assert options["frequency_penalty"] == 0.5
assert options["max_tokens"] == 128
assert options["stop"] == ["\n"]
assert options["n"] == 2
assert options["logit_bias"] == {"1": 1}
assert not options["stream"]
def test_create_options_azure_data():
az_source = AzureAISearchDataSources(
indexName="test-index", endpoint="test-endpoint", key="test-key"
)
az_data = AzureDataSources(type="AzureCognitiveSearch", parameters=az_source)
extra = ExtraBody(dataSources=[az_data])
settings = AzureChatRequestSettings(extra_body=extra)
options = settings.prepare_settings_dict()
assert options["extra_body"] == extra.model_dump(exclude_none=True, by_alias=True)
def test_azure_open_ai_chat_request_settings_with_data_sources(): # noqa: E501
input_dict = {
"messages": [{"role": "system", "content": "Hello"}],
"extra_body": {
"dataSources": [
{
"type": "AzureCosmosDB",
"parameters": {
"authentication": {
"type": "ConnectionString",
"connectionString": "mongodb+srv://onyourdatatest:{password}$@{cluster-name}.mongocluster.cosmos.azure.com/?tls=true&authMechanism=SCRAM-SHA-256&retrywrites=false&maxIdleTimeMS=120000",
},
"databaseName": "vectordb",
"containerName": "azuredocs",
"indexName": "azuredocindex",
"embeddingDependency": {
"type": "DeploymentName",
"deploymentName": "{embedding deployment name}",
},
"fieldsMapping": {"vectorFields": ["contentvector"]},
},
}
]
},
}
settings = AzureChatRequestSettings.model_validate(
input_dict, strict=True, from_attributes=True
)
assert settings.extra_body["dataSources"][0]["type"] == "AzureCosmosDB"
@@ -0,0 +1,18 @@
# Copyright (c) Microsoft. All rights reserved.
from semantic_kernel.connectors.ai import (
AIRequestSettings,
)
def test_default_complete_request_settings():
settings = AIRequestSettings()
assert settings.service_id is None
assert settings.extension_data == {}
def test_custom_complete_request_settings():
ext_data = {"test": "test"}
settings = AIRequestSettings(service_id="test", extension_data=ext_data)
assert settings.service_id == "test"
assert settings.extension_data["test"] == "test"
@@ -1,95 +0,0 @@
# Copyright (c) Microsoft. All rights reserved.
from semantic_kernel.connectors.ai.chat_request_settings import ChatRequestSettings
from semantic_kernel.connectors.ai.complete_request_settings import (
CompleteRequestSettings,
)
def test_default_complete_request_settings():
settings = CompleteRequestSettings()
assert settings.temperature == 0.0
assert settings.top_p == 1.0
assert settings.presence_penalty == 0.0
assert settings.frequency_penalty == 0.0
assert settings.max_tokens == 256
assert settings.stop_sequences == []
assert settings.number_of_responses == 1
assert settings.logprobs == 0
assert settings.token_selection_biases == {}
assert settings.chat_system_prompt == "Assistant is a large language model."
def test_custom_complete_request_settings():
settings = CompleteRequestSettings(
temperature=0.5,
top_p=0.5,
presence_penalty=0.5,
frequency_penalty=0.5,
max_tokens=128,
stop_sequences=["\n"],
number_of_responses=2,
logprobs=1,
token_selection_biases={1: 1},
chat_system_prompt="Hello",
)
assert settings.temperature == 0.5
assert settings.top_p == 0.5
assert settings.presence_penalty == 0.5
assert settings.frequency_penalty == 0.5
assert settings.max_tokens == 128
assert settings.stop_sequences == ["\n"]
assert settings.number_of_responses == 2
assert settings.logprobs == 1
assert settings.token_selection_biases == {1: 1}
assert settings.chat_system_prompt == "Hello"
def test_default_chat_request_settings():
settings = ChatRequestSettings()
assert settings.temperature == 0.0
assert settings.top_p == 1.0
assert settings.presence_penalty == 0.0
assert settings.frequency_penalty == 0.0
assert settings.max_tokens == 256
assert settings.stop_sequences == []
assert settings.number_of_responses == 1
assert settings.token_selection_biases == {}
def test_complete_request_settings_from_default_completion_config():
settings = CompleteRequestSettings()
chat_settings = ChatRequestSettings.from_completion_config(settings)
chat_settings = ChatRequestSettings()
assert chat_settings.temperature == 0.0
assert chat_settings.top_p == 1.0
assert chat_settings.presence_penalty == 0.0
assert chat_settings.frequency_penalty == 0.0
assert chat_settings.max_tokens == 256
assert chat_settings.stop_sequences == []
assert chat_settings.number_of_responses == 1
assert chat_settings.token_selection_biases == {}
def test_chat_request_settings_from_custom_completion_config():
settings = CompleteRequestSettings(
temperature=0.5,
top_p=0.5,
presence_penalty=0.5,
frequency_penalty=0.5,
max_tokens=128,
stop_sequences=["\n"],
number_of_responses=2,
logprobs=1,
token_selection_biases={1: 1},
chat_system_prompt="Hello",
)
chat_settings = ChatRequestSettings.from_completion_config(settings)
assert chat_settings.temperature == 0.5
assert chat_settings.top_p == 0.5
assert chat_settings.presence_penalty == 0.5
assert chat_settings.frequency_penalty == 0.5
assert chat_settings.max_tokens == 128
assert chat_settings.stop_sequences == ["\n"]
assert chat_settings.number_of_responses == 2
assert chat_settings.token_selection_biases == {1: 1}
@@ -4,61 +4,59 @@ import json
import pytest
from semantic_kernel.connectors.ai.open_ai.models.chat.open_ai_chat_message import (
OpenAIChatMessage,
)
from semantic_kernel.connectors.ai.open_ai.request_settings.open_ai_request_settings import (
OpenAIChatRequestSettings,
)
from semantic_kernel.semantic_functions.chat_prompt_template import ChatPromptTemplate
from semantic_kernel.semantic_functions.prompt_template_config import (
PromptTemplateConfig,
)
from semantic_kernel.template_engine.prompt_template_engine import PromptTemplateEngine
def test_default_prompt_template_config():
prompt_template_config = PromptTemplateConfig()
assert prompt_template_config.schema == 1
assert prompt_template_config.schema_ == 1
assert prompt_template_config.type == "completion"
assert prompt_template_config.description == ""
assert prompt_template_config.completion.temperature == 0.0
assert prompt_template_config.completion.top_p == 1.0
assert prompt_template_config.completion.presence_penalty == 0.0
assert prompt_template_config.completion.frequency_penalty == 0.0
assert prompt_template_config.completion.max_tokens == 256
assert prompt_template_config.completion.number_of_responses == 1
assert prompt_template_config.completion.stop_sequences == []
assert prompt_template_config.completion.token_selection_biases == {}
assert prompt_template_config.completion.chat_system_prompt is None
assert prompt_template_config.completion.extension_data == {}
def test_default_chat_prompt_template_from_empty_dict():
with pytest.raises(KeyError):
_ = PromptTemplateConfig().from_dict({})
_ = PromptTemplateConfig.from_dict({})
def test_default_chat_prompt_template_from_empty_string():
with pytest.raises(json.decoder.JSONDecodeError):
_ = PromptTemplateConfig().from_json("")
_ = PromptTemplateConfig.from_json("")
def test_default_chat_prompt_template_from_empty_json():
with pytest.raises(KeyError):
_ = PromptTemplateConfig().from_json("{}")
_ = PromptTemplateConfig.from_json("{}")
def test_custom_prompt_template_config():
prompt_template_config = PromptTemplateConfig(
schema=2,
schema_=2,
type="completion2",
description="Custom description.",
completion=PromptTemplateConfig.CompletionConfig(
completion=OpenAIChatRequestSettings(
temperature=0.5,
top_p=0.5,
presence_penalty=0.5,
frequency_penalty=0.5,
max_tokens=128,
number_of_responses=2,
stop_sequences=["\n"],
token_selection_biases={1: 1},
chat_system_prompt="Custom system prompt.",
stop=["\n"],
logit_bias={"1": 1.0},
),
)
assert prompt_template_config.schema == 2
assert prompt_template_config.schema_ == 2
assert prompt_template_config.type == "completion2"
assert prompt_template_config.description == "Custom description."
assert prompt_template_config.completion.temperature == 0.5
@@ -67,11 +65,8 @@ def test_custom_prompt_template_config():
assert prompt_template_config.completion.frequency_penalty == 0.5
assert prompt_template_config.completion.max_tokens == 128
assert prompt_template_config.completion.number_of_responses == 2
assert prompt_template_config.completion.stop_sequences == ["\n"]
assert prompt_template_config.completion.token_selection_biases == {1: 1}
assert (
prompt_template_config.completion.chat_system_prompt == "Custom system prompt."
)
assert prompt_template_config.completion.stop == ["\n"]
assert prompt_template_config.completion.logit_bias == {"1": 1.0}
def test_custom_prompt_template_config_from_dict():
@@ -86,26 +81,22 @@ def test_custom_prompt_template_config_from_dict():
"frequency_penalty": 0.5,
"max_tokens": 128,
"number_of_responses": 2,
"stop_sequences": ["\n"],
"token_selection_biases": {1: 1},
"chat_system_prompt": "Custom system prompt.",
"stop": ["\n"],
"logit_bias": {"1": 1},
},
}
prompt_template_config = PromptTemplateConfig().from_dict(prompt_template_dict)
assert prompt_template_config.schema == 2
prompt_template_config = PromptTemplateConfig.from_dict(prompt_template_dict)
assert prompt_template_config.schema_ == 2
assert prompt_template_config.type == "completion2"
assert prompt_template_config.description == "Custom description."
assert prompt_template_config.completion.temperature == 0.5
assert prompt_template_config.completion.top_p == 0.5
assert prompt_template_config.completion.presence_penalty == 0.5
assert prompt_template_config.completion.frequency_penalty == 0.5
assert prompt_template_config.completion.max_tokens == 128
assert prompt_template_config.completion.number_of_responses == 2
assert prompt_template_config.completion.stop_sequences == ["\n"]
assert prompt_template_config.completion.token_selection_biases == {1: 1}
assert (
prompt_template_config.completion.chat_system_prompt == "Custom system prompt."
)
assert prompt_template_config.completion.extension_data["temperature"] == 0.5
assert prompt_template_config.completion.extension_data["top_p"] == 0.5
assert prompt_template_config.completion.extension_data["presence_penalty"] == 0.5
assert prompt_template_config.completion.extension_data["frequency_penalty"] == 0.5
assert prompt_template_config.completion.extension_data["max_tokens"] == 128
assert prompt_template_config.completion.extension_data["number_of_responses"] == 2
assert prompt_template_config.completion.extension_data["stop"] == ["\n"]
assert prompt_template_config.completion.extension_data["logit_bias"] == {"1": 1}
def test_custom_prompt_template_config_from_json():
@@ -121,14 +112,15 @@ def test_custom_prompt_template_config_from_json():
"frequency_penalty": 0.5,
"max_tokens": 128,
"number_of_responses": 2,
"stop_sequences": ["s"],
"token_selection_biases": {"1": 1},
"chat_system_prompt": "Custom system prompt."
"stop": ["s"],
"logit_bias": {"1": 1}
}
}
"""
prompt_template_config = PromptTemplateConfig().from_json(prompt_template_json)
assert prompt_template_config.schema == 2
prompt_template_config = PromptTemplateConfig[OpenAIChatRequestSettings].from_json(
prompt_template_json
)
assert prompt_template_config.schema_ == 2
assert prompt_template_config.type == "completion2"
assert prompt_template_config.description == "Custom description."
assert prompt_template_config.completion.temperature == 0.5
@@ -137,39 +129,36 @@ def test_custom_prompt_template_config_from_json():
assert prompt_template_config.completion.frequency_penalty == 0.5
assert prompt_template_config.completion.max_tokens == 128
assert prompt_template_config.completion.number_of_responses == 2
assert prompt_template_config.completion.stop_sequences == ["s"]
assert prompt_template_config.completion.token_selection_biases == {1: 1}
assert (
prompt_template_config.completion.chat_system_prompt == "Custom system prompt."
)
assert prompt_template_config.completion.stop == ["s"]
assert prompt_template_config.completion.logit_bias == {"1": 1}
def test_chat_prompt_template():
chat_prompt_template = ChatPromptTemplate(
"{{$user_input}}",
None,
PromptTemplateEngine(),
prompt_config=PromptTemplateConfig(),
)
assert chat_prompt_template._messages == []
assert chat_prompt_template.messages == []
def test_chat_prompt_template_with_system_prompt():
prompt_template_config = PromptTemplateConfig(
completion=PromptTemplateConfig.CompletionConfig(
chat_system_prompt="Custom system prompt.",
)
prompt_template_config = PromptTemplateConfig[
OpenAIChatRequestSettings
].from_completion_parameters(
messages=[{"role": "system", "content": "Custom system prompt."}],
)
chat_prompt_template = ChatPromptTemplate(
chat_prompt_template = ChatPromptTemplate[OpenAIChatMessage](
"{{$user_input}}",
None,
PromptTemplateEngine(),
prompt_config=prompt_template_config,
)
print(chat_prompt_template._messages)
print(chat_prompt_template.messages)
assert len(chat_prompt_template.messages) == 1
assert chat_prompt_template._messages[0].role == "system"
assert chat_prompt_template.messages[0].role == "system"
assert (
chat_prompt_template._messages[0].content_template._template
chat_prompt_template.messages[0].content_template.template
== "Custom system prompt."
)