字节后端一面凉经
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 简单自我介绍,并挑一个项目详细讲。
- 讲一下完整的请求链路。Think 循环每一轮怎么发生,谁决定下一轮做什么?
- 把思考过程给用户看,用户看到的是什么?对用户有什么用?
- 为什么关闭框架内置的工具自动执行,改成手动执行?两种方式各有什么优劣?
- 最大步数 20 是怎么定的?终止工具如果一直不调用怎么办?
- 设计一个通用的熔断 / 降级策略:什么时候继续重试,什么时候快速失败?
- 文档从入库到被检索到,中间经历了哪些步骤?
- 只向量化标题有什么问题?命中正文太长、撑爆上下文怎么办?
- 检索不到内容时怎么办?如何防止模型硬凑答案、产生幻觉?
- 如何评估 RAG / Agent 的检索质量?有自动化评测机制吗?
- 进程、线程、协程的本质区别是什么?请从调度方式、资源消耗、上下文切换说明。
- 内核态和用户态如何切换?是否有状态标志?
- 并发编程本质上是为了解决什么难题?
- 设计并发框架要解决哪些问题?
- 什么是乐观锁和悲观锁?
- 除了加锁,还有哪些方式可以保障并发数据安全?
- 手撕算法:力扣 253 会议室 II。
《参考解析》
Think 循环与完整请求链路:一次请求的链路大致是「接入层鉴权限流 → 装载上下文(系统提示、历史消息、可用工具清单)→ 调用模型 → 解析输出:若是工具调用就执行并把结果作为新消息追加回上下文 → 再调模型,直到模型给出终止回答或触达步数上限」。每一轮做什么的决策者是模型本身,循环控制代码只负责把当前完整上下文发出去、执行它选定的工具、把结果塞回去。工程上要把这个循环写成显式状态机,每轮记录步号、耗时、token、工具名与参数,否则出问题无法回放。
为什么改成手动执行工具:自动执行让模型一给出调用就立刻跑,代码短、延迟低;代价是权限、幂等与可观测性全交给框架,中间插不进参数校验、人工确认、审计和重试策略,模型一旦参数写错或连续重复调用,你没有拦截点。手动执行后每一轮都要过自己的校验:schema 校验参数、按风险分级决定自动执行还是等人确认、结果截断与脱敏、按错误类型决定重试还是快速失败。判据是这个 Agent 是否触碰有副作用的系统——只读检索可以放手自动跑,有写操作必须自己控。
只向量化标题的问题与上下文预算:只对标题做向量,召回粒度就是整篇,标题里那几个词承载不了正文的条件、数字与例外,用户换个说法就漏召;命中后又只能整篇塞进上下文,噪声大还容易撑爆。正确做法是按语义单元(标题层级 + 段落 + 表格)切片、每片单独向量化并保留父子层级与出处,命中片、按预算把所属小节拼回上下文。控制长度有三招:给召回结果设 token 预算并按相关度截断,用 top-k 加重排挑真正相关的几段,长文档先摘要压缩再入上下文。
检索不到时的兜底与防幻觉:不能只靠提示词写一句「不要编」。要有相似度阈值,全部低于阈值时直接返回「知识库里没有相关内容」并给出改写建议或转人工,而不是把低分结果硬塞给模型;把回答约束成必须引用片段编号,生成后校验引用是否真实存在、是否支持结论;再用一次忠实度检查(把答案与片段一起判定是否被支持),不通过就降级成「建议人工确认」。把「不知道」设计成正常输出而不是失败路径,用户对「没查到,可以这样问」的接受度远高于编得很像的答案。
怎么评估检索质量:分层看。检索层用 Hit Rate@k、Recall@k、MRR/NDCG、上下文相关率,前提是有一份「问题 → 正确片段」标注集;生成层看忠实度、答案相关度、完整性与拒答是否恰当。自动化评测的可行路径是:把线上真实问题(含负反馈与追问)抽样成集,人工标注标准答案与依据片段,固定模型版本与参数跑批,每次改切分、改 embedding、改重排都重跑同一份集看指标涨跌。线上再补一组观测:无结果率、追问率、点踩率、平均轮次、工具失败率与延迟,把线上错例回流进评测集。离线涨线上不涨是常态,两边要一起看。
进程、线程、协程:进程是资源分配单位,独立地址空间,切换要换页表、刷 TLB,最贵;线程是调度单位,共享地址空间与文件描述符,切换只存寄存器和栈,开销中等,但同步要加锁、一个线程崩溃可能带崩整个进程;协程是用户态执行单元,由运行时自己调度,切换不陷内核、只保存少量寄存器,所以能开到几十万个,代价是内核看不见它——一个协程做阻塞式系统调用会占住整个底层线程,需要运行时把阻塞调用转成异步。选型:CPU 密集用进程或线程池,高并发 IO 用协程或虚拟线程,要隔离才上多进程。
内核态与用户态的切换:CPU 有特权级(x86 的 ring0~ring3),用户态代码不能执行特权指令、不能直接访问内核页,要读文件、发包、申请内存必须通过系统调用陷入内核。切换时要保存用户态寄存器与栈指针、切到内核栈、按调用号分发,返回时恢复现场;这个过程中 CPU 处于内核态。状态标志主要体现在段选择子的特权级、现场保存的寄存器以及进程控制块里记录的状态上。用户态到内核态的陷入本身不必然伴随进程切换,时间片轮转才切。代价是保存现场与安全检查,所以逐字节读写很吃亏,要用 readv/writev、sendfile、mmap 这类批量接口减少陷入次数。
乐观锁、悲观锁与其他并发安全手段:悲观锁假设冲突一定发生,先加锁再操作(select ... for update、synchronized),适合写多、冲突概率高的场景,代价是阻塞、死锁风险与吞吐下降。乐观锁假设冲突少,先读版本号、提交时用 CAS 或 where version = ? 判断是否被改过,失败就重读重试,适合读多写少,高冲突时会退化成大量无效重试。除加锁外还有几条路:无锁结构与原子类(CAS)、不可变对象与线程私有、单写者模型(actor、串行化队列)、用消息队列把并发写变串行消费、数据库唯一索引与约束兜底、以及分片把竞争分散开。核心思路是减少共享——不共享就不需要同步。
手撕 力扣 253 会议室 II:求同时进行的会议数最大值。差分或扫描线写法最省事:把所有开始记 +1、结束记 -1,事件按时间排序,同一时刻结束要排在开始之前(结束的那场不占用新开始的那间),再遍历求前缀和的最大值,复杂度 O(n log n)。另一种是最小堆贪心:按开始时间排序,堆里存正在使用的会议室的结束时间,每来一场先弹出所有已结束的,再看堆大小,答案取历史最大值。面试里要主动说清两个边界:同一时刻结束与开始的先后,以及区间端点是否包含。差分更短,堆更容易讲出会议室复用的直觉。