1. 为什么我要做这个 AI 面试官 Skill
先说说我做这个东西的起因。过去大半年,我前后面了大概三十多个候选人,岗位从 RAG 工程师到 Agent 开发都有。面到后面我发现一个很尴尬的事:简历上写着“精通 LangChain、熟悉 LangGraph、做过 RAG 项目”的人一抓一大把,但真正能扛住追问的没几个。你问他 RAG 检索命中率怎么优化,他背得出“混合检索、重排序、查询改写”这几个词,但你接着问“你的 chunk size 怎么定的、为什么是这个值、换一个会怎样”,基本就卡壳了。LangGraph 更明显,很多人能画出 StateGraph 的图,但一问“checkpointer 在什么场景下必须开、中断恢复怎么保证幂等”,就开始含糊。
我自己也是从“背八股”过来的,很清楚问题出在哪:市面上讲 RAG、Agent 的教程和面试题,绝大多数停留在概念层,缺少那种一层一层往下挖的追问。而真实面试里,面试官的价值恰恰在于追问——你答完一个点,他顺着你的回答再往下扎一刀,看你是真做过还是只背过。这种追问能力,靠刷题库是练不出来的。
所以我干脆动手做了一个AI 面试官 Skill,核心目标就一个:专练 RAG、Agent、LangChain、LangGraph 这四个方向,并且支持基于你项目经历的深度追问。它不是那种“你问我答”的固定题库,而是一个能根据你的回答动态生成下一轮问题的对话式训练工具。你可以把它理解成一个不知疲倦、专挑你知识盲区下手的陪练。
这个 Skill 适合谁?如果你正在准备 AI 应用开发岗的面试,尤其是 RAG 或 Agent 方向,它能帮你把“背过的知识”变成“扛得住追问的能力”。如果你已经在做相关项目,想系统梳理自己的技术盲区,它也能当自查工具用。哪怕你只是想入门 LangChain 和 LangGraph,通过被追问的方式学习,记忆深度也远超看文档。
下面我把整个 Skill 的设计思路、核心实现、追问机制、踩过的坑,以及怎么复现,完整拆一遍。内容比较长,但都是实操层面的东西,你可以直接抄作业。
2. Skill 的整体设计与核心思路拆解
2.1 为什么是 Skill 而不是一个普通脚本
这里要先解释一下“Skill”这个概念。在 AI 应用开发里,Skill 通常指一个封装好的、可被 Agent 调用的能力单元,它有自己的输入输出契约、有自己的内部逻辑,可以被编排进更大的工作流。我把它做成 Skill 而不是一个独立脚本,主要考虑三点。
第一,可复用。Skill 的接口是标准化的,我可以把它挂到不同的 Agent 框架下,今天用 LangGraph 编排,明天换个 Agent 框架,Skill 本身不用改。第二,可组合。面试官 Skill 可以和其他 Skill 组合,比如接一个“简历解析 Skill”自动读取候选人项目,再接一个“评分 Skill”输出结构化评估。第三,符合当前 Agent 开发的工程习惯。现在做 Agent 应用,大家都在往“主 Agent + 多个 Skill”的架构上走,把面试官做成 Skill,本身就是一次很好的工程实践。
提示:如果你只是想快速跑起来,把 Skill 写成一个带明确输入输出定义的函数就够了,不必一上来就搞复杂的插件协议。先跑通,再抽象。
2.2 四个技术方向的追问深度设计
RAG、Agent、LangChain、LangGraph 这四个方向,难度和追问方式完全不同,我在设计时给每个方向定了不同的追问策略。
RAG 方向,追问核心是“数据流每一环的决策依据”。从文档切分、向量化、索引、检索、重排到生成,每一环都能往下挖。比如你说用了混合检索,我就追问“BM25 和向量检索的分数怎么融合的、权重怎么定、有没有做归一化”。这类追问考的是你对检索链路的掌控程度。
Agent 方向,追问核心是“决策与容错”。Agent 的本质是让模型自主决定调用什么工具、按什么顺序调。所以我会追问“工具调用失败怎么处理、怎么防止死循环、多轮对话里上下文怎么管理、并发场景下状态怎么隔离”。这些问题在真实项目里全是坑。
LangChain 方向,追问核心是“抽象层的理解”。LangChain 封装了大量组件,很多人会用但不知道底层发生了什么。我会追问“MultiVectorRetriever 和普通 Retriever 的区别、什么场景下必须用、它的索引结构长什么样”。考的是你穿透抽象层的能力。
LangGraph 方向,追问核心是“状态机与持久化”。LangGraph 把 Agent 建模成图,节点、边、状态、检查点这些概念必须吃透。我会追问“条件边怎么设计、checkpointer 存什么、中断恢复时状态怎么对齐、子图怎么嵌套”。
2.3 追问机制的整体架构
整个 Skill 的追问机制,我拆成了三层。
第一层是主题路由。用户选定方向后,Skill 从该方向的知识图谱里选一个切入点作为首问。这个知识图谱是我手工整理的,每个方向大概 40 到 60 个知识点,按难度和依赖关系组织。
第二层是回答评估。用户回答后,Skill 会判断回答的完整度、准确度和深度,决定是继续深挖、换角度追问,还是跳到下一个知识点。这里我没有用复杂的评分模型,而是用了一套基于关键词覆盖和逻辑结构的启发式规则,后面会详细讲。
第三层是项目追问。这是最有价值的部分。用户可以把自己的项目描述贴进来,Skill 会从中提取技术点,然后针对每个技术点生成追问链。比如你写“用 LangGraph 做了一个多轮对话客服”,它就会追问“你的图有几个节点、状态里存了什么、怎么处理用户中途改需求”。
这三层不是串行的,而是动态交织的。回答评估的结果会影响主题路由,项目追问又会引入新的知识点。
2.4 技术选型背后的取舍
实现上我用了 Python + LangChain + LangGraph 这套组合。为什么不用更轻的方案?因为我要练的就是这套技术栈,用它们来实现本身就是最好的练习。而且 LangGraph 的状态管理和中断恢复能力,正好适合做多轮追问这种有状态对话。
模型层我用的是本地可跑的方案加云端 API 双通道。本地用 Ollama 跑一个小参数模型做快速响应和规则判断,复杂追问生成走云端 API。这样既控制了成本,又保证了追问质量。如果你完全本地化,用 Ollama 加一个 7B 到 14B 的模型也能跑,只是追问的锐度会下降一些。
存储层用 SQLite 存知识图谱和追问历史,向量检索用 FAISS 做本地索引。整个项目不依赖任何外部数据库服务,clone 下来就能跑。
3. 核心细节解析与实操要点
3.1 知识图谱怎么建才经得起追问
知识图谱是整个 Skill 的地基,建得好不好直接决定追问质量。我的做法是每个方向建一棵树,树的每个节点是一个知识点,节点上挂三类信息:核心问题、追问分支、评判要点。
以 RAG 方向为例,根节点是“RAG 整体链路”,往下分“文档处理”“索引构建”“检索策略”“生成优化”四个一级节点。“检索策略”下面再分“向量检索”“关键词检索”“混合检索”“重排序”。每个叶子节点上,我写了一个核心问题和三到五个追问分支。
举个具体的。叶子节点“混合检索”的核心问题是“你的混合检索怎么实现的”。追问分支包括:
- 追问1:BM25 和向量检索的分数怎么融合?是加权求和还是 RRF?
- 追问2:权重怎么定的?有没有做实验对比?
- 追问3:两个检索器的召回数量怎么设?为什么?
- 追问4:融合后有没有做去重?怎么去?
- 追问5:混合检索相比单路检索,在你的场景下提升多少?
评判要点我写的是:能说出 RRF 或加权融合的具体做法,能给出权重选择的依据,能说出召回数量对最终效果的影响。
注意:知识图谱不要贪大。我一开始想覆盖所有细节,结果建了两百多个节点,追问反而变得散乱。后来砍到每个方向 50 个节点左右,聚焦高频考点和真实项目里的关键决策点,效果反而更好。
3.2 回答评估的启发式规则设计
很多人以为评估回答必须用大模型打分,其实不一定。我用了一套启发式规则,成本低、响应快,效果也够用。
规则的核心是三个维度:关键词覆盖度、结构完整度、深度信号。
关键词覆盖度很好理解,每个知识点我预设了一组必答关键词。比如问“checkpointer 的作用”,必答关键词是“状态持久化”“中断恢复”“线程隔离”。用户回答里覆盖了几个,就对应不同的追问策略。
结构完整度看的是回答有没有分层。如果用户回答是一大段没有逻辑的话,说明他自己没想清楚,这时候应该换一个更基础的角度重新问,而不是继续深挖。
深度信号是最有意思的。我会检测回答里有没有出现“因为”“所以”“我试过”“对比下来”“踩过坑”这类词。出现这些词,说明用户有真实经验,可以往更深的工程细节追问。如果全是“一般来说”“通常”“应该是”这种模糊表述,说明是背的,这时候要往具体场景追问,逼他给出实例。
# 简化的评估逻辑示意 def evaluate_answer(answer, knowledge_point): keywords_hit = sum(1 for kw in knowledge_point.keywords if kw in answer) coverage = keywords_hit / len(knowledge_point.keywords) depth_signals = ["因为", "所以", "我试过", "对比", "踩坑", "实测"] depth_score = sum(1 for s in depth_signals if s in answer) if coverage > 0.7 and depth_score >= 2: return "deep_dive" # 继续深挖 elif coverage > 0.4: return "probe" # 换角度追问 else: return "fallback" # 退回基础问题这套规则我调了两周,现在判断准确率大概能到八成。剩下的两成靠大模型兜底,当规则判断置信度低时,才调用模型做二次判断。
3.3 项目追问链的生成逻辑
项目追问是这个 Skill 最核心的差异化能力。用户贴一段项目描述进来,Skill 要能自动提取技术点并生成追问链。
我的做法分三步。第一步是技术点抽取,用正则加关键词匹配,从项目描述里识别出涉及的技术栈和动作。比如“用 LangGraph 做多轮对话”会抽出LangGraph和多轮对话两个标签。第二步是追问链匹配,每个技术标签在知识图谱里对应一组预设追问链。第三步是上下文注入,把用户项目描述里的具体信息填进追问模板,让问题看起来是针对他项目的,而不是通用的。
举个例子。用户写:“我用 LangChain 的 MultiVectorRetriever 做了一个文档问答系统,支持按摘要检索。”
抽取出的技术点是MultiVectorRetriever和摘要检索。匹配到的追问链是:
- 你为什么选 MultiVectorRetriever 而不是普通向量检索?它解决了什么问题?
- 你的摘要向量和原文向量是存在同一个索引里还是分开的?
- 检索时命中的是摘要,返回给模型的是原文还是摘要?怎么映射的?
- 摘要生成用的什么模型?摘要质量怎么评估?
- 这套方案相比直接检索原文,召回率提升多少?有没有做 A/B 对比?
你看,这些问题全是针对他项目里真实用到的技术,不是泛泛而谈。候选人如果真做过,能答上来;如果只是简历上写了,立马露馅。
3.4 多轮对话的状态管理
多轮追问天然是有状态的,用户答到第几轮、当前在哪个知识点、历史回答的评估结果,这些都要存下来。我用 LangGraph 的 StateGraph 来管理。
状态结构大概是这样:
class InterviewState(TypedDict): direction: str # 当前方向 current_node: str # 当前知识点 history: list # 问答历史 eval_results: list # 每轮评估结果 project_context: str # 项目描述 follow_up_chain: list # 当前追问链 chain_index: int # 追问链进度图的结构是:入口节点做主题路由,然后进入追问节点,追问节点生成问题后进入等待节点,用户回答后进入评估节点,评估节点根据结果决定回到追问节点还是路由节点。整个图用 checkpointer 做持久化,这样即使中途退出,下次还能接着面。
提示:checkpointer 一定要开。我一开始图省事没开,结果用户刷新页面整个面试进度就丢了,体验极差。开了之后不仅进度能恢复,还能做“回看历史追问”这种功能。
4. 实操过程与核心环节实现
4.1 环境准备与依赖安装
先把环境搭起来。我用的是 Python 3.10,太新的版本有些库兼容性还没跟上。
# 创建虚拟环境 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 核心依赖 pip install langchain langchain-community langgraph pip install faiss-cpu # 向量检索,有 GPU 可以换 faiss-gpu pip install ollama # 本地模型调用 pip install sqlalchemy # 存知识图谱和历史如果你要用云端模型,再装对应的 SDK。本地模型的话,先装好 Ollama,然后拉一个模型下来:
ollama pull qwen2.5:7b选 7B 是因为它在追问生成上够用,而且消费级显卡就能跑。如果你机器好,上 14B 效果更稳。
4.2 知识图谱的构建与加载
知识图谱我用 YAML 文件维护,方便手改。每个方向一个文件,结构如下:
direction: RAG nodes: - id: rag_retrieval_hybrid name: 混合检索 core_question: 你的混合检索怎么实现的? keywords: [BM25, 向量检索, RRF, 加权融合, 召回] follow_ups: - 两个检索器的分数怎么融合? - 权重怎么定的?有没有实验依据? - 召回数量怎么设?为什么? criteria: 能说出融合方式,能给出权重依据,能说明召回数量影响加载的时候用 SQLAlchemy 存进 SQLite,同时把 keywords 和 criteria 做向量化,存进 FAISS 索引,方便后续做语义匹配。
import yaml from langchain_community.vectorstores import FAISS from langchain_community.embeddings import OllamaEmbeddings def load_knowledge_graph(path): with open(path) as f: data = yaml.safe_load(f) # 存 SQLite # 建 FAISS 索引 embeddings = OllamaEmbeddings(model="qwen2.5:7b") texts = [f"{n['name']}: {n['core_question']}" for n in data['nodes']] vectorstore = FAISS.from_texts(texts, embeddings) return data, vectorstore这里有个细节:向量化的时候我把知识点名称和核心问题拼在一起,而不是只向量化名称。因为名称太短,语义信息不够,检索时容易匹配错。拼上核心问题后,语义区分度明显提升。
4.3 追问节点的实现
追问节点是整个 Skill 的大脑。它的输入是当前状态,输出是下一个问题。
def follow_up_node(state: InterviewState): node = get_node(state["current_node"]) chain = state["follow_up_chain"] idx = state["chain_index"] if idx < len(chain): question = chain[idx] else: # 追问链走完,生成新的追问或换知识点 question = generate_next_question(state) return {"history": state["history"] + [question], "chain_index": idx + 1}generate_next_question是兜底逻辑,当预设追问链走完时,用大模型基于历史回答生成新问题。这里我加了一个约束:生成的问题必须包含用户之前回答里的至少一个具体技术词。这样能保证追问是连贯的,不会突然跳到无关话题。
4.4 评估节点的实现
评估节点负责判断用户回答的质量,决定下一步走向。
def evaluate_node(state: InterviewState): last_answer = state["history"][-1] node = get_node(state["current_node"]) result = evaluate_answer(last_answer, node) state["eval_results"].append(result) if result == "deep_dive": # 继续深挖当前追问链 return {"chain_index": state["chain_index"]} elif result == "probe": # 换角度,重新生成追问 new_q = generate_probe_question(state) return {"history": state["history"] + [new_q]} else: # 退回基础,换知识点 return {"current_node": route_to_easier_node(state)}这套逻辑跑下来,整个面试的节奏就比较自然了。答得好就往下扎,答得一般就换角度,答不上来就退回去问基础。不会出现一直问难题把人问死,或者一直问简单题没挑战的情况。
4.5 项目追问的接入
项目追问的接入点是在面试开始前。用户可以选择“通用面试”或“项目面试”。选项目面试时,会多一个输入框让用户贴项目描述。
def extract_tech_points(project_desc): # 关键词匹配 + 正则 tech_keywords = ["LangChain", "LangGraph", "RAG", "Agent", "MultiVectorRetriever", "checkpointer", "混合检索"] found = [kw for kw in tech_keywords if kw in project_desc] return found def build_follow_up_chain(tech_points, project_desc): chain = [] for tp in tech_points: templates = get_templates(tp) for t in templates: chain.append(t.format(project=project_desc[:50])) return chain这里有个经验:项目描述不要截断得太狠,但也不要全塞进模板。我一般取前 50 个字作为项目标识,让问题里带上项目特征,比如“你那个文档问答系统里,MultiVectorRetriever 的摘要向量是怎么存的”。这样用户一听就知道是针对他项目的,代入感强很多。
4.6 完整跑一遍的实测记录
我把整个流程跑了一遍,记录一下关键节点。
启动后,选择 RAG 方向,通用面试。首问是“你的 RAG 系统整体链路是怎么设计的”。我回答了一个包含文档切分、向量化、检索、生成的完整链路,提到了混合检索和重排序。
评估结果:关键词覆盖度高,有“因为”“对比”等深度信号,判定为 deep_dive。追问链推进到“混合检索的分数怎么融合”。我回答用了 RRF,并说了 RRF 的公式和 k 值取 60 的经验值。
评估结果:覆盖度高,深度信号强,继续 deep_dive。追问“k 值为什么取 60,换一个值会怎样”。这个问题我答得一般,只说了“经验值”,没有实验依据。评估判定为 probe,换角度追问“你有没有试过加权融合,和 RRF 比效果如何”。
这一轮我答得比较实,说了试过加权融合但调参麻烦,最后选了 RRF。评估判定为 deep_dive,继续追问“加权融合的权重你是怎么调的”。
整个流程下来,大概问了 12 轮,覆盖了 RAG 链路的多个关键决策点。体验上确实比刷题库累,因为每一轮都要真思考,但收获也大,好几个问题是我之前没认真想过的。
5. 常见问题与排查技巧实录
5.1 追问跑偏怎么办
最常见的问题是追问跑偏。比如用户回答里提了一个无关的技术词,大模型生成的追问就顺着那个词跑远了。我的解决办法是在生成追问时加一个约束:新问题必须和当前知识点有语义相似度阈值以上的关联。具体做法是把当前知识点的核心问题和生成的问题都向量化,算余弦相似度,低于 0.6 就重新生成。
提示:这个阈值不要设太高,0.6 到 0.7 之间比较合适。设太高会导致追问太死板,永远在同一个点上打转。
5.2 用户回答太短怎么处理
有些用户回答就一句话,比如“用了 RRF”。这种回答信息量太少,评估规则会判定为 fallback,退回基础问题。但有时候用户是真的懂,只是懒得展开。这时候我会加一个追问:“能具体说说 RRF 的实现细节吗?”逼他展开。如果展开后质量高,再继续深挖。
5.3 本地模型追问质量不够
用 Ollama 跑 7B 模型时,追问的锐度确实不如云端大模型。表现是问题比较泛,不够具体。我的优化办法是给本地模型加 few-shot 示例,在 prompt 里塞三到五个高质量的追问样例,让它模仿。加了之后质量提升明显,虽然还是比不上大模型,但日常练习够用了。
5.4 状态丢失与恢复
前面提过 checkpointer 必须开。但开了之后还有个坑:如果用户中途换了方向,旧的状态怎么处理。我的做法是每个方向一个独立的 thread_id,切换方向时新建 thread,旧 thread 保留但不再写入。这样用户切回来还能接着之前的进度。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
| 追问跑偏 | 生成时缺少知识点约束 | 加语义相似度阈值过滤 |
| 回答太短无法评估 | 用户回答信息量不足 | 加展开追问,逼用户补充 |
| 本地模型问题太泛 | 缺少示例引导 | prompt 里加 few-shot 样例 |
| 状态丢失 | checkpointer 未开或 thread 混乱 | 开启 checkpointer,按方向隔离 thread |
| 追问链走完没题了 | 预设链太短 | 加兜底生成逻辑,基于历史动态出题 |
| 评估判断不准 | 启发式规则覆盖不全 | 低置信度时调大模型二次判断 |
5.6 几个我踩过的坑
第一个坑是知识图谱建太大。前面提过,两百多个节点导致追问散乱,砍到五十个才顺。第二个坑是评估规则太严。一开始关键词覆盖度要求 0.8 以上才算 deep_dive,结果大部分回答都被判为 fallback,面试节奏很拖沓。后来降到 0.7,配合深度信号,节奏就正常了。第三个坑是项目追问的模板太死。一开始模板是固定的,用户项目描述再具体,问题也是通用句式。后来改成把项目描述片段注入模板,代入感强了很多。
注意:项目描述注入时要做长度限制和敏感信息过滤。用户可能贴了公司名或内部系统名,这些不能出现在追问里。我加了一个简单的过滤,把常见公司名和“内部”“机密”这类词替换掉。
6. 这个 Skill 还能怎么扩展
跑通之后我又想了几个扩展方向。一个是加评分报告,面试结束后输出一份结构化评估,列出每个知识点的掌握程度和薄弱环节。这个用评估节点的历史数据就能生成,不难。另一个是加多候选人对比,同一个项目描述下,不同人的回答质量对比,适合团队内部做技术摸底。
还有一个我觉得挺有价值的方向是反向训练。现在是我问用户答,反过来可以让用户扮演面试官,Skill 扮演候选人,用户来练怎么追问。这个对面试官本身的能力提升也有帮助。
代码我已经整理得比较干净了,知识图谱用 YAML 维护,核心逻辑就三个节点,改起来方便。如果你想加自己的知识点,直接改 YAML 就行,不用动代码。整个项目没有外部服务依赖,clone 下来装完依赖就能跑。
最后分享一个小技巧:如果你用这个 Skill 练习,建议每次练完把追问历史导出,过一周再回看。你会发现当时答不上来的问题,现在能答了,这种进步感比刷题强得多。我自己就是这么练的,RAG 那几个检索优化的追问,从第一次的磕磕绊绊到现在能完整讲清楚决策依据,也就练了三四轮。