-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
prompt.ts
320 lines (295 loc) Β· 9.3 KB
/
prompt.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
// Default generic "any" values are for backwards compatibility.
// Replace with "string" when we are comfortable with a breaking change.
import { BaseStringPromptTemplate } from "./string.js";
import type {
BasePromptTemplateInput,
TypedPromptInputValues,
} from "./base.js";
import {
checkValidTemplate,
parseTemplate,
renderTemplate,
type TemplateFormat,
} from "./template.js";
import type { SerializedPromptTemplate } from "./serde.js";
import type { InputValues, PartialValues } from "../utils/types/index.js";
import { MessageContent } from "../messages/index.js";
/**
* Inputs to create a {@link PromptTemplate}
* @augments BasePromptTemplateInput
*/
export interface PromptTemplateInput<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
RunInput extends InputValues = any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
PartialVariableName extends string = any,
Format extends TemplateFormat = TemplateFormat
> extends BasePromptTemplateInput<RunInput, PartialVariableName> {
/**
* The prompt template
*/
template: MessageContent;
/**
* The format of the prompt template. Options are "f-string" and "mustache"
*/
templateFormat?: Format;
/**
* Whether or not to try validating the template on initialization
*
* @defaultValue `true`
*/
validateTemplate?: boolean;
}
type NonAlphanumeric =
| " "
| "\t"
| "\n"
| "\r"
| '"'
| "'"
| "{"
| "["
| "("
| "`"
| ":"
| ";";
/**
* Recursive type to extract template parameters from a string.
* @template T - The input string.
* @template Result - The resulting array of extracted template parameters.
*/
type ExtractTemplateParamsRecursive<
T extends string,
Result extends string[] = []
> = T extends `${string}{${infer Param}}${infer Rest}`
? Param extends `${NonAlphanumeric}${string}`
? ExtractTemplateParamsRecursive<Rest, Result> // for non-template variables that look like template variables e.g. see https://github.com/langchain-ai/langchainjs/blob/main/langchain/src/chains/query_constructor/prompt.ts
: ExtractTemplateParamsRecursive<Rest, [...Result, Param]>
: Result;
export type ParamsFromFString<T extends string> = {
[Key in
| ExtractTemplateParamsRecursive<T>[number]
| (string & Record<never, never>)]: string;
};
export type ExtractedFStringParams<
T extends string,
// eslint-disable-next-line @typescript-eslint/ban-types
RunInput extends InputValues = Symbol
// eslint-disable-next-line @typescript-eslint/ban-types
> = RunInput extends Symbol ? ParamsFromFString<T> : RunInput;
/**
* Schema to represent a basic prompt for an LLM.
* @augments BasePromptTemplate
* @augments PromptTemplateInput
*
* @example
* ```ts
* import { PromptTemplate } from "langchain/prompts";
*
* const prompt = new PromptTemplate({
* inputVariables: ["foo"],
* template: "Say {foo}",
* });
* ```
*/
export class PromptTemplate<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
RunInput extends InputValues = any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
PartialVariableName extends string = any
>
extends BaseStringPromptTemplate<RunInput, PartialVariableName>
implements PromptTemplateInput<RunInput, PartialVariableName>
{
static lc_name() {
return "PromptTemplate";
}
template: MessageContent;
templateFormat: TemplateFormat = "f-string";
validateTemplate = true;
constructor(input: PromptTemplateInput<RunInput, PartialVariableName>) {
super(input);
// If input is mustache and validateTemplate is not defined, set it to false
if (
input.templateFormat === "mustache" &&
input.validateTemplate === undefined
) {
this.validateTemplate = false;
}
Object.assign(this, input);
if (this.validateTemplate) {
if (this.templateFormat === "mustache") {
throw new Error("Mustache templates cannot be validated.");
}
let totalInputVariables: string[] = this.inputVariables;
if (this.partialVariables) {
totalInputVariables = totalInputVariables.concat(
Object.keys(this.partialVariables)
);
}
checkValidTemplate(
this.template,
this.templateFormat,
totalInputVariables
);
}
}
_getPromptType(): "prompt" {
return "prompt";
}
/**
* Formats the prompt template with the provided values.
* @param values The values to be used to format the prompt template.
* @returns A promise that resolves to a string which is the formatted prompt.
*/
async format(values: TypedPromptInputValues<RunInput>): Promise<string> {
const allValues = await this.mergePartialAndUserVariables(values);
return renderTemplate(
this.template as string,
this.templateFormat,
allValues
);
}
/**
* Take examples in list format with prefix and suffix to create a prompt.
*
* Intended to be used a a way to dynamically create a prompt from examples.
*
* @param examples - List of examples to use in the prompt.
* @param suffix - String to go after the list of examples. Should generally set up the user's input.
* @param inputVariables - A list of variable names the final prompt template will expect
* @param exampleSeparator - The separator to use in between examples
* @param prefix - String that should go before any examples. Generally includes examples.
*
* @returns The final prompt template generated.
*/
static fromExamples(
examples: string[],
suffix: string,
inputVariables: string[],
exampleSeparator = "\n\n",
prefix = ""
) {
const template = [prefix, ...examples, suffix].join(exampleSeparator);
return new PromptTemplate({
inputVariables,
template,
});
}
/**
* Load prompt template from a template f-string
*/
static fromTemplate<
// eslint-disable-next-line @typescript-eslint/ban-types
RunInput extends InputValues = Symbol,
T extends string = string
>(
template: T,
options?: Omit<
PromptTemplateInput<RunInput, string, "f-string">,
"template" | "inputVariables"
>
): PromptTemplate<ExtractedFStringParams<T, RunInput>>;
static fromTemplate<
// eslint-disable-next-line @typescript-eslint/ban-types
RunInput extends InputValues = Symbol,
T extends string = string
>(
template: T,
options?: Omit<
PromptTemplateInput<RunInput, string>,
"template" | "inputVariables"
>
): PromptTemplate<ExtractedFStringParams<T, RunInput>>;
static fromTemplate<
// eslint-disable-next-line @typescript-eslint/ban-types
RunInput extends InputValues = Symbol,
T extends string = string
>(
template: T,
options?: Omit<
PromptTemplateInput<RunInput, string, "mustache">,
"template" | "inputVariables"
>
): PromptTemplate<InputValues>;
static fromTemplate<
// eslint-disable-next-line @typescript-eslint/ban-types
RunInput extends InputValues = Symbol,
T extends string = string
>(
template: T,
options?: Omit<
PromptTemplateInput<RunInput, string, TemplateFormat>,
"template" | "inputVariables"
>
): PromptTemplate<ExtractedFStringParams<T, RunInput> | InputValues> {
const { templateFormat = "f-string", ...rest } = options ?? {};
const names = new Set<string>();
parseTemplate(template, templateFormat).forEach((node) => {
if (node.type === "variable") {
names.add(node.name);
}
});
return new PromptTemplate({
// Rely on extracted types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
inputVariables: [...names] as any[],
templateFormat,
template,
...rest,
});
}
/**
* Partially applies values to the prompt template.
* @param values The values to be partially applied to the prompt template.
* @returns A new instance of PromptTemplate with the partially applied values.
*/
async partial<NewPartialVariableName extends string>(
values: PartialValues<NewPartialVariableName>
) {
const newInputVariables = this.inputVariables.filter(
(iv) => !(iv in values)
) as Exclude<Extract<keyof RunInput, string>, NewPartialVariableName>[];
const newPartialVariables = {
...(this.partialVariables ?? {}),
...values,
} as PartialValues<PartialVariableName | NewPartialVariableName>;
const promptDict = {
...this,
inputVariables: newInputVariables,
partialVariables: newPartialVariables,
};
return new PromptTemplate<
InputValues<
Exclude<Extract<keyof RunInput, string>, NewPartialVariableName>
>
>(promptDict);
}
serialize(): SerializedPromptTemplate {
if (this.outputParser !== undefined) {
throw new Error(
"Cannot serialize a prompt template with an output parser"
);
}
return {
_type: this._getPromptType(),
input_variables: this.inputVariables,
template: this.template,
template_format: this.templateFormat,
};
}
static async deserialize(
data: SerializedPromptTemplate
): Promise<PromptTemplate> {
if (!data.template) {
throw new Error("Prompt template must have a template");
}
const res = new PromptTemplate({
inputVariables: data.input_variables,
template: data.template,
templateFormat: data.template_format,
});
return res;
}
// TODO(from file)
}