Next.js 15 + FastAPI + DeepSeek:从零搭建高校全栈智能问答系统
一、写在前面
目前市面上的 AI 问答产品(ChatGPT、Kimi、DeepSeek 等)虽然能力强大,但缺乏对高校学生场景的深度定制。学业规划、考研答疑、就业指导、竞赛选择……这些高频场景在通用对话产品中体验不佳。
本文手把手带你搭建一套专为高校大学生打造的智能问答系统,支持 DeepSeek / OpenAI / Claude 多模型切换,内置知识库 RAG 检索增强,覆盖 9 大成长场景。
🎯 适合人群:想入门全栈 AI 开发的同学 / 准备做毕业设计的计算机专业学生 / 对 RAG 架构感兴趣的后端工程师
二、技术栈总览
├── 前端 Next.js 15 + React 18 + TypeScript │ ├── 样式 Tailwind CSS │ ├── 状态管理 Zustand │ ├── Markdown 渲染 react-markdown + rehype-highlight │ └── 代码高亮 react-syntax-highlighter (atomOneDark) │ ├── 后端 FastAPI + Python 3.12 │ ├── ORM SQLAlchemy (异步模式) │ ├── 数据库 SQLite │ ├── 向量数据库 ChromaDB │ ├── 嵌入模型 sentence-transformers (shibing624/text2vec-base-chinese) │ └── 流式输出 Server-Sent Events (SSE) │ └── AI 模型 DeepSeek / OpenAI / Claude API
这套技术栈的选择逻辑很简单:
-
Next.js 15:App Router 天然支持 API Routes + 前端一体化,开发效率拉满
-
FastAPI:异步性能强悍,原生 SSE 支持完美契合流式输出
-
ChromaDB:轻量级向量数据库,无需额外部署,适合中小项目快速原型
-
sentence-transformers:中文本分词嵌入模型,RAG 检索的基石
三、项目架构设计
3.1 整体架构图
┌──────────────┐ SSE Stream ┌────────────────┐ │ Next.js 15 │ ◄──────────────► │ FastAPI │ │ (App Router) │ HTTP/JSON │ (Python 3.12) │ │ │ │ │ │ Zustand状态 │ │ LLM Service │──► DeepSeek/OpenAI/Claude │ react-markdown│ │ │ │ Tailwind CSS │ │ RAG Engine │──► ChromaDB └──────┬───────┘ │ │ │ │ 知识库管理 │──► SQLite │ Markdown + 代码高亮 └────────┬───────┘ ▼ │ ┌──────────────┐ │ │ 用户浏览器 │ ┌──────▼──────┐ └──────────────┘ │ 9大分类知识库 │ │ (向量化存储) │ └──────────────┘
3.2 9 大成长场景知识库
系统内置的知识库覆盖了大学生活的核心节点:
| 场景 | 覆盖内容 | 典型问题示例 |
|---|---|---|
| 📚 专业学习 | 专业课知识点、学习方法、文献阅读 | "操作系统进程调度算法有哪些?" |
| 💼 就业准备 | 简历优化、面试技巧、行业分析 | "前端面试常考哪些手写题?" |
| 🎓 考研规划 | 院校选择、复习计划、调剂策略 | "408统考数据结构重点是什么?" |
| 🏆 竞赛选择 | ACM/蓝桥杯/数模等备赛指南 | "数学建模美赛有哪些技巧?" |
| 🚀 创业咨询 | 项目孵化、BP撰写、融资流程 | "大学生创业有哪些扶持政策?" |
| 🌍 留学申请 | 选校定位、文书写作、签证指南 | "CS专业美研选校推荐?" |
| 📖 考证指导 | 软考/CPA/雅思等备考策略 | "软考中级系统集成值得考吗?" |
| 🔬 科研入门 | 论文阅读、实验设计、投稿建议 | "本科生如何找到科研导师?" |
| 💡 生活适应 | 时间管理、心理调节、社交技巧 | "大一时间如何高效规划?" |
四、核心代码实现
4.1 后端:FastAPI 流式问答接口
这是整个系统的核心——通过 SSE 实现流式输出:
# backend/app/api/chat.py
from fastapi import APIRouter, HTTPException, Depends
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from typing import Optional
import json
router = APIRouter()
class ChatRequest(BaseModel):
question: str
model_provider: str = "deepseek" # deepseek / openai / claude
category: Optional[str] = None # 知识库分类
enable_rag: bool = True # 是否启用RAG
@router.post("/chat")
async def chat_stream(request: ChatRequest):
"""流式对话接口,返回 SSE 事件流"""
async def event_stream():
try:
# 1. RAG 检索增强
rag_context = ""
if request.enable_rag:
rag_context = await rag_retrieve(request.question, request.category)
# 2. 构造带上下文的 Prompt
prompt = build_prompt(request.question, rag_context)
# 3. 调用大模型流式生成
async for chunk in llm_stream(
prompt=prompt,
provider=request.model_provider
):
yield f"data: {json.dumps({'type': 'token', 'content': chunk})}\n\n"
# 4. 发送完成信号
yield f"data: {json.dumps({'type': 'done'})}\n\n"
except Exception as e:
yield f"data: {json.dumps({'type': 'error', 'content': str(e)})}\n\n"
return StreamingResponse(
event_stream(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
}
)
4.2 RAG 检索增强引擎
知识库的核心是向量检索 + 中文分词的组合策略:
# backend/app/rag/retriever.py
import chromadb
from sentence_transformers import SentenceTransformer
import jieba
import numpy as np
from typing import List, Tuple
class RAGRetriever:
def __init__(self, persist_directory: str = "./chroma_db"):
self.encoder = SentenceTransformer(
"shibing624/text2vec-base-chinese"
)
self.client = chromadb.PersistentClient(
path=persist_directory
)
def retrieve(
self,
query: str,
category: str = None,
top_k: int = 5,
confidence_threshold: float = 0.5
) -> List[dict]:
"""检索最相关的知识片段"""
# 中文分词增强查询
words = " ".join(jieba.cut(query))
# 向量化查询
query_embedding = self.encoder.encode(query).tolist()
# 选择知识库集合
collection_name = category or "all"
collection = self.client.get_collection(collection_name)
# 向量检索
results = collection.query(
query_embeddings=[query_embedding],
n_results=top_k
)
# 带置信度评分返回
documents = []
for i in range(len(results["ids"][0])):
score = 1 - np.linalg.norm(
np.array(query_embedding) -
np.array(results["embeddings"][0][i])
)
if score >= confidence_threshold:
documents.append({
"content": results["documents"][0][i],
"score": round(score, 4),
"category": results["metadatas"][0][i].get("category"),
"source": results["metadatas"][0][i].get("source")
})
return sorted(documents, key=lambda x: x["score"], reverse=True)
4.3 前端:流式响应与 Markdown 渲染
前端侧配合 SSE 实现逐字输出,并用 react-markdown 实现即时渲染:
// frontend/app/hooks/useChat.ts
import { useCallback, useRef } from 'react';
import { useChatStore } from '@/store/chatStore';
export function useChat() {
const abortRef = useRef<AbortController | null>(null);
const sendMessage = useCallback(async (question: string, model: string) => {
abortRef.current = new AbortController();
const response = await fetch('/api/chat/proxy', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question, model_provider: model }),
signal: abortRef.current.signal,
});
const reader = response.body?.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader!.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
// 逐 token 更新 store,触发 UI 即时渲染
useChatStore.getState().appendToken(data.content);
}
}
}
}, []);
const stopGeneration = useCallback(() => {
abortRef.current?.abort();
}, []);
return { sendMessage, stopGeneration };
}
4.4 前端:Zustand 状态管理
清晰的状态管理是复杂交互的基础:
// frontend/store/chatStore.ts
import { create } from 'zustand';
interface Message {
id: string;
role: 'user' | 'assistant';
content: string;
timestamp: number;
feedback?: 'like' | 'dislike';
feedbackReason?: string;
}
interface ChatState {
messages: Message[];
isStreaming: boolean;
currentStream: string;
conversations: Record<string, Message[]>;
// Actions
addMessage: (msg: Message) => void;
appendToken: (token: string) => void;
stopStream: () => void;
clearMessages: () => void;
deleteMessage: (id: string) => void;
toggleFeedback: (id: string, type: 'like' | 'dislike', reason?: string) => void;
regenerateMessage: (id: string) => void;
}
4.5 前端:键盘快捷键与交互
用户期望的流畅交互体验:
// frontend/app/components/ChatInput.tsx
const ChatInput = () => {
const textareaRef = useRef<HTMLTextAreaElement>(null);
const handleKeyDown = (e: React.KeyboardEvent) => {
// Enter 发送(无 Shift)
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSend();
}
// Shift+Enter 换行
// Ctrl+Enter 发送
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
handleSend();
}
// Esc 清空输入框 / 停止生成
if (e.key === 'Escape') {
e.preventDefault();
if (isStreaming) {
stopGeneration();
} else {
setInput('');
}
}
};
return (
<div className="relative">
<textarea
ref={textareaRef}
onKeyDown={handleKeyDown}
placeholder="输入你的问题,Enter 发送 · Shift+Enter 换行"
className="w-full rounded-xl border p-4 pr-12 resize-none focus:outline-none focus:ring-2"
rows={3}
/>
<span className="absolute bottom-3 right-3 text-xs text-gray-400">
Enter 发送
</span>
</div>
);
};
4.6 通用 LLM 适配层
增加多模型支持,方便灵活切换:
# backend/app/llm/provider.py
from abc import ABC, abstractmethod
import asyncio
from typing import AsyncGenerator
import httpx
class LLMProvider(ABC):
@abstractmethod
async def stream_chat(self, prompt: str) -> AsyncGenerator[str, None]:
pass
class DeepSeekProvider(LLMProvider):
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://api.deepseek.com/v1"
async def stream_chat(self, prompt: str) -> AsyncGenerator[str, None]:
async with httpx.AsyncClient(timeout=60) as client:
async with client.stream(
"POST",
f"{self.base_url}/chat/completions",
json={
"model": "deepseek-chat",
"messages": [{"role": "user", "content": prompt}],
"stream": True,
},
headers={"Authorization": f"Bearer {self.api_key}"},
) as response:
async for line in response.aiter_lines():
if line.startswith("data: ") and line != "data: [DONE]":
yield json.loads(line[6:])["choices"][0]["delta"].get("content", "")
class OpenAIProvider(LLMProvider):
# 类似实现,略
pass
class ClaudeProvider(LLMProvider):
# Anthropic API 实现,略
pass
4.7 前端的 Chat 页面路由 (App Router)
Next.js 15 App Router 的路由组织方式:
// frontend/app/(chat)/chat/[conversationId]/page.tsx
export default async function ChatPage({
params,
}: {
params: Promise<{ conversationId: string }>;
}) {
const { conversationId } = await params;
return (
<div className="flex h-screen bg-white dark:bg-gray-900">
{/* 侧边栏:对话历史 */}
<Sidebar currentId={conversationId} />
{/* 主聊天区域 */}
<main className="flex-1 flex flex-col">
<ChatHeader conversationId={conversationId} />
<MessageList conversationId={conversationId} />
<ChatInputArea conversationId={conversationId} />
</main>
</div>
);
}
五、项目结构一览
ai-campus-qa/ ├── frontend/ # Next.js 15 前端 │ ├── app/ │ │ ├── (chat)/ # 聊天路由组 │ │ └── api/chat/proxy/ # API 代理路由 │ ├── components/ │ │ ├── ChatMessage.tsx # 消息气泡 (Markdown渲染+代码高亮) │ │ ├── ModelSelector.tsx # 模型切换 │ │ ├── CategoryPicker.tsx # 9大场景选择器 │ │ ├── FeedbackButtons.tsx # 点赞点踩 │ │ └── Toast.tsx # Toast通知 │ ├── store/ │ │ └── chatStore.ts # Zustand 状态 │ ├── hooks/ │ │ └── useChat.ts # 对话 Hooks │ └── styles/ │ └── markdown.css # Markdown 自定义样式 │ ├── backend/ # FastAPI 后端 │ ├── app/ │ │ ├── api/ │ │ │ └── chat.py # 流式对话接口 │ │ ├── llm/ │ │ │ └── provider.py # 多模型适配层 │ │ ├── rag/ │ │ │ └── retriever.py # RAG 检索引擎 │ │ ├── db/ │ │ │ ├── models.py # SQLAlchemy 模型 │ │ │ └── database.py # 异步数据库配置 │ │ ├── seed/ │ │ │ └── knowledge.py # 9大分类知识库初始化 │ │ └── main.py # FastAPI 入口 │ └── requirements.txt │ └── docker-compose.yml # 容器化部署
六、部署与运行
6.1 后端启动
cd backend python -m venv venv source venv/bin/activate # Windows: venv\Scripts\activate pip install -r requirements.txt # 初始化知识库 python -m app.seed.knowledge # 启动服务 uvicorn app.main:app --reload --port 8000
6.2 前端启动
cd frontend npm install npm run dev
访问 http://localhost:3000 即可体验。
七、交互效果亮点
系统上线后获得的核心体验反馈:
1. 流式输出体验
-
SSE 协议实现逐字输出,首 token 延迟 < 500ms
-
react-markdown 即时渲染,代码块自动触发 atomOneDark 主题高亮
2. 知识库精准检索
-
中文分词 + 嵌入向量双路检索,覆盖 9 大场景
-
置信度评分可视化,用户可知晓答案可信度
3. 完整交互闭环
-
复制回答 / 重新生成 / 点赞点踩 + 原因反馈
-
对话历史管理(相对时间展示 + 二次确认删除)
-
Toast 通知系统(success / error / warning / info)
4. 双端适配
-
桌面端多栏布局,移动端底部输入栏跟随键盘
-
Tailwind CSS 响应式断点实现无缝切换
八、总结与展望
这套系统从零搭建,用到了当前社区最活跃的技术栈。它的价值不在于「又一个 Chatbot」,而在于:
-
RAG 思维:知识库 + 大模型的组合是当下最实用的 AI 工程路径
-
全栈实践:从前端交互到后端 API 到 AI 模型调用,完整链路打通
-
场景驱动:不是通用聊天,而是解决高校学生的真实痛点
可以继续迭代的方向
-
✅ 接入文件上传解析(PDF / Word / 图片 OCR)
-
✅ WebSocket 替代 SSE,支持双向通信
-
✅ 多模态模型接入(分析图表、识别公式)
-
✅ 对话记录持久化 + 用户系统
-
✅ 一键导出对话为笔记文档
源码地址:GitHub 搜索
ai-campus-qa或访问 github.com/your-username/ai-campus-qa如果你正在做毕业设计或想入门 AI 全栈开发,这个项目可以给你一个完整的参考。有任何问题欢迎评论区交流~
更多推荐



所有评论(0)