@promptbook/cli 0.98.0-5 โ 0.98.0-8
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 +778 -592
- package/esm/index.es.js.map +1 -1
- package/esm/typings/src/_packages/anthropic-claude.index.d.ts +2 -2
- package/esm/typings/src/_packages/openai.index.d.ts +4 -0
- package/esm/typings/src/_packages/types.index.d.ts +10 -2
- package/esm/typings/src/config.d.ts +1 -1
- package/esm/typings/src/execution/createPipelineExecutor/$OngoingTaskResult.d.ts +1 -0
- package/esm/typings/src/execution/utils/validatePromptResult.d.ts +53 -0
- package/esm/typings/src/llm-providers/anthropic-claude/AnthropicClaudeExecutionTools.d.ts +3 -3
- package/esm/typings/src/llm-providers/anthropic-claude/AnthropicClaudeExecutionToolsOptions.d.ts +2 -2
- package/esm/typings/src/llm-providers/openai/OpenAiCompatibleExecutionTools.d.ts +4 -4
- package/esm/typings/src/llm-providers/openai/OpenAiCompatibleExecutionToolsOptions.d.ts +42 -1
- package/esm/typings/src/llm-providers/openai/createOpenAiCompatibleExecutionTools.d.ts +58 -1
- package/esm/typings/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/umd/index.umd.js +778 -592
- package/umd/index.umd.js.map +1 -1
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-
|
|
50
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.98.0-8';
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
@@ -4677,28 +5064,6 @@ async function loadArchive(filePath, fs) {
|
|
|
4677
5064
|
|
|
4678
5065
|
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
5066
|
|
|
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
5067
|
/**
|
|
4703
5068
|
* Function `validatePipelineString` will validate the if the string is a valid pipeline string
|
|
4704
5069
|
* 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 +5127,6 @@ function prettifyMarkdown(content) {
|
|
|
4762
5127
|
}
|
|
4763
5128
|
}
|
|
4764
5129
|
|
|
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
5130
|
/**
|
|
4775
5131
|
* Converts promptbook in JSON format to string format
|
|
4776
5132
|
*
|
|
@@ -5682,142 +6038,41 @@ const CsvFormatParser = {
|
|
|
5682
6038
|
${block(csv.errors.map((error) => error.message).join('\n\n'))}
|
|
5683
6039
|
|
|
5684
6040
|
The CSV setings:
|
|
5685
|
-
${block(JSON.stringify({ ...settings, ...MANDATORY_CSV_SETTINGS }, null, 2))}
|
|
5686
|
-
|
|
5687
|
-
The CSV data:
|
|
5688
|
-
${block(value)}
|
|
5689
|
-
`));
|
|
5690
|
-
}
|
|
5691
|
-
const mappedData = await Promise.all(csv.data.map(async (row, rowIndex) => {
|
|
5692
|
-
return /* not await */ Promise.all(Object.entries(row).map(async ([key, value], columnIndex, array) => {
|
|
5693
|
-
const index = rowIndex * Object.keys(row).length + columnIndex;
|
|
5694
|
-
return /* not await */ mapCallback({ [key]: value }, index, array.length);
|
|
5695
|
-
}));
|
|
5696
|
-
}));
|
|
5697
|
-
return unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
|
|
5698
|
-
},
|
|
5699
|
-
},
|
|
5700
|
-
],
|
|
5701
|
-
};
|
|
5702
|
-
/**
|
|
5703
|
-
* TODO: [๐] In `CsvFormatParser` implement simple `isValid`
|
|
5704
|
-
* TODO: [๐] In `CsvFormatParser` implement partial `canBeValid`
|
|
5705
|
-
* TODO: [๐] In `CsvFormatParser` implement `heal
|
|
5706
|
-
* TODO: [๐] In `CsvFormatParser` implement `subvalueParsers`
|
|
5707
|
-
* TODO: [๐ข] Allow to expect something inside CSV objects and other formats
|
|
5708
|
-
*/
|
|
5709
|
-
|
|
5710
|
-
/**
|
|
5711
|
-
* Definition for JSON format
|
|
5712
|
-
*
|
|
5713
|
-
* @private still in development [๐ข]
|
|
5714
|
-
*/
|
|
5715
|
-
const JsonFormatParser = {
|
|
5716
|
-
formatName: 'JSON',
|
|
5717
|
-
mimeType: 'application/json',
|
|
5718
|
-
isValid(value, settings, schema) {
|
|
5719
|
-
return isValidJsonString(value);
|
|
5720
|
-
},
|
|
5721
|
-
canBeValid(partialValue, settings, schema) {
|
|
5722
|
-
return true;
|
|
5723
|
-
},
|
|
5724
|
-
heal(value, settings, schema) {
|
|
5725
|
-
throw new Error('Not implemented');
|
|
5726
|
-
},
|
|
5727
|
-
subvalueParsers: [],
|
|
5728
|
-
};
|
|
5729
|
-
/**
|
|
5730
|
-
* TODO: [๐ง ] Maybe proper instance of object
|
|
5731
|
-
* TODO: [0] Make string_serialized_json
|
|
5732
|
-
* TODO: [1] Make type for JSON Settings and Schema
|
|
5733
|
-
* TODO: [๐ง ] What to use for validating JSONs - JSON Schema, ZoD, typescript types/interfaces,...?
|
|
5734
|
-
* TODO: [๐] In `JsonFormatParser` implement simple `isValid`
|
|
5735
|
-
* TODO: [๐] In `JsonFormatParser` implement partial `canBeValid`
|
|
5736
|
-
* TODO: [๐] In `JsonFormatParser` implement `heal
|
|
5737
|
-
* TODO: [๐] In `JsonFormatParser` implement `subvalueParsers`
|
|
5738
|
-
* TODO: [๐ข] Allow to expect something inside JSON objects and other formats
|
|
5739
|
-
*/
|
|
5740
|
-
|
|
5741
|
-
/**
|
|
5742
|
-
* Definition for any text - this will be always valid
|
|
5743
|
-
*
|
|
5744
|
-
* Note: This is not useful for validation, but for splitting and mapping with `subvalueParsers`
|
|
5745
|
-
*
|
|
5746
|
-
* @public exported from `@promptbook/core`
|
|
5747
|
-
*/
|
|
5748
|
-
const TextFormatParser = {
|
|
5749
|
-
formatName: 'TEXT',
|
|
5750
|
-
isValid(value) {
|
|
5751
|
-
return typeof value === 'string';
|
|
5752
|
-
},
|
|
5753
|
-
canBeValid(partialValue) {
|
|
5754
|
-
return typeof partialValue === 'string';
|
|
5755
|
-
},
|
|
5756
|
-
heal() {
|
|
5757
|
-
throw new UnexpectedError('It does not make sense to call `TextFormatParser.heal`');
|
|
5758
|
-
},
|
|
5759
|
-
subvalueParsers: [
|
|
5760
|
-
{
|
|
5761
|
-
subvalueName: 'LINE',
|
|
5762
|
-
async mapValues(options) {
|
|
5763
|
-
const { value, mapCallback, onProgress } = options;
|
|
5764
|
-
const lines = value.split('\n');
|
|
5765
|
-
const mappedLines = await Promise.all(lines.map((lineContent, lineNumber, array) =>
|
|
5766
|
-
// TODO: [๐ง ] Maybe option to skip empty line
|
|
5767
|
-
/* not await */ mapCallback({
|
|
5768
|
-
lineContent,
|
|
5769
|
-
// TODO: [๐ง ] Maybe also put here `lineNumber`
|
|
5770
|
-
}, lineNumber, array.length)));
|
|
5771
|
-
return mappedLines.join('\n');
|
|
6041
|
+
${block(JSON.stringify({ ...settings, ...MANDATORY_CSV_SETTINGS }, null, 2))}
|
|
6042
|
+
|
|
6043
|
+
The CSV data:
|
|
6044
|
+
${block(value)}
|
|
6045
|
+
`));
|
|
6046
|
+
}
|
|
6047
|
+
const mappedData = await Promise.all(csv.data.map(async (row, rowIndex) => {
|
|
6048
|
+
return /* not await */ Promise.all(Object.entries(row).map(async ([key, value], columnIndex, array) => {
|
|
6049
|
+
const index = rowIndex * Object.keys(row).length + columnIndex;
|
|
6050
|
+
return /* not await */ mapCallback({ [key]: value }, index, array.length);
|
|
6051
|
+
}));
|
|
6052
|
+
}));
|
|
6053
|
+
return unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
|
|
5772
6054
|
},
|
|
5773
6055
|
},
|
|
5774
|
-
// <- TODO: [๐ง ][๐ค ] Here should be all words, characters, lines, paragraphs, pages available as subvalues
|
|
5775
6056
|
],
|
|
5776
6057
|
};
|
|
5777
6058
|
/**
|
|
5778
|
-
* TODO: [
|
|
5779
|
-
* TODO: [
|
|
5780
|
-
* TODO: [๐] In `
|
|
5781
|
-
* TODO: [๐] In `
|
|
5782
|
-
* TODO: [
|
|
5783
|
-
* TODO: [๐] In `TextFormatParser` implement `subvalueParsers`
|
|
5784
|
-
* TODO: [๐ข] Allow to expect something inside each item of list and other formats
|
|
5785
|
-
*/
|
|
5786
|
-
|
|
5787
|
-
/**
|
|
5788
|
-
* Function to check if a string is valid XML
|
|
5789
|
-
*
|
|
5790
|
-
* @param value
|
|
5791
|
-
* @returns `true` if the string is a valid XML string, false otherwise
|
|
5792
|
-
*
|
|
5793
|
-
* @public exported from `@promptbook/utils`
|
|
6059
|
+
* TODO: [๐] In `CsvFormatParser` implement simple `isValid`
|
|
6060
|
+
* TODO: [๐] In `CsvFormatParser` implement partial `canBeValid`
|
|
6061
|
+
* TODO: [๐] In `CsvFormatParser` implement `heal
|
|
6062
|
+
* TODO: [๐] In `CsvFormatParser` implement `subvalueParsers`
|
|
6063
|
+
* TODO: [๐ข] Allow to expect something inside CSV objects and other formats
|
|
5794
6064
|
*/
|
|
5795
|
-
function isValidXmlString(value) {
|
|
5796
|
-
try {
|
|
5797
|
-
const parser = new DOMParser();
|
|
5798
|
-
const parsedDocument = parser.parseFromString(value, 'application/xml');
|
|
5799
|
-
const parserError = parsedDocument.getElementsByTagName('parsererror');
|
|
5800
|
-
if (parserError.length > 0) {
|
|
5801
|
-
return false;
|
|
5802
|
-
}
|
|
5803
|
-
return true;
|
|
5804
|
-
}
|
|
5805
|
-
catch (error) {
|
|
5806
|
-
assertsError(error);
|
|
5807
|
-
return false;
|
|
5808
|
-
}
|
|
5809
|
-
}
|
|
5810
6065
|
|
|
5811
6066
|
/**
|
|
5812
|
-
* Definition for
|
|
6067
|
+
* Definition for JSON format
|
|
5813
6068
|
*
|
|
5814
6069
|
* @private still in development [๐ข]
|
|
5815
6070
|
*/
|
|
5816
|
-
const
|
|
5817
|
-
formatName: '
|
|
5818
|
-
mimeType: 'application/
|
|
6071
|
+
const JsonFormatParser = {
|
|
6072
|
+
formatName: 'JSON',
|
|
6073
|
+
mimeType: 'application/json',
|
|
5819
6074
|
isValid(value, settings, schema) {
|
|
5820
|
-
return
|
|
6075
|
+
return isValidJsonString(value);
|
|
5821
6076
|
},
|
|
5822
6077
|
canBeValid(partialValue, settings, schema) {
|
|
5823
6078
|
return true;
|
|
@@ -5829,443 +6084,282 @@ const XmlFormatParser = {
|
|
|
5829
6084
|
};
|
|
5830
6085
|
/**
|
|
5831
6086
|
* TODO: [๐ง ] Maybe proper instance of object
|
|
5832
|
-
* TODO: [0] Make
|
|
5833
|
-
* TODO: [1] Make type for
|
|
5834
|
-
* TODO: [๐ง ] What to use for validating
|
|
5835
|
-
* TODO: [๐] In `
|
|
5836
|
-
* TODO: [๐] In `
|
|
5837
|
-
* TODO: [๐] In `
|
|
5838
|
-
* TODO: [๐] In `
|
|
5839
|
-
* TODO: [๐ข] Allow to expect something inside
|
|
5840
|
-
*/
|
|
5841
|
-
|
|
5842
|
-
/**
|
|
5843
|
-
* Definitions for all formats supported by Promptbook
|
|
5844
|
-
*
|
|
5845
|
-
* @private internal index of `...` <- TODO [๐ข]
|
|
5846
|
-
*/
|
|
5847
|
-
const FORMAT_DEFINITIONS = [JsonFormatParser, XmlFormatParser, TextFormatParser, CsvFormatParser];
|
|
5848
|
-
/**
|
|
5849
|
-
* Note: [๐] Ignore a discrepancy between file name and entity name
|
|
5850
|
-
*/
|
|
5851
|
-
|
|
5852
|
-
/**
|
|
5853
|
-
* Maps available parameters to expected parameters for a pipeline task.
|
|
5854
|
-
*
|
|
5855
|
-
* The strategy is:
|
|
5856
|
-
* 1) First, match parameters by name where both available and expected.
|
|
5857
|
-
* 2) Then, if there are unmatched expected and available parameters, map them by order.
|
|
5858
|
-
*
|
|
5859
|
-
* @throws {PipelineExecutionError} If the number of unmatched expected and available parameters does not match, or mapping is ambiguous.
|
|
5860
|
-
* @private within the repository used in `createPipelineExecutor`
|
|
5861
|
-
*/
|
|
5862
|
-
function mapAvailableToExpectedParameters(options) {
|
|
5863
|
-
const { expectedParameters, availableParameters } = options;
|
|
5864
|
-
const availableParametersNames = new Set(Object.keys(availableParameters));
|
|
5865
|
-
const expectedParameterNames = new Set(Object.keys(expectedParameters));
|
|
5866
|
-
const mappedParameters = {};
|
|
5867
|
-
// Phase 1๏ธโฃ: Matching mapping
|
|
5868
|
-
for (const parameterName of Array.from(union(availableParametersNames, expectedParameterNames))) {
|
|
5869
|
-
// Situation: Parameter is available and expected
|
|
5870
|
-
if (availableParametersNames.has(parameterName) && expectedParameterNames.has(parameterName)) {
|
|
5871
|
-
mappedParameters[parameterName] = availableParameters[parameterName];
|
|
5872
|
-
// <- Note: [๐ฉโ๐ฉโ๐ง] Maybe detect parameter collision here?
|
|
5873
|
-
availableParametersNames.delete(parameterName);
|
|
5874
|
-
expectedParameterNames.delete(parameterName);
|
|
5875
|
-
}
|
|
5876
|
-
// Situation: Parameter is available but NOT expected
|
|
5877
|
-
else if (availableParametersNames.has(parameterName) && !expectedParameterNames.has(parameterName)) ;
|
|
5878
|
-
// Situation: Parameter is NOT available BUT expected
|
|
5879
|
-
else if (!availableParametersNames.has(parameterName) && expectedParameterNames.has(parameterName)) ;
|
|
5880
|
-
}
|
|
5881
|
-
if (expectedParameterNames.size === 0) {
|
|
5882
|
-
// Note: [๐จโ๐จโ๐ง] Now we can freeze `mappedParameters` to prevent accidental modifications after mapping
|
|
5883
|
-
Object.freeze(mappedParameters);
|
|
5884
|
-
return mappedParameters;
|
|
5885
|
-
}
|
|
5886
|
-
// Phase 2๏ธโฃ: Non-matching mapping
|
|
5887
|
-
if (expectedParameterNames.size !== availableParametersNames.size) {
|
|
5888
|
-
throw new PipelineExecutionError(spaceTrim((block) => `
|
|
5889
|
-
Can not map available parameters to expected parameters
|
|
5890
|
-
|
|
5891
|
-
Mapped parameters:
|
|
5892
|
-
${block(Object.keys(mappedParameters)
|
|
5893
|
-
.map((parameterName) => `- {${parameterName}}`)
|
|
5894
|
-
.join('\n'))}
|
|
5895
|
-
|
|
5896
|
-
Expected parameters which can not be mapped:
|
|
5897
|
-
${block(Array.from(expectedParameterNames)
|
|
5898
|
-
.map((parameterName) => `- {${parameterName}}`)
|
|
5899
|
-
.join('\n'))}
|
|
5900
|
-
|
|
5901
|
-
Remaining available parameters:
|
|
5902
|
-
${block(Array.from(availableParametersNames)
|
|
5903
|
-
.map((parameterName) => `- {${parameterName}}`)
|
|
5904
|
-
.join('\n'))}
|
|
5905
|
-
|
|
5906
|
-
`));
|
|
5907
|
-
}
|
|
5908
|
-
const expectedParameterNamesArray = Array.from(expectedParameterNames);
|
|
5909
|
-
const availableParametersNamesArray = Array.from(availableParametersNames);
|
|
5910
|
-
for (let i = 0; i < expectedParameterNames.size; i++) {
|
|
5911
|
-
mappedParameters[expectedParameterNamesArray[i]] = availableParameters[availableParametersNamesArray[i]];
|
|
5912
|
-
}
|
|
5913
|
-
// 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;
|
|
6014
|
-
}
|
|
6015
|
-
/**
|
|
6016
|
-
* TODO: Add some auto-healing logic + extract YAML, JSON5, TOML, etc.
|
|
6017
|
-
* TODO: [๐ข] Make this logic part of `JsonFormatParser` or `isValidJsonString`
|
|
6087
|
+
* TODO: [0] Make string_serialized_json
|
|
6088
|
+
* TODO: [1] Make type for JSON Settings and Schema
|
|
6089
|
+
* TODO: [๐ง ] What to use for validating JSONs - JSON Schema, ZoD, typescript types/interfaces,...?
|
|
6090
|
+
* TODO: [๐] In `JsonFormatParser` implement simple `isValid`
|
|
6091
|
+
* TODO: [๐] In `JsonFormatParser` implement partial `canBeValid`
|
|
6092
|
+
* TODO: [๐] In `JsonFormatParser` implement `heal
|
|
6093
|
+
* TODO: [๐] In `JsonFormatParser` implement `subvalueParsers`
|
|
6094
|
+
* TODO: [๐ข] Allow to expect something inside JSON objects and other formats
|
|
6018
6095
|
*/
|
|
6019
6096
|
|
|
6020
6097
|
/**
|
|
6021
|
-
*
|
|
6098
|
+
* Definition for any text - this will be always valid
|
|
6022
6099
|
*
|
|
6023
|
-
*
|
|
6024
|
-
* 2) Undefined returns empty array
|
|
6025
|
-
* 3) Array returns itself
|
|
6100
|
+
* Note: This is not useful for validation, but for splitting and mapping with `subvalueParsers`
|
|
6026
6101
|
*
|
|
6027
|
-
* @
|
|
6102
|
+
* @public exported from `@promptbook/core`
|
|
6103
|
+
*/
|
|
6104
|
+
const TextFormatParser = {
|
|
6105
|
+
formatName: 'TEXT',
|
|
6106
|
+
isValid(value) {
|
|
6107
|
+
return typeof value === 'string';
|
|
6108
|
+
},
|
|
6109
|
+
canBeValid(partialValue) {
|
|
6110
|
+
return typeof partialValue === 'string';
|
|
6111
|
+
},
|
|
6112
|
+
heal() {
|
|
6113
|
+
throw new UnexpectedError('It does not make sense to call `TextFormatParser.heal`');
|
|
6114
|
+
},
|
|
6115
|
+
subvalueParsers: [
|
|
6116
|
+
{
|
|
6117
|
+
subvalueName: 'LINE',
|
|
6118
|
+
async mapValues(options) {
|
|
6119
|
+
const { value, mapCallback, onProgress } = options;
|
|
6120
|
+
const lines = value.split('\n');
|
|
6121
|
+
const mappedLines = await Promise.all(lines.map((lineContent, lineNumber, array) =>
|
|
6122
|
+
// TODO: [๐ง ] Maybe option to skip empty line
|
|
6123
|
+
/* not await */ mapCallback({
|
|
6124
|
+
lineContent,
|
|
6125
|
+
// TODO: [๐ง ] Maybe also put here `lineNumber`
|
|
6126
|
+
}, lineNumber, array.length)));
|
|
6127
|
+
return mappedLines.join('\n');
|
|
6128
|
+
},
|
|
6129
|
+
},
|
|
6130
|
+
// <- TODO: [๐ง ][๐ค ] Here should be all words, characters, lines, paragraphs, pages available as subvalues
|
|
6131
|
+
],
|
|
6132
|
+
};
|
|
6133
|
+
/**
|
|
6134
|
+
* TODO: [1] Make type for XML Text and Schema
|
|
6135
|
+
* TODO: [๐ง ][๐ค ] Here should be all words, characters, lines, paragraphs, pages available as subvalues
|
|
6136
|
+
* TODO: [๐] In `TextFormatParser` implement simple `isValid`
|
|
6137
|
+
* TODO: [๐] In `TextFormatParser` implement partial `canBeValid`
|
|
6138
|
+
* TODO: [๐] In `TextFormatParser` implement `heal
|
|
6139
|
+
* TODO: [๐] In `TextFormatParser` implement `subvalueParsers`
|
|
6140
|
+
* TODO: [๐ข] Allow to expect something inside each item of list and other formats
|
|
6028
6141
|
*/
|
|
6029
|
-
function arrayableToArray(input) {
|
|
6030
|
-
if (input === undefined) {
|
|
6031
|
-
return [];
|
|
6032
|
-
}
|
|
6033
|
-
if (input instanceof Array) {
|
|
6034
|
-
return input;
|
|
6035
|
-
}
|
|
6036
|
-
return [input];
|
|
6037
|
-
}
|
|
6038
6142
|
|
|
6039
6143
|
/**
|
|
6040
|
-
*
|
|
6144
|
+
* Function to check if a string is valid XML
|
|
6041
6145
|
*
|
|
6042
|
-
*
|
|
6043
|
-
*
|
|
6146
|
+
* @param value
|
|
6147
|
+
* @returns `true` if the string is a valid XML string, false otherwise
|
|
6044
6148
|
*
|
|
6045
|
-
* @param template the template with parameters in {curly} braces
|
|
6046
|
-
* @param parameters the object with parameters
|
|
6047
|
-
* @returns the template with replaced parameters
|
|
6048
|
-
* @throws {PipelineExecutionError} if parameter is not defined, not closed, or not opened
|
|
6049
6149
|
* @public exported from `@promptbook/utils`
|
|
6050
6150
|
*/
|
|
6051
|
-
function
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
throw new UnexpectedError(`Parameter \`{${parameterName}}\` is restricted to use`);
|
|
6059
|
-
}
|
|
6060
|
-
}
|
|
6061
|
-
let replacedTemplates = template;
|
|
6062
|
-
let match;
|
|
6063
|
-
let loopLimit = LOOP_LIMIT;
|
|
6064
|
-
while ((match = /^(?<precol>.*){(?<parameterName>\w+)}(.*)/m /* <- Not global */
|
|
6065
|
-
.exec(replacedTemplates))) {
|
|
6066
|
-
if (loopLimit-- < 0) {
|
|
6067
|
-
throw new LimitReachedError('Loop limit reached during parameters replacement in `templateParameters`');
|
|
6068
|
-
}
|
|
6069
|
-
const precol = match.groups.precol;
|
|
6070
|
-
const parameterName = match.groups.parameterName;
|
|
6071
|
-
if (parameterName === '') {
|
|
6072
|
-
// Note: Skip empty placeholders. It's used to avoid confusion with JSON-like strings
|
|
6073
|
-
continue;
|
|
6074
|
-
}
|
|
6075
|
-
if (parameterName.indexOf('{') !== -1 || parameterName.indexOf('}') !== -1) {
|
|
6076
|
-
throw new PipelineExecutionError('Parameter is already opened or not closed');
|
|
6077
|
-
}
|
|
6078
|
-
if (parameters[parameterName] === undefined) {
|
|
6079
|
-
throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
|
|
6080
|
-
}
|
|
6081
|
-
let parameterValue = parameters[parameterName];
|
|
6082
|
-
if (parameterValue === undefined) {
|
|
6083
|
-
throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
|
|
6084
|
-
}
|
|
6085
|
-
parameterValue = valueToString(parameterValue);
|
|
6086
|
-
// Escape curly braces in parameter values to prevent prompt-injection
|
|
6087
|
-
parameterValue = parameterValue.replace(/[{}]/g, '\\$&');
|
|
6088
|
-
if (parameterValue.includes('\n') && /^\s*\W{0,3}\s*$/.test(precol)) {
|
|
6089
|
-
parameterValue = parameterValue
|
|
6090
|
-
.split('\n')
|
|
6091
|
-
.map((line, index) => (index === 0 ? line : `${precol}${line}`))
|
|
6092
|
-
.join('\n');
|
|
6151
|
+
function isValidXmlString(value) {
|
|
6152
|
+
try {
|
|
6153
|
+
const parser = new DOMParser();
|
|
6154
|
+
const parsedDocument = parser.parseFromString(value, 'application/xml');
|
|
6155
|
+
const parserError = parsedDocument.getElementsByTagName('parsererror');
|
|
6156
|
+
if (parserError.length > 0) {
|
|
6157
|
+
return false;
|
|
6093
6158
|
}
|
|
6094
|
-
|
|
6095
|
-
replacedTemplates.substring(0, match.index + precol.length) +
|
|
6096
|
-
parameterValue +
|
|
6097
|
-
replacedTemplates.substring(match.index + precol.length + parameterName.length + 2);
|
|
6098
|
-
}
|
|
6099
|
-
// [๐ซ] Check if there are parameters that are not closed properly
|
|
6100
|
-
if (/{\w+$/.test(replacedTemplates)) {
|
|
6101
|
-
throw new PipelineExecutionError('Parameter is not closed');
|
|
6159
|
+
return true;
|
|
6102
6160
|
}
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6161
|
+
catch (error) {
|
|
6162
|
+
assertsError(error);
|
|
6163
|
+
return false;
|
|
6106
6164
|
}
|
|
6107
|
-
return replacedTemplates;
|
|
6108
6165
|
}
|
|
6109
6166
|
|
|
6110
6167
|
/**
|
|
6111
|
-
*
|
|
6168
|
+
* Definition for XML format
|
|
6112
6169
|
*
|
|
6113
|
-
* @
|
|
6170
|
+
* @private still in development [๐ข]
|
|
6114
6171
|
*/
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
}
|
|
6172
|
+
const XmlFormatParser = {
|
|
6173
|
+
formatName: 'XML',
|
|
6174
|
+
mimeType: 'application/xml',
|
|
6175
|
+
isValid(value, settings, schema) {
|
|
6176
|
+
return isValidXmlString(value);
|
|
6177
|
+
},
|
|
6178
|
+
canBeValid(partialValue, settings, schema) {
|
|
6179
|
+
return true;
|
|
6180
|
+
},
|
|
6181
|
+
heal(value, settings, schema) {
|
|
6182
|
+
throw new Error('Not implemented');
|
|
6183
|
+
},
|
|
6184
|
+
subvalueParsers: [],
|
|
6185
|
+
};
|
|
6124
6186
|
/**
|
|
6125
|
-
* TODO: [
|
|
6187
|
+
* TODO: [๐ง ] Maybe proper instance of object
|
|
6188
|
+
* TODO: [0] Make string_serialized_xml
|
|
6189
|
+
* TODO: [1] Make type for XML Settings and Schema
|
|
6190
|
+
* TODO: [๐ง ] What to use for validating XMLs - XSD,...
|
|
6191
|
+
* TODO: [๐] In `XmlFormatParser` implement simple `isValid`
|
|
6192
|
+
* TODO: [๐] In `XmlFormatParser` implement partial `canBeValid`
|
|
6193
|
+
* TODO: [๐] In `XmlFormatParser` implement `heal
|
|
6194
|
+
* TODO: [๐] In `XmlFormatParser` implement `subvalueParsers`
|
|
6195
|
+
* TODO: [๐ข] Allow to expect something inside XML and other formats
|
|
6126
6196
|
*/
|
|
6127
6197
|
|
|
6128
6198
|
/**
|
|
6129
|
-
*
|
|
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.
|
|
6199
|
+
* Definitions for all formats supported by Promptbook
|
|
6136
6200
|
*
|
|
6137
|
-
* @
|
|
6201
|
+
* @private internal index of `...` <- TODO [๐ข]
|
|
6138
6202
|
*/
|
|
6139
|
-
const
|
|
6203
|
+
const FORMAT_DEFINITIONS = [JsonFormatParser, XmlFormatParser, TextFormatParser, CsvFormatParser];
|
|
6140
6204
|
/**
|
|
6141
|
-
* TODO: [๐ง ] Should be this `constants.ts` or `config.ts`?
|
|
6142
6205
|
* Note: [๐] Ignore a discrepancy between file name and entity name
|
|
6143
6206
|
*/
|
|
6144
6207
|
|
|
6145
6208
|
/**
|
|
6146
|
-
*
|
|
6209
|
+
* Maps available parameters to expected parameters for a pipeline task.
|
|
6147
6210
|
*
|
|
6148
|
-
*
|
|
6211
|
+
* The strategy is:
|
|
6212
|
+
* 1) First, match parameters by name where both available and expected.
|
|
6213
|
+
* 2) Then, if there are unmatched expected and available parameters, map them by order.
|
|
6149
6214
|
*
|
|
6150
|
-
* @
|
|
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,...
|
|
6215
|
+
* @throws {PipelineExecutionError} If the number of unmatched expected and available parameters does not match, or mapping is ambiguous.
|
|
6216
|
+
* @private within the repository used in `createPipelineExecutor`
|
|
6160
6217
|
*/
|
|
6218
|
+
function mapAvailableToExpectedParameters(options) {
|
|
6219
|
+
const { expectedParameters, availableParameters } = options;
|
|
6220
|
+
const availableParametersNames = new Set(Object.keys(availableParameters));
|
|
6221
|
+
const expectedParameterNames = new Set(Object.keys(expectedParameters));
|
|
6222
|
+
const mappedParameters = {};
|
|
6223
|
+
// Phase 1๏ธโฃ: Matching mapping
|
|
6224
|
+
for (const parameterName of Array.from(union(availableParametersNames, expectedParameterNames))) {
|
|
6225
|
+
// Situation: Parameter is available and expected
|
|
6226
|
+
if (availableParametersNames.has(parameterName) && expectedParameterNames.has(parameterName)) {
|
|
6227
|
+
mappedParameters[parameterName] = availableParameters[parameterName];
|
|
6228
|
+
// <- Note: [๐ฉโ๐ฉโ๐ง] Maybe detect parameter collision here?
|
|
6229
|
+
availableParametersNames.delete(parameterName);
|
|
6230
|
+
expectedParameterNames.delete(parameterName);
|
|
6231
|
+
}
|
|
6232
|
+
// Situation: Parameter is available but NOT expected
|
|
6233
|
+
else if (availableParametersNames.has(parameterName) && !expectedParameterNames.has(parameterName)) ;
|
|
6234
|
+
// Situation: Parameter is NOT available BUT expected
|
|
6235
|
+
else if (!availableParametersNames.has(parameterName) && expectedParameterNames.has(parameterName)) ;
|
|
6236
|
+
}
|
|
6237
|
+
if (expectedParameterNames.size === 0) {
|
|
6238
|
+
// Note: [๐จโ๐จโ๐ง] Now we can freeze `mappedParameters` to prevent accidental modifications after mapping
|
|
6239
|
+
Object.freeze(mappedParameters);
|
|
6240
|
+
return mappedParameters;
|
|
6241
|
+
}
|
|
6242
|
+
// Phase 2๏ธโฃ: Non-matching mapping
|
|
6243
|
+
if (expectedParameterNames.size !== availableParametersNames.size) {
|
|
6244
|
+
throw new PipelineExecutionError(spaceTrim((block) => `
|
|
6245
|
+
Can not map available parameters to expected parameters
|
|
6161
6246
|
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
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
|
-
*/
|
|
6247
|
+
Mapped parameters:
|
|
6248
|
+
${block(Object.keys(mappedParameters)
|
|
6249
|
+
.map((parameterName) => `- {${parameterName}}`)
|
|
6250
|
+
.join('\n'))}
|
|
6175
6251
|
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6252
|
+
Expected parameters which can not be mapped:
|
|
6253
|
+
${block(Array.from(expectedParameterNames)
|
|
6254
|
+
.map((parameterName) => `- {${parameterName}}`)
|
|
6255
|
+
.join('\n'))}
|
|
6256
|
+
|
|
6257
|
+
Remaining available parameters:
|
|
6258
|
+
${block(Array.from(availableParametersNames)
|
|
6259
|
+
.map((parameterName) => `- {${parameterName}}`)
|
|
6260
|
+
.join('\n'))}
|
|
6261
|
+
|
|
6262
|
+
`));
|
|
6263
|
+
}
|
|
6264
|
+
const expectedParameterNamesArray = Array.from(expectedParameterNames);
|
|
6265
|
+
const availableParametersNamesArray = Array.from(availableParametersNames);
|
|
6266
|
+
for (let i = 0; i < expectedParameterNames.size; i++) {
|
|
6267
|
+
mappedParameters[expectedParameterNamesArray[i]] = availableParameters[availableParametersNamesArray[i]];
|
|
6268
|
+
}
|
|
6269
|
+
// Note: [๐จโ๐จโ๐ง] Now we can freeze `mappedParameters` to prevent accidental modifications after mapping
|
|
6270
|
+
Object.freeze(mappedParameters);
|
|
6271
|
+
return mappedParameters;
|
|
6183
6272
|
}
|
|
6184
|
-
/**
|
|
6185
|
-
* TODO: [๐ฅด] Implement counting in formats - like JSON, CSV, XML,...
|
|
6186
|
-
*/
|
|
6187
6273
|
|
|
6188
6274
|
/**
|
|
6189
|
-
*
|
|
6275
|
+
* Takes an item or an array of items and returns an array of items
|
|
6190
6276
|
*
|
|
6191
|
-
*
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
return text.split(/[.!?]+/).filter((sentence) => sentence.trim() !== '');
|
|
6195
|
-
}
|
|
6196
|
-
/**
|
|
6197
|
-
* Counts number of sentences in the text
|
|
6277
|
+
* 1) Any item except array and undefined returns array with that one item (also null)
|
|
6278
|
+
* 2) Undefined returns empty array
|
|
6279
|
+
* 3) Array returns itself
|
|
6198
6280
|
*
|
|
6199
|
-
* @
|
|
6281
|
+
* @private internal utility
|
|
6200
6282
|
*/
|
|
6201
|
-
function
|
|
6202
|
-
|
|
6283
|
+
function arrayableToArray(input) {
|
|
6284
|
+
if (input === undefined) {
|
|
6285
|
+
return [];
|
|
6286
|
+
}
|
|
6287
|
+
if (input instanceof Array) {
|
|
6288
|
+
return input;
|
|
6289
|
+
}
|
|
6290
|
+
return [input];
|
|
6203
6291
|
}
|
|
6204
|
-
/**
|
|
6205
|
-
* TODO: [๐ฅด] Implement counting in formats - like JSON, CSV, XML,...
|
|
6206
|
-
*/
|
|
6207
6292
|
|
|
6208
6293
|
/**
|
|
6209
|
-
*
|
|
6294
|
+
* Replaces parameters in template with values from parameters object
|
|
6210
6295
|
*
|
|
6211
|
-
*
|
|
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
|
|
6296
|
+
* Note: This function is not places strings into string,
|
|
6297
|
+
* It's more complex and can handle this operation specifically for LLM models
|
|
6226
6298
|
*
|
|
6299
|
+
* @param template the template with parameters in {curly} braces
|
|
6300
|
+
* @param parameters the object with parameters
|
|
6301
|
+
* @returns the template with replaced parameters
|
|
6302
|
+
* @throws {PipelineExecutionError} if parameter is not defined, not closed, or not opened
|
|
6227
6303
|
* @public exported from `@promptbook/utils`
|
|
6228
6304
|
*/
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
};
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
if (
|
|
6257
|
-
throw new
|
|
6258
|
-
}
|
|
6259
|
-
|
|
6260
|
-
|
|
6305
|
+
function templateParameters(template, parameters) {
|
|
6306
|
+
for (const [parameterName, parameterValue] of Object.entries(parameters)) {
|
|
6307
|
+
if (parameterValue === RESERVED_PARAMETER_MISSING_VALUE) {
|
|
6308
|
+
throw new UnexpectedError(`Parameter \`{${parameterName}}\` has missing value`);
|
|
6309
|
+
}
|
|
6310
|
+
else if (parameterValue === RESERVED_PARAMETER_RESTRICTED) {
|
|
6311
|
+
// TODO: [๐ต]
|
|
6312
|
+
throw new UnexpectedError(`Parameter \`{${parameterName}}\` is restricted to use`);
|
|
6313
|
+
}
|
|
6314
|
+
}
|
|
6315
|
+
let replacedTemplates = template;
|
|
6316
|
+
let match;
|
|
6317
|
+
let loopLimit = LOOP_LIMIT;
|
|
6318
|
+
while ((match = /^(?<precol>.*){(?<parameterName>\w+)}(.*)/m /* <- Not global */
|
|
6319
|
+
.exec(replacedTemplates))) {
|
|
6320
|
+
if (loopLimit-- < 0) {
|
|
6321
|
+
throw new LimitReachedError('Loop limit reached during parameters replacement in `templateParameters`');
|
|
6322
|
+
}
|
|
6323
|
+
const precol = match.groups.precol;
|
|
6324
|
+
const parameterName = match.groups.parameterName;
|
|
6325
|
+
if (parameterName === '') {
|
|
6326
|
+
// Note: Skip empty placeholders. It's used to avoid confusion with JSON-like strings
|
|
6327
|
+
continue;
|
|
6328
|
+
}
|
|
6329
|
+
if (parameterName.indexOf('{') !== -1 || parameterName.indexOf('}') !== -1) {
|
|
6330
|
+
throw new PipelineExecutionError('Parameter is already opened or not closed');
|
|
6331
|
+
}
|
|
6332
|
+
if (parameters[parameterName] === undefined) {
|
|
6333
|
+
throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
|
|
6334
|
+
}
|
|
6335
|
+
let parameterValue = parameters[parameterName];
|
|
6336
|
+
if (parameterValue === undefined) {
|
|
6337
|
+
throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
|
|
6338
|
+
}
|
|
6339
|
+
parameterValue = valueToString(parameterValue);
|
|
6340
|
+
// Escape curly braces in parameter values to prevent prompt-injection
|
|
6341
|
+
parameterValue = parameterValue.replace(/[{}]/g, '\\$&');
|
|
6342
|
+
if (parameterValue.includes('\n') && /^\s*\W{0,3}\s*$/.test(precol)) {
|
|
6343
|
+
parameterValue = parameterValue
|
|
6344
|
+
.split('\n')
|
|
6345
|
+
.map((line, index) => (index === 0 ? line : `${precol}${line}`))
|
|
6346
|
+
.join('\n');
|
|
6261
6347
|
}
|
|
6348
|
+
replacedTemplates =
|
|
6349
|
+
replacedTemplates.substring(0, match.index + precol.length) +
|
|
6350
|
+
parameterValue +
|
|
6351
|
+
replacedTemplates.substring(match.index + precol.length + parameterName.length + 2);
|
|
6352
|
+
}
|
|
6353
|
+
// [๐ซ] Check if there are parameters that are not closed properly
|
|
6354
|
+
if (/{\w+$/.test(replacedTemplates)) {
|
|
6355
|
+
throw new PipelineExecutionError('Parameter is not closed');
|
|
6356
|
+
}
|
|
6357
|
+
// [๐ซ] Check if there are parameters that are not opened properly
|
|
6358
|
+
if (/^\w+}/.test(replacedTemplates)) {
|
|
6359
|
+
throw new PipelineExecutionError('Parameter is not opened');
|
|
6262
6360
|
}
|
|
6361
|
+
return replacedTemplates;
|
|
6263
6362
|
}
|
|
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
6363
|
|
|
6270
6364
|
/**
|
|
6271
6365
|
* Executes a pipeline task with multiple attempts, including joker and retry logic. Handles different task types
|
|
@@ -6289,13 +6383,13 @@ async function executeAttempts(options) {
|
|
|
6289
6383
|
// TODO: [๐] Make arrayable LLMs -> single LLM DRY
|
|
6290
6384
|
const _llms = arrayableToArray(tools.llm);
|
|
6291
6385
|
const llmTools = _llms.length === 1 ? _llms[0] : joinLlmExecutionTools(..._llms);
|
|
6292
|
-
attempts: for (let
|
|
6293
|
-
const isJokerAttempt =
|
|
6294
|
-
const jokerParameterName = jokerParameterNames[jokerParameterNames.length +
|
|
6386
|
+
attempts: for (let attemptIndex = -jokerParameterNames.length; attemptIndex < maxAttempts; attemptIndex++) {
|
|
6387
|
+
const isJokerAttempt = attemptIndex < 0;
|
|
6388
|
+
const jokerParameterName = jokerParameterNames[jokerParameterNames.length + attemptIndex];
|
|
6295
6389
|
// TODO: [๐ง ][๐ญ] JOKERS, EXPECTATIONS, POSTPROCESSING and FOREACH
|
|
6296
6390
|
if (isJokerAttempt && !jokerParameterName) {
|
|
6297
6391
|
throw new UnexpectedError(spaceTrim$1((block) => `
|
|
6298
|
-
Joker not found in attempt ${
|
|
6392
|
+
Joker not found in attempt ${attemptIndex}
|
|
6299
6393
|
|
|
6300
6394
|
${block(pipelineIdentification)}
|
|
6301
6395
|
`));
|
|
@@ -6493,35 +6587,18 @@ async function executeAttempts(options) {
|
|
|
6493
6587
|
}
|
|
6494
6588
|
}
|
|
6495
6589
|
// TODO: [๐] Unite object for expecting amount and format
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
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
|
-
`));
|
|
6590
|
+
// Use the common validation function for both format and expectations
|
|
6591
|
+
if (task.format || task.expectations) {
|
|
6592
|
+
const validationResult = validatePromptResult({
|
|
6593
|
+
resultString: $ongoingTaskResult.$resultString || '',
|
|
6594
|
+
expectations: task.expectations,
|
|
6595
|
+
format: task.format,
|
|
6596
|
+
});
|
|
6597
|
+
if (!validationResult.isValid) {
|
|
6598
|
+
throw validationResult.error;
|
|
6520
6599
|
}
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
if (task.expectations) {
|
|
6524
|
-
checkExpectations(task.expectations, $ongoingTaskResult.$resultString || '');
|
|
6600
|
+
// Update the result string in case format processing modified it (e.g., JSON extraction)
|
|
6601
|
+
$ongoingTaskResult.$resultString = validationResult.processedResultString;
|
|
6525
6602
|
}
|
|
6526
6603
|
break attempts;
|
|
6527
6604
|
}
|
|
@@ -6535,6 +6612,7 @@ async function executeAttempts(options) {
|
|
|
6535
6612
|
$ongoingTaskResult.$failedResults = [];
|
|
6536
6613
|
}
|
|
6537
6614
|
$ongoingTaskResult.$failedResults.push({
|
|
6615
|
+
attemptIndex,
|
|
6538
6616
|
result: $ongoingTaskResult.$resultString,
|
|
6539
6617
|
error: error,
|
|
6540
6618
|
});
|
|
@@ -6559,19 +6637,13 @@ async function executeAttempts(options) {
|
|
|
6559
6637
|
});
|
|
6560
6638
|
}
|
|
6561
6639
|
}
|
|
6562
|
-
if ($ongoingTaskResult.$expectError !== null &&
|
|
6563
|
-
//
|
|
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
|
|
6640
|
+
if ($ongoingTaskResult.$expectError !== null && attemptIndex === maxAttempts - 1) {
|
|
6641
|
+
// Note: Create a summary of all failures
|
|
6570
6642
|
const failuresSummary = $ongoingTaskResult.$failedResults
|
|
6571
|
-
.map((failure
|
|
6643
|
+
.map((failure) => spaceTrim$1((block) => {
|
|
6572
6644
|
var _a, _b;
|
|
6573
6645
|
return `
|
|
6574
|
-
Attempt ${
|
|
6646
|
+
Attempt ${failure.attemptIndex + 1}:
|
|
6575
6647
|
Error ${((_a = failure.error) === null || _a === void 0 ? void 0 : _a.name) || ''}:
|
|
6576
6648
|
${block((_b = failure.error) === null || _b === void 0 ? void 0 : _b.message.split('\n').map((line) => `> ${line}`).join('\n'))}
|
|
6577
6649
|
|
|
@@ -17323,7 +17395,7 @@ resultContent, rawResponse) {
|
|
|
17323
17395
|
*/
|
|
17324
17396
|
|
|
17325
17397
|
/**
|
|
17326
|
-
* Execution Tools for calling OpenAI API or other
|
|
17398
|
+
* Execution Tools for calling OpenAI API or other OpenAI compatible provider
|
|
17327
17399
|
*
|
|
17328
17400
|
* @public exported from `@promptbook/openai`
|
|
17329
17401
|
*/
|
|
@@ -17893,6 +17965,7 @@ class OllamaExecutionTools extends OpenAiCompatibleExecutionTools {
|
|
|
17893
17965
|
baseURL: DEFAULT_OLLAMA_BASE_URL,
|
|
17894
17966
|
...ollamaOptions,
|
|
17895
17967
|
apiKey: 'ollama',
|
|
17968
|
+
isProxied: false, // <- Note: Ollama is always local
|
|
17896
17969
|
};
|
|
17897
17970
|
super(openAiCompatibleOptions);
|
|
17898
17971
|
}
|
|
@@ -18079,7 +18152,7 @@ const _OpenAiCompatibleMetadataRegistration = $llmToolsMetadataRegister.register
|
|
|
18079
18152
|
title: 'Open AI Compatible',
|
|
18080
18153
|
packageName: '@promptbook/openai',
|
|
18081
18154
|
className: 'OpenAiCompatibleExecutionTools',
|
|
18082
|
-
envVariables: ['OPENAI_API_KEY'],
|
|
18155
|
+
envVariables: ['OPENAI_API_KEY', 'OPENAI_BASE_URL'],
|
|
18083
18156
|
trustLevel: 'CLOSED',
|
|
18084
18157
|
order: MODEL_ORDERS.TOP_TIER,
|
|
18085
18158
|
getBoilerplateConfiguration() {
|
|
@@ -18089,11 +18162,35 @@ const _OpenAiCompatibleMetadataRegistration = $llmToolsMetadataRegister.register
|
|
|
18089
18162
|
className: 'OpenAiCompatibleExecutionTools',
|
|
18090
18163
|
options: {
|
|
18091
18164
|
apiKey: 'sk-',
|
|
18165
|
+
baseURL: 'https://api.openai.com/v1',
|
|
18166
|
+
isProxied: false,
|
|
18167
|
+
remoteServerUrl: DEFAULT_REMOTE_SERVER_URL,
|
|
18092
18168
|
maxRequestsPerMinute: DEFAULT_MAX_REQUESTS_PER_MINUTE,
|
|
18093
18169
|
},
|
|
18094
18170
|
};
|
|
18095
18171
|
},
|
|
18096
18172
|
createConfigurationFromEnv(env) {
|
|
18173
|
+
// Note: OpenAiCompatibleExecutionTools is an abstract class and cannot be instantiated directly
|
|
18174
|
+
// However, we can provide configuration for users who want to manually instantiate it
|
|
18175
|
+
if (typeof env.OPENAI_API_KEY === 'string') {
|
|
18176
|
+
const options = {
|
|
18177
|
+
apiKey: env.OPENAI_API_KEY,
|
|
18178
|
+
isProxied: false,
|
|
18179
|
+
remoteServerUrl: DEFAULT_REMOTE_SERVER_URL,
|
|
18180
|
+
maxRequestsPerMinute: DEFAULT_MAX_REQUESTS_PER_MINUTE,
|
|
18181
|
+
defaultModelName: 'gpt-4-turbo',
|
|
18182
|
+
};
|
|
18183
|
+
// Add baseURL if provided in environment
|
|
18184
|
+
if (typeof env.OPENAI_BASE_URL === 'string') {
|
|
18185
|
+
options.baseURL = env.OPENAI_BASE_URL;
|
|
18186
|
+
}
|
|
18187
|
+
return {
|
|
18188
|
+
title: 'Open AI Compatible (from env)',
|
|
18189
|
+
packageName: '@promptbook/openai',
|
|
18190
|
+
className: 'OpenAiCompatibleExecutionTools',
|
|
18191
|
+
options,
|
|
18192
|
+
};
|
|
18193
|
+
}
|
|
18097
18194
|
return null;
|
|
18098
18195
|
},
|
|
18099
18196
|
});
|
|
@@ -18146,7 +18243,7 @@ class OpenAiExecutionTools extends OpenAiCompatibleExecutionTools {
|
|
|
18146
18243
|
* Default model for chat variant.
|
|
18147
18244
|
*/
|
|
18148
18245
|
getDefaultChatModel() {
|
|
18149
|
-
return this.getDefaultModel('gpt-
|
|
18246
|
+
return this.getDefaultModel('gpt-4-turbo');
|
|
18150
18247
|
}
|
|
18151
18248
|
/**
|
|
18152
18249
|
* Default model for completion variant.
|
|
@@ -18176,6 +18273,9 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
|
|
|
18176
18273
|
* @param options which are relevant are directly passed to the OpenAI client
|
|
18177
18274
|
*/
|
|
18178
18275
|
constructor(options) {
|
|
18276
|
+
if (options.isProxied) {
|
|
18277
|
+
throw new NotYetImplementedError(`Proxy mode is not yet implemented for OpenAI assistants`);
|
|
18278
|
+
}
|
|
18179
18279
|
super(options);
|
|
18180
18280
|
this.assistantId = options.assistantId;
|
|
18181
18281
|
// TODO: [๐ฑ] Make limiter same as in `OpenAiExecutionTools`
|
|
@@ -18357,14 +18457,97 @@ const createOpenAiAssistantExecutionTools = Object.assign((options) => {
|
|
|
18357
18457
|
* @public exported from `@promptbook/openai`
|
|
18358
18458
|
*/
|
|
18359
18459
|
const createOpenAiCompatibleExecutionTools = Object.assign((options) => {
|
|
18460
|
+
if (options.isProxied) {
|
|
18461
|
+
return new RemoteLlmExecutionTools({
|
|
18462
|
+
...options,
|
|
18463
|
+
identification: {
|
|
18464
|
+
isAnonymous: true,
|
|
18465
|
+
llmToolsConfiguration: [
|
|
18466
|
+
{
|
|
18467
|
+
title: 'OpenAI Compatible (proxied)',
|
|
18468
|
+
packageName: '@promptbook/openai',
|
|
18469
|
+
className: 'OpenAiCompatibleExecutionTools',
|
|
18470
|
+
options: {
|
|
18471
|
+
...options,
|
|
18472
|
+
isProxied: false,
|
|
18473
|
+
},
|
|
18474
|
+
},
|
|
18475
|
+
],
|
|
18476
|
+
},
|
|
18477
|
+
});
|
|
18478
|
+
}
|
|
18360
18479
|
if (($isRunningInBrowser() || $isRunningInWebWorker()) && !options.dangerouslyAllowBrowser) {
|
|
18361
18480
|
options = { ...options, dangerouslyAllowBrowser: true };
|
|
18362
18481
|
}
|
|
18363
|
-
return new
|
|
18482
|
+
return new HardcodedOpenAiCompatibleExecutionTools(options.defaultModelName, options);
|
|
18364
18483
|
}, {
|
|
18365
18484
|
packageName: '@promptbook/openai',
|
|
18366
18485
|
className: 'OpenAiCompatibleExecutionTools',
|
|
18367
18486
|
});
|
|
18487
|
+
/**
|
|
18488
|
+
* Execution Tools for calling ONE SPECIFIC PRECONFIGURED OpenAI compatible provider
|
|
18489
|
+
*
|
|
18490
|
+
* @private for `createOpenAiCompatibleExecutionTools`
|
|
18491
|
+
*/
|
|
18492
|
+
class HardcodedOpenAiCompatibleExecutionTools extends OpenAiCompatibleExecutionTools {
|
|
18493
|
+
/**
|
|
18494
|
+
* Creates OpenAI compatible Execution Tools.
|
|
18495
|
+
*
|
|
18496
|
+
* @param options which are relevant are directly passed to the OpenAI compatible client
|
|
18497
|
+
*/
|
|
18498
|
+
constructor(defaultModelName, options) {
|
|
18499
|
+
super(options);
|
|
18500
|
+
this.defaultModelName = defaultModelName;
|
|
18501
|
+
this.options = options;
|
|
18502
|
+
}
|
|
18503
|
+
get title() {
|
|
18504
|
+
return `${this.defaultModelName} on ${this.options.baseURL}`;
|
|
18505
|
+
}
|
|
18506
|
+
get description() {
|
|
18507
|
+
return `OpenAI compatible connected to "${this.options.baseURL}" model "${this.defaultModelName}"`;
|
|
18508
|
+
}
|
|
18509
|
+
/**
|
|
18510
|
+
* List all available models (non dynamically)
|
|
18511
|
+
*
|
|
18512
|
+
* Note: Purpose of this is to provide more information about models than standard listing from API
|
|
18513
|
+
*/
|
|
18514
|
+
get HARDCODED_MODELS() {
|
|
18515
|
+
return [
|
|
18516
|
+
{
|
|
18517
|
+
modelName: this.defaultModelName,
|
|
18518
|
+
modelVariant: 'CHAT',
|
|
18519
|
+
modelDescription: '', // <- TODO: What is the best value here, maybe `this.description`?
|
|
18520
|
+
},
|
|
18521
|
+
];
|
|
18522
|
+
}
|
|
18523
|
+
/**
|
|
18524
|
+
* Computes the usage
|
|
18525
|
+
*/
|
|
18526
|
+
computeUsage(...args) {
|
|
18527
|
+
return {
|
|
18528
|
+
...computeOpenAiUsage(...args),
|
|
18529
|
+
price: UNCERTAIN_ZERO_VALUE, // <- TODO: Maybe in future pass this counting mechanism, but for now, we dont know
|
|
18530
|
+
};
|
|
18531
|
+
}
|
|
18532
|
+
/**
|
|
18533
|
+
* Default model for chat variant.
|
|
18534
|
+
*/
|
|
18535
|
+
getDefaultChatModel() {
|
|
18536
|
+
return this.getDefaultModel(this.defaultModelName);
|
|
18537
|
+
}
|
|
18538
|
+
/**
|
|
18539
|
+
* Default model for completion variant.
|
|
18540
|
+
*/
|
|
18541
|
+
getDefaultCompletionModel() {
|
|
18542
|
+
throw new PipelineExecutionError(`${this.title} does not support COMPLETION model variant`);
|
|
18543
|
+
}
|
|
18544
|
+
/**
|
|
18545
|
+
* Default model for completion variant.
|
|
18546
|
+
*/
|
|
18547
|
+
getDefaultEmbeddingModel() {
|
|
18548
|
+
throw new PipelineExecutionError(`${this.title} does not support EMBEDDING model variant`);
|
|
18549
|
+
}
|
|
18550
|
+
}
|
|
18368
18551
|
/**
|
|
18369
18552
|
* TODO: [๐ฆบ] Is there some way how to put `packageName` and `className` on top and function definition on bottom?
|
|
18370
18553
|
* TODO: [๐ถ] Naming "constructor" vs "creator" vs "factory"
|
|
@@ -18381,6 +18564,9 @@ const createOpenAiExecutionTools = Object.assign((options) => {
|
|
|
18381
18564
|
if (($isRunningInBrowser() || $isRunningInWebWorker()) && !options.dangerouslyAllowBrowser) {
|
|
18382
18565
|
options = { ...options, dangerouslyAllowBrowser: true };
|
|
18383
18566
|
}
|
|
18567
|
+
if (options.isProxied) {
|
|
18568
|
+
throw new NotYetImplementedError(`Proxy mode is not yet implemented in createOpenAiExecutionTools`);
|
|
18569
|
+
}
|
|
18384
18570
|
return new OpenAiExecutionTools(options);
|
|
18385
18571
|
}, {
|
|
18386
18572
|
packageName: '@promptbook/openai',
|