mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Update sample to also include function calling telemetry (#577)
This commit is contained in:
committed by
GitHub
Unverified
parent
cf0e779638
commit
19210fc5c9
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Metrics;
|
||||
using Azure.AI.OpenAI;
|
||||
@@ -8,13 +9,14 @@ using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenAI;
|
||||
using OpenTelemetry;
|
||||
using OpenTelemetry.Logs;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OpenTelemetry.Resources;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
#region Setup Telemetry
|
||||
|
||||
const string SourceName = "OpenTelemetryAspire.ConsoleApp";
|
||||
const string ServiceName = "AgentOpenTelemetry";
|
||||
|
||||
@@ -24,7 +26,7 @@ AppContext.SetSwitch("Microsoft.Extensions.AI.Agents.EnableTelemetry", true);
|
||||
// Configure OpenTelemetry for Aspire dashboard
|
||||
var otlpEndpoint = Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318";
|
||||
|
||||
// Create a resource to identify this service (like Python example)
|
||||
// Create a resource to identify this service
|
||||
var resource = ResourceBuilder.CreateDefault()
|
||||
.AddService(ServiceName, serviceVersion: "1.0.0")
|
||||
.AddAttributes(new Dictionary<string, object>
|
||||
@@ -40,23 +42,17 @@ using var tracerProvider = Sdk.CreateTracerProviderBuilder()
|
||||
.AddSource(SourceName) // Our custom activity source
|
||||
.AddSource("Microsoft.Extensions.AI.Agents") // Agent Framework telemetry
|
||||
.AddHttpClientInstrumentation() // Capture HTTP calls to OpenAI
|
||||
.AddOtlpExporter(options =>
|
||||
{
|
||||
options.Endpoint = new Uri(otlpEndpoint);
|
||||
})
|
||||
.AddOtlpExporter(options => { options.Endpoint = new Uri(otlpEndpoint); })
|
||||
.Build();
|
||||
|
||||
// Setup metrics with resource and instrument name filtering (like Python example)
|
||||
// Setup metrics with resource and instrument name filtering
|
||||
using var meterProvider = Sdk.CreateMeterProviderBuilder()
|
||||
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(ServiceName, serviceVersion: "1.0.0"))
|
||||
.AddMeter(SourceName) // Our custom meter
|
||||
.AddMeter("Microsoft.Extensions.AI.Agents") // Agent Framework metrics
|
||||
.AddHttpClientInstrumentation() // HTTP client metrics
|
||||
.AddRuntimeInstrumentation() // .NET runtime metrics
|
||||
.AddOtlpExporter(options =>
|
||||
{
|
||||
options.Endpoint = new Uri(otlpEndpoint);
|
||||
})
|
||||
.AddOtlpExporter(options => { options.Endpoint = new Uri(otlpEndpoint); })
|
||||
.Build();
|
||||
|
||||
// Setup structured logging with OpenTelemetry
|
||||
@@ -74,16 +70,19 @@ serviceCollection.AddLogging(loggingBuilder => loggingBuilder
|
||||
options.IncludeFormattedMessage = true;
|
||||
}));
|
||||
|
||||
var serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
var logger = serviceProvider.GetRequiredService<ILogger<Program>>();
|
||||
|
||||
using var activitySource = new ActivitySource(SourceName);
|
||||
using var meter = new Meter(SourceName);
|
||||
|
||||
// Create custom metrics (similar to Python example)
|
||||
// Create custom metrics
|
||||
var interactionCounter = meter.CreateCounter<int>("agent_interactions_total", description: "Total number of agent interactions");
|
||||
var responseTimeHistogram = meter.CreateHistogram<double>("agent_response_time_seconds", description: "Agent response time in seconds");
|
||||
|
||||
#endregion
|
||||
|
||||
var serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
|
||||
var appLogger = loggerFactory.CreateLogger<Program>();
|
||||
|
||||
Console.WriteLine("""
|
||||
=== OpenTelemetry Aspire Demo ===
|
||||
This demo shows OpenTelemetry integration with the Agent Framework.
|
||||
@@ -91,34 +90,39 @@ Console.WriteLine("""
|
||||
Type your message and press Enter. Type 'exit' or empty message to quit.
|
||||
""");
|
||||
|
||||
// Log application startup
|
||||
logger.LogInformation("OpenTelemetry Aspire Demo application started");
|
||||
logger.LogInformation("OTLP endpoint configured: {OtlpEndpoint}", otlpEndpoint);
|
||||
logger.LogDebug("Service name: {ServiceName}, Source name: {SourceName}", ServiceName, SourceName);
|
||||
|
||||
// Create the chat client
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT environment variable is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
|
||||
|
||||
logger.LogInformation("Initializing Azure OpenAI client with endpoint: {Endpoint}", endpoint);
|
||||
logger.LogDebug("Using deployment: {DeploymentName}", deploymentName);
|
||||
// Log application startup
|
||||
appLogger.LogInformation("OpenTelemetry Aspire Demo application started");
|
||||
|
||||
// Create a logger specifically for the agent
|
||||
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
|
||||
[Description("Get the weather for a given location.")]
|
||||
static async Task<string> GetWeather([Description("The location to get the weather for.")] string location)
|
||||
{
|
||||
await Task.Delay(2000);
|
||||
return $"The weather in {location} is cloudy with a high of 15°C.";
|
||||
}
|
||||
|
||||
// Create the agent with OpenTelemetry instrumentation
|
||||
logger.LogInformation("Creating Agent with OpenTelemetry instrumentation");
|
||||
// To ensure chat client's function calling is captured in the open telemetry, the chat client needs to have UseOpenTelemetry after UseFunctionInvocation
|
||||
using var instrumentedChatClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.AsIChatClient() // Converts a native OpenAI SDK ChatClient into a Microsoft.Extensions.AI.IChatClient
|
||||
.AsBuilder()
|
||||
.UseFunctionInvocation()
|
||||
.UseOpenTelemetry(loggerFactory: loggerFactory, sourceName: SourceName, (cfg) => { cfg.EnableSensitiveData = true; })
|
||||
.Build();
|
||||
|
||||
using var agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(
|
||||
appLogger.LogInformation("Creating Agent with OpenTelemetry instrumentation");
|
||||
// Create the agent with the instrumented chat client
|
||||
using var agent = new ChatClientAgent(instrumentedChatClient,
|
||||
name: "OpenTelemetryDemoAgent",
|
||||
instructions: "You are a helpful assistant that provides concise and informative responses.")
|
||||
.WithOpenTelemetry(loggerFactory, SourceName);
|
||||
instructions: "You are a helpful assistant that provides concise and informative responses.",
|
||||
tools: [AIFunctionFactory.Create(GetWeather)])
|
||||
.WithOpenTelemetry(loggerFactory, SourceName); // Enable telemetry on the agent
|
||||
|
||||
var thread = agent.GetNewThread();
|
||||
|
||||
logger.LogInformation("Agent created successfully with ID: {AgentId}", agent.Id);
|
||||
appLogger.LogInformation("Agent created successfully with ID: {AgentId}", agent.Id);
|
||||
|
||||
// Create a parent span for the entire agent session
|
||||
using var sessionActivity = activitySource.StartActivity("Agent Session");
|
||||
@@ -127,8 +131,8 @@ sessionActivity?.SetTag("agent.name", "OpenTelemetryDemoAgent");
|
||||
sessionActivity?.SetTag("session.id", sessionId);
|
||||
sessionActivity?.SetTag("session.start_time", DateTimeOffset.UtcNow.ToString("O"));
|
||||
|
||||
logger.LogInformation("Starting agent session with ID: {SessionId}", sessionId);
|
||||
using (logger.BeginScope(new Dictionary<string, object> { ["SessionId"] = sessionId, ["AgentName"] = "OpenTelemetryDemoAgent" }))
|
||||
appLogger.LogInformation("Starting agent session with ID: {SessionId}", sessionId);
|
||||
using (appLogger.BeginScope(new Dictionary<string, object> { ["SessionId"] = sessionId, ["AgentName"] = "OpenTelemetryDemoAgent" }))
|
||||
{
|
||||
var interactionCount = 0;
|
||||
|
||||
@@ -139,12 +143,12 @@ using (logger.BeginScope(new Dictionary<string, object> { ["SessionId"] = sessio
|
||||
|
||||
if (string.IsNullOrWhiteSpace(userInput) || userInput.Equals("exit", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
logger.LogInformation("User requested to exit the session");
|
||||
appLogger.LogInformation("User requested to exit the session");
|
||||
break;
|
||||
}
|
||||
|
||||
interactionCount++;
|
||||
logger.LogInformation("Processing user interaction #{InteractionNumber}: {UserInput}", interactionCount, userInput);
|
||||
appLogger.LogInformation("Processing user interaction #{InteractionNumber}: {UserInput}", interactionCount, userInput);
|
||||
|
||||
// Create a child span for each individual interaction
|
||||
using var activity = activitySource.StartActivity("Agent Interaction");
|
||||
@@ -156,7 +160,7 @@ using (logger.BeginScope(new Dictionary<string, object> { ["SessionId"] = sessio
|
||||
|
||||
try
|
||||
{
|
||||
logger.LogDebug("Starting agent execution for interaction #{InteractionNumber}", interactionCount);
|
||||
appLogger.LogDebug("Starting agent execution for interaction #{InteractionNumber}", interactionCount);
|
||||
Console.Write("Agent: ");
|
||||
|
||||
// Run the agent (this will create its own internal telemetry spans)
|
||||
@@ -177,7 +181,7 @@ using (logger.BeginScope(new Dictionary<string, object> { ["SessionId"] = sessio
|
||||
|
||||
activity?.SetTag("response.success", true);
|
||||
|
||||
logger.LogInformation("Agent interaction #{InteractionNumber} completed successfully in {ResponseTime:F2} seconds",
|
||||
appLogger.LogInformation("Agent interaction #{InteractionNumber} completed successfully in {ResponseTime:F2} seconds",
|
||||
interactionCount, responseTime);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -197,7 +201,7 @@ using (logger.BeginScope(new Dictionary<string, object> { ["SessionId"] = sessio
|
||||
activity?.SetTag("error.message", ex.Message);
|
||||
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
|
||||
|
||||
logger.LogError(ex, "Agent interaction #{InteractionNumber} failed after {ResponseTime:F2} seconds: {ErrorMessage}",
|
||||
appLogger.LogError(ex, "Agent interaction #{InteractionNumber} failed after {ResponseTime:F2} seconds: {ErrorMessage}",
|
||||
interactionCount, responseTime, ex.Message);
|
||||
}
|
||||
}
|
||||
@@ -206,7 +210,7 @@ using (logger.BeginScope(new Dictionary<string, object> { ["SessionId"] = sessio
|
||||
sessionActivity?.SetTag("session.total_interactions", interactionCount);
|
||||
sessionActivity?.SetTag("session.end_time", DateTimeOffset.UtcNow.ToString("O"));
|
||||
|
||||
logger.LogInformation("Agent session completed. Total interactions: {TotalInteractions}", interactionCount);
|
||||
appLogger.LogInformation("Agent session completed. Total interactions: {TotalInteractions}", interactionCount);
|
||||
} // End of logging scope
|
||||
|
||||
logger.LogInformation("OpenTelemetry Aspire Demo application shutting down");
|
||||
appLogger.LogInformation("OpenTelemetry Aspire Demo application shutting down");
|
||||
|
||||
@@ -18,7 +18,7 @@ internal static class ChatClientExtensions
|
||||
chatBuilder.UseAgentInvocation();
|
||||
}
|
||||
|
||||
if (chatClient.GetService<NewFunctionInvokingChatClient>() is null)
|
||||
if (chatClient.GetService<FunctionInvokingChatClient>() is null && chatClient.GetService<NewFunctionInvokingChatClient>() is null)
|
||||
{
|
||||
_ = chatBuilder.Use((IChatClient innerClient, IServiceProvider services) =>
|
||||
{
|
||||
@@ -33,7 +33,17 @@ internal static class ChatClientExtensions
|
||||
if (options?.ChatOptions?.Tools is { Count: > 0 })
|
||||
{
|
||||
// When tools are provided in the constructor, set the tools for the whole lifecycle of the chat client
|
||||
agentChatClient.GetService<NewFunctionInvokingChatClient>()!.AdditionalTools = options.ChatOptions.Tools;
|
||||
var newFunctionService = agentChatClient.GetService<NewFunctionInvokingChatClient>();
|
||||
var oldFunctionService = agentChatClient.GetService<FunctionInvokingChatClient>();
|
||||
|
||||
if (newFunctionService is not null)
|
||||
{
|
||||
newFunctionService.AdditionalTools = options.ChatOptions.Tools;
|
||||
}
|
||||
else
|
||||
{
|
||||
oldFunctionService!.AdditionalTools = options.ChatOptions.Tools;
|
||||
}
|
||||
}
|
||||
|
||||
return agentChatClient;
|
||||
|
||||
Reference in New Issue
Block a user