Metadata-Version: 2.4
Name: offline-token-counter
Version: 0.1.1
Summary: Offline token counter for DeepSeek V3/V4 — no network required
Author-email: rRR0VrFP <rrr0vrfp@qq.com>
Maintainer-email: rRR0VrFP <rrr0vrfp@qq.com>
License: MIT
Project-URL: Homepage, https://gitee.com/rRR0VrFP/offline-token-counter
Project-URL: Source, https://gitee.com/rRR0VrFP/offline-token-counter
Keywords: deepseek,tokenizer,token-count,offline,llm
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: tokenizers>=0.15
Requires-Dist: jinja2>=3.0
Provides-Extra: test
Requires-Dist: pytest>=8.0; extra == "test"
Dynamic: license-file

# offline-token-counter

offline-token-counter 是一个轻量级 Python 库，专为需要**完全离线**、精确计数大语言模型 Token 的场景而设计。将所有主流模型的 tokenizer 数据直接打包在安装包内，无需联网、无需额外下载，开箱即用。支持对字符串（str）、对话消息（messages）和工具调用（tools）三种输入格式进行精确 Token 计数，全面覆盖大模型应用开发中的各类使用场景。

## 特性

- **完全离线** — tokenizer 文件随包分发，无需联网下载
- **多模型支持** — 支持 429 个模型别名，覆盖 47 个独立 tokenizer（含 DeepSeek、Qwen、GLM、MiniMax、JiuTian、Janus 等系列）
- **多种输入格式** — 纯文本、对话消息、工具调用
- **智能解析** — 支持组织前缀剥离、大小写容错、模糊拼写纠错
- **无 `transformers` 依赖** — 仅依赖轻量 `tokenizers`、`jinja2`、`sentencepiece`

## 支持的模型

### DeepSeek 系列

| 模型名称 | 数据目录 | 词表大小 |
|---------|---------|---------|
| deepseek-llm-7b-base | deepseek-llm-7b-base | 100,015 |
| deepseek-coder-33b-base | deepseek-coder-33b-base | 32,022 |
| deepseek-coder-7b-base-v1.5 | deepseek-coder-7b-base-v1.5 | 100,016 |
| deepseek-v2 | deepseek-v2 | 100,002 |
| deepseek-v2.5 | deepseek-v2.5 | 100,018 |
| deepseek-v3 | deepseek-v3 | 128,818 |
| deepseek-v3.1 | deepseek-v3.1 | 128,818 |
| deepseek-v3.2 | deepseek-v3.2 | 128,818 |
| deepseek-v4 | deepseek-v4 | 129,283 |
| deepseek-r1 | deepseek-r1 | 128,818 |
| deepseek-r1-0528-qwen3-8b | deepseek-r1-0528-qwen3-8b | 151,671 |
| deepseek-r1-distill-llama-8b | deepseek-r1-distill-llama-8b | 128,256 |
| deepseek-r1-distill-qwen-7b | deepseek-r1-distill-qwen-7b | 151,665 |
| deepseek-vl-7b-base | deepseek-vl-7b-base | 100,016 |
| deepseek-vl2 | deepseek-vl2 | 128,826 |
| deepseek-vl2-small | deepseek-vl2-small | 100,011 |
| deepseek-ocr | deepseek-ocr | 128,830 |

### Qwen 系列

| 模型名称 | 数据目录 | 词表大小 |
|---------|---------|---------|
| qwen-2.5 | qwen-2.5 | 151,665 |
| qwen-3 | qwen-3 | 151,669 |
| qwen-3.5 | qwen-3.5 | 248,070 |
| qwen-3.6 | qwen-3.5 | 248,070 |
| qwen-vl | qwen-2.5 | 151,665 |
| qwen2-vl-2b | qwen2-vl-2b | 151,655 |
| qwen2-vl-2b-instruct-awq | qwen2-vl-2b-instruct-awq | 151,657 |
| qwen2.5-72b-instruct-awq | qwen2.5-72b-instruct-awq | 151,665 |
| qwen2.5-math-prm-7b | qwen2.5-math-prm-7b | 151,652 |
| qwen2.5-omni-3b | qwen2.5-omni-3b | 151,665 |
| qwen2.5-omni-7b-awq | qwen2.5-omni-7b-awq | 151,665 |
| qwen2.5-vl-3b-instruct-awq | qwen2.5-vl-3b-instruct-awq | 151,665 |
| qwen3-235b-a22b-mlx-4bit | qwen3-235b-a22b-mlx-4bit | 151,670 |
| qwq-32b | qwq-32b | 151,669 |

### GLM 系列

| 模型名称 | 数据目录 | 词表大小 |
|---------|---------|---------|
| glm-4 | glm-4 | 151,343 |
| glm-4v | glm-4 | 151,343 |
| glm-4-32b-0414 | glm-4-32b-0414 | 151,343 |
| glm-4-9b-0414 | glm-4-32b-0414 | 151,343 |
| glm-z1-9b-0414 | glm-4-32b-0414 | 151,343 |
| glm-z1-32b-0414 | glm-4-32b-0414 | 151,343 |
| glm-4.5v | glm-4.5v | 151,365 |
| glm-4.6v | glm-4.5v | 151,365 |
| glm-4.6v-flash | glm-4.5v | 151,365 |
| glm-4.7-flash | glm-4.7-flash | 154,856 |
| glm-5 | glm-4.7-flash | 154,856 |
| glm-5.1 | glm-4.7-flash | 154,856 |
| glm-5.2 | glm-4.7-flash | 154,856 |

### MiniMax 系列

| 模型名称 | 数据目录 | 词表大小 |
|---------|---------|---------|
| minimax | minimax | ~200,054 |
| minimax-m1-40k | minimax-m1-40k | ~200,036 |
| minimax-m3 | minimax-m3 | ~200,061 |
| minimax-text-01 | minimax-text-01 | ~200,026 |

### JiuTian 系列

| 模型名称 | 数据目录 | 词表大小 |
|---------|---------|---------|
| jt-coder-8b-base | jt-coder-8b-base | 151,667 |
| jt-math-8b-instruct | jt-math-8b-instruct | 151,665 |
| jt-da-8b | jt-da-8b | 151,665 |
| jiutian-139moe-chat | jiutian-139moe-chat | 69,120 |

### Janus 系列

| 模型名称 | 数据目录 | 词表大小 |
|---------|---------|---------|
| janus-1.3b | janus-1.3b | 100,594 |
| janus-pro-1b | janus-pro-1b | 100,590 |
| janus-pro-7b | janus-pro-7b | 100,603 |
| janusflow-1.3b | janusflow-1.3b | 100,008 |

### SynLogic 系列

| 模型名称 | 数据目录 | 词表大小 |
|---------|---------|---------|
| synlogic-32b | qwen-2.5 | 151,665 |
| synlogic-7b | qwen-2.5 | 151,665 |
| synlogic-mix-3-32b | qwen-2.5 | 151,665 |

## 快速别名参考

| 别名 | 解析为 |
|------|-------|
| `v3` | `deepseek-v3` |
| `v4` | `deepseek-v4-pro` |
| `r1` | `deepseek-r1` |
| `qwen2.5` | `qwen-2.5` |
| `qwen3` | `qwen-3` |
| `glm4` | `glm-4` |
| `glm4v` | `glm-4v` |

## 安装

```bash
pip install offline-token-counter
```

本地开发：

```bash
pip install -e ".[test]"
```

## 用法

```python
from offline_token_counter import count_tokens

# 纯文本计数
count_tokens("Hello, world!")
# => 4 (deepseek-v4)

# 指定模型
count_tokens("你好", model="qwen-2.5")

# 对话消息
messages = [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "What is the capital of France?"},
]
count_tokens(messages, model="glm-4")

# 带工具定义
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get current weather for a location",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {"type": "string"},
                },
                "required": ["location"],
            },
        },
    },
]
count_tokens(messages, tools=tools, model="deepseek-r1")
```

## API

### `count_tokens(messages, tools=None, model="deepseek-v4")`

| 参数 | 类型 | 说明 |
|------|------|------|
| messages | `str` 或 `list[dict]` | 文本字符串或消息字典列表 |
| tools | `list[dict] \| None` | 工具定义（OpenAI function-calling 格式） |
| model | `str` | 模型名称或别名（默认 `"v3"`） |

**返回：** `int` — Token 数量。

**异常：** 不抛异常。失败时自动降级为启发式估算。

## 版本记录

### v0.1.1

- 修复：30 个模型的 `chat_template.jinja` 文件内容为 `"Entry not found"`（下载工具错误），删除后改用 `tokenizer_config.json` 中的合法模板
- 修复：`qwen2.5-72b-instruct-awq` 的 `tokenizer.json` 与 `qwen-2.5` 完全一致，删除重复文件
- 修复：`jiutian-139moe-chat` SentencePiece 模型从未被用于 token 计数（回落 heuristic），现转换为独立 `tokenizer.json.zip`（Unigram, 69120 vocab）
- 修复：synlogic / qwen2.5-vl 系列模型映射错误（指向 `jt-math-8b-instruct` → 修正为 `qwen-2.5`）
- 修复：`count_tokens()` 中 `_get_tokenizer` 与 `_render_messages` 调用顺序错误，导致 `chat_template`/`bos_token` 加载时机不确定
- 新增：多模态图像 token 计数支持（Qwen2.5-VL、GLM-4V、DeepSeek-VL2 等视觉模型）
- 新增：全模型回归测试用例（strings / messages / tools / vision），锁定每个 canonical 模型的精确 token 计数
- 修复：`qwen2-vl-2b` 和 `qwen2-vl-2b-instruct-awq` 的 `tokenizer_config.json` 中 chat_template 损坏（仅处理视觉 content 数组，不支持标准消息格式），删除后回落 fallback 格式

### v0.1.0

- 首个正式发布版本
- 支持 DeepSeek、Qwen、GLM、MiniMax、JiuTian、Janus 六大系列共 47 个独立 tokenizer（429 个模型别名）
- 支持纯文本、对话消息、工具调用三种输入格式
- 支持组织前缀剥离、大小写容错、模糊拼写纠错
- 失败时自动降级为启发式估算

## 许可证

MIT
