跳到主要内容
注册

把 LangChain 的 ChatOpenAI 和 ChatAnthropic 接到 Router One

LangChain 接入 Router One,用的就是它对接 OpenAI 和 Anthropic 的那两个聊天模型类。ChatOpenAI 配上 base URL https://api.router.one/v1,发送的是 Chat Completions,这个端点服务目录里的所有聊天模型;ChatAnthropic 配上主机根地址 https://api.router.one,发送的是 Anthropic Messages,这个端点服务 Claude 系列模型与 DeepSeek ID。链、Agent 循环和你的工具都在你自己的进程里由 LangChain 运行,它发出的每次模型请求在控制台 → 日志里都有独立的成本与延迟 Trace。本指南依次说明安装和 base URL 相关的环境变量、保留流式 usage 的 Python ChatOpenAI 配置、Python 与 JS 如何在 Chat Completions 和 Responses API 之间做选择(两者的判断方式不同)、用 ChatAnthropic 拿到 Claude 的思考内容和 JSON 输出、init_chat_model 与 create_agent、重试,以及长调用失败时你实际看到的错误。按 langchain-openai 1.7.0、langchain-anthropic 1.7.5、langchain 1.4.4(Python)与 @langchain/openai 1.6.2、@langchain/anthropic 1.5.12(JS)核对(2026-10-09),依据各包的文档与源码。

安装依赖包,并在代码里写明 base URL

Python:pip install -U langchain-openai 提供 ChatOpenAI;需要 ChatAnthropic 时再装 langchain-anthropic,需要 init_chat_model 和 create_agent 时再装 langchain。三者都要求 Python 3.10 或更新版本。JS:npm install @langchain/openai @langchain/core,需要 ChatAnthropic 时再加 @langchain/anthropic;@langchain/openai 1.6.2 要求 Node.js 22 或更新版本,底层是 openai 7.x SDK。请在代码里传入 base URL,不要依赖环境变量。两种语言里 ChatOpenAI 解析 base URL 的顺序相同:先看 base_url 参数(JS 里是 configuration.baseURL),再看 OPENAI_API_BASE,最后才是 OPENAI_BASE_URL。所以旧教程留下的 OPENAI_API_BASE 会压过你刚设置的 OPENAI_BASE_URL。Key 取自 api_key 参数(JS 里是 apiKey),没传时回退到 OPENAI_API_KEY;示例从 ROUTER_ONE_API_KEY 读取 Key(这是本指南自己取的变量名),环境里遗留的变量就改不了请求的去向。在 Python 里,自定义 base URL 还有第二个影响,下文会讲到:除非你显式要求,否则流式 usage 保持关闭。

terminal
python -m pip install -U langchain-openai langchain-anthropic langchain
npm install @langchain/openai @langchain/anthropic @langchain/core
export ROUTER_ONE_API_KEY="sk-your-router-one-key"

把 LangChain 配置到 Router One base URL

把下面的代码保存为 langchain_example.py 并运行。base_url 是 ChatOpenAI 里 openai_api_base 的别名,必须以 /v1 结尾,因为底层的 openai SDK 会在后面拼上 /chat/completions。use_responses_api=False 是一个显式选择,而在 Python 里显式值始终优先:无论模型名是什么、传了哪些选项,每个请求都发往 POST /v1/chat/completions,所以 /models 里的任何聊天模型 ID 都能用,带厂商前缀的也一样。stream_usage=True 之所以必要,是因为 ChatOpenAI 只有在既没设 base URL、也没设 OPENAI_BASE_URL 时才默认打开流式 usage。指向 Router One 的 base URL 后它保持关闭,流式请求不带 include_usage,流式消息上的 usage_metadata 是空的。打开之后,invoke() 和 stream() 都会返回带输入、输出 token 数的 usage_metadata,可以与控制台 → 日志逐条对账。去掉 api_key 时,Key 会回退到 OPENAI_API_KEY。

langchain_example.py
import os
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="anthropic/claude-sonnet-5",
    base_url="https://api.router.one/v1",
    api_key=os.environ["ROUTER_ONE_API_KEY"],
    use_responses_api=False,  # every request goes to /v1/chat/completions
    stream_usage=True,  # off by default once base_url is set
)

reply = llm.invoke("Hello!")
print(reply.content, reply.usage_metadata)

for chunk in llm.stream("Count to five."):
    print(chunk.content, end="", flush=True)

Python 与 JS 选择端点的方式不同

ChatOpenAI 既可以发送 Chat Completions,也可以发送 Responses API 请求,而两种语言的包判断方式不同。Router One 的 Chat Completions 服务所有聊天模型,Responses 只服务 GPT 系列模型、DeepSeek ID 与 Grok 对话模型,所以走哪个端点决定了哪些 ID 能用。在 Python 里,显式设置的 use_responses_api 就是最终结果。不设置时,遇到 Responses 专属设置(reasoning、include、truncation、text、previous_response_id、上下文管理、内置工具,或把 output_version 设为 responses/v1),以及它认为该走 Responses 的模型名时,ChatOpenAI 会自动改走 Responses:模型名的判断依据是一组名称前缀、绑定了工具时以 gpt-6 开头的名称(Router One 带厂商前缀的 ID,例如 openai/gpt-5.6-sol、openai/gpt-6-sol,两条都不会命中),以及名称里含 codex(codex-auto-review 会命中)。在 JS 里,useResponsesApi: false 关不掉这种自动切换。只要模型名包含 gpt-5.6 或 codex(或少数几个 -pro 名称)、绑定了内置工具或自定义工具,或者设置了 previous_response_id、text、truncation、include、reasoning.summary 这类 Responses 专属调用选项,@langchain/openai 1.6.2 仍会把请求发到 Responses。因此即使设了这个选项,openai/gpt-5.6-sol、openai/gpt-5.6-terra、azure/gpt-5.6-* 系列 ID 和 codex-auto-review 从 LangChain JS 发出时仍会走 POST /v1/responses。Router One 在这个端点上原生服务这些 ID,所以调用仍然成功,只是走的不是 Chat Completions。要在 JS 里让所有 ID 都走 Chat Completions,请改用 @langchain/openai 导出的 ChatOpenAICompletions,传入相同的字段。

Python 与 JS 选择端点的方式不同
设置请求发往的端点需要核对
Python ChatOpenAI(..., use_responses_api=False)无论模型名和选项如何,都发往 POST /v1/chat/completions目录里任何聊天模型 ID
Python ChatOpenAI 不设置 use_responses_apiChat Completions;遇到 Responses 专属设置、内置工具或含 codex 的模型名时改走 Responsescodex-auto-review 会改走 POST /v1/responses,该端点服务它;Claude ID 若因 Responses 专属设置被送到那里,会得到 HTTP 400
JS new ChatOpenAI({ useResponsesApi: false })Chat Completions;模型名含 gpt-5.6 或 codex、绑定了内置或自定义工具、设置了 Responses 专属调用选项时除外这些请求发往 POST /v1/responses,该端点服务 GPT 系列、DeepSeek 与 Grok 对话模型 ID
JS new ChatOpenAICompletions({ ... })始终发往 POST /v1/chat/completions任何聊天模型 ID;这个类不提供 Responses 专属功能
ChatAnthropic(Python 或 JS)配主机根地址POST /v1/messages只限 Claude 系列与 DeepSeek ID;其他 ID 会在调用任何模型之前被 HTTP 400 拒绝
OpenAIEmbeddings没有路由(HTTP 404)embeddings 请留在提供该服务的供应商那里

用 ChatAnthropic 在 /v1/messages 上调用 Claude 与 DeepSeek

ChatAnthropic 使用 Anthropic Messages 协议,Router One 为当前目录中的 Claude 系列模型(anthropic/*、aws/claude-* 与 vertex/claude-* 这几类 ID)以及 DeepSeek ID deepseek-v4.1-flash、deepseek-v4-flash 提供这个端点。base URL 请填主机根地址 https://api.router.one,不带 /v1:底层的 Anthropic SDK 会自己拼上 /v1/messages,以 /v1 结尾的 base URL 会变成 /v1/v1/messages,得到一个提示你去掉末尾 /v1 的 HTTP 404。不传 base_url 时,ChatAnthropic 先读 ANTHROPIC_API_URL,再读 ANTHROPIC_BASE_URL;Key 回退到 ANTHROPIC_API_KEY,以 x-api-key 请求头发送,网关把它当作与 Bearer 请求头等效的方式接受。请务必设置 max_tokens。ChatAnthropic 会从一张按 Anthropic 自家模型名索引的配置表里取默认值,而 Router One 的目录 ID 不在表中,于是回退到 4096,较长的回答会提前截断,stop_reason 为 max_tokens。在 LangChain Python 里,Chat Completions 会丢掉的东西,ChatAnthropic 都能保留:思考内容以 thinking 内容块返回;with_structured_output(schema, method="json_schema") 把 schema 作为 output_config.format 发送,这是网关文档中让 Claude ID 稳定输出 JSON 的方式。默认的 function_calling 方式则会强制模型调用工具,而在 Claude Opus 5.5 上,网关会把强制的 tool_choice 降为 auto,模型可能直接用文本回答。Anthropic 的服务端工具也能用:web search 和 code execution 会被接受并计量,web fetch 和 MCP connector 会被 HTTP 400 拒绝。

langchain_anthropic_example.py
import os
from pydantic import BaseModel
from langchain_anthropic import ChatAnthropic

claude = ChatAnthropic(
    model="anthropic/claude-sonnet-5",
    base_url="https://api.router.one",  # host root: the SDK appends /v1/messages
    api_key=os.environ["ROUTER_ONE_API_KEY"],
    max_tokens=2048,  # otherwise 4096 for catalog IDs
)

class Verdict(BaseModel):
    prime: bool
    reason: str

checker = claude.with_structured_output(Verdict, method="json_schema")
print(checker.invoke("Is 221 prime?"))

LangChain JS:同样的类,不同的默认值

在 JS 里,base URL 写在 configuration 里,这是交给底层 openai 客户端的选项对象。JS 的 streamUsage 默认为 true,流式消息不用额外设置就带 usage_metadata。JS 还会保留 Chat Completions 返回的 Claude 思考文本:它放在消息的 additional_kwargs.reasoning_content 里;而 Python 的 ChatOpenAI 会丢掉 reasoning_content,它自己的文档也提醒过 OpenAI 规范以外的字段不会保留。在 JS 里使用 ChatAnthropic 时,把主机根地址传给 anthropicApiUrl(或 clientOptions.baseURL),否则它会读取 ANTHROPIC_API_URL 或 ANTHROPIC_BASE_URL;它对目录 ID 同样默认 maxTokens 为 4096,所以也要设置 maxTokens。把下面的示例保存为 langchain-example.mjs,在 Node.js 22 或更新版本上用 node 运行。

langchain-example.mjs
import { ChatOpenAI, ChatOpenAICompletions } from "@langchain/openai";

const fields = {
  model: "anthropic/claude-sonnet-5",
  apiKey: process.env.ROUTER_ONE_API_KEY,
  configuration: { baseURL: "https://api.router.one/v1" },
};

// Chat Completions for this ID; names containing gpt-5.6 or codex still go to Responses
const llm = new ChatOpenAI({ ...fields, useResponsesApi: false });
console.log((await llm.invoke("Hello!")).content);

// Chat Completions for every ID, whatever the name
const completionsOnly = new ChatOpenAICompletions({
  ...fields,
  model: "openai/gpt-5.6-sol",
});
console.log((await completionsOnly.invoke("Hello!")).content);

Agent:init_chat_model 与 create_agent

显式指定集成时,init_chat_model 构造出来的就是同一个 ChatOpenAI,写法见下例。第一个冒号之前的部分决定用 LangChain 的哪个集成,之后的全部内容是模型名,所以完整的目录 ID(包括斜杠)会原样到达网关。请保留 openai: 前缀,或者传 model_provider="openai"。不写前缀时,init_chat_model 会根据名称猜测集成:deepseek-v4.1-flash 会选中 DeepSeek 集成,grok-4.7 会选中 xAI 集成,各自需要自己的包、端点和 Key;anthropic/claude-sonnet-5 则直接报错 Unable to infer model provider。把结果传给 langchain.agents 里的 create_agent;用模型实例构建的 Agent,发出的请求与这个实例完全相同。Agent 的每一步都是一个 POST /v1/chat/completions,在控制台 → 日志里各占一行:模型返回工具调用,LangChain 在你的进程里运行工具,下一次请求带上工具结果。create_agent 底下的运行时是 LangGraph,checkpoint、状态和人在回路的暂停都留在你的进程和你的存储里;网关只看到每次模型请求里的消息。给每个 Agent 单独配一把设了 maxSpend 的 Key:不停调用工具的运行到达上限时会收到 HTTP 402 而停下,你的其他 Key 照常可用。

agent.py
import os
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model

model = init_chat_model(
    "openai:anthropic/claude-sonnet-5",  # openai: picks ChatOpenAI; the rest is the model ID
    base_url="https://api.router.one/v1",
    api_key=os.environ["ROUTER_ONE_API_KEY"],
    use_responses_api=False,
    stream_usage=True,
)

def add(a: int, b: int) -> int:
    """Add two integers."""
    return a + b

agent = create_agent(model=model, tools=[add])
result = agent.invoke({"messages": [{"role": "user", "content": "What is 2 + 3?"}]})
print(result["messages"][-1].content)

长调用、重试,以及你实际看到的错误

非流式调用运行超过网关的保活时间点(约 25 秒)后才失败时,返回的是 HTTP 200 加顶层 error 对象,里面没有 choices。LangChain Python 会抛出带网关错误信息的 ValueError;LangChain JS 则抛出一个看似无关的 TypeError:Cannot read properties of undefined (reading 'message'),把真正的原因藏了起来。流式调用可以避开这两种情况,所以长时间的生成和推理模型请用 llm.stream()。重试会叠加在网关自己的故障转移之上:请求由你指定的模型处理,某条线路出现 429、5xx 或超时时,网关会在返回之前换到同一模型的另一条线路重试。Python 的 ChatOpenAI 把重试交给 openai SDK,默认两次,用 max_retries 设置。LangChain JS 给每次调用套了一层自己的重试循环,默认最多重试 6 次,并把内部 openai 客户端的重试设为 0;批处理任务请调低 maxRetries,免得一条失败的输入变成 7 次请求。客户端每一次经网关发往模型的尝试在控制台 → 日志里都有自己的 request_id,报障时请提供它。

LangChain 该填哪个模型 ID?

从 /models 页复制精确的模型 ID,保留大小写、连字符和版本后缀,不要用展示名称代替。打开该模型的详情页,核对支持的 API 端点、上下文窗口和工具调用等能力,再与 LangChain 当前选择的 provider 和功能对应。模型出现在目录里,不等于当前客户端能调用它的所有功能。建议为每个客户端或应用单独建 API Key,并设置 maxSpend 消费上限。

LangChain 用的是哪种 API 协议?

OpenAI 兼容描述的是接口格式,不能据此推断 Chat Completions(/v1/chat/completions)、Responses(/v1/responses)和 Anthropic Messages(/v1/messages)可以互换。先核对工具当前版本、provider 配置和实际请求路径,再查模型详情与 API 兼容性事实页。一次普通对话成功,也不能证明服务端工具、历史状态或文件编辑功能都受支持。

在请求日志里核对 LangChain 的调用

先在 LangChain 发出一次简单文本请求,再到控制台 → 日志按时间、模型和 request_id 找到这条记录,核对 Token、花费、总耗时和状态码。成功后再分别验证流式输出、工具调用和多轮历史。失败时保留实际请求路径、完整错误消息及 request_id;没有对应日志时,要么请求没到达网关(客户端配置或网络问题),要么网关在调用模型之前就拒绝了它——Key 问题的 401、余额不足或 Key 达到 maxSpend 上限的 402、Key 或账户自身限额触发的 429,或模型 ID、端点不对的 400 / 404;在输出任何内容之前就失败的流式请求也可能没有日志。看客户端收到的状态码和错误消息就能区分是哪一种;先确认这一点,再判断是不是网关或上游故障。

常见问题

为什么设了 useResponsesApi: false,LangChain JS 还是会请求 /v1/responses?

因为在 @langchain/openai 1.6.2 里,这个选项只能打开 Responses,关不掉自动切换。模型名包含 gpt-5.6 或 codex、绑定了内置工具或自定义工具、或者设置了 Responses 专属调用选项时,这个包仍会把请求发到 Responses。在 Router One 上,受影响的是 openai/gpt-5.6-sol、openai/gpt-5.6-terra、azure/gpt-5.6-* 系列 ID 和 codex-auto-review,网关在 /v1/responses 上原生服务它们,所以这些调用仍然成功。如果需要所有 ID 都走 Chat Completions,请用 @langchain/openai 里的 ChatOpenAICompletions,传入相同的字段。Python 的行为不同:在那里 use_responses_api=False 始终优先。

为什么在 Python 里流式调用时 usage_metadata 是空的?

ChatOpenAI 只有在没配置 base URL、也没设 OPENAI_BASE_URL 时才默认打开 stream_usage。无论通过参数还是环境变量指向 Router One,stream_usage 都保持关闭,流式请求不带 include_usage,也就不会返回 usage chunk。请在模型上设置 stream_usage=True,或者在单次 stream() 调用里传 stream_usage=True。无论哪种情况,控制台 → 日志都会记录 tokens 和费用;这个设置只决定你的代码能不能拿到它们。

Claude 的思考内容去哪了?

LangChain Python 的 ChatOpenAI 不保留 OpenAI 规范以外的响应字段,reasoning_content 就是其中之一,所以 anthropic/claude-sonnet-4.6-thinking 这类 ID 的思考内容虽然已经生成、也按输出计费,却从消息里丢掉了。请改用 ChatAnthropic,它通过 /v1/messages 以内容块的形式返回思考内容。LangChain JS 的 ChatOpenAI 则把它保留在 additional_kwargs.reasoning_content 里。

怎样让 Claude 模型稳定输出 JSON?

用 ChatAnthropic 的 with_structured_output(schema, method="json_schema"),它会在 /v1/messages 上把 schema 作为 output_config.format 发送。ChatOpenAI 的 with_structured_output 默认通过 response_format 使用 json_schema,而在 /v1/chat/completions 上,response_format 并不能让 Claude 系列 ID 稳定输出 JSON。ChatAnthropic 自己的默认方式 function_calling 会强制调用工具,在 Claude Opus 5.5 上网关会把它降为 tool_choice auto。其他 ID 请先在模型详情页核对结构化输出支持;json_schema 类型的 response_format 如果缺少 name 或 schema,网关会返回 HTTP 400,格式合法的则原样转发。

应该设置 OPENAI_API_BASE 还是 OPENAI_BASE_URL?

能在代码里传 base_url 的话,两个都不用设。在 Python 和 JS 里,ChatOpenAI 都是先看参数,再看 OPENAI_API_BASE,最后看 OPENAI_BASE_URL,所以旧的 OPENAI_API_BASE 会压过较新的 OPENAI_BASE_URL;而单独使用 openai SDK 时,它根本不读 OPENAI_API_BASE。在 Python 里,设置 OPENAI_BASE_URL 还会关掉默认的流式 usage。ChatAnthropic 对应的变量是 ANTHROPIC_API_URL,其次是 ANTHROPIC_BASE_URL,两者都必须填主机根地址。

能通过 Router One 使用 OpenAIEmbeddings 吗?

不能。Router One 不提供 /v1/embeddings,把 OpenAIEmbeddings 指向网关会得到 HTTP 404。检索器或向量库的 embedder 请留在提供 embeddings 的供应商那里,用该供应商自己的 Key,只让聊天模型的调用经过 Router One。

LangChain 能通过网关用哪些模型?

选用当前目录中同时支持 LangChain 所用端点和所需功能的模型。精确 ID、当前单价与能力以 /models 及模型详情为准;不要仅按 GPT、Claude 等系列名判断兼容性。客户端能列出模型,只说明它从自身配置或 GET /v1/models 读到了这个 ID,仍需验证实际调用。

能列出模型,但调用报 400 或 404,怎么办?

先记录实际请求路径和错误消息,再核对精确模型 ID。400 可能是参数、工具类型或模型与端点不匹配;404 可能是请求路径或资源不存在,不能直接判定模型下线。若错误提示 must be called via,按它指明的端点调整客户端 provider,或换用支持当前端点的模型。不要在所有工具里统一增删 /v1 或 /chat/completions。

中国大陆能直连吗?

能。网关在大陆可直连、无需 VPN,配置与全球环境完全一致。

报 401/402/403/429 怎么排查?

先看客户端收到的响应:状态码、错误消息和 X-Request-ID 响应头。401(Key 缺失、不存在、已吊销或已过期)、402(钱包余额不足,或这把 Key 达到 maxSpend 上限)以及 Key 或账户自身限额触发的 429,都在调用任何模型之前被拒绝,不会出现在控制台 → 日志里;模型 ID 或端点写错导致的 400 / 404 也一样。401 查 Key 是否完整传入、在控制台 → API 密钥里是否仍有效;402 充值或调高这把 Key 的 maxSpend;403 请保留完整错误消息。429 请退避后重试;日志里能查到的 429 是网关重试之后上游仍返回的。保留 request_id,再按错误码速查页逐项排查。