Files
2026Technology-Competition/tests/test_inference_token.py
T
lhl 8febc50eb8 feat(inference): CJK 保守 token 估算(T9 架构审查整改)
- Issue11: approximate_token_count 重写
  - 新增 _is_cjk_char(CJK 统一表意/扩展A/假名/韩文/兼容/全角六大 Unicode 范围)
  - CJK 字符按 1.5 token/字符(旧 4 字符 1 token 严重低估,裁剪失效致 API 超限)
  - 其余字符仍 4 字符 1 token;最少 1 token
- 同步 config-design.md token_estimation 注释
- 新增 4 用例,全量 186 passed / 100.00%(995 stmts/252 br)
2026-08-12 09:54:20 +08:00

103 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import pytest
from genesis.inference.token import (
_tiktoken_estimator,
approximate_token_count,
make_estimator,
)
def test_approximate_count_minimum():
assert approximate_token_count("") >= 1
assert approximate_token_count("a") == 1
def test_approximate_count_linear():
# 每 4 字符约 1 token(向上取整)
assert approximate_token_count("abcd") == 1
assert approximate_token_count("abcdefgh") == 2
assert approximate_token_count("abcdefghi") == 3
# ---------- T9: CJK 保守估算 ----------
def test_approximate_cjk_conservative():
"""CJK(中文/日文/韩文)字符按保守 ×1.5/字符 估算,远高于 4 字符 1 token。"""
cjk = "日本語の設計書" # 7 个 CJK 字符
assert approximate_token_count(cjk) >= len(cjk) # 至少 1 token/字符
# 旧逻辑 len//4=1(严重低估);新逻辑 ≥7
assert approximate_token_count(cjk) > len(cjk) // 4
def test_approximate_ascii_unchanged():
"""纯 ASCII 仍按 4 字符 1 token(不回归)。"""
assert approximate_token_count("abcdefgh") == 2
def test_approximate_mixed_cjk_and_ascii():
"""混合文本:CJK 部分保守估算 + ASCII 部分按 4 字符 1 token。"""
# "abc" 3 ASCII → 1 token"日本語" 3 CJK → ceil(3*1.5)=5;合计 ≥ 6
total = approximate_token_count("abc日本語")
assert total >= 1 + 3
def test_approximate_cjk_fullwidth_forms():
"""全角符号(CJK 兼容/全角区块)同样保守估算(日文文档常见)。"""
assert approximate_token_count("(設計)") >= 4 # 4 个全角字符,至少 4 token
def test_tiktoken_estimator_missing_falls_back():
# 未安装 tiktoken 或不可用时返回 None(由 make_estimator 回落 approximate
r = _tiktoken_estimator("hello")
assert r is None or isinstance(r, int)
def test_make_estimator_approximate_backend():
est = make_estimator("approximate")
assert est("abcd") == 1
def test_make_estimator_default_without_tiktoken(monkeypatch):
# 强制模拟 tiktoken 缺失:make_estimator 必须回落 approximate
import builtins
real_import = builtins.__import__
def fake_import(name, *args, **kwargs):
if name == "tiktoken":
raise ImportError("no tiktoken")
return real_import(name, *args, **kwargs)
monkeypatch.setattr(builtins, "__import__", fake_import)
est = make_estimator("tiktoken")
assert est("abcd") == 1
def test_tiktoken_estimator_success_path(monkeypatch):
# 注入假 tiktoken 模块:验证编码成功路径(len(encode(text))
import sys
class _FakeEncoding:
def encode(self, s: str):
return ["t"] * len(s)
class _FakeTiktoken:
@staticmethod
def get_encoding(name):
assert name == "cl100k_base"
return _FakeEncoding()
monkeypatch.setitem(sys.modules, "tiktoken", _FakeTiktoken)
assert _tiktoken_estimator("hello") == 5
def test_tiktoken_estimator_encoding_error(monkeypatch):
# tiktoken 可用但编码抛异常:返回 None(由 make_estimator 回落 approximate
import sys
class _FakeTiktoken:
@staticmethod
def get_encoding(name):
raise RuntimeError("boom")
monkeypatch.setitem(sys.modules, "tiktoken", _FakeTiktoken)
assert _tiktoken_estimator("hello") is None