UNPKG

@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
(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`), }, }, /**/ /**/ {