ARTICLE DETAIL

资讯详情

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

从零构建码上面试Agent:ReAct模式、工具调用与状态管理实战

从零构建码上面试Agent:ReAct模式、工具调用与状态管理实战 1. 项目背景为什么做一个码上面试的Agent搞码上面试这个项目的初衷其实非常个人我在给自己做技术面试备战的时候总觉得常规的刷题方式有一种说不出的空洞感。题库刷了很多道代码也都能跑通但一旦进入真实面试的场景面对面试官的连续追问和当场打断我依旧会有明显的卡壳。后来我想明白一件事——传统刷题工具只解决了做题的问题完全没有解决被面试官审视的问题。这才萌生了做一个Agent项目的想法。1.1 从刷题到实战面试的痛点挖洞刷题和真实面试之间至少隔了三道墙。第一道墙是没人追问。你在刷题时写完一段代码只要测试用例过了就结束但面试官会问为什么要用这个思路、有没有更优解、边界条件是否都处理了。第二道墙是没人挑战你的代码。真实面试里面试官经常会指出一个你压根没考虑到的场景而刷题软件只会告诉你通过/不通过。第三道墙是没人给你一个多维度的评价。面试结束你一般只知道自己过没过但面试官觉得你思路错在哪、表达哪里不清晰、复杂度分析是否到位这些信息你完全拿不到。当时我正好在系统学习Agent相关的知识也看了不少AI Agent应用案例很多都是在做模拟角色这件事。于是灵感就很自然地来了能不能用Agent做一个足够可靠的面试官它拥有题库、能阅读和分析代码、能决定何时运行代码验证结果、能在面试过程中持续追问边界和复杂度、最后还能给出结构化报告。如果这个Agent真的能稳定跑下来它对我的价值远不止刷题工具这么简单——它能让我在低频次、低成本的情况下反复进入被面试的状态。这就是码上面试的雏形。1.2 这个Agent必须解决的四个核心问题在真正动手之前我先把这个项目需要解决的能力拆成了四块并分别标注了它们的技术重心第一块是出题。Agent要根据岗位方向、候选人水平从题库中选出一道合适的题目同时把题目描述、输入输出约定、复杂度要求写清楚。这块技术难点不大但是要和后面的评估形成闭环——出过的题要有记录不能无脑重复。第二块是追问。这是我认为Agent和普通判题系统最本质的区别。候选人写完代码后Agent不能直接说对或不对而要决定下一步动作是继续追问思路是要求补充边界用例还是反问复杂度分析。追问策略是否合理直接决定了模拟面试的逼真程度。第三块是验证。Agent必须有能力运行候选人提交的代码并拿测试用例去验证。这要求Agent具备真正的工具调用能力而不是仅在文本层面评价代码看起来对不对。第四块是评估。面试结束之后Agent要输出一个结构化的结果至少包括代码正确性、思路清晰度、沟通表达能力、复杂度分析水平、最终面试结论。评估如果做得好对使用者来说会比一个简单的通过/不通过有价值得多。把这四块列完整个项目的技术选型方向也就出来了需要一个可控的循环流程来模拟面试节奏需要工具调用能力来执行代码需要记忆能力来承接多轮对话状态还需要一个稳定的结构化输出层来生成评估结果。这些都是Agent开发里最核心的组成部分正好可以作为一次完整的agent项目实战来学习。顺便说一句这也是这个项目对我来说最大的吸引力所在——它能让我把网上的零散知识真正变成一个闭环。以前看各种agent理论学习资料总觉得看完就会一做就废这次终于可以把自己写的Agent拿到一个足够复杂的场景里好好磨一遍。2. Agent架构设计核心思考与方案取舍确定项目要解决的问题之后我面临的最大选择题就是到底是用成熟框架来搭还是自己写一套轻量的编排逻辑。这个决策非常关键它先于所有功能实现而且事后证明这个方向判断对后面所有开发效率都有决定性影响。2.1 框架选型为什么不直接套用现成平台搭成品立项初期我把市面上活跃的Agent框架和平台基本都过了一遍包括LangGraph这类偏底层的编排框架也包括Dify、扣子这类偏应用层的Agent平台。说实话每个都有让我心动的地方可视化编排界面上手快内置的对话记忆、知识库、工作流节点都是开箱即用。尤其是如果你只是想快速做一个能和人聊天的Agent产品直接用这些平台是最省力的路径。但码上面试这个项目有一个特殊点它不是一个通用对话型Agent而是一个有严格领域流程的Agent。面试过程里的行为取决于候选人处于哪个阶段是在审题、已经提交代码、还是正在回答追问。整个流程更像一个状态机而不是一条直线的对话流。虽然通用框架也支持定义复杂状态但在实际用起来的时候每加一个判断分支都要在可视化画布上拖一堆节点很快画布就开始变成一团乱麻。而且一旦某个环节需要深度定制比如当代码执行超时需要自动重试并调整提示词用平台实现反而比自己写循环更绕。另一个更实际的考虑是我想借这个项目把Agent内部的机制彻底搞明白。如果我把记忆管理、工具调用协议、上下文压缩这些都交给平台框架那我最后只会得到一个它的运行结果看起来不错的结论但对内部发生了什么一无所知。对一个正处于agent学习阶段的人来说这是本末倒置的。所以最终结论是自建一个轻量级Agent编排核心只借用大模型的对话能力把流程控制、工具注册、状态管理这些关键部分都自己写。写到后面你会发现自建并没有想象中那么复杂反而会让你对Agent的理解深很多。2.2 定制循环把ReAct模式改造成标准面试流程有朋友问过我为什么强调用了ReAct这个概念。简单说ReAct就是让模型在每一轮先思考reason再决定是否调用某个工具去行动action最后根据工具返回的观察结果observation继续推理。这个思路用在码上面试上非常合适。我把这个模式改造成了一个面向面试流程的定制循环每一轮只做四件事第一步读取当前面试状态。状态里包含题目、候选人最新代码、已经问过的追问、当前阶段的标记。第二步把状态和最近几轮对话拼进Prompt同时附上当前可用的工具声明一起发给大模型。第三步解析模型返回的结果如果返回的是普通文本就说明面试官在正常对话如果返回的是结构化工具调用指令就执行对应的工具比如运行代码。第四步把工具执行的观察结果写回状态判断是否要继续循环或进入面试结束。这个循环看起来简单但它是整个Agent的行为中枢。它实际起到的最重要作用是让面试官的每次动作都先经过状态判断再经过工具验证而不是直接凭感觉输出结论。这才是Agent稳定性的来源。2.3 模块划分让决策层与工具层解耦在设计代码结构的时候我给自己定了一个硬性要求Agent的决策逻辑里不允许直接出现工具实现的细节。面试官可以决定我要运行代码但它不需要知道代码是通过本地子进程跑还是通过容器跑也不需要理解超时参数怎么配。这个解耦带来的好处在后面切换执行引擎时体现得非常明显——我后来把并发的执行逻辑调整过一次完全没动到决策层。具体到项目模块我分成了四层入口层负责接收命令行或前端传来的候选人输入编排层负责维护主循环和状态流转工具层负责管理所有可执行工具的注册和调用存储层负责把面试记录持久化到数据库。这样分层之后每一层的职责非常单一调试问题的时候也能很快定位到出问题的模块。有一条经验想反复强调尽量少在System Prompt里塞工具逻辑。早期版本里我把代码执行工具的用途、参数、调用规则全部写进系统提示词指望模型自己判断该做什么。但实际测试后发现模型经常输出我觉得应该运行代码这种文本却不真正发出工具调用指令。后面改成通过原生的Tool Calling协议传输工具定义和参数这种现象才基本消失。这也是我在架构设计阶段踩过的第一个比较深的坑。3. 核心机制实现记忆、工具调用与状态管理架构定下来之后真正把代码写起来时所有问题都集中在三个关键词上记忆、工具调用、状态管理。这三个东西其实互相纠缠一个没做好另外两个也会跟着出问题。下面把我用的具体方案记录一下。3.1 两套记忆机制短期摘要与长时存储先说短期工作记忆。面试过程中每轮新增的信息非常多比如候选人写了一半的代码、随口说的一句思路、某次运行的一个报错。如果全部一股脑塞进上下文几轮之后输入长度就彻底失控。我的做法是每轮对话结束后把关键信息提炼成一个结构化状态对象只保留结论不保留原始文本。我用一段JSON来示意码上面试在面试中期记录的状态大概是这样的{ phase: coding, code_version: 3, exec_result: { passed: false, failed_cases: [case_03, case_07] }, asked_questions: [solution_idea, edge_case_negative] }这个状态对象会被写进下一轮的Prompt让模型始终知道我们现在聊到哪一步了但又不至于每轮都重放几十KB的对话原文。做了这个改造之后单场面试进行到第20轮时上下文长度仍然非常可控模型的稳定性也随之明显好转。再说长时记忆。对于同一个候选人可能会进行多场面试也会涉及不同的题目。我不希望Agent每次都像第一次见面一样完全不记得之前的评估结果尤其当候选人说我上次那道题已经改了思路时Agent如果毫无记忆那整个模拟面试的体验就会很假。目前我的实现比较基础在数据库里存候选人ID、面试场次、题目标识、评估要点下一次面试开始时会主动读取一次。这个功能虽然不复杂但对连续面试场景的作用非常大。3.2 工具调用协议从假装调用到强制执行工具调用这块是整个项目里最需要认真对待的部分因为模型非常容易在文本回复里口头使用工具却不真正触发执行。早期版本中我见过模型输出类似让我运行代码验证一下你的答案的回复然后就没有下文了根本没有调用任何工具。原因就在于工具说明写在了系统提示词里模型把它当成了普通文本语境的一部分。后面我改成了原生的Tool Calling协议在请求模型时传入一份工具定义列表模型只有在真正决定要行动时才会返回结构化的工具调用参数。我也在Prompt里做了非常明确的约束不要用文字描述调用意图不要假装已经执行。如果模型输出了工具调用指令编排层会负责调度相应的执行器如果工具的返回结果没有生成Agent就原则性地不允许进入评分等下游环节。这里给大家一个直接可参考的循环伪代码while not interview_finished(state): response model.chat(build_messages(state), toolstool_definitions) if response.tool_calls: result tool_executor.run(response.tool_calls[0]) state consume_tool_result(state, result) else: state record_utterance(state, response.content)这个模式看起来平平无奇但它是稳定Agent的基石。有了这个强制循环运行代码和等待下一步提问这两种行为就被严格区分开来不会互相污染。3.3 代码沙箱执行安全、超时与资源限制的取舍码上面试要运行候选人提交的代码执行安全就必须认真考虑。我当前阶段采用的是本地子进程方案用subprocess启动一个隔离的Python解释器外加超时控制和内存限制。具体参数上我设置执行超时5秒内存上限256MB一旦超出就杀掉进程并把执行超时/资源不足作为观察结果返回给Agent。这个方案在调试期特别友好几乎零成本也能快速验证Agent的决策逻辑。我也专门去调研过Docker容器执行方案。隔离性更好适合生产环境但有两个问题让我在现阶段先绕开了一是每次执行都要启动容器的开销在并发场景下会被放大二是镜像管理、网络配置、挂载这些复杂度对于验证Agent逻辑这个当前目标来说有点过度设计。更现实的做法是先在本地子进程方案上把Agent行为调顺将来上线前再切换更严格的执行引擎。也正是因为前面做了决策层和工具层的解耦这个切换不会伤筋动骨。4. 实操踩坑记录与排查技巧Agent项目有个特点就是前期的坑比普通后端项目多很多很多问题不是逻辑写错了而是模型表现得不可控。这个章节把我碰到的高频问题、排查思路和最终解法整理成速查希望能帮大家少走弯路。4.1 上下文爆炸模型开始失忆后先别急着调Prompt我第一次出现模型失忆是在面试进行到第20轮左右。现象是整个Agent开始变得恍惚突然重复之前问过的问题或者忘记候选人已经写完了代码甚至围绕一个早已确认过的边界条件反复纠缠。我最初的反应是加大Prompt中对面试官的指令约束但调了几版都收效甚微。后来看了一眼token统计才发现问题根本不是什么提示词不够好而是上下文太长模型根本照顾不过来。解决思路就是我前面提到的结构化状态摘要截断保留最近6轮完整消息更早的内容全部压缩成状态摘要。同时把摘要写得更有指向性比如明确记录已问过复杂度和候选人尚未实现边界处理这样即使模型的即时上下文不包含远古对话也能通过状态对象知道关键结论。另外我强烈建议遇到模型表现不稳定先排查上下文长度而不是盲目叠Prompt魔法。这是我踩了好几次坑之后才真正养成的习惯。4.2 幻觉问题没跑代码就说全部通过最要命对码上面试这个场景来说最危险的幻觉就是Agent在没有执行代码的情况下直接对候选人说你的代码看起来不错应该可以全部通过。这种事情只要发生一次使用者对这个Agent的信任就会完全崩塌。我后来在回归测试里专门为这种情况加了用例一旦发现就视为严重回归。根因还是工具调用和文本输出混在一起导致的。为了让Agent不飘我在评分环节前面加了一道硬校验只有拿到过代码执行工具的真实返回结果Agent才允许给出任何有关通过率的判断否则必须继续追问或者等工具执行完成。同时在工具返回结果里我会把执行的原始数据运行时间、通过样例数、失败用例ID显式地提供给模型。这样模型是基于真实证据做评估而不是凭空脑补。测试中我还发现了一个衍生问题Agent有时候会因为觉得候选人写得差不多而放松验证这其实也是一种伪验证。后来我在Prompt里加了一句话无论候选人给出多自信的解释在对正确性下结论之前都必须至少运行一次代码。这句话配合硬校验规则一起才真正把虚假通过的问题摁住。4.3 并发测试先把工具执行稳定性稳住再谈扩展最近关于AI Agent怎么扛并发的讨论不少我也看了很多资料并做了一些小规模压测但结论可能和大家想的不太一样。在我这个项目的当前阶段最大的并发瓶颈并不是模型调用而是工具执行层。模型调用一般有现成的API并发通道可以扩展但代码执行这层如果设计得不好多场面试同时跑起来时很容易出现进程争抢CPU、执行延迟飙升、甚至被误判成超时的情况。我在本地做过多线程模拟多场面试的测试发现同时启动大量子进程跑代码时延迟确实有明显波动。解决方式是给代码执行增加有界线程池限制同时最多跑4个执行任务同时放宽超时阈值避免误杀。经过这轮调整后并发整体稳定了不少。不过我始终提醒自己在Agent的业务逻辑还没完全打磨稳之前不应该过早追求高并发这种目标先让单场面试可靠才有资格讨论扩展性。4.4 评测闭环给Agent准备一套回归测试集Agent类项目最让人头疼的一点是它的行为可能有随机性同样的输入在不同时间跑结果未必一致。这就意味着你不能靠看一眼输出对不对来验证改动是否破坏了原有功能。为了解决这个问题我给我的码上面试Agent准备了一个回归测试集。这个测试集目前有十几个场景覆盖了核心链路正常面试且代码通过后总结报告、代码报错后Agent追问、边界条件缺失时Agent进一步提示、上下文超长时Agent仍然保持良好的状态衔接等。每次改动Prompt或重构流程之后我都会完整跑一遍这些场景对比预期输出与实际输出。这个习惯帮了我大忙最典型的一次是我重构上下文摘要逻辑后发现某个场景里Agent不再追问边界条件如果没有回归测试这种隐形回归很难被发现。对于Agent开发者我真心推荐尽早建立这么一套回归集。它能帮你在无数次反复调整Prompt和逻辑时守住行为的底线不至于改一处坏一片而毫无察觉。5. 第一阶段做下来的真实体会第一阶段做到能稳定服务完整面试流程对码上面试这个项目来说是一个重要里程碑。这期间踩了不少坑也积累了很多关于Agent开发的实践经验趁着做学习记录的机会把最核心的几条感想留下来。5.1 把Agent当成有工具的决策系统来设计最大的认知转变是我开始更倾向于把Agent理解为一个有工具的决策系统而不是一个更聪明的对话机器人。对话能力只是它表面的出口真正支撑它稳定工作的是状态管理、工具调度、上下文压缩这些看起来不那么炫酷的底层机制。大模型负责聪明编排层负责可靠工具层负责执行状态层负责记忆四者缺一不可。在真实落地时这个认知直接影响了我的优先级分配。比如我会花更多时间去打磨工具调用协议的强校验而不是没完没了地调整面试官的性格Prompt。我会更认真地设计状态对象的结构化字段而不是寄希望于让模型把整段对话背下来。这种思考方式迭代了几轮之后项目的推进速度反而变得更稳了。5.2 下一步的具体规划工具生态、可观测性、评分沉淀第一阶段结束后续的方向也已经比较明确。我会先把工具生态补齐当前只有代码执行这一核心工具后续计划把题库查询、标准解获取、复杂度分析这些都做成独立工具让Agent在面试中能够动态获取更多信息。然后会强化调试与可观测性把输入状态→模型决策→工具执行→下一轮状态的完整链路记录下来让定位问题变得更快。最后一块是评分标准的沉淀把评估维度拆得更细让Agent在给出总评之前先列出分维度评分与理由使报告对使用者的帮助更具体。这篇学习记录一就暂告一段落。个人感觉这个项目的价值已经远超当初做个面试陪练的原始目标——光是亲自动手把一个Agent做成型这个过程就对agent架构、记忆设计、工具调用这些概念有了完全不一样的理解。后续有新的进展我会继续在这个系列里更新也欢迎大家分享自己做Agent项目时踩过的坑。
返回列表