实战:多模态聊天应用

1. 认识 AI SDK

动手写项目之前,得先搞明白 Vercel AI SDK 是啥------这玩意儿就是咱们这次的核心。

做了十年 CRUD,我最烦的就是那些重复劳动。AI SDK 正好把 AI 应用开发里一堆脏活累活给包了,省得你从零搓 HTTP、管状态、搞流式。

  • 不用自己写 HTTP 请求了(谢天谢地)
  • 消息历史它帮你管
  • 流式响应------就是一个字一个字蹦出来那种
  • 加载状态、报错,都有现成的
  • 文字重复?也处理了

GitHub:https://github.com/vercel/ai

官网:https://ai-sdk.dev/docs/introduction

AI SDK 分三块(6.0 版本):

  1. AI SDK UI:React 里用的 Hooks,useChat、useCompletion 这些,前端同学应该不陌生

  2. AI SDK Core:后端 API 用的,streamText、generateText,写接口的时候靠它

  3. AI SDK RSC:提供了服务端直接⽣成界⾯并返回的 API,还在实验阶段,先别急着上生产

2. AI SDK 核心功能

2.1 整一个基本的聊天功能

聊天功能就俩 API,前后端各一个:

  1. 前端 useChat
  2. 后端 streamText

useChat 大概是 AI SDK 里用得最多的 Hook 了,它替你干这些:

  • 存对话历史
  • 流式响应,打字机效果
  • 加载状态自动管
  • 错误也帮你兜着

做个聊天机器人,起码得:记住聊过啥、流式输出、让用户知道正在加载。这些 useChat 简化开发流程。

前端 useChat 代码示例(typescript)

TypeScript 复制代码
//从 @ai-sdk/react 中导入
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
function ChatDemo() {
//在函数组件里使用
const { messages, sendMessage, status } = useChat({
transport: new DefaultChatTransport({
api: '/api/ai-sdk/chat', // 后端 API 地址
}),
});
// useChat 调用后返回值解释:
// messages 就是所有的对话历史
// status 告诉你当前状态:'ready' | 'submitted' | 'streaming'
// sendMessage 用来发送消息
}

useChat 是 React Hook,只能在函数组件里用。类组件?那是上一个时代的产物了。

后端 streamText 代码示例(typescript)

TypeScript 复制代码
import { streamText } from 'ai';
import { deepseek } from '@/lib/ai/models';
export default async function handler(req: Request) { 
const { messages } = await req.json();
//向大模型发起请求
const result = await streamText({
model: deepseek, // 使用哪个 AI 模型
messages: modelMessages, // 对话历史
system: '你是一个友好的 AI 助手...', // 系统提示词
});
//转换为 useChat 需要的格式
return result.toUIMessageStreamResponse();
}

Next.js 后端接口

streamText 调完模型,toUIMessageStreamResponse() 转成前端能识别的流式格式,打字机效果就出来了。

用户输入之后,整条链路是这样的:

用户输入 → 前端 sendMessage() → 后端 streamText() → AI 模型 → 流式返回 → 前端 messages 更新 → UI 刷新

Provider 模型标准化

各家大模型 API 参数、返回格式都不一样,换模型改代码改到吐。AI SDK Core 用 Provider 把交互统一了,换模型改配置文件就行,不用全项目搜索替换。

  1. 调用链:streamText / generateText 这些顶层 API → Language Model Specification → Provider(各厂商自己实现)
  2. 官方支持的 Provider:xAI Grok、OpenAI、Azure OpenAI、Anthropic、Google Generative AI
  3. DeepSeek Provider 配置示例:
TypeScript 复制代码
import { createOpenAI } from '@ai-sdk/openai';
export const deepseek = createOpenAI({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: 'https://sg.uiuiapi.com/v1',
}).chat('deepseek-v3');

2.2 单次对话 / 文本补全

API:useCompletion

不用记上下文的单次生成------代码补全、写首诗、问一句答一句、短文生成,这种场景用它。

跟 useChat 的核心区别:

  1. useChat:多轮对话,历史全留着,适合连续聊
  2. useCompletion:每次独立,不记上下文,就一个 prompt 进去

2.3 工具调用 (Tools)

本质就是大模型的 Function Call。让 AI 能调你写的函数------本项目里图 / 视频生成,全靠这个。

完整流程

用户说话 → AI 看懂意图 → 调自定义工具 → 工具跑外部 API → 结果回给 AI → AI 整合完输出

举个例子:用户说「帮我画一只可爱的小猫」

  1. AI 识别到要画图,调 generateImage
  2. 工具调图像生成模型,拿到图片 URL
  3. AI 带着链接返回,前端渲染

工具定义完整代码示例(typescript)

TypeScript 复制代码
import { tool } from 'ai';
import { z } from 'zod';
// 定义一个工具
const generateImage = tool({
description: '根据用户描述生成图片', // AI 用这个描述来判断是否需要调用
inputSchema: z.object({
prompt: z.string().describe('图片描述'),
}),
execute: async ({ prompt }) => {
// 调用图片生成 API
const imageUrl = await callImageAPI(prompt);
return { success: true, imageUrl };
});
// 在 streamText 中注册工具
const result = await streamText({
model: deepseek,
messages: modelMessages,
tools: {
generateImage, // 注册工具
});

工具调用就三步

  1. 定义工具:tool() 写清楚能干啥、入参校验、执行逻辑
  2. AI 自己决定:看用户输入和工具描述,要不要调
  3. 执行返回:跑完把结果扔回大模型,生成最终回复

3. 功能设计

3.1 功能

1)文本聊天

基础对话能力,需求点:

  • 多轮对话,自动留存完整对话历史
  • 流式打字机输出,实时展示 AI 思考过程
  • 自动管理加载状态、统一异常捕获处理
  • 支持自定义系统提示词

2)图片生成

触发条件(满足任意其一):

  • 用户明确表述:生成图片、画一张、帮我生成一张图
  • 句式指令:生成图片,内容是 XXX

功能细节:

  • 底层调用 doubao-seedream-4.0生成图像
  • 聊天窗口内直接渲染图片
  • 图片交互:放大查看、复制链接、本地下载

3)视频生成

触发条件(满足任意其一):

  • 用户明确表述:生成视频、做一个视频、帮我生成一个视频
  • 句式指令:生成视频,内容是 XXX

功能细节:

  • 底层调用豆包 Seedance 1.5 Pro 视频模型
  • 聊天窗口内置播放器展示视频
  • 播放器功能:全屏、暂停、快进、本地下载

3.2 技术栈

|------------|------------------------------------------------------|
| 模块 | 选型说明 |
| 前端框架 | Next.js + React |
| AI 开发框架 | Vercel AI SDK(ai、@ai-sdk/react、@ai-sdk/openai),本项目核心 |
| 文本对话模型 | DeepSeek 系列,文本对话 |
| 图片生成模型 | 豆包 seedream 4.0,出图 |
| 视频生成模型 | 豆包 Seedance 1.5 Pro,出视频 |
| 数据校验 | Zod,入参校验,AI 写 tool 的时候离不开 |

整个系统架构

3.3 AI编码

功能和技术栈定好了,代码还得让 AI 写。但得先告诉它规矩------代码规范、功能规范、项目结构,全写进 md 文件里。十年 CRUD 的老习惯:文档先行,不然 AI 写出来的代码你根本不敢 merge。

创建 TECH_STACK.md


description: "技术栈说明"


技术栈

  • 前端框架 React+Next.js

  • 风格样式 Tailwind CSS

  • AI SDK Vercel AI SDK

  • 文本聊天模型 DeepSeek V4 Pro

  • 图片生成模型 doubao-seedream-4.0

  • 视频生成模型 豆包 Seeddance 1.5 Pro

技术架构

用户操作

输入框+操作按钮 → useChat Hook → MultimodalChat组件

请求:/api/ai-sdk/multimodal

意图识别模块

├─文本对话 → 文本聊天处理 → DeepSeek(文本)

├─图片生成 → 图片生成处理 → 豆包 seedream(图生)

└─视频生成 → 视频生成处理 → 豆包 Seedance(视频)

↓(结果回流)

useChat Hook 更新消息状态

消息列表UI 展示对话/图片/视频

依赖项

安装核心依赖

npm install ai @ai-sdk/react @ai-sdk/openai

安装 Next.js 和 React(如果还没有安装)

npm install next react react-dom

安装其他必要的依赖

npm install zod

安装开发依赖(TypeScript 类型定义)

npm install -D @types/node @types/react @types/react-dom typescript

创建 PROJECT_STRUCTURE.md


description: "项目结构规范"


mllm-chat/

├── pages/ # Next.js 页面和 API 路由

│ ├── _app.tsx # 应用入口,全局样式引入

│ ├── index.tsx # 前端首页

│ └── api/ # API 路由目录

├── lib/ # 工具库和配置

├── types/ # TypeScript 类型定义

├── styles/ # 全局样式

│ └── globals.css # Tailwind CSS 和自定义样式

├── node_modules/ # 依赖包(自动生成)

├── .next/ # Next.js 构建输出(自动生成,已忽略)

├── package.json # 项目配置和依赖

├── package-lock.json # 依赖锁定文件

├── tsconfig.json # TypeScript 配置

├── next.config.js # Next.js 配置

├── tailwind.config.cjs # Tailwind CSS 配置

├── postcss.config.cjs # PostCSS 配置

├── env.example # 环境变量示例文件

├── API_DOCUMENTATION.md # API 接口文档

├── SETUP.md # 项目设置指南

├── PROJECT_STRUCTURE.md # 本文件:项目结构文档

├── CODE_STYLE.md # 项目代码风格规范

├── FUNCTION_STYLE.md # 功能实现规范

└── test-*.sh # 测试脚本

创建 CODE_STYLE.md


description: "项目代码风格规范"

globs: "\*\*/\*.ts", "\*\*/\*.tsx", "\*\*/\*.js", "\*\*/\*.jsx"

alwaysApply: true


代码风格规范

缩进与格式

  • 使用 2 个空格缩进,禁止使用制表符

  • 单行最大长度 120 字符

  • 语句末尾必须加分号

  • 使用单引号包裹字符串

命名约定

  • 变量和函数:camelCase(如 `userName`、`fetchData`)

  • 组件和类:PascalCase(如 `UserProfile`、`DataService`)

  • 常量:UPPER_SNAKE_CASE(如 `API_BASE_URL`)

  • 接口和类型:PascalCase(如 `UserData`、`ButtonProps`)

TypeScript 规范

  • 启用严格模式(`strict: true`)

  • 禁止使用 `any` 类型

  • 所有函数参数和返回值必须显式声明类型

  • 使用 `interface` 定义对象类型

导入规范

  • 导入语句按类型分组排序:第三方库 → 项目内部模块 → 相对路径

  • 按需导入,避免导入未使用的模块

  • 使用路径别名(如 `@/components/Button`)

注释规范

  • 公共 API 必须添加 JSDoc 注释

  • 复杂逻辑需添加行内说明

  • 使用 `TODO`、`FIXME` 标记待处理项

创建 FUNCTION_STYLE.md


description: "功能实现规范"

globs: "\*\*/\*.ts", "\*\*/\*.tsx"

alwaysApply: true


功能实现规范

React 组件规范

  • 使用函数组件 + Hooks,禁止使用类组件

  • Props 必须定义接口类型

  • 复杂计算使用 `useMemo`/`useCallback` 优化性能

  • 使用自定义 Hook 提取可复用逻辑

状态管理

  • 组件内部状态:`useState`

  • 跨组件状态:`useContext` 或状态管理库

  • 避免在组件中直接修改 props

API 调用规范

  • 异步操作必须添加错误处理(`try/catch` 或 `.catch()`)

  • 使用统一的请求工具函数(如 `@/utils/request`)

  • 响应数据必须使用类型包装器

  • 禁止在组件中直接调用第三方 API 库

错误处理

  • 所有异步操作必须捕获错误

  • 使用错误边界包装页面组件

  • 禁止吞掉异常,必须记录或抛出

性能优化

  • 大列表使用虚拟化技术

  • 图片使用懒加载

  • 避免在渲染函数中进行复杂计算

  • 使用 `React.memo` 包装纯展示组件

测试规范

  • 关键功能必须有单元测试

  • 测试文件命名:`{{ComponentName}}.test.tsx`

  • 使用 `describe` + `it` 结构组织测试用例

  • 测试覆盖率不低于 80%

代码质量

  • 禁止使用 `console.log` 生产环境

  • 避免魔法数字,使用常量定义

  • 函数长度不超过 50 行

  • 文件大小不超过 500 行

先来一个最基础的聊天功能,别一上来就整多模态,容易把自己整懵。

AI 提示词

这是一个多模态聊天应用,请根据以下描述实现相关功能:

1、技术栈及功能实现 请依据 TECH_STACK.md

2、项目基础架构的搭建 请依据 PROJECT_STRUCTURE.md

3、代码规范 请依据 CODE_STYLE.md

4、功能实现规范 请依据 FUNCTION_STYLE.md

5、页面风格要求简洁

然后等着 AI 完成编码任务

3.4 AI 模型 API Key 获取

AI 写代码要时间,闲着也是闲着,先把 API Key 申请了。豆包的视频和图片模型,走字节火山引擎。

本项目图像、视频生成能力依赖火山方舟大模型服务,开发者可自行前往火山引擎官方平台完成服务接入配置

进入平台,第一步建议身份认证,没认证很多功能开不了。

认证完,选择需要的模型。

模型广场 → 卡片视图,搜 doubao-seedream-4.0 和 doubao-seedance-1.5 pro,这俩就是咱们要用的。

然后跟着步骤点就行

4. 代码Review

Cursor 写完代码,根目录照着 env.example 建 .env.local,把真的 apikey 填进去。别 commit 到 git,十年 CRUD 的基本素养。

提示词:「启动项目并进行代码 review」

等着看 review 结果

review 里 AI 会提一堆改进建议,让它按建议改。改完 npm run dev,跑起来再说。

测图片和视频生成

测视频生成的时候报 404。查了一圈,DOUBAO_VIDEO_ENDPOINT 跟火山引擎官方示例对不上。

配置文件写的是 /video/generations,官网示例是 /contents/generations/tasks。

先把配置改成跟官网一致,再把官方 curl 示例丢给 AI,让它照着改。这种坑,文档不写清楚,只能自己踩。

提示词

生成视频调用 API,请根据以下官方示例进行代码修改

1、创建任务

创建 图生视频 任务

curl -X POST https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks \

-H "Content-Type: application/json" \

-H "Authorization: Bearer $ARK_API_KEY" \

-d '{

"model": "doubao-seedance-1-5-pro-251215",

"content": [

{

"type": "text",

"text": "无人机以极快速度穿越复杂障碍或自然奇观,带来沉浸式飞行体验 --duration 5 --camerafixed false --watermark true"

},

{

"type": "image_url",

"image_url": {

"url": "https://ark-project.tos-cn-beijing.volces.com/doc_image/seepro_i2v.png"

}

}

]

}'

2、查询任务

查询任务(需将id替换成第1步返回的任务id)

curl -X GET https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks/{id} \

-H "Content-Type: application/json" \

-H "Authorization: Bearer $ARK_API_KEY"

等 AI 改完,再测一遍视频

5. 功能及页面 UI 优化

前面提示词里页面风格和细节写得比较糙,这里补一刀。

提示词:

请对多模态聊天应用的UI页面及功能进行以下优化

1、聊天窗口UI的设计 请参照"钉钉聊天页面"

聊天页面中发送按钮 background color 改为 #1E90FF

聊天框中,发送消息气泡框背景色,参照微信的设计进行修改

请注意Tailwind CSS 全局样式与自定义样式CSS的冲突问题,防止自定义样式不生效的问题

2、文本聊天功能优化

1)支持多轮对话,自动保存历史(不能聊几句就忘了之前说的话)

2)流式响应,打字机效果(让用户知道AI在"思考")

3)自动管理加载状态和错误处理

4)支持系统提示词自定义

3、图片生成

1)当用户说"帮我画一张..."的时候,AI要能自动识别并生成图片:

触发条件:

用户说"生成图片"、"画一张"、"帮我生成一张图"等或者明确说"生成图片,内容是XXXX"

2)聊天界面中展示生成的图片

3)支持操作:点击放大、复制、下载到本地

4、视频生成

1)当用户说"帮我做一个视频..."的时候,AI要能自动识别并生成视频:

触发条件:

用户说"生成视频"、"做一个视频"、"帮我生成一个视频"等

或者明确说"生成视频,内容是XXXX"

2)在聊天界面中展示生成的视频

3)支持视频操作:全屏播放、暂停、快进、下载到本地

最终效果展示:

完整项目代码已上传 GitHub