面灵AI→

字节业务中台 AI 全栈实习一面:Agent 八股、网络优化与 LRU 手撕

轮次
一面
时间
2026-10
来源
牛客网

《面试题目》

  1. 有在大型公司/正规企业实习过吗?
  2. Agent 是用的框架吗?
  3. 手写:Agent Loop 伪代码
  4. 除了步数耗尽以外,还有什么方法防止 Agent Loop 死循环?
  5. ReAct、Workflow、Replan 分别在什么场景下适用?
  6. MCP 和 function call 有什么区别?
  7. Agent 的 state、context、memory、MCP 分别是干什么的?
  8. Codex 的上下文压缩机制有什么优势?
  9. 已经有了一个 session,怎么跨会话?或者怎么在新会话中使用之前那个会话的信息?
  10. Skill、MCP、CLI、plugin 分别适用在哪些场景下?
  11. 如果有 100 个 Skill 和 100 个 MCP,应该如何处理?怎么让 Agent 精确匹配到你写的 MCP?
  12. SKILL 由哪些组成部分?
  13. 从 URL 到用户浏览器,是一个怎样的过程?
  14. 这些过程哪些地方可以去优化?
  15. DNS 优化有哪些?
  16. 假如有 100 个不同的图片请求,怎么优化?
  17. 浏览器会限制并发请求吗?HTTP/1.0 会限制吗?HTTP/2.0 会限制吗?
  18. HTTP/3.0 会比 HTTP/2.0 和 HTTP/1.0 更快吗?
  19. 手写:闭包计数(3 分钟)
  20. 手撕:经典 LRU 缓存(5–7 分钟)

《参考解析》

Agent Loop 的骨架与防死循环。 伪代码可以写成:messages = [system, user]; while steps < MAX: plan = llm(messages, tools); if plan.is_final: return plan.answer; result = execute(plan.tool_call); messages.append(observation(result)); steps++,加上异常与超时处理、以及「无进展」检测。除步数上限外的防死循环手段:① 重复动作检测——对(工具名 + 归一化参数)做哈希,连续重复或短周期重复即中断;② 进展度量——维护任务状态表(待办/已完成/已验证),若连续 N 轮状态无变化则停止或换策略;③ 预算控制——token 预算与时间预算(不只是步数),以及单工具调用次数上限;④ 外部校验——每轮由校验器(测试、lint、schema)判断是否真的推进,无推进则 Replan;⑤ 确定性终止条件——明确「什么算完成」的判定函数(测试通过、文件生成、接口返回码),而不是让模型自称完成;⑥ 人工/上级 Agent 兜底——超预算转人工或转交规划器重写计划。答题时要点出「死循环的根因是反馈信号不可信」,所以核心是引入独立于模型的外部判据。

ReAct / Workflow / Replan 的适用边界。 ReAct(Thought-Action-Observation 循环)适合步骤数不确定、需要根据环境反馈决定下一步的探索型任务(调试、检索、开放式问答),代价是延迟高、成本高、容易漂移;Workflow(固定的 DAG/流程图,步骤与分支由人预先定义)适合流程稳定、要求可预测与可审计的任务(审批流、数据管道、标准化的报告生成),代价是灵活性差、异常路径需要显式覆盖——生产系统里多数「Agent」实际上应该是 Workflow + 局部 Agent 节点;Replan(执行失败或偏离目标时重新规划)适合长任务且失败可恢复的场景,作为 ReAct 的补充层,代价是可能反复重规划导致成本失控,所以要限制重规划次数并保留已完成的成果。MCP 与 function call 的区别:function call 是「模型输出结构化调用请求」的能力(协议层面让模型表达要调哪个函数、参数是什么),本身不含工具如何被发现、连接、鉴权、跨进程传输;MCP(Model Context Protocol)是工具与上下文的接入协议,规定了 server 如何暴露 tools/resources/prompts、client 如何发现与调用、以及传输(stdio/HTTP+SSE),它是 function call 的上层——模型仍然通过 function call 触发,但工具来自遵循 MCP 的 server,好处是工具可以跨进程/跨语言复用、按需挂载。state/context/memory/MCP 的职责:state 是任务的结构化状态(目标、进度、变量);context 是本次送入模型窗口的内容(有长度约束,需要组装与压缩);memory 是跨轮次/跨会话的持久知识(向量库、KV、事实表),按需检索进 context;MCP 是外部能力与数据的接入层。跨会话的做法:把会话沉淀为可检索的长期记忆(摘要 + 关键事实 + 产物引用),新会话开始时按当前任务检索相关记忆注入,并用稳定的用户/项目标识做隔离;注意写入门槛(什么值得记)与冲突处理(新旧事实谁优先)。

100 个 Skill / MCP 的管理问题,本质是「检索与路由」问题。 全部塞进上下文会让工具描述互相干扰、选择准确率下降且成本爆炸。解法分三层:① 分层路由——先让模型在粗粒度分类里选(域名/业务域),只加载该域下的工具描述(渐进式披露 progressive disclosure);② 检索式选择——用工具描述做向量检索 + 关键词混合召回,按当前任务只注入 TopK 个候选(这也正是很多 Agent 框架的做法);③ 合并与收敛——定期把低频、重叠的工具合并成一个带参数的通用工具,删掉没人用的(工具集的熵要主动治理)。Skill 侧的治理同理:Skill 是「给模型看的可执行文档」,应该按需加载而不是常驻。SKILL 的组成部分(结合通用实践):触发条件(什么时候该用)、目标与边界(能做什么、不能做什么)、步骤或工作流、可调用的工具与脚本引用、输入输出的约定与示例、以及失败与降级语义;工程上还要有版本、依赖声明、测试与可观测性(调用成功率、人工接管率)。Codex 之类系统的上下文压缩优势:把长会话压成结构化摘要 + 保留关键产物引用,从而在小窗口里维持长任务的连续性;优势是成本与延迟可控、关键信息不易被淹没,代价是摘要丢细节(所以需要可回查的原文与自检)。

网络部分:从 URL 到渲染,与每一段可做的优化。 流程:URL 解析 → DNS 查询(浏览器缓存 → 系统 hosts/缓存 → 递归 DNS)→ 建立 TCP 连接(三次握手)→ TLS 握手(1.2 需 2-RTT,1.3 降到 1-RTT/0-RTT)→ 发送 HTTP 请求 → 服务端处理与响应 → 浏览器解析 HTML 构建 DOM → 遇到 CSS/JS/图片按优先级请求 → 构建 CSSOM → 合成渲染树 → 布局(layout)→ 绘制(paint)→ 合成(composite)→ 交互。优化点按阶段列:DNS 用 dns-prefetch/preconnect 提前、减少域名数但不牺牲并行度;连接层用 HTTP/2 多路复用、TLS 1.3、OCSP stapling;请求层用 CDN 就近接入、缓存(强缓存 + 协商缓存 + Service Worker)、压缩(Brotli/gzip)、减少重定向;资源层用代码分割与按需加载、preload 关键资源、defer/async 加载脚本、图片用 WebP/AVIF + 懒加载 + 响应式尺寸(srcset)+ 占位符防布局位移;渲染层减少重排重绘(用 transform/opacity 做动画、批量读写 DOM、避免强制同步布局)、用虚拟列表处理长列表;指标上用 LCP/INP/CLS 衡量并建立监控。DNS 优化:减少解析次数与层级(CNAME 链短)、dns-prefetch/preconnect 预热、使用低延迟的公共 DNS 或 HTTPDNS(防劫持 + 就近解析)、配置合理的 TTL、CDN 侧做 Anycast。100 张图片的优化:合并雪碧图或图标字体(减少请求数,HTTP/2 下收益降低但仍省 CPU)、用 <img loading="lazy"> 让非首屏图片延后加载、限制并发(浏览器本身有每域名并发上限,HTTP/2 下由 SETTINGS_MAX_CONCURRENT_STREAMS 控制)、图片按需压缩与合适尺寸、首屏优先加载关键图(fetchpriority="high")、其余用 IntersectionObserver 懒加载与占位图。并发限制:HTTP/1.0 每个请求一条连接(且无 keep-alive,开销大);HTTP/1.1 允许持久连接,但浏览器对同域名并发连接数限制在 6–8 个,超出的请求排队;HTTP/2 在一条连接上多路复用,不再受同域名连接数限制,但服务端可用 MAX_CONCURRENT_STREAMS 限制流的并发数,且底层 TCP 仍有队头阻塞。HTTP/3 是否更快:在理想网络下延迟优势明显(0-RTT 建连、无 TCP 队头阻塞、连接迁移),但在丢包少、延迟低的场景提升有限,甚至可能因为 UDP 被中间设备限速而更慢;准确说法是「HTTP/3 在高丢包与移动网络下收益最大,收益取决于网络条件而不是协议本身一定更快」。

手撕两题。 闭包计数:用 IIFE 或闭包返回一个函数,内部变量 let n = 0 不暴露在外,每次调用 n++;考点是「变量私有化 + 状态持久化」,以及不要用全局变量。LRU 缓存:哈希表 + 双向链表(哈希表 O(1) 定位节点,双向链表 O(1) 移动/删除,头插尾删),get 命中后把节点移到头部并返回值,put 时存在则更新并移到头部、不存在则插入头部,超容量删除尾节点;Python 里用 OrderedDict 可以几十秒写完(move_to_end + popitem(last=False)),面试时主动说明「手写会用哈希表 + 双向链表,这里用有序字典是因为它已提供 O(1) 的移动与弹出」,避免被认为只会调库;边界要处理容量为 0/1、重复 put 同一 key、以及并发场景(需要加锁或分段)。