Node.js
使用 Anthropic 和 OpenAI SDK 的 Node.js 示例:普通请求、流式输出、工具调用往返、图片、计算 token 数、列出模型和处理错误。
何时阅读本页
你在 Node.js 或 TypeScript 中通过官方 Anthropic SDK(@anthropic-ai/sdk)或 OpenAI SDK(openai)调用 EZAT Router。原始 HTTP 请求见 curl。
安装与配置
安装你要用的 SDK(或者两个都装),并把 key 放进环境变量:
npm install @anthropic-ai/sdk openai
export EZAT_API_KEY="ezat_sk_…"示例是使用顶层 await 的 TypeScript ES 模块。把一个示例保存为 example.mts,然后运行 node example.mts(Node.js 22.18 及以上版本可以直接运行 TypeScript);也可以去掉类型注解,保存为 example.mjs。
每个 SDK 只需要改 base URL:
| SDK | baseURL | 调用的端点 |
|---|---|---|
@anthropic-ai/sdk | https://api.router.ezat.io | /v1/messages |
openai | https://api.router.ezat.io/openai/v1 | /openai/v1/chat/completions |
两个 SDK 也会从环境变量读取 base URL 和 key,对于不是你自己编写的工具很方便:ANTHROPIC_BASE_URL 和 ANTHROPIC_API_KEY,或 OPENAI_BASE_URL 和 OPENAI_API_KEY。
普通请求
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ baseURL: "https://api.router.ezat.io", apiKey: process.env.EZAT_API_KEY });
const message = await client.messages.create({
model: "anthropic/claude-sonnet-5-5",
max_tokens: 64,
messages: [{ role: "user", content: "Say hello in five words." }],
});
for (const block of message.content) {
if (block.type === "text") console.log(block.text);
}
console.log(message.usage);
console.log("request-id:", message._request_id);usage 里是计费所依据的 token 数;见价格。
流式输出
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ baseURL: "https://api.router.ezat.io", apiKey: process.env.EZAT_API_KEY });
const stream = client.messages.stream({
model: "anthropic/claude-sonnet-5-5",
max_tokens: 64,
messages: [{ role: "user", content: "Count from one to five." }],
});
stream.on("text", (text) => process.stdout.write(text));
const message = await stream.finalMessage();
console.log("\n", message.usage);工具调用往返
模型请求调用工具,你的代码运行工具,再把结果连同目前为止的对话一起发回。
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ baseURL: "https://api.router.ezat.io", apiKey: process.env.EZAT_API_KEY });
const tools: Anthropic.Tool[] = [
{
name: "get_weather",
description: "Get the current weather in a city.",
input_schema: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
},
},
];
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: "What is the weather in Paris? Use the get_weather tool." },
];
const first = await client.messages.create({ model: "anthropic/claude-sonnet-5-5", max_tokens: 64, tools, messages });
const call = first.content.find((block): block is Anthropic.ToolUseBlock => block.type === "tool_use");
if (!call) throw new Error(`No tool call (stop_reason: ${first.stop_reason})`);
console.log("Tool call:", call.name, call.input);
// Run the tool, then send its result back.
messages.push({ role: "assistant", content: first.content });
messages.push({
role: "user",
content: [{ type: "tool_result", tool_use_id: call.id, content: "Sunny and 18 degrees Celsius" }],
});
const second = await client.messages.create({ model: "anthropic/claude-sonnet-5-5", max_tokens: 64, tools, messages });
for (const block of second.content) {
if (block.type === "text") console.log(block.text);
}图片
图片以 base64 内嵌发送。这里是一张 8×8 的红色 PNG;如果要用你自己的文件,可以用 node:fs 中的 readFileSync("photo.png").toString("base64")。
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ baseURL: "https://api.router.ezat.io", apiKey: process.env.EZAT_API_KEY });
const png =
"iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAEUlEQVR42mP4z8CAFTEMLQkAKP8/wc53yE8AAAAASUVORK5CYII=";
const message = await client.messages.create({
model: "anthropic/claude-sonnet-5-5",
max_tokens: 64,
messages: [
{
role: "user",
content: [
{ type: "image", source: { type: "base64", media_type: "image/png", data: png } },
{ type: "text", text: "What colour is this image? Answer in one word." },
],
},
],
});
for (const block of message.content) {
if (block.type === "text") console.log(block.text);
}计算 token 数
仅限 Anthropic SDK。计算 token 数免费,但会计入 key 的每分钟请求数。
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ baseURL: "https://api.router.ezat.io", apiKey: process.env.EZAT_API_KEY });
const count = await client.messages.countTokens({
model: "anthropic/claude-sonnet-5-5",
messages: [{ role: "user", content: "Say hello in five words." }],
});
console.log(count.input_tokens);列出模型
下面的代码列出你的 key 能调用的模型。带价格的公开模型目录见模型。
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ baseURL: "https://api.router.ezat.io", apiKey: process.env.EZAT_API_KEY });
for await (const model of client.models.list()) {
console.log(model.id, model.display_name);
}处理错误
每个 SDK 都会抛出自己的错误类。SDK 已经会对连接错误、429 和 5xx 重试两次,所以你捕获到的错误已经用完了重试次数(如需重试更久,请在客户端上调高 maxRetries)。这个请求使用了一个不存在的模型,所以会以 404 失败。
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ baseURL: "https://api.router.ezat.io", apiKey: process.env.EZAT_API_KEY });
try {
await client.messages.create({
model: "anthropic/no-such-model",
max_tokens: 16,
messages: [{ role: "user", content: "Hi" }],
});
} catch (error) {
if (error instanceof Anthropic.APIConnectionError) {
console.error("Could not reach the API:", error.message);
process.exit(1);
}
if (!(error instanceof Anthropic.APIError)) throw error;
if (error instanceof Anthropic.RateLimitError) {
console.log("Rate limited. Retry after", error.headers?.get("retry-after"), "seconds");
}
console.log("status:", error.status, "type:", error.type);
// request-id is set once a request reaches a model; x-request-id is on every response.
console.log("request-id:", error.requestID ?? "none");
console.log("x-request-id:", error.headers?.get("x-request-id"));
}| 类 | 状态码 |
|---|---|
BadRequestError | 400 |
AuthenticationError | 401 |
status 为 402 的 APIError | 402:需要充值 |
PermissionDeniedError | 403 |
NotFoundError | 404 |
RateLimitError | 429 |
InternalServerError | 500 及以上,包括 529(Anthropic)和 503(OpenAI) |
APIConnectionError | 没有响应:网络问题或超时 |
每个状态码的含义以及是否应该重试,见错误。
给 AI agent 的说明
- 对
@anthropic-ai/sdk把baseURL设为https://api.router.ezat.io,对openai设为https://api.router.ezat.io/openai/v1,并从process.env.EZAT_API_KEY传入 key。 - 使用
GET https://api.router.ezat.io/v1/models返回的模型 id;不要自己编造。 - 使用 Anthropic SDK 时,
max_tokens为必填项。 - 除非需要超过两次重试,否则不要在 SDK 之上为 429 和 5xx 再加一层自己的重试循环;改为调高
maxRetries。 - 记录错误中的
request-id(或x-request-id),方便用户提供给 support@ezat.io。