mirror of
https://github.com/langchain-ai/langgraphjs-api.git
synced 2026-07-19 13:25:54 -04:00
feat(api): add support for dynamically created graphs via runnable config
This commit is contained in:
@@ -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("|"));
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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 } }
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user