ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

模拟面试Agent实战:状态机、Prompt与记忆设计的关键边界

2026/10/1 5:39:06 拓冰建站 浏览量
模拟面试Agent实战:状态机、Prompt与记忆设计的关键边界 最近一直在折腾 Agent 方向的落地项目手头正在做的是一个叫《码上面试》的模拟面试 Agent。这个项目说来也简单就是让大模型扮演面试官陪程序员做技术面试练习但它不是那种你问一句它答一句的聊天机器人而是要像一个真实面试官那样开场、提问、追问、打断、收尾最后还给出一份评估报告。这一篇先把整体设计和第一版实现记录下来包括为什么选这个场景、架构怎么定、状态机怎么写、出题模块怎么控制质量以及最关键的——实际跑起来之后踩到的几个坑。我本身是大模型应用开发工程师平时主要做 Agent 框架、Prompt 工程和模型调用优化这一类工作。做这个项目的初衷很简单市面上刷题工具很多但真正模拟被面试的产品少。刷题是单向的背八股是记忆性的而技术面试本质上是对话博弈——面试官会追问、会反问、会打断你需要的是那种随时可能被问住的压力感。我想用 Agent 把这个体验做出来。1. 为什么要做码上面试技术面试的真实缺口在哪里1.1 刷题和真实面试之间缺了一个追问先聊场景。国内程序员准备面试常见的路径是刷 LeetCode、看面经、背八股文。这三个动作其实都指向一个共同的问题——缺少对话式的对抗反馈。刷题练的是解题能力但面试时很多题目是口述的你要边想边说面试官还会打断你问你这个方案有没有考虑并发问题你说用缓存那缓存穿透怎么办。八股文背得再熟被换一个角度追问就露馅。面经更不用说了那是别人的面试记录你只是看不是你在被问。所以我的判断是Agent 最适合干的不是出题而是陪练。它要能模拟面试官的语气、追问逻辑和打断时机实时对候选人的回答做出反应。这是普通的题库App和ChatBot做不到的。1.2 这个Agent的核心人群与服务边界《码上面试》主要面向三类人准备跳槽的中高级开发、准备校招的应届生、以及想评估团队候选人水平的面试官本人用来自测题目质量。服务边界我一开始就划清楚了不追求大而全先做好算法面试 技术深挖面试两个场景。算法面试就是 LeetCode 风格的题目面试官 Agent 引导候选人讲思路、写代码、分析复杂度技术深挖面试覆盖 Java、Go、前端、数据库、Redis 这些八股高频方向。项目名码上面试也是个双关——既是码上/马上开始面试也是写代码码上面的面试。这个定位决定了后面的技术路线对话要可控、节奏要像人、评分要有依据。这三个要求直接影响了 Agent 的架构选型而不是像很多人想的那样接一个大模型API就完事了。2. 第一版架构单Agent状态机是我觉得最稳妥的起点2.1 为什么没有一上来就做多Agent协作现在圈子里一聊 Agent 就是多智能体、编排、协作好像不上多 Agent 就显得不专业。但实际做落地项目我的经验是如果是第一次做单 Agent 先跑通闭环比一上来就拆五个 Agent 靠谱得多。多 Agent 协作的调试成本非常高。A 给你的结论要传给 BB 的理解如果有偏差最后 C 给出的结果就完全失控而且你很难定位是哪个环节出了问题。面试场景尤其如此——面试官这一个角色里包含出题、提问、追问、评分等多种能力如果把这些强行拆成多个 Agent反而会把同一场对话的一致性搞坏。所以第一版架构我定了一个原则先让一个 Agent 具备所有能力通过状态机和工具调用来控制行为边界。等流程稳定了再把评分这类独立环节拆成子 Agent。这个思路其实和写代码一样先单体应用再微服务拆分不要反过来。2.2 技术选型与模块划分整体的技术栈不算复杂我列了个表模块选型说明本体框架自研状态机 工具调用不依赖重量级 Agent 框架降低抽象成本后端服务FastAPI提供 WebSocket 接口承载对话流前端React Vite简单聊天界面后续可升级语音模型层国产大模型 APIOpenAI 兼容格式便于替换和对比模型效果会话记忆Redis 短期窗口 向量库长期记忆短期存上下文长期存用户能力画像向量库Chroma轻量本地开发够用模块划分上我做了四个核心组件状态机控制器管理面试流程防止 Agent 乱跳话题。出题引擎根据用户的技术栈和期望职级生成候选题目。面试官对话模块负责生成面试官话语包括提问、追问、反馈。评估模块面试结束后基于完整对话生成评估报告。这四个模块挂在同一个 Agent 入口下面Agent 的每次响应都会先查询当前处于哪个状态再决定调用哪个模块的能力。2.3 数据流和记忆设计对话数据流是这样的用户消息进入后端后端先把消息写入短期记忆窗口然后请求状态机控制器判断当前状态状态机返回下一步动作提问、追问、收尾再调用对话模块生成话术最后把话术返回前端。记忆设计上短期记忆就是一个滑动窗口最近 6 轮对话全部保留保证上下文连贯。长期记忆比较关键我在面试结束后会把用户的整体表现向量化存入 Chroma下次用户再来时Agent 能调出这个人上次面过薄弱点是系统设计直接作为开场追问的素材。这里插一句很多 Agent 项目忽略了记忆分层全部塞进上下文窗口结果就是 token 爆炸和关键信息稀释。面试场景尤其需要面试官真的记得你上轮说过什么这个能力靠滑动窗口肯定不够必须单独做记忆存储。3. 面试官状态机的核心实现追问、倾听、评分都要受控3.1 面试流程的状态定义与迁移规则面试官不像闲聊机器人它必须有一个清晰的流程。我把面试拆成了六个状态状态含义进入条件行为IDLE空闲会话建立输出欢迎语询问目标岗位INTRO自我介绍用户说明目标引导用户做自我介绍QUESTION出题提问自我介绍完成从题目池选题并提问LISTENING倾听回答问题已提问记录用户回答不打断FOLLOW_UP追问用户回答不完整/有漏洞基于当前回答继续深挖EVALUATION评估收尾达到题目上限或用户放弃生成阶段性反馈并结束迁移规则是硬性的不允许跳级和回退。比如 QUESTION 之后只能到 LISTENINGLISTENING 之后只能到 FOLLOW_UP 或下一个 QUESTION绝对不允许从 FOLLOW_UP 直接跳回 QUESTION。这个约束很重要因为大模型如果完全自由发挥很容易出现上一个话题还没聊完突然换了个新题目的怪现象。3.2 状态机控制代码的结构我用 Python 写了一个非常轻量的状态机核心就一个字典加一个迁移函数class InterviewStateMachine: def __init__(self): self.state IDLE self.transitions { IDLE: [INTRO], INTRO: [QUESTION], QUESTION: [LISTENING], LISTENING: [FOLLOW_UP, QUESTION], FOLLOW_UP: [FOLLOW_UP, QUESTION, EVALUATION], EVALUATION: [END], } def can_transit(self, next_state): return next_state in self.transitions.get(self.state, []) def transit(self, next_state): if not self.can_transit(next_state): return False self.state next_state return True每次 Agent 要输出内容前会先调用can_transit做校验。如果模型在 Prompt 里生成了一个非法转移动作状态机会直接拒绝并强制让模型重新生成。这个设计相当于给模型加了一条物理规则比单纯在 Prompt 里写请遵守面试流程可靠得多。在实际使用中我还加了一个状态锁存当用户在规定时间内重复发送消息时只有处于 LISTENING 状态才允许追加内容其他状态一律提示请等待面试官提问。这样能避免用户在界面上疯狂打字导致对话节奏被打乱。3.3 关键Prompt设计与上下文管理状态机管流程Prompt 管语气和质量。面试官 Agent 的 System Prompt 我改了很多版最终核心人设是这样一段话你是一名资深的技术面试官风格专业但不冷漠。你不是聊天机器人不要复述用户的话。你有三个任务理解候选人的回答、确认候选人的知识边界、通过追问验证候选人的真实水平。禁止在面试结束前透露标准答案。面试结束时你会收到评估指令。这里有两个关键点一是不要复述用户的话因为通用 ChatBot 有个习惯会先说你刚才提到xxx很好真人面试官基本不会这么说话二是禁止在面试结束前透露标准答案这是防止大模型一听到我不会就直接把答案讲出来那样面试就变成教学了。上下文管理上每次调用模型时我会在消息列表里注入一段当前状态标记当前状态FOLLOW_UP 最近一轮用户回答……摘要 你已经追问过的问题列表1.…… 2.…… 3.…… 请基于以上信息生成下一句追问或判断是否结束追问。这一段保证模型不会重复问已经问过的问题同时让它有依据决定是否结束追问。这个已问问题列表不是存在系统 Prompt 里写死的而是从记忆模块动态取出每轮更新。4. 出题模块的难点模型出题容易假大空怎么办4.1 让模型在限定范围内出题出题是面试 Agent 的第一道关口也是最容易被低估的模块。直接让模型出几个 Java 面试题它大概率会给你一堆什么是面向对象什么是 JDK这种既陈旧又空泛的题目。所以我做了一个结构化出题模板把出题范围锁死在技能点、难度、题型三个维度里。Prompt 模板大概是这个思路技术栈Java 候选职级P6高级开发 技能点并发编程、JVM内存模型、MySQL索引 题目类型场景题 2 道概念辨析题 1 道 难度要求 - 场景题必须给出一个具体业务场景不要问如何优化 - 概念题必须包含两个相似概念的对比不要问什么是xxx - 所有题目必须适合口述回答不需要写代码这样约束之后模型给出的题目就从什么是 JVM升级成了类似订单量突然上涨数据库查询变慢你会从哪些层面排查请说明你的排查顺序和理由这种能真正展开追问的题目。出题的本质是给 Agent 一个追问的锚点锚点越具体追问越自然。4.2 题目自检与难度校准我还在出题环节加了一道自检题目生成后再用一次模型调用做质量校验校验维度包括是否有标准答案锚点、是否存在歧义、是否适合口述、难度是否匹配目标职级。这道自检会过滤掉大约 15% 的生成题目剩下的才能进入题目池。其实这也是一个省 token 的优化思路——宁可多花一次模型调用做质检也不要让面试官 Agent 拿着一道烂题在那里硬撑十分钟。难度校准方面我引入了简单的规则纠正用户回答里如果频繁出现不知道没做过下一题难度自动降一级如果用户对追问也能给出完整方案下一题难度升一级。这个规则放在状态机迁移逻辑里不依赖模型判断。5. 实战踩坑记录失控追问、评分幻觉、隐私边界5.1 坑一追问成了一团乱麻靠状态锁存救场第一个版本跑通后发现一个很严重的问题追问阶段 Agent 的提问会变得很散。它可能上一句还在问这个方案的并发瓶颈是什么下一句突然跳到你再说说 Redis 的数据结构。这个问题的根因不在模型能力而在上下文结构——追问阶段我最初没有把当前正在讨论的题目单独拎出来所有历史对话都平铺在上下文里模型很难聚焦。解决办法就是在记忆模块里加了一个当前焦点字段。每道题开始的时候把题目和标准答案锚点单独存一份追问阶段的 Prompt 会优先注入这份焦点信息而不是从完整对话历史里找线索。相当于给模型画了一条线你正在追问的题目是 A其他历史内容只能作为背景不能跳转话题。5.2 坑二小模型只会说正确的废话我前期为了省成本试了一轮中小尺寸开源模型发现在中文面试场景下效果很拉胯。它倒是不会乱答但极其喜欢说正确的废话比如这个问题可以从多个方面来考虑首先我们需要明确一下概念我觉得你答得不错但还有一些细节需要注意这些话放在面试官场景里就是灾难。真人面试官不会说我觉得你答得不错他会直接追问你说的缓存穿透那你觉得怎么避免缓存雪崩。所以我后来做了一个混合调用策略普通对话状态下用小模型做意图识别和状态分类只有追问话术生成和评分环节才调用能力更强的商用大模型。成本和数据质量之间取了个平衡。如果后续要完全本地化部署我会考虑用 70B 以上的大模型或者做专门的 prompt distillation但这都是后话。5.3 坑三评分虚高先给理由再给分数评估模块最初的设计是让模型直接输出一个总分比如 85 分。结果发现它严重虚高动不动就给 90 分以上。原因也不难理解——大模型受到对话中用户努力回答问题的姿态影响倾向于给出正面评价。后来我改了评分逻辑核心思路是要求模型先给出证据再给分数评估要求 1. 先列出用户在对话中实际说出的技术要点逐条对照标准答案 - 用户说到的要点 - 用户遗漏的要点 - 用户的错误表述 2. 然后按照技术准确性40%、沟通表达30%、知识深度30%三个维度打分 3. 最后生成总分和一句总结评语这样改了之后评分明显可信多了。因为模型必须先被迫逐条对照再汇总分数而不是凭感觉给个数字。评估报告的本质是证据链不是分数本身。5.4 坑四隐私边界一定要在系统层面堵住做面试场景最容易忽视的是隐私问题。用户在练习中可能会说出自己的真实姓名、手机号、所在公司甚至项目中的保密信息。如果这些数据被存进向量库后续再被 Agent 调出来就是个安全隐患。我在系统层面做了三道处理第一面试开始时明确提示用户请使用匿名身份不要透露真实姓名、手机号、公司名称第二后端在写入记忆之前对常见 PII 字段做正则脱敏手机号、邮箱、身份证号直接打码第三向量库中的用户画像不存原始对话只存技能评估结果需要追溯原始上下文时再关联到短期对话记录而不是直接以向量形式长期保留敏感信息。6. 下一步规划多Agent拆分与MCP扩展方向6.1 面试官与评估者拆分的动机第一版跑通之后我明显感觉到一个问题面试官 Agent 同时负责对话和评估会产生角色冲突。对话阶段它要表现得像个人要共情、要引导评估阶段它又要冷酷地挑毛病。这两个要求在同一个模型上下文里互相干扰导致对话阶段偶尔会过早剧透评价评估阶段又会带上对话时的关系滤镜。所以下一步我会把评估模块独立成一个 Evaluation Agent。面试结束后面试官 Agent 把完整的结构化面试记录问题和回答、追问链、各题耗时传给评估 Agent由评估 Agent 单独生成报告。面试官 Agent 在整个面试过程中完全接触不到评分逻辑这样能减少因为聊得融洽所以给高分的偏差。6.2 接入MCP工具与记忆增强另外一个方向是 MCP。现在的教训其实挺常见的比如面算法题时正好考察一些基础能力做个简单编译验证就能快速判断答案对不对或者挂一道在线编程题让候选人现场写代码。这些都是未来可以扩展的点等初版框架稳定后再逐个接入也不迟。关于记忆增强我的想法是给长期记忆增加一个能力雷达图结构每次面试结束后更新。用户在平台上多面几次之后Agent 能在开场阶段直接说上次我们面了 MySQL 索引优化这次我们先聊聊缓存吧。这种连续性会让整个模拟面试的体验往上走一个台阶。这个《码上面试》项目还在持续迭代中第一篇文章先记录到这里。整体做下来的体会是Agent 项目能不能落地很多时候不取决于模型有多强而取决于你给模型画了多清晰的边界。状态机是边界Prompt 是边界记忆设计也是边界。把这些边界管住了大模型才能真正扮演好面试官这个角色而不是变成一个什么都能聊但什么都聊不透的聊天机器人。