AIREITER

LangChain OpenRouter Setup: Base URLs, Tools, and Fixes

Last Updated: 2026-08-27 02:04:52

For new LangChain apps, use ChatOpenRouter; keep ChatOpenAI with https://openrouter.ai/api/v1 for older projects that cannot be upgraded, and use https://openrouter.ai/api only for the Anthropic Agent SDK path.

The one-minute answer: use the native integration for new LangChain projects

OpenRouter provides the model endpoint and routing layer; LangChain provides the chat-model, chain, and agent abstractions. The current native pairing is langchain-openrouter with ChatOpenRouter in Python, or @langchain/openrouter with ChatOpenRouter in JavaScript and TypeScript.

Your situationUse this pathMain reason
New LangChain Python applangchain-openrouter + ChatOpenRouterProvider routing, metadata, reasoning, tools, and structured-output controls are exposed by the integration
New LangChain JS/TS app@langchain/openrouter + ChatOpenRouterKeeps LangChain’s native message, streaming, and tool interfaces
Existing older LangChain appChatOpenAI + base_url="https://openrouter.ai/api/v1"Smallest compatibility change
Existing app using init_chat_modelLangChain v1 plus langchain-openrouterEnables the model_provider="openrouter" dispatch path
Claude/Anthropic Agent SDK appANTHROPIC_BASE_URL="https://openrouter.ai/api"The Agent SDK uses the Anthropic-compatible route
Next.js app using Vercel AI SDK@openrouter/ai-sdk-provider + createOpenRouter()Uses the AI SDK’s streamText() and tool abstractions

LangChain’s first-party package announcement says the native package preserves OpenRouter-specific behavior that a generic ChatOpenAI wrapper can lose, including reasoning content, structured output, routing metadata, and tracing identity. If those fields do not matter, an existing compatibility setup does not need an immediate rewrite.

Start with the endpoint map, not the framework name

Different SDKs append different request paths to their base URL. The OpenAI-compatible route is https://openrouter.ai/api/v1; the OpenRouter OpenAI SDK guide uses that value. The Anthropic Agent SDK guide instead sets ANTHROPIC_BASE_URL to https://openrouter.ai/api.

Client or SDKBase URL to configureTypical request family
OpenAI Python/JS SDKhttps://openrouter.ai/api/v1OpenAI Chat Completions
Legacy LangChain ChatOpenAIhttps://openrouter.ai/api/v1OpenAI-compatible chat calls
LangChain ChatOpenRouterUsually no custom base URLNative OpenRouter integration
Vercel AI SDK OpenRouter providerUsually no custom base URLProvider package manages the endpoint
Anthropic Agent SDKhttps://openrouter.ai/apiAnthropic-compatible Agent SDK calls

Do not put /chat/completions into an SDK’s base URL unless that SDK explicitly requests a complete endpoint. If an error contains a duplicated path such as /v1/v1, inspect how the SDK combines its base URL with the endpoint path.

For a legacy OpenAI-compatible LangChain client, the minimum working shape is:

import os
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="openai/gpt-4o",
    api_key=os.environ["OPENROUTER_API_KEY"],
    base_url="https://openrouter.ai/api/v1",
)

response = llm.invoke("Explain provider routing in one sentence.")
print(response.content)

Use an OpenRouter API key, not an OpenAI key. OpenRouter’s Quickstart specifies Bearer-token authentication. HTTP-Referer and X-OpenRouter-Title are optional app-attribution headers, not substitutes for Authorization.

LangChain + OpenRouter: current setup

Python: langchain-openrouter

Install the integration and keep the key outside the source file:

pip install -U langchain-openrouter
export OPENROUTER_API_KEY="sk-or-..."

Then instantiate the native chat model. The LangChain Python integration documents model, temperature, max_tokens, and max_retries in this form:

from langchain_openrouter import ChatOpenRouter

model = ChatOpenRouter(
    model="anthropic/claude-sonnet-4.5",
    temperature=0,
    max_tokens=1024,
    max_retries=2,
)

response = model.invoke([
    ("system", "You are a concise technical assistant."),
    ("human", "What does OpenRouter add to a LangChain application?"),
])

print(response.content)

OpenRouter model names generally use provider/model, such as anthropic/claude-sonnet-4.5, openai/gpt-4o, or deepseek/deepseek-r1. Check the OpenRouter Models API before deployment because model IDs, aliases, variants, availability, and supported parameters can change.

JavaScript and TypeScript: @langchain/openrouter

Install the integration and LangChain core:

npm install @langchain/openrouter @langchain/core

The object-style constructor in the LangChain JavaScript integration is explicit and easy to compare with the Python example:

import { ChatOpenRouter } from "@langchain/openrouter";

const model = new ChatOpenRouter({
  model: "anthropic/claude-sonnet-4.5",
  temperature: 0,
  maxTokens: 1024,
});

const response = await model.invoke([
  { role: "system", content: "You are a concise technical assistant." },
  { role: "user", content: "Explain the provider/model format." },
]);

console.log(response.content);

Python uses max_tokens; the JavaScript example uses maxTokens. Both integrations read OPENROUTER_API_KEY by default. If a model appears in the catalog but fails in code, check the exact slug, variant suffix, and supported request parameters.

init_chat_model and the version trap

A common error is:

ValueError: Unsupported model_provider='openrouter'.

A LangChain Forum troubleshooting thread attributes this case to a pre-v1 LangChain installation being used with v1 documentation. The answer recommends upgrading the framework and partner package together:

pip install -U "langchain>=1" langchain-openrouter

Then use the generic initializer:

import os
from langchain.chat_models import init_chat_model

os.environ["OPENROUTER_API_KEY"] = "sk-or-..."

model = init_chat_model(
    "anthropic/claude-sonnet-4.5",
    model_provider="openrouter",
)

print(model.invoke("Say hello in one sentence.").content)

If the project cannot move to LangChain v1, install langchain-openrouter and construct ChatOpenRouter directly. That bypasses the generic provider registry and makes the OpenRouter dependency explicit.

Migrate from ChatOpenAI to ChatOpenRouter

The code change is small, but the integration boundary changes from a generic OpenAI adapter to a provider-specific class:

# Before: OpenAI-compatible workaround
from langchain_openai import ChatOpenAI

model = ChatOpenAI(
    model="anthropic/claude-sonnet-4.5",
    base_url="https://openrouter.ai/api/v1",
    api_key=os.environ["OPENROUTER_API_KEY"],
)

# After: native OpenRouter integration
from langchain_openrouter import ChatOpenRouter

model = ChatOpenRouter(model="anthropic/claude-sonnet-4.5")

Keep the same prompt and chain call sites first. Add provider routing, reasoning, structured output, or metadata handling only after the basic native call succeeds.

Streaming, callbacks, tools, and structured output in LangChain

model.stream() yields message chunks for simple text consumption. stream_events(..., version="v3") exposes lifecycle events that are better suited to callback-style logging, UI state, and tracing; both surfaces are documented in the LangChain Python integration.

model = ChatOpenRouter(model="openai/gpt-4o")

for event in model.stream_events(
    "Explain why OpenRouter has provider fallbacks.",
    version="v3",
):
    if event["event"] == "on_chat_model_stream":
        chunk = event["data"]["chunk"]
        print(chunk.text, end="", flush=True)

Use a callback when the application needs start, token, end, or error notifications. Use stream() when it only needs model chunks. Read usage and response metadata from the completed response or aggregated stream result, as shown in the linked integration documentation, rather than from an arbitrary first chunk.

Tool calling and provider routing

OpenRouter documents a normalized tool-calling flow: the model proposes a tool call, the application executes it, and the application sends the tool result back. ChatOpenRouter.bind_tools() accepts LangChain tools, Python functions, Pydantic classes, and dictionary schemas.

from pydantic import BaseModel, Field

class GetWeather(BaseModel):
    """Get current weather for a city."""

    location: str = Field(
        description="City and state, for example San Francisco, CA"
    )

model_with_tools = model.bind_tools(
    [GetWeather],
    strict=True,
)

result = model_with_tools.invoke("What is the weather in San Francisco?")
print(result.tool_calls)

The first call only returns the model’s proposed tool calls. A complete manual round trip creates a ToolMessage for each call before asking the model for the final answer:

from langchain_core.messages import HumanMessage, ToolMessage
from langchain_core.tools import tool

@tool
def get_weather(location: str) -> str:
    """Get the current weather in a location."""
    return f"Sunny in {location}."

model_with_tools = model.bind_tools([get_weather])
messages = [HumanMessage("What is the weather in San Francisco?")]
ai_msg = model_with_tools.invoke(messages)
messages.append(ai_msg)

for call in ai_msg.tool_calls:
    tool_output = get_weather.invoke(call["args"])
    messages.append(
        ToolMessage(
            content=tool_output,
            tool_call_id=call["id"],
        )
    )

final_msg = model_with_tools.invoke(messages)
print(final_msg.content)

Provider routing belongs in the OpenRouter-specific model configuration described by the LangChain integration and OpenRouter provider-routing guide:

routed_model = ChatOpenRouter(
    model="anthropic/claude-sonnet-4.5",
    openrouter_provider={
        "order": ["Anthropic", "Google"],
        "allow_fallbacks": True,
        "require_parameters": True,
        "data_collection": "deny",
    },
)

order expresses preference. only restricts eligible providers, and ignore excludes providers. allow_fallbacks=True can send a request beyond the preferred order. require_parameters=True keeps a request away from providers that do not support every parameter in the payload. These settings trade availability against provider control.

For older ChatOpenAI clients, OpenRouter-specific fields may need to travel through extra_body or model_kwargs, depending on the client version. One real user asked in a LangChain/OpenRouter Reddit thread:

“Wondering, doesn't openrouter support the openai API spec such that the openai client works?” — u/tuxedo0.

The dedicated package makes the OpenRouter integration boundary explicit instead of requiring a generic OpenAI wrapper to expose every provider-specific field.

Structured output: check the endpoint, not only the model name

The native integration exposes typed output through with_structured_output(). The LangChain Python documentation shows the native JSON Schema method:

from pydantic import BaseModel, Field

class Movie(BaseModel):
    title: str
    year: int
    director: str
    rating: float = Field(description="Rating from 0 to 10")

structured_model = ChatOpenRouter(
    model="openai/gpt-5.5",
).with_structured_output(
    Movie,
    method="json_schema",
    strict=True,
)

movie = structured_model.invoke("Give details about the movie Inception.")
print(movie)

The integration also documents function_calling; strict is not supported with json_mode. OpenRouter’s structured-output behavior can vary by serving endpoint, so a model name alone is not a guarantee. The OpenRouter structured-output guide explains why provider capability checks, require_parameters, and client-side validation matter.

Vercel AI SDK + OpenRouter

For a Next.js or TypeScript application using Vercel’s AI SDK, install the OpenRouter provider:

npm install @openrouter/ai-sdk-provider ai zod

This compact example covers text streaming and a local Zod tool. The OpenRouter Vercel AI SDK guide uses the same createOpenRouter() and openrouter("provider/model") pattern.

import { createOpenRouter } from "@openrouter/ai-sdk-provider";
import { isStepCount, streamText, tool } from "ai";
import { z } from "zod";

const openrouter = createOpenRouter({
  apiKey: process.env.OPENROUTER_API_KEY,
});

const result = streamText({
  model: openrouter("openai/gpt-4o"),
  tools: {
    getWeather: tool({
      description: "Get the current weather in a location",
      inputSchema: z.object({
        location: z.string().describe("City and state"),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 18,
        unit: "celsius",
      }),
    }),
  },
  stopWhen: isStepCount(3),
  prompt: "What is the weather in San Francisco?",
});

for await (const textPart of result.textStream) {
  process.stdout.write(textPart);
}

textStream exposes text deltas. Use fullStream when the application needs reasoning, tool calls, tool results, step boundaries, or finish events; the AI SDK streamText() reference documents those result surfaces. The weather executor above is a mock, not a live API call.

A public OpenRouter provider issue reported perceived buffering in one multi-chunk tool-call path with @openrouter/ai-sdk-provider 2.2.3, AI SDK 6.0.81, Node.js 24, and openai/gpt-5.2. The report described a missing tool-input-end event. Treat it as a version-specific issue, not a universal behavior claim. If it reproduces, the issue reports this workaround:

import { createOpenAI } from "@ai-sdk/openai";

const openrouter = createOpenAI({
  apiKey: process.env.OPENROUTER_API_KEY,
  baseURL: "https://openrouter.ai/api/v1",
});

const model = openrouter.chat("openai/gpt-5.2");

Install @ai-sdk/openai separately and pin the versions you test. This workaround changes the provider adapter, so verify which OpenRouter-specific options remain available.

Anthropic Agent SDK + OpenRouter

The Anthropic Agent SDK is a separate integration boundary. OpenRouter’s official Agent SDK guide says the SDK uses Claude Code as its runtime and accepts this environment configuration:

export ANTHROPIC_BASE_URL="https://openrouter.ai/api"
export ANTHROPIC_AUTH_TOKEN="$OPENROUTER_API_KEY"
export ANTHROPIC_API_KEY=""

The empty ANTHROPIC_API_KEY is intentional. The OpenRouter key goes in ANTHROPIC_AUTH_TOKEN, and the /api path should not be changed to the OpenAI-compatible /api/v1 path for this Agent SDK setup.

The guide’s TypeScript shape is:

npm install @anthropic-ai/claude-agent-sdk
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Find and explain the bug in auth.py.",
  options: {
    allowedTools: ["Read"],
  },
})) {
  if (message.type === "assistant") {
    console.log(message.message.content);
  }
}

OpenRouter supplies the model endpoint; the Agent SDK retains the agent loop and tool runtime. Test tools, streaming, context behavior, and model-specific options on the exact route you will deploy because Anthropic-native feature parity is not automatic.

401, 404, and “it works in curl but not in the SDK”

Identify the failing layer before changing models. Authentication, URL construction, model lookup, provider filtering, and parameter validation require different fixes.

SymptomLikely layerFirst check
401 Missing Authentication headerHeader or environment loadingConfirm Authorization: Bearer ...; test /api/v1/key
401 Unauthorized with a key presentWrong, revoked, or empty keyConfirm the process sees OPENROUTER_API_KEY; use an OpenRouter key
404 with a malformed pathBase URL assemblyOpenAI-compatible clients use /api/v1; Agent SDK uses /api; inspect duplicated /v1
404 Invalid model or model_not_foundModel identifierQuery /api/v1/models and use the returned id or a documented alias
404 No allowed providers are availableProvider filters or endpoint availabilityInspect only, ignore, max_price, require_parameters, and data-policy settings
400 for top_k, min_p, tools, or JSON SchemaCapability mismatchCheck supported_parameters; require compatible providers when routing
curl works but framework code failsSDK path, casing, serialization, or dropped headersCompare the final URL, auth header, model ID, and JSON body

OpenRouter documents GET /api/v1/key as an authenticated API-key check. Run it before debugging LangChain:

curl -sS https://openrouter.ai/api/v1/key \
  -H "Authorization: Bearer $OPENROUTER_API_KEY"

Then verify model discovery:

curl -sS https://openrouter.ai/api/v1/models \
  -H "Authorization: Bearer $OPENROUTER_API_KEY"

The Models API can filter for tool-capable models before you add tools:

curl -sS \
  "https://openrouter.ai/api/v1/models?supported_parameters=tools" \
  -H "Authorization: Bearer $OPENROUTER_API_KEY"

A 404 can also mean that the model exists but no eligible provider remains after request filters. To inspect routing decisions, enable the opt-in X-OpenRouter-Metadata: enabled header; the Router Metadata documentation describes the resulting openrouter_metadata object and its error behavior.

In development, compare the generated request after redacting the key: base URL, final path, Authorization presence, exact model ID, and provider-specific body fields. For a focused authentication follow-up, see AIReiter’s invalid API-key and 401/403 troubleshooting guide.

A migration checklist for production

  1. Align package generations. Keep langchain, langchain-core, and langchain-openrouter or @langchain/openrouter compatible instead of copying versions from an old tutorial.
  2. Resolve canonical model IDs. Use /api/v1/models or the OpenRouter model catalog; treat aliases as a deliberate behavior change.
  3. Choose the fallback boundary. Provider fallback improves availability. Model fallback can change quality, latency, capabilities, and cost. Record the final model returned by OpenRouter.
  4. Require capabilities when they matter. Use provider capability filters for tool and structured-output requests; do not treat tool support as proof of strict JSON Schema support.
  5. Validate application outputs. Pydantic, Zod, or another validator should check tool arguments and structured responses before downstream code acts on them.
  6. Log useful metadata. Retain request ID, model, usage, finish reason, and serving-provider information where your privacy policy permits. The LangChain integration also documents session_id for grouping requests and trace for per-request metadata.
  7. Test both streaming modes. A successful non-streaming call does not prove that tool-call chunks or UI streaming behave correctly.
  8. Exercise provider restrictions. If you use only, ignore, data-collection filters, or a price ceiling, test the no-eligible-provider failure path.
  9. Keep credentials server-side. Store OPENROUTER_API_KEY in the server environment or a secret manager, never in a browser bundle or committed .env file.

The dependable order is: verify the key, verify the model ID, make one plain-text call, then add routing, tools, structured output, and multi-step streaming.