import pRetry from "p-retry"; import { CallbackManager, CallbackManagerForChainRun, BaseCallbackConfig, } from "../../callbacks/manager.js"; import { Serializable } from "../../load/serializable.js"; import { IterableReadableStream } from "../../util/stream.js"; import { RunnableConfig as _RunnableConfig, getCallbackMangerForConfig, } from "./config.js"; import { AsyncCaller } from "../../util/async_caller.js"; export type RunnableConfig = _RunnableConfig; export type RunnableFunc = ( input: RunInput ) => RunOutput | Promise; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type RunnableLike = | Runnable | RunnableFunc | { [key: string]: RunnableLike }; export type RunnableBatchOptions = { maxConcurrency?: number; returnExceptions?: boolean; }; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type RunnableRetryFailedAttemptHandler = (error: any) => any; type RunnableConfigAndOptions = RunnableConfig & { runType?: string }; // eslint-disable-next-line @typescript-eslint/no-explicit-any function _coerceToDict(value: any, defaultKey: string) { return value && !Array.isArray(value) && typeof value === "object" ? value : { [defaultKey]: value }; } /** * A Runnable is a generic unit of work that can be invoked, batched, streamed, and/or * transformed. */ export abstract class Runnable< // eslint-disable-next-line @typescript-eslint/no-explicit-any RunInput = any, // eslint-disable-next-line @typescript-eslint/no-explicit-any RunOutput = any, CallOptions extends RunnableConfig = RunnableConfig > extends Serializable { protected lc_runnable = true; abstract invoke( input: RunInput, options?: Partial ): Promise; /** * Bind arguments to a Runnable, returning a new Runnable. * @param kwargs * @returns A new RunnableBinding that, when invoked, will apply the bound args. */ bind( kwargs: Partial ): RunnableBinding { // eslint-disable-next-line @typescript-eslint/no-use-before-define return new RunnableBinding({ bound: this, kwargs }); } /** * Bind arguments to a Runnable, returning a new Runnable. * @param kwargs * @returns A new RunnableBinding that, when invoked, will apply the bound args. */ withRetry(fields?: { stopAfterAttempt?: number; onFailedAttempt?: RunnableRetryFailedAttemptHandler; }): RunnableRetry { // eslint-disable-next-line @typescript-eslint/no-use-before-define return new RunnableRetry({ bound: this, kwargs: {}, maxAttemptNumber: fields?.stopAfterAttempt, ...fields, }); } /** * Create a new runnable from the current one that will try invoking * other passed fallback runnables if the initial invocation fails. * @param fields.fallbacks Other runnables to call if the runnable errors. * @returns A new RunnableWithFallbacks. */ withFallbacks(fields: { fallbacks: Runnable[]; }): RunnableWithFallbacks { // eslint-disable-next-line @typescript-eslint/no-use-before-define return new RunnableWithFallbacks({ runnable: this, fallbacks: fields.fallbacks, }); } protected _getOptionsList( options: Partial | Partial[], length = 0 ): Partial[] { if (Array.isArray(options)) { if (options.length !== length) { throw new Error( `Passed "options" must be an array with the same length as the inputs, but got ${options.length} options for ${length} inputs` ); } return options; } return Array.from({ length }, () => options); } /** * Default implementation of batch, which calls invoke N times. * Subclasses should override this method if they can batch more efficiently. * @param inputs Array of inputs to each batch call. * @param options Either a single call options object to apply to each batch call or an array for each call. * @param batchOptions.maxConcurrency Maximum number of calls to run at once. * @param batchOptions.returnExceptions Whether to return errors rather than throwing on the first one * @returns An array of RunOutputs, or mixed RunOutputs and errors if batchOptions.returnExceptions is set */ async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions & { returnExceptions?: false } ): Promise; async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions & { returnExceptions: true } ): Promise<(RunOutput | Error)[]>; async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions ): Promise<(RunOutput | Error)[]>; async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions ): Promise<(RunOutput | Error)[]> { const configList = this._getOptionsList(options ?? {}, inputs.length); const caller = new AsyncCaller({ maxConcurrency: batchOptions?.maxConcurrency, onFailedAttempt: (e) => { throw e; }, }); const batchCalls = inputs.map((input, i) => caller.call(async () => { try { const result = await this.invoke(input, configList[i]); return result; } catch (e) { if (batchOptions?.returnExceptions) { return e as Error; } throw e; } }) ); return Promise.all(batchCalls); } /** * Default streaming implementation. * Subclasses should override this method if they support streaming output. * @param input * @param options */ async *_streamIterator( input: RunInput, options?: Partial ): AsyncGenerator { yield this.invoke(input, options); } /** * Stream output in chunks. * @param input * @param options * @returns A readable stream that is also an iterable. */ async stream( input: RunInput, options?: Partial ): Promise> { return IterableReadableStream.fromAsyncGenerator( this._streamIterator(input, options) ); } protected _separateRunnableConfigFromCallOptions( options: Partial = {} ): [RunnableConfig, Omit, keyof RunnableConfig>] { const runnableConfig: RunnableConfig = { callbacks: options.callbacks, tags: options.tags, metadata: options.metadata, }; const callOptions = { ...options }; delete callOptions.callbacks; delete callOptions.tags; delete callOptions.metadata; return [runnableConfig, callOptions]; } protected async _callWithConfig( func: | ((input: T) => Promise) | (( input: T, config?: RunnableConfig, runManager?: CallbackManagerForChainRun ) => Promise), input: T, options?: RunnableConfigAndOptions ) { const callbackManager_ = await getCallbackMangerForConfig(options); const runManager = await callbackManager_?.handleChainStart( this.toJSON(), _coerceToDict(input, "input"), undefined, options?.runType ); let output; try { output = await func.bind(this)(input, options, runManager); } catch (e) { await runManager?.handleChainError(e); throw e; } await runManager?.handleChainEnd(_coerceToDict(output, "output")); return output; } /** * Internal method that handles batching and configuration for a runnable * It takes a function, input values, and optional configuration, and * returns a promise that resolves to the output values. * @param func The function to be executed for each input value. * @param input The input values to be processed. * @param config Optional configuration for the function execution. * @returns A promise that resolves to the output values. */ async _batchWithConfig( func: ( inputs: T[], configs?: RunnableConfig[], runManagers?: (CallbackManagerForChainRun | undefined)[], batchOptions?: RunnableBatchOptions ) => Promise<(RunOutput | Error)[]>, inputs: T[], options?: | Partial | Partial[], batchOptions?: RunnableBatchOptions ): Promise<(RunOutput | Error)[]> { const configs = this._getOptionsList( (options ?? {}) as CallOptions, inputs.length ); const callbackManagers = await Promise.all( configs.map(getCallbackMangerForConfig) ); const runManagers = await Promise.all( callbackManagers.map((callbackManager, i) => callbackManager?.handleChainStart( this.toJSON(), _coerceToDict(inputs[i], "input") ) ) ); let outputs: (RunOutput | Error)[]; try { outputs = await func(inputs, configs, runManagers, batchOptions); } catch (e) { await Promise.all( runManagers.map((runManager) => runManager?.handleChainError(e)) ); throw e; } await Promise.all( runManagers.map((runManager) => runManager?.handleChainEnd(_coerceToDict(outputs, "output")) ) ); return outputs; } /** * Helper method to transform an Iterator of Input values into an Iterator of * Output values, with callbacks. * Use this to implement `stream()` or `transform()` in Runnable subclasses. */ protected async *_transformStreamWithConfig< I extends RunInput, O extends RunOutput >( inputGenerator: AsyncGenerator, transformer: ( generator: AsyncGenerator, runManager?: CallbackManagerForChainRun, options?: Partial ) => AsyncGenerator, options?: RunnableConfig & { runType?: string } ): AsyncGenerator { let finalInput: I | undefined; let finalInputSupported = true; let finalOutput: O | undefined; let finalOutputSupported = true; const callbackManager_ = await getCallbackMangerForConfig(options); let runManager: CallbackManagerForChainRun | undefined; const serializedRepresentation = this.toJSON(); async function* wrapInputForTracing() { for await (const chunk of inputGenerator) { if (!runManager) { // Start the run manager AFTER the iterator starts to preserve // tracing order runManager = await callbackManager_?.handleChainStart( serializedRepresentation, { input: "" }, undefined, options?.runType ); } if (finalInputSupported) { if (finalInput === undefined) { finalInput = chunk; } else { try { // eslint-disable-next-line @typescript-eslint/no-explicit-any finalInput = (finalInput as any).concat(chunk); } catch { finalInput = undefined; finalInputSupported = false; } } } yield chunk; } } const wrappedInputGenerator = wrapInputForTracing(); try { const outputIterator = transformer( wrappedInputGenerator, runManager, options ); for await (const chunk of outputIterator) { yield chunk; if (finalOutputSupported) { if (finalOutput === undefined) { finalOutput = chunk; } else { try { // eslint-disable-next-line @typescript-eslint/no-explicit-any finalOutput = (finalOutput as any).concat(chunk); } catch { finalOutput = undefined; finalOutputSupported = false; } } } } } catch (e) { await runManager?.handleChainError(e, undefined, undefined, undefined, { inputs: _coerceToDict(finalInput, "input"), }); throw e; } await runManager?.handleChainEnd( finalOutput ?? {}, undefined, undefined, undefined, { inputs: _coerceToDict(finalInput, "input") } ); } _patchConfig( config: Partial = {}, callbackManager: CallbackManager | undefined = undefined ): Partial { return { ...config, callbacks: callbackManager }; } /** * Create a new runnable sequence that runs each individual runnable in series, * piping the output of one runnable into another runnable or runnable-like. * @param coerceable A runnable, function, or object whose values are functions or runnables. * @returns A new runnable sequence. */ pipe( coerceable: RunnableLike ): RunnableSequence> { // eslint-disable-next-line @typescript-eslint/no-use-before-define return new RunnableSequence({ first: this, last: _coerceToRunnable(coerceable), }); } /** * Default implementation of transform, which buffers input and then calls stream. * Subclasses should override this method if they can start producing output while * input is still being generated. * @param generator * @param options */ async *transform( generator: AsyncGenerator, options: Partial ): AsyncGenerator { let finalChunk; for await (const chunk of generator) { if (!finalChunk) { finalChunk = chunk; } else { // Make a best effort to gather, for any type that supports concat. // This method should throw an error if gathering fails. // eslint-disable-next-line @typescript-eslint/no-explicit-any finalChunk = (finalChunk as any).concat(chunk); } } yield* this._streamIterator(finalChunk, options); } // eslint-disable-next-line @typescript-eslint/no-explicit-any static isRunnable(thing: any): thing is Runnable { return thing.lc_runnable; } } export type RunnableBindingArgs< RunInput, RunOutput, CallOptions extends RunnableConfig > = { bound: Runnable; kwargs: Partial; }; /** * A runnable that delegates calls to another runnable with a set of kwargs. */ export class RunnableBinding< RunInput, RunOutput, CallOptions extends BaseCallbackConfig > extends Runnable { static lc_name() { return "RunnableBinding"; } lc_namespace = ["langchain", "schema", "runnable"]; lc_serializable = true; bound: Runnable; protected kwargs: Partial; constructor(fields: RunnableBindingArgs) { super(fields); this.bound = fields.bound; this.kwargs = fields.kwargs; } bind( kwargs: Partial ): RunnableBinding { return new RunnableBinding({ bound: this.bound, kwargs: { ...this.kwargs, ...kwargs }, }); } async invoke( input: RunInput, options?: Partial ): Promise { return this.bound.invoke(input, { ...options, ...this.kwargs }); } async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions & { returnExceptions?: false } ): Promise; async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions & { returnExceptions: true } ): Promise<(RunOutput | Error)[]>; async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions ): Promise<(RunOutput | Error)[]>; async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions ): Promise<(RunOutput | Error)[]> { const mergedOptions = Array.isArray(options) ? options.map((individualOption) => ({ ...individualOption, ...this.kwargs, })) : { ...options, ...this.kwargs }; return this.bound.batch(inputs, mergedOptions, batchOptions); } async *_streamIterator( input: RunInput, options?: Partial | undefined ) { yield* this.bound._streamIterator(input, { ...options, ...this.kwargs }); } async stream( input: RunInput, options?: Partial | undefined ): Promise> { return this.bound.stream(input, { ...options, ...this.kwargs }); } async *transform( // eslint-disable-next-line @typescript-eslint/no-explicit-any generator: AsyncGenerator, options: Partial ): AsyncGenerator { yield* this.bound.transform(generator, options); } static isRunnableBinding( // eslint-disable-next-line @typescript-eslint/no-explicit-any thing: any // eslint-disable-next-line @typescript-eslint/no-explicit-any ): thing is RunnableBinding { return thing.bound && Runnable.isRunnable(thing.bound); } } /** * Base class for runnables that can be retried a * specified number of times. */ export class RunnableRetry< // eslint-disable-next-line @typescript-eslint/no-explicit-any RunInput = any, // eslint-disable-next-line @typescript-eslint/no-explicit-any RunOutput = any, CallOptions extends RunnableConfig = RunnableConfig > extends RunnableBinding { static lc_name() { return "RunnableRetry"; } lc_namespace = ["langchain", "schema", "runnable"]; protected maxAttemptNumber = 3; // eslint-disable-next-line @typescript-eslint/no-explicit-any onFailedAttempt?: RunnableRetryFailedAttemptHandler = () => {}; constructor( fields: RunnableBindingArgs & { maxAttemptNumber?: number; // eslint-disable-next-line @typescript-eslint/no-explicit-any onFailedAttempt?: RunnableRetryFailedAttemptHandler; } ) { super(fields); this.maxAttemptNumber = fields.maxAttemptNumber ?? this.maxAttemptNumber; this.onFailedAttempt = fields.onFailedAttempt ?? this.onFailedAttempt; } _patchConfigForRetry( attempt: number, config?: Partial, runManager?: CallbackManagerForChainRun ): Partial { const tag = attempt > 1 ? `retry:attempt:${attempt}` : undefined; return this._patchConfig(config, runManager?.getChild(tag)); } protected async _invoke( input: RunInput, config?: CallOptions, runManager?: CallbackManagerForChainRun ): Promise { return pRetry( (attemptNumber: number) => super.invoke( input, this._patchConfigForRetry(attemptNumber, config, runManager) ), { onFailedAttempt: this.onFailedAttempt, retries: Math.max(this.maxAttemptNumber - 1, 0), randomize: true, } ); } /** * Method that invokes the runnable with the specified input, run manager, * and config. It handles the retry logic by catching any errors and * recursively invoking itself with the updated config for the next retry * attempt. * @param input The input for the runnable. * @param runManager The run manager for the runnable. * @param config The config for the runnable. * @returns A promise that resolves to the output of the runnable. */ async invoke(input: RunInput, config?: CallOptions): Promise { return this._callWithConfig(this._invoke, input, config); } async _batch( inputs: RunInput[], configs?: RunnableConfig[], runManagers?: (CallbackManagerForChainRun | undefined)[], batchOptions?: RunnableBatchOptions ) { const resultsMap: Record = {}; try { await pRetry( async (attemptNumber: number) => { const remainingIndexes = inputs .map((_, i) => i) .filter( (i) => resultsMap[i.toString()] === undefined || // eslint-disable-next-line no-instanceof/no-instanceof resultsMap[i.toString()] instanceof Error ); const remainingInputs = remainingIndexes.map((i) => inputs[i]); const patchedConfigs = remainingIndexes.map((i) => this._patchConfigForRetry( attemptNumber, configs?.[i] as CallOptions, runManagers?.[i] ) ); const results = await super.batch(remainingInputs, patchedConfigs, { ...batchOptions, returnExceptions: true, }); let firstException; for (let i = 0; i < results.length; i += 1) { const result = results[i]; const resultMapIndex = remainingIndexes[i]; // eslint-disable-next-line no-instanceof/no-instanceof if (result instanceof Error) { if (firstException === undefined) { firstException = result; } } resultsMap[resultMapIndex.toString()] = result; } if (firstException) { throw firstException; } return results; }, { onFailedAttempt: this.onFailedAttempt, retries: Math.max(this.maxAttemptNumber - 1, 0), randomize: true, } ); } catch (e) { if (batchOptions?.returnExceptions !== true) { throw e; } } return Object.keys(resultsMap) .sort((a, b) => parseInt(a, 10) - parseInt(b, 10)) .map( (key) => resultsMap[parseInt(key, 10)] ) as ReturnExceptions extends false ? RunOutput[] : (RunOutput | Error)[]; } async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions & { returnExceptions?: false } ): Promise; async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions & { returnExceptions: true } ): Promise<(RunOutput | Error)[]>; async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions ): Promise<(RunOutput | Error)[]>; async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions ): Promise<(RunOutput | Error)[]> { return this._batchWithConfig( this._batch.bind(this), inputs, options, batchOptions ); } } /** * A sequence of runnables, where the output of each is the input of the next. */ export class RunnableSequence< // eslint-disable-next-line @typescript-eslint/no-explicit-any RunInput = any, // eslint-disable-next-line @typescript-eslint/no-explicit-any RunOutput = any > extends Runnable { static lc_name() { return "RunnableSequence"; } protected first: Runnable; protected middle: Runnable[] = []; // eslint-disable-next-line @typescript-eslint/no-explicit-any protected last: Runnable; lc_serializable = true; lc_namespace = ["langchain", "schema", "runnable"]; constructor(fields: { first: Runnable; middle?: Runnable[]; // eslint-disable-next-line @typescript-eslint/no-explicit-any last: Runnable; }) { super(fields); this.first = fields.first; this.middle = fields.middle ?? this.middle; this.last = fields.last; } get steps() { return [this.first, ...this.middle, this.last]; } async invoke(input: RunInput, options?: RunnableConfig): Promise { const callbackManager_ = await getCallbackMangerForConfig(options); const runManager = await callbackManager_?.handleChainStart( this.toJSON(), _coerceToDict(input, "input") ); let nextStepInput = input; let finalOutput: RunOutput; try { for (const step of [this.first, ...this.middle]) { nextStepInput = await step.invoke( nextStepInput, this._patchConfig(options, runManager?.getChild()) ); } // TypeScript can't detect that the last output of the sequence returns RunOutput, so call it out of the loop here finalOutput = await this.last.invoke( nextStepInput, this._patchConfig(options, runManager?.getChild()) ); } catch (e) { await runManager?.handleChainError(e); throw e; } await runManager?.handleChainEnd(_coerceToDict(finalOutput, "output")); return finalOutput; } async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions & { returnExceptions?: false } ): Promise; async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions & { returnExceptions: true } ): Promise<(RunOutput | Error)[]>; async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions ): Promise<(RunOutput | Error)[]>; async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions ): Promise<(RunOutput | Error)[]> { const configList = this._getOptionsList(options ?? {}, inputs.length); const callbackManagers = await Promise.all( configList.map(getCallbackMangerForConfig) ); const runManagers = await Promise.all( callbackManagers.map((callbackManager, i) => callbackManager?.handleChainStart( this.toJSON(), _coerceToDict(inputs[i], "input") ) ) ); // eslint-disable-next-line @typescript-eslint/no-explicit-any let nextStepInputs: any = inputs; let finalOutputs: (RunOutput | Error)[]; try { for (let i = 0; i < [this.first, ...this.middle].length; i += 1) { const step = this.steps[i]; nextStepInputs = await step.batch( nextStepInputs, runManagers.map((runManager, j) => this._patchConfig(configList[j], runManager?.getChild()) ), batchOptions ); } finalOutputs = await this.last.batch( nextStepInputs, runManagers.map((runManager) => this._patchConfig( configList[this.steps.length - 1], runManager?.getChild() ) ), batchOptions ); } catch (e) { await Promise.all( runManagers.map((runManager) => runManager?.handleChainError(e)) ); throw e; } await Promise.all( runManagers.map((runManager, i) => runManager?.handleChainEnd(_coerceToDict(finalOutputs[i], "output")) ) ); return finalOutputs; } async *_streamIterator( input: RunInput, options?: RunnableConfig ): AsyncGenerator { const callbackManager_ = await getCallbackMangerForConfig(options); const runManager = await callbackManager_?.handleChainStart( this.toJSON(), _coerceToDict(input, "input") ); let nextStepInput = input; const steps = [this.first, ...this.middle, this.last]; // Find the index of the last runnable in the sequence that doesn't have an overridden .transform() method // and start streaming from there const streamingStartStepIndex = Math.min( steps.length - 1, steps.length - [...steps].reverse().findIndex((step) => { const isDefaultImplementation = step.transform === Runnable.prototype.transform; const boundRunnableIsDefaultImplementation = RunnableBinding.isRunnableBinding(step) && step.bound?.transform === Runnable.prototype.transform; return ( isDefaultImplementation || boundRunnableIsDefaultImplementation ); }) - 1 ); try { for (const step of steps.slice(0, streamingStartStepIndex)) { nextStepInput = await step.invoke( nextStepInput, this._patchConfig(options, runManager?.getChild()) ); } } catch (e) { await runManager?.handleChainError(e); throw e; } let concatSupported = true; let finalOutput; try { let finalGenerator = await steps[streamingStartStepIndex]._streamIterator( nextStepInput, this._patchConfig(options, runManager?.getChild()) ); for (const step of steps.slice(streamingStartStepIndex + 1)) { finalGenerator = await step.transform( finalGenerator, this._patchConfig(options, runManager?.getChild()) ); } for await (const chunk of finalGenerator) { yield chunk; if (concatSupported) { if (finalOutput === undefined) { finalOutput = chunk; } else { try { // eslint-disable-next-line @typescript-eslint/no-explicit-any finalOutput = (finalOutput as any).concat(chunk); } catch (e) { finalOutput = undefined; concatSupported = false; } } } } } catch (e) { await runManager?.handleChainError(e); throw e; } await runManager?.handleChainEnd(_coerceToDict(finalOutput, "output")); } pipe( coerceable: RunnableLike ): RunnableSequence> { if (RunnableSequence.isRunnableSequence(coerceable)) { return new RunnableSequence({ first: this.first, middle: this.middle.concat([ this.last, coerceable.first, ...coerceable.middle, ]), last: coerceable.last, }); } else { return new RunnableSequence({ first: this.first, middle: [...this.middle, this.last], last: _coerceToRunnable(coerceable), }); } } // eslint-disable-next-line @typescript-eslint/no-explicit-any static isRunnableSequence(thing: any): thing is RunnableSequence { return Array.isArray(thing.middle) && Runnable.isRunnable(thing); } static from([first, ...runnables]: [ RunnableLike, ...RunnableLike[], // eslint-disable-next-line @typescript-eslint/no-explicit-any RunnableLike ]) { return new RunnableSequence>({ first: _coerceToRunnable(first), middle: runnables.slice(0, -1).map(_coerceToRunnable), last: _coerceToRunnable(runnables[runnables.length - 1]), }); } } /** * A runnable that runs a mapping of runnables in parallel, * and returns a mapping of their outputs. */ export class RunnableMap extends Runnable< RunInput, // eslint-disable-next-line @typescript-eslint/no-explicit-any Record > { static lc_name() { return "RunnableMap"; } lc_namespace = ["langchain", "schema", "runnable"]; lc_serializable = true; protected steps: Record>; constructor(fields: { steps: Record> }) { super(fields); this.steps = {}; for (const [key, value] of Object.entries(fields.steps)) { this.steps[key] = _coerceToRunnable(value); } } async invoke( input: RunInput, options?: Partial // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise> { const callbackManager_ = await getCallbackMangerForConfig(options); const runManager = await callbackManager_?.handleChainStart(this.toJSON(), { input, }); // eslint-disable-next-line @typescript-eslint/no-explicit-any const output: Record = {}; try { for (const [key, runnable] of Object.entries(this.steps)) { const result = await runnable.invoke( input, this._patchConfig(options, runManager?.getChild()) ); output[key] = result; } } catch (e) { await runManager?.handleChainError(e); throw e; } await runManager?.handleChainEnd(output); return output; } } /** * A runnable that runs a callable. */ export class RunnableLambda extends Runnable< RunInput, RunOutput > { static lc_name() { return "RunnableLambda"; } lc_namespace = ["langchain", "schema", "runnable"]; protected func: RunnableFunc; constructor(fields: { func: RunnableFunc }) { super(fields); this.func = fields.func; } async invoke( input: RunInput, options?: Partial ): Promise { return this._callWithConfig( async (input: RunInput) => this.func(input), input, options ); } } /** * A runnable that passes through the input. */ export class RunnablePassthrough extends Runnable< RunInput, RunInput > { static lc_name() { return "RunnablePassthrough"; } lc_namespace = ["langchain", "schema", "runnable"]; lc_serializable = true; async invoke( input: RunInput, options?: Partial ): Promise { return this._callWithConfig( (input: RunInput) => Promise.resolve(input), input, options ); } } export type RouterInput = { key: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any input: any; }; /** * A runnable that routes to a set of runnables based on Input['key']. * Returns the output of the selected runnable. */ export class RouterRunnable< RunInput extends RouterInput, RunnableInput, RunOutput > extends Runnable { static lc_name() { return "RouterRunnable"; } lc_namespace = ["langchain", "schema", "runnable"]; lc_serializable = true; runnables: Record>; constructor(fields: { runnables: Record>; }) { super(fields); this.runnables = fields.runnables; } async invoke( input: RunInput, options?: Partial ): Promise { const { key, input: actualInput } = input; const runnable = this.runnables[key]; if (runnable === undefined) { throw new Error(`No runnable associated with key "${key}".`); } return runnable.invoke(actualInput, options); } async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions & { returnExceptions?: false } ): Promise; async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions & { returnExceptions: true } ): Promise<(RunOutput | Error)[]>; async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions ): Promise<(RunOutput | Error)[]>; async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions ): Promise<(RunOutput | Error)[]> { const keys = inputs.map((input) => input.key); const actualInputs = inputs.map((input) => input.input); const missingKey = keys.find((key) => this.runnables[key] === undefined); if (missingKey !== undefined) { throw new Error(`One or more keys do not have a corresponding runnable.`); } const runnables = keys.map((key) => this.runnables[key]); const optionsList = this._getOptionsList(options ?? {}, inputs.length); const batchSize = batchOptions?.maxConcurrency && batchOptions.maxConcurrency > 0 ? batchOptions?.maxConcurrency : inputs.length; const batchResults = []; for (let i = 0; i < actualInputs.length; i += batchSize) { const batchPromises = actualInputs .slice(i, i + batchSize) .map((actualInput, i) => runnables[i].invoke(actualInput, optionsList[i]) ); const batchResult = await Promise.all(batchPromises); batchResults.push(batchResult); } return batchResults.flat(); } async stream( input: RunInput, options?: Partial ): Promise> { const { key, input: actualInput } = input; const runnable = this.runnables[key]; if (runnable === undefined) { throw new Error(`No runnable associated with key "${key}".`); } return runnable.stream(actualInput, options); } } /** * A Runnable that can fallback to other Runnables if it fails. */ export class RunnableWithFallbacks extends Runnable< RunInput, RunOutput > { static lc_name() { return "RunnableWithFallbacks"; } lc_namespace = ["langchain", "schema", "runnable"]; lc_serializable = true; protected runnable: Runnable; protected fallbacks: Runnable[]; constructor(fields: { runnable: Runnable; fallbacks: Runnable[]; }) { super(fields); this.runnable = fields.runnable; this.fallbacks = fields.fallbacks; } *runnables() { yield this.runnable; for (const fallback of this.fallbacks) { yield fallback; } } async invoke( input: RunInput, options?: Partial ): Promise { const callbackManager_ = await CallbackManager.configure( options?.callbacks, undefined, options?.tags, undefined, options?.metadata ); const runManager = await callbackManager_?.handleChainStart( this.toJSON(), _coerceToDict(input, "input") ); let firstError; for (const runnable of this.runnables()) { try { const output = await runnable.invoke( input, this._patchConfig(options, runManager?.getChild()) ); await runManager?.handleChainEnd(_coerceToDict(output, "output")); return output; } catch (e) { if (firstError === undefined) { firstError = e; } } } if (firstError === undefined) { throw new Error("No error stored at end of fallback."); } await runManager?.handleChainError(firstError); throw firstError; } async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions & { returnExceptions?: false } ): Promise; async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions & { returnExceptions: true } ): Promise<(RunOutput | Error)[]>; async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions ): Promise<(RunOutput | Error)[]>; async batch( inputs: RunInput[], options?: Partial | Partial[], batchOptions?: RunnableBatchOptions ): Promise<(RunOutput | Error)[]> { const configList = this._getOptionsList(options ?? {}, inputs.length); const callbackManagers = await Promise.all( configList.map((config) => CallbackManager.configure( config?.callbacks, undefined, config?.tags, undefined, config?.metadata ) ) ); const runManagers = await Promise.all( callbackManagers.map((callbackManager, i) => callbackManager?.handleChainStart( this.toJSON(), _coerceToDict(inputs[i], "input") ) ) ); // eslint-disable-next-line @typescript-eslint/no-explicit-any let firstError: any; for (const runnable of this.runnables()) { try { const outputs = await runnable.batch( inputs, runManagers.map((runManager, j) => this._patchConfig(configList[j], runManager?.getChild()) ), batchOptions ); await Promise.all( runManagers.map((runManager, i) => runManager?.handleChainEnd(_coerceToDict(outputs[i], "output")) ) ); return outputs; } catch (e) { if (firstError === undefined) { firstError = e; } } } if (!firstError) { throw new Error("No error stored at end of fallbacks."); } await Promise.all( runManagers.map((runManager) => runManager?.handleChainError(firstError)) ); throw firstError; } } // TODO: Figure out why the compiler needs help eliminating Error as a RunOutput type function _coerceToRunnable( coerceable: RunnableLike ): Runnable> { if (typeof coerceable === "function") { return new RunnableLambda({ func: coerceable }) as Runnable< RunInput, Exclude >; } else if (Runnable.isRunnable(coerceable)) { return coerceable as Runnable>; } else if (!Array.isArray(coerceable) && typeof coerceable === "object") { const runnables: Record> = {}; for (const [key, value] of Object.entries(coerceable)) { runnables[key] = _coerceToRunnable(value); } return new RunnableMap({ steps: runnables, }) as unknown as Runnable>; } else { throw new Error( `Expected a Runnable, function or object.\nInstead got an unsupported type.` ); } }