@langchain/langgraph
Version:
LangGraph
457 lines • 17.6 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.RemoteGraph = void 0;
const langgraph_sdk_1 = require("@langchain/langgraph-sdk");
const graph_1 = require("@langchain/core/runnables/graph");
const runnables_1 = require("@langchain/core/runnables");
const messages_1 = require("@langchain/core/messages");
const web_js_1 = require("../web.cjs");
const constants_js_1 = require("../constants.cjs");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const _serializeInputs = (obj) => {
if (obj === null || typeof obj !== "object") {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(_serializeInputs);
}
// Handle BaseMessage instances by converting them to a serializable format
if ((0, messages_1.isBaseMessage)(obj)) {
const dict = obj.toDict();
return {
...dict.data,
role: obj.getType(),
};
}
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, _serializeInputs(value)]));
};
/**
* Return a tuple of the final list of stream modes sent to the
* remote graph and a boolean flag indicating if only one stream mode was
* originally requested and whether stream mode 'updates'
* was present in the original list of stream modes.
*
* 'updates' mode is always added to the list of stream modes so that interrupts
* can be detected in the remote graph.
*/
const getStreamModes = (streamMode, defaultStreamMode = "updates") => {
const updatedStreamModes = [];
let reqUpdates = false;
let reqSingle = true;
if (streamMode !== undefined &&
(typeof streamMode === "string" ||
(Array.isArray(streamMode) && streamMode.length > 0))) {
reqSingle = typeof streamMode === "string";
const mapped = Array.isArray(streamMode) ? streamMode : [streamMode];
updatedStreamModes.push(...mapped);
}
else {
updatedStreamModes.push(defaultStreamMode);
}
if (updatedStreamModes.includes("updates")) {
reqUpdates = true;
}
else {
updatedStreamModes.push("updates");
}
return {
updatedStreamModes,
reqUpdates,
reqSingle,
};
};
/**
* The `RemoteGraph` class is a client implementation for calling remote
* APIs that implement the LangGraph Server API specification.
*
* For example, the `RemoteGraph` class can be used to call APIs from deployments
* on LangGraph Cloud.
*
* `RemoteGraph` behaves the same way as a `StateGraph` and can be used directly as
* a node in another `StateGraph`.
*
* @example
* ```ts
* import { RemoteGraph } from "@langchain/langgraph/remote";
*
* // Can also pass a LangGraph SDK client instance directly
* const remoteGraph = new RemoteGraph({
* graphId: process.env.LANGGRAPH_REMOTE_GRAPH_ID!,
* apiKey: process.env.LANGGRAPH_REMOTE_GRAPH_API_KEY,
* url: process.env.LANGGRAPH_REMOTE_GRAPH_API_URL,
* });
*
* const input = {
* messages: [
* {
* role: "human",
* content: "Hello world!",
* },
* ],
* };
*
* const config = {
* configurable: { thread_id: "threadId1" },
* };
*
* await remoteGraph.invoke(input, config);
* ```
*/
class RemoteGraph extends runnables_1.Runnable {
static lc_name() {
return "RemoteGraph";
}
constructor(params) {
super(params);
Object.defineProperty(this, "lc_namespace", {
enumerable: true,
configurable: true,
writable: true,
value: ["langgraph", "pregel"]
});
Object.defineProperty(this, "lg_is_pregel", {
enumerable: true,
configurable: true,
writable: true,
value: true
});
Object.defineProperty(this, "config", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "graphId", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "client", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "interruptBefore", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "interruptAfter", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.graphId = params.graphId;
this.client =
params.client ??
new langgraph_sdk_1.Client({
apiUrl: params.url,
apiKey: params.apiKey,
defaultHeaders: params.headers,
});
this.config = params.config;
this.interruptBefore = params.interruptBefore;
this.interruptAfter = params.interruptAfter;
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore Remove ignore when we remove support for 0.2 versions of core
withConfig(config) {
const mergedConfig = (0, runnables_1.mergeConfigs)(this.config, config);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new this.constructor({ ...this, config: mergedConfig });
}
_sanitizeConfig(config) {
const reservedConfigurableKeys = new Set([
"callbacks",
"checkpoint_map",
"checkpoint_id",
"checkpoint_ns",
]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const sanitizeObj = (obj) => {
// Remove non-JSON serializable fields from the given object
if (obj && typeof obj === "object") {
if (Array.isArray(obj)) {
return obj.map((v) => sanitizeObj(v));
}
else {
return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, sanitizeObj(v)]));
}
}
try {
JSON.stringify(obj);
return obj;
}
catch {
return null;
}
};
// Remove non-JSON serializable fields from the config
const sanitizedConfig = sanitizeObj(config);
// Only include configurable keys that are not reserved and
// not starting with "__pregel_" prefix
const newConfigurable = Object.fromEntries(Object.entries(sanitizedConfig.configurable ?? {}).filter(([k]) => !reservedConfigurableKeys.has(k) && !k.startsWith("__pregel_")));
return {
tags: sanitizedConfig.tags ?? [],
metadata: sanitizedConfig.metadata ?? {},
configurable: newConfigurable,
};
}
_getConfig(checkpoint) {
return {
configurable: {
thread_id: checkpoint.thread_id,
checkpoint_ns: checkpoint.checkpoint_ns,
checkpoint_id: checkpoint.checkpoint_id,
checkpoint_map: checkpoint.checkpoint_map ?? {},
},
};
}
_getCheckpoint(config) {
if (config?.configurable === undefined) {
return undefined;
}
const checkpointKeys = [
"thread_id",
"checkpoint_ns",
"checkpoint_id",
"checkpoint_map",
];
const checkpoint = Object.fromEntries(checkpointKeys
.map((key) => [key, config.configurable[key]])
.filter(([_, value]) => value !== undefined));
return Object.keys(checkpoint).length > 0 ? checkpoint : undefined;
}
_createStateSnapshot(state) {
const tasks = state.tasks.map((task) => {
return {
id: task.id,
name: task.name,
error: task.error ? { message: task.error } : undefined,
interrupts: task.interrupts,
// eslint-disable-next-line no-nested-ternary
state: task.state
? this._createStateSnapshot(task.state)
: task.checkpoint
? { configurable: task.checkpoint }
: undefined,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
result: task.result,
};
});
return {
values: state.values,
next: state.next ? [...state.next] : [],
config: {
configurable: {
thread_id: state.checkpoint.thread_id,
checkpoint_ns: state.checkpoint.checkpoint_ns,
checkpoint_id: state.checkpoint.checkpoint_id,
checkpoint_map: state.checkpoint.checkpoint_map ?? {},
},
},
metadata: state.metadata
? state.metadata
: undefined,
createdAt: state.created_at ?? undefined,
parentConfig: state.parent_checkpoint
? {
configurable: {
thread_id: state.parent_checkpoint.thread_id,
checkpoint_ns: state.parent_checkpoint.checkpoint_ns,
checkpoint_id: state.parent_checkpoint.checkpoint_id,
checkpoint_map: state.parent_checkpoint.checkpoint_map ?? {},
},
}
: undefined,
tasks,
};
}
async invoke(input, options) {
let lastValue;
const stream = await this.stream(input, {
...options,
streamMode: "values",
});
for await (const chunk of stream) {
lastValue = chunk;
}
return lastValue;
}
streamEvents(_input, _options) {
throw new Error("Not implemented.");
}
async *_streamIterator(input, options) {
const mergedConfig = (0, runnables_1.mergeConfigs)(this.config, options);
const sanitizedConfig = this._sanitizeConfig(mergedConfig);
const streamProtocolInstance = options?.configurable?.[constants_js_1.CONFIG_KEY_STREAM];
const streamSubgraphs = options?.subgraphs ?? streamProtocolInstance !== undefined;
const interruptBefore = options?.interruptBefore ?? this.interruptBefore;
const interruptAfter = options?.interruptAfter ?? this.interruptAfter;
const { updatedStreamModes, reqSingle, reqUpdates } = getStreamModes(options?.streamMode);
const extendedStreamModes = [
...new Set([
...updatedStreamModes,
...(streamProtocolInstance?.modes ?? new Set()),
]),
].map((mode) => {
if (mode === "messages")
return "messages-tuple";
return mode;
});
let command;
let serializedInput;
if ((0, constants_js_1.isCommand)(input)) {
// TODO: Remove cast when SDK type fix gets merged
command = input.toJSON();
serializedInput = undefined;
}
else {
serializedInput = _serializeInputs(input);
}
for await (const chunk of this.client.runs.stream(sanitizedConfig.configurable.thread_id, this.graphId, {
command,
input: serializedInput,
config: sanitizedConfig,
streamMode: extendedStreamModes,
interruptBefore: interruptBefore,
interruptAfter: interruptAfter,
streamSubgraphs,
ifNotExists: "create",
})) {
let mode;
let namespace;
if (chunk.event.includes(constants_js_1.CHECKPOINT_NAMESPACE_SEPARATOR)) {
const eventComponents = chunk.event.split(constants_js_1.CHECKPOINT_NAMESPACE_SEPARATOR);
// eslint-disable-next-line prefer-destructuring
mode = eventComponents[0];
namespace = eventComponents.slice(1);
}
else {
mode = chunk.event;
namespace = [];
}
const callerNamespace = options?.configurable?.checkpoint_ns;
if (typeof callerNamespace === "string") {
namespace = callerNamespace
.split(constants_js_1.CHECKPOINT_NAMESPACE_SEPARATOR)
.concat(namespace);
}
if (streamProtocolInstance !== undefined &&
streamProtocolInstance.modes?.has(chunk.event)) {
streamProtocolInstance.push([namespace, mode, chunk.data]);
}
if (chunk.event.startsWith("updates")) {
if (typeof chunk.data === "object" &&
chunk.data?.[constants_js_1.INTERRUPT] !== undefined) {
throw new web_js_1.GraphInterrupt(chunk.data[constants_js_1.INTERRUPT]);
}
if (!reqUpdates) {
continue;
}
}
else if (chunk.event?.startsWith("error")) {
throw new web_js_1.RemoteException(typeof chunk.data === "string"
? chunk.data
: JSON.stringify(chunk.data));
}
if (!updatedStreamModes.includes(chunk.event.split(constants_js_1.CHECKPOINT_NAMESPACE_SEPARATOR)[0])) {
continue;
}
if (options?.subgraphs) {
if (reqSingle) {
yield [namespace, chunk.data];
}
else {
yield [namespace, mode, chunk.data];
}
}
else if (reqSingle) {
yield chunk.data;
}
else {
yield [mode, chunk.data];
}
}
}
async updateState(inputConfig, values, asNode) {
const mergedConfig = (0, runnables_1.mergeConfigs)(this.config, inputConfig);
const response = await this.client.threads.updateState(mergedConfig.configurable?.thread_id, { values, asNode, checkpoint: this._getCheckpoint(mergedConfig) });
// TODO: Fix SDK typing
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return this._getConfig(response.checkpoint);
}
async *getStateHistory(config, options) {
const mergedConfig = (0, runnables_1.mergeConfigs)(this.config, config);
const states = await this.client.threads.getHistory(mergedConfig.configurable?.thread_id, {
limit: options?.limit ?? 10,
// TODO: Fix type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
before: this._getCheckpoint(options?.before),
metadata: options?.filter,
checkpoint: this._getCheckpoint(mergedConfig),
});
for (const state of states) {
yield this._createStateSnapshot(state);
}
}
_getDrawableNodes(nodes) {
const nodesMap = {};
for (const node of nodes) {
const nodeId = node.id;
nodesMap[nodeId] = {
id: nodeId.toString(),
name: typeof node.data === "string" ? node.data : node.data?.name ?? "",
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: node.data ?? {},
metadata: typeof node.data !== "string" ? node.data?.metadata ?? {} : {},
};
}
return nodesMap;
}
async getState(config, options) {
const mergedConfig = (0, runnables_1.mergeConfigs)(this.config, config);
const state = await this.client.threads.getState(mergedConfig.configurable?.thread_id, this._getCheckpoint(mergedConfig), options);
return this._createStateSnapshot(state);
}
/** @deprecated Use getGraphAsync instead. The async method will become the default in the next minor release. */
getGraph(_) {
throw new Error(`The synchronous "getGraph" is not supported for this graph. Call "getGraphAsync" instead.`);
}
/**
* Returns a drawable representation of the computation graph.
*/
async getGraphAsync(config) {
const graph = await this.client.assistants.getGraph(this.graphId, {
xray: config?.xray,
});
return new graph_1.Graph({
nodes: this._getDrawableNodes(graph.nodes),
edges: graph.edges,
});
}
/** @deprecated Use getSubgraphsAsync instead. The async method will become the default in the next minor release. */
getSubgraphs() {
throw new Error(`The synchronous "getSubgraphs" method is not supported for this graph. Call "getSubgraphsAsync" instead.`);
}
async *getSubgraphsAsync(namespace, recurse = false) {
const subgraphs = await this.client.assistants.getSubgraphs(this.graphId, {
namespace,
recurse,
});
for (const [ns, graphSchema] of Object.entries(subgraphs)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const remoteSubgraph = new this.constructor({
...this,
graphId: graphSchema.graph_id,
});
yield [ns, remoteSubgraph];
}
}
}
exports.RemoteGraph = RemoteGraph;
//# sourceMappingURL=remote.js.map
;