面试官皱眉:“就一个Agent项目也敢来面试?”我气笑了:“简历下半页还有一个你倒是看看啊。”面试官:“挺能抗压啊你。”
大家好,我是二哥呀。
面试官老王翻着简历,眉头越皱越紧:“就一个 Agent 项目,也敢来面 AI 应用开发?”
我气笑了:“简历下半页还有一个,您倒是往下看看啊。”
老王的手停在简历中间,没往下翻,嘴角动了一下:“挺能抗压啊你。行,那就从上半页这个开始,接得住,我再看下半页。”
这场连环拷问一共 11 问,从记忆架构一路问到量化评估。答案不是背来的八股——写这篇之前,我把两个项目的源码从头到尾翻了一遍,每一问都能落到具体实现上。AI 面试连环拷问系列又添一篇,关注二哥,一篇不落。
(全文比较肝,保证大家能学到很多很多,系好安全带,我们粗粗发~)
content
01、平时用什么 AI 编程工具
老王的第一问很家常:“平时用什么 AI 编程工具?”
“主力是 Codex 和 Claude Code,看代码用 IntelliJ IDEA,写文本和前端用 VSCode。工具层面我觉得够用就行,别去折腾那些有的没的。”
“用得多了就想搞明白 Claude Code 内部是怎么转的,所以照着它的思路用 Java 写了一个 Agent 命令行工具,就是简历上半页这个 PaiCLI。”
老王点点头:“那就从它开始问。”
【截图:我的工具栈;风格:skill-card;截图目标:展示 Codex、Claude Code、IDEA、VSCode 的分工;关键词:Codex、Claude Code、IntelliJ IDEA、VSCode】
02、三层记忆架构为什么这么设计
“简历上写了三层记忆架构,为什么是三层?”
“这三层对标的是 Claude Code 的 CLAUDE.md 方案,在我的项目里叫 PAI.md,按加载顺序分别是:”
- 用户级
~/.paicli/PAI.md:存跨项目的个人偏好,换哪个仓库都生效 - 项目级
PAI.md:存这个仓库的构建命令、代码规范,跟着 Git 走,全队共享 - 本地覆盖
PAI.local.md:存只属于我这台机器的配置,不进版本库
“每轮对话构建 system prompt 时全部重新加载,所以改了文件,下一轮就生效,不存在缓存不一致。三层的本质是作用域——全局的、共享的、私有的,谁覆盖谁清清楚楚。”
【截图:三层记忆的加载顺序;风格:three-layer;截图目标:展示用户级、项目级、本地覆盖三层的位置与覆盖关系;关键词:PAI.md、用户级、项目级、本地覆盖】
老王追问:“光有文件记忆够吗?”
“不够,这只是文件维度。对话维度还有短期和长期两条线:短期记忆是会话内的消息历史,预算是上下文窗口的 45%,超了就触发压缩;长期记忆是跨会话的事实,存成 JSON 文件,分项目和全局两个作用域。项目作用域用项目的绝对路径做隔离键,检索时只召回当前项目可见的条目,防止 A 项目的偏好污染 B 项目。”
03、记忆为什么用文件,不用向量数据库
老王身体往后一靠:“记忆为什么用文件存?直接上向量数据库不好吗?”
“我知道很多人要说,Mem0 很吊,向量记忆很吊,装个插件 Agent 就有记忆了。但记忆和检索是两个问题。记忆的量级很小,几十条偏好、几千字规范,模型一次就能读完,用不着向量检索;而且记忆要求可读可改,用户打开 Markdown 文件就能看到 Agent 记住了什么,改一行就能纠正它。向量库把内容变成黑盒,这两个优点全丢了。”
“向量检索我也用了,但用在代码搜索上:把代码切块存进 SQLite,向量(embedding)存成 JSON 字段,给语义搜索兜底。工具描述里明确写了,精确定位优先走 grep,语义检索只做辅助。”
【截图:记忆与检索的分工;风格:whiteboard;截图目标:展示文件记忆和代码向量检索是两套独立系统,各管各的场景;关键词:PAI.md、JSON 文件、SQLite、embedding、grep 优先】
老王追问:“文件当事实源,时效性和一致性怎么保证?”
“靠两条。一致性靠单一事实源:同一个事实只存在一个文件里,项目规范在项目级文件,个人偏好在用户级文件,改动只改一处,不存在两份副本互相打架。时效性靠重读:文件不做常驻缓存,每轮构建 prompt 时重新读取,磁盘上是什么,模型看到的就是什么。代价是每轮多几次文件读取,用这点开销换确定性,划算。”
老王接着问:“那怎么防止记忆越存越乱?”
“三道闸门:”
- 入口收紧:保存记忆的工具描述里写死,只在用户明确说“记住”“记一下”时才调用,一次性任务请求不存
- 内容过滤:以“帮我”“新建”这类动词开头的临时请求,带“可能”“猜测”的推断句,直接丢弃
- 完全去重:新条目和已有条目内容完全相同时跳过写入
“另外工具执行结果进入记忆前会截断到 500 字符,防止一次超长输出把预算撑爆。”
04、记忆文件太大怎么办
“文件越攒越大,每次全量读肯定不行,怎么办?”
我说:“分两个层面限制。文件层面,三层 PAI.md 合并后有 24000 字符的总量上限,支持用 @路径 语法引用其他文档,引用递归最多 3 层,同时校验路径防止越界和循环引用。”
“读取层面,读文件的工具默认一次只读 200 行,上限 2000 行,截断时会在结果末尾返回‘可用 offset=N 继续读取’的提示,让模型自己决定要不要接着读。大文件永远不会被整个塞进上下文。”
【截图:大文件的两层限流;风格:checklist-card;截图目标:展示 24000 字符总量上限、3 层引用深度、200 行默认读取这三个数值约束;关键词:24000 字符、递归 3 层、200 行、offset 续读】
05、Agent 是怎么调用工具的
老王换了个方向:“Agent 是怎么调用工具的?它怎么判断该不该调?”
“标准的函数调用(Function Calling)流程,四步:”
- 注册:每个工具定义成 JSON Schema,包含名称、描述、参数类型和必填项
- 决策:调模型时把 schema 列表随消息一起传过去,模型根据工具描述和当前任务的匹配度,自己判断要不要调、调哪个、参数是什么,框架不做判断
- 执行:模型返回 toolCalls 结构,框架路由到对应工具执行
- 回传:结果以 tool 消息追加进对话历史,进入下一轮循环,直到模型不再调用工具,输出最终回答
“一轮返回多个 toolCalls 时,用 4 个线程并发执行,整批 90 秒超时,谁也不阻塞谁。”
【截图:工具调用一个来回;风格:swimlane;截图目标:展示模型、框架、工具三方在一轮调用中的时序;关键词:JSON Schema、toolCalls、tool 消息、并发执行】
老王追问:“除了 Function Calling,还有别的调用方式吗?”
“有两种。一种是 MCP,把工具挂在外部服务上,等会儿可以细说。另一种是 ReAct 的 JSON 约定:不依赖模型原生的 toolCalls 能力,在 system prompt 里约定模型只能返回两种 JSON,要么 tool_call 要么 final_answer,框架解析 JSON 决定是调工具还是收尾。遇到不支持原生工具调用的模型,这条路照样能跑。”
06、工具调用失败,怎么防死循环
“工具调用失败了怎么办?Agent 会不会原地打转?”
“失败不做自动重试,而是把异常转成模型能读懂的字符串,策略拒绝、执行失败、超时各有各的提示语,作为工具结果回传。模型看到失败信息,自己决定换参数重试还是换工具。”
“防死循环有三道兜底,先到先触发:”
- 停滞检测:连续 3 轮工具名和参数完全相同,判定为死循环,直接退出
- 硬性上限:累计 50 轮强制终止,防止慢性消耗
- token 预算:默认不启用,长上下文场景可以显式配置
“实际最顶用的是第一道。把工具名和参数拼成签名做比对,成本几乎为零,却能拦住绝大多数原地打转——模型卡住时的典型症状就是反复发起一模一样的调用。”
【截图:三道兜底的触发条件;风格:checklist-card;截图目标:展示停滞检测 3 轮、硬上限 50 轮、token 预算可选这三道防线;关键词:停滞检测、3 轮同签名、50 轮上限、token 预算】
07、MCP 解决了什么问题
老王点点头:“刚才提到 MCP,它到底解决了什么问题?”
“M 个应用对接 N 个工具的组合爆炸问题。没有 MCP,每个 Agent 应用要为每个外部工具单独写对接代码,工作量是 M 乘 N;MCP 把工具方统一成 server、应用方统一成 client,工作量降到 M 加 N。”
“落到实现上是三件事:”
- 传输层:本地工具走 stdio 子进程通信,远程走 Streamable HTTP,配置里写了 command 就是前者,写了 url 就是后者
- 协议层:JSON-RPC 2.0,客户端用自增 id 标记请求,拿一个 ConcurrentHashMap 存未完成请求,value 是 CompletableFuture,响应回来按 id 配对完成
- 握手:先发 initialize 请求交换协议版本和能力清单,再发 initialized 通知,之后才能拉取工具列表
“拉回来的 MCP 工具会加上服务名前缀做命名空间隔离,防止和内置工具重名,然后合并成同一份列表交给模型。server 端工具变了会推送 list_changed 通知,客户端自动重建工具列表,不用重启。”
【截图:MCP 的分层实现;风格:whiteboard;截图目标:展示传输层、协议层、握手三层结构以及工具合并进主列表的路径;关键词:stdio、Streamable HTTP、JSON-RPC 2.0、命名空间】
08、上下文窗口满了,怎么压缩
“上下文窗口满了怎么办?”
“先说触发时机。压缩阈值不是拍脑袋定的,是从模型窗口大小推出来的:窗口减去给摘要输出预留的 20000 token,再减去 13000 token 的缓冲,得到触发线,同时保证触发线不低于窗口的一半。换模型不用改配置,阈值跟着窗口自动变。”
“压缩方式有两种。一种是 Map-Reduce 摘要:保留最近 3 轮对话,更早的消息按 5 条一片分组摘要,每片压到 200 字以内,再合并成一份总摘要回注上下文。另一种是发送前压缩:每轮调模型之前检查即将发送的消息列表,超线就压成 system 消息、一条历史摘要、再加最近 3 个用户回合的完整消息。”
老王追问:“为什么要两套?”
“因为早期只有第一套,它压的是内部记忆的度量,真正发给模型的消息列表并没有变短,窗口该爆还是爆。第二套是针对这个问题打的补丁,压缩对象直接换成即将发送的消息列表,这才真正省下了 token。”
“这里还有个容易踩的坑:分割点必须落在用户消息的边界上。tool_call 和 tool_result 是成对协议,要是从中间切断,只留一半,下一次请求直接被 API 拒绝。”
老王身体往前倾了倾:“这个坑说得这么具体,是真踩过。”
【截图:压缩触发线的推导;风格:data-board;截图目标:展示窗口减 20000 再减 13000 得到触发线、下限为窗口一半的计算关系;关键词:触发阈值、20000 预留、13000 缓冲、窗口一半】
【截图:简历里的项目卡片;风格:skill-card;截图目标:展示 PaiCLI 项目在简历上的包装结构,名称、简介、技术栈、核心职责一目了然;关键词:PaiCLI、技术栈、核心职责、量化数据】
聊到这,上半页的追问链告一段落。这个项目写进简历可以这么包装:
项目名称:PaiCLI 项目简介:对标 Claude Code 的 Java Agent 命令行工具 技术栈:Java、OpenAI 兼容 API、MCP、JSON-RPC 2.0 核心职责:
- 基于函数调用实现 ReAct 主循环,支持一轮多工具 4 线程并发执行,批处理超时 90 秒
- 设计三层文件记忆与短期长期双轨对话记忆,长期记忆按项目作用域隔离,写入前去重过滤
- 实现停滞检测与 50 轮硬上限的双重防死循环机制,连续 3 轮相同调用签名即熔断退出
- 接入 MCP 协议,支持 stdio 与 Streamable HTTP 双传输,工具列表随通知动态热更新
- 实现窗口自适应的两级上下文压缩,分割点保持用户消息边界,保证工具调用消息成对完整
完整实现在 GitHub 上,感兴趣的小伙伴可以去翻源码,这里就不占篇幅了。
【截图:Agent 面试追问清单;风格:checklist-card;截图目标:汇总 11 问的追问链,做成可收藏的自查清单;关键词:记忆架构、工具调用、MCP、上下文压缩、多 Agent】
09、多 Agent 系统怎么协作
老王终于把简历翻到了下半页:“哟,还真有第二个。工作流编排平台?多 Agent 怎么协作的?”
“先老实交代边界:PaiAgent 不是多个 Agent 开会协商的那种架构,是节点式的工作流编排,思路上类似 Dify 和 n8n。吹成多智能体自主协商我也想,但追问两层就得当场承认那种架构我懂个屁,不如一开始就说实话。平台有二十多种节点,模型节点、条件分支、知识库检索、网页搜索、多模态生成,其中有一个 ReAct Agent 节点,内部自带工具循环,默认最多 5 步,可配置上限 20 步。”
“所谓协作,是把这些节点连成一张有向无环图(DAG):拓扑排序后按序执行,条件节点按字段规则选分支,没被选中的分支整条下游跳过。图的构建用 LangGraph4j 的 StateGraph,加节点、加边、编译、执行;条件分支和断点续跑做在另一套自研 DAG 引擎里,两套引擎按工作流配置切换。”
【截图:工作流的节点编排;风格:whiteboard;截图目标:展示节点连成有向无环图、条件分支选路、未选中分支跳过的执行逻辑;关键词:DAG、拓扑排序、条件分支、ReAct 节点】
10、Agent 之间的上下文和任务状态怎么传递
“节点之间的上下文怎么管?任务状态怎么传?”
“共享状态本质是一个 Map,里面固定几个键。一个键存当前输入,每个节点执行完把自己的输出覆盖进去传给下游;另一个键是节点输出表,按节点 id 存放每个节点的历史输出,下游想引用哪个前驱的结果都拿得到。没有字段级隔离,这是用简化设计换来的低复杂度。”
“持久化走快照:每个节点执行前后各写一次数据库快照,记录输入输出、状态、耗时、重试次数,序列化用 JSON。断点续跑就建立在快照上,恢复时找到状态为 FAILED 的节点作为起点,已成功节点的输出直接从快照反序列化,不用重跑。”
老王追问:“知识库检索节点里的 RAG 是怎么做的?”
“文档按 800 字符一块切分,块与块之间重叠 100 字符,embedding 和原文一起入库。查询时算两个分数,向量的余弦相似度和关键词匹配度,两者取最大值做混合打分,默认返回前 5 条、相似度阈值 0.2。也老实说一句,这是内存级实现,检索时全表扫描算相似度,数据量大了得换专业方案,比如 pgvector 或者 Elasticsearch。”
【截图:节点状态的传递与快照;风格:swimlane;截图目标:展示当前输入覆盖式传递、节点输出表全量可见、快照落库与断点恢复的关系;关键词:当前输入、节点输出表、执行快照、断点续跑】
11、多 Agent 的效果怎么量化评估
老王抛出最后一问:“工作流跑得好不好,怎么量化评估?”
“这题我答一半。已经落地的是执行层指标:每个节点的耗时、每次模型调用的输入输出 token 用量,都随快照入了库,单次执行的开销一目了然。”
“还没做的是质量层评估,成功率聚合和模型评分都在计划里。思路是拿快照数据做执行对比:同一个工作流改动前后各跑一遍,对比成功节点数、失败节点数、总耗时的差值,先把回归问题看住;再往上一层,抽样把节点的输入输出交给一个强模型按标准打分,做自动化的质量抽检。”
老王合上简历,笑了:“行,两个项目都能聊到实现层。开头那句话我收回,下半页确实该看。”
【截图:执行指标看板;风格:data-board;截图目标:展示节点耗时与 token 用量已落地、成功率与模型评分待建设的现状对比;关键词:耗时统计、token 用量、成功率、模型评分】
ending
以前面试拼的是八股背得熟不熟,现在我们需要懂记忆、懂工具调用、懂协议、懂工程化。
【要学的东西越来越多,但只要把项目真正做深,每一问都能往实现层挖一锹,连环拷问就不再是压力,而是我们展示底气的机会。】
我们有机会站在 AI 发展的风口浪尖,亲手把 Agent 从玩具做成产品,参与这个时代最激动人心的技术变革。虽然挑战不少,但也充满了无限可能。
加油吧,兄弟姐妹们。
下期见。
