跳到主要内容
注册

在 Vercel AI SDK 里接入 Router One:OpenAI、OpenAI 兼容与 Anthropic 三种 provider

Vercel AI SDK(ai 包)通过 provider 包调用模型,其中有三个可以配上 baseURL 和 Key 指向 Router One:@ai-sdk/openai、@ai-sdk/openai-compatible 和 @ai-sdk/anthropic。选哪个 provider、在它上面调用哪个方法,决定了请求发往哪个端点。routerOne.chat(id) 发送 Chat Completions,这个端点服务目录里的所有聊天模型;而 OpenAI provider 的默认调用 routerOne(id) 发送的是 Responses API,Router One 只为 GPT 系列、DeepSeek 与 Grok 对话模型 ID 提供这个端点。generateText、streamText 和工具循环都在你的服务端代码里运行;网关负责每一次模型请求,并为它记录成本与延迟 Trace。本指南依次说明 AI SDK 7 的安装要求、三个 provider 的对照、会把目录 ID 发到 Vercel 自家 AI Gateway 的纯字符串写法、流式 usage 与思考文本、通过 /v1/messages 调用 Claude、会让旧示例跑不起来的 AI SDK 7 改动、重试,以及长调用失败时产生的错误。按 ai 7.0.136、@ai-sdk/openai 4.0.91、@ai-sdk/openai-compatible 3.0.67 与 @ai-sdk/anthropic 4.0.78 核对(2026-10-09),依据 AI SDK 文档与各包源码。

安装 AI SDK 7,并把 Key 留在服务端

npm install ai @ai-sdk/openai 安装核心包和 OpenAI provider;要用下文介绍的另外两个 provider,再装 @ai-sdk/openai-compatible 或 @ai-sdk/anthropic。AI SDK 7 要求 Node.js 22 或更新版本,并且只提供 ESM,require() 已经加载不了它:请在 ES 模块里用 import,也就是在 package.json 里写 "type": "module",或者使用 .mjs、.mts 文件。在 Next.js 里,请在路由处理函数、server action 或其他服务端代码中调用 SDK,并把 Key 放在服务端环境变量里;加上 NEXT_PUBLIC_ 前缀会把它打包进浏览器端代码。Next.js 会自动加载 .env.local,普通 Node.js 脚本可以用 node --env-file=.env.local。没有传 baseURL 和 apiKey 时,createOpenAI 会回退到 OPENAI_BASE_URL 和 OPENAI_API_KEY,createAnthropic 会回退到 ANTHROPIC_BASE_URL 和 ANTHROPIC_API_KEY。示例都显式传入这两个值,并从 ROUTER_ONE_API_KEY 读取 Key(这是本指南自己取的变量名),为其他工具设置的变量就改不了请求的去向。请在控制台为这个应用单独创建一把 Key,并设置 maxSpend 上限。

.env.local
# Server-side only: never prefix it with NEXT_PUBLIC_
ROUTER_ONE_API_KEY=sk-your-router-one-key

把 Vercel AI SDK 配置到 Router One base URL

在 package.json 设了 "type": "module" 的项目里,把下面的代码保存为 ai-sdk-example.ts,然后在 Node.js 22.18 或更新版本上用 node ai-sdk-example.ts 运行(这些版本可以直接运行 TypeScript 文件),或者用 tsx 这类运行器。createOpenAI 接收带 /v1 的 base URL,provider 会在后面拼上端点路径。routerOne.chat(id) 发送 POST /v1/chat/completions,这个端点服务目录里的所有聊天模型,所以同一个文件换成 anthropic/claude-sonnet-5、deepseek-v4.1-flash、google/gemini-3.7-flash 或 grok-4.7 都能运行。不要简写成 routerOne(id):从 AI SDK 5 起,OpenAI provider 的默认调用使用 Responses API,把 Claude 系列 ID 发过去,会在调用任何模型之前被 HTTP 400 拒绝。instructions 是 AI SDK 7 对系统提示词的新叫法,.chat() 会把它作为 system 消息发送。maxOutputTokens 以 max_tokens 发出,原因是这个 provider 不会把带厂商前缀的 ID 当作 OpenAI 推理模型;Router One 的 Chat Completions 接受它作为输出上限。在 .chat() 上使用 streamText 时总会请求 include_usage,所以流结束后 await result.usage 就能拿到 token 数;在 AI SDK 7 里,结果上的 usage 是工具循环所有步骤的合计。

ai-sdk-example.ts
import { createOpenAI } from "@ai-sdk/openai";
import { generateText, streamText } from "ai";

const routerOne = createOpenAI({
  baseURL: "https://api.router.one/v1",
  apiKey: process.env.ROUTER_ONE_API_KEY,
});

// .chat(id) sends POST /v1/chat/completions: every chat model in the catalog
const { text, usage } = await generateText({
  model: routerOne.chat("anthropic/claude-sonnet-5"),
  instructions: "Answer in one sentence.",
  prompt: "What does an LLM API gateway do?",
  maxOutputTokens: 512,
});
console.log(text, usage);

const result = streamText({
  model: routerOne.chat("deepseek-v4.1-flash"),
  prompt: "Count to five.",
});
for await (const delta of result.textStream) process.stdout.write(delta);
console.log(await result.usage);

哪种 provider 调用发出哪种请求

有三个 provider 包可以连到 Router One,而对 OpenAI provider 来说,调用哪个方法和选哪个包同样重要。请把代码里的每个调用对照到下表的一行:

哪种 provider 调用发出哪种请求
provider 调用Router One 端点需要知道的事
createOpenAI({ baseURL, apiKey }).chat(id)POST /v1/chat/completions所有聊天模型;流式时总会请求 usage;非标准的 reasoning_content 字段不会返回给你
createOpenAI(...)(id)、.languageModel(id) 或 .responses(id)POST /v1/responses只限模型详情页列出该端点的 ID(GPT 系列、DeepSeek 与 Grok 对话模型 ID);Claude ID 会得到 HTTP 400 must be called via /v1/messages or /v1/chat/completions
createOpenAICompatible({ name, baseURL, apiKey })(id)POST /v1/chat/completions所有聊天模型;reasoning_content 变成 reasoningText;includeUsage 和 supportsStructuredOutputs 需要自己打开
createAnthropic({ baseURL: 'https://api.router.one/v1', apiKey })(id)POST /v1/messagesClaude 系列与 DeepSeek ID;baseURL 必须以 /v1 结尾
纯字符串,例如 model: 'anthropic/claude-sonnet-5'Vercel 的 AI Gateway,而不是 Router One通过 globalThis.AI_SDK_DEFAULT_PROVIDER 解析,没有设置时回退到 Vercel 的 gateway provider
embed() 或 embedMany() 配 routerOne.embedding(id)没有路由(HTTP 404)embeddings 请留在提供该服务的供应商那里

纯字符串的坑:目录 ID 长得像 AI Gateway 的模型字符串

AI SDK 7 允许把 model 写成纯字符串,并通过 globalThis.AI_SDK_DEFAULT_PROVIDER 解析;没有注册任何 provider 时,回退到 Vercel 的 AI Gateway provider。Router One 的目录 ID,例如 anthropic/claude-sonnet-5 和 openai/gpt-5.5,与 AI Gateway 的模型字符串一样是 provider/model 的形式,所以 generateText({ model: "anthropic/claude-sonnet-5" }) 根本不会到达 Router One,而是发往 Vercel 的 gateway:没设 AI_GATEWAY_API_KEY 时报 Unauthenticated request to AI Gateway,设了则走你的 Vercel AI Gateway 账户;两种情况下控制台 → 日志里都看不到这次请求。请像其他示例那样传入 provider 的模型对象,或者在启动时注册一个默认 provider,而且要选对这个默认值。把 createOpenAI provider 设为默认时,纯字符串会经由 routerOne(id) 解析,也就是走 Responses API,于是只有 Responses 支持的 ID 能用。像下例这样把 createOpenAICompatible provider 设为默认时,纯字符串发往 /v1/chat/completions,所有聊天模型都能用。

default-provider.ts
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { generateText } from "ai";

globalThis.AI_SDK_DEFAULT_PROVIDER = createOpenAICompatible({
  name: "router-one",
  baseURL: "https://api.router.one/v1",
  apiKey: process.env.ROUTER_ONE_API_KEY,
  includeUsage: true,
});

// Plain strings now resolve through Router One's Chat Completions endpoint
const { text } = await generateText({
  model: "anthropic/claude-sonnet-5",
  prompt: "Hello!",
});

@ai-sdk/openai-compatible:思考文本与显式 usage

createOpenAICompatible 构造的 provider 只使用 Chat Completions。name 和 baseURL 都是必填项,也都不会从环境变量读取;apiKey 以 Bearer 令牌发送。有两个设置默认关闭,接 Router One 时值得打开。includeUsage: true 会在流式请求里加上 stream_options.include_usage;不打开的话,流式结果里没有 usage。supportsStructuredOutputs: true 让 Output.object 发送 json_schema 类型的 response_format,name 为 response;不打开时,provider 会退回 json_object,并警告 schema 没有发送。对 Claude 系列 ID 来说,Chat Completions 上的 response_format 本来就不能稳定得到 JSON,结构化输出指南介绍了走 /v1/messages 的做法。这个 provider 还会读取非标准的 reasoning_content 字段:anthropic/claude-sonnet-4.6-thinking 这类 ID 的思考内容,在结果里是 reasoningText,在流里是 reasoning 增量,而 OpenAI provider 的 .chat() 做不到这一点。maxOutputTokens 以 max_tokens 发送。

compatible.ts
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { generateText } from "ai";

const routerOne = createOpenAICompatible({
  name: "router-one",
  baseURL: "https://api.router.one/v1",
  apiKey: process.env.ROUTER_ONE_API_KEY,
  includeUsage: true,
  supportsStructuredOutputs: true,
});

const { text, reasoningText, usage } = await generateText({
  model: routerOne("anthropic/claude-sonnet-4.6-thinking"),
  prompt: "Is 221 prime?",
  maxOutputTokens: 2048,
});
console.log(reasoningText, text, usage);

用 @ai-sdk/anthropic 在 /v1/messages 上调用 Claude 与 DeepSeek

@ai-sdk/anthropic 使用 Anthropic Messages 协议,Router One 为当前目录中的 Claude 系列模型与 DeepSeek ID 提供这个端点。与 Anthropic 官方 SDK 不同,这个 provider 只在 baseURL 后面拼 /messages,而且只对 api.anthropic.com 自动补 /v1,所以 baseURL 必须写成 https://api.router.one/v1。填主机根地址时,它会请求 https://api.router.one/messages,得到 HTTP 404;而为 Claude Code 导出的 ANTHROPIC_BASE_URL 恰好就是这个主机根地址,所以请显式传入 baseURL。apiKey 以 x-api-key 请求头发送,网关接受这种方式;authToken 以 Bearer 令牌发送;两者同时传会直接报错。这个 provider 会从环境变量读取 ANTHROPIC_API_KEY,但不读 ANTHROPIC_AUTH_TOKEN,所以 Claude Code 配置里的令牌变量不会被用上。每次调用都请设置 maxOutputTokens。这个 provider 按带连字符的 Anthropic 模型名查找各模型的默认值,而 Router One 带点号的目录 ID(anthropic/claude-sonnet-4.6、anthropic/claude-opus-4.8,以及 aws/、vertex/ 开头的 ID)会落到它的通用 Claude 条目:max_tokens 为 128000,temperature、topP 和 topK 会从请求里去掉,并在 result.warnings 里留下警告。能按名称匹配上的,例如 anthropic/claude-sonnet-5,采样参数同样会被去掉;DeepSeek ID 的默认值则是 4096 个 token。Anthropic 服务端工具在网关接受的范围内可用:web search 和 code execution 会被接受并计量,web fetch 和 MCP servers 会被 HTTP 400 拒绝。

anthropic.ts
import { createAnthropic } from "@ai-sdk/anthropic";
import { generateText } from "ai";

const routerOneMessages = createAnthropic({
  baseURL: "https://api.router.one/v1", // this provider appends /messages
  apiKey: process.env.ROUTER_ONE_API_KEY,
});

const { text } = await generateText({
  model: routerOneMessages("anthropic/claude-sonnet-5"),
  prompt: "Hello!",
  maxOutputTokens: 1024,
});
console.log(text);

会让旧示例跑不起来的 AI SDK 7 改动

为 AI SDK 5 或 6 写的代码,要改几处才能在 7 上运行。system 已被弃用,改用 instructions,两者同时设置时以 instructions 为准。prompt 或 messages 里的 system 消息现在默认会被拒绝,报错为 System messages are not allowed in the prompt or messages fields. Use the instructions option instead. 请把它们移到 instructions 里;只有对你自己服务端写入的历史记录,才传 allowSystemInMessages: true,因为用户提交的 system 消息可能覆盖你的提示词。generateText 和 streamText 结果上的 usage 现在是工具循环所有步骤的合计,也就是以前 totalUsage 的含义,所以请把它和该循环在控制台 → 日志里各行的总和对账,每一步的数字从 steps 里读取。stepCountIs 改名为 isStepCount;除非 stopWhen 允许更多步,generateText 在一步之后就会停止,而每一步都是一次模型请求。这些包只提供 ESM,需要 Node.js 22 或更新版本。官方 codemod(例如 npx @ai-sdk/codemod v7/rename-system-to-instructions)能处理大部分改名。

重试、工具循环与长调用错误

generateText 和 streamText 默认对失败的调用重试两次(maxRetries),这叠加在网关自己的故障转移之上:请求由你指定的模型处理,某条线路出现 429、5xx 或超时时,网关会在返回之前换到同一模型的另一条线路重试。在工具循环里,每一步都是一个独立的请求,客户端每一次经网关发往模型的尝试在控制台 → 日志里都有自己的 request_id。请用 stopWhen 限制循环步数,批处理任务调低 maxRetries,并给这个应用配一把设了 maxSpend 的 Key,失控的循环会在你设的上限处收到 HTTP 402 而停下。非流式调用运行超过网关的保活时间点(约 25 秒)后才失败时,返回的是 HTTP 200 加顶层 error 对象。这时两个 OpenAI 系 provider 都会抛出 APICallError,消息为 Invalid JSON response,网关自己的错误文本在这个错误的 responseBody 里。streamText 不会遇到这个问题,所以长时间的生成和推理模型请优先用它。

Vercel AI SDK 该填哪个模型 ID?

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

Vercel AI SDK 用的是哪种 API 协议?

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

在请求日志里核对 Vercel AI SDK 的调用

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

常见问题

为什么 routerOne('<id>') 返回 400 must be called via…?

因为在 createOpenAI provider 上,routerOne(id) 使用的是 Responses API(从 AI SDK 5 起这是 OpenAI provider 的默认值),而 Router One 只为 GPT 系列、DeepSeek 与 Grok 对话模型 ID 提供 /v1/responses。Claude 系列 ID 发到这里,会在调用任何模型之前被拒绝,消息为 model '<id>' must be called via /v1/messages or /v1/chat/completions。请改用 routerOne.chat(id),它对所有聊天模型都走 /v1/chat/completions;或者换成默认调用就是 Chat Completions 的 @ai-sdk/openai-compatible。需要 previous_response_id 这类 Responses 功能时,只对模型详情页列出 POST /v1/responses 的 ID 使用 routerOne.responses(id)。

该用 .chat() 还是 @ai-sdk/openai-compatible?

两者都向所有聊天模型发送 Chat Completions。如果还想在同一个 provider 上对 GPT 系列 ID 使用 Responses API,或者需要 OpenAI 专属的 provider 选项,就用 createOpenAI(...).chat(id);它总会请求流式 usage。如果需要思考文本,就用 createOpenAICompatible,它会把 reasoning_content 作为 reasoningText 返回,.chat() 则不会。它需要 includeUsage: true 才有流式 usage,需要 supportsStructuredOutputs: true 才能输出 json_schema,这两项默认都关闭;另外,拿它做纯字符串模型 ID 的全局默认 provider 更稳妥。

能直接把目录 ID 写成纯字符串吗?

只有把 Router One 的 provider 注册为 globalThis.AI_SDK_DEFAULT_PROVIDER 之后才行。否则 anthropic/claude-sonnet-5 这样的字符串会发往 Vercel 的 AI Gateway,永远到不了 Router One,也不会出现在控制台 → 日志里。请注册 createOpenAICompatible provider,让字符串发往 /v1/chat/completions;如果把 createOpenAI provider 设为默认,字符串会被发到 /v1/responses,那里只有 GPT 系列、DeepSeek 与 Grok 对话模型 ID 能用。

升级到 AI SDK 7 后为什么报 System messages are not allowed?

AI SDK 7 默认拒绝 prompt 或 messages 里的 system 消息,而且在发出请求之前就拒绝,所以请求根本没有到达网关。请把系统提示词放进 instructions;对于你自己服务端写入、本来就含有 system 消息的对话历史,可以传 allowSystemInMessages: true。

Claude 的思考内容在哪里?

OpenAI provider 的 .chat() 不读取非标准的 reasoning_content 字段,所以 -thinking 类 ID 的思考内容已经生成、也按输出计费,却不会返回给你。请改用 @ai-sdk/openai-compatible,它通过 Chat Completions 把思考内容作为 reasoningText 返回;或者用 @ai-sdk/anthropic 走 /v1/messages,思考内容以 reasoning 部分返回。

embeddings 能通过 Router One 调用吗?

不能。Router One 不提供 /v1/embeddings,用 Router One 的 provider 调用 embed() 和 embedMany() 会得到 HTTP 404。embedding 模型请留在提供 embeddings 的供应商那里,用该供应商自己的 Key,只把聊天调用发到网关。

Vercel AI SDK 能通过网关用哪些模型?

选用当前目录中同时支持 Vercel AI SDK 所用端点和所需功能的模型。精确 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,再按错误码速查页逐项排查。