关于 vercel-ai-sdk
Vercel AI SDK 提供 React Hooks 和服务端工具,用于构建流式聊天界面。它支持工具调用、文件附件、多步骤流程,以及客户端和服务端执行的类型化消息部分。
主要功能
- 01React 聊天 Hooks
- 02服务端流式处理工具
- 03带执行状态的工具调用
- 04文件与自定义数据消息部分
- 05类型化消息与工具
适用场景
- 01构建流式聊天界面
- 02呈现工具调用进度与结果
- 03在聊天中处理文件附件
- 04实现服务端和客户端工具
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ existential-birds/ beagle --skill vercel-ai-sdk安装后在 Claude Code 中输入/vercel-ai-sdk确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/messages.mdstreaming.mdtools.mduse-chat.md详细介绍
Vercel AI SDK
Vercel AI SDK 提供 React 钩子和服务器工具,用于构建支持工具调用、文件附件和多步推理的流式聊天界面。
快速参考
基本的 useChat 设置
import { useChat } from '@ai-sdk/react';
const { messages, status, sendMessage, stop, regenerate } = useChat({
id: 'chat-id',
messages: initialMessages,
onFinish: ({ message, messages, isAbort, isError }) => {
console.log('Chat finished');
},
onError: (error) => {
console.error('Chat error:', error);
}
});
// Send a message
sendMessage({ text: 'Hello', metadata: { createdAt: Date.now() } });
// Send with files
sendMessage({
text: 'Analyze this',
files: fileList // FileList or FileUIPart[]
});
ChatStatus 状态
status 字段表示聊天的当前状态:
ready:聊天处于空闲状态,可以接收新消息submitted:消息已发送至 API,正在等待响应流开始streaming:正在从 API 接收流式响应error:请求过程中发生错误
消息结构
消息使用基于 parts 的结构,类型为 UIMessage:
interface UIMessage {
id: string;
role: 'system' | 'user' | 'assistant';
metadata?: unknown;
parts: Array<UIMessagePart>; // text, file, tool-*, reasoning, etc.
}
Part 类型包括:
text:文本内容,可包含可选的流式状态file:文件附件(图像、文档)tool-{toolName}:带状态机的工具调用reasoning:AI 推理轨迹data-{typeName}:自定义数据 part
服务端流式传输
import { streamText } from 'ai';
import { convertToModelMessages } from 'ai';
const result = streamText({
model: openai('gpt-4'),
messages: convertToModelMessages(uiMessages),
tools: {
getWeather: tool({
description: 'Get weather',
inputSchema: z.object({ city: z.string() }),
execute: async ({ city }) => {
return { temperature: 72, weather: 'sunny' };
}
})
}
});
return result.toUIMessageStreamResponse({
originalMessages: uiMessages,
onFinish: ({ messages }) => {
// Save to database
}
});
工具处理模式
客户端工具执行:
const { addToolOutput } = useChat({
onToolCall: async ({ toolCall }) => {
if (toolCall.toolName === 'getLocation') {
addToolOutput({
tool: 'getLocation',
toolCallId: toolCall.toolCallId,
output: 'San Francisco'
});
}
}
});
渲染工具状态:
{message.parts.map(part => {
if (part.type === 'tool-getWeather') {
switch (part.state) {
case 'input-streaming':
return <pre>{JSON.stringify(part.input, null, 2)}</pre>;
case 'input-available':
return <div>Getting weather for {part.input.city}...</div>;
case 'output-available':
return <div>Weather: {part.output.weather}</div>;
case 'output-error':
return <div>Error: {part.errorText}</div>;
}
}
})}
参考文件
有关特定方面的详细文档:
- use-chat.md:完整的 useChat API 参考
- messages.md:UIMessage 结构和 part 类型
- streaming.md:服务端流式传输实现
- tools.md:工具定义和执行模式
常见模式
错误处理
const { error, clearError } = useChat({
onError: (error) => {
toast.error(error.message);
}
});
// Clear error and reset to ready state
if (error) {
clearError();
}
重新生成消息
const { regenerate } = useChat();
// Regenerate last assistant message
await regenerate();
// Regenerate specific message
await regenerate({ messageId: 'msg-123' });
自定义传输
import { DefaultChatTransport } from 'ai';
const { messages } = useChat({
transport: new DefaultChatTransport({
api: '/api/chat',
prepareSendMessagesRequest: ({ id, messages, trigger, messageId }) => ({
body: {
chatId: id,
lastMessage: messages[messages.length - 1],
trigger,
messageId
}
})
})
});
性能优化
// Throttle UI updates to reduce re-renders
const chat = useChat({
experimental_throttle: 100 // Update max once per 100ms
});
自动发送消息
import { lastAssistantMessageIsCompleteWithToolCalls } from 'ai';
const chat = useChat({
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls
// Automatically resend when all tool calls have outputs
});
类型安全
SDK 为工具和消息提供完整的类型推断:
import { InferUITools, UIMessage } from 'ai';
const tools = {
getWeather: tool({
inputSchema: z.object({ city: z.string() }),
execute: async ({ city }) => ({ weather: 'sunny' })
})
};
type MyMessage = UIMessage<
{ createdAt: number }, // Metadata type
UIDataTypes,
InferUITools<typeof tools> // Tool types
>;
const { messages } = useChat<MyMessage>();
核心概念
基于 Parts 的架构
消息使用 parts 数组,而非单一的 content 字段。这样可以:
- 在保留其他 parts 的同时流式传输文本
- 使用独立状态机处理工具调用
- 将文件附件和自定义数据与文本混合
工具状态机
工具 part 会经历以下状态:
input-streaming:工具输入正在流式传输(可选)input-available:工具输入已完成approval-requested:正在等待用户批准(可选)approval-responded:用户已批准或拒绝(可选)output-available:工具执行已完成output-error:工具执行失败output-denied:用户拒绝了批准请求
流式传输协议
SDK 使用 Server-Sent Events(SSE)和 UIMessageChunk 类型:
text-start、text-delta、text-endtool-input-available、tool-output-availablereasoning-start、reasoning-delta、reasoning-endstart、finish、abort
客户端工具与服务端工具
服务端工具包含 execute 函数,并在 API 路由中运行。
客户端工具不包含 execute,而是通过 onToolCall 和 addToolOutput 处理。
检查关卡
按以下顺序执行;只有当代码或 UI 中满足通过条件时,才算完成该步骤(而不是“应该能用”)。
- 流式路由 — 通过条件: 聊天处理程序按顺序调用
convertToModelMessages→streamText(或应用标准化的 SDK 模式)→toUIMessageStreamResponse(或等效的流式响应)。未通过条件: 响应是普通 JSON 字符串,没有遵循 UI 消息流约定。 - 客户端 ↔ 路由 — 通过条件:
useChat的id/DefaultChatTransport的api(以及prepareSendMessagesRequest的请求体)与路由路径及服务器读取的请求体一致。未通过条件: 客户端发送请求的路径或请求体结构与处理程序的预期不一致。 - 工具闭环 — 通过条件:
tools中的每个工具都有服务器端execute,或onToolCall+addToolOutput,且使用相同的toolCallId;UI 也会处理你呈现的tool-*部分状态。未通过条件:tools中存在某个工具名称,但没有对应的处理程序,或渲染器缺少相应状态。 - 持久化(如有) — 通过条件: 保存前,服务器会运行
validateUIMessages(或更严格的验证)。未通过条件: 未经验证的客户端请求内容被写入存储。
最佳实践
- 始终处理
error状态并向用户提供反馈 - 对高频更新使用
experimental_throttle - 根据
status实现适当的加载状态 - 使用自定义元数据和工具为消息添加类型
- 使用
sendAutomaticallyWhen处理多轮工具工作流 - 在 UI 中处理所有工具状态,以提供更好的用户体验
- 使用
stop()允许用户取消长时间运行的请求 - 在服务器上使用
validateUIMessages验证消息
评论
支持 Markdown