@langchain/core
Version:
Core LangChain.js abstractions and schemas
437 lines (436 loc) • 15.4 kB
JavaScript
"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;