Skip to content

Commit 25738b1

Browse files
jalaterasclaude
andcommitted
fix: consolidate num_tokens_from_messages implementations #2134
Resolves inconsistency between two different implementations of num_tokens_from_messages in the cookbook notebooks by creating a unified utility function that supports all current OpenAI models. - Created shared token_counting_utils.py module in examples/utils/ - Consolidated logic from both notebook versions into single function - Added support for all current models including gpt-4o variants - Updated both notebooks to import from shared utility module - Maintains backward compatibility with existing code This ensures consistent token counting across all cookbook examples and makes it easier to maintain model support in one location. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 7433ba1 commit 25738b1

File tree

3 files changed

+133
-101
lines changed

3 files changed

+133
-101
lines changed

examples/How_to_count_tokens_with_tiktoken.ipynb

Lines changed: 5 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -197,16 +197,10 @@
197197
},
198198
{
199199
"cell_type": "code",
200-
"execution_count": 6,
200+
"execution_count": null,
201201
"metadata": {},
202202
"outputs": [],
203-
"source": [
204-
"def num_tokens_from_string(string: str, encoding_name: str) -> int:\n",
205-
" \"\"\"Returns the number of tokens in a text string.\"\"\"\n",
206-
" encoding = tiktoken.get_encoding(encoding_name)\n",
207-
" num_tokens = len(encoding.encode(string))\n",
208-
" return num_tokens"
209-
]
203+
"source": "# Import num_tokens_from_string function from our utility module\nfrom utils.token_counting_utils import num_tokens_from_string"
210204
},
211205
{
212206
"cell_type": "code",
@@ -460,54 +454,10 @@
460454
},
461455
{
462456
"cell_type": "code",
463-
"execution_count": 14,
457+
"execution_count": null,
464458
"metadata": {},
465459
"outputs": [],
466-
"source": [
467-
"def num_tokens_from_messages(messages, model=\"gpt-4o-mini-2024-07-18\"):\n",
468-
" \"\"\"Return the number of tokens used by a list of messages.\"\"\"\n",
469-
" try:\n",
470-
" encoding = tiktoken.encoding_for_model(model)\n",
471-
" except KeyError:\n",
472-
" print(\"Warning: model not found. Using o200k_base encoding.\")\n",
473-
" encoding = tiktoken.get_encoding(\"o200k_base\")\n",
474-
" if model in {\n",
475-
" \"gpt-3.5-turbo-0125\",\n",
476-
" \"gpt-4-0314\",\n",
477-
" \"gpt-4-32k-0314\",\n",
478-
" \"gpt-4-0613\",\n",
479-
" \"gpt-4-32k-0613\",\n",
480-
" \"gpt-4o-mini-2024-07-18\",\n",
481-
" \"gpt-4o-2024-08-06\"\n",
482-
" }:\n",
483-
" tokens_per_message = 3\n",
484-
" tokens_per_name = 1\n",
485-
" elif \"gpt-3.5-turbo\" in model:\n",
486-
" print(\"Warning: gpt-3.5-turbo may update over time. Returning num tokens assuming gpt-3.5-turbo-0125.\")\n",
487-
" return num_tokens_from_messages(messages, model=\"gpt-3.5-turbo-0125\")\n",
488-
" elif \"gpt-4o-mini\" in model:\n",
489-
" print(\"Warning: gpt-4o-mini may update over time. Returning num tokens assuming gpt-4o-mini-2024-07-18.\")\n",
490-
" return num_tokens_from_messages(messages, model=\"gpt-4o-mini-2024-07-18\")\n",
491-
" elif \"gpt-4o\" in model:\n",
492-
" print(\"Warning: gpt-4o and gpt-4o-mini may update over time. Returning num tokens assuming gpt-4o-2024-08-06.\")\n",
493-
" return num_tokens_from_messages(messages, model=\"gpt-4o-2024-08-06\")\n",
494-
" elif \"gpt-4\" in model:\n",
495-
" print(\"Warning: gpt-4 may update over time. Returning num tokens assuming gpt-4-0613.\")\n",
496-
" return num_tokens_from_messages(messages, model=\"gpt-4-0613\")\n",
497-
" else:\n",
498-
" raise NotImplementedError(\n",
499-
" f\"\"\"num_tokens_from_messages() is not implemented for model {model}.\"\"\"\n",
500-
" )\n",
501-
" num_tokens = 0\n",
502-
" for message in messages:\n",
503-
" num_tokens += tokens_per_message\n",
504-
" for key, value in message.items():\n",
505-
" num_tokens += len(encoding.encode(value))\n",
506-
" if key == \"name\":\n",
507-
" num_tokens += tokens_per_name\n",
508-
" num_tokens += 3 # every reply is primed with <|start|>assistant<|message|>\n",
509-
" return num_tokens\n"
510-
]
460+
"source": "# Import the unified token counting function\nimport sys\nimport os\n# Add the utils directory to the path so we can import our utility\nsys.path.append(os.path.join(os.path.dirname(os.path.abspath('.')), 'utils'))\n\nfrom utils.token_counting_utils import num_tokens_from_messages\n\n# The num_tokens_from_messages function is now imported from the shared utility module\n# It supports all current OpenAI models including:\n# - gpt-3.5-turbo variants\n# - gpt-4 variants \n# - gpt-4o and gpt-4o-mini variants"
511461
},
512462
{
513463
"cell_type": "code",
@@ -811,4 +761,4 @@
811761
},
812762
"nbformat": 4,
813763
"nbformat_minor": 2
814-
}
764+
}

examples/How_to_format_inputs_to_ChatGPT_models.ipynb

Lines changed: 3 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -508,53 +508,10 @@
508508
},
509509
{
510510
"cell_type": "code",
511-
"execution_count": 16,
511+
"execution_count": null,
512512
"metadata": {},
513513
"outputs": [],
514-
"source": [
515-
"import tiktoken\n",
516-
"\n",
517-
"\n",
518-
"def num_tokens_from_messages(messages, model=\"gpt-3.5-turbo-0613\"):\n",
519-
" \"\"\"Return the number of tokens used by a list of messages.\"\"\"\n",
520-
" try:\n",
521-
" encoding = tiktoken.encoding_for_model(model)\n",
522-
" except KeyError:\n",
523-
" print(\"Warning: model not found. Using cl100k_base encoding.\")\n",
524-
" encoding = tiktoken.get_encoding(\"cl100k_base\")\n",
525-
" if model in {\n",
526-
" \"gpt-3.5-turbo-0613\",\n",
527-
" \"gpt-3.5-turbo-16k-0613\",\n",
528-
" \"gpt-4-0314\",\n",
529-
" \"gpt-4-32k-0314\",\n",
530-
" \"gpt-4-0613\",\n",
531-
" \"gpt-4-32k-0613\",\n",
532-
" }:\n",
533-
" tokens_per_message = 3\n",
534-
" tokens_per_name = 1\n",
535-
" elif model == \"gpt-3.5-turbo-0301\":\n",
536-
" tokens_per_message = 4 # every message follows <|start|>{role/name}\\n{content}<|end|>\\n\n",
537-
" tokens_per_name = -1 # if there's a name, the role is omitted\n",
538-
" elif \"gpt-3.5-turbo\" in model:\n",
539-
" print(\"Warning: gpt-3.5-turbo may update over time. Returning num tokens assuming gpt-3.5-turbo-0613.\")\n",
540-
" return num_tokens_from_messages(messages, model=\"gpt-3.5-turbo-0613\")\n",
541-
" elif \"gpt-4\" in model:\n",
542-
" print(\"Warning: gpt-4 may update over time. Returning num tokens assuming gpt-4-0613.\")\n",
543-
" return num_tokens_from_messages(messages, model=\"gpt-4-0613\")\n",
544-
" else:\n",
545-
" raise NotImplementedError(\n",
546-
" f\"\"\"num_tokens_from_messages() is not implemented for model {model}.\"\"\"\n",
547-
" )\n",
548-
" num_tokens = 0\n",
549-
" for message in messages:\n",
550-
" num_tokens += tokens_per_message\n",
551-
" for key, value in message.items():\n",
552-
" num_tokens += len(encoding.encode(value))\n",
553-
" if key == \"name\":\n",
554-
" num_tokens += tokens_per_name\n",
555-
" num_tokens += 3 # every reply is primed with <|start|>assistant<|message|>\n",
556-
" return num_tokens\n"
557-
]
514+
"source": "# Import the unified token counting function\nimport sys\nimport os\n# Add the utils directory to the path so we can import our utility\nsys.path.append(os.path.join(os.path.dirname(os.path.abspath('.')), 'utils'))\n\nfrom utils.token_counting_utils import num_tokens_from_messages\n\n# The num_tokens_from_messages function is now imported from the shared utility module\n# It supports all current OpenAI models including:\n# - gpt-3.5-turbo variants\n# - gpt-4 variants \n# - gpt-4o and gpt-4o-mini variants"
558515
},
559516
{
560517
"cell_type": "code",
@@ -678,4 +635,4 @@
678635
},
679636
"nbformat": 4,
680637
"nbformat_minor": 2
681-
}
638+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
"""
2+
Utility functions for counting tokens used by OpenAI models.
3+
4+
This module provides functions to estimate the number of tokens that will be
5+
used by various OpenAI models when processing messages.
6+
"""
7+
8+
import tiktoken
9+
10+
11+
def num_tokens_from_messages(messages, model="gpt-4o-mini"):
12+
"""
13+
Return the number of tokens used by a list of messages.
14+
15+
Args:
16+
messages: List of message dictionaries with 'role' and 'content' keys
17+
model: Model name string (e.g., "gpt-4", "gpt-3.5-turbo", "gpt-4o-mini")
18+
19+
Returns:
20+
int: Estimated number of tokens used by the messages
21+
22+
Note:
23+
Token counts are estimates and may vary slightly from actual API usage.
24+
The exact token counting method may change between model versions.
25+
"""
26+
try:
27+
encoding = tiktoken.encoding_for_model(model)
28+
except KeyError:
29+
print(f"Warning: model {model} not found. Using o200k_base encoding.")
30+
encoding = tiktoken.get_encoding("o200k_base")
31+
32+
# Models that use o200k_base encoding
33+
if model in {
34+
"gpt-4o",
35+
"gpt-4o-2024-05-13",
36+
"gpt-4o-2024-08-06",
37+
"gpt-4o-mini",
38+
"gpt-4o-mini-2024-07-18",
39+
}:
40+
# For o200k_base models, use o200k_base encoding
41+
try:
42+
encoding = tiktoken.get_encoding("o200k_base")
43+
except KeyError:
44+
pass
45+
# Models that use cl100k_base encoding
46+
elif model in {
47+
"gpt-3.5-turbo-0125",
48+
"gpt-3.5-turbo-0613",
49+
"gpt-3.5-turbo-16k-0613",
50+
"gpt-4-0314",
51+
"gpt-4-32k-0314",
52+
"gpt-4-0613",
53+
"gpt-4-32k-0613",
54+
}:
55+
# For cl100k_base models, ensure we're using cl100k_base
56+
try:
57+
encoding = tiktoken.get_encoding("cl100k_base")
58+
except KeyError:
59+
pass
60+
61+
# Set tokens per message and per name based on model
62+
if model in {
63+
"gpt-3.5-turbo-0125",
64+
"gpt-3.5-turbo-0613",
65+
"gpt-3.5-turbo-16k-0613",
66+
"gpt-4-0314",
67+
"gpt-4-32k-0314",
68+
"gpt-4-0613",
69+
"gpt-4-32k-0613",
70+
"gpt-4o-mini-2024-07-18",
71+
"gpt-4o-mini",
72+
"gpt-4o-2024-08-06",
73+
"gpt-4o",
74+
}:
75+
tokens_per_message = 3
76+
tokens_per_name = 1
77+
elif model == "gpt-3.5-turbo-0301":
78+
# Special handling for gpt-3.5-turbo-0301
79+
tokens_per_message = 4 # every message follows <|start|>{role/name}\n{content}<|end|>\n
80+
tokens_per_name = -1 # if there's a name, the role is omitted
81+
# Handle base model names that may update over time
82+
elif "gpt-3.5-turbo" in model:
83+
print("Warning: gpt-3.5-turbo may update over time. Returning num tokens assuming gpt-3.5-turbo-0125.")
84+
return num_tokens_from_messages(messages, model="gpt-3.5-turbo-0125")
85+
elif "gpt-4o-mini" in model:
86+
print("Warning: gpt-4o-mini may update over time. Returning num tokens assuming gpt-4o-mini-2024-07-18.")
87+
return num_tokens_from_messages(messages, model="gpt-4o-mini-2024-07-18")
88+
elif "gpt-4o" in model:
89+
print("Warning: gpt-4o may update over time. Returning num tokens assuming gpt-4o-2024-08-06.")
90+
return num_tokens_from_messages(messages, model="gpt-4o-2024-08-06")
91+
elif "gpt-4" in model:
92+
print("Warning: gpt-4 may update over time. Returning num tokens assuming gpt-4-0613.")
93+
return num_tokens_from_messages(messages, model="gpt-4-0613")
94+
else:
95+
raise NotImplementedError(
96+
f"num_tokens_from_messages() is not implemented for model {model}. "
97+
f"See https://github.com/openai/openai-python/blob/main/chatml.md "
98+
f"for information on how messages are converted to tokens."
99+
)
100+
101+
num_tokens = 0
102+
for message in messages:
103+
num_tokens += tokens_per_message
104+
for key, value in message.items():
105+
num_tokens += len(encoding.encode(value))
106+
if key == "name":
107+
num_tokens += tokens_per_name
108+
num_tokens += 3 # every reply is primed with <|start|>assistant<|message|>
109+
return num_tokens
110+
111+
112+
def num_tokens_from_string(string: str, encoding_name: str) -> int:
113+
"""
114+
Returns the number of tokens in a text string using the specified encoding.
115+
116+
Args:
117+
string: The text string to tokenize
118+
encoding_name: The name of the encoding to use (e.g., "cl100k_base", "o200k_base")
119+
120+
Returns:
121+
int: Number of tokens in the string
122+
"""
123+
encoding = tiktoken.get_encoding(encoding_name)
124+
num_tokens = len(encoding.encode(string))
125+
return num_tokens

0 commit comments

Comments
 (0)