前言
Vercel AI SDK 是目前前端圈最火的 AI 开发库之一,它提供了一套统一的接口来对接各种大模型(OpenAI, Anthropic, Mistral 等)。但是,官方文档主要以 OpenAI 或 Vercel AI Gateway 为例,对于国内开发者来说,网络连接和支付往往是最大的痛点。
今天这篇教程,我们将使用 Nuxt 3 结合 Vercel AI SDK ,并接入国内强大的 DeepSeek(深度求索) API,手把手带你开发一个丝滑的 AI 聊天应用。
为什么选择这套技术栈?
- Nuxt 3: Vue 3 的最佳全栈框架,开发体验极佳。
- Vercel AI SDK (Core + Vue): 处理流式传输(Streaming)和状态管理的神器,省去自己写 WebSocket 或 SSE 的麻烦。
- DeepSeek: 国内顶尖大模型,API 完全兼容 OpenAI 格式,价格亲民且无需复杂的网络配置。
准备工作
- Node.js 环境: 建议 v18 或更高版本。
- DeepSeek API Key : 去 DeepSeek 开放平台 申请一个 API Key。(你也可以用 Moonshot/Kimi、阿里通义千问等支持 OpenAI 兼容协议的国内模型,配置方法类似)。
第一步:初始化 Nuxt 项目
打开终端,使用 nuxi 创建一个新的 Nuxt 项目。
bash
npx nuxi@latest init my-ai-app
cd my-ai-app
安装依赖(建议使用 pnpm 或 npm):
bash
npm install
第二步:安装 AI SDK 和相关依赖
我们需要安装 ai 核心库、@ai-sdk/core 核心模块、Vue 适配库以及 OpenAI 适配器。同时安装 zod 用于定义数据结构。
bash
npm install ai @ai-sdk/core @ai-sdk/vue @ai-sdk/openai zod
注意 :这里我们虽然用的是 DeepSeek,但因为通过 OpenAI 协议调用,所以安装
@ai-sdk/openai是正确的做法。
第三步:配置环境变量
在项目根目录下创建一个 .env 文件,填入你的 DeepSeek API Key。
env
# .env 文件
NUXT_DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
接下来,为了让 Nuxt 能读取到这个变量,我们需要修改 nuxt.config.ts:
typescript
// nuxt.config.ts
export default defineNuxtConfig({
compatibilityDate: '2024-11-01',
devtools: { enabled: true },
// 将环境变量暴露给服务端运行时
runtimeConfig: {
deepseekApiKey: process.env.NUXT_DEEPSEEK_API_KEY,
},
})
第四步:创建后端 API 路由 (核心)
这是最关键的一步。我们需要在 Nuxt 的服务端创建一个 API 接口,用来处理前端发来的消息,并调用 DeepSeek 模型,最后将结果以**流(Stream)**的形式返回给前端。
创建文件 server/api/chat.ts:
typescript
// server/api/chat.ts
import { createOpenAI } from '@ai-sdk/openai';
import { streamText } from 'ai';
export default defineLazyEventHandler(async () => {
const config = useRuntimeConfig();
const apiKey = config.deepseekApiKey;
if (!apiKey) throw new Error('未找到 DeepSeek API Key');
// 1. 初始化 OpenAI 客户端,但在 baseURL 中指向 DeepSeek 的地址
const deepseek = createOpenAI({
apiKey: apiKey,
baseURL: 'https://api.deepseek.com', // DeepSeek 的官方 API 地址
});
return defineEventHandler(async (event) => {
// 2. 解析前端传来的请求体
const { messages } = await readBody(event);
// 3. 调用模型
const result = streamText({
// 使用 deepseek-chat 模型
model: deepseek('deepseek-chat'),
// 直接使用 messages(AI SDK v4+ 已内置转换)
messages,
// 可选:设置系统提示词
system: '你是一个乐于助人的中文 AI 助手。',
});
// 4. 将流式响应返回给前端
return result.toDataStreamResponse();
});
});
代码解析:
createOpenAI: 我们利用这个工厂函数创建了一个自定义的 provider,将baseURL指向 DeepSeek,从而完美复用 OpenAI 的 SDK 逻辑。streamText: 这是 AI SDK 的核心函数,负责向模型发起流式请求。toDataStreamResponse(): 将 AI 的回复通过 HTTP Response Stream 实时推流给前端,实现"打字机"效果。
第五步:构建前端 UI
现在回到前端,使用 @ai-sdk/vue 提供的 useChat 钩子,它帮我们封装了发送消息、加载状态、自动追加消息列表等所有逻辑。
修改 app.vue (或者 pages/index.vue):
vue
<script setup lang="ts">
import { useChat } from '@ai-sdk/vue';
// 使用 useChat 钩子
// 它会自动调用 /api/chat 接口
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();
</script>
<template>
<div class="container">
<header>
<h1>DeepSeek AI 助手</h1>
</header>
<div class="chat-box">
<div v-for="m in messages" :key="m.id" class="message" :class="m.role">
<div class="role-label">{{ m.role === 'user' ? '我' : 'DeepSeek' }}:</div>
<div class="content">{{ m.content }}</div>
</div>
<div v-if="isLoading && messages[messages.length - 1]?.role === 'user'" class="loading">
思考中...
</div>
</div>
<form @submit="handleSubmit" class="input-area">
<input
class="input-box"
:value="input"
placeholder="输入你想问的问题..."
@change="handleInputChange"
/>
<button type="submit" :disabled="isLoading">发送</button>
</form>
</div>
</template>
<style scoped>
/* 简单的样式美化 */
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
font-family: sans-serif;
height: 100vh;
display: flex;
flex-direction: column;
}
header {
text-align: center;
margin-bottom: 20px;
border-bottom: 1px solid #eee;
}
.chat-box {
flex: 1;
overflow-y: auto;
padding: 10px;
border: 1px solid #ddd;
border-radius: 8px;
margin-bottom: 20px;
background-color: #f9f9f9;
}
.message {
margin-bottom: 15px;
padding: 10px;
border-radius: 6px;
}
.message.user {
background-color: #e3f2fd;
align-self: flex-end;
}
.message.assistant {
background-color: #fff;
border: 1px solid #eee;
}
.role-label {
font-weight: bold;
font-size: 0.8rem;
margin-bottom: 4px;
color: #666;
}
.content {
white-space: pre-wrap; /* 保留换行格式 */
line-height: 1.6;
}
.input-area {
display: flex;
gap: 10px;
}
.input-box {
flex: 1;
padding: 12px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
}
button {
padding: 0 20px;
background-color: #1976d2;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.loading {
font-size: 0.8rem;
color: #999;
margin-left: 10px;
}
</style>
第六步:运行测试
一切就绪!运行项目:
bash
npm run dev
打开浏览器访问 http://localhost:3000。
- 在输入框输入"你好,请介绍一下你自己"。
- 点击发送。
- 你应该能看到 DeepSeek 几乎瞬间开始逐字输出回复,体验非常流畅。
进阶:如何集成组件库(如 Element Plus)
如果要在实际项目中使用,我们通常会配合国内常用的组件库 Element Plus。
- 安装 Element Plus:
npm install element-plus(并按 Nuxt 官方文档配置)。 - 替换
app.vue中的原生标签:
vue
<script setup lang="ts">
import { useChat } from '@ai-sdk/vue';
// 使用 useChat 钩子,并指定 API 端点
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
api: '/api/chat',
});
</script>
<template>
<div class="chat-container">
<el-scrollbar height="600px">
<div v-for="m in messages" :key="m.id">
<el-card shadow="hover" :style="{ marginBottom: '10px', marginLeft: m.role === 'user' ? '50px' : '0', marginRight: m.role === 'user' ? '0' : '50px' }">
<template #header>
<el-tag :type="m.role === 'user' ? 'primary' : 'success'">{{ m.role === 'user' ? 'User' : 'AI' }}</el-tag>
</template>
<div style="white-space: pre-wrap;">{{ m.content }}</div>
</el-card>
</div>
</el-scrollbar>
<div style="margin-top: 20px; display: flex;">
<!-- 注意:input 是 Ref,需要用 :modelValue 而不是 v-model -->
<el-input
:modelValue="input"
@update:modelValue="input = $event"
placeholder="请输入内容"
@keydown.enter="handleSubmit"
/>
<el-button type="primary" @click="handleSubmit" :loading="isLoading" style="margin-left: 10px;">发送</el-button>
</div>
</div>
</template>
注意:useChat 返回的 input 是一个 Ref 响应式对象,在 Element Plus 中需要使用 :modelValue + @update:modelValue 的方式来实现双向绑定,或者直接绑定事件。
总结
通过这篇教程,我们成功实现了:
- Nuxt 3 全栈框架搭建。
- Vercel AI SDK 接入标准 OpenAI 协议。
- DeepSeek 国内 API 的无缝集成。
快去试试吧!如果觉得有用,记得点赞收藏哦。