news 2026/9/14 12:55:09

PostHog AI 可观测性成本属性全解析:`$ai_total_cost_usd` 与 LLM 费用归因实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PostHog AI 可观测性成本属性全解析:`$ai_total_cost_usd` 与 LLM 费用归因实战

PostHog AI 可观测性成本属性全解析:$ai_total_cost_usd与 LLM 费用归因实战

【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog

导读

本文以 PostHog 的 AI 可观测性(AI Observability)功能为背景,系统讲解 LLM 调用成本在事件上的落盘方式:成本属性只存在于$ai_generation$ai_embedding两类事件上、由模型与 Token 用量在摄取(ingestion)时推导而来,且以$ai_total_cost_usd为唯一权威总额。读完本文,你将掌握完整的成本属性 Schema、为何聚合时只能求和$ai_total_cost_usd而不能拆分组件、不同 Provider 缓存费用的口径差异、trace/evaluation 事件集的正确统计边界,以及如何结合 PostHog 内置的 HogQL 查询与/ai-observability仪表盘做费用归因。

本文主体源自仓库文档 cost-properties.md,属于exploring-llm-costs技能包(见 SKILL.md)的核心参考之一,并辅以仓库源码验证。

成本属性总览:一份完整的 Schema

所有成本均以USD计,在事件摄取时按"模型 + Provider + Token 用量"计算后写入事件属性。你不能手工设置这些成本值并期望它们长期有效——摄取管线会在每次事件到达时重新推导。成本只存在于两类事件上:

  • $ai_generation:一次大模型生成调用(含推理、工具调用、多模态内容生成);
  • $ai_embedding:一次向量嵌入调用。

完整属性表如下(摘自 cost-properties.md):

属性所在事件含义
$ai_total_cost_usdgeneration, embedding本次调用的总成本——权威总额,做聚合请用它
$ai_input_cost_usdgeneration, embedding输入 Token 对应的成本
$ai_output_cost_usdgeneration, embedding输出 Token 对应的成本
$ai_request_cost_usdgeneration, embedding每次请求的固定费用(如 Anthropic 的 per-request fee);通常为0
$ai_web_search_cost_usdgeneration, embedding生成过程中网页搜索工具调用产生的成本;通常为0
$ai_audio_cost_usdgeneration模型按独立费率收取音频模态成本时使用;通常为0
$ai_image_cost_usdgeneration图像模态成本;通常为0
$ai_video_cost_usdgeneration视频模态成本;通常为0
$ai_input_tokensgeneration, embedding发送给模型的 Token 数(跨所有模态合计)
$ai_output_tokensgeneration模型返回的 Token 数(跨所有模态合计)
$ai_total_tokensgeneration, embedding输入 + 输出 Token 数
$ai_cache_read_input_tokensgeneration由 Provider 提示词缓存命中的输入 Token 数
$ai_cache_creation_input_tokensgeneration写入 Provider 提示词缓存的输入 Token 数
$ai_reasoning_tokensgeneration推理模型的思考 Token(按输出计费)
$ai_modelgeneration, embedding成本归因的主维度
$ai_providergeneration, embedding次要归因维度(openai、anthropic 等)
$ai_is_errorgeneration用于在成本汇总中排除/包含失败调用
$ai_trace_id所有$ai_*事件将成本向上归集到 trace 层级
$ai_session_id所有$ai_*事件将成本向上归集到 session 层级(把一系列相关 trace 归为一组)

注意$ai_total_tokens$ai_input_tokens+$ai_output_tokens的差异:前者同时出现在 generation 与 embedding 事件上,而$ai_output_tokens只出现在 generation 上——嵌入调用没有"输出 Token"概念,这一点在后续写聚合 SQL 时会影响sum()的取值。

核心铁律:永远求和$ai_total_cost_usd,不要拆组件

这是整份文档最重要的一条规则。在摄取时:

$ai_total_cost_usd = input + output + request + web_search(再加上各模态成本)

如果只求$ai_input_cost_usd + $ai_output_cost_usd,会静默丢掉 request 与 web-search 费用——这两项对 Anthropic 的按请求收费和任何带工具(tool)增强的生成调用来说都是真实且非零的。UI 的成本单元格正是按如下方式计算的:

event IN ('$ai_generation', '$ai_embedding') 上的 $ai_total_cost_usd 求和

写查询时请镜像这一口径。

源码佐证:UI 与后端都只聚合总额

这一规则在仓库中有多处直接印证:

  • 前端products/ai_observability/frontend/utils.ts中的costContextFromProperties()从事件属性构造CostContext时,把totalCost设为props.$ai_total_cost_usd,并先判断其类型是否为number(非数字则返回undefined),同时把 input/output/request/webSearch 作为旁路信息携带——但totalCost是唯一必填字段,其它组件字段都可能缺失。
  • 仪表盘模板products/ai_observability/backend/dashboard_templates.py中内置的 "Total cost (USD)"、"Cost per user (USD)" 等 insight 图块,其趋势查询math均为"sum"math_property均为"$ai_total_cost_usd",事件过滤为$ai_generation
  • 后端接口products/ai_observability/backend/api/personal_spend.py中的_fetch_summary()_fetch_by_product()_fetch_by_tool()_fetch_by_model()_fetch_by_day()等查询全部以sum(toFloat(properties.$ai_total_cost_usd))作为成本口径,且事件集合固定为_event_in(["$ai_generation", "$ai_embedding"])

从源码结构可以推断:toFloat()转换意味着属性在 ClickHouse 中按字符串存储,HogQL 聚合前需要显式类型转换,这也是所有示例 SQL 都写toFloat(properties.$ai_total_cost_usd)的原因。

缓存费用:Provider 的两种报告口径

提示词缓存(prompt cache)费用的归属因 Provider 而异,直接求和$ai_input_cost_usd会得出误导性结论:

  • 排他式(exclusive)报告:缓存 Token不计入$ai_input_tokens。Anthropic 目前多数 SDK 采用此口径。这类 Provider 的缓存读/写花费也会体现在$ai_input_cost_usd之外,因此仅看$ai_input_cost_usd低估真实的输入侧花费。
  • 包含式(inclusive)报告:缓存 Token已计入$ai_input_tokens。OpenAI 及多数其他 Provider 目前采用此口径,缓存花费已打包进$ai_input_cost_usd

这个差异还随 SDK 版本变化——同一个 Provider 在不同 SDK 版本上可能切换口径。因此文档给出两个明确建议:

  1. 永远以$ai_total_cost_usd为权威总额,它已经按事件实际采用的口径计算完毕,无需你关心差异;
  2. 需要自己算"缓存命中率"时,不要按 Provider 名硬编码,而要按事件级标志$ai_cache_reporting_exclusive分支。

缓存命中率公式(按事件级标志分支)

仓库配套文档 cache-accounting.md 给出了可直接运行的 HogQL 配方:

posthog:execute-sql SELECT properties.$ai_model AS model, if(properties.$ai_cache_reporting_exclusive = 'true', sum(toInt(properties.$ai_cache_read_input_tokens)) / nullIf(sum(toInt(properties.$ai_input_tokens)) + sum(toInt(properties.$ai_cache_read_input_tokens)) + sum(toInt(properties.$ai_cache_creation_input_tokens)), 0), sum(toInt(properties.$ai_cache_read_input_tokens)) / nullIf(sum(toInt(properties.$ai_input_tokens)), 0) ) AS cache_hit_rate FROM events WHERE event = '$ai_generation' AND timestamp >= now() - INTERVAL 30 DAY GROUP BY model, properties.$ai_cache_reporting_exclusive

关键点:

  • 排他式:真实输入总量 =$ai_input_tokens + $ai_cache_read_input_tokens + $ai_cache_creation_input_tokens,命中率 = 缓存读 / 该总量;
  • 包含式$ai_input_tokens已含缓存,命中率 = 缓存读 /$ai_input_tokens
  • nullIf(..., 0)用于防止除零;若某个模型混用了两种报告风格(少见),应在GROUP BY中加入$ai_cache_reporting_exclusive分别统计,而不是用any()取其一。

$ai_cache_reporting_exclusive是布尔标志,由摄取管线在每条$ai_generation上自动探测并写入解析结果;手动捕获时也可通过$ai_cache_reporting_exclusive: true|false覆盖。这份公式同样驱动着 breakdown-patterns.md 中"输入 vs 输出 vs 缓存经济学"配方的cache_hit_rate字段。

事件集规则:trace、span 与 evaluation 的成本边界

trace 与 span 不承载可汇总的成本

$ai_trace$ai_span事件不携带用于汇总的成本。要获得一条 trace 的总成本,正确做法是:

sum($ai_total_cost_usd) WHERE $ai_trace_id = <trace_id> AND event IN ('$ai_generation', '$ai_embedding')

需要注意的是:部分 SDK 封装层会出于便利把$ai_total_cost_usd复制一份到$ai_trace事件上,但查询执行器(query runner)仍然只对event IN ('$ai_generation', '$ai_embedding')做聚合——不要混用事件集,否则会双重计数(double-count)。

evaluation 事件:默认不算入总额

$ai_evaluation事件也会产出成本属性(摄取时与$ai_generation$ai_embedding一并计费),但内置的/ai-observability汇总与查询执行器都不把它计入成本总额。处理原则:

  • 如果用户只想要"标准 LLM 花费",保持event IN ('$ai_generation', '$ai_embedding'),与 UI 对齐;
  • 如果用户明确要求"含评估的总花费",则显式扩展事件过滤为event IN ('$ai_generation', '$ai_embedding', '$ai_evaluation'),并明确说明这是扩展口径

这条"不混用事件集"的纪律在 SKILL.md 的 Tips 中反复出现:在$ai_span上求和得到的是 0,$ai_trace上的复制值不可纳入汇总,$ai_evaluation只在用户显式要求时才加入。

源码佐证:事件集合的固定写法

products/ai_observability/backend/api/personal_spend.py中通过_event_in(events)构造event IN (...)过滤,所有成本查询统一传入["$ai_generation", "$ai_embedding"]_fetch_by_tool()则额外限定equals(event, '$ai_generation')(工具调用只出现在生成事件上)。这从后端实现上印证了"成本聚合的事件集 = generation + embedding"这一约定。

用户维度:distinct_id是规范维度

  • distinct_id规范的用户维度,客户通常在 SDK 中设置它;
  • 需要更丰富的按用户拆分时,使用 person 属性(例如emailcompany_tier);
  • 写查询前先用posthog:read-data-schema工具发现项目里实际存在的自定义属性,不要猜测属性名

有两个与用户统计相关的坑(来自 SKILL.md Tips):

  1. 按用户汇总时,排除distinct_id = properties.$ai_trace_id的行——部分 SDK 在未设置用户时会把distinct_id默认成 trace ID;
  2. 想用 person 属性(如email)做过滤时,事件行通过person_id关联 person 表读取属性,HogQL 写法形如person.properties.email,与 AI 可观测性 "Users" 标签页对events表的查询同构(见 personal_spend.py 中_email_filter()的注释)。

实战:一段可直接运行的总成本查询

把上述规则落成一段标准查询(模板来自 SKILL.md):

posthog:execute-sql SELECT round(sum(toFloat(properties.$ai_total_cost_usd)), 4) AS total_cost_usd FROM events WHERE event IN ('$ai_generation', '$ai_embedding') AND timestamp >= now() - INTERVAL 30 DAY

它同时体现了三条黄金规则:

  1. 聚合总额而非组件(否则丢掉 request / web-search 费);
  2. 同时包含 generation 与 embedding(漏掉 embedding 会静默少算,嵌入调用单次便宜但规模大了很可观);
  3. 总是设置时间范围(不带时间范围的成本查询会全表扫描 events 表)。

更多配方(按天成本曲线、按模型/用户/trace/自定义维度拆分、单次调用成本分位数分布、输入 vs 输出 vs 缓存经济性)见 breakdown-patterns.md;成本为 0 或缺失时的排查路径与三条摄取计费路径(预计算 passthrough、自定义单价 custom、自动模型匹配 openrouter/manual)见 cost-sources.md,其中$ai_cost_model_source是排障第一入口。

总结

PostHog 的 LLM 成本模型可以浓缩为几句话:成本只写在$ai_generation$ai_embedding事件上,以$ai_total_cost_usd为唯一权威总额;所有成本问题都是对这两类事件按$ai_total_cost_usd聚合后的分组、过滤与比较;缓存口径按事件级标志$ai_cache_reporting_exclusive分支而非按 Provider 硬编码;trace/span/evaluation 事件集各有明确的成本边界;用户维度以distinct_id为准。这套约定在 UI 成本单元格、后端personal_spend接口与内置仪表盘模板中高度一致,遵循它即可让自定义查询与官方展示口径永远对齐。

【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 12:54:52

基于Qt与OpenCV DNN的YOLOv5 GPU目标检测桌面应用实战

简介&#xff1a;一套基于Qt部署YOLOv5、并通过OpenCV DNN模块与CUDA实现加速推理的完整项目资料&#xff0c;面向正在准备毕业设计、课程设计或期末大作业的计算机相关专业学生。源码结构完整&#xff0c;涵盖界面设计、推理封装与模型调用等模块&#xff0c;配合文档说明可快…

作者头像 李华
网站建设 2026/9/14 12:53:05

RS485与Modbus分层协作原理及工业通信实战避坑指南

1. 这不是协议之争&#xff0c;而是物理层、数据链路层和应用层的三层协作现场实录你手头那台PLC突然收不到温控器的数据&#xff0c;串口调试助手刷出一堆乱码&#xff1b;现场接线时发现RS485总线上挂了7个从站&#xff0c;一上电就通信中断&#xff1b;用Modbus Poll测试时明…

作者头像 李华
网站建设 2026/9/14 12:52:52

GoFr 如何连接 Couchbase 执行 KV 读写与 N1QL 查询

GoFr 如何连接 Couchbase 执行 KV 读写与 N1QL 查询 【免费下载链接】gofr An opinionated GoLang framework for accelerated microservice development. Built in support for databases and observability. 项目地址: https://gitcode.com/GitHub_Trending/go/gofr 在…

作者头像 李华
网站建设 2026/9/14 12:49:47

雷达信号处理链路详解:LFM脉压、相参积累与CFAR检测的MATLAB实现

简介&#xff1a;面向雷达通信与信号处理方向的Matlab源码包&#xff0c;围绕线性调频&#xff08;LFM&#xff09;信号处理链展开&#xff0c;完整覆盖信号产生、脉冲压缩、相参积累、恒虚警&#xff08;CFAR&#xff09;检测、目标信息提取等典型步骤&#xff0c;适合高校学生…

作者头像 李华