@promptbook/openai 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
@@ -19,7 +19,7 @@ const BOOK_LANGUAGE_VERSION = '1.0.0';
19
19
  * @generated
20
20
  * @see https://github.com/webgptorg/promptbook
21
21
  */
22
- const PROMPTBOOK_ENGINE_VERSION = '0.101.0-13';
22
+ const PROMPTBOOK_ENGINE_VERSION = '0.101.0-15';
23
23
  /**
24
24
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
25
25
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -2012,6 +2012,62 @@ resultContent, rawResponse) {
2012
2012
  * TODO: [🤝] DRY Maybe some common abstraction between `computeOpenAiUsage` and `computeAnthropicClaudeUsage`
2013
2013
  */
2014
2014
 
2015
+ /**
2016
+ * Parses an OpenAI error message to identify which parameter is unsupported
2017
+ *
2018
+ * @param errorMessage The error message from OpenAI API
2019
+ * @returns The parameter name that is unsupported, or null if not an unsupported parameter error
2020
+ * @private utility of LLM Tools
2021
+ */
2022
+ function parseUnsupportedParameterError(errorMessage) {
2023
+ // Pattern to match "Unsupported value: 'parameter' does not support ..."
2024
+ const unsupportedValueMatch = errorMessage.match(/Unsupported value:\s*'([^']+)'\s*does not support/i);
2025
+ if (unsupportedValueMatch === null || unsupportedValueMatch === void 0 ? void 0 : unsupportedValueMatch[1]) {
2026
+ return unsupportedValueMatch[1];
2027
+ }
2028
+ // Pattern to match "'parameter' of type ... is not supported with this model"
2029
+ const parameterTypeMatch = errorMessage.match(/'([^']+)'\s*of type.*is not supported with this model/i);
2030
+ if (parameterTypeMatch === null || parameterTypeMatch === void 0 ? void 0 : parameterTypeMatch[1]) {
2031
+ return parameterTypeMatch[1];
2032
+ }
2033
+ return null;
2034
+ }
2035
+ /**
2036
+ * Creates a copy of model requirements with the specified parameter removed
2037
+ *
2038
+ * @param modelRequirements Original model requirements
2039
+ * @param unsupportedParameter The parameter to remove
2040
+ * @returns New model requirements without the unsupported parameter
2041
+ * @private utility of LLM Tools
2042
+ */
2043
+ function removeUnsupportedModelRequirement(modelRequirements, unsupportedParameter) {
2044
+ const newRequirements = { ...modelRequirements };
2045
+ // Map of parameter names that might appear in error messages to ModelRequirements properties
2046
+ const parameterMap = {
2047
+ 'temperature': 'temperature',
2048
+ 'max_tokens': 'maxTokens',
2049
+ 'maxTokens': 'maxTokens',
2050
+ 'seed': 'seed',
2051
+ };
2052
+ const propertyToRemove = parameterMap[unsupportedParameter];
2053
+ if (propertyToRemove && propertyToRemove in newRequirements) {
2054
+ delete newRequirements[propertyToRemove];
2055
+ }
2056
+ return newRequirements;
2057
+ }
2058
+ /**
2059
+ * Checks if an error is an "Unsupported value" error from OpenAI
2060
+ * @param error The error to check
2061
+ * @returns true if this is an unsupported parameter error
2062
+ * @private utility of LLM Tools
2063
+ */
2064
+ function isUnsupportedParameterError(error) {
2065
+ const errorMessage = error.message.toLowerCase();
2066
+ return errorMessage.includes('unsupported value:') ||
2067
+ errorMessage.includes('is not supported with this model') ||
2068
+ errorMessage.includes('does not support');
2069
+ }
2070
+
2015
2071
  /**
2016
2072
  * Execution Tools for calling OpenAI API or other OpenAI compatible provider
2017
2073
  *
@@ -2029,6 +2085,10 @@ class OpenAiCompatibleExecutionTools {
2029
2085
  * OpenAI API client.
2030
2086
  */
2031
2087
  this.client = null;
2088
+ /**
2089
+ * Tracks models and parameters that have already been retried to prevent infinite loops
2090
+ */
2091
+ this.retriedUnsupportedParameters = new Set();
2032
2092
  // TODO: Allow configuring rate limits via options
2033
2093
  this.limiter = new Bottleneck({
2034
2094
  minTime: 60000 / (this.options.maxRequestsPerMinute || DEFAULT_MAX_REQUESTS_PER_MINUTE),
@@ -2090,21 +2150,27 @@ class OpenAiCompatibleExecutionTools {
2090
2150
  * Calls OpenAI compatible API to use a chat model.
2091
2151
  */
2092
2152
  async callChatModel(prompt) {
2153
+ return this.callChatModelWithRetry(prompt, prompt.modelRequirements);
2154
+ }
2155
+ /**
2156
+ * Internal method that handles parameter retry for chat model calls
2157
+ */
2158
+ async callChatModelWithRetry(prompt, currentModelRequirements) {
2093
2159
  var _a;
2094
2160
  if (this.options.isVerbose) {
2095
- console.info(`💬 ${this.title} callChatModel call`, { prompt });
2161
+ console.info(`💬 ${this.title} callChatModel call`, { prompt, currentModelRequirements });
2096
2162
  }
2097
- const { content, parameters, modelRequirements, format } = prompt;
2163
+ const { content, parameters, format } = prompt;
2098
2164
  const client = await this.getClient();
2099
2165
  // TODO: [☂] Use here more modelRequirements
2100
- if (modelRequirements.modelVariant !== 'CHAT') {
2166
+ if (currentModelRequirements.modelVariant !== 'CHAT') {
2101
2167
  throw new PipelineExecutionError('Use callChatModel only for CHAT variant');
2102
2168
  }
2103
- const modelName = modelRequirements.modelName || this.getDefaultChatModel().modelName;
2169
+ const modelName = currentModelRequirements.modelName || this.getDefaultChatModel().modelName;
2104
2170
  const modelSettings = {
2105
2171
  model: modelName,
2106
- max_tokens: modelRequirements.maxTokens,
2107
- temperature: modelRequirements.temperature,
2172
+ max_tokens: currentModelRequirements.maxTokens,
2173
+ temperature: currentModelRequirements.temperature,
2108
2174
  // <- TODO: [🈁] Use `seed` here AND/OR use is `isDeterministic` for entire execution tools
2109
2175
  // <- Note: [🧆]
2110
2176
  }; // <- TODO: [💩] Guard here types better
@@ -2119,12 +2185,12 @@ class OpenAiCompatibleExecutionTools {
2119
2185
  const rawRequest = {
2120
2186
  ...modelSettings,
2121
2187
  messages: [
2122
- ...(modelRequirements.systemMessage === undefined
2188
+ ...(currentModelRequirements.systemMessage === undefined
2123
2189
  ? []
2124
2190
  : [
2125
2191
  {
2126
2192
  role: 'system',
2127
- content: modelRequirements.systemMessage,
2193
+ content: currentModelRequirements.systemMessage,
2128
2194
  },
2129
2195
  ]),
2130
2196
  {
@@ -2138,69 +2204,110 @@ class OpenAiCompatibleExecutionTools {
2138
2204
  if (this.options.isVerbose) {
2139
2205
  console.info(colors.bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
2140
2206
  }
2141
- const rawResponse = await this.limiter
2142
- .schedule(() => this.makeRequestWithRetry(() => client.chat.completions.create(rawRequest)))
2143
- .catch((error) => {
2144
- assertsError(error);
2207
+ try {
2208
+ const rawResponse = await this.limiter
2209
+ .schedule(() => this.makeRequestWithNetworkRetry(() => client.chat.completions.create(rawRequest)))
2210
+ .catch((error) => {
2211
+ assertsError(error);
2212
+ if (this.options.isVerbose) {
2213
+ console.info(colors.bgRed('error'), error);
2214
+ }
2215
+ throw error;
2216
+ });
2145
2217
  if (this.options.isVerbose) {
2146
- console.info(colors.bgRed('error'), error);
2218
+ console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
2147
2219
  }
2148
- throw error;
2149
- });
2150
- if (this.options.isVerbose) {
2151
- console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
2152
- }
2153
- const complete = $getCurrentDate();
2154
- if (!rawResponse.choices[0]) {
2155
- throw new PipelineExecutionError(`No choises from ${this.title}`);
2156
- }
2157
- if (rawResponse.choices.length > 1) {
2158
- // TODO: This should be maybe only warning
2159
- throw new PipelineExecutionError(`More than one choise from ${this.title}`);
2220
+ const complete = $getCurrentDate();
2221
+ if (!rawResponse.choices[0]) {
2222
+ throw new PipelineExecutionError(`No choises from ${this.title}`);
2223
+ }
2224
+ if (rawResponse.choices.length > 1) {
2225
+ // TODO: This should be maybe only warning
2226
+ throw new PipelineExecutionError(`More than one choise from ${this.title}`);
2227
+ }
2228
+ const resultContent = rawResponse.choices[0].message.content;
2229
+ const usage = this.computeUsage(content || '', resultContent || '', rawResponse);
2230
+ if (resultContent === null) {
2231
+ throw new PipelineExecutionError(`No response message from ${this.title}`);
2232
+ }
2233
+ return exportJson({
2234
+ name: 'promptResult',
2235
+ message: `Result of \`OpenAiCompatibleExecutionTools.callChatModel\``,
2236
+ order: [],
2237
+ value: {
2238
+ content: resultContent,
2239
+ modelName: rawResponse.model || modelName,
2240
+ timing: {
2241
+ start,
2242
+ complete,
2243
+ },
2244
+ usage,
2245
+ rawPromptContent,
2246
+ rawRequest,
2247
+ rawResponse,
2248
+ // <- [🗯]
2249
+ },
2250
+ });
2160
2251
  }
2161
- const resultContent = rawResponse.choices[0].message.content;
2162
- const usage = this.computeUsage(content || '', resultContent || '', rawResponse);
2163
- if (resultContent === null) {
2164
- throw new PipelineExecutionError(`No response message from ${this.title}`);
2252
+ catch (error) {
2253
+ assertsError(error);
2254
+ // Check if this is an unsupported parameter error
2255
+ if (!isUnsupportedParameterError(error)) {
2256
+ throw error;
2257
+ }
2258
+ // Parse which parameter is unsupported
2259
+ const unsupportedParameter = parseUnsupportedParameterError(error.message);
2260
+ if (!unsupportedParameter) {
2261
+ if (this.options.isVerbose) {
2262
+ console.warn(colors.bgYellow('Warning'), 'Could not parse unsupported parameter from error:', error.message);
2263
+ }
2264
+ throw error;
2265
+ }
2266
+ // Create a unique key for this model + parameter combination to prevent infinite loops
2267
+ const retryKey = `${modelName}-${unsupportedParameter}`;
2268
+ if (this.retriedUnsupportedParameters.has(retryKey)) {
2269
+ // Already retried this parameter, throw the error
2270
+ if (this.options.isVerbose) {
2271
+ console.warn(colors.bgRed('Error'), `Parameter '${unsupportedParameter}' for model '${modelName}' already retried once, throwing error:`, error.message);
2272
+ }
2273
+ throw error;
2274
+ }
2275
+ // Mark this parameter as retried
2276
+ this.retriedUnsupportedParameters.add(retryKey);
2277
+ // Log warning in verbose mode
2278
+ if (this.options.isVerbose) {
2279
+ console.warn(colors.bgYellow('Warning'), `Removing unsupported parameter '${unsupportedParameter}' for model '${modelName}' and retrying request`);
2280
+ }
2281
+ // Remove the unsupported parameter and retry
2282
+ const modifiedModelRequirements = removeUnsupportedModelRequirement(currentModelRequirements, unsupportedParameter);
2283
+ return this.callChatModelWithRetry(prompt, modifiedModelRequirements);
2165
2284
  }
2166
- return exportJson({
2167
- name: 'promptResult',
2168
- message: `Result of \`OpenAiCompatibleExecutionTools.callChatModel\``,
2169
- order: [],
2170
- value: {
2171
- content: resultContent,
2172
- modelName: rawResponse.model || modelName,
2173
- timing: {
2174
- start,
2175
- complete,
2176
- },
2177
- usage,
2178
- rawPromptContent,
2179
- rawRequest,
2180
- rawResponse,
2181
- // <- [🗯]
2182
- },
2183
- });
2184
2285
  }
2185
2286
  /**
2186
2287
  * Calls OpenAI API to use a complete model.
2187
2288
  */
2188
2289
  async callCompletionModel(prompt) {
2290
+ return this.callCompletionModelWithRetry(prompt, prompt.modelRequirements);
2291
+ }
2292
+ /**
2293
+ * Internal method that handles parameter retry for completion model calls
2294
+ */
2295
+ async callCompletionModelWithRetry(prompt, currentModelRequirements) {
2189
2296
  var _a;
2190
2297
  if (this.options.isVerbose) {
2191
- console.info(`🖋 ${this.title} callCompletionModel call`, { prompt });
2298
+ console.info(`🖋 ${this.title} callCompletionModel call`, { prompt, currentModelRequirements });
2192
2299
  }
2193
- const { content, parameters, modelRequirements } = prompt;
2300
+ const { content, parameters } = prompt;
2194
2301
  const client = await this.getClient();
2195
2302
  // TODO: [☂] Use here more modelRequirements
2196
- if (modelRequirements.modelVariant !== 'COMPLETION') {
2303
+ if (currentModelRequirements.modelVariant !== 'COMPLETION') {
2197
2304
  throw new PipelineExecutionError('Use callCompletionModel only for COMPLETION variant');
2198
2305
  }
2199
- const modelName = modelRequirements.modelName || this.getDefaultCompletionModel().modelName;
2306
+ const modelName = currentModelRequirements.modelName || this.getDefaultCompletionModel().modelName;
2200
2307
  const modelSettings = {
2201
2308
  model: modelName,
2202
- max_tokens: modelRequirements.maxTokens,
2203
- temperature: modelRequirements.temperature,
2309
+ max_tokens: currentModelRequirements.maxTokens,
2310
+ temperature: currentModelRequirements.temperature,
2204
2311
  // <- TODO: [🈁] Use `seed` here AND/OR use is `isDeterministic` for entire execution tools
2205
2312
  // <- Note: [🧆]
2206
2313
  };
@@ -2214,46 +2321,81 @@ class OpenAiCompatibleExecutionTools {
2214
2321
  if (this.options.isVerbose) {
2215
2322
  console.info(colors.bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
2216
2323
  }
2217
- const rawResponse = await this.limiter
2218
- .schedule(() => this.makeRequestWithRetry(() => client.completions.create(rawRequest)))
2219
- .catch((error) => {
2220
- assertsError(error);
2324
+ try {
2325
+ const rawResponse = await this.limiter
2326
+ .schedule(() => this.makeRequestWithNetworkRetry(() => client.completions.create(rawRequest)))
2327
+ .catch((error) => {
2328
+ assertsError(error);
2329
+ if (this.options.isVerbose) {
2330
+ console.info(colors.bgRed('error'), error);
2331
+ }
2332
+ throw error;
2333
+ });
2221
2334
  if (this.options.isVerbose) {
2222
- console.info(colors.bgRed('error'), error);
2335
+ console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
2223
2336
  }
2224
- throw error;
2225
- });
2226
- if (this.options.isVerbose) {
2227
- console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
2228
- }
2229
- const complete = $getCurrentDate();
2230
- if (!rawResponse.choices[0]) {
2231
- throw new PipelineExecutionError(`No choises from ${this.title}`);
2337
+ const complete = $getCurrentDate();
2338
+ if (!rawResponse.choices[0]) {
2339
+ throw new PipelineExecutionError(`No choises from ${this.title}`);
2340
+ }
2341
+ if (rawResponse.choices.length > 1) {
2342
+ // TODO: This should be maybe only warning
2343
+ throw new PipelineExecutionError(`More than one choise from ${this.title}`);
2344
+ }
2345
+ const resultContent = rawResponse.choices[0].text;
2346
+ const usage = this.computeUsage(content || '', resultContent || '', rawResponse);
2347
+ return exportJson({
2348
+ name: 'promptResult',
2349
+ message: `Result of \`OpenAiCompatibleExecutionTools.callCompletionModel\``,
2350
+ order: [],
2351
+ value: {
2352
+ content: resultContent,
2353
+ modelName: rawResponse.model || modelName,
2354
+ timing: {
2355
+ start,
2356
+ complete,
2357
+ },
2358
+ usage,
2359
+ rawPromptContent,
2360
+ rawRequest,
2361
+ rawResponse,
2362
+ // <- [🗯]
2363
+ },
2364
+ });
2232
2365
  }
2233
- if (rawResponse.choices.length > 1) {
2234
- // TODO: This should be maybe only warning
2235
- throw new PipelineExecutionError(`More than one choise from ${this.title}`);
2366
+ catch (error) {
2367
+ assertsError(error);
2368
+ // Check if this is an unsupported parameter error
2369
+ if (!isUnsupportedParameterError(error)) {
2370
+ throw error;
2371
+ }
2372
+ // Parse which parameter is unsupported
2373
+ const unsupportedParameter = parseUnsupportedParameterError(error.message);
2374
+ if (!unsupportedParameter) {
2375
+ if (this.options.isVerbose) {
2376
+ console.warn(colors.bgYellow('Warning'), 'Could not parse unsupported parameter from error:', error.message);
2377
+ }
2378
+ throw error;
2379
+ }
2380
+ // Create a unique key for this model + parameter combination to prevent infinite loops
2381
+ const retryKey = `${modelName}-${unsupportedParameter}`;
2382
+ if (this.retriedUnsupportedParameters.has(retryKey)) {
2383
+ // Already retried this parameter, throw the error
2384
+ if (this.options.isVerbose) {
2385
+ console.warn(colors.bgRed('Error'), `Parameter '${unsupportedParameter}' for model '${modelName}' already retried once, throwing error:`, error.message);
2386
+ }
2387
+ throw error;
2388
+ }
2389
+ // Mark this parameter as retried
2390
+ this.retriedUnsupportedParameters.add(retryKey);
2391
+ // Log warning in verbose mode
2392
+ if (this.options.isVerbose) {
2393
+ console.warn(colors.bgYellow('Warning'), `Removing unsupported parameter '${unsupportedParameter}' for model '${modelName}' and retrying request`);
2394
+ }
2395
+ // Remove the unsupported parameter and retry
2396
+ const modifiedModelRequirements = removeUnsupportedModelRequirement(currentModelRequirements, unsupportedParameter);
2397
+ return this.callCompletionModelWithRetry(prompt, modifiedModelRequirements);
2236
2398
  }
2237
- const resultContent = rawResponse.choices[0].text;
2238
- const usage = this.computeUsage(content || '', resultContent || '', rawResponse);
2239
- return exportJson({
2240
- name: 'promptResult',
2241
- message: `Result of \`OpenAiCompatibleExecutionTools.callCompletionModel\``,
2242
- order: [],
2243
- value: {
2244
- content: resultContent,
2245
- modelName: rawResponse.model || modelName,
2246
- timing: {
2247
- start,
2248
- complete,
2249
- },
2250
- usage,
2251
- rawPromptContent,
2252
- rawRequest,
2253
- rawResponse,
2254
- // <- [🗯]
2255
- },
2256
- });
2257
2399
  }
2258
2400
  /**
2259
2401
  * Calls OpenAI compatible API to use a embedding model
@@ -2279,7 +2421,7 @@ class OpenAiCompatibleExecutionTools {
2279
2421
  console.info(colors.bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
2280
2422
  }
2281
2423
  const rawResponse = await this.limiter
2282
- .schedule(() => this.makeRequestWithRetry(() => client.embeddings.create(rawRequest)))
2424
+ .schedule(() => this.makeRequestWithNetworkRetry(() => client.embeddings.create(rawRequest)))
2283
2425
  .catch((error) => {
2284
2426
  assertsError(error);
2285
2427
  if (this.options.isVerbose) {
@@ -2341,7 +2483,7 @@ class OpenAiCompatibleExecutionTools {
2341
2483
  /**
2342
2484
  * Makes a request with retry logic for network errors like ECONNRESET
2343
2485
  */
2344
- async makeRequestWithRetry(requestFn) {
2486
+ async makeRequestWithNetworkRetry(requestFn) {
2345
2487
  let lastError;
2346
2488
  for (let attempt = 1; attempt <= CONNECTION_RETRIES_LIMIT; attempt++) {
2347
2489
  try {
@@ -2353,8 +2495,8 @@ class OpenAiCompatibleExecutionTools {
2353
2495
  // Check if this is a retryable network error
2354
2496
  const isRetryableError = this.isRetryableNetworkError(error);
2355
2497
  if (!isRetryableError || attempt === CONNECTION_RETRIES_LIMIT) {
2356
- if (this.options.isVerbose) {
2357
- console.info(colors.bgRed('Final error after retries'), `Attempt ${attempt}/${CONNECTION_RETRIES_LIMIT}:`, error);
2498
+ if (this.options.isVerbose && this.isRetryableNetworkError(error)) {
2499
+ console.info(colors.bgRed('Final network error after retries'), `Attempt ${attempt}/${CONNECTION_RETRIES_LIMIT}:`, error);
2358
2500
  }
2359
2501
  throw error;
2360
2502
  }
@@ -2364,7 +2506,7 @@ class OpenAiCompatibleExecutionTools {
2364
2506
  const jitterDelay = Math.random() * 500; // Add some randomness
2365
2507
  const totalDelay = backoffDelay + jitterDelay;
2366
2508
  if (this.options.isVerbose) {
2367
- console.info(colors.bgYellow('Retrying request'), `Attempt ${attempt}/${CONNECTION_RETRIES_LIMIT}, waiting ${Math.round(totalDelay)}ms:`, error.message);
2509
+ console.info(colors.bgYellow('Retrying network request'), `Attempt ${attempt}/${CONNECTION_RETRIES_LIMIT}, waiting ${Math.round(totalDelay)}ms:`, error.message);
2368
2510
  }
2369
2511
  // Wait before retrying
2370
2512
  await new Promise((resolve) => setTimeout(resolve, totalDelay));