or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced

error-handling.mdtype-inference.md
glossary.mdindex.mdquick-reference.mdtask-index.md
tile.json

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