When using OpenAI chat models, you must account for extra tokens added by the ChatML format (e.g., <|start|>, <|end|>, and role/name metadata). Because these overhead tokens vary by model (e.g., gpt-4 vs gpt-3.5-turbo), you should implement a counting logic that applies specific offsets based on the model name and the structure of your ChatMessage list.
To implement this, you need to:
- Retrieve the correct
Encoding from the EncodingRegistry for the specific model. - Determine the
tokensPerMessage and tokensPerName overhead based on the model family. - Iterate through the messages, summing the tokens for the content, the role, and (if present) the name, plus the model-specific overhead.
- Add the final priming tokens (e.g., 3 tokens for the assistant reply start).
private int countMessageTokens(
EncodingRegistry registry,
String model,
List<ChatMessage> messages // consists of role, content and an optional name
) {
Encoding encoding = registry.getEncodingForModel(model).orElseThrow();
int tokensPerMessage;
int tokensPerName;
if (model.startsWith("gpt-4")) {
tokensPerMessage = 3;
tokensPerName = 1;
} else if (model.startsWith("gpt-3.5-turbo")) {
tokensPerMessage = 4; // every message follows <|start|>{role/name}\n{content}<|end|>
tokensPerName = -1; // if there's a name, the role is omitted
} else {
throw new IllegalArgumentException("Unsupported model: " + model);
}
int sum = 0;
for (final var message : messages) {
sum += tokensPerMessage;
sum += encoding.countTokens(message.getContent());
sum += encoding.countTokens(message.getRole());
if (message.hasName()) {
sum += encoding.countTokens(message.getName());
sum += tokensPerName;
}
}
sum += 3; // every reply is primed with <|start|>assistant<|message|>
return sum;
}