Skip to content

Commit

Permalink
Merge branch 'main' into vector-store-gettingstarted
Browse files Browse the repository at this point in the history
  • Loading branch information
westey-m authored Nov 1, 2024
2 parents 6a46170 + 936366e commit 8a871cc
Show file tree
Hide file tree
Showing 64 changed files with 1,450 additions and 260 deletions.
1 change: 1 addition & 0 deletions .github/_typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ extend-exclude = [
"PopulationByCountry.csv",
"PopulationByAdmin1.csv",
"WomensSuffrage.txt",
"SK-dotnet.sln.DotSettings"
]

[default.extend-words]
Expand Down
1 change: 1 addition & 0 deletions dotnet/SK-dotnet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FunctionCalling", "Function
ProjectSection(SolutionItems) = preProject
src\InternalUtilities\connectors\AI\FunctionCalling\FunctionCallingUtilities.props = src\InternalUtilities\connectors\AI\FunctionCalling\FunctionCallingUtilities.props
src\InternalUtilities\connectors\AI\FunctionCalling\FunctionCallsProcessor.cs = src\InternalUtilities\connectors\AI\FunctionCalling\FunctionCallsProcessor.cs
src\InternalUtilities\connectors\AI\FunctionCalling\FunctionCallsProcessorLoggerExtensions.cs = src\InternalUtilities\connectors\AI\FunctionCalling\FunctionCallsProcessorLoggerExtensions.cs
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connectors.Weaviate.UnitTests", "src\Connectors\Connectors.Weaviate.UnitTests\Connectors.Weaviate.UnitTests.csproj", "{E8FC97B0-B417-4A90-993C-B8AA9223B058}"
Expand Down
2 changes: 1 addition & 1 deletion dotnet/nuget/nuget-package.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<!-- Central version prefix - applies to all nuget packages. -->
<VersionPrefix>1.25.0</VersionPrefix>
<VersionPrefix>1.26.0</VersionPrefix>
<PackageVersion Condition="'$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix)</PackageVersion>
<PackageVersion Condition="'$(VersionSuffix)' == ''">$(VersionPrefix)</PackageVersion>

Expand Down
90 changes: 80 additions & 10 deletions dotnet/samples/Concepts/Filtering/FunctionInvocationFiltering.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,26 +63,63 @@ public async Task FunctionFilterResultOverrideOnStreamingAsync()
{
var builder = Kernel.CreateBuilder();

// This filter overrides streaming results with "item * 2" logic.
// This filter overrides streaming results with new ending in each chunk.
builder.Services.AddSingleton<IFunctionInvocationFilter, StreamingFunctionFilterExample>();

var kernel = builder.Build();

static async IAsyncEnumerable<int> GetData()
static async IAsyncEnumerable<string> GetData()
{
yield return 1;
yield return 2;
yield return 3;
yield return "chunk1";
yield return "chunk2";
yield return "chunk3";
}

var function = KernelFunctionFactory.CreateFromMethod(GetData);

await foreach (var item in kernel.InvokeStreamingAsync<int>(function))
await foreach (var item in kernel.InvokeStreamingAsync<string>(function))
{
Console.WriteLine(item);
}

// Output: 2, 4, 6.
// Output:
// chunk1 - updated from filter
// chunk2 - updated from filter
// chunk3 - updated from filter
}

[Fact]
public async Task FunctionFilterResultOverrideForBothStreamingAndNonStreamingAsync()
{
var builder = Kernel.CreateBuilder();

// This filter overrides result for both streaming and non-streaming invocation modes.
builder.Services.AddSingleton<IFunctionInvocationFilter, DualModeFilter>();

var kernel = builder.Build();

static async IAsyncEnumerable<string> GetData()
{
yield return "chunk1";
yield return "chunk2";
yield return "chunk3";
}

var nonStreamingFunction = KernelFunctionFactory.CreateFromMethod(() => "Result");
var streamingFunction = KernelFunctionFactory.CreateFromMethod(GetData);

var nonStreamingResult = await kernel.InvokeAsync(nonStreamingFunction);
var streamingResult = await kernel.InvokeStreamingAsync<string>(streamingFunction).ToListAsync();

Console.WriteLine($"Non-streaming result: {nonStreamingResult}");
Console.WriteLine($"Streaming result \n: {string.Join("\n", streamingResult)}");

// Output:
// Non-streaming result: Result - updated from filter
// Streaming result:
// chunk1 - updated from filter
// chunk2 - updated from filter
// chunk3 - updated from filter
}

[Fact]
Expand Down Expand Up @@ -172,16 +209,16 @@ public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, F

// In streaming scenario, async enumerable is available in context result object.
// To override data: get async enumerable from function result, override data and set new async enumerable in context result:
var enumerable = context.Result.GetValue<IAsyncEnumerable<int>>();
var enumerable = context.Result.GetValue<IAsyncEnumerable<string>>();
context.Result = new FunctionResult(context.Result, OverrideStreamingDataAsync(enumerable!));
}

private async IAsyncEnumerable<int> OverrideStreamingDataAsync(IAsyncEnumerable<int> data)
private async IAsyncEnumerable<string> OverrideStreamingDataAsync(IAsyncEnumerable<string> data)
{
await foreach (var item in data)
{
// Example: override streaming data
yield return item * 2;
yield return $"{item} - updated from filter";
}
}
}
Expand Down Expand Up @@ -255,6 +292,39 @@ private async IAsyncEnumerable<string> StreamingWithExceptionHandlingAsync(IAsyn
}
}

/// <summary>Filter that can be used for both streaming and non-streaming invocation modes at the same time.</summary>
private sealed class DualModeFilter : IFunctionInvocationFilter
{
public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next)
{
await next(context);

if (context.IsStreaming)
{
var enumerable = context.Result.GetValue<IAsyncEnumerable<string>>();
context.Result = new FunctionResult(context.Result, OverrideStreamingDataAsync(enumerable!));
}
else
{
var data = context.Result.GetValue<string>();
context.Result = new FunctionResult(context.Result, OverrideNonStreamingData(data!));
}
}

private async IAsyncEnumerable<string> OverrideStreamingDataAsync(IAsyncEnumerable<string> data)
{
await foreach (var item in data)
{
yield return $"{item} - updated from filter";
}
}

private string OverrideNonStreamingData(string data)
{
return $"{data} - updated from filter";
}
}

#endregion

#region Filters
Expand Down
76 changes: 62 additions & 14 deletions dotnet/samples/Concepts/Filtering/TelemetryWithFilters.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Diagnostics;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
Expand All @@ -17,8 +18,10 @@ namespace Filtering;
/// </summary>
public class TelemetryWithFilters(ITestOutputHelper output) : BaseTest(output)
{
[Fact]
public async Task LoggingAsync()
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task LoggingAsync(bool isStreaming)
{
// Initialize kernel with chat completion service.
var builder = Kernel
Expand Down Expand Up @@ -69,9 +72,25 @@ public async Task LoggingAsync()
{
// Invoke prompt with arguments.
const string Prompt = "Given the current time of day and weather, what is the likely color of the sky in {{$city}}?";
var result = await kernel.InvokePromptAsync(Prompt, new(executionSettings) { ["city"] = "Boston" });

Console.WriteLine(result);
var arguments = new KernelArguments(executionSettings) { ["city"] = "Boston" };

if (isStreaming)
{
await foreach (var item in kernel.InvokePromptStreamingAsync<StreamingChatMessageContent>(Prompt, arguments))
{
if (item.Content is not null)
{
Console.Write(item.Content);
}
}
}
else
{
var result = await kernel.InvokePromptAsync(Prompt, arguments);

Console.WriteLine(result);
}
}

// Output:
Expand Down Expand Up @@ -127,17 +146,8 @@ public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, F
await next(context);

logger.LogInformation("Function {FunctionName} succeeded.", context.Function.Name);
logger.LogTrace("Function result: {Result}", context.Result.ToString());

if (logger.IsEnabled(LogLevel.Information))
{
var usage = context.Result.Metadata?["Usage"];

if (usage is not null)
{
logger.LogInformation("Usage: {Usage}", JsonSerializer.Serialize(usage));
}
}
await this.LogFunctionResultAsync(context);
}
catch (Exception exception)
{
Expand All @@ -156,6 +166,44 @@ public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, F
}
}
}

private async Task LogFunctionResultAsync(FunctionInvocationContext context)
{
string? result = null;
object? usage = null;

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

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

usage = item.Metadata?["Usage"];
}

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

if (result is not null)
{
logger.LogTrace("Function result: {Result}", result);
}

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

/// <summary>
Expand Down
67 changes: 67 additions & 0 deletions dotnet/samples/Concepts/FunctionCalling/FunctionCalling.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,19 @@ namespace FunctionCalling;
/// * The <see cref="FunctionChoiceBehaviorOptions.AllowConcurrentInvocation"/> option enables concurrent invocation of functions by SK.
/// By default, this option is set to false, meaning that functions are invoked sequentially. Concurrent invocation is only possible if the AI model can
/// call or select multiple functions for invocation in a single request; otherwise, there is no distinction between sequential and concurrent invocation.
/// * The <see cref="FunctionChoiceBehaviorOptions.AllowParallelCalls"/> option instructs the AI model to call multiple functions in one request if the model supports parallel function calls.
/// By default, this option is set to null, meaning that the AI model default value will be used.
///
/// The following table summarizes the effects of different combinations of these options:
///
/// | AllowParallelCalls | AllowConcurrentInvocation | AI function call requests | Concurrent Invocation |
/// |---------------------|---------------------------|--------------------------------|-----------------------|
/// | false | false | one request per call | false |
/// | false | true | one request per call | false* |
/// | true | false | one request per multiple calls | false |
/// | true | true | one request per multiple calls | true |
///
/// `*` There's only one function to call
/// </summary>
public class FunctionCalling(ITestOutputHelper output) : BaseTest(output)
{
Expand Down Expand Up @@ -458,6 +470,61 @@ public async Task RunNonStreamingChatCompletionApiWithConcurrentFunctionInvocati
// Expected output: Good morning! The current UTC time is 07:47 on October 22, 2024. Here are the latest news headlines: 1. Squirrel Steals Show - Discover the unexpected star of a recent event. 2. Dog Wins Lottery - Unbelievably, a lucky canine has hit the jackpot.
}

[Fact]
/// <summary>
/// This example demonstrates usage of the non-streaming chat completion API with <see cref="FunctionChoiceBehavior.Auto"/> that
/// advertises all kernel functions to the AI model and instructs the model to call multiple functions in parallel.
/// </summary>
public async Task RunNonStreamingChatCompletionApiWithParallelFunctionCallOptionAsync()
{
Kernel kernel = CreateKernel();

// The `AllowParallelCalls` option instructs the AI model to call multiple functions in parallel if the model supports parallel function calls.
FunctionChoiceBehaviorOptions options = new() { AllowParallelCalls = true };

OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(options: options) };

IChatCompletionService chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();

ChatMessageContent result = await chatCompletionService.GetChatMessageContentAsync(
"Good morning! What’s the current time and latest news headlines?",
settings,
kernel);

// Assert
Console.WriteLine(result);

// Expected output: Good morning! The current UTC time is 07:47 on October 22, 2024. Here are the latest news headlines: 1. Squirrel Steals Show - Discover the unexpected star of a recent event. 2. Dog Wins Lottery - Unbelievably, a lucky canine has hit the jackpot.
}

[Fact]
/// <summary>
/// This example demonstrates usage of the non-streaming chat completion API with <see cref="FunctionChoiceBehavior.Auto"/> that
/// advertises all kernel functions to the AI model, instructs the model to call multiple functions in parallel, and invokes them concurrently.
/// </summary>
public async Task RunNonStreamingChatCompletionApiWithParallelFunctionCallAndConcurrentFunctionInvocationOptionsAsync()
{
Kernel kernel = CreateKernel();

// The `AllowParallelCalls` option instructs the AI model to call multiple functions in parallel if the model supports parallel function calls.
// The `AllowConcurrentInvocation` option enables concurrent invocation of the functions.
FunctionChoiceBehaviorOptions options = new() { AllowParallelCalls = true, AllowConcurrentInvocation = true };

OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(options: options) };

IChatCompletionService chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();

ChatMessageContent result = await chatCompletionService.GetChatMessageContentAsync(
"Good morning! What’s the current time and latest news headlines?",
settings,
kernel);

// Assert
Console.WriteLine(result);

// Expected output: Good morning! The current UTC time is 07:47 on October 22, 2024. Here are the latest news headlines: 1. Squirrel Steals Show - Discover the unexpected star of a recent event. 2. Dog Wins Lottery - Unbelievably, a lucky canine has hit the jackpot.
}

private static Kernel CreateKernel()
{
// Create kernel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public ProcessController(Kernel kernel)
public async Task<IActionResult> PostAsync(string processId)
{
var process = this.GetProcess();
var processContext = await process.StartAsync(this._kernel, new KernelProcessEvent() { Id = CommonEvents.StartProcess }, processId: processId);
var processContext = await process.StartAsync(new KernelProcessEvent() { Id = CommonEvents.StartProcess }, processId: processId);
var finalState = await processContext.GetStateAsync();

return this.Ok(processId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ public void RenderDone()
/// Render exception
/// </summary>
[KernelFunction]
public void RenderError(Exception exception, ILogger logger)
public void RenderError(KernelProcessError error, ILogger logger)
{
string message = string.IsNullOrWhiteSpace(exception.Message) ? "Unexpected failure" : exception.Message;
Render($"ERROR: {message} [{exception.GetType().Name}]{Environment.NewLine}{exception.StackTrace}");
logger.LogError(exception, "Unexpected failure.");
string message = string.IsNullOrWhiteSpace(error.Message) ? "Unexpected failure" : error.Message;
Render($"ERROR: {message} [{error.GetType().Name}]{Environment.NewLine}{error.StackTrace}");
logger.LogError("Unexpected failure: {ErrorMessage} [{ErrorType}]", error.Message, error.Type);
}

/// <summary>
Expand Down
Loading

0 comments on commit 8a871cc

Please sign in to comment.