React + Fastify + DeepSeek 实现一个简单的对话式 AI 应用

目录

  • 一、核心思路(高层)
  • 二、推荐架构(简单图)
  • [三、关键 API 流程(说明 + 代码示例)](#三、关键 API 流程(说明 + 代码示例))
    • [1、对话 API(LLM)](#1、对话 API(LLM))
    • [2、Vision API(图像问答)](#2、Vision API(图像问答))
    • [3、Embedding API](#3、Embedding API)
  • 四、后端实现(Fastify)
    • 1、安装项目依赖
    • [2、env 配置](#2、env 配置)
    • [3、index.js(Fastify 主服务)](#3、index.js(Fastify 主服务))
  • [五、前端:React 客户端(简易聊天 UI)](#五、前端:React 客户端(简易聊天 UI))
    • [1、API 封装 api.js(封装前端 API)](#1、API 封装 api.js(封装前端 API))
    • [2、app.jsx(简单对话 UI)](#2、app.jsx(简单对话 UI))
  • [六、加上 Vision 上传按钮(选做)](#六、加上 Vision 上传按钮(选做))
  • [七、加上 Embedding 演示(选做)](#七、加上 Embedding 演示(选做))
  • 八、项目结构图
  • [九、DeepSeek + React + Fastify 常见坑](#九、DeepSeek + React + Fastify 常见坑)

一、核心思路(高层)

前端(React):负责用户交互,包括聊天输入、消息展示、图片上传、触发 Embedding 计算等。

后端(Fastify):代理 DeepSeek API 请求,隐藏 API Key,处理逻辑(LLM / Vision / Embedding)。

DeepSeek API:提供三类能力:

  • LLM:自然语言对话
  • Vision:图像理解和问答
  • Embedding:文本向量生成,用于检索或语义搜索

核心原则:

  • API Key 永不暴露在前端
  • 消息与图像统一通过后端转发
  • Embedding / 文档检索可用于 RAG(可选拓展)

二、推荐架构(简单图)

说明:

  • 前端仅和 Fastify 后端 通信
  • 后端根据请求类型调用 DeepSeek 的不同接口
  • 前端可以显示消息、图片解析结果、embedding 长度等(可选)

三、关键 API 流程(说明 + 代码示例)

1、对话 API(LLM)

流程说明:

  • 前端发送 messages 数组到 /api/chat
  • Fastify 读取 env 中的 API Key,调用 DeepSeek Chat API
  • 返回 LLM 回复内容给前端

示例代码:

typescript 复制代码
// Fastify 后端
app.post("/api/chat", async (req, reply) => {
  const { messages } = req.body;

  const completion = await deepseek.chat.completions.create({
    model: "deepseek-chat",
    messages,
  });

  reply.send({ reply: completion.choices[0].message.content });
});

2、Vision API(图像问答)

流程说明:

  • 前端上传图片(Base64 编码) + 问题
  • 后端调用 DeepSeek Vision 模型
  • 返回模型对图片的理解

示例代码:

typescript 复制代码
app.post("/api/vision", async (req, reply) => {
  const { imageBase64, question } = req.body;

  const completion = await deepseek.chat.completions.create({
    model: "deepseek-vision",
    messages: [
      { role: "user", content: [
        { type: "input_text", text: question },
        { type: "input_image", image_url: `data:image/jpeg;base64,${imageBase64}` }
      ]}
    ]
  });

  reply.send({ reply: completion.choices[0].message.content });
});

3、Embedding API

流程说明:

  • 前端发送文本
  • 后端生成向量 embedding
  • 可用于向量数据库存储或语义搜索

示例代码:

typescript 复制代码
app.post("/api/embed", async (req, reply) => {
  const { text } = req.body;

  const emb = await deepseek.embeddings.create({
    model: "deepseek-embedding",
    input: text,
  });

  reply.send({ embedding: emb.data[0].embedding });
});

四、后端实现(Fastify)

1、安装项目依赖

bash 复制代码
npm install fastify fastify-cors dotenv openai

2、env 配置

bash 复制代码
DEEPSEEK_API_KEY=你的key
DEEPSEEK_BASE_URL=https://api.deepseek.com

3、index.js(Fastify 主服务)

typescript 复制代码
import Fastify from "fastify";
import cors from "fastify-cors";
import { deepseek } from "./deepseek.js";

const app = Fastify();
app.register(cors, { origin: "*" });

/** ================================
 *  LLM 对话接口
 *  ================================
 */
app.post("/api/chat", async (req, reply) => {
  const { messages } = req.body;

  const completion = await deepseek.chat.completions.create({
    model: "deepseek-chat",
    messages,
  });

  reply.send({ reply: completion.choices[0].message.content });
});

/** ================================
 *  Vision 图像识别 + 对话
 *  ================================
 */
app.post("/api/vision", async (req, reply) => {
  const { imageBase64, question } = req.body;

  const completion = await deepseek.chat.completions.create({
    model: "deepseek-vision",
    messages: [
      { role: "user", content: [
        { type: "input_text", text: question },
        {
          type: "input_image",
          image_url: `data:image/jpeg;base64,${imageBase64}`
        }
      ]}
    ]
  });

  reply.send({ reply: completion.choices[0].message.content });
});

/** ================================
 *  Embedding
 *  ================================
 */
app.post("/api/embed", async (req, reply) => {
  const { text } = req.body;

  const emb = await deepseek.embeddings.create({
    model: "deepseek-embedding",
    input: text,
  });

  reply.send({ embedding: emb.data[0].embedding });
});

app.listen(3001, () => {
  console.log("Fastify server running at http://localhost:3001");
});

五、前端:React 客户端(简易聊天 UI)

1、API 封装 api.js(封装前端 API)

typescript 复制代码
import axios from "axios";

const API = "http://localhost:3001/api";

/** LLM 对话 */
export const chat = (messages) =>
  axios.post(`${API}/chat`, { messages });

/** Vision */
export const visionAsk = (imageBase64, question) =>
  axios.post(`${API}/vision`, { imageBase64, question });

/** Embeddings */
export const embed = (text) =>
  axios.post(`${API}/embed`, { text });

2、app.jsx(简单对话 UI)

typescript 复制代码
import { useState } from "react";
import { chat, embed, visionAsk } from "./api";

export default function App() {
  const [messages, setMessages] = useState([]);
  const [input, setInput] = useState("");

  async function send() {
    const newMessages = [...messages, { role: "user", content: input }];
    setMessages(newMessages);

    const res = await chat(newMessages);
    setMessages([
      ...newMessages,
      { role: "assistant", content: res.data.reply }
    ]);

    setInput("");
  }

  return (
    <div style={{ padding: 30, maxWidth: 600, margin: "0 auto" }}>
      <h1>DeepSeek Chat (React + Fastify)</h1>

      {/* message list */}
      <div style={{ height: 400, overflowY: "auto", border: "1px solid #ddd", padding: 16 }}>
        {messages.map((m, i) => (
          <div key={i} style={{ marginBottom: 16 }}>
            <b>{m.role}:</b> {m.content}
          </div>
        ))}
      </div>

      {/* input */}
      <div style={{ marginTop: 20 }}>
        <input
          value={input}
          onChange={e => setInput(e.target.value)}
          style={{ width: "80%", padding: 8 }}
        />
        <button onClick={send} style={{ padding: "8px 16px" }}>Send</button>
      </div>
    </div>
  );
}

六、加上 Vision 上传按钮(选做)

typescript 复制代码
<input
  type="file"
  accept="image/*"
  onChange={async e => {
    const file = e.target.files[0];
    const base64 = await fileToBase64(file);
    const res = await visionAsk(base64, "图里有什么?");

    setMessages(m => [
      ...m,
      { role: "user", content: "[图片上传]" },
      { role: "assistant", content: res.data.reply }
    ]);
  }}
/>
typescript 复制代码
function fileToBase64(file) {
  return new Promise(r => {
    const reader = new FileReader();
    reader.onload = () => r(reader.result.split(",")[1]);
    reader.readAsDataURL(file);
  });
}

七、加上 Embedding 演示(选做)

在 React 中加入一个按钮:

typescript 复制代码
<button
  onClick={async () => {
    const res = await embed("Hello DeepSeek!");
    alert("Embedding length: " + res.data.embedding.length);
  }}
>
  Generate Embedding
</button>

八、项目结构图

bash 复制代码
frontend/ (React)
  ├─ src/
  │   ├─ api.js
  │   └─ App.jsx

backend/ (Fastify)
  ├─ index.js
  └─ .env

九、DeepSeek + React + Fastify 常见坑

问题 原因 解决方案
CORS 报错 Fastify 默认不开 CORS fastify-cors
前端暴露 API Key 不安全 只能放到 Fastify 后端
Vision Base64 太大导致请求失败 默认 JSON 限制较小 app.addContentTypeParser 开大限制 或 nginx 上传限制
Chat 需要 message 数组 OpenAI 格式固定 [{ role, content }]
Fastify body 不解析 需要 app.register(require("@fastify/formbody"))(如使用 form) 使用 JSON body 无需
Embedding 返回维度太大(1024/2048) 正常 用向量数据库存储
前端直接调用 DeepSeek 接口跨域失败 DeepSeek 不支持前端 CORS 调用 必须走 Fastify 后端
相关推荐
阿里云大数据AI技术35 分钟前
Agentic Memory Extension 支持对接主流Agent - 适用于 Claude Code、CodeX等
人工智能·agent
我唔知啊1 小时前
不是让 AI 写代码,我是在指挥 AI 干活:一套打磨出来的 AI 编程工作流
人工智能
ZzT1 小时前
在 GitHub 上 @一下 claude,它自己把 issue 改成 PR
人工智能·开源
Ruihong1 小时前
Vue withDefaults 转 React:VuReact 怎么处理?
vue.js·react.js·面试
不加辣椒1 小时前
第15章 上下文窗口管理与长文本策略
人工智能
牛奶2 小时前
AI 能赚钱了——但赚的不是你
人工智能·ai编程·nvidia
凌杰2 小时前
AI 学习笔记:研究方法的演变
人工智能
半盏药香3 小时前
由于jinja2的starlette版本过高引发的问题:500 Server Error TypeError: unhashable type: 'dict'
人工智能
阿里云大数据AI技术3 小时前
MiniMax M3、Kimi K2.7 Code来啦!PAI已支持一键部署,开源前沿触手可及
人工智能·agent
百度Geek说3 小时前
AI Coding 的底层框架:一切优化都是在对抗熵增
人工智能