企业级LLM应用监控实战:成本追踪与性能分析从零搭建
发布时间:2026/10/1 17:41:31来源:尧图网络
最近我们团队负责的智能客服系统正式接入了多个大模型日均请求量在几千到上万之间波动。一开始大家只关心效果直到月底财务把账单甩到群里——光调用模型就花了六位数而且没有任何明细能说清楚是哪条业务线、哪个用户、哪个场景烧了这么多钱。同时客服那边开始抱怨系统时不时卡顿有的用户等十几秒才收到首个字。我觉得不能再拍脑袋优化了于是用两周时间从零搭了一套LLM监控系统核心覆盖成本追踪与性能分析。这套系统上线后成本立刻降了约三成线上告警也能精确到某一个请求ID。这篇文章就把我踩过的坑和具体搭建步骤写出来适合所有已经在用或准备用LLM做业务、又担心费用失控和响应变慢的团队参考。1. 为什么企业级LLM应用必须做监控很多小团队刚开始接大模型接口时只关心“这个模型回得好不好”Token消耗和调用延迟都被当成后置问题。可一旦请求量上来它们两就会反过来咬你一口。先不谈安全合规单说成本和性能这两件事就足以逼你搭建一套正经的LLM监控系统。1.1 成本失控一次对话可能比你想的贵十倍大模型是按Token计费的Token可以粗略理解成“模型读和写的字符片段”一个汉字通常等于1到2个Token一个英文单词约等于1个Token。表面上每个Token单价并不高但实际跑起来后你很快会发现几个隐藏的“吞Token黑洞”第一是上下文续接。你做客服机器人时为了记住对话历史往往会把前面所有轮次的内容重新发给模型。假设用户聊了20轮每轮平均500字再算上系统提示词、格式化结构、工具定义这些内容每次请求都要整体重发。这意味着一个真实业务请求可能消耗的Token是用户输入文本的5倍甚至10倍。第二是重试导致的双倍计费。一个请求如果因超时或网络抖动被触发自动重试大概率会重复计费而且失败的那一次也照样扣钱。第三是不同模型之间的距离。GPT-4级别模型的单位Token价格可能是轻量模型的几十倍稍微用错模型成本差距会非常夸张。我在刚开始统计成本时发现某个内部运营工具每天只有几百次调用但费用占总账单的比例超过四分之一。后来查出来是代码里写死了“必须用最强模型生成摘要”完全没有考虑这个任务其实用轻量模型就能搞定。这个案例很典型没有成本追踪之前这些问题就像藏在棉花里的针外表感觉不到账单一到就戳得人心疼。所以成本追踪的首要目标是“摸清每一分钱去哪了”然后才能谈省钱。1.2 性能风险延迟和错误会直接毁灭产品口碑用户对大模型应用的耐心比传统网页还要低。你加载一个网页花了3秒用户还能忍如果和AI对话时首字迟迟不出来用户立刻会感觉“系统卡住了”。偏客服、问答这类场景尤其明显响应慢一点满意度曲线掉得飞快。LLM请求的延迟有个特点它不是一条固定直线。同样一个模型请求内容短时可能几百毫秒就返回内容长或并发高时就要等到十几秒甚至几十秒。而且大模型供应商的服务端压力你是完全不可见的上一秒还稳定下一秒可能整体变慢。如果没有监控你根本分不清到底是模型供应商出了问题、你的网络链路出了问题还是你的Prompt太长导致生成时间过久。不只延迟错误率同样重要。模型接口返回的错误五花八门有鉴权问题、限流问题、Schema校验失败、上下文长度超限还有各种网关错误。多数错误不会让进程崩溃但会让用户看到一条“AI服务开小差”的提示。长期不监控你的产品就变成了薛定谔的AI——看起来能用但你不知道它什么时候会对特定用户群体发火。1.3 监控也是优化手段不只是记录工具我见过不少团队把监控理解为“事后看报表”实际上监控系统最大的价值是能引导你实时做优化。比如通过性能数据发现某个场景90%的时间都花在“生成第一个Token之前”也就是模型预填充阶段。那很可能是因为你的Prompt带有巨大的工具定义和示例每次都要让模型先“读”一遍。这时候如果把工具定义精简、把上下文裁剪一部分TTFT就能明显下降成本也能同步降低。再比如通过成本追踪发现某个高频调用可以用“语义缓存”命中完全不需要重复请求模型。我在优化之后加了一层缓存命中率大概在30%左右那部分调用成本直接归零。这些都是监控数据反哺业务的好例子。2. 监控系统的整体架构与数据链路拿传统后端监控的思路来搭LLM监控最容易犯的错就是“只做两层”应用埋点上报然后看个曲线。但LLM调用涉及的数据维度比普通API多得多比如模型名、Token用量、Prompt大小、流式状态等而且这些数据横跨了成本与性能两个维度。所以需要一条清晰的数据链路从采集到存储到展示告警都提前设计好。2.1 采集层从一个请求的完整生命周期讲起一个典型的LLM调用过程大概是用户输入进入你的后端服务服务拼装Prompt后调用模型API模型流式返回文本你再把结果转发给前端。在这个过程里每一个环节都应该被埋点。我建议先从中间件开始也就是拦截所有调用模型API的入口和出口。开源的SDK通常自带Usage信息但不要只记usage字段还要记录一个“请求快照”。一个可用的采集结构大致包含这些字段请求ID、时间戳、业务线标识、用户标识、调用来源哪个应用模块。请求的模型名称、供应商名称、接入点名称。请求内容的Token数如果没有现成数值用分词的tokenizer估算以及系统提示词、上下文、用户输入分别占多少。响应内容里的usageprompt_tokens、completion_tokens、FinishReason。性能数据接入前等待时间、网络往返时间、首个Token生成时间、总耗时、是否流式。采集方式可以用两种并行。第一种是同步日志每条请求结束后写一条JSON日志保证完整关联第二种是指标打点把延迟、Token数等聚合成Metrics推到Prometheus用来做实时告警。# 一个轻量的LLM调用中间件伪代码FastAPI风格 async def llm_call_with_metrics(request_ctx: RequestContext): start time.perf_counter() raw_request build_request(request_ctx) response await provider.chat(raw_request) elapsed time.perf_counter() - start record_log( request_idrequest_ctx.request_id, modelraw_request.model, prompt_tokensraw_request.estimated_tokens, completion_tokensresponse.usage.get(completion_tokens, 0), latency_mselapsed, first_token_latency_msresponse.first_token_latency_ms, statussuccess, ) observe_metrics( namellm_request_latency_milliseconds, valueelapsed, labels{model: raw_request.model, scene: request_ctx.scene} ) return response埋点层不要做得太重把明细日志和聚合指标分开处理就好。这样即使指标数据被Prometheus拉走了明细日志仍然能支撑后续查账单、查单个请求的问题。2.2 存储层用明细表支撑查账和分析我把存储分成了两大部分明细日志和分析指标。明细日志负责回答“某个请求发生了什么”这类问题分析指标负责回答“现在整体延迟和成本是什么趋势”。明细日志我推荐用ClickHouse或高配PostgreSQL核心是一张不丢细节的调用账单表。如果你嫌重先用PostgreSQL也能跑但要注意数据膨胀。日志量到每天几十万条之后建议还是用列式存储压缩率高查询聚合也快。一张基本的表结构大概长这样CREATE TABLE llm_call_records ( request_id String, ts DateTime64(3), scene String, user_id String, trace_id String, provider String, model String, prompt_tokens UInt64, completion_tokens UInt64, total_tokens UInt64, unit_cost_usd Decimal(18, 8), total_cost_usd Decimal(18, 8), latency_ms UInt32, ttft_ms UInt32, finish_reason String, status String, error_message String ) ENGINE MergeTree() PARTITION BY toYYYYMMDD(ts) ORDER BY (ts, request_id)很多团队一开始会用ES来存日志也能用但成本与性能分析需要大量按模型、按场景的GROUP BY聚合ES的聚合性能不如ClickHouse。如果你还在起步阶段我的建议是别在存储上投入太多精力先保证完整记录后续再迁移都来得及。2.3 可视化与告警链路Prometheus Grafana 的组合相对来说指标侧我推荐比较常见的Prometheus生态。你的应用通过PrometheusSDK暴露/metrics端点Prometheus定期抓取Grafana负责展示Alertmanager负责发告警。这套组合足够稳定插件丰富而且大家熟悉排障效率高。如果你已经有微服务监控体系直接把指标挂进去就行没必要单独搭一套。指标命名建议统一前缀llm_total_tokens_consumed_total总Token消耗累计llm_cost_usd_total总成本累计llm_request_latency_seconds请求延迟直方图llm_first_token_latency_seconds首Token延迟直方图llm_error_requests_total错误请求计数llm_success_requests_total成功请求计数。有了这些指标Grafana上可以很快画出趋势图。一个比较实用的Dashboard组合是左上角显示按小时成本右上角显示请求量和Token吞吐中间放P50/P95/P99延迟曲线底部放各模型错误率。这个布局能让你一屏看清系统和供应商的健康度。3. 成本追踪从零落地有了前面的数据链路成本追踪就不再是“看完账单拍大腿”而是可以一个请求一个请求地精确核算。3.1 建立“价格表”和全局Token计量做成本追踪第一步是把所有可能用到的模型价格配置成一张“价格表”。价格信息要包含模型名称、供应商、输入单价per 1M tokens、输出单价per 1M tokens、币种。不要相信记忆大模型价格变动频繁最好每次发版前更新一次。在计算成本时需要注意总成本 prompt_tokens * 输入单价 completion_tokens * 输出单价。但很多模型对长上下文还有特殊的“缓存读取价格”比如命中缓存后输入Token价格会更便宜。如果你的系统用了这类能力价格表里要加一个cached_prompt_unit_cost字段单独计算。我踩过的坑是系统早期没有区分输入和输出价格统一按某个“平均价”估算结果和账单对不上差十万块。后来改成精确价格表每个请求在写入明细表时就算好成本月底对账几乎零误差。3.2 多维成本聚合谁花的钱多一眼看出成本追踪的目的不是做一堆报表而是快速定位问题。所以聚合维度非常重要。我用的几个核心维度是业务线scene、用户user_id、模型model、供应商provider、时间粒度小时或天。通过这几个维度几乎可以回答所有常见问题。举一个真实场景业务方问“这几天成本为什么涨了三倍”我不会急着让他们自查而是先跑一条查询SELECT scene, model, sum(total_tokens) AS tokens, sum(total_cost_usd) AS cost_usd FROM llm_call_records WHERE ts now() - INTERVAL 7 DAY GROUP BY scene, model ORDER BY cost_usd DESC LIMIT 20;结果往往是某个新上线的场景用了贵模型或某个老场景的请求量突然大涨。锁定后再点进去看明细很快就能找到具体原因。为了减少查询压力我还会做一个预聚合表按小时和场景粒度把Token、成本、请求数、错误数提前算好。这样做报表和看板时响应快毕竟明细表只用来做深挖。3.3 预算控制与动态配额硬限制和软告警成本追踪不只是“事后看账”更要在预算边界上提前拦截。实现方式分两级软告警和硬限制。软告警是指当某条业务线的日成本超过预设阈值时通过告警渠道通知负责人硬限制则是指当超出预算时在应用层直接拒绝或降级调用避免继续烧钱。硬限制不建议直接用数据库查询来判断因为频繁调用会打爆数据库。我倾向于用一个轻量的Redis计数器方案每天凌晨把预算值写入Redis每个请求结束后在Redis里累加当天已用成本检查累计值是否超过阈值。由于成本计算已经完成这里只是加法和比较性能开销很小。一旦超限服务端可以返回一个自定义错误或自动切换到更便宜的备用模型效果立竿见影。# Redis预算控制示例 cost compute_cost(usage, model_price) today datetime.utcnow().strftime(%Y%m%d) key fcost_budget:{scene}:{today} new_total redis.incrbyfloat(key, cost) if new_total scene_budget_limit[scene]: redis.decrbyfloat(key, cost) # 回滚避免超额 raise BudgetExceededError(scenescene)注意不要把成本判断放在真正调用模型之前因为你不调用就不知道准确的Token用量预算控制会有较大误差。模型调用之后做控制最多只会因为并发导致略微超出一点配合告警完全可以接受。4. 性能分析从零落地性能分析的目标是回答两个问题模型服务“现在到底快不快”以及“如果慢了瓶颈出在哪个环节”。这两问题并不是同一个Dashboard能解决的背后需要不同的指标体系和链路追踪方式。4.1 核心性能指标别只看“平均延迟”很多团队刚开始只看“平均响应时间”这是最容易被迷惑的数字。一次超长的慢请求就能把平均值拉到天上导致你以为全局都慢了。我建议至少围绕几个指标做分析指标含义推荐监控方式TTFTTime To First Token从发出请求到收到第一个Token的耗时分位数 P50/P95/P99TBTToken间延迟流式响应中相邻Token返回的时间间隔关注高百分位总延迟从发出请求到完整响应结束的耗时分位数 按模型对比请求成功率成功响应占总请求比例按场景/模型分组Token生成速率completion_tokens / 总耗时用于衡量模型“输出速度”其中最容易被忽视的是TTFT。流式接口下用户感知的“第一反应速度”就是TTFT它直接决定用户是否认为系统卡顿。总延迟虽然也很重要但流式场景里只要TTFT够快总延迟稍微长一点用户也能接受。用Grafana做监控时Prometheus的Histogram类型可以比较好地支持分位数统计。比如定义llm_first_token_latency_seconds为Histogram配置Bucket从0.01到30秒然后在Grafana里用histogram_quantile(0.99, sum(rate(...)))查询P99。这样你看到的是真实分布不是平均值骗人的幻觉。4.2 在真实请求中埋点与链路追踪如果你已经有OpenTelemetry或SkyWalking建议直接用现成的链路追踪把LLM调用当作一个Span接入。比如在OpenTelemetry里添加一个llm类型的Span记录模型名、Token用量、TTFT、错误信息等。这样当调用链出错时你能同时看到数据库查询耗时、外部API耗时、LLM调用耗时整个请求流程一目了然。没有链路系统也不要紧我早期就只在自己的代码里加了一个简单装饰器把所有关键时间点打印到结构化日志里。重点要记录四个时间点请求开始、进入模型服务商网关前、收到首个Token、收到最终响应。用这四个点相减就能算出“本地排队耗时”“网络/服务端耗时”“生成耗时”。dataclass class LlmTiming: request_start: float gateway_start: float first_token_at: float end_at: float property def queue_ms(self): return (self.gateway_start - self.request_start) * 1000 property def ttft_ms(self): return (self.first_token_at - self.gateway_start) * 1000 property def generation_ms(self): return (self.end_at - self.first_token_at) * 1000注意gateway_start必须在发送HTTP请求之前打点这样能捕捉到连接池等待、DNS解析等本地耗时。很多工具只统计请求结束后拿到的耗时往往丢失了最前端的几毫秒对慢请求排查不够用。4.3 性能瓶颈定位案例这里分享一个线上真实排障案例。某天客服系统反馈“AI回复特别慢”我看Grafana上的总延迟曲线确实从2秒涨到了8秒但TTFT曲线基本没动说明问题出在生成阶段而不是连接或预处理阶段。继续看Token生成速率发现从50 Token/秒降到了10 Token/秒。我第一时间怀疑是不是对应用户的历史消息太长导致模型要读的上下文变大了。查了明细表发现慢请求里记忆历史字符串长度为5000多字是普通请求的3倍。后来我们把历史消息压缩成摘要只保留最近5轮对话TTFT和生成速率都恢复到了正常水平。另一个案例是在某个并发高峰期TTFT突然飙到了15秒。查链路发现本地到模型服务商的网络往返只有200毫秒但服务商网关排队时间超过10秒明显是供应商侧限流或者服务端过载。这时候不是做应用优化能解决的我选择在代码里加了一个“退避重试自动降级到备选供应商”的逻辑大幅缓解了用户体验。所以性能分析一定要把耗时拆开否则你只是在看一个整体风险根本不知道要优化谁。5. 常见问题与排查技巧实录搭建监控系统不是写几个脚本就结束真正磨人是上线后的各种异常。下面这些坑都是我自己踩过或者团队同事踩过的列出来给后面的人当避雷针。5.1 Token统计对不上谁偷了你的Token最典型的问题是本地估算的Token数和供应商账单里的Usage对不上。主要原因包括模型API本身会额外附加一些隐藏指令或JSON格式要求本地没有统计函数调用Function Calling的工具参数也被模型当成输入Token消耗供应商对某些标点、特殊字符的Token化方式和开源Tokenizer细节不同。我的处理方式是“以供应商账单为准”。在每次响应里读response.usage字段而不是本地估算。本地估算只能用来做预判和限额最终成本记录必须以服务端返回为准。如果供应商没返回usage就按模型官方Tokenizer跑一遍估算但要在系统标注为“估算值”。5.2 日志和监控数据疯狂增长存储成本反超调用成本这是监控系统上线后很容易出现的“第二波账单”。一条LLM请求日志如果带上完整Prompt和响应内容动辄几KB按日均十万请求算一天就是几个GB一个月下来存储费用非常可观。我建议做三层处理第一默认不存储完整Prompt只存截断到200字符的样本需要排查时再用TraceID去临时抓取或者只对特定业务线保留完整内容。第二明细表设置TTL例如成本报表保留90天性能指标保留30天超过自动删除。第三对非常高基数的用户分析使用预聚合表而不是明细表。通过这三招我的存储成本控制在总成本的5%以内。5.3 误报告警轰炸如何调参更合理监控告警刚上线时所有人都会被一秒一次的告警弄得神经衰弱。原因很简单你给延迟设置了一个固定的阈值比如P99大于5秒但LLM供应商的延迟天然是波动的白天高峰和凌晨差很多。更合理的告警策略是采用分位数或相对基线计算过去15分钟的延迟相比过去7天同一时间段延迟是否上涨超过50%再触发告警或者只对P99大于某个绝对且明确异常的值比如超过10秒告警。同时要设置告警抑制和分组避免同一个接口故障连续发几百条消息。我们后来还给告警加了“自动静默”逻辑如果同一Trace ID触发了多条告警只保留一条中心告警。5.4 Request failed / Schema invalid 这类供应商异常怎么定位有一类棘手错误是llm request failed: provider rejected the request schema or tool payload。很多团队第一次碰到会以为是自己代码报错但细看会发现是发给模型的工具定义Tool Payload不符合供应商的Schema要求比如工具参数里出现了未声明的类型、重复字段、或者函数名保留字。这种错误和监控系统的关系在于它经常会在某次发布后成批出现非常容易被误判成网络故障。排查方法并不复杂在监控系统里筛出该错误码的请求对比出错请求的工具定义JSON和最近发布记录。通常问题出在新增了一个工具但schema没有同步到所有环境。我会在采集层把Tool Payload的哈希值也记录下来一旦出错可以直接按哈希值聚合快速定位到底哪个工具定义有问题。5.5 定时任务和批处理场景该怎么监控前面讲的都是在线交互请求但很多企业还会用LLM跑离线任务比如批量生成摘要、编辑文案、知识库切分。这些任务的特点是单次请求时间长、批量并发高如果还按在线请求的监控方式容易在高峰期把并发打满导致在线业务受影响。我建议给离线任务单独加一层“并发控制”和“队列指标”。监控时要格外关注批次成功率和平均单条成本如果某批次失败率超过阈值应该立刻暂停避免白白烧钱。成本追踪上离线任务往往是一次性大额消耗如果在聚合维度里不区分任务类型很容易让你误以为业务异常增长。6. 写在最后一点个人工具箱与心得这套系统我们从无到有用了大约两周时间第一周做埋点、存储和看板第二周补告警和预算管控。现在每天各类LLM调用数据都能实时看到成本从“月底抽盲盒”变成了“小时级透明”性能问题也能在用户大面积感知之前被定位到具体请求整体收获非常大。如果你也想做我的建议是不要一开始就追求大而全的工具链。先用一个小服务把结构化日志落好再加上Grafana和Prometheus就已经能覆盖90%的需求。后面根据痛点再逐步加预算控制、链路追踪、语义缓存。我自己的体会是成本追踪和性能分析是同一套数据的一体两面记录好请求级明细两个问题都能解开反而是那些“看起来高大上”的模型路由、动态网关等你数据足够丰富了再考虑也不迟。最后分享一个小技巧不要只盯着模型供应商提供的用量接口最好的监控时机是在你代码调用模型的前后各埋一个点然后把这些信息串成一条完整的请求链。只有这样当账单和体验一起出问题时你才能从容地指着一行日志说“看就是它。”
网站建设高端定制企业官网