0323 面经内容消化
题目来自小红书
某巴巴agent应用研发
- 找出数字连续的最长序列长度 用set(hash表)存已有数字,若n-1不在set中,说明n是一个序列的开头,接着循环判断n+i是否在列表中
平常打开淘宝的使用链路
- 发射端:手机内部开始“打包”你的身份信息(Token)、设备 ID 和请求地址。
- 网络传输层:系统通过DNS获取最靠近你所在地区的边缘节点IP,阿里的大规模流量调度系统会把你引导到最近的CDN节点
- 接入层:请求进入阿里的大型数据中心,通过TLS/SSL建立加密连接,并在阿里的网关通过WAF(防火墙)拦截、反爬虫验证以及身份鉴权,网关将请求进行负载均衡
- 应用层:请求在这里被分发到具体的业务集群,首页聚合服务会同时调用上百个微服务:
- 用户中心:确认你的用户信息
- 商品中心:拉取基本的商品标题、价格
- 推荐引擎:基于你的历史偏好,算出你想看到的瀑布流内容
- 持久化层:阿里采用异地多活单元化架构,请求会在离你最近的数据中心完成,若涉及下单或状态更新,数据最终会写入OceanBase或PolarDB,确保在高并发下的事务一致性。
- 展现层:服务端将结果压缩成Json或Protobuf格式,图片由CDN节点下发
缓存与数据库不一致导致优惠券显示异常 在淘宝这种高并发场景下,优惠券显示异常通常是因为Cache和DB之间的状态同步出现了时差。处理这类问题的核心逻辑是:确保最终一致性,并对核心链路进行“强一致”兜底。
- 首先,当数据中的优惠券状态发生变化时,不要尝试去更新缓存,而是直接删除缓存。在删除缓存之前先更新数据库。
- 为了应对“先删缓存、后更数据库”期间,由于并发查询导致的旧数据回写问题,先删除缓存,再更新数据库,休眠一段时间以后再次删除缓存,可以确保主从同步延迟期间产生的脏缓存被彻底清除。
- 基于Binlog的消息驱动,数据库变更 提取 Binlog 发送到消息队列 (RocketMQ) 缓存失效服务消费消息 删除对应缓存。
- 多级缓存与版本号对比:在优惠券这种对“钱”敏感的场景,可以在数据包中加入 Version(版本号) 或 Last-Modified 时间戳。
- 业务规避:用户在首页、列表页看到“领券”按钮,允许秒级的延迟(显示有券但点进去没了)。在下单扣减和详情页点击领取时,必须绕过缓存直接请求数据库(或强一致性缓存单元)。准则:导购页看重性能(允许不一致),交易页看重准确(必须强一致)。
plus:为什么执行了先更新数据库,后删除缓存策略,还会出现先删缓存,再更新数据库的期间?
首先,这是一个非常经典且细微的并发时序问题。即便代码逻辑是顺序的,在高并发环境下,程序执行的原子性和网络延迟回导致操作被插队。产生这个期间主要有两个场景
1. 读写并发竞争
此时有两个并发请求A(写操作,更新券状态)和请求B(读操作,查询券信息)
请求A更新了数据库中的优惠券余额,此时网络发生了抖动或上下文切换,请求A还没来得及执行删除缓存这一步,请求B进来查询优惠券,它先看缓存,发现旧值还在,直接返回了旧数据
2. 先删后更的幻觉:读操作回写脏数据
请求A删除了缓存中的优惠券信息,而请求B发现缓存没了,在数据库中读取了A还没更新完的旧数据,然后把旧数据重新写回缓存,此时A再更新数据库。这就是为什么需要延迟双删来修补这个漏洞。
3. 主从同步延迟(架构级原因)
请求A更新了主库,并删除了缓存,请求B查询缓存失效,去读从库,此时主库的数据还没同步到从库,请求B读到了从库的旧数据,并将其写到了缓存。
解决方案:每一张优惠券在数据库里都有一个 update_time。请求返回给 App 时带上这个时间。如果 App 发现收到的数据时间比本地已有的还旧,就丢弃掉,或者提示用户“加载中”,通过前端手段屏蔽掉这个微小的不一致。
某节水山引擎agent应用开发
- 多Agent系统如何设计
多Agent系统的核心挑战不是"让多个Agent跑起来",而是如何保证它们协作产出的质量超过单Agent。如果设计不当,多Agent反而会因为上下文割裂、信息衰减和协调开销导致质量下降。我遵循以下设计原则:
一、基于RFC的任务分解(DAG化)
入口是一份RFC/PRD文档。由Orchestrator Agent读取后,将任务拆解为有向无环图(DAG)中的工作单元。每个工作单元定义:
id+name:唯一标识deps:显式依赖关系(只有真实代码依赖才建边,不做虚假依赖)acceptance:可验证的验收标准tier:复杂度分级(trivial / small / medium / large)
分解的铁律:
- 优先产出更少但更聚合的单元(降低merge冲突概率)
- 最小化跨单元文件重叠(两个Agent不要同时改同一个文件)
- 测试和实现必须在同一个单元内(绝不分拆"实现X"和"测试X")
二、分层编排与并行隔离
DAG决定了执行顺序——同一层无依赖的单元并行执行,有依赖的单元等待上游完成后串行推进。每个工作单元运行在独立的worktree中(git worktree或jj),保证文件系统级别的隔离。
三、按复杂度分级走质量流水线
不同tier走不同深度的pipeline,防止简单改动被过度审查、复杂改动被草率通过:
| Tier | Pipeline |
|---|---|
| trivial | implement → test |
| small | implement → test → code-review |
| medium | research → plan → implement → test → PRD-review + code-review → review-fix |
| large | … → final-review(加一道终审门禁) |
四、消除作者偏差——分窗Agent
这是最关键的设计决策:每个阶段跑在独立的Agent进程/上下文窗口中。Research、Plan、Implement、Test、Review各自由不同的Agent完成,Reviewer绝不看到Implementer的上下文。这从根本上消除了"自己写的代码自己审"的盲区。
阶段间通过结构化artifact传递信息(research产出context doc,plan产出implementation steps,implement产出filesChanged + whatWasDone),而不是靠Agent"记住"。
五、Agent合并队列
所有单元通过质量门禁后进入merge queue:
- Rebase到main → 有冲突?捕获完整冲突上下文,驱逐回implementer
- 运行全量build + test → 失败?捕获失败日志,驱逐
- 通过 → Fast-forward合入main
驱逐不是盲重试——每次驱逐都携带完整的冲突上下文(冲突文件diff、失败测试输出),Agent基于这些信息做智能修复而非无脑重跑。
六、跨迭代上下文桥接
通过SHARED_TASK_NOTES.md在迭代间传递进度、已知陷阱和下一步方向。这是一种轻量但非常有效的上下文持久化手段,避免了每个迭代从零开始。
- 你的评估流是如何工作的
我的评估流遵循Eval-First Loop方法论,核心理念是:在写任何一行实现代码之前,先定义好"什么叫做完"和"什么叫做对了"。
第一步:定义双层Eval
- Capability Eval(能力评估):针对新功能的验收用例。回答"这个Agent能不能完成我们期望它完成的任务?" 包含正常路径、边界条件、异常输入的完整覆盖。
- Regression Eval(回归评估):所有已有功能的测试套件。回答"我们有没有破坏之前已经工作的东西?" 这是Agent迭代的安全网。
第二步:跑基线,抓失败签名
在实现开始前,先跑一遍当前状态的eval,记录基线结果。每一个失败case都会被捕获:
- 失败类型(逻辑错误 / 超时 / 工具调用错误 / 输出格式错误)
- 根因假设
- 最小复现步骤
第三步:实现与迭代
实现Agent按计划执行。每次代码变更后自动触发eval重跑,不依赖人工"记得跑测试"。关键指标被持续追踪:
| 指标 | 含义 |
|---|---|
| completion rate | 任务完成率 |
| pass@1 / pass@3 | 一次通过率 / 三次内通过率 |
| retries per task | 每个任务平均重试次数 |
| cost per successful task | 每个成功任务的token/费用成本 |
| failure class distribution | 失败类型分布(用于定位系统性问题) |
第四步:Delta分析
实现前后对比eval结果:
- 哪些regression被修复了?哪些被引入了?
- capability eval的通过率从X%提升到了Y%
- 是否有tradeoff(修复A导致B退化)?
第五步:结构化观察与自动恢复
每个工具调用返回的不是裸数据,而是结构化观察:
| |
对于error路径,必须包含:
- root cause hint(根因线索)
- safe retry instruction(安全重试指引)
- explicit stop condition(明确的止损边界)
这确保Agent在失败时不会盲重试,而是基于信息做决策。
持续追踪闭环
评估不是一次性的。我会建立长期追踪面板,按任务类型、模型、时间维度切分,让你能回答:“上周我们的Agent任务完成率是78%,本周优化后提升到91%,主要收益来自错误恢复路径的改善。”
- 如果让你优化Agent,提高任务完成率,有什么优化手段?
优化Agent不是调prompt玄学,而是一套系统工程方法。我按四个维度系统性地排查和优化:
维度一:Action Space(动作空间)
Agent能调用的工具决定了它的能力上限。
- 粒度调优:高风险操作(deploy、migration、权限变更)用micro-tool,每个工具只做一件事且行为完全确定。中频操作(edit/read/search)用medium-tool平衡效率。只在round-trip开销成为主导瓶颈时才考虑macro-tool。
- 消除语义重叠:两个名字不同但能力相似的tool会让Agent在选择时犹豫,增加不必要的"思考-纠错"循环。合并或明确分化。
- 输出确定性:每个tool的返回shape必须稳定。Agent不应该需要"猜"工具返回了什么格式的数据。
维度二:Observation Quality(观察质量)
Agent只能基于它能"看到"的信息做决策。
- 所有工具输出统一为
status + summary + next_actions + artifacts四段式结构。 - 错误返回必须包含可操作的恢复信息,而不是只抛一个error code。Agent看到一个"permission denied"比看到HTTP 403能更准确地决定下一步。
- 减少噪音:日志输出只保留对决策有用的信息,避免Agent被大量无意义输出淹没。
维度三:Error Recovery(错误恢复)
这是完成率提升的最大杠杆。大多数Agent失败不是"不会做",而是"做错了不知道怎么改"。
- 建立失败模式分类体系(网络超时、权限不足、文件不存在、逻辑错误、工具误用),每个分类有预设的恢复策略。
- 引入Introspection Debugging(自省调试):当Agent连续失败时,不盲重试,而是先进入一个四阶段诊断流程:
- Failure Capture:精确记录错误类型、最后一次成功步骤、环境假设
- Root-Cause Diagnosis:匹配已知失败模式,判断是逻辑/状态/环境/策略失败
- Contained Recovery:用最小的可逆操作验证诊断(而不是直接大改)
- 如果三次尝试仍无进展,止损升级,向人类报告完整诊断链
- 止损条件:每种重试都有明确的停止条件(“重试3次后如果仍然失败,报告并暂停”),防止Agent进入无限循环消耗token。
维度四:Context Budget(上下文预算)
上下文是Agent最稀缺的资源之一。上下文过长会导致推理质量显著下降(lost-in-the-middle效应)。
- 系统prompt保持最小化且不变,大段指导性内容放到按需加载的skill中。
- 在phase边界做上下文压缩(如完成一个工作单元后,只保留关键结论而非完整对话历史)。
- 对大文件采用reference方式而非inline嵌入。
- 如果一个任务需要超过N轮交互,主动提示拆分或切换到更结构化的执行模式。
进阶手段:模型路由与多模式编排
- 按任务复杂度路由模型:Haiku做分类/boilerplate,Sonnet做实现/重构,Opus做架构分析/根因诊断。不要在简单任务上浪费昂贵模型,也不要在需要深度推理的任务上使用能力不足的模型。
- De-Sloppify模式:不要试图在一个prompt里既让Agent"充分实现"又"不要过度工程"。分两步走——Implementer尽情实现(测试充分、覆盖全面),然后一个独立的Cleanup Agent做精简(去掉测试语言特性而非业务逻辑的用例、去掉不可能状态的防御代码、去掉console.log和注释掉的代码)。两个聚焦的Agent胜过三个被约束的Agent。
- Verification Loop:每次变更后自动跑构建+lint+测试,把验证结果作为下一个Agent步骤的输入,而不是依赖Agent"记住"要验证。
我优化Agent的实战思路:不是从"改prompt"开始,而是先看数据——哪类任务完成率最低?失败集中在哪个阶段?是工具调用错误、逻辑推理错误、还是环境问题?用数据定位瓶颈,再用上述四个维度中针对性的手段去解决。这是工程师的做法,不是prompt engineer的做法。