📌 内容摘要
- 从安装 SDK 到生产可用,覆盖 Node.js 调用 Claude API 的完整链路。
- JavaScript 和 TypeScript 双版本代码,每个示例都能直接运行。
- 重点覆盖四个核心功能:基础调用、流式输出(SSE)、多轮对话、Tool Use 工具调用。
- 附生产级封装:错误处理、重试逻辑、环境变量管理,可直接用于实际项目。
一、环境准备
Node.js 版本要求
# 检查 Node.js 版本(需要 18.0.0 以上) node --version # 如果版本太低,到 nodejs.org 下载最新 LTS 版本 # 推荐使用 nvm 管理多版本: # Mac/Linux: curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash # 然后: nvm install --lts && nvm use --lts
创建项目并安装 SDK
# 创建项目目录
mkdir claude-demo && cd claude-demo
# 初始化项目
npm init -y
# 安装 Anthropic SDK
npm install @anthropic-ai/sdk
# 如果要用 TypeScript(推荐)
npm install -D typescript @types/node ts-node
npx tsc --init
# 验证安装
node -e "const a = require('@anthropic-ai/sdk'); console.log('SDK 版本:', require('./node_modules/@anthropic-ai/sdk/package.json').version)"
设置 API Key
# 方式一:环境变量(推荐) export ANTHROPIC_API_KEY="sk-ant-api03-你的key" # Mac/Linux $env:ANTHROPIC_API_KEY = "sk-ant-api03-你的key" # Windows PowerShell # 方式二:.env 文件(项目开发推荐) npm install dotenv # 创建 .env 文件 echo "ANTHROPIC_API_KEY=sk-ant-api03-你的key" > .env echo ".env" >> .gitignore # 防止泄漏到 git
二、基础调用
JavaScript 版本
// basic.js
require("dotenv").config();
const Anthropic = require("@anthropic-ai/sdk");
const client = new Anthropic(); // 自动读取 ANTHROPIC_API_KEY 环境变量
async function main() {
const message = await client.messages.create({
model: "claude-haiku-4-5-20251001", // 练习用最便宜的模型
max_tokens: 256,
messages: [
{
role: "user",
content: "你好!用一句话介绍你自己。"
}
]
});
// 提取回复文本
console.log(message.content[0].text);
// 查看 token 用量
console.log(`\n输入:${message.usage.input_tokens} tokens`);
console.log(`输出:${message.usage.output_tokens} tokens`);
console.log(`停止原因:${message.stop_reason}`);
}
main().catch(console.error);
node basic.js
TypeScript 版本
// basic.ts
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY, // 显式传入(可选,不传也会自动读取)
});
async function main(): Promise {
const message = await client.messages.create({
model: "claude-haiku-4-5-20251001",
max_tokens: 256,
messages: [
{
role: "user",
content: "你好!用一句话介绍你自己。"
}
]
});
// TypeScript 类型安全的方式提取文本
const text = message.content
.filter(block => block.type === "text")
.map(block => block.text)
.join("");
console.log(text);
console.log(`\n输入:${message.usage.input_tokens} tokens`);
console.log(`输出:${message.usage.output_tokens} tokens`);
}
main().catch(console.error);
npx ts-node basic.ts
关键参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
model |
string,必填 | 模型名称,见下方模型列表 |
max_tokens |
number,必填 | 最多生成多少 token,无默认值 |
messages |
array,必填 | 对话历史,role 只能是 “user” 或 “assistant” |
system |
string,可选 | 角色设定,不放在 messages 里 |
temperature |
number 0-1,可选 | 输出随机性,0最确定,1最多样 |
三、设置角色(System Prompt)
// system.js
const Anthropic = require("@anthropic-ai/sdk");
const client = new Anthropic();
async function askExpert(question) {
const response = await client.messages.create({
model: "claude-haiku-4-5-20251001",
max_tokens: 512,
system: "你是一名资深的前端工程师,擅长 React 和 TypeScript。" +
"回答时给出具体的代码示例,使用中文,代码用英文注释。",
messages: [
{ role: "user", content: question }
]
});
return response.content[0].text;
}
(async () => {
const answer = await askExpert("如何用 React hooks 实现一个防抖函数?");
console.log(answer);
})();
四、流式输出
流式输出让用户看到逐字生成的效果,而不是等待完整响应。Node.js SDK 有两种流式方式:
方式一:.stream() 方法(推荐,更简洁)
// stream.js
const Anthropic = require("@anthropic-ai/sdk");
const client = new Anthropic();
async function streamChat(userMessage) {
process.stdout.write("Claude:");
// .stream() 返回一个流对象
const stream = client.messages.stream({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: userMessage }],
});
// 逐块输出文本
stream.on("text", (text) => {
process.stdout.write(text);
});
// 等待流结束,获取完整统计
const finalMessage = await stream.finalMessage();
console.log(); // 换行
console.log(`\n输入:${finalMessage.usage.input_tokens} tokens`);
console.log(`输出:${finalMessage.usage.output_tokens} tokens`);
console.log(`停止原因:${finalMessage.stop_reason}`);
return finalMessage;
}
streamChat("用 100 字介绍一下 Node.js 的事件循环机制");
方式二:for await…of 迭代器(适合需要处理每个事件的场景)
// stream_events.js
const Anthropic = require("@anthropic-ai/sdk");
const client = new Anthropic();
async function streamWithEvents(userMessage) {
const stream = await client.messages.stream({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: userMessage }],
});
// 迭代所有流事件
for await (const event of stream) {
switch (event.type) {
case "message_start":
// 请求开始
break;
case "content_block_delta":
// 文本增量
if (event.delta.type === "text_delta") {
process.stdout.write(event.delta.text);
}
break;
case "message_delta":
// 消息结束时的 stop_reason 和用量
if (event.usage) {
console.log(`\n输出 tokens:${event.usage.output_tokens}`);
}
break;
case "message_stop":
console.log("\n--- 流结束 ---");
break;
}
}
}
streamWithEvents("列举 JavaScript 的5个新特性");
TypeScript 类型安全的流式输出
// stream.ts import Anthropic from "@anthropic-ai/sdk"; const client = new Anthropic(); async function streamChat(userMessage: string): Promise{ let fullText = ""; process.stdout.write("Claude:"); const stream = client.messages.stream({ model: "claude-sonnet-4-6", max_tokens: 1024, messages: [{ role: "user", content: userMessage }], }); // TypeScript 自动推断类型 for await (const chunk of stream) { if ( chunk.type === "content_block_delta" && chunk.delta.type === "text_delta" ) { process.stdout.write(chunk.delta.text); fullText += chunk.delta.text; } } const finalMsg = await stream.finalMessage(); console.log(`\n\n共 ${finalMsg.usage.output_tokens} output tokens`); return fullText; } streamChat("简单介绍 TypeScript 的泛型").catch(console.error);
五、多轮对话
// conversation.js
const Anthropic = require("@anthropic-ai/sdk");
const readline = require("readline");
const client = new Anthropic();
// 对话历史(每次调用都传完整历史)
const history = [];
async function chat(userInput) {
// 加入用户消息
history.push({ role: "user", content: userInput });
// 控制历史长度(最多保留20条,防止 token 超限)
const recentHistory = history.slice(-20);
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
system: "你是一个友善的 AI 助手,记住整个对话的上下文。",
messages: recentHistory,
});
const reply = response.content
.filter(b => b.type === "text")
.map(b => b.text)
.join("");
// 加入 AI 回复
history.push({ role: "assistant", content: reply });
return reply;
}
// 交互式命令行聊天
async function startChat() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
console.log("开始聊天(输入 exit 退出)\n");
const ask = () => {
rl.question("你:", async (input) => {
if (input.trim() === "exit") {
console.log("再见!");
rl.close();
return;
}
try {
const reply = await chat(input);
console.log(`\nClaude:${reply}\n`);
} catch (err) {
console.error("出错了:", err.message);
}
ask(); // 继续下一轮
});
};
ask();
}
startChat();
六、Tool Use(工具调用)
// tools.js
const Anthropic = require("@anthropic-ai/sdk");
const client = new Anthropic();
// 定义工具
const tools = [
{
name: "get_weather",
description: "获取指定城市的当前天气",
input_schema: {
type: "object",
properties: {
city: {
type: "string",
description: "城市名称,如 '北京'、'上海'"
}
},
required: ["city"]
}
},
{
name: "calculate",
description: "执行数学计算",
input_schema: {
type: "object",
properties: {
expression: {
type: "string",
description: "数学表达式,如 '(100 + 200) * 1.08'"
}
},
required: ["expression"]
}
}
];
// 工具执行函数
function executeTool(name, input) {
switch (name) {
case "get_weather":
// 实际项目替换为真实 API
return JSON.stringify({
city: input.city,
temperature: 22,
condition: "晴",
humidity: "45%"
});
case "calculate":
try {
const result = Function(`"use strict"; return (${input.expression})`)();
return String(result);
} catch {
return "计算出错,请检查表达式格式";
}
default:
return JSON.stringify({ error: `未知工具:${name}` });
}
}
// 带工具调用的完整对话循环
async function chatWithTools(userMessage) {
const messages = [{ role: "user", content: userMessage }];
while (true) {
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
tools,
messages,
});
console.log(`stop_reason: ${response.stop_reason}`);
// 自然结束
if (response.stop_reason === "end_turn") {
const finalText = response.content
.filter(b => b.type === "text")
.map(b => b.text)
.join("");
return finalText;
}
// 需要调用工具
if (response.stop_reason === "tool_use") {
// 把 AI 的回复加入消息历史
messages.push({ role: "assistant", content: response.content });
// 执行所有工具调用
const toolResults = [];
for (const block of response.content) {
if (block.type === "tool_use") {
console.log(`调用工具:${block.name}(${JSON.stringify(block.input)})`);
const result = executeTool(block.name, block.input);
console.log(`工具结果:${result}`);
toolResults.push({
type: "tool_result",
tool_use_id: block.id,
content: result,
});
}
}
// 把工具结果放入 user 消息发回给 Claude
messages.push({ role: "user", content: toolResults });
// 继续循环,让 Claude 基于工具结果生成最终回复
}
}
}
// 测试
(async () => {
const result = await chatWithTools(
"北京今天天气怎么样?顺便帮我算一下 (1200 + 800) * 0.85 等于多少"
);
console.log("\n最终回复:\n", result);
})();
七、生产级封装
// claude-client.js
// 生产级封装:含重试、错误处理、日志
require("dotenv").config();
const Anthropic = require("@anthropic-ai/sdk");
class ClaudeClient {
constructor({
apiKey = process.env.ANTHROPIC_API_KEY,
defaultModel= "claude-sonnet-4-6",
maxRetries = 3,
timeout = 60_000,
} = {}) {
if (!apiKey) throw new Error("ANTHROPIC_API_KEY 未设置");
this.client = new Anthropic({
apiKey,
maxRetries: 0, // 关闭 SDK 内置重试,用我们自己的
timeout,
});
this.defaultModel = defaultModel;
this.maxRetries = maxRetries;
}
// 判断错误是否可以重试
_isRetryable(error) {
if (error instanceof Anthropic.RateLimitError) return true;
if (error instanceof Anthropic.InternalServerError) return true;
if (error instanceof Anthropic.APIConnectionError) return true;
if (error instanceof Anthropic.APIConnectionTimeoutError) return true;
if (error instanceof Anthropic.APIStatusError) {
return [500, 502, 503, 529].includes(error.status);
}
return false;
}
// 指数退避
_backoff(attempt) {
const base = Math.min(1000 * Math.pow(2, attempt), 30_000);
const jitter= Math.random() * 0.4 + 0.8; // 0.8–1.2 倍随机
return Math.floor(base * jitter);
}
// 带重试的调用
async complete({
messages,
system = "",
model = this.defaultModel,
maxTokens = 1024,
temperature,
}) {
let lastError;
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
try {
const params = {
model,
max_tokens: maxTokens,
messages,
};
if (system) params.system = system;
if (temperature !== undefined) params.temperature = temperature;
const response = await this.client.messages.create(params);
return {
text: response.content.filter(b => b.type === "text").map(b => b.text).join(""),
usage: response.usage,
model: response.model,
stop: response.stop_reason,
};
} catch (error) {
lastError = error;
// 不可重试的错误直接抛出
if (!this._isRetryable(error)) throw error;
if (attempt < this.maxRetries) {
// 优先读 Retry-After 响应头
const retryAfter = error.headers?.["retry-after"];
const wait = retryAfter
? parseFloat(retryAfter) * 1000
: this._backoff(attempt);
console.warn(`[Claude] 第 ${attempt + 1} 次失败,${(wait/1000).toFixed(1)}s 后重试...`);
await new Promise(r => setTimeout(r, wait));
}
}
}
throw lastError;
}
// 流式输出(带重试)
async *stream({
messages,
system = "",
model = this.defaultModel,
maxTokens = 1024,
}) {
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
try {
const params = { model, max_tokens: maxTokens, messages };
if (system) params.system = system;
const s = this.client.messages.stream(params);
for await (const chunk of s) {
if (
chunk.type === "content_block_delta" &&
chunk.delta.type === "text_delta"
) {
yield chunk.delta.text;
}
}
return; // 成功,退出重试循环
} catch (error) {
if (!this._isRetryable(error) || attempt >= this.maxRetries) throw error;
const wait = this._backoff(attempt);
console.warn(`[Claude] 流失败,${(wait/1000).toFixed(1)}s 后重试...`);
await new Promise(r => setTimeout(r, wait));
}
}
}
}
module.exports = { ClaudeClient };
// ── 使用示例 ──────────────────────────────────────
(async () => {
const claude = new ClaudeClient({
defaultModel: "claude-sonnet-4-6",
maxRetries: 3,
});
// 普通调用
const result = await claude.complete({
system: "你是一个简洁的助手,回答不超过100字。",
messages: [{ role: "user", content: "什么是闭包?" }],
maxTokens: 256,
});
console.log(result.text);
// 流式调用
process.stdout.write("\n流式输出:");
for await (const chunk of claude.stream({
messages: [{ role: "user", content: "数到5" }],
maxTokens: 64,
})) {
process.stdout.write(chunk);
}
console.log();
})();
八、Express 集成示例
// server.js
require("dotenv").config();
const express = require("express");
const Anthropic = require("@anthropic-ai/sdk");
const app = express();
const client = new Anthropic();
app.use(express.json());
// 普通接口
app.post("/api/chat", async (req, res) => {
const { message, system } = req.body;
if (!message) return res.status(400).json({ error: "message 不能为空" });
try {
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
system: system || "你是一个专业的 AI 助手。",
messages: [{ role: "user", content: message }],
});
res.json({
content: response.content[0].text,
inputTokens: response.usage.input_tokens,
outputTokens: response.usage.output_tokens,
});
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 流式接口(SSE)
app.post("/api/chat/stream", async (req, res) => {
const { message } = req.body;
if (!message) return res.status(400).json({ error: "message 不能为空" });
// 设置 SSE 响应头
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("X-Accel-Buffering", "no"); // 关闭 Nginx 缓冲
res.flushHeaders();
let closed = false;
req.on("close", () => { closed = true; });
try {
const stream = client.messages.stream({
model: "claude-sonnet-4-6",
max_tokens: 2048,
messages: [{ role: "user", content: message }],
});
for await (const chunk of stream) {
if (closed) break;
if (
chunk.type === "content_block_delta" &&
chunk.delta.type === "text_delta"
) {
res.write(`data: ${JSON.stringify({ text: chunk.delta.text })}\n\n`);
}
}
res.write(`data: ${JSON.stringify({ done: true })}\n\n`);
res.end();
} catch (err) {
if (!closed) {
res.write(`data: ${JSON.stringify({ error: err.message })}\n\n`);
res.end();
}
}
});
app.listen(3000, () => console.log("服务启动:http://localhost:3000"));
npm install express
node server.js
# 测试普通接口
curl -X POST http://localhost:3000/api/chat \
-H "Content-Type: application/json" \
-d '{"message": "你好"}'
# 测试流式接口
curl -X POST http://localhost:3000/api/chat/stream \
-H "Content-Type: application/json" \
-d '{"message": "数到5"}'
常见问题
Q:用 ES Module(import/export)还是 CommonJS(require)?
两种都支持。CommonJS(require)在 Node.js 里直接用,不需要额外配置;ES Module(import)需要在 package.json 里加 "type": "module" 或把文件改为 .mjs。TypeScript 项目推荐 ES Module 风格,tsconfig.json 设 "module": "commonjs" 编译后可兼容两种。
Q:流式输出的回调和 async iterator 哪个更好用?
简单场景用事件回调(stream.on("text", ...)),需要更精细控制每个事件类型时用 for await...of。本文两种都给出了示例,按需选择。如果接入 Express 或 Fastify 做 SSE 接口,for await...of 写法更清晰。
Q:多轮对话的历史记录存在内存里,服务重启就丢失了怎么办?
生产环境用 Redis 或数据库存储历史,session_id 作为 Key,每次请求时从存储里读取并传给 Claude API。本文的内存方案适合单用户 CLI 工具和快速原型,多用户 Web 应用需要持久化存储。
总结
Node.js 调用 Claude API 的核心要点:max_tokens 必须传(没有默认值);system 是独立参数不在 messages 里;流式输出用 .stream() 配合 for await...of;多轮对话需要自己维护消息历史数组并完整传入每次请求;工具调用完成后要把结果放在 user 角色的消息里传回。生产环境在初始化 Anthropic 客户端时关闭内置重试、用自己的指数退避逻辑,便于控制日志和监控。