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_usd | generation, embedding | 本次调用的总成本——权威总额,做聚合请用它 |
$ai_input_cost_usd | generation, embedding | 输入 Token 对应的成本 |
$ai_output_cost_usd | generation, embedding | 输出 Token 对应的成本 |
$ai_request_cost_usd | generation, embedding | 每次请求的固定费用(如 Anthropic 的 per-request fee);通常为0 |
$ai_web_search_cost_usd | generation, embedding | 生成过程中网页搜索工具调用产生的成本;通常为0 |
$ai_audio_cost_usd | generation | 模型按独立费率收取音频模态成本时使用;通常为0 |
$ai_image_cost_usd | generation | 图像模态成本;通常为0 |
$ai_video_cost_usd | generation | 视频模态成本;通常为0 |
$ai_input_tokens | generation, embedding | 发送给模型的 Token 数(跨所有模态合计) |
$ai_output_tokens | generation | 模型返回的 Token 数(跨所有模态合计) |
$ai_total_tokens | generation, embedding | 输入 + 输出 Token 数 |
$ai_cache_read_input_tokens | generation | 由 Provider 提示词缓存命中的输入 Token 数 |
$ai_cache_creation_input_tokens | generation | 写入 Provider 提示词缓存的输入 Token 数 |
$ai_reasoning_tokens | generation | 推理模型的思考 Token(按输出计费) |
$ai_model | generation, embedding | 成本归因的主维度 |
$ai_provider | generation, embedding | 次要归因维度(openai、anthropic 等) |
$ai_is_error | generation | 用于在成本汇总中排除/包含失败调用 |
$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 版本上可能切换口径。因此文档给出两个明确建议:
- 永远以
$ai_total_cost_usd为权威总额,它已经按事件实际采用的口径计算完毕,无需你关心差异; - 需要自己算"缓存命中率"时,不要按 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 属性(例如
email、company_tier); - 写查询前先用
posthog:read-data-schema工具发现项目里实际存在的自定义属性,不要猜测属性名。
有两个与用户统计相关的坑(来自 SKILL.md Tips):
- 按用户汇总时,排除
distinct_id = properties.$ai_trace_id的行——部分 SDK 在未设置用户时会把distinct_id默认成 trace ID; - 想用 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它同时体现了三条黄金规则:
- 聚合总额而非组件(否则丢掉 request / web-search 费);
- 同时包含 generation 与 embedding(漏掉 embedding 会静默少算,嵌入调用单次便宜但规模大了很可观);
- 总是设置时间范围(不带时间范围的成本查询会全表扫描 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),仅供参考