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

agents.mddocs/api-reference/

Agent API Reference

Complete API reference for creating and executing LangChain agents.

createAgent

/**
 * Creates a production-ready ReAct (Reasoning + Acting) agent with comprehensive configuration options
 *
 * ReAct agents iteratively reason about tasks and call tools to gather information and take actions.
 * This function creates a fully-configured agent with support for tools, structured outputs, state management,
 * middleware, and more.
 *
 * @template TConfig - Agent type configuration (inferred automatically from params)
 * @param params - Agent configuration parameters. At minimum, must include a model.
 * @param params.model - Language model identifier (e.g., "openai:gpt-4o", "anthropic:claude-3-5-sonnet-20241022")
 *                       or a ChatModel instance for full control. This is effectively required - the agent cannot
 *                       function without a model. String format is "provider:model-name".
 * @param params.llm - Alias for params.model (either can be used)
 * @param params.tools - Array of tools the agent can call, created with tool() function or a configured ToolNode.
 *                       Tools give agents the ability to take actions like searching, calculating, or making API calls.
 * @param params.systemPrompt - System instructions that guide the agent's behavior. Can be a string or SystemMessage.
 *                              Should explain the agent's role, constraints, and available capabilities.
 * @param params.prompt - Alias for params.systemPrompt (either can be used)
 * @param params.responseFormat - Structured output schema using Zod schemas, JSON schemas, or response format strategies.
 *                                When set, the agent will return a validated structuredResponse in addition to messages.
 *                                Supports single schemas, array of schemas (union types), or strategy instances.
 * @param params.stateSchema - Custom state schema for adding persistent state properties beyond built-in state (messages).
 *                             State persists across invocations when using a checkpointer. Can be a Zod object or
 *                             LangGraph AnnotationRoot. Properties from this schema become required/optional parameters
 *                             in agent.invoke() and are accessible in the returned state.
 * @param params.contextSchema - Context schema for read-only data provided per invocation but not persisted.
 *                               Useful for request-scoped data like request IDs, user info, or IP addresses.
 *                               Can be a Zod object or LangGraph AnnotationRoot. Passed via config.context in invoke().
 * @param params.middleware - Array of middleware instances for extending agent behavior with cross-cutting concerns
 *                            like logging, retries, human-in-the-loop, PII detection, etc. Middleware can add hooks,
 *                            state, context, and tools. Executes in array order for lifecycle hooks.
 * @param params.checkpointer - BaseCheckpointSaver instance for persisting agent state across invocations.
 *                              Required for conversation memory and resuming interrupted executions.
 *                              Use thread_id in config to identify separate conversation threads.
 * @param params.store - BaseStore instance for long-term memory storage separate from checkpointer.
 *                       Useful for facts, preferences, or data that outlives conversation threads.
 * @param params.name - Optional agent name for identification in multi-agent systems or logging
 * @param params.description - Optional agent description explaining its purpose or capabilities
 * @param params.includeAgentName - How to expose agent name to the model:
 *                                   - true: Include in all messages
 *                                   - false: Never include (default)
 *                                   - "tool_messages": Include only in tool messages
 * @param params.signal - AbortSignal for cancelling agent execution. Use AbortSignal.timeout(ms) for timeouts.
 * @param params.version - LangGraph version to use ("v1" or "v2"). Defaults to "v2". Usually not needed.
 * @returns ReactAgent instance with methods for execution (invoke, stream, streamEvents, batch)
 *
 * @example Basic agent
 * ```typescript
 * const agent = createAgent({
 *   model: "openai:gpt-4o",
 *   tools: [],
 *   systemPrompt: "You are a helpful assistant.",
 * });
 * ```
 *
 * @example Agent with tools and structured output
 * ```typescript
 * const searchTool = tool(
 *   async ({ query }) => `Results for: ${query}`,
 *   { name: "search", description: "Search", schema: z.object({ query: z.string() }) }
 * );
 *
 * const ResultSchema = z.object({
 *   answer: z.string(),
 *   sources: z.array(z.string()),
 * });
 *
 * const agent = createAgent({
 *   model: "openai:gpt-4o",
 *   tools: [searchTool],
 *   responseFormat: ResultSchema,
 *   systemPrompt: "You are a research assistant.",
 * });
 * ```
 *
 * @example Agent with state and persistence
 * ```typescript
 * import { MemorySaver } from "@langchain/langgraph";
 *
 * const StateSchema = z.object({
 *   userId: z.string(),
 *   preferences: z.object({ theme: z.string() }).optional(),
 * });
 *
 * const agent = createAgent({
 *   model: "openai:gpt-4o",
 *   tools: [],
 *   stateSchema: StateSchema,
 *   checkpointer: new MemorySaver(),
 * });
 *
 * // First invocation
 * await agent.invoke(
 *   { messages: [{ role: "user", content: "Hi, I'm Alice" }], userId: "user-123" },
 *   { configurable: { thread_id: "thread-1" } }
 * );
 *
 * // Later invocation - state is restored
 * const result = await agent.invoke(
 *   { messages: [{ role: "user", content: "What's my name?" }], userId: "user-123" },
 *   { configurable: { thread_id: "thread-1" } }
 * );
 * ```
 */
function createAgent<TConfig>(params: CreateAgentParams): ReactAgent<TConfig>;

interface CreateAgentParams<
  StructuredResponseFormat = any,
  StateSchema = any,
  ContextSchema = any,
  TResponseFormat = any
> {
  /**
   * Language model as string identifier (e.g., "openai:gpt-4o", "anthropic:claude-3-5-sonnet-20241022")
   * or model instance for full control
   * Required parameter - must be provided
   * Note: "llm" is an alias for "model" (both are accepted)
   */
  model?: string | ChatModel;

  /**
   * Alias for model parameter (either model or llm can be used)
   */
  llm?: string | ChatModel;

  /**
   * Array of tools created with tool() function or configured ToolNode
   */
  tools?: Tool[] | ToolNode;

  /**
   * System instructions as string or SystemMessage
   */
  systemPrompt?: string | SystemMessage;

  /**
   * Alias for systemPrompt parameter
   */
  prompt?: string | SystemMessage;

  /**
   * Structured output schema using Zod schema, JSON schema, or response format strategy
   * When set, agent will return typed structuredResponse in state
   */
  responseFormat?: TResponseFormat;

  /**
   * Custom state schema for memory and additional state properties (persisted)
   * Can be Zod object or LangGraph AnnotationRoot
   */
  stateSchema?: StateSchema;

  /**
   * Context schema for read-only data not persisted across invocations
   * Can be Zod object or LangGraph AnnotationRoot
   */
  contextSchema?: ContextSchema;

  /**
   * Array of middleware instances for extending agent behavior
   */
  middleware?: AgentMiddleware[];

  /**
   * Checkpointer for state persistence across invocations
   */
  checkpointer?: BaseCheckpointSaver;

  /**
   * Long-term memory storage (separate from checkpointer)
   */
  store?: BaseStore;

  /**
   * Agent name for identification
   */
  name?: string;

  /**
   * Agent description
   */
  description?: string;

  /**
   * How to expose agent name to the model
   * - true: Include in all messages
   * - false: Never include
   * - "tool_messages": Include only in tool messages
   */
  includeAgentName?: boolean | "tool_messages";

  /**
   * Abort signal for cancellation
   */
  signal?: AbortSignal;

  /**
   * Graph version to use ("v1" or "v2")
   * @default "v2"
   */
  version?: "v1" | "v2";
}

ReactAgent Class

/**
 * ReAct agent instance with execution methods
 */
class ReactAgent<TConfig> {
  /**
   * Execute agent synchronously and return final state
   * @param input - User input with messages and optional state
   * @param config - Configuration including context and thread_id
   * @returns Promise resolving to final state with messages and structuredResponse
   */
  invoke(
    input: UserInput<TConfig>,
    config?: InvokeConfiguration<TConfig>
  ): Promise<State<TConfig>>;

  /**
   * Stream agent execution with real-time updates
   * @param input - User input with messages and optional state
   * @param config - Stream configuration with streamMode option
   * @returns Async generator yielding state updates
   */
  stream(
    input: UserInput<TConfig>,
    config?: StreamConfiguration<TConfig>
  ): AsyncGenerator<State<TConfig>>;

  /**
   * Stream detailed events during agent execution
   * @param input - User input with messages and optional state
   * @param config - Stream configuration
   * @returns Async generator yielding execution events
   */
  streamEvents(
    input: UserInput<TConfig>,
    config?: StreamConfiguration<TConfig>
  ): AsyncGenerator<StreamEvent>;

  /**
   * Execute agent on multiple inputs in parallel
   * @param inputs - Array of user inputs
   * @param config - Optional batch configuration
   * @returns Promise resolving to array of states
   */
  batch(
    inputs: UserInput<TConfig>[],
    config?: BatchConfiguration<TConfig>
  ): Promise<State<TConfig>[]>;
}

Input Types

/**
 * User input for agent invocation
 */
type UserInput<TStateSchema = any> = {
  /**
   * Array of messages to process
   */
  messages: MessageInput[];
} & Partial<InferSchemaInput<TStateSchema>>;

/**
 * Message input formats
 */
type MessageInput =
  | string // Simple string message (treated as user message)
  | BaseMessage // Full message object
  | {
      role: "user" | "assistant" | "system" | "tool";
      content: string;
      tool_call_id?: string; // Required for tool role
    };

Configuration Types

/**
 * Configuration for agent invocation
 */
interface InvokeConfiguration<ContextSchema = any> {
  /**
   * Read-only context data not persisted across invocations
   */
  context?: InferContextInput<ContextSchema>;

  /**
   * Configurable options including thread_id for persistence
   */
  configurable?: {
    /**
     * Thread ID for state persistence (requires checkpointer)
     */
    thread_id?: string;
    [key: string]: any;
  };

  /**
   * Abort signal for cancellation
   */
  signal?: AbortSignal;
}

/**
 * Configuration for streaming
 */
interface StreamConfiguration<ContextSchema = any> extends InvokeConfiguration<ContextSchema> {
  /**
   * Stream mode
   * - "values": Full state updates
   * - "updates": Only changed values
   * - "messages": Only new messages
   */
  streamMode?: "values" | "updates" | "messages";
}

/**
 * Configuration for batch processing
 */
interface BatchConfiguration<ContextSchema = any> extends InvokeConfiguration<ContextSchema> {
  /**
   * Maximum concurrency for batch processing
   */
  maxConcurrency?: number;
}

State Types

/**
 * Agent state returned from invoke/stream
 */
interface State<TConfig = any> {
  /**
   * Conversation message history
   */
  messages: BaseMessage[];

  /**
   * Structured response when responseFormat is set
   */
  structuredResponse?: InferStructuredResponse<TConfig>;

  /**
   * Additional state properties from stateSchema
   */
  [key: string]: any;
}

/**
 * Built-in state properties
 */
interface BuiltInState {
  /**
   * Message history
   */
  messages: BaseMessage[];

  /**
   * Structured response (when responseFormat is used)
   */
  structuredResponse?: any;
}

Tool Types

/**
 * Information about a tool call
 */
interface ToolCall {
  id: string;
  name: string;
  args: Record<string, any>;
}

/**
 * Information about a tool result
 */
interface ToolResult {
  tool_call_id: string;
  content: string;
  error?: string;
}

/**
 * Executed tool call with result
 */
interface ExecutedToolCall extends ToolCall {
  result: string;
  error?: Error;
}

Interrupt Types

/**
 * Interrupt information from middleware
 */
interface Interrupt<TValue = any> {
  value: TValue;
  resumable: boolean;
  when: "before-tools" | "after-tools" | "before-model" | "after-model";
}

/**
 * Jump targets for controlling execution flow
 */
type JumpToTarget = "model" | "tools" | "end";

Runtime Types

/**
 * Runtime context available to middleware
 */
interface Runtime<TContext = any> {
  /**
   * Read-only context data
   */
  context: TContext;

  /**
   * Configurable options
   */
  configurable: {
    thread_id?: string;
    [key: string]: any;
  };

  /**
   * Additional LangGraph runtime properties
   */
  [key: string]: any;
}

Utility Functions

/**
 * Type guard to check if value is a BaseChatModel
 */
function isBaseChatModel(value: any): value is BaseChatModel;

/**
 * Type guard to check if model is configurable
 */
function isConfigurableModel(value: any): value is ConfigurableModelInterface;

/**
 * Interface for configurable models
 */
interface ConfigurableModelInterface {
  model?: string | ChatModel;
  [key: string]: any;
}

/**
 * Check if tool is a client tool (should run on client)
 */
function isClientTool(tool: Tool): boolean;

/**
 * Validate that LLM has no tools bound
 */
function validateLLMHasNoBoundTools(llm: ChatModel): void;

/**
 * Check if message has tool calls
 */
function hasToolCalls(message: BaseMessage): boolean;

/**
 * Normalize system prompt input
 */
function normalizeSystemPrompt(
  prompt: string | SystemMessage | ((state: State) => string | SystemMessage),
  state?: State
): SystemMessage;

Error Classes

/**
 * Thrown when attempting to bind structured output to a model that already has tools bound
 */
class MultipleToolsBoundError extends Error {
  name: "MultipleToolsBoundError";
  message: "The model already has tools bound to it. Cannot bind structured output format.";
}

/**
 * Thrown when multiple structured outputs are returned but only one was expected
 */
class MultipleStructuredOutputsError extends Error {
  name: "MultipleStructuredOutputsError";
  message: "Multiple structured outputs returned when one was expected";
  outputs: any[];
}

/**
 * Thrown when structured output parsing fails
 */
class StructuredOutputParsingError extends Error {
  name: "StructuredOutputParsingError";
  message: string;
  cause?: Error;
  rawOutput?: string;
}

/**
 * Thrown when tool invocation fails
 */
class ToolInvocationError extends Error {
  name: "ToolInvocationError";
  message: string;
  toolName: string;
  toolInput: any;
  cause?: Error;
}

Agent Name Mode

/**
 * How to include agent name in messages
 */
type AgentNameMode = boolean | "tool_messages";

Type Inference Utilities

Complete type inference system for extracting types from agents at compile time.

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
 * @template T - Agent type to extract from
 * @template K - Key of AgentTypeConfig to extract
 */
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
 * Returns the inferred type of the structured response
 * @template T - Agent type
 */
type InferAgentResponse<T> = InferAgentType<T, "response">;

/**
 * Extract full merged state from agent (includes built-in state)
 * Merges agent state schema with middleware states and built-in state
 * @template T - Agent type
 */
type InferAgentState<T> = InferAgentType<T, "state">;

/**
 * Extract raw state schema from agent
 * Returns the state schema without merging
 * @template T - Agent type
 */
type InferAgentStateSchema<T> = T extends ReactAgent<infer Config>
  ? Config extends { stateSchema: infer Schema }
    ? Schema
    : undefined
  : never;

/**
 * Extract full merged context from agent
 * Merges agent context schema with middleware contexts
 * @template T - Agent type
 */
type InferAgentContext<T> = InferAgentType<T, "context">;

/**
 * Extract raw context schema from agent
 * Returns the context schema without merging
 * @template T - Agent type
 */
type InferAgentContextSchema<T> = T extends ReactAgent<infer Config>
  ? Config extends { contextSchema: infer Schema }
    ? Schema
    : undefined
  : never;

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

/**
 * Extract tools array from agent
 * Includes both agent tools and middleware tools
 * @template T - Agent type
 */
type InferAgentTools<T> = InferAgentType<T, "tools">;

Usage Example:

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

const StateSchema = z.object({
  userId: z.string(),
  count: z.number(),
});

const ResponseSchema = z.object({
  answer: z.string(),
  confidence: z.number(),
});

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

// Infer state type (includes built-in state)
type State = InferAgentState<typeof agent>;
// { messages: BaseMessage[]; userId: string; count: number; structuredResponse?: { answer: string; confidence: number } }

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

// Type-safe access
const result = await agent.invoke({
  messages: [{ role: "user", content: "Hello" }],
  userId: "user123",
  count: 1,
});

const answer: string = result.structuredResponse.answer;
const userId: string = result.userId;

Helper Type Utilities

/**
 * Extract union type from Zod schema array
 * Useful for inferring union response types
 * @template T - Array of Zod schemas
 */
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
 * Creates a mapped type with tool names as keys
 * @template T - Array of tools
 */
type ToolsToMessageToolSet<T extends readonly Tool[]> = {
  [K in T[number]["name"]]: Extract<T[number], { name: K }>;
};

/**
 * Merge agent and middleware tools
 * Combines tools from agent and all middleware
 * @template TAgentTools - Agent's tools array
 * @template TMiddleware - Middleware array
 */
type CombineTools<
  TAgentTools extends readonly Tool[],
  TMiddleware extends readonly AgentMiddleware[]
> = [...TAgentTools, ...InferMiddlewareTools<TMiddleware>];

/**
 * Infer tools from middleware array
 * Recursively extracts tools from all middleware
 * @template T - Middleware array
 */
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
 * @template T - Middleware array or other type
 */
type InferMiddlewareToolsArray<T> = T extends readonly AgentMiddleware[]
  ? InferMiddlewareTools<T>
  : [];

/**
 * Check if all properties are optional
 * Used for context optionality checking
 * @template T - Object type to check
 */
type IsAllOptional<T> = {} extends Required<T> ? true : false;

/**
 * Context optionality helper
 * Makes context optional if all properties are optional
 * @template TContext - Context type
 */
type WithMaybeContext<TContext> = IsAllOptional<TContext> extends true
  ? { context?: TContext }
  : { context: TContext };

/**
 * Infer input type from schema
 * Handles both Zod schemas and plain objects
 * @template T - Schema type
 */
type InferSchemaInput<T> = T extends z.ZodType<infer Output, any, infer Input>
  ? Input
  : T extends { [key: string]: any }
  ? T
  : {};

/**
 * Infer context input type
 * Handles both Zod schemas and plain objects
 * @template ContextSchema - Context schema type
 */
type InferContextInput<ContextSchema> = ContextSchema extends z.ZodType<
  any,
  any,
  infer Input
>
  ? Input
  : ContextSchema extends { [key: string]: any }
  ? ContextSchema
  : {};

/**
 * Resolve agent type configuration
 * Maps CreateAgentParams to AgentTypeConfig
 * @template T - CreateAgentParams type
 */
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
 * Used when no custom types are specified
 */
type DefaultAgentTypeConfig = AgentTypeConfig<
  undefined,
  Record<string, never>,
  Record<string, never>,
  readonly AgentMiddleware[],
  readonly Tool[]
>;

/**
 * Any annotation root type
 * Placeholder for LangGraph annotation roots
 */
type AnyAnnotationRoot = {
  [key: string]: any;
};

/**
 * Normalized schema input
 * Extracts input type from Zod schema or returns schema as-is
 * @template TSchema - Schema type
 */
type NormalizedSchemaInput<TSchema> = TSchema extends z.ZodType<
  any,
  any,
  infer Input
>
  ? Input
  : TSchema;

Advanced Type Inference Examples:

// Union response types
const EmailSchema = z.object({ type: z.literal("email"), to: z.string() });
const TaskSchema = z.object({ type: z.literal("task"), title: z.string() });

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

type Response = InferAgentResponse<typeof agent>;
// { type: "email", to: string } | { type: "task", title: string }

// Tool name extraction
const searchTool = tool(async ({ query }) => "...", {
  name: "search",
  description: "Search",
  schema: z.object({ query: z.string() }),
});

const tools = [searchTool] as const;
type ToolNames = typeof tools[number]["name"];
// "search"

// Tools to message set
type ToolSet = ToolsToMessageToolSet<typeof tools>;
// { search: typeof searchTool }

See Type Inference Guide for complete documentation and examples.

Utility Functions

Model Utilities

/**
 * Type guard to check if value is a BaseChatModel
 * @param value - Value to check
 * @returns True if value is a BaseChatModel instance
 */
function isBaseChatModel(value: any): value is BaseChatModel;

/**
 * Type guard to check if model is configurable
 * @param value - Value to check
 * @returns True if value implements ConfigurableModelInterface
 */
function isConfigurableModel(value: any): value is ConfigurableModelInterface;

/**
 * Interface for configurable models
 */
interface ConfigurableModelInterface {
  model?: string | ChatModel;
  [key: string]: any;
}

Tool Utilities

/**
 * Check if tool is a client tool (should run on client)
 * @param tool - Tool to check
 * @returns True if tool should run on client side
 */
function isClientTool(tool: Tool): boolean;

/**
 * Validate that LLM has no tools bound
 * Throws error if tools are already bound to prevent conflicts
 * @param llm - Chat model to validate
 * @throws MultipleToolsBoundError if tools already bound
 */
function validateLLMHasNoBoundTools(llm: ChatModel): void;

/**
 * Check if message has tool calls
 * @param message - Message to check
 * @returns True if message contains tool calls
 */
function hasToolCalls(message: BaseMessage): boolean;

System Prompt Utilities

/**
 * Normalize system prompt input to SystemMessage
 * @param prompt - System prompt as string, SystemMessage, or function
 * @param state - Optional state for function prompts
 * @returns Normalized SystemMessage
 */
function normalizeSystemPrompt(
  prompt: string | SystemMessage | ((state: State) => string | SystemMessage),
  state?: State
): SystemMessage;

Runtime Types

/**
 * Runtime context available to middleware and agent internals
 */
interface Runtime<TContext = any> {
  /**
   * Read-only context data
   */
  context: TContext;

  /**
   * Configurable options including thread_id
   */
  configurable: {
    thread_id?: string;
    [key: string]: any;
  };

  /**
   * Additional LangGraph runtime properties
   */
  [key: string]: any;
}

Advanced Agent Behaviors

Execution Flow Details

The agent execution follows this precise flow:

  1. Initialization Phase

    • Validate model configuration
    • Check for conflicting configurations (tools + structured output binding)
    • Merge state/context schemas from agent and middleware
    • Compile middleware hooks in order
    • Build tool node with all tools (agent + middleware)
  2. Pre-Execution Phase (per invoke/stream)

    • Execute beforeAgent hooks from all middleware (in array order)
    • Validate input against state schema
    • Normalize message inputs to BaseMessage instances
    • Apply context from config to runtime
  3. Execution Loop (ReAct pattern)

    • Execute beforeModel hooks from all middleware
    • Execute wrapModelCall hooks (innermost to outermost)
    • Call language model with current messages
    • Execute afterModel hooks from all middleware
    • If model returns tool calls:
      • Execute wrapToolCall hooks for each tool (innermost to outermost)
      • Execute tools (parallel or sequential based on configuration)
      • Add tool results to messages
      • Loop back to step 1 (model call)
    • If model returns final response:
      • Extract structured response if responseFormat is set
      • Proceed to post-execution
  4. Post-Execution Phase

    • Execute afterAgent hooks from all middleware (in array order)
    • Validate structured response against schema
    • Save checkpoint if checkpointer is configured
    • Return final state

State Management Details

State Persistence:

  • State is persisted only when checkpointer is configured
  • Requires thread_id in config.configurable
  • State is loaded before execution and saved after completion
  • Failed executions do not save state (atomic updates)

State Merging Order:

  1. Built-in state (messages, structuredResponse)
  2. Agent state schema properties
  3. Middleware state schema properties (merged in middleware array order)
  4. Later middleware can override earlier middleware state

State Schema Validation:

  • Input is validated against merged state schema before execution
  • Validation uses Zod's parse/safeParse based on schema type
  • Invalid input throws validation error with details
  • Optional properties with defaults are automatically populated

Context vs State

Context (read-only, per-invocation):

  • Passed via config.context in invoke/stream calls
  • Available to middleware hooks via runtime.context
  • Not persisted across invocations
  • Useful for request-scoped data (request IDs, IP addresses, auth tokens)
  • Merged from agent context schema + middleware context schemas

State (persisted, mutable):

  • Passed via input properties in invoke/stream calls
  • Available to middleware hooks via state parameter
  • Persisted when checkpointer is configured
  • Useful for conversation history, user preferences, session data
  • Merged from agent state schema + middleware state schemas

Tool Execution Details

Tool Invocation:

  • Tools are called based on model's tool_calls in AIMessage
  • Multiple tool calls can execute in parallel (default behavior)
  • Tool errors are caught and returned as error messages
  • Failed tools don't halt execution (agent can retry/continue)

Tool Call Limiting:

  • No built-in limit on tool calls per invocation
  • Use toolCallLimitMiddleware to enforce limits
  • Infinite loops are possible if agent keeps calling tools
  • Consider implementing max_iterations logic in middleware

Tool Arguments:

  • Arguments are validated against tool schema before invocation
  • Invalid arguments result in tool error message
  • Model receives error message and can retry with corrected args
  • Type coercion follows Zod schema coercion rules

Structured Output Details

Response Format Strategies:

  • Direct schema: Model's native structured output (provider-specific)
  • Tool strategy: Wraps schema as a tool call (universal compatibility)
  • Provider strategy: Uses provider-specific structured output API
  • JSON schema: Direct JSON schema format (some providers only)

Extraction Process:

  1. Model generates response (varies by strategy)
  2. Extract structured data from response
  3. Validate against schema (Zod parse/safeParse)
  4. Set structuredResponse in state
  5. Validation failure throws StructuredOutputParsingError

Union Type Handling:

  • Array of schemas creates discriminated union
  • Model chooses which schema to return
  • Validation tries each schema in order
  • First successful parse is returned
  • All schemas fail → StructuredOutputParsingError

Message Processing Details

Message Normalization:

  • String → HumanMessage(content)
  • {role, content} → Corresponding message type
  • BaseMessage → Pass through unchanged
  • Invalid format → Validation error

Message History Management:

  • Messages accumulate in state.messages array
  • No automatic trimming/summarization (use middleware)
  • Large message histories can exceed token limits
  • Use trimMessages or summarizationMiddleware for long conversations

Tool Messages:

  • Automatically created from tool execution results
  • Include tool_call_id linking to originating tool call
  • Error messages include error field
  • Model receives both successful and failed tool results

Middleware Composition

Hook Execution Order:

  • beforeAgent: Middleware array order (0 → N)
  • beforeModel: Middleware array order (0 → N)
  • afterModel: Middleware array order (0 → N)
  • afterAgent: Middleware array order (0 → N)
  • wrapToolCall: Nested (innermost middleware wraps outermost)
  • wrapModelCall: Nested (innermost middleware wraps outermost)

State/Context Merging:

  • Middleware state schemas merge left-to-right
  • Later middleware can override earlier middleware properties
  • Conflicts are resolved by taking rightmost value
  • Context follows same merging rules

Tool Merging:

  • Agent tools + middleware tools (concatenated)
  • Later middleware tools added to end
  • Name conflicts are not detected (first match wins)
  • Use unique tool names across all middleware

Performance Considerations

Token Usage:

  • Every tool call adds messages (tool call + result)
  • System prompts included in every model call
  • Large state schemas don't directly affect tokens (not sent to model)
  • Use summarizationMiddleware to manage conversation length

Latency:

  • Sequential tool calls add latency (one round-trip per call)
  • Parallel tool calls execute concurrently (limited by provider)
  • Middleware hooks add minimal overhead (< 1ms typically)
  • Streaming reduces perceived latency for long responses

Memory:

  • Message history grows unbounded without trimming
  • State persisted in checkpointer (memory/disk depends on implementation)
  • Large structured outputs consume memory
  • Consider streaming for memory-constrained environments

Concurrency:

  • Single agent instance is thread-safe for reads
  • Multiple concurrent invocations supported
  • State isolation via thread_id
  • Tool execution can be parallel or sequential

Error Recovery

Automatic Recovery:

  • Tool errors: Agent can retry with different args
  • Model errors: No automatic retry (use modelRetryMiddleware)
  • Parse errors: No automatic retry (use retry middleware)
  • Network errors: No automatic retry (use retry middleware)

Manual Recovery:

  • Catch errors in application code
  • Inspect error details (type, cause, tool name)
  • Retry with modified input
  • Resume from checkpoint if using checkpointer

Error Propagation:

  • Tool errors → Returned as tool error messages (execution continues)
  • Model errors → Thrown immediately (execution halts)
  • Validation errors → Thrown immediately (execution halts)
  • Middleware errors → Thrown immediately (execution halts)

Internal Implementation Notes

For Advanced Users:

The agent is implemented as a LangGraph StateGraph with nodes:

  • __start__: Entry point
  • agent: Model invocation node
  • tools: Tool execution node
  • __end__: Exit point

Edges:

  • __start__agent
  • agenttools (if tool calls present)
  • agent__end__ (if no tool calls)
  • toolsagent (loop back after tool execution)

Middleware is implemented as graph transforms that:

  • Add state channels for middleware state/context
  • Wrap nodes with before/after hooks
  • Intercept tool/model calls with wrapper functions

This architecture allows:

  • Resumable execution (via checkpointer)
  • Human-in-the-loop interrupts (middleware can raise interrupts)
  • Custom state management (arbitrary state properties)
  • Tool streaming (events emitted during execution)

Related Documentation