@promptbook/cli 0.101.0-13 → 0.101.0-15

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/esm/index.es.js CHANGED
@@ -45,7 +45,7 @@ const BOOK_LANGUAGE_VERSION = '1.0.0';
45
45
  * @generated
46
46
  * @see https://github.com/webgptorg/promptbook
47
47
  */
48
- const PROMPTBOOK_ENGINE_VERSION = '0.101.0-13';
48
+ const PROMPTBOOK_ENGINE_VERSION = '0.101.0-15';
49
49
  /**
50
50
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
51
51
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -7873,6 +7873,7 @@ async function preparePersona(personaDescription, tools, options) {
7873
7873
  };
7874
7874
  }
7875
7875
  /**
7876
+ * TODO: [😩] DRY `preparePersona` and `selectBestModelFromAvailable`
7876
7877
  * TODO: [🔃][main] If the persona was prepared with different version or different set of models, prepare it once again
7877
7878
  * TODO: [🏢] Check validity of `modelName` in pipeline
7878
7879
  * TODO: [🏢] Check validity of `systemMessage` in pipeline
@@ -17945,6 +17946,62 @@ resultContent, rawResponse) {
17945
17946
  * TODO: [🤝] DRY Maybe some common abstraction between `computeOpenAiUsage` and `computeAnthropicClaudeUsage`
17946
17947
  */
17947
17948
 
17949
+ /**
17950
+ * Parses an OpenAI error message to identify which parameter is unsupported
17951
+ *
17952
+ * @param errorMessage The error message from OpenAI API
17953
+ * @returns The parameter name that is unsupported, or null if not an unsupported parameter error
17954
+ * @private utility of LLM Tools
17955
+ */
17956
+ function parseUnsupportedParameterError(errorMessage) {
17957
+ // Pattern to match "Unsupported value: 'parameter' does not support ..."
17958
+ const unsupportedValueMatch = errorMessage.match(/Unsupported value:\s*'([^']+)'\s*does not support/i);
17959
+ if (unsupportedValueMatch === null || unsupportedValueMatch === void 0 ? void 0 : unsupportedValueMatch[1]) {
17960
+ return unsupportedValueMatch[1];
17961
+ }
17962
+ // Pattern to match "'parameter' of type ... is not supported with this model"
17963
+ const parameterTypeMatch = errorMessage.match(/'([^']+)'\s*of type.*is not supported with this model/i);
17964
+ if (parameterTypeMatch === null || parameterTypeMatch === void 0 ? void 0 : parameterTypeMatch[1]) {
17965
+ return parameterTypeMatch[1];
17966
+ }
17967
+ return null;
17968
+ }
17969
+ /**
17970
+ * Creates a copy of model requirements with the specified parameter removed
17971
+ *
17972
+ * @param modelRequirements Original model requirements
17973
+ * @param unsupportedParameter The parameter to remove
17974
+ * @returns New model requirements without the unsupported parameter
17975
+ * @private utility of LLM Tools
17976
+ */
17977
+ function removeUnsupportedModelRequirement(modelRequirements, unsupportedParameter) {
17978
+ const newRequirements = { ...modelRequirements };
17979
+ // Map of parameter names that might appear in error messages to ModelRequirements properties
17980
+ const parameterMap = {
17981
+ 'temperature': 'temperature',
17982
+ 'max_tokens': 'maxTokens',
17983
+ 'maxTokens': 'maxTokens',
17984
+ 'seed': 'seed',
17985
+ };
17986
+ const propertyToRemove = parameterMap[unsupportedParameter];
17987
+ if (propertyToRemove && propertyToRemove in newRequirements) {
17988
+ delete newRequirements[propertyToRemove];
17989
+ }
17990
+ return newRequirements;
17991
+ }
17992
+ /**
17993
+ * Checks if an error is an "Unsupported value" error from OpenAI
17994
+ * @param error The error to check
17995
+ * @returns true if this is an unsupported parameter error
17996
+ * @private utility of LLM Tools
17997
+ */
17998
+ function isUnsupportedParameterError(error) {
17999
+ const errorMessage = error.message.toLowerCase();
18000
+ return errorMessage.includes('unsupported value:') ||
18001
+ errorMessage.includes('is not supported with this model') ||
18002
+ errorMessage.includes('does not support');
18003
+ }
18004
+
17948
18005
  /**
17949
18006
  * Execution Tools for calling OpenAI API or other OpenAI compatible provider
17950
18007
  *
@@ -17962,6 +18019,10 @@ class OpenAiCompatibleExecutionTools {
17962
18019
  * OpenAI API client.
17963
18020
  */
17964
18021
  this.client = null;
18022
+ /**
18023
+ * Tracks models and parameters that have already been retried to prevent infinite loops
18024
+ */
18025
+ this.retriedUnsupportedParameters = new Set();
17965
18026
  // TODO: Allow configuring rate limits via options
17966
18027
  this.limiter = new Bottleneck({
17967
18028
  minTime: 60000 / (this.options.maxRequestsPerMinute || DEFAULT_MAX_REQUESTS_PER_MINUTE),
@@ -18023,21 +18084,27 @@ class OpenAiCompatibleExecutionTools {
18023
18084
  * Calls OpenAI compatible API to use a chat model.
18024
18085
  */
18025
18086
  async callChatModel(prompt) {
18087
+ return this.callChatModelWithRetry(prompt, prompt.modelRequirements);
18088
+ }
18089
+ /**
18090
+ * Internal method that handles parameter retry for chat model calls
18091
+ */
18092
+ async callChatModelWithRetry(prompt, currentModelRequirements) {
18026
18093
  var _a;
18027
18094
  if (this.options.isVerbose) {
18028
- console.info(`💬 ${this.title} callChatModel call`, { prompt });
18095
+ console.info(`💬 ${this.title} callChatModel call`, { prompt, currentModelRequirements });
18029
18096
  }
18030
- const { content, parameters, modelRequirements, format } = prompt;
18097
+ const { content, parameters, format } = prompt;
18031
18098
  const client = await this.getClient();
18032
18099
  // TODO: [☂] Use here more modelRequirements
18033
- if (modelRequirements.modelVariant !== 'CHAT') {
18100
+ if (currentModelRequirements.modelVariant !== 'CHAT') {
18034
18101
  throw new PipelineExecutionError('Use callChatModel only for CHAT variant');
18035
18102
  }
18036
- const modelName = modelRequirements.modelName || this.getDefaultChatModel().modelName;
18103
+ const modelName = currentModelRequirements.modelName || this.getDefaultChatModel().modelName;
18037
18104
  const modelSettings = {
18038
18105
  model: modelName,
18039
- max_tokens: modelRequirements.maxTokens,
18040
- temperature: modelRequirements.temperature,
18106
+ max_tokens: currentModelRequirements.maxTokens,
18107
+ temperature: currentModelRequirements.temperature,
18041
18108
  // <- TODO: [🈁] Use `seed` here AND/OR use is `isDeterministic` for entire execution tools
18042
18109
  // <- Note: [🧆]
18043
18110
  }; // <- TODO: [💩] Guard here types better
@@ -18052,12 +18119,12 @@ class OpenAiCompatibleExecutionTools {
18052
18119
  const rawRequest = {
18053
18120
  ...modelSettings,
18054
18121
  messages: [
18055
- ...(modelRequirements.systemMessage === undefined
18122
+ ...(currentModelRequirements.systemMessage === undefined
18056
18123
  ? []
18057
18124
  : [
18058
18125
  {
18059
18126
  role: 'system',
18060
- content: modelRequirements.systemMessage,
18127
+ content: currentModelRequirements.systemMessage,
18061
18128
  },
18062
18129
  ]),
18063
18130
  {
@@ -18071,69 +18138,110 @@ class OpenAiCompatibleExecutionTools {
18071
18138
  if (this.options.isVerbose) {
18072
18139
  console.info(colors.bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
18073
18140
  }
18074
- const rawResponse = await this.limiter
18075
- .schedule(() => this.makeRequestWithRetry(() => client.chat.completions.create(rawRequest)))
18076
- .catch((error) => {
18077
- assertsError(error);
18141
+ try {
18142
+ const rawResponse = await this.limiter
18143
+ .schedule(() => this.makeRequestWithNetworkRetry(() => client.chat.completions.create(rawRequest)))
18144
+ .catch((error) => {
18145
+ assertsError(error);
18146
+ if (this.options.isVerbose) {
18147
+ console.info(colors.bgRed('error'), error);
18148
+ }
18149
+ throw error;
18150
+ });
18078
18151
  if (this.options.isVerbose) {
18079
- console.info(colors.bgRed('error'), error);
18152
+ console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
18080
18153
  }
18081
- throw error;
18082
- });
18083
- if (this.options.isVerbose) {
18084
- console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
18085
- }
18086
- const complete = $getCurrentDate();
18087
- if (!rawResponse.choices[0]) {
18088
- throw new PipelineExecutionError(`No choises from ${this.title}`);
18089
- }
18090
- if (rawResponse.choices.length > 1) {
18091
- // TODO: This should be maybe only warning
18092
- throw new PipelineExecutionError(`More than one choise from ${this.title}`);
18154
+ const complete = $getCurrentDate();
18155
+ if (!rawResponse.choices[0]) {
18156
+ throw new PipelineExecutionError(`No choises from ${this.title}`);
18157
+ }
18158
+ if (rawResponse.choices.length > 1) {
18159
+ // TODO: This should be maybe only warning
18160
+ throw new PipelineExecutionError(`More than one choise from ${this.title}`);
18161
+ }
18162
+ const resultContent = rawResponse.choices[0].message.content;
18163
+ const usage = this.computeUsage(content || '', resultContent || '', rawResponse);
18164
+ if (resultContent === null) {
18165
+ throw new PipelineExecutionError(`No response message from ${this.title}`);
18166
+ }
18167
+ return exportJson({
18168
+ name: 'promptResult',
18169
+ message: `Result of \`OpenAiCompatibleExecutionTools.callChatModel\``,
18170
+ order: [],
18171
+ value: {
18172
+ content: resultContent,
18173
+ modelName: rawResponse.model || modelName,
18174
+ timing: {
18175
+ start,
18176
+ complete,
18177
+ },
18178
+ usage,
18179
+ rawPromptContent,
18180
+ rawRequest,
18181
+ rawResponse,
18182
+ // <- [🗯]
18183
+ },
18184
+ });
18093
18185
  }
18094
- const resultContent = rawResponse.choices[0].message.content;
18095
- const usage = this.computeUsage(content || '', resultContent || '', rawResponse);
18096
- if (resultContent === null) {
18097
- throw new PipelineExecutionError(`No response message from ${this.title}`);
18186
+ catch (error) {
18187
+ assertsError(error);
18188
+ // Check if this is an unsupported parameter error
18189
+ if (!isUnsupportedParameterError(error)) {
18190
+ throw error;
18191
+ }
18192
+ // Parse which parameter is unsupported
18193
+ const unsupportedParameter = parseUnsupportedParameterError(error.message);
18194
+ if (!unsupportedParameter) {
18195
+ if (this.options.isVerbose) {
18196
+ console.warn(colors.bgYellow('Warning'), 'Could not parse unsupported parameter from error:', error.message);
18197
+ }
18198
+ throw error;
18199
+ }
18200
+ // Create a unique key for this model + parameter combination to prevent infinite loops
18201
+ const retryKey = `${modelName}-${unsupportedParameter}`;
18202
+ if (this.retriedUnsupportedParameters.has(retryKey)) {
18203
+ // Already retried this parameter, throw the error
18204
+ if (this.options.isVerbose) {
18205
+ console.warn(colors.bgRed('Error'), `Parameter '${unsupportedParameter}' for model '${modelName}' already retried once, throwing error:`, error.message);
18206
+ }
18207
+ throw error;
18208
+ }
18209
+ // Mark this parameter as retried
18210
+ this.retriedUnsupportedParameters.add(retryKey);
18211
+ // Log warning in verbose mode
18212
+ if (this.options.isVerbose) {
18213
+ console.warn(colors.bgYellow('Warning'), `Removing unsupported parameter '${unsupportedParameter}' for model '${modelName}' and retrying request`);
18214
+ }
18215
+ // Remove the unsupported parameter and retry
18216
+ const modifiedModelRequirements = removeUnsupportedModelRequirement(currentModelRequirements, unsupportedParameter);
18217
+ return this.callChatModelWithRetry(prompt, modifiedModelRequirements);
18098
18218
  }
18099
- return exportJson({
18100
- name: 'promptResult',
18101
- message: `Result of \`OpenAiCompatibleExecutionTools.callChatModel\``,
18102
- order: [],
18103
- value: {
18104
- content: resultContent,
18105
- modelName: rawResponse.model || modelName,
18106
- timing: {
18107
- start,
18108
- complete,
18109
- },
18110
- usage,
18111
- rawPromptContent,
18112
- rawRequest,
18113
- rawResponse,
18114
- // <- [🗯]
18115
- },
18116
- });
18117
18219
  }
18118
18220
  /**
18119
18221
  * Calls OpenAI API to use a complete model.
18120
18222
  */
18121
18223
  async callCompletionModel(prompt) {
18224
+ return this.callCompletionModelWithRetry(prompt, prompt.modelRequirements);
18225
+ }
18226
+ /**
18227
+ * Internal method that handles parameter retry for completion model calls
18228
+ */
18229
+ async callCompletionModelWithRetry(prompt, currentModelRequirements) {
18122
18230
  var _a;
18123
18231
  if (this.options.isVerbose) {
18124
- console.info(`🖋 ${this.title} callCompletionModel call`, { prompt });
18232
+ console.info(`🖋 ${this.title} callCompletionModel call`, { prompt, currentModelRequirements });
18125
18233
  }
18126
- const { content, parameters, modelRequirements } = prompt;
18234
+ const { content, parameters } = prompt;
18127
18235
  const client = await this.getClient();
18128
18236
  // TODO: [☂] Use here more modelRequirements
18129
- if (modelRequirements.modelVariant !== 'COMPLETION') {
18237
+ if (currentModelRequirements.modelVariant !== 'COMPLETION') {
18130
18238
  throw new PipelineExecutionError('Use callCompletionModel only for COMPLETION variant');
18131
18239
  }
18132
- const modelName = modelRequirements.modelName || this.getDefaultCompletionModel().modelName;
18240
+ const modelName = currentModelRequirements.modelName || this.getDefaultCompletionModel().modelName;
18133
18241
  const modelSettings = {
18134
18242
  model: modelName,
18135
- max_tokens: modelRequirements.maxTokens,
18136
- temperature: modelRequirements.temperature,
18243
+ max_tokens: currentModelRequirements.maxTokens,
18244
+ temperature: currentModelRequirements.temperature,
18137
18245
  // <- TODO: [🈁] Use `seed` here AND/OR use is `isDeterministic` for entire execution tools
18138
18246
  // <- Note: [🧆]
18139
18247
  };
@@ -18147,46 +18255,81 @@ class OpenAiCompatibleExecutionTools {
18147
18255
  if (this.options.isVerbose) {
18148
18256
  console.info(colors.bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
18149
18257
  }
18150
- const rawResponse = await this.limiter
18151
- .schedule(() => this.makeRequestWithRetry(() => client.completions.create(rawRequest)))
18152
- .catch((error) => {
18153
- assertsError(error);
18258
+ try {
18259
+ const rawResponse = await this.limiter
18260
+ .schedule(() => this.makeRequestWithNetworkRetry(() => client.completions.create(rawRequest)))
18261
+ .catch((error) => {
18262
+ assertsError(error);
18263
+ if (this.options.isVerbose) {
18264
+ console.info(colors.bgRed('error'), error);
18265
+ }
18266
+ throw error;
18267
+ });
18154
18268
  if (this.options.isVerbose) {
18155
- console.info(colors.bgRed('error'), error);
18269
+ console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
18156
18270
  }
18157
- throw error;
18158
- });
18159
- if (this.options.isVerbose) {
18160
- console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
18161
- }
18162
- const complete = $getCurrentDate();
18163
- if (!rawResponse.choices[0]) {
18164
- throw new PipelineExecutionError(`No choises from ${this.title}`);
18271
+ const complete = $getCurrentDate();
18272
+ if (!rawResponse.choices[0]) {
18273
+ throw new PipelineExecutionError(`No choises from ${this.title}`);
18274
+ }
18275
+ if (rawResponse.choices.length > 1) {
18276
+ // TODO: This should be maybe only warning
18277
+ throw new PipelineExecutionError(`More than one choise from ${this.title}`);
18278
+ }
18279
+ const resultContent = rawResponse.choices[0].text;
18280
+ const usage = this.computeUsage(content || '', resultContent || '', rawResponse);
18281
+ return exportJson({
18282
+ name: 'promptResult',
18283
+ message: `Result of \`OpenAiCompatibleExecutionTools.callCompletionModel\``,
18284
+ order: [],
18285
+ value: {
18286
+ content: resultContent,
18287
+ modelName: rawResponse.model || modelName,
18288
+ timing: {
18289
+ start,
18290
+ complete,
18291
+ },
18292
+ usage,
18293
+ rawPromptContent,
18294
+ rawRequest,
18295
+ rawResponse,
18296
+ // <- [🗯]
18297
+ },
18298
+ });
18165
18299
  }
18166
- if (rawResponse.choices.length > 1) {
18167
- // TODO: This should be maybe only warning
18168
- throw new PipelineExecutionError(`More than one choise from ${this.title}`);
18300
+ catch (error) {
18301
+ assertsError(error);
18302
+ // Check if this is an unsupported parameter error
18303
+ if (!isUnsupportedParameterError(error)) {
18304
+ throw error;
18305
+ }
18306
+ // Parse which parameter is unsupported
18307
+ const unsupportedParameter = parseUnsupportedParameterError(error.message);
18308
+ if (!unsupportedParameter) {
18309
+ if (this.options.isVerbose) {
18310
+ console.warn(colors.bgYellow('Warning'), 'Could not parse unsupported parameter from error:', error.message);
18311
+ }
18312
+ throw error;
18313
+ }
18314
+ // Create a unique key for this model + parameter combination to prevent infinite loops
18315
+ const retryKey = `${modelName}-${unsupportedParameter}`;
18316
+ if (this.retriedUnsupportedParameters.has(retryKey)) {
18317
+ // Already retried this parameter, throw the error
18318
+ if (this.options.isVerbose) {
18319
+ console.warn(colors.bgRed('Error'), `Parameter '${unsupportedParameter}' for model '${modelName}' already retried once, throwing error:`, error.message);
18320
+ }
18321
+ throw error;
18322
+ }
18323
+ // Mark this parameter as retried
18324
+ this.retriedUnsupportedParameters.add(retryKey);
18325
+ // Log warning in verbose mode
18326
+ if (this.options.isVerbose) {
18327
+ console.warn(colors.bgYellow('Warning'), `Removing unsupported parameter '${unsupportedParameter}' for model '${modelName}' and retrying request`);
18328
+ }
18329
+ // Remove the unsupported parameter and retry
18330
+ const modifiedModelRequirements = removeUnsupportedModelRequirement(currentModelRequirements, unsupportedParameter);
18331
+ return this.callCompletionModelWithRetry(prompt, modifiedModelRequirements);
18169
18332
  }
18170
- const resultContent = rawResponse.choices[0].text;
18171
- const usage = this.computeUsage(content || '', resultContent || '', rawResponse);
18172
- return exportJson({
18173
- name: 'promptResult',
18174
- message: `Result of \`OpenAiCompatibleExecutionTools.callCompletionModel\``,
18175
- order: [],
18176
- value: {
18177
- content: resultContent,
18178
- modelName: rawResponse.model || modelName,
18179
- timing: {
18180
- start,
18181
- complete,
18182
- },
18183
- usage,
18184
- rawPromptContent,
18185
- rawRequest,
18186
- rawResponse,
18187
- // <- [🗯]
18188
- },
18189
- });
18190
18333
  }
18191
18334
  /**
18192
18335
  * Calls OpenAI compatible API to use a embedding model
@@ -18212,7 +18355,7 @@ class OpenAiCompatibleExecutionTools {
18212
18355
  console.info(colors.bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
18213
18356
  }
18214
18357
  const rawResponse = await this.limiter
18215
- .schedule(() => this.makeRequestWithRetry(() => client.embeddings.create(rawRequest)))
18358
+ .schedule(() => this.makeRequestWithNetworkRetry(() => client.embeddings.create(rawRequest)))
18216
18359
  .catch((error) => {
18217
18360
  assertsError(error);
18218
18361
  if (this.options.isVerbose) {
@@ -18274,7 +18417,7 @@ class OpenAiCompatibleExecutionTools {
18274
18417
  /**
18275
18418
  * Makes a request with retry logic for network errors like ECONNRESET
18276
18419
  */
18277
- async makeRequestWithRetry(requestFn) {
18420
+ async makeRequestWithNetworkRetry(requestFn) {
18278
18421
  let lastError;
18279
18422
  for (let attempt = 1; attempt <= CONNECTION_RETRIES_LIMIT; attempt++) {
18280
18423
  try {
@@ -18286,8 +18429,8 @@ class OpenAiCompatibleExecutionTools {
18286
18429
  // Check if this is a retryable network error
18287
18430
  const isRetryableError = this.isRetryableNetworkError(error);
18288
18431
  if (!isRetryableError || attempt === CONNECTION_RETRIES_LIMIT) {
18289
- if (this.options.isVerbose) {
18290
- console.info(colors.bgRed('Final error after retries'), `Attempt ${attempt}/${CONNECTION_RETRIES_LIMIT}:`, error);
18432
+ if (this.options.isVerbose && this.isRetryableNetworkError(error)) {
18433
+ console.info(colors.bgRed('Final network error after retries'), `Attempt ${attempt}/${CONNECTION_RETRIES_LIMIT}:`, error);
18291
18434
  }
18292
18435
  throw error;
18293
18436
  }
@@ -18297,7 +18440,7 @@ class OpenAiCompatibleExecutionTools {
18297
18440
  const jitterDelay = Math.random() * 500; // Add some randomness
18298
18441
  const totalDelay = backoffDelay + jitterDelay;
18299
18442
  if (this.options.isVerbose) {
18300
- console.info(colors.bgYellow('Retrying request'), `Attempt ${attempt}/${CONNECTION_RETRIES_LIMIT}, waiting ${Math.round(totalDelay)}ms:`, error.message);
18443
+ console.info(colors.bgYellow('Retrying network request'), `Attempt ${attempt}/${CONNECTION_RETRIES_LIMIT}, waiting ${Math.round(totalDelay)}ms:`, error.message);
18301
18444
  }
18302
18445
  // Wait before retrying
18303
18446
  await new Promise((resolve) => setTimeout(resolve, totalDelay));