From 6acc91b4c211d1680b7dca8a612da3c44e2c3caa Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Wed, 26 Mar 2025 22:12:47 +0100 Subject: [PATCH] feat(api): add support for dynamically created graphs via runnable config --- libs/langgraph-api/src/api/assistants.mts | 9 ++++-- libs/langgraph-api/src/graph/load.mts | 16 +++++++--- libs/langgraph-api/src/graph/load.utils.mts | 33 ++++++++++++++++----- libs/langgraph-api/src/storage/ops.mts | 20 ++++++++++--- libs/langgraph-api/src/stream.mts | 2 +- 5 files changed, 60 insertions(+), 20 deletions(-) diff --git a/libs/langgraph-api/src/api/assistants.mts b/libs/langgraph-api/src/api/assistants.mts index bfc9f2e..a668124 100644 --- a/libs/langgraph-api/src/api/assistants.mts +++ b/libs/langgraph-api/src/api/assistants.mts @@ -109,9 +109,10 @@ api.get( const assistant = await Assistants.get(assistantId); const { xray } = c.req.valid("query"); - const graph = getGraph(assistant.graph_id); + const config = getRunnableConfig(assistant.config); + const graph = await getGraph(assistant.graph_id, config); const drawable = await graph.getGraphAsync({ - ...getRunnableConfig(assistant.config), + ...config, xray: xray ?? undefined, }); return c.json(drawable.toJSON()); @@ -152,7 +153,9 @@ api.get( const assistantId = getAssistantId(assistant_id); const assistant = await Assistants.get(assistantId); - const graph = getGraph(assistant.graph_id); + + const config = getRunnableConfig(assistant.config); + const graph = await getGraph(assistant.graph_id, config); const graphSchema = await getGraphSchema(assistant.graph_id); const rootGraphId = Object.keys(graphSchema).find((i) => !i.includes("|")); diff --git a/libs/langgraph-api/src/graph/load.mts b/libs/langgraph-api/src/graph/load.mts index 16f98b4..30343e8 100644 --- a/libs/langgraph-api/src/graph/load.mts +++ b/libs/langgraph-api/src/graph/load.mts @@ -6,9 +6,11 @@ import type { BaseCheckpointSaver, BaseStore, CompiledGraph, + LangGraphRunnableConfig, } from "@langchain/langgraph"; import { HTTPException } from "hono/http-exception"; import { + type CompiledGraphFactory, type GraphSchema, type GraphSpec, resolveGraph, @@ -18,7 +20,10 @@ import { checkpointer } from "../storage/checkpoint.mjs"; import { store } from "../storage/store.mjs"; import { logger } from "../logging.mjs"; -export const GRAPHS: Record> = {}; +export const GRAPHS: Record< + string, + CompiledGraph | CompiledGraphFactory +> = {}; export const GRAPH_SPEC: Record = {}; export const GRAPH_SCHEMA: Record> = {}; @@ -69,8 +74,9 @@ export async function registerFromEnv( ); } -export function getGraph( +export async function getGraph( graphId: string, + config: LangGraphRunnableConfig | undefined, options?: { checkpointer?: BaseCheckpointSaver | null; store?: BaseStore; @@ -79,9 +85,11 @@ export function getGraph( if (!GRAPHS[graphId]) throw new HTTPException(404, { message: `Graph "${graphId}" not found` }); - // TODO: have a check for the type of graph + const compiled = + typeof GRAPHS[graphId] === "function" + ? await GRAPHS[graphId](config ?? { configurable: {} }) + : GRAPHS[graphId]; - const compiled = GRAPHS[graphId]; if (typeof options?.checkpointer !== "undefined") { compiled.checkpointer = options?.checkpointer ?? undefined; } else { diff --git a/libs/langgraph-api/src/graph/load.utils.mts b/libs/langgraph-api/src/graph/load.utils.mts index 8904d85..1111f45 100644 --- a/libs/langgraph-api/src/graph/load.utils.mts +++ b/libs/langgraph-api/src/graph/load.utils.mts @@ -23,13 +23,17 @@ export interface GraphSpec { exportSymbol: string; } +export type CompiledGraphFactory = (config: { + configurable?: Record; +}) => Promise>; + export async function resolveGraph( spec: string, options: { cwd: string; onlyFilePresence?: false }, ): Promise<{ sourceFile: string; exportSymbol: string; - resolved: CompiledGraph; + resolved: CompiledGraph | CompiledGraphFactory; }>; export async function resolveGraph( @@ -55,7 +59,9 @@ export async function resolveGraph( type GraphUnknown = | GraphLike | Promise - | (() => GraphLike | Promise) + | ((config: { + configurable?: Record; + }) => GraphLike | Promise) | undefined; const isGraph = (graph: GraphLike): graph is Graph => { @@ -68,13 +74,24 @@ export async function resolveGraph( ).then((module) => module[exportSymbol || "default"]); // obtain the graph, and if not compiled, compile it - const resolved: CompiledGraph = await (async () => { - if (!graph) throw new Error("Failed to load graph: graph is nullush"); - const graphLike = typeof graph === "function" ? await graph() : await graph; + const resolved: CompiledGraph | CompiledGraphFactory = + await (async () => { + if (!graph) throw new Error("Failed to load graph: graph is nullush"); - if (isGraph(graphLike)) return graphLike.compile(); - return graphLike; - })(); + const afterResolve = (graphLike: GraphLike): CompiledGraph => { + const graph = isGraph(graphLike) ? graphLike.compile() : graphLike; + return graph; + }; + + if (typeof graph === "function") { + return async (config: { configurable?: Record }) => { + const graphLike = await graph(config); + return afterResolve(graphLike); + }; + } + + return afterResolve(await graph); + })(); return { sourceFile, exportSymbol, resolved }; } diff --git a/libs/langgraph-api/src/storage/ops.mts b/libs/langgraph-api/src/storage/ops.mts index 4450d91..7e3503e 100644 --- a/libs/langgraph-api/src/storage/ops.mts +++ b/libs/langgraph-api/src/storage/ops.mts @@ -803,7 +803,10 @@ export class Threads { }; } - const graph = await getGraph(graphId, { checkpointer, store }); + const graph = await getGraph(graphId, thread.config, { + checkpointer, + store, + }); const result = await graph.getState(config, { subgraphs }); if ( @@ -843,7 +846,10 @@ export class Threads { config.configurable ??= {}; config.configurable.graph_id ??= graphId; - const graph = await getGraph(graphId, { checkpointer, store }); + const graph = await getGraph(graphId, thread.config, { + checkpointer, + store, + }); const updateConfig = structuredClone(config); updateConfig.configurable ??= {}; @@ -894,7 +900,10 @@ export class Threads { config.configurable ??= {}; config.configurable.graph_id ??= graphId; - const graph = await getGraph(graphId, { checkpointer, store }); + const graph = await getGraph(graphId, thread.config, { + checkpointer, + store, + }); const updateConfig = structuredClone(config); updateConfig.configurable ??= {}; @@ -940,7 +949,10 @@ export class Threads { const graphId = thread.metadata?.graph_id as string | undefined | null; if (graphId == null) return []; - const graph = await getGraph(graphId, { checkpointer, store }); + const graph = await getGraph(graphId, thread.config, { + checkpointer, + store, + }); const before: RunnableConfig | undefined = typeof options?.before === "string" ? { configurable: { checkpoint_id: options.before } } diff --git a/libs/langgraph-api/src/stream.mts b/libs/langgraph-api/src/stream.mts index 66d6610..7500b0e 100644 --- a/libs/langgraph-api/src/stream.mts +++ b/libs/langgraph-api/src/stream.mts @@ -153,7 +153,7 @@ export async function* streamState( throw new Error("Invalid or missing graph_id"); } - const graph = getGraph(graphId, { + const graph = await getGraph(graphId, kwargs.config, { checkpointer: kwargs.temporary ? null : undefined, });