Skip to content

Repository files navigation

InterviewAgent Python

基于 FastAPI、LangGraph 与 DeepSeek 的可恢复 AI 模拟面试后端。项目从相邻的 Go 版本重构而来,保留 MySQL、Milvus、Redis 和原 WebSocket 消息类型,并增加可跨断网、进程重启和多实例部署的断线续面协议。

5 分钟启动

cp .env.example .env
# 在 .env 中填写 DEEPSEEK_API_KEY,并为两个 SECRET 分别生成至少 32 位的随机值
docker compose --profile app up -d --build
docker compose --profile app ps
curl -fsS http://localhost:9092/health/ready

首次启动会下载 BGE-M3、跨语言 reranker、Whisper 和中文 VITS 模型,因此时间取决于网络;后续启动会复用 Docker 镜像、构建层和模型卷。所有服务健康后打开 http://localhost:9092/,创建本地账号即可测试。前端由 FastAPI 直接托管,不需要 Node.js。

flowchart LR
  Browser["浏览器:PDF / 数字人 / 语音"] --> API["FastAPI + WebSocket v2"]
  API --> Flow["LangGraph 面试流程"]
  Flow --> DeepSeek["DeepSeek"]
  Flow --> MySQL["MySQL:权威状态"]
  API --> Redis["Redis Streams:补发与连接租约"]
  Flow --> RAG["BM25 + BGE-M3 + Milvus + Reranker"]
  API --> Voice["Whisper ASR + 中文 VITS"]
Loading

关键能力

  • JD 分析、简历匹配、混合 RAG 出题、自适应难度、追问、评分、评估报告和复习计划。
  • MySQL 权威检查点 + outbox、Redis Streams 事件补发、Milvus 用户隔离题库。
  • session_id + resume_token + last_event_id 续面,client_message_id 写入幂等。
  • 后建立连接接管同一面试会话;未完成会话默认保留 24 小时。
  • DeepSeek 只通过 DEEPSEEK_API_KEY 环境变量调用;仓库不保存真实密钥。
  • 内置 Apple Developer 风格响应式前端,覆盖认证、面试、续面、题库与档案。
  • JD 与简历均支持 PDF 原文件预览;桌面端可拖动分隔条放大 PDF 面板,也可全屏查看,后端抽取文字不会显示到页面。
  • 内置状态驱动数字人、本地普通话问题播报、麦克风录音与可编辑 ASR 转写;语音失败时可继续文字面试。
  • 使用 uv 管理 Python、虚拟环境、依赖与锁文件。

领域词汇见 CONTEXT.md,完整验收规格见 spec.md

环境要求

  • Docker Desktop / Docker Engine + Compose
  • uv 0.11+
  • DeepSeek API Key
  • 本机建议至少预留 8 GB 可用内存和 10 GB 磁盘给 BGE-M3、跨语言 reranker、Whisper、中文 VITS、Milvus 与 MySQL

当前 docker-compose.yml 默认使用已解析 digest 固定的 Apple Silicon ARM64 嵌入镜像。官方暂未发布带版本号的 ARM64 标签;x86_64 Linux 启动前设置:

export TEI_IMAGE=ghcr.io/huggingface/text-embeddings-inference:cpu-1.9

Compose 将 BGE-M3 的 TEI 动态批次限制为 1024 tokens,并以同一个 TEI 镜像运行固定 revision 的 multilingual MiniLM INT8 reranker。Whisper 在 CPU 上以 int8 推理、空闲 60 秒后卸载,避免 8 GB Docker Desktop 内存下模型互相挤出。首次启动还会自动下载 Faster Whisper Base 和 sherpa-onnx 中文 VITS;模型分别持久化,健康状态转为 healthy 前请勿开始测试。

Docker 会自动复用本机已有的同标签镜像与构建层。MYSQL_IMAGE 可在全新数据卷上改为本机已有的 MySQL 版本;不要把 8.4 已初始化的数据卷直接降级挂载到 8.0。Dockerfile 将依赖清单与源码分层,单纯修改前端或文档时会复用 uv sync 依赖层。

本地启动

cp .env.example .env
# 编辑 .env,填写 DEEPSEEK_API_KEY,并分别生成 JWT_SECRET、RESUME_TOKEN_SECRET

uv sync --frozen
docker compose up -d mysql redis etcd minio milvus embeddings reranker speech speech-models
uv run alembic upgrade head
uv run interview-agent serve --reload

这个开发模式适合调试 Python;完整语音链路建议使用上面的 --profile app 全容器启动,因为中文 VITS 模型卷会直接挂载到应用容器。

服务地址:

浏览器验收流程

前端由 FastAPI 直接托管,不需要安装 Node.js。打开 http://localhost:9092/ 后:

对应的可编辑 Figma 面试台位于 Interview Room — Apple Developer UI

  1. 创建账号,在“面试台”为 JD 和简历粘贴文字、填写 URL,或分别上传 PDF。上传后页面原样预览 PDF,提取文字只交给后端面试流程,不显示在编辑器中。 桌面端向右拖动 PDF 面板旁的分隔条即可放大;方向键可微调,Shift + 方向键 可快速调节,双击恢复默认宽度。“放大”按钮会打开全屏原文件预览。
  2. 收到题目后数字人自动用普通话播报;点击“语音回答”录音,结束后可编辑 Whisper 转写,再提交并观察评分、追问、评估报告与复习计划。
  3. 面试途中点击“模拟断线”,客户端会自动用 session_id + resume_token + last_event_id 续面。
  4. 在等待回答时直接刷新浏览器,事件时间线与当前作答状态应恢复。
  5. 使用“我的题库”上传 Markdown、TXT、PDF 或 DOCX,再开始面试验证个人 RAG;完成后可在“面试档案”查看历史报告。

为避免覆盖机器上常见的本地服务,Compose 默认把 MySQL、Redis、Milvus 分别映射到 3307638119531,应用映射到 9092;都可通过同名 *_PORT 环境变量覆盖。MinIO 只供 Milvus 内部使用,不暴露宿主机端口。

项目启动命令默认关闭 Uvicorn 访问日志,因为浏览器 WebSocket 需要在连接 URL 中携带访问令牌;应用自身日志不会记录该 URL 或令牌。

也可以将应用一并放入 Compose:

docker compose --profile app up -d --build

WebSocket v2 最小流程

连接后初始化:

{"type":"connection_init","protocol_version":2,"client_message_id":"uuid"}

开始面试:

{
  "type":"start_interview",
  "protocol_version":2,
  "client_message_id":"uuid",
  "jd":"岗位描述文本",
  "resume":"简历文本"
}

保存服务端 session_created 中的 session_idresume_token 和最新 event_id。断线后发送:

{
  "type":"resume_session",
  "protocol_version":2,
  "client_message_id":"uuid",
  "session_id":"session uuid",
  "resume_token":"short lived token",
  "last_event_id":"redis stream id"
}

提交答案:

{
  "type":"answer",
  "protocol_version":2,
  "client_message_id":"new uuid for this answer",
  "session_id":"session uuid",
  "content":"候选人的回答"
}

同一个 client_message_id 可以安全重发,只会推进一次面试状态。

旧客户端兼容

旧消息类型 chatstart_interviewanswerupload_questionsquit_interview 仍然可用。未发送 v2 字段时,服务端自动生成消息 ID,但该连接不具备可靠的客户端重试语义。

常用命令

make lint
make test
make ci
make run
make infra-up
make infra-status
make infra-down
make eval
make secrets

包含真实 MySQL/Redis 的恢复链路测试需要中间件已启动:

RUN_INTEGRATION=1 uv run pytest tests/integration/test_durable_session.py -q

全栈容器已经健康后,可运行真实 RAG 与语音集成测试:

RUN_INTEGRATION=1 uv run pytest tests/integration -q

RAG 评测集包含 100 条 tuning 和 200 条冻结 test 样本,覆盖五个主题、五种问法及跨主题 hard negative。准备固定题库并运行四组实验:

uv run python scripts/generate_rag_eval_dataset.py
uv run interview-agent eval --prepare \
  --experiments vector keyword hybrid_rrf hybrid_rrf_rerank

评测输出到 data/eval/reports/,默认不提交生成报告。

GitHub Actions 与镜像发布

.github/workflows/ci.yml 在每次 push、Pull Request 和手动运行时执行:

  • 敏感信息与完整 Git 历史扫描;
  • uv 锁文件、Ruff、Mypy、JavaScript 语法与 56 项单测;
  • 真实 MySQL 8.4 + Redis 7.4 持久化/重连集成测试;
  • Compose 配置验证与应用 Docker 镜像构建。

mainv* 标签通过全部检查后,使用 GitHub 自带的短期 GITHUB_TOKEN 发布到 ghcr.io/<owner>/<repo>,无需保存自定义 Registry Token。部署到具体服务器仍需根据目标平台新增受保护 Environment;仓库不会预设长期服务器凭据。

数据与恢复边界

  • MySQL:用户、会话检查点、客户端消息幂等记录、outbox、面试历史、候选人画像和题库元数据。
  • Redis:会话事件流、连接所有权、worker 租约和客户端事件确认位置。
  • Milvus:BGE-M3 生成的题库向量;不是权威业务存储。
  • Redis 丢失时,未发布 outbox 会再次发布;事件业务 ID 用于识别重复发布。
  • WebSocket 断开后,面试在等待答案的检查点暂停,不保留依赖旧连接的内存 channel。

安全提醒

如果 API Key 曾出现在聊天、工单或日志中,应在 DeepSeek 控制台轮换。不要把真实 .env、数据库卷、上传 PDF、录音或访问令牌提交到仓库。

发布前执行 make secrets。它会拒绝被跟踪的运行时 .env,并检查当前文件和完整 Git 历史中的 DeepSeek Key、GitHub Token、AWS Key、Slack Token 与私钥头。GitHub Actions 也会执行相同检查;即便扫描通过,曾经公开过的 Key 仍必须轮换。

Releases

Packages

Contributors

Languages