Low-level orchestration framework for building stateful, multi-actor applications with LLMs
Schema extraction and validation utilities for LangGraph using Zod. Enables runtime type checking, JSON schema generation, and typed state management with Zod schemas.
Extract JSON schemas from compiled graphs for documentation, validation, and API specification generation.
Extract the state schema from a compiled graph.
function getStateTypeSchema(
graph: unknown,
registry?: SchemaMetaRegistry
): JSONSchema | undefined;Returns the complete state type schema including all state channels and their types.
Extract the update/input schema for state updates.
function getUpdateTypeSchema(
graph: unknown,
registry?: SchemaMetaRegistry
): JSONSchema | undefined;Returns the schema for partial state updates, showing which fields can be updated and their reducer types.
Extract the input schema for graph invocation.
function getInputTypeSchema(
graph: unknown,
registry?: SchemaMetaRegistry
): JSONSchema | undefined;Returns the schema for initial graph input, which may differ from the full state schema.
Extract the output schema from a graph.
function getOutputTypeSchema(
graph: unknown,
registry?: SchemaMetaRegistry
): JSONSchema | undefined;Returns the schema for graph output values.
Extract the configuration schema including context and configurable fields.
function getConfigTypeSchema(
graph: unknown,
registry?: SchemaMetaRegistry
): JSONSchema | undefined;import { StateGraph, Annotation } from "@langchain/langgraph";
import {
getStateTypeSchema,
getInputTypeSchema,
getOutputTypeSchema
} from "@langchain/langgraph/zod";
const State = Annotation.Root({
count: Annotation<number>,
message: Annotation<string>
});
const graph = new StateGraph(State)
.addNode("increment", (s) => ({ count: s.count + 1 }))
.compile();
// Extract schemas
const stateSchema = getStateTypeSchema(graph);
console.log(stateSchema);
// {
// type: "object",
// properties: {
// count: { type: "number" },
// message: { type: "string" }
// },
// required: ["count", "message"]
// }
const inputSchema = getInputTypeSchema(graph);
const outputSchema = getOutputTypeSchema(graph);import { StateGraph } from "@langchain/langgraph";
import { z } from "zod";
import { getStateTypeSchema } from "@langchain/langgraph/zod";
// Define state with Zod
const StateSchema = z.object({
count: z.number().default(0),
messages: z.array(z.string()).default([]),
metadata: z.record(z.string(), z.any()).optional()
});
const graph = new StateGraph(StateSchema)
.addNode("process", (state) => ({
count: state.count + 1,
messages: [...state.messages, "processed"]
}))
.compile();
// Extract as JSON Schema
const jsonSchema = getStateTypeSchema(graph);import { z } from "zod";
const InputSchema = z.object({
query: z.string(),
maxResults: z.number().optional()
});
const InternalStateSchema = z.object({
query: z.string(),
maxResults: z.number(),
results: z.array(z.any()),
processingSteps: z.array(z.string())
});
const OutputSchema = z.object({
results: z.array(z.any()),
totalFound: z.number()
});
const graph = new StateGraph(InternalStateSchema, {
input: InputSchema,
output: OutputSchema
})
.addNode("search", searchNode)
.addNode("process", processNode)
.compile();
// Extract different schemas
const inputSchema = getInputTypeSchema(graph); // Based on InputSchema
const stateSchema = getStateTypeSchema(graph); // Based on InternalStateSchema
const outputSchema = getOutputTypeSchema(graph); // Based on OutputSchemaimport { Annotation } from "@langchain/langgraph";
import { getUpdateTypeSchema } from "@langchain/langgraph/zod";
const State = Annotation.Root({
items: Annotation<string[]>({
reducer: (a, b) => a.concat(b),
default: () => []
}),
total: Annotation<number>({
reducer: (a, b) => a + b,
default: () => 0
})
});
const graph = new StateGraph(State)
.addNode("add", (s) => ({ items: ["new"], total: 1 }))
.compile();
const updateSchema = getUpdateTypeSchema(graph);
console.log(updateSchema);
// Shows that items accepts string[] (array to concat)
// and total accepts number (number to add)import { z } from "zod";
import { getInputTypeSchema } from "@langchain/langgraph/zod";
const StateSchema = z.object({
age: z.number().min(0).max(120),
email: z.string().email(),
name: z.string().min(1)
});
const graph = new StateGraph(StateSchema)
.addNode("validate", (s) => s)
.compile();
// Get schema for validation
const inputSchema = getInputTypeSchema(graph);
// Validate input before invoking
const input = {
age: 25,
email: "user@example.com",
name: "John"
};
// Zod will automatically validate during invoke
try {
const result = await graph.invoke(input);
} catch (error) {
console.error("Validation error:", error);
}import { getStateTypeSchema, getInputTypeSchema } from "@langchain/langgraph/zod";
function generateAPIDoc(graph: any) {
const inputSchema = getInputTypeSchema(graph);
const outputSchema = getOutputTypeSchema(graph);
return {
endpoint: "/api/graph/invoke",
method: "POST",
requestBody: {
schema: inputSchema,
example: generateExample(inputSchema)
},
response: {
schema: outputSchema,
example: generateExample(outputSchema)
}
};
}
const apiDoc = generateAPIDoc(myGraph);Schema metadata registry for advanced schema customization.
class SchemaMetaRegistry {
getExtendedChannelSchemas(
schema: InteropZodObject,
options?: {
withReducerSchema?: boolean;
withJsonSchemaExtrasAsDescription?: boolean;
asPartial?: boolean;
}
): InteropZodObject;
}
const schemaMetaRegistry: SchemaMetaRegistry;
const registry: LanggraphZodMetaRegistry; // Main registry instanceAttach LangGraph metadata to Zod schemas for use with reducers and defaults.
function withLangGraph<T extends z.ZodTypeAny>(
schema: T,
meta: SchemaMeta
): T;
interface SchemaMeta {
reducer?: (a: any, b: any) => any;
default?: () => any;
jsonSchemaExtra?: Record<string, unknown>;
}type InteropZodToStateDefinition<T extends z.ZodObject<any>>; // Convert Zod to state definition
type UpdateType<T extends z.ZodObject<any>>; // Extract update type from Zod schema
type ReducedZodChannel<T, R>; // Channel with reducer schemaimport { schemaMetaRegistry, withLangGraph } from "@langchain/langgraph/zod";
import { z } from "zod";
// Add metadata to Zod schema
const MessageSchema = withLangGraph(
z.array(z.object({ content: z.string() })),
{
reducer: (a, b) => a.concat(b),
default: () => []
}
);
// Use registry for custom schema processing
const customSchema = schemaMetaRegistry.getExtendedChannelSchemas(
baseSchema,
{
withReducerSchema: true,
asPartial: true
}
);import { z } from "zod";
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string(),
email: z.string().email()
});
const MessageSchema = z.object({
id: z.string(),
content: z.string(),
timestamp: z.date(),
sender: UserSchema
});
const ConversationSchema = z.object({
id: z.string(),
messages: z.array(MessageSchema),
participants: z.array(UserSchema),
metadata: z.record(z.string(), z.unknown())
});
const graph = new StateGraph(ConversationSchema)
.addNode("addMessage", addMessageNode)
.compile();const EventSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal("message"),
content: z.string(),
sender: z.string()
}),
z.object({
type: z.literal("action"),
action: z.enum(["join", "leave"]),
user: z.string()
}),
z.object({
type: z.literal("system"),
message: z.string(),
level: z.enum(["info", "warning", "error"])
})
]);
const StateSchema = z.object({
events: z.array(EventSchema)
});const StateSchema = z.object({
email: z.string().email().transform(e => e.toLowerCase()),
age: z.number().refine(age => age >= 18, {
message: "Must be 18 or older"
}),
password: z.string().min(8).refine(
pwd => /[A-Z]/.test(pwd) && /[a-z]/.test(pwd) && /[0-9]/.test(pwd),
{ message: "Password must contain uppercase, lowercase, and number" }
)
});const StateSchema = z.object({
required: z.string(),
optional: z.string().optional(),
withDefault: z.number().default(0),
nullable: z.string().nullable(),
optionalWithDefault: z.string().optional().default("default")
});Extract TypeScript types from Zod schemas.
import { z } from "zod";
const StateSchema = z.object({
count: z.number(),
items: z.array(z.string())
});
type State = z.infer<typeof StateSchema>;
// { count: number; items: string[] }
const graph = new StateGraph(StateSchema)
.addNode("process", (state: State) => {
// Fully typed state
return { count: state.count + 1 };
})
.compile();import { StateGraph } from "@langchain/langgraph";
import { z } from "zod";
import {
getInputTypeSchema,
getOutputTypeSchema,
getStateTypeSchema
} from "@langchain/langgraph/zod";
// Define schemas
const InputSchema = z.object({
userId: z.string().uuid(),
query: z.string().min(1),
options: z.object({
limit: z.number().min(1).max(100).default(10),
sortBy: z.enum(["date", "relevance"]).default("relevance")
}).optional()
});
const StateSchema = z.object({
userId: z.string().uuid(),
query: z.string(),
options: z.object({
limit: z.number(),
sortBy: z.enum(["date", "relevance"])
}),
results: z.array(z.object({
id: z.string(),
title: z.string(),
score: z.number()
})),
metadata: z.object({
totalFound: z.number(),
searchTime: z.number()
})
});
const OutputSchema = z.object({
results: z.array(z.object({
id: z.string(),
title: z.string(),
score: z.number()
})),
totalFound: z.number(),
searchTime: z.number()
});
// Create graph
const graph = new StateGraph(StateSchema, {
input: InputSchema,
output: OutputSchema
})
.addNode("search", async (state) => {
const results = await performSearch(state.query, state.options);
return {
results: results.items,
metadata: {
totalFound: results.total,
searchTime: results.duration
}
};
})
.compile();
// Extract schemas for API documentation
const apiSpecs = {
input: getInputTypeSchema(graph),
output: getOutputTypeSchema(graph),
state: getStateTypeSchema(graph)
};
// Type-safe invocation
type Input = z.infer<typeof InputSchema>;
type Output = z.infer<typeof OutputSchema>;
async function searchAPI(input: Input): Promise<Output> {
return await graph.invoke(input);
}