在准备技术面试的过程中,很多同学都遇到过类似的困境:刷题刷了不少,但真正面对面试官时,要么表达没有条理,要么一紧张就写不出完整代码。最近我一直在关注一类新的 AI 工具:让 AI 扮演面试官,用语音和候选人做多轮对话,并直接在线考察编码能力。Prepin 正是这个方向里比较有代表性的产品——它的定位很清晰:AI that interviews engineers with voice and live coding。
这篇文章我不会去分析某个具体产品值不值得买,而是想围绕“AI 面试官 + 语音对话 + 在线编程”这条技术主线,拆解这类工具的使用流程、底层原理,并带大家手写一个极简可运行的 Demo。无论你是准备面试的候选人,还是想在企业内部搭建面试陪练系统的开发者,这篇文章都能给你一个相对完整的视角。
1. 背景与核心概念
1.1 什么是“AI 面试官”工具
AI 面试官工具本质上是一个面向“技术面试场景”的 AI Agent。它不只是简单地生成面试题,而是能够根据候选人的回答进行追问、引导,甚至在候选人写代码时给出即时反馈。
它的核心能力可以拆成四块:
- 语音输入与输出:模拟真实面试中的口头交流。
- 语义理解与追问:理解候选人的答案,判断是继续追问还是换一个问题。
- 在线编程环境:候选人可以在页面里直接写代码,系统会运行并评测。
- 结果复盘:面试结束后生成评分报告和面试记录。
Prepin 的业务形态就是围绕这四块能力展开的。它把“面算法题”和“面工程能力”结合到一起,目标不是替代 HR,而是替代技术面试官的一部分重复劳动。
1.2 语音 + 在线编程意味着什么
过去很多刷题平台只考察“代码是否正确”,但真实技术面试里,沟通能力、思路表达和代码实现同样重要。
语音交互主要解决两个问题:
- 还原真实面试压力:口头表达的时候,候选人不能像写笔记那样慢慢组织语言,这对临场反应是很大的考验。
- 识别表达逻辑:AI 可以通过候选人的语音内容判断其思路是否清晰,而不是只盯着最后提交的代码。
而 live coding(在线编程)则把考察范围从“八股文”扩展到真实代码能力。候选人需要在限定时间内完成编码,AI 再通过测试用例、静态检查等方式评估代码质量。
1.3 这类工具适合谁
- 正在准备大厂面试的候选人:用它做模拟面试,避免“只会刷题、不会开口”。
- 企业内部招聘团队:降低技术初筛的人力成本,让工程师把时间留给终面。
- AI 应用开发者:研究语音对话、代码生成、自动评测这些模块如何组合成一个完整产品。
2. 使用前的环境准备与配置
在使用 Prepin 这类 AI 面试官工具之前,先确认本地环境是否满足基本要求。虽然不同产品在细节上有差异,但通用的准备逻辑是相似的。
2.1 基本运行环境
工具大多以 Web 页面形式提供,所以你的电脑只需要满足:
- 操作系统:Windows、macOS、Linux 均可,建议 Chrome 或 Edge 最新版本。
- 麦克风:面试过程中需要语音答题,请提前测试麦克风权限。
- 网络:需要稳定访问模型服务,语音识别和代码评测都依赖网络。
- 摄像头非必须,但如果产品支持视频回放,可以提前确认权限。
这里建议你在正式开始面试前,先做一个“设备自检”,例如录一段自己的语音并回放,确认声音清晰、没有明显回声。
2.2 模型服务与账号配置
如果你是在浏览器里直接用官方页面,只需要注册账号并选择面试岗位方向即可。
如果你打算自己搭建或改造一个类似的系统,则需要准备:
- 一个大模型 API Key,例如 OpenAI 兼容接口,或者其他国内云厂商的模型服务地址。
- 一个语音识别服务,常见的有云厂商的 ASR 接口,也可以使用本地开源方案。
- 如果希望 AI 用语音提问,还需要一个 TTS 语音合成服务。
版本需要根据你的项目实际情况调整,本文示例以常见的 OpenAI 兼容接口为例,重点演示配置思路。
2.3 准备一个“面试房间”
在企业内部落地时,建议按“面试房间”的方式组织功能:
- 每个候选人一个独立的会话记录。
- 面试题目按照岗位方向配置题库。
- 面试结束后自动生成报告,包含语音转写文本、代码提交记录和评分。
这个思路和 Prepin 的产品形态类似,核心是让整个过程可追溯、可复盘,而不仅仅是“聊一次天”。
3. 核心流程拆解:一场 AI 面试如何运转
3.1 四个关键阶段
一次完整的 AI 技术面试,大致可以分为四个阶段:
- 开场与自我介绍:AI 简短介绍自己,并让候选人做简单沟通,用于设备调试和状态放松。
- 基础问题考察:围绕语言基础、操作系统、网络、数据库等方向出题,候选人用语音回答。
- 在线编程环节:给出一个具体的编码题目,候选人在编辑器里完成代码,系统自动运行测试用例。
- 反馈与复盘:面试官根据回答内容、代码质量、沟通表达能力输出综合评分。
3.2 面试官角色如何被约束
这里最容易被忽略的一点是:AI 一旦“太自由”,就会忍不住直接替候选人回答。所以在系统里,我们通常会通过 system prompt 对模型做角色约束。
一个典型的 prompt 大概长这样:
你是一名资深后端技术面试官。你的任务是考察候选人的真实水平,而不是展示你的知识。 规则: 1. 每次只问一个问题,候选人回答后,根据回答内容决定追问还是换题。 2. 不要替候选人回答问题,也不要直接给出完整答案。 3. 当候选人代码有误时,给出提示,让候选人自己修正。 4. 用中文交流,语气专业但不冷漠。这样的约束能让 AI 更像“面试官”而不是“讲解员”。
3.3 题目难度与追问策略
AI 面试官的追问逻辑通常有两种:
- 正向深入:候选人答对了基础概念,就追加一个工程场景题,考察知识迁移能力。
- 反向纠错:候选人回答不完整,AI 会挑出漏洞,引导候选人重新思考。
这两种策略组合在一起,AI 才能在有限时间内尽可能探测出候选人的能力边界。
4. 技术原理:这类系统背后的实现方式
4.1 语音链路
语音输入链路是:麦克风采集音频 → ASR 语音识别 → 生成文本 → 送入大模型。
语音输出链路是:大模型生成文本 → TTS 语音合成 → 扬声器播放。
在这条链路里,最容易出问题的两个点是:ASR 识别误差和 TTS 延迟。如果候选人有比较重的口音,或者环境噪声较大,识别结果会直接影响后续的对话质量。
4.2 对话与代码生成
面试官 Agent 需要维护一段会话历史,把之前的提问和回答都传给大模型,让模型具备“上下文记忆”。这和普通聊天机器人不同,它需要更严格的角色约束。
当进入 live coding 环节时,系统通常会单独把“代码题”和“候选人提交的代码”作为输入,传递给一个评测模块,而不是让大模型直接判断代码是否通过。因为大模型判断代码结果容易产生幻觉,最可靠的方式还是真正跑一边测试用例。
4.3 代码执行与评测
代码评测部分,一般会做这几件事:
- 把候选人代码保存成临时文件。
- 在隔离环境中执行代码,传入标准输入。
- 捕获标准输出、错误输出、运行时间和退出码。
- 与预置测试用例的期望输出做对比。
这里最关键的问题不是评测逻辑,而是“隔离”。如果直接在主机的 shell 里执行候选人提交的任意代码,一旦遇到恶意代码,后果会非常严重。
4.4 评分与复盘
评分通常不是让大模型直接给一个分数,而是先采集多维数据:
- 语音转写记录
- 每个问题的回答时长
- 代码是否正确通过测试用例
- 代码风格与边界处理
然后再由结构化规则 + 大模型综合判断生成评语。这样才能避免“模型觉得你答得好,但测试用例一个都没过”的情况。
5. 实战:实现一个极简“语音 + 在线编程”AI 面试 Demo
下面我们用一个 Python 项目,把上面这套流程简化实现出来。项目会包含:
- 语音提问与语音答题(可切换为文字模式)
- AI 生成面试题并按回答进行追问
- 一个简单的在线编程评测环节
5.1 项目结构
先创建项目目录:
ai_interview_demo/ ├── requirements.txt ├── voice_io.py ├── interviewer.py ├── code_runner.py └── main.py5.2 安装依赖
依赖文件内容:
openai speechrecognition pyttsx3 pyaudio安装命令:
pip install -r requirements.txt如果你的电脑没有可用的麦克风,或者 pyaudio 安装失败,可以先跳过语音模块,使用文字模式跑通流程。pyaudio 在 Windows 和 Linux 上有时需要额外安装系统依赖,具体以你本机环境为准。
5.3 语音模块
语音模块负责两件事:播放面试官的问题,识别候选人的回答。
# 文件路径:ai_interview_demo/voice_io.py import speech_recognition as sr import pyttsx3 engine = pyttsx3.init() recognizer = sr.Recognizer() def speak(text: str) -> None: """让面试官把文本内容用语音播报出来。""" engine.say(text) engine.runAndWait() def listen(timeout: int = 8) -> str: """从麦克风获取候选人语音,并转成文本。""" with sr.Microphone() as source: print("请开始回答...") recognizer.adjust_for_ambient_noise(source) audio = recognizer.listen(source, timeout=timeout) try: return recognizer.recognize_google(audio, language="zh-CN") except sr.UnknownValueError: return "" except sr.RequestError as e: return f"[语音服务异常] {e}"这里使用的是系统默认麦克风和 Google 的免费语音识别,适合本地演示。生产环境建议替换为云厂商 ASR,识别速度和准确率会更高。
5.4 题目生成模块
题目生成模块负责调用大模型,并维护面试官的角色。
# 文件路径:ai_interview_demo/interviewer.py import os try: from openai import OpenAI client = OpenAI( api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1"), ) except Exception: client = None FALLBACK_QUESTIONS = [ "请介绍一下 Python 中列表和元组的区别,以及各自适用的场景。", "在 Web 服务里,你会怎么设计数据库连接池?", "谈谈你对 RESTful API 的理解,幂等性体现在哪里?", "如果线上接口突然变慢,你的排查思路是什么?", ] _FALLBACK_INDEX = 0 def generate_question(job_role: str, last_answer: str = "") -> str: """生成面试官的下一个问题。 如果没有配置大模型 API,则退回到内置题库, 方便先跑通整体流程。 """ global _FALLBACK_INDEX if client is None: q = FALLBACK_QUESTIONS[_FALLBACK_INDEX % len(FALLBACK_QUESTIONS)] _FALLBACK_INDEX += 1 return q messages = [ { "role": "system", "content": ( f"你是一名严格的{job_role}岗位面试官。" "每次只问一个问题,不要替候选人回答," "根据候选人的回答决定是追问还是换下一个问题。" ), } ] if last_answer: messages.append( {"role": "user", "content": f"候选人刚才的回答是:{last_answer}\n请点评并继续追问。"} ) else: messages.append({"role": "user", "content": "请开始面试第一题。"}) try: resp = client.chat.completions.create( model=os.getenv("INTERVIEW_MODEL", "gpt-4o-mini"), messages=messages, temperature=0.7, ) return resp.choices[0].message.content except Exception as e: return f"[调用模型失败] {e}"这里要注意,gpt-4o-mini只是示例模型名,实际请替换为你账号可用的模型名。
5.5 代码评测模块
代码评测模块把候选人提交的代码保存为临时文件,并使用子进程执行,然后对标准输入、输出、超时做统一处理。
# 文件路径:ai_interview_demo/code_runner.py import os import subprocess import tempfile def run_python_code(code: str, stdin_data: str = "") -> dict: """在子进程中运行一段 Python 代码。 安全提醒:该函数只适合本地学习和演示。 生产环境必须在 Docker 等沙箱中执行,绝不能直接运行不可信代码。 """ with tempfile.NamedTemporaryFile("w", suffix=".py", delete=False, encoding="utf-8") as f: f.write(code) code_path = f.name try: proc = subprocess.run( ["python3", code_path], input=stdin_data, capture_output=True, text=True, timeout=10, ) return { "returncode": proc.returncode, "stdout": proc.stdout, "stderr": proc.stderr, } except subprocess.TimeoutExpired: return {"returncode": -1, "stdout": "", "stderr": "执行超时"} finally: os.unlink(code_path) def check_with_test_cases(code: str, test_cases: list) -> tuple: """依次运行多个测试用例,对比期望输出与程序实际输出。""" for idx, case in enumerate(test_cases, 1): result = run_python_code(code, stdin_data=case["input"]) stderr = result.get("stderr", "").strip() stdout = result.get("stdout", "").strip() expected = case["expected"].strip() if stderr: return False, f"第 {idx} 个用例执行报错:{stderr}" if stdout != expected: return False, f"第 {idx} 个用例结果不符:期望 {expected},实际 {stdout}" return True, "全部测试用例通过"5.6 主流程
主流程把语音模块、题目生成模块和代码评测模块串起来:
# 文件路径:ai_interview_demo/main.py from interviewer import generate_question from code_runner import check_with_test_cases from voice_io import speak, listen def coding_phase() -> None: """在线编程环节:候选人把代码写入 solution.py,系统自动评测。""" print("\n下面进入在线编程环节。") print("题目:请编写一个 Python 程序,读取一行两个整数,输出它们的和。") print("请把代码写到当前目录的 solution.py 中,写完后按回车继续。") input("按回车继续 ...") try: with open("solution.py", "r", encoding="utf-8") as f: code = f.read() except FileNotFoundError: print("没有找到 solution.py,请先创建该文件后再试。") return test_cases = [ {"input": "1 2\n", "expected": "3"}, {"input": "10 -5\n", "expected": "5"}, {"input": "0 0\n", "expected": "0"}, ] passed, msg = check_with_test_cases(code, test_cases) print("评测结果:", msg) if passed: speak("你的代码通过了全部测试用例,很好。") else: speak("部分测试用例没有通过,请再检查一下边界情况。") def main() -> None: job_role = "Python 后端开发" answer_mode = input("请选择回答方式:1=语音,2=文字,默认语音:").strip() or "1" print(f"AI 面试官已就绪,岗位方向:{job_role}") speak("你好,欢迎参加今天的面试,我们开始吧。") first = generate_question(job_role) print(f"面试官:{first}") speak(first) for _ in range(3): if answer_mode == "2": answer = input("你的回答(输入 exit 结束):").strip() else: answer = listen() if not answer: print("没有获取到有效回答,再试一次。") continue if answer in ("exit", "退出"): break print(f"候选人:{answer}") if "编程题" in answer or "在线编程" in answer: coding_phase() break next_q = generate_question(job_role, last_answer=answer) print(f"面试官:{next_q}") speak(next_q) if __name__ == "__main__": main()5.7 运行与验证
在没有配置大模型 API Key 的情况下,运行效果如下:
python main.py程序会先让你选择回答方式,然后从内置题库开始提问。输入“我想做在线编程题”即可进入代码评测环节。
如果配置了OPENAI_API_KEY环境变量,AI 会根据你的回答动态生成追问,面试体验会更接近真实场景。
这个 Demo 的代码量不大,但它已经包含了一个 AI 面试官系统的核心骨架:语音交互、动态出题、多轮追问、代码运行评测。后续你可以继续扩展评分模块、会话记录存储和更丰富的题库。
6. 常见问题与排查思路
在实际使用这类系统时,不同环节都可能出问题。这里整理了一份高频问题清单:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 麦克风没有声音 | 浏览器或系统权限未开启 | 检查系统麦克风权限,在浏览器设置中允许访问麦克风 |
| 语音识别成功率低 | 环境噪声大、口音较重、识别服务能力弱 | 换安静环境,更换为云厂商 ASR 服务,增加重试机制 |
| pyttsx3 在 Linux 上没有声音 | 缺少音频后端 | 安装 espeak 或换用其他 TTS 服务 |
| 模型总是直接给出答案 | system prompt 缺少角色约束 | 在 prompt 中明确“不要替候选人回答问题” |
| 代码评测结果不稳定 | 测试用例覆盖不足,或有隐藏的输入输出格式问题 | 补充边界用例,统一输入输出格式,检查程序退出码 |
| 候选人代码包含恶意操作 | 直接在主进程执行了不可信代码 | 生产环境必须使用沙箱、容器或云函数隔离执行 |
排查时建议按“先设备、再服务、后逻辑”的顺序:
- 先确认麦克风和扬声器是否正常。
- 再检查 ASR/TTS 服务是否可用。
- 最后检查模型返回的文本和代码执行结果是否符合预期。
7. 工程化与安全最佳实践
如果你要把 Demo 扩展成一个真正可用的系统,下面几个点必须重点关注。
7.1 代码执行必须沙箱化
这是整个系统里安全风险最高的环节。直接在宿主机上执行候选人提交的代码,一旦遇到如下代码,风险会非常大:
import os os.system("rm -rf /home/your_user/important_dir")所以生产环境至少要采取这些措施:
- 使用 Docker 容器,限制 CPU、内存和运行时长。
- 关闭容器网络,禁止代码访问外部网络。
- 使用只读文件系统,避免写入宿主目录。
- 如果条件允许,使用 gVisor、Firecracker 等更强隔离的运行时。
最小权限原则在这里必须严格执行。
7.2 Prompt 与评测设计
面试官的人设约束要放在系统提示词里,并且要持续维护上下文。每次调用模型时,建议把之前的问答记录一并传入,保证追问逻辑连贯。
评测不能只依赖大模型“主观打分”。至少要保证:
- 代码题有确定性的测试用例。
- 用例覆盖正常输入、边界输入和异常输入。
- 评分逻辑包含“代码是否可运行”“测试是否通过”“回答是否完整”三个维度。
7.3 数据与隐私保护
面试过程中会产生大量敏感数据,包括:
- 候选人姓名、联系方式、简历内容。
- 面试音频和语音转写文本。
- 候选人编写的代码。
这些数据在存储和传输时都要加密,访问权限要按角色严格控制。面试录音在保存前要明确告知候选人,并设置合理的保留期限。如果企业有合规要求,优先选择私有化部署,而不是把数据传到外部服务。
7.4 监控与审计
线上系统需要记录完整的会话日志:
- 每次面试的开始时间和结束时间。
- 每个问题的提问时间、回答耗时。
- 代码提交记录和运行结果。
- 模型的调用次数和 Token 消耗。
这些日志一方面用于排查线上问题,另一方面也是后续优化提示词和评测规则的依据。
8. 学习路线与后续方向
如果你对 AI 面试官这个方向感兴趣,可以按下面这条路线继续深入:
- 先把本文的 Demo 跑通,理解语音、对话、代码评测这三条链路如何串联。
- 深入学习 ASR 和 TTS 的接入方式,重点看时延和准确率。
- 研究大模型 prompt 工程,尤其是多轮对话中的角色一致性。
- 学习 Docker 沙箱与代码评测系统的设计,包括资源限制和网络隔离。
- 关注 AI Agent 开发框架,了解如何把工具调用、状态管理、外部服务集成到一起。
这类产品本质上是一个典型的 AI Agent 应用:有输入感知、有推理决策、有工具调用、有结果反馈。即使你不打算做面试系统,这套“语音 + 多轮对话 + 代码执行 + 自动评测”的架构,也可以迁移到在线教育、编程训练、客服质检等场景。
动手跑一遍上面的 Demo,再去思考如何替换成更强的模型、更稳的语音服务和更安全的代码沙箱,你会对这个方向有更实际的理解。