跳到主要内容
Router One

用一个模型 url 和一把 Key 把 Mastra Agent 接到 Router One

Mastra 是用来构建 AI Agent 和工作流的 TypeScript 框架。它的模型路由通常接收 provider/model 形式的字符串,用环境变量里该 provider 自己的 Key 去调用该 provider 自己的 API;对其他 OpenAI 兼容服务,官方文档给出的写法是一个带 id、url 和 apiKey 的模型对象。设置了 url 之后,Mastra 会向这个 base URL 发送 Chat Completions,所以指向 Router One 就能调用目录里在 /v1/chat/completions 上提供服务的所有聊天模型,每次请求都有成本和延迟 Trace;Agent 循环、你的工具、记忆、存储和 Studio 仍在你自己的进程里运行。本指南用对象写法配置一个 Agent,说明 id 的第一段是怎样被处理的(它决定了到达网关的模型字符串),先带一个工具跑 generate(),再跑 stream(),并说明 maxSteps 和 maxRetries 各限制什么、哪些记忆功能需要网关不提供的 embedder,以及怎样把 Mastra 的 token 统计和 Dashboard → Logs 对上。

安装 Mastra 并设置凭证

Mastra v1 要求 Node.js 22.13.0 或更新版本;官方文档还说明 Node.js 22.18.0 及以上可以直接运行 TypeScript 文件,本指南就是这样运行 agent.ts 的。新项目可以用 create-mastra:默认会生成一个按你所选模型 provider(OpenAI、Anthropic、Gemini 或 xAI)配置好的 starter,加 --empty 则生成不含 Agent、不绑定 provider 的空脚手架,更适合这里的用法。在已有项目里加 Mastra,则先确认 package.json 里有 "type": "module",再安装 Mastra 快速上手文档列出的包,见下方命令。本指南的对象写法不需要安装任何 AI SDK provider 包:@mastra/core 已经内置了它据此对象构造的 OpenAI 兼容 provider。下面是 macOS/Linux 的 shell 示例,把占位符换成为这个 Agent 单独创建的 Router One Key。ROUTER_ONE_API_KEY 是本示例自己定义并显式读取的变量名。模型设置了 url 时,Mastra 不会去读任何环境变量,所以 Key 必须通过 apiKey 传入,也不需要设置 OPENAI_API_KEY。

terminal
# New project instead: npx create-mastra@latest my-app --empty
npm install @mastra/core@latest zod@latest typescript@latest @types/node@latest mastra@latest
export ROUTER_ONE_API_KEY="sk-your-router-one-key"

把 Mastra 配置到 Router One base URL

保存为 agent.ts,再运行 node agent.ts。model 字段填的是一个对象,而不是 provider/model 字符串。id 会在第一个斜杠处拆开:第一段(这里的 custom,是 Mastra 文档给自定义端点用的标签)成为 Mastra 日志和 span 里的 provider 名,斜杠之后的全部内容作为请求的 model 字段发出,所以 custom/anthropic/claude-sonnet-5 发出的是 anthropic/claude-sonnet-5,custom/grok-4.6 发出的是 grok-4.6。把占位符换成 /models 里的精确 ID,并保留前面的 custom/。url 填带 /v1 的 base URL,不是聊天端点:内置的 OpenAI 兼容 provider 会自行拼接 /chat/completions,所以每次模型调用都是一个 POST /v1/chat/completions。apiKey 会成为 Authorization: Bearer 请求头。工具用 createTool 定义(id、description、zod 的 inputSchema、execute),并以 addTool 这个键注册;这个键就是模型在请求的 tools 字段里看到的函数名,同时带 tool_choice auto。generate() 在整个循环结束后返回:这个提示词的第一次请求会返回一个工具调用,Mastra 在你的进程里运行 execute,第二次请求返回答案,所以 result.steps 有两项,result.usage 是 API 为这两次请求报告的 token 之和。maxSteps: 3 把循环限制在三次模型调用以内;不设时默认是 5。stream() 发送同样的请求并带 stream: true,文本通过 textStream 边到边输出。在 Mastra 项目里,把 Agent 放在 src/mastra/agents 下,并在 src/mastra/index.ts 里用 new Mastra({ agents: { routerOneAgent } }) 注册;模型对象完全一样。

agent.ts
import { Agent } from "@mastra/core/agent";
import { createTool } from "@mastra/core/tools";
import { z } from "zod";

const addTool = createTool({
  id: "add",
  description: "Add two numbers",
  inputSchema: z.object({ a: z.number(), b: z.number() }),
  execute: async ({ a, b }) => ({ sum: a + b }),
});

export const routerOneAgent = new Agent({
  id: "router-one-agent",
  name: "Router One Agent",
  instructions: "You are a concise assistant. Use the add tool for arithmetic.",
  model: {
    // Mastra strips the "custom/" label and sends the rest as the model, e.g. custom/anthropic/claude-sonnet-5
    id: "custom/<exact-model-id-from-/models>",
    url: "https://api.router.one/v1",
    apiKey: process.env.ROUTER_ONE_API_KEY,
  },
  tools: { addTool },
});

// 1. generate(): one POST /v1/chat/completions per step; maxSteps caps the loop (default 5)
const result = await routerOneAgent.generate("What is 2 + 3?", { maxSteps: 3 });
console.log(result.text, result.steps.length, result.usage);

// 2. stream(): the same request with stream: true
const stream = await routerOneAgent.stream("Say hello in five words.");
for await (const chunk of stream.textStream) process.stdout.write(chunk);

Mastra 的哪个设置发出哪种请求

连接信息由模型对象携带,一次调用发出多少请求、每个请求要什么则由 Agent 的选项决定。下表按本指南涉及的设置,给出该填的值、它产生的请求,以及使用前要确认的内容。

Mastra 设置填什么怎么验证
model → urlhttps://api.router.one/v1内置 provider 在后面拼 /chat/completions;AI_APICallError 的 statusCode 为 404 且 url 里没有 /v1,说明 base URL 少了 /v1
model → apiKey为这个 Agent 单独创建、设了 maxSpend 的 Router One Key作为 Authorization: Bearer 发送;设置了 url 时不读任何环境变量,值为 undefined 时请求不带该请求头,网关返回 401
model → idcustom/ 加上精确的目录 ID,例如 custom/anthropic/claude-sonnet-5Logs 里每条 Trace 的模型与 /models 逐字一致;不加这个标签时,ID 自带的前缀会在发送前被去掉
tools: { addTool }(用 createTool 构建)带 tools 字段和 tool_choice auto 的 Chat Completions;对象的键就是函数名模型详情页列出工具调用;execute 在你的进程里运行,工具结果随下一次请求发出
maxSteps(默认 5)限制一次 generate() 或 stream() 里连续的模型调用次数;每一步是一次请求没有重试时,result.steps.length 等于这次调用在 Logs 里的行数
Agent 上的 maxRetries(默认 0)遇到 408、409、429、5xx 或非 HTTP 响应类错误时,每次模型调用额外尝试的次数每一次到达网关的尝试在 Logs 里都是独立一行;401、402 和 404 不会重试
structuredOutput: { schema }给请求加上 response_format json_schema;jsonPromptInjection 则改为把 schema 写进提示词模型详情页列出结构化输出;result.object 会按 schema 校验
agent.stream()同一个请求,改为 stream: true;对象写法不发送 stream_options模型页的流式支持;中途断开的流记为 HTTP 499 client_cancelled

给一个 Agent 定预算,并逐次核对请求

一次 generate() 或 stream() 调用是一个循环:每一步是一个 POST /v1/chat/completions,返回工具调用的那一步之后,Mastra 运行工具并再发下一步;模型不再调用工具而直接回答,或到达 maxSteps(默认 5)时,循环结束。每个请求都会重新发送指令、对话以及此前所有的工具调用和结果,所以输入 token 会逐步增长:maxSteps 限制的是请求数,不是花费。重试只有在你要求时才会增加请求:当前 API 里 Agent 的 maxRetries 选项默认是 0,失败的调用不会重发;设为 maxRetries: 2 时,持续的 5xx 会让这一步变成三次请求。可重试的情况是 408、409、429、5xx 或非 HTTP 响应类错误;401、402 和 404 从不重试。旧的 generateLegacy() 路径保留自己的 maxRetries,默认值是 2。带回退项的 model 数组会再乘一层,因为每一项有自己的重试次数;网关在 5xx 和超时时的同系列回退发生在响应到达 Mastra 之前,所以客户端的回退列表只会在网关自己返回错误时增加请求。一些可选功能会发出自己的模型请求:给 structuredOutput 单独指定 model 时每个回答是两次调用,generateTitle 会在 Agent 的模型上多一次调用(除非另外给它指定模型),Observational Memory 会在后台运行 Observer 和 Reflector 两个 Agent。每一次到达网关的尝试在 Dashboard → Logs 里都是独立一行,有各自的 request_id、费用和状态。给每个 Agent 单独一把设了 maxSpend 的 Key:失控的循环会在你设的上限处收到 HTTP 402,表现为 statusCode 为 402 的 AI_APICallError,钱包和其他 Key 不受影响。result.usage、result.steps 里每一步的 usage,以及 Mastra tracing 里的 token 和成本数字,是 Mastra 对 API 报告用量的统计和估算;费用以 Logs 记录为准。按 Key、时间、模型和 token 数对账,报障时保留 request_id。Router One 只记录模型调用元数据,不运行 Agent 循环、你的工具、工作流、记忆或存储。

记忆、semantic recall 和 embedder 都留在你这一侧

记忆是 Mastra 的功能,由你自己的存储支撑:安装 @mastra/memory 和一个存储适配器(例如 @mastra/libsql),并在每次调用时传入 resource 和 thread。之后消息历史默认会把最近 10 条消息(lastMessages)带进每个请求,这会增加每一步的输入 token,但不会多发请求。semantic recall 默认关闭,开启还需要另外两样东西:向量库和 embedder;每一轮对话都会调用 embedder,先为检索给新提示词生成向量,之后再为索引给新消息生成向量。Router One 不提供 /v1/embeddings,所以不要把 embedder 指向网关:让它留在提供 embeddings 的 provider 上,例如 ModelRouterEmbeddingModel('openai/text-embedding-3-small') 配该 provider 自己的 Key,或者用 @mastra/fastembed 里的 fastembed 在本地运行。这些调用不会出现在 Dashboard → Logs 里。Observational Memory 则不同:它的 Observer 和 Reflector 是聊天模型调用,不指定模型时默认使用一个 Google 模型,经 Mastra 的模型路由、用你环境里的 Google API Key 解析,不经过网关。它的 model 选项接受的类型与 Agent 的 model 相同,所以传入同一个带 id、url 和 apiKey 的对象,这些后台调用就会走你的 Router One Key 并出现在 Logs 里。generateTitle 同样可以指定 model,不指定时使用 Agent 自己的模型。线程、向量和 working memory 都保存在你配置的存储里,网关只看到每次模型请求里的消息。

Mastra 该填哪个模型 ID?

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

Mastra 用的是哪种 API 协议?

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

在 trace 里验证 Mastra 的调用

先在 Mastra 发出一次简单文本请求,再到 Dashboard → Logs 按时间、模型和 request_id 核对 trace:tokens、花费、延迟和状态码。成功后再分别验证流式输出、工具调用和多轮历史。失败时保留实际请求路径、完整错误消息及 request_id;没有对应日志时,先查客户端配置和网络,不能仅凭客户端报错认定是网关或上游故障。

常见问题

模型 ID 明明是从 /models 复制的,调用却报模型错误。Mastra 实际发出了什么?

Mastra 把 model.id 的第一段当作 provider 标签,只发送后面的部分。写 id: 'anthropic/claude-sonnet-5' 并设置 url 时,请求体里是 model: claude-sonnet-5,'openai/gpt-5.5' 会变成 gpt-5.5;而在 Router One,厂商前缀是目录 ID 的一部分,被截短的名字并不是你选的那个模型。把 id 写成 custom/ 加完整的目录 ID:custom/anthropic/claude-sonnet-5、custom/openai/gpt-5.5、custom/grok-4.6。完全不含斜杠的 ID 根本不会离开你的进程:Mastra 会抛出 [Agent:<name>] - Failed to resolve model configuration,details.originalError 的内容是 Attempted to parse provider/model from grok-4.6 but this ID doesn't appear to contain a provider。不要用 netlify 或 mastra 作标签,它们是 Mastra 的 gateway 名,带 gateway 前缀的 ID 会按 gateway/provider/model 解析。所有 HTTP 失败都以 AI_APICallError 出现:generate() 会把它抛出,stream() 则把它交给 onError 和 stream.error,textStream 只是直接结束。查看错误上的 statusCode、url 和 responseBody。404 且 url 是 https://api.router.one/chat/completions,说明 model.url 少了 /v1,网关的消息会提示你修正 base URL。401 是 Key 未被接受,或者 apiKey 为 undefined、请求没有带 Authorization 请求头。Mastra 还会记录一条 Upstream LLM API error 日志,里面有 provider 和 modelId,这个 modelId 就是实际发出的模型字符串。

可以不用模型对象,改传 AI SDK 的 provider 吗?两种写法各自调用哪个端点?

可以。Agent 的 model 也接受 AI SDK 的语言模型,此时不会做任何 ID 解析:你传什么字符串,发出去的就是什么。@ai-sdk/openai-compatible 的 createOpenAICompatible 传入 name、baseURL 和 apiKey 后调用的是 /chat/completions,chatModel('anthropic/claude-sonnet-5') 会原样发出;Mastra 的对象写法内部构造的就是同一个 provider。加上 includeUsage: true 会让流式请求带上含 include_usage 的 stream_options,对象写法不会发送它,所以需要在流式结果上拿到 token 用量时选这条路径。@ai-sdk/openai 的 createOpenAI 配 baseURL 则不同:从 AI SDK 5 起,它的默认调用 provider(modelId) 使用 Responses API,请求发往 /v1/responses,而 Router One 的这个端点只服务当前在售的 GPT 系列与 DeepSeek ID。用 provider.chat(modelId) 才会留在 /v1/chat/completions 上,该端点提供目录里的所有聊天模型。Mastra 文档还为对象写法介绍了 api: 'responses';除非模型详情页列出该模型和你要用的功能支持 /v1/responses,否则不要设置它。

Mastra Studio 和 Mastra 的 tracing 会向 Router One 发送什么吗?

只有模型调用。mastra dev 会在你本机的 localhost:4111 启动 Studio,它列出的是在 src/mastra/index.ts 的 Mastra 实例上注册的 Agent;在那里聊天运行的是同一个 Agent、同一个模型对象,所以每一步都会像你自己代码发出的调用一样出现在 Dashboard → Logs 里。Trace、日志和指标由 @mastra/observability 采集,写入你配置的存储或 exporter;路由模型写入 span 的内容里不含 apiKey、headers 和 url。那里的 token 数来自 API 报告的 usage,成本是 Mastra 的估算,所以实际扣费以 Logs 为准。如果想把本地 Studio 的调用和线上流量分开,就给它单独用一把 Key。

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

选用当前目录中同时支持 Mastra 所用端点和所需功能的模型。精确 ID、当前单价与能力以 /models 及模型详情为准;不要仅按 GPT、Claude 等系列名判断兼容性。客户端能列出模型,只证明模型发现成功,仍需验证实际调用。

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

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

中国大陆能直连吗?

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

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

先到 Dashboard → Logs 核对请求及错误消息。401 查 Key 是否传入和有效;402 查钱包余额与 maxSpend;403 查 Key 权限和访问限制;429 查请求频率、token 限额及上游限流,按错误来源处理。保留 request_id,再按错误码速查页逐项排查。