@tstdl/base 0.93.191 → 0.93.193
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.
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { memoize } from '../../utils/function/memoize.js';
|
|
2
|
-
import {
|
|
2
|
+
import { objectEntries } from '../../utils/object/object.js';
|
|
3
3
|
import { assertDefined, isArray, isDefined, isObject, isString } from '../../utils/type-guards.js';
|
|
4
4
|
const whitespacePattern = /^\s*/;
|
|
5
|
+
const INDENT_SIZE = 4;
|
|
5
6
|
// --- Factories ---
|
|
6
7
|
function list(style, instructionOrItems, itemsOrNothing) {
|
|
7
8
|
const instruction = isString(instructionOrItems) ? instructionOrItems : undefined;
|
|
@@ -20,10 +21,9 @@ export function unorderedList(instructionOrItems, itemsOrNothing) {
|
|
|
20
21
|
}
|
|
21
22
|
// --- Type Guards ---
|
|
22
23
|
function isInstructionsList(obj) {
|
|
23
|
-
return isObject(obj) &&
|
|
24
|
+
return isObject(obj) && ('style' in obj) && ('items' in obj);
|
|
24
25
|
}
|
|
25
|
-
// ---
|
|
26
|
-
const INDENT_SIZE = 4;
|
|
26
|
+
// --- Formatting Helpers ---
|
|
27
27
|
function getPrefix(style, index, sectionDepth) {
|
|
28
28
|
switch (style) {
|
|
29
29
|
case 'ordered':
|
|
@@ -36,20 +36,23 @@ function getPrefix(style, index, sectionDepth) {
|
|
|
36
36
|
return '';
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
function getIndent(context) {
|
|
40
|
+
return context.style == 'sections' ? '' : ' '.repeat(context.indentDepth * INDENT_SIZE);
|
|
41
|
+
}
|
|
39
42
|
function dedent(text) {
|
|
40
43
|
const lines = text.split('\n');
|
|
41
44
|
if (lines.length <= 1) {
|
|
42
45
|
return text.trim();
|
|
43
46
|
}
|
|
44
47
|
// Remove leading empty line if any (common in template literals)
|
|
45
|
-
if (lines[0].trim().length
|
|
48
|
+
if (lines[0].trim().length == 0) {
|
|
46
49
|
lines.shift();
|
|
47
50
|
}
|
|
48
51
|
// Remove trailing empty line if any
|
|
49
|
-
if (lines.length > 0 && lines.at(-1).trim().length
|
|
52
|
+
if (lines.length > 0 && lines.at(-1).trim().length == 0) {
|
|
50
53
|
lines.pop();
|
|
51
54
|
}
|
|
52
|
-
if (lines.length
|
|
55
|
+
if (lines.length == 0) {
|
|
53
56
|
return '';
|
|
54
57
|
}
|
|
55
58
|
// Find minimum indentation (excluding empty lines)
|
|
@@ -84,118 +87,103 @@ function formatWithHangingIndent(text, baseIndent, prefix) {
|
|
|
84
87
|
...lines.slice(1).map((line) => `${hangingSpacer}${line}`),
|
|
85
88
|
].join('\n');
|
|
86
89
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
// However, if we have a "Root Wrapper" with an instruction (rare in your example), handle it:
|
|
104
|
-
const header = node.instruction ? `${dedent(node.instruction)}\n\n` : '';
|
|
105
|
-
// Determine next context
|
|
106
|
-
const nextContext = {
|
|
107
|
-
indentDepth: context.indentDepth, // Wrappers don't indent themselves, their content does
|
|
90
|
+
function createNextContext(context, overrides = {}) {
|
|
91
|
+
return { ...context, isRoot: false, ...overrides };
|
|
92
|
+
}
|
|
93
|
+
// --- Node Processors ---
|
|
94
|
+
function processString(node, context, prefix = '') {
|
|
95
|
+
return formatWithHangingIndent(dedent(node), getIndent(context), prefix);
|
|
96
|
+
}
|
|
97
|
+
function processWrapper(node, context, prefix = '') {
|
|
98
|
+
if (isDefined(node.instruction)) {
|
|
99
|
+
if (context.isRoot == true) {
|
|
100
|
+
const header = `${dedent(node.instruction)}\n\n`;
|
|
101
|
+
return header + processNode(node.items, createNextContext(context, { style: node.style }));
|
|
102
|
+
}
|
|
103
|
+
const header = formatWithHangingIndent(dedent(node.instruction), getIndent(context), prefix);
|
|
104
|
+
const nextContext = createNextContext(context, {
|
|
105
|
+
indentDepth: context.indentDepth + 1,
|
|
108
106
|
style: node.style,
|
|
109
|
-
sectionDepth: node.style == 'sections' ? context.sectionDepth : context.sectionDepth,
|
|
110
|
-
};
|
|
111
|
-
return header
|
|
107
|
+
sectionDepth: node.style == 'sections' ? context.sectionDepth : context.sectionDepth,
|
|
108
|
+
});
|
|
109
|
+
return `${header}\n${processNode(node.items, nextContext)}`;
|
|
112
110
|
}
|
|
113
|
-
|
|
111
|
+
return processNode(node.items, createNextContext(context, { style: node.style }));
|
|
112
|
+
}
|
|
113
|
+
function processArray(node, context) {
|
|
114
|
+
const separator = context.style == 'sections' ? '\n\n' : '\n';
|
|
115
|
+
return node.map((item, index) => {
|
|
116
|
+
const prefix = getPrefix(context.style, index, context.sectionDepth);
|
|
117
|
+
if (isString(item)) {
|
|
118
|
+
return processString(item, context, prefix);
|
|
119
|
+
}
|
|
120
|
+
if (isInstructionsList(item)) {
|
|
121
|
+
return processWrapper(item, context, prefix);
|
|
122
|
+
}
|
|
123
|
+
return processNode(item, createNextContext(context));
|
|
124
|
+
}).join(separator);
|
|
125
|
+
}
|
|
126
|
+
function processObject(node, context) {
|
|
114
127
|
const isSection = context.style == 'sections';
|
|
115
|
-
const
|
|
128
|
+
const baseIndent = getIndent(context);
|
|
116
129
|
const separator = isSection ? '\n\n' : '\n';
|
|
117
|
-
// 2. Handle Arrays
|
|
118
|
-
if (isArray(node)) {
|
|
119
|
-
return node.map((item, index) => {
|
|
120
|
-
const prefix = getPrefix(context.style, index, context.sectionDepth);
|
|
121
|
-
if (isString(item)) {
|
|
122
|
-
return formatWithHangingIndent(dedent(item), currentBaseIndent, prefix);
|
|
123
|
-
}
|
|
124
|
-
return processNode(item, context);
|
|
125
|
-
}).join(separator);
|
|
126
|
-
}
|
|
127
|
-
// 3. Handle Objects (Key-Value Maps)
|
|
128
130
|
return objectEntries(node).map(([key, value], index) => {
|
|
129
131
|
const prefix = getPrefix(context.style, index, context.sectionDepth);
|
|
130
|
-
// Detect if the Value is a Wrapper (e.g. `Key: ordered(...)`)
|
|
131
|
-
// This allows us to pull the wrapper's "instruction" up to the Key line.
|
|
132
132
|
const isValueWrapper = isInstructionsList(value);
|
|
133
133
|
const effectiveValue = isValueWrapper ? value.items : value;
|
|
134
134
|
const instruction = (isValueWrapper && isDefined(value.instruction)) ? ` ${dedent(value.instruction)}` : '';
|
|
135
|
-
const childStyle = isValueWrapper ? value.style :
|
|
136
|
-
// Formatting the Header Line (The Key)
|
|
135
|
+
const childStyle = isValueWrapper ? value.style : 'unordered';
|
|
137
136
|
let headerLine = '';
|
|
138
137
|
if (isSection) {
|
|
139
|
-
// Header format: "# Key" or "# Key\n\nInstruction"
|
|
140
138
|
const instructionPart = (instruction.length > 0) ? `\n\n${instruction.trim()}` : '';
|
|
141
|
-
headerLine = `${
|
|
139
|
+
headerLine = `${baseIndent}${prefix}${key}${instructionPart}`;
|
|
142
140
|
}
|
|
143
141
|
else {
|
|
144
|
-
|
|
145
|
-
const keyPart = `**${key}:**`;
|
|
146
|
-
headerLine = `${currentBaseIndent}${prefix}${keyPart}${instruction}`;
|
|
142
|
+
headerLine = `${baseIndent}${prefix}**${key}:**${instruction}`;
|
|
147
143
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const nextSectionDepth = (isValueWrapper && value.style == 'sections')
|
|
154
|
-
? context.sectionDepth + 1
|
|
155
|
-
: context.sectionDepth;
|
|
156
|
-
// Recurse
|
|
157
|
-
// If the value is a simple string, we print it inline if possible, or block if it's long?
|
|
158
|
-
// Your requirement: Strings in objects are usually descriptions.
|
|
144
|
+
const nextContext = createNextContext(context, {
|
|
145
|
+
indentDepth: isSection ? 0 : context.indentDepth + 1,
|
|
146
|
+
sectionDepth: (isValueWrapper && value.style == 'sections') ? context.sectionDepth + 1 : context.sectionDepth,
|
|
147
|
+
style: childStyle,
|
|
148
|
+
});
|
|
159
149
|
if (isString(effectiveValue)) {
|
|
160
|
-
// If it's a string, we treat it as content on the SAME line for lists (via hanging indent logic),
|
|
161
|
-
// or a new paragraph for Sections.
|
|
162
150
|
if (isSection) {
|
|
163
|
-
// Section: Header \n\n Content
|
|
164
151
|
return `${headerLine}\n\n${dedent(effectiveValue)}`;
|
|
165
152
|
}
|
|
166
|
-
// List: "- **Key:** Value"
|
|
167
|
-
// We need to construct the full string to calculate hanging indent correctly.
|
|
168
|
-
// headerLine already contains indentation + prefix + key.
|
|
169
|
-
// We strip the indentation to feed it into the formatting helper effectively.
|
|
170
153
|
const fullLine = `${headerLine} ${dedent(effectiveValue)}`.trim();
|
|
171
|
-
return formatWithHangingIndent(fullLine,
|
|
154
|
+
return formatWithHangingIndent(fullLine, baseIndent, '');
|
|
172
155
|
}
|
|
173
|
-
|
|
174
|
-
const body = processNode(effectiveValue, {
|
|
175
|
-
indentDepth: nextIndentDepth,
|
|
176
|
-
style: childStyle,
|
|
177
|
-
sectionDepth: nextSectionDepth,
|
|
178
|
-
});
|
|
156
|
+
const body = processNode(effectiveValue, nextContext);
|
|
179
157
|
const bodySeparator = isSection ? '\n\n' : '\n';
|
|
180
|
-
// Edge case: If it's a section, we constructed the header, now append body.
|
|
181
|
-
// If it's a list, the header line serves as the parent item.
|
|
182
158
|
return `${headerLine}${bodySeparator}${body}`;
|
|
183
159
|
}).join(separator);
|
|
184
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Main recursive formatter.
|
|
163
|
+
*/
|
|
164
|
+
function processNode(node, context) {
|
|
165
|
+
if (isString(node)) {
|
|
166
|
+
return processString(node, context);
|
|
167
|
+
}
|
|
168
|
+
if (isInstructionsList(node)) {
|
|
169
|
+
return processWrapper(node, context);
|
|
170
|
+
}
|
|
171
|
+
if (isArray(node)) {
|
|
172
|
+
return processArray(node, context);
|
|
173
|
+
}
|
|
174
|
+
return processObject(node, context);
|
|
175
|
+
}
|
|
185
176
|
function _formatInstructions(node, options = {}) {
|
|
186
|
-
// Heuristic: If passing a raw object, assume it's a Root Section unless specified otherwise.
|
|
187
|
-
// If passing a Wrapper, the Wrapper dictates the style.
|
|
188
177
|
const initialStyle = isInstructionsList(node)
|
|
189
178
|
? node.style
|
|
190
|
-
: isArray(node)
|
|
179
|
+
: isArray(node) || isString(node)
|
|
191
180
|
? 'unordered'
|
|
192
|
-
:
|
|
193
|
-
? 'unordered'
|
|
194
|
-
: 'sections';
|
|
181
|
+
: 'sections';
|
|
195
182
|
return processNode(node, {
|
|
196
183
|
indentDepth: options.initialDepth ?? 0,
|
|
197
184
|
sectionDepth: 1,
|
|
198
185
|
style: initialStyle,
|
|
186
|
+
isRoot: true,
|
|
199
187
|
}).trim();
|
|
200
188
|
}
|
|
201
189
|
const formatInstructionsMemoized = memoize(_formatInstructions, { weak: true });
|
|
@@ -14,6 +14,8 @@ export declare class PromptBuilder {
|
|
|
14
14
|
setTask(task: string): this;
|
|
15
15
|
setSystemOutputSchema<Input = ObjectLiteral, Output = ObjectLiteral>(schema: SchemaTestable<Output>, examples?: FewShotExample<Input, Output>[]): this;
|
|
16
16
|
setOutputSchema<Input = ObjectLiteral, Output = ObjectLiteral>(schema: SchemaTestable<Output>, examples?: FewShotExample<Input, Output>[]): this;
|
|
17
|
+
setSystemOutputExamples<Input = ObjectLiteral, Output = ObjectLiteral>(examples: FewShotExample<Input, Output>[]): this;
|
|
18
|
+
setOutputExamples<Input = ObjectLiteral, Output = ObjectLiteral>(examples: FewShotExample<Input, Output>[]): this;
|
|
17
19
|
setSystemInstructionsOverride(override: ((instructions: Instructions) => Instructions | string) | undefined): this;
|
|
18
20
|
setInstructionsOverride(override: ((instructions: Instructions) => Instructions | string) | undefined): this;
|
|
19
21
|
setLanguage(language: string): this;
|
|
@@ -49,6 +49,14 @@ export class PromptBuilder {
|
|
|
49
49
|
this.#outputExamples = examples;
|
|
50
50
|
return this;
|
|
51
51
|
}
|
|
52
|
+
setSystemOutputExamples(examples) {
|
|
53
|
+
this.#systemOutputExamples = examples;
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
setOutputExamples(examples) {
|
|
57
|
+
this.#outputExamples = examples;
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
52
60
|
setSystemInstructionsOverride(override) {
|
|
53
61
|
this.#systemInstructionsOverride = override;
|
|
54
62
|
return this;
|
|
@@ -143,10 +151,13 @@ function buildPrompt(data) {
|
|
|
143
151
|
if (isDefined(data.instructions) && (objectKeys(data.instructions).length > 0)) {
|
|
144
152
|
instructions['**Instructions**'] = data.instructions;
|
|
145
153
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
154
|
+
const hasOutputSchema = isDefined(data.outputSchema);
|
|
155
|
+
if (hasOutputSchema || isDefined(data.outputExamples)) {
|
|
156
|
+
if (hasOutputSchema) {
|
|
157
|
+
const schema = convertToOpenApiSchema(data.outputSchema);
|
|
158
|
+
const schemaJson = JSON.stringify(schema, null, 2);
|
|
159
|
+
instructions['**Output Schema**'] = `\`\`\`json\n${schemaJson}\n\`\`\``;
|
|
160
|
+
}
|
|
150
161
|
instructions['**Output Schema Instructions**'] = unorderedList({
|
|
151
162
|
'Schema Compliance': 'Generate valid JSON that strictly matches the provided schema.',
|
|
152
163
|
'Nullable fields with missing data': 'Must be set to literal `null`, not the string "null".',
|
package/ai/prompts/steering.js
CHANGED