🚀 Vercel AI SDK 使用指南:Nuxt 3 + Element Plus 实现第一个AI 聊天应用

前言

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 格式,价格亲民且无需复杂的网络配置。

准备工作

  1. Node.js 环境: 建议 v18 或更高版本。
  2. 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

  1. 在输入框输入"你好,请介绍一下你自己"。
  2. 点击发送。
  3. 你应该能看到 DeepSeek 几乎瞬间开始逐字输出回复,体验非常流畅。

进阶:如何集成组件库(如 Element Plus)

如果要在实际项目中使用,我们通常会配合国内常用的组件库 Element Plus

  1. 安装 Element Plus: npm install element-plus (并按 Nuxt 官方文档配置)。
  2. 替换 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 的方式来实现双向绑定,或者直接绑定事件。


总结

通过这篇教程,我们成功实现了:

  1. Nuxt 3 全栈框架搭建。
  2. Vercel AI SDK 接入标准 OpenAI 协议。
  3. DeepSeek 国内 API 的无缝集成。

快去试试吧!如果觉得有用,记得点赞收藏哦。

相关推荐
后端小肥肠4 小时前
小红书虚拟商品怎么做?我先用 Skill 跑通了壁纸品类
人工智能·aigc·agent
Java陈序员5 小时前
企业级!一个基于 Java 开发的开源 AI 应用开发平台!
spring boot·agent·mcp
Chen666785 小时前
我让一个Agent Team长时间自治运行后,发现问题不在“怎么组队”
agent
Randyliu5 小时前
20260508-Agent搭建记录以及对ReAct框架的理解
面试·agent
小九九的爸爸6 小时前
前端想要入门Agent开发,要具备哪些Python基础?
python·agent·ai编程
陌路遥6 小时前
别被 Demo 骗了:当前 Agent 的"自主规划",LLM 其实一句都没懂
agent
Bolt6 小时前
读懂 Claude Code `/loop` 与编码 Agent 的循环革命
人工智能·程序员·agent
码哥字节6 小时前
给 Claude Code 布置任务,它为什么总是理解错——我找到原因了
agent
一tiao咸鱼7 小时前
Ai 相关 7月1日学习
前端·agent
DarkLONGLOVE7 小时前
快速上手 Pinia!Vue3 极简状态管理使用教程
javascript·vue.js