mirror of
https://github.com/langchain-ai/langgraphjs.git
synced 2026-07-22 09:05:28 -04:00
fix(cli): allow description field on config (#1500)
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@langchain/langgraph-api": patch
|
||||
"@langchain/langgraph-cli": patch
|
||||
---
|
||||
|
||||
support description property for `langgraph.json`
|
||||
@@ -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 };
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
Reference in New Issue
Block a user