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