From 703fed2f699bf53bb04d0e67dd6ff6509eba7698 Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Sat, 3 May 2025 00:08:47 +0200 Subject: [PATCH] Simplify build process at the expense of emitting all declaration files --- libs/langgraph-api/package.json | 10 ++- libs/langgraph-api/scripts/build.mjs | 6 -- libs/langgraph-api/src/api/assistants.mts | 13 +-- libs/langgraph-api/src/graph/load.mts | 60 +++++--------- libs/langgraph-api/src/graph/load.utils.mts | 71 +---------------- libs/langgraph-api/src/graph/parser/index.mts | 79 +++++++++++++++++++ .../langgraph-api/src/graph/parser/parser.mts | 16 +++- libs/langgraph-api/tsconfig.json | 1 + 8 files changed, 132 insertions(+), 124 deletions(-) create mode 100644 libs/langgraph-api/src/graph/parser/index.mts diff --git a/libs/langgraph-api/package.json b/libs/langgraph-api/package.json index 70d3f37..4c92a15 100644 --- a/libs/langgraph-api/package.json +++ b/libs/langgraph-api/package.json @@ -12,17 +12,21 @@ ], "exports": { ".": { - "types": "./dist/cli/spawn.d.ts", + "types": "./dist/cli/spawn.d.mts", "default": "./dist/cli/spawn.mjs" }, "./auth": { - "types": "./dist/auth/index.d.ts", + "types": "./dist/auth/index.d.mts", "default": "./dist/auth/index.mjs" }, "./semver": { - "types": "./dist/semver/index.d.ts", + "types": "./dist/semver/index.d.mts", "default": "./dist/semver/index.mjs" }, + "./schema": { + "types": "./dist/graph/parser/index.d.mts", + "default": "./dist/graph/parser/index.mjs" + }, "./package.json": "./package.json" }, "repository": { diff --git a/libs/langgraph-api/scripts/build.mjs b/libs/langgraph-api/scripts/build.mjs index 03a69bc..7322715 100644 --- a/libs/langgraph-api/scripts/build.mjs +++ b/libs/langgraph-api/scripts/build.mjs @@ -4,12 +4,6 @@ import { $ } from "./utils.mjs"; await $`rm -rf dist`; await $`pnpm tsc --outDir dist`; -await Promise.all([ - $`pnpm tsc --module nodenext --outDir dist/src/cli -d src/cli/spawn.mts`, - $`pnpm tsc --module nodenext --outDir dist/src/auth -d src/auth/index.mts`, - $`pnpm tsc --module nodenext --outDir dist/src/semver -d src/semver/index.mts`, -]); - await $`cp src/graph/parser/schema/types.template.mts dist/src/graph/parser/schema`; await $`rm -rf dist/src/graph/parser/schema/types.template.mjs`; diff --git a/libs/langgraph-api/src/api/assistants.mts b/libs/langgraph-api/src/api/assistants.mts index a025ee0..62fecc4 100644 --- a/libs/langgraph-api/src/api/assistants.mts +++ b/libs/langgraph-api/src/api/assistants.mts @@ -7,9 +7,9 @@ import { z } from "zod"; import { getAssistantId, getGraph, - getGraphSchema, - getRuntimeGraphSchema, + getCachedStaticGraphSchema, } from "../graph/load.mjs"; +import { getRuntimeGraphSchema } from "../graph/parser/index.mjs"; import { Assistants } from "../storage/ops.mjs"; import * as schemas from "../schemas.mjs"; @@ -146,7 +146,7 @@ api.get( const runtimeSchema = await getRuntimeGraphSchema(graph); if (runtimeSchema) return runtimeSchema; - const graphSchema = await getGraphSchema(assistant.graph_id); + const graphSchema = await getCachedStaticGraphSchema(assistant.graph_id); const rootGraphId = Object.keys(graphSchema).find( (i) => !i.includes("|"), ); @@ -191,13 +191,16 @@ api.get( : // @ts-expect-error older versions of langgraph don't have getSubgraphsAsync graph.getSubgraphs.bind(graph); - let graphSchemaPromise: ReturnType | undefined; + let graphSchemaPromise: + | ReturnType + | undefined; + for await (const [ns, subgraph] of subgraphsGenerator(namespace, recurse)) { const schema = await (async () => { const runtimeSchema = await getRuntimeGraphSchema(subgraph); if (runtimeSchema) return runtimeSchema; - graphSchemaPromise ??= getGraphSchema(assistant.graph_id); + graphSchemaPromise ??= getCachedStaticGraphSchema(assistant.graph_id); const graphSchema = await graphSchemaPromise; const rootGraphId = Object.keys(graphSchema).find( diff --git a/libs/langgraph-api/src/graph/load.mts b/libs/langgraph-api/src/graph/load.mts index ac163fa..9b6e43e 100644 --- a/libs/langgraph-api/src/graph/load.mts +++ b/libs/langgraph-api/src/graph/load.mts @@ -2,22 +2,16 @@ import { z } from "zod"; import * as uuid from "uuid"; import { Assistants } from "../storage/ops.mjs"; -import type { JSONSchema7 } from "json-schema"; import type { - Pregel, BaseCheckpointSaver, BaseStore, CompiledGraph, LangGraphRunnableConfig, } from "@langchain/langgraph"; import { HTTPException } from "hono/http-exception"; -import { - type CompiledGraphFactory, - type GraphSchema, - type GraphSpec, - resolveGraph, - runGraphSchemaWorker, -} from "./load.utils.mjs"; +import { type CompiledGraphFactory, resolveGraph } from "./load.utils.mjs"; +import type { GraphSchema, GraphSpec } from "./parser/index.mjs"; +import { getStaticGraphSchema } from "./parser/index.mjs"; import { checkpointer } from "../storage/checkpoint.mjs"; import { store } from "../storage/store.mjs"; import { logger } from "../logging.mjs"; @@ -107,42 +101,30 @@ export async function getGraph( return compiled; } -export async function getRuntimeGraphSchema( - graph: Pregel, -): Promise { - try { - const { - getInputTypeSchema, - getOutputTypeSchema, - getStateTypeSchema, - getConfigTypeSchema, - } = await import("@langchain/langgraph/zod/schema"); - - const result = { - input: getInputTypeSchema(graph) as JSONSchema7 | undefined, - output: getOutputTypeSchema(graph) as JSONSchema7 | undefined, - state: getStateTypeSchema(graph) as JSONSchema7 | undefined, - config: getConfigTypeSchema(graph) as JSONSchema7 | undefined, - }; - - if (Object.values(result).every((i) => i == null)) return undefined; - return result; - } catch { - // ignore - } - - return undefined; -} - -export async function getGraphSchema(graphId: string) { +export async function getCachedStaticGraphSchema(graphId: string) { if (!GRAPH_SPEC[graphId]) throw new HTTPException(404, { message: `Spec for "${graphId}" not found`, }); - if (!GRAPH_SCHEMA[graphId] || true) { + if (!GRAPH_SCHEMA[graphId]) { + let timeoutMs = 30_000; try { - GRAPH_SCHEMA[graphId] = await runGraphSchemaWorker(GRAPH_SPEC[graphId]); + const envTimeout = Number.parseInt( + process.env.LANGGRAPH_SCHEMA_RESOLVE_TIMEOUT_MS ?? "0", + 10, + ); + if (!Number.isNaN(envTimeout) && envTimeout > 0) { + timeoutMs = envTimeout; + } + } catch { + // ignore + } + + try { + GRAPH_SCHEMA[graphId] = await getStaticGraphSchema(GRAPH_SPEC[graphId], { + timeoutMs, + }); } catch (error) { throw new Error(`Failed to extract schema for "${graphId}"`, { cause: error, diff --git a/libs/langgraph-api/src/graph/load.utils.mts b/libs/langgraph-api/src/graph/load.utils.mts index 1111f45..6d686fc 100644 --- a/libs/langgraph-api/src/graph/load.utils.mts +++ b/libs/langgraph-api/src/graph/load.utils.mts @@ -1,28 +1,14 @@ -import { Worker } from "node:worker_threads"; -import * as fs from "node:fs/promises"; import type { CompiledGraph, Graph } from "@langchain/langgraph"; -import * as path from "node:path"; -import { fileURLToPath, pathToFileURL } from "node:url"; import * as uuid from "uuid"; -import type { JSONSchema7 } from "json-schema"; +import * as path from "node:path"; +import * as fs from "node:fs/promises"; +import { pathToFileURL } from "node:url"; export const GRAPHS: Record> = {}; export const NAMESPACE_GRAPH = uuid.parse( "6ba7b821-9dad-11d1-80b4-00c04fd430c8", ); -export interface GraphSchema { - state: JSONSchema7 | undefined; - input: JSONSchema7 | undefined; - output: JSONSchema7 | undefined; - config: JSONSchema7 | undefined; -} - -export interface GraphSpec { - sourceFile: string; - exportSymbol: string; -} - export type CompiledGraphFactory = (config: { configurable?: Record; }) => Promise>; @@ -51,7 +37,7 @@ export async function resolveGraph( // validate file exists await fs.stat(sourceFile); if (options?.onlyFilePresence) { - return { sourceFile: userFile, exportSymbol, resolved: undefined }; + return { sourceFile, exportSymbol, resolved: undefined }; } type GraphLike = CompiledGraph | Graph; @@ -95,52 +81,3 @@ export async function resolveGraph( return { sourceFile, exportSymbol, resolved }; } - -export async function runGraphSchemaWorker( - spec: GraphSpec, - options?: { mainThread?: boolean }, -) { - let SCHEMA_RESOLVE_TIMEOUT_MS = 30_000; - try { - const envTimeout = Number.parseInt( - process.env.LANGGRAPH_SCHEMA_RESOLVE_TIMEOUT_MS ?? "0", - 10, - ); - if (!Number.isNaN(envTimeout) && envTimeout > 0) { - SCHEMA_RESOLVE_TIMEOUT_MS = envTimeout; - } - } catch { - // ignore - } - - if (options?.mainThread) { - const { SubgraphExtractor } = await import("./parser/parser.mjs"); - return SubgraphExtractor.extractSchemas( - spec.sourceFile, - spec.exportSymbol, - { strict: false }, - ); - } - - return await new Promise>((resolve, reject) => { - const worker = new Worker( - fileURLToPath(new URL("./parser/parser.worker.mjs", import.meta.url)), - { argv: process.argv.slice(-1) }, - ); - - // Set a timeout to reject if the worker takes too long - const timeoutId = setTimeout(() => { - worker.terminate(); - reject(new Error("Schema extract worker timed out")); - }, SCHEMA_RESOLVE_TIMEOUT_MS); - - worker.on("message", (result) => { - worker.terminate(); - clearTimeout(timeoutId); - resolve(result); - }); - - worker.on("error", reject); - worker.postMessage(spec); - }); -} diff --git a/libs/langgraph-api/src/graph/parser/index.mts b/libs/langgraph-api/src/graph/parser/index.mts new file mode 100644 index 0000000..03bfd8d --- /dev/null +++ b/libs/langgraph-api/src/graph/parser/index.mts @@ -0,0 +1,79 @@ +import { fileURLToPath } from "node:url"; +import { Worker } from "node:worker_threads"; +import type { JSONSchema7 } from "json-schema"; +import type { Pregel } from "@langchain/langgraph"; + +export interface GraphSchema { + state: JSONSchema7 | undefined; + input: JSONSchema7 | undefined; + output: JSONSchema7 | undefined; + config: JSONSchema7 | undefined; +} + +export interface GraphSpec { + sourceFile: string; + exportSymbol: string; +} + +export async function getStaticGraphSchema( + spec: GraphSpec, + options?: { mainThread?: boolean; timeoutMs?: number }, +): Promise> { + if (options?.mainThread) { + const { SubgraphExtractor } = await import("./parser.mjs"); + return SubgraphExtractor.extractSchemas( + spec.sourceFile, + spec.exportSymbol, + { strict: false }, + ); + } + + return await new Promise>((resolve, reject) => { + const worker = new Worker( + fileURLToPath(new URL("./parser/parser.worker.mjs", import.meta.url)), + { argv: process.argv.slice(-1) }, + ); + + // Set a timeout to reject if the worker takes too long + const timeoutId = setTimeout(() => { + worker.terminate(); + reject(new Error("Schema extract worker timed out")); + }, options?.timeoutMs ?? 30000); + + worker.on("message", (result) => { + worker.terminate(); + clearTimeout(timeoutId); + resolve(result); + }); + + worker.on("error", reject); + worker.postMessage(spec); + }); +} + +export async function getRuntimeGraphSchema( + graph: Pregel, +): Promise { + try { + const { + getInputTypeSchema, + getOutputTypeSchema, + getUpdateTypeSchema, + getConfigTypeSchema, + } = await import("@langchain/langgraph/zod/schema"); + + const result = { + state: getUpdateTypeSchema(graph), + input: getInputTypeSchema(graph), + output: getOutputTypeSchema(graph), + config: getConfigTypeSchema(graph), + } as GraphSchema; + + if (Object.values(result).every((i) => i == null)) return undefined; + return result; + } catch { + // ignore + } + + return undefined; +} diff --git a/libs/langgraph-api/src/graph/parser/parser.mts b/libs/langgraph-api/src/graph/parser/parser.mts index 36e9a02..79895fc 100644 --- a/libs/langgraph-api/src/graph/parser/parser.mts +++ b/libs/langgraph-api/src/graph/parser/parser.mts @@ -4,6 +4,14 @@ import * as path from "node:path"; import dedent from "dedent"; import { buildGenerator } from "./schema/types.mjs"; import { fileURLToPath } from "node:url"; +import type { JSONSchema7 } from "json-schema"; + +interface GraphSchema { + state: JSONSchema7 | undefined; + input: JSONSchema7 | undefined; + output: JSONSchema7 | undefined; + config: JSONSchema7 | undefined; +} const __dirname = fileURLToPath(new URL(".", import.meta.url)); @@ -249,7 +257,7 @@ export class SubgraphExtractor { ), ].join("\n\n"); - const inferFilePath = "__langraph__infer.mts"; + const inferFilePath = "__langgraph__infer.mts"; const inferContents = [ ...typeExports.map( ({ typeName }) => @@ -351,7 +359,7 @@ export class SubgraphExtractor { }, name: string, options?: { strict?: boolean }, - ) { + ): Record { const dirname = typeof target === "string" ? path.dirname(target) : __dirname; @@ -441,7 +449,7 @@ export class SubgraphExtractor { } const extract = ts.createProgram({ - rootNames: [path.resolve(dirname, "./__langraph__infer.mts")], + rootNames: [path.resolve(dirname, "./__langgraph__infer.mts")], options: compilerOptions, host, }); @@ -463,9 +471,9 @@ export class SubgraphExtractor { exports.map(({ typeName, graphName }) => [ graphName, { + state: trySymbol(schemaGenerator, `${typeName}__update`), input: trySymbol(schemaGenerator, `${typeName}__input`), output: trySymbol(schemaGenerator, `${typeName}__output`), - state: trySymbol(schemaGenerator, `${typeName}__update`), config: trySymbol(schemaGenerator, `${typeName}__config`), }, ]), diff --git a/libs/langgraph-api/tsconfig.json b/libs/langgraph-api/tsconfig.json index 446f990..1b5e5d3 100644 --- a/libs/langgraph-api/tsconfig.json +++ b/libs/langgraph-api/tsconfig.json @@ -5,6 +5,7 @@ "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, + "declaration": true, "module": "NodeNext", "allowJs": true, "jsx": "react-jsx",