新しくLangChainアプリを作るなら、ChatOpenRouterを使うのが基本です。アップグレードできない既存プロジェクトでは、https://openrouter.ai/api/v1を指定したChatOpenAIを使い続けても構いません。https://openrouter.ai/apiを使うのは、Anthropic Agent SDK経由の場合だけです。
まず結論:新規のLangChainプロジェクトではネイティブ連携を選ぶ
OpenRouterはモデルへの接続とプロバイダーのルーティングを担当し、LangChainはチャットモデル、チェーン、エージェントの抽象化を提供します。現在の推奨構成は、Pythonならlangchain-openrouterとChatOpenRouter、JavaScript/TypeScriptなら@langchain/openrouterとChatOpenRouterの組み合わせです。
| 状況 | 選ぶ構成 | 主な理由 |
|---|---|---|
| 新規のLangChain Pythonアプリ | langchain-openrouter + ChatOpenRouter | プロバイダーのルーティング、メタデータ、推論、ツール、構造化出力を制御できる |
| 新規のLangChain JS/TSアプリ | @langchain/openrouter + ChatOpenRouter | LangChain標準のメッセージ、ストリーミング、ツールインターフェースを維持できる |
| 既存の古いLangChainアプリ | ChatOpenAI + base_url="https://openrouter.ai/api/v1" | 互換性を保ったまま変更を最小限に抑えられる |
init_chat_modelを使う既存アプリ | LangChain v1 + langchain-openrouter | model_provider="openrouter"による振り分けが使える |
| Claude/Anthropic Agent SDKアプリ | ANTHROPIC_BASE_URL="https://openrouter.ai/api" | Agent SDKがAnthropic互換のルートを使うため |
| Vercel AI SDKを使うNext.jsアプリ | @openrouter/ai-sdk-provider + createOpenRouter() | AI SDKのstreamText()とツール抽象化を利用できる |
LangChainの公式パッケージ発表によると、ネイティブパッケージでは、汎用的なChatOpenAIラッパーで失われる可能性があるOpenRouter固有の情報も扱えます。対象となるのは、推論内容、構造化出力、ルーティングメタデータ、トレーシング時の識別情報などです。これらを使わないのであれば、既存の互換構成を急いで書き換える必要はありません。
フレームワーク名より先に、エンドポイントの対応関係を確認する
SDKによって、ベースURLの後ろに付加するリクエストパスが異なります。OpenAI互換ルートはhttps://openrouter.ai/api/v1で、OpenRouterのOpenAI SDKガイドでもこの値を使っています。一方、Anthropic Agent SDKガイドでは、ANTHROPIC_BASE_URLにhttps://openrouter.ai/apiを指定します。
| クライアント/SDK | 設定するベースURL | 通常使われるリクエスト系統 |
|---|---|---|
| OpenAI Python/JS SDK | https://openrouter.ai/api/v1 | OpenAI Chat Completions |
従来のLangChain ChatOpenAI | https://openrouter.ai/api/v1 | OpenAI互換のチャット呼び出し |
LangChain ChatOpenRouter | 通常はカスタムベースURL不要 | OpenRouterネイティブ連携 |
| Vercel AI SDKのOpenRouterプロバイダー | 通常はカスタムベースURL不要 | プロバイダーパッケージがエンドポイントを管理 |
| Anthropic Agent SDK | https://openrouter.ai/api | Anthropic互換のAgent SDK呼び出し |
SDKが完全なエンドポイントを要求している場合を除き、ベースURLに/chat/completionsまで含めないでください。/v1/v1のようにパスが重複したエラーが出る場合は、SDKがベースURLとエンドポイントパスをどう結合しているかを確認します。
従来のOpenAI互換LangChainクライアントで最低限必要な構成は次のとおりです。
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)
使うのはOpenAIのキーではなく、OpenRouterのAPIキーです。OpenRouterのクイックスタートでは、Bearerトークンによる認証を指定しています。HTTP-RefererとX-OpenRouter-Titleはアプリの識別に使える任意のヘッダーであり、Authorizationの代わりにはなりません。
LangChainとOpenRouterを現在の構成で連携する
Python:langchain-openrouter
連携パッケージをインストールし、APIキーはソースコードの外で管理します。
pip install -U langchain-openrouter
export OPENROUTER_API_KEY="sk-or-..."
続いて、ネイティブのチャットモデルを初期化します。LangChain Python連携のドキュメントでは、model、temperature、max_tokens、max_retriesを次のように指定しています。
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のモデル名は通常、provider/model形式です。たとえばanthropic/claude-sonnet-4.5、openai/gpt-4o、deepseek/deepseek-r1などがあります。モデルID、エイリアス、バリアント、提供状況、対応パラメーターは変わる可能性があるため、本番投入前にOpenRouter Models APIで確認してください。
JavaScript/TypeScript:@langchain/openrouter
連携パッケージとLangChain coreをインストールします。
npm install @langchain/openrouter @langchain/core
LangChain JavaScript連携にあるオブジェクト形式のコンストラクターは、Pythonの例とも比較しやすく、設定内容が明確です。
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ではmax_tokens、JavaScriptの例ではmaxTokensを使います。どちらの連携でも、デフォルトでOPENROUTER_API_KEYを読み込みます。カタログには存在するモデルがコード上で動かない場合は、正確なスラッグ、バリアントのサフィックス、リクエストパラメーターの対応状況を確認してください。
init_chat_modelとバージョンの落とし穴
よくあるエラーは次のとおりです。
ValueError: Unsupported model_provider='openrouter'.
LangChain Forumのトラブルシューティングスレッドでは、このケースの原因として、v1向けドキュメントをv1以前のLangChainで実行している可能性を挙げています。対処法として、フレームワーク本体とパートナーパッケージをまとめてアップグレードする方法が推奨されています。
pip install -U "langchain>=1" langchain-openrouter
そのうえで、汎用イニシャライザーを使います。
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)
LangChain v1へ移行できないプロジェクトでは、langchain-openrouterをインストールし、ChatOpenRouterを直接生成してください。汎用プロバイダーレジストリを経由しないため、OpenRouterへの依存関係も明確になります。
ChatOpenAIからChatOpenRouterへ移行する
コードの変更自体は小さなものです。ただし、汎用的なOpenAIアダプターから、プロバイダー専用クラスへ連携の境界が変わります。
# 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")
まずはプロンプトやチェーンの呼び出し箇所を変えずに、基本的なネイティブ呼び出しが成功することを確認します。プロバイダーのルーティング、推論、構造化出力、メタデータの処理は、その後に追加するのが安全です。
LangChainでストリーミング、コールバック、ツール、構造化出力を使う
単純にテキストを処理するなら、model.stream()でメッセージチャンクを順番に取得できます。コールバック形式のログ、UIの状態管理、トレーシングには、ライフサイクルイベントを取得できるstream_events(..., version="v3")が向いています。どちらの方法もLangChain Python連携のドキュメントで説明されています。
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)
開始、トークン、終了、エラーを通知したいアプリではコールバックを使います。モデルのチャンクだけが必要ならstream()で十分です。使用量やレスポンスのメタデータは、任意の最初のチャンクから読むのではなく、完了したレスポンスまたは集約後のストリーム結果から取得してください。
ツール呼び出しとプロバイダーのルーティング
OpenRouterは正規化されたツール呼び出しの流れを説明しています。モデルがツール呼び出しを提案し、アプリケーションが実行し、その結果をモデルへ返すという流れです。ChatOpenRouter.bind_tools()は、LangChainツール、Python関数、Pydanticクラス、辞書形式のスキーマに対応しています。
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)
最初の呼び出しで返ってくるのは、モデルが提案したツール呼び出しだけです。手動で一連の処理を行う場合は、各呼び出しに対応するToolMessageを作成してから、最終回答をモデルに求めます。
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)
プロバイダーのルーティングは、LangChain連携のドキュメントとOpenRouterのプロバイダールーティングガイドで説明されている、OpenRouter固有のモデル設定に記述します。
routed_model = ChatOpenRouter(
model="anthropic/claude-sonnet-4.5",
openrouter_provider={
"order": ["Anthropic", "Google"],
"allow_fallbacks": True,
"require_parameters": True,
"data_collection": "deny",
},
)
orderは優先順位を表します。onlyを使うと対象プロバイダーを限定でき、ignoreを使うと特定のプロバイダーを除外できます。allow_fallbacks=Trueでは、優先順に指定したプロバイダー以外へリクエストが送られる場合があります。require_parameters=Trueを指定すると、ペイロード内のすべてのパラメーターに対応していないプロバイダーは選ばれません。これらの設定は、可用性とプロバイダーの制御範囲を引き換えにするものです。
古いChatOpenAIクライアントでは、OpenRouter固有のフィールドをextra_bodyまたはmodel_kwargs経由で渡す必要がある場合があります。どちらを使うかはクライアントのバージョンによって異なります。あるユーザーはLangChain/OpenRouterのRedditスレッドで次のように質問しています。
「OpenRouterはOpenAI API仕様に対応しているのだから、OpenAIクライアントが動くのでは?」— u/tuxedo0
専用パッケージを使えば、汎用的なOpenAIラッパーにプロバイダー固有のフィールドをすべて公開させる必要がなくなり、OpenRouterとの連携境界を明示できます。
構造化出力では、モデル名だけでなくエンドポイントを確認する
ネイティブ連携では、with_structured_output()によって型付きの出力を扱えます。LangChain Pythonドキュメントでは、ネイティブJSON Schema方式を次のように示しています。
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)
連携機能ではfunction_callingもサポートされています。ただし、json_modeではstrictを利用できません。OpenRouterの構造化出力は、配信エンドポイントによって挙動が変わる場合があります。そのため、モデル名だけで対応を保証することはできません。OpenRouterの構造化出力ガイドでは、プロバイダーの対応状況の確認、require_parameters、クライアント側のバリデーションが重要な理由を説明しています。
Vercel AI SDKとOpenRouterを組み合わせる
VercelのAI SDKを使うNext.jsまたはTypeScriptアプリでは、OpenRouterプロバイダーをインストールします。
npm install @openrouter/ai-sdk-provider ai zod
次のコンパクトな例では、テキストストリーミングとローカルで実行するZodツールを扱います。OpenRouterのVercel AI SDKガイドでも、同じcreateOpenRouter()とopenrouter("provider/model")の形式を使っています。
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ではテキストの差分を取得できます。推論、ツール呼び出し、ツールの実行結果、ステップの境界、終了イベントまで扱う必要があるなら、fullStreamを使います。これらの結果インターフェースについては、AI SDKのstreamText()リファレンスにまとまっています。上の天気情報の実行部分はモックであり、実際のAPIは呼び出していません。
公開されているOpenRouterプロバイダーのIssueでは、@openrouter/ai-sdk-provider 2.2.3、AI SDK 6.0.81、Node.js 24、openai/gpt-5.2の組み合わせで、複数チャンクに分かれたツール呼び出しの一部にバッファリングのような挙動が報告されています。報告内容では、tool-input-endイベントが発生しませんでした。これはバージョン固有の問題として扱うべきで、常に起きる現象だと考えてはいけません。再現する場合、Issueでは次の回避策が示されています。
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");
@ai-sdk/openaiは別途インストールし、テストしたバージョンを固定してください。この回避策ではプロバイダーアダプターが変わるため、OpenRouter固有のオプションがどこまで利用できるかを確認しましょう。
Anthropic Agent SDKとOpenRouterを連携する
Anthropic Agent SDKは、LangChainやVercel AI SDKとは別の連携境界を持ちます。OpenRouterの公式Agent SDKガイドによると、SDKはClaude Codeをランタイムとして使い、次の環境設定を受け付けます。
export ANTHROPIC_BASE_URL="https://openrouter.ai/api"
export ANTHROPIC_AUTH_TOKEN="$OPENROUTER_API_KEY"
export ANTHROPIC_API_KEY=""
ANTHROPIC_API_KEYを空にするのは意図的な設定です。OpenRouterのキーはANTHROPIC_AUTH_TOKENに入れます。また、このAgent SDK構成では、/apiをOpenAI互換の/api/v1へ変更しないでください。
ガイドにあるTypeScriptの基本形は次のとおりです。
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がモデルのエンドポイントを提供し、Agent SDKがエージェントループとツールの実行環境を担います。Anthropic本来の機能と同じように動くとは限らないため、本番で使う正確なルート上で、ツール、ストリーミング、コンテキストの挙動、モデル固有オプションをテストしてください。
401、404、「curlでは動くのにSDKでは動かない」場合の切り分け
モデルを変更する前に、どの層で失敗しているかを特定しましょう。認証、URLの組み立て、モデル検索、プロバイダーの絞り込み、パラメーターの検証では、必要な対処がそれぞれ異なります。
| 症状 | 原因になりやすい層 | 最初に確認すること |
|---|---|---|
401 Missing Authentication header | ヘッダーまたは環境変数の読み込み | Authorization: Bearer ...があるか確認し、/api/v1/keyをテストする |
キーがあるのに401 Unauthorized | キーの誤り、失効、空文字 | プロセスがOPENROUTER_API_KEYを認識しているか、OpenRouterのキーを使っているか確認する |
パスが不正な404 | ベースURLの組み立て | OpenAI互換クライアントは/api/v1、Agent SDKは/apiを使う。/v1の重複も確認する |
404 Invalid modelまたはmodel_not_found | モデル識別子 | /api/v1/modelsを問い合わせ、返されたidまたは文書化されたエイリアスを使う |
404 No allowed providers are available | プロバイダーの制限またはエンドポイントの提供状況 | only、ignore、max_price、require_parameters、データポリシーの設定を確認する |
top_k、min_p、ツール、JSON Schemaで400 | 機能対応の不一致 | supported_parametersを確認し、ルーティング時は対応プロバイダーを必須にする |
curlでは動くのにフレームワークのコードで失敗する | SDKのパス、大文字・小文字、シリアライズ、ヘッダーの欠落 | 最終URL、認証ヘッダー、モデルID、JSONボディを比較する |
OpenRouterでは、認証済みAPIキーの確認用としてGET /api/v1/keyが用意されています。LangChainのデバッグを始める前に、まず次のコマンドを実行してください。
curl -sS https://openrouter.ai/api/v1/key \
-H "Authorization: Bearer $OPENROUTER_API_KEY"
続いて、モデルの検出も確認します。
curl -sS https://openrouter.ai/api/v1/models \
-H "Authorization: Bearer $OPENROUTER_API_KEY"
Models APIでは、ツールに対応したモデルを絞り込んでから、ツールを追加することもできます。
curl -sS \
"https://openrouter.ai/api/v1/models?supported_parameters=tools" \
-H "Authorization: Bearer $OPENROUTER_API_KEY"
404は、モデル自体は存在していても、リクエストのフィルター適用後に利用可能なプロバイダーが残っていないことを意味する場合があります。ルーティングの判断を確認するには、オプトインのX-OpenRouter-Metadata: enabledヘッダーを有効にしてください。Router Metadataのドキュメントでは、返されるopenrouter_metadataオブジェクトとエラー時の挙動を説明しています。
開発中は、キーを伏せたうえで生成されたリクエストを比較します。確認対象は、ベースURL、最終パス、Authorizationヘッダーの有無、正確なモデルID、プロバイダー固有のボディフィールドです。認証に絞った追加の確認方法は、AIReiterの無効なAPIキーと401/403エラーのトラブルシューティングガイドも参照してください。
本番移行前に確認したいチェックリスト
- パッケージの世代をそろえる。古いチュートリアルからバージョン番号をそのままコピーせず、
langchain、langchain-core、langchain-openrouterまたは@langchain/openrouterの互換性を保つ。 - 正規のモデルIDを解決する。
/api/v1/modelsまたはOpenRouterのモデルカタログを使い、エイリアスは意図的な挙動変更として扱う。 - フォールバックの境界を決める。プロバイダーのフォールバックは可用性を高めます。一方、モデルのフォールバックでは品質、レイテンシー、機能、コストが変わる可能性があります。OpenRouterから返された最終的な
modelを記録する。 - 必要な機能を必須条件にする。ツールや構造化出力を使う場合は、プロバイダーの機能フィルターを利用する。ツール対応だけを根拠に、厳密なJSON Schemaにも対応していると判断しない。
- アプリの出力を検証する。Pydantic、Zod、その他のバリデーターで、ツール引数や構造化レスポンスを検証してから後続処理へ渡す。
- 役立つメタデータをログに残す。プライバシーポリシーが許す範囲で、リクエストID、モデル、使用量、終了理由、配信プロバイダーの情報を保持する。LangChain連携では、リクエストをグループ化する
session_idと、リクエスト単位のメタデータに使えるtraceも説明されています。 - 2種類のストリーミングをテストする。非ストリーミング呼び出しが成功しても、ツール呼び出しのチャンクやUI向けストリーミングが正しく動くとは限らない。
- プロバイダー制限の動作を確認する。
only、ignore、データ収集フィルター、価格上限を使う場合は、利用可能なプロバイダーが存在しないときの失敗経路もテストする。 - 認証情報はサーバー側で管理する。
OPENROUTER_API_KEYはサーバー環境またはシークレットマネージャーに保存し、ブラウザバンドルやコミット済みの.envファイルには絶対に含めない。
安定して進めるなら、キーの確認、モデルIDの確認、プレーンテキストによる単純な呼び出しの順に検証し、その後でルーティング、ツール、構造化出力、複数ステップのストリーミングを追加します。