OpenAI API 请求前令牌计数方法
问题背景
OpenAI 的文本模型有上下文长度限制(例如 GPT-4 模型的上下文窗口为 128,000 令牌。在生成文本时,您需要设置 max_tokens
参数来控制生成文本的长度,确保:
text
max_tokens = 模型最大上下文长度 - 提示内容令牌数
但关键问题在于:如何在发送 API 请求前准确计算提示文本的令牌数量,从而避免超过模型限制或浪费资源。
核心技术方案
1. 使用官方 tiktoken 库
官方推荐方案
OpenAI 官方提供了 tiktoken 库用于精确计数字符串令牌数:
python
# 安装库
pip install tiktoken
基础用法
python
import tiktoken
def count_tokens(prompt: str, model: str) -> int:
"""计算指定模型下提示的令牌数"""
try:
# 为模型获取正确的编码器
encoding = tiktoken.encoding_for_model(model)
except KeyError:
# 后备编码方案(新版模型使用)
encoding = tiktoken.get_encoding("cl100k_base")
return len(encoding.encode(prompt))
# 示例用法
prompt = "Hello world, 自然语言处理很有趣!"
token_count = count_tokens(prompt, "gpt-3.5-turbo")
print(f"令牌数量: {token_count}")
不同模型的编码方案
编码方案 | 支持的 OpenAI 模型 |
---|---|
o200k_base | GPT-4o 系列 |
cl100k_base | GPT-4/GPT-3.5 系列/Embedding 模型 |
p50k_base | 已弃用 |
重要提示
新模型(例如 GPT-4o)需要使用 o200k_base
编码,确保选择与目标模型匹配的编码器
2. 聊天消息的令牌计算
当使用 Chat Completions API 时,系统消息和消息元数据也需计入令牌数:
python
import tiktoken
def count_chat_tokens(messages: list, model: str) -> int:
"""计算聊天消息的令牌总数"""
try:
encoding = tiktoken.encoding_for_model(model)
except KeyError:
encoding = tiktoken.get_encoding("cl100k_base")
tokens_per_message = 3 # GPT-3.5/4 的默认值
tokens_per_name = 1
total_tokens = 0
for message in messages:
total_tokens += tokens_per_message
for key, value in message.items():
total_tokens += len(encoding.encode(value))
if key == "name":
total_tokens += tokens_per_name
total_tokens += 3 # 结尾标记
return total_tokens
# 示例聊天消息结构
messages = [
{"role": "system", "content": "你是有帮助的助手"},
{"role": "user", "content": "解释量子计算"}
]
token_count = count_chat_tokens(messages, "gpt-4")
模型差异警告
令牌计算规则因模型版本而异:
- GPT-3.5-turbo-0301:
tokens_per_message = 4
- 新型号模型:
tokens_per_message = 3
3. 动态设置 max_tokens
基于令牌计数结果动态计算最大生成长度:
python
def calculate_max_tokens(prompt_tokens: int, model: str) -> int:
"""计算可用的max_tokens值"""
model_context_size = {
"gpt-3.5-turbo": 4096,
"gpt-4-1106-preview": 128000,
"gpt-4o": 128000
}
# 默认为4096如果未找到模型
context_size = model_context_size.get(model, 4096)
return context_size - prompt_tokens - 50 # 预留安全空间
# 使用示例
max_output_tokens = calculate_max_tokens(prompt_tokens, "gpt-4o")
多语言实现方案
除 Python 外,其他语言的令牌计数器实现:
JavaScript
javascript
// 使用 npm 包 @dqbd/tiktoken
import { encoding_for_model } from "@dqbd/tiktoken";
const encoder = encoding_for_model("gpt-4");
const tokens = encoder.encode("你的文本");
console.log(`Tokens: ${tokens.length}`);
Java
java
// 使用 jtokkit 库
import com.knuddels.jtokkit.api.EncodingRegistry;
EncodingRegistry registry = Encodings.newDefaultEncodingRegistry();
var encoding = registry.getEncodingForModel("gpt-4");
int tokenCount = encoding.countTokens("您的文本");
常见问题解答
为什么 tiktoken 与 API 报告值有差异?
- API 返回值包含特殊控制令牌(如
<|start|>
) - 推荐在实际请求后确认
response.usage.prompt_tokens
计算令牌的最佳实践
- 始终为您的目标模型指定正确编码器
- 在部署前验证本地计数与 API 返回值
- 为长文本设置令牌缓存机制
- 使用
max_tokens = context_size - prompt_tokens - buffer
公式(建议预留 50 令牌缓冲)
官方更新备注:截至 2024 年 5 月,
cl100k_base
编码支持所有 GPT-3.5/4 系列模型,o200k_base
用于 GPT-4o
备选方案比较
方法 | 精度 | 易用性 | 适用场景 |
---|---|---|---|
tiktoken | ★★★★★ | ★★★★ | 生产环境首选 |
API 后验计数 | ★★★★★ | ★★★ | 结果验证 |
基于空格/字符的分割 | ★★☆ | ★★★★★ | 快速估算/后备方案 |
注意事项:避免使用过时的 GPT-2 分词器,其与 OpenAI 当前模型的分词方式存在显著差异。
扩展工具资源
- 在线工具:OpenAI 官方分词查看器
- 验证脚本:OpenAI Cookbook 示例
- 高级方案参考:LangChain Token 管理模块