1. 引言:为什么需要优化 Token 消耗?

在大模型(LLM)应用开发中,Token 是计算成本与 API 费用的核心计量单位。无论是 OpenAI GPT、Claude、文心一言还是通义千问,其定价通常基于输入与输出的 Token 数量。一次不经优化的调用可能消耗数千甚至数万 Token,导致:

  • 成本飙升:高频调用下,费用可能远超预期。
  • 响应延迟:过长的输入/输出会增加网络传输与模型计算时间。
  • 上下文窗口受限:模型有最大上下文长度限制(如 128K),低效的 Token 使用会更快触及上限,影响长文档处理能力。

因此,掌握 Token 优化技巧是构建高效、经济大模型应用的关键。本文将系统介绍从提示工程、数据预处理到架构设计的全方位优化策略。

2. 理解 Token:计数规则与成本构成

2.1 什么是 Token?

Token 是模型处理文本的基本单位。在英文中,一个 Token 大约对应 0.75 个单词;在中文中,一个汉字通常为 1-2 个 Token。标点符号、空格也可能被计为 Token。

示例:

  • "Hello, world!" → 可能被分词为 ["Hello", ",", " world", "!"],计为 4个 Token。
  • "你好,世界!" → 可能被分词为 ["你", "好", ",", "世", "界", "!"],计为 6个 Token。

2.2 成本公式

单次调用成本 ≈ (输入Token数 + 输出Token数) × 单价
优化需同时关注 输入(Prompt) 与 输出(Completion) 两部分。

3. 核心优化策略

3.1 提示工程优化(减少输入 Token)

这是最直接、最有效的优化手段。

策略具体做法预期效果
精简指令删除冗余客套话、重复说明。使用清晰、简洁的指令。减少 10%-30% 的 Prompt Token。
使用系统提示(System Prompt)将固定的角色设定、行为准则放在 system 消息中,它通常比 user 消息计价更低(取决于平台)。优化成本结构。
结构化输入使用 JSON、YAML、XML 等格式传递结构化数据,而非冗长的自然语言描述。提高信息密度,减少解释性文字。
示例少而精(Few-Shot)提供 1-2 个最具代表性的示例,而非堆砌大量例子。显著减少示例部分的 Token 消耗。
利用函数调用(Function Calling)让模型返回结构化数据(如 JSON)而非长文本,再由代码解析执行。精准控制输出长度,避免模型“自由发挥”产生冗余。

优化前:

用户:你好,AI助手。我希望你能帮我总结一下下面这篇文章的核心内容。这篇文章可能有点长,请你耐心阅读。总结时请抓住重点,不要太啰嗦,但也要全面。文章内容是:...

优化后:

系统:你是一个专业的文本总结助手。
用户:总结以下文章:...

3.2 输出控制优化(减少输出 Token)

控制模型的“表达欲”,引导其产出精炼内容。

策略具体做法预期效果
设置 max_tokens 参数明确限制生成文本的最大长度,避免生成无关紧要的结尾。直接防止输出过长。
使用停止序列(Stop Sequences)设置如 “。”、“总结完毕”、“```” 等停止词,让模型在合适位置自然结束。避免生成格式尾部的多余内容。
明确输出格式与长度在指令中要求“用100字以内总结”、“以要点列表形式输出”、“返回JSON对象”。引导模型进行自我约束。
流式传输(Streaming)虽然不减少总 Token 数,但可以边生成边处理,提升用户体验,并在达到所需信息后提前中断。改善感知速度,允许提前终止。

3.3 数据预处理优化

在数据送入模型前进行“瘦身”。

策略具体做法适用场景
文本清洗与压缩移除无关的广告、页眉页脚、多余的空格和换行符。处理网页爬取内容、PDF 解析文本。
摘要或提取关键句先用轻量级模型或算法(如 TextRank)对长文档进行预摘要,再将摘要送入大模型。处理超长文档(远超上下文窗口)。
分块处理(Chunking)将长文档按语义切分成块,分别处理后再合并结果。需设计好块间的上下文传递。文档问答、长文本分析。
嵌入检索(RAG)建立向量数据库,仅检索与问题最相关的文档片段送入上下文,而非整个文档库。知识库问答、私有数据查询。

3.4 架构与缓存优化

从系统设计层面减少不必要的调用。

策略具体做法预期效果
实现对话状态管理在多轮对话中,只将最新的用户消息和必要的上下文历史(而非全部历史)发送给模型。避免对话历史无限增长导致 Token 爆炸。
缓存重复结果对相同或相似的查询结果进行缓存(TTL缓存),直接返回缓存内容。对常见、确定性高的查询大幅减少调用。
使用更经济的模型非核心任务使用小型/廉价模型(如 GPT-3.5-Turbo vs GPT-4)。直接降低单价。
批量处理(Batching)将多个独立请求合并为一个批量请求发送(如果 API 支持)。减少网络开销,有时享有批量折扣。

4. 实战工具与代码示例

4.1 使用 tiktoken 库精确计算 Token(Python)

import tiktoken

def num_tokens_from_string(string: str, encoding_name: str = "cl100k_base") -> int:
    """返回文本的 Token 数量。"""
    encoding = tiktoken.get_encoding(encoding_name)
    num_tokens = len(encoding.encode(string))
    return num_tokens

text = "大模型调用,如何优化Token消耗?"
print(f"Token 数: {num_tokens_from_string(text)}")  # 输出示例: Token 数: 13

4.2 构建一个带 Token 预算的对话管理器

class ConversationManager:
    def __init__(self, system_prompt, max_context_tokens=4096, token_buffer=200):
        self.messages = [{"role": "system", "content": system_prompt}]
        self.max_context_tokens = max_context_tokens
        self.token_buffer = token_buffer # 为输出预留空间
        self.encoder = tiktoken.get_encoding("cl100k_base")

    def _count_tokens(self, messages):
        """估算消息列表的Token数(简化版)。"""
        total = 0
        for msg in messages:
            total += len(self.encoder.encode(msg["content"]))
            total += 4  # 每个消息的格式开销近似值
        return total

    def add_user_message(self, user_input):
        self.messages.append({"role": "user", "content": user_input})
        self._prune_context()

    def _prune_context(self):
        """修剪上下文,确保不超过Token限制。"""
        while self._count_tokens(self.messages) > (self.max_context_tokens - self.token_buffer):
            # 优先移除最早的`user`/`assistant`对话,保留`system`提示
            if len(self.messages) > 1 and self.messages[1]["role"] in ("user", "assistant"):
                removed = self.messages.pop(1)
                print(f"已移除历史消息以节省Token: {removed['content'][:50]}...")
            else:
                break # 无法再修剪

# 使用示例
manager = ConversationManager("你是一个有帮助的助手。", max_context_tokens=2000)
manager.add_user_message("什么是机器学习?")
# ... 多次对话后,上下文会自动修剪

更多推荐