Skip to content

Commit

Permalink
Merge branch 'main' into joslat-process-framework-markdown-renderer
Browse files Browse the repository at this point in the history
  • Loading branch information
joslat authored Nov 25, 2024
2 parents 9cb8249 + 5ae74d7 commit c7cbdb9
Show file tree
Hide file tree
Showing 76 changed files with 2,389 additions and 1,074 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/dotnet-build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ jobs:

# Generate test reports and check coverage
- name: Generate test reports
uses: danielpalme/ReportGenerator-GitHub-Action@5.3.10
uses: danielpalme/ReportGenerator-GitHub-Action@5.4.1
with:
reports: "./TestResults/Coverage/**/coverage.cobertura.xml"
targetdir: "./TestResults/Reports"
Expand Down
Binary file added docs/code_maps/Python.pdf
Binary file not shown.
Binary file added docs/code_maps/dotNET.pdf
Binary file not shown.
57 changes: 38 additions & 19 deletions dotnet/samples/Concepts/Filtering/TelemetryWithFilters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,17 @@ public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, F

logger.LogInformation("Function {FunctionName} succeeded.", context.Function.Name);

await this.LogFunctionResultAsync(context);
if (context.IsStreaming)
{
// Overriding the result in a streaming scenario enables the filter to stream chunks
// back to the operation's origin without interrupting the data flow.
var enumerable = context.Result.GetValue<IAsyncEnumerable<StreamingChatMessageContent>>();
context.Result = new FunctionResult(context.Result, ProcessFunctionResultStreamingAsync(enumerable!));
}
else
{
ProcessFunctionResult(context.Result);
}
}
catch (Exception exception)
{
Expand All @@ -167,34 +177,43 @@ public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, F
}
}

private async Task LogFunctionResultAsync(FunctionInvocationContext context)
private void ProcessFunctionResult(FunctionResult functionResult)
{
string? result = functionResult.GetValue<string>();
object? usage = functionResult.Metadata?["Usage"];

if (!string.IsNullOrWhiteSpace(result))
{
logger.LogTrace("Function result: {Result}", result);
}

if (logger.IsEnabled(LogLevel.Information) && usage is not null)
{
logger.LogInformation("Usage: {Usage}", JsonSerializer.Serialize(usage));
}
}

private async IAsyncEnumerable<StreamingChatMessageContent> ProcessFunctionResultStreamingAsync(IAsyncEnumerable<StreamingChatMessageContent> data)
{
string? result = null;
object? usage = null;

if (context.IsStreaming)
var stringBuilder = new StringBuilder();

await foreach (var item in data)
{
var stringBuilder = new StringBuilder();
yield return item;

await foreach (var item in context.Result.GetValue<IAsyncEnumerable<StreamingChatMessageContent>>()!)
if (item.Content is not null)
{
if (item.Content is not null)
{
stringBuilder.Append(item.Content);
}

usage = item.Metadata?["Usage"];
stringBuilder.Append(item.Content);
}

result = stringBuilder.ToString();
}
else
{
result = context.Result.GetValue<string>();
usage = context.Result.Metadata?["Usage"];
usage = item.Metadata?["Usage"];
}

if (result is not null)
var result = stringBuilder.ToString();

if (!string.IsNullOrWhiteSpace(result))
{
logger.LogTrace("Function result: {Result}", result);
}
Expand Down
14 changes: 8 additions & 6 deletions dotnet/samples/Demos/OllamaFunctionCalling/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
var settings = new OllamaPromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };

Console.WriteLine("Ask questions or give instructions to the copilot such as:\n" +
"- Change the alarm to 8\n" +
"- What is the current alarm set?\n" +
"- Is the light on?\n" +
"- Turn the light off please.\n" +
"- Set an alarm for 6:00 am.\n");
Console.WriteLine("""
Ask questions or give instructions to the copilot such as:
- Change the alarm to 8
- What is the current alarm set?
- Is the light on?
- Turn the light off please.
- Set an alarm for 6:00 am.
""");

Console.Write("> ");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ void AttachErrorStep(ProcessStepBuilder step, params string[] functionNames)
{
step
.OnFunctionError(functionName)
.SendEventTo(new ProcessFunctionTargetBuilder(renderMessageStep, RenderMessageStep.Functions.RenderError, "exception"))
.SendEventTo(new ProcessFunctionTargetBuilder(renderMessageStep, RenderMessageStep.Functions.RenderError, "error"))
.StopProcess();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,41 @@ public async Task FiltersAreExecutedCorrectlyAsync()
Assert.Equal("Test chat response", result.ToString());
}

[Fact]
public async Task FunctionSequenceIndexIsCorrectForConcurrentCallsAsync()
{
// Arrange
List<int> functionSequenceNumbers = [];
List<int> expectedFunctionSequenceNumbers = [0, 1, 0, 1];

var function1 = KernelFunctionFactory.CreateFromMethod((string parameter) => { return parameter; }, "Function1");
var function2 = KernelFunctionFactory.CreateFromMethod((string parameter) => { return parameter; }, "Function2");

var plugin = KernelPluginFactory.CreateFromFunctions("MyPlugin", [function1, function2]);

var kernel = this.GetKernelWithFilter(plugin, async (context, next) =>
{
functionSequenceNumbers.Add(context.FunctionSequenceIndex);

await next(context);
});

this._messageHandlerStub.ResponsesToReturn = GetFunctionCallingResponses();

// Act
var result = await kernel.InvokePromptAsync("Test prompt", new(new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(options: new()
{
AllowParallelCalls = true,
AllowConcurrentInvocation = true
})
}));

// Assert
Assert.Equal(expectedFunctionSequenceNumbers, functionSequenceNumbers);
}

[Fact]
public async Task FiltersAreExecutedCorrectlyOnStreamingAsync()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Diagnostics.CodeAnalysis;
using System.Threading;
using Microsoft.SemanticKernel.ChatCompletion;

Expand All @@ -9,7 +8,6 @@ namespace Microsoft.SemanticKernel;
/// <summary>
/// Class with data related to automatic function invocation.
/// </summary>
[Experimental("SKEXP0001")]
public class AutoFunctionInvocationContext
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;

namespace Microsoft.SemanticKernel;
Expand All @@ -11,7 +10,6 @@ namespace Microsoft.SemanticKernel;
/// <summary>
/// Interface for filtering actions during automatic function invocation.
/// </summary>
[Experimental("SKEXP0001")]
public interface IAutoFunctionInvocationFilter
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Diagnostics.CodeAnalysis;
using System.Threading;

namespace Microsoft.SemanticKernel;
Expand Down Expand Up @@ -38,7 +37,6 @@ internal FunctionInvocationContext(Kernel kernel, KernelFunction function, Kerne
/// <summary>
/// Boolean flag which indicates whether a filter is invoked within streaming or non-streaming mode.
/// </summary>
[Experimental("SKEXP0001")]
public bool IsStreaming { get; init; }

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Diagnostics.CodeAnalysis;
using System.Threading;

namespace Microsoft.SemanticKernel;

/// <summary>
Expand Down Expand Up @@ -37,7 +37,6 @@ internal PromptRenderContext(Kernel kernel, KernelFunction function, KernelArgum
/// <summary>
/// Boolean flag which indicates whether a filter is invoked within streaming or non-streaming mode.
/// </summary>
[Experimental("SKEXP0001")]
public bool IsStreaming { get; init; }

/// <summary>
Expand Down
1 change: 0 additions & 1 deletion dotnet/src/SemanticKernel.Abstractions/Kernel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ public Kernel Clone() =>
/// <summary>
/// Gets the collection of auto function invocation filters available through the kernel.
/// </summary>
[Experimental("SKEXP0001")]
public IList<IAutoFunctionInvocationFilter> AutoFunctionInvocationFilters =>
this._autoFunctionInvocationFilters ??
Interlocked.CompareExchange(ref this._autoFunctionInvocationFilters, [], null) ??
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,22 @@ namespace Microsoft.SemanticKernel;
[DebuggerDisplay("{DebuggerDisplay,nq}")]
internal sealed partial class KernelFunctionFromMethod : KernelFunction
{
private static readonly Dictionary<Type, Func<string, object>> s_jsonStringParsers = new(12)
{
{ typeof(bool), s => bool.Parse(s) },
{ typeof(int), s => int.Parse(s) },
{ typeof(uint), s => uint.Parse(s) },
{ typeof(long), s => long.Parse(s) },
{ typeof(ulong), s => ulong.Parse(s) },
{ typeof(float), s => float.Parse(s) },
{ typeof(double), s => double.Parse(s) },
{ typeof(decimal), s => decimal.Parse(s) },
{ typeof(short), s => short.Parse(s) },
{ typeof(ushort), s => ushort.Parse(s) },
{ typeof(byte), s => byte.Parse(s) },
{ typeof(sbyte), s => sbyte.Parse(s) }
};

/// <summary>
/// Creates a <see cref="KernelFunction"/> instance for a method, specified via an <see cref="MethodInfo"/> instance
/// and an optional target object if the method is an instance method.
Expand Down Expand Up @@ -710,26 +726,34 @@ private static (Func<KernelFunction, Kernel, KernelArguments, CancellationToken,

object? Process(object? value)
{
if (!type.IsAssignableFrom(value?.GetType()))
if (type.IsAssignableFrom(value?.GetType()))
{
return value;
}

if (converter is not null && value is not JsonElement or JsonDocument or JsonNode)
{
if (converter is not null)
try
{
try
{
return converter(value, kernel.Culture);
}
catch (Exception e) when (!e.IsCriticalException())
{
throw new ArgumentOutOfRangeException(name, value, e.Message);
}
return converter(value, kernel.Culture);
}

if (value is not null && TryToDeserializeValue(value, type, jsonSerializerOptions, out var deserializedValue))
catch (Exception e) when (!e.IsCriticalException())
{
return deserializedValue;
throw new ArgumentOutOfRangeException(name, value, e.Message);
}
}

if (value is JsonElement element && element.ValueKind == JsonValueKind.String
&& s_jsonStringParsers.TryGetValue(type, out var jsonStringParser))
{
return jsonStringParser(element.GetString()!);
}

if (value is not null && TryToDeserializeValue(value, type, jsonSerializerOptions, out var deserializedValue))
{
return deserializedValue;
}

return value;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,66 @@ public async Task ItSupportsArgumentsImplicitConversionAsync()
await function.InvokeAsync(this._kernel, arguments);
}

[Fact]
public async Task ItSupportsJsonElementArgumentsImplicitConversionAsync()
{
//Arrange
var arguments = new KernelArguments()
{
["l"] = JsonSerializer.Deserialize<JsonElement>((long)1), //Passed to parameter of type long
["i"] = JsonSerializer.Deserialize<JsonElement>((byte)1), //Passed to parameter of type int
["d"] = JsonSerializer.Deserialize<JsonElement>((float)1.0), //Passed to parameter of type double
["f"] = JsonSerializer.Deserialize<JsonElement>((uint)1.0), //Passed to parameter of type float
["g"] = JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize(new Guid("35626209-b0ab-458c-bfc4-43e6c7bd13dc"))), //Passed to parameter of type string
["dof"] = JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize(DayOfWeek.Thursday)), //Passed to parameter of type int
["b"] = JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize("true")), //Passed to parameter of type bool
};

var function = KernelFunctionFactory.CreateFromMethod((long l, int i, double d, float f, string g, int dof, bool b) =>
{
Assert.Equal(1, l);
Assert.Equal(1, i);
Assert.Equal(1.0, d);
Assert.Equal("35626209-b0ab-458c-bfc4-43e6c7bd13dc", g);
Assert.Equal(4, dof);
Assert.True(b);
},
functionName: "Test");

await function.InvokeAsync(this._kernel, arguments);
await function.AsAIFunction().InvokeAsync(arguments);
}

[Fact]
public async Task ItSupportsStringJsonElementArgumentsImplicitConversionAsync()
{
//Arrange
var arguments = new KernelArguments()
{
["l"] = JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize("1")), //Passed to parameter of type long
["i"] = JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize("1")), //Passed to parameter of type int
["d"] = JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize("1.0")), //Passed to parameter of type double
["f"] = JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize("1.0")), //Passed to parameter of type float
["g"] = JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize("35626209-b0ab-458c-bfc4-43e6c7bd13dc")), //Passed to parameter of type Guid
["dof"] = JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize("4")), //Passed to parameter of type int
["b"] = JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize("false")), //Passed to parameter of type bool
};

var function = KernelFunctionFactory.CreateFromMethod((long l, int i, double d, float f, Guid g, int dof, bool b) =>
{
Assert.Equal(1, l);
Assert.Equal(1, i);
Assert.Equal(1.0, d);
Assert.Equal(new Guid("35626209-b0ab-458c-bfc4-43e6c7bd13dc"), g);
Assert.Equal(4, dof);
Assert.False(b);
},
functionName: "Test");

await function.InvokeAsync(this._kernel, arguments);
await function.AsAIFunction().InvokeAsync(arguments);
}

[Fact]
public async Task ItSupportsParametersWithDefaultValuesAsync()
{
Expand Down
2 changes: 1 addition & 1 deletion python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ classifiers = [
dependencies = [
"aiohttp ~= 3.8",
"cloudevents ~=1.0",
"pydantic ~= 2.0",
"pydantic >=2.0,<2.10",
"pydantic-settings ~= 2.0",
"defusedxml ~= 0.7",
# azure identity
Expand Down
3 changes: 1 addition & 2 deletions python/samples/concepts/chat_completion/chat_bedrock_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
import asyncio

from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.bedrock.bedrock_prompt_execution_settings import BedrockChatPromptExecutionSettings
from semantic_kernel.connectors.ai.bedrock.services.bedrock_chat_completion import BedrockChatCompletion
from semantic_kernel.connectors.ai.bedrock import BedrockChatCompletion, BedrockChatPromptExecutionSettings
from semantic_kernel.contents import ChatHistory

system_message = """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from openai import AsyncOpenAI

from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion import OpenAIChatCompletion
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
from semantic_kernel.contents.chat_history import ChatHistory
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.kernel import Kernel
Expand Down
Loading

0 comments on commit c7cbdb9

Please sign in to comment.