@langchain/langgraph
Version:
1 lines • 10.1 kB
Source Map (JSON)
{"version":3,"file":"delta.cjs","names":["BaseChannel","_getOverwriteValue","_isOverwriteValue","InvalidUpdateError","EmptyChannelError"],"sources":["../../src/channels/delta.ts"],"sourcesContent":["import {\n type CheckpointPendingWrite,\n DeltaSnapshot,\n isDeltaSnapshot,\n} from \"@langchain/langgraph-checkpoint\";\nimport {\n _getOverwriteValue,\n _isOverwriteValue,\n type OverwriteValue,\n} from \"../constants.js\";\nimport { EmptyChannelError, InvalidUpdateError } from \"../errors.js\";\nimport { BaseChannel } from \"./base.js\";\n\n/**\n * A batch reducer for use with {@link DeltaChannel}.\n *\n * Receives the current accumulated value and a batch of writes in one call,\n * returning the new accumulated value:\n * `reducer(state, [write1, write2, ...]) -> newState`.\n *\n * Reducers must be deterministic and batching-invariant (associative across\n * folds): applying two consecutive write batches separately must produce the\n * same state as applying their concatenation once:\n *\n * ```text\n * reducer(reducer(state, xs), ys) === reducer(state, xs.concat(ys))\n * ```\n *\n * This lets LangGraph replay checkpointed writes in larger batches than they\n * were originally produced without changing reconstructed state. If your\n * reducer is not associative, use {@link BinaryOperatorAggregate} instead —\n * `DeltaChannel` is not a drop-in replacement for every reducer.\n */\nexport type DeltaReducer<ValueType, UpdateType = unknown> = (\n state: ValueType,\n writes: UpdateType[]\n) => ValueType;\n\ntype OverwriteOrValue<ValueType, UpdateType> =\n | OverwriteValue<ValueType>\n | UpdateType;\n\nconst isDeltaChannel = (\n value: BaseChannel\n): value is DeltaChannel<unknown, unknown> => {\n return value != null && value.lc_graph_name === \"DeltaChannel\";\n};\n\n/**\n * Reducer channel that stores only a sentinel in checkpoint blobs and\n * reconstructs state by replaying ancestor writes through the reducer.\n *\n * `DeltaChannel` avoids re-serializing the full accumulated value at every\n * step. Instead of writing the value into `channel_values`, the channel is\n * omitted entirely and its state is reconstructed on read by walking the\n * ancestor chain and replaying the per-step writes through the reducer (see\n * {@link BaseCheckpointSaver.getDeltaChannelHistory}).\n *\n * Snapshot cadence is driven by two counters: a per-channel update count and\n * the total supersteps since the last snapshot. A full {@link DeltaSnapshot}\n * blob is written when EITHER the update count reaches `snapshotFrequency` OR\n * the supersteps count reaches the system-wide\n * `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT` bound (default 5000), bounding replay\n * depth even for channels that stop receiving writes.\n *\n * @remarks Beta. The API and on-disk representation may change in future\n * releases. Threads written with `DeltaChannel` today are expected to remain\n * readable, but the surrounding contract (`getDeltaChannelHistory`, the\n * `DeltaSnapshot` blob shape, the `counters_since_delta_snapshot` metadata\n * field) is not yet stable.\n *\n * @example\n * ```typescript\n * import { Annotation } from \"@langchain/langgraph\";\n * import { DeltaChannel, messagesDeltaReducer } from \"@langchain/langgraph\";\n *\n * const State = Annotation.Root({\n * messages: Annotation<BaseMessage[]>({\n * reducer: () => [], // ignored; DeltaChannel is supplied below\n * }),\n * });\n * ```\n */\nexport class DeltaChannel<\n ValueType = unknown,\n UpdateType = unknown,\n> extends BaseChannel<\n ValueType,\n OverwriteOrValue<ValueType, UpdateType>,\n undefined\n> {\n lc_graph_name = \"DeltaChannel\";\n\n /** `undefined` represents the Python `MISSING` sentinel (empty channel). */\n value: ValueType | undefined;\n\n reducer: DeltaReducer<ValueType, UpdateType>;\n\n snapshotFrequency: number;\n\n initialValueFactory: () => ValueType;\n\n constructor(\n reducer: DeltaReducer<ValueType, UpdateType>,\n options?: {\n snapshotFrequency?: number;\n initialValueFactory?: () => ValueType;\n }\n ) {\n super();\n const snapshotFrequency = options?.snapshotFrequency ?? 1000;\n if (!Number.isInteger(snapshotFrequency) || snapshotFrequency <= 0) {\n throw new Error(\n `snapshotFrequency must be a positive integer, got ${snapshotFrequency}`\n );\n }\n this.reducer = reducer;\n this.snapshotFrequency = snapshotFrequency;\n this.initialValueFactory =\n options?.initialValueFactory ?? (() => [] as ValueType);\n this.value = undefined;\n }\n\n public fromCheckpoint(checkpoint?: undefined | DeltaSnapshot | ValueType) {\n const empty = new DeltaChannel<ValueType, UpdateType>(this.reducer, {\n snapshotFrequency: this.snapshotFrequency,\n initialValueFactory: this.initialValueFactory,\n });\n if (checkpoint === undefined) {\n empty.value = this.initialValueFactory();\n } else if (isDeltaSnapshot(checkpoint)) {\n empty.value = checkpoint.value as ValueType;\n } else {\n empty.value = checkpoint as ValueType;\n }\n return empty as this;\n }\n\n /**\n * Apply ancestor writes oldest-to-newest via a single reducer call.\n *\n * If any write is an Overwrite, the last one in the sequence acts as the\n * reset point: its value becomes the new base and only writes after it are\n * passed to the reducer.\n */\n public replayWrites(writes: CheckpointPendingWrite[]): void {\n const values = writes.map((w) => w[2]);\n if (values.length === 0) return;\n let base = this.value as ValueType;\n let start = 0;\n for (let i = 0; i < values.length; i += 1) {\n const [isOverwrite, overwriteValue] = _getOverwriteValue<ValueType>(\n values[i]\n );\n if (isOverwrite) {\n base =\n overwriteValue !== undefined && overwriteValue !== null\n ? overwriteValue\n : this.initialValueFactory();\n start = i + 1;\n }\n }\n const remaining = values.slice(start) as UpdateType[];\n this.value = remaining.length > 0 ? this.reducer(base, remaining) : base;\n }\n\n public update(values: OverwriteOrValue<ValueType, UpdateType>[]): boolean {\n if (values.length === 0) return false;\n\n let overwriteValue: ValueType | undefined;\n let hasOverwrite = false;\n for (const value of values) {\n if (_isOverwriteValue<ValueType>(value)) {\n if (hasOverwrite) {\n throw new InvalidUpdateError(\n \"Can receive only one Overwrite value per step.\"\n );\n }\n hasOverwrite = true;\n [, overwriteValue] = _getOverwriteValue<ValueType>(value);\n }\n }\n\n if (hasOverwrite) {\n // An Overwrite wins the entire super-step: every sibling write (before\n // AND after) is discarded, mirroring `BinaryOperatorAggregate` — hence\n // only one Overwrite per step. The loop force-snapshots channels that saw\n // an Overwrite, so reconstruction seeds from this value without replaying.\n this.value =\n overwriteValue !== undefined && overwriteValue !== null\n ? overwriteValue\n : this.initialValueFactory();\n return true;\n }\n\n const base =\n this.value === undefined ? this.initialValueFactory() : this.value;\n this.value = this.reducer(base, values as UpdateType[]);\n return true;\n }\n\n public get(): ValueType {\n if (this.value === undefined) {\n throw new EmptyChannelError();\n }\n return this.value;\n }\n\n /**\n * Always returns `undefined` (the Python `MISSING` sentinel). Snapshot\n * decisions live in `createCheckpoint`, which has the channel version and\n * writes a {@link DeltaSnapshot} directly into `channel_values`. For\n * non-snapshot steps the channel does not appear in `channel_values`;\n * reconstruction walks ancestor writes via the saver's\n * `getDeltaChannelHistory`.\n */\n public checkpoint(): undefined {\n return undefined;\n }\n\n isAvailable(): boolean {\n return this.value !== undefined;\n }\n\n equals(other: BaseChannel): boolean {\n if (this === other) return true;\n if (!isDeltaChannel(other)) return false;\n if (this.snapshotFrequency !== other.snapshotFrequency) return false;\n return this.reducer === other.reducer;\n }\n}\n"],"mappings":";;;;;AA0CA,MAAM,kBACJ,UAC4C;AAC5C,QAAO,SAAS,QAAQ,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsClD,IAAa,eAAb,MAAa,qBAGHA,aAAAA,YAIR;CACA,gBAAgB;;CAGhB;CAEA;CAEA;CAEA;CAEA,YACE,SACA,SAIA;AACA,SAAO;EACP,MAAM,oBAAoB,SAAS,qBAAqB;AACxD,MAAI,CAAC,OAAO,UAAU,kBAAkB,IAAI,qBAAqB,EAC/D,OAAM,IAAI,MACR,qDAAqD,oBACtD;AAEH,OAAK,UAAU;AACf,OAAK,oBAAoB;AACzB,OAAK,sBACH,SAAS,8BAA8B,EAAE;AAC3C,OAAK,QAAQ,KAAA;;CAGf,eAAsB,YAAoD;EACxE,MAAM,QAAQ,IAAI,aAAoC,KAAK,SAAS;GAClE,mBAAmB,KAAK;GACxB,qBAAqB,KAAK;GAC3B,CAAC;AACF,MAAI,eAAe,KAAA,EACjB,OAAM,QAAQ,KAAK,qBAAqB;gEACf,WAAW,CACpC,OAAM,QAAQ,WAAW;MAEzB,OAAM,QAAQ;AAEhB,SAAO;;;;;;;;;CAUT,aAAoB,QAAwC;EAC1D,MAAM,SAAS,OAAO,KAAK,MAAM,EAAE,GAAG;AACtC,MAAI,OAAO,WAAW,EAAG;EACzB,IAAI,OAAO,KAAK;EAChB,IAAI,QAAQ;AACZ,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,GAAG;GACzC,MAAM,CAAC,aAAa,kBAAkBC,kBAAAA,mBACpC,OAAO,GACR;AACD,OAAI,aAAa;AACf,WACE,mBAAmB,KAAA,KAAa,mBAAmB,OAC/C,iBACA,KAAK,qBAAqB;AAChC,YAAQ,IAAI;;;EAGhB,MAAM,YAAY,OAAO,MAAM,MAAM;AACrC,OAAK,QAAQ,UAAU,SAAS,IAAI,KAAK,QAAQ,MAAM,UAAU,GAAG;;CAGtE,OAAc,QAA4D;AACxE,MAAI,OAAO,WAAW,EAAG,QAAO;EAEhC,IAAI;EACJ,IAAI,eAAe;AACnB,OAAK,MAAM,SAAS,OAClB,KAAIC,kBAAAA,kBAA6B,MAAM,EAAE;AACvC,OAAI,aACF,OAAM,IAAIC,eAAAA,mBACR,iDACD;AAEH,kBAAe;AACf,MAAG,kBAAkBF,kBAAAA,mBAA8B,MAAM;;AAI7D,MAAI,cAAc;AAKhB,QAAK,QACH,mBAAmB,KAAA,KAAa,mBAAmB,OAC/C,iBACA,KAAK,qBAAqB;AAChC,UAAO;;EAGT,MAAM,OACJ,KAAK,UAAU,KAAA,IAAY,KAAK,qBAAqB,GAAG,KAAK;AAC/D,OAAK,QAAQ,KAAK,QAAQ,MAAM,OAAuB;AACvD,SAAO;;CAGT,MAAwB;AACtB,MAAI,KAAK,UAAU,KAAA,EACjB,OAAM,IAAIG,eAAAA,mBAAmB;AAE/B,SAAO,KAAK;;;;;;;;;;CAWd,aAA+B;CAI/B,cAAuB;AACrB,SAAO,KAAK,UAAU,KAAA;;CAGxB,OAAO,OAA6B;AAClC,MAAI,SAAS,MAAO,QAAO;AAC3B,MAAI,CAAC,eAAe,MAAM,CAAE,QAAO;AACnC,MAAI,KAAK,sBAAsB,MAAM,kBAAmB,QAAO;AAC/D,SAAO,KAAK,YAAY,MAAM"}