@promptbook/cli 0.98.0-6 โ†’ 0.98.0-9

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
@@ -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.98.0-6';
50
+ const PROMPTBOOK_ENGINE_VERSION = '0.98.0-9';
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
@@ -261,7 +261,7 @@ const DEFAULT_MAX_PARALLEL_COUNT = 5; // <- TODO: [๐Ÿคนโ€โ™‚๏ธ]
261
261
  *
262
262
  * @public exported from `@promptbook/core`
263
263
  */
264
- const DEFAULT_MAX_EXECUTION_ATTEMPTS = 3; // <- TODO: [๐Ÿคนโ€โ™‚๏ธ]
264
+ const DEFAULT_MAX_EXECUTION_ATTEMPTS = 7; // <- TODO: [๐Ÿคนโ€โ™‚๏ธ]
265
265
  // <- TODO: [๐Ÿ]
266
266
  /**
267
267
  * Where to store your books
@@ -1060,7 +1060,7 @@ function jsonParse(value) {
1060
1060
  throw new Error(spaceTrim((block) => `
1061
1061
  ${block(error.message)}
1062
1062
 
1063
- The JSON text:
1063
+ The expected JSON text:
1064
1064
  ${block(value)}
1065
1065
  `));
1066
1066
  }
@@ -2758,6 +2758,361 @@ function extractParameterNames(template) {
2758
2758
  return parameterNames;
2759
2759
  }
2760
2760
 
2761
+ /**
2762
+ * Function isValidJsonString will tell you if the string is valid JSON or not
2763
+ *
2764
+ * @param value The string to check
2765
+ * @returns `true` if the string is a valid JSON string, false otherwise
2766
+ *
2767
+ * @public exported from `@promptbook/utils`
2768
+ */
2769
+ function isValidJsonString(value /* <- [๐Ÿ‘จโ€โš–๏ธ] */) {
2770
+ try {
2771
+ JSON.parse(value);
2772
+ return true;
2773
+ }
2774
+ catch (error) {
2775
+ assertsError(error);
2776
+ if (error.message.includes('Unexpected token')) {
2777
+ return false;
2778
+ }
2779
+ return false;
2780
+ }
2781
+ }
2782
+
2783
+ /**
2784
+ * Makes first letter of a string uppercase
2785
+ *
2786
+ * @public exported from `@promptbook/utils`
2787
+ */
2788
+ function capitalize(word) {
2789
+ return word.substring(0, 1).toUpperCase() + word.substring(1);
2790
+ }
2791
+
2792
+ /**
2793
+ * Extracts all code blocks from markdown.
2794
+ *
2795
+ * Note: There are multiple similar functions:
2796
+ * - `extractBlock` just extracts the content of the code block which is also used as built-in function for postprocessing
2797
+ * - `extractJsonBlock` extracts exactly one valid JSON code block
2798
+ * - `extractOneBlockFromMarkdown` extracts exactly one code block with language of the code block
2799
+ * - `extractAllBlocksFromMarkdown` extracts all code blocks with language of the code block
2800
+ *
2801
+ * @param markdown any valid markdown
2802
+ * @returns code blocks with language and content
2803
+ * @throws {ParseError} if block is not closed properly
2804
+ * @public exported from `@promptbook/markdown-utils`
2805
+ */
2806
+ function extractAllBlocksFromMarkdown(markdown) {
2807
+ const codeBlocks = [];
2808
+ const lines = markdown.split('\n');
2809
+ // Note: [0] Ensure that the last block notated by gt > will be closed
2810
+ lines.push('');
2811
+ let currentCodeBlock = null;
2812
+ for (const line of lines) {
2813
+ if (line.startsWith('> ') || line === '>') {
2814
+ if (currentCodeBlock === null) {
2815
+ currentCodeBlock = { blockNotation: '>', language: null, content: '' };
2816
+ } /* not else */
2817
+ if (currentCodeBlock.blockNotation === '>') {
2818
+ if (currentCodeBlock.content !== '') {
2819
+ currentCodeBlock.content += '\n';
2820
+ }
2821
+ currentCodeBlock.content += line.slice(2);
2822
+ }
2823
+ }
2824
+ else if (currentCodeBlock !== null && currentCodeBlock.blockNotation === '>' /* <- Note: [0] */) {
2825
+ codeBlocks.push(currentCodeBlock);
2826
+ currentCodeBlock = null;
2827
+ }
2828
+ /* not else */
2829
+ if (line.startsWith('```')) {
2830
+ const language = line.slice(3).trim() || null;
2831
+ if (currentCodeBlock === null) {
2832
+ currentCodeBlock = { blockNotation: '```', language, content: '' };
2833
+ }
2834
+ else {
2835
+ if (language !== null) {
2836
+ throw new ParseError(`${capitalize(currentCodeBlock.language || 'the')} code block was not closed and already opening new ${language} code block`);
2837
+ }
2838
+ codeBlocks.push(currentCodeBlock);
2839
+ currentCodeBlock = null;
2840
+ }
2841
+ }
2842
+ else if (currentCodeBlock !== null && currentCodeBlock.blockNotation === '```') {
2843
+ if (currentCodeBlock.content !== '') {
2844
+ currentCodeBlock.content += '\n';
2845
+ }
2846
+ currentCodeBlock.content += line.split('\\`\\`\\`').join('```') /* <- TODO: Maybe make proper unescape */;
2847
+ }
2848
+ }
2849
+ if (currentCodeBlock !== null) {
2850
+ throw new ParseError(`${capitalize(currentCodeBlock.language || 'the')} code block was not closed at the end of the markdown`);
2851
+ }
2852
+ return codeBlocks;
2853
+ }
2854
+ /**
2855
+ * TODO: Maybe name for `blockNotation` instead of '```' and '>'
2856
+ */
2857
+
2858
+ /**
2859
+ * Extracts extracts exactly one valid JSON code block
2860
+ *
2861
+ * - When given string is a valid JSON as it is, it just returns it
2862
+ * - When there is no JSON code block the function throws a `ParseError`
2863
+ * - When there are multiple JSON code blocks the function throws a `ParseError`
2864
+ *
2865
+ * Note: It is not important if marked as ```json BUT if it is VALID JSON
2866
+ * Note: There are multiple similar function:
2867
+ * - `extractBlock` just extracts the content of the code block which is also used as build-in function for postprocessing
2868
+ * - `extractJsonBlock` extracts exactly one valid JSON code block
2869
+ * - `extractOneBlockFromMarkdown` extracts exactly one code block with language of the code block
2870
+ * - `extractAllBlocksFromMarkdown` extracts all code blocks with language of the code block
2871
+ *
2872
+ * @public exported from `@promptbook/markdown-utils`
2873
+ * @throws {ParseError} if there is no valid JSON block in the markdown
2874
+ */
2875
+ function extractJsonBlock(markdown) {
2876
+ if (isValidJsonString(markdown)) {
2877
+ return markdown;
2878
+ }
2879
+ const codeBlocks = extractAllBlocksFromMarkdown(markdown);
2880
+ const jsonBlocks = codeBlocks.filter(({ content }) => isValidJsonString(content));
2881
+ if (jsonBlocks.length === 0) {
2882
+ throw new Error('There is no valid JSON block in the markdown');
2883
+ }
2884
+ if (jsonBlocks.length > 1) {
2885
+ throw new Error('There are multiple JSON code blocks in the markdown');
2886
+ }
2887
+ return jsonBlocks[0].content;
2888
+ }
2889
+ /**
2890
+ * TODO: Add some auto-healing logic + extract YAML, JSON5, TOML, etc.
2891
+ * TODO: [๐Ÿข] Make this logic part of `JsonFormatParser` or `isValidJsonString`
2892
+ */
2893
+
2894
+ /**
2895
+ * Counts number of characters in the text
2896
+ *
2897
+ * @public exported from `@promptbook/utils`
2898
+ */
2899
+ function countCharacters(text) {
2900
+ // Remove null characters
2901
+ text = text.replace(/\0/g, '');
2902
+ // Replace emojis (and also ZWJ sequence) with hyphens
2903
+ text = text.replace(/(\p{Extended_Pictographic})\p{Modifier_Symbol}/gu, '$1');
2904
+ text = text.replace(/(\p{Extended_Pictographic})[\u{FE00}-\u{FE0F}]/gu, '$1');
2905
+ text = text.replace(/\p{Extended_Pictographic}(\u{200D}\p{Extended_Pictographic})*/gu, '-');
2906
+ return text.length;
2907
+ }
2908
+ /**
2909
+ * TODO: [๐Ÿฅด] Implement counting in formats - like JSON, CSV, XML,...
2910
+ */
2911
+
2912
+ /**
2913
+ * Number of characters per standard line with 11pt Arial font size.
2914
+ *
2915
+ * @public exported from `@promptbook/utils`
2916
+ */
2917
+ const CHARACTERS_PER_STANDARD_LINE = 63;
2918
+ /**
2919
+ * Number of lines per standard A4 page with 11pt Arial font size and standard margins and spacing.
2920
+ *
2921
+ * @public exported from `@promptbook/utils`
2922
+ */
2923
+ const LINES_PER_STANDARD_PAGE = 44;
2924
+ /**
2925
+ * TODO: [๐Ÿง ] Should be this `constants.ts` or `config.ts`?
2926
+ * Note: [๐Ÿ’ž] Ignore a discrepancy between file name and entity name
2927
+ */
2928
+
2929
+ /**
2930
+ * Counts number of lines in the text
2931
+ *
2932
+ * Note: This does not check only for the presence of newlines, but also for the length of the standard line.
2933
+ *
2934
+ * @public exported from `@promptbook/utils`
2935
+ */
2936
+ function countLines(text) {
2937
+ text = text.replace('\r\n', '\n');
2938
+ text = text.replace('\r', '\n');
2939
+ const lines = text.split('\n');
2940
+ return lines.reduce((count, line) => count + Math.ceil(line.length / CHARACTERS_PER_STANDARD_LINE), 0);
2941
+ }
2942
+ /**
2943
+ * TODO: [๐Ÿฅด] Implement counting in formats - like JSON, CSV, XML,...
2944
+ */
2945
+
2946
+ /**
2947
+ * Counts number of pages in the text
2948
+ *
2949
+ * 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.
2950
+ *
2951
+ * @public exported from `@promptbook/utils`
2952
+ */
2953
+ function countPages(text) {
2954
+ return Math.ceil(countLines(text) / LINES_PER_STANDARD_PAGE);
2955
+ }
2956
+ /**
2957
+ * TODO: [๐Ÿฅด] Implement counting in formats - like JSON, CSV, XML,...
2958
+ */
2959
+
2960
+ /**
2961
+ * Counts number of paragraphs in the text
2962
+ *
2963
+ * @public exported from `@promptbook/utils`
2964
+ */
2965
+ function countParagraphs(text) {
2966
+ return text.split(/\n\s*\n/).filter((paragraph) => paragraph.trim() !== '').length;
2967
+ }
2968
+ /**
2969
+ * TODO: [๐Ÿฅด] Implement counting in formats - like JSON, CSV, XML,...
2970
+ */
2971
+
2972
+ /**
2973
+ * Split text into sentences
2974
+ *
2975
+ * @public exported from `@promptbook/utils`
2976
+ */
2977
+ function splitIntoSentences(text) {
2978
+ return text.split(/[.!?]+/).filter((sentence) => sentence.trim() !== '');
2979
+ }
2980
+ /**
2981
+ * Counts number of sentences in the text
2982
+ *
2983
+ * @public exported from `@promptbook/utils`
2984
+ */
2985
+ function countSentences(text) {
2986
+ return splitIntoSentences(text).length;
2987
+ }
2988
+ /**
2989
+ * TODO: [๐Ÿฅด] Implement counting in formats - like JSON, CSV, XML,...
2990
+ */
2991
+
2992
+ /**
2993
+ * Counts number of words in the text
2994
+ *
2995
+ * @public exported from `@promptbook/utils`
2996
+ */
2997
+ function countWords(text) {
2998
+ text = text.replace(/[\p{Extended_Pictographic}]/gu, 'a');
2999
+ text = removeDiacritics(text);
3000
+ // Add spaces before uppercase letters preceded by lowercase letters (for camelCase)
3001
+ text = text.replace(/([a-z])([A-Z])/g, '$1 $2');
3002
+ return text.split(/[^a-zะฐ-ั0-9]+/i).filter((word) => word.length > 0).length;
3003
+ }
3004
+ /**
3005
+ * TODO: [๐Ÿฅด] Implement counting in formats - like JSON, CSV, XML,...
3006
+ */
3007
+
3008
+ /**
3009
+ * Index of all counter functions
3010
+ *
3011
+ * @public exported from `@promptbook/utils`
3012
+ */
3013
+ const CountUtils = {
3014
+ CHARACTERS: countCharacters,
3015
+ WORDS: countWords,
3016
+ SENTENCES: countSentences,
3017
+ PARAGRAPHS: countParagraphs,
3018
+ LINES: countLines,
3019
+ PAGES: countPages,
3020
+ };
3021
+ /**
3022
+ * TODO: [๐Ÿง ][๐Ÿค ] This should be probably as part of `TextFormatParser`
3023
+ * Note: [๐Ÿ’ž] Ignore a discrepancy between file name and entity name
3024
+ */
3025
+
3026
+ /**
3027
+ * Function checkExpectations will check if the expectations on given value are met
3028
+ *
3029
+ * Note: There are two similar functions:
3030
+ * - `checkExpectations` which throws an error if the expectations are not met
3031
+ * - `isPassingExpectations` which returns a boolean
3032
+ *
3033
+ * @throws {ExpectError} if the expectations are not met
3034
+ * @returns {void} Nothing
3035
+ * @private internal function of `createPipelineExecutor`
3036
+ */
3037
+ function checkExpectations(expectations, value) {
3038
+ for (const [unit, { max, min }] of Object.entries(expectations)) {
3039
+ const amount = CountUtils[unit.toUpperCase()](value);
3040
+ if (min && amount < min) {
3041
+ throw new ExpectError(`Expected at least ${min} ${unit} but got ${amount}`);
3042
+ } /* not else */
3043
+ if (max && amount > max) {
3044
+ throw new ExpectError(`Expected at most ${max} ${unit} but got ${amount}`);
3045
+ }
3046
+ }
3047
+ }
3048
+ /**
3049
+ * TODO: [๐Ÿ’] Unite object for expecting amount and format
3050
+ * TODO: [๐Ÿง ][๐Ÿค ] This should be part of `TextFormatParser`
3051
+ * Note: [๐Ÿ’] and [๐Ÿค ] are interconnected together
3052
+ */
3053
+
3054
+ /**
3055
+ * Validates a prompt result against expectations and format requirements.
3056
+ * This function provides a common abstraction for result validation that can be used
3057
+ * by both execution logic and caching logic to ensure consistency.
3058
+ *
3059
+ * @param options - The validation options including result string, expectations, and format
3060
+ * @returns Validation result with processed string and validity status
3061
+ * @private internal function of `createPipelineExecutor` and `cacheLlmTools`
3062
+ */
3063
+ function validatePromptResult(options) {
3064
+ const { resultString, expectations, format } = options;
3065
+ let processedResultString = resultString;
3066
+ let validationError;
3067
+ try {
3068
+ // TODO: [๐Ÿ’] Unite object for expecting amount and format
3069
+ if (format) {
3070
+ if (format === 'JSON') {
3071
+ if (!isValidJsonString(processedResultString)) {
3072
+ // TODO: [๐Ÿข] Do more universally via `FormatParser`
3073
+ try {
3074
+ processedResultString = extractJsonBlock(processedResultString);
3075
+ }
3076
+ catch (error) {
3077
+ keepUnused(error);
3078
+ throw new ExpectError(spaceTrim$1((block) => `
3079
+ Expected valid JSON string
3080
+
3081
+ The expected JSON text:
3082
+ ${block(processedResultString)}
3083
+ `));
3084
+ }
3085
+ }
3086
+ }
3087
+ else {
3088
+ throw new UnexpectedError(`Unknown format "${format}"`);
3089
+ }
3090
+ }
3091
+ // TODO: [๐Ÿ’] Unite object for expecting amount and format
3092
+ if (expectations) {
3093
+ checkExpectations(expectations, processedResultString);
3094
+ }
3095
+ return {
3096
+ isValid: true,
3097
+ processedResultString,
3098
+ };
3099
+ }
3100
+ catch (error) {
3101
+ if (error instanceof ExpectError) {
3102
+ validationError = error;
3103
+ }
3104
+ else {
3105
+ // Re-throw non-ExpectError errors (like UnexpectedError)
3106
+ throw error;
3107
+ }
3108
+ return {
3109
+ isValid: false,
3110
+ processedResultString,
3111
+ error: validationError,
3112
+ };
3113
+ }
3114
+ }
3115
+
2761
3116
  /**
2762
3117
  * Intercepts LLM tools and counts total usage of the tools
2763
3118
  *
@@ -2788,6 +3143,7 @@ function cacheLlmTools(llmTools, options = {}) {
2788
3143
  },
2789
3144
  };
2790
3145
  const callCommonModel = async (prompt) => {
3146
+ var _a;
2791
3147
  const { parameters, content, modelRequirements } = prompt;
2792
3148
  // <- Note: These are relevant things from the prompt that the cache key should depend on.
2793
3149
  // TODO: Maybe some standalone function for normalization of content for cache
@@ -2843,11 +3199,42 @@ function cacheLlmTools(llmTools, options = {}) {
2843
3199
  // 1. It has a content property that is null or undefined
2844
3200
  // 2. It has an error property that is truthy
2845
3201
  // 3. It has a success property that is explicitly false
2846
- const isFailedResult = promptResult.content === null ||
3202
+ // 4. It doesn't meet the prompt's expectations or format requirements
3203
+ const isBasicFailedResult = promptResult.content === null ||
2847
3204
  promptResult.content === undefined ||
2848
3205
  promptResult.error ||
2849
3206
  promptResult.success === false;
2850
- if (!isFailedResult) {
3207
+ let shouldCache = !isBasicFailedResult;
3208
+ // If the basic result is valid, check against expectations and format
3209
+ if (shouldCache && promptResult.content) {
3210
+ try {
3211
+ const validationResult = validatePromptResult({
3212
+ resultString: promptResult.content,
3213
+ expectations: prompt.expectations,
3214
+ format: prompt.format,
3215
+ });
3216
+ shouldCache = validationResult.isValid;
3217
+ if (!shouldCache && isVerbose) {
3218
+ console.info('Not caching result that fails expectations/format validation for key:', key, {
3219
+ content: promptResult.content,
3220
+ expectations: prompt.expectations,
3221
+ format: prompt.format,
3222
+ validationError: (_a = validationResult.error) === null || _a === void 0 ? void 0 : _a.message,
3223
+ });
3224
+ }
3225
+ }
3226
+ catch (error) {
3227
+ // If validation throws an unexpected error, don't cache
3228
+ shouldCache = false;
3229
+ if (isVerbose) {
3230
+ console.info('Not caching result due to validation error for key:', key, {
3231
+ content: promptResult.content,
3232
+ validationError: error instanceof Error ? error.message : String(error),
3233
+ });
3234
+ }
3235
+ }
3236
+ }
3237
+ if (shouldCache) {
2851
3238
  await storage.setItem(key, {
2852
3239
  date: $getCurrentDate(),
2853
3240
  promptbookVersion: PROMPTBOOK_ENGINE_VERSION,
@@ -2864,7 +3251,7 @@ function cacheLlmTools(llmTools, options = {}) {
2864
3251
  promptResult,
2865
3252
  });
2866
3253
  }
2867
- else if (isVerbose) {
3254
+ else if (isVerbose && isBasicFailedResult) {
2868
3255
  console.info('Not caching failed result for key:', key, {
2869
3256
  content: promptResult.content,
2870
3257
  error: promptResult.error,
@@ -3342,6 +3729,7 @@ function createLlmToolsFromConfiguration(configuration, options = {}) {
3342
3729
  .list()
3343
3730
  .find(({ packageName, className }) => llmConfiguration.packageName === packageName && llmConfiguration.className === className);
3344
3731
  if (registeredItem === undefined) {
3732
+ console.log('!!! $llmToolsRegister.list()', $llmToolsRegister.list());
3345
3733
  throw new Error(spaceTrim((block) => `
3346
3734
  There is no constructor for LLM provider \`${llmConfiguration.className}\` from \`${llmConfiguration.packageName}\`
3347
3735
 
@@ -4677,28 +5065,6 @@ async function loadArchive(filePath, fs) {
4677
5065
 
4678
5066
  var PipelineCollection = [{title:"Prepare Knowledge from Markdown",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-from-markdown.book",formfactorName:"GENERIC",parameters:[{name:"knowledgeContent",description:"Markdown document content",isInput:true,isOutput:false},{name:"knowledgePieces",description:"The knowledge JSON object",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced data researcher, extract the important knowledge from the document.\n\n# Rules\n\n- Make pieces of information concise, clear, and easy to understand\n- One piece of information should be approximately 1 paragraph\n- Divide the paragraphs by markdown horizontal lines ---\n- Omit irrelevant information\n- Group redundant information\n- Write just extracted information, nothing else\n\n# The document\n\nTake information from this document:\n\n> {knowledgeContent}",resultingParameterName:"knowledgePieces",dependentParameterNames:["knowledgeContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Knowledge from Markdown\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-from-markdown.book`\n- INPUT PARAMETER `{knowledgeContent}` Markdown document content\n- OUTPUT PARAMETER `{knowledgePieces}` The knowledge JSON object\n\n## Knowledge\n\n<!-- TODO: [๐Ÿ†] -FORMAT JSON -->\n\n```markdown\nYou are experienced data researcher, extract the important knowledge from the document.\n\n# Rules\n\n- Make pieces of information concise, clear, and easy to understand\n- One piece of information should be approximately 1 paragraph\n- Divide the paragraphs by markdown horizontal lines ---\n- Omit irrelevant information\n- Group redundant information\n- Write just extracted information, nothing else\n\n# The document\n\nTake information from this document:\n\n> {knowledgeContent}\n```\n\n`-> {knowledgePieces}`\n"}],sourceFile:"./books/prepare-knowledge-from-markdown.book"},{title:"Prepare Keywords",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-keywords.book",formfactorName:"GENERIC",parameters:[{name:"knowledgePieceContent",description:"The content",isInput:true,isOutput:false},{name:"keywords",description:"Keywords separated by comma",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced data researcher, detect the important keywords in the document.\n\n# Rules\n\n- Write just keywords separated by comma\n\n# The document\n\nTake information from this document:\n\n> {knowledgePieceContent}",resultingParameterName:"keywords",dependentParameterNames:["knowledgePieceContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Keywords\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-keywords.book`\n- INPUT PARAMETER `{knowledgePieceContent}` The content\n- OUTPUT PARAMETER `{keywords}` Keywords separated by comma\n\n## Knowledge\n\n<!-- TODO: [๐Ÿ†] -FORMAT JSON -->\n\n```markdown\nYou are experienced data researcher, detect the important keywords in the document.\n\n# Rules\n\n- Write just keywords separated by comma\n\n# The document\n\nTake information from this document:\n\n> {knowledgePieceContent}\n```\n\n`-> {keywords}`\n"}],sourceFile:"./books/prepare-knowledge-keywords.book"},{title:"Prepare Knowledge-piece Title",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-title.book",formfactorName:"GENERIC",parameters:[{name:"knowledgePieceContent",description:"The content",isInput:true,isOutput:false},{name:"title",description:"The title of the document",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced content creator, write best title for the document.\n\n# Rules\n\n- Write just title, nothing else\n- Write maximum 5 words for the title\n\n# The document\n\n> {knowledgePieceContent}",resultingParameterName:"title",expectations:{words:{min:1,max:8}},dependentParameterNames:["knowledgePieceContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Knowledge-piece Title\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-title.book`\n- INPUT PARAMETER `{knowledgePieceContent}` The content\n- OUTPUT PARAMETER `{title}` The title of the document\n\n## Knowledge\n\n- EXPECT MIN 1 WORD\n- EXPECT MAX 8 WORDS\n\n```markdown\nYou are experienced content creator, write best title for the document.\n\n# Rules\n\n- Write just title, nothing else\n- Write maximum 5 words for the title\n\n# The document\n\n> {knowledgePieceContent}\n```\n\n`-> {title}`\n"}],sourceFile:"./books/prepare-knowledge-title.book"},{title:"Prepare Persona",pipelineUrl:"https://promptbook.studio/promptbook/prepare-persona.book",formfactorName:"GENERIC",parameters:[{name:"availableModels",description:"List of available model names together with their descriptions as JSON",isInput:true,isOutput:false},{name:"personaDescription",description:"Description of the persona",isInput:true,isOutput:false},{name:"modelsRequirements",description:"Specific requirements for the model",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"make-model-requirements",title:"Make modelRequirements",content:"You are an experienced AI engineer, you need to find the best models for virtual assistants:\n\n## Example\n\n```json\n[\n {\n \"modelName\": \"gpt-4o\",\n \"systemMessage\": \"You are experienced AI engineer and helpful assistant.\",\n \"temperature\": 0.7\n },\n {\n \"modelName\": \"claude-3-5-sonnet\",\n \"systemMessage\": \"You are a friendly and knowledgeable chatbot.\",\n \"temperature\": 0.5\n }\n]\n```\n\n## Instructions\n\n- Your output format is JSON array\n- Sort best-fitting models first\n- Omit any models that are not suitable\n- Write just the JSON, no other text should be present\n- Array contain items with following keys:\n - `modelName`: The name of the model to use\n - `systemMessage`: The system message to provide context to the model\n - `temperature`: The sampling temperature to use\n\n### Key `modelName`\n\nHere are the available models:\n\n```json\n{availableModels}\n```\n\n### Key `systemMessage`\n\nThe system message is used to communicate instructions or provide context to the model at the beginning of a conversation. It is displayed in a different format compared to user messages, helping the model understand its role in the conversation. The system message typically guides the model's behavior, sets the tone, or specifies desired output from the model. By utilizing the system message effectively, users can steer the model towards generating more accurate and relevant responses.\n\nFor example:\n\n> You are an experienced AI engineer and helpful assistant.\n\n> You are a friendly and knowledgeable chatbot.\n\n### Key `temperature`\n\nThe sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.\n\nYou can pick a value between 0 and 2. For example:\n\n- `0.1`: Low temperature, extremely conservative and deterministic\n- `0.5`: Medium temperature, balanced between conservative and creative\n- `1.0`: High temperature, creative and bit random\n- `1.5`: Very high temperature, extremely creative and often chaotic and unpredictable\n- `2.0`: Maximum temperature, completely random and unpredictable, for some extreme creative use cases\n\n# The assistant\n\nTake this description of the persona:\n\n> {personaDescription}",resultingParameterName:"modelsRequirements",format:"JSON",dependentParameterNames:["availableModels","personaDescription"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Persona\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-persona.book`\n- INPUT PARAMETER `{availableModels}` List of available model names together with their descriptions as JSON\n- INPUT PARAMETER `{personaDescription}` Description of the persona\n- OUTPUT PARAMETER `{modelsRequirements}` Specific requirements for the model\n\n## Make modelRequirements\n\n- FORMAT JSON\n\n```markdown\nYou are an experienced AI engineer, you need to find the best models for virtual assistants:\n\n## Example\n\n\\`\\`\\`json\n[\n {\n \"modelName\": \"gpt-4o\",\n \"systemMessage\": \"You are experienced AI engineer and helpful assistant.\",\n \"temperature\": 0.7\n },\n {\n \"modelName\": \"claude-3-5-sonnet\",\n \"systemMessage\": \"You are a friendly and knowledgeable chatbot.\",\n \"temperature\": 0.5\n }\n]\n\\`\\`\\`\n\n## Instructions\n\n- Your output format is JSON array\n- Sort best-fitting models first\n- Omit any models that are not suitable\n- Write just the JSON, no other text should be present\n- Array contain items with following keys:\n - `modelName`: The name of the model to use\n - `systemMessage`: The system message to provide context to the model\n - `temperature`: The sampling temperature to use\n\n### Key `modelName`\n\nHere are the available models:\n\n\\`\\`\\`json\n{availableModels}\n\\`\\`\\`\n\n### Key `systemMessage`\n\nThe system message is used to communicate instructions or provide context to the model at the beginning of a conversation. It is displayed in a different format compared to user messages, helping the model understand its role in the conversation. The system message typically guides the model's behavior, sets the tone, or specifies desired output from the model. By utilizing the system message effectively, users can steer the model towards generating more accurate and relevant responses.\n\nFor example:\n\n> You are an experienced AI engineer and helpful assistant.\n\n> You are a friendly and knowledgeable chatbot.\n\n### Key `temperature`\n\nThe sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.\n\nYou can pick a value between 0 and 2. For example:\n\n- `0.1`: Low temperature, extremely conservative and deterministic\n- `0.5`: Medium temperature, balanced between conservative and creative\n- `1.0`: High temperature, creative and bit random\n- `1.5`: Very high temperature, extremely creative and often chaotic and unpredictable\n- `2.0`: Maximum temperature, completely random and unpredictable, for some extreme creative use cases\n\n# The assistant\n\nTake this description of the persona:\n\n> {personaDescription}\n```\n\n`-> {modelsRequirements}`\n"}],sourceFile:"./books/prepare-persona.book"},{title:"Prepare Title",pipelineUrl:"https://promptbook.studio/promptbook/prepare-title.book",formfactorName:"GENERIC",parameters:[{name:"book",description:"The book to prepare the title for",isInput:true,isOutput:false},{name:"title",description:"Best title for the book",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"make-title",title:"Make title",content:"Make best title for given text which describes the workflow:\n\n## Rules\n\n- Write just title, nothing else\n- Title should be concise and clear - Write maximum ideally 2 words, maximum 5 words\n- Title starts with emoticon\n- Title should not mention the input and output of the workflow but the main purpose of the workflow\n _For example, not \"โœ Convert Knowledge-piece to title\" but \"โœ Title\"_\n\n## The workflow\n\n> {book}",resultingParameterName:"title",expectations:{words:{min:1,max:8},lines:{min:1,max:1}},dependentParameterNames:["book"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Title\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-title.book`\n- INPUT PARAMETER `{book}` The book to prepare the title for\n- OUTPUT PARAMETER `{title}` Best title for the book\n\n## Make title\n\n- EXPECT MIN 1 Word\n- EXPECT MAX 8 Words\n- EXPECT EXACTLY 1 Line\n\n```markdown\nMake best title for given text which describes the workflow:\n\n## Rules\n\n- Write just title, nothing else\n- Title should be concise and clear - Write maximum ideally 2 words, maximum 5 words\n- Title starts with emoticon\n- Title should not mention the input and output of the workflow but the main purpose of the workflow\n _For example, not \"โœ Convert Knowledge-piece to title\" but \"โœ Title\"_\n\n## The workflow\n\n> {book}\n```\n\n`-> {title}`\n"}],sourceFile:"./books/prepare-title.book"}];
4679
5067
 
4680
- /**
4681
- * Function isValidJsonString will tell you if the string is valid JSON or not
4682
- *
4683
- * @param value The string to check
4684
- * @returns `true` if the string is a valid JSON string, false otherwise
4685
- *
4686
- * @public exported from `@promptbook/utils`
4687
- */
4688
- function isValidJsonString(value /* <- [๐Ÿ‘จโ€โš–๏ธ] */) {
4689
- try {
4690
- JSON.parse(value);
4691
- return true;
4692
- }
4693
- catch (error) {
4694
- assertsError(error);
4695
- if (error.message.includes('Unexpected token')) {
4696
- return false;
4697
- }
4698
- return false;
4699
- }
4700
- }
4701
-
4702
5068
  /**
4703
5069
  * Function `validatePipelineString` will validate the if the string is a valid pipeline string
4704
5070
  * It does not check if the string is fully logically correct, but if it is a string that can be a pipeline string or the string looks completely different.
@@ -4762,15 +5128,6 @@ function prettifyMarkdown(content) {
4762
5128
  }
4763
5129
  }
4764
5130
 
4765
- /**
4766
- * Makes first letter of a string uppercase
4767
- *
4768
- * @public exported from `@promptbook/utils`
4769
- */
4770
- function capitalize(word) {
4771
- return word.substring(0, 1).toUpperCase() + word.substring(1);
4772
- }
4773
-
4774
5131
  /**
4775
5132
  * Converts promptbook in JSON format to string format
4776
5133
  *
@@ -5911,111 +6268,9 @@ function mapAvailableToExpectedParameters(options) {
5911
6268
  mappedParameters[expectedParameterNamesArray[i]] = availableParameters[availableParametersNamesArray[i]];
5912
6269
  }
5913
6270
  // Note: [๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ง] Now we can freeze `mappedParameters` to prevent accidental modifications after mapping
5914
- Object.freeze(mappedParameters);
5915
- return mappedParameters;
5916
- }
5917
-
5918
- /**
5919
- * Extracts all code blocks from markdown.
5920
- *
5921
- * Note: There are multiple similar functions:
5922
- * - `extractBlock` just extracts the content of the code block which is also used as built-in function for postprocessing
5923
- * - `extractJsonBlock` extracts exactly one valid JSON code block
5924
- * - `extractOneBlockFromMarkdown` extracts exactly one code block with language of the code block
5925
- * - `extractAllBlocksFromMarkdown` extracts all code blocks with language of the code block
5926
- *
5927
- * @param markdown any valid markdown
5928
- * @returns code blocks with language and content
5929
- * @throws {ParseError} if block is not closed properly
5930
- * @public exported from `@promptbook/markdown-utils`
5931
- */
5932
- function extractAllBlocksFromMarkdown(markdown) {
5933
- const codeBlocks = [];
5934
- const lines = markdown.split('\n');
5935
- // Note: [0] Ensure that the last block notated by gt > will be closed
5936
- lines.push('');
5937
- let currentCodeBlock = null;
5938
- for (const line of lines) {
5939
- if (line.startsWith('> ') || line === '>') {
5940
- if (currentCodeBlock === null) {
5941
- currentCodeBlock = { blockNotation: '>', language: null, content: '' };
5942
- } /* not else */
5943
- if (currentCodeBlock.blockNotation === '>') {
5944
- if (currentCodeBlock.content !== '') {
5945
- currentCodeBlock.content += '\n';
5946
- }
5947
- currentCodeBlock.content += line.slice(2);
5948
- }
5949
- }
5950
- else if (currentCodeBlock !== null && currentCodeBlock.blockNotation === '>' /* <- Note: [0] */) {
5951
- codeBlocks.push(currentCodeBlock);
5952
- currentCodeBlock = null;
5953
- }
5954
- /* not else */
5955
- if (line.startsWith('```')) {
5956
- const language = line.slice(3).trim() || null;
5957
- if (currentCodeBlock === null) {
5958
- currentCodeBlock = { blockNotation: '```', language, content: '' };
5959
- }
5960
- else {
5961
- if (language !== null) {
5962
- throw new ParseError(`${capitalize(currentCodeBlock.language || 'the')} code block was not closed and already opening new ${language} code block`);
5963
- }
5964
- codeBlocks.push(currentCodeBlock);
5965
- currentCodeBlock = null;
5966
- }
5967
- }
5968
- else if (currentCodeBlock !== null && currentCodeBlock.blockNotation === '```') {
5969
- if (currentCodeBlock.content !== '') {
5970
- currentCodeBlock.content += '\n';
5971
- }
5972
- currentCodeBlock.content += line.split('\\`\\`\\`').join('```') /* <- TODO: Maybe make proper unescape */;
5973
- }
5974
- }
5975
- if (currentCodeBlock !== null) {
5976
- throw new ParseError(`${capitalize(currentCodeBlock.language || 'the')} code block was not closed at the end of the markdown`);
5977
- }
5978
- return codeBlocks;
5979
- }
5980
- /**
5981
- * TODO: Maybe name for `blockNotation` instead of '```' and '>'
5982
- */
5983
-
5984
- /**
5985
- * Extracts extracts exactly one valid JSON code block
5986
- *
5987
- * - When given string is a valid JSON as it is, it just returns it
5988
- * - When there is no JSON code block the function throws a `ParseError`
5989
- * - When there are multiple JSON code blocks the function throws a `ParseError`
5990
- *
5991
- * Note: It is not important if marked as ```json BUT if it is VALID JSON
5992
- * Note: There are multiple similar function:
5993
- * - `extractBlock` just extracts the content of the code block which is also used as build-in function for postprocessing
5994
- * - `extractJsonBlock` extracts exactly one valid JSON code block
5995
- * - `extractOneBlockFromMarkdown` extracts exactly one code block with language of the code block
5996
- * - `extractAllBlocksFromMarkdown` extracts all code blocks with language of the code block
5997
- *
5998
- * @public exported from `@promptbook/markdown-utils`
5999
- * @throws {ParseError} if there is no valid JSON block in the markdown
6000
- */
6001
- function extractJsonBlock(markdown) {
6002
- if (isValidJsonString(markdown)) {
6003
- return markdown;
6004
- }
6005
- const codeBlocks = extractAllBlocksFromMarkdown(markdown);
6006
- const jsonBlocks = codeBlocks.filter(({ content }) => isValidJsonString(content));
6007
- if (jsonBlocks.length === 0) {
6008
- throw new Error('There is no valid JSON block in the markdown');
6009
- }
6010
- if (jsonBlocks.length > 1) {
6011
- throw new Error('There are multiple JSON code blocks in the markdown');
6012
- }
6013
- return jsonBlocks[0].content;
6271
+ Object.freeze(mappedParameters);
6272
+ return mappedParameters;
6014
6273
  }
6015
- /**
6016
- * TODO: Add some auto-healing logic + extract YAML, JSON5, TOML, etc.
6017
- * TODO: [๐Ÿข] Make this logic part of `JsonFormatParser` or `isValidJsonString`
6018
- */
6019
6274
 
6020
6275
  /**
6021
6276
  * Takes an item or an array of items and returns an array of items
@@ -6107,166 +6362,6 @@ function templateParameters(template, parameters) {
6107
6362
  return replacedTemplates;
6108
6363
  }
6109
6364
 
6110
- /**
6111
- * Counts number of characters in the text
6112
- *
6113
- * @public exported from `@promptbook/utils`
6114
- */
6115
- function countCharacters(text) {
6116
- // Remove null characters
6117
- text = text.replace(/\0/g, '');
6118
- // Replace emojis (and also ZWJ sequence) with hyphens
6119
- text = text.replace(/(\p{Extended_Pictographic})\p{Modifier_Symbol}/gu, '$1');
6120
- text = text.replace(/(\p{Extended_Pictographic})[\u{FE00}-\u{FE0F}]/gu, '$1');
6121
- text = text.replace(/\p{Extended_Pictographic}(\u{200D}\p{Extended_Pictographic})*/gu, '-');
6122
- return text.length;
6123
- }
6124
- /**
6125
- * TODO: [๐Ÿฅด] Implement counting in formats - like JSON, CSV, XML,...
6126
- */
6127
-
6128
- /**
6129
- * Number of characters per standard line with 11pt Arial font size.
6130
- *
6131
- * @public exported from `@promptbook/utils`
6132
- */
6133
- const CHARACTERS_PER_STANDARD_LINE = 63;
6134
- /**
6135
- * Number of lines per standard A4 page with 11pt Arial font size and standard margins and spacing.
6136
- *
6137
- * @public exported from `@promptbook/utils`
6138
- */
6139
- const LINES_PER_STANDARD_PAGE = 44;
6140
- /**
6141
- * TODO: [๐Ÿง ] Should be this `constants.ts` or `config.ts`?
6142
- * Note: [๐Ÿ’ž] Ignore a discrepancy between file name and entity name
6143
- */
6144
-
6145
- /**
6146
- * Counts number of lines in the text
6147
- *
6148
- * Note: This does not check only for the presence of newlines, but also for the length of the standard line.
6149
- *
6150
- * @public exported from `@promptbook/utils`
6151
- */
6152
- function countLines(text) {
6153
- text = text.replace('\r\n', '\n');
6154
- text = text.replace('\r', '\n');
6155
- const lines = text.split('\n');
6156
- return lines.reduce((count, line) => count + Math.ceil(line.length / CHARACTERS_PER_STANDARD_LINE), 0);
6157
- }
6158
- /**
6159
- * TODO: [๐Ÿฅด] Implement counting in formats - like JSON, CSV, XML,...
6160
- */
6161
-
6162
- /**
6163
- * Counts number of pages in the text
6164
- *
6165
- * 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.
6166
- *
6167
- * @public exported from `@promptbook/utils`
6168
- */
6169
- function countPages(text) {
6170
- return Math.ceil(countLines(text) / LINES_PER_STANDARD_PAGE);
6171
- }
6172
- /**
6173
- * TODO: [๐Ÿฅด] Implement counting in formats - like JSON, CSV, XML,...
6174
- */
6175
-
6176
- /**
6177
- * Counts number of paragraphs in the text
6178
- *
6179
- * @public exported from `@promptbook/utils`
6180
- */
6181
- function countParagraphs(text) {
6182
- return text.split(/\n\s*\n/).filter((paragraph) => paragraph.trim() !== '').length;
6183
- }
6184
- /**
6185
- * TODO: [๐Ÿฅด] Implement counting in formats - like JSON, CSV, XML,...
6186
- */
6187
-
6188
- /**
6189
- * Split text into sentences
6190
- *
6191
- * @public exported from `@promptbook/utils`
6192
- */
6193
- function splitIntoSentences(text) {
6194
- return text.split(/[.!?]+/).filter((sentence) => sentence.trim() !== '');
6195
- }
6196
- /**
6197
- * Counts number of sentences in the text
6198
- *
6199
- * @public exported from `@promptbook/utils`
6200
- */
6201
- function countSentences(text) {
6202
- return splitIntoSentences(text).length;
6203
- }
6204
- /**
6205
- * TODO: [๐Ÿฅด] Implement counting in formats - like JSON, CSV, XML,...
6206
- */
6207
-
6208
- /**
6209
- * Counts number of words in the text
6210
- *
6211
- * @public exported from `@promptbook/utils`
6212
- */
6213
- function countWords(text) {
6214
- text = text.replace(/[\p{Extended_Pictographic}]/gu, 'a');
6215
- text = removeDiacritics(text);
6216
- // Add spaces before uppercase letters preceded by lowercase letters (for camelCase)
6217
- text = text.replace(/([a-z])([A-Z])/g, '$1 $2');
6218
- return text.split(/[^a-zะฐ-ั0-9]+/i).filter((word) => word.length > 0).length;
6219
- }
6220
- /**
6221
- * TODO: [๐Ÿฅด] Implement counting in formats - like JSON, CSV, XML,...
6222
- */
6223
-
6224
- /**
6225
- * Index of all counter functions
6226
- *
6227
- * @public exported from `@promptbook/utils`
6228
- */
6229
- const CountUtils = {
6230
- CHARACTERS: countCharacters,
6231
- WORDS: countWords,
6232
- SENTENCES: countSentences,
6233
- PARAGRAPHS: countParagraphs,
6234
- LINES: countLines,
6235
- PAGES: countPages,
6236
- };
6237
- /**
6238
- * TODO: [๐Ÿง ][๐Ÿค ] This should be probably as part of `TextFormatParser`
6239
- * Note: [๐Ÿ’ž] Ignore a discrepancy between file name and entity name
6240
- */
6241
-
6242
- /**
6243
- * Function checkExpectations will check if the expectations on given value are met
6244
- *
6245
- * Note: There are two similar functions:
6246
- * - `checkExpectations` which throws an error if the expectations are not met
6247
- * - `isPassingExpectations` which returns a boolean
6248
- *
6249
- * @throws {ExpectError} if the expectations are not met
6250
- * @returns {void} Nothing
6251
- * @private internal function of `createPipelineExecutor`
6252
- */
6253
- function checkExpectations(expectations, value) {
6254
- for (const [unit, { max, min }] of Object.entries(expectations)) {
6255
- const amount = CountUtils[unit.toUpperCase()](value);
6256
- if (min && amount < min) {
6257
- throw new ExpectError(`Expected at least ${min} ${unit} but got ${amount}`);
6258
- } /* not else */
6259
- if (max && amount > max) {
6260
- throw new ExpectError(`Expected at most ${max} ${unit} but got ${amount}`);
6261
- }
6262
- }
6263
- }
6264
- /**
6265
- * TODO: [๐Ÿ’] Unite object for expecting amount and format
6266
- * TODO: [๐Ÿง ][๐Ÿค ] This should be part of `TextFormatParser`
6267
- * Note: [๐Ÿ’] and [๐Ÿค ] are interconnected together
6268
- */
6269
-
6270
6365
  /**
6271
6366
  * Executes a pipeline task with multiple attempts, including joker and retry logic. Handles different task types
6272
6367
  * (prompt, script, dialog, etc.), applies postprocessing, checks expectations, and updates the execution report.
@@ -6289,13 +6384,13 @@ async function executeAttempts(options) {
6289
6384
  // TODO: [๐Ÿš] Make arrayable LLMs -> single LLM DRY
6290
6385
  const _llms = arrayableToArray(tools.llm);
6291
6386
  const llmTools = _llms.length === 1 ? _llms[0] : joinLlmExecutionTools(..._llms);
6292
- attempts: for (let attempt = -jokerParameterNames.length; attempt < maxAttempts; attempt++) {
6293
- const isJokerAttempt = attempt < 0;
6294
- const jokerParameterName = jokerParameterNames[jokerParameterNames.length + attempt];
6387
+ attempts: for (let attemptIndex = -jokerParameterNames.length; attemptIndex < maxAttempts; attemptIndex++) {
6388
+ const isJokerAttempt = attemptIndex < 0;
6389
+ const jokerParameterName = jokerParameterNames[jokerParameterNames.length + attemptIndex];
6295
6390
  // TODO: [๐Ÿง ][๐Ÿญ] JOKERS, EXPECTATIONS, POSTPROCESSING and FOREACH
6296
6391
  if (isJokerAttempt && !jokerParameterName) {
6297
6392
  throw new UnexpectedError(spaceTrim$1((block) => `
6298
- Joker not found in attempt ${attempt}
6393
+ Joker not found in attempt ${attemptIndex}
6299
6394
 
6300
6395
  ${block(pipelineIdentification)}
6301
6396
  `));
@@ -6493,35 +6588,18 @@ async function executeAttempts(options) {
6493
6588
  }
6494
6589
  }
6495
6590
  // TODO: [๐Ÿ’] Unite object for expecting amount and format
6496
- if (task.format) {
6497
- if (task.format === 'JSON') {
6498
- if (!isValidJsonString($ongoingTaskResult.$resultString || '')) {
6499
- // TODO: [๐Ÿข] Do more universally via `FormatParser`
6500
- try {
6501
- $ongoingTaskResult.$resultString = extractJsonBlock($ongoingTaskResult.$resultString || '');
6502
- }
6503
- catch (error) {
6504
- keepUnused(error);
6505
- throw new ExpectError(spaceTrim$1((block) => `
6506
- Expected valid JSON string
6507
-
6508
- ${block(
6509
- /*<- Note: No need for `pipelineIdentification`, it will be catched and added later */ '')}
6510
- `));
6511
- }
6512
- }
6513
- }
6514
- else {
6515
- throw new UnexpectedError(spaceTrim$1((block) => `
6516
- Unknown format "${task.format}"
6517
-
6518
- ${block(pipelineIdentification)}
6519
- `));
6591
+ // Use the common validation function for both format and expectations
6592
+ if (task.format || task.expectations) {
6593
+ const validationResult = validatePromptResult({
6594
+ resultString: $ongoingTaskResult.$resultString || '',
6595
+ expectations: task.expectations,
6596
+ format: task.format,
6597
+ });
6598
+ if (!validationResult.isValid) {
6599
+ throw validationResult.error;
6520
6600
  }
6521
- }
6522
- // TODO: [๐Ÿ’] Unite object for expecting amount and format
6523
- if (task.expectations) {
6524
- checkExpectations(task.expectations, $ongoingTaskResult.$resultString || '');
6601
+ // Update the result string in case format processing modified it (e.g., JSON extraction)
6602
+ $ongoingTaskResult.$resultString = validationResult.processedResultString;
6525
6603
  }
6526
6604
  break attempts;
6527
6605
  }
@@ -6535,6 +6613,7 @@ async function executeAttempts(options) {
6535
6613
  $ongoingTaskResult.$failedResults = [];
6536
6614
  }
6537
6615
  $ongoingTaskResult.$failedResults.push({
6616
+ attemptIndex,
6538
6617
  result: $ongoingTaskResult.$resultString,
6539
6618
  error: error,
6540
6619
  });
@@ -6559,19 +6638,13 @@ async function executeAttempts(options) {
6559
6638
  });
6560
6639
  }
6561
6640
  }
6562
- if ($ongoingTaskResult.$expectError !== null && attempt === maxAttempts - 1) {
6563
- // Store the current failure before throwing
6564
- $ongoingTaskResult.$failedResults = $ongoingTaskResult.$failedResults || [];
6565
- $ongoingTaskResult.$failedResults.push({
6566
- result: $ongoingTaskResult.$resultString,
6567
- error: $ongoingTaskResult.$expectError,
6568
- });
6569
- // Create a summary of all failures
6641
+ if ($ongoingTaskResult.$expectError !== null && attemptIndex === maxAttempts - 1) {
6642
+ // Note: Create a summary of all failures
6570
6643
  const failuresSummary = $ongoingTaskResult.$failedResults
6571
- .map((failure, index) => spaceTrim$1((block) => {
6644
+ .map((failure) => spaceTrim$1((block) => {
6572
6645
  var _a, _b;
6573
6646
  return `
6574
- Attempt ${index + 1}:
6647
+ Attempt ${failure.attemptIndex + 1}:
6575
6648
  Error ${((_a = failure.error) === null || _a === void 0 ? void 0 : _a.name) || ''}:
6576
6649
  ${block((_b = failure.error) === null || _b === void 0 ? void 0 : _b.message.split('\n').map((line) => `> ${line}`).join('\n'))}
6577
6650
 
@@ -17323,7 +17396,7 @@ resultContent, rawResponse) {
17323
17396
  */
17324
17397
 
17325
17398
  /**
17326
- * Execution Tools for calling OpenAI API or other OpeenAI compatible provider
17399
+ * Execution Tools for calling OpenAI API or other OpenAI compatible provider
17327
17400
  *
17328
17401
  * @public exported from `@promptbook/openai`
17329
17402
  */
@@ -17893,6 +17966,7 @@ class OllamaExecutionTools extends OpenAiCompatibleExecutionTools {
17893
17966
  baseURL: DEFAULT_OLLAMA_BASE_URL,
17894
17967
  ...ollamaOptions,
17895
17968
  apiKey: 'ollama',
17969
+ isProxied: false, // <- Note: Ollama is always local
17896
17970
  };
17897
17971
  super(openAiCompatibleOptions);
17898
17972
  }
@@ -18079,7 +18153,7 @@ const _OpenAiCompatibleMetadataRegistration = $llmToolsMetadataRegister.register
18079
18153
  title: 'Open AI Compatible',
18080
18154
  packageName: '@promptbook/openai',
18081
18155
  className: 'OpenAiCompatibleExecutionTools',
18082
- envVariables: ['OPENAI_API_KEY'],
18156
+ envVariables: ['OPENAI_API_KEY', 'OPENAI_BASE_URL'],
18083
18157
  trustLevel: 'CLOSED',
18084
18158
  order: MODEL_ORDERS.TOP_TIER,
18085
18159
  getBoilerplateConfiguration() {
@@ -18089,6 +18163,9 @@ const _OpenAiCompatibleMetadataRegistration = $llmToolsMetadataRegister.register
18089
18163
  className: 'OpenAiCompatibleExecutionTools',
18090
18164
  options: {
18091
18165
  apiKey: 'sk-',
18166
+ baseURL: 'https://api.openai.com/v1',
18167
+ isProxied: false,
18168
+ remoteServerUrl: DEFAULT_REMOTE_SERVER_URL,
18092
18169
  maxRequestsPerMinute: DEFAULT_MAX_REQUESTS_PER_MINUTE,
18093
18170
  },
18094
18171
  };
@@ -18146,7 +18223,7 @@ class OpenAiExecutionTools extends OpenAiCompatibleExecutionTools {
18146
18223
  * Default model for chat variant.
18147
18224
  */
18148
18225
  getDefaultChatModel() {
18149
- return this.getDefaultModel('gpt-4o');
18226
+ return this.getDefaultModel('gpt-4-turbo');
18150
18227
  }
18151
18228
  /**
18152
18229
  * Default model for completion variant.
@@ -18176,6 +18253,9 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
18176
18253
  * @param options which are relevant are directly passed to the OpenAI client
18177
18254
  */
18178
18255
  constructor(options) {
18256
+ if (options.isProxied) {
18257
+ throw new NotYetImplementedError(`Proxy mode is not yet implemented for OpenAI assistants`);
18258
+ }
18179
18259
  super(options);
18180
18260
  this.assistantId = options.assistantId;
18181
18261
  // TODO: [๐Ÿ‘ฑ] Make limiter same as in `OpenAiExecutionTools`
@@ -18357,14 +18437,97 @@ const createOpenAiAssistantExecutionTools = Object.assign((options) => {
18357
18437
  * @public exported from `@promptbook/openai`
18358
18438
  */
18359
18439
  const createOpenAiCompatibleExecutionTools = Object.assign((options) => {
18440
+ if (options.isProxied) {
18441
+ return new RemoteLlmExecutionTools({
18442
+ ...options,
18443
+ identification: {
18444
+ isAnonymous: true,
18445
+ llmToolsConfiguration: [
18446
+ {
18447
+ title: 'OpenAI Compatible (proxied)',
18448
+ packageName: '@promptbook/openai',
18449
+ className: 'OpenAiCompatibleExecutionTools',
18450
+ options: {
18451
+ ...options,
18452
+ isProxied: false,
18453
+ },
18454
+ },
18455
+ ],
18456
+ },
18457
+ });
18458
+ }
18360
18459
  if (($isRunningInBrowser() || $isRunningInWebWorker()) && !options.dangerouslyAllowBrowser) {
18361
18460
  options = { ...options, dangerouslyAllowBrowser: true };
18362
18461
  }
18363
- return new OpenAiExecutionTools(options);
18462
+ return new HardcodedOpenAiCompatibleExecutionTools(options.defaultModelName, options);
18364
18463
  }, {
18365
18464
  packageName: '@promptbook/openai',
18366
18465
  className: 'OpenAiCompatibleExecutionTools',
18367
18466
  });
18467
+ /**
18468
+ * Execution Tools for calling ONE SPECIFIC PRECONFIGURED OpenAI compatible provider
18469
+ *
18470
+ * @private for `createOpenAiCompatibleExecutionTools`
18471
+ */
18472
+ class HardcodedOpenAiCompatibleExecutionTools extends OpenAiCompatibleExecutionTools {
18473
+ /**
18474
+ * Creates OpenAI compatible Execution Tools.
18475
+ *
18476
+ * @param options which are relevant are directly passed to the OpenAI compatible client
18477
+ */
18478
+ constructor(defaultModelName, options) {
18479
+ super(options);
18480
+ this.defaultModelName = defaultModelName;
18481
+ this.options = options;
18482
+ }
18483
+ get title() {
18484
+ return `${this.defaultModelName} on ${this.options.baseURL}`;
18485
+ }
18486
+ get description() {
18487
+ return `OpenAI compatible connected to "${this.options.baseURL}" model "${this.defaultModelName}"`;
18488
+ }
18489
+ /**
18490
+ * List all available models (non dynamically)
18491
+ *
18492
+ * Note: Purpose of this is to provide more information about models than standard listing from API
18493
+ */
18494
+ get HARDCODED_MODELS() {
18495
+ return [
18496
+ {
18497
+ modelName: this.defaultModelName,
18498
+ modelVariant: 'CHAT',
18499
+ modelDescription: '', // <- TODO: What is the best value here, maybe `this.description`?
18500
+ },
18501
+ ];
18502
+ }
18503
+ /**
18504
+ * Computes the usage
18505
+ */
18506
+ computeUsage(...args) {
18507
+ return {
18508
+ ...computeOpenAiUsage(...args),
18509
+ price: UNCERTAIN_ZERO_VALUE, // <- TODO: Maybe in future pass this counting mechanism, but for now, we dont know
18510
+ };
18511
+ }
18512
+ /**
18513
+ * Default model for chat variant.
18514
+ */
18515
+ getDefaultChatModel() {
18516
+ return this.getDefaultModel(this.defaultModelName);
18517
+ }
18518
+ /**
18519
+ * Default model for completion variant.
18520
+ */
18521
+ getDefaultCompletionModel() {
18522
+ throw new PipelineExecutionError(`${this.title} does not support COMPLETION model variant`);
18523
+ }
18524
+ /**
18525
+ * Default model for completion variant.
18526
+ */
18527
+ getDefaultEmbeddingModel() {
18528
+ throw new PipelineExecutionError(`${this.title} does not support EMBEDDING model variant`);
18529
+ }
18530
+ }
18368
18531
  /**
18369
18532
  * TODO: [๐Ÿฆบ] Is there some way how to put `packageName` and `className` on top and function definition on bottom?
18370
18533
  * TODO: [๐ŸŽถ] Naming "constructor" vs "creator" vs "factory"
@@ -18381,6 +18544,9 @@ const createOpenAiExecutionTools = Object.assign((options) => {
18381
18544
  if (($isRunningInBrowser() || $isRunningInWebWorker()) && !options.dangerouslyAllowBrowser) {
18382
18545
  options = { ...options, dangerouslyAllowBrowser: true };
18383
18546
  }
18547
+ if (options.isProxied) {
18548
+ throw new NotYetImplementedError(`Proxy mode is not yet implemented in createOpenAiExecutionTools`);
18549
+ }
18384
18550
  return new OpenAiExecutionTools(options);
18385
18551
  }, {
18386
18552
  packageName: '@promptbook/openai',