CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-langchain--langgraph

Low-level orchestration framework for building stateful, multi-actor applications with LLMs

Overview
Eval results
Files

zod.mddocs/api/

Zod Integration

Schema extraction and validation utilities for LangGraph using Zod. Enables runtime type checking, JSON schema generation, and typed state management with Zod schemas.

Schema Extraction Functions

Extract JSON schemas from compiled graphs for documentation, validation, and API specification generation.

getStateTypeSchema

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.

getUpdateTypeSchema

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.

getInputTypeSchema

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.

getOutputTypeSchema

Extract the output schema from a graph.

function getOutputTypeSchema(
  graph: unknown,
  registry?: SchemaMetaRegistry
): JSONSchema | undefined;

Returns the schema for graph output values.

getConfigTypeSchema

Extract the configuration schema including context and configurable fields.

function getConfigTypeSchema(
  graph: unknown,
  registry?: SchemaMetaRegistry
): JSONSchema | undefined;

Usage Examples

Basic Schema Extraction

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);

Using Zod Schemas for State

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);

Separate Input/Output Schemas

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 OutputSchema

Schema with Reducers

import { 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)

Runtime Validation

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);
}

API Documentation Generation

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 Registry

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 instance

withLangGraph()

Attach 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>;
}

Zod Type Utilities

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 schema

Custom Schema Metadata

import { 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
  }
);

Zod Schema Patterns

Complex Nested States

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();

Discriminated Unions

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)
});

Refinements and Transformations

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" }
  )
});

Optional and Default Values

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")
});

Type Inference

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();

Complete Example: Type-Safe API

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);
}

Install with Tessl CLI

npx tessl i tessl/npm-langchain--langgraph

docs

api

channels.md

control-flow-api.md

execution-api.md

functional-api.md

graph-api.md

graph-construction-full.md

imports.md

persistence-api.md

persistence-full.md

prebuilt.md

remote.md

state-management.md

types.md

zod.md

index.md

tile.json