Haystack Token Counters 完全指南:在调用模型前精确估算对话与工具 Schema 的 Token 占用
2026/9/12 1:41:35 网站建设 项目流程

Haystack Token Counters 完全指南:在调用模型前精确估算对话与工具 Schema 的 Token 占用

【免费下载链接】haystackOpen-source AI orchestration framework for building context-engineered, production-ready LLM applications. Design modular pipelines and agent workflows with explicit control over retrieval, routing, memory, and generation. Built for scalable agents, RAG, multimodal applications, semantic search, and conversational systems.项目地址: https://gitcode.com/GitHub_Trending/ha/haystack

本文是一份围绕 Haystacktoken_counters模块的深度技术指南。该模块提供了TokenCounter协议与三种内置实现(ApproximateTokenCounterTiktokenCounterOpenAITokenCounter),用于在把消息发送给大模型之前估算一段对话和工具 Schema 会占用多少 token,从而支撑上下文窗口预算校验、Agent 上下文压缩、token 计费等场景。读完本文,你将掌握每个计数器的定位、全部构造参数与调用方法、内部渲染与计数的实现原理,以及如何自定义属于自己的计数器。

为什么需要 Token 计数器

在生产级 LLM 应用中,很多功能需要在发送请求前知道对话的"体积":

  • 判断当前对话是否超出模型的上下文窗口,超限时触发上下文压缩(Haystack 的 agent 上下文压缩机制即可借助计数器决定要丢弃多少历史消息);
  • Agent 多轮工具调用场景中,估算不断累积的系统提示、工具结果和工具 Schema 对上下文的消耗;
  • 为计费或配额管理预留估算。

ChatMessage列表和可选工具 Schema 正是模型输入的主体,token_counters 模块 围绕list[ChatMessage]tools两个输入统一暴露计数能力。所有计数器都会把消息中的角色、文本、工具调用、工具结果以及可选的工具 Schema 纳入统计,并覆盖消息中的图片、文件等非文本内容。

TokenCounter 协议:所有计数器的统一接口

所有计数器都实现TokenCounter协议(定义见 haystack/token_counters/types/protocol.py),它要求实现三个成员:

  • count(messages, tools=None) -> int:返回给定消息(及可选工具 Schema)估算占用的 token 数。传入tools会将其 Schema 一并计入,置为None则只测消息本身;
  • to_dict() -> dict[str, Any]:把计数器序列化为字典,保证其设置能够在序列化后保留下来;
  • from_dict(cls, data) -> TokenCounter(类方法):反序列化。默认实现直接调用default_from_dict,把参数字典原样传回构造函数;只有当to_dict()输出需要先重建再传入构造函数的值(例如Secret或嵌套组件)时,才需要重写它。

协议默认的from_dict对普通标量参数(chars_per_tokenencodingtokens_per_image等)已经足够,三个内置计数器因此都没有重写from_dict

三种内置计数器对比

计数器计数方式额外依赖适合场景
ApproximateTokenCounter将渲染后的文本长度除以可配置的"每 token 字符数"快速、零依赖的估算
TiktokenCounter使用 OpenAI 的tiktoken字节对编码器在本地计数tiktoken针对 OpenAI 模型更准确的估算
OpenAITokenCounter调用 OpenAI 输入 token 计数 API(POST /v1/responses/input_tokensOpenAI API Key精确的、模型特定的计数,涵盖图片、文件与工具

此外,在anthropic-haystackgoogle-genai-haystack等官方集成包中还提供了AnthropicTokenCounterGoogleGenAITokenCounter等 provider 专属实现(见 token counters 指南),本仓库内置的三种实现则无需安装任何集成包即可使用。

ApproximateTokenCounter:零依赖的字符比估算

ApproximateTokenCounter(源码见 haystack/token_counters/approximate_counter.py)是所有计数器中唯一无需安装任何额外依赖、也无需 warm_up 加载模型的实现,适合在初始化阶段或资源受限环境中做快速预算。

构造参数

__init__( chars_per_token: float = 4.0, tokens_per_image: int = 85, tokens_per_file: int = 1000, ) -> None
  • chars_per_tokenfloat,默认4.0):多少个字符按一个 token 计。这个值决定了估算的松紧度——对于英文内容 4 字符/token 是常用经验值,中文等字符密度较高的内容可适当调低;
  • tokens_per_imageint,默认85):每张图片收取的 token 数。默认值对应 OpenAI 对小尺寸图片的计费,若发送大图应调高;
  • tokens_per_fileint,默认1000):每个文件收取的 token 数,是对短文档的粗略替代值,实际成本取决于页数,发送长文档时应调高;
  • 异常:当chars_per_token <= 0时抛出ValueError(源码第 44-45 行)。

count:一次简单的除法

count的实现非常直观(源码第 50-64 行):

text = _rendered_conversation(messages) + _rendered_tools(tools) text_tokens = int(len(text) / self.chars_per_token) return text_tokens + _non_text_tokens(...)

messagestools均为空时直接返回0。文本 token 数 = 渲染后文本总长度(字符数)除以chars_per_token后取整,最后再加上图片与文件的固定估算值。

使用示例

from haystack.dataclasses import ChatMessage from haystack.token_counters import ApproximateTokenCounter counter = ApproximateTokenCounter(chars_per_token=4.0) messages = [ ChatMessage.from_user("Hello, how are you?"), ChatMessage.from_assistant("I'm good, thank you! How can I assist you today?") ] token_count = counter.count(messages) print(f"Estimated token count: {token_count}")

TiktokenCounter:本地字节对编码估算

TiktokenCounter(源码见 haystack/token_counters/tiktoken_counter.py)使用 OpenAI 开源的tiktoken字节对编码器在本地完成计数,比字符比估算更贴近 OpenAI 模型的实际分词结果。

构造参数

__init__( encoding: str = "o200k_base", tokens_per_image: int = 85, tokens_per_file: int = 1000, ) -> None
  • encodingstr,默认"o200k_base"):使用的tiktoken编码。o200k_base是当前 OpenAI 模型使用的编码,旧模型可改用cl100k_base等;
  • tokens_per_image/tokens_per_file:与ApproximateTokenCounter含义一致,用于给分词器无法度量的图片、文件记固定开销;
  • 异常:未安装tiktoken时在构造阶段抛出ImportError(提示运行pip install tiktoken)。测试 test_tiktoken_counter.py 明确验证了"依赖缺失在构造时报告,而非在首次 count 时才失败",这避免了错误在运行中途才暴露。

warm_up:按需加载编码器

def warm_up(self) -> None: if self._encoder is not None: return self._encoder = tiktoken.get_encoding(self.encoding)

warm_up会加载编码器,首次使用时若本地无缓存会自动下载词表;重复调用是幂等的(编码器已加载则直接返回)。count()内部会自动调用warm_up(),所以日常使用无需手动预热。

两个必须知道的限制

  • 仅文本:图片和文件只能按tokens_per_image/tokens_per_file的固定值估算,无法真实分词;
  • 它是 OpenAI 的编码器:其他提供商的模型分词方式不同,计数会有偏差,跨 provider 使用时只能作为参考。

使用示例

from haystack.dataclasses import ChatMessage from haystack.token_counters import TiktokenCounter counter = TiktokenCounter(encoding="o200k_base") messages = [ ChatMessage.from_user("Hello, how are you?"), ChatMessage.from_assistant("I'm good, thank you! How can I assist you today?") ] token_count = counter.count(messages) print(f"Token count: {token_count}")

OpenAITokenCounter:调用官方 API 获取精确计数

OpenAITokenCounter(源码见 haystack/token_counters/openai_counter.py)与前两者完全不同——它把输入发送到 OpenAI 的POST /v1/responses/input_tokens计数端点,返回的结果包含模型特定的消息与工具 Schema 格式化开销,以及图片、文件等受支持的非文本内容,是三种实现中唯一能给出"精确"计数的方案。

构造参数

__init__( model: str, *, api_key: Secret = Secret.from_env_var("OPENAI_API_KEY"), api_base_url: str | None = None, organization: str | None = None, timeout: float | None = None, max_retries: int | None = None, http_client_kwargs: dict[str, Any] | None = None, ) -> None
  • modelstr,必填):按哪个模型的分词规则计数,例如"gpt-5-mini"
  • api_keySecret,默认读取OPENAI_API_KEY环境变量):也可显式传入Secret.from_token("...")
  • api_base_urlstr | None):OpenAI API 的可选基础 URL,用于代理或兼容端点;
  • organizationstr | None):OpenAI 组织 ID;
  • timeoutfloat | None):客户端调用超时。未设置时使用OPENAI_TIMEOUT环境变量,默认 30 秒(源码第 77 行);
  • max_retriesint | None):最大重试次数。未设置时使用OPENAI_MAX_RETRIES环境变量,默认 5(源码第 78-80 行);
  • http_client_kwargsdict[str, Any] | None):用于配置底层 HTTPX 客户端的关键字参数,经由 init_http_client 构造。

生命周期:warm_up / count / close

  • warm_up():初始化 OpenAI 客户端(重复调用幂等)。客户端通过init_http_client构建并传入OpenAI(...)
  • count(messages, tools=None):把每条ChatMessage通过_convert_chat_message_to_responses_api_format(来自 haystack/components/generators/chat/openai_responses.py)转换为 Responses API 输入格式;若传入了tools,则通过flatten_tools_or_toolsets展平后包装为{"type": "function", ...}并入请求,最后调用client.responses.input_tokens.count(**request)并返回response.input_tokens。空输入同样返回0
  • close():关闭 OpenAI 客户端及其底层 HTTP 资源,并把self.client置空。这在长生命周期服务中释放连接时很有用。

使用示例

from haystack.dataclasses import ChatMessage from haystack.token_counters import OpenAITokenCounter counter = OpenAITokenCounter("gpt-5-mini") messages = [ChatMessage.from_user("Hello, how are you?")] token_count = counter.count(messages) print(f"Token count: {token_count}")

注意:该计数器需要网络请求与 API Key,计数会消耗配额与时间,适合对精确度要求高(例如超限判定、计费)的场景。

统一渲染机制:计数器内部是如何"看见"对话的

三种计数器对文本的测量都建立在同一个渲染层上(见 haystack/token_counters/utils.py),理解它就能理解"计数结果到底是什么":

  • _render_message:把单条ChatMessage渲染为若干行纯文本,规则包括:
    • 普通消息带角色前缀,如[user] Hello[system] rules
    • 工具调用渲染为[assistant -> tool_call] search({"q": "x"})(参数 JSON 按键排序,保证渲染结果稳定);
    • 工具结果渲染为[tool:search] found it,出错时带(error)标记;
    • 图片与文件用占位符代替<image><file: report.pdf>),因为它们没有文本形态但同样消耗 token;
    • 推理内容(ReasoningContent)被刻意排除——provider 在轮次间会丢弃推理内容,它不属于被测上下文(源码第 38-39 行注释)。
  • _rendered_conversation:把整段对话拼接为一个纯文本块,这正是计数器测量的对象;
  • _rendered_tools:把工具 Schema 序列化为一个 JSON 块,模拟 provider 随消息一起发送的格式;
  • _non_text_tokens:统计图片与文件数量乘以固定费率。注意它会遍历工具结果——嵌套在工具结果中的ImageContent/FileContent(例如工具返回的截图)同样会被计入,因为工具结果本身就在上下文里。

test/token_counters/test_utils.py 中完整覆盖了各类消息(系统、用户、带工具调用的助手消息、工具结果、错误工具结果、带图片和文件的用户消息、空消息)的渲染输出,可作为理解该机制的精确参考。

工具 Schema 的计数

工具 Schema 会与消息一起发送给模型并消耗上下文 token,因此所有计数器都支持传入tools

from typing import Annotated from haystack.dataclasses import ChatMessage from haystack.token_counters import ApproximateTokenCounter from haystack.tools import tool @tool def search(query: Annotated[str, "The search query"]) -> str: """Search for documents that match the query.""" return "Search results" messages = [ChatMessage.from_user("Find information about Haystack.")] counter = ApproximateTokenCounter() token_count = counter.count(messages, tools=[search])

三种计数器的行为一致:

  • 传入tools后其 Schema 计入估算(测试test_tool_schemas_add_to_the_count验证了counter.count(messages, tools=[search]) > counter.count(messages));
  • 可以只数工具不数消息counter.count([], tools=[search])同样返回正数;
  • messagestools都为空时才返回0

序列化与反序列化:让计数器配置可持久化

三个内置计数器都通过default_to_dict实现to_dict,序列化结果遵循 Haystack 统一格式——包含type(类的完整限定名)与init_parameters。例如 test_approximate_counter.py 验证的 round-trip:

data = ApproximateTokenCounter(chars_per_token=3.5, tokens_per_image=200, tokens_per_file=3000).to_dict() # data == { # "type": "haystack.token_counters.approximate_counter.ApproximateTokenCounter", # "init_parameters": {"chars_per_token": 3.5, "tokens_per_image": 200, "tokens_per_file": 3000}, # } restored = ApproximateTokenCounter.from_dict(data)

TiktokenCounterto_dict序列化encodingtokens_per_imagetokens_per_fileOpenAITokenCounter额外序列化api_keySecret对象)、modelapi_base_urlorganizationtimeoutmax_retrieshttp_client_kwargs。由于OpenAITokenCounterto_dict输出了Secret,理论上需要重写from_dict来重建Secret——不过其默认from_dict路径依赖default_from_dictSecret的兼容处理,实际使用时以测试与序列化工具的实际行为为准。

自定义 TokenCounter:接入自己的计数逻辑

当内置实现不满足需求时(例如对接某个提供商的专有计数端点),实现TokenCounter协议即可(参考 token counters 指南):

from typing import Any from haystack.core.serialization import default_to_dict from haystack.dataclasses import ChatMessage from haystack.token_counters import TokenCounter from haystack.tools import ToolsType class ProviderTokenCounter(TokenCounter): def count( self, messages: list[ChatMessage], tools: ToolsType | None = None, ) -> int: # Call the provider's token-counting endpoint here. ... def to_dict(self) -> dict[str, Any]: return default_to_dict(self)
  • 必须实现count()to_dict()两个方法;
  • 默认from_dict()会直接还原普通构造参数;当to_dict()序列化了需要先重建的值(如Secret或嵌套组件)时,再重写from_dict()

选型建议与适用前提

  • 零依赖快速估算(CI 检查、粗略预算):选ApproximateTokenCounter,无需安装任何包、无需网络;
  • OpenAI 模型的本地高精度估算:选TiktokenCounter,需pip install tiktoken,首次warm_up会下载词表,且只对 OpenAI 编码准确;
  • 精确计数(含图片、文件与工具格式开销):选OpenAITokenCounter,需要OPENAI_API_KEY与网络请求,注意其存在超时与重试配置(默认 30 秒 / 5 次重试);
  • 若使用 Claude、Gemini 等模型,可查阅官方集成包中的AnthropicTokenCounterGoogleGenAITokenCounter实现。

延伸阅读

  • Token Counters 指南文档:各计数器细分指南(docs-website/docs/token-counters/目录下的approximatetokencounter.mdxtiktokencounter.mdxopenaitokencounter.mdx等);
  • Token Counters API 参考:本 API 参考原始页面;
  • 源码:haystack/token_counters/(计数器实现与渲染工具)、haystack/token_counters/types/protocol.py(协议定义);
  • 测试:test/token_counters/(test_approximate_counter.pytest_tiktoken_counter.pytest_openai_counter.pytest_utils.py,覆盖计数、异常、序列化 round-trip 与工具计数行为)。

【免费下载链接】haystackOpen-source AI orchestration framework for building context-engineered, production-ready LLM applications. Design modular pipelines and agent workflows with explicit control over retrieval, routing, memory, and generation. Built for scalable agents, RAG, multimodal applications, semantic search, and conversational systems.项目地址: https://gitcode.com/GitHub_Trending/ha/haystack

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询