@promptbook/openai
Version:
It's time for a paradigm shift. The future of software in plain English, French or Latin
1,363 lines (1,295 loc) โข 95 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('spacetrim'), require('crypto'), require('openai')) :
typeof define === 'function' && define.amd ? define(['exports', 'colors', 'spacetrim', 'crypto', 'openai'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-openai"] = {}, global.colors, global.spaceTrim, global.crypto, global.OpenAI));
})(this, (function (exports, colors, spaceTrim, crypto, OpenAI) { 'use strict';
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var colors__default = /*#__PURE__*/_interopDefaultLegacy(colors);
var spaceTrim__default = /*#__PURE__*/_interopDefaultLegacy(spaceTrim);
var OpenAI__default = /*#__PURE__*/_interopDefaultLegacy(OpenAI);
// โ ๏ธ WARNING: This code has been generated so that any manual changes will be overwritten
/**
* The version of the Book language
*
* @generated
* @see https://github.com/webgptorg/book
*/
const BOOK_LANGUAGE_VERSION = '1.0.0';
/**
* The version of the Promptbook engine
*
* @generated
* @see https://github.com/webgptorg/promptbook
*/
const PROMPTBOOK_ENGINE_VERSION = '0.89.0-18';
/**
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
* Note: [๐] Ignore a discrepancy between file name and entity name
*/
/**
* Detects if the code is running in a browser environment in main thread (Not in a web worker)
*
* Note: `$` is used to indicate that this function is not a pure function - it looks at the global object to determine the environment
*
* @public exported from `@promptbook/utils`
*/
const $isRunningInBrowser = new Function(`
try {
return this === window;
} catch (e) {
return false;
}
`);
/**
* TODO: [๐บ]
*/
/**
* Detects if the code is running in a web worker
*
* Note: `$` is used to indicate that this function is not a pure function - it looks at the global object to determine the environment
*
* @public exported from `@promptbook/utils`
*/
const $isRunningInWebWorker = new Function(`
try {
if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) {
return true;
} else {
return false;
}
} catch (e) {
return false;
}
`);
/**
* TODO: [๐บ]
*/
/**
* This error type indicates that some part of the code is not implemented yet
*
* @public exported from `@promptbook/core`
*/
class NotYetImplementedError extends Error {
constructor(message) {
super(spaceTrim.spaceTrim((block) => `
${block(message)}
Note: This feature is not implemented yet but it will be soon.
If you want speed up the implementation or just read more, look here:
https://github.com/webgptorg/promptbook
Or contact us on pavol@ptbk.io
`));
this.name = 'NotYetImplementedError';
Object.setPrototypeOf(this, NotYetImplementedError.prototype);
}
}
/**
* Generates random token
*
* Note: This function is cryptographically secure (it uses crypto.randomBytes internally)
*
* @private internal helper function
* @returns secure random token
*/
function $randomToken(randomness) {
return crypto.randomBytes(randomness).toString('hex');
}
/**
* TODO: Maybe use nanoid instead https://github.com/ai/nanoid
*/
/**
* This error indicates errors during the execution of the pipeline
*
* @public exported from `@promptbook/core`
*/
class PipelineExecutionError extends Error {
constructor(message) {
// Added id parameter
super(message);
this.name = 'PipelineExecutionError';
// TODO: [๐] DRY - Maybe $randomId
this.id = `error-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid simmilar char conflicts */)}`;
Object.setPrototypeOf(this, PipelineExecutionError.prototype);
}
}
/**
* TODO: [๐ง ][๐] Add id to all errors
*/
/**
* Freezes the given object and all its nested objects recursively
*
* Note: `$` is used to indicate that this function is not a pure function - it mutates given object
* Note: This function mutates the object and returns the original (but mutated-deep-freezed) object
*
* @returns The same object as the input, but deeply frozen
* @public exported from `@promptbook/utils`
*/
function $deepFreeze(objectValue) {
if (Array.isArray(objectValue)) {
return Object.freeze(objectValue.map((item) => $deepFreeze(item)));
}
const propertyNames = Object.getOwnPropertyNames(objectValue);
for (const propertyName of propertyNames) {
const value = objectValue[propertyName];
if (value && typeof value === 'object') {
$deepFreeze(value);
}
}
Object.freeze(objectValue);
return objectValue;
}
/**
* TODO: [๐ง ] Is there a way how to meaningfully test this utility
*/
/**
* Represents the uncertain value
*
* @public exported from `@promptbook/core`
*/
const ZERO_VALUE = $deepFreeze({ value: 0 });
/**
* Represents the uncertain value
*
* @public exported from `@promptbook/core`
*/
const UNCERTAIN_ZERO_VALUE = $deepFreeze({ value: 0, isUncertain: true });
/**
* Represents the usage with no resources consumed
*
* @public exported from `@promptbook/core`
*/
$deepFreeze({
price: ZERO_VALUE,
input: {
tokensCount: ZERO_VALUE,
charactersCount: ZERO_VALUE,
wordsCount: ZERO_VALUE,
sentencesCount: ZERO_VALUE,
linesCount: ZERO_VALUE,
paragraphsCount: ZERO_VALUE,
pagesCount: ZERO_VALUE,
},
output: {
tokensCount: ZERO_VALUE,
charactersCount: ZERO_VALUE,
wordsCount: ZERO_VALUE,
sentencesCount: ZERO_VALUE,
linesCount: ZERO_VALUE,
paragraphsCount: ZERO_VALUE,
pagesCount: ZERO_VALUE,
},
});
/**
* Represents the usage with unknown resources consumed
*
* @public exported from `@promptbook/core`
*/
const UNCERTAIN_USAGE = $deepFreeze({
price: UNCERTAIN_ZERO_VALUE,
input: {
tokensCount: UNCERTAIN_ZERO_VALUE,
charactersCount: UNCERTAIN_ZERO_VALUE,
wordsCount: UNCERTAIN_ZERO_VALUE,
sentencesCount: UNCERTAIN_ZERO_VALUE,
linesCount: UNCERTAIN_ZERO_VALUE,
paragraphsCount: UNCERTAIN_ZERO_VALUE,
pagesCount: UNCERTAIN_ZERO_VALUE,
},
output: {
tokensCount: UNCERTAIN_ZERO_VALUE,
charactersCount: UNCERTAIN_ZERO_VALUE,
wordsCount: UNCERTAIN_ZERO_VALUE,
sentencesCount: UNCERTAIN_ZERO_VALUE,
linesCount: UNCERTAIN_ZERO_VALUE,
paragraphsCount: UNCERTAIN_ZERO_VALUE,
pagesCount: UNCERTAIN_ZERO_VALUE,
},
});
/**
* Note: [๐] Ignore a discrepancy between file name and entity name
*/
/**
* Simple wrapper `new Date().toISOString()`
*
* Note: `$` is used to indicate that this function is not a pure function - it is not deterministic because it depends on the current time
*
* @returns string_date branded type
* @public exported from `@promptbook/utils`
*/
function $getCurrentDate() {
return new Date().toISOString();
}
/**
* Name for the Promptbook
*
* TODO: [๐ฝ] Unite branding and make single place for it
*
* @public exported from `@promptbook/core`
*/
const NAME = `Promptbook`;
/**
* Email of the responsible person
*
* @public exported from `@promptbook/core`
*/
const ADMIN_EMAIL = 'pavol@ptbk.io';
/**
* Name of the responsible person for the Promptbook on GitHub
*
* @public exported from `@promptbook/core`
*/
const ADMIN_GITHUB_NAME = 'hejny';
// <- TODO: [๐ง ] Better system for generator warnings - not always "code" and "by `@promptbook/cli`"
/**
* The maximum number of iterations for a loops
*
* @private within the repository - too low-level in comparison with other `MAX_...`
*/
const LOOP_LIMIT = 1000;
/**
* Strings to represent various values in the context of parameter values
*
* @public exported from `@promptbook/utils`
*/
const VALUE_STRINGS = {
empty: '(nothing; empty string)',
null: '(no value; null)',
undefined: '(unknown value; undefined)',
nan: '(not a number; NaN)',
infinity: '(infinity; โ)',
negativeInfinity: '(negative infinity; -โ)',
unserializable: '(unserializable value)',
circular: '(circular JSON)',
};
/**
* Small number limit
*
* @public exported from `@promptbook/utils`
*/
const SMALL_NUMBER = 0.001;
// <- TODO: [๐งโโ๏ธ]
/**
* @@@
*
* @public exported from `@promptbook/core`
*/
Object.freeze({
delimiter: ',',
quoteChar: '"',
newline: '\n',
skipEmptyLines: true,
});
/**
* Note: [๐] Ignore a discrepancy between file name and entity name
* TODO: [๐ง ][๐งโโ๏ธ] Maybe join remoteServerUrl and path into single value
*/
/**
* Orders JSON object by keys
*
* @returns The same type of object as the input re-ordered
* @public exported from `@promptbook/utils`
*/
function orderJson(options) {
const { value, order } = options;
const orderedValue = {
...(order === undefined ? {} : Object.fromEntries(order.map((key) => [key, undefined]))),
...value,
};
return orderedValue;
}
/**
* Make error report URL for the given error
*
* @private private within the repository
*/
function getErrorReportUrl(error) {
const report = {
title: `๐ Error report from ${NAME}`,
body: spaceTrim__default["default"]((block) => `
\`${error.name || 'Error'}\` has occurred in the [${NAME}], please look into it @${ADMIN_GITHUB_NAME}.
\`\`\`
${block(error.message || '(no error message)')}
\`\`\`
## More info:
- **Promptbook engine version:** ${PROMPTBOOK_ENGINE_VERSION}
- **Book language version:** ${BOOK_LANGUAGE_VERSION}
- **Time:** ${new Date().toISOString()}
<details>
<summary>Stack trace:</summary>
## Stack trace:
\`\`\`stacktrace
${block(error.stack || '(empty)')}
\`\`\`
</details>
`),
};
const reportUrl = new URL(`https://github.com/webgptorg/promptbook/issues/new`);
reportUrl.searchParams.set('labels', 'bug');
reportUrl.searchParams.set('assignees', ADMIN_GITHUB_NAME);
reportUrl.searchParams.set('title', report.title);
reportUrl.searchParams.set('body', report.body);
return reportUrl;
}
/**
* This error type indicates that the error should not happen and its last check before crashing with some other error
*
* @public exported from `@promptbook/core`
*/
class UnexpectedError extends Error {
constructor(message) {
super(spaceTrim.spaceTrim((block) => `
${block(message)}
Note: This error should not happen.
It's probbably a bug in the pipeline collection
Please report issue:
${block(getErrorReportUrl(new Error(message)).href)}
Or contact us on ${ADMIN_EMAIL}
`));
this.name = 'UnexpectedError';
Object.setPrototypeOf(this, UnexpectedError.prototype);
}
}
/**
* This error type indicates that somewhere in the code non-Error object was thrown and it was wrapped into the `WrappedError`
*
* @public exported from `@promptbook/core`
*/
class WrappedError extends Error {
constructor(whatWasThrown) {
const tag = `[๐คฎ]`;
console.error(tag, whatWasThrown);
super(spaceTrim.spaceTrim(`
Non-Error object was thrown
Note: Look for ${tag} in the console for more details
Please report issue on ${ADMIN_EMAIL}
`));
this.name = 'WrappedError';
Object.setPrototypeOf(this, WrappedError.prototype);
}
}
/**
* Helper used in catch blocks to assert that the error is an instance of `Error`
*
* @param whatWasThrown Any object that was thrown
* @returns Nothing if the error is an instance of `Error`
* @throws `WrappedError` or `UnexpectedError` if the error is not standard
*
* @private within the repository
*/
function assertsError(whatWasThrown) {
// Case 1: Handle error which was rethrown as `WrappedError`
if (whatWasThrown instanceof WrappedError) {
const wrappedError = whatWasThrown;
throw wrappedError;
}
// Case 2: Handle unexpected errors
if (whatWasThrown instanceof UnexpectedError) {
const unexpectedError = whatWasThrown;
throw unexpectedError;
}
// Case 3: Handle standard errors - keep them up to consumer
if (whatWasThrown instanceof Error) {
return;
}
// Case 4: Handle non-standard errors - wrap them into `WrappedError` and throw
throw new WrappedError(whatWasThrown);
}
/**
* Checks if the value is [๐] serializable as JSON
* If not, throws an UnexpectedError with a rich error message and tracking
*
* - Almost all primitives are serializable BUT:
* - `undefined` is not serializable
* - `NaN` is not serializable
* - Objects and arrays are serializable if all their properties are serializable
* - Functions are not serializable
* - Circular references are not serializable
* - `Date` objects are not serializable
* - `Map` and `Set` objects are not serializable
* - `RegExp` objects are not serializable
* - `Error` objects are not serializable
* - `Symbol` objects are not serializable
* - And much more...
*
* @throws UnexpectedError if the value is not serializable as JSON
* @public exported from `@promptbook/utils`
*/
function checkSerializableAsJson(options) {
const { value, name, message } = options;
if (value === undefined) {
throw new UnexpectedError(`${name} is undefined`);
}
else if (value === null) {
return;
}
else if (typeof value === 'boolean') {
return;
}
else if (typeof value === 'number' && !isNaN(value)) {
return;
}
else if (typeof value === 'string') {
return;
}
else if (typeof value === 'symbol') {
throw new UnexpectedError(`${name} is symbol`);
}
else if (typeof value === 'function') {
throw new UnexpectedError(`${name} is function`);
}
else if (typeof value === 'object' && Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
}
}
else if (typeof value === 'object') {
if (value instanceof Date) {
throw new UnexpectedError(spaceTrim__default["default"]((block) => `
\`${name}\` is Date
Use \`string_date_iso8601\` instead
Additional message for \`${name}\`:
${block(message || '(nothing)')}
`));
}
else if (value instanceof Map) {
throw new UnexpectedError(`${name} is Map`);
}
else if (value instanceof Set) {
throw new UnexpectedError(`${name} is Set`);
}
else if (value instanceof RegExp) {
throw new UnexpectedError(`${name} is RegExp`);
}
else if (value instanceof Error) {
throw new UnexpectedError(spaceTrim__default["default"]((block) => `
\`${name}\` is unserialized Error
Use function \`serializeError\`
Additional message for \`${name}\`:
${block(message || '(nothing)')}
`));
}
else {
for (const [subName, subValue] of Object.entries(value)) {
if (subValue === undefined) {
// Note: undefined in object is serializable - it is just omited
continue;
}
checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
}
try {
JSON.stringify(value); // <- TODO: [0]
}
catch (error) {
assertsError(error);
throw new UnexpectedError(spaceTrim__default["default"]((block) => `
\`${name}\` is not serializable
${block(error.stack || error.message)}
Additional message for \`${name}\`:
${block(message || '(nothing)')}
`));
}
/*
TODO: [0] Is there some more elegant way to check circular references?
const seen = new Set();
const stack = [{ value }];
while (stack.length > 0) {
const { value } = stack.pop()!;
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
throw new UnexpectedError(`${name} has circular reference`);
}
seen.add(value);
if (Array.isArray(value)) {
stack.push(...value.map((value) => ({ value })));
} else {
stack.push(...Object.values(value).map((value) => ({ value })));
}
}
}
*/
return;
}
}
else {
throw new UnexpectedError(spaceTrim__default["default"]((block) => `
\`${name}\` is unknown type
Additional message for \`${name}\`:
${block(message || '(nothing)')}
`));
}
}
/**
* TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
* TODO: [๐ง ][main] !!3 In-memory cache of same values to prevent multiple checks
* Note: [๐ ] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
*/
/**
* @@@
*
* @public exported from `@promptbook/utils`
*/
function deepClone(objectValue) {
return JSON.parse(JSON.stringify(objectValue));
/*
TODO: [๐ง ] Is there a better implementation?
> const propertyNames = Object.getOwnPropertyNames(objectValue);
> for (const propertyName of propertyNames) {
> const value = (objectValue as really_any)[propertyName];
> if (value && typeof value === 'object') {
> deepClone(value);
> }
> }
> return Object.assign({}, objectValue);
*/
}
/**
* TODO: [๐ง ] Is there a way how to meaningfully test this utility
*/
/**
* Utility to export a JSON object from a function
*
* 1) Checks if the value is serializable as JSON
* 2) Makes a deep clone of the object
* 2) Orders the object properties
* 2) Deeply freezes the cloned object
*
* Note: This function does not mutates the given object
*
* @returns The same type of object as the input but read-only and re-ordered
* @public exported from `@promptbook/utils`
*/
function exportJson(options) {
const { name, value, order, message } = options;
checkSerializableAsJson({ name, value, message });
const orderedValue =
// TODO: Fix error "Type instantiation is excessively deep and possibly infinite."
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
order === undefined
? deepClone(value)
: orderJson({
value: value,
// <- Note: checkSerializableAsJson asserts that the value is serializable as JSON
order: order,
});
$deepFreeze(orderedValue);
return orderedValue;
}
/**
* TODO: [๐ง ] Is there a way how to meaningfully test this utility
*/
/**
* Nonce which is used for replacing things in strings
*
* @private within the repository
*/
const REPLACING_NONCE = 'ptbkauk42kV2dzao34faw7FudQUHYPtW';
/**
* @@@
*
* @private within the repository
*/
const RESERVED_PARAMETER_MISSING_VALUE = 'MISSING-' + REPLACING_NONCE;
/**
* @@@
*
* @private within the repository
*/
const RESERVED_PARAMETER_RESTRICTED = 'RESTRICTED-' + REPLACING_NONCE;
/**
* The names of the parameters that are reserved for special purposes
*
* @public exported from `@promptbook/core`
*/
exportJson({
name: 'RESERVED_PARAMETER_NAMES',
message: `The names of the parameters that are reserved for special purposes`,
value: [
'content',
'context',
'knowledge',
'examples',
'modelName',
'currentDate',
// <- TODO: list here all command names
// <- TODO: Add more like 'date', 'modelName',...
// <- TODO: Add [emoji] + instructions ACRY when adding new reserved parameter
],
});
/**
* Note: [๐] Ignore a discrepancy between file name and entity name
*/
/**
* This error type indicates that some limit was reached
*
* @public exported from `@promptbook/core`
*/
class LimitReachedError extends Error {
constructor(message) {
super(message);
this.name = 'LimitReachedError';
Object.setPrototypeOf(this, LimitReachedError.prototype);
}
}
/**
* Format either small or big number
*
* @public exported from `@promptbook/utils`
*/
function numberToString(value) {
if (value === 0) {
return '0';
}
else if (Number.isNaN(value)) {
return VALUE_STRINGS.nan;
}
else if (value === Infinity) {
return VALUE_STRINGS.infinity;
}
else if (value === -Infinity) {
return VALUE_STRINGS.negativeInfinity;
}
for (let exponent = 0; exponent < 15; exponent++) {
const factor = 10 ** exponent;
const valueRounded = Math.round(value * factor) / factor;
if (Math.abs(value - valueRounded) / value < SMALL_NUMBER) {
return valueRounded.toFixed(exponent);
}
}
return value.toString();
}
/**
* Function `valueToString` will convert the given value to string
* This is useful and used in the `templateParameters` function
*
* Note: This function is not just calling `toString` method
* It's more complex and can handle this conversion specifically for LLM models
* See `VALUE_STRINGS`
*
* Note: There are 2 similar functions
* - `valueToString` converts value to string for LLM models as human-readable string
* - `asSerializable` converts value to string to preserve full information to be able to convert it back
*
* @public exported from `@promptbook/utils`
*/
function valueToString(value) {
try {
if (value === '') {
return VALUE_STRINGS.empty;
}
else if (value === null) {
return VALUE_STRINGS.null;
}
else if (value === undefined) {
return VALUE_STRINGS.undefined;
}
else if (typeof value === 'string') {
return value;
}
else if (typeof value === 'number') {
return numberToString(value);
}
else if (value instanceof Date) {
return value.toISOString();
}
else {
try {
return JSON.stringify(value);
}
catch (error) {
if (error instanceof TypeError && error.message.includes('circular structure')) {
return VALUE_STRINGS.circular;
}
throw error;
}
}
}
catch (error) {
assertsError(error);
console.error(error);
return VALUE_STRINGS.unserializable;
}
}
/**
* Replaces parameters in template with values from parameters object
*
* Note: This function is not places strings into string,
* It's more complex and can handle this operation specifically for LLM models
*
* @param template the template with parameters in {curly} braces
* @param parameters the object with parameters
* @returns the template with replaced parameters
* @throws {PipelineExecutionError} if parameter is not defined, not closed, or not opened
* @public exported from `@promptbook/utils`
*/
function templateParameters(template, parameters) {
for (const [parameterName, parameterValue] of Object.entries(parameters)) {
if (parameterValue === RESERVED_PARAMETER_MISSING_VALUE) {
throw new UnexpectedError(`Parameter \`{${parameterName}}\` has missing value`);
}
else if (parameterValue === RESERVED_PARAMETER_RESTRICTED) {
// TODO: [๐ต]
throw new UnexpectedError(`Parameter \`{${parameterName}}\` is restricted to use`);
}
}
let replacedTemplates = template;
let match;
let loopLimit = LOOP_LIMIT;
while ((match = /^(?<precol>.*){(?<parameterName>\w+)}(.*)/m /* <- Not global */
.exec(replacedTemplates))) {
if (loopLimit-- < 0) {
throw new LimitReachedError('Loop limit reached during parameters replacement in `templateParameters`');
}
const precol = match.groups.precol;
const parameterName = match.groups.parameterName;
if (parameterName === '') {
// Note: Skip empty placeholders. It's used to avoid confusion with JSON-like strings
continue;
}
if (parameterName.indexOf('{') !== -1 || parameterName.indexOf('}') !== -1) {
throw new PipelineExecutionError('Parameter is already opened or not closed');
}
if (parameters[parameterName] === undefined) {
throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
}
let parameterValue = parameters[parameterName];
if (parameterValue === undefined) {
throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
}
parameterValue = valueToString(parameterValue);
// Escape curly braces in parameter values to prevent prompt-injection
parameterValue = parameterValue.replace(/[{}]/g, '\\$&');
if (parameterValue.includes('\n') && /^\s*\W{0,3}\s*$/.test(precol)) {
parameterValue = parameterValue
.split('\n')
.map((line, index) => (index === 0 ? line : `${precol}${line}`))
.join('\n');
}
replacedTemplates =
replacedTemplates.substring(0, match.index + precol.length) +
parameterValue +
replacedTemplates.substring(match.index + precol.length + parameterName.length + 2);
}
// [๐ซ] Check if there are parameters that are not closed properly
if (/{\w+$/.test(replacedTemplates)) {
throw new PipelineExecutionError('Parameter is not closed');
}
// [๐ซ] Check if there are parameters that are not opened properly
if (/^\w+}/.test(replacedTemplates)) {
throw new PipelineExecutionError('Parameter is not opened');
}
return replacedTemplates;
}
/**
* Counts number of characters in the text
*
* @public exported from `@promptbook/utils`
*/
function countCharacters(text) {
// Remove null characters
text = text.replace(/\0/g, '');
// Replace emojis (and also ZWJ sequence) with hyphens
text = text.replace(/(\p{Extended_Pictographic})\p{Modifier_Symbol}/gu, '$1');
text = text.replace(/(\p{Extended_Pictographic})[\u{FE00}-\u{FE0F}]/gu, '$1');
text = text.replace(/\p{Extended_Pictographic}(\u{200D}\p{Extended_Pictographic})*/gu, '-');
return text.length;
}
/**
* TODO: [๐ฅด] Implement counting in formats - like JSON, CSV, XML,...
*/
/**
* Number of characters per standard line with 11pt Arial font size.
*
* @public exported from `@promptbook/utils`
*/
const CHARACTERS_PER_STANDARD_LINE = 63;
/**
* Number of lines per standard A4 page with 11pt Arial font size and standard margins and spacing.
*
* @public exported from `@promptbook/utils`
*/
const LINES_PER_STANDARD_PAGE = 44;
/**
* TODO: [๐ง ] Should be this `constants.ts` or `config.ts`?
* Note: [๐] Ignore a discrepancy between file name and entity name
*/
/**
* Counts number of lines in the text
*
* Note: This does not check only for the presence of newlines, but also for the length of the standard line.
*
* @public exported from `@promptbook/utils`
*/
function countLines(text) {
text = text.replace('\r\n', '\n');
text = text.replace('\r', '\n');
const lines = text.split('\n');
return lines.reduce((count, line) => count + Math.ceil(line.length / CHARACTERS_PER_STANDARD_LINE), 0);
}
/**
* TODO: [๐ฅด] Implement counting in formats - like JSON, CSV, XML,...
*/
/**
* Counts number of pages in the text
*
* Note: This does not check only for the count of newlines, but also for the length of the standard line and length of the standard page.
*
* @public exported from `@promptbook/utils`
*/
function countPages(text) {
return Math.ceil(countLines(text) / LINES_PER_STANDARD_PAGE);
}
/**
* TODO: [๐ฅด] Implement counting in formats - like JSON, CSV, XML,...
*/
/**
* Counts number of paragraphs in the text
*
* @public exported from `@promptbook/utils`
*/
function countParagraphs(text) {
return text.split(/\n\s*\n/).filter((paragraph) => paragraph.trim() !== '').length;
}
/**
* TODO: [๐ฅด] Implement counting in formats - like JSON, CSV, XML,...
*/
/**
* Split text into sentences
*
* @public exported from `@promptbook/utils`
*/
function splitIntoSentences(text) {
return text.split(/[.!?]+/).filter((sentence) => sentence.trim() !== '');
}
/**
* Counts number of sentences in the text
*
* @public exported from `@promptbook/utils`
*/
function countSentences(text) {
return splitIntoSentences(text).length;
}
/**
* TODO: [๐ฅด] Implement counting in formats - like JSON, CSV, XML,...
*/
const defaultDiacriticsRemovalMap = [
{
base: 'A',
letters: '\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F',
},
{ base: 'AA', letters: '\uA732' },
{ base: 'AE', letters: '\u00C6\u01FC\u01E2' },
{ base: 'AO', letters: '\uA734' },
{ base: 'AU', letters: '\uA736' },
{ base: 'AV', letters: '\uA738\uA73A' },
{ base: 'AY', letters: '\uA73C' },
{
base: 'B',
letters: '\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181',
},
{
base: 'C',
letters: '\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E',
},
{
base: 'D',
letters: '\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779\u00D0',
},
{ base: 'DZ', letters: '\u01F1\u01C4' },
{ base: 'Dz', letters: '\u01F2\u01C5' },
{
base: 'E',
letters: '\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E',
},
{ base: 'F', letters: '\u0046\u24BB\uFF26\u1E1E\u0191\uA77B' },
{
base: 'G',
letters: '\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E',
},
{
base: 'H',
letters: '\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D',
},
{
base: 'I',
letters: '\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197',
},
{ base: 'J', letters: '\u004A\u24BF\uFF2A\u0134\u0248' },
{
base: 'K',
letters: '\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2',
},
{
base: 'L',
letters: '\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780',
},
{ base: 'LJ', letters: '\u01C7' },
{ base: 'Lj', letters: '\u01C8' },
{ base: 'M', letters: '\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C' },
{
base: 'N',
letters: '\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4',
},
{ base: 'NJ', letters: '\u01CA' },
{ base: 'Nj', letters: '\u01CB' },
{
base: 'O',
letters: '\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C',
},
{ base: 'OI', letters: '\u01A2' },
{ base: 'OO', letters: '\uA74E' },
{ base: 'OU', letters: '\u0222' },
{ base: 'OE', letters: '\u008C\u0152' },
{ base: 'oe', letters: '\u009C\u0153' },
{
base: 'P',
letters: '\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754',
},
{ base: 'Q', letters: '\u0051\u24C6\uFF31\uA756\uA758\u024A' },
{
base: 'R',
letters: '\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782',
},
{
base: 'S',
letters: '\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784',
},
{
base: 'T',
letters: '\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786',
},
{ base: 'TZ', letters: '\uA728' },
{
base: 'U',
letters: '\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244',
},
{ base: 'V', letters: '\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245' },
{ base: 'VY', letters: '\uA760' },
{
base: 'W',
letters: '\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72',
},
{ base: 'X', letters: '\u0058\u24CD\uFF38\u1E8A\u1E8C' },
{
base: 'Y',
letters: '\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE',
},
{
base: 'Z',
letters: '\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762',
},
{
base: 'a',
letters: '\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250',
},
{ base: 'aa', letters: '\uA733' },
{ base: 'ae', letters: '\u00E6\u01FD\u01E3' },
{ base: 'ao', letters: '\uA735' },
{ base: 'au', letters: '\uA737' },
{ base: 'av', letters: '\uA739\uA73B' },
{ base: 'ay', letters: '\uA73D' },
{
base: 'b',
letters: '\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253',
},
{
base: 'c',
letters: '\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184',
},
{
base: 'd',
letters: '\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A',
},
{ base: 'dz', letters: '\u01F3\u01C6' },
{
base: 'e',
letters: '\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD',
},
{ base: 'f', letters: '\u0066\u24D5\uFF46\u1E1F\u0192\uA77C' },
{
base: 'g',
letters: '\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F',
},
{
base: 'h',
letters: '\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265',
},
{ base: 'hv', letters: '\u0195' },
{
base: 'i',
letters: '\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131',
},
{ base: 'j', letters: '\u006A\u24D9\uFF4A\u0135\u01F0\u0249' },
{
base: 'k',
letters: '\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3',
},
{
base: 'l',
letters: '\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747',
},
{ base: 'lj', letters: '\u01C9' },
{ base: 'm', letters: '\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F' },
{
base: 'n',
letters: '\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5',
},
{ base: 'nj', letters: '\u01CC' },
{
base: 'o',
letters: '\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275',
},
{ base: 'oi', letters: '\u01A3' },
{ base: 'ou', letters: '\u0223' },
{ base: 'oo', letters: '\uA74F' },
{
base: 'p',
letters: '\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755',
},
{ base: 'q', letters: '\u0071\u24E0\uFF51\u024B\uA757\uA759' },
{
base: 'r',
letters: '\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783',
},
{
base: 's',
letters: '\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B',
},
{
base: 't',
letters: '\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787',
},
{ base: 'tz', letters: '\uA729' },
{
base: 'u',
letters: '\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289',
},
{ base: 'v', letters: '\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C' },
{ base: 'vy', letters: '\uA761' },
{
base: 'w',
letters: '\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73',
},
{ base: 'x', letters: '\u0078\u24E7\uFF58\u1E8B\u1E8D' },
{
base: 'y',
letters: '\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF',
},
{
base: 'z',
letters: '\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763',
},
];
/**
* Map of letters from diacritic variant to diacritless variant
* Contains lowercase and uppercase separatelly
*
* > "รก" => "a"
* > "ฤ" => "e"
* > "ฤ" => "A"
* > ...
*
* @public exported from `@promptbook/utils`
*/
const DIACRITIC_VARIANTS_LETTERS = {};
// tslint:disable-next-line: prefer-for-of
for (let i = 0; i < defaultDiacriticsRemovalMap.length; i++) {
const letters = defaultDiacriticsRemovalMap[i].letters;
// tslint:disable-next-line: prefer-for-of
for (let j = 0; j < letters.length; j++) {
DIACRITIC_VARIANTS_LETTERS[letters[j]] = defaultDiacriticsRemovalMap[i].base;
}
}
// <- TODO: [๐] Put to maker function to save execution time if not needed
/*
@see https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* @@@
*
* @param input @@@
* @returns @@@
* @public exported from `@promptbook/utils`
*/
function removeDiacritics(input) {
/*eslint no-control-regex: "off"*/
return input.replace(/[^\u0000-\u007E]/g, (a) => {
return DIACRITIC_VARIANTS_LETTERS[a] || a;
});
}
/**
* TODO: [ะ] Variant for cyrillic (and in general non-latin) letters
*/
/**
* Counts number of words in the text
*
* @public exported from `@promptbook/utils`
*/
function countWords(text) {
text = text.replace(/[\p{Extended_Pictographic}]/gu, 'a');
text = removeDiacritics(text);
// Add spaces before uppercase letters preceded by lowercase letters (for camelCase)
text = text.replace(/([a-z])([A-Z])/g, '$1 $2');
return text.split(/[^a-zะฐ-ั0-9]+/i).filter((word) => word.length > 0).length;
}
/**
* TODO: [๐ฅด] Implement counting in formats - like JSON, CSV, XML,...
*/
/**
* Helper of usage compute
*
* @param content the content of prompt or response
* @returns part of UsageCounts
*
* @private internal utility of LlmExecutionTools
*/
function computeUsageCounts(content) {
return {
charactersCount: { value: countCharacters(content) },
wordsCount: { value: countWords(content) },
sentencesCount: { value: countSentences(content) },
linesCount: { value: countLines(content) },
paragraphsCount: { value: countParagraphs(content) },
pagesCount: { value: countPages(content) },
};
}
/**
* Make UncertainNumber
*
* @param value
*
* @private utility for initializating UncertainNumber
*/
function uncertainNumber(value) {
if (value === null || value === undefined || Number.isNaN(value)) {
return UNCERTAIN_ZERO_VALUE;
}
return { value };
}
/**
* Function computeUsage will create price per one token based on the string value found on openai page
*
* @private within the repository, used only as internal helper for `OPENAI_MODELS`
*/
function computeUsage(value) {
const [price, tokens] = value.split(' / ');
return parseFloat(price.replace('$', '')) / parseFloat(tokens.replace('M tokens', '')) / 1000000;
}
/**
* List of available OpenAI models with pricing
*
* Note: Done at 2024-05-20
*
* @see https://platform.openai.com/docs/models/
* @see https://openai.com/api/pricing/
* @public exported from `@promptbook/openai`
*/
const OPENAI_MODELS = exportJson({
name: 'OPENAI_MODELS',
value: [
/*/
{
modelTitle: 'dall-e-3',
modelName: 'dall-e-3',
},
/**/
/*/
{
modelTitle: 'whisper-1',
modelName: 'whisper-1',
},
/**/
/**/
{
modelVariant: 'COMPLETION',
modelTitle: 'davinci-002',
modelName: 'davinci-002',
pricing: {
prompt: computeUsage(`$2.00 / 1M tokens`),
output: computeUsage(`$2.00 / 1M tokens`), // <- not sure
},
},
/**/
/*/
{
modelTitle: 'dall-e-2',
modelName: 'dall-e-2',
},
/**/
/**/
{
modelVariant: 'CHAT',
modelTitle: 'gpt-3.5-turbo-16k',
modelName: 'gpt-3.5-turbo-16k',
pricing: {
prompt: computeUsage(`$3.00 / 1M tokens`),
output: computeUsage(`$4.00 / 1M tokens`),
},
},
/**/
/*/
{
modelTitle: 'tts-1-hd-1106',
modelName: 'tts-1-hd-1106',
},
/**/
/*/
{
modelTitle: 'tts-1-hd',
modelName: 'tts-1-hd',
},
/**/
/**/
{
modelVariant: 'CHAT',
modelTitle: 'gpt-4',
modelName: 'gpt-4',
pricing: {
prompt: computeUsage(`$30.00 / 1M tokens`),
output: computeUsage(`$60.00 / 1M tokens`),
},
},
/**/
/**/
{
modelVariant: 'CHAT',
modelTitle: 'gpt-4-32k',
modelName: 'gpt-4-32k',
pricing: {
prompt: computeUsage(`$60.00 / 1M tokens`),
output: computeUsage(`$120.00 / 1M tokens`),
},
},
/**/
/*/
{
modelVariant: 'CHAT',
modelTitle: 'gpt-4-0613',
modelName: 'gpt-4-0613',
pricing: {
prompt: computeUsage(` / 1M tokens`),
output: computeUsage(` / 1M tokens`),
},
},
/**/
/**/
{
modelVariant: 'CHAT',
modelTitle: 'gpt-4-turbo-2024-04-09',
modelName: 'gpt-4-turbo-2024-04-09',
pricing: {
prompt: computeUsage(`$10.00 / 1M tokens`),
output: computeUsage(`$30.00 / 1M tokens`),
},
},
/**/
/**/
{