这是一个面向“智能招聘助手 MVP”笔试题的端到端 Demo,选择场景 B:AI 模拟面试官 Agent。
系统目标不是做一个完整 ATS,而是跑通一条核心闭环:把非结构化的 JD 和简历转成稳定结构化信息,再生成面试计划,进行多轮动态追问,最后输出面试评估报告和可复用的候选人画像。
视频展示了从上传 JD 和简历、候选人自我介绍、生成正式面试计划、多轮追问,到最终输出面试评估报告的完整流程。
- 上传简历 PDF / Word,粘贴 JD 文本。
- 使用 LLM 将 JD 和简历结构化,抽取岗位要求、业务场景、候选人技能、项目、实习/工作经历等。
- 基于结构化 JD 和简历生成面试前匹配分析,识别匹配点、能力缺口和待验证假设。
- 第一轮先让候选人做自我介绍,随后结合 JD、简历、Matcher 和自我介绍生成正式面试计划。
- 面试轮次由正式 plan 中的考察方向数量决定,不固定 8 轮,也不设置固定总轮次。
- 面试过程采用文字交互:面试官展示问题,候选人输入回答。
- 每一轮回答后由 Evaluation Agent 做观察,判断是否需要追问。
- 每个“大方向”结束后,由 Direction Scoring Agent 对该方向下的多轮问答统一评分和写评语。
- 面试结束后生成面向招聘负责人阅读的 Markdown 报告。
- 使用 SQLite 单表保存最近一次面试记忆,包括问答、方向评分、最终候选人画像等。
- 前端交互:Streamlit
- LLM 接入:OpenAI SDK 兼容接口,可接 OpenAI、Qwen、Kimi、GLM 等兼容 Chat Completions 的模型服务
- 文档解析:PyMuPDF、python-docx
- 结构化建模:Pydantic
- 题库检索:SQLite FTS5 + BM25
- 记忆持久化:SQLite 单表 + JSON 字段
- 配置管理:python-dotenv +
.env
JD 和简历不会直接丢给面试 Agent。系统先通过 src/structurer.py 把非结构化文本变成 JSON。
JD 结构化字段包括:
job_title:岗位名称business_context:业务背景responsibilities:核心职责required_skills:必备能力preferred_skills:加分项core_capabilities:核心能力画像
简历结构化字段包括:
candidate_name:候选人姓名skills:技能education:教育经历internships:实习/工作经历projects:项目经历papers:论文competitions:竞赛/奖项
结构化层只抽取事实,不提前生成风险判断。风险点由 Matcher、Evaluator 和 Direction Scoring Agent 在后续阶段基于证据逐步产生。
src/matcher.py 负责把 JD 要求和简历事实对齐,输出面试前的匹配分析。
它不做最终录用判断,也不输出分数,主要产出:
- 候选人与岗位匹配的能力点
- 简历中没有充分体现的能力缺口
- 面试中需要验证的假设
- 值得深挖的项目、实习或工作经历
- 建议优先考察的方向
这样设计的原因是:Matcher 的职责是帮助面试开始得更准,而不是在没有问答证据时过早给候选人下结论。
系统开始时只生成一个自我介绍临时计划。候选人完成自我介绍后,src/planner.py 再根据 JD、简历、Matcher 结果和自我介绍内容生成正式面试计划。
正式计划不是固定 8 轮,也不是提前写死每一道问题,而是围绕候选人的项目、实习或工作经历生成稳定的大方向:
experience_probe_plan:围绕项目、实习、工作经历生成考察方向。
例如一个项目可能生成 2-3 个方向:
- 个人贡献与真实性
- 方法链路与执行过程
- 结果指标与复盘能力
后续面试过程中,大方向保持稳定,小问题根据候选人具体经历、上一轮回答和题库检索结果动态生成。
轮次控制采用 plan-driven 策略:experience_probe_plan 中有多少大方向,就推进多少计划方向;如果某个方向回答不充分,Flow Controller 才会追加追问。项目问完后直接结束并生成报告,不再单独进入业务场景题环节。
本项目的面试 Agent 不是“问一题、等一题、随机追问”的聊天机器人,而是有一个主动推进的面试控制层。
核心机制:
自我介绍
-> Planner 生成项目/实习考察地图
-> Probe Planner 选择当前大方向
-> Question Composer 生成当前小问题
-> Candidate Answer
-> Evaluator 判断回答是否足够
-> Flow Controller 决定继续深挖还是切换方向
-> Direction Scoring Agent 记录该方向评分
-> 下一个 plan 方向
这个设计的亮点在于:
- 面试不是固定题单,而是围绕候选人真实经历生成考察路径。
- 候选人自我介绍会影响后续 plan,系统会优先追问候选人主动强调的项目和贡献。
- 大方向稳定,小问题动态生成,避免每轮问题都漂移。
- Flow Controller 负责推进节奏,Memory 只记录证据,职责清晰。
- 每个大方向结束后都会沉淀评分和评语,成为后续报告和候选人画像的证据来源。
- 项目/实习方向问完后直接结束出报告,不再强行进入无关业务场景题。
主动式推进解决的是面试中的核心问题:系统知道“当前为什么问、问到什么程度可以停、下一步该换到哪里”。这比简单多轮聊天更接近真实面试官的工作方式。
src/question_retriever.py 使用 SQLite FTS5 + BM25 从本地题库取 Top 10 相关问题。
设计原则:
- 检索题库是为了提供参考,不是照抄题库问题。
- 当前考察大方向不变。
src/question_composer.py会结合候选人的具体项目/经历、当前方向、上一轮回答和题库参考,生成一个更贴合上下文的问题。
这比纯 LLM 生成更稳定,也比固定题单更适合多轮追问。
系统把面试过程拆成三个角色:
- Interviewer Agent:负责生成当前问题。
- Evaluation Agent:负责观察单轮回答,判断是否需要追问,不打分。
- Direction Scoring Agent:一个大方向结束后,汇总该方向下的多轮问答,给 1-4 档评分和评语。
这样避免每个小问题都打分导致噪声过大。小问题只是采证工具,真正评分对象是 Planner 生成的大方向。
方向评分四档:
1 = 未验证:回答空泛、回避,或明确不是本人负责。
2 = 部分验证:有局部经验,但缺少关键细节或证据。
3 = 基本验证:有真实实践和主要细节,但指标、边界或复盘不够完整。
4 = 充分验证:回答具体,有个人贡献、方法细节、指标证据和复盘思考。
src/flow_controller.py 根据 Evaluation Agent 的结构化观察和当前 Memory 状态决定下一步:
- 继续围绕当前方向追问
- 当前方向结束,进入 Direction Scoring Agent
- 切换到下一个计划方向
- 面试结束,生成报告
Memory 只负责记录状态和证据,不负责做流程决策。这样职责更清楚,也方便后续调参。
系统当前使用一张 SQLite 表 interview_memory 保存最近一次面试。
核心保存内容:
- JD 原文、简历原文
- JD / 简历结构化 JSON
- Matcher 结果
- 面试计划
- 多轮问答记录
- 每个大方向的评分与评语
final_profile_json- 最终展示报告
report_json
其中 final_profile_json 是长期候选人画像,用于后续面试或后续系统读取。最终 Markdown 报告是给人看的展示材料,不作为长期画像本体。
flowchart TD
A["输入<br/>JD 文本 + 简历 PDF/Word"] --> B["文档解析<br/>PDF/Word 转文本"]
B --> C["结构化抽取<br/>JD JSON + Resume JSON"]
C --> D["匹配分析 Matcher<br/>匹配点 / 缺口 / 待验证假设"]
D --> E["面试前准备<br/>Pre-interview Brief + 面试官角色"]
E --> F["第一轮自我介绍<br/>固定简短开场问题"]
F --> G["Planner<br/>结合 JD / 简历 / Matcher / 自我介绍<br/>生成正式考察地图"]
G --> H["多轮文字面试<br/>按 plan 大方向动态提问"]
H --> I["Direction Scoring Agent<br/>每个大方向结束后统一评分"]
I --> J["面试记忆<br/>记录问答、证据、方向评分"]
J --> K["最终候选人画像<br/>final_profile_json"]
J --> L["面试评估报告<br/>Markdown readable_report"]
K --> M["SQLite interview_memory"]
L --> M
flowchart TD
A["正式考察计划<br/>experience_probe_plan"] --> B["Probe Planner<br/>选择当前项目大方向"]
B --> C["Question Retriever<br/>FTS5 + BM25 检索 Top 10"]
C --> D["Question Composer<br/>结合简历经历和题库参考生成问题"]
D --> E["候选人文字回答"]
E --> F["Evaluation Agent<br/>单轮观察,不打分"]
F --> G{"Flow Controller<br/>下一步决策"}
G -- "信息不足,需要追问" --> B
G -- "当前方向已问透" --> H["Direction Scoring Agent<br/>对该方向多轮问答统一评分"]
H --> I["Memory<br/>写入方向评分和证据"]
I --> B
G -- "plan 方向走完" --> J["Reporter<br/>生成最终报告"]
flowchart LR
UI["app.py<br/>Streamlit UI"] --> Loader["document_loader<br/>PDF/Word 解析"]
Loader --> Structurer["structurer<br/>JD/简历结构化"]
Structurer --> Matcher["matcher<br/>面试前匹配分析"]
Matcher --> Role["role_builder / profile<br/>角色与面试前简报"]
Role --> Planner["planner<br/>自我介绍后生成正式 plan"]
Planner --> Interviewer["interviewer<br/>选择方向 + 生成问题"]
Interviewer --> Retriever["question_retriever<br/>SQLite FTS5/BM25 Top 10"]
Retriever --> Composer["question_composer<br/>融合经历事实生成问题"]
Composer --> Evaluator["evaluator<br/>单轮观察"]
Evaluator --> Flow["flow_controller<br/>追问 / 切换 / 结束"]
Flow --> Scorer["direction_scorer<br/>大方向评分"]
Scorer --> Reporter["reporter / profile<br/>报告与最终画像"]
Reporter --> Storage["storage<br/>SQLite interview_memory"]
app.py:Streamlit 入口,负责页面状态和端到端编排。src/document_loader.py:解析 PDF / Word 简历。src/structurer.py:JD 和简历结构化抽取。src/matcher.py:JD-Resume 匹配分析。src/config_parser.py:解析用户自然语言面试要求。src/role_builder.py:生成面试官角色设定。src/planner.py:自我介绍后生成正式考察地图,并按 plan 大方向数量驱动面试推进。src/probe_planner.py:选择当前大方向,生成检索 query。src/question_retriever.py:SQLite FTS5 / BM25 题库检索。src/question_composer.py:生成上下文相关的具体问题。src/interviewer.py:串联方向选择、题库检索和问题生成。src/evaluator.py:单轮回答观察,不做评分。src/flow_controller.py:决定继续追问、切换方向或结束面试。src/direction_scorer.py:对大方向进行 1-4 档评分。src/memory.py:运行时面试记忆。src/profile.py:面试前简报和面试后候选人画像。src/reporter.py:生成最终可读面试报告。src/storage.py:SQLite 单表持久化。src/llm_client.py:LLM 兼容接口和 fallback 处理。
本项目没有用一个大 Prompt 处理所有事情,而是按职责拆成多个小 Prompt:
- Structurer Prompt:只抽取事实字段,避免提前判断。
- Matcher Prompt:做 JD-Resume 对齐,输出匹配点、缺口和待验证假设。
- Planner Prompt:生成稳定考察方向,不提前固定每一轮问题。
- Question Composer Prompt:在固定大方向下,结合简历细节和题库参考生成当前问题。
- Evaluator Prompt:只观察单轮回答,输出是否追问、证据、缺失点和风险。
- Direction Scorer Prompt:汇总一个大方向下的多轮问答后统一评分。
- Reporter Prompt:把结构化结果转成招聘负责人能读懂的 Markdown 报告。
稳定性处理:
- 要求 LLM 输出 JSON。
- 使用
extract_json从 Markdown 代码块或混杂文本中提取 JSON。 - 每个核心节点都有 fallback,未配置 API Key 时仍可演示主流程。
- 流程决策由
FlowController基于结构化信号完成,避免完全依赖 LLM 随机判断。 - Question Composer 会校验题库问题和当前经历是否相关,禁止把简历/JD/当前方向中没有的概念硬塞进问题。
Structurer 的核心约束:
只抽取 JD/简历原文中存在的信息,不要补充常识、推测条件或创造新经历。
结构化层只输出事实字段,不输出风险判断。
缺失字段使用空数组或 unknown。
Matcher 的核心约束:
不要输出匹配分,不要做最终录用判断。
将风险表述为“待验证假设”,例如个人贡献边界、项目真实性、指标口径。
结论必须服务于下一步面试应该验证什么。
Planner 的核心约束:
不要生成固定总轮次。
正式 plan 必须结合 JD、简历、Matcher 和候选人自我介绍。
experience_probe_plan 针对项目/实习生成 2-3 个大方向。
技能和业务场景不单独成环节,而是融入项目/实习深挖方向中验证。
Question Composer 的核心约束:
题库候选题只能作为参考角度,不能照搬。
如果题库问题中的概念没有出现在候选人经历、JD 或当前考察方向中,禁止写进最终问题。
最终问题必须是一段自然中文,不要把简历原文大段贴给候选人。
Direction Scorer 的核心约束:
不要给每个小问题打分。
只在一个大方向结束后,汇总该方向下的多轮问答,给 1-4 档验证等级。
评分必须包含证据、缺失点、评语和下一步建议。
如果直接把 JD 和简历丢给一个聊天模型,问题会很容易发散,且难以解释为什么问这个问题。
解决方案:
- 先用 Structurer 把 JD 和简历变成稳定 JSON。
- 再用 Matcher 输出匹配点、缺口和待验证假设。
- 自我介绍后再生成正式 plan,让计划吸收候选人主动强调的经历。
- 后续问题都绑定到 plan 的大方向。
如果每个动态小问题都打分,评分会受提问措辞影响,噪声较大。
解决方案:
- 小问题只负责采证。
- Evaluation Agent 只做单轮观察,不打分。
- Direction Scoring Agent 在一个大方向结束后汇总多轮问答,统一给 1-4 档评分。
普通聊天式面试容易出现两个问题:一是模型追问容易发散,二是系统不知道什么时候该结束当前话题。
解决方案:
- 用 Planner 生成稳定的大方向,作为面试推进地图。
- 用 Probe Planner 根据当前进度选择下一个方向。
- 用 Evaluator 输出结构化信号,例如
need_follow_up、direction_complete、signal_sufficient。 - 用 Flow Controller 统一决定继续深挖、切换方向或结束面试。
- 让 Direction Scoring Agent 在方向结束时写入评分和评语,形成可复用记忆。
这样系统具备了“主动面试推进能力”:不是候选人说到哪就跟到哪,而是围绕岗位目标和候选人经历持续收集证据。
python -m venv .venv
.\.venv\Scripts\activate
pip install -r requirements.txt
copy .env.example .env
streamlit run app.py.env 示例:
OPENAI_API_KEY=your_api_key
OPENAI_MODEL=gpt-4o-mini
OPENAI_BASE_URL=
OPENAI_TIMEOUT=180
OPENAI_MAX_RETRIES=0
OCR_MODEL=PaddlePaddle/PaddleOCR-VL-1.5
OCR_TIMEOUT=180
BROWSER_TTS_ENABLED=false
XINGYUN_AVATAR_ENABLED=false如果不配置 OPENAI_API_KEY,系统会进入本地 fallback 模式,可以演示主流程,但真实提取和回答质量会低于接入模型后的效果。
- 启动 Streamlit。
- 上传候选人简历 PDF / Word。
- 粘贴 JD 文本。
- 可选填写面试官风格要求。
- 点击开始面试。
- 第一轮候选人完成自我介绍。
- 系统结合自我介绍生成正式面试 plan。
- 候选人用文字回答后续问题。
- 系统根据回答质量选择继续追问或切换方向。
- 面试结束后展示最终 Markdown 报告。
建议演示视频覆盖:
- JD + 简历输入
- 结构化结果
- Matcher 分析
- 自我介绍后生成的 Planner 考察方向
- 至少两轮动态追问
- 一个大方向评分
- 最终可读报告
当前 MVP 默认保存最近一次 latest session,适合笔试 Demo。
后续可以扩展:
- 支持多候选人、多岗位、多轮面试 session。
- 题库检索升级为 BM25 + Embedding Hybrid Retrieval + Reranker。
- 将方向评分 rubric 做成可配置能力模型。
- 把
final_profile_json接入下一轮面试,形成跨轮候选人画像。 - 接入语音/TTS/数字人作为展示层,但不改变核心面试链路。