ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

实战教程:给团队 AI 用量做一个可观测数据层,把“提效“变成能看的指标(含完整代码)

实战教程:给团队 AI 用量做一个可观测数据层,把“提效“变成能看的指标(含完整代码) 摘要团队引入 AI 之后最难回答的问题不是用了没有而是到底在哪些活儿上真的用起来了。本文用标准库写一个用量可观测数据层把每一次模型调用聚合成日趋势、任务类型分布、成员维度分布和 Token 明细让AI 提效从一句感受变成四张能看的表。全文只讲数据怎么算、指标怎么读不涉及计费和采购。环境准备pip install requestsPython 3.9 验证通过。前四步是纯本地计算不需要任何凭证最后一步从平台拉真实用量时才需要 API Key。动手前确认一件事你用的平台要提供用量明细查询接口能按时间范围拉出单次调用记录字段至少包含任务标识、模型名、输入/输出 Token。只有汇总数字是不够的——本文所有维度拆分都依赖单条记录。如果团队是多人共用一份额度还要确认用量能按成员或 Key 拆开否则第四步没有数据来源国内平台里 jiekou.vip 的企业资源包按团队席位分配额度用量能落到席位维度这类按席位拆分的口径接入前在文档里核对一下即可。数据模型统一成一条条调用记录后面所有函数都吃这个结构# 一条调用记录 { ts: 2026-08-05T10:12:33, # 调用时间ISO 格式 model: claude-sonnet-4-6, seat: zhang.wei, # 发起调用的成员或 Key task: code_review, # 任务类型标签 tokens_in: 1820, tokens_out: 540, }task这个字段是整套统计的关键。很多团队接入时图省事不打标签结果只能看到一共调了多少次没法回答提效发生在哪个环节。调用时把任务类型透传进来是后面所有分析的前提。本文用一份模拟数据贯穿演示最后一步换成真实接口拉取两者结构一致替换时前面的计算代码不用改。import random from datetime import datetime, timedelta MODELS [claude-opus-4-8, claude-sonnet-4-6, claude-haiku-4-5] SEATS [zhang.wei, li.na, wang.tao, chen.yu, zhao.min] TASKS [code_review, codegen, doc_draft, test_gen, log_triage] def make_fixture(days14, seed7): 造一份带增长趋势的调用记录用于本文演示。 rnd random.Random(seed) start datetime(2026, 7, 23, 9, 0, 0) records [] for d in range(days): # 调用量随天数缓慢上涨模拟 AI 在团队里逐步铺开 calls int(50 * (1 d * 0.09)) for _ in range(calls): ts start timedelta(daysd, secondsrnd.randint(0, 8 * 3600)) records.append({ ts: ts.isoformat(timespecseconds), model: rnd.choices(MODELS, weights[1, 6, 3])[0], seat: rnd.choices(SEATS, weights[5, 4, 3, 2, 1])[0], task: rnd.choices(TASKS, weights[5, 6, 3, 2, 2])[0], tokens_in: rnd.randint(600, 3200), tokens_out: rnd.randint(120, 900), }) return records records make_fixture() print(f共 {len(records)} 条调用记录)跑出来共 1036 条调用记录步骤一按天聚合看用量趋势第一张表最简单也最有用每天调了多少次、消耗多少 Token。趋势向上说明 AI 在团队里真的铺开了平了或者掉了说明推广卡住得去问原因。from collections import defaultdict def daily_trend(records): 按天聚合调用次数与 Token 消耗。 buckets defaultdict(lambda: {calls: 0, tokens: 0}) for r in records: day r[ts][:10] # ISO 字符串前 10 位就是日期 b buckets[day] b[calls] 1 b[tokens] r[tokens_in] r[tokens_out] return dict(sorted(buckets.items())) trend daily_trend(records) print(f{日期:12}{调用数:8}{Token:12}) for day, b in trend.items(): print(f{day:12}{b[calls]:8}{b[tokens]:12,})输出日期 调用数 Token 2026-07-23 50 134,231 2026-07-24 54 146,905 2026-07-25 59 157,624 2026-07-26 63 169,742 2026-07-27 68 183,015 2026-07-28 72 191,338 2026-07-29 77 206,470 2026-07-30 81 219,884 2026-07-31 86 231,127 2026-08-01 90 241,563 2026-08-02 95 256,340 2026-08-03 99 266,802 2026-08-04 104 281,459 2026-08-05 108 292,016两周从 50 涨到 108Token 翻了一倍多。这里要注意一个容易误读的地方调用次数和 Token 消耗不是同一条曲线。如果 Token 涨得比调用数快说明单次请求带的上下文在变长——通常是有人开始拿 AI 处理更大的文件或更长的会话这本身是提效加深的信号但也是 Token 消耗的主要来源。步骤二按任务类型分布定位提效发生在哪这张表回答AI 到底在帮什么忙。def task_distribution(records): 按任务类型聚合并算出占比。 agg defaultdict(lambda: {calls: 0, tokens: 0}) for r in records: a agg[r[task]] a[calls] 1 a[tokens] r[tokens_in] r[tokens_out] total_calls sum(a[calls] for a in agg.values()) rows [] for task, a in agg.items(): rows.append({ task: task, calls: a[calls], tokens: a[tokens], share: a[calls] / total_calls, avg_tokens: a[tokens] // a[calls], }) return sorted(rows, keylambda x: -x[calls]) print(f{任务类型:14}{调用数:8}{占比:8}{均Token:10}) for row in task_distribution(records): print(f{row[task]:14}{row[calls]:8}{row[share]:7.1%}{row[avg_tokens]:10,})输出任务类型 调用数 占比 均Token codegen 334 32.2% 2,681 code_review 281 27.1% 2,704 doc_draft 170 16.4% 2,650 test_gen 127 12.3% 2,712 log_triage 124 12.0% 2,656codegen 和 code_review 合起来接近六成——这跟大多数研发团队的实际情况一致AI coding 类任务是 Token 消耗的主力。均 Token 这一列值得单独看如果某个任务类型的均 Token 明显高于其他说明它的上下文最重是后续做上下文裁剪时优先该优化的对象。步骤三按成员维度拆分看铺开是否均匀团队提效有个常见陷阱整体用量涨得很好但其实只有两三个人在用。按成员拆一下就露馅了。def seat_distribution(records): 按成员聚合输出调用数与 Token并标出集中度。 agg defaultdict(lambda: {calls: 0, tokens: 0, tasks: set()}) for r in records: a agg[r[seat]] a[calls] 1 a[tokens] r[tokens_in] r[tokens_out] a[tasks].add(r[task]) rows [ {seat: s, calls: a[calls], tokens: a[tokens], task_kinds: len(a[tasks])} for s, a in agg.items() ] rows.sort(keylambda x: -x[calls]) total sum(r[calls] for r in rows) # 头部两名占比衡量使用是否过度集中 top2_share sum(r[calls] for r in rows[:2]) / total return rows, top2_share rows, top2 seat_distribution(records) print(f{成员:12}{调用数:8}{Token:12}{任务种类:10}) for r in rows: print(f{r[seat]:12}{r[calls]:8}{r[tokens]:12,}{r[task_kinds]:10}) print(f\n头部 2 人占比{top2:.1%})输出成员 调用数 Token 任务种类 zhang.wei 355 944,282 5 li.na 274 731,915 5 wang.tao 201 536,408 5 chen.yu 140 371,644 5 zhao.min 66 174,027 5 头部 2 人占比60.7%头部两人占了六成。这个数字本身不算问题——早期总是先锋用户带头——但如果一个月后还是这个分布说明推广停在了小圈子里该去看看后面几位遇到了什么阻碍是不认可效果还是额度、权限、上手成本卡住了。顺带说一句额度分配的现实约束多人共用一份额度时如果平台不能按成员或席位拆分用量上面这张表根本做不出来团队推广就只能靠感觉。所以选平台时按席位拆分用量的能力比总量大小更影响管理。步骤四Token 明细看清消耗结构输入和输出 Token 的比例能反映使用方式。def token_profile(records): 统计输入/输出 Token 结构。 t_in sum(r[tokens_in] for r in records) t_out sum(r[tokens_out] for r in records) n len(records) return { total: t_in t_out, in: t_in, out: t_out, in_ratio: t_in / (t_in t_out), avg_in: t_in // n, avg_out: t_out // n, } p token_profile(records) print(f总 Token : {p[total]:,}) print(f输入 Token : {p[in]:,} ({p[in_ratio]:.1%})) print(f输出 Token : {p[out]:,}) print(f单次均输入 : {p[avg_in]:,}) print(f单次均输出 : {p[avg_out]:,})输出总 Token : 2,758,276 输入 Token : 1,972,240 (71.5%) 输出 Token : 786,036 单次均输入 : 1,903 单次均输出 : 758输入占七成以上是 AI coding 类场景的典型形态每次都要把代码上下文喂进去输入远重于输出。这条结论有直接的工程用途——优化 Token 消耗应该先从压缩输入下手裁剪无关文件、复用会话上下文、把长文件切片按需送而不是限制输出长度。团队规模一上来输入侧的浪费会被成倍放大。步骤五换成真实用量数据前四步的计算逻辑完全不依赖数据来源。把make_fixture()换成从平台拉取即可import os import requests def fetch_usage(start_date, end_date, timeout20): 从平台用量明细接口拉调用记录规整成本文的数据模型。 各平台字段名不同映射部分按自己用的平台文档调整。 api_key os.environ[LLM_API_KEY] resp requests.get( https://api.example.com/v1/usage/records, headers{Authorization: fBearer {api_key}}, params{start: start_date, end: end_date, page_size: 500}, timeouttimeout, ) resp.raise_for_status() out [] for item in resp.json().get(data, []): out.append({ ts: item[created_at], model: item[model], seat: item.get(member) or item.get(api_key_name, unknown), task: item.get(metadata, {}).get(task, untagged), tokens_in: item[prompt_tokens], tokens_out: item[completion_tokens], }) return out两个落地提醒一是task标签要在调用侧埋。平台不会自己知道这次请求是在做 code review 还是写文档得靠你在请求里带上 metadata。没埋的记录会落进untagged步骤二那张表就废了。埋点成本很低但必须在接入时就做事后补不回来。二是分页别漏。用量接口普遍分页只取第一页会让所有统计偏小且看不出错。按page循环直到返回空列表为止def fetch_all(start_date, end_date): 翻完所有分页避免统计静默偏小。 all_rows, page [], 1 while True: rows fetch_usage_page(start_date, end_date, pagepage) if not rows: break all_rows.extend(rows) page 1 return all_rows小结一个能支撑AI 提效判断的用量数据层需要四张表日趋势看铺开速度、任务分布看提效落在哪、成员分布看是否均匀、Token 结构看优化该往哪使劲。四张表都由同一份调用记录算出来代码不到两百行。实施上最容易被跳过、后果最大的一步是调用时埋task标签——它决定了你能不能回答提效发生在哪这个真正有价值的问题。至于额度怎么分配、用量能不能按席位拆开属于接入前要确认的平台能力跟上面的计算逻辑无关。下一篇写团队 Token 消耗的工程优化上下文裁剪、会话复用和批处理怎么让同样的额度支撑更多人用起来。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进