AI服务的可观测性与运维
AI服务的可观测性与运维当 AI 服务从开发环境走向生产可观测性Observability成为运维的基石。传统的监控CPU、内存、请求量已不足以应对 AI 系统的复杂性我们需要深入追踪每个 AI 交互的细节Token 消耗、模型延迟、函数调用路径、成本分布等。同时AI 模型的持续迭代要求我们具备A/B 测试和灰度发布能力以安全地引入新模型或提示词。将构建一套完整的 AI 可观测性体系涵盖 OpenTelemetry 埋点、实验框架以及成本分析工具。1 OpenTelemetry 在 AI 链路中的埋点Token 消耗、延迟1.1 为什么需要 OpenTelemetryOpenTelemetryOTel是云原生可观测性的行业标准提供统一的 API 和 SDK 来收集追踪Traces、指标Metrics和日志Logs。对于 AI 应用我们需要将 LLM 调用、向量检索、插件执行等环节作为 span 串联起来形成完整的调用链从而定位性能瓶颈和异常。10.1.2 在 .NET 中集成 OpenTelemetry首先安装必要的 NuGet 包dotnetaddpackage OpenTelemetry.Extensions.Hosting dotnetaddpackage OpenTelemetry.Instrumentation.AspNetCore dotnetaddpackage OpenTelemetry.Instrumentation.Http dotnetaddpackage OpenTelemetry.Exporter.Console dotnetaddpackage OpenTelemetry.Exporter.OpenTelemetryProtocol# 导出到 OTLP 兼容后端在 Program.cs 中配置usingOpenTelemetry;usingOpenTelemetry.Metrics;usingOpenTelemetry.Trace;usingOpenTelemetry.Resources;varserviceNameEnterpriseAI;varserviceVersion1.0.0;builder.Services.AddOpenTelemetry().WithTracing(tracingtracing.AddSource(serviceName).AddAspNetCoreInstrumentation().AddHttpClientInstrumentation().AddSource(Microsoft.SemanticKernel)// 自动捕获 SK 的 spans.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(serviceName,serviceVersion:serviceVersion)).AddOtlpExporter())// 发送到 Jaeger、Aspire Dashboard 等.WithMetrics(metricsmetrics.AddAspNetCoreInstrumentation().AddHttpClientInstrumentation().AddMeter(serviceName).AddPrometheusExporter());// 可选暴露 Prometheus 端点1.3 为 AI 组件添加自定义 Span我们需要在关键位置创建自定义 Span记录模型调用、Token 用量等关键信息。创建 ActivitySourcepublicstaticclassAIDiagnostics{publicstaticreadonlyActivitySourceActivitySourcenew(EnterpriseAI.AI,1.0.0);}在 Semantic Kernel 调用中添加 Span由于 Semantic Kernel 内部已支持 OpenTelemetry通过HttpClient的自动追踪但我们可以包装 Invoke 方法以添加更细粒度的信息。publicclassObservableKernel{privatereadonlyKernel_kernel;publicasyncTaskstringInvokePromptWithTelemetryAsync(stringprompt,KernelArguments?argumentsnull){usingvaractivityAIDiagnostics.ActivitySource.StartActivity(AI Invoke,ActivityKind.Server);activity?.SetTag(prompt,prompt);activity?.SetTag(arguments,arguments?.ToString());varstopwatchStopwatch.StartNew();try{varresultawait_kernel.InvokePromptAsync(prompt,arguments);stopwatch.Stop();activity?.SetTag(duration_ms,stopwatch.ElapsedMilliseconds);activity?.SetTag(output_length,result.Length);// 记录 Token 用量需从响应中提取// 注意OpenAI SDK 返回的 Completions 对象包含 Usage 信息// 这里仅示例实际需要从 Kernel 返回的结果中解析returnresult;}catch(Exceptionex){activity?.SetStatus(ActivityStatusCode.Error,ex.Message);throw;}}}对于 ONNX Runtime 推理也可以添加类似的 Span。在插件方法中添加 SpanpublicclassWeatherPlugin{[KernelFunction]publicasyncTaskstringGetWeatherAsync(stringcity){usingvaractivityAIDiagnostics.ActivitySource.StartActivity(WeatherPlugin.GetWeather);activity?.SetTag(city,city);try{varweatherawait_weatherService.GetAsync(city);activity?.SetTag(result,weather);returnweather;}catch(Exceptionex){activity?.SetStatus(ActivityStatusCode.Error,ex.Message);throw;}}}1.4 记录 Token 消耗与成本Token 消耗是 LLM 调用的核心指标。Azure OpenAI 和 OpenAI 的响应中都包含Usage对象。在 Semantic Kernel 中可以通过配置OpenAIPromptExecutionSettings并捕获响应来提取。自定义 DelegatingHandler 捕获响应publicclassTokenUsageHandler:DelegatingHandler{publicstaticreadonlyMeterMeternew(EnterpriseAI.AI);publicstaticreadonlyCounterintInputTokenCounterMeter.CreateCounterint(ai.input_tokens);publicstaticreadonlyCounterintOutputTokenCounterMeter.CreateCounterint(ai.output_tokens);publicstaticreadonlyHistogramdoubleRequestDurationMeter.CreateHistogramdouble(ai.request_duration);protectedoverrideasyncTaskHttpResponseMessageSendAsync(HttpRequestMessagerequest,CancellationTokencancellationToken){varstopwatchStopwatch.StartNew();varresponseawaitbase.SendAsync(request,cancellationToken);stopwatch.Stop();// 尝试解析响应中的 usagevarcontentawaitresponse.Content.ReadAsStringAsync();if(response.IsSuccessStatusCodeTryExtractUsage(content,outvarinputTokens,outvaroutputTokens)){InputTokenCounter.Add(inputTokens);OutputTokenCounter.Add(outputTokens);RequestDuration.Record(stopwatch.Elapsed.TotalSeconds);}// 重新包装内容流因为已经读取了一次response.ContentnewStringContent(content);returnresponse;}privateboolTryExtractUsage(stringjson,outintinputTokens,outintoutputTokens){// 解析 OpenAI/Azure OpenAI 响应格式// 示例{usage:{prompt_tokens:10,completion_tokens:20,total_tokens:30}}inputTokensoutputTokens0;try{vardocJsonDocument.Parse(json);if(doc.RootElement.TryGetProperty(usage,outvarusage)){inputTokensusage.GetProperty(prompt_tokens).GetInt32();outputTokensusage.GetProperty(completion_tokens).GetInt32();returntrue;}}catch{}returnfalse;}}在注册 HttpClient 时添加此 Handlerbuilder.Services.AddHttpClient(AzureOpenAI).AddHttpMessageHandlerTokenUsageHandler();1.5 集成可观测性后端本地开发使用 .NET Aspire Dashboard 或 Jaeger 查看 traces。生产环境导出到 Azure Application Insights、AWS X-Ray、Datadog 或 Prometheus Grafana。导出到 Application InsightsAzure Monitordotnetaddpackage OpenTelemetry.Exporter.AzureMonitor配置builder.Services.AddOpenTelemetry().WithTracing(tracingtracing.AddAzureMonitorTraceExporter(options{options.ConnectionStringYour Application Insights Connection String;}));2 模型的 A/B 测试与灰度发布策略2.1 为什么需要 A/B 测试模型迭代速度快新模型可能在准确率、延迟、成本上有差异。直接全量替换风险极高。通过 A/B 测试我们可以比较不同模型版本的效果如 GPT-3.5 vs GPT-4验证新提示词模板的效果评估不同部署后端如本地 vs 云端的性能2.2 构建实验框架设计一个简单的实验框架支持按用户、租户或随机流量分配变体。定义实验配置publicclassExperimentConfig{publicstringName{get;set;}model_ab_test;publicListVariantVariants{get;set;}new();publicstringDefaultVariant{get;set;}}publicclassVariant{publicstringName{get;set;}publicintWeight{get;set;}// 流量权重百分比publicstringModelName{get;set;}publicstringPromptTemplate{get;set;}publicDictionarystring,objectParameters{get;set;}}配置存储在appsettings.json{Experiments:{model_ab_test:{Variants:[{Name:gpt35,Weight:50,ModelName:gpt-3.5-turbo,PromptTemplate:default},{Name:gpt4,Weight:50,ModelName:gpt-4o,PromptTemplate:default}],DefaultVariant:gpt35}}}实验分配器publicinterfaceIExperimentAssigner{VariantGetVariant(stringexperimentName,stringuserId);}publicclassRandomExperimentAssigner:IExperimentAssigner{privatereadonlyIConfiguration_config;privatereadonlyRandom_randomnew();publicVariantGetVariant(stringexperimentName,stringuserId){varexpConfig_config.GetSection($Experiments:{experimentName}).GetExperimentConfig();if(expConfignull)returnnull;// 可以基于 userId 实现确定性哈希保证同一用户始终看到同一变体varhash(userId?.GetHashCode()??0)%100;intcumulative0;foreach(varvariantinexpConfig.Variants){cumulativevariant.Weight;if(hashcumulative)returnvariant;}returnexpConfig.Variants.FirstOrDefault(vv.NameexpConfig.DefaultVariant);}}在服务中使用publicclassChatService{privatereadonlyIExperimentAssigner_assigner;privatereadonlyKernelFactory_kernelFactory;publicasyncTaskstringChatAsync(stringuserMessage,stringuserId){varvariant_assigner.GetVariant(model_ab_test,userId);varkernel_kernelFactory.Create(variant);varresultawaitkernel.InvokePromptAsync(variant.PromptTemplate,new(){[input]userMessage});// 记录实验数据见下节returnresult;}}2.3 灰度发布策略灰度发布是逐步将流量切换到新版本的过程。与 A/B 测试不同灰度通常有一个明确的目标全量切换。我们可以利用相同的实验框架通过调整权重实现初始新模型权重 0%旧模型 100%。灰度 1%新模型 1%旧模型 99%。灰度 10%新模型 10%旧模型 90%。…直至 100%。自动化灰度结合监控指标错误率、延迟当新版本指标异常时自动回滚。2.4 实验数据收集与评估A/B 测试需要收集关键指标如业务指标用户满意度点赞/点踩、任务完成率技术指标延迟、Token 消耗、错误率成本指标每次请求的成本将实验变体信息添加到 OpenTelemetry span 的标签中usingvaractivityAIDiagnostics.ActivitySource.StartActivity(Chat);activity?.SetTag(experiment,model_ab_test);activity?.SetTag(variant,variant.Name);在后端如 Application Insights中可以通过这些标签进行分组分析比较不同变体的表现。3 成本控制Token 经济学与缓存策略3.1 Token 成本构成LLM 的成本与 Token 数量直接相关输入 Token提示词、历史对话、检索的上下文输出 Token模型生成的回答3.2 缓存策略1. 精确缓存Exact Match Cache对于完全相同的用户问题直接返回缓存的结果。适用于 FAQ 类场景。publicclassExactMatchCache{privatereadonlyIMemoryCache_cache;privatereadonlyTimeSpan_ttl;publicasyncTaskstringGetOrAddAsync(stringuserInput,FuncTaskstringfactory){returnawait_cache.GetOrCreateAsync(userInput,asyncentry{entry.AbsoluteExpirationRelativeToNow_ttl;returnawaitfactory();});}}2. 语义缓存Semantic Cache对于语义相似但表述不同的问题可以基于向量相似度判断是否命中缓存。将问题向量化计算与缓存项的相似度超过阈值则返回。publicclassSemanticCache{privatereadonlyIVectorDatabase_vectorDb;privatereadonlyITextEmbeddingGenerationService_embeddingService;publicasyncTaskstring?GetAsync(stringuserInput,floatsimilarityThreshold0.95f){varembeddingawait_embeddingService.GenerateEmbeddingAsync(userInput);varresultsawait_vectorDb.SearchAsync(embedding.ToArray(),topK:1);if(results.Any()results[0].ScoresimilarityThreshold){returnresults[0].Metadata[response];}returnnull;}publicasyncTaskSetAsync(stringuserInput,stringresponse){varembeddingawait_embeddingService.GenerateEmbeddingAsync(userInput);await_vectorDb.AddAsync(Guid.NewGuid().ToString(),response,embedding.ToArray(),metadata:newDictionarystring,object{[response]response});}}3. 提示词压缩对于包含大量上下文的 RAG 应用可以对检索到的内容进行摘要或提取关键句子减少输入 Token。publicasyncTaskstringSummarizeContextAsync(stringcontext,intmaxTokens){varprompt$Summarize the following context in less than{maxTokens}tokens:\n{context};varsummaryawait_kernel.InvokePromptAsync(prompt);returnsummary;}3.3 模型选择策略根据任务复杂度动态选择模型简单任务如情感分析、分类使用轻量级模型GPT-3.5-turbo 或本地小模型复杂任务如代码生成、逻辑推理使用 GPT-4o可批处理的任务使用异步批处理模式降低单次调用成本3.4 成本监控与告警在 OpenTelemetry 指标中增加成本指标publicstaticclassCostMetrics{publicstaticreadonlyHistogramdoubleRequestCostMeter.CreateHistogramdouble(ai.request_cost,USD);}// 在 TokenUsageHandler 中计算成本并记录varcost(inputTokens*0.000005)(outputTokens*0.000015);// 示例单价CostMetrics.RequestCost.Record(cost,newTagList{{model,modelName}});在 Prometheus/Grafana 中建立成本仪表板并设置预算告警如日成本超过 $100 时触发。3.5 限流与配额管理防止单个用户或租户滥用导致成本激增publicclassRateLimiter{privatereadonlyIMemoryCache_cache;publicboolIsAllowed(stringuserId,intmaxTokensPerDay){varkey$user_tokens_{userId};vartodayTokens_cache.Getint(key);if(todayTokensmaxTokensPerDay)returnfalse;// 在请求后增加 Token 计数returntrue;}publicvoidRecordTokens(stringuserId,inttokens){varkey$user_tokens_{userId};_cache.Set(key,_cache.Getint(key)tokens,TimeSpan.FromDays(1));}}总结OpenTelemetry 埋点为 AI 链路添加分布式追踪和指标记录 Token 消耗和延迟。A/B 测试与灰度发布通过实验框架安全地迭代模型和提示词降低变更风险。成本控制通过缓存、模型选择、限流等策略在保证体验的前提下优化成本。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2488828.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!