一个学 Java 后端的大学生,用 AI Coding 从零开发了一个 AI 面试陪练工具,和大家聊聊全过程踩坑与收获

作者:MuYi | Java 后端学习者
项目地址:https://gitee.com/MuYi-2025/interview-assistance-system

前言

大家好,我是一名正在找 Java 后端实习的大学生。

最近秋招/春招季,身边同学都在忙着刷八股、改简历、投递记录。我发现大家面临的问题出奇地一致:

  • 八股背了就忘,看的时候觉得都会,面试一问就卡壳
  • 简历写完不知道质量如何,找学长看又不好意思总麻烦人家
  • 投了几十家公司,根本记不清哪家到什么阶段了

于是我花了些时间,做了一个 AI 面试教练(Interview Copilot),用 LLM 来帮助大家系统化地准备面试。

写这篇文章,一方面是分享这个工具的使用方法,另一方面也聊聊我在开发过程中的一些思考和收获。


一、这个工具能做什么?

先上功能清单:

功能 说明
面试模拟 从题库抽题,逐题作答,AI 实时评分 + 反馈
简历诊断 上传 PDF,AI 从六个维度分析简历质量
岗位匹配 粘贴 JD,AI 分析简历匹配度和改进建议
场景出题 根据你的技能标签,AI 生成真实业务场景题
题库管理 批量导入面试题,支持 JSON/CSV/TXT
投递记录 管理求职进度,状态筛选 + 统计概览

核心思路很简单:把面试准备这件事,从"散装学习"变成"系统化训练"


二、为什么用 LLM 而不是传统方案?

做这个项目之前,我也想过用传统的规则引擎来做面试评分。但很快就发现了问题:

面试题的回答是开放式的。

比如问"HashMap 的底层原理",你可以说"数组 + 链表 + 红黑树",也可以说"通过 key 的 hashCode 定位桶,冲突时用链表,链表过长转红黑树"。两种回答都对,但深度不同。

用传统方案很难评判这种差异,而 LLM 天然擅长这个。它能:

  • 理解回答的语义,而不是做关键词匹配
  • 准确性、完整性、表达清晰度多个维度打分
  • 给出具体的改进建议,而不是泛泛的"答得不错"

这就是为什么选择 LLM 作为核心引擎。


三、架构设计

项目采用前后端分离架构,整体分为三层:

┌─────────────────────────────────────────────┐
│           Vue 3 前端 (Element Plus)         │
│  首页 │ 简历管理 │ 面试练习 │ 题库 │ 投递记录  │
└──────────────────┬──────────────────────────┘
                   │ HTTP / SSE(流式传输)
┌──────────────────▼──────────────────────────┐
│           FastAPI 后端 (Python)              │
│  用户管理 │ 简历分析 │ 面试引擎 │ 投递记录    │
└──────────────────┬──────────────────────────┘
                   │
┌──────────────────▼──────────────────────────┐
│              基础设施层                       │
│  DeepSeek LLM │ 百炼向量模型 │ ChromaDB      │
│  JSON 文件存储  │ pdfplumber PDF 解析         │
└─────────────────────────────────────────────┘

技术选型考量

组件 选择 为什么
LLM DeepSeek 性价比高,OpenAI 兼容接口,切换成本低
向量模型 阿里云百炼 text-embedding-v4 中文语义理解好,同样是 OpenAI 兼容接口
向量库 ChromaDB 轻量级,无需额外部署服务,本地文件即可运行
后端 FastAPI Python 生态 + 异步支持 + 自动 API 文档
前端 Vue 3 + Element Plus 组件丰富,上手快,适合快速开发
数据存储 JSON 文件 简历、面试记录这类文本数据量不大,JSON 文件足够用,省去数据库部署

关于数据存储多说两句。一开始我尝试过用 MySQL 存面试记录,但发现一个尴尬的问题:面试记录里有大量的文本(问题、回答、反馈、报告),这些内容塞进 MySQL 的 TEXT 字段并不优雅,而且对于个人工具来说,部署一个 MySQL 服务太重了。

最终决定全部用 JSON 文件存储,配合目录结构来组织数据,简洁且可移植。这也是很多 CLI 工具(如 VS Code 的配置、npm 的 package-lock.json)采用的方案。


四、核心流程详解

4.1 面试模拟流程

这是整个项目最核心的功能,流程如下:

用户选择难度和题数
        │
        ▼
  无放回随机抽题(random.sample)
        │
        ▼
  ┌─→ 展示第 N 题 ←─────────────────┐
  │     │                           │
  │     ▼                           │
  │  用户作答                        │
  │     │                           │
  │     ▼                           │
  │  SSE 流式评分(LLM 逐 token 输出)│
  │     │                           │
  │     ▼                           │
  │  显示评分 + 反馈                  │
  │     │                           │
  │     └── 还有下一题?── 是 ────────┘
  │              │
  │              否
  │              ▼
  │     SSE 流式生成综合报告
  │              │
  │              ▼
  │     保存到 JSON 文件
  │     (题目、回答、评分、报告)

关于抽题算法:最初用 random.choice() 循环抽题,但发现题库较小时重复率很高。后来改成 random.sample() 做无放回抽样,一次抽好 N 道题,天然不重复。这是一个很典型的"小优化解决大问题"的例子。

关于流式传输:LLM 生成完整报告可能需要 10-30 秒。如果用传统的同步请求,用户盯着 loading 转圈,体验很差。改用 SSE(Server-Sent Events)后,用户能实时看到文字一个个"蹦"出来,等待感大幅降低。

实现方式并不复杂:

后端(FastAPI)

def event_stream():
    for chunk in llm.stream(prompt):
        if chunk.content:
            yield f"data: {json.dumps({'chunk': chunk.content})}\n\n"

return StreamingResponse(event_stream(), media_type="text/event-stream")

前端(Vue 3 fetch)

const response = await fetch(url, { method: 'POST', body: ... })
const reader = response.body.getReader()
while (true) {
  const { done, value } = await reader.read()
  if (done) break
  // 解析 SSE 数据,追加到显示区域
}

4.2 简历诊断流程

上传 PDF → pdfplumber 提取文本
                │
                ▼
        LLM 六维度诊断(流式)
        - 基本信息完整性
        - 技术栈匹配度
        - 项目经历质量
        - 成果量化程度
        - 排版与表达
        - 整体竞争力
                │
                ▼
        自动提取技能清单
        (用于后续场景出题)
                │
                ▼
        保存报告 + 技能 → 可下载 Markdown

关于 PDF 解析:用的是 pdfplumber,纯 Python 库,不需要安装额外依赖。它能很好地处理大多数简历的排版,提取出干净的文本。遇到扫描版 PDF(纯图片)会提取失败,这种情况下会提示用户。

4.3 RAG 场景出题

这个功能结合了向量检索LLM 生成

用户技能标签:["Java", "Spring", "MySQL"]
        │
        ▼
  对每个技能,用向量模型在题库中检索最相关的 3 道题
        │
        ▼
  将检索到的题目作为参考,连同技能标签一起发给 LLM
        │
        ▼
  LLM 生成结合实际项目场景的面试题

这比单纯让 LLM 出题效果好很多,因为有了题库的"锚点",生成的题目更有针对性,不会跑偏。

向量检索的实现用 ChromaDB + 阿里云百炼的 embedding 模型:

from chromadb import PersistentClient
from langchain_openai import OpenAIEmbeddings

# 初始化
embeddings = OpenAIEmbeddings(model="text-embedding-v4")
client = PersistentClient(path="chroma_db")
collection = client.get_or_create_collection("questions")

# 检索
query_vector = embeddings.embed_query("Java 集合框架")
results = collection.query(query_embeddings=query_vector, n_results=3)

4.4 投递记录管理

这个功能相对简单,就是对 JSON 文件的 CRUD 操作。但有一个设计细节值得说一下:状态流转

投递状态有标准的流转顺序:

未投递 → 已投递 → 测评 → 笔试 → 一面 → 二面 → 三面 → 终面 → offer / 已终止

同时还需要处理历史数据中的旧状态名称(比如"笔试进行中"要映射到"笔试"),所以单独抽了一个 normalize_status() 函数来做状态归一化。


五、开箱即用,5 分钟跑起来

这个项目的另一个目标是低门槛。不想让大家为了用一个工具先折腾半天环境。

准备工作

  • Python 3.10+
  • Node.js 18+
  • DeepSeek API Key(去 platform.deepseek.com 注册获取)
  • 阿里云百炼 API Key(去 dashscope.console.aliyun.com 获取)

3 步启动

# 第 1 步:配置环境变量
cp .env.example .env
# 编辑 .env,填入两个 API Key

# 第 2 步:启动后端
pip install -r requirements.txt
python main.py
# → http://localhost:8000/docs 自带 API 文档

# 第 3 步:启动前端
cd frontend
npm install
npm run dev
# → http://localhost:5173

打开浏览器,创建用户,上传简历,开始练习。就是这么简单。

不需要安装数据库,不需要配置 Docker,不需要申请额外的云服务。 两个 API Key + 两个命令,搞定一切。

如果你不想用 Web 界面,也可以直接 python main.py --cli 走命令行交互。


六、开发过程中的一些感悟

6.1 从"能用"到"好用"的距离

项目的第一版是纯 CLI 的,能跑通核心流程,但体验很原始。

后来加了 Web 界面后,才发现"能用"和"好用"之间差了很多东西:

  • 流式传输:LLM 生成需要时间,用户盯着 loading 圈会焦虑。改成流式输出后,用户能看到内容在"生长",心理感受完全不同。
  • Markdown 渲染:LLM 输出的是 Markdown 格式的文本,直接显示就是一堆 ##**。用 md-editor-v3 的预览组件渲染后,报告的可读性提升了一个档次。
  • 报告下载:分析完一份简历后,用户可能想保存或分享。加了"下载为 Markdown"按钮后,这个需求就闭环了。

这些都不是"核心功能",但每一个都能让用户多用几次。

6.2 关于数据存储的取舍

一开始我用 MySQL 存面试记录,后来改成了 JSON 文件。这个过程让我理解了一个道理:

技术选型要看场景,不要看"正确性"。

MySQL 是"正确"的关系型数据库,但对于一个个人工具来说:

  • 面试记录主要是文本数据,不适合塞进关系表
  • 部署 MySQL 对于小工具来说太重了
  • JSON 文件可读、可编辑、可版本控制、可直接复制备份

后来我把这个思路也用在了其他项目上:小工具用文件,大系统用数据库,别一开始就上重型方案。

6.3 Prompt Engineering 比代码更重要

这个项目里,真正"写代码"的时间可能只占 40%,剩下 60% 都在调 prompt。

举个例子,评分 prompt 经历了三个版本:

V1(太简单)

请给这个面试回答打分。

→ 输出格式不统一,分数解析困难。

V2(加了维度)

请从准确性、完整性、表达清晰度三个维度打分,每项 0-10 分。

→ 格式统一了,但反馈太泛,像"准确性不错,建议提升完整性"。

V3(加了约束)

请从三个维度打分,每个维度给出分数后,必须指出回答中具体的亮点和不足,并给出改进建议。格式:准确性:X 分 - …

→ 反馈具体了,能指出"你提到了红黑树但没说明触发条件"。

Prompt 就是和 AI 沟通的"代码",它的质量直接决定了输出质量。这个认知对我帮助很大,不仅在这个项目里,后来做其他 LLM 相关的尝试时,都会先花时间打磨 prompt。

6.4 关于"重复造轮子"

做这个项目的过程中,我也犹豫过:市面上不是有类似的工具吗?为什么还要自己做?

后来想通了:造轮子不是为了替代别人的轮子,而是为了理解轮子是怎么转的。

通过这个项目,我学到了:

  • RAG 架构:向量检索 + LLM 生成的结合方式
  • SSE 流式传输:前后端如何实现实时数据流
  • PDF 文本提取:pdfplumber 的使用和局限性
  • 前端状态管理:Pinia 在实际项目中的用法
  • API 设计:RESTful 接口的规划和文档化

这些经验是看再多教程也换不来的。


七、未来计划

目前项目已经覆盖了面试准备的基本场景,后续还打算:

  • 面试历史回顾:查看过去的面试记录,跟踪进步轨迹
  • 更多题型支持:目前主要是八股文,后续加入算法题、系统设计题
  • 面试报告对比:多次练习后,对比不同时间的得分趋势
  • 导出更多格式:支持导出 PDF 格式的报告

写在最后

这个项目是我学习后端开发过程中的一个实践。它不完美,但对我而言意义重大——从"学知识"到"做东西",这个跨越只有动手了才能完成。

如果你也在准备面试,或者对 LLM 应用开发感兴趣,欢迎试试这个工具,也欢迎提 issue 和 PR。

  • 项目地址:https://gitee.com/MuYi-2025/interview-assistance-system
  • GitHub:https://github.com/Y-MuYi/Interview-Copilot.git

觉得有用的话,点个 Star 支持一下吧!


如果这篇文章对你有帮助,欢迎点赞、收藏、转发。有问题也可以在评论区交流~

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐