Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
"updateInternalDependencies": "patch",
"ignore": [
"@langchain/langgraph-sdk-validation",
"@langchain/langgraph-benchmark",
"examples"
"@langchain/langgraph-benchmark"
],
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"onlyUpdatePeerDependentsWhenOutOfRange": true
Expand Down
5 changes: 5 additions & 0 deletions .changeset/thick-wings-open.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@langchain/langgraph": minor
---

Remove deprecated `getGraph` method. Use `getGraphAsync` instead.
217 changes: 31 additions & 186 deletions libs/langgraph/src/graph/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,7 @@ import {
START,
TAG_HIDDEN,
} from "../constants.js";
import {
gatherIterator,
gatherIteratorSync,
RunnableCallable,
} from "../utils.js";
import { gatherIterator, RunnableCallable } from "../utils.js";
import {
InvalidUpdateError,
NodeInterrupt,
Expand Down Expand Up @@ -755,13 +751,32 @@ export class CompiledGraph<
}
if (xray) {
const newXrayValue = typeof xray === "number" ? xray - 1 : xray;
const drawableSubgraph =
subgraphs[key] !== undefined
? await subgraphs[key].getGraphAsync({
...config,
xray: newXrayValue,
})

const hasGraphAsync = (
node: unknown
): node is {
getGraphAsync: (config: RunnableConfig) => Promise<DrawableGraph>;
} => {
return (
typeof node === "object" &&
node !== null &&
"getGraphAsync" in node &&
typeof node.getGraphAsync === "function"
);
};

const drawableSubgraph = await (async () => {
if (subgraphs[key] !== undefined) {
return subgraphs[key].getGraphAsync({
...config,
xray: newXrayValue,
});
}

return hasGraphAsync(node)
? node.getGraphAsync(config)
: node.getGraph(config);
})();

drawableSubgraph.trimFirstNode();
drawableSubgraph.trimLastNode();
Expand Down Expand Up @@ -890,182 +905,12 @@ export class CompiledGraph<
/**
* Returns a drawable representation of the computation graph.
*
* @deprecated Use getGraphAsync instead. The async method will be the default in the next minor core release.
* @deprecated Use getGraphAsync instead.
*/
override getGraph(
config?: RunnableConfig & { xray?: boolean | number }
): DrawableGraph {
const xray = config?.xray;
const graph = new DrawableGraph();
const startNodes: Record<string, DrawableGraphNode> = {
[START]: graph.addNode(
{
schema: z.any(),
},
START
),
};
const endNodes: Record<string, DrawableGraphNode> = {};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let subgraphs: Record<string, CompiledGraph<any>> = {};
if (xray) {
subgraphs = Object.fromEntries(
gatherIteratorSync(this.getSubgraphs()).filter(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(x): x is [string, CompiledGraph<any>] => isCompiledGraph(x[1])
)
);
}

function addEdge(
start: string,
end: string,
label?: string,
conditional = false
) {
if (end === END && endNodes[END] === undefined) {
endNodes[END] = graph.addNode({ schema: z.any() }, END);
}
return graph.addEdge(
startNodes[start],
endNodes[end],
label !== end ? label : undefined,
conditional
);
}

for (const [key, nodeSpec] of Object.entries(this.builder.nodes) as [
N,
NodeSpec<State, Update>
][]) {
const displayKey = _escapeMermaidKeywords(key);
const node = nodeSpec.runnable;
const metadata = nodeSpec.metadata ?? {};
if (
this.interruptBefore?.includes(key) &&
this.interruptAfter?.includes(key)
) {
metadata.__interrupt = "before,after";
} else if (this.interruptBefore?.includes(key)) {
metadata.__interrupt = "before";
} else if (this.interruptAfter?.includes(key)) {
metadata.__interrupt = "after";
}
if (xray) {
const newXrayValue = typeof xray === "number" ? xray - 1 : xray;
const drawableSubgraph =
subgraphs[key] !== undefined
? subgraphs[key].getGraph({
...config,
xray: newXrayValue,
})
: node.getGraph(config);
drawableSubgraph.trimFirstNode();
drawableSubgraph.trimLastNode();
if (Object.keys(drawableSubgraph.nodes).length > 1) {
const [e, s] = graph.extend(drawableSubgraph, displayKey);
if (e === undefined) {
throw new Error(
`Could not extend subgraph "${key}" due to missing entrypoint.`
);
}

// TODO: Remove default name once we stop supporting core 0.2.0
// eslint-disable-next-line no-inner-declarations
function _isRunnableInterface(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
thing: any
): thing is RunnableInterface {
return thing ? thing.lc_runnable : false;
}
// eslint-disable-next-line no-inner-declarations
function _nodeDataStr(
id: string | undefined,
data: RunnableInterface | RunnableIOSchema
): string {
if (id !== undefined && !isUuid(id)) {
return id;
} else if (_isRunnableInterface(data)) {
try {
let dataStr = data.getName();
dataStr = dataStr.startsWith("Runnable")
? dataStr.slice("Runnable".length)
: dataStr;
return dataStr;
} catch (error) {
return data.getName();
}
} else {
return data.name ?? "UnknownSchema";
}
}
// TODO: Remove casts when we stop supporting core 0.2.0
if (s !== undefined) {
startNodes[displayKey] = {
name: _nodeDataStr(s.id, s.data),
...s,
} as DrawableGraphNode;
}
endNodes[displayKey] = {
name: _nodeDataStr(e.id, e.data),
...e,
} as DrawableGraphNode;
} else {
// TODO: Remove when we stop supporting core 0.2.0
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const newNode = graph.addNode(node, displayKey, metadata);
startNodes[displayKey] = newNode;
endNodes[displayKey] = newNode;
}
} else {
// TODO: Remove when we stop supporting core 0.2.0
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const newNode = graph.addNode(node, displayKey, metadata);
startNodes[displayKey] = newNode;
endNodes[displayKey] = newNode;
}
}
const sortedEdges = [...this.builder.allEdges].sort(([a], [b]) => {
if (a < b) {
return -1;
} else if (b > a) {
return 1;
} else {
return 0;
}
});
for (const [start, end] of sortedEdges) {
addEdge(_escapeMermaidKeywords(start), _escapeMermaidKeywords(end));
}
for (const [start, branches] of Object.entries(this.builder.branches)) {
const defaultEnds: Record<string, string> = {
...Object.fromEntries(
Object.keys(this.builder.nodes)
.filter((k) => k !== start)
.map((k) => [_escapeMermaidKeywords(k), _escapeMermaidKeywords(k)])
),
[END]: END,
};
for (const branch of Object.values(branches)) {
let ends;
if (branch.ends !== undefined) {
ends = branch.ends;
} else {
ends = defaultEnds;
}
for (const [label, end] of Object.entries(ends)) {
addEdge(
_escapeMermaidKeywords(start),
_escapeMermaidKeywords(end),
label,
true
);
}
}
}
return graph;
override getGraph(): DrawableGraph {
throw new Error(
`The synchronous "getGraph" is not supported for this graph. Call "getGraphAsync" instead.`
);
}
}

Expand Down
33 changes: 8 additions & 25 deletions libs/langgraph/src/pregel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -666,19 +666,18 @@ export class Pregel<
}

/**
* Gets all subgraphs within this graph.
* Gets all subgraphs within this graph asynchronously.
* A subgraph is a Pregel instance that is nested within a node of this graph.
*
* @deprecated Use getSubgraphsAsync instead. The async method will become the default in the next minor release.
* @param namespace - Optional namespace to filter subgraphs
* @param recurse - Whether to recursively get subgraphs of subgraphs
* @returns Generator yielding tuples of [name, subgraph]
* @returns AsyncGenerator yielding tuples of [name, subgraph]
*/
*getSubgraphs(
async *getSubgraphsAsync(
namespace?: string,
recurse?: boolean
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): Generator<[string, Pregel<any, any>]> {
): AsyncGenerator<[string, Pregel<any, any>]> {
for (const [name, node] of Object.entries(this.nodes)) {
// filter by prefix
if (namespace !== undefined) {
Expand Down Expand Up @@ -710,10 +709,10 @@ export class Pregel<
if (namespace !== undefined) {
newNamespace = namespace.slice(name.length + 1);
}
for (const [subgraphName, subgraph] of graph.getSubgraphs(
newNamespace,
recurse
)) {
for await (const [
subgraphName,
subgraph,
] of graph.getSubgraphsAsync(newNamespace, recurse)) {
yield [
`${name}${CHECKPOINT_NAMESPACE_SEPARATOR}${subgraphName}`,
subgraph,
Expand All @@ -725,22 +724,6 @@ export class Pregel<
}
}

/**
* Gets all subgraphs within this graph asynchronously.
* A subgraph is a Pregel instance that is nested within a node of this graph.
*
* @param namespace - Optional namespace to filter subgraphs
* @param recurse - Whether to recursively get subgraphs of subgraphs
* @returns AsyncGenerator yielding tuples of [name, subgraph]
*/
async *getSubgraphsAsync(
namespace?: string,
recurse?: boolean
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): AsyncGenerator<[string, Pregel<any, any>]> {
yield* this.getSubgraphs(namespace, recurse);
}

/**
* Prepares a state snapshot from saved checkpoint data.
* This is an internal method used by getState and getStateHistory.
Expand Down
16 changes: 0 additions & 16 deletions libs/langgraph/src/pregel/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,15 +574,6 @@ export class RemoteGraph<
return this._createStateSnapshot(state);
}

/** @deprecated Use getGraphAsync instead. The async method will become the default in the next minor release. */
override getGraph(
_?: RunnableConfig & { xray?: boolean | number }
): DrawableGraph {
throw new Error(
`The synchronous "getGraph" is not supported for this graph. Call "getGraphAsync" instead.`
);
}

/**
* Returns a drawable representation of the computation graph.
*/
Expand All @@ -596,13 +587,6 @@ export class RemoteGraph<
});
}

/** @deprecated Use getSubgraphsAsync instead. The async method will become the default in the next minor release. */
getSubgraphs(): Generator<[string, PregelInterface<Nn, Cc, ContextType>]> {
throw new Error(
`The synchronous "getSubgraphs" method is not supported for this graph. Call "getSubgraphsAsync" instead.`
);
}

async *getSubgraphsAsync(
namespace?: string,
recurse = false
Expand Down
7 changes: 0 additions & 7 deletions libs/langgraph/src/pregel/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,13 +359,6 @@ export interface PregelInterface<
config: RunnableConfig & { xray?: boolean | number }
): Promise<DrawableGraph>;

/** @deprecated Use getSubgraphsAsync instead. The async method will become the default in the next minor release. */
getSubgraphs(
namespace?: string,
recurse?: boolean
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): Generator<[string, PregelInterface<any, any>]>;

getSubgraphsAsync(
namespace?: string,
recurse?: boolean
Expand Down
6 changes: 3 additions & 3 deletions libs/langgraph/src/tests/diagrams.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ test("prebuilt agent", async () => {

const app = createReactAgent({ llm: model, tools });

const graph = app.getGraph();
const graph = await app.getGraphAsync();
const mermaid = graph.drawMermaid();
expect(mermaid).toEqual(`%%{init: {'flowchart': {'curve': 'linear'}}}%%
graph TD;
Expand Down Expand Up @@ -40,7 +40,7 @@ test("graph with multiple sinks", async () => {
.addConditionalEdges("inner1", async () => "inner2", ["inner2", "inner3"])
.compile();

const graph = app.getGraph();
const graph = await app.getGraphAsync();
const mermaid = graph.drawMermaid();
expect(mermaid).toEqual(`%%{init: {'flowchart': {'curve': 'linear'}}}%%
graph TD;
Expand Down Expand Up @@ -77,7 +77,7 @@ test("graph with subgraphs", async () => {
.addConditionalEdges("starter", async () => "final", ["inner", "final"])
.compile({ interruptBefore: ["starter"] });

const graph = app.getGraph({ xray: true });
const graph = await app.getGraphAsync({ xray: true });
const mermaid = graph.drawMermaid();
expect(mermaid).toEqual(`%%{init: {'flowchart': {'curve': 'linear'}}}%%
graph TD;
Expand Down
Loading
Loading