@langchain/core
Version:
Core LangChain.js abstractions and schemas
361 lines (359 loc) • 11.4 kB
JavaScript
const require_ai = require('../messages/ai.cjs');
const require_tracers_base = require('./base.cjs');
const require_utils_stream = require('../utils/stream.cjs');
const require_outputs = require('../outputs.cjs');
//#region src/tracers/event_stream.ts
function assignName({ name, serialized }) {
if (name !== void 0) return name;
if (serialized?.name !== void 0) return serialized.name;
else if (serialized?.id !== void 0 && Array.isArray(serialized?.id)) return serialized.id[serialized.id.length - 1];
return "Unnamed";
}
const isStreamEventsHandler = (handler) => handler.name === "event_stream_tracer";
/**
* 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`.
*/
var EventStreamCallbackHandler = class extends require_tracers_base.BaseTracer {
autoClose = true;
includeNames;
includeTypes;
includeTags;
excludeNames;
excludeTypes;
excludeTags;
runInfoMap = /* @__PURE__ */ new Map();
tappedPromises = /* @__PURE__ */ new Map();
transformStream;
writer;
receiveStream;
name = "event_stream_tracer";
lc_prefer_streaming = true;
constructor(fields) {
super({
_awaitHandler: true,
...fields
});
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.transformStream = new TransformStream();
this.writer = this.transformStream.writable.getWriter();
this.receiveStream = require_utils_stream.IterableReadableStream.fromReadableStream(this.transformStream.readable);
}
[Symbol.asyncIterator]() {
return this.receiveStream;
}
async persistRun(_run) {}
_includeRun(run) {
const runTags = run.tags ?? [];
let include = this.includeNames === void 0 && this.includeTags === void 0 && this.includeTypes === void 0;
if (this.includeNames !== void 0) include = include || this.includeNames.includes(run.name);
if (this.includeTypes !== void 0) include = include || this.includeTypes.includes(run.runType);
if (this.includeTags !== void 0) include = include || runTags.find((tag) => this.includeTags?.includes(tag)) !== void 0;
if (this.excludeNames !== void 0) include = include && !this.excludeNames.includes(run.name);
if (this.excludeTypes !== void 0) include = include && !this.excludeTypes.includes(run.runType);
if (this.excludeTags !== void 0) include = include && runTags.every((tag) => !this.excludeTags?.includes(tag));
return include;
}
async *tapOutputIterable(runId, outputStream) {
const firstChunk = await outputStream.next();
if (firstChunk.done) return;
const runInfo = this.runInfoMap.get(runId);
if (runInfo === void 0) {
yield firstChunk.value;
return;
}
function _formatOutputChunk(eventType, data) {
if (eventType === "llm" && typeof data === "string") return new require_outputs.GenerationChunk({ text: data });
return data;
}
let tappedPromise = this.tappedPromises.get(runId);
if (tappedPromise === void 0) {
let tappedPromiseResolver;
tappedPromise = new Promise((resolve) => {
tappedPromiseResolver = resolve;
});
this.tappedPromises.set(runId, tappedPromise);
try {
const event = {
event: `on_${runInfo.runType}_stream`,
run_id: runId,
name: runInfo.name,
tags: runInfo.tags,
metadata: runInfo.metadata,
data: {}
};
await this.send({
...event,
data: { chunk: _formatOutputChunk(runInfo.runType, firstChunk.value) }
}, runInfo);
yield firstChunk.value;
for await (const chunk of outputStream) {
if (runInfo.runType !== "tool" && runInfo.runType !== "retriever") await this.send({
...event,
data: { chunk: _formatOutputChunk(runInfo.runType, chunk) }
}, runInfo);
yield chunk;
}
} finally {
tappedPromiseResolver?.();
}
} else {
yield firstChunk.value;
for await (const chunk of outputStream) yield chunk;
}
}
async send(payload, run) {
if (this._includeRun(run)) await this.writer.write(payload);
}
async sendEndEvent(payload, run) {
const tappedPromise = this.tappedPromises.get(payload.run_id);
if (tappedPromise !== void 0) tappedPromise.then(() => {
this.send(payload, run);
});
else await this.send(payload, run);
}
async onLLMStart(run) {
const runName = assignName(run);
const runType = run.inputs.messages !== void 0 ? "chat_model" : "llm";
const runInfo = {
tags: run.tags ?? [],
metadata: run.extra?.metadata ?? {},
name: runName,
runType,
inputs: run.inputs
};
this.runInfoMap.set(run.id, runInfo);
const eventName = `on_${runType}_start`;
await this.send({
event: eventName,
data: { input: run.inputs },
name: runName,
tags: run.tags ?? [],
run_id: run.id,
metadata: run.extra?.metadata ?? {}
}, runInfo);
}
async onLLMNewToken(run, token, kwargs) {
const runInfo = this.runInfoMap.get(run.id);
let chunk;
let eventName;
if (runInfo === void 0) throw new Error(`onLLMNewToken: Run ID ${run.id} not found in run map.`);
if (this.runInfoMap.size === 1) return;
if (runInfo.runType === "chat_model") {
eventName = "on_chat_model_stream";
if (kwargs?.chunk === void 0) chunk = new require_ai.AIMessageChunk({
content: token,
id: `run-${run.id}`
});
else chunk = kwargs.chunk.message;
} else if (runInfo.runType === "llm") {
eventName = "on_llm_stream";
if (kwargs?.chunk === void 0) chunk = new require_outputs.GenerationChunk({ text: token });
else chunk = kwargs.chunk;
} else throw new Error(`Unexpected run type ${runInfo.runType}`);
await this.send({
event: eventName,
data: { chunk },
run_id: run.id,
name: runInfo.name,
tags: runInfo.tags,
metadata: runInfo.metadata
}, runInfo);
}
async onLLMEnd(run) {
const runInfo = this.runInfoMap.get(run.id);
this.runInfoMap.delete(run.id);
let eventName;
if (runInfo === void 0) throw new Error(`onLLMEnd: Run ID ${run.id} not found in run map.`);
const generations = run.outputs?.generations;
let output;
if (runInfo.runType === "chat_model") {
for (const generation of generations ?? []) {
if (output !== void 0) break;
output = generation[0]?.message;
}
eventName = "on_chat_model_end";
} else if (runInfo.runType === "llm") {
output = {
generations: generations?.map((generation) => {
return generation.map((chunk) => {
return {
text: chunk.text,
generationInfo: chunk.generationInfo
};
});
}),
llmOutput: run.outputs?.llmOutput ?? {}
};
eventName = "on_llm_end";
} else throw new Error(`onLLMEnd: Unexpected run type: ${runInfo.runType}`);
await this.sendEndEvent({
event: eventName,
data: {
output,
input: runInfo.inputs
},
run_id: run.id,
name: runInfo.name,
tags: runInfo.tags,
metadata: runInfo.metadata
}, runInfo);
}
async onChainStart(run) {
const runName = assignName(run);
const runType = run.run_type ?? "chain";
const runInfo = {
tags: run.tags ?? [],
metadata: run.extra?.metadata ?? {},
name: runName,
runType: run.run_type
};
let eventData = {};
if (run.inputs.input === "" && Object.keys(run.inputs).length === 1) {
eventData = {};
runInfo.inputs = {};
} else if (run.inputs.input !== void 0) {
eventData.input = run.inputs.input;
runInfo.inputs = run.inputs.input;
} else {
eventData.input = run.inputs;
runInfo.inputs = run.inputs;
}
this.runInfoMap.set(run.id, runInfo);
await this.send({
event: `on_${runType}_start`,
data: eventData,
name: runName,
tags: run.tags ?? [],
run_id: run.id,
metadata: run.extra?.metadata ?? {}
}, runInfo);
}
async onChainEnd(run) {
const runInfo = this.runInfoMap.get(run.id);
this.runInfoMap.delete(run.id);
if (runInfo === void 0) throw new Error(`onChainEnd: Run ID ${run.id} not found in run map.`);
const eventName = `on_${run.run_type}_end`;
const inputs = run.inputs ?? runInfo.inputs ?? {};
const outputs = run.outputs?.output ?? run.outputs;
const data = {
output: outputs,
input: inputs
};
if (inputs.input && Object.keys(inputs).length === 1) {
data.input = inputs.input;
runInfo.inputs = inputs.input;
}
await this.sendEndEvent({
event: eventName,
data,
run_id: run.id,
name: runInfo.name,
tags: runInfo.tags,
metadata: runInfo.metadata ?? {}
}, runInfo);
}
async onToolStart(run) {
const runName = assignName(run);
const runInfo = {
tags: run.tags ?? [],
metadata: run.extra?.metadata ?? {},
name: runName,
runType: "tool",
inputs: run.inputs ?? {}
};
this.runInfoMap.set(run.id, runInfo);
await this.send({
event: "on_tool_start",
data: { input: run.inputs ?? {} },
name: runName,
run_id: run.id,
tags: run.tags ?? [],
metadata: run.extra?.metadata ?? {}
}, runInfo);
}
async onToolEnd(run) {
const runInfo = this.runInfoMap.get(run.id);
this.runInfoMap.delete(run.id);
if (runInfo === void 0) throw new Error(`onToolEnd: Run ID ${run.id} not found in run map.`);
if (runInfo.inputs === void 0) throw new Error(`onToolEnd: Run ID ${run.id} is a tool call, and is expected to have traced inputs.`);
const output = run.outputs?.output === void 0 ? run.outputs : run.outputs.output;
await this.sendEndEvent({
event: "on_tool_end",
data: {
output,
input: runInfo.inputs
},
run_id: run.id,
name: runInfo.name,
tags: runInfo.tags,
metadata: runInfo.metadata
}, runInfo);
}
async onRetrieverStart(run) {
const runName = assignName(run);
const runType = "retriever";
const runInfo = {
tags: run.tags ?? [],
metadata: run.extra?.metadata ?? {},
name: runName,
runType,
inputs: { query: run.inputs.query }
};
this.runInfoMap.set(run.id, runInfo);
await this.send({
event: "on_retriever_start",
data: { input: { query: run.inputs.query } },
name: runName,
tags: run.tags ?? [],
run_id: run.id,
metadata: run.extra?.metadata ?? {}
}, runInfo);
}
async onRetrieverEnd(run) {
const runInfo = this.runInfoMap.get(run.id);
this.runInfoMap.delete(run.id);
if (runInfo === void 0) throw new Error(`onRetrieverEnd: Run ID ${run.id} not found in run map.`);
await this.sendEndEvent({
event: "on_retriever_end",
data: {
output: run.outputs?.documents ?? run.outputs,
input: runInfo.inputs
},
run_id: run.id,
name: runInfo.name,
tags: runInfo.tags,
metadata: runInfo.metadata
}, runInfo);
}
async handleCustomEvent(eventName, data, runId) {
const runInfo = this.runInfoMap.get(runId);
if (runInfo === void 0) throw new Error(`handleCustomEvent: Run ID ${runId} not found in run map.`);
await this.send({
event: "on_custom_event",
run_id: runId,
name: eventName,
tags: runInfo.tags,
metadata: runInfo.metadata,
data
}, runInfo);
}
async finish() {
const pendingPromises = [...this.tappedPromises.values()];
Promise.all(pendingPromises).finally(() => {
this.writer.close();
});
}
};
//#endregion
exports.EventStreamCallbackHandler = EventStreamCallbackHandler;
exports.isStreamEventsHandler = isStreamEventsHandler;
//# sourceMappingURL=event_stream.cjs.map