Create a new class ObservabilityMiddleware that implements IAgentMiddleware.
using Microsoft.AgentFramework;
using Microsoft.AgentFramework.Abstractions;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using System.Text;
public class ObservabilityMiddleware : IAgentMiddleware
{
private readonly ILogger<ObservabilityMiddleware> _logger;
private readonly ITelemetryService _telemetry;
private readonly IAnomalyDetector _anomalyDetector;
public ObservabilityMiddleware(
ILogger<ObservabilityMiddleware> logger,
ITelemetryService telemetry,
IAnomalyDetector anomalyDetector)
{
_logger = logger;
_telemetry = telemetry;
_anomalyDetector = anomalyDetector;
}
public async Task InvokeAsync(AgentContext context, Func<Task> next)
{
var stopwatch = Stopwatch.StartNew();
var agentName = context.Agent?.Name ?? "Unknown";
var input = context.Input ?? "";
var inputHash = ComputeSha256Hash(input);
var isAnomalous = await _anomalyDetector.CheckInputAsync(input);
if (isAnomalous)
{
_logger.LogWarning("Anomalous input detected for agent {AgentName}. Hash: {InputHash}",
agentName, inputHash);
}
var toolInterceptor = new ToolCallInterceptor();
var originalToolHandler = context.ToolCallHandler;
context.ToolCallHandler = async (toolCall, ct) =>
{
_logger.LogDebug("Agent {AgentName} calling tool {ToolName} with args {Args}",
agentName, toolCall.Name, toolCall.Arguments);
toolInterceptor.AddCall(toolCall.Name, toolCall.Arguments);
var result = await originalToolHandler(toolCall, ct);
_logger.LogDebug("Tool {ToolName} returned: {Result}", toolCall.Name, result);
return result;
};
try
{
await next();
var duration = stopwatch.Elapsed;
var toolsCalled = toolInterceptor.GetCalls();
_logger.LogInformation(
"Agent {AgentName} completed in {DurationMs}ms. Tools: {ToolCount}",
agentName, duration.TotalMilliseconds, toolsCalled.Count);
_telemetry.TrackAgentExecution(new AgentTelemetry
{
AgentName = agentName,
Duration = duration,
InputHash = inputHash,
ToolCalls = toolsCalled,
Success = true
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Agent {AgentName} failed after {ElapsedMs}ms",
agentName, stopwatch.Elapsed.TotalMilliseconds);
_telemetry.TrackAgentExecution(new AgentTelemetry
{
AgentName = agentName,
Duration = stopwatch.Elapsed,
InputHash = inputHash,
Success = false,
Error = ex.Message
});
throw;
}
}
private static string ComputeSha256Hash(string rawData)
{
using var sha256 = System.Security.Cryptography.SHA256.Create();
var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(rawData));
return Convert.ToBase64String(bytes);
}
}
public class ToolCallInterceptor
{
private readonly List<ToolCallInfo> _calls = new();
public void AddCall(string name, string arguments)
=> _calls.Add(new ToolCallInfo(name, arguments, DateTime.UtcNow));
public IReadOnlyList<ToolCallInfo> GetCalls() => _calls.AsReadOnly();
}
public record ToolCallInfo(string Name, string Arguments, DateTime Timestamp);
Step 2: Define Telemetry Service and Anomaly Detector
We'll create simple interfaces. For production, you'd implement these with Application Insights and a proper ML service.
public interface ITelemetryService
{
void TrackAgentExecution(AgentTelemetry telemetry);
}
public class AgentTelemetry
{
public string AgentName { get; set; }
public TimeSpan Duration { get; set; }
public string InputHash { get; set; }
public IReadOnlyList<ToolCallInfo> ToolCalls { get; set; }
public bool Success { get; set; }
public string Error { get; set; }
}
public interface IAnomalyDetector
{
Task<bool> CheckInputAsync(string input);
}
public class SimpleAnomalyDetector : IAnomalyDetector
{
public Task<bool> CheckInputAsync(string input)
{
if (input.Length > 5000)
return Task.FromResult(true);
var jailbreakPhrases = new[]
{
"ignore previous instructions",
"ignore all instructions",
"you are now",
"DAN",
"do anything now"
};
if (jailbreakPhrases.Any(p => input.Contains(p, StringComparison.OrdinalIgnoreCase)))
return Task.FromResult(true);
if (input.Contains("<SystemPolicy>") || input.Contains("\"role\": \"system\""))
return Task.FromResult(true);
return Task.FromResult(false);
}
}
Step 3: Implement Telemetry with Application Insights
Install the NuGet package: Microsoft.ApplicationInsights.WorkerService
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;
public class AppInsightsTelemetryService : ITelemetryService
{
private readonly TelemetryClient _telemetryClient;
public AppInsightsTelemetryService(TelemetryConfiguration telemetryConfig)
{
_telemetryClient = new TelemetryClient(telemetryConfig);
}
public void TrackAgentExecution(AgentTelemetry telemetry)
{
var evt = new EventTelemetry("AgentExecution");
evt.Properties["AgentName"] = telemetry.AgentName;
evt.Properties["InputHash"] = telemetry.InputHash;
evt.Properties["Success"] = telemetry.Success.ToString();
evt.Properties["ToolCount"] = telemetry.ToolCalls?.Count.ToString() ?? "0";
evt.Properties["DurationMs"] = telemetry.Duration.TotalMilliseconds.ToString("F2");
if (!string.IsNullOrEmpty(telemetry.Error))
evt.Properties["Error"] = telemetry.Error;
if (telemetry.ToolCalls?.Any() == true)
{
evt.Properties["Tools"] = string.Join(",", telemetry.ToolCalls.Select(t => t.Name));
}
_telemetryClient.TrackEvent(evt);
}
}
Step 4: Register Everything in Dependency Injection
In your Program.cs (or wherever you build the host), add the services.
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.AgentFramework;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddApplicationInsightsTelemetryWorkerService(options =>
{
options.ConnectionString = "InstrumentationKey=...;IngestionEndpoint=...";
});
builder.Services.AddSingleton<ITelemetryService, AppInsightsTelemetryService>();
builder.Services.AddSingleton<IAnomalyDetector, SimpleAnomalyDetector>();
builder.Services.AddAgentFramework()
.AddAgent<MyAgent>()
.UseMiddleware<ObservabilityMiddleware>();
builder.Services.AddHostedService<AgentHostedService>();
var host = builder.Build();
await host.RunAsync();
Let's create a simple agent with a calculator tool to see the middleware in action.
using Microsoft.AgentFramework;
using Microsoft.AgentFramework.Abstractions;
using System.ComponentModel;
public class MyAgent : IAgent
{
private readonly IChatModel _model;
private readonly IToolRegistry _toolRegistry;
public MyAgent(IChatModel model, IToolRegistry toolRegistry)
{
_model = model;
_toolRegistry = toolRegistry;
_toolRegistry.RegisterTool(CalculatorTool.Add);
}
public async Task RunAsync(CancellationToken cancellationToken)
{
Console.WriteLine("Agent is ready. Ask something like 'What is 23+19?'");
while (true)
{
var input = Console.ReadLine();
if (input == "exit") break;
var response = await _model.GenerateAsync(input, cancellationToken);
Console.WriteLine(response);
}
}
}
public static class CalculatorTool
{
[Tool("Adds two numbers")]
public static int Add(
[ToolParameter("First number")] int a,
[ToolParameter("Second number")] int b) => a + b;
}
Step 6: Run and Verify Telemetry in Application Insights
After running the agent and making a few queries, go to your Application Insights resource. Navigate to Logs and try these KQL queries:
Query 1: Agent execution summary over time
customEvents
| where name == "AgentExecution"
| project timestamp,
agentName = customDimensions.AgentName,
success = customDimensions.Success,
durationMs = todouble(customDimensions.DurationMs),
tools = customDimensions.Tools
| summarize avg(durationMs) by agentName, bin(timestamp, 1h)
| render timechart
Query 2: Most active agents by request count
customEvents
| where name == "AgentExecution"
| summarize RequestCount = count() by AgentName = customDimensions.AgentName
| top 10 by RequestCount desc
Query 3: Detect anomalies – high error rates
customEvents
| where name == "AgentExecution"
| summarize Failures = countif(customDimensions.Success == "False"),
Total = count()
by bin(timestamp, 5m)
| extend FailureRate = todouble(Failures) / todouble(Total) * 100
| where FailureRate > 20
| project timestamp, FailureRate
customEvents
| where name == "AgentExecution"
| where isnotempty(customDimensions.Tools)
| extend tools = split(customDimensions.Tools, ",")
| mv-expand tools
| summarize ToolCount = count() by tostring(tools)
| render piechart
Step 7: Build a Real-Time Dashboard
In Application Insights, you can create a Workbook that combines these queries into a single view. Include:
A time chart of agent requests and latencies
A table of recent anomalous inputs (by input hash)
A pie chart of tool usage
An alert rule that triggers when error rate exceeds a threshold