nextjs16+@ai-sdk实现AI对话

功能概览

使用 Next.js 16 App Router、AI SDK 7、@ai-sdk/react 和 DeepSeek Provider 实现流式 AI 对话。

  • 前端通过 useChat 管理会话消息和请求状态。
  • 后端通过 POST /api/chat 接收 UI 消息,转换为模型消息并调用 DeepSeek。
  • 服务端将模型生成的文本流转换为 AI SDK UI Message Stream,供前端逐步显示。
  • 对话页面提供空状态、快捷提问、消息区、加载状态、错误提示和输入区。

请求流程

1
2
3
4
5
6
7
8
9
用户输入
-> page.tsx 的 sendMessage({ text })
-> useChat 默认请求 POST /api/chat
-> route.ts 读取 { messages }
-> convertToModelMessages(messages)
-> streamText 调用 DeepSeek
-> toUIMessageStream(result.stream)
-> createUIMessageStreamResponse({ stream })
-> useChat 接收流并更新 messages

前端对话页面

文件:src/app/page.tsx

Client Component 与 useChat

页面使用 React 状态、事件和 useChat,因此文件顶部需要声明:

1
"use client"

通过 useChat 获取消息、发送方法、请求状态和错误:

1
2
const { messages, sendMessage, status, error } = useChat()
const isLoading = status === "submitted" || status === "streaming"

messages 中的内容是 UIMessage。每条消息通过 role 区分用户和助手,正文在 parts 中:

1
2
3
4
5
6
7
8
9
messages.map((message) => (
<div key={message.id}>
{message.parts.map((part, index) =>
part.type === "text" ? (
<span key={`${message.id}-${index}`}>{part.text}</span>
) : null,
)}
</div>
))

使用 message.id 作为消息 key;消息正文按 text part 渲染。whitespace-pre-wrap 可保留换行,break-words 可避免长文本撑破布局。

发送消息

输入区使用表单处理点击发送和键盘提交,并在发送前去除首尾空白:

1
2
3
4
5
6
7
8
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
const text = input.trim()
if (!text || isLoading) return

sendMessage({ text })
setInput("")
}

输入框的 Enter 提交,Shift + Enter 不触发提交。发送按钮在输入为空或请求进行中时禁用。空状态中的快捷提问也直接调用 sendMessage({ text })。

页面交互状态

  • messages.length === 0 时展示欢迎文案和快捷提示。
  • status 为 submitted 或 streaming 时显示加载动画,并暂时禁用输入。
  • error 存在时显示请求失败提示。
  • useEffect 监听消息和加载状态,滚动到消息列表底部。
  • 新对话按钮通过刷新页面重置当前内存中的会话。
  • 使用 Tailwind 的宽度、间距和断点类适配窄屏与桌面布局。

服务端 API Route

文件:src/app/api/chat/route.ts

Next.js App Router 使用命名导出声明 HTTP 方法:

1
2
3
export async function POST(req: Request) {
// ...
}

转换 UIMessage

前端 useChat 发送的是 UIMessage,而模型调用需要 ModelMessage。不要把收到的 messages 直接放进 prompt;使用 AI SDK 提供的转换函数:

1
2
const { messages } = await req.json()
const modelMessages = await convertToModelMessages(messages)

随后将转换结果传给 streamText 的 messages 参数:

1
2
3
4
5
const result = streamText({
model: createDeepSeek({ apiKey })("deepseek-flash"),
messages: modelMessages,
instructions: "你是一个女仆,按照你的性格给予用户帮助",
})

AI SDK 7 流式响应

AI SDK 7 已将 streamText result 上的 toUIMessageStreamResponse() 等便捷方法标记为废弃。当前推荐将 stream 转换和 Response 创建拆开:

1
2
3
4
5
const stream = toUIMessageStream({
stream: result.stream,
})

return createUIMessageStreamResponse({ stream })

本项目保留的旧写法仅作为迁移参考,且处于注释状态:

1
2
3
// 旧写法:result.toUIMessageStreamResponse()
// 该方法在 AI SDK 7 中已废弃,后续版本可能移除。
// return result.toUIMessageStreamResponse()

错误处理

Route Handler 中捕获同步解析和转换阶段的异常,写入服务端日志并返回 JSON 500:

1
2
3
4
5
6
try {
// 解析输入、转换消息、创建模型流
} catch (error) {
console.error("POST /api/chat failed:", error)
return Response.json({ error: "聊天请求失败" }, { status: 500 })
}

注意:模型流开始返回后发生的 provider 错误通常属于流式阶段,不能只依赖外围 try/catch;调试时还要检查服务端终端日志和客户端 AI SDK 的 error 状态。


nextjs16+@ai-sdk实现AI对话
https://zouhualu.github.io/20260930/nextjs16-ai-sdk实现AI对话/
作者
花鹿
发布于
2026年9月30日
许可协议