个人主页: > for_ever_love__ <(欢迎各位大佬莅临😊)
其他栏目: > 大模型开发从0到1 <
其他栏目: > iOS项目总结大全 <
其他栏目: > 我想学python了 <
其他栏目: > iOS UI <
文章目录
- [LangChain Token 计数与成本控制:先知道钱花在哪](#LangChain Token 计数与成本控制:先知道钱花在哪)
-
- [一、Token 是什么,为什么按它收费](#一、Token 是什么,为什么按它收费)
- 二、三种统计方式
-
- [2.1 方式一:从返回值读(最简单)](#2.1 方式一:从返回值读(最简单))
- [2.2 方式二:回调统计(推荐)](#2.2 方式二:回调统计(推荐))
- [2.3 方式三:OpenAI 专用回调](#2.3 方式三:OpenAI 专用回调)
- 三、调用前预估:别等花了才知道
- 四、五個降本动作(按性价比排序)
-
- [4.1 限制 max_tokens(零风险)](#4.1 限制 max_tokens(零风险))
- [4.2 开缓存(省 30%~60%)](#4.2 开缓存(省 30%~60%))
- [4.3 分级用模型(省 50%+)](#4.3 分级用模型(省 50%+))
- [4.4 缩短 prompt(省 10%~30%)](#4.4 缩短 prompt(省 10%~30%))
- [4.5 减少不必要的调用](#4.5 减少不必要的调用)
- 五、做个成本看板
- 六、设置预算告警
- 七、常见误区
- 八、小结
LangChain Token 计数与成本控制:先知道钱花在哪
上线第一个月账单出来,很多人是懵的:
"我就跑了几个 demo,怎么花了这么多?"
这篇讲怎么精确统计每次调用的 token ,
以及五个立刻能用的降本动作。
一、Token 是什么,为什么按它收费
Token 不是字符,也不是单词,是模型自己的"分词单位"。
"向量数据库" → 可能被切成 ["向量", "数据", "库"] 3 个 token
"vector db" → ["vector", " db"] 2 个 token
粗略估算(中文):
| 内容 | 约合 |
|---|---|
| 1 个汉字 | 1~2 token |
| 1 个英文单词 | 1~1.3 token |
| 1000 汉字 | 约 1000~1500 token |
别用字符数估算 ,误差能有 50%。
要精确就用 tokenizer 数。
二、三种统计方式
2.1 方式一:从返回值读(最简单)
python
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini")
result = llm.invoke("你好")
print(result.response_metadata.get("token_usage"))
# {'prompt_tokens': 9, 'completion_tokens': 3, 'total_tokens': 12}
print(result.usage_metadata)
# {'input_tokens': 9, 'output_tokens': 3, 'total_tokens': 12}
推荐用 usage_metadata ------它是 LangChain 统一后的字段名,
不同模型厂商返回的结构一致。response_metadata 是厂商原始的,
各家的 key 不一样(OpenAI 叫 prompt_tokens,有的叫别的)。
不适合链式调用:你要在每个环节手动取值,很麻烦。
2.2 方式二:回调统计(推荐)
python
from langchain_core.callbacks import BaseCallbackHandler
class TokenTracker(BaseCallbackHandler):
def __init__(self):
self.input_tokens = 0
self.output_tokens = 0
def on_llm_end(self, response, **kwargs):
usage = response.usage_metadata or {}
self.input_tokens += usage.get("input_tokens", 0)
self.output_tokens += usage.get("output_tokens", 0)
tracker = TokenTracker()
chain.invoke({"q": "..."}, config={"callbacks": [tracker]})
print(f"输入 {tracker.input_tokens},输出 {tracker.output_tokens}")
优点 :一次挂载,链里所有 LLM 调用(包括 Agent 多轮)
都会被统计到。
2.3 方式三:OpenAI 专用回调
python
from langchain_community.callbacks import get_openai_callback
with get_openai_callback() as cb:
result = chain.invoke({"q": "..."})
print(cb)
# Tokens Used: 245
# Prompt Tokens: 200
# Completion Tokens: 45
# Successful Requests: 1
# Total Cost (USD): $0.000031
它自带单价表,直接算出美元成本。 但只支持 OpenAI,
换供应商就得自己写。
三、调用前预估:别等花了才知道
重要场景:发送前先算一下会不会超预算/超窗口。
python
import tiktoken
def count_tokens(text: str, model: str = "gpt-4o-mini") -> int:
enc = tiktoken.encoding_for_model(model)
return len(enc.encode(text))
def count_messages(messages) -> int:
"""消息列表的 token 数(含格式开销)"""
total = 0
for m in messages:
total += 4 # 每条消息的固定开销
total += count_tokens(to_text(m.content))
total += count_tokens(m.type) # role
return total + 2 # 结尾开销
# 发送前检查
n = count_messages(messages)
if n > 8000:
logging.warning(f"上下文 {n} token,超出预算")
消息列表不能直接数文本 ------每条消息有约 4 token 的格式开销,
漏算会导致实际超出窗口。
四、五個降本动作(按性价比排序)
4.1 限制 max_tokens(零风险)
python
llm = ChatOpenAI(model="gpt-4o-mini", max_tokens=300)
输出 token 通常比输入贵 3~4 倍 ,
限制输出长度是最直接的省钱方式。
分类、标签、短问答这类任务,设 50~100 就够,
默认可能让它输出 500。
4.2 开缓存(省 30%~60%)
见上一篇。重复请求直接不花钱。
4.3 分级用模型(省 50%+)
python
cheap = ChatOpenAI(model="gpt-4o-mini") # $0.15/百万输入
strong = ChatOpenAI(model="gpt-4o") # $2.5/百万输入(约 17 倍)
def route(question: str):
llm = strong if is_complex(question) else cheap
return (prompt | llm).invoke({"q": question})
90% 的简单问题用便宜模型,只有 10% 需要强模型 ,
整体成本能降到只用强模型的 1/5。
4.4 缩短 prompt(省 10%~30%)
常见的浪费:
python
# ❌ 大段重复的规则,每条消息都带上
SYSTEM = """你是一个助手。你很专业。你要认真回答。
你不要胡说。你要简洁。你要用中文。你要......
(500 字)"""
# ✅ 压缩到核心
SYSTEM = """企业知识库助手。规则:
1. 只依据上下文回答,没有就说"我不知道"
2. 中文,简洁,句末标注 [编号] 来源"""
规则不是写得越全越好,太长反而会被忽略,还花钱。
RAG 场景更大的浪费是检索回来的上下文 。
用上下文压缩(前面讲过)把 8000 字压到 1200 字,
单次能省 80% 的输入 token。
4.5 减少不必要的调用
检查这几处:
- Agent 的步数 :每多一步就多一次完整上下文的调用。
recursion_limit不只是防死循环,也是控成本; - 多查询改写 :生成 5 个改写 = 5 次检索,
降到 3 个能省 40%; - 重排 :
top_n越大越贵,20 条够用就别用 50; - 开发调试:开缓存或用 Fake LLM。
五、做个成本看板
生产环境至少要能看到这三个数:
python
import logging
from collections import defaultdict
class CostMonitor(BaseCallbackHandler):
"""按用户/按场景统计成本"""
def __init__(self):
self.by_user = defaultdict(lambda: {"in": 0, "out": 0, "calls": 0})
def on_llm_start(self, serialized, prompts, *, metadata=None, **kwargs):
self._meta = metadata or {}
def on_llm_end(self, response, **kwargs):
uid = self._meta.get("user_id", "anon")
u = response.usage_metadata or {}
self.by_user[uid]["in"] += u.get("input_tokens", 0)
self.by_user[uid]["out"] += u.get("output_tokens", 0)
self.by_user[uid]["calls"] += 1
def report(self):
for uid, d in sorted(self.by_user.items(),
key=lambda x: -x[1]["in"] - x[1]["out"])[:10]:
logging.info(f"user={uid} 调用{d['calls']}次 "
f"输入{d['in']} 输出{d['out']} token")
重点看"谁在烧钱" ------
往往是少数几个用户/场景贡献了大部分成本,
找到他们针对性优化。
六、设置预算告警
python
class BudgetGuard(BaseCallbackHandler):
def __init__(self, limit_per_call: int = 10000):
self.limit = limit_per_call
self.session_total = 0
def on_llm_end(self, response, **kwargs):
u = response.usage_metadata or {}
total = u.get("total_tokens", 0)
self.session_total += total
if total > self.limit:
logging.error(f"单次调用 {total} token,超过阈值 {self.limit}")
if self.session_total > self.limit * 100:
logging.error("会话累计成本超限,已熔断")
raise RuntimeError("成本熔断")
熔断很重要 ------一个失控的 Agent 循环,
几分钟就能烧掉几百块。
七、常见误区
| 误区 | 真相 |
|---|---|
| "输入比输出贵" | 反过来,输出通常贵 3~4 倍 |
| "少写点 prompt 就能省大钱" | 输入便宜,大头在输出和调用次数 |
| "缓存能解决一切" | 用户提问各异,精确缓存命中率可能只有 10% |
| "用了便宜模型效果就差" | 简单任务上便宜模型差异极小,分级用 |
| "token 数 = 字符数" | 中文约 1~2 token/字,别按字符估 |
八、小结
- 统计用
response.usage_metadata(统一字段名)
或 回调 (能统计整条链);OpenAI 可用get_openai_callback直接看美元; - 发送前用 tiktoken 预估,消息列表要算上每条 4 token 的开销;
- 五个降本动作:限 max_tokens → 开缓存 → 分级用模型 → 缩短 prompt/上下文 → 减少调用次数;
- 输出 token 比输入贵 3~4 倍,这是优化的重点;
- 做成本看板找到"谁在烧钱",并设预算熔断防失控。
下一篇讲 LangSmith------链跑错了怎么查。