实战:多模态聊天应用

实战:多模态聊天应用
1. 认识 AI SDK动手写项目之前得先搞明白 Vercel AI SDK 是啥——这玩意儿就是咱们这次的核心。做了十年 CRUD我最烦的就是那些重复劳动。AI SDK 正好把 AI 应用开发里一堆脏活累活给包了省得你从零搓 HTTP、管状态、搞流式。不用自己写 HTTP 请求了谢天谢地消息历史它帮你管流式响应——就是一个字一个字蹦出来那种加载状态、报错都有现成的文字重复也处理了GitHubhttps://github.com/vercel/ai官网https://ai-sdk.dev/docs/introductionAI SDK 分三块6.0 版本1. AI SDK UIReact 里用的 HooksuseChat、useCompletion 这些前端同学应该不陌生2. AI SDK Core后端 API 用的streamText、generateText写接口的时候靠它3. AI SDK RSC提供了服务端直接⽣成界⾯并返回的 API还在实验阶段先别急着上生产2. AI SDK 核心功能2.1 整一个基本的聊天功能聊天功能就俩 API前后端各一个前端 useChat后端 streamTextuseChat 大概是 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 代码示例typescriptimport { 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 把交互统一了换模型改配置文件就行不用全项目搜索替换。调用链streamText / generateText 这些顶层 API → Language Model Specification → Provider各厂商自己实现官方支持的 ProviderxAI Grok、OpenAI、Azure OpenAI、Anthropic、Google Generative AIDeepSeek 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 单次对话 / 文本补全APIuseCompletion不用记上下文的单次生成——代码补全、写首诗、问一句答一句、短文生成这种场景用它。跟 useChat 的核心区别useChat多轮对话历史全留着适合连续聊useCompletion每次独立不记上下文就一个 prompt 进去2.3 工具调用 (Tools)本质就是大模型的 Function Call。让 AI 能调你写的函数——本项目里图 / 视频生成全靠这个。完整流程用户说话 → AI 看懂意图 → 调自定义工具 → 工具跑外部 API → 结果回给 AI → AI 整合完输出举个例子用户说「帮我画一只可爱的小猫」AI 识别到要画图调 generateImage工具调图像生成模型拿到图片 URLAI 带着链接返回前端渲染工具定义完整代码示例typescriptimport { 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, // 注册工具 });工具调用就三步定义工具tool() 写清楚能干啥、入参校验、执行逻辑AI 自己决定看用户输入和工具描述要不要调执行返回跑完把结果扔回大模型生成最终回复3. 功能设计3.1 功能1文本聊天基础对话能力需求点多轮对话自动留存完整对话历史流式打字机输出实时展示 AI 思考过程自动管理加载状态、统一异常捕获处理支持自定义系统提示词2图片生成触发条件满足任意其一用户明确表述生成图片、画一张、帮我生成一张图句式指令生成图片内容是 XXX功能细节底层调用doubao-seedream-4.0生成图像聊天窗口内直接渲染图片图片交互放大查看、复制链接、本地下载3视频生成触发条件满足任意其一用户明确表述生成视频、做一个视频、帮我生成一个视频句式指令生成视频内容是 XXX功能细节底层调用豆包 Seedance 1.5 Pro 视频模型聊天窗口内置播放器展示视频播放器功能全屏、暂停、快进、本地下载3.2 技术栈模块选型说明前端框架Next.js ReactAI 开发框架Vercel AI SDKai、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: 技术栈说明---# 技术栈- 前端框架 ReactNext.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.md2、项目基础架构的搭建 请依据 PROJECT_STRUCTURE.md3、代码规范 请依据 CODE_STYLE.md4、功能实现规范 请依据 FUNCTION_STYLE.md5、页面风格要求简洁然后等着 AI 完成编码任务3.4 AI 模型 API Key 获取AI 写代码要时间闲着也是闲着先把 API Key 申请了。豆包的视频和图片模型走字节火山引擎。本项目图像、视频生成能力依赖火山方舟大模型服务开发者可自行前往火山引擎官方平台完成服务接入配置进入平台第一步建议身份认证没认证很多功能开不了。认证完选择需要的模型。模型广场 → 卡片视图搜 doubao-seedream-4.0 和 doubao-seedance-1.5 pro这俩就是咱们要用的。然后跟着步骤点就行4. 代码ReviewCursor 写完代码根目录照着 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要能自动识别并生成图片:触发条件:用户说生成图片、画一张、帮我生成一张图等或者明确说生成图片内容是XXXX2)聊天界面中展示生成的图片3)支持操作点击放大、复制、下载到本地4、视频生成1)当用户说帮我做一个视频...的时候AI要能自动识别并生成视频:触发条件:用户说生成视频、做一个视频、帮我生成一个视频等或者明确说生成视频内容是XXXX2)在聊天界面中展示生成的视频3)支持视频操作:全屏播放、暂停、快进、下载到本地最终效果展示完整项目代码已上传 GitHub