Coverage for src\genesis\inference\token.py: 100%
26 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-26 14:20 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-26 14:20 +0800
1from __future__ import annotations
3import unicodedata
4from typing import Callable
6# CJK 每字符保守 token 数(T9 整改:cl100k/4字符1token 对中文/日文严重低估)
7# 日文/中文实际每字符 1~3 token,取保守 1.5 防止上下文裁剪失效导致 API 超限
8_CJK_TOKENS_PER_CHAR = 1.5
11def _is_cjk_char(ch: str) -> bool:
12 """判断字符是否属于 CJK 密集区(中文/日文假名/韩文/全角符号)。"""
13 cp = ord(ch)
14 return (
15 0x4E00 <= cp <= 0x9FFF # CJK 统一表意文字
16 or 0x3040 <= cp <= 0x30FF # 平假名/片假名
17 or 0xAC00 <= cp <= 0xD7AF # 韩文音节
18 or 0xF900 <= cp <= 0xFAFF # CJK 兼容表意文字
19 or 0xFF00 <= cp <= 0xFFEF # 全角形式(全角标点/字母)
20 or 0x3400 <= cp <= 0x4DBF # CJK 扩展 A
21 )
24def approximate_token_count(text: str) -> int:
25 """内置近似估算(T9 CJK 保守):CJK 字符按 1.5 token/字符,
26 其余字符按 4 字符 ≈ 1 token;最少 1 token(无外部依赖,可离线)。"""
27 cjk_chars = sum(1 for ch in text if _is_cjk_char(ch))
28 other_chars = len(text) - cjk_chars
29 tokens = cjk_chars * _CJK_TOKENS_PER_CHAR + (other_chars + 3) // 4
30 return max(1, int(tokens))
33def _tiktoken_estimator(text: str) -> int | None:
34 """tiktoken 编码估算;tiktoken 未安装时返回 None。"""
35 try:
36 import tiktoken
37 except ImportError:
38 return None
39 try:
40 enc = tiktoken.get_encoding("cl100k_base")
41 return len(enc.encode(text))
42 except Exception:
43 return None
46def make_estimator(backend: str = "tiktoken") -> Callable[[str], int]:
47 """按配置选择估算器:backend="tiktoken"(默认)优先 tiktoken,
48 缺失或异常回落内置 approximate;backend="approximate" 直接用近似。"""
49 if backend == "approximate":
50 return approximate_token_count
51 return lambda text: _tiktoken_estimator(text) or approximate_token_count(text)