2023-11-06 13:13:53 -08:00
|
|
|
const { getEncodingNameForModel, getEncoding } = require("js-tiktoken");
|
|
|
|
|
2025-01-30 17:55:03 -08:00
|
|
|
/**
|
|
|
|
* @class TokenManager
|
|
|
|
*
|
|
|
|
* @notice
|
|
|
|
* We cannot do estimation of tokens here like we do in the collector
|
|
|
|
* because we need to know the model to do it.
|
|
|
|
* Other issues are we also do reverse tokenization here for the chat history during cannonballing.
|
|
|
|
* So here we are stuck doing the actual tokenization and encoding until we figure out what to do with prompt overflows.
|
|
|
|
*/
|
2023-11-06 13:13:53 -08:00
|
|
|
class TokenManager {
|
2025-01-30 17:55:03 -08:00
|
|
|
static instance = null;
|
|
|
|
static currentModel = null;
|
|
|
|
|
2023-11-06 13:13:53 -08:00
|
|
|
constructor(model = "gpt-3.5-turbo") {
|
2025-01-30 17:55:03 -08:00
|
|
|
if (TokenManager.instance && TokenManager.currentModel === model) {
|
|
|
|
this.log("Returning existing instance for model:", model);
|
|
|
|
return TokenManager.instance;
|
|
|
|
}
|
|
|
|
|
2023-11-06 13:13:53 -08:00
|
|
|
this.model = model;
|
2024-01-04 15:47:00 -08:00
|
|
|
this.encoderName = this.#getEncodingFromModel(model);
|
2023-11-06 13:13:53 -08:00
|
|
|
this.encoder = getEncoding(this.encoderName);
|
2025-01-30 17:55:03 -08:00
|
|
|
|
|
|
|
TokenManager.instance = this;
|
|
|
|
TokenManager.currentModel = model;
|
|
|
|
this.log("Initialized new TokenManager instance for model:", model);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
log(text, ...args) {
|
|
|
|
console.log(`\x1b[35m[TokenManager]\x1b[0m ${text}`, ...args);
|
2023-11-06 13:13:53 -08:00
|
|
|
}
|
|
|
|
|
2024-01-04 15:47:00 -08:00
|
|
|
#getEncodingFromModel(model) {
|
2023-11-06 13:13:53 -08:00
|
|
|
try {
|
|
|
|
return getEncodingNameForModel(model);
|
|
|
|
} catch {
|
|
|
|
return "cl100k_base";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-30 17:55:03 -08:00
|
|
|
/**
|
|
|
|
* Pass in an empty array of disallowedSpecials to handle all tokens as text and to be tokenized.
|
|
|
|
* @param {string} input
|
|
|
|
* @returns {number[]}
|
|
|
|
*/
|
2024-01-05 09:39:19 -08:00
|
|
|
tokensFromString(input = "") {
|
2024-07-22 12:53:11 -07:00
|
|
|
try {
|
|
|
|
const tokens = this.encoder.encode(String(input), undefined, []);
|
|
|
|
return tokens;
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
|
|
|
return [];
|
|
|
|
}
|
2024-01-05 09:39:19 -08:00
|
|
|
}
|
|
|
|
|
2025-01-30 17:55:03 -08:00
|
|
|
/**
|
|
|
|
* Converts an array of tokens back to a string.
|
|
|
|
* @param {number[]} tokens
|
|
|
|
* @returns {string}
|
|
|
|
*/
|
2023-11-06 13:13:53 -08:00
|
|
|
bytesFromTokens(tokens = []) {
|
|
|
|
const bytes = this.encoder.decode(tokens);
|
|
|
|
return bytes;
|
|
|
|
}
|
|
|
|
|
2025-01-30 17:55:03 -08:00
|
|
|
/**
|
|
|
|
* Counts the number of tokens in a string.
|
|
|
|
* @param {string} input
|
|
|
|
* @returns {number}
|
|
|
|
*/
|
2023-11-06 13:13:53 -08:00
|
|
|
countFromString(input = "") {
|
2024-01-05 09:39:19 -08:00
|
|
|
const tokens = this.tokensFromString(input);
|
2023-11-06 13:13:53 -08:00
|
|
|
return tokens.length;
|
|
|
|
}
|
|
|
|
|
2025-01-30 17:55:03 -08:00
|
|
|
/**
|
|
|
|
* Estimates the number of tokens in a string or array of strings.
|
|
|
|
* @param {string | string[]} input
|
|
|
|
* @returns {number}
|
|
|
|
*/
|
2023-11-06 13:13:53 -08:00
|
|
|
statsFrom(input) {
|
|
|
|
if (typeof input === "string") return this.countFromString(input);
|
|
|
|
|
|
|
|
// What is going on here?
|
|
|
|
// https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb Item 6.
|
|
|
|
// The only option is to estimate. From repeated testing using the static values in the code we are always 2 off,
|
|
|
|
// which means as of Nov 1, 2023 the additional factor on ln: 476 changed from 3 to 5.
|
|
|
|
if (Array.isArray(input)) {
|
|
|
|
const perMessageFactorTokens = input.length * 3;
|
|
|
|
const tokensFromContent = input.reduce(
|
|
|
|
(a, b) => a + this.countFromString(b.content),
|
|
|
|
0
|
|
|
|
);
|
|
|
|
const diffCoefficient = 5;
|
|
|
|
return perMessageFactorTokens + tokensFromContent + diffCoefficient;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new Error("Not a supported tokenized format.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
TokenManager,
|
|
|
|
};
|