晚上八点,一家面馆忙得不可开交。顾客点了“牛肉面,少辣多葱”,前台在小票上记下原话;后厨大厨按菜单起锅烧油,需要牛肉时让跑堂去隔壁肉铺取货;最后装盘上桌。整个过程看似顺畅,但若顾客投诉“面等太久”,店长怎么知道是前台记错、大厨手慢、还是肉铺缺货?答案是一张能实时显示每张订单走到哪一步、每步花了多久的“厨房监控屏”。 大模型应用和这家面馆一模一样:用户的一条指令(Prompt)进入应用后,会依次经过提示词加工、模型推理、工具调用、响应输出多个环节,这就是“调用链路”(Trace)。LLM Trace 可视化要做的,就是给每个环节盖上时间戳、记下消耗,再把整条链路画成清晰的时间线,让开发者像店长看监控屏一样,一眼定位到“哪一步慢了、哪一步失败了、花了多少钱”。
学完本节,你将掌握 LLM 调用链路的核心概念与可视化方法论,能借助 LangSmith、Phoenix 等工具构建大模型应用的可观测性体系,成为团队中懂得“给 AI 看病”的 MLOps 关键角色。
可视化演示
本节配有交互动效(进入视口后自动加载,约 3KB)。
LLM Trace 瀑布图可视化:展示 Prompt 构建、模型推理、工具调用、后处理各环节的延迟分布和 Token 消耗。
调用链路全景
面馆的订单小票从进店那一刻起,就被盖上了时间戳:几点几分前台接单、几点几分大厨起锅、几点几分跑堂出门取肉、几点几分装盘上桌。店长不用跑去后厨逐一追问,只要看一眼监控屏上的小票流转图,就知道整家店此刻在忙什么。LLM 应用里的“订单小票”就是 Trace(调用链路),小票上的每一道工序记录叫做 Span(环节片段)。
一次典型的 LLM 调用,用户只看到“输入一句话、输出一段回答”,但背后是一条完整的调用链:提示词输入捕获、模型推理、可能的工具调用、最后的响应输出。Trace 把这条链路上的每个环节都记录下来,包括开始时间、结束时间、输入输出摘要、Token 用量和状态码;Span 之间还有父子嵌套关系——比如“工具调用”内部又包含“请求搜索 API”和“解析搜索结果”两个子 Span。
在 LangSmith、Phoenix 这类工具里,每一条 Trace 都有全局唯一的 trace ID,对应一次用户请求;链路中的每个 Span 也有自己的 span ID 和父 Span ID。这些 ID 把分散在日志、中间件、模型服务中的数据串成一条可以回放的时间线。量化一次请求快不快,最朴素的指标就是链路总延迟,它可以拆解为各环节耗时的总和。
有了这份“小票分解账”,店长就能指着监控屏说:不是厨师慢,是跑堂去肉铺那趟花了 1.2 秒。LLM 应用同理,总延迟只能告诉你“慢”,Trace 才能告诉你“慢在哪一环”。
- L_total(链路总耗时)一次请求从发出到收到完整响应的总时长,单位通常为毫秒。
- 求和符号把调用链上所有环节的耗时逐一累加。
- 四项展开最常见的四个环节——提示词输入、模型推理、工具调用、响应输出——相加得到总延迟。
提示词输入追踪
面馆前台有个好习惯:客人说“牛肉面少辣多葱”,她会把“少辣”“多葱”这两个定制要求原封不动写在小票上,后厨才不会做成默认口味。提示词输入追踪做的正是这件事——把用户发的原始指令连同参数一起留档,而不是等模型跑完只剩一句“已处理”。
很多 LLM 应用不是把用户的话直接丢给模型,而是套进一个提示词模板:模板里挖好槽位,把用户输入和系统指令、上下文数据填进去。比如客服机器人会拼出“你是客服小助手,用户的问题是:{},请用礼貌语气回答”。Trace 会记录模板渲染前后的差异:原始输入是什么、最终发给模型的长提示词是什么、用到了哪个版本的模板。
记录输入不只是为了审计,更是为了复现和对比。线上出问题时,如果没有输入快照,你根本不知道模型是被哪句怪话带偏的。LangSmith 会在 Trace 详情里展示每一步的输入输出,并给模板打上版本号,方便团队回滚或 A/B 对比。此外,提示词的规模直接影响计费——模型按 Token 收费,Token 是文本被切分成的最小单位。
就像小票上会同时记录“客人要了什么(输入)”和“后厨做了什么(输出)”一样,Token 账本把前后两本账分开记,哪边超支一目了然。
- T_total(总 Token 数)一次请求计费时使用的 Token 总量。
- T_prompt(输入 Token)用户指令和模板合并后发送给模型的文本,切词后得到输入 Token 数。
- T_completion(输出 Token)模型生成的回答被切词后的数量。
模型推理监控
大厨是店里最贵的资产,也是最大的煤气消耗户。监控屏专门给他装了一块“燃气表”和一块“秒表”:炒一碗面用了多少方气(Token)、花了多少秒(延迟),都要记在小票上。模型推理环节正是 LLM 调用链上的“大厨”——它决定了响应质量,也吃掉了大部分延迟和费用。
模型推理监控围绕三个数字展开:延迟、Token、成本。延迟看分布而不是看单次:线上请求忽快忽慢时,P50、P95 延迟能告诉你大多数用户是快是慢、最差的情况有多糟。Token 则分输入输出两类,OpenAI 等厂商的计费表里,输入和输出单价往往不同,输出通常更贵。最后把三者相乘相加,就得到单次调用的成本。
成本公式看起来吓人,其实就是一个“单价 × 数量”的乘法。价格通常以每百万 Token(1M tokens)计,所以要除以 1000 换算成每千 Token 再乘消耗量。Trace 工具会在每个推理 Span 上标注 model 名称、输入输出 Token 数,LangSmith 还能按天聚合出“哪个模型花了多少钱”。
知道了每道菜的燃气成本,店长就能算出“爆炒系列”是不是该涨价、要不要换成节能灶——对应到工程上,就是根据 Trace 数据决定是否换更便宜的模型、精简输入。
- Cost(单次调用成本)这一次模型请求花了多少钱。
- 输入 Token 费用输入 Token 数量除以 1000,再乘以输入单价。
- 输出 Token 费用输出 Token 数量除以 1000,再乘以输出单价。
- 两项相加输入部分与输出部分合计为总成本。
工具调用记录
面馆的招牌面需要特制牛肉,后厨自己没备货,跑堂得去隔壁肉铺买。肉铺不是自家开的,可能排队、可能缺货,这一来一回的时间全记在小票上。LLM 应用里的“跑堂”就是工具调用:模型遇到不会算的数学题、查不到的信息、需要操作的外部系统时,会发起一次函数调用(Function Calling),把结果拿回来再继续生成。
工具调用是 LLM 应用中不确定性最高的环节——它的耗时和成败往往不归模型管,而归搜索引擎、数据库、第三方 API 管。Trace 必须记录工具名、传入参数、返回结果摘要和状态码。工具调用失败时,工程上常见的做法是有限重试(比如最多 2 次),仍失败就走兜底回复,而不是让用户干等。
在可视化时间线上,工具调用通常表现为模型推理下方的一个子 Span,内部再嵌套网络请求、解析等子 Span。多次工具调用的平均耗时是有价值的健康指标:它突然升高,往往意味着下游服务变慢或被限流。Phoenix 会把工具调用的输入输出单独抽出来,方便检查“模型是否把参数传错了”。
店长如果发现最近肉铺平均要 5 分钟才送到,就知道该换供应商了——工具调用的平均耗时,就是提醒你“外部依赖是否该换”的温度计。
- 平均工具耗时多次工具调用的平均延迟,衡量外部依赖的健康度。
- 算术平均1 除以调用次数 m,表示“取平均”。
- 求和把第 1 次到第 m 次工具调用的耗时全部累加。
响应输出分析
客人坐下后最焦虑的是“我的面好了吗”。就算整碗面要 10 分钟,只要 2 分钟内先端上一碟小菜,客人的体验就好很多。LLM 应用也讲究“先出声”:模型不是憋 10 秒一次性吐完,而是像打字机一样逐字生成,第一个字出来的速度,直接决定了用户觉得“快不快”。
这个指标叫 TTFT(Time to First Token,首字延迟):从用户发出请求到收到模型返回的第一个 Token 的耗时。首字延迟取决于排队时间、网络往返、模型预处理和首轮推理速度。Trace 会把请求发出时刻和首个 Token 返回时刻都记录下来,让这两个时间戳的差无可争议。响应输出的另一面是质量和长度:输出 Token 越多,费用越高、等待越久,所以工程上常给 max_tokens 设上限。
输出环节还包含校验:检查答案是否为合法 JSON、是否包含敏感词、是否符合格式要求。校验失败时,好的应用会走兜底策略——重试一次或返回预设文案,而不是把错误裸奔给用户。Trace 会记录校验结果与兜底是否被触发,帮助团队统计“模型答非所问”的真实比例。
“先上小菜再等主菜”的策略,让客人不再盯着空桌面;监控 TTFT 就是确保每一桌都能及时看到“锅气”。
- TTFT(首字延迟)衡量应用响应体感的关键指标。
- t_first(首字到达时刻)客户端收到模型第一个输出 Token 的时间点。
- t_request(请求发出时刻)用户请求离开客户端的时间点。
- 作差用到达时刻减去发出时刻,差值即首字延迟。
可视化工具
前面讲的所有“小票记录”,如果只是锁在抽屉里,店长永远发现不了问题。可视化工具就是那面挂在墙上的监控屏:把每张订单的流转、每道工序的耗时画成图表。LangSmith 和 Phoenix 就是 LLM 领域最常用的两块“监控屏”。
LangSmith 由 LangChain 团队开发,提供云端一站式追踪:注册后埋点 SDK,就能在网页上看到每条 Trace 的输入输出、Token 消耗、延迟瀑布,还能把调试样本转成数据集做回归测试。Phoenix 则是开源自托管的可观测性平台,强调本地部署和数据私有化,适合对数据合规要求高的团队。两者的核心视图殊途同归:瀑布图(Waterfall)和火焰图(Flame Graph)。
瀑布图是最直观的 Trace 视图:一条横向时间轴,根 Span 是一条大横条,子 Span 在下方按时间位置错落排开,宽度即耗时。火焰图则把每个 Span 的耗时占比叠放成“横条堆叠”,一眼看出哪一段最宽——最宽处往往就是瓶颈。占比的计算很简单:某个环节耗时除以总耗时。
监控屏不是挂了就完事,它要让人 3 秒内看懂问题在哪。瀑布图和火焰图能做到这一点,正因为它把“哪里最宽”用最直白的方式怼到你眼前。
- 占比 R_i第 i 个环节在整条链路中的耗时百分比。
- 分子(环节耗时)被考察环节独自消耗的时间。
- 分母(总耗时)所有环节耗时的总和。
- 乘以 100%把比值换算成百分比形式。
异常定位优化
监控屏最大的价值不在平时,而在出事那 5 分钟:深夜突然有 3 桌客人同时投诉“面没上”,店长扫一眼屏,发现跑堂去肉铺的订单全卡在“缺货”状态。异常定位就是顺着 Trace 找到“卡住的订单”,优化则是从根上解决:给肉铺装个库存告警、或者干脆换一家供货商。
异常定位的第一步是成功率统计:把一段时间内成功响应与总请求数相除,低于阈值就告警。第二步是延迟分布,P95 比平均值更靠谱——少数慢请求把平均值拉高时,P95 能暴露“最差的一批人到底有多慢”。第三步是看 Trace 细节:把失败的 Trace 按错误类型聚合,最常见的错误就是优化的方向。
优化手段和厨房如出一辙:缓存相当于“把常点的招牌面提前备好料”,命中后模型都不用跑;精简提示词相当于“少放点配料”——输入 Token 少了,费用和延迟一起降;简单任务换小模型相当于“炸薯条不用请大厨”,成本能省一个量级。LangSmith 的 Trace 对比功能可以并排看优化前后的耗时和成本,让每次优化都有数据背书。
面馆靠监控屏熬过了最忙的晚高峰,靠的是“发现缺货 → 预制备料 → 换供应商”的闭环;LLM 应用也要跑通“监控 → 定位 → 优化”的循环,Trace 可视化正是这个循环的起点和终点。
- 成功率 S链路监控中最基础的健康指标。
- 成功次数 N_success返回正常结果且无报错的请求数。
- 总请求数 N_total统计周期内全部请求数(含失败)。
- 百分比换算相除后乘以 100% 得到百分比。
AI 应用场景
场景一:客服机器人的“事故复盘”
阶段:生产环境监控与排障
解决的问题:客服机器人上线后收到大量差评,传统日志只能看到“调用失败”四个字,无法回答“是哪一步失败、失败前模型看到了什么”。
具体案例:某电商客服机器人出现“答非所问”,工程师在 LangSmith 上打开失败的 Trace,发现是工具调用环节把商品 ID 传反了参数,导致查询结果与问题无关。修复参数映射后,差评率一周内下降 60%。
场景二:A/B 选型与成本审计
阶段:模型选型与成本优化
解决的问题:团队不知道该用 GPT-4o 还是更便宜的小模型,也不知道每月 Token 钱花在哪。
具体案例:通过 Phoenix 聚合一周的 Trace 数据,发现 70% 的请求属于“摘要归类”类简单任务,但全部走了大模型。改用小模型 + 缓存后,单日推理成本下降 45%,P95 延迟从 2.1s 降到 1.2s。
场景三:金融合规审计留痕
阶段:安全合规与审计
解决的问题:金融行业要求对 AI 回复的每句话都能追溯“它依据了什么、经过了哪些处理环节”。
具体案例:某银行智能投顾系统接入 Trace 后,每次对话都完整保留提示词原文、工具调用的行情数据源、模型版本和输出校验结果,稽查时可一键导出某个客户全部会话的调用链。
Python 演示
# -*- coding: utf-8 -*-"""简易 LLM 调用链路追踪器:模拟一次带工具调用的问答,输出各环节耗时与成本"""import randomimport timefrom collections import OrderedDict class Span: """一个环节的耗时记录,相当于厨房里的一道工序""" def __init__(self, name): self.name = name self.start = None self.end = None self.children = [] def __enter__(self): self.start = time.time() return self def __exit__(self, exc_type, exc_val, exc_tb): self.end = time.time() @property def duration_ms(self): return (self.end - self.start) * 1000 class Tracer: """链路追踪器:像订单小票一样记录整条调用链的嵌套关系""" def __init__(self, trace_name): self.trace_name = trace_name self.root = Span(trace_name) self.root.start = time.time() self._stack = [self.root] def span(self, name): child = Span(name) self._stack[-1].children.append(child) self._stack.append(child) return child def end_span(self): self._stack.pop() def finish(self): self.root.end = time.time() def to_dict(self): def conv(s): return OrderedDict([ ("name", s.name), ("duration_ms", round(s.duration_ms, 2)), ("children", [conv(c) for c in s.children]), ]) return conv(self.root) def call_llm(prompt, trace): """模拟一次 LLM 调用链:输入-推理-工具-输出,返回成本""" with trace.span("prompt_capture"): time.sleep(random.uniform(0.01, 0.03)) prompt_tokens = max(1, len(prompt) // 2) with trace.span("model_inference"): time.sleep(random.uniform(0.1, 0.4)) completion_tokens = random.randint(80, 200) with trace.span("tool_query"): time.sleep(random.uniform(0.05, 0.2)) with trace.span("response_output"): time.sleep(random.uniform(0.01, 0.02)) cost = prompt_tokens * 0.02 / 1000 + completion_tokens * 0.06 / 1000 return cost def main(): results = [] for i in range(5): trace = Tracer("customer_query") cost = call_llm("帮我查一下上海明天的天气", trace) trace.finish() total_ms = trace.root.duration_ms results.append((total_ms, cost)) print("调用 %d 总耗时 %.1f ms,成本 $%.5f" % (i + 1, total_ms, cost)) print(trace.to_dict()) print() times = sorted(r[0] for r in results) avg = sum(times) / len(times) p95 = times[-1] if len(times) < 20 else times[int(len(times) * 0.95) - 1] total_cost = sum(r[1] for r in results) print("平均耗时 %.1f ms,P95 耗时 %.1f ms,总成本 $%.5f" % (avg, p95, total_cost)) if __name__ == "__main__": main()
self._stack = [self.root]用栈保存嵌套上下文,新 Span 总是挂在栈顶父节点之下。def span(self, name):创建子 Span 并压栈,返回对象供 with 语句使用。with trace.span("model_inference"):模拟模型推理环节,进入时记开始时间、退出时自动记结束时间。cost = prompt_tokens * 0.02 / 1000 + completion_tokens * 0.06 / 1000按输入 0.02、输出 0.06 美元每千 Token 计算单次成本。trace.finish()给根 Span 盖上结束时间戳,总耗时才可以计算。print(trace.to_dict())把嵌套链路序列化为字典打印,相当于把订单小票摊开给人看。p95 = times[-1] if len(times) < 20 else times[int(len(times) * 0.95) - 1]样本不足 20 时用最大耗时近似 P95,防止索引越界。
常见误区
误区我只要把每一步都 print 出来,出了问题翻日志就行,没必要用 Trace 工具。
正解日志是一条条孤立的“流水账”,每行之间没有关联;Trace 则有全局 trace ID 把各个环节串成树形结构。好比厨房里每道工序各写各的记录,找“这碗面为什么慢”时还得凭时间瞎猜;监控屏把订单小票按 ID 串起来,一查便知。日志是素材,Trace 是关系图,分布式系统里没有关联的日志等于没有日志。
为何常见:小项目里日志确实够用,一旦并发请求变多、链路变深,日志排序混乱的问题才会暴露,初学者容易低估关联性的价值。
误区接口变慢了,肯定是模型推理拖后腿,换个更快的模型就行。
正解Trace 数据常常打脸:瓶颈可能是工具调用——一个慢速搜索 API 吃掉 60% 的延迟;也可能是排队、网络重试甚至 JSON 解析。就像客人抱怨上菜慢,老板第一反应怪大厨,结果发现是跑堂去肉铺堵车了。不拆分环节就优化,等于盲人摸象。
为何常见:模型推理名词最响、占比最大,直觉上最“像”瓶颈,而工具调用、序列化这些环节容易被忽视。
误区埋点会影响线上的性能,测不准还拖慢应用,不如不搞。
正解成熟方案的采样策略把开销控制在 1% 以内——只记录部分请求、异步上报、只存摘要不存全量原文。监控系统本身也要“记账”,但记账的成本远低于事故排查的成本。就像监控屏本身耗不了几度电,却能让整个厨房少出乱子。
为何常见:听过“监控有侵入性”的说法,但没有区分“全量同步记录”和“采样异步上报”两种截然不同的实现。
误区用户只看模型回答,生成的输出才是主要成本,输入不用管。
正解输入 Token 同样计费,而且很多应用输入比输出长得多——把整篇文档塞进提示词做总结,输入可能占账单的 70% 以上。公式是成本 = 输入价 + 输出价,两边都要记账。好比客人点菜时一次性说了 20 个要求,前台誊写的小票纸也是要花钱的。
为何常见:直觉上“回答是模型最费力的部分”,忽略了 Token 计费按字面量计算、与模型算力上的“费不费力”无关。
面试题
Q1. LLM Trace 的核心作用是什么?
- A.提升模型自身准确率
- B.追踪请求的完整调用链← 正确答案
- C.辅助提示词工程调优
- D.压缩模型的参数量
答案:B。Trace 的价值在于把一次请求经过的各个环节记录并串联起来,让开发者看清“慢在哪、错在哪”,而不是直接提高模型准确率。它解决的是可观测性问题,与模型调优和压缩无关。
Q2. 在链路追踪中,"Span"指的是什么?
- A.一个独立子环节的计时段← 正确答案
- B.一条完整的业务日志
- C.模型的词表文件内容
- D.工具的接口认证密钥
答案:A。Span 是链路中的一个环节片段,记录该环节的开始时间、结束时间、属性等;多个 Span 通过父子关系组成一条 Trace。日志是文本记录,词表和密钥与追踪无关。
Q3. 关于 Token 消耗的计算,下列说法正确的是?
- A.输出 Token 不计费用
- B.总 Token 为输入加输出← 正确答案
- C.输入输出 Token 必相等
- D.Token 与延迟无关联
答案:B。总 Token 数 = 输入 Token + 输出 Token,两者分别计费。输入输出长度没有必然相等的约束;Token 数量影响计费,也常与生成耗时正相关。
Q4. “首字延迟(TTFT)”衡量的是什么?
- A.生成最后一个字的耗时
- B.请求到首个字的耗时← 正确答案
- C.工具调用的往返耗时
- D.提示词模板渲染耗时
答案:B。TTFT 是从用户发出请求到收到模型第一个 Token 的耗时,决定用户的第一感受。生成结尾耗时是总延迟的一部分,工具往返和模板渲染只是链路中的子环节。
Q5. 下列属于 LLM 调用链主要环节的是?
- A.Prompt 输入捕获← 正确答案
- B.模型推理处理← 正确答案
- C.工具调用执行← 正确答案
- D.模型训练任务
答案:ABC。调用链覆盖一次推理请求的生命周期:输入捕获、模型推理、工具调用、响应输出。模型训练属于离线开发阶段,不在线上单次请求的调用链内。
Q6. 某次请求中“模型推理”耗时 800ms,“工具调用”耗时 1200ms,“输出”耗时 300ms,总链路 2300ms,瓶颈是?
- A.模型推理环节
- B.工具调用环节← 正确答案
- C.响应输出环节
- D.提示词捕获环节
答案:B。四个给定环节中工具调用耗时 1200ms 最长,占总耗时一半以上,是首要优化对象。瓶颈判断看各环节耗时占比,而非名称是否“重要”,这正是 Trace 拆分归因的价值。
Q7. 关于火焰图在 Trace 可视化中的作用,下列说法正确的是?
- A.展示各环节耗时占比← 正确答案
- B.识别最长耗时调用路径← 正确答案
- C.替代日志存储原始文本
- D.帮助定位性能瓶颈点← 正确答案
答案:ABD。火焰图把各 Span 耗时段按占比叠放,最宽的横条即瓶颈路径,用于快速定位性能问题;它是分析视图而非存储系统,不能替代日志保存原始文本。
Q8. 工具调用失败后,工程上常见的可靠处理策略是?
- A.直接报错返回给用户
- B.记录重试并兜底回复← 正确答案
- C.忽略失败继续生成
- D.无限重试直到成功
答案:B。可靠做法是记录失败原因 + 有限次重试 + 兜底回复,避免用户看到裸错误。直接报错体验差,忽略失败会污染生成结果,无限重试会拖垮下游服务。
Q9. P95 延迟的含义是?
- A.九成五请求延迟低于此值← 正确答案
- B.最慢 95 个请求平均延迟
- C.九成五请求都会失败
- D.所有请求延迟的中位值
答案:A。P95 表示排序后 95% 位置的延迟值,即 95% 的请求比它快。它比平均值更能暴露长尾慢请求,用于评估最差一批用户的真实体验。
Q10. 下列哪些手段可以有效降低 LLM 应用的 Token 成本?
- A.精简提示词减少冗余← 正确答案
- B.复用缓存结果避免重复← 正确答案
- C.简单任务选用小模型← 正确答案
- D.完全关闭输出长度上限
答案:ABC。精简输入 Token、缓存命中避免重复计费、简单任务用小模型(单价低、输出少)都是降本手段。完全关闭输出上限会让单次成本失控,反而推高费用。