你把程式的模型名稱換成 gpt-6-astra,還沒送出 API 請求,tiktoken 就丟出 KeyError。換成另一個名稱又能跑了:是模型不能用、套件太舊,還是你挑錯了切字規則?這三件事要分開查,否則只是把錯誤藏進一個看似正常的數字。
這篇寫給第一次替 AI 應用核對用量、願意複製幾行 Python 的新手。先用白話搞懂 Tokenizer,再完成四步工作表:查名稱、算文字、計請求、對用量。沒有程式背景也能先讀懂每一張收據;實作部分需要已有 Python 環境,服務端步驟另需自己的 OpenAI API 存取。
本文依截至 2026 年 10 月 10 日的官方原始碼與 API 文件整理。AlphaLab 本次在 macOS/Apple Silicon、Python 3.9.6、tiktoken 0.14.0 執行了名稱查找與五類純文字測例;未呼叫模型或 input token count API,因此下方服務端程式是待你執行的驗證流程。
先說結論:tiktoken 查名稱,API 收據查用量
名稱映射是索引,文字計數是尺,API usage 是收據。像寄包裹:先查貨品編號,再量物品,最後看整件包裹的收據。量到物品,還不等於量到外箱;索引查不到,也不能直接推定物流服務拒收。
- 查名稱失敗:保存套件版本與完整模型名稱,先確認錯誤發生在哪一層。
- 選 encoding(編碼規則)估算:直接指定規則可以切文字,但未知模型的對應關係仍須有證據。
- 計完整請求:使用 Responses API 的 input token count 端點,把角色、工具與輸入一起交給服務端。
- 生成後對帳:保存實際
usage,將輸入、輸出與各分項放回同一次請求。
原理:Tokenizer 是什麼?為何中文字數對不上?
Token 是模型讀寫文字的單位;Tokenizer 是把文字切成這些單位的程式。它像一套切菜規則,同一盤食材可以切成不同大小。tiktoken 官方 README說明它使用 BPE(Byte Pair Encoding,位元組對編碼),將常見片段表示成 token。想補上模型如何使用這些單位,可先讀LLM 原理白話介紹。
一個中文字、一個英文單字或一個看得見的 Emoji,都不應直接當成一個 token。Emoji 可能含連接符與膚色修飾;JSON 的引號、空白與程式碼換行也都是輸入的一部分。你需要保存原始字串,而不是只寫「中文一句話」。同樣重要的是 encoding 名稱:同一段文字換一把尺,計數可能不同。
模型名稱與 encoding 名稱也不是同一種名稱。encoding_for_model()先查模型的對照,再取得 encoding;get_encoding()則直接取得你指定的規則。本次核對的 model.py依序檢查完整名稱與前綴,找不到就拋 KeyError。前綴匹配甚至可能接受不存在的模型字串,所以「查得到」也不是 API 存取的證明。

第一步:固定 tiktoken 版本,先驗名稱映射
痛點是同一段程式在不同電腦有不同結果。解法是把版本和查找結果一起記。在專案的獨立環境執行 python3 -m venv .venv,macOS/Linux 用 source .venv/bin/activate 啟用,再執行 python -m pip install "tiktoken==0.14.0"。Windows 的啟用路徑可對照Python venv 文件;若 wheel 與既有 Python 不相容,先處理安裝錯誤,不把它算成模型映射失敗。
import platform
from importlib.metadata import version
from tiktoken.model import encoding_name_for_model
print(platform.python_version(), version("tiktoken"))
for model in ("gpt-6-astra", "gpt-6-sol", "gpt-6-luna", "gpt-5.6-sol", "gpt-4o"):
try:
print(model, encoding_name_for_model(model))
except KeyError:
print(model, "KeyError: mapping unresolved")
本次三個 GPT-6 名稱均為 KeyError;gpt-5.6-sol 與 gpt-4o 均解析成 o200k_base。這個結果只限上述版本與名稱查找。對照組成功讓你知道查找函式有正常運作,還不能回答 GPT-6 服務端到底用哪個 tokenizer。
官方倉庫 issue #608於 2026 年 9 月 24 日開立,10 月 7 日有相同版本的查找紀錄,10 月 9 日又引用第三方樣本比較。本次查核時仍開啟。那些樣本可提供調查方向,但不能將樣本計數相同升格為所有輸入等價,或維護者已確認的模型映射。
第二步:tiktoken 文字計數,給估算加上身分
查不到名稱,如何先檢查本機切字?解法是明示候選 encoding,在紀錄裡標成 experimental_for_gpt6。把下方存成 text_probe.py,執行 python text_probe.py。兩種 encoding 是對照用的尺,不是替 GPT-6 宣告答案。
import hashlib, json, tiktoken
from pathlib import Path
samples = {
"zh": "請用繁體中文說明 Token。",
"en": "Explain tokens in plain English.",
"emoji": "👨👩👧👦👍🏽🙂",
"json": '{"ok":true,"count":3}',
"code": "def add(a, b):\n return a + b\n",
}
rows = []
for name in ("o200k_base", "cl100k_base"):
enc = tiktoken.get_encoding(name)
for sample, text in samples.items():
ids = enc.encode(text)
assert enc.decode(ids) == text
rows.append({"sample": sample, "encoding": name, "count": len(ids),
"ids": ids, "text": text,
"text_sha256": hashlib.sha256(text.encode()).hexdigest(),
"status": "experimental_for_gpt6"})
Path("local-counts.json").write_text(json.dumps(rows, ensure_ascii=False, indent=2))
本機五類樣本已跑完,並通過整串 encode/decode 回復檢查。JSON 與程式碼樣本在兩種 encoding 下碰巧有相同的 token 數,token IDs 卻不同。這正好提醒你:數量相同,仍不代表切分規則相同。回復原文證明的是這次本機往返,不是與 API 等價。
如果輸入含 <|endoftext|> 之類特殊字串,預設 encode() 可能拋 ValueError;這和模型名稱的 KeyError 是兩種問題。官方 core.py分別提供特殊 token 參數與 encode_ordinary()。要把該字串當普通文字,可以明確採用後者,並記錄這個選擇;不要為了消除錯誤一律開 allowed_special="all"。
只想在終端快速看 token,ttok 作者倉庫提供計數、截斷和列出 tokens 的 CLI。但它的模型選擇仍經過 tiktoken 的查找流程。換一層命令列介面,不能自動替未知映射取得新的權威證據。
第三步:計完整 API 請求,一次只加一層
痛點是你只量 user 文字,實際請求還帶著 developer 指令與工具描述。解法是把同一份輸入依序包成三種形狀:A 只有字串;B 加角色與指令;C 保留 B,再加工具 schema(欄位規格)。不要同時換模型、文字和工具,否則差值很難歸因。
官方 input token count API提供 POST /v1/responses/input_tokens。它回傳請求輸入計數,可接收訊息與工具等結構;計數指南也說明角色、邊界等格式 token 可能不在你本機切分的字串裡。Python SDK 寫法是 client.responses.input_tokens.count()。
在同一環境執行 python -m pip install openai,保存 python -m pip freeze 的版本清單,再將下方存為 api_probe.py。預設只計數;先跑 python api_probe.py --sample zh。key 由隱藏輸入提供,程式不寫進收據。樣本都是本文公開的測試字串;不要替換成未獲准送出的資料。
import argparse, getpass, hashlib, json
from pathlib import Path
from openai import OpenAI
parser = argparse.ArgumentParser()
parser.add_argument('--sample', choices=['zh','en','emoji','json','code'], default='zh')
parser.add_argument('--generate', action='store_true')
args = parser.parse_args()
samples = {
'zh': '請用繁體中文說明 Token。',
'en': 'Explain tokens in plain English.',
'emoji': '👨👩👧👦👍🏽🙂',
'json': '{"ok":true,"count":3}',
'code': 'def add(a, b):\n return a + b\n',
}
text = samples[args.sample]
client = OpenAI(api_key=getpass.getpass('API key: '), max_retries=0, timeout=30)
tool = {
'type': 'function', 'name': 'lookup_note',
'description': 'Look up a note by its ID.', 'strict': True,
'parameters': {'type': 'object', 'properties': {'id': {'type': 'string'}},
'required': ['id'], 'additionalProperties': False},
}
variants = {
'text': {'input': text},
'messages': {'input': [{'role': 'developer', 'content': 'Reply briefly.'},
{'role': 'user', 'content': text}]},
'tools': {'input': [{'role': 'developer', 'content': 'Reply briefly.'},
{'role': 'user', 'content': text}],
'tools': [tool], 'tool_choice': 'none'},
}
root = Path('receipts') / args.sample
root.mkdir(parents=True, exist_ok=True)
for name, shape in variants.items():
payload = {'model': 'gpt-6-astra', **shape}
encoded = json.dumps(payload, ensure_ascii=False, sort_keys=True).encode()
(root / f'{name}-payload.json').write_bytes(encoded)
receipt = {'sample': args.sample, 'variant': name,
'payload_sha256': hashlib.sha256(encoded).hexdigest()}
try:
count = client.responses.input_tokens.count(**payload)
receipt['count'] = count.model_dump()
if args.generate:
response = client.responses.create(**payload, max_output_tokens=256, store=False)
receipt['response_id'] = response.id
receipt['status'] = response.status
receipt['usage'] = response.usage.model_dump() if response.usage else None
except Exception as exc:
receipt['error_type'] = type(exc).__name__
(root / f'{name}-receipt.json').write_text(json.dumps(receipt, ensure_ascii=False, indent=2))
raise SystemExit(f'{name}: {type(exc).__name__}; stop and inspect this receipt')
(root / f'{name}-receipt.json').write_text(json.dumps(receipt, ensure_ascii=False, indent=2))
print(name, receipt)
每一組產出 payload 與 receipt 檔。把 --sample 改成 en、emoji、json、code,就能讓同五類測例走完整三組對照。C 設 tool_choice="none",保留工具定義但不要求工具呼叫;這輪看的是請求結構,不是工具真正執行的成本。
這份小程式使用非串流、關閉 SDK 自動重試;任何例外先記類型並停止。官方 SDK 的錯誤與重試說明可用來擴充正式系統的 timeout、429/5xx 退避與 request ID 記錄。此處省略自動恢復,讓第一輪不會在不知情下增加生成次數。權限或模型存取失敗應另列,不填成 token 數零。
第四步:生成後核對 usage,別把輸出當輸入差額
計數完成後,若你已接受這幾次生成的費用,再執行 python api_probe.py --sample zh --generate。這會為該樣本的三個變體各計數一次、各生成一次。生成使用相同的 model、input、工具與工具選擇,另加 max_output_tokens=256 與 store=False;因此要將共享的輸入設定與生成控制分開保存。
請求上限是生成 token 的上限,不保證交出 256 個可見文字 tokens。把生成回應的 status 和 usage 一起留下;若回應不完整,也仍是一份必須對帳的結果。本篇沒有用量數字可讓你照抄,驗收是把自己的 count 與回應放在同一列。
- 先比
count.input_tokens與該次usage.input_tokens;這才是輸入範圍的對照。 - 將
usage.output_tokens和total_tokens另存,不把 total 與本機文字數直接相減。 - 保留
input_tokens_details與output_tokens_details原始分項,再看快取與 reasoning。分項的層級要按這個 API 的 schema 解讀。 - 差異存在時,先核對 payload 指紋、模型、歷史訊息、工具與輸入是否一致;差異為零時,也只記「此測例吻合」。
官方 ResponseUsage 定義列出輸入、輸出、總量與細部分項。Chat Completions 的欄位名稱則與 Responses 不同;你應先看端點,再找欄位。接下來若要把用量換成團隊成本,可讀API 成本歸屬與拆帳,不要把本篇的切字診斷誤用成完整帳單。
把結果接回應用:未知、估算、對帳各有狀態
完成四步後,應用程式至少保留三種狀態:mapping_unresolved、experimental_estimate、api_reported。前者提醒你模型對照待解決,中間者可用於試算,最後者記錄指定請求的服務端數字。若操作會依計數裁掉文字或決定是否送出,先要求完整請求的計數證據,再執行那個決策。
工作表每列至少放測例名稱、原文指紋、套件版本、模型名稱、encoding、映射狀態、變體、count、usage 與錯誤類型。encoding 未確認就標未知;API 未執行就留待測。以後升級套件或換模型,先重跑同一批樣本,保留前後差異,再移除臨時估算。
這種責任通常放在模型之外的執行層。Agent Harness 原理幫你理解誰管理請求;最小 Harness 實作則能把驗收與停止條件接到流程。你今天要改善的是計數證據與行為的關係,而不是把每個錯誤都轉成一個成功數字。
FAQ:tiktoken 與 GPT-6 的八個直接答案
1. KeyError 代表 GPT-6 API 不能用嗎?
不能據此判斷。本例錯誤出現在本機模型名稱映射,API 是否接受請求需另驗帳號、模型與端點。
2. 更新 tiktoken 就一定能解決嗎?
不一定。先記版本,再查該版的完整名稱與前綴;升級後用同一組查找測例確認,不能只看安裝成功。
3. 可以直接用 o200k_base 嗎?
可以明確用它做本機文字估算;在 GPT-6 對應未確認時,紀錄必須保留「實驗估算」狀態。
4. 五個樣本都相同,就證明 tokenizer 相同嗎?
不成立。相同計數可能對應不同 IDs;有限樣本也沒有涵蓋所有字串與特殊輸入。
5. JSON 整包 encode,就是完整請求計數嗎?
不是服務端計數的證明。那是對你序列化出的 JSON 文字切字;請求處理還有角色與格式結構,應對照 input token count。
6. 定義工具但沒有呼叫,也需要核對嗎?
需要。先將工具 schema 留在請求計數裡,再另看生成或工具執行。不要從沒有工具結果推定整份定義沒有參與輸入。
7. usage 比可見回答多,一定是計費錯誤嗎?
不能如此推定。生成 token 包含可見文字之外的結構;先按端點與分項解讀,再拿相同請求核對。
8. 沒有 API key,今天可以做什麼?
先完成版本、名稱查找與五類本機樣本,將服務端欄位留待測。你已能分清套件問題與估算狀態,再接自己的帳號。
給新手的三個重點
- 錯誤先定位:安裝、名稱映射、文字編碼與 API 請求,是不同的檢查點。
- 每個數字帶範圍:encoding、模型、原文與請求形狀一起保存,避免比較兩把不同的尺。
- 證據決定狀態:本機成功是本機成功;完整請求計數與 usage 對帳完成後,再修改應用決策。
接著閱讀
左右滑動查看更多推薦
下一步:先交出一張能對得上的工作表
今天先選一段公開短文字,固定版本,完成名稱查找與兩把尺的本機計數。有 API 存取時,再把同一段文字放進 A、B、C 三種請求,保存 count 和 usage。記住:索引、尺、收據,各自回答一個問題。當三者都有身分,你就能修正應用,而不是猜出一個好看的 token 數。
想繼續把這套驗收方法用到 AI 開發,可從AI 文章主題中心選下一個概念,再到AlphaLab 課程安排自己的實作學習路線。先留好第一份收據,後面的升級才有可比較的基線。






