Token 概念与成本
要点
- Token 是 LLM 处理文本的基本单位,不等于字或词
- Token 数量直接影响 API 调用成本和响应速度
- 中文大约 1 个字 = 1-2 个 token,英文大约 1 个单词 = 1-1.5 个 token
- 成本计算:输入 token + 输出 token,不同价格
- 控制成本的关键:减少不必要的 token,选择合适的模型
- 需要监控和限制 token 使用,防止成本失控
1. Token 是什么
1.1 定义
Token 是 LLM 处理文本的基本单位。LLM 不直接处理文字,而是先把文本转换成 token:
文本: "Hello, world!"
Token: ["Hello", ",", " world", "!"]1.2 Token 不是字
中文和英文的 token 化方式不同:
英文: "Hello world"
Token: ["Hello", " world"] // 2 个 token
中文: "你好世界"
Token: ["你", "好", "世界"] // 3 个 token规则:
- 英文:大约 1 个单词 = 1-1.5 个 token
- 中文:大约 1 个字 = 1-2 个 token
- 代码:变量名、关键字各算 token
1.3 为什么是 Token
Token 的设计影响模型的:
- 词汇量:token 越多,词汇量越大
- 上下文窗口:以 token 为单位计算
- 成本:按 token 数量计费
- 性能:token 越多,处理越慢
2. Token 计算
2.1 使用 Tiktoken
OpenAI 官方的 token 计算库:
import { encoding_for_model } from 'tiktoken'
// 为特定模型创建编码器
const encoder = encoding_for_model('gpt-4')
// 计算 token 数量
function countTokens(text: string): number {
return encoder.encode(text).length
}
// 示例
console.log(countTokens('Hello, world!')) // 4
console.log(countTokens('你好世界')) // 52.2 计算消息的 Token
function countMessageTokens(message: Message): number {
// 每条消息有固定的开销
let tokens = 4 // role, content 等元数据
tokens += countTokens(message.content as string)
return tokens
}
function countMessagesTokens(messages: Message[]): number {
let total = 0
for (const message of messages) {
total += countMessageTokens(message)
}
// 每次请求有固定的开销
total += 3 // 每次请求的 priming tokens
return total
}2.3 在线工具
3. 成本计算
3.1 定价模型
LLM API 按 token 计费,分为输入和输出:
// GPT-4 定价(2024 年)
const PRICING = {
'gpt-4': {
input: 0.03, // $/1K tokens
output: 0.06, // $/1K tokens
},
'gpt-4-turbo': {
input: 0.01,
output: 0.03,
},
'gpt-3.5-turbo': {
input: 0.0005,
output: 0.0015,
},
}3.2 成本计算
function calculateCost(
model: string,
inputTokens: number,
outputTokens: number
): number {
const pricing = PRICING[model as keyof typeof PRICING]
const inputCost = (inputTokens / 1000) * pricing.input
const outputCost = (outputTokens / 1000) * pricing.output
return inputCost + outputCost
}
// 示例
const cost = calculateCost('gpt-4', 1000, 500)
console.log(`Cost: $${cost.toFixed(4)}`) // $0.06003.3 实际成本示例
// 场景 1: 简单问答
// 输入: 100 tokens, 输出: 50 tokens
const cost1 = calculateCost('gpt-4', 100, 50) // $0.0060
// 场景 2: 长对话(10 轮)
// 输入: 5000 tokens, 输出: 500 tokens
const cost2 = calculateCost('gpt-4', 5000, 500) // $0.1800
// 场景 3: 代码审查(大段代码)
// 输入: 20000 tokens, 输出: 2000 tokens
const cost3 = calculateCost('gpt-4', 20000, 2000) // $0.7200
// 场景 4: 使用 GPT-3.5
const cost4 = calculateCost('gpt-3.5-turbo', 20000, 2000) // $0.01304. 成本控制策略
4.1 选择合适的模型
// 简单任务用便宜的模型
if (task.complexity === 'low') {
return 'gpt-3.5-turbo'
}
// 复杂任务用更好的模型
if (task.complexity === 'high') {
return 'gpt-4'
}4.2 减少输入 Token
缩短 System Prompt:
// ❌ 冗长
const systemPrompt = `
你是一位非常专业的编程导师,你有很多年的经验,
你擅长各种编程语言,包括 JavaScript、Python、Java...
(200 字)
`
// ✅ 简洁
const systemPrompt = '你是资深编程导师,擅长用简单语言解释复杂概念。'压缩历史对话:
// 使用滑动窗口,只保留最近 5 轮
const messages = [
system,
...history.slice(-10), // 只保留最近 10 条消息
currentUserMessage,
]摘要旧对话:
async function compressHistory(messages: Message[]): Promise<Message[]> {
if (messages.length <= 10) return messages
const old = messages.slice(0, -10)
const recent = messages.slice(-10)
const summary = await summarize(old)
return [
{ role: 'system', content: summary },
...recent,
]
}4.3 限制输出 Token
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: 'gpt-4',
messages,
max_tokens: 500, // 限制输出长度
}),
})4.4 缓存相似请求
import { createHash } from 'crypto'
const cache = new Map<string, string>()
async function callLLMWithCache(messages: Message[]): Promise<string> {
// 生成缓存 key
const key = createHash('sha256')
.update(JSON.stringify(messages))
.digest('hex')
// 检查缓存
if (cache.has(key)) {
return cache.get(key)!
}
// 调用 API
const response = await callLLM(messages)
const result = response.choices[0].message.content
// 存储到缓存
cache.set(key, result)
return result
}4.5 批量处理
// ❌ 逐个处理
for (const question of questions) {
await callLLM([
{ role: 'user', content: question },
])
}
// ✅ 批量处理
const batchPrompt = `
请回答以下问题:
1. ${questions[0]}
2. ${questions[1]}
3. ${questions[2]}
请按顺序回答。
`
await callLLM([
{ role: 'user', content: batchPrompt },
])5. Token 监控
5.1 记录 Token 使用
export const tokenUsage = pgTable('token_usage', {
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('user_id').notNull(),
model: text('model').notNull(),
inputTokens: integer('input_tokens').notNull(),
outputTokens: integer('output_tokens').notNull(),
cost: decimal('cost', { precision: 10, scale: 6 }).notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
})
async function trackUsage(
userId: string,
model: string,
inputTokens: number,
outputTokens: number
) {
const cost = calculateCost(model, inputTokens, outputTokens)
await db.insert(tokenUsage).values({
userId,
model,
inputTokens,
outputTokens,
cost: cost.toString(),
})
}5.2 实时查询
// 查询用户今日使用情况
async function getTodayUsage(userId: string) {
const today = new Date()
today.setHours(0, 0, 0, 0)
const usage = await db
.select({
totalTokens: sql<number>`sum(${tokenUsage.inputTokens} + ${tokenUsage.outputTokens})`,
totalCost: sql<string>`sum(${tokenUsage.cost})`,
})
.from(tokenUsage)
.where(and(
eq(tokenUsage.userId, userId),
gte(tokenUsage.createdAt, today)
))
return usage[0]
}5.3 配额限制
const QUOTA_LIMITS = {
free: { tokens: 100000, cost: 3.00 }, // 每月 10 万 token 或 $3
pro: { tokens: 1000000, cost: 30.00 },
enterprise: { tokens: -1, cost: -1 }, // 无限制
}
async function checkQuota(userId: string, estimatedTokens: number) {
const user = await db.query.users.findFirst({
where: eq(users.id, userId),
})
const plan = user?.plan || 'free'
const limits = QUOTA_LIMITS[plan as keyof typeof QUOTA_LIMITS]
if (limits.tokens === -1) return true // 无限制
const usage = await getMonthlyUsage(userId)
if (usage.totalTokens + estimatedTokens > limits.tokens) {
throw new Error('Token quota exceeded')
}
if (parseFloat(usage.totalCost) + estimatedCost > limits.cost) {
throw new Error('Cost quota exceeded')
}
return true
}6. 成本优化实践
6.1 使用更便宜的 Embedding 模型
// 向量搜索用便宜的模型
const embedding = await callOpenAI({
model: 'text-embedding-3-small', // $0.00002 / 1K tokens
input: text,
})
// 不需要用 GPT-4
// const embedding = await callOpenAI({
// model: 'gpt-4', // $0.03 / 1K tokens - 太贵了!
// })6.2 Prompt 缓存
OpenAI 支持 Prompt Caching,可以节省成本:
// 相同的 system prompt 会被缓存
const systemPrompt = '你是一位专业的编程导师...'
// 第一次调用:正常价格
await callLLM([
{ role: 'system', content: systemPrompt },
{ role: 'user', content: '问题 1' },
])
// 后续调用:缓存命中,价格降低 50%
await callLLM([
{ role: 'system', content: systemPrompt }, // 缓存命中
{ role: 'user', content: '问题 2' },
])6.3 流式响应的成本
流式响应不节省 token,但改善用户体验:
// 成本相同,但用户感觉更快
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: { ... },
body: JSON.stringify({
model: 'gpt-4',
messages,
stream: true, // 流式响应
}),
})
// 逐字返回给用户
for await (const chunk of response.body) {
const content = parseChunk(chunk)
sendToUser(content)
}6.4 避免不必要的调用
// ❌ 每次都调用 LLM
async function answer(question: string) {
return await callLLM([{ role: 'user', content: question }])
}
// ✅ 先检查缓存或简单规则
async function answer(question: string) {
// 1. 检查缓存
const cached = cache.get(question)
if (cached) return cached
// 2. 简单问题用规则
if (question === '你好') {
return '你好!有什么可以帮助你的吗?'
}
// 3. 复杂问题才调用 LLM
return await callLLM([{ role: 'user', content: question }])
}7. 成本分析仪表板
app.get('/api/usage/dashboard', async (c) => {
const userId = c.get('user').id
// 今日使用
const today = await getTodayUsage(userId)
// 本月使用
const month = await getMonthlyUsage(userId)
// 按模型统计
const byModel = await db
.select({
model: tokenUsage.model,
totalTokens: sql<number>`sum(${tokenUsage.inputTokens} + ${tokenUsage.outputTokens})`,
totalCost: sql<string>`sum(${tokenUsage.cost})`,
})
.from(tokenUsage)
.where(eq(tokenUsage.userId, userId))
.groupBy(tokenUsage.model)
// 趋势(最近 7 天)
const trend = await db
.select({
date: sql<string>`date(${tokenUsage.createdAt})`,
totalCost: sql<string>`sum(${tokenUsage.cost})`,
})
.from(tokenUsage)
.where(and(
eq(tokenUsage.userId, userId),
gte(tokenUsage.createdAt, new Date(Date.now() - 7 * 24 * 60 * 60 * 1000))
))
.groupBy(sql`date(${tokenUsage.createdAt})`)
.orderBy(sql`date(${tokenUsage.createdAt})`)
return c.json({
today,
month,
byModel,
trend,
})
})总结
Token 是 LLM 处理文本的基本单位,直接影响成本和性能。控制 token 使用是 AI 应用的关键。
这一节涉及到的几个实践:
- Token 概念:不等于字或词,中文约 1 字 = 1-2 token
- Token 计算:使用 Tiktoken 库
- 成本计算:输入 + 输出 token,不同价格
- 成本控制:选择合适模型、减少输入、限制输出、缓存
- 监控:记录使用、实时查询、配额限制
- 优化:便宜模型、Prompt 缓存、避免不必要调用
Token 管理是 AI 应用的核心。好的 token 管理能显著降低成本,提高性能。
下一篇看温度、Top P 与采样参数——如何控制 AI 输出的随机性和创造性。