实战:多模态聊天应用

实战:多模态聊天应用

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)

//从 @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)

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 配置示例:
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)

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