-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
output_parser.ts
371 lines (336 loc) Β· 10.5 KB
/
output_parser.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
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
import { BaseCallbackConfig, Callbacks } from "../callbacks/manager.js";
import {
BasePromptValue,
Generation,
ChatGeneration,
BaseMessage,
isBaseMessage,
isBaseMessageChunk,
ChatGenerationChunk,
GenerationChunk,
} from "./index.js";
import { Runnable } from "./runnable/index.js";
import { RunnableConfig } from "./runnable/config.js";
import { deepCompareStrict } from "../util/@cfworker/json-schema/index.js";
/**
* Options for formatting instructions.
*/
export interface FormatInstructionsOptions {}
/**
* Abstract base class for parsing the output of a Large Language Model
* (LLM) call. It provides methods for parsing the result of an LLM call
* and invoking the parser with a given input.
*/
export abstract class BaseLLMOutputParser<T = unknown> extends Runnable<
string | BaseMessage,
T
> {
/**
* Parses the result of an LLM call. This method is meant to be
* implemented by subclasses to define how the output from the LLM should
* be parsed.
* @param generations The generations from an LLM call.
* @param callbacks Optional callbacks.
* @returns A promise of the parsed output.
*/
abstract parseResult(
generations: Generation[] | ChatGeneration[],
callbacks?: Callbacks
): Promise<T>;
/**
* Parses the result of an LLM call with a given prompt. By default, it
* simply calls `parseResult`.
* @param generations The generations from an LLM call.
* @param _prompt The prompt used in the LLM call.
* @param callbacks Optional callbacks.
* @returns A promise of the parsed output.
*/
parseResultWithPrompt(
generations: Generation[] | ChatGeneration[],
_prompt: BasePromptValue,
callbacks?: Callbacks
): Promise<T> {
return this.parseResult(generations, callbacks);
}
/**
* Calls the parser with a given input and optional configuration options.
* If the input is a string, it creates a generation with the input as
* text and calls `parseResult`. If the input is a `BaseMessage`, it
* creates a generation with the input as a message and the content of the
* input as text, and then calls `parseResult`.
* @param input The input to the parser, which can be a string or a `BaseMessage`.
* @param options Optional configuration options.
* @returns A promise of the parsed output.
*/
async invoke(
input: string | BaseMessage,
options?: RunnableConfig
): Promise<T> {
if (typeof input === "string") {
return this._callWithConfig(
async (input: string): Promise<T> =>
this.parseResult([{ text: input }]),
input,
{ ...options, runType: "parser" }
);
} else {
return this._callWithConfig(
async (input: BaseMessage): Promise<T> =>
this.parseResult([
{
message: input,
text:
typeof input.content === "string"
? input.content
: JSON.stringify(input.content),
},
]),
input,
{ ...options, runType: "parser" }
);
}
}
}
/**
* Class to parse the output of an LLM call.
*/
export abstract class BaseOutputParser<
T = unknown
> extends BaseLLMOutputParser<T> {
parseResult(
generations: Generation[] | ChatGeneration[],
callbacks?: Callbacks
): Promise<T> {
return this.parse(generations[0].text, callbacks);
}
/**
* Parse the output of an LLM call.
*
* @param text - LLM output to parse.
* @returns Parsed output.
*/
abstract parse(text: string, callbacks?: Callbacks): Promise<T>;
async parseWithPrompt(
text: string,
_prompt: BasePromptValue,
callbacks?: Callbacks
): Promise<T> {
return this.parse(text, callbacks);
}
/**
* Return a string describing the format of the output.
* @returns Format instructions.
* @param options - Options for formatting instructions.
* @example
* ```json
* {
* "foo": "bar"
* }
* ```
*/
abstract getFormatInstructions(options?: FormatInstructionsOptions): string;
/**
* Return the string type key uniquely identifying this class of parser
*/
_type(): string {
throw new Error("_type not implemented");
}
}
/**
* Class to parse the output of an LLM call that also allows streaming inputs.
*/
export abstract class BaseTransformOutputParser<
T = unknown
> extends BaseOutputParser<T> {
protected async *_transform(
inputGenerator: AsyncGenerator<string | BaseMessage>
): AsyncGenerator<T> {
for await (const chunk of inputGenerator) {
if (typeof chunk === "string") {
yield this.parseResult([{ text: chunk }]);
} else {
yield this.parseResult([
{
message: chunk,
text:
typeof chunk.content === "string"
? chunk.content
: JSON.stringify(chunk.content),
},
]);
}
}
}
/**
* Transforms an asynchronous generator of input into an asynchronous
* generator of parsed output.
* @param inputGenerator An asynchronous generator of input.
* @param options A configuration object.
* @returns An asynchronous generator of parsed output.
*/
async *transform(
inputGenerator: AsyncGenerator<string | BaseMessage>,
options: BaseCallbackConfig
): AsyncGenerator<T> {
yield* this._transformStreamWithConfig(
inputGenerator,
this._transform.bind(this),
{
...options,
runType: "parser",
}
);
}
}
export type BaseCumulativeTransformOutputParserInput = { diff?: boolean };
/**
* A base class for output parsers that can handle streaming input. It
* extends the `BaseTransformOutputParser` class and provides a method for
* converting parsed outputs into a diff format.
*/
export abstract class BaseCumulativeTransformOutputParser<
T = unknown
> extends BaseTransformOutputParser<T> {
protected diff = false;
constructor(fields?: BaseCumulativeTransformOutputParserInput) {
super(fields);
this.diff = fields?.diff ?? this.diff;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
protected abstract _diff(prev: any | undefined, next: any): any;
abstract parsePartialResult(
generations: Generation[] | ChatGeneration[]
): Promise<T | undefined>;
protected async *_transform(
inputGenerator: AsyncGenerator<string | BaseMessage>
): AsyncGenerator<T> {
let prevParsed: T | undefined;
let accGen: GenerationChunk | undefined;
for await (const chunk of inputGenerator) {
if (typeof chunk !== "string" && typeof chunk.content !== "string") {
throw new Error("Cannot handle non-string output.");
}
let chunkGen: GenerationChunk;
if (isBaseMessageChunk(chunk)) {
if (typeof chunk.content !== "string") {
throw new Error("Cannot handle non-string message output.");
}
chunkGen = new ChatGenerationChunk({
message: chunk,
text: chunk.content,
});
} else if (isBaseMessage(chunk)) {
if (typeof chunk.content !== "string") {
throw new Error("Cannot handle non-string message output.");
}
chunkGen = new ChatGenerationChunk({
message: chunk.toChunk(),
text: chunk.content,
});
} else {
chunkGen = new GenerationChunk({ text: chunk });
}
if (accGen === undefined) {
accGen = chunkGen;
} else {
accGen = accGen.concat(chunkGen);
}
const parsed = await this.parsePartialResult([accGen]);
if (
parsed !== undefined &&
parsed !== null &&
!deepCompareStrict(parsed, prevParsed)
) {
if (this.diff) {
yield this._diff(prevParsed, parsed);
} else {
yield parsed;
}
prevParsed = parsed;
}
}
}
}
/**
* OutputParser that parses LLMResult into the top likely string.
*/
export class StringOutputParser extends BaseTransformOutputParser<string> {
static lc_name() {
return "StrOutputParser";
}
lc_namespace = ["langchain", "schema", "output_parser"];
lc_serializable = true;
/**
* Parses a string output from an LLM call. This method is meant to be
* implemented by subclasses to define how a string output from an LLM
* should be parsed.
* @param text The string output from an LLM call.
* @param callbacks Optional callbacks.
* @returns A promise of the parsed output.
*/
parse(text: string): Promise<string> {
return Promise.resolve(text);
}
getFormatInstructions(): string {
return "";
}
}
/**
* OutputParser that parses LLMResult into the top likely string and
* encodes it into bytes.
*/
export class BytesOutputParser extends BaseTransformOutputParser<Uint8Array> {
static lc_name() {
return "BytesOutputParser";
}
lc_namespace = ["langchain", "schema", "output_parser"];
lc_serializable = true;
protected textEncoder = new TextEncoder();
parse(text: string): Promise<Uint8Array> {
return Promise.resolve(this.textEncoder.encode(text));
}
getFormatInstructions(): string {
return "";
}
}
/**
* Exception that output parsers should raise to signify a parsing error.
*
* This exists to differentiate parsing errors from other code or execution errors
* that also may arise inside the output parser. OutputParserExceptions will be
* available to catch and handle in ways to fix the parsing error, while other
* errors will be raised.
*
* @param message - The error that's being re-raised or an error message.
* @param llmOutput - String model output which is error-ing.
* @param observation - String explanation of error which can be passed to a
* model to try and remediate the issue.
* @param sendToLLM - Whether to send the observation and llm_output back to an Agent
* after an OutputParserException has been raised. This gives the underlying
* model driving the agent the context that the previous output was improperly
* structured, in the hopes that it will update the output to the correct
* format.
*/
export class OutputParserException extends Error {
llmOutput?: string;
observation?: string;
sendToLLM: boolean;
constructor(
message: string,
llmOutput?: string,
observation?: string,
sendToLLM = false
) {
super(message);
this.llmOutput = llmOutput;
this.observation = observation;
this.sendToLLM = sendToLLM;
if (sendToLLM) {
if (observation === undefined || llmOutput === undefined) {
throw new Error(
"Arguments 'observation' & 'llmOutput' are required if 'sendToLlm' is true"
);
}
}
}
}