---
title: LangChain v1 migration guide
sidebarTitle: LangChain v1
---

This migration guide outlines the major changes in LangChain v1. To learn more about the new features of v1, see the [introductory post](/oss/releases/langchain-v1).

To upgrade,

<CodeGroup>
```bash npm
npm install langchain@latest @langchain/core@latest
```
```bash pnpm
pnpm install langchain@latest @langchain/core@latest
```
```bash yarn
yarn add langchain@latest @langchain/core@latest
```
```bash bun
bun add langchain@latest @langchain/core@latest
```
</CodeGroup>

## `createAgent`

In v1, the react agent prebuilt is now in the langchain package. The table below outlines what functionality has changed:

| Section | What changed |
|---------|--------------|
| [Import path](#import-path) | Package moved from `@langchain/langgraph/prebuilts` to `langchain` |
| [Prompts](#prompts) | Parameter renamed to `systemPrompt`, dynamic prompts use middleware |
| [Pre-model hook](#pre-model-hook) | Replaced by middleware with `beforeModel` method |
| [Post-model hook](#post-model-hook) | Replaced by middleware with `afterModel` method |
| [Custom state](#custom-state) | Defined in middleware, zod objects only |
| [Model](#model) | Dynamic selection via middleware, pre-bound models not supported |
| [Tools](#tools) | Tool error handling moved to middleware with `wrapToolCall` |
| [Structured output](#structured-output) | prompted output removed, use `toolStrategy`/`providerStrategy` |
| [Streaming node name](#streaming-node-name-rename) | Node name changed from `"agent"` to `"model"` |
| [Runtime context](#runtime-context) | `context` property instead of `config.configurable` |
| [Namespace](#simplified-package) | Streamlined to focus on agent building blocks, legacy code moved to `@langchain/classic` |

### Import path

The import path for the react agent prebuilt has changed from `@langchain/langgraph/prebuilts` to `langchain`. The name of the function has changed from `createReactAgent` to `createAgent`:

```typescript
import { createReactAgent } from "@langchain/langgraph/prebuilts"; // [!code --]
import { createAgent } from "langchain"; // [!code ++]
```

### Prompts

#### Static prompt rename

The `prompt` parameter has been renamed to `systemPrompt`:

<CodeGroup>
```typescript v1 (new)
import { createAgent } from "langchain";

agent = createAgent({
  model,
  tools,
  systemPrompt: "You are a helpful assistant.", // [!code highlight]
});
```
```typescript v0 (old)
import { createReactAgent } from "@langchain/langgraph/prebuilts";

const agent = createReactAgent({
  model,
  tools,
  prompt: "You are a helpful assistant.", // [!code highlight]
});
```
</CodeGroup>

#### `SystemMessage`

If using `SystemMessage` objects in the system prompt, the string content is now used directly:

<CodeGroup>
```typescript v1 (new)
import { SystemMessage, createAgent } from "langchain";

const agent = createAgent({
  model,
  tools,
  systemPrompt: "You are a helpful assistant.", // [!code highlight]
});
```
```typescript v0 (old)
import { createReactAgent } from "@langchain/langgraph/prebuilts";

const agent = createReactAgent({
  model,
  tools,
  prompt: new SystemMessage(content: "You are a helpful assistant."), // [!code highlight]
});
```
</CodeGroup>

#### Dynamic prompts

Dynamic prompts are a core context engineering pattern— they adapt what you tell the model based on the current conversation state. To do this, use `dynamicSystemPromptMiddleware`:

<CodeGroup>
```typescript v1 (new)
import { createAgent, dynamicSystemPromptMiddleware } from "langchain";
import * as z from "zod";

const contextSchema = z.object({
  userRole: z.enum(["expert", "beginner"]).default("beginner"),
});

const userRolePrompt = dynamicSystemPromptMiddleware<z.infer<typeof contextSchema>>( // [!code highlight]
    (_state, runtime) => {
        const userRole = runtime.context.userRole;
        const basePrompt = "You are a helpful assistant.";

        if (userRole === "expert") {
            return `${basePrompt} Provide detailed technical responses.`;
        } else if (userRole === "beginner") {
            return `${basePrompt} Explain concepts simply and avoid jargon.`;
        }
        return basePrompt; // [!code highlight]
    }
);

const agent = createAgent({
  model,
  tools,
  middleware: [userRolePrompt],
  contextSchema,
});

await agent.invoke(
  {
    messages: [new HumanMessage("Explain async programming")],
  },
  {
    context: {
      userRole: "expert",
    },
  }
);
```

```typescript v0 (old)
import { createReactAgent } from "@langchain/langgraph/prebuilts";

const contextSchema = z.object({
  userRole: z.enum(["expert", "beginner"]),
});

const agent = createReactAgent({
  model,
  tools,
  prompt: (state) => {
    const userRole = state.context.userRole;
    const basePrompt = "You are a helpful assistant.";

    if (userRole === "expert") {
      return `${basePrompt} Provide detailed technical responses.`;
    } else if (userRole === "beginner") {
      return `${basePrompt} Explain concepts simply and avoid jargon.`;
    }
    return basePrompt;
  },
  contextSchema,
});

// Use with context via config.configurable
await agent.invoke(
  {
    messages: [new HumanMessage("Explain async programming")],
  },
  {
    config: {
      configurable: { userRole: "expert" },
    },
  }
);
```
</CodeGroup>


### Pre-model hook

Pre-model hooks are now implemented as middleware with the `beforeModel` method. This pattern is more extensible--you can define multiple middlewares to run before the model is called and reuse them across agents.

Common use cases include:
- Summarizing conversation history
- Trimming messages
- Input guardrails, like PII redaction

v1 includes built-in summarization middleware:

<CodeGroup>
```typescript v1 (new)
import { createAgent, summarizationMiddleware } from "langchain";

const agent = createAgent({
  model: "claude-sonnet-4-5-20250929",
  tools,
  middleware: [
    summarizationMiddleware({
      model: "claude-sonnet-4-5-20250929",
      trigger: { tokens: 1000 },
    }),
  ],
});
```
```typescript v0 (old)
import { createReactAgent } from "@langchain/langgraph/prebuilts";

function customSummarization(state) {
  // Custom logic for message summarization
}

const agent = createReactAgent({
  model: "claude-sonnet-4-5-20250929",
  tools,
  preModelHook: customSummarization,
});
```
</CodeGroup>

### Post-model hook

Post-model hooks are now implemented as middleware with the `afterModel` method. This lets you compose multiple handlers after the model responds.

Common use cases include:
- Human-in-the-loop approval
- Output guardrails

v1 includes a built-in human-in-the-loop middleware:

<CodeGroup>
```typescript v1 (new)
import { createAgent, humanInTheLoopMiddleware } from "langchain";

const agent = createAgent({
  model: "claude-sonnet-4-5-20250929",
  tools: [readEmail, sendEmail],
  middleware: [
    humanInTheLoopMiddleware({
      interruptOn: {
        sendEmail: { allowedDecisions: ["approve", "edit", "reject"] },
      },
    }),
  ],
});
```
```typescript v0 (old)
import { createReactAgent } from "@langchain/langgraph/prebuilts";

function customHumanInTheLoopHook(state) {
  // Custom approval logic
}

const agent = createReactAgent({
  model: "claude-sonnet-4-5-20250929",
  tools: [readEmail, sendEmail],
  postModelHook: customHumanInTheLoopHook,
});
```
</CodeGroup>

### Custom state

Custom state is now defined in middleware using the `stateSchema` property. Use Zod to declare additional state fields that are carried through the agent run.

<CodeGroup>
```typescript v1 (new)
import * as z from "zod";
import { createAgent, createMiddleware, tool } from "langchain";

const UserState = z.object({
  userName: z.string(),
});

const userState = createMiddleware({
  name: "UserState",
  stateSchema: UserState,
  beforeModel: (state) => {
    // Access custom state properties
    const name = state.userName;
    // Optionally modify messages/system prompt based on state
    return;
  },
});

const greet = tool(
  async () => {
    return "Hello!";
  },
  {
    name: "greet",
    description: "Greet the user",
    schema: z.object({}),
  }
);

const agent = createAgent({
  model: "claude-sonnet-4-5-20250929",
  tools: [greet],
  middleware: [userState],
});

await agent.invoke({
  messages: [{ role: "user", content: "Hi" }],
  userName: "Ada",
});
```
```typescript v0 (old)
import { getCurrentTaskInput } from "@langchain/langgraph";
import { createReactAgent } from "@langchain/langgraph/prebuilts";
import * as z from "zod";

const UserState = z.object({
  userName: z.string(),
});

const greet = tool(
  async () => {
    const state = await getCurrentTaskInput();
    const userName = state.userName;
    return `Hello ${userName}!`;
  },
);

// Custom state was provided via agent-level state schema or accessed ad hoc in hooks
const agent = createReactAgent({
  model: "claude-sonnet-4-5-20250929",
  tools: [greet],
  stateSchema: UserState,
});
```
</CodeGroup>

### Model

Dynamic model selection now happens via middleware. Use `wrapModelCall` to swap models (and tools) based on state or runtime context. In `createReactAgent`, this was done via a function passed to the `model` parameter.

This functionality has been ported to the middleware interface in v1.

#### Dynamic model selection

<CodeGroup>
```typescript v1 (new)
import { createAgent, createMiddleware } from "langchain";

const dynamicModel = createMiddleware({
  name: "DynamicModel",
  wrapModelCall: (request, handler) => {
    const messageCount = request.state.messages.length;
    const model = messageCount > 10 ? "openai:gpt-5" : "openai:gpt-5-nano";
    return handler({ ...request, model });
  },
});

const agent = createAgent({
  model: "gpt-5-nano",
  tools,
  middleware: [dynamicModel],
});
```
```typescript v0 (old)
import { createReactAgent } from "@langchain/langgraph/prebuilts";

function selectModel(state) {
  return state.messages.length > 10 ? "openai:gpt-5" : "openai:gpt-5-nano";
}

const agent = createReactAgent({
  model: selectModel,
  tools,
});
```
</CodeGroup>

#### Pre-bound models

To better support structured output, `createAgent` should receive a plain model (string or instance) and a separate `tools` list. Avoid passing models pre-bound with tools when using structured output.

```typescript
// No longer supported
// const modelWithTools = new ChatOpenAI({ model: "gpt-4o-mini" }).bindTools([someTool]);
// const agent = createAgent({ model: modelWithTools, tools: [] });

// Use instead
const agent = createAgent({ model: "gpt-4o-mini", tools: [someTool] });
```

### Tools

The `tools` argument to `createAgent` accepts:

- Functions created with `tool`
- LangChain tool instances
- Objects that represent built-in provider tools

#### Handling tool errors

You can now configure the handling of tool errors with middleware implementing the `wrapToolCall` method.

<CodeGroup>
```typescript v1 (new)
import { createAgent, createMiddleware, ToolMessage } from "langchain";

const handleToolErrors = createMiddleware({
  name: "HandleToolErrors",
  wrapToolCall: async (request, handler) => {
    try {
      return await handler(request);
    } catch (error) {
      // Only handle errors that occur during tool execution due to invalid inputs
      // that pass schema validation but fail at runtime (e.g., invalid SQL syntax).
      // Do NOT handle:
      // - Network failures (use tool retry middleware instead)
      // - Incorrect tool implementation errors (should bubble up)
      // - Schema mismatch errors (already auto-handled by the framework)
      //
      // Return a custom error message to the model
      return new ToolMessage({
        content: `Tool error: Please check your input and try again. (${error})`,
        tool_call_id: request.toolCall.id!,
      });
    }
  },
});

const agent = createAgent({
  model: "claude-sonnet-4-5-20250929",
  tools: [checkWeather, searchWeb],
  middleware: [handleToolErrors],
});
```
```typescript v0 (old)
import { createReactAgent, ToolNode } from "@langchain/langgraph/prebuilts";

const agent = createReactAgent({
  model: "claude-sonnet-4-5-20250929",
  tools: new ToolNode(
    [checkWeather, searchWeb],
    { handleToolErrors: true } // [!code highlight]
  ),
});
```
</CodeGroup>

### Structured output

#### Node changes

Structured output used to be generated in a separate node from the main agent. This is no longer the case. Structured output is generated in the main loop (no extra LLM call), reducing cost and latency.

#### Tool and provider strategies

In v1, there are two strategies:

- `toolStrategy` uses artificial tool calling to generate structured output
- `providerStrategy` uses provider-native structured output generation

<CodeGroup>
```typescript v1 (new)
import { createAgent, toolStrategy } from "langchain";
import * as z from "zod";

const OutputSchema = z.object({
  summary: z.string(),
  sentiment: z.string(),
});

const agent = createAgent({
  model: "gpt-4o-mini",
  tools,
  // explicitly using tool strategy
  responseFormat: toolStrategy(OutputSchema), // [!code highlight]
});
```
```typescript v0 (old)
import { createReactAgent } from "@langchain/langgraph/prebuilts";
import * as z from "zod";

const OutputSchema = z.object({
  summary: z.string(),
  sentiment: z.string(),
});

const agent = createReactAgent({
  model: "gpt-4o-mini",
  tools,
  // Structured output was driven primarily via tool-calling with fewer options
  responseFormat: OutputSchema,
});
```
</CodeGroup>

#### Prompted output removed

Prompted output via custom instructions in `responseFormat` is removed in favor of the above strategies.

### Streaming node name rename

When streaming events from agents, the node name was changed from `"agent"` to `"model"` to better reflect the node's purpose.

### Runtime context

When invoking an agent, pass static, read-only configuration via the `context` config argument. This replaces patterns that used `config.configurable`.

<CodeGroup>
```typescript v1 (new)
import { createAgent, HumanMessage } from "langchain";
import * as z from "zod";

const agent = createAgent({
  model: "gpt-4o",
  tools,
  contextSchema: z.object({ userId: z.string(), sessionId: z.string() }),
});

const result = await agent.invoke(
  { messages: [new HumanMessage("Hello")] },
  { context: { userId: "123", sessionId: "abc" } }, // [!code highlight]
);
```
```typescript v0 (old)
import { createReactAgent, HumanMessage } from "@langchain/langgraph/prebuilts";

const agent = createReactAgent({ model, tools });

// Pass context via config.configurable
const result = await agent.invoke(
  { messages: [new HumanMessage("Hello")] },
  {
    config: { // [!code highlight]
      configurable: { userId: "123", sessionId: "abc" }, // [!code highlight]
    }, // [!code highlight]
  }
);
```
</CodeGroup>

<Note>
    The old `config.configurable` pattern still works for backward compatibility, but using the new `context` parameter is recommended for new applications or applications migrating to v1.
</Note>

---

## Standard content

In v1, messages gain provider-agnostic standard content blocks. Access them via `message.contentBlocks` for a consistent, typed view across providers. The existing `message.content` field remains unchanged for strings or provider-native structures.

### What changed

- New `contentBlocks` property on messages for normalized content.
- New TypeScript types under `ContentBlock` for strong typing.
- Optional serialization of standard blocks into `content` via `LC_OUTPUT_VERSION=v1` or `outputVersion: "v1"`.

### Read standardized content

<CodeGroup>
```typescript v1 (new)
import { initChatModel } from "langchain";

const model = await initChatModel("gpt-5-nano");
const response = await model.invoke("Explain AI");

for (const block of response.contentBlocks) {
  if (block.type === "reasoning") {
    console.log(block.reasoning);
  } else if (block.type === "text") {
    console.log(block.text);
  }
}
```
```typescript v0 (old)
// Provider-native formats vary; you needed per-provider handling.
const response = await model.invoke("Explain AI");
for (const item of response.content as any[]) {
  if (item.type === "reasoning") {
    // OpenAI-style reasoning
  } else if (item.type === "thinking") {
    // Anthropic-style thinking
  } else if (item.type === "text") {
    // Text
  }
}
```
</CodeGroup>

### Create multimodal messages

<CodeGroup>
```typescript v1 (new)
import { HumanMessage } from "langchain";

const message = new HumanMessage({
  contentBlocks: [
    { type: "text", text: "Describe this image." },
    { type: "image", url: "https://example.com/image.jpg" },
  ],
});
const res = await model.invoke([message]);
```
```typescript v0 (old)
import { HumanMessage } from "langchain";

const message = new HumanMessage({
  // Provider-native structure
  content: [
    { type: "text", text: "Describe this image." },
    { type: "image_url", image_url: { url: "https://example.com/image.jpg" } },
  ],
});
const res = await model.invoke([message]);
```
</CodeGroup>

### Example block types

```typescript
import { ContentBlock } from "langchain";

const textBlock: ContentBlock.Text = {
  type: "text",
  text: "Hello world",
};

const imageBlock: ContentBlock.Multimodal.Image = {
  type: "image",
  url: "https://example.com/image.png",
  mimeType: "image/png",
};
```

See the content blocks [reference](/oss/langchain/messages#content-block-reference) for more details.

### Serialize standard content

Standard content blocks are **not serialized** into the `content` attribute by default. If you need to access standard content blocks in the `content` attribute (e.g., when sending messages to a client), you can opt-in to serializing them into `content`.

<CodeGroup>
```bash
export LC_OUTPUT_VERSION=v1
```
```typescript
import { initChatModel } from "langchain";

const model = await initChatModel("gpt-5-nano", {
  outputVersion: "v1",
});
```
</CodeGroup>

<Note>
    Learn more: [Messages](/oss/langchain/messages#message-content) and [Standard content blocks](/oss/langchain/messages#standard-content-blocks). See [Multimodal](/oss/langchain/messages#multimodal) for input examples.
</Note>

---

## Simplified package

The `langchain` package namespace is streamlined to focus on agent building blocks. Legacy functionality has moved to `@langchain/classic`. The new package exposes only the most useful and relevant functionality.

### Exports

The v1 package includes:

| Module | What's available | Notes |
|--------|------------------|-------|
| Agents | `createAgent`, `AgentState` | Core agent creation functionality |
| Messages | Message types, content blocks, `trimMessages` | Re-exported from `@langchain/core` |
| Tools | `tool`, tool classes | Re-exported from `@langchain/core` |
| Chat models | `initChatModel`, `BaseChatModel` | Unified model initialization |

### `@langchain/classic`

If you use legacy chains, the indexing API, or functionality previously re-exported from `@langchain/community`, install `@langchain/classic` and update imports:

<CodeGroup>
```bash npm
npm install @langchain/classic
```
```bash pnpm
pnpm install @langchain/classic
```
```bash yarn
yarn add @langchain/classic
```
```bash bun
bun add @langchain/classic
```
</CodeGroup>

```typescript
// v1 (new)
import { ... } from "@langchain/classic";
import { ... } from "@langchain/classic/chains";

// v0 (old)
import { ... } from "langchain";
import { ... } from "langchain/chains";
```

---

## Breaking changes

### Dropped Node 18 support

All LangChain packages now require **Node.js 20 or higher**. Node.js 18 reached [end of life](https://nodejs.org/en/about/releases/) in March 2025.

### New build outputs

Builds for all langchain packages now use a bundler based approach instead of using raw typescript outputs. If you were importing files from the `dist/` directory (which is not recommended), you will need to update your imports to use the new module system.

### Legacy code moved to `@langchain/classic`

Legacy functionality outside the focus of standard interfaces and agents has been moved to the [`@langchain/classic`](https://www.npmjs.com/package/@langchain/classic) package. See the [Simplified package](#simplified-package) section for details on what's available in the core `langchain` package and what moved to `@langchain/classic`.

### Removal of deprecated APIs

Methods, functions, and other objects that were already deprecated and slated for removal in 1.0 have been deleted.

<Accordion title="View removed deprecated APIs">

The following deprecated APIs have been removed in v1:

#### Core functionality
- `TraceGroup` - Use LangSmith tracing instead
- `BaseDocumentLoader.loadAndSplit` - Use `.load()` followed by a text splitter
- `RemoteRunnable` - No longer supported

#### Prompts
- `BasePromptTemplate.serialize` and `.deserialize` - Use JSON serialization directly
- `ChatPromptTemplate.fromPromptMessages` - Use `ChatPromptTemplate.fromMessages`

#### Retrievers
- `BaseRetrieverInterface.getRelevantDocuments` - Use `.invoke()` instead

#### Runnables
- `Runnable.bind` - Use `.bindTools()` or other specific binding methods
- `Runnable.map` - Use `.batch()` instead
- `RunnableBatchOptions.maxConcurrency` - Use `maxConcurrency` in the config object

#### Chat models
- `BaseChatModel.predictMessages` - Use `.invoke()` instead
- `BaseChatModel.predict` - Use `.invoke()` instead
- `BaseChatModel.serialize` - Use JSON serialization directly
- `BaseChatModel.callPrompt` - Use `.invoke()` instead
- `BaseChatModel.call` - Use `.invoke()` instead

#### LLMs
- `BaseLLMParams.concurrency` - Use `maxConcurrency` in the config object
- `BaseLLM.call` - Use `.invoke()` instead
- `BaseLLM.predict` - Use `.invoke()` instead
- `BaseLLM.predictMessages` - Use `.invoke()` instead
- `BaseLLM.serialize` - Use JSON serialization directly

#### Streaming
- `createChatMessageChunkEncoderStream` - Use `.stream()` method directly

#### Tracing
- `BaseTracer.runMap` - Use LangSmith tracing APIs
- `getTracingCallbackHandler` - Use LangSmith tracing
- `getTracingV2CallbackHandler` - Use LangSmith tracing
- `LangChainTracerV1` - Use LangSmith tracing

#### Memory and storage
- `BaseListChatMessageHistory.addAIChatMessage` - Use `.addMessage()` with `AIMessage`
- `BaseStoreInterface` - Use specific store implementations

#### Utilities
- `getRuntimeEnvironmentSync` - Use async `getRuntimeEnvironment()`

</Accordion>

