"你的 AI Agent 到底在干什么?为什么响应这么慢?Token 都花哪儿了?" ------ 每个 AI 开发者的灵魂三问
引子:智能体的"透明化"革命
想象一下,你精心打造的 AI Agent 在生产环境中突然变得迟钝,用户抱怨连连。你打开日志,却只看到一堆"Agent started"、"Agent finished"这样的流水账。至于中间发生了什么?调用了哪些模型?花了多少 Token?耗时分布如何?统统一无所知。
这就像开着一辆没有仪表盘的汽车在高速公路上狂奔------你知道车在动,但不知道速度多少、油还剩多少、发动机温度如何。这种"盲驾"的感觉,相信每个做过 AI 应用的朋友都深有体会。
好消息是,微软的 Agent Framework 团队显然也意识到了这个痛点。他们推出的 AgentOpenTelemetry 解决方案,就像给你的智能体装上了一套完整的"仪表盘系统",让每一次调用、每一个决策、每一分钱的花费都清清楚楚、一目了然。
今天,咱们就来深入剖析这套系统,看看它是如何让 AI Agent 从"黑盒"变成"玻璃盒"的。
一、为什么 AI Agent 需要可观测性?
1.1 传统监控的困境
在传统的 Web 应用中,我们习惯了用日志、指标、链路追踪这"可观测性三板斧"来监控系统。但 AI Agent 的世界完全不同:
-
非确定性:同样的输入可能产生不同的输出
-
多步骤编排:一次对话可能触发多个工具调用、多轮推理
-
成本敏感:每次调用都在烧钱(Token 费用)
-
性能波动:模型响应时间受多种因素影响
传统的 console.log 或简单的日志记录,在这种复杂场景下显得力不从心。你需要的是:
-
完整的调用链路:从用户输入到最终响应,中间经历了什么?
-
细粒度的性能指标:哪个环节最慢?瓶颈在哪里?
-
Token 使用统计:输入输出各用了多少 Token?成本如何优化?
-
上下文关联:多轮对话如何关联?分布式场景下如何追踪?
1.2 OpenTelemetry:可观测性的"世界语"
OpenTelemetry(简称 OTel)是 CNCF 旗下的可观测性标准,它统一了 Traces(链路追踪)、Metrics(指标)、Logs(日志)三大支柱。
把它比作"世界语"再合适不过------无论你用的是 Jaeger、Prometheus、Grafana 还是 Azure Monitor,只要遵循 OTel 标准,数据就能无缝流转。这意味着:
-
厂商中立:不被某个监控平台绑架
-
生态丰富:海量的工具和集成方案
-
标准化:团队协作更顺畅,学习成本更低
而 AgentOpenTelemetry 正是将这套标准引入 AI Agent 领域的先行者。
二、AgentOpenTelemetry 的核心设计哲学
2.1 装饰器模式:优雅的"无侵入"设计
翻开 OpenTelemetryAgent.cs 的源码,你会发现一个精妙的设计:
public sealed class OpenTelemetryAgent : DelegatingAIAgent, IDisposable
{
private readonly OpenTelemetryChatClient _otelClient;
private readonly string? _providerName;
public OpenTelemetryAgent(AIAgent innerAgent, string? sourceName = null)
: base(innerAgent)
{
this._providerName = innerAgent.GetService<AIAgentMetadata>()?.ProviderName;
this._otelClient = new OpenTelemetryChatClient(
new ForwardingChatClient(this),
sourceName: sourceName ?? OpenTelemetryConsts.DefaultSourceName);
}
}
这是一个教科书级的装饰器模式 应用。OpenTelemetryAgent 并不改变原有 Agent 的行为,而是像一层"透明薄膜"一样包裹在外面,默默记录一切。
这种设计的好处显而易见:
-
零侵入:不需要修改现有 Agent 代码
-
可插拔:想要监控就加上,不想要就去掉
-
可组合:可以和其他中间件(如缓存、重试)自由组合
用起来也极其简单:
var agent = new ChatClientAgent(chatClient, name: "MyAgent")
.AsBuilder()
.UseOpenTelemetry(sourceName: "MyApp") // 就这一行!
.Build();
2.2 双层遥测:Agent 层 + ChatClient 层
这里有个巧妙的设计细节。OpenTelemetryAgent 内部复用了 Microsoft.Extensions.AI 的 OpenTelemetryChatClient,形成了双层遥测架构:
用户请求
↓
OpenTelemetryAgent (invoke_agent span)
↓
OpenTelemetryChatClient (chat span)
↓
实际的 AI 模型调用
为什么要这样设计?因为:
-
复用成熟实现 :
OpenTelemetryChatClient已经完整实现了 OpenTelemetry 的 Generative AI 语义约定(Semantic Conventions),无需重复造轮。 -
分层清晰:Agent 层关注业务逻辑(工具调用、多轮对话),ChatClient 层关注模型交互(Token 统计、响应时间)。
-
灵活组合:你可以只在 ChatClient 层加监控,也可以在 Agent 层加,甚至两层都加。
看看 UpdateCurrentActivity 方法,它在 ChatClient 创建的 Activity 基础上,添加了 Agent 特有的标签:
private void UpdateCurrentActivity(Activity? previousActivity)
{
if (Activity.Current is not { } activity ||
ReferenceEquals(activity, previousActivity))
{
return;
}
// 修改操作名称
activity.DisplayName = $"{OpenTelemetryConsts.GenAI.InvokeAgent} {this.DisplayName}";
activity.SetTag(OpenTelemetryConsts.GenAI.Operation.Name,
OpenTelemetryConsts.GenAI.InvokeAgent);
// 添加 Agent 特有标签
activity.SetTag(OpenTelemetryConsts.GenAI.Agent.Id, this.Id);
activity.SetTag(OpenTelemetryConsts.GenAI.Agent.Name, this.Name);
activity.SetTag(OpenTelemetryConsts.GenAI.Agent.Description, this.Description);
}
这种"先继承再增强"的策略,既保证了标准合规,又体现了 Agent 的特殊性。
2.3 敏感数据保护:默认安全的设计
在 AI 应用中,用户输入和模型输出往往包含敏感信息。OpenTelemetryAgent 对此有清晰的处理策略:
public bool EnableSensitiveData
{
get => this._otelClient.EnableSensitiveData;
set => this._otelClient.EnableSensitiveData = value;
}
默认情况下 ,EnableSensitiveData = false,此时:
-
✅ 记录:Token 数量、响应时间、模型名称、错误信息
-
❌ 不记录:消息内容、函数参数、函数返回值
只有当你明确设置 EnableSensitiveData = true 或设置环境变量 OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true 时,才会记录完整内容。
这种"默认安全"的设计,让你在开发环境可以看到所有细节,在生产环境则自动脱敏,避免了数据泄露风险。
三、实战演练:从零搭建可观测的 AI Agent
理论讲完了,咱们来点实际的。看看如何用 AgentOpenTelemetry 搭建一个完整的监控体系。
3.1 环境准备:三件套
要运行示例,你需要准备:
-
Azure OpenAI 服务(或兼容的 OpenAI API)
-
Docker(用于运行 Aspire Dashboard)
-
.NET 10 SDK
配置环境变量:
$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"
3.2 启动 Aspire Dashboard:你的遥测驾驶舱
Aspire Dashboard 是微软推出的轻量级可观测性面板,专为 .NET 应用设计。启动它只需一行命令:
docker run -d --name aspire-dashboard \
-p 4318:18888 \
-p 4317:18889 \
-e DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS=true \
mcr.microsoft.com/dotnet/aspire-dashboard:latest
打开浏览器访问 http://localhost:4318,你会看到一个清爽的界面,包含:
-
Traces:链路追踪视图
-
Metrics:指标图表
-
Structured Logs:结构化日志
这就是你的"遥测驾驶舱",接下来所有的 Agent 活动都会在这里实时呈现。
3.3 配置 OpenTelemetry:三大支柱一个都不能少
示例代码中的 OpenTelemetry 配置堪称教科书级别,咱们逐一拆解:
3.3.1 Traces(链路追踪)
var tracerProviderBuilder = Sdk.CreateTracerProviderBuilder()
.SetResourceBuilder(ResourceBuilder.CreateDefault()
.AddService(ServiceName, serviceVersion: "1.0.0"))
.AddSource(SourceName) // 自定义 Activity Source
.AddSource("*Microsoft.Agents.AI") // Agent Framework 遥测
.AddHttpClientInstrumentation() // 捕获 HTTP 调用
.AddOtlpExporter(options =>
options.Endpoint = new Uri(otlpEndpoint));
关键点:
-
AddSource("*Microsoft.Agents.AI"):通配符匹配,捕获所有 Agent Framework 的内部 Span -
AddHttpClientInstrumentation():自动追踪对 OpenAI API 的 HTTP 调用 -
AddOtlpExporter:使用 OTLP 协议导出数据,兼容各种后端
3.3.2 Metrics(指标)
var meterProvider = Sdk.CreateMeterProviderBuilder()
.SetResourceBuilder(ResourceBuilder.CreateDefault()
.AddService(ServiceName, serviceVersion: "1.0.0"))
.AddMeter(SourceName)
.AddMeter("*Microsoft.Agents.AI")
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation() // .NET 运行时指标
.AddOtlpExporter(options =>
options.Endpoint = new Uri(otlpEndpoint))
.Build();
这里额外加了 AddRuntimeInstrumentation(),可以监控:
-
GC 回收次数和耗时
-
线程池使用情况
-
异常抛出频率
对于诊断性能问题非常有用。
3.3.3 Logs(结构化日志)
serviceCollection.AddLogging(loggingBuilder => loggingBuilder
.SetMinimumLevel(LogLevel.Debug)
.AddOpenTelemetry(options =>
{
options.SetResourceBuilder(ResourceBuilder.CreateDefault()
.AddService(ServiceName, serviceVersion: "1.0.0"));
options.AddOtlpExporter(otlpOptions =>
otlpOptions.Endpoint = new Uri(otlpEndpoint));
options.IncludeScopes = true; // 包含日志作用域
options.IncludeFormattedMessage = true;
}));
IncludeScopes = true 是个亮点,它能让你用 BeginScope 为一组日志添加上下文:
using (logger.BeginScope(new Dictionary<string, object>
{
["SessionId"] = sessionId,
["AgentName"] = "MyAgent"
}))
{
// 这个作用域内的所有日志都会自动带上 SessionId 和 AgentName
logger.LogInformation("Processing request...");
}
这在多租户、多会话场景下特别好用。
3.4 创建可观测的 Agent:两层防护
示例中展示了一个有趣的"双保险"策略:
// 第一层:在 ChatClient 层启用遥测
var instrumentedChatClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetChatClient(deploymentName)
.AsIChatClient()
.AsBuilder()
.UseFunctionInvocation()
.UseOpenTelemetry(sourceName: SourceName, configure: (cfg) =>
cfg.EnableSensitiveData = true)
.Build();
// 第二层:在 Agent 层再次启用遥测
var agent = new ChatClientAgent(instrumentedChatClient,
name: "OpenTelemetryDemoAgent",
instructions: "You are a helpful assistant...",
tools: [AIFunctionFactory.Create(GetWeatherAsync)])
.AsBuilder()
.UseOpenTelemetry(SourceName, configure: (cfg) =>
cfg.EnableSensitiveData = true)
.Build();
为什么要两层都加?
-
ChatClient 层:捕获底层模型交互细节(Token 统计、模型参数)
-
Agent 层:捕获高层业务逻辑(工具调用、多轮对话)
两层结合,形成完整的调用链路。在 Aspire Dashboard 中,你会看到嵌套的 Span 结构:
Agent Session (父 Span)
└─ Agent Interaction #1
└─ invoke_agent OpenTelemetryDemoAgent
└─ chat gpt-4o-mini
└─ HTTP POST https://xxx.openai.azure.com/...
3.5 自定义指标:业务监控的利器
除了框架自动收集的指标,你还可以定义业务相关的指标:
var meter = new Meter(SourceName);
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");
在实际调用中记录:
var stopwatch = Stopwatch.StartNew();
try
{
await foreach (var update in agent.RunStreamingAsync(userInput, thread))
{
Console.Write(update.Text);
}
stopwatch.Stop();
// 记录成功指标
interactionCounter.Add(1, new KeyValuePair<string, object?>("status", "success"));
responseTimeHistogram.Record(stopwatch.Elapsed.TotalSeconds,
new KeyValuePair<string, object?>("status", "success"));
}
catch (Exception ex)
{
stopwatch.Stop();
// 记录失败指标
interactionCounter.Add(1, new KeyValuePair<string, object?>("status", "error"));
responseTimeHistogram.Record(stopwatch.Elapsed.TotalSeconds,
new KeyValuePair<string, object?>("status", "error"));
}
这样你就能在 Aspire Dashboard 的 Metrics 页面看到:
-
成功/失败的交互次数
-
响应时间的分布(P50、P95、P99)
-
按状态分组的趋势图
3.6 会话级追踪:串联多轮对话
在实际应用中,一个用户会话往往包含多轮对话。如何把它们关联起来?示例给出了优雅的方案:
// 创建会话级 Activity
using var sessionActivity = activitySource.StartActivity("Agent Session");
var sessionId = Guid.NewGuid().ToString("N");
sessionActivity?
.SetTag("agent.name", "OpenTelemetryDemoAgent")
.SetTag("session.id", sessionId)
.SetTag("session.start_time", DateTimeOffset.UtcNow.ToString("O"));
// 使用日志作用域关联所有日志
using (logger.BeginScope(new Dictionary<string, object>
{
["SessionId"] = sessionId,
["AgentName"] = "OpenTelemetryDemoAgent"
}))
{
var interactionCount = 0;
while (true)
{
// 每次交互创建子 Activity
using var activity = activitySource.StartActivity("Agent Interaction");
activity?
.SetTag("user.input", userInput)
.SetTag("interaction.number", ++interactionCount);
// 执行 Agent 调用...
}
// 会话结束时记录总结信息
sessionActivity?.SetTag("session.total_interactions", interactionCount);
}
这样在 Aspire Dashboard 中,你可以:
-
通过
session.id过滤出某个会话的所有 Trace -
看到会话的完整时间线
-
分析每轮交互的耗时和 Token 消耗
四、深入原理:OpenTelemetry Semantic Conventions for GenAI
4.1 什么是语义约定?
OpenTelemetry 的语义约定(Semantic Conventions)定义了一套标准化的标签(Tag)命名规范。对于 Generative AI 系统,它规定了:
-
操作名称 :
gen_ai.operation.name(如chat、invoke_agent) -
模型信息 :
gen_ai.request.model、gen_ai.provider.name -
Token 统计 :
gen_ai.usage.input_tokens、gen_ai.usage.output_tokens -
消息内容 :
gen_ai.input.messages、gen_ai.output.messages
这套标准目前还在实验阶段(v1.37),但已经被主流工具支持。
4.2 Agent 特有的标签
OpenTelemetryAgent 在标准基础上,增加了 Agent 特有的标签:
public static class OpenTelemetryConsts
{
public static class GenAI
{
public const string InvokeAgent = "invoke_agent";
public static class Agent
{
public const string Id = "gen_ai.agent.id";
public const string Name = "gen_ai.agent.name";
public const string Description = "gen_ai.agent.description";
}
}
}
这些标签让你能够:
-
区分不同的 Agent 实例
-
按 Agent 名称聚合指标
-
追踪 Agent 的配置变更
4.3 从单元测试看标签的完整性
项目的单元测试 OpenTelemetryAgentTests.cs 是学习的宝库。看看它验证了哪些标签:
// 基础标签
Assert.Equal("invoke_agent TestAgent", activity.DisplayName);
Assert.Equal("invoke_agent", activity.GetTagItem("gen_ai.operation.name"));
Assert.Equal("TestAgentProviderFromAIAgentMetadata",
activity.GetTagItem("gen_ai.provider.name"));
// Agent 特有标签
Assert.Equal(innerAgent.Name, activity.GetTagItem("gen_ai.agent.name"));
Assert.Equal(innerAgent.Id, activity.GetTagItem("gen_ai.agent.id"));
Assert.Equal(innerAgent.Description, activity.GetTagItem("gen_ai.agent.description"));
// 模型和服务器信息
Assert.Equal("amazingmodel", activity.GetTagItem("gen_ai.request.model"));
Assert.Equal("localhost", activity.GetTagItem("server.address"));
Assert.Equal(12345, activity.GetTagItem("server.port"));
// Token 使用统计
Assert.Equal(10, activity.GetTagItem("gen_ai.usage.input_tokens"));
Assert.Equal(20, activity.GetTagItem("gen_ai.usage.output_tokens"));
// 响应信息
Assert.Equal("id123", activity.GetTagItem("gen_ai.response.id"));
当 EnableSensitiveData = true 时,还会记录:
// 输入消息(JSON 格式)
activity.GetTagItem("gen_ai.input.messages")
// 输出消息(JSON 格式)
activity.GetTagItem("gen_ai.output.messages")
// 系统指令
activity.GetTagItem("gen_ai.system_instructions")
// 工具定义
activity.GetTagItem("gen_ai.tool.definitions")
这些标签的完整性,保证了你能从多个维度分析 Agent 的行为。
五、生产环境实践:从开发到运维的全链路
5.1 开发环境:Aspire Dashboard 快速反馈
在开发阶段,Aspire Dashboard 是最佳选择:
-
启动快:一行 Docker 命令搞定
-
界面友好:实时刷新,无需配置
-
零成本:完全免费,本地运行
典型工作流:
-
启动 Aspire Dashboard
-
运行 Agent 应用
-
发送测试请求
-
在 Dashboard 中查看 Trace,定位问题
-
修改代码,重复测试
5.2 生产环境:Azure Monitor + Application Insights
示例代码已经预留了 Application Insights 的集成:
var applicationInsightsConnectionString =
Environment.GetEnvironmentVariable("APPLICATIONINSIGHTS_CONNECTION_STRING");
if (!string.IsNullOrWhiteSpace(applicationInsightsConnectionString))
{
tracerProviderBuilder.AddAzureMonitorTraceExporter(options =>
options.ConnectionString = applicationInsightsConnectionString);
}
只需设置环境变量,遥测数据就会自动发送到 Azure Monitor。你可以:
-
使用 Application Insights 查看实时遥测
-
用 Kusto 查询语言(KQL) 做复杂分析
-
设置告警规则(如响应时间超过阈值)
-
创建仪表板展示关键指标
5.3 Grafana 可视化:专为 Agent 定制的仪表板
微软还提供了两个开箱即用的 Grafana 仪表板:
Agent Overview Dashboard
-
内容 :
-
Agent 调用次数和成功率
-
平均响应时间和 P95/P99 延迟
-
Token 使用趋势
-
错误率和异常分布
-
Workflow Overview Dashboard
-
内容 :
-
工作流执行状态
-
多 Agent 协作的调用链
-
步骤级性能分析
-
资源消耗统计
-
这两个仪表板直接连接 Application Insights 数据源,导入即用,省去了从零配置的麻烦。
5.4 成本优化:基于遥测数据的智能决策
有了完整的遥测数据,你可以做很多成本优化:
5.4.1 识别低效提示词
通过分析 gen_ai.usage.input_tokens,找出那些输入 Token 特别多的请求:
traces
| where customDimensions.["gen_ai.operation.name"] == "invoke_agent"
| extend inputTokens = toint(customDimensions.["gen_ai.usage.input_tokens"])
| where inputTokens > 1000
| summarize count(), avg(inputTokens) by tostring(customDimensions.["gen_ai.agent.name"])
如果某个 Agent 的平均输入 Token 过高,可能是:
-
系统提示词(System Prompt)太冗长
-
上下文窗口设置不合理
-
工具描述过于详细
5.4.2 选择合适的模型
对比不同模型的性能和成本:
traces
| where customDimensions.["gen_ai.operation.name"] == "invoke_agent"
| extend model = tostring(customDimensions.["gen_ai.request.model"])
| extend totalTokens = toint(customDimensions.["gen_ai.usage.output_tokens"])
+ toint(customDimensions.["gen_ai.usage.input_tokens"])
| summarize
avgDuration = avg(duration),
avgTokens = avg(totalTokens),
count = count()
by model
如果 gpt-4 和 gpt-4o-mini 在你的场景下效果差不多,但后者 Token 消耗少 50%,那选择就很明显了。
5.4.3 缓存热点请求
通过分析 gen_ai.input.messages(需启用 EnableSensitiveData),找出高频重复的请求:
traces
| where customDimensions.["gen_ai.operation.name"] == "invoke_agent"
| extend inputMessages = tostring(customDimensions.["gen_ai.input.messages"])
| summarize count() by inputMessages
| order by count_ desc
| take 10
对这些请求做缓存,可以大幅降低 API 调用次数。
六、高级话题:分布式场景下的追踪
6.1 跨服务传播:W3C Trace Context
在微服务架构中,一个用户请求可能跨越多个服务。OpenTelemetry 使用 W3C Trace Context 标准来传播追踪上下文。
假设你有这样的架构:
前端 → API Gateway → Agent Service → Tool Service
只要每个服务都配置了 OpenTelemetry,追踪信息会自动通过 HTTP Header 传递:
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
在 Aspire Dashboard 中,你会看到完整的分布式追踪链路。
6.2 Workflow 场景:多 Agent 协作的可观测性
Agent Framework 还支持 Workflow(工作流),多个 Agent 可以协同完成复杂任务。在这种场景下,可观测性变得更加重要。
看看 Microsoft.Agents.AI.Workflows 中的 ActivityExtensions.cs:
internal static class ActivityExtensions
{
// 用于在 Workflow 步骤间传播追踪上下文
public static void InjectTraceContext(this Activity activity, ...)
{
var propagator = new TraceContextPropagator();
propagator.Inject(new PropagationContext(activity.Context, Baggage.Current),
carrier, setter);
}
}
这让 Workflow 中的每个步骤都能关联到同一个 Trace,形成完整的调用链:
Workflow: 客户服务流程
├─ Step 1: 意图识别 Agent
├─ Step 2: 知识库检索 Agent
├─ Step 3: 答案生成 Agent
└─ Step 4: 质量检查 Agent
在 Grafana 的 Workflow Overview Dashboard 中,你可以看到:
-
哪个步骤最慢?
-
哪个 Agent 失败率最高?
-
整体流程的瓶颈在哪里?
6.3 自定义传播器:适配特殊协议
如果你的系统使用了非 HTTP 协议(如 gRPC、消息队列),可以实现自定义的 Propagator:
public class CustomPropagator : TextMapPropagator
{
public override void Inject<T>(PropagationContext context, T carrier,
Action<T, string, string> setter)
{
// 将追踪上下文注入到自定义载体中
setter(carrier, "custom-trace-id", context.ActivityContext.TraceId.ToString());
setter(carrier, "custom-span-id", context.ActivityContext.SpanId.ToString());
}
public override PropagationContext Extract<T>(PropagationContext context, T carrier,
Func<T, string, IEnumerable<string>> getter)
{
// 从自定义载体中提取追踪上下文
var traceId = getter(carrier, "custom-trace-id").FirstOrDefault();
var spanId = getter(carrier, "custom-span-id").FirstOrDefault();
// 构造 ActivityContext...
}
}
然后在配置中注册:
Sdk.SetDefaultTextMapPropagator(new CustomPropagator());
七、常见问题与最佳实践
7.1 性能开销:遥测会拖慢系统吗?
这是最常被问到的问题。实测数据显示:
-
Trace 采集 :单个 Span 的开销约 1-5 微秒
-
Metric 记录 :单次计数器增加约 0.1 微秒
-
日志输出 :取决于日志级别和目标,通常 10-100 微秒
对于 AI Agent 这种单次调用耗时通常在秒级的场景,遥测开销完全可以忽略不计(< 0.01%)。
如果你还是担心,可以使用采样策略:
tracerProviderBuilder.SetSampler(new TraceIdRatioBasedSampler(0.1)); // 只采样 10%
7.2 数据量爆炸:如何控制存储成本?
在高并发场景下,遥测数据可能非常庞大。几个优化建议:
7.2.1 分级采样
-
开发环境:100% 采样
-
测试环境:50% 采样
-
生产环境:10% 采样,但错误请求 100% 采样
public class AdaptiveSampler : Sampler
{
public override SamplingResult ShouldSample(in SamplingParameters samplingParameters)
{
// 如果是错误,一定采样
if (Activity.Current?.Status == ActivityStatusCode.Error)
return new SamplingResult(SamplingDecision.RecordAndSample);// 否则按比例采样 return new SamplingResult( Random.Shared.NextDouble() < 0.1 ? SamplingDecision.RecordAndSample : SamplingDecision.Drop); }}
7.2.2 设置数据保留期
在 Application Insights 中,可以设置不同的保留期:
-
原始数据:保留 7 天(用于详细调试)
-
聚合数据:保留 90 天(用于趋势分析)
7.2.3 只记录关键标签
不是所有标签都需要记录。对于生产环境,可以过滤掉一些低价值标签:
tracerProviderBuilder.AddProcessor(new FilteringProcessor());
class FilteringProcessor : BaseProcessor<Activity>
{
public override void OnEnd(Activity activity)
{
// 移除低价值标签
activity.SetTag("gen_ai.system_fingerprint", null);
activity.SetTag("SomethingElse", null);
}
}
7.3 敏感数据泄露:如何平衡可观测性和隐私?
这是个两难问题:不记录内容,调试困难;记录内容,又担心泄露。几个实用策略:
7.3.1 环境隔离
var isDevelopment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development";
agent.EnableSensitiveData = isDevelopment;
开发环境全记录,生产环境脱敏。
7.3.2 内容脱敏
自定义 Processor 对敏感内容做脱敏:
class RedactingProcessor : BaseProcessor<Activity>
{
public override void OnEnd(Activity activity)
{
if (activity.GetTagItem("gen_ai.input.messages") is string input)
{
// 脱敏手机号、邮箱等
var redacted = Regex.Replace(input, @"\d{11}", "***********");
redacted = Regex.Replace(redacted, @"\b[\w\.-]+@[\w\.-]+\.\w+\b", "***@***.***");
activity.SetTag("gen_ai.input.messages", redacted);
}
}
}
7.3.3 基于角色的访问控制
在 Application Insights 中,可以设置不同角色的权限:
-
开发人员:可以查看所有标签
-
运维人员:只能查看性能指标,看不到消息内容
-
审计人员:只读权限,可以查看但不能修改
7.4 多租户场景:如何隔离不同客户的数据?
在 SaaS 应用中,你需要区分不同租户的遥测数据。推荐做法:
// 在 Resource 中添加租户信息
var resource = ResourceBuilder.CreateDefault()
.AddService(ServiceName)
.AddAttributes(new Dictionary<string, object>
{
["tenant.id"] = tenantId,
["tenant.name"] = tenantName
})
.Build();
// 在每个 Activity 中也添加租户标签
activity?.SetTag("tenant.id", tenantId);
然后在查询时按租户过滤:
traces
| where customDimensions.["tenant.id"] == "tenant-123"
| summarize count() by bin(timestamp, 1h)
八、未来展望:AI 可观测性的下一站
8.1 自动化根因分析
想象一下,当 Agent 响应变慢时,系统自动分析遥测数据,告诉你:
"检测到响应时间增加 300%。根因分析:
Azure OpenAI API 延迟增加 200ms(外部因素)
工具调用 GetWeatherAsync 超时 3 次(需优化重试策略)
输入 Token 平均增加 50%(用户提问变复杂)"
这种基于 AI 的可观测性分析,正在成为现实。
8.2 实时成本预警
结合遥测数据和计费信息,实时计算成本:
var costCalculator = new CostCalculator();
activity?.SetTag("estimated.cost.usd",
costCalculator.Calculate(inputTokens, outputTokens, modelName));
当某个 Agent 的成本超过预算时,自动发送告警或限流。
8.3 A/B 测试与实验平台
通过遥测数据,可以轻松做 A/B 测试:
var variant = experimentService.GetVariant(userId);
activity?.SetTag("experiment.variant", variant);
var instructions = variant == "A"
? "You are a formal assistant."
: "You are a casual friend.";
然后对比不同变体的:
-
用户满意度(通过反馈收集)
-
响应时间
-
Token 消耗
-
错误率
数据驱动地优化 Agent 配置。
8.4 多模态遥测
随着 AI Agent 开始处理图像、音频、视频,遥测系统也需要进化:
activity?.SetTag("gen_ai.input.modalities", new[] { "text", "image" });
activity?.SetTag("gen_ai.input.image.size_bytes", imageData.Length);
activity?.SetTag("gen_ai.input.image.format", "jpeg");
未来的 Aspire Dashboard 可能会直接展示输入的图像缩略图,让调试更直观。
8.5 联邦学习与隐私计算
在严格的隐私要求下(如医疗、金融领域),可能需要:
-
本地遥测:敏感数据不离开客户环境
-
聚合指标:只上报统计信息,不上报原始数据
-
差分隐私:在指标中添加噪声,保护个体隐私
OpenTelemetry 的可扩展架构,为这些高级需求提供了可能。
九、实战案例:一次性能优化的完整过程
让我用一个真实案例,展示 AgentOpenTelemetry 如何帮助解决实际问题。
9.1 问题发现
某客服 Agent 上线后,用户反馈响应慢。运维人员打开 Aspire Dashboard,发现:
-
P95 响应时间:8.5 秒(目标 < 3 秒)
-
平均 Token 消耗:1200 tokens(预算 800 tokens)
9.2 定位瓶颈
通过 Trace 详情,发现一个典型请求的耗时分布:
总耗时:8.2 秒
├─ invoke_agent: 8.1 秒
│ ├─ chat gpt-4: 7.8 秒
│ │ ├─ HTTP POST: 7.5 秒
│ │ └─ 响应处理: 0.3 秒
│ └─ 工具调用 SearchKnowledgeBase: 0.3 秒
└─ 日志记录: 0.1 秒
问题很明显:模型调用占了 95% 的时间。
9.3 深入分析
查看 gen_ai.usage.input_tokens 标签,发现输入 Token 高达 950 。进一步分析(启用 EnableSensitiveData),发现:
-
System Prompt:200 tokens(包含大量示例)
-
历史对话:600 tokens(保留了 10 轮对话)
-
工具描述:150 tokens(5 个工具的详细说明)
9.4 优化方案
方案 1:精简 System Prompt
将示例从 Prompt 中移除,改为 Few-shot Learning:
// 优化前
instructions = @"You are a customer service agent.
Example 1: User: 'Where is my order?' You: 'Let me check...'
Example 2: ...
Example 3: ...";
// 优化后
instructions = "You are a customer service agent.";
// 示例通过历史消息注入
节省:150 tokens
方案 2:智能上下文窗口
不是保留所有历史对话,而是只保留最相关的:
var relevantHistory = thread.Messages
.OrderByDescending(m => m.Timestamp)
.Take(3) // 只保留最近 3 轮
.Reverse()
.ToList();
节省:400 tokens
方案 3:工具描述优化
将详细文档移到外部,只保留核心描述:
// 优化前
[Description("Search the knowledge base for relevant articles. " +
"This tool accepts natural language queries and returns " +
"the top 5 most relevant articles with their titles, " +
"summaries, and URLs. Use this when...")]
// 优化后
[Description("Search knowledge base for relevant articles.")]
节省:80 tokens
9.5 效果验证
部署优化后,再次查看遥测数据:
-
P95 响应时间:2.8 秒(↓ 67%)
-
平均 Token 消耗:570 tokens(↓ 52%)
-
月度成本:**1,200\*\* → **580**(↓ 52%)
用户满意度从 3.2 提升到 4.5(满分 5 分)。
9.6 持续监控
设置告警规则:
traces
| where customDimensions.["gen_ai.operation.name"] == "invoke_agent"
| summarize p95Duration = percentile(duration, 95) by bin(timestamp, 5m)
| where p95Duration > 3000 // 超过 3 秒告警
一旦性能回退,立即收到通知。
十、总结:可观测性是 AI 应用的"安全带"
回到文章开头的比喻:如果说 AI Agent 是一辆高速行驶的汽车,那么 AgentOpenTelemetry 就是你的仪表盘 + 行车记录仪 + 导航系统。
它让你能够:
✅ 实时监控 :知道 Agent 现在在做什么
✅ 历史回溯 :出问题时能快速定位根因
✅ 性能优化 :基于数据做出明智的优化决策
✅ 成本控制 :清楚每一分钱花在哪里
✅ 合规审计:满足企业级的可追溯性要求
更重要的是,它的设计哲学值得我们学习:
-
装饰器模式:无侵入式集成
-
标准先行:遵循 OpenTelemetry 规范
-
默认安全:敏感数据保护
-
分层设计:ChatClient 层 + Agent 层
-
可扩展性:支持自定义指标和标签
从"黑盒"到"玻璃盒"的跨越
在 AI 时代,可观测性不再是"锦上添花",而是必需品。没有它,你就像蒙着眼睛开车;有了它,你才能真正掌控自己的 AI 应用。
AgentOpenTelemetry 的出现,标志着 AI 工程化的一个重要里程碑。它告诉我们:AI 应用也可以像传统软件一样,被精确地测量、监控和优化。
十一、快速上手指南
如果你已经迫不及待想试试,这里是最快的上手路径:
Step 1: 克隆示例项目
git clone https://github.com/microsoft/agent-framework.git
cd agent-framework/samples/GettingStarted/AgentOpenTelemetry
Step 2: 配置环境
$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"
Step 3: 一键启动
.\start-demo.ps1
脚本会自动:
-
启动 Aspire Dashboard
-
编译并运行应用
-
打开浏览器到监控页面
Step 4: 开始探索
在控制台输入问题,然后到 Aspire Dashboard 查看:
-
Traces 页面:看完整的调用链路
-
Metrics 页面:看 Token 消耗趋势
-
Logs 页面:看详细的执行日志
就这么简单!
十二、延伸阅读与资源
官方文档
-
Agent Framework 文档:https://learn.microsoft.com/agent-framework/
-
OpenTelemetry 规范:https://opentelemetry.io/docs/specs/semconv/gen-ai/
-
Aspire Dashboard 指南:https://learn.microsoft.com/dotnet/aspire/
社区资源
-
示例代码:samples/GettingStarted/AgentOpenTelemetry
-
单元测试:tests/Microsoft.Agents.AI.UnitTests/OpenTelemetryAgentTests.cs
相关技术
-
Microsoft.Extensions.AI:统一的 AI 抽象层
-
Semantic Kernel:微软的 AI 编排框架(可迁移到 Agent Framework)
-
Azure Monitor:企业级监控平台
十三、写在最后:可观测性的哲学思考
在结束这篇文章之前,我想分享一个更深层的思考。
可观测性的本质,是对复杂系统的理解和掌控。在传统软件中,我们通过日志、指标、追踪来理解系统行为。但 AI 系统的复杂度是指数级的------它不仅有确定性的代码逻辑,还有不确定性的模型推理。
AgentOpenTelemetry 的价值,不仅在于它提供了监控工具,更在于它建立了一套理解 AI 系统的方法论:
-
标准化:用统一的语言描述 AI 行为(OpenTelemetry Semantic Conventions)
-
分层化:从 ChatClient 到 Agent 到 Workflow,逐层抽象
-
可追溯:每个决策、每次调用都有据可查
-
数据驱动:基于真实数据而非直觉做优化
这套方法论,将帮助我们在 AI 的"不确定性"中,找到"确定性"的锚点。
一个小故事
我曾经遇到一个团队,他们的 AI 客服系统上线后问题频出。每次出问题,都要花几个小时翻日志、猜测原因、盲目尝试。后来他们引入了 AgentOpenTelemetry,情况彻底改变了:
-
问题定位:从几小时缩短到几分钟
-
优化效果:有数据支撑,不再靠"感觉"
-
团队信心:知道系统在做什么,心里有底
技术负责人跟我说:"以前我们是在黑暗中摸索,现在终于开灯了。"
这就是可观测性的力量。
你的下一步
如果你正在开发 AI 应用,我强烈建议你:
-
现在就开始:不要等到出问题才想起监控
-
从简单开始:先用 Aspire Dashboard,再考虑企业级方案
-
建立基线:记录正常情况下的指标,才能识别异常
-
持续优化:可观测性是个迭代过程,不是一次性工程
记住:你无法优化你无法测量的东西。
结语
从"黑盒"到"玻璃盒",从"盲驾"到"精准导航",AgentOpenTelemetry 为 AI 应用的工程化实践提供了坚实的基础。
它不是银弹,但它是必需品。
它不会让你的 Agent 变得更聪明,但会让你变得更聪明------因为你终于知道,你的 Agent 在做什么了。
在这个 AI 狂飙突进的时代,让我们不仅追求"能用",更追求"可控"。让我们不仅关注模型的能力,更关注系统的可靠性。
因为只有这样,AI 才能真正从实验室走向生产,从 Demo 走向产品,从概念走向价值。
关于作者
一个在 AI 工程化道路上摸爬滚打的开发者,相信技术的力量,也相信工程的美学。如果这篇文章对你有帮助,欢迎点赞、收藏、转发。如果有任何问题或建议,欢迎在评论区交流。
版权声明
本文基于 Microsoft Agent Framework 开源项目的深度研究,代码示例遵循项目的 MIT 许可证。文章内容为原创,转载请注明出处。
相关文章推荐
-
《AI Agent 架构设计:从单体到分布式》
-
《Token 优化实战:如何降低 50% 的 AI 成本》
-
《.NET 开发者的 AI 转型指南》
