fix(cli): allow description field on config (#1500)

This commit is contained in:
Hunter Lovell
2025-08-05 17:28:27 -07:00
committed by GitHub
parent ed4ce9ef5b
commit 31cc9f7baa
7 changed files with 95 additions and 15 deletions
+6
View File
@@ -0,0 +1,6 @@
---
"@langchain/langgraph-api": patch
"@langchain/langgraph-cli": patch
---
support description property for `langgraph.json`
+1 -1
View File
@@ -9,7 +9,7 @@ export async function spawnServer(
},
context: {
config: {
graphs: Record<string, string>;
graphs: Record<string, string | { path: string; description?: string }>;
ui?: Record<string, string>;
ui_config?: { shared?: string[] };
auth?: { path?: string; disable_studio_auth?: boolean };
+11 -4
View File
@@ -10,10 +10,17 @@ const options = JSON.parse(lastArg || "{}");
// find the first file, as `parentURL` needs to be a valid file URL
// if no graph found, just assume a dummy default file, which should
// be working fine as well.
const firstGraphFile =
Object.values(options.graphs)
.flatMap((i) => i.split(":").at(0))
.at(0) || "index.mts";
const graphFiles = Object.values(options.graphs)
.map((i) => {
if (typeof i === "string") {
return i.split(":").at(0);
} else if (i && typeof i === "object" && i.path) {
return i.path.split(":").at(0);
}
return null;
})
.filter(Boolean);
const firstGraphFile = graphFiles.at(0) || "index.mts";
// enforce API @langchain/langgraph resolution
register("./graph/load.hooks.mjs", import.meta.url, {
+24 -2
View File
@@ -34,7 +34,12 @@ export const StartServerSchema = z.object({
nWorkers: z.number(),
host: z.string(),
cwd: z.string(),
graphs: z.record(z.string()),
graphs: z.record(
z.union([
z.string(),
z.object({ path: z.string(), description: z.string().optional() }),
])
),
auth: z
.object({
path: z.string().optional(),
@@ -98,7 +103,24 @@ export async function startServer(options: z.infer<typeof StartServerSchema>) {
registerSdkLogger();
logger.info(`Registering graphs from ${options.cwd}`);
await registerFromEnv(options.graphs, { cwd: options.cwd });
let hasGraphDescriptions = false;
const graphPaths = Object.fromEntries(
Object.entries(options.graphs).map(([graphId, rawSpec]) => {
if (typeof rawSpec === "string") {
return [graphId, rawSpec];
}
if (rawSpec.description) {
hasGraphDescriptions = true;
}
return [graphId, rawSpec.path];
})
);
if (hasGraphDescriptions) {
logger.warn(
"A graph definition in `langgraph.json` has a `description` property. Local MCP features are not yet supported with the JS CLI and will be ignored."
);
}
await registerFromEnv(graphPaths, { cwd: options.cwd });
registerRuntimeLogFormatter((info) => {
const config = getConfig();
+11 -2
View File
@@ -179,7 +179,10 @@ async function updateGraphPaths(
config: Config,
localDeps: LocalDeps
) {
for (const [graphId, importStr] of Object.entries(config.graphs)) {
for (const [graphId, graphDef] of Object.entries(config.graphs)) {
const importStr = typeof graphDef === "string" ? graphDef : graphDef.path;
const description =
typeof graphDef === "string" ? undefined : graphDef.description;
let [moduleStr, attrStr] = importStr.split(":", 2);
if (!moduleStr || !attrStr) {
throw new Error(
@@ -226,7 +229,13 @@ async function updateGraphPaths(
}
}
config["graphs"][graphId] = `${moduleStr}:${attrStr}`;
const resolvedPath = `${moduleStr}:${attrStr}`;
config["graphs"][graphId] = description
? {
path: resolvedPath,
description,
}
: resolvedPath;
}
}
}
+15 -6
View File
@@ -1,13 +1,21 @@
import { z } from "zod";
import { extname } from "node:path";
const GraphPathSchema = z.string().refine((i) => i.includes(":"), {
message: "Import string must be in format '<file>:<export>'",
});
const BaseConfigSchema = z.object({
docker_compose_file: z.string().optional(),
dockerfile_lines: z.array(z.string()).default([]),
graphs: z.record(
z.string().refine((i) => i.includes(":"), {
message: "Import string must be in format '<file>:<export>'",
})
z.union([
GraphPathSchema,
z.object({
path: GraphPathSchema,
description: z.string().optional(),
}),
])
),
ui: z.record(z.string()).optional(),
ui_config: z.object({ shared: z.array(z.string()).optional() }).optional(),
@@ -88,9 +96,10 @@ export const getConfig = (config: z.input<typeof ConfigSchema> | string) => {
let input = typeof config === "string" ? JSON.parse(config) : config;
const { graphs } = BaseConfigSchema.parse(input);
const isPython = Object.values(graphs).map((i) =>
PYTHON_EXTENSIONS.includes(extname(i.split(":")[0]))
);
const isPython = Object.values(graphs).map((graphDef) => {
const importStr = typeof graphDef === "string" ? graphDef : graphDef.path;
return PYTHON_EXTENSIONS.includes(extname(importStr.split(":")[0]));
});
const somePython = isPython.some((i) => i);
const someNode = !isPython.every((i) => i);
+27
View File
@@ -327,6 +327,33 @@ describe("config to docker", () => {
RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not found, skipping") || tsx /api/langgraph_api/js/build.mts
`);
});
it("description", async () => {
const graphs = {
agent: { path: "./graphs/agent.js:graph", description: "My agent" },
};
const config = getConfig({
...DEFAULT_CONFIG,
env: {},
node_version: "20" as const,
graphs,
});
const actual = await configToDocker(
PATH_TO_CONFIG,
config,
await assembleLocalDeps(PATH_TO_CONFIG, config)
);
expect(actual).toEqual(dedenter`
FROM langchain/langgraphjs-api:20
ADD . /deps/unit_tests
ENV LANGSERVE_GRAPHS='{"agent":{"path":"./graphs/agent.js:graph","description":"My agent"}}'
WORKDIR /deps/unit_tests
RUN npm i
RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not found, skipping") || tsx /api/langgraph_api/js/build.mts
`);
});
});
describe("config to compose", () => {