@promptbook/openai 0.105.0-8 → 0.105.0
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 +0 -4
- package/esm/index.es.js +1403 -1047
- package/esm/index.es.js.map +1 -1
- package/esm/typings/src/_packages/browser.index.d.ts +2 -0
- package/esm/typings/src/_packages/components.index.d.ts +20 -0
- package/esm/typings/src/_packages/core.index.d.ts +21 -11
- package/esm/typings/src/_packages/node.index.d.ts +2 -0
- package/esm/typings/src/_packages/openai.index.d.ts +4 -0
- package/esm/typings/src/_packages/types.index.d.ts +32 -2
- package/esm/typings/src/_packages/utils.index.d.ts +2 -0
- package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +10 -1
- package/esm/typings/src/book-2.0/agent-source/parseTeamCommitment.d.ts +28 -0
- package/esm/typings/src/book-components/BookEditor/BookEditor.d.ts +1 -1
- package/esm/typings/src/book-components/Chat/AgentChat/AgentChatProps.d.ts +5 -0
- package/esm/typings/src/book-components/Chat/AgentChip/AgentChip.d.ts +67 -0
- package/esm/typings/src/book-components/Chat/AgentChip/index.d.ts +2 -0
- package/esm/typings/src/book-components/Chat/Chat/ChatMessageItem.d.ts +33 -1
- package/esm/typings/src/book-components/Chat/Chat/ChatProps.d.ts +87 -6
- package/esm/typings/src/book-components/Chat/Chat/ChatSoundToggle.d.ts +23 -0
- package/esm/typings/src/book-components/Chat/Chat/ClockIcon.d.ts +9 -0
- package/esm/typings/src/book-components/Chat/LlmChat/FriendlyErrorMessage.d.ts +20 -0
- package/esm/typings/src/book-components/Chat/LlmChat/LlmChatProps.d.ts +13 -0
- package/esm/typings/src/book-components/Chat/SourceChip/SourceChip.d.ts +35 -0
- package/esm/typings/src/book-components/Chat/SourceChip/index.d.ts +2 -0
- package/esm/typings/src/book-components/Chat/effects/ChatEffectsSystem.d.ts +14 -0
- package/esm/typings/src/book-components/Chat/effects/components/ConfettiEffect.d.ts +18 -0
- package/esm/typings/src/book-components/Chat/effects/components/HeartsEffect.d.ts +18 -0
- package/esm/typings/src/book-components/Chat/effects/configs/defaultEffectConfigs.d.ts +7 -0
- package/esm/typings/src/book-components/Chat/effects/index.d.ts +18 -0
- package/esm/typings/src/book-components/Chat/effects/types/ChatEffect.d.ts +20 -0
- package/esm/typings/src/book-components/Chat/effects/types/ChatEffectConfig.d.ts +21 -0
- package/esm/typings/src/book-components/Chat/effects/types/ChatEffectType.d.ts +6 -0
- package/esm/typings/src/book-components/Chat/effects/types/ChatEffectsSystemProps.d.ts +32 -0
- package/esm/typings/src/book-components/Chat/effects/utils/detectEffects.d.ts +12 -0
- package/esm/typings/src/book-components/Chat/types/ChatMessage.d.ts +34 -6
- package/esm/typings/src/book-components/Chat/types/ChatParticipant.d.ts +8 -0
- package/esm/typings/src/book-components/Chat/utils/createTeamToolNameFromUrl.d.ts +12 -0
- package/esm/typings/src/book-components/Chat/utils/getToolCallChipletText.d.ts +40 -0
- package/esm/typings/src/book-components/Chat/utils/loadAgentProfile.d.ts +69 -0
- package/esm/typings/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +53 -0
- package/esm/typings/src/book-components/Chat/utils/resolveCitationUrl.d.ts +11 -0
- package/esm/typings/src/book-components/Chat/utils/resolveCitationUrl.test.d.ts +1 -0
- package/esm/typings/src/book-components/Chat/utils/toolCallParsing.d.ts +64 -0
- package/esm/typings/src/book-components/icons/EmailIcon.d.ts +15 -0
- package/esm/typings/src/commitments/TEAM/TEAM.d.ts +45 -0
- package/esm/typings/src/commitments/TEMPLATE/TEMPLATE.d.ts +44 -0
- package/esm/typings/src/commitments/TEMPLATE/TEMPLATE.test.d.ts +1 -0
- package/esm/typings/src/commitments/USE_BROWSER/USE_BROWSER.d.ts +19 -1
- package/esm/typings/src/commitments/USE_BROWSER/fetchUrlContent.d.ts +22 -0
- package/esm/typings/src/commitments/USE_BROWSER/fetchUrlContentViaBrowser.d.ts +13 -0
- package/esm/typings/src/commitments/USE_EMAIL/USE_EMAIL.d.ts +48 -0
- package/esm/typings/src/commitments/USE_EMAIL/resolveSendEmailToolForNode.d.ts +11 -0
- package/esm/typings/src/commitments/USE_EMAIL/sendEmailViaBrowser.d.ts +18 -0
- package/esm/typings/src/commitments/USE_IMAGE_GENERATOR/USE_IMAGE_GENERATOR.d.ts +46 -0
- package/esm/typings/src/commitments/USE_IMAGE_GENERATOR/USE_IMAGE_GENERATOR.test.d.ts +1 -0
- package/esm/typings/src/commitments/USE_SEARCH_ENGINE/USE_SEARCH_ENGINE.d.ts +5 -0
- package/esm/typings/src/commitments/USE_SEARCH_ENGINE/USE_SEARCH_ENGINE.test.d.ts +1 -0
- package/esm/typings/src/commitments/USE_TIME/USE_TIME.d.ts +6 -0
- package/esm/typings/src/commitments/_base/BaseCommitmentDefinition.d.ts +6 -0
- package/esm/typings/src/commitments/_base/CommitmentDefinition.d.ts +6 -0
- package/esm/typings/src/commitments/_base/formatOptionalInstructionBlock.d.ts +6 -0
- package/esm/typings/src/commitments/_common/commitmentToolFunctions.d.ts +26 -0
- package/esm/typings/src/commitments/_common/getAllCommitmentDefinitions.d.ts +8 -0
- package/esm/typings/src/commitments/_common/getAllCommitmentTypes.d.ts +8 -0
- package/esm/typings/src/commitments/_common/getAllCommitmentsToolFunctionsForBrowser.d.ts +9 -0
- package/esm/typings/src/commitments/_common/getAllCommitmentsToolFunctionsForNode.d.ts +13 -0
- package/esm/typings/src/commitments/_common/getAllCommitmentsToolTitles.d.ts +7 -0
- package/esm/typings/src/commitments/_common/getCommitmentDefinition.d.ts +10 -0
- package/esm/typings/src/commitments/_common/getGroupedCommitmentDefinitions.d.ts +17 -0
- package/esm/typings/src/commitments/_common/isCommitmentSupported.d.ts +9 -0
- package/esm/typings/src/commitments/index.d.ts +5 -58
- package/esm/typings/src/config.d.ts +6 -0
- package/esm/typings/src/constants.d.ts +129 -0
- package/esm/typings/src/executables/$provideExecutablesForNode.d.ts +1 -0
- package/esm/typings/src/execution/AvailableModel.d.ts +5 -4
- package/esm/typings/src/execution/PromptResult.d.ts +2 -19
- package/esm/typings/src/execution/createPipelineExecutor/10-executePipeline.d.ts +1 -1
- package/esm/typings/src/execution/utils/$provideExecutionToolsForNode.d.ts +1 -0
- package/esm/typings/src/llm-providers/agent/Agent.d.ts +15 -1
- package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +6 -1
- package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +5 -0
- package/esm/typings/src/llm-providers/google/createGoogleExecutionTools.d.ts +1 -0
- package/esm/typings/src/llm-providers/openai/OpenAiAgentExecutionTools.d.ts +43 -0
- package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +4 -2
- package/esm/typings/src/llm-providers/openai/OpenAiCompatibleExecutionTools.d.ts +1 -1
- package/esm/typings/src/llm-providers/openai/createOpenAiAgentExecutionTools.d.ts +11 -0
- package/esm/typings/src/llm-providers/openai/utils/uploadFilesToOpenAi.d.ts +7 -0
- package/esm/typings/src/pipeline/prompt-notation.d.ts +27 -2
- package/esm/typings/src/pipeline/prompt-notation.test.d.ts +1 -1
- package/esm/typings/src/scrapers/_common/register/$provideFilesystemForNode.d.ts +1 -0
- package/esm/typings/src/scrapers/_common/register/$provideScrapersForNode.d.ts +1 -0
- package/esm/typings/src/scrapers/_common/register/$provideScriptingForNode.d.ts +1 -0
- package/esm/typings/src/search-engines/SearchEngine.d.ts +1 -1
- package/esm/typings/src/search-engines/bing/BingSearchEngine.d.ts +1 -1
- package/esm/typings/src/search-engines/dummy/DummySearchEngine.d.ts +1 -1
- package/esm/typings/src/search-engines/google/GoogleSearchEngine.d.ts +1 -1
- package/esm/typings/src/search-engines/serp/SerpSearchEngine.d.ts +1 -1
- package/esm/typings/src/speech-recognition/OpenAiSpeechRecognition.d.ts +3 -0
- package/esm/typings/src/types/ModelRequirements.d.ts +6 -0
- package/esm/typings/src/types/Prompt.d.ts +12 -0
- package/esm/typings/src/types/ToolCall.d.ts +37 -0
- package/esm/typings/src/utils/markdown/extractAllListItemsFromMarkdown.d.ts +1 -2
- package/esm/typings/src/utils/markdown/humanizeAiTextEllipsis.d.ts +1 -1
- package/esm/typings/src/utils/markdown/humanizeAiTextEmdashed.d.ts +1 -1
- package/esm/typings/src/utils/markdown/humanizeAiTextWhitespace.d.ts +1 -1
- package/esm/typings/src/utils/markdown/parseMarkdownSection.d.ts +1 -3
- package/esm/typings/src/utils/markdown/splitMarkdownIntoSections.d.ts +1 -2
- package/esm/typings/src/utils/misc/linguisticHash.d.ts +4 -1
- package/esm/typings/src/utils/parameters/templateParameters.d.ts +1 -2
- package/esm/typings/src/version.d.ts +1 -1
- package/esm/typings/src/wizard/wizard.d.ts +1 -4
- package/package.json +2 -2
- package/umd/index.umd.js +1565 -1207
- package/umd/index.umd.js.map +1 -1
package/esm/index.es.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import colors from 'colors';
|
|
2
|
+
import { randomBytes } from 'crypto';
|
|
2
3
|
import spaceTrim$2, { spaceTrim as spaceTrim$1 } from 'spacetrim';
|
|
4
|
+
import Bottleneck from 'bottleneck';
|
|
5
|
+
import OpenAI from 'openai';
|
|
3
6
|
import 'path';
|
|
4
|
-
import { randomBytes } from 'crypto';
|
|
5
7
|
import 'crypto-js';
|
|
6
8
|
import 'crypto-js/enc-hex';
|
|
7
|
-
import Bottleneck from 'bottleneck';
|
|
8
|
-
import OpenAI from 'openai';
|
|
9
9
|
import { io } from 'socket.io-client';
|
|
10
10
|
|
|
11
11
|
// ⚠️ WARNING: This code has been generated so that any manual changes will be overwritten
|
|
@@ -22,7 +22,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
|
|
|
22
22
|
* @generated
|
|
23
23
|
* @see https://github.com/webgptorg/promptbook
|
|
24
24
|
*/
|
|
25
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.105.0
|
|
25
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.105.0';
|
|
26
26
|
/**
|
|
27
27
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
28
28
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -69,6 +69,149 @@ function $isRunningInWebWorker() {
|
|
|
69
69
|
* TODO: [🎺]
|
|
70
70
|
*/
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Generates random token
|
|
74
|
+
*
|
|
75
|
+
* Note: `$` is used to indicate that this function is not a pure function - it is not deterministic
|
|
76
|
+
* Note: This function is cryptographically secure (it uses crypto.randomBytes internally)
|
|
77
|
+
*
|
|
78
|
+
* @private internal helper function
|
|
79
|
+
* @returns secure random token
|
|
80
|
+
*/
|
|
81
|
+
function $randomToken(randomness) {
|
|
82
|
+
return randomBytes(randomness).toString('hex');
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* TODO: [🤶] Maybe export through `@promptbook/utils` or `@promptbook/random` package
|
|
86
|
+
* TODO: Maybe use nanoid instead https://github.com/ai/nanoid
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* This error indicates errors during the execution of the pipeline
|
|
91
|
+
*
|
|
92
|
+
* @public exported from `@promptbook/core`
|
|
93
|
+
*/
|
|
94
|
+
class PipelineExecutionError extends Error {
|
|
95
|
+
constructor(message) {
|
|
96
|
+
// Added id parameter
|
|
97
|
+
super(message);
|
|
98
|
+
this.name = 'PipelineExecutionError';
|
|
99
|
+
// TODO: [🐙] DRY - Maybe $randomId
|
|
100
|
+
this.id = `error-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid similar char conflicts */)}`;
|
|
101
|
+
Object.setPrototypeOf(this, PipelineExecutionError.prototype);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* TODO: [🧠][🌂] Add id to all errors
|
|
106
|
+
*/
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Freezes the given object and all its nested objects recursively
|
|
110
|
+
*
|
|
111
|
+
* Note: `$` is used to indicate that this function is not a pure function - it mutates given object
|
|
112
|
+
* Note: This function mutates the object and returns the original (but mutated-deep-freezed) object
|
|
113
|
+
*
|
|
114
|
+
* @returns The same object as the input, but deeply frozen
|
|
115
|
+
* @public exported from `@promptbook/utils`
|
|
116
|
+
*/
|
|
117
|
+
function $deepFreeze(objectValue) {
|
|
118
|
+
if (Array.isArray(objectValue)) {
|
|
119
|
+
return Object.freeze(objectValue.map((item) => $deepFreeze(item)));
|
|
120
|
+
}
|
|
121
|
+
const propertyNames = Object.getOwnPropertyNames(objectValue);
|
|
122
|
+
for (const propertyName of propertyNames) {
|
|
123
|
+
const value = objectValue[propertyName];
|
|
124
|
+
if (value && typeof value === 'object') {
|
|
125
|
+
$deepFreeze(value);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
Object.freeze(objectValue);
|
|
129
|
+
return objectValue;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* TODO: [🧠] Is there a way how to meaningfully test this utility
|
|
133
|
+
*/
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Represents the uncertain value
|
|
137
|
+
*
|
|
138
|
+
* @public exported from `@promptbook/core`
|
|
139
|
+
*/
|
|
140
|
+
const ZERO_VALUE = $deepFreeze({ value: 0 });
|
|
141
|
+
/**
|
|
142
|
+
* Represents the uncertain value
|
|
143
|
+
*
|
|
144
|
+
* @public exported from `@promptbook/core`
|
|
145
|
+
*/
|
|
146
|
+
const UNCERTAIN_ZERO_VALUE = $deepFreeze({ value: 0, isUncertain: true });
|
|
147
|
+
/**
|
|
148
|
+
* Represents the usage with no resources consumed
|
|
149
|
+
*
|
|
150
|
+
* @public exported from `@promptbook/core`
|
|
151
|
+
*/
|
|
152
|
+
const ZERO_USAGE = $deepFreeze({
|
|
153
|
+
price: ZERO_VALUE,
|
|
154
|
+
input: {
|
|
155
|
+
tokensCount: ZERO_VALUE,
|
|
156
|
+
charactersCount: ZERO_VALUE,
|
|
157
|
+
wordsCount: ZERO_VALUE,
|
|
158
|
+
sentencesCount: ZERO_VALUE,
|
|
159
|
+
linesCount: ZERO_VALUE,
|
|
160
|
+
paragraphsCount: ZERO_VALUE,
|
|
161
|
+
pagesCount: ZERO_VALUE,
|
|
162
|
+
},
|
|
163
|
+
output: {
|
|
164
|
+
tokensCount: ZERO_VALUE,
|
|
165
|
+
charactersCount: ZERO_VALUE,
|
|
166
|
+
wordsCount: ZERO_VALUE,
|
|
167
|
+
sentencesCount: ZERO_VALUE,
|
|
168
|
+
linesCount: ZERO_VALUE,
|
|
169
|
+
paragraphsCount: ZERO_VALUE,
|
|
170
|
+
pagesCount: ZERO_VALUE,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
/**
|
|
174
|
+
* Represents the usage with unknown resources consumed
|
|
175
|
+
*
|
|
176
|
+
* @public exported from `@promptbook/core`
|
|
177
|
+
*/
|
|
178
|
+
const UNCERTAIN_USAGE = $deepFreeze({
|
|
179
|
+
price: UNCERTAIN_ZERO_VALUE,
|
|
180
|
+
input: {
|
|
181
|
+
tokensCount: UNCERTAIN_ZERO_VALUE,
|
|
182
|
+
charactersCount: UNCERTAIN_ZERO_VALUE,
|
|
183
|
+
wordsCount: UNCERTAIN_ZERO_VALUE,
|
|
184
|
+
sentencesCount: UNCERTAIN_ZERO_VALUE,
|
|
185
|
+
linesCount: UNCERTAIN_ZERO_VALUE,
|
|
186
|
+
paragraphsCount: UNCERTAIN_ZERO_VALUE,
|
|
187
|
+
pagesCount: UNCERTAIN_ZERO_VALUE,
|
|
188
|
+
},
|
|
189
|
+
output: {
|
|
190
|
+
tokensCount: UNCERTAIN_ZERO_VALUE,
|
|
191
|
+
charactersCount: UNCERTAIN_ZERO_VALUE,
|
|
192
|
+
wordsCount: UNCERTAIN_ZERO_VALUE,
|
|
193
|
+
sentencesCount: UNCERTAIN_ZERO_VALUE,
|
|
194
|
+
linesCount: UNCERTAIN_ZERO_VALUE,
|
|
195
|
+
paragraphsCount: UNCERTAIN_ZERO_VALUE,
|
|
196
|
+
pagesCount: UNCERTAIN_ZERO_VALUE,
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
/**
|
|
200
|
+
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
201
|
+
*/
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Simple wrapper `new Date().toISOString()`
|
|
205
|
+
*
|
|
206
|
+
* Note: `$` is used to indicate that this function is not a pure function - it is not deterministic because it depends on the current time
|
|
207
|
+
*
|
|
208
|
+
* @returns string_date branded type
|
|
209
|
+
* @public exported from `@promptbook/utils`
|
|
210
|
+
*/
|
|
211
|
+
function $getCurrentDate() {
|
|
212
|
+
return new Date().toISOString();
|
|
213
|
+
}
|
|
214
|
+
|
|
72
215
|
/**
|
|
73
216
|
* Trims string from all 4 sides
|
|
74
217
|
*
|
|
@@ -1096,33 +1239,6 @@ function orderJson(options) {
|
|
|
1096
1239
|
return orderedValue;
|
|
1097
1240
|
}
|
|
1098
1241
|
|
|
1099
|
-
/**
|
|
1100
|
-
* Freezes the given object and all its nested objects recursively
|
|
1101
|
-
*
|
|
1102
|
-
* Note: `$` is used to indicate that this function is not a pure function - it mutates given object
|
|
1103
|
-
* Note: This function mutates the object and returns the original (but mutated-deep-freezed) object
|
|
1104
|
-
*
|
|
1105
|
-
* @returns The same object as the input, but deeply frozen
|
|
1106
|
-
* @public exported from `@promptbook/utils`
|
|
1107
|
-
*/
|
|
1108
|
-
function $deepFreeze(objectValue) {
|
|
1109
|
-
if (Array.isArray(objectValue)) {
|
|
1110
|
-
return Object.freeze(objectValue.map((item) => $deepFreeze(item)));
|
|
1111
|
-
}
|
|
1112
|
-
const propertyNames = Object.getOwnPropertyNames(objectValue);
|
|
1113
|
-
for (const propertyName of propertyNames) {
|
|
1114
|
-
const value = objectValue[propertyName];
|
|
1115
|
-
if (value && typeof value === 'object') {
|
|
1116
|
-
$deepFreeze(value);
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
Object.freeze(objectValue);
|
|
1120
|
-
return objectValue;
|
|
1121
|
-
}
|
|
1122
|
-
/**
|
|
1123
|
-
* TODO: [🧠] Is there a way how to meaningfully test this utility
|
|
1124
|
-
*/
|
|
1125
|
-
|
|
1126
1242
|
/**
|
|
1127
1243
|
* Make error report URL for the given error
|
|
1128
1244
|
*
|
|
@@ -1488,64 +1604,304 @@ exportJson({
|
|
|
1488
1604
|
*/
|
|
1489
1605
|
|
|
1490
1606
|
/**
|
|
1491
|
-
*
|
|
1607
|
+
* This error type indicates that some limit was reached
|
|
1492
1608
|
*
|
|
1493
|
-
*
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1609
|
+
* @public exported from `@promptbook/core`
|
|
1610
|
+
*/
|
|
1611
|
+
class LimitReachedError extends Error {
|
|
1612
|
+
constructor(message) {
|
|
1613
|
+
super(message);
|
|
1614
|
+
this.name = 'LimitReachedError';
|
|
1615
|
+
Object.setPrototypeOf(this, LimitReachedError.prototype);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
/**
|
|
1620
|
+
* Format either small or big number
|
|
1499
1621
|
*
|
|
1500
1622
|
* @public exported from `@promptbook/utils`
|
|
1501
1623
|
*/
|
|
1502
|
-
function
|
|
1503
|
-
if (
|
|
1504
|
-
return
|
|
1624
|
+
function numberToString(value) {
|
|
1625
|
+
if (value === 0) {
|
|
1626
|
+
return '0';
|
|
1505
1627
|
}
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
url = url.replace(/^blob:/, '');
|
|
1509
|
-
}
|
|
1510
|
-
const urlObject = new URL(url /* because fail is handled */);
|
|
1511
|
-
if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
|
|
1512
|
-
return false;
|
|
1513
|
-
}
|
|
1514
|
-
return true;
|
|
1628
|
+
else if (Number.isNaN(value)) {
|
|
1629
|
+
return VALUE_STRINGS.nan;
|
|
1515
1630
|
}
|
|
1516
|
-
|
|
1517
|
-
return
|
|
1631
|
+
else if (value === Infinity) {
|
|
1632
|
+
return VALUE_STRINGS.infinity;
|
|
1633
|
+
}
|
|
1634
|
+
else if (value === -Infinity) {
|
|
1635
|
+
return VALUE_STRINGS.negativeInfinity;
|
|
1636
|
+
}
|
|
1637
|
+
for (let exponent = 0; exponent < 15; exponent++) {
|
|
1638
|
+
const factor = 10 ** exponent;
|
|
1639
|
+
const valueRounded = Math.round(value * factor) / factor;
|
|
1640
|
+
if (Math.abs(value - valueRounded) / value < SMALL_NUMBER) {
|
|
1641
|
+
return valueRounded.toFixed(exponent);
|
|
1642
|
+
}
|
|
1518
1643
|
}
|
|
1644
|
+
return value.toString();
|
|
1519
1645
|
}
|
|
1520
1646
|
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
{
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1647
|
+
/**
|
|
1648
|
+
* Function `valueToString` will convert the given value to string
|
|
1649
|
+
* This is useful and used in the `templateParameters` function
|
|
1650
|
+
*
|
|
1651
|
+
* Note: This function is not just calling `toString` method
|
|
1652
|
+
* It's more complex and can handle this conversion specifically for LLM models
|
|
1653
|
+
* See `VALUE_STRINGS`
|
|
1654
|
+
*
|
|
1655
|
+
* Note: There are 2 similar functions
|
|
1656
|
+
* - `valueToString` converts value to string for LLM models as human-readable string
|
|
1657
|
+
* - `asSerializable` converts value to string to preserve full information to be able to convert it back
|
|
1658
|
+
*
|
|
1659
|
+
* @public exported from `@promptbook/utils`
|
|
1660
|
+
*/
|
|
1661
|
+
function valueToString(value) {
|
|
1662
|
+
try {
|
|
1663
|
+
if (value === '') {
|
|
1664
|
+
return VALUE_STRINGS.empty;
|
|
1665
|
+
}
|
|
1666
|
+
else if (value === null) {
|
|
1667
|
+
return VALUE_STRINGS.null;
|
|
1668
|
+
}
|
|
1669
|
+
else if (value === undefined) {
|
|
1670
|
+
return VALUE_STRINGS.undefined;
|
|
1671
|
+
}
|
|
1672
|
+
else if (typeof value === 'string') {
|
|
1673
|
+
return value;
|
|
1674
|
+
}
|
|
1675
|
+
else if (typeof value === 'number') {
|
|
1676
|
+
return numberToString(value);
|
|
1677
|
+
}
|
|
1678
|
+
else if (value instanceof Date) {
|
|
1679
|
+
return value.toISOString();
|
|
1680
|
+
}
|
|
1681
|
+
else {
|
|
1682
|
+
try {
|
|
1683
|
+
return JSON.stringify(value);
|
|
1684
|
+
}
|
|
1685
|
+
catch (error) {
|
|
1686
|
+
if (error instanceof TypeError && error.message.includes('circular structure')) {
|
|
1687
|
+
return VALUE_STRINGS.circular;
|
|
1688
|
+
}
|
|
1689
|
+
throw error;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
catch (error) {
|
|
1694
|
+
assertsError(error);
|
|
1695
|
+
console.error(error);
|
|
1696
|
+
return VALUE_STRINGS.unserializable;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
/**
|
|
1701
|
+
* Replaces parameters in template with values from parameters object
|
|
1702
|
+
*
|
|
1703
|
+
* Note: This function is not places strings into string,
|
|
1704
|
+
* It's more complex and can handle this operation specifically for LLM models
|
|
1705
|
+
*
|
|
1706
|
+
* @param template the template with parameters in {curly} braces
|
|
1707
|
+
* @param parameters the object with parameters
|
|
1708
|
+
* @returns the template with replaced parameters
|
|
1709
|
+
* @throws {PipelineExecutionError} if parameter is not defined, not closed, or not opened
|
|
1710
|
+
* @public exported from `@promptbook/utils`
|
|
1711
|
+
*/
|
|
1712
|
+
function templateParameters(template, parameters) {
|
|
1713
|
+
for (const [parameterName, parameterValue] of Object.entries(parameters)) {
|
|
1714
|
+
if (parameterValue === RESERVED_PARAMETER_MISSING_VALUE) {
|
|
1715
|
+
throw new UnexpectedError(`Parameter \`{${parameterName}}\` has missing value`);
|
|
1716
|
+
}
|
|
1717
|
+
else if (parameterValue === RESERVED_PARAMETER_RESTRICTED) {
|
|
1718
|
+
// TODO: [🍵]
|
|
1719
|
+
throw new UnexpectedError(`Parameter \`{${parameterName}}\` is restricted to use`);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
let replacedTemplates = template;
|
|
1723
|
+
let match;
|
|
1724
|
+
let loopLimit = LOOP_LIMIT;
|
|
1725
|
+
while ((match = /^(?<precol>.*){(?<parameterName>\w+)}(.*)/m /* <- Not global */
|
|
1726
|
+
.exec(replacedTemplates))) {
|
|
1727
|
+
if (loopLimit-- < 0) {
|
|
1728
|
+
throw new LimitReachedError('Loop limit reached during parameters replacement in `templateParameters`');
|
|
1729
|
+
}
|
|
1730
|
+
const precol = match.groups.precol;
|
|
1731
|
+
const parameterName = match.groups.parameterName;
|
|
1732
|
+
if (parameterName === '') {
|
|
1733
|
+
// Note: Skip empty placeholders. It's used to avoid confusion with JSON-like strings
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
1736
|
+
if (parameterName.indexOf('{') !== -1 || parameterName.indexOf('}') !== -1) {
|
|
1737
|
+
throw new PipelineExecutionError('Parameter is already opened or not closed');
|
|
1738
|
+
}
|
|
1739
|
+
if (parameters[parameterName] === undefined) {
|
|
1740
|
+
throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
|
|
1741
|
+
}
|
|
1742
|
+
let parameterValue = parameters[parameterName];
|
|
1743
|
+
if (parameterValue === undefined) {
|
|
1744
|
+
throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
|
|
1745
|
+
}
|
|
1746
|
+
parameterValue = valueToString(parameterValue);
|
|
1747
|
+
// Escape curly braces in parameter values to prevent prompt-injection
|
|
1748
|
+
parameterValue = parameterValue.replace(/[{}]/g, '\\$&');
|
|
1749
|
+
if (parameterValue.includes('\n') && /^\s*\W{0,3}\s*$/.test(precol)) {
|
|
1750
|
+
parameterValue = parameterValue
|
|
1751
|
+
.split(/\r?\n/)
|
|
1752
|
+
.map((line, index) => (index === 0 ? line : `${precol}${line}`))
|
|
1753
|
+
.join('\n');
|
|
1754
|
+
}
|
|
1755
|
+
replacedTemplates =
|
|
1756
|
+
replacedTemplates.substring(0, match.index + precol.length) +
|
|
1757
|
+
parameterValue +
|
|
1758
|
+
replacedTemplates.substring(match.index + precol.length + parameterName.length + 2);
|
|
1759
|
+
}
|
|
1760
|
+
// [💫] Check if there are parameters that are not closed properly
|
|
1761
|
+
if (/{\w+$/.test(replacedTemplates)) {
|
|
1762
|
+
throw new PipelineExecutionError('Parameter is not closed');
|
|
1763
|
+
}
|
|
1764
|
+
// [💫] Check if there are parameters that are not opened properly
|
|
1765
|
+
if (/^\w+}/.test(replacedTemplates)) {
|
|
1766
|
+
throw new PipelineExecutionError('Parameter is not opened');
|
|
1767
|
+
}
|
|
1768
|
+
return replacedTemplates;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
/**
|
|
1772
|
+
* Counts number of characters in the text
|
|
1773
|
+
*
|
|
1774
|
+
* @public exported from `@promptbook/utils`
|
|
1775
|
+
*/
|
|
1776
|
+
function countCharacters(text) {
|
|
1777
|
+
// Remove null characters
|
|
1778
|
+
text = text.replace(/\0/g, '');
|
|
1779
|
+
// Replace emojis (and also ZWJ sequence) with hyphens
|
|
1780
|
+
text = text.replace(/(\p{Extended_Pictographic})\p{Modifier_Symbol}/gu, '$1');
|
|
1781
|
+
text = text.replace(/(\p{Extended_Pictographic})[\u{FE00}-\u{FE0F}]/gu, '$1');
|
|
1782
|
+
text = text.replace(/\p{Extended_Pictographic}(\u{200D}\p{Extended_Pictographic})*/gu, '-');
|
|
1783
|
+
return text.length;
|
|
1784
|
+
}
|
|
1785
|
+
/**
|
|
1786
|
+
* TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
|
|
1787
|
+
* TODO: [🧠][✌️] Make some Promptbook-native token system
|
|
1788
|
+
*/
|
|
1789
|
+
|
|
1790
|
+
/**
|
|
1791
|
+
* Number of characters per standard line with 11pt Arial font size.
|
|
1792
|
+
*
|
|
1793
|
+
* @public exported from `@promptbook/utils`
|
|
1794
|
+
*/
|
|
1795
|
+
const CHARACTERS_PER_STANDARD_LINE = 63;
|
|
1796
|
+
/**
|
|
1797
|
+
* Number of lines per standard A4 page with 11pt Arial font size and standard margins and spacing.
|
|
1798
|
+
*
|
|
1799
|
+
* @public exported from `@promptbook/utils`
|
|
1800
|
+
*/
|
|
1801
|
+
const LINES_PER_STANDARD_PAGE = 44;
|
|
1802
|
+
/**
|
|
1803
|
+
* TODO: [🧠] Should be this `constants.ts` or `config.ts`?
|
|
1804
|
+
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
1805
|
+
*/
|
|
1806
|
+
|
|
1807
|
+
/**
|
|
1808
|
+
* Counts number of lines in the text
|
|
1809
|
+
*
|
|
1810
|
+
* Note: This does not check only for the presence of newlines, but also for the length of the standard line.
|
|
1811
|
+
*
|
|
1812
|
+
* @public exported from `@promptbook/utils`
|
|
1813
|
+
*/
|
|
1814
|
+
function countLines(text) {
|
|
1815
|
+
if (text === '') {
|
|
1816
|
+
return 0;
|
|
1817
|
+
}
|
|
1818
|
+
text = text.replace('\r\n', '\n');
|
|
1819
|
+
text = text.replace('\r', '\n');
|
|
1820
|
+
const lines = text.split(/\r?\n/);
|
|
1821
|
+
return lines.reduce((count, line) => count + Math.max(Math.ceil(line.length / CHARACTERS_PER_STANDARD_LINE), 1), 0);
|
|
1822
|
+
}
|
|
1823
|
+
/**
|
|
1824
|
+
* TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
|
|
1825
|
+
* TODO: [🧠][✌️] Make some Promptbook-native token system
|
|
1826
|
+
*/
|
|
1827
|
+
|
|
1828
|
+
/**
|
|
1829
|
+
* Counts number of pages in the text
|
|
1830
|
+
*
|
|
1831
|
+
* Note: This does not check only for the count of newlines, but also for the length of the standard line and length of the standard page.
|
|
1832
|
+
*
|
|
1833
|
+
* @public exported from `@promptbook/utils`
|
|
1834
|
+
*/
|
|
1835
|
+
function countPages(text) {
|
|
1836
|
+
return Math.ceil(countLines(text) / LINES_PER_STANDARD_PAGE);
|
|
1837
|
+
}
|
|
1838
|
+
/**
|
|
1839
|
+
* TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
|
|
1840
|
+
* TODO: [🧠][✌️] Make some Promptbook-native token system
|
|
1841
|
+
*/
|
|
1842
|
+
|
|
1843
|
+
/**
|
|
1844
|
+
* Counts number of paragraphs in the text
|
|
1845
|
+
*
|
|
1846
|
+
* @public exported from `@promptbook/utils`
|
|
1847
|
+
*/
|
|
1848
|
+
function countParagraphs(text) {
|
|
1849
|
+
return text.split(/\n\s*\n/).filter((paragraph) => paragraph.trim() !== '').length;
|
|
1850
|
+
}
|
|
1851
|
+
/**
|
|
1852
|
+
* TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
|
|
1853
|
+
* TODO: [🧠][✌️] Make some Promptbook-native token system
|
|
1854
|
+
*/
|
|
1855
|
+
|
|
1856
|
+
/**
|
|
1857
|
+
* Split text into sentences
|
|
1858
|
+
*
|
|
1859
|
+
* @public exported from `@promptbook/utils`
|
|
1860
|
+
*/
|
|
1861
|
+
function splitIntoSentences(text) {
|
|
1862
|
+
return text.split(/[.!?]+/).filter((sentence) => sentence.trim() !== '');
|
|
1863
|
+
}
|
|
1864
|
+
/**
|
|
1865
|
+
* Counts number of sentences in the text
|
|
1866
|
+
*
|
|
1867
|
+
* @public exported from `@promptbook/utils`
|
|
1868
|
+
*/
|
|
1869
|
+
function countSentences(text) {
|
|
1870
|
+
return splitIntoSentences(text).length;
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1873
|
+
* TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
|
|
1874
|
+
* TODO: [🧠][✌️] Make some Promptbook-native token system
|
|
1875
|
+
*/
|
|
1876
|
+
|
|
1877
|
+
const defaultDiacriticsRemovalMap = [
|
|
1878
|
+
{
|
|
1879
|
+
base: 'A',
|
|
1880
|
+
letters: '\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F',
|
|
1881
|
+
},
|
|
1882
|
+
{ base: 'AA', letters: '\uA732' },
|
|
1883
|
+
{ base: 'AE', letters: '\u00C6\u01FC\u01E2' },
|
|
1884
|
+
{ base: 'AO', letters: '\uA734' },
|
|
1885
|
+
{ base: 'AU', letters: '\uA736' },
|
|
1886
|
+
{ base: 'AV', letters: '\uA738\uA73A' },
|
|
1887
|
+
{ base: 'AY', letters: '\uA73C' },
|
|
1888
|
+
{
|
|
1889
|
+
base: 'B',
|
|
1890
|
+
letters: '\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181',
|
|
1891
|
+
},
|
|
1892
|
+
{
|
|
1893
|
+
base: 'C',
|
|
1894
|
+
letters: '\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E',
|
|
1895
|
+
},
|
|
1896
|
+
{
|
|
1897
|
+
base: 'D',
|
|
1898
|
+
letters: '\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779\u00D0',
|
|
1899
|
+
},
|
|
1900
|
+
{ base: 'DZ', letters: '\u01F1\u01C4' },
|
|
1901
|
+
{ base: 'Dz', letters: '\u01F2\u01C5' },
|
|
1902
|
+
{
|
|
1903
|
+
base: 'E',
|
|
1904
|
+
letters: '\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E',
|
|
1549
1905
|
},
|
|
1550
1906
|
{ base: 'F', letters: '\u0046\u24BB\uFF26\u1E1E\u0191\uA77B' },
|
|
1551
1907
|
{
|
|
@@ -1782,921 +2138,78 @@ function removeDiacritics(input) {
|
|
|
1782
2138
|
*/
|
|
1783
2139
|
|
|
1784
2140
|
/**
|
|
1785
|
-
*
|
|
1786
|
-
*
|
|
1787
|
-
* For example, when the format value is not a valid JSON or CSV
|
|
1788
|
-
* This is not thrown directly but in extended classes
|
|
1789
|
-
*
|
|
1790
|
-
* @public exported from `@promptbook/core`
|
|
1791
|
-
*/
|
|
1792
|
-
class AbstractFormatError extends Error {
|
|
1793
|
-
// Note: To allow instanceof do not put here error `name`
|
|
1794
|
-
// public readonly name = 'AbstractFormatError';
|
|
1795
|
-
constructor(message) {
|
|
1796
|
-
super(message);
|
|
1797
|
-
Object.setPrototypeOf(this, AbstractFormatError.prototype);
|
|
1798
|
-
}
|
|
1799
|
-
}
|
|
1800
|
-
|
|
1801
|
-
/**
|
|
1802
|
-
* This error indicates problem with parsing of CSV
|
|
1803
|
-
*
|
|
1804
|
-
* @public exported from `@promptbook/core`
|
|
1805
|
-
*/
|
|
1806
|
-
class CsvFormatError extends AbstractFormatError {
|
|
1807
|
-
constructor(message) {
|
|
1808
|
-
super(message);
|
|
1809
|
-
this.name = 'CsvFormatError';
|
|
1810
|
-
Object.setPrototypeOf(this, CsvFormatError.prototype);
|
|
1811
|
-
}
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
/**
|
|
1815
|
-
* AuthenticationError is thrown from login function which is dependency of remote server
|
|
1816
|
-
*
|
|
1817
|
-
* @public exported from `@promptbook/core`
|
|
1818
|
-
*/
|
|
1819
|
-
class AuthenticationError extends Error {
|
|
1820
|
-
constructor(message) {
|
|
1821
|
-
super(message);
|
|
1822
|
-
this.name = 'AuthenticationError';
|
|
1823
|
-
Object.setPrototypeOf(this, AuthenticationError.prototype);
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1826
|
-
|
|
1827
|
-
/**
|
|
1828
|
-
* This error indicates that the pipeline collection cannot be properly loaded
|
|
1829
|
-
*
|
|
1830
|
-
* @public exported from `@promptbook/core`
|
|
1831
|
-
*/
|
|
1832
|
-
class CollectionError extends Error {
|
|
1833
|
-
constructor(message) {
|
|
1834
|
-
super(message);
|
|
1835
|
-
this.name = 'CollectionError';
|
|
1836
|
-
Object.setPrototypeOf(this, CollectionError.prototype);
|
|
1837
|
-
}
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
|
-
/**
|
|
1841
|
-
* This error indicates error from the database
|
|
1842
|
-
*
|
|
1843
|
-
* @public exported from `@promptbook/core`
|
|
1844
|
-
*/
|
|
1845
|
-
class DatabaseError extends Error {
|
|
1846
|
-
constructor(message) {
|
|
1847
|
-
super(message);
|
|
1848
|
-
this.name = 'DatabaseError';
|
|
1849
|
-
Object.setPrototypeOf(this, DatabaseError.prototype);
|
|
1850
|
-
}
|
|
1851
|
-
}
|
|
1852
|
-
/**
|
|
1853
|
-
* TODO: [🐱🚀] Explain that NotFoundError ([🐱🚀] and other specific errors) has priority over DatabaseError in some contexts
|
|
1854
|
-
*/
|
|
1855
|
-
|
|
1856
|
-
/**
|
|
1857
|
-
* This error type indicates that you try to use a feature that is not available in the current environment
|
|
1858
|
-
*
|
|
1859
|
-
* @public exported from `@promptbook/core`
|
|
1860
|
-
*/
|
|
1861
|
-
class EnvironmentMismatchError extends Error {
|
|
1862
|
-
constructor(message) {
|
|
1863
|
-
super(message);
|
|
1864
|
-
this.name = 'EnvironmentMismatchError';
|
|
1865
|
-
Object.setPrototypeOf(this, EnvironmentMismatchError.prototype);
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
|
|
1869
|
-
/**
|
|
1870
|
-
* This error occurs when some expectation is not met in the execution of the pipeline
|
|
2141
|
+
* Counts number of words in the text
|
|
1871
2142
|
*
|
|
1872
|
-
* @public exported from `@promptbook/
|
|
1873
|
-
* Note: Do not throw this error, its reserved for `checkExpectations` and `createPipelineExecutor` and public ONLY to be serializable through remote server
|
|
1874
|
-
* Note: Always thrown in `checkExpectations` and catched in `createPipelineExecutor` and rethrown as `PipelineExecutionError`
|
|
1875
|
-
* Note: This is a kindof subtype of PipelineExecutionError
|
|
2143
|
+
* @public exported from `@promptbook/utils`
|
|
1876
2144
|
*/
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
2145
|
+
function countWords(text) {
|
|
2146
|
+
text = text.replace(/[\p{Extended_Pictographic}]/gu, 'a');
|
|
2147
|
+
text = removeDiacritics(text);
|
|
2148
|
+
// Add spaces before uppercase letters preceded by lowercase letters (for camelCase)
|
|
2149
|
+
text = text.replace(/([a-z])([A-Z])/g, '$1 $2');
|
|
2150
|
+
return text.split(/[^a-zа-я0-9]+/i).filter((word) => word.length > 0).length;
|
|
1883
2151
|
}
|
|
1884
|
-
|
|
1885
2152
|
/**
|
|
1886
|
-
*
|
|
1887
|
-
*
|
|
1888
|
-
*
|
|
2153
|
+
* TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
|
|
2154
|
+
* TODO: [🧠][✌️] Make some Promptbook-native token system
|
|
2155
|
+
* TODO: [✌️] `countWords` should be just `splitWords(...).length`, and all other counters should use this pattern as well
|
|
1889
2156
|
*/
|
|
1890
|
-
class KnowledgeScrapeError extends Error {
|
|
1891
|
-
constructor(message) {
|
|
1892
|
-
super(message);
|
|
1893
|
-
this.name = 'KnowledgeScrapeError';
|
|
1894
|
-
Object.setPrototypeOf(this, KnowledgeScrapeError.prototype);
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
2157
|
|
|
1898
2158
|
/**
|
|
1899
|
-
*
|
|
2159
|
+
* Helper of usage compute
|
|
1900
2160
|
*
|
|
1901
|
-
* @
|
|
1902
|
-
|
|
1903
|
-
class LimitReachedError extends Error {
|
|
1904
|
-
constructor(message) {
|
|
1905
|
-
super(message);
|
|
1906
|
-
this.name = 'LimitReachedError';
|
|
1907
|
-
Object.setPrototypeOf(this, LimitReachedError.prototype);
|
|
1908
|
-
}
|
|
1909
|
-
}
|
|
1910
|
-
|
|
1911
|
-
/**
|
|
1912
|
-
* This error type indicates that some tools are missing for pipeline execution or preparation
|
|
2161
|
+
* @param content the content of prompt or response
|
|
2162
|
+
* @returns part of UsageCounts
|
|
1913
2163
|
*
|
|
1914
|
-
* @
|
|
2164
|
+
* @private internal utility of LlmExecutionTools
|
|
1915
2165
|
*/
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
Object.setPrototypeOf(this, MissingToolsError.prototype);
|
|
1926
|
-
}
|
|
2166
|
+
function computeUsageCounts(content) {
|
|
2167
|
+
return {
|
|
2168
|
+
charactersCount: { value: countCharacters(content) },
|
|
2169
|
+
wordsCount: { value: countWords(content) },
|
|
2170
|
+
sentencesCount: { value: countSentences(content) },
|
|
2171
|
+
linesCount: { value: countLines(content) },
|
|
2172
|
+
paragraphsCount: { value: countParagraphs(content) },
|
|
2173
|
+
pagesCount: { value: countPages(content) },
|
|
2174
|
+
};
|
|
1927
2175
|
}
|
|
1928
2176
|
|
|
1929
2177
|
/**
|
|
1930
|
-
*
|
|
2178
|
+
* Make UncertainNumber
|
|
1931
2179
|
*
|
|
1932
|
-
* @
|
|
1933
|
-
|
|
1934
|
-
class NotAllowed extends Error {
|
|
1935
|
-
constructor(message) {
|
|
1936
|
-
super(message);
|
|
1937
|
-
this.name = 'NotAllowed';
|
|
1938
|
-
Object.setPrototypeOf(this, NotAllowed.prototype);
|
|
1939
|
-
}
|
|
1940
|
-
}
|
|
1941
|
-
|
|
1942
|
-
/**
|
|
1943
|
-
* This error indicates that promptbook not found in the collection
|
|
2180
|
+
* @param value value of the uncertain number, if `NaN` or `undefined`, it will be set to 0 and `isUncertain=true`
|
|
2181
|
+
* @param isUncertain if `true`, the value is uncertain, otherwise depends on the value
|
|
1944
2182
|
*
|
|
1945
|
-
* @
|
|
2183
|
+
* @private utility for initializating UncertainNumber
|
|
1946
2184
|
*/
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
this.name = 'NotFoundError';
|
|
1951
|
-
Object.setPrototypeOf(this, NotFoundError.prototype);
|
|
2185
|
+
function uncertainNumber(value, isUncertain) {
|
|
2186
|
+
if (value === null || value === undefined || Number.isNaN(value)) {
|
|
2187
|
+
return UNCERTAIN_ZERO_VALUE;
|
|
1952
2188
|
}
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
/**
|
|
1956
|
-
* This error type indicates that some part of the code is not implemented yet
|
|
1957
|
-
*
|
|
1958
|
-
* @public exported from `@promptbook/core`
|
|
1959
|
-
*/
|
|
1960
|
-
class NotYetImplementedError extends Error {
|
|
1961
|
-
constructor(message) {
|
|
1962
|
-
super(spaceTrim$1((block) => `
|
|
1963
|
-
${block(message)}
|
|
1964
|
-
|
|
1965
|
-
Note: This feature is not implemented yet but it will be soon.
|
|
1966
|
-
|
|
1967
|
-
If you want speed up the implementation or just read more, look here:
|
|
1968
|
-
https://github.com/webgptorg/promptbook
|
|
1969
|
-
|
|
1970
|
-
Or contact us on pavol@ptbk.io
|
|
1971
|
-
|
|
1972
|
-
`));
|
|
1973
|
-
this.name = 'NotYetImplementedError';
|
|
1974
|
-
Object.setPrototypeOf(this, NotYetImplementedError.prototype);
|
|
2189
|
+
if (isUncertain === true) {
|
|
2190
|
+
return { value, isUncertain };
|
|
1975
2191
|
}
|
|
2192
|
+
return { value };
|
|
1976
2193
|
}
|
|
1977
2194
|
|
|
1978
2195
|
/**
|
|
1979
|
-
*
|
|
2196
|
+
* Create price per one token based on the string value found on openai page
|
|
1980
2197
|
*
|
|
1981
|
-
* @
|
|
2198
|
+
* @private within the repository, used only as internal helper for `OPENAI_MODELS`
|
|
1982
2199
|
*/
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
this.name = 'ParseError';
|
|
1987
|
-
Object.setPrototypeOf(this, ParseError.prototype);
|
|
1988
|
-
}
|
|
2200
|
+
function pricing(value) {
|
|
2201
|
+
const [price, tokens] = value.split(' / ');
|
|
2202
|
+
return parseFloat(price.replace('$', '')) / parseFloat(tokens.replace('M tokens', '')) / 1000000;
|
|
1989
2203
|
}
|
|
1990
|
-
/**
|
|
1991
|
-
* TODO: Maybe split `ParseError` and `ApplyError`
|
|
1992
|
-
*/
|
|
1993
2204
|
|
|
1994
2205
|
/**
|
|
1995
|
-
*
|
|
1996
|
-
*
|
|
1997
|
-
* Note: `$` is used to indicate that this function is not a pure function - it is not deterministic
|
|
1998
|
-
* Note: This function is cryptographically secure (it uses crypto.randomBytes internally)
|
|
2206
|
+
* List of available OpenAI models with pricing
|
|
1999
2207
|
*
|
|
2000
|
-
*
|
|
2001
|
-
* @returns secure random token
|
|
2002
|
-
*/
|
|
2003
|
-
function $randomToken(randomness) {
|
|
2004
|
-
return randomBytes(randomness).toString('hex');
|
|
2005
|
-
}
|
|
2006
|
-
/**
|
|
2007
|
-
* TODO: [🤶] Maybe export through `@promptbook/utils` or `@promptbook/random` package
|
|
2008
|
-
* TODO: Maybe use nanoid instead https://github.com/ai/nanoid
|
|
2009
|
-
*/
|
|
2010
|
-
|
|
2011
|
-
/**
|
|
2012
|
-
* This error indicates errors during the execution of the pipeline
|
|
2208
|
+
* Note: Synced with official API docs at 2025-11-19
|
|
2013
2209
|
*
|
|
2014
|
-
* @
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
constructor(message) {
|
|
2018
|
-
// Added id parameter
|
|
2019
|
-
super(message);
|
|
2020
|
-
this.name = 'PipelineExecutionError';
|
|
2021
|
-
// TODO: [🐙] DRY - Maybe $randomId
|
|
2022
|
-
this.id = `error-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid similar char conflicts */)}`;
|
|
2023
|
-
Object.setPrototypeOf(this, PipelineExecutionError.prototype);
|
|
2024
|
-
}
|
|
2025
|
-
}
|
|
2026
|
-
/**
|
|
2027
|
-
* TODO: [🧠][🌂] Add id to all errors
|
|
2028
|
-
*/
|
|
2029
|
-
|
|
2030
|
-
/**
|
|
2031
|
-
* This error indicates that the promptbook object has valid syntax (=can be parsed) but contains logical errors (like circular dependencies)
|
|
2032
|
-
*
|
|
2033
|
-
* @public exported from `@promptbook/core`
|
|
2034
|
-
*/
|
|
2035
|
-
class PipelineLogicError extends Error {
|
|
2036
|
-
constructor(message) {
|
|
2037
|
-
super(message);
|
|
2038
|
-
this.name = 'PipelineLogicError';
|
|
2039
|
-
Object.setPrototypeOf(this, PipelineLogicError.prototype);
|
|
2040
|
-
}
|
|
2041
|
-
}
|
|
2042
|
-
|
|
2043
|
-
/**
|
|
2044
|
-
* This error indicates errors in referencing promptbooks between each other
|
|
2045
|
-
*
|
|
2046
|
-
* @public exported from `@promptbook/core`
|
|
2047
|
-
*/
|
|
2048
|
-
class PipelineUrlError extends Error {
|
|
2049
|
-
constructor(message) {
|
|
2050
|
-
super(message);
|
|
2051
|
-
this.name = 'PipelineUrlError';
|
|
2052
|
-
Object.setPrototypeOf(this, PipelineUrlError.prototype);
|
|
2053
|
-
}
|
|
2054
|
-
}
|
|
2055
|
-
|
|
2056
|
-
/**
|
|
2057
|
-
* Error thrown when a fetch request fails
|
|
2058
|
-
*
|
|
2059
|
-
* @public exported from `@promptbook/core`
|
|
2060
|
-
*/
|
|
2061
|
-
class PromptbookFetchError extends Error {
|
|
2062
|
-
constructor(message) {
|
|
2063
|
-
super(message);
|
|
2064
|
-
this.name = 'PromptbookFetchError';
|
|
2065
|
-
Object.setPrototypeOf(this, PromptbookFetchError.prototype);
|
|
2066
|
-
}
|
|
2067
|
-
}
|
|
2068
|
-
|
|
2069
|
-
/**
|
|
2070
|
-
* Index of all custom errors
|
|
2071
|
-
*
|
|
2072
|
-
* @public exported from `@promptbook/core`
|
|
2073
|
-
*/
|
|
2074
|
-
const PROMPTBOOK_ERRORS = {
|
|
2075
|
-
AbstractFormatError,
|
|
2076
|
-
CsvFormatError,
|
|
2077
|
-
CollectionError,
|
|
2078
|
-
EnvironmentMismatchError,
|
|
2079
|
-
ExpectError,
|
|
2080
|
-
KnowledgeScrapeError,
|
|
2081
|
-
LimitReachedError,
|
|
2082
|
-
MissingToolsError,
|
|
2083
|
-
NotFoundError,
|
|
2084
|
-
NotYetImplementedError,
|
|
2085
|
-
ParseError,
|
|
2086
|
-
PipelineExecutionError,
|
|
2087
|
-
PipelineLogicError,
|
|
2088
|
-
PipelineUrlError,
|
|
2089
|
-
AuthenticationError,
|
|
2090
|
-
PromptbookFetchError,
|
|
2091
|
-
UnexpectedError,
|
|
2092
|
-
WrappedError,
|
|
2093
|
-
NotAllowed,
|
|
2094
|
-
DatabaseError,
|
|
2095
|
-
// TODO: [🪑]> VersionMismatchError,
|
|
2096
|
-
};
|
|
2097
|
-
/**
|
|
2098
|
-
* Index of all javascript errors
|
|
2099
|
-
*
|
|
2100
|
-
* @private for internal usage
|
|
2101
|
-
*/
|
|
2102
|
-
const COMMON_JAVASCRIPT_ERRORS = {
|
|
2103
|
-
Error,
|
|
2104
|
-
EvalError,
|
|
2105
|
-
RangeError,
|
|
2106
|
-
ReferenceError,
|
|
2107
|
-
SyntaxError,
|
|
2108
|
-
TypeError,
|
|
2109
|
-
URIError,
|
|
2110
|
-
AggregateError,
|
|
2111
|
-
/*
|
|
2112
|
-
Note: Not widely supported
|
|
2113
|
-
> InternalError,
|
|
2114
|
-
> ModuleError,
|
|
2115
|
-
> HeapError,
|
|
2116
|
-
> WebAssemblyCompileError,
|
|
2117
|
-
> WebAssemblyRuntimeError,
|
|
2118
|
-
*/
|
|
2119
|
-
};
|
|
2120
|
-
/**
|
|
2121
|
-
* Index of all errors
|
|
2122
|
-
*
|
|
2123
|
-
* @private for internal usage
|
|
2124
|
-
*/
|
|
2125
|
-
const ALL_ERRORS = {
|
|
2126
|
-
...PROMPTBOOK_ERRORS,
|
|
2127
|
-
...COMMON_JAVASCRIPT_ERRORS,
|
|
2128
|
-
};
|
|
2129
|
-
/**
|
|
2130
|
-
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
2131
|
-
*/
|
|
2132
|
-
|
|
2133
|
-
/**
|
|
2134
|
-
* Deserializes the error object
|
|
2135
|
-
*
|
|
2136
|
-
* @public exported from `@promptbook/utils`
|
|
2137
|
-
*/
|
|
2138
|
-
function deserializeError(error, isStackAddedToMessage = true) {
|
|
2139
|
-
const { name, stack, id } = error; // Added id
|
|
2140
|
-
let { message } = error;
|
|
2141
|
-
let ErrorClass = ALL_ERRORS[error.name];
|
|
2142
|
-
if (ErrorClass === undefined) {
|
|
2143
|
-
ErrorClass = Error;
|
|
2144
|
-
message = `${name}: ${message}`;
|
|
2145
|
-
}
|
|
2146
|
-
if (isStackAddedToMessage && stack !== undefined && stack !== '') {
|
|
2147
|
-
message = spaceTrim$2((block) => `
|
|
2148
|
-
${block(message)}
|
|
2149
|
-
|
|
2150
|
-
Original stack trace:
|
|
2151
|
-
${block(stack || '')}
|
|
2152
|
-
`);
|
|
2153
|
-
}
|
|
2154
|
-
const deserializedError = new ErrorClass(message);
|
|
2155
|
-
deserializedError.id = id; // Assign id to the error object
|
|
2156
|
-
return deserializedError;
|
|
2157
|
-
}
|
|
2158
|
-
|
|
2159
|
-
/**
|
|
2160
|
-
* Serializes an error into a [🚉] JSON-serializable object
|
|
2161
|
-
*
|
|
2162
|
-
* @public exported from `@promptbook/utils`
|
|
2163
|
-
*/
|
|
2164
|
-
function serializeError(error) {
|
|
2165
|
-
const { name, message, stack } = error;
|
|
2166
|
-
const { id } = error;
|
|
2167
|
-
if (!Object.keys(ALL_ERRORS).includes(name)) {
|
|
2168
|
-
console.error(spaceTrim$2((block) => `
|
|
2169
|
-
|
|
2170
|
-
Cannot serialize error with name "${name}"
|
|
2171
|
-
|
|
2172
|
-
Authors of Promptbook probably forgot to add this error into the list of errors:
|
|
2173
|
-
https://github.com/webgptorg/promptbook/blob/main/src/errors/0-index.ts
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
${block(stack || message)}
|
|
2177
|
-
|
|
2178
|
-
`));
|
|
2179
|
-
}
|
|
2180
|
-
return {
|
|
2181
|
-
name: name,
|
|
2182
|
-
message,
|
|
2183
|
-
stack,
|
|
2184
|
-
id, // Include id in the serialized object
|
|
2185
|
-
};
|
|
2186
|
-
}
|
|
2187
|
-
|
|
2188
|
-
/**
|
|
2189
|
-
* Async version of Array.forEach
|
|
2190
|
-
*
|
|
2191
|
-
* @param array - Array to iterate over
|
|
2192
|
-
* @param options - Options for the function
|
|
2193
|
-
* @param callbackfunction - Function to call for each item
|
|
2194
|
-
* @public exported from `@promptbook/utils`
|
|
2195
|
-
* @deprecated [🪂] Use queues instead
|
|
2196
|
-
*/
|
|
2197
|
-
async function forEachAsync(array, options, callbackfunction) {
|
|
2198
|
-
const { maxParallelCount = Infinity } = options;
|
|
2199
|
-
let index = 0;
|
|
2200
|
-
let runningTasks = [];
|
|
2201
|
-
const tasks = [];
|
|
2202
|
-
for (const item of array) {
|
|
2203
|
-
const currentIndex = index++;
|
|
2204
|
-
const task = callbackfunction(item, currentIndex, array);
|
|
2205
|
-
tasks.push(task);
|
|
2206
|
-
runningTasks.push(task);
|
|
2207
|
-
/* not await */ Promise.resolve(task).then(() => {
|
|
2208
|
-
runningTasks = runningTasks.filter((runningTask) => runningTask !== task);
|
|
2209
|
-
});
|
|
2210
|
-
if (maxParallelCount < runningTasks.length) {
|
|
2211
|
-
await Promise.race(runningTasks);
|
|
2212
|
-
}
|
|
2213
|
-
}
|
|
2214
|
-
await Promise.all(tasks);
|
|
2215
|
-
}
|
|
2216
|
-
|
|
2217
|
-
/**
|
|
2218
|
-
* Format either small or big number
|
|
2219
|
-
*
|
|
2220
|
-
* @public exported from `@promptbook/utils`
|
|
2221
|
-
*/
|
|
2222
|
-
function numberToString(value) {
|
|
2223
|
-
if (value === 0) {
|
|
2224
|
-
return '0';
|
|
2225
|
-
}
|
|
2226
|
-
else if (Number.isNaN(value)) {
|
|
2227
|
-
return VALUE_STRINGS.nan;
|
|
2228
|
-
}
|
|
2229
|
-
else if (value === Infinity) {
|
|
2230
|
-
return VALUE_STRINGS.infinity;
|
|
2231
|
-
}
|
|
2232
|
-
else if (value === -Infinity) {
|
|
2233
|
-
return VALUE_STRINGS.negativeInfinity;
|
|
2234
|
-
}
|
|
2235
|
-
for (let exponent = 0; exponent < 15; exponent++) {
|
|
2236
|
-
const factor = 10 ** exponent;
|
|
2237
|
-
const valueRounded = Math.round(value * factor) / factor;
|
|
2238
|
-
if (Math.abs(value - valueRounded) / value < SMALL_NUMBER) {
|
|
2239
|
-
return valueRounded.toFixed(exponent);
|
|
2240
|
-
}
|
|
2241
|
-
}
|
|
2242
|
-
return value.toString();
|
|
2243
|
-
}
|
|
2244
|
-
|
|
2245
|
-
/**
|
|
2246
|
-
* Function `valueToString` will convert the given value to string
|
|
2247
|
-
* This is useful and used in the `templateParameters` function
|
|
2248
|
-
*
|
|
2249
|
-
* Note: This function is not just calling `toString` method
|
|
2250
|
-
* It's more complex and can handle this conversion specifically for LLM models
|
|
2251
|
-
* See `VALUE_STRINGS`
|
|
2252
|
-
*
|
|
2253
|
-
* Note: There are 2 similar functions
|
|
2254
|
-
* - `valueToString` converts value to string for LLM models as human-readable string
|
|
2255
|
-
* - `asSerializable` converts value to string to preserve full information to be able to convert it back
|
|
2256
|
-
*
|
|
2257
|
-
* @public exported from `@promptbook/utils`
|
|
2258
|
-
*/
|
|
2259
|
-
function valueToString(value) {
|
|
2260
|
-
try {
|
|
2261
|
-
if (value === '') {
|
|
2262
|
-
return VALUE_STRINGS.empty;
|
|
2263
|
-
}
|
|
2264
|
-
else if (value === null) {
|
|
2265
|
-
return VALUE_STRINGS.null;
|
|
2266
|
-
}
|
|
2267
|
-
else if (value === undefined) {
|
|
2268
|
-
return VALUE_STRINGS.undefined;
|
|
2269
|
-
}
|
|
2270
|
-
else if (typeof value === 'string') {
|
|
2271
|
-
return value;
|
|
2272
|
-
}
|
|
2273
|
-
else if (typeof value === 'number') {
|
|
2274
|
-
return numberToString(value);
|
|
2275
|
-
}
|
|
2276
|
-
else if (value instanceof Date) {
|
|
2277
|
-
return value.toISOString();
|
|
2278
|
-
}
|
|
2279
|
-
else {
|
|
2280
|
-
try {
|
|
2281
|
-
return JSON.stringify(value);
|
|
2282
|
-
}
|
|
2283
|
-
catch (error) {
|
|
2284
|
-
if (error instanceof TypeError && error.message.includes('circular structure')) {
|
|
2285
|
-
return VALUE_STRINGS.circular;
|
|
2286
|
-
}
|
|
2287
|
-
throw error;
|
|
2288
|
-
}
|
|
2289
|
-
}
|
|
2290
|
-
}
|
|
2291
|
-
catch (error) {
|
|
2292
|
-
assertsError(error);
|
|
2293
|
-
console.error(error);
|
|
2294
|
-
return VALUE_STRINGS.unserializable;
|
|
2295
|
-
}
|
|
2296
|
-
}
|
|
2297
|
-
|
|
2298
|
-
/**
|
|
2299
|
-
* Replaces parameters in template with values from parameters object
|
|
2300
|
-
*
|
|
2301
|
-
* Note: This function is not places strings into string,
|
|
2302
|
-
* It's more complex and can handle this operation specifically for LLM models
|
|
2303
|
-
*
|
|
2304
|
-
* @param template the template with parameters in {curly} braces
|
|
2305
|
-
* @param parameters the object with parameters
|
|
2306
|
-
* @returns the template with replaced parameters
|
|
2307
|
-
* @throws {PipelineExecutionError} if parameter is not defined, not closed, or not opened
|
|
2308
|
-
* @public exported from `@promptbook/utils`
|
|
2309
|
-
*/
|
|
2310
|
-
function templateParameters(template, parameters) {
|
|
2311
|
-
for (const [parameterName, parameterValue] of Object.entries(parameters)) {
|
|
2312
|
-
if (parameterValue === RESERVED_PARAMETER_MISSING_VALUE) {
|
|
2313
|
-
throw new UnexpectedError(`Parameter \`{${parameterName}}\` has missing value`);
|
|
2314
|
-
}
|
|
2315
|
-
else if (parameterValue === RESERVED_PARAMETER_RESTRICTED) {
|
|
2316
|
-
// TODO: [🍵]
|
|
2317
|
-
throw new UnexpectedError(`Parameter \`{${parameterName}}\` is restricted to use`);
|
|
2318
|
-
}
|
|
2319
|
-
}
|
|
2320
|
-
let replacedTemplates = template;
|
|
2321
|
-
let match;
|
|
2322
|
-
let loopLimit = LOOP_LIMIT;
|
|
2323
|
-
while ((match = /^(?<precol>.*){(?<parameterName>\w+)}(.*)/m /* <- Not global */
|
|
2324
|
-
.exec(replacedTemplates))) {
|
|
2325
|
-
if (loopLimit-- < 0) {
|
|
2326
|
-
throw new LimitReachedError('Loop limit reached during parameters replacement in `templateParameters`');
|
|
2327
|
-
}
|
|
2328
|
-
const precol = match.groups.precol;
|
|
2329
|
-
const parameterName = match.groups.parameterName;
|
|
2330
|
-
if (parameterName === '') {
|
|
2331
|
-
// Note: Skip empty placeholders. It's used to avoid confusion with JSON-like strings
|
|
2332
|
-
continue;
|
|
2333
|
-
}
|
|
2334
|
-
if (parameterName.indexOf('{') !== -1 || parameterName.indexOf('}') !== -1) {
|
|
2335
|
-
throw new PipelineExecutionError('Parameter is already opened or not closed');
|
|
2336
|
-
}
|
|
2337
|
-
if (parameters[parameterName] === undefined) {
|
|
2338
|
-
throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
|
|
2339
|
-
}
|
|
2340
|
-
let parameterValue = parameters[parameterName];
|
|
2341
|
-
if (parameterValue === undefined) {
|
|
2342
|
-
throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
|
|
2343
|
-
}
|
|
2344
|
-
parameterValue = valueToString(parameterValue);
|
|
2345
|
-
// Escape curly braces in parameter values to prevent prompt-injection
|
|
2346
|
-
parameterValue = parameterValue.replace(/[{}]/g, '\\$&');
|
|
2347
|
-
if (parameterValue.includes('\n') && /^\s*\W{0,3}\s*$/.test(precol)) {
|
|
2348
|
-
parameterValue = parameterValue
|
|
2349
|
-
.split('\n')
|
|
2350
|
-
.map((line, index) => (index === 0 ? line : `${precol}${line}`))
|
|
2351
|
-
.join('\n');
|
|
2352
|
-
}
|
|
2353
|
-
replacedTemplates =
|
|
2354
|
-
replacedTemplates.substring(0, match.index + precol.length) +
|
|
2355
|
-
parameterValue +
|
|
2356
|
-
replacedTemplates.substring(match.index + precol.length + parameterName.length + 2);
|
|
2357
|
-
}
|
|
2358
|
-
// [💫] Check if there are parameters that are not closed properly
|
|
2359
|
-
if (/{\w+$/.test(replacedTemplates)) {
|
|
2360
|
-
throw new PipelineExecutionError('Parameter is not closed');
|
|
2361
|
-
}
|
|
2362
|
-
// [💫] Check if there are parameters that are not opened properly
|
|
2363
|
-
if (/^\w+}/.test(replacedTemplates)) {
|
|
2364
|
-
throw new PipelineExecutionError('Parameter is not opened');
|
|
2365
|
-
}
|
|
2366
|
-
return replacedTemplates;
|
|
2367
|
-
}
|
|
2368
|
-
|
|
2369
|
-
/**
|
|
2370
|
-
* Number of characters per standard line with 11pt Arial font size.
|
|
2371
|
-
*
|
|
2372
|
-
* @public exported from `@promptbook/utils`
|
|
2373
|
-
*/
|
|
2374
|
-
const CHARACTERS_PER_STANDARD_LINE = 63;
|
|
2375
|
-
/**
|
|
2376
|
-
* Number of lines per standard A4 page with 11pt Arial font size and standard margins and spacing.
|
|
2377
|
-
*
|
|
2378
|
-
* @public exported from `@promptbook/utils`
|
|
2379
|
-
*/
|
|
2380
|
-
const LINES_PER_STANDARD_PAGE = 44;
|
|
2381
|
-
/**
|
|
2382
|
-
* TODO: [🧠] Should be this `constants.ts` or `config.ts`?
|
|
2383
|
-
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
2384
|
-
*/
|
|
2385
|
-
|
|
2386
|
-
/**
|
|
2387
|
-
* Counts number of characters in the text
|
|
2388
|
-
*
|
|
2389
|
-
* @public exported from `@promptbook/utils`
|
|
2390
|
-
*/
|
|
2391
|
-
function countCharacters(text) {
|
|
2392
|
-
// Remove null characters
|
|
2393
|
-
text = text.replace(/\0/g, '');
|
|
2394
|
-
// Replace emojis (and also ZWJ sequence) with hyphens
|
|
2395
|
-
text = text.replace(/(\p{Extended_Pictographic})\p{Modifier_Symbol}/gu, '$1');
|
|
2396
|
-
text = text.replace(/(\p{Extended_Pictographic})[\u{FE00}-\u{FE0F}]/gu, '$1');
|
|
2397
|
-
text = text.replace(/\p{Extended_Pictographic}(\u{200D}\p{Extended_Pictographic})*/gu, '-');
|
|
2398
|
-
return text.length;
|
|
2399
|
-
}
|
|
2400
|
-
/**
|
|
2401
|
-
* TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
|
|
2402
|
-
* TODO: [🧠][✌️] Make some Promptbook-native token system
|
|
2403
|
-
*/
|
|
2404
|
-
|
|
2405
|
-
/**
|
|
2406
|
-
* Counts number of lines in the text
|
|
2407
|
-
*
|
|
2408
|
-
* Note: This does not check only for the presence of newlines, but also for the length of the standard line.
|
|
2409
|
-
*
|
|
2410
|
-
* @public exported from `@promptbook/utils`
|
|
2411
|
-
*/
|
|
2412
|
-
function countLines(text) {
|
|
2413
|
-
if (text === '') {
|
|
2414
|
-
return 0;
|
|
2415
|
-
}
|
|
2416
|
-
text = text.replace('\r\n', '\n');
|
|
2417
|
-
text = text.replace('\r', '\n');
|
|
2418
|
-
const lines = text.split('\n');
|
|
2419
|
-
return lines.reduce((count, line) => count + Math.max(Math.ceil(line.length / CHARACTERS_PER_STANDARD_LINE), 1), 0);
|
|
2420
|
-
}
|
|
2421
|
-
/**
|
|
2422
|
-
* TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
|
|
2423
|
-
* TODO: [🧠][✌️] Make some Promptbook-native token system
|
|
2424
|
-
*/
|
|
2425
|
-
|
|
2426
|
-
/**
|
|
2427
|
-
* Counts number of pages in the text
|
|
2428
|
-
*
|
|
2429
|
-
* Note: This does not check only for the count of newlines, but also for the length of the standard line and length of the standard page.
|
|
2430
|
-
*
|
|
2431
|
-
* @public exported from `@promptbook/utils`
|
|
2432
|
-
*/
|
|
2433
|
-
function countPages(text) {
|
|
2434
|
-
return Math.ceil(countLines(text) / LINES_PER_STANDARD_PAGE);
|
|
2435
|
-
}
|
|
2436
|
-
/**
|
|
2437
|
-
* TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
|
|
2438
|
-
* TODO: [🧠][✌️] Make some Promptbook-native token system
|
|
2439
|
-
*/
|
|
2440
|
-
|
|
2441
|
-
/**
|
|
2442
|
-
* Counts number of paragraphs in the text
|
|
2443
|
-
*
|
|
2444
|
-
* @public exported from `@promptbook/utils`
|
|
2445
|
-
*/
|
|
2446
|
-
function countParagraphs(text) {
|
|
2447
|
-
return text.split(/\n\s*\n/).filter((paragraph) => paragraph.trim() !== '').length;
|
|
2448
|
-
}
|
|
2449
|
-
/**
|
|
2450
|
-
* TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
|
|
2451
|
-
* TODO: [🧠][✌️] Make some Promptbook-native token system
|
|
2452
|
-
*/
|
|
2453
|
-
|
|
2454
|
-
/**
|
|
2455
|
-
* Split text into sentences
|
|
2456
|
-
*
|
|
2457
|
-
* @public exported from `@promptbook/utils`
|
|
2458
|
-
*/
|
|
2459
|
-
function splitIntoSentences(text) {
|
|
2460
|
-
return text.split(/[.!?]+/).filter((sentence) => sentence.trim() !== '');
|
|
2461
|
-
}
|
|
2462
|
-
/**
|
|
2463
|
-
* Counts number of sentences in the text
|
|
2464
|
-
*
|
|
2465
|
-
* @public exported from `@promptbook/utils`
|
|
2466
|
-
*/
|
|
2467
|
-
function countSentences(text) {
|
|
2468
|
-
return splitIntoSentences(text).length;
|
|
2469
|
-
}
|
|
2470
|
-
/**
|
|
2471
|
-
* TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
|
|
2472
|
-
* TODO: [🧠][✌️] Make some Promptbook-native token system
|
|
2473
|
-
*/
|
|
2474
|
-
|
|
2475
|
-
/**
|
|
2476
|
-
* Counts number of words in the text
|
|
2477
|
-
*
|
|
2478
|
-
* @public exported from `@promptbook/utils`
|
|
2479
|
-
*/
|
|
2480
|
-
function countWords(text) {
|
|
2481
|
-
text = text.replace(/[\p{Extended_Pictographic}]/gu, 'a');
|
|
2482
|
-
text = removeDiacritics(text);
|
|
2483
|
-
// Add spaces before uppercase letters preceded by lowercase letters (for camelCase)
|
|
2484
|
-
text = text.replace(/([a-z])([A-Z])/g, '$1 $2');
|
|
2485
|
-
return text.split(/[^a-zа-я0-9]+/i).filter((word) => word.length > 0).length;
|
|
2486
|
-
}
|
|
2487
|
-
/**
|
|
2488
|
-
* TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
|
|
2489
|
-
* TODO: [🧠][✌️] Make some Promptbook-native token system
|
|
2490
|
-
* TODO: [✌️] `countWords` should be just `splitWords(...).length`, and all other counters should use this pattern as well
|
|
2491
|
-
*/
|
|
2492
|
-
|
|
2493
|
-
/**
|
|
2494
|
-
* Simple wrapper `new Date().toISOString()`
|
|
2495
|
-
*
|
|
2496
|
-
* Note: `$` is used to indicate that this function is not a pure function - it is not deterministic because it depends on the current time
|
|
2497
|
-
*
|
|
2498
|
-
* @returns string_date branded type
|
|
2499
|
-
* @public exported from `@promptbook/utils`
|
|
2500
|
-
*/
|
|
2501
|
-
function $getCurrentDate() {
|
|
2502
|
-
return new Date().toISOString();
|
|
2503
|
-
}
|
|
2504
|
-
|
|
2505
|
-
/**
|
|
2506
|
-
* Normalizes a text string to SCREAMING_CASE (all uppercase with underscores).
|
|
2507
|
-
*
|
|
2508
|
-
* Note: [🔂] This function is idempotent.
|
|
2509
|
-
*
|
|
2510
|
-
* @param text The text string to be converted to SCREAMING_CASE format.
|
|
2511
|
-
* @returns The normalized text in SCREAMING_CASE format.
|
|
2512
|
-
* @example 'HELLO_WORLD'
|
|
2513
|
-
* @example 'I_LOVE_PROMPTBOOK'
|
|
2514
|
-
* @public exported from `@promptbook/utils`
|
|
2515
|
-
*/
|
|
2516
|
-
function normalizeTo_SCREAMING_CASE(text) {
|
|
2517
|
-
let charType;
|
|
2518
|
-
let lastCharType = 'OTHER';
|
|
2519
|
-
let normalizedName = '';
|
|
2520
|
-
for (const char of text) {
|
|
2521
|
-
let normalizedChar;
|
|
2522
|
-
if (/^[a-z]$/.test(char)) {
|
|
2523
|
-
charType = 'LOWERCASE';
|
|
2524
|
-
normalizedChar = char.toUpperCase();
|
|
2525
|
-
}
|
|
2526
|
-
else if (/^[A-Z]$/.test(char)) {
|
|
2527
|
-
charType = 'UPPERCASE';
|
|
2528
|
-
normalizedChar = char;
|
|
2529
|
-
}
|
|
2530
|
-
else if (/^[0-9]$/.test(char)) {
|
|
2531
|
-
charType = 'NUMBER';
|
|
2532
|
-
normalizedChar = char;
|
|
2533
|
-
}
|
|
2534
|
-
else {
|
|
2535
|
-
charType = 'OTHER';
|
|
2536
|
-
normalizedChar = '_';
|
|
2537
|
-
}
|
|
2538
|
-
if (charType !== lastCharType &&
|
|
2539
|
-
!(lastCharType === 'UPPERCASE' && charType === 'LOWERCASE') &&
|
|
2540
|
-
!(lastCharType === 'NUMBER') &&
|
|
2541
|
-
!(charType === 'NUMBER')) {
|
|
2542
|
-
normalizedName += '_';
|
|
2543
|
-
}
|
|
2544
|
-
normalizedName += normalizedChar;
|
|
2545
|
-
lastCharType = charType;
|
|
2546
|
-
}
|
|
2547
|
-
normalizedName = normalizedName.replace(/_+/g, '_');
|
|
2548
|
-
normalizedName = normalizedName.replace(/_?\/_?/g, '/');
|
|
2549
|
-
normalizedName = normalizedName.replace(/^_/, '');
|
|
2550
|
-
normalizedName = normalizedName.replace(/_$/, '');
|
|
2551
|
-
return normalizedName;
|
|
2552
|
-
}
|
|
2553
|
-
/**
|
|
2554
|
-
* TODO: Tests
|
|
2555
|
-
* > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'Moje tabule' })).toEqual('/VtG7sR9rRJqwNEdM2/Moje tabule');
|
|
2556
|
-
* > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'ěščřžžýáíúů' })).toEqual('/VtG7sR9rRJqwNEdM2/escrzyaieuu');
|
|
2557
|
-
* > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj');
|
|
2558
|
-
* > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj_ahojAhoj ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj-ahoj-ahoj-ahoj');
|
|
2559
|
-
* TODO: [🌺] Use some intermediate util splitWords
|
|
2560
|
-
*/
|
|
2561
|
-
|
|
2562
|
-
/**
|
|
2563
|
-
* Normalizes a text string to snake_case format.
|
|
2564
|
-
*
|
|
2565
|
-
* Note: [🔂] This function is idempotent.
|
|
2566
|
-
*
|
|
2567
|
-
* @param text The text string to be converted to snake_case format.
|
|
2568
|
-
* @returns The normalized text in snake_case format.
|
|
2569
|
-
* @example 'hello_world'
|
|
2570
|
-
* @example 'i_love_promptbook'
|
|
2571
|
-
* @public exported from `@promptbook/utils`
|
|
2572
|
-
*/
|
|
2573
|
-
function normalizeTo_snake_case(text) {
|
|
2574
|
-
return normalizeTo_SCREAMING_CASE(text).toLowerCase();
|
|
2575
|
-
}
|
|
2576
|
-
|
|
2577
|
-
/**
|
|
2578
|
-
* Represents the uncertain value
|
|
2579
|
-
*
|
|
2580
|
-
* @public exported from `@promptbook/core`
|
|
2581
|
-
*/
|
|
2582
|
-
const ZERO_VALUE = $deepFreeze({ value: 0 });
|
|
2583
|
-
/**
|
|
2584
|
-
* Represents the uncertain value
|
|
2585
|
-
*
|
|
2586
|
-
* @public exported from `@promptbook/core`
|
|
2587
|
-
*/
|
|
2588
|
-
const UNCERTAIN_ZERO_VALUE = $deepFreeze({ value: 0, isUncertain: true });
|
|
2589
|
-
/**
|
|
2590
|
-
* Represents the usage with no resources consumed
|
|
2591
|
-
*
|
|
2592
|
-
* @public exported from `@promptbook/core`
|
|
2593
|
-
*/
|
|
2594
|
-
const ZERO_USAGE = $deepFreeze({
|
|
2595
|
-
price: ZERO_VALUE,
|
|
2596
|
-
input: {
|
|
2597
|
-
tokensCount: ZERO_VALUE,
|
|
2598
|
-
charactersCount: ZERO_VALUE,
|
|
2599
|
-
wordsCount: ZERO_VALUE,
|
|
2600
|
-
sentencesCount: ZERO_VALUE,
|
|
2601
|
-
linesCount: ZERO_VALUE,
|
|
2602
|
-
paragraphsCount: ZERO_VALUE,
|
|
2603
|
-
pagesCount: ZERO_VALUE,
|
|
2604
|
-
},
|
|
2605
|
-
output: {
|
|
2606
|
-
tokensCount: ZERO_VALUE,
|
|
2607
|
-
charactersCount: ZERO_VALUE,
|
|
2608
|
-
wordsCount: ZERO_VALUE,
|
|
2609
|
-
sentencesCount: ZERO_VALUE,
|
|
2610
|
-
linesCount: ZERO_VALUE,
|
|
2611
|
-
paragraphsCount: ZERO_VALUE,
|
|
2612
|
-
pagesCount: ZERO_VALUE,
|
|
2613
|
-
},
|
|
2614
|
-
});
|
|
2615
|
-
/**
|
|
2616
|
-
* Represents the usage with unknown resources consumed
|
|
2617
|
-
*
|
|
2618
|
-
* @public exported from `@promptbook/core`
|
|
2619
|
-
*/
|
|
2620
|
-
const UNCERTAIN_USAGE = $deepFreeze({
|
|
2621
|
-
price: UNCERTAIN_ZERO_VALUE,
|
|
2622
|
-
input: {
|
|
2623
|
-
tokensCount: UNCERTAIN_ZERO_VALUE,
|
|
2624
|
-
charactersCount: UNCERTAIN_ZERO_VALUE,
|
|
2625
|
-
wordsCount: UNCERTAIN_ZERO_VALUE,
|
|
2626
|
-
sentencesCount: UNCERTAIN_ZERO_VALUE,
|
|
2627
|
-
linesCount: UNCERTAIN_ZERO_VALUE,
|
|
2628
|
-
paragraphsCount: UNCERTAIN_ZERO_VALUE,
|
|
2629
|
-
pagesCount: UNCERTAIN_ZERO_VALUE,
|
|
2630
|
-
},
|
|
2631
|
-
output: {
|
|
2632
|
-
tokensCount: UNCERTAIN_ZERO_VALUE,
|
|
2633
|
-
charactersCount: UNCERTAIN_ZERO_VALUE,
|
|
2634
|
-
wordsCount: UNCERTAIN_ZERO_VALUE,
|
|
2635
|
-
sentencesCount: UNCERTAIN_ZERO_VALUE,
|
|
2636
|
-
linesCount: UNCERTAIN_ZERO_VALUE,
|
|
2637
|
-
paragraphsCount: UNCERTAIN_ZERO_VALUE,
|
|
2638
|
-
pagesCount: UNCERTAIN_ZERO_VALUE,
|
|
2639
|
-
},
|
|
2640
|
-
});
|
|
2641
|
-
/**
|
|
2642
|
-
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
2643
|
-
*/
|
|
2644
|
-
|
|
2645
|
-
/**
|
|
2646
|
-
* Helper of usage compute
|
|
2647
|
-
*
|
|
2648
|
-
* @param content the content of prompt or response
|
|
2649
|
-
* @returns part of UsageCounts
|
|
2650
|
-
*
|
|
2651
|
-
* @private internal utility of LlmExecutionTools
|
|
2652
|
-
*/
|
|
2653
|
-
function computeUsageCounts(content) {
|
|
2654
|
-
return {
|
|
2655
|
-
charactersCount: { value: countCharacters(content) },
|
|
2656
|
-
wordsCount: { value: countWords(content) },
|
|
2657
|
-
sentencesCount: { value: countSentences(content) },
|
|
2658
|
-
linesCount: { value: countLines(content) },
|
|
2659
|
-
paragraphsCount: { value: countParagraphs(content) },
|
|
2660
|
-
pagesCount: { value: countPages(content) },
|
|
2661
|
-
};
|
|
2662
|
-
}
|
|
2663
|
-
|
|
2664
|
-
/**
|
|
2665
|
-
* Make UncertainNumber
|
|
2666
|
-
*
|
|
2667
|
-
* @param value value of the uncertain number, if `NaN` or `undefined`, it will be set to 0 and `isUncertain=true`
|
|
2668
|
-
* @param isUncertain if `true`, the value is uncertain, otherwise depends on the value
|
|
2669
|
-
*
|
|
2670
|
-
* @private utility for initializating UncertainNumber
|
|
2671
|
-
*/
|
|
2672
|
-
function uncertainNumber(value, isUncertain) {
|
|
2673
|
-
if (value === null || value === undefined || Number.isNaN(value)) {
|
|
2674
|
-
return UNCERTAIN_ZERO_VALUE;
|
|
2675
|
-
}
|
|
2676
|
-
if (isUncertain === true) {
|
|
2677
|
-
return { value, isUncertain };
|
|
2678
|
-
}
|
|
2679
|
-
return { value };
|
|
2680
|
-
}
|
|
2681
|
-
|
|
2682
|
-
/**
|
|
2683
|
-
* Create price per one token based on the string value found on openai page
|
|
2684
|
-
*
|
|
2685
|
-
* @private within the repository, used only as internal helper for `OPENAI_MODELS`
|
|
2686
|
-
*/
|
|
2687
|
-
function pricing(value) {
|
|
2688
|
-
const [price, tokens] = value.split(' / ');
|
|
2689
|
-
return parseFloat(price.replace('$', '')) / parseFloat(tokens.replace('M tokens', '')) / 1000000;
|
|
2690
|
-
}
|
|
2691
|
-
|
|
2692
|
-
/**
|
|
2693
|
-
* List of available OpenAI models with pricing
|
|
2694
|
-
*
|
|
2695
|
-
* Note: Synced with official API docs at 2025-11-19
|
|
2696
|
-
*
|
|
2697
|
-
* @see https://platform.openai.com/docs/models/
|
|
2698
|
-
* @see https://openai.com/api/pricing/
|
|
2699
|
-
* @public exported from `@promptbook/openai`
|
|
2210
|
+
* @see https://platform.openai.com/docs/models/
|
|
2211
|
+
* @see https://openai.com/api/pricing/
|
|
2212
|
+
* @public exported from `@promptbook/openai`
|
|
2700
2213
|
*/
|
|
2701
2214
|
const OPENAI_MODELS = exportJson({
|
|
2702
2215
|
name: 'OPENAI_MODELS',
|
|
@@ -3290,71 +2803,558 @@ const OPENAI_MODELS = exportJson({
|
|
|
3290
2803
|
],
|
|
3291
2804
|
});
|
|
3292
2805
|
/**
|
|
3293
|
-
* Note: [🤖] Add models of new variant
|
|
3294
|
-
* TODO: [🧠] Some mechanism to propagate unsureness
|
|
3295
|
-
* TODO: [🎰] Some mechanism to auto-update available models
|
|
3296
|
-
* TODO: [🎰][👮♀️] Make this list dynamic - dynamically can be listed modelNames but not modelVariant, legacy status, context length and pricing
|
|
3297
|
-
* TODO: [🧠][👮♀️] Put here more info like description, isVision, trainingDateCutoff, languages, strengths ( Top-level performance, intelligence, fluency, and understanding), contextWindow,...
|
|
3298
|
-
* @see https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4
|
|
3299
|
-
* @see https://openai.com/api/pricing/
|
|
3300
|
-
* @see /other/playground/playground.ts
|
|
3301
|
-
* TODO: [🍓][💩] Make better
|
|
3302
|
-
* TODO: Change model titles to human eg: "gpt-4-turbo-2024-04-09" -> "GPT-4 Turbo (2024-04-09)"
|
|
3303
|
-
* TODO: [🚸] Not all models are compatible with JSON mode, add this information here and use it
|
|
3304
|
-
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
2806
|
+
* Note: [🤖] Add models of new variant
|
|
2807
|
+
* TODO: [🧠] Some mechanism to propagate unsureness
|
|
2808
|
+
* TODO: [🎰] Some mechanism to auto-update available models
|
|
2809
|
+
* TODO: [🎰][👮♀️] Make this list dynamic - dynamically can be listed modelNames but not modelVariant, legacy status, context length and pricing
|
|
2810
|
+
* TODO: [🧠][👮♀️] Put here more info like description, isVision, trainingDateCutoff, languages, strengths ( Top-level performance, intelligence, fluency, and understanding), contextWindow,...
|
|
2811
|
+
* @see https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4
|
|
2812
|
+
* @see https://openai.com/api/pricing/
|
|
2813
|
+
* @see /other/playground/playground.ts
|
|
2814
|
+
* TODO: [🍓][💩] Make better
|
|
2815
|
+
* TODO: Change model titles to human eg: "gpt-4-turbo-2024-04-09" -> "GPT-4 Turbo (2024-04-09)"
|
|
2816
|
+
* TODO: [🚸] Not all models are compatible with JSON mode, add this information here and use it
|
|
2817
|
+
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
2818
|
+
*/
|
|
2819
|
+
|
|
2820
|
+
/**
|
|
2821
|
+
* Computes the usage of the OpenAI API based on the response from OpenAI
|
|
2822
|
+
*
|
|
2823
|
+
* @param promptContent The content of the prompt
|
|
2824
|
+
* @param resultContent The content of the result (for embedding prompts or failed prompts pass empty string)
|
|
2825
|
+
* @param rawResponse The raw response from OpenAI API
|
|
2826
|
+
* @throws {PipelineExecutionError} If the usage is not defined in the response from OpenAI
|
|
2827
|
+
* @private internal utility of `OpenAiExecutionTools`
|
|
2828
|
+
*/
|
|
2829
|
+
function computeOpenAiUsage(promptContent, // <- Note: Intentionally using [] to access type properties to bring jsdoc from Prompt/PromptResult to consumer
|
|
2830
|
+
resultContent, rawResponse) {
|
|
2831
|
+
var _a, _b;
|
|
2832
|
+
if (rawResponse.usage === undefined) {
|
|
2833
|
+
throw new PipelineExecutionError('The usage is not defined in the response from OpenAI');
|
|
2834
|
+
}
|
|
2835
|
+
if (((_a = rawResponse.usage) === null || _a === void 0 ? void 0 : _a.prompt_tokens) === undefined) {
|
|
2836
|
+
throw new PipelineExecutionError('In OpenAI response `usage.prompt_tokens` not defined');
|
|
2837
|
+
}
|
|
2838
|
+
const inputTokens = rawResponse.usage.prompt_tokens;
|
|
2839
|
+
const outputTokens = ((_b = rawResponse.usage) === null || _b === void 0 ? void 0 : _b.completion_tokens) || 0;
|
|
2840
|
+
let isUncertain = false;
|
|
2841
|
+
let modelInfo = OPENAI_MODELS.find((model) => model.modelName === rawResponse.model);
|
|
2842
|
+
if (modelInfo === undefined) {
|
|
2843
|
+
// Note: Model is not in the list of known models, fallback to the family of the models and mark price as uncertain
|
|
2844
|
+
modelInfo = OPENAI_MODELS.find((model) => (rawResponse.model || SALT_NONCE).startsWith(model.modelName));
|
|
2845
|
+
if (modelInfo !== undefined) {
|
|
2846
|
+
isUncertain = true;
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
let price;
|
|
2850
|
+
if (modelInfo === undefined || modelInfo.pricing === undefined) {
|
|
2851
|
+
price = uncertainNumber();
|
|
2852
|
+
}
|
|
2853
|
+
else {
|
|
2854
|
+
price = uncertainNumber(inputTokens * modelInfo.pricing.prompt + outputTokens * modelInfo.pricing.output, isUncertain);
|
|
2855
|
+
}
|
|
2856
|
+
return {
|
|
2857
|
+
price,
|
|
2858
|
+
input: {
|
|
2859
|
+
tokensCount: uncertainNumber(rawResponse.usage.prompt_tokens),
|
|
2860
|
+
...computeUsageCounts(promptContent),
|
|
2861
|
+
},
|
|
2862
|
+
output: {
|
|
2863
|
+
tokensCount: uncertainNumber(outputTokens),
|
|
2864
|
+
...computeUsageCounts(resultContent),
|
|
2865
|
+
},
|
|
2866
|
+
};
|
|
2867
|
+
}
|
|
2868
|
+
/**
|
|
2869
|
+
* TODO: [🤝] DRY Maybe some common abstraction between `computeOpenAiUsage` and `computeAnthropicClaudeUsage`
|
|
2870
|
+
*/
|
|
2871
|
+
|
|
2872
|
+
/**
|
|
2873
|
+
* Tests if given string is valid URL.
|
|
2874
|
+
*
|
|
2875
|
+
* Note: [🔂] This function is idempotent.
|
|
2876
|
+
* Note: Dataurl are considered perfectly valid.
|
|
2877
|
+
* Note: There are few similar functions:
|
|
2878
|
+
* - `isValidUrl` *(this one)* which tests any URL
|
|
2879
|
+
* - `isValidAgentUrl` which tests just agent URL
|
|
2880
|
+
* - `isValidPipelineUrl` which tests just pipeline URL
|
|
2881
|
+
*
|
|
2882
|
+
* @public exported from `@promptbook/utils`
|
|
2883
|
+
*/
|
|
2884
|
+
function isValidUrl(url) {
|
|
2885
|
+
if (typeof url !== 'string') {
|
|
2886
|
+
return false;
|
|
2887
|
+
}
|
|
2888
|
+
try {
|
|
2889
|
+
if (url.startsWith('blob:')) {
|
|
2890
|
+
url = url.replace(/^blob:/, '');
|
|
2891
|
+
}
|
|
2892
|
+
const urlObject = new URL(url /* because fail is handled */);
|
|
2893
|
+
if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
|
|
2894
|
+
return false;
|
|
2895
|
+
}
|
|
2896
|
+
return true;
|
|
2897
|
+
}
|
|
2898
|
+
catch (error) {
|
|
2899
|
+
return false;
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
/**
|
|
2904
|
+
* This error indicates problems parsing the format value
|
|
2905
|
+
*
|
|
2906
|
+
* For example, when the format value is not a valid JSON or CSV
|
|
2907
|
+
* This is not thrown directly but in extended classes
|
|
2908
|
+
*
|
|
2909
|
+
* @public exported from `@promptbook/core`
|
|
2910
|
+
*/
|
|
2911
|
+
class AbstractFormatError extends Error {
|
|
2912
|
+
// Note: To allow instanceof do not put here error `name`
|
|
2913
|
+
// public readonly name = 'AbstractFormatError';
|
|
2914
|
+
constructor(message) {
|
|
2915
|
+
super(message);
|
|
2916
|
+
Object.setPrototypeOf(this, AbstractFormatError.prototype);
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
/**
|
|
2921
|
+
* This error indicates problem with parsing of CSV
|
|
2922
|
+
*
|
|
2923
|
+
* @public exported from `@promptbook/core`
|
|
2924
|
+
*/
|
|
2925
|
+
class CsvFormatError extends AbstractFormatError {
|
|
2926
|
+
constructor(message) {
|
|
2927
|
+
super(message);
|
|
2928
|
+
this.name = 'CsvFormatError';
|
|
2929
|
+
Object.setPrototypeOf(this, CsvFormatError.prototype);
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
/**
|
|
2934
|
+
* AuthenticationError is thrown from login function which is dependency of remote server
|
|
2935
|
+
*
|
|
2936
|
+
* @public exported from `@promptbook/core`
|
|
2937
|
+
*/
|
|
2938
|
+
class AuthenticationError extends Error {
|
|
2939
|
+
constructor(message) {
|
|
2940
|
+
super(message);
|
|
2941
|
+
this.name = 'AuthenticationError';
|
|
2942
|
+
Object.setPrototypeOf(this, AuthenticationError.prototype);
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
/**
|
|
2947
|
+
* This error indicates that the pipeline collection cannot be properly loaded
|
|
2948
|
+
*
|
|
2949
|
+
* @public exported from `@promptbook/core`
|
|
2950
|
+
*/
|
|
2951
|
+
class CollectionError extends Error {
|
|
2952
|
+
constructor(message) {
|
|
2953
|
+
super(message);
|
|
2954
|
+
this.name = 'CollectionError';
|
|
2955
|
+
Object.setPrototypeOf(this, CollectionError.prototype);
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
/**
|
|
2960
|
+
* This error indicates error from the database
|
|
2961
|
+
*
|
|
2962
|
+
* @public exported from `@promptbook/core`
|
|
2963
|
+
*/
|
|
2964
|
+
class DatabaseError extends Error {
|
|
2965
|
+
constructor(message) {
|
|
2966
|
+
super(message);
|
|
2967
|
+
this.name = 'DatabaseError';
|
|
2968
|
+
Object.setPrototypeOf(this, DatabaseError.prototype);
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
/**
|
|
2972
|
+
* TODO: [🐱🚀] Explain that NotFoundError ([🐱🚀] and other specific errors) has priority over DatabaseError in some contexts
|
|
2973
|
+
*/
|
|
2974
|
+
|
|
2975
|
+
/**
|
|
2976
|
+
* This error type indicates that you try to use a feature that is not available in the current environment
|
|
2977
|
+
*
|
|
2978
|
+
* @public exported from `@promptbook/core`
|
|
2979
|
+
*/
|
|
2980
|
+
class EnvironmentMismatchError extends Error {
|
|
2981
|
+
constructor(message) {
|
|
2982
|
+
super(message);
|
|
2983
|
+
this.name = 'EnvironmentMismatchError';
|
|
2984
|
+
Object.setPrototypeOf(this, EnvironmentMismatchError.prototype);
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
/**
|
|
2989
|
+
* This error occurs when some expectation is not met in the execution of the pipeline
|
|
2990
|
+
*
|
|
2991
|
+
* @public exported from `@promptbook/core`
|
|
2992
|
+
* Note: Do not throw this error, its reserved for `checkExpectations` and `createPipelineExecutor` and public ONLY to be serializable through remote server
|
|
2993
|
+
* Note: Always thrown in `checkExpectations` and catched in `createPipelineExecutor` and rethrown as `PipelineExecutionError`
|
|
2994
|
+
* Note: This is a kindof subtype of PipelineExecutionError
|
|
2995
|
+
*/
|
|
2996
|
+
class ExpectError extends Error {
|
|
2997
|
+
constructor(message) {
|
|
2998
|
+
super(message);
|
|
2999
|
+
this.name = 'ExpectError';
|
|
3000
|
+
Object.setPrototypeOf(this, ExpectError.prototype);
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
|
|
3004
|
+
/**
|
|
3005
|
+
* This error indicates that the promptbook can not retrieve knowledge from external sources
|
|
3006
|
+
*
|
|
3007
|
+
* @public exported from `@promptbook/core`
|
|
3008
|
+
*/
|
|
3009
|
+
class KnowledgeScrapeError extends Error {
|
|
3010
|
+
constructor(message) {
|
|
3011
|
+
super(message);
|
|
3012
|
+
this.name = 'KnowledgeScrapeError';
|
|
3013
|
+
Object.setPrototypeOf(this, KnowledgeScrapeError.prototype);
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
/**
|
|
3018
|
+
* This error type indicates that some tools are missing for pipeline execution or preparation
|
|
3019
|
+
*
|
|
3020
|
+
* @public exported from `@promptbook/core`
|
|
3021
|
+
*/
|
|
3022
|
+
class MissingToolsError extends Error {
|
|
3023
|
+
constructor(message) {
|
|
3024
|
+
super(spaceTrim$1((block) => `
|
|
3025
|
+
${block(message)}
|
|
3026
|
+
|
|
3027
|
+
Note: You have probably forgot to provide some tools for pipeline execution or preparation
|
|
3028
|
+
|
|
3029
|
+
`));
|
|
3030
|
+
this.name = 'MissingToolsError';
|
|
3031
|
+
Object.setPrototypeOf(this, MissingToolsError.prototype);
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
/**
|
|
3036
|
+
* This error indicates that promptbook operation is not allowed
|
|
3037
|
+
*
|
|
3038
|
+
* @public exported from `@promptbook/core`
|
|
3039
|
+
*/
|
|
3040
|
+
class NotAllowed extends Error {
|
|
3041
|
+
constructor(message) {
|
|
3042
|
+
super(message);
|
|
3043
|
+
this.name = 'NotAllowed';
|
|
3044
|
+
Object.setPrototypeOf(this, NotAllowed.prototype);
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
/**
|
|
3049
|
+
* This error indicates that promptbook not found in the collection
|
|
3050
|
+
*
|
|
3051
|
+
* @public exported from `@promptbook/core`
|
|
3052
|
+
*/
|
|
3053
|
+
class NotFoundError extends Error {
|
|
3054
|
+
constructor(message) {
|
|
3055
|
+
super(message);
|
|
3056
|
+
this.name = 'NotFoundError';
|
|
3057
|
+
Object.setPrototypeOf(this, NotFoundError.prototype);
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
/**
|
|
3062
|
+
* This error type indicates that some part of the code is not implemented yet
|
|
3063
|
+
*
|
|
3064
|
+
* @public exported from `@promptbook/core`
|
|
3065
|
+
*/
|
|
3066
|
+
class NotYetImplementedError extends Error {
|
|
3067
|
+
constructor(message) {
|
|
3068
|
+
super(spaceTrim$1((block) => `
|
|
3069
|
+
${block(message)}
|
|
3070
|
+
|
|
3071
|
+
Note: This feature is not implemented yet but it will be soon.
|
|
3072
|
+
|
|
3073
|
+
If you want speed up the implementation or just read more, look here:
|
|
3074
|
+
https://github.com/webgptorg/promptbook
|
|
3075
|
+
|
|
3076
|
+
Or contact us on pavol@ptbk.io
|
|
3077
|
+
|
|
3078
|
+
`));
|
|
3079
|
+
this.name = 'NotYetImplementedError';
|
|
3080
|
+
Object.setPrototypeOf(this, NotYetImplementedError.prototype);
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
/**
|
|
3085
|
+
* This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
|
|
3086
|
+
*
|
|
3087
|
+
* @public exported from `@promptbook/core`
|
|
3088
|
+
*/
|
|
3089
|
+
class ParseError extends Error {
|
|
3090
|
+
constructor(message) {
|
|
3091
|
+
super(message);
|
|
3092
|
+
this.name = 'ParseError';
|
|
3093
|
+
Object.setPrototypeOf(this, ParseError.prototype);
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
/**
|
|
3097
|
+
* TODO: Maybe split `ParseError` and `ApplyError`
|
|
3098
|
+
*/
|
|
3099
|
+
|
|
3100
|
+
/**
|
|
3101
|
+
* This error indicates that the promptbook object has valid syntax (=can be parsed) but contains logical errors (like circular dependencies)
|
|
3102
|
+
*
|
|
3103
|
+
* @public exported from `@promptbook/core`
|
|
3104
|
+
*/
|
|
3105
|
+
class PipelineLogicError extends Error {
|
|
3106
|
+
constructor(message) {
|
|
3107
|
+
super(message);
|
|
3108
|
+
this.name = 'PipelineLogicError';
|
|
3109
|
+
Object.setPrototypeOf(this, PipelineLogicError.prototype);
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
|
|
3113
|
+
/**
|
|
3114
|
+
* This error indicates errors in referencing promptbooks between each other
|
|
3115
|
+
*
|
|
3116
|
+
* @public exported from `@promptbook/core`
|
|
3117
|
+
*/
|
|
3118
|
+
class PipelineUrlError extends Error {
|
|
3119
|
+
constructor(message) {
|
|
3120
|
+
super(message);
|
|
3121
|
+
this.name = 'PipelineUrlError';
|
|
3122
|
+
Object.setPrototypeOf(this, PipelineUrlError.prototype);
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
|
|
3126
|
+
/**
|
|
3127
|
+
* Error thrown when a fetch request fails
|
|
3128
|
+
*
|
|
3129
|
+
* @public exported from `@promptbook/core`
|
|
3130
|
+
*/
|
|
3131
|
+
class PromptbookFetchError extends Error {
|
|
3132
|
+
constructor(message) {
|
|
3133
|
+
super(message);
|
|
3134
|
+
this.name = 'PromptbookFetchError';
|
|
3135
|
+
Object.setPrototypeOf(this, PromptbookFetchError.prototype);
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
|
|
3139
|
+
/**
|
|
3140
|
+
* Index of all custom errors
|
|
3141
|
+
*
|
|
3142
|
+
* @public exported from `@promptbook/core`
|
|
3143
|
+
*/
|
|
3144
|
+
const PROMPTBOOK_ERRORS = {
|
|
3145
|
+
AbstractFormatError,
|
|
3146
|
+
CsvFormatError,
|
|
3147
|
+
CollectionError,
|
|
3148
|
+
EnvironmentMismatchError,
|
|
3149
|
+
ExpectError,
|
|
3150
|
+
KnowledgeScrapeError,
|
|
3151
|
+
LimitReachedError,
|
|
3152
|
+
MissingToolsError,
|
|
3153
|
+
NotFoundError,
|
|
3154
|
+
NotYetImplementedError,
|
|
3155
|
+
ParseError,
|
|
3156
|
+
PipelineExecutionError,
|
|
3157
|
+
PipelineLogicError,
|
|
3158
|
+
PipelineUrlError,
|
|
3159
|
+
AuthenticationError,
|
|
3160
|
+
PromptbookFetchError,
|
|
3161
|
+
UnexpectedError,
|
|
3162
|
+
WrappedError,
|
|
3163
|
+
NotAllowed,
|
|
3164
|
+
DatabaseError,
|
|
3165
|
+
// TODO: [🪑]> VersionMismatchError,
|
|
3166
|
+
};
|
|
3167
|
+
/**
|
|
3168
|
+
* Index of all javascript errors
|
|
3169
|
+
*
|
|
3170
|
+
* @private for internal usage
|
|
3171
|
+
*/
|
|
3172
|
+
const COMMON_JAVASCRIPT_ERRORS = {
|
|
3173
|
+
Error,
|
|
3174
|
+
EvalError,
|
|
3175
|
+
RangeError,
|
|
3176
|
+
ReferenceError,
|
|
3177
|
+
SyntaxError,
|
|
3178
|
+
TypeError,
|
|
3179
|
+
URIError,
|
|
3180
|
+
AggregateError,
|
|
3181
|
+
/*
|
|
3182
|
+
Note: Not widely supported
|
|
3183
|
+
> InternalError,
|
|
3184
|
+
> ModuleError,
|
|
3185
|
+
> HeapError,
|
|
3186
|
+
> WebAssemblyCompileError,
|
|
3187
|
+
> WebAssemblyRuntimeError,
|
|
3188
|
+
*/
|
|
3189
|
+
};
|
|
3190
|
+
/**
|
|
3191
|
+
* Index of all errors
|
|
3192
|
+
*
|
|
3193
|
+
* @private for internal usage
|
|
3194
|
+
*/
|
|
3195
|
+
const ALL_ERRORS = {
|
|
3196
|
+
...PROMPTBOOK_ERRORS,
|
|
3197
|
+
...COMMON_JAVASCRIPT_ERRORS,
|
|
3198
|
+
};
|
|
3199
|
+
/**
|
|
3200
|
+
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
3201
|
+
*/
|
|
3202
|
+
|
|
3203
|
+
/**
|
|
3204
|
+
* Deserializes the error object
|
|
3205
|
+
*
|
|
3206
|
+
* @public exported from `@promptbook/utils`
|
|
3207
|
+
*/
|
|
3208
|
+
function deserializeError(error, isStackAddedToMessage = true) {
|
|
3209
|
+
const { name, stack, id } = error; // Added id
|
|
3210
|
+
let { message } = error;
|
|
3211
|
+
let ErrorClass = ALL_ERRORS[error.name];
|
|
3212
|
+
if (ErrorClass === undefined) {
|
|
3213
|
+
ErrorClass = Error;
|
|
3214
|
+
message = `${name}: ${message}`;
|
|
3215
|
+
}
|
|
3216
|
+
if (isStackAddedToMessage && stack !== undefined && stack !== '') {
|
|
3217
|
+
message = spaceTrim$2((block) => `
|
|
3218
|
+
${block(message)}
|
|
3219
|
+
|
|
3220
|
+
Original stack trace:
|
|
3221
|
+
${block(stack || '')}
|
|
3222
|
+
`);
|
|
3223
|
+
}
|
|
3224
|
+
const deserializedError = new ErrorClass(message);
|
|
3225
|
+
deserializedError.id = id; // Assign id to the error object
|
|
3226
|
+
return deserializedError;
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
/**
|
|
3230
|
+
* Serializes an error into a [🚉] JSON-serializable object
|
|
3231
|
+
*
|
|
3232
|
+
* @public exported from `@promptbook/utils`
|
|
3233
|
+
*/
|
|
3234
|
+
function serializeError(error) {
|
|
3235
|
+
const { name, message, stack } = error;
|
|
3236
|
+
const { id } = error;
|
|
3237
|
+
if (!Object.keys(ALL_ERRORS).includes(name)) {
|
|
3238
|
+
console.error(spaceTrim$2((block) => `
|
|
3239
|
+
|
|
3240
|
+
Cannot serialize error with name "${name}"
|
|
3241
|
+
|
|
3242
|
+
Authors of Promptbook probably forgot to add this error into the list of errors:
|
|
3243
|
+
https://github.com/webgptorg/promptbook/blob/main/src/errors/0-index.ts
|
|
3244
|
+
|
|
3245
|
+
|
|
3246
|
+
${block(stack || message)}
|
|
3247
|
+
|
|
3248
|
+
`));
|
|
3249
|
+
}
|
|
3250
|
+
return {
|
|
3251
|
+
name: name,
|
|
3252
|
+
message,
|
|
3253
|
+
stack,
|
|
3254
|
+
id, // Include id in the serialized object
|
|
3255
|
+
};
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3258
|
+
/**
|
|
3259
|
+
* Async version of Array.forEach
|
|
3260
|
+
*
|
|
3261
|
+
* @param array - Array to iterate over
|
|
3262
|
+
* @param options - Options for the function
|
|
3263
|
+
* @param callbackfunction - Function to call for each item
|
|
3264
|
+
* @public exported from `@promptbook/utils`
|
|
3265
|
+
* @deprecated [🪂] Use queues instead
|
|
3266
|
+
*/
|
|
3267
|
+
async function forEachAsync(array, options, callbackfunction) {
|
|
3268
|
+
const { maxParallelCount = Infinity } = options;
|
|
3269
|
+
let index = 0;
|
|
3270
|
+
let runningTasks = [];
|
|
3271
|
+
const tasks = [];
|
|
3272
|
+
for (const item of array) {
|
|
3273
|
+
const currentIndex = index++;
|
|
3274
|
+
const task = callbackfunction(item, currentIndex, array);
|
|
3275
|
+
tasks.push(task);
|
|
3276
|
+
runningTasks.push(task);
|
|
3277
|
+
/* not await */ Promise.resolve(task).then(() => {
|
|
3278
|
+
runningTasks = runningTasks.filter((runningTask) => runningTask !== task);
|
|
3279
|
+
});
|
|
3280
|
+
if (maxParallelCount < runningTasks.length) {
|
|
3281
|
+
await Promise.race(runningTasks);
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
await Promise.all(tasks);
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3287
|
+
/**
|
|
3288
|
+
* Normalizes a text string to SCREAMING_CASE (all uppercase with underscores).
|
|
3289
|
+
*
|
|
3290
|
+
* Note: [🔂] This function is idempotent.
|
|
3291
|
+
*
|
|
3292
|
+
* @param text The text string to be converted to SCREAMING_CASE format.
|
|
3293
|
+
* @returns The normalized text in SCREAMING_CASE format.
|
|
3294
|
+
* @example 'HELLO_WORLD'
|
|
3295
|
+
* @example 'I_LOVE_PROMPTBOOK'
|
|
3296
|
+
* @public exported from `@promptbook/utils`
|
|
3297
|
+
*/
|
|
3298
|
+
function normalizeTo_SCREAMING_CASE(text) {
|
|
3299
|
+
let charType;
|
|
3300
|
+
let lastCharType = 'OTHER';
|
|
3301
|
+
let normalizedName = '';
|
|
3302
|
+
for (const char of text) {
|
|
3303
|
+
let normalizedChar;
|
|
3304
|
+
if (/^[a-z]$/.test(char)) {
|
|
3305
|
+
charType = 'LOWERCASE';
|
|
3306
|
+
normalizedChar = char.toUpperCase();
|
|
3307
|
+
}
|
|
3308
|
+
else if (/^[A-Z]$/.test(char)) {
|
|
3309
|
+
charType = 'UPPERCASE';
|
|
3310
|
+
normalizedChar = char;
|
|
3311
|
+
}
|
|
3312
|
+
else if (/^[0-9]$/.test(char)) {
|
|
3313
|
+
charType = 'NUMBER';
|
|
3314
|
+
normalizedChar = char;
|
|
3315
|
+
}
|
|
3316
|
+
else {
|
|
3317
|
+
charType = 'OTHER';
|
|
3318
|
+
normalizedChar = '_';
|
|
3319
|
+
}
|
|
3320
|
+
if (charType !== lastCharType &&
|
|
3321
|
+
!(lastCharType === 'UPPERCASE' && charType === 'LOWERCASE') &&
|
|
3322
|
+
!(lastCharType === 'NUMBER') &&
|
|
3323
|
+
!(charType === 'NUMBER')) {
|
|
3324
|
+
normalizedName += '_';
|
|
3325
|
+
}
|
|
3326
|
+
normalizedName += normalizedChar;
|
|
3327
|
+
lastCharType = charType;
|
|
3328
|
+
}
|
|
3329
|
+
normalizedName = normalizedName.replace(/_+/g, '_');
|
|
3330
|
+
normalizedName = normalizedName.replace(/_?\/_?/g, '/');
|
|
3331
|
+
normalizedName = normalizedName.replace(/^_/, '');
|
|
3332
|
+
normalizedName = normalizedName.replace(/_$/, '');
|
|
3333
|
+
return normalizedName;
|
|
3334
|
+
}
|
|
3335
|
+
/**
|
|
3336
|
+
* TODO: Tests
|
|
3337
|
+
* > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'Moje tabule' })).toEqual('/VtG7sR9rRJqwNEdM2/Moje tabule');
|
|
3338
|
+
* > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'ěščřžžýáíúů' })).toEqual('/VtG7sR9rRJqwNEdM2/escrzyaieuu');
|
|
3339
|
+
* > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj');
|
|
3340
|
+
* > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj_ahojAhoj ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj-ahoj-ahoj-ahoj');
|
|
3341
|
+
* TODO: [🌺] Use some intermediate util splitWords
|
|
3305
3342
|
*/
|
|
3306
3343
|
|
|
3307
3344
|
/**
|
|
3308
|
-
*
|
|
3345
|
+
* Normalizes a text string to snake_case format.
|
|
3309
3346
|
*
|
|
3310
|
-
*
|
|
3311
|
-
*
|
|
3312
|
-
* @param
|
|
3313
|
-
* @
|
|
3314
|
-
* @
|
|
3347
|
+
* Note: [🔂] This function is idempotent.
|
|
3348
|
+
*
|
|
3349
|
+
* @param text The text string to be converted to snake_case format.
|
|
3350
|
+
* @returns The normalized text in snake_case format.
|
|
3351
|
+
* @example 'hello_world'
|
|
3352
|
+
* @example 'i_love_promptbook'
|
|
3353
|
+
* @public exported from `@promptbook/utils`
|
|
3315
3354
|
*/
|
|
3316
|
-
function
|
|
3317
|
-
|
|
3318
|
-
var _a, _b;
|
|
3319
|
-
if (rawResponse.usage === undefined) {
|
|
3320
|
-
throw new PipelineExecutionError('The usage is not defined in the response from OpenAI');
|
|
3321
|
-
}
|
|
3322
|
-
if (((_a = rawResponse.usage) === null || _a === void 0 ? void 0 : _a.prompt_tokens) === undefined) {
|
|
3323
|
-
throw new PipelineExecutionError('In OpenAI response `usage.prompt_tokens` not defined');
|
|
3324
|
-
}
|
|
3325
|
-
const inputTokens = rawResponse.usage.prompt_tokens;
|
|
3326
|
-
const outputTokens = ((_b = rawResponse.usage) === null || _b === void 0 ? void 0 : _b.completion_tokens) || 0;
|
|
3327
|
-
let isUncertain = false;
|
|
3328
|
-
let modelInfo = OPENAI_MODELS.find((model) => model.modelName === rawResponse.model);
|
|
3329
|
-
if (modelInfo === undefined) {
|
|
3330
|
-
// Note: Model is not in the list of known models, fallback to the family of the models and mark price as uncertain
|
|
3331
|
-
modelInfo = OPENAI_MODELS.find((model) => (rawResponse.model || SALT_NONCE).startsWith(model.modelName));
|
|
3332
|
-
if (modelInfo !== undefined) {
|
|
3333
|
-
isUncertain = true;
|
|
3334
|
-
}
|
|
3335
|
-
}
|
|
3336
|
-
let price;
|
|
3337
|
-
if (modelInfo === undefined || modelInfo.pricing === undefined) {
|
|
3338
|
-
price = uncertainNumber();
|
|
3339
|
-
}
|
|
3340
|
-
else {
|
|
3341
|
-
price = uncertainNumber(inputTokens * modelInfo.pricing.prompt + outputTokens * modelInfo.pricing.output, isUncertain);
|
|
3342
|
-
}
|
|
3343
|
-
return {
|
|
3344
|
-
price,
|
|
3345
|
-
input: {
|
|
3346
|
-
tokensCount: uncertainNumber(rawResponse.usage.prompt_tokens),
|
|
3347
|
-
...computeUsageCounts(promptContent),
|
|
3348
|
-
},
|
|
3349
|
-
output: {
|
|
3350
|
-
tokensCount: uncertainNumber(outputTokens),
|
|
3351
|
-
...computeUsageCounts(resultContent),
|
|
3352
|
-
},
|
|
3353
|
-
};
|
|
3355
|
+
function normalizeTo_snake_case(text) {
|
|
3356
|
+
return normalizeTo_SCREAMING_CASE(text).toLowerCase();
|
|
3354
3357
|
}
|
|
3355
|
-
/**
|
|
3356
|
-
* TODO: [🤝] DRY Maybe some common abstraction between `computeOpenAiUsage` and `computeAnthropicClaudeUsage`
|
|
3357
|
-
*/
|
|
3358
3358
|
|
|
3359
3359
|
/**
|
|
3360
3360
|
* Function `addUsage` will add multiple usages into one
|
|
@@ -3617,11 +3617,35 @@ class OpenAiCompatibleExecutionTools {
|
|
|
3617
3617
|
},
|
|
3618
3618
|
]),
|
|
3619
3619
|
...threadMessages,
|
|
3620
|
-
|
|
3620
|
+
];
|
|
3621
|
+
if ('files' in prompt && Array.isArray(prompt.files) && prompt.files.length > 0) {
|
|
3622
|
+
const filesContent = await Promise.all(prompt.files.map(async (file) => {
|
|
3623
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
3624
|
+
const base64 = Buffer.from(arrayBuffer).toString('base64');
|
|
3625
|
+
return {
|
|
3626
|
+
type: 'image_url',
|
|
3627
|
+
image_url: {
|
|
3628
|
+
url: `data:${file.type};base64,${base64}`,
|
|
3629
|
+
},
|
|
3630
|
+
};
|
|
3631
|
+
}));
|
|
3632
|
+
messages.push({
|
|
3633
|
+
role: 'user',
|
|
3634
|
+
content: [
|
|
3635
|
+
{
|
|
3636
|
+
type: 'text',
|
|
3637
|
+
text: rawPromptContent,
|
|
3638
|
+
},
|
|
3639
|
+
...filesContent,
|
|
3640
|
+
],
|
|
3641
|
+
});
|
|
3642
|
+
}
|
|
3643
|
+
else {
|
|
3644
|
+
messages.push({
|
|
3621
3645
|
role: 'user',
|
|
3622
3646
|
content: rawPromptContent,
|
|
3623
|
-
}
|
|
3624
|
-
|
|
3647
|
+
});
|
|
3648
|
+
}
|
|
3625
3649
|
let totalUsage = {
|
|
3626
3650
|
price: uncertainNumber(0),
|
|
3627
3651
|
input: {
|
|
@@ -3678,18 +3702,26 @@ class OpenAiCompatibleExecutionTools {
|
|
|
3678
3702
|
const usage = this.computeUsage(content || '', responseMessage.content || '', rawResponse);
|
|
3679
3703
|
totalUsage = addUsage(totalUsage, usage);
|
|
3680
3704
|
if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) {
|
|
3705
|
+
const toolCallStartedAt = new Map();
|
|
3681
3706
|
if (onProgress) {
|
|
3682
3707
|
onProgress({
|
|
3683
3708
|
content: responseMessage.content || '',
|
|
3684
3709
|
modelName: rawResponse.model || modelName,
|
|
3685
3710
|
timing: { start, complete: $getCurrentDate() },
|
|
3686
3711
|
usage: totalUsage,
|
|
3687
|
-
toolCalls: responseMessage.tool_calls.map((toolCall) =>
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3712
|
+
toolCalls: responseMessage.tool_calls.map((toolCall) => {
|
|
3713
|
+
const calledAt = $getCurrentDate();
|
|
3714
|
+
if (toolCall.id) {
|
|
3715
|
+
toolCallStartedAt.set(toolCall.id, calledAt);
|
|
3716
|
+
}
|
|
3717
|
+
return {
|
|
3718
|
+
name: toolCall.function.name,
|
|
3719
|
+
arguments: toolCall.function.arguments,
|
|
3720
|
+
result: '',
|
|
3721
|
+
rawToolCall: toolCall,
|
|
3722
|
+
createdAt: calledAt,
|
|
3723
|
+
};
|
|
3724
|
+
}),
|
|
3693
3725
|
rawPromptContent,
|
|
3694
3726
|
rawRequest,
|
|
3695
3727
|
rawResponse,
|
|
@@ -3698,6 +3730,9 @@ class OpenAiCompatibleExecutionTools {
|
|
|
3698
3730
|
await forEachAsync(responseMessage.tool_calls, {}, async (toolCall) => {
|
|
3699
3731
|
const functionName = toolCall.function.name;
|
|
3700
3732
|
const functionArgs = toolCall.function.arguments;
|
|
3733
|
+
const calledAt = toolCall.id
|
|
3734
|
+
? toolCallStartedAt.get(toolCall.id) || $getCurrentDate()
|
|
3735
|
+
: $getCurrentDate();
|
|
3701
3736
|
const executionTools = this.options
|
|
3702
3737
|
.executionTools;
|
|
3703
3738
|
if (!executionTools || !executionTools.script) {
|
|
@@ -3708,6 +3743,7 @@ class OpenAiCompatibleExecutionTools {
|
|
|
3708
3743
|
? executionTools.script
|
|
3709
3744
|
: [executionTools.script];
|
|
3710
3745
|
let functionResponse;
|
|
3746
|
+
let errors;
|
|
3711
3747
|
try {
|
|
3712
3748
|
const scriptTool = scriptTools[0]; // <- TODO: [🧠] Which script tool to use?
|
|
3713
3749
|
functionResponse = await scriptTool.execute({
|
|
@@ -3722,6 +3758,7 @@ class OpenAiCompatibleExecutionTools {
|
|
|
3722
3758
|
catch (error) {
|
|
3723
3759
|
assertsError(error);
|
|
3724
3760
|
functionResponse = `Error: ${error.message}`;
|
|
3761
|
+
errors = [serializeError(error)];
|
|
3725
3762
|
}
|
|
3726
3763
|
messages.push({
|
|
3727
3764
|
role: 'tool',
|
|
@@ -3733,6 +3770,8 @@ class OpenAiCompatibleExecutionTools {
|
|
|
3733
3770
|
arguments: functionArgs,
|
|
3734
3771
|
result: functionResponse,
|
|
3735
3772
|
rawToolCall: toolCall,
|
|
3773
|
+
createdAt: calledAt,
|
|
3774
|
+
errors,
|
|
3736
3775
|
});
|
|
3737
3776
|
});
|
|
3738
3777
|
continue;
|
|
@@ -4111,7 +4150,12 @@ class OpenAiCompatibleExecutionTools {
|
|
|
4111
4150
|
quality: currentModelRequirements.quality,
|
|
4112
4151
|
style: currentModelRequirements.style,
|
|
4113
4152
|
};
|
|
4114
|
-
|
|
4153
|
+
let rawPromptContent = templateParameters(content, { ...parameters, modelName });
|
|
4154
|
+
if ('attachments' in prompt && Array.isArray(prompt.attachments) && prompt.attachments.length > 0) {
|
|
4155
|
+
rawPromptContent +=
|
|
4156
|
+
'\n\n' +
|
|
4157
|
+
prompt.attachments.map((attachment) => `Image attachment: ${attachment.url}`).join('\n');
|
|
4158
|
+
}
|
|
4115
4159
|
const rawRequest = {
|
|
4116
4160
|
...modelSettings,
|
|
4117
4161
|
prompt: rawPromptContent,
|
|
@@ -4404,6 +4448,243 @@ class OpenAiExecutionTools extends OpenAiCompatibleExecutionTools {
|
|
|
4404
4448
|
}
|
|
4405
4449
|
}
|
|
4406
4450
|
|
|
4451
|
+
/**
|
|
4452
|
+
* Execution Tools for calling OpenAI API using the Responses API (Agents)
|
|
4453
|
+
*
|
|
4454
|
+
* @public exported from `@promptbook/openai`
|
|
4455
|
+
*/
|
|
4456
|
+
class OpenAiAgentExecutionTools extends OpenAiExecutionTools {
|
|
4457
|
+
constructor(options) {
|
|
4458
|
+
super(options);
|
|
4459
|
+
this.vectorStoreId = options.vectorStoreId;
|
|
4460
|
+
}
|
|
4461
|
+
get title() {
|
|
4462
|
+
return 'OpenAI Agent';
|
|
4463
|
+
}
|
|
4464
|
+
get description() {
|
|
4465
|
+
return 'Use OpenAI Responses API (Agentic)';
|
|
4466
|
+
}
|
|
4467
|
+
/**
|
|
4468
|
+
* Calls OpenAI API to use a chat model with streaming.
|
|
4469
|
+
*/
|
|
4470
|
+
async callChatModelStream(prompt, onProgress) {
|
|
4471
|
+
if (this.options.isVerbose) {
|
|
4472
|
+
console.info('💬 OpenAI Agent callChatModel call', { prompt });
|
|
4473
|
+
}
|
|
4474
|
+
const { content, parameters, modelRequirements } = prompt;
|
|
4475
|
+
const client = await this.getClient();
|
|
4476
|
+
if (modelRequirements.modelVariant !== 'CHAT') {
|
|
4477
|
+
throw new PipelineExecutionError('Use callChatModel only for CHAT variant');
|
|
4478
|
+
}
|
|
4479
|
+
const rawPromptContent = templateParameters(content, {
|
|
4480
|
+
...parameters,
|
|
4481
|
+
modelName: 'agent',
|
|
4482
|
+
});
|
|
4483
|
+
// Build input items
|
|
4484
|
+
const input = []; // TODO: Type properly when OpenAI types are updated
|
|
4485
|
+
// Add previous messages from thread (if any)
|
|
4486
|
+
if ('thread' in prompt && Array.isArray(prompt.thread)) {
|
|
4487
|
+
const previousMessages = prompt.thread.map((msg) => ({
|
|
4488
|
+
role: msg.sender === 'assistant' ? 'assistant' : 'user',
|
|
4489
|
+
content: msg.content,
|
|
4490
|
+
}));
|
|
4491
|
+
input.push(...previousMessages);
|
|
4492
|
+
}
|
|
4493
|
+
// Add current user message
|
|
4494
|
+
input.push({
|
|
4495
|
+
role: 'user',
|
|
4496
|
+
content: rawPromptContent,
|
|
4497
|
+
});
|
|
4498
|
+
// Prepare tools
|
|
4499
|
+
const tools = modelRequirements.tools ? mapToolsToOpenAi(modelRequirements.tools) : undefined;
|
|
4500
|
+
// Add file_search if vector store is present
|
|
4501
|
+
const agentTools = tools ? [...tools] : [];
|
|
4502
|
+
let toolResources = undefined;
|
|
4503
|
+
if (this.vectorStoreId) {
|
|
4504
|
+
agentTools.push({ type: 'file_search' });
|
|
4505
|
+
toolResources = {
|
|
4506
|
+
file_search: {
|
|
4507
|
+
vector_store_ids: [this.vectorStoreId],
|
|
4508
|
+
},
|
|
4509
|
+
};
|
|
4510
|
+
}
|
|
4511
|
+
// Add file_search also if knowledgeSources are present in the prompt (passed via AgentLlmExecutionTools)
|
|
4512
|
+
if (modelRequirements.knowledgeSources &&
|
|
4513
|
+
modelRequirements.knowledgeSources.length > 0 &&
|
|
4514
|
+
!this.vectorStoreId) {
|
|
4515
|
+
// Note: Vector store should have been created by AgentLlmExecutionTools and passed via options.
|
|
4516
|
+
// If we are here, it means we have knowledge sources but no vector store ID.
|
|
4517
|
+
// We can't easily create one here without persisting it.
|
|
4518
|
+
console.warn('Knowledge sources provided but no vector store ID. Creating temporary vector store is not implemented in callChatModelStream.');
|
|
4519
|
+
}
|
|
4520
|
+
const start = $getCurrentDate();
|
|
4521
|
+
// Construct the request
|
|
4522
|
+
const rawRequest = {
|
|
4523
|
+
// TODO: Type properly as OpenAI.Responses.CreateResponseParams
|
|
4524
|
+
model: modelRequirements.modelName || 'gpt-4o',
|
|
4525
|
+
input,
|
|
4526
|
+
instructions: modelRequirements.systemMessage,
|
|
4527
|
+
tools: agentTools.length > 0 ? agentTools : undefined,
|
|
4528
|
+
tool_resources: toolResources,
|
|
4529
|
+
store: false, // Stateless by default as we pass full history
|
|
4530
|
+
};
|
|
4531
|
+
if (this.options.isVerbose) {
|
|
4532
|
+
console.info(colors.bgWhite('rawRequest (Responses API)'), JSON.stringify(rawRequest, null, 4));
|
|
4533
|
+
}
|
|
4534
|
+
// Call Responses API
|
|
4535
|
+
// Note: Using any cast because types might not be updated yet
|
|
4536
|
+
const response = await client.responses.create(rawRequest);
|
|
4537
|
+
if (this.options.isVerbose) {
|
|
4538
|
+
console.info(colors.bgWhite('rawResponse'), JSON.stringify(response, null, 4));
|
|
4539
|
+
}
|
|
4540
|
+
const complete = $getCurrentDate();
|
|
4541
|
+
let resultContent = '';
|
|
4542
|
+
const toolCalls = [];
|
|
4543
|
+
// Parse output items
|
|
4544
|
+
if (response.output) {
|
|
4545
|
+
for (const item of response.output) {
|
|
4546
|
+
if (item.type === 'message' && item.role === 'assistant') {
|
|
4547
|
+
for (const contentPart of item.content) {
|
|
4548
|
+
if (contentPart.type === 'output_text') {
|
|
4549
|
+
// "output_text" based on migration guide, or "text"? Guide says "output_text" in example.
|
|
4550
|
+
resultContent += contentPart.text;
|
|
4551
|
+
}
|
|
4552
|
+
else if (contentPart.type === 'text') {
|
|
4553
|
+
resultContent += contentPart.text.value || contentPart.text;
|
|
4554
|
+
}
|
|
4555
|
+
}
|
|
4556
|
+
}
|
|
4557
|
+
else if (item.type === 'function_call') ;
|
|
4558
|
+
}
|
|
4559
|
+
}
|
|
4560
|
+
// Use output_text helper if available (mentioned in guide)
|
|
4561
|
+
if (response.output_text) {
|
|
4562
|
+
resultContent = response.output_text;
|
|
4563
|
+
}
|
|
4564
|
+
// TODO: Handle tool calls properly (Requires clearer docs or experimentation)
|
|
4565
|
+
onProgress({
|
|
4566
|
+
content: resultContent,
|
|
4567
|
+
modelName: response.model || 'agent',
|
|
4568
|
+
timing: { start, complete },
|
|
4569
|
+
usage: UNCERTAIN_USAGE,
|
|
4570
|
+
rawPromptContent,
|
|
4571
|
+
rawRequest,
|
|
4572
|
+
rawResponse: response,
|
|
4573
|
+
});
|
|
4574
|
+
return exportJson({
|
|
4575
|
+
name: 'promptResult',
|
|
4576
|
+
message: `Result of \`OpenAiAgentExecutionTools.callChatModelStream\``,
|
|
4577
|
+
order: [],
|
|
4578
|
+
value: {
|
|
4579
|
+
content: resultContent,
|
|
4580
|
+
modelName: response.model || 'agent',
|
|
4581
|
+
timing: { start, complete },
|
|
4582
|
+
usage: UNCERTAIN_USAGE,
|
|
4583
|
+
rawPromptContent,
|
|
4584
|
+
rawRequest,
|
|
4585
|
+
rawResponse: response,
|
|
4586
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
4587
|
+
},
|
|
4588
|
+
});
|
|
4589
|
+
}
|
|
4590
|
+
/**
|
|
4591
|
+
* Creates a vector store from knowledge sources
|
|
4592
|
+
*/
|
|
4593
|
+
static async createVectorStore(client, name, knowledgeSources) {
|
|
4594
|
+
// Create a vector store
|
|
4595
|
+
const vectorStore = await client.beta.vectorStores.create({
|
|
4596
|
+
name: `${name} Knowledge Base`,
|
|
4597
|
+
});
|
|
4598
|
+
const vectorStoreId = vectorStore.id;
|
|
4599
|
+
// Upload files from knowledge sources to the vector store
|
|
4600
|
+
const fileStreams = [];
|
|
4601
|
+
for (const source of knowledgeSources) {
|
|
4602
|
+
try {
|
|
4603
|
+
// Check if it's a URL
|
|
4604
|
+
if (source.startsWith('http://') || source.startsWith('https://')) {
|
|
4605
|
+
// Download the file
|
|
4606
|
+
const response = await fetch(source);
|
|
4607
|
+
if (!response.ok) {
|
|
4608
|
+
console.error(`Failed to download ${source}: ${response.statusText}`);
|
|
4609
|
+
continue;
|
|
4610
|
+
}
|
|
4611
|
+
const buffer = await response.arrayBuffer();
|
|
4612
|
+
const filename = source.split('/').pop() || 'downloaded-file';
|
|
4613
|
+
const blob = new Blob([buffer]);
|
|
4614
|
+
const file = new File([blob], filename);
|
|
4615
|
+
fileStreams.push(file);
|
|
4616
|
+
}
|
|
4617
|
+
else {
|
|
4618
|
+
// Local files not supported in browser env easily, same as before
|
|
4619
|
+
}
|
|
4620
|
+
}
|
|
4621
|
+
catch (error) {
|
|
4622
|
+
console.error(`Error processing knowledge source ${source}:`, error);
|
|
4623
|
+
}
|
|
4624
|
+
}
|
|
4625
|
+
// Batch upload files to the vector store
|
|
4626
|
+
if (fileStreams.length > 0) {
|
|
4627
|
+
try {
|
|
4628
|
+
await client.beta.vectorStores.fileBatches.uploadAndPoll(vectorStoreId, {
|
|
4629
|
+
files: fileStreams,
|
|
4630
|
+
});
|
|
4631
|
+
}
|
|
4632
|
+
catch (error) {
|
|
4633
|
+
console.error('Error uploading files to vector store:', error);
|
|
4634
|
+
}
|
|
4635
|
+
}
|
|
4636
|
+
return vectorStoreId;
|
|
4637
|
+
}
|
|
4638
|
+
/**
|
|
4639
|
+
* Discriminant for type guards
|
|
4640
|
+
*/
|
|
4641
|
+
get discriminant() {
|
|
4642
|
+
return 'OPEN_AI_AGENT';
|
|
4643
|
+
}
|
|
4644
|
+
/**
|
|
4645
|
+
* Type guard to check if given `LlmExecutionTools` are instanceof `OpenAiAgentExecutionTools`
|
|
4646
|
+
*/
|
|
4647
|
+
static isOpenAiAgentExecutionTools(llmExecutionTools) {
|
|
4648
|
+
return llmExecutionTools.discriminant === 'OPEN_AI_AGENT';
|
|
4649
|
+
}
|
|
4650
|
+
}
|
|
4651
|
+
|
|
4652
|
+
/**
|
|
4653
|
+
* Execution Tools for calling OpenAI API using Responses API
|
|
4654
|
+
*
|
|
4655
|
+
* @public exported from `@promptbook/openai`
|
|
4656
|
+
*/
|
|
4657
|
+
const createOpenAiAgentExecutionTools = Object.assign((options) => {
|
|
4658
|
+
if (($isRunningInBrowser() || $isRunningInWebWorker()) && !options.dangerouslyAllowBrowser) {
|
|
4659
|
+
options = { ...options, dangerouslyAllowBrowser: true };
|
|
4660
|
+
}
|
|
4661
|
+
return new OpenAiAgentExecutionTools(options);
|
|
4662
|
+
}, {
|
|
4663
|
+
packageName: '@promptbook/openai',
|
|
4664
|
+
className: 'OpenAiAgentExecutionTools',
|
|
4665
|
+
});
|
|
4666
|
+
|
|
4667
|
+
/**
|
|
4668
|
+
* Uploads files to OpenAI and returns their IDs
|
|
4669
|
+
*
|
|
4670
|
+
* @private utility for `OpenAiAssistantExecutionTools` and `OpenAiCompatibleExecutionTools`
|
|
4671
|
+
*/
|
|
4672
|
+
async function uploadFilesToOpenAi(client, files) {
|
|
4673
|
+
const fileIds = [];
|
|
4674
|
+
for (const file of files) {
|
|
4675
|
+
// Note: OpenAI API expects a File object or a ReadStream
|
|
4676
|
+
// In browser environment, we can pass the File object directly
|
|
4677
|
+
// In Node.js environment, we might need to convert it or use a different approach
|
|
4678
|
+
// But since `Prompt.files` already contains `File` objects, we try to pass them directly
|
|
4679
|
+
const uploadedFile = await client.files.create({
|
|
4680
|
+
file: file,
|
|
4681
|
+
purpose: 'assistants',
|
|
4682
|
+
});
|
|
4683
|
+
fileIds.push(uploadedFile.id);
|
|
4684
|
+
}
|
|
4685
|
+
return fileIds;
|
|
4686
|
+
}
|
|
4687
|
+
|
|
4407
4688
|
/**
|
|
4408
4689
|
* Execution Tools for calling OpenAI API Assistants
|
|
4409
4690
|
*
|
|
@@ -4417,6 +4698,7 @@ class OpenAiExecutionTools extends OpenAiCompatibleExecutionTools {
|
|
|
4417
4698
|
* - `RemoteAgent` - which is an `Agent` that connects to a Promptbook Agents Server
|
|
4418
4699
|
*
|
|
4419
4700
|
* @public exported from `@promptbook/openai`
|
|
4701
|
+
* @deprecated Use `OpenAiAgentExecutionTools` instead which uses the new OpenAI Responses API
|
|
4420
4702
|
*/
|
|
4421
4703
|
class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
|
|
4422
4704
|
/**
|
|
@@ -4455,7 +4737,7 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
|
|
|
4455
4737
|
* Calls OpenAI API to use a chat model with streaming.
|
|
4456
4738
|
*/
|
|
4457
4739
|
async callChatModelStream(prompt, onProgress) {
|
|
4458
|
-
var _a, _b, _c, _d;
|
|
4740
|
+
var _a, _b, _c, _d, _e, _f;
|
|
4459
4741
|
if (this.options.isVerbose) {
|
|
4460
4742
|
console.info('💬 OpenAI callChatModel call', { prompt });
|
|
4461
4743
|
}
|
|
@@ -4500,16 +4782,26 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
|
|
|
4500
4782
|
const threadMessages = [];
|
|
4501
4783
|
// TODO: [🈹] Maybe this should not be here but in other place, look at commit 39d705e75e5bcf7a818c3af36bc13e1c8475c30c
|
|
4502
4784
|
// Add previous messages from thread (if any)
|
|
4503
|
-
if ('thread' in prompt &&
|
|
4504
|
-
Array.isArray(prompt.thread)) {
|
|
4785
|
+
if ('thread' in prompt && Array.isArray(prompt.thread)) {
|
|
4505
4786
|
const previousMessages = prompt.thread.map((msg) => ({
|
|
4506
|
-
role: (msg.
|
|
4787
|
+
role: (msg.sender === 'assistant' ? 'assistant' : 'user'),
|
|
4507
4788
|
content: msg.content,
|
|
4508
4789
|
}));
|
|
4509
4790
|
threadMessages.push(...previousMessages);
|
|
4510
4791
|
}
|
|
4511
4792
|
// Always add the current user message
|
|
4512
|
-
|
|
4793
|
+
const currentUserMessage = {
|
|
4794
|
+
role: 'user',
|
|
4795
|
+
content: rawPromptContent,
|
|
4796
|
+
};
|
|
4797
|
+
if ('files' in prompt && Array.isArray(prompt.files) && prompt.files.length > 0) {
|
|
4798
|
+
const fileIds = await uploadFilesToOpenAi(client, prompt.files);
|
|
4799
|
+
currentUserMessage.attachments = fileIds.map((fileId) => ({
|
|
4800
|
+
file_id: fileId,
|
|
4801
|
+
tools: [{ type: 'file_search' }, { type: 'code_interpreter' }],
|
|
4802
|
+
}));
|
|
4803
|
+
}
|
|
4804
|
+
threadMessages.push(currentUserMessage);
|
|
4513
4805
|
// Check if tools are being used - if so, use non-streaming mode
|
|
4514
4806
|
const hasTools = modelRequirements.tools !== undefined && modelRequirements.tools.length > 0;
|
|
4515
4807
|
const start = $getCurrentDate();
|
|
@@ -4539,6 +4831,8 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
|
|
|
4539
4831
|
// Create thread and run
|
|
4540
4832
|
const threadAndRun = await client.beta.threads.createAndRun(rawRequest);
|
|
4541
4833
|
let run = threadAndRun;
|
|
4834
|
+
const completedToolCalls = [];
|
|
4835
|
+
const toolCallStartedAt = new Map();
|
|
4542
4836
|
// Poll until run completes or requires action
|
|
4543
4837
|
while (run.status === 'queued' || run.status === 'in_progress' || run.status === 'requires_action') {
|
|
4544
4838
|
if (run.status === 'requires_action' && ((_a = run.required_action) === null || _a === void 0 ? void 0 : _a.type) === 'submit_tool_outputs') {
|
|
@@ -4549,6 +4843,10 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
|
|
|
4549
4843
|
if (toolCall.type === 'function') {
|
|
4550
4844
|
const functionName = toolCall.function.name;
|
|
4551
4845
|
const functionArgs = JSON.parse(toolCall.function.arguments);
|
|
4846
|
+
const calledAt = $getCurrentDate();
|
|
4847
|
+
if (toolCall.id) {
|
|
4848
|
+
toolCallStartedAt.set(toolCall.id, calledAt);
|
|
4849
|
+
}
|
|
4552
4850
|
onProgress({
|
|
4553
4851
|
content: '',
|
|
4554
4852
|
modelName: 'assistant',
|
|
@@ -4563,6 +4861,7 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
|
|
|
4563
4861
|
arguments: toolCall.function.arguments,
|
|
4564
4862
|
result: '',
|
|
4565
4863
|
rawToolCall: toolCall,
|
|
4864
|
+
createdAt: calledAt,
|
|
4566
4865
|
},
|
|
4567
4866
|
],
|
|
4568
4867
|
});
|
|
@@ -4580,6 +4879,7 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
|
|
|
4580
4879
|
? executionTools.script
|
|
4581
4880
|
: [executionTools.script];
|
|
4582
4881
|
let functionResponse;
|
|
4882
|
+
let errors;
|
|
4583
4883
|
try {
|
|
4584
4884
|
const scriptTool = scriptTools[0]; // <- TODO: [🧠] Which script tool to use?
|
|
4585
4885
|
functionResponse = await scriptTool.execute({
|
|
@@ -4596,12 +4896,14 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
|
|
|
4596
4896
|
}
|
|
4597
4897
|
catch (error) {
|
|
4598
4898
|
assertsError(error);
|
|
4899
|
+
const serializedError = serializeError(error);
|
|
4900
|
+
errors = [serializedError];
|
|
4599
4901
|
functionResponse = spaceTrim$2((block) => `
|
|
4600
4902
|
|
|
4601
4903
|
The invoked tool \`${functionName}\` failed with error:
|
|
4602
4904
|
|
|
4603
4905
|
\`\`\`json
|
|
4604
|
-
${block(JSON.stringify(
|
|
4906
|
+
${block(JSON.stringify(serializedError, null, 4))}
|
|
4605
4907
|
\`\`\`
|
|
4606
4908
|
|
|
4607
4909
|
`);
|
|
@@ -4612,6 +4914,14 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
|
|
|
4612
4914
|
tool_call_id: toolCall.id,
|
|
4613
4915
|
output: functionResponse,
|
|
4614
4916
|
});
|
|
4917
|
+
completedToolCalls.push({
|
|
4918
|
+
name: functionName,
|
|
4919
|
+
arguments: toolCall.function.arguments,
|
|
4920
|
+
result: functionResponse,
|
|
4921
|
+
rawToolCall: toolCall,
|
|
4922
|
+
createdAt: toolCall.id ? toolCallStartedAt.get(toolCall.id) || calledAt : calledAt,
|
|
4923
|
+
errors,
|
|
4924
|
+
});
|
|
4615
4925
|
}
|
|
4616
4926
|
}
|
|
4617
4927
|
// Submit tool outputs
|
|
@@ -4651,6 +4961,7 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
|
|
|
4651
4961
|
rawPromptContent,
|
|
4652
4962
|
rawRequest,
|
|
4653
4963
|
rawResponse: { run, messages: messages.data },
|
|
4964
|
+
toolCalls: completedToolCalls.length > 0 ? completedToolCalls : undefined,
|
|
4654
4965
|
};
|
|
4655
4966
|
onProgress(finalChunk);
|
|
4656
4967
|
return exportJson({
|
|
@@ -4730,8 +5041,38 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
|
|
|
4730
5041
|
if (((_b = rawResponse[0].content[0]) === null || _b === void 0 ? void 0 : _b.type) !== 'text') {
|
|
4731
5042
|
throw new PipelineExecutionError(`There is NOT 'text' BUT ${(_c = rawResponse[0].content[0]) === null || _c === void 0 ? void 0 : _c.type} finalMessages content type from OpenAI`);
|
|
4732
5043
|
}
|
|
4733
|
-
|
|
4734
|
-
//
|
|
5044
|
+
let resultContent = (_d = rawResponse[0].content[0]) === null || _d === void 0 ? void 0 : _d.text.value;
|
|
5045
|
+
// Process annotations to replace file IDs with filenames
|
|
5046
|
+
if ((_e = rawResponse[0].content[0]) === null || _e === void 0 ? void 0 : _e.text.annotations) {
|
|
5047
|
+
const annotations = (_f = rawResponse[0].content[0]) === null || _f === void 0 ? void 0 : _f.text.annotations;
|
|
5048
|
+
// Map to store file ID -> filename to avoid duplicate requests
|
|
5049
|
+
const fileIdToName = new Map();
|
|
5050
|
+
for (const annotation of annotations) {
|
|
5051
|
+
if (annotation.type === 'file_citation') {
|
|
5052
|
+
const fileId = annotation.file_citation.file_id;
|
|
5053
|
+
let filename = fileIdToName.get(fileId);
|
|
5054
|
+
if (!filename) {
|
|
5055
|
+
try {
|
|
5056
|
+
const file = await client.files.retrieve(fileId);
|
|
5057
|
+
filename = file.filename;
|
|
5058
|
+
fileIdToName.set(fileId, filename);
|
|
5059
|
+
}
|
|
5060
|
+
catch (error) {
|
|
5061
|
+
console.error(`Failed to retrieve file info for ${fileId}`, error);
|
|
5062
|
+
// Fallback to "Source" or keep original if fetch fails
|
|
5063
|
+
filename = 'Source';
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
5066
|
+
if (filename && resultContent) {
|
|
5067
|
+
// Replace the citation marker with filename
|
|
5068
|
+
// Regex to match the second part of the citation: 【id†source】 -> 【id†filename】
|
|
5069
|
+
// Note: annotation.text contains the exact marker like 【4:0†source】
|
|
5070
|
+
const newText = annotation.text.replace(/†.*?】/, `†${filename}】`);
|
|
5071
|
+
resultContent = resultContent.replace(annotation.text, newText);
|
|
5072
|
+
}
|
|
5073
|
+
}
|
|
5074
|
+
}
|
|
5075
|
+
}
|
|
4735
5076
|
// eslint-disable-next-line prefer-const
|
|
4736
5077
|
complete = $getCurrentDate();
|
|
4737
5078
|
const usage = UNCERTAIN_USAGE;
|
|
@@ -4827,7 +5168,14 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
|
|
|
4827
5168
|
continue;
|
|
4828
5169
|
}
|
|
4829
5170
|
const buffer = await response.arrayBuffer();
|
|
4830
|
-
|
|
5171
|
+
let filename = source.split('/').pop() || 'downloaded-file';
|
|
5172
|
+
try {
|
|
5173
|
+
const url = new URL(source);
|
|
5174
|
+
filename = url.pathname.split('/').pop() || filename;
|
|
5175
|
+
}
|
|
5176
|
+
catch (error) {
|
|
5177
|
+
// Keep default filename
|
|
5178
|
+
}
|
|
4831
5179
|
const blob = new Blob([buffer]);
|
|
4832
5180
|
const file = new File([blob], filename);
|
|
4833
5181
|
fileStreams.push(file);
|
|
@@ -4928,7 +5276,14 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
|
|
|
4928
5276
|
continue;
|
|
4929
5277
|
}
|
|
4930
5278
|
const buffer = await response.arrayBuffer();
|
|
4931
|
-
|
|
5279
|
+
let filename = source.split('/').pop() || 'downloaded-file';
|
|
5280
|
+
try {
|
|
5281
|
+
const url = new URL(source);
|
|
5282
|
+
filename = url.pathname.split('/').pop() || filename;
|
|
5283
|
+
}
|
|
5284
|
+
catch (error) {
|
|
5285
|
+
// Keep default filename
|
|
5286
|
+
}
|
|
4932
5287
|
const blob = new Blob([buffer]);
|
|
4933
5288
|
const file = new File([blob], filename);
|
|
4934
5289
|
fileStreams.push(file);
|
|
@@ -5012,6 +5367,7 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
|
|
|
5012
5367
|
*/
|
|
5013
5368
|
const DISCRIMINANT = 'OPEN_AI_ASSISTANT_V1';
|
|
5014
5369
|
/**
|
|
5370
|
+
* TODO: !!!!! [✨🥚] Knowledge should work both with and without scrapers
|
|
5015
5371
|
* TODO: [🙎] In `OpenAiAssistantExecutionTools` Allow to create abstract assistants with `isCreatingNewAssistantsAllowed`
|
|
5016
5372
|
* TODO: [🧠][🧙♂️] Maybe there can be some wizard for those who want to use just OpenAI
|
|
5017
5373
|
* TODO: Maybe make custom OpenAiError
|
|
@@ -5455,5 +5811,5 @@ const _OpenAiCompatibleRegistration = $llmToolsRegister.register(createOpenAiCom
|
|
|
5455
5811
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
5456
5812
|
*/
|
|
5457
5813
|
|
|
5458
|
-
export { BOOK_LANGUAGE_VERSION, OPENAI_MODELS, OpenAiAssistantExecutionTools, OpenAiCompatibleExecutionTools, OpenAiExecutionTools, PROMPTBOOK_ENGINE_VERSION, _OpenAiAssistantRegistration, _OpenAiCompatibleRegistration, _OpenAiRegistration, createOpenAiAssistantExecutionTools, createOpenAiCompatibleExecutionTools, createOpenAiExecutionTools };
|
|
5814
|
+
export { BOOK_LANGUAGE_VERSION, OPENAI_MODELS, OpenAiAgentExecutionTools, OpenAiAssistantExecutionTools, OpenAiCompatibleExecutionTools, OpenAiExecutionTools, PROMPTBOOK_ENGINE_VERSION, _OpenAiAssistantRegistration, _OpenAiCompatibleRegistration, _OpenAiRegistration, createOpenAiAgentExecutionTools, createOpenAiAssistantExecutionTools, createOpenAiCompatibleExecutionTools, createOpenAiExecutionTools };
|
|
5459
5815
|
//# sourceMappingURL=index.es.js.map
|