Skip to content

Tokenizers

Token counting is provider-specific. convopack accepts any Tokenizer implementation and ships adapters for the common ones.

Spec strings

The fastest way to choose a tokenizer is by passing a string to Packer:

Packer(tokenizer="approx", ...)
Packer(tokenizer="tiktoken:gpt-4o", ...)
Packer(tokenizer="anthropic:claude-sonnet-4-6", ...)
Packer(tokenizer="huggingface:meta-llama/Llama-3.1-8B", ...)
Spec Backend Deps
approx char_count / 4 estimate none (zero-dep default)
tiktoken:<model-or-encoding> OpenAI BPE pip install "convopack[tiktoken]"
anthropic:<model> Approx + optional online count pip install "convopack[anthropic]"
huggingface:<model-id> HuggingFace AutoTokenizer pip install "convopack[huggingface]"

ApproxTokenizer

Char count divided by four, with a small per-message overhead. Off by 10–30 % depending on language; fine for a soft budget but never use it for billing.

from convopack import ApproxTokenizer

tok = ApproxTokenizer()
tok.count("hello world")          # 3
tok.count_message(message)        # includes per-message overhead
tok.count_messages([m1, m2, m3])  # sums + per-reply overhead

TiktokenAdapter

Wraps the official tiktoken encoder. Pass either a model name or an encoding name:

from convopack.tokenizers.tiktoken_adapter import TiktokenAdapter

TiktokenAdapter("gpt-4o")
TiktokenAdapter("o200k_base")

Unknown model names fall back to tiktoken.get_encoding.

AnthropicAdapter

Anthropic doesn't ship a local BPE; the canonical method is client.messages.count_tokens, which is a network call. Two modes:

from convopack.tokenizers.anthropic_adapter import AnthropicAdapter

# default: offline approximation, fast
AnthropicAdapter("claude-sonnet-4-6")

# online: per-call network round-trip, exact
AnthropicAdapter("claude-sonnet-4-6", offline=False)

Online mode requires the anthropic SDK and a configured API key. Use it for billing-grade counts; otherwise the offline mode is fine.

HFTokenizerAdapter

Wraps transformers.AutoTokenizer so any open-weights model can be tokenised correctly:

from convopack.tokenizers.huggingface_adapter import HFTokenizerAdapter

HFTokenizerAdapter("meta-llama/Llama-3.1-8B")
HFTokenizerAdapter("/local/path/to/tokenizer")
HFTokenizerAdapter("meta-llama/Llama-3.1-8B", local_files_only=True)

Writing your own

Implement the protocol:

from typing import Iterable
from convopack import Message

class MyTokenizer:
    name = "my:counter"

    def count(self, text: str) -> int: ...
    def count_message(self, message: Message) -> int: ...
    def count_messages(self, messages: Iterable[Message]) -> int: ...

Pass an instance to Packer(tokenizer=...) and it works everywhere a spec string would.

A note on accuracy

count_message adds a small per-message overhead to cover role tags and separators. The exact number varies by provider and is best-effort — use online counting modes when overshoot matters more than throughput.