@promptbook/cli 0.92.0-31 → 0.92.0-32

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
@@ -26,8 +26,8 @@ import http from 'http';
26
26
  import { Server } from 'socket.io';
27
27
  import swaggerUi from 'swagger-ui-express';
28
28
  import Anthropic from '@anthropic-ai/sdk';
29
- import { OpenAIClient, AzureKeyCredential } from '@azure/openai';
30
29
  import Bottleneck from 'bottleneck';
30
+ import { OpenAIClient, AzureKeyCredential } from '@azure/openai';
31
31
  import OpenAI from 'openai';
32
32
  import { Readability } from '@mozilla/readability';
33
33
  import { JSDOM } from 'jsdom';
@@ -47,7 +47,7 @@ const BOOK_LANGUAGE_VERSION = '1.0.0';
47
47
  * @generated
48
48
  * @see https://github.com/webgptorg/promptbook
49
49
  */
50
- const PROMPTBOOK_ENGINE_VERSION = '0.92.0-31';
50
+ const PROMPTBOOK_ENGINE_VERSION = '0.92.0-32';
51
51
  /**
52
52
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
53
53
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -3119,23 +3119,17 @@ class MultipleLlmExecutionTools {
3119
3119
  * Check the configuration of all execution tools
3120
3120
  */
3121
3121
  async checkConfiguration() {
3122
- // TODO: Maybe do it in parallel
3123
- for (const llmExecutionTools of this.llmExecutionTools) {
3124
- await llmExecutionTools.checkConfiguration();
3125
- }
3122
+ // Note: Run checks in parallel
3123
+ await Promise.all(this.llmExecutionTools.map((tools) => tools.checkConfiguration()));
3126
3124
  }
3127
3125
  /**
3128
3126
  * List all available models that can be used
3129
3127
  * This lists is a combination of all available models from all execution tools
3130
3128
  */
3131
3129
  async listModels() {
3132
- const availableModels = [];
3133
- for (const llmExecutionTools of this.llmExecutionTools) {
3134
- // TODO: [🪂] Obtain models in parallel
3135
- const models = await llmExecutionTools.listModels();
3136
- availableModels.push(...models);
3137
- }
3138
- return availableModels;
3130
+ // Obtain all models in parallel and flatten
3131
+ const modelArrays = await Promise.all(this.llmExecutionTools.map((tools) => tools.listModels()));
3132
+ return modelArrays.flat();
3139
3133
  }
3140
3134
  /**
3141
3135
  * Calls the best available chat model
@@ -15249,6 +15243,8 @@ class AnthropicClaudeExecutionTools {
15249
15243
  * Anthropic Claude API client.
15250
15244
  */
15251
15245
  this.client = null;
15246
+ const rate = this.options.maxRequestsPerMinute || DEFAULT_MAX_REQUESTS_PER_MINUTE;
15247
+ this.limiter = new Bottleneck({ minTime: 60000 / rate });
15252
15248
  }
15253
15249
  get title() {
15254
15250
  return 'Anthropic Claude';
@@ -15300,8 +15296,6 @@ class AnthropicClaudeExecutionTools {
15300
15296
  // <- TODO: [🌾] Make some global max cap for maxTokens
15301
15297
  temperature: modelRequirements.temperature,
15302
15298
  system: modelRequirements.systemMessage,
15303
- // <- TODO: [🈁] Use `seed` here AND/OR use is `isDeterministic` for entire execution tools
15304
- // <- Note: [🧆]
15305
15299
  messages: [
15306
15300
  {
15307
15301
  role: 'user',
@@ -15310,14 +15304,14 @@ class AnthropicClaudeExecutionTools {
15310
15304
  // @see https://docs.anthropic.com/en/docs/test-and-evaluate/strengthen-guardrails/increase-consistency#specify-the-desired-output-format
15311
15305
  },
15312
15306
  ],
15313
- // TODO: Is here some equivalent of user identification?> user: this.options.user,
15314
15307
  };
15315
15308
  const start = $getCurrentDate();
15316
- let complete;
15317
15309
  if (this.options.isVerbose) {
15318
15310
  console.info(colors.bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
15319
15311
  }
15320
- const rawResponse = await client.messages.create(rawRequest).catch((error) => {
15312
+ const rawResponse = await this.limiter
15313
+ .schedule(() => client.messages.create(rawRequest))
15314
+ .catch((error) => {
15321
15315
  if (this.options.isVerbose) {
15322
15316
  console.info(colors.bgRed('error'), error);
15323
15317
  }
@@ -15337,12 +15331,11 @@ class AnthropicClaudeExecutionTools {
15337
15331
  throw new PipelineExecutionError(`Returned content is not "text" type but "${contentBlock.type}"`);
15338
15332
  }
15339
15333
  const resultContent = contentBlock.text;
15340
- // eslint-disable-next-line prefer-const
15341
- complete = $getCurrentDate();
15334
+ const complete = $getCurrentDate();
15342
15335
  const usage = computeAnthropicClaudeUsage(rawPromptContent || '', resultContent || '', rawResponse);
15343
15336
  return exportJson({
15344
15337
  name: 'promptResult',
15345
- message: `Result of \`AzureOpenAiExecutionTools.callChatModel\``,
15338
+ message: `Result of \`AnthropicClaudeExecutionTools.callChatModel\``,
15346
15339
  order: [],
15347
15340
  value: {
15348
15341
  content: resultContent,
@@ -15359,83 +15352,59 @@ class AnthropicClaudeExecutionTools {
15359
15352
  },
15360
15353
  });
15361
15354
  }
15362
- /*
15363
- TODO: [👏]
15364
- public async callCompletionModel(
15365
- prompt: Pick<Prompt, 'content' | 'parameters' | 'modelRequirements'>,
15366
- ): Promise<CompletionPromptResult> {
15367
-
15355
+ /**
15356
+ * Calls Anthropic Claude API to use a completion model.
15357
+ */
15358
+ async callCompletionModel(prompt) {
15368
15359
  if (this.options.isVerbose) {
15369
15360
  console.info('🖋 Anthropic Claude callCompletionModel call');
15370
15361
  }
15371
-
15372
15362
  const { content, parameters, modelRequirements } = prompt;
15373
-
15374
- // TODO: [☂] Use here more modelRequirements
15375
15363
  if (modelRequirements.modelVariant !== 'COMPLETION') {
15376
15364
  throw new PipelineExecutionError('Use callCompletionModel only for COMPLETION variant');
15377
15365
  }
15378
-
15366
+ const client = await this.getClient();
15379
15367
  const modelName = modelRequirements.modelName || this.getDefaultChatModel().modelName;
15380
- const modelSettings = {
15368
+ const rawPromptContent = templateParameters(content, { ...parameters, modelName });
15369
+ const rawRequest = {
15381
15370
  model: modelName,
15382
- max_tokens: modelRequirements.maxTokens || 2000, // <- Note: 2000 is for lagacy reasons
15383
- // <- TODO: [🌾] Make some global max cap for maxTokens
15384
- // <- TODO: Use here `systemMessage`, `temperature` and `seed`
15385
- };
15386
-
15387
- const rawRequest: xxxx.Completions.CompletionCreateParamsNonStreaming = {
15388
- ...modelSettings,
15371
+ max_tokens_to_sample: modelRequirements.maxTokens || 2000,
15372
+ temperature: modelRequirements.temperature,
15389
15373
  prompt: rawPromptContent,
15390
- user: this.options.user,
15391
15374
  };
15392
- const start: string_date_iso8601 = $getCurrentDate();
15393
- let complete: string_date_iso8601;
15394
-
15395
- if (this.options.isVerbose) {
15396
- console.info(colors.bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
15397
- }
15398
- const rawResponse = await this.client.completions.create(rawRequest).catch((error) => {
15399
- if (this.options.isVerbose) {
15400
- console.info(colors.bgRed('error'), error);
15401
- }
15402
- throw error;
15403
- });
15404
-
15405
-
15375
+ const start = $getCurrentDate();
15376
+ const rawResponse = await this.limiter
15377
+ .schedule(() => client.completions.create(rawRequest))
15378
+ .catch((error) => {
15379
+ if (this.options.isVerbose) {
15380
+ console.info(colors.bgRed('error'), error);
15381
+ }
15382
+ throw error;
15383
+ });
15406
15384
  if (this.options.isVerbose) {
15407
15385
  console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
15408
15386
  }
15409
-
15410
- if (!rawResponse.choices[0]) {
15411
- throw new PipelineExecutionError('No choises from Anthropic Claude');
15387
+ if (!rawResponse.completion) {
15388
+ throw new PipelineExecutionError('No completion from Anthropic Claude');
15412
15389
  }
15413
-
15414
- if (rawResponse.choices.length > 1) {
15415
- // TODO: This should be maybe only warning
15416
- throw new PipelineExecutionError('More than one choise from Anthropic Claude');
15417
- }
15418
-
15419
- const resultContent = rawResponse.choices[0].text;
15420
- // eslint-disable-next-line prefer-const
15421
- complete = $getCurrentDate();
15422
- const usage = { price: 'UNKNOWN', inputTokens: 0, outputTokens: 0 /* <- TODO: [🐞] Compute usage * / } satisfies Usage;
15423
-
15424
-
15425
-
15426
- return $exportJson({ name: 'promptResult',message: Result of \`AzureOpenAiExecutionTools callChatModel\`, order: [],value:{
15427
- content: resultContent,
15428
- modelName: rawResponse.model || model,
15429
- timing: {
15430
- start,
15431
- complete,
15390
+ const resultContent = rawResponse.completion;
15391
+ const complete = $getCurrentDate();
15392
+ const usage = computeAnthropicClaudeUsage(rawPromptContent, resultContent, rawResponse);
15393
+ return exportJson({
15394
+ name: 'promptResult',
15395
+ message: `Result of \`AnthropicClaudeExecutionTools.callCompletionModel\``,
15396
+ order: [],
15397
+ value: {
15398
+ content: resultContent,
15399
+ modelName: rawResponse.model || modelName,
15400
+ timing: { start, complete },
15401
+ usage,
15402
+ rawPromptContent,
15403
+ rawRequest,
15404
+ rawResponse,
15432
15405
  },
15433
- usage,
15434
- rawResponse,
15435
- // <- [🗯]
15436
15406
  });
15437
15407
  }
15438
- */
15439
15408
  // <- Note: [🤖] callXxxModel
15440
15409
  /**
15441
15410
  * Get the model that should be used as default
@@ -15444,7 +15413,7 @@ class AnthropicClaudeExecutionTools {
15444
15413
  const model = ANTHROPIC_CLAUDE_MODELS.find(({ modelName }) => modelName.startsWith(defaultModelName));
15445
15414
  if (model === undefined) {
15446
15415
  throw new UnexpectedError(spaceTrim((block) => `
15447
- Cannot find model in OpenAI models with name "${defaultModelName}" which should be used as default.
15416
+ Cannot find model in Anthropic Claude models with name "${defaultModelName}" which should be used as default.
15448
15417
 
15449
15418
  Available models:
15450
15419
  ${block(ANTHROPIC_CLAUDE_MODELS.map(({ modelName }) => `- "${modelName}"`).join('\n'))}
@@ -17078,11 +17047,9 @@ function computeOpenAiUsage(promptContent, // <- Note: Intentionally using [] to
17078
17047
  resultContent, rawResponse) {
17079
17048
  var _a, _b;
17080
17049
  if (rawResponse.usage === undefined) {
17081
- console.log('!!! computeOpenAiUsage', 'The usage is not defined in the response from OpenAI');
17082
17050
  throw new PipelineExecutionError('The usage is not defined in the response from OpenAI');
17083
17051
  }
17084
17052
  if (((_a = rawResponse.usage) === null || _a === void 0 ? void 0 : _a.prompt_tokens) === undefined) {
17085
- console.log('!!! computeOpenAiUsage', 'In OpenAI response `usage.prompt_tokens` not defined');
17086
17053
  throw new PipelineExecutionError('In OpenAI response `usage.prompt_tokens` not defined');
17087
17054
  }
17088
17055
  const inputTokens = rawResponse.usage.prompt_tokens;
@@ -17096,15 +17063,6 @@ resultContent, rawResponse) {
17096
17063
  isUncertain = true;
17097
17064
  }
17098
17065
  }
17099
- console.log('!!! computeOpenAiUsage', {
17100
- inputTokens,
17101
- outputTokens,
17102
- rawResponse,
17103
- 'rawResponse.model': rawResponse.model,
17104
- OPENAI_MODELS,
17105
- resultContent,
17106
- modelInfo,
17107
- });
17108
17066
  let price;
17109
17067
  if (modelInfo === undefined || modelInfo.pricing === undefined) {
17110
17068
  price = uncertainNumber();
@@ -17248,7 +17206,6 @@ class OpenAiExecutionTools {
17248
17206
  user: (_a = this.options.userId) === null || _a === void 0 ? void 0 : _a.toString(),
17249
17207
  };
17250
17208
  const start = $getCurrentDate();
17251
- let complete;
17252
17209
  if (this.options.isVerbose) {
17253
17210
  console.info(colors.bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
17254
17211
  }
@@ -17264,6 +17221,7 @@ class OpenAiExecutionTools {
17264
17221
  if (this.options.isVerbose) {
17265
17222
  console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
17266
17223
  }
17224
+ const complete = $getCurrentDate();
17267
17225
  if (!rawResponse.choices[0]) {
17268
17226
  throw new PipelineExecutionError('No choises from OpenAI');
17269
17227
  }
@@ -17272,8 +17230,6 @@ class OpenAiExecutionTools {
17272
17230
  throw new PipelineExecutionError('More than one choise from OpenAI');
17273
17231
  }
17274
17232
  const resultContent = rawResponse.choices[0].message.content;
17275
- // eslint-disable-next-line prefer-const
17276
- complete = $getCurrentDate();
17277
17233
  const usage = computeOpenAiUsage(content || '', resultContent || '', rawResponse);
17278
17234
  if (resultContent === null) {
17279
17235
  throw new PipelineExecutionError('No response message from OpenAI');
@@ -17327,7 +17283,6 @@ class OpenAiExecutionTools {
17327
17283
  user: (_a = this.options.userId) === null || _a === void 0 ? void 0 : _a.toString(),
17328
17284
  };
17329
17285
  const start = $getCurrentDate();
17330
- let complete;
17331
17286
  if (this.options.isVerbose) {
17332
17287
  console.info(colors.bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
17333
17288
  }
@@ -17343,6 +17298,7 @@ class OpenAiExecutionTools {
17343
17298
  if (this.options.isVerbose) {
17344
17299
  console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
17345
17300
  }
17301
+ const complete = $getCurrentDate();
17346
17302
  if (!rawResponse.choices[0]) {
17347
17303
  throw new PipelineExecutionError('No choises from OpenAI');
17348
17304
  }
@@ -17351,8 +17307,6 @@ class OpenAiExecutionTools {
17351
17307
  throw new PipelineExecutionError('More than one choise from OpenAI');
17352
17308
  }
17353
17309
  const resultContent = rawResponse.choices[0].text;
17354
- // eslint-disable-next-line prefer-const
17355
- complete = $getCurrentDate();
17356
17310
  const usage = computeOpenAiUsage(content || '', resultContent || '', rawResponse);
17357
17311
  return exportJson({
17358
17312
  name: 'promptResult',
@@ -17393,7 +17347,6 @@ class OpenAiExecutionTools {
17393
17347
  model: modelName,
17394
17348
  };
17395
17349
  const start = $getCurrentDate();
17396
- let complete;
17397
17350
  if (this.options.isVerbose) {
17398
17351
  console.info(colors.bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
17399
17352
  }
@@ -17409,12 +17362,11 @@ class OpenAiExecutionTools {
17409
17362
  if (this.options.isVerbose) {
17410
17363
  console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
17411
17364
  }
17365
+ const complete = $getCurrentDate();
17412
17366
  if (rawResponse.data.length !== 1) {
17413
17367
  throw new PipelineExecutionError(`Expected exactly 1 data item in response, got ${rawResponse.data.length}`);
17414
17368
  }
17415
17369
  const resultContent = rawResponse.data[0].embedding;
17416
- // eslint-disable-next-line prefer-const
17417
- complete = $getCurrentDate();
17418
17370
  const usage = computeOpenAiUsage(content || '', '',
17419
17371
  // <- Note: Embedding does not have result content
17420
17372
  rawResponse);
@@ -17442,7 +17394,8 @@ class OpenAiExecutionTools {
17442
17394
  * Get the model that should be used as default
17443
17395
  */
17444
17396
  getDefaultModel(defaultModelName) {
17445
- const model = OPENAI_MODELS.find(({ modelName }) => modelName === defaultModelName);
17397
+ // Note: Match exact or prefix for model families
17398
+ const model = OPENAI_MODELS.find(({ modelName }) => modelName === defaultModelName || modelName.startsWith(defaultModelName));
17446
17399
  if (model === undefined) {
17447
17400
  throw new UnexpectedError(spaceTrim((block) => `
17448
17401
  Cannot find model in OpenAI models with name "${defaultModelName}" which should be used as default.