CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-langchain

TypeScript framework for building LLM-powered applications with agents, tools, middleware, and model interoperability

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

type-inference.mddocs/advanced/

Type Inference System

Complete type inference utilities for extracting types from agents, middleware, and schemas.

Agent Type Inference

/**
 * Complete agent type configuration
 */
type AgentTypeConfig<TResponse, TState, TContext, TMiddleware, TTools> = {
  response: TResponse;
  state: TState;
  context: TContext;
  middleware: TMiddleware;
  tools: TTools;
};

/**
 * Extract any agent type property
 */
type InferAgentType<T, K extends keyof AgentTypeConfig<any, any, any, any, any>> =
  T extends ReactAgent<infer Config>
    ? Config extends AgentTypeConfig<any, any, any, any, any>
      ? Config[K]
      : never
    : never;

/**
 * Extract response type from agent
 */
type InferAgentResponse<T> = InferAgentType<T, "response">;

/**
 * Extract full merged state from agent (includes built-in state)
 */
type InferAgentState<T> = InferAgentType<T, "state">;

/**
 * Extract raw state schema from agent
 */
type InferAgentStateSchema<T> = T extends ReactAgent<infer Config>
  ? Config extends { stateSchema: infer Schema }
    ? Schema
    : undefined
  : never;

/**
 * Extract full merged context from agent
 */
type InferAgentContext<T> = InferAgentType<T, "context">;

/**
 * Extract raw context schema from agent
 */
type InferAgentContextSchema<T> = T extends ReactAgent<infer Config>
  ? Config extends { contextSchema: infer Schema }
    ? Schema
    : undefined
  : never;

/**
 * Extract middleware array from agent
 */
type InferAgentMiddleware<T> = InferAgentType<T, "middleware">;

/**
 * Extract tools array from agent
 */
type InferAgentTools<T> = InferAgentType<T, "tools">;

Middleware Type Inference

/**
 * Complete middleware type configuration
 */
type MiddlewareTypeConfig<TSchema, TContextSchema, TFullContext, TTools> = {
  schema: TSchema;
  contextSchema: TContextSchema;
  fullContext: TFullContext;
  tools: TTools;
};

/**
 * Extract middleware schema
 */
type InferMiddlewareSchema<T> = T extends AgentMiddleware<
  infer Schema,
  any,
  any,
  any
>
  ? Schema
  : never;

/**
 * Extract middleware state
 */
type InferMiddlewareState<T> = T extends AgentMiddleware<
  infer Schema,
  any,
  any,
  any
>
  ? InferSchemaInput<Schema>
  : never;

/**
 * Extract middleware input state
 */
type InferMiddlewareInputState<T> = T extends AgentMiddleware<
  infer Schema,
  any,
  any,
  any
>
  ? Schema extends z.ZodType<any, any, infer Input>
    ? Input
    : Schema
  : never;

/**
 * Merge states from middleware array
 */
type InferMiddlewareStates<T extends readonly AgentMiddleware[]> =
  T extends readonly [infer First, ...infer Rest]
    ? First extends AgentMiddleware
      ? InferMiddlewareState<First> &
          (Rest extends readonly AgentMiddleware[]
            ? InferMiddlewareStates<Rest>
            : {})
      : {}
    : {};

/**
 * Extract middleware context
 */
type InferMiddlewareContext<T> = T extends AgentMiddleware<
  any,
  infer ContextSchema,
  any,
  any
>
  ? InferSchemaInput<ContextSchema>
  : never;

/**
 * Merge contexts from middleware array
 */
type InferMiddlewareContexts<T extends readonly AgentMiddleware[]> =
  T extends readonly [infer First, ...infer Rest]
    ? First extends AgentMiddleware
      ? InferMiddlewareContext<First> &
          (Rest extends readonly AgentMiddleware[]
            ? InferMiddlewareContexts<Rest>
            : {})
      : {}
    : {};

/**
 * Full merged state with built-in state
 */
type InferMergedState<T> = BuiltInState & InferMiddlewareStates<T>;

Schema Type Inference

/**
 * Infer schema input type
 */
type InferSchemaInput<A> = A extends z.ZodType<any, any, infer Input>
  ? Input
  : A extends { [key: string]: any }
  ? A
  : {};

/**
 * Infer context input type
 */
type InferContextInput<ContextSchema> = ContextSchema extends z.ZodType<
  any,
  any,
  infer Input
>
  ? Input
  : ContextSchema extends { [key: string]: any }
  ? ContextSchema
  : {};

/**
 * Resolve agent type configuration
 */
type ResolveAgentTypeConfig<T> = T extends CreateAgentParams<
  infer Response,
  infer State,
  infer Context,
  infer ResponseFormat
>
  ? AgentTypeConfig<Response, State, Context, any, any>
  : DefaultAgentTypeConfig;

/**
 * Default agent type configuration
 */
type DefaultAgentTypeConfig = AgentTypeConfig<
  undefined,
  Record<string, never>,
  Record<string, never>,
  readonly AgentMiddleware[],
  readonly Tool[]
>;

Tool Type Inference

/**
 * Extract union type from Zod schema array
 */
type ExtractZodArrayTypes<T extends readonly any[]> = T extends readonly [
  infer First,
  ...infer Rest
]
  ? z.infer<First> | ExtractZodArrayTypes<Rest>
  : never;

/**
 * Convert tools array to message tool set
 */
type ToolsToMessageToolSet<T extends readonly Tool[]> = {
  [K in T[number]["name"]]: Extract<T[number], { name: K }>;
};

/**
 * Merge agent and middleware tools
 */
type CombineTools<
  TAgentTools extends readonly Tool[],
  TMiddleware extends readonly AgentMiddleware[]
> = [...TAgentTools, ...InferMiddlewareTools<TMiddleware>];

/**
 * Infer tools from middleware
 */
type InferMiddlewareTools<T extends readonly AgentMiddleware[]> =
  T extends readonly [infer First, ...infer Rest]
    ? First extends AgentMiddleware<any, any, any, infer Tools>
      ? Tools extends readonly Tool[]
        ? [...Tools, ...(Rest extends readonly AgentMiddleware[] ? InferMiddlewareTools<Rest> : [])]
        : Rest extends readonly AgentMiddleware[]
        ? InferMiddlewareTools<Rest>
        : []
      : []
    : [];

/**
 * Infer tools array from middleware array
 */
type InferMiddlewareToolsArray<T> = T extends readonly AgentMiddleware[]
  ? InferMiddlewareTools<T>
  : [];

Middleware Type Inference

Complete type system for extracting types from middleware.

/**
 * Complete middleware type configuration
 */
type MiddlewareTypeConfig<TSchema, TContextSchema, TFullContext, TTools> = {
  schema: TSchema;
  contextSchema: TContextSchema;
  fullContext: TFullContext;
  tools: TTools;
};

/**
 * Extract middleware schema
 * @template T - Middleware type
 */
type InferMiddlewareSchema<T> = T extends AgentMiddleware<
  infer Schema,
  any,
  any,
  any
>
  ? Schema
  : never;

/**
 * Extract middleware state
 * @template T - Middleware type
 */
type InferMiddlewareState<T> = T extends AgentMiddleware<
  infer Schema,
  any,
  any,
  any
>
  ? InferSchemaInput<Schema>
  : never;

/**
 * Extract middleware input state
 * @template T - Middleware type
 */
type InferMiddlewareInputState<T> = T extends AgentMiddleware<
  infer Schema,
  any,
  any,
  any
>
  ? Schema extends z.ZodType<any, any, infer Input>
    ? Input
    : Schema
  : never;

/**
 * Merge states from middleware array
 * Recursively combines state from all middleware
 * @template T - Middleware array
 */
type InferMiddlewareStates<T extends readonly AgentMiddleware[]> =
  T extends readonly [infer First, ...infer Rest]
    ? First extends AgentMiddleware
      ? InferMiddlewareState<First> &
          (Rest extends readonly AgentMiddleware[]
            ? InferMiddlewareStates<Rest>
            : {})
      : {}
    : {};

/**
 * Extract middleware context
 * @template T - Middleware type
 */
type InferMiddlewareContext<T> = T extends AgentMiddleware<
  any,
  infer ContextSchema,
  any,
  any
>
  ? InferSchemaInput<ContextSchema>
  : never;

/**
 * Merge contexts from middleware array
 * Recursively combines context from all middleware
 * @template T - Middleware array
 */
type InferMiddlewareContexts<T extends readonly AgentMiddleware[]> =
  T extends readonly [infer First, ...infer Rest]
    ? First extends AgentMiddleware
      ? InferMiddlewareContext<First> &
          (Rest extends readonly AgentMiddleware[]
            ? InferMiddlewareContexts<Rest>
            : {})
      : {}
    : {};

/**
 * Full merged state with built-in state
 * Combines built-in agent state with middleware states
 * @template T - Middleware array
 */
type InferMergedState<T> = BuiltInState & InferMiddlewareStates<T>;

Middleware Type Inference Example:

import {
  createMiddleware,
  type InferMiddlewareState,
  type InferMiddlewareStates,
} from "langchain";
import { z } from "zod";

// Create middleware with state
const analyticsMiddleware = createMiddleware({
  name: "analytics",
  stateSchema: z.object({
    requestCount: z.number().default(0),
    errorCount: z.number().default(0),
  }),
});

// Infer single middleware state
type AnalyticsState = InferMiddlewareState<typeof analyticsMiddleware>;
// { requestCount: number; errorCount: number }

// Create another middleware
const sessionMiddleware = createMiddleware({
  name: "session",
  stateSchema: z.object({
    sessionId: z.string(),
    startTime: z.number(),
  }),
});

// Combine middleware
const middleware = [analyticsMiddleware, sessionMiddleware] as const;

// Infer combined state
type CombinedState = InferMiddlewareStates<typeof middleware>;
// { requestCount: number; errorCount: number; sessionId: string; startTime: number }

// Use with agent
const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [],
  middleware: middleware,
});

// Agent state includes all middleware state
const result = await agent.invoke({
  messages: [{ role: "user", content: "Hello" }],
  requestCount: 0,
  errorCount: 0,
  sessionId: "session-123",
  startTime: Date.now(),
});

// Type-safe access to all middleware state
console.log(result.requestCount);
console.log(result.sessionId);

Helper Types

/**
 * Check if all properties are optional
 * Used to determine if context parameter should be required or optional in agent invocations
 * @template T - Object type to check
 * @example
 * type AllOptional = IsAllOptional<{ a?: string; b?: number }>; // true
 * type HasRequired = IsAllOptional<{ a: string; b?: number }>; // false
 */
type IsAllOptional<T> = {} extends Required<T> ? true : false;

/**
 * Context optionality helper
 * Determines if context should be required or optional based on schema properties
 * @template TContext - Context type to wrap
 * @example
 * // All optional - context becomes optional
 * type OptionalCtx = WithMaybeContext<{ a?: string }>;
 * // { context?: { a?: string } }
 *
 * // Has required - context becomes required
 * type RequiredCtx = WithMaybeContext<{ a: string }>;
 * // { context: { a: string } }
 */
type WithMaybeContext<TContext> = IsAllOptional<TContext> extends true
  ? { context?: TContext }
  : { context: TContext };

/**
 * Any annotation root type
 * Represents LangGraph annotation roots which can have arbitrary properties
 */
type AnyAnnotationRoot = {
  [key: string]: any;
};

/**
 * Normalized schema input
 * Extracts input type from Zod schema or returns the schema type itself
 * Handles both Zod schemas and plain object schemas
 * @template TSchema - Schema to normalize
 */
type NormalizedSchemaInput<TSchema> = TSchema extends z.ZodType<
  any,
  any,
  infer Input
>
  ? Input
  : TSchema;

/**
 * Extract structured response type from response format
 * Handles single schemas, schema arrays (unions), and strategy types
 * @template TResponseFormat - Response format configuration
 */
type InferStructuredResponse<TResponseFormat> =
  TResponseFormat extends z.ZodType<infer Output, any, any>
    ? Output
    : TResponseFormat extends readonly z.ZodType<any, any, any>[]
    ? ExtractZodArrayTypes<TResponseFormat>
    : TResponseFormat extends ToolStrategy<infer T>
    ? T
    : TResponseFormat extends ProviderStrategy<infer T>
    ? T
    : TResponseFormat extends JsonSchemaFormat
    ? any
    : TResponseFormat extends JsonSchemaFormat[]
    ? any
    : undefined;

/**
 * Agent name mode type
 * Controls how agent name is included in messages
 * - true: Include name in all messages
 * - false: Never include name
 * - "tool_messages": Include name only in tool messages
 */
type AgentNameMode = boolean | "tool_messages";

/**
 * Extract output type from Zod schema
 * @template T - Zod schema
 */
type InferZodOutput<T> = T extends z.ZodType<infer Output, any, any>
  ? Output
  : never;

/**
 * Extract input type from Zod schema
 * @template T - Zod schema
 */
type InferZodInput<T> = T extends z.ZodType<any, any, infer Input>
  ? Input
  : never;

/**
 * Check if type is never
 * @template T - Type to check
 */
type IsNever<T> = [T] extends [never] ? true : false;

/**
 * Make properties optional if they have defaults
 * @template T - Object type
 */
type WithDefaults<T> = {
  [K in keyof T as T[K] extends { _def: { defaultValue: () => any } }
    ? K
    : never]?: T[K];
} & {
  [K in keyof T as T[K] extends { _def: { defaultValue: () => any } }
    ? never
    : K]: T[K];
};

/**
 * Prettify type for better IDE display
 * Expands object types to show all properties
 * @template T - Type to prettify
 */
type Prettify<T> = {
  [K in keyof T]: T[K];
} & {};

/**
 * Make all properties deeply optional
 * @template T - Type to make optional
 */
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

/**
 * Make all properties deeply required
 * @template T - Type to make required
 */
type DeepRequired<T> = {
  [K in keyof T]-?: T[K] extends object ? DeepRequired<T[K]> : T[K];
};

/**
 * Union to intersection
 * Converts union type to intersection type
 * @template U - Union type
 */
type UnionToIntersection<U> = (
  U extends any ? (k: U) => void : never
) extends (k: infer I) => void
  ? I
  : never;

/**
 * Merge two types with proper handling of optionality
 * @template T - First type
 * @template U - Second type
 */
type Merge<T, U> = Omit<T, keyof U> & U;

/**
 * Extract required keys from object type
 * @template T - Object type
 */
type RequiredKeys<T> = {
  [K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];

/**
 * Extract optional keys from object type
 * @template T - Object type
 */
type OptionalKeys<T> = {
  [K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];

Usage Examples

Inferring Agent Types

import { createAgent, type InferAgentState, type InferAgentContext } from "langchain";
import { z } from "zod";

const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [],
  stateSchema: z.object({
    count: z.number(),
  }),
  contextSchema: z.object({
    userId: z.string(),
  }),
});

// Infer state type
type AgentState = InferAgentState<typeof agent>;
// { messages: BaseMessage[]; count: number; structuredResponse?: any }

// Infer context type
type AgentContext = InferAgentContext<typeof agent>;
// { userId: string }

Inferring Middleware Types

import { createMiddleware, type InferMiddlewareState } from "langchain";
import { z } from "zod";

const middleware = createMiddleware({
  name: "session",
  stateSchema: z.object({
    sessionId: z.string(),
    startTime: z.number(),
  }),
});

// Infer middleware state
type MiddlewareState = InferMiddlewareState<typeof middleware>;
// { sessionId: string; startTime: number }

Type-Safe Agent Invocation

import { createAgent } from "langchain";
import { z } from "zod";

const StateSchema = z.object({
  userPreferences: z.object({
    theme: z.string(),
    language: z.string(),
  }),
});

const ContextSchema = z.object({
  userId: z.string(),
  requestId: z.string(),
});

const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [],
  stateSchema: StateSchema,
  contextSchema: ContextSchema,
});

// Type-safe invocation
const result = await agent.invoke(
  {
    messages: [{ role: "user", content: "Hello" }],
    userPreferences: {
      theme: "dark",
      language: "en",
    },
  },
  {
    context: {
      userId: "user123",
      requestId: "req456",
    },
  }
);

// Type-safe access
console.log(result.userPreferences.theme); // "dark"

Inferring Response Types

import { createAgent, type InferAgentResponse } from "langchain";
import { z } from "zod";

const ResponseSchema = z.object({
  answer: z.string(),
  confidence: z.number().min(0).max(1),
  sources: z.array(z.string()),
});

const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [],
  responseFormat: ResponseSchema,
});

// Infer response type
type Response = InferAgentResponse<typeof agent>;
// { answer: string; confidence: number; sources: string[] }

const result = await agent.invoke({
  messages: [{ role: "user", content: "What is TypeScript?" }],
});

// Type-safe access to structured response
const answer: string = result.structuredResponse.answer;
const confidence: number = result.structuredResponse.confidence;
const sources: string[] = result.structuredResponse.sources;

Union Response Types

import { createAgent, type InferAgentResponse } from "langchain";
import { z } from "zod";

const EmailResponse = z.object({
  type: z.literal("email"),
  to: z.string().email(),
  subject: z.string(),
  body: z.string(),
});

const TaskResponse = z.object({
  type: z.literal("task"),
  title: z.string(),
  dueDate: z.string(),
  priority: z.enum(["low", "medium", "high"]),
});

const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [],
  responseFormat: [EmailResponse, TaskResponse],
});

// Infer union type
type Response = InferAgentResponse<typeof agent>;
// { type: "email", to: string, subject: string, body: string } | { type: "task", title: string, dueDate: string, priority: "low" | "medium" | "high" }

const result = await agent.invoke({
  messages: [{ role: "user", content: "Send email or create task" }],
});

// Type narrowing with discriminated union
if (result.structuredResponse.type === "email") {
  console.log(result.structuredResponse.to); // TypeScript knows this exists
  console.log(result.structuredResponse.subject);
} else {
  console.log(result.structuredResponse.title); // TypeScript knows this exists
  console.log(result.structuredResponse.priority);
}

Type-Safe Middleware Composition

import { createMiddleware, createAgent, type InferMiddlewareStates } from "langchain";
import { z } from "zod";

const analyticsMiddleware = createMiddleware({
  name: "analytics",
  stateSchema: z.object({
    requestCount: z.number().default(0),
  }),
});

const sessionMiddleware = createMiddleware({
  name: "session",
  stateSchema: z.object({
    sessionId: z.string(),
    startTime: z.number(),
  }),
});

const middleware = [analyticsMiddleware, sessionMiddleware] as const;

// Infer combined middleware state
type CombinedState = InferMiddlewareStates<typeof middleware>;
// { requestCount: number; sessionId: string; startTime: number }

const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [],
  middleware: middleware,
});

// Agent state includes all middleware state
const result = await agent.invoke({
  messages: [{ role: "user", content: "Hello" }],
  requestCount: 0,
  sessionId: "session-123",
  startTime: Date.now(),
});

// Type-safe access to middleware state
console.log(result.requestCount);
console.log(result.sessionId);
console.log(result.startTime);

Extracting Tool Types

import { tool, type ToolsToMessageToolSet } from "langchain";
import { z } from "zod";

const searchTool = tool(
  async ({ query }) => `Results for: ${query}`,
  {
    name: "search",
    description: "Search for information",
    schema: z.object({
      query: z.string(),
    }),
  }
);

const calculatorTool = tool(
  async ({ expression }) => String(eval(expression)),
  {
    name: "calculator",
    description: "Evaluate expressions",
    schema: z.object({
      expression: z.string(),
    }),
  }
);

const tools = [searchTool, calculatorTool] as const;

// Create mapped type of tools by name
type ToolSet = ToolsToMessageToolSet<typeof tools>;
// { search: typeof searchTool; calculator: typeof calculatorTool }

// Access tools by name with type safety
const search: typeof searchTool = {} as ToolSet["search"];
const calculator: typeof calculatorTool = {} as ToolSet["calculator"];

Best Practices

Type Safety

  • Always define explicit schemas: Use Zod schemas for state, context, and responses to get both runtime validation and type inference
  • Leverage type inference: Use InferAgent* utilities instead of manually defining types
  • Use discriminated unions: For complex response types, use discriminated unions with literal types for type narrowing
  • Const assertions: Use as const for middleware and tools arrays to preserve literal types

Performance

  • Type inference is compile-time: All type inference happens during TypeScript compilation with no runtime cost
  • Complex types may slow compilation: Very deep type hierarchies can slow down TypeScript's compiler
  • Use type aliases: Define type aliases for frequently-used complex types to reduce redundant computation
  • Profile if needed: Use tsc --extendedDiagnostics to identify slow type operations

Maintainability

  • Document custom types: Add JSDoc comments to explain complex custom types
  • Use descriptive schema names: Name your schemas clearly (e.g., UserProfileSchema, not Schema1)
  • Keep type hierarchies shallow: Avoid deeply nested generic types when possible
  • Prefer inference over manual types: Let TypeScript infer types from your runtime code rather than duplicating type definitions

Testing

  • Verify inferred types: Use TypeScript's type checking in your tests to ensure types match expectations
  • Test type narrowing: For union types, test that type narrowing works correctly
  • Ensure middleware merging: Verify that middleware state and context types merge properly
  • Check optional properties: Ensure optional properties are correctly typed with ? modifiers

Advanced Patterns

Conditional Types Based on State

import { createAgent } from "langchain";
import { z } from "zod";

// Define state with optional admin properties
const StateSchema = z.object({
  userId: z.string(),
  isAdmin: z.boolean(),
  adminPermissions: z.array(z.string()).optional(),
});

const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [],
  stateSchema: StateSchema,
});

// Type-safe conditional logic
const result = await agent.invoke({
  messages: [{ role: "user", content: "Hello" }],
  userId: "user123",
  isAdmin: true,
  adminPermissions: ["read", "write"],
});

if (result.isAdmin && result.adminPermissions) {
  // TypeScript knows adminPermissions is defined here
  console.log(result.adminPermissions.join(", "));
}

Generic Agent Factory

import { createAgent, type InferAgentState } from "langchain";
import { z, type ZodType } from "zod";

function createTypedAgent<TStateSchema extends ZodType>(
  stateSchema: TStateSchema,
  model: string = "openai:gpt-4o"
) {
  return createAgent({
    model,
    tools: [],
    stateSchema,
  });
}

// Create agents with different state schemas
const userAgent = createTypedAgent(
  z.object({
    userId: z.string(),
    userName: z.string(),
  })
);

const sessionAgent = createTypedAgent(
  z.object({
    sessionId: z.string(),
    expiresAt: z.number(),
  })
);

// Inferred types are different for each agent
type UserAgentState = InferAgentState<typeof userAgent>;
// { messages: BaseMessage[]; userId: string; userName: string; structuredResponse?: any }

type SessionAgentState = InferAgentState<typeof sessionAgent>;
// { messages: BaseMessage[]; sessionId: string; expiresAt: number; structuredResponse?: any }

Type-Safe Tool Collections

import { tool, createAgent, type InferMiddlewareTools } from "langchain";
import { z } from "zod";

// Create tool collections
const mathTools = [
  tool(async ({ a, b }) => String(a + b), {
    name: "add",
    description: "Add numbers",
    schema: z.object({ a: z.number(), b: z.number() }),
  }),
  tool(async ({ a, b }) => String(a * b), {
    name: "multiply",
    description: "Multiply numbers",
    schema: z.object({ a: z.number(), b: z.number() }),
  }),
] as const;

const searchTools = [
  tool(async ({ query }) => `Results for: ${query}`, {
    name: "search",
    description: "Search",
    schema: z.object({ query: z.string() }),
  }),
] as const;

// Combine tool collections with type safety
const allTools = [...mathTools, ...searchTools] as const;

const agent = createAgent({
  model: "openai:gpt-4o",
  tools: allTools,
});

// TypeScript knows all available tool names
type ToolNames = typeof allTools[number]["name"];
// "add" | "multiply" | "search"

Troubleshooting

Type Inference Not Working

If type inference isn't working as expected:

  1. Use const assertions: Ensure you're using as const for arrays and objects

    const middleware = [mw1, mw2] as const; // Good
    const middleware = [mw1, mw2]; // Bad - loses literal types
  2. Define schemas before agent: Schemas must be defined before creating the agent

    const StateSchema = z.object({ count: z.number() });
    const agent = createAgent({ stateSchema: StateSchema }); // Good
  3. Check TypeScript version: Use TypeScript >=4.7 for best results

    npm install -D typescript@latest
  4. Use explicit type annotations: As a fallback, manually annotate types

    const result: State<MyStateType> = await agent.invoke(...);

Circular Type References

If you encounter circular type errors:

  1. Use type aliases: Break circular dependencies with intermediate types

    type AgentState = InferAgentState<typeof agent>;
    type MiddlewareState = InferMiddlewareStates<typeof middleware>;
  2. Simplify schema structures: Avoid self-referential schemas

    // Avoid
    const Schema = z.object({
      children: z.array(z.lazy(() => Schema)),
    });
    
    // Prefer
    const Schema = z.object({
      children: z.array(z.any()),
    });
  3. Use any as escape hatch: When necessary, use any to break cycles

    type MyType = InferAgentState<any>; // Last resort

Performance Issues

If TypeScript compilation is slow:

  1. Simplify type hierarchies: Reduce nesting depth of generic types
  2. Use type aliases: Cache complex type computations
    type AgentState = InferAgentState<typeof agent>; // Compute once
  3. Profile compilation: Use diagnostics to find bottlenecks
    tsc --extendedDiagnostics
  4. Disable strict checks temporarily: For development, relax some checks
    {
      "compilerOptions": {
        "skipLibCheck": true
      }
    }

Union Type Not Narrowing

If type narrowing doesn't work with union types:

  1. Use discriminated unions: Add a literal type field for discrimination

    const Schema1 = z.object({ type: z.literal("A"), value: z.string() });
    const Schema2 = z.object({ type: z.literal("B"), count: z.number() });
  2. Use type guards: Implement custom type guard functions

    function isTypeA(obj: Response): obj is { type: "A", value: string } {
      return obj.type === "A";
    }
  3. Check discriminant property first: Always check the discriminant before accessing other properties

    if (response.type === "A") {
      console.log(response.value); // Now TypeScript knows the type
    }

Complete Type Reference

All Agent Type Utilities

// Agent type extraction
type InferAgentState<T> = /* Extract full merged state */;
type InferAgentResponse<T> = /* Extract structured response type */;
type InferAgentContext<T> = /* Extract full merged context */;
type InferAgentStateSchema<T> = /* Extract raw state schema */;
type InferAgentContextSchema<T> = /* Extract raw context schema */;
type InferAgentMiddleware<T> = /* Extract middleware array */;
type InferAgentTools<T> = /* Extract tools array */;

// Middleware type extraction
type InferMiddlewareSchema<T> = /* Extract middleware schema */;
type InferMiddlewareState<T> = /* Extract middleware state */;
type InferMiddlewareInputState<T> = /* Extract middleware input state */;
type InferMiddlewareStates<T> = /* Merge states from middleware array */;
type InferMiddlewareContext<T> = /* Extract middleware context */;
type InferMiddlewareContexts<T> = /* Merge contexts from middleware array */;
type InferMiddlewareTools<T> = /* Extract tools from middleware */;
type InferMiddlewareToolsArray<T> = /* Extract tools array from middleware array */;

// Schema type extraction
type InferSchemaInput<A> = /* Infer schema input type */;
type InferContextInput<ContextSchema> = /* Infer context input type */;
type InferZodOutput<T> = /* Extract output type from Zod schema */;
type InferZodInput<T> = /* Extract input type from Zod schema */;
type NormalizedSchemaInput<TSchema> = /* Normalize schema to input type */;

// Tool type utilities
type ExtractZodArrayTypes<T> = /* Extract union from Zod schema array */;
type ToolsToMessageToolSet<T> = /* Convert tools array to mapped type */;
type CombineTools<TAgentTools, TMiddleware> = /* Merge agent and middleware tools */;

// Helper types
type IsAllOptional<T> = /* Check if all properties optional */;
type WithMaybeContext<TContext> = /* Make context optional if all props optional */;
type IsNever<T> = /* Check if type is never */;
type Prettify<T> = /* Expand type for better IDE display */;
type DeepPartial<T> = /* Make all properties deeply optional */;
type DeepRequired<T> = /* Make all properties deeply required */;
type UnionToIntersection<U> = /* Convert union to intersection */;
type Merge<T, U> = /* Merge two types */;
type RequiredKeys<T> = /* Extract required keys */;
type OptionalKeys<T> = /* Extract optional keys */;
type WithDefaults<T> = /* Make properties with defaults optional */;

// Configuration types
type ResolveAgentTypeConfig<T> = /* Resolve agent type configuration */;
type DefaultAgentTypeConfig = /* Default agent type configuration */;
type AgentTypeConfig<TResponse, TState, TContext, TMiddleware, TTools> = /* Complete agent type config */;
type MiddlewareTypeConfig<TSchema, TContextSchema, TFullContext, TTools> = /* Complete middleware type config */;

// Response format types
type InferStructuredResponse<TResponseFormat> = /* Extract structured response type */;
type ResponseFormat = /* Union of all response format types */;

// Built-in types
type AgentNameMode = /* Agent name inclusion mode */;
type AnyAnnotationRoot = /* Any annotation root type */;

Type System Architecture

The LangChain type system is built on several key principles:

  1. Type Inference from Runtime Values: Types are inferred from runtime agent/middleware/tool definitions, ensuring types stay in sync with implementation
  2. Compositional Type Merging: State and context from multiple middleware merge automatically
  3. Discriminated Union Support: Response formats can be union types with proper type narrowing
  4. Zero Runtime Cost: All type operations happen at compile time
  5. Progressive Enhancement: Start with basic types, add more sophisticated types as needed

Internal Type Implementation Details

For advanced users who need to understand how the type system works internally:

/**
 * Internal: How agent type configuration is constructed
 * This is built automatically from CreateAgentParams
 */
type AgentTypeConfig<TResponse, TState, TContext, TMiddleware, TTools> = {
  response: TResponse; // From responseFormat
  state: TState; // Merged from stateSchema + middleware state
  context: TContext; // Merged from contextSchema + middleware context
  middleware: TMiddleware; // Array of middleware instances
  tools: TTools; // Merged from tools + middleware tools
};

/**
 * Internal: How state is merged
 * 1. Built-in state (messages, structuredResponse)
 * 2. + Agent state schema
 * 3. + Middleware state schemas (merged)
 */
type MergedState<
  TStateSchema,
  TMiddleware extends readonly AgentMiddleware[]
> = BuiltInState &
    InferSchemaInput<TStateSchema> &
    InferMiddlewareStates<TMiddleware>;

/**
 * Internal: How context is merged
 * 1. Agent context schema
 * 2. + Middleware context schemas (merged)
 */
type MergedContext<
  TContextSchema,
  TMiddleware extends readonly AgentMiddleware[]
> = InferContextInput<TContextSchema> &
    InferMiddlewareContexts<TMiddleware>;

/**
 * Internal: How tools are merged
 * 1. Agent tools
 * 2. + Middleware tools (concatenated)
 */
type MergedTools<
  TAgentTools extends readonly Tool[],
  TMiddleware extends readonly AgentMiddleware[]
> = [...TAgentTools, ...InferMiddlewareTools<TMiddleware>];

/**
 * Internal: Response format resolution
 * Handles all response format variants:
 * - Single Zod schema → infer output type
 * - Array of Zod schemas → union of output types
 * - Tool strategy → extract type parameter
 * - Provider strategy → extract type parameter
 * - JSON schema → any (no static type info)
 */
type ResolveResponseFormat<TResponseFormat> =
  TResponseFormat extends z.ZodType<infer Output, any, any>
    ? Output
    : TResponseFormat extends readonly z.ZodType<any, any, any>[]
    ? { [K in keyof TResponseFormat]: z.infer<TResponseFormat[K]> }[number]
    : TResponseFormat extends ToolStrategy<infer T>
    ? T
    : TResponseFormat extends ProviderStrategy<infer T>
    ? T
    : undefined;

Advanced Type Patterns

Generic Middleware Factory

import { createMiddleware, type AgentMiddleware } from "langchain";
import { z, type ZodType } from "zod";

/**
 * Create a factory for middleware with a specific state pattern
 */
function createStatefulMiddleware<TSchema extends ZodType>(
  name: string,
  stateSchema: TSchema,
  beforeModel?: (state: z.infer<TSchema>) => Promise<z.infer<TSchema>>
): AgentMiddleware<TSchema, never, never, []> {
  return createMiddleware({
    name,
    stateSchema,
    beforeModel: beforeModel as any,
  });
}

// Usage
const counterMiddleware = createStatefulMiddleware(
  "counter",
  z.object({ count: z.number().default(0) }),
  async (state) => {
    return { ...state, count: state.count + 1 };
  }
);

Type-Safe Tool Builder

import { tool, type Tool } from "langchain";
import { z, type ZodType } from "zod";

/**
 * Builder for creating type-safe tool collections
 */
class ToolBuilder {
  private tools: Tool[] = [];

  add<TSchema extends ZodType>(
    name: string,
    description: string,
    schema: TSchema,
    func: (input: z.infer<TSchema>) => Promise<string>
  ) {
    this.tools.push(tool(func as any, { name, description, schema }));
    return this;
  }

  build() {
    return this.tools as const;
  }
}

// Usage
const tools = new ToolBuilder()
  .add("search", "Search", z.object({ query: z.string() }), async ({ query }) => `Results for ${query}`)
  .add("calculate", "Calculate", z.object({ expr: z.string() }), async ({ expr }) => String(eval(expr)))
  .build();

// Tools are readonly and typed
type SearchTool = typeof tools[0];
type CalculateTool = typeof tools[1];

Conditional State Types

import { createAgent, type InferAgentState } from "langchain";
import { z } from "zod";

/**
 * Create agents with conditional state based on configuration
 */
function createConditionalAgent<TMode extends "basic" | "advanced">(
  mode: TMode
) {
  const basicSchema = z.object({
    userId: z.string(),
  });

  const advancedSchema = z.object({
    userId: z.string(),
    adminLevel: z.number(),
    permissions: z.array(z.string()),
  });

  return createAgent({
    model: "openai:gpt-4o",
    tools: [],
    stateSchema: mode === "advanced" ? advancedSchema : basicSchema,
  });
}

// Types are correctly inferred based on mode
const basicAgent = createConditionalAgent("basic");
type BasicState = InferAgentState<typeof basicAgent>;
// { messages: BaseMessage[]; userId: string; structuredResponse?: any }

const advancedAgent = createConditionalAgent("advanced");
type AdvancedState = InferAgentState<typeof advancedAgent>;
// { messages: BaseMessage[]; userId: string; adminLevel: number; permissions: string[]; structuredResponse?: any }

Related Documentation

  • Agent API Reference - Complete agent API with all types
  • Middleware System - Middleware type system
  • Error Handling - Error types and handling patterns
  • Quick Reference - Quick type lookup reference

docs

advanced

error-handling.md

type-inference.md

glossary.md

index.md

quick-reference.md

task-index.md

tile.json