第 2 章 · 快速开始:环境搭建与第一次生成
本章目标:
- 从零搭建一个可运行的 AI SDK Node.js 项目
- 配置 Vercel AI Gateway 密钥并理解自定义 provider 的等价配置
- 用
streamText实现终端流式聊天- 为 Agent 添加 tools 并启用
stopWhen多步工具调用
2.1 前置条件
跟随本章实操,你需要:
- 本机安装 Node.js 22+ 与 pnpm;
- 一个 Vercel AI Gateway API key(在 Vercel 官网注册获取);
- 或者:任意 OpenAI 兼容服务端的
baseURL+apiKey(自定义 provider 方式,见 2.4 节)。
2.2 创建应用与安装依赖
用 mkdir 新建目录,进入后执行 pnpm init 生成 package.json:
mkdir my-ai-app
cd my-ai-app
pnpm init安装 AI SDK 及其他必要依赖:
pnpm add ai zod dotenv
pnpm add -D @types/node tsx typescriptai包是 AI SDK 本体;zod用于定义类型安全的 schema 并传给 LLM;dotenv用于读取环境变量(AI Gateway key 或自定义 provider 凭证);- 三个
-D开发依赖用于运行 TypeScript 代码。
2.3 配置密钥
在项目根目录创建 .env 文件:
touch .env编辑 .env,填入你的凭证:
# 方式一:Vercel AI Gateway
AI_GATEWAY_API_KEY=xxxxxxxxx💡 AI SDK 会自动读取
AI_GATEWAY_API_KEY环境变量完成 AI Gateway 认证。若使用自定义 OpenAI 兼容 provider,则改为:
envOPENAI_COMPATIBLE_BASE_URL=https://api.custom.com/v1 OPENAI_COMPATIBLE_API_KEY=yyyyyyyyy
2.4 第一个流式聊天程序
创建 index.ts:
import { ModelMessage, streamText, createGateway } from 'ai';
import 'dotenv/config';
import * as readline from 'node:readline/promises';
const gateway = createGateway({
apiKey: process.env.AI_GATEWAY_API_KEY ?? '',
});
const terminal = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const messages: ModelMessage[] = [];
async function main() {
while (true) {
const userInput = await terminal.question('You: ');
messages.push({ role: 'user', content: userInput });
const result = streamText({
model: gateway('openai/gpt-5'),
messages,
});
let fullResponse = '';
process.stdout.write('\nAssistant: ');
for await (const delta of result.textStream) {
fullResponse += delta;
process.stdout.write(delta);
}
process.stdout.write('\n\n');
messages.push({ role: 'assistant', content: fullResponse });
}
}
main().catch(console.error);自定义 provider 版本只需替换 model 构造(其余完全相同):
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
const myProvider = createOpenAICompatible({
name: 'my-provider',
baseURL: process.env.OPENAI_COMPATIBLE_BASE_URL ?? '',
apiKey: process.env.OPENAI_COMPATIBLE_API_KEY ?? '',
});
// 之后把 model 改为:
// model: myProvider('gpt-4o-mini'),代码解读:
- 建立 readline 接口从终端读取输入,支持命令行交互会话;
- 初始化
messages数组保存对话历史,让 Agent 在多轮对话中保持上下文; main函数循环内:- 采集用户输入存入
userInput; - 把输入以 user 消息加入
messages; - 调用从
ai包导入的streamText,传入model与messages; - 遍历
result.textStream把增量文本实时打印到终端; - 将助手回复追加进
messages。
- 采集用户输入存入
运行应用:
pnpm tsx index.ts终端出现提示后输入消息,即可看到 AI 实时回复!
2.5 为 Agent 添加工具
LLM 生成能力很强,但面对离散任务(如数学计算)或与外部世界交互(如查天气)时力不从心——这正是 tools 的用武之地。
Tools 是 LLM 可以调用的动作,其结果会被回传给 LLM 参与下一轮响应。比如用户询问天气时,没有工具的 Agent 只能凭训练数据给出泛泛之谈;有了天气工具就能提供实时、具体位置的信息。
修改 index.ts 加入一个简单的天气工具:
import { ModelMessage, streamText, tool, createGateway } from 'ai';
import 'dotenv/config';
import { z } from 'zod';
import * as readline from 'node:readline/promises';
const gateway = createGateway({
apiKey: process.env.AI_GATEWAY_API_KEY ?? '',
});
const terminal = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const messages: ModelMessage[] = [];
async function main() {
while (true) {
const userInput = await terminal.question('You: ');
messages.push({ role: 'user', content: userInput });
const result = streamText({
model: gateway('openai/gpt-5'),
messages,
tools: {
weather: tool({
description: 'Get the weather in a location (fahrenheit)',
inputSchema: z.object({
location: z
.string()
.describe('The location to get the weather for'),
}),
execute: async ({ location }) => {
const temperature = Math.round(Math.random() * (90 - 32) + 32);
return {
location,
temperature,
};
},
}),
},
});
let fullResponse = '';
process.stdout.write('\nAssistant: ');
for await (const delta of result.textStream) {
fullResponse += delta;
process.stdout.write(delta);
}
process.stdout.write('\n\n');
messages.push({ role: 'assistant', content: fullResponse });
}
}
main().catch(console.error);更新后的代码要点:
- 从
ai包导入tool函数; - 定义含
weather工具的tools对象,该工具:- 通过
description帮助 Agent 理解何时使用它; - 用 Zod schema 定义
inputSchema,声明必须提供location字符串。Agent 会尝试从对话上下文提取该参数,取不到时会反问用户; - 定义
execute异步函数模拟获取天气数据(这里返回随机温度)——它在服务端运行,完全可以替换为真实的第三方 API 调用。
- 通过
试着问 "What's the weather in New York?" 观察 Agent 如何使用新工具。注意助手回复为空?这是因为 Agent 这次生成的是 tool call 而非文本。可以在结果的 toolCalls 与 toolResults 键中访问它们:
console.log(await result.toolCalls);
console.log(await result.toolResults);2.6 启用多步工具调用
你可能注意到:工具结果虽然可见,但 Agent 并没有用它回答最初的问题——因为一旦生成了 tool call,本轮生成就算完成了。
解决方案是用 stopWhen 启用多步工具调用:它会自动把工具结果发回给 Agent 触发新一轮生成,直到满足你定义的停止条件。本例中我们希望 Agent 利用天气工具的结果作答。
继续修改 index.ts:
import { ModelMessage, streamText, tool, isStepCount, createGateway } from 'ai';
import 'dotenv/config';
import { z } from 'zod';
import * as readline from 'node:readline/promises';
const gateway = createGateway({
apiKey: process.env.AI_GATEWAY_API_KEY ?? '',
});
const terminal = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const messages: ModelMessage[] = [];
async function main() {
while (true) {
const userInput = await terminal.question('You: ');
messages.push({ role: 'user', content: userInput });
const result = streamText({
model: gateway('openai/gpt-5'),
messages,
tools: {
weather: tool({
description: 'Get the weather in a location (fahrenheit)',
inputSchema: z.object({
location: z
.string()
.describe('The location to get the weather for'),
}),
execute: async ({ location }) => {
const temperature = Math.round(Math.random() * (90 - 32) + 32);
return {
location,
temperature,
};
},
}),
},
stopWhen: isStepCount(5),
onStepEnd: async ({ toolResults }) => {
if (toolResults.length) {
console.log(JSON.stringify(toolResults, null, 2));
}
},
});
let fullResponse = '';
process.stdout.write('\nAssistant: ');
for await (const delta of result.textStream) {
fullResponse += delta;
process.stdout.write(delta);
}
process.stdout.write('\n\n');
messages.push({ role: 'assistant', content: fullResponse });
}
}
main().catch(console.error);两处新增:
stopWhen: isStepCount(5)允许单次生成最多消耗 5 个「步骤」;onStepEnd回调打印每一步的toolResults,帮助观察 Agent 的工具使用情况(因此可以删掉上一例中的两个console.log)。
2.7 添加第二个工具
再增加一个华氏转摄氏的工具,体会多步协作:
convertFahrenheitToCelsius: tool({
description: 'Convert a temperature in fahrenheit to celsius',
inputSchema: z.object({
temperature: z
.number()
.describe('The temperature in fahrenheit to convert'),
}),
execute: async ({ temperature }) => {
const celsius = Math.round((temperature - 32) * (5 / 9));
return {
celsius,
};
},
}),把它放进上面代码的 tools 对象后,问一句 "What's the weather in New York in celsius?",你会看到完整的交互链:
- Agent 调用 weather 工具查询纽约;
- 终端打印出工具结果;
- 接着调用温度转换工具,把华氏度换算成摄氏度;
- Agent 汇总信息,用自然语言回答纽约的气温。
这种多步方式让 Agent 能够收集信息并给出更准确、更贴合语境的回答。你可以创建更复杂的工具对接真实 API、数据库或任何外部系统,弥合模型知识截止时间与实时世界之间的鸿沟。
本章小结
- 项目四件套:
pnpm init→ 安装ai/zod/dotenv→ 写.env→pnpm tsx index.ts运行; streamText+ 遍历textStream是实现流式输出的标准姿势,messages数组维护多轮上下文;- tool 三要素:
description、ZodinputSchema、execute异步执行函数; - 默认一次 tool call 即结束生成;
stopWhen: isStepCount(n)让工具结果自动回流触发后续步骤; - 所有示例的 model 既可用
createGateway构造,也可换成createOpenAICompatible自定义实例。
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. tool 的哪个属性帮助 Agent 理解何时应该使用这个工具?
2. 为什么第一次添加 weather 工具后,助手的文本回复是空的?
3. stopWhen: isStepCount(5) 的作用是什么?
4. 若想改用自定义 OpenAI 兼容服务端,需要改动哪些内容?
🛠️ 动手实践
- 完成 2.2–2.4 节的完整搭建,分别用 Gateway 和自定义 provider 各跑通一次终端聊天。
- 给 Agent 再加一个
getCurrentTime工具(返回当前时间字符串),测试提问「现在几点了」时的行为差异。 - 把
isStepCount(5)改成isStepCount(1)再问天气问题,观察并解释现象;再改回 5 验证恢复。