mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
.Net: SK Telemetry improvements (#1905)
### Motivation and Context <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> This PR contains changes to improve observability for Semantic Kernel users, including: - Logging: using already existing `ILogger` interface with some improvements described below. - Metering: implemented using `Meter` and `MeterListener` classes from `System.Diagnostics.Metrics` namespace. - Tracing: implemented using `Activity`, `ActivitySource` and `ActivityListener` classes from `System.Diagnostics` namespace. Added telemetry uses native .NET methods, which means that it's not dependent on specific telemetry tool. Current PR contains console application using Application Insights as example to show telemetry capabilities in Kernel. Changes include instrumentation for `SequentialPlanner` as a starting point and later there will be new PRs for other types of planners and more improvements for kernel telemetry. Also, there are updates for `LogLevel` handling across codebase to align with described log level purpose: https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel?view=dotnet-plat-ext-7.0 Particularly, there are changes for `LogLevel.Trace` to be used for sensitive data and most detailed messages, which should not be enabled in prodution environments. ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> Changes: - Marked `.WithLogger` method in KernelBuilder as obsolete and added new method `.WithLogging`. - Added new methods `.WithMetering` and `.WithTracing` in KernelBuilder. - Improved `LogLevel` handling across codebase to cover cases of logging sensitive data. - Implemented `InstrumentedSequentialPlanner` for instrumentation using decorator pattern. - Extended `InvokeAsync` method in `Plan` for instrumentation. ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [x] The code builds clean without any errors or warnings - [x] The PR follows SK Contribution Guidelines (https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) - [x] The code follows the .NET coding conventions (https://learn.microsoft.com/dotnet/csharp/fundamentals/coding-style/coding-conventions) verified with `dotnet format` - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone 😄 --------- Co-authored-by: Shawn Callegari <[email protected]>
This commit is contained in:
co-authored by
Shawn Callegari
parent
f622bfeb88
commit
55dc7942e5
@@ -8,12 +8,14 @@
|
||||
<PackageVersion Include="Azure.AI.OpenAI" Version="1.0.0-beta.5" />
|
||||
<PackageVersion Include="Azure.Identity" Version="1.9.0" />
|
||||
<PackageVersion Include="Azure.Search.Documents" Version="11.5.0-beta.3" />
|
||||
<PackageVersion Include="Microsoft.ApplicationInsights.WorkerService" Version="2.21.0" />
|
||||
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="[1.1.0, )" />
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||
<PackageVersion Include="NRedisStack" Version="0.8.0" />
|
||||
<PackageVersion Include="Pgvector" Version="0.1.3" />
|
||||
<PackageVersion Include="Polly" Version="7.2.4" />
|
||||
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="7.0.2" />
|
||||
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
|
||||
<PackageVersion Include="System.Text.Json" Version="6.0.8" />
|
||||
<!-- Microsoft.Extensions.Logging -->
|
||||
|
||||
@@ -145,6 +145,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connectors.AI.Oobabooga", "
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Planning.StepwisePlanner", "src\Extensions\Planning.StepwisePlanner\Planning.StepwisePlanner.csproj", "{4762BCAF-E1C5-4714-B88D-E50FA333C50E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApplicationInsightsExample", "samples\ApplicationInsightsExample\ApplicationInsightsExample.csproj", "{C754950A-E16C-4F96-9CC7-9328E361B5AF}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -356,6 +358,12 @@ Global
|
||||
{4762BCAF-E1C5-4714-B88D-E50FA333C50E}.Publish|Any CPU.Build.0 = Publish|Any CPU
|
||||
{4762BCAF-E1C5-4714-B88D-E50FA333C50E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4762BCAF-E1C5-4714-B88D-E50FA333C50E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C754950A-E16C-4F96-9CC7-9328E361B5AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C754950A-E16C-4F96-9CC7-9328E361B5AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C754950A-E16C-4F96-9CC7-9328E361B5AF}.Publish|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C754950A-E16C-4F96-9CC7-9328E361B5AF}.Publish|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C754950A-E16C-4F96-9CC7-9328E361B5AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C754950A-E16C-4F96-9CC7-9328E361B5AF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -407,6 +415,7 @@ Global
|
||||
{E6EDAB8F-3406-4DBF-9AAB-DF40DC2CA0FA} = {FA3720F1-C99A-49B2-9577-A940257098BF}
|
||||
{677F1381-7830-4115-9C1A-58B282629DC6} = {0247C2C9-86C3-45BA-8873-28B0948EDC0C}
|
||||
{4762BCAF-E1C5-4714-B88D-E50FA333C50E} = {078F96B4-09E1-4E0E-B214-F71A4F4BF633}
|
||||
{C754950A-E16C-4F96-9CC7-9328E361B5AF} = {FA3720F1-C99A-49B2-9577-A940257098BF}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<RollForward>LatestMajor</RollForward>
|
||||
<OutputType>Exe</OutputType>
|
||||
<LangVersion>10</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
<!-- Suppress: "Declare types in namespaces", "Require ConfigureAwait" -->
|
||||
<NoWarn>CA1050;CA1707;CA2007;VSTHRD111</NoWarn>
|
||||
<UserSecretsId>5ee045b0-aea3-4f08-8d31-32d1a6f8fed0</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.ApplicationInsights.WorkerService" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Extensions\Planning.SequentialPlanner\Planning.SequentialPlanner.csproj" />
|
||||
<ProjectReference Include="..\..\src\Connectors\Connectors.AI.OpenAI\Connectors.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\src\SemanticKernel\SemanticKernel.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,192 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.ApplicationInsights;
|
||||
using Microsoft.ApplicationInsights.DataContracts;
|
||||
using Microsoft.ApplicationInsights.Extensibility;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.ApplicationInsights;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Planning;
|
||||
using Microsoft.SemanticKernel.Planning.Sequential;
|
||||
|
||||
/// <summary>
|
||||
/// Example of telemetry in Semantic Kernel using Application Insights within console application.
|
||||
/// </summary>
|
||||
public sealed class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// Log level to be used by <see cref="ILogger"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="LogLevel.Information"/> is set by default. <para />
|
||||
/// <see cref="LogLevel.Trace"/> will enable logging with more detailed information, including sensitive data. Should not be used in production. <para />
|
||||
/// </remarks>
|
||||
private static LogLevel LogLevel = LogLevel.Information;
|
||||
|
||||
public static async Task Main()
|
||||
{
|
||||
var serviceProvider = GetServiceProvider();
|
||||
|
||||
var telemetryClient = serviceProvider.GetRequiredService<TelemetryClient>();
|
||||
var logger = serviceProvider.GetRequiredService<ILogger<Program>>();
|
||||
|
||||
using var meterListener = new MeterListener();
|
||||
using var activityListener = new ActivityListener();
|
||||
|
||||
ConfigureMetering(meterListener, telemetryClient);
|
||||
ConfigureTracing(activityListener, telemetryClient);
|
||||
|
||||
var kernel = GetKernel(logger);
|
||||
var planner = GetPlanner(kernel, logger);
|
||||
|
||||
try
|
||||
{
|
||||
using var operation = telemetryClient.StartOperation<DependencyTelemetry>("ApplicationInsights.Example");
|
||||
|
||||
Console.WriteLine("Operation/Trace ID:");
|
||||
Console.WriteLine(Activity.Current?.TraceId);
|
||||
|
||||
var plan = await planner.CreatePlanAsync("Write a poem about John Doe, then translate it into Italian.");
|
||||
|
||||
Console.WriteLine("Original plan:");
|
||||
Console.WriteLine(plan.ToPlanString());
|
||||
|
||||
var result = await kernel.RunAsync(plan);
|
||||
|
||||
Console.WriteLine("Result:");
|
||||
Console.WriteLine(result.Result);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Explicitly call Flush() followed by sleep is required in console apps.
|
||||
// This is to ensure that even if application terminates, telemetry is sent to the back-end.
|
||||
telemetryClient.Flush();
|
||||
await Task.Delay(5000);
|
||||
}
|
||||
}
|
||||
|
||||
private static ServiceProvider GetServiceProvider()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
ConfigureApplicationInsightsTelemetry(services);
|
||||
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
private static void ConfigureApplicationInsightsTelemetry(ServiceCollection services)
|
||||
{
|
||||
string instrumentationKey = Env.Var("ApplicationInsights__InstrumentationKey");
|
||||
|
||||
services.AddLogging(loggingBuilder =>
|
||||
{
|
||||
loggingBuilder.AddFilter<ApplicationInsightsLoggerProvider>(typeof(Program).FullName, LogLevel);
|
||||
loggingBuilder.SetMinimumLevel(LogLevel);
|
||||
});
|
||||
|
||||
services.AddApplicationInsightsTelemetryWorkerService(options =>
|
||||
{
|
||||
options.ConnectionString = $"InstrumentationKey={instrumentationKey}";
|
||||
});
|
||||
}
|
||||
|
||||
private static IKernel GetKernel(ILogger logger)
|
||||
{
|
||||
string folder = RepoFiles.SampleSkillsPath();
|
||||
|
||||
var kernel = new KernelBuilder()
|
||||
.WithLogger(logger)
|
||||
.WithAzureChatCompletionService(
|
||||
Env.Var("AzureOpenAI__ChatDeploymentName"),
|
||||
Env.Var("AzureOpenAI__Endpoint"),
|
||||
Env.Var("AzureOpenAI__ApiKey"))
|
||||
.Build();
|
||||
|
||||
kernel.ImportSemanticSkillFromDirectory(folder, "SummarizeSkill", "WriterSkill");
|
||||
|
||||
return kernel;
|
||||
}
|
||||
|
||||
private static ISequentialPlanner GetPlanner(
|
||||
IKernel kernel,
|
||||
ILogger logger,
|
||||
int maxTokens = 1024)
|
||||
{
|
||||
var plannerConfig = new SequentialPlannerConfig { MaxTokens = maxTokens };
|
||||
|
||||
return new SequentialPlanner(kernel, plannerConfig).WithInstrumentation(logger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Example of metering configuration in Application Insights
|
||||
/// using <see cref="MeterListener"/> to attach for <see cref="Meter"/> recordings.
|
||||
/// </summary>
|
||||
/// <param name="meterListener">Instance of <see cref="MeterListener"/> for metering configuration.</param>
|
||||
/// <param name="telemetryClient">Instance of Application Insights <see cref="TelemetryClient"/>.</param>
|
||||
private static void ConfigureMetering(MeterListener meterListener, TelemetryClient telemetryClient)
|
||||
{
|
||||
meterListener.InstrumentPublished = (instrument, listener) =>
|
||||
{
|
||||
// Subscribe to all metrics in Semantic Kernel
|
||||
if (instrument.Meter.Name.StartsWith("Microsoft.SemanticKernel", StringComparison.Ordinal))
|
||||
{
|
||||
listener.EnableMeasurementEvents(instrument);
|
||||
}
|
||||
};
|
||||
|
||||
MeasurementCallback<double> measurementCallback = (instrument, measurement, tags, state) =>
|
||||
{
|
||||
telemetryClient.GetMetric(instrument.Name).TrackValue(measurement);
|
||||
};
|
||||
|
||||
meterListener.SetMeasurementEventCallback(measurementCallback);
|
||||
|
||||
meterListener.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Example of advanced distributed tracing configuration in Application Insights
|
||||
/// using <see cref="ActivityListener"/> to attach for <see cref="Activity"/> events.
|
||||
/// </summary>
|
||||
/// <param name="activityListener">Instance of <see cref="ActivityListener"/> for tracing configuration.</param>
|
||||
/// <param name="telemetryClient">Instance of Application Insights <see cref="TelemetryClient"/>.</param>
|
||||
private static void ConfigureTracing(ActivityListener activityListener, TelemetryClient telemetryClient)
|
||||
{
|
||||
var operations = new ConcurrentDictionary<string, IOperationHolder<DependencyTelemetry>>();
|
||||
|
||||
// For more detailed tracing we need to attach Activity entity to Application Insights operation manually.
|
||||
Action<Activity> activityStarted = activity =>
|
||||
{
|
||||
var operation = telemetryClient.StartOperation<DependencyTelemetry>(activity);
|
||||
operation.Telemetry.Type = activity.Kind.ToString();
|
||||
|
||||
operations.TryAdd(activity.TraceId.ToString(), operation);
|
||||
};
|
||||
|
||||
// We also need to manually stop Application Insights operation when Activity entity is stopped.
|
||||
Action<Activity> activityStopped = activity =>
|
||||
{
|
||||
if (operations.TryRemove(activity.TraceId.ToString(), out var operation))
|
||||
{
|
||||
telemetryClient.StopOperation(operation);
|
||||
}
|
||||
};
|
||||
|
||||
// Subscribe to all traces in Semantic Kernel
|
||||
activityListener.ShouldListenTo =
|
||||
activitySource => activitySource.Name.StartsWith("Microsoft.SemanticKernel", StringComparison.Ordinal);
|
||||
|
||||
activityListener.Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllData;
|
||||
activityListener.SampleUsingParentId = (ref ActivityCreationOptions<string> _) => ActivitySamplingResult.AllData;
|
||||
activityListener.ActivityStarted = activityStarted;
|
||||
activityListener.ActivityStopped = activityStopped;
|
||||
|
||||
ActivitySource.AddActivityListener(activityListener);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
#pragma warning disable CA1812 // instantiated by AddUserSecrets
|
||||
internal sealed class Env
|
||||
#pragma warning restore CA1812
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple helper used to load env vars and secrets like credentials,
|
||||
/// to avoid hard coding them in the sample code
|
||||
/// </summary>
|
||||
/// <param name="name">Secret name / Env var name</param>
|
||||
/// <returns>Value found in Secret Manager or Environment Variable</returns>
|
||||
internal static string Var(string name)
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddUserSecrets<Env>()
|
||||
.Build();
|
||||
|
||||
var value = configuration[name];
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
value = Environment.GetEnvironmentVariable(name);
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
throw new ArgumentException($"Secret / Env var not set: {name}");
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
internal static class RepoFiles
|
||||
{
|
||||
/// <summary>
|
||||
/// Scan the local folders from the repo, looking for "samples/skills" folder.
|
||||
/// </summary>
|
||||
/// <returns>The full path to samples/skills</returns>
|
||||
public static string SampleSkillsPath()
|
||||
{
|
||||
const string Parent = "samples";
|
||||
const string Folder = "skills";
|
||||
|
||||
bool SearchPath(string pathToFind, out string result, int maxAttempts = 10)
|
||||
{
|
||||
var currDir = Path.GetFullPath(Assembly.GetExecutingAssembly().Location);
|
||||
bool found;
|
||||
do
|
||||
{
|
||||
result = Path.Join(currDir, pathToFind);
|
||||
found = Directory.Exists(result);
|
||||
currDir = Path.GetFullPath(Path.Combine(currDir, ".."));
|
||||
} while (maxAttempts-- > 0 && !found);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
if (!SearchPath(Parent + Path.DirectorySeparatorChar + Folder, out string path)
|
||||
&& !SearchPath(Folder, out path))
|
||||
{
|
||||
throw new DirectoryNotFoundException("Skills directory not found. The app needs the skills from the repo to work.");
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ internal static class Example12_SequentialPlanner
|
||||
// - WriterSkill.Translate language='Italian' INPUT='' =>
|
||||
|
||||
Console.WriteLine("Original plan:");
|
||||
Console.WriteLine(plan.ToPlanString());
|
||||
Console.WriteLine(plan.ToPlanWithGoalString());
|
||||
|
||||
var result = await kernel.RunAsync(plan);
|
||||
|
||||
@@ -124,7 +124,7 @@ internal static class Example12_SequentialPlanner
|
||||
// - email.SendEmail INPUT='$TRANSLATED_SUMMARY' email_address='$EMAIL_ADDRESS' =>
|
||||
|
||||
Console.WriteLine("Original plan:");
|
||||
Console.WriteLine(plan.ToPlanString());
|
||||
Console.WriteLine(plan.ToPlanWithGoalString());
|
||||
|
||||
var input =
|
||||
"Once upon a time, in a faraway kingdom, there lived a kind and just king named Arjun. " +
|
||||
@@ -166,7 +166,7 @@ internal static class Example12_SequentialPlanner
|
||||
// - WriterSkill.NovelChapter chapterIndex='3' previousChapter='$CHAPTER_2_SYNOPSIS' INPUT='$CHAPTER_3_SYNOPSIS' theme='Children's mystery' => RESULT__CHAPTER_3
|
||||
|
||||
Console.WriteLine("Original plan:");
|
||||
Console.WriteLine(originalPlan.ToPlanString());
|
||||
Console.WriteLine(originalPlan.ToPlanWithGoalString());
|
||||
|
||||
Stopwatch sw = new();
|
||||
sw.Start();
|
||||
@@ -215,7 +215,7 @@ internal static class Example12_SequentialPlanner
|
||||
var plan = await planner.CreatePlanAsync(goal);
|
||||
|
||||
Console.WriteLine("Original plan:");
|
||||
Console.WriteLine(plan.ToPlanString());
|
||||
Console.WriteLine(plan.ToPlanWithGoalString());
|
||||
}
|
||||
|
||||
private static IKernel InitializeKernelAndPlanner(out SequentialPlanner planner, int maxTokens = 1024)
|
||||
|
||||
@@ -147,7 +147,7 @@ public class MarkupSkill
|
||||
var plan = docString.FromMarkup("Run a piece of xml markup", context);
|
||||
|
||||
Console.WriteLine("Markup plan:");
|
||||
Console.WriteLine(plan.ToPlanString());
|
||||
Console.WriteLine(plan.ToPlanWithGoalString());
|
||||
Console.WriteLine();
|
||||
|
||||
var result = await plan.InvokeAsync();
|
||||
|
||||
@@ -141,7 +141,7 @@ which are not grounded in the original.
|
||||
var plannerConfig = new SequentialPlannerConfig { };
|
||||
var planner = new SequentialPlanner(kernel, plannerConfig);
|
||||
var plan = await planner.CreatePlanAsync(ask);
|
||||
Console.WriteLine(plan.ToPlanString());
|
||||
Console.WriteLine(plan.ToPlanWithGoalString());
|
||||
|
||||
var results = await plan.InvokeAsync(s_groundingText);
|
||||
Console.WriteLine(results.Result);
|
||||
|
||||
@@ -1,41 +1,15 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using Microsoft.SemanticKernel.Planning;
|
||||
|
||||
namespace RepoUtils;
|
||||
|
||||
internal static class PlanExtensions
|
||||
{
|
||||
internal static string ToPlanString(this Plan originalPlan, string indent = " ")
|
||||
internal static string ToPlanWithGoalString(this Plan plan, string indent = " ")
|
||||
{
|
||||
string goalHeader = $"{indent}Goal: {originalPlan.Description}\n\n{indent}Steps:\n";
|
||||
string goalHeader = $"{indent}Goal: {plan.Description}\n\n{indent}Steps:\n";
|
||||
|
||||
string stepItems = string.Join("\n", originalPlan.Steps.Select(step =>
|
||||
{
|
||||
if (step.Steps.Count == 0)
|
||||
{
|
||||
string skillName = step.SkillName;
|
||||
string stepName = step.Name;
|
||||
|
||||
string parameters = string.Join(" ", step.Parameters.Select(param => $"{param.Key}='{param.Value}'"));
|
||||
if (!string.IsNullOrEmpty(parameters))
|
||||
{
|
||||
parameters = $" {parameters}";
|
||||
}
|
||||
|
||||
string? outputs = step.Outputs.FirstOrDefault();
|
||||
if (!string.IsNullOrEmpty(outputs))
|
||||
{
|
||||
outputs = $" => {outputs}";
|
||||
}
|
||||
|
||||
return $"{indent}{indent}- {string.Join(".", skillName, stepName)}{parameters}{outputs}";
|
||||
}
|
||||
|
||||
return step.ToPlanString(indent + indent);
|
||||
}));
|
||||
|
||||
return goalHeader + stepItems;
|
||||
return goalHeader + plan.ToPlanString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ internal sealed class EmailSkill
|
||||
[Description("The name of the person whose email address needs to be found.")] string input,
|
||||
ILogger? logger = null)
|
||||
{
|
||||
logger?.LogDebug("Returning hard coded email for {0}", input);
|
||||
// Sensitive data, logging as trace, disabled by default
|
||||
logger?.LogTrace("Returning hard coded email for {0}", input);
|
||||
|
||||
return "[email protected]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Azure;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Core;
|
||||
@@ -31,7 +32,7 @@ public abstract class AzureOpenAIClientBase : ClientBase
|
||||
string endpoint,
|
||||
string apiKey,
|
||||
HttpClient? httpClient = null,
|
||||
ILogger? logger = null)
|
||||
ILogger? logger = null) : base(logger)
|
||||
{
|
||||
Verify.NotNullOrWhiteSpace(modelId);
|
||||
Verify.NotNullOrWhiteSpace(endpoint);
|
||||
@@ -62,7 +63,7 @@ public abstract class AzureOpenAIClientBase : ClientBase
|
||||
string endpoint,
|
||||
TokenCredential credential,
|
||||
HttpClient? httpClient = null,
|
||||
ILogger? logger = null)
|
||||
ILogger? logger = null) : base(logger)
|
||||
{
|
||||
Verify.NotNullOrWhiteSpace(modelId);
|
||||
Verify.NotNullOrWhiteSpace(endpoint);
|
||||
@@ -95,4 +96,13 @@ public abstract class AzureOpenAIClientBase : ClientBase
|
||||
this.ModelId = modelId;
|
||||
this.Client = openAIClient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs Azure OpenAI action details.
|
||||
/// </summary>
|
||||
/// <param name="callerMemberName">Caller member name. Populated automatically by runtime.</param>
|
||||
private protected void LogActionDetails([CallerMemberName] string? callerMemberName = default)
|
||||
{
|
||||
this.Logger.LogInformation("Action: {Action}. Azure OpenAI Deployment Name: {DeploymentName}.", callerMemberName, this.ModelId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Azure;
|
||||
using Azure.AI.OpenAI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.SemanticKernel.AI;
|
||||
using Microsoft.SemanticKernel.AI.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.AI.Embeddings;
|
||||
@@ -25,7 +27,10 @@ public abstract class ClientBase
|
||||
private const int MaxResultsPerPrompt = 128;
|
||||
|
||||
// Prevent external inheritors
|
||||
private protected ClientBase() { }
|
||||
private protected ClientBase(ILogger? logger = null)
|
||||
{
|
||||
this.Logger = logger ?? NullLogger.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Model Id or Deployment Name
|
||||
@@ -37,6 +42,11 @@ public abstract class ClientBase
|
||||
/// </summary>
|
||||
private protected abstract OpenAIClient Client { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Logger instance
|
||||
/// </summary>
|
||||
private protected ILogger Logger { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates completions for the prompt and settings.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Core;
|
||||
using Azure.Core.Pipeline;
|
||||
@@ -29,7 +30,7 @@ public abstract class OpenAIClientBase : ClientBase
|
||||
string apiKey,
|
||||
string? organization = null,
|
||||
HttpClient? httpClient = null,
|
||||
ILogger? logger = null)
|
||||
ILogger? logger = null) : base(logger)
|
||||
{
|
||||
Verify.NotNullOrWhiteSpace(modelId);
|
||||
Verify.NotNullOrWhiteSpace(apiKey);
|
||||
@@ -49,4 +50,13 @@ public abstract class OpenAIClientBase : ClientBase
|
||||
|
||||
this.Client = new OpenAIClient(apiKey, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs OpenAI action details.
|
||||
/// </summary>
|
||||
/// <param name="callerMemberName">Caller member name. Populated automatically by runtime.</param>
|
||||
private protected void LogActionDetails([CallerMemberName] string? callerMemberName = default)
|
||||
{
|
||||
this.Logger.LogInformation("Action: {Action}. OpenAI Model ID: {ModelId}.", callerMemberName, this.ModelId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ public sealed class AzureChatCompletion : AzureOpenAIClientBase, IChatCompletion
|
||||
ChatRequestSettings? requestSettings = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.LogActionDetails();
|
||||
return this.InternalGetChatResultsAsync(chat, requestSettings, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -81,6 +82,7 @@ public sealed class AzureChatCompletion : AzureOpenAIClientBase, IChatCompletion
|
||||
ChatRequestSettings? requestSettings = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.LogActionDetails();
|
||||
return this.InternalGetChatStreamingResultsAsync(chat, requestSettings, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -96,6 +98,7 @@ public sealed class AzureChatCompletion : AzureOpenAIClientBase, IChatCompletion
|
||||
CompleteRequestSettings? requestSettings = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.LogActionDetails();
|
||||
return this.InternalGetChatStreamingResultsAsTextAsync(text, requestSettings, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -105,6 +108,7 @@ public sealed class AzureChatCompletion : AzureOpenAIClientBase, IChatCompletion
|
||||
CompleteRequestSettings? requestSettings = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.LogActionDetails();
|
||||
return this.InternalGetChatResultsAsTextAsync(text, requestSettings, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ public sealed class OpenAIChatCompletion : OpenAIClientBase, IChatCompletion, IT
|
||||
ChatRequestSettings? requestSettings = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.LogActionDetails();
|
||||
return this.InternalGetChatResultsAsync(chat, requestSettings, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -50,6 +51,7 @@ public sealed class OpenAIChatCompletion : OpenAIClientBase, IChatCompletion, IT
|
||||
ChatRequestSettings? requestSettings = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.LogActionDetails();
|
||||
return this.InternalGetChatStreamingResultsAsync(chat, requestSettings, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -65,6 +67,7 @@ public sealed class OpenAIChatCompletion : OpenAIClientBase, IChatCompletion, IT
|
||||
CompleteRequestSettings? requestSettings = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.LogActionDetails();
|
||||
return this.InternalGetChatStreamingResultsAsTextAsync(text, requestSettings, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -74,6 +77,7 @@ public sealed class OpenAIChatCompletion : OpenAIClientBase, IChatCompletion, IT
|
||||
CompleteRequestSettings? requestSettings = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.LogActionDetails();
|
||||
return this.InternalGetChatResultsAsTextAsync(text, requestSettings, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ public abstract class OpenAIClientBase
|
||||
}
|
||||
catch (Exception ex) when (ex is NotSupportedException or JsonException)
|
||||
{
|
||||
this._log.LogTrace("Unable to extract error from response body content. Exception: {0}:{1}", ex.GetType(), ex.Message);
|
||||
this._log.LogError(ex, "Unable to extract error from response body content. Exception: {0}:{1}", ex.GetType(), ex.Message);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -159,7 +159,7 @@ public abstract class OpenAIClientBase
|
||||
response = await this._httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
this._log.LogTrace("HTTP response: {0} {1}", (int)response.StatusCode, response.StatusCode.ToString("G"));
|
||||
this._log.LogDebug("HTTP response: {0} {1}", (int)response.StatusCode, response.StatusCode.ToString("G"));
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
|
||||
@@ -71,6 +71,7 @@ public sealed class AzureTextCompletion : AzureOpenAIClientBase, ITextCompletion
|
||||
CompleteRequestSettings requestSettings,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.LogActionDetails();
|
||||
return this.InternalGetTextStreamingResultsAsync(text, requestSettings, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -80,6 +81,7 @@ public sealed class AzureTextCompletion : AzureOpenAIClientBase, ITextCompletion
|
||||
CompleteRequestSettings requestSettings,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.LogActionDetails();
|
||||
return this.InternalGetTextResultsAsync(text, requestSettings, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ public sealed class OpenAITextCompletion : OpenAIClientBase, ITextCompletion
|
||||
CompleteRequestSettings requestSettings,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.LogActionDetails();
|
||||
return this.InternalGetTextStreamingResultsAsync(text, requestSettings, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -49,6 +50,7 @@ public sealed class OpenAITextCompletion : OpenAIClientBase, ITextCompletion
|
||||
CompleteRequestSettings requestSettings,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.LogActionDetails();
|
||||
return this.InternalGetTextResultsAsync(text, requestSettings, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -60,6 +60,7 @@ public sealed class AzureTextEmbeddingGeneration : AzureOpenAIClientBase, ITextE
|
||||
IList<string> data,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.LogActionDetails();
|
||||
return this.InternalGetEmbeddingsAsync(data, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -43,6 +43,7 @@ public sealed class OpenAITextEmbeddingGeneration : OpenAIClientBase, ITextEmbed
|
||||
IList<string> data,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.LogActionDetails();
|
||||
return this.InternalGetEmbeddingsAsync(data, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,11 +52,11 @@ public sealed class CosmosMemoryStore : IMemoryStore
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.Created)
|
||||
{
|
||||
newStore._log.LogInformation("Created database {0}", newStore._databaseName);
|
||||
newStore._log.LogDebug("Created database {0}", newStore._databaseName);
|
||||
}
|
||||
else if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
newStore._log.LogInformation("Database {0}", newStore._databaseName);
|
||||
newStore._log.LogDebug("Database {0}", newStore._databaseName);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -84,11 +84,11 @@ public sealed class CosmosMemoryStore : IMemoryStore
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.Created)
|
||||
{
|
||||
this._log.LogInformation("Created collection {0}", collectionName);
|
||||
this._log.LogDebug("Created collection {0}", collectionName);
|
||||
}
|
||||
else if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
this._log.LogInformation("Collection {0} already exists", collectionName);
|
||||
this._log.LogDebug("Collection {0} already exists", collectionName);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -191,7 +191,7 @@ public sealed class CosmosMemoryStore : IMemoryStore
|
||||
|
||||
if (response.StatusCode is HttpStatusCode.OK or HttpStatusCode.Created)
|
||||
{
|
||||
this._log.LogInformation("Upserted item to collection {0}", collectionName);
|
||||
this._log.LogDebug("Upserted item to collection {0}", collectionName);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -221,7 +221,7 @@ public sealed class CosmosMemoryStore : IMemoryStore
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
this._log.LogInformation("Record deleted from {0}", collectionName);
|
||||
this._log.LogDebug("Record deleted from {0}", collectionName);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -47,7 +47,7 @@ public sealed class PineconeClient : IPineconeClient
|
||||
bool includeValues = false,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._logger.LogInformation("Searching vectors by id");
|
||||
this._logger.LogDebug("Searching vectors by id");
|
||||
|
||||
string basePath = await this.GetVectorOperationsApiBasePathAsync(indexName).ConfigureAwait(false);
|
||||
|
||||
@@ -101,7 +101,7 @@ public sealed class PineconeClient : IPineconeClient
|
||||
bool includeMetadata = true,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._logger.LogInformation("Querying top {0} nearest vectors", query.TopK);
|
||||
this._logger.LogDebug("Querying top {0} nearest vectors", query.TopK);
|
||||
|
||||
using HttpRequestMessage request = QueryRequest.QueryIndex(query)
|
||||
.WithMetadata(includeMetadata)
|
||||
@@ -154,7 +154,7 @@ public sealed class PineconeClient : IPineconeClient
|
||||
Dictionary<string, object>? filter = default,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._logger.LogInformation("Searching top {0} nearest vectors with threshold {1}", topK, threshold);
|
||||
this._logger.LogDebug("Searching top {0} nearest vectors with threshold {1}", topK, threshold);
|
||||
|
||||
List<(PineconeDocument document, float score)> documents = new();
|
||||
|
||||
@@ -203,7 +203,7 @@ public sealed class PineconeClient : IPineconeClient
|
||||
string indexNamespace = "",
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._logger.LogInformation("Upserting vectors");
|
||||
this._logger.LogDebug("Upserting vectors");
|
||||
|
||||
int totalUpserted = 0;
|
||||
int totalBatches = 0;
|
||||
@@ -239,10 +239,10 @@ public sealed class PineconeClient : IPineconeClient
|
||||
|
||||
totalUpserted += data.UpsertedCount;
|
||||
|
||||
this._logger.LogInformation("Upserted batch {0} with {1} vectors", totalBatches, data.UpsertedCount);
|
||||
this._logger.LogDebug("Upserted batch {0} with {1} vectors", totalBatches, data.UpsertedCount);
|
||||
}
|
||||
|
||||
this._logger.LogInformation("Upserted {0} vectors in {1} batches", totalUpserted, totalBatches);
|
||||
this._logger.LogDebug("Upserted {0} vectors in {1} batches", totalUpserted, totalBatches);
|
||||
|
||||
return totalUpserted;
|
||||
}
|
||||
@@ -273,7 +273,7 @@ public sealed class PineconeClient : IPineconeClient
|
||||
.FromNamespace(indexNamespace)
|
||||
.FilterBy(filter);
|
||||
|
||||
this._logger.LogInformation("Delete operation for Index {0}: {1}", indexName, deleteRequest.ToString());
|
||||
this._logger.LogDebug("Delete operation for Index {0}: {1}", indexName, deleteRequest.ToString());
|
||||
|
||||
string basePath = await this.GetVectorOperationsApiBasePathAsync(indexName).ConfigureAwait(false);
|
||||
|
||||
@@ -295,7 +295,7 @@ public sealed class PineconeClient : IPineconeClient
|
||||
/// <inheritdoc />
|
||||
public async Task UpdateAsync(string indexName, PineconeDocument document, string indexNamespace = "", CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._logger.LogInformation("Updating vector: {0}", document.Id);
|
||||
this._logger.LogDebug("Updating vector: {0}", document.Id);
|
||||
|
||||
string basePath = await this.GetVectorOperationsApiBasePathAsync(indexName).ConfigureAwait(false);
|
||||
|
||||
@@ -380,7 +380,7 @@ public sealed class PineconeClient : IPineconeClient
|
||||
/// <inheritdoc />
|
||||
public async Task CreateIndexAsync(IndexDefinition indexDefinition, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._logger.LogInformation("Creating index {0}", indexDefinition.ToString());
|
||||
this._logger.LogDebug("Creating index {0}", indexDefinition.ToString());
|
||||
|
||||
string indexName = indexDefinition.Name;
|
||||
|
||||
@@ -412,7 +412,7 @@ public sealed class PineconeClient : IPineconeClient
|
||||
/// <inheritdoc />
|
||||
public async Task DeleteIndexAsync(string indexName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._logger.LogInformation("Deleting index {0}", indexName);
|
||||
this._logger.LogDebug("Deleting index {0}", indexName);
|
||||
|
||||
using HttpRequestMessage request = DeleteIndexRequest.Create(indexName).Build();
|
||||
|
||||
@@ -433,13 +433,13 @@ public sealed class PineconeClient : IPineconeClient
|
||||
throw;
|
||||
}
|
||||
|
||||
this._logger.LogInformation("Index: {0} has been successfully deleted.", indexName);
|
||||
this._logger.LogDebug("Index: {0} has been successfully deleted.", indexName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> DoesIndexExistAsync(string indexName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._logger.LogInformation("Checking for index {0}", indexName);
|
||||
this._logger.LogDebug("Checking for index {0}", indexName);
|
||||
|
||||
List<string?>? indexNames = await this.ListIndexesAsync(cancellationToken).ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -558,9 +558,14 @@ public sealed class PineconeClient : IPineconeClient
|
||||
|
||||
string responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
string logMessage = response.IsSuccessStatusCode ? "Pinecone responded successfully" : "Pinecone responded with error";
|
||||
|
||||
this._logger.LogTrace("{0} - {1}", logMessage, responseContent);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
this._logger.LogDebug("Pinecone responded successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
this._logger.LogWarning("Pinecone responded with error");
|
||||
}
|
||||
|
||||
return (response, responseContent);
|
||||
}
|
||||
@@ -572,7 +577,7 @@ public sealed class PineconeClient : IPineconeClient
|
||||
return indexHost;
|
||||
}
|
||||
|
||||
this._logger.LogInformation("Getting index host from Pinecone.");
|
||||
this._logger.LogDebug("Getting index host from Pinecone.");
|
||||
|
||||
PineconeIndex? pineconeIndex = await this.DescribeIndexAsync(indexName, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -590,7 +595,7 @@ public sealed class PineconeClient : IPineconeClient
|
||||
$"Host of index {indexName} is unknown.");
|
||||
}
|
||||
|
||||
this._logger.LogInformation("Found host {0} for index {1}", pineconeIndex.Status.Host, indexName);
|
||||
this._logger.LogDebug("Found host {0} for index {1}", pineconeIndex.Status.Host, indexName);
|
||||
|
||||
this._indexHostMapping.TryAdd(indexName, pineconeIndex.Status.Host);
|
||||
|
||||
|
||||
@@ -459,13 +459,14 @@ public sealed class QdrantVectorDbClient : IQdrantVectorDbClient
|
||||
HttpResponseMessage response = await this._httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
string responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
this._logger.LogTrace("Qdrant responded successfully");
|
||||
this._logger.LogDebug("Qdrant responded successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
this._logger.LogTrace("Qdrant responded with error");
|
||||
this._logger.LogWarning("Qdrant responded with error");
|
||||
}
|
||||
|
||||
return (response, responseContent);
|
||||
|
||||
@@ -104,7 +104,7 @@ public class WeaviateMemoryStore : IMemoryStore
|
||||
string className = ToWeaviateFriendlyClassName(collectionName);
|
||||
string description = ToWeaviateFriendlyClassDescription(collectionName);
|
||||
|
||||
this._logger.LogTrace("Creating collection: {0}, with class name: {1}", collectionName, className);
|
||||
this._logger.LogDebug("Creating collection: {0}, with class name: {1}", collectionName, className);
|
||||
|
||||
using HttpRequestMessage request = CreateClassSchemaRequest.Create(className, description).Build();
|
||||
|
||||
@@ -120,7 +120,7 @@ public class WeaviateMemoryStore : IMemoryStore
|
||||
$"Name conflict for collection: {collectionName} with class name: {className}");
|
||||
}
|
||||
|
||||
this._logger.LogTrace("Created collection: {0}, with class name: {1}", collectionName, className);
|
||||
this._logger.LogDebug("Created collection: {0}, with class name: {1}", collectionName, className);
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
@@ -135,7 +135,8 @@ public class WeaviateMemoryStore : IMemoryStore
|
||||
Verify.NotNullOrWhiteSpace(collectionName, "Collection name is empty");
|
||||
|
||||
string className = ToWeaviateFriendlyClassName(collectionName);
|
||||
this._logger.LogTrace("Does collection exist: {0}, with class name: {1}:", collectionName, className);
|
||||
|
||||
this._logger.LogDebug("Does collection exist: {0}, with class name: {1}:", collectionName, className);
|
||||
|
||||
using HttpRequestMessage request = GetClassRequest.Create(className).Build();
|
||||
|
||||
@@ -147,7 +148,7 @@ public class WeaviateMemoryStore : IMemoryStore
|
||||
bool exists = response.StatusCode != HttpStatusCode.NotFound;
|
||||
if (!exists)
|
||||
{
|
||||
this._logger.LogTrace("Collection: {0}, with class name: {1}, does not exist.", collectionName, className);
|
||||
this._logger.LogDebug("Collection: {0}, with class name: {1}, does not exist.", collectionName, className);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -174,7 +175,7 @@ public class WeaviateMemoryStore : IMemoryStore
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<string> GetCollectionsAsync([EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._logger.LogTrace("Listing collections");
|
||||
this._logger.LogDebug("Listing collections");
|
||||
|
||||
using HttpRequestMessage request = GetSchemaRequest.Create().Build();
|
||||
string responseContent;
|
||||
@@ -206,7 +207,8 @@ public class WeaviateMemoryStore : IMemoryStore
|
||||
Verify.NotNullOrWhiteSpace(collectionName, "Collection name is empty");
|
||||
|
||||
string className = ToWeaviateFriendlyClassName(collectionName);
|
||||
this._logger.LogTrace("Deleting collection: {0}, with class name: {1}", collectionName, className);
|
||||
|
||||
this._logger.LogDebug("Deleting collection: {0}, with class name: {1}", collectionName, className);
|
||||
|
||||
if (await this.DoesCollectionExistAsync(collectionName, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
@@ -237,7 +239,7 @@ public class WeaviateMemoryStore : IMemoryStore
|
||||
{
|
||||
Verify.NotNullOrWhiteSpace(collectionName, "Collection name is empty");
|
||||
|
||||
this._logger.LogTrace("Upsert vectors");
|
||||
this._logger.LogDebug("Upsert vectors");
|
||||
|
||||
string className = ToWeaviateFriendlyClassName(collectionName);
|
||||
BatchRequest requestBuilder = BatchRequest.Create(className);
|
||||
@@ -315,7 +317,7 @@ public class WeaviateMemoryStore : IMemoryStore
|
||||
embedding: new(weaviateObject.Vector ?? Array.Empty<float>()),
|
||||
metadata: ToMetadata(weaviateObject));
|
||||
|
||||
this._logger.LogTrace("Vector found with key: {0}", key);
|
||||
this._logger.LogDebug("Vector found with key: {0}", key);
|
||||
|
||||
return record;
|
||||
}
|
||||
@@ -345,7 +347,8 @@ public class WeaviateMemoryStore : IMemoryStore
|
||||
Verify.NotNull(key, "Key is NULL");
|
||||
|
||||
string className = ToWeaviateFriendlyClassName(collectionName);
|
||||
this._logger.LogTrace("Deleting vector with key: {0}, from collection {1}, with class name: {2}:", key, collectionName, className);
|
||||
|
||||
this._logger.LogDebug("Deleting vector with key: {0}, from collection {1}, with class name: {2}:", key, collectionName, className);
|
||||
|
||||
DeleteObjectRequest requestBuilder = new()
|
||||
{
|
||||
@@ -359,7 +362,8 @@ public class WeaviateMemoryStore : IMemoryStore
|
||||
{
|
||||
(HttpResponseMessage response, string _) = await this.ExecuteHttpRequestAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
this._logger.LogTrace("Vector deleted");
|
||||
|
||||
this._logger.LogDebug("Vector deleted");
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
@@ -382,8 +386,10 @@ public class WeaviateMemoryStore : IMemoryStore
|
||||
bool withEmbeddings = false,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._logger.LogTrace("Searching top {0} nearest vectors", limit);
|
||||
Verify.NotNull(embedding, "The given vector is NULL");
|
||||
|
||||
this._logger.LogDebug("Searching top {0} nearest vectors", limit);
|
||||
|
||||
string className = ToWeaviateFriendlyClassName(collectionName);
|
||||
|
||||
using HttpRequestMessage request = new CreateGraphRequest
|
||||
@@ -517,7 +523,9 @@ public class WeaviateMemoryStore : IMemoryStore
|
||||
|
||||
HttpResponseMessage response = await this._httpClient.SendAsync(request, cancel).ConfigureAwait(false);
|
||||
string? responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
this._logger.LogTrace("Weaviate responded with {0}", response.StatusCode);
|
||||
|
||||
this._logger.LogDebug("Weaviate responded with {0}", response.StatusCode);
|
||||
|
||||
return (response, responseContent);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.SemanticKernel.Planning.Sequential;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for planner that uses semantic function to create a sequential plan.
|
||||
/// </summary>
|
||||
public interface ISequentialPlanner
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a plan for a goal.
|
||||
/// </summary>
|
||||
/// <param name="goal">The goal to create a plan for.</param>
|
||||
/// <returns>The plan.</returns>
|
||||
/// <exception cref="PlanningException">Thrown when the plan cannot be created.</exception>
|
||||
Task<Plan> CreatePlanAsync(string goal);
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace Microsoft.SemanticKernel.Planning.Sequential;
|
||||
|
||||
/// <summary>
|
||||
/// Instrumented planner that uses semantic function to create a sequential plan.
|
||||
/// Captures planner-related logs and metrics.
|
||||
/// </summary>
|
||||
public sealed class InstrumentedSequentialPlanner : ISequentialPlanner
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialize a new instance of the <see cref="InstrumentedSequentialPlanner"/> class.
|
||||
/// </summary>
|
||||
/// <param name="planner">Instance of <see cref="ISequentialPlanner"/> to decorate.</param>
|
||||
/// <param name="logger">Optional logger.</param>
|
||||
public InstrumentedSequentialPlanner(
|
||||
ISequentialPlanner planner,
|
||||
ILogger? logger = null)
|
||||
{
|
||||
this._planner = planner;
|
||||
this._logger = logger ?? NullLogger.Instance;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Plan> CreatePlanAsync(string goal)
|
||||
{
|
||||
using var activity = s_activitySource.StartActivity("SequentialPlanner.CreatePlan");
|
||||
|
||||
this._logger.LogInformation("Plan creation started.");
|
||||
|
||||
// Sensitive data, logging as trace, disabled by default
|
||||
this._logger.LogTrace("Plan Goal: {Goal}", goal);
|
||||
|
||||
var stopwatch = new Stopwatch();
|
||||
|
||||
try
|
||||
{
|
||||
stopwatch.Start();
|
||||
|
||||
var plan = await this._planner.CreatePlanAsync(goal).ConfigureAwait(false);
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
this._logger.LogInformation("Plan creation status: {Status}", "Success");
|
||||
|
||||
this._logger.LogInformation("Created plan: \n {Plan}", plan.ToSafePlanString());
|
||||
|
||||
// Sensitive data, logging as trace, disabled by default
|
||||
this._logger.LogTrace("Created plan with details: \n {Plan}", plan.ToPlanString());
|
||||
|
||||
return plan;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this._logger.LogInformation("Plan creation status: {Status}", "Failed");
|
||||
this._logger.LogError(ex, "Plan creation exception details: {Message}", ex.Message);
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
this._logger.LogInformation("Plan creation finished in {ExecutionTime}ms.", stopwatch.ElapsedMilliseconds);
|
||||
|
||||
s_createPlanExecutionTime.Record(stopwatch.ElapsedMilliseconds);
|
||||
}
|
||||
}
|
||||
|
||||
#region private ================================================================================
|
||||
|
||||
private readonly ISequentialPlanner _planner;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Instance of <see cref="ActivitySource"/> for planner-related activities.
|
||||
/// </summary>
|
||||
private static ActivitySource s_activitySource = new(typeof(InstrumentedSequentialPlanner).FullName);
|
||||
|
||||
/// <summary>
|
||||
/// Instance of <see cref="Meter"/> for planner-related metrics.
|
||||
/// </summary>
|
||||
private static Meter s_meter = new(typeof(InstrumentedSequentialPlanner).FullName);
|
||||
|
||||
/// <summary>
|
||||
/// Instance of <see cref="Histogram{T}"/> to record plan creation execution time.
|
||||
/// </summary>
|
||||
private static Histogram<double> s_createPlanExecutionTime =
|
||||
s_meter.CreateHistogram<double>(
|
||||
name: "SK.SequentialPlanner.CreatePlan.ExecutionTime",
|
||||
unit: "ms",
|
||||
description: "Execution time of plan creation");
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace Microsoft.SemanticKernel.Planning;
|
||||
/// <summary>
|
||||
/// A planner that uses semantic function to create a sequential plan.
|
||||
/// </summary>
|
||||
public sealed class SequentialPlanner
|
||||
public sealed class SequentialPlanner : ISequentialPlanner
|
||||
{
|
||||
private const string StopSequence = "<!-- END -->";
|
||||
|
||||
@@ -49,12 +49,7 @@ public sealed class SequentialPlanner
|
||||
this._context = kernel.CreateNewContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a plan for a goal.
|
||||
/// </summary>
|
||||
/// <param name="goal">The goal to create a plan for.</param>
|
||||
/// <returns>The plan.</returns>
|
||||
/// <exception cref="PlanningException">Thrown when the plan cannot be created.</exception>
|
||||
/// <inheritdoc />
|
||||
public async Task<Plan> CreatePlanAsync(string goal)
|
||||
{
|
||||
if (string.IsNullOrEmpty(goal))
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.SemanticKernel.Planning.Sequential;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="SequentialPlanner"/> class.
|
||||
/// </summary>
|
||||
public static class SequentialPlannerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns decorated instance of <see cref="ISequentialPlanner"/> with enabled instrumentation.
|
||||
/// </summary>
|
||||
/// <param name="planner">Instance of <see cref="ISequentialPlanner"/> to decorate.</param>
|
||||
/// <param name="logger">Optional logger.</param>
|
||||
public static ISequentialPlanner WithInstrumentation(this ISequentialPlanner planner, ILogger? logger = null)
|
||||
{
|
||||
return new InstrumentedSequentialPlanner(planner, logger);
|
||||
}
|
||||
}
|
||||
@@ -25,11 +25,11 @@ internal sealed class EmailSkillFake
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
logger.LogDebug("Returning hard coded email for {0}", input);
|
||||
logger.LogTrace("Returning hard coded email for {0}", input);
|
||||
return Task.FromResult("[email protected]");
|
||||
}
|
||||
|
||||
logger.LogDebug("Returning dynamic email for {0}", input);
|
||||
logger.LogTrace("Returning dynamic email for {0}", input);
|
||||
return Task.FromResult($"{input}@example.com");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.SemanticKernel.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for common telemetry events to track actions across the semantic kernel.
|
||||
/// </summary>
|
||||
public interface ITelemetryService
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a telemetry event when a skill function is executed.
|
||||
/// </summary>
|
||||
/// <param name="skillName">Name of the skill</param>
|
||||
/// <param name="functionName">Skill function name</param>
|
||||
/// <param name="success">If the skill executed successfully</param>
|
||||
void TrackSkillFunction(string skillName, string functionName, bool success);
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
@@ -231,6 +233,7 @@ public sealed class Plan : ISKFunction
|
||||
kernel.Skills,
|
||||
kernel.Log,
|
||||
cancellationToken);
|
||||
|
||||
return this.InvokeNextStepAsync(context);
|
||||
}
|
||||
|
||||
@@ -250,7 +253,13 @@ public sealed class Plan : ISKFunction
|
||||
var functionVariables = this.GetNextStepVariables(context.Variables, step);
|
||||
|
||||
// Execute the step
|
||||
var functionContext = new SKContext(functionVariables, context.Memory, context.Skills, context.Log, context.CancellationToken);
|
||||
var functionContext = new SKContext(
|
||||
functionVariables,
|
||||
context.Memory,
|
||||
context.Skills,
|
||||
context.Log,
|
||||
context.CancellationToken);
|
||||
|
||||
var result = await step.InvokeAsync(functionContext).ConfigureAwait(false);
|
||||
var resultValue = result.Result.Trim();
|
||||
|
||||
@@ -328,13 +337,10 @@ public sealed class Plan : ISKFunction
|
||||
{
|
||||
if (this.Function is not null)
|
||||
{
|
||||
var result = await this.Function.InvokeAsync(context, settings).ConfigureAwait(false);
|
||||
var result = await this.InstrumentedInvokeAsync(this.Function, context, settings).ConfigureAwait(false);
|
||||
|
||||
if (result.ErrorOccurred)
|
||||
{
|
||||
result.Log.LogError(
|
||||
result.LastException,
|
||||
"Something went wrong in plan step {0}.{1}:'{2}'", this.SkillName, this.Name, context.LastErrorDescription);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -587,6 +593,57 @@ public sealed class Plan : ISKFunction
|
||||
return stepVariables;
|
||||
}
|
||||
|
||||
private async Task<SKContext> InstrumentedInvokeAsync(
|
||||
ISKFunction function,
|
||||
SKContext context,
|
||||
CompleteRequestSettings? settings = null)
|
||||
{
|
||||
using var activity = s_activitySource.StartActivity($"{this.SkillName}.{this.Name}");
|
||||
|
||||
context.Log.LogInformation("{SkillName}.{StepName}: Step execution started.", this.SkillName, this.Name);
|
||||
|
||||
var stopwatch = new Stopwatch();
|
||||
|
||||
stopwatch.Start();
|
||||
|
||||
var result = await function.InvokeAsync(context, settings).ConfigureAwait(false);
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
if (!result.ErrorOccurred)
|
||||
{
|
||||
context.Log.LogInformation(
|
||||
"{SkillName}.{StepName}: Step execution status: {Status}.",
|
||||
this.SkillName, this.Name, "Success");
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Log.LogInformation(
|
||||
"{SkillName}.{StepName}: Step execution status: {Status}.",
|
||||
this.SkillName, this.Name, "Failed");
|
||||
|
||||
context.Log.LogError(
|
||||
result.LastException,
|
||||
"Something went wrong in plan step {SkillName}.{StepName}:'{ErrorDescription}'",
|
||||
this.SkillName, this.Name, context.LastErrorDescription);
|
||||
}
|
||||
|
||||
context.Log.LogInformation(
|
||||
"{SkillName}.{StepName}: Step execution finished in {ExecutionTime}ms.",
|
||||
this.SkillName, this.Name, stopwatch.ElapsedMilliseconds);
|
||||
|
||||
var stepExecutionTimeMetricName = string.Format(CultureInfo.InvariantCulture, StepExecutionTimeMetricFormat, this.SkillName, this.Name);
|
||||
|
||||
var stepExecutionTimeHistogram = s_meter.CreateHistogram<double>(
|
||||
name: stepExecutionTimeMetricName,
|
||||
unit: "ms",
|
||||
description: "Plan step execution time");
|
||||
|
||||
stepExecutionTimeHistogram.Record(stopwatch.ElapsedMilliseconds);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void SetFunction(ISKFunction function)
|
||||
{
|
||||
this.Function = function;
|
||||
@@ -625,4 +682,20 @@ public sealed class Plan : ISKFunction
|
||||
return display;
|
||||
}
|
||||
}
|
||||
|
||||
#region Instrumentation
|
||||
|
||||
private const string StepExecutionTimeMetricFormat = "SK.{0}.{1}.ExecutionTime";
|
||||
|
||||
/// <summary>
|
||||
/// Instance of <see cref="ActivitySource"/> for plan-related activities.
|
||||
/// </summary>
|
||||
private static ActivitySource s_activitySource = new(typeof(Plan).FullName);
|
||||
|
||||
/// <summary>
|
||||
/// Instance of <see cref="Meter"/> for planner-related metrics.
|
||||
/// </summary>
|
||||
private static Meter s_meter = new(typeof(Plan).FullName);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.SemanticKernel.Planning;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="Plan"/> type.
|
||||
/// </summary>
|
||||
public static class PlanExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs string representation of <see cref="Plan"/> without sensitive data.
|
||||
/// </summary>
|
||||
/// <param name="plan">Instance of <see cref="Plan"/> for string construction.</param>
|
||||
/// <param name="indent">Optional indentation.</param>
|
||||
public static string ToSafePlanString(this Plan plan, string indent = " ")
|
||||
{
|
||||
string planString = string.Join("\n", plan.Steps.Select(step =>
|
||||
{
|
||||
if (step.Steps.Count == 0)
|
||||
{
|
||||
string skillName = step.SkillName;
|
||||
string stepName = step.Name;
|
||||
|
||||
return $"{indent}{indent}- {string.Join(".", skillName, stepName)}";
|
||||
}
|
||||
|
||||
return step.ToSafePlanString(indent + indent);
|
||||
}));
|
||||
|
||||
return planString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs string representation of <see cref="Plan"/>.
|
||||
/// </summary>
|
||||
/// <param name="plan">Instance of <see cref="Plan"/> for string construction.</param>
|
||||
/// <param name="indent">Optional indentation.</param>
|
||||
public static string ToPlanString(this Plan plan, string indent = " ")
|
||||
{
|
||||
string planString = string.Join("\n", plan.Steps.Select(step =>
|
||||
{
|
||||
if (step.Steps.Count == 0)
|
||||
{
|
||||
string skillName = step.SkillName;
|
||||
string stepName = step.Name;
|
||||
|
||||
string parameters = string.Join(" ", step.Parameters.Select(param => $"{param.Key}='{param.Value}'"));
|
||||
if (!string.IsNullOrEmpty(parameters))
|
||||
{
|
||||
parameters = $" {parameters}";
|
||||
}
|
||||
|
||||
string? outputs = step.Outputs.FirstOrDefault();
|
||||
if (!string.IsNullOrEmpty(outputs))
|
||||
{
|
||||
outputs = $" => {outputs}";
|
||||
}
|
||||
|
||||
return $"{indent}{indent}- {string.Join(".", skillName, stepName)}{parameters}{outputs}";
|
||||
}
|
||||
|
||||
return step.ToPlanString(indent + indent);
|
||||
}));
|
||||
|
||||
return planString;
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,10 @@
|
||||
<InternalsVisibleTo Include="SemanticKernel.UnitTests" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Diagnostics.DiagnosticSource" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SemanticKernel.Abstractions\SemanticKernel.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -119,8 +119,9 @@ internal sealed class CodeBlock : Block, ICodeRendering
|
||||
// If the code syntax is {{functionName 'value'}} use "value" instead of $input
|
||||
if (this._tokens.Count > 1)
|
||||
{
|
||||
// TODO: PII
|
||||
// Sensitive data, logging as trace, disabled by default
|
||||
this.Log.LogTrace("Passing variable/value: `{0}`", this._tokens[1].Content);
|
||||
|
||||
string input = ((ITextRendering)this._tokens[1]).Render(contextClone.Variables);
|
||||
// Keep previous trust information when updating the input
|
||||
contextClone.Variables.Update(input);
|
||||
|
||||
@@ -91,8 +91,9 @@ public class PromptTemplateEngine : IPromptTemplateEngine
|
||||
result.Append(await t.ConfigureAwait(false));
|
||||
}
|
||||
|
||||
// TODO: remove PII, allow tracing prompts differently
|
||||
this._log.LogDebug("Rendered prompt: {0}", result);
|
||||
// Sensitive data, logging as trace, disabled by default
|
||||
this._log.LogTrace("Rendered prompt: {0}", result);
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ public sealed class TextMemorySkill
|
||||
Verify.NotNullOrWhiteSpace(collection, $"{nameof(context)}.{nameof(context.Variables)}[{CollectionParam}]");
|
||||
Verify.NotNullOrWhiteSpace(key, $"{nameof(context)}.{nameof(context.Variables)}[{KeyParam}]");
|
||||
|
||||
context.Log.LogTrace("Recalling memory with key '{0}' from collection '{1}'", key, collection);
|
||||
context.Log.LogDebug("Recalling memory with key '{0}' from collection '{1}'", key, collection);
|
||||
|
||||
var memory = await context.Memory.GetAsync(collection, key).ConfigureAwait(false);
|
||||
|
||||
@@ -105,7 +105,7 @@ public sealed class TextMemorySkill
|
||||
relevance ??= DefaultRelevance;
|
||||
limit ??= DefaultLimit;
|
||||
|
||||
context.Log.LogTrace("Searching memories in collection '{0}', relevance '{1}'", collection, relevance);
|
||||
context.Log.LogDebug("Searching memories in collection '{0}', relevance '{1}'", collection, relevance);
|
||||
|
||||
// Search memory
|
||||
List<MemoryQueryResult> memories = await context.Memory
|
||||
@@ -119,7 +119,7 @@ public sealed class TextMemorySkill
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
context.Log.LogTrace("Done looking for memories in collection '{0}')", collection);
|
||||
context.Log.LogDebug("Done looking for memories in collection '{0}')", collection);
|
||||
return limit == 1 ? memories[0].Metadata.Text : JsonSerializer.Serialize(memories.Select(x => x.Metadata.Text));
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ public sealed class TextMemorySkill
|
||||
Verify.NotNullOrWhiteSpace(collection, $"{nameof(context)}.{nameof(context.Variables)}[{CollectionParam}]");
|
||||
Verify.NotNullOrWhiteSpace(key, $"{nameof(context)}.{nameof(context.Variables)}[{KeyParam}]");
|
||||
|
||||
context.Log.LogTrace("Saving memory to collection '{0}'", collection);
|
||||
context.Log.LogDebug("Saving memory to collection '{0}'", collection);
|
||||
|
||||
await context.Memory.SaveInformationAsync(collection, text: text, id: key).ConfigureAwait(false);
|
||||
}
|
||||
@@ -169,7 +169,7 @@ public sealed class TextMemorySkill
|
||||
Verify.NotNullOrWhiteSpace(collection, $"{nameof(context)}.{nameof(context.Variables)}[{CollectionParam}]");
|
||||
Verify.NotNullOrWhiteSpace(key, $"{nameof(context)}.{nameof(context.Variables)}[{KeyParam}]");
|
||||
|
||||
context.Log.LogTrace("Removing memory from collection '{0}'", collection);
|
||||
context.Log.LogDebug("Removing memory from collection '{0}'", collection);
|
||||
|
||||
await context.Memory.RemoveAsync(collection, key).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ public sealed class DocumentSkill
|
||||
[Description("Path to the file to read")] string filePath,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._logger.LogInformation("Reading text from {0}", filePath);
|
||||
this._logger.LogDebug("Reading text from {0}", filePath);
|
||||
using var stream = await this._fileSystemConnector.GetFileContentStreamAsync(filePath, cancellationToken).ConfigureAwait(false);
|
||||
return this._documentConnector.ReadText(stream);
|
||||
}
|
||||
@@ -97,17 +97,17 @@ public sealed class DocumentSkill
|
||||
// If the document already exists, open it. If not, create it.
|
||||
if (await this._fileSystemConnector.FileExistsAsync(filePath, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
this._logger.LogInformation("Writing text to file {0}", filePath);
|
||||
this._logger.LogDebug("Writing text to file {0}", filePath);
|
||||
using Stream stream = await this._fileSystemConnector.GetWriteableFileStreamAsync(filePath, cancellationToken).ConfigureAwait(false);
|
||||
this._documentConnector.AppendText(stream, text);
|
||||
}
|
||||
else
|
||||
{
|
||||
this._logger.LogInformation("File does not exist. Creating file at {0}", filePath);
|
||||
this._logger.LogDebug("File does not exist. Creating file at {0}", filePath);
|
||||
using Stream stream = await this._fileSystemConnector.CreateFileAsync(filePath, cancellationToken).ConfigureAwait(false);
|
||||
this._documentConnector.Initialize(stream);
|
||||
|
||||
this._logger.LogInformation("Writing text to {0}", filePath);
|
||||
this._logger.LogDebug("Writing text to {0}", filePath);
|
||||
this._documentConnector.AppendText(stream, text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,8 @@ public sealed class CalendarSkill
|
||||
Attendees = attendees is not null ? attendees.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries) : Enumerable.Empty<string>(),
|
||||
};
|
||||
|
||||
this._logger.LogInformation("Adding calendar event '{0}'", calendarEvent.Subject);
|
||||
// Sensitive data, logging as trace, disabled by default
|
||||
this._logger.LogTrace("Adding calendar event '{0}'", calendarEvent.Subject);
|
||||
await this._connector.AddEventAsync(calendarEvent).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -124,7 +125,7 @@ public sealed class CalendarSkill
|
||||
[Description("Optional number of events to skip before retrieving results.")] int? skip = 0,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._logger.LogInformation("Getting calendar events with query options top: '{0}', skip:'{1}'.", maxResults, skip);
|
||||
this._logger.LogDebug("Getting calendar events with query options top: '{0}', skip:'{1}'.", maxResults, skip);
|
||||
|
||||
const string SelectString = "start,subject,organizer,location";
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ public sealed class CloudDriveSkill
|
||||
|
||||
using StreamReader sr = new(fileContentStream);
|
||||
string content = await sr.ReadToEndAsync().ConfigureAwait(false);
|
||||
this._logger.LogDebug("File content: {0}", content);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,8 @@ public sealed class EmailSkill
|
||||
throw new ArgumentException("Variable was null or whitespace", nameof(subject));
|
||||
}
|
||||
|
||||
this._logger.LogInformation("Sending email to '{0}' with subject '{1}'", recipients, subject);
|
||||
// Sensitive data, logging as trace, disabled by default
|
||||
this._logger.LogTrace("Sending email to '{0}' with subject '{1}'", recipients, subject);
|
||||
string[] recipientList = recipients.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
await this._connector.SendEmailAsync(subject, content, recipientList, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
@@ -109,7 +110,7 @@ public sealed class EmailSkill
|
||||
[Description("Optional number of message to skip before retrieving results.")] int? skip = 0,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._logger.LogInformation("Getting email messages with query options top: '{0}', skip:'{1}'.", maxResults, skip);
|
||||
this._logger.LogDebug("Getting email messages with query options top: '{0}', skip:'{1}'.", maxResults, skip);
|
||||
|
||||
const string SelectString = "subject,receivedDateTime,bodyPreview";
|
||||
|
||||
|
||||
@@ -90,7 +90,9 @@ public sealed class TaskListSkill
|
||||
title: title,
|
||||
reminder: reminder);
|
||||
|
||||
this._logger.LogInformation("Adding task '{0}' to task list '{1}'", task.Title, defaultTaskList.Name);
|
||||
// Sensitive data, logging as trace, disabled by default
|
||||
this._logger.LogTrace("Adding task '{0}' to task list '{1}'", task.Title, defaultTaskList.Name);
|
||||
|
||||
await this._connector.AddTaskAsync(defaultTaskList.Id, task, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -335,7 +335,7 @@ public static class KernelOpenApiExtensions
|
||||
result += CultureInfo.CurrentCulture.TextInfo.ToTitleCase(formattedToken.ToLower(CultureInfo.CurrentCulture));
|
||||
}
|
||||
|
||||
logger.LogInformation("Operation name \"{0}\" converted to \"{1}\" to comply with SK Function name requirements. Use \"{2}\" when invoking function.", operationId, result, result);
|
||||
logger.LogDebug("Operation name \"{0}\" converted to \"{1}\" to comply with SK Function name requirements. Use \"{2}\" when invoking function.", operationId, result, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -69,6 +69,8 @@ public sealed class BingConnector : IWebSearchEngineConnector
|
||||
this._logger.LogDebug("Response received: {0}", response.StatusCode);
|
||||
|
||||
string json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
// Sensitive data, logging as trace, disabled by default
|
||||
this._logger.LogTrace("Response content received: {0}", json);
|
||||
|
||||
BingSearchResponse? data = JsonSerializer.Deserialize<BingSearchResponse>(json);
|
||||
|
||||
@@ -213,11 +213,11 @@ internal sealed class Program
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
logger.LogDebug("Unable to extract JSON from planner response, it is likely not from an OpenAPI skill.");
|
||||
logger.LogWarning("Unable to extract JSON from planner response, it is likely not from an OpenAPI skill.");
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
logger.LogDebug("Unable to extract JSON from planner response, it may already be proper JSON.");
|
||||
logger.LogWarning("Unable to extract JSON from planner response, it may already be proper JSON.");
|
||||
}
|
||||
|
||||
json = string.Empty;
|
||||
|
||||
Reference in New Issue
Block a user