@langchain/core 0.1.27-rc.1 → 0.1.27-rc.2
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/dist/runnables/base.cjs +202 -7
- package/dist/runnables/base.d.ts +47 -1
- package/dist/runnables/base.js +203 -8
- package/dist/runnables/utils.cjs +84 -0
- package/dist/runnables/utils.d.ts +25 -0
- package/dist/runnables/utils.js +80 -0
- package/dist/tracers/base.cjs +1 -1
- package/dist/tracers/base.d.ts +3 -1
- package/dist/tracers/base.js +1 -1
- package/dist/tracers/log_stream.cjs +109 -10
- package/dist/tracers/log_stream.d.ts +91 -4
- package/dist/tracers/log_stream.js +109 -10
- package/dist/utils/testing/index.cjs +4 -2
- package/dist/utils/testing/index.d.ts +1 -1
- package/dist/utils/testing/index.js +4 -2
- package/package.json +1 -1
package/dist/runnables/base.cjs
CHANGED
|
@@ -12,6 +12,7 @@ const stream_js_1 = require("../utils/stream.cjs");
|
|
|
12
12
|
const config_js_1 = require("./config.cjs");
|
|
13
13
|
const async_caller_js_1 = require("../utils/async_caller.cjs");
|
|
14
14
|
const root_listener_js_1 = require("../tracers/root_listener.cjs");
|
|
15
|
+
const utils_js_1 = require("./utils.cjs");
|
|
15
16
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
17
|
function _coerceToDict(value, defaultKey) {
|
|
17
18
|
return value &&
|
|
@@ -353,21 +354,28 @@ class Runnable extends serializable_js_1.Serializable {
|
|
|
353
354
|
* @param streamOptions
|
|
354
355
|
*/
|
|
355
356
|
async *streamLog(input, options, streamOptions) {
|
|
356
|
-
const
|
|
357
|
+
const logStreamCallbackHandler = new log_stream_js_1.LogStreamCallbackHandler({
|
|
357
358
|
...streamOptions,
|
|
358
359
|
autoClose: false,
|
|
360
|
+
_schemaFormat: "original",
|
|
359
361
|
});
|
|
360
362
|
const config = (0, config_js_1.ensureConfig)(options);
|
|
363
|
+
yield* this._streamLog(input, logStreamCallbackHandler, config);
|
|
364
|
+
}
|
|
365
|
+
async *_streamLog(input, logStreamCallbackHandler, config) {
|
|
361
366
|
const { callbacks } = config;
|
|
362
367
|
if (callbacks === undefined) {
|
|
363
|
-
|
|
368
|
+
// eslint-disable-next-line no-param-reassign
|
|
369
|
+
config.callbacks = [logStreamCallbackHandler];
|
|
364
370
|
}
|
|
365
371
|
else if (Array.isArray(callbacks)) {
|
|
366
|
-
|
|
372
|
+
// eslint-disable-next-line no-param-reassign
|
|
373
|
+
config.callbacks = callbacks.concat([logStreamCallbackHandler]);
|
|
367
374
|
}
|
|
368
375
|
else {
|
|
369
376
|
const copiedCallbacks = callbacks.copy();
|
|
370
|
-
copiedCallbacks.inheritableHandlers.push(
|
|
377
|
+
copiedCallbacks.inheritableHandlers.push(logStreamCallbackHandler);
|
|
378
|
+
// eslint-disable-next-line no-param-reassign
|
|
371
379
|
config.callbacks = copiedCallbacks;
|
|
372
380
|
}
|
|
373
381
|
const runnableStreamPromise = this.stream(input, config);
|
|
@@ -384,16 +392,16 @@ class Runnable extends serializable_js_1.Serializable {
|
|
|
384
392
|
},
|
|
385
393
|
],
|
|
386
394
|
});
|
|
387
|
-
await
|
|
395
|
+
await logStreamCallbackHandler.writer.write(patch);
|
|
388
396
|
}
|
|
389
397
|
}
|
|
390
398
|
finally {
|
|
391
|
-
await
|
|
399
|
+
await logStreamCallbackHandler.writer.close();
|
|
392
400
|
}
|
|
393
401
|
}
|
|
394
402
|
const runnableStreamConsumePromise = consumeRunnableStream();
|
|
395
403
|
try {
|
|
396
|
-
for await (const log of
|
|
404
|
+
for await (const log of logStreamCallbackHandler) {
|
|
397
405
|
yield log;
|
|
398
406
|
}
|
|
399
407
|
}
|
|
@@ -401,6 +409,187 @@ class Runnable extends serializable_js_1.Serializable {
|
|
|
401
409
|
await runnableStreamConsumePromise;
|
|
402
410
|
}
|
|
403
411
|
}
|
|
412
|
+
/**
|
|
413
|
+
* Generate a stream of events emitted by the internal steps of the runnable.
|
|
414
|
+
*
|
|
415
|
+
* Use to create an iterator over StreamEvents that provide real-time information
|
|
416
|
+
* about the progress of the runnable, including StreamEvents from intermediate
|
|
417
|
+
* results.
|
|
418
|
+
*
|
|
419
|
+
* A StreamEvent is a dictionary with the following schema:
|
|
420
|
+
*
|
|
421
|
+
* - `event`: string - Event names are of the format: on_[runnable_type]_(start|stream|end).
|
|
422
|
+
* - `name`: string - The name of the runnable that generated the event.
|
|
423
|
+
* - `run_id`: string - Randomly generated ID associated with the given execution of
|
|
424
|
+
* the runnable that emitted the event. A child runnable that gets invoked as part of the execution of a
|
|
425
|
+
* parent runnable is assigned its own unique ID.
|
|
426
|
+
* - `tags`: string[] - The tags of the runnable that generated the event.
|
|
427
|
+
* - `metadata`: Record<string, any> - The metadata of the runnable that generated the event.
|
|
428
|
+
* - `data`: Record<string, any>
|
|
429
|
+
*
|
|
430
|
+
* Below is a table that illustrates some events that might be emitted by various
|
|
431
|
+
* chains. Metadata fields have been omitted from the table for brevity.
|
|
432
|
+
* Chain definitions have been included after the table.
|
|
433
|
+
*
|
|
434
|
+
* | event | name | chunk | input | output |
|
|
435
|
+
* |----------------------|------------------|------------------------------------|-----------------------------------------------|-------------------------------------------------|
|
|
436
|
+
* | on_llm_start | [model name] | | {'input': 'hello'} | |
|
|
437
|
+
* | on_llm_stream | [model name] | 'Hello' OR AIMessageChunk("hello") | | |
|
|
438
|
+
* | on_llm_end | [model name] | | 'Hello human!' |
|
|
439
|
+
* | on_chain_start | format_docs | | | |
|
|
440
|
+
* | on_chain_stream | format_docs | "hello world!, goodbye world!" | | |
|
|
441
|
+
* | on_chain_end | format_docs | | [Document(...)] | "hello world!, goodbye world!" |
|
|
442
|
+
* | on_tool_start | some_tool | | {"x": 1, "y": "2"} | |
|
|
443
|
+
* | on_tool_stream | some_tool | {"x": 1, "y": "2"} | | |
|
|
444
|
+
* | on_tool_end | some_tool | | | {"x": 1, "y": "2"} |
|
|
445
|
+
* | on_retriever_start | [retriever name] | | {"query": "hello"} | |
|
|
446
|
+
* | on_retriever_chunk | [retriever name] | {documents: [...]} | | |
|
|
447
|
+
* | on_retriever_end | [retriever name] | | {"query": "hello"} | {documents: [...]} |
|
|
448
|
+
* | on_prompt_start | [template_name] | | {"question": "hello"} | |
|
|
449
|
+
* | on_prompt_end | [template_name] | | {"question": "hello"} | ChatPromptValue(messages: [SystemMessage, ...]) |
|
|
450
|
+
*/
|
|
451
|
+
async *streamEvents(input, options, streamOptions) {
|
|
452
|
+
if (options.version !== "v1") {
|
|
453
|
+
throw new Error(`Only version "v1" of the events schema is currently supported.`);
|
|
454
|
+
}
|
|
455
|
+
let runLog;
|
|
456
|
+
let hasEncounteredStartEvent = false;
|
|
457
|
+
const config = (0, config_js_1.ensureConfig)(options);
|
|
458
|
+
const rootTags = config.tags ?? [];
|
|
459
|
+
const rootMetadata = config.metadata ?? {};
|
|
460
|
+
const rootName = config.runName ?? this.getName();
|
|
461
|
+
const logStreamCallbackHandler = new log_stream_js_1.LogStreamCallbackHandler({
|
|
462
|
+
...streamOptions,
|
|
463
|
+
autoClose: false,
|
|
464
|
+
_schemaFormat: "streaming_events",
|
|
465
|
+
});
|
|
466
|
+
const rootEventFilter = new utils_js_1._RootEventFilter({
|
|
467
|
+
...streamOptions,
|
|
468
|
+
});
|
|
469
|
+
const logStream = this._streamLog(input, logStreamCallbackHandler, config);
|
|
470
|
+
for await (const log of logStream) {
|
|
471
|
+
if (!runLog) {
|
|
472
|
+
runLog = log_stream_js_1.RunLog.fromRunLogPatch(log);
|
|
473
|
+
}
|
|
474
|
+
else {
|
|
475
|
+
runLog = runLog.concat(log);
|
|
476
|
+
}
|
|
477
|
+
if (runLog.state === undefined) {
|
|
478
|
+
throw new Error(`Internal error: "streamEvents" state is missing. Please open a bug report.`);
|
|
479
|
+
}
|
|
480
|
+
// Yield the start event for the root runnable if it hasn't been seen.
|
|
481
|
+
// The root run is never filtered out
|
|
482
|
+
if (!hasEncounteredStartEvent) {
|
|
483
|
+
hasEncounteredStartEvent = true;
|
|
484
|
+
const state = { ...runLog.state };
|
|
485
|
+
const event = {
|
|
486
|
+
run_id: state.id,
|
|
487
|
+
event: `on_${state.type}_start`,
|
|
488
|
+
name: rootName,
|
|
489
|
+
tags: rootTags,
|
|
490
|
+
metadata: rootMetadata,
|
|
491
|
+
data: {
|
|
492
|
+
input,
|
|
493
|
+
},
|
|
494
|
+
};
|
|
495
|
+
if (rootEventFilter.includeEvent(event, state.type)) {
|
|
496
|
+
yield event;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
const paths = log.ops
|
|
500
|
+
.filter((op) => op.path.startsWith("/logs/"))
|
|
501
|
+
.map((op) => op.path.split("/")[2]);
|
|
502
|
+
const dedupedPaths = [...new Set(paths)];
|
|
503
|
+
for (const path of dedupedPaths) {
|
|
504
|
+
let eventType;
|
|
505
|
+
let data = {};
|
|
506
|
+
const logEntry = runLog.state.logs[path];
|
|
507
|
+
if (logEntry.end_time === undefined) {
|
|
508
|
+
if (logEntry.streamed_output.length > 0) {
|
|
509
|
+
eventType = "stream";
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
eventType = "start";
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
eventType = "end";
|
|
517
|
+
}
|
|
518
|
+
if (eventType === "start") {
|
|
519
|
+
// Include the inputs with the start event if they are available.
|
|
520
|
+
// Usually they will NOT be available for components that operate
|
|
521
|
+
// on streams, since those components stream the input and
|
|
522
|
+
// don't know its final value until the end of the stream.
|
|
523
|
+
if (logEntry.inputs !== undefined) {
|
|
524
|
+
data.input = logEntry.inputs;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
else if (eventType === "end") {
|
|
528
|
+
if (logEntry.inputs !== undefined) {
|
|
529
|
+
data.input = logEntry.inputs;
|
|
530
|
+
}
|
|
531
|
+
data.output = logEntry.final_output;
|
|
532
|
+
}
|
|
533
|
+
else if (eventType === "stream") {
|
|
534
|
+
const chunkCount = logEntry.streamed_output.length;
|
|
535
|
+
if (chunkCount !== 1) {
|
|
536
|
+
throw new Error(`Expected exactly one chunk of streamed output, got ${chunkCount} instead. Encountered in: "${logEntry.name}"`);
|
|
537
|
+
}
|
|
538
|
+
data = { chunk: logEntry.streamed_output[0] };
|
|
539
|
+
// Clean up the stream, we don't need it anymore.
|
|
540
|
+
// And this avoids duplicates as well!
|
|
541
|
+
logEntry.streamed_output = [];
|
|
542
|
+
}
|
|
543
|
+
yield {
|
|
544
|
+
event: `on_${logEntry.type}_${eventType}`,
|
|
545
|
+
name: logEntry.name,
|
|
546
|
+
run_id: logEntry.id,
|
|
547
|
+
tags: logEntry.tags,
|
|
548
|
+
metadata: logEntry.metadata,
|
|
549
|
+
data,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
// Finally, we take care of the streaming output from the root chain
|
|
553
|
+
// if there is any.
|
|
554
|
+
const { state } = runLog;
|
|
555
|
+
if (state.streamed_output.length > 0) {
|
|
556
|
+
const chunkCount = state.streamed_output.length;
|
|
557
|
+
if (chunkCount !== 1) {
|
|
558
|
+
throw new Error(`Expected exactly one chunk of streamed output, got ${chunkCount} instead. Encountered in: "${state.name}"`);
|
|
559
|
+
}
|
|
560
|
+
const data = { chunk: state.streamed_output[0] };
|
|
561
|
+
// Clean up the stream, we don't need it anymore.
|
|
562
|
+
state.streamed_output = [];
|
|
563
|
+
const event = {
|
|
564
|
+
event: `on_${state.type}_stream`,
|
|
565
|
+
run_id: state.id,
|
|
566
|
+
tags: rootTags,
|
|
567
|
+
metadata: rootMetadata,
|
|
568
|
+
name: rootName,
|
|
569
|
+
data,
|
|
570
|
+
};
|
|
571
|
+
if (rootEventFilter.includeEvent(event, state.type)) {
|
|
572
|
+
yield event;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
const state = runLog?.state;
|
|
577
|
+
if (state !== undefined) {
|
|
578
|
+
// Finally, yield the end event for the root runnable.
|
|
579
|
+
const event = {
|
|
580
|
+
event: `on_${state.type}_end`,
|
|
581
|
+
name: rootName,
|
|
582
|
+
run_id: state.id,
|
|
583
|
+
tags: rootTags,
|
|
584
|
+
metadata: rootMetadata,
|
|
585
|
+
data: {
|
|
586
|
+
output: state.final_output,
|
|
587
|
+
},
|
|
588
|
+
};
|
|
589
|
+
if (rootEventFilter.includeEvent(event, state.type))
|
|
590
|
+
yield event;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
404
593
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
405
594
|
static isRunnable(thing) {
|
|
406
595
|
return thing ? thing.lc_runnable : false;
|
|
@@ -540,6 +729,12 @@ class RunnableBinding extends Runnable {
|
|
|
540
729
|
generator, options) {
|
|
541
730
|
yield* this.bound.transform(generator, await this._mergeConfig(options, this.kwargs));
|
|
542
731
|
}
|
|
732
|
+
async *streamEvents(input, options, streamOptions) {
|
|
733
|
+
yield* this.bound.streamEvents(input, {
|
|
734
|
+
...(await this._mergeConfig(options, this.kwargs)),
|
|
735
|
+
version: options.version,
|
|
736
|
+
}, streamOptions);
|
|
737
|
+
}
|
|
543
738
|
static isRunnableBinding(
|
|
544
739
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
545
740
|
thing
|
package/dist/runnables/base.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CallbackManagerForChainRun } from "../callbacks/manager.js";
|
|
2
|
-
import { LogStreamCallbackHandlerInput, RunLogPatch } from "../tracers/log_stream.js";
|
|
2
|
+
import { LogStreamCallbackHandler, LogStreamCallbackHandlerInput, RunLogPatch, StreamEvent } from "../tracers/log_stream.js";
|
|
3
3
|
import { Serializable } from "../load/serializable.js";
|
|
4
4
|
import { IterableReadableStream, type IterableReadableStreamInterface } from "../utils/stream.js";
|
|
5
5
|
import { RunnableConfig } from "./config.js";
|
|
@@ -176,6 +176,49 @@ export declare abstract class Runnable<RunInput = any, RunOutput = any, CallOpti
|
|
|
176
176
|
* @param streamOptions
|
|
177
177
|
*/
|
|
178
178
|
streamLog(input: RunInput, options?: Partial<CallOptions>, streamOptions?: Omit<LogStreamCallbackHandlerInput, "autoClose">): AsyncGenerator<RunLogPatch>;
|
|
179
|
+
protected _streamLog(input: RunInput, logStreamCallbackHandler: LogStreamCallbackHandler, config: Partial<CallOptions>): AsyncGenerator<RunLogPatch>;
|
|
180
|
+
/**
|
|
181
|
+
* Generate a stream of events emitted by the internal steps of the runnable.
|
|
182
|
+
*
|
|
183
|
+
* Use to create an iterator over StreamEvents that provide real-time information
|
|
184
|
+
* about the progress of the runnable, including StreamEvents from intermediate
|
|
185
|
+
* results.
|
|
186
|
+
*
|
|
187
|
+
* A StreamEvent is a dictionary with the following schema:
|
|
188
|
+
*
|
|
189
|
+
* - `event`: string - Event names are of the format: on_[runnable_type]_(start|stream|end).
|
|
190
|
+
* - `name`: string - The name of the runnable that generated the event.
|
|
191
|
+
* - `run_id`: string - Randomly generated ID associated with the given execution of
|
|
192
|
+
* the runnable that emitted the event. A child runnable that gets invoked as part of the execution of a
|
|
193
|
+
* parent runnable is assigned its own unique ID.
|
|
194
|
+
* - `tags`: string[] - The tags of the runnable that generated the event.
|
|
195
|
+
* - `metadata`: Record<string, any> - The metadata of the runnable that generated the event.
|
|
196
|
+
* - `data`: Record<string, any>
|
|
197
|
+
*
|
|
198
|
+
* Below is a table that illustrates some events that might be emitted by various
|
|
199
|
+
* chains. Metadata fields have been omitted from the table for brevity.
|
|
200
|
+
* Chain definitions have been included after the table.
|
|
201
|
+
*
|
|
202
|
+
* | event | name | chunk | input | output |
|
|
203
|
+
* |----------------------|------------------|------------------------------------|-----------------------------------------------|-------------------------------------------------|
|
|
204
|
+
* | on_llm_start | [model name] | | {'input': 'hello'} | |
|
|
205
|
+
* | on_llm_stream | [model name] | 'Hello' OR AIMessageChunk("hello") | | |
|
|
206
|
+
* | on_llm_end | [model name] | | 'Hello human!' |
|
|
207
|
+
* | on_chain_start | format_docs | | | |
|
|
208
|
+
* | on_chain_stream | format_docs | "hello world!, goodbye world!" | | |
|
|
209
|
+
* | on_chain_end | format_docs | | [Document(...)] | "hello world!, goodbye world!" |
|
|
210
|
+
* | on_tool_start | some_tool | | {"x": 1, "y": "2"} | |
|
|
211
|
+
* | on_tool_stream | some_tool | {"x": 1, "y": "2"} | | |
|
|
212
|
+
* | on_tool_end | some_tool | | | {"x": 1, "y": "2"} |
|
|
213
|
+
* | on_retriever_start | [retriever name] | | {"query": "hello"} | |
|
|
214
|
+
* | on_retriever_chunk | [retriever name] | {documents: [...]} | | |
|
|
215
|
+
* | on_retriever_end | [retriever name] | | {"query": "hello"} | {documents: [...]} |
|
|
216
|
+
* | on_prompt_start | [template_name] | | {"question": "hello"} | |
|
|
217
|
+
* | on_prompt_end | [template_name] | | {"question": "hello"} | ChatPromptValue(messages: [SystemMessage, ...]) |
|
|
218
|
+
*/
|
|
219
|
+
streamEvents(input: RunInput, options: Partial<CallOptions> & {
|
|
220
|
+
version: "v1";
|
|
221
|
+
}, streamOptions?: Omit<LogStreamCallbackHandlerInput, "autoClose">): AsyncGenerator<StreamEvent>;
|
|
179
222
|
static isRunnable(thing: any): thing is Runnable;
|
|
180
223
|
/**
|
|
181
224
|
* Bind lifecycle listeners to a Runnable, returning a new Runnable.
|
|
@@ -231,6 +274,9 @@ export declare class RunnableBinding<RunInput, RunOutput, CallOptions extends Ru
|
|
|
231
274
|
_streamIterator(input: RunInput, options?: Partial<CallOptions> | undefined): AsyncGenerator<Awaited<RunOutput>, void, unknown>;
|
|
232
275
|
stream(input: RunInput, options?: Partial<CallOptions> | undefined): Promise<IterableReadableStream<RunOutput>>;
|
|
233
276
|
transform(generator: AsyncGenerator<RunInput>, options: Partial<CallOptions>): AsyncGenerator<RunOutput>;
|
|
277
|
+
streamEvents(input: RunInput, options: Partial<CallOptions> & {
|
|
278
|
+
version: "v1";
|
|
279
|
+
}, streamOptions?: Omit<LogStreamCallbackHandlerInput, "autoClose">): AsyncGenerator<StreamEvent>;
|
|
234
280
|
static isRunnableBinding(thing: any): thing is RunnableBinding<any, any, any>;
|
|
235
281
|
/**
|
|
236
282
|
* Bind lifecycle listeners to a Runnable, returning a new Runnable.
|
package/dist/runnables/base.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import pRetry from "p-retry";
|
|
2
2
|
import { CallbackManager, } from "../callbacks/manager.js";
|
|
3
|
-
import { LogStreamCallbackHandler, RunLogPatch, } from "../tracers/log_stream.js";
|
|
3
|
+
import { LogStreamCallbackHandler, RunLog, RunLogPatch, } from "../tracers/log_stream.js";
|
|
4
4
|
import { Serializable } from "../load/serializable.js";
|
|
5
5
|
import { IterableReadableStream, concat, atee, pipeGeneratorWithSetup, AsyncGeneratorWithSetup, } from "../utils/stream.js";
|
|
6
6
|
import { DEFAULT_RECURSION_LIMIT, ensureConfig, getCallbackManagerForConfig, mergeConfigs, patchConfig, } from "./config.js";
|
|
7
7
|
import { AsyncCaller } from "../utils/async_caller.js";
|
|
8
8
|
import { RootListenersTracer } from "../tracers/root_listener.js";
|
|
9
|
+
import { _RootEventFilter } from "./utils.js";
|
|
9
10
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
11
|
export function _coerceToDict(value, defaultKey) {
|
|
11
12
|
return value &&
|
|
@@ -346,21 +347,28 @@ export class Runnable extends Serializable {
|
|
|
346
347
|
* @param streamOptions
|
|
347
348
|
*/
|
|
348
349
|
async *streamLog(input, options, streamOptions) {
|
|
349
|
-
const
|
|
350
|
+
const logStreamCallbackHandler = new LogStreamCallbackHandler({
|
|
350
351
|
...streamOptions,
|
|
351
352
|
autoClose: false,
|
|
353
|
+
_schemaFormat: "original",
|
|
352
354
|
});
|
|
353
355
|
const config = ensureConfig(options);
|
|
356
|
+
yield* this._streamLog(input, logStreamCallbackHandler, config);
|
|
357
|
+
}
|
|
358
|
+
async *_streamLog(input, logStreamCallbackHandler, config) {
|
|
354
359
|
const { callbacks } = config;
|
|
355
360
|
if (callbacks === undefined) {
|
|
356
|
-
|
|
361
|
+
// eslint-disable-next-line no-param-reassign
|
|
362
|
+
config.callbacks = [logStreamCallbackHandler];
|
|
357
363
|
}
|
|
358
364
|
else if (Array.isArray(callbacks)) {
|
|
359
|
-
|
|
365
|
+
// eslint-disable-next-line no-param-reassign
|
|
366
|
+
config.callbacks = callbacks.concat([logStreamCallbackHandler]);
|
|
360
367
|
}
|
|
361
368
|
else {
|
|
362
369
|
const copiedCallbacks = callbacks.copy();
|
|
363
|
-
copiedCallbacks.inheritableHandlers.push(
|
|
370
|
+
copiedCallbacks.inheritableHandlers.push(logStreamCallbackHandler);
|
|
371
|
+
// eslint-disable-next-line no-param-reassign
|
|
364
372
|
config.callbacks = copiedCallbacks;
|
|
365
373
|
}
|
|
366
374
|
const runnableStreamPromise = this.stream(input, config);
|
|
@@ -377,16 +385,16 @@ export class Runnable extends Serializable {
|
|
|
377
385
|
},
|
|
378
386
|
],
|
|
379
387
|
});
|
|
380
|
-
await
|
|
388
|
+
await logStreamCallbackHandler.writer.write(patch);
|
|
381
389
|
}
|
|
382
390
|
}
|
|
383
391
|
finally {
|
|
384
|
-
await
|
|
392
|
+
await logStreamCallbackHandler.writer.close();
|
|
385
393
|
}
|
|
386
394
|
}
|
|
387
395
|
const runnableStreamConsumePromise = consumeRunnableStream();
|
|
388
396
|
try {
|
|
389
|
-
for await (const log of
|
|
397
|
+
for await (const log of logStreamCallbackHandler) {
|
|
390
398
|
yield log;
|
|
391
399
|
}
|
|
392
400
|
}
|
|
@@ -394,6 +402,187 @@ export class Runnable extends Serializable {
|
|
|
394
402
|
await runnableStreamConsumePromise;
|
|
395
403
|
}
|
|
396
404
|
}
|
|
405
|
+
/**
|
|
406
|
+
* Generate a stream of events emitted by the internal steps of the runnable.
|
|
407
|
+
*
|
|
408
|
+
* Use to create an iterator over StreamEvents that provide real-time information
|
|
409
|
+
* about the progress of the runnable, including StreamEvents from intermediate
|
|
410
|
+
* results.
|
|
411
|
+
*
|
|
412
|
+
* A StreamEvent is a dictionary with the following schema:
|
|
413
|
+
*
|
|
414
|
+
* - `event`: string - Event names are of the format: on_[runnable_type]_(start|stream|end).
|
|
415
|
+
* - `name`: string - The name of the runnable that generated the event.
|
|
416
|
+
* - `run_id`: string - Randomly generated ID associated with the given execution of
|
|
417
|
+
* the runnable that emitted the event. A child runnable that gets invoked as part of the execution of a
|
|
418
|
+
* parent runnable is assigned its own unique ID.
|
|
419
|
+
* - `tags`: string[] - The tags of the runnable that generated the event.
|
|
420
|
+
* - `metadata`: Record<string, any> - The metadata of the runnable that generated the event.
|
|
421
|
+
* - `data`: Record<string, any>
|
|
422
|
+
*
|
|
423
|
+
* Below is a table that illustrates some events that might be emitted by various
|
|
424
|
+
* chains. Metadata fields have been omitted from the table for brevity.
|
|
425
|
+
* Chain definitions have been included after the table.
|
|
426
|
+
*
|
|
427
|
+
* | event | name | chunk | input | output |
|
|
428
|
+
* |----------------------|------------------|------------------------------------|-----------------------------------------------|-------------------------------------------------|
|
|
429
|
+
* | on_llm_start | [model name] | | {'input': 'hello'} | |
|
|
430
|
+
* | on_llm_stream | [model name] | 'Hello' OR AIMessageChunk("hello") | | |
|
|
431
|
+
* | on_llm_end | [model name] | | 'Hello human!' |
|
|
432
|
+
* | on_chain_start | format_docs | | | |
|
|
433
|
+
* | on_chain_stream | format_docs | "hello world!, goodbye world!" | | |
|
|
434
|
+
* | on_chain_end | format_docs | | [Document(...)] | "hello world!, goodbye world!" |
|
|
435
|
+
* | on_tool_start | some_tool | | {"x": 1, "y": "2"} | |
|
|
436
|
+
* | on_tool_stream | some_tool | {"x": 1, "y": "2"} | | |
|
|
437
|
+
* | on_tool_end | some_tool | | | {"x": 1, "y": "2"} |
|
|
438
|
+
* | on_retriever_start | [retriever name] | | {"query": "hello"} | |
|
|
439
|
+
* | on_retriever_chunk | [retriever name] | {documents: [...]} | | |
|
|
440
|
+
* | on_retriever_end | [retriever name] | | {"query": "hello"} | {documents: [...]} |
|
|
441
|
+
* | on_prompt_start | [template_name] | | {"question": "hello"} | |
|
|
442
|
+
* | on_prompt_end | [template_name] | | {"question": "hello"} | ChatPromptValue(messages: [SystemMessage, ...]) |
|
|
443
|
+
*/
|
|
444
|
+
async *streamEvents(input, options, streamOptions) {
|
|
445
|
+
if (options.version !== "v1") {
|
|
446
|
+
throw new Error(`Only version "v1" of the events schema is currently supported.`);
|
|
447
|
+
}
|
|
448
|
+
let runLog;
|
|
449
|
+
let hasEncounteredStartEvent = false;
|
|
450
|
+
const config = ensureConfig(options);
|
|
451
|
+
const rootTags = config.tags ?? [];
|
|
452
|
+
const rootMetadata = config.metadata ?? {};
|
|
453
|
+
const rootName = config.runName ?? this.getName();
|
|
454
|
+
const logStreamCallbackHandler = new LogStreamCallbackHandler({
|
|
455
|
+
...streamOptions,
|
|
456
|
+
autoClose: false,
|
|
457
|
+
_schemaFormat: "streaming_events",
|
|
458
|
+
});
|
|
459
|
+
const rootEventFilter = new _RootEventFilter({
|
|
460
|
+
...streamOptions,
|
|
461
|
+
});
|
|
462
|
+
const logStream = this._streamLog(input, logStreamCallbackHandler, config);
|
|
463
|
+
for await (const log of logStream) {
|
|
464
|
+
if (!runLog) {
|
|
465
|
+
runLog = RunLog.fromRunLogPatch(log);
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
runLog = runLog.concat(log);
|
|
469
|
+
}
|
|
470
|
+
if (runLog.state === undefined) {
|
|
471
|
+
throw new Error(`Internal error: "streamEvents" state is missing. Please open a bug report.`);
|
|
472
|
+
}
|
|
473
|
+
// Yield the start event for the root runnable if it hasn't been seen.
|
|
474
|
+
// The root run is never filtered out
|
|
475
|
+
if (!hasEncounteredStartEvent) {
|
|
476
|
+
hasEncounteredStartEvent = true;
|
|
477
|
+
const state = { ...runLog.state };
|
|
478
|
+
const event = {
|
|
479
|
+
run_id: state.id,
|
|
480
|
+
event: `on_${state.type}_start`,
|
|
481
|
+
name: rootName,
|
|
482
|
+
tags: rootTags,
|
|
483
|
+
metadata: rootMetadata,
|
|
484
|
+
data: {
|
|
485
|
+
input,
|
|
486
|
+
},
|
|
487
|
+
};
|
|
488
|
+
if (rootEventFilter.includeEvent(event, state.type)) {
|
|
489
|
+
yield event;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
const paths = log.ops
|
|
493
|
+
.filter((op) => op.path.startsWith("/logs/"))
|
|
494
|
+
.map((op) => op.path.split("/")[2]);
|
|
495
|
+
const dedupedPaths = [...new Set(paths)];
|
|
496
|
+
for (const path of dedupedPaths) {
|
|
497
|
+
let eventType;
|
|
498
|
+
let data = {};
|
|
499
|
+
const logEntry = runLog.state.logs[path];
|
|
500
|
+
if (logEntry.end_time === undefined) {
|
|
501
|
+
if (logEntry.streamed_output.length > 0) {
|
|
502
|
+
eventType = "stream";
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
eventType = "start";
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
eventType = "end";
|
|
510
|
+
}
|
|
511
|
+
if (eventType === "start") {
|
|
512
|
+
// Include the inputs with the start event if they are available.
|
|
513
|
+
// Usually they will NOT be available for components that operate
|
|
514
|
+
// on streams, since those components stream the input and
|
|
515
|
+
// don't know its final value until the end of the stream.
|
|
516
|
+
if (logEntry.inputs !== undefined) {
|
|
517
|
+
data.input = logEntry.inputs;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
else if (eventType === "end") {
|
|
521
|
+
if (logEntry.inputs !== undefined) {
|
|
522
|
+
data.input = logEntry.inputs;
|
|
523
|
+
}
|
|
524
|
+
data.output = logEntry.final_output;
|
|
525
|
+
}
|
|
526
|
+
else if (eventType === "stream") {
|
|
527
|
+
const chunkCount = logEntry.streamed_output.length;
|
|
528
|
+
if (chunkCount !== 1) {
|
|
529
|
+
throw new Error(`Expected exactly one chunk of streamed output, got ${chunkCount} instead. Encountered in: "${logEntry.name}"`);
|
|
530
|
+
}
|
|
531
|
+
data = { chunk: logEntry.streamed_output[0] };
|
|
532
|
+
// Clean up the stream, we don't need it anymore.
|
|
533
|
+
// And this avoids duplicates as well!
|
|
534
|
+
logEntry.streamed_output = [];
|
|
535
|
+
}
|
|
536
|
+
yield {
|
|
537
|
+
event: `on_${logEntry.type}_${eventType}`,
|
|
538
|
+
name: logEntry.name,
|
|
539
|
+
run_id: logEntry.id,
|
|
540
|
+
tags: logEntry.tags,
|
|
541
|
+
metadata: logEntry.metadata,
|
|
542
|
+
data,
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
// Finally, we take care of the streaming output from the root chain
|
|
546
|
+
// if there is any.
|
|
547
|
+
const { state } = runLog;
|
|
548
|
+
if (state.streamed_output.length > 0) {
|
|
549
|
+
const chunkCount = state.streamed_output.length;
|
|
550
|
+
if (chunkCount !== 1) {
|
|
551
|
+
throw new Error(`Expected exactly one chunk of streamed output, got ${chunkCount} instead. Encountered in: "${state.name}"`);
|
|
552
|
+
}
|
|
553
|
+
const data = { chunk: state.streamed_output[0] };
|
|
554
|
+
// Clean up the stream, we don't need it anymore.
|
|
555
|
+
state.streamed_output = [];
|
|
556
|
+
const event = {
|
|
557
|
+
event: `on_${state.type}_stream`,
|
|
558
|
+
run_id: state.id,
|
|
559
|
+
tags: rootTags,
|
|
560
|
+
metadata: rootMetadata,
|
|
561
|
+
name: rootName,
|
|
562
|
+
data,
|
|
563
|
+
};
|
|
564
|
+
if (rootEventFilter.includeEvent(event, state.type)) {
|
|
565
|
+
yield event;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
const state = runLog?.state;
|
|
570
|
+
if (state !== undefined) {
|
|
571
|
+
// Finally, yield the end event for the root runnable.
|
|
572
|
+
const event = {
|
|
573
|
+
event: `on_${state.type}_end`,
|
|
574
|
+
name: rootName,
|
|
575
|
+
run_id: state.id,
|
|
576
|
+
tags: rootTags,
|
|
577
|
+
metadata: rootMetadata,
|
|
578
|
+
data: {
|
|
579
|
+
output: state.final_output,
|
|
580
|
+
},
|
|
581
|
+
};
|
|
582
|
+
if (rootEventFilter.includeEvent(event, state.type))
|
|
583
|
+
yield event;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
397
586
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
398
587
|
static isRunnable(thing) {
|
|
399
588
|
return thing ? thing.lc_runnable : false;
|
|
@@ -532,6 +721,12 @@ export class RunnableBinding extends Runnable {
|
|
|
532
721
|
generator, options) {
|
|
533
722
|
yield* this.bound.transform(generator, await this._mergeConfig(options, this.kwargs));
|
|
534
723
|
}
|
|
724
|
+
async *streamEvents(input, options, streamOptions) {
|
|
725
|
+
yield* this.bound.streamEvents(input, {
|
|
726
|
+
...(await this._mergeConfig(options, this.kwargs)),
|
|
727
|
+
version: options.version,
|
|
728
|
+
}, streamOptions);
|
|
729
|
+
}
|
|
535
730
|
static isRunnableBinding(
|
|
536
731
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
537
732
|
thing
|