UNPKG

@langchain/langgraph

Version:
234 lines 8.4 kB
import { ERROR, INTERRUPT, RETURN, TAG_HIDDEN, } from "../constants.js"; import { EmptyChannelError } from "../errors.js"; import { readChannels } from "./io.js"; import { findSubgraphPregel } from "./utils/subgraph.js"; const COLORS_MAP = { blue: { start: "\x1b[34m", end: "\x1b[0m", }, green: { start: "\x1b[32m", end: "\x1b[0m", }, yellow: { start: "\x1b[33;1m", end: "\x1b[0m", }, }; /** * Wrap some text in a color for printing to the console. */ export const wrap = (color, text) => `${color.start}${text}${color.end}`; export function printCheckpoint(step, channels) { console.log([ `${wrap(COLORS_MAP.blue, "[langgraph/checkpoint]")}`, `Finishing step ${step}. Channel values:\n`, `\n${JSON.stringify(Object.fromEntries(_readChannels(channels)), null, 2)}`, ].join("")); } export function* _readChannels(channels // eslint-disable-next-line @typescript-eslint/no-explicit-any ) { for (const [name, channel] of Object.entries(channels)) { try { yield [name, channel.get()]; // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error) { if (error.name === EmptyChannelError.unminifiable_name) { // Skip the channel if it's empty continue; } else { throw error; // Re-throw the error if it's not an EmptyChannelError } } } } export function* mapDebugTasks(step, tasks) { const ts = new Date().toISOString(); for (const { id, name, input, config, triggers, writes } of tasks) { if (config?.tags?.includes(TAG_HIDDEN)) continue; const interrupts = writes .filter(([writeId, n]) => { return writeId === id && n === INTERRUPT; }) .map(([, v]) => { return v; }); yield { type: "task", timestamp: ts, step, payload: { id, name, input, triggers, interrupts, }, }; } } export function* mapDebugTaskResults(step, tasks, streamChannels) { const ts = new Date().toISOString(); for (const [{ id, name, config }, writes] of tasks) { if (config?.tags?.includes(TAG_HIDDEN)) continue; yield { type: "task_result", timestamp: ts, step, payload: { id, name, result: writes.filter(([channel]) => { return Array.isArray(streamChannels) ? streamChannels.includes(channel) : channel === streamChannels; }), interrupts: writes.filter((w) => w[0] === INTERRUPT).map((w) => w[1]), }, }; } } export function* mapDebugCheckpoint(step, config, channels, streamChannels, metadata, tasks, pendingWrites, parentConfig, outputKeys) { function formatConfig(config) { // make sure the config is consistent with Python const pyConfig = {}; if (config.callbacks != null) pyConfig.callbacks = config.callbacks; if (config.configurable != null) pyConfig.configurable = config.configurable; if (config.maxConcurrency != null) pyConfig.max_concurrency = config.maxConcurrency; if (config.metadata != null) pyConfig.metadata = config.metadata; if (config.recursionLimit != null) pyConfig.recursion_limit = config.recursionLimit; if (config.runId != null) pyConfig.run_id = config.runId; if (config.runName != null) pyConfig.run_name = config.runName; if (config.tags != null) pyConfig.tags = config.tags; return pyConfig; } const parentNs = config.configurable?.checkpoint_ns; const taskStates = {}; for (const task of tasks) { const candidates = task.subgraphs?.length ? task.subgraphs : [task.proc]; if (!candidates.find(findSubgraphPregel)) continue; let taskNs = `${task.name}:${task.id}`; if (parentNs) taskNs = `${parentNs}|${taskNs}`; taskStates[task.id] = { configurable: { thread_id: config.configurable?.thread_id, checkpoint_ns: taskNs, }, }; } const ts = new Date().toISOString(); yield { type: "checkpoint", timestamp: ts, step, payload: { config: formatConfig(config), values: readChannels(channels, streamChannels), metadata, next: tasks.map((task) => task.name), tasks: tasksWithWrites(tasks, pendingWrites, taskStates, outputKeys), parentConfig: parentConfig ? formatConfig(parentConfig) : undefined, }, }; } export function tasksWithWrites(tasks, pendingWrites, states, outputKeys) { return tasks.map((task) => { const error = pendingWrites.find(([id, n]) => id === task.id && n === ERROR)?.[2]; const interrupts = pendingWrites .filter(([id, n]) => id === task.id && n === INTERRUPT) .map(([, , v]) => v); const result = (() => { if (error || interrupts.length || !pendingWrites.length) return undefined; const idx = pendingWrites.findIndex(([tid, n]) => tid === task.id && n === RETURN); if (idx >= 0) return pendingWrites[idx][2]; if (typeof outputKeys === "string") { return pendingWrites.find(([tid, n]) => tid === task.id && n === outputKeys)?.[2]; } if (Array.isArray(outputKeys)) { const results = pendingWrites .filter(([tid, n]) => tid === task.id && outputKeys.includes(n)) .map(([, n, v]) => [n, v]); if (!results.length) return undefined; return Object.fromEntries(results); } return undefined; })(); if (error) { return { id: task.id, name: task.name, path: task.path, error, interrupts, result, }; } const taskState = states?.[task.id]; return { id: task.id, name: task.name, path: task.path, interrupts, ...(taskState !== undefined ? { state: taskState } : {}), result, }; }); } export function printStepCheckpoint(step, channels, whitelist) { console.log([ `${wrap(COLORS_MAP.blue, `[${step}:checkpoint]`)}`, `\x1b[1m State at the end of step ${step}:\x1b[0m\n`, JSON.stringify(readChannels(channels, whitelist), null, 2), ].join("")); } export function printStepTasks(step, nextTasks) { const nTasks = nextTasks.length; console.log([ `${wrap(COLORS_MAP.blue, `[${step}:tasks]`)}`, `\x1b[1m Starting step ${step} with ${nTasks} task${nTasks === 1 ? "" : "s"}:\x1b[0m\n`, nextTasks .map((task) => `- ${wrap(COLORS_MAP.green, String(task.name))} -> ${JSON.stringify(task.input, null, 2)}`) .join("\n"), ].join("")); } export function printStepWrites(step, writes, whitelist) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const byChannel = {}; for (const [channel, value] of writes) { if (whitelist.includes(channel)) { if (!byChannel[channel]) { byChannel[channel] = []; } byChannel[channel].push(value); } } console.log([ `${wrap(COLORS_MAP.blue, `[${step}:writes]`)}`, `\x1b[1m Finished step ${step} with writes to ${Object.keys(byChannel).length} channel${Object.keys(byChannel).length !== 1 ? "s" : ""}:\x1b[0m\n`, Object.entries(byChannel) .map(([name, vals]) => `- ${wrap(COLORS_MAP.yellow, name)} -> ${vals .map((v) => JSON.stringify(v)) .join(", ")}`) .join("\n"), ].join("")); } //# sourceMappingURL=debug.js.map