用 AI 复现 AI 面试官。项目代号:Interview Agent
以下记录 P1 / P2 中已落地功能,与上方规格描述对照阅读。
- 状态机 21 个状态,30+ 转移规则完整实现
- PDF 简历解析 → 背景画像 → 规划问题 → 技术问答 → 人格评估 → Fuku 式报告
- 动态人格问题:
personality_dynamic_questions=true(默认)时 LLM 根据 JD + 画像生成问题,而非静态列表 - 动态追问决策:
dynamic_follow_up_decision=true时每题由 LLM 自主判断追问/下一题/收尾 - 画像合并(简历初稿 + 引导增量),冲突处理与来源标记
- 多段经历逐个深挖,经历级综合评估
- 会话状态落盘(JSON + JSONL 事件流)
- 实时听写:录音开始 2s 后开始每 5s 上传累积完整 webm 块,输入框实时显示转写结果(不等停止)
- 停止后补发:停止录音时若有未转写分块,立即补发最终段
- TTS 朗读:点击 🔊 按钮,
window.speechSynthesis朗读当前题目(zh-CN) - STT 三选一:通过
STT_BACKEND切换sensevoice_http(默认,最快)/xfyun/local_whisper - SenseVoice 本机服务:
SenseVoice/目录已纳入主仓库(做法 A),uvicorn SenseVoice.api:app启动 - VAD 切段(
webrtcvad,无则固定 55s 窗口兜底),避免讯飞 60s 上限 - 文字输入全程保留,可随时切换说/打字
GET /api/config返回transcribe_auto_submit/tts_enabled/stt_backend
ai-interview-agent/
├── app/
│ ├── config.py # 所有配置(STT/LLM/Guards/人格开关)
│ ├── main.py # FastAPI 入口 + 所有 HTTP 端点
│ ├── schemas/ # Pydantic 模型(Context/Plan/Scores)
│ ├── services/
│ │ ├── session_runner.py # 业务编排层
│ │ └── stt/ # STT 提供者工厂
│ │ ├── factory.py # STT_BACKEND → 提供者路由
│ │ ├── sensevoice_http.py
│ │ ├── xfyun_iat.py
│ │ ├── local_whisper.py
│ │ ├── audio_pcm.py # webm → PCM 16k 单声道
│ │ └── vad_segment.py # VAD 静音检测切段
│ └── state_machine/ # 状态机引擎
├── static/
│ ├── app.js # 前端(含录音/TTS/实时听写/轮询)
│ └── index.html # 面试主界面
├── SenseVoice/ # FunAudioLLM/SenseVoice 裁剪版(已纳入 Git)
├── docs/SENSEVOICE.md # SenseVoice 子模块说明
└── run.py # 启动脚本
目标: 构建一个开源的 AI 驱动的面试系统,能够自主引导面试全流程(从候选人背景收集到问题生成到评分),生成结构化评估报告。
定位: 对标 Fuku AI Interview Agent,但完全透明、开源可审计、支持本地部署。
核心价值:
- 全流程可复现、可审计(不是 black box)
- AI 主动引导,非填表式——更像真人面试
- 支持本地部署(隐私敏感场景)
- 多语言、多面试场景适配
流程执行以状态机为准,详见:
STATE_MACHINE.md(本目录)。
候选人 ────────────────────────────────────────────────►
┌─────────────────────────────────────────────────────┐
│ 【阶段 0】面试规划(AI 内部分析) │
│ AI 读取候选人背景 + JD │
│ → 分析每段经历的关键问题点 │
│ → 规划本次面试的问题列表 │
│ → 确定每个问题考察什么能力 │
└────────────────────────┬──────────────────────────┘
▼
┌─────────────────────────────────────────────────────┐
│ 【阶段 1】AI 引导自我介绍 & 背景收集 │
│ AI 主动提问,候选人文字回复 │
│ → 收集:学历、实习经历、技术栈、求职目标、薪资期望 │
│ → 输出:候选人背景画像(JSON) │
└────────────────────────┬──────────────────────────┘
▼
┌─────────────────────────────────────────────────────┐
│ 【阶段 2】AI 根据背景 + 规划提问列表逐题提问 │
│ LLM(MiniMax M2.7)按规划的问题顺序提问 │
│ 候选人文字回复 │
│ → 每回答后 AI 决定:追问 / 换下一题 / 结束 │
└────────────────────────┬──────────────────────────┘
▼
┌─────────────────────────────────────────────────────┐
│ 【阶段 3】AI 逐题评分(实时) │
│ LLM(MiniMax M2.7)逐题评分 │
│ → 三维度打分 + 理由 + 改进建议 │
└────────────────────────┬──────────────────────────┘
▼
┌─────────────────────────────────────────────────────┐
│ 【阶段 4】生成最终评估报告 │
│ → JSON 文件存储(可导出 HTML 报告) │
│ → 包含:背景画像 + 面试规划 + 每题评分 + 总体建议│
└─────────────────────────────────────────────────────┘
◄──────────────────────────────────────────────────────
参考 Fuku AI,每道题从 3 个维度打分:
| 维度 | 分制 | 含义 |
|---|---|---|
| Expertise(专业度) | 1-5 | 技术深度、准确性、是否到 senior 水平 |
| Clarity(表达清晰度) | 1-5 | 逻辑结构、术语准确性、是否有重复/模糊表述 |
| Detailedness(详细程度) | 1-5 | 是否有具体数据/实例/实现细节/量化结果 |
每个维度的评分标准:
| 分数 | 标签 | 含义 |
|---|---|---|
| 1 | Below average | 明显缺失或错误 |
| 2-3 | Average | 基本到位但不深入 |
| 3-4 | Good | 不错,有亮点 |
| 4-5 | Excellent | 非常出色,超越预期 |
每题 AI 输出格式(固定结构):
## 题目 X:[问题原文]
### 评分
- Expertise:3/5(Good)
理由:候选人展示了XXX的正确理解,正确识别了关键组件和挑战。然而,缺乏数学层面的清晰度和更广泛的设计考量,表明他还未达到 senior 水平。
- Clarity:2/5(Average)
理由:回答有些口语化、表达不精确,有用词重复和不清晰的表述,影响了整体的连贯性。
- Detailedness:2/5(Average)
理由:虽然覆盖了主要模块,但缺少实现细节、替代方案和量化证据。损失函数类型、噪声处理、评估指标等关键方面均未涉及。
### 改进建议
1. **结构化回答**:先用简要概述,再用具体过程,最后说挑战和结果。
示例:"The RERAW model focuses on reconstructing RAW images from RGB inputs. My approach involved designing modules for color reconstruction..."
2. **使用精确术语**:避免口语化表达,用准确的技术术语描述。
示例:"The multi-head gamma predictor allows us to estimate the RAW image before applying gamma transformation..."
3. **突出具体挑战**:说清楚遇到了什么问题、如何解决的、结果如何。
示例:"One challenge was balancing the gradients from different gamma heads. I adjusted the loss function to improve the model's accuracy..."
4. **用数据量化结果**:尽可能给出具体指标。
示例:"As a result, the model's PSNR improved by 2dB, leading to more accurate RAW image reconstructions."
这是我们 vs Fuku AI 最重要的区别!
Fuku AI 是"一题一评"模式,随机抽题。我们要有面试规划——像真人面试官一样,面试前就想好要问什么。
PLANNING_PROMPT = """
你是一个资深面试官。面试开始前,请根据候选人背景和目标岗位,做面试规划。
【候选人背景】
{candidate_background}
【目标岗位 JD】
{job_description}
【规划任务】
1. 分析候选人每段实习/工作经历,找出 2-3 个最值得深挖的点
2. 针对每个深挖点,规划 1 个核心问题 + 可能的追问方向
3. 确定本次面试的问题列表(建议 3-5 题,覆盖不同能力维度)
4. 说明每道题考察什么能力
【输出格式】
```json
{
"interview_plan": [
{
"question_id": "Q1",
"target_experience": "华为实习 - RERAW 轻量化模型",
"ability_dimension": "计算机视觉 / 模型设计能力",
"main_question": "请介绍一下你在华为做的 RERAW 轻量化模型的设计思路和遇到的挑战...",
"follow_up_directions": [
"如果回答模糊,追问具体模块设计",
"如果只说结果,追问训练过程和 loss 设计",
"如果缺乏量化,追问具体提升了多少 PSNR"
],
"expected_depth": "能说清楚模型架构、设计取舍、实验结果"
},
{
"question_id": "Q2",
"target_experience": "Infineon - TCL 自动化脚本",
"ability_dimension": "工程能力 / 脚本自动化",
"main_question": "在 Infineon 做芯片验证时,你如何用 TCL 脚本优化测试流程...",
"follow_up_directions": [
"如果只说做了什么,追问具体减少了多少 runtime",
"如果缺乏具体技术,追问脚本的核心逻辑"
],
"expected_depth": "能说清脚本逻辑、优化效果、可量化的结果"
}
],
"question_order": ["Q1", "Q2", ...],
"estimated_duration": "10-15 分钟"
}每道题回答后,AI 自主决定是否追问:
FOLLOW_UP_DECISION_PROMPT = """
候选人回答了问题:"{question}"
回答内容:"{answer}"
【追问决策】
请判断是否需要追问。规则:
- 如果回答已经足够具体(包含具体数字、步骤、结果)→ 不追问
- 如果回答模糊、有明显漏洞、可以深挖 → 追问
- 如果候选人说"不知道"或明显跑题 → 追问(换一种问法)
【输出格式】
```json
{
"decision": "follow_up" | "next_question" | "wrap_up",
"reason": "判断理由(1-2句)",
"follow_up_question": "(如果决定追问)追问内容",
"new_direction": "(如果换方向)新的追问方向"
}所有规划问题问完后,AI 判断是否结束:
WRAP_UP_PROMPT = """
面试已完成 {n} 道题。
【各题评分概览】
{scores_summary}
【最后一道题的回答】
{last_answer}
判断:
- 如果某维度普遍低于 2 分,考虑追加补充问题
- 如果整体不错,主动告知面试结束
- 任何时候不超过 12 道题(避免候选人疲劳)
【输出格式】
```json
{
"continue": true | false,
"reason": "判断理由",
"additional_questions": ["如果继续,追加的问题(最多1-2道)"]
}不是填表,是 AI 主动提问! 模仿真人面试官的开场:
引导问题序列(LLM 自动生成):
1. "请简单介绍一下你的学历背景,包括学校、专业和毕业时间。"
2. "请介绍一下你最近的实习或工作经历,重点说清楚你负责的项目和使用的技术。"
3. "在这些经历中,你最擅长的技术领域是什么?"
4. "你为什么想要离开目前的岗位/寻找新机会?"
5. "你希望下一份工作是什么样的?(公司类型、团队氛围、技术方向等)"
6. "你的期望薪资范围是多少?"
AI 根据候选人回答动态追问,最后生成背景画像 JSON:
{
"name": "候选人姓名",
"education": "NTU CS Master 2026",
"experience": [
{
"company": "华为新加坡研究院",
"role": "Video/Audio Research Engineer Intern",
"skills": ["CV", "RAW重建", "KVCACHE优化"],
"projects": ["RERAW轻量化模型", "VLM KVCACHE"]
}
],
"strongest_skills": ["计算机视觉", "算法优化", "系统调优"],
"reason_to_move": "作为应届生,希望获得全面成长...",
"career_goals": "AI方向entry-level,挑战性项目,协作团队...",
"salary_expectation": "10,000 SGD/月"
}该模块在状态机中由
PERSONALITY_ASK -> PERSONALITY_EVAL承载,详见STATE_MACHINE.md。
参考 Fuku AI 的 5 维度滑动条,AI 根据回答内容推断:
| 维度 | A 端 | B 端 |
|---|---|---|
| 稳定性(Stability) | Conservative | Innovative |
| 思维(Thinking) | Logical | Compassionate |
| 社交(Social) | Introvert | Extrovert |
| 角色(Role) | Individual Contributor | Team Manager |
| 创新方式(Innovation style) | Incremental | Transformative |
说明:稳定性与创新方式是两轴:前者偏整体是否守成/拥抱变化;后者偏创新路径是渐进迭代还是颠覆式/范式级,英文极名 deliberately 不同,避免报告页两栏文案看起来「一模一样」。
在技术问答结束后,进入 PERSONALITY_ASK:题量由配置 personality_questions_count 控制(默认约 6 题)。题目需兼顾:
- 行为证据(供五维人格量表):如需求不明时的推进、分歧处理、协作偏好、高压取舍等。
- 与 Fuku 式报告叙事对齐:在画像/技术面材料仍不足时,通过口语题补采 候选人概览与亮点、核心专长与岗位匹配、看机会动机、职业目标、薪资期望 等(薪资提问宜委婉,允许不透露具体数字)。
动态出题由 personality_questions_plan_user 提示词约束;关闭 personality_dynamic_questions 时使用 prompts.PERSONALITY_QUESTIONS 静态回退列表。
说明:本阶段以短问短答为主,一般不再多轮深挖;若回答过于抽象,可在后续版本中考虑单题一次追问。
- 输出 5 维度倾向(滑动条两端)
- 每个维度必须包含:
倾向+证据+置信度 - 证据来源优先级:人格问题回答 > 技术问答中的行为片段 > 背景引导内容
输出格式:
{
"personality": {
"innovation": {
"pole_a": "Incremental",
"pole_b": "Transformative",
"leaning": "Transformative",
"rationale": "…"
}
}
}分阶段交付,与 RESEARCH.md 中的选型建议一致;P1 明确不做 RAG,先跑通状态机与核心链路。
目标: 文本面试全流程可跑通、可审计、本地可部署。
前端: 纯 HTML + Vanilla JS(单文件,零框架成本;后续 P3 再工程化)
后端: Python FastAPI(状态机驱动,见 STATE_MACHINE.md)
LLM: MiniMax M2.7(CodePlan 覆盖时零额外模型成本)
JD / 题库: 硬编码或配置文件,不接入向量检索 —— 不做 RAG
存储: 本地 JSON + 事件 JSONL(无数据库)
部署: pip install + python run.py
目标: 在 P1 文本主链路稳定的前提下,增加「语音输入」路径。每题录制一段音频,录音停止后自动转写并注入评分流程。
P2 第一步(已定案):语音子状态仅前端。 录音、上传、等 STT 等 UI 阶段不写入服务端 State 枚举;服务端在作答环节与 P1 一样保持「等待本题答案」,直至收到 POST .../answer 的文本。转写由 POST /api/audio/upload 完成,返回文本后由前端填入输入框(可编辑)再发送,或产品确认后自动提交——服务端路径与纯打字一致。详见 STATE_MACHINE.md §11.0。
用户交互流程:
AI 提问
-> 候选人点击 🎤 开始录音
-> 候选人点击 ⏹ 停止录音
-> 前端上传音频到后端
-> 后端 STT 转写
-> 转写文本进入现有评分流程(状态机不变)
-> AI 追问 / 下一题 / 收尾
技术方案:
| 环节 | 选型 | 说明 |
|---|---|---|
| 录音 | 浏览器 MediaRecorder API | 单段 .webm 文件,每题一段 |
| 后端接收 | FastAPI POST /api/audio/upload |
UploadFile 接收 .webm |
| STT 转写 | Faster-Whisper(本地 CPU/GPU) | 零云成本,隐私友好,支持中文 |
| 降噪处理 | 音频预处理(可选) | 去除静音片段,降低 STT 错误率 |
| 分析注入 | 转写文本经 POST /answer 进入与 P1 相同的后续状态 |
服务端状态机不增录音态 |
STT 备选方案(按接入难度排序):
| 方案 | 优势 | 劣势 |
|---|---|---|
| Faster-Whisper(本地) | 零成本,隐私,不依赖网络 | 首次需下载模型(~1GB) |
| MiniMax STT API | 接入简单,中文效果好 | 走云端,有成本 |
| Groq Whisper | 速度快,$0.004/分钟,极便宜 | 走云端,需 API Key |
| 讯飞语音听写(流式版 WebAPI) | 中文/方言能力强,控制台有免费额度;流式可边传边收 | 走云端与商务套餐;WebSocket + 服务端鉴权;单路约 60s,长回答需 VAD 多段串行 或 文件转写(见下文) |
讯飞听写(与现有 P2 架构的接法):
- 官方文档:语音听写(流式版)WebAPI — 端点示例
wss://iat-api.xfyun.cn/v2/iat,握手带authorization/date/host(HMAC-SHA256 + Base64),需 AppID、APIKey、APISecret(仅放服务端环境变量)。 - 与本项目
POST /api/audio/upload对齐:浏览器仍上传 单段录音(如.webm);FastAPI 将音频转为接口要求的格式(常见为 16 kHz、16 bit、单声道 PCM,或文档支持的 speex/mp3 等),再在服务端建立 WebSocket、按文档分帧发送(注意单帧 base64 长度等限制),聚合返回 JSON 中的文本后作为text返回给前端。不要在浏览器直连讯飞 WebSocket(会泄露密钥且跨域/鉴权不便)。 - 流式听写单路会话上限约 60s(以 官方说明 为准)。面试单题口述往往更长,必须在实现层应对,不能只「卡用户 1 分钟」。
长回答应对策略(与 60s 限制自适应,可组合):
-
服务端 VAD 自适应切分 + 多路串行听写(推荐,仍用流式听写 API)
- 整段音频解码为 16 kHz 单声道 PCM 后,用 VAD(如 Silero-VAD、WebRTC VAD) 在静音/停顿处切分为多个片段;每片段目标时长 ≤ 55s(留余量,避免单路触顶),尽量避免在说话中间硬切。
- 找不到足够长静音(连续长篇大论)时,再对 PCM 按时长兜底切分(例如每 50~55s 一刀),作为次要策略。
- 每个片段 独立一次 WebSocket 听写会话,按时间顺序执行,将各段识别结果 顺序拼接(段间可用空格或换行)。
- 边界减损:相邻片段可保留 约 0.3~0.5s 重叠 再分别送识别,合并文本时对重叠区做简单去重(可选,避免重复词)。
-
整段 HTTP 转写(同平台备选或自动降级)
- 评估讯飞 录音文件转写、极速转写等 整文件上传 能力:若支持 数分钟级 单文件,则
POST /api/audio/upload内可对「超过某阈值时长」的音频 自动走文件转写,避免自研多段 WS 拼接。 - 与方案 1 可并存:短音频单路 WS;长音频文件接口 或 VAD 多段 WS,由服务端策略选择。
- 评估讯飞 录音文件转写、极速转写等 整文件上传 能力:若支持 数分钟级 单文件,则
-
前端分段录制(补充)
- 引导用户 停顿后再续录(多段 blob 或多请求),服务端仍按「多段转写 + 拼接」处理;体验弱于「一次长录、服务端自适应切」,可作为无 VAD 时的兜底。
-
产品兜底
- 保留 文字输入;极端失败时提示重录或改打字。
降噪/预处理(与长音频一致): VAD 既可用于去静音提升准确率,也是 自适应切分 的核心信号,建议放在同一套音频管线中实现。
新增 API 端点:
POST /api/sessions/{session_id}/transcribe (推荐:路径即校验会话存在)
Body: multipart/form-data
- audio: 单段录音(常用 webm;服务端 ffmpeg 解码为 16k PCM)
Response: { "text": "…", "confidence": null | number }
说明:只转写,不推进会话状态;前端拿到 text 后再 POST /answer。
POST /api/audio/upload (与历史 SPEC 字段兼容)
Body: multipart/form-data
- session_id: 字符串(必填)
- audio: 同上
Response: 同上
另:GET /api/config 返回 transcribe_auto_submit、tts_enabled、stt_backend 供前端行为开关(可选)。
降噪/预处理(可选 Phase 2.1):
- 使用
silero-vad(等)检测语音边界:既可裁掉首尾静音、也可驱动上文「长回答切分」 - 减少无意义音频输入,提升 STT 准确率并降低幻觉
文字输入保留(Fallback):
- Phase 2 同时保留文字输入路径
- 候选人可自由切换"说"或"打字",体验一致
- 架构:输入层抽象,
AnswerInput = TextInput | AudioInput
TTS 播报题目(可选 Phase 2.2):
- AI 提问后可选 TTS 朗读题目(提升真实感)
- 方案:Web Speech API(浏览器原生,无需后端)或 MiniMax TTS API
目标: 提升可用性、可读性与可维护性,不等于必须上云。
前端: Next.js + TailwindCSS(或 React + Vite 工程化后再迁)
体验: 会话状态展示、报告可视化、加载与错误态
可选: Docker Compose 打包、多会话/多候选人管理界面
- RAG:JD/题库/公司知识库向量检索(如 Chroma / Qdrant)
- 存储升级:PostgreSQL + 对象存储
- 产品化:账号体系、与招聘系统集成等
SCORING_PROMPT = """
你是一个资深面试评分官。请根据候选人的回答,从以下三个维度评分。
【评分维度】
- Expertise(专业度):技术深度、准确性、是否达到 senior 水平
- Clarity(表达清晰度):逻辑结构、术语准确性、表达连贯性
- Detailedness(详细程度):具体数据/实例/量化结果/实现细节
【输出格式】
对每个维度,输出:
- 分数(1-5)和等级标签(Below average / Average / Good / Excellent)
- 2-3 句话的理由
- 4 条改进建议,每条带一句话示例
【评分标准】
1 = 明显缺失或错误
2-3 = 基本到位但不深入
3-4 = 不错,有亮点
4-5 = 非常出色
【问题】
{question}
【候选人回答】
{answer}
请开始评分:
"""与 §7 技术架构 的 P1 / P2 / P3 对齐。
- 流程编排采用状态机驱动(
STATE_MACHINE.md) - PDF 简历提取 → 初稿画像 → 引导补全 → 按经历技术问答 → 收尾与人格(若开启)→ 报告
- 文字对话为主;JD 硬编码或配置文件,不接入 RAG
- 单页 HTML + Vanilla JS 或最小前端;存储为本地 JSON / JSONL
- 每题一段 .webm 录音(MediaRecorder API)
- 后端
POST /api/audio/upload仅 STT 转写;服务端状态机不增加录音相关状态 - 转写文本经与 P1 相同的
/answer提交后进入评分等后续逻辑 - 保留文字输入作为 Fallback,支持混合使用
- 可选:TTS 播报题目(Web Speech API 或 MiniMax TTS)
- Next.js + Tailwind(或 React 工程化方案)重写/增强 UI
- 报告与会话体验优化 多会话列表等
- RAG(题库 / JD / 知识库)、PostgreSQL、账号与产品化集成等 —— 见 §7「后续可选」
- 项目名:
ai-interview-agent - License:Apache 2.0
- Fuku AI Interview Agent(产品参考,评分 rubric 来
- TechSpar(记忆系统):https://github.com/AnnaSuSu/TechSpar
- openhire(语音面试):https://github.com/brijeshmarch16/openhire
- 本项目状态机规范:
STATE_MACHINE.md