UNPKG

@langchain/core

Version:
437 lines (436 loc) 15.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LogStreamCallbackHandler = exports.isLogStreamHandler = exports.RunLog = exports.RunLogPatch = void 0; const index_js_1 = require("../utils/fast-json-patch/index.cjs"); const base_js_1 = require("./base.cjs"); const stream_js_1 = require("../utils/stream.cjs"); const ai_js_1 = require("../messages/ai.cjs"); /** * List of jsonpatch JSONPatchOperations, which describe how to create the run state * from an empty dict. This is the minimal representation of the log, designed to * be serialized as JSON and sent over the wire to reconstruct the log on the other * side. Reconstruction of the state can be done with any jsonpatch-compliant library, * see https://jsonpatch.com for more information. */ class RunLogPatch { constructor(fields) { Object.defineProperty(this, "ops", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.ops = fields.ops ?? []; } concat(other) { const ops = this.ops.concat(other.ops); const states = (0, index_js_1.applyPatch)({}, ops); // eslint-disable-next-line @typescript-eslint/no-use-before-define return new RunLog({ ops, state: states[states.length - 1].newDocument, }); } } exports.RunLogPatch = RunLogPatch; class RunLog extends RunLogPatch { constructor(fields) { super(fields); Object.defineProperty(this, "state", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.state = fields.state; } concat(other) { const ops = this.ops.concat(other.ops); const states = (0, index_js_1.applyPatch)(this.state, other.ops); return new RunLog({ ops, state: states[states.length - 1].newDocument }); } static fromRunLogPatch(patch) { const states = (0, index_js_1.applyPatch)({}, patch.ops); // eslint-disable-next-line @typescript-eslint/no-use-before-define return new RunLog({ ops: patch.ops, state: states[states.length - 1].newDocument, }); } } exports.RunLog = RunLog; const isLogStreamHandler = (handler) => handler.name === "log_stream_tracer"; exports.isLogStreamHandler = isLogStreamHandler; /** * Extract standardized inputs from a run. * * Standardizes the inputs based on the type of the runnable used. * * @param run - Run object * @param schemaFormat - The schema format to use. * * @returns Valid inputs are only dict. By conventions, inputs always represented * invocation using named arguments. * A null means that the input is not yet known! */ async function _getStandardizedInputs(run, schemaFormat) { if (schemaFormat === "original") { throw new Error("Do not assign inputs with original schema drop the key for now. " + "When inputs are added to streamLog they should be added with " + "standardized schema for streaming events."); } const { inputs } = run; if (["retriever", "llm", "prompt"].includes(run.run_type)) { return inputs; } if (Object.keys(inputs).length === 1 && inputs?.input === "") { return undefined; } // new style chains // These nest an additional 'input' key inside the 'inputs' to make sure // the input is always a dict. We need to unpack and user the inner value. // We should try to fix this in Runnables and callbacks/tracers // Runnables should be using a null type here not a placeholder // dict. return inputs.input; } async function _getStandardizedOutputs(run, schemaFormat) { const { outputs } = run; if (schemaFormat === "original") { // Return the old schema, without standardizing anything return outputs; } if (["retriever", "llm", "prompt"].includes(run.run_type)) { return outputs; } // TODO: Remove this hacky check if (outputs !== undefined && Object.keys(outputs).length === 1 && outputs?.output !== undefined) { return outputs.output; } return outputs; } function isChatGenerationChunk(x) { return x !== undefined && x.message !== undefined; } /** * Class that extends the `BaseTracer` class from the * `langchain.callbacks.tracers.base` module. It represents a callback * handler that logs the execution of runs and emits `RunLog` instances to a * `RunLogStream`. */ class LogStreamCallbackHandler extends base_js_1.BaseTracer { constructor(fields) { super({ _awaitHandler: true, ...fields }); Object.defineProperty(this, "autoClose", { enumerable: true, configurable: true, writable: true, value: true }); Object.defineProperty(this, "includeNames", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "includeTypes", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "includeTags", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "excludeNames", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "excludeTypes", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "excludeTags", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_schemaFormat", { enumerable: true, configurable: true, writable: true, value: "original" }); Object.defineProperty(this, "rootId", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "keyMapByRunId", { enumerable: true, configurable: true, writable: true, value: {} }); Object.defineProperty(this, "counterMapByRunName", { enumerable: true, configurable: true, writable: true, value: {} }); Object.defineProperty(this, "transformStream", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "writer", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "receiveStream", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: "log_stream_tracer" }); Object.defineProperty(this, "lc_prefer_streaming", { enumerable: true, configurable: true, writable: true, value: true }); this.autoClose = fields?.autoClose ?? true; this.includeNames = fields?.includeNames; this.includeTypes = fields?.includeTypes; this.includeTags = fields?.includeTags; this.excludeNames = fields?.excludeNames; this.excludeTypes = fields?.excludeTypes; this.excludeTags = fields?.excludeTags; this._schemaFormat = fields?._schemaFormat ?? this._schemaFormat; this.transformStream = new TransformStream(); this.writer = this.transformStream.writable.getWriter(); this.receiveStream = stream_js_1.IterableReadableStream.fromReadableStream(this.transformStream.readable); } [Symbol.asyncIterator]() { return this.receiveStream; } async persistRun(_run) { // This is a legacy method only called once for an entire run tree // and is therefore not useful here } _includeRun(run) { if (run.id === this.rootId) { return false; } const runTags = run.tags ?? []; let include = this.includeNames === undefined && this.includeTags === undefined && this.includeTypes === undefined; if (this.includeNames !== undefined) { include = include || this.includeNames.includes(run.name); } if (this.includeTypes !== undefined) { include = include || this.includeTypes.includes(run.run_type); } if (this.includeTags !== undefined) { include = include || runTags.find((tag) => this.includeTags?.includes(tag)) !== undefined; } if (this.excludeNames !== undefined) { include = include && !this.excludeNames.includes(run.name); } if (this.excludeTypes !== undefined) { include = include && !this.excludeTypes.includes(run.run_type); } if (this.excludeTags !== undefined) { include = include && runTags.every((tag) => !this.excludeTags?.includes(tag)); } return include; } async *tapOutputIterable(runId, output) { // Tap an output async iterator to stream its values to the log. for await (const chunk of output) { // root run is handled in .streamLog() if (runId !== this.rootId) { // if we can't find the run silently ignore // eg. because this run wasn't included in the log const key = this.keyMapByRunId[runId]; if (key) { await this.writer.write(new RunLogPatch({ ops: [ { op: "add", path: `/logs/${key}/streamed_output/-`, value: chunk, }, ], })); } } yield chunk; } } async onRunCreate(run) { if (this.rootId === undefined) { this.rootId = run.id; await this.writer.write(new RunLogPatch({ ops: [ { op: "replace", path: "", value: { id: run.id, name: run.name, type: run.run_type, streamed_output: [], final_output: undefined, logs: {}, }, }, ], })); } if (!this._includeRun(run)) { return; } if (this.counterMapByRunName[run.name] === undefined) { this.counterMapByRunName[run.name] = 0; } this.counterMapByRunName[run.name] += 1; const count = this.counterMapByRunName[run.name]; this.keyMapByRunId[run.id] = count === 1 ? run.name : `${run.name}:${count}`; const logEntry = { id: run.id, name: run.name, type: run.run_type, tags: run.tags ?? [], metadata: run.extra?.metadata ?? {}, start_time: new Date(run.start_time).toISOString(), streamed_output: [], streamed_output_str: [], final_output: undefined, end_time: undefined, }; if (this._schemaFormat === "streaming_events") { logEntry.inputs = await _getStandardizedInputs(run, this._schemaFormat); } await this.writer.write(new RunLogPatch({ ops: [ { op: "add", path: `/logs/${this.keyMapByRunId[run.id]}`, value: logEntry, }, ], })); } async onRunUpdate(run) { try { const runName = this.keyMapByRunId[run.id]; if (runName === undefined) { return; } const ops = []; if (this._schemaFormat === "streaming_events") { ops.push({ op: "replace", path: `/logs/${runName}/inputs`, value: await _getStandardizedInputs(run, this._schemaFormat), }); } ops.push({ op: "add", path: `/logs/${runName}/final_output`, value: await _getStandardizedOutputs(run, this._schemaFormat), }); if (run.end_time !== undefined) { ops.push({ op: "add", path: `/logs/${runName}/end_time`, value: new Date(run.end_time).toISOString(), }); } const patch = new RunLogPatch({ ops }); await this.writer.write(patch); } finally { if (run.id === this.rootId) { const patch = new RunLogPatch({ ops: [ { op: "replace", path: "/final_output", value: await _getStandardizedOutputs(run, this._schemaFormat), }, ], }); await this.writer.write(patch); if (this.autoClose) { await this.writer.close(); } } } } async onLLMNewToken(run, token, kwargs) { const runName = this.keyMapByRunId[run.id]; if (runName === undefined) { return; } // TODO: Remove hack const isChatModel = run.inputs.messages !== undefined; let streamedOutputValue; if (isChatModel) { if (isChatGenerationChunk(kwargs?.chunk)) { streamedOutputValue = kwargs?.chunk; } else { streamedOutputValue = new ai_js_1.AIMessageChunk({ id: `run-${run.id}`, content: token, }); } } else { streamedOutputValue = token; } const patch = new RunLogPatch({ ops: [ { op: "add", path: `/logs/${runName}/streamed_output_str/-`, value: token, }, { op: "add", path: `/logs/${runName}/streamed_output/-`, value: streamedOutputValue, }, ], }); await this.writer.write(patch); } } exports.LogStreamCallbackHandler = LogStreamCallbackHandler;