You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在FoundationModels中正确获取对话会话的剩余令牌数?

如何在FoundationModels中正确获取对话会话的剩余令牌数?

兄弟,我太懂你这种靠经验拍脑袋凑数的痛苦了——用空格拆分单词算令牌数确实太粗糙,LLM的令牌化逻辑可不是简单按空格或换行来的,长单词会拆、短词可能合并、标点甚至中文汉字的令牌数都有自己的规则,完全不准。下面给你说最靠谱的解决办法,都是实战过的:

核心思路:用官方令牌编码器精准计算

每个大模型都有自己专属的令牌化规则,只有用模型对应的官方TokenEncoder编码对话历史,得到的令牌数才是100%准确的,再也不用靠经验蒙1000这种数了。

具体实现步骤(Swift + FoundationModels)

假设你用的是苹果的FoundationModels框架(从你代码的Swift风格看应该是),直接上可运行的代码:

  1. 先导入框架并初始化对应模型的令牌编码器
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)")
    }
}
  1. 计算对话历史的已用令牌数
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
    }
}
  1. 计算剩余令牌数(带安全余量)
// 对应模型的最大上下文窗口令牌数,比如:
// 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-turbogpt-3.5-turbo-16k的编码器是不同的,填错了会导致计算不准。
  • 对话历史要完整:如果你的对话是多轮结构化的(比如"用户:你好\n助手:你好呀!\n用户:..."),要把整个结构化的字符串传进去编码,包括角色标识和换行,这些都会占令牌。
  • 别漏了系统提示词:如果你的对话有系统级提示(比如"你是一个专业的技术助手,回答要简洁准确"),这个也要算进对话历史里,它的令牌数也会占用上下文窗口。
  • 缓存令牌编码器:不要每次计算剩余令牌都重新初始化TokenEncoder,初始化是有开销的,全局或类内持有一个实例就行。

为什么你之前的方法会错?

你之前用conversationHistory.components(separatedBy: .whitespacesAndNewlines).count的问题:

  1. 令牌≠单词/汉字:比如英文中"don't"会拆成2个令牌,中文"苹果手机"会拆成3个左右的令牌,按空格拆分完全算不准。
  2. 忽略了非单词字符:标点、换行、特殊符号(比如@#$)都会被编码成令牌,你的方法完全没统计这些。
  3. 合并令牌的情况:有些常用短词组合会被合并成一个令牌(比如"of the"),按单词数算会多算令牌数。

用官方编码器的话,这些问题都能解决,计算出来的剩余令牌数和模型实际判断的完全一致,再也不会出现“明明算着还有剩余,提交却报错上下文过长”的情况了!

火山引擎 最新活动