用 LangChain 跑起来不难,难的是月底想知道钱花哪了。链条里一层套一层,模型调用、工具调用、重试混在一起,回调里拿到的用量信息各家用得还不一样。这篇给一套能直接改的追踪思路:在哪挂回调、字段怎么存、多出来的开销怎么归因。
用 LangChain 把应用跑起来不难,难的是月底回头看账单,发现钱花出去了但不知道花在哪。链条里一层套一层,模型调用、工具调用、重试、缓存混在一起,回调里拿到的用量字段各家给得还不一样,有人甚至连输出 token 都没拿到就写进了统计。
这篇给一套能直接改的追踪思路:回调挂在哪、每次调用该存什么、怎么把用量换成钱、最后怎么和平台账单对上。目标是让每一次调用都变成日志里的一行可查记录,而不是等出账了才开始猜。
追踪点挂在哪儿:回调是唯一稳的位置
优先用框架自带的回调机制,在模型开始和结束的钩子里取用量,不要跑到业务代码里到处手动加计数器。手动埋点漏一处,整条链的账就偏了,而且后面加新功能时很容易忘记补上。回调的好处是无论你从哪个入口调模型,只要走了框架,账就会自动落下来。
链式嵌套时要特别注意父子关系。一次上层链跑完,底下可能触发了好几次模型调用,统计单位要按「单次调用」记,上层链再按自己的 ID 聚合。如果直接在上层记总数,中间某次调用失败重试,次数和金额都会对不上,排查时你会不知道是哪一层多出来的。
- 流式输出场景下,用量往往在结束事件里才给全,别在流式中间急着写账,容易写到一半的数字;
- 想统一入口也可以自己包一层函数,但要确保所有调用都走这个入口,绕过它的地方就是账目黑洞;
- 重试要单独记一行,不要覆盖原记录,否则重试造成的翻倍永远查不出来;
- 回调里拿不到用量的情况要先兜底记一条「用量缺失」,而不是直接跳过,缺失本身也是信号。
每次调用该记哪些字段:一张最小清单
字段设计决定了后面能不能查。基础项至少要有时间、调用方、模型标识、输入用量、输出用量、是否命中缓存、耗时。调用方要细化到业务模块、用户或任务 ID 这一级,不然聚合视图只能看总数,看不出哪个功能在烧钱。
| 字段类别 | 具体字段 | 为什么必须记 |
|---|---|---|
| 基础项 | 时间、调用方、模型标识、输入/输出用量、耗时 | 没有这些连一行记录都不成立 |
| 缓存项 | 是否命中缓存、缓存读/写用量 | 缓存命中和未命中的计价口径不同 |
| 归因项 | 所属链/任务、第几次尝试 | 重试要单独成行,不能覆盖原记录 |
| 成本项 | 单价、币种、汇率、计算后金额 | 单价和用量分开存,价格变了才能回算历史 |
工具调用和嵌入调用也要各记一行,它们经常被漏掉但占比不低。工具调用是否计费、按什么单位计费,各家口径不一样,到站内的 /stacks/frameworks 对照框架清单时,顺便确认一下你用的那套工具链有没有额外计费项。单价这类会变的数字,到 /official-api 核对当时的数字后再写进配置。
把用量换算成钱:别在代码里写死价格
单价放配置或数据库,代码只负责「用量 × 单价」。写死在代码里的价格是最常见的账目失真来源,价格一调,历史账目全部错位。输入和输出单价通常不同,缓存命中的读、写又各有一套口径,这四类要分开累加,别用一个平均价糊过去,否则对账时差额从哪来都说不清。
- 先确定计价口径到 /tools/model-cost-calculator 和 /tools/token-price-calculator 对着算一遍,确认输入、输出、缓存读、缓存写各是什么单位,再决定自己代码里怎么拆字段。
- 把单价抽成配置单价、币种、汇率都放进配置或数据库,代码只做乘法;每次价格调整只改一处,历史记录里的单价字段保持原样不动。
- 多币种单独记汇率和币种也要落到每条记录上,过段时间回看时,才知道那笔账当时按什么价算的。
- 写一条校验规则对每条记录断言「用量不为空」和「单价不为空」,缺任何一个就打日志告警,别等月底才发现某类调用一直没记上。
聚合、对账与排查:从日志回到账单
日志攒起来之后,至少做三张聚合视图:按任务、按模型、按天。哪张视图对不上,就说明那个维度的归因字段缺失。比如按天对得上、按任务对不上,多半是某些调用没记任务 ID;按模型对不上,多半是某类模型调用没走统一入口。
和平台账单对账时,差额通常来自三类:重试没单独记、缓存命中口径不一致、工具调用是否计费没算清。按这个顺序排查最快,因为重试造成的重复计费最容易在自家日志里先发现,缓存和工具计费则往往要回头查平台说明。想先跑一轮数据链路是否通畅,也可以用站内的 /tools/speed-test 检查连通性,避免把网络问题误判成账目问题。
- 发现某条链成本异常,先看调用次数再看待办量:是次数涨了还是单次变贵了,两个方向的修法完全不同;
- 日志字段要留可扩展位,将来接新模型或新的计价单位时不用改表结构;
- 对账差额超过预期时,先把重试记录单独拉出来看,它是最常见的解释;
- 聚合视图里保留未归因调用这一栏,它的比例应该越来越小,如果一直很大说明埋点有漏。
成本追踪不是月末算总账,而是每一次调用都留下能对账的一行。下一步想先确认自己用的模型和计价口径,再去 /official-api 看当时的资料库,顺手用 /tools/model-cost-calculator 跑一遍换算,回来改自己的字段设计会更有底。→
常见问题
LangChain 回调里拿到的 token 数和平台账单对不上,正常吗?
经常对不上。差异集中在三处:重试产生的重复调用框架可能只记一次;缓存命中的读写在两边的口径不同;流式输出提前中断时用量可能没回传。先把日志按「每次调用一行」拆开,再去 /usage 或平台账单核对总量,一般能定位到是哪一类。
一定要用回调吗,自己手动加计数行不行?
能用但容易漏。手动计数的问题在于只要有一个人加了新调用没带上计数,账就少一块,而且很难事后发现。回调的好处是挂一次就全覆盖,代价是要理解嵌套和流式事件的触发顺序,一次搞懂长期省事。
价格变了,历史记录怎么算?
别只存最终金额。把「用量」和「当时单价」分开存两列,价格调整后只影响新记录,历史记录用当时单价回算即可。如果只存了金额,价格一变历史数据就失去可比性,只能整体作废重来。