从零构建全栈 AI 简历助手:复刻 DeepSeek 交互与长期记忆实现
Published in:2025-11-29 |
Words: 1.3k | Reading time: 5min | reading:

[硬核实战] 从零构建全栈 AI 简历助手:复刻 DeepSeek 交互与长期记忆实现

摘要:如何将一个简单的 LangChain Demo 进化为生产级的 AI SaaS 应用?本文记录了 JD_Agent 项目的里程碑式更新。我们引入了 Next.js 复刻 DeepSeek 的丝滑 UI,基于 SQLModel 实现了完整的用户鉴权与会话管理,并利用 RAG 技术 实现了“简历解析与长期记忆”功能,让 AI 真正拥有了“记住用户”的能力。


1. 项目演进:从 Script 到 Product

在之前的版本中,我们的 JD_Agent 只是一个无状态的 API 接口:用户发 JD,AI 返回分析。
但一个真正的 AI 产品需要具备:

  1. 用户系统:数据隔离,每个人只能看自己的历史。
  2. 交互体验:流式输出、Markdown 渲染、历史记录回溯。
  3. 长期记忆:记住用户的简历背景,不需要每次 Prompt 都重复“我是 Python 开发…”。

今天,我们完成了这次全栈重构


2. 技术栈全景

  • Frontend: Next.js 14 (App Router) + Tailwind CSS + Lucide React
  • Backend: FastAPI + Uvicorn
  • Database: SQLModel (SQLite) + Alembic
  • Auth: JWT (OAuth2PasswordBearer) + Passlib (Bcrypt)
  • AI Core: LangChain + OpenAI/DeepSeek + FAISS (RAG)

3. 核心功能实现

3.1 完美复刻 DeepSeek 的交互界面

我们抛弃了简陋的 HTML,采用 Next.js + Tailwind 打造了现代化的 Chat UI。

  • 布局攻坚:为了实现“顶部固定、侧边栏独立滚动、输入框底部悬浮且不遮挡内容”,我们采用了 fixed inset-0 锁死视口高度,并配合 pb-[200px] 的底部内边距策略。
  • Markdown 渲染:后端返回的结构化 JSON 报告,在前端被动态组装成 Markdown,并通过 react-markdown 渲染出漂亮的排版。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 前端布局核心 Trick
<div className="fixed inset-0 flex ...">
{/* 侧边栏 */}
<div className="w-[260px] hidden md:flex ...">...</div>

{/* 主聊天区 */}
<div className="flex-1 flex flex-col h-full relative">
{/* 消息列表:底部留白防止被输入框遮挡 */}
<div className="flex-1 overflow-y-auto pb-[200px]">...</div>

{/* 悬浮输入框:背景渐变透明 */}
<div className="absolute bottom-0 ... bg-gradient-to-t ...">...</div>
</div>
</div>

3.2 长期记忆:简历解析与用户画像 (LTM)

这是本次更新的灵魂功能。我们不希望 AI 聊完就忘,而是构建了一个长期记忆系统 (Long-Term Memory)

实现流程

  1. 上传:用户上传 PDF/Word 简历。
  2. 解析:后端使用 pdfplumber 提取纯文本。
  3. ETL:利用 LangChain 的 resume_extractor 链,从杂乱的简历中提取出结构化画像(如:tech_stack: ["Python", "FastAPI"], experience: "5年")。
  4. 存储:存入 UserProfile 数据库表。
  5. 回想:下次用户发送 JD 时,系统自动读取 UserProfile,注入到 System Prompt 中。

后端代码片段 (Service Layer)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# app/services/interview_service.py
async def generate_interview_guide(request, db, user_id):
# 1. 读取长期记忆 (从数据库获取用户画像)
ltm_profile = get_user_profile_str(db, user_id)

# 2. 读取短期记忆 (从 ChatMessage 表获取最近对话)
chat_history = get_recent_chat_history(db, user_id)

# 3. 带着记忆去思考
task_tech = generate_tech_async(
...,
user_profile=ltm_profile, # <--- 注入长期记忆
chat_history=chat_history # <--- 注入短期记忆
)

3.3 数据闭环:会话历史管理

为了实现侧边栏的“历史记录”功能,我们引入了 SQLModel

  • 表结构设计

    • User: 存储用户名、加密密码。
    • ChatSession: 代表一次对话(如“神州邦邦面试准备”)。
    • ChatMessage: 存储具体的 User/Assistant 消息内容。
  • 难点处理:Pydantic 对象无法直接存入数据库,我们在存库前将其序列化为 JSON 字符串,在读取时再反序列化,确保前端能拿到结构化的 meta 数据进行渲染。


4. 工程化踩坑实录

在开发过程中,我们解决了一系列真实的工程问题:

坑位 1:Tailwind CSS 样式失效

  • 现象:页面元素堆叠在左上角,样式全无。
  • 原因:Next.js 初始化时 tailwind.config.js 路径配置错误,且误装了不兼容的 Tailwind v4 版本。
  • 解决:降级至 tailwindcss@3.4.17,并手动修正 content 路径覆盖 src/ 目录。

坑位 2:Bcrypt 版本冲突

  • 现象:注册时报错 AttributeError: module 'bcrypt' has no attribute '__about__'
  • 原因passlib 库与最新版 bcrypt 4.0+ 不兼容。
  • 解决:强制锁定版本 pip install "bcrypt==3.2.2"

坑位 3:LangChain 参数丢失

  • 现象KeyError: "Input to ChatPromptTemplate is missing variables {'user_profile'}"
  • 原因:在 Prompt 模板中定义了 {user_profile} 占位符,但在 Chain 调用 (ainvoke) 时忘记传入该参数。
  • 解决:在 Service 层获取记忆后,显式传递给 Chain。

5. 总结与展望

通过今天的迭代,JD_Agent 已经具备了一个商业化 AI 产品的雏形:

  • 好看:媲美 DeepSeek 的 UI。
  • 好用:支持简历一键解析,AI 越用越懂你。
  • 稳健:完整的鉴权与日志系统。

下一步计划

  1. 多智能体模拟面试:引入两个 Agent 互相对话,用户旁观“模拟面试”过程(基于 SSE 流式输出)。
  2. 语音交互:接入 TTS/ASR,实现真正的语音模拟面试。

技术改变生活,AI 赋能求职。如果你对这个项目感兴趣,欢迎关注我的 GitHub!

Prev:
实时语音模拟面试
Next:
基于 RAG 与 Agent 协作的智能面试助手开发指南