diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs index a7a209e2586..69de81f564f 100644 --- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs @@ -191,6 +191,10 @@ public override async Task CompleteAsync(IList chat { _ = Throw.IfNull(chatMessages); + // A single request into this CompleteAsync may result in multiple requests to the inner client. + // Create an activity to group them together for better observability. + using Activity? activity = _activitySource?.StartActivity(nameof(FunctionInvokingChatClient)); + ChatCompletion? response = null; HashSet? messagesToRemove = null; HashSet? contentsToRemove = null; @@ -307,6 +311,10 @@ public override async IAsyncEnumerable CompleteSt { _ = Throw.IfNull(chatMessages); + // A single request into this CompleteStreamingAsync may result in multiple requests to the inner client. + // Create an activity to group them together for better observability. + using Activity? activity = _activitySource?.StartActivity(nameof(FunctionInvokingChatClient)); + HashSet? messagesToRemove = null; List functionCallContents = []; int? choice; @@ -349,6 +357,7 @@ public override async IAsyncEnumerable CompleteSt } yield return update; + Activity.Current = activity; // workaround for https://github.com/dotnet/runtime/issues/47802 } // If there are no tools to call, or for any other reason we should stop, return the response. diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs index 9da805932f2..aabc8078609 100644 --- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs @@ -197,6 +197,7 @@ public override async IAsyncEnumerable CompleteSt trackedUpdates.Add(update); yield return update; + Activity.Current = activity; // workaround for https://github.com/dotnet/runtime/issues/47802 } } finally diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientTests.cs index 86369edbc75..fdf2b4ecd66 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientTests.cs @@ -485,7 +485,14 @@ async Task InvokeAsync(Func work) Assert.Collection(activities, activity => Assert.Equal("chat", activity.DisplayName), activity => Assert.Equal("Func1", activity.DisplayName), - activity => Assert.Equal("chat", activity.DisplayName)); + activity => Assert.Equal("chat", activity.DisplayName), + activity => Assert.Equal(nameof(FunctionInvokingChatClient), activity.DisplayName)); + + for (int i = 0; i < activities.Count - 1; i++) + { + // Activities are exported in the order of completion, so all except the last are children of the last (i.e., outer) + Assert.Same(activities[activities.Count - 1], activities[i].Parent); + } } else {