.NET: Add OpenTelemetry Chat Client to harness stack (#6961)
* Add OpenTelemetry Chat Client to harness stack * Address PR comments
This commit is contained in:
@@ -190,6 +190,11 @@ public sealed class HarnessAgent : DelegatingAIAgent
|
||||
}
|
||||
}
|
||||
|
||||
CompactionProvider? compactionProvider = compactionStrategy is not null
|
||||
? new CompactionProvider(compactionStrategy, loggerFactory: loggerFactory)
|
||||
: null;
|
||||
|
||||
// Build ChatHistoryProvider
|
||||
ChatHistoryProvider chatHistoryProvider = options?.ChatHistoryProvider
|
||||
?? (compactionStrategy is not null
|
||||
? new InMemoryChatHistoryProvider(new InMemoryChatHistoryProviderOptions
|
||||
@@ -198,6 +203,7 @@ public sealed class HarnessAgent : DelegatingAIAgent
|
||||
})
|
||||
: new InMemoryChatHistoryProvider());
|
||||
|
||||
// Build instructions
|
||||
string harnessInstructions = options?.HarnessInstructions ?? DefaultInstructions;
|
||||
string? agentInstructions = options?.ChatOptions?.Instructions;
|
||||
|
||||
@@ -209,14 +215,11 @@ public sealed class HarnessAgent : DelegatingAIAgent
|
||||
(false, false) => $"{harnessInstructions}\n\n{agentInstructions}",
|
||||
};
|
||||
|
||||
// Build Chat Options & context providers
|
||||
ChatOptions chatOptions = BuildChatOptions(options, instructions, options?.MaxOutputTokens);
|
||||
|
||||
CompactionProvider? compactionProvider = compactionStrategy is not null
|
||||
? new CompactionProvider(compactionStrategy, loggerFactory: loggerFactory)
|
||||
: null;
|
||||
|
||||
IEnumerable<AIContextProvider> contextProviders = BuildContextProviders(options, loggerFactory);
|
||||
|
||||
// Build ChatClient stack
|
||||
ChatClientBuilder chatClientBuilder = chatClient.AsBuilder();
|
||||
|
||||
if (options?.DisableNonApprovalRequiredFunctionBypassing is not true)
|
||||
@@ -224,7 +227,7 @@ public sealed class HarnessAgent : DelegatingAIAgent
|
||||
chatClientBuilder.UseNonApprovalRequiredFunctionBypassing();
|
||||
}
|
||||
|
||||
ChatClientBuilder pipeline = chatClientBuilder
|
||||
chatClientBuilder = chatClientBuilder
|
||||
.UseFunctionInvocation(loggerFactory, configure: options?.MaximumIterationsPerRequest is int maxIterations
|
||||
? ficc => ficc.MaximumIterationsPerRequest = maxIterations
|
||||
: null)
|
||||
@@ -233,10 +236,16 @@ public sealed class HarnessAgent : DelegatingAIAgent
|
||||
|
||||
if (compactionProvider is not null)
|
||||
{
|
||||
pipeline = pipeline.UseAIContextProviders(compactionProvider);
|
||||
chatClientBuilder = chatClientBuilder.UseAIContextProviders(compactionProvider);
|
||||
}
|
||||
|
||||
return pipeline
|
||||
if (options?.DisableOpenTelemetry is not true)
|
||||
{
|
||||
chatClientBuilder = chatClientBuilder.UseOpenTelemetry(sourceName: options?.OpenTelemetrySourceName);
|
||||
}
|
||||
|
||||
// Build Chat Client Agent
|
||||
return chatClientBuilder
|
||||
.BuildAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Id = options?.Id,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -844,6 +846,90 @@ public class HarnessAgentTests
|
||||
Assert.NotNull(agent.GetService<OpenTelemetryAgent>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that the inner agent's ChatClient pipeline includes OpenTelemetryChatClient when
|
||||
/// OpenTelemetry is enabled, so model calls are traced in addition to the agent-level
|
||||
/// <see cref="OpenTelemetryAgent"/> wrapper.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Pipeline_IncludesOpenTelemetryChatClientWhenEnabled()
|
||||
{
|
||||
// Arrange
|
||||
var chatClient = new Mock<IChatClient>().Object;
|
||||
var options = CreateAllDisabledOptions();
|
||||
options.DisableOpenTelemetry = false;
|
||||
|
||||
// Act
|
||||
var agent = new HarnessAgent(chatClient, options);
|
||||
var innerAgent = agent.GetService<ChatClientAgent>();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(innerAgent);
|
||||
Assert.NotNull(innerAgent!.ChatClient.GetService<OpenTelemetryChatClient>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that the inner agent's ChatClient pipeline excludes OpenTelemetryChatClient when
|
||||
/// OpenTelemetry is disabled.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Pipeline_ExcludesOpenTelemetryChatClientWhenDisabled()
|
||||
{
|
||||
// Arrange
|
||||
var chatClient = new Mock<IChatClient>().Object;
|
||||
|
||||
// Act
|
||||
var agent = new HarnessAgent(chatClient, CreateAllDisabledOptions());
|
||||
var innerAgent = agent.GetService<ChatClientAgent>();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(innerAgent);
|
||||
Assert.Null(innerAgent!.ChatClient.GetService<OpenTelemetryChatClient>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that the chat-client-level OpenTelemetry instrumentation emits a chat span under the
|
||||
/// configured <see cref="HarnessAgentOptions.OpenTelemetrySourceName"/>, proving both that the
|
||||
/// ChatClient pipeline is instrumented and that the source name is propagated.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task OpenTelemetry_ChatClientEmitsChatSpanUnderConfiguredSourceNameAsync()
|
||||
{
|
||||
// Arrange
|
||||
var sourceName = Guid.NewGuid().ToString();
|
||||
var activities = new ConcurrentQueue<Activity>();
|
||||
using var listener = new ActivityListener
|
||||
{
|
||||
ShouldListenTo = source => source.Name == sourceName,
|
||||
Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllDataAndRecorded,
|
||||
ActivityStopped = activities.Enqueue,
|
||||
};
|
||||
ActivitySource.AddActivityListener(listener);
|
||||
|
||||
var mockClient = new Mock<IChatClient>();
|
||||
mockClient
|
||||
.Setup(c => c.GetResponseAsync(
|
||||
It.IsAny<IEnumerable<ChatMessage>>(),
|
||||
It.IsAny<ChatOptions>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new ChatResponse(new ChatMessage(ChatRole.Assistant, "Hello!")));
|
||||
|
||||
var options = CreateAllDisabledOptions();
|
||||
options.DisableOpenTelemetry = false;
|
||||
options.OpenTelemetrySourceName = sourceName;
|
||||
|
||||
var agent = new HarnessAgent(mockClient.Object, options);
|
||||
var session = await agent.CreateSessionAsync();
|
||||
|
||||
// Act
|
||||
await agent.RunAsync([new ChatMessage(ChatRole.User, "Hi")], session);
|
||||
|
||||
// Assert
|
||||
Assert.Contains(
|
||||
activities,
|
||||
a => string.Equals(a.GetTagItem("gen_ai.operation.name") as string, "chat", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Feature: WebSearch
|
||||
|
||||
Reference in New Issue
Block a user