ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AI Agent面试系统架构拆解:规划执行分离与记忆管理实战

AI Agent面试系统架构拆解:规划执行分离与记忆管理实战 1. 从“码上面试”说起这个 Agent 项目到底在做什么“码上面试”这个名字第一次看到的时候我脑子里蹦出来的画面是——面试官坐在屏幕对面你在这头敲代码系统实时给你打分。后来真正把项目跑起来、翻完代码结构才发现它比这个画面要立体得多它本质上是一个面向技术面试场景的 AI Agent 系统把“出题、追问、评估、反馈”这一整条链路交给 Agent 去编排而不是简单地丢一个题库出来让你背答案。我是一名做大模型应用开发的工程师日常工作里跟 Agent 打交道的时间占了一大半。这两年 Agent 从概念炒到落地框架换了一茬又一茬但真正能拿来做“学习记录”并且值得反复拆的项目其实不多。《码上面试》这个项目吸引我的点在于它没有一上来就堆花哨的多模态、也没有硬蹭什么热点而是老老实实把 Agent 的几个核心能力——任务规划、工具调用、记忆管理、状态流转——用在一个具体、可验证的场景里。对想入门 Agent 开发的人来说这种“场景明确、边界清晰”的项目比那些号称“万能助手”的 demo 有价值得多。这篇文章是我学习这个项目的第一篇记录主要解决三个问题这个 Agent 的整体架构是怎么设计的、核心模块各自承担什么职责、以及我在复现过程中踩到的坑和总结出来的实操要点。适合谁看如果你已经会写基本的 Python、调用过大模型 API但对“怎么把一堆 prompt 和工具串成一个能跑起来的 Agent”还没什么手感那这篇应该能帮你少走点弯路。如果你已经是老手也可以把它当成一次架构对照看看别人的编排思路和你的习惯差在哪。需要先说明一点下面涉及的具体实现细节有一部分是项目里明确写了的有一部分是我基于“一个合格 Agent 项目在这个场景下最合理的做法”补全的我会尽量把两者区分开避免误导。2. 整体架构拆解为什么这么设计2.1 核心链路的四段式划分把项目跑通之后我习惯性地把它的主链路画成了四段输入解析 → 任务规划 → 执行与工具调用 → 评估反馈。这个划分不是项目文档里写的是我自己拆代码时总结的但它确实能解释为什么代码要这么组织。第一段输入解析负责把用户的原始输入比如“我想练一道中等难度的动态规划题”转成结构化意图。这里项目没有用复杂的 NLU 模型而是靠大模型做一次意图抽取输出 JSON。为什么这么做因为面试场景的输入空间其实不大用规则大模型抽取的组合比训一个专门的分类模型性价比高太多。第二段任务规划是 Agent 的“大脑”决定接下来要出什么题、要不要追问、追问几个回合。第三段执行负责真正调用工具——题库检索、代码执行沙箱、评分器。第四段评估反馈把执行结果汇总成给用户的反馈。这四段看起来平平无奇但关键在于每一段的边界是清晰的。我见过太多 Agent 项目把所有逻辑塞进一个巨大的 prompt 里结果就是调试的时候根本不知道是哪一步出了问题。《码上面试》把链路切开好处是每一段都可以单独测试、单独替换。比如你想把题库从本地换成远程 API只需要改执行层规划层完全不用动。2.2 为什么选择“规划-执行”分离而不是 ReAct 一把梭现在一提 Agent很多人第一反应就是 ReAct——Thought、Action、Observation 循环。ReAct 确实灵活但它在面试这种有明确流程约束的场景里反而容易失控。你想想如果让模型自由决定“下一步干什么”它很可能在出完题之后自己开始答题或者在用户还没回答的时候就急着给评分。这个项目采用的是“规划-执行”分离的思路规划层先产出一个有限状态的任务计划执行层严格按照计划走。这本质上是一种受控 Agent的设计。它的优势在于可预测性强——你知道它最多追问几轮、知道它什么时候会结束。代价是灵活性下降但对于面试这种流程相对固定的场景这个取舍是划算的。提示如果你做的 Agent 场景是开放式的比如通用问答助手ReAct 更合适如果是流程明确的面试、表单填写、工单处理优先考虑受控式设计。选错方向后面调 prompt 会调到怀疑人生。2.3 记忆模块的定位短期状态 vs 长期画像Agent 记忆是这两年被讨论最多的模块之一热词里“agent记忆”“agent 存储 working memory”出现频率极高。这个项目里记忆被拆成了两层会话级短期记忆和用户级长期画像。短期记忆存的是当前这场面试的上下文——已经问了哪些题、用户答得怎么样、当前进行到第几轮。这部分用的是一个滑动窗口 摘要压缩的策略窗口内保留原始对话超出窗口的部分用大模型压缩成摘要。为什么不全量保留因为 token 是要花钱的而且上下文太长反而会稀释模型的注意力。长期画像存的是跨会话的信息——这个用户擅长什么、薄弱点在哪、历史正确率。这部分项目里用的是结构化的键值存储而不是向量库。我一开始觉得奇怪后来想明白了面试评估需要的是精确的、可聚合的指标不是模糊的语义相似。你用向量库去存“用户上次动态规划题做错了”检索出来还得再让模型判断不如直接存一个{topic: dp, last_result: fail}来得干脆。记忆类型存储内容存储方式典型用途短期记忆当前会话对话、轮次状态滑动窗口 摘要维持对话连贯长期画像能力标签、历史表现结构化 KV个性化出题2.4 工具层的设计原则工具调用是 Agent 的手脚。这个项目里我数了一下核心工具大概有这么几类题库检索、代码执行、答案评分、难度调节。每个工具都有明确的输入输出 schema这一点很重要——工具的接口定义越严格模型调用时越不容易出错。我特别想说的是代码执行工具。面试场景里让用户写代码就必须有一个安全的执行环境。项目里用的是容器化沙箱把用户代码丢进一个隔离环境跑限制 CPU 时间和内存。这里有个细节沙箱的启动是有开销的如果每道题都新起一个容器延迟会很难看。项目里的做法是预热容器池提前起好几个待命用完回收。这个优化思路在“ai agent 怎么扛并发”这个话题下非常关键后面我会单独展开。3. 核心模块的实操要点与避坑3.1 规划层的 prompt 怎么写才不跑偏规划层是整个 Agent 最考验 prompt 功底的地方。我复现的时候第一版 prompt 写得太“宽松”结果模型经常自作主张比如用户说“换一道题”它直接把整场面试重置了。后来我调整了策略核心是三点第一把状态机显式写进 prompt。不要指望模型自己记住流程直接把“当前状态等待用户作答可选动作评分、追问、跳过”这样的信息喂给它。第二用枚举约束输出。规划层的输出必须是固定的几个动作之一不允许自由发挥。第三给出反例。在 prompt 里明确写“不要因为用户说‘太难了’就直接降低难度应该先追问确认”。# 规划层输出 schema 示例基于常见实践补全 PLANNER_OUTPUT_SCHEMA { action: enum[ask_question, evaluate, follow_up, adjust_difficulty, end], reason: string, 简短说明决策理由, payload: object, 动作相关参数 }注意规划层的输出一定要做校验。我踩过的坑是模型偶尔会返回 schema 之外的 action如果不校验直接往下传执行层会直接崩。加一层 Pydantic 校验成本很低收益很大。3.2 工具调用的参数校验与重试工具调用最容易出问题的地方是参数。模型生成的参数经常有细微错误——比如题目 ID 少了一位、难度值传了字符串而不是数字。项目里的做法是双重校验schema 层校验类型业务层校验取值范围。重试策略也值得说。不是所有失败都值得重试。参数格式错误重试大概率还是错应该直接把错误信息回传给模型让它修正而网络超时这种重试才有意义。我一般会区分可重试错误和不可重试错误前者退避重试后者直接反馈。def call_tool_with_retry(tool, params, max_retry2): for attempt in range(max_retry 1): try: return tool.invoke(params) except ValidationError as e: # 参数错误回传模型修正不重试 return {error: invalid_params, detail: str(e)} except TimeoutError: if attempt max_retry: raise time.sleep(2 ** attempt) # 指数退避3.3 记忆压缩的触发时机记忆压缩这件事触发时机比压缩算法本身更重要。压得太早上下文丢失对话会变得前言不搭后语压得太晚token 爆了请求直接失败。项目里的策略是按 token 数触发而不是按轮次。因为一轮对话的长度差异很大按轮次触发很容易误判。具体阈值怎么定我的经验是取模型上下文窗口的 60% 到 70% 作为触发点。留出 30% 给压缩后的摘要和后续对话。压缩的时候prompt 要明确告诉模型“保留关键决策和用户表现丢弃寒暄和重复内容”。3.4 沙箱执行的资源限制代码执行沙箱如果不做资源限制就是一个定时炸弹。用户写个死循环整个服务就挂了。项目里限制了几个维度CPU 时间比如 5 秒、内存比如 256MB、进程数、网络访问直接禁用。这里有个实操细节超时时间的设置要区分编译和执行。有些语言编译本身就慢如果统一设 5 秒编译型语言可能还没跑起来就超时了。我的做法是编译给 10 秒执行给 5 秒分开计时。限制维度建议值说明CPU 时间5s执行阶段编译单独计时内存256MB防止内存炸弹进程数1禁止 fork网络禁用面试场景不需要联网4. 完整实操流程从零把项目跑起来4.1 环境准备与依赖安装先把基础环境搭起来。我用的 Python 3.11项目对版本没有特别苛刻的要求但建议不要低于 3.10因为用到了不少新语法。依赖管理我习惯用虚拟环境 requirements比全局装干净得多。python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install -r requirements.txt这里有个坑如果项目里用到了容器化沙箱本地得先装好容器运行时。我一开始没装跑测试的时候一直报连接错误排查了半天才发现是环境问题。装完之后记得把当前用户加到容器用户组否则每次都要 sudo很烦。4.2 配置文件的填写要点配置文件是新手最容易翻车的地方。项目里通常需要一个.env或者config.yaml里面放模型 API 的地址、密钥、以及各种阈值参数。我的建议是先把所有必填项列一个清单填一个勾一个避免漏填导致运行时才报错。几个关键配置项模型名称和 API 地址、记忆窗口大小、沙箱超时时间、最大追问轮次。其中最大追问轮次这个参数我建议一开始设小一点比如 2跑通了再往上加。设太大容易在调试阶段陷入无限追问。提示密钥这类敏感信息千万不要硬编码进代码也不要提交到版本库。用环境变量或者独立的配置文件并且把配置文件加进 .gitignore。4.3 启动与首次对话测试配置填好之后先别急着跑完整流程用最简单的输入测一下。比如输入“开始面试”看它能不能正常出题。这一步的目的是验证主链路是通的而不是验证效果好不好。我一般会准备一组冒烟测试用例正常输入、空输入、超长输入、非法输入。四个用例跑一遍基本能覆盖大部分低级错误。如果这一步就报错先看日志Agent 项目的日志通常会把每一步的输入输出都打出来顺着日志往下找比瞎猜快得多。4.4 关键参数的计算与选择过程这里重点说一下并发相关参数。热词里“ai agent 怎么扛并发”是个高频问题这个项目虽然是个学习项目但并发设计上有值得借鉴的地方。Agent 的并发瓶颈通常不在模型调用本身而在工具执行和状态管理。模型调用可以异步但沙箱执行是重资源操作。假设单个沙箱执行平均耗时 3 秒你想支撑 100 QPS理论上需要 300 个并发沙箱。这个数字显然不现实所以必须用容器池 队列的方式削峰。我的计算逻辑是这样的先测出单次执行的 P95 耗时然后根据目标 QPS 算出需要的并发数再乘以一个安全系数一般 1.5。容器池的大小就按这个数来设。池子太小会排队太大会浪费资源。# 容器池大小估算基于常见实践 p95_latency 3.0 # 秒 target_qps 20 safety_factor 1.5 pool_size math.ceil(p95_latency * target_qps * safety_factor) # 结果904.5 一次完整的面试流程实录我把一次完整的流程走了一遍记录一下关键节点。用户输入“我想练一道中等难度的算法题”规划层解析出意图从题库检索到一道动态规划题执行层把题目返回。用户提交代码后沙箱执行评分器给出结果规划层根据结果决定是追问还是进入下一题。整个过程里我观察到规划层在“用户答错”这个分支上的处理比较讲究它不是直接给答案而是先追问“你觉得哪里可能有问题”给用户一次自我修正的机会。这个设计在面试场景里很合理也更接近真实面试官的 behavior。5. 常见问题与排查技巧实录5.1 模型输出格式错误的排查这是最高频的问题。表现是执行层报 schema 校验失败。排查思路先把模型的原始输出打出来看大概率是多了 markdown 代码块标记或者 JSON 里带了注释。解决办法是在 prompt 里明确要求“只输出 JSON不要任何额外文字”同时在解析前做一次清洗把 json 这类标记去掉。5.2 记忆丢失导致的对话断裂表现是用户说“刚才那道题再讲一下”Agent 却一脸茫然。原因通常是记忆压缩把关键信息压没了或者窗口滑动把早期对话挤出去了。排查方法是把压缩前后的记忆都打出来对比。解决办法是调整压缩 prompt明确要求保留“题目 ID、用户答案、评分结果”这三类信息。5.3 沙箱超时与资源耗尽表现是执行一直 pending 然后超时。先看是不是用户代码本身的问题死循环再看是不是沙箱资源不够。如果是并发高的时候才出现那就是容器池太小需要扩容。我踩过的坑是容器用完没回收导致池子越来越小最后全部阻塞。一定要确保异常路径下也能回收容器。5.4 常见问题速查表问题现象可能原因排查方向解决手段schema 校验失败模型输出带额外标记打印原始输出清洗 prompt 约束对话断裂记忆压缩过度对比压缩前后调整压缩 prompt执行超时死循环或池子不足看并发和代码扩容 资源限制规划跑偏prompt 约束不足看规划层输出加状态机和反例评分不一致评分 prompt 不稳定多次跑同一答案降低温度 加 rubric5.5 几个我踩过的坑第一个坑是过度依赖模型做判断。一开始我让模型自己决定“用户答得对不对”结果同一份答案跑两次结论不一样。后来改成规则模型结合客观题用规则判主观题用模型判但加 rubric 约束。第二个坑是忽略冷启动。第一次请求特别慢因为要初始化各种连接。解决办法是在服务启动时做一次预热把模型连接、容器池都提前建好。第三个坑是日志打太少。Agent 的调用链很长出问题的时候如果日志不全根本没法定位。我的经验是每一步的输入输出都要打宁可日志多一点也别到时候抓瞎。6. 关于 Agent 学习路线的一点个人体会学 Agent 这件事我的建议是别一上来就啃框架。框架是别人抽象好的东西你不理解底层在解决什么问题学框架就是背 API。正确的顺序应该是先手写一个最小的 ReAct 循环理解 Thought-Action-Observation 到底在干嘛然后加上记忆理解上下文管理再加上工具理解 schema 设计最后再去看框架你会发现框架里的每个设计你都能对上号。这个《码上面试》项目就是一个很好的练手对象因为它场景明确、链路完整、又不至于复杂到劝退。我打算接下来继续拆它的评估模块和并发设计尤其是“agent 评测”这块怎么设计一套靠谱的评测集是个值得单独写一篇的话题。如果你也在学 Agent建议不要只看不写把项目跑起来改几个参数看行为怎么变这个过程中学到的东西比看十篇教程都多。
返回列表