Token 成本优化的 7 个真实有效的方法
Token 成本优化的 7 个真实有效的方法
适读人群:AI 应用开发者、对降低 LLM 调用成本有需求的工程师 | 阅读时长:约 12 分钟 | 核心价值:7 个实际项目中验证过的 Token 成本优化方法,每个都有具体数据
我的账单是我最诚实的老师。
两年来,我做过大大小小十几个 AI 应用项目,每个月都在看账单,试各种优化方法,记录哪些真的省钱、哪些只是感觉上省了钱但实际没什么用。
今天写出来的这 7 个方法,每一个都在真实项目里跑过,都有具体的数据支撑。最后我也会说哪些「优化」是伪优化,别被那些理论文章骗了。
方法一:Prompt 瘦身——砍掉那些废话
这是最直接、收益最确定的方法。
很多 Prompt 是这样的:
你是一个专业的文本分析助手,你的任务是帮助用户分析文本内容。
你需要认真阅读用户提供的文本,然后根据文本内容进行深入分析,
识别出文本中的关键主题、情感倾向、以及重要的实体信息。
请确保你的分析是准确的、全面的、有深度的。
在分析过程中,请保持客观中立的态度,不要加入个人情感和偏见。
最终请以结构化的方式输出你的分析结果。
请分析以下文本:
{text}这段 Prompt 有大量冗余:「认真阅读」「深入分析」「准确的、全面的、有深度的」——这些都是废话,模型不需要你告诉它「要认真」才会认真。
优化后:
分析以下文本,输出:
- 主题(1-3个)
- 情感倾向(正面/负面/中性)
- 关键实体
文本:{text}实际数据:在一个每天处理 5000 条文本的项目里,Prompt 从 180 tokens 压缩到 35 tokens,节省 145 tokens/次。按每天 5000 次、输入 token $0.0015/1k 算,每月节省约 $32.6。不多,但这是什么都不改就能拿到的钱。
方法二:响应长度控制
LLM 默认倾向于生成比必要更长的回复。你不控制,它就不停写。
方法一:在 Prompt 里明确限制
分析以下文本,用不超过50字回答,直接给结论不需要解释过程。方法二:用 max_tokens 参数
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
max_tokens=150 # 根据任务需要设置合理的上限
)方法三:用结构化输出减少描述性文字
与其让模型写「经过分析,该文本的情感倾向为正面」,不如直接要求 JSON 输出 {"sentiment": "positive"}。
实际数据:一个客服意图识别功能,优化前平均输出 320 tokens(模型会解释识别过程),用 JSON 强制输出后平均 28 tokens。节省了 91% 的输出 token。输出 token 是输入的 3-4 倍价格,节省效果很显著。在日均 8000 次调用的规模下,每月节省约 $180。
方法三:缓存——同样的请求不要调两次
这个方法理论上大家都知道,但实际落地有门道。
精确缓存:对完全相同的输入做哈希,命中则直接返回缓存。适合:FAQ 问答、文本分类、数据格式转换等输入有限的场景。
import hashlib
import json
import redis
class LLMCache:
def __init__(self, redis_client, ttl_seconds=3600):
self.redis = redis_client
self.ttl = ttl_seconds
def _make_cache_key(self, model: str, messages: list, **kwargs) -> str:
"""生成缓存key"""
cache_content = {
'model': model,
'messages': messages,
'temperature': kwargs.get('temperature', 1.0),
'max_tokens': kwargs.get('max_tokens')
}
content_str = json.dumps(cache_content, sort_keys=True, ensure_ascii=False)
return 'llm:' + hashlib.sha256(content_str.encode()).hexdigest()
def get(self, model: str, messages: list, **kwargs):
key = self._make_cache_key(model, messages, **kwargs)
cached = self.redis.get(key)
if cached:
return json.loads(cached)
return None
def set(self, model: str, messages: list, response: dict, **kwargs):
key = self._make_cache_key(model, messages, **kwargs)
self.redis.setex(key, self.ttl, json.dumps(response, ensure_ascii=False))
# 使用示例
cache = LLMCache(redis_client, ttl_seconds=7200) # 2小时缓存
def cached_llm_call(messages: list, **kwargs):
# 1. 查缓存
cached = cache.get("gpt-4o-mini", messages, **kwargs)
if cached:
return cached # 缓存命中,0 token 消耗
# 2. 调API
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
**kwargs
)
# 3. 写缓存
cache.set("gpt-4o-mini", messages, response.model_dump(), **kwargs)
return response.model_dump()语义缓存:对语义相似的输入返回同一结果。实现复杂一些,需要向量相似度检索,但在问答场景下命中率更高。
from openai import OpenAI
import numpy as np
class SemanticCache:
def __init__(self, embedding_client, redis_client, similarity_threshold=0.95):
self.embed_client = embedding_client
self.redis = redis_client
self.threshold = similarity_threshold
def get_embedding(self, text: str) -> list:
response = self.embed_client.embeddings.create(
model="text-embedding-3-small",
input=text
)
return response.data[0].embedding
def cosine_similarity(self, a: list, b: list) -> float:
a_arr, b_arr = np.array(a), np.array(b)
return float(np.dot(a_arr, b_arr) / (np.linalg.norm(a_arr) * np.linalg.norm(b_arr)))
def find_similar(self, query: str, top_k: int = 5):
query_embedding = self.get_embedding(query)
# 从Redis获取所有缓存的embedding key
all_keys = self.redis.smembers('semantic_cache:keys')
best_match = None
best_similarity = 0
for key in all_keys:
cached_data = self.redis.get(f'semantic_cache:item:{key.decode()}')
if not cached_data:
continue
item = json.loads(cached_data)
similarity = self.cosine_similarity(query_embedding, item['embedding'])
if similarity > self.threshold and similarity > best_similarity:
best_similarity = similarity
best_match = item
return best_match, best_similarity实际数据:在一个内部知识库 QA 系统里,同类问题重复率很高(「如何申请报销」被不同措辞问了几百遍)。引入语义缓存后,缓存命中率达到 62%,每月 token 消耗减少 58%,节省约 $220。
方法四:用小模型做分诊,用大模型做复杂任务
不是所有任务都需要 GPT-4o。很多任务用 GPT-4o-mini 或 Claude Haiku 完全够用,价格便宜 10-20 倍。
关键是建立一个「任务路由」:
class TaskRouter:
"""根据任务复杂度路由到合适的模型"""
SIMPLE_TASKS = {
'sentiment_analysis', # 情感分析
'intent_classification', # 意图分类
'keyword_extraction', # 关键词提取
'text_translation', # 简单翻译
'format_conversion', # 格式转换
'simple_qa', # 简单问答(FAQ类)
}
COMPLEX_TASKS = {
'code_generation', # 代码生成
'complex_reasoning', # 复杂推理
'creative_writing', # 创意写作
'document_analysis', # 深度文档分析
'strategy_planning', # 策略规划
}
MODEL_MAPPING = {
'simple': 'gpt-4o-mini', # 便宜 + 快
'standard': 'gpt-4o-mini', # 默认走这个
'complex': 'gpt-4o', # 必要时用
}
def route(self, task_type: str, input_complexity: float = 0.5) -> str:
"""
input_complexity: 0-1,根据输入长度和内容复杂度评估
"""
if task_type in self.SIMPLE_TASKS:
return self.MODEL_MAPPING['simple']
if task_type in self.COMPLEX_TASKS:
# 复杂任务类型但输入简单,还是可以用小模型
if input_complexity < 0.3:
return self.MODEL_MAPPING['standard']
return self.MODEL_MAPPING['complex']
# 未知任务类型,根据复杂度决定
if input_complexity > 0.7:
return self.MODEL_MAPPING['complex']
return self.MODEL_MAPPING['standard']
def estimate_complexity(text: str) -> float:
"""简单的复杂度估算(0-1)"""
score = 0.0
# 长度因子
if len(text) > 2000:
score += 0.4
elif len(text) > 500:
score += 0.2
# 技术词汇因子(代码、专业术语等)
technical_patterns = [r'```', r'def ', r'SELECT ', r'import ']
import re
for pattern in technical_patterns:
if re.search(pattern, text):
score += 0.15
break
# 多步骤指令因子
if text.count('。') > 5 or text.count('\n') > 8:
score += 0.2
return min(score, 1.0)实际数据:一个内容审核 + 分类系统,原来全部用 GPT-4o,切换后 80% 的任务走 GPT-4o-mini,每月成本从 $680 降到 $120,节省 82%。分类准确率从 96% 降到 94%,在这个场景下可以接受。
方法五:批量处理替代单条调用
很多数据处理场景可以批量处理:
async def batch_classify_texts(texts: list[str], batch_size: int = 20) -> list[dict]:
"""
批量分类文本
把多条文本合并到一个API调用里,而不是一条一条调
"""
results = []
for i in range(0, len(texts), batch_size):
batch = texts[i:i + batch_size]
# 构建批量分类的Prompt
texts_formatted = "\n".join([
f"{j+1}. {text}"
for j, text in enumerate(batch)
])
response = await client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "user",
"content": f"""对以下{len(batch)}条文本分别做情感分析,
返回JSON数组,每个元素格式:{{"id": 序号, "sentiment": "positive/negative/neutral"}}
{texts_formatted}"""
}
],
response_format={"type": "json_object"},
max_tokens=len(batch) * 30 # 每条约30 tokens
)
batch_results = json.loads(response.choices[0].message.content)
results.extend(batch_results.get('results', []))
return results实际数据:每个 API 调用都有固定的 overhead(约 50-100 tokens 的系统消耗)。把 20 条文本合并为一次调用,vs. 20 次单独调用:
- 20 次单独调用:20 × (80 tokens overhead + 50 tokens 内容) = 2600 tokens
- 1 次批量调用:1 × (80 tokens overhead + 20 × 50 tokens 内容) = 1080 tokens
节省约 58%。
方法六:压缩上下文而不是删除历史
上篇文章里提过上下文管理,这里专门说成本角度:与其保留完整的历史消息,不如对历史做摘要压缩。
一段 20 轮的对话可能有 3000 tokens,摘要后可以压缩到 300 tokens,节省 90%。
关键是摘要本身的成本要低于它节省的成本:
async def compress_history(history: list, llm_client) -> str:
"""将对话历史压缩成摘要"""
history_text = "\n".join([
f"{m['role']}: {m['content'][:200]}" # 每条消息截取前200字做摘要
for m in history
])
# 用最便宜的模型做摘要
response = await llm_client.chat.completions.create(
model="gpt-4o-mini", # 便宜,够用
messages=[{
"role": "user",
"content": f"将以下对话压缩成100字以内的摘要,保留关键信息:\n{history_text}"
}],
max_tokens=150,
temperature=0
)
return response.choices[0].message.content实际数据:在一个平均 30 轮的长对话场景里,不做压缩时每次调用的上下文平均 4500 tokens,压缩后维持在 800 tokens 左右。在 GPT-4o 上,每次调用节省 3700 tokens 输入,按 $0.0025/1k tokens 算,每次节省约 $0.0093。日均 2000 次调用,每月节省约 $558。这是效果最明显的一个方法。
方法七:Prompt 缓存(Anthropic / OpenAI 均支持)
Anthropic 的 Prompt Caching 功能:对长 System Prompt 做缓存,重复调用时缓存部分只收取原价 10% 的费用。
import anthropic
client = anthropic.Anthropic()
# 包含大量知识的长System Prompt
LONG_SYSTEM_PROMPT = """
你是一个专业的法律助手。以下是相关法律条文和判例(约2000字):
[法律条文内容...]
[判例摘要...]
[常见问题解答...]
""" # 假设这段有 2000 tokens
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
system=[
{
"type": "text",
"text": LONG_SYSTEM_PROMPT,
"cache_control": {"type": "ephemeral"} # 标记为可缓存
}
],
messages=[
{"role": "user", "content": user_question}
]
)
# 第一次调用:正常价格
# 第二次及以后:system prompt部分只收10%实际数据:一个法律 QA 系统,System Prompt 包含大量条文,约 3500 tokens。日均 1000 次调用,启用 Prompt Caching 前后:
- 之前:1000 × 3500 tokens × $0.003/1k = $10.5/天
- 之后:前几次全价 + 剩余 90% 缓存命中 × $0.0003/1k ≈ $1.2/天
每月节省约 $280,而且 Prompt Caching 完全不影响输出质量。
哪些「优化」是伪优化
说完有效的,说几个我测过但不推荐的:
伪优化一:把英文翻译成中文 Prompt 节省 token
有人说中文比英文 token 效率高(同样的意思,中文 token 数更少)。实测:对于 Claude 和 GPT-4,中英文 Prompt 的效果差异大于 token 节省的收益。不要因为省几十个 token 影响质量。
伪优化二:手动拆词、缩写减少 token
把「我需要你帮我分析这段文本的情感倾向」改成「分析文本情感」。在极端情况下反而会降低输出质量,而且节省的 token 很少(就那么几个),成本几乎没变化。
伪优化三:用无系统提示(no system prompt)
把所有指令都塞进 user message 来省略 system message。实际上不会节省 token,只是把 system 角色换成 user,总量不变。而且会影响模型对指令的理解。
伪优化四:降低 temperature 省 token
temperature 控制随机性,不影响 token 数量,完全不相关。
一个优化优先级建议
按投入产出比排序:
- 语义缓存(高频重复场景,投入小,收益大)
- 响应长度控制(所有场景都适用,改一行代码)
- 模型分层路由(工程量适中,收益可达 70-80%)
- 历史压缩(长对话场景必做)
- Prompt Caching(长 System Prompt 场景,改动极小)
- 批量处理(批量数据处理场景)
- Prompt 瘦身(做了有收益,但单项收益有限)
这 7 个方法全部做完,在我的实际项目里综合节省了 75-85% 的 token 成本。不是夸大,是真实测出来的数字。
