123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 |
- import { BaseCallbackHandler, } from "../callbacks/base.js";
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- function _coerceToDict(value, defaultKey) {
- return value && !Array.isArray(value) && typeof value === "object"
- ? value
- : { [defaultKey]: value };
- }
- function stripNonAlphanumeric(input) {
- return input.replace(/[-:.]/g, "");
- }
- function convertToDottedOrderFormat(epoch, runId, executionOrder) {
- const paddedOrder = executionOrder.toFixed(0).slice(0, 3).padStart(3, "0");
- return (stripNonAlphanumeric(`${new Date(epoch).toISOString().slice(0, -1)}${paddedOrder}Z`) + runId);
- }
- export function isBaseTracer(x) {
- return typeof x._addRunToRunMap === "function";
- }
- export class BaseTracer extends BaseCallbackHandler {
- constructor(_fields) {
- super(...arguments);
- Object.defineProperty(this, "runMap", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: new Map()
- });
- }
- copy() {
- return this;
- }
- stringifyError(error) {
- // eslint-disable-next-line no-instanceof/no-instanceof
- if (error instanceof Error) {
- return error.message + (error?.stack ? `\n\n${error.stack}` : "");
- }
- if (typeof error === "string") {
- return error;
- }
- return `${error}`;
- }
- _addChildRun(parentRun, childRun) {
- parentRun.child_runs.push(childRun);
- }
- _addRunToRunMap(run) {
- const currentDottedOrder = convertToDottedOrderFormat(run.start_time, run.id, run.execution_order);
- const storedRun = { ...run };
- if (storedRun.parent_run_id !== undefined) {
- const parentRun = this.runMap.get(storedRun.parent_run_id);
- if (parentRun) {
- this._addChildRun(parentRun, storedRun);
- parentRun.child_execution_order = Math.max(parentRun.child_execution_order, storedRun.child_execution_order);
- storedRun.trace_id = parentRun.trace_id;
- if (parentRun.dotted_order !== undefined) {
- storedRun.dotted_order = [
- parentRun.dotted_order,
- currentDottedOrder,
- ].join(".");
- }
- else {
- // This can happen naturally for callbacks added within a run
- // console.debug(`Parent run with UUID ${storedRun.parent_run_id} has no dotted order.`);
- }
- }
- else {
- // This can happen naturally for callbacks added within a run
- // console.debug(
- // `Parent run with UUID ${storedRun.parent_run_id} not found.`
- // );
- }
- }
- else {
- storedRun.trace_id = storedRun.id;
- storedRun.dotted_order = currentDottedOrder;
- }
- this.runMap.set(storedRun.id, storedRun);
- return storedRun;
- }
- async _endTrace(run) {
- const parentRun = run.parent_run_id !== undefined && this.runMap.get(run.parent_run_id);
- if (parentRun) {
- parentRun.child_execution_order = Math.max(parentRun.child_execution_order, run.child_execution_order);
- }
- else {
- await this.persistRun(run);
- }
- this.runMap.delete(run.id);
- await this.onRunUpdate?.(run);
- }
- _getExecutionOrder(parentRunId) {
- const parentRun = parentRunId !== undefined && this.runMap.get(parentRunId);
- // If a run has no parent then execution order is 1
- if (!parentRun) {
- return 1;
- }
- return parentRun.child_execution_order + 1;
- }
- /**
- * Create and add a run to the run map for LLM start events.
- * This must sometimes be done synchronously to avoid race conditions
- * when callbacks are backgrounded, so we expose it as a separate method here.
- */
- _createRunForLLMStart(llm, prompts, runId, parentRunId, extraParams, tags, metadata, name) {
- const execution_order = this._getExecutionOrder(parentRunId);
- const start_time = Date.now();
- const finalExtraParams = metadata
- ? { ...extraParams, metadata }
- : extraParams;
- const run = {
- id: runId,
- name: name ?? llm.id[llm.id.length - 1],
- parent_run_id: parentRunId,
- start_time,
- serialized: llm,
- events: [
- {
- name: "start",
- time: new Date(start_time).toISOString(),
- },
- ],
- inputs: { prompts },
- execution_order,
- child_runs: [],
- child_execution_order: execution_order,
- run_type: "llm",
- extra: finalExtraParams ?? {},
- tags: tags || [],
- };
- return this._addRunToRunMap(run);
- }
- async handleLLMStart(llm, prompts, runId, parentRunId, extraParams, tags, metadata, name) {
- const run = this.runMap.get(runId) ??
- this._createRunForLLMStart(llm, prompts, runId, parentRunId, extraParams, tags, metadata, name);
- await this.onRunCreate?.(run);
- await this.onLLMStart?.(run);
- return run;
- }
- /**
- * Create and add a run to the run map for chat model start events.
- * This must sometimes be done synchronously to avoid race conditions
- * when callbacks are backgrounded, so we expose it as a separate method here.
- */
- _createRunForChatModelStart(llm, messages, runId, parentRunId, extraParams, tags, metadata, name) {
- const execution_order = this._getExecutionOrder(parentRunId);
- const start_time = Date.now();
- const finalExtraParams = metadata
- ? { ...extraParams, metadata }
- : extraParams;
- const run = {
- id: runId,
- name: name ?? llm.id[llm.id.length - 1],
- parent_run_id: parentRunId,
- start_time,
- serialized: llm,
- events: [
- {
- name: "start",
- time: new Date(start_time).toISOString(),
- },
- ],
- inputs: { messages },
- execution_order,
- child_runs: [],
- child_execution_order: execution_order,
- run_type: "llm",
- extra: finalExtraParams ?? {},
- tags: tags || [],
- };
- return this._addRunToRunMap(run);
- }
- async handleChatModelStart(llm, messages, runId, parentRunId, extraParams, tags, metadata, name) {
- const run = this.runMap.get(runId) ??
- this._createRunForChatModelStart(llm, messages, runId, parentRunId, extraParams, tags, metadata, name);
- await this.onRunCreate?.(run);
- await this.onLLMStart?.(run);
- return run;
- }
- async handleLLMEnd(output, runId, _parentRunId, _tags, extraParams) {
- const run = this.runMap.get(runId);
- if (!run || run?.run_type !== "llm") {
- throw new Error("No LLM run to end.");
- }
- run.end_time = Date.now();
- run.outputs = output;
- run.events.push({
- name: "end",
- time: new Date(run.end_time).toISOString(),
- });
- run.extra = { ...run.extra, ...extraParams };
- await this.onLLMEnd?.(run);
- await this._endTrace(run);
- return run;
- }
- async handleLLMError(error, runId, _parentRunId, _tags, extraParams) {
- const run = this.runMap.get(runId);
- if (!run || run?.run_type !== "llm") {
- throw new Error("No LLM run to end.");
- }
- run.end_time = Date.now();
- run.error = this.stringifyError(error);
- run.events.push({
- name: "error",
- time: new Date(run.end_time).toISOString(),
- });
- run.extra = { ...run.extra, ...extraParams };
- await this.onLLMError?.(run);
- await this._endTrace(run);
- return run;
- }
- /**
- * Create and add a run to the run map for chain start events.
- * This must sometimes be done synchronously to avoid race conditions
- * when callbacks are backgrounded, so we expose it as a separate method here.
- */
- _createRunForChainStart(chain, inputs, runId, parentRunId, tags, metadata, runType, name) {
- const execution_order = this._getExecutionOrder(parentRunId);
- const start_time = Date.now();
- const run = {
- id: runId,
- name: name ?? chain.id[chain.id.length - 1],
- parent_run_id: parentRunId,
- start_time,
- serialized: chain,
- events: [
- {
- name: "start",
- time: new Date(start_time).toISOString(),
- },
- ],
- inputs,
- execution_order,
- child_execution_order: execution_order,
- run_type: runType ?? "chain",
- child_runs: [],
- extra: metadata ? { metadata } : {},
- tags: tags || [],
- };
- return this._addRunToRunMap(run);
- }
- async handleChainStart(chain, inputs, runId, parentRunId, tags, metadata, runType, name) {
- const run = this.runMap.get(runId) ??
- this._createRunForChainStart(chain, inputs, runId, parentRunId, tags, metadata, runType, name);
- await this.onRunCreate?.(run);
- await this.onChainStart?.(run);
- return run;
- }
- async handleChainEnd(outputs, runId, _parentRunId, _tags, kwargs) {
- const run = this.runMap.get(runId);
- if (!run) {
- throw new Error("No chain run to end.");
- }
- run.end_time = Date.now();
- run.outputs = _coerceToDict(outputs, "output");
- run.events.push({
- name: "end",
- time: new Date(run.end_time).toISOString(),
- });
- if (kwargs?.inputs !== undefined) {
- run.inputs = _coerceToDict(kwargs.inputs, "input");
- }
- await this.onChainEnd?.(run);
- await this._endTrace(run);
- return run;
- }
- async handleChainError(error, runId, _parentRunId, _tags, kwargs) {
- const run = this.runMap.get(runId);
- if (!run) {
- throw new Error("No chain run to end.");
- }
- run.end_time = Date.now();
- run.error = this.stringifyError(error);
- run.events.push({
- name: "error",
- time: new Date(run.end_time).toISOString(),
- });
- if (kwargs?.inputs !== undefined) {
- run.inputs = _coerceToDict(kwargs.inputs, "input");
- }
- await this.onChainError?.(run);
- await this._endTrace(run);
- return run;
- }
- /**
- * Create and add a run to the run map for tool start events.
- * This must sometimes be done synchronously to avoid race conditions
- * when callbacks are backgrounded, so we expose it as a separate method here.
- */
- _createRunForToolStart(tool, input, runId, parentRunId, tags, metadata, name) {
- const execution_order = this._getExecutionOrder(parentRunId);
- const start_time = Date.now();
- const run = {
- id: runId,
- name: name ?? tool.id[tool.id.length - 1],
- parent_run_id: parentRunId,
- start_time,
- serialized: tool,
- events: [
- {
- name: "start",
- time: new Date(start_time).toISOString(),
- },
- ],
- inputs: { input },
- execution_order,
- child_execution_order: execution_order,
- run_type: "tool",
- child_runs: [],
- extra: metadata ? { metadata } : {},
- tags: tags || [],
- };
- return this._addRunToRunMap(run);
- }
- async handleToolStart(tool, input, runId, parentRunId, tags, metadata, name) {
- const run = this.runMap.get(runId) ??
- this._createRunForToolStart(tool, input, runId, parentRunId, tags, metadata, name);
- await this.onRunCreate?.(run);
- await this.onToolStart?.(run);
- return run;
- }
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- async handleToolEnd(output, runId) {
- const run = this.runMap.get(runId);
- if (!run || run?.run_type !== "tool") {
- throw new Error("No tool run to end");
- }
- run.end_time = Date.now();
- run.outputs = { output };
- run.events.push({
- name: "end",
- time: new Date(run.end_time).toISOString(),
- });
- await this.onToolEnd?.(run);
- await this._endTrace(run);
- return run;
- }
- async handleToolError(error, runId) {
- const run = this.runMap.get(runId);
- if (!run || run?.run_type !== "tool") {
- throw new Error("No tool run to end");
- }
- run.end_time = Date.now();
- run.error = this.stringifyError(error);
- run.events.push({
- name: "error",
- time: new Date(run.end_time).toISOString(),
- });
- await this.onToolError?.(run);
- await this._endTrace(run);
- return run;
- }
- async handleAgentAction(action, runId) {
- const run = this.runMap.get(runId);
- if (!run || run?.run_type !== "chain") {
- return;
- }
- const agentRun = run;
- agentRun.actions = agentRun.actions || [];
- agentRun.actions.push(action);
- agentRun.events.push({
- name: "agent_action",
- time: new Date().toISOString(),
- kwargs: { action },
- });
- await this.onAgentAction?.(run);
- }
- async handleAgentEnd(action, runId) {
- const run = this.runMap.get(runId);
- if (!run || run?.run_type !== "chain") {
- return;
- }
- run.events.push({
- name: "agent_end",
- time: new Date().toISOString(),
- kwargs: { action },
- });
- await this.onAgentEnd?.(run);
- }
- /**
- * Create and add a run to the run map for retriever start events.
- * This must sometimes be done synchronously to avoid race conditions
- * when callbacks are backgrounded, so we expose it as a separate method here.
- */
- _createRunForRetrieverStart(retriever, query, runId, parentRunId, tags, metadata, name) {
- const execution_order = this._getExecutionOrder(parentRunId);
- const start_time = Date.now();
- const run = {
- id: runId,
- name: name ?? retriever.id[retriever.id.length - 1],
- parent_run_id: parentRunId,
- start_time,
- serialized: retriever,
- events: [
- {
- name: "start",
- time: new Date(start_time).toISOString(),
- },
- ],
- inputs: { query },
- execution_order,
- child_execution_order: execution_order,
- run_type: "retriever",
- child_runs: [],
- extra: metadata ? { metadata } : {},
- tags: tags || [],
- };
- return this._addRunToRunMap(run);
- }
- async handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata, name) {
- const run = this.runMap.get(runId) ??
- this._createRunForRetrieverStart(retriever, query, runId, parentRunId, tags, metadata, name);
- await this.onRunCreate?.(run);
- await this.onRetrieverStart?.(run);
- return run;
- }
- async handleRetrieverEnd(documents, runId) {
- const run = this.runMap.get(runId);
- if (!run || run?.run_type !== "retriever") {
- throw new Error("No retriever run to end");
- }
- run.end_time = Date.now();
- run.outputs = { documents };
- run.events.push({
- name: "end",
- time: new Date(run.end_time).toISOString(),
- });
- await this.onRetrieverEnd?.(run);
- await this._endTrace(run);
- return run;
- }
- async handleRetrieverError(error, runId) {
- const run = this.runMap.get(runId);
- if (!run || run?.run_type !== "retriever") {
- throw new Error("No retriever run to end");
- }
- run.end_time = Date.now();
- run.error = this.stringifyError(error);
- run.events.push({
- name: "error",
- time: new Date(run.end_time).toISOString(),
- });
- await this.onRetrieverError?.(run);
- await this._endTrace(run);
- return run;
- }
- async handleText(text, runId) {
- const run = this.runMap.get(runId);
- if (!run || run?.run_type !== "chain") {
- return;
- }
- run.events.push({
- name: "text",
- time: new Date().toISOString(),
- kwargs: { text },
- });
- await this.onText?.(run);
- }
- async handleLLMNewToken(token, idx, runId, _parentRunId, _tags, fields) {
- const run = this.runMap.get(runId);
- if (!run || run?.run_type !== "llm") {
- throw new Error(`Invalid "runId" provided to "handleLLMNewToken" callback.`);
- }
- run.events.push({
- name: "new_token",
- time: new Date().toISOString(),
- kwargs: { token, idx, chunk: fields?.chunk },
- });
- await this.onLLMNewToken?.(run, token, { chunk: fields?.chunk });
- return run;
- }
- }
|