LangChain Token 计数与成本控制:先知道钱花在哪

个人主页: > for_ever_love__ <(欢迎各位大佬莅临😊)
其他栏目: > 大模型开发从0到1 <
其他栏目: > iOS项目总结大全 <
其他栏目: > 我想学python了 <
其他栏目: > iOS UI <

文章目录

  • [LangChain Token 计数与成本控制:先知道钱花在哪](#LangChain Token 计数与成本控制:先知道钱花在哪)

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------链跑错了怎么查。

相关推荐
m0_547486661 小时前
《数据结构 (C语言)》期末试卷及答案(10套)2026版
c语言·开发语言·数据结构
Sand(ContextGate)1 小时前
从开发者调试工具到消费级 App:一个 AI 电商导购项目的 C 端改造与 6 个连环踩坑实录
开发语言·人工智能·python·ai·langchain
白杨尚青1 小时前
C++入门篇(十七):priority_queue 完整模拟实现——仿函数、区间建堆与指针特化(附完整代码仓库)
开发语言·c++·stl·仿函数
逐光者9332 小时前
滤波器的 R 与 C 如何选择:从截止频率到工程实践
c语言·开发语言·r语言
Eider19982 小时前
大模型输出格式幻觉解决思路
开发语言·前端·javascript
Want5952 小时前
Python太极八卦图
开发语言·python
饕餮争锋2 小时前
python开发工具uv简介
开发语言·python·uv
丨白色风车丨2 小时前
LangChain 1.0 核心用法速查:创建模型、创建 Agent、调用
数据库·langchain
奇树谦2 小时前
LRU 缓存原理与 C++ 实践示例
开发语言·c++·缓存