跳到主内容
快讯直播
AI智模界
教程

用 AI 搭一套技术面试模拟器:从出题到复盘

跑完这篇教程,你会得到一个能直接用的面试模拟器:把目标岗位的 JD 和自己的简历丢进去,它会按「自我介绍 → 项目追问 → 编码题 → 场景设计 → 反问收尾」的顺序一轮轮提问,答完自动给出带证据的评分和一份 Markdown 复盘报告。全程在你自己的终端里跑,代码不到两百行。

和市面上现成的模拟面试产品相比,这套东西的价值在于可改:你可以换提示词、加自己的题库、把评分维度改成目标岗位真正在意的东西,也能把每次的薄弱点积累成错题本,面试前一天拿来热身。

前置条件清单

  • 一台能联网的电脑,Windows / macOS / Linux 都可以
  • Python 3.10 或更高版本(安装方式与当前版本号以 Python 官方文档为准)
  • 一个支持 Chat Completions 风格接口的模型服务,拿到三样东西:接口地址(Base URL)、API Key、模型名
  • 终端基本操作:会 cd、会设置环境变量
  • 心里有一份目标岗位的 JD 文本,和你自己简历的要点

模型选择上,面试官角色适合用表达自然、追问能力强的模型;评分环节适合用更稳、更听话的模型。两者可以不是同一个,后面会讲怎么分开配置。具体哪个模型便宜好用、上下文多长,以各家官方页面当前的信息为准,这里不做推荐。

步骤一:搭好目录,把密钥关进环境变量

先建目录:

```bash

mkdir -p ai-interview/prompts ai-interview/sessions

cd ai-interview

python -m venv .venv

source .venv/bin/activate # Windows 用 .venv\Scripts\activate

pip install openai

```

openai 这个包是通用客户端,只要服务商兼容这套接口就能用。装哪个版本、要不要额外依赖,以官方文档当前版本为准。

接着建一个 .env.example,把配置写成环境变量,密钥永远不要写进代码:

```bash

.env.example —— 复制成 .env 后填入自己的值

export LLM_BASE_URL="https://你的服务商接口地址/v1"

export LLM_API_KEY="你的密钥"

export LLM_MODEL="服务商文档里的模型名"

```

```bash

cp .env.example .env

编辑 .env 填好三行,然后

source .env

```

顺手加一个 .gitignore,写两行 .env.venv/,避免哪天手滑把密钥推到公开仓库。

配置读进代码里:

```python

config.py

import os

BASE_URL = os.environ.get("LLM_BASE_URL", "")

API_KEY = os.environ.get("LLM_API_KEY", "")

MODEL = os.environ.get("LLM_MODEL", "")

TIMEOUT = 60

if not (BASE_URL and API_KEY and MODEL):

raise SystemExit("请先 source .env,确认三个环境变量都已设置")

```

步骤二:封装一个带重试的调用函数

网络抖动、限流、偶发超时都会打断面试,所以调用层要自带重试。把它单独放一个文件,后面所有环节都复用它:

```python

llm.py

import time

from openai import OpenAI

import config

client = OpenAI(base_url=config.BASE_URL, api_key=config.API_KEY, timeout=config.TIMEOUT)

def chat(messages, temperature=0.7, retries=3):

"""统一的模型调用入口,失败时退避重试。"""

last_err = None

for i in range(retries):

try:

resp = client.chat.completions.create(

model=config.MODEL,

messages=messages,

temperature=temperature,

)

return resp.choices[0].message.content

except Exception as e: # 限流、超时、网络错误都在这里兜住

last_err = e

time.sleep(2 ** i) # 1s, 2s, 4s 退避

raise RuntimeError(f"模型调用连续失败:{last_err}")

```

评分环节建议把 temperature 设成 0 或接近 0,让输出稳定;面试官环节设 0.5~0.8,让追问更自然。

步骤三:写一份「面试官规则」,这是整套东西的核心

代码只是壳,提示词才是面试官本人。新建 prompts/interviewer.txt,把规则写死:

```text

你是一名有十年经验的技术面试官,正在面试一名后端 / 算法方向的候选人。

你必须遵守:

1. 每次回复只包含一个问题,控制在 80 字以内,不要一次抛出多个问题。

2. 面试进行中不要给出答案、不要点评、不要说「很好」「不错」这类空话。

3. 候选人回答含糊时——没有具体数字、没讲技术取舍、没提失败经历——先用一句话点出含糊之处,再追问一个更具体的问题。

4. 同一个话题的追问不超过两轮,之后换话题,避免钻牛角尖。

5. 出编码题时要给出可验证的题目描述和两个边界用例,题目里不要暗示解法。

6. 涉及候选人的项目经历时,优先问「你具体做了什么」「数据是多少」「如果重来会怎么改」。

7. 全程使用中文,语气专业、平实,像真实面试,不要过度鼓励。

```

然后在代码里把 JD 和简历拼到规则后面:

```python

interviewer.py(片段)

import os

from datetime import datetime

from llm import chat

def build_system(jd: str, resume: str) -> str:

rules = open("prompts/interviewer.txt", encoding="utf-8").read()

return (

rules

+ "\n\n【岗位描述】\n" + jd

+ "\n\n【候选人简历要点】\n" + resume

)

```

读文件时务必显式写 encoding="utf-8",Windows 上默认编码不是 UTF-8,中文会乱码。

步骤四:用一个「阶段机」控制面试节奏

只靠模型自由发挥,它很容易在第一个话题上聊十分钟。解决办法是把整场面试切成固定轮次的阶段,到点就注入一句系统提示推动换场:

```python

stages.py

STAGES = [

("开场与自我介绍", 2),

("项目与基础概念追问", 5),

("编码题", 3),

("场景与系统设计", 3),

("候选人反问与收尾", 1),

]

def stage_of(turn_index: int) -> str:

acc = 0

for name, n in STAGES:

acc += n

if turn_index < acc:

return name

return "收尾"

def stage_hint(name: str) -> dict:

return {"role": "system", "content": f"现在进入「{name}」环节。"}

```

轮次数字按你的时间和目标调整:如果只有十五分钟热身,把编码题和场景设计各砍到一轮。

步骤五:主循环,顺便把每轮问答落盘

```python

main.py

import os

from datetime import datetime

from llm import chat

from interviewer import build_system

from stages import stage_of, stage_hint

def save_markdown(path, turns):

lines = [f"# 面试记录 {path}\n"]

for i, t in enumerate(turns, 1):

lines.append(f"## 第 {i} 轮 · {t['stage']}\n")

lines.append(f"面试官: {t['q']}\n")

lines.append(f"我: {t['a']}\n")

with open(path, "w", encoding="utf-8") as f:

f.write("\n".join(lines))

def run_session(jd, resume, max_turns=14):

messages = [

{"role": "system", "content": build_system(jd, resume)},

{"role": "user", "content": "开始面试。"},

]

turns, current_stage = [], None

os.makedirs("sessions", exist_ok=True)

path = f"sessions/{datetime.now():%Y%m%d-%H%M%S}.md"

for i in range(max_turns):

stage = stage_of(i)

if stage != current_stage:

current_stage = stage

messages.append(stage_hint(stage))

print(f"\n===== 环节:{stage} =====")

question = chat(messages, temperature=0.6)

print("\n面试官:" + question)

answer = input("\n我(输入 exit 结束):").strip()

if answer.lower() in {"exit", "quit", "退出"}:

break

messages.append({"role": "assistant", "content": question})

messages.append({"role": "user", "content": answer})

turns.append({"stage": stage, "q": question, "a": answer})

save_markdown(path, turns) # 每轮保存,中途关掉也不丢

return turns, path

if __name__ == "__main__":

jd = open("jd.txt", encoding="utf-8").read()

resume = open("resume.txt", encoding="utf-8").read()

turns, path = run_session(jd, resume)

print(f"\n面试结束,记录已保存到 {path}")

```

注意角色对应关系:AI 说的每句话都记成 assistant,你说的记成 user。这一点搞反,模型会开始替你答题——这是新手最常见的翻车方式。

步骤六:编码题改成「贴代码 + 单独点评」

面试过程中不要点评,但编码题结束后建议单独发一次调用做代码评审,用低温度保证稳定:

```python

review.py

from llm import chat

CODE_REVIEW_PROMPT = """你是一名资深工程师,正在评审候选人现场写的代码。

请按顺序输出四段,每段不超过 120 字:

1. 正确性:给出一个能推翻它或验证它的具体用例;

2. 复杂度:时间与空间,写出推导过程;

3. 工程习惯:命名、边界处理、异常处理中需要改的一点;

4. 一条最值得改的具体建议。

除非候选人要求,不要重写完整代码。"""

def review_code(question: str, code: str) -> str:

messages = [

{"role": "system", "content": CODE_REVIEW_PROMPT},

{"role": "user", "content": f"【题目】\n{question}\n\n【候选人代码】\n{code}"},

]

return chat(messages, temperature=0.1)

```

如果你习惯在本地 IDE 写题,就把代码粘贴进终端时用三反引号包起来,模型解析代码块更稳。

步骤七:评分与复盘报告,要求「说出证据」

评分最大的毛病是模型给谁都打 7 分。破解办法只有一个:强制它引用你的原话作为证据,没有证据就不许给分。

```python

report.py

import json

from llm import chat

RUBRIC = {

"技术深度": "能否讲到实现细节、边界条件与取舍理由",

"表达结构": "结论是否先行,层次是否清楚",

"项目真实性": "数字、职责边界、失败经验是否自洽",

"编码能力": "正确性、复杂度分析、边界处理",

"主动沟通": "是否主动澄清需求,反问质量如何",

}

def build_prompt(turns):

transcript = "\n\n".join(f"问:{t['q']}\n答:{t['a']}" for t in turns)

dims = "\n".join(f"- {k}:{v}" for k, v in RUBRIC.items())

return (

"你是面试评估官。请根据面试记录,对下列维度各打 0-10 的整数分。\n"

"硬性要求:每个分数必须附上一条引用候选人原话的证据;找不到证据就写「无证据」,该维度不超过 5 分。\n"

"只输出 JSON,不要任何多余文字,格式:\n"

'{"scores":[{"dimension":"","score":0,"evidence":"","suggestion":""}],'

'"weakest":"","summary":""}\n\n'

f"维度说明:\n{dims}\n\n面试记录:\n{transcript}"

)

def parse_json(raw: str) -> dict:

text = raw.strip()

if text.startswith("```"):

text = text.split("```")[1]

if text.startswith("json"):

text = text[4:]

return json.loads(text)

def make_report(turns):

raw = chat([{"role": "user", "content": build_prompt(turns)}], temperature=0)

data = parse_json(raw)

lines = ["# 复盘报告\n"]

for s in data["scores"]:

lines.append(f"## {s['dimension']}:{s['score']}/10")

lines.append(f"- 证据:{s['evidence']}")

lines.append(f"- 建议:{s['suggestion']}\n")

lines.append("## 最弱项\n" + data["weakest"])

lines.append("\n## 总评\n" + data["summary"])

return "\n".join(lines)

```

如果服务商支持结构化输出(例如 JSON Schema 强约束),优先用它,能省掉解析兜底。支持情况以官方文档当前版本为准。

步骤八:把薄弱点沉淀成错题本

复盘报告生成后,追加一行到 weak_points.md:日期、岗位、最弱维度、那条证据。下一场面试开始时,把最近三次的错题本内容拼进 system 提示:

```python

def build_system(jd, resume, weak_points=""):

rules = open("prompts/interviewer.txt", encoding="utf-8").read()

base = rules + "\n\n【岗位描述】\n" + jd + "\n\n【候选人简历要点】\n" + resume

if weak_points:

base += (

"\n\n【历史薄弱点(本次请重点考察,但不要直接告诉候选人)】\n"

+ weak_points

)

return base

```

跑过三五场之后,你会明显感觉到它开始往你不熟的地方钻,这比随机问一堆问题有用得多。

常见坑与排错

模型一味夸人。 在面试官规则里明确禁止过程点评,并加一句「如果候选人回答没有具体信息,直接指出并追问」。规则越具体,跑偏越少。

模型自己把答案说了。 通常是角色搞反了,或者提示词里出现了「请给出参考答案」。检查消息里的 assistant / user 归属;参考答案另存一个文件,面试过程中不要读。

追问变复读。 模型会反复问同一个角度。办法是限制追问轮数(规则第 4 条),并在阶段切换时注入新环节提示。

上下文越来越长、越来越慢。 超过一定轮次后,把早期问答压缩成一段摘要,只保留最近几轮全文。具体留多少轮,取决于你用的模型上下文长度。

评分全是中位数。 强制引用原话证据是最有效的约束;另外可以要求它输出「最弱的一项」并说明理由,逼它做区分。

报错就中断。 重试逻辑之外,每轮即时落盘。哪怕进程被杀,前面答过的内容还在 sessions/ 里。

中文乱码。 所有 open() 都加 encoding="utf-8";Windows 终端可以先执行 chcp 65001

密钥泄露。 只用环境变量,.env.gitignore。如果不小心提交过一次,立刻去服务商后台吊销重建。

终端粘贴长代码出错。 把代码写进 .py 文件,然后让脚本读文件内容,比直接在终端粘贴几百行更稳。

下一步建议

跑通最小版本后,可以按这几个方向扩展:

1. 加语音输入。 接一个语音转文字模型,把 input() 换成录音识别,训练口头表达能力。这类接口的参数与调用方式以各家官方文档为准。

2. 多模型交叉评审。 用两个不同模型分别评分,分数差异大的维度往往就是最值得复盘的地方。

3. 按 JD 批量出题。 把目标公司的岗位描述批量喂进去,先离线生成一份专属题库,再让人工挑出好题固化进提示词。

4. 加「澄清需求」环节。 在编码题开头故意给一个模糊的需求,看你是否会主动问清输入范围、数据规模、性能要求——真实面试里这一项经常决定评价高低。

5. 固定一套热身流程。 面试前用 15 分钟跑一轮:两分钟自我介绍、一道编码题、一次反问练习,然后只看复盘报告的最弱项,不做多余复习。

真正的提升不来自多跑几十场,而来自每次跑完都认真读一遍那份带证据的复盘报告,把最弱的那一条改掉,然后下一场专门去盯它。

AI 生成本文由 AI 基于公开信息自动生成,仅供参考。