fix(langgraph): fix invalid safe serialization of RunnableConfig for RemoteGraph (#1473)

This commit is contained in:
David Duong
2025-07-31 02:40:34 +02:00
committed by GitHub
parent c1b971a3a5
commit f2cc7043d1
3 changed files with 80 additions and 14 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@langchain/langgraph": patch
---
fix(langgraph): RemotePregel serialization fix
+15 -14
View File
@@ -219,24 +219,25 @@ export class RemoteGraph<
"checkpoint_ns",
]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const sanitizeObj = (obj: any): any => {
// 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)])
);
}
}
const sanitizeObj = <T>(obj: T): T => {
try {
// This will only throw if we're trying to serialize a circular reference
// or trying to serialize a BigInt...
JSON.stringify(obj);
return obj;
} catch {
return null;
const seen = new WeakSet();
return JSON.parse(
JSON.stringify(obj, (_, value) => {
if (typeof value === "object" && value != null) {
if (seen.has(value)) return "[Circular]";
seen.add(value);
}
if (typeof value === "bigint") return value.toString();
return value;
})
);
}
};
+60
View File
@@ -577,4 +577,64 @@ describe("RemoteGraph", () => {
}),
]);
});
test("handle circular references", async () => {
const client = new Client({});
const streamSpy = vi
.spyOn((client as any).runs, "stream")
.mockImplementation(async function* () {
yield {
event: "values",
data: { messages: [{ type: "human", content: "world" }] },
};
});
const remotePregel = new RemoteGraph({ client, graphId: "test_graph_id" });
const config: any = {
configurable: { thread_id: "thread_1", bigint: 123n },
metadata: { source: "test" },
tags: [],
};
config.configurable.circular = config;
config.metadata.circular = config;
config.tags.push(config);
const result = await remotePregel.invoke({}, config);
expect(result).toEqual({ messages: [{ type: "human", content: "world" }] });
expect(streamSpy).toHaveBeenCalledWith(
"thread_1",
"test_graph_id",
expect.objectContaining({
config: expect.objectContaining({
configurable: {
circular: "[Circular]",
bigint: "123",
thread_id: "thread_1",
},
metadata: {
source: "test",
circular: "[Circular]",
thread_id: "thread_1",
},
tags: [
{
configurable: {
circular: "[Circular]",
bigint: "123",
thread_id: "thread_1",
},
metadata: {
source: "test",
circular: "[Circular]",
thread_id: "thread_1",
},
tags: ["[Circular]"],
},
],
}),
})
);
});
});