首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >用 Vercel AI SDK 6 构建生产级 AI 对话界面:从 useChat 到 Tool Loop Agent

用 Vercel AI SDK 6 构建生产级 AI 对话界面:从 useChat 到 Tool Loop Agent

作者头像
陆业聪
发布2026-06-09 18:38:28
发布2026-06-09 18:38:28
110
举报
前端 AI 对话界面
前端 AI 对话界面

📚 Agent全栈工程师 · 第3/20篇

前情回顾:第1篇 LangChain 三件套搭 RAG 管线 | 第2篇 LangGraph 状态图编排多步 Agent

本篇:把后端的流式与 Tool Loop,呈现在前端用户面前

📰 每日要闻

Vercel AI SDK 6 正式发布:agents 转稳定、MCP 从 experimental 转正、新增 Realtime API 实验性语音支持,前端工程师手上多了把瑞士军刀(6 月 5 日)

ChatGPT 将迎史上最大幅度升级:36氪报道,AI 等机器网络请求量首超人类,OpenAI 与美国政府商讨捐赠股权事宜

📈 美联储新掌门沃什「首秀」在即:特朗普再施压不应加息,CPI 本周接棒非农,市场降息幻觉被数据生吞,加息风暴预期升温(Investing.com)

📊 美 5 月非农就业周五公布:CNBC 称今年偏强的就业开局可能迎来现实检验,是观察利率路径的关键窗口

「华超神控」获亿元天使轮融资:经纬创投领投新一代 AI 超声脑机接口平台,AI×医疗赛道继续发热

上周末我把项目里那段写了大半年的 useChat 代码整个推倒重来了。原因很简单——AI SDK 6 在 6 月 5 号正式发布,message-parts 模型直接把 v4 时代靠字符串拼接的渲染逻辑废掉了一半。

这一篇是「Agent 全栈工程师:企业级知识库项目」系列的第 3 篇。前两篇我们用 LangChain 搭好了 RAG 管线,又用 LangGraph 把多步 Agent 工作流跑通了。后端这边骨架算是立住了,但用户看不见。从这一篇开始,我们把视线挪到前端——具体说,就是怎么用 Vercel AI SDK 6 配合 Next.js 15,把后端的流式输出、工具调用、HITL 审批,全都呈现在一个真正能上生产的对话界面里。

我打算先快速过一遍 v4→v5→v6 的协议演进,让你明白为什么旧代码非改不可;然后从 useChat 这个 Hook 切入,把 message-parts、Server Actions + Edge Runtime、Tool Calling、HITL 审批、多模态输入这几块 v6 真正变了的东西讲清楚;最后落到生产 checklist——错误边界、超时重连、Token 预算、可观测性这些工程细节,是我自己在内网项目里摔过坑总结出来的。

一、为什么是 Vercel AI SDK:框架无关才是它的杀手锄

老实说,我最初是拒绝 Vercel AI SDK 的。一个前端服务商做的 AI SDK,听起来就像是为了绑定 Vercel 那套 Edge 生态凑出来的胶水层。这些年唬唬咻咻到上手之后发现,我错了。它这套设计里真正值钱的不是 Edge,是「框架无关」这四个字。

什么意思?我们后端是在前两篇里用 Python + LangChain 写的,跑在公司内网一台 GPU 机器上;前端是 Next.js 15 部署在 CDN 后面。两者中间隔着一道 HTTP 边界。AI SDK 最让我意外的点在于:它不要求你后端也用 TypeScript,不要求你部署在 Vercel,甚至不要求你用 OpenAI。只要你的后端能吝出符合 SSE 格式的 chunk,前端这边的 useChat 就能接上。

💡 内网证据:KM 657176 《前端开发者构建 AI Agent 系统》有个让我印象深刻的数据——同样实现一个带工具调用的聊天 UI,裸用 OpenAI SDK + 手写 SSE 解析大约要 800 行代码;换成 Vercel AI SDK 后只剩 320 行,减少 60% 左右,还不算各种错误边界许业务代码。

系列中的定位:SDK 是「前后端契约」这一层

为了不迷路,我画了个完整架构图,把本篇要讲的东西放进整个全栈里。

用户(浏览器)

Next.js 15 + AI SDK 6 UI 层

本篇 → useChat / Tool Approval / Realtime

↓ HTTP Streaming

Python 服务端(LangGraph Agent)

前2篇 → Chain / Tool / StateGraph

Milvus / Embedding / 后续篇章覆盖

务必看清楚一件事:useChat 只是个 Hook,背后是一套 HTTP 协议。后端不一定要是 Vercel AI SDK 的 streamText——只要你实现了他们定义的 SSE 事件格式,用 Java 写的 Spring Boot 也能接。这件事后面讲 streamText 时会给你看。

二、AI SDK 4→5→6:三个版本的协议演进

这一节你可能会觉得枯燥,但老项目迁移者请仔细看。我们公司内网那个项目上个月从 v4 升 v6,被坑了三天,原因全部藏在「破坏性变更」这几个字里。

版本

消息模型

Tool Calling

HITL

v4

字符串 + toolInvocations 数组

实验性

全手工

v5

过渡 parts 概念(部分场景)

稳定 + Zod schema

cookbook 示例

v6

message-parts 统一,类型完备

multi-step + Tool Loop Agent

原生 API

重点在第一列。v4 时代的 message 是一个带 content: string 的东西,所以你看到的聊天页面本质上是在渲染一个字符串。他们顺便给了你 toolInvocations 这个辅助数组,让你手动拼接。

v6 直接把 content 变成了 parts: MessagePart[]。每个 part 是一个独立单元,可能是文本、可能是工具调用、可能是推理过程、可能是附件。这个变化的影响在于——你原来那些『message.content.includes(...)』『ReactMarkdown source={message.content}』的写法全部废了。

代码语言:javascript
复制
// v4 旧写法(现在会崩)
{messages.map(m => (
<div key={m.id}>
<ReactMarkdown>
{m.content}
</ReactMarkdown>
{m.toolInvocations?.map(
ti => <ToolCard ti={ti}/>
)}
</div>
))}// v6 新写法
{messages.map(m => (
<div key={m.id}>
{m.parts.map(
(p, i) => {
if (p.type ===
'text')
return <Markdown
key={i}
text={p.text}/>;
if (p.type ===
'tool-call')
return <ToolCall
key={i}
part={p}/>;
return null;
}
)}
</div>
))}

看出问题了吗?不是代码量变多了,是你的「消息渲染」工作单元从「一条消息」变为「一条消息里的一个 part」。这是思维转变,不是 API 换名。官方提供了 codemod 帮你迁移,但业务代码里那些「临时拼出一个 tool result 插到 markdown 里」的魔法写法,所有人都得重写。

三、useChat 实战:message-parts 下的对话 UI

说三个小时模型不如看一眼代码。下面是我项目里一个生产可用的 ChatPanel 组件(简化后),用 v6 的 useChat,背后接我们前两篇的 LangGraph Agent。

骨架:消息列表 + 输入框 + 全状态机

代码语言:javascript
复制
// app/chat/ChatPanel.tsx
'use client';import { useChat }
from 'ai/react';export function
ChatPanel() {
const {
messages,
sendMessage,
status,
stop,
error,
reload,
} = useChat({
api: '/api/chat',
// v6 多步 Agent
// 的关键开关
maxSteps: 5,
onError: (e) => {
// 上报到 LangSmith
reportToSentry(e);
},
});return (
<div className="chat">
<MessageList
items={messages}/>
<StatusBar
status={status}
error={error}
onStop={stop}
onReload={reload}/>
<Composer
onSend={sendMessage}
disabled={
status ===
'streaming'
}/>
</div>
);
}

有三个细节是我迫于踩过坑才提醒你的。第一是 status,v6 把以前那个调用者质疑的 isLoading 换成了有限状态机:submitted | streaming | ready | error。用 status 继距判断 UI 状态事半功倍,别再同时看 isLoading + error + messages.length 这种查表式代码了。

第二是 maxSteps。这东西默认为 1,意思是调用一次模型就结束。但我们这是 Agent 系统,模型返回一个 tool-call、工具产出 tool-result、模型拿到结果继续生成——这是三步。设为 5 是个经验值,留点冗余,不够拍脑门再加。

第三是 stop()reload()。用户发现模型跳蹬了要能中断,这事你是越早提供越好。不要依赖用户刷页了事。

MessageList:从 parts 渲染不同类型

代码语言:javascript
复制
function
MessageList({ items }) {
return items.map(
(m) => (
<div
key={m.id}
data-role={m.role}
>
{m.parts.map(
(p, i) =>
renderPart(p, i)
)}
</div>
)
);
}function
renderPart(p, i) {
switch (p.type) {
case 'text':
return <Md
key={i}
text={p.text}/>;
case 'tool-call':
return <ToolCall
key={i}
part={p}/>;
case 'reasoning':
return <Think
key={i}
text={p.text}/>;
case 'file':
return <FileCard
key={i}
part={p}/>;
default:
return null;
}
}

重点在那个 'reasoning' 分支。上个月报道出来说 OpenAI/Anthropic 都推了思考类模型,v6 原生支持了这种 part。你可以在 UI 上用折叠的「思考过程」胶囊把它裹起来,不动声色地开了个难以身价的交互类别。

这个 switch 还有个隐藏好处:未来 SDK 增加新的 part 类型(比如 audio、image-generation),你的代码不会炸——default 返回 null 就是了。这是老手才会留的后门。

四、服务端:Server Actions + Edge Runtime 最小架构

前端的脸只是门面,里面的发动机在 /api/chat。我们这个项目里这一层是个薄代理——主要责任是接住请求、给 LangGraph 后端打一次 HTTP、然后把 SSE 转换为 AI SDK 能读懂的东西。变压器层。

代码语言:javascript
复制
// app/api/chat/route.ts
import { streamText, tool }
from 'ai';
import { openai }
from '@ai-sdk/openai';
import { z }
from 'zod';export const
runtime = 'edge';export async function
POST(req) {
const { messages } =
await req.json();const result =
streamText({
model: openai(
'gpt-4.1-mini'
),
messages,
maxSteps: 5,
tools: {
kbSearch: tool({
description:
'检索企业知识库',
inputSchema:
z.object({
query: z.string(),
topK: z.number()
.default(
5
),
}),
execute: async ({
query,
topK,
}) => {
return
callLangGraph(
query, topK
);
},
}),
},
});return
result
.toUIMessageStreamResponse();
}

三个细节重要到我愿意单独拿出来说。

一是 runtime = 'edge'。你仿佛会问——我后端都部署在内网 GPU 机器上了,这个 Edge 是干嘛的?它不负责推理,只负责转发。Edge Runtime 最大的价值是冷启动几乎为零,而且天生为 HTTP 流优化。Node.js Runtime 也能跑,就是冷启动会让第一个 token 慢个 200ms 以上。

二是 tool({ inputSchema: z.object(...) })。Zod schema 是这些年社区达成的组合拳,TypeScript 类型 + 运行时校验一步到位。模型返回的参数不符合 schema,SDK 会自动重试,不会让你吞个初始 token 的 NaN。

三是 toUIMessageStreamResponse()。这个方法在 v6 被重命名过(v5 叫 toDataStreamResponse),作用是拼出符合 message-parts 协议的 SSE 响应。迁移者应该记在 codemod 扫描清单里。

五、Tool Calling 与 HITL:让 Agent 等一等用户

这是 v6 最让我兴奋的部分。企业知识库系统里总会遇到一些「高风险动作」——比如删除文档、导出敏感表、向外部发邮件。模型不能自作主张干,必须让用户按一下「确认执行」。以前这种需求要自己在 toolInvocations 上加 awaitingApproval 状态、手写 resume 逻辑——一身胶水。

v6 原生支持了。思路是:服务端定义工具时不给 execute 实现,只留 schema。到了前端会接到一个 tool-call part 却没 tool-result,这时取决于用户点 「同意」还是「拒绝」,前端调用 addToolResult() 把结果什进去,Agent 才接着跳下一步。

代码语言:javascript
复制
// 服务端只给 schema
tools: {
deleteDoc: tool({
description:
'删除知识库文档',
inputSchema:
z.object({
docId:
z.string(),
}),
// 注意:不给 execute
}),
}// 前端 ToolCall 组件
function
ToolCall({ part }) {
const { addToolResult } =
useChat();if (
part.toolName ===
'deleteDoc' &&
part.state ===
'input-available'
) {
return (
<ApprovalCard
title={
'要删除文档?'
}
docId={
part.input.docId
}
onApprove={
() => addToolResult(
{
toolCallId:
part.toolCallId,
result: {
ok: true,
},
}
)
}
onReject={
() => addToolResult(
{
toolCallId:
part.toolCallId,
result: {
ok: false,
reason:
'用户拒绝',
},
}
)
}/>
);
}
return
<ToolResultCard
part={part}/>;
}

这个设计优雅在哪?它把「是否人工审批」从后端架构决策点变成了「前端是否提供 execute」这么个一行代码可控的开关。业务人员说这个动作不需要审批了,在服务端加回 execute 即可。

💡 内网经验:KM 660327 《Agent 对话式 UI 中的定制化交互》里对比了 MCP Apps、Frontend Tool、Inline Render 三种方案。结论是一样的——HITL 场景最合适用 Frontend Tool,因为只有这种模式能拿到 React 状态、能调用业务表单、能接入现有权限体系。

六、多模态与 Realtime:v6 还带了什么新玩具

文件/图片上传

v6 的 useChat 现在原生接受 attachments。你不需要手写 base64 转换、不需要自己拼 multipart:sendMessage 接受 files: FileList 参数,SDK 自动处理。服务端会以 file part 的形式拿到。

另外一个容易被忽略的点:openai('gpt-4.1-mini') 这种拽字符串的写法背后,是 provider 适配器在起作用。换成 anthropic、google、xai 都是一行代码的事。公司内部要是接了自己的模型网关,实现个 custom provider 类也就 50 行。

Realtime(experimental):语音到语音

新增的 Realtime API 接入了 OpenAI/Google/xAI 的 speech-to-speech 能力,浏览器录音直推模型、模型出音返身。我试了个 demo,延迟低到 600ms 以内,能做出「在说中被打断」的交互。但标 experimental 有标的道理,生产谨慎。我们这个知识库项目预计下个季度才开试点。

七、生产级 Checklist:上线前你一定要走一遍

这些都是我项目上线前两周被 QA 推进生产环境猛推出来的问题清单,各位可以照这个表什骤一骤过。

问题

推荐做法

错误边界

model 502 干死 UI

onError 接管 + reload 可见

超时重连

市务网络中断

SSE 末尾插 keep-alive ping

Token 预算

思考模型烧钱快

maxOutputTokens + 会话摘要

可观测性

问题定位难

后端接 LangSmith(下期)

防重发

双点发送

status 为 streaming 时禁 send

最后一点话。西经东译上周那期 Cole Medin 聊 Harness Engineering,说顶级 Agent 工程师与一般工程师的差距,不在写 prompt 的手艺,而在能不能给 Agent 搭一根「控制之绳」(harness)。我越品越觉得,前端这一层就是 harness 的关键环节——哪里该马上运行、哪里该等人、哪里该中断。以前这些都要手搭,v6 把它们变成了库提供的原语。

八、下一篇预告:Milvus 向量数据库

前 3 篇是 Agent 架构的三个层:LangChain 是胶水、LangGraph 是编排、AI SDK 是后端与前端的握手。下一篇我们要去动「脑」里面的东西了——从 Milvus 安装到百万级向量检索,会讲透如何选索引类型、IVF 与 HNSW 的取舍、如何调参数让召回与延迟同时走在绿线。

你可以提前报个名:安装 Milvus 2.5、准备 100 万条 Embedding 样本、预设 IVF\_FLAT 与 HNSW 两个索引。下周一见。

📚 Agent全栈工程师:企业级知识库项目系列 · 第3/20篇

从 LangChain 到 Kubernetes,20篇系统掌握AI Agent全栈开发

✅ 第1篇:LangChain入门:Chain/Agent/Tool三件套搭起第一个RAG管线

✅ 第2篇:LangGraph实战:用状态图构建多步Agent工作流

📝 第3篇:Vercel AI SDK前端接入:流式响应与Conversation UI实现(本篇)

⏳ 第4篇:Milvus向量数据库:从安装到百万级向量检索的工程化实践

⏳ 第5篇:文档解析与Chunking策略

⏳ 第6篇:Embedding模型选型

⏳ 第7篇:ElasticSearch全文检索

⏳ 第8-20篇:混合检索、知识图谱、评估、观测、K8s部署……

—— 如果你走到了这里,代码都读了,那请你点个赞。下周一见。 ——

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-06-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 陆业聪 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档