Skip to content

第 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

bash
mkdir my-ai-app
cd my-ai-app
pnpm init

安装 AI SDK 及其他必要依赖:

bash
pnpm add ai zod dotenv
pnpm add -D @types/node tsx typescript
  • ai 包是 AI SDK 本体;
  • zod 用于定义类型安全的 schema 并传给 LLM;
  • dotenv 用于读取环境变量(AI Gateway key 或自定义 provider 凭证);
  • 三个 -D 开发依赖用于运行 TypeScript 代码。

2.3 配置密钥

在项目根目录创建 .env 文件:

bash
touch .env

编辑 .env,填入你的凭证:

env
# 方式一:Vercel AI Gateway
AI_GATEWAY_API_KEY=xxxxxxxxx

💡 AI SDK 会自动读取 AI_GATEWAY_API_KEY 环境变量完成 AI Gateway 认证。

若使用自定义 OpenAI 兼容 provider,则改为:

env
OPENAI_COMPATIBLE_BASE_URL=https://api.custom.com/v1
OPENAI_COMPATIBLE_API_KEY=yyyyyyyyy

2.4 第一个流式聊天程序

创建 index.ts

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 构造(其余完全相同):

ts
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'),

代码解读:

  1. 建立 readline 接口从终端读取输入,支持命令行交互会话;
  2. 初始化 messages 数组保存对话历史,让 Agent 在多轮对话中保持上下文;
  3. main 函数循环内:
    • 采集用户输入存入 userInput
    • 把输入以 user 消息加入 messages
    • 调用从 ai 包导入的 streamText,传入 modelmessages
    • 遍历 result.textStream 把增量文本实时打印到终端;
    • 将助手回复追加进 messages

运行应用:

bash
pnpm tsx index.ts

终端出现提示后输入消息,即可看到 AI 实时回复!

2.5 为 Agent 添加工具

LLM 生成能力很强,但面对离散任务(如数学计算)或与外部世界交互(如查天气)时力不从心——这正是 tools 的用武之地。

Tools 是 LLM 可以调用的动作,其结果会被回传给 LLM 参与下一轮响应。比如用户询问天气时,没有工具的 Agent 只能凭训练数据给出泛泛之谈;有了天气工具就能提供实时、具体位置的信息。

修改 index.ts 加入一个简单的天气工具:

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);

更新后的代码要点:

  1. ai 包导入 tool 函数;
  2. 定义含 weather 工具的 tools 对象,该工具:
    • 通过 description 帮助 Agent 理解何时使用它;
    • 用 Zod schema 定义 inputSchema,声明必须提供 location 字符串。Agent 会尝试从对话上下文提取该参数,取不到时会反问用户;
    • 定义 execute 异步函数模拟获取天气数据(这里返回随机温度)——它在服务端运行,完全可以替换为真实的第三方 API 调用。

试着问 "What's the weather in New York?" 观察 Agent 如何使用新工具。注意助手回复为空?这是因为 Agent 这次生成的是 tool call 而非文本。可以在结果的 toolCallstoolResults 键中访问它们:

ts
console.log(await result.toolCalls);
console.log(await result.toolResults);

2.6 启用多步工具调用

你可能注意到:工具结果虽然可见,但 Agent 并没有用它回答最初的问题——因为一旦生成了 tool call,本轮生成就算完成了。

解决方案是用 stopWhen 启用多步工具调用:它会自动把工具结果发回给 Agent 触发新一轮生成,直到满足你定义的停止条件。本例中我们希望 Agent 利用天气工具的结果作答。

继续修改 index.ts

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);

两处新增:

  1. stopWhen: isStepCount(5) 允许单次生成最多消耗 5 个「步骤」;
  2. onStepEnd 回调打印每一步的 toolResults,帮助观察 Agent 的工具使用情况(因此可以删掉上一例中的两个 console.log)。

2.7 添加第二个工具

再增加一个华氏转摄氏的工具,体会多步协作:

ts
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?",你会看到完整的交互链:

  1. Agent 调用 weather 工具查询纽约;
  2. 终端打印出工具结果;
  3. 接着调用温度转换工具,把华氏度换算成摄氏度;
  4. Agent 汇总信息,用自然语言回答纽约的气温。

这种多步方式让 Agent 能够收集信息并给出更准确、更贴合语境的回答。你可以创建更复杂的工具对接真实 API、数据库或任何外部系统,弥合模型知识截止时间与实时世界之间的鸿沟。

本章小结

  • 项目四件套:pnpm init → 安装 ai/zod/dotenv → 写 .envpnpm tsx index.ts 运行;
  • streamText + 遍历 textStream 是实现流式输出的标准姿势,messages 数组维护多轮上下文;
  • tool 三要素:description、Zod inputSchemaexecute 异步执行函数;
  • 默认一次 tool call 即结束生成;stopWhen: isStepCount(n) 让工具结果自动回流触发后续步骤;
  • 所有示例的 model 既可用 createGateway 构造,也可换成 createOpenAICompatible 自定义实例。

🛠️ 动手实践

  1. 完成 2.2–2.4 节的完整搭建,分别用 Gateway 和自定义 provider 各跑通一次终端聊天。
  2. 给 Agent 再加一个 getCurrentTime 工具(返回当前时间字符串),测试提问「现在几点了」时的行为差异。
  3. isStepCount(5) 改成 isStepCount(1) 再问天气问题,观察并解释现象;再改回 5 验证恢复。