关于vercel ai sdk的入门记录
为什么选择vercel ai
当前端开发准备开始搭建ai应用的时候,需要考虑很多因素。
- 首先,不同的供应商的接口差异化巨大,如果开发一半需要换模型成本巨高。
- 其次,如果供应商的api升级了,我们也需要同步升级。在ai以周为单位的进步速度,想要学习和维护的成本非常高。
- 第三,全栈开发如何兼顾前后端开发的一致性,后端需要顾及文本处理,前端需要处理状态管理和流式响应,这些处理需要手动封装。
vercel ai sdk 给出了解决方案。
快速使用
安装可以直接看官网,以nextjs为例。这里可能需要理解的是为什么要安装zod这个库。zod一般被用来作为数据验证的库,在应用中使用从第三方或后端 API 获取的数据前,先用 zod 进行验证,可以有效防止因 API 返回数据格式变化而导致的程序崩溃。但是在这里,他主要负责,定义数据结构、引导模型输出、自动校验与安全保障。总而言之,这有效防止了因模型输出的“幻觉”或格式错乱导致的应用崩溃。
创建route接口
具体的创建方式可以看官网,这里提一下,官网给了三串代码,分别是Gateway、Provider、Custom。这三者的区别是Gateway相当于使用vercel的服务,充值、计费、密钥管理都是在vercel平台完成。他更类似一个中转站,作用是快速的去验证各个模型间的差异,无需担心被任何一家供应商锁定。对于已经有code plan的开发者来说,这不是一条好的路线。更多时候我们应该用Provider。
Provider 模式需要手动引用大模型供应商import { openai } from '@ai-sdk/openai';如果供应商较多,需要安装的也多。
当你需要的模型vercel ai 没有维护,那么就需要Custom模式,自己维护。需要安装你自己写的或第三方的自定义包。一般来说,如果是内网用户或者本地部署,可能会使用到。
连接界面
连接界面的代码可以直接看官网,这里主要说引用和使用hooks。
'use client';
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';
export default function Chat() {
const [input, setInput] = useState('');
const { messages, sendMessage } = useChat();
...tsx内容
使用useChat
useChat是前端的核心hooks。提供实时聊天消息流式流,抽象输入、消息、加载和错误的状态管理,实现无缝集成到任何界面设计中。具体内容可以看官网。
最重要的就是它可以管理消息列表、消息状态、自动处理流式渲染。
可配置的参数
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
api | string | '/api/chat' | 后端聊天 API 的端点 |
transport | ChatTransport | DefaultChatTransport | 消息传输层,用于更灵活地控制请求 |
messages | UIMessage[] | [] | 初始消息列表,用于恢复历史聊天记录 |
id | string | 自动生成 | 聊天会话的唯一标识符 |
headers | Record 或 Headers | - | 自定义 HTTP 请求头 |
body | object | - | 附加到请求中的额外数据 |
credentials | RequestCredentials | - | 请求的凭证模式 |
onFinish | function | - | 当 AI 响应流式传输完成时的回调 |
onError | function | - | 发生错误时的回调 |
onToolCall | function | - | 接收到工具调用时的回调 |
throttle | number | undefined | 消息更新的节流等待时间(毫秒) |
resume | boolean | false | 是否恢复中断的生成流 |
generateId | function | 默认实现 | 自定义生成消息和聊天 ID 的函数 |
返回值
实践记录
比如想要连接apiKey,首先需要创建一个供应商。
import { createDeepSeek } from "@ai-sdk/deepseek";
//在这里我们连接deepseek,比如这些都兼容openai的也可以安装openai。
const deepseek = createDeepSeek({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: "https://api.deepseek.com",
});
//填写自己的apiKey 与 baseUrl
//创建文本生成
const result = streamText({
model: deepseek("deepseek-v4-flash"), //选择要使用的模型名称
instructions: "You are a helpful assistant.",
messages: await convertToModelMessages(messages),
});
core
streamText作为核心api,里面有很多细节。比如。
messages和prompt是互斥的,prompt提示词负责简单的单轮对话这类一次性任务,messages负责多轮对话,带上下文信息。convertToModelMessages负责将ui层面的数据,转换成模型侧需要的数据。生成的信息是模型侧信息,之后需要通过toUIMessageStream的信息。
streamText和 generateText的区别
| 特性 | generateText | streamText |
|---|---|---|
| 设计目标 | 非交互式(批量处理、自动化任务) | 交互式(聊天、实时反馈) |
| 返回方式 | 一次性返回完整结果(Promise) | 流式返回,逐步输出(AsyncIterable / ReadableStream) |
| 用户体验 | 用户需等待全部生成完成才能看到结果 | 用户可实时看到生成过程(类似 ChatGPT) |
| 常用场景 | 摘要、翻译、分类、邮件起草、数据提取 | 聊天机器人、实时助手、逐步推理展示 |
| 主要返回值 | text、usage、toolCalls、finishReason 等(一次性) | textStream、stream(含各种事件)、toUIMessageStreamResponse() |
| 回调支持 | onStart、onStepEnd、onEnd 等(按步骤) | onChunk、onError、onStepEnd、onEnd(流式事件) |
| 工具调用 | 支持,但工具执行和结果在全部完成后返回 | 支持,且可在多步调用时实时反馈中间工具调用和结果 |
工具调用
Vercel AI SDK 把工具分成三类:服务端调用、客户端调用、用户点击。
可以理解为后端直接写死,客户端请求传参还是用户动态选择。而对于模型来说,sdk并没有直接提供工具,它只是把工具的返回值给到大模型。它类似一个插座,工具可以是自己写的,开源社区下载的,通过mcp协议接入的。
tools: {
// 工具 1:添加待办
addTodo: tool({
description: "添加一条新的待办事项",
inputSchema: z.object({
text: z.string().describe('待办内容,比如"买牛奶"'),
}),
execute: async ({ text }) => {
const newTodo = { id: idCounter++, text };
todos.push(newTodo);
return `已添加"${text}",当前共有 ${todos.length} 条待办`;
},
}),
// 工具 2:查看列表
listTodos: tool({
description: "列出当前所有的待办事项",
inputSchema: z.object({}), // 无参数也要写空对象
execute: async () => {
if (todos.length === 0) return "当前没有任何待办";
const list = todos.map((t) => `${t.id}. ${t.text}`).join("\n");
return `当前待办列表:\n${list}`;
},
}),
deleteTodo: tool({
description: "删除关键字相关待办事项",
inputSchema: z.object({
text: z.string().describe("要删除的待办内容关键字"),
}),
execute: async ({ text }) => {
const initialLength = todos.length;
todos = todos.filter((t) => !t.text.includes(text));
const deletedCount = initialLength - todos.length;
return `已删除 ${deletedCount} 条待办`;
},
}),
// 工具 3:清空(防手滑)
clearTodos: tool({
description: "清空所有待办事项",
inputSchema: z.object({}),
execute: async () => {
todos = [];
idCounter = 1;
return "所有待办已清空";
},
}),
},
如果我们需要ai做一个待办,先写这些工具。和预期不一样的是,工具是执行一次之做一次操作。比如,我告诉它。
我需要买一些食材下火锅,你帮我记一下。有牛肉卷,羊肉卷,金针菇,花菇,平菇,鱼豆腐,蟹棒。
它记录下来了。之后我说。
删除肉类。
他返回了好的,把肉类(牛肉卷、羊肉卷)删掉。 删好啦!肉类(牛肉卷、羊肉卷)已移除,现在清单剩 5 项: 1. 🍄 金针菇 2. 🍄 花菇 3. 🍄 平菇 4. 🐟 鱼豆腐 5. 🦀 蟹棒 还需要调整的话随时告诉我~
但是在终端打印可以发现,它执行了2次,也就是说,对于工具来说,是执行多次而不是,执行一次。
MCP
在 MCP 协议中,角色划分是非常明确的:
- MCP 客户端(Client):负责发起连接、协商协议、提交请求。
- MCP 服务端(Server):是能力的暴露面。它负责接收请求、执行具体的业务逻辑(比如查数据库、调 API),然后返回结构化结果。
但是这里的客户端不是指前端页面。客户端是使用方,服务端是提供方。双方通过MCP协议连接。比如我们在github上发现一个使用的mcp服务,在自己项目的服务端是可以使用的,不过前端页面也可以调用。
“服务端调用 MCP”,主要是为了让 AI 去访问数据库、查天气、调 API 这种后端能力。
但如果你想在前端页面使用 MCP,意味着你想让 AI 直接操控网页上的 UI、读取浏览器里的数据、甚至跨设备控制你的应用。
| 维度 | 服务端 MCP (Server-side) | 前端 MCP (Client-side / Browser) |
|---|---|---|
| 主要目的 | 让 AI 获取数据、执行后端任务(如查数据库、发请求) | 让 AI 操控 UI、读取浏览器状态、辅助前端开发 |
| 运行环境 | Node.js 后端 (route.ts) | 浏览器端 (Browser) 或 IDE 插件 |
| 典型工具 | GitHub API、数据库查询、天气接口 | 修改网页主题、点击按钮、获取页面报错 |
| 安全性 | 需要严格鉴权,防止 AI 乱删数据 | 受限于浏览器沙箱,相对安全 |
安装
npm install @ai-sdk/mcp
const mcpClient = await createMCPClient({
transport: {
type: "http",
url: process.env.MCP_SERVER_URL || "http://localhost:3001/mcp",
},
});//创建服务
const mcpTools = await mcpClient.tools();//使用
总结
了解这些后,能理解什么是工具调用、MCP、Skill。
- 工具调用(Function Call / Tools)
- 行话:这是“能力层”。它是最小执行单元,解决的是“AI 能不能做这件事”的问题。
- MCP 是让用户可以使用别人写在网络上的工具
- 行话:这是“连接层 / 协议层”。它解决的是“AI 怎么低成本、标准化地获取这些能力”的问题。就像你说的,不用自己造轮子,直接拔插别人的“USB 设备”。
- Skill 是把工具调用变得有规则
- 行话:这是“业务层 / 逻辑层”。它解决的是“AI 怎么把一堆零散的能力,组合成一件靠谱的事”的问题。没有 Skill,AI 就是个拿着锤子到处乱敲的莽汉;有了 Skill,AI 就是个按图纸施工的工程师。