作者:互联网 时间: 2026-07-29 08:32:57
大家好,我是二哥呀。
接下来是一份硬核面经,写给那些相信努力与过程、愿意一步一个脚印前进,也相信自己能在 AI 时代分一杯羹的人,希望你能认真读完。
全文写得比较肝,但保证大家能学到很多很多;系好安全带,我们粗粗发~
老王先抛出一道概念题:讲讲 LLM 和 Agent 有什么联系,又有哪些区别。
先谈联系:Agent 作出的每一次决策,都来自 LLM。
是否调用工具、选择哪个工具、参数怎样填写以及任务是否完成,都由模型决定,Agent 框架自身并不负责判断。
二者的区别体现在职责边界上。LLM 是无状态的文本生成服务,一次调用接收一段上下文并输出一段文本;调用结束后既不会保留记忆,也无法改变外部世界。
Agent 则是以 LLM 为核心搭建的执行系统,为模型补齐了三种欠缺的能力:
概括来说,LLM 负责思考,Agent 则把思考结果真正付诸行动。
老王接着问:网页版对话助手能算 Agent 吗?
关键要看是否存在 ReAct 循环。单纯问答只调用一次,输入一段话、输出一段话,因此不算;如果它能自主联网检索、运行代码,并依据中间结果继续推动任务,就已属于轻量级 Agent。
判断时不看产品形态,而要确认是否形成决策、行动、观察结果、再次决策的闭环。ReAct 怎样具体运转,正好留到下一题展开。
老王点头后问:你了解 ReAct Agent 吧?它包含哪些部分?
ReAct 指推理加行动(Reasoning + Acting)构成的循环。以我编写的 PaiCLI-Python 为例,可以拆成五部分,而且每部分都有对应的具体模块:
老王又追问:用户交给它一个问题后,完整流程具体怎样运行?
开始时先完成两项准备:将匹配到的 Skill 候选列表注入上下文,再核查消息总量是否需要压缩。
随后启动循环,把消息历史连同工具列表发送给模型。模型以流式方式返回内容,可能直接输出文本答案,也可能提出工具调用。
若模型选择后者,执行器运行工具,并把结果作为 tool 消息加入历史,然后再次调用模型。模型读取工具结果后继续决策,或继续调用,或给出最终答案。
退出有两个条件:模型不再要求调用工具时正常结束,或者达到 20 轮上限后强制收尾。
执行层面还有两点:只读工具最多支持 4 个并发,写操作则必须严格串行,以免相互覆盖;流式场景中的工具调用参数会分片到达,需要依照序号完整拼接参数片段,再解析为 JSON 交给执行器。若过早拼接,得到的只是半截 JSON,会直接导致解析失败。
老王靠向椅背问:假如从零开始设计 Agent 框架,你会怎样划分模块?
核心可以划为六层:
安全层很容易被忽略,因此值得单独说明。Agent 会真实执行命令,黑名单针对的都是高风险对象,sudo、rm -rf 等命令会被直接拦截。
写文件和执行命令要么标注危险等级,要么强制进行人工确认,并将全部执行记录写入审计日志。系统能力越强,越应提前设计好约束机制。
系统启动时完成组装,由入口层将内置工具和 MCP 工具合并注册到同一张工具注册表,再交给 Agent。
运行过程中,Agent 保存消息历史;每轮将历史交给模型层,再把模型返回的调用请求交由工具层执行,执行结果回填历史,如此循环。
设计中的关键决定,是要求所有模块对外仅输出统一格式的流式事件。无论文本增量、思考增量、工具调用还是用量统计,都统一表示为事件,入口层只负责渲染,不参与业务逻辑。
这种设计让各层都能独立替换:更换模型只改模型层,增加工具只调整注册表,修改界面只需处理入口层。
老王在本子上做了记录,接着让我谈谈 MCP 和 tool 之间的联系与区别。
区别主要看归属。tool 是应用内部的函数,由我编写并注册,随代码运行,无法直接供其他应用使用。
MCP 将工具从应用内部剥离,使其成为独立服务进程,任何支持 MCP 的客户端都能连接使用,从而解决 M 个应用与 N 个工具对接时的组合爆炸问题。
二者的联系在于最终形态一致。MCP 工具接入后,会被包装成与内置工具相同的形式,登记到同一张工具注册表中,再统一以函数调用(Function Calling)格式提供给模型。
模型既不知道,也不必知道某项工具究竟属于本地函数还是远端服务。
具体实现中,我完成了三件事:
配置采用分层合并:用户目录保存全局配置,项目目录保存局部配置;服务同名时由后者覆盖前者,路径支持展开环境变量。
如果某个 server 无法连接,就对其单独隔离并记录错误,不让其他工具的正常注册受到影响。
“还有个容易被忽略的点。MCP 除了工具还有资源和提示词模板,我把它们也映射成了虚拟工具,列资源、读资源和调用普通工具走的是同一条路径,模型侧不用学新动作。”
老王翻到简历下一页,问项目中的智能问答如何处理短期记忆和长期记忆。
短期记忆就是当前会话的消息历史,以列表保存,上限为 100 条,并与上下文压缩配合运行。
当内容达到可用输入预算的 80%时触发压缩,并压到 55%;最近 6 条消息保持原样,更早轮次采用提取式摘要。分割边界设在用户消息处,从而保证工具调用与结果成对保留。
长期记忆则存入用户目录下的 SQLite,可跨会话生效,并按照项目路径隔离作用域。其中有几项设计细节值得展开:
还要守住一条边界:压缩产生的摘要只供当前会话使用,因为它属于模型生成的二手信息,不会升级为长期记忆。
长期记忆只接受用户明确要求保存的事实。一旦放松这一界限,模型自己的转述很快就会污染记忆库。
老王继续问:业界主流的三层记忆系统如何划分,每一层分别保存什么?
主流方案按照作用域划分为三层:
| 层次 | 存储内容 | PaiCLI-Python 中的实现 |
|---|---|---|
| 会话层 | 当前对话消息 | 内存中的消息列表,上限 100 条 |
| 项目层 | 仓库规范与构建命令 | 项目根的 PAI.md,跟着 Git 走 |
| 全局层 | 跨项目个人偏好 | 用户目录中的记忆库及全局配置 |
文件记忆还设有本地覆盖层 PAI.local.md,用于存放仅属于本机且不进入版本库的配置。加载过程同样受预算约束:单文件截断至 6000 字符,合并内容总量限制为 16000 字符,避免记忆过度占用窗口。
建议保存这张表,回答记忆类问题时基本都能套用。
老王提出下一题:是否了解 Skill 的渐进式披露(progressive disclosure)机制?
了解,核心可以概括为八个字:索引常驻,正文按需。整个过程分为两段。
第一段发生在每次用户输入时,只向上下文注入匹配度最高的 5 个 Skill 名称和描述。每条描述最多保留 300 字符,索引总量不超过 4000 字符。此时模型只知道有哪些技能可以使用,正文内容尚未加载。
第二段中,模型判断任务与某个 Skill 匹配后,主动调用加载工具,此时才读取 SKILL.md 正文,上限为 5000 字符。正文不会立即进入当前轮,而会先放入缓冲区,并在下一轮随工具结果一同注入;缓冲区仅保留最近 3 条,以免持续累积。
Skill 目录自身也分为内置、用户级和项目级三层;出现同名内容时,项目级覆盖用户级,这与记忆文件的分层逻辑相同。
这套机制带来的收益可以量化:若将 20 个 Skill 按每篇 5000 字符的上限全部加载,总量达到 10 万字符;采用渐进式披露后,常驻成本仅为 4000 字符索引,相差 25 倍。
老王又问:候选项是怎样匹配出来的?
通过加权评分完成。用户明确点名某个 Skill 时直接赋予最高分;否则根据命中位置计算,名称命中权重最高,标签其次,描述最低。针对中文,还采用二元、三元分词提高召回。
直白地说,它就是一个微型搜索引擎,只是检索对象由网页变成了技能。
老王提出最后一道正式问题:使用 AI 时,应当怎样保障输出内容的质量?
我按三层回答,先介绍自己已经实现的部分。
老王追问。“这些都是工程手段,提示词层面呢?”
“三条实践。把验收标准直接写进提示词,让模型知道什么叫合格;复杂任务要求模型先复述一遍理解再动手,提前暴露偏差;重要产出让模型对照标准自查一轮再交付。”
也要坦白说明,通用钩子机制和结构化输出校验尚未完成,主循环同样没有自动重试。面试中最忌讳把没做过的功能说成已经实现;质量保障的首要原则,就是先确保自己的陈述真实。
老王笑了笑,把本子合上。
项目名称:PaiCLI-Python
项目简介:一款对标 Claude Code 的 Python Agent 命令行工具,提供 ReAct、计划执行、多 Agent 三种模式
技术栈:Python、asyncio、httpx、MCP、SQLite
核心职责:
面试进入收尾环节,轮到我提问:结合刚才的表现,能否给一些后端和 Agent 方面的学习建议?
老王思考后给出了信息量很大的建议,实在太用心了,兄弟,我都想向他鞠个躬。把原话逐项拆开,就是一份完整的自查清单:
清单中的每一项,都可以结合一个真实项目的源码完整过一遍。
过去后端面试比拼并发和中间件,如今还要比拼对模型、工具调用、记忆及上下文的理解。
我们有机会置身 AI 发展的风口浪尖,把 Agent 从名词清单变成写在简历上、经得起追问的项目。挑战固然不少,但可能性同样无限。
兄弟姐妹们,加油吧。
下期再见。