AI SDK
AI SDK 是 Vercel 开发的一个强大的 TypeScript 库,专门用于帮助开发者构建 AI 驱动的应用程序。它提供了统一的接口来与各种大语言模型进行交互,让你可以轻松地在不同模型和提供商之间切换。
前置条件
在开始之前,你需要:
- Node.js 18+ 和 pnpm 安装在本地开发机器上
- OpenAI API 密钥
如果还没有获取 OpenAI API 密钥,可以在 OpenAI 官网 注册获取。
创建应用程序
1. 创建 Next.js 应用
pnpm create next-app@latest my-ai-app
确保在提示时选择使用 App Router 和 Tailwind CSS。
2. 安装依赖
cd my-ai-app
pnpm add ai @ai-sdk/react @ai-sdk/openai zod
ai
: AI SDK 核心包@ai-sdk/react
: AI SDK 的 React hooks@ai-sdk/openai
: AI SDK 的 OpenAI 提供商zod
: 用于数据验证
3. 配置 OpenAI API 密钥
创建 .env.local
文件:
touch .env.local
在文件中添加你的 OpenAI API 密钥:
OPENAI_API_KEY=your_api_key_here
创建路由处理器
创建 app/api/chat/route.ts
文件:
import { openai } from "@ai-sdk/openai";
import { streamText, UIMessage, convertToModelMessages } from "ai";
// 允许流式响应最多 30 秒
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: openai("gpt-4o"),
messages: convertToModelMessages(messages),
});
return result.toUIMessageStreamResponse();
}
代码解析
- 提取消息: 从请求体中提取
messages
,包含用户与聊天机器人的对话历史 - 调用 streamText: 使用 AI SDK 的
streamText
函数,传入模型提供商和消息 - 转换消息格式: 使用
convertToModelMessages
将 UI 消息转换为模型消息格式 - 返回流式响应: 使用
toUIMessageStreamResponse()
返回流式响应
连接 UI
更新 app/page.tsx
文件:
"use client";
import { useChat } from "@ai-sdk/react";
import { useState } from "react";
export default function Chat() {
const [input, setInput] = useState("");
const { messages, sendMessage } = useChat();
return (
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
{messages.map((message) => (
<div key={message.id} className="whitespace-pre-wrap">
{message.role === "user" ? "User: " : "AI: "}
{message.parts.map((part, i) => {
switch (part.type) {
case "text":
return <div key={`${message.id}-${i}`}>{part.text}</div>;
}
})}
</div>
))}
<form
onSubmit={(e) => {
e.preventDefault();
sendMessage({ text: input });
setInput("");
}}
>
<input
className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
value={input}
placeholder="Say something..."
onChange={(e) => setInput(e.currentTarget.value)}
/>
</form>
</div>
);
}
useChat Hook 解析
messages
: 当前聊天消息数组,每个对象包含id
、role
和parts
属性sendMessage
: 发送消息到聊天 API 的函数parts
: 消息部分数组,包含模型生成的所有内容(文本、推理令牌等)
运行应用程序
pnpm run dev
访问 http://localhost:3000
即可看到聊天界面。
增强聊天机器人功能
添加工具支持
大语言模型在离散任务(如数学计算)和与外部世界交互(如获取天气)方面存在局限性,这时就需要工具来扩展功能。
更新 app/api/chat/route.ts
:
import { openai } from "@ai-sdk/openai";
import { streamText, UIMessage, convertToModelMessages, tool } from "ai";
import { z } from "zod";
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: openai("gpt-4o"),
messages: convertToModelMessages(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,
};
},
}),
},
});
return result.toUIMessageStreamResponse();
}
更新 UI 以显示工具调用
更新 app/page.tsx
:
"use client";
import { useChat } from "@ai-sdk/react";
import { useState } from "react";
export default function Chat() {
const [input, setInput] = useState("");
const { messages, sendMessage } = useChat();
return (
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
{messages.map((message) => (
<div key={message.id} className="whitespace-pre-wrap">
{message.role === "user" ? "User: " : "AI: "}
{message.parts.map((part, i) => {
switch (part.type) {
case "text":
return <div key={`${message.id}-${i}`}>{part.text}</div>;
case "tool-weather":
return <pre key={`${message.id}-${i}`}>{JSON.stringify(part, null, 2)}</pre>;
}
})}
</div>
))}
<form
onSubmit={(e) => {
e.preventDefault();
sendMessage({ text: input });
setInput("");
}}
>
<input
className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
value={input}
placeholder="Say something..."
onChange={(e) => setInput(e.currentTarget.value)}
/>
</form>
</div>
);
}
启用多步工具调用
默认情况下,模型在生成工具调用后就完成了生成。要让它使用工具结果来回答问题,需要启用多步工具调用。
更新路由处理器
import { openai } from "@ai-sdk/openai";
import { streamText, UIMessage, convertToModelMessages, tool, stepCountIs } from "ai";
import { z } from "zod";
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: openai("gpt-4o"),
messages: convertToModelMessages(messages),
stopWhen: stepCountIs(5), // 允许最多 5 步
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,
};
},
}),
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,
};
},
}),
},
});
return result.toUIMessageStreamResponse();
}
更新前端以处理新工具
"use client";
import { useChat } from "@ai-sdk/react";
import { useState } from "react";
export default function Chat() {
const [input, setInput] = useState("");
const { messages, sendMessage } = useChat();
return (
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
{messages.map((message) => (
<div key={message.id} className="whitespace-pre-wrap">
{message.role === "user" ? "User: " : "AI: "}
{message.parts.map((part, i) => {
switch (part.type) {
case "text":
return <div key={`${message.id}-${i}`}>{part.text}</div>;
case "tool-weather":
case "tool-convertFahrenheitToCelsius":
return <pre key={`${message.id}-${i}`}>{JSON.stringify(part, null, 2)}</pre>;
}
})}
</div>
))}
<form
onSubmit={(e) => {
e.preventDefault();
sendMessage({ text: input });
setInput("");
}}
>
<input
className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
value={input}
placeholder="Say something..."
onChange={(e) => setInput(e.currentTarget.value)}
/>
</form>
</div>
);
}
核心概念总结
1. 消息类型
- UIMessage: 用于应用程序 UI 的消息类型,包含完整消息历史和元数据
- ModelMessage: 模型期望的消息格式,不包含 UI 特定的元数据
2. 工具系统
- 工具定义: 使用
tool()
函数定义工具,包含描述、输入模式和执行函数 - 多步调用: 通过
stopWhen
配置允许模型进行多步工具调用 - 工具结果: 模型可以使用工具结果来生成更准确的响应
3. 流式响应
- 实时更新: 使用
streamText
实现实时响应流 - 消息部分: 每个消息包含
parts
数组,按生成顺序排列
4. React Hooks
- useChat: 提供聊天功能的核心 Hook,管理消息状态和发送功能
- 自动路由: 默认使用
/api/chat
路由,可自定义
扩展功能
AI SDK 还支持:
- 多模态: 处理图像、音频等多种输入类型
- 嵌入: 生成文本嵌入用于搜索和相似性计算
- 结构化数据: 生成 JSON 格式的结构化响应
- 错误处理: 内置错误处理和重试机制
- 测试: 提供测试工具和模拟器
最佳实践
- 环境变量: 始终使用环境变量存储 API 密钥
- 错误处理: 实现适当的错误处理和用户反馈
- 类型安全: 使用 TypeScript 和 Zod 确保类型安全
- 性能优化: 合理设置超时时间和步数限制
- 用户体验: 提供加载状态和错误提示
通过 AI SDK,你可以快速构建功能强大的 AI 应用程序,而无需处理复杂的模型集成和流式响应逻辑。