Merge branch 'microsoft:main' into update-modelid

This commit is contained in:
Sandra Ahlgrimm
2024-01-22 16:08:03 +01:00
committed by GitHub
179 changed files with 3425 additions and 2565 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
<IsPackable>true</IsPackable>
<!-- Package validation. Baseline Version should be lower than current version. -->
<PackageValidationBaselineVersion>1.0.1</PackageValidationBaselineVersion>
<PackageValidationBaselineVersion>1.1.0</PackageValidationBaselineVersion>
<!-- Validate assembly attributes only for Publish builds -->
<NoWarn Condition="'$(Configuration)' != 'Publish'">$(NoWarn);CP0003</NoWarn>
<!-- Do not validate reference assemblies -->
@@ -9,12 +9,17 @@ using Azure.AI.OpenAI;
using Azure.Core.Pipeline;
using Microsoft.SemanticKernel;
using RepoUtils;
using Xunit;
using Xunit.Abstractions;
public static class Example52_CustomOpenAIClient
namespace Examples;
public sealed class Example52_CustomOpenAIClient : BaseTest
{
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
Console.WriteLine("======== Using a custom OpenAI client ========");
this.WriteLine("======== Using a custom OpenAI client ========");
string endpoint = TestConfiguration.AzureOpenAI.Endpoint;
string deploymentName = TestConfiguration.AzureOpenAI.ChatDeploymentName;
@@ -22,7 +27,7 @@ public static class Example52_CustomOpenAIClient
if (endpoint is null || deploymentName is null || apiKey is null)
{
Console.WriteLine("Azure OpenAI credentials not found. Skipping example.");
this.WriteLine("Azure OpenAI credentials not found. Skipping example.");
return;
}
@@ -51,8 +56,10 @@ public static class Example52_CustomOpenAIClient
kernel.Plugins["FunPlugin"]["Excuses"],
new() { ["input"] = "I have no homework" }
);
Console.WriteLine(result.GetValue<string>());
this.WriteLine(result.GetValue<string>());
httpClient.Dispose();
}
public Example52_CustomOpenAIClient(ITestOutputHelper output) : base(output) { }
}
@@ -1,22 +1,23 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
public class Example58_ConfigureExecutionSettings : BaseTest
public sealed class Example58_ConfigureExecutionSettings : BaseTest
{
/// <summary>
/// Show how to configure model execution settings
/// </summary>
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
Console.WriteLine("======== Example58_ConfigureExecutionSettings ========");
this.WriteLine("======== Example58_ConfigureExecutionSettings ========");
string serviceId = TestConfiguration.AzureOpenAI.ServiceId;
string apiKey = TestConfiguration.AzureOpenAI.ApiKey;
@@ -26,7 +27,7 @@ public class Example58_ConfigureExecutionSettings : BaseTest
if (apiKey == null || chatDeploymentName == null || endpoint == null)
{
Console.WriteLine("AzureOpenAI endpoint, apiKey, or deploymentName not found. Skipping example.");
this.WriteLine("AzureOpenAI endpoint, apiKey, or deploymentName not found. Skipping example.");
return;
}
@@ -50,7 +51,7 @@ public class Example58_ConfigureExecutionSettings : BaseTest
MaxTokens = 60,
Temperature = 0.7
}));
Console.WriteLine(result.GetValue<string>());
this.WriteLine(result.GetValue<string>());
// Option 2:
// Load prompt template configuration including the execution settings from a JSON payload
@@ -74,7 +75,7 @@ public class Example58_ConfigureExecutionSettings : BaseTest
var func = kernel.CreateFunctionFromPrompt(promptConfig);
result = await kernel.InvokeAsync(func);
Console.WriteLine(result.GetValue<string>());
this.WriteLine(result.GetValue<string>());
/* OUTPUT (using gpt4):
Hello! As an AI language model, I can help you with a variety of tasks, such as:
@@ -1,6 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
@@ -153,15 +152,15 @@ public class Example70_Agent : BaseTest
try
{
// Display agent identifier.
Console.WriteLine($"[{agent.Id}]");
this.WriteLine($"[{agent.Id}]");
// Process each user message and agent response.
foreach (var response in messages.Select(m => thread.InvokeAsync(agent, m, arguments)))
{
await foreach (var message in response)
{
Console.WriteLine($"[{message.Id}]");
Console.WriteLine($"# {message.Role}: {message.Content}");
this.WriteLine($"[{message.Id}]");
this.WriteLine($"# {message.Role}: {message.Content}");
}
}
}
@@ -5,12 +5,16 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.SemanticKernel.Experimental.Agents;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// ReSharper disable once InconsistentNaming
/// <summary>
/// Showcase usage of code_interpreter and retrieval tools.
/// </summary>
public static class Example75_AgentTools
public sealed class Example75_AgentTools : BaseTest
{
/// <summary>
/// Specific model is required that supports agents and parallel function calling.
@@ -19,18 +23,19 @@ public static class Example75_AgentTools
private const string OpenAIFunctionEnabledModel = "gpt-4-1106-preview";
// Track agents for clean-up
private static readonly List<IAgent> s_agents = new();
private readonly List<IAgent> _agents = new();
/// <summary>
/// Show how to utilize code_interpreter and retrieval tools.
/// </summary>
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
Console.WriteLine("======== Example75_AgentTools ========");
this.WriteLine("======== Example75_AgentTools ========");
if (TestConfiguration.OpenAI.ApiKey == null)
{
Console.WriteLine("OpenAI apiKey not found. Skipping example.");
this.WriteLine("OpenAI apiKey not found. Skipping example.");
return;
}
@@ -41,9 +46,9 @@ public static class Example75_AgentTools
await RunRetrievalToolAsync();
}
private static async Task RunCodeInterpreterToolAsync()
private async Task RunCodeInterpreterToolAsync()
{
Console.WriteLine("======== Run:CodeInterpreterTool ========");
this.WriteLine("======== Run:CodeInterpreterTool ========");
var builder =
new AgentBuilder()
@@ -68,13 +73,13 @@ public static class Example75_AgentTools
}
finally
{
await Task.WhenAll(s_agents.Select(a => a.DeleteAsync()));
await Task.WhenAll(this._agents.Select(a => a.DeleteAsync()));
}
}
private static async Task RunRetrievalToolAsync()
private async Task RunRetrievalToolAsync()
{
Console.WriteLine("======== Run:RunRetrievalTool ========");
this.WriteLine("======== Run:RunRetrievalTool ========");
// REQUIRED:
//
@@ -111,7 +116,7 @@ public static class Example75_AgentTools
}
finally
{
await Task.WhenAll(s_agents.Select(a => a.DeleteAsync()));
await Task.WhenAll(this._agents.Select(a => a.DeleteAsync()));
}
}
@@ -119,21 +124,21 @@ public static class Example75_AgentTools
/// Common chat loop used for: RunCodeInterpreterToolAsync and RunRetrievalToolAsync.
/// Processes each question for both "default" and "enabled" agents.
/// </summary>
private static async Task ChatAsync(
private async Task ChatAsync(
IAgent defaultAgent,
IAgent enabledAgent,
params string[] questions)
{
foreach (var question in questions)
{
Console.WriteLine("\nDEFAULT AGENT:");
this.WriteLine("\nDEFAULT AGENT:");
await InvokeAgentAsync(defaultAgent, question);
Console.WriteLine("\nTOOL ENABLED AGENT:");
this.WriteLine("\nTOOL ENABLED AGENT:");
await InvokeAgentAsync(enabledAgent, question);
}
static async Task InvokeAgentAsync(IAgent agent, string question)
async Task InvokeAgentAsync(IAgent agent, string question)
{
await foreach (var message in agent.InvokeAsync(question))
{
@@ -143,26 +148,28 @@ public static class Example75_AgentTools
content = content.Replace(annotation.Label, string.Empty, StringComparison.Ordinal);
}
Console.WriteLine($"# {message.Role}: {content}");
this.WriteLine($"# {message.Role}: {content}");
if (message.Annotations.Count > 0)
{
Console.WriteLine("\n# files:");
this.WriteLine("\n# files:");
foreach (var annotation in message.Annotations)
{
Console.WriteLine($"* {annotation.FileId}");
this.WriteLine($"* {annotation.FileId}");
}
}
}
Console.WriteLine();
this.WriteLine();
}
}
private static IAgent Track(IAgent agent)
private IAgent Track(IAgent agent)
{
s_agents.Add(agent);
this._agents.Add(agent);
return agent;
}
public Example75_AgentTools(ITestOutputHelper output) : base(output) { }
}
@@ -47,6 +47,11 @@ public class Step1_Create_Kernel : BaseTest
// Example 4. Invoke the kernel with a templated prompt and execution settings
arguments = new(new OpenAIPromptExecutionSettings { MaxTokens = 500, Temperature = 0.5 }) { { "topic", "dogs" } };
WriteLine(await kernel.InvokePromptAsync("Tell me a story about {{$topic}}", arguments));
// Example 5. Invoke the kernel with a templated prompt and execution settings configured to return JSON
#pragma warning disable SKEXP0013
arguments = new(new OpenAIPromptExecutionSettings { ResponseFormat = "json_object" }) { { "topic", "chocolate" } };
WriteLine(await kernel.InvokePromptAsync("Create a recipe for a {{$topic}} cake in JSON format", arguments));
}
public Step1_Create_Kernel(ITestOutputHelper output) : base(output)
@@ -179,6 +179,7 @@ internal static class RestApiOperationExtensions
expand: false,
RestApiOperationParameterLocation.Body,
RestApiOperationParameterStyle.Simple,
defaultValue: property.DefaultValue,
description: property.Description,
schema: property.Schema));
}
@@ -50,7 +50,7 @@ public sealed class RestApiOperationParameter
/// <summary>
/// The default value.
/// </summary>
public string? DefaultValue { get; }
public object? DefaultValue { get; }
/// <summary>
/// Specifies whether arrays and objects should generate separate parameters for each array item or object property.
@@ -83,7 +83,7 @@ public sealed class RestApiOperationParameter
RestApiOperationParameterLocation location,
RestApiOperationParameterStyle? style = null,
string? arrayItemType = null,
string? defaultValue = null,
object? defaultValue = null,
string? description = null,
KernelJsonSchema? schema = null)
{
@@ -39,22 +39,30 @@ public sealed class RestApiOperationPayloadProperty
/// </summary>
public KernelJsonSchema? Schema { get; }
/// <summary>
/// The default value.
/// </summary>
public object? DefaultValue { get; }
/// <summary>
/// Creates an instance of a <see cref="RestApiOperationPayloadProperty"/> class.
/// </summary>
/// <param name="name">Property name.</param>
/// <param name="type">Property type.</param>
/// <param name="isRequired">Flag specifying if the property is required or not.</param>
/// <param name="properties">Properties.</param>
/// <param name="description">Property description.</param>
/// <param name="name">The name of the property.</param>
/// <param name="type">The type of the property.</param>
/// <param name="isRequired">A flag specifying if the property is required or not.</param>
/// <param name="properties">A list of properties for the payload property.</param>
/// <param name="description">A description of the property.</param>
/// <param name="schema">The schema of the payload property.</param>
/// <param name="defaultValue">The default value of the property.</param>
/// <returns>Returns a new instance of the <see cref="RestApiOperationPayloadProperty"/> class.</returns>
public RestApiOperationPayloadProperty(
string name,
string type,
bool isRequired,
IList<RestApiOperationPayloadProperty> properties,
string? description = null,
KernelJsonSchema? schema = null)
KernelJsonSchema? schema = null,
object? defaultValue = null)
{
this.Name = name;
this.Type = type;
@@ -62,5 +70,6 @@ public sealed class RestApiOperationPayloadProperty
this.Description = description;
this.Properties = properties;
this.Schema = schema;
this.DefaultValue = defaultValue;
}
}
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
@@ -313,7 +312,8 @@ internal sealed class OpenApiDocumentParser : IOpenApiDocumentParser
requiredProperties.Contains(propertyName),
GetPayloadProperties(operationId, propertySchema, requiredProperties, level + 1),
propertySchema.Description,
propertySchema.ToJsonSchema());
propertySchema.ToJsonSchema(),
GetParameterValue(propertySchema.Default));
result.Add(property);
}
@@ -326,7 +326,7 @@ internal sealed class OpenApiDocumentParser : IOpenApiDocumentParser
/// </summary>
/// <param name="valueMetadata">The value metadata.</param>
/// <returns>The parameter value.</returns>
private static string? GetParameterValue(IOpenApiAny valueMetadata)
private static object? GetParameterValue(IOpenApiAny valueMetadata)
{
if (valueMetadata is not IOpenApiPrimitive value)
{
@@ -335,17 +335,17 @@ internal sealed class OpenApiDocumentParser : IOpenApiDocumentParser
return value.PrimitiveType switch
{
PrimitiveType.Integer => ((OpenApiInteger)value).Value.ToString(CultureInfo.InvariantCulture),
PrimitiveType.Long => ((OpenApiLong)value).Value.ToString(CultureInfo.InvariantCulture),
PrimitiveType.Float => ((OpenApiFloat)value).Value.ToString(CultureInfo.InvariantCulture),
PrimitiveType.Double => ((OpenApiDouble)value).Value.ToString(CultureInfo.InvariantCulture),
PrimitiveType.String => ((OpenApiString)value).Value.ToString(CultureInfo.InvariantCulture),
PrimitiveType.Byte => Convert.ToBase64String(((OpenApiByte)value).Value),
PrimitiveType.Binary => Encoding.UTF8.GetString(((OpenApiBinary)value).Value),
PrimitiveType.Boolean => ((OpenApiBoolean)value).Value.ToString(CultureInfo.InvariantCulture),
PrimitiveType.Date => ((OpenApiDate)value).Value.ToString("o").Substring(0, 10),
PrimitiveType.DateTime => ((OpenApiDateTime)value).Value.ToString(CultureInfo.InvariantCulture),
PrimitiveType.Password => ((OpenApiPassword)value).Value.ToString(CultureInfo.InvariantCulture),
PrimitiveType.Integer => ((OpenApiInteger)value).Value,
PrimitiveType.Long => ((OpenApiLong)value).Value,
PrimitiveType.Float => ((OpenApiFloat)value).Value,
PrimitiveType.Double => ((OpenApiDouble)value).Value,
PrimitiveType.String => ((OpenApiString)value).Value,
PrimitiveType.Byte => ((OpenApiByte)value).Value,
PrimitiveType.Binary => ((OpenApiBinary)value).Value,
PrimitiveType.Boolean => ((OpenApiBoolean)value).Value,
PrimitiveType.Date => ((OpenApiDate)value).Value,
PrimitiveType.DateTime => ((OpenApiDateTime)value).Value,
PrimitiveType.Password => ((OpenApiPassword)value).Value,
_ => throw new KernelException($"The value type - {value.PrimitiveType} is not supported."),
};
}
@@ -280,18 +280,53 @@ public class RestApiOperationExtensionsTests
private static RestApiOperationPayload CreateTestJsonPayload()
{
var name = new RestApiOperationPayloadProperty("name", "string", true, new List<RestApiOperationPayloadProperty>(), "The name.");
var name = new RestApiOperationPayloadProperty(
name: "name",
type: "string",
isRequired: true,
properties: new List<RestApiOperationPayloadProperty>(),
description: "The name.");
var leader = new RestApiOperationPayloadProperty("leader", "string", true, new List<RestApiOperationPayloadProperty>(), "The leader.");
var leader = new RestApiOperationPayloadProperty(
name: "leader",
type: "string",
isRequired: true,
properties: new List<RestApiOperationPayloadProperty>(),
description: "The leader.");
var landmarks = new RestApiOperationPayloadProperty("landmarks", "array", false, new List<RestApiOperationPayloadProperty>(), "The landmarks.");
var location = new RestApiOperationPayloadProperty("location", "object", true, new[] { landmarks }, "The location.");
var landmarks = new RestApiOperationPayloadProperty(
name: "landmarks",
type: "array",
isRequired: false,
properties: new List<RestApiOperationPayloadProperty>(),
description: "The landmarks.");
var rulingCouncil = new RestApiOperationPayloadProperty("rulingCouncil", "object", true, new[] { leader }, "The ruling council.");
var location = new RestApiOperationPayloadProperty(
name: "location",
type: "object",
isRequired: true,
properties: new[] { landmarks },
description: "The location.");
var population = new RestApiOperationPayloadProperty("population", "integer", true, new List<RestApiOperationPayloadProperty>(), "The population.");
var rulingCouncil = new RestApiOperationPayloadProperty(
name: "rulingCouncil",
type: "object",
isRequired: true,
properties: new[] { leader },
description: "The ruling council.");
var hasMagicWards = new RestApiOperationPayloadProperty("hasMagicWards", "boolean", false, new List<RestApiOperationPayloadProperty>());
var population = new RestApiOperationPayloadProperty(
name: "population",
type: "integer",
isRequired: true,
properties: new List<RestApiOperationPayloadProperty>(),
description: "The population.");
var hasMagicWards = new RestApiOperationPayloadProperty(
name: "hasMagicWards",
type: "boolean",
isRequired: false,
properties: new List<RestApiOperationPayloadProperty>());
return new RestApiOperationPayload("application/json", new[] { name, location, rulingCouncil, population, hasMagicWards });
}
@@ -175,7 +175,7 @@ public sealed class OpenApiDocumentParserV20Tests : IDisposable
var apiVersion = GetParameterMetadata(operations, "SetSecret", RestApiOperationParameterLocation.Header, "X-API-Version");
Assert.Equal("integer", apiVersion.Type);
Assert.Equal("10", apiVersion.DefaultValue);
Assert.Equal(10, apiVersion.DefaultValue);
Assert.Equal("Requested API version.", apiVersion.Description);
Assert.True(apiVersion.IsRequired);
}
@@ -225,7 +225,7 @@ public sealed class OpenApiDocumentParserV20Tests : IDisposable
var operations = await this._sut.ParseAsync(this._openApiDocument);
// Assert
Assert.Equal(3, operations.Count);
Assert.Equal(4, operations.Count);
}
[Fact]
@@ -293,6 +293,65 @@ public sealed class OpenApiDocumentParserV20Tests : IDisposable
JsonSerializer.Serialize(response.Schema));
}
[Fact]
public async Task ItCanWorkWithDefaultParametersOfVariousTypesAsync()
{
//Act
var operations = await this._sut.ParseAsync(this._openApiDocument);
//Assert
Assert.NotNull(operations);
Assert.True(operations.Any());
var operation = operations.Single(o => o.Id == "TestDefaultValues");
Assert.NotNull(operation);
var parameters = operation.GetParameters();
Assert.Equal(11, parameters.Count);
var stringParameter = parameters.Single(p => p.Name == "string-parameter");
Assert.Equal("string-value", stringParameter.DefaultValue);
var booleanParameter = parameters.Single(p => p.Name == "boolean-parameter");
Assert.True(booleanParameter.DefaultValue is bool value);
var integerParameter = parameters.Single(p => p.Name == "integer-parameter");
Assert.True(integerParameter.DefaultValue is int);
Assert.Equal(281, integerParameter.DefaultValue);
var longParameter = parameters.Single(p => p.Name == "long-parameter");
Assert.True(longParameter.DefaultValue is long);
Assert.Equal((long)-2814, longParameter.DefaultValue);
var floatParameter = parameters.Single(p => p.Name == "float-parameter");
Assert.True(floatParameter.DefaultValue is float);
Assert.Equal((float)12.01, floatParameter.DefaultValue);
var doubleParameter = parameters.Single(p => p.Name == "double-parameter");
Assert.True(doubleParameter.DefaultValue is double);
Assert.Equal((double)-12.01, doubleParameter.DefaultValue);
var encodedCharactersParameter = parameters.Single(p => p.Name == "encoded-characters-parameter");
Assert.True(encodedCharactersParameter.DefaultValue is byte[]);
Assert.Equal(new byte[] { 1, 2, 3, 4, 5 }, encodedCharactersParameter.DefaultValue);
var binaryDataParameter = parameters.Single(p => p.Name == "binary-data-parameter");
Assert.True(binaryDataParameter.DefaultValue is byte[]);
Assert.Equal(new byte[] { 50, 51, 52, 53, 54 }, binaryDataParameter.DefaultValue);
var dateParameter = parameters.Single(p => p.Name == "date-parameter");
Assert.True(dateParameter.DefaultValue is DateTime);
Assert.Equal(new DateTime(2017, 07, 21), dateParameter.DefaultValue);
var dateTimeParameter = parameters.Single(p => p.Name == "date-time-parameter");
Assert.True(dateTimeParameter.DefaultValue is DateTimeOffset);
Assert.Equal(new DateTimeOffset(2017, 07, 21, 17, 32, 28, TimeSpan.Zero), dateTimeParameter.DefaultValue);
var passwordParameter = parameters.Single(p => p.Name == "password-parameter");
Assert.True(passwordParameter.DefaultValue is string);
Assert.Equal("password-value", passwordParameter.DefaultValue);
}
private static RestApiOperationParameter GetParameterMetadata(IList<RestApiOperation> operations, string operationId,
RestApiOperationParameterLocation location, string name)
{
@@ -161,7 +161,7 @@ public sealed class OpenApiDocumentParserV30Tests : IDisposable
var apiVersion = GetParameterMetadata(operations, "SetSecret", RestApiOperationParameterLocation.Header, "X-API-Version");
Assert.Equal("integer", apiVersion.Type);
Assert.Equal("10", apiVersion.DefaultValue);
Assert.Equal(10, apiVersion.DefaultValue);
Assert.Equal("Requested API version.", apiVersion.Description);
Assert.True(apiVersion.IsRequired);
}
@@ -226,7 +226,7 @@ public sealed class OpenApiDocumentParserV30Tests : IDisposable
var operations = await this._sut.ParseAsync(this._openApiDocument);
// Assert
Assert.Equal(3, operations.Count);
Assert.Equal(4, operations.Count);
}
[Fact]
@@ -366,6 +366,65 @@ public sealed class OpenApiDocumentParserV30Tests : IDisposable
JsonSerializer.Serialize(response.Schema));
}
[Fact]
public async Task ItCanWorkWithDefaultParametersOfVariousTypesAsync()
{
//Act
var operations = await this._sut.ParseAsync(this._openApiDocument);
//Assert
Assert.NotNull(operations);
Assert.True(operations.Any());
var operation = operations.Single(o => o.Id == "TestDefaultValues");
Assert.NotNull(operation);
var parameters = operation.GetParameters();
Assert.Equal(11, parameters.Count);
var stringParameter = parameters.Single(p => p.Name == "string-parameter");
Assert.Equal("string-value", stringParameter.DefaultValue);
var booleanParameter = parameters.Single(p => p.Name == "boolean-parameter");
Assert.True(booleanParameter.DefaultValue is bool value);
var integerParameter = parameters.Single(p => p.Name == "integer-parameter");
Assert.True(integerParameter.DefaultValue is int);
Assert.Equal(281, integerParameter.DefaultValue);
var longParameter = parameters.Single(p => p.Name == "long-parameter");
Assert.True(longParameter.DefaultValue is long);
Assert.Equal((long)-2814, longParameter.DefaultValue);
var floatParameter = parameters.Single(p => p.Name == "float-parameter");
Assert.True(floatParameter.DefaultValue is float);
Assert.Equal((float)12.01, floatParameter.DefaultValue);
var doubleParameter = parameters.Single(p => p.Name == "double-parameter");
Assert.True(doubleParameter.DefaultValue is double);
Assert.Equal((double)-12.01, doubleParameter.DefaultValue);
var encodedCharactersParameter = parameters.Single(p => p.Name == "encoded-characters-parameter");
Assert.True(encodedCharactersParameter.DefaultValue is byte[]);
Assert.Equal(new byte[] { 1, 2, 3, 4, 5 }, encodedCharactersParameter.DefaultValue);
var binaryDataParameter = parameters.Single(p => p.Name == "binary-data-parameter");
Assert.True(binaryDataParameter.DefaultValue is byte[]);
Assert.Equal(new byte[] { 50, 51, 52, 53, 54 }, binaryDataParameter.DefaultValue);
var dateParameter = parameters.Single(p => p.Name == "date-parameter");
Assert.True(dateParameter.DefaultValue is DateTime);
Assert.Equal(new DateTime(2017, 07, 21), dateParameter.DefaultValue);
var dateTimeParameter = parameters.Single(p => p.Name == "date-time-parameter");
Assert.True(dateTimeParameter.DefaultValue is DateTimeOffset);
Assert.Equal(new DateTimeOffset(2017, 07, 21, 17, 32, 28, TimeSpan.Zero), dateTimeParameter.DefaultValue);
var passwordParameter = parameters.Single(p => p.Name == "password-parameter");
Assert.True(passwordParameter.DefaultValue is string);
Assert.Equal("password-value", passwordParameter.DefaultValue);
}
private static MemoryStream ModifyOpenApiDocument(Stream openApiDocument, Action<JsonObject> transformer)
{
var json = JsonSerializer.Deserialize<JsonObject>(openApiDocument);
@@ -176,7 +176,7 @@ public sealed class OpenApiDocumentParserV31Tests : IDisposable
var apiVersion = GetParameterMetadata(operations, "SetSecret", RestApiOperationParameterLocation.Header, "X-API-Version");
Assert.Equal("integer", apiVersion.Type);
Assert.Equal("10", apiVersion.DefaultValue);
Assert.Equal(10, apiVersion.DefaultValue);
Assert.Equal("Requested API version.", apiVersion.Description);
Assert.True(apiVersion.IsRequired);
}
@@ -226,7 +226,7 @@ public sealed class OpenApiDocumentParserV31Tests : IDisposable
var operations = await this._sut.ParseAsync(this._openApiDocument);
// Assert
Assert.Equal(3, operations.Count);
Assert.Equal(4, operations.Count);
}
[Fact]
@@ -343,6 +343,65 @@ public sealed class OpenApiDocumentParserV31Tests : IDisposable
JsonSerializer.Serialize(response.Schema));
}
[Fact]
public async Task ItCanWorkWithDefaultParametersOfVariousTypesAsync()
{
//Act
var operations = await this._sut.ParseAsync(this._openApiDocument);
//Assert
Assert.NotNull(operations);
Assert.True(operations.Any());
var operation = operations.Single(o => o.Id == "TestDefaultValues");
Assert.NotNull(operation);
var parameters = operation.GetParameters();
Assert.Equal(11, parameters.Count);
var stringParameter = parameters.Single(p => p.Name == "string-parameter");
Assert.Equal("string-value", stringParameter.DefaultValue);
var booleanParameter = parameters.Single(p => p.Name == "boolean-parameter");
Assert.True(booleanParameter.DefaultValue is bool value);
var integerParameter = parameters.Single(p => p.Name == "integer-parameter");
Assert.True(integerParameter.DefaultValue is int);
Assert.Equal(281, integerParameter.DefaultValue);
var longParameter = parameters.Single(p => p.Name == "long-parameter");
Assert.True(longParameter.DefaultValue is long);
Assert.Equal((long)-2814, longParameter.DefaultValue);
var floatParameter = parameters.Single(p => p.Name == "float-parameter");
Assert.True(floatParameter.DefaultValue is float);
Assert.Equal((float)12.01, floatParameter.DefaultValue);
var doubleParameter = parameters.Single(p => p.Name == "double-parameter");
Assert.True(doubleParameter.DefaultValue is double);
Assert.Equal((double)-12.01, doubleParameter.DefaultValue);
var encodedCharactersParameter = parameters.Single(p => p.Name == "encoded-characters-parameter");
Assert.True(encodedCharactersParameter.DefaultValue is byte[]);
Assert.Equal(new byte[] { 1, 2, 3, 4, 5 }, encodedCharactersParameter.DefaultValue);
var binaryDataParameter = parameters.Single(p => p.Name == "binary-data-parameter");
Assert.True(binaryDataParameter.DefaultValue is byte[]);
Assert.Equal(new byte[] { 50, 51, 52, 53, 54 }, binaryDataParameter.DefaultValue);
var dateParameter = parameters.Single(p => p.Name == "date-parameter");
Assert.True(dateParameter.DefaultValue is DateTime);
Assert.Equal(new DateTime(2017, 07, 21), dateParameter.DefaultValue);
var dateTimeParameter = parameters.Single(p => p.Name == "date-time-parameter");
Assert.True(dateTimeParameter.DefaultValue is DateTimeOffset);
Assert.Equal(new DateTimeOffset(2017, 07, 21, 17, 32, 28, TimeSpan.Zero), dateTimeParameter.DefaultValue);
var passwordParameter = parameters.Single(p => p.Name == "password-parameter");
Assert.True(passwordParameter.DefaultValue is string);
Assert.Equal("password-value", passwordParameter.DefaultValue);
}
private static MemoryStream ModifyOpenApiDocument(Stream openApiDocument, Action<IDictionary<string, object>> transformer)
{
var serializer = new SharpYaml.Serialization.Serializer();
@@ -212,7 +212,7 @@
{
"in": "body",
"name": "body",
"description": "excuse event",
"description": "excuse event",
"schema": {
"type": "string"
}
@@ -227,6 +227,101 @@
}
}
}
},
"/test-default-values/{string-parameter}": {
"put": {
"description": "Operation to test default parameter values.",
"operationId": "TestDefaultValues",
"parameters": [
{
"in": "path",
"name": "string-parameter",
"default": "string-value",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "boolean-parameter",
"type": "boolean",
"default": true
},
{
"in": "header",
"name": "integer-parameter",
"type": "integer",
"format": "int32",
"default": 281
},
{
"in": "header",
"name": "long-parameter",
"type": "integer",
"format": "int64",
"default": -2814
},
{
"in": "body",
"name": "body",
"required": true,
"schema": {
"properties": {
"attributes": {
"description": "attributes",
"properties": {
"double-parameter": {
"type": "number",
"format": "double",
"default": -12.01
}
},
"type": "object"
},
"float-parameter": {
"type": "number",
"format": "float",
"default": 12.01
},
"encoded-characters-parameter": {
"type": "string",
"format": "byte",
"default": "AQIDBAU="
},
"binary-data-parameter": {
"type": "string",
"format": "binary",
"default": "23456"
},
"date-parameter": {
"type": "string",
"format": "date",
"default": "2017-07-21"
},
"date-time-parameter": {
"type": "string",
"format": "date-time",
"default": "2017-07-21T17:32:28Z"
},
"password-parameter": {
"type": "string",
"format": "password",
"default": "password-value"
}
},
"type": "object"
}
}
],
"responses": {
"200": {
"description": "The OK response",
"schema": {
"type": "string"
}
}
},
"summary": "Get secret"
}
}
},
"produces": [],
@@ -201,6 +201,109 @@
}
}
}
},
"/test-default-values/{string-parameter}": {
"put": {
"summary": "Operation to test default parameter values.",
"description": "Operation to test default parameter values.",
"operationId": "TestDefaultValues",
"parameters": [
{
"name": "string-parameter",
"in": "path",
"required": true,
"schema": {
"type": "string",
"default": "string-value"
}
},
{
"name": "boolean-parameter",
"in": "query",
"schema": {
"type": "boolean",
"default": true
}
},
{
"name": "integer-parameter",
"in": "header",
"schema": {
"type": "integer",
"format": "int32",
"default": 281
}
},
{
"name": "long-parameter",
"in": "header",
"schema": {
"type": "integer",
"format": "int64",
"default": -2814
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"attributes": {
"type": "object",
"properties": {
"double-parameter": {
"type": "number",
"format": "double",
"default": -12.01
}
},
"description": "attributes"
},
"float-parameter": {
"type": "number",
"format": "float",
"default": 12.01
},
"encoded-characters-parameter": {
"type": "string",
"format": "byte",
"default": "AQIDBAU="
},
"binary-data-parameter": {
"type": "string",
"format": "binary",
"default": "23456"
},
"date-parameter": {
"type": "string",
"format": "date",
"default": "2017-07-21"
},
"date-time-parameter": {
"type": "string",
"format": "date-time",
"default": "2017-07-21T17:32:28.0000000+00:00"
},
"password-parameter": {
"type": "string",
"format": "password",
"default": "password-value"
}
}
}
}
},
"required": true,
"x-bodyName": "body"
},
"responses": {
"200": {
"description": "The OK response"
}
}
}
}
},
"components": {
@@ -35,7 +35,6 @@ paths:
- name: explodeFormParam
in: query
style: form
explode: true
schema:
type: array
items:
@@ -133,6 +132,78 @@ paths:
text/plain:
schema:
type: string
'/test-default-values/{string-parameter}':
put:
summary: Operation to test default parameter values.
description: Operation to test default parameter values.
operationId: TestDefaultValues
parameters:
- name: string-parameter
in: path
required: true
schema:
type: string
default: string-value
- name: boolean-parameter
in: query
schema:
type: boolean
default: true
- name: integer-parameter
in: header
schema:
type: integer
format: int32
default: 281
- name: long-parameter
in: header
schema:
type: integer
format: int64
default: -2814
requestBody:
content:
application/json:
schema:
type: object
properties:
attributes:
type: object
properties:
double-parameter:
type: number
format: double
default: -12.01
description: attributes
float-parameter:
type: number
format: float
default: 12.01
encoded-characters-parameter:
type: string
format: byte
default: AQIDBAU=
binary-data-parameter:
type: string
format: binary
default: '23456'
date-parameter:
type: string
format: date
default: '2017-07-21'
date-time-parameter:
type: string
format: date-time
default: '2017-07-21T17:32:28.0000000+00:00'
password-parameter:
type: string
format: password
default: password-value
required: true
x-bodyName: body
responses:
'200':
description: The OK response
components:
securitySchemes:
oauth2_auth:
@@ -22,6 +22,8 @@
</ItemGroup>
<ItemGroup>
<None Remove="prompts\GenerateStoryHandlebars.yaml" />
<None Remove="skills\FunSkill\Joke\config.json" />
<None Remove="skills\FunSkill\Joke\skprompt.txt" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" />
@@ -89,4 +91,13 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="skills\FunSkill\Joke\config.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="skills\FunSkill\Joke\skprompt.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
</Project>
@@ -13,6 +13,7 @@ using Microsoft.SemanticKernel.Plugins.Web;
using Microsoft.SemanticKernel.Plugins.Web.Bing;
using SemanticKernel.IntegrationTests.Fakes;
using SemanticKernel.IntegrationTests.TestSettings;
using xRetry;
using Xunit;
using Xunit.Abstractions;
@@ -76,7 +77,7 @@ public sealed class FunctionCallingStepwisePlannerTests : IDisposable
}
}
[Fact]
[RetryFact(typeof(HttpOperationException))]
public async Task DoesNotThrowWhenPluginFunctionThrowsNonCriticalExceptionAsync()
{
// Arrange
@@ -107,7 +108,7 @@ public sealed class FunctionCallingStepwisePlannerTests : IDisposable
Assert.Contains("Email_SendEmail", serializedChatHistory, StringComparison.InvariantCultureIgnoreCase);
}
[Fact]
[RetryFact(typeof(HttpOperationException))]
public async Task ThrowsWhenPluginFunctionThrowsCriticalExceptionAsync()
{
// Arrange
@@ -42,7 +42,7 @@ public class SamplePluginsTests
Assert.NotNull(kernel.Plugins);
var metadata = kernel.Plugins.GetFunctionsMetadata();
Assert.NotNull(metadata);
Assert.Equal(48, metadata.Count); // currently we have 48 sample plugin functions
Assert.Single(metadata);
metadata.ToList().ForEach(function =>
{
Assert.NotNull(kernel.Plugins.GetFunction(function.PluginName, function.Name));
+1 -12
View File
@@ -28,18 +28,7 @@ internal static class TestHelpers
internal static void ImportAllSampleSkills(Kernel kernel)
{
ImportSamplePromptFunctions(kernel, "../../../../../../samples/skills",
"ChatSkill",
"SummarizeSkill",
"WriterSkill",
"CalendarSkill",
"ChildrensBookSkill",
"ClassificationSkill",
"CodingSkill",
"FunSkill",
"IntentDetectionSkill",
"MiscSkill",
"QASkill");
ImportSamplePromptFunctions(kernel, "./skills", "FunSkill");
}
internal static IReadOnlyKernelPluginCollection ImportSamplePlugins(Kernel kernel, params string[] pluginNames)
@@ -0,0 +1,26 @@
{
"schema": 1,
"description": "Generate a funny joke",
"type": "completion",
"completion": {
"max_tokens": 1000,
"temperature": 0.9,
"top_p": 0.0,
"presence_penalty": 0.0,
"frequency_penalty": 0.0
},
"input": {
"parameters": [
{
"name": "input",
"description": "Joke subject",
"defaultValue": ""
},
{
"name": "style",
"description": "Give a hint about the desired joke style",
"defaultValue": ""
}
]
}
}
@@ -0,0 +1,13 @@
WRITE EXACTLY ONE JOKE or HUMOROUS STORY ABOUT THE TOPIC BELOW
JOKE MUST BE:
- G RATED
- WORKPLACE/FAMILY SAFE
NO SEXISM, RACISM OR OTHER BIAS/BIGOTRY
BE CREATIVE AND FUNNY. I WANT TO LAUGH.
Incorporate the style suggestion, if provided: {{$style}}
+++++
{{$input}}
+++++
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Microsoft.SemanticKernel.ChatCompletion;
@@ -13,6 +14,8 @@ internal static class ChatPromptParser
{
private const string MessageTagName = "message";
private const string RoleAttributeName = "role";
private const string ImageTagName = "image";
private const string TextTagName = "text";
/// <summary>
/// Parses a prompt for an XML representation of a <see cref="ChatHistory"/>.
@@ -48,29 +51,69 @@ internal static class ChatPromptParser
{
chatHistory = null;
foreach (var node in nodes)
foreach (var node in nodes.Where(IsValidChatMessage))
{
if (IsValidChatMessage(node))
{
var role = node.Attributes[RoleAttributeName];
var content = node.Content!;
(chatHistory ??= new()).AddMessage(new AuthorRole(role), content);
}
(chatHistory ??= new()).Add(ParseChatNode(node));
}
return chatHistory is not null;
}
/// <summary>
/// Parses a chat node and constructs a <see cref="ChatMessageContent"/> object.
/// </summary>
/// <param name="node">The prompt node to parse.</param>
/// <returns><see cref="ChatMessageContent"/> object.</returns>
private static ChatMessageContent ParseChatNode(PromptNode node)
{
ChatMessageContentItemCollection items = new();
foreach (var childNode in node.ChildNodes.Where(childNode => childNode.Content is not null))
{
if (childNode.TagName.Equals(ImageTagName, StringComparison.OrdinalIgnoreCase))
{
items.Add(new ImageContent(new Uri(childNode.Content!)));
}
else if (childNode.TagName.Equals(TextTagName, StringComparison.OrdinalIgnoreCase))
{
items.Add(new TextContent(childNode.Content));
}
}
if (items.Count == 1 && items[0] is TextContent textContent)
{
node.Content = textContent.Text;
items.Clear();
}
var authorRole = new AuthorRole(node.Attributes[RoleAttributeName]);
return items.Count > 0
? new ChatMessageContent(authorRole, items)
: new ChatMessageContent(authorRole, node.Content);
}
/// <summary>
/// Checks if <see cref="PromptNode"/> is valid chat message.
/// </summary>
/// <param name="node">Instance of <see cref="PromptNode"/>.</param>
/// <remarks>
/// A valid chat message is a node with the following structure:<br/>
/// TagName = "message"<br/>
/// Attributes = { "role" : "..." }<br/>
/// optional one or more child nodes <image>...</image><br/>
/// content not null or single child node <text>...</text>
/// </remarks>
private static bool IsValidChatMessage(PromptNode node)
{
return
node.TagName.Equals(MessageTagName, StringComparison.OrdinalIgnoreCase) &&
node.Attributes.ContainsKey(RoleAttributeName) &&
node.Content is not null;
IsValidChildNodes(node);
}
private static bool IsValidChildNodes(PromptNode node)
{
var textTagsCount = node.ChildNodes.Count(n => n.TagName.Equals(TextTagName, StringComparison.OrdinalIgnoreCase));
return textTagsCount == 1 || (textTagsCount == 0 && node.Content is not null);
}
}
@@ -10,7 +10,7 @@ namespace Microsoft.SemanticKernel;
/// Provides an <see cref="EventArgs"/> for cancelable operations related
/// to <see cref="Kernel"/>-based operations.
/// </summary>
[Experimental("SKEXP0003")]
[Experimental("SKEXP0004")]
public abstract class CancelKernelEventArgs : KernelEventArgs
{
/// <summary>
@@ -0,0 +1,140 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Xunit;
namespace SemanticKernel.UnitTests.Prompt;
public sealed class ChatPromptParserTests
{
[Theory]
[InlineData("This is plain prompt")]
[InlineData("<message This is invalid chat prompt>")]
[InlineData("<message role='user'><text>This is invalid</text><text>chat prompt</text></message>")]
public void ItReturnsNullChatHistoryWhenPromptIsPlainTextOrInvalid(string prompt)
{
// Act
var result = ChatPromptParser.TryParse(prompt, out var chatHistory);
// Assert
Assert.False(result);
Assert.Null(chatHistory);
}
[Fact]
public void ItReturnsChatHistoryWithValidRolesWhenPromptIsValid()
{
// Arrange
string prompt = GetSimpleValidPrompt();
// Act
bool result = ChatPromptParser.TryParse(prompt, out var chatHistory);
// Assert
Assert.True(result);
Assert.NotNull(chatHistory);
Assert.Collection(chatHistory,
c => c.Role = AuthorRole.System,
c => c.Role = AuthorRole.User,
c => c.Role = AuthorRole.Assistant,
c => c.Role = AuthorRole.System,
c => c.Role = AuthorRole.User);
}
[Fact]
public void ItReturnsChatHistoryWithValidContentWhenSimplePrompt()
{
// Arrange
string prompt = GetSimpleValidPrompt();
// Act
bool result = ChatPromptParser.TryParse(prompt, out var chatHistory);
// Assert
Assert.True(result);
Assert.NotNull(chatHistory);
Assert.Collection(chatHistory,
c => Assert.Equal("Test with role in double quotes and content in new line.", c.Content),
c => Assert.Equal("Test with role in single quotes and content in the same line.", c.Content),
c => Assert.Equal("""
Test with multiline content.
Second line.
""", c.Content),
c => Assert.Equal("Test line with tab.", c.Content),
c => Assert.Equal("Hello, I'm a user.", c.Content));
}
[Fact]
public void ItReturnsChatHistoryWithValidContentItemsWhenNestedPrompt()
{
// Arrange
string prompt = GetNestedItemsValidPrompt();
// Act
bool result = ChatPromptParser.TryParse(prompt, out var chatHistory);
// Assert
Assert.True(result);
Assert.NotNull(chatHistory);
Assert.Collection(chatHistory,
c => Assert.Equal("Hi how are you?", c.Content),
c => Assert.Equal("""
Test with multiline content.
Second line.
""", c.Content),
c => Assert.True(((TextContent)c.Items![0]).Text!.Equals("explain image", StringComparison.Ordinal)
&& ((ImageContent)c.Items![1]).Uri!.AbsoluteUri == "https://fake-link-to-image/"));
}
private static string GetSimpleValidPrompt()
{
return
"""
<message role="system">
Test with role in double quotes and content in new line.
</message>
<message role='user'>Test with role in single quotes and content in the same line.</message>
<message role="assistant">
Test with multiline content.
Second line.
</message>
<message role='system'>
Test line with tab.
</message>
<message role='user'>
Hello, I'm a user.
</message>
""";
}
private static string GetNestedItemsValidPrompt()
{
return
"""
<message role='user'><text>Hi how are you?</text></message>
<message role="assistant">
Test with multiline content.
Second line.
</message>
<message role='user'>
<text>explain image</text>
<image>https://fake-link-to-image/</image>
</message>
""";
}
}
+1 -1
View File
@@ -98,7 +98,7 @@ Python notebooks:
- [Using Context Variables to Build a Chat Experience](./notebooks/04-context-variables-chat.ipynb)
- [Introduction to planners](./notebooks/05-using-the-planner.ipynb)
- [Building Memory with Embeddings](./notebooks/06-memory-and-embeddings.ipynb)
- [Using Hugging Face for Skills](./notebooks/07-hugging-face-for-skills.ipynb)
- [Using Hugging Face for Plugins](./notebooks/07-hugging-face-for-plugins.ipynb)
- [Combining native functions and semantic functions](./notebooks/08-native-function-inline.ipynb)
- [Groundedness Checking with Semantic Kernel](./notebooks/09-groundedness-checking.ipynb)
- [Returning multiple results per prompt](./notebooks/10-multiple-results-per-prompt.ipynb)
+7 -5
View File
@@ -58,7 +58,8 @@
"api_key, org_id = sk.openai_settings_from_dot_env()\n",
"\n",
"kernel.add_chat_service(\n",
" \"chat-gpt\", OpenAIChatCompletion(ai_model_id=\"gpt-3.5-turbo-1106\", api_key=api_key, org_id=org_id)\n",
" \"chat-gpt\",\n",
" OpenAIChatCompletion(ai_model_id=\"gpt-3.5-turbo-1106\", api_key=api_key, org_id=org_id),\n",
")"
]
},
@@ -91,7 +92,8 @@
"deployment, api_key, endpoint = sk.azure_openai_settings_from_dot_env()\n",
"\n",
"kernel.add_chat_service(\n",
" \"chat_completion\", AzureChatCompletion(deployment_name=deployment, endpoint=endpoint, api_key=api_key)\n",
" \"chat_completion\",\n",
" AzureChatCompletion(deployment_name=deployment, endpoint=endpoint, api_key=api_key),\n",
")"
]
},
@@ -102,7 +104,7 @@
"source": [
"# Run a Semantic Function\n",
"\n",
"**Step 3**: Load a Skill and run a semantic function:"
"**Step 3**: Load a Plugin and run a semantic function:"
]
},
{
@@ -111,8 +113,8 @@
"metadata": {},
"outputs": [],
"source": [
"skill = kernel.import_semantic_skill_from_directory(\"../../samples/skills\", \"FunSkill\")\n",
"joke_function = skill[\"Joke\"]\n",
"plugin = kernel.import_semantic_plugin_from_directory(\"../../samples/plugins\", \"FunPlugin\")\n",
"joke_function = plugin[\"Joke\"]\n",
"\n",
"print(joke_function(\"time travel to dinosaur age\"))"
]
@@ -6,16 +6,16 @@
"id": "692e361b",
"metadata": {},
"source": [
"# How to run a semantic skills from file\n",
"Now that you're familiar with Kernel basics, let's see how the kernel allows you to run Semantic Skills and Semantic Functions stored on disk. \n",
"# How to run a semantic plugins from file\n",
"Now that you're familiar with Kernel basics, let's see how the kernel allows you to run Semantic Plugins and Semantic Functions stored on disk. \n",
"\n",
"A Semantic Skill is a collection of Semantic Functions, where each function is defined with natural language that can be provided with a text file. \n",
"A Semantic Plugin is a collection of Semantic Functions, where each function is defined with natural language that can be provided with a text file. \n",
"\n",
"Refer to our [glossary](https://github.com/microsoft/semantic-kernel/blob/main/docs/GLOSSARY.md) for an in-depth guide to the terms.\n",
"\n",
"The repository includes some examples under the [samples](https://github.com/microsoft/semantic-kernel/tree/main/samples) folder.\n",
"\n",
"For instance, [this](../../skills/FunSkill/Joke/skprompt.txt) is the **Joke function** part of the **FunSkill skill**:"
"For instance, [this](../../plugins/FunPlugin/Joke/skprompt.txt) is the **Joke function** part of the **FunPlugin plugin**:"
]
},
{
@@ -55,7 +55,7 @@
"metadata": {},
"source": [
"\n",
"In the same folder you'll notice a second [config.json](../../skills/FunSkill/Joke/config.json) file. The file is optional, and is used to set some parameters for large language models like Temperature, TopP, Stop Sequences, etc.\n",
"In the same folder you'll notice a second [config.json](../../plugins/FunPlugin/Joke/config.json) file. The file is optional, and is used to set some parameters for large language models like Temperature, TopP, Stop Sequences, etc.\n",
"\n",
"```\n",
"{\n",
@@ -128,7 +128,7 @@
"id": "fd5ff1f4",
"metadata": {},
"source": [
"Import the skill and all its functions:"
"Import the plugin and all its functions:"
]
},
{
@@ -138,10 +138,10 @@
"metadata": {},
"outputs": [],
"source": [
"# note: using skills from the samples folder\n",
"skills_directory = \"../../samples/skills\"\n",
"# note: using plugins from the samples folder\n",
"plugins_directory = \"../../samples/plugins\"\n",
"\n",
"funFunctions = kernel.import_semantic_skill_from_directory(skills_directory, \"FunSkill\")\n",
"funFunctions = kernel.import_semantic_plugin_from_directory(plugins_directory, \"FunPlugin\")\n",
"\n",
"jokeFunction = funFunctions[\"Joke\"]"
]
@@ -152,7 +152,7 @@
"id": "edd99fa0",
"metadata": {},
"source": [
"How to use the skill functions, e.g. generate a joke about \"*time travel to dinosaur age*\":"
"How to use the plugin functions, e.g. generate a joke about \"*time travel to dinosaur age*\":"
]
},
{
@@ -162,13 +162,8 @@
"metadata": {},
"outputs": [],
"source": [
"result = jokeFunction(\"time travel to dinosaur age\")\n",
"\n",
"print(result)\n",
"\n",
"# You can also invoke functions asynchronously\n",
"# result = await jokeFunction.invoke_async(\"time travel to dinosaur age\")\n",
"# print(result)"
"result = await jokeFunction.invoke_async(\"time travel to dinosaur age\")\n",
"print(result)"
]
},
{
@@ -177,7 +172,7 @@
"id": "2281a1fc",
"metadata": {},
"source": [
"Great, now that you know how to load a skill from disk, let's show how you can [create and run a semantic function inline.](./03-semantic-function-inline.ipynb)"
"Great, now that you know how to load a plugin from disk, let's show how you can [create and run a semantic function inline.](./03-semantic-function-inline.ipynb)"
]
}
],
@@ -84,7 +84,7 @@
" kernel.add_text_completion_service(\"dv\", azure_text_service)\n",
"else:\n",
" api_key, org_id = sk.openai_settings_from_dot_env()\n",
" oai_text_service = OpenAITextCompletion(ai_model_id=\"text-davinci-003\", api_key=api_key, org_id=org_id)\n",
" oai_text_service = OpenAITextCompletion(ai_model_id=\"gpt-3.5-turbo-instruct\", api_key=api_key, org_id=org_id)\n",
" kernel.add_text_completion_service(\"dv\", oai_text_service)"
]
},
@@ -170,7 +170,7 @@
"id": "1c2c1262",
"metadata": {},
"source": [
"# Using ChatCompletion for Semantic Skills"
"# Using ChatCompletion for Semantic Plugins"
]
},
{
@@ -179,7 +179,7 @@
"id": "29b59b28",
"metadata": {},
"source": [
"You can also use chat completion models (like `gpt-35-turbo` and `gpt4`) for creating skills. Normally you would have to tweak the API to accommodate for a system and user role, but SK abstracts that away for you by using `kernel.add_chat_service` and `AzureChatCompletion` or `OpenAIChatCompletion`"
"You can also use chat completion models (like `gpt-35-turbo` and `gpt4`) for creating plugins. Normally you would have to tweak the API to accommodate for a system and user role, but SK abstracts that away for you by using `kernel.add_chat_service` and `AzureChatCompletion` or `OpenAIChatCompletion`"
]
},
{
@@ -271,7 +271,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.6"
"version": "3.10.12"
}
},
"nbformat": 4,
@@ -279,7 +279,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.6"
"version": "3.10.12"
}
},
"nbformat": 4,
+42 -37
View File
@@ -28,7 +28,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 1,
"id": "11e59885",
"metadata": {},
"outputs": [],
@@ -53,8 +53,8 @@
"else:\n",
" api_key, org_id = sk.openai_settings_from_dot_env()\n",
" kernel.add_chat_service(\n",
" \"gpt-3.5\",\n",
" OpenAIChatCompletion(ai_model_id=\"gpt-3.5-turbo\", api_key=api_key, org_id=org_id),\n",
" \"gpt-4\",\n",
" OpenAIChatCompletion(ai_model_id=\"gpt-3.5-turbo-1106\", api_key=api_key, org_id=org_id),\n",
" )"
]
},
@@ -68,7 +68,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 2,
"id": "93bc6103",
"metadata": {},
"outputs": [],
@@ -83,25 +83,25 @@
"id": "a5d86739",
"metadata": {},
"source": [
"### Providing skills to the planner\n",
"The planner needs to know what skills are available to it. Here we'll give it access to the `SummarizeSkill` and `WriterSkill` we have defined on disk. This will include many semantic functions, of which the planner will intelligently choose a subset. \n",
"### Providing plugins to the planner\n",
"The planner needs to know what plugins are available to it. Here we'll give it access to the `SummarizePlugin` and `WriterPlugin` we have defined on disk. This will include many semantic functions, of which the planner will intelligently choose a subset. \n",
"\n",
"You can also include native functions as well. Here we'll add the TextSkill."
"You can also include native functions as well. Here we'll add the TextPlugin."
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 3,
"id": "ca0e7604",
"metadata": {},
"outputs": [],
"source": [
"from semantic_kernel.core_skills.text_skill import TextSkill\n",
"from semantic_kernel.core_plugins.text_plugin import TextPlugin\n",
"\n",
"skills_directory = \"../../samples/skills/\"\n",
"summarize_skill = kernel.import_semantic_skill_from_directory(skills_directory, \"SummarizeSkill\")\n",
"writer_skill = kernel.import_semantic_skill_from_directory(skills_directory, \"WriterSkill\")\n",
"text_skill = kernel.import_skill(TextSkill(), \"TextSkill\")"
"plugins_directory = \"../../samples/plugins/\"\n",
"summarize_plugin = kernel.import_semantic_plugin_from_directory(plugins_directory, \"SummarizePlugin\")\n",
"writer_plugin = kernel.import_semantic_plugin_from_directory(plugins_directory, \"WriterPlugin\")\n",
"text_plugin = kernel.import_plugin(TextPlugin(), \"TextPlugin\")"
]
},
{
@@ -130,7 +130,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 4,
"id": "20d35ed0",
"metadata": {},
"outputs": [],
@@ -142,7 +142,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 5,
"id": "d5697c09",
"metadata": {},
"outputs": [],
@@ -165,7 +165,7 @@
"id": "0f3a48f8",
"metadata": {},
"source": [
"You can see that the Planner took my ask and converted it into an JSON-based plan detailing how the AI would go about solving this task, making use of the skills that the Kernel has available to it.\n",
"You can see that the Planner took my ask and converted it into an JSON-based plan detailing how the AI would go about solving this task, making use of the plugins that the Kernel has available to it.\n",
"\n",
"As you can see in the above plan, the AI has determined which functions to call in order to fulfill the user ask. The output of each step of the plan becomes the input to the next function."
]
@@ -175,7 +175,7 @@
"id": "cd4df0c2",
"metadata": {},
"source": [
"Let's also define an inline skill and have it be available to the Planner. Be sure to give it a function name and skill name."
"Let's also define an inline plugin and have it be available to the Planner. Be sure to give it a function name and plugin name."
]
},
{
@@ -193,7 +193,7 @@
"shakespeareFunction = kernel.create_semantic_function(\n",
" prompt_template=sk_prompt,\n",
" function_name=\"shakespeare\",\n",
" skill_name=\"ShakespeareSkill\",\n",
" plugin_name=\"ShakespearePlugin\",\n",
" max_tokens=2000,\n",
" temperature=0.8,\n",
")"
@@ -204,7 +204,7 @@
"id": "5057cf9b",
"metadata": {},
"source": [
"Let's update our ask using this new skill"
"Let's update our ask using this new plugin"
]
},
{
@@ -299,7 +299,7 @@
"id": "a1c66d83",
"metadata": {},
"source": [
"The sequential planner is an XML-based step-by-step planner. You can see the prompt used for it here (https://github.com/microsoft/semantic-kernel/blob/main/python/semantic_kernel/planning/sequential_planner/Skills/SequentialPlanning/skprompt.txt)"
"The sequential planner is an XML-based step-by-step planner. You can see the prompt used for it here (https://github.com/microsoft/semantic-kernel/blob/main/python/semantic_kernel/planning/sequential_planner/Plugins/SequentialPlanning/skprompt.txt)"
]
},
{
@@ -404,7 +404,7 @@
"id": "53b1f296",
"metadata": {},
"source": [
"Let's add more skills to the kernel"
"Let's add more plugins to the kernel"
]
},
{
@@ -414,12 +414,17 @@
"metadata": {},
"outputs": [],
"source": [
"from semantic_kernel.core_skills import FileIOSkill, MathSkill, TextSkill, TimeSkill\n",
"from semantic_kernel.core_plugins import (\n",
" FileIOPlugin,\n",
" MathPlugin,\n",
" TextPlugin,\n",
" TimePlugin,\n",
")\n",
"\n",
"kernel.import_skill(MathSkill(), \"math\")\n",
"kernel.import_skill(FileIOSkill(), \"fileIO\")\n",
"kernel.import_skill(TimeSkill(), \"time\")\n",
"kernel.import_skill(TextSkill(), \"text\")"
"kernel.import_plugin(MathPlugin(), \"math\")\n",
"kernel.import_plugin(FileIOPlugin(), \"fileIO\")\n",
"kernel.import_plugin(TimePlugin(), \"time\")\n",
"kernel.import_plugin(TextPlugin(), \"text\")"
]
},
{
@@ -498,7 +503,7 @@
"id": "e0a00bde",
"metadata": {},
"source": [
"Let's create a Bing Search native skill that we can pass in to the Kernel.\n",
"Let's create a Bing Search native plugin that we can pass in to the Kernel.\n",
"\n",
"Make sure you have a Bing Search API key in your `.env` file\n",
"\n",
@@ -512,13 +517,13 @@
"metadata": {},
"outputs": [],
"source": [
"class WebSearchEngineSkill:\n",
"class WebSearchEnginePlugin:\n",
" \"\"\"\n",
" A search engine skill.\n",
" A search engine plugin.\n",
" \"\"\"\n",
"\n",
" from semantic_kernel.orchestration.sk_context import SKContext\n",
" from semantic_kernel.skill_definition import (\n",
" from semantic_kernel.plugin_definition import (\n",
" sk_function,\n",
" sk_function_context_parameter,\n",
" )\n",
@@ -548,7 +553,7 @@
"\n",
"BING_API_KEY = sk.bing_search_settings_from_dot_env()\n",
"connector = BingConnector(BING_API_KEY)\n",
"kernel.import_skill(WebSearchEngineSkill(connector), skill_name=\"WebSearch\")"
"kernel.import_plugin(WebSearchEnginePlugin(connector), plugin_name=\"WebSearch\")"
]
},
{
@@ -556,7 +561,7 @@
"id": "effdf3ab",
"metadata": {},
"source": [
"Let's also add a couple more skills"
"Let's also add a couple more plugins"
]
},
{
@@ -566,11 +571,11 @@
"metadata": {},
"outputs": [],
"source": [
"from semantic_kernel.core_skills.math_skill import MathSkill\n",
"from semantic_kernel.core_skills.time_skill import TimeSkill\n",
"from semantic_kernel.core_plugins.math_plugin import MathPlugin\n",
"from semantic_kernel.core_plugins.time_plugin import TimePlugin\n",
"\n",
"kernel.import_skill(TimeSkill(), \"time\")\n",
"kernel.import_skill(MathSkill(), \"math\")"
"kernel.import_plugin(TimePlugin(), \"time\")\n",
"kernel.import_plugin(MathPlugin(), \"math\")"
]
},
{
@@ -641,7 +646,7 @@
"for index, step in enumerate(plan._steps):\n",
" print(\"Step:\", index)\n",
" print(\"Description:\", step.description)\n",
" print(\"Function:\", step.skill_name + \".\" + step._function.name)\n",
" print(\"Function:\", step.plugin_name + \".\" + step._function.name)\n",
" if len(step._outputs) > 0:\n",
" print(\" Output:\\n\", str.replace(result[step._outputs[0]], \"\\n\", \"\\n \"))"
]
@@ -89,7 +89,7 @@
" kernel.add_text_embedding_generation_service(\"ada\", oai_text_embedding)\n",
"\n",
"kernel.register_memory_store(memory_store=sk.memory.VolatileMemoryStore())\n",
"kernel.import_skill(sk.core_skills.TextMemorySkill())"
"kernel.import_plugin(sk.core_plugins.TextMemoryPlugin())"
]
},
{
@@ -202,7 +202,7 @@
"id": "1ed54a32",
"metadata": {},
"source": [
"This is done by using the `TextMemorySkill` which exposes the `recall` native function.\n",
"This is done by using the `TextMemoryPlugin` which exposes the `recall` native function.\n",
"\n",
"`recall` takes an input ask and performs a similarity search on the contents that have\n",
"been embedded in the Memory Store and returns the most relevant memory. "
@@ -244,8 +244,8 @@
" context[\"fact4\"] = \"where have I traveled?\"\n",
" context[\"fact5\"] = \"what do I do for work?\"\n",
"\n",
" context[sk.core_skills.TextMemorySkill.COLLECTION_PARAM] = \"aboutMe\"\n",
" context[sk.core_skills.TextMemorySkill.RELEVANCE_PARAM] = \"0.8\"\n",
" context[sk.core_plugins.TextMemoryPlugin.COLLECTION_PARAM] = \"aboutMe\"\n",
" context[sk.core_plugins.TextMemoryPlugin.RELEVANCE_PARAM] = \"0.8\"\n",
"\n",
" context[\"chat_history\"] = \"\"\n",
"\n",
@@ -348,13 +348,13 @@
"] = \"README: Installation, getting started, and how to contribute\"\n",
"github_files[\n",
" \"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/02-running-prompts-from-file.ipynb\"\n",
"] = \"Jupyter notebook describing how to pass prompts from a file to a semantic skill or function\"\n",
"] = \"Jupyter notebook describing how to pass prompts from a file to a semantic plugin or function\"\n",
"github_files[\n",
" \"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/00-getting-started.ipynb\"\n",
"] = \"Jupyter notebook describing how to get started with the Semantic Kernel\"\n",
"github_files[\n",
" \"https://github.com/microsoft/semantic-kernel/tree/main/samples/skills/ChatSkill/ChatGPT\"\n",
"] = \"Sample demonstrating how to create a chat skill interfacing with ChatGPT\"\n",
" \"https://github.com/microsoft/semantic-kernel/tree/main/samples/plugins/ChatPlugin/ChatGPT\"\n",
"] = \"Sample demonstrating how to create a chat plugin interfacing with ChatGPT\"\n",
"github_files[\n",
" \"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/src/SemanticKernel/Memory/Volatile/VolatileMemoryStore.cs\"\n",
"] = \"C# class that defines a volatile embedding store\""
@@ -6,9 +6,9 @@
"id": "68e1c158",
"metadata": {},
"source": [
"# Using Hugging Face With Skills\n",
"# Using Hugging Face With Plugins\n",
"\n",
"In this notebook, we demonstrate using Hugging Face models for Skills using both SemanticMemory and text completions. \n",
"In this notebook, we demonstrate using Hugging Face models for Plugins using both SemanticMemory and text completions. \n",
"\n",
"SK supports downloading models from the Hugging Face that can perform the following tasks: text-generation, text2text-generation, summarization, and sentence-similarity. You can search for models by task at https://huggingface.co/models."
]
@@ -64,14 +64,15 @@
"\n",
"# Configure LLM service\n",
"kernel.add_text_completion_service(\n",
" service_id=\"gpt2\", service=sk_hf.HuggingFaceTextCompletion(ai_model_id=\"gpt2\", task=\"text-generation\")\n",
" service_id=\"gpt2\",\n",
" service=sk_hf.HuggingFaceTextCompletion(ai_model_id=\"gpt2\", task=\"text-generation\"),\n",
")\n",
"kernel.add_text_embedding_generation_service(\n",
" service_id=\"sentence-transformers/all-MiniLM-L6-v2\",\n",
" service=sk_hf.HuggingFaceTextEmbedding(ai_model_id=\"sentence-transformers/all-MiniLM-L6-v2\"),\n",
")\n",
"kernel.register_memory_store(memory_store=sk.memory.VolatileMemoryStore())\n",
"kernel.import_skill(sk.core_skills.TextMemorySkill())"
"kernel.import_plugin(sk.core_plugins.TextMemoryPlugin())"
]
},
{
@@ -80,9 +81,9 @@
"id": "2a7e7ca4",
"metadata": {},
"source": [
"### Add Memories and Define a skill to use them\n",
"### Add Memories and Define a plugin to use them\n",
"\n",
"Most models available on huggingface.co are not as powerful as OpenAI GPT-3+. Your skills will likely need to be simpler to accommodate this."
"Most models available on huggingface.co are not as powerful as OpenAI GPT-3+. Your plugins will likely need to be simpler to accommodate this."
]
},
{
@@ -122,8 +123,8 @@
"outputs": [],
"source": [
"context = kernel.create_new_context()\n",
"context[sk.core_skills.TextMemorySkill.COLLECTION_PARAM] = \"animal-facts\"\n",
"context[sk.core_skills.TextMemorySkill.RELEVANCE_PARAM] = \"0.3\"\n",
"context[sk.core_plugins.TextMemoryPlugin.COLLECTION_PARAM] = \"animal-facts\"\n",
"context[sk.core_plugins.TextMemoryPlugin.RELEVANCE_PARAM] = \"0.3\"\n",
"\n",
"context[\"query1\"] = \"animal that swims\"\n",
"context[\"query2\"] = \"animal that flies\"\n",
@@ -57,7 +57,10 @@
"outputs": [],
"source": [
"import semantic_kernel as sk\n",
"from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, OpenAIChatCompletion\n",
"from semantic_kernel.connectors.ai.open_ai import (\n",
" AzureChatCompletion,\n",
" OpenAIChatCompletion,\n",
")\n",
"\n",
"kernel = sk.Kernel()\n",
"\n",
@@ -102,15 +105,18 @@
"outputs": [],
"source": [
"import random\n",
"from semantic_kernel.skill_definition import sk_function\n",
"from semantic_kernel.plugin_definition import sk_function\n",
"\n",
"\n",
"class GenerateNumberSkill:\n",
"class GenerateNumberPlugin:\n",
" \"\"\"\n",
" Description: Generate a number between 3-x.\n",
" \"\"\"\n",
"\n",
" @sk_function(description=\"Generate a random number between 3-x\", name=\"GenerateNumberThreeOrHigher\")\n",
" @sk_function(\n",
" description=\"Generate a random number between 3-x\",\n",
" name=\"GenerateNumberThreeOrHigher\",\n",
" )\n",
" def generate_number_three_or_higher(self, input: str) -> str:\n",
" \"\"\"\n",
" Generate a number between 3-<input>\n",
@@ -156,14 +162,14 @@
"corgi_story = kernel.create_semantic_function(\n",
" prompt_template=sk_prompt,\n",
" function_name=\"CorgiStory\",\n",
" skill_name=\"CorgiSkill\",\n",
" plugin_name=\"CorgiPlugin\",\n",
" description=\"Write a short story about two Corgis on an adventure\",\n",
" max_tokens=500,\n",
" temperature=0.5,\n",
" top_p=0.5,\n",
")\n",
"\n",
"generate_number_skill = kernel.import_skill(GenerateNumberSkill())"
"generate_number_plugin = kernel.import_plugin(GenerateNumberPlugin())"
]
},
{
@@ -174,7 +180,7 @@
"outputs": [],
"source": [
"# Run the number generator\n",
"generate_number_three_or_higher = generate_number_skill[\"GenerateNumberThreeOrHigher\"]\n",
"generate_number_three_or_higher = generate_number_plugin[\"GenerateNumberThreeOrHigher\"]\n",
"number_result = generate_number_three_or_higher(6)\n",
"print(number_result)"
]
@@ -224,7 +230,10 @@
"outputs": [],
"source": [
"import semantic_kernel as sk\n",
"from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, OpenAIChatCompletion\n",
"from semantic_kernel.connectors.ai.open_ai import (\n",
" AzureChatCompletion,\n",
" OpenAIChatCompletion,\n",
")\n",
"\n",
"kernel = sk.Kernel()\n",
"\n",
@@ -260,16 +269,19 @@
"outputs": [],
"source": [
"import random\n",
"from semantic_kernel.skill_definition import sk_function, sk_function_context_parameter\n",
"from semantic_kernel.plugin_definition import sk_function, sk_function_context_parameter\n",
"from semantic_kernel import SKContext\n",
"\n",
"\n",
"class GenerateNumberSkill:\n",
"class GenerateNumberPlugin:\n",
" \"\"\"\n",
" Description: Generate a number between a min and a max.\n",
" \"\"\"\n",
"\n",
" @sk_function(description=\"Generate a random number between min and max\", name=\"GenerateNumber\")\n",
" @sk_function(\n",
" description=\"Generate a random number between min and max\",\n",
" name=\"GenerateNumber\",\n",
" )\n",
" @sk_function_context_parameter(name=\"min\", description=\"Minimum number of paragraphs.\")\n",
" @sk_function_context_parameter(name=\"max\", description=\"Maximum number of paragraphs.\", default_value=\"10\")\n",
" def generate_number(self, context: SKContext) -> str:\n",
@@ -297,8 +309,8 @@
"metadata": {},
"outputs": [],
"source": [
"generate_number_skill = kernel.import_skill(GenerateNumberSkill())\n",
"generate_number = generate_number_skill[\"GenerateNumber\"]"
"generate_number_plugin = kernel.import_plugin(GenerateNumberPlugin())\n",
"generate_number = generate_number_plugin[\"GenerateNumber\"]"
]
},
{
@@ -330,7 +342,7 @@
"corgi_story = kernel.create_semantic_function(\n",
" prompt_template=sk_prompt,\n",
" function_name=\"CorgiStory\",\n",
" skill_name=\"CorgiSkill\",\n",
" plugin_name=\"CorgiPlugin\",\n",
" description=\"Write a short story about two Corgis on an adventure\",\n",
" max_tokens=500,\n",
" temperature=0.5,\n",
@@ -417,7 +429,7 @@
"\n",
"We will make our CorgiStory semantic function call a native function `GenerateNames` which will return names for our Corgi characters.\n",
"\n",
"We do this using the syntax `{{skill_name.function_name}}`. You can read more about our prompte templating syntax [here](../../../docs/PROMPT_TEMPLATE_LANGUAGE.md). "
"We do this using the syntax `{{plugin_name.function_name}}`. You can read more about our prompte templating syntax [here](../../../docs/PROMPT_TEMPLATE_LANGUAGE.md). "
]
},
{
@@ -428,10 +440,10 @@
"outputs": [],
"source": [
"import random\n",
"from semantic_kernel.skill_definition import sk_function\n",
"from semantic_kernel.plugin_definition import sk_function\n",
"\n",
"\n",
"class GenerateNamesSkill:\n",
"class GenerateNamesPlugin:\n",
" \"\"\"\n",
" Description: Generate character names.\n",
" \"\"\"\n",
@@ -460,8 +472,8 @@
"metadata": {},
"outputs": [],
"source": [
"generate_names_skill = kernel.import_skill(GenerateNamesSkill(), skill_name=\"GenerateNames\")\n",
"generate_names = generate_names_skill[\"generate_names\"]"
"generate_names_plugin = kernel.import_plugin(GenerateNamesPlugin(), plugin_name=\"GenerateNames\")\n",
"generate_names = generate_names_plugin[\"generate_names\"]"
]
},
{
@@ -493,7 +505,7 @@
"corgi_story = kernel.create_semantic_function(\n",
" prompt_template=sk_prompt,\n",
" function_name=\"CorgiStory\",\n",
" skill_name=\"CorgiSkill\",\n",
" plugin_name=\"CorgiPlugin\",\n",
" description=\"Write a short story about two Corgis on an adventure\",\n",
" max_tokens=500,\n",
" temperature=0.5,\n",
@@ -5,7 +5,7 @@
"id": "f5c76c5f",
"metadata": {},
"source": [
"# Groundedness Checking Skills\n",
"# Groundedness Checking Plugins\n",
"\n",
"A well-known problem with large language models (LLMs) is that they make things up. These are sometimes called 'hallucinations' but a safer (and less anthropomorphic) term is 'ungrounded addition' - something in the text which cannot be firmly established. When attempting to establish whether or not something in an LLM response is 'true' we can either check for it in the supplied prompt (this is called 'narrow grounding') or use our general knowledge ('broad grounding'). Note that narrow grounding can lead to things being classified as 'true, but ungrounded.' For example \"I live in Switzerland\" is **not** _narrowly_ grounded in \"I live in Geneva\" even though it must be true (it **is** _broadly_ grounded).\n",
"\n",
@@ -93,7 +93,10 @@
"outputs": [],
"source": [
"import semantic_kernel as sk\n",
"from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, OpenAIChatCompletion\n",
"from semantic_kernel.connectors.ai.open_ai import (\n",
" AzureChatCompletion,\n",
" OpenAIChatCompletion,\n",
")\n",
"\n",
"kernel = sk.Kernel()\n",
"\n",
@@ -117,9 +120,9 @@
"id": "0c65f786",
"metadata": {},
"source": [
"## Import the Skills\n",
"## Import the Plugins\n",
"\n",
"We are going to be using the grounding skill, to check its quality, and remove ungrounded additions:"
"We are going to be using the grounding plugin, to check its quality, and remove ungrounded additions:"
]
},
{
@@ -129,10 +132,10 @@
"metadata": {},
"outputs": [],
"source": [
"# note: using skills from the samples folder\n",
"skills_directory = \"../../samples/skills\"\n",
"# note: using plugins from the samples folder\n",
"plugins_directory = \"../../samples/plugins\"\n",
"\n",
"groundingSemanticFunctions = kernel.import_semantic_skill_from_directory(skills_directory, \"GroundingSkill\")"
"groundingSemanticFunctions = kernel.import_semantic_plugin_from_directory(plugins_directory, \"GroundingPlugin\")"
]
},
{
@@ -197,7 +200,7 @@
"- A reference to Rome has been added\n",
"\n",
"\n",
"The grounding skill has three stages:\n",
"The grounding plugin has three stages:\n",
"\n",
"1. Extract entities from a summary text\n",
"2. Perform a reference check against the grounding text\n",
@@ -213,7 +216,7 @@
"source": [
"### Preparing the Context\n",
"\n",
"Semantic functions operate in a context, which provides extra parameters for their operation. For the grounding skill, the context is expected to supply the topic for the reference checking, and some particular examples of them:"
"Semantic functions operate in a context, which provides extra parameters for their operation. For the grounding plugin, the context is expected to supply the topic for the reference checking, and some particular examples of them:"
]
},
{
@@ -1,387 +1,404 @@
{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"id": "68e1c158",
"metadata": {},
"source": [
"# Multiple Results"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "fb81bacd",
"metadata": {},
"source": [
"In this notebook we show how you can in a single request, have the LLM model return multiple results per prompt. This is useful for running experiments where you want to evaluate the robustness of your prompt and the parameters of your config against a particular large language model."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a77bdf89",
"metadata": {},
"outputs": [],
"source": [
"!python -m pip install semantic-kernel==0.4.5.dev0"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3f4bfee4",
"metadata": {},
"outputs": [],
"source": [
"from services import Service\n",
"\n",
"# Select a service to use for this notebook (available services: OpenAI, AzureOpenAI, HuggingFace)\n",
"selectedService = Service.AzureOpenAI"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "508ad44f",
"metadata": {},
"outputs": [],
"source": [
"import semantic_kernel as sk\n",
"if selectedService == Service.OpenAI or selectedService == Service.AzureOpenAI:\n",
" from semantic_kernel.connectors.ai.open_ai.request_settings.open_ai_request_settings import (\n",
" OpenAITextRequestSettings,\n",
" OpenAIChatRequestSettings,\n",
" )\n",
" from semantic_kernel.connectors.ai.open_ai.request_settings.azure_chat_request_settings import AzureChatRequestSettings\n",
" from semantic_kernel.connectors.ai.open_ai import (\n",
" AzureTextCompletion,\n",
" AzureChatCompletion,\n",
" OpenAITextCompletion,\n",
" OpenAIChatCompletion,\n",
" )\n",
"if selectedService == Service.HuggingFace:\n",
" from semantic_kernel.connectors.ai.hugging_face import HuggingFaceTextCompletion"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "d8ddffc1",
"metadata": {},
"source": [
"First, we will set up the text and chat services we will be submitting prompts to."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8f8dcbc6",
"metadata": {},
"outputs": [],
"source": [
"kernel = sk.Kernel()\n",
"\n",
"# Configure Azure LLM service\n",
"if selectedService == Service.AzureOpenAI:\n",
" deployment, api_key, endpoint = sk.azure_openai_settings_from_dot_env()\n",
" azure_text_service = AzureTextCompletion(\n",
" deployment_name=\"gpt-35-turbo-instruct\", endpoint=endpoint, api_key=api_key\n",
" ) # set the deployment name to the value of your text model (e.g. gpt-35-turbo-instruct or text-davinci-003)\n",
" azure_chat_service = AzureChatCompletion(\n",
" deployment_name=\"gpt-35-turbo\", endpoint=endpoint, api_key=api_key\n",
" ) # set the deployment name to the value of your chat model\n",
"\n",
"# Configure OpenAI service\n",
"if selectedService == Service.OpenAI:\n",
" api_key, org_id = sk.openai_settings_from_dot_env()\n",
" oai_text_service = OpenAITextCompletion(ai_model_id=\"gpt-35-turbo-instruct\", api_key=api_key, org_id=org_id)\n",
" oai_chat_service = OpenAIChatCompletion(ai_model_id=\"gpt-3.5-turbo\", api_key=api_key, org_id=org_id)\n",
"\n",
"# Configure Hugging Face service\n",
"if selectedService == Service.HuggingFace:\n",
" hf_text_service = HuggingFaceTextCompletion(ai_model_id=\"distilgpt2\", task=\"text-generation\")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "50561d82",
"metadata": {},
"source": [
"Next, we'll set up the completion request settings for text completion services."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "628c843e",
"metadata": {},
"outputs": [],
"source": [
"oai_text_request_settings = OpenAITextRequestSettings(\n",
" extension_data={\n",
" \"max_tokens\": 80,\n",
" \"temperature\": 0.7,\n",
" \"top_p\": 1,\n",
" \"frequency_penalty\": 0.5,\n",
" \"presence_penalty\": 0.5,\n",
" \"number_of_responses\": 3,\n",
" }\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "857a9c89",
"metadata": {},
"source": [
"## Multiple Open AI Text Completions"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e2979db8",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.OpenAI:\n",
" prompt = \"what is the purpose of a rubber duck?\"\n",
" results = await oai_text_service.complete_async(prompt=prompt, settings=oai_text_request_settings)\n",
" i = 1\n",
" for result in results:\n",
" print(f\"Result {i}: {result}\")\n",
" i += 1"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "4288d09f",
"metadata": {},
"source": [
"## Multiple Azure Open AI Text Completions"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5319f14d",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.AzureOpenAI:\n",
" prompt = \"provide me a list of possible meanings for the acronym 'ORLD'\"\n",
" results = await azure_text_service.complete_async(prompt=prompt, settings=oai_text_request_settings)\n",
" i = 1\n",
" for result in results:\n",
" print(f\"Result {i}: {result}\")\n",
" i += 1"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "eb548f9c",
"metadata": {},
"source": [
"## Multiple Hugging Face Text Completions"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4a148709",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.HuggingFace:\n",
" from semantic_kernel.connectors.ai.hugging_face.hf_request_settings import (\n",
" HuggingFaceRequestSettings,\n",
" )\n",
"\n",
" hf_request_settings = HuggingFaceRequestSettings(extension_data={\"max_new_tokens\": 80, \"temperature\": 0.7, \"top_p\": 1})"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9525e4f3",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.HuggingFace:\n",
" prompt = \"The purpose of a rubber duck is\"\n",
" results = await hf_text_service.complete_async(prompt=prompt, request_settings=hf_request_settings)\n",
" print(\"\".join(results))"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "da632e12",
"metadata": {},
"source": [
"Here, we're setting up the settings for Chat completions."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e5f11e46",
"metadata": {},
"outputs": [],
"source": [
"oai_chat_request_settings = OpenAIChatRequestSettings(\n",
" max_tokens=80, temperature=0.7, top_p=1, frequency_penalty=0.5, presence_penalty=0.5, number_of_responses=3\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "d6bf238e",
"metadata": {},
"source": [
"## Multiple OpenAI Chat Completions"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dabc6a4c",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.OpenAI:\n",
" role = \"user\"\n",
" content = \"It's a beautiful day outside, birds are singing, flowers are blooming. On days like these, kids like you...\"\n",
" message = {\"role\": role, \"content\": content}\n",
" results = await oai_chat_service.complete_chat_async(messages=[message], settings=oai_chat_request_settings)\n",
" i = 0\n",
" for result in results:\n",
" print(f\"Result {i}: {result[0]}\")\n",
" i += 1"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "cdb8f740",
"metadata": {},
"source": [
"## Multiple Azure OpenAI Chat Completions"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "66ba4767",
"metadata": {},
"outputs": [],
"source": [
"az_oai_request_settings = AzureChatRequestSettings(\n",
" max_tokens=80, temperature=0.7, top_p=1, frequency_penalty=0.5, presence_penalty=0.5, number_of_responses=3\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b74a64a9",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.AzureOpenAI:\n",
" role = \"user\"\n",
" content = \"Tomorow is going to be a great day, I can feel it. I'm going to wake up early, go for a run, and then...\"\n",
" message = {\"role\": role, \"content\": content}\n",
" results = await azure_chat_service.complete_chat_async(messages=[message], settings=az_oai_request_settings)\n",
" i = 0\n",
" for result in results:\n",
" print(f\"Result {i}: {result[0]}\")\n",
" i += 1"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "98c8191d",
"metadata": {},
"source": [
"## Streaming Multiple Results\n",
"\n",
"Here is an example pattern if you want to stream your multiple results. Note that this is not supported for Hugging Face text completions at this time."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "26a37702",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.OpenAI:\n",
" import os\n",
" from IPython.display import clear_output\n",
" import time\n",
"\n",
" # Determine the clear command based on OS\n",
" clear_command = \"cls\" if os.name == \"nt\" else \"clear\"\n",
"\n",
" prompt = \"what is the purpose of a rubber duck?\"\n",
" stream = oai_text_service.complete_stream_async(prompt=prompt, settings=oai_text_request_settings)\n",
" number_of_responses = oai_text_request_settings.number_of_responses\n",
" texts = [\"\"] * number_of_responses\n",
"\n",
" last_clear_time = time.time()\n",
" clear_interval = 0.5 # seconds\n",
"\n",
" # Note: there are some quirks with displaying the output, which sometimes flashes and disappears.\n",
" # This could be influenced by a few factors specific to Jupyter notebooks and asynchronous processing.\n",
" # The following code attempts to buffer the results to avoid the output flashing on/off the screen.\n",
"\n",
" async for results in stream:\n",
" current_time = time.time()\n",
"\n",
" # Update texts with new results\n",
" for idx, result in enumerate(results):\n",
" if idx < number_of_responses:\n",
" texts[idx] += result\n",
"\n",
" # Clear and display output at intervals\n",
" if current_time - last_clear_time > clear_interval:\n",
" clear_output(wait=True)\n",
" for idx, text in enumerate(texts):\n",
" print(f\"Result {idx + 1}: {text}\")\n",
" last_clear_time = current_time\n",
"\n",
" print(\"----------------------------------------\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"id": "68e1c158",
"metadata": {},
"source": [
"# Multiple Results"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "fb81bacd",
"metadata": {},
"source": [
"In this notebook we show how you can in a single request, have the LLM model return multiple results per prompt. This is useful for running experiments where you want to evaluate the robustness of your prompt and the parameters of your config against a particular large language model."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a77bdf89",
"metadata": {},
"outputs": [],
"source": [
"!python -m pip install semantic-kernel==0.4.5.dev0"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3f4bfee4",
"metadata": {},
"outputs": [],
"source": [
"from services import Service\n",
"\n",
"# Select a service to use for this notebook (available services: OpenAI, AzureOpenAI, HuggingFace)\n",
"selectedService = Service.AzureOpenAI"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "508ad44f",
"metadata": {},
"outputs": [],
"source": [
"import semantic_kernel as sk\n",
"\n",
"if selectedService == Service.OpenAI or selectedService == Service.AzureOpenAI:\n",
" from semantic_kernel.connectors.ai.open_ai.request_settings.open_ai_request_settings import (\n",
" OpenAITextRequestSettings,\n",
" OpenAIChatRequestSettings,\n",
" )\n",
" from semantic_kernel.connectors.ai.open_ai.request_settings.azure_chat_request_settings import (\n",
" AzureChatRequestSettings,\n",
" )\n",
" from semantic_kernel.connectors.ai.open_ai import (\n",
" AzureTextCompletion,\n",
" AzureChatCompletion,\n",
" OpenAITextCompletion,\n",
" OpenAIChatCompletion,\n",
" )\n",
"if selectedService == Service.HuggingFace:\n",
" from semantic_kernel.connectors.ai.hugging_face import HuggingFaceTextCompletion"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "d8ddffc1",
"metadata": {},
"source": [
"First, we will set up the text and chat services we will be submitting prompts to."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8f8dcbc6",
"metadata": {},
"outputs": [],
"source": [
"kernel = sk.Kernel()\n",
"\n",
"# Configure Azure LLM service\n",
"if selectedService == Service.AzureOpenAI:\n",
" deployment, api_key, endpoint = sk.azure_openai_settings_from_dot_env()\n",
" azure_text_service = AzureTextCompletion(\n",
" deployment_name=\"gpt-35-turbo-instruct\", endpoint=endpoint, api_key=api_key\n",
" ) # set the deployment name to the value of your text model (e.g. gpt-35-turbo-instruct or text-davinci-003)\n",
" azure_chat_service = AzureChatCompletion(\n",
" deployment_name=\"gpt-35-turbo\", endpoint=endpoint, api_key=api_key\n",
" ) # set the deployment name to the value of your chat model\n",
"\n",
"# Configure OpenAI service\n",
"if selectedService == Service.OpenAI:\n",
" api_key, org_id = sk.openai_settings_from_dot_env()\n",
" oai_text_service = OpenAITextCompletion(ai_model_id=\"gpt-3.5-turbo-instruct\", api_key=api_key, org_id=org_id)\n",
" oai_chat_service = OpenAIChatCompletion(ai_model_id=\"gpt-3.5-turbo\", api_key=api_key, org_id=org_id)\n",
"\n",
"# Configure Hugging Face service\n",
"if selectedService == Service.HuggingFace:\n",
" hf_text_service = HuggingFaceTextCompletion(ai_model_id=\"distilgpt2\", task=\"text-generation\")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "50561d82",
"metadata": {},
"source": [
"Next, we'll set up the completion request settings for text completion services."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "628c843e",
"metadata": {},
"outputs": [],
"source": [
"oai_text_request_settings = OpenAITextRequestSettings(\n",
" extension_data={\n",
" \"max_tokens\": 80,\n",
" \"temperature\": 0.7,\n",
" \"top_p\": 1,\n",
" \"frequency_penalty\": 0.5,\n",
" \"presence_penalty\": 0.5,\n",
" \"number_of_responses\": 3,\n",
" }\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "857a9c89",
"metadata": {},
"source": [
"## Multiple Open AI Text Completions"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e2979db8",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.OpenAI:\n",
" prompt = \"what is the purpose of a rubber duck?\"\n",
" results = await oai_text_service.complete_async(prompt=prompt, settings=oai_text_request_settings)\n",
" i = 1\n",
" for result in results:\n",
" print(f\"Result {i}: {result}\")\n",
" i += 1"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "4288d09f",
"metadata": {},
"source": [
"## Multiple Azure Open AI Text Completions"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5319f14d",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.AzureOpenAI:\n",
" prompt = \"provide me a list of possible meanings for the acronym 'ORLD'\"\n",
" results = await azure_text_service.complete_async(prompt=prompt, settings=oai_text_request_settings)\n",
" i = 1\n",
" for result in results:\n",
" print(f\"Result {i}: {result}\")\n",
" i += 1"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "eb548f9c",
"metadata": {},
"source": [
"## Multiple Hugging Face Text Completions"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4a148709",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.HuggingFace:\n",
" from semantic_kernel.connectors.ai.hugging_face.hf_request_settings import (\n",
" HuggingFaceRequestSettings,\n",
" )\n",
"\n",
" hf_request_settings = HuggingFaceRequestSettings(\n",
" extension_data={\"max_new_tokens\": 80, \"temperature\": 0.7, \"top_p\": 1}\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9525e4f3",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.HuggingFace:\n",
" prompt = \"The purpose of a rubber duck is\"\n",
" results = await hf_text_service.complete_async(prompt=prompt, request_settings=hf_request_settings)\n",
" print(\"\".join(results))"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "da632e12",
"metadata": {},
"source": [
"Here, we're setting up the settings for Chat completions."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e5f11e46",
"metadata": {},
"outputs": [],
"source": [
"oai_chat_request_settings = OpenAIChatRequestSettings(\n",
" max_tokens=80,\n",
" temperature=0.7,\n",
" top_p=1,\n",
" frequency_penalty=0.5,\n",
" presence_penalty=0.5,\n",
" number_of_responses=3,\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "d6bf238e",
"metadata": {},
"source": [
"## Multiple OpenAI Chat Completions"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dabc6a4c",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.OpenAI:\n",
" role = \"user\"\n",
" content = (\n",
" \"It's a beautiful day outside, birds are singing, flowers are blooming. On days like these, kids like you...\"\n",
" )\n",
" message = {\"role\": role, \"content\": content}\n",
" results = await oai_chat_service.complete_chat_async(messages=[message], settings=oai_chat_request_settings)\n",
" i = 0\n",
" for result in results:\n",
" print(f\"Result {i}: {result[0]}\")\n",
" i += 1"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "cdb8f740",
"metadata": {},
"source": [
"## Multiple Azure OpenAI Chat Completions"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "66ba4767",
"metadata": {},
"outputs": [],
"source": [
"az_oai_request_settings = AzureChatRequestSettings(\n",
" max_tokens=80,\n",
" temperature=0.7,\n",
" top_p=1,\n",
" frequency_penalty=0.5,\n",
" presence_penalty=0.5,\n",
" number_of_responses=3,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b74a64a9",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.AzureOpenAI:\n",
" role = \"user\"\n",
" content = \"Tomorow is going to be a great day, I can feel it. I'm going to wake up early, go for a run, and then...\"\n",
" message = {\"role\": role, \"content\": content}\n",
" results = await azure_chat_service.complete_chat_async(messages=[message], settings=az_oai_request_settings)\n",
" i = 0\n",
" for result in results:\n",
" print(f\"Result {i}: {result[0]}\")\n",
" i += 1"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "98c8191d",
"metadata": {},
"source": [
"## Streaming Multiple Results\n",
"\n",
"Here is an example pattern if you want to stream your multiple results. Note that this is not supported for Hugging Face text completions at this time."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "26a37702",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.OpenAI:\n",
" import os\n",
" from IPython.display import clear_output\n",
" import time\n",
"\n",
" # Determine the clear command based on OS\n",
" clear_command = \"cls\" if os.name == \"nt\" else \"clear\"\n",
"\n",
" prompt = \"what is the purpose of a rubber duck?\"\n",
" stream = oai_text_service.complete_stream_async(prompt=prompt, settings=oai_text_request_settings)\n",
" number_of_responses = oai_text_request_settings.number_of_responses\n",
" texts = [\"\"] * number_of_responses\n",
"\n",
" last_clear_time = time.time()\n",
" clear_interval = 0.5 # seconds\n",
"\n",
" # Note: there are some quirks with displaying the output, which sometimes flashes and disappears.\n",
" # This could be influenced by a few factors specific to Jupyter notebooks and asynchronous processing.\n",
" # The following code attempts to buffer the results to avoid the output flashing on/off the screen.\n",
"\n",
" async for results in stream:\n",
" current_time = time.time()\n",
"\n",
" # Update texts with new results\n",
" for idx, result in enumerate(results):\n",
" if idx < number_of_responses:\n",
" texts[idx] += result\n",
"\n",
" # Clear and display output at intervals\n",
" if current_time - last_clear_time > clear_interval:\n",
" clear_output(wait=True)\n",
" for idx, text in enumerate(texts):\n",
" print(f\"Result {idx + 1}: {text}\")\n",
" last_clear_time = current_time\n",
"\n",
" print(\"----------------------------------------\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+328 -327
View File
@@ -1,329 +1,330 @@
{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"id": "68e1c158",
"metadata": {},
"source": [
"# Streaming Results\n",
"Here is an example pattern if you want to stream your multiple results. Note that this is not supported for Hugging Face text completions at this time."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a77bdf89",
"metadata": {},
"outputs": [],
"source": [
"!python -m pip install semantic-kernel==0.4.5.dev0"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from services import Service\n",
"\n",
"# Select a service to use for this notebook (available services: OpenAI, AzureOpenAI, HuggingFace)\n",
"selectedService = Service.AzureOpenAI"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "508ad44f",
"metadata": {},
"outputs": [],
"source": [
"import semantic_kernel as sk\n",
"if selectedService == Service.OpenAI or selectedService == Service.AzureOpenAI:\n",
" from semantic_kernel.connectors.ai.open_ai.request_settings.open_ai_request_settings import (\n",
" OpenAITextRequestSettings,\n",
" OpenAIChatRequestSettings,\n",
" )\n",
" from semantic_kernel.connectors.ai.open_ai.request_settings.azure_chat_request_settings import (\n",
" AzureChatRequestSettings,\n",
" )\n",
" from semantic_kernel.connectors.ai.open_ai import (\n",
" AzureTextCompletion,\n",
" AzureChatCompletion,\n",
" OpenAITextCompletion,\n",
" OpenAIChatCompletion,\n",
" )\n",
"if selectedService == Service.HuggingFace:\n",
" from semantic_kernel.connectors.ai.hugging_face import HuggingFaceTextCompletion"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "d8ddffc1",
"metadata": {},
"source": [
"First, we will set up the text and chat services we will be submitting prompts to."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8f8dcbc6",
"metadata": {},
"outputs": [],
"source": [
"kernel = sk.Kernel()\n",
"\n",
"# Configure Azure LLM service\n",
"if selectedService == Service.AzureOpenAI:\n",
" deployment, api_key, endpoint = sk.azure_openai_settings_from_dot_env()\n",
" azure_text_service = AzureTextCompletion(\n",
" deployment_name=\"gpt-35-turbo-instruct\", endpoint=endpoint, api_key=api_key\n",
" ) # set the deployment name to the value of your text model (e.g. gpt-35-turbo-instruct or text-davinci-003)\n",
" azure_chat_service = AzureChatCompletion(\n",
" deployment_name=\"gpt-35-turbo\", endpoint=endpoint, api_key=api_key\n",
" ) # set the deployment name to the value of your chat model\n",
"\n",
"# Configure OpenAI service\n",
"if selectedService == Service.OpenAI:\n",
" api_key, org_id = sk.openai_settings_from_dot_env()\n",
" oai_text_service = OpenAITextCompletion(ai_model_id=\"gpt-35-turbo-instruct\", api_key=api_key, org_id=org_id)\n",
" oai_chat_service = OpenAIChatCompletion(ai_model_id=\"gpt-3.5-turbo\", api_key=api_key, org_id=org_id)\n",
"\n",
"# Configure Hugging Face service\n",
"if selectedService == Service.HuggingFace:\n",
" hf_text_service = HuggingFaceTextCompletion(ai_model_id=\"distilgpt2\", task=\"text-generation\")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "50561d82",
"metadata": {},
"source": [
"Next, we'll set up the completion request settings for text completion services."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "628c843e",
"metadata": {},
"outputs": [],
"source": [
"oai_request_settings = OpenAITextRequestSettings(\n",
" max_tokens=150,\n",
" temperature=0.7,\n",
" top_p=1,\n",
" frequency_penalty=0.5,\n",
" presence_penalty=0.5,\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "857a9c89",
"metadata": {},
"source": [
"## Streaming Open AI Text Completion"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e2979db8",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.OpenAI:\n",
" prompt = \"what is the purpose of a rubber duck?\"\n",
" stream = oai_text_service.complete_stream_async(prompt=prompt, settings=oai_request_settings)\n",
" async for text in stream:\n",
" print(text, end=\"\") # end = \"\" to avoid newlines"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "4288d09f",
"metadata": {},
"source": [
"## Streaming Azure Open AI Text Completion"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5319f14d",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.AzureOpenAI:\n",
" prompt = \"provide me a list of possible meanings for the acronym 'ORLD'\"\n",
" stream = azure_text_service.complete_stream_async(prompt=prompt, settings=oai_request_settings)\n",
" async for text in stream:\n",
" print(text, end=\"\") # end = \"\" to avoid newlines"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "eb548f9c",
"metadata": {},
"source": [
"## Streaming Hugging Face Text Completion"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "be7b1c2e",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.HuggingFace:\n",
" from semantic_kernel.connectors.ai.hugging_face.hf_request_settings import (\n",
" HuggingFaceRequestSettings,\n",
" )\n",
"\n",
" hf_request_settings = HuggingFaceRequestSettings(\n",
" extension_data={\n",
" \"max_new_tokens\": 80,\n",
" \"top_p\": 1,\n",
" \"eos_token_id\": 11,\n",
" \"pad_token_id\": 0,\n",
" }\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9525e4f3",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.HuggingFace:\n",
" prompt = \"The purpose of a rubber duck is\"\n",
" stream = hf_text_service.complete_stream_async(prompt=prompt, request_settings=hf_request_settings)\n",
" async for text in stream:\n",
" print(text, end=\"\") # end = \"\" to avoid newlines"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "da632e12",
"metadata": {},
"source": [
"Here, we're setting up the settings for Chat completions."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e5f11e46",
"metadata": {},
"outputs": [],
"source": [
"oai_chat_request_settings = OpenAIChatRequestSettings(\n",
" max_tokens=150,\n",
" temperature=0.7,\n",
" top_p=1,\n",
" frequency_penalty=0.5,\n",
" presence_penalty=0.5,\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "d6bf238e",
"metadata": {},
"source": [
"## Streaming OpenAI Chat Completion"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dabc6a4c",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.OpenAI:\n",
" role = \"system\"\n",
" content = \"You are an AI assistant that helps people find information.\"\n",
" message = {\"role\": role, \"content\": content}\n",
" stream = oai_chat_service.complete_chat_stream_async(messages=[message], settings=oai_chat_request_settings)\n",
" async for text in stream:\n",
" print(text, end=\"\") # end = \"\" to avoid newlines"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "cdb8f740",
"metadata": {},
"source": [
"## Streaming Azure OpenAI Chat Completion"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "da1e9f59",
"metadata": {},
"outputs": [],
"source": [
"az_oai_chat_request_settings = AzureChatRequestSettings(\n",
" max_tokens=150,\n",
" temperature=0.7,\n",
" top_p=1,\n",
" frequency_penalty=0.5,\n",
" presence_penalty=0.5,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b74a64a9",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.AzureOpenAI:\n",
" role = \"system\"\n",
" content = \"You are an AI assistant that helps people find information.\"\n",
" message = {\"role\": role, \"content\": content}\n",
" stream = azure_chat_service.complete_chat_stream_async(messages=[message], settings=az_oai_chat_request_settings)\n",
" async for text in stream:\n",
" print(text, end=\"\") # end = \"\" to avoid newlines"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"id": "68e1c158",
"metadata": {},
"source": [
"# Streaming Results\n",
"Here is an example pattern if you want to stream your multiple results. Note that this is not supported for Hugging Face text completions at this time."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a77bdf89",
"metadata": {},
"outputs": [],
"source": [
"!python -m pip install semantic-kernel==0.4.5.dev0"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from services import Service\n",
"\n",
"# Select a service to use for this notebook (available services: OpenAI, AzureOpenAI, HuggingFace)\n",
"selectedService = Service.AzureOpenAI"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "508ad44f",
"metadata": {},
"outputs": [],
"source": [
"import semantic_kernel as sk\n",
"\n",
"if selectedService == Service.OpenAI or selectedService == Service.AzureOpenAI:\n",
" from semantic_kernel.connectors.ai.open_ai.request_settings.open_ai_request_settings import (\n",
" OpenAITextRequestSettings,\n",
" OpenAIChatRequestSettings,\n",
" )\n",
" from semantic_kernel.connectors.ai.open_ai.request_settings.azure_chat_request_settings import (\n",
" AzureChatRequestSettings,\n",
" )\n",
" from semantic_kernel.connectors.ai.open_ai import (\n",
" AzureTextCompletion,\n",
" AzureChatCompletion,\n",
" OpenAITextCompletion,\n",
" OpenAIChatCompletion,\n",
" )\n",
"if selectedService == Service.HuggingFace:\n",
" from semantic_kernel.connectors.ai.hugging_face import HuggingFaceTextCompletion"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "d8ddffc1",
"metadata": {},
"source": [
"First, we will set up the text and chat services we will be submitting prompts to."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8f8dcbc6",
"metadata": {},
"outputs": [],
"source": [
"kernel = sk.Kernel()\n",
"\n",
"# Configure Azure LLM service\n",
"if selectedService == Service.AzureOpenAI:\n",
" deployment, api_key, endpoint = sk.azure_openai_settings_from_dot_env()\n",
" azure_text_service = AzureTextCompletion(\n",
" deployment_name=\"text-davinci-003\", endpoint=endpoint, api_key=api_key\n",
" ) # set the deployment name to the value of your text model (e.g. gpt-35-turbo-instruct or text-davinci-003)\n",
" azure_chat_service = AzureChatCompletion(\n",
" deployment_name=\"gpt-35-turbo\", endpoint=endpoint, api_key=api_key\n",
" ) # set the deployment name to the value of your chat model\n",
"\n",
"# Configure OpenAI service\n",
"if selectedService == Service.OpenAI:\n",
" api_key, org_id = sk.openai_settings_from_dot_env()\n",
" oai_text_service = OpenAITextCompletion(ai_model_id=\"gpt-3.5-turbo-instruct\", api_key=api_key, org_id=org_id)\n",
" oai_chat_service = OpenAIChatCompletion(ai_model_id=\"gpt-3.5-turbo\", api_key=api_key, org_id=org_id)\n",
"\n",
"# Configure Hugging Face service\n",
"if selectedService == Service.HuggingFace:\n",
" hf_text_service = HuggingFaceTextCompletion(ai_model_id=\"distilgpt2\", task=\"text-generation\")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "50561d82",
"metadata": {},
"source": [
"Next, we'll set up the completion request settings for text completion services."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "628c843e",
"metadata": {},
"outputs": [],
"source": [
"oai_request_settings = OpenAITextRequestSettings(\n",
" max_tokens=150,\n",
" temperature=0.7,\n",
" top_p=1,\n",
" frequency_penalty=0.5,\n",
" presence_penalty=0.5,\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "857a9c89",
"metadata": {},
"source": [
"## Streaming Open AI Text Completion"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e2979db8",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.OpenAI:\n",
" prompt = \"what is the purpose of a rubber duck?\"\n",
" stream = oai_text_service.complete_stream_async(prompt=prompt, settings=oai_request_settings)\n",
" async for text in stream:\n",
" print(text, end=\"\") # end = \"\" to avoid newlines"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "4288d09f",
"metadata": {},
"source": [
"## Streaming Azure Open AI Text Completion"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5319f14d",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.AzureOpenAI:\n",
" prompt = \"provide me a list of possible meanings for the acronym 'ORLD'\"\n",
" stream = azure_text_service.complete_stream_async(prompt=prompt, settings=oai_request_settings)\n",
" async for text in stream:\n",
" print(text, end=\"\") # end = \"\" to avoid newlines"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "eb548f9c",
"metadata": {},
"source": [
"## Streaming Hugging Face Text Completion"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "be7b1c2e",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.HuggingFace:\n",
" from semantic_kernel.connectors.ai.hugging_face.hf_request_settings import (\n",
" HuggingFaceRequestSettings,\n",
" )\n",
"\n",
" hf_request_settings = HuggingFaceRequestSettings(\n",
" extension_data={\n",
" \"max_new_tokens\": 80,\n",
" \"top_p\": 1,\n",
" \"eos_token_id\": 11,\n",
" \"pad_token_id\": 0,\n",
" }\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9525e4f3",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.HuggingFace:\n",
" prompt = \"The purpose of a rubber duck is\"\n",
" stream = hf_text_service.complete_stream_async(prompt=prompt, request_settings=hf_request_settings)\n",
" async for text in stream:\n",
" print(text, end=\"\") # end = \"\" to avoid newlines"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "da632e12",
"metadata": {},
"source": [
"Here, we're setting up the settings for Chat completions."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e5f11e46",
"metadata": {},
"outputs": [],
"source": [
"oai_chat_request_settings = OpenAIChatRequestSettings(\n",
" max_tokens=150,\n",
" temperature=0.7,\n",
" top_p=1,\n",
" frequency_penalty=0.5,\n",
" presence_penalty=0.5,\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "d6bf238e",
"metadata": {},
"source": [
"## Streaming OpenAI Chat Completion"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dabc6a4c",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.OpenAI:\n",
" role = \"system\"\n",
" content = \"You are an AI assistant that helps people find information.\"\n",
" message = {\"role\": role, \"content\": content}\n",
" stream = oai_chat_service.complete_chat_stream_async(messages=[message], settings=oai_chat_request_settings)\n",
" async for text in stream:\n",
" print(text, end=\"\") # end = \"\" to avoid newlines"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "cdb8f740",
"metadata": {},
"source": [
"## Streaming Azure OpenAI Chat Completion"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "da1e9f59",
"metadata": {},
"outputs": [],
"source": [
"az_oai_chat_request_settings = AzureChatRequestSettings(\n",
" max_tokens=150,\n",
" temperature=0.7,\n",
" top_p=1,\n",
" frequency_penalty=0.5,\n",
" presence_penalty=0.5,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b74a64a9",
"metadata": {},
"outputs": [],
"source": [
"if selectedService == Service.AzureOpenAI:\n",
" role = \"system\"\n",
" content = \"You are an AI assistant that helps people find information.\"\n",
" message = {\"role\": role, \"content\": content}\n",
" stream = azure_chat_service.complete_chat_stream_async(messages=[message], settings=az_oai_chat_request_settings)\n",
" async for text in stream:\n",
" print(text, end=\"\") # end = \"\" to avoid newlines"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -206,7 +206,7 @@
"kernel.add_text_embedding_generation_service(\"ada\", OpenAITextEmbedding(\"text-embedding-ada-002\", api_key, org_id))\n",
"\n",
"kernel.register_memory_store(memory_store=store)\n",
"kernel.import_skill(sk.core_skills.TextMemorySkill())"
"kernel.import_plugin(sk.core_plugins.TextMemoryPlugin())"
]
},
{
@@ -339,8 +339,8 @@
" context[\"fact4\"] = \"where have I traveled?\"\n",
" context[\"fact5\"] = \"what do I do for work?\"\n",
"\n",
" context[sk.core_skills.TextMemorySkill.COLLECTION_PARAM] = COLLECTION\n",
" context[sk.core_skills.TextMemorySkill.RELEVANCE_PARAM] = 0.8\n",
" context[sk.core_plugins.TextMemoryPlugin.COLLECTION_PARAM] = COLLECTION\n",
" context[sk.core_plugins.TextMemoryPlugin.RELEVANCE_PARAM] = 0.8\n",
"\n",
" context[\"chat_history\"] = \"\"\n",
"\n",
@@ -416,13 +416,13 @@
"] = \"README: Installation, getting started, and how to contribute\"\n",
"github_files[\n",
" \"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/02-running-prompts-from-file.ipynb\"\n",
"] = \"Jupyter notebook describing how to pass prompts from a file to a semantic skill or function\"\n",
"] = \"Jupyter notebook describing how to pass prompts from a file to a semantic plugin or function\"\n",
"github_files[\n",
" \"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/00-getting-started.ipynb\"\n",
"] = \"Jupyter notebook describing how to get started with the Semantic Kernel\"\n",
"github_files[\n",
" \"https://github.com/microsoft/semantic-kernel/tree/main/samples/skills/ChatSkill/ChatGPT\"\n",
"] = \"Sample demonstrating how to create a chat skill interfacing with ChatGPT\"\n",
" \"https://github.com/microsoft/semantic-kernel/tree/main/samples/plugins/ChatPlugin/ChatGPT\"\n",
"] = \"Sample demonstrating how to create a chat plugin interfacing with ChatGPT\"\n",
"github_files[\n",
" \"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/src/SemanticKernel/Memory/Volatile/VolatileMemoryStore.cs\"\n",
"] = \"C# class that defines a volatile embedding store\""
@@ -4,7 +4,12 @@ import semantic_kernel as sk
from semantic_kernel.connectors.ai.open_ai import (
OpenAIChatCompletion,
)
from semantic_kernel.core_skills import FileIOSkill, MathSkill, TextSkill, TimeSkill
from semantic_kernel.core_plugins import (
FileIOPlugin,
MathPlugin,
TextPlugin,
TimePlugin,
)
from semantic_kernel.planning import ActionPlanner
@@ -13,10 +18,10 @@ async def main():
api_key, org_id = sk.openai_settings_from_dot_env()
kernel.add_chat_service("chat-gpt", OpenAIChatCompletion("gpt-3.5-turbo", api_key, org_id))
kernel.import_skill(MathSkill(), "math")
kernel.import_skill(FileIOSkill(), "fileIO")
kernel.import_skill(TimeSkill(), "time")
kernel.import_skill(TextSkill(), "text")
kernel.import_plugin(MathPlugin(), "math")
kernel.import_plugin(FileIOPlugin(), "fileIO")
kernel.import_plugin(TimePlugin(), "time")
kernel.import_plugin(TextPlugin(), "text")
# create an instance of action planner.
planner = ActionPlanner(kernel)
@@ -46,7 +46,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_config = sk.PromptTemplateConfig(execution_settings=req_settings)
prompt_template = sk.ChatPromptTemplate("{{$user_input}}", kernel.prompt_template_engine, prompt_config)
@@ -38,7 +38,7 @@ az_source = AzureAISearchDataSources(**azure_ai_search_settings)
az_data = AzureDataSources(type="AzureCognitiveSearch", parameters=az_source)
extra = ExtraBody(dataSources=[az_data])
req_settings = AzureChatRequestSettings(extra_body=extra)
prompt_config = sk.PromptTemplateConfig(completion=req_settings)
prompt_config = sk.PromptTemplateConfig(execution_settings=req_settings)
# When using data, set use_extensions=True and use the 2023-12-01-preview API version.
chat_service = sk_oai.AzureChatCompletion(
@@ -26,7 +26,7 @@ az_source = AzureAISearchDataSources(**azure_ai_search_settings)
az_data = AzureDataSources(type="AzureCognitiveSearch", parameters=az_source)
extra = ExtraBody(data_sources=[az_data], input_language="fr", output_language="de")
req_settings = AzureChatRequestSettings(extra_body=extra)
prompt_config = sk.PromptTemplateConfig(completion=req_settings)
prompt_config = sk.PromptTemplateConfig(execution_settings=req_settings)
# For example, AI Search index may contain the following document:
@@ -19,7 +19,7 @@ from semantic_kernel.connectors.ai.open_ai.utils import (
chat_completion_with_function_call,
get_function_calling_object,
)
from semantic_kernel.core_skills.time_skill import TimeSkill
from semantic_kernel.core_plugins.time_plugin import TimePlugin
kernel = sk.Kernel()
@@ -51,20 +51,20 @@ kernel.add_chat_service(
chat_service,
)
skills_directory = os.path.join(__file__, "../../../../samples/skills")
# adding skills to the kernel
# the joke skill in the FunSkills is a semantic skill and has the function calling disabled.
kernel.import_semantic_skill_from_directory(skills_directory, "FunSkill")
# the math skill is a core skill and has the function calling enabled.
kernel.import_skill(TimeSkill(), skill_name="time")
plugins_directory = os.path.join(__file__, "../../../../samples/plugins")
# adding plugins to the kernel
# the joke plugin in the FunPlugins is a semantic plugin and has the function calling disabled.
kernel.import_semantic_plugin_from_directory(plugins_directory, "FunPlugin")
# the math plugin is a core plugin and has the function calling enabled.
kernel.import_plugin(TimePlugin(), plugin_name="time")
# enabling or disabling function calling is done by setting the tool_choice parameter for the completion.
# when the tool_choice parameter is set to "auto" the model will decide which function to use, if any.
# if you only want to use a specific tool, set the name of that tool in this parameter,
# the format for that is 'SkillName-FunctionName', (i.e. 'math-Add').
# the format for that is 'PluginName-FunctionName', (i.e. 'math-Add').
# if the model or api version do not support this you will get an error.
req_settings.tool_choice = "auto"
prompt_config = sk.PromptTemplateConfig(completion=req_settings)
prompt_config = sk.PromptTemplateConfig(execution_settings=req_settings)
prompt_template = OpenAIChatPromptTemplate("{{$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 an AI assistant here to answer your questions.")
@@ -73,9 +73,9 @@ 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.
# to enable or disable function calling or set the function calling to a specific plugin.
# see the openai_function_calling example for how to use this with a unrelated function definition
filter = {"exclude_skill": ["ChatBot"]}
filter = {"exclude_plugin": ["ChatBot"]}
functions = get_function_calling_object(kernel, filter)
@@ -96,7 +96,7 @@ async def chat(context: sk.SKContext) -> Tuple[bool, sk.SKContext]:
context = await chat_completion_with_function_call(
kernel,
chat_skill_name="ChatBot",
chat_plugin_name="ChatBot",
chat_function_name="Chat",
context=context,
functions=functions,
@@ -32,7 +32,10 @@ azure_ai_search_settings["fieldsMapping"] = {
"vectorFields": ["vector"],
}
# Add Ada embedding deployment name to the settings and use vector search.
azure_ai_search_settings["embeddingDependency"] = {"type": "DeploymentName", "deploymentName": "ada-002"}
azure_ai_search_settings["embeddingDependency"] = {
"type": "DeploymentName",
"deploymentName": "ada-002",
}
azure_ai_search_settings["queryType"] = "vector"
# Create the data source settings
@@ -40,7 +43,7 @@ az_source = AzureAISearchDataSources(**azure_ai_search_settings)
az_data = AzureDataSources(type="AzureCognitiveSearch", parameters=az_source)
extra = ExtraBody(dataSources=[az_data])
req_settings = AzureChatRequestSettings(extra_body=extra)
prompt_config = sk.PromptTemplateConfig(completion=req_settings)
prompt_config = sk.PromptTemplateConfig(execution_settings=req_settings)
# When using data, set use_extensions=True and use the 2023-12-01-preview API version.
chat_service = sk_oai.AzureChatCompletion(
@@ -5,7 +5,7 @@ from dotenv import load_dotenv
import semantic_kernel as sk
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.connectors.search_engine import BingConnector
from semantic_kernel.core_skills import WebSearchEngineSkill
from semantic_kernel.core_plugins import WebSearchEnginePlugin
load_dotenv()
@@ -23,10 +23,10 @@ async def main():
),
)
connector = BingConnector(api_key=os.getenv("BING_API_KEY"))
web_skill = kernel.import_skill(WebSearchEngineSkill(connector), "WebSearch")
web_plugin = kernel.import_plugin(WebSearchEnginePlugin(connector), "WebSearch")
prompt = "Who is Leonardo DiCaprio's current girlfriend?"
search_async = web_skill["searchAsync"]
search_async = web_plugin["searchAsync"]
result = await search_async.invoke_async(prompt)
print(result)
@@ -20,7 +20,7 @@ kernel = sk.Kernel()
api_key, org_id = sk.openai_settings_from_dot_env()
kernel.add_chat_service("chat-gpt", sk_oai.OpenAIChatCompletion("gpt-3.5-turbo", api_key, org_id))
prompt_config = sk.PromptTemplateConfig.from_completion_parameters(max_tokens=2000, temperature=0.7, top_p=0.4)
prompt_config = sk.PromptTemplateConfig.from_execution_settings(max_tokens=2000, temperature=0.7, top_p=0.4)
prompt_template = sk.PromptTemplate(sk_prompt, kernel.prompt_template_engine, prompt_config)
@@ -19,7 +19,7 @@ kernel = sk.Kernel()
api_key, org_id = sk.openai_settings_from_dot_env()
kernel.add_chat_service("chat-gpt", sk_oai.OpenAIChatCompletion("gpt-3.5-turbo", api_key, org_id))
prompt_config = sk.PromptTemplateConfig.from_completion_parameters(max_tokens=2000, temperature=0.7, top_p=0.8)
prompt_config = sk.PromptTemplateConfig.from_execution_settings(max_tokens=2000, temperature=0.7, top_p=0.8)
prompt_template = sk.ChatPromptTemplate("{{$user_input}}", kernel.prompt_template_engine, prompt_config)
@@ -13,7 +13,7 @@ from semantic_kernel.connectors.ai.open_ai.utils import (
chat_completion_with_function_call,
get_function_calling_object,
)
from semantic_kernel.core_skills import MathSkill
from semantic_kernel.core_plugins import MathPlugin
system_message = """
You are a chat bot. Your name is Mosscap and
@@ -42,27 +42,27 @@ kernel.add_chat_service(
),
)
skills_directory = os.path.join(__file__, "../../../../samples/skills")
# adding skills to the kernel
# the joke skill in the FunSkills is a semantic skill and has the function calling disabled.
kernel.import_semantic_skill_from_directory(skills_directory, "FunSkill")
# the math skill is a core skill and has the function calling enabled.
kernel.import_skill(MathSkill(), skill_name="math")
plugins_directory = os.path.join(__file__, "../../../../samples/plugins")
# adding plugins to the kernel
# the joke plugin in the FunPlugins is a semantic plugin and has the function calling disabled.
kernel.import_semantic_plugin_from_directory(plugins_directory, "FunPlugin")
# the math plugin is a core plugin and has the function calling enabled.
kernel.import_plugin(MathPlugin(), plugin_name="math")
# enabling or disabling function calling is done by setting the function_call parameter for the completion.
# when the function_call parameter is set to "auto" the model will decide which function to use, if any.
# 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').
# the format for that is 'PluginName-FunctionName', (i.e. 'math-Add').
# if the model or api version do not support this you will get an error.
prompt_config = sk.PromptTemplateConfig(
completion=sk_oai.AzureChatRequestSettings(
execution_settings=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"]}),
functions=get_function_calling_object(kernel, {"exclude_plugin": ["ChatBot"]}),
)
)
prompt_template = OpenAIChatPromptTemplate("{{$user_input}}", kernel.prompt_template_engine, prompt_config)
@@ -91,7 +91,7 @@ async def chat(context: sk.SKContext) -> Tuple[bool, sk.SKContext]:
context = await chat_completion_with_function_call(
kernel,
chat_skill_name="ChatBot",
chat_plugin_name="ChatBot",
chat_function_name="Chat",
context=context,
)
@@ -13,7 +13,7 @@ kernel.add_text_embedding_generation_service("gecko", palm_text_embed)
palm_chat_completion = sk_gp.GooglePalmChatCompletion("models/chat-bison-001", apikey)
kernel.add_chat_service("models/chat-bison-001", palm_chat_completion)
kernel.register_memory_store(memory_store=sk.memory.VolatileMemoryStore())
kernel.import_skill(sk.core_skills.TextMemorySkill())
kernel.import_plugin(sk.core_plugins.TextMemoryPlugin())
async def populate_memory(kernel: sk.Kernel) -> None:
@@ -67,7 +67,7 @@ async def setup_chat_with_memory(
""".strip()
prompt_config = sk.PromptTemplateConfig.from_completion_parameters(max_tokens=2000, temperature=0.7, top_p=0.8)
prompt_config = sk.PromptTemplateConfig.from_execution_settings(max_tokens=2000, temperature=0.7, top_p=0.8)
prompt_template = sk.ChatPromptTemplate( # Create the chat prompt template
"{{$user_input}}", kernel.prompt_template_engine, prompt_config
)
@@ -82,8 +82,8 @@ async def setup_chat_with_memory(
context["fact4"] = "where did I travel last year?"
context["fact5"] = "what do I do for work?"
context[sk.core_skills.TextMemorySkill.COLLECTION_PARAM] = "aboutMe"
context[sk.core_skills.TextMemorySkill.RELEVANCE_PARAM] = 0.6
context[sk.core_plugins.TextMemoryPlugin.COLLECTION_PARAM] = "aboutMe"
context[sk.core_plugins.TextMemoryPlugin.RELEVANCE_PARAM] = 0.6
context["chat_history"] = ""
@@ -30,13 +30,13 @@ kernel = sk.Kernel()
api_key = sk.google_palm_settings_from_dot_env()
palm_chat_completion = sk_gp.GooglePalmChatCompletion("models/chat-bison-001", api_key)
kernel.add_chat_service("models/chat-bison-001", palm_chat_completion)
prompt_config = sk.PromptTemplateConfig.from_completion_parameters(max_tokens=2000, temperature=0.7, top_p=0.8)
prompt_config = sk.PromptTemplateConfig.from_execution_settings(max_tokens=2000, temperature=0.7, top_p=0.8)
prompt_template = sk.ChatPromptTemplate("{{$user_input}}", kernel.prompt_template_engine, prompt_config)
prompt_template.add_system_message(system_message) # Add the system message for context
prompt_template.add_user_message("Hi there, my name is Andrea, who are you?") # Include a chat history
prompt_template.add_assistant_message("I am Blackbeard.")
function_config = sk.SemanticFunctionConfig(prompt_config, prompt_template)
chat_function = kernel.register_semantic_function("PirateSkill", "Chat", function_config)
chat_function = kernel.register_semantic_function("PiratePlugin", "Chat", function_config)
async def chat() -> bool:
@@ -7,7 +7,7 @@ from dotenv import load_dotenv
import semantic_kernel as sk
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
from semantic_kernel.connectors.search_engine import GoogleConnector
from semantic_kernel.core_skills import WebSearchEngineSkill
from semantic_kernel.core_plugins import WebSearchEnginePlugin
load_dotenv()
@@ -33,12 +33,12 @@ async def main():
search_engine_id=os.getenv("GOOGLE_SEARCH_ENGINE_ID"),
)
# Import the WebSearchEngineSkill and pass the Google Connector to it.
web_skill = kernel.import_skill(WebSearchEngineSkill(connector), "WebSearch")
# Import the WebSearchEnginePlugin and pass the Google Connector to it.
web_plugin = kernel.import_plugin(WebSearchEnginePlugin(connector), "WebSearch")
# The search query
prompt = "Who is Leonardo DiCaprio's current girlfriend?"
search_async = web_skill["searchAsync"]
search_async = web_plugin["searchAsync"]
# By default, only one search result is provided
result = await search_async.invoke_async(prompt)
@@ -50,7 +50,7 @@ async def main():
Relationship From the beginning of their romance to today, we track their relationship here. By..."]
"""
# Following example demonstrates the use of the skill within a semantic function
# Following example demonstrates the use of the plugin within a semantic function
prompt = """
Answer the question using only the data that is provided in the data section.
Do not use any prior knowledge to answer the question.
@@ -63,7 +63,7 @@ async def main():
context = kernel.create_new_context()
"""
Two context parameters can be passed to the search engine skill.
Two context parameters can be passed to the search engine plugin.
- num_results controls the number of results returned by the web search.
- offset controls the number of results to omit.
"""
@@ -75,10 +75,10 @@ def setup(use_azure: bool = False):
OpenAIChatCompletion(ai_model_id="gpt-3.5-turbo", api_key=api_key, org_id=org_id),
)
# note: using skills from the samples folder
skills_directory = "../samples/skills/"
# note: using plugins from the samples folder
plugins_directory = "../samples/plugins/"
grounding_semantic_functions = kernel.import_semantic_skill_from_directory(skills_directory, "GroundingSkill")
grounding_semantic_functions = kernel.import_semantic_plugin_from_directory(plugins_directory, "GroundingPlugin")
# entity_extraction = grounding_semantic_functions["ExtractEntities"]
# reference_check = grounding_semantic_functions["ReferenceCheckEntities"]
@@ -118,7 +118,7 @@ async def run_entity_excision(semantic_functions, summary_text, context):
async def run_grounding(use_azure: bool = False):
kernel, semantic_functions = setup(use_azure)
print(f"\n{Colors.CBOLD}Groundingsness Checking Skills\n{Colors.CEND}")
print(f"\n{Colors.CBOLD}Groundingsness Checking Plugins\n{Colors.CEND}")
print(f"\n{ '-'*80 }\n")
print(
f"""{Colors.CGREEN}A well-known problem with large language models (LLMs) is that they make things up. These are sometimes called 'hallucinations' but a safer (and less anthropomorphic) term is 'ungrounded addition' - something in the text which cannot be firmly established. When attempting to establish whether or not something in an LLM response is 'true' we can either check for it in the supplied prompt (this is called 'narrow grounding') or use our general knowledge ('broad grounding'). Note that narrow grounding can lead to things being classified as 'true, but ungrounded.' For example "I live in Switzerland" is **not** _narrowly_ grounded in "I live in Geneva" even though it must be true (it **is** _broadly_ grounded). # noqa: E501
@@ -147,7 +147,7 @@ What is an 'entity' in this context? In its simplest form, it's a named object s
- A reference to Rome has been added
The grounding skill has three stages:
The grounding plugin has three stages:
1. Extract entities from a summary text
2. Perform a reference check against the grounding text
@@ -62,8 +62,8 @@ async def setup_chat_with_memory(
context["fact4"] = "where have I traveled?"
context["fact5"] = "what do I do for work?"
context[sk.core_skills.TextMemorySkill.COLLECTION_PARAM] = "aboutMe"
context[sk.core_skills.TextMemorySkill.RELEVANCE_PARAM] = 0.8
context[sk.core_plugins.TextMemoryPlugin.COLLECTION_PARAM] = "aboutMe"
context[sk.core_plugins.TextMemoryPlugin.RELEVANCE_PARAM] = 0.8
context["chat_history"] = ""
@@ -102,7 +102,7 @@ async def main() -> None:
)
kernel.register_memory_store(memory_store=sk.memory.VolatileMemoryStore())
kernel.import_skill(sk.core_skills.TextMemorySkill())
kernel.import_plugin(sk.core_plugins.TextMemoryPlugin())
print("Populating memory...")
await populate_memory(kernel)
@@ -8,7 +8,7 @@ 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
from semantic_kernel.core_plugins import MathPlugin
system_message = """
You are a chat bot. Your name is Mosscap and
@@ -25,60 +25,61 @@ you will return a full answer to me as soon as possible.
kernel = sk.Kernel()
deployment_name, api_key, endpoint = sk.azure_openai_settings_from_dot_env()
api_version = "2023-07-01-preview"
api_key, org_id = sk.openai_settings_from_dot_env()
kernel.add_chat_service(
"chat-gpt",
sk_oai.AzureChatCompletion(
deployment_name,
endpoint,
api_version=api_version,
"gpt-3.5-turbo",
sk_oai.OpenAIChatCompletion(
ai_model_id="gpt-3.5-turbo-1106",
api_key=api_key,
),
)
skills_directory = os.path.join(__file__, "../../../../samples/skills")
# adding skills to the kernel
# the joke skill in the FunSkills is a semantic skill and has the function calling disabled.
kernel.import_semantic_skill_from_directory(skills_directory, "FunSkill")
# the math skill is a core skill and has the function calling enabled.
kernel.import_skill(MathSkill(), skill_name="math")
plugins_directory = os.path.join(__file__, "../../../../samples/plugins")
# adding plugins to the kernel
# the joke plugin in the FunPlugins is a semantic plugin and has the function calling disabled.
kernel.import_semantic_plugin_from_directory(plugins_directory, "FunPlugin")
# the math plugin is a core plugin and has the function calling enabled.
kernel.import_plugin(MathPlugin(), plugin_name="math")
# enabling or disabling function calling is done by setting the function_call parameter for the completion.
# when the function_call parameter is set to "auto" the model will decide which function to use, if any.
# 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').
# the format for that is 'PluginName-FunctionName', (i.e. 'math-Add').
# if the model or api version do not support this you will get an error.
functions = [
tools = [
{
"name": "search_hotels",
"description": "Retrieves hotels from the search index based on the parameters provided",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The location of the hotel (i.e. Seattle, WA)",
},
"max_price": {
"type": "number",
"description": "The maximum price for the hotel",
},
"features": {
"type": "string",
"description": "A comma separated list of features (i.e. beachfront, free wifi, etc.)",
"type": "function",
"function": {
"name": "search_hotels",
"description": "Retrieves hotels from the search index based on the parameters provided",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The location of the hotel (i.e. Seattle, WA)",
},
"max_price": {
"type": "number",
"description": "The maximum price for the hotel",
},
"features": {
"type": "string",
"description": "A comma separated list of features (i.e. beachfront, free wifi, etc.)",
},
},
"required": ["location"],
},
"required": ["location"],
},
}
]
prompt_config = sk.PromptTemplateConfig.from_completion_parameters(
prompt_config = sk.PromptTemplateConfig.from_execution_settings(
max_tokens=2000,
temperature=0.7,
top_p=0.8,
function_call="auto",
functions=functions,
tool_choice="auto",
tools=tools,
)
prompt_template = sk.ChatPromptTemplate[OpenAIChatMessage](
"{{$user_input}}", kernel.prompt_template_engine, prompt_config
@@ -97,12 +98,12 @@ async def main() -> None:
context.variables["user_input"] = "I want to find a hotel in Seattle with free wifi and a pool."
context = await chat_function.invoke_async(context=context)
if function_call := context.objects.pop("function_call"):
if function_call := context.objects.get("function_call"):
print(f"Function to be called: {function_call.name}")
print(f"Function parameters: \n{function_call.arguments}")
return
print("No function was called")
print(f"Output was: {str(context)}")
else:
print("No function was called")
print(f"Output was: {str(context)}")
if __name__ == "__main__":
@@ -68,7 +68,7 @@ async def chat_request_example(kernel, api_key, org_id):
settings = kernel.get_request_settings_from_service(ChatCompletionClientBase, "chat_service")
settings = _config_ban_tokens(settings, keys)
prompt_config = sk.PromptTemplateConfig.from_completion_parameters(max_tokens=2000, temperature=0.7, top_p=0.8)
prompt_config = sk.PromptTemplateConfig.from_execution_settings(max_tokens=2000, temperature=0.7, top_p=0.8)
prompt_template = sk.ChatPromptTemplate("{{$user_input}}", kernel.prompt_template_engine, prompt_config)
# Setup chat with prompt
@@ -106,7 +106,7 @@ async def chat_request_example(kernel, api_key, org_id):
async def text_complete_request_example(kernel, api_key, org_id):
openai_text_completion = sk_oai.OpenAITextCompletion("text-davinci-002", api_key, org_id)
openai_text_completion = sk_oai.OpenAITextCompletion("gpt-3.5-turbo-instruct", api_key, org_id)
kernel.add_text_completion_service("text_service", openai_text_completion)
# Spaces and capitalization affect the token ids.
@@ -6,4 +6,4 @@
3. Start the server by running `python openapi_server.py`.
4. In another terminal, do steps 1 & 2. Then, run `python openapi_client.py`, which will register a skill representing the API defined in openapi.yaml
4. In another terminal, do steps 1 & 2. Then, run `python openapi_client.py`, which will register a plugin representing the API defined in openapi.yaml
@@ -1,13 +1,13 @@
import asyncio
import semantic_kernel as sk
from semantic_kernel.connectors.openapi import register_openapi_skill
from semantic_kernel.connectors.openapi import register_openapi_plugin
if __name__ == "__main__":
"""Client"""
kernel = sk.Kernel()
openapi_skill = register_openapi_skill(kernel, "openApiSkill", "openapi.yaml")
openapi_plugin = register_openapi_plugin(kernel, "openApiPlugin", "openapi.yaml")
context_variables = sk.ContextVariables(
variables={
@@ -19,6 +19,6 @@ if __name__ == "__main__":
)
result = asyncio.run(
# Call the function defined in openapi.yaml
openapi_skill["helloWorld"].invoke_async(variables=context_variables)
openapi_plugin["helloWorld"].invoke_async(variables=context_variables)
)
print(result)
@@ -0,0 +1,34 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
import semantic_kernel as sk
import semantic_kernel.connectors.ai.open_ai as sk_oai
kernel = sk.Kernel()
useAzureOpenAI = True
model = "gpt-35-turbo-instruct" if useAzureOpenAI else "gpt-3.5-turbo-instruct"
service_id = model
# Configure AI service used by the kernel
if useAzureOpenAI:
deployment_name, api_key, endpoint = sk.azure_openai_settings_from_dot_env()
kernel.add_text_completion_service(
service_id,
sk_oai.AzureTextCompletion(deployment_name=model, api_key=api_key, endpoint=endpoint),
)
else:
api_key, org_id = sk.openai_settings_from_dot_env()
kernel.add_text_completion_service(
service_id,
sk_oai.OpenAITextCompletion(deployment_name=model, api_key=api_key, org_id=org_id),
)
# note: using plugins from the samples folder
plugins_directory = os.path.join(__file__, "../../../../samples/plugins")
plugin = kernel.import_semantic_plugin_from_directory(plugins_directory, "FunPlugin")
result = asyncio.run(kernel.run_async(plugin["Joke"], input_str="time travel to dinosaur age"))
print(result)
@@ -12,7 +12,7 @@ from semantic_kernel.connectors.ai.open_ai import (
from semantic_kernel.connectors.memory.azure_cognitive_search import (
AzureCognitiveSearchMemoryStore,
)
from semantic_kernel.core_skills.text_memory_skill import TextMemorySkill
from semantic_kernel.core_plugins.text_memory_plugin import TextMemoryPlugin
from semantic_kernel.orchestration.context_variables import ContextVariables
COLLECTION_NAME = "generic"
@@ -35,8 +35,8 @@ async def populate_memory(kernel: sk.Kernel) -> None:
async def main() -> None:
kernel = sk.Kernel()
tms = TextMemorySkill()
kernel.import_skill(tms, "memory")
tms = TextMemoryPlugin()
kernel.import_plugin(tms, "memory")
config = dotenv_values(".env")
@@ -101,7 +101,13 @@ Remember, just answer Grounded or Ungrounded or Unclear: """.strip()
answer = await kernel.run_async(
chat_func,
input_vars=ContextVariables(variables={"user_input": user_input, "collection": COLLECTION_NAME, "limit": "2"}),
input_vars=ContextVariables(
variables={
"user_input": user_input,
"collection": COLLECTION_NAME,
"limit": "2",
}
),
)
print(f"Answer: {str(answer).strip()}")
check = await kernel.run_async(self_critique_func, input_context=answer)
@@ -2,7 +2,12 @@
import semantic_kernel as sk
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
from semantic_kernel.core_skills import FileIOSkill, MathSkill, TextSkill, TimeSkill
from semantic_kernel.core_plugins import (
FileIOPlugin,
MathPlugin,
TextPlugin,
TimePlugin,
)
from semantic_kernel.planning import SequentialPlanner
@@ -10,11 +15,11 @@ async def main():
kernel = sk.Kernel()
api_key, org_id = sk.openai_settings_from_dot_env()
kernel.add_chat_service("gpt-3.5", OpenAIChatCompletion("gpt-3.5-turbo", api_key, org_id))
kernel.import_skill(MathSkill(), "math")
kernel.import_skill(FileIOSkill(), "fileIO")
kernel.import_skill(TimeSkill(), "time")
kernel.import_skill(TextSkill(), "text")
kernel.add_chat_service("gpt-3.5", OpenAIChatCompletion("gpt-3.5-turbo", api_key=api_key, org_id=org_id))
kernel.import_plugin(MathPlugin(), "math")
kernel.import_plugin(FileIOPlugin(), "fileIO")
kernel.import_plugin(TimePlugin(), "time")
kernel.import_plugin(TextPlugin(), "text")
# create an instance of sequential planner.
planner = SequentialPlanner(kernel)
@@ -18,9 +18,9 @@ async def main():
kernel.add_chat_service("chat-gpt", OpenAIChatCompletion("gpt-3.5-turbo", api_key, org_id))
skill = kernel.import_semantic_skill_from_directory("../../samples/skills", "FunSkill")
plugin = kernel.import_semantic_plugin_from_directory("../../samples/plugins", "FunPlugin")
joke_function = skill["Joke"]
joke_function = plugin["Joke"]
print(joke_function("time travel to dinosaur age"))
@@ -1,28 +0,0 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
import semantic_kernel as sk
import semantic_kernel.connectors.ai.open_ai as sk_oai
kernel = sk.Kernel()
useAzureOpenAI = False
model = "text-davinci-002"
service_id = model
# Configure AI service used by the kernel
if useAzureOpenAI:
api_key, endpoint = sk.azure_openai_settings_from_dot_env()
kernel.add_text_completion_service(service_id, sk_oai.AzureTextCompletion(model, api_key, endpoint))
else:
api_key, org_id = sk.openai_settings_from_dot_env()
kernel.add_text_completion_service(service_id, sk_oai.OpenAITextCompletion(model, api_key, org_id))
# note: using skills from the samples folder
skills_directory = os.path.join(__file__, "../../../../samples/skills")
skill = kernel.import_semantic_skill_from_directory(skills_directory, "FunSkill")
result = asyncio.run(kernel.run_async(skill["Joke"], input_str="time travel to dinosaur age"))
print(result)
+2 -2
View File
@@ -1,6 +1,6 @@
# Copyright (c) Microsoft. All rights reserved.
from semantic_kernel import core_skills, memory
from semantic_kernel import core_plugins, memory
from semantic_kernel.kernel import Kernel
from semantic_kernel.orchestration.context_variables import ContextVariables
from semantic_kernel.orchestration.sk_context import SKContext
@@ -51,6 +51,6 @@ __all__ = [
"SKFunctionBase",
"SKContext",
"memory",
"core_skills",
"core_plugins",
"setup_logging",
]
@@ -127,7 +127,7 @@ class GooglePalmChatCompletion(ChatCompletionClientBase, TextCompletionClientBas
conversation has not been initiated yet, it is assumed that chat history
is needed for context. All messages preceding the last message will be
utilized for context. This also enables Google PaLM to utilize memory
and skills, which should be stored in the messages parameter as system
and plugins, which should be stored in the messages parameter as system
messages.
Arguments:
@@ -9,8 +9,12 @@ from pydantic import HttpUrl
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.ollama.ollama_request_settings import OllamaChatRequestSettings
from semantic_kernel.connectors.ai.chat_completion_client_base import (
ChatCompletionClientBase,
)
from semantic_kernel.connectors.ai.ollama.ollama_request_settings import (
OllamaChatRequestSettings,
)
from semantic_kernel.connectors.ai.ollama.utils import AsyncSession
from semantic_kernel.connectors.ai.text_completion_client_base import (
TextCompletionClientBase,
@@ -9,7 +9,9 @@ from pydantic import HttpUrl
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.ollama.ollama_request_settings import OllamaTextRequestSettings
from semantic_kernel.connectors.ai.ollama.ollama_request_settings import (
OllamaTextRequestSettings,
)
from semantic_kernel.connectors.ai.ollama.utils import AsyncSession
from semantic_kernel.connectors.ai.text_completion_client_base import (
TextCompletionClientBase,
@@ -42,7 +42,8 @@ class OllamaTextEmbedding(EmbeddingGeneratorBase, AIServiceClientBase):
"""
async with AsyncSession(self.session) as session:
async with session.post(
self.url, json={"model": self.ai_model_id, "texts": texts, "options": kwargs}
self.url,
json={"model": self.ai_model_id, "texts": texts, "options": kwargs},
) as response:
response.raise_for_status()
return array(await response.json())
@@ -11,6 +11,7 @@ class FunctionCall(SKBaseModel):
name: str
arguments: str
id: str
def parse_arguments(self) -> Dict[str, str]:
"""Parse the arguments into a dictionary."""
@@ -25,12 +26,12 @@ class FunctionCall(SKBaseModel):
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."""
"""Split the name into a plugin and function name."""
if "-" not in self.name:
return None, self.name
return self.name.split("-")
def split_name_dict(self) -> dict:
"""Split the name into a skill and function name."""
"""Split the name into a plugin and function name."""
parts = self.split_name()
return {"skill_name": parts[0], "function_name": parts[1]}
return {"plugin_name": parts[0], "function_name": parts[1]}
@@ -71,20 +71,20 @@ class OpenAIChatPromptTemplate(ChatPromptTemplate[OpenAIChatMessage]):
) -> "OpenAIChatPromptTemplate":
"""Restore a ChatPromptTemplate from a list of role and message pairs.
If there is a chat_system_prompt in the prompt_config.completion settings,
If there is a chat_system_prompt in the prompt_config.execution_settings settings,
that takes precedence over the first message in the list of messages,
if that is a system message.
"""
if log:
logger.warning("The `log` parameter is deprecated. Please use the `logging` module instead.")
chat_template = cls(template, template_engine, prompt_config)
if prompt_config.completion.chat_system_prompt and messages[0]["role"] == "system":
if prompt_config.execution_settings.chat_system_prompt and messages[0]["role"] == "system":
existing_system_message = messages.pop(0)
if existing_system_message["message"] != prompt_config.completion.chat_system_prompt:
if existing_system_message["message"] != prompt_config.execution_settings.chat_system_prompt:
logger.info(
"Overriding system prompt with chat_system_prompt, old system message: %s, new system message: %s",
existing_system_message["message"],
prompt_config.completion.chat_system_prompt,
prompt_config.execution_settings.chat_system_prompt,
)
for message in messages:
chat_template.add_message(
@@ -23,7 +23,7 @@ def _describe_function(function: SKFunctionBase) -> Dict[str, str]:
"""
func_view = function.describe()
return {
"name": f"{func_view.skill_name}-{func_view.name}",
"name": f"{func_view.plugin_name}-{func_view.name}",
"description": func_view.description,
"parameters": {
"type": "object",
@@ -41,51 +41,51 @@ def get_function_calling_object(kernel: Kernel, filter: Dict[str, List[str]]) ->
args:
kernel: the kernel.
filter: a dictionary with keys
exclude_skill, include_skill, exclude_function, include_function
exclude_plugin, include_plugin, exclude_function, include_function
and lists of the required filter.
The function name should be in the format "skill_name-function_name".
Using exclude_skill and include_skill at the same time will raise an error.
The function name should be in the format "plugin_name-function_name".
Using exclude_plugin and include_plugin at the same time will raise an error.
Using exclude_function and include_function at the same time will raise an error.
If using include_* implies that all other function will be excluded.
Example:
filter = {
"exclude_skill": ["skill1", "skill2"],
"include_function": ["skill3-function1", "skill4-function2"],
"exclude_plugin": ["plugin1", "plugin2"],
"include_function": ["plugin3-function1", "plugin4-function2"],
}
will return only skill3-function1 and skill4-function2.
will return only plugin3-function1 and plugin4-function2.
filter = {
"exclude_function": ["skill1-function1", "skill2-function2"],
"exclude_function": ["plugin1-function1", "plugin2-function2"],
}
will return all functions except skill1-function1 and skill2-function2.
will return all functions except plugin1-function1 and plugin2-function2.
caller_function_name: the name of the function that is calling the other functions.
returns:
a filtered list of dictionaries of the functions in the kernel that can be passed to the function calling api.
"""
include_skill = filter.get("include_skill", None)
exclude_skill = filter.get("exclude_skill", [])
include_plugin = filter.get("include_plugin", None)
exclude_plugin = filter.get("exclude_plugin", [])
include_function = filter.get("include_function", None)
exclude_function = filter.get("exclude_function", [])
if include_skill and exclude_skill:
raise ValueError("Cannot use both include_skill and exclude_skill at the same time.")
if include_plugin and exclude_plugin:
raise ValueError("Cannot use both include_plugin and exclude_plugin at the same time.")
if include_function and exclude_function:
raise ValueError("Cannot use both include_function and exclude_function at the same time.")
if include_skill:
include_skill = [skill.lower() for skill in include_skill]
if exclude_skill:
exclude_skill = [skill.lower() for skill in exclude_skill]
if include_plugin:
include_plugin = [plugin.lower() for plugin in include_plugin]
if exclude_plugin:
exclude_plugin = [plugin.lower() for plugin in exclude_plugin]
if include_function:
include_function = [function.lower() for function in include_function]
if exclude_function:
exclude_function = [function.lower() for function in exclude_function]
result = []
for (
skill_name,
skill,
) in kernel.skills.data.items():
if skill_name in exclude_skill or (include_skill and skill_name not in include_skill):
plugin_name,
plugin,
) in kernel.plugins.data.items():
if plugin_name in exclude_plugin or (include_plugin and plugin_name not in include_plugin):
continue
for function_name, function in skill.items():
current_name = f"{skill_name}-{function_name}"
for function_name, function in plugin.items():
current_name = f"{plugin_name}-{function_name}"
if current_name in exclude_function or (include_function and current_name not in include_function):
continue
result.append(_describe_function(function))
@@ -106,7 +106,7 @@ async def execute_function_call(kernel: Kernel, function_call: FunctionCall, log
async def chat_completion_with_function_call(
kernel: Kernel,
context: SKContext,
chat_skill_name: Optional[str] = None,
chat_plugin_name: Optional[str] = None,
chat_function_name: Optional[str] = None,
chat_function: Optional[SKFunctionBase] = None,
*,
@@ -129,10 +129,10 @@ async def chat_completion_with_function_call(
functions: the function calling object,
make sure to use get_function_calling_object method to create it.
Optional arguments:
chat_skill_name: the skill name of the chat function.
chat_plugin_name: the plugin name of the chat function.
chat_function_name: the function name of the chat function.
chat_function: the chat function, if not provided, it will be retrieved from the kernel.
make sure to provide either the chat_function or the chat_skill_name and chat_function_name.
make sure to provide either the chat_function or the chat_plugin_name and chat_function_name.
max_function_calls: the maximum number of function calls to execute, defaults to 5.
current_call_count: the current number of function calls executed.
@@ -147,11 +147,11 @@ async def chat_completion_with_function_call(
current_call_count = kwargs.get("current_call_count", 0)
# get the chat function
if chat_function is None:
chat_function = kernel.func(skill_name=chat_skill_name, function_name=chat_function_name)
chat_function = kernel.func(plugin_name=chat_plugin_name, function_name=chat_function_name)
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
settings = chat_function._chat_prompt_template.prompt_config.execution_settings
if current_call_count >= max_function_calls:
settings.functions = []
context = await chat_function.invoke_async(
@@ -189,12 +189,15 @@ def _parse_message(
Tuple[Optional[str], Optional[Dict]] -- The parsed message.
"""
content = message.content if hasattr(message, "content") else None
function_call = message.function_call if hasattr(message, "function_call") else None
if function_call:
function_call = FunctionCall(
name=function_call.name,
arguments=function_call.arguments,
)
tool_calls = message.tool_calls if hasattr(message, "tool_calls") else None
function_calls = (
[FunctionCall(id=call.id, name=call.function.name, arguments=call.function.arguments) for call in tool_calls]
if tool_calls
else None
)
# todo: support multiple function calls
function_call = function_calls[0] if function_calls else None
if not with_data:
return (content, None, function_call)
@@ -1,5 +1,5 @@
from semantic_kernel.connectors.openapi.sk_openapi import register_openapi_skill
from semantic_kernel.connectors.openapi.sk_openapi import register_openapi_plugin
__all__ = [
"register_openapi_skill",
"register_openapi_plugin",
]
@@ -16,7 +16,7 @@ from semantic_kernel.connectors.ai.open_ai.const import (
)
from semantic_kernel.connectors.telemetry import HTTP_USER_AGENT
from semantic_kernel.orchestration.sk_function_base import SKFunctionBase
from semantic_kernel.skill_definition import sk_function, sk_function_context_parameter
from semantic_kernel.plugin_definition import sk_function, sk_function_context_parameter
logger: logging.Logger = logging.getLogger(__name__)
@@ -239,17 +239,17 @@ class OpenApiRunner:
"""
Registers a skill with the kernel that can run OpenAPI operations.
:param kernel: The kernel to register the skill with
:param skill_name: The name of the skill
Registers a plugin with the kernel that can run OpenAPI operations.
:param kernel: The kernel to register the plugin with
:param plugin_name: The name of the plugin
:param openapi_document: The OpenAPI document to register. Can be a filename or URL
:return: A dictionary of SKFunctions keyed by operationId
"""
def register_openapi_skill(
def register_openapi_plugin(
kernel: Kernel,
skill_name: str,
plugin_name: str,
openapi_document: str,
) -> Dict[str, SKFunctionBase]:
parser = OpenApiParser()
@@ -257,7 +257,7 @@ def register_openapi_skill(
operations = parser.create_rest_api_operations(parsed_doc)
openapi_runner = OpenApiRunner(parsed_openapi_document=parsed_doc)
skill = {}
plugin = {}
def create_run_operation_function(runner: OpenApiRunner, operation: RestApiOperation):
@sk_function(
@@ -286,6 +286,6 @@ def register_openapi_skill(
return run_openapi_operation
for operation_id, operation in operations.items():
logger.info(f"Registering OpenAPI operation: {skill_name}.{operation_id}")
skill[operation_id] = create_run_operation_function(openapi_runner, operation)
return kernel.import_skill(skill, skill_name)
logger.info(f"Registering OpenAPI operation: {plugin_name}.{operation_id}")
plugin[operation_id] = create_run_operation_function(openapi_runner, operation)
return kernel.import_plugin(plugin, plugin_name)
@@ -0,0 +1,23 @@
# Copyright (c) Microsoft. All rights reserved.
from semantic_kernel.core_plugins.conversation_summary_plugin import (
ConversationSummaryPlugin,
)
from semantic_kernel.core_plugins.file_io_plugin import FileIOPlugin
from semantic_kernel.core_plugins.http_plugin import HttpPlugin
from semantic_kernel.core_plugins.math_plugin import MathPlugin
from semantic_kernel.core_plugins.text_memory_plugin import TextMemoryPlugin
from semantic_kernel.core_plugins.text_plugin import TextPlugin
from semantic_kernel.core_plugins.time_plugin import TimePlugin
from semantic_kernel.core_plugins.web_search_engine_plugin import WebSearchEnginePlugin
__all__ = [
"TextMemoryPlugin",
"TextPlugin",
"FileIOPlugin",
"TimePlugin",
"HttpPlugin",
"ConversationSummaryPlugin",
"MathPlugin",
"WebSearchEnginePlugin",
]
@@ -6,12 +6,12 @@ if TYPE_CHECKING:
from semantic_kernel.orchestration.sk_context import SKContext
class ConversationSummarySkill:
class ConversationSummaryPlugin:
"""
Semantic skill that enables conversations summarization.
Semantic plugin that enables conversations summarization.
"""
from semantic_kernel.skill_definition import sk_function
from semantic_kernel.plugin_definition import sk_function
# The max tokens to process in a single semantic function call.
_max_tokens = 1024
@@ -28,10 +28,10 @@ class ConversationSummarySkill:
def __init__(self, kernel: "Kernel"):
self._summarizeConversationFunction = kernel.create_semantic_function(
ConversationSummarySkill._summarize_conversation_prompt_template,
skill_name=ConversationSummarySkill.__name__,
ConversationSummaryPlugin._summarize_conversation_prompt_template,
plugin_name=ConversationSummaryPlugin.__name__,
description=("Given a section of a conversation transcript, summarize the part of" " the conversation."),
max_tokens=ConversationSummarySkill._max_tokens,
max_tokens=ConversationSummaryPlugin._max_tokens,
temperature=0.1,
top_p=0.5,
)
@@ -54,7 +54,7 @@ class ConversationSummarySkill:
aggregate_chunked_results_async,
)
lines = text_chunker._split_text_lines(input, ConversationSummarySkill._max_tokens, True)
paragraphs = text_chunker._split_text_paragraph(lines, ConversationSummarySkill._max_tokens)
lines = text_chunker._split_text_lines(input, ConversationSummaryPlugin._max_tokens, True)
paragraphs = text_chunker._split_text_paragraph(lines, ConversationSummaryPlugin._max_tokens)
return await aggregate_chunked_results_async(self._summarizeConversationFunction, paragraphs, context)
@@ -5,19 +5,19 @@ import typing as t
import aiofiles
from semantic_kernel.plugin_definition import sk_function, sk_function_context_parameter
from semantic_kernel.sk_pydantic import SKBaseModel
from semantic_kernel.skill_definition import sk_function, sk_function_context_parameter
if t.TYPE_CHECKING:
from semantic_kernel.orchestration.sk_context import SKContext
class FileIOSkill(SKBaseModel):
class FileIOPlugin(SKBaseModel):
"""
Description: Read and write from a file.
Usage:
kernel.import_skill(FileIOSkill(), skill_name="file")
kernel.import_plugin(FileIOPlugin(), plugin_name="file")
Examples:
@@ -5,19 +5,19 @@ import typing as t
import aiohttp
from semantic_kernel.plugin_definition import sk_function, sk_function_context_parameter
from semantic_kernel.sk_pydantic import SKBaseModel
from semantic_kernel.skill_definition import sk_function, sk_function_context_parameter
if t.TYPE_CHECKING:
from semantic_kernel.orchestration.sk_context import SKContext
class HttpSkill(SKBaseModel):
class HttpPlugin(SKBaseModel):
"""
A skill that provides HTTP functionality.
A plugin that provides HTTP functionality.
Usage:
kernel.import_skill(HttpSkill(), "http")
kernel.import_plugin(HttpPlugin(), "http")
Examples:
@@ -1,19 +1,19 @@
# Copyright (c) Microsoft. All rights reserved.
import typing as t
from semantic_kernel.plugin_definition import sk_function, sk_function_context_parameter
from semantic_kernel.sk_pydantic import SKBaseModel
from semantic_kernel.skill_definition import sk_function, sk_function_context_parameter
if t.TYPE_CHECKING:
from semantic_kernel.orchestration.sk_context import SKContext
class MathSkill(SKBaseModel):
class MathPlugin(SKBaseModel):
"""
Description: MathSkill provides a set of functions to make Math calculations.
Description: MathPlugin provides a set of functions to make Math calculations.
Usage:
kernel.import_skill(MathSkill(), skill_name="math")
kernel.import_plugin(MathPlugin(), plugin_name="math")
Examples:
{{math.Add}} => Returns the sum of initial_value_text and Amount (provided in the SKContext)
@@ -38,7 +38,7 @@ class MathSkill(SKBaseModel):
:param context: Contains the context to get the numbers from
:return: The resulting sum as a string
"""
return MathSkill.add_or_subtract(initial_value_text, context, add=True)
return MathPlugin.add_or_subtract(initial_value_text, context, add=True)
@sk_function(
description="Subtracts value to a value",
@@ -59,7 +59,7 @@ class MathSkill(SKBaseModel):
:param context: Contains the context to get the numbers from
:return: The resulting subtraction as a string
"""
return MathSkill.add_or_subtract(initial_value_text, context, add=False)
return MathPlugin.add_or_subtract(initial_value_text, context, add=False)
@staticmethod
def add_or_subtract(initial_value_text: str, context: "SKContext", add: bool) -> str:
@@ -4,8 +4,8 @@ import logging
import typing as t
from typing import ClassVar
from semantic_kernel.plugin_definition import sk_function, sk_function_context_parameter
from semantic_kernel.sk_pydantic import SKBaseModel
from semantic_kernel.skill_definition import sk_function, sk_function_context_parameter
if t.TYPE_CHECKING:
from semantic_kernel.orchestration.sk_context import SKContext
@@ -13,7 +13,7 @@ if t.TYPE_CHECKING:
logger: logging.Logger = logging.getLogger(__name__)
class TextMemorySkill(SKBaseModel):
class TextMemoryPlugin(SKBaseModel):
COLLECTION_PARAM: ClassVar[str] = "collection"
RELEVANCE_PARAM: ClassVar[str] = "relevance"
KEY_PARAM: ClassVar[str] = "key"
@@ -66,17 +66,17 @@ class TextMemorySkill(SKBaseModel):
if context.memory is None:
raise ValueError("The context doesn't have a memory instance to search")
collection = context.variables.get(TextMemorySkill.COLLECTION_PARAM, TextMemorySkill.DEFAULT_COLLECTION)
collection = context.variables.get(TextMemoryPlugin.COLLECTION_PARAM, TextMemoryPlugin.DEFAULT_COLLECTION)
if not collection:
raise ValueError("Memory collection not defined for TextMemorySkill")
raise ValueError("Memory collection not defined for TextMemoryPlugin")
relevance = context.variables.get(TextMemorySkill.RELEVANCE_PARAM, TextMemorySkill.DEFAULT_RELEVANCE)
relevance = context.variables.get(TextMemoryPlugin.RELEVANCE_PARAM, TextMemoryPlugin.DEFAULT_RELEVANCE)
if not relevance:
raise ValueError("Relevance value not defined for TextMemorySkill")
raise ValueError("Relevance value not defined for TextMemoryPlugin")
limit = context.variables.get(TextMemorySkill.LIMIT_PARAM, TextMemorySkill.DEFAULT_LIMIT)
limit = context.variables.get(TextMemoryPlugin.LIMIT_PARAM, TextMemoryPlugin.DEFAULT_LIMIT)
if limit is None or str(limit).strip() == "":
raise ValueError("Limit value not defined for TextMemorySkill")
raise ValueError("Limit value not defined for TextMemoryPlugin")
results = await context.memory.search_async(
collection=collection,
@@ -110,7 +110,7 @@ class TextMemorySkill(SKBaseModel):
Example:
sk_context["input"] = "the capital of France is Paris"
sk_context[TextMemorySkill.KEY_PARAM] = "countryInfo1"
sk_context[TextMemoryPlugin.KEY_PARAM] = "countryInfo1"
{{memory.save $input}}
Args:
@@ -124,12 +124,12 @@ class TextMemorySkill(SKBaseModel):
if context.memory is None:
raise ValueError("The context doesn't have a memory instance to search")
collection = context.variables.get(TextMemorySkill.COLLECTION_PARAM, TextMemorySkill.DEFAULT_COLLECTION)
collection = context.variables.get(TextMemoryPlugin.COLLECTION_PARAM, TextMemoryPlugin.DEFAULT_COLLECTION)
if not collection:
raise ValueError("Memory collection not defined for TextMemorySkill")
raise ValueError("Memory collection not defined for TextMemoryPlugin")
key = context.variables.get(TextMemorySkill.KEY_PARAM, None)
key = context.variables.get(TextMemoryPlugin.KEY_PARAM, None)
if not key:
raise ValueError("Memory key not defined for TextMemorySkill")
raise ValueError("Memory key not defined for TextMemoryPlugin")
await context.memory.save_information_async(collection, text=text, id=key)
@@ -1,15 +1,15 @@
# Copyright (c) Microsoft. All rights reserved.
from semantic_kernel.plugin_definition import sk_function
from semantic_kernel.sk_pydantic import SKBaseModel
from semantic_kernel.skill_definition import sk_function
class TextSkill(SKBaseModel):
class TextPlugin(SKBaseModel):
"""
TextSkill provides a set of functions to manipulate strings.
TextPlugin provides a set of functions to manipulate strings.
Usage:
kernel.import_skill(TextSkill(), skill_name="text")
kernel.import_plugin(TextPlugin(), plugin_name="text")
Examples:
SKContext["input"] = " hello world "
@@ -2,17 +2,17 @@
import datetime
from semantic_kernel.plugin_definition import sk_function
from semantic_kernel.sk_pydantic import SKBaseModel
from semantic_kernel.skill_definition import sk_function
class TimeSkill(SKBaseModel):
class TimePlugin(SKBaseModel):
"""
Description: TimeSkill provides a set of functions
Description: TimePlugin provides a set of functions
to get the current time and date.
Usage:
kernel.import_skill(TimeSkill(), skill_name="time")
kernel.import_plugin(TimePlugin(), plugin_name="time")
Examples:
{{time.date}} => Sunday, 12 January, 2031
@@ -2,16 +2,16 @@
import asyncio
from semantic_kernel.plugin_definition import sk_function
from semantic_kernel.sk_pydantic import SKBaseModel
from semantic_kernel.skill_definition import sk_function
class WaitSkill(SKBaseModel):
class WaitPlugin(SKBaseModel):
"""
WaitSkill provides a set of functions to wait for a certain amount of time.
WaitPlugin provides a set of functions to wait for a certain amount of time.
Usage:
kernel.import_skill(WaitSkill(), skill_name="wait")
kernel.import_plugin(WaitPlugin(), plugin_name="wait")
Examples:
{{wait.seconds 5}} => Wait for 5 seconds
@@ -1,19 +1,19 @@
import typing as t
from semantic_kernel.connectors.search_engine.connector import ConnectorBase
from semantic_kernel.skill_definition import sk_function, sk_function_context_parameter
from semantic_kernel.plugin_definition import sk_function, sk_function_context_parameter
if t.TYPE_CHECKING:
from semantic_kernel.orchestration.sk_context import SKContext
class WebSearchEngineSkill:
class WebSearchEnginePlugin:
"""
Description: A skill that provides web search engine functionality
Description: A plugin that provides web search engine functionality
Usage:
connector = BingConnector(bing_search_api_key)
kernel.import_skill(WebSearchEngineSkill(connector), skill_name="WebSearch")
kernel.import_plugin(WebSearchEnginePlugin(connector), plugin_name="WebSearch")
Examples:
{{WebSearch.SearchAsync "What is semantic kernel?"}}
@@ -1,23 +0,0 @@
# Copyright (c) Microsoft. All rights reserved.
from semantic_kernel.core_skills.conversation_summary_skill import (
ConversationSummarySkill,
)
from semantic_kernel.core_skills.file_io_skill import FileIOSkill
from semantic_kernel.core_skills.http_skill import HttpSkill
from semantic_kernel.core_skills.math_skill import MathSkill
from semantic_kernel.core_skills.text_memory_skill import TextMemorySkill
from semantic_kernel.core_skills.text_skill import TextSkill
from semantic_kernel.core_skills.time_skill import TimeSkill
from semantic_kernel.core_skills.web_search_engine_skill import WebSearchEngineSkill
__all__ = [
"TextMemorySkill",
"TextSkill",
"FileIOSkill",
"TimeSkill",
"HttpSkill",
"ConversationSummarySkill",
"MathSkill",
"WebSearchEngineSkill",
]
@@ -1,7 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from semantic_kernel.orchestration.sk_context import SKContext
from semantic_kernel.skill_definition.function_view import FunctionView
from semantic_kernel.plugin_definition.function_view import FunctionView
class SKEventArgs:
+98 -89
View File
@@ -29,6 +29,14 @@ from semantic_kernel.orchestration.context_variables import ContextVariables
from semantic_kernel.orchestration.sk_context import SKContext
from semantic_kernel.orchestration.sk_function import SKFunction
from semantic_kernel.orchestration.sk_function_base import SKFunctionBase
from semantic_kernel.plugin_definition.function_view import FunctionView
from semantic_kernel.plugin_definition.plugin_collection import PluginCollection
from semantic_kernel.plugin_definition.plugin_collection_base import (
PluginCollectionBase,
)
from semantic_kernel.plugin_definition.read_only_plugin_collection_base import (
ReadOnlyPluginCollectionBase,
)
from semantic_kernel.reliability.pass_through_without_retry import (
PassThroughWithoutRetry,
)
@@ -40,17 +48,14 @@ from semantic_kernel.semantic_functions.prompt_template_config import (
from semantic_kernel.semantic_functions.semantic_function_config import (
SemanticFunctionConfig,
)
from semantic_kernel.skill_definition.function_view import FunctionView
from semantic_kernel.skill_definition.read_only_skill_collection_base import (
ReadOnlySkillCollectionBase,
)
from semantic_kernel.skill_definition.skill_collection import SkillCollection
from semantic_kernel.skill_definition.skill_collection_base import SkillCollectionBase
from semantic_kernel.template_engine.prompt_template_engine import PromptTemplateEngine
from semantic_kernel.template_engine.protocols.prompt_templating_engine import (
PromptTemplatingEngine,
)
from semantic_kernel.utils.validation import validate_function_name, validate_skill_name
from semantic_kernel.utils.validation import (
validate_function_name,
validate_plugin_name,
)
T = TypeVar("T")
@@ -58,20 +63,20 @@ logger: logging.Logger = logging.getLogger(__name__)
class Kernel:
_skill_collection: SkillCollectionBase
_plugin_collection: PluginCollectionBase
_prompt_template_engine: PromptTemplatingEngine
_memory: SemanticTextMemoryBase
def __init__(
self,
skill_collection: Optional[SkillCollectionBase] = None,
plugin_collection: Optional[PluginCollectionBase] = None,
prompt_template_engine: Optional[PromptTemplatingEngine] = None,
memory: Optional[SemanticTextMemoryBase] = None,
log: Optional[Any] = None,
) -> None:
if log:
logger.warning("The `log` parameter is deprecated. Please use the `logging` module instead.")
self._skill_collection = skill_collection if skill_collection else SkillCollection()
self._plugin_collection = plugin_collection if plugin_collection else PluginCollection()
self._prompt_template_engine = prompt_template_engine if prompt_template_engine else PromptTemplateEngine()
self._memory = memory if memory else NullMemory()
@@ -97,30 +102,30 @@ class Kernel:
return self._prompt_template_engine
@property
def skills(self) -> ReadOnlySkillCollectionBase:
return self._skill_collection.read_only_skill_collection
def plugins(self) -> ReadOnlyPluginCollectionBase:
return self._plugin_collection.read_only_plugin_collection
def register_semantic_function(
self,
skill_name: Optional[str],
plugin_name: Optional[str],
function_name: str,
function_config: SemanticFunctionConfig,
) -> SKFunctionBase:
if skill_name is None or skill_name == "":
skill_name = SkillCollection.GLOBAL_SKILL
assert skill_name is not None # for type checker
if plugin_name is None or plugin_name == "":
plugin_name = PluginCollection.GLOBAL_PLUGIN
assert plugin_name is not None # for type checker
validate_skill_name(skill_name)
validate_plugin_name(plugin_name)
validate_function_name(function_name)
function = self._create_semantic_function(skill_name, function_name, function_config)
self._skill_collection.add_semantic_function(function)
function = self._create_semantic_function(plugin_name, function_name, function_config)
self._plugin_collection.add_semantic_function(function)
return function
def register_native_function(
self,
skill_name: Optional[str],
plugin_name: Optional[str],
sk_function: Callable,
) -> SKFunctionBase:
if not hasattr(sk_function, "__sk_function__"):
@@ -130,23 +135,23 @@ class Kernel:
)
function_name = sk_function.__sk_function_name__
if skill_name is None or skill_name == "":
skill_name = SkillCollection.GLOBAL_SKILL
assert skill_name is not None # for type checker
if plugin_name is None or plugin_name == "":
plugin_name = PluginCollection.GLOBAL_PLUGIN
assert plugin_name is not None # for type checker
validate_skill_name(skill_name)
validate_plugin_name(plugin_name)
validate_function_name(function_name)
function = SKFunction.from_native_method(sk_function, skill_name)
function = SKFunction.from_native_method(sk_function, plugin_name)
if self.skills.has_function(skill_name, function_name):
if self.plugins.has_function(plugin_name, function_name):
raise KernelException(
KernelException.ErrorCodes.FunctionOverloadNotSupported,
"Overloaded functions are not supported, " "please differentiate function names.",
)
function.set_default_skill_collection(self.skills)
self._skill_collection.add_native_function(function)
function.set_default_plugin_collection(self.plugins)
self._plugin_collection.add_native_function(function)
return function
@@ -194,7 +199,7 @@ class Kernel:
context = SKContext(
variables,
self._memory,
self._skill_collection.read_only_skill_collection,
self._plugin_collection.read_only_plugin_collection,
)
else:
raise ValueError("No functions passed to run")
@@ -207,7 +212,7 @@ class Kernel:
# TODO: "critical exceptions"
logger.error(
"Something went wrong in stream function. During function invocation:"
f" '{stream_function.skill_name}.{stream_function.name}'. Error"
f" '{stream_function.plugin_name}.{stream_function.name}'. Error"
f" description: '{str(ex)}'"
)
raise KernelException(
@@ -249,7 +254,7 @@ class Kernel:
context = SKContext(
variables,
self._memory,
self._skill_collection.read_only_skill_collection,
self._plugin_collection.read_only_plugin_collection,
)
pipeline_step = 0
@@ -275,7 +280,7 @@ class Kernel:
and function_invoking_args.is_cancel_requested
):
cancel_message = "Execution was cancelled on function invoking event of pipeline step"
logger.info(f"{cancel_message} {pipeline_step}: {func.skill_name}.{func.name}.")
logger.info(f"{cancel_message} {pipeline_step}: {func.plugin_name}.{func.name}.")
return context
if (
@@ -283,7 +288,7 @@ class Kernel:
and function_invoking_args.is_skip_requested
):
skip_message = "Execution was skipped on function invoking event of pipeline step"
logger.info(f"{skip_message} {pipeline_step}: {func.skill_name}.{func.name}.")
logger.info(f"{skip_message} {pipeline_step}: {func.plugin_name}.{func.name}.")
break
context = await func.invoke_async(input=None, context=context, **kwargs)
@@ -291,7 +296,7 @@ class Kernel:
if context.error_occurred:
logger.error(
f"Something went wrong in pipeline step {pipeline_step}. "
f"During function invocation: '{func.skill_name}.{func.name}'. "
f"During function invocation: '{func.plugin_name}.{func.name}'. "
f"Error description: '{context.last_error_description}'"
)
return context
@@ -303,14 +308,14 @@ class Kernel:
and function_invoked_args.is_cancel_requested
):
cancel_message = "Execution was cancelled on function invoked event of pipeline step"
logger.info(f"{cancel_message} {pipeline_step}: {func.skill_name}.{func.name}.")
logger.info(f"{cancel_message} {pipeline_step}: {func.plugin_name}.{func.name}.")
return context
if (
isinstance(function_invoked_args, FunctionInvokedEventArgs)
and function_invoked_args.is_repeat_requested
):
repeat_message = "Execution was repeated on function invoked event of pipeline step"
logger.info(f"{repeat_message} {pipeline_step}: {func.skill_name}.{func.name}.")
logger.info(f"{repeat_message} {pipeline_step}: {func.plugin_name}.{func.name}.")
continue
else:
break
@@ -318,7 +323,7 @@ class Kernel:
except Exception as ex:
logger.error(
f"Something went wrong in pipeline step {pipeline_step}. "
f"During function invocation: '{func.skill_name}.{func.name}'. "
f"During function invocation: '{func.plugin_name}.{func.name}'. "
f"Error description: '{str(ex)}'"
)
context.fail(str(ex), ex)
@@ -328,11 +333,11 @@ class Kernel:
return context
def func(self, skill_name: str, function_name: str) -> SKFunctionBase:
if self.skills.has_native_function(skill_name, function_name):
return self.skills.get_native_function(skill_name, function_name)
def func(self, plugin_name: str, function_name: str) -> SKFunctionBase:
if self.plugins.has_native_function(plugin_name, function_name):
return self.plugins.get_native_function(plugin_name, function_name)
return self.skills.get_semantic_function(skill_name, function_name)
return self.plugins.get_semantic_function(plugin_name, function_name)
def use_memory(
self,
@@ -367,7 +372,7 @@ class Kernel:
return SKContext(
ContextVariables() if not variables else variables,
self._memory,
self.skills,
self.plugins,
)
def on_function_invoking(self, function_view: FunctionView, context: SKContext) -> FunctionInvokingEventArgs:
@@ -386,26 +391,26 @@ class Kernel:
return args
return None
def import_skill(self, skill_instance: Any, skill_name: str = "") -> Dict[str, SKFunctionBase]:
if skill_name.strip() == "":
skill_name = SkillCollection.GLOBAL_SKILL
logger.debug(f"Importing skill {skill_name} into the global namespace")
def import_plugin(self, plugin_instance: Any, plugin_name: str = "") -> Dict[str, SKFunctionBase]:
if plugin_name.strip() == "":
plugin_name = PluginCollection.GLOBAL_PLUGIN
logger.debug(f"Importing plugin {plugin_name} into the global namespace")
else:
logger.debug(f"Importing skill {skill_name}")
logger.debug(f"Importing plugin {plugin_name}")
functions = []
if isinstance(skill_instance, dict):
candidates = skill_instance.items()
if isinstance(plugin_instance, dict):
candidates = plugin_instance.items()
else:
candidates = inspect.getmembers(skill_instance, inspect.ismethod)
# Read every method from the skill instance
candidates = inspect.getmembers(plugin_instance, inspect.ismethod)
# Read every method from the plugin instance
for _, candidate in candidates:
# If the method is a semantic function, register it
if not hasattr(candidate, "__sk_function__"):
continue
functions.append(SKFunction.from_native_method(candidate, skill_name))
functions.append(SKFunction.from_native_method(candidate, plugin_name))
logger.debug(f"Methods imported: {len(functions)}")
@@ -417,13 +422,13 @@ class Kernel:
("Overloaded functions are not supported, " "please differentiate function names."),
)
skill = {}
plugin = {}
for function in functions:
function.set_default_skill_collection(self.skills)
self._skill_collection.add_native_function(function)
skill[function.name] = function
function.set_default_plugin_collection(self.plugins)
self._plugin_collection.add_native_function(function)
plugin[function.name] = function
return skill
return plugin
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."""
@@ -619,7 +624,7 @@ class Kernel:
def _create_semantic_function(
self,
skill_name: str,
plugin_name: str,
function_name: str,
function_config: SemanticFunctionConfig,
) -> SKFunctionBase:
@@ -630,13 +635,15 @@ class Kernel:
f"Function type not supported: {function_type}",
)
function = SKFunction.from_semantic_config(skill_name, function_name, function_config)
function.request_settings.update_from_ai_request_settings(function_config.prompt_template_config.completion)
function = SKFunction.from_semantic_config(plugin_name, function_name, function_config)
function.request_settings.update_from_ai_request_settings(
function_config.prompt_template_config.execution_settings
)
# Connect the function to the current kernel skill
# Connect the function to the current kernel plugin
# collection, in case the function is invoked manually
# without a context and without a way to find other functions.
function.set_default_skill_collection(self.skills)
function.set_default_plugin_collection(self.plugins)
if function_config.has_chat_prompt:
service = self.get_ai_service(
@@ -648,7 +655,7 @@ class Kernel:
req_settings_type = service.__closure__[0].cell_contents.get_request_settings_class()
function.set_chat_configuration(
req_settings_type.from_ai_request_settings(function_config.prompt_template_config.completion)
req_settings_type.from_ai_request_settings(function_config.prompt_template_config.execution_settings)
)
if service is None:
@@ -672,7 +679,7 @@ class Kernel:
req_settings_type = service.__closure__[0].cell_contents.get_request_settings_class()
function.set_ai_configuration(
req_settings_type.from_ai_request_settings(function_config.prompt_template_config.completion)
req_settings_type.from_ai_request_settings(function_config.prompt_template_config.execution_settings)
)
if service is None:
@@ -689,20 +696,20 @@ class Kernel:
return function
def import_native_skill_from_directory(
self, parent_directory: str, skill_directory_name: str
def import_native_plugin_from_directory(
self, parent_directory: str, plugin_directory_name: str
) -> Dict[str, SKFunctionBase]:
MODULE_NAME = "native_function"
validate_skill_name(skill_directory_name)
validate_plugin_name(plugin_directory_name)
skill_directory = os.path.abspath(os.path.join(parent_directory, skill_directory_name))
native_py_file_path = os.path.join(skill_directory, f"{MODULE_NAME}.py")
plugin_directory = os.path.abspath(os.path.join(parent_directory, plugin_directory_name))
native_py_file_path = os.path.join(plugin_directory, f"{MODULE_NAME}.py")
if not os.path.exists(native_py_file_path):
raise ValueError(f"Native Skill Python File does not exist: {native_py_file_path}")
raise ValueError(f"Native Plugin Python File does not exist: {native_py_file_path}")
skill_name = os.path.basename(skill_directory)
plugin_name = os.path.basename(plugin_directory)
spec = importlib.util.spec_from_file_location(MODULE_NAME, native_py_file_path)
module = importlib.util.module_from_spec(spec)
@@ -713,28 +720,28 @@ class Kernel:
None,
)
if class_name:
skill_obj = getattr(module, class_name)()
return self.import_skill(skill_obj, skill_name)
plugin_obj = getattr(module, class_name)()
return self.import_plugin(plugin_obj, plugin_name)
return {}
def import_semantic_skill_from_directory(
self, parent_directory: str, skill_directory_name: str
def import_semantic_plugin_from_directory(
self, parent_directory: str, plugin_directory_name: str
) -> Dict[str, SKFunctionBase]:
CONFIG_FILE = "config.json"
PROMPT_FILE = "skprompt.txt"
validate_skill_name(skill_directory_name)
validate_plugin_name(plugin_directory_name)
skill_directory = os.path.join(parent_directory, skill_directory_name)
skill_directory = os.path.abspath(skill_directory)
plugin_directory = os.path.join(parent_directory, plugin_directory_name)
plugin_directory = os.path.abspath(plugin_directory)
if not os.path.exists(skill_directory):
raise ValueError(f"Skill directory does not exist: {skill_directory_name}")
if not os.path.exists(plugin_directory):
raise ValueError(f"Plugin directory does not exist: {plugin_directory_name}")
skill = {}
plugin = {}
directories = glob.glob(skill_directory + "/*/")
directories = glob.glob(plugin_directory + "/*/")
for directory in directories:
dir_name = os.path.dirname(directory)
function_name = os.path.basename(dir_name)
@@ -755,15 +762,17 @@ class Kernel:
# Prepare lambda wrapping AI logic
function_config = SemanticFunctionConfig(config, template)
skill[function_name] = self.register_semantic_function(skill_directory_name, function_name, function_config)
plugin[function_name] = self.register_semantic_function(
plugin_directory_name, function_name, function_config
)
return skill
return plugin
def create_semantic_function(
self,
prompt_template: str,
function_name: Optional[str] = None,
skill_name: Optional[str] = None,
plugin_name: Optional[str] = None,
description: Optional[str] = None,
**kwargs: Any,
) -> "SKFunctionBase":
@@ -772,17 +781,17 @@ class Kernel:
config = PromptTemplateConfig(
description=(description if description is not None else "Generic function, unknown purpose"),
type="completion",
completion=AIRequestSettings(extension_data=kwargs),
execution_settings=AIRequestSettings(extension_data=kwargs),
)
validate_function_name(function_name)
if skill_name is not None:
validate_skill_name(skill_name)
if plugin_name is not None:
validate_plugin_name(plugin_name)
template = PromptTemplate(prompt_template, self.prompt_template_engine, config)
function_config = SemanticFunctionConfig(config, template)
return self.register_semantic_function(skill_name, function_name, function_config)
return self.register_semantic_function(plugin_name, function_name, function_config)
def add_function_invoking_handler(self, handler: Callable) -> None:
self._function_invoking_handlers[id(handler)] = handler
+2 -2
View File
@@ -22,8 +22,8 @@ class KernelException(Exception):
InvalidServiceConfiguration = 5
# Service not found.
ServiceNotFound = 6
# Skill collection not set.
SkillCollectionNotSet = 7
# Plugin collection not set.
PluginCollectionNotSet = 7
# Represents an error that occurs when invoking a function.
FunctionInvokeError = 8
# Ambiguous implementation.
@@ -19,7 +19,7 @@ def _infers(delegate_type):
def _is_annotation_of_type(annotation, type_to_match) -> bool:
return (annotation is type_to_match) or (
# Handle cases where the annotation is provided as a string to avoid circular imports
# for example: `async def read_async(self, context: "SKContext"):` in file_io_skill.py
# for example: `async def read_async(self, context: "SKContext"):` in file_io_plugin.py
isinstance(annotation, str)
and annotation == type_to_match.__name__
)
@@ -11,13 +11,13 @@ from semantic_kernel.memory.semantic_text_memory_base import (
SemanticTextMemoryT,
)
from semantic_kernel.orchestration.context_variables import ContextVariables
from semantic_kernel.plugin_definition.read_only_plugin_collection import (
ReadOnlyPluginCollection,
)
from semantic_kernel.plugin_definition.read_only_plugin_collection_base import (
ReadOnlyPluginCollectionBase,
)
from semantic_kernel.sk_pydantic import SKBaseModel
from semantic_kernel.skill_definition.read_only_skill_collection import (
ReadOnlySkillCollection,
)
from semantic_kernel.skill_definition.read_only_skill_collection_base import (
ReadOnlySkillCollectionBase,
)
logger: logging.Logger = logging.getLogger(__name__)
@@ -28,7 +28,7 @@ class SKContext(SKBaseModel, Generic[SemanticTextMemoryT]):
memory: SemanticTextMemoryT
variables: ContextVariables
# This field can be used to hold anything that is not a string
skill_collection: ReadOnlySkillCollection = Field(default_factory=ReadOnlySkillCollection)
plugin_collection: ReadOnlyPluginCollection = Field(default_factory=ReadOnlyPluginCollection)
_objects: Dict[str, Any] = PrivateAttr(default_factory=dict)
_error_occurred: bool = PrivateAttr(False)
_last_exception: Optional[Exception] = PrivateAttr(None)
@@ -38,7 +38,7 @@ class SKContext(SKBaseModel, Generic[SemanticTextMemoryT]):
self,
variables: ContextVariables,
memory: SemanticTextMemoryBase,
skill_collection: Union[ReadOnlySkillCollection, None],
plugin_collection: Union[ReadOnlyPluginCollection, None],
**kwargs,
# TODO: cancellation token?
) -> None:
@@ -48,15 +48,15 @@ class SKContext(SKBaseModel, Generic[SemanticTextMemoryT]):
Arguments:
variables {ContextVariables} -- The context variables.
memory {SemanticTextMemoryBase} -- The semantic text memory.
skill_collection {ReadOnlySkillCollectionBase} -- The skill collection.
plugin_collection {ReadOnlyPluginCollectionBase} -- The plugin collection.
"""
if kwargs.get("logger"):
logger.warning("The `logger` parameter is deprecated. Please use the `logging` module instead.")
if skill_collection is None:
skill_collection = ReadOnlySkillCollection()
if plugin_collection is None:
plugin_collection = ReadOnlyPluginCollection()
super().__init__(variables=variables, memory=memory, skill_collection=skill_collection)
super().__init__(variables=variables, memory=memory, plugin_collection=plugin_collection)
def fail(self, error_description: str, exception: Optional[Exception] = None):
"""
@@ -126,21 +126,21 @@ class SKContext(SKBaseModel, Generic[SemanticTextMemoryT]):
return self._objects
@property
def skills(self) -> ReadOnlySkillCollectionBase:
def plugins(self) -> ReadOnlyPluginCollectionBase:
"""
Read only skills collection.
Read only plugins collection.
Returns:
ReadOnlySkillCollectionBase -- The skills collection.
ReadOnlyPluginCollectionBase -- The plugins collection.
"""
return self.skill_collection
return self.plugin_collection
@skills.setter
def skills(self, value: ReadOnlySkillCollectionBase) -> None:
@plugins.setter
def plugins(self, value: ReadOnlyPluginCollectionBase) -> None:
"""
Set the value of skills collection
Set the value of plugins collection
"""
self.skill_collection = value
self.plugin_collection = value
def __setitem__(self, key: str, value: Any) -> None:
"""
@@ -164,27 +164,27 @@ class SKContext(SKBaseModel, Generic[SemanticTextMemoryT]):
"""
return self.variables[key]
def func(self, skill_name: str, function_name: str):
def func(self, plugin_name: str, function_name: str):
"""
Access registered functions by skill + name. Not case sensitive.
Access registered functions by plugin + name. Not case sensitive.
The function might be native or semantic, it's up to the caller
handling it.
Arguments:
skill_name {str} -- The skill name.
plugin_name {str} -- The plugin name.
function_name {str} -- The function name.
Returns:
SKFunctionBase -- The function.
"""
if self.skill_collection is None:
raise ValueError("The skill collection hasn't been set")
assert self.skill_collection is not None # for type checker
if self.plugin_collection is None:
raise ValueError("The plugin collection hasn't been set")
assert self.plugin_collection is not None # for type checker
if self.skill_collection.has_native_function(skill_name, function_name):
return self.skill_collection.get_native_function(skill_name, function_name)
if self.plugin_collection.has_native_function(plugin_name, function_name):
return self.plugin_collection.get_native_function(plugin_name, function_name)
return self.skill_collection.get_semantic_function(skill_name, function_name)
return self.plugin_collection.get_semantic_function(plugin_name, function_name)
def __str__(self) -> str:
if self._error_occurred:
@@ -192,43 +192,43 @@ class SKContext(SKBaseModel, Generic[SemanticTextMemoryT]):
return self.result
def throw_if_skill_collection_not_set(self) -> None:
def throw_if_plugin_collection_not_set(self) -> None:
"""
Throws an exception if the skill collection hasn't been set.
Throws an exception if the plugin collection hasn't been set.
"""
if self.skill_collection is None:
if self.plugin_collection is None:
raise KernelException(
KernelException.ErrorCodes.SkillCollectionNotSet,
"Skill collection not found in the context",
KernelException.ErrorCodes.PluginCollectionNotSet,
"Plugin collection not found in the context",
)
def is_function_registered(
self, skill_name: str, function_name: str
self, plugin_name: str, function_name: str
) -> Union[Tuple[Literal[True], Any], Tuple[Literal[False], None]]:
"""
Checks whether a function is registered in this context.
Arguments:
skill_name {str} -- The skill name.
plugin_name {str} -- The plugin name.
function_name {str} -- The function name.
Returns:
Tuple[bool, SKFunctionBase] -- A tuple with a boolean indicating
whether the function is registered and the function itself (or None).
"""
self.throw_if_skill_collection_not_set()
assert self.skill_collection is not None # for type checker
self.throw_if_plugin_collection_not_set()
assert self.plugin_collection is not None # for type checker
if self.skill_collection.has_native_function(skill_name, function_name):
the_func = self.skill_collection.get_native_function(skill_name, function_name)
if self.plugin_collection.has_native_function(plugin_name, function_name):
the_func = self.plugin_collection.get_native_function(plugin_name, function_name)
return True, the_func
if self.skill_collection.has_native_function(None, function_name):
the_func = self.skill_collection.get_native_function(None, function_name)
if self.plugin_collection.has_native_function(None, function_name):
the_func = self.plugin_collection.get_native_function(None, function_name)
return True, the_func
if self.skill_collection.has_semantic_function(skill_name, function_name):
the_func = self.skill_collection.get_semantic_function(skill_name, function_name)
if self.plugin_collection.has_semantic_function(plugin_name, function_name):
the_func = self.plugin_collection.get_semantic_function(plugin_name, function_name)
return True, the_func
return False, None
@@ -23,15 +23,15 @@ from semantic_kernel.orchestration.delegate_handlers import DelegateHandlers
from semantic_kernel.orchestration.delegate_inference import DelegateInference
from semantic_kernel.orchestration.delegate_types import DelegateTypes
from semantic_kernel.orchestration.sk_function_base import SKFunctionBase
from semantic_kernel.plugin_definition.function_view import FunctionView
from semantic_kernel.plugin_definition.parameter_view import ParameterView
from semantic_kernel.plugin_definition.read_only_plugin_collection_base import (
ReadOnlyPluginCollectionBase,
)
from semantic_kernel.semantic_functions.chat_prompt_template import ChatPromptTemplate
from semantic_kernel.semantic_functions.semantic_function_config import (
SemanticFunctionConfig,
)
from semantic_kernel.skill_definition.function_view import FunctionView
from semantic_kernel.skill_definition.parameter_view import ParameterView
from semantic_kernel.skill_definition.read_only_skill_collection_base import (
ReadOnlySkillCollectionBase,
)
if TYPE_CHECKING:
from semantic_kernel.orchestration.sk_context import SKContext
@@ -51,13 +51,13 @@ class SKFunction(SKFunctionBase):
_parameters: List[ParameterView]
_delegate_type: DelegateTypes
_function: Callable[..., Any]
_skill_collection: Optional[ReadOnlySkillCollectionBase]
_plugin_collection: Optional[ReadOnlyPluginCollectionBase]
_ai_service: Optional[Union[TextCompletionClientBase, ChatCompletionClientBase]]
_ai_request_settings: AIRequestSettings
_chat_prompt_template: ChatPromptTemplate
@staticmethod
def from_native_method(method, skill_name="", log=None) -> "SKFunction":
def from_native_method(method, plugin_name="", log=None) -> "SKFunction":
if log:
logger.warning("The `log` parameter is deprecated. Please use the `logging` module instead.")
if method is None:
@@ -104,14 +104,14 @@ class SKFunction(SKFunctionBase):
delegate_stream_function=method,
parameters=parameters,
description=method.__sk_function_description__,
skill_name=skill_name,
plugin_name=plugin_name,
function_name=method.__sk_function_name__,
is_semantic=False,
)
@staticmethod
def from_semantic_config(
skill_name: str,
plugin_name: str,
function_name: str,
function_config: SemanticFunctionConfig,
log: Optional[Any] = None,
@@ -213,7 +213,7 @@ class SKFunction(SKFunctionBase):
delegate_stream_function=_local_stream_func,
parameters=function_config.prompt_template.get_parameters(),
description=function_config.prompt_template_config.description,
skill_name=skill_name,
plugin_name=plugin_name,
function_name=function_name,
is_semantic=True,
chat_prompt_template=function_config.prompt_template if function_config.has_chat_prompt else None,
@@ -224,8 +224,8 @@ class SKFunction(SKFunctionBase):
return self._name
@property
def skill_name(self) -> str:
return self._skill_name
def plugin_name(self) -> str:
return self._plugin_name
@property
def description(self) -> str:
@@ -253,7 +253,7 @@ class SKFunction(SKFunctionBase):
delegate_function: Callable[..., Any],
parameters: List[ParameterView],
description: str,
skill_name: str,
plugin_name: str,
function_name: str,
is_semantic: bool,
log: Optional[Any] = None,
@@ -267,17 +267,17 @@ class SKFunction(SKFunctionBase):
self._function = delegate_function
self._parameters = parameters
self._description = description
self._skill_name = skill_name
self._plugin_name = plugin_name
self._name = function_name
self._is_semantic = is_semantic
self._stream_function = delegate_stream_function
self._skill_collection = None
self._plugin_collection = None
self._ai_service = None
self._ai_request_settings = AIRequestSettings()
self._chat_prompt_template = kwargs.get("chat_prompt_template", None)
def set_default_skill_collection(self, skills: ReadOnlySkillCollectionBase) -> "SKFunction":
self._skill_collection = skills
def set_default_plugin_collection(self, plugins: ReadOnlyPluginCollectionBase) -> "SKFunction":
self._plugin_collection = plugins
return self
def set_ai_service(self, ai_service: Callable[[], TextCompletionClientBase]) -> "SKFunction":
@@ -311,7 +311,7 @@ class SKFunction(SKFunctionBase):
def describe(self) -> FunctionView:
return FunctionView(
name=self.name,
skill_name=self.skill_name,
plugin_name=self.plugin_name,
description=self.description,
is_semantic=self.is_semantic,
parameters=self._parameters,
@@ -353,7 +353,7 @@ class SKFunction(SKFunctionBase):
if context is None:
context = SKContext(
variables=ContextVariables("") if variables is None else variables,
skill_collection=self._skill_collection,
plugin_collection=self._plugin_collection,
memory=memory if memory is not None else NullMemory.instance,
)
else:
@@ -400,7 +400,7 @@ class SKFunction(SKFunctionBase):
if context is None:
context = SKContext(
variables=ContextVariables("") if variables is None else variables,
skill_collection=self._skill_collection,
plugin_collection=self._plugin_collection,
memory=memory if memory is not None else NullMemory.instance,
)
else:
@@ -424,7 +424,7 @@ class SKFunction(SKFunctionBase):
async def _invoke_semantic_async(self, context: "SKContext", settings: AIRequestSettings, **kwargs):
self._verify_is_semantic()
self._ensure_context_has_skills(context)
self._ensure_context_has_plugins(context)
new_context = await self._function(self._ai_service, settings or self._ai_request_settings, context)
context.variables.merge_or_overwrite(new_context.variables)
return context
@@ -432,7 +432,7 @@ class SKFunction(SKFunctionBase):
async def _invoke_native_async(self, context):
self._verify_is_native()
self._ensure_context_has_skills(context)
self._ensure_context_has_plugins(context)
delegate = DelegateHandlers.get_handler(self._delegate_type)
# for python3.9 compatibility (staticmethod is not callable)
@@ -475,7 +475,7 @@ class SKFunction(SKFunctionBase):
if context is None:
context = SKContext(
variables=ContextVariables("") if variables is None else variables,
skill_collection=self._skill_collection,
plugin_collection=self._plugin_collection,
memory=memory if memory is not None else NullMemory.instance,
)
else:
@@ -504,14 +504,14 @@ class SKFunction(SKFunctionBase):
async def _invoke_semantic_stream_async(self, context, settings):
self._verify_is_semantic()
self._ensure_context_has_skills(context)
self._ensure_context_has_plugins(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):
self._verify_is_native()
self._ensure_context_has_skills(context)
self._ensure_context_has_plugins(context)
delegate = DelegateHandlers.get_handler(self._delegate_type)
# for python3.9 compatibility (staticmethod is not callable)
@@ -525,11 +525,11 @@ class SKFunction(SKFunctionBase):
context.variables.update(completion)
def _ensure_context_has_skills(self, context) -> None:
if context.skills is not None:
def _ensure_context_has_plugins(self, context) -> None:
if context.plugins is not None:
return
context.skills = self._skill_collection
context.plugins = self._plugin_collection
def _trace_function_type_Call(self, type: Enum) -> None:
logger.debug(f"Executing function type {type}: {type.name}")
@@ -9,13 +9,13 @@ from semantic_kernel.connectors.ai.text_completion_client_base import (
)
from semantic_kernel.memory.semantic_text_memory_base import SemanticTextMemoryBase
from semantic_kernel.orchestration.context_variables import ContextVariables
from semantic_kernel.plugin_definition.function_view import FunctionView
from semantic_kernel.sk_pydantic import SKBaseModel
from semantic_kernel.skill_definition.function_view import FunctionView
if TYPE_CHECKING:
from semantic_kernel.orchestration.sk_context import SKContext
from semantic_kernel.skill_definition.read_only_skill_collection_base import (
ReadOnlySkillCollectionBase,
from semantic_kernel.plugin_definition.read_only_plugin_collection_base import (
ReadOnlyPluginCollectionBase,
)
@@ -26,19 +26,19 @@ class SKFunctionBase(SKBaseModel):
"""
Name of the function.
The name is used by the skill collection and in
prompt templates; e.g., {{skillName.functionName}}
The name is used by the plugin collection and in
prompt templates; e.g., {{pluginName.functionName}}
"""
pass
@property
@abstractmethod
def skill_name(self) -> str:
def plugin_name(self) -> str:
"""
Name of the skill that contains this function.
Name of the plugin that contains this function.
The name is used by the skill collection and in
prompt templates; e.g., {{skillName.functionName}}"""
The name is used by the plugin collection and in
prompt templates; e.g., {{pluginName.functionName}}"""
pass
@property
@@ -140,17 +140,17 @@ class SKFunctionBase(SKBaseModel):
pass
@abstractmethod
def set_default_skill_collection(
def set_default_plugin_collection(
self,
skills: "ReadOnlySkillCollectionBase",
plugins: "ReadOnlyPluginCollectionBase",
) -> "SKFunctionBase":
"""
Sets the skill collection to use when the function is
Sets the plugin collection to use when the function is
invoked without a context or with a context that doesn't have
a skill collection
a plugin collection
Arguments:
skills {ReadOnlySkillCollectionBase} -- Kernel's skill collection
plugins {ReadOnlyPluginCollectionBase} -- Kernel's plugin collection
Returns:
SKFunctionBase -- The function instance
@@ -17,9 +17,9 @@ from semantic_kernel.planning.action_planner.action_planner_config import (
)
from semantic_kernel.planning.plan import Plan
from semantic_kernel.planning.planning_exception import PlanningException
from semantic_kernel.skill_definition import sk_function, sk_function_context_parameter
from semantic_kernel.skill_definition.function_view import FunctionView
from semantic_kernel.skill_definition.parameter_view import ParameterView
from semantic_kernel.plugin_definition import sk_function, sk_function_context_parameter
from semantic_kernel.plugin_definition.function_view import FunctionView
from semantic_kernel.plugin_definition.parameter_view import ParameterView
logger: logging.Logger = logging.getLogger(__name__)
@@ -33,7 +33,7 @@ class ActionPlanner:
"no function" if nothing relevant is available.
"""
RESTRICTED_SKILL_NAME = "ActionPlanner_Excluded"
RESTRICTED_PLUGIN_NAME = "ActionPlanner_Excluded"
config: ActionPlannerConfig
_stop_sequence: str = "#END-OF-PLAN"
@@ -65,12 +65,12 @@ class ActionPlanner:
self._prompt_template = prompt if prompt else open(__prompt_file, "r").read()
self._planner_function = kernel.create_semantic_function(
skill_name=self.RESTRICTED_SKILL_NAME,
plugin_name=self.RESTRICTED_PLUGIN_NAME,
prompt_template=self._prompt_template,
max_tokens=self.config.max_tokens,
stop_sequences=[self._stop_sequence],
)
kernel.import_skill(self, self.RESTRICTED_SKILL_NAME)
kernel.import_plugin(self, self.RESTRICTED_PLUGIN_NAME)
self._kernel = kernel
self._context = kernel.create_new_context()
@@ -139,15 +139,15 @@ class ActionPlanner:
logger.warn("No suitable function has been identified by ActionPlanner.")
plan = Plan(description=goal)
elif "." in generated_plan["plan"]["function"]:
skill, fun = generated_plan["plan"]["function"].split(".")
function_ref = self._context.skills.get_function(skill, fun)
plugin, fun = generated_plan["plan"]["function"].split(".")
function_ref = self._context.plugins.get_function(plugin, fun)
logger.info(
f"ActionPlanner has picked {skill}.{fun}. Reference to this function"
f"ActionPlanner has picked {plugin}.{fun}. Reference to this function"
f" found in context: {function_ref}"
)
plan = Plan(description=goal, function=function_ref)
else:
function_ref = self._context.skills.get_function(generated_plan["plan"]["function"])
function_ref = self._context.plugins.get_function(generated_plan["plan"]["function"])
logger.info(
f"ActionPlanner has picked {generated_plan['plan']['function']}. "
" Reference to this function found in context:"
@@ -171,23 +171,23 @@ class ActionPlanner:
[EXAMPLE]
- List of functions:
// Read a file.
FileIOSkill.ReadAsync
FileIOPlugin.ReadAsync
Parameter ""path"": Source file.
// Write a file.
FileIOSkill.WriteAsync
FileIOPlugin.WriteAsync
Parameter ""path"": Destination file. (default value: sample.txt)
Parameter ""content"": File content.
// Get the current time.
TimeSkill.Time
TimePlugin.Time
No parameters.
// Makes a POST request to a uri.
HttpSkill.PostAsync
HttpPlugin.PostAsync
Parameter ""body"": The body of the request.
- End list of functions.
Goal: create a file called ""something.txt"".
{""plan"":{
""rationale"": ""the list contains a function that allows to create files"",
""function"": ""FileIOSkill.WriteAsync"",
""function"": ""FileIOPlugin.WriteAsync"",
""parameters"": {
""path"": ""something.txt"",
""content"": null
@@ -207,17 +207,17 @@ class ActionPlanner:
[EXAMPLE]
- List of functions:
// Get the current time.
TimeSkill.Time
TimePlugin.Time
No parameters.
// Write a file.
FileIOSkill.WriteAsync
FileIOPlugin.WriteAsync
Parameter ""path"": Destination file. (default value: sample.txt)
Parameter ""content"": File content.
// Makes a POST request to a uri.
HttpSkill.PostAsync
HttpPlugin.PostAsync
Parameter ""body"": The body of the request.
// Read a file.
FileIOSkill.ReadAsync
FileIOPlugin.ReadAsync
Parameter ""path"": Source file.
- End list of functions.
Goal: tell me a joke.
@@ -233,14 +233,14 @@ class ActionPlanner:
@sk_function(description="List all functions available in the kernel", name="ListOfFunctions")
@sk_function_context_parameter(name="goal", description="The current goal processed by the planner")
def list_of_functions(self, goal: str, context: SKContext) -> str:
if context.skills is None:
if context.plugins is None:
raise PlanningException(
error_code=PlanningException.ErrorCodes.InvalidConfiguration,
message="Suitable plan not generated by ActionPlanner.",
inner_exception=ValueError("No plugins are available."),
)
functions_view = context.skills.get_functions_view()
functions_view = context.plugins.get_functions_view()
available_functions: List[FunctionView] = [
*functions_view.semantic_functions.values(),
@@ -252,8 +252,8 @@ class ActionPlanner:
self._create_function_string(func)
for func in available_functions
if (
func.skill_name != self.RESTRICTED_SKILL_NAME
and func.skill_name not in self.config.excluded_skills
func.plugin_name != self.RESTRICTED_PLUGIN_NAME
and func.plugin_name not in self.config.excluded_plugins
and func.name not in self.config.excluded_functions
)
]
@@ -269,7 +269,7 @@ class ActionPlanner:
Takes an instance of FunctionView and returns a string that consists of
function name, function description and parameters in the following format
// <function-description>
<skill-name>.<function-name>
<plugin-name>.<function-name>
Parameter ""<parameter-name>"": <parameter-description> (default value: `default_value`)
...
@@ -278,8 +278,8 @@ class ActionPlanner:
"""
if not function.description:
logger.warn(f"{function.skill_name}.{function.name} is missing a description")
description = f"// Function {function.skill_name}.{function.name}."
logger.warn(f"{function.plugin_name}.{function.name} is missing a description")
description = f"// Function {function.plugin_name}.{function.name}."
else:
description = f"// {function.description}"
@@ -287,7 +287,7 @@ class ActionPlanner:
if description[-1] != ".":
description = f"{description}."
name = f"{function.skill_name}.{function.name}"
name = f"{function.plugin_name}.{function.name}"
parameters_list = [
result for x in function.parameters if (result := self._create_parameter_string(x)) is not None
@@ -4,10 +4,10 @@ from typing import List
class ActionPlannerConfig:
def __init__(
self,
excluded_skills: List[str] = None,
excluded_plugins: List[str] = None,
excluded_functions: List[str] = None,
max_tokens: int = 1024,
):
self.excluded_skills: List[str] = excluded_skills or []
self.excluded_plugins: List[str] = excluded_plugins or []
self.excluded_functions: List[str] = excluded_functions or []
self.max_tokens: int = max_tokens
@@ -40,24 +40,24 @@ description: looks up the a contact and retrieves their email address
args:
- name: the name to look up
WriterSkill.EmailTo
WriterPlugin.EmailTo
description: email the input text to a recipient
args:
- input: the text to email
- recipient: the recipient's email address. Multiple addresses may be included if separated by ';'.
WriterSkill.Translate
WriterPlugin.Translate
description: translate the input to another language
args:
- input: the text to translate
- language: the language to translate to
WriterSkill.Summarize
WriterPlugin.Summarize
description: summarize input text
args:
- input: the text to summarize
FunSkill.Joke
FunPlugin.Joke
description: Generate a funny joke
args:
- input: the input to generate a joke about
@@ -69,29 +69,29 @@ args:
{
"input": "cars",
"subtasks": [
{"function": "FunSkill.Joke"},
{"function": "WriterSkill.Translate", "args": {"language": "Spanish"}}
{"function": "FunPlugin.Joke"},
{"function": "WriterPlugin.Translate", "args": {"language": "Spanish"}}
]
}
[AVAILABLE FUNCTIONS]
WriterSkill.Brainstorm
WriterPlugin.Brainstorm
description: Brainstorm ideas
args:
- input: the input to brainstorm about
EdgarAllenPoeSkill.Poe
EdgarAllenPoePlugin.Poe
description: Write in the style of author Edgar Allen Poe
args:
- input: the input to write about
WriterSkill.EmailTo
WriterPlugin.EmailTo
description: Write an email to a recipient
args:
- input: the input to write about
- recipient: the recipient's email address.
WriterSkill.Translate
WriterPlugin.Translate
description: translate the input to another language
args:
- input: the text to translate
@@ -106,10 +106,10 @@ E-mail these ideas to my significant other. Translate it to French."
{
"input": "Valentine's Day Date Ideas",
"subtasks": [
{"function": "WriterSkill.Brainstorm"},
{"function": "EdgarAllenPoeSkill.Poe"},
{"function": "WriterSkill.EmailTo", "args": {"recipient": "significant_other"}},
{"function": "WriterSkill.Translate", "args": {"language": "French"}}
{"function": "WriterPlugin.Brainstorm"},
{"function": "EdgarAllenPoePlugin.Poe"},
{"function": "WriterPlugin.EmailTo", "args": {"recipient": "significant_other"}},
{"function": "WriterPlugin.Translate", "args": {"language": "French"}}
]
}
@@ -133,21 +133,21 @@ class BasicPlanner:
Given an instance of the Kernel, create the [AVAILABLE FUNCTIONS]
string for the prompt.
"""
# Get a dictionary of skill names to all native and semantic functions
native_functions = kernel.skills.get_functions_view().native_functions
semantic_functions = kernel.skills.get_functions_view().semantic_functions
# Get a dictionary of plugin names to all native and semantic functions
native_functions = kernel.plugins.get_functions_view().native_functions
semantic_functions = kernel.plugins.get_functions_view().semantic_functions
native_functions.update(semantic_functions)
# Create a mapping between all function names and their descriptions
# and also a mapping between function names and their parameters
all_functions = native_functions
skill_names = list(all_functions.keys())
plugin_names = list(all_functions.keys())
all_functions_descriptions_dict = {}
all_functions_params_dict = {}
for skill_name in skill_names:
for func in all_functions[skill_name]:
key = skill_name + "." + func.name
for plugin_name in plugin_names:
for func in all_functions[plugin_name]:
key = plugin_name + "." + func.name
all_functions_descriptions_dict[key] = func.description
all_functions_params_dict[key] = func.parameters
@@ -211,8 +211,8 @@ class BasicPlanner:
subtasks = generated_plan["subtasks"]
for subtask in subtasks:
skill_name, function_name = subtask["function"].split(".")
sk_function = kernel.skills.get_function(skill_name, function_name)
plugin_name, function_name = subtask["function"].split(".")
sk_function = kernel.plugins.get_function(plugin_name, function_name)
# Get the arguments dictionary for the function
args = subtask.get("args", None)
+38 -38
View File
@@ -19,12 +19,12 @@ from semantic_kernel.memory.semantic_text_memory_base import SemanticTextMemoryB
from semantic_kernel.orchestration.context_variables import ContextVariables
from semantic_kernel.orchestration.sk_context import SKContext
from semantic_kernel.orchestration.sk_function_base import SKFunctionBase
from semantic_kernel.skill_definition.function_view import FunctionView
from semantic_kernel.skill_definition.read_only_skill_collection import (
ReadOnlySkillCollection,
from semantic_kernel.plugin_definition.function_view import FunctionView
from semantic_kernel.plugin_definition.read_only_plugin_collection import (
ReadOnlyPluginCollection,
)
from semantic_kernel.skill_definition.read_only_skill_collection_base import (
ReadOnlySkillCollectionBase,
from semantic_kernel.plugin_definition.read_only_plugin_collection_base import (
ReadOnlyPluginCollectionBase,
)
logger: logging.Logger = logging.getLogger(__name__)
@@ -39,7 +39,7 @@ class Plan(SKFunctionBase):
_has_next_step: bool = PrivateAttr()
_next_step_index: int = PrivateAttr()
_name: str = PrivateAttr()
_skill_name: str = PrivateAttr()
_plugin_name: str = PrivateAttr()
_description: str = PrivateAttr()
_is_semantic: bool = PrivateAttr()
_request_settings: AIRequestSettings = PrivateAttr()
@@ -54,8 +54,8 @@ class Plan(SKFunctionBase):
return self._state
@property
def skill_name(self) -> str:
return self._skill_name
def plugin_name(self) -> str:
return self._plugin_name
@property
def description(self) -> str:
@@ -95,7 +95,7 @@ class Plan(SKFunctionBase):
def __init__(
self,
name: Optional[str] = None,
skill_name: Optional[str] = None,
plugin_name: Optional[str] = None,
description: Optional[str] = None,
next_step_index: Optional[int] = None,
state: Optional[ContextVariables] = None,
@@ -106,7 +106,7 @@ class Plan(SKFunctionBase):
) -> None:
super().__init__()
self._name = "" if name is None else name
self._skill_name = "" if skill_name is None else skill_name
self._plugin_name = "" if plugin_name is None else plugin_name
self._description = "" if description is None else description
self._next_step_index = 0 if next_step_index is None else next_step_index
self._state = ContextVariables() if state is None else state
@@ -123,7 +123,7 @@ class Plan(SKFunctionBase):
@classmethod
def from_goal(cls, goal: str) -> "Plan":
return cls(description=goal, skill_name=cls.__name__)
return cls(description=goal, plugin_name=cls.__name__)
@classmethod
def from_function(cls, function: SKFunctionBase) -> "Plan":
@@ -148,7 +148,7 @@ class Plan(SKFunctionBase):
if context is None:
context = SKContext(
variables=self._state,
skill_collection=ReadOnlySkillCollection(),
plugin_collection=ReadOnlyPluginCollection(),
memory=memory or NullMemory(),
)
@@ -157,7 +157,7 @@ class Plan(SKFunctionBase):
if result.error_occurred:
logger.error(
"Something went wrong in plan step {0}.{1}:'{2}'".format(
self._skill_name, self._name, result.last_error_description
self._plugin_name, self._name, result.last_error_description
)
)
return result
@@ -188,7 +188,7 @@ class Plan(SKFunctionBase):
if context is None:
context = SKContext(
variables=self._state,
skill_collection=ReadOnlySkillCollection(),
plugin_collection=ReadOnlyPluginCollection(),
memory=memory or NullMemory(),
)
@@ -198,7 +198,7 @@ class Plan(SKFunctionBase):
logger.error(
result.last_exception,
"Something went wrong in plan step {0}.{1}:'{2}'".format(
self.skill_name, self.name, context.last_error_description
self.plugin_name, self.name, context.last_error_description
),
)
return result
@@ -233,26 +233,28 @@ class Plan(SKFunctionBase):
if self._function is not None:
self._function.set_ai_service(service)
def set_default_skill_collection(
def set_default_plugin_collection(
self,
skills: ReadOnlySkillCollectionBase,
plugins: ReadOnlyPluginCollectionBase,
) -> SKFunctionBase:
if self._function is not None:
self._function.set_default_skill_collection(skills)
self._function.set_default_plugin_collection(plugins)
def describe(self) -> FunctionView:
return self._function.describe()
def describe(self) -> Optional[FunctionView]:
if self._function is not None:
return self._function.describe()
return None
def set_available_functions(self, plan: "Plan", context: SKContext) -> "Plan":
if len(plan.steps) == 0:
if context.skills is None:
if context.plugins is None:
raise KernelException(
KernelException.ErrorCodes.SkillCollectionNotSet,
"Skill collection not found in the context",
KernelException.ErrorCodes.PluginCollectionNotSet,
"Plugin collection not found in the context",
)
try:
skillFunction = context.skills.get_function(plan.skill_name, plan.name)
plan.set_function(skillFunction)
pluginFunction = context.plugins.get_function(plan.plugin_name, plan.name)
plan.set_function(pluginFunction)
except Exception:
pass
else:
@@ -268,7 +270,7 @@ class Plan(SKFunctionBase):
else:
new_step = Plan(
name=step.name,
skill_name=step.skill_name,
plugin_name=step.plugin_name,
description=step.description,
next_step_index=0,
state=ContextVariables(),
@@ -282,7 +284,7 @@ class Plan(SKFunctionBase):
def set_function(self, function: SKFunctionBase) -> None:
self._function = function
self._name = function.name
self._skill_name = function.skill_name
self._plugin_name = function.plugin_name
self._description = function.description
self._is_semantic = function.is_semantic
self._request_settings = function.request_settings
@@ -306,7 +308,7 @@ class Plan(SKFunctionBase):
func_context = SKContext(
variables=variables,
memory=context.memory,
skill_collection=context.skills,
plugin_collection=context.plugins,
)
result = await step.invoke_async(context=func_context)
result_value = result.result
@@ -400,19 +402,17 @@ class Plan(SKFunctionBase):
elif param.name in self._state and (self._state[param.name] is not None and self._state[param.name] != ""):
step_variables.set(param.name, self._state[param.name])
for param_var in step.parameters.variables:
if param_var in step_variables:
for param_name, param_val in step.parameters.variables.items():
if param_name in step_variables:
continue
expanded_value = self.expand_from_variables(variables, param_var)
if expanded_value.lower() == param_var.lower():
step_variables.set(param_var, step.parameters.variables[param_var])
elif param_var in variables:
step_variables.set(param_var, variables[param_var])
elif param_var in self._state:
step_variables.set(param_var, self._state[param_var])
if param_name in variables:
step_variables.set(param_name, param_val)
elif param_name in self._state:
step_variables.set(param_name, self._state[param_name])
else:
step_variables.set(param_var, expanded_value)
expanded_value = self.expand_from_variables(variables, param_val)
step_variables.set(param_name, expanded_value)
for item in variables.variables:
if item not in step_variables:
@@ -10,18 +10,16 @@
"frequency_penalty": 0,
"stop_sequences": ["<!-- END -->"]
},
"input": {
"parameters": [
{
"name": "input",
"description": "The question to answer",
"defaultValue": ""
},
{
"name": "available_functions",
"description": "The list of the agent's available_functions",
"defaultValue": ""
}
]
}
"input_variables": [
{
"name": "input",
"description": "The question to answer",
"defaultValue": ""
},
{
"name": "available_functions",
"description": "The list of the agent's available_functions",
"defaultValue": ""
}
]
}
@@ -33,8 +33,8 @@ SEQUENTIAL_PLANNER_DEFAULT_DESCRIPTION = (
)
CUR_DIR = os.path.dirname(os.path.realpath(__file__))
PROMPT_CONFIG_FILE_PATH = os.path.join(CUR_DIR, "Skills/SequentialPlanning/config.json")
PROMPT_TEMPLATE_FILE_PATH = os.path.join(CUR_DIR, "Skills/SequentialPlanning/skprompt.txt")
PROMPT_CONFIG_FILE_PATH = os.path.join(CUR_DIR, "Plugins/SequentialPlanning/config.json")
PROMPT_TEMPLATE_FILE_PATH = os.path.join(CUR_DIR, "Plugins/SequentialPlanning/skprompt.txt")
def read_file(file_path: str) -> str:
@@ -43,7 +43,7 @@ def read_file(file_path: str) -> str:
class SequentialPlanner:
RESTRICTED_SKILL_NAME = "SequentialPlanner_Excluded"
RESTRICTED_PLUGIN_NAME = "SequentialPlanner_Excluded"
config: SequentialPlannerConfig
_context: "SKContext"
@@ -53,7 +53,7 @@ class SequentialPlanner:
assert isinstance(kernel, Kernel)
self.config = config or SequentialPlannerConfig()
self.config.excluded_skills.append(self.RESTRICTED_SKILL_NAME)
self.config.excluded_plugins.append(self.RESTRICTED_PLUGIN_NAME)
self._function_flow_function = self._init_flow_function(prompt, kernel)
@@ -62,7 +62,7 @@ class SequentialPlanner:
def _init_flow_function(self, prompt: str, kernel: Kernel):
prompt_config = PromptTemplateConfig.from_json(read_file(PROMPT_CONFIG_FILE_PATH))
prompt_template = prompt or read_file(PROMPT_TEMPLATE_FILE_PATH)
prompt_config.completion.extension_data["max_tokens"] = self.config.max_tokens
prompt_config.execution_settings.extension_data["max_tokens"] = self.config.max_tokens
prompt_template = PromptTemplate(
template=prompt_template,
@@ -72,8 +72,8 @@ class SequentialPlanner:
function_config = SemanticFunctionConfig(prompt_config, prompt_template)
return kernel.register_semantic_function(
skill_name=self.RESTRICTED_SKILL_NAME,
function_name=self.RESTRICTED_SKILL_NAME,
plugin_name=self.RESTRICTED_PLUGIN_NAME,
function_name=self.RESTRICTED_PLUGIN_NAME,
function_config=function_config,
)
@@ -98,13 +98,13 @@ class SequentialPlanner:
plan_result_string = plan_result.result.strip()
try:
get_skill_function = self.config.get_skill_function or SequentialPlanParser.get_skill_function(
get_plugin_function = self.config.get_plugin_function or SequentialPlanParser.get_plugin_function(
self._context
)
plan = SequentialPlanParser.to_plan_from_xml(
plan_result_string,
goal,
get_skill_function,
get_plugin_function,
self.config.allow_missing_functions,
)
@@ -8,20 +8,20 @@ class SequentialPlannerConfig:
self,
relevancy_threshold: Optional[float] = None,
max_relevant_functions: int = 100,
excluded_skills: List[str] = None,
excluded_plugins: List[str] = None,
excluded_functions: List[str] = None,
included_functions: List[str] = None,
max_tokens: int = 1024,
allow_missing_functions: bool = False,
get_available_functions_async: Callable = None,
get_skill_function: Callable = None,
get_plugin_function: Callable = None,
):
self.relevancy_threshold: float = relevancy_threshold
self.max_relevant_functions: int = max_relevant_functions
self.excluded_skills: List[str] = excluded_skills or []
self.excluded_plugins: List[str] = excluded_plugins or []
self.excluded_functions: List[str] = excluded_functions or []
self.included_functions: List[str] = included_functions or []
self.max_tokens: int = max_tokens
self.allow_missing_functions: bool = allow_missing_functions
self.get_available_functions_async = get_available_functions_async
self.get_skill_function = get_skill_function
self.get_plugin_function = get_plugin_function
@@ -11,7 +11,7 @@ from semantic_kernel.orchestration.sk_context import SKContext
from semantic_kernel.planning.sequential_planner.sequential_planner_config import (
SequentialPlannerConfig,
)
from semantic_kernel.skill_definition.function_view import FunctionView
from semantic_kernel.plugin_definition.function_view import FunctionView
logger: logging.Logger = logging.getLogger(__name__)
@@ -32,7 +32,7 @@ class SequentialPlannerFunctionViewExtension:
@staticmethod
def to_fully_qualified_name(function: FunctionView):
return f"{function.skill_name}.{function.name}"
return f"{function.plugin_name}.{function.name}"
@staticmethod
def to_embedding_string(function: FunctionView):
@@ -67,17 +67,17 @@ class SequentialPlannerSKContextExtension:
config: SequentialPlannerConfig,
semantic_query: str = None,
):
excluded_skills = config.excluded_skills or []
excluded_plugins = config.excluded_plugins or []
excluded_functions = config.excluded_functions or []
included_functions = config.included_functions or []
if context.skills is None:
if context.plugins is None:
raise KernelException(
KernelException.ErrorCodes.SkillCollectionNotSet,
"Skill collection not found in the context",
KernelException.ErrorCodes.PluginCollectionNotSet,
"Plugin collection not found in the context",
)
functions_view = context.skills.get_functions_view()
functions_view = context.plugins.get_functions_view()
available_functions: List[FunctionView] = [
*functions_view.semantic_functions.values(),
@@ -88,7 +88,7 @@ class SequentialPlannerSKContextExtension:
available_functions = [
func
for func in available_functions
if (func.skill_name not in excluded_skills and func.name not in excluded_functions)
if (func.plugin_name not in excluded_plugins and func.name not in excluded_functions)
]
if semantic_query is None or isinstance(context.memory, NullMemory) or config.relevancy_threshold is None:
@@ -119,7 +119,7 @@ class SequentialPlannerSKContextExtension:
relevant_functions += [func for func in available_functions if func.name in missing_functions]
return sorted(relevant_functions, key=lambda x: (x.skill_name, x.name))
return sorted(relevant_functions, key=lambda x: (x.plugin_name, x.name))
@staticmethod
async def get_relevant_functions_async(

Some files were not shown because too many files have changed in this diff Show More