@promptbook/cli 0.88.0 → 0.89.0-11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/esm/index.es.js +2152 -1545
- package/esm/index.es.js.map +1 -1
- package/esm/typings/src/_packages/core.index.d.ts +18 -6
- package/esm/typings/src/_packages/remote-client.index.d.ts +6 -8
- package/esm/typings/src/_packages/remote-server.index.d.ts +6 -6
- package/esm/typings/src/_packages/types.index.d.ts +18 -10
- package/esm/typings/src/cli/cli-commands/login.d.ts +15 -0
- package/esm/typings/src/cli/common/$addGlobalOptionsToCommand.d.ts +7 -0
- package/esm/typings/src/cli/common/$provideLlmToolsForCli.d.ts +15 -0
- package/esm/typings/src/config.d.ts +15 -8
- package/esm/typings/src/errors/0-index.d.ts +6 -0
- package/esm/typings/src/errors/AuthenticationError.d.ts +9 -0
- package/esm/typings/src/errors/PromptbookFetchError.d.ts +9 -0
- package/esm/typings/src/execution/PipelineExecutorResult.d.ts +2 -2
- package/esm/typings/src/execution/PromptResult.d.ts +2 -2
- package/esm/typings/src/execution/{PromptResultUsage.d.ts → Usage.d.ts} +5 -5
- package/esm/typings/src/execution/utils/addUsage.d.ts +2 -2
- package/esm/typings/src/execution/utils/computeUsageCounts.d.ts +3 -3
- package/esm/typings/src/execution/utils/usage-constants.d.ts +77 -60
- package/esm/typings/src/execution/utils/usageToHuman.d.ts +5 -5
- package/esm/typings/src/execution/utils/usageToWorktime.d.ts +5 -5
- package/esm/typings/src/llm-providers/_common/register/$provideEnvFilename.d.ts +12 -0
- package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsConfigurationFromEnv.d.ts +2 -8
- package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsForTestingAndScriptsAndPlayground.d.ts +2 -0
- package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsForWizzardOrCli.d.ts +36 -1
- package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsFromEnv.d.ts +1 -0
- package/esm/typings/src/llm-providers/_common/utils/count-total-usage/LlmExecutionToolsWithTotalUsage.d.ts +9 -2
- package/esm/typings/src/llm-providers/_common/utils/count-total-usage/{countTotalUsage.d.ts → countUsage.d.ts} +1 -1
- package/esm/typings/src/llm-providers/_common/utils/count-total-usage/limitTotalUsage.d.ts +2 -2
- package/esm/typings/src/llm-providers/anthropic-claude/AnthropicClaudeExecutionToolsOptions.d.ts +1 -1
- package/esm/typings/src/llm-providers/anthropic-claude/computeAnthropicClaudeUsage.d.ts +2 -2
- package/esm/typings/src/llm-providers/anthropic-claude/register-configuration.d.ts +1 -1
- package/esm/typings/src/llm-providers/openai/OpenAiExecutionTools.d.ts +0 -9
- package/esm/typings/src/llm-providers/openai/computeOpenAiUsage.d.ts +2 -2
- package/esm/typings/src/pipeline/PipelineJson/PreparationJson.d.ts +2 -2
- package/esm/typings/src/playground/playground.d.ts +5 -0
- package/esm/typings/src/remote-server/RemoteServer.d.ts +23 -0
- package/esm/typings/src/remote-server/socket-types/_subtypes/{PromptbookServer_Identification.d.ts → Identification.d.ts} +5 -4
- package/esm/typings/src/remote-server/socket-types/listModels/PromptbookServer_ListModels_Request.d.ts +2 -2
- package/esm/typings/src/remote-server/socket-types/prepare/PromptbookServer_PreparePipeline_Request.d.ts +2 -2
- package/esm/typings/src/remote-server/socket-types/prompt/PromptbookServer_Prompt_Request.d.ts +2 -2
- package/esm/typings/src/remote-server/startRemoteServer.d.ts +2 -2
- package/esm/typings/src/remote-server/types/RemoteClientOptions.d.ts +4 -12
- package/esm/typings/src/remote-server/types/RemoteServerOptions.d.ts +88 -6
- package/esm/typings/src/scrapers/_common/utils/{scraperFetch.d.ts → promptbookFetch.d.ts} +2 -2
- package/esm/typings/src/storage/env-storage/$EnvStorage.d.ts +37 -0
- package/esm/typings/src/types/typeAliases.d.ts +8 -2
- package/esm/typings/src/utils/organization/TODO_narrow.d.ts +6 -0
- package/package.json +3 -1
- package/umd/index.umd.js +2169 -1562
- package/umd/index.umd.js.map +1 -1
package/umd/index.umd.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('commander'), require('spacetrim'), require('waitasecond'), require('path'), require('fs/promises'), require('crypto-js/enc-hex'), require('crypto-js/sha256'), require('crypto'), require('
|
|
3
|
-
typeof define === 'function' && define.amd ? define(['exports', 'colors', 'commander', 'spacetrim', 'waitasecond', 'path', 'fs/promises', 'crypto-js/enc-hex', 'crypto-js/sha256', 'crypto', '
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-cli"] = {}, global.colors, global.commander, global.spaceTrim, global.waitasecond, global.path, global.promises, global.hexEncoder, global.sha256, global.crypto, global.
|
|
5
|
-
})(this, (function (exports, colors, commander, spaceTrim, waitasecond, path, promises, hexEncoder, sha256, crypto,
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('commander'), require('spacetrim'), require('waitasecond'), require('prompts'), require('path'), require('fs/promises'), require('dotenv'), require('crypto-js/enc-hex'), require('crypto-js/sha256'), require('crypto'), require('socket.io-client'), require('rxjs'), require('child_process'), require('jszip'), require('prettier'), require('prettier/parser-html'), require('papaparse'), require('crypto-js'), require('mime-types'), require('glob-promise'), require('moment'), require('express'), require('http'), require('socket.io'), require('swagger-jsdoc'), require('swagger-ui-express'), require('@anthropic-ai/sdk'), require('@azure/openai'), require('openai'), require('@mozilla/readability'), require('jsdom'), require('showdown')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports', 'colors', 'commander', 'spacetrim', 'waitasecond', 'prompts', 'path', 'fs/promises', 'dotenv', 'crypto-js/enc-hex', 'crypto-js/sha256', 'crypto', 'socket.io-client', 'rxjs', 'child_process', 'jszip', 'prettier', 'prettier/parser-html', 'papaparse', 'crypto-js', 'mime-types', 'glob-promise', 'moment', 'express', 'http', 'socket.io', 'swagger-jsdoc', 'swagger-ui-express', '@anthropic-ai/sdk', '@azure/openai', 'openai', '@mozilla/readability', 'jsdom', 'showdown'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-cli"] = {}, global.colors, global.commander, global.spaceTrim, global.waitasecond, global.prompts, global.path, global.promises, global.dotenv, global.hexEncoder, global.sha256, global.crypto, global.socket_ioClient, global.rxjs, global.child_process, global.JSZip, global.prettier, global.parserHtml, global.papaparse, global.cryptoJs, global.mimeTypes, global.glob, global.moment, global.express, global.http, global.socket_io, global.swaggerJsdoc, global.swaggerUi, global.Anthropic, global.openai, global.OpenAI, global.readability, global.jsdom, global.showdown));
|
|
5
|
+
})(this, (function (exports, colors, commander, spaceTrim, waitasecond, prompts, path, promises, dotenv, hexEncoder, sha256, crypto, socket_ioClient, rxjs, child_process, JSZip, prettier, parserHtml, papaparse, cryptoJs, mimeTypes, glob, moment, express, http, socket_io, swaggerJsdoc, swaggerUi, Anthropic, openai, OpenAI, readability, jsdom, showdown) { 'use strict';
|
|
6
6
|
|
|
7
7
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
8
8
|
|
|
@@ -27,16 +27,18 @@
|
|
|
27
27
|
var colors__default = /*#__PURE__*/_interopDefaultLegacy(colors);
|
|
28
28
|
var commander__default = /*#__PURE__*/_interopDefaultLegacy(commander);
|
|
29
29
|
var spaceTrim__default = /*#__PURE__*/_interopDefaultLegacy(spaceTrim);
|
|
30
|
+
var prompts__default = /*#__PURE__*/_interopDefaultLegacy(prompts);
|
|
31
|
+
var dotenv__namespace = /*#__PURE__*/_interopNamespace(dotenv);
|
|
30
32
|
var hexEncoder__default = /*#__PURE__*/_interopDefaultLegacy(hexEncoder);
|
|
31
33
|
var sha256__default = /*#__PURE__*/_interopDefaultLegacy(sha256);
|
|
32
|
-
var dotenv__namespace = /*#__PURE__*/_interopNamespace(dotenv);
|
|
33
34
|
var JSZip__default = /*#__PURE__*/_interopDefaultLegacy(JSZip);
|
|
34
35
|
var parserHtml__default = /*#__PURE__*/_interopDefaultLegacy(parserHtml);
|
|
35
36
|
var glob__default = /*#__PURE__*/_interopDefaultLegacy(glob);
|
|
36
|
-
var prompts__default = /*#__PURE__*/_interopDefaultLegacy(prompts);
|
|
37
37
|
var moment__default = /*#__PURE__*/_interopDefaultLegacy(moment);
|
|
38
38
|
var express__default = /*#__PURE__*/_interopDefaultLegacy(express);
|
|
39
39
|
var http__default = /*#__PURE__*/_interopDefaultLegacy(http);
|
|
40
|
+
var swaggerJsdoc__default = /*#__PURE__*/_interopDefaultLegacy(swaggerJsdoc);
|
|
41
|
+
var swaggerUi__default = /*#__PURE__*/_interopDefaultLegacy(swaggerUi);
|
|
40
42
|
var Anthropic__default = /*#__PURE__*/_interopDefaultLegacy(Anthropic);
|
|
41
43
|
var OpenAI__default = /*#__PURE__*/_interopDefaultLegacy(OpenAI);
|
|
42
44
|
|
|
@@ -54,7 +56,7 @@
|
|
|
54
56
|
* @generated
|
|
55
57
|
* @see https://github.com/webgptorg/promptbook
|
|
56
58
|
*/
|
|
57
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.
|
|
59
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.89.0-11';
|
|
58
60
|
/**
|
|
59
61
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
60
62
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -223,6 +225,7 @@
|
|
|
223
225
|
*/
|
|
224
226
|
const DEFAULT_BOOKS_DIRNAME = './books';
|
|
225
227
|
// <- TODO: [🕝] Make also `BOOKS_DIRNAME_ALTERNATIVES`
|
|
228
|
+
// TODO: !!!!!! Just .promptbook dir, hardocode others
|
|
226
229
|
/**
|
|
227
230
|
* Where to store the temporary downloads
|
|
228
231
|
*
|
|
@@ -247,6 +250,21 @@
|
|
|
247
250
|
* @public exported from `@promptbook/core`
|
|
248
251
|
*/
|
|
249
252
|
const DEFAULT_SCRAPE_CACHE_DIRNAME = './.promptbook/scrape-cache';
|
|
253
|
+
/**
|
|
254
|
+
* Id of application for the CLI when using remote server
|
|
255
|
+
*
|
|
256
|
+
* @public exported from `@promptbook/core`
|
|
257
|
+
*/
|
|
258
|
+
const CLI_APP_ID = 'cli';
|
|
259
|
+
/*
|
|
260
|
+
TODO: [🌃]
|
|
261
|
+
/**
|
|
262
|
+
* Id of application for the wizzard when using remote server
|
|
263
|
+
*
|
|
264
|
+
* @public exported from `@promptbook/core`
|
|
265
|
+
* /
|
|
266
|
+
ex-port const WIZZARD_APP_ID: string_app_id = 'wizzard';
|
|
267
|
+
*/
|
|
250
268
|
/**
|
|
251
269
|
* The name of the builded pipeline collection made by CLI `ptbk make` and for lookup in `createCollectionFromDirectory`
|
|
252
270
|
*
|
|
@@ -267,13 +285,7 @@
|
|
|
267
285
|
*
|
|
268
286
|
* @public exported from `@promptbook/core`
|
|
269
287
|
*/
|
|
270
|
-
const
|
|
271
|
-
/**
|
|
272
|
-
* @@@
|
|
273
|
-
*
|
|
274
|
-
* @public exported from `@promptbook/core`
|
|
275
|
-
*/
|
|
276
|
-
const DEFAULT_REMOTE_URL_PATH = '/promptbook/socket.io';
|
|
288
|
+
const DEFAULT_REMOTE_SERVER_URL = 'https://api.pavolhejny.com/promptbook';
|
|
277
289
|
// <- TODO: [🧜♂️]
|
|
278
290
|
/**
|
|
279
291
|
* @@@
|
|
@@ -315,7 +327,7 @@
|
|
|
315
327
|
true);
|
|
316
328
|
/**
|
|
317
329
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
318
|
-
* TODO: [🧠][🧜♂️] Maybe join
|
|
330
|
+
* TODO: [🧠][🧜♂️] Maybe join remoteServerUrl and path into single value
|
|
319
331
|
*/
|
|
320
332
|
|
|
321
333
|
/**
|
|
@@ -493,7 +505,8 @@
|
|
|
493
505
|
helloCommand.alias('hi');
|
|
494
506
|
helloCommand.argument('[name]', 'Your name', 'Paul');
|
|
495
507
|
helloCommand.option('-g, --greeting <greeting>', `Greeting`, 'Hello');
|
|
496
|
-
helloCommand.action(handleActionErrors(async (name,
|
|
508
|
+
helloCommand.action(handleActionErrors(async (name, cliOptions) => {
|
|
509
|
+
const { greeting } = cliOptions;
|
|
497
510
|
console.info(colors__default["default"].cyan(`${greeting} ${name}`));
|
|
498
511
|
await waitasecond.forTime(1000);
|
|
499
512
|
console.info(colors__default["default"].rainbow(`Nice to meet you!`));
|
|
@@ -507,40 +520,27 @@
|
|
|
507
520
|
*/
|
|
508
521
|
|
|
509
522
|
/**
|
|
510
|
-
*
|
|
511
|
-
* No side effects.
|
|
512
|
-
*
|
|
513
|
-
* Note: It can be usefull suppressing eslint errors of unused variables
|
|
523
|
+
* This error type indicates that some part of the code is not implemented yet
|
|
514
524
|
*
|
|
515
|
-
* @
|
|
516
|
-
* @returns void
|
|
517
|
-
* @private within the repository
|
|
525
|
+
* @public exported from `@promptbook/core`
|
|
518
526
|
*/
|
|
519
|
-
|
|
520
|
-
|
|
527
|
+
class NotYetImplementedError extends Error {
|
|
528
|
+
constructor(message) {
|
|
529
|
+
super(spaceTrim.spaceTrim((block) => `
|
|
530
|
+
${block(message)}
|
|
521
531
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
532
|
+
Note: This feature is not implemented yet but it will be soon.
|
|
533
|
+
|
|
534
|
+
If you want speed up the implementation or just read more, look here:
|
|
535
|
+
https://github.com/webgptorg/promptbook
|
|
536
|
+
|
|
537
|
+
Or contact us on pavol@ptbk.io
|
|
538
|
+
|
|
539
|
+
`));
|
|
540
|
+
this.name = 'NotYetImplementedError';
|
|
541
|
+
Object.setPrototypeOf(this, NotYetImplementedError.prototype);
|
|
530
542
|
}
|
|
531
|
-
return {
|
|
532
|
-
stat: promises.stat,
|
|
533
|
-
access: promises.access,
|
|
534
|
-
constants: promises.constants,
|
|
535
|
-
readFile: promises.readFile,
|
|
536
|
-
writeFile: promises.writeFile,
|
|
537
|
-
readdir: promises.readdir,
|
|
538
|
-
mkdir: promises.mkdir,
|
|
539
|
-
};
|
|
540
543
|
}
|
|
541
|
-
/**
|
|
542
|
-
* Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
|
|
543
|
-
*/
|
|
544
544
|
|
|
545
545
|
/**
|
|
546
546
|
* Make error report URL for the given error
|
|
@@ -611,375 +611,362 @@
|
|
|
611
611
|
}
|
|
612
612
|
|
|
613
613
|
/**
|
|
614
|
-
*
|
|
614
|
+
* @@@
|
|
615
615
|
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
616
|
+
* Note: `$` is used to indicate that this function is not a pure function - it access global scope
|
|
617
|
+
*
|
|
618
|
+
* @private internal function of `$Register`
|
|
618
619
|
*/
|
|
619
|
-
function
|
|
620
|
-
|
|
621
|
-
const orderedValue = {
|
|
622
|
-
...(order === undefined ? {} : Object.fromEntries(order.map((key) => [key, undefined]))),
|
|
623
|
-
...value,
|
|
624
|
-
};
|
|
625
|
-
return orderedValue;
|
|
620
|
+
function $getGlobalScope() {
|
|
621
|
+
return Function('return this')();
|
|
626
622
|
}
|
|
627
623
|
|
|
628
624
|
/**
|
|
629
|
-
*
|
|
630
|
-
*
|
|
631
|
-
* Note: `$` is used to indicate that this function is not a pure function - it mutates given object
|
|
632
|
-
* Note: This function mutates the object and returns the original (but mutated-deep-freezed) object
|
|
625
|
+
* @@@
|
|
633
626
|
*
|
|
634
|
-
* @
|
|
627
|
+
* @param text @@@
|
|
628
|
+
* @returns @@@
|
|
629
|
+
* @example 'HELLO_WORLD'
|
|
630
|
+
* @example 'I_LOVE_PROMPTBOOK'
|
|
635
631
|
* @public exported from `@promptbook/utils`
|
|
636
632
|
*/
|
|
637
|
-
function
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
633
|
+
function normalizeTo_SCREAMING_CASE(text) {
|
|
634
|
+
let charType;
|
|
635
|
+
let lastCharType = 'OTHER';
|
|
636
|
+
let normalizedName = '';
|
|
637
|
+
for (const char of text) {
|
|
638
|
+
let normalizedChar;
|
|
639
|
+
if (/^[a-z]$/.test(char)) {
|
|
640
|
+
charType = 'LOWERCASE';
|
|
641
|
+
normalizedChar = char.toUpperCase();
|
|
642
|
+
}
|
|
643
|
+
else if (/^[A-Z]$/.test(char)) {
|
|
644
|
+
charType = 'UPPERCASE';
|
|
645
|
+
normalizedChar = char;
|
|
646
|
+
}
|
|
647
|
+
else if (/^[0-9]$/.test(char)) {
|
|
648
|
+
charType = 'NUMBER';
|
|
649
|
+
normalizedChar = char;
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
charType = 'OTHER';
|
|
653
|
+
normalizedChar = '_';
|
|
654
|
+
}
|
|
655
|
+
if (charType !== lastCharType &&
|
|
656
|
+
!(lastCharType === 'UPPERCASE' && charType === 'LOWERCASE') &&
|
|
657
|
+
!(lastCharType === 'NUMBER') &&
|
|
658
|
+
!(charType === 'NUMBER')) {
|
|
659
|
+
normalizedName += '_';
|
|
646
660
|
}
|
|
661
|
+
normalizedName += normalizedChar;
|
|
662
|
+
lastCharType = charType;
|
|
647
663
|
}
|
|
648
|
-
|
|
649
|
-
|
|
664
|
+
normalizedName = normalizedName.replace(/_+/g, '_');
|
|
665
|
+
normalizedName = normalizedName.replace(/_?\/_?/g, '/');
|
|
666
|
+
normalizedName = normalizedName.replace(/^_/, '');
|
|
667
|
+
normalizedName = normalizedName.replace(/_$/, '');
|
|
668
|
+
return normalizedName;
|
|
650
669
|
}
|
|
651
670
|
/**
|
|
652
|
-
* TODO:
|
|
671
|
+
* TODO: Tests
|
|
672
|
+
* > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'Moje tabule' })).toEqual('/VtG7sR9rRJqwNEdM2/Moje tabule');
|
|
673
|
+
* > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'ěščřžžýáíúů' })).toEqual('/VtG7sR9rRJqwNEdM2/escrzyaieuu');
|
|
674
|
+
* > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj');
|
|
675
|
+
* > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj_ahojAhoj ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj-ahoj-ahoj-ahoj');
|
|
676
|
+
* TODO: [🌺] Use some intermediate util splitWords
|
|
653
677
|
*/
|
|
654
678
|
|
|
655
679
|
/**
|
|
656
|
-
*
|
|
657
|
-
* If not, throws an UnexpectedError with a rich error message and tracking
|
|
658
|
-
*
|
|
659
|
-
* - Almost all primitives are serializable BUT:
|
|
660
|
-
* - `undefined` is not serializable
|
|
661
|
-
* - `NaN` is not serializable
|
|
662
|
-
* - Objects and arrays are serializable if all their properties are serializable
|
|
663
|
-
* - Functions are not serializable
|
|
664
|
-
* - Circular references are not serializable
|
|
665
|
-
* - `Date` objects are not serializable
|
|
666
|
-
* - `Map` and `Set` objects are not serializable
|
|
667
|
-
* - `RegExp` objects are not serializable
|
|
668
|
-
* - `Error` objects are not serializable
|
|
669
|
-
* - `Symbol` objects are not serializable
|
|
670
|
-
* - And much more...
|
|
680
|
+
* @@@
|
|
671
681
|
*
|
|
672
|
-
* @
|
|
682
|
+
* @param text @@@
|
|
683
|
+
* @returns @@@
|
|
684
|
+
* @example 'hello_world'
|
|
685
|
+
* @example 'i_love_promptbook'
|
|
673
686
|
* @public exported from `@promptbook/utils`
|
|
674
687
|
*/
|
|
675
|
-
function
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
throw new UnexpectedError(`${name} is undefined`);
|
|
679
|
-
}
|
|
680
|
-
else if (value === null) {
|
|
681
|
-
return;
|
|
682
|
-
}
|
|
683
|
-
else if (typeof value === 'boolean') {
|
|
684
|
-
return;
|
|
685
|
-
}
|
|
686
|
-
else if (typeof value === 'number' && !isNaN(value)) {
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
|
-
else if (typeof value === 'string') {
|
|
690
|
-
return;
|
|
691
|
-
}
|
|
692
|
-
else if (typeof value === 'symbol') {
|
|
693
|
-
throw new UnexpectedError(`${name} is symbol`);
|
|
694
|
-
}
|
|
695
|
-
else if (typeof value === 'function') {
|
|
696
|
-
throw new UnexpectedError(`${name} is function`);
|
|
697
|
-
}
|
|
698
|
-
else if (typeof value === 'object' && Array.isArray(value)) {
|
|
699
|
-
for (let i = 0; i < value.length; i++) {
|
|
700
|
-
checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
else if (typeof value === 'object') {
|
|
704
|
-
if (value instanceof Date) {
|
|
705
|
-
throw new UnexpectedError(spaceTrim__default["default"]((block) => `
|
|
706
|
-
\`${name}\` is Date
|
|
707
|
-
|
|
708
|
-
Use \`string_date_iso8601\` instead
|
|
688
|
+
function normalizeTo_snake_case(text) {
|
|
689
|
+
return normalizeTo_SCREAMING_CASE(text).toLowerCase();
|
|
690
|
+
}
|
|
709
691
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
692
|
+
/**
|
|
693
|
+
* Register is @@@
|
|
694
|
+
*
|
|
695
|
+
* Note: `$` is used to indicate that this function is not a pure function - it accesses and adds variables in global scope.
|
|
696
|
+
*
|
|
697
|
+
* @private internal utility, exported are only signleton instances of this class
|
|
698
|
+
*/
|
|
699
|
+
class $Register {
|
|
700
|
+
constructor(registerName) {
|
|
701
|
+
this.registerName = registerName;
|
|
702
|
+
const storageName = `_promptbook_${normalizeTo_snake_case(registerName)}`;
|
|
703
|
+
const globalScope = $getGlobalScope();
|
|
704
|
+
if (globalScope[storageName] === undefined) {
|
|
705
|
+
globalScope[storageName] = [];
|
|
713
706
|
}
|
|
714
|
-
else if (
|
|
715
|
-
throw new UnexpectedError(
|
|
707
|
+
else if (!Array.isArray(globalScope[storageName])) {
|
|
708
|
+
throw new UnexpectedError(`Expected (global) ${storageName} to be an array, but got ${typeof globalScope[storageName]}`);
|
|
716
709
|
}
|
|
717
|
-
|
|
718
|
-
|
|
710
|
+
this.storage = globalScope[storageName];
|
|
711
|
+
}
|
|
712
|
+
list() {
|
|
713
|
+
// <- TODO: ReadonlyDeep<ReadonlyArray<TRegistered>>
|
|
714
|
+
return this.storage;
|
|
715
|
+
}
|
|
716
|
+
register(registered) {
|
|
717
|
+
const { packageName, className } = registered;
|
|
718
|
+
const existingRegistrationIndex = this.storage.findIndex((item) => item.packageName === packageName && item.className === className);
|
|
719
|
+
const existingRegistration = this.storage[existingRegistrationIndex];
|
|
720
|
+
if (!existingRegistration) {
|
|
721
|
+
this.storage.push(registered);
|
|
719
722
|
}
|
|
720
|
-
else
|
|
721
|
-
|
|
723
|
+
else {
|
|
724
|
+
this.storage[existingRegistrationIndex] = registered;
|
|
722
725
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
if (subValue === undefined) {
|
|
737
|
-
// Note: undefined in object is serializable - it is just omited
|
|
738
|
-
continue;
|
|
739
|
-
}
|
|
740
|
-
checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
|
|
741
|
-
}
|
|
742
|
-
try {
|
|
743
|
-
JSON.stringify(value); // <- TODO: [0]
|
|
744
|
-
}
|
|
745
|
-
catch (error) {
|
|
746
|
-
if (!(error instanceof Error)) {
|
|
747
|
-
throw error;
|
|
748
|
-
}
|
|
749
|
-
throw new UnexpectedError(spaceTrim__default["default"]((block) => `
|
|
750
|
-
\`${name}\` is not serializable
|
|
751
|
-
|
|
752
|
-
${block(error.stack || error.message)}
|
|
753
|
-
|
|
754
|
-
Additional message for \`${name}\`:
|
|
755
|
-
${block(message || '(nothing)')}
|
|
756
|
-
`));
|
|
757
|
-
}
|
|
758
|
-
/*
|
|
759
|
-
TODO: [0] Is there some more elegant way to check circular references?
|
|
760
|
-
const seen = new Set();
|
|
761
|
-
const stack = [{ value }];
|
|
762
|
-
while (stack.length > 0) {
|
|
763
|
-
const { value } = stack.pop()!;
|
|
764
|
-
if (typeof value === 'object' && value !== null) {
|
|
765
|
-
if (seen.has(value)) {
|
|
766
|
-
throw new UnexpectedError(`${name} has circular reference`);
|
|
767
|
-
}
|
|
768
|
-
seen.add(value);
|
|
769
|
-
if (Array.isArray(value)) {
|
|
770
|
-
stack.push(...value.map((value) => ({ value })));
|
|
771
|
-
} else {
|
|
772
|
-
stack.push(...Object.values(value).map((value) => ({ value })));
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
*/
|
|
777
|
-
return;
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
else {
|
|
781
|
-
throw new UnexpectedError(spaceTrim__default["default"]((block) => `
|
|
782
|
-
\`${name}\` is unknown type
|
|
783
|
-
|
|
784
|
-
Additional message for \`${name}\`:
|
|
785
|
-
${block(message || '(nothing)')}
|
|
786
|
-
`));
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
/**
|
|
790
|
-
* TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
|
|
791
|
-
* TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
|
|
792
|
-
* Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
|
|
793
|
-
*/
|
|
726
|
+
return {
|
|
727
|
+
registerName: this.registerName,
|
|
728
|
+
packageName,
|
|
729
|
+
className,
|
|
730
|
+
get isDestroyed() {
|
|
731
|
+
return false;
|
|
732
|
+
},
|
|
733
|
+
destroy() {
|
|
734
|
+
throw new NotYetImplementedError(`Registration to ${this.registerName} is permanent in this version of Promptbook`);
|
|
735
|
+
},
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
}
|
|
794
739
|
|
|
795
740
|
/**
|
|
796
741
|
* @@@
|
|
797
742
|
*
|
|
798
|
-
*
|
|
799
|
-
|
|
800
|
-
function deepClone(objectValue) {
|
|
801
|
-
return JSON.parse(JSON.stringify(objectValue));
|
|
802
|
-
/*
|
|
803
|
-
TODO: [🧠] Is there a better implementation?
|
|
804
|
-
> const propertyNames = Object.getOwnPropertyNames(objectValue);
|
|
805
|
-
> for (const propertyName of propertyNames) {
|
|
806
|
-
> const value = (objectValue as really_any)[propertyName];
|
|
807
|
-
> if (value && typeof value === 'object') {
|
|
808
|
-
> deepClone(value);
|
|
809
|
-
> }
|
|
810
|
-
> }
|
|
811
|
-
> return Object.assign({}, objectValue);
|
|
812
|
-
*/
|
|
813
|
-
}
|
|
814
|
-
/**
|
|
815
|
-
* TODO: [🧠] Is there a way how to meaningfully test this utility
|
|
816
|
-
*/
|
|
817
|
-
|
|
818
|
-
/**
|
|
819
|
-
* Utility to export a JSON object from a function
|
|
820
|
-
*
|
|
821
|
-
* 1) Checks if the value is serializable as JSON
|
|
822
|
-
* 2) Makes a deep clone of the object
|
|
823
|
-
* 2) Orders the object properties
|
|
824
|
-
* 2) Deeply freezes the cloned object
|
|
825
|
-
*
|
|
826
|
-
* Note: This function does not mutates the given object
|
|
827
|
-
*
|
|
828
|
-
* @returns The same type of object as the input but read-only and re-ordered
|
|
829
|
-
* @public exported from `@promptbook/utils`
|
|
830
|
-
*/
|
|
831
|
-
function exportJson(options) {
|
|
832
|
-
const { name, value, order, message } = options;
|
|
833
|
-
checkSerializableAsJson({ name, value, message });
|
|
834
|
-
const orderedValue =
|
|
835
|
-
// TODO: Fix error "Type instantiation is excessively deep and possibly infinite."
|
|
836
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
837
|
-
// @ts-ignore
|
|
838
|
-
order === undefined
|
|
839
|
-
? deepClone(value)
|
|
840
|
-
: orderJson({
|
|
841
|
-
value: value,
|
|
842
|
-
// <- Note: checkSerializableAsJson asserts that the value is serializable as JSON
|
|
843
|
-
order: order,
|
|
844
|
-
});
|
|
845
|
-
$deepFreeze(orderedValue);
|
|
846
|
-
return orderedValue;
|
|
847
|
-
}
|
|
848
|
-
/**
|
|
849
|
-
* TODO: [🧠] Is there a way how to meaningfully test this utility
|
|
850
|
-
*/
|
|
851
|
-
|
|
852
|
-
/**
|
|
853
|
-
* Order of keys in the pipeline JSON
|
|
854
|
-
*
|
|
743
|
+
* Note: `$` is used to indicate that this interacts with the global scope
|
|
744
|
+
* @singleton Only one instance of each register is created per build, but thare can be more @@@
|
|
855
745
|
* @public exported from `@promptbook/core`
|
|
856
746
|
*/
|
|
857
|
-
const
|
|
858
|
-
// Note: [🍙] In this order will be pipeline serialized
|
|
859
|
-
'title',
|
|
860
|
-
'pipelineUrl',
|
|
861
|
-
'bookVersion',
|
|
862
|
-
'description',
|
|
863
|
-
'formfactorName',
|
|
864
|
-
'parameters',
|
|
865
|
-
'tasks',
|
|
866
|
-
'personas',
|
|
867
|
-
'preparations',
|
|
868
|
-
'knowledgeSources',
|
|
869
|
-
'knowledgePieces',
|
|
870
|
-
'sources', // <- TODO: [🧠] Where should the `sources` be
|
|
871
|
-
];
|
|
872
|
-
/**
|
|
873
|
-
* Nonce which is used for replacing things in strings
|
|
874
|
-
*
|
|
875
|
-
* @private within the repository
|
|
876
|
-
*/
|
|
877
|
-
const REPLACING_NONCE = 'ptbkauk42kV2dzao34faw7FudQUHYPtW';
|
|
747
|
+
const $llmToolsMetadataRegister = new $Register('llm_tools_metadata');
|
|
878
748
|
/**
|
|
879
|
-
*
|
|
880
|
-
*
|
|
881
|
-
* @private within the repository
|
|
749
|
+
* TODO: [®] DRY Register logic
|
|
882
750
|
*/
|
|
883
|
-
|
|
751
|
+
|
|
884
752
|
/**
|
|
885
753
|
* @@@
|
|
886
754
|
*
|
|
887
|
-
*
|
|
888
|
-
|
|
889
|
-
const RESERVED_PARAMETER_RESTRICTED = 'RESTRICTED-' + REPLACING_NONCE;
|
|
890
|
-
/**
|
|
891
|
-
* The names of the parameters that are reserved for special purposes
|
|
892
|
-
*
|
|
755
|
+
* Note: `$` is used to indicate that this interacts with the global scope
|
|
756
|
+
* @singleton Only one instance of each register is created per build, but thare can be more @@@
|
|
893
757
|
* @public exported from `@promptbook/core`
|
|
894
758
|
*/
|
|
895
|
-
const
|
|
896
|
-
name: 'RESERVED_PARAMETER_NAMES',
|
|
897
|
-
message: `The names of the parameters that are reserved for special purposes`,
|
|
898
|
-
value: [
|
|
899
|
-
'content',
|
|
900
|
-
'context',
|
|
901
|
-
'knowledge',
|
|
902
|
-
'examples',
|
|
903
|
-
'modelName',
|
|
904
|
-
'currentDate',
|
|
905
|
-
// <- TODO: list here all command names
|
|
906
|
-
// <- TODO: Add more like 'date', 'modelName',...
|
|
907
|
-
// <- TODO: Add [emoji] + instructions ACRY when adding new reserved parameter
|
|
908
|
-
],
|
|
909
|
-
});
|
|
759
|
+
const $llmToolsRegister = new $Register('llm_execution_tools_constructors');
|
|
910
760
|
/**
|
|
911
|
-
*
|
|
761
|
+
* TODO: [®] DRY Register logic
|
|
912
762
|
*/
|
|
913
763
|
|
|
914
|
-
// <- TODO: Auto convert to type `import { ... } from 'type-fest';`
|
|
915
764
|
/**
|
|
916
|
-
*
|
|
765
|
+
* Path to the `.env` file which was used to configure LLM tools
|
|
917
766
|
*
|
|
918
|
-
*
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
*
|
|
923
|
-
* - Circular references are not serializable
|
|
924
|
-
* - `Date` objects are not serializable
|
|
925
|
-
* - `Map` and `Set` objects are not serializable
|
|
926
|
-
* - `RegExp` objects are not serializable
|
|
927
|
-
* - `Error` objects are not serializable
|
|
928
|
-
* - `Symbol` objects are not serializable
|
|
929
|
-
* - And much more...
|
|
767
|
+
* Note: `$` is used to indicate that this variable is changed by side effect in `$provideLlmToolsConfigurationFromEnv` through `$setUsedEnvFilename`
|
|
768
|
+
*/
|
|
769
|
+
let $usedEnvFilename = null;
|
|
770
|
+
/**
|
|
771
|
+
* Pass the `.env` file which was used to configure LLM tools
|
|
930
772
|
*
|
|
773
|
+
* Note: `$` is used to indicate that this variable is making side effect
|
|
931
774
|
*
|
|
932
|
-
* @
|
|
775
|
+
* @private internal log of `$provideLlmToolsConfigurationFromEnv` and `$registeredLlmToolsMessage`
|
|
933
776
|
*/
|
|
934
|
-
function
|
|
935
|
-
|
|
936
|
-
checkSerializableAsJson({ value });
|
|
937
|
-
return true;
|
|
938
|
-
}
|
|
939
|
-
catch (error) {
|
|
940
|
-
return false;
|
|
941
|
-
}
|
|
777
|
+
function $setUsedEnvFilename(filepath) {
|
|
778
|
+
$usedEnvFilename = filepath;
|
|
942
779
|
}
|
|
943
780
|
/**
|
|
944
|
-
*
|
|
945
|
-
* TODO: [🧠][💺] Can be done this on type-level?
|
|
946
|
-
*/
|
|
947
|
-
|
|
948
|
-
/**
|
|
949
|
-
* Stringify the PipelineJson with proper formatting
|
|
781
|
+
* Creates a message with all registered LLM tools
|
|
950
782
|
*
|
|
951
|
-
* Note:
|
|
952
|
-
* Note: In contrast to JSON.stringify, this function ensures that **embedding index** is on single line
|
|
783
|
+
* Note: This function is used to create a (error) message when there is no constructor for some LLM provider
|
|
953
784
|
*
|
|
954
|
-
* @
|
|
785
|
+
* @private internal function of `createLlmToolsFromConfiguration` and `$provideLlmToolsFromEnv`
|
|
955
786
|
*/
|
|
956
|
-
function
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
There can be multiple reasons:
|
|
962
|
-
1) The pipeline contains circular references
|
|
963
|
-
2) It is not a valid PipelineJson
|
|
964
|
-
`));
|
|
787
|
+
function $registeredLlmToolsMessage() {
|
|
788
|
+
let env;
|
|
789
|
+
if ($isRunningInNode()) {
|
|
790
|
+
env = process.env;
|
|
791
|
+
// <- TODO: [⚛] Some DRY way how to get to `process.env` and pass it into functions - ACRY search for `env`
|
|
965
792
|
}
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
pipelineJsonStringified = pipelineJsonStringified.replace(/(-?0\.\d+),[\n\s]+(-?0\.\d+)/gms, `$1${REPLACING_NONCE}$2`);
|
|
793
|
+
else {
|
|
794
|
+
env = {};
|
|
969
795
|
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
796
|
+
/**
|
|
797
|
+
* Mixes registered LLM tools from $llmToolsMetadataRegister and $llmToolsRegister
|
|
798
|
+
*/
|
|
799
|
+
const all = [];
|
|
800
|
+
for (const { title, packageName, className, envVariables } of $llmToolsMetadataRegister.list()) {
|
|
801
|
+
if (all.some((item) => item.packageName === packageName && item.className === className)) {
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
all.push({ title, packageName, className, envVariables });
|
|
805
|
+
}
|
|
806
|
+
for (const { packageName, className } of $llmToolsRegister.list()) {
|
|
807
|
+
if (all.some((item) => item.packageName === packageName && item.className === className)) {
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
all.push({ packageName, className });
|
|
811
|
+
}
|
|
812
|
+
const metadata = all.map((metadata) => {
|
|
813
|
+
var _a, _b;
|
|
814
|
+
const isMetadataAviailable = $llmToolsMetadataRegister
|
|
815
|
+
.list()
|
|
816
|
+
.find(({ packageName, className }) => metadata.packageName === packageName && metadata.className === className);
|
|
817
|
+
const isInstalled = $llmToolsRegister
|
|
818
|
+
.list()
|
|
819
|
+
.find(({ packageName, className }) => metadata.packageName === packageName && metadata.className === className);
|
|
820
|
+
const isFullyConfigured = ((_a = metadata.envVariables) === null || _a === void 0 ? void 0 : _a.every((envVariableName) => env[envVariableName] !== undefined)) || false;
|
|
821
|
+
const isPartiallyConfigured = ((_b = metadata.envVariables) === null || _b === void 0 ? void 0 : _b.some((envVariableName) => env[envVariableName] !== undefined)) || false;
|
|
822
|
+
// <- Note: [🗨]
|
|
823
|
+
return { ...metadata, isMetadataAviailable, isInstalled, isFullyConfigured, isPartiallyConfigured };
|
|
824
|
+
});
|
|
825
|
+
const usedEnvMessage = $usedEnvFilename === null ? `Unknown \`.env\` file` : `Used \`.env\` file:\n${$usedEnvFilename}`;
|
|
826
|
+
if (metadata.length === 0) {
|
|
827
|
+
return spaceTrim__default["default"]((block) => `
|
|
828
|
+
No LLM providers are available.
|
|
829
|
+
|
|
830
|
+
${block(usedEnvMessage)}
|
|
831
|
+
`);
|
|
832
|
+
}
|
|
833
|
+
return spaceTrim__default["default"]((block) => `
|
|
834
|
+
|
|
835
|
+
${block(usedEnvMessage)}
|
|
836
|
+
|
|
837
|
+
Relevant environment variables:
|
|
838
|
+
${block(Object.keys(env)
|
|
839
|
+
.filter((envVariableName) => metadata.some(({ envVariables }) => envVariables === null || envVariables === void 0 ? void 0 : envVariables.includes(envVariableName)))
|
|
840
|
+
.map((envVariableName) => `- \`${envVariableName}\``)
|
|
841
|
+
.join('\n'))}
|
|
842
|
+
|
|
843
|
+
Available LLM providers are:
|
|
844
|
+
${block(metadata
|
|
845
|
+
.map(({ title, packageName, className, envVariables, isMetadataAviailable, isInstalled, isFullyConfigured, isPartiallyConfigured, }, i) => {
|
|
846
|
+
const morePieces = [];
|
|
847
|
+
if (just(false)) ;
|
|
848
|
+
else if (!isMetadataAviailable && !isInstalled) {
|
|
849
|
+
// TODO: [�][�] Maybe do allow to do auto-install if package not registered and not found
|
|
850
|
+
morePieces.push(`Not installed and no metadata, looks like a unexpected behavior`);
|
|
851
|
+
}
|
|
852
|
+
else if (isMetadataAviailable && !isInstalled) {
|
|
853
|
+
// TODO: [�][�]
|
|
854
|
+
morePieces.push(`Not installed`);
|
|
855
|
+
}
|
|
856
|
+
else if (!isMetadataAviailable && isInstalled) {
|
|
857
|
+
morePieces.push(`No metadata but installed, looks like a unexpected behavior`);
|
|
858
|
+
}
|
|
859
|
+
else if (isMetadataAviailable && isInstalled) {
|
|
860
|
+
morePieces.push(`Installed`);
|
|
861
|
+
}
|
|
862
|
+
else {
|
|
863
|
+
morePieces.push(`unknown state, looks like a unexpected behavior`);
|
|
864
|
+
} /* not else */
|
|
865
|
+
if (isFullyConfigured) {
|
|
866
|
+
morePieces.push(`Configured`);
|
|
867
|
+
}
|
|
868
|
+
else if (isPartiallyConfigured) {
|
|
869
|
+
morePieces.push(`Partially confugured, missing ${envVariables === null || envVariables === void 0 ? void 0 : envVariables.filter((envVariable) => env[envVariable] === undefined).join(' + ')}`);
|
|
870
|
+
}
|
|
871
|
+
else {
|
|
872
|
+
if (envVariables !== null) {
|
|
873
|
+
morePieces.push(`Not configured, to configure set env ${envVariables === null || envVariables === void 0 ? void 0 : envVariables.join(' + ')}`);
|
|
874
|
+
}
|
|
875
|
+
else {
|
|
876
|
+
morePieces.push(`Not configured`); // <- Note: Can not be configured via environment variables
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
let providerMessage = spaceTrim__default["default"](`
|
|
880
|
+
${i + 1}) **${title}** \`${className}\` from \`${packageName}\`
|
|
881
|
+
${morePieces.join('; ')}
|
|
882
|
+
`);
|
|
883
|
+
if ($isRunningInNode) {
|
|
884
|
+
if (isInstalled && isFullyConfigured) {
|
|
885
|
+
providerMessage = colors__default["default"].green(providerMessage);
|
|
886
|
+
}
|
|
887
|
+
else if (isInstalled && isPartiallyConfigured) {
|
|
888
|
+
providerMessage = colors__default["default"].yellow(providerMessage);
|
|
889
|
+
}
|
|
890
|
+
else {
|
|
891
|
+
providerMessage = colors__default["default"].gray(providerMessage);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return providerMessage;
|
|
895
|
+
})
|
|
896
|
+
.join('\n'))}
|
|
897
|
+
`);
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* TODO: [®] DRY Register logic
|
|
901
|
+
* TODO: [🧠][⚛] Maybe pass env as argument
|
|
902
|
+
*/
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* Just says that the variable is not used but should be kept
|
|
906
|
+
* No side effects.
|
|
907
|
+
*
|
|
908
|
+
* Note: It can be usefull for:
|
|
909
|
+
*
|
|
910
|
+
* 1) Suppressing eager optimization of unused imports
|
|
911
|
+
* 2) Suppressing eslint errors of unused variables in the tests
|
|
912
|
+
* 3) Keeping the type of the variable for type testing
|
|
913
|
+
*
|
|
914
|
+
* @param value any values
|
|
915
|
+
* @returns void
|
|
916
|
+
* @private within the repository
|
|
917
|
+
*/
|
|
918
|
+
function keepUnused(...valuesToKeep) {
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* Just says that the variable is not used directlys but should be kept because the existence of the variable is important
|
|
923
|
+
*
|
|
924
|
+
* @param value any values
|
|
925
|
+
* @returns void
|
|
926
|
+
* @private within the repository
|
|
927
|
+
*/
|
|
928
|
+
function $sideEffect(...sideEffectSubjects) {
|
|
929
|
+
keepUnused(...sideEffectSubjects);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* Just marks a place of place where should be something implemented
|
|
934
|
+
* No side effects.
|
|
935
|
+
*
|
|
936
|
+
* Note: It can be usefull suppressing eslint errors of unused variables
|
|
937
|
+
*
|
|
938
|
+
* @param value any values
|
|
939
|
+
* @returns void
|
|
940
|
+
* @private within the repository
|
|
941
|
+
*/
|
|
942
|
+
function TODO_USE(...value) {
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/**
|
|
946
|
+
* @@@
|
|
947
|
+
*
|
|
948
|
+
* @public exported from `@promptbook/node`
|
|
949
|
+
*/
|
|
950
|
+
function $provideFilesystemForNode(options) {
|
|
951
|
+
if (!$isRunningInNode()) {
|
|
952
|
+
throw new EnvironmentMismatchError('Function `$provideFilesystemForNode` works only in Node.js environment');
|
|
953
|
+
}
|
|
954
|
+
return {
|
|
955
|
+
stat: promises.stat,
|
|
956
|
+
access: promises.access,
|
|
957
|
+
constants: promises.constants,
|
|
958
|
+
readFile: promises.readFile,
|
|
959
|
+
writeFile: promises.writeFile,
|
|
960
|
+
readdir: promises.readdir,
|
|
961
|
+
mkdir: promises.mkdir,
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
|
|
966
|
+
*/
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Checks if the file exists
|
|
983
970
|
*
|
|
984
971
|
* @private within the repository
|
|
985
972
|
*/
|
|
@@ -1004,99 +991,607 @@
|
|
|
1004
991
|
*/
|
|
1005
992
|
|
|
1006
993
|
/**
|
|
1007
|
-
*
|
|
1008
|
-
*
|
|
1009
|
-
* @param text with emojis
|
|
1010
|
-
* @returns text without emojis
|
|
1011
|
-
* @public exported from `@promptbook/utils`
|
|
1012
|
-
*/
|
|
1013
|
-
function removeEmojis(text) {
|
|
1014
|
-
// Replace emojis (and also ZWJ sequence) with hyphens
|
|
1015
|
-
text = text.replace(/(\p{Extended_Pictographic})\p{Modifier_Symbol}/gu, '$1');
|
|
1016
|
-
text = text.replace(/(\p{Extended_Pictographic})[\u{FE00}-\u{FE0F}]/gu, '$1');
|
|
1017
|
-
text = text.replace(/(\p{Extended_Pictographic})(\u{200D}\p{Extended_Pictographic})*/gu, '$1');
|
|
1018
|
-
text = text.replace(/\p{Extended_Pictographic}/gu, '');
|
|
1019
|
-
return text;
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
/**
|
|
1023
|
-
* Tests if given string is valid URL.
|
|
994
|
+
* Determines if the given path is a root path.
|
|
1024
995
|
*
|
|
1025
996
|
* Note: This does not check if the file exists only if the path is valid
|
|
1026
997
|
* @public exported from `@promptbook/utils`
|
|
1027
998
|
*/
|
|
1028
|
-
function
|
|
1029
|
-
if (
|
|
1030
|
-
return false;
|
|
1031
|
-
}
|
|
1032
|
-
if (filename.split('\n').length > 1) {
|
|
1033
|
-
return false;
|
|
1034
|
-
}
|
|
1035
|
-
if (filename.split(' ').length >
|
|
1036
|
-
5 /* <- TODO: [🧠][🈷] Make some better non-arbitrary way how to distinct filenames from informational texts */) {
|
|
1037
|
-
return false;
|
|
1038
|
-
}
|
|
1039
|
-
const filenameSlashes = filename.split('\\').join('/');
|
|
1040
|
-
// Absolute Unix path: /hello.txt
|
|
1041
|
-
if (/^(\/)/i.test(filenameSlashes)) {
|
|
1042
|
-
// console.log(filename, 'Absolute Unix path: /hello.txt');
|
|
1043
|
-
return true;
|
|
1044
|
-
}
|
|
1045
|
-
// Absolute Windows path: /hello.txt
|
|
1046
|
-
if (/^([A-Z]{1,2}:\/?)\//i.test(filenameSlashes)) {
|
|
1047
|
-
// console.log(filename, 'Absolute Windows path: /hello.txt');
|
|
1048
|
-
return true;
|
|
1049
|
-
}
|
|
1050
|
-
// Relative path: ./hello.txt
|
|
1051
|
-
if (/^(\.\.?\/)+/i.test(filenameSlashes)) {
|
|
1052
|
-
// console.log(filename, 'Relative path: ./hello.txt');
|
|
1053
|
-
return true;
|
|
1054
|
-
}
|
|
1055
|
-
// Allow paths like foo/hello
|
|
1056
|
-
if (/^[^/]+\/[^/]+/i.test(filenameSlashes)) {
|
|
1057
|
-
// console.log(filename, 'Allow paths like foo/hello');
|
|
999
|
+
function isRootPath(value) {
|
|
1000
|
+
if (value === '/') {
|
|
1058
1001
|
return true;
|
|
1059
1002
|
}
|
|
1060
|
-
|
|
1061
|
-
if (/^[^/]+\.[^/]+$/i.test(filenameSlashes)) {
|
|
1062
|
-
// console.log(filename, 'Allow paths like hello.book');
|
|
1003
|
+
if (/^[A-Z]:\\$/i.test(value)) {
|
|
1063
1004
|
return true;
|
|
1064
1005
|
}
|
|
1065
1006
|
return false;
|
|
1066
1007
|
}
|
|
1067
1008
|
/**
|
|
1068
|
-
* TODO: [🍏]
|
|
1009
|
+
* TODO: [🍏] Make for MacOS paths
|
|
1069
1010
|
*/
|
|
1070
1011
|
|
|
1071
1012
|
/**
|
|
1072
|
-
*
|
|
1013
|
+
* Provides the path to the `.env` file
|
|
1073
1014
|
*
|
|
1074
|
-
* Note:
|
|
1075
|
-
* Note: There are two simmilar functions:
|
|
1076
|
-
* - `isValidUrl` which tests any URL
|
|
1077
|
-
* - `isValidPipelineUrl` *(this one)* which tests just promptbook URL
|
|
1015
|
+
* Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file
|
|
1078
1016
|
*
|
|
1079
|
-
* @
|
|
1017
|
+
* @private within the repository - for CLI utils
|
|
1080
1018
|
*/
|
|
1081
|
-
function
|
|
1082
|
-
if (
|
|
1083
|
-
|
|
1084
|
-
}
|
|
1085
|
-
try {
|
|
1086
|
-
if (url.startsWith('blob:')) {
|
|
1087
|
-
url = url.replace(/^blob:/, '');
|
|
1088
|
-
}
|
|
1089
|
-
const urlObject = new URL(url /* because fail is handled */);
|
|
1090
|
-
if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
|
|
1091
|
-
return false;
|
|
1092
|
-
}
|
|
1093
|
-
return true;
|
|
1094
|
-
}
|
|
1095
|
-
catch (error) {
|
|
1096
|
-
return false;
|
|
1019
|
+
async function $provideEnvFilename() {
|
|
1020
|
+
if (!$isRunningInNode()) {
|
|
1021
|
+
throw new EnvironmentMismatchError('Function `$provideEnvFilename` works only in Node.js environment');
|
|
1097
1022
|
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1023
|
+
const envFilePatterns = [
|
|
1024
|
+
'.env',
|
|
1025
|
+
'.env.test',
|
|
1026
|
+
'.env.local',
|
|
1027
|
+
'.env.development.local',
|
|
1028
|
+
'.env.development',
|
|
1029
|
+
'.env.production.local',
|
|
1030
|
+
'.env.production',
|
|
1031
|
+
'.env.prod.local',
|
|
1032
|
+
'.env.prod',
|
|
1033
|
+
// <- TODO: Maybe add more patterns
|
|
1034
|
+
];
|
|
1035
|
+
let rootDirname = process.cwd();
|
|
1036
|
+
up_to_root: for (let i = 0; i < LOOP_LIMIT; i++) {
|
|
1037
|
+
for (const pattern of envFilePatterns) {
|
|
1038
|
+
const envFilename = path.join(rootDirname, pattern);
|
|
1039
|
+
if (await isFileExisting(envFilename, $provideFilesystemForNode())) {
|
|
1040
|
+
$setUsedEnvFilename(envFilename);
|
|
1041
|
+
return envFilename;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
if (isRootPath(rootDirname)) {
|
|
1045
|
+
break up_to_root;
|
|
1046
|
+
}
|
|
1047
|
+
// Note: If the directory does not exist, try the parent directory
|
|
1048
|
+
rootDirname = path.join(rootDirname, '..');
|
|
1049
|
+
}
|
|
1050
|
+
return null;
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
|
|
1054
|
+
*/
|
|
1055
|
+
|
|
1056
|
+
/**
|
|
1057
|
+
* Stores data in .env variables (Remove !!! nonce 1)
|
|
1058
|
+
*
|
|
1059
|
+
* Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file and also writes to `process.env`
|
|
1060
|
+
*
|
|
1061
|
+
* @private within the repository - for CLI utils
|
|
1062
|
+
*/
|
|
1063
|
+
class $EnvStorage {
|
|
1064
|
+
constructor() {
|
|
1065
|
+
this.envFilename = null;
|
|
1066
|
+
}
|
|
1067
|
+
async $provideOrCreateEnvFile() {
|
|
1068
|
+
if (this.envFilename !== null) {
|
|
1069
|
+
return this.envFilename;
|
|
1070
|
+
}
|
|
1071
|
+
let envFilename = await $provideEnvFilename();
|
|
1072
|
+
if (envFilename !== null) {
|
|
1073
|
+
this.envFilename = envFilename;
|
|
1074
|
+
return envFilename;
|
|
1075
|
+
}
|
|
1076
|
+
envFilename = path.join(process.cwd(), '.env');
|
|
1077
|
+
await promises.writeFile(envFilename, '# This file was initialized by Promptbook', 'utf-8');
|
|
1078
|
+
this.envFilename = envFilename;
|
|
1079
|
+
return envFilename;
|
|
1080
|
+
}
|
|
1081
|
+
transformKey(key) {
|
|
1082
|
+
return normalizeTo_SCREAMING_CASE(key);
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Returns the number of key/value pairs currently present in the list associated with the object.
|
|
1086
|
+
*/
|
|
1087
|
+
get length() {
|
|
1088
|
+
throw new NotYetImplementedError('Method `$EnvStorage.length` not implemented.');
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Empties the list associated with the object of all key/value pairs, if there are any.
|
|
1092
|
+
*/
|
|
1093
|
+
clear() {
|
|
1094
|
+
throw new NotYetImplementedError('Method `$EnvStorage.clear` not implemented.');
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Returns the current value associated with the given key, or null if the given key does not exist in the list associated with the object.
|
|
1098
|
+
*/
|
|
1099
|
+
async getItem(key) {
|
|
1100
|
+
dotenv__namespace.config({ path: await this.$provideOrCreateEnvFile() });
|
|
1101
|
+
return process.env[this.transformKey(key)] || null;
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Returns the name of the nth key in the list, or null if n is greater than or equal to the number of key/value pairs in the object.
|
|
1105
|
+
*/
|
|
1106
|
+
key(index) {
|
|
1107
|
+
throw new NotYetImplementedError('Method `$EnvStorage.key` not implemented.');
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.
|
|
1111
|
+
*/
|
|
1112
|
+
async setItem(key, value) {
|
|
1113
|
+
const envFilename = await this.$provideOrCreateEnvFile();
|
|
1114
|
+
const envContent = await promises.readFile(envFilename, 'utf-8');
|
|
1115
|
+
const newEnvContent = spaceTrim__default["default"]((block) => `
|
|
1116
|
+
${block(envContent)}
|
|
1117
|
+
|
|
1118
|
+
# Note: Added by Promptbook
|
|
1119
|
+
${this.transformKey(key)}=${JSON.stringify(value)}
|
|
1120
|
+
|
|
1121
|
+
`);
|
|
1122
|
+
// <- TODO: !!! Add note and use spacetrim
|
|
1123
|
+
promises.writeFile(envFilename, newEnvContent, 'utf-8');
|
|
1124
|
+
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Removes the key/value pair with the given key from the list associated with the object, if a key/value pair with the given key exists.
|
|
1127
|
+
*/
|
|
1128
|
+
removeItem(key) {
|
|
1129
|
+
throw new NotYetImplementedError('Method `$EnvStorage.removeItem` not implemented.');
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
/**
|
|
1134
|
+
* Orders JSON object by keys
|
|
1135
|
+
*
|
|
1136
|
+
* @returns The same type of object as the input re-ordered
|
|
1137
|
+
* @public exported from `@promptbook/utils`
|
|
1138
|
+
*/
|
|
1139
|
+
function orderJson(options) {
|
|
1140
|
+
const { value, order } = options;
|
|
1141
|
+
const orderedValue = {
|
|
1142
|
+
...(order === undefined ? {} : Object.fromEntries(order.map((key) => [key, undefined]))),
|
|
1143
|
+
...value,
|
|
1144
|
+
};
|
|
1145
|
+
return orderedValue;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* Freezes the given object and all its nested objects recursively
|
|
1150
|
+
*
|
|
1151
|
+
* Note: `$` is used to indicate that this function is not a pure function - it mutates given object
|
|
1152
|
+
* Note: This function mutates the object and returns the original (but mutated-deep-freezed) object
|
|
1153
|
+
*
|
|
1154
|
+
* @returns The same object as the input, but deeply frozen
|
|
1155
|
+
* @public exported from `@promptbook/utils`
|
|
1156
|
+
*/
|
|
1157
|
+
function $deepFreeze(objectValue) {
|
|
1158
|
+
if (Array.isArray(objectValue)) {
|
|
1159
|
+
return Object.freeze(objectValue.map((item) => $deepFreeze(item)));
|
|
1160
|
+
}
|
|
1161
|
+
const propertyNames = Object.getOwnPropertyNames(objectValue);
|
|
1162
|
+
for (const propertyName of propertyNames) {
|
|
1163
|
+
const value = objectValue[propertyName];
|
|
1164
|
+
if (value && typeof value === 'object') {
|
|
1165
|
+
$deepFreeze(value);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
Object.freeze(objectValue);
|
|
1169
|
+
return objectValue;
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* TODO: [🧠] Is there a way how to meaningfully test this utility
|
|
1173
|
+
*/
|
|
1174
|
+
|
|
1175
|
+
/**
|
|
1176
|
+
* Checks if the value is [🚉] serializable as JSON
|
|
1177
|
+
* If not, throws an UnexpectedError with a rich error message and tracking
|
|
1178
|
+
*
|
|
1179
|
+
* - Almost all primitives are serializable BUT:
|
|
1180
|
+
* - `undefined` is not serializable
|
|
1181
|
+
* - `NaN` is not serializable
|
|
1182
|
+
* - Objects and arrays are serializable if all their properties are serializable
|
|
1183
|
+
* - Functions are not serializable
|
|
1184
|
+
* - Circular references are not serializable
|
|
1185
|
+
* - `Date` objects are not serializable
|
|
1186
|
+
* - `Map` and `Set` objects are not serializable
|
|
1187
|
+
* - `RegExp` objects are not serializable
|
|
1188
|
+
* - `Error` objects are not serializable
|
|
1189
|
+
* - `Symbol` objects are not serializable
|
|
1190
|
+
* - And much more...
|
|
1191
|
+
*
|
|
1192
|
+
* @throws UnexpectedError if the value is not serializable as JSON
|
|
1193
|
+
* @public exported from `@promptbook/utils`
|
|
1194
|
+
*/
|
|
1195
|
+
function checkSerializableAsJson(options) {
|
|
1196
|
+
const { value, name, message } = options;
|
|
1197
|
+
if (value === undefined) {
|
|
1198
|
+
throw new UnexpectedError(`${name} is undefined`);
|
|
1199
|
+
}
|
|
1200
|
+
else if (value === null) {
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
else if (typeof value === 'boolean') {
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
else if (typeof value === 'number' && !isNaN(value)) {
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
else if (typeof value === 'string') {
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
else if (typeof value === 'symbol') {
|
|
1213
|
+
throw new UnexpectedError(`${name} is symbol`);
|
|
1214
|
+
}
|
|
1215
|
+
else if (typeof value === 'function') {
|
|
1216
|
+
throw new UnexpectedError(`${name} is function`);
|
|
1217
|
+
}
|
|
1218
|
+
else if (typeof value === 'object' && Array.isArray(value)) {
|
|
1219
|
+
for (let i = 0; i < value.length; i++) {
|
|
1220
|
+
checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
else if (typeof value === 'object') {
|
|
1224
|
+
if (value instanceof Date) {
|
|
1225
|
+
throw new UnexpectedError(spaceTrim__default["default"]((block) => `
|
|
1226
|
+
\`${name}\` is Date
|
|
1227
|
+
|
|
1228
|
+
Use \`string_date_iso8601\` instead
|
|
1229
|
+
|
|
1230
|
+
Additional message for \`${name}\`:
|
|
1231
|
+
${block(message || '(nothing)')}
|
|
1232
|
+
`));
|
|
1233
|
+
}
|
|
1234
|
+
else if (value instanceof Map) {
|
|
1235
|
+
throw new UnexpectedError(`${name} is Map`);
|
|
1236
|
+
}
|
|
1237
|
+
else if (value instanceof Set) {
|
|
1238
|
+
throw new UnexpectedError(`${name} is Set`);
|
|
1239
|
+
}
|
|
1240
|
+
else if (value instanceof RegExp) {
|
|
1241
|
+
throw new UnexpectedError(`${name} is RegExp`);
|
|
1242
|
+
}
|
|
1243
|
+
else if (value instanceof Error) {
|
|
1244
|
+
throw new UnexpectedError(spaceTrim__default["default"]((block) => `
|
|
1245
|
+
\`${name}\` is unserialized Error
|
|
1246
|
+
|
|
1247
|
+
Use function \`serializeError\`
|
|
1248
|
+
|
|
1249
|
+
Additional message for \`${name}\`:
|
|
1250
|
+
${block(message || '(nothing)')}
|
|
1251
|
+
|
|
1252
|
+
`));
|
|
1253
|
+
}
|
|
1254
|
+
else {
|
|
1255
|
+
for (const [subName, subValue] of Object.entries(value)) {
|
|
1256
|
+
if (subValue === undefined) {
|
|
1257
|
+
// Note: undefined in object is serializable - it is just omited
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
|
|
1261
|
+
}
|
|
1262
|
+
try {
|
|
1263
|
+
JSON.stringify(value); // <- TODO: [0]
|
|
1264
|
+
}
|
|
1265
|
+
catch (error) {
|
|
1266
|
+
if (!(error instanceof Error)) {
|
|
1267
|
+
throw error;
|
|
1268
|
+
}
|
|
1269
|
+
throw new UnexpectedError(spaceTrim__default["default"]((block) => `
|
|
1270
|
+
\`${name}\` is not serializable
|
|
1271
|
+
|
|
1272
|
+
${block(error.stack || error.message)}
|
|
1273
|
+
|
|
1274
|
+
Additional message for \`${name}\`:
|
|
1275
|
+
${block(message || '(nothing)')}
|
|
1276
|
+
`));
|
|
1277
|
+
}
|
|
1278
|
+
/*
|
|
1279
|
+
TODO: [0] Is there some more elegant way to check circular references?
|
|
1280
|
+
const seen = new Set();
|
|
1281
|
+
const stack = [{ value }];
|
|
1282
|
+
while (stack.length > 0) {
|
|
1283
|
+
const { value } = stack.pop()!;
|
|
1284
|
+
if (typeof value === 'object' && value !== null) {
|
|
1285
|
+
if (seen.has(value)) {
|
|
1286
|
+
throw new UnexpectedError(`${name} has circular reference`);
|
|
1287
|
+
}
|
|
1288
|
+
seen.add(value);
|
|
1289
|
+
if (Array.isArray(value)) {
|
|
1290
|
+
stack.push(...value.map((value) => ({ value })));
|
|
1291
|
+
} else {
|
|
1292
|
+
stack.push(...Object.values(value).map((value) => ({ value })));
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
*/
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
else {
|
|
1301
|
+
throw new UnexpectedError(spaceTrim__default["default"]((block) => `
|
|
1302
|
+
\`${name}\` is unknown type
|
|
1303
|
+
|
|
1304
|
+
Additional message for \`${name}\`:
|
|
1305
|
+
${block(message || '(nothing)')}
|
|
1306
|
+
`));
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
|
|
1311
|
+
* TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
|
|
1312
|
+
* Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
|
|
1313
|
+
*/
|
|
1314
|
+
|
|
1315
|
+
/**
|
|
1316
|
+
* @@@
|
|
1317
|
+
*
|
|
1318
|
+
* @public exported from `@promptbook/utils`
|
|
1319
|
+
*/
|
|
1320
|
+
function deepClone(objectValue) {
|
|
1321
|
+
return JSON.parse(JSON.stringify(objectValue));
|
|
1322
|
+
/*
|
|
1323
|
+
TODO: [🧠] Is there a better implementation?
|
|
1324
|
+
> const propertyNames = Object.getOwnPropertyNames(objectValue);
|
|
1325
|
+
> for (const propertyName of propertyNames) {
|
|
1326
|
+
> const value = (objectValue as really_any)[propertyName];
|
|
1327
|
+
> if (value && typeof value === 'object') {
|
|
1328
|
+
> deepClone(value);
|
|
1329
|
+
> }
|
|
1330
|
+
> }
|
|
1331
|
+
> return Object.assign({}, objectValue);
|
|
1332
|
+
*/
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* TODO: [🧠] Is there a way how to meaningfully test this utility
|
|
1336
|
+
*/
|
|
1337
|
+
|
|
1338
|
+
/**
|
|
1339
|
+
* Utility to export a JSON object from a function
|
|
1340
|
+
*
|
|
1341
|
+
* 1) Checks if the value is serializable as JSON
|
|
1342
|
+
* 2) Makes a deep clone of the object
|
|
1343
|
+
* 2) Orders the object properties
|
|
1344
|
+
* 2) Deeply freezes the cloned object
|
|
1345
|
+
*
|
|
1346
|
+
* Note: This function does not mutates the given object
|
|
1347
|
+
*
|
|
1348
|
+
* @returns The same type of object as the input but read-only and re-ordered
|
|
1349
|
+
* @public exported from `@promptbook/utils`
|
|
1350
|
+
*/
|
|
1351
|
+
function exportJson(options) {
|
|
1352
|
+
const { name, value, order, message } = options;
|
|
1353
|
+
checkSerializableAsJson({ name, value, message });
|
|
1354
|
+
const orderedValue =
|
|
1355
|
+
// TODO: Fix error "Type instantiation is excessively deep and possibly infinite."
|
|
1356
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1357
|
+
// @ts-ignore
|
|
1358
|
+
order === undefined
|
|
1359
|
+
? deepClone(value)
|
|
1360
|
+
: orderJson({
|
|
1361
|
+
value: value,
|
|
1362
|
+
// <- Note: checkSerializableAsJson asserts that the value is serializable as JSON
|
|
1363
|
+
order: order,
|
|
1364
|
+
});
|
|
1365
|
+
$deepFreeze(orderedValue);
|
|
1366
|
+
return orderedValue;
|
|
1367
|
+
}
|
|
1368
|
+
/**
|
|
1369
|
+
* TODO: [🧠] Is there a way how to meaningfully test this utility
|
|
1370
|
+
*/
|
|
1371
|
+
|
|
1372
|
+
/**
|
|
1373
|
+
* Order of keys in the pipeline JSON
|
|
1374
|
+
*
|
|
1375
|
+
* @public exported from `@promptbook/core`
|
|
1376
|
+
*/
|
|
1377
|
+
const ORDER_OF_PIPELINE_JSON = [
|
|
1378
|
+
// Note: [🍙] In this order will be pipeline serialized
|
|
1379
|
+
'title',
|
|
1380
|
+
'pipelineUrl',
|
|
1381
|
+
'bookVersion',
|
|
1382
|
+
'description',
|
|
1383
|
+
'formfactorName',
|
|
1384
|
+
'parameters',
|
|
1385
|
+
'tasks',
|
|
1386
|
+
'personas',
|
|
1387
|
+
'preparations',
|
|
1388
|
+
'knowledgeSources',
|
|
1389
|
+
'knowledgePieces',
|
|
1390
|
+
'sources', // <- TODO: [🧠] Where should the `sources` be
|
|
1391
|
+
];
|
|
1392
|
+
/**
|
|
1393
|
+
* Nonce which is used for replacing things in strings
|
|
1394
|
+
*
|
|
1395
|
+
* @private within the repository
|
|
1396
|
+
*/
|
|
1397
|
+
const REPLACING_NONCE = 'ptbkauk42kV2dzao34faw7FudQUHYPtW';
|
|
1398
|
+
/**
|
|
1399
|
+
* @@@
|
|
1400
|
+
*
|
|
1401
|
+
* @private within the repository
|
|
1402
|
+
*/
|
|
1403
|
+
const RESERVED_PARAMETER_MISSING_VALUE = 'MISSING-' + REPLACING_NONCE;
|
|
1404
|
+
/**
|
|
1405
|
+
* @@@
|
|
1406
|
+
*
|
|
1407
|
+
* @private within the repository
|
|
1408
|
+
*/
|
|
1409
|
+
const RESERVED_PARAMETER_RESTRICTED = 'RESTRICTED-' + REPLACING_NONCE;
|
|
1410
|
+
/**
|
|
1411
|
+
* The names of the parameters that are reserved for special purposes
|
|
1412
|
+
*
|
|
1413
|
+
* @public exported from `@promptbook/core`
|
|
1414
|
+
*/
|
|
1415
|
+
const RESERVED_PARAMETER_NAMES = exportJson({
|
|
1416
|
+
name: 'RESERVED_PARAMETER_NAMES',
|
|
1417
|
+
message: `The names of the parameters that are reserved for special purposes`,
|
|
1418
|
+
value: [
|
|
1419
|
+
'content',
|
|
1420
|
+
'context',
|
|
1421
|
+
'knowledge',
|
|
1422
|
+
'examples',
|
|
1423
|
+
'modelName',
|
|
1424
|
+
'currentDate',
|
|
1425
|
+
// <- TODO: list here all command names
|
|
1426
|
+
// <- TODO: Add more like 'date', 'modelName',...
|
|
1427
|
+
// <- TODO: Add [emoji] + instructions ACRY when adding new reserved parameter
|
|
1428
|
+
],
|
|
1429
|
+
});
|
|
1430
|
+
/**
|
|
1431
|
+
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
1432
|
+
*/
|
|
1433
|
+
|
|
1434
|
+
// <- TODO: Auto convert to type `import { ... } from 'type-fest';`
|
|
1435
|
+
/**
|
|
1436
|
+
* Tests if the value is [🚉] serializable as JSON
|
|
1437
|
+
*
|
|
1438
|
+
* - Almost all primitives are serializable BUT:
|
|
1439
|
+
* - `undefined` is not serializable
|
|
1440
|
+
* - `NaN` is not serializable
|
|
1441
|
+
* - Objects and arrays are serializable if all their properties are serializable
|
|
1442
|
+
* - Functions are not serializable
|
|
1443
|
+
* - Circular references are not serializable
|
|
1444
|
+
* - `Date` objects are not serializable
|
|
1445
|
+
* - `Map` and `Set` objects are not serializable
|
|
1446
|
+
* - `RegExp` objects are not serializable
|
|
1447
|
+
* - `Error` objects are not serializable
|
|
1448
|
+
* - `Symbol` objects are not serializable
|
|
1449
|
+
* - And much more...
|
|
1450
|
+
*
|
|
1451
|
+
*
|
|
1452
|
+
* @public exported from `@promptbook/utils`
|
|
1453
|
+
*/
|
|
1454
|
+
function isSerializableAsJson(value) {
|
|
1455
|
+
try {
|
|
1456
|
+
checkSerializableAsJson({ value });
|
|
1457
|
+
return true;
|
|
1458
|
+
}
|
|
1459
|
+
catch (error) {
|
|
1460
|
+
return false;
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
|
|
1465
|
+
* TODO: [🧠][💺] Can be done this on type-level?
|
|
1466
|
+
*/
|
|
1467
|
+
|
|
1468
|
+
/**
|
|
1469
|
+
* Stringify the PipelineJson with proper formatting
|
|
1470
|
+
*
|
|
1471
|
+
* Note: [0] It can be used for more JSON types like whole collection of pipelines, single knowledge piece, etc.
|
|
1472
|
+
* Note: In contrast to JSON.stringify, this function ensures that **embedding index** is on single line
|
|
1473
|
+
*
|
|
1474
|
+
* @public exported from `@promptbook/editable`
|
|
1475
|
+
*/
|
|
1476
|
+
function stringifyPipelineJson(pipeline) {
|
|
1477
|
+
if (!isSerializableAsJson(pipeline)) {
|
|
1478
|
+
throw new UnexpectedError(spaceTrim__default["default"](`
|
|
1479
|
+
Cannot stringify the pipeline, because it is not serializable as JSON
|
|
1480
|
+
|
|
1481
|
+
There can be multiple reasons:
|
|
1482
|
+
1) The pipeline contains circular references
|
|
1483
|
+
2) It is not a valid PipelineJson
|
|
1484
|
+
`));
|
|
1485
|
+
}
|
|
1486
|
+
let pipelineJsonStringified = JSON.stringify(pipeline, null, 4);
|
|
1487
|
+
for (let i = 0; i < LOOP_LIMIT; i++) {
|
|
1488
|
+
pipelineJsonStringified = pipelineJsonStringified.replace(/(-?0\.\d+),[\n\s]+(-?0\.\d+)/gms, `$1${REPLACING_NONCE}$2`);
|
|
1489
|
+
}
|
|
1490
|
+
pipelineJsonStringified = pipelineJsonStringified.split(REPLACING_NONCE).join(', ');
|
|
1491
|
+
pipelineJsonStringified += '\n';
|
|
1492
|
+
return pipelineJsonStringified;
|
|
1493
|
+
}
|
|
1494
|
+
/**
|
|
1495
|
+
* TODO: [🐝] Not Working propperly @see https://promptbook.studio/examples/mixed-knowledge.book
|
|
1496
|
+
* TODO: [🧠][0] Maybe rename to `stringifyPipelineJson`, `stringifyIndexedJson`,...
|
|
1497
|
+
* TODO: [🧠] Maybe more elegant solution than replacing via regex
|
|
1498
|
+
* TODO: [🍙] Make some standard order of json properties
|
|
1499
|
+
*/
|
|
1500
|
+
|
|
1501
|
+
/**
|
|
1502
|
+
* Removes emojis from a string and fix whitespaces
|
|
1503
|
+
*
|
|
1504
|
+
* @param text with emojis
|
|
1505
|
+
* @returns text without emojis
|
|
1506
|
+
* @public exported from `@promptbook/utils`
|
|
1507
|
+
*/
|
|
1508
|
+
function removeEmojis(text) {
|
|
1509
|
+
// Replace emojis (and also ZWJ sequence) with hyphens
|
|
1510
|
+
text = text.replace(/(\p{Extended_Pictographic})\p{Modifier_Symbol}/gu, '$1');
|
|
1511
|
+
text = text.replace(/(\p{Extended_Pictographic})[\u{FE00}-\u{FE0F}]/gu, '$1');
|
|
1512
|
+
text = text.replace(/(\p{Extended_Pictographic})(\u{200D}\p{Extended_Pictographic})*/gu, '$1');
|
|
1513
|
+
text = text.replace(/\p{Extended_Pictographic}/gu, '');
|
|
1514
|
+
return text;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
/**
|
|
1518
|
+
* Tests if given string is valid URL.
|
|
1519
|
+
*
|
|
1520
|
+
* Note: This does not check if the file exists only if the path is valid
|
|
1521
|
+
* @public exported from `@promptbook/utils`
|
|
1522
|
+
*/
|
|
1523
|
+
function isValidFilePath(filename) {
|
|
1524
|
+
if (typeof filename !== 'string') {
|
|
1525
|
+
return false;
|
|
1526
|
+
}
|
|
1527
|
+
if (filename.split('\n').length > 1) {
|
|
1528
|
+
return false;
|
|
1529
|
+
}
|
|
1530
|
+
if (filename.split(' ').length >
|
|
1531
|
+
5 /* <- TODO: [🧠][🈷] Make some better non-arbitrary way how to distinct filenames from informational texts */) {
|
|
1532
|
+
return false;
|
|
1533
|
+
}
|
|
1534
|
+
const filenameSlashes = filename.split('\\').join('/');
|
|
1535
|
+
// Absolute Unix path: /hello.txt
|
|
1536
|
+
if (/^(\/)/i.test(filenameSlashes)) {
|
|
1537
|
+
// console.log(filename, 'Absolute Unix path: /hello.txt');
|
|
1538
|
+
return true;
|
|
1539
|
+
}
|
|
1540
|
+
// Absolute Windows path: /hello.txt
|
|
1541
|
+
if (/^([A-Z]{1,2}:\/?)\//i.test(filenameSlashes)) {
|
|
1542
|
+
// console.log(filename, 'Absolute Windows path: /hello.txt');
|
|
1543
|
+
return true;
|
|
1544
|
+
}
|
|
1545
|
+
// Relative path: ./hello.txt
|
|
1546
|
+
if (/^(\.\.?\/)+/i.test(filenameSlashes)) {
|
|
1547
|
+
// console.log(filename, 'Relative path: ./hello.txt');
|
|
1548
|
+
return true;
|
|
1549
|
+
}
|
|
1550
|
+
// Allow paths like foo/hello
|
|
1551
|
+
if (/^[^/]+\/[^/]+/i.test(filenameSlashes)) {
|
|
1552
|
+
// console.log(filename, 'Allow paths like foo/hello');
|
|
1553
|
+
return true;
|
|
1554
|
+
}
|
|
1555
|
+
// Allow paths like hello.book
|
|
1556
|
+
if (/^[^/]+\.[^/]+$/i.test(filenameSlashes)) {
|
|
1557
|
+
// console.log(filename, 'Allow paths like hello.book');
|
|
1558
|
+
return true;
|
|
1559
|
+
}
|
|
1560
|
+
return false;
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* TODO: [🍏] Implement for MacOs
|
|
1564
|
+
*/
|
|
1565
|
+
|
|
1566
|
+
/**
|
|
1567
|
+
* Tests if given string is valid URL.
|
|
1568
|
+
*
|
|
1569
|
+
* Note: Dataurl are considered perfectly valid.
|
|
1570
|
+
* Note: There are two simmilar functions:
|
|
1571
|
+
* - `isValidUrl` which tests any URL
|
|
1572
|
+
* - `isValidPipelineUrl` *(this one)* which tests just promptbook URL
|
|
1573
|
+
*
|
|
1574
|
+
* @public exported from `@promptbook/utils`
|
|
1575
|
+
*/
|
|
1576
|
+
function isValidUrl(url) {
|
|
1577
|
+
if (typeof url !== 'string') {
|
|
1578
|
+
return false;
|
|
1579
|
+
}
|
|
1580
|
+
try {
|
|
1581
|
+
if (url.startsWith('blob:')) {
|
|
1582
|
+
url = url.replace(/^blob:/, '');
|
|
1583
|
+
}
|
|
1584
|
+
const urlObject = new URL(url /* because fail is handled */);
|
|
1585
|
+
if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
|
|
1586
|
+
return false;
|
|
1587
|
+
}
|
|
1588
|
+
return true;
|
|
1589
|
+
}
|
|
1590
|
+
catch (error) {
|
|
1591
|
+
return false;
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1100
1595
|
const defaultDiacriticsRemovalMap = [
|
|
1101
1596
|
{
|
|
1102
1597
|
base: 'A',
|
|
@@ -1509,690 +2004,792 @@
|
|
|
1509
2004
|
*/
|
|
1510
2005
|
|
|
1511
2006
|
/**
|
|
1512
|
-
*
|
|
2007
|
+
* This error indicates problems parsing the format value
|
|
1513
2008
|
*
|
|
1514
|
-
*
|
|
2009
|
+
* For example, when the format value is not a valid JSON or CSV
|
|
2010
|
+
* This is not thrown directly but in extended classes
|
|
1515
2011
|
*
|
|
1516
|
-
* @
|
|
1517
|
-
* @returns secure random token
|
|
2012
|
+
* @public exported from `@promptbook/core`
|
|
1518
2013
|
*/
|
|
1519
|
-
|
|
1520
|
-
|
|
2014
|
+
class AbstractFormatError extends Error {
|
|
2015
|
+
// Note: To allow instanceof do not put here error `name`
|
|
2016
|
+
// public readonly name = 'AbstractFormatError';
|
|
2017
|
+
constructor(message) {
|
|
2018
|
+
super(message);
|
|
2019
|
+
Object.setPrototypeOf(this, AbstractFormatError.prototype);
|
|
2020
|
+
}
|
|
1521
2021
|
}
|
|
2022
|
+
|
|
1522
2023
|
/**
|
|
1523
|
-
*
|
|
2024
|
+
* This error indicates problem with parsing of CSV
|
|
2025
|
+
*
|
|
2026
|
+
* @public exported from `@promptbook/core`
|
|
1524
2027
|
*/
|
|
2028
|
+
class CsvFormatError extends AbstractFormatError {
|
|
2029
|
+
constructor(message) {
|
|
2030
|
+
super(message);
|
|
2031
|
+
this.name = 'CsvFormatError';
|
|
2032
|
+
Object.setPrototypeOf(this, CsvFormatError.prototype);
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
1525
2035
|
|
|
1526
2036
|
/**
|
|
1527
|
-
*
|
|
2037
|
+
* AuthenticationError is thrown from login function which is dependency of remote server
|
|
1528
2038
|
*
|
|
1529
2039
|
* @public exported from `@promptbook/core`
|
|
1530
2040
|
*/
|
|
1531
|
-
class
|
|
2041
|
+
class AuthenticationError extends Error {
|
|
1532
2042
|
constructor(message) {
|
|
1533
|
-
// Added id parameter
|
|
1534
2043
|
super(message);
|
|
1535
|
-
this.name = '
|
|
1536
|
-
|
|
1537
|
-
this.id = `error-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid simmilar char conflicts */)}`;
|
|
1538
|
-
Object.setPrototypeOf(this, PipelineExecutionError.prototype);
|
|
2044
|
+
this.name = 'AuthenticationError';
|
|
2045
|
+
Object.setPrototypeOf(this, AuthenticationError.prototype);
|
|
1539
2046
|
}
|
|
1540
2047
|
}
|
|
2048
|
+
|
|
1541
2049
|
/**
|
|
1542
|
-
*
|
|
2050
|
+
* This error indicates that the pipeline collection cannot be propperly loaded
|
|
2051
|
+
*
|
|
2052
|
+
* @public exported from `@promptbook/core`
|
|
1543
2053
|
*/
|
|
2054
|
+
class CollectionError extends Error {
|
|
2055
|
+
constructor(message) {
|
|
2056
|
+
super(message);
|
|
2057
|
+
this.name = 'CollectionError';
|
|
2058
|
+
Object.setPrototypeOf(this, CollectionError.prototype);
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
1544
2061
|
|
|
1545
2062
|
/**
|
|
1546
|
-
*
|
|
2063
|
+
* This error occurs when some expectation is not met in the execution of the pipeline
|
|
1547
2064
|
*
|
|
1548
2065
|
* @public exported from `@promptbook/core`
|
|
2066
|
+
* Note: Do not throw this error, its reserved for `checkExpectations` and `createPipelineExecutor` and public ONLY to be serializable through remote server
|
|
2067
|
+
* Note: Always thrown in `checkExpectations` and catched in `createPipelineExecutor` and rethrown as `PipelineExecutionError`
|
|
2068
|
+
* Note: This is a kindof subtype of PipelineExecutionError
|
|
1549
2069
|
*/
|
|
1550
|
-
class
|
|
1551
|
-
constructor() {
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
* Returns the number of key/value pairs currently present in the list associated with the object.
|
|
1556
|
-
*/
|
|
1557
|
-
get length() {
|
|
1558
|
-
return Object.keys(this.storage).length;
|
|
1559
|
-
}
|
|
1560
|
-
/**
|
|
1561
|
-
* Empties the list associated with the object of all key/value pairs, if there are any.
|
|
1562
|
-
*/
|
|
1563
|
-
clear() {
|
|
1564
|
-
this.storage = {};
|
|
1565
|
-
}
|
|
1566
|
-
/**
|
|
1567
|
-
* Returns the current value associated with the given key, or null if the given key does not exist in the list associated with the object.
|
|
1568
|
-
*/
|
|
1569
|
-
getItem(key) {
|
|
1570
|
-
return this.storage[key] || null;
|
|
1571
|
-
}
|
|
1572
|
-
/**
|
|
1573
|
-
* Returns the name of the nth key in the list, or null if n is greater than or equal to the number of key/value pairs in the object.
|
|
1574
|
-
*/
|
|
1575
|
-
key(index) {
|
|
1576
|
-
return Object.keys(this.storage)[index] || null;
|
|
1577
|
-
}
|
|
1578
|
-
/**
|
|
1579
|
-
* Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.
|
|
1580
|
-
*/
|
|
1581
|
-
setItem(key, value) {
|
|
1582
|
-
this.storage[key] = value;
|
|
1583
|
-
}
|
|
1584
|
-
/**
|
|
1585
|
-
* Removes the key/value pair with the given key from the list associated with the object, if a key/value pair with the given key exists.
|
|
1586
|
-
*/
|
|
1587
|
-
removeItem(key) {
|
|
1588
|
-
delete this.storage[key];
|
|
2070
|
+
class ExpectError extends Error {
|
|
2071
|
+
constructor(message) {
|
|
2072
|
+
super(message);
|
|
2073
|
+
this.name = 'ExpectError';
|
|
2074
|
+
Object.setPrototypeOf(this, ExpectError.prototype);
|
|
1589
2075
|
}
|
|
1590
2076
|
}
|
|
1591
2077
|
|
|
1592
2078
|
/**
|
|
1593
|
-
*
|
|
1594
|
-
*
|
|
1595
|
-
* Note: `$` is used to indicate that this function is not a pure function - it is not deterministic because it depends on the current time
|
|
2079
|
+
* This error indicates that the promptbook can not retrieve knowledge from external sources
|
|
1596
2080
|
*
|
|
1597
|
-
* @
|
|
1598
|
-
* @public exported from `@promptbook/utils`
|
|
2081
|
+
* @public exported from `@promptbook/core`
|
|
1599
2082
|
*/
|
|
1600
|
-
|
|
1601
|
-
|
|
2083
|
+
class KnowledgeScrapeError extends Error {
|
|
2084
|
+
constructor(message) {
|
|
2085
|
+
super(message);
|
|
2086
|
+
this.name = 'KnowledgeScrapeError';
|
|
2087
|
+
Object.setPrototypeOf(this, KnowledgeScrapeError.prototype);
|
|
2088
|
+
}
|
|
1602
2089
|
}
|
|
1603
2090
|
|
|
1604
2091
|
/**
|
|
1605
|
-
*
|
|
1606
|
-
*
|
|
1607
|
-
* Note: It can take extended `LlmExecutionTools` and cache the
|
|
2092
|
+
* This error type indicates that some limit was reached
|
|
1608
2093
|
*
|
|
1609
|
-
* @param llmTools LLM tools to be intercepted with usage counting, it can contain extra methods like `totalUsage`
|
|
1610
|
-
* @returns LLM tools with same functionality with added total cost counting
|
|
1611
2094
|
* @public exported from `@promptbook/core`
|
|
1612
2095
|
*/
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
get title() {
|
|
1619
|
-
// TODO: [🧠] Maybe put here some suffix
|
|
1620
|
-
return llmTools.title;
|
|
1621
|
-
},
|
|
1622
|
-
get description() {
|
|
1623
|
-
// TODO: [🧠] Maybe put here some suffix
|
|
1624
|
-
return llmTools.description;
|
|
1625
|
-
},
|
|
1626
|
-
listModels() {
|
|
1627
|
-
// TODO: [🧠] Should be model listing also cached?
|
|
1628
|
-
return /* not await */ llmTools.listModels();
|
|
1629
|
-
},
|
|
1630
|
-
};
|
|
1631
|
-
const callCommonModel = async (prompt) => {
|
|
1632
|
-
const { parameters, content, modelRequirements } = prompt;
|
|
1633
|
-
// <- Note: These are relevant things from the prompt that the cache key should depend on.
|
|
1634
|
-
const key = titleToName(prompt.title.substring(0, MAX_FILENAME_LENGTH - 10) +
|
|
1635
|
-
'-' +
|
|
1636
|
-
sha256__default["default"](hexEncoder__default["default"].parse(JSON.stringify({ parameters, content, modelRequirements }))).toString( /* hex */));
|
|
1637
|
-
const cacheItem = !isCacheReloaded ? await storage.getItem(key) : null;
|
|
1638
|
-
if (cacheItem) {
|
|
1639
|
-
return cacheItem.promptResult;
|
|
1640
|
-
}
|
|
1641
|
-
let promptResult;
|
|
1642
|
-
variant: switch (prompt.modelRequirements.modelVariant) {
|
|
1643
|
-
case 'CHAT':
|
|
1644
|
-
promptResult = await llmTools.callChatModel(prompt);
|
|
1645
|
-
break variant;
|
|
1646
|
-
case 'COMPLETION':
|
|
1647
|
-
promptResult = await llmTools.callCompletionModel(prompt);
|
|
1648
|
-
break variant;
|
|
1649
|
-
case 'EMBEDDING':
|
|
1650
|
-
promptResult = await llmTools.callEmbeddingModel(prompt);
|
|
1651
|
-
break variant;
|
|
1652
|
-
// <- case [🤖]:
|
|
1653
|
-
default:
|
|
1654
|
-
throw new PipelineExecutionError(`Unknown model variant "${prompt.modelRequirements.modelVariant}"`);
|
|
1655
|
-
}
|
|
1656
|
-
// TODO: [🧠] !!5 How to do timing in mixed cache / non-cache situation
|
|
1657
|
-
// promptResult.timing: FromtoItems
|
|
1658
|
-
await storage.setItem(key, {
|
|
1659
|
-
date: $getCurrentDate(),
|
|
1660
|
-
promptbookVersion: PROMPTBOOK_ENGINE_VERSION,
|
|
1661
|
-
prompt,
|
|
1662
|
-
promptResult,
|
|
1663
|
-
});
|
|
1664
|
-
return promptResult;
|
|
1665
|
-
};
|
|
1666
|
-
if (llmTools.callChatModel !== undefined) {
|
|
1667
|
-
proxyTools.callChatModel = async (prompt) => {
|
|
1668
|
-
return /* not await */ callCommonModel(prompt);
|
|
1669
|
-
};
|
|
1670
|
-
}
|
|
1671
|
-
if (llmTools.callCompletionModel !== undefined) {
|
|
1672
|
-
proxyTools.callCompletionModel = async (prompt) => {
|
|
1673
|
-
return /* not await */ callCommonModel(prompt);
|
|
1674
|
-
};
|
|
1675
|
-
}
|
|
1676
|
-
if (llmTools.callEmbeddingModel !== undefined) {
|
|
1677
|
-
proxyTools.callEmbeddingModel = async (prompt) => {
|
|
1678
|
-
return /* not await */ callCommonModel(prompt);
|
|
1679
|
-
};
|
|
2096
|
+
class LimitReachedError extends Error {
|
|
2097
|
+
constructor(message) {
|
|
2098
|
+
super(message);
|
|
2099
|
+
this.name = 'LimitReachedError';
|
|
2100
|
+
Object.setPrototypeOf(this, LimitReachedError.prototype);
|
|
1680
2101
|
}
|
|
1681
|
-
// <- Note: [🤖]
|
|
1682
|
-
return proxyTools;
|
|
1683
2102
|
}
|
|
2103
|
+
|
|
1684
2104
|
/**
|
|
1685
|
-
*
|
|
1686
|
-
*
|
|
1687
|
-
*
|
|
1688
|
-
* @@@ write discussion about this and storages
|
|
1689
|
-
* @@@ write how to combine multiple interceptors
|
|
2105
|
+
* This error type indicates that some tools are missing for pipeline execution or preparation
|
|
2106
|
+
*
|
|
2107
|
+
* @public exported from `@promptbook/core`
|
|
1690
2108
|
*/
|
|
2109
|
+
class MissingToolsError extends Error {
|
|
2110
|
+
constructor(message) {
|
|
2111
|
+
super(spaceTrim.spaceTrim((block) => `
|
|
2112
|
+
${block(message)}
|
|
2113
|
+
|
|
2114
|
+
Note: You have probbably forgot to provide some tools for pipeline execution or preparation
|
|
2115
|
+
|
|
2116
|
+
`));
|
|
2117
|
+
this.name = 'MissingToolsError';
|
|
2118
|
+
Object.setPrototypeOf(this, MissingToolsError.prototype);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
1691
2121
|
|
|
1692
2122
|
/**
|
|
1693
|
-
*
|
|
2123
|
+
* This error indicates that promptbook not found in the collection
|
|
1694
2124
|
*
|
|
1695
2125
|
* @public exported from `@promptbook/core`
|
|
1696
2126
|
*/
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
paragraphsCount: { value: 0 },
|
|
1706
|
-
pagesCount: { value: 0 },
|
|
1707
|
-
},
|
|
1708
|
-
output: {
|
|
1709
|
-
tokensCount: { value: 0 },
|
|
1710
|
-
charactersCount: { value: 0 },
|
|
1711
|
-
wordsCount: { value: 0 },
|
|
1712
|
-
sentencesCount: { value: 0 },
|
|
1713
|
-
linesCount: { value: 0 },
|
|
1714
|
-
paragraphsCount: { value: 0 },
|
|
1715
|
-
pagesCount: { value: 0 },
|
|
1716
|
-
},
|
|
1717
|
-
});
|
|
2127
|
+
class NotFoundError extends Error {
|
|
2128
|
+
constructor(message) {
|
|
2129
|
+
super(message);
|
|
2130
|
+
this.name = 'NotFoundError';
|
|
2131
|
+
Object.setPrototypeOf(this, NotFoundError.prototype);
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
|
|
1718
2135
|
/**
|
|
1719
|
-
*
|
|
2136
|
+
* This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
|
|
1720
2137
|
*
|
|
1721
2138
|
* @public exported from `@promptbook/core`
|
|
1722
2139
|
*/
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
linesCount: { value: 0, isUncertain: true },
|
|
1731
|
-
paragraphsCount: { value: 0, isUncertain: true },
|
|
1732
|
-
pagesCount: { value: 0, isUncertain: true },
|
|
1733
|
-
},
|
|
1734
|
-
output: {
|
|
1735
|
-
tokensCount: { value: 0, isUncertain: true },
|
|
1736
|
-
charactersCount: { value: 0, isUncertain: true },
|
|
1737
|
-
wordsCount: { value: 0, isUncertain: true },
|
|
1738
|
-
sentencesCount: { value: 0, isUncertain: true },
|
|
1739
|
-
linesCount: { value: 0, isUncertain: true },
|
|
1740
|
-
paragraphsCount: { value: 0, isUncertain: true },
|
|
1741
|
-
pagesCount: { value: 0, isUncertain: true },
|
|
1742
|
-
},
|
|
1743
|
-
});
|
|
2140
|
+
class ParseError extends Error {
|
|
2141
|
+
constructor(message) {
|
|
2142
|
+
super(message);
|
|
2143
|
+
this.name = 'ParseError';
|
|
2144
|
+
Object.setPrototypeOf(this, ParseError.prototype);
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
1744
2147
|
/**
|
|
1745
|
-
*
|
|
2148
|
+
* TODO: Maybe split `ParseError` and `ApplyError`
|
|
1746
2149
|
*/
|
|
1747
2150
|
|
|
1748
2151
|
/**
|
|
1749
|
-
*
|
|
2152
|
+
* Generates random token
|
|
1750
2153
|
*
|
|
1751
|
-
* Note:
|
|
2154
|
+
* Note: This function is cryptographically secure (it uses crypto.randomBytes internally)
|
|
1752
2155
|
*
|
|
1753
|
-
* @
|
|
2156
|
+
* @private internal helper function
|
|
2157
|
+
* @returns secure random token
|
|
1754
2158
|
*/
|
|
1755
|
-
function
|
|
1756
|
-
return
|
|
1757
|
-
var _a;
|
|
1758
|
-
acc.price.value += ((_a = item.price) === null || _a === void 0 ? void 0 : _a.value) || 0;
|
|
1759
|
-
for (const key of Object.keys(acc.input)) {
|
|
1760
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1761
|
-
//@ts-ignore
|
|
1762
|
-
if (item.input[key]) {
|
|
1763
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1764
|
-
//@ts-ignore
|
|
1765
|
-
acc.input[key].value += item.input[key].value || 0;
|
|
1766
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1767
|
-
//@ts-ignore
|
|
1768
|
-
if (item.input[key].isUncertain) {
|
|
1769
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1770
|
-
//@ts-ignore
|
|
1771
|
-
acc.input[key].isUncertain = true;
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
for (const key of Object.keys(acc.output)) {
|
|
1776
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1777
|
-
//@ts-ignore
|
|
1778
|
-
if (item.output[key]) {
|
|
1779
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1780
|
-
//@ts-ignore
|
|
1781
|
-
acc.output[key].value += item.output[key].value || 0;
|
|
1782
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1783
|
-
//@ts-ignore
|
|
1784
|
-
if (item.output[key].isUncertain) {
|
|
1785
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1786
|
-
//@ts-ignore
|
|
1787
|
-
acc.output[key].isUncertain = true;
|
|
1788
|
-
}
|
|
1789
|
-
}
|
|
1790
|
-
}
|
|
1791
|
-
return acc;
|
|
1792
|
-
}, deepClone(ZERO_USAGE));
|
|
2159
|
+
function $randomToken(randomness) {
|
|
2160
|
+
return crypto.randomBytes(randomness).toString('hex');
|
|
1793
2161
|
}
|
|
2162
|
+
/**
|
|
2163
|
+
* TODO: Maybe use nanoid instead https://github.com/ai/nanoid
|
|
2164
|
+
*/
|
|
1794
2165
|
|
|
1795
2166
|
/**
|
|
1796
|
-
*
|
|
2167
|
+
* This error indicates errors during the execution of the pipeline
|
|
1797
2168
|
*
|
|
1798
|
-
* @param llmTools LLM tools to be intercepted with usage counting
|
|
1799
|
-
* @returns LLM tools with same functionality with added total cost counting
|
|
1800
2169
|
* @public exported from `@promptbook/core`
|
|
1801
2170
|
*/
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
// TODO: [🧠] Maybe put here some suffix
|
|
1811
|
-
return llmTools.description;
|
|
1812
|
-
},
|
|
1813
|
-
async checkConfiguration() {
|
|
1814
|
-
return /* not await */ llmTools.checkConfiguration();
|
|
1815
|
-
},
|
|
1816
|
-
listModels() {
|
|
1817
|
-
return /* not await */ llmTools.listModels();
|
|
1818
|
-
},
|
|
1819
|
-
getTotalUsage() {
|
|
1820
|
-
// <- Note: [🥫] Not using getter `get totalUsage` but `getTotalUsage` to allow this object to be proxied
|
|
1821
|
-
return totalUsage;
|
|
1822
|
-
},
|
|
1823
|
-
};
|
|
1824
|
-
if (llmTools.callChatModel !== undefined) {
|
|
1825
|
-
proxyTools.callChatModel = async (prompt) => {
|
|
1826
|
-
// console.info('[🚕] callChatModel through countTotalUsage');
|
|
1827
|
-
const promptResult = await llmTools.callChatModel(prompt);
|
|
1828
|
-
totalUsage = addUsage(totalUsage, promptResult.usage);
|
|
1829
|
-
return promptResult;
|
|
1830
|
-
};
|
|
1831
|
-
}
|
|
1832
|
-
if (llmTools.callCompletionModel !== undefined) {
|
|
1833
|
-
proxyTools.callCompletionModel = async (prompt) => {
|
|
1834
|
-
// console.info('[🚕] callCompletionModel through countTotalUsage');
|
|
1835
|
-
const promptResult = await llmTools.callCompletionModel(prompt);
|
|
1836
|
-
totalUsage = addUsage(totalUsage, promptResult.usage);
|
|
1837
|
-
return promptResult;
|
|
1838
|
-
};
|
|
1839
|
-
}
|
|
1840
|
-
if (llmTools.callEmbeddingModel !== undefined) {
|
|
1841
|
-
proxyTools.callEmbeddingModel = async (prompt) => {
|
|
1842
|
-
// console.info('[🚕] callEmbeddingModel through countTotalUsage');
|
|
1843
|
-
const promptResult = await llmTools.callEmbeddingModel(prompt);
|
|
1844
|
-
totalUsage = addUsage(totalUsage, promptResult.usage);
|
|
1845
|
-
return promptResult;
|
|
1846
|
-
};
|
|
2171
|
+
class PipelineExecutionError extends Error {
|
|
2172
|
+
constructor(message) {
|
|
2173
|
+
// Added id parameter
|
|
2174
|
+
super(message);
|
|
2175
|
+
this.name = 'PipelineExecutionError';
|
|
2176
|
+
// TODO: [🐙] DRY - Maybe $randomId
|
|
2177
|
+
this.id = `error-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid simmilar char conflicts */)}`;
|
|
2178
|
+
Object.setPrototypeOf(this, PipelineExecutionError.prototype);
|
|
1847
2179
|
}
|
|
1848
|
-
// <- Note: [🤖]
|
|
1849
|
-
return proxyTools;
|
|
1850
2180
|
}
|
|
1851
2181
|
/**
|
|
1852
|
-
* TODO:
|
|
1853
|
-
* TODO: [🧠] Is there some meaningfull way how to test this util
|
|
1854
|
-
* TODO: [🧠][🌯] Maybe a way how to hide ability to `get totalUsage`
|
|
1855
|
-
* > const [llmToolsWithUsage,getUsage] = countTotalUsage(llmTools);
|
|
1856
|
-
* TODO: [👷♂️] @@@ Manual about construction of llmTools
|
|
2182
|
+
* TODO: !!!!!! Add id to all errors
|
|
1857
2183
|
*/
|
|
1858
2184
|
|
|
1859
2185
|
/**
|
|
1860
|
-
* This error
|
|
2186
|
+
* This error indicates that the promptbook object has valid syntax (=can be parsed) but contains logical errors (like circular dependencies)
|
|
1861
2187
|
*
|
|
1862
2188
|
* @public exported from `@promptbook/core`
|
|
1863
2189
|
*/
|
|
1864
|
-
class
|
|
2190
|
+
class PipelineLogicError extends Error {
|
|
1865
2191
|
constructor(message) {
|
|
1866
|
-
super(
|
|
1867
|
-
|
|
2192
|
+
super(message);
|
|
2193
|
+
this.name = 'PipelineLogicError';
|
|
2194
|
+
Object.setPrototypeOf(this, PipelineLogicError.prototype);
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
1868
2197
|
|
|
1869
|
-
|
|
2198
|
+
/**
|
|
2199
|
+
* This error indicates errors in referencing promptbooks between each other
|
|
2200
|
+
*
|
|
2201
|
+
* @public exported from `@promptbook/core`
|
|
2202
|
+
*/
|
|
2203
|
+
class PipelineUrlError extends Error {
|
|
2204
|
+
constructor(message) {
|
|
2205
|
+
super(message);
|
|
2206
|
+
this.name = 'PipelineUrlError';
|
|
2207
|
+
Object.setPrototypeOf(this, PipelineUrlError.prototype);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
1870
2210
|
|
|
1871
|
-
|
|
1872
|
-
|
|
2211
|
+
/**
|
|
2212
|
+
* Error thrown when a fetch request fails
|
|
2213
|
+
*
|
|
2214
|
+
* @public exported from `@promptbook/core`
|
|
2215
|
+
*/
|
|
2216
|
+
class PromptbookFetchError extends Error {
|
|
2217
|
+
constructor(message) {
|
|
2218
|
+
super(message);
|
|
2219
|
+
this.name = 'PromptbookFetchError';
|
|
2220
|
+
Object.setPrototypeOf(this, PromptbookFetchError.prototype);
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
1873
2223
|
|
|
1874
|
-
|
|
2224
|
+
/**
|
|
2225
|
+
* Index of all custom errors
|
|
2226
|
+
*
|
|
2227
|
+
* @public exported from `@promptbook/core`
|
|
2228
|
+
*/
|
|
2229
|
+
const PROMPTBOOK_ERRORS = {
|
|
2230
|
+
AbstractFormatError,
|
|
2231
|
+
CsvFormatError,
|
|
2232
|
+
CollectionError,
|
|
2233
|
+
EnvironmentMismatchError,
|
|
2234
|
+
ExpectError,
|
|
2235
|
+
KnowledgeScrapeError,
|
|
2236
|
+
LimitReachedError,
|
|
2237
|
+
MissingToolsError,
|
|
2238
|
+
NotFoundError,
|
|
2239
|
+
NotYetImplementedError,
|
|
2240
|
+
ParseError,
|
|
2241
|
+
PipelineExecutionError,
|
|
2242
|
+
PipelineLogicError,
|
|
2243
|
+
PipelineUrlError,
|
|
2244
|
+
UnexpectedError,
|
|
2245
|
+
// TODO: [🪑]> VersionMismatchError,
|
|
2246
|
+
};
|
|
2247
|
+
/**
|
|
2248
|
+
* Index of all javascript errors
|
|
2249
|
+
*
|
|
2250
|
+
* @private for internal usage
|
|
2251
|
+
*/
|
|
2252
|
+
const COMMON_JAVASCRIPT_ERRORS = {
|
|
2253
|
+
Error,
|
|
2254
|
+
EvalError,
|
|
2255
|
+
RangeError,
|
|
2256
|
+
ReferenceError,
|
|
2257
|
+
SyntaxError,
|
|
2258
|
+
TypeError,
|
|
2259
|
+
URIError,
|
|
2260
|
+
AggregateError,
|
|
2261
|
+
AuthenticationError,
|
|
2262
|
+
PromptbookFetchError,
|
|
2263
|
+
/*
|
|
2264
|
+
Note: Not widely supported
|
|
2265
|
+
> InternalError,
|
|
2266
|
+
> ModuleError,
|
|
2267
|
+
> HeapError,
|
|
2268
|
+
> WebAssemblyCompileError,
|
|
2269
|
+
> WebAssemblyRuntimeError,
|
|
2270
|
+
*/
|
|
2271
|
+
};
|
|
2272
|
+
/**
|
|
2273
|
+
* Index of all errors
|
|
2274
|
+
*
|
|
2275
|
+
* @private for internal usage
|
|
2276
|
+
*/
|
|
2277
|
+
const ALL_ERRORS = {
|
|
2278
|
+
...PROMPTBOOK_ERRORS,
|
|
2279
|
+
...COMMON_JAVASCRIPT_ERRORS,
|
|
2280
|
+
};
|
|
2281
|
+
/**
|
|
2282
|
+
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
2283
|
+
*/
|
|
2284
|
+
|
|
2285
|
+
/**
|
|
2286
|
+
* Deserializes the error object
|
|
2287
|
+
*
|
|
2288
|
+
* @public exported from `@promptbook/utils`
|
|
2289
|
+
*/
|
|
2290
|
+
function deserializeError(error) {
|
|
2291
|
+
const { name, stack, id } = error; // Added id
|
|
2292
|
+
let { message } = error;
|
|
2293
|
+
let ErrorClass = ALL_ERRORS[error.name];
|
|
2294
|
+
if (ErrorClass === undefined) {
|
|
2295
|
+
ErrorClass = Error;
|
|
2296
|
+
message = `${name}: ${message}`;
|
|
2297
|
+
}
|
|
2298
|
+
if (stack !== undefined && stack !== '') {
|
|
2299
|
+
message = spaceTrim__default["default"]((block) => `
|
|
2300
|
+
${block(message)}
|
|
1875
2301
|
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
2302
|
+
Original stack trace:
|
|
2303
|
+
${block(stack || '')}
|
|
2304
|
+
`);
|
|
1879
2305
|
}
|
|
2306
|
+
const deserializedError = new ErrorClass(message);
|
|
2307
|
+
deserializedError.id = id; // Assign id to the error object
|
|
2308
|
+
return deserializedError;
|
|
1880
2309
|
}
|
|
1881
2310
|
|
|
1882
2311
|
/**
|
|
1883
|
-
*
|
|
2312
|
+
* Creates a connection to the remote proxy server.
|
|
1884
2313
|
*
|
|
1885
|
-
* Note:
|
|
2314
|
+
* Note: This function creates a connection to the remote server and returns a socket but responsibility of closing the connection is on the caller
|
|
1886
2315
|
*
|
|
1887
|
-
*
|
|
2316
|
+
* @private internal utility function
|
|
1888
2317
|
*/
|
|
1889
|
-
function
|
|
1890
|
-
|
|
2318
|
+
async function createRemoteClient(options) {
|
|
2319
|
+
const { remoteServerUrl } = options;
|
|
2320
|
+
let path = new URL(remoteServerUrl).pathname;
|
|
2321
|
+
if (path.endsWith('/')) {
|
|
2322
|
+
path = path.slice(0, -1);
|
|
2323
|
+
}
|
|
2324
|
+
path = `${path}/socket.io`;
|
|
2325
|
+
return new Promise((resolve, reject) => {
|
|
2326
|
+
const socket = socket_ioClient.io(remoteServerUrl, {
|
|
2327
|
+
retries: CONNECTION_RETRIES_LIMIT,
|
|
2328
|
+
timeout: CONNECTION_TIMEOUT_MS,
|
|
2329
|
+
path,
|
|
2330
|
+
transports: [/*'websocket', <- TODO: [🌬] Make websocket transport work */ 'polling'],
|
|
2331
|
+
});
|
|
2332
|
+
// console.log('Connecting to', this.options.remoteServerUrl.href, { socket });
|
|
2333
|
+
socket.on('connect', () => {
|
|
2334
|
+
resolve(socket);
|
|
2335
|
+
});
|
|
2336
|
+
// TODO: [💩] Better timeout handling
|
|
2337
|
+
setTimeout(() => {
|
|
2338
|
+
reject(new Error(`Timeout while connecting to ${remoteServerUrl}`));
|
|
2339
|
+
}, CONNECTION_TIMEOUT_MS);
|
|
2340
|
+
});
|
|
1891
2341
|
}
|
|
1892
2342
|
|
|
1893
2343
|
/**
|
|
1894
|
-
*
|
|
2344
|
+
* Remote server is a proxy server that uses its execution tools internally and exposes the executor interface externally.
|
|
1895
2345
|
*
|
|
1896
|
-
*
|
|
1897
|
-
*
|
|
1898
|
-
*
|
|
1899
|
-
* @
|
|
1900
|
-
* @public exported from `@promptbook/
|
|
2346
|
+
* You can simply use `RemoteExecutionTools` on client-side javascript and connect to your remote server.
|
|
2347
|
+
* This is useful to make all logic on browser side but not expose your API keys or no need to use customer's GPU.
|
|
2348
|
+
*
|
|
2349
|
+
* @see https://github.com/webgptorg/promptbook#remote-server
|
|
2350
|
+
* @public exported from `@promptbook/remote-client`
|
|
1901
2351
|
*/
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
2352
|
+
class RemoteLlmExecutionTools {
|
|
2353
|
+
/* <- TODO: [🍚] `, Destroyable` */
|
|
2354
|
+
constructor(options) {
|
|
2355
|
+
this.options = options;
|
|
2356
|
+
}
|
|
2357
|
+
get title() {
|
|
2358
|
+
// TODO: [🧠] Maybe fetch title+description from the remote server (as well as if model methods are defined)
|
|
2359
|
+
return 'Remote server';
|
|
2360
|
+
}
|
|
2361
|
+
get description() {
|
|
2362
|
+
return 'Use all models by your remote server';
|
|
2363
|
+
}
|
|
2364
|
+
/**
|
|
2365
|
+
* Check the configuration of all execution tools
|
|
2366
|
+
*/
|
|
2367
|
+
async checkConfiguration() {
|
|
2368
|
+
const socket = await createRemoteClient(this.options);
|
|
2369
|
+
socket.disconnect();
|
|
2370
|
+
// TODO: [main] !!3 Check version of the remote server and compatibility
|
|
2371
|
+
// TODO: [🎍] Send checkConfiguration
|
|
2372
|
+
}
|
|
2373
|
+
/**
|
|
2374
|
+
* List all available models that can be used
|
|
2375
|
+
*/
|
|
2376
|
+
async listModels() {
|
|
2377
|
+
// TODO: [👒] Listing models (and checking configuration) probbably should go through REST API not Socket.io
|
|
2378
|
+
const socket = await createRemoteClient(this.options);
|
|
2379
|
+
socket.emit('listModels-request', {
|
|
2380
|
+
identification: this.options.identification,
|
|
2381
|
+
} /* <- Note: [🤛] */);
|
|
2382
|
+
const promptResult = await new Promise((resolve, reject) => {
|
|
2383
|
+
socket.on('listModels-response', (response) => {
|
|
2384
|
+
resolve(response.models);
|
|
2385
|
+
socket.disconnect();
|
|
2386
|
+
});
|
|
2387
|
+
socket.on('error', (error) => {
|
|
2388
|
+
reject(deserializeError(error));
|
|
2389
|
+
socket.disconnect();
|
|
2390
|
+
});
|
|
2391
|
+
});
|
|
2392
|
+
socket.disconnect();
|
|
2393
|
+
return promptResult;
|
|
2394
|
+
}
|
|
2395
|
+
/**
|
|
2396
|
+
* Calls remote proxy server to use a chat model
|
|
2397
|
+
*/
|
|
2398
|
+
callChatModel(prompt) {
|
|
2399
|
+
if (this.options.isVerbose) {
|
|
2400
|
+
console.info(`🖋 Remote callChatModel call`);
|
|
1919
2401
|
}
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
2402
|
+
return /* not await */ this.callCommonModel(prompt);
|
|
2403
|
+
}
|
|
2404
|
+
/**
|
|
2405
|
+
* Calls remote proxy server to use a completion model
|
|
2406
|
+
*/
|
|
2407
|
+
callCompletionModel(prompt) {
|
|
2408
|
+
if (this.options.isVerbose) {
|
|
2409
|
+
console.info(`💬 Remote callCompletionModel call`);
|
|
1923
2410
|
}
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
2411
|
+
return /* not await */ this.callCommonModel(prompt);
|
|
2412
|
+
}
|
|
2413
|
+
/**
|
|
2414
|
+
* Calls remote proxy server to use a embedding model
|
|
2415
|
+
*/
|
|
2416
|
+
callEmbeddingModel(prompt) {
|
|
2417
|
+
if (this.options.isVerbose) {
|
|
2418
|
+
console.info(`💬 Remote callEmbeddingModel call`);
|
|
1929
2419
|
}
|
|
1930
|
-
|
|
1931
|
-
|
|
2420
|
+
return /* not await */ this.callCommonModel(prompt);
|
|
2421
|
+
}
|
|
2422
|
+
// <- Note: [🤖] callXxxModel
|
|
2423
|
+
/**
|
|
2424
|
+
* Calls remote proxy server to use both completion or chat model
|
|
2425
|
+
*/
|
|
2426
|
+
async callCommonModel(prompt) {
|
|
2427
|
+
const socket = await createRemoteClient(this.options);
|
|
2428
|
+
socket.emit('prompt-request', {
|
|
2429
|
+
identification: this.options.identification,
|
|
2430
|
+
prompt,
|
|
2431
|
+
} /* <- Note: [🤛] */);
|
|
2432
|
+
const promptResult = await new Promise((resolve, reject) => {
|
|
2433
|
+
socket.on('prompt-response', (response) => {
|
|
2434
|
+
resolve(response.promptResult);
|
|
2435
|
+
socket.disconnect();
|
|
2436
|
+
});
|
|
2437
|
+
socket.on('error', (error) => {
|
|
2438
|
+
reject(deserializeError(error));
|
|
2439
|
+
socket.disconnect();
|
|
2440
|
+
});
|
|
2441
|
+
});
|
|
2442
|
+
socket.disconnect();
|
|
2443
|
+
return promptResult;
|
|
1932
2444
|
}
|
|
1933
|
-
normalizedName = normalizedName.replace(/_+/g, '_');
|
|
1934
|
-
normalizedName = normalizedName.replace(/_?\/_?/g, '/');
|
|
1935
|
-
normalizedName = normalizedName.replace(/^_/, '');
|
|
1936
|
-
normalizedName = normalizedName.replace(/_$/, '');
|
|
1937
|
-
return normalizedName;
|
|
1938
2445
|
}
|
|
1939
2446
|
/**
|
|
1940
|
-
* TODO:
|
|
1941
|
-
*
|
|
1942
|
-
*
|
|
1943
|
-
*
|
|
1944
|
-
*
|
|
1945
|
-
* TODO: [
|
|
1946
|
-
*/
|
|
1947
|
-
|
|
1948
|
-
/**
|
|
1949
|
-
* @@@
|
|
1950
|
-
*
|
|
1951
|
-
* @param text @@@
|
|
1952
|
-
* @returns @@@
|
|
1953
|
-
* @example 'hello_world'
|
|
1954
|
-
* @example 'i_love_promptbook'
|
|
1955
|
-
* @public exported from `@promptbook/utils`
|
|
2447
|
+
* TODO: Maybe use `$exportJson`
|
|
2448
|
+
* TODO: [🧠][🛍] Maybe not `isAnonymous: boolean` BUT `mode: 'ANONYMOUS'|'COLLECTION'`
|
|
2449
|
+
* TODO: [🍓] Allow to list compatible models with each variant
|
|
2450
|
+
* TODO: [🗯] RemoteLlmExecutionTools should extend Destroyable and implement IDestroyable
|
|
2451
|
+
* TODO: [🧠][🌰] Allow to pass `title` for tracking purposes
|
|
2452
|
+
* TODO: [🧠] Maybe remove `@promptbook/remote-client` and just use `@promptbook/core`
|
|
1956
2453
|
*/
|
|
1957
|
-
function normalizeTo_snake_case(text) {
|
|
1958
|
-
return normalizeTo_SCREAMING_CASE(text).toLowerCase();
|
|
1959
|
-
}
|
|
1960
2454
|
|
|
1961
2455
|
/**
|
|
1962
|
-
*
|
|
1963
|
-
*
|
|
1964
|
-
* Note: `$` is used to indicate that this function is not a pure function - it accesses and adds variables in global scope.
|
|
2456
|
+
* Stores data in memory (HEAP)
|
|
1965
2457
|
*
|
|
1966
|
-
* @
|
|
2458
|
+
* @public exported from `@promptbook/core`
|
|
1967
2459
|
*/
|
|
1968
|
-
class
|
|
1969
|
-
constructor(
|
|
1970
|
-
this.
|
|
1971
|
-
const storageName = `_promptbook_${normalizeTo_snake_case(registerName)}`;
|
|
1972
|
-
const globalScope = $getGlobalScope();
|
|
1973
|
-
if (globalScope[storageName] === undefined) {
|
|
1974
|
-
globalScope[storageName] = [];
|
|
1975
|
-
}
|
|
1976
|
-
else if (!Array.isArray(globalScope[storageName])) {
|
|
1977
|
-
throw new UnexpectedError(`Expected (global) ${storageName} to be an array, but got ${typeof globalScope[storageName]}`);
|
|
1978
|
-
}
|
|
1979
|
-
this.storage = globalScope[storageName];
|
|
2460
|
+
class MemoryStorage {
|
|
2461
|
+
constructor() {
|
|
2462
|
+
this.storage = {};
|
|
1980
2463
|
}
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
2464
|
+
/**
|
|
2465
|
+
* Returns the number of key/value pairs currently present in the list associated with the object.
|
|
2466
|
+
*/
|
|
2467
|
+
get length() {
|
|
2468
|
+
return Object.keys(this.storage).length;
|
|
1984
2469
|
}
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
return
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2470
|
+
/**
|
|
2471
|
+
* Empties the list associated with the object of all key/value pairs, if there are any.
|
|
2472
|
+
*/
|
|
2473
|
+
clear() {
|
|
2474
|
+
this.storage = {};
|
|
2475
|
+
}
|
|
2476
|
+
/**
|
|
2477
|
+
* Returns the current value associated with the given key, or null if the given key does not exist in the list associated with the object.
|
|
2478
|
+
*/
|
|
2479
|
+
getItem(key) {
|
|
2480
|
+
return this.storage[key] || null;
|
|
2481
|
+
}
|
|
2482
|
+
/**
|
|
2483
|
+
* Returns the name of the nth key in the list, or null if n is greater than or equal to the number of key/value pairs in the object.
|
|
2484
|
+
*/
|
|
2485
|
+
key(index) {
|
|
2486
|
+
return Object.keys(this.storage)[index] || null;
|
|
2487
|
+
}
|
|
2488
|
+
/**
|
|
2489
|
+
* Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.
|
|
2490
|
+
*/
|
|
2491
|
+
setItem(key, value) {
|
|
2492
|
+
this.storage[key] = value;
|
|
2493
|
+
}
|
|
2494
|
+
/**
|
|
2495
|
+
* Removes the key/value pair with the given key from the list associated with the object, if a key/value pair with the given key exists.
|
|
2496
|
+
*/
|
|
2497
|
+
removeItem(key) {
|
|
2498
|
+
delete this.storage[key];
|
|
2006
2499
|
}
|
|
2007
2500
|
}
|
|
2008
2501
|
|
|
2009
2502
|
/**
|
|
2010
|
-
*
|
|
2503
|
+
* Simple wrapper `new Date().toISOString()`
|
|
2011
2504
|
*
|
|
2012
|
-
* Note: `$` is used to indicate that this
|
|
2013
|
-
*
|
|
2014
|
-
* @
|
|
2015
|
-
|
|
2016
|
-
const $llmToolsMetadataRegister = new $Register('llm_tools_metadata');
|
|
2017
|
-
/**
|
|
2018
|
-
* TODO: [®] DRY Register logic
|
|
2505
|
+
* Note: `$` is used to indicate that this function is not a pure function - it is not deterministic because it depends on the current time
|
|
2506
|
+
*
|
|
2507
|
+
* @returns string_date branded type
|
|
2508
|
+
* @public exported from `@promptbook/utils`
|
|
2019
2509
|
*/
|
|
2510
|
+
function $getCurrentDate() {
|
|
2511
|
+
return new Date().toISOString();
|
|
2512
|
+
}
|
|
2020
2513
|
|
|
2021
2514
|
/**
|
|
2022
|
-
*
|
|
2515
|
+
* Intercepts LLM tools and counts total usage of the tools
|
|
2023
2516
|
*
|
|
2024
|
-
* Note:
|
|
2025
|
-
*
|
|
2517
|
+
* Note: It can take extended `LlmExecutionTools` and cache the
|
|
2518
|
+
*
|
|
2519
|
+
* @param llmTools LLM tools to be intercepted with usage counting, it can contain extra methods like `totalUsage`
|
|
2520
|
+
* @returns LLM tools with same functionality with added total cost counting
|
|
2521
|
+
* @public exported from `@promptbook/core`
|
|
2026
2522
|
*/
|
|
2027
|
-
function
|
|
2028
|
-
|
|
2029
|
-
|
|
2523
|
+
function cacheLlmTools(llmTools, options = {}) {
|
|
2524
|
+
const { storage = new MemoryStorage(), isCacheReloaded = false } = options;
|
|
2525
|
+
const proxyTools = {
|
|
2526
|
+
...llmTools,
|
|
2527
|
+
// <- Note: [🥫]
|
|
2528
|
+
get title() {
|
|
2529
|
+
// TODO: [🧠] Maybe put here some suffix
|
|
2530
|
+
return llmTools.title;
|
|
2531
|
+
},
|
|
2532
|
+
get description() {
|
|
2533
|
+
// TODO: [🧠] Maybe put here some suffix
|
|
2534
|
+
return llmTools.description;
|
|
2535
|
+
},
|
|
2536
|
+
listModels() {
|
|
2537
|
+
// TODO: [🧠] Should be model listing also cached?
|
|
2538
|
+
return /* not await */ llmTools.listModels();
|
|
2539
|
+
},
|
|
2540
|
+
};
|
|
2541
|
+
const callCommonModel = async (prompt) => {
|
|
2542
|
+
const { parameters, content, modelRequirements } = prompt;
|
|
2543
|
+
// <- Note: These are relevant things from the prompt that the cache key should depend on.
|
|
2544
|
+
const key = titleToName(prompt.title.substring(0, MAX_FILENAME_LENGTH - 10) +
|
|
2545
|
+
'-' +
|
|
2546
|
+
sha256__default["default"](hexEncoder__default["default"].parse(JSON.stringify({ parameters, content, modelRequirements }))).toString( /* hex */));
|
|
2547
|
+
const cacheItem = !isCacheReloaded ? await storage.getItem(key) : null;
|
|
2548
|
+
if (cacheItem) {
|
|
2549
|
+
return cacheItem.promptResult;
|
|
2550
|
+
}
|
|
2551
|
+
let promptResult;
|
|
2552
|
+
variant: switch (prompt.modelRequirements.modelVariant) {
|
|
2553
|
+
case 'CHAT':
|
|
2554
|
+
promptResult = await llmTools.callChatModel(prompt);
|
|
2555
|
+
break variant;
|
|
2556
|
+
case 'COMPLETION':
|
|
2557
|
+
promptResult = await llmTools.callCompletionModel(prompt);
|
|
2558
|
+
break variant;
|
|
2559
|
+
case 'EMBEDDING':
|
|
2560
|
+
promptResult = await llmTools.callEmbeddingModel(prompt);
|
|
2561
|
+
break variant;
|
|
2562
|
+
// <- case [🤖]:
|
|
2563
|
+
default:
|
|
2564
|
+
throw new PipelineExecutionError(`Unknown model variant "${prompt.modelRequirements.modelVariant}"`);
|
|
2565
|
+
}
|
|
2566
|
+
// TODO: [🧠] !!5 How to do timing in mixed cache / non-cache situation
|
|
2567
|
+
// promptResult.timing: FromtoItems
|
|
2568
|
+
await storage.setItem(key, {
|
|
2569
|
+
date: $getCurrentDate(),
|
|
2570
|
+
promptbookVersion: PROMPTBOOK_ENGINE_VERSION,
|
|
2571
|
+
prompt,
|
|
2572
|
+
promptResult,
|
|
2573
|
+
});
|
|
2574
|
+
return promptResult;
|
|
2575
|
+
};
|
|
2576
|
+
if (llmTools.callChatModel !== undefined) {
|
|
2577
|
+
proxyTools.callChatModel = async (prompt) => {
|
|
2578
|
+
return /* not await */ callCommonModel(prompt);
|
|
2579
|
+
};
|
|
2030
2580
|
}
|
|
2031
|
-
if (
|
|
2032
|
-
|
|
2581
|
+
if (llmTools.callCompletionModel !== undefined) {
|
|
2582
|
+
proxyTools.callCompletionModel = async (prompt) => {
|
|
2583
|
+
return /* not await */ callCommonModel(prompt);
|
|
2584
|
+
};
|
|
2033
2585
|
}
|
|
2034
|
-
|
|
2586
|
+
if (llmTools.callEmbeddingModel !== undefined) {
|
|
2587
|
+
proxyTools.callEmbeddingModel = async (prompt) => {
|
|
2588
|
+
return /* not await */ callCommonModel(prompt);
|
|
2589
|
+
};
|
|
2590
|
+
}
|
|
2591
|
+
// <- Note: [🤖]
|
|
2592
|
+
return proxyTools;
|
|
2035
2593
|
}
|
|
2036
2594
|
/**
|
|
2037
|
-
* TODO: [
|
|
2595
|
+
* TODO: [🧠][💸] Maybe make some common abstraction `interceptLlmTools` and use here (or use javascript Proxy?)
|
|
2596
|
+
* TODO: [🧠] Is there some meaningfull way how to test this util
|
|
2597
|
+
* TODO: [👷♂️] @@@ Manual about construction of llmTools
|
|
2598
|
+
* @@@ write discussion about this and storages
|
|
2599
|
+
* @@@ write how to combine multiple interceptors
|
|
2038
2600
|
*/
|
|
2039
2601
|
|
|
2040
2602
|
/**
|
|
2041
|
-
*
|
|
2603
|
+
* Represents the uncertain value
|
|
2042
2604
|
*
|
|
2043
|
-
* Note: `$` is used to indicate that this interacts with the global scope
|
|
2044
|
-
* @singleton Only one instance of each register is created per build, but thare can be more @@@
|
|
2045
2605
|
* @public exported from `@promptbook/core`
|
|
2046
2606
|
*/
|
|
2047
|
-
const
|
|
2607
|
+
const ZERO_VALUE = $deepFreeze({ value: 0 });
|
|
2048
2608
|
/**
|
|
2049
|
-
*
|
|
2609
|
+
* Represents the uncertain value
|
|
2610
|
+
*
|
|
2611
|
+
* @public exported from `@promptbook/core`
|
|
2050
2612
|
*/
|
|
2051
|
-
|
|
2613
|
+
const UNCERTAIN_ZERO_VALUE = $deepFreeze({ value: 0, isUncertain: true });
|
|
2052
2614
|
/**
|
|
2053
|
-
*
|
|
2615
|
+
* Represents the usage with no resources consumed
|
|
2054
2616
|
*
|
|
2055
|
-
*
|
|
2617
|
+
* @public exported from `@promptbook/core`
|
|
2056
2618
|
*/
|
|
2057
|
-
|
|
2619
|
+
const ZERO_USAGE = $deepFreeze({
|
|
2620
|
+
price: ZERO_VALUE,
|
|
2621
|
+
input: {
|
|
2622
|
+
tokensCount: ZERO_VALUE,
|
|
2623
|
+
charactersCount: ZERO_VALUE,
|
|
2624
|
+
wordsCount: ZERO_VALUE,
|
|
2625
|
+
sentencesCount: ZERO_VALUE,
|
|
2626
|
+
linesCount: ZERO_VALUE,
|
|
2627
|
+
paragraphsCount: ZERO_VALUE,
|
|
2628
|
+
pagesCount: ZERO_VALUE,
|
|
2629
|
+
},
|
|
2630
|
+
output: {
|
|
2631
|
+
tokensCount: ZERO_VALUE,
|
|
2632
|
+
charactersCount: ZERO_VALUE,
|
|
2633
|
+
wordsCount: ZERO_VALUE,
|
|
2634
|
+
sentencesCount: ZERO_VALUE,
|
|
2635
|
+
linesCount: ZERO_VALUE,
|
|
2636
|
+
paragraphsCount: ZERO_VALUE,
|
|
2637
|
+
pagesCount: ZERO_VALUE,
|
|
2638
|
+
},
|
|
2639
|
+
});
|
|
2058
2640
|
/**
|
|
2059
|
-
*
|
|
2060
|
-
*
|
|
2061
|
-
* Note: `$` is used to indicate that this variable is making side effect
|
|
2641
|
+
* Represents the usage with unknown resources consumed
|
|
2062
2642
|
*
|
|
2063
|
-
* @
|
|
2643
|
+
* @public exported from `@promptbook/core`
|
|
2064
2644
|
*/
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2645
|
+
const UNCERTAIN_USAGE = $deepFreeze({
|
|
2646
|
+
price: UNCERTAIN_ZERO_VALUE,
|
|
2647
|
+
input: {
|
|
2648
|
+
tokensCount: UNCERTAIN_ZERO_VALUE,
|
|
2649
|
+
charactersCount: UNCERTAIN_ZERO_VALUE,
|
|
2650
|
+
wordsCount: UNCERTAIN_ZERO_VALUE,
|
|
2651
|
+
sentencesCount: UNCERTAIN_ZERO_VALUE,
|
|
2652
|
+
linesCount: UNCERTAIN_ZERO_VALUE,
|
|
2653
|
+
paragraphsCount: UNCERTAIN_ZERO_VALUE,
|
|
2654
|
+
pagesCount: UNCERTAIN_ZERO_VALUE,
|
|
2655
|
+
},
|
|
2656
|
+
output: {
|
|
2657
|
+
tokensCount: UNCERTAIN_ZERO_VALUE,
|
|
2658
|
+
charactersCount: UNCERTAIN_ZERO_VALUE,
|
|
2659
|
+
wordsCount: UNCERTAIN_ZERO_VALUE,
|
|
2660
|
+
sentencesCount: UNCERTAIN_ZERO_VALUE,
|
|
2661
|
+
linesCount: UNCERTAIN_ZERO_VALUE,
|
|
2662
|
+
paragraphsCount: UNCERTAIN_ZERO_VALUE,
|
|
2663
|
+
pagesCount: UNCERTAIN_ZERO_VALUE,
|
|
2664
|
+
},
|
|
2665
|
+
});
|
|
2068
2666
|
/**
|
|
2069
|
-
*
|
|
2667
|
+
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
2668
|
+
*/
|
|
2669
|
+
|
|
2670
|
+
/**
|
|
2671
|
+
* Function `addUsage` will add multiple usages into one
|
|
2070
2672
|
*
|
|
2071
|
-
* Note:
|
|
2673
|
+
* Note: If you provide 0 values, it returns ZERO_USAGE
|
|
2072
2674
|
*
|
|
2073
|
-
* @
|
|
2675
|
+
* @public exported from `@promptbook/core`
|
|
2074
2676
|
*/
|
|
2075
|
-
function
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
for (const { packageName, className } of $llmToolsRegister.list()) {
|
|
2095
|
-
if (all.some((item) => item.packageName === packageName && item.className === className)) {
|
|
2096
|
-
continue;
|
|
2097
|
-
}
|
|
2098
|
-
all.push({ packageName, className });
|
|
2099
|
-
}
|
|
2100
|
-
const metadata = all.map((metadata) => {
|
|
2101
|
-
var _a, _b;
|
|
2102
|
-
const isMetadataAviailable = $llmToolsMetadataRegister
|
|
2103
|
-
.list()
|
|
2104
|
-
.find(({ packageName, className }) => metadata.packageName === packageName && metadata.className === className);
|
|
2105
|
-
const isInstalled = $llmToolsRegister
|
|
2106
|
-
.list()
|
|
2107
|
-
.find(({ packageName, className }) => metadata.packageName === packageName && metadata.className === className);
|
|
2108
|
-
const isFullyConfigured = ((_a = metadata.envVariables) === null || _a === void 0 ? void 0 : _a.every((envVariableName) => env[envVariableName] !== undefined)) || false;
|
|
2109
|
-
const isPartiallyConfigured = ((_b = metadata.envVariables) === null || _b === void 0 ? void 0 : _b.some((envVariableName) => env[envVariableName] !== undefined)) || false;
|
|
2110
|
-
// <- Note: [🗨]
|
|
2111
|
-
return { ...metadata, isMetadataAviailable, isInstalled, isFullyConfigured, isPartiallyConfigured };
|
|
2112
|
-
});
|
|
2113
|
-
const usedEnvMessage = $usedEnvFilename === null ? `Unknown \`.env\` file` : `Used \`.env\` file:\n${$usedEnvFilename}`;
|
|
2114
|
-
if (metadata.length === 0) {
|
|
2115
|
-
return spaceTrim__default["default"]((block) => `
|
|
2116
|
-
No LLM providers are available.
|
|
2117
|
-
|
|
2118
|
-
${block(usedEnvMessage)}
|
|
2119
|
-
`);
|
|
2120
|
-
}
|
|
2121
|
-
return spaceTrim__default["default"]((block) => `
|
|
2122
|
-
|
|
2123
|
-
${block(usedEnvMessage)}
|
|
2124
|
-
|
|
2125
|
-
Relevant environment variables:
|
|
2126
|
-
${block(Object.keys(env)
|
|
2127
|
-
.filter((envVariableName) => metadata.some(({ envVariables }) => envVariables === null || envVariables === void 0 ? void 0 : envVariables.includes(envVariableName)))
|
|
2128
|
-
.map((envVariableName) => `- \`${envVariableName}\``)
|
|
2129
|
-
.join('\n'))}
|
|
2130
|
-
|
|
2131
|
-
Available LLM providers are:
|
|
2132
|
-
${block(metadata
|
|
2133
|
-
.map(({ title, packageName, className, envVariables, isMetadataAviailable, isInstalled, isFullyConfigured, isPartiallyConfigured, }, i) => {
|
|
2134
|
-
const morePieces = [];
|
|
2135
|
-
if (just(false)) ;
|
|
2136
|
-
else if (!isMetadataAviailable && !isInstalled) {
|
|
2137
|
-
// TODO: [�][�] Maybe do allow to do auto-install if package not registered and not found
|
|
2138
|
-
morePieces.push(`Not installed and no metadata, looks like a unexpected behavior`);
|
|
2139
|
-
}
|
|
2140
|
-
else if (isMetadataAviailable && !isInstalled) {
|
|
2141
|
-
// TODO: [�][�]
|
|
2142
|
-
morePieces.push(`Not installed`);
|
|
2143
|
-
}
|
|
2144
|
-
else if (!isMetadataAviailable && isInstalled) {
|
|
2145
|
-
morePieces.push(`No metadata but installed, looks like a unexpected behavior`);
|
|
2146
|
-
}
|
|
2147
|
-
else if (isMetadataAviailable && isInstalled) {
|
|
2148
|
-
morePieces.push(`Installed`);
|
|
2149
|
-
}
|
|
2150
|
-
else {
|
|
2151
|
-
morePieces.push(`unknown state, looks like a unexpected behavior`);
|
|
2152
|
-
} /* not else */
|
|
2153
|
-
if (isFullyConfigured) {
|
|
2154
|
-
morePieces.push(`Configured`);
|
|
2155
|
-
}
|
|
2156
|
-
else if (isPartiallyConfigured) {
|
|
2157
|
-
morePieces.push(`Partially confugured, missing ${envVariables === null || envVariables === void 0 ? void 0 : envVariables.filter((envVariable) => env[envVariable] === undefined).join(' + ')}`);
|
|
2158
|
-
}
|
|
2159
|
-
else {
|
|
2160
|
-
if (envVariables !== null) {
|
|
2161
|
-
morePieces.push(`Not configured, to configure set env ${envVariables === null || envVariables === void 0 ? void 0 : envVariables.join(' + ')}`);
|
|
2677
|
+
function addUsage(...usageItems) {
|
|
2678
|
+
return usageItems.reduce((acc, item) => {
|
|
2679
|
+
var _a;
|
|
2680
|
+
acc.price.value += ((_a = item.price) === null || _a === void 0 ? void 0 : _a.value) || 0;
|
|
2681
|
+
for (const key of Object.keys(acc.input)) {
|
|
2682
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2683
|
+
//@ts-ignore
|
|
2684
|
+
if (item.input[key]) {
|
|
2685
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2686
|
+
//@ts-ignore
|
|
2687
|
+
acc.input[key].value += item.input[key].value || 0;
|
|
2688
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2689
|
+
//@ts-ignore
|
|
2690
|
+
if (item.input[key].isUncertain) {
|
|
2691
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2692
|
+
//@ts-ignore
|
|
2693
|
+
acc.input[key].isUncertain = true;
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2162
2696
|
}
|
|
2163
|
-
|
|
2164
|
-
|
|
2697
|
+
for (const key of Object.keys(acc.output)) {
|
|
2698
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2699
|
+
//@ts-ignore
|
|
2700
|
+
if (item.output[key]) {
|
|
2701
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2702
|
+
//@ts-ignore
|
|
2703
|
+
acc.output[key].value += item.output[key].value || 0;
|
|
2704
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2705
|
+
//@ts-ignore
|
|
2706
|
+
if (item.output[key].isUncertain) {
|
|
2707
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2708
|
+
//@ts-ignore
|
|
2709
|
+
acc.output[key].isUncertain = true;
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2165
2712
|
}
|
|
2713
|
+
return acc;
|
|
2714
|
+
}, deepClone(ZERO_USAGE));
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
/**
|
|
2718
|
+
* Intercepts LLM tools and counts total usage of the tools
|
|
2719
|
+
*
|
|
2720
|
+
* @param llmTools LLM tools to be intercepted with usage counting
|
|
2721
|
+
* @returns LLM tools with same functionality with added total cost counting
|
|
2722
|
+
* @public exported from `@promptbook/core`
|
|
2723
|
+
*/
|
|
2724
|
+
function countUsage(llmTools) {
|
|
2725
|
+
let totalUsage = ZERO_USAGE;
|
|
2726
|
+
const spending = new rxjs.Subject();
|
|
2727
|
+
const proxyTools = {
|
|
2728
|
+
get title() {
|
|
2729
|
+
// TODO: [🧠] Maybe put here some suffix
|
|
2730
|
+
return llmTools.title;
|
|
2731
|
+
},
|
|
2732
|
+
get description() {
|
|
2733
|
+
// TODO: [🧠] Maybe put here some suffix
|
|
2734
|
+
return llmTools.description;
|
|
2735
|
+
},
|
|
2736
|
+
checkConfiguration() {
|
|
2737
|
+
return /* not await */ llmTools.checkConfiguration();
|
|
2738
|
+
},
|
|
2739
|
+
listModels() {
|
|
2740
|
+
return /* not await */ llmTools.listModels();
|
|
2741
|
+
},
|
|
2742
|
+
spending() {
|
|
2743
|
+
return spending.asObservable();
|
|
2744
|
+
},
|
|
2745
|
+
getTotalUsage() {
|
|
2746
|
+
// <- Note: [🥫] Not using getter `get totalUsage` but `getTotalUsage` to allow this object to be proxied
|
|
2747
|
+
return totalUsage;
|
|
2748
|
+
},
|
|
2749
|
+
};
|
|
2750
|
+
if (llmTools.callChatModel !== undefined) {
|
|
2751
|
+
proxyTools.callChatModel = async (prompt) => {
|
|
2752
|
+
// console.info('[🚕] callChatModel through countTotalUsage');
|
|
2753
|
+
const promptResult = await llmTools.callChatModel(prompt);
|
|
2754
|
+
totalUsage = addUsage(totalUsage, promptResult.usage);
|
|
2755
|
+
spending.next(promptResult.usage);
|
|
2756
|
+
return promptResult;
|
|
2757
|
+
};
|
|
2166
2758
|
}
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
}
|
|
2175
|
-
else if (isInstalled && isPartiallyConfigured) {
|
|
2176
|
-
providerMessage = colors__default["default"].yellow(providerMessage);
|
|
2177
|
-
}
|
|
2178
|
-
else {
|
|
2179
|
-
providerMessage = colors__default["default"].gray(providerMessage);
|
|
2180
|
-
}
|
|
2759
|
+
if (llmTools.callCompletionModel !== undefined) {
|
|
2760
|
+
proxyTools.callCompletionModel = async (prompt) => {
|
|
2761
|
+
// console.info('[🚕] callCompletionModel through countTotalUsage');
|
|
2762
|
+
const promptResult = await llmTools.callCompletionModel(prompt);
|
|
2763
|
+
totalUsage = addUsage(totalUsage, promptResult.usage);
|
|
2764
|
+
spending.next(promptResult.usage);
|
|
2765
|
+
return promptResult;
|
|
2766
|
+
};
|
|
2181
2767
|
}
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2768
|
+
if (llmTools.callEmbeddingModel !== undefined) {
|
|
2769
|
+
proxyTools.callEmbeddingModel = async (prompt) => {
|
|
2770
|
+
// console.info('[🚕] callEmbeddingModel through countTotalUsage');
|
|
2771
|
+
const promptResult = await llmTools.callEmbeddingModel(prompt);
|
|
2772
|
+
totalUsage = addUsage(totalUsage, promptResult.usage);
|
|
2773
|
+
spending.next(promptResult.usage);
|
|
2774
|
+
return promptResult;
|
|
2775
|
+
};
|
|
2776
|
+
}
|
|
2777
|
+
// <- Note: [🤖]
|
|
2778
|
+
return proxyTools;
|
|
2186
2779
|
}
|
|
2187
2780
|
/**
|
|
2188
|
-
* TODO: [
|
|
2189
|
-
* TODO: [🧠]
|
|
2781
|
+
* TODO: [🧠][💸] Maybe make some common abstraction `interceptLlmTools` and use here (or use javascript Proxy?)
|
|
2782
|
+
* TODO: [🧠] Is there some meaningfull way how to test this util
|
|
2783
|
+
* TODO: [🧠][🌯] Maybe a way how to hide ability to `get totalUsage`
|
|
2784
|
+
* > const [llmToolsWithUsage,getUsage] = countTotalUsage(llmTools);
|
|
2785
|
+
* TODO: [👷♂️] @@@ Manual about construction of llmTools
|
|
2190
2786
|
*/
|
|
2191
2787
|
|
|
2192
2788
|
/**
|
|
2193
2789
|
* @@@
|
|
2194
2790
|
*
|
|
2195
2791
|
* @@@ .env
|
|
2792
|
+
* Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file
|
|
2196
2793
|
*
|
|
2197
2794
|
* It looks for environment variables:
|
|
2198
2795
|
* - `process.env.OPENAI_API_KEY`
|
|
@@ -2206,33 +2803,9 @@
|
|
|
2206
2803
|
if (!$isRunningInNode()) {
|
|
2207
2804
|
throw new EnvironmentMismatchError('Function `$provideLlmToolsFromEnv` works only in Node.js environment');
|
|
2208
2805
|
}
|
|
2209
|
-
const
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
'.env.local',
|
|
2213
|
-
'.env.development.local',
|
|
2214
|
-
'.env.development',
|
|
2215
|
-
'.env.production.local',
|
|
2216
|
-
'.env.production',
|
|
2217
|
-
'.env.prod.local',
|
|
2218
|
-
'.env.prod',
|
|
2219
|
-
// <- TODO: Maybe add more patterns
|
|
2220
|
-
];
|
|
2221
|
-
let rootDirname = process.cwd();
|
|
2222
|
-
up_to_root: for (let i = 0; i < LOOP_LIMIT; i++) {
|
|
2223
|
-
for (const pattern of envFilePatterns) {
|
|
2224
|
-
const envFilename = path.join(rootDirname, pattern);
|
|
2225
|
-
if (await isFileExisting(envFilename, $provideFilesystemForNode())) {
|
|
2226
|
-
$setUsedEnvFilename(envFilename);
|
|
2227
|
-
dotenv__namespace.config({ path: envFilename });
|
|
2228
|
-
break up_to_root;
|
|
2229
|
-
}
|
|
2230
|
-
}
|
|
2231
|
-
if (isRootPath(rootDirname)) {
|
|
2232
|
-
break up_to_root;
|
|
2233
|
-
}
|
|
2234
|
-
// Note: If the directory does not exist, try the parent directory
|
|
2235
|
-
rootDirname = path.join(rootDirname, '..');
|
|
2806
|
+
const envFilepath = await $provideEnvFilename();
|
|
2807
|
+
if (envFilepath !== null) {
|
|
2808
|
+
dotenv__namespace.config({ path: envFilepath });
|
|
2236
2809
|
}
|
|
2237
2810
|
const llmToolsConfiguration = $llmToolsMetadataRegister
|
|
2238
2811
|
.list()
|
|
@@ -2241,15 +2814,8 @@
|
|
|
2241
2814
|
return llmToolsConfiguration;
|
|
2242
2815
|
}
|
|
2243
2816
|
/**
|
|
2244
|
-
* TODO: [🧠][🪁] Maybe do allow to do auto-install if package not registered and not found
|
|
2245
|
-
* TODO: Add Azure OpenAI
|
|
2246
|
-
* TODO: [🧠][🍛]
|
|
2247
|
-
* TODO: [🧠] Is there some meaningfull way how to test this util
|
|
2248
2817
|
* Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
|
|
2249
|
-
|
|
2250
|
-
* TODO: This should be maybe not under `_common` but under `utils`
|
|
2251
|
-
* TODO: [🧠][⚛] Maybe pass env as argument
|
|
2252
|
-
* TODO: [®] DRY Register logic */
|
|
2818
|
+
*/
|
|
2253
2819
|
|
|
2254
2820
|
/**
|
|
2255
2821
|
* Multiple LLM Execution Tools is a proxy server that uses multiple execution tools internally and exposes the executor interface externally.
|
|
@@ -2414,6 +2980,7 @@
|
|
|
2414
2980
|
`);
|
|
2415
2981
|
// TODO: [🟥] Detect browser / node and make it colorfull
|
|
2416
2982
|
console.warn(warningMessage);
|
|
2983
|
+
// <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
|
|
2417
2984
|
/*
|
|
2418
2985
|
return {
|
|
2419
2986
|
async listModels() {
|
|
@@ -2498,6 +3065,7 @@
|
|
|
2498
3065
|
* Note: This function is not cached, every call creates new instance of `MultipleLlmExecutionTools`
|
|
2499
3066
|
*
|
|
2500
3067
|
* @@@ .env
|
|
3068
|
+
* Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file
|
|
2501
3069
|
*
|
|
2502
3070
|
* It looks for environment variables:
|
|
2503
3071
|
* - `process.env.OPENAI_API_KEY`
|
|
@@ -2542,16 +3110,44 @@
|
|
|
2542
3110
|
/**
|
|
2543
3111
|
* Returns LLM tools for CLI
|
|
2544
3112
|
*
|
|
3113
|
+
* Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file and also writes this .env file
|
|
3114
|
+
*
|
|
2545
3115
|
* @private within the repository - for CLI utils
|
|
2546
3116
|
*/
|
|
2547
3117
|
async function $provideLlmToolsForWizzardOrCli(options) {
|
|
2548
3118
|
if (!$isRunningInNode()) {
|
|
2549
3119
|
throw new EnvironmentMismatchError('Function `$provideLlmToolsForWizzardOrCli` works only in Node.js environment');
|
|
2550
3120
|
}
|
|
2551
|
-
|
|
2552
|
-
|
|
3121
|
+
options = options !== null && options !== void 0 ? options : { strategy: 'BRING_YOUR_OWN_KEYS' };
|
|
3122
|
+
const { strategy, isCacheReloaded } = options;
|
|
3123
|
+
let llmExecutionTools;
|
|
3124
|
+
if (strategy === 'REMOTE_SERVER') {
|
|
3125
|
+
const { remoteServerUrl = DEFAULT_REMOTE_SERVER_URL, loginPrompt } = options;
|
|
3126
|
+
const storage = new $EnvStorage(); // <- TODO: !!!!!! Save to `.promptbook` folder
|
|
3127
|
+
let key = `PROMPTBOOK_TOKEN`;
|
|
3128
|
+
if (remoteServerUrl !== DEFAULT_REMOTE_SERVER_URL) {
|
|
3129
|
+
key = `${key}_${remoteServerUrl.replace(/^https?:\/\//i, '')}`;
|
|
3130
|
+
}
|
|
3131
|
+
let identification = await storage.getItem(key);
|
|
3132
|
+
if (identification === null) {
|
|
3133
|
+
identification = await loginPrompt();
|
|
3134
|
+
await storage.setItem(key, identification);
|
|
3135
|
+
}
|
|
3136
|
+
llmExecutionTools = new RemoteLlmExecutionTools({
|
|
3137
|
+
remoteServerUrl,
|
|
3138
|
+
identification,
|
|
3139
|
+
});
|
|
3140
|
+
}
|
|
3141
|
+
else if (strategy === 'BRING_YOUR_OWN_KEYS') {
|
|
3142
|
+
llmExecutionTools = await $provideLlmToolsFromEnv();
|
|
3143
|
+
}
|
|
3144
|
+
else {
|
|
3145
|
+
throw new UnexpectedError(`\`$provideLlmToolsForWizzardOrCli\` wrong strategy "${strategy}"`);
|
|
3146
|
+
}
|
|
3147
|
+
return cacheLlmTools(countUsage(
|
|
3148
|
+
// <- TODO: [🌯] We dont use countUsage at all, maybe just unwrap it
|
|
2553
3149
|
// <- Note: for example here we don`t want the [🌯]
|
|
2554
|
-
|
|
3150
|
+
llmExecutionTools), {
|
|
2555
3151
|
storage: new FileCacheStorage({ fs: $provideFilesystemForNode() }, {
|
|
2556
3152
|
rootFolderPath: path.join(process.cwd(), DEFAULT_EXECUTION_CACHE_DIRNAME),
|
|
2557
3153
|
}),
|
|
@@ -2567,31 +3163,157 @@
|
|
|
2567
3163
|
*/
|
|
2568
3164
|
|
|
2569
3165
|
/**
|
|
2570
|
-
*
|
|
2571
|
-
* No side effects.
|
|
2572
|
-
*
|
|
2573
|
-
* Note: It can be usefull for:
|
|
3166
|
+
* The built-in `fetch' function with a lightweight error handling wrapper as default fetch function used in Promptbook scrapers
|
|
2574
3167
|
*
|
|
2575
|
-
*
|
|
2576
|
-
|
|
2577
|
-
|
|
3168
|
+
* @public exported from `@promptbook/core`
|
|
3169
|
+
*/
|
|
3170
|
+
const promptbookFetch = async (urlOrRequest, init) => {
|
|
3171
|
+
try {
|
|
3172
|
+
return await fetch(urlOrRequest, init);
|
|
3173
|
+
}
|
|
3174
|
+
catch (error) {
|
|
3175
|
+
if (!(error instanceof Error)) {
|
|
3176
|
+
throw error;
|
|
3177
|
+
}
|
|
3178
|
+
let url;
|
|
3179
|
+
if (typeof urlOrRequest === 'string') {
|
|
3180
|
+
url = urlOrRequest;
|
|
3181
|
+
}
|
|
3182
|
+
else if (urlOrRequest instanceof Request) {
|
|
3183
|
+
url = urlOrRequest.url;
|
|
3184
|
+
}
|
|
3185
|
+
throw new PromptbookFetchError(spaceTrim__default["default"]((block) => `
|
|
3186
|
+
Can not fetch "${url}"
|
|
3187
|
+
|
|
3188
|
+
Fetch error:
|
|
3189
|
+
${block(error.message)}
|
|
3190
|
+
|
|
3191
|
+
`));
|
|
3192
|
+
}
|
|
3193
|
+
};
|
|
3194
|
+
/**
|
|
3195
|
+
* TODO: [🧠] Maybe rename because it is not used only for scrapers but also in `$getCompiledBook`
|
|
3196
|
+
*/
|
|
3197
|
+
|
|
3198
|
+
/**
|
|
3199
|
+
* Checks if value is valid email
|
|
2578
3200
|
*
|
|
2579
|
-
* @
|
|
2580
|
-
* @returns void
|
|
2581
|
-
* @private within the repository
|
|
3201
|
+
* @public exported from `@promptbook/utils`
|
|
2582
3202
|
*/
|
|
2583
|
-
function
|
|
3203
|
+
function isValidEmail(email) {
|
|
3204
|
+
if (typeof email !== 'string') {
|
|
3205
|
+
return false;
|
|
3206
|
+
}
|
|
3207
|
+
if (email.split('\n').length > 1) {
|
|
3208
|
+
return false;
|
|
3209
|
+
}
|
|
3210
|
+
return /^.+@.+\..+$/.test(email);
|
|
2584
3211
|
}
|
|
2585
3212
|
|
|
2586
3213
|
/**
|
|
2587
|
-
*
|
|
2588
|
-
*
|
|
2589
|
-
* @param value any values
|
|
2590
|
-
* @returns void
|
|
2591
|
-
* @private within the repository
|
|
3214
|
+
* @private utility of CLI
|
|
2592
3215
|
*/
|
|
2593
|
-
function $
|
|
2594
|
-
|
|
3216
|
+
function $provideLlmToolsForCli(options) {
|
|
3217
|
+
const { cliOptions: {
|
|
3218
|
+
/* TODO: Use verbose: isVerbose, */ interactive: isInteractive, provider, remoteServerUrl: remoteServerUrlRaw, }, } = options;
|
|
3219
|
+
let strategy;
|
|
3220
|
+
if (/^b/i.test(provider)) {
|
|
3221
|
+
strategy = 'BRING_YOUR_OWN_KEYS';
|
|
3222
|
+
}
|
|
3223
|
+
else if (/^r/i.test(provider)) {
|
|
3224
|
+
strategy = 'REMOTE_SERVER';
|
|
3225
|
+
}
|
|
3226
|
+
else {
|
|
3227
|
+
console.log(colors__default["default"].red(`Unknown provider: "${provider}", please use "BRING_YOUR_OWN_KEYS" or "REMOTE_SERVER"`));
|
|
3228
|
+
process.exit(1);
|
|
3229
|
+
}
|
|
3230
|
+
if (strategy === 'BRING_YOUR_OWN_KEYS') {
|
|
3231
|
+
return /* not await */ $provideLlmToolsForWizzardOrCli({ strategy, ...options });
|
|
3232
|
+
}
|
|
3233
|
+
else if (strategy === 'REMOTE_SERVER') {
|
|
3234
|
+
if (!isValidUrl(remoteServerUrlRaw)) {
|
|
3235
|
+
console.log(colors__default["default"].red(`Invalid URL of remote server: "${remoteServerUrlRaw}"`));
|
|
3236
|
+
process.exit(1);
|
|
3237
|
+
}
|
|
3238
|
+
const remoteServerUrl = remoteServerUrlRaw.endsWith('/') ? remoteServerUrlRaw.slice(0, -1) : remoteServerUrlRaw;
|
|
3239
|
+
return /* not await */ $provideLlmToolsForWizzardOrCli({
|
|
3240
|
+
strategy,
|
|
3241
|
+
appId: CLI_APP_ID,
|
|
3242
|
+
remoteServerUrl,
|
|
3243
|
+
...options,
|
|
3244
|
+
async loginPrompt() {
|
|
3245
|
+
if (!isInteractive) {
|
|
3246
|
+
console.log(colors__default["default"].red(`You can not login to remote server in non-interactive mode`));
|
|
3247
|
+
process.exit(1);
|
|
3248
|
+
}
|
|
3249
|
+
const { username, password } = await prompts__default["default"]([
|
|
3250
|
+
{
|
|
3251
|
+
type: 'text',
|
|
3252
|
+
name: 'username',
|
|
3253
|
+
message: 'Enter your email:',
|
|
3254
|
+
validate: (value) => (isValidEmail(value) ? true : 'Valid email is required'),
|
|
3255
|
+
},
|
|
3256
|
+
{
|
|
3257
|
+
type: 'password',
|
|
3258
|
+
name: 'password',
|
|
3259
|
+
message: 'Enter your password:',
|
|
3260
|
+
validate: (value) => value.length /* <- TODO: [🧠] Better password validation */ > 0
|
|
3261
|
+
? true
|
|
3262
|
+
: 'Password is required',
|
|
3263
|
+
},
|
|
3264
|
+
]);
|
|
3265
|
+
const loginUrl = `${remoteServerUrl}/login`;
|
|
3266
|
+
console.log('!!!', { loginUrl });
|
|
3267
|
+
// TODO: [🧠] Should we use normal `fetch` or `scraperFetch`
|
|
3268
|
+
const response = await promptbookFetch(loginUrl, {
|
|
3269
|
+
method: 'POST',
|
|
3270
|
+
headers: {
|
|
3271
|
+
'Content-Type': 'application/json',
|
|
3272
|
+
},
|
|
3273
|
+
body: JSON.stringify({
|
|
3274
|
+
appId: CLI_APP_ID,
|
|
3275
|
+
username,
|
|
3276
|
+
password,
|
|
3277
|
+
}),
|
|
3278
|
+
});
|
|
3279
|
+
console.log('!!!', {
|
|
3280
|
+
loginUrl,
|
|
3281
|
+
username,
|
|
3282
|
+
password,
|
|
3283
|
+
// type: response.type,
|
|
3284
|
+
// text: await response.text(),
|
|
3285
|
+
});
|
|
3286
|
+
const { isSuccess, message, error, identification } = (await response.json());
|
|
3287
|
+
console.log('!!!', {
|
|
3288
|
+
isSuccess,
|
|
3289
|
+
message,
|
|
3290
|
+
error,
|
|
3291
|
+
identification,
|
|
3292
|
+
});
|
|
3293
|
+
if (message) {
|
|
3294
|
+
if (isSuccess) {
|
|
3295
|
+
console.log(colors__default["default"].green(message));
|
|
3296
|
+
}
|
|
3297
|
+
else {
|
|
3298
|
+
console.log(colors__default["default"].red(message));
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
if (!isSuccess) {
|
|
3302
|
+
// Note: Login failed
|
|
3303
|
+
process.exit(1);
|
|
3304
|
+
}
|
|
3305
|
+
if (!identification) {
|
|
3306
|
+
// Note: Do not get identification here, but server signalizes the success so exiting but with code 0
|
|
3307
|
+
// This can mean for example that user needs to verify email
|
|
3308
|
+
process.exit(0);
|
|
3309
|
+
}
|
|
3310
|
+
return identification;
|
|
3311
|
+
},
|
|
3312
|
+
});
|
|
3313
|
+
}
|
|
3314
|
+
else {
|
|
3315
|
+
throw new UnexpectedError(`\`$provideLlmToolsForCli\` wrong strategy "${strategy}"`);
|
|
3316
|
+
}
|
|
2595
3317
|
}
|
|
2596
3318
|
|
|
2597
3319
|
/**
|
|
@@ -2608,8 +3330,10 @@
|
|
|
2608
3330
|
`));
|
|
2609
3331
|
listModelsCommand.alias('models');
|
|
2610
3332
|
listModelsCommand.alias('llm');
|
|
2611
|
-
listModelsCommand.action(handleActionErrors(async () => {
|
|
2612
|
-
|
|
3333
|
+
listModelsCommand.action(handleActionErrors(async (cliOptions) => {
|
|
3334
|
+
console.log('!!!', cliOptions);
|
|
3335
|
+
// TODO: !!!!!! Not relevant for remote server and also for `about` command
|
|
3336
|
+
const llm = await $provideLlmToolsForCli({ cliOptions });
|
|
2613
3337
|
$sideEffect(llm);
|
|
2614
3338
|
// <- Note: Providing LLM tools will make a side effect of registering all available LLM tools to show the message
|
|
2615
3339
|
console.info($registeredLlmToolsMessage());
|
|
@@ -2707,6 +3431,7 @@
|
|
|
2707
3431
|
}
|
|
2708
3432
|
else {
|
|
2709
3433
|
console.warn(`Command "${humanReadableCommand}" exceeded time limit of ${timeout}ms but continues running`);
|
|
3434
|
+
// <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
|
|
2710
3435
|
resolve('Command exceeded time limit');
|
|
2711
3436
|
}
|
|
2712
3437
|
});
|
|
@@ -2732,6 +3457,7 @@
|
|
|
2732
3457
|
output.push(stderr.toString());
|
|
2733
3458
|
if (isVerbose && stderr.toString().trim()) {
|
|
2734
3459
|
console.warn(stderr.toString());
|
|
3460
|
+
// <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
|
|
2735
3461
|
}
|
|
2736
3462
|
});
|
|
2737
3463
|
const finishWithCode = (code) => {
|
|
@@ -2743,6 +3469,7 @@
|
|
|
2743
3469
|
else {
|
|
2744
3470
|
if (isVerbose) {
|
|
2745
3471
|
console.warn(`Command "${humanReadableCommand}" exited with code ${code}`);
|
|
3472
|
+
// <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
|
|
2746
3473
|
}
|
|
2747
3474
|
resolve(spaceTrim.spaceTrim(output.join('\n')));
|
|
2748
3475
|
}
|
|
@@ -2764,6 +3491,7 @@
|
|
|
2764
3491
|
else {
|
|
2765
3492
|
if (isVerbose) {
|
|
2766
3493
|
console.warn(error);
|
|
3494
|
+
// <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
|
|
2767
3495
|
}
|
|
2768
3496
|
resolve(spaceTrim.spaceTrim(output.join('\n')));
|
|
2769
3497
|
}
|
|
@@ -3165,49 +3893,73 @@
|
|
|
3165
3893
|
*/
|
|
3166
3894
|
|
|
3167
3895
|
/**
|
|
3168
|
-
*
|
|
3169
|
-
*
|
|
3170
|
-
* Note: Functions `collectionToJson` and `createCollectionFromJson` are complementary
|
|
3896
|
+
* Initializes `login` command for Promptbook CLI utilities
|
|
3171
3897
|
*
|
|
3172
|
-
*
|
|
3173
|
-
*/
|
|
3174
|
-
async function collectionToJson(collection) {
|
|
3175
|
-
const pipelineUrls = await collection.listPipelines();
|
|
3176
|
-
const promptbooks = await Promise.all(pipelineUrls.map((url) => collection.getPipelineByUrl(url)));
|
|
3177
|
-
return promptbooks;
|
|
3178
|
-
}
|
|
3179
|
-
/**
|
|
3180
|
-
* TODO: [🧠] Maybe clear `sourceFile` or clear when exposing through API or remote server
|
|
3181
|
-
*/
|
|
3182
|
-
|
|
3183
|
-
/**
|
|
3184
|
-
* This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
|
|
3898
|
+
* Note: `$` is used to indicate that this function is not a pure function - it registers a command in the CLI
|
|
3185
3899
|
*
|
|
3186
|
-
* @
|
|
3900
|
+
* @private internal function of `promptbookCli`
|
|
3187
3901
|
*/
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3902
|
+
function $initializeLoginCommand(program) {
|
|
3903
|
+
const loginCommand = program.command('login');
|
|
3904
|
+
loginCommand.description(spaceTrim__default["default"](`
|
|
3905
|
+
Login to the remote Promptbook server
|
|
3906
|
+
`));
|
|
3907
|
+
loginCommand.action(handleActionErrors(async () => {
|
|
3908
|
+
// @@@
|
|
3909
|
+
console.error(colors__default["default"].green(spaceTrim__default["default"](`
|
|
3910
|
+
You will be logged in to https://promptbook.studio server.
|
|
3911
|
+
If you don't have an account, it will be created automatically.
|
|
3912
|
+
`)));
|
|
3913
|
+
// !!!!!!!!! Remove from here and use $provideLlmToolsForCli
|
|
3914
|
+
const { email, password } = await prompts__default["default"]([
|
|
3915
|
+
{
|
|
3916
|
+
type: 'text',
|
|
3917
|
+
name: 'email',
|
|
3918
|
+
message: 'Enter your email:',
|
|
3919
|
+
validate: (value) => (isValidEmail(value) ? true : 'Valid email is required'),
|
|
3920
|
+
},
|
|
3921
|
+
{
|
|
3922
|
+
type: 'password',
|
|
3923
|
+
name: 'password',
|
|
3924
|
+
message: 'Enter your password:',
|
|
3925
|
+
validate: (value) => value.length /* <- TODO: [🧠] Better password validation */ > 0 ? true : 'Password is required',
|
|
3926
|
+
},
|
|
3927
|
+
]);
|
|
3928
|
+
TODO_USE(email, password);
|
|
3929
|
+
await waitasecond.forTime(1000);
|
|
3930
|
+
console.error(colors__default["default"].green(spaceTrim__default["default"](`
|
|
3931
|
+
Your account ${email} was successfully created.
|
|
3932
|
+
|
|
3933
|
+
Please verify your email:
|
|
3934
|
+
https://brj.app/api/v1/customer/register-account?apiKey=PRODdh003eNKaec7PoO1AzU244tsL4WO
|
|
3935
|
+
|
|
3936
|
+
After verification, you will receive 500 000 credits for free 🎉
|
|
3937
|
+
`)));
|
|
3938
|
+
return process.exit(0);
|
|
3939
|
+
}));
|
|
3194
3940
|
}
|
|
3195
3941
|
/**
|
|
3196
|
-
* TODO:
|
|
3942
|
+
* TODO: Pass remote server URL (and path)
|
|
3943
|
+
* TODO: Implement non-interactive login
|
|
3944
|
+
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
3945
|
+
* Note: [🟡] Code in this file should never be published outside of `@promptbook/cli`
|
|
3197
3946
|
*/
|
|
3198
3947
|
|
|
3199
3948
|
/**
|
|
3200
|
-
*
|
|
3949
|
+
* Converts PipelineCollection to serialized JSON
|
|
3950
|
+
*
|
|
3951
|
+
* Note: Functions `collectionToJson` and `createCollectionFromJson` are complementary
|
|
3201
3952
|
*
|
|
3202
3953
|
* @public exported from `@promptbook/core`
|
|
3203
3954
|
*/
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
Object.setPrototypeOf(this, PipelineLogicError.prototype);
|
|
3209
|
-
}
|
|
3955
|
+
async function collectionToJson(collection) {
|
|
3956
|
+
const pipelineUrls = await collection.listPipelines();
|
|
3957
|
+
const promptbooks = await Promise.all(pipelineUrls.map((url) => collection.getPipelineByUrl(url)));
|
|
3958
|
+
return promptbooks;
|
|
3210
3959
|
}
|
|
3960
|
+
/**
|
|
3961
|
+
* TODO: [🧠] Maybe clear `sourceFile` or clear when exposing through API or remote server
|
|
3962
|
+
*/
|
|
3211
3963
|
|
|
3212
3964
|
/**
|
|
3213
3965
|
* Tests if given string is valid semantic version
|
|
@@ -3605,21 +4357,6 @@
|
|
|
3605
4357
|
|
|
3606
4358
|
var PipelineCollection = [{title:"Prepare Knowledge from Markdown",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-from-markdown.book",formfactorName:"GENERIC",parameters:[{name:"knowledgeContent",description:"Markdown document content",isInput:true,isOutput:false},{name:"knowledgePieces",description:"The knowledge JSON object",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced data researcher, extract the important knowledge from the document.\n\n# Rules\n\n- Make pieces of information concise, clear, and easy to understand\n- One piece of information should be approximately 1 paragraph\n- Divide the paragraphs by markdown horizontal lines ---\n- Omit irrelevant information\n- Group redundant information\n- Write just extracted information, nothing else\n\n# The document\n\nTake information from this document:\n\n> {knowledgeContent}",resultingParameterName:"knowledgePieces",dependentParameterNames:["knowledgeContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Knowledge from Markdown\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-from-markdown.book`\n- INPUT PARAMETER `{knowledgeContent}` Markdown document content\n- OUTPUT PARAMETER `{knowledgePieces}` The knowledge JSON object\n\n## Knowledge\n\n<!-- TODO: [🍆] -FORMAT JSON -->\n\n```markdown\nYou are experienced data researcher, extract the important knowledge from the document.\n\n# Rules\n\n- Make pieces of information concise, clear, and easy to understand\n- One piece of information should be approximately 1 paragraph\n- Divide the paragraphs by markdown horizontal lines ---\n- Omit irrelevant information\n- Group redundant information\n- Write just extracted information, nothing else\n\n# The document\n\nTake information from this document:\n\n> {knowledgeContent}\n```\n\n`-> {knowledgePieces}`\n"}],sourceFile:"./books/prepare-knowledge-from-markdown.book"},{title:"Prepare Keywords",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-keywords.book",formfactorName:"GENERIC",parameters:[{name:"knowledgePieceContent",description:"The content",isInput:true,isOutput:false},{name:"keywords",description:"Keywords separated by comma",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced data researcher, detect the important keywords in the document.\n\n# Rules\n\n- Write just keywords separated by comma\n\n# The document\n\nTake information from this document:\n\n> {knowledgePieceContent}",resultingParameterName:"keywords",dependentParameterNames:["knowledgePieceContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Keywords\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-keywords.book`\n- INPUT PARAMETER `{knowledgePieceContent}` The content\n- OUTPUT PARAMETER `{keywords}` Keywords separated by comma\n\n## Knowledge\n\n<!-- TODO: [🍆] -FORMAT JSON -->\n\n```markdown\nYou are experienced data researcher, detect the important keywords in the document.\n\n# Rules\n\n- Write just keywords separated by comma\n\n# The document\n\nTake information from this document:\n\n> {knowledgePieceContent}\n```\n\n`-> {keywords}`\n"}],sourceFile:"./books/prepare-knowledge-keywords.book"},{title:"Prepare Knowledge-piece Title",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-title.book",formfactorName:"GENERIC",parameters:[{name:"knowledgePieceContent",description:"The content",isInput:true,isOutput:false},{name:"title",description:"The title of the document",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced content creator, write best title for the document.\n\n# Rules\n\n- Write just title, nothing else\n- Write maximum 5 words for the title\n\n# The document\n\n> {knowledgePieceContent}",resultingParameterName:"title",expectations:{words:{min:1,max:8}},dependentParameterNames:["knowledgePieceContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Knowledge-piece Title\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-title.book`\n- INPUT PARAMETER `{knowledgePieceContent}` The content\n- OUTPUT PARAMETER `{title}` The title of the document\n\n## Knowledge\n\n- EXPECT MIN 1 WORD\n- EXPECT MAX 8 WORDS\n\n```markdown\nYou are experienced content creator, write best title for the document.\n\n# Rules\n\n- Write just title, nothing else\n- Write maximum 5 words for the title\n\n# The document\n\n> {knowledgePieceContent}\n```\n\n`-> {title}`\n"}],sourceFile:"./books/prepare-knowledge-title.book"},{title:"Prepare Persona",pipelineUrl:"https://promptbook.studio/promptbook/prepare-persona.book",formfactorName:"GENERIC",parameters:[{name:"availableModelNames",description:"List of available model names separated by comma (,)",isInput:true,isOutput:false},{name:"personaDescription",description:"Description of the persona",isInput:true,isOutput:false},{name:"modelRequirements",description:"Specific requirements for the model",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"make-model-requirements",title:"Make modelRequirements",content:"You are experienced AI engineer, you need to create virtual assistant.\nWrite\n\n## Example\n\n```json\n{\n\"modelName\": \"gpt-4o\",\n\"systemMessage\": \"You are experienced AI engineer and helpfull assistant.\",\n\"temperature\": 0.7\n}\n```\n\n## Instructions\n\n- Your output format is JSON object\n- Write just the JSON object, no other text should be present\n- It contains the following keys:\n - `modelName`: The name of the model to use\n - `systemMessage`: The system message to provide context to the model\n - `temperature`: The sampling temperature to use\n\n### Key `modelName`\n\nPick from the following models:\n\n- {availableModelNames}\n\n### Key `systemMessage`\n\nThe system message is used to communicate instructions or provide context to the model at the beginning of a conversation. It is displayed in a different format compared to user messages, helping the model understand its role in the conversation. The system message typically guides the model's behavior, sets the tone, or specifies desired output from the model. By utilizing the system message effectively, users can steer the model towards generating more accurate and relevant responses.\n\nFor example:\n\n> You are an experienced AI engineer and helpful assistant.\n\n> You are a friendly and knowledgeable chatbot.\n\n### Key `temperature`\n\nThe sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.\n\nYou can pick a value between 0 and 2. For example:\n\n- `0.1`: Low temperature, extremely conservative and deterministic\n- `0.5`: Medium temperature, balanced between conservative and creative\n- `1.0`: High temperature, creative and bit random\n- `1.5`: Very high temperature, extremely creative and often chaotic and unpredictable\n- `2.0`: Maximum temperature, completely random and unpredictable, for some extreme creative use cases\n\n# The assistant\n\nTake this description of the persona:\n\n> {personaDescription}",resultingParameterName:"modelRequirements",format:"JSON",dependentParameterNames:["availableModelNames","personaDescription"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Persona\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-persona.book`\n- INPUT PARAMETER `{availableModelNames}` List of available model names separated by comma (,)\n- INPUT PARAMETER `{personaDescription}` Description of the persona\n- OUTPUT PARAMETER `{modelRequirements}` Specific requirements for the model\n\n## Make modelRequirements\n\n- FORMAT JSON\n\n```markdown\nYou are experienced AI engineer, you need to create virtual assistant.\nWrite\n\n## Example\n\n\\`\\`\\`json\n{\n\"modelName\": \"gpt-4o\",\n\"systemMessage\": \"You are experienced AI engineer and helpfull assistant.\",\n\"temperature\": 0.7\n}\n\\`\\`\\`\n\n## Instructions\n\n- Your output format is JSON object\n- Write just the JSON object, no other text should be present\n- It contains the following keys:\n - `modelName`: The name of the model to use\n - `systemMessage`: The system message to provide context to the model\n - `temperature`: The sampling temperature to use\n\n### Key `modelName`\n\nPick from the following models:\n\n- {availableModelNames}\n\n### Key `systemMessage`\n\nThe system message is used to communicate instructions or provide context to the model at the beginning of a conversation. It is displayed in a different format compared to user messages, helping the model understand its role in the conversation. The system message typically guides the model's behavior, sets the tone, or specifies desired output from the model. By utilizing the system message effectively, users can steer the model towards generating more accurate and relevant responses.\n\nFor example:\n\n> You are an experienced AI engineer and helpful assistant.\n\n> You are a friendly and knowledgeable chatbot.\n\n### Key `temperature`\n\nThe sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.\n\nYou can pick a value between 0 and 2. For example:\n\n- `0.1`: Low temperature, extremely conservative and deterministic\n- `0.5`: Medium temperature, balanced between conservative and creative\n- `1.0`: High temperature, creative and bit random\n- `1.5`: Very high temperature, extremely creative and often chaotic and unpredictable\n- `2.0`: Maximum temperature, completely random and unpredictable, for some extreme creative use cases\n\n# The assistant\n\nTake this description of the persona:\n\n> {personaDescription}\n```\n\n`-> {modelRequirements}`\n"}],sourceFile:"./books/prepare-persona.book"},{title:"Prepare Title",pipelineUrl:"https://promptbook.studio/promptbook/prepare-title.book",formfactorName:"GENERIC",parameters:[{name:"book",description:"The book to prepare the title for",isInput:true,isOutput:false},{name:"title",description:"Best title for the book",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"make-title",title:"Make title",content:"Make best title for given text which describes the workflow:\n\n## Rules\n\n- Write just title, nothing else\n- Title should be concise and clear - Write maximum ideally 2 words, maximum 5 words\n- Title starts with emoticon\n- Title should not mention the input and output of the workflow but the main purpose of the workflow\n _For example, not \"✍ Convert Knowledge-piece to title\" but \"✍ Title\"_\n\n## The workflow\n\n> {book}",resultingParameterName:"title",expectations:{words:{min:1,max:8},lines:{min:1,max:1}},dependentParameterNames:["book"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Title\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-title.book`\n- INPUT PARAMETER `{book}` The book to prepare the title for\n- OUTPUT PARAMETER `{title}` Best title for the book\n\n## Make title\n\n- EXPECT MIN 1 Word\n- EXPECT MAX 8 Words\n- EXPECT EXACTLY 1 Line\n\n```markdown\nMake best title for given text which describes the workflow:\n\n## Rules\n\n- Write just title, nothing else\n- Title should be concise and clear - Write maximum ideally 2 words, maximum 5 words\n- Title starts with emoticon\n- Title should not mention the input and output of the workflow but the main purpose of the workflow\n _For example, not \"✍ Convert Knowledge-piece to title\" but \"✍ Title\"_\n\n## The workflow\n\n> {book}\n```\n\n`-> {title}`\n"}],sourceFile:"./books/prepare-title.book"}];
|
|
3607
4359
|
|
|
3608
|
-
/**
|
|
3609
|
-
* Checks if value is valid email
|
|
3610
|
-
*
|
|
3611
|
-
* @public exported from `@promptbook/utils`
|
|
3612
|
-
*/
|
|
3613
|
-
function isValidEmail(email) {
|
|
3614
|
-
if (typeof email !== 'string') {
|
|
3615
|
-
return false;
|
|
3616
|
-
}
|
|
3617
|
-
if (email.split('\n').length > 1) {
|
|
3618
|
-
return false;
|
|
3619
|
-
}
|
|
3620
|
-
return /^.+@.+\..+$/.test(email);
|
|
3621
|
-
}
|
|
3622
|
-
|
|
3623
4360
|
/**
|
|
3624
4361
|
* Function isValidJsonString will tell you if the string is valid JSON or not
|
|
3625
4362
|
*
|
|
@@ -3853,32 +4590,6 @@
|
|
|
3853
4590
|
* TODO: [🧠] Should be in generated .book.md file GENERATOR_WARNING
|
|
3854
4591
|
*/
|
|
3855
4592
|
|
|
3856
|
-
/**
|
|
3857
|
-
* This error indicates that promptbook not found in the collection
|
|
3858
|
-
*
|
|
3859
|
-
* @public exported from `@promptbook/core`
|
|
3860
|
-
*/
|
|
3861
|
-
class NotFoundError extends Error {
|
|
3862
|
-
constructor(message) {
|
|
3863
|
-
super(message);
|
|
3864
|
-
this.name = 'NotFoundError';
|
|
3865
|
-
Object.setPrototypeOf(this, NotFoundError.prototype);
|
|
3866
|
-
}
|
|
3867
|
-
}
|
|
3868
|
-
|
|
3869
|
-
/**
|
|
3870
|
-
* This error indicates errors in referencing promptbooks between each other
|
|
3871
|
-
*
|
|
3872
|
-
* @public exported from `@promptbook/core`
|
|
3873
|
-
*/
|
|
3874
|
-
class PipelineUrlError extends Error {
|
|
3875
|
-
constructor(message) {
|
|
3876
|
-
super(message);
|
|
3877
|
-
this.name = 'PipelineUrlError';
|
|
3878
|
-
Object.setPrototypeOf(this, PipelineUrlError.prototype);
|
|
3879
|
-
}
|
|
3880
|
-
}
|
|
3881
|
-
|
|
3882
4593
|
/**
|
|
3883
4594
|
* Parses the task and returns the list of all parameter names
|
|
3884
4595
|
*
|
|
@@ -4049,24 +4760,6 @@
|
|
|
4049
4760
|
return new SimplePipelineCollection(...promptbooks);
|
|
4050
4761
|
}
|
|
4051
4762
|
|
|
4052
|
-
/**
|
|
4053
|
-
* This error type indicates that some tools are missing for pipeline execution or preparation
|
|
4054
|
-
*
|
|
4055
|
-
* @public exported from `@promptbook/core`
|
|
4056
|
-
*/
|
|
4057
|
-
class MissingToolsError extends Error {
|
|
4058
|
-
constructor(message) {
|
|
4059
|
-
super(spaceTrim.spaceTrim((block) => `
|
|
4060
|
-
${block(message)}
|
|
4061
|
-
|
|
4062
|
-
Note: You have probbably forgot to provide some tools for pipeline execution or preparation
|
|
4063
|
-
|
|
4064
|
-
`));
|
|
4065
|
-
this.name = 'MissingToolsError';
|
|
4066
|
-
Object.setPrototypeOf(this, MissingToolsError.prototype);
|
|
4067
|
-
}
|
|
4068
|
-
}
|
|
4069
|
-
|
|
4070
4763
|
/**
|
|
4071
4764
|
* Determine if the pipeline is fully prepared
|
|
4072
4765
|
*
|
|
@@ -4132,177 +4825,7 @@
|
|
|
4132
4825
|
}
|
|
4133
4826
|
/**
|
|
4134
4827
|
* TODO: Type the return type correctly
|
|
4135
|
-
*/
|
|
4136
|
-
|
|
4137
|
-
/**
|
|
4138
|
-
* This error indicates problems parsing the format value
|
|
4139
|
-
*
|
|
4140
|
-
* For example, when the format value is not a valid JSON or CSV
|
|
4141
|
-
* This is not thrown directly but in extended classes
|
|
4142
|
-
*
|
|
4143
|
-
* @public exported from `@promptbook/core`
|
|
4144
|
-
*/
|
|
4145
|
-
class AbstractFormatError extends Error {
|
|
4146
|
-
// Note: To allow instanceof do not put here error `name`
|
|
4147
|
-
// public readonly name = 'AbstractFormatError';
|
|
4148
|
-
constructor(message) {
|
|
4149
|
-
super(message);
|
|
4150
|
-
Object.setPrototypeOf(this, AbstractFormatError.prototype);
|
|
4151
|
-
}
|
|
4152
|
-
}
|
|
4153
|
-
|
|
4154
|
-
/**
|
|
4155
|
-
* This error indicates problem with parsing of CSV
|
|
4156
|
-
*
|
|
4157
|
-
* @public exported from `@promptbook/core`
|
|
4158
|
-
*/
|
|
4159
|
-
class CsvFormatError extends AbstractFormatError {
|
|
4160
|
-
constructor(message) {
|
|
4161
|
-
super(message);
|
|
4162
|
-
this.name = 'CsvFormatError';
|
|
4163
|
-
Object.setPrototypeOf(this, CsvFormatError.prototype);
|
|
4164
|
-
}
|
|
4165
|
-
}
|
|
4166
|
-
|
|
4167
|
-
/**
|
|
4168
|
-
* This error indicates that the pipeline collection cannot be propperly loaded
|
|
4169
|
-
*
|
|
4170
|
-
* @public exported from `@promptbook/core`
|
|
4171
|
-
*/
|
|
4172
|
-
class CollectionError extends Error {
|
|
4173
|
-
constructor(message) {
|
|
4174
|
-
super(message);
|
|
4175
|
-
this.name = 'CollectionError';
|
|
4176
|
-
Object.setPrototypeOf(this, CollectionError.prototype);
|
|
4177
|
-
}
|
|
4178
|
-
}
|
|
4179
|
-
|
|
4180
|
-
/**
|
|
4181
|
-
* This error occurs when some expectation is not met in the execution of the pipeline
|
|
4182
|
-
*
|
|
4183
|
-
* @public exported from `@promptbook/core`
|
|
4184
|
-
* Note: Do not throw this error, its reserved for `checkExpectations` and `createPipelineExecutor` and public ONLY to be serializable through remote server
|
|
4185
|
-
* Note: Always thrown in `checkExpectations` and catched in `createPipelineExecutor` and rethrown as `PipelineExecutionError`
|
|
4186
|
-
* Note: This is a kindof subtype of PipelineExecutionError
|
|
4187
|
-
*/
|
|
4188
|
-
class ExpectError extends Error {
|
|
4189
|
-
constructor(message) {
|
|
4190
|
-
super(message);
|
|
4191
|
-
this.name = 'ExpectError';
|
|
4192
|
-
Object.setPrototypeOf(this, ExpectError.prototype);
|
|
4193
|
-
}
|
|
4194
|
-
}
|
|
4195
|
-
|
|
4196
|
-
/**
|
|
4197
|
-
* This error indicates that the promptbook can not retrieve knowledge from external sources
|
|
4198
|
-
*
|
|
4199
|
-
* @public exported from `@promptbook/core`
|
|
4200
|
-
*/
|
|
4201
|
-
class KnowledgeScrapeError extends Error {
|
|
4202
|
-
constructor(message) {
|
|
4203
|
-
super(message);
|
|
4204
|
-
this.name = 'KnowledgeScrapeError';
|
|
4205
|
-
Object.setPrototypeOf(this, KnowledgeScrapeError.prototype);
|
|
4206
|
-
}
|
|
4207
|
-
}
|
|
4208
|
-
|
|
4209
|
-
/**
|
|
4210
|
-
* This error type indicates that some limit was reached
|
|
4211
|
-
*
|
|
4212
|
-
* @public exported from `@promptbook/core`
|
|
4213
|
-
*/
|
|
4214
|
-
class LimitReachedError extends Error {
|
|
4215
|
-
constructor(message) {
|
|
4216
|
-
super(message);
|
|
4217
|
-
this.name = 'LimitReachedError';
|
|
4218
|
-
Object.setPrototypeOf(this, LimitReachedError.prototype);
|
|
4219
|
-
}
|
|
4220
|
-
}
|
|
4221
|
-
|
|
4222
|
-
/**
|
|
4223
|
-
* Index of all custom errors
|
|
4224
|
-
*
|
|
4225
|
-
* @public exported from `@promptbook/core`
|
|
4226
|
-
*/
|
|
4227
|
-
const PROMPTBOOK_ERRORS = {
|
|
4228
|
-
AbstractFormatError,
|
|
4229
|
-
CsvFormatError,
|
|
4230
|
-
CollectionError,
|
|
4231
|
-
EnvironmentMismatchError,
|
|
4232
|
-
ExpectError,
|
|
4233
|
-
KnowledgeScrapeError,
|
|
4234
|
-
LimitReachedError,
|
|
4235
|
-
MissingToolsError,
|
|
4236
|
-
NotFoundError,
|
|
4237
|
-
NotYetImplementedError,
|
|
4238
|
-
ParseError,
|
|
4239
|
-
PipelineExecutionError,
|
|
4240
|
-
PipelineLogicError,
|
|
4241
|
-
PipelineUrlError,
|
|
4242
|
-
UnexpectedError,
|
|
4243
|
-
// TODO: [🪑]> VersionMismatchError,
|
|
4244
|
-
};
|
|
4245
|
-
/**
|
|
4246
|
-
* Index of all javascript errors
|
|
4247
|
-
*
|
|
4248
|
-
* @private for internal usage
|
|
4249
|
-
*/
|
|
4250
|
-
const COMMON_JAVASCRIPT_ERRORS = {
|
|
4251
|
-
Error,
|
|
4252
|
-
EvalError,
|
|
4253
|
-
RangeError,
|
|
4254
|
-
ReferenceError,
|
|
4255
|
-
SyntaxError,
|
|
4256
|
-
TypeError,
|
|
4257
|
-
URIError,
|
|
4258
|
-
AggregateError,
|
|
4259
|
-
/*
|
|
4260
|
-
Note: Not widely supported
|
|
4261
|
-
> InternalError,
|
|
4262
|
-
> ModuleError,
|
|
4263
|
-
> HeapError,
|
|
4264
|
-
> WebAssemblyCompileError,
|
|
4265
|
-
> WebAssemblyRuntimeError,
|
|
4266
|
-
*/
|
|
4267
|
-
};
|
|
4268
|
-
/**
|
|
4269
|
-
* Index of all errors
|
|
4270
|
-
*
|
|
4271
|
-
* @private for internal usage
|
|
4272
|
-
*/
|
|
4273
|
-
const ALL_ERRORS = {
|
|
4274
|
-
...PROMPTBOOK_ERRORS,
|
|
4275
|
-
...COMMON_JAVASCRIPT_ERRORS,
|
|
4276
|
-
};
|
|
4277
|
-
/**
|
|
4278
|
-
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
4279
|
-
*/
|
|
4280
|
-
|
|
4281
|
-
/**
|
|
4282
|
-
* Deserializes the error object
|
|
4283
|
-
*
|
|
4284
|
-
* @public exported from `@promptbook/utils`
|
|
4285
|
-
*/
|
|
4286
|
-
function deserializeError(error) {
|
|
4287
|
-
const { name, stack, id } = error; // Added id
|
|
4288
|
-
let { message } = error;
|
|
4289
|
-
let ErrorClass = ALL_ERRORS[error.name];
|
|
4290
|
-
if (ErrorClass === undefined) {
|
|
4291
|
-
ErrorClass = Error;
|
|
4292
|
-
message = `${name}: ${message}`;
|
|
4293
|
-
}
|
|
4294
|
-
if (stack !== undefined && stack !== '') {
|
|
4295
|
-
message = spaceTrim__default["default"]((block) => `
|
|
4296
|
-
${block(message)}
|
|
4297
|
-
|
|
4298
|
-
Original stack trace:
|
|
4299
|
-
${block(stack || '')}
|
|
4300
|
-
`);
|
|
4301
|
-
}
|
|
4302
|
-
const deserializedError = new ErrorClass(message);
|
|
4303
|
-
deserializedError.id = id; // Assign id to the error object
|
|
4304
|
-
return deserializedError;
|
|
4305
|
-
}
|
|
4828
|
+
*/
|
|
4306
4829
|
|
|
4307
4830
|
/**
|
|
4308
4831
|
* Asserts that the execution of a Promptbook is successful
|
|
@@ -4317,6 +4840,7 @@
|
|
|
4317
4840
|
const { isSuccessful, errors, warnings } = executionResult;
|
|
4318
4841
|
for (const warning of warnings) {
|
|
4319
4842
|
console.warn(warning.message);
|
|
4843
|
+
// <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
|
|
4320
4844
|
}
|
|
4321
4845
|
if (isSuccessful === true) {
|
|
4322
4846
|
return;
|
|
@@ -4454,6 +4978,10 @@
|
|
|
4454
4978
|
|
|
4455
4979
|
Cannot serialize error with name "${name}"
|
|
4456
4980
|
|
|
4981
|
+
Authors of Promptbook probably forgot to add this error into the list of errors:
|
|
4982
|
+
https://github.com/webgptorg/promptbook/blob/main/src/errors/0-index.ts
|
|
4983
|
+
|
|
4984
|
+
|
|
4457
4985
|
${block(stack || message)}
|
|
4458
4986
|
|
|
4459
4987
|
`));
|
|
@@ -4658,7 +5186,7 @@
|
|
|
4658
5186
|
if (parameterNames.has(subparameterName)) {
|
|
4659
5187
|
parameterNames.delete(subparameterName);
|
|
4660
5188
|
parameterNames.add(foreach.parameterName);
|
|
4661
|
-
// <- TODO: [
|
|
5189
|
+
// <- TODO: [🏮] Warn/logic error when `subparameterName` not used
|
|
4662
5190
|
}
|
|
4663
5191
|
}
|
|
4664
5192
|
}
|
|
@@ -6273,6 +6801,7 @@
|
|
|
6273
6801
|
|
|
6274
6802
|
@see more at https://ptbk.io/prepare-pipeline
|
|
6275
6803
|
`));
|
|
6804
|
+
// <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
|
|
6276
6805
|
}
|
|
6277
6806
|
let runCount = 0;
|
|
6278
6807
|
const pipelineExecutorWithCallback = async (inputParameters, onProgress) => {
|
|
@@ -6440,32 +6969,6 @@
|
|
|
6440
6969
|
return mimeTypes.extension(value) || null;
|
|
6441
6970
|
}
|
|
6442
6971
|
|
|
6443
|
-
/**
|
|
6444
|
-
* The built-in `fetch' function with a lightweight error handling wrapper as default fetch function used in Promptbook scrapers
|
|
6445
|
-
*
|
|
6446
|
-
* @private as default `fetch` function used in Promptbook scrapers
|
|
6447
|
-
*/
|
|
6448
|
-
const scraperFetch = async (url, init) => {
|
|
6449
|
-
try {
|
|
6450
|
-
return await fetch(url, init);
|
|
6451
|
-
}
|
|
6452
|
-
catch (error) {
|
|
6453
|
-
if (!(error instanceof Error)) {
|
|
6454
|
-
throw error;
|
|
6455
|
-
}
|
|
6456
|
-
throw new KnowledgeScrapeError(spaceTrim__default["default"]((block) => `
|
|
6457
|
-
Can not fetch "${url}"
|
|
6458
|
-
|
|
6459
|
-
Fetch error:
|
|
6460
|
-
${block(error.message)}
|
|
6461
|
-
|
|
6462
|
-
`));
|
|
6463
|
-
}
|
|
6464
|
-
};
|
|
6465
|
-
/**
|
|
6466
|
-
* TODO: [🧠] Maybe rename because it is not used only for scrapers but also in `$getCompiledBook`
|
|
6467
|
-
*/
|
|
6468
|
-
|
|
6469
6972
|
/**
|
|
6470
6973
|
* @@@
|
|
6471
6974
|
*
|
|
@@ -6474,7 +6977,7 @@
|
|
|
6474
6977
|
async function makeKnowledgeSourceHandler(knowledgeSource, tools, options) {
|
|
6475
6978
|
// console.log('!! makeKnowledgeSourceHandler', knowledgeSource);
|
|
6476
6979
|
var _a;
|
|
6477
|
-
const { fetch =
|
|
6980
|
+
const { fetch = promptbookFetch } = tools;
|
|
6478
6981
|
const { knowledgeSourceContent } = knowledgeSource;
|
|
6479
6982
|
let { name } = knowledgeSource;
|
|
6480
6983
|
const { rootDirname = null,
|
|
@@ -6615,63 +7118,73 @@
|
|
|
6615
7118
|
const { maxParallelCount = DEFAULT_MAX_PARALLEL_COUNT, rootDirname, isVerbose = DEFAULT_IS_VERBOSE } = options;
|
|
6616
7119
|
const knowledgePreparedUnflatten = new Array(knowledgeSources.length);
|
|
6617
7120
|
await forEachAsync(knowledgeSources, { maxParallelCount }, async (knowledgeSource, index) => {
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
7121
|
+
try {
|
|
7122
|
+
let partialPieces = null;
|
|
7123
|
+
const sourceHandler = await makeKnowledgeSourceHandler(knowledgeSource, tools, { rootDirname, isVerbose });
|
|
7124
|
+
const scrapers = arrayableToArray(tools.scrapers);
|
|
7125
|
+
for (const scraper of scrapers) {
|
|
7126
|
+
if (!scraper.metadata.mimeTypes.includes(sourceHandler.mimeType)
|
|
7127
|
+
// <- TODO: [🦔] Implement mime-type wildcards
|
|
7128
|
+
) {
|
|
7129
|
+
continue;
|
|
7130
|
+
}
|
|
7131
|
+
const partialPiecesUnchecked = await scraper.scrape(sourceHandler);
|
|
7132
|
+
if (partialPiecesUnchecked !== null) {
|
|
7133
|
+
partialPieces = [...partialPiecesUnchecked];
|
|
7134
|
+
// <- TODO: [🪓] Here should be no need for spreading new array, just `partialPieces = partialPiecesUnchecked`
|
|
7135
|
+
break;
|
|
7136
|
+
}
|
|
7137
|
+
console.warn(spaceTrim__default["default"]((block) => `
|
|
7138
|
+
Cannot scrape knowledge from source despite the scraper \`${scraper.metadata.className}\` supports the mime type "${sourceHandler.mimeType}".
|
|
6635
7139
|
|
|
6636
|
-
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
7140
|
+
The source:
|
|
7141
|
+
${block(knowledgeSource.knowledgeSourceContent
|
|
7142
|
+
.split('\n')
|
|
7143
|
+
.map((line) => `> ${line}`)
|
|
7144
|
+
.join('\n'))}
|
|
6641
7145
|
|
|
6642
|
-
|
|
7146
|
+
${block($registeredScrapersMessage(scrapers))}
|
|
6643
7147
|
|
|
6644
7148
|
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
7149
|
+
`));
|
|
7150
|
+
// <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
|
|
7151
|
+
}
|
|
7152
|
+
if (partialPieces === null) {
|
|
7153
|
+
throw new KnowledgeScrapeError(spaceTrim__default["default"]((block) => `
|
|
7154
|
+
Cannot scrape knowledge
|
|
6650
7155
|
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
7156
|
+
The source:
|
|
7157
|
+
> ${block(knowledgeSource.knowledgeSourceContent
|
|
7158
|
+
.split('\n')
|
|
7159
|
+
.map((line) => `> ${line}`)
|
|
7160
|
+
.join('\n'))}
|
|
6656
7161
|
|
|
6657
|
-
|
|
7162
|
+
No scraper found for the mime type "${sourceHandler.mimeType}"
|
|
6658
7163
|
|
|
6659
|
-
|
|
7164
|
+
${block($registeredScrapersMessage(scrapers))}
|
|
6660
7165
|
|
|
6661
7166
|
|
|
6662
|
-
|
|
7167
|
+
`));
|
|
7168
|
+
}
|
|
7169
|
+
const pieces = partialPieces.map((partialPiece) => ({
|
|
7170
|
+
...partialPiece,
|
|
7171
|
+
sources: [
|
|
7172
|
+
{
|
|
7173
|
+
name: knowledgeSource.name,
|
|
7174
|
+
// line, column <- TODO: [☀]
|
|
7175
|
+
// <- TODO: [❎]
|
|
7176
|
+
},
|
|
7177
|
+
],
|
|
7178
|
+
}));
|
|
7179
|
+
knowledgePreparedUnflatten[index] = pieces;
|
|
7180
|
+
}
|
|
7181
|
+
catch (error) {
|
|
7182
|
+
if (!(error instanceof Error)) {
|
|
7183
|
+
throw error;
|
|
7184
|
+
}
|
|
7185
|
+
console.warn(error);
|
|
7186
|
+
// <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
|
|
6663
7187
|
}
|
|
6664
|
-
const pieces = partialPieces.map((partialPiece) => ({
|
|
6665
|
-
...partialPiece,
|
|
6666
|
-
sources: [
|
|
6667
|
-
{
|
|
6668
|
-
name: knowledgeSource.name,
|
|
6669
|
-
// line, column <- TODO: [☀]
|
|
6670
|
-
// <- TODO: [❎]
|
|
6671
|
-
},
|
|
6672
|
-
],
|
|
6673
|
-
}));
|
|
6674
|
-
knowledgePreparedUnflatten[index] = pieces;
|
|
6675
7188
|
});
|
|
6676
7189
|
const knowledgePrepared = knowledgePreparedUnflatten.flat();
|
|
6677
7190
|
return knowledgePrepared;
|
|
@@ -6777,7 +7290,7 @@
|
|
|
6777
7290
|
// TODO: [🚐] Make arrayable LLMs -> single LLM DRY
|
|
6778
7291
|
const _llms = arrayableToArray(tools.llm);
|
|
6779
7292
|
const llmTools = _llms.length === 1 ? _llms[0] : joinLlmExecutionTools(..._llms);
|
|
6780
|
-
const llmToolsWithUsage =
|
|
7293
|
+
const llmToolsWithUsage = countUsage(llmTools);
|
|
6781
7294
|
// <- TODO: [🌯]
|
|
6782
7295
|
/*
|
|
6783
7296
|
TODO: [🧠][🪑][🔃] Should this be done or not
|
|
@@ -8496,7 +9009,8 @@
|
|
|
8496
9009
|
if ($pipelineJson.defaultModelRequirements[command.key] !== undefined) {
|
|
8497
9010
|
if ($pipelineJson.defaultModelRequirements[command.key] === command.value) {
|
|
8498
9011
|
console.warn(`Multiple commands \`MODEL ${command.key} ${command.value}\` in the pipeline head`);
|
|
8499
|
-
// <- TODO: [
|
|
9012
|
+
// <- TODO: [🏮] Some better way how to get warnings from pipeline parsing / logic
|
|
9013
|
+
// <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
|
|
8500
9014
|
}
|
|
8501
9015
|
else {
|
|
8502
9016
|
throw new ParseError(spaceTrim__default["default"](`
|
|
@@ -8528,6 +9042,7 @@
|
|
|
8528
9042
|
modelVariant: 'VARIANT',
|
|
8529
9043
|
maxTokens: '???',
|
|
8530
9044
|
}[command.key]} ${command.value}\` in the task "${$taskJson.title || $taskJson.name}"`);
|
|
9045
|
+
// <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
|
|
8531
9046
|
}
|
|
8532
9047
|
else {
|
|
8533
9048
|
throw new ParseError(spaceTrim__default["default"](`
|
|
@@ -8807,15 +9322,15 @@
|
|
|
8807
9322
|
}
|
|
8808
9323
|
console.warn(spaceTrim__default["default"](`
|
|
8809
9324
|
|
|
8810
|
-
|
|
9325
|
+
Persona "${personaName}" is defined multiple times with different description:
|
|
8811
9326
|
|
|
8812
|
-
|
|
8813
|
-
|
|
9327
|
+
First definition:
|
|
9328
|
+
${persona.description}
|
|
8814
9329
|
|
|
8815
|
-
|
|
8816
|
-
|
|
9330
|
+
Second definition:
|
|
9331
|
+
${personaDescription}
|
|
8817
9332
|
|
|
8818
|
-
|
|
9333
|
+
`));
|
|
8819
9334
|
persona.description += spaceTrim__default["default"]('\n\n' + personaDescription);
|
|
8820
9335
|
}
|
|
8821
9336
|
|
|
@@ -11351,7 +11866,6 @@
|
|
|
11351
11866
|
makeCommand.option('--no-validation', `Do not validate logic of pipelines in collection`, true);
|
|
11352
11867
|
makeCommand.option('--validation', `Types of validations separated by comma (options "logic","imports")`, 'logic,imports');
|
|
11353
11868
|
makeCommand.option('-r, --reload', `Call LLM models even if same prompt with result is in the cache`, false);
|
|
11354
|
-
makeCommand.option('-v, --verbose', `Is output verbose`, false);
|
|
11355
11869
|
makeCommand.option('-o, --output <path>', spaceTrim__default["default"](`
|
|
11356
11870
|
Where to save the builded collection
|
|
11357
11871
|
|
|
@@ -11365,7 +11879,8 @@
|
|
|
11365
11879
|
Note: This can be used only with "javascript" or "typescript" format
|
|
11366
11880
|
|
|
11367
11881
|
`), DEFAULT_GET_PIPELINE_COLLECTION_FUNCTION_NAME);
|
|
11368
|
-
makeCommand.action(handleActionErrors(async (path$1,
|
|
11882
|
+
makeCommand.action(handleActionErrors(async (path$1, cliOptions) => {
|
|
11883
|
+
const { projectName, rootUrl, format, functionName, validation, reload: isCacheReloaded, verbose: isVerbose, output, } = cliOptions;
|
|
11369
11884
|
if (!isValidJavascriptName(functionName)) {
|
|
11370
11885
|
console.error(colors__default["default"].red(`Function name "${functionName}" is not valid javascript name`));
|
|
11371
11886
|
return process.exit(1);
|
|
@@ -11393,7 +11908,10 @@
|
|
|
11393
11908
|
isCacheReloaded,
|
|
11394
11909
|
}; /* <- TODO: ` satisfies PrepareAndScrapeOptions` */
|
|
11395
11910
|
const fs = $provideFilesystemForNode(prepareAndScrapeOptions);
|
|
11396
|
-
const llm = await $
|
|
11911
|
+
const llm = await $provideLlmToolsForCli({
|
|
11912
|
+
cliOptions,
|
|
11913
|
+
...prepareAndScrapeOptions,
|
|
11914
|
+
});
|
|
11397
11915
|
const executables = await $provideExecutablesForNode(prepareAndScrapeOptions);
|
|
11398
11916
|
const tools = {
|
|
11399
11917
|
llm,
|
|
@@ -11446,6 +11964,7 @@
|
|
|
11446
11964
|
: path.join(path$1, `${DEFAULT_PIPELINE_COLLECTION_BASE_FILENAME}.${extension}`);
|
|
11447
11965
|
if (!output.endsWith(`.${extension}`)) {
|
|
11448
11966
|
console.warn(colors__default["default"].yellow(`Warning: Extension of output file should be "${extension}"`));
|
|
11967
|
+
// <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
|
|
11449
11968
|
}
|
|
11450
11969
|
await promises.mkdir(path.dirname(filename), { recursive: true });
|
|
11451
11970
|
if (typeof content === 'string') {
|
|
@@ -11546,6 +12065,7 @@
|
|
|
11546
12065
|
}
|
|
11547
12066
|
if (formats.length > 0) {
|
|
11548
12067
|
console.warn(colors__default["default"].yellow(`Format ${formats.join(' and ')} is not supported`));
|
|
12068
|
+
// <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
|
|
11549
12069
|
}
|
|
11550
12070
|
console.info(colors__default["default"].green(`Collection builded successfully`));
|
|
11551
12071
|
if (isVerbose) {
|
|
@@ -11592,7 +12112,8 @@
|
|
|
11592
12112
|
`));
|
|
11593
12113
|
}
|
|
11594
12114
|
console.warn(`No place where to put the section <!--${sectionName}-->, using the end of the file`);
|
|
11595
|
-
// <- TODO: [
|
|
12115
|
+
// <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
|
|
12116
|
+
// <- TODO: [🏮] Some better way how to get warnings from pipeline parsing / logic
|
|
11596
12117
|
return spaceTrim.spaceTrim((block) => `
|
|
11597
12118
|
${block(content)}
|
|
11598
12119
|
|
|
@@ -11658,8 +12179,8 @@
|
|
|
11658
12179
|
// <- TODO: [🧟♂️] Unite path to promptbook collection argument
|
|
11659
12180
|
'Pipelines to prettify as glob pattern');
|
|
11660
12181
|
prettifyCommand.option('-i, --ignore <glob>', `Ignore as glob pattern`);
|
|
11661
|
-
prettifyCommand.
|
|
11662
|
-
|
|
12182
|
+
prettifyCommand.action(handleActionErrors(async (filesGlob, cliOptions) => {
|
|
12183
|
+
const { ignore, verbose: isVerbose } = cliOptions;
|
|
11663
12184
|
const filenames = await glob__default["default"](filesGlob, { ignore });
|
|
11664
12185
|
// <- TODO: [😶]
|
|
11665
12186
|
for (const filename of filenames) {
|
|
@@ -12260,13 +12781,12 @@
|
|
|
12260
12781
|
// TODO: [🧅] DRY command arguments
|
|
12261
12782
|
runCommand.argument('[pipelineSource]', 'Path to book file OR URL to book file, if not provided it will be asked');
|
|
12262
12783
|
runCommand.option('-r, --reload', `Call LLM models even if same prompt with result is in the cache`, false);
|
|
12263
|
-
runCommand.option('-v, --verbose', `Is output verbose`, false);
|
|
12264
|
-
runCommand.option('--no-interactive', `Input is not interactive, if true you need to pass all the input parameters through --json`);
|
|
12265
12784
|
runCommand.option('--no-formfactor', `When set, behavior of the interactive mode is not changed by the formfactor of the pipeline`);
|
|
12266
12785
|
runCommand.option('-j, --json <json>', `Pass all or some input parameters as JSON record, if used the output is also returned as JSON`);
|
|
12267
12786
|
runCommand.option('-s, --save-report <path>', `Save report to file`);
|
|
12268
|
-
runCommand.action(handleActionErrors(async (pipelineSource,
|
|
12269
|
-
|
|
12787
|
+
runCommand.action(handleActionErrors(async (pipelineSource, cliOptions) => {
|
|
12788
|
+
console.log('!!!', cliOptions);
|
|
12789
|
+
const { reload: isCacheReloaded, interactive: isInteractive, formfactor: isFormfactorUsed, json, verbose: isVerbose, saveReport, } = cliOptions;
|
|
12270
12790
|
if (pipelineSource.includes('-') && normalizeToKebabCase(pipelineSource) === pipelineSource) {
|
|
12271
12791
|
console.error(colors__default["default"].red(`""${pipelineSource}" is not a valid command or book. See 'ptbk --help'.`));
|
|
12272
12792
|
return process.exit(1);
|
|
@@ -12291,7 +12811,7 @@
|
|
|
12291
12811
|
const fs = $provideFilesystemForNode(prepareAndScrapeOptions);
|
|
12292
12812
|
let llm;
|
|
12293
12813
|
try {
|
|
12294
|
-
llm = await $
|
|
12814
|
+
llm = await $provideLlmToolsForCli({ cliOptions, ...prepareAndScrapeOptions });
|
|
12295
12815
|
}
|
|
12296
12816
|
catch (error) {
|
|
12297
12817
|
if (!(error instanceof Error)) {
|
|
@@ -12346,9 +12866,9 @@
|
|
|
12346
12866
|
const tools = {
|
|
12347
12867
|
llm,
|
|
12348
12868
|
fs,
|
|
12349
|
-
fetch:
|
|
12869
|
+
fetch: promptbookFetch,
|
|
12350
12870
|
scrapers: await $provideScrapersForNode({ fs, llm, executables }, prepareAndScrapeOptions),
|
|
12351
|
-
script: [new JavascriptExecutionTools(
|
|
12871
|
+
script: [new JavascriptExecutionTools(cliOptions)],
|
|
12352
12872
|
};
|
|
12353
12873
|
if (isVerbose) {
|
|
12354
12874
|
console.info(colors__default["default"].gray('--- Getting the book ---'));
|
|
@@ -12512,11 +13032,12 @@
|
|
|
12512
13032
|
* @public exported from `@promptbook/remote-server`
|
|
12513
13033
|
*/
|
|
12514
13034
|
function startRemoteServer(options) {
|
|
12515
|
-
const { port, collection, createLlmExecutionTools, isAnonymousModeAllowed, isApplicationModeAllowed, isVerbose = DEFAULT_IS_VERBOSE, } = {
|
|
13035
|
+
const { port, collection, createLlmExecutionTools, isAnonymousModeAllowed, isApplicationModeAllowed, isVerbose = DEFAULT_IS_VERBOSE, login, } = {
|
|
12516
13036
|
isAnonymousModeAllowed: false,
|
|
12517
13037
|
isApplicationModeAllowed: false,
|
|
12518
13038
|
collection: null,
|
|
12519
13039
|
createLlmExecutionTools: null,
|
|
13040
|
+
login: null,
|
|
12520
13041
|
...options,
|
|
12521
13042
|
};
|
|
12522
13043
|
// <- TODO: [🦪] Some helper type to be able to use discriminant union types with destructuring
|
|
@@ -12583,9 +13104,38 @@
|
|
|
12583
13104
|
response.setHeader('X-Powered-By', 'Promptbook engine');
|
|
12584
13105
|
next();
|
|
12585
13106
|
});
|
|
13107
|
+
const swaggerOptions = {
|
|
13108
|
+
definition: {
|
|
13109
|
+
openapi: '3.0.0',
|
|
13110
|
+
info: {
|
|
13111
|
+
title: 'Promptbook Remote Server API',
|
|
13112
|
+
version: '1.0.0',
|
|
13113
|
+
description: 'API documentation for the Promptbook Remote Server',
|
|
13114
|
+
},
|
|
13115
|
+
servers: [
|
|
13116
|
+
{
|
|
13117
|
+
url: `http://localhost:${port}${rootPath}`,
|
|
13118
|
+
// <- TODO: !!!!! Probbably: Pass `remoteServerUrl` instead of `port` and `rootPath`
|
|
13119
|
+
},
|
|
13120
|
+
],
|
|
13121
|
+
},
|
|
13122
|
+
apis: ['./src/remote-server/**/*.ts'], // Adjust path as needed
|
|
13123
|
+
};
|
|
13124
|
+
const swaggerSpec = swaggerJsdoc__default["default"](swaggerOptions);
|
|
13125
|
+
app.use([`/api-docs`, `${rootPath}/api-docs`], swaggerUi__default["default"].serve, swaggerUi__default["default"].setup(swaggerSpec));
|
|
12586
13126
|
const runningExecutionTasks = [];
|
|
12587
13127
|
// <- TODO: [🤬] Identify the users
|
|
12588
13128
|
// TODO: [🧠] Do here some garbage collection of finished tasks
|
|
13129
|
+
/**
|
|
13130
|
+
* @swagger
|
|
13131
|
+
* /:
|
|
13132
|
+
* get:
|
|
13133
|
+
* summary: Get server details
|
|
13134
|
+
* description: Returns details about the Promptbook server.
|
|
13135
|
+
* responses:
|
|
13136
|
+
* 200:
|
|
13137
|
+
* description: Server details in markdown format.
|
|
13138
|
+
*/
|
|
12589
13139
|
app.get(['/', rootPath], async (request, response) => {
|
|
12590
13140
|
var _a;
|
|
12591
13141
|
if ((_a = request.url) === null || _a === void 0 ? void 0 : _a.includes('socket.io')) {
|
|
@@ -12622,9 +13172,12 @@
|
|
|
12622
13172
|
|
|
12623
13173
|
## Paths
|
|
12624
13174
|
|
|
12625
|
-
${block(
|
|
12626
|
-
.
|
|
12627
|
-
|
|
13175
|
+
${block([
|
|
13176
|
+
...app._router.stack
|
|
13177
|
+
.map(({ route }) => (route === null || route === void 0 ? void 0 : route.path) || null)
|
|
13178
|
+
.filter((path) => path !== null),
|
|
13179
|
+
'/api-docs',
|
|
13180
|
+
]
|
|
12628
13181
|
.map((path) => `- ${path}`)
|
|
12629
13182
|
.join('\n'))}
|
|
12630
13183
|
|
|
@@ -12642,7 +13195,96 @@
|
|
|
12642
13195
|
https://github.com/webgptorg/promptbook
|
|
12643
13196
|
`));
|
|
12644
13197
|
});
|
|
12645
|
-
|
|
13198
|
+
/**
|
|
13199
|
+
* @swagger
|
|
13200
|
+
*
|
|
13201
|
+
* /login:
|
|
13202
|
+
* post:
|
|
13203
|
+
* summary: Login to the server
|
|
13204
|
+
* description: Login to the server and get identification.
|
|
13205
|
+
* requestBody:
|
|
13206
|
+
* required: true
|
|
13207
|
+
* content:
|
|
13208
|
+
* application/json:
|
|
13209
|
+
* schema:
|
|
13210
|
+
* type: object
|
|
13211
|
+
* properties:
|
|
13212
|
+
* username:
|
|
13213
|
+
* type: string
|
|
13214
|
+
* password:
|
|
13215
|
+
* type: string
|
|
13216
|
+
* appId:
|
|
13217
|
+
* type: string
|
|
13218
|
+
* responses:
|
|
13219
|
+
* 200:
|
|
13220
|
+
* description: Successful login
|
|
13221
|
+
* content:
|
|
13222
|
+
* application/json:
|
|
13223
|
+
* schema:
|
|
13224
|
+
* type: object
|
|
13225
|
+
* properties:
|
|
13226
|
+
* identification:
|
|
13227
|
+
* type: object
|
|
13228
|
+
*/
|
|
13229
|
+
app.post([`/login`, `${rootPath}/login`], async (request, response) => {
|
|
13230
|
+
if (!isApplicationModeAllowed || login === null) {
|
|
13231
|
+
response.status(400).send('Application mode is not allowed');
|
|
13232
|
+
return;
|
|
13233
|
+
}
|
|
13234
|
+
try {
|
|
13235
|
+
const username = request.body.username;
|
|
13236
|
+
const password = request.body.password;
|
|
13237
|
+
const appId = request.body.appId;
|
|
13238
|
+
const { isSuccess, error, message, identification } = await login({
|
|
13239
|
+
username,
|
|
13240
|
+
password,
|
|
13241
|
+
appId,
|
|
13242
|
+
rawRequest: request,
|
|
13243
|
+
rawResponse: response,
|
|
13244
|
+
});
|
|
13245
|
+
response.status(201).send({
|
|
13246
|
+
isSuccess,
|
|
13247
|
+
message,
|
|
13248
|
+
error: error ? serializeError(error) : undefined,
|
|
13249
|
+
identification,
|
|
13250
|
+
});
|
|
13251
|
+
return;
|
|
13252
|
+
}
|
|
13253
|
+
catch (error) {
|
|
13254
|
+
if (!(error instanceof Error)) {
|
|
13255
|
+
throw error;
|
|
13256
|
+
}
|
|
13257
|
+
if (error instanceof AuthenticationError) {
|
|
13258
|
+
response.status(401).send({
|
|
13259
|
+
isSuccess: false,
|
|
13260
|
+
message: error.message,
|
|
13261
|
+
error: serializeError(error),
|
|
13262
|
+
});
|
|
13263
|
+
}
|
|
13264
|
+
console.warn(`Login function thrown different error than AuthenticationError`, {
|
|
13265
|
+
error,
|
|
13266
|
+
serializedError: serializeError(error),
|
|
13267
|
+
});
|
|
13268
|
+
response.status(400).send({ error: serializeError(error) });
|
|
13269
|
+
}
|
|
13270
|
+
});
|
|
13271
|
+
/**
|
|
13272
|
+
* @swagger
|
|
13273
|
+
* /books:
|
|
13274
|
+
* get:
|
|
13275
|
+
* summary: List all books
|
|
13276
|
+
* description: Returns a list of all available books in the collection.
|
|
13277
|
+
* responses:
|
|
13278
|
+
* 200:
|
|
13279
|
+
* description: A list of books.
|
|
13280
|
+
* content:
|
|
13281
|
+
* application/json:
|
|
13282
|
+
* schema:
|
|
13283
|
+
* type: array
|
|
13284
|
+
* items:
|
|
13285
|
+
* type: string
|
|
13286
|
+
*/
|
|
13287
|
+
app.get([`/books`, `${rootPath}/books`], async (request, response) => {
|
|
12646
13288
|
if (collection === null) {
|
|
12647
13289
|
response.status(500).send('No collection available');
|
|
12648
13290
|
return;
|
|
@@ -12652,7 +13294,30 @@
|
|
|
12652
13294
|
response.send(pipelines);
|
|
12653
13295
|
});
|
|
12654
13296
|
// TODO: [🧠] Is it secure / good idea to expose source codes of hosted books
|
|
12655
|
-
|
|
13297
|
+
/**
|
|
13298
|
+
* @swagger
|
|
13299
|
+
* /books/{bookId}:
|
|
13300
|
+
* get:
|
|
13301
|
+
* summary: Get book content
|
|
13302
|
+
* description: Returns the content of a specific book.
|
|
13303
|
+
* parameters:
|
|
13304
|
+
* - in: path
|
|
13305
|
+
* name: bookId
|
|
13306
|
+
* required: true
|
|
13307
|
+
* schema:
|
|
13308
|
+
* type: string
|
|
13309
|
+
* description: The ID of the book to retrieve.
|
|
13310
|
+
* responses:
|
|
13311
|
+
* 200:
|
|
13312
|
+
* description: The content of the book.
|
|
13313
|
+
* content:
|
|
13314
|
+
* text/markdown:
|
|
13315
|
+
* schema:
|
|
13316
|
+
* type: string
|
|
13317
|
+
* 404:
|
|
13318
|
+
* description: Book not found.
|
|
13319
|
+
*/
|
|
13320
|
+
app.get([`/books/*`, `${rootPath}/books/*`], async (request, response) => {
|
|
12656
13321
|
try {
|
|
12657
13322
|
if (collection === null) {
|
|
12658
13323
|
response.status(500).send('No collection nor books available');
|
|
@@ -12706,10 +13371,26 @@
|
|
|
12706
13371
|
};
|
|
12707
13372
|
}
|
|
12708
13373
|
}
|
|
12709
|
-
|
|
13374
|
+
/**
|
|
13375
|
+
* @swagger
|
|
13376
|
+
* /executions:
|
|
13377
|
+
* get:
|
|
13378
|
+
* summary: List all executions
|
|
13379
|
+
* description: Returns a list of all running execution tasks.
|
|
13380
|
+
* responses:
|
|
13381
|
+
* 200:
|
|
13382
|
+
* description: A list of execution tasks.
|
|
13383
|
+
* content:
|
|
13384
|
+
* application/json:
|
|
13385
|
+
* schema:
|
|
13386
|
+
* type: array
|
|
13387
|
+
* items:
|
|
13388
|
+
* type: object
|
|
13389
|
+
*/
|
|
13390
|
+
app.get([`/executions`, `${rootPath}/executions`], async (request, response) => {
|
|
12710
13391
|
response.send(runningExecutionTasks.map((runningExecutionTask) => exportExecutionTask(runningExecutionTask, false)));
|
|
12711
13392
|
});
|
|
12712
|
-
app.get(`${rootPath}/executions/last
|
|
13393
|
+
app.get([`/executions/last`, `${rootPath}/executions/last`], async (request, response) => {
|
|
12713
13394
|
// TODO: [🤬] Filter only for user
|
|
12714
13395
|
if (runningExecutionTasks.length === 0) {
|
|
12715
13396
|
response.status(404).send('No execution tasks found');
|
|
@@ -12718,7 +13399,7 @@
|
|
|
12718
13399
|
const lastExecutionTask = runningExecutionTasks[runningExecutionTasks.length - 1];
|
|
12719
13400
|
response.send(exportExecutionTask(lastExecutionTask, true));
|
|
12720
13401
|
});
|
|
12721
|
-
app.get(`${rootPath}/executions/:taskId
|
|
13402
|
+
app.get([`/executions/:taskId`, `${rootPath}/executions/:taskId`], async (request, response) => {
|
|
12722
13403
|
const { taskId } = request.params;
|
|
12723
13404
|
// TODO: [🤬] Filter only for user
|
|
12724
13405
|
const executionTask = runningExecutionTasks.find((executionTask) => executionTask.taskId === taskId);
|
|
@@ -12730,7 +13411,36 @@
|
|
|
12730
13411
|
}
|
|
12731
13412
|
response.send(exportExecutionTask(executionTask, true));
|
|
12732
13413
|
});
|
|
12733
|
-
|
|
13414
|
+
/**
|
|
13415
|
+
* @swagger
|
|
13416
|
+
* /executions/new:
|
|
13417
|
+
* post:
|
|
13418
|
+
* summary: Start a new execution
|
|
13419
|
+
* description: Starts a new execution task for a given pipeline.
|
|
13420
|
+
* requestBody:
|
|
13421
|
+
* required: true
|
|
13422
|
+
* content:
|
|
13423
|
+
* application/json:
|
|
13424
|
+
* schema:
|
|
13425
|
+
* type: object
|
|
13426
|
+
* properties:
|
|
13427
|
+
* pipelineUrl:
|
|
13428
|
+
* type: string
|
|
13429
|
+
* inputParameters:
|
|
13430
|
+
* type: object
|
|
13431
|
+
* identification:
|
|
13432
|
+
* type: object
|
|
13433
|
+
* responses:
|
|
13434
|
+
* 200:
|
|
13435
|
+
* description: The newly created execution task.
|
|
13436
|
+
* content:
|
|
13437
|
+
* application/json:
|
|
13438
|
+
* schema:
|
|
13439
|
+
* type: object
|
|
13440
|
+
* 400:
|
|
13441
|
+
* description: Invalid input.
|
|
13442
|
+
*/
|
|
13443
|
+
app.post([`/executions/new`, `${rootPath}/executions/new`], async (request, response) => {
|
|
12734
13444
|
try {
|
|
12735
13445
|
const { inputParameters, identification /* <- [🤬] */ } = request.body;
|
|
12736
13446
|
const pipelineUrl = request.body.pipelineUrl || request.body.book;
|
|
@@ -12907,6 +13617,15 @@
|
|
|
12907
13617
|
}
|
|
12908
13618
|
let isDestroyed = false;
|
|
12909
13619
|
return {
|
|
13620
|
+
get httpServer() {
|
|
13621
|
+
return httpServer;
|
|
13622
|
+
},
|
|
13623
|
+
get expressApp() {
|
|
13624
|
+
return app;
|
|
13625
|
+
},
|
|
13626
|
+
get socketIoServer() {
|
|
13627
|
+
return server;
|
|
13628
|
+
},
|
|
12910
13629
|
get isDestroyed() {
|
|
12911
13630
|
return isDestroyed;
|
|
12912
13631
|
},
|
|
@@ -12957,12 +13676,12 @@
|
|
|
12957
13676
|
`));
|
|
12958
13677
|
startServerCommand.option('--allow-anonymous', `Is anonymous mode allowed`, false);
|
|
12959
13678
|
startServerCommand.option('-r, --reload', `Call LLM models even if same prompt with result is in the cache`, false);
|
|
12960
|
-
startServerCommand.option('-v, --verbose', `Is output verbose`, false);
|
|
12961
13679
|
startServerCommand.description(spaceTrim__default["default"](`
|
|
12962
13680
|
Starts a remote server to execute books
|
|
12963
13681
|
`));
|
|
12964
13682
|
startServerCommand.alias('server');
|
|
12965
|
-
startServerCommand.action(handleActionErrors(async (path,
|
|
13683
|
+
startServerCommand.action(handleActionErrors(async (path, cliOptions) => {
|
|
13684
|
+
const { port: portRaw, url: rawUrl, allowAnonymous: isAnonymousModeAllowed, reload: isCacheReloaded, verbose: isVerbose, } = cliOptions;
|
|
12966
13685
|
if (rawUrl && !isValidUrl(rawUrl)) {
|
|
12967
13686
|
console.error(colors__default["default"].red(`Invalid URL: ${rawUrl}`));
|
|
12968
13687
|
return process.exit(1);
|
|
@@ -12975,6 +13694,7 @@
|
|
|
12975
13694
|
const url = !rawUrl ? null : new URL(rawUrl);
|
|
12976
13695
|
if (url !== null && url.port !== port.toString()) {
|
|
12977
13696
|
console.warn(colors__default["default"].yellow(`Port in --url is different from --port which the server will listen on, this is ok only if you proxy from one port to another, for exaple via nginx or docker`));
|
|
13697
|
+
// <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
|
|
12978
13698
|
}
|
|
12979
13699
|
let rootUrl = undefined;
|
|
12980
13700
|
if (url !== null) {
|
|
@@ -12990,7 +13710,7 @@
|
|
|
12990
13710
|
isCacheReloaded,
|
|
12991
13711
|
}; /* <- TODO: ` satisfies PrepareAndScrapeOptions` */
|
|
12992
13712
|
const fs = $provideFilesystemForNode(prepareAndScrapeOptions);
|
|
12993
|
-
const llm = await $
|
|
13713
|
+
const llm = await $provideLlmToolsForCli({ cliOptions, ...prepareAndScrapeOptions });
|
|
12994
13714
|
const executables = await $provideExecutablesForNode(prepareAndScrapeOptions);
|
|
12995
13715
|
const tools = {
|
|
12996
13716
|
llm,
|
|
@@ -13014,6 +13734,9 @@
|
|
|
13014
13734
|
isAnonymousModeAllowed,
|
|
13015
13735
|
isApplicationModeAllowed: true,
|
|
13016
13736
|
collection,
|
|
13737
|
+
async login() {
|
|
13738
|
+
throw new AuthenticationError('You can not login to the server started by `ptbk start-server` in cli, use `startRemoteServer` function instead.');
|
|
13739
|
+
},
|
|
13017
13740
|
createLlmExecutionTools(options) {
|
|
13018
13741
|
const { appId, userId } = options;
|
|
13019
13742
|
TODO_USE({ appId, userId });
|
|
@@ -13050,8 +13773,8 @@
|
|
|
13050
13773
|
testCommand.option('--no-validation', `Do not validate logic of pipelines in collection`, true);
|
|
13051
13774
|
testCommand.option('--no-prepare', `Do not prepare the pipelines, ideal when no LLM tools or scrapers available`, true);
|
|
13052
13775
|
testCommand.option('-r, --reload', `Call LLM models even if same prompt with result is in the cache `, false);
|
|
13053
|
-
testCommand.
|
|
13054
|
-
|
|
13776
|
+
testCommand.action(handleActionErrors(async (filesGlob, cliOptions) => {
|
|
13777
|
+
const { ignore: ignoreRaw = '', validation: isValidated, prepare: isPrepared, reload: isCacheReloaded, verbose: isVerbose, } = cliOptions;
|
|
13055
13778
|
let tools = undefined;
|
|
13056
13779
|
if (isPrepared) {
|
|
13057
13780
|
// TODO: DRY [◽]
|
|
@@ -13060,7 +13783,7 @@
|
|
|
13060
13783
|
isCacheReloaded,
|
|
13061
13784
|
}; /* <- TODO: ` satisfies PrepareAndScrapeOptions` */
|
|
13062
13785
|
const fs = $provideFilesystemForNode(prepareAndScrapeOptions);
|
|
13063
|
-
const llm = await $
|
|
13786
|
+
const llm = await $provideLlmToolsForCli({ cliOptions, ...prepareAndScrapeOptions });
|
|
13064
13787
|
const executables = await $provideExecutablesForNode(prepareAndScrapeOptions);
|
|
13065
13788
|
tools = {
|
|
13066
13789
|
llm,
|
|
@@ -13120,6 +13843,18 @@
|
|
|
13120
13843
|
* Note: This is named "test-command.ts" to avoid name collision with jest unit test files
|
|
13121
13844
|
*/
|
|
13122
13845
|
|
|
13846
|
+
/**
|
|
13847
|
+
* Note: `$` is used to indicate that this function is not a pure function - it registers an option in the CLI
|
|
13848
|
+
*
|
|
13849
|
+
* @private utility of CLI
|
|
13850
|
+
*/
|
|
13851
|
+
function $addGlobalOptionsToCommand(command) {
|
|
13852
|
+
command.option('-v, --verbose', `Log more details`, false);
|
|
13853
|
+
command.option('--no-interactive', `Input is not interactive, if true, no CLI input is prompted if required, so either defaults are used or the command fails with exit code 1`);
|
|
13854
|
+
command.option('-p, --provider <provider>', `Which LLM provider to use: "BYOK" / "BRING_YOUR_OWN_KEYS" or "REMOTE_SERVER" / "RS"`, 'REMOTE_SERVER');
|
|
13855
|
+
command.option('--remote-server-url <url>', `URL of remote server to use when `, DEFAULT_REMOTE_SERVER_URL);
|
|
13856
|
+
}
|
|
13857
|
+
|
|
13123
13858
|
/**
|
|
13124
13859
|
* Runs CLI utilities of Promptbook package
|
|
13125
13860
|
*
|
|
@@ -13144,8 +13879,10 @@
|
|
|
13144
13879
|
program.alias('ptbk');
|
|
13145
13880
|
program.version(PROMPTBOOK_ENGINE_VERSION);
|
|
13146
13881
|
program.description(CLAIM);
|
|
13882
|
+
// Note: Theese options are valid for all commands
|
|
13147
13883
|
$initializeAboutCommand(program);
|
|
13148
13884
|
$initializeRunCommand(program);
|
|
13885
|
+
$initializeLoginCommand(program);
|
|
13149
13886
|
$initializeHelloCommand(program);
|
|
13150
13887
|
$initializeMakeCommand(program);
|
|
13151
13888
|
$initializePrettifyCommand(program);
|
|
@@ -13153,6 +13890,8 @@
|
|
|
13153
13890
|
$initializeListModelsCommand(program);
|
|
13154
13891
|
$initializeListScrapersCommand(program);
|
|
13155
13892
|
$initializeStartServerCommand(program);
|
|
13893
|
+
// TODO: [🧠] Should it be here or not> $addGlobalOptionsToCommand(program);
|
|
13894
|
+
program.commands.forEach($addGlobalOptionsToCommand);
|
|
13156
13895
|
program.parse(process.argv);
|
|
13157
13896
|
}
|
|
13158
13897
|
/**
|
|
@@ -13202,8 +13941,7 @@
|
|
|
13202
13941
|
options: {
|
|
13203
13942
|
apiKey: 'sk-ant-api03-',
|
|
13204
13943
|
isProxied: true,
|
|
13205
|
-
|
|
13206
|
-
path: DEFAULT_REMOTE_URL_PATH,
|
|
13944
|
+
remoteServerUrl: DEFAULT_REMOTE_SERVER_URL,
|
|
13207
13945
|
},
|
|
13208
13946
|
};
|
|
13209
13947
|
},
|
|
@@ -13226,146 +13964,6 @@
|
|
|
13226
13964
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
13227
13965
|
*/
|
|
13228
13966
|
|
|
13229
|
-
/**
|
|
13230
|
-
* Creates a connection to the remote proxy server.
|
|
13231
|
-
*
|
|
13232
|
-
* Note: This function creates a connection to the remote server and returns a socket but responsibility of closing the connection is on the caller
|
|
13233
|
-
*
|
|
13234
|
-
* @private internal utility function
|
|
13235
|
-
*/
|
|
13236
|
-
async function createRemoteClient(options) {
|
|
13237
|
-
const { remoteUrl, path } = options;
|
|
13238
|
-
return new Promise((resolve, reject) => {
|
|
13239
|
-
const socket = socket_ioClient.io(remoteUrl, {
|
|
13240
|
-
retries: CONNECTION_RETRIES_LIMIT,
|
|
13241
|
-
timeout: CONNECTION_TIMEOUT_MS,
|
|
13242
|
-
path,
|
|
13243
|
-
// path: `${this.remoteUrl.pathname}/socket.io`,
|
|
13244
|
-
transports: [/*'websocket', <- TODO: [🌬] Make websocket transport work */ 'polling'],
|
|
13245
|
-
});
|
|
13246
|
-
// console.log('Connecting to', this.options.remoteUrl.href, { socket });
|
|
13247
|
-
socket.on('connect', () => {
|
|
13248
|
-
resolve(socket);
|
|
13249
|
-
});
|
|
13250
|
-
// TODO: [💩] Better timeout handling
|
|
13251
|
-
setTimeout(() => {
|
|
13252
|
-
reject(new Error(`Timeout while connecting to ${remoteUrl}`));
|
|
13253
|
-
}, CONNECTION_TIMEOUT_MS);
|
|
13254
|
-
});
|
|
13255
|
-
}
|
|
13256
|
-
|
|
13257
|
-
/**
|
|
13258
|
-
* Remote server is a proxy server that uses its execution tools internally and exposes the executor interface externally.
|
|
13259
|
-
*
|
|
13260
|
-
* You can simply use `RemoteExecutionTools` on client-side javascript and connect to your remote server.
|
|
13261
|
-
* This is useful to make all logic on browser side but not expose your API keys or no need to use customer's GPU.
|
|
13262
|
-
*
|
|
13263
|
-
* @see https://github.com/webgptorg/promptbook#remote-server
|
|
13264
|
-
* @public exported from `@promptbook/remote-client`
|
|
13265
|
-
*/
|
|
13266
|
-
class RemoteLlmExecutionTools {
|
|
13267
|
-
/* <- TODO: [🍚] `, Destroyable` */
|
|
13268
|
-
constructor(options) {
|
|
13269
|
-
this.options = options;
|
|
13270
|
-
}
|
|
13271
|
-
get title() {
|
|
13272
|
-
// TODO: [🧠] Maybe fetch title+description from the remote server (as well as if model methods are defined)
|
|
13273
|
-
return 'Remote server';
|
|
13274
|
-
}
|
|
13275
|
-
get description() {
|
|
13276
|
-
return 'Use all models by your remote server';
|
|
13277
|
-
}
|
|
13278
|
-
/**
|
|
13279
|
-
* Check the configuration of all execution tools
|
|
13280
|
-
*/
|
|
13281
|
-
async checkConfiguration() {
|
|
13282
|
-
const socket = await createRemoteClient(this.options);
|
|
13283
|
-
socket.disconnect();
|
|
13284
|
-
// TODO: [main] !!3 Check version of the remote server and compatibility
|
|
13285
|
-
// TODO: [🎍] Send checkConfiguration
|
|
13286
|
-
}
|
|
13287
|
-
/**
|
|
13288
|
-
* List all available models that can be used
|
|
13289
|
-
*/
|
|
13290
|
-
async listModels() {
|
|
13291
|
-
// TODO: [👒] Listing models (and checking configuration) probbably should go through REST API not Socket.io
|
|
13292
|
-
const socket = await createRemoteClient(this.options);
|
|
13293
|
-
socket.emit('listModels-request', {
|
|
13294
|
-
identification: this.options.identification,
|
|
13295
|
-
} /* <- Note: [🤛] */);
|
|
13296
|
-
const promptResult = await new Promise((resolve, reject) => {
|
|
13297
|
-
socket.on('listModels-response', (response) => {
|
|
13298
|
-
resolve(response.models);
|
|
13299
|
-
socket.disconnect();
|
|
13300
|
-
});
|
|
13301
|
-
socket.on('error', (error) => {
|
|
13302
|
-
reject(deserializeError(error));
|
|
13303
|
-
socket.disconnect();
|
|
13304
|
-
});
|
|
13305
|
-
});
|
|
13306
|
-
socket.disconnect();
|
|
13307
|
-
return promptResult;
|
|
13308
|
-
}
|
|
13309
|
-
/**
|
|
13310
|
-
* Calls remote proxy server to use a chat model
|
|
13311
|
-
*/
|
|
13312
|
-
callChatModel(prompt) {
|
|
13313
|
-
if (this.options.isVerbose) {
|
|
13314
|
-
console.info(`🖋 Remote callChatModel call`);
|
|
13315
|
-
}
|
|
13316
|
-
return /* not await */ this.callCommonModel(prompt);
|
|
13317
|
-
}
|
|
13318
|
-
/**
|
|
13319
|
-
* Calls remote proxy server to use a completion model
|
|
13320
|
-
*/
|
|
13321
|
-
callCompletionModel(prompt) {
|
|
13322
|
-
if (this.options.isVerbose) {
|
|
13323
|
-
console.info(`💬 Remote callCompletionModel call`);
|
|
13324
|
-
}
|
|
13325
|
-
return /* not await */ this.callCommonModel(prompt);
|
|
13326
|
-
}
|
|
13327
|
-
/**
|
|
13328
|
-
* Calls remote proxy server to use a embedding model
|
|
13329
|
-
*/
|
|
13330
|
-
callEmbeddingModel(prompt) {
|
|
13331
|
-
if (this.options.isVerbose) {
|
|
13332
|
-
console.info(`💬 Remote callEmbeddingModel call`);
|
|
13333
|
-
}
|
|
13334
|
-
return /* not await */ this.callCommonModel(prompt);
|
|
13335
|
-
}
|
|
13336
|
-
// <- Note: [🤖] callXxxModel
|
|
13337
|
-
/**
|
|
13338
|
-
* Calls remote proxy server to use both completion or chat model
|
|
13339
|
-
*/
|
|
13340
|
-
async callCommonModel(prompt) {
|
|
13341
|
-
const socket = await createRemoteClient(this.options);
|
|
13342
|
-
socket.emit('prompt-request', {
|
|
13343
|
-
identification: this.options.identification,
|
|
13344
|
-
prompt,
|
|
13345
|
-
} /* <- Note: [🤛] */);
|
|
13346
|
-
const promptResult = await new Promise((resolve, reject) => {
|
|
13347
|
-
socket.on('prompt-response', (response) => {
|
|
13348
|
-
resolve(response.promptResult);
|
|
13349
|
-
socket.disconnect();
|
|
13350
|
-
});
|
|
13351
|
-
socket.on('error', (error) => {
|
|
13352
|
-
reject(deserializeError(error));
|
|
13353
|
-
socket.disconnect();
|
|
13354
|
-
});
|
|
13355
|
-
});
|
|
13356
|
-
socket.disconnect();
|
|
13357
|
-
return promptResult;
|
|
13358
|
-
}
|
|
13359
|
-
}
|
|
13360
|
-
/**
|
|
13361
|
-
* TODO: Maybe use `$exportJson`
|
|
13362
|
-
* TODO: [🧠][🛍] Maybe not `isAnonymous: boolean` BUT `mode: 'ANONYMOUS'|'COLLECTION'`
|
|
13363
|
-
* TODO: [🍓] Allow to list compatible models with each variant
|
|
13364
|
-
* TODO: [🗯] RemoteLlmExecutionTools should extend Destroyable and implement IDestroyable
|
|
13365
|
-
* TODO: [🧠][🌰] Allow to pass `title` for tracking purposes
|
|
13366
|
-
* TODO: [🧠] Maybe remove `@promptbook/remote-client` and just use `@promptbook/core`
|
|
13367
|
-
*/
|
|
13368
|
-
|
|
13369
13967
|
/**
|
|
13370
13968
|
* Function computeUsage will create price per one token based on the string value found on openai page
|
|
13371
13969
|
*
|
|
@@ -13466,7 +14064,7 @@
|
|
|
13466
14064
|
* Helper of usage compute
|
|
13467
14065
|
*
|
|
13468
14066
|
* @param content the content of prompt or response
|
|
13469
|
-
* @returns part of
|
|
14067
|
+
* @returns part of UsageCounts
|
|
13470
14068
|
*
|
|
13471
14069
|
* @private internal utility of LlmExecutionTools
|
|
13472
14070
|
*/
|
|
@@ -13490,7 +14088,7 @@
|
|
|
13490
14088
|
*/
|
|
13491
14089
|
function uncertainNumber(value) {
|
|
13492
14090
|
if (value === null || value === undefined || Number.isNaN(value)) {
|
|
13493
|
-
return
|
|
14091
|
+
return UNCERTAIN_ZERO_VALUE;
|
|
13494
14092
|
}
|
|
13495
14093
|
return { value };
|
|
13496
14094
|
}
|
|
@@ -13727,7 +14325,7 @@
|
|
|
13727
14325
|
const resultContent = rawResponse.choices[0].text;
|
|
13728
14326
|
// eslint-disable-next-line prefer-const
|
|
13729
14327
|
complete = $getCurrentDate();
|
|
13730
|
-
const usage = { price: 'UNKNOWN', inputTokens: 0, outputTokens: 0 /* <- TODO: [🐞] Compute usage * / } satisfies
|
|
14328
|
+
const usage = { price: 'UNKNOWN', inputTokens: 0, outputTokens: 0 /* <- TODO: [🐞] Compute usage * / } satisfies Usage;
|
|
13731
14329
|
|
|
13732
14330
|
|
|
13733
14331
|
|
|
@@ -14823,11 +15421,17 @@
|
|
|
14823
15421
|
throw new PipelineExecutionError('No response message');
|
|
14824
15422
|
}
|
|
14825
15423
|
const complete = $getCurrentDate();
|
|
14826
|
-
|
|
14827
|
-
|
|
14828
|
-
|
|
14829
|
-
|
|
14830
|
-
|
|
15424
|
+
const usage = {
|
|
15425
|
+
price: UNCERTAIN_ZERO_VALUE,
|
|
15426
|
+
input: {
|
|
15427
|
+
tokensCount: uncertainNumber(rawResponse.usage.promptTokens),
|
|
15428
|
+
...computeUsageCounts(rawPromptContent),
|
|
15429
|
+
},
|
|
15430
|
+
output: {
|
|
15431
|
+
tokensCount: uncertainNumber(rawResponse.usage.completionTokens),
|
|
15432
|
+
...computeUsageCounts(rawResponse.text),
|
|
15433
|
+
},
|
|
15434
|
+
};
|
|
14831
15435
|
return exportJson({
|
|
14832
15436
|
name: 'promptResult',
|
|
14833
15437
|
message: `Result of \`createExecutionToolsFromVercelProvider.callChatModel\``,
|
|
@@ -15180,15 +15784,18 @@
|
|
|
15180
15784
|
}
|
|
15181
15785
|
return this.client;
|
|
15182
15786
|
}
|
|
15787
|
+
/*
|
|
15788
|
+
Note: Commenting this out to avoid circular dependency
|
|
15183
15789
|
/**
|
|
15184
15790
|
* Create (sub)tools for calling OpenAI API Assistants
|
|
15185
15791
|
*
|
|
15186
15792
|
* @param assistantId Which assistant to use
|
|
15187
15793
|
* @returns Tools for calling OpenAI API Assistants with same token
|
|
15188
|
-
|
|
15189
|
-
createAssistantSubtools(assistantId) {
|
|
15794
|
+
* /
|
|
15795
|
+
public createAssistantSubtools(assistantId: string_token): OpenAiAssistantExecutionTools {
|
|
15190
15796
|
return new OpenAiAssistantExecutionTools({ ...this.options, assistantId });
|
|
15191
15797
|
}
|
|
15798
|
+
*/
|
|
15192
15799
|
/**
|
|
15193
15800
|
* Check the `options` passed to `constructor`
|
|
15194
15801
|
*/
|