feat(api): add support for dynamically created graphs via runnable config

This commit is contained in:
Tat Dat Duong
2025-03-26 22:12:47 +01:00
parent 01a657b5fe
commit 6acc91b4c2
5 changed files with 60 additions and 20 deletions
+6 -3
View File
@@ -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("|"));
+12 -4
View File
@@ -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<string, CompiledGraph<string>> = {};
export const GRAPHS: Record<
string,
CompiledGraph<string> | CompiledGraphFactory<string>
> = {};
export const GRAPH_SPEC: Record<string, GraphSpec> = {};
export const GRAPH_SCHEMA: Record<string, Record<string, GraphSchema>> = {};
@@ -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 {
+25 -8
View File
@@ -23,13 +23,17 @@ export interface GraphSpec {
exportSymbol: string;
}
export type CompiledGraphFactory<T extends string> = (config: {
configurable?: Record<string, unknown>;
}) => Promise<CompiledGraph<T>>;
export async function resolveGraph(
spec: string,
options: { cwd: string; onlyFilePresence?: false },
): Promise<{
sourceFile: string;
exportSymbol: string;
resolved: CompiledGraph<string>;
resolved: CompiledGraph<string> | CompiledGraphFactory<string>;
}>;
export async function resolveGraph(
@@ -55,7 +59,9 @@ export async function resolveGraph(
type GraphUnknown =
| GraphLike
| Promise<GraphLike>
| (() => GraphLike | Promise<GraphLike>)
| ((config: {
configurable?: Record<string, unknown>;
}) => GraphLike | Promise<GraphLike>)
| undefined;
const isGraph = (graph: GraphLike): graph is Graph<string> => {
@@ -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<string> = 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<string> | CompiledGraphFactory<string> =
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<string> => {
const graph = isGraph(graphLike) ? graphLike.compile() : graphLike;
return graph;
};
if (typeof graph === "function") {
return async (config: { configurable?: Record<string, unknown> }) => {
const graphLike = await graph(config);
return afterResolve(graphLike);
};
}
return afterResolve(await graph);
})();
return { sourceFile, exportSymbol, resolved };
}
+16 -4
View File
@@ -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 } }
+1 -1
View File
@@ -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,
});