如何在FoundationModels中正确获取对话会话的剩余令牌数?
如何在FoundationModels中正确获取对话会话的剩余令牌数?
兄弟,我太懂你这种靠经验拍脑袋凑数的痛苦了——用空格拆分单词算令牌数确实太粗糙,LLM的令牌化逻辑可不是简单按空格或换行来的,长单词会拆、短词可能合并、标点甚至中文汉字的令牌数都有自己的规则,完全不准。下面给你说最靠谱的解决办法,都是实战过的:
核心思路:用官方令牌编码器精准计算
每个大模型都有自己专属的令牌化规则,只有用模型对应的官方TokenEncoder编码对话历史,得到的令牌数才是100%准确的,再也不用靠经验蒙1000这种数了。
具体实现步骤(Swift + FoundationModels)
假设你用的是苹果的FoundationModels框架(从你代码的Swift风格看应该是),直接上可运行的代码:
- 先导入框架并初始化对应模型的令牌编码器
import FoundationModels // 全局或类内持有令牌编码器(不要每次计算都初始化,影响性能) private var tokenEncoder: TokenEncoder? // 在初始化方法里创建编码器,模型名称要和你实际用的完全一致 init() { do { // 比如用gpt-3.5-turbo,换成你实际使用的模型名(比如gpt-4、gpt-3.5-turbo-16k) tokenEncoder = try TokenEncoder(model: "gpt-3.5-turbo") } catch { print("初始化令牌编码器失败:\(error.localizedDescription)") } }
- 计算对话历史的已用令牌数
private var conversationHistory: String = "" // 你的对话历史字符串(要包含所有轮次的用户/助手消息) private func usedTokensInConversation() -> Int { guard let encoder = tokenEncoder, !conversationHistory.isEmpty else { return 0 } do { // 把完整对话历史编码成令牌数组,取数组长度就是已用令牌数 let tokens = try encoder.encode(conversationHistory) return tokens.count } catch { print("编码对话历史时出错:\(error.localizedDescription)") return 0 } }
- 计算剩余令牌数(带安全余量)
// 对应模型的最大上下文窗口令牌数,比如: // gpt-3.5-turbo → 4096 // gpt-3.5-turbo-16k → 16384 // gpt-4 → 8192 / 32768 private let maxContextTokens = 4096 // 安全余量,避免刚好卡满令牌导致提交失败,可根据需求调整 private let safeMargin = 596 // 4096 - 3500,和你之前的safeTokenLimit对应 private var remainingTokens: Int { let used = usedTokensInConversation() // 确保剩余令牌数不会为负数 return max(0, maxContextTokens - used - safeMargin) }
必须注意的几个细节
- 模型名称必须完全匹配:不同模型的令牌编码器不一样,比如
gpt-3.5-turbo和gpt-3.5-turbo-16k的编码器是不同的,填错了会导致计算不准。 - 对话历史要完整:如果你的对话是多轮结构化的(比如
"用户:你好\n助手:你好呀!\n用户:..."),要把整个结构化的字符串传进去编码,包括角色标识和换行,这些都会占令牌。 - 别漏了系统提示词:如果你的对话有系统级提示(比如
"你是一个专业的技术助手,回答要简洁准确"),这个也要算进对话历史里,它的令牌数也会占用上下文窗口。 - 缓存令牌编码器:不要每次计算剩余令牌都重新初始化
TokenEncoder,初始化是有开销的,全局或类内持有一个实例就行。
为什么你之前的方法会错?
你之前用
conversationHistory.components(separatedBy: .whitespacesAndNewlines).count的问题:
- 令牌≠单词/汉字:比如英文中
"don't"会拆成2个令牌,中文"苹果手机"会拆成3个左右的令牌,按空格拆分完全算不准。- 忽略了非单词字符:标点、换行、特殊符号(比如
@#$)都会被编码成令牌,你的方法完全没统计这些。- 合并令牌的情况:有些常用短词组合会被合并成一个令牌(比如
"of the"),按单词数算会多算令牌数。
用官方编码器的话,这些问题都能解决,计算出来的剩余令牌数和模型实际判断的完全一致,再也不会出现“明明算着还有剩余,提交却报错上下文过长”的情况了!




