LangChain + OpenRouter:一条 API 调用 400+ 大模型的实战指南

A Practical Guide: Calling 400+ LLMs Through a Single LangChain Integration


如果你正在用 LangChain 构建 AI 应用,迟早会面临一个选择题:绑定 OpenAI 的 GPT 系列?接入 Anthropic 的 Claude?还是给 Google 的 Gemini 留一条后路?每个选择都意味着一套独立的 API 密钥、不同的参数格式、各自的故障模式。更麻烦的是,当你想从 GPT-4 切换到 Claude,或者给国内用户配一个 DeepSeek 的备选方案时,你的链条(chain)里几乎每一行代码都要跟着改。

OpenRouter 的解法很直接:一个 OpenAI 兼容的 API 端点,背后挂着 400 多个模型、70 多家提供商。你改一个字符串,模型就换了,而你的 prompts、工具定义、输出解析逻辑完全不用动。LangChain 官方现在也有了自己的 ChatOpenRouter 封装包,不再是以前那种”用 ChatOpenAIbase_url“的野路子。

这篇文章不讲虚的,只讲怎么在真实项目里把这套东西跑起来,以及那些官方文档不会告诉你的细节。


为什么要在 LangChain 里接 OpenRouter?

Why OpenRouter Instead of a Single Provider?

核心问题:我已经在用 LangChain 了,为什么还要在中间加一层 OpenRouter?

答案分三层。第一层是模型选择的自由度。OpenRouter 的模型目录里有 400 多个选项,从 OpenAI 的 GPT-5 系列到 Anthropic 的 Claude Sonnet 4.5,从 Google 的 Gemini 到 Meta 的 Llama,再到国内的 DeepSeek、Qwen,全部走同一个端点。你的 model 参数只是一个 provider/model 格式的字符串,比如 anthropic/claude-sonnet-4.5。想换模型?改这个字符串就行,prompt 不用重写,工具定义不用改,甚至连 token 计费的返回格式都保持一致。

第二层是路由层的隐性价值。OpenRouter 不只是个 API 聚合器,它自带负载均衡和故障转移。当你调用一个模型时,它会在多个提供该模型的服务商之间做价格负载均衡,自动避开最近 30 秒内出过故障的节点。如果某个提供商挂了,请求会自动漂移到下一个可用的节点,你的应用代码里连 try-except 都不用写。更关键的是——没成功的请求不收钱。

第三层是LangChain 的原生支持。以前接 OpenRouter 要靠 ChatOpenAIbase_url 覆盖,参数透传全靠 model_kwargs,结构化输出和工具调用经常踩坑。现在 PyPI 上有 langchain-openrouter,npm 上有 @langchain/openrouter,是官方维护的封装包,类型提示、参数校验、文档都齐全。beta 阶段更新快,但功能已经完整可用。

我最早接 OpenRouter 是在一个客服工单分类的项目里。当时客户要求同时测试 Claude 和 GPT-4 的效果,如果用原生 SDK,我得维护两套调用逻辑。切到 OpenRouter 之后,我把模型名改成变量,跑 A/B 测试只需要改配置文件里的一行。后来生产环境上线,某个 Claude 的提供商凌晨挂了两次,我早上看日志才发现——应用自己扛过去了,一条告警都没触发。这就是路由层的意义,它不是锦上添花,是兜底。


五分钟跑通:安装、认证、第一次调用

Quickstart: Install, Authenticate, and Invoke in 5 Minutes

核心问题:最快需要几步才能让 LangChain 通过 OpenRouter 调通一个模型?

三步:装包、配密钥、写代码。

Step 1: 安装与认证

Python 用户:

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

TypeScript 用户:

npm install @langchain/openrouter

-U 这个参数很重要。langchain-openrouter 目前处于 beta,迭代速度不慢,锁旧版本容易遇到兼容性问题。密钥在 openrouter.ai/settings/keys 生成,格式是 sk-or- 开头。ChatOpenRouter 会自动读取环境变量 OPENROUTER_API_KEY,当然你也可以在构造函数里显式传 api_key,如果你用 Vault 或者 AWS Secrets Manager 管理密钥的话。

Step 2: Python 调用

from langchain_openrouter import ChatOpenRouter

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

response = model.invoke("Summarize this support ticket in one sentence.")
print(response.content)

temperaturemax_tokensmax_retries 的语义和 LangChain 里其他任何 chat model 完全一致。唯一的 OpenRouter 专属参数是 model,它必须是 provider/model 的 slug 格式。

想先确认密钥和模型是否可用,可以直接 curl:

curl https://openrouter.ai/api/v1/chat/completions \
  -H "Authorization: Bearer $OPENROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-4.5",
    "messages": [{"role": "user", "content": "Summarize this support ticket in one sentence."}]
  }'

端点返回的是标准 OpenAI Chat Completion 格式,ChatOpenRouter 本质上就是对这个端点的类型化封装。

Step 3: TypeScript 版本

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

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

const response = await model.invoke(
  "Summarize this support ticket in one sentence.",
);
console.log(response.content);

TypeScript 的构造函数签名略有不同,第一个位置参数是模型名,第二个是配置对象。行为和 Python 版本一致。

一个小坑:TypeScript 版的 @langchain/openrouter 版本号不一定和 Python 版同步。我遇到过 Python 包已经支持 reasoning 参数了,npm 包还没跟上。如果你需要用到后面讲到的进阶功能,先查一下 npm 上最新版的 release notes。


模型选择:provider/model 字符串是唯一的”开关”

Picking a Model: The Only Thing You Change Is a String

核心问题:怎么知道该填什么模型名?换了模型之后我的链条会坏吗?

模型目录在 openrouter.ai/models,这是唯一可信的来源。页面上会显示每个模型的提供商列表、每百万 token 的输入/输出价格、支持的功能(工具调用、JSON Schema、多模态等)。文档里的模型名只是示例,实际使用时务必以目录为准。

provider/model 这个格式的妙处在于零侵入切换。今天你的链条跑在 anthropic/claude-sonnet-4.5 上,明天产品经理说”试试 GPT-5-mini 看效果有没有差”,你只需要改一行:

model = ChatOpenRouter(
    model="openai/gpt-5-mini",  # 就改这里
    temperature=0,
    max_tokens=1024,
)

Prompts、工具定义、输出解析器、记忆模块——全部原地不动。这种设计在需要快速做模型 A/B 测试或者按成本动态降级的场景里特别实用。

LangChain 的 agent 还有个更短的写法:

from langchain.agents import create_agent

agent = create_agent(model="openrouter:anthropic/claude-sonnet-4.5")

openrouter: 前缀会让 create_agent 自动解析到 ChatOpenRouter 类。适合那种”只想快速搭个原型,不想显式 import”的场景。


流式输出:用户体验 vs 账单真相

Streaming: What It Costs and What It Doesn’t

核心问题:流式输出(streaming)会不会更贵?怎么在 LangChain 里实现?

不会。流式和非流式的 token 单价完全一样。你用 streaming 是为了让用户看到”字一个个蹦出来”的实时感,不是为了省钱。

LangChain 里用 stream_events(或异步版的 astream_events):

for event in model.stream_events(
    "Explain provider routing in three sentences.",
    version="v3"
):
    if event["event"] == "on_chat_model_stream":
        print(event["data"]["chunk"].text, end="", flush=True)

version="v3" 必须传,否则事件 schema 可能不对。异步版本:

async for event in model.astream_events(
    "Explain provider routing in three sentences.",
    version="v3"
):
    if event["event"] == "on_chat_model_stream":
        print(event["data"]["chunk"].text, end="", flush=True)

流结束后,最终聚合消息上会有 usage_metadata,包含完整的 token 计数。不需要为了拿用量信息再发一次非流式请求。

我在一个实时写作助手项目里用过这个。用户输入一个标题,模型开始流式生成大纲。起初我担心频繁的事件回调会拖慢前端,实际上 LangChain 的 stream_events 开销很低,瓶颈在模型本身的 TTF(Time To First Token)。OpenRouter 的 sort="latency" 路由策略在这里能派上用场,后面会讲。


工具调用与结构化输出:从”返回文本”到”返回数据”

Tool Calling and Structured Output: From Text to Data

核心问题:OpenRouter 接进来的模型,能不能像原生 Claude 或 GPT-4 一样做工具调用和返回结构化 JSON?

能,而且比 base_url 覆盖时代干净得多。

工具调用(Tool Calling)

from pydantic import BaseModel, Field

class GetWeather(BaseModel):
    """Get the current weather for a city."""
    city: str = Field(description="City name, e.g. 'Lisbon'")

model_with_tools = model.bind_tools([GetWeather], strict=True)
result = model_with_tools.invoke("What's the weather in Lisbon?")
print(result.tool_calls)

strict=True 会强制模型严格遵循 schema,不会瞎编参数名。这在生产环境里是必选项——我见过太多模型把 city 写成 location 或者 place,导致下游解析失败。

结构化输出(Structured Output)

class TicketSummary(BaseModel):
    sentiment: str
    priority: int
    summary: str

structured = model.with_structured_output(TicketSummary, method="json_schema")
summary = structured.invoke("Customer is furious the export button is broken again.")
print(summary.priority, summary.summary)

默认方法是 function_calling,传 method="json_schema" 会用模型原生的 JSON Schema 约束能力(如果模型支持)。不是所有模型都支持 json_schema,目录页面上会标注每个模型的能力矩阵。

这里有个实际踩过的坑:有些模型声称支持工具调用,但 strict=True 下表现不稳定。我的做法是,对关键路径一律设 require_parameters: true(在 openrouter_provider 里),让 OpenRouter 只把请求路由到那些真正完整支持参数约束的提供商。这比在代码里写一堆 fallback 逻辑干净多了。


路由策略与故障转移:你的应用怎么自动”换供应商”

Provider Routing and Fallbacks: Automatic Failover Without Code

核心问题:如果某个模型提供商挂了,我的应用会崩吗?我能控制流量走向吗?

默认行为下,不会崩。OpenRouter 的路由层做了三件事:

  1. 价格负载均衡:在多个提供同一模型的服务商之间,按价格分配流量。
  2. 故障避让:最近 30 秒内出过故障的提供商会被自动跳过。
  3. 跨提供商故障转移:首选提供商挂了,请求自动漂到下一个,你的代码无感知。

这些都不需要你在 LangChain 里写重试逻辑。没完成的请求最终也不会计费。

自定义提供商偏好

通过 openrouter_provider 参数,你可以精细控制路由:

model = ChatOpenRouter(
    model="anthropic/claude-sonnet-4.5",
    openrouter_provider={
        "order": ["Anthropic", "Google"],
        "allow_fallbacks": True,
        "data_collection": "deny",
        "sort": "throughput",
    },
)
  • order: 提供商优先级列表。
  • allow_fallbacks: 是否允许在首选提供商不可用时漂移到其他提供商。
  • sort: "throughput" 按吞吐量排序,"latency" 按延迟排序。对实时交互场景选 latency,对批量处理选默认的价格排序。
  • data_collection: "deny": 拒绝把请求路由到那些会用你的 prompt 做训练的提供商。合规场景必开。
  • only / ignore: 显式白名单或黑名单某些提供商。
  • require_parameters: True: 只路由到完整支持你传入参数(如 strict=True、自定义 reasoning 等)的提供商。

跨模型故障转移

提供商层面的故障转移是默认开启的。如果你想进一步——当 Claude 不可用时自动降级到 GPT-5-mini——可以用 route="fallback" 配合 model_kwargs 里的 models 数组:

model = ChatOpenRouter(
    model="anthropic/claude-sonnet-4.5",
    route="fallback",
    model_kwargs={
        "models": [
            "anthropic/claude-sonnet-4.5",
            "openai/gpt-5-mini",
            "google/gemini-3-flash-preview",
        ],
    },
)

models 不是构造函数的具名参数,所以必须塞进 model_kwargs,它会原样透传给 API。OpenRouter 会依次尝试:先找 Claude 的可用提供商,全挂了再试 GPT-5-mini,再试 Gemini。配合 openrouter_provider 里的 sort: {by, partition: "none"},还可以让排序逻辑横跨所有列出的模型,而不是每个模型内部单独排序。

这个功能在我眼里是 OpenRouter 最大的差异化价值。很多团队自己写故障转移逻辑,本质上是在重复造轮子——检测超时、维护提供商健康状态、处理不同模型的响应格式差异。OpenRouter 把这一层抽走了,你只付成功请求的钱,失败的尝试由平台承担成本。


进阶功能:推理预算、多模态、缓存与可观测性

Reasoning, Multimodal, Caching, and Observability

核心问题:除了基本的文本生成,OpenRouter 还支持哪些高级能力?在 LangChain 里怎么用?

推理预算(Reasoning)

model = ChatOpenRouter(
    model="anthropic/claude-sonnet-4.5",
    reasoning={"effort": "high", "summary": "auto"},
)

effortxhighnone 共六档。推理 token 的消耗会单独体现在 usage_metadata.output_token_details.reasoning 里,方便你核算”思考成本”和”输出成本”。

多模态输入

图片、音频、视频、PDF 都通过 LangChain 标准的 HumanMessage 内容块传入,和接其他多模态模型没有区别。具体支持哪些模态取决于你选的模型,目录页面有标注。

Prompt 缓存

在消息内容块里加 cache_control: {"type": "ephemeral"} 即可启用缓存。缓存命中情况会显示在 usage_metadata.input_token_details.cache_read 里。对于长上下文、重复性强的场景(比如每次请求都带一份很长的系统 prompt),这个优化能显著降低 token 成本。

可观测性

session_id(最长 256 字符)可以把相关请求归为一组;传 trace 对象可以附加自定义元数据。这些数据会转发到你配置的 Broadcast 目的地,不需要在应用层额外埋点。

这些全都是构造函数或请求级别的参数,不需要改动你的 chain 结构。


常见问题排查:四个高频坑

Common Problems and Fixes

核心问题:接入过程中最容易踩的坑是什么?

1. 版本兼容性

langchain-openrouter 是 beta 包,要求较新版本的 LangChain 核心库。不要从旧教程里复制版本号,直接去 PyPI 或 npm 看最新版,同步升级 LangChain。

2. 还在用 ChatOpenAI + base_url?

老版本 LangChain 没有专用包时,用 ChatOpenAI(base_url="https://openrouter.ai/api/v1") 加 OpenRouter 密钥是一种 workaround。现在有了 ChatOpenRouter,能直接访问路由策略、推理参数、结构化输出等原生功能。如果你的 LangChain 版本够新,建议迁移。

3. 模型总是返回相同答案

先检查 temperature 是不是设成了 0。再检查是不是启用了 prompt 缓存,命中缓存时会返回之前的结果。这不是缺陷,是预期行为。

4. 参数不被支持

不是所有模型都支持所有参数。不确定时,在 openrouter_provider 里设 require_parameters: true,让 OpenRouter 自动过滤掉不兼容的提供商,或者先去目录页面确认目标模型的能力矩阵。


FAQ:你可能还想问

Frequently Asked Questions

Q1: OpenRouter 和 LangChain 是什么关系?是竞争关系吗?

不是竞争,是组合。OpenRouter 是模型提供商和路由器,提供统一的 API 端点;LangChain 是编排框架,负责链条、代理、记忆、工具调用等逻辑。你在 LangChain 里把 OpenRouter 当作一个 model 组件来用。

Q2: 怎么在 LangChain 里接入 OpenRouter?

装包(langchain-openrouter@langchain/openrouter),设 OPENROUTER_API_KEY,实例化 ChatOpenRouter(model="provider/model"),然后像任何 LangChain chat model 一样调用 .invoke().stream_events().bind_tools().with_structured_output()

Q3: LangChain 支持通过 OpenRouter 做工具调用和结构化输出吗?

支持。用 model.bind_tools([...], strict=True) 做工具调用,用 model.with_structured_output(Schema, method="json_schema") 做结构化输出。这些是 ChatOpenRouter 包的一等公民方法,不需要 workaround。

Q4: 能在 LangChain 里配置提供商路由和故障转移吗?

可以。传 openrouter_provider={...} 控制提供商偏好和排序;传 model_kwargs={"models": [...]} 实现跨模型故障转移。提供商故障转移默认开启,无需额外配置。

Q5: 还有必要用 ChatOpenAI + base_url 的方式吗?

当前版本的 LangChain 下,专用 ChatOpenRouter 包是推荐路径。ChatOpenAI 覆盖 base_url 的方式只建议在你无法升级 LangChain 的旧项目里保留。

Q6: 可以用哪些模型?

OpenRouter 目录里的 400+ 模型都可以,通过 provider/model slug 引用。模型可用性和价格会动态变化,以 openrouter.ai/models 的实时数据为准。

Q7: 流式输出会额外收费吗?

不会。流式和非流式的 per-token 费率相同。

Q8: 请求失败了会扣费吗?

不会。OpenRouter 只对你最终成功的请求计费,故障转移过程中的失败尝试由平台承担。


实用摘要 / 操作清单

Practical Cheat Sheet

任务 Python 代码片段 关键参数
基础调用 ChatOpenRouter(model="anthropic/claude-sonnet-4.5") model, temperature, max_tokens
流式输出 model.stream_events(..., version="v3") version="v3"
工具调用 model.bind_tools([Schema], strict=True) strict=True
结构化输出 model.with_structured_output(Schema, method="json_schema") method="json_schema"
提供商偏好 openrouter_provider={"order": [...], "sort": "latency"} order, sort, data_collection
跨模型降级 model_kwargs={"models": [A, B, C]} + route="fallback" models 数组
推理预算 reasoning={"effort": "high", "summary": "auto"} effort 档位
缓存控制 消息块内加 cache_control: {"type": "ephemeral"} cache_read 在 metadata 中

一页速览

One-Page Summary

  • 装包pip install -U langchain-openrouter(Python)或 npm install @langchain/openrouter(TS)
  • 认证:环境变量 OPENROUTER_API_KEY,或构造函数传 api_key
  • 选模型:去 openrouter.ai/models 查 slug,格式 provider/model
  • 切模型:改 model 参数即可,prompts 和工具定义不动
  • 流式stream_events / astream_events,费率不变
  • 工具/结构化输出bind_tools(strict=True) + with_structured_output(method="json_schema")
  • 路由openrouter_provider 控制提供商偏好;model_kwargs["models"] 实现跨模型故障转移
  • 故障转移:默认开启,失败请求不扣费
  • 进阶reasoning 控推理深度,消息块加 cache_control 省缓存,session_id + trace 做可观测性
  • 避坑:保持包和 LangChain 核心版本同步,不确定参数兼容性时开 require_parameters: true

最后说两句。OpenRouter 不是那种”有了它你就不需要关心底层模型”的魔法——你仍然需要理解每个模型的能力边界、token 限制、价格结构。但它确实把”接入”和”切换”这两件事的摩擦降到了最低。在 LangChain 的生态里,ChatOpenRouter 现在是一个一等公民,值得在新项目里直接采用,而不是先搭一套原生 SDK 再考虑迁移。beta 阶段更新快,锁好版本号,保持关注 release notes,基本不会踩大坑。