Skip to content

Commit 30e1cb1

Browse files
committed
Address PR feedback
1 parent 1a7d670 commit 30e1cb1

File tree

4 files changed

+78
-13
lines changed

4 files changed

+78
-13
lines changed

src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,15 @@ public int? MaximumIterationsPerRequest
178178
}
179179
}
180180

181+
/// <summary>
182+
/// Gets or sets a value indicating whether <see cref="Activity"/>s should be used to
183+
/// provide telemetry for function invocation.
184+
/// </summary>
185+
/// <remarks>
186+
/// The default value is <see langword="true"/>.
187+
/// </remarks>
188+
public bool EnableTelemetry { get; set; } = true;
189+
181190
/// <inheritdoc/>
182191
public override async Task<ChatCompletion> CompleteAsync(IList<ChatMessage> chatMessages, ChatOptions? options = null, CancellationToken cancellationToken = default)
183192
{
@@ -576,7 +585,7 @@ FunctionResultContent CreateFunctionResultContent(FunctionInvocationResult resul
576585
{
577586
_ = Throw.IfNull(context);
578587

579-
using Activity? activity = _activitySource.StartActivity(context.Function.Metadata.Name);
588+
using Activity? activity = EnableTelemetry ? _activitySource.StartActivity(context.Function.Metadata.Name) : null;
580589

581590
long startingTimestamp = 0;
582591
if (_logger.IsEnabled(LogLevel.Debug))
@@ -601,8 +610,8 @@ FunctionResultContent CreateFunctionResultContent(FunctionInvocationResult resul
601610
{
602611
if (activity is not null)
603612
{
604-
activity.SetTag("error.type", e.GetType().FullName);
605-
activity.SetStatus(ActivityStatusCode.Error, e.Message);
613+
_ = activity.SetTag("error.type", e.GetType().FullName)
614+
.SetStatus(ActivityStatusCode.Error, e.Message);
606615
}
607616

608617
if (e is OperationCanceledException)
@@ -620,12 +629,7 @@ FunctionResultContent CreateFunctionResultContent(FunctionInvocationResult resul
620629
{
621630
if (_logger.IsEnabled(LogLevel.Debug))
622631
{
623-
TimeSpan elapsed =
624-
#if NET
625-
Stopwatch.GetElapsedTime(startingTimestamp);
626-
#else
627-
new TimeSpan((long)((Stopwatch.GetTimestamp() - startingTimestamp) * ((double)TimeSpan.TicksPerSecond / Stopwatch.Frequency)));
628-
#endif
632+
TimeSpan elapsed = GetElapsedTime(startingTimestamp);
629633

630634
if (result is not null && _logger.IsEnabled(LogLevel.Trace))
631635
{
@@ -641,6 +645,13 @@ FunctionResultContent CreateFunctionResultContent(FunctionInvocationResult resul
641645
return result;
642646
}
643647

648+
private static TimeSpan GetElapsedTime(long startingTimestamp) =>
649+
#if NET
650+
Stopwatch.GetElapsedTime(startingTimestamp);
651+
#else
652+
new((long)((Stopwatch.GetTimestamp() - startingTimestamp) * ((double)TimeSpan.TicksPerSecond / Stopwatch.Frequency)));
653+
#endif
654+
644655
[LoggerMessage(LogLevel.Debug, "Invoking {MethodName}.", SkipEnabledCheck = true)]
645656
private partial void LogInvoking(string methodName);
646657

src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClientBuilderExtensions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5+
using Microsoft.Extensions.DependencyInjection;
56
using Microsoft.Extensions.Logging;
67
using Microsoft.Shared.Diagnostics;
78

@@ -29,6 +30,8 @@ public static ChatClientBuilder UseFunctionInvocation(
2930

3031
return builder.Use((services, innerClient) =>
3132
{
33+
loggerFactory ??= services.GetService<ILoggerFactory>();
34+
3235
var chatClient = new FunctionInvokingChatClient(innerClient, loggerFactory?.CreateLogger(typeof(FunctionInvokingChatClient)));
3336
configure?.Invoke(chatClient);
3437
return chatClient;
Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
#pragma warning disable CA1031 // Do not catch general exception types
5+
#pragma warning disable S108 // Nested blocks of code should not be left empty
6+
#pragma warning disable S2486 // Generic exceptions should not be ignored
7+
48
using System.Text.Json;
59

610
namespace Microsoft.Extensions.AI;
@@ -9,16 +13,22 @@ namespace Microsoft.Extensions.AI;
913
internal static class LoggingHelpers
1014
{
1115
/// <summary>Serializes <paramref name="value"/> as JSON for logging purposes.</summary>
12-
public static string AsJson<T>(T value, JsonSerializerOptions? options = null)
16+
public static string AsJson<T>(T value, JsonSerializerOptions? options)
1317
{
1418
if (options?.TryGetTypeInfo(typeof(T), out var typeInfo) is true ||
1519
AIJsonUtilities.DefaultOptions.TryGetTypeInfo(typeof(T), out typeInfo))
1620
{
17-
return JsonSerializer.Serialize(value, typeInfo);
21+
try
22+
{
23+
return JsonSerializer.Serialize(value, typeInfo);
24+
}
25+
catch
26+
{
27+
}
1828
}
1929

20-
// Unable to get a type info for the value, so return an empty JSON object.
21-
// We do not want lack of type info to disrupt application behavior with exceptions.
30+
// If we're unable to get a type info for the value, or if we fail to serialize,
31+
// return an empty JSON object. We do not want lack of type info to disrupt application behavior with exceptions.
2232
return "{}";
2333
}
2434
}

test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientTests.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Diagnostics;
67
using System.Linq;
78
using System.Threading;
89
using System.Threading.Tasks;
910
using Microsoft.Extensions.DependencyInjection;
1011
using Microsoft.Extensions.Logging;
12+
using OpenTelemetry.Trace;
1113
using Xunit;
1214

1315
namespace Microsoft.Extensions.AI;
@@ -345,6 +347,45 @@ await InvokeAndAssertAsync(options, [
345347
}
346348
}
347349

350+
[Theory]
351+
[InlineData(false)]
352+
[InlineData(true)]
353+
public async Task FunctionInvocationTrackedWithActivity(bool enableTelemetry)
354+
{
355+
var activities = new List<Activity>();
356+
using var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder()
357+
.AddSource("Microsoft.Extensions.AI.*")
358+
.AddInMemoryExporter(activities)
359+
.Build();
360+
361+
var options = new ChatOptions
362+
{
363+
Tools = [AIFunctionFactory.Create(() => "Result 1", "Func1")]
364+
};
365+
366+
await InvokeAndAssertAsync(options, [
367+
new ChatMessage(ChatRole.User, "hello"),
368+
new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("callId1", "Func1", new Dictionary<string, object?> { ["arg1"] = "value1" })]),
369+
new ChatMessage(ChatRole.Tool, [new FunctionResultContent("callId1", "Func1", result: "Result 1")]),
370+
new ChatMessage(ChatRole.Assistant, "world"),
371+
], configurePipeline: b => b.Use(c =>
372+
new FunctionInvokingChatClient(c) { EnableTelemetry = enableTelemetry }));
373+
374+
if (enableTelemetry)
375+
{
376+
var activity = Assert.Single(activities);
377+
378+
Assert.NotNull(activity.Id);
379+
Assert.NotEmpty(activity.Id);
380+
381+
Assert.Equal("Func1", activity.DisplayName);
382+
}
383+
else
384+
{
385+
Assert.Empty(activities);
386+
}
387+
}
388+
348389
private static async Task<List<ChatMessage>> InvokeAndAssertAsync(
349390
ChatOptions options,
350391
List<ChatMessage> plan,

0 commit comments

Comments
 (0)