-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Implement OpenAI token counting using tiktoken
#3447
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 15 commits
d7f0b87
80a61f1
cc8cbf0
c1be8c1
1332cd8
cb5da87
6396f5d
46cd331
86a0b89
bacf788
acf86b0
6d2d4dd
9943173
75f29fa
6deaea2
88a132d
7db7fb5
275f16a
6a58dfb
c16ab50
e94755a
6b07449
4aa4c99
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,7 +18,11 @@ | |
| from .._output import DEFAULT_OUTPUT_TOOL_NAME, OutputObjectDefinition | ||
| from .._run_context import RunContext | ||
| from .._thinking_part import split_content_into_text_and_thinking | ||
| from .._utils import guard_tool_call_id as _guard_tool_call_id, now_utc as _now_utc, number_to_datetime | ||
| from .._utils import ( | ||
| guard_tool_call_id as _guard_tool_call_id, | ||
| now_utc as _now_utc, | ||
| number_to_datetime, | ||
| ) | ||
| from ..builtin_tools import CodeExecutionTool, ImageGenerationTool, MCPServerTool, WebSearchTool | ||
| from ..exceptions import UserError | ||
| from ..messages import ( | ||
|
|
@@ -55,6 +59,7 @@ | |
| from . import Model, ModelRequestParameters, StreamedResponse, check_allow_model_requests, download_item, get_user_agent | ||
|
|
||
| try: | ||
| import tiktoken | ||
| from openai import NOT_GIVEN, APIConnectionError, APIStatusError, AsyncOpenAI, AsyncStream | ||
| from openai.types import AllModels, chat, responses | ||
| from openai.types.chat import ( | ||
|
|
@@ -1008,6 +1013,24 @@ def _inline_text_file_part(text: str, *, media_type: str, identifier: str) -> Ch | |
| ) | ||
| return ChatCompletionContentPartTextParam(text=text, type='text') | ||
|
|
||
| async def count_tokens( | ||
| self, | ||
| messages: list[ModelMessage], | ||
| model_settings: ModelSettings | None, | ||
| model_request_parameters: ModelRequestParameters, | ||
| ) -> usage.RequestUsage: | ||
| """Count the number of tokens in the given messages.""" | ||
| if self.system != 'openai': | ||
| raise NotImplementedError('Token counting is only supported for OpenAI system.') | ||
|
|
||
| model_settings, model_request_parameters = self.prepare_request(model_settings, model_request_parameters) | ||
| openai_messages = await self._map_messages(messages, model_request_parameters) | ||
| token_count = _num_tokens_from_messages(openai_messages, self.model_name) | ||
|
|
||
| return usage.RequestUsage( | ||
| input_tokens=token_count, | ||
| ) | ||
|
|
||
|
|
||
| @deprecated( | ||
| '`OpenAIModel` was renamed to `OpenAIChatModel` to clearly distinguish it from `OpenAIResponsesModel` which ' | ||
|
|
@@ -1804,6 +1827,26 @@ async def _map_user_prompt(part: UserPromptPart) -> responses.EasyInputMessagePa | |
| assert_never(item) | ||
| return responses.EasyInputMessageParam(role='user', content=content) | ||
|
|
||
| async def count_tokens( | ||
DouweM marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| self, | ||
| messages: list[ModelMessage], | ||
| model_settings: ModelSettings | None, | ||
| model_request_parameters: ModelRequestParameters, | ||
| ) -> usage.RequestUsage: | ||
| """Count the number of tokens in the given messages.""" | ||
| if self.system != 'openai': | ||
| raise NotImplementedError('Token counting is only supported for OpenAI system.') | ||
|
|
||
| model_settings, model_request_parameters = self.prepare_request(model_settings, model_request_parameters) | ||
| _, openai_messages = await self._map_messages( | ||
| messages, cast(OpenAIResponsesModelSettings, model_settings or {}), model_request_parameters | ||
| ) | ||
| token_count = _num_tokens_from_messages(openai_messages, self.model_name) | ||
|
|
||
| return usage.RequestUsage( | ||
| input_tokens=token_count, | ||
| ) | ||
|
|
||
|
|
||
| @dataclass | ||
| class OpenAIStreamedResponse(StreamedResponse): | ||
|
|
@@ -2519,3 +2562,31 @@ def _map_mcp_call( | |
| provider_name=provider_name, | ||
| ), | ||
| ) | ||
|
|
||
|
|
||
| def _num_tokens_from_messages( | ||
| messages: list[chat.ChatCompletionMessageParam] | list[responses.ResponseInputItemParam], | ||
| model: OpenAIModelName, | ||
| ) -> int: | ||
| """Return the number of tokens used by a list of messages.""" | ||
| try: | ||
| encoding = tiktoken.encoding_for_model(model) | ||
| except KeyError: | ||
| encoding = tiktoken.get_encoding('o200k_base') | ||
|
|
||
| if 'gpt-5' in model: | ||
| tokens_per_message = 3 | ||
| final_primer = 2 # "reverse engineered" based on test cases | ||
| else: | ||
| # Adapted from https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken#6-counting-tokens-for-chat-completions-api-calls | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at the cookbook again, I think we should also try to implement support for counting the tokens of tool definitions: |
||
| tokens_per_message = 3 | ||
| final_primer = 3 # every reply is primed with <|start|>assistant<|message|> | ||
|
|
||
| num_tokens = 0 | ||
| for message in messages: | ||
| num_tokens += tokens_per_message | ||
| for value in message.values(): | ||
|
||
| if isinstance(value, str): | ||
|
||
| num_tokens += len(encoding.encode(value)) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this (or the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The methods which download the encoding file are
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| num_tokens += final_primer | ||
| return num_tokens | ||
Uh oh!
There was an error while loading. Please reload this page.