关于 analyzing-expensive-users
用于分析 AI 可观测性中高成本用户的技能,面向需要解释用户支出来源的数据分析人员。它结合成本排名、基线比较、模型与令牌拆解及代表性追踪记录,分析成本驱动因素。
主要功能
- 01按生成调用成本排名用户
- 02与项目基线比较用户指标
- 03拆解模型、令牌及缓存成本
- 04读取高成本追踪记录
- 05按已发现的自定义维度分组
适用场景
- 01找出 AI 使用支出最高的用户
- 02判断成本来自调用量还是单次成本
- 03用追踪记录调查重试或上下文膨胀
- 04按产品维度定位高成本工作流
MCP 相关
指导调用 PostHog MCP 工具执行 SQL、查询 LLM 追踪记录、读取数据架构并生成应用链接。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ posthog/ ai-plugin --skill analyzing-expensive-users安装后在 Claude Code 中输入/analyzing-expensive-users确认已加载。
详细介绍
分析高成本用户
当用户想要了解 AI 可观测性中成本最高的用户时,使用此技能。任务不只是按成本给用户排名。有用的答案还应解释排名靠前的用户为何成本高:调用量、模型选择、提示词大小、输出大小、缓存行为、重试/错误、跟踪类型、功能或租户维度,以及有代表性的跟踪示例。
对于常规成本汇总,也请使用 exploring-llm-costs。对于读取单条跟踪,也请使用 exploring-llm-traces。
工具
| 工具 | 用途 |
|---|---|
posthog:execute-sql | 对用户排名,并将其指标与项目基线进行比较 |
posthog:query-llm-traces-list | 查找特定用户的高成本跟踪 |
posthog:query-llm-trace | 读取有代表性的跟踪,以说明实际发生了什么 |
posthog:read-data-schema | 在按自定义事件或个人属性分组之前发现这些属性 |
posthog:generate-app-url | 构建包含区域和项目信息的 UI 链接 |
核心规则
- 从有界时间范围开始。 如果用户未指定时间范围,请使用最近 30 天并说明这一点。如果用户提供了链接或现有筛选条件,请保留日期范围、测试账户筛选条件和属性筛选条件。
- 从生成调用支出入手。 按用户排名的查询会按
distinct_id对$ai_generation行分组,并包含traces、generations、errors、total_cost、first_seen和last_seen。这是查找高成本用户的最佳初步方法。 - 如需统计用户的全部支出,请有意纳入嵌入。 更广泛的成本汇总应包含
event IN ('$ai_generation', '$ai_embedding'),但要指出事件集合发生了变化。 - 解读用户时,过滤默认的跟踪 ID。 某些 SDK 在未设置用户时会将
$ai_trace_id用作distinct_id。对于已识别的用户,请排除distinct_id = properties.$ai_trace_id,并指出有多少支出因此无法归属到用户。 - 不要猜测自定义维度。 在按
feature、tenant_id、plan、workflow_name或类似的客户特定字段分组之前,先发现事件属性和个人属性。 - 先读取跟踪,再解释因果关系。 汇总数据可以找出可疑对象;有代表性的跟踪则能说明用户成本高是因为实际工作流、重试、循环、大型上下文、工具密集型生成,还是其他行为。
工作流
1. 按生成调用支出给用户排名
如果用户询问成本最高的用户,先使用此方法:
posthog:execute-sql
SELECT
distinct_id,
argMax(email, timestamp) AS email,
argMax(name, timestamp) AS name,
countDistinctIf(ai_trace_id, notEmpty(ai_trace_id)) AS traces,
count() AS generations,
countIf(notEmpty(ai_error) OR ai_is_error = 'true') AS errors,
round(sum(ai_total_cost_usd), 4) AS total_cost,
round(avg(ai_total_cost_usd), 6) AS avg_cost_per_generation,
sum(ai_input_tokens) AS input_tokens,
sum(ai_output_tokens) AS output_tokens,
min(timestamp) AS first_seen,
max(timestamp) AS last_seen
FROM (
SELECT
distinct_id,
timestamp,
toString(properties.$ai_trace_id) AS ai_trace_id,
toFloat(properties.$ai_total_cost_usd) AS ai_total_cost_usd,
toString(properties.$ai_error) AS ai_error,
toString(properties.$ai_is_error) AS ai_is_error,
toInt(properties.$ai_input_tokens) AS ai_input_tokens,
toInt(properties.$ai_output_tokens) AS ai_output_tokens,
toString(person.properties.email) AS email,
toString(person.properties.name) AS name
FROM events
WHERE event = '$ai_generation'
AND timestamp >= now() - INTERVAL 30 DAY
)
GROUP BY distinct_id
ORDER BY total_cost DESC
LIMIT 25
如果用户询问已识别的用户,请在内层 WHERE 子句中添加:
AND (
properties.$ai_trace_id IS NULL
OR distinct_id != properties.$ai_trace_id
)
只查询所需的显式标签列,例如 email 和 name。绝不要选择原始的 person.properties 对象或包含它的元组:这会将完整的属性数据块序列化到结果中,泄露远超标签所需范围的个人数据。如果用户没有电子邮件或姓名,则回退到 distinct_id。
2. 建立基线
排名第一的用户只有与其他所有用户比较才有意义。按用户运行基线查询,以便判断某个用户成本高,是因为生成次数更多、追踪更多、单次生成成本更高、提示词更长、输出更长,还是错误率更高。
posthog:execute-sql
WITH per_user AS (
SELECT
distinct_id,
count() AS generations,
countDistinctIf(toString(properties.$ai_trace_id), notEmpty(toString(properties.$ai_trace_id))) AS traces,
countIf(notEmpty(toString(properties.$ai_error)) OR toString(properties.$ai_is_error) = 'true') AS errors,
sum(toFloat(properties.$ai_total_cost_usd)) AS total_cost,
avg(toFloat(properties.$ai_total_cost_usd)) AS avg_cost_per_generation,
avg(toInt(properties.$ai_input_tokens)) AS avg_input_tokens,
avg(toInt(properties.$ai_output_tokens)) AS avg_output_tokens
FROM events
WHERE event = '$ai_generation'
AND timestamp >= now() - INTERVAL 30 DAY
GROUP BY distinct_id
)
SELECT
count() AS users,
round(sum(total_cost), 4) AS project_total_cost,
round(avg(total_cost), 4) AS avg_cost_per_user,
round(quantile(0.5)(total_cost), 4) AS p50_user_cost,
round(quantile(0.9)(total_cost), 4) AS p90_user_cost,
round(quantile(0.99)(total_cost), 4) AS p99_user_cost,
round(avg(avg_cost_per_generation), 6) AS mean_user_cost_per_generation,
round(avg(avg_input_tokens), 0) AS mean_user_input_tokens,
round(avg(avg_output_tokens), 0) AS mean_user_output_tokens,
round(sum(errors) / nullIf(sum(generations), 0), 4) AS error_rate
FROM per_user
外层聚合列的名称与其聚合的 CTE 列名称不同(project_total_cost,而不是 total_cost)。HogQL 会将外层 sum()/avg() 中未限定的 total_cost 解析为同名输出别名,从而导致一个聚合函数嵌套在另一个聚合函数中,查询因此失败,并报错 Aggregate function sum(per_user.total_cost) is found inside another aggregate function。请保持这两层的名称各不相同。
如果基线查询仍然报错,请在回复中说明基线不可用。不要捏造 p50/p90/p99 数值,也不要在没有这些数据的情况下声称某个用户「高于中位数 Nx」——应按绝对成本和支出占比排序,并注明无法计算每用户分布。
报告排名靠前的用户时,请列出每位用户占总支出的比例,以及其数值是 p50/p90 的多少倍。这样偏斜程度会一目了然。
3. 分解排名第一用户的成本驱动因素
对于每位值得分析的排名靠前用户,按模型和令牌成本构成拆分其支出。
posthog:execute-sql
SELECT
toString(properties.$ai_provider) AS provider,
toString(properties.$ai_model) AS model,
count() AS generations,
countDistinctIf(toString(properties.$ai_trace_id), notEmpty(toString(properties.$ai_trace_id))) AS traces,
round(sum(toFloat(properties.$ai_total_cost_usd)), 4) AS total_cost,
round(avg(toFloat(properties.$ai_total_cost_usd)), 6) AS avg_cost_per_generation,
sum(toInt(properties.$ai_input_tokens)) AS input_tokens,
sum(toInt(properties.$ai_output_tokens)) AS output_tokens,
sum(toInt(properties.$ai_reasoning_tokens)) AS reasoning_tokens,
sum(toInt(properties.$ai_cache_read_input_tokens)) AS cache_read_tokens,
sum(toInt(properties.$ai_cache_creation_input_tokens)) AS cache_write_tokens,
round(sum(toFloat(properties.$ai_input_cost_usd)), 4) AS input_cost,
round(sum(toFloat(properties.$ai_output_cost_usd)), 4) AS output_cost,
round(sum(toFloat(properties.$ai_request_cost_usd)), 4) AS request_cost,
round(sum(toFloat(properties.$ai_web_search_cost_usd)), 4) AS web_search_cost,
countIf(notEmpty(toString(properties.$ai_error)) OR toString(properties.$ai_is_error) = 'true') AS errors
FROM events
WHERE event = '$ai_generation'
AND timestamp >= now() - INTERVAL 30 DAY
AND distinct_id = '<distinct_id>'
GROUP BY provider, model
ORDER BY total_cost DESC
使用以下决策树解读结果:
- 生成次数高,单次生成成本正常意味着驱动因素是调用量。
- 单次生成成本高,调用量正常意味着驱动因素是昂贵的模型、较长的上下文、较长的输出、推理令牌、网络搜索费用或请求费用。
- 输入令牌数高通常说明上下文臃肿、对话历史重复、大型检索文档或缺少截断。
- 输出令牌或推理令牌数高说明回答冗长、使用链式思考风格的推理模型、缺少输出限制或存在工具循环。
- 缓存复用率低且重复输入较多说明提示词缓存未能命中。请使用
exploring-llm-costs/references/cache-accounting.md中的缓存公式。 - 错误较多或高成本追踪较多说明可能存在重试、工具调用失败或循环。请先阅读追踪,再判断原因。
- 请求费用或网络搜索费用高说明成本来自服务提供商的固定费用或频繁使用工具的生成,而不只是令牌用量。
4. 将排名第一的用户与其他所有用户比较
对整个项目运行相同的模型或令牌拆分查询,然后进行比较。不要只看总量。你需要得出类似这样的结论:「该用户使用的模型与其他人相同,但生成次数多了 9 倍」,或「该用户的调用量正常,但 82% 的支出都用于在其他用户中很少见的高成本模型」。
有用的比较指标:
- 排名第一用户的支出占项目总成本的比例
- 该用户的生成次数和追踪数与 p50/p90 用户的对比
- 平均单次生成成本与项目平均值的对比
- 平均每次生成的输入令牌数与项目平均值的对比
- 平均每次生成的输出令牌数或推理令牌数与项目平均值的对比
- 错误率与项目平均值的对比
- 模型组合与全局模型组合的对比
- 同一模型的缓存命中率与全局缓存命中率的对比
5. 找出该用户的高成本追踪
使用 SQL 获取排名靠前的追踪列表,然后使用
posthog:query-llm-trace 阅读有代表性的追踪。
posthog:execute-sql
SELECT
toString(properties.$ai_trace_id) AS trace_id,
count() AS generations,
round(sum(toFloat(properties.$ai_total_cost_usd)), 4) AS total_cost,
round(avg(toFloat(properties.$ai_total_cost_usd)), 6) AS avg_cost_per_generation,
sum(toInt(properties.$ai_input_tokens)) AS input_tokens,
sum(toInt(properties.$ai_output_tokens)) AS output_tokens,
countIf(notEmpty(toString(properties.$ai_error)) OR toString(properties.$ai_is_error) = 'true') AS errors,
min(timestamp) AS started_at,
max(timestamp) AS ended_at
FROM events
WHERE event = '$ai_generation'
AND timestamp >= now() - INTERVAL 30 DAY
AND distinct_id = '<distinct_id>'
AND notEmpty(toString(properties.$ai_trace_id))
GROUP BY trace_id
ORDER BY total_cost DESC
LIMIT 10
至少打开该用户排名靠前的 2-3 条追踪:
posthog:query-llm-trace
{
"traceId": "<trace_id>",
"dateRange": { "date_from": "-30d" }
}
寻找第一个能够解释汇总结果的具体模式:
- 重复调用工具或重试循环
- 较大的上下文窗口或重复检索的文档
- 很长的多轮会话
- 对普通任务选用了昂贵的模型
- 同一工作流发出了许多小型调用
- 输出冗长或推理未受限制
- 网络搜索或请求费用高的调用
- 已产生模型成本的错误
6. 当聚合结果不明确时,检查自定义维度
如果排名靠前的用户成本很高,但模型/Token 明细无法解释原因,请查找 $ai_generation 上的自定义事件属性,并按可能的产品维度分组。常见示例包括 feature、tenant_id、organization_id、workflow_name、agent、route 或 environment,但不要猜测。
- 调用
posthog:read-data-schema,并设置kind: "event_properties"和event_name: "$ai_generation"。 - 对有希望的字段,调用
posthog:read-data-schema,并设置kind: "event_property_values"以确认实际取值。 - 按发现的属性对排名靠前用户的成本分组。
posthog:execute-sql
SELECT
toString(properties.<property_name>) AS dimension,
count() AS generations,
countDistinctIf(toString(properties.$ai_trace_id), notEmpty(toString(properties.$ai_trace_id))) AS traces,
round(sum(toFloat(properties.$ai_total_cost_usd)), 4) AS total_cost,
round(avg(toFloat(properties.$ai_total_cost_usd)), 6) AS avg_cost_per_generation
FROM events
WHERE event = '$ai_generation'
AND timestamp >= now() - INTERVAL 30 DAY
AND distinct_id = '<distinct_id>'
AND isNotNull(properties.<property_name>)
GROUP BY dimension
ORDER BY total_cost DESC
LIMIT 20
这往往能让你从“用户 123 成本很高”进一步发现“他们的合同审查工作流成本很高,因为每次运行都会将一份 90k Token 的文档输入成本最高的模型”。
构建 UI 链接
使用 posthog:generate-app-url 生成链接。不要硬编码主机,因为项目可能位于不同区域。
- 追踪列表:
generate-app-url { "url": "/ai-observability/traces" } - 单条追踪:
generate-app-url { "url": "/ai-observability/traces/{id}", "params": { "id": "<trace_id>" } }
对于单条追踪,如果你有追踪时间戳,请附加 ?timestamp=<url_encoded_started_at>,这样 UI 就会打开正确的时间范围。
响应格式
先给出答案,而不是查询过程。一份好的响应应包含:
- 成本最高的用户 - 按总成本排序,并列出总成本、支出占比、
生成次数、追踪次数、每次生成的平均成本和错误率。每位用户仅使用一个标签标识(邮箱、姓名或
distinct_id)。不要输出原始的person.properties对象,也不要输出用户未要求的其他个人字段。 - 成本高的原因 - 针对每位用户给出一到两个具体驱动因素,并与基线对比。
- 证据 - 模型/Token/缓存/自定义维度明细,以及你查看过的带链接示例追踪。
- 可能的优化手段 - 针对观察到的驱动因素提出具体优化建议:缩减上下文、限制输出、在某个工作流中使用更便宜的模型、改进缓存、修复重试循环,或拆分某项功能的流量。
- 注意事项 - 说明结果是否包含嵌入、是否排除了 trace-id 默认值,或使用了与初始排名不同的事件集。
避免泛泛而谈。如果数据没有显示模型组合是驱动因素,“使用更便宜的模型”就没有意义。如果输入 Token 相对于基线并不高,“缩短提示词”也没有意义。
相关技能
exploring-llm-costs— 项目整体支出:总额、明细和成本回归exploring-llm-traces— 查看导致用户生成成本高的追踪