mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
Merge branch 'main' into update-modelid
This commit is contained in:
@@ -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
@@ -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
|
||||
Generated
+91
-4
@@ -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"},
|
||||
|
||||
@@ -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."
|
||||
|
||||
+11
-11
@@ -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."""
|
||||
|
||||
+79
@@ -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
|
||||
+86
@@ -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
|
||||
+18
-12
@@ -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,
|
||||
|
||||
-106
@@ -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
|
||||
|
||||
-138
@@ -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
|
||||
|
||||
+31
-20
@@ -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
|
||||
|
||||
+29
-4
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user