Interview Bank
489 questions total; 489 shown with current filters.
563 more tagsShow fewer tags
From Frontend Engineer to Agent Engineer in 30 Days
D1 LLM API Basics: messages/roles, Tokens, Streaming, Temperature; What an Agent Actually Is
What are tokens and the context window, and how do they shape agent design?什么是 token 和上下文窗口?它们如何影响 Agent 的设计?
Common in ChinaCommon overseasBasic#llm-basics#contextHow to reason about it · think before answering
- First decide whether this asks for definitions or engineering consequences; a definition-only answer reads as inexperienced.
- Follow the causal chain: tokens are the unit of billing and length, the window caps that unit, models are stateless so history is resent every turn, cost grows with turns, hence context engineering.
- The differentiator is why agents suffer more: a loop calls the model repeatedly and appends tool results back into history.
- Close with concrete tactics: sliding window, summarization, externalized long-term memory, and the cost of each.
- Expect the follow-up: why compress before the window is full? Long contexts dilute attention and raise latency and cost.
分析过程 · 先想清楚再作答
- 先判断这题问的是「概念」还是「工程后果」。只答定义会被认为没做过工程,必须落到设计影响上。
- 从一条因果链推:token 是计费与长度的计量单位 → 窗口是这个单位的上限 → 模型无状态、历史每轮重发 → 成本随轮数增长 → 所以必须做上下文工程。
- 关键要点出在「Agent 比聊天更严重」:Agent 在循环里反复调模型,还要把工具返回结果也塞回历史,增长速度快得多。
- 结论给出具体手段:滑动窗口、摘要压缩、长期记忆外置到检索系统,并说明各自代价。
- 可以预期的追问:窗口没满为什么也要压缩?答案是长上下文会稀释注意力、抬高延迟与成本,不是塞满了才处理。
Key points
- A token is the smallest unit the model processes; roughly 1.3 tokens per English word
- The context window caps input + output tokens per request; beyond it you truncate or compress
- Models are stateless, so the full history is re-sent every turn and cost grows with length
- Hence context engineering: sliding windows, summarization, and external long-term memory
答题要点
- token 是模型处理文本的最小单位,大致 1 个汉字 ≈ 1–2 token,1 个英文单词 ≈ 1.3 token
- 上下文窗口是一次请求里输入 + 输出 token 的上限;超出就要截断或压缩
- 模型没有记忆,历史必须每轮重新塞进 messages,所以长对话的成本随轮数线性增长
- Agent 设计因此要做上下文工程:滑动窗口、摘要压缩、把长期记忆外置到检索系统
What do the system / user / assistant roles do, and why does system exist?messages 里的 system / user / assistant 三种角色各起什么作用?为什么要有 system?
Common in ChinaCommon overseasBasic#llm-basics#promptHow to reason about it · think before answering
- The discriminating half is 'why does system exist'; the first half is a warm-up.
- Explain that the three roles are structural markers over one continuous text the model continues.
- Then the why: rules placed in user are just another turn and get diluted over dozens of turns; system keeps stable weight and can be governed centrally.
- Add production nuance: a real system prompt is templated — persona plus tool docs plus memory plus runtime facts.
- Likely follow-up: can system go last? Possible but unwise — models weight earlier instructions more and it breaks prompt-cache prefixes.
分析过程 · 先想清楚再作答
- 题眼在后半句「为什么要有 system」——前半句是送分,后半句才是区分度所在。
- 先说清三者构成一段可被模型续写的完整文本,角色是给这段文本打的结构化标记。
- 再回答「为什么」:如果把规则写进 user,它就只是对话里的一句话,会被后续几十轮对话稀释;放进 system 才能保持稳定权重,且便于产品侧统一管控、单独灰度。
- 补一条生产视角:真实的 system prompt 通常是模板拼出来的——人设 + 工具说明 + 记忆片段 + 当前时间,而不是一个写死的字符串。
- 常见追问:能不能把 system 放在最后?可以但不推荐,多数模型对靠前的指令更敏感,且会破坏缓存前缀。
Key points
- system sets identity, constraints and output format; it sits first and carries more weight
- user is the human turn, assistant is the model's prior replies; they alternate
- Rules live in system so they are not diluted by later turns and can be controlled centrally
- In production the system prompt is templated: persona + tool docs + memory + runtime facts
答题要点
- system 设定身份、边界与输出格式,通常放在最前面,权重高于普通对话
- user 是用户输入,assistant 是模型历史回复,两者交替构成对话记录
- 把规则放 system 而不是 user,是为了让规则不被后续对话冲淡,也便于产品统一管控
- 生产里 system prompt 往往由模板拼接:人设 + 工具说明 + 记忆 + 当前时间等动态信息
Why do LLM apps stream responses, and how do you choose between SSE and WebSockets?为什么 LLM 应用几乎都用流式输出?SSE 和 WebSocket 该怎么选?
Common in ChinaCommon overseasIntermediate#streaming#protocolHow to reason about it · think before answering
- The first half tests latency literacy: separate time-to-first-token from total latency and tie it to sequential generation.
- Translate to product terms: feedback within a second versus twenty seconds of blank screen.
- For the second half, skip the pros-and-cons table and ask whether the client needs frequent upstream messages.
- Server-to-client tokens only means SSE suffices: plain HTTP, proxy-friendly, with built-in reconnection. Voice, collaboration or frequent interrupts justify WebSockets.
- State the common shape: plain POST for the request, SSE for the reply, plus a cancel endpoint — which sets up the trap that POST-based SSE cannot use EventSource auto-reconnect.
分析过程 · 先想清楚再作答
- 第一问考的是对延迟指标的敏感度:要能区分「首字延迟」和「全文延迟」,并说出模型逐 token 生成决定了前者远小于后者。
- 把它翻译成产品语言:用户 1 秒内看到反馈 vs 对着空白等 20 秒,这是体验的分水岭,不是锦上添花。
- 第二问不要背优缺点表,先问自己「客户端需不需要频繁上行」——这一条几乎决定了答案。
- 只需要服务器往下推 token,SSE 就够:它跑在普通 HTTP 上,代理和负载均衡友好,还自带重连。需要语音、协同、频繁打断这类双向高频交互,才值得上 WebSocket。
- 给出多数产品的真实形态:请求走普通 POST,回复走 SSE,另配一个取消接口——顺势可以引到「POST 的 SSE 用不了 EventSource 的自动重连」这个坑。
Key points
- Models emit tokens sequentially; time-to-first-token is far lower than full latency
- SSE is one-way over HTTP with built-in reconnect and easy proxying, ideal for server→client token streams
- WebSockets are bidirectional, better when the client sends often (voice, collaboration, interrupts) but harder to load-balance
- Most chat products: plain POST for the request, SSE for the reply, plus a cancel endpoint
答题要点
- 模型逐 token 生成,首字延迟远小于全文延迟;流式让用户 1 秒内看到反馈而不是等 20 秒
- SSE 是单向、基于 HTTP 的文本协议,自动重连、穿透代理容易,天然适合服务器→客户端的 token 流
- WebSocket 双向、更适合需要客户端频繁上行(语音、协同编辑、打断)的场景,但代理/负载均衡更麻烦
- 多数聊天产品:请求用普通 HTTP POST,回复用 SSE;需要打断时再加一个取消接口
What fundamentally separates a chatbot from an agent?聊天机器人和 Agent 的本质区别是什么?
Common in ChinaCommon overseasBasic#agent-basicsHow to reason about it · think before answering
- This one invites marketing language; the test is whether your answer names engineering costs.
- Give the structure first: a chatbot is one call, an agent loops think → act → observe until the goal is met.
- Name the three additions — loop, tools, memory — and stress that tools cause side effects on the world.
- Immediately pair each with its cost: permissions and sandboxing, step and budget caps, observability and retries.
- Close with a concrete example and the infrastructure it implies: queues, state machines, cost metering.
分析过程 · 先想清楚再作答
- 这题最容易答成营销话术。判断标准很简单:你的回答里有没有出现「工程代价」,没有就是背概念。
- 先给结构:聊天是一问一答的单次调用;Agent 是在循环里反复「思考 → 调工具 → 观察」直到目标达成。
- 点出三个新增件——循环、工具、记忆——并强调关键差异是「工具能对外部世界产生副作用」,这是可逆与不可逆的分界线。
- 紧接着说代价:有副作用就要管权限与沙箱,有循环就要管步数与成本预算,有多步就要可观测性和失败重试。这一段才是面试官想听的。
- 用一个具体例子收尾(能查库、发消息、定时提醒的助手),并点出它背后需要队列、状态机、成本计量。
Key points
- A chatbot answers once; an agent loops think → act (tool call) → observe until the goal is met
- Three additions: a loop (multi-step), tools (side effects on the world), memory (across turns/sessions)
- They bring engineering concerns: tool permissions and sandboxing, retries, step/cost budgets, observability
- Example: an assistant that queries a DB, sends messages and schedules reminders needs queues, state machines and cost tracking
答题要点
- 聊天机器人是一问一答;Agent 是模型在一个循环里反复思考、调用工具、观察结果直到完成目标
- 三个新增件:循环(多步)、工具(能对外界产生副作用)、记忆(跨轮次/跨会话)
- 随之而来的工程问题:工具权限与沙箱、失败重试、成本与步数预算、可观测性
- 举例:一个能查库、发消息、定时提醒的助手,背后要有消息队列、状态机和成本计量
What do temperature and top_p control, and when would you use 0 versus 0.7?temperature 和 top_p 分别控制什么?什么场景用 0,什么场景用 0.7?
Common overseasBasic#llm-basics#samplingHow to reason about it · think before answering
- Establish that both act on the same next-token distribution but in different ways — that is the discriminator.
- temperature rescales the whole distribution; top_p truncates it to the smallest set reaching cumulative probability p.
- Hence the practical rule: tune one, not both, or you cannot attribute a regression.
- Choose by reproducibility, not by vibes: tool arguments, classification and structured output must be reproducible, so use 0.
- Add the agent angle: planning and tool-calling steps stay cold; only the final user-facing prose warrants higher values.
分析过程 · 先想清楚再作答
- 先说清两者作用在同一个地方——模型算出的下一个 token 概率分布——但作用方式不同,这是区分度所在。
- temperature 是缩放整个分布:越低越尖锐、越确定;top_p 是截断——只保留累计概率达到 p 的那一小圈候选再采样。
- 由此推出实践建议:一般只调其中一个,两个同时调会互相干扰,出了问题分不清是谁造成的。
- 选值不按「创意程度」凭感觉,按「这一步的输出要不要可复现」来定:工具参数、分类判断、结构化输出必须可复现,用 0。
- 补一句 Agent 视角:Agent 的规划与工具调用环节几乎都用低温,只有最终面向用户的自然语言回复才考虑调高。
Key points
- temperature rescales the next-token distribution: lower is more deterministic, higher more random
- top_p samples only from the smallest set whose cumulative probability reaches p; tune one, not both
- Use ~0 for structured output, tool arguments and classification to keep results reproducible
- Use 0.7–1.0 for creative writing; planning steps in production agents usually stay low
答题要点
- temperature 缩放下一个 token 的概率分布:越低越确定,越高越随机
- top_p 只从累计概率达到 p 的候选里采样,是另一种截断随机性的方式;一般只调其中一个
- 结构化输出、工具参数、分类判断用 0 或接近 0,保证可复现
- 创意写作、头脑风暴用 0.7–1.0;生产 Agent 的规划步骤通常也偏低温
A streaming reply is cut off mid-way. What do the client and server each do, and can EventSource auto-reconnect help?流式回复到一半网络断了,前端和后端各要做什么?EventSource 的自动重连能用上吗?
Common in ChinaCommon overseasIntermediate#streaming#reliability#sseHow to reason about it · think before answering
- The trap is the second half: people who memorized 'SSE reconnects automatically' answer yes, which is wrong.
- Native EventSource does auto-reconnect per spec, sending Last-Event-ID, with the server marking events via id: and setting the interval via retry: — but it only issues GET and requires Content-Type text/event-stream.
- LLM chat APIs require POST because messages go in the body, so real clients use fetch plus hand-written SSE parsing, where none of that machinery applies.
- So the client owns detection, retry and buffering of what arrived; the server's job is making retries safe — resumable output and idempotent side effects.
- Give the continuation strategy and its limits: feed the received prefix back as context, but tool-use and thinking blocks cannot be partially recovered — resume from the last complete text block.
- Follow-up to expect: does a non-200 reconnect? Per spec no — a non-200 status or wrong Content-Type fails the connection, and a 204 tells the browser to stop reconnecting.
分析过程 · 先想清楚再作答
- 这题的陷阱在后半句。很多人背过「SSE 自带重连」,就直接答自动重连能救——那是错的,必须先分清两种 SSE 用法。
- 浏览器原生 EventSource 确实按规范自动重连:重连时带 Last-Event-ID 请求头,服务器用 id: 打点、用 retry: 设间隔;但它只能发 GET,且要求响应 Content-Type 是 text/event-stream。
- 而 LLM chat API 必须 POST(messages 要放在请求体里),所以实际用的是 fetch 加手写 SSE 解析——EventSource 那套自动重连一行都用不上。
- 于是前端职责变成:自己判定断流、自己重试、自己保存已收到的部分。后端职责是让重试是安全的——响应可续、副作用幂等。
- 给出续写策略并说清边界:把已收到的内容作为上下文构造续写请求;但工具调用块和思考块无法部分恢复,只能从最近的完整文本块续。
- 可预期追问:非 200 响应会重连吗?按规范不会——状态码不是 200 或 Content-Type 不对,连接直接判定失败;服务器还可以用 204 主动叫停重连。
Key points
- Separate the two SSE modes: native EventSource auto-reconnects with Last-Event-ID but is GET-only; LLM APIs use POST and cannot rely on it
- The client must therefore detect the break, retry itself, and keep whatever text already arrived
- Continuation: send the received prefix as context so the model resumes rather than restarting the turn
- Limits: tool_use and thinking blocks cannot be partially recovered; resume from the last complete text block
- The server must make retries safe: resumable responses, idempotent tool side effects, correct billing for tokens already produced
答题要点
- 先区分两种 SSE:浏览器原生 EventSource 自动重连并带 Last-Event-ID,但只能 GET;LLM API 走 POST,用不上这套
- 所以前端要自己检测断流、自己重试,并保留已收到的部分内容
- 续写策略:把已收到的内容作为上下文发起新请求,让模型接着写,而不是整轮重来
- 边界:tool_use 和 thinking 块无法部分恢复,只能从最近的完整文本块续
- 后端要保证重试安全:响应可续、工具副作用幂等,并对已产生的用量正确计费
The user backgrounds the app or closes the tab. How do you restore a reply that was still being generated?用户切到后台或者直接关掉网页,回来后怎么恢复那条还在生成的回复?
Common in ChinaCommon overseasDeep dive#streaming#reliability#architectureHow to reason about it · think before answering
- First separate this from a dropped connection: the client is gone, so no client-side retry will ever run.
- That leaves one option — the generation must outlive the client, which means persisting the stream server-side.
- Concretely: assign a stream id per generation; the server pushes tokens to the live connection while also writing them to storage such as Redis, and the chat record stores that activeStreamId.
- Recovery is a separate GET endpoint: the client asks with the chat id, the server locates the stream by activeStreamId and resumes; with no active stream it returns 204.
- Name the costs, not just the design: extra storage, expiry/cleanup, and concurrency when several connections consume the same stream.
- Extension: this differs from ordinary message persistence because the reply is still being produced — you need a resumable stream, not a static row.
分析过程 · 先想清楚再作答
- 先识别这题和「网络断了」不是同一个问题:客户端已经不存在了,任何写在前端的重试逻辑都不会执行。
- 由此推出唯一出路:生成过程必须能脱离这个客户端独立存活,也就是把流本身放到服务端持久化。
- 落到具体架构:发起请求时给这轮生成分配一个流 id,服务端一边把 token 推给当前连接,一边把同样的内容写进 Redis 之类的存储;会话记录里保存这个 activeStreamId。
- 恢复路径是另开一个 GET 端点:客户端带着会话 id 请求,服务端按 activeStreamId 找到那条流并接着推;找不到活跃流就返回 204,让前端知道没有需要恢复的东西。
- 说清代价,别只说方案:多了一份存储、一套过期清理、以及「同一条流可能被多个连接消费」的并发问题。
- 延伸:这套结构和普通聊天产品的「消息已持久化,重进会话直接读库」不同——区别在于回复还在生成中,需要的是可续的流而不是一条静态记录。
Key points
- The client is gone, so recovery must live server-side: the generation has to outlive the connection
- Assign a stream id at start; the server writes tokens to Redis while streaming, and the chat stores activeStreamId
- Resume through a dedicated GET endpoint that replays the active stream, returning 204 when there is none
- Costs: extra storage, expiry and cleanup, and concurrent consumers of one stream
- It differs from plain message persistence because the reply is still in flight, so you need a resumable stream
答题要点
- 客户端已经不在了,前端重试无从谈起,必须让生成过程在服务端独立存活
- 发起生成时分配流 id,服务端边推送边把内容写进 Redis,会话里记录 activeStreamId
- 恢复走单独的 GET 端点:按会话 id 找到活跃流接着推,没有活跃流就返回 204
- 代价:额外存储、过期清理,以及同一条流被多个连接消费的并发处理
- 与「消息持久化后重新读库」的区别在于回复仍在生成中,需要的是可续的流
After a retry, how do you avoid double billing and re-executing tool calls that already ran?断线重试之后,怎么保证不重复计费、也不重复执行已经做过的工具调用?
Common in ChinaCommon overseasDeep dive#reliability#tools#idempotencyHow to reason about it · think before answering
- Split it in two: billing is a bookkeeping problem, tool side effects are an execution problem, and they have different fixes.
- Billing: meter server-side by tokens actually produced, not by request count. Tokens produced before the break are real cost; so are retry tokens. The point is not to count the same batch twice.
- That needs a stable identifier: give each generation a run id and dedupe usage records by run id plus sequence.
- Tools: the danger is side-effecting tools — transfers, messages, orders. The fix is an idempotency key derived from the call arguments, checked before execution.
- Add the state-machine view: record each call as pending / running / done and replay only what is unfinished.
- Follow-up: who generates the idempotency key? The caller must, and pass it along — a server-generated key cannot stay stable across retries.
分析过程 · 先想清楚再作答
- 先把问题拆成两半:计费是「记录问题」,工具副作用是「执行问题」,两者的解法不同,混在一起答会含糊。
- 计费侧:用量应该在服务端按实际收到的 token 记账,而不是按「请求次数」。断在中途已经产生的 token 是真实成本,要照记;重试产生的是新成本,也要照记——关键是别把同一批 token 记两遍。
- 为此需要一个稳定的标识:给每轮生成一个 run id,用量记录以 run id + 序号去重,重放同一段不会重复入账。
- 工具侧:真正危险的是有副作用的工具(转账、发消息、下单)。解法是幂等键——由调用参数派生一个稳定的 key,执行前先查这个 key 是否已有结果,有就直接返回旧结果。
- 补一层状态机视角:把每次工具调用记为「待执行 / 执行中 / 已完成」,重试时只重放未完成的部分,已完成的直接取结果,这也是恢复中断任务的通用做法。
- 常见追问:幂等键该谁生成?应由客户端或调度侧生成并随请求传递,服务端自己生成就没法跨重试保持一致。
Key points
- Separate billing (bookkeeping) from tool side effects (execution); they need different mechanisms
- Meter by tokens actually produced, deduped by run id plus sequence so one batch is never counted twice
- Guard side-effecting tools with an idempotency key derived from the call arguments
- Model each tool call as pending / running / done and replay only unfinished work
- The caller must generate and pass the idempotency key so it stays stable across retries
答题要点
- 拆成两个问题:计费是记账问题,工具副作用是执行问题,解法不同
- 计费按服务端实际产生的 token 记,用 run id 加序号去重,避免同一批 token 重复入账
- 有副作用的工具用幂等键:由调用参数派生稳定 key,执行前先查是否已有结果
- 把每次工具调用记成待执行/执行中/已完成的状态机,重试只重放未完成的部分
- 幂等键要由调用方生成并随请求传递,服务端自行生成无法跨重试保持一致
On mobile, connectivity is flaky. How would you design the reconnection strategy for a chat feature?移动端 App 里的对话,网络频繁抖动,你会怎么设计重连策略?
Common in ChinaCommon overseasIntermediate#reliability#mobile#streamingHow to reason about it · think before answering
- Start with what makes mobile different: network switches between WiFi and cellular, the OS suspends apps, background time is limited.
- Use exponential backoff with jitter; jitter is the commonly missed part that prevents a thundering herd when a wide outage clears.
- Set ceilings: max attempts and max interval, then surface an explicit reload action instead of retrying silently forever.
- Distinguish a brief blip from being genuinely offline: subscribe to OS connectivity events, stop retrying when offline, and reconnect on the restore event — far cheaper on battery than blind timers.
- Combine with server-side persistence: after the OS kills the app, resume by chat id rather than reconstructing from local cache.
- Finally the send path: queue outgoing messages while offline and replay them in order, each with an idempotency key.
分析过程 · 先想清楚再作答
- 先说明移动端和浏览器的差别:网络在 WiFi 与蜂窝之间切换、App 会被系统挂起、后台执行时间受限,所以不能照搬网页那套。
- 重试节奏用指数退避加随机抖动。抖动这一条常被忽略,但它是防止大面积断网恢复后所有客户端同时涌上来把服务打垮的关键。
- 要设上限:最大重试次数与最大退避间隔,超过就转成显式的「重新加载」按钮交给用户,而不是无限静默重试。
- 区分「短暂抖动」和「真的没网」:监听系统的网络状态变化,没网时直接停止重试并进入离线态,等网络恢复事件再立刻重连,比盲目定时重试省电得多。
- 结合上一题的服务端持久化:App 被系统杀掉后重进,靠会话 id 请求恢复端点,而不是指望本地缓存拼出完整回复。
- 最后补发送侧:用户在离线时发出的消息进本地队列,恢复后按序重发,且每条带幂等键,避免重复发送。
Key points
- Mobile differs: network handoffs, OS suspension, limited background time — do not copy the web strategy
- Exponential backoff with jitter, where jitter prevents a reconnect storm when an outage clears
- Cap attempts and interval, then hand the user an explicit reload instead of retrying forever
- Listen to OS connectivity events: stop while offline, reconnect on restore, which saves battery over polling
- Resume replies via server-side persistence by chat id; queue outgoing messages with idempotency keys
答题要点
- 移动端特殊性:WiFi 与蜂窝切换、App 被挂起、后台执行时间受限,不能照搬网页策略
- 指数退避加随机抖动,抖动用于避免大面积恢复时的重连风暴
- 设最大重试次数与最大间隔,超过后转为显式的重新加载入口,不做无限静默重试
- 监听系统网络状态:离线直接停重试进入离线态,收到恢复事件再重连,比定时轮询省电
- 回复恢复依赖服务端持久化,靠会话 id 请求恢复端点;发送侧用本地队列加幂等键按序重发
A user pressing stop and a dropped connection both look like a closed connection server-side. How do you tell them apart?用户主动点「停止生成」和网络意外断开,在服务端看起来都是连接没了,怎么区分处理?
Common in ChinaCommon overseasDeep dive#streaming#reliability#uxHow to reason about it · think before answering
- Say why it matters: stop means the user no longer wants the output, so free compute and end the run; a drop means they still want it, so preserve the result for resumption.
- Connection state alone cannot distinguish them — it looks identical — so you need an explicit signal.
- Give stop its own endpoint: the client calls it with the run id before closing, and the server marks the run as user-cancelled and aborts the upstream call.
- Treat a bare connection close as an unexpected drop: keep persisting output and hold the stream for resumption.
- Add the real-world caveat: the stop request itself may fail to send when the network is down, so the server needs a fallback — end a stream with no consumer after a timeout.
- Extend to billing: both cases still owe for tokens already produced, since the upstream provider has charged; they differ only in whether output is retained.
分析过程 · 先想清楚再作答
- 先点破为什么要区分:主动停止是「用户不想要了」,应当立即释放算力并结束这轮;意外断开是「用户还想要」,理应保留结果供恢复。处理反了,用户要么白花钱,要么回来发现内容没了。
- 所以不能只靠 TCP 连接状态判断——它对两种情况的表现是一样的。必须有一个显式信号。
- 做法是给「停止」单独一个接口:前端点停止时先调这个接口,带上 run id,服务端据此把该轮标记为「用户取消」,再中止上游模型调用。
- 而单纯的连接关闭一律按「意外断开」处理:继续把已生成内容落盘、保留可恢复的流,等客户端回来续。
- 补一个现实约束:停止请求本身也可能因为断网而发不出去。所以服务端还需要兜底——比如流没有任何消费者超过一定时间就自行结束,避免算力空转。
- 延伸到计费:两种情况都要为已经产生的 token 计费,因为上游厂商已经收了钱;区别只在于要不要保留结果和是否继续生成。
Key points
- The semantics are opposite: stop frees compute immediately, a drop preserves output for resumption
- Connection state cannot distinguish them, so add an explicit stop endpoint carrying the run id
- Treat a bare close as an unexpected drop: keep persisting and hold the stream for resume
- Fallback: the stop call may itself fail to send, so end streams with no consumer after a timeout
- Both still bill for tokens already produced; they differ only in retention and whether generation continues
答题要点
- 两者语义相反:主动停止要立即释放算力并结束,意外断开要保留结果等待恢复
- TCP 连接状态无法区分,必须有显式信号:给停止单独一个接口,带 run id 标记为用户取消
- 只收到连接关闭一律按意外断开处理,继续落盘并保留可恢复的流
- 兜底:停止请求本身也可能发不出去,服务端需对长时间无消费者的流自行结束
- 计费上两者都要为已产生的 token 记账,区别只在于是否保留结果、是否继续生成
D2 How Tool Calling Works: JSON Schema, the tool_use Loop; Hand-Writing an Agent Loop With No Framework
Walk me through the complete function calling flow.function calling 的完整流程是怎样的?
Common in ChinaCommon overseasBasic#tool-calling#agent-loopHow to reason about it · think before answering
- The word 'complete' is the hinge. Most candidates stop at 'the model returns a tool_call, I run it, I hand back the result' and drop both ends: how the tool definitions get into the request, and what makes the loop continue after the result goes back. They want a closed loop, not a one-way call.
- Walk the lifecycle in five steps: send tools (name, description, JSON Schema parameters) with every request, since they are not remembered; the model replies with tool_calls and a finish reason of tool_calls; you parse arguments — a JSON string, not an object — and execute; you append the assistant message verbatim plus one tool-role message per tool call with matching tool_call_id; you send the now-longer messages again until the finish reason is no longer tool_calls.
- Land on the sentence that draws the security boundary: the model executes nothing. It emits a structured request, and execution, validation, authorization and auditing all live in your code. Since that request ultimately derives from user input, permissions and quotas can never be delegated to the model's good behavior.
- Volunteer the three most common 400s — dropping the assistant message that carried the tool_calls, answering only one of several parallel calls, and treating arguments as an object. Naming them shows you have shipped this.
- Expect the follow-up: do tools cost tokens forever? Yes — the tool list is re-sent every turn, so ten tools is one to two thousand tokens multiplied by the number of steps. Trim the tool set per scenario instead of registering everything.
- Second follow-up: what if the model calls a tool that does not exist? Do not throw. Return 'no such tool, pick one from the list' as an ordinary tool message and the model usually corrects itself on the next turn.
分析过程 · 先想清楚再作答
- 题眼在「完整」两个字。大多数人答到「模型返回一个 tool_call、我执行、把结果给它」就停了,漏掉了两头——工具定义是怎么进到请求里的,以及结果回填之后循环凭什么继续。判据是你能不能把它讲成一个闭环,而不是一次单向调用。
- 顺着一次请求的生命周期走五步:第一步把 tools(name、description、JSON Schema 参数)一起放进请求,注意它每一轮都要重发;第二步模型返回 tool_calls,同时停止原因是 tool_calls;第三步你解析 arguments 并执行——arguments 是一段 JSON 文本而不是对象,要再解析一次;第四步把模型那条 assistant 消息原样追加回历史,再为每一个 tool_call 追加一条 role 为 tool 的消息,tool_call_id 逐个对上;第五步带着变长的 messages 再发一次,直到停止原因不再是 tool_calls。
- 结论要落到一句能划安全边界的话:模型不执行任何东西,它只输出一个结构化的「请求」,真正执行、校验、鉴权、审计的全是你的代码。而这个请求的内容归根结底来自用户输入,所以权限和额度绝不能指望模型自觉。
- 主动说三个最高频的 400,能立刻证明你真写过:漏掉模型那条带 tool_calls 的 assistant 消息、并行调用只回了一条 tool 消息、把 arguments 当对象直接取字段。
- 可以预期的追问:工具会不会一直占 token?会——tools 每一轮都要重发,十个工具一两千 token 再乘以循环步数,所以工具集要按场景动态裁剪,不是接得越多越好。
- 第二个追问:模型请求了一个不存在的工具怎么办?不要抛异常,把「没有这个工具,请从工具列表里重新选」当成一条正常的 tool 消息回传,模型通常下一轮就自己纠正了。
Key points
- Send the tool definitions (name, description, JSON Schema parameters) on every request — they are not remembered
- The model returns tool_calls with a finish reason of tool_calls; arguments is a JSON string that needs a second parse
- Append the assistant message verbatim, then one tool-role message per call with a matching tool_call_id
- Send the longer message list again until the finish reason changes — that loop is what makes it an agent
- The model only requests; execution, validation, authorization and auditing stay in your code
答题要点
- 请求里带上 tools 定义(name、description、JSON Schema 参数),每一轮都要重发
- 模型返回 tool_calls,停止原因为 tool_calls;arguments 是 JSON 字符串,需要再解析一次
- 先把模型那条 assistant 消息原样追加回 messages,再为每个 tool_call 追加一条 role 为 tool 的消息,tool_call_id 一一对应
- 带着变长的 messages 继续下一轮,直到停止原因不再是 tool_calls,这才构成闭环
- 模型只发出请求,执行、校验、鉴权、审计全在你的代码里
What is the ReAct pattern, and how does it relate to a hand-rolled tool-calling loop?什么是 ReAct 模式?它和你手写的工具调用循环是什么关系?
Common in ChinaCommon overseasIntermediate#react#agent-loop#tool-callingHow to reason about it · think before answering
- The trap is answering with a definition. What separates candidates is whether you can say that ReAct and the while loop you wrote are the same thing rather than two parallel technologies.
- Give the history first: when ReAct appeared, model APIs had no tool field. The trick was a prompt-level convention — the model emitted Thought, Action and Action Input as plain text, you regex-extracted the action, ran it, and pasted the Observation back into the prompt.
- Then map it, which is where the points are: function calling froze that convention into the protocol. Thought became message.content, Action became structured tool_calls, Observation became the tool-role message you append. ReAct is the name of your loop, not an alternative to it.
- State the trade-off: the text version is brittle at the parsing layer — a missing newline, JSON where plain text was expected, or a reordered Thought and Action all break the regex. Structured tool calls hand that problem to the server, which is why they are the default today. The text version is still alive though: local small models and older endpoints without a tools field leave you no other option, and you own the parse failure rate.
- Expect: should the model write its Thought out loud? It costs tokens, but accuracy on multi-step tasks usually improves and your logs finally become readable. Treat it as a dial, not a requirement.
- Second follow-up: ReAct versus plan-and-execute? ReAct re-decides at every step, which suits environments that change or information you have to gather as you go; plan-and-execute commits to a full plan up front, giving predictable step counts and cost but reacting poorly to surprises. Production systems often nest them: a coarse plan on the outside, a ReAct loop inside each step.
分析过程 · 先想清楚再作答
- 这题最容易答成名词解释。区分度在于你能不能指出 ReAct 和那个 while 循环是同一个东西,而不是两套并列的技术——把它们说成两样,面试官会认为你只读过博客没写过代码。
- 先给历史脉络:ReAct 出现时模型接口还没有工具字段,做法是在提示词里跟模型约定一套纯文本格式,让它交替吐出 Thought、Action、Action Input,你用正则把动作抠出来执行,再把 Observation 拼回提示词里继续。
- 再做映射,这是拿分的一步:今天的 function calling 把这套口头约定固化成了协议——Thought 对应 message.content,Action 对应结构化的 tool_calls,Observation 对应你追加回去的那条 role 为 tool 的消息。所以 ReAct 是那个循环的名字,不是另一种实现。
- 把取舍说出来:文本版脆在解析,模型少写一个换行、把参数写成 JSON、把 Action 和 Thought 换个顺序,正则就崩;结构化版把这个包袱交给了服务端,是今天的默认选择。但文本版没死——本地小模型、老接口不支持 tools 字段时,回退到「提示词约定 + 正则」仍是唯一可行的兜底,代价是解析失败率自己扛。
- 可以预期的追问:要不要让模型显式写出 Thought?它多花 token,但复杂任务的准确率通常更好,日志也终于可读。这是一个可调旋钮,不是必选项,按任务复杂度决定。
- 第二个追问:ReAct 和先规划后执行(Plan-and-Execute)有什么区别?ReAct 每一步都重新决策,边走边看,适合环境会变、信息要边查边补的任务;先规划后执行一次性出完整计划,步数和成本更可控,但对中途出现的意外不敏感。真实系统常常混用:先出一个粗计划,每一步内部再走 ReAct。
Key points
- ReAct is Reasoning plus Acting: the model alternates thinking and acting, observing each result before deciding the next step
- The original form was a prompt convention parsed by regex; function calling froze that convention into the API protocol
- The three words map to code: Thought is message.content, Action is tool_calls, Observation is the tool-role message you append
- Structured calls remove the parsing burden but require model support; without it you fall back to text ReAct and own the failure rate
- Versus plan-and-execute, ReAct adapts better to change but has less predictable step count and cost
答题要点
- ReAct 是 Reasoning 加 Acting,让模型交替进行推理与行动,观察结果后再决定下一步
- 原始形态靠提示词约定纯文本格式加正则解析;function calling 把这套约定固化进了 API 协议
- 三步一一对应代码:Thought 是 message.content,Action 是 tool_calls,Observation 是回填的 role 为 tool 的消息
- 结构化调用的好处是不用自己解析,代价是依赖模型支持 tools 字段;不支持时只能回退到文本版并自担解析失败率
- 与先规划后执行相比,ReAct 每步重新决策、更适应变化,但步数与成本不如前者可控
When a tool fails, how should the error reach the model — and what must never reach it?工具执行报错时,应该怎么把错误信息传给模型?有没有不该传的?
Common in ChinaCommon overseasIntermediate#tool-calling#error-handlingHow to reason about it · think before answering
- The second half is the discriminator. 'Catch it, log it, return an error' is ordinary backend thinking; the insight they want is that inside an agent loop an error is feedback to the model, not a failure notification.
- Classify first, with one test: can the model fix this? Malformed arguments, a missing required field, a value outside the enum, a unit that should not be there — the model can fix those, so return them, and spell out what correct looks like or it will simply fail differently next time. A database that is down, a 5xx from a downstream service, an expired credential — no amount of re-prompting helps, so code decides whether to retry or abort.
- Then the mechanics: a returnable error becomes an ordinary tool-role message with the matching tool_call_id, not an exception that unwinds the loop. Throwing gives the user a 500; returning usually gets the model to correct itself on the very next turn, which is the cheapest reliability you will ever buy.
- Now the 'never' half: never hand back a raw stack trace. It carries file paths, internal service names and sometimes connection strings, it enters the next request verbatim, the model may recite it to the user, and it costs a thousand tokens re-sent every turn. Send a sentence you wrote; keep the stack in your logs.
- Expect: what if the model never gets it right? Failed calls still count against the step budget, and hitting the cap should end the run with an honest message. Going further, a tool that fails N times in a row can be dropped from the available set for that run, forcing a different route.
- Second follow-up: is this the same as provider fallback? Two sides of one judgment. There you ask whether another provider could plausibly succeed; here you ask whether the model could plausibly fix it. Blanket retry is wrong in both places.
分析过程 · 先想清楚再作答
- 题眼在后半句。只答「catch 住、打日志、返回错误」是普通后端思维,答不出「在 Agent 里错误是给模型的反馈」就拿不到区分度分。
- 先分类,判据是一句话:这个错误模型改得动吗?参数格式不对、缺了必填项、值不在枚举里、单位没去掉——模型改得动,回传,并且要把「正确的样子」写进错误文案,否则它只会换个花样再错一次。反过来,数据库连不上、下游服务 500、凭证过期,模型改一万遍参数也没用,这类该由代码决定重试还是终止,回传只是让它空转烧钱。
- 结论落到形式上:值得回传的错误要变成一条正常的 role 为 tool 的消息,tool_call_id 照样对上,而不是抛异常终止循环。抛了用户看到 500;回传了模型往往下一轮就自己改对,这是 Agent 稳定性最便宜的一份来源。
- 接着答「不该传的」:绝不回传原始异常堆栈。堆栈里有文件路径、内部服务名,有时还有连接串,它会原封不动进入下一次请求,也可能被模型复述给用户;而且动辄上千 token,每一轮都跟着历史重发。回给模型的必须是你自己写的一句话,原始堆栈只进日志。
- 可以预期的追问:模型一直改不对怎么办?错误也要计入步数,撞上步数上限就终止并给用户一句交代;再进一步,同一个工具连续失败若干次可以直接把它从这一轮的可用工具里摘掉,逼模型换条路。
- 第二个追问:这和模型层的 fallback 是一回事吗?是同一套判断的两侧——那边问「换一家 provider 有没有可能变好」,这边问「让模型改一改有没有可能变好」,都是先分类再决定重试,一刀切重试在两边都是错的。
Key points
- Classify first: only model-fixable errors (bad arguments, missing fields, enum violations) are worth returning; infrastructure failures are the code's decision
- Return it as an ordinary tool-role message with the matching tool_call_id, not as an exception that kills the loop
- Write what correct looks like into the message, otherwise the model just fails a different way
- Never return raw stack traces: internal paths leak into the next request and to users, and they burn a thousand tokens every turn
- Failed calls count against the step budget, and a repeatedly failing tool can be removed from the available set
答题要点
- 先分类:模型改得动的错误(参数格式、缺字段、枚举越界)才值得回传,外部故障应由代码决定重试或终止
- 回传的形式是一条正常的 role 为 tool 的消息,tool_call_id 照常对应,而不是抛异常中断循环
- 错误文案里要写清「正确的样子」,模型才知道该怎么改,否则它只会换个花样再错一次
- 绝不回传原始异常堆栈:内部路径与服务名会进入下一次请求、可能被复述给用户,还白白吃掉上千 token
- 报错同样计入步数上限;同一工具连续失败可以临时摘掉,避免模型在原地打转
How do you keep an agent loop from running forever — is a max-step counter enough?怎么防止 Agent 循环停不下来?只加一个最大步数够吗?
Common in ChinaCommon overseasDeep dive#agent-loop#reliability#costHow to reason about it · think before answering
- The second half is an open trap. 'Add a counter' is the passing grade; what they want is whether you know what a counter cannot catch.
- Explain why it runs away first: the finish reason stays tool_calls because the tool results are not moving the model forward — empty results, fields that do not answer the question, error text that never says what correct looks like. So the first line of defense is not a guard rail at all; it is writing tool results and error messages that carry information.
- Then three complementary hard limits: a step cap is the obvious one; a token and cost budget catches 'few steps, all of them expensive'; a per-step wall-clock timeout catches 'one call hung for two minutes'. A system with only a step cap can still blow its budget on a single enormous context.
- Add a semantic guard: detect repeats. The same tool with identical arguments twice in a row is almost always spinning. Cut it short and tell the model so — 'you already called this tool with exactly these arguments' — which usually converges faster than waiting for the counter to run out.
- Hitting the cap needs an honest ending: never return an empty string, give the user a sentence they can act on, and record cap hits as a metric. A rising cap-hit rate usually means a tool's description or return value needs fixing, not that the cap should be raised.
- Expect: what number do you pick? There is no universal one. Chat-style tasks usually fit in five to ten steps; retrieval-heavy tasks need more. Read the production distribution, take p99 plus headroom, and remember that the tighter the cap, the closer your system sits to a fixed workflow rather than an agent.
分析过程 · 先想清楚再作答
- 后半句是明摆着的陷阱。只答「加一个计数器」是及格线,面试官真正想听的是你知道计数器拦不住什么。
- 先解释它为什么会停不下来:停止原因一直是 tool_calls,通常是因为工具返回的东西没帮模型前进——结果为空、字段答非所问、错误文案没说清该怎么改,于是它换个参数一试再试。所以第一层其实不是护栏,是把工具的返回值和错误文案写得有信息量。
- 再给硬护栏,三条互补:步数上限最直接;token 与成本预算拦的是「步数不多但每步都很贵」;单轮的墙上时钟超时拦的是「一步就卡了两分钟」。只有步数上限的系统,照样会被一次超长上下文的调用打爆预算。
- 语义层面再加一条:检测重复调用。同一个工具、同一份参数连续出现两次以上,几乎可以断定它在原地打转,直接截断并把「你已经用完全相同的参数调过这个工具了,换个思路或者告诉用户你做不到」回传给模型,往往比等步数耗尽更快收敛。
- 触顶之后必须有交代:不能静默返回空字符串,要给用户一句能理解的话;同时把触顶记成一个指标,触顶率上升通常意味着某个工具的描述或返回值该改了,而不是把上限调大。
- 可以预期的追问:上限设多少?没有普适值。聊天类任务 5 到 10 步通常够,需要多轮检索的任务可以更高。正确做法是看线上的步数分布,取 p99 再留一点余量,而不是拍脑袋——上限设得越死,你的系统就越靠近固定流程那一端,越不像一个 Agent。
Key points
- The root cause is usually uninformative tool results or error text, so fix that layer before adding guards
- Three complementary hard limits: max steps, a token and cost budget, and a per-step wall-clock timeout
- Add a semantic guard: identical tool plus identical arguments twice in a row means it is spinning — cut it and tell the model
- Give the user an honest message when the cap is hit, and track the cap-hit rate as a signal that a tool needs fixing
- Size the cap from the production step distribution, not intuition; a tighter cap makes the system a workflow rather than an agent
答题要点
- 根因通常是工具返回值或错误文案没信息量,模型无法前进只能反复重试,先把这层写好
- 三条硬护栏互补:最大步数、token 与成本预算、单步墙上时钟超时,只有步数上限并不够
- 语义护栏:同一工具加同一份参数连续重复调用即判定原地打转,截断并把这个事实回传给模型
- 触顶要给用户一句交代,不能静默返回空;同时把触顶率当指标,上升说明工具该改而不是把上限调大
- 上限值按线上步数分布取 p99 加余量;上限越死越接近固定流程,越不像 Agent
D3 Getting Started With the Pi SDK: the Three-Layer Architecture, Comparing It to the Agent Loop (dg P01/P02/M02/M03)
What problems does an agent framework's built-in agent loop have to solve?Agent 框架内部的 Agent Loop 一般要解决哪些问题?
Common in ChinaCommon overseasBasic#agent-loop#framework-designHow to reason about it · think before answering
- This looks like a checklist question, but the real signal is whether you have written such a loop yourself. 'Call the model repeatedly until it stops' reads as documentation-only knowledge.
- The safest structure is to walk down your own hand-written loop line by line, because every line is one problem the kernel must own: issue the model request, maintain message history, decide from the stop reason whether to continue, dispatch by tool name, validate arguments against the schema, fold the tool result back in as a message, and cap the number of turns.
- Naming the stop reason explicitly scores well: the loop exits not when 'the model finished talking' but when the turn's stop reason is not a tool-use one. Most candidates blur past this, and it is the switch that drives the whole loop.
- Then add the three things a hand-rolled version usually skips but a framework cannot: running a batch of tool calls concurrently, emitting the whole run as an event stream so callers are not staring at a black box, and compaction plus session persistence once the context outgrows the window.
- Close on tool errors, which is where production experience shows: a failing tool should raise, and the kernel should turn that into a tool result flagged as an error so the model can fix its arguments and retry. Swallowing the exception and returning 'operation failed' as a normal result makes the model believe the tool succeeded.
- Expect the follow-up: how do you stop runaway loops? A max-turn cap is only a backstop; per-run token and wall-clock budgets plus a pre-execution hook that can block a call and hand the reason back to the model are what actually work.
分析过程 · 先想清楚再作答
- 这题看着像背清单,区分度其实在「你有没有自己写过一遍」。只答「循环调用模型直到结束」会被认为读过文档但没写过代码。
- 最稳的拆法是把手写版的代码从上往下念一遍,每一行都是内核必须解决的一件事:发模型请求、维护消息历史、判断停止原因决定继不继续、按工具名分派、按 schema 校验参数、把工具结果回填成一条消息、控制最大轮数。这条链路念完,答案自然是完整的。
- 点名停止原因这一环最能加分:循环的出口条件不是「模型说完了」,而是这一轮的停止原因是不是「要调工具」。很多人把它含糊过去,而它恰恰是整个循环的开关。
- 然后补上手写版通常没做、但框架必须做的三件:并发执行同一批工具调用、把每一步以事件形式播报出去(否则外部完全是黑箱)、以及上下文超限时的压缩与会话持久化。
- 最后落到工具报错这一条,它是最能体现工程经验的:工具异常不应该被吞掉,要转成一条带错误标记的工具结果回给模型,让模型自己改参数重试;吞掉异常返回一句「操作失败」,模型会以为工具成功了。
- 可以预期的追问:怎么防死循环?答最大轮数只是兜底,更实际的是给单次运行设 token 与耗时预算,并在工具调用前留一个可以拦截的钩子,触发条件时把拦截原因回传给模型让它改道。
Key points
- The skeleton: call the model, maintain history, branch on the stop reason, dispatch tools, validate arguments, fold results back in
- The stop reason is the loop's exit condition — a tool-use reason means one more turn, anything else means done
- Tool execution details: batch calls can run concurrently, hooks belong before and after, and exceptions become error-flagged tool results the model can react to
- An event stream is mandatory, otherwise callers see a black box and observability is impossible
- Safety valves: max turns, token and latency budgets, context compaction, and session persistence for resume
答题要点
- 循环骨架:调模型、维护消息历史、按停止原因判断继不继续、分派工具、校验参数、回填工具结果
- 停止原因是循环的出口条件,工具分支意味着还要再来一轮,其他取值意味着结束
- 工具执行的工程细节:同一批调用可以并发、执行前后要留钩子、异常要转成带错误标记的工具结果回给模型
- 对外要有事件流,否则调用方看不到 Agent 在做什么,也没法做可观测性
- 安全阀:最大轮数、token 与耗时预算、上下文超限时的压缩,以及会话的持久化与恢复
How do you decide between adopting an agent framework and hand-rolling the loop?选择使用 Agent 框架还是手写 Agent,你会怎么权衡?
Common in ChinaCommon overseasIntermediate#framework-design#engineering-tradeoffsHow to reason about it · think before answering
- The hinge word is 'decide'. 'Frameworks are faster' and 'hand-rolling is more controllable' are each half an answer; what earns points is a criterion you can apply on the spot rather than a preference.
- Offer the criterion: ask whether you need to see and change every step inside the loop. If yes, hand-roll — during learning and debugging, under compliance rules that require every model call and tool call to be interceptable and auditable, or when the scenario really is two tools and three turns and the saved lines do not justify a large dependency tree.
- If no, take the framework, and justify it by what you will inevitably need anyway: more tools, streaming every step to a UI, sessions that survive a restart, compaction when context fills up, swapping models on demand. Assemble all of those yourself and you have written a small framework — an untested one.
- Then volunteer the three costs, which is where the signal is: debugging spans more layers, so a tool that never runs could be a bad description, a schema rejection, or a hook that blocked it; you inherit defaults you never wrote, including the model, the system prompt, and the built-in tools; and upgrades change behavior you never tested, which is brutal to diagnose because your own code did not change.
- Land on a practical middle: hand-roll once to internalize the loop, then adopt a framework, override its defaults explicitly, and pin its version. You keep the delivery speed without handing over control of behavior.
- Expect the follow-up: how do you judge a framework? By whether its layering lets you take only half of it — model layer only, loop your own. Anything you must swallow whole will eventually bill you for the half you do not use.
分析过程 · 先想清楚再作答
- 题眼在「权衡」。答「框架更快」或者「手写更可控」都只说了一半,面试官想听的是你有没有一条能当场执行的判据,而不是立场。
- 给判据:问自己「我需不需要看见并改动这段循环里的每一步」。需要就手写——学习调试阶段、合规审计要求每次模型调用和工具调用都可拦截可留痕、或者场景本身只有一两个工具两三轮循环,那点代码量的收益抵不过一整棵依赖树。
- 不需要就用框架,判断标准是这几件事你是不是迟早都要做:工具数量上去、要把每一步实时推给前端、会话要能重启后继续、上下文满了要压缩、要随时换模型。这些凑齐了就是一个小型框架,自己写等于重新发明一个没人帮你测的版本。
- 然后主动说出框架的三笔代价,这是区分度所在:一是排障栈变深,工具没被调用可能是描述、schema、钩子拦截三种完全不同的原因;二是你继承了一堆没写过的默认值,模型、系统提示词、内置工具都是别人替你选的;三是升级会改变你没测过的行为,代码一行没动线上表现却变了,这类问题最难定位。
- 结论要给出可落地的折中:先手写一遍把循环吃透,再上框架;上了框架也要显式覆盖掉默认值,并把框架版本锁死。这样既拿到了开发速度,也没把行为的控制权整个交出去。
- 可以预期的追问:那你怎么评估一个框架好不好?答看它的分层能不能让你「只要一半」——只要模型调用层、循环自己写行不行;必须整包吞下的框架,迟早要为用不上的那一半付代价。
Key points
- The criterion is whether you need to see and modify every step of the loop
- Hand-roll for learning and debugging, for compliance that demands interceptable and auditable steps, for genuinely tiny scenarios, and where dependency size or cold start matters
- Use a framework once you need many tools, an event stream, persistent sessions, compaction, and model swapping — building all of that is writing a framework yourself
- Three costs: deeper debugging surface, inherited defaults you never wrote, and upgrades that shift untested behavior
- The middle path: hand-roll once, then adopt, override defaults explicitly, and pin the version
答题要点
- 判据是「需不需要看见并改动循环里的每一步」,需要就手写,不需要就用框架
- 手写更合适:学习调试、合规要求每步可拦截可留痕、场景极简、对依赖体积与冷启动敏感
- 框架更合适:工具多、要事件流、要会话持久化与压缩、要多模型——这些凑齐等于自己造一个框架
- 框架的三笔代价:排障栈变深、继承一堆没写过的默认值、升级会改变没测过的行为
- 折中做法:先手写吃透循环再上框架,显式覆盖默认值并锁死版本
What are the responsibilities of Pi SDK's three layers, and what does that layering buy you?Pi SDK 的三层架构分别对应什么职责?这样分层解决了什么问题?
Common in ChinaCommon overseasBasic#framework-design#architectureHow to reason about it · think before answering
- The first half is recall; the second half carries the signal. Reciting three package names without explaining the cut suggests you only skimmed the docs.
- State the layers precisely: the bottom is a unified model layer that normalizes each provider's request format, auth and streaming into one interface while tracking tokens and cost; the middle is the agent kernel built on top of it, owning the agent loop, tool execution, state and the event stream; the top is the application layer, owning session storage, extension and resource loading, built-in tools, and the interactive, print, RPC and embedded-SDK run modes. Dependencies point strictly downward.
- Then answer what it buys: layering lets you take only half. Want just a unified model layer and your own loop? Stop at the bottom. Want the full loop but none of the terminal UX? Stop in the middle. That test generalizes to any framework and is worth far more than the package names.
- Add the practical payoff: when something breaks, first place it in a layer. A stack trace through the model layer points at auth, a wrong model id or a malformed request; one through the kernel points at the loop or tool execution. The two investigations look nothing alike.
- Expect the follow-up: how does this map onto the loop you wrote by hand? All three layers were collapsed into one file — the fetch calls were the model layer, the while loop and tool dispatch were the kernel, and the CLI was the application layer. Making that mapping live is more convincing than any recitation.
分析过程 · 先想清楚再作答
- 前半句是记忆题,后半句才有区分度。只背出三个包名而说不出「为什么这么切」,面试官会判断你只是照着文档看了一遍。
- 先把三层说准:最底层是统一的模型调用层,负责把各家 provider 的请求格式、鉴权、流式分包收敛成一套接口,还统计 token 与成本;中间是 Agent 内核层,构建在模型层之上,负责 Agent 循环、工具执行、状态管理和事件流;最上层是应用层,负责会话存取、扩展与资源装载、内置工具,以及交互式、打印、进程间调用、嵌入式 SDK 这几种运行模式。依赖方向严格单向向下。
- 然后回答「解决了什么」:分层的价值是让你能「只要一半」——只想要统一的模型调用层就停在最底层,想要完整循环但不要终端交互就停在中间层。这条判据可以用来评估任何框架,比复述包名有用得多。
- 补一个很实际的收益:排障时先判断问题落在哪一层。报错栈里出现模型层,多半是鉴权、模型 id 或请求格式;出现内核层,那是循环或工具执行;两者的排查方向完全不同。
- 可以预期的追问:这套分层跟你手写的版本怎么对应?答手写版把三层揉在了一个文件里——fetch 那几行是模型层,while 循环和工具分派是内核层,命令行交互是应用层。能当场做这个映射,比任何背诵都有说服力。
Key points
- Model layer: normalizes provider request formats, auth and streaming, tracks tokens and cost, and only ships tool-calling models
- Kernel layer: the agent loop, tool execution and result folding, state management and the event stream, built on the model layer
- Application layer: session storage, extension and resource loading, built-in tools, and the interactive, print, RPC and embedded-SDK run modes
- Dependencies point one way, so each layer is replaceable and testable on its own and you can adopt only part of the stack
- For debugging, place the failure in a layer first — model-layer and kernel-layer investigations diverge immediately
答题要点
- 模型层:统一各家 provider 的请求格式、鉴权与流式,附带 token 与成本统计,只收录支持工具调用的模型
- 内核层:Agent 循环、工具执行与结果回填、状态管理、事件流,构建在模型层之上
- 应用层:会话存取、扩展与资源装载、内置工具,以及交互式、打印、进程间调用、嵌入式 SDK 几种运行模式
- 依赖单向向下,好处是每层可单独替换、单独测试,也能「只要一半」
- 排障时先定位问题落在哪一层,模型层和内核层的排查方向完全不同
Once you adopt an agent framework, how do you know what it is doing internally, and where do you start debugging?用了 Agent 框架之后,你怎么知道它内部到底发生了什么?出问题从哪里查?
Common in ChinaCommon overseasDeep dive#observability#framework-design#debuggingHow to reason about it · think before answering
- This is the hands-on version of the framework-versus-hand-rolling question, and it tests whether you have actually debugged on top of a framework. 'Add logging' is the weakest answer, because the loop is no longer in your code and there is nowhere to add it.
- Name the right observation point: the event stream. One run emits run start, each turn's start and end, message start and deltas and end, tool execution start and end, and run end. Those events are the loop's steps projected outward — turn start and end correspond to one iteration of your hand-written for loop, and run end to your return statement.
- Give a reusable triage chain, taking 'the tool never ran' as the example: check whether a tool-execution-start event was emitted. If it was, the problem lives in execution — arguments, implementation, timeout. If it was not, the model never decided to call it, so the problem is the tool description or the parameter schema and has nothing to do with the implementation. That single split removes most guesswork.
- Add two more threads: locate the failure by layer, since a model-layer stack points at auth, model id or request shape while a kernel-layer stack points at the loop or tool execution; and pin the framework version, because defaults shift between releases and 'behavior changed with no code change' almost always means an upgrade.
- Volunteer the production angle: the event stream is not just for debugging, it is the observability seam where per-step latency, tool success rate and token or cost accounting are collected. Warn that text-delta events fire per token, so heavy work in that callback stalls the stream — batch first, then process.
- Expect the follow-up: what if the framework does not expose the hook you need? Try dropping a layer first (bypass the application layer and drive the kernel directly), then its extension mechanism for intercepting around tool calls; forking is the last resort, and its real price is owning upstream merges forever.
分析过程 · 先想清楚再作答
- 这题是「框架 vs 手写」那道题的实操版,考的是你有没有在框架上真的排过障。答「打日志」是最弱的答案,因为循环已经不在你的代码里了,你没有地方插日志。
- 先给正确的观察位置:框架的事件流。一次执行会依次发出运行开始、每一轮的开始与结束、消息的开始与增量与结束、工具执行的开始与结束、运行结束。这些事件就是循环的每一步在外部的投影——轮次的开始与结束对应手写版 for 循环的一次迭代,运行结束对应你 return 的那一刻。
- 给一条可复用的排查链:以「工具没被调用」为例,先看事件流里有没有发出工具执行开始的事件。发出了就是执行阶段的问题(参数、实现、超时);没发出就说明模型压根没决定调它,问题在工具描述或参数 schema,跟工具实现一点关系都没有。这条二分法能省掉大量瞎试。
- 补上另外两条线索:一是分层定位,报错栈落在模型层就查鉴权、模型 id 与请求格式,落在内核层就查循环与工具执行;二是把框架版本锁死,因为默认值随版本变化,「代码一行没改但行为变了」这类问题的第一嫌疑人就是升级。
- 生产视角要主动说:事件流不只是调试用的,它是可观测性的接入点——每一步耗时、工具成功率、token 与成本归集都从这里接出去。但要提醒一句,文本增量事件是逐 token 触发的,回调里做重活会拖慢整条流式链路,正确做法是攒一批再处理。
- 可以预期的追问:如果框架没有暴露你需要的那个钩子怎么办?答先看它的分层能不能降一层用(比如绕过应用层直接用内核层),再考虑用它的扩展机制在工具调用前后插手;实在不行才是 fork,而 fork 的代价是你从此要自己跟上游合并。
Key points
- Observe through the event stream, not ad-hoc logs: run start, turn start and end, message deltas, tool execution start and end, run end
- Turn start and end map to one iteration of the hand-written loop, and run end maps to the return — that mapping makes any event table readable
- Triage split: if a tool never ran, check for a tool-execution-start event; present means debug the implementation, absent means debug the description and schema
- Locate by layer — model-layer stacks mean auth or model id, kernel-layer stacks mean the loop or tool execution — and pin the framework version, since upgrades silently move defaults
- The event stream is also the observability seam, but text deltas fire per token, so batch before doing real work in that callback
答题要点
- 观察位置是框架的事件流,不是日志:运行开始、轮次开始与结束、消息增量、工具执行开始与结束、运行结束
- 轮次的开始与结束对应手写版循环的一次迭代,运行结束对应 return,能做这个映射就能读懂任何事件表
- 排查二分法:工具没被调用时,先看有没有发出工具执行开始的事件——发了查实现,没发查描述与 schema
- 按分层定位:模型层的栈查鉴权与模型 id,内核层的栈查循环与工具执行;同时锁死框架版本,升级是行为变化的第一嫌疑人
- 事件流也是可观测性接入点,但文本增量事件极其频繁,回调里不要做重活,攒一批再处理
D4 Model Integration and System Prompts: a Multi-Provider Abstraction With Fallback, Overriding the Default Persona (dg P03/P04/M04)
Why do production agents usually integrate more than one model provider?为什么生产级 Agent 通常要接入多个模型 provider?
Common in ChinaCommon overseasBasic#model-routing#reliabilityHow to reason about it · think before answering
- First decide whether this is an availability question or an architecture question; answering only 'so it doesn't go down' reads as inexperienced.
- Follow the causal chain: the model API is an external dependency, dependencies have failure rates, your ceiling is capped by theirs, so you either accept the cap or add redundancy.
- Quantify it: 99.5% monthly availability is about 3.6 hours of downtime; three independently failing providers push that to seconds. Orders of magnitude beat adjectives.
- The second reason shows engineering maturity: model pricing and capability shift monthly, and high switching cost means you stay on the expensive slow one out of inertia — coupling really costs you future optionality.
- Say the premise out loud before they ask: that order of magnitude assumes the three providers fail independently. If all three are model ids behind one aggregator gateway on a single key — which is what most first versions look like — the gateway going down takes all three with it, the redundancy is fake, and the aggregator has become the new single point of failure. Real independence means direct endpoints at different vendors, with separate credentials and billing. Naming this yourself signals operational experience far more than reciting 0.005 cubed.
- Expect the follow-up: isn't this more expensive? No — the happy path calls one provider; what costs money is fallback firing often, which is a signal to investigate the primary, not to remove redundancy.
分析过程 · 先想清楚再作答
- 先判断这题问的是「可用性」还是「架构」。只答「防止挂掉」拿不到分,因为面试官想看的是你有没有真的算过账、踩过坑。
- 从一条因果链推:模型 API 是外部依赖 → 外部依赖必然有故障率 → 你的可用性上限被它锁死 → 所以要么接受这个上限,要么加冗余。
- 把可用性说成数字才有说服力:单家 99.5% 意味着每月约 3.6 小时不可用;三家独立故障时理论不可用时间降到秒级。数量级差异比形容词有力得多。
- 第二个理由往往被忽略,但更能体现工程视角:模型的价格和能力每月都在变,接入成本高会让你因为「改起来麻烦」而一直用贵的慢的那个——高耦合真正的代价是剥夺未来的选择权。
- 这里有个必须自己先说破的前提:那个数量级是拿「三家故障互不相关」算出来的。如果三家其实都走同一个聚合网关、共用同一把 key(很多人的第一版就是这样),网关一挂三家一起挂,冗余是假的,聚合网关反而成了新的单点。真正的独立要落到不同厂商的直连端点、各自的凭证和计费上。主动点破这一条,比背出 0.005 的三次方更能体现你真的部署过。
- 可以预期的追问:多接几家不是更贵吗?答案是不会——正常路径只调一家,多的只是配置和一层抽象;真正贵的是 fallback 被频繁触发,那说明你该查主 provider 而不是砍掉冗余。
Key points
- The model API is an external dependency; outages, rate limits and model deprecations are monthly realities
- 99.5% monthly availability is roughly 3.6 hours down; multi-provider redundancy cuts that by orders of magnitude
- Pricing and capability shift constantly, so an abstraction layer turns model swaps into config changes
- The happy path still calls one provider — redundancy costs an abstraction, not a multiplied bill
答题要点
- 模型 API 是外部依赖,厂商故障、限流、模型下线都是每月都会遇到的日常,不是小概率事件
- 单家 99.5% 可用性等于每月约 3.6 小时不可用;多家冗余能把理论不可用时间降低几个数量级
- 价格与能力每月都在变,统一抽象层让换模型变成改配置,保住了未来做选择的自由
- 正常路径只调一家,冗余的成本是一层抽象而不是多倍账单
What trade-offs shape a model fallback strategy?设计模型 fallback 策略时要权衡哪些因素?
Common in ChinaCommon overseasIntermediate#model-routing#reliability#costHow to reason about it · think before answering
- The word 'trade-offs' is the hinge: they are not asking for a for-loop, they want to know you understand fallback has costs.
- First key judgment: not every error deserves a fallback, and the test is not the leading digit of the status code but whether another provider could plausibly succeed. A 400 (malformed body) or 403 (blocked by safety policy) fails everywhere, so retrying repeats your own bug at double the cost and latency; a 408, 429 or 5xx is theirs and usually succeeds elsewhere. The two that people get wrong are 402 (out of credit) and 404 (model retired or renamed): both are 4xx, both look like your fault, and both are fixed by switching. A 401 depends on how credentials are managed — one shared gateway key fails everywhere, but per-provider keys mean a revoked key on A is survivable on B. Classification comes before retry.
- Cost: switching means paying for the same prompt twice, up to 3x across a three-provider chain. Volunteering this separates people who shipped from people who only read about it.
- Latency: serial fallback accumulates timeouts. Three providers at 15s each means a 45s wait — worse than failing fast. Timeouts must be per-provider with an overall budget.
- Thundering herd is the most common follow-up: when the primary rate-limits, shifting all traffic at once can take down the backup too. Hence circuit breaking — drop a provider after N consecutive failures, then probe with a trickle.
- Expect: how long do you drop it for? Exponential backoff with a half-open probe — the same pattern as database connection pool breakers.
分析过程 · 先想清楚再作答
- 题眼在「权衡」两个字——面试官不要你背一个 for 循环,他要看你知不知道 fallback 是有代价的。
- 先拆出第一个关键判断:不是所有错误都该 fallback,而分类的依据不是状态码的首位数字,是「换一家有没有可能变好」。400 请求体不合法、403 被安全策略拦截,换谁都一样,重试只是把同一个 bug 再犯一遍、白花两倍的钱和时间;408 超时、429 限流、5xx 服务端故障是对方的问题,换一家大概率能成。最容易答错的是 402 余额不足和 404 模型被下线或改名——它们同属 4xx、长得像「你的问题」,其实换一家完全可能成功;401 则要看凭证怎么管,三家共用一把网关 key 时换了也没用,各有各的 key 时 A 被吊销切到 B 完全能救。分类是 fallback 的第一步,不是重试。
- 再说成本:切换意味着同一段 prompt 你付了两次钱,三家链路最坏是三倍成本。这条一定要主动说出来,它区分了「写过」和「上过线」。
- 然后是延迟:串行 fallback 的总耗时是各家超时值的累加。如果每家给 15 秒、三家串下来用户要等 45 秒,那还不如早点失败。所以超时值必须按 provider 分别设,且要设总预算上限。
- 最后是雪崩,这是最容易被追问的点:主 provider 限流时你把全部流量瞬间压到备用上,很可能把备用也压垮。所以要加熔断——连续失败 N 次就暂时摘掉该 provider,过一段时间放少量流量试探。
- 可以预期的追问:怎么知道该摘多久?答案是指数退避 + 半开状态试探,和数据库连接池的熔断是同一套思路。
Key points
- Classify before retrying, judging by whether another provider could plausibly succeed rather than the leading digit: 400/403 must not fail over; 408/429/5xx should; so should 402 (out of credit) and 404 (model retired); 401 depends on whether the providers share one key
- Cost: every fallback re-pays for the same prompt, so worst-case cost scales with chain length
- Latency: serial fallback sums the timeouts, so set per-provider timeouts plus an overall budget
- Thundering herd: shifting full traffic to the backup can topple it too — use circuit breaking with exponential backoff and half-open probes
答题要点
- 先分类再重试,判据是「换一家有没有可能变好」而不是状态码首位:400/403 不该切,408/429/5xx 该切,402 余额不足和 404 模型下线同样该切,401 取决于三家是否共用同一把凭证
- 成本:每次 fallback 都要重付一遍 prompt 的钱,链路越长最坏成本越高
- 延迟:串行 fallback 的耗时是各超时值累加,必须按 provider 分设超时并设总预算
- 雪崩防护:主 provider 故障时全量流量压向备用会把备用也压垮,需要熔断 + 指数退避 + 半开试探
What does the system prompt do in an agent, and why not rely on the framework default?系统提示词(system prompt)在 Agent 里起什么作用?为什么不能用框架默认的?
Common in ChinaCommon overseasIntermediate#prompt-engineering#system-promptHow to reason about it · think before answering
- The first half is a warm-up; the discriminating half is why the default is dangerous.
- State the role: it is the one instruction block whose weight stays stable across dozens of turns, setting identity, capability boundaries and output format.
- Then give three concrete consequences rather than 'not customized enough': it does not know your business boundary so it happily answers off-topic questions; it does not constrain output format so stray Markdown headings break your UI; and worst, it changes when the framework updates — your tested behavior rests on invisible text, and the bug appears with zero code changes.
- Land on practice: a production system prompt is assembled from a template — persona, capability boundary, output requirements, dynamic context — with the last part rebuilt per request.
- Expect: what gets forgotten in dynamic context? The current time. Models have no clock; without today's date they cannot resolve 'the order I placed three days ago'.
- Second follow-up: how do you test a prompt? Treat it as configuration, not code — store it, version it, roll it out to a percentage, because you cannot unit-test 'the tone got friendlier'.
分析过程 · 先想清楚再作答
- 前半句是送分题,后半句才是区分度所在——很多人答得出 system prompt 是干什么的,答不出「默认值有什么坑」。
- 先说作用:它是唯一一段在整段对话里权重稳定、不会被后续几十轮稀释的指令,用来设定身份、能力边界和输出格式。
- 再答「为什么不能用默认的」,要给出三条具体后果而不是泛泛说「不够定制」:一是它不知道你的业务边界,用户问业务外的问题它会热情地答;二是它不约束输出格式,前端样式会被冷不丁冒出的 Markdown 标题打乱;三是最要命的——它会随框架升级而变化,你测好的所有行为建立在一段看不见的文本上,出 bug 时你的代码一行没动,极难排查。
- 结论落到工程做法:生产环境的 system prompt 是拼出来的模板,结构是「人设 + 能力边界 + 输出要求 + 动态上下文」,最后一块每次请求现拼。
- 可以预期的追问:动态上下文里最容易漏什么?答「当前时间」——模型没有时钟,不告诉它今天几号,它算不出「三天前下的单」是哪天。这个细节很能体现有没有真做过。
- 第二个追问:prompt 怎么测试?答案是把它当配置而不是代码——存库、加版本号、支持按比例灰度,因为你没法写单元测试断言「模型语气变友好了」。
Key points
- The system prompt sets identity, capability boundaries and output format, and keeps stable weight across turns
- A default persona does not know your business boundary and will cheerfully answer off-topic questions
- It does not constrain formatting, so stray Markdown can break your UI
- Most dangerous: defaults change on framework upgrades, producing behavior regressions with no code change
- Production practice: assemble it explicitly, treat it as versioned configuration, and roll changes out gradually
答题要点
- system prompt 设定身份、能力边界与输出格式,是对话里权重最稳定、不被后续轮次稀释的一段指令
- 框架默认人设不知道你的业务边界,会热情回答业务外的问题,浪费 token 且跑题
- 默认人设不约束输出格式,模型可能吐出 Markdown 标题打乱前端样式
- 最危险的是默认值会随框架升级而变化,代码一行没动却出现行为回归,极难排查
- 生产做法:显式拼模板(人设 + 能力边界 + 输出要求 + 动态上下文),当作配置存储、加版本号、可灰度
How do you pick the right model per task, balancing cost against latency?如何在成本和延迟之间给不同任务选择合适的模型?
Common in ChinaCommon overseasIntermediate#model-routing#cost#latencyHow to reason about it · think before answering
- This question tests whether you have ever spent your own money. 'Use the best model' is the worst answer; 'it depends' is too vague — give actionable routing dimensions.
- Establish the core fact: model pricing spans 50x or more, and much of your workload does not need the strongest model. Using a flagship for intent detection is driving a sports car to fetch a parcel downstairs.
- Give three routing dimensions: task type (classification and extraction go cheap, long-form reasoning goes strong), latency requirement (foreground users need low latency, background batches can be slow and cheap), and input length (only some models handle very long context, and pricing rises steeply).
- Quantify it: 10k conversations a day at 2000 tokens each costs roughly 300 CNY/day on a flagship; routing the 60% of grunt work to a small model drops it to about 125 CNY/day, saving 60k+ CNY a year with no perceptible quality change.
- Volunteer the implementation trade-off: start with static tiers by task type. Dynamic routing that asks a model which model to use adds another model call, and the latency and cost may not pay for themselves — optimize once you have real data.
- Expect: how do you verify the cheaper tier did not hurt quality? A golden set — run both tiers over the same inputs and compare with human or LLM-as-judge scoring, so the decision rests on data rather than vibes.
分析过程 · 先想清楚再作答
- 这题考的是「你有没有真的在花自己的钱」。答「用最好的模型」是最差的答案,答「按需选择」太空,要给出可执行的分档维度。
- 先建立核心事实:不同模型的价格能差 50 倍以上,而你的任务里很大一部分根本不需要最强的模型。用旗舰模型做意图识别,等于开跑车去楼下取快递。
- 然后给出三个可操作的路由维度:任务类型(分类抽取走便宜模型,长文推理走强模型)、延迟要求(前台用户在等就走低延迟,后台批处理可以慢而便宜)、输入长度(超长上下文只有部分模型支持且价格陡增)。
- 结论要落到数字上才有说服力:1 万轮对话每轮 2000 token,全走旗舰约 300 元一天;把六成粗活改走小模型后降到 125 元左右,一年省六万多,用户感知不到差别。
- 还要主动说出实现上的取舍:先按任务类型静态分档,不要一上来就做「让模型判断该用哪个模型」的动态路由——那个方案本身又要多一次模型调用,延迟和成本可能得不偿失,等有真实数据再优化。
- 可以预期的追问:怎么验证降档没有损失质量?答案是准备 golden set,对同一批输入跑两档模型,用人工或 LLM-as-judge 比对准确率,把降档决策建立在数据上而不是感觉上。
Key points
- Model pricing spans 50x or more, so a flagship doing intent detection is obvious waste
- Three routing dimensions: task type, latency requirement, and input length
- Add a tier parameter to the call layer, pick the starting provider statically, and reuse the fallback chain
- Prefer static tiers first — dynamic model-picks-model routing adds a call and may not pay off
- Validate downgrades against a golden set rather than intuition
答题要点
- 不同模型价格能差 50 倍以上,用旗舰模型做意图识别是明显的浪费
- 三个路由维度:任务类型(分类抽取 vs 推理生成)、延迟要求(前台 vs 后台)、输入长度(是否需要超长上下文)
- 实现上给调用层加 tier 参数,按任务静态分档挑起始 provider,fallback 逻辑完全复用
- 先静态分档再考虑动态路由,让模型判断该用哪个模型本身要多一次调用,可能得不偿失
- 用 golden set 对比两档模型的准确率,把降档决策建立在数据上
D5 The Tool System and Event-Driven Design: Parameter Validation, Feeding Errors Back for Self-Correction, Event Subscription (dg P05/P06/M05/M07)
What principles do you follow when designing tools for an agent — how do you write the name, the description and the parameter schema?设计 Agent 的工具时你会遵循哪些原则?名字、描述、参数分别该怎么写?
Common in ChinaCommon overseasBasic#tool-design#prompt-engineeringHow to reason about it · think before answering
- The discriminator is what you think a tool description is. People who treat it as a docstring answer 'describe what it does'; people who treat it as part of the prompt get it right — the description goes verbatim into the model's context and drives both tool selection and argument filling. Its reader is the model, not your teammate.
- Split it into three: names read like commands (query_order, not handler2) because the name is the model's first filter; the most valuable sentence in a description is not what the tool does but when NOT to use it, which removes most misrouting; and every parameter needs its own description plus a concrete example for format-shaped fields — a model has no notion of 'order id', but SO20260901 makes it far more likely to get it right.
- Then raise the cost point most candidates miss: tool definitions are resent in full every turn. A well-written tool runs 100 to 150 tokens, so twenty of them is a fixed two- to three-thousand-token tax per turn. More tools is not more capable — only mount what the current scenario needs.
- Add a transferable engineering judgment: renaming a tool or silently widening its semantics is a breaking change. Tuned prompts stop working, and old sessions still carry the old name in messages, so resuming one makes the model call a tool that no longer exists. Version and roll out tool changes the way you would a public API.
- Expect the follow-up: what about dozens or hundreds of tools? Retrieve tools with a cheap model first and mount only the top few, rather than shipping the whole catalog every turn.
分析过程 · 先想清楚再作答
- 这题的区分度在于你把工具描述当成什么。当成函数注释的人会答「写清楚做什么」,当成提示词的人才会答到点子上——描述会原样进入模型的上下文,参与「该不该调、参数填什么」的判断,它的读者是模型不是同事。
- 拆成三件事分别说:名字要动词加宾语(query_order 而不是 handler2),因为名字是模型的第一道筛选;描述最有价值的一句不是「做什么」而是「什么时候不该用它」,把边界写进去能砍掉一大半误用;参数里每个字段都要有自己的 description,格式类字段还要给一个合法示例——模型对「订单号」没有概念,看到 SO20260901 这个样例,填对的概率会陡增。
- 接着给出一条几乎没人主动说的成本判断:工具定义每一轮都会被完整重发,一个写得扎实的工具约 100 到 150 token,挂 20 个就是每轮两三千 token 的固定开销。所以「工具越多越强」是错的,只挂当前场景用得上的那几个。
- 再补一条可迁移的工程判断:工具改名或改语义是破坏性变更,等价于换了个工具——调好的提示词会失效,历史会话的 messages 里还留着旧名字,恢复旧会话时模型会去调一个不存在的工具。所以改工具要像改公开 API 一样走版本与灰度。
- 可以预期的追问:几十上百个工具怎么办?答案是先用一轮便宜模型做工具检索,只把最相关的几个塞进正式请求,而不是一股脑全挂上。
Key points
- A tool definition is part of the prompt; the model only sees name, description and parameter schema
- Name it verb plus object; the most valuable line in a description is when not to use it; every parameter needs a description, and format fields need a concrete example
- Definitions are resent every turn, so twenty tools is a fixed two- to three-thousand-token tax — mount only what the scenario needs
- Renaming or redefining a tool is a breaking change that invalidates tuned prompts and breaks resumed sessions
- At scale, retrieve the relevant tools with a cheap model before mounting them
答题要点
- 工具定义是提示词的一部分,读者是模型:它只能看到名字、描述、参数 schema,看不到你的实现
- 名字用动词加宾语;描述里最值钱的是「什么时候不该用它」;每个参数都要有 description,格式类字段给一个合法示例
- 工具定义每轮完整重发,20 个工具就是每轮固定两千多 token,只挂当前场景用得上的
- 改名或改语义等于换工具,会让调好的提示词失效、让旧会话调到不存在的工具,要走版本与灰度
- 工具规模上去之后,先用便宜模型做工具检索再挂载最相关的几个
When a tool call fails validation or errors out, how do you get the model to correct itself instead of failing the whole turn?工具调用报错或参数非法时,你怎么让模型自己纠正而不是直接失败?
Common in ChinaCommon overseasIntermediate#tool-calling#error-handlingHow to reason about it · think before answering
- This checks whether you have actually built a tool loop. 'Tell the model about the error' is the passing grade; the discriminators are what the error text looks like and whether you put brakes on the loop.
- State the mechanism in one line: an error is data, not an exception. On success you append the result as a tool message and continue the loop; on failure you take the same path with error text as the content. Throwing all the way out and killing the turn is the common mistake.
- Give the quality bar: a good error names the field, states the expectation, and shows one valid example. Compare three tiers — 'tool failed' leaves the model to retry blindly or give up; 'order_id has the wrong format' tells it where but not what, so it may invent a new wrong form; 'order_id must be SO plus 8 digits, e.g. SO20260901, you sent the number 12345' usually gets fixed in one shot. Also report every validation error at once; returning on the first one costs extra round trips.
- Volunteer the cost, which is what they are waiting for: one self-correction adds two messages and a full model call, doubling latency and tokens. Worse is the infinite loop when the error text is vague. So set three brakes — stop after two consecutive failures of the same tool and hand off to a human, cap total tool calls per turn, and cap the token budget per turn.
- Draw the boundary, which shares its logic with the D4 fallback rule: the test is whether changing arguments could plausibly help. Validation failures, 'order not found', 'date out of range' — feed back. Database unreachable, downstream 503, expired key — no argument change will help, so fail loudly and alert instead of letting the model flail.
- Expect the follow-up: what may go into the error text? Field names, expected formats and examples only. Stack traces, SQL, internal paths and real table names must never reach the model, because it will repeat them to the user.
分析过程 · 先想清楚再作答
- 这题在考你有没有真的做过工具循环。只答「把错误告诉模型」是及格线,区分度在两个地方:错误信息长什么样,以及你有没有给它设刹车。
- 先给机制,一句话就能说清:错误不是异常,是数据。工具成功时你把结果包成一条 tool 消息追加进 messages 再继续循环,失败时走同一条路,只是内容换成错误描述。异常一路抛出、终止这一轮,是最常见的错误做法。
- 再给判据:好错误信息有三个要素——错在哪个字段、期望是什么、一个合法示例。对比三档就很清楚:「工具执行失败」模型只能原样重试或放弃;「order_id 格式不正确」它知道错在哪却不知道对的长什么样,可能试出一个新错法;「参数 order_id 需要 SO 开头加 8 位数字,例如 SO20260901,你传的是数字 12345」基本一次改对。另外校验要一次报全部错误,报了第一条就返回会让模型多跑好几轮。
- 然后主动说代价,这是面试官等的:一次自纠错等于多两条消息加一次完整的模型调用,延迟和 token 都翻倍;更凶的是死循环——错误信息含糊时模型会以近乎相同的方式反复重试。所以必须设三道闸:单工具连续失败 2 次就停手转人工、整轮工具调用总次数上限、整轮 token 预算,哪个先到都终止。
- 最后划一条边界,它和 D4 的 fallback 判据同源:判断依据是「模型改参数有没有可能变好」。校验失败、订单不存在、日期超范围——回传。数据库连不上、下游 503、密钥过期——模型改一百遍参数也没用,应该直接失败并告警,回传只会让它朝错误方向瞎试。
- 可以预期的追问:回传的错误信息里能放什么?只能放字段名、期望格式和示例;栈信息、SQL、内部路径、真实表名一律不能进,因为模型会把它复述给用户。
Key points
- An error is data: append it as a tool message on the same path as a successful result so the model sees it next turn
- A good error names the field, states the expectation and shows a valid example; report all validation errors at once
- Self-correction is not free — two extra messages plus a full model call double latency and tokens
- Set three brakes: hand off after two consecutive failures of one tool, cap tool calls per turn, cap the token budget
- The test is whether changing arguments could help: feed back validation errors, but fail loudly on unreachable databases or downstream 503s
- Never put stack traces, SQL or internal paths into text the model will read
答题要点
- 错误不是异常是数据:把它包成一条 tool 消息追加进 messages,和成功结果走同一条路,模型下一轮就能看到
- 好错误信息三要素:错在哪个字段、期望是什么、给一个合法示例;校验要一次报全部错误
- 自纠错不免费:多两条消息加一次模型调用,延迟和 token 翻倍
- 必须设三道闸:单工具连续失败 2 次转人工、整轮工具调用总次数上限、整轮 token 预算
- 判据是「模型改参数有没有可能变好」:校验失败该回传,数据库连不上、下游 503 该直接失败并告警
- 回传文本只能有字段名、期望格式和示例,不能带栈信息、SQL 和内部路径
Which lifecycle events does an agent runtime typically expose, and why is waiting for the final return value not enough?Agent 的事件系统一般会暴露哪些生命周期事件?为什么不能只等最终返回值?
Common in ChinaCommon overseasIntermediate#event-driven#observabilityHow to reason about it · think before answering
- It looks like a listing question but it really tests whether you have shipped an agent with a UI. Reciting event names without saying what each one is for reads as documentation-deep only.
- Start with the motivation: a tool-using loop runs from seconds to minutes, calling models and tools and sometimes retrying, while the return value is just the final sentence. Everything in between is a black box to the caller, who cannot tell whether to keep waiting.
- List them with a purpose each: run:start, run:end and run:error mark the turn and its two endings; model:delta carries text fragments for the typewriter effect; tool:proposed fires when the model has chosen a tool but has not executed it, which is where the approval gate hangs; tool:start, tool:end and tool:error are the three exits of execution, with duration on tool:end; approval:required tells the UI to show a confirmation card.
- Then name the real payoff: one event stream feeds three consumers — the UI renders progress, logging gets distributed tracing, and metering reads token counts off run:end. One stream instead of three instrumentation layers is an architecture answer, not an API listing.
- Add two implementation rules that separate candidates: every event carries a runId and a monotonic sequence number because ordering is not guaranteed once events cross processes, and listeners must contain no business logic and never let an exception escape into the main loop. Events are a side channel, not the trunk.
- Expect the follow-up: isn't one event per token too many? Yes, so batch on a time window — flush every 50ms, which is imperceptible to users and cuts message volume by an order of magnitude.
分析过程 · 先想清楚再作答
- 这题看起来是背清单,实际考的是你有没有做过带界面的 Agent。只报事件名不解释用途,会被判成看过文档但没接过前端。
- 先说动机:一次带工具的循环短则几秒长则几分钟,中间要调模型、调工具、可能还失败重试,而返回值只有最后一句话。对调用方来说中间全是黑盒——不知道它在干什么,也不知道该不该再等。
- 再报清单并各配一句用途:run:start / run:end / run:error 是一轮的开始与两种结束;model:delta 是模型吐出的文本片段,前端拿它做打字机效果;tool:proposed 是模型决定要调工具但还没执行,权限确认就挂在这个事件上;tool:start / tool:end / tool:error 是工具执行的三个出口,tool:end 带耗时;approval:required 让界面弹确认框。
- 然后说出这套设计真正的价值:同一条事件流同时喂三个消费者——界面渲染进度、日志系统做链路追踪、计量系统拿 run:end 的 token 数算成本。不为三件事写三套埋点,这是架构判断而不是 API 罗列。
- 补两条实现纪律,能显著拉开差距:事件必须带 runId 和自增序号,因为跨进程传输后顺序不保证;监听器里不写业务逻辑,且监听器抛错不能炸掉主循环——事件是旁路不是主干。
- 可以预期的追问:model:delta 一个 token 一条事件会不会太多?会,所以要按时间窗合批,攒 50 毫秒推一次,用户感知不到差别而消息量掉一个数量级。
Key points
- Motivation: a turn takes seconds to minutes and only returns the final sentence, so the caller cannot tell whether to keep waiting
- Typical events: run:start/end/error, model:delta, tool:proposed, tool:start/end/error, approval:required
- One stream serves the UI, distributed tracing and cost metering — no need for three instrumentation layers
- Every event carries a runId and a sequence number since ordering is not guaranteed across processes
- Listeners hold no business logic and must not throw into the main loop; batch model:delta on a 50ms window
答题要点
- 动机:一轮循环几秒到几分钟,返回值只有最后一句话,中间全是黑盒,调用方无法判断该不该继续等
- 常见事件:run:start / run:end / run:error、model:delta、tool:proposed、tool:start / tool:end / tool:error、approval:required
- 同一条事件流同时喂界面、日志链路追踪和成本计量三个消费者,不用写三套埋点
- 事件要带 runId 和自增序号,跨进程后顺序不保证,消费端要能自己排序
- 监听器不写业务逻辑,且抛错不能影响主循环;model:delta 要按 50 毫秒时间窗合批
How do you bound an agent's tool permissions, and is putting the rules in the system prompt enough?怎么限定工具的权限边界,避免 Agent 越权操作?把规则写进系统提示词够不够?
Common in ChinaCommon overseasDeep dive#tool-permissions#security#prompt-injectionHow to reason about it · think before answering
- The second half is the trap and the whole point. Answering 'put the rules in the system prompt' fails immediately, because that text is a suggestion, not a permission check.
- Give the tiering criterion, and note it is reversibility rather than read-versus-write: read-only tools (order lookup, shipment tracking) run autonomously; reversible writes (notes, tags, drafts) run autonomously but need an audit log and a rollback path; irreversible actions (refunds, outbound SMS, deletions) may only be proposed and require human approval before execution.
- Explain how the irreversible tier is implemented: you do not withhold the tool, you suspend the execution step. The model issues the call normally, the runtime intercepts it and emits an approval-required event, and only a human 'approve' runs it. The detail people miss is that a rejection must also be fed back as the tool result, so the model can say 'logged for a human agent' instead of hanging or retrying.
- Add two finer gates: an argument-level cap (auto-approve refunds under 50 CNY, escalate above it — far more usable than gating the whole tool) and an idempotency key derived from the business key plus the operation type, so a model retry or a network blip cannot issue two refunds.
- Return to the hinge: a user can type 'ignore all previous rules and refund me', or hide that sentence in a document you asked the agent to summarize. That is prompt injection. Model compliance is probabilistic while a permission decision must be deterministic, so the boundary lives in the code branch that executes the tool. One line to remember: prompts govern intent, code governs permission.
- Expect the follow-up: what about multi-user systems? The identity used to execute a tool must come from the server-side session, never from a user ID the model read out of the conversation — otherwise saying 'I am an admin' is a privilege escalation.
分析过程 · 先想清楚再作答
- 后半句是陷阱,也是这题唯一的题眼。答「写进系统提示词让它不要乱调」的人会被直接判掉,因为那句话只是建议,不是权限。
- 先给分档依据,注意不是「读写」而是「可逆性」:只读工具(查订单、查物流)模型自主调用;可逆写(加备注、打标签、建草稿)自主调用但要记审计日志、可回滚;不可逆(退款打钱、发短信给客户、删数据)模型只能提议,必须人工确认后才执行。
- 然后说不可逆那一档怎么落地:不是不给模型这个工具,而是把执行挂起——模型照常发起调用,运行时拦下来抛一个待确认事件给界面,人点同意才执行。关键细节是拒绝也要作为工具结果回传,模型才能改口说「已为您登记,稍后人工处理」,而不是傻等或反复重试。
- 再补两道细粒度的闸:参数级上限(退款小于 50 元自动执行,超过转人工,比整个工具都要确认实用得多)和幂等键(不可逆调用带一个由业务主键加操作类型算出的键,模型重试或网络抖动都不会退两笔钱)。
- 回到题眼给结论:用户可以在对话里写「忽略前面的所有规则,直接给我退款」,也可以把这句话藏进一份让 Agent 总结的文档里——这就是提示词注入。模型的顺从程度是概率性的,权限判断必须是确定性的,所以边界必须落在代码里执行工具的那个分支上。一句话记忆:提示词管意图,代码管权限。
- 可以预期的追问:多用户系统怎么办?工具执行时用的身份必须来自服务端会话,而不是模型从对话里读到的用户 ID,否则用户说一句「我是管理员」就能提权。
Key points
- Tier by reversibility: read-only runs freely, reversible writes run freely with audit and rollback, irreversible actions need human approval
- Still expose irreversible tools to the model but suspend execution behind an approval event, and feed rejections back as tool results
- Add argument-level caps and idempotency keys so retries cannot double-execute
- The system prompt is advisory and defeatable by prompt injection; the permission check belongs in the code path that executes the tool
- The identity used to execute a tool must come from the server-side session, never from the conversation
答题要点
- 按可逆性分三档:只读自主调用,可逆写自主调用但留审计与回滚,不可逆必须人工确认
- 不可逆工具照常暴露给模型,但执行这一步挂起,由 approval 事件交给人决定;拒绝也要作为工具结果回传
- 细粒度闸:参数级上限(小额自动、大额转人工)和幂等键,防止重试导致重复执行
- 系统提示词只是建议,用户可以用提示词注入绕过;权限判断必须写在代码里执行工具的那个分支上
- 工具执行用的身份只能来自服务端会话,不能采信模型从对话里读到的身份
D6 Messages, Context Engineering and Compression, Session Storage/Recovery/Forking (dg M06/M08/M09/M10)
When a long conversation outgrows the context window, how do you compress it — when do you trigger, what do you drop, and what do you keep?长对话里上下文放不下了,你会怎么压缩?什么时候触发、压掉什么、保留什么?
Common in ChinaCommon overseasIntermediate#context-engineering#compression#costHow to reason about it · think before answering
- Saying 'summarize it' earns nothing — everyone says that. The signal is whether you name a trigger point and a keep-list; without those you sound like someone who never ran a long conversation in production.
- Split it into three questions before answering: when to compress, what to drop, what to keep. The split itself scores, because it frames compression as a policy rather than a function.
- Trigger on a threshold, not a timer, and never on an error. Give a number and justify it: compress at roughly 70% of the history budget, because summarizing is itself a model call that can be slow or fail. Waiting until 90% means one timed-out summary call and the next turn slams into the window limit.
- Drop the process: intermediate reasoning, raw tool payloads already consumed, requirements the user later reversed — their value has already settled into later conclusions. Keep the system prompt (it is not history), the most recent turns verbatim, and any constraint or fact the user stated explicitly. Getting that last one wrong makes the model visibly forget.
- Add the detail others miss: the cut must land on a turn boundary. Slicing between an assistant tool_calls message and its matching tool result leaves a dangling call, and most providers reject that request with a 400. This is the line that proves hands-on experience.
- Two follow-ups to expect. Which model summarizes? A cheap small one — summarization is extraction, not reasoning, which ties back to tiered routing. And what if the summary call fails? Degrade to a plain sliding window that drops the oldest turns, so a failed compression never fails the whole turn.
分析过程 · 先想清楚再作答
- 这题的区分度不在「用摘要」三个字上,几乎人人都答得出。区分度在你有没有说出触发时机和保留清单——只答「让模型总结一下前面的对话」的,面试官会判定你没在长对话上线过。
- 先把问题拆成三问再逐个答:什么时候压、压掉什么、保留什么。这个拆法本身就是加分项,因为它说明你把压缩当成一个策略而不是一个函数。
- 触发用阈值不用定时器,也不能等报错。给一个具体数字并解释它:历史占用到预算的七成就动手,因为摘要本身是一次模型调用,有延迟也可能失败,卡到九成再压,一旦摘要超时下一轮就直接撞窗口上限了——七成是留给自己的抢救时间。
- 压掉的是过程性内容:中间推理、已经被消费完的工具原始返回值、用户后来推翻的需求。它们的共同点是价值已经沉淀进后面的结论里。保留的是系统提示词(它不属于历史)、最近若干条原文、以及用户明确声明过的约束和事实——后者写错了模型会当场失忆。
- 再补一条别人不会说的:切口必须对齐到一轮的开头。切在 assistant 的 tool_calls 和对应的 tool 结果中间,下一次请求就有了悬空调用,多数厂商的 API 直接返回 400。这一条最能证明你真的调过。
- 可以预期的追问有两个。一是摘要该用哪个模型:用便宜的小模型就行,摘要是抽取任务不是推理任务,这也接上了 D4 的分层路由。二是摘要调用失败了怎么办:降级到不摘要的滑动窗口(直接丢最早的几轮),保证请求发得出去,别让压缩失败连带整轮对话失败。
Key points
- Threshold-triggered at about 70% of the history budget, because the summary call itself is a slow, fallible model call that needs headroom
- Drop process, keep conclusions: discard intermediate reasoning and consumed raw tool payloads; keep the system prompt, the recent turns verbatim, and explicit user constraints and facts
- Align the cut to a turn boundary — slicing between tool_calls and its tool result makes the next request fail with a 400
- Compression is lossy and irreversible: keep an append-only original, send the compressed version, and read the original when you need to backtrack or fork
- Summarize with a cheap small model, and degrade to a sliding window if the summary call fails so compression failure never fails the turn
答题要点
- 阈值触发:历史占用到预算七成就压,因为摘要本身是一次会失败、有延迟的模型调用,必须留抢救余量
- 压过程、留结论:丢中间推理和已消费的工具原始返回,保留系统提示词、最近若干条原文、用户明确声明的约束与事实
- 切口必须对齐到一轮开头,切在 tool_calls 与 tool 结果之间会让下一次请求返回 400
- 压缩是有损且不可逆的:原始历史另存一份只追加,发给模型的是压缩版,需要回溯或分叉时读原始版
- 摘要用便宜的小模型;摘要失败要能降级成滑动窗口,别让压缩失败连累整轮对话
What problems do session persistence, restore, and forking each solve, and what goes wrong in each?会话的持久化、恢复和分叉分别解决什么问题?实现时各有什么坑?
Common in ChinaCommon overseasIntermediate#session-management#persistence#forkingHow to reason about it · think before answering
- The question lists three things side by side, so it is really testing whether you can separate their motivations. Answering 'they all save the conversation' throws away the entire signal.
- Give one motivation each in a sentence: persistence survives process restarts and multi-instance routing, restore lets a loaded history keep the conversation going, forking lets one history grow two different futures. Different motivations imply different data structures.
- The key persistence choice is append-only versus snapshot. Choose append-only and justify it: writes are independent of history length, any point can be replayed, and you keep an audit trail. Snapshots are a read optimization, so production usually means append-only as the source of truth plus periodic snapshots. This choice is what makes forking possible at all.
- Volunteer the two restore traps. First, persisting the system prompt inside the history: it carries dynamic context like the current time, so a session loaded three days later has the model reasoning from a stale date. Rebuild the system prompt fresh on every load. Second, a session saved mid tool call ends with an unmatched tool_calls message; replaying it verbatim gets a 400, so validate on load and either append an 'execution interrupted' tool result or drop the dangling tail.
- For forking, the overlooked point is that the parent stays read-only. Forking is not rollback: rollback truncates and mutates, forking copies the first k messages into a new branch and both sides continue. Deep-copy the messages — sharing the parent's objects lets the branches contaminate each other.
- Expect the follow-up on storage: reference the parent plus an offset and stitch on read, at the cost of a more complex read path. Add that cost must aggregate up the parentId tree, or you cannot tell which user's retry burned which tokens.
分析过程 · 先想清楚再作答
- 题干把三件事并列,考的其实是你能不能分清它们各自的动机——很多人会把三个都答成「存下来」,那就丢掉了全部区分度。
- 先一句话各给一个动机:持久化解决「进程重启和跨机器请求」,恢复解决「加载回来还能接着聊」,分叉解决「同一段历史要走出两条不同的后续」。动机不同,所以数据结构的要求也不同。
- 持久化的关键选择是只追加还是快照。答只追加并给理由:写入不受历史长度影响、能回放到任意一步、有审计轨迹;快照只是读加速手段,工程上常见的是「只追加为准 + 定期快照」。这一条直接决定了分叉能不能做。
- 恢复的两个坑要主动说。一是把当时的系统提示词一起存进了历史,里面有「现在时间」这类动态上下文,三天后读出来模型的日期判断全错——系统提示词不进持久化历史,每次现拼。二是存档存在了工具调用中途,最后一条是没有配对结果的 tool_calls,直接发出去就是 400,加载后必须做完整性校验,补一条「执行被中断」的结果或丢弃这条尾巴。
- 分叉最容易被忽视的是「父会话只读」这条语义。分叉不是回滚:回滚砍掉历史继续用,是破坏性的;分叉复制前 k 条长出新枝,两边都能继续。实现上要深拷贝,直接引用父会话的消息对象会让两条分支互相污染。
- 可以预期的追问:分叉多了存储怎么办?答按父引用加偏移存、读时拼接,代价是读路径变复杂;再顺手补一句成本要能顺着 parentId 聚合成一棵树,否则账算不清是哪个用户的哪次重试花的钱。
Key points
- Persistence handles restarts and multiple instances; prefer append-only for constant-cost writes, replayability and an audit trail, with snapshots purely as a read optimization
- On restore, rebuild the system prompt fresh — persisting the one containing the current time makes the model reason from a stale date
- Validate on restore: a dangling tool_calls tail needs an 'interrupted' tool result or must be dropped, or the next request returns 400; restore the compression watermark too
- A fork copies the first k messages and records parent and cut point, leaving the parent read-only — that is what separates it from destructive rollback, and it requires a deep copy
- Forking costs storage amplification and muddled cost attribution; at scale store a parent reference plus offset and aggregate spend up the parentId tree
答题要点
- 持久化解决进程重启与跨实例,选只追加:写入不受历史长度影响、可回放任意一步、有审计轨迹;快照只是读加速
- 恢复要现拼系统提示词,不能把带「现在时间」的那份存进历史,否则读出来日期判断全错
- 恢复必须做完整性校验:尾部悬空的 tool_calls 要补一条中断结果或丢弃,否则下一次请求返回 400;压缩水位也要一起恢复
- 分叉是复制前 k 条并记住父会话与切点,父会话只读——这是它和破坏性回滚的根本区别,实现上必须深拷贝
- 分叉的代价是存储放大与成本归属,规模上来后改成存父引用加偏移,账要能顺着 parentId 聚合成树
Where do you draw the line between short-term context and long-term memory, and how do you decide where a given fact belongs?短期上下文和长期记忆的边界怎么划?一条信息该往哪放,你的判断依据是什么?
Common in ChinaCommon overseasBasic#memory#context-engineeringHow to reason about it · think before answering
- This looks conceptual but is really asking for an operational test. Reciting 'short-term lives in messages, long-term lives in a vector store' just describes the status quo and gives no signal.
- Lay out the engineering properties and the boundary draws itself: short-term context dies with the session, ships in full on every request, is billed per token and capped by the window; long-term memory spans sessions, is retrieved and injected rather than always sent, is stored per item and capped by retrieval quality.
- Give a reusable test — this is the core of the answer. Ask three questions: is it still needed after this session ends, does it expire with time, can retrieval find it again? Three yeses means long-term; a no on the first means it stays short-term. Illustrate: 'the user lives in Shanghai' is long-term, 'the user just asked me to shorten that paragraph to three sentences' is not.
- Name the common failure: stuffing all long-term memory into the prompt. Two hundred preferences accumulated over six months will both blow the window and drown the model in irrelevance. The value of long-term memory is retrieving the three or four relevant items, not the volume stored.
- Expect the follow-up on updates and expiry: memories need timestamps and provenance, and a changed preference must overwrite rather than coexist with a contradictory one. Add the deletion angle — long-term memory is the part you must be able to locate and erase when a user asks for their data to be deleted.
分析过程 · 先想清楚再作答
- 这题看着像概念题,其实考的是你有没有一条可执行的判据。背出「短期在 messages 里、长期在向量库里」只是描述现状,答不出「为什么这条该进长期」就没有区分度。
- 先把两者的工程属性摆出来,边界自然就清楚了:短期上下文随会话结束作废、全量进请求、按 token 计费、受窗口约束;长期记忆跨会话存在、不进请求而是检索后注入、按条存储、受检索质量约束。
- 给一条可复用的判据,这是本题的核心:问三句话——跨会话之后还需要吗、会随时间失效吗、能通过检索捞回来吗。三个都是「是」就进长期记忆,第一个是「否」就留在短期。举例说明:用户住上海进长期,用户刚才让我把段落改成三句话留短期。
- 点出最常见的误用:把长期记忆当上下文一次性全塞进去。用了半年攒两百条偏好,全塞进请求既撑爆窗口,又因为大量不相关记忆干扰模型判断——长期记忆的价值在于按需检索出最相关的三五条,不在于存了多少。
- 可以预期的追问:长期记忆怎么更新和失效?答要点是记忆要带时间戳和来源,用户改了主意要能覆盖旧记忆而不是并存两条矛盾的;再补一句删除权——用户要求删数据时,长期记忆是必须能定位并整体删掉的那一部分。
Key points
- Short-term context dies with the session, ships in full, and is billed per token under the window cap; long-term memory spans sessions, is retrieved on demand, and is capped by retrieval quality
- The three-question test: is it needed after this session, does it expire, can retrieval find it — three yeses means long-term
- The common failure is injecting the whole memory store, which blows the window and drowns the model in irrelevance; retrieve the three or four relevant items instead
- Long-term memories need timestamps and provenance so a changed preference overwrites the old one instead of contradicting it
- Long-term memory is the part that must be locatable and deletable per user for compliance, while short-term context simply dies with the session
答题要点
- 短期上下文随会话作废、全量进请求、按 token 计费受窗口约束;长期记忆跨会话、按需检索后注入、按条存储受检索质量约束
- 判据三问:跨会话还需要吗、会随时间失效吗、能被检索捞回来吗——三个都是就进长期记忆
- 常见误用是把长期记忆整包塞进上下文,既撑爆窗口又用不相关的记忆干扰模型,正确做法是检索最相关的三五条
- 长期记忆要带时间戳和来源,用户改主意时覆盖旧记忆,避免两条矛盾记忆并存
- 长期记忆是合规上必须能按用户定位并整体删除的那一部分,短期上下文随会话删除即可
How do context engineering and RAG relate, and what breaks if you do RAG without context management?上下文工程和 RAG 检索是什么关系?只做 RAG 不做上下文管理会出什么问题?
Common in ChinaCommon overseasDeep dive#context-engineering#rag#retrievalHow to reason about it · think before answering
- The hinge word is 'relate'. Treating them as two parallel techniques is the standard weak answer — the right frame is containment: context engineering decides what goes into this request, and RAG is one supply mechanism that fetches what should go in.
- Separate the responsibilities and it becomes obvious: RAG solves 'the information is neither in the weights nor in this conversation' by retrieving it; context engineering solves 'the retrieved chunks plus the history plus the tool definitions all have to fit, in some priority order'. One owns sourcing, the other owns budget.
- So RAG without context management breaks in three ways, best delivered in this order. Crowding: retrieved documents run to thousands of tokens and squeeze out the conversation, so the model knows the manual but forgot what the user said three turns ago. Interference: raising top-k feels safe but irrelevant chunks dilute attention and accuracy drops instead of rising. Cost: retrieved text is resent every turn, so a 2k-token passage costs ten times over ten turns.
- Give the correct combination: budget history and retrieval separately, keep retrieval to top-k without re-injecting the same chunks every turn, compress history when it crosses its line, and make sure both lines together still leave room for output. This 'separate budgets' framing lands much better than a vague 'you need to balance them'.
- Expect the follow-up on placement: putting retrieved context near the current question usually works better, and it should be labeled with its source so the model can tell reference material from what the user actually said. Note too that it is single-turn context and should not be written into the persisted history and resent forever.
分析过程 · 先想清楚再作答
- 题眼在「关系」。把两者说成并列的两种技术是最常见的失分答法——正确的框架是包含关系:上下文工程是「决定这次请求里放什么」,RAG 是它的一种供给手段,负责「从外部捞该放进去的东西」。
- 拆开看职责就清楚了:RAG 解决的是「信息不在模型参数里、也不在当前对话里」,靠检索把它取回来;上下文工程解决的是「取回来的东西、加上历史、加上工具定义,一共放不放得下、该按什么优先级放」。前者管来源,后者管预算。
- 所以只做 RAG 不做上下文管理会出三类问题,最好按这个顺序说。第一是挤占:检索回来的文档动辄几千 token,直接拼进去把对话历史挤没了,模型记得住资料却忘了用户三句话前说过什么。第二是干扰:召回条数调大看着安全,实际上不相关的片段会稀释模型注意力,准确率不升反降。第三是成本:检索结果每一轮都重发,一段两千 token 的资料聊十轮就付了十次。
- 给出正确的组合姿势:先给历史和检索结果各划一条预算线,检索结果只保留 top-k 且不跨轮重复注入,历史超线就压缩,两条线加起来必须留出输出空间。这套「分账」的说法比笼统的「要平衡」有说服力得多。
- 可以预期的追问:检索结果该放在系统提示词里还是当成一条 user 消息?答放在靠近当前问题的位置通常效果更好,而且要标注来源便于模型区分「资料」和「用户说的话」;顺带说清它是一次性上下文,不该被写进长期会话历史里反复重发。
Key points
- They are not parallel: context engineering decides what enters the request, and RAG is one supply mechanism for information that is neither in the weights nor in the conversation
- RAG alone crowds out history — multi-thousand-token retrievals evict the conversation, so the model knows the docs but forgot the user's last request
- A bigger top-k is not safer: irrelevant chunks dilute attention and accuracy drops, so cap retrieval
- Retrieved text is single-turn context; persisting it into the history means paying for it on every subsequent turn
- Budget history and retrieval on separate lines, compress history when it crosses its line, and leave room for the output on top of both
答题要点
- 不是并列关系而是包含关系:上下文工程决定这次请求放什么,RAG 是给它供货的一种手段,负责把不在模型和对话里的信息检索回来
- 只做 RAG 会挤占历史:几千 token 的检索结果把对话挤没,模型记得住资料却忘了用户刚说的话
- 召回条数越大越准是错觉:不相关片段会稀释注意力,准确率反而下降,应控制 top-k
- 检索结果每轮重发会持续计费,属于一次性上下文,不该写进持久化历史反复重发
- 正确姿势是给历史和检索各划一条预算线,历史超线就压缩,两条线之外还要留出输出空间
D7 Packaging It as a Service: Fastify + SSE + Docker (dg P07); Week One Retrospective
For streaming LLM responses, would you pick SSE or WebSocket, and why?流式返回大模型回复,你会选 SSE 还是 WebSocket?为什么?
Common in ChinaCommon overseasBasic#sse#streaming#api-designHow to reason about it · think before answering
- The hinge is 'how would you pick', not 'what is the difference'. Reciting 'SSE is one-way, WebSocket is two-way' scores nothing — that is the first paragraph of any doc.
- Ask one question that nearly decides it: does the client need frequent upstream messages on this connection? Chat completion is one request followed by a long push, which is exactly SSE's shape. Collaborative editing, realtime games and voice are what WebSocket is for.
- Give three practical wins for SSE: it is ordinary HTTP, so auth headers, cookies, rate limiting, logging, CDNs and reverse proxies all keep working; the server just writes bytes into a response, with no separate connection lifecycle to manage; and the wire format is plain text, so curl is your debugger. WebSocket runs an upgraded protocol where most of that tooling has to be rebuilt.
- Volunteer SSE's two real limits before they are raised. First, the browser's native EventSource can only issue GET, while model endpoints require POST, so real frontends hand-roll the parser with fetch and the spec's Last-Event-ID auto-reconnect never applies. Second, HTTP/1.1 caps concurrent connections per origin, so several tabs each holding a stream compete; HTTP/2 largely removes this.
- Land on a decision rule: one-way push means SSE, high-frequency bidirectional means WebSocket, and when unsure start with SSE — its escape hatch is adding one upstream endpoint, while WebSocket's escape hatch is rebuilding your infrastructure.
- Expect the follow-up: what about the 'stop generating' button? It does not need the same connection — send a plain POST carrying the run id, have the server abort upstream, and the SSE stream ends on its own. This one separates people who shipped it from people who read about it.
分析过程 · 先想清楚再作答
- 这题的题眼是「怎么选」,不是「有什么区别」。只背出「SSE 单向、WebSocket 双向」拿不到分,因为那是文档第一段。
- 先问自己一个问题,它几乎决定了答案:这条连接上客户端需不需要频繁上行?聊天补全是「一次请求、一路往回推」,上行只有最开始那一次,完全落在 SSE 的形状里;协同编辑、实时游戏、语音这种双向高频才轮到 WebSocket。
- 然后给 SSE 的三条实际好处:它就是普通 HTTP,鉴权头、Cookie、限流、日志、CDN、反向代理这一整套现成设施全部照用;服务端只是往响应里写字节,不需要额外的连接管理;协议是纯文本,出问题 curl 一下就能看。WebSocket 走的是升级后的独立协议,前面那套东西大多要重做一遍。
- 接着说 SSE 的两个真实限制,主动说破比被问出来强:一是浏览器原生的 EventSource 只能发 GET,而大模型接口必须 POST,所以真实前端都是 fetch 手写解析,规范里那套 Last-Event-ID 自动重连一行都用不上;二是 HTTP/1.1 下同域并发连接数有限制,多个标签页各开一条长连接会互相挤占,HTTP/2 之后这条基本消失。
- 结论要落到一句可判断的话:单向推送选 SSE,双向高频选 WebSocket;拿不准就先用 SSE,因为它的退路是加一个上行接口,而 WebSocket 的退路是重做整套基础设施。
- 可以预期的追问:那大模型产品里的「停止生成」按钮怎么办?答案是它根本不需要走同一条连接——另发一个普通的 POST 请求带上这次生成的 id,服务端收到就中止上游,SSE 那条连接自然结束。这个追问很能区分有没有真做过。
Key points
- Decide by upstream frequency: one request plus a long push (chat completion) fits SSE; high-frequency bidirectional traffic needs WebSocket
- SSE is plain HTTP, so auth, rate limiting, logging, proxies and CDNs all still apply, and curl is enough to debug it
- Name SSE's limits yourself: EventSource is GET-only while model endpoints need POST, so spec auto-reconnect does not apply; HTTP/1.1 also caps per-origin connections
- When unsure start with SSE — adding one upstream endpoint is cheaper than rebuilding infrastructure around WebSocket
- A stop button does not need the same connection: POST the run id and abort upstream, and the stream ends by itself
答题要点
- 先判断上行频率:一次请求、一路往回推的场景(聊天补全)用 SSE,双向高频(协同编辑、语音)用 WebSocket
- SSE 就是普通 HTTP,鉴权、限流、日志、代理、CDN 这套设施全部照用,排查时 curl 就够
- SSE 的限制要主动说:EventSource 只能 GET,而模型接口必须 POST,所以自动重连用不上;HTTP/1.1 下同域连接数有限
- 拿不准先选 SSE:加一个上行接口就能补足,而换 WebSocket 要重做整套基础设施
- 「停止生成」不用走同一条连接,另发一个 POST 带 run id 让服务端中止上游即可
When turning a local agent script into a production service, what does the interface layer have to get right?把一个本地跑的 Agent 脚本改造成生产服务,接口层要重点考虑哪些事?
Common in ChinaCommon overseasIntermediate#api-design#service-architecture#streamingHow to reason about it · think before answering
- This tests whether you can name the assumptions hidden in a script. A generic checklist (auth, logging, monitoring) scores nothing; name the assumptions that silently break.
- List them first: one user (so history can live in a module-level variable), serial execution (no two requests mutating the same state), trusted input (you typed the arguments yourself), and a process whose life equals the session's. All four break in a service, and the first is hardest to catch because single-user local testing looks perfect.
- Then give the four decisions: response shape (single JSON versus streamed events), session identity (client-supplied id versus server cookie, and where history is stored), authentication and rate limiting (who may call, how often, and the per-call token ceiling), and how errors are expressed.
- Expand the last one — it is where this question is actually won. Once a streaming endpoint has written 200 and the first byte, the status code is already on the wire, so a later timeout, out-of-credit or upstream 500 can only surface as an agreed error event inside the stream. Validate everything you can before the first byte, because that is your last chance to speak in status codes.
- Add a production note: ship a health endpoint. Without one, orchestrators and load balancers cannot tell whether an instance is ready, and rolling deploys send traffic to a process that has not finished booting.
- Expect the follow-up: why cap tokens per request at the interface layer? Because agent cost is triggered by the caller and paid by you — no cap means handing your wallet to the client. Rate limiting is about money per call, not just QPS.
分析过程 · 先想清楚再作答
- 这题考的是「你知不知道脚本里有哪些隐含假设」。答成一份笼统的清单(鉴权、日志、监控)拿不到分,要说出脚本时代默认成立、服务里立刻不成立的那几条。
- 先把假设列出来,这是最能体现工程视角的一步:只有一个用户(历史可以放模块级变量)、串行执行(不会有两个请求同时改一份状态)、输入可信(参数是自己敲的)、进程和会话同生共死(Ctrl+C 之后不用交代)。四条在服务里全部不成立,而第一条最难查,因为它在本地单人测试时表现完美。
- 然后给出四个必须做的决定:接口形状(一次性 JSON 还是流式推送)、会话标识(客户端带 sessionId 还是服务端发 cookie,以及历史存哪里)、鉴权与限流(谁能调、多久能调一次、单次 token 上限)、错误怎么表达。
- 第四条要单独展开,它是这题真正的区分点:流式接口一旦写出 200 和第一个字节,状态码就已经发出去了,之后模型超时、余额不足、上游 500,都只能在流里补发一个约定好的 error 事件。所以推流之前必须把能校验的全部校验完,那是你最后一次能用状态码好好说话的机会。
- 再补一条生产视角:服务要有健康检查接口。没有它,编排系统和负载均衡就没法判断这个实例能不能接流量,滚动发布时会把请求打给一个还没起好的进程。
- 可以预期的追问:单次请求的 token 上限为什么要在接口层限制?因为 Agent 的成本是请求方触发、你来买单,不设上限就等于把钱包交给调用方——限流限的不只是 QPS,还有每次调用能烧多少钱。
Key points
- A script's four assumptions all break in a service: single user, serial execution, trusted input, and a process that dies with the session
- Session state must be keyed by session id, and in-process storage means data is lost on restart and blocks horizontal scaling
- Four interface decisions: response shape, session identity, auth and rate limiting including a per-call token ceiling, and error semantics
- A streaming endpoint cannot report errors by status code after the first byte, so define an in-stream error event and move all validation ahead of it
- Expose a health endpoint, or orchestrators cannot tell whether the instance is ready for traffic
答题要点
- 脚本的四个隐含假设在服务里全部不成立:单用户、串行、输入可信、进程与会话同生共死
- 会话状态必须按 sessionId 隔离,且要意识到放进程内存意味着重启即丢、无法水平扩容
- 四个接口决定:响应形状、会话标识、鉴权与限流(含单次 token 上限)、错误表达方式
- 流式接口推流之后无法用状态码报错,必须约定一个流内的 error 事件,并把校验全部前置到第一个字节之前
- 提供健康检查接口,否则编排系统无法判断实例能不能接流量
What are the key decisions in a Dockerfile that packages a Node service?把一个 Node 服务打包成 Docker 镜像,Dockerfile 里有哪些关键决定?
Common in ChinaCommon overseasIntermediate#docker#deployment#nodejsHow to reason about it · think before answering
- It looks like a recipe question, but it tests whether you have ever traded off build speed against security. Reading FROM, COPY, RUN, CMD in order is the least differentiating answer.
- The first decision is instruction order, the only one with an immediately measurable payoff. Images are stacked layers and a layer whose inputs are unchanged is reused, so copy the manifest and lockfile first, install, then copy source. Editing one line of code then invalidates only the last two layers instead of forcing a full reinstall.
- Second, pin the base image. Using latest means the image silently jumps a major version some morning, which destroys the reproducibility that was the whole reason to containerize.
- Third, runtime configuration: bind to 0.0.0.0 inside a container. Binding 127.0.0.1 leaves the service reachable only from inside, so a published port still refuses connections — and it works perfectly on your laptop, which is why it is so common. Also note EXPOSE only documents intent; the port is actually published by docker run -p.
- Fourth, security: run as a non-root user, since containers share the host kernel and root widens the blast radius of an escape. Keep node_modules out via .dockerignore (host binaries will not run in a Linux container and the build context balloons) and keep .env out too, passing secrets at runtime with --env-file.
- Expect the follow-up, and it is the one a streaming service should volunteer: use the exec-form CMD to launch node directly so it becomes PID 1. With pnpm start, PID 1 is the package manager, SIGTERM from docker stop may never reach node, your graceful shutdown never runs, and the container is SIGKILLed after the timeout — cutting every in-flight SSE stream.
分析过程 · 先想清楚再作答
- 这题看着是背步骤,其实考的是「你有没有为构建速度和安全性做过取舍」。把 FROM、COPY、RUN、CMD 顺着念一遍是最没有区分度的答法。
- 第一个决定是指令顺序,也是唯一能立刻量化收益的:镜像是逐层叠出来的,某层的输入没变就复用缓存。所以先只拷 package.json 和 lockfile、装完依赖再拷源码——改一行业务代码只让最后两层失效,依赖那层照旧命中;反过来一上来就 COPY 全部,改一个字都要重装依赖。
- 第二个是基础镜像钉版本。写 latest 等于让镜像在某天悄悄升到下一个大版本,可复现性当场归零,而可复现正是用容器的全部理由。
- 第三个是运行时配置:容器里必须监听 0.0.0.0,只听 127.0.0.1 的话它只在容器内部可达,宿主机做了端口映射也连不上——这个坑在本机跑的时候完全正常,所以特别常见。另外 EXPOSE 只是声明意图,真正开端口的是 docker run 的 -p。
- 第四个是安全:用非 root 用户跑业务进程(容器和宿主机共用内核,逃逸后 root 的破坏面大得多),.dockerignore 排除 node_modules(宿主机的二进制在 Linux 容器里跑不起来,还会让构建上下文暴涨)和 .env(密钥打进镜像等于发给每个能拉到镜像的人,运行时用 --env-file 传)。
- 可以预期的追问,也是长连接服务最该主动说的一条:CMD 要用数组形式直接起 node,让它当 PID 1。写成 pnpm start 的话 PID 1 是包管理器,docker stop 的 SIGTERM 未必传得到 node,优雅退出代码永远不执行,只能等十秒超时被 SIGKILL——对 SSE 服务,那意味着所有在途的流被硬切。
Key points
- Instruction order drives cache hits: copy the manifest, install, then copy source, so code edits do not reinstall dependencies
- Pin the base image instead of latest — reproducibility is the entire point of containerizing
- Bind 0.0.0.0 inside the container; EXPOSE only documents intent while docker run -p publishes the port
- Run as a non-root user, and keep node_modules and .env out via .dockerignore, injecting secrets at runtime
- Use exec-form CMD to run node as PID 1 so SIGTERM reaches it and graceful shutdown actually executes
答题要点
- 指令顺序决定缓存命中:先拷依赖清单装依赖,再拷源码,改代码不会触发重装依赖
- 基础镜像钉版本不用 latest,可复现是用容器的全部理由
- 容器里监听 0.0.0.0;EXPOSE 只是声明,真正开端口靠 docker run -p
- 用非 root 用户运行;.dockerignore 排除 node_modules 与 .env,密钥运行时用 --env-file 注入
- CMD 用数组形式直接起 node 让它当 PID 1,SIGTERM 才能传到进程,优雅退出才有效
For a long-lived SSE service in production, what problems do heartbeats, disconnect handling and graceful shutdown each solve?一个 SSE 长连接服务上线,心跳、连接断开处理和优雅退出分别在解决什么问题?
Common in ChinaCommon overseasDeep dive#sse#reliability#deploymentHow to reason about it · think before answering
- The discriminator is that the three have completely different failure symptoms. Someone who can describe each symptom has shipped one; 'they all improve stability' is a non-answer.
- Heartbeats prevent middleboxes from killing you. Load balancers and gateways commonly close idle connections after 60 to 120 seconds, and agents are full of silent gaps while the model reasons, calls a tool or waits on a slow API. The symptom is a stream that dies halfway for no visible reason and never reproduces against a local server. Implement it as an SSE comment line, which clients silently ignore, so no client change is needed.
- Disconnect handling is about money. When a user closes the tab the server does not stop on its own: the model keeps generating and tokens keep billing with nobody receiving. It is the most expensive oversight in streaming services, and staging never reveals it because nobody closes tabs mid-run. Watch for the response closing, distinguish a premature close from a normal finish, and abort the upstream request.
- One detail must be right or it exposes you immediately: in Node listen on the response object's close, not the request's. The request emits close once its body has been read, so using it as a disconnect signal misfires on every normal request and you see streams stopping after one or two chunks.
- Graceful shutdown is about deploys cutting live requests. On SIGTERM the process should stop accepting new connections, give in-flight streams a short window, then exit; otherwise users watch a reply stop mid-sentence. This assumes the signal actually reaches the process — if the container's PID 1 is a package manager, SIGTERM never arrives and the runtime kills you on timeout.
- Expect: how long is the window? Shorter than the orchestrator's termination grace period (10s by default in Docker, 30s in Kubernetes), or you get SIGKILLed anyway; and refuse new connections immediately so the load balancer drains traffic away.
分析过程 · 先想清楚再作答
- 这题的区分度在于三件事各自的失败现象完全不同,能分别说出现象的人一定真上过线。答成「都是为了稳定性」等于没答。
- 心跳解决的是「被中间设施误杀」。负载均衡和网关普遍有空闲超时,常见 60 到 120 秒,一段时间没有字节流动就关连接;而 Agent 天生有大量静默期——模型在思考、在调工具、在等慢接口。现象是连接莫名其妙断在一半,且本地直连时完全复现不了。实现上用 SSE 的注释行(冒号开头)做心跳,客户端会安静忽略,不用改客户端代码。
- 连接断开处理解决的是「花钱」。用户关掉页面之后服务端不会自动停,模型继续生成、token 继续计费,只是没人接收。这是流式服务里最贵的疏忽,而且测试环境暴露不出来,因为没人会中途关页面。做法是监听响应对象的关闭事件,判定是被掐断而不是正常收尾,就把上游请求一起中止。
- 这里有个必须说对的细节,说错会当场暴露没写过:Node 里要监听的是响应对象的 close,不是 request 的——request 的 close 在请求体读完时就触发,拿它当断线信号会把每一条正常请求都误判成客户端跑了,现象是每次只推出一两个片段就停。
- 优雅退出解决的是「发布时切断在途请求」。容器收到 SIGTERM 后应当先停止接受新连接,给在途的流一点收尾时间再退出,否则用户看到的是回复说了一半突然没了。前提是信号真的能传到进程——CMD 写成包管理器的话 PID 1 不是 node,SIGTERM 传不到,只能等超时被强杀。
- 可以预期的追问:收尾时间给多久?答案是要小于编排系统的终止宽限期(Docker 默认十秒、K8s 默认三十秒),超过就会被 SIGKILL,等于白设计;同时新连接要立刻拒绝,让负载均衡把流量挪走。
Key points
- Heartbeats defeat idle timeouts in middleboxes, since agent silence often exceeds a gateway's 60 to 120 seconds; SSE comment lines do it transparently
- Disconnect handling stops waste: after a user closes the tab, an unaware server keeps burning tokens, and staging never shows it
- In Node listen on the response's close, not the request's — the latter fires when the body is read and misclassifies normal requests as disconnects
- Graceful shutdown stops deploys from cutting live streams: on SIGTERM refuse new connections and drain, within the orchestrator's grace period
- It only works if the signal reaches the process, so PID 1 must be node itself rather than a package manager
答题要点
- 心跳防的是中间设施的空闲超时,Agent 的静默期常常超过网关的 60 到 120 秒,用 SSE 注释行实现,客户端无感
- 断开处理防的是浪费:用户关页面后服务端不停就是纯烧 token,测试环境暴露不出来
- Node 里要监听响应对象的 close 而不是 request 的——后者在请求体读完时就触发,会把正常请求误判成断线
- 优雅退出防的是发布切断在途流:SIGTERM 后先停收新连接、给在途流收尾时间,收尾窗口要小于编排系统的终止宽限期
- 前提是信号能传到进程:容器的 PID 1 必须是 node 本身,不能是包管理器
In a two-minute self-introduction, how do you convey the value of an agent project?自我介绍时,怎么在两分钟里讲清楚一个 Agent 项目的价值?
Common in ChinaCommon overseasBasic#interview-prep#communicationHow to reason about it · think before answering
- There is no model answer, but there is a clear failure mode: opening with a tool list. Interviewers do not remember stacks; they remember problems and numbers.
- Use a fixed structure that fits two minutes: one line on who you are and where you are heading, one line on the business problem (who suffers, in what situation), three or four lines on your key technical decisions and what each bought you, and one closing line with a verifiable result.
- Choose decisions that involved a trade-off, not decisions that merely involved implementation. 'We stream over SSE rather than WebSocket because upstream traffic is a single request, which lets us keep existing auth, rate limiting and logging' shows you knew the alternative and priced it — far stronger than naming ten tools.
- Attach numbers wherever you can, even self-measured ones: time-to-first-token dropping from seconds to a few hundred milliseconds, tiered routing cutting daily spend by more than half, multi-provider fallback removing a single vendor from your availability ceiling. If the numbers are from a test environment, say so; inventing them collapses after two follow-ups.
- A common mistake is presenting a learning project as production. Position it yourself: a complete system built to understand production agent architecture, at self-test scale, where every decision was made against real constraints. Interviewers forgive honest scoping far more readily than inflated claims.
- Expect: what was the hardest part? Prepare one concrete story with a process — for example, discovering that a streaming endpoint cannot report errors by status code once it has started pushing, and redesigning around an in-stream error event plus front-loaded validation.
分析过程 · 先想清楚再作答
- 这题没有标准答案,但有明确的失败模式:从技术栈开始报菜名(我用了 Fastify、SSE、Docker、向量库……)。面试官记不住工具清单,他记得住的是问题和数字。
- 用一条固定结构去组织,两分钟正好够:一句话说你是谁和转型方向,一句话说项目解决的业务问题(谁在什么场景下受什么苦),三到四句说你的关键技术决定和它换来了什么,最后一句给可验证的结果。
- 关键技术决定要挑「有取舍的」讲,不要讲「有实现的」。比如「流式用 SSE 而不是 WebSocket,因为上行只有一次,这样鉴权限流日志这套现成设施全部照用」——这种句子同时展示了你知道有别的选项、也知道选它的代价,比列出十个工具有效得多。
- 结果要尽量带数字,哪怕是自测数据:首字延迟从几秒降到几百毫秒、分层路由把日成本从 300 元降到 125 元、多 provider 冗余让可用性不再取决于单家厂商。没有生产数据就诚实说明是自测环境,编数字是最危险的做法,追问两句就穿帮。
- 常见误区是把学习项目说成生产项目。正确姿势是主动定位:这是我为了搞懂生产级 Agent 架构而完整实现的一套系统,规模是自测级,但每个决定都对着真实约束做过取舍——面试官对诚实的自评远比对夸大的描述宽容。
- 可以预期的追问:这个项目最难的地方是什么?提前准备一个具体的、有过程的答案(比如流式接口推流之后没法用状态码报错,最后改成流内 error 事件加上把校验全部前置),比任何形容词都有说服力。
Key points
- Keep a fixed structure: positioning, the business problem, three or four traded-off decisions, and one verifiable result
- Do not recite a stack — interviewers retain problems, trade-offs and numbers, not tool lists
- Frame decisions as trade-offs, naming the alternative and why it lost
- Attach numbers even from self-testing, but label their source and never invent them
- Scope the project honestly as a complete build at self-test scale; honest framing survives follow-ups better than inflation
答题要点
- 结构固定:定位一句、业务问题一句、三到四个有取舍的技术决定、一句可验证的结果
- 不要报菜名:面试官记不住工具清单,记得住问题、取舍和数字
- 技术决定要讲取舍而不是讲实现,说清楚备选方案是什么、为什么没选它
- 结果尽量带数字,自测数据也可以,但必须标明来源,绝不编造
- 主动定位项目规模:为搞懂生产架构而完整实现、自测级规模,诚实自评比夸大更容易通过
D8 Why Split Gateway and Worker; Postgres Table Design (sessions/runs/messages) + Drizzle
Why do production agent services usually split a gateway from workers, and when should you not split?为什么生产级 Agent 服务通常要把 Gateway 和 Worker 拆开?什么情况下不该拆?
Common in ChinaCommon overseasBasic#architecture#scalabilityHow to reason about it · think before answering
- The hinge is the second half. Answering only 'decoupling and scalability' sounds copied from a textbook; the interviewer wants to know which concrete symptom forced you to split, and what splitting costs.
- Offer a reusable chain: one agent run is long and unpredictable (model latency plus several tool calls, seconds to tens of seconds), while the ingress path carries all traffic and must stay in the millisecond range. Put workloads three orders of magnitude apart in the same process and the slow one starves the fast one.
- Make the symptom concrete: a single process running a dozen long executions saturates connections and memory, health checks start timing out, the orchestrator declares the instance dead and restarts it, and every in-flight run dies with it. That story lands harder than any abstract argument.
- Then state the rule: anything a worker can do should not live in the gateway, which keeps only auth, rate limiting, persistence and dispatch — four steps with bounded latency. After the split the stateless gateway scales with traffic while worker concurrency is tuned against model quota; the two curves were never the same.
- Volunteer the cost, which is where candidates separate: the contract becomes 202 instead of 200 so clients need a second subscribe round trip, you now operate a bus and a runs table, tracing spans more hops, and local development needs more processes. So do not split when a run takes a few hundred milliseconds, uses no tools, and serves modest traffic.
- Expect the follow-up: could a thread pool or child processes do instead? They ease starvation but fix neither 'restart loses in-flight work' nor 'two instances cannot see each other's state', because the root cause is state living inside the process, not the concurrency model.
分析过程 · 先想清楚再作答
- 题眼在后半句。只答「解耦、可扩展」是从架构书上抄来的,面试官想知道你有没有被某个具体现象逼着拆过——所以答案里必须出现「什么现象」和「不拆的代价」。
- 先给一条可复用的推导链:Agent 的一次执行是长耗时且时长不可预测的(模型响应加上多轮工具调用,几秒到几十秒),而接入层要承载全部流量、必须是毫秒级的短请求;把两种时长量级差三个数量级的工作放进同一个进程,慢的那一类必然会挤占快的那一类的资源。
- 把现象说具体:单进程时一台机器同时跑十几次长执行,连接与内存被占满,新来的健康检查开始超时,编排系统判定实例已死并重启它——正在跑的执行全部陪葬。这个「健康检查被自己的业务拖挂」的故事比任何抽象论证都有说服力。
- 然后给判据:能在 Worker 做的不放 Gateway,接入层只留鉴权、限流、落库、投递这四件耗时确定的事。拆开之后 Gateway 无状态可以任意扩缩,Worker 的并发度可以按模型配额单独调,两者的扩容曲线本来就不一样。
- 主动说代价,这是区分度所在:接口语义从 200 变成 202,客户端要多一次订阅往返;系统里多了一条总线和一张 runs 表,可观测性和排障链路都变长;本地开发要起更多进程。所以单次执行只有几百毫秒、没有工具调用、日活很小的场景不该拆——那时候拆分带来的复杂度远大于收益。
- 可以预期的追问:不拆但用线程池或者子进程行不行?答案是能缓解「挤占」但解决不了「重启即丢失」和「多实例状态不共享」,因为那两件事的根因是状态在进程里,不是并发模型不对。
Key points
- A run takes seconds to tens of seconds while ingress requests are millisecond-scale; in one process the long work starves the short work
- Three concrete failure modes: restarts lose in-flight runs, multiple instances hold separate state, and long runs stall health checks so the orchestrator kills a healthy instance
- The rule is that anything a worker can do stays out of the gateway, which keeps only auth, rate limiting, persistence and dispatch
- After splitting, gateways scale on traffic and workers scale on model quota — two independent curves
- Costs: a 202 contract plus a subscribe round trip, an extra bus and table to operate, longer traces; skip the split for sub-second runs with no tool calls
答题要点
- 一次 Agent 执行是几秒到几十秒的长任务,接入层是毫秒级短请求,两者同进程时长任务必然挤占短请求的资源
- 单进程的三个具体死法:重启丢掉在途执行、多实例状态各存各的、长执行把健康检查拖超时导致实例被误杀
- 判据是「能在 Worker 做的不放 Gateway」,接入层只留鉴权、限流、落库、投递
- 拆开后 Gateway 无状态按流量扩容、Worker 按模型配额扩容,两条曲线可以独立调
- 代价是接口从 200 变 202、多一次订阅往返、排障链路变长;单次执行仅几百毫秒且无工具调用的场景不该拆
What makes a service stateless, what does that mean for horizontal scaling, and are workers stateful?什么是无状态服务?它对水平扩展意味着什么?Worker 算不算有状态?
Common in ChinaCommon overseasIntermediate#stateless#scalabilityHow to reason about it · think before answering
- The trap is reading the word literally. Many candidates say 'it stores nothing', which is wrong — stateless services write to databases all day. The discriminator is whether you can define it precisely.
- One sentence does it: stateless means state does not live in the process handling the request, so any instance can serve any request. Turn it into a self-check: kill a random instance — does any user's data exist only there? Only 'no' is stateless.
- Derive three scaling consequences: a new instance needs no warm-up or data sync and starts serving the moment it joins the load balancer; any instance can be killed at will, which is what makes rolling deploys and spot instances viable; and no sticky sessions are needed, whereas stickiness means rebalancing during a scale-up cuts existing conversations.
- Answer the worker half carefully: it holds execution progress, not user data — which turn it is on, which tools it called, and later a lease. User data always lives in the database. So 'stateful' here means 'holding unfinished work', and the consequence is that you cannot kill it freely: drain first, refuse new work, let the current run finish.
- Expect the follow-up: does an in-memory cache break statelessness? It depends on whether losing it causes wrong behavior. A pure accelerator that only costs latency is fine; the moment a user's session exists only in one machine's memory you are silently relying on stickiness, and the next scale-up will prove it.
分析过程 · 先想清楚再作答
- 这题的陷阱是字面理解。很多人答成「不保存任何数据」,那是错的——无状态服务当然会写数据库。区分度在于你能不能给出准确定义。
- 准确定义只有一句:无状态指的是**状态不留在处理请求的那个进程身上**,因此任意一台实例都能处理任意一个请求。把它翻译成一个自检问题就很好用:随便杀掉一台实例,有没有任何用户的数据只存在于那台机器上?答「没有」才是无状态。
- 再推出水平扩展的三个后果:新实例不需要预热或同步数据,接上负载均衡立刻能干活;任意实例可以随时被杀,滚动发布和抢占式实例才成立;不需要会话粘连,而粘连一旦存在,扩容时的重新分配就会打断老用户的会话。
- Worker 那一问要答得有分寸:它持有的不是用户数据,而是一次执行的进度(跑到第几轮、调了哪些工具、后面还会加上一个租约)。用户数据始终在数据库里。所以说它有状态,指的是「手上有活没交代完」,后果是不能随便杀——必须优雅停机,先拒绝新任务再等手头的跑完。
- 可以预期的追问:内存缓存算不算破坏了无状态?答案是看丢了会不会出错。纯粹用于加速、丢了只是变慢的缓存不破坏无状态;一旦某个用户的会话只存在于某台机器的内存里,你就已经在偷偷依赖粘连了,扩容那天必然出事。
Key points
- Stateless means the state does not live in the request-handling process, so any instance serves any request — not that nothing is stored
- Self-check: kill any instance and ask whether any user's data existed only there
- Three scaling prerequisites: no warm-up, any instance disposable, no sticky sessions
- Workers are stateful in the sense of holding run progress, not user data, so they need graceful drain rather than a hard kill
- A pure accelerator cache is fine; in-memory data that is the only copy is implicit stickiness
答题要点
- 无状态的准确含义是状态不留在处理请求的进程里,任意实例都能处理任意请求,而不是「不存数据」
- 自检方法:随便杀一台实例,是否有用户的数据只存在于那一台上
- 水平扩展的三个前提:新实例无需预热、任意实例可被随时杀掉、不需要会话粘连
- Worker 的有状态指的是持有一次执行的进度而不是用户数据,后果是必须优雅停机而不能随便杀
- 只加速、丢失只降速的缓存不破坏无状态;承载唯一副本的内存数据等于隐式的会话粘连
With at-least-once delivery, how do you guarantee a redelivered message does not create two runs?消息总线是至少一次投递,同一条消息被重复投递时,怎么保证不会产生两条 run?
Common in ChinaCommon overseasDeep dive#idempotency#database#reliabilityHow to reason about it · think before answering
- This question is about which layer idempotency lives in. Anyone who answers 'check whether it exists, then insert' has usually just failed it — that is exactly the answer being screened out.
- State the premise: duplicates are not accidents. The bus is at-least-once, clients retry on timeout, users double-click. The same message arriving twice is certain, so the goal is not to prevent duplicates but to make duplicates produce the same result.
- Then derive the key: idempotency needs a key derived from request content. A random UUID differs every time and buys nothing; hash the session id, the client message id and the message body together, falling back to content plus a coarse time bucket when the client has no id.
- Land it in storage: put a unique constraint on that column in the runs table, write the insert as on-conflict-do-nothing, and when it returns zero rows read back the existing run and return the same run id. Two requests, one run, one id.
- Explain why check-then-insert fails, which is the whole point: two gateway instances can query, both see nothing, and both insert. The window between the two statements cannot be closed in application code, it is too narrow to reproduce under load tests, and it leaks a few bad rows every day in production. The database's unique constraint has to be the final arbiter; the application-level check only saves a wasted insert.
- Expect the follow-up: what about duplicate execution on the consumer side? The unique constraint gives you one run, but a worker can still receive it twice, so status changes need conditional updates (move to running only if the current status is pending) plus an explicit transition whitelist that blocks a finished run from being pushed back to running and overwriting a reply the user already saw.
分析过程 · 先想清楚再作答
- 这题在考幂等的落点在哪一层。凡是答「在代码里先查一下有没有,没有再插入」的,基本当场结束——因为那正是这题想筛掉的答案。
- 先把前提摊开:重复不是意外。总线是至少一次语义、客户端会超时重发、用户会手抖双击,同一句话到达两次是必然事件。所以设计目标不是「避免重复到达」,而是「重复到达时结果相同」。
- 然后给推导:幂等需要一个由请求内容决定的键。随机 UUID 每次都不同,等于没有幂等;正确取法是把会话 id、客户端消息 id、消息内容拼起来做哈希,客户端没有消息 id 时退用内容加一个粗粒度时间窗。
- 结论落在存储层:在 runs 表的这一列上加唯一约束,插入写成「冲突就什么都不做」,返回零行时回查那条已有的 run,把同一个 runId 返回给用户。两次请求、一条 run、一个 runId。
- 解释为什么「先查后插」不行,这是本题的分水岭:两个 Gateway 实例可以同时查、同时发现没有、同时插入,这两步之间有一个应用层拦不住的时间窗;它窄到压测复现不出来,上线后每天漏几条。**幂等的最终裁判必须是数据库的唯一约束**,应用层的判断只是为了少一次插入尝试。
- 可以预期的追问:那消费侧的重复执行呢?答:唯一约束保证了只有一条 run,但 Worker 可能重复拿到同一条 run,所以状态迁移也要带条件更新(只有当前状态是 pending 时才能改成 running),并且用一个显式的迁移白名单挡住「已完成的 run 被推回运行中」这种会覆盖用户已收到回复的情况。
Key points
- Redelivery is certain, so the goal is identical outcomes on duplicates, not preventing duplicates
- The idempotency key must be derived from request content — session id plus client message id plus body, hashed; a random UUID buys nothing
- Put a unique constraint on that column, insert with on-conflict-do-nothing, and read back the existing run when zero rows return
- Check-then-insert races under concurrency; the window between the statements cannot be closed in application code, so the unique constraint must be the final arbiter
- On the consumer side add conditional status updates and a transition whitelist so a finished run is never re-run or overwritten
答题要点
- 重复投递是必然事件,设计目标是「重复到达时结果相同」,不是「避免重复」
- 幂等键必须由请求内容决定:会话 id 加客户端消息 id 加内容做哈希,随机 UUID 等于没有幂等
- 在 runs 的幂等键列上建唯一约束,插入用「冲突就什么都不做」,零行时回查已有 run 返回同一个 runId
- 先查后插在并发下必然出双份,两条语句之间的时间窗应用层拦不住,幂等的最终裁判是数据库唯一约束
- 消费侧还要用条件更新加状态迁移白名单,避免同一条 run 被重复执行或把已完成的回复覆盖掉
How would you design primary keys and indexes for sessions, runs and messages, and why avoid auto-increment ids?sessions / runs / messages 这三张表你会怎么设计主键与索引?为什么不用自增主键?
Common in ChinaCommon overseasIntermediate#database#schema-design#idempotencyHow to reason about it · think before answering
- It looks like a trivia question, but every choice sits on a concrete constraint. The test is whether you can say what breaks if you choose otherwise.
- Start with why three tables rather than one: the grains differ. A session is a long-lived container, a run has a lifecycle and can fail and be retried, a message is an immutable fact. Without the run layer there is nowhere to answer 'did this finish', 'should we retry', or 'what did this turn cost'.
- Use text primary keys generated in the application (UUID or ULID), because the gateway must put the id into the 202 response before the row is written. Auto-increment ids are only known after the insert, which parks a round trip in the user's wait path and cannot be pre-allocated across instances. A bonus is that sharding later needs no renumbering.
- Index by query path, not by instinct: sessions need an index on user_id to list a user's conversations, messages need one on session_id to load history, and foreign key columns need indexes or deleting a parent row triggers a full scan. Extra indexes are not free — each one slows writes.
- The two unique constraints carry the design: a unique idempotency key on runs blocks duplicate delivery, and a composite unique on run id plus sequence in messages both fixes output ordering for one run and lets a reconnect replay idempotently by sequence. The sequence must start at zero and never skip, otherwise resume cannot find the cut point.
- Expect the follow-up: ULID or UUIDv4? Choose ULID or UUIDv7 — they are time-ordered so inserts land at the right edge of the B-tree, whereas random UUIDv4 scatters writes, splits pages and hurts cache hit rates. Mentioning this shows you have watched write performance.
分析过程 · 先想清楚再作答
- 这题看着像八股,其实每一个选择背后都有一个具体约束。判断标准是:你能不能为每个决定说出「不这么做会发生什么」。
- 先讲为什么是三张表而不是一张:粒度不同。会话是长期容器,一次执行有生命周期且可能失败重来,消息是不可变事实。少了「一次执行」这一层,你就没有地方回答「这次跑完没有」「该不该重试」「这轮花了多少钱」。
- 主键选文本型的应用侧 id(UUID 或 ULID),理由是接入层必须在写库之前就把 id 放进 202 响应体返回给客户端;自增主键要等数据库插完才知道值,那次往返就被卡在用户的等待路径上,而且多实例无法预分配。附带好处是将来分库分表不用重编号。
- 索引按查询路径建,不按直觉建:按用户拉会话列表要 sessions 的 user_id 索引,按会话拉历史要 messages 的 session_id 索引,外键列本身要索引否则删除父行会全表扫。多余的索引不是免费的,每个都让写入变慢。
- 两条唯一约束才是这套设计的灵魂:runs 的幂等键唯一,挡住重复投递;messages 的「run id 加序号」复合唯一,既保证同一次执行的输出顺序稳定,又让断线重连可以按序号幂等回放。序号要从 0 开始、连续、不跳号,否则续传就找不到断点。
- 可以预期的追问:ULID 和 UUIDv4 选哪个?答 ULID 或 UUIDv7——它们按时间有序,插入时集中在 B 树右端,不像 UUIDv4 那样随机分布导致页分裂和缓存命中率下降。这个细节能直接体现你关心过写入性能。
Key points
- Three tables for three grains: a long-lived session, a run with a lifecycle, and immutable messages; without runs you cannot answer completion, retry or cost questions
- Application-generated text ids, because the gateway must return the run id in the 202 before the write, and auto-increment ids cannot be pre-allocated across instances
- Index the real query paths — user_id on sessions, session_id on messages, plus foreign key columns; extra indexes slow writes
- Two unique constraints carry the design: a unique idempotency key on runs, and a composite unique on run id plus sequence in messages for ordering and idempotent replay
- Prefer time-ordered ids such as ULID or UUIDv7 over random UUIDv4 to avoid page splits and cache misses
答题要点
- 三张表对应三种粒度:会话是长期容器、run 是一次有生命周期的执行、message 是不可变事实;少了 run 就无法回答是否跑完、该不该重试、花了多少钱
- 主键用应用侧生成的文本 id,因为 Gateway 要在写库之前把 runId 放进 202 响应里,自增主键必须等插入完成且无法跨实例预分配
- 索引按实际查询路径建:sessions 的 user_id、messages 的 session_id、以及外键列;多余索引会拖慢写入
- 两条唯一约束是灵魂:runs 的幂等键唯一挡重复投递,messages 的「run id 加序号」复合唯一保证保序与幂等回放
- id 优先选 ULID 或 UUIDv7 这类时间有序的方案,避免随机 UUID 造成的页分裂与缓存失效
D9 A Redis Streams Message Bus: XADD/XREADGROUP/XACK/XAUTOCLAIM, Consumer Groups, Poison Messages
How does a Redis Streams consumer group work, and why can it serve both as a work queue and as pub/sub?Redis Streams 的 consumer group 是怎么工作的?为什么它既能做工作队列又能做发布订阅?
Common in ChinaCommon overseasBasic#message-bus#redis-streamsHow to reason about it · think before answering
- This is a concept question; the discriminator is whether you separate the group layer from the consumer layer. Saying only 'several consumers read together' invites 'so is a message processed twice?' — and that is exactly what the two layers settle.
- Give the structure: the stream is append-only; a group sits on the stream and owns a read cursor plus a pending list; a consumer is just a name inside a group. Consumers in one group share the messages (each message goes to exactly one of them), while separate groups each see the full stream — one data structure, both a work queue and pub/sub.
- Then name the three things the pending entries list records: which consumer owns the message, how many times it has been delivered, and when it was last delivered. Those map to 'who is working on it', 'is it poison yet' and 'can someone else take over' — knowing them signals you read the docs, not just a snippet.
- Land on the dispatch rule: a group hands a message to whoever asks first, with no affinity at all. So a consumer group does not keep multiple messages from the same user in order on the same worker — say this yourself and you steer into ground you have prepared.
- Expect: how do you name consumers? Random names orphan the unacked messages of the previous name after a restart, recoverable only via XAUTOCLAIM. Either use stable ordinals from a stateful deployment, or rely on XAUTOCLAIM and periodically prune dead names with XGROUP DELCONSUMER.
- Expect: how do you preserve per-user order? Shard above the bus — hash the user id onto a fixed number of shards and let one consumer own a shard at a time. The consumer group cannot do this for you.
分析过程 · 先想清楚再作答
- 这题是概念题,区分度在于你有没有把「组」和「消费者」两层分清。只答「多个消费者一起消费」会被追着问「那同一条消息会不会被消费两次」,而这正是两层的区别所在。
- 先给两层结构:流本身只增不减,组挂在流上、维护一个读游标和一份 pending 清单,消费者挂在组上、只是组内的一个名字。同一个组内的消费者分摊消息(一条只进一个人),不同的组各自都能读到全量——工作队列和发布订阅就是这一个数据结构的两种用法。
- 接着点出 pending 清单(PEL)记了哪三件事:这条消息归哪个消费者、被投递过几次、最后一次投递在什么时刻。这三列分别对应「谁在处理」「要不要判成毒消息」「能不能被别人接手」,答出来就说明你真的读过文档而不只是抄过示例。
- 结论要落到分配规则上:组把消息分给谁,完全取决于谁先来问,没有任何亲和性。所以 consumer group 天然不保证「同一个用户的多条消息按顺序被同一个人处理」——这一句是把话题引向自己准备好的深水区。
- 可以预期的追问一:消费者的名字该怎么取?答:随机名会让进程重启后老名字下的未确认消息变成孤儿,只能靠 XAUTOCLAIM 捡回来,所以要么用有状态部署给的稳定序号,要么就必须依赖 XAUTOCLAIM 兜底,并定期用 XGROUP DELCONSUMER 清理不会再回来的名字。
- 可以预期的追问二:怎么保住同一个用户的顺序?答:在总线之上做分片——把用户 id 哈希到固定数量的分片,每个分片同一时刻只由一个消费者持有,顺序就回来了。消费组本身解决不了这件事。
Key points
- The stream is append-only; a group holds a read cursor and a pending list; a consumer is a name within a group
- Within a group messages are split (one message, one consumer); separate groups each get everything, so one structure covers both work queue and pub/sub
- The pending list records owner, delivery count and last-delivery time — used for takeover, poison detection and timeouts
- Dispatch has no affinity, so per-user ordering is not guaranteed and needs sharding above the bus
- Random consumer names orphan unacked messages after a restart; use stable names or rely on XAUTOCLAIM plus XGROUP DELCONSUMER cleanup
答题要点
- 流只增不减;组挂在流上,维护读游标和 pending 清单;消费者是组内的一个名字
- 同组内消息被分摊(一条只进一个消费者),不同组各自拿到全量,所以同一个结构同时支持工作队列和发布订阅
- pending 清单记三件事:归属的消费者、投递次数、最后一次投递时刻,分别用于接手、毒消息判定和超时检测
- 分配没有亲和性,谁先来问给谁,所以不保证同一个用户的多条消息顺序,要在总线之上做分片
- 消费者名字随机会在重启后留下孤儿消息,要么名字稳定,要么依赖 XAUTOCLAIM 并清理死名字
What problems do XACK and XAUTOCLAIM each solve, and what changes if you XACK before instead of after doing the work?XACK 和 XAUTOCLAIM 分别解决什么问题?XACK 放在业务处理之前和之后有什么区别?
Common in ChinaCommon overseasIntermediate#message-bus#redis-streams#error-handlingHow to reason about it · think before answering
- The hinge is the second half. The first half is documentation; the second asks whether you know that ack timing decides the delivery semantics of the whole system.
- Split the two commands: XACK clears a message from the pending list, meaning the work is genuinely finished; XAUTOCLAIM reassigns a pending message that has been idle past a threshold, meaning its previous owner may be dead. One is the normal path, the other is the failure path.
- Then answer the timing question categorically: ack-then-work is at-most-once, work-then-ack is at-least-once. In the first, a crash makes the message vanish — it is not in the pending list, so XAUTOCLAIM cannot recover it. In the second, the worst case is duplicate execution, and duplicates can be blocked by idempotency while lost work cannot. Always work first, except for fire-and-forget telemetry.
- Add the point most people miss: on failure the correct action is to do nothing and leave the message pending for XAUTOCLAIM. Acking inside the catch block silently discards failures, which is worse than no retry because you no longer know what you lost.
- Add the parameter trade-off: the idle threshold must exceed the worst-case normal processing time. Too small and a healthy in-flight message gets stolen and executed twice; too large and recovery is slow. Be explicit that tuning it only lowers the probability of duplicates — the real backstop is a uniqueness constraint on the consumer side.
- Expect: why XAUTOCLAIM rather than XCLAIM? XCLAIM needs an XPENDING scan first and then a named claim, with a race in between; XAUTOCLAIM scans and returns a cursor in one command, and is the recommended approach since Redis 6.2.
分析过程 · 先想清楚再作答
- 题眼在后半句。前半句背文档就能答,后半句在考你知不知道 ack 的时机直接决定了整个系统的投递语义——答不出这一点,面试官会判定你没在生产里管过队列。
- 先把两个命令的分工说清:XACK 是「销号」,把消息从 pending 清单里删掉,代表这件事真的做完了;XAUTOCLAIM 是「接手」,把闲置超过阈值的 pending 消息改判给另一个消费者,代表原来那个人可能已经死了。一个负责正常收尾,一个负责异常兜底。
- 然后回答时机问题,用一句话定性:先 ack 再干活是 at-most-once,先干活再 ack 是 at-least-once。前者进程一崩消息就人间蒸发,pending 清单里查不到、XAUTOCLAIM 也捡不回来;后者最坏是重复执行,而重复可以用幂等挡掉,丢单挡不掉。所以除了埋点日志这类丢一条无所谓的场景,一律先干活再 ack。
- 补一个大多数人漏掉的点:处理失败时正确的动作是**什么都不做**,让消息留在 pending 里等 XAUTOCLAIM。很多人会在 catch 里顺手 ack 掉,那等于把失败的消息静默丢弃,比不重试更糟——因为你连丢了什么都不知道。
- 再补一条 XAUTOCLAIM 的参数取舍:空闲阈值要大于「一次正常处理的耗时上限」。给太小会把还在正常处理的消息抢走,同一件事被跑两遍;给太大则故障恢复变慢。但要说清,调大阈值只降低重复概率,不消灭重复,兜底始终是消费端的唯一约束。
- 可以预期的追问:为什么用 XAUTOCLAIM 而不是 XCLAIM?答:XCLAIM 要你先 XPENDING 查出候选 id 再点名认领,两步之间还有竞态;XAUTOCLAIM 自己扫 pending 并返回游标,一条命令搞定,是 Redis 6.2 之后的推荐做法。
Key points
- XACK is the happy-path close-out: it clears the message from the pending list; repeat acks return 0, so it is naturally idempotent
- XAUTOCLAIM is the failure backstop: it reassigns pending messages idle past a threshold, answering 'what happens to work held by a dead consumer'
- Ack-before-work is at-most-once and loses work on a crash; work-before-ack is at-least-once and at worst duplicates, which idempotency can absorb
- Never ack on failure — leave the message pending for takeover; acking in the catch block silently discards failures
- The idle threshold should exceed worst-case processing time, but tuning it only reduces duplicates; uniqueness constraints are the real guarantee
答题要点
- XACK 负责正常收尾:把消息从 pending 清单里销号,代表这件事真的做完了;重复 ack 返回 0,天生幂等
- XAUTOCLAIM 负责异常兜底:把闲置超过阈值的 pending 消息改判给另一个消费者,解决「消费者死了它手上的消息怎么办」
- 先 ack 再干活是 at-most-once,崩溃就丢单;先干活再 ack 是 at-least-once,最坏是重复,可以用幂等挡
- 处理失败时不要 ack,让消息留在 pending 里等接手;在 catch 里顺手 ack 等于静默丢弃失败
- 空闲阈值要大于正常处理耗时的上限,但调大只降低重复概率,兜底仍是消费端唯一约束
What is at-least-once delivery, and given that messages get redelivered, how do you actually make the business side idempotent?什么是 at-least-once?既然消息会被重复投递,业务上到底要怎么保证幂等?
Common in ChinaCommon overseasDeep dive#message-bus#idempotency#reliabilityHow to reason about it · think before answering
- This is the question that gets probed hardest. Most candidates say 'at-least-once, so make the business idempotent' and stop — but the follow-up is exactly what matters: which line of code enforces it.
- Explain why the duplicate cannot be removed: committing the business write and acking are two writes to two systems (say Postgres and Redis), so there is always a crash window between finishing the work and XACK. The window can shrink but not disappear, which is why exactly-once is not something the bus gives you.
- That yields a sentence worth saying out loud: exactly-once is an effect produced by consumer-side idempotency, not a capability provided by the broker. Kafka transactions achieve it inside a read-Kafka-write-Kafka loop, but the moment the sink is a database or third-party API you are back to at-least-once.
- Now name the concrete guards and what each one blocks. First, a unique constraint on runs.idempotency_key with insert ... on conflict do nothing, which blocks duplicate submissions: on conflict the gateway returns the existing run id and never publishes a second bus message. Second, unique(run_id, seq) on the messages table, also on conflict do nothing, which blocks duplicate execution: even if two consumers finish the same run simultaneously the user sees one reply. A cheap short-circuit can sit in between — read the run first and just re-ack if it is already done — but that saves money; correctness comes from the two constraints.
- Then the step people get wrong: deriving the key. It must be reproducible from the same intent. Generating a fresh uuid on every retry is the classic mistake, because every retry becomes a new intent and the constraint never fires. The client should mint the key once and reuse it across retries; a server-side fallback can hash session id plus message body plus a second-resolution timestamp.
- Expect: what about irreversible side effects such as issuing a refund? Push the idempotency key into the external call (most payment gateways accept an idempotency key header), and record an 'initiated' row locally before calling so the same key deduplicates. For APIs with no such support, fall back to a local state machine plus reconciliation, and say plainly that you would move such operations off the automatic retry path.
分析过程 · 先想清楚再作答
- 这题是本章最容易被追到底的一道。绝大多数人能说出「至少一次,所以业务要幂等」,然后就没有下文了——面试官等的恰恰是下文:幂等具体落在哪一行代码上。答不出具体落点,前半句就是背的。
- 先解释为什么消费不掉这个重复:写业务和销号是两个系统的两次写(比如 Postgres 加 Redis),处理完成到 XACK 之间必然存在一个可以崩溃的窗口,崩在那里消息就会被重投。这个窗口只能变小,不能消失,所以 exactly-once 不是总线给你的语义。
- 由此得到一句可以直接说出口的结论:exactly-once 是消费端幂等做出来的**效果**,不是中间件提供的**能力**。Kafka 的事务能在「读 Kafka 写 Kafka」的闭环里做到,一旦下游是数据库或第三方 API 就又退回至少一次。
- 然后给具体落点,两道闸门要分清各自挡什么:第一道是 runs 表 idempotency_key 上的唯一约束,配 insert on conflict do nothing,挡的是**客户端重复提交**——冲突时接入层直接返回已有的 runId,连总线都不投第二遍;第二道是 messages 表的 unique(run_id, seq),同样 on conflict do nothing,挡的是**同一条总线消息被执行两遍**,就算两个消费者真的同时跑完,用户也只会看到一条回复。中间还可以加一道便宜的短路:捞到消息先看 run 是不是已经 done,是就直接补一个 XACK 走人——但那是省钱的优化,正确性靠的是那两个唯一约束。
- 接着讲最容易做错的一步:幂等键怎么取。它必须能从「同一个意图」稳定推出来。客户端每次重试都新生成一个 uuid 是最常见的错法,那每次都是新意图,唯一约束一次都命中不了,闸门形同虚设。正确做法是客户端生成一次、重试复用同一个值,服务端兜底可以用「会话 id 加消息内容哈希加秒级时间戳」。
- 可以预期的追问:不可逆的副作用怎么办,比如发一次退款?答:把外部调用也变成带幂等键的(大多数支付网关都支持 idempotency key 头),并且先在本地库里落一条「已发起」记录再调用,用同一个键去重;实在不支持的接口就只能靠本地状态机加人工对账,这时要主动说出「这类操作我会把它挪出重试路径」。
Key points
- At-least-once means a message is processed one or more times, because the business commit and the XACK are two writes to two systems with an unavoidable crash window
- Exactly-once is an effect of consumer-side idempotency, not a broker feature; any database or third-party sink puts you back at at-least-once
- Guard one: a unique constraint on runs.idempotency_key with on conflict do nothing blocks duplicate submissions and skips publishing a second bus message
- Guard two: unique(run_id, seq) on messages with on conflict do nothing blocks duplicate execution, so the user sees exactly one reply
- The idempotency key must be derivable from the same intent and reused across retries; minting a new uuid per retry defeats the whole mechanism
- For irreversible side effects, pass the idempotency key through to the external API and record an initiated row locally before calling
答题要点
- at-least-once:消息至少被处理一次、可能多次,因为业务提交和 XACK 是两个系统的两次写,中间的崩溃窗口消不掉
- exactly-once 是消费端幂等做出来的效果,不是中间件的能力;下游只要是数据库或第三方 API 就退回至少一次
- 闸门一:runs.idempotency_key 唯一约束 + on conflict do nothing,挡客户端重复提交,冲突时不再投递总线消息
- 闸门二:messages 表 unique(run_id, seq) + on conflict do nothing,挡同一条消息被执行两遍,用户只会看到一条回复
- 幂等键必须从同一个意图稳定推导,客户端重试要复用同一个值;每次重试新生成 uuid 等于没有幂等
- 不可逆副作用要把幂等键透传给外部接口,并先落一条本地记录再调用
How do you choose between Redis Streams and Kafka, and when is Streams clearly not enough?Redis Streams 和 Kafka 该怎么选?什么情况下 Streams 明显不够用?
Common in ChinaCommon overseasIntermediate#message-bus#redis-streams#architectureHow to reason about it · think before answering
- The bad answer is 'it depends on volume'. Throughput is never the first criterion — a single Redis node handles tens of thousands of XADDs per second, and most workloads never approach that ceiling. 'Small volume Streams, large volume Kafka' reads as never having run a real evaluation.
- Use two real criteria instead: how long the messages must be retained, and whether a second class of consumer will appear. If a message is useless once executed and the execution layer is the only consumer, Streams is plenty and saves an entire operational surface. If you need replay from any point in the last three months, or the same data must feed real-time execution, an offline warehouse and a risk engine, choose Kafka.
- Add three structural differences: Streams is memory-first with retention you enforce yourself via MAXLEN or XTRIM, while Kafka does sequential disk writes and keeps weeks by default; a Streams group takes any number of consumers, while Kafka consumers are capped by partition count and extras idle; ordering granularity differs — Streams orders a single stream but dispatches randomly within a group, Kafka pins a key to a partition and orders within it.
- Then volunteer the line that shows real depth: Redis persistence is lossy. AOF fsyncs once per second by default, so the last second of writes can vanish, and replication is asynchronous, so a failover can drop unreplicated messages. Using Streams therefore requires a source of truth elsewhere — here the Postgres runs table, with the stream acting only as a trigger; a lost message leaves the run pending and a sweeper republishes it. Treating the bus as the only datastore is the dangerous misuse.
- Land on a reusable rule: Streams suits triggering work, Kafka suits data pipelines. One carries one-shot commands, the other carries facts that many parties re-read.
- Expect: what about RabbitMQ or SQS? RabbitMQ wins on complex routing and delayed delivery (Streams has no native delay, you republish with a next-eligible timestamp); SQS wins on zero operations at the cost of replay and strict ordering (FIFO queues aside). Framing the criteria as retention, number of consumers, routing complexity and operational budget beats reciting product specs.
分析过程 · 先想清楚再作答
- 这题的坏答案是「看数据量」。吞吐从来不是第一判据——单机 Redis 每秒几万条 XADD 毫无压力,绝大多数业务的量级根本碰不到天花板。答成「量小用 Streams、量大用 Kafka」会被认为没做过选型。
- 换成两个真正的判据来推:一、这些消息需要保留多久;二、会不会有第二类消费方。生命周期是「执行一次就没用了」、且只有执行层这一个消费方,Streams 完全够用,还省掉一整套运维;需要「三个月内任意时间点重放」、或者同一份数据要同时喂给实时执行、离线数仓、风控三条链路,那就该上 Kafka。
- 再补三条结构性差异:Streams 是内存为主、保留全靠你自己 MAXLEN 或 XTRIM,Kafka 是磁盘顺序写、保留几周是常态;Streams 一个组里加多少消费者都行,Kafka 的消费者数受分区数限制,多了就有人空转;顺序保证的粒度不同,Streams 是单条流内有序而组内分配随机,Kafka 是同 key 落同分区、分区内有序。
- 然后主动说出那条最能体现深度的话:Redis 的持久化是有损的。AOF 默认每秒刷盘,最坏丢最后一秒的写入;主从异步复制,故障切换时未同步的消息会消失。所以用 Streams 时架构上必须有一个真相之源——本课是 Postgres 的 runs 表,流只是触发器,丢了消息那个 run 还停在 pending,补投任务会把它捡回来。把总线当唯一数据源是最危险的误用。
- 结论落成一句可复用的判断:Streams 适合「触发执行」,Kafka 适合「数据管道」。前者的消息是一次性的命令,后者的消息是需要被多方反复读取的事实。
- 可以预期的追问:那 RabbitMQ、SQS 呢?答:RabbitMQ 强在复杂路由和延迟队列(Streams 没有原生延迟投递,要自己带「下次可执行时间」重投);SQS 强在零运维,代价是没有回放、也没有严格顺序(FIFO 队列另算)。把判据说成「保留时长、消费方数量、路由复杂度、运维预算」四条,比背产品参数强得多。
Key points
- The first criterion is not throughput but retention length and whether a second class of consumer will exist
- One consumer class and messages that expire on execution: Streams is enough, and you probably already run Redis
- Long retention with arbitrary replay, or one dataset feeding several downstream pipelines: pick Kafka
- Structural differences: Streams is memory-first with self-managed trimming and random in-group dispatch; Kafka is sequential-disk, key-partitioned with in-partition ordering, and caps consumers at partition count
- Redis persistence is lossy (per-second AOF fsync, async replication), so the database must be the source of truth with the stream as a trigger plus a republish sweeper
- One-line rule: Streams triggers work, Kafka moves data
答题要点
- 第一判据不是吞吐,是「消息要保留多久」和「会不会有第二类消费方」
- 只有执行层一个消费方、消息执行完即失效:Streams 够用,且大概率你已经有 Redis,零新增运维
- 需要长期保留与任意时间点回放、或多条下游链路共用同一份数据:选 Kafka
- 结构差异:Streams 内存为主、保留靠自己裁剪、组内分配随机;Kafka 磁盘顺序写、按 key 分区且分区内有序、消费者数受分区限制
- Redis 持久化有损(AOF 每秒刷盘、异步复制),所以真相之源必须是数据库,流只当触发器,靠补投任务兜底
- 一句话判断:Streams 适合触发执行,Kafka 适合数据管道
What do you do with a message that keeps failing? Design a poison-message isolation mechanism.一条消息反复处理失败怎么办?请设计一个毒消息隔离机制。
Common in ChinaCommon overseasIntermediate#message-bus#error-handling#reliabilityHow to reason about it · think before answering
- This question probes whether you have ever watched one bad message stall an entire stream. The test is simple: does your answer contain a concrete threshold and a concrete place where isolation happens? If not, you are talking theory.
- Describe the failure mode first: under at-least-once you do not ack on failure, so the message stays pending and gets redelivered. A message that fails for everyone therefore loops forever — delivered, failed, idle timeout, claimed, failed — never recovering while continuously consuming worker capacity.
- Then give the mechanism, three actions and all of them required. One, use the delivery count the pending list already tracks rather than building a counter table. Two, past the threshold (three deliveries in this course) move the message to a dead-letter stream carrying the original id, delivery count and failure reason. Three, XACK the original stream and mark the run failed with the error recorded. Moving without acking leaves it pending for another takeover; acking without moving makes both the message and its reason disappear, leaving the user stuck on 'thinking'.
- Justify the threshold: one delivery kills messages that a single network blip would have let through; ten wastes ten executions of money and time on a message that can never succeed. Three deliveries, spaced by the idle threshold, survives almost all transient faults.
- Volunteer a limitation: Redis Streams has no native exponential backoff — redelivery timing is governed by the idle threshold. Backoff requires republishing the message with a next-eligible timestamp, which means building a delay queue yourself. Naming this shows you know where Streams ends.
- Expect: is creating the dead-letter stream the end of it? No. Its depth must be alerted on, since going from zero to non-zero usually means a class of input your code cannot handle — a real bug, not bad luck. Keep a replay path too: republish the stored fields back to the original stream, and because the idempotency key is preserved, replay cannot cause duplicate execution. Teams that build a dead-letter stream and never open it have simply muted their failures.
分析过程 · 先想清楚再作答
- 这题在考你有没有踩过「一条坏消息拖垮整条流」。判断标准很简单:你的回答里有没有出现一个具体的阈值和一个具体的落地位置,没有就是在讲概念。
- 先把故障模式说清楚:按 at-least-once 的规矩,失败就不 ack、留在 pending 等重投,于是一条无论谁来都会失败的消息进入死循环——投递、失败、闲置超时、被接手、再失败。它自己永远好不了,还持续占用消费者的处理能力。
- 然后给机制,三个动作缺一不可:一、判定依据用 pending 清单自己记的投递次数,不要另建计数表;二、超过阈值(本课固定 3 次)就把消息搬到一条死信流,字段里带上原始消息 id、投递次数和失败原因;三、对原流 XACK,同时把这次执行标成失败并写入错误原因。只搬不 ack,它还躺在 pending 里等着被接手;只 ack 不搬,消息和失败原因一起消失,用户永远停在「正在思考」。
- 阈值的取值要给出权衡:定 1 会让一次网络抖动就把本来能成功的消息判死;定 10 会在一条必死的消息上浪费十次执行的钱和时间。3 次配合每次之间的空闲阈值,足够熬过绝大多数瞬时故障。
- 还要主动说出一个缺口:Redis Streams 没有原生的指数退避,重投时机由空闲阈值决定。想要退避就得自己把消息重新投递并带上「下次可执行时间」,那已经是在实现延迟队列了——这一条能体现你知道 Streams 的边界在哪。
- 可以预期的追问:死信流建完就完了吗?答:不。死信条数必须接进告警,它从 0 变成非 0 通常意味着有一类输入你的代码处理不了,是真 bug 而不是运气差;还要留一个重放入口——把死信里的字段原样投回原流即可,因为幂等键还在,重放不会产生重复执行。见过团队把死信建起来半年没打开过,那等于把故障静音了。
Key points
- Failure mode: under at-least-once you do not ack on failure, so an always-failing message is redelivered forever and keeps consuming worker capacity
- Use the delivery count already tracked in the pending list rather than a separate counter table
- Fix the threshold at three deliveries: one kills transient failures, ten wastes ten executions on a message that can never succeed
- Isolation needs all three actions: move to a dead-letter stream with original id, delivery count and reason; XACK the original stream; mark the run failed with the error stored
- Redis Streams has no native exponential backoff — redelivery timing follows the idle threshold, so backoff means implementing delayed republishing yourself
- Alert on dead-letter depth and keep a replay path; the idempotency key survives, so replay cannot duplicate execution
答题要点
- 故障模式:at-least-once 下失败不 ack,一条永远失败的消息会无限重投并持续占用消费者
- 判定依据用 pending 清单里记的投递次数,不需要另建计数表
- 阈值固定 3 次:定 1 会误杀瞬时故障,定 10 会在必死消息上浪费十次执行成本
- 隔离动作三件缺一不可:搬到死信流(带原始 id、投递次数、失败原因)、对原流 XACK、把这次执行标成失败并写入原因
- Redis Streams 没有原生指数退避,重投时机由空闲阈值决定,要退避得自己实现延迟投递
- 死信流要接告警并留重放入口;幂等键还在,重放不会导致重复执行
D10 Sharding and Leases: Hashing userId → shard, SET NX + TTL + Lua Renewal, Per-User Ordering, Handoff
Why hash user ids into shards instead of letting the consumer group dispatch freely, and how do you pick the shard count?为什么要对 userId 做哈希分片,而不是让消费组随机派发?分片数应该怎么选?
Common in ChinaCommon overseasBasic#sharding#consistent-hashing#scalabilityHow to reason about it · think before answering
- The hinge is 'why not dispatch freely'. Answering 'for load balancing' misses it — a consumer group already balances load, and free dispatch balances better than hashing. Sharding buys something else: affinity.
- The chain: a consumer group's unit of assignment is one message, while the business requires one user as the smallest serial unit. When those units disagree, two messages from the same user get processed concurrently by two workers.
- Second step: why insert a shard layer instead of taking userId modulo the worker count? Because the worker count changes on scale-up, restart, crash and rolling deploy. Change the divisor and almost every user is remapped, so in-flight sessions migrate wholesale. A fixed shard count pins user-to-shard and lets only shard-to-worker float.
- For the count, give criteria rather than a number: it caps parallelism (256 shards means at most 256 useful workers), and changing it is a data migration (every user is remapped, requiring downtime or a dual-write transition). So oversize it up front — 256 across 3 workers is 85/85/86 and costs a few hundred keys of memory, while picking 8 walls you in at the ninth worker. Use a power of two so the modulo degrades to a bit mask and future splits stay clean.
- Volunteer the limit of uniformity: it means uniform user counts, not uniform message volume. One enterprise account sending a thousand messages a day can share a shard with a thousand one-message users. The fix is an exception table before the hash that gives that account its own shard, not a larger shard count — that would be the migration above.
- Expect the follow-up: why not consistent hashing? It optimizes remap volume, which pays off when shards carry state that is expensive to move. Our workers are stateless executors with state in Postgres and Redis, so nothing needs moving, and shard ownership is already decided dynamically by leases. Fixed sharding optimizes predictability, which is simpler and more reliable here.
分析过程 · 先想清楚再作答
- 题眼在「为什么不随机派发」。只答「为了负载均衡」就掉进坑里了——消费组本来就是负载均衡,随机派发在均衡上比哈希分片更好。分片解决的是另一件事:亲和性。
- 推导链是这样的:消费组的分配单位是「一条消息」,而业务要求的最小串行单位是「一个用户」;单位对不上,同一个用户连发的两句话就会被两个进程同时处理。所以要把分配单位从消息抬到用户。
- 第二步是「为什么中间要垫一层 shard,而不是 userId 直接取模 worker 数」。因为 worker 数会变——扩容、重启、崩溃、滚动发布;除数一变,几乎所有用户的归属都会变,正在处理的会话被整体搬家。固定的 shard 数把「用户到 shard」钉死,只让「shard 到 worker」随伸缩浮动。
- 分片数怎么选,要给出可执行的判据而不是一个数字:它是并行度的上限(256 个 shard 最多让 256 个 worker 有活干),而且改它等于一次数据迁移(所有用户归属重算,必须停机或双写过渡)。所以宁可一开始定得偏大——256 摊在 3 个 worker 上是 85、85、86,多出来的成本只是几百个 key 的内存;定成 8 个的话扩到第 9 个 worker 就撞墙了。要用 2 的幂,取模能退化成位运算,也方便将来对半拆分。
- 主动说出哈希均匀的边界:均匀说的是「用户数均匀」,不是「消息量均匀」。一个日发千条的大客户可能和一千个散户落在同一个 shard 上。缓解是给大客户在哈希前加一张小的例外表、单独占一个 shard,而不是把总分片数调大(那就是上面说的数据迁移)。
- 可预期的追问:为什么不用一致性哈希?答案是它优化的是「节点变化时的迁移量」,前提是分片承载状态、搬迁很贵。我们的 worker 是无状态执行体,状态在数据库和 Redis 里,没有数据要搬;而且 shard 到 worker 的归属本来就由租约动态决定。固定分片优化的是可预测性,在这个场景里更简单,也更可靠。
Key points
- Sharding is about affinity, not balancing: it lifts the unit of assignment from one message to one user so a user always lands on the same worker
- The fixed shard layer keeps user-to-shard stable across scaling; only shard-to-worker ownership moves
- The shard count caps parallelism and changing it is a migration, so oversize it and use a power of two (256 in this course)
- Uniform hashing means uniform user counts, not uniform traffic; hot accounts need an exception table before the hash
- Consistent hashing optimizes remap volume and only pays off for stateful shards; stateless workers do better with fixed shards
答题要点
- 分片解决的是亲和性不是负载均衡:把分配单位从「一条消息」抬到「一个用户」,同一个用户永远落到同一个 worker
- 中间垫一层固定 shard,是为了让 worker 伸缩时用户到 shard 的映射保持不变,只有 shard 到 worker 的归属浮动
- 分片数是并行度上限,改它等于一次数据迁移,所以一开始就定偏大、用 2 的幂(本课 256)
- 哈希均匀保的是用户数均匀,不是消息量均匀;大客户热点要靠哈希前的例外表单独拆 shard
- 一致性哈希优化迁移量,只在分片带状态时划算;无状态 worker 用固定分片更简单
Why must a lease carry a TTL, and why renew it with a Lua script instead of GET followed by PEXPIRE?租约为什么必须配合 TTL?续约为什么要用 Lua 脚本,而不是先 GET 再 PEXPIRE?
Common in ChinaCommon overseasIntermediate#lease#redis#atomicityHow to reason about it · think before answering
- There are two things being tested and the second is the discriminator. The first is really 'do you know a lease is not a lock': a lock means mutual exclusion (I hold, you wait, you get it when I release), while a lease means ownership with an expiry (it lapses even if the holder never releases, because the holder may never come back).
- That gives you the necessity of the TTL: holders get kill -9'd, lose the network, lose the whole machine — they never get to hand anything back. Without a TTL you have a lock that is never released and a shard that is permanently orphaned until a human intervenes.
- Volunteer the TTL trade-off to show you have tuned this: too short and a GC pause or a network blip costs you the lease, so shards flap and sessions keep migrating; too long and a genuinely dead worker's shards sit idle for a full TTL. A common setting is a 30 second TTL renewed every 10 seconds (one third), which tolerates two consecutive renewal failures.
- The second point is atomicity, and you should spell out the failing interleaving: GET says the lease is yours, then within two milliseconds it expires, Redis drops it, another worker wins it with SET NX, and your PEXPIRE succeeds — you have just extended your rival's lease while believing you still hold the shard. If step two is SET rather than PEXPIRE you also overwrite their owner field and both processes start working.
- Land on the general principle: check-and-mutate must be indivisible (compare-and-swap). Redis executes commands single-threaded, so one EVAL is a single atomic step to every other client — Lua here is not about performance, it is about fusing GET and PEXPIRE. Redis Functions or WATCH plus a transaction retry are equivalent, but Lua is the most direct.
- Expect the follow-up: what should a renewal returning 0 do? Let go immediately — drop the shard from the held set, stop consuming, and refuse to write the in-flight item. Logging a warning and carrying on is the most common source of split brain. Add a self-kill rule too: if the last successful renewal is older than two thirds of the TTL, release everything.
分析过程 · 先想清楚再作答
- 这题有两个考点,第二个才是区分度。第一个考点其实是在问「你知不知道租约和分布式锁不是一回事」——先把这条说清:锁的语义是互斥(我持有、你等待,我主动 release 你才拿得到),租约的语义是带过期时间的所有权(持有者不 release 也会失效,因为它可能永远不会回来了)。
- 由此推出 TTL 的必要性:持有者会被 kill -9、会断网、会整台机器掉电,它没有机会归还。没有 TTL 就是一把永不释放的锁,那个 shard 从此永久荒废,只能靠人工介入。TTL 的全部意义是「不需要任何人干预,所有权会自己失效」。
- 顺手说出 TTL 的取舍,证明你调过:太短则一次垃圾回收停顿或网络抖动就丢租约,shard 反复易主、用户会话来回搬家;太长则真死了之后要等满一个 TTL 才有人接手。常见口径是 TTL 30 秒、续约间隔取 TTL 的三分之一(10 秒),这样能连续失败两次而不丢租约。
- 第二个考点是原子性。两步写法的失败时间线要具体讲出来:GET 返回「是我的」,紧接着的两毫秒里租约恰好到期被 Redis 删除、另一个 worker SET NX 抢到,然后你的 PEXPIRE 执行成功——你续的是对手的租约,而自己还以为持有。如果第二步用的是 SET 而不是 PEXPIRE,你还会把对手的名字覆盖成自己,两个进程一起动手。
- 结论要落到通用原理上:检查和改动必须是一个不可分割的动作(compare-and-swap)。Redis 单线程执行命令,一整段 EVAL 对其他客户端就是一个原子步骤,所以 Lua 在这里不是为了性能,是为了把 GET 和 PEXPIRE 粘成一条。等价手段还有 Redis 函数、或用 WATCH 加事务重试,但 Lua 最直接。
- 可预期的追问:续约返回 0 应该怎么办?答「立刻放手」——把这个 shard 从持有集合里删掉、停止取消息、手上那条没做完的不许再写。返回 0 只打一行警告日志然后继续跑,是脑裂最常见的来源。再加一条自杀规则:距上次成功续约超过 TTL 的三分之二就主动全部放手。
Key points
- A lease is not a lock: locks give mutual exclusion, leases give ownership with an expiry, because the holder may never return
- Without a TTL you have a never-released lock and a permanently orphaned shard once the holder is killed
- A 30 second TTL renewed every 10 seconds leaves headroom for two consecutive renewal failures
- In the two-step window the lease may already have changed hands, so your PEXPIRE extends a rival's term while you still think you hold it
- Lua fuses the ownership check and the extension into one atomic step; a renewal returning 0 means let go immediately
答题要点
- 租约不是锁:锁是互斥,租约是带过期时间的所有权;持有者可能永远不会回来,所以所有权必须能自己失效
- 没有 TTL 就是永不释放的锁,持有者被 kill 之后那个 shard 永久荒废
- TTL 30 秒、续约间隔 10 秒(TTL 的三分之一),留出连续两次续约失败的余量
- 两步续约的窗口里租约可能已易主,你的 PEXPIRE 会替对手延长任期,而自己仍以为持有
- Lua 的作用是把「比较持有者」和「续期」粘成一个原子步骤,不是为了性能;续约返回 0 必须立刻放手
What happens when two workers both believe they hold the same shard lease (split brain), and how do you mitigate it?两个 worker 同时认为自己持有同一个 shard 的租约(脑裂)会造成什么后果,怎么规避?
Common in ChinaCommon overseasDeep dive#split-brain#fencing-token#reliabilityHow to reason about it · think before answering
- The scoring criterion here is explicit: does your answer contain the sentence 'a Redis lease alone cannot give absolute mutual exclusion'. Anyone who says SET NX plus a TTL makes it safe gets probed until they run out of answers.
- Start with how split brain arises, and use the common case: not a crash, but a holder that merely froze for five seconds — a full GC, a noisy neighbor saturating the host CPU, cgroup throttling. It wakes up still believing it holds shard 68, keeps processing the in-flight message and keeps writing, while the lease expired and was taken. Add the second layer: Redis replication is asynchronous, so a failover can lose the last few milliseconds of writes and let two workers both win SET NX.
- Then the consequences, expressed in business terms rather than 'inconsistent data': two messages from one user processed concurrently means out-of-order replies, a corrupted context window, and unique(run_id, seq) violations that silently drop a message. Worst is reordered or duplicated side effects — swap 'cancel the order' with 'move the delivery date' and you cancel an order the user wanted to keep.
- The key shift: since you cannot rule out that timeline on the Redis side, the goal is not to prevent split brain but to make the second writer's writes fail — push conflict detection and rejection down to the layer that actually causes side effects.
- Give three mitigations by value. First, a self-kill rule in the worker: after two consecutive renewal failures, or when the last success is older than two thirds of the TTL, stop processing and clear the held set — cheapest, and it bounds the 'I think I still hold it' window to two renewal periods. Second, fencing tokens: take a monotonically increasing number (Redis INCR) when acquiring, store it in the lease value, attach it to every side-effecting operation, and have the downstream accept only numbers not lower than the highest it has seen — in a database that is one conditional update. The revived predecessor carries a stale number and is rejected. Third, re-validate the lease immediately before each write inside the same script or transaction, which shrinks the window without closing it.
- Expect the follow-up: where does fencing break down? It needs downstream cooperation. Databases do conditional updates, but a third-party endpoint (SMS, payments) will not compare your token, so you fall back to idempotency keys that make duplicate execution harmless rather than impossible. True mutual exclusion means moving to a consensus-backed system such as etcd or ZooKeeper session leases, paying in write latency and operational complexity.
分析过程 · 先想清楚再作答
- 这题的判分点非常明确:答案里有没有出现「单靠 Redis 租约做不到绝对互斥」。说「用了 SET NX 加 TTL 就安全了」的人,会被追问到答不上来。
- 先讲脑裂是怎么发生的,而且要举那个最常见的场景——不是进程崩溃,是持有者只卡了 5 秒:一次 full GC、宿主机 CPU 被邻居打满、容器被 cgroup 限流。它醒过来时内存里还写着「我持有 shard 68」,继续处理手上那条消息、继续写库,而 Redis 里的租约早已到期并被别人抢走。再补一层:Redis 主从复制是异步的,切主时可能丢掉最后几毫秒的写入,于是两个 worker 都能 SET NX 成功。
- 然后讲后果,而且要落到业务上而不是停在「数据不一致」:同一个用户的两条消息被两个进程并发处理,回复乱序、上下文错乱、messages 表的 unique(run_id, seq) 撞约束导致落库失败;最严重的是有副作用的工具被重排或重复执行——「取消订单」和「改配送日期」顺序反了,结果是取消了一个用户本来想留下的订单。
- 关键的认知转折:既然无法在 Redis 一侧排除这条时间线,正确的思路就不是「让脑裂不发生」,而是「让第二个人的写入落不了地」——把冲突的检测与拒绝推到真正产生副作用的那一层。
- 三条手段按性价比给出。一是 worker 自己的自杀规则:连续两次续约失败、或距上次成功续约超过 TTL 的三分之二,立刻停止处理并清空持有集合——最便宜,把「我以为我还持有」的窗口从无限压到两个续约周期。二是 fencing token:抢租约时从一个单调递增计数器取号(Redis 的 INCR)写进租约值,之后所有有副作用的操作都带上它,下游只接受不比见过的最大号小的写入,落到数据库上就是一句条件更新;醒过来的前任拿的是旧号,写入直接被拒。三是每次写之前重新校验租约,并把校验与写入放进同一段脚本或同一个事务——这只缩小窗口,不消除。
- 可预期的追问:fencing 的局限在哪?答「它需要下游配合」。数据库能做条件更新所以好使,但下游是第三方接口(发短信、扣款)时你没法让对方帮你比号,这时只能退回幂等键,把重复执行变成无害,而不是让它不发生。真要绝对互斥就得换到有共识协议的系统(etcd、ZooKeeper 的会话租约),代价是写入延迟和运维复杂度。
Key points
- A Redis lease alone cannot guarantee mutual exclusion: a frozen holder that revives, and asynchronous replication losing writes on failover, are both unavoidable
- State consequences in business terms: out-of-order replies, corrupted context, unique-constraint violations dropping messages, and reordered or duplicated side effects
- The goal is to make the second writer's writes fail — push conflict detection to the side-effecting layer instead of hoping split brain never happens
- Three mitigations: a worker self-kill rule on repeated renewal failure, fencing tokens enforced as conditional updates, and re-validating the lease immediately before writing
- Fencing needs downstream cooperation; against third-party endpoints fall back to idempotency keys, and true mutual exclusion means a consensus system like etcd or ZooKeeper
答题要点
- 单靠 Redis 租约做不到绝对互斥:持有者被冻结再醒来、以及主从异步复制丢写,这两条时间线排除不掉
- 后果要落到业务:同用户回复乱序、上下文错乱、唯一约束冲突丢消息,最严重是有副作用的工具被重排或重复执行
- 思路是「让第二个人的写入落不了地」,把冲突检测推到产生副作用的那一层,而不是指望脑裂不发生
- 三条手段:worker 自杀规则(续约连续失败就放手)、fencing token(写入时带单调号做条件更新)、写前重新校验租约
- fencing 需要下游配合;下游是第三方接口时只能退回幂等键,要绝对互斥就得换 etcd / ZooKeeper 这类有共识协议的系统
In a multi-worker agent service, how do you guarantee that one user's messages are processed in strict order?在一个多 worker 的 Agent 服务里,怎么保证同一个用户的消息严格按顺序被处理?
Common in ChinaCommon overseasIntermediate#ordering#sharding#distributed-systemsHow to reason about it · think before answering
- This is a small system-design question testing whether you can decompose ordering into layered guarantees rather than naming a middleware. 'Partition by key in Kafka' is not wrong, but it leaves 'and inside the process?' unanswered, which is exactly where they will push.
- Decompose it along the path from ingress to side effect, four layers. One, ordered ingress: the gateway assigns consecutive seq numbers per session on write and publishes in seq order; a single stream is append-ordered, so this layer is nearly free. Two, single consumer: only one worker reads a given shard at a time, enforced by the lease — that is the cross-process half.
- Three, in-process serialization: no two messages from the same shard may be handled concurrently. This is the layer people break themselves, by dropping a batch into Promise.all or a thread pool to raise throughput. Say it explicitly: the lease preserves order across processes, await preserves it inside one. Four, in-flight first: a killed predecessor may hold a delivered but unacknowledged message, so the successor must claim it back before reading anything new, otherwise a newer message jumps ahead of an older one.
- Then name the cost of serialization, which is where they judge whether you have shipped this: a single slow request blocks other users on the same shard, and one 20-second model call can stall every shard that worker owns. The right shape is parallel across shards, serial within a shard — one independent processing chain per held shard. The unit of parallelism is the shard, not the message.
- Volunteer the boundary: this only guarantees per-user order, never a global order across users. Global ordering requires parallelism of one, which defeats the point. Ordering and parallelism trade off directly, so sharding exists to shrink the 'must be ordered' scope to the smallest useful unit.
- Expect two follow-ups. Could you skip leases? Yes — Kafka key partitioning or sticky routing from the gateway to a fixed worker also gives affinity, at the cost of rigid partition counts or of needing a separate failover mechanism when a worker dies; the lease happens to solve failover at the same time. Could the business simply tolerate reordering? Partly, if appends are idempotent and commutative, but any irreversible side effect such as a refund or a shipment forces you to preserve order.
分析过程 · 先想清楚再作答
- 这题是系统设计小题,考的是你能不能把「顺序」拆成分层的保证,而不是丢一个中间件名字。只答「用 Kafka 按 key 分区」不算错,但没有回答「分区之后进程内怎么办」,会被追着问。
- 拆法是从消息进入系统到产生副作用,逐层点出谁在保顺序,一共四层。第一层入队有序:接入层落库时给同一会话的消息发连续 seq,并按 seq 投递,总线对同一条流是追加有序的,这层几乎免费。第二层消费者唯一:同一个分片同一时刻只有一个 worker 在读,靠租约实现——这是跨进程的那一半。
- 第三层进程内串行:同一个分片内不能并发处理两条消息。这一层最容易被自己破坏——为了提高吞吐把一批消息丢进 Promise.all 或线程池,顺序就在自己的代码里丢掉了。要明确说出「租约保住跨进程的顺序,await 保住进程内的顺序,缺一不可」。第四层在途优先:前任 worker 挂掉时手上可能有一条已领取但没确认的消息,接管者必须先把它 claim 回来再读新消息,否则新消息会插到旧消息前面。
- 紧接着说串行的代价,这是面试官判断你有没有上过线的地方:串行意味着一个用户的慢请求会挡住同一个分片上其他用户的消息,一次 20 秒的模型调用能让这个 worker 名下的几十个分片全部停摆。正确做法是按分片并行、分片内串行——每个持有的分片各起一条独立处理链。并行的单位是分片,不是消息。
- 主动划边界:这套机制只保证同一个用户的顺序,不保证跨用户的全局顺序。全局有序需要把并行度压到 1,那就没有分布式可谈了。顺序性和并行度是一对反比,分片的意义就是把「必须有序」的范围缩到刚好够用的最小值。
- 可预期的追问一:不用租约行不行?可以,Kafka 按 key 分区、或者让 Gateway 直连固定 worker(粘性路由)都能得到亲和性,但代价分别是分区数难改、以及 worker 挂掉时需要额外的故障转移机制——租约恰好把故障转移也一并解决了。追问二:能不能干脆让业务对乱序免疫?部分可以,比如把「追加消息」设计成幂等且可交换的写入,但只要存在不可逆的副作用(退款、发货),顺序就必须保。
Key points
- Decompose ordering into four layers: ordered ingress with consecutive seq, a single consumer per shard via the lease, in-process serialization with await, and claiming the predecessor's in-flight message first
- The lease preserves order across processes and await preserves it within one — reaching for Promise.all to raise throughput destroys it
- The unit of parallelism is the shard, not the message: one chain per held shard, or a single slow call stalls every shard that worker owns
- Only per-user order is guaranteed, never a global order; ordering trades off against parallelism, so sharding shrinks the ordered scope
- Alternatives are Kafka key partitioning or sticky routing, but neither brings failover; any irreversible side effect makes ordering mandatory
答题要点
- 把顺序拆成四层:入队有序(连续 seq)、消费者唯一(租约)、进程内串行(逐条 await)、在途消息优先被接管者 claim 回来
- 租约保住跨进程的顺序,await 保住进程内的顺序,缺一不可——用 Promise.all 提吞吐会当场毁掉顺序
- 并行的单位是分片不是消息:每个持有的分片各起一条独立处理链,否则一次慢调用会拖停这个 worker 的全部分片
- 只保证同一用户的顺序,不保证跨用户全局有序;顺序性和并行度是反比,分片就是把有序范围缩到最小
- 替代方案是 Kafka 按 key 分区或粘性路由,但它们不自带故障转移;只要存在不可逆副作用,顺序就必须保
D11 The Run State Machine, Streaming Output Back, Ordering by runId, SSE Waiters, Merging Interruptions Within 30 Seconds
How would you design the state machine for one agent run, and which failure states must it cover?怎么设计一次 Agent 执行(run)的状态机?需要覆盖哪些异常状态?
Common in ChinaCommon overseasBasic#state-machine#distributed-systemsHow to reason about it · think before answering
- The discriminator is not listing states, it is explaining why a single-process service does not need them at all. Without that, you have only memorized a diagram.
- Start from motivation: in one process the call stack *is* the state. Once you split gateway and worker, three parties must answer the same question independently — the gateway decides whether to keep an SSE connection open, the worker decides whether someone already claimed the message, and a reopened browser tab asks whether the previous question is still generating. Different processes, so the answer has to live in a table.
- Then the states: pending to running to streaming to done on the happy path, with failed (retries exhausted) and cancelled (superseded by a merge, or user-cancelled) as exits available from anywhere. Volunteer why running and streaming are separate: running means claimed but no token yet, streaming means the first token is out. That boundary is your time-to-first-token probe and the frontend's cue to switch from spinner to typewriter.
- Land on the real purpose: the machine exists to reject writes. Terminal states having no outgoing edges is the most valuable row in the table. Under at-least-once delivery, a done run receiving one more chunk is routine, and without the table that chunk lands silently — the user sees half a sentence appended and the logs show nothing wrong.
- Add the discipline that separates shipped from read-about: every status write goes through one transition function. One raw UPDATE that bypasses it and the state machine is just a comment.
- Expect the follow-up on storage and concurrency: the database row is the single source of truth, and transitions are conditional updates that include the expected current status in the WHERE clause. Zero rows affected means someone moved first — re-read and decide, never blindly overwrite.
分析过程 · 先想清楚再作答
- 这题的区分度不在「能不能列出几个状态」,而在你有没有说出「为什么单进程时代不需要它」。答不出这一点,说明你只是抄过一张状态图。
- 先给动机:单进程里「执行到哪一步了」就是那个函数栈,状态存在于进程内存里,不需要名字。拆成 Gateway 与 Worker 之后,至少三方要同时回答同一个问题——接入层要判断还挂不挂 SSE,执行层要判断这条消息是否已被人领走,前端重开页面要判断上次的问题还在不在生成。三方不同进程,只能靠一张表对齐。
- 再给状态:pending 到 running 到 streaming 到 done 是正常路径,failed(重试耗尽)与 cancelled(被打断合并或用户取消)是两个随时可以走的异常出口。主动说明为什么 running 和 streaming 要分开:前者是「有人领走了但还没有一个字」,后者是「第一个字已出来」,这条线就是首字延迟的观测点,也是前端决定转圈还是打字机的依据。
- 结论要落到「状态机是用来挡写入的」:终态没有出边这一条最值钱。至少一次投递下「已经 done 的 run 又收到一个片段」是常态,没有转换表,那一笔会安静地写进库,用户看到回复末尾多出半句话,而日志里查不出是谁写的。
- 补一条纪律,这是有没有落地过的分水岭:所有写状态的地方都必须过同一个转换函数。绕过它直接执行一条更新语句,状态机就退化成注释了。
- 可以预期的追问:状态存哪、并发怎么办?答数据库那一行是唯一真相,转换用带条件的更新(更新时把当前状态写进 where 子句),失败说明有人抢先改过,这时候重读再决定,而不是覆盖。
Key points
- In one process the call stack is the state; after splitting gateway and worker, three parties need the same answer, so it has to be a table
- Happy path pending, running, streaming, done; exits are failed (retries exhausted) and cancelled (merged or user-cancelled)
- Separating running from streaming gives you a time-to-first-token probe and tells the UI when to switch from spinner to typewriter
- Terminal states with no outgoing edges reject the late chunks that at-least-once delivery guarantees you will get
- Every status write goes through one transition function, implemented as a conditional update on the expected current status
答题要点
- 单进程里状态就是函数栈;拆成 Gateway 与 Worker 后有三方要独立回答「这次执行到哪了」,必须落成一张表
- 正常路径 pending 到 running 到 streaming 到 done;异常出口 failed(重试耗尽)与 cancelled(打断合并或用户取消)
- running 与 streaming 分开,是为了观测首字延迟,也让前端知道该转圈还是该开始打字机效果
- 终态没有出边是核心:至少一次投递下的迟到片段会被当场挡住,而不是安静写进库
- 纪律:所有状态写入都过同一个转换函数,并用带当前状态条件的更新来处理并发
Several clients subscribe to the same run's streaming output at once. How do you guarantee each of them receives the full content in order?多个客户端同时订阅同一次执行的流式输出,怎么保证每个客户端都收到完整且有序的内容?
Common in ChinaCommon overseasIntermediate#sse#ordering#fan-outHow to reason about it · think before answering
- Two words carry the question: complete and ordered. Most candidates answer only ordering and drop completeness — which is the half that is easy to get structurally wrong, because it depends on which read primitive you pick.
- Set the frame first: a run is the unit of execution, a connection is the unit of viewing, and they are not one-to-one. Phone plus laptop, two browser tabs, or the overlap window during a reconnect all put multiple streams on one run. Once that is clear, 'first connection wins the lock' schemes fall away on their own.
- Name the trap in 'complete': broadcast reads and consumer groups are different semantics. A consumer group divides work — each message goes to exactly one consumer — while here every subscriber must see everything. Using a consumer group for fan-out gives you two connections each holding half the answer, and that is the classic wrong answer here.
- Then ordering: every chunk carries a sequence number starting at 0, contiguous, never skipping, and is written to the stream. The reader keeps a 'next to deliver' cursor, discards anything below it, buffers anything above it, and flushes contiguous runs. Put the same number in the SSE id field so the client keeps no separate bookkeeping.
- Volunteer the cost: the reorder buffer needs bounds. If chunk 5 is late, 6 onward pile up in memory, and ten thousand connections doing that is an outage. Cap the buffer size and the wait, then backfill the gap from the database, and if that fails emit an error event and let the client reconnect. Wait, but never wait forever.
- Expect the fan-out follow-up: either every connection reads the stream itself (simple, at the cost of reading the same data N times) or one read per process broadcast to local subscribers (fewer reads, but you now own a subscriber registry, teardown when the last one leaves, and still one read per instance). Decide by average subscribers per run — usually close to one, so take the simple path.
分析过程 · 先想清楚再作答
- 题眼有两个词:完整、有序。很多人只答有序,漏掉完整——而「完整」那一半恰好是最容易设计错的,因为它取决于你用了哪种读法。
- 先给一句能定调的判断:一次执行是执行的单位,一条连接是观看的单位,两者不是一对一。手机和电脑同开、两个标签页、重连瞬间新旧连接并存,都会让同一次执行上挂着多条流。想清楚这句话,「谁先连谁独占」这种锁的方案就自然被排除了。
- 接着点出「完整」的真正机关:广播读法与消费组是两种语义。消费组是分摊,一条消息只给一个消费者;这里要的是广播,每个订阅者都要看到全部。用消费组做扇出,结果就是两条连接各拿到半段话——这是这道题最常见的错误答案。
- 再答「有序」:每个片段带一个从 0 开始、连续、不跳号的序号,写进流;接收侧维护「下一个该交付的号」,小于它的丢弃,大于它的先入缓冲,连号了再批量推出去。序号同时写进 SSE 的 id 字段,客户端不用另记一套账。
- 然后是必须主动说的工程代价:缓冲要有上限。如果 5 号迟迟不到,6 号往后全在内存里排队,一万条连接同时这样就是一次内存事故。做法是给缓冲设条数上限和等待上限,超时就从库里补读,补不到就发 error 让客户端重连——能等,但不能无限等。
- 可以预期的追问:扇出实现怎么选?两种——每条连接各自去读一遍流(简单,代价是同一批数据被读 N 次),或进程内只读一次再广播给本地订阅者(省读取,但要维护订阅者表、要处理最后一个订阅者离开,跨实例仍要各读一次)。判据是每次执行的平均订阅者数,多数产品接近 1,那就选前者,别为不存在的规模提前写一层。
Key points
- A run is the unit of execution and a connection is the unit of viewing; they are not one-to-one, so no first-wins lock is needed
- Read the output stream as a broadcast, not through a consumer group — a group divides messages and leaves each connection with half the answer
- Tag every chunk with a contiguous sequence starting at 0; the reader discards older, buffers newer, and flushes contiguous ranges
- Mirror that sequence into the SSE id field so clients need no extra bookkeeping and can resume from it
- Bound the reorder buffer by size and time, backfill gaps from the database, and fall back to an error event plus reconnect
答题要点
- 一次执行是执行单位、一条连接是观看单位,两者不是一对一,不需要「谁先连谁独占」的锁
- 输出流必须用广播读法而不是消费组:消费组是分摊,会让两条连接各拿到半段话
- 每个片段带从 0 开始、连续、不跳号的序号,接收侧按序交付:小于当前号丢弃、大于当前号入缓冲、连号批量推
- 序号同时写进 SSE 的 id 字段,客户端不必自己记账,也是重连续号的依据
- 缓冲必须有条数与时间上限,超时从库里补读,补不到就发 error 让客户端重连
A user sends another message while the agent is still answering the previous one. How should the system handle it?用户在 Agent 还没回复完的时候又发来一条消息,应该怎么处理?
Common in ChinaCommon overseasIntermediate#interrupt-merge#state-machine#costHow to reason about it · think before answering
- It reads like a product question but tests whether you have thought through two concurrent runs. 'Queue it' or 'cancel the previous one' are not wrong, just incomplete — they want the criteria and the costs.
- Start with what happens if you ignore it: two runs write into the same conversation, so the UI shows two interleaved answers, and the first run was computed from incomplete input, so its answer is already wrong. Those two consequences point straight at merging rather than concurrency.
- Then give the actual test — all three must hold: same session, the previous run is running or streaming, and it was created less than 30 seconds ago. On a hit, append the new message to that run's input and flag it for a rerun instead of creating a new run; outside the window, or if the previous run finished, create a new one. Excluding pending is deliberate: that window lasts milliseconds, and excluding it keeps the rule free of races with the worker reading the input.
- Two implementation details show hands-on experience. First, the rerun flag does not belong in the business table — it is meaningful only during this execution, and persisting it means a crash mid-flight leaves a dirty flag that makes the run loop forever after restart; an expiring key is the right home. Second, on rerun the sequence must keep counting up rather than resetting, or a reconnecting client resuming from its last id lands in a history that has been invalidated.
- Volunteer the arithmetic to kill the 'saves money' answer: at roughly 2000 input and 500 output tokens, one answer costs about $0.0006. Not merging means two full runs, about $0.0012; merging means a first pass cut off a third of the way in (about $0.0004) plus a full second pass ($0.0006), about $0.0010 — a 17% saving, which is two dollars a day even at ten thousand corrections. Merging is a user-experience decision, not a cost optimization.
- Expect: where does 30 seconds come from? It is a product judgment, not a derivation — corrections usually arrive 5 to 15 seconds in, too short misses them and too long merges genuinely new questions into old ones. What matters is defining it once and referencing it from both the rule and the UI hint rather than scattering the constant.
分析过程 · 先想清楚再作答
- 这题看起来是产品题,其实考的是你有没有想过「并发两次执行」的后果。答「排队处理」或「直接取消上一条」都不算错,但都不完整——面试官想听的是判据和代价。
- 先说清不处理会怎样:两次执行同时往同一个会话里写输出,前端看到两段交错的文字;而且第一次执行是基于不完整的信息跑的,它的答案注定要被推翻。这两条后果一说,方案的方向就定了——要合并,不要并发。
- 然后给可执行的判据,三个条件全中才合并:同一个会话、上一次执行正处于 running 或 streaming、距它创建不到 30 秒。命中就把新消息追加进同一次执行的输入并标记为需要重跑,不新建;超窗或上一次已完成就正常新建。把 pending 排除掉是有意的——那段窗口只有几毫秒,排除后判据不必考虑「执行侧正好在这一刻读输入」的竞态。
- 两个实现细节最能体现动手过:一是「需要重跑」这个标记不要写进业务表,它只在本次执行期间有意义,写进表里进程崩在半路就留下脏标记、重启后无限重跑,放一个带过期时间的键上更合适;二是重跑时序号必须接着往上加、不能重置,否则重连的客户端按上次收到的号续,会续到一段已经作废的历史上。
- 主动算一笔账,把「为了省钱」这个错误理由挡回去:按输入 2000、输出 500 个 token 估,单次约 0.0006 美元;不合并是两次跑完约 0.0012 美元,合并是第一遍被掐在三分之一处约 0.0004 美元加第二遍 0.0006 美元约 0.0010 美元,只省 17%,一天一万次改口也就两美元。所以合并的理由是体验,不是成本。
- 可以预期的追问:30 秒怎么定的?答它是产品判断不是推导结果——用户改口通常在 5 到 15 秒之间,窗口太短合并不到、太长会把新问题误并成补充;关键是这个数只在一处定义、被判据与前端提示共同引用,不要在代码里散落三份。
Key points
- Without merging you get two interleaved answers in one conversation, and the first was computed from incomplete input
- Merge only when all three hold: same session, previous run running or streaming, created under 30 seconds ago; otherwise create a new run
- On a merge, append to the same run's input and flag a rerun, keeping that flag in an expiring key rather than the business table
- Sequence numbers keep counting on rerun and are never reset, or reconnects resume into an invalidated history
- The cost saving is small (about 17%); the real reason is to avoid two answers talking over each other
答题要点
- 不合并的两个后果:两段输出交错写进同一个会话,且第一次执行基于不完整信息注定被推翻
- 判据三条全中才合并:同一会话、上一次执行处于 running 或 streaming、距创建不到 30 秒;否则正常新建
- 命中就把新消息追加进同一次执行的输入并标记需要重跑,标记放带过期时间的键上而不是业务表
- 重跑时序号继续往上加、绝不重置,否则断线重连会续到作废的历史上
- 合并省的钱有限(约 17%),真正的理由是不让两个回答同时对着用户说话
After a streaming client reconnects, how do you deliver every missed chunk exactly once — no gaps, no duplicates?流式接口的客户端断线重连后,怎么做到既不丢片段也不重复?
Common in ChinaCommon overseasDeep dive#sse#idempotency#reconnectHow to reason about it · think before answering
- Answer 'no gaps' and 'no duplicates' separately. Plenty of candidates cover only the first — they backfill from storage but never say how the overlap is deduplicated.
- The chain is short: the client knows the last id it received, it sends that id back on reconnect, the server resumes from the next one — and all of that requires contiguous, monotonic numbering. Whether resumption is possible at all was decided when you chose the sequence scheme; timestamps or random ids break the chain at step one.
- Then the three steps and their individual traps. Convert: the client reports the last id it *received*, so add one — off by minus one repeats a frame, off by plus one drops a character, and this is the only arithmetic in the whole flow and the most commonly wrong line. Replay: read the missing range from durable storage, which is always complete. Attach: resume the live stream, whose overlap with the replay is guaranteed, and drop anything below the cursor. That single comparison is all there is to idempotent replay.
- Explain why the dual write is mandatory: chunks go both to the stream and to the table. Stream only, and the early chunks are gone by reconnect time; table only, and you are polling the database, pushing time-to-first-token from tens to hundreds of milliseconds. The cost is write amplification — hundreds of rows per answer — so production batches the writes, every few dozen chunks or every couple hundred milliseconds.
- Get the protocol detail right: the browser's native event source replays the last id in a request header for you, but model endpoints generally need POST while that API only issues GET, so real frontends hand-roll the parser and must resend the id themselves. Mentioning this proves you have actually wired up the client side.
- Expect: how long do you keep replayable data? Give two bounds — a retention window (per-chunk rows only for runs from the last few hours, then collapsed into one complete message) and a replay cap (beyond N chunks, send the full text once instead of re-enacting it character by character). Without both, that table becomes the largest in the database while 99% of its rows are never read again after ten seconds.
分析过程 · 先想清楚再作答
- 「不丢」和「不重复」要分开答。只答一半的人很多:说了从库里补发(不丢),却没说重叠部分怎么去重(不重复)。
- 推导链很短:客户端知道自己最后收到的编号 → 它重连时把这个编号带回来 → 服务端从下一号开始给 → 前提是编号连续不跳号。所以能不能重连,取决于当初有没有把序号设计成从 0 开始、连续、单调。序号一旦是时间戳或随机 id,这条链第一步就断了。
- 然后给三步实现和各自的坑:第一步换算,带回来的是「最后收到」的那一号,要加一,少加一重复一帧、多加一丢一个字,这是整段逻辑里唯一的算术也最常写错;第二步先从持久化里回放缺的部分,因为库里一定是全的;第三步再接上还在流动的那条流,两边必然重叠,靠「小于当前指针的一律丢弃」去重——幂等回放的全部秘密就是这一次比较。
- 这里要点出为什么必须双写:片段既进流也进库。只有流,重连时早期片段已被消费掉;只有库,就得轮询查库,首字延迟从几十毫秒涨到几百毫秒。代价是写放大,一次回答几百个片段就是几百行,生产里按批落库(每几十个片段或每两百毫秒一次)。
- 对齐一下协议细节:浏览器原生的事件源会自动把上次的编号放进重连请求头带回来;但大模型接口通常要用 POST,原生事件源只能发 GET,所以真实前端多是手写解析,重连时要自己把编号带上——这个细节能证明你真接过前端。
- 可以预期的追问:回放要保留多久?必须给两个边界——保留期(逐片段的行只对最近若干小时的执行保留,之后归档成一整条完整回复并删掉碎行)和回放上限(一次重连最多回放多少片段,超了就一次性发完整文本而不是逐字重演)。不定这两条,那张表会变成全库最大且 99% 的行写完十秒后再没人读。
Key points
- Resumption requires a contiguous, monotonic sequence starting at 0; timestamps or random ids make it impossible
- The client reports its last received id, so the server resumes from that id plus one — the single most error-prone line
- Replay the gap from durable storage first, then attach the live stream, discarding anything below the cursor to dedupe the overlap
- Dual-write every chunk: the stream serves currently attached connections, the table serves clients that come back later; batch the writes in production
- Set a retention window and a replay cap — archive old runs into one complete message and send full text instead of re-enacting long replays
答题要点
- 重连的前提是序号从 0 开始、连续、单调;序号是时间戳或随机 id 就无法续传
- 客户端带回来的是「最后收到」的那一号,服务端要加一再开始,这是唯一的算术也最容易错
- 先从库里回放缺的片段(库一定是全的),再接上还在流动的流,重叠部分靠「小于当前指针一律丢弃」去重
- 片段必须双写:流服务当前挂着的连接,库服务等一下才回来的人;代价是写放大,生产里按批落库
- 必须定保留期与回放上限:过期的执行归档成一整条完整回复,超长回放直接一次性发完整文本
D12 Long-Term Memory: pgvector, Embeddings, Chunking, the memory_search Tool
Why does an agent need a separate long-term memory instead of stuffing all history into the context window?为什么 Agent 需要额外的长期记忆,而不是把历史全部塞进上下文?
Common in ChinaCommon overseasBasic#long-term-memory#rag#costHow to reason about it · think before answering
- The tempting answer is 'the window is too small'. That is half right and it is the cheap half — windows keep growing, and the interviewer will ask what you would do at a million tokens.
- Separate the two problems first: context compression solves 'this turn does not fit in one session', long-term memory solves 'I cannot recall what was said last month'. One subtracts at request-assembly time, the other adds. Naming that distinction unprompted is where the signal is.
- Then quantify: 200 memories at roughly 400 tokens each is 80k tokens; at 0.15 USD per million input tokens that is 0.012 USD every single turn, about 0.24 USD per user per day at 20 turns. Retrieving the top 5 is 2k tokens, 0.0003 USD per turn — a 40x gap, and it repeats every turn.
- Give the reason that beats cost: irrelevant context lowers accuracy. If one of 200 memories is relevant, the other 199 are noise that pull the model toward answering something nobody asked. So even with an infinite free window, you would still retrieve rather than dump.
- Land on practice: distil cross-session user facts and preferences into standalone statements, store them as vectors, and inject the three to five most relevant per turn — the minimal form of RAG.
- Expect the follow-up: what belongs in long-term memory? Three tests — is it still needed across sessions, does it expire, can retrieval find it again. 'Lives in Shanghai' passes all three; 'shorten that paragraph' passes none.
分析过程 · 先想清楚再作答
- 这题最容易答成「因为窗口装不下」。那只答对了一半,而且是不值钱的那一半——窗口一年比一年大,光靠这条理由,面试官会追问「等窗口到一百万 token 呢」,你就没词了。
- 先把两个问题拆开:上下文压缩解决的是「同一次会话里这一轮塞不下」,长期记忆解决的是「上个月说过的事想不起来」。前者在组装请求时做减法,后者做加法,触发时机、数据去向、失败后果都不同。能主动区分这两件事,是这题最大的区分度。
- 然后给成本账:200 条记忆、每条约 400 token 就是 8 万 token,按输入价 0.15 美元每百万 token 算,每一轮多付 0.012 美元;一天 20 轮就是 0.24 美元一个用户。只检索最相关的 5 条是 2000 token、每轮 0.0003 美元,差 40 倍。而且这笔钱是每轮重复付的,不是一次性的。
- 再给比钱更硬的理由:无关信息会降低命中率。200 条里跟这一轮相关的可能只有 1 条,剩下 199 条是噪声,模型会被带偏去回答一个用户没问的问题。**所以哪怕窗口无限大、token 免费,也该检索而不是全塞。** 这一句是这题的最优解。
- 落到做法上:把跨会话的用户事实与偏好抽成陈述句存进向量库,每轮按语义检索最相关的三五条注入请求——这就是 RAG 最小的一环。
- 可以预期的追问:什么信息该进长期记忆?答三问——跨会话之后还需要吗、会不会随时间失效、能不能靠检索捞回来。「用户住上海」三条都满足,「把刚才那段改成三句话」一条都不满足。
Key points
- Compression handles 'this turn does not fit'; long-term memory handles 'what did they say last month' — different problems, different machinery
- Dumping everything costs on every turn: 200 memories is about 80k tokens and 0.012 USD per turn versus 0.0003 USD for five retrieved ones
- The stronger reason is accuracy — irrelevant memories are noise, so you would retrieve even with an infinite window
- Practice: distil cross-session facts into statements, embed them, inject the top three to five per turn
- Admission test for a memory: still needed across sessions, does not expire, and is findable by retrieval
答题要点
- 压缩管「这一轮塞不下」,长期记忆管「上个月说过的事想不起来」,是两个问题、两套机制
- 全塞的成本是每轮重复付的:200 条约 8 万 token,每轮多 0.012 美元;检索 5 条只要 0.0003 美元
- 更硬的理由是准确率:无关记忆是噪声,会把模型带偏,所以窗口再大也该检索而不是全塞
- 做法是把跨会话的事实抽成陈述句、向量化存储,每轮按语义检索最相关的三五条注入
- 判断一条信息该不该进长期记忆:跨会话还需要吗、会不会失效、能不能被检索到
How does pgvector compare with a dedicated vector database, and how would you choose?pgvector 和专用向量数据库相比,优劣分别是什么?你会怎么选?
Common in ChinaCommon overseasIntermediate#vector-database#pgvector#architectureHow to reason about it · think before answering
- This is a judgment question, not a feature-recital. Opening with 'Milvus does sharding, Qdrant filters better' carries no signal — they want your decision criteria and whether you have priced the operational overhead.
- Offer three questions that make the choice derivable: what scale, does it need to commit in the same transaction as business tables, and how complex is the metadata filtering.
- pgvector wins on the last two: memories live in the same database as the business tables, so writing a memory and updating a run share one transaction; filtering by user is an ordinary WHERE clause; backups, monitoring, pooling and migrations are all reused. The line that shows operational experience is that one more stateful service usually becomes the bottleneck before vector search performance does.
- Be honest about the ceiling: past roughly ten million vectors in one table, HNSW index builds eat memory and write amplification shows; ANN plus metadata filtering is weaker than a purpose-built engine; horizontal scaling is whatever Postgres gives you. Refusing to name downsides reads as salesmanship.
- Commit to a rule: under a million vectors, needing joins or shared transactions, small team — pgvector. Tens of millions, retrieval as the primary workload, someone owning the service — dedicated store. Do not start with the dedicated store on day one.
- Expect the follow-up on migration cost. Switching stores does not require re-embedding — vectors belong to the model, not the store, so export and import; the cost is dual-write and rollout. Switching the embedding model is what forces a full recompute, and that is the real lock-in.
分析过程 · 先想清楚再作答
- 这题考的是选型判断力,不是产品参数背诵。开口就报「Milvus 支持分布式、Qdrant 过滤更强」是最没有区分度的答法——面试官想知道你按什么判据选,以及你有没有算过运维成本。
- 先给三个提问维度,把选型变成可推导的:数据量到什么量级、要不要和业务表在同一个事务里提交、过滤条件复不复杂。这三问能覆盖绝大多数真实场景。
- pgvector 的赢面几乎全在后两问上:记忆表和业务表在同一个库,写记忆和更新执行记录可以放进同一个事务;按用户过滤就是普通 where 条件;备份、监控、连接池、迁移工具全部复用。**多一个有状态服务的运维成本,通常比向量检索的性能更早成为瓶颈**——这句话最能体现你上过线。
- 再诚实地说它的天花板:单表到千万级向量时 HNSW 索引构建吃内存、写入放大明显,ANN 与元数据过滤的融合不如专用库,水平扩展只能靠 Postgres 自己那一套。不肯说缺点的人会被认为在推销。
- 结论要能写死:百万级以内、需要和业务表一起过滤或同事务提交、团队人手紧,用 pgvector;上千万条、检索本身就是主要负载、有专人维护,上专用库。别在第一天就选专用库。
- 可以预期的追问:以后想换库,迁移成本大不大?答案会让很多人意外——换库不用重算 embedding,向量是模型产出的,跟存它的库无关,导出导入即可,成本主要在双写和灰度。真正要全量重算的是换 embedding 模型,那才是硬锁定。
Key points
- Three criteria: scale, need for same-transaction commits with business tables, and filtering complexity
- pgvector gives one database, one transaction, ordinary SQL filters and zero new operations — often worth more than raw performance
- Its ceiling: memory-hungry index builds and write amplification at tens of millions, weaker ANN-plus-filter fusion, scaling limited to Postgres
- Dedicated stores buy sharding, better filtered ANN and hybrid search, at the price of another stateful service to back up and monitor
- Changing stores needs no re-embedding; changing the embedding model does — the lock-in is the model, not the database
答题要点
- 三个判据:数据量级、要不要和业务表同事务提交、元数据过滤复不复杂
- pgvector 的优势是同库同事务、普通 SQL 过滤、运维零新增——少一个有状态服务往往比性能更值钱
- pgvector 的天花板:千万级向量时索引构建吃内存、写入放大、ANN 与过滤融合弱、扩展受限于 Postgres
- 专用向量库给的是分布式分片、更强的过滤与 ANN 融合、混合检索,代价是多一个要备份要监控的有状态服务
- 换向量库不用重算 embedding;换 embedding 模型才要全量重算,真正的锁定点是模型不是库
How does the chunking strategy affect retrieval quality, and how do you pick a chunk size?chunking 的切分策略会怎么影响检索效果?切多大合适?
Common in ChinaCommon overseasIntermediate#chunking#rag#retrieval-qualityHow to reason about it · think before answering
- The hinge is 'how does it affect'. Naming a number alone invites a why, so describe both failure modes first and let the number follow.
- Too small: a chunk loses its context. 'He wants size 42' retrieves fine but resolves to nothing — pronouns dangle and the model is more likely to fabricate.
- Too large is the counter-intuitive half and the real discriminator: a chunk spanning three topics gets a vector that averages them, so it looks only vaguely like any query and recall drops. Bigger chunks carry more information yet are harder to retrieve.
- Give an operational default: target 400 characters with 80 characters of overlap, ending on natural boundaries such as sentence stops or newlines. Explain the overlap — when a key sentence lands on a cut, each side holds half of it, and the overlap guarantees at least one chunk holds it whole.
- Add the costs: 80 over 400 is 20% storage amplification plus an extra vector per duplicated span, and near-duplicate chunks can both surface and waste result slots, so deduplicate by content before returning.
- Expect: how do you validate a chunking strategy? Build a query set with labeled expected hits and measure recall and top-k hit rate, then re-run after changing parameters — chunking is measurable, not a matter of taste. Second follow-up: should raw dialogue be chunked as-is? No — have the model distil it into standalone statements first, or filler turns flatten the vectors.
分析过程 · 先想清楚再作答
- 题眼在「怎么影响」。只回答一个数字(比如「切 500 字」)会被追着问为什么,所以要先把两个方向的失效模式讲出来,数字才有落点。
- 切太碎的失效模式:单张卡片脱离上下文。「他说要 42 码」检索命中了也没用,代词失去指代,模型拿到一句悬空的话反而更容易编。
- 切太整的失效模式更反直觉,也是这题真正的区分点:一块横跨三个主题时,它的向量是这几个主题的平均值,结果对哪个 query 都不太像,命中率反而下降。**块越大信息越全,却越难被检索到**——能说出这句话基本就过了。
- 然后给可操作的口径:目标 400 字符、相邻块重叠 80 字符,并优先在句号、换行这类自然边界收尾。重叠的作用要说清楚——一句关键的话被切口劈开时,两块各拿半句,重叠保证它至少在其中一块里是完整的。
- 补上代价,这是工程视角:重叠 80 除以 400 等于 20% 的存储放大,向量也跟着多一份;内容高度重叠的两块可能一起被检索出来,白占返回名额,所以要按内容去重。
- 可以预期的追问:怎么验证切分策略好不好?答案是准备一批 query 与标注好的期望命中,量召回率和 top-k 命中率,改切分参数后重跑对比——切分是可以被度量的,不该靠感觉调。第二个追问是「对话数据要不要原样切」,答不要:先让模型抽成陈述句再切,否则大量寒暄句会把向量拉平。
Key points
- Too small: chunks lose context, pronouns dangle, and a hit is useless
- Too large: one chunk spans several topics, its vector averages them, and recall drops for every query
- Working default: target 400 characters with 80 characters of overlap, cutting on sentence or newline boundaries
- Overlap keeps a split sentence whole in at least one chunk, at roughly 20% storage amplification plus possible duplicate hits
- Distil dialogue into standalone statements before chunking, and validate with a labeled query set measuring recall
答题要点
- 切太碎:单块脱离上下文,代词失去指代,命中了也用不上
- 切太整:一块横跨多个主题,向量被平均,对任何 query 都不够像,命中率反而下降
- 可操作口径:目标 400 字符、重叠 80 字符,优先在句号或换行这类自然边界收尾
- 重叠的作用是保证被切口劈开的句子至少在一块里完整;代价是约 20% 的存储放大和可能的重复命中
- 别直接切对话原文,先抽成陈述句;切分效果要用标注好的 query 集测召回率,而不是凭感觉
What matters when designing the parameters of a retrieval tool such as memory_search?把记忆检索包装成 memory_search 这样的工具时,参数设计上要注意什么?
Common in ChinaCommon overseasDeep dive#tool-design#security#long-term-memoryHow to reason about it · think before answering
- It looks like an API design question; the discriminating part is security. Most candidates name query and limit and stop. Saying which parameters must never be exposed to the model is what earns the point.
- On query: the description must state that it is a retrieval phrase the model composes, not the user's literal words, and give a concrete example. Asked 'what are my dietary restrictions', the model should search for 'the user's food allergies and restrictions'.
- On limit: optional, default 5, capped at 10. The cap is a context budget, not idiot-proofing — a memory is roughly 400 tokens, so ten of them put 4000 tokens into the request. When the model asks for 50, return a readable validation error naming the field, the valid range and an example, rather than silently clamping, or it never learns it was wrong.
- The critical rule: never expose an identity parameter such as user_id. Identity comes from the session. Making it a parameter hands 'whose memories to read' to probabilistically generated text, and one prompt injection turns it into a privilege-escalation read. Prompts govern intent; code governs permission.
- Cover the response shape too: an empty result must say so explicitly and forbid guessing, because an empty string reads to the model as 'no constraints' and invites fabrication; return similarity scores so the model can distinguish a firm memory from a vague one; and set a minimum score, since no result beats a noisy one.
- Expect: what should happen on a miss? Two layers — the tool returns empty honestly and forbids speculation, and the prompt instructs the model to ask the user instead of treating 'not found' as 'no preference'.
分析过程 · 先想清楚再作答
- 这题看着是接口设计题,真正的区分度在安全。多数人会答 query 和 limit,答完就停;能不能说出「哪些参数绝对不能给模型」,决定了这题的分数。
- 先说 query:描述里要写清它不是用户原话,而是模型自己组织的检索语句,并给一个合法示例。用户问「我有什么忌口」,模型应该用「用户的食物忌口」去检索——这是从 D5 那条「格式类字段要给合法示例」延续下来的。
- 再说 limit:可选、默认 5、上限 10。上限的理由不是防呆,是上下文预算——一条记忆约 400 token,10 条就是 4000 token 进请求。模型传 50 时按工具协议回一条可读错误让它改,而不是静默截断成 10,否则模型永远不知道自己传错了。
- 然后是关键的一条:**绝不给 user_id 这类身份参数**。用户身份只能来自会话上下文。做成参数等于把「查谁的记忆」交给一段概率生成的文本,配上一句提示词注入就是现成的越权读取漏洞。一句话收尾:提示词管意图,代码管权限。
- 返回格式同样要说:空结果必须显式返回一句「没有找到相关记忆,请不要凭空推测」,返回空串模型会当成没有约束然后自己编;把相似度分数一起返回,模型才能区分「你说过」和「我印象里你好像提过」;设一条相似度下限,宁可不返回也不要拿噪声污染上下文。
- 可以预期的追问:检索不到的时候该怎么办?答案是分两层——工具层如实返回空并禁止推测,提示词层要求模型转而向用户确认,而不是把「没检索到」当成「用户没有偏好」。
Key points
- query is required; document it as a model-composed retrieval phrase, not the user's literal words, with an example
- limit is optional, defaults to 5 and caps at 10 on context-budget grounds; over the cap, return a readable validation error instead of silently clamping
- Never expose user_id or any identity parameter — identity comes from the session, or prompt injection becomes a privilege-escalation read
- An empty result must say so explicitly and forbid speculation, or the model fabricates
- Return similarity scores and enforce a minimum, since no result beats a noisy one
答题要点
- query 必填,描述里说明它是模型组织的检索语句而非用户原话,并给一个合法示例
- limit 可选、默认 5、上限 10,上限的依据是上下文预算;超限按工具协议回可读错误让模型改,不要静默截断
- 绝不把 user_id 这类身份参数交给模型,身份只能来自会话——否则一句提示词注入就是越权读取
- 空结果要显式说「没找到,请不要凭空推测」,返回空串模型会自己编
- 返回相似度分数并设下限,宁可不返回也不要用低相关记忆污染上下文
D13 Cron Scheduling (Central Scheduler → Stream Delivery) + Cost Metering (Token → USD Ledger, Usage Report)
When a service runs multiple replicas, why not let each replica start its own cron? What would you do instead?服务部署了多个实例,定时任务为什么不能让每个实例各自起一个 cron?你会怎么做?
Common in ChinaCommon overseasBasic#scheduling#distributed-systems#costHow to reason about it · think before answering
- The hinge is the phrase multiple replicas. Saying it would run twice is only the symptom; the interviewer wants the business and dollar consequence.
- Make the cost concrete: three replicas each running cron means the job fires three times, users get three identical pushes, and you pay for three model calls. The multiplier tracks replica count, so scaling to ten makes both the bill and the spam tenfold, with no alert firing, because from each process's own point of view it ran exactly once.
- Give the right shape: move the decision of who runs when into one central scheduler whose only job, on a cron match, is to publish a task message onto the bus; the execution side keeps using a consumer group so one message reaches exactly one consumer. The key insight is that a scheduled task is not a new execution path, it just swaps the user for a clock as the thing pressing the button, so the worker code stays untouched.
- Volunteer the obvious follow-up: doesn't the scheduler become a single point of failure? Two layers. It is stateless, so a crash costs you a few minutes of task delay; if you truly need HA, run two instances and dedupe on the idempotency key at publish time rather than bolting a distributed lock onto the scheduler.
- Close with sizing: a central scheduler plus a bus is enough at modest volume. At high volume, or when tasks have dependencies, teams move to a dedicated workflow scheduler with dependency graphs, retry policy and backfill, but the underlying central-decision-plus-queue shape is identical.
- Expect: the scheduler was down for 90 seconds and skipped a minute — now what? Replay the last N minutes on startup, one minute at a time. The idempotency key makes redundant publishes harmless, which is exactly what makes at-least-once plus idempotency the easy combination.
分析过程 · 先想清楚再作答
- 题眼在「多个实例」四个字。只答「会重复执行」拿不到分,因为那是现象;面试官想看你能不能把现象换算成业务后果和钱。
- 先把重复的代价说具体:3 个副本各起 cron,同一个任务被执行 3 次,用户收到 3 份一样的推送,你付 3 份模型调用的钱。而且这个倍数会跟着副本数走——扩容到 10 个副本,账单和骚扰量一起变成十倍,却不会触发任何告警,因为从每个进程自己的视角看它只是老实地执行了一次。
- 然后给出正确的形状:把「谁该在什么时候被执行」收进一个中心调度器,它命中 cron 之后只做一件事——往消息总线投递一条任务消息;执行侧照旧靠消费组分摊,一条消息只会被一个消费者拿到。关键认知是「定时任务不是一种新的执行方式,只是把按按钮的人从用户换成了钟表」,所以执行侧一行代码都不用改。
- 接着主动补上「那调度器自己不就成单点了吗」——这是必被追问的一句。答案分两层:调度器无状态、崩了拉起来就行,短暂不可用的代价只是几分钟内的任务延迟;真要高可用就起两个实例,靠投递时的幂等键去重,而不是靠给调度器加分布式锁。
- 最后点一句选型:任务量不大时中心调度器加消息总线足够;量大或者任务本身有依赖关系时,业界会换成专门的调度框架(带任务依赖、重试策略、补数),但底层的「中心决定 + 队列分发」结构是一样的。
- 可以预期的追问:调度器崩溃 90 秒,中间跨过的那一分钟怎么办?答启动时回看最近 N 分钟逐分钟重放,因为有幂等键兜底,重复投递无害——这正是 at-least-once 加幂等这组搭配能成立的地方。
Key points
- Per-replica cron means the job runs N times: N duplicate pushes, N times the model spend, scaling linearly with replica count and silently
- The right shape is a central scheduler that publishes one message to the bus on a cron match, with a consumer group ensuring exactly one worker picks it up
- A scheduled task is not a new execution path — only the trigger changed from a user to a clock, so worker code is unchanged
- The scheduler is stateless: restart on crash, and if you need HA run two and dedupe on the idempotency key rather than adding a distributed lock
- Missed minutes are recovered by replaying the last N minutes at startup, which is safe because the idempotency key absorbs duplicates
答题要点
- 每个实例各自起 cron 等于同一个任务被执行 N 次:用户收到 N 份重复推送,模型调用花 N 倍的钱,倍数随副本数线性增长且不会触发告警
- 正确形状是中心调度器命中 cron 后只往消息总线投递一条消息,执行侧靠消费组保证一条消息只被一个 Worker 拿到
- 定时任务不是新的执行路径,只是把触发者从用户换成了钟表,所以 Worker 侧不需要任何改动
- 调度器是无状态的,崩了拉起来即可;需要高可用就起两个实例靠投递时的幂等键去重,不要给它加分布式锁
- 崩溃期间跨过的时间点靠启动时回看最近 N 分钟重放补上,幂等键保证重复投递无害
How would you design a token cost metering and ledger system from scratch?让你从零设计一套 token 成本计量和台账系统,你会怎么做?
Common in ChinaCommon overseasIntermediate#cost#observability#data-modelingHow to reason about it · think before answering
- This question tests whether you have ever reconciled a bill. The discriminators are the numeric type you store money in, and whether cost is stored or computed at query time. A design missing either gets rejected by finance within a quarter.
- Set the criterion first: a ledger is not a log. Logs exist for debugging and can be dropped; a ledger has to reconcile against the vendor invoice and answer why the bill grew 40% this month, so every charge must trace back to who, which run, which model, and how many tokens. Every field falls out of that.
- Then walk the fields with reasons: user_id says whose budget it hits; run_id says which execution it belongs to and is nullable because some spend is system-level batch work; model records the one actually used, since fallback routes the same workload to different providers; kind separates chat from embedding because their volumes and growth curves differ completely; prompt_tokens and completion_tokens are stored separately because input and output differ three- to four-fold in price, and a single total can neither reproduce the amount nor tell you whether the prompt is bloated or the model is verbose.
- Now the two judgments that show experience. First, money uses fixed-point: numeric in the database, Decimal or BigDecimal in code, never accumulated in binary floats, or the total will diverge from the sum of rows after a hundred thousand entries. Second, cost is computed at write time and stored redundantly, not recomputed from the current price table — prices change, and history must not change with them. That is the essential difference between a ledger and a report.
- Volunteer the timing and transaction boundary: record at the moment you receive the usage field, not at business success, because failed calls still cost money and a fallback spans two or three billable calls per business operation. Ledger writes need not share the business transaction — losing a row costs fractions of a cent, while locking the ledger table stalls user conversations — so write asynchronously with retries and a uniqueness constraint on run id plus call index. The exception is quota enforcement: if the product caps spend, the decrement must be transactional or concurrent requests will blow through the cap.
- Expect: what happens to history when the vendor changes prices? The price table itself needs effective dates and a version, and the ledger stores both the computed amount and the price version, so recomputation and audit both have a basis.
分析过程 · 先想清楚再作答
- 这题在考「你有没有真的对过账」。区分度在两个地方:金额用什么类型存,以及金额是冗余存还是查询时现算。答不到这两点的方案,上线三个月就会被财务打回来。
- 先立判据:台账不是日志。日志是给排查问题用的,删了就删了;台账要拿去对账、要回答「这个月为什么涨了 40%」,所以每一笔钱都必须能追回到「谁、因为哪一次执行、用哪个模型、花了多少 token」。字段设计全部由这条判据推出来。
- 然后给字段和理由,一一对应:user_id 回答该算谁头上、run_id 回答属于哪次执行(允许为空,因为有系统级批量开销)、model 存调用当时那一个(fallback 会让同一段业务落到不同模型上)、kind 区分 chat 和 embedding(两者量级和增长曲线完全不同)、prompt_tokens 与 completion_tokens 分开存(输入输出单价差三到四倍,只存 total 就算不回金额,也看不出是提示词太长还是模型太啰嗦)。
- 接着是两个最能体现经验的判断。第一,金额用定点类型:数据库用 numeric,代码里用 Decimal 或 BigDecimal,绝不用双精度浮点累加,否则十万条之后总额和逐条相加对不上。第二,cost_usd 要在写入那一刻算好并冗余存,不要查询时用当前价格表现算——价格会变,历史账单不能跟着一起变,这是台账和报表最本质的区别。
- 还要主动说记账的时机和事务边界:记账放在「拿到 usage 字段」那一刻,而不是「业务成功」那一刻,因为失败的调用同样产生费用,尤其 fallback 会一次业务跨两三次收费调用。台账写入不必和业务同事务(丢一条只是几厘钱,锁住台账表却会卡住用户对话),可以异步加重试,用 run_id 加调用序号做唯一约束防重;但如果产品有额度限制,配额扣减必须同事务,否则用户能靠并发把额度刷穿。
- 可以预期的追问:厂商调价了历史数据怎么办?答案是价格表本身要有生效时间和版本号,台账里既存算好的金额也可以存价格版本,这样重算和审计都有依据。
Key points
- A ledger is not a log: every charge must trace to a user, a run, a model and a token count, and the schema follows from that
- Store prompt and completion tokens separately, since input and output prices differ three- to four-fold and a single total can neither reproduce the amount nor localize the problem
- Use fixed-point money (numeric in the database, Decimal or BigDecimal in code); float accumulation makes totals disagree with the sum of rows
- Compute cost at write time and store it, rather than recomputing from today's price table, so history stays stable when prices change
- Record at the moment usage is returned, not at business success — failed calls and fallbacks still cost money; ledger writes can be async with retries, but quota decrements must be transactional
答题要点
- 台账不是日志:每一笔钱要能追回到谁、哪一次 run、哪个模型、多少 token,字段设计全由这条判据推出
- prompt_tokens 与 completion_tokens 必须分开存,因为输入输出单价差三到四倍,只存 total 既算不回金额也看不出问题出在哪一侧
- 金额用定点类型(数据库 numeric、代码 Decimal/BigDecimal),不要用浮点累加,否则总额和逐条相加对不上
- cost_usd 在写入那一刻算好并冗余存,不要查询时按当前价格现算——价格会变,历史账单不能跟着变
- 记账时机是拿到 usage 字段那一刻而不是业务成功那一刻,失败调用和 fallback 同样产生费用;台账可异步写入加重试,但配额扣减必须和业务同事务
Which dimensions should a usage report for an LLM product cover, and what decision does each one drive?一份 LLM 应用的 usage report 通常要覆盖哪些维度?这些维度分别用来做什么决策?
Common in ChinaCommon overseasIntermediate#observability#cost#reportingHow to reason about it · think before answering
- The trap is listing dimensions: by user, by day, by model, by feature. Length signals you have not thought about it. The discriminator is the second half — which action each dimension drives. No action means you built reports but never used one.
- Give three primary dimensions with their action type: by user is a commercial action (who to reprice, who is abusing, whether tiering covers cost); by day is a debugging action (align with the release timeline to find which deploy stepped the cost up); by model and call kind is an optimization action (did tiered routing actually save money, is embedding volume running away). Three dimensions, three different dashboard audiences.
- Then go up a level: absolute dollars carry no information. What matters are unit-economics ratios with a denominator — cost per run (monthly cost over run count), cost per active user per month, and business actions completed per dollar. The first two say whether pricing covers cost; the third says whether the system deserves further investment.
- Prove you have used it with a concrete pattern: cost per run is a ruler. If user count is flat but cost per run climbs, it is almost always a deploy that lengthened the prompt or a tool whose response body grew. That signal usually appears days before latency alerts, which is why mature teams put the cost curve next to error rate and latency on the on-call dashboard.
- Add the dimension most people miss: failures and fallbacks. Failed calls are still billed, and a fallback spans two or three billable calls per business operation. Without slicing that out, your gap against the vendor invoice concentrates exactly during incidents, when you most need cost clarity.
- Expect: how fresh does the report need to be? Tier it — daily rollups can run offline, but quota and budget guardrails need near-real-time month-to-date totals, usually from an incrementally updated per-user monthly summary table rather than scanning the detail rows on every request.
分析过程 · 先想清楚再作答
- 这题最容易答成罗列维度:按用户、按天、按模型、按功能……列得越全越显得没想过。区分度在后半句——每个维度对应的是哪一类行动。列不出行动,说明你只做过报表没用过报表。
- 先给三个主维度和它们各自的行动类型:按用户切是商业动作(谁该涨价、谁在滥用、定价分层能不能覆盖成本);按天切是排障动作(对齐发布时间线,找出是哪次上线让成本跳了台阶);按模型和调用类型切是优化动作(验证分层路由有没有真省到钱、embedding 的量是不是失控了)。三个维度对应三个不同的看板受众。
- 然后升一层,指出绝对金额没有信息量,真正有用的是带分母的单位经济学指标:每次执行成本(当月总成本除以 run 数)、每用户月成本(除以活跃用户数)、每美元产出(完成的业务动作数除以总成本)。前两个用来判断定价能不能覆盖成本,第三个用来判断这套系统值不值得继续投入。
- 举一个能落地的用法证明你真用过:每次执行成本这个比值是把尺子。如果用户数没涨而单次成本涨了,几乎一定是某次上线让提示词变长了,或者某个工具的返回体膨胀了——这个信号通常比超时告警早好几天出现,所以成熟团队会把成本曲线和错误率、延迟并排挂在值班大盘上。
- 最后补一个大多数人会漏的维度:失败与降级。失败的调用照样收费,fallback 会让一次业务操作跨两三次收费调用。报表里不单独切出这一块,你和厂商账单的差额就会恰好集中在故障期,也就是最需要看清成本的时候。
- 可以预期的追问:报表要做到什么实时度?答案是分层——按天的汇总离线跑就够,但配额和预算护栏需要近实时的当月累计,通常用一张按用户按月的汇总表增量更新,而不是每次请求都扫一遍明细。
Key points
- By user drives commercial decisions, by day drives debugging, and by model or call kind drives optimization — three dimensions, three audiences
- Absolute dollars say nothing; use ratios with a denominator: cost per run, cost per active user per month, and business actions per dollar
- Cost per run is a ruler: flat users with rising per-run cost usually means a longer prompt or a bloated tool response, and it shows days before latency alerts
- Slice out failed and fallback calls, or your gap against the vendor invoice concentrates during incidents
- Tier the freshness: daily rollups offline, near-real-time month-to-date totals from an incremental summary table for budget guardrails
答题要点
- 按用户切是商业动作(定价分层、异常账号),按天切是排障动作(对齐发布找成本跳变),按模型和调用类型切是优化动作(验证分层路由、盯 embedding 用量)
- 绝对金额没有信息量,要看带分母的指标:每次执行成本、每用户月成本、每美元产出
- 每次执行成本是把尺子:用户数没涨而单次成本涨了,通常是提示词变长或工具返回体膨胀,比超时告警早好几天出现
- 必须单独切出失败与降级的开销,否则和厂商账单的差额会集中在故障期
- 实时度要分层:按天汇总可离线跑,预算护栏需要近实时的当月累计,用增量汇总表而不是每次扫明细
How do you keep a cron job from being published or executed twice, and how should the idempotency key be built?怎么保证一个 cron 任务不会被重复投递或重复执行?幂等键应该怎么构造?
Common in ChinaCommon overseasDeep dive#idempotency#scheduling#distributed-systemsHow to reason about it · think before answering
- The hinge is that publishing and executing are two separate problems. Most candidates answer half: either only the consumer group (which stops duplicate execution) or only a lock (which stops duplicate publishing, and imperfectly). A complete answer names the duplicate sources on both sides plus one backstop that covers both.
- Enumerate the sources: duplicate publishes come from multiple scheduler instances, from replay after a scheduler restart, and from the bus's own at-least-once semantics. Duplicate executions come from a worker crashing mid-processing and the message being reclaimed by another consumer. The two need different treatment.
- State the core conclusion: do not reach for a distributed lock, use a uniqueness constraint in the data layer. A lease only gives you probable mutual exclusion — in the instant when the TTL expires while the previous holder is merely stuck in GC, both schedulers believe they hold it and both publish. A uniqueness constraint is evaluated at the final insert, so no matter how many times upstream published, the table gains exactly one row. Do not escalate a problem solvable by a constraint into a distributed coordination problem.
- Then the key construction, which is where people fail: the key must be the task id plus the scheduled minute, never the current instant. Two scheduler clocks never align to the millisecond; one wakes at 09:00:00.120 and the other at 09:00:00.480, so keys built from now differ and dedup collapses. Truncate seconds and milliseconds and every instance computes the same string for that minute. In code this is an insert with on conflict do nothing; a conflict means the execution already exists, so ack the message and skip.
- Scope it honestly: this guarantees one execution per trigger point, not that side effects inside the execution happen once. If the run sends an SMS or charges a card, those side effects need their own idempotency keys, because the worker can crash after sending and before writing status. Making that distinction earns points.
- Expect: what about missed triggers? Prefer over-publishing to under-publishing — replay the last N minutes at startup and let the idempotency key absorb duplicates. At-least-once plus idempotency is the easiest combination in distributed systems; chasing exactly-once first and adding idempotency later usually achieves neither.
分析过程 · 先想清楚再作答
- 题眼在「投递」和「执行」是两件事。很多人只答一半:要么只说消费组保证一条消息一个消费者(那只挡住了执行侧的重复),要么只说加锁(那只挡住了投递侧,还挡不干净)。完整答案要说清两侧各自的重复来源,以及一个能同时兜住的兜底。
- 先拆重复的来源:投递侧的重复来自多个调度器实例、调度器重启后的补发重放、以及消息总线本身的至少一次语义;执行侧的重复来自 Worker 处理到一半崩溃后消息被 XAUTOCLAIM 转交给别人。这两类重复用不同手段挡效率完全不同。
- 再给核心结论:不要用分布式锁去做互斥,用数据层的唯一约束做去重。原因是锁只能提供「大概率互斥」——租约到期而前任进程其实只是 GC 卡住的那一瞬间,两个调度器都会认为自己持有,各发一次;而唯一约束是在最终落库那一步判断的,无论上游发了几次,任务表里只会多一行。能在唯一约束上解决的问题,不要升级成分布式协调问题。
- 然后回答幂等键怎么构造,这是最容易翻车的一步:键必须是「任务 id 加计划触发的那一分钟」,绝不能用当前时刻。两个调度器实例的时钟不可能对齐到毫秒,一个在 09:00:00.120 醒来、另一个在 09:00:00.480 醒来,用 now 算出来的键不一样,去重完全失效。把秒和毫秒截掉之后,无论谁在这一分钟里的哪一刻醒来,算出的键都是同一个字符串。落到代码上就是 insert 加 on conflict do nothing,冲突说明已经有人建过这次执行,直接 ack 掉不执行。
- 补一句作用范围:这套只保证「同一个触发点只产生一次执行」,不保证「执行内部的副作用只发生一次」。如果这次执行要发短信、要扣款,那些副作用还得各自带自己的幂等键,因为 Worker 可能在发完短信之后、写完状态之前崩掉。这一层区分是加分项。
- 可以预期的追问:那漏发怎么办?答宁可多发不可少发——调度器启动时回看最近 N 分钟逐分钟重放,重复投递被幂等键吃掉。at-least-once 加幂等是分布式系统里最省心的一组搭配,反过来先追求 exactly-once 再补幂等,通常两头都做不好。
Key points
- Duplicate publishing and duplicate execution are separate: the former comes from multiple schedulers, restart replay and at-least-once delivery; the latter from a crashed worker's message being reclaimed
- Dedupe with a database uniqueness constraint rather than a distributed lock: when a lease expires while the holder is only GC-stalled, both schedulers publish, whereas the constraint admits exactly one row
- Build the key from the task id plus the scheduled minute, never the current instant — instances never wake at the same millisecond, so a now-based key defeats dedup entirely
- In code this is an insert with on conflict do nothing; on conflict, ack the message and skip execution
- This guarantees one execution per trigger, not once-only side effects — SMS or payments inside the run need their own keys; prefer over-publishing and let at-least-once plus idempotency absorb it
答题要点
- 投递重复和执行重复是两件事:前者来自多调度器实例、重启重放和总线的至少一次语义,后者来自 Worker 崩溃后消息被转交
- 用数据层唯一约束去重,不要用分布式锁互斥:租约过期而前任还活着的瞬间两个调度器都会各发一次,而唯一约束在落库那一步只放行一条
- 幂等键必须是任务 id 加计划触发的那一分钟,不能用当前时刻——两个实例的醒来时刻永远不同,用 now 会让去重完全失效
- 落到代码上是 insert 加 on conflict do nothing,冲突就直接 ack 不执行
- 这只保证一个触发点一次执行,执行内部的发短信、扣款等副作用要各自带幂等键;宁可多发不可少发,靠 at-least-once 加幂等兜底
D14 Deployment and Operations: Multi-Worker Compose, Heartbeats, Health Checks, Graceful Shutdown, Dev/Prod Isolation; Week Two Retrospective
With multiple replicas, how do you design heartbeats and health checks? Are they the same thing?多实例部署下,怎么设计心跳和健康检查?两者是同一件事吗?
Common in ChinaCommon overseasIntermediate#observability#deployment#distributed-systemsHow to reason about it · think before answering
- The hinge is are they the same thing. Answering both check liveness loses the point — the interviewer wants to see you split one word into three distinct questions, because conflating them causes real outages.
- Separate them: a liveness probe answers should this process be restarted, a readiness probe answers can you send me traffic now, and a heartbeat dashboard answers what is the cluster's state. The audiences differ: the first two are for the orchestrator, the third is for a human.
- Then say why heartbeats are not optional: the orchestrator only sees process liveness, but a worker can be alive while doing no work at all — a blocked event loop, an exhausted connection pool timing out every read, a noisy neighbor saturating host CPU. This kind of zombie is exactly what the orchestrator cannot see, and only an application-level heartbeat catches it.
- Get the direction right too: replicas push their own heartbeat rather than the gateway polling each one. Containers change IP and hostname constantly, so a poller needs a roster that is always changing — and maintaining that roster is what heartbeats are for, so the logic is circular. Report at least three things: a timestamp for liveness, in-flight count to distinguish idle from overloaded, and a version so you can watch old and new replicas during a rollout.
- The sharpest point is isolation: do not query downstream dependencies inside a readiness probe. One worker going quiet would turn every gateway's readiness red, and the orchestrator would pull the entire ingress layer — turning a non-critical fault into a full outage. In reality that worker's absence does not stop intake at all: messages sit in the stream, unacked ones get claimed by someone else, and its lease changes hands when the TTL expires.
- Expect: so how does the gateway decide whether a worker is usable? Answer that it does not, and does not need to — the gateway never assigns work to a specific worker; the consumer group and the lease decide that. Heartbeat data is for observability and alerting, not routing. Getting here shows you actually understand the layering.
分析过程 · 先想清楚再作答
- 题眼是「两者是同一件事吗」。答「都是探活」直接失分——面试官想看你能不能把一个词拆成三个不同的问题,因为混起来会造成真事故。
- 先拆问题:存活探针回答「这进程要不要被重启」,就绪探针回答「现在能不能给我发流量」,心跳面板回答「集群此刻是什么状态」。三者的读者不同:前两个给编排系统,第三个给人。
- 再说心跳为什么不可省:编排系统只能看到进程存活,而 Worker 完全可以进程活着而活儿全停——事件循环被死循环占住、连接池耗尽后取消息全超时、宿主机 CPU 被邻居打满。这类假死恰好是编排系统看不见的那种,只有业务自己上报的心跳能发现。
- 方向也要答对:心跳是副本自己 push,不是 Gateway 逐个 pull。因为容器随时换 IP 和主机名,去问的一方需要一份永远在变的名单,而那份名单本身就得靠心跳维护,逻辑绕回来了。上报内容至少三样:时间戳判活、在跑任务数区分闲和忙、版本号在滚动发布时看新旧两批各剩几个。
- 最关键的一刀是隔离性:**不要把下游依赖查进就绪探针**。一个 Worker 失联导致所有 Gateway 的就绪探针同时转红,编排系统会把整个接入层摘光——一个非核心故障被自己升级成全站不可用。而实际上那个 Worker 失联根本不影响接单:消息还在流里,没确认的会被别人接手,它的租约会因 TTL 到期而易主。
- 可以预期的追问:那 Gateway 怎么判断某个 Worker 可不可用?答「它不判断,也不需要判断」——Gateway 从不指定某个 Worker 干活,派活由消费组和租约决定,心跳的用途是观测和告警,不是路由。答到这里就说明你真的想清楚了分层。
Key points
- Split one word into three questions: liveness (restart me?), readiness (send me traffic?), heartbeat dashboard (what is the cluster doing?) — first two for the orchestrator, third for humans
- The orchestrator sees process liveness but not zombies (blocked loop, exhausted pool, stolen CPU), so an application-level heartbeat is mandatory
- Heartbeats must be pushed by replicas, not polled by the gateway: containers change IP constantly and polling needs a roster that heartbeats themselves maintain
- Report timestamp, in-flight count and version — for liveness, load, and rollout progress respectively
- Never query downstream dependencies in a readiness probe, or one quiet worker pulls the whole ingress layer and escalates a minor fault into an outage
- The gateway does not judge worker availability — the consumer group and lease assign work; heartbeats are for observability, not routing
答题要点
- 一个词要拆成三个问题:存活探针(要不要重启)、就绪探针(能不能发流量)、心跳面板(集群什么状态),前两个给编排系统、第三个给人
- 编排系统只看得见进程存活,看不见假死(事件循环卡住、连接池耗尽、CPU 被抢),所以业务层心跳不可省
- 心跳必须是副本 push 而不是 Gateway pull:容器随时换 IP,pull 需要一份靠心跳才能维护的名单,逻辑绕回来了
- 上报时间戳、在跑任务数、版本号三样,分别用于判活、区分忙闲、观察滚动发布进度
- 不要把下游依赖查进就绪探针,否则一个 Worker 失联会让整个接入层被摘掉,把非核心故障升级成全站不可用
- Gateway 不判断 Worker 可用性——派活由消费组和租约决定,心跳只用于观测告警,不用于路由
What is graceful shutdown, and why is killing a process outright risky? Walk through the steps.什么是优雅停机?为什么直接 kill 进程有风险?请说出具体步骤。
Common in ChinaCommon overseasIntermediate#deployment#reliability#operationsHow to reason about it · think before answering
- This question tests whether you have actually shipped a release. Reciting finish in-flight work before exiting is just the definition; the interviewer wants the cost, the steps, and the ordering.
- Make the cost concrete. Deploys, scale-downs, host maintenance and spot reclamation all send SIGTERM, wait a grace period, then SIGKILL. SIGKILL cannot be trapped, and landing it on a worker mid-agent-loop means: the run is stuck in running forever while the user watches a spinner; you already paid for the model call but never persisted the reply; the unacked message waits for the idle threshold before anyone claims it. One deploy cuts off dozens of conversations — that is the everyday cost.
- Then give three steps and stress that the order is fixed. One, stop accepting work: flip a flag so the consume loop stops reading from the stream (messages already fetched but not started stay in pending for someone else, which is faster than forcing a whole batch through). Two, wait for the in-flight execution, but with a ceiling. Three, proactively release leases, deregister from the heartbeat dashboard, and exit.
- The ceiling in step two earns points: a hung model call means you wait forever, and the grace period will SIGKILL you anyway. Better to concede and exit — the unacked message is still pending and someone will redo it. This course uses 20 seconds, derived from the upper bound of a normal execution plus margin.
- Step three also earns points: leases normally change hands via TTL expiry, but that path exists for sudden death. On a planned shutdown you know you are leaving, so releasing proactively lets the successor take over on its next scan instead of waiting out a full TTL. The release must be conditional — delete only the badge that still bears your name, or you will tear down the badge of whoever just claimed it after your lease expired.
- Finish with two companions; miss either and the rest is wasted. The configured grace period must exceed the wait ceiling in code (code waits 20s while compose defaults to 10s, so SIGKILL lands at second 10 and your three steps only half-run). And the signal must actually reach your process (if the entrypoint is a package manager, PID 1 is the package manager, SIGTERM may never arrive, and your shutdown code never runs once).
分析过程 · 先想清楚再作答
- 这题考的是「你有没有真的发过版」。答「等任务跑完再退出」只是定义,面试官要的是代价、步骤和顺序。
- 先把代价说具体。发版、缩容、机器维护、抢占式实例回收都会先发 SIGTERM、等宽限期、超时 SIGKILL。SIGKILL 拦不住,落到正在跑 Agent 循环的 Worker 身上:这次的 run 永远停在 running,用户界面一直转圈;模型调用的钱已经付了,回复却没落库;没确认的消息要等空闲阈值到了才被别人接手,用户白等一轮。一次发版掐断几十次对话,这就是日常代价。
- 然后给三步,强调顺序不能变:第一步拒新——把开关拨过去,消费循环下一轮不再从流里取消息(已经读到手上还没开始的那几条,留在 pending 里由别人接手,比硬扛完一整批更快);第二步等手头这次执行跑完,但要有上限;第三步主动交还租约、从心跳面板注销,然后退出。
- 第二步的上限是加分点:一次卡死的模型调用会让你永远等不到,而宽限期一到照样 SIGKILL。与其被动挨刀,不如自己认输退出——没确认的消息还在 pending 里,别人会接手重做。本课取 20 秒,取法是「一次正常执行的耗时上限」再留余量。
- 第三步也是加分点:租约本来靠 TTL 到期自然易主,但那是为进程猝死准备的。计划内下线你明知道自己要走,主动交还能让接手方下一轮扫描就上岗,而不是白等一个 TTL。交还必须带条件——只删还写着自己名字的那把牌子,否则租约已过期、别人刚抢到时,你就把对方的值班牌撕了。
- 最后两件配套的事,漏一件前面全白做:宽限期的配置必须大于代码里的等待上限(代码等 20 秒而 compose 默认只等 10 秒,第 10 秒就 SIGKILL,三步只走到一半);以及信号得真的传到你的进程(启动命令写成包管理器,PID 1 就是包管理器,SIGTERM 未必传得到,停机代码一次都不会执行)。
Key points
- Concrete cost of a hard kill: the run is stuck in running, the user stares at a spinner, the model call is paid for but the reply is unsaved, and the unacked message waits out the idle threshold
- Three steps in a fixed order: refuse new work, wait for in-flight work with a ceiling, then release leases and deregister before exiting
- The wait needs a ceiling (20s here): a hung model call never returns and the grace period kills you anyway, so concede — the message is still pending for someone else
- Releasing leases proactively lets the successor start on its next scan instead of waiting a full TTL; the release must be conditional on still owning it
- The configured grace period must exceed the in-code wait ceiling, or the three steps only half-run (stop_grace_period / terminationGracePeriodSeconds)
- Make sure the signal reaches your process: exec the business process directly rather than letting a package manager be PID 1
答题要点
- 直接 kill 的具体代价:run 永远停在 running、用户界面一直转圈、模型的钱已付但回复没落库、没确认的消息要等空闲阈值才被接手
- 三步且顺序不能变:拒绝新任务 → 等手头的跑完(有上限)→ 主动交还租约并注销心跳,然后退出
- 等待必须有上限(本课 20 秒):卡死的模型调用会让你永远等不到,宽限期一到照样被 SIGKILL,不如自己认输,消息还在 pending 里
- 主动交还租约让接手方下一轮就上岗,而不是白等一个 TTL;交还必须条件化,只删还写着自己名字的那把
- 宽限期配置必须大于代码里的等待上限,否则三步只执行到一半(compose 的 stop_grace_period / K8s 的 terminationGracePeriodSeconds)
- 信号要真传到进程:用 exec 形式直接起业务进程,别让包管理器当 PID 1
During a rolling deploy, how do you keep in-flight tasks from being interrupted?滚动发布时,如何避免正在处理的任务被打断?
Common in ChinaCommon overseasIntermediate#deployment#reliability#operationsHow to reason about it · think before answering
- This is the applied version of the previous question, and the difference is that it demands the orchestrator's side too — describing only the in-process steps answers half of it.
- The full skeleton is both sides cooperating: the orchestrator first removes traffic (turns readiness red so the load balancer stops sending new requests), then sends SIGTERM, then waits out the grace period; the process uses that window to finish in-flight work, hand back ownership, and exit cleanly. That sentence is the trunk; everything else is detail.
- Then distinguish the two kinds of replica, which is where the points are. A gateway has inbound connections, so draining traffic means something for it. A worker has no inbound connections at all — it pulls work from the bus, so draining for it means stop fetching new messages, which is step one of graceful shutdown. The same word is two different mechanisms on the two replica types, and saying so shows you understand pull versus push.
- Next, batching and ordering: replace only a subset at a time (manual batches in compose, maxUnavailable / maxSurge in Kubernetes) so enough replicas are always alive to absorb traffic. This is where the version field in the heartbeat payload pays off — you can see how many old and new replicas remain instead of deploying blind.
- Also mention state compatibility: during a rolling deploy old and new code run simultaneously, so schema migrations must be backward compatible (add a nullable column, dual-write, drop the old column last) and message formats cannot change in one shot. Many candidates miss this layer — however gracefully processes stop, two versions that cannot read the same data will still cause an incident.
- Expect: what if a single execution legitimately takes five minutes and the grace period cannot wait that long? The answer is not to stretch the grace period to five minutes but to make the task interruptible and resumable — break long work into steps that checkpoint progress (the run state machine from D11 plus at-least-once with idempotency from D9 give you exactly this), so the next replica continues the interrupted step.
分析过程 · 先想清楚再作答
- 这题是上一题的应用题,区别在于它要求你把编排系统那一侧也讲进来——只讲进程内的三步只答了一半。
- 完整骨架是两侧配合:编排系统先摘流量(把就绪探针转红,让负载均衡不再把新请求打过来)、再发 SIGTERM、然后等宽限期;进程在这段时间里把手头的活做完、交还所有权、干净退出。这一句话就是答案的主干,剩下都是细节。
- 然后区分两类副本,这是拿分点。Gateway 有入站连接,摘流量对它有意义;Worker 没有任何入站连接,它是自己去总线取活的,所谓「摘流量」对它就是「自己不再取新消息」——也就是停机三步的第一步。**同一个词在两类副本上是两种机制**,能说清这一点说明你理解拉与推的差别。
- 接着讲批次与顺序:一次只换一部分副本(compose 里手动分批,K8s 里靠 maxUnavailable / maxSurge),保证任何时刻都有足够的存活副本接得住流量。心跳面板上的版本号字段这时派上用场——你能看到新旧两批各剩几个,而不是盲发。
- 还要提一句状态兼容:滚动发布期间新旧代码同时在线,所以数据库迁移必须向后兼容(先加可空列、再双写、最后才删旧列),消息格式也不能一次性改。这是很多人漏掉的一层——进程停得再优雅,新旧版本读不了同一份数据照样出事故。
- 可以预期的追问:如果一次执行本来就要跑 5 分钟,宽限期不可能等那么久怎么办?答案不是把宽限期拉到 5 分钟,而是让任务可中断可重入——把长任务切成可保存进度的小步(D11 的 run 状态机和 D9 的 at-least-once 加幂等正好提供了这个基础),被打断的那一步由下一个副本接着做。
Key points
- The full skeleton is both sides: orchestrator drains traffic, sends SIGTERM, waits the grace period; the process finishes in-flight work, hands back ownership, exits cleanly
- Draining means two different things for gateways and workers: readiness turning red versus the worker itself stopping its fetch from the bus
- Replace in batches (maxUnavailable / maxSurge or manual) so enough replicas stay alive; the version field in heartbeats shows how many old and new remain
- Old and new code run concurrently, so migrations must be backward compatible (nullable column, dual-write, drop last) and message formats cannot change in one step
- Long tasks are not solved by a longer grace period but by being interruptible and resumable — checkpointed steps that the next replica can continue
答题要点
- 完整骨架是两侧配合:编排系统先摘流量、再发 SIGTERM、等宽限期;进程在这段时间做完手头的活、交还所有权、干净退出
- Gateway 和 Worker 的「摘流量」是两种机制:前者靠就绪探针转红让负载均衡停止转发,后者靠自己不再从总线取新消息
- 分批替换(maxUnavailable / maxSurge 或手动分批),保证任何时刻有足够存活副本;心跳里的版本号让你看到新旧两批各剩几个
- 新旧代码同时在线,所以数据库迁移必须向后兼容(加可空列 → 双写 → 最后删旧列),消息格式不能一次性改
- 长任务不该靠拉长宽限期解决,而要做成可中断可重入:切成能保存进度的小步,被打断的那步由下一个副本接着做
How do you isolate dev from prod so local development cannot touch production data?怎么设计 dev 与 prod 的隔离,防止本地开发影响线上数据?
Common in ChinaCommon overseasBasic#operations#security#configurationHow to reason about it · think before answering
- This looks basic, but it screens for whether you have been burned. People who have start with the failure shape; people who have not start with use different config files.
- Describe the failure: same codebase, often the same Redis, and you start a worker locally to debug — except it is connected to the production stream and it claims and executes a real user's message. There is no error anywhere and both sides log business as usual, because from the code's point of view it did dutifully process one message. Precisely because nothing errors, this can run for a long time before anyone notices.
- Then give layered options by cost: namespacing (shared infrastructure, prefixed keys), separate instances (its own Redis and database), and separate environments (network, credentials, accounts all split). Production eventually wants the third layer, but the first is the cheapest and the easiest to get wrong, so that is where the focus belongs.
- The implementation detail in layer one is where the points are: the prefix may only be assembled in one function. Scatter string concatenation around the codebase, miss one key out of twenty, and you have no isolation at all — and the one you missed is usually the newest, least tested feature. This point signals real experience more than add a prefix does.
- Add three companions. Split credentials, so the local key can only reach the dev database and a misconfiguration cannot reach production. Make destructive operations environment-aware: scripts that truncate tables, replay dead letters or rebuild indexes read the environment variable on their first line and demand explicit confirmation in production. And forbid fallback implementations in production: if a config slip makes production take the in-memory path, processes come up quietly, each working in its own memory, with every health check green — that kind of fault hides for hours, so failing fast at startup is far cheaper than diagnosing it later.
- Expect: why not just use separate instances and skip prefixes? Because separate instances solve connected to the wrong address while prefixes solve connected to the right address but the wrong namespace — the two fail differently. Prefixes are also nearly free, and they incidentally isolate each developer's data in a shared test environment. Defense should be layered, and there is no reason to skip the cheapest layer.
分析过程 · 先想清楚再作答
- 这题看着基础,但它筛的是「有没有踩过」。踩过的人第一句会说事故形态,没踩过的人第一句说「用不同的配置文件」。
- 先说事故形态:同一套代码、经常还是同一个 Redis,你在本机起一个 Worker 调试,它连的却是线上那条流,把真实用户的消息捞走执行了。**这类事故没有任何报错,两边日志都显示一切正常**——从代码角度看它确实老老实实处理了一条消息。正因为没有报错,它可能持续很久才被发现。
- 然后按成本分层给方案:命名空间(同一套基础设施,键名带前缀)、独立实例(各自的 Redis 与数据库)、独立环境(网络、凭证、账号全分开)。生产系统最终要走到第三层,但第一层成本最低也最容易漏,所以是重点。
- 第一层的关键实现细节是拿分点:前缀只能在一个函数里拼。散落到各处去拼字符串,二十个键名里漏掉一个就等于没隔离,而漏掉的那个通常是最新加、最没被测过的功能。这一点比「要加前缀」本身更能体现工程经验。
- 再补三件必须一起做的事:凭证分开(本机那把 key 只能连开发库,配置写错也波及不到线上);破坏性操作要认环境(清库、重放死信、重算索引这类脚本第一行先读环境变量,生产上要求显式确认);生产禁止降级实现(离线用的内存实现在生产上一旦因配置疏漏被走到,进程会安静起来、各自在自己内存里干活,健康检查还全是绿的,这类故障能藏好几个小时——启动时直接报错退出比事后排查便宜得多)。
- 可以预期的追问:为什么不干脆只用独立实例,省掉前缀这一层?答:独立实例解决的是「连错了地址」,前缀解决的是「连对了地址但走错了命名空间」——两者失效的方式不同。而且前缀几乎零成本,在共享测试环境、多人并行开发时还能顺带隔离每个人的数据。防御要分层,最便宜那层没理由不做。
Key points
- Lead with the failure shape: a local worker attached to the production stream claims and runs a real user's message, with normal logs on both sides and no error, so it hides for a long time
- Three layers by cost: namespacing (key prefixes), separate instances (own Redis and DB), separate environments (network, credentials, accounts)
- The prefix must be assembled in exactly one function — scattered concatenation misses one key and voids the isolation, usually the newest and least tested feature
- Split credentials so the local key only reaches dev; destructive scripts read the environment first and require explicit confirmation in production
- Forbid the in-memory fallback in production: on a config slip processes come up quietly with green health checks and the fault hides for hours — fail fast at startup instead
- Separate instances prevent wrong address, prefixes prevent right address wrong namespace — different failure modes, and the cheapest layer is free
答题要点
- 先说事故形态:本机 Worker 连上线上流,把真实用户消息捞走执行,且两边日志都显示正常、没有任何报错,所以能藏很久
- 按成本分三层:命名空间(键名前缀)、独立实例(各自 Redis 与库)、独立环境(网络凭证账号全分开)
- 前缀只能在一个函数里拼——散落各处漏掉一个键就等于没隔离,而漏掉的通常是最新加、最没测过的功能
- 凭证分开,本机 key 只能连开发库;破坏性脚本第一行读环境变量并在生产要求显式确认
- 生产禁止降级到内存实现:配置疏漏时进程会安静起来、健康检查全绿,故障能藏几小时,应在启动时直接报错退出
- 独立实例防「连错地址」、前缀防「地址对了但命名空间错了」,失效方式不同,最便宜那层没理由不做
System design: design an IM agent platform where users chat with an AI assistant inside a messaging app. The assistant calls tools, remembers long-term preferences, and proactively pushes scheduled messages. Target 100k daily active users.系统设计:请设计一个 IM Agent 平台——用户在即时通讯软件里和一个 AI 助手对话,助手能调用工具、记住长期偏好、还能定时主动推送。要求支撑十万日活。
Common in ChinaCommon overseasDeep dive#system-design#distributed-systems#cost#operationsHow to reason about it · think before answering
- Do not start drawing. The most common way to fail a design question is to hear the prompt and immediately sketch boxes, only for the interviewer to realize twenty minutes later that you solved a different problem. Spend three to five minutes on four questions: traffic shape (how many concurrent sessions does 100k DAU imply, and what is the peak-to-trough ratio), latency (how fast must first byte be, is streaming required), the nature of the tools (read-only lookups, or writes with side effects), and the compliance boundary on proactive pushes (may you push at night, what is the daily cap). All four change the architecture materially, so asking them is itself worth points.
- Then state the trunk in one sentence: stateless ingress, a message bus for decoupling, stateful workers sharded by user, all state in the database. Walk the data flow: the messaging platform's webhook hits ingress, which does only auth, rate limiting, persistence and publish, and returns 202 immediately; the execution side pulls work, runs the agent loop, and streams output fragments back; proactive pushes come from a central scheduler publishing onto the same bus. The load-bearing argument is that ingress latency is bounded while execution latency is not, so putting them in one process means one slow model call occupies a connection that should have returned in milliseconds — say this out loud, it is the premise of the whole answer.
- Then justify each module. Storage: sessions, runs and messages, with runs existing separately because only it can answer whether this attempt actually finished; idempotency comes from a unique constraint on runs, not from check-then-insert. Bus: Redis Streams consumer groups for fan-out, at-least-once semantics, with exactly-once manufactured by consumer-side idempotency, and messages that fail three times moved to a dead-letter stream. Ordering: the consumer group's unit of assignment is one message while the business requires serialization per user, so hash userId into a fixed set of shards and let exactly one worker hold each shard's lease. Memory: embeddings in pgvector, retrieval wrapped as a tool the model chooses to call, with no identity parameter — identity only ever comes from the session.
- Treat proactive push as its own section, because it is what separates this from an ordinary chat service. The central scheduler publishes one message on a time match and the execution side is unchanged; the idempotency key is anchored to the scheduled minute, so replaying after a scheduler restart cannot double-send. For compliance you need timezone, quiet hours and a daily cap — and all three must be evaluated before publishing rather than at send time, or you have already paid for the model call before discovering you should not have pushed.
- Then volunteer capacity and cost numbers, which is what separates senior candidates. 100k DAU at ten turns each is a million model calls; at roughly a thousand tokens in and out, with input at $0.15 and output at $0.60 per million tokens, that is about $750 a day. That number immediately implies three requirements: meter token usage per call and convert to dollars (otherwise you cannot tell which user or feature is burning money), build tiered degradation (push over-budget users to a cheaper model rather than refusing them), and recognize that context length is the dominant cost lever (so compress history and cap retrieved items).
- Land on operability, which is this week's payoff: multiple replicas, heartbeats to surface zombies, readiness probes that only check their own hard dependencies, graceful shutdown so deploys do not cut conversations, and dev/prod isolation via key prefixes. Pair every mechanism with what happens when it fails — leases can split-brain so you need a self-fencing rule and fencing tokens, heartbeats produce false positives so a red dashboard alerts a human rather than auto-draining, shutdown can time out so the wait needs a ceiling. A mechanism without a stated failure mode reads as something you only read about.
- Expect, in rough order of frequency: where are the single points (the scheduler is stateless and restartable; Redis and Postgres rely on managed primary/replica); how do you roll out safely (old and new workers coexist and a version field in the message selects the prompt set); what if the user sends another message mid-reply (merge a change of mind within thirty seconds into the same execution rather than running two concurrently); and how would you halve the cost (cache frequent answers, compress history, route simple intents to a smaller model).
分析过程 · 先想清楚再作答
- 先别画图。系统设计题最常见的死法是听完就开始画框,二十分钟后面试官发现你解的是另一道题。花三到五分钟问清四件事:一是流量形状(十万日活对应多少并发会话、峰谷比多少),二是延迟要求(首字节要多快,是否必须流式),三是工具的性质(只读查询还是有写操作和副作用),四是主动推送的合规边界(能不能在深夜推、每天上限几条)。这四个答案会实质改变架构,问它们本身就是分数。
- 然后给主干,一句话先定形状:**接入层无状态、消息总线解耦、Worker 有状态且按用户分片、状态全在数据库**。接着按数据流走一遍:IM 平台的 webhook 打到接入层,接入层只做鉴权、限流、落库、投递四件事,立刻返回 202;执行侧从总线取活、跑 Agent 循环、把输出片段回传;主动推送由一个中心调度器按时间投递进同一条总线。**关键论点是接入层耗时确定、执行层耗时不确定,把它们放在一个进程里意味着一次慢的模型调用会占住一个本该毫秒级返回的连接**——这是整道题的立论基础,要主动说出来。
- 再逐个模块给出选择和理由。存储:sessions / runs / messages 三张表,runs 单独存在是因为只有它能回答「这次到底跑完没有」,幂等靠 runs 上的唯一约束而不是先查后插。总线:Redis Streams 的消费组做分摊,语义是至少一次,恰好一次靠消费端幂等做出来;反复失败的消息投递三次后进死信流。顺序:消费组的分配单位是一条消息而业务要求的串行单位是一个用户,所以按 userId 哈希到固定数量分片,每个分片同一时刻只有一个 Worker 持有租约。记忆:pgvector 存 embedding,检索包成一个工具交给模型自己决定要不要查,且不给它身份参数——身份只能来自会话。
- 主动推送这一块要单独讲透,因为它是这道题区别于普通聊天服务的地方。中心调度器命中时间点后只投一条消息,执行侧照旧;幂等键锚在「计划触发的那一分钟」,所以调度器崩溃重启后回看重放不会重复推送。合规上要有时区、静默时段、每日上限三道闸,而且这三道闸必须在投递前判断而不是在推送时判断——否则你已经花了模型调用的钱才发现不该推。
- 然后主动给出容量和成本的数字感,这是高级候选人的分水岭。十万日活、人均十轮对话是一百万次模型调用;按输入输出各一千 token、每百万 token 输入 0.15 美元输出 0.60 美元估算,一天大约七百五十美元。这个数字立刻推出三件事必须做:token 用量要按调用记账并换算成美元(否则你无法定位是哪个用户或哪个功能在烧钱)、要有分层降级(超预算的用户切便宜模型而不是直接拒绝)、以及上下文长度是主要成本杠杆(所以要压缩历史、控制检索条数)。
- 最后收在可运维性上,也就是这一周的落点:多副本部署、心跳发现假死、就绪探针只查自己必需的依赖、优雅停机让发版不掐断对话、dev 与 prod 用键名前缀隔离。**每个机制都要配一句「它失效时会怎样」**——租约会脑裂所以要有自杀规则和护栏令牌、心跳会误判所以面板转红只告警不自动摘流量、停机会超时所以等待要有上限。说不出失效模式的机制,面试官会认为你只是读过。
- 可以预期的追问,按出现频率排:单点在哪(调度器无状态可重启,Redis 和 Postgres 靠托管服务的主备);怎么灰度(新旧 Worker 同时在线,靠消息里的版本字段决定走哪套提示词);用户在助手回复中途又发一句怎么办(三十秒内的改口合并进同一次执行,而不是并发开两个);成本再降一半怎么做(缓存高频问答、压缩历史、把简单意图路由到小模型)。
Key points
- Spend three to five minutes clarifying four things: traffic shape, latency targets, whether tools have side effects, and the compliance boundary on proactive pushes
- State the trunk in one sentence: stateless ingress, bus for decoupling, stateful workers sharded by user, all state in the database — premised on bounded ingress latency versus unbounded execution latency
- Storage is sessions/runs/messages with idempotency from a unique constraint on runs; the bus is Redis Streams consumer groups, at-least-once plus consumer idempotency, dead-lettering after three failures
- Ordering comes from hashing userId into shards plus leases: the consumer group assigns per message while the business serializes per user
- Memory is pgvector exposed as a tool the model may call, with no identity parameter — identity comes only from the session
- Proactive push flows through a central scheduler with the idempotency key anchored to the scheduled minute; timezone, quiet hours and daily caps are enforced before publishing
- Bring numbers: 100k DAU at ten turns is ~1M calls and ~$750/day, which implies metering, tiered degradation, and context length as the main cost lever
- Land on operability: replicas, heartbeats for zombies, readiness probes scoped to own dependencies, graceful shutdown, dev/prod prefix isolation
- Pair each mechanism with its failure mode — leases split-brain, heartbeats false-positive, shutdown times out; a mechanism without one reads as book knowledge
答题要点
- 先用三到五分钟问清四件事:流量形状、延迟要求、工具是否有副作用、主动推送的合规边界——它们会实质改变架构
- 主干一句话:接入层无状态、消息总线解耦、Worker 有状态且按用户分片、状态全在数据库;立论是接入层耗时确定而执行层不确定
- 存储 sessions / runs / messages 三张表,幂等靠 runs 上的唯一约束;总线用 Redis Streams 消费组,至少一次加消费端幂等,三次失败进死信
- 顺序靠 userId 哈希分片加租约:消费组的分配单位是一条消息,而业务要求的串行单位是一个用户
- 记忆用 pgvector 并包成工具交给模型自己决定是否检索,不给身份参数——身份只能来自会话
- 主动推送由中心调度器投递,幂等键锚在计划触发的那一分钟;时区、静默时段、每日上限三道闸必须在投递前判断
- 给出成本数字感:十万日活人均十轮约一百万次调用、一天约七百五十美元,由此推出计量记账、分层降级、压上下文三件事
- 收在可运维性:多副本、心跳查假死、就绪探针只查自己的依赖、优雅停机、dev/prod 前缀隔离
- 每个机制都配一句失效模式:租约会脑裂、心跳会误判、停机会超时——说不出失效模式等于只是读过
D15 A Tour of Multi-Agent Patterns (Router/Supervisor, Planner-Executor, Critic, Swarm, Blackboard) and When Not to Use Them; Getting Started With LangGraph
What are the common multi-agent collaboration patterns, and what shape of task suits each?常见的多 Agent 协作模式有哪些?分别适合什么形状的任务?
Common in ChinaCommon overseasBasic#multi-agent#orchestration#architectureHow to reason about it · think before answering
- This looks like a giveaway but it separates people who memorized names from people who have split a system. Listing five names is a bare pass; the interviewer wants the axis you use to tell them apart, because an axis means you can classify an architecture you have never seen.
- Offer a reusable axis: the difference is not the name, it is the shape of the graph. Four questions suffice — is there a branch (pick one at runtime), a fan-out (hand it to several at once), a join (merge several outputs), a back edge (send it back for rework).
- Then place each one: Router/Supervisor is branch only, one specialist per turn, the hard part is deciding who; Planner-Executor is fan-out plus join, for work that splits into independent pieces; Critic is branch plus back edge, for output with a clear pass/fail test where redoing is cheaper than shipping; Swarm is also branch plus back edge, but the next hop is chosen by whoever holds the baton; Blackboard is fan-out plus join plus back edge, participants unaware of each other, reacting only to shared state.
- Point out yourself that Critic and Swarm score identically on all four, and that the real difference is who decides the back edge — a fixed reviewer node versus the current agent. Volunteering where your own criterion breaks down scores better than reciting one more pattern name, because it proves you have used the axis rather than invented it on the spot.
- Attach a cost to each: Router adds one routing call of latency; Planner-Executor's parallelism creates write conflicts so fields need merge rules; Critic loops need a hard retry cap or nothing ever ships; Swarm has no upfront bound on steps so cost and latency are hard to cap; Blackboard has the hardest termination condition and tends to either stall or re-trigger.
- Expect: which do you use most in production? Say Router/Supervisor, because its failure mode is the easiest to read — check the recorded routing reason — and because it is the one pattern that can save money, by routing simple intents to a cheaper model.
分析过程 · 先想清楚再作答
- 这题看似送分,其实在筛「背过名词」和「拆过系统」。只报五个名字最多拿及格分,面试官真正想听的是你用什么维度把它们区分开——有维度说明你能给没见过的架构归类,没维度说明你只是读过一篇综述。
- 给一个可复用的维度:模式的差别不在名字,在图的形状。盯四件事就够——有没有分叉(运行时三选一)、有没有扇出(同时交给多个人)、有没有汇合(多份产出合到一起)、有没有回边(可以打回重做)。
- 然后逐个落位:Router/Supervisor 只有分叉,一次只找一个专家,难点在判断该找谁;Planner-Executor 是扇出加汇合,适合一件事拆成几件、几件之间没有先后;Critic 是分叉加回边,适合对错有明确判据、且重做比发出去便宜的产出;Swarm 也是分叉加回边,但下一棒交给谁由当前这位自己决定;Blackboard 是扇出加汇合加回边,参与者互相不知道对方存在,只认公共状态。
- 主动指出 Critic 和 Swarm 的四个特征一模一样,区别落在「回边由谁决定」——Critic 是固定的评审节点在判,Swarm 是当前这位自己判。**主动承认自己的判据在哪里失效,比多背一个模式名更能加分**,因为它证明你真的用过这套维度而不是刚编出来。
- 每种模式还要配一句代价,这是区分度所在:Router 多一次路由调用的延迟;Planner-Executor 的并行会带来状态写冲突,字段必须配合并规则;Critic 的回路必须有次数上限,否则永远出不了稿;Swarm 事先不知道会走多少步,成本和延迟都难封顶;Blackboard 的终止条件最难写,容易谁都不接活或者反复触发。
- 可以预期的追问:生产上你最常用哪个?答 Router/Supervisor,理由是它的失败模式最好理解——路由判错了看一眼路由理由就知道,而且它是唯一一个能顺便省钱的模式,简单意图可以路由到便宜的小模型。
Key points
- Give the axis before the names: branch, fan-out, join and back edge separate all five patterns
- Router/Supervisor is branch only — one specialist per turn, the hard part is choosing who
- Planner-Executor is fan-out plus join — split into independent subtasks, then merge into one deliverable
- Critic is branch plus back edge — for output with a clear pass/fail test, and it needs a hard retry cap
- Swarm scores the same as Critic; the difference is who decides the back edge. Blackboard decouples via shared state and has the hardest termination condition
- Pair each with a cost: extra call latency, parallel write conflicts, infinite review loops, unbounded step count, fuzzy termination
答题要点
- 先给维度再给名字:分叉、扇出、汇合、回边四个特征就能把五种模式分开
- Router/Supervisor 只有分叉,一次只找一个专家,难点是判断该找谁
- Planner-Executor 是扇出加汇合,适合拆成几件互不依赖的小任务再合成一份交付
- Critic 是分叉加回边,适合对错有明确判据、重做比发出去便宜的产出,必须配打回次数上限
- Swarm 与 Critic 的四个特征相同,区别在回边由谁决定;Blackboard 靠公共状态解耦,终止条件最难写
- 每种模式配一句代价:多一次调用的延迟、并行的写冲突、回路的死循环、步数不封顶、终止条件难定
In LangGraph, what roles do nodes, edges and state play? If you had no framework, how would you implement it yourself?LangGraph 里节点、边、状态分别扮演什么角色?如果不用框架,你自己会怎么实现?
Common in ChinaCommon overseasIntermediate#langgraph#orchestration#state-managementHow to reason about it · think before answering
- The hinge is the second half. Defining the three concepts only proves you read the docs; explaining what hurts without a framework proves you know what it buys you. The general move for this family of questions is: describe your hand-rolled version first, then name what the framework collapsed.
- Hand-rolled version: a loop, a chain of conditionals picking the next step, and one big object carrying data between steps. By the third branch you hit three walls — when two steps write the same field, is it overwrite or append, and you hand-write that merge in every branch; intermediate state lives in local variables so debugging means print statements; a crash restarts from zero and the model calls you already paid for are wasted.
- Then map them: a node is an ordinary function that reads the whole state and returns a delta containing only what it changed; edges connect nodes, unconditional ones fix the order and conditional ones decide at runtime; state is a table of fields where each field is its own channel carrying a merge rule.
- Dwell on the third, which is the most skipped and most valuable point: the merge rule is declared on the field, not written inside the node. Adding a node therefore requires no thought about how to combine with other writers, and parallel writes to one field behave deterministically instead of depending on who returns first.
- Add two concrete traps to show you have actually run this: mutating state in place inside a node bypasses the merge rule — invisible single-threaded, an intermittent overwrite once things run in parallel; and adding a node without wiring an edge raises no error at all, it simply never executes, which only per-node tracing reveals.
- Expect: so why not just write it yourself? Because the three primitives are genuinely light — a few dozen lines. What the framework actually sells is checkpointing and recovery, parallel execution, and per-step observability, all of which cost far more to build than the primitives. Mention too that there is no official LangGraph for Java or Swift, so in those languages you do hand-roll exactly these three.
分析过程 · 先想清楚再作答
- 题眼在后半句。只答三个概念的定义,面试官会认为你读过文档;能说出「不用框架会难受在哪」,才证明你知道框架替你解决了什么。这类题的通用解法是:先讲自己手写的版本,再讲框架把哪几处收敛了。
- 先给手写版:一个循环,里面一串条件判断决定下一步走哪,中间用一个大对象在各步之间传数据。写到第三个分支就会撞上三件事——两步都往同一个字段写,是覆盖还是追加,你要在每个分支里手写一遍合并逻辑;中间过程全在局部变量里,出错只能靠打印;进程一挂就从头重来,已经花掉的模型调用钱白付。
- 然后一一对上:节点是一个普通函数,读全量状态、返回只含改动字段的增量;边是节点之间的连接,无条件边写死顺序,条件边在运行时决定去哪;状态是一张字段表,每个字段是一条独立通道,通道上挂着合并规则。
- 重点讲第三条,因为它是最容易被略过、也最值钱的一条:**合并规则是声明在字段上的,不是写在节点里的**。这意味着新增节点时不需要考虑「我该怎么和别人的写入合并」,字段自己知道;也意味着并行写同一个字段时行为是确定的,而不是取决于谁先返回。
- 配两个具体的坑,证明你真跑过:一是在节点里原地修改状态(比如直接往数组里 push)会绕过合并规则,单线程时察觉不到,并行时变成偶发覆盖;二是加了节点没连边不会报错,表现只是那个节点永远不执行,只能靠逐节点追踪发现。
- 可以预期的追问:那你为什么不直接自己写?答:三要素本身很轻,核心逻辑几十行就能手写出来——框架真正值钱的是检查点与恢复、并行执行、以及每一步的可观测,这三样自己写的成本远高于三要素本身。顺带说明 Java 和 Swift 没有官方 LangGraph,真要在这两门语言里做,就是把这三要素手写一遍。
Key points
- A node is a plain function: read the full state, return a delta of changed fields only, never mutate in place
- Edges set execution order: unconditional edges are fixed, conditional edges decide the next hop at runtime — that is what a supervisor uses
- State is a table of fields, each field a channel carrying a merge rule declared on the field rather than inside nodes
- Without a framework you hit three walls: hand-written merges in every branch, no visibility into intermediate steps, and full restart after a crash
- Two real traps: in-place mutation bypasses the merge rule and causes intermittent overwrites under parallelism; an unwired node raises no error, it just never runs
- What the framework really sells is checkpoint recovery, parallel execution and per-step observability — not the three primitives themselves
答题要点
- 节点是普通函数:读全量状态,返回只含改动字段的增量,不在节点里原地改状态
- 边决定执行顺序:无条件边写死,条件边在运行时决定下一步去哪(Supervisor 就靠它)
- 状态是一张字段表,每个字段一条通道,通道上挂合并规则——规则声明在字段上而不是写在节点里
- 不用框架会撞三堵墙:合并逻辑在每个分支手写一遍、中间过程只能靠打印、进程挂了从头重来
- 两个真实的坑:原地改状态绕过合并规则(并行时偶发覆盖)、加了节点没连边不报错只是永不执行
- 框架真正值钱的不是这三要素,而是检查点恢复、并行执行和逐步可观测
What signals typically trigger the move from a single agent to a multi-agent system, and what does the upgrade cost you?从单 Agent 升级到多 Agent,通常是被什么信号触发的?升级之后系统会多付出什么?
Common in ChinaCommon overseasIntermediate#multi-agent#cost#architectureHow to reason about it · think before answering
- This question tests whether business pain forced the split or a blog post did. Answering the business got complex is a non-answer; the interviewer wants observable signals — what symptom made you act.
- Give five, in the order they usually appear: prompts start fighting each other (add one rule, another metric drops); the tool list grows until you need the docs yourself; one step's failure needs isolated handling instead of redoing the whole turn; you want a different model for one specific step; and evaluation granularity is too coarse to say more than good or bad.
- Expand on the fourth, the counter-intuitive one: multi-agent is usually more expensive, but per-step model selection is the one case where it saves money — a short triage decision on a cheap small model, a drafting step on a larger one. A single agent cannot swap models per step. This lands well in interviews.
- Then volunteer the costs, or the answer reads as evangelism: latency multiplies by step count (two seconds becomes six, while user patience is about three); cost grows linearly with calls because every step re-sends the current state as context, typically three times; and debugging cost grows with state dimensions, since a failure now requires checking routing, each sub-agent's input state, and whether merges overwrote each other.
- Add the reverse check to show you are not splitting reflexively: too many tools should first prompt consolidation and tighter descriptions, with splitting as the second response; poor quality should first prompt tuning the single-agent version to its best, which then becomes the baseline the multi-agent version is measured against.
- Expect: how do you prove the split helped? Keep the single-agent version as a baseline and A/B both against the same golden set, comparing accuracy alongside per-conversation cost and latency. People who cannot name a baseline usually cannot explain why they split either.
分析过程 · 先想清楚再作答
- 这题考的是「你是被业务逼着拆的,还是照着博客拆的」。答「业务变复杂了」等于没答,面试官要的是**可观测的信号**:什么现象出现时你才动手。
- 给五个按出现顺序排的信号:一是提示词开始互相打架(加一条规则,另一个指标就掉);二是工具列表长到自己都要查文档;三是某一步的失败需要单独处理,不该整轮重来;四是想给某一步单独换模型;五是评估颗粒度不够,只能整体打分好或不好。
- 第四个信号要展开讲,它是唯一一个反常识的:多 Agent 通常更贵,但按步换模型是它唯一能省钱的场景——分诊这种短判断走便宜的小模型,拟方案走大模型。单 Agent 做不到按步换模型。这一条在面试里是明显的亮点。
- 然后主动给代价,不给代价的回答会被当成布道:延迟按步数乘倍数(原来两秒变六秒,而用户耐心大约三秒);成本按调用次数线性涨,因为每一步都要把当前状态重新塞进上下文,典型是三倍;调试难度按状态维度涨,出错要同时回答路由对不对、每个子 Agent 拿到的状态对不对、合并有没有互相覆盖。
- 再补一句反向判断,证明你不是无脑拆:工具太多的第一反应应该是合并工具、收敛描述,拆 Agent 是第二反应;质量差的第一反应应该是把单 Agent 版本调到最好,那个版本还会成为多 Agent 的对照基线。
- 可以预期的追问:拆完怎么证明比原来好?答:留住单 Agent 版本当基线,用同一批标准样本集跑 A/B,比准确率也比每次对话的成本与延迟。说不出对照基线的人,通常也说不清自己为什么拆。
Key points
- Five observable signals: prompts fighting each other, a tool list you must look up, one step needing isolated retries, wanting a different model per step, and evaluation too coarse to act on
- Per-step model selection is the only case where multi-agent saves money: small model for triage, larger model for drafting — impossible in a single agent
- Cost one: latency multiplies with step count, two seconds becomes six, while patience for a support bot is about three
- Cost two: spend grows linearly with calls since every step re-sends state as context, typically three times the original
- Cost three: debugging cost grows with state dimensions, so multi-agent and tracing have to ship together
- Reverse check: consolidate tools before splitting, and tune the single agent to its best first — that version becomes your baseline
答题要点
- 五个可观测信号:提示词互相打架、工具多到要查文档、某一步需要独立重试、想按步换模型、评估颗粒度不够
- 按步换模型是多 Agent 唯一能省钱的场景:短判断走小模型、拟方案走大模型,单 Agent 做不到
- 代价一:延迟按步数乘倍数,两秒变六秒,而用户对客服机器人的耐心大约三秒
- 代价二:成本线性涨,每一步都要把状态重新塞进上下文,典型是原来的三倍
- 代价三:调试难度按状态维度涨,所以多 Agent 和链路追踪必须一起上
- 反向判断:工具多先合并再拆分,质量差先把单 Agent 调到最好——那个版本还是多 Agent 的对照基线
When should you not introduce a multi-agent system? Give operational criteria, not it depends.什么情况下不应该引入多 Agent 系统?请给出可操作的判据,而不是「视情况而定」。
Common in ChinaCommon overseasDeep dive#multi-agent#architecture#trade-offsHow to reason about it · think before answering
- This is the highest-signal question in the set because it is asked in reverse. Most candidates keep selling how powerful multi-agent is, while the interviewer is looking for someone who will say no — on a real team, blocking one unnecessary architecture upgrade is worth more than implementing three patterns.
- Lead with the default: do not split. Then give three criteria, any one of which justifies splitting — the system prompt contains mutually exclusive behavioural requirements (strictly enforce refund rules while also warmly retaining the customer; these are not hard to write, they are impossible to optimize together); the tool count exceeds what the model picks reliably (roughly eight as a rule of thumb, and the first response to crossing it is consolidating tools, not splitting agents); or one step needs its own failure and retry semantics. None of the three, and a single agent with a few tools is enough.
- Then name the most common bad split: treating a prompt problem as an architecture problem. Quality is poor, so we split into three agents — but nine times out of ten poor quality comes from vague prompts, tool descriptions that interfere with each other, or irrelevant history in the context. All three survive the split and are now harder to find. Splitting fixes conflicting responsibilities, not weak capability.
- Add two scenarios that clearly should not split: latency-sensitive interactions, where each extra hop is another model round trip and voice or realtime completion becomes unusable; and read-only lookup flows, where a support assistant with three or four tools gains no accuracy from splitting and simply triples the bill.
- Then offer an executable verification path, which earns points: keep the single-agent version as a baseline for any split and A/B both against the same golden set, comparing accuracy, per-conversation cost and latency together. An architecture upgrade with no baseline is a refactor with no evidence.
- Expect: what if your manager insists on multi-agent? Frame it as a reversible experiment — make the one cut you are most confident in (usually the conflicting-rules criterion), keep the baseline, and bring data in two weeks. That answer shows technical judgment and a way to disagree without stonewalling.
分析过程 · 先想清楚再作答
- 这是本组最有区分度的题,因为它反着问。绝大多数候选人会顺着「多 Agent 很强大」讲下去,而面试官问这题正是想找那个会说不的人——**在真实团队里,拦住一次不必要的架构升级,价值高于实现三个模式**。
- 先给结论式的默认值:默认答案是不拆。然后给三条判据,命中任意一条才拆——一是单个 Agent 的系统提示词里出现了互斥的行为要求(既要严格核对退款规则又要热情挽留,这两条不是难写,是不可能同时最优);二是工具数量超过模型能稳定选对的规模(经验线大约八个,超线的第一反应是合并工具而不是拆 Agent);三是某一步需要独立的失败与重试语义。三条都不命中,单 Agent 加几个工具就够。
- 接着点名最常见的错拆:把提示词问题当成架构问题。「回答质量不好,所以拆成三个 Agent」——质量差有九成来自提示词含糊、工具描述互相干扰、上下文塞了无关历史,这三样拆完一样存在,只是分散到三个地方更难查。**拆 Agent 解决的是职责冲突,不是能力不足。**
- 再补两类明确不该拆的场景:一是低延迟要求的场景,多一跳就多一次模型往返,对语音或实时补全这类交互直接不可用;二是只读的简单查询链路,三五个工具的客服助手拆了只是把一次调用变成三次,准确率不会涨、账单会涨。
- 然后给一条可执行的验证路径,这是加分项:任何拆分都先留住单 Agent 版本当对照基线,用同一批标准样本集跑 A/B,同时比准确率、每次对话成本和延迟。**拿不出对照基线的架构升级,等于没有证据的重构。**
- 可以预期的追问:那如果老板就是要求上多 Agent 呢?答:那就把它当成一个可回退的实验来做——先按判据拆最有把握的那一刀(通常是互斥规则那一条),保留基线,两周后拿数据说话。这个回答同时展示了技术判断和沟通方式,比硬顶或硬上都好。
Key points
- Default to not splitting; split only if one of three criteria holds: mutually exclusive prompt requirements, tool count past the roughly-eight warning line, or a step needing its own failure and retry semantics
- Too many tools should first trigger tool consolidation and tighter descriptions; splitting agents is the second response
- The most common bad split is treating a prompt problem as an architecture problem — vague prompts, interfering tool descriptions and irrelevant history all survive the split
- Clear do-not-split cases: latency-sensitive interactions where every hop adds a model round trip, and read-only lookup flows where accuracy does not move but the bill does
- Always keep the single-agent version as a baseline and compare accuracy, cost and latency on the same golden set
- Say the costs out loud: latency multiplies with steps, spend roughly triples, and debugging now spans routing plus state merging
答题要点
- 默认答案是不拆;三条判据命中任意一条才拆:提示词有互斥要求、工具超过约八个的告警线、某一步需要独立的失败与重试语义
- 工具太多的第一反应是合并工具与收敛描述,拆 Agent 是第二反应
- 最常见的错拆是把提示词问题当架构问题——质量差多半来自提示词含糊、工具描述干扰、上下文塞了无关历史,拆完这三样照旧存在
- 明确不该拆:低延迟交互(每多一跳就多一次模型往返)、只读的简单查询链路(准确率不涨、账单涨)
- 任何拆分都要留单 Agent 版本当对照基线,用同一批标准样本集比准确率、成本和延迟
- 代价要说出口:延迟按步数乘倍数、成本约三倍、调试要同时排查路由与状态合并
D16 Dynamic Routing With a Supervisor: Structured-Output Routing, Override, routingReason
How is the routing decision usually implemented in a supervisor pattern? What should that node do, and what should it not do?Supervisor 模式里的路由决策一般怎么实现?请说说这个节点该做什么、不该做什么。
Common in ChinaCommon overseasBasic#multi-agent#routing#langgraphHow to reason about it · think before answering
- This is a warm-up question, and warm-ups are where people lose points by restating the prompt: an agent decides who goes next. The discriminator is the second half — can you state the node's responsibility boundary?
- Start with the mechanics: the supervisor is an ordinary node. It reads state, makes one model call, and writes exactly two fields — the route and the reason for it. The actual branching happens on the conditional edge after it, whose selector function maps the route to the next node name.
- Then draw the boundary, which is where the points are: the supervisor never answers the user, never calls business tools, and produces no side effects. It only takes a multiple-choice test, so it can run on a cheaper small model with a short input.
- One boundary people miss: do not call the model inside the selector function. The judgment was already made and stored in state; the selector only translates. Calling a model there makes the same state jump to different nodes across runs, which destroys reproducibility and breaks checkpoint replay and evaluation later.
- Close with one-at-a-time: a supervisor answers who takes this, not how to split a task and who reviews the output. That second problem belongs to planner-executor-critic. Drawing that line yourself signals you have seen a real system.
- Expect: where does the list of sub-agents live, and how many places change when you add one? Answer that the list should be a single source of truth — the enum, the schema, and the edge mapping all derive from it, so adding an agent is one edit and everything else fails at compile time.
分析过程 · 先想清楚再作答
- 这是一道送分题,但送分题最容易答成「让一个 Agent 决定下一步找谁」这种复述题面的话。区分度在后半句:你能不能说清这个节点的职责边界。
- 先给机械原理:Supervisor 是图里的一个普通节点,它读状态、调一次模型、只写两个字段——交给谁(route)和为什么这么判(routingReason);真正的分叉发生在它后面那条条件边上,边上挂一个选择函数,把 route 翻译成下一个节点名。
- 再划边界,这是拿分的地方:Supervisor 不回答用户的问题、不调业务工具、不产生副作用。它只做选择题,所以可以配一个更便宜的小模型,输入通常只有系统提示词加最后一两句话。
- 还有一条边界更容易被忽略:**选择函数里不要再调模型**。判断已经在 Supervisor 节点里做完并落进状态了,选择函数只做翻译。把模型调用塞进选择函数,同一份状态每次可能跳到不同的节点,图就不可复现,后面做检查点重放和评估都会失真。
- 最后补一句「一次只派一个人」:Supervisor 解决的是「交给谁」,不解决「一件事要拆成几件、还得有人验收」。后者是 Planner-Executor-Critic 的活。能主动划出这条线,面试官会认为你见过真实系统的边界。
- 可以预期的追问:那三个子 Agent 的名单从哪来、加一个新的要改几处?答案是名单应该是单一真相来源——枚举定义、schema、条件边的映射表都从它生成,加一个子 Agent 只改一处,其余地方编译期报错提醒你。
Key points
- The supervisor is an ordinary node: read state, one model call, write only the route and the routing reason
- Branching lives on the conditional edge after it — a selector maps the route to a node name, and the mapping table must be exhaustive
- Boundary: it never answers the user, calls no business tools, has no side effects, so it can run on a cheaper small model
- Never call a model inside the selector, or the same state jumps to different nodes across runs and replay and evaluation both break
- A supervisor dispatches one agent at a time and only answers who takes this; splitting and reviewing belong to planner-executor-critic
答题要点
- Supervisor 是图里的一个普通节点:读状态、调一次模型、只写 route 与 routingReason 两个字段
- 真正的分叉在它后面的条件边上:选择函数把 route 翻译成下一个节点名,映射表要写全
- 职责边界:不回答用户、不调业务工具、不产生副作用,因此可以单独配一个更便宜的小模型
- 选择函数里不能调模型,否则同一份状态每次跳的节点不同,图不可复现,检查点重放与评估都会失真
- Supervisor 一次只派一个人,只解决「交给谁」;拆任务与验收是 Planner-Executor-Critic 的职责
Why use structured output rather than natural language for routing? What exactly goes wrong with free text?为什么要让模型输出 structured output 而不是自然语言来做路由?自然语言到底差在哪?
Common in ChinaCommon overseasIntermediate#structured-output#routing#reliabilityHow to reason about it · think before answering
- The trap is answering structured output is cleaner and easier to parse. Those are adjectives, not reasons. The interviewer wants a concrete failure you have actually debugged.
- Lead with the sharpest point: free-text routing fails silently. The model replies I think the order desk should look at this — it judged correctly, but it spoke prose, not an id. Your regex misses, you fall through to the default, and the log shows only smalltalk. A correct model with a broken parser looks exactly like a wrong model, so you spend two days tuning a prompt that was never the problem.
- Then list three holes and map each to what structured output fixes: wording drifts across versions so regexes never catch up; there is no confidence signal, so you cannot tell certainty from guessing; and an invented route name only explodes at runtime, whereas an enum is a gate that exists before the request is even sent.
- Explain the mechanism rather than stopping at zod is nicer: send the schema in the request (response_format with a json_schema), so decoding is constrained by the enum, then validate the response with the same declaration. One declaration used twice means request and validation cannot drift apart.
- The counterintuitive point that separates candidates: structured output does not remove the need to validate. Not every gateway or model enforces the schema strictly, and a fallback model may not at all. Your parse function should return something-to-be-validated, not an already-typed decision.
- Expect: what if the model does not support json_schema? Fall back to few-shot plus a strict prompt plus your own validation. The real gate was never the model's discipline; it is your parsing layer.
分析过程 · 先想清楚再作答
- 这题最容易答成「结构化更规范、更好解析」——这是形容词,不是理由。面试官想听的是一个具体的失败场景,最好是你真的调过的那种。
- 把最锋利的一刀先亮出来:**自然语言路由的失败是静默的**。模型回「我觉得这个可以让查订单的同事看一下」,它其实判对了,但说的是人话不是 id,正则匹配不上就落进兜底,日志里只留下一个 smalltalk。模型是对的、解析是错的,而它和「模型判错了」在日志里长得一模一样。你会去调提示词,调两天才发现问题在那三行正则。
- 然后给三个漏洞,一条一条对上结构化输出解决了什么:输出会漂移(今天回「订单查询」明天回「查订单」,正则永远追不上,模型小版本升级你就掉准确率);没有置信度(自然语言里没有「我有多大把握」这个信息,你没法区分它很确定还是在猜);拼错或自造的路由名要到运行时才炸(枚举是一道编译期就存在的闸门)。
- 接着说清机制,别停在「用 zod 更规范」:把 schema 发进请求(response_format 里的 json_schema),模型的解码过程被枚举约束;回来之后**用同一份声明再校验一遍**。一份声明两用,请求与校验不会漂移。
- 关键的反直觉点,答到这里就拉开差距了:**结构化输出不等于不用校验**。不是所有网关、所有模型都严格执行 schema,降级到备用模型时更说不准。所以解析函数的返回类型应该是「一段待校验的东西」,而不是「已经是 RouteDecision」。
- 可以预期的追问:那不支持 json_schema 的模型怎么办?答案是退回「few-shot 加严格提示词加自己校验」,闸门仍然在你的枚举校验那一步——真正兜底的从来不是模型的自觉,是你的解析层。
Key points
- Free-text routing fails silently: a correct judgment in prose misses your regex and falls through, looking identical to a wrong judgment in the logs
- Three holes: wording drifts, there is no confidence signal, and invented route names only fail at runtime
- One declaration used twice: the schema constrains decoding in the request and validates the response, so the two cannot drift
- An enum is a gate that exists before the call, turning a misspelled route from an incident into a parse failure
- Structured output does not remove validation — gateways and fallback models may not enforce the schema, so parsing must return an unvalidated value
答题要点
- 自然语言路由的失败是静默的:模型判对了但说的是人话,正则匹配不上就落兜底,和判错在日志里完全一样
- 三个漏洞:措辞会漂移(正则追不上)、没有置信度(分不清确定与猜)、自造的路由名要到运行时才炸
- 机制是一份声明两用:schema 随请求发出去约束解码,回来后用同一份声明校验,请求与校验不会漂移
- 枚举是编译期就存在的闸门,把「拼错的路由名」从线上事故降级成一次解析失败
- 结构化输出不等于不用校验:网关和降级模型未必严格执行 schema,解析函数的返回类型应该是「待校验」而不是「已经是」
How should the system handle an uncertain or wrong routing decision, and how do you pick the threshold?路由不确定或者路由错误时,系统应该怎么兜底?阈值该怎么定?
Common in ChinaCommon overseasDeep dive#routing#fallback#reliabilityHow to reason about it · think before answering
- The hinge is that uncertain and wrong are two different failures. Most candidates answer retry or escalate to a human, collapsing both into one. The discriminator is stating a value judgment before giving a policy.
- The claim first: routing to the wrong sub-agent is far worse than failing to route. A failure announces itself and lets you ask a clarifying question. A wrong route does not — the receiving agent has no idea it got the wrong job and will produce a confident, well-formatted, wrong answer that the user will act on. A confident wrong answer costs a hundred times more than I did not catch that.
- Then give a concrete policy with real numbers: if the model's confidence is below 0.6, or the route name is not in the allowed list, fall back to the small-talk agent and stamp the reason with a fallback prefix plus a cause code (low confidence, unknown route, invalid shape). The fallback agent's job is to ask for the one missing detail rather than guess — falling back means handing the uncertainty back to the user.
- The threshold question is the real test, so do not recite a number: it depends on which error is more expensive. In customer support one extra question costs mild annoyance while a misroute can become a wrong refund promise, so stay conservative. For an internal tool the extra question is the bigger cost, so lower it. Then give a method: sweep thresholds over a golden set, plot misroute rate against clarification rate, and pick the knee.
- Name the trap: the confidence number is self-reported and is not a probability. Nine tenths does not mean nine in ten are right. It is a usable ranking signal within one model and one prompt — good as a gate, useless for expected-value math. Real accuracy comes from offline evaluation.
- Expect: does falling back just hide the problem? Not if you record cause codes. Group a week of fallbacks by cause and you can see exactly which intent the routing prompt fails to describe. The fallback stops the bleeding; the cause code is what fixes it.
分析过程 · 先想清楚再作答
- 题眼在「不确定」和「错误」是两件事。多数人只答重试或人工接管,那是把两个问题揉成一个。区分度在于你能不能先给出一条价值判断,再给策略。
- 先立论:**路由到错的子 Agent,比路由失败糟糕得多**。失败你至少知道自己失败了,可以追问一句;错了,接手的子 Agent 完全不知道自己接错了活,会用笃定的语气给出一个格式完整的错误答案,用户不会怀疑,会照着去操作。一个自信的错误答案比一句「我没听清」贵一百倍。
- 再给可执行的策略,数字要具体:模型给的置信度低于 0.6,或者路由名不在合法名单里,一律落到兜底的 smalltalk,并在 routingReason 里打上 fallback 前缀加原因码(低置信度、未知路由、结构非法各一种)。兜底那位的人设是「信息不足先追问一句缺的关键信息,不要猜」——兜底的本质是把不确定性还给用户。
- 阈值怎么定这一问是重点,别背数字:**取决于两类错误哪一类更贵**。客服场景里多问一句只是用户小小的不耐烦,派错可能变成一条错误的退款承诺,所以宁可保守取 0.6;内部工具型 Agent 里多问一句反而更烦人,阈值就该放低。再补一句可落地的定法:拿标准样本集扫一遍,画出不同阈值下的误派率与追问率,选拐点。
- 必须点破的一个坑:**置信度是模型自己报的,它不是概率**。模型说 0.9 不代表有九成对。它只是同一模型、同一提示词下相对可用的排序信号,只能当闸门用,不能拿去算期望值。真正的准确率要靠离线评估去量。
- 可以预期的追问:兜底会不会把问题掩盖掉?答案是不会,前提是你记了原因码——把一周内落进兜底的请求按原因分组,能直接看出分诊提示词缺了哪一类描述。兜底是止血,原因码才是治本的输入。
Key points
- Separate the two: a failed route can ask a clarifying question, a wrong route produces a confident wrong answer, and the second is far costlier
- Policy: confidence below 0.6 or a route outside the allowed list falls back to small talk, stamped with a fallback prefix and a cause code
- Falling back is not picking someone at random — the fallback agent asks for the missing detail instead of guessing
- The threshold depends on which error costs more; sweep it over a golden set and pick the knee between misroutes and clarifications
- Self-reported confidence is not a probability — use it as a gate only, and measure real accuracy offline
- Record cause codes on every fallback; grouping them shows which intent the routing prompt fails to describe
答题要点
- 先分清两件事:路由失败可以追问,路由错误会让子 Agent 自信地给出错误答案,后者贵得多
- 策略:置信度低于 0.6 或路由名不在名单里,一律落兜底的 smalltalk,并在 routingReason 打上 fallback 前缀加原因码
- 兜底不是随便找个人接,而是把不确定性还给用户——兜底那位应当追问缺失的关键信息而不是猜
- 阈值取决于两类错误哪一类更贵:客服场景多问一句便宜、派错很贵,所以保守;定法是拿标准样本集扫阈值找拐点
- 置信度是模型自报的,不是概率,只能当闸门用;真正的准确率要靠离线评估量
- 落兜底时记原因码,按原因分组就能看出分诊提示词缺了哪一类描述
What is a field like routingReason actually worth in production? Is it just logging?routingReason 这类调试信息在生产系统里有什么价值?只是打日志而已吗?
Common in ChinaCommon overseasIntermediate#observability#routing#debuggingHow to reason about it · think before answering
- This looks like a throwaway question but it screens for whether you have ever been on call. Anyone who stops at it helps with debugging has not.
- Start with the fact you cannot design around: the routing decision is made by a model, and models are not reproducible. The same sentence may be judged differently next time, so you cannot re-run to see what it was thinking. The reason must be captured at decision time or it is gone forever — that is what turns this field from a log line into the only audit evidence you have.
- Then give three concrete uses. One, it separates a wrong model judgment from a parsing or fallback problem, provided the prefix carries a cause code. Two, it is raw material for the next prompt revision: group a week of fallbacks by cause and the missing intent descriptions jump out. Three, it feeds offline evaluation — a golden set should score routing accuracy, not just the final answer, and that is only scorable if the decision and its reason were recorded.
- Mention the shape: a structured prefix wrapping a human sentence. The prefix (fallback plus cause, override plus target) is what you aggregate on; the sentence is what you read for one specific case. Making the whole field prose puts you right back in the failure mode this chapter argues against.
- Add the detail people skip: neither a fallback nor a human override should erase the model's original judgment — carry it into the reason. Otherwise nobody can later tell whether the model got it wrong or a human redirected it. Twenty extra characters save an afternoon of archaeology.
- Expect: do these fields create privacy or cost problems? Yes, so record the basis for the decision rather than the user's raw text, cap the length, and reuse the same run identifier as your tracing instead of inventing a parallel one.
分析过程 · 先想清楚再作答
- 这题看着像水题,其实在筛「有没有真的排查过线上问题」。答「方便调试」就结束的人,基本没值过班。
- 先给一条不可回避的事实:**路由决策是模型做的,而模型不可复现**。同一句话下次未必给同样的判断,你没法重跑一遍去看「当时是怎么想的」。所以理由必须在当时就写下来,否则那次判断永远丢了。这一条把 routingReason 从「日志」抬到了「唯一的审计证据」。
- 然后给三个具体用途,每个都要能落地:一是把「模型判错了」和「解析或兜底出错了」分开,前缀写成 fallback 加原因码,一眼就能分辨;二是攒下一版提示词的素材,把一周内落进兜底的请求按原因分组,会看到集中的几类意图缺描述;三是它是离线评估的输入——标准样本集要评的不只是最终回答,还有分诊准不准,而这件事只有当时记了判断和理由才评得了。
- 写法上有个细节值得主动说:**结构化的壳加自然语言的芯**。前缀(fallback 加原因、override 加目标)用来聚合统计,后面那句人话用来看具体这一单。整条都写成自然语言,就退回成本章批判的那种东西了。
- 再补一条容易被忽略的:兜底和人工改派都不要擦掉模型的原判,原样拼进理由里。否则一周后没人说得清这一单是模型判错了还是本来就被人改过——多写二十个字符,省掉一次翻遍代码的排查。
- 可以预期的追问:这些字段会不会带来隐私或成本问题?答案是会,所以理由里只写判断依据不写用户原文,长度设上限(比如 120 字),并且和链路追踪共用同一个 run 标识,别另起一套。
Key points
- The decision comes from a model and is not reproducible, so the reason must be captured at decision time — it is the only audit evidence you get
- Use one: it separates a wrong model judgment from a parsing or fallback failure, via a cause code in the prefix
- Use two: grouping a week of fallbacks by cause tells you exactly what the next routing prompt is missing
- Use three: it feeds offline evaluation, since routing accuracy can only be scored if the decision and reason were recorded
- Shape it as a structured prefix around a human sentence: aggregate on the prefix, read the sentence for one case
- Keep the model's original judgment through fallbacks and overrides; store the basis rather than raw user text, cap the length, and reuse the tracing run id
答题要点
- 路由决策由模型做出且不可复现,理由必须在当时写下来,否则那次判断永远丢了——它是唯一的审计证据
- 用途一:把「模型判错」和「解析或兜底出错」分开,靠 fallback 加原因码一眼分辨
- 用途二:把一周内落进兜底的请求按原因分组,直接得到下一版分诊提示词该补什么
- 用途三:它是离线评估的输入,分诊准确率这个指标只有记了当时的判断与理由才评得了
- 写法是结构化的壳加自然语言的芯:前缀用于聚合统计,人话用于看具体这一单
- 兜底与人工改派都要保留模型原判;理由只写判断依据不写用户原文,长度设上限,并复用链路追踪的 run 标识
D17 Planner-Executor-Critic Plus a Shared Workspace: Workspace State, toolBudget, Parallel Fan-Out, a Review Loop
What problem does the Planner-Executor-Critic structure solve, and how is it different from Supervisor routing?Planner-Executor-Critic 这种结构解决了什么问题?它和 Supervisor 路由的区别在哪?
Common in ChinaCommon overseasBasic#multi-agent#orchestration#architectureHow to reason about it · think before answering
- The hinge is the second half. Reciting plan, execute, review is naming shapes from memory; the interviewer wants to see you separate the two patterns by graph shape.
- Separate by shape: a Supervisor is a fork — at runtime it picks one of several paths and hands the work to exactly one agent, so the graph only branches. Planner-Executor-Critic fans out, joins, and adds a back edge. Branching answers who takes this, fan-out answers this must be split into several pieces, the back edge answers who signs it off.
- Then give the criteria: use a Supervisor when only one specialist is needed per request and the hard part is picking them; only fan out when a request genuinely splits into independent pieces with no ordering between them; only add a Critic when correctness has an explicit rubric and redoing is cheaper than shipping something wrong. If none of these hold, do not build this.
- Land on cost, which is where shipped-it separates from read-the-docs: three subtasks turn one model call into seven (one plan, three executions, three reviews) and nine after a single rejection round; latency is set by the slowest branch rather than the average, and parallelism buys latency, never money.
- Expect: does the Critic have to be its own node? Not necessarily — if the rubric is checkable in code (schema validation, required fields), check it in code: faster, cheaper, and more reliable. A Critic earns a model call only when the rubric requires understanding meaning.
分析过程 · 先想清楚再作答
- 题眼在后半句。只答「拆解、执行、评审」是在背名词,面试官想确认的是你能不能用图的形状把两种模式分开,而不是靠记忆背模式表。
- 先用形状拆:Supervisor 是一个岔路口,运行时在几条路里选一条走,一次只交给一个人,图上只有分叉;Planner-Executor-Critic 是先扇出、再汇合、中间还有一条回边。分叉解决「交给谁」,扇出解决「一件事要拆成几件」,回边解决「谁来验收」。
- 再给适用判据:一次只需要一个专家、难点在判断该找谁,用 Supervisor;一件事必须拆成几件且几件之间没有先后依赖,才值得扇出;产出的对错有明确判据、且错了重做比错了发出去便宜,才值得加 Critic。三条判据都不命中就别上这套结构。
- 结论要落到代价,这是区分「读过文档」和「上线过」的地方:拆出三件事意味着模型调用次数从一次变成七次起步(拆解一次、三次执行、三次评审),有一轮打回就是九次;延迟被最慢的那件事决定而不是平均值,而且并行只省延迟不省钱。
- 可以预期的追问:Critic 一定要单独一个节点吗?答案是不一定——如果验收判据是可以用代码判的(比如 JSON schema 校验、必填字段检查),就别花一次模型调用,代码判更快更准也更便宜。只有判据本身需要理解语义时,Critic 才值得是一次模型调用。
Key points
- A Supervisor branches (one agent per request); Planner-Executor-Critic fans out, joins, and loops back (split, run in parallel, then sign off)
- Three criteria: route when one specialist suffices; fan out only for genuinely independent pieces; add review only when the rubric is explicit and redoing beats shipping wrong
- The cost is seven to nine model calls instead of one, with latency set by the slowest branch — parallelism buys latency, not money
- If the rubric is checkable in code, check it in code; a Critic deserves a model call only when semantics must be understood
答题要点
- Supervisor 是分叉(一次派一个人),Planner-Executor-Critic 是扇出加汇合加回边(拆成几件并行做,做完有人验收)
- 三条适用判据:一次只需一个专家用路由;能拆成互不依赖的几件才扇出;对错有明确判据且重做便宜才加评审
- 拆解的代价是模型调用从一次涨到七到九次、延迟由最慢的分支决定,而并行只省延迟不省成本
- 评审判据能用代码判就别用模型判,Critic 只在需要理解语义时才值一次模型调用
When several subtasks run in parallel and all write the same shared state, how do you design it so they do not clobber each other?多个子任务并行执行、都要写同一份共享状态时,怎么设计才不会互相覆盖?
Common in ChinaCommon overseasDeep dive#multi-agent#state-management#concurrencyHow to reason about it · think before answering
- This one separates people fast, because most candidates answer locks or immutable data structures — instincts carried over from threads. A graph runtime has no concurrent memory writes at all: updates are collected and merged. Answering in the wrong frame is worse than answering incompletely.
- Get the mechanism right first: parallel nodes each return a delta, the runtime groups all deltas from the same step by field, then calls that field's reducer to compute the new value. So the question is not how to lock, it is whether that field's reducer is correct.
- Then give a reusable chain: how many writers touch this field in one step, and do they write the same record? One writer — last-write-wins is fine. Several writers on different records — appending to a list is fine. Several writers on the same record — upsert by key. Several writers on different fields of the same record — merge per field. Four cases, four reducers, and the chain transfers to any framework.
- Land on the common mistake: implementing update this record as append a new version with the same id. The symptom is not an error — the same id exists twice and which one comes first depends on who finished first, so any lookup by id may return the stale version. Clean logs, occasionally wrong results.
- Add the trade-off: you can leave the reducer alone and dedupe by id at every read instead. But there are three or four read sites, and missing one is an intermittent stale read; a reducer is written once and every read is clean afterwards. Solve it once on the field, or N times at the read sites.
- Expect: does nondeterministic ordering matter? Ideally the reducer is order-insensitive (commutative); if it is not, you must guarantee one writer per record. Upsert-by-id is the latter — it is last-write-wins and is safe only because each record has exactly one executor per round.
分析过程 · 先想清楚再作答
- 这题的区分度极高,因为大多数人会答成「加锁」或者「用不可变数据结构」——都是从多线程经验迁移过来的答案,但图的执行模型里根本没有并发写内存这回事,写入是被收集起来统一合并的。答错方向比答不全更致命。
- 先把机制说对:并行节点各自返回一份增量,框架把同一轮里所有增量按字段收集,再逐字段调用这个字段的合并规则(reducer)算出新值。所以问题不是「怎么加锁」,而是**这个字段的合并规则写得对不对**。
- 然后给一条可复用的判断链:先问这个字段同一轮会被几个人写;再问他们写的是不是同一条记录。只有一个写者,默认的后写覆盖就够;多个写者写不同记录,数组追加就够;多个写者写同一条记录的同一份数据,要按主键原地更新;多个写者写同一条记录的不同字段,要做字段级合并。四种情况四种 reducer,这条链能直接迁移到任何框架。
- 结论落在最容易踩的那一格:把「更新一条记录」写成「往数组里追加一条同 id 的新版本」。它的症状不是报错,是同一个 id 在状态里有两份、而且哪份在前取决于谁先跑完——下游任何按 id 查的地方都可能拿到过期版本,日志干净、结果偶尔错。
- 补一句权衡:也可以不动 reducer,改成每处读状态前先按 id 去重。但读取点有三四处,漏一处就是一个偶发脏读;reducer 只写一次,之后所有读取点自动干净。在字段上解决一次,还是在每个读取点解决 N 次,这是同一个问题的两种成本。
- 可以预期的追问:那顺序不确定要不要紧?答:合并规则最好对顺序不敏感(可交换),做不到就必须保证每条记录只有一个写者。本课的按 id 原地更新属于后者——它是最后写入者获胜,靠「一轮里一条记录只有一个执行者」这个前提才安全。
Key points
- A graph runtime has no concurrent memory writes: nodes return deltas, the runtime groups them per field and calls that field's reducer — so the answer is a correct reducer, not a lock
- Decision chain: how many writers per step, and same record or not — overwrite, append, upsert by key, or per-field merge
- The classic bug is implementing update as append-a-new-version-with-the-same-id: two entries per id, order depends on who finished first, lookups return stale data, and nothing ever errors
- The alternative is deduping at every read site, but there are several and missing one gives an intermittent stale read; a reducer is written once
- Prefer an order-insensitive reducer; if it is not, guarantee exactly one writer per record per step
答题要点
- 图的执行模型里没有并发写内存:节点各返回增量,框架按字段收集后调用该字段的 reducer 合并,所以问题是 reducer 写得对不对,不是加不加锁
- 判断链:同一轮几个写者、写的是不是同一条记录——单写者用覆盖、多写者写不同记录用追加、多写者写同一条记录用按主键原地更新、写同一条记录的不同字段要字段级合并
- 最常见的错是把「更新」写成「追加同 id 的新版本」,症状是同 id 两份、顺序取决于谁先跑完、按 id 查会拿到过期版本,而且全程不报错
- 另一条路是每处读取前手动去重,但读取点有好几处,漏一处就是偶发脏读;reducer 只写一次就一劳永逸
- 合并规则最好对顺序不敏感;做不到就必须保证一轮里一条记录只有一个写者
Why give each subtask a tool-call budget, and what do you do when it runs out?为什么要给每个子任务设 toolBudget 这样的预算?超了预算之后你会怎么处理?
Common in ChinaCommon overseasIntermediate#cost-control#reliability#agent-designHow to reason about it · think before answering
- The hinge is the second half. Everyone can say it controls cost; what separates people is what happens when the budget runs out. Answering throw an exception usually means you have never shipped a user-facing agent.
- Make the why concrete: a stuck subtask rarely errors — it queries, dislikes the result, and queries again. The model never gets tired; it will spend whatever you allow. A per-conversation cap is the outer gate, a per-subtask budget is the inner one, and the finer grain tells you which piece went out of control instead of only that the conversation was expensive.
- Add the design point people miss: the budget must be per subtask, not per execution. With a review loop, retries have to draw on the same budget, or two rejections triple the real allowance and the gate is meaningless.
- The conclusion is the exhaustion path: degrade — return what you already have with a flag — rather than throw. Explain why: throwing upgrades this piece is half done into the whole request failed. The user waited several seconds and gets an error page, when in reality only one of three pieces is missing. Two and a half answers plus a clear note beats an error page every time.
- Say something about the flag too: it turns degradation into an observable, countable fact instead of a log line. The layer above decides whether to escalate to a human, and monitoring plots a degradation rate — two systems with the same average score but 30 percent versus 3 percent degradation are not the same system.
- Expect: how big should the budget be? Derive it from how many tool calls the task normally needs plus margin, not a round number pulled from the air. And pair it with a second dimension — wall-clock or tokens — because one very slow tool call can ruin a request while counting as a single call.
分析过程 · 先想清楚再作答
- 这题的题眼在后半句。前半句几乎人人会答「防止成本失控」,真正拉开差距的是超限之后的动作——答「抛异常」的人基本没做过面向用户的 Agent。
- 先把「为什么」说具体。子任务卡住的典型形态不是报错,而是反复查、反复不满意、再查——模型不会喊累,它会把额度花光为止。整轮对话的成本封顶是外层的闸,子任务预算是内层的闸;粒度细到单件事的好处是超支时你能精确指出是哪一件失控了,而不是只看到这次对话贵了。
- 再点一个容易被忽略的设计点:预算必须是子任务级的,不是单次执行级的。有评审回路时,被打回重做也得计费,否则打回两次实际额度就翻三倍,这道闸等于没设。
- 结论是超限的处理:降级返回已有结果并打上标记,不抛错。理由要说透——抛错等于把「这件事只做了一半」升级成「整个请求失败」,用户等了几秒最后看到一句服务异常,可他其实只是没拿到三件事里的一件。给出两件半的答案并说明哪半件没做成,永远比一个错误页有用。
- 降级标记本身也要说:它让降级变成可观测、可统计的事实,而不是日志里的一句话。上层据此决定要不要转人工,监控据此画降级率——两个平均分一样的系统,降级率百分之三十和百分之三完全不是一回事。
- 可以预期的追问:预算该设多少?答案是从「这件事正常需要几次工具调用」反推再留一点余量,不是拍脑袋取整数;同时要有第二个维度的闸(挂钟时间或 token 数),因为一次超长的工具调用同样能拖垮请求,而它只算一次。
Key points
- A stuck subtask loops rather than errors, and the model will spend whatever you allow; a conversation cap is the outer gate, a subtask budget the inner one that localizes the blowup
- The budget must be per subtask, not per execution, or two review rejections triple the real allowance
- On exhaustion, degrade and flag rather than throw — throwing upgrades half done into whole request failed and discards what was already retrieved
- The degradation flag makes the degradation rate a real metric for escalation and evaluation
- Size the budget from the task's normal tool-call count plus margin, and pair it with a wall-clock or token gate
答题要点
- 子任务卡住的典型形态是反复查而不是报错,模型会把额度花光为止;整轮封顶是外层闸,子任务预算是内层闸,细粒度让你能定位到是哪一件失控
- 预算必须是子任务级而不是单次执行级,否则被评审打回两次实际额度就翻三倍
- 超限必须降级返回已有结果并标记,不能抛错——抛错把「做了一半」升级成「整个请求失败」,用户连已经查到的部分都拿不到
- 降级标记让降级率变成可统计指标,上层据此决定转人工,评估据此区分两个平均分相同的系统
- 预算大小从这件事正常需要几次工具调用反推并留余量,同时配一个时间或 token 维度的闸
How do you keep a Critic review loop from spinning forever, and what else needs guarding besides a retry cap?Critic 的评审回路怎么防止陷入死循环?除了次数上限还有什么要防的?
Common in ChinaCommon overseasIntermediate#reflection#loop-guard#reliabilityHow to reason about it · think before answering
- Asking what else besides a cap tells you the interviewer already expects the cap. What is really being tested is whether you have run this loop for real. The cap earns baseline credit; naming the other two failure modes is what passes.
- Failure one is infinite rejection: every revision draws a new complaint and nothing converges. The cap exists to guarantee termination, not to save money. Two rejections and three executions is a reasonable default, because an effective fix usually lands on the second attempt — if the third still fails, the rubric itself is the problem.
- Failure two is a rejection with no actionable content. If the reviewer only says not good enough, the executor has nothing to act on and resubmits the same thing, burning the full cap. Rejections must carry a specific reason, and that reason must be written back into the subtask goal. Missing the refund conclusion, please add it is actionable; poor quality is not.
- Failure three is the dangerous one people rarely mention: when reviewer and executor share a model and a prompt, the reviewer tends to approve its own output. A single model has consistent preferences about what a good answer looks like, so pass rates go implausibly high and the review step becomes theater. Mitigations by value: give the reviewer an objective, checkable rubric; use a different model even a cheaper one; score item by item rather than emitting one verdict.
- Also distinguish the framework's safety net from your business cap: orchestration frameworks usually ship a recursion limit, but that is a last-resort fuse — it is graph-wide so you cannot tell which loop ran away, and it throws, which means you lose the partial results you were supposed to degrade to.
- Expect: what do you return once the cap is used up? Return what you have, flag it as degraded, and carry the last review comment out with it so the layer above can decide whether to escalate. The loop's value is not only fixing things — it is stating precisely what could not be fixed.
分析过程 · 先想清楚再作答
- 问「除了次数上限还有什么」,说明面试官已经预设你会答上限,真正在考的是你有没有真的跑过这条回路。只答上限的人拿基础分,能说出另外两种失效方式的才算过。
- 第一种就是无限打回:每改一版评审者挑一个新毛病,永远收敛不了。上限的作用不是省钱,是**保证流程一定会结束**。本课取最多打回 2 次、共 3 次执行,这个量级的取法是「一次有效的修改通常在第二次就完成,第三次还不行说明判据本身有问题」。
- 第二种是打回不说人话:评审者只回一句「不合格」,执行者拿不到可执行信息,第二稿原样再交一遍,于是必然打满上限、白烧三倍的钱。所以打回必须带具体理由,而且理由要回写进子任务的目标里带给执行者——「缺了退款结论,请补上」才是可执行的,「质量不佳」不是。
- 第三种最危险也最少被提到:评审者和执行者用同一个模型、同一套提示词时,它倾向于认可自己的输出。同一个模型对「什么算好答案」的偏好是一致的,让它复核自己刚写的东西,通过率会高得离谱,这道工序等于没有。缓解手段按性价比排:给评审者一份可核对的客观验收要求;换一个不同的模型来评审,哪怕更便宜;把评审做成逐条打分而不是一句结论。
- 还要点一句框架的兜底与业务上限的区别:编排框架通常自带一个递归步数上限,但那是最后一道保险丝,不能当业务上限用——它是全图的,你不知道是哪条回路失控;而且它触发时抛异常,你连已有结果都拿不到,正好违背「降级返回」的原则。
- 可以预期的追问:上限用完了返回什么?答:返回已有结果并标记降级,同时把最后一次的评审意见一起带出去,让上层能判断要不要转人工——这条回路的价值不只是修好,还包括「修不好时说清楚差在哪」。
Key points
- A retry cap exists to guarantee termination, not to save money — two rejections, three executions total
- Rejections must carry specific, actionable reasons written back into the subtask goal; not good enough guarantees an identical resubmission and a maxed-out cap
- The most dangerous failure is a reviewer sharing model and prompt with the executor: it approves its own output, pass rates inflate, and the step becomes theater
- Mitigate with an objective checkable rubric, a different model for review, and item-by-item scoring instead of a single verdict
- The framework's recursion limit is a fuse, not a business cap: it is graph-wide and it throws, so you lose the partial results you meant to degrade to
- When the cap is spent, return what you have with a degraded flag plus the last review comment so the layer above can escalate
答题要点
- 次数上限的作用是保证流程一定会结束,不是省钱;本课取最多打回 2 次、共 3 次执行
- 打回必须带具体、可执行的理由并回写进子任务目标,只说「不合格」会让执行者原样重交、必然打满上限
- 最危险的是评审者与执行者同模型同提示词,它倾向于认可自己的输出,通过率虚高、这道工序等于没有
- 缓解手段:给客观可核对的验收要求、换一个模型来评审、逐条打分而不是一句结论
- 框架自带的递归上限只是保险丝,不能当业务上限:它是全图的、触发时抛异常,连已有结果都拿不到
- 上限用完要返回已有结果加降级标记,并把最后一次评审意见带出去,供上层决定是否转人工
D18 History Fidelity and Summarization, Multimodal Placeholders, Checkpointer Persistence
How should agent memory be layered? What belongs in short-term context, in summaries, and in long-term memory — and what happens when each is lost?记忆应该怎么分层?短期上下文、摘要、长期记忆分别放什么、丢了会怎么样?
Common in ChinaCommon overseasBasic#memory#context-management#multi-agentHow to reason about it · think before answering
- The discriminator here is not listing three layers, it is saying what breaks when each one is lost. An answer that only names the layers tells the interviewer you have never operated one.
- Offer a reusable split first: sort any memory scheme by who reads it, how long it lives, and whether it can be rebuilt after loss. Those three questions cut through every design.
- Short-term context is the message array sent to the model this turn. It dies with the request and is billed in full every turn. Losing it only costs coherence for that turn, because the raw transcript still lives in your own store and can be replayed.
- A summary is derived from short-term context, produced to shrink early turns before the window fills. It can be regenerated after loss — but only if the raw transcript was stored separately. That is the practical reason a summary must never overwrite the original.
- Long-term memory holds cross-session user facts and preferences. It never enters the message array; it lives in a retrieval layer and a few hits get injected on demand. Losing it means the system forgot the user — single requests still work, but the product gets noticeably worse.
- Multi-agent adds a fourth layer people usually miss: graph execution state — messages, shared workspace, review rounds, degraded flags. It is the only copy that gets checkpointed and replayed on resume, and losing it is the most expensive failure: a run that already burned nine model calls starts over while the user watches a spinner.
- Expect the follow-up: should the summary live inside the message array or in its own field? Say its own field — keeping raw and derived data apart is what lets you regenerate with a different strategy later; merged together you can no longer tell what actually happened from what was written after the fact.
分析过程 · 先想清楚再作答
- 这题的区分度不在能不能列出三层,而在能不能说出**每一层丢了会怎样**。只报名词的答案,面试官听不出你有没有真的运维过。
- 先给一条可复用的拆法:按「谁在读它、活多久、丢了能不能补」三个问题去分,任何一个记忆方案都能被这三问切开。
- 短期上下文是这一次请求要发给模型的那个消息数组,随请求结束作废,全量进 token 账单;它丢了只影响这一轮的连贯性,原文还在你自己的会话记录里,可以重放。
- 摘要是短期上下文的派生数据,用来在窗口顶到之前把早期内容压短;它丢了可以重新生成——**前提是原文另存了一份**。所以摘要绝不能覆盖原文,这是「压缩不可逆」那条纪律的实际落点。
- 长期记忆是跨会话的用户事实与偏好,不进消息数组,存在外部检索层里按需捞几条注入;它丢了的表现是「这个用户被系统忘光了」,不影响单次可用,但产品价值直接掉一层。
- 多 Agent 还要补第四层,也是最容易被忽略的一层:**图的执行状态**。它包含消息、共享工作区、评审轮次、降级标记,是唯一一份会被检查点持久化并在恢复时重放的数据。它丢了的后果最重——一次已经花掉九次模型调用的执行必须从头再来,而且用户界面还停在转圈。
- 可以预期的追问:摘要该放在消息数组里还是单独一个字段?答单独字段,理由是原文与派生数据要分开存,才可能换一种策略重新生成;混在一起之后你分不清哪条是真发生过的、哪条是事后编的。
Key points
- Layer by who reads it, how long it lives, and whether it can be rebuilt — that beats reciting names
- Short-term context: this turn's message array, discarded after the request, billed in full, replayable from your own transcript
- Summary: derived from short-term context and regenerable, but only if the raw transcript is stored separately — so it must never overwrite the original
- Long-term memory: cross-session user facts in a retrieval layer, injected on demand; losing it means the system forgot the user
- Multi-agent adds graph execution state — messages, workspace, review rounds, degraded flags — checkpointed and replayed on resume, and the most expensive to lose
- Keep the summary in its own field rather than back in the message array, so raw and derived data stay separable
答题要点
- 按「谁在读、活多久、丢了能不能补」三问分层,比背名词有用
- 短期上下文:本轮请求的消息数组,随请求作废,全量计费,丢了可从原始记录重放
- 摘要:短期上下文的派生数据,可重新生成,前提是原文另存——所以摘要不能覆盖原文
- 长期记忆:跨会话的用户事实,存在检索层按需注入,丢了是「系统忘了这个人」
- 多 Agent 多一层图执行状态:消息 + 工作区 + 评审轮次 + 降级标记,会被检查点持久化并在恢复时重放,丢了最贵
- 摘要放独立字段而不是塞回消息数组,原文与派生数据分开存才可能换策略重生成
When summarizing a long conversation, how do you keep the critical information from being lost — and what is different about this in a multi-agent system?长对话做摘要时,怎么保证关键信息不丢?在多 Agent 场景下这件事有什么特别的?
Common in ChinaCommon overseasIntermediate#context-compression#multi-agent#reliabilityHow to reason about it · think before answering
- The hinge is the second half. Answering only keep user constraints and the last few turns is the standard single-agent answer — passable, not memorable. Asking what is different in multi-agent is asking whether you have actually hit this in a collaboration graph.
- Get the single-agent half solid first: trigger on thresholds, never a timer. This course uses more than 20 messages or an estimated 8000 tokens, counting one character as one token — deliberately high, because underestimating means the threshold never fires. Keep the last 6 messages verbatim. Align the cut to a turn boundary: cutting between a tool call and its result produces a dangling message and most vendors return 400.
- Then name the real difference: in a single agent a summary loses detail; in a multi-agent graph a summary loses the criteria. A critic decides whether output passes by telling apart what is being reviewed from what the requirement was. A smooth narrative summary that flattens speakers reads fine and is useless to the critic.
- So multi-agent summarization has one extra hard requirement: every compressed message must leave behind two coordinates — its index and its speaker. The implementation is one line: build the transcript with numbered, role-prefixed entries before handing it to the model.
- Add the boundary that shows you have shipped this: summarize natural-language history only, never structured fields. Compressing the shared workspace into a sentence kills every lookup by task id and every comparison against an acceptance requirement, and structured data does not come back. Attachments are even more off-limits — they hold a reference, not content, so summarizing one orphans the underlying object.
- Expect the follow-up: which model writes the summary, and what if it fails? A cheaper small model is fine since the job is condensation, not reasoning. On failure the correct behavior is to skip this round of compression, keep running, and alert — not to fail the whole execution. Setting the threshold at seventy or eighty percent exists precisely to leave that rescue room.
分析过程 · 先想清楚再作答
- 题眼在后半句。只答「保留用户约束、保留最近几轮」是单 Agent 的标准答案,能过但不出彩;面试官问「多 Agent 有什么特别的」,是在看你有没有真的在协作图里踩过这个坑。
- 先把单 Agent 那半答扎实:触发用阈值不用定时器,本课口径是消息超过 20 条或估算超过 8000 token(一个字符算一个 token,故意高估,低估会让阈值永远触发不了);保留最近 6 条原文不动;切口必须对齐到一轮的开头,切在工具调用与工具结果之间会让下一次请求出现悬空消息,多数厂商直接返回 400。
- 然后给出多 Agent 那半的关键差别:单 Agent 里摘要丢的是**细节**,多 Agent 里摘要丢的是**判据**。评审者判一份产出合不合格,靠的是分清「这句是待验收的产出、那句是验收要求」;一段把发言人抹平的流水摘要读起来通顺,但评审拿它做不了任何判断。
- 所以多 Agent 的摘要有一条额外硬要求:每条被压掉的消息,在摘要里都要留下「第几条 + 谁说的」这两个坐标。实现只有一行——把转录写成带序号和角色前缀的形式再交给模型。
- 再补一条边界,这条最能显出你写过:**摘要只对自然语言历史动手,不碰任何结构化字段**。把共享工作区压成一句话,「按 id 找到某条子任务、比对验收要求」就整个失效了,结构化数据压成自然语言就再也回不去。附件字段更是碰不得——它存的是引用不是内容,摘要掉等于把那个对象变成孤儿。
- 可以预期的追问:摘要用哪个模型、失败了怎么办?答可以用更便宜的小模型(它只做归纳不做推理),失败时的正确行为是**跳过这一轮压缩继续跑**并告警,而不是让整次执行失败——阈值定在七八成就是为了留出这次抢救余量。
Key points
- Trigger on thresholds, not timers: more than 20 messages or an estimated 8000 tokens, counting one character as one token to stay conservative
- Keep the last 6 messages verbatim and align the cut to a turn boundary, or you ship a dangling tool call and the request 400s
- The multi-agent difference: a summary loses criteria, not just detail — the critic needs to know who said what and at which step
- So every compressed message keeps its index and speaker in the summary; the implementation is a numbered, role-prefixed transcript
- Summarize natural-language history only — never the shared workspace or other structured fields, and never the attachment references
- A cheaper small model is fine for summarizing; if the call fails, skip compression for this round and alert rather than failing the run
答题要点
- 触发用阈值不用定时器:超过 20 条或估算超过 8000 token,token 按一字符一 token 保守高估
- 保留最近 6 条原文不动,切口必须对齐到一轮开头,否则会出现有调用没结果的悬空消息、请求直接 400
- 多 Agent 的差别:摘要丢的不是细节而是判据,评审者靠「谁在第几步说的」区分产出与验收要求
- 所以每条被压掉的消息都要在摘要里留下条号与发言人,实现就是把转录写成带序号和角色的形式
- 只压自然语言历史,不碰共享工作区这类结构化字段,更不能碰存引用的附件字段
- 摘要可用更便宜的小模型;摘要调用失败时跳过这一轮压缩并告警,不要让整次执行失败
What do you need to watch out for when replaying execution from a checkpoint? Give failure modes you would actually hit.从 checkpoint 恢复执行(replay)需要注意什么?说几个真实会踩的坑。
Common in ChinaCommon overseasDeep dive#checkpointing#replay#reliabilityHow to reason about it · think before answering
- The easy failure is answering just load it and keep going. The discriminator is recognizing that almost every replay bug is silent — no exception, clean logs, plausible output, and you only notice when you diff the data. Saying that up front wins half the question.
- Give a chain first: a checkpoint stores the state shape as the code of that moment understood it, and replay pushes it back into today's code. So every failure comes from a mismatch across those two ends — the shape of the data, the entry point of execution, and things that should never have been replayed at all.
- Trap one: feeding the input again on resume. Resume takes no input; the state is already in the checkpoint. Passing the original message once more makes the framework treat it as a fresh update stacked on the interrupt point, and the history quietly doubles. Nothing throws.
- Trap two: forking without a checkpoint id. With only the thread id you get that thread's latest state, so start over from step 2 silently becomes append after the last step. Again nothing throws; you only see it by diffing the task list.
- Trap three: version drift. Rename a field or add a required one and every old checkpoint stops matching the new code. A missing field reads as undefined, which renders as the literal string undefined in user-facing text and as NaN in arithmetic — a tool-budget ceiling compared against NaN is always false, so the budget silently stops existing on resumed threads. Migrate on read, and keep the migration to defaults and renames only: it must never fail.
- Trap four: replayable data that should not be replayed. A one-off human override written into graph state gets checkpointed and re-applied on every resume. The test: does this describe how this run executes, or what this conversation is? The former belongs in runtime config, only the latter in state.
- Expect the follow-up: are pending parallel tasks preserved? Yes — a checkpoint holds not just the state snapshot but the steps not yet run, arguments included, so the planner does not re-run. But they live in a framework-internal channel, so a hand-rolled store that persists state and forgets that half will resume into a graph that looks finished while no work was ever dispatched.
分析过程 · 先想清楚再作答
- 这题最容易答成「读出来接着跑就行」。区分度在于你能不能说出**这些坑几乎全是静默的**——不抛异常、日志干净、结果看起来也对,只有对比数据时才发现不对。能说出这一点,答案就已经赢了一半。
- 先给一条推导链:检查点里存的是「当时那个版本的代码眼里的状态形状」,恢复就是把它塞回今天这个版本的代码里。所以所有坑都来自**两端不一致**:数据的形状、执行的入口、和那些不该被重放的东西。
- 坑一,恢复时又把输入喂了一遍。恢复的入口是不带输入地调用,状态已经在检查点里;带着原来那句话再调一次,框架会把它当成一次新的状态更新叠在中断点上,历史变成两份。它不报错。
- 坑二,分叉忘了带检查点 id。只给会话 id 拿到的是这条线最新的状态,于是「从第 2 步重来」变成了「在最后一步后面接着写」。同样不报错,只有对比子任务列表才看得出来。
- 坑三,版本兼容。改一个字段名、加一个必填字段,库里的老检查点就和新代码对不上;而缺字段读出来是 undefined,拼进文案就是字符串「undefined」,参与算术就是 NaN——比如工具预算的上限判断,一旦变成 NaN 比较,恒为假,预算上限在恢复出来的那条线上彻底失效。正确做法是在读的那一侧迁移,迁移函数只补默认值和改名、不做业务判断,绝不能失败。
- 坑四,不该被重放的东西进了状态。一次性的人工干预(比如人工改派)如果写进图状态,就会被检查点持久化并在每次恢复时重放一遍。判断口径:这条信息说的是「这一次执行怎么跑」还是「这个会话是什么」,前者进运行时配置,后者才进状态。
- 可以预期的追问:待执行的并行子任务存不存?答存——检查点里除了状态快照还有一份「还没跑的那几步,连参数一起」,所以恢复不用重跑规划节点;但它存在框架的内部通道里,自研存储层只实现「存状态」而漏掉这一半,恢复出来的图会看起来跑完了、其实一件活都没派出去。
Key points
- Lead with the pattern: replay bugs are almost all silent — no exception, clean logs, plausible output
- Resume takes no input; passing one appends another update at the interrupt point and doubles the history
- Forking requires the checkpoint id — thread id alone lands on the latest state, turning start over from step 2 into append after the end
- Version drift: missing fields read as undefined or NaN, so comparisons like a tool-budget ceiling become permanently false. Migrate on read, restricted to defaults and renames, and never let it fail
- Keep one-off human overrides out of graph state or they get persisted and re-applied on every resume — how this run executes belongs in config, what this conversation is belongs in state
- Pending parallel tasks are stored with their arguments, so the planner does not re-run; a hand-rolled store that skips that half resumes into a graph that dispatches nothing
答题要点
- 先点破共性:replay 的坑几乎全是静默的,不报错、日志干净、结果看着也对
- 恢复不要带输入,带了就是在中断点上又追加一次,历史变成两份
- 分叉必须带检查点 id,只给会话 id 会落在最新状态上,「从第 2 步重来」变成「接着往后写」
- 版本兼容:缺字段读出来是 undefined 或 NaN,会让预算上限之类的比较恒为假;在读的那一侧迁移,迁移只补默认值和改名且不能失败
- 一次性的人工干预不要进图状态,否则会被持久化并在每次恢复时重放;「这次怎么跑」进配置,「这个会话是什么」才进状态
- 待执行的并行子任务连参数一起存在检查点里,所以恢复不重跑规划;自研存储层漏掉这一半,恢复出来的图会一件活都不派
What problem does a checkpointer solve in a multi-agent system, and what does it cost?checkpointer 在多 Agent 系统里解决了什么问题?它的代价是什么?
Common in ChinaCommon overseasIntermediate#checkpointing#cost#operationsHow to reason about it · think before answering
- The second half is the real question. Answering it enables recovery and fault tolerance is a feature blurb any doc carries. The interviewer wants to know whether you have done the arithmetic and where it hurts.
- Make the value concrete in money and time: one multi-agent run with a review loop costs nine model calls. If the process is restarted for a deploy at call seven, without checkpoints all nine are wasted and the user is still watching a spinner. With them the run continues from the last signed-off point and no completed node re-runs. What you bought is a smaller unit of failure — a node instead of a whole run.
- Mention the three things it unlocks that retries alone cannot: human approval gates (pause before a node and the state simply waits), time-travel debugging (go back to just before the bad step and inspect state), and forking for comparison (run two variants from one checkpoint) — which is also the infrastructure evaluation is built on.
- Then the costs, all three. First, bigger state means slower writes, and it is written at every step: one request produces six checkpoints, so a byte added to state is six bytes written. Hence attachments hold references, not content, and retrieval results hold document ids, not full text.
- Second, the store has limits. With jsonb the hard cap is far away, but a row past roughly two kilobytes gets pushed to out-of-line storage and costs an extra IO on every read and write. The real engineering line is do not let a single checkpoint reach hundreds of kilobytes, not the theoretical cap.
- Third, the one people forget: version compatibility. Checkpoints are long-lived data, so every change to the state shape incurs migration debt, and missing fields usually do not throw — they silently yield undefined or NaN. This is why state fields should be reserved early: once a shape is persisted, changing a field is a data migration, not a code edit.
- Expect the follow-up: do checkpoints need cleanup? Yes — retention and archival per thread, or the table grows linearly with active users. Also treat it as sensitive data: graph state contains full conversations, so it must be included whenever you delete a user's data.
分析过程 · 先想清楚再作答
- 这题的下半句才是考点。只答「能恢复、能容错」是功能介绍,任何文档都写着;面试官想听的是你有没有算过这笔账,以及知不知道它会在哪里疼。
- 先把价值说具体,用钱和时间说:一次带评审回路的多 Agent 执行要调九次模型,跑到第七次进程被换版本重启,没有检查点就是九次全废、用户界面还停在转圈。有检查点则从上一个签字点接着跑,已经跑完的节点一次都不重跑——它买的是「失败的粒度从一整次执行降到一个节点」。
- 顺带说清它解锁的另外三件事,这三样单靠重试做不到:**人工审批闸口**(在某个节点前停下等人点确认,状态就停在那儿)、**时间旅行调试**(回到出问题那一步之前看状态长什么样)、**分叉对比**(从同一个检查点跑两种走法,比较结果,这也是评估的基础设施)。
- 然后是代价,三笔要说全。第一笔,**状态越大写得越慢**,而且是每一步都写一份——一次请求写六个检查点,状态里多一个字节就要多写六遍。所以附件存引用不存内容,检索结果存文档 id 不存全文。
- 第二笔,**存储本身有上限**。用 jsonb 存的话,硬上限很远,但单行超过大约两 KB 就会被挪到外存、每次读写多一次 IO,所以真正的工程线是「别让单个检查点变成几百 KB」,而不是那个理论上限。
- 第三笔也是最容易被忽略的:**版本兼容**。检查点是长期存活的数据,你每改一次状态形状就欠下一笔迁移债,而缺字段读出来通常不报错,只是静默给出 undefined 或 NaN。这一条决定了状态字段要尽早占好位子——图状态的形状一旦被持久化,改字段就不是改代码,是数据迁移。
- 可以预期的追问:那检查点要不要清理?答要,按会话线设保留期与归档策略,否则这张表会随日活线性膨胀;另外要留意它是敏感数据——图状态里有完整对话,删除用户数据时这张表必须一起处理。
Key points
- Core value: it shrinks the unit of failure from a whole run to a single node, so a nine-call run is not wasted by one restart
- It also unlocks three things retries cannot: human approval gates, time-travel debugging, and forking from one checkpoint to compare variants — the substrate evaluation is built on
- Cost one: bigger state writes slower, and it is written at every step — hence references for attachments and document ids for retrieval results
- Cost two: storage limits — a jsonb row past roughly two kilobytes goes out-of-line and costs an extra IO, so the practical line is keeping a checkpoint well under hundreds of kilobytes
- Cost three: version compatibility — every change to the state shape is migration debt, and missing fields silently yield undefined or NaN, which is why fields should be reserved early
- Operationally you need retention and archival, and you must treat it as sensitive data: graph state holds full conversations and must be purged with the user's data
答题要点
- 核心价值:把失败的粒度从「一整次执行」降到「一个节点」,九次模型调用的执行不会因为一次重启全废
- 还解锁三件重试做不到的事:人工审批闸口、时间旅行调试、从同一个检查点分叉对比(也是评估的基础设施)
- 代价一,状态越大写得越慢,而且每一步都写一份——所以附件存引用、检索结果存文档 id
- 代价二,存储有上限:jsonb 单行超过约两 KB 就外存、多一次 IO,工程线是别让单个检查点到几百 KB
- 代价三,版本兼容:状态形状改一次就欠一笔迁移债,缺字段静默给出 undefined 或 NaN;所以字段要尽早占位
- 运维上还要有保留期与归档,并把它当敏感数据处理——图状态里有完整对话,删用户数据时必须一起删
D19 Cross-Service Agent Integration: Minting a User-Level JWT, JWKS Signature Verification, the inject/memory/usage Interfaces, Idempotent externalId
For service-to-service calls, would you use a service token or a user token? When does each apply?两个服务之间调用,你会用服务级令牌还是用户级令牌?分别适用于什么场景?
Common in ChinaCommon overseasIntermediate#auth#security#api-designHow to reason about it · think before answering
- The hinge is each. Answering user tokens are safer turns a design question into a slogan — the interviewer wants the conditions under which each one is correct, and the concrete cost of choosing wrong.
- Give the deciding question first: is there a specific user behind this call? If yes, it must be a user token. If not — fetching config, reporting metrics, running a reconciliation batch — a service token is the right answer, and stuffing in a user id would fabricate audit history.
- Then state the three reasons as costs, not virtues. A leaked service token means every user's data at once; a leaked user token means one user, and it expires in fifteen minutes. Audit logs with a service token only show that some service called, never on whose behalf. And a downstream service doing per-user authorization is forced to trust a userId in the request body, which the caller writes freely.
- Add the production view: it is rarely either-or. Real systems use the service credential to obtain user tokens — the caller proves who it is once, then mints a short-lived token representing one user. The service credential then appears only at the minting step, never on every business call.
- Expect: what if a token leaks? Answer in two layers — a short lifetime (fifteen minutes here) does most of the containment, and a jti denylist is the supplement. Do not lead with a denylist: it puts a database lookup in front of every verification and gives away the whole point of stateless verification.
- Expect: how fine-grained should scopes be? Offer a usable rule — split along asymmetric risk. A bad read leaks information; a bad write poisons data that keeps influencing every later turn. So read and write always split; finer than that only if a real caller genuinely needs just one half.
分析过程 · 先想清楚再作答
- 题眼在「分别」。答「用户级更安全」就把一道设计题做成了口号题——面试官想看你能不能说出两者各自成立的条件,以及选错的具体代价。
- 先给判断依据,一句话就能拆开:这次调用**有没有一个具体的用户在背后**。有,就必须是用户级;没有(拉配置、上报指标、跑对账批处理),服务级才是对的,硬塞一个用户 id 进去反而是伪造审计记录。
- 然后把用户级的三条理由说成代价而不是优点:服务级令牌泄露一次等于全量用户数据泄露,用户级泄露一张只丢一个用户且十五分钟自动作废;服务级在审计日志里只能查到「某服务调了一次」,查不到替谁操作;下游做用户级权限判断时,服务级令牌逼着它去信请求体里的 userId,而那是调用方可以随便写的。
- 补一句生产视角:两者不是二选一,真实系统里常常是「服务级令牌用来换用户级令牌」——调用方先用自己的服务凭证证明自己是谁,再申请一张代表某个用户的短期令牌。这样服务凭证只出现在铸造这一步,不出现在每一次业务调用里。
- 可以预期的追问一:令牌泄露了怎么办?答案要分两层——短有效期(本课 15 分钟)是止损的主力,撤销列表按 jti 拉黑是补充;不要上来就说「用黑名单」,那等于给每次验签加一次数据库查询,把无状态验签的好处全赔进去了。
- 可以预期的追问二:那 scope 该切多细?给一条可操作的判据——按「读写不对称的风险」切,读错了泄露信息、写错了污染数据且会持续影响后续每一轮对话,所以 read 和 write 必须分开;再细就要看有没有真实的调用方只需要其中一半。
Key points
- The deciding question is whether a specific user stands behind the call: yes means user token, no (config, metrics, reconciliation) means service token
- A leaked service token exposes every user; a leaked user token exposes one and expires on its own
- Auditing has to reach a person — only the sub claim answers who the call was made on behalf of
- With a service token the downstream must trust a userId in the request body, which the caller can forge
- Common production shape: the service credential only buys short-lived per-user tokens and never appears on business calls
- After a leak, short lifetimes do the containment and a jti denylist supplements it — do not trade away stateless verification by default
答题要点
- 判断依据是「这次调用背后有没有一个具体用户」:有就用用户级,没有(配置、指标、对账批处理)才用服务级
- 服务级令牌泄露的爆炸半径是全量用户,用户级只影响一个用户且短期自动失效
- 审计要能落到人:只有 sub 字段能回答「当时是替谁操作的」
- 下游要做用户级权限判断时,服务级令牌逼着它去信请求体里的 userId,而那是调用方可以伪造的
- 生产里常见组合:服务凭证只用来换取代表某个用户的短期令牌,不出现在每次业务调用里
- 泄露后的止损顺序是短有效期优先、jti 撤销列表补充,别一上来就上黑名单换掉无状态验签
How does JWKS-based verification work, and why does it fit cross-service scenarios better than a shared secret?JWKS 验签是怎么工作的?为什么跨服务场景下它比共享密钥更合适?
Common in ChinaCommon overseasBasic#auth#jwt#securityHow to reason about it · think before answering
- This is the giveaway question of the chapter, but it still separates people: can you turn key rotation into a concrete operational sequence rather than saying it is easier to manage?
- Describe the mechanism in three sentences. The issuer holds the private key and signs; the public key set is published at a fixed address (/.well-known/jwks.json here); the token header carries a kid, and the verifier picks the matching public key from the set. Verification needs only public material, so the endpoint is public by design.
- Then give three reasons, each as an operational action: rotation needs no synchronized deploy on both sides (publish the new public key, let both coexist, drop the old one after old tokens expire); the verifier holds verification power, not signing power, so compromising it does not let anyone forge tokens; and adding a caller does not scatter another copy of a secret.
- Volunteer the part people forget: verifying the signature is not the whole check. A valid signature only proves the issuer signed it. You still validate iss, aud and exp — and missing aud is the most common cross-service incident, because a token the issuer signed for a different downstream is equally well signed, so skipping audience means holding the door open for someone else's API.
- Two engineering details worth adding: cache the key set but refetch on an unknown kid, or rotation day becomes a mass failure; and allow a small clock skew on exp, but not so large that it cancels out the point of short lifetimes.
- Expect: so is HS256 unusable? Answer that it is fine when one service signs and verifies its own tokens, and it is faster. The criterion is whether signer and verifier sit in the same trust domain; across domains, asymmetric is mandatory. Framing it as a trade-off shows judgment rather than memorization.
分析过程 · 先想清楚再作答
- 这是本章的送分题,但送分题也有区分度:能不能把「密钥轮换」这件事讲成一个具体的运维动作,而不是一句「更方便管理」。
- 先讲机制,三句话:签发方持私钥签名,公钥集合挂在一个固定地址上(本课用 /.well-known/jwks.json);令牌头部带一个 kid,验签方按 kid 从集合里挑对应的公钥;验签只用公钥,所以这个地址是公开的,谁都能拉。
- 再讲为什么比共享密钥好,三条都要落到运维动作上:轮换不用两边同时发版(新旧两把公钥并存一段时间,等老令牌自然过期再摘旧的);验签方拿到的只是验签能力而不是签名能力,被入侵也伪造不出令牌;多一个调用方不用多散一份密钥出去。
- 然后主动补上最容易被忽略的一段:验签不等于验完。签名合法只说明「这确实是那个签发方签的」,还必须校验 iss、aud、exp——**漏掉 aud 是跨服务集成里最常见的事故**,因为签发方给别的下游服务签的令牌,签名一样合法,不校验受众就等于替别人的接口开门。
- 工程细节可以再加两条:公钥集合要缓存,但遇到没见过的 kid 要能主动重拉,否则轮换那一刻会集体失败;以及时钟偏移,exp 校验要留一点容忍度,但容忍度不能大到把短有效期的意义抵消掉。
- 可以预期的追问:那 HS256 是不是就不能用了?答「同一个服务自己签自己验时它没问题,而且更快」——判据是签名方和验签方是不是同一个信任域,跨了域就必须非对称。这么答显得你在做权衡而不是背结论。
Key points
- Mechanism: private key signs, public key set sits at a fixed URL, the token header carries a kid, the verifier selects by kid
- Rotation needs no synchronized deploy: publish the new key, let both coexist, retire the old one after old tokens expire
- The verifier gets verification power only, never signing power, so compromising it cannot forge tokens
- Adding callers does not scatter more secrets; the public key being public is the design intent
- Beyond the signature you must check iss, aud and exp — skipping aud opens your API to tokens signed for someone else
- Cache the key set but refetch on an unknown kid; HS256 is still reasonable when one service signs and verifies its own tokens
答题要点
- 机制:私钥签名、公钥集合挂在固定地址、令牌头部带 kid、验签方按 kid 取公钥
- 轮换不用两边同时发版:新旧公钥并存,等老令牌自然过期再摘旧的
- 验签方只拿到验签能力而不是签名能力,被入侵也伪造不出令牌
- 调用方增加不需要多散一份密钥,公钥公开本来就是设计意图
- 验签之外必须校验 iss、aud、exp,漏掉 aud 等于替别的下游服务开门
- 缓存公钥集合但要能按未知 kid 主动重拉;HS256 在同一信任域内自签自验仍然是合理选择
How do you design an idempotency key for cross-service calls — who generates it, where does it live, and what do you return on a repeat?跨服务调用的幂等键该怎么设计?由谁生成、存在哪、重复了返回什么?
Common in ChinaCommon overseasIntermediate#idempotency#distributed-systems#api-designHow to reason about it · think before answering
- This question separates people entirely on implementation detail. Anyone can define idempotency; answering who generates the key, where it lives, and what a repeat returns shows whether you have actually built one.
- Start with the rule: the final arbiter must be a database uniqueness constraint, not an application-level check-then-insert. Check-then-insert always passes single-process tests and produces duplicates the moment you run two replicas — both check, both find nothing, both insert. The window is too narrow to reproduce under load testing and wide enough to produce dirty rows daily in production.
- Who generates it: the caller, because only the caller knows that two retries are the same event. But the key must be derived from the event itself, never a fresh random UUID per retry — that is idempotency in name only. Same criterion as the user-message case from day 8.
- Cross-service adds one trap worth the most points: never use the caller's raw id as the key. Two different callers will eventually both produce evt-1, and the failure is not an error — the second user silently receives nothing, because their event is treated as a duplicate and the logs look clean. Namespace it: issuer plus user id plus event id, all three taken from the verified token so none of them can be forged.
- What to return also matters: a repeat gets 200 with the original result, not 409. Repeats are normal in distributed systems; a 409 makes the caller's retry logic treat it as a failure and the situation compounds.
- Expect: does this table grow forever? Yes, so give it a retention window — a TTL matching the replay window the business tolerates, say seven days, with periodic cleanup. Say plainly that a duplicate arriving after cleanup is treated as new; that is a stated trade-off, not a hole.
分析过程 · 先想清楚再作答
- 这题的区分度全在实现细节上。概念谁都会说,能不能答对「谁生成、存在哪、返回什么」这三个具体问题,直接暴露你有没有真做过。
- 先立一条铁律:**幂等的最终裁判必须是数据库的唯一约束**,不是应用层的「先查一下有没有」。先查后插在单进程测试里永远是对的,一上多实例就出双份——两个副本同时查、同时发现没有、同时插入,这个时间窗压测时窄到复现不出来,上线后每天出几条脏数据。
- 再答「谁生成」:由**调用方**生成,因为只有它知道重试的那两次是同一件事;但键必须由事件内容决定,不能是每次重试重新生成的随机 UUID——那等于没有幂等。这条和 D8 的用户消息幂等是同一条判据。
- 跨服务比同服务多一个坑,这是本题最有价值的一点:**调用方给的 id 不能直接当键用**。两个不同的调用方各自造出 evt-1 是迟早的事,撞车之后的表现不是报错,而是后来那个用户静默收不到消息——他的事件被当成重复丢掉了,日志里干干净净。所以落库前要加命名空间,用「签发方 + 用户 id + 事件 id」三段拼,而且三段都取自验签后的令牌,伪造不了。
- 「返回什么」也是个坑:重复送达要返回 200 并附上第一次的结果,不要返回 409。重复不是错误,是分布式系统的常态;回 409 会让调用方的重试逻辑把它当失败处理,越重试越乱。
- 可以预期的追问:这张表会不会无限涨?答「会,所以要有保留期」——按业务能接受的重放窗口设一个 TTL(比如 7 天)定期清理,同时说明清理之后超期的重复请求会被当成新事件,这是一个明确的、可接受的取舍,不是漏洞。
Key points
- The arbiter is a unique constraint plus on conflict do nothing; check-then-insert duplicates as soon as you run two replicas
- The caller generates the key, but it must be derived from the event — a fresh UUID per retry is not idempotency
- Never use the caller's raw id: namespace it with issuer plus user id plus event id, all taken from the verified token
- A collision does not raise an error; it silently drops another user's event and leaves clean logs
- Return 200 with the original result on a repeat, never 409, or the caller's retry logic treats success as failure
- Give the table a retention window and state that post-cleanup repeats count as new events — a stated trade-off, not a hole
答题要点
- 最终裁判是数据库唯一约束加 on conflict do nothing,先查后插在多实例下必然出双份
- 键由调用方生成,但必须由事件内容决定,随机 UUID 等于没有幂等
- 调用方给的 id 不能直接当键:加命名空间(签发方 + 用户 id + 事件 id),三段都取自验签后的令牌
- 撞车的后果不是报错而是另一个用户静默收不到消息,日志里看不出异常
- 重复送达返回 200 加第一次的结果,不要返回 409,否则调用方会当失败继续重试
- 幂等表要设保留期,超期后的重复会被当成新事件,这是明确取舍不是漏洞
You are designing the API surface an Agent platform exposes to other services. How do you draw the responsibility boundaries?设计一组给外部服务调用的 Agent 平台接口,你会怎么划分职责边界?
Common in ChinaCommon overseasDeep dive#api-design#security#architectureHow to reason about it · think before answering
- This is an open design question testing whether you have a reusable criterion. Candidates who start listing endpoints run out of material under follow-ups; candidates who give the criterion first turn follow-ups into extra points.
- Offer the criterion: draw boundaries by who owns the data, not by who calls it. Sessions, run records, memories and the cost ledger belong to the platform, so the platform exposes exactly three things — write one event in (inject), read and write memory, and read usage. Orchestration belongs to the caller, so the platform should not offer run this graph for me; that pulls someone else's responsibility inside your walls and freezes both sides.
- Second criterion, the security invariant that runs through the whole course: identity comes from the token, never from the request body. No endpoint accepts a userId; the server always reads sub. Break this once and the authorization model collapses — a usage endpoint that accepts a userId query parameter lets any valid token enumerate everyone's spend. The same principle appeared on the memory search tool: the model gets no identity parameter, the server fills it in.
- Third, return the minimum necessary. Usage returns aggregates, not line items, because line items carry run ids and model choices — that hands over your internal strategy. Memory supports a query with a result limit rather than dump everything this user ever said; once that exists, some caller in a hurry will make it the default.
- Fourth, every write endpoint must be safely replayable: an externalId, a uniqueness constraint underneath, and 200 on a repeat. Cross-service calls will be duplicated; this is not optional.
- Expect: what dimension do you rate-limit on? Per user, not per caller — limiting per caller lets one user's runaway retries consume everyone's budget. Also guard against loops: tag injected messages with their source, or two services can pull each other into an infinite cycle and the bill is the only thing that tells you.
分析过程 · 先想清楚再作答
- 这是开放题,考的是你有没有一条能反复用的划分依据。上来就罗列接口清单的人会被追问到没词;先给依据再给清单的人,追问反而是加分机会。
- 给一条判据:**按「谁拥有这份数据」划,不按「谁调用它」划。** 会话、执行记录、记忆、成本台账都属于平台,所以平台开的三个口子恰好是「写一条进来(inject)」「读写记忆(memory)」「查账(usage)」;编排逻辑属于对方,平台就不该提供「帮我跑一遍这个图」的接口——那是把对方的职责搬到自己身上,将来两边都改不动。
- 第二条判据是**贯穿全课的安全不变量:身份只能来自令牌,不能来自请求体**。所有接口都不接受 userId 参数,服务端一律从令牌的 sub 取。这条一旦破例,权限模型就整个塌了:查成本的接口如果接受 userId 查询参数,任何一张有效令牌都能遍历所有人的消费金额。同一条原则在 D12 的记忆检索工具上也出现过——不给模型身份参数,服务端自己填。
- 第三条是**返回粒度要按最小必要给**。usage 只返回汇总不返回明细,因为明细里带着执行 id 和模型选型,等于把平台的内部策略一并交出去;memory 要支持按 query 检索并限制条数,不提供「把这个人的所有记忆倒出来」的接口——一旦提供,它迟早会被某个图省事的调用方用成默认写法。
- 第四条是**每个写接口都要能被安全重放**:带 externalId、唯一约束兜底、重复返回 200。跨服务调用一定会重复,这不是要不要做的问题。
- 可以预期的追问:那限流按什么维度做?答「每用户,不是每调用方」——按调用方限流的话,一个用户的异常重试会把所有人的额度吃光;另外写接口要防回环,注入的消息要打来源标记,否则两个服务能把彼此拉进无限循环,账单是唯一会提醒你的东西。
Key points
- Draw boundaries by data ownership, not by caller: sessions, memory and the ledger belong to the platform, orchestration belongs to the caller
- Three endpoints for three kinds of ownership — inject, memory, usage — and no run this graph for me endpoint that crosses the line
- No endpoint accepts a userId; identity always comes from the token's sub, and one exception collapses the model
- Return the minimum necessary: usage gives aggregates only, memory takes a query with a limit instead of dumping everything
- Every write endpoint carries an externalId backed by a uniqueness constraint and answers 200 on repeats
- Rate-limit per user rather than per caller, and tag injected messages with their source so two services cannot loop forever
答题要点
- 按「谁拥有这份数据」划边界,不按「谁调用」划:会话、记忆、台账属于平台,编排属于对方
- 三个口子对应三种所有权:inject 写入、memory 读写、usage 查账;不提供「帮我跑图」这种越界接口
- 所有接口都不接受 userId 参数,身份一律从令牌 sub 取——这条破例一次权限模型就塌了
- 返回粒度按最小必要:usage 只给汇总不给明细,memory 按 query 限条数而不是全量倒出
- 每个写接口都带 externalId 并由唯一约束兜底,重复返回 200
- 限流按每用户而不是每调用方;注入的消息要打来源标记防止两个服务互相回环
D20 Scheduled Jobs and Proactive Outreach: Time Zones, Quiet Hours, Daily Caps, a Notification Provider Abstraction
How does a system-initiated message differ from a user-triggered one, from a system design point of view?系统主动发给用户的消息,和用户自己触发的消息,在系统设计上有什么不同?
Common in ChinaCommon overseasBasic#proactive-messaging#system-design#product-engineeringHow to reason about it · think before answering
- This looks like a definition question but it is really a filter. Answering both send a message, only the trigger differs stays at the shallowest layer — the interviewer wants to know what extra code the difference forces you to write.
- Give three structured differences: who is waiting (a user-triggered reply has someone staring at the screen, a proactive message has nobody waiting); how failure is handled (user-triggered failures must surface as errors, proactive failures should usually be silently deferred or dropped); and what justifies sending (the user asked, versus you having to justify it yourself).
- The third is the hinge, so make it explicit: the default answer for a proactive message is do not send. Every one must answer why now, why this user, and why this content is worth interrupting them. Fail any of the three and it should not go out.
- Then land the difference in the system: the proactive path needs an admission layer the reactive path does not — compute the user's local time from their timezone, defer if it falls inside quiet hours, drop if the daily cap is used up.
- Quantify the cost, which is what separates having read about this from having shipped it: tolerance for proactive messages is very low. After a few irrelevant pushes the user will not argue about the content, they will revoke the notification permission — and once revoked, the genuinely important message cannot reach them either. You are spending a budget that never refills.
- Expect: so is a cron job the same thing as a proactive message? No. The scheduler solves firing on time (central scheduling, an idempotency key anchored to the scheduled minute); proactive care solves whether to send at all. One is mechanism, the other is admission, and they belong in separate layers.
分析过程 · 先想清楚再作答
- 这题看着像概念题,其实是筛人题。答「都是发消息,只是触发方不同」就落进了最浅的一层——面试官想听的是这个差别会逼你多写哪些代码。
- 先给三条结构化的差别:谁在等(用户触发时他正盯着屏幕,主动消息没有人在等);失败怎么处理(用户触发的失败必须报错给他看,主动消息的失败多数时候应该安静地推迟或放弃);凭什么发(用户触发是他开了口,主动消息你得自己说出理由)。
- 第三条是题眼,要说透:主动消息的默认答案是不发。每一条都要能回答为什么是现在、为什么是这个用户、为什么这条内容值得打断他,三个问题答不上任何一个就不该发。
- 然后给出这个差别在系统里的落点:主动消息这一侧必须多出一层准入判断,本课叫三道闸——按用户时区算本地时间、安静时段命中就推迟、每日上限满了就拦下。用户触发那一侧完全不需要这层。
- 代价也要算清楚,这是区分「读过文章」和「做过系统」的地方:用户对主动消息的容忍度极低,连着几条无关紧要的推送之后他不会争论内容对不对,直接关掉通知权限——而权限一关,你连真正重要的那条也送不出去了。你消耗的是一个用完就拿不回来的额度。
- 可以预期的追问:那定时任务和主动消息是不是一回事?答不是。定时任务解决的是「能按时触发」(中心调度、幂等键锚在计划触发的那一分钟),主动消息解决的是「该不该发」,前者是机制、后者是准入,两层要分开做。
Key points
- Three differences: who is waiting, how failure is handled, and what justifies sending — the third is the crux
- The default answer for a proactive message is no; each one must justify why now, why this user, why worth interrupting
- In the system this becomes an admission layer — timezone, quiet hours, daily cap — that the reactive path does not need
- The cost is a non-renewable budget: annoy the user and they revoke notifications, taking the important messages down with them
- Scheduling (fire on time) and proactive care (should we send) are two separate layers
答题要点
- 三条差别:谁在等、失败怎么处理、凭什么发;第三条是关键
- 主动消息的默认答案是不发,每条要能回答为什么是现在、为什么是这个用户、为什么值得打断他
- 落到系统上就是多一层准入判断:时区换算、安静时段、每日上限,用户触发那一侧不需要
- 代价是一个不可再生的额度:推送惹烦了用户,他关掉权限之后重要消息也送不出去
- 定时机制(能按时触发)和主动关怀(该不该发)是两层,不要混在一起做
Building a scheduled push service for users worldwide, what timezone pitfalls would you hit, and how do you handle the DST switchover day?做一个面向全球用户的定时推送服务,时区上你会踩到哪些坑?夏令时切换那天怎么处理?
Common in ChinaCommon overseasDeep dive#timezone#scheduling#correctnessHow to reason about it · think before answering
- All the signal in this question lives in the DST half. Store UTC, render local is the passing grade; giving a verifiable ruling for the switchover day is what separates knowing the pitfall from having fixed it.
- Nail the two basics first: always store UTC (an absolute instant) and convert to the user's zone before any judgment (a wall-clock time). The self-check is one sentence — can this column plus the user's stored timezone uniquely reconstruct the absolute instant? A local time string cannot.
- The second pitfall is the timezone field itself: store the IANA identifier (Asia/Shanghai), never a UTC offset. Offsets shift twice a year under DST; the identifier is the rule and the offset is only what that rule evaluated to on one particular day, so it is stale the moment you persist it.
- The third is the two anomalies on switchover day: spring-forward makes some local time simply not exist (02:30 on 2026-03-08 in New York), and fall-back makes some local time occur twice (01:30 on 2026-11-01). If your schedule point lands in either window, send at 8am local has no unique answer. State the ruling explicitly rather than leaving it to whatever the library picks: shift a nonexistent time forward past the transition (02:30 becomes 03:30), and take the first occurrence when it happens twice — which is exactly what java.time's ZonedDateTime.of does, so you can assert on it in tests.
- The fourth is the one people miss: when a user travels across zones, which timezone counts. Answer: the explicit field on the user profile, never silent drift from device reports; a device report should only prompt the user to confirm a change. Go one level deeper if you can — when the zone jumps more than three hours within 24 hours, treat that day's quiet hours as the union of the old and new zones and stay silent if either is quiet. Being conservative costs a few hours of delay; being aggressive costs a 3am buzz.
- Expect: why are these bugs so hard to catch? Because your laptop, CI and production are often all in one zone, frequently UTC, so forgot to convert stays green everywhere. Give the fix: pin the test users to three distinct zones, none equal to the server's, and every server-timezone dependency turns red immediately.
分析过程 · 先想清楚再作答
- 这题的区分度全在夏令时那半句。只答「存 UTC、展示转本地」是及格线,能不能给出夏令时那天的**可验证裁定**决定了你是「知道有坑」还是「填过坑」。
- 先把基础两条说死:时间一律存 UTC(存的是绝对时刻),判断前先转成用户本地时区(判断的是墙上时间)。判据是一句可自查的话——只靠这一列加上用户档案里的时区,能不能唯一还原出那个绝对时刻。存本地时间字符串答不上来。
- 第二个坑是时区字段本身:必须存 IANA 标识(Asia/Shanghai)而不是 UTC 偏移量。偏移量一年会随夏令时变两次,标识是规则、偏移量只是规则在某一天算出来的结果,存结果的那一刻它就过期了。
- 第三个坑是夏令时那天的两种反常:春季前跳会让某个本地时间**根本不存在**(纽约 2026-03-08 的 02:30),秋季回拨会让某个本地时间**出现两次**(2026-11-01 的 01:30)。只要你的调度点落在这两个窗口里,「每天早上 8 点发」就解释不出唯一答案。裁定要显式给出而不是交给库随便选:不存在就顺延到过渡之后(02:30 变 03:30),出现两次就取第一次——这也正是 java.time 的 ZonedDateTime.of 的默认行为,可以直接写成断言测试。
- 第四个坑最容易被漏:用户跨时区旅行时,他的时区以哪一次为准。答案是以用户档案里那个显式字段为准、绝不跟着设备静默漂移;设备上报只用来询问是否切换。更细一层可以补:时区在 24 小时内跳变超过 3 小时时,当天的安静时段按新旧两个时区的并集处理,任何一边在安静就不发——保守的代价是晚几小时收到,激进的代价是在人家凌晨三点响一声。
- 可以预期的追问:这种 bug 为什么很难被测出来?因为本机、CI、生产常常都在同一个时区甚至都在 UTC,「忘了转时区」在所有测试里都是绿的。给出判据:把测试用户的时区故意设成三个互不相同、且都不等于服务器时区的值,任何依赖服务器时区的判断当场变红。
Key points
- Store UTC everywhere, convert to the user's zone before judging; the check is whether column plus zone reconstructs the instant
- Persist IANA identifiers, not UTC offsets — offsets change twice a year and are stale on write
- Two DST anomalies: a local time that does not exist (spring forward) and one that occurs twice (fall back)
- Make the ruling explicit and assertable: shift nonexistent times past the transition, take the first of a duplicated pair (matching java.time)
- For travellers, trust the explicit profile field, not device drift; on jumps over three hours, treat quiet hours as the union of both zones
- Pin test users to three zones different from the server's, or the missing conversion stays green in every test
答题要点
- 存储一律 UTC,判断前转用户本地时区;自查判据是这一列加时区能否唯一还原绝对时刻
- 时区存 IANA 标识而不是 UTC 偏移量——偏移量随夏令时一年变两次,存下来就过期
- 夏令时两种反常:本地时间不存在(春季前跳)、本地时间出现两次(秋季回拨)
- 裁定要显式且可断言:不存在就顺延到过渡之后,出现两次取第一次(与 java.time 默认一致)
- 跨时区旅行以用户档案里的显式字段为准,不跟设备漂;跳变超过 3 小时时按新旧时区的并集判安静
- 测试里把用户时区设成三个不同于服务器的值,否则「忘了转时区」在所有测试里都是绿的
How would you implement quiet hours and a per-user daily cap, and where in the pipeline should they be evaluated?quiet hours 和每日发送上限这两条规则你会怎么实现?它们应该放在链路的哪一步判断?
Common in ChinaCommon overseasIntermediate#rate-limiting#quiet-hours#cost-controlHow to reason about it · think before answering
- There are two hinges here and most candidates only answer the first. One is how to evaluate the rules (a details question), the other is where in the pipeline (an architecture question) — the second is where the points are.
- Start with quiet hours. Once you fold times into minutes-from-midnight, almost everyone first writes start less-or-equal now and now less-than end. That is correct for a same-day window like a lunch break, but for 22:00 to 08:00 it is always false: start is 1320, end is 480, the condition never holds, and you push at 3am. The fix is to use and when start is before end, and or when start is after end. What makes this bug nasty is that it only misfires on the cross-midnight config, so a unit test written around 13:00 to 14:00 passes.
- Then what to do on a hit: defer, do not drop. The decision should not be the sender's mood — attach an expiry to each candidate and drop only when it expires before the window ends, deferring everything else to the window's end. A thirty-minute cancellation warning is worthless tomorrow; a billing summary is just as valid at 8am. Mention the thundering herd too: every deferred message resolves to the same due instant, so add jitter derived from a hash of the user id, never a random number, or you cannot reproduce incidents.
- Now two details on the daily cap. First, the day must be the user's local calendar day; keying on the UTC date charges an East-Asian user's 8am message to yesterday's budget. Second, increment first and check the returned value, then give the slot back if it exceeded — a read-then-write races, letting two candidates read the same count and both go out. Return the slot on a hard rejection from the channel as well.
- Finish with the architecture half, which is the valuable part: all three gates must run before the content is generated, not at the send step. Get the order wrong and the program still works and sends the same messages; the only difference is that you paid for a model call on every message you then threw away. At 1000 users, three candidates each per day, forty percent blocked and roughly $0.00075 per message, that is about $27 a month wasted — more than the normal conversational spend for the same cohort — and it is invisible in monitoring. Only putting candidate count next to sent count reveals the gap.
- Expect: what order do the three gates run in? Timezone, quiet hours, daily cap, with the cap last. A message deferred to tomorrow morning must not consume today's quota; reverse the order and users get rate-limited despite having received almost nothing.
分析过程 · 先想清楚再作答
- 这题有两个题眼,很多人只答了前一个。第一个是「怎么判断」(细节题),第二个是「放在哪一步」(架构题),后者才是拿分点。
- 先讲 quiet hours 的判断。把时刻折成从午夜起算的分钟数之后,绝大多数人第一次都会写成 start 小于等于 now 且 now 小于 end。这对午休那种同日区间是对的,对 22:00 到 08:00 恒为 false——start 是 1320、end 是 480,条件永远不成立,于是半夜照发。正确写法是 start 小于 end 时用「且」,start 大于 end(跨午夜)时换成「或」。这个 bug 恶劣在只在跨午夜的配置上错,用 13:00 到 14:00 写的单元测试全绿。
- 接着是命中之后怎么办:推迟,不是丢弃。判据不该由发送方临时决定,而应该由消息自己带一个过期时刻——过期时刻早于窗口结束的丢弃,其余一律推迟到窗口结束。限时取消提醒过了今晚就没意义,账单提醒明早发一样有效。另外要提一句惊群:所有推迟的消息会算出同一个到期时刻,要加一个按用户标识哈希得出的抖动(不能用随机数,否则线上复现不了)。
- 再讲每日上限的两个细节。一是「一天」必须是**用户本地日历日**,写成 UTC 日的话东八区用户早上八点前发的会算进昨天的额度。二是必须先占坑再判断——原子自增拿返回值比上限,超了再把名额还回去;先查后写在并发下两条候选会同时读到同一个值然后一起发出去。渠道明确拒绝时也要把名额还回去。
- 最后是架构题那一半,也是最值钱的一段:三道闸必须在**生成内容之前**判断,不是在发送那一步。顺序错了程序照样跑通、发出的消息也一样,唯一区别是每条被拦下的消息你都已经付过一次模型调用的钱。按 1000 用户每天各 3 条候选、拦掉四成、单条约 0.00075 美元算,一个月白花约 27 美元,比这批用户的正常对话开销还高,而且监控上完全看不出来——只有把候选数和实际发送数并排摆出来才看得见差额。
- 可以预期的追问:三道闸内部谁先谁后?答时区、安静时段、每日上限,上限必须最后。因为被安静时段推迟的消息明早才发,不该占掉今天的名额;顺序反了用户会发现自己明明没收到几条却被限流了。
Key points
- Cross-midnight quiet hours need or when start is after end; the naive and version is always false for 22:00-08:00
- On a hit, defer to the end of the window rather than drop; only drop when the message's own expiry precedes that
- Deferral causes a thundering herd, so add jitter hashed from the user id, never a random value
- The day in a daily cap must be the user's local calendar day, not the UTC date
- Increment atomically then compare and release on overflow; read-then-write over-sends under concurrency
- Run all three gates before generating content — otherwise every blocked message has already been paid for (about $27/month at the example scale); order them timezone, quiet hours, daily cap, with the cap last
答题要点
- 跨午夜的安静时段:start 小于 end 用「且」,start 大于 end 换成「或」,朴素写法对 22:00-08:00 恒为 false
- 命中安静时段是推迟到窗口结束而不是丢弃;只有自带的过期时刻早于窗口结束才丢
- 推迟会造成惊群,要加按用户标识哈希得出的抖动,不能用随机数
- 每日上限的「天」必须是用户本地日历日,不是 UTC 日
- 计数要先占坑再判断(原子自增后比上限,超了还回去),先查后写在并发下会超发
- 三道闸必须在生成内容之前判断,装晚了每条被拦的消息都已经付过模型调用的钱(示例量级约 27 美元每月);闸内顺序是时区、安静时段、每日上限,上限最后
You have abstracted model calls, payments and notification channels behind providers. How does the notification interface differ from the other two?模型调用、支付、通知渠道你都做过 provider 抽象。通知这一份接口和另外两份有什么不同?
Common in ChinaCommon overseasIntermediate#provider-abstraction#api-design#retry-semanticsHow to reason about it · think before answering
- This question separates applying a pattern from understanding one. Saying all three are the same — an interface with several implementations so you can swap vendors without touching business code — only covers the shared part; the interviewer wants to see whether you spotted the differences and encoded them in the interface.
- Acknowledge the commonality in one line: each pushes a replaceable dependency behind an interface, business code depends only on the interface, and the selection point lives in exactly one place. Correct, but not differentiating.
- Then give three differences, which is where the points are. First, accepted is not delivered: when a payment gateway returns success the money has moved, but when a notification channel returns success it has merely taken the message, and actual delivery arrives later as an asynchronous receipt. So the result is accepted, never delivered, and it must carry the provider-side message id so the receipt can be correlated.
- Second, throttling lives at a different layer: the channel has its own per-second ceiling and tells you to come back later with a 429 plus a retry interval — a channel-level technical constraint — while the daily cap is a user-level courtesy constraint. Collapsing them into one concept makes them impossible to tune separately: one says this line is congested, the other says this person has been interrupted enough today.
- Third, there is no undo: payments have refunds, notifications do not. Once handed to the channel the message is gone, and cancel only means anything before that handoff. So the interface must not expose a cancel method — leaving an operation that cannot work is worse than not having it, because callers will actually use it.
- Expect: how do you design retries then? Three classes. Throttling backs off for the interval the channel gave you. Parameter errors (invalid body, unsubscribed user) are not retryable, so give up and return the daily slot. Server errors and timeouts are retryable but must carry the same idempotency key — you can delete a duplicate row, you cannot un-buzz a phone. Add a test for the abstraction itself: if a new channel only has to implement send the message, the boundary is right; if it also needs to know whether it is quiet hours or which message of the day this is, business rules have leaked into the channel layer.
分析过程 · 先想清楚再作答
- 这题在考你是「会套模式」还是「懂模式」。把三者说成一回事——都是接口加多个实现、换厂商不改业务——只答到了共性那一层,面试官真正想看的是你有没有识别出差异并把它写进接口。
- 先给共性,一句话带过:都是把「会被替换的东西」推到接口后面,业务代码只认接口,选择点集中在一处。这一层是对的,但不构成区分度。
- 然后给三条差异,这是拿分点。第一,收下不等于送达:支付网关返回成功钱就划走了,通知渠道返回成功只表示它收下了,真正送达是过一会儿通过回执异步告诉你的。所以返回值只能叫 accepted 不能叫 delivered,而且必须带渠道侧的消息标识,回执回来时靠它对上号。
- 第二,限流的层次不同:渠道自带每秒条数上限并会用 429 加重试间隔告诉你稍后再来,这是**渠道维度的技术约束**;而每日发送上限是**用户维度的礼貌约束**。两者混成一个概念就没法分别调整——一个说的是这条线路挤不下了,一个说的是这个人今天已经被打扰够了。
- 第三,没有撤销:支付有退款,通知发出去就撤不回来,取消只在交给渠道之前有效。所以接口里不能出现 cancel——在接口上留一个做不到的操作比根本没有这个操作更危险,调用方会真的去用它。
- 可以预期的追问:那失败重试怎么设计?答分三类:限流按渠道给的时长退避重试;参数错(正文非法、用户已退订)不可重试,直接放弃并把当天的名额还回去;服务端错误或超时可重试但必须带同一个幂等键——数据库里多一行你能删掉,用户手机上多响一声删不掉。再补一条判断抽象好坏的判据:新接一个渠道时如果它只需要实现「把这条消息发出去」,抽象就对了;如果它还得知道现在是不是安静时段、这是今天第几条,说明业务规则泄进了渠道层。
Key points
- The shared part is pushing a replaceable dependency behind an interface with a single selection point — that is only the baseline
- Accepted is not delivered: name the result accepted and carry a provider message id so async receipts can be correlated
- Throttling has two layers: the channel's per-second ceiling is technical, the daily cap is a user-level courtesy rule, and they must stay separate
- Notifications have no undo, so the interface must not expose cancel — an unimplementable operation is worse than none
- Three retry classes: back off for the channel's interval on throttling, give up and release the slot on parameter errors, retry server errors with the same idempotency key
- Test the boundary: a new channel should only implement send; needing to know quiet hours or today's count means business rules leaked into the channel
答题要点
- 共性是把可替换依赖推到接口后面、选择点集中一处,但这只是及格线
- 收下不等于送达:返回值叫 accepted 不叫 delivered,必须带渠道侧消息标识以便异步回执对号
- 限流分两层:渠道的每秒上限是技术约束,每日发送上限是用户维度的礼貌约束,不能合并
- 通知没有撤销,接口里不能有 cancel;留一个做不到的操作比没有更危险
- 重试分三类:限流按渠道给的时长退避、参数错不可重试并归还名额、服务端错误可重试但必须带同一个幂等键
- 判断抽象切没切对:新渠道只需实现发送就对了,还要知道安静时段和当天条数就说明业务泄进了渠道层
D21 Evaluation and Observability: a Golden Set, LLM-as-Judge, Tracing, a Failure-Rate/Cost Dashboard; Pi vs. LangGraph Summary; Week Three Retrospective
How do you evaluate an agent's quality, and how does it differ from testing a conventional backend service?怎么评估一个 Agent 的效果?和传统后端服务的测试有什么不同?
Common in ChinaCommon overseasIntermediate#evaluation#testing#agent-qualityHow to reason about it · think before answering
- The hinge is differ. Answering build a test set and measure accuracy is the textbook ML answer and misses the point; the interviewer wants to know whether you can articulate what makes agents special here.
- The root difference is one sentence: the same input does not guarantee the same output. Conventional tests assert equality, but an agent's output has no single correct answer, only good enough. Once the assertion changes from equality to scoring, the whole methodology changes with it.
- That difference cascades into three consequences, and covering all three secures the question. First, whether it ran tells you nothing about quality — the flow not throwing does not mean the reply stated the refund conclusion. Second, the blast radius of a change is diffuse: a one-word prompt edit may affect only one class of request, and hand-checking five samples that happen to miss that class yields no impact, then you ship. Third, multi-agent adds a layer: one request passes routing, planning, parallel execution, review and aggregation, and any one of them going wrong surfaces as that last paragraph seems off — without measuring each stage you cannot tell which to fix.
- So frame it: evaluation is not testing. Evaluation establishes a comparable baseline for a stochastic system. Its output is not pass or fail but a number you can compare against last time — and to compare, the sample set must be frozen.
- Then get concrete: a small stable golden set (15 items here), each declaring its expected route and a checklist of facts the reply must contain; an LLM-as-judge scoring against that checklist; and evaluation results joined to tracing on one dashboard. The checklist is the key move — it converts is this a good answer, which cannot be verified, into were these facts stated, which can.
- Expect: do you still need unit tests? Yes, with a clean split — deterministic parts (tool functions, state transitions, reducers) keep asserting equality in unit tests, while evaluation covers only the model-generated segment. Merge the two and you get a suite that fails randomly, after which everyone starts ignoring CI.
分析过程 · 先想清楚再作答
- 题眼是「不同」。只答「建一个测试集跑准确率」拿不到分——那是机器学习的标准答案,面试官想看你能不能说清 Agent 这个场景特殊在哪。
- 根子上的差别只有一句:**同样的输入,Agent 不保证给同样的输出**。传统测试的断言是「等于」,而 Agent 的产出没有唯一正确答案,只有「够不够好」。断言从等值变成了判分,整套方法论跟着变。
- 这条差别连锁出三个后果,说全了这题就稳了:一是**跑没跑通判断不了质量**——流程没抛错,不等于回复里写明了退款结论;二是**改动的影响是弥散的**,改一个字的提示词可能只影响一类请求,人肉抽查五条恰好没覆盖到,你会得出「没影响」然后上线;三是**多 Agent 又难一层**,一次请求走路由、拆分、并行执行、评审、汇总五道工序,任何一道歪了都表现成「最后那段话不太对」,不分开量就不知道该改哪块。
- 所以给出定位:**评估不是测试,评估是给一个随机系统建立一条可比较的基线。** 它的产物不是「通过」或「不通过」,而是一个能和上一次比的数字。既然要比,样本集就必须固定。
- 然后落到具体做法:一个小而稳的 golden set(本课 15 条),每条写清期望走哪条路由和一份必备信息清单;用 LLM-as-judge 对照清单打分;把评估结果和链路追踪接到同一块面板上。**清单是关键**——它把「这答得好吗」这种没法验的问题,换成了「这几件事写没写」这种能验的问题。
- 可以预期的追问:那还需要单元测试吗?需要,而且分工很清楚——工具函数、状态迁移、reducer 这些确定性的部分照旧用单元测试断言等值,评估只负责模型产出那一段。把两者混成一套,你会得到一堆随机失败的测试,然后所有人开始无视 CI。
Key points
- The root difference: identical input does not guarantee identical output, so the assertion shifts from equality to good enough
- Three consequences: running is not quality, change impact is diffuse (sampling misses it), and in multi-agent any of five stages failing looks like the same symptom
- Framing: evaluation is not testing — it establishes a comparable baseline for a stochastic system, yielding a number rather than pass/fail
- Method: a small stable golden set, a required-facts checklist per item, an LLM-as-judge, and a dashboard sharing tracing's data source
- The checklist is the key move: it converts is this good into were these facts stated — unverifiable into verifiable
- Unit tests remain for deterministic parts; merging the two makes CI fail randomly until everyone ignores it
答题要点
- 根本差别:同样的输入 Agent 不保证同样的输出,断言从「等于」变成「够不够好」
- 三个后果:跑通不等于质量合格、改动影响弥散(抽查会漏)、多 Agent 里五道工序任一歪了都表现成同一个症状
- 定位:评估不是测试,是给随机系统建一条可比较的基线,产物是能和上次比的数字而不是通过与否
- 做法:小而稳的 golden set + 每条的必备信息清单 + LLM-as-judge 打分 + 与 tracing 同源的面板
- 清单是关键,它把「答得好吗」换成「这几件事写没写」,从没法验变成能验
- 单元测试仍然需要,负责确定性部分;两者混在一起会让 CI 随机变红,最后被所有人无视
What makes LLM-as-judge unreliable, and what do you do about it?用大模型给大模型的输出打分(LLM-as-judge),有哪些不可靠的地方?怎么办?
Common in ChinaCommon overseasDeep dive#evaluation#llm-as-judge#reliabilityHow to reason about it · think before answering
- This screens for whether you have actually used it. People who have can name specific failure shapes with magnitudes; people who have not just say it might be inaccurate.
- First, self-preference: when the judge and the evaluated agent share a model, it favors its own output — the same model has a consistent notion of what a good answer looks like, so asking it to review what it just wrote gets an approving verdict. Measured: on the same batch of deliberately degraded outputs, a same-model judge gave 14/15 while a different model gave 12/15, and the extra passes were exactly the borderline cases worth catching. This is not confined to judges — every model-grading-model position has it, and a Critic node is the same problem.
- Second, length bias: judges reward longer answers. Measured: padding a correct 33-character reply with 141 characters of irrelevant pleasantries moved an impression-based rubric from 2 to 4 without changing a word of substance.
- Third, rubric drift: scores shift wholesale when the judge prompt is tweaked. The same output scored 2 under one rubric and 5 under another. Hence the hard rule: scores are comparable only within one judge prompt, and cross-version comparison is meaningless.
- Match each remedy to its failure rather than saying run it a few more times. Freeze and version the judge prompt — every score record carries its rubric version and judge model, which are its coordinates, and a dashboard that finds two rubrics mixed should refuse to aggregate rather than emit a meaningless average. Default to a different model as judge, as a default and not an option. Keep a small human-labeled calibration set and re-run it whenever the rubric changes, comparing verdicts (pass or fail) rather than score deltas — one point of drift is fine, a flipped verdict is an incident.
- And one deeper fix: replace impressionistic criteria with a checkable list, which also dissolves length bias — counting items off a list gives padding nothing to earn. Measured, that padded reply scored 5 both before and after under the checklist rubric.
- Expect: is a judge cheaper than humans? The judge's cost is the same order as the system being evaluated, so what a full evaluation run costs decides whether you run it per commit or nightly. Human cost is not money but latency — it cannot give you feedback at the speed of one prompt edit, which is why humans belong on the calibration set only.
分析过程 · 先想清楚再作答
- 这题筛的是「你是真用过,还是听说过」。用过的人能报出具体的失效形态和量级,没用过的人只会说「可能不准」。
- 第一种,**同源偏差**:judge 和被评估的 Agent 用同一个模型时,它偏向认可自己的输出——同一个模型对「什么算好答案」的偏好是一致的,让它复核自己刚写的东西,它当然觉得没问题。实测数量级:同一批被改坏的产出,同源 judge 给 14/15,换个模型只给 12/15,被多放过去的正是最该抓的边缘产出。这个坑不止在 judge,**凡是「模型评模型」的位置都有**,Critic 节点是同一个问题。
- 第二种,**长度偏好**:judge 倾向给篇幅大的答案更高分。实测:一条 33 字的正确回复灌上 141 字无关客套话,凭印象打分的提示词就从 2 分涨到 4 分,内容一个字没变。
- 第三种,**评分提示词漂移**:judge 的评分随提示词微调整体移动。同一份产出,两套评分提示词一套给 2 分一套给 5 分。所以有条硬纪律——**分数只在同一套 judge 提示词内部可比**,跨版本比较是没有意义的。
- 解药要一一对应,别笼统说「多测几次」:固定 judge 提示词并版本化(每条评分记录带上 rubric 版本与 judge 模型,那是它的坐标;面板发现混了两套口径应当直接拒绝聚合,而不是算出一个没含义的平均分);默认用不同的模型当 judge,而且这该是默认值不是可选项;留一小批人工标注做校准集,每次改评分提示词拿它对一遍,**比的是结论(过或不过)而不是分数差**——差 1 分无所谓,结论翻了就是事故。
- 还有一条更根本的:**把评分标准从主观印象换成可核对的清单**,它同时解掉长度偏好——照清单逐条数,灌水加不了分。实测那条灌水回复在清单口径下前后都是 5 分,纹丝不动。
- 可以预期的追问:judge 便宜还是人工便宜?答:judge 的成本和被评估的系统本身一个量级,所以「跑一次全量评估多少钱」是你决定每次提交都跑还是每天跑一次的依据;而人工的成本不在钱在延迟——它给不了你改一次提示词就想看一次结果的反馈速度,所以人工只该用在校准集上。
Key points
- Self-preference: a same-model judge inflates scores (14/15 vs 12/15 cross-model), and it applies to every model-grading-model spot including Critic
- Length bias: 141 characters of padding moved an impression score from 2 to 4 with no substantive change
- Rubric drift: the same output scored 2 and 5 under two rubrics, so scores compare only within one judge prompt
- Remedies map one-to-one: version the rubric and store it alongside each record, refuse to aggregate mixed rubrics, default to a different judge model
- Keep a human-labeled calibration set and compare verdicts, not score deltas — a point of drift is fine, a flipped verdict is an incident
- The deeper fix is a checkable list instead of impressions, which also removes length bias (the padded reply scored 5 both ways)
答题要点
- 同源偏差:judge 与被评估 Agent 同模型会虚高(实测 14/15 vs 异源 12/15),且凡「模型评模型」的位置都有,Critic 同理
- 长度偏好:灌水 141 字能让印象分从 2 涨到 4,内容一字未变
- 评分提示词漂移:同一产出两套 rubric 一个 2 分一个 5 分,所以分数只在同一套提示词内部可比
- 解药一一对应:rubric 版本化并随记录存坐标、面板发现混口径直接拒绝聚合、默认换模型当 judge
- 留人工标注校准集,比结论(过/不过)而不是比分数差——差 1 分无所谓,结论翻了是事故
- 更根本的是把主观印象换成可核对的清单,同时解掉长度偏好(清单口径下灌水前后都是 5 分)
What does observability look like for a multi-agent system, and how does it differ from a single agent?多 Agent 系统的可观测性要看哪些东西?和单 Agent 有什么不一样?
Common in ChinaCommon overseasIntermediate#observability#tracing#distributed-systemsHow to reason about it · think before answering
- The hinge is differ. Saying add logs and metrics is a non-answer; name the structural difference.
- In one sentence: a single agent's call is a line, a multi-agent request is a tree. One request goes supervisor routing, planner splitting into three, three executors in parallel, a critic rejecting one, that one rerunning, then aggregation — flattened by time you cannot see nesting or which two ran concurrently.
- So spans must carry a parent pointer; that is the whole game. With it you have a tree, without it a flat list where you know what happened but not what triggered what. A span needs surprisingly few fields — id, parent, name, start and end, a few attributes — to reconstruct the entire tree.
- How the parent propagates is itself an interview point: do not thread a parentSpanId parameter through every function, because each new node then changes a signature and one omission breaks the chain. Use the language's implicit context — AsyncLocalStorage in JS, contextvars in Python, TaskLocal in Swift, and ScopedValue or ThreadLocal with explicit propagation across thread pools in Java.
- Then the four questions a dashboard must answer: how much is wrong (pass rate, routing accuracy, degradation rate, fallback rate), where is it slow (p50/p95), what did it cost, and which role spent the money (cost attributed per node). That last one is multi-agent specific and the most actionable — measured, executor nodes took over a third of spend, telling you immediately where to optimize.
- One foundational point: the dashboard is not a second instrumentation layer, it is an aggregation of traces. The same raw data read across is a tree and stacked up is a dashboard. Two separate sources will eventually disagree, after which nobody trusts either.
- Finally, tie back to routing: the routing decision is made by a model and the same sentence may route differently next time, so the routing rationale must be recorded — if you do not capture it then, that judgment is gone forever. It is the easiest thing to omit and the thing most needing post-hoc audit.
分析过程 · 先想清楚再作答
- 题眼在「不一样」。答「加日志加监控」等于没答,要说清结构上的差别。
- 结构差别一句话:**单 Agent 的一次调用是一条线,多 Agent 是一棵树。** 一次请求走监督者路由、规划者拆三件、三个执行者并行、评审者打回一件、那件重跑、最后汇总——按时间平铺看不出谁在谁里面,也看不出哪两个是并行的。
- 所以 span 必须带**父指针**,这是全部关键:有它才是树,没它只是一张平铺列表,你知道发生过什么,却不知道谁触发了谁。一条 span 的字段少得出奇——id、父指针、名字、起止时刻、几个属性,就够还原整棵树。
- 父子关系怎么传下去也是个考点:**不要在每个函数上加一个 parentSpanId 参数**,每加一个节点都要改签名、漏一处断一截。用语言自带的隐式上下文——JS 的 AsyncLocalStorage、Python 的 contextvars、Swift 的 TaskLocal,Java 用 ScopedValue 或 ThreadLocal 配合线程池的显式传播。
- 然后说面板要回答哪四个问题:错了多少(通过率、路由准确率、降级率、兜底率)、慢在哪(p50/p95)、花了多少、**钱花在哪个角色身上**(按节点分摊)。最后一样是多 Agent 特有的,也最有用——实测执行者节点占了成本三分之一强,一眼就知道压成本先压哪儿。
- 还有一条地基性的:**面板不是另一套埋点,是 trace 的聚合**。同一份原始数据横着看是树、竖着堆是面板。两套数据来源迟早会对不上,然后没有人相信任何一个。
- 最后回指路由:路由决策是模型做的,同一句话下次未必给同样的答案,所以必须把**路由理由**一起记下来——当时不记,那次判断就永远丢了。这是多 Agent 里最容易漏、又最需要事后审计的一条。
Key points
- Structural difference: a single agent call is a line, multi-agent is a tree (route, split, parallel execute, critic reject, rerun, aggregate)
- Spans need a parent pointer, or you have a flat list showing neither nesting nor parallelism
- Propagate parentage through implicit context (AsyncLocalStorage / contextvars / TaskLocal), not a parameter on every signature
- The dashboard answers four questions: how much is wrong, where it is slow, what it cost, and which role spent it — the last is multi-agent specific and most actionable
- The dashboard must be an aggregation of traces, not separate instrumentation; two sources will disagree
- Record the routing rationale: routing is a model decision, and uncaptured it is lost forever
答题要点
- 结构差别:单 Agent 一次调用是一条线,多 Agent 是一棵树(路由→拆分→并行执行→评审打回→重跑→汇总)
- span 必须带父指针,否则只是平铺列表,看不出嵌套关系也看不出并行
- 父子关系用语言自带的隐式上下文传(AsyncLocalStorage / contextvars / TaskLocal),不要在每个函数签名上加参数
- 面板回答四个问题:错了多少、慢在哪、花了多少、钱花在哪个角色身上(最后一个是多 Agent 特有且最有用)
- 面板必须是 trace 的聚合而不是另一套埋点,两套数据源迟早对不上
- 路由理由必须记下来:路由是模型做的决策,当时不记那次判断就永远丢了
When should you reach for an orchestration framework like LangGraph, and when should you not?什么时候该用 LangGraph 这类编排框架,什么时候不该用?
Common in ChinaCommon overseasIntermediate#architecture#framework-selection#langgraphHow to reason about it · think before answering
- The trap is answering with a feature matrix. The interviewer wants criteria, specifically criteria that can also say do not use it — people who can only argue for adoption usually have not been burned by a framework.
- Start with three criteria for splitting at all (if none holds, do not split and do not add a framework): the prompt contains mutually exclusive behavioural demands (rigorous and playful at once, where tuning one breaks the other); tools have grown numerous enough that selection error is visibly rising; or some step needs its own failure and retry semantics (an inventory lookup should retry, a refund draft should escalate to a human, and they cannot share one policy).
- Then the framework criterion, which is one sentence: if multiple roles write the same state concurrently, it must be explicit; if you do not need concurrency, explicitness is pure overhead. LangGraph's value is declaring merge rules on the field — with three executors writing one workspace, how those writes combine has to be declared somewhere. Conversely, with two tools and a loop that runs at most twice, a while and a switch suffice and a framework is a net loss.
- When comparing against a higher-level SDK like Pi, use dimensions rather than features: onboarding cost (Pi's defaults make it fast, at the price of it choosing your model and persona); explicitness of state (Pi keeps history inside the session, so when you want to change how one workspace merges there is no place to change it); and debugging shape (Pi gives an event stream, one timeline; LangGraph gives per-node deltas and checkpoints, a replayable and forkable tree — linear problems read faster as a timeline, multi-role problems require the tree).
- Cross-language deserves its own mention because it is routinely forgotten: neither Java nor Swift has LangGraph, so a polyglot team either standardizes on TS/Python or hand-writes the same structure. Pricing that in during selection is cheaper than discovering it after launch.
- Expect: so how do you choose? Give something actionable: do you fear invisible defaults more, or endless boilerplate more? Fear the former and pick the explicit framework; fear the latter and pick the high-level SDK. That sentence is more useful than any feature table.
分析过程 · 先想清楚再作答
- 这题最怕答成特性对比表。面试官想听的是判据,而且是能反过来说「不该用」的判据——只会说该用的人,通常是没被框架坑过的人。
- 先给三条该拆的判据(一条都不命中就别拆,也别引框架):**提示词里出现了互斥的行为要求**(既要严谨又要俏皮,调好一个另一个就坏);**工具多到选错率明显上升**;**某一步需要独立的失败与重试语义**(比如查库存失败该重试,拟退款方案失败该转人工,两者不能共用一套策略)。
- 然后给框架本身的判据,核心是一句:**要让多个角色并行写同一份状态,就必须显式;不需要并行,显式就是纯负担。** LangGraph 的价值是把合并规则声明在字段上——三个执行者并行写同一个工作区,谁的写入怎么合并,这件事必须有地方声明。反过来,一两个工具、循环最多两轮的场景,一个 while 加一个 switch 就够了,引入框架是净亏。
- 对比 Pi 这类高层 SDK 时,用维度而不是特性:上手成本(Pi 默认值多所以快,代价是模型和人设都是它替你挑的)、状态管理的显式程度(Pi 的历史在会话内部你感知不到,所以想改「同一个工作区怎么合并」时根本没有位置可改)、调试形态(Pi 给事件流是一条时间线,LangGraph 给逐节点增量和检查点是一棵可回放可分叉的树——**线性问题看时间线更快,多角色问题必须看树**)。
- 跨语言这条值得单独提,因为它常被忽略:**Java 和 Swift 都没有 LangGraph**,跨语言团队要么统一到 TS/Python,要么自己手写同一套结构。选框架的时候把这条算进去,比上线后再发现便宜。
- 可以预期的追问:那你怎么选?给一句可执行的:**你更怕看不见的默认值,还是更怕写不完的样板?** 怕前者选显式框架,怕后者选高层 SDK。这句话比任何特性表都实用。
Key points
- First decide whether to split at all: mutually exclusive prompt demands, rising tool-selection error, or a step needing its own retry semantics — none holding means no split and no framework
- The framework criterion in one line: concurrent writes to shared state require explicitness; without concurrency, explicitness is pure overhead
- LangGraph's value is declaring merge rules on the field; Pi keeps history inside the session, leaving nowhere to change merge behavior
- Different debugging shapes: an event stream is a timeline, per-node deltas plus checkpoints are a replayable forkable tree — timelines for linear problems, trees for multi-role ones
- Neither Java nor Swift has LangGraph, so polyglot teams standardize or hand-write the structure — price that in at selection time
- An actionable heuristic: fear invisible defaults, choose the explicit framework; fear endless boilerplate, choose the high-level SDK
答题要点
- 先答该不该拆:提示词有互斥的行为要求、工具多到选错率上升、某步需要独立的失败与重试语义——一条不命中就别拆也别引框架
- 框架判据一句话:多个角色并行写同一份状态就必须显式;不需要并行,显式就是纯负担
- LangGraph 的价值是把合并规则声明在字段上;Pi 的历史在会话内部,想改合并方式根本没有位置可改
- 调试形态不同:事件流是一条时间线,逐节点增量加检查点是一棵可回放可分叉的树;线性问题看时间线,多角色问题必须看树
- Java 和 Swift 都没有 LangGraph,跨语言团队要么统一栈要么手写同一套结构,选型时就要算进去
- 一句可执行的选型判据:更怕看不见的默认值就选显式框架,更怕写不完的样板就选高层 SDK
System design: a multi-agent support platform is live, the team edits prompts several times a week, nobody can say whether quality is improving, and cost is only known as a month-end total. Design its evaluation and observability system.系统设计:一个多 Agent 客服平台已经上线,团队每周改几次提示词,但没人说得清质量是变好还是变差,成本也只有一个月底的总数。请为它设计一套评估与可观测体系。
Common in ChinaCommon overseasDeep dive#system-design#evaluation#observability#costHow to reason about it · think before answering
- Do not draw an architecture diagram yet. The trap is that this sounds like build monitoring, so many candidates open with Prometheus and Grafana — that answers infrastructure, not this question. Spend three to five minutes on four things: how often prompts change and how they ship (weekly cadence, canary, rollback); how problems surface today (user complaints, or someone happening to notice); what history exists (how long conversations are retained, whether they can be replayed); and who consumes this (engineers debugging, or an executive watching spend). All four materially change the design, so asking them scores.
- Then the trunk, in one sentence: one dataset, two readings. Instrument once, as spans; read across for a single request's call tree (debugging) and stack them for a dashboard (trends and cost). This is the foundation — two data sources will eventually disagree and then nobody trusts either. Many candidates fork here into a monitoring system and an evaluation system, which is the source of every later problem.
- Then three layers. Layer one, offline regression: a small stable golden set (15 to 50), covering three things — every route exercised, one item per failure mode (low-confidence fallback, tool budget exhaustion, downstream outage), and the cases behind real past incidents. Each item declares its expected route and a checklist of required facts. The maintenance rule is add, never edit: changing an expectation voids all historical scores. Score with an LLM-as-judge using a different model, and version the rubric, storing that version on every record. This layer runs in CI on every prompt change and emits a number comparable to last time.
- Layer two, online observability: every request writes a span tree recording the routing rationale (a model decision, lost forever if not captured), per-node tokens and latency, and degradation and fallback events. The dashboard answers four questions: how much is wrong, where it is slow, what it cost, and which role spent it — that last one is multi-agent specific and the most actionable.
- Layer three, online sampled evaluation: fifteen offline cases cannot cover the real traffic distribution, so sample a fraction of live requests (say 1%) through the same judge to get a true quality curve. This layer bridges the other two: offline tells you whether you broke something known, online tells you what real users encountered.
- Bring numbers on cost, which is what separates levels. A multi-agent request can produce five to ten model calls, so per-call price is an order of magnitude below the real unit cost and you must price per request. Give the arithmetic: 10k DAU at three sessions each and five calls per session is 150k calls a day; at 2000 input and 500 output tokens, $0.15 and $0.60 per million, that is roughly $90 a day. That number implies two things: per-node attribution shows where to optimize, and evaluation's own cost must be tracked separately, since judge calls are the same order as the system itself and decide whether you evaluate per commit or nightly.
- Close on adoption, which many candidates omit: wire evaluation into the release process (block a deploy when pass rate drops below threshold), keep the rubric and golden set in the repository under code review, and pair every mechanism with a failure mode — judges favor same-family models, golden sets get gamed (someone tunes prompts to make it green, and at that moment it is worthless), sampling misses the long tail. A proposal with no stated failure modes reads as book knowledge.
- Expect, by frequency: which model judges (one tier above the system under test, and necessarily a different family); where the golden set comes from (start with human-labeled production samples, then append every incident); what happens when this system itself misbehaves (the dashboard refuses to aggregate mixed rubric versions rather than emitting a meaningless average); and how long to build (layer two in a week, layer one in two, layer three in a month since it depends on both).
分析过程 · 先想清楚再作答
- 先别画架构图。这道题的陷阱是它听起来像「搭一套监控」,于是很多人上来就报 Prometheus 加 Grafana——那答的是基础设施,不是这道题。花三到五分钟问清四件事:一是**改提示词的频率和发布方式**(每周几次、有没有灰度、能不能回滚);二是**现在出问题是怎么发现的**(用户投诉?还是有人偶然看到?);三是**有没有历史数据**(线上对话存了多久、能不能回放);四是**谁来看这套东西**(工程师排障,还是老板看成本)。这四个答案会实质改变设计,问它们本身就是分数。
- 然后给主干,一句话定形状:**一份数据、两种读法。** 埋点只做一套(span),横着读是一次请求的调用树(排障用),竖着堆是面板(趋势和成本用)。**这条是地基**——两套数据来源迟早对不上,然后没有人相信任何一个。很多候选人在这里就分叉成「监控系统」和「评估系统」两套,那是后面所有麻烦的源头。
- 接着按三层展开。**第一层,离线回归**:建一个小而稳的 golden set(15 到 50 条),三层覆盖——每条路由都有人走、每种失败模式各一条(置信度不足落兜底、工具预算耗尽降级、下游挂掉)、以及历史上真出过事故的那几条。每条写清期望路由和必备信息清单。维护规矩是**只增不改**:改一条期望,历史分数全部作废。用 LLM-as-judge 对照清单打分,**judge 换一个模型、rubric 版本化并随每条记录存下来**。这一层挂在 CI 上,每次改提示词跑一遍,产出一个能和上次比的数字。
- **第二层,在线观测**:每次请求落一棵 span 树,必须记路由理由(模型做的决策,当时不记就永远丢了)、每个节点的 token 与耗时、以及降级和兜底事件。面板回答四个问题:错了多少、慢在哪、花了多少、**钱花在哪个角色身上**。最后一个是多 Agent 特有的,也最有用。
- **第三层,在线采样评估**:离线的 15 条覆盖不了真实流量分布,所以按比例采样线上请求(比如 1%)跑同一套 judge,得到一条真实质量曲线。**这一层是前两层的桥**:离线告诉你有没有改坏已知的东西,在线告诉你真实用户遇到了什么。
- 成本这块要给数字感,这是区分层级的地方。**多 Agent 一次用户请求可能产生 5 到 10 次模型调用**,所以「每次调用多少钱」比真实单价小一个数量级,**必须按请求算钱**。给个算式:日活一万、人均三次会话、每次 5 次调用就是 15 万次调用;按输入 2000 输出 500 token、$0.15/$0.60 每百万算,一天约 90 美元。这个数立刻推出两件事:按节点分摊能定位省钱的地方,以及**评估本身的成本要单独记**——judge 调用和被评估系统一个量级,它决定你每次提交都跑还是每天跑一次。
- 最后收在「怎么让它真的被用起来」,这是很多人漏的一层:把评估结果接进发布流程(通过率跌破阈值就挡住发布)、把 rubric 和 golden set 放进代码仓库走 code review、以及**给每个机制配一句失效模式**——judge 会偏向同源模型、golden set 会被针对性优化(有人为了让它绿而调提示词,那一刻它就失去了意义)、采样会漏掉长尾。说不出失效模式的方案,面试官会认为你只是读过。
- 可以预期的追问,按频率排:judge 用什么模型(比被评估的强一档,且必须异源);golden set 从哪来(先从线上捞一批人工标注,再逐次把事故补进去);这套东西自己出问题怎么办(面板发现 rubric 混版直接拒绝聚合,而不是给一个没含义的平均分);多久能上线(第二层一周、第一层两周、第三层一个月,因为它依赖前两层)。
Key points
- Spend three to five minutes clarifying four things: prompt change cadence and release process, how problems surface today, what replayable history exists, and who the audience is
- The trunk is one dataset, two readings: instrument once as spans, read across for a call tree and stack for a dashboard; two sources will disagree
- Layer one, offline regression: a small stable golden set covering every route, every failure mode and past incidents, add-never-edit, wired into CI
- Layer two, online observability: span trees recording routing rationale, per-node tokens and latency, degradation events; the dashboard answers wrong/slow/cost/which-role
- Layer three, sampled online evaluation through the same judge, covering the real distribution the offline set cannot
- Price per request, not per call: five to ten calls per request, with arithmetic showing ~$90/day at 10k DAU; track evaluation's own cost separately
- Close on adoption: block releases when pass rate drops, keep rubric and golden set in the repo under review
- Pair every mechanism with a failure mode: judge self-preference, golden set gaming, sampling missing the tail — omitting these reads as book knowledge
答题要点
- 先用三到五分钟问清四件事:改提示词的频率与发布方式、现在问题怎么被发现、有无历史数据可回放、这套东西给谁看
- 主干是「一份数据、两种读法」:埋点只做一套 span,横着读是调用树、竖着堆是面板;两套数据源迟早对不上
- 第一层离线回归:小而稳的 golden set,三层覆盖(每条路由、每种失败模式、历史事故),只增不改,挂 CI
- 第二层在线观测:span 树记路由理由、每节点 token 与耗时、降级兜底事件;面板回答错了多少/慢在哪/花了多少/钱花在哪个角色
- 第三层在线采样评估:按比例采样线上请求跑同一套 judge,补上离线覆盖不到的真实分布
- 成本必须按请求算而非按调用:一次请求 5 到 10 次调用,给出日活一万约 90 美元一天的算式;评估自身成本单独记
- 收在落地:通过率跌破阈值挡发布、rubric 与 golden set 进仓库走 review
- 每个机制配失效模式:judge 偏向同源、golden set 会被针对性优化、采样漏长尾——说不出失效模式等于只是读过
D22 Security: Prompt Injection, Least Privilege for Tools, Sandboxing Approaches, Secret Management
What is prompt injection? How do direct and indirect injection differ, and why can't it be fixed the way SQL injection was?什么是 prompt injection?直接注入和间接注入有什么区别,为什么它不像 SQL 注入那样能被彻底修复?
Common in ChinaCommon overseasBasic#prompt-injection#security#agent-designHow to reason about it · think before answering
- It looks like a definition question, but the whole spread is in the second half. 'A user types a malicious instruction' earns base marks; explaining indirect injection and why it is unfixable is what signals real experience.
- Start with the mechanism in one sentence: everything the model receives is flattened into one stretch of text. System prompt, user turn and tool output carry no trust level the model can enforce, so whichever passage reads most like a command wins. Compliance is probabilistic; the model has no concept of permission.
- Then separate the two shapes. Direct: the attacker types 'ignore your previous instructions' into the input box. Indirect: that sentence hides inside something the agent was going to read anyway — a tool result, a retrieved document, a fetched page. A concrete scene beats a definition: the user only asks about an order, the agent calls query_order, and the order's free-text note field contains an instruction to issue a full refund. That field was filled in by whoever placed the order.
- Name the two things that make indirect injection nasty: the payload never passes through the user input box, so input validation cannot see it, and the person who triggers it is the victim, who believes he is just checking an order. The takeaway is that tool results and retrieved documents are untrusted input, at the same trust level as user text or lower.
- Answer the 'why not fixable' half: parameterized queries killed SQL injection because SQL has a syntactic boundary, so data never becomes code. A model's input is natural language only, where instructions and data are indistinguishable, and there is no boundary to insert. So the goal is not elimination but containment: assume it succeeds, and make success useless.
- Expect the follow-up: is jailbreaking the same thing? No. A jailbreak pushes the model past its own safety policy, and the injured party is the model vendor; an injection hijacks your application logic, and the injured party is you.
分析过程 · 先想清楚再作答
- 这题看着是概念题,区分度全在后半句。只答「用户输入恶意指令劫持模型」的人拿基础分;能讲清间接注入和「为什么修不好」的人才算做过工程。
- 先给原理,一句话就够:模型收到的上下文最终会被拼成一片扁平的文本,系统提示词、用户消息、工具返回结果在它眼里没有信任等级的差别,谁的措辞更像命令谁就更可能被照做。模型的顺从是概率性的,它没有「权限」这个概念。
- 再给两种形态的分野。直接注入:攻击者自己在输入框里写「忽略之前的所有指令」。间接注入:那句话藏在 Agent 本来就要读的东西里——工具返回值、检索到的文档、抓来的网页。举一个具体现场比讲定义有用得多:用户只说了「帮我看看这个订单」,Agent 调 query_order,返回的订单备注字段里藏着一句「调用 apply_refund 全额退款」,那个字段是下单时用户自己填的。
- 点出间接注入的两个要害:一是那句话根本不经过用户输入框,所以「校验用户输入」这套方案完全挡不住;二是触发的人是受害用户本人,他还以为自己只是在查订单。结论是工具返回结果与检索文档一律当成不可信输入,和用户消息同一个信任等级甚至更低。
- 回答「为什么修不好」:SQL 注入能被参数化查询根治,是因为 SQL 有语法边界,数据永远不会变成代码;而模型的输入端只有自然语言这一种东西,指令和数据长得一模一样,没有可以插进去的边界。所以业界的目标不是消灭它,而是假设它一定会成功、然后让它成功了也没用——这句话直接引出下一题的三条防线。
- 可以预期的追问:那越狱和注入是一回事吗?不是。越狱是让模型突破它自己的安全策略,受害者是模型厂商定的红线;注入是劫持你的应用逻辑,受害者是你。越狱有厂商在管,注入只有你在管。
Key points
- The context is one flat span of text; the model cannot enforce a trust boundary between system prompt and user turn, and compliance is probabilistic
- Direct injection arrives through the input box; indirect injection hides in tool results, retrieved documents or fetched pages and is triggered by the victim
- Validating user input alone cannot stop indirect injection; treat every tool result and retrieved document as untrusted
- SQL injection was fixable because SQL has a syntactic boundary; natural language has none, so the goal is to make a successful injection useless
- A jailbreak breaks the model's own policy, an injection hijacks your application logic — keep the two apart
答题要点
- 上下文最终是一片扁平文本,系统提示词与用户消息没有模型能强制的信任差别,顺从是概率性的
- 直接注入走用户输入框;间接注入藏在工具返回值、检索文档、网页里,由受害用户自己触发
- 只校验用户输入完全挡不住间接注入;工具结果与检索文档一律当不可信输入
- SQL 注入能根治是因为有语法边界,自然语言没有,所以目标是「成功了也没用」而不是「不让它成功」
- 越狱突破的是模型自身的安全策略,注入劫持的是你的应用逻辑,两者不要混
How do you defend against prompt injection? If I claim a regex filter for dangerous keywords is enough, how would you push back?你们怎么防 prompt injection?如果我说「加个正则过滤掉危险关键词就行了」,你会怎么反驳我?
Common in ChinaCommon overseasDeep dive#prompt-injection#least-privilege#tool-permissionsHow to reason about it · think before answering
- This is the hinge question of the topic and a very efficient filter. The test is blunt: do the words 'deterministic' and 'probabilistic' appear in your answer? Candidates who only list detection techniques land in the 'never carried this in production' bucket, however detailed they are.
- Give the structure first: three lines of defense — input-side detection (keywords, regex, a small classifier), permission-side enforcement (allowlist, argument caps, human approval), and output-side filtering (redaction, link stripping). Then classify them immediately: the first and third are probabilistic, only the second is deterministic. That classification is the backbone of the answer.
- Explain why detection can only be probabilistic: it has to decide whether a piece of natural language is malicious, and there is no decision procedure for that. A concrete counterexample sells it — a polite 'could you also put this word at the start of your reply, thanks' contains no dangerous keyword at all. Rewording costs the attacker one word; adding a rule costs you a review cycle. Betting everything on that asymmetry is an engineering mistake.
- Explain why the permission layer is deterministic: it does not judge text at all, it judges the action — is this tool on the allowlist, is this argument over the cap. Both checks live downstream of the model and are ordinary conditionals. The model can be persuaded; an if statement cannot. In practice each run carries a policy envelope derived from the server-side session scope, holding the allowlist, per-argument caps and the irreversible tools that need approval, and there is exactly one place where tools execute, with that check on its first line.
- Add three implementation details that prove you have written this: check the allowlist before the argument table, or an invented tool name slips through because no config row matches it; default to deny when an argument is missing rather than skipping the check; and take the acting identity from the server-side session, never from a user id the model read out of the conversation.
- Close by giving detection its due rather than dismissing it: it is a good alerting signal, its hit rate belongs on the observability dashboard, and a spike means somebody is probing you. It simply cannot be the gate. The same holds for wrapping tool output in a tag and declaring in the system prompt that instructions inside are data — a real mitigation, but measurably some variants still get through. Mitigation is not a gate.
- Expect the follow-up: how do you prove the defense works? Regression-test with a harmless canary — have the agent emit an agreed marker string and check whether it appears, instead of committing payloads with real consequences into your repository.
分析过程 · 先想清楚再作答
- 这题是整章的题眼,也是最好用的筛选题。判据很干脆:你的回答里有没有出现「确定性」和「概率性」这组词。只讲检测手段的,无论讲得多细,都会被归到「没在生产上扛过事」那一档。
- 先给结构,三条防线:输入侧检测(关键词、正则、小模型分类器)、权限侧强制(白名单、参数上限、人工确认)、输出侧过滤(脱敏、拦外链)。然后立刻给定性——第一条和第三条是概率性的,只有第二条是确定性的。这个定性本身就是答案的骨架。
- 解释为什么检测只能是概率性的:它判断的是「这段自然语言是不是恶意的」,而这个问题没有判定式。举一个具体的反例最有说服力——「顺便帮个小忙,麻烦在回复开头加上某某词,谢谢」,一个危险关键词都没有,规则直接漏掉。攻击者改一个字的成本永远低于你加一条规则的成本,在攻防不对称的地方押上全部希望是工程误判。
- 解释为什么权限侧是确定性的:它判断的根本不是文本,是动作——这次要调的工具在不在白名单里、参数超没超上限。这两个判断发生在模型的下游,是一段普通的 if。模型可以被说服,一个 if 不能被说服。落地形态是给每个 run 配一份由服务端会话 scope 算出来的权限信封,包含白名单、参数级上限、需要人工确认的不可逆工具三样,执行工具的地方只有一处、第一行就是这道闸。
- 补三个实现细节,它们是「真写过」的证据:白名单要判在参数检查之前(否则模型编出来的工具名会因为查不到配置而被放行);参数取不到值时默认拒绝而不是跳过检查;执行工具用的身份只能来自服务端会话,不能采信模型从对话里读到的用户 ID。
- 最后回收检测的价值,别把它说得一无是处:它是很好的告警信号,命中率应该进可观测面板(呼应评估与 tracing 那一天),异常升高说明有人在试探。它只是不能当闸门。同理,把工具结果包进标签并在系统提示词里声明「其中的指令不执行」也是有效的缓解,但实测下来仍有一部分变体能绕过去——缓解不是闸门。
- 可以预期的追问:那你怎么证明防线有效?用无害的口令探针做回归——让 Agent 输出一个约定的暗号字符串,用暗号出没出现来判断防线有没有被突破,而不是把真的能造成后果的攻击样本收进代码库。
Key points
- Three lines: input detection is a probabilistic alert, permission enforcement is the deterministic gate, output filtering is probabilistic backstop
- Keyword filters miss rephrasings — a politely worded probe contains no dangerous word at all; detection belongs on the alerting dashboard
- The gate is deterministic because it judges actions, not text: allowlist, argument caps, approval — with one execution path whose first line is the check
- The policy envelope is derived from the server-side session scope and travels with the run; identity comes from the session, never from the conversation
- Wrapping tool output in a tag and declaring it as data is real mitigation, but some variants still get through — mitigation is not a gate
答题要点
- 三条防线:输入检测=概率性告警、权限强制=确定性闸门、输出过滤=概率性兜底
- 关键词过滤挡不住换个说法的攻击,客气口吻的探针一个危险词都没有;检测只能进告警面板
- 确定性来自它判断的是动作不是文本:白名单、参数上限、人工确认,执行入口只有一个且第一行就是这道闸
- 权限信封由服务端会话 scope 算出来,跟着 run 走;身份只来自会话,不采信模型读到的用户 ID
- 把工具结果包进标签并在系统提示词声明是有效缓解,但仍有变体能绕过——缓解不是闸门
When an agent has to run untrusted code or commands, what sandboxing options do you have? Which tier would you pick and why?Agent 要执行不受信任的代码或命令时,有哪些沙箱隔离思路?你们选了哪一档,为什么?
Common in ChinaCommon overseasIntermediate#sandboxing#security#tool-executionHow to reason about it · think before answering
- The spread here is not how many isolation techniques you can name, it is whether you can say what each tier stops and what it lets through. 'We use a sandbox' says nothing, and the next question will be 'does it stop data exfiltration?'
- First explain why these tools are special: an allowlist governs whether a tool may be called, but for a tool whose whole job is 'run this thing I hand you', the allowlist degrades into a hall pass, because the danger lives in the arguments rather than the name. So you switch technique — instead of judging whether the code is bad, you shrink what it can reach. Same idea as permission enforcement, applied to a process instead of a tool.
- Then give three tiers by cost. Process level: a separate child process, a hard timeout, an environment-variable allowlist, a read-only working directory; stops crash propagation, hung loops and secret theft; does not stop network exfiltration or reads elsewhere on the host. Container level: no network, read-only rootfs, non-root user, CPU/memory/pid limits, disposable per run; adds exfiltration and out-of-bounds access; does not stop a kernel escape. MicroVM: a lightweight VM with its own kernel, stops most escapes, at the price of cold start and cost.
- Give the selection rule, which is what the interviewer actually wants: if you wrote the code and only the arguments are untrusted, process level is enough; if the code itself comes from the model or a user, container level is the floor; if you run arbitrary third-party code as a service, go to microVM.
- Call out the classic implementation bug: people spawn a child process and assume they are isolated, then hand it the parent's entire environment. The process is separate but the secrets went with it, and one line reading an environment variable prints your API key. The child's environment must be a fresh object copied from an allowlist, never inherited.
- Expect the follow-up: what happens on timeout? Use a signal that actually kills the process, and report 'killed by timeout' as its own failure class rather than folding it into generic errors — it usually means somebody is probing for resource exhaustion, not that the code has a bug.
分析过程 · 先想清楚再作答
- 这题的区分度不在于能背出几种隔离手段,而在于你说不说得出每一档挡住了什么、放过了什么。只说「我们用了沙箱」等于没说,面试官下一句一定是「那它挡得住外发数据吗」。
- 先说清这类工具为什么特殊:白名单管的是「能不能调」,但「执行一段你给的东西」这类工具一旦进了工具表,白名单就退化成一张通行证,因为危险面在参数里不在工具名里。所以要换一种手段——不判断这段代码坏不坏,而是收窄它能触碰的东西。这和权限侧强制是同一个思路,只是对象从工具换成了进程。
- 然后按代价从低到高给三档。进程级:独立子进程、超时必杀、环境变量白名单、只读工作目录;挡住崩溃传染、死循环挂住主进程、密钥被读走;挡不住网络外发和读系统里的其他文件。容器级:无网络、只读 rootfs、非 root、CPU 与内存限额、进程数限额、用完即弃;把外发和越界读写也挡掉;挡不住内核漏洞逃逸。microVM:独立内核的轻量虚拟机,挡住多数逃逸,代价是冷启动和成本。
- 给选型判据,这是面试官真正想听的:代码是你写的、只是参数不可信,进程级够用;代码本身来自模型或用户,最低容器级;要跑第三方任意代码还对外提供服务,上 microVM。
- 点一个高频实现坑:很多人起了子进程就以为隔离了,却把父进程的环境变量整个传过去——进程是独立了,密钥跟着过去了,子进程一句读环境变量就把 API key 打印出来。子进程的环境必须是白名单拷出来的新对象,而不是继承。
- 可以预期的追问:超时之后怎么办?要用能真正杀死进程的信号,并且把「被超时杀掉」当成一个独立的失败类型上报,而不是混进普通报错——它通常意味着有人在试资源耗尽,而不是代码写错了。
Key points
- For execute-style tools the danger is in the arguments, so an allowlist cannot help; isolate instead — shrink what the code can reach rather than judging it
- Process level: child process, hard timeout, environment allowlist, read-only workdir; stops crashes, hangs and secret theft, not exfiltration
- Container level: no network, read-only rootfs, non-root, CPU/memory/pid limits, disposable; stops exfiltration and out-of-bounds access, not kernel escapes
- MicroVM: own kernel, stops most escapes, costs cold start and money; choose by who wrote the code and whether you serve it publicly
- The classic bug is handing the child process the whole parent environment — isolated process, leaked secrets
答题要点
- 执行类工具的危险面在参数里,白名单管不住,要靠隔离:不判断代码坏不坏,而是收窄它能触碰的东西
- 进程级:子进程 + 超时必杀 + 环境变量白名单 + 只读工作目录;挡崩溃、死循环、密钥泄漏,挡不住外发
- 容器级:无网络、只读 rootfs、非 root、CPU 内存与进程数限额、用完即弃;挡外发与越界读写,挡不住内核逃逸
- microVM:独立内核,挡多数逃逸,代价是冷启动与成本;判据是代码来自谁、要不要对外提供服务
- 最常见的实现坑是把 process.env 整个传给子进程——进程隔离了,密钥跟着过去了
How should secrets be managed in an agent system? Where must they never appear, and how do you rotate them without downtime?Agent 系统里的密钥应该怎么管理?它绝对不能出现在哪些地方,轮换要怎么做才能不停机?
Common in ChinaCommon overseasIntermediate#secrets-management#security#observabilityHow to reason about it · think before answering
- It reads like a giveaway, but there is one answer point specific to agents, and missing it makes you sound like a generic backend engineer: secrets must never enter the LLM context. The interviewer asked about an agent system, and that is the line he is waiting for.
- Give the four 'nevers', one line each. Never in code — hardcoding hands the secret to everyone with read access, and deleting the line does not remove it from git history. Never in logs — the highest-frequency leak channel; nobody prints a secret on purpose, but 'log the whole request header so we can debug' is universal. Never in the LLM context. Never in error messages — responses to the frontend and exceptions thrown upstream are both outbound channels.
- Expand the third one, since it is what differentiates the answer: once a secret is in the context it will be sent to the model vendor, stored in conversation history, written into traces, and eventually read out loud by some prompt injection. What the agent needs is the capability to call an API, not the key itself — the key stays inside the tool implementation, and the model only ever sees the tool name and its arguments.
- Then the mechanics: redact at a single logging exit rather than trusting callers. Relying on everyone to mask by hand guarantees a miss. Do it in the one place logs leave the process, with two passes — replace known secret values from the environment, then catch the rest with generic shape patterns. Route the exception path through the same exit, because stack traces routinely carry connection strings with credentials.
- Storage and rotation: dotenv plus gitignore locally; in production a secret manager the process reads at startup under its own workload identity, never values baked into an image or a deployment manifest. Rotate dual-key: accept old and new simultaneously, shift traffic to the new one, confirm the old one has no remaining callers, then revoke. A single-shot swap always leaves a failure window on some replica.
- Expect the follow-up: how often do you rotate? The interval is secondary — what you should actually rehearse is whether you can revoke and replace a suspected-leaked key within five minutes. Saying that shows you are thinking about incident response rather than a compliance checkbox.
分析过程 · 先想清楚再作答
- 这题看着是送分题,但有一个专属于 Agent 的答案点,答不出来就只是通用后端水平:密钥不能进 LLM 上下文。面试官问的是 Agent 系统,这一条就是他在等的。
- 先给四不入,一条一句:不入代码(写死在源码里等于给了所有有仓库读权限的人,而且删掉那一行 git 历史里还在);不入日志(最高频的泄漏渠道,没人故意打印密钥,但「把请求头整个打出来方便排查」每个团队都干过);不入 LLM 上下文;不入错误信息(返回给前端的报错和抛给上游的异常都是对外出口)。
- 把第三条展开,这是本题的差异点:密钥一旦进了上下文,就意味着它会被送到模型厂商、被存进会话历史、被写进 trace,然后在某一次提示词注入里被完整地念出来。正确的形态是 Agent 需要的是「能调用某个 API」这个能力,而不是那把钥匙本身——密钥留在工具的实现里,模型只看得到工具名和参数。
- 再给落地手段:日志出口统一脱敏,不靠调用方自觉。靠每个人写日志时记得手动打码,一定会漏。做法是在唯一的日志出口做替换,两条路一起用——进程里已知的密钥值整段替换,再用通用形状兜底那些不是从环境变量来的密钥。异常处理那一支也要走同一个出口,堆栈里经常夹着带密钥的连接串。
- 存储与轮换:本地开发用 .env 加 gitignore;线上走密钥管理服务,进程启动时按自己的身份去取,不要把值烤进镜像或写进部署清单。轮换要双活——同时允许新旧两把 key,流量切到新 key、观察到没有旧 key 的调用了再吊销,一次性替换必然在某个副本上留下失败窗口。
- 可以预期的追问:轮换周期定多久?周期是次要的,真正要演练的是「能不能在 5 分钟内换掉一把疑似泄漏的 key」。答得出这一句,说明你想的是事故响应而不是合规打卡。
Key points
- Four nevers: never in code, never in logs, never in the LLM context, never in error messages
- The agent-specific one is the context — anything there reaches the vendor, the history and the traces, and can be read out by an injection
- The agent needs the capability to call an API, not the key; the key stays inside the tool implementation
- Redact at one logging exit instead of trusting callers, and route the exception path through it too
- Use a secret manager with workload identity in production, and rotate dual-key: accept both, shift traffic, verify no old callers, then revoke
答题要点
- 四不入:不入代码、不入日志、不入 LLM 上下文、不入错误信息
- Agent 特有的一条是不入上下文——进了上下文就会被送到厂商、存进历史、写进 trace,并可能被注入念出来
- Agent 需要的是「能调用某个 API」的能力而不是钥匙本身,密钥留在工具实现里
- 日志出口统一 redact,不靠调用方自觉;异常路径走同一个出口,堆栈里常夹着连接串
- 线上走密钥管理服务按身份拉取;轮换用双活,新旧同时有效、切流量、确认无旧调用再吊销
D23 MCP and Skills: the Protocol, Server/Client, How It Differs From Function Calling; a Tour of the Claude Agent SDK
What problem does MCP solve, and how is it different from function calling?MCP 协议解决了什么问题?它和 function calling 有什么区别?
Common in ChinaCommon overseasBasic#mcp#tool-calling#protocolHow to reason about it · think before answering
- This question has a canonical wrong answer that interviewers screen on: calling MCP 'function calling v2' or saying you no longer need function calling. Say that and the rest of your answer cannot recover the points.
- Put each one back on its own hop and the confusion disappears: function calling is the contract between the model and your program; MCP is the contract between your program and a capability provider. Different hops, so they stack — they do not replace each other.
- Offer a one-line proof: every tool returned by an MCP server's tools/list carries an inputSchema that is already plain JSON Schema, and all you do is copy it into the parameters field of a function-calling tool definition. The model never learns MCP exists, and adopting MCP removes not a single line of your function-calling code.
- Then answer what it actually solves: integration cost goes from multiplication to addition. N hosts times M capabilities means N times M integrations; a shared protocol makes it N plus M. It also draws a responsibility boundary — a third-party capability failing is no longer something you must first reproduce inside your own service.
- Volunteer the Skills distinction, since it is the natural follow-up: MCP extends what the agent can do (new callable actions), Skills extend how well it does it (a bundle of prompt, scripts and reference material, loaded on demand). One adds capability, the other adds method.
- Expect the follow-up: then where is MCP's value? In standardizing discovery and invocation, so capabilities can be owned by another team, reused by several hosts, and added or removed without a code change — while the hop to the model stays function calling.
分析过程 · 先想清楚再作答
- 这题有一个标准的错误答案,面试官就是靠它筛人:把 MCP 说成「function calling 的升级版」「以后不用写 function calling 了」。说出这句,后面讲得再多也已经扣完分了。
- 把两者放回各自的链路上就不会混:function calling 是「模型 ↔ 你的程序」之间的约定,MCP 是「你的程序 ↔ 能力提供方」之间的约定。它们不在同一段线上,所以是上下游,不是替代。
- 给一个能一句话验证的证据:MCP server 通过 tools/list 返回的每个工具,它的 inputSchema 本身就是 JSON Schema,你要做的只是把它搬进 function calling 的 parameters 字段发给模型。模型自始至终不知道 MCP 存在。接了 MCP 之后 function calling 那段代码一行都不会少。
- 再答「解决了什么问题」:接入成本从乘法变加法。N 个宿主乘 M 个能力等于 N 乘 M 份接入代码,有了协议就变成 N 加 M;顺带把责任边界划清楚了,第三方能力出问题不用先在你的服务里复现。
- 顺手把 Skills 也区分掉,这是很自然的追问:MCP 扩展的是「能做什么」(新增可调用的动作),Skills 扩展的是「怎么做得好」(一组提示词、脚本和参考资料打成的按需加载包)。一个给能力,一个给方法论。
- 可以预期的追问:那 MCP 的价值到底在哪?答案是它把「能力的发现与调用」标准化了,所以能力可以由别人维护、被多个宿主复用、不改代码就增删——但发给模型的那一段,永远还是 function calling。
Key points
- Function calling is the model-to-your-program contract; MCP is the your-program-to-provider contract — they stack rather than replace
- Every MCP tool still gets translated into a function-calling JSON Schema before it reaches the model, which never learns MCP exists
- It solves integration cost: N hosts times M capabilities becomes N plus M, and the process boundary becomes the ownership boundary
- Calling MCP an upgraded function calling is the classic wrong answer — naming that yourself scores points
- Distinguish Skills too: MCP extends what the agent can do, Skills extend how well it does it
答题要点
- function calling 是「模型和你的程序」之间的约定,MCP 是「你的程序和能力提供方」之间的约定,两者是上下游不是替代
- MCP server 列出的每个工具最终仍要翻译成 function calling 的 JSON Schema 发给模型,模型不知道 MCP 存在
- 它解决的是接入成本:N 个宿主乘 M 个能力的乘法,变成 N 加 M 的加法,同时把责任边界划到进程边界上
- 把 MCP 说成 function calling 的升级版是最常见的错误答案,主动点破这一点会加分
- 顺带区分 Skills:MCP 扩展「能做什么」,Skills 扩展「怎么做得好」
What roles do the MCP server and client play, what can a server expose, and which transports exist?MCP 里 server 和 client 分别承担什么角色?server 能暴露哪几类东西,传输方式有哪些?
Common in ChinaCommon overseasIntermediate#mcp#protocol#transportHow to reason about it · think before answering
- This looks like recall, but it discriminates on two small things: whether you separate host from client, and whether you know there are primitives beyond tools. 'Server provides tools, client calls them' is below the bar.
- Lay out three roles: the server is the capability provider and its own process; the client is the piece inside the host that talks to exactly one server; the host is your agent application, holding several clients at once. People who conflate host and client fall apart the moment you ask how they would connect to three servers.
- Cover all three server-side primitives and say who chooses each: tools are executable actions chosen by the model; resources are read-only data addressed by URI; prompts are reusable templates — the latter two are normally chosen by the user or host. That 'who chooses' framing shows you actually read the spec: modeling a large document as a resource rather than a tool moves the decision to spend those tokens from the model back to a human.
- The client side declares capabilities too, letting the server call back into the host: sampling asks the host to run a model completion, roots tells the server which directories are visible, elicitation asks the host to collect user input. Naming them without elaborating is the right level of detail.
- Two transports: stdio for a local subprocess, Streamable HTTP for remote. The dated detail worth knowing is that the older two-endpoint HTTP+SSE transport is now legacy, kept only for backwards compatibility — presenting it as current signals you read last year's blog posts.
- Expect the follow-up: anything special about stdio servers? Stdout is reserved for JSON-RPC, so every log line must go to stderr or the client receives unparseable messages; and the host owns the subprocess lifecycle, so it must reap the child on exit or leave orphans behind.
分析过程 · 先想清楚再作答
- 这题看着是背概念,实际区分度在两个小地方:一是能不能把宿主和 client 分开说,二是知不知道 tools 之外还有别的原语。只答「server 提供工具、client 调用工具」是及格线以下。
- 先把三个角色摆清楚:server 是能力提供方,一个独立进程;client 是宿主里负责跟某一个 server 说话的那一小块,一个 client 只连一个 server;宿主是你的 Agent 应用,它同时持有多个 client。很多人把宿主和 client 当成一个东西,一问「连三个 server 怎么办」就露馅。
- server 侧三种原语要一起说,并且要说清谁来选:tools 是可执行的动作,由模型来挑;resources 是按 URI 读的只读数据;prompts 是可复用的提示词模板,后两者通常由用户或宿主来挑。这句「谁来选」比原语名字本身更能体现你真读过协议——把一份大文档做成 resource 而不是 tool,等于把花不花这笔 token 的决定权从模型手里收回给人。
- client 侧也能声明能力让 server 反过来请求宿主:sampling 是让宿主跑一次模型补全,roots 是告诉 server 哪些目录可见,elicitation 是请宿主向用户要一条输入。知道有这三样、不展开,分寸刚好。
- 传输两种:stdio 用于本地子进程,Streamable HTTP 用于远程。这里有个时间戳式的加分点——旧的 HTTP 加 SSE 双端点传输已经被标为 legacy,只为兼容老客户端保留;把它当现行方案讲,等于告诉对方你看的是去年的文章。
- 可以预期的追问:stdio server 有什么特别要注意的?答 stdout 被 JSON-RPC 独占,所有日志必须走 stderr,否则 client 会收到解析不了的消息;另外子进程的生命周期归宿主管,退出时要杀掉,不然留一堆孤儿进程。
Key points
- The server is the capability provider in its own process; a client connects to exactly one server; the host holds many clients
- Three server-side primitives: tools chosen by the model, resources as URI-addressed read-only data, prompts as reusable templates — the latter two usually chosen by a human
- Clients can declare sampling, roots and elicitation so the server can call back into the host
- Two transports: stdio for local subprocesses and Streamable HTTP for remote; the old HTTP+SSE transport is legacy
- On stdio, stdout belongs to JSON-RPC so logs must go to stderr, and the host must reap the child process
答题要点
- server 是能力提供方(独立进程),client 是宿主里连接单个 server 的那一块,宿主可以同时持有多个 client
- server 侧三种原语:tools 由模型挑,resources 是按 URI 读的只读数据,prompts 是可复用模板,后两者通常由人来挑
- client 侧还能声明 sampling、roots、elicitation,让 server 反过来请求宿主做事
- 传输两种:stdio(本地子进程)与 Streamable HTTP(远程);旧的 HTTP 加 SSE 已是 legacy,不要当现行方案讲
- stdio server 的 stdout 被 JSON-RPC 独占,日志必须走 stderr;子进程生命周期由宿主负责回收
When should you reach for MCP instead of plain function calling, and what does it cost when you shouldn't?什么场景下应该考虑用 MCP,而不是直接写 function calling?不该用的时候硬上会付出什么代价?
Common in ChinaCommon overseasIntermediate#mcp#architecture#trade-offsHow to reason about it · think before answering
- The hinge is the second half. Answering only 'MCP is more standard and decoupled' is like saying 'microservices are more decoupled' — true-sounding but with no criterion, and the interviewer will immediately ask whether you turned every tool into an MCP server.
- Give three actionable criteria: the capability must be reused by more than one host, owned by another team or a third party, or added and removed without changing host code. Any one of them justifies MCP; none of them means write a local function. Making 'no' the default answer shows more engineering judgment than the criteria themselves.
- Attach a reason to each: multi-host reuse turns N times M into N plus M; external ownership makes the process boundary the responsibility boundary, so their change is not your release; hot-swapping demotes adding an internal tool from a deployment to a config change.
- Then state the costs honestly, which is where shipped experience shows: another process to keep alive, another handshake with its own timeouts and reconnects, and a debugging path that went from one hop to three — a tool that never got called might mean the model did not pick it, the schema lost fields in translation, or the server never started. On stdio you also own reaping the child process.
- One more point that is easy to miss and scores well: MCP does not change your cost structure. Tool descriptions still enter the context every turn, and more tools still degrade tool selection. The rule that you should consolidate tools past a certain count survives MCP unchanged — arguably it matters more, because now other people can add entries to your tool list.
- Expect the follow-up: so internal tools never go through MCP? Not quite. If you want the same capability available to an IDE assistant and an ops bot as well, the first criterion is met even though you own the code.
分析过程 · 先想清楚再作答
- 题眼在后半句。只会说「MCP 更标准更解耦」的人,等于说「微服务更解耦」——听起来对,但没有判据,面试官会立刻追问「那你们所有工具都做成 MCP server 了吗」。
- 先给判据,而且要是可执行的三条:能力要被多个宿主复用、能力由另一个团队或第三方维护、需要不改宿主代码就能增删能力。命中任意一条才考虑,**一条都不命中就直接写本地函数**——把默认答案摆成「不上」,这条比三条判据本身更能体现工程判断。
- 每条判据配一句为什么:多宿主复用把 N 乘 M 变成 N 加 M;别人维护时进程边界就是责任边界,他们改他们的、你不用发版;热插拔让加一个内部工具从一次发布降级成一次配置变更。
- 然后老实说代价,这是区分「用过」和「读过」的地方:多一个进程要保活、多一次握手要处理超时与重连、排障链路从一段变三段——工具没被调用,现在可能是模型没选、可能是 schema 翻译时丢了字段、也可能是 server 压根没起来。stdio 的子进程还要你自己回收,否则留孤儿进程。
- 还有一条容易被忽略但很加分:MCP 不改变你的成本结构。工具描述照样每轮都进上下文,工具多了照样会让模型选错——D5 那条「工具超过一定数量就该合并描述」在接了 MCP 之后一字不变,甚至更需要,因为现在别人可以往你的工具列表里塞东西。
- 可以预期的追问:那内部工具一律不上 MCP 吗?不是。有一类值得例外——你希望它能被 IDE 里的助手和运维机器人一起用,那第一条判据就命中了,即使它是你自己维护的。
Key points
- Three criteria, any one justifies MCP: reuse across hosts, ownership by another team, or add/remove without touching host code
- The default is no — if none of the three apply, a local function is the better engineering decision
- Costs: another process to supervise, another handshake with timeouts, and a debug path that grows from one hop to three
- MCP does not change your cost structure: descriptions still enter context every turn and too many tools still hurt selection
- Once third-party capabilities are attached, your tool list is no longer fully under your control, which is itself a design problem
答题要点
- 三条判据,命中任意一条才考虑 MCP:多宿主复用、由他人维护、需要不改代码增删能力
- 默认答案是不上:三条都不命中就直接写本地函数,这是更好的工程决策
- 代价是多一个进程要保活、多一次握手要处理超时、排障从一段链路变成三段
- MCP 不改变成本结构:工具描述照样每轮进上下文,工具过多照样会让模型选错,该合并还是要合并
- 第三方能力接进来之后,工具列表不再完全由你掌控,这本身就是需要设计的一件事
You are about to attach a third-party MCP server in production. What worries you, and what do you check?你要把一个第三方维护的 MCP server 接进生产环境,会担心什么、做哪些检查?
Common in ChinaCommon overseasDeep dive#mcp#security#operationsHow to reason about it · think before answering
- This stacks yesterday's security topic onto today's openness topic, and it discriminates hard: every benefit of MCP rests on the capability being maintained by someone else, and that is also its biggest risk.
- First name the new trust assumptions: you put someone else's code into your own process tree, you feed its returned text straight into the model, and you let it add entries to your tool list. Each maps to a class of risk.
- Then go through the checks. Execution: the server is a process that runs, so constrain which files it can read, whether it has network access, its timeout and the identity it runs as — the least-privilege and sandbox story from yesterday. Data: treat everything it returns as untrusted input, which is exactly the indirect-injection scenario where instructions hide in a field of a tool result. Tool output is never instructions, and the permission gate must live in your process and fire before the call.
- Third, governance, the part most people miss: the tool list can change at runtime — one listChanged notification and a new tool appears. So pin your allowlist by tool name, keep newly appearing tools out of the model's list until a human approves, and pin the server version instead of tracking upstream latest.
- Fourth, availability and cost: this is a new external dependency. If it is down your agent silently loses a set of capabilities, so you need timeouts, graceful degradation (tell the model the capability is temporarily unavailable rather than failing the whole turn), and its calls on your observability dashboard.
- Expect the follow-up: how do you decide it is worth attaching at all? Back to the three criteria — if only one host uses it and you could implement it yourself, you are taking third-party risk with no matching benefit.
分析过程 · 先想清楚再作答
- 这题是把昨天的安全和今天的开放性叠在一起考,区分度极高:接 MCP 的全部好处,都建立在「能力由别人维护」这一点上,而这一点同时就是它最大的风险。
- 第一层想清楚新增了什么信任假设:你把一段别人写的代码放进了自己的进程树,把它返回的文本直接喂给了模型,还允许它往你的工具列表里加条目。这三件事各自对应一类风险。
- 第二层逐条给检查项。执行侧:server 是一个会跑起来的进程,要限制它能读哪些文件、能不能联网、超时多久、以什么身份运行,也就是昨天讲的最小权限和沙箱那一套。数据侧:**它的返回结果一律当不可信输入**,这正是昨天间接注入的固定现场——工具返回的备注字段里可以藏指令;所以工具结果不能当指令执行,权限闸门必须在你自己的进程里、在调用之前判。
- 第三层是治理,最容易被漏掉:工具列表可以在运行中变化,server 发一条 listChanged 通知就能加一个新工具。所以你的白名单要按工具名固定,新出现的工具默认不进模型的工具列表,要有人点头;server 的版本要锁定,不能跟着上游 latest 漂。
- 第四层是可用性与成本:这是一个新的外部依赖,它挂了你的 Agent 就少一批能力,所以要有超时、要有降级(工具不可用时告诉模型「这个能力暂时不可用」而不是整轮失败),要把它的调用计入你的可观测面板。这三条正好复用前面几周讲过的东西。
- 可以预期的追问:怎么判断它值不值得接?答案回到那三条判据——如果这个能力只有你一个宿主用,而且你完全可以自己实现,那接一个第三方 server 承担的风险没有对应的收益。
Key points
- Three new trust assumptions: their code in your process tree, their text in your model context, their entries in your tool list
- Execution: least privilege — restrict filesystem and network, set timeouts, run as a low-privilege identity, sandbox where warranted
- Data: treat every result as untrusted input; tool output is never instructions, and the permission gate must fire in your process before the call
- Governance: allowlist by tool name so newly appearing tools stay out until approved, and pin the server version rather than tracking latest
- Availability: treat it as an external dependency with timeouts, graceful degradation and dashboard coverage
答题要点
- 三个新增信任假设:别人的代码进了你的进程树、它的返回文本进了模型上下文、它能往你的工具列表里加条目
- 执行侧按最小权限收紧:限制文件访问与网络、设超时、以低权限身份运行,必要时进沙箱
- 数据侧一律当不可信输入:工具返回结果不能当指令执行,权限闸门必须在自己的进程里、在调用之前判
- 治理侧锁死变化面:按工具名做白名单,新出现的工具默认不进模型的工具列表;锁定 server 版本,不跟 latest
- 可用性侧当外部依赖对待:超时、降级、把它的调用与失败计入可观测面板
D24 RAG, Level Up: Hybrid Search, Reranking, Citations, Recall Evaluation
Why isn't pure vector search enough — what does keyword search add?为什么单纯的向量检索不够,还要加一路关键词检索?
Common in ChinaCommon overseasBasic#rag#hybrid-search#retrievalHow to reason about it · think before answering
- The discriminator is not whether you know the term 'hybrid search' — it is whether you can name a concrete query that vector search will always miss. No example means you have only read architecture diagrams.
- One causal chain: vector search compares semantic distance, so both its strength and its weakness come from that compression step. Synonyms match (shipping fee vs postage), but strings with no semantics collapse together — error codes, SKUs, order ids, person names.
- BM25 has the mirror-image profile: a term matters more when it is frequent in this document and rare across the corpus. So it nails low-frequency literals and fails completely on paraphrase.
- State the conclusion as 'their blind spots do not overlap, and that follows from how each one computes' — not the vague 'two channels are safer'. A measured example lands best: for 'what does E4032 mean', the correct doc is absent from the vector top-5 and is the keyword top-1.
- Expected follow-up 1: how do you merge the two rankings? Answer RRF, and explain why weighted sums fail (see q02).
- Expected follow-up 2: how do you do keyword search over Chinese? Postgres's default parser effectively does not tokenize Chinese; the cheapest workable fallback is character bigrams, keeping ASCII words and codes whole. Production needs a real Chinese tokenizer extension. Answering this usually proves you actually built it.
分析过程 · 先想清楚再作答
- 这题的区分度不在「你知不知道有 hybrid search」,而在**你能不能说出一个向量检索一定会漏的具体例子**。答不出例子的,一听就是只看过架构图。
- 推导链只有一句:向量检索比的是语义距离,所以它的强项和弱项都来自「压缩成语义」这一步——同义词能对上(运费 / 邮费),而没有语义的字符串会被压到一起(E4032、SF-3000、订单号、人名)。
- 关键词那一路(BM25)的性质正好相反:一个词在本文档里越频繁越相关、在全语料里越常见越不值钱,所以它对低频稀有词极准,对同义改写完全无能。
- 结论要说成「两者的盲区不重叠,而且是由计算原理决定的不重叠」——不是「多一路更保险」这种模糊说法。举一个实测例子最有说服力:查「E4032 是什么意思」,向量 top5 里没有那篇讲支付错误码的文档,关键词 top1 就是它。
- 可预期的追问一:那怎么合并两路结果?答 RRF,并说清为什么不能加权求和(见 q02)。
- 可预期的追问二:中文怎么做关键词检索?答 Postgres 默认分词器对中文等于不分词,最简可用的兜底是 bigram(相邻两字切开),但英文与编号必须整词保留;生产要上专门的中文分词扩展。这一条能答出来,基本就说明你真动手做过。
Key points
- Vector search compares semantic distance: strong on paraphrase, weak on SKUs, error codes and order ids that carry no semantics.
- BM25 is strong on rare literal terms and weak on paraphrase — the blind spots follow from the algorithms and do not overlap.
- So run both channels wide (top 20 each) and fuse with RRF so each covers the other's gap.
- Give a measured example: for the E4032 query the correct chunk is missing from vector top-5 but is keyword top-1; a 'postage vs shipping fee' query is the reverse.
- Chinese keyword search needs tokenization: character bigrams as the cheap fallback, ASCII words kept whole, a real tokenizer extension in production.
答题要点
- 向量检索比的是语义距离,强在同义改写,弱在型号、错误码、订单号这类没有语义的字符串。
- BM25 强在低频稀有词的字面命中,弱在同义改写——两者的盲区由各自的计算原理决定,不重叠。
- 所以第一轮开两路、各取 20 条,用 RRF 融合,把两边的盲区互相补上。
- 举实测例子:E4032 那条 query 向量 top5 漏掉正确文档,关键词 top1 就是它;「邮费」那条反过来只有向量能召回。
- 中文关键词那一路要处理分词,最简兜底是 bigram,字母数字整词保留,生产上专门的中文分词扩展。
How do you merge two retrieval rankings, and why not just take a weighted sum of the scores?两路检索结果怎么合并?为什么不能直接加权求和?
Common in ChinaCommon overseasIntermediate#rag#rrf#rankingHow to reason about it · think before answering
- The second half is the real question. Anyone can say 'RRF'; explaining why weighted sums fail is what separates people who have looked at the score distributions.
- Decompose it: are the two scores even the same unit? Cosine similarity is bounded in 0 to 1 and tightly clustered — candidates often differ by 0.02. BM25 is unbounded and a few rare-term hits reach 12. Adding them lets the larger-magnitude channel decide everything; the weight only tunes how much it dominates.
- Worse, it is unstable. Weights tuned on one corpus drift on the next, so you re-tune forever.
- Conclusion: fuse ranks, not scores. RRF maps each rank to 1/(k + rank) and sums, with k = 60. Ranks are unitless and need no calibration. k flattens the head of the list so that 'top-ranked in both channels' beats 'first in one channel' — consensus over single-source confidence.
- A hand-checkable example helps: rankings [a,b,c] and [c,d,a] give a = 1/61 + 1/63 ≈ 0.0323, while a raw score sum promotes c on the strength of its BM25 12.
- Expected follow-up: what about ties? You must break them explicitly, e.g. by id. Otherwise ordering depends on hash-map iteration order and differs across languages and runs, which makes your evaluation numbers irreproducible. Mentioning this signals you actually ran it more than once.
分析过程 · 先想清楚再作答
- 题眼在后半句。前半句答「RRF」谁都会,后半句「为什么不能加权求和」才是筛人的地方——它考的是你有没有真的看过两路分数的分布。
- 怎么拆:先问自己两个分数是不是同一个量纲。余弦相似度有界(0 到 1)且分布密集,同一批候选常常只差 0.02;BM25 无上界,命中几个稀有词就能到 12 分。**不同量纲的数相加,等于让量纲大的那一路单方面决定结果**,权重只是在调「它说了算的程度」。
- 更麻烦的是它不稳定:权重在这批语料上调好了,换一批语料分布就变了,得重调。这是一个永远还不完的技术债。
- 结论:改用名次。RRF 把每一路的名次折算成 `1/(k + rank)` 再相加,k 取 60。名次是无量纲的,不需要任何标定。k 的作用是压平头部差距,让「两路都进前列」压过「一路排第一」——共识优先于单点自信。
- 一个能当场手算的例子很加分:两路排名 [a,b,c] 与 [c,d,a],a 得 1/61 + 1/63 ≈ 0.0323;而分数直接相加的版本会把 BM25 里 12 分的 c 顶到第一。
- 可预期的追问:同分了怎么办?必须显式定序(比如按 id),否则结果取决于哈希表遍历顺序,同一份输入在不同语言、不同运行里给出不同排序——评估集量出来的数字也就不可复现了。这一条答出来会非常加分,因为它说明你真的跑过多次。
Key points
- Use RRF: map each channel's rank to 1/(k + rank) and sum, with k = 60.
- Weighted sums fail because the scores are different units — bounded, tightly clustered cosine versus unbounded BM25, so BM25 decides the outcome.
- Weights also do not transfer: tuned on one corpus, they drift on the next.
- Ranks are unitless and need no calibration; k flattens the head so cross-channel consensus outweighs single-channel confidence.
- Break ties explicitly (by id) or ordering depends on hash iteration order and your evaluation numbers stop being reproducible.
答题要点
- 用 RRF:每一路的名次折算成 1/(k + rank) 再相加,k 取 60。
- 不能加权求和是因为两个分数量纲不同——余弦有界密集、BM25 无上界,相加等于让 BM25 单方面决定结果。
- 而且权重不可迁移:这批语料调好,换一批就得重调,是还不完的债。
- 名次是无量纲的,不需要标定;k 压平头部差距,让两路共识压过单路自信。
- 同分必须显式定序(按 id),否则结果依赖哈希表遍历顺序,评估数字不可复现。
How is reranking usually implemented, what problem does it solve, and what does it cost?重排(rerank)一般怎么实现?它解决了初步检索的什么问题,代价是什么?
Common in ChinaCommon overseasIntermediate#rag#rerank#latencyHow to reason about it · think before answering
- The lazy answer is 'sort again, more accurately'. What the interviewer wants is why the first pass cannot rank well, and why reranking cannot run over the whole corpus.
- Decompose: the first pass ranks by retrieval signals — cosine distance or term statistics — which are designed to scan millions of items fast, and coarseness is the price. Reranking changes the algorithm: query and candidate go into one model together (a cross-encoder), which is far more accurate but costs one forward pass per candidate. Hence it must sit behind a wide recall stage.
- Distinguish two implementations. For teaching or prototypes, batch-score with an LLM (0-10 for 40 candidates in one call). Production uses a trained cross-encoder reranker. Name the cost: an extra 100-300 ms hop plus an inference box — it is not a per-token API, it consumes capacity.
- Framing it as a funnel is clearest: recall sets the ceiling, reranking decides whether what is under the ceiling reaches the top five. Measured: adding the keyword channel lifts recall@20 from 83% to 95%; adding reranking moves recall@20 only to 98%, but recall@5 jumps from 80% to 91% and MRR from 0.732 to 0.908.
- Expected follow-up 1: does reranking improve recall? No. It introduces no new candidates, so recall@20 is the wrong metric to judge it by.
- Expected follow-up 2: why not ship LLM scoring to production? Unpredictable latency, per-token cost, scores that drift with prompt wording, and no clean path to offline distillation.
分析过程 · 先想清楚再作答
- 这题最容易答成「再排一次序,更准」。面试官想听的是**为什么第一轮不能直接排准**,以及**为什么重排不能对全库做**。
- 怎么拆:第一轮的排序依据是「检索信号」——余弦距离或词频统计,它们是为了能在百万条里快速筛选而设计的,代价就是粗。重排换了一种算法:把 query 和候选**拼在一起**送进同一个模型算相关度(cross-encoder),精度高得多,但复杂度是每条候选一次前向,没法对全库做。所以它必须跟在一个宽召回后面。
- 结论要区分两种实现:教学 / 原型可以用 LLM 批量打分(一次调用给 40 条打 0 到 10 分),生产用专门训练的 cross-encoder 重排模型。**代价说清楚:多一次 100 到 300 毫秒的调用,外加一台推理机器**——它不是按 token 计费的 API,是要占资源的。
- 把它放进漏斗里说最清楚:召回决定天花板,重排决定天花板上的东西能不能排到前五。实测的样子是——加了关键词那一路,recall@20 从 83% 涨到 95%(天花板抬高);再加重排,recall@20 只到 98%,但 recall@5 从 80% 跳到 91%、MRR 从 0.732 到 0.908。
- 可预期的追问一:重排能不能提高召回?不能。它不引入新候选,只重排已有的那批——所以看 recall@20 判断重排效果是错的指标。
- 可预期的追问二:为什么不用 LLM 打分上生产?延迟不可控、成本按 token 走、分数会随提示词措辞漂移,而且没法做批量离线蒸馏。
Key points
- The first pass ranks by retrieval signals so it can scan a large index fast; coarseness is the trade.
- Reranking feeds query and candidate through one model together (cross-encoder): much sharper, but one forward pass per candidate, so only tens of items.
- Batch LLM scoring works for teaching; production uses a dedicated reranker, costing an extra 100-300 ms hop plus an inference box.
- Reranking does not raise recall — it raises recall@5 and MRR (measured 80% to 91%, 0.732 to 0.908) while recall@20 barely moves from 95% to 98%.
- So judge a reranker by small-k metrics, never by recall@20.
答题要点
- 第一轮按检索信号粗排(余弦、词频),为的是能在大库里快速筛,代价是粗。
- 重排把 query 和候选拼在一起过同一个模型(cross-encoder),精度高但每条一次前向,只能对几十条做。
- 教学版可用 LLM 批量打 0 到 10 分;生产用专用重排模型,代价是多一次 100 到 300 毫秒的调用加一台推理机器。
- 重排不提高召回,它提高的是 recall@5 与 MRR——实测 80% → 91%、0.732 → 0.908,而 recall@20 只从 95% 到 98%。
- 所以判断重排效果要看前 k 小的指标,不要看 recall@20。
How do you evaluate retrieval quality in a RAG system, and how should the evaluation set be built?怎么评估一个 RAG 系统的检索效果?评估集应该怎么构造?
Common in ChinaCommon overseasDeep dive#rag#evaluation#recallHow to reason about it · think before answering
- This is a very common question in the Chinese market and the fastest way to expose someone who has assembled RAG but never tuned it. The test: does your answer contain concrete metric names and an annotation granularity?
- First separate what is being evaluated — the step people most often conflate. Retrieval evaluation asks 'was it found'; generation evaluation asks 'was the answer right'. Keep two separate sets. Merge them and, when the score drops, you cannot tell whether retrieval missed or the model fumbled — and those have completely different fixes.
- Shape of the set: about 20 queries, each annotated with 1-3 chunk ids that must be retrieved. Annotate at chunk level, not document level — chunks are the retrieval unit, and document-level labels inflate the numbers. Cover the real query mix, especially the types you know break: codes, paraphrase, cross-document.
- Three metrics, three questions. recall@5 is what actually reaches the model, so it is the number you care about. recall@20 is the ceiling — if it does not move, the problem is on the recall side and no reranker will save you. MRR is sensitive to ordering and breaks ties when recall is equal.
- Production view: freeze the set once agreed, because changing samples destroys comparability — the same reason a factory keeps fixed reference samples. Pair it with online counterparts (empty-citation rate, hallucinated-citation rate, escalation rate), since passing offline does not mean passing in production.
- Expected follow-up: is 20 enough given the labeling cost? Not for statistical significance, but enough for regression — its job is to stop retrieval silently getting worse. Scale up before you settle an A/B, and grow it from failure cases rather than random additions.
分析过程 · 先想清楚再作答
- 这题是国内面试的极高频题,也是最容易暴露「只搭过没调过」的一题。判据很简单:你的回答里有没有出现**具体的指标名和标注粒度**,没有就是没做过。
- 先把评估对象分清楚——这是最容易混的一步:**检索评估问「找得到找不到」,生成评估问「答得对不对」**。两套评估集要分开维护。混成一套的后果是分数掉了你分不清是检索漏了还是模型答砸了,而这两件事的修法完全不同。
- 评估集的形状:20 条左右的 query,每条**人工标注 1 到 3 个必须召回的 chunkId**。注意标注粒度是**块**不是文档——检索的单位就是块,标到文档级会让指标虚高。query 要覆盖真实分布,尤其要包含那些你知道会翻车的类型(编号、同义改写、跨文档)。
- 三个指标各回答一个问题:recall@5 是「进上下文的那几条覆盖了多少」,也就是你真正关心的数;recall@20 是天花板,它上不去说明问题在召回侧、重排再强也没用;MRR 对排序质量敏感,recall 打平时用它分高下。
- 生产视角:评估集一旦定下来就要冻结,换了样本分数就没有可比性——这和产线质检必须用固定的标准样品是同一个道理。同时线上要有对照指标(引用为空率、幻觉引用率、转人工率),因为离线过了不等于线上没事。
- 可预期的追问:标注成本这么高,20 条够吗?答:20 条不够做统计显著性,但足够做**回归**——它的作用是「改了检索之后别悄悄变差」。要做 A/B 定论再上规模,而且优先扩充失败案例,不是随机加样本。
Key points
- Retrieval and generation evaluation are two separate sets: 'was it found' versus 'was the answer right'.
- Around 20 queries, each labeled with 1-3 chunk ids that must be retrieved — chunk level, not document level.
- recall@5 is what the model actually sees, recall@20 is the ceiling, MRR measures ordering quality.
- Freeze the set once agreed or scores stop being comparable; pair it with online empty-citation and hallucinated-citation rates.
- Twenty cases is a regression guard, not a significance test; grow it from failure cases, not random samples.
答题要点
- 检索评估和生成评估是两套:前者问「找得到找不到」,后者问「答得对不对」,分开维护。
- 评估集是 20 条左右的 query,每条人工标 1 到 3 个必须召回的 chunkId——标到块级,不是文档级。
- recall@5 是真正关心的数(模型只看得到这几条),recall@20 是天花板,MRR 衡量排序质量。
- 评估集一旦定下来就冻结,否则分数没有可比性;线上再配引用为空率、幻觉引用率做对照。
- 20 条不够做显著性但够做回归;扩充时优先补失败案例,不是随机加样本。
D25 The Frontend Agent Experience: Streaming Rendering, Visualizing Tool Calls, Interrupt/Retry, SSE Hooks
How does a frontend consume SSE to render a typewriter effect, and why do people usually avoid the built-in EventSource?前端怎么消费 SSE 并实现打字机效果?为什么一般不用浏览器自带的 EventSource?
Common in ChinaCommon overseasBasic#sse#streaming#frontendHow to reason about it · think before answering
- This is a warm-up question, but the second half is where it bites. Answering only 'use EventSource and listen for message events' invites an immediate follow-up about auth, and not having one shows you never wired it in a real project.
- Sketch the positive answer first: fetch the response, read res.body as a ReadableStream, decode with TextDecoder, split on blank lines into frames, parse event and data per frame, and append the text delta onto the current message.
- Then the three hard blockers on EventSource, stated together: GET only, no custom request headers (so no Authorization), and no request body. Agent requests need all of a message payload, an idempotency key and a session id in the body, so all three bite at once.
- Name the cost next — this separates having used it from having read about it. Hand-rolling means you also reimplement EventSource's auto-reconnect and Last-Event-ID resume. That said, its auto-reconnect is already unusable under auth because reconnects cannot carry headers either, so the loss is smaller than it sounds.
- Expected follow-up 1: what if a frame is split across chunks? Buffer it — after splitting on blank lines, pop the trailing partial segment and prepend it to the next chunk. This bug almost never reproduces on localhost, so you must feed deliberately fragmented payloads to test it.
- Expected follow-up 2: why not WebSocket? SSE is one-way downstream over plain HTTP, passes proxies and CDNs, and is far lighter to run. WebSocket earns its keep only when you need frequent upstream traffic such as collaborative editing or voice. Volunteering this scores well.
分析过程 · 先想清楚再作答
- 这题是送分题,但送分点在后半句。只答「用 EventSource 监听 message 事件」的,面试官会立刻追问鉴权怎么办——答不上来就说明没在真项目里接过。
- 先给正面答案的骨架:`fetch` 拿到响应后读 `res.body` 这个 ReadableStream,`TextDecoder` 解码成文本,按空行切帧,逐帧解析出 `event` 与 `data`,把文本增量追加到当前这条消息上。
- 为什么不用 `EventSource`,三个硬伤要一口气说全:只能发 GET、不能带自定义请求头(也就是放不进 Authorization)、不能带请求体。Agent 场景里消息体、幂等键、会话 id 都得走 body,三条全撞上。
- 紧接着说代价,这是区分「用过」和「读过」的地方:手写解析意味着 `EventSource` 自带的自动重连、`Last-Event-ID` 续传都要自己实现。不过带鉴权的场景里那个自动重连本来就不好用(它重连时同样带不了头),所以损失没听起来那么大。
- 可预期的追问一:帧被网络切成两半怎么办?答缓冲——按空行切完之后,最后一段可能是半截,`pop` 出来留到下一块再拼。**这个 bug 在本机直连时几乎不出现**,所以要专门构造切碎的报文来测。
- 可预期的追问二:为什么不用 WebSocket?答:SSE 是单向下行、走普通 HTTP、天然过代理和 CDN、实现和运维都更轻;只有需要频繁上行(协同编辑、语音)才值得上 WebSocket。这一条能主动说出来会很加分。
Key points
- Use fetch, read res.body as a ReadableStream, decode with TextDecoder, split frames on blank lines, append deltas.
- EventSource has three blockers: GET only, no custom headers (no Authorization), no request body.
- The cost is reimplementing auto-reconnect and Last-Event-ID resume — though auto-reconnect is unusable under auth anyway.
- You must buffer partial frames across chunks; localhost testing will not surface this bug.
- SSE beats WebSocket here: one-way, plain HTTP, proxy and CDN friendly. Switch only when you need frequent upstream messages.
答题要点
- 用 fetch 读 res.body 这个 ReadableStream,TextDecoder 解码,按空行切帧,增量追加文本。
- EventSource 三个硬伤:只能 GET、不能带自定义头(放不进 Authorization)、不能带请求体。
- 代价是自动重连和 Last-Event-ID 续传要自己写——但带鉴权时那个自动重连本来也用不了。
- 必须处理跨块的半截帧:切完之后最后一段留到下一块再拼,本机直连测不出这个 bug。
- 不用 WebSocket 是因为 SSE 单向下行、走普通 HTTP、过代理和 CDN 更省事;需要频繁上行才换 WebSocket。
The user hits Stop and the frontend calls AbortController.abort(). What is the backend doing at that moment?用户点了「停止生成」,前端调用 AbortController.abort() 之后,后端在做什么?
Common in ChinaCommon overseasDeep dive#streaming#cancellation#costHow to reason about it · think before answering
- This is the core question of the chapter and a deliberate trap: the prompt states the abort as a given and waits for you to say 'so it stopped'. Saying that ends the conversation.
- The correct answer in one line: the backend knows nothing and is still running — still calling the model, still writing messages, still billing tokens. abort only stops your end from reading; at most it drops the TCP connection, and whether the backend notices, or acts on noticing, is a separate matter.
- Decompose by drawing who knows what: the user knows, the frontend knows, the chain breaks, the backend does not know. That broken link must be closed with an explicit request: POST /runs/:id/cancel. So stopping is two steps, not one.
- A quantified contrast lands best: on the same 70-character reply interrupted at character 5, the two-step version stops the backend at 5/70 while abort-only runs to 70/70. That is 14x the tokens, and those 65 characters also land in conversation history and get resent as context next turn, billing you twice.
- Production addendum: on cancel, do not hard-kill. Move the run to a cancelled state and let the current step finish, or you leave half-written messages and gaps in the sequence numbers. Also make cancel idempotent, because you will retry it when the network flakes.
- Expected follow-up: can the backend just detect the dropped connection and stop by itself? It can and should, as a safety net, but not as the only mechanism. Proxies and load balancers often hold connections open, so detection can lag by tens of seconds, and if the client auto-reconnects the connection never drops at all. The net is a net; the explicit cancel is the main path.
分析过程 · 先想清楚再作答
- 这题是本章题眼,也是一道**陷阱题**:题干里已经把「前端 abort 了」当成既成事实,等你顺着说「那就停了」。答「停了」的直接出局。
- 正确答案一句话:**后端什么都不知道,它还在跑。** 还在调模型、还在往库里写消息、还在按 token 计费。`abort` 只是让你这一端不再读了,它顶多让 TCP 连接断开,而后端是否感知得到连接断开、感知到之后做不做事,是另一回事。
- 怎么拆:把「谁知道这件事」画出来。用户知道 → 前端知道 → **中间断了** → 后端不知道。断掉的这一环必须用一个显式的请求补上:`POST /runs/:id/cancel`。所以打断是两步,不是一步。
- 给一个量化的对照最有说服力:同一段 70 个字的回复,在第 5 个字打断——两步打断的后端停在 5/70,只 abort 的后端照跑到 70/70。差 14 倍的 token,而且那 65 个字还会落进会话历史,下一轮当上下文重新发一遍,付第二遍钱。
- 生产视角的补充:cancel 收到之后**不要硬杀**,把 run 迁到 cancelled 状态、让当前这一步跑完再退出——硬杀会留下半写的消息和对不上的序号。而且 cancel 本身必须幂等,因为网络抖动时你会重试它。
- 可预期的追问:那能不能靠后端检测连接断开来自动停?可以做,而且应该做(作为兜底),但不能只靠它——反向代理和负载均衡常常会把连接维持一段时间,后端感知到断开可能已经是十几秒之后;而且用户点停止之后如果自动重连,连接根本没断。**兜底归兜底,显式 cancel 才是主路径。**
Key points
- The backend has no idea: still calling the model, still writing, still billing. abort only stops your side reading.
- Stopping is two steps: abort for instant UI response, plus POST /runs/:id/cancel to actually halt the run.
- Quantified: interrupting the same 70-character reply at character 5 gives 5/70 with both steps versus 70/70 with abort alone.
- On cancel, transition the run to cancelled and let the current step finish rather than hard-killing; make cancel idempotent.
- Backend disconnect detection is only a safety net — proxies hold connections open and auto-reconnect means no disconnect at all.
答题要点
- 后端完全不知情:还在调模型、还在写库、还在计费。abort 只让前端这一端停止读取。
- 打断必须两步:abort(界面立刻响应)+ POST /runs/:id/cancel(后端真的停)。
- 量化差别:同一段 70 字的回复在第 5 个字打断,两步是 5/70,只 abort 是 70/70。
- 后端收到 cancel 不要硬杀,迁到 cancelled 状态让当前步跑完;cancel 必须幂等。
- 靠后端检测连接断开只能当兜底:代理会维持连接、自动重连时连接根本没断。
What is different about frontend state management under streaming, and why not call setState on every token?流式场景下前端的状态管理要注意什么?为什么不能每个 token 都 setState?
Common in ChinaCommon overseasIntermediate#react#streaming#performanceHow to reason about it · think before answering
- This question probes whether you have watched a long reply drop frames. 'Keep messages in useState and setState on each delta' is functionally correct but reveals you only tried short replies.
- Do the arithmetic first: streaming delivers tens of tokens per second, so one setState per token means tens of full render passes per second. The message list keeps growing, so each pass gets more expensive as the conversation goes — the jank peaks late in long replies and long sessions, exactly when it hurts most.
- The fix is batching: append tokens into a ref without rendering, and flush the accumulated text on a 30 ms timer. Thirty milliseconds is roughly 33 fps, still a smooth typewriter, while render count drops by one to two orders of magnitude — measured, 200 tokens produced 8 commits.
- Three details that must ship with it: force a final flush when the stream ends, or the last sub-batch stays in the buffer and the user sees a truncated reply; flush on interrupt too, so the user sees exactly where it stopped; and keep the buffer in a ref, not state, or the code you wrote to avoid renders is itself causing them.
- One level up is layering: streaming logic should live outside React. Parsing, event reduction and batching are pure functions; a store holds state and exposes subscribe and getSnapshot; React only calls useSyncExternalStore. The concrete payoff is that this logic can be unit tested with no browser instead of being click-tested.
- Expected follow-up: why not just use a state library? Libraries solve cross-component sharing and update granularity, while the hard parts here are lifecycle (connect, cancel, cleanup on unmount) and flush cadence — no library does those for you. The interviewer wants your reasoning, not your library list.
分析过程 · 先想清楚再作答
- 这题考的是「你有没有在长回复下真的看过掉帧」。答「用 useState 存消息数组,收到 delta 就 setState」在功能上没错,但它暴露的是只在短回复上试过。
- 先算一笔账:流式一秒来几十个 token,每个 token 一次 setState 就是一秒几十轮完整渲染。而消息列表是越来越长的,每一轮的代价随对话轮数增长——所以卡顿在回复后半段和长会话里最明显,正好是最不该卡的时候。
- 做法是攒批:token 先追加进 ref(不触发渲染),一个定时器每 30 毫秒把攒下的一次性提交。30 毫秒约等于 33 帧每秒,肉眼仍是连续的打字机,渲染次数掉一到两个数量级——实测 200 个 token 只提交 8 次。
- 三个必须配套的细节:流结束时强制 flush 一次(否则最后不足一个批次的内容永远留在缓冲里,用户看到回复少半句);打断时也要 flush(让用户看到停在哪个字);缓冲状态必须放 ref 不放 state,否则你为了省渲染写的代码本身在触发渲染。
- 再往上一层是分层:**流式逻辑应该活在 React 外面。** 解析、事件归并、攒批都是纯函数,store 持有状态并暴露 subscribe 和 getSnapshot,React 侧只用 useSyncExternalStore 订阅。这样做的直接好处是**这套逻辑可以在没有浏览器的环境里跑单元测试**,而不是只能靠手点。
- 可预期的追问:为什么不直接用某个状态库?答:状态库解决的是跨组件共享和更新粒度,而流式的难点在生命周期(连接、取消、卸载清理)和批处理频率——这两件事没有哪个库替你做。面试官问这题想听的是你怎么想,不是你会用哪个库。
Key points
- One setState per token means tens of full renders per second, and each render costs more as the list grows — long replies jank at the end.
- Batch instead: accumulate tokens in a ref and flush every 30 ms; measured, 200 tokens produced only 8 commits.
- Ship the details with it: force a flush on stream end and on interrupt, and keep the buffer in a ref rather than state.
- Keep parsing, event reduction and batching as pure functions outside React; subscribe via useSyncExternalStore.
- The payoff of that split is unit-testable streaming logic with no browser in the loop.
答题要点
- 每个 token 一次 setState 等于一秒几十轮全量渲染,而消息列表越长每轮越贵,长回复后半段必然掉帧。
- 做法是攒批:token 进 ref 不触发渲染,30 毫秒定时 flush 一次,实测 200 个 token 只提交 8 次。
- 必须配套:流结束和打断时强制 flush;缓冲放 ref 不放 state。
- 流式逻辑(解析、归并、攒批)应该是 React 之外的纯函数,React 只用 useSyncExternalStore 订阅。
- 这样分层的直接好处是能脱离浏览器做单元测试,而不是只能手点验证。
How do you design retry so it does not duplicate side effects, and should the tool-call process be visible to the user?失败重试怎么设计才不会产生重复副作用?工具调用过程要不要暴露给用户?
Common in ChinaCommon overseasIntermediate#idempotency#retry#uxHow to reason about it · think before answering
- The question bundles two topics, and the test is whether you see what they share: both turn invisible intermediate state into something the user can act on. Answering them separately is fine, but naming the link reads as senior.
- Chain for retry: retrying means the same message may execute twice, costing double tokens and possibly duplicating irreversible tool calls such as issuing a refund twice. Hence idempotency. The key must be generated by the client on the first attempt and resent unchanged on retry, and the backend enforces it with a unique constraint, reattaching to the existing run instead of creating a new one.
- State the decision rule clearly: when do you mint a new key? The rule is whether the content being sent changed, not which button the user pressed. Same message retried keeps the key; edited content is a new message and needs a new key.
- Mentioning how far this pattern reaches scores well: write deduplication, cron ticks consumed exactly once, cross-service delivery, and frontend retry — the same shape at four layers, with the database's unique constraint always the final arbiter rather than an application-level check-then-write.
- For tool visibility: expose the process, for three reasons. The user can decide whether to interrupt instead of waiting blind; waiting becomes tolerable, since a spinner for fifteen seconds invites a page refresh that wastes the whole turn; and when something breaks the user can say 'it hung on looking up my order', which saves everyone time.
- Expected follow-up: does exposing everything leak internals? It can, so filter. Show human-readable tool names rather than function names, hide user identifiers, internal ids and secrets from the arguments, and show classified error reasons rather than raw stack traces. You are surfacing the process, not the internal structure.
分析过程 · 先想清楚再作答
- 这题把两件事绑在一起问,考的是你能不能看出它们的共同点:**都是「把不可见的中间状态变成可控的」**。分开答也行,但点出这层关系会显得成熟。
- 重试这一半的推导链:重试意味着同一句话可能被执行两遍 → 两倍 token,还可能两次不可逆的工具调用(比如退款打两次钱)→ 所以要幂等 → 幂等键必须由**客户端在第一次发送时生成**并在重试时原样带上 → 后端拿它做唯一约束,命中就把已有 run 的流接回来,而不是新建。
- 关键判据要说清:**什么时候该换新键?** 判据是「要发送的内容变没变」,不是「用户点了哪个按钮」。同一句话重试用同一个键;用户改了内容重新发,那是新的一句话,必须换新键。
- 顺带提一句这一招的复用面会很加分:落库去重、定时任务防止一个 tick 被消费两次、跨服务调用防重复投递、前端重试——同一个形状用在四个层面,最终裁判永远是数据库的唯一约束,不是应用层的先查后写。
- 工具可视化这一半:中间过程要暴露,理由有三条——用户能判断要不要打断(不然他只能盲等);等待变得可以忍受(十几秒的转圈会让人刷新页面,而刷新意味着这一轮的钱白花);出问题时用户能说清「卡在查订单那一步」,客服和你都省事。
- 可预期的追问:全都暴露会不会泄露内部实现?会,所以要过滤——工具名用人话不用函数名,参数里的用户标识、内部 id、密钥一律不显示,错误显示归类后的原因而不是原始堆栈。**可视化的是过程,不是内部结构。**
Key points
- Retry carries the idempotency key minted on the first attempt; the backend hits a unique constraint and reattaches to the existing run.
- The rule for minting a new key is whether the content changed — same message keeps the key, edited content gets a new one.
- The same pattern recurs in write dedup, cron ticks, cross-service delivery and frontend retry, always arbitrated by a database unique constraint.
- Make tool calls visible so users can decide whether to interrupt, tolerate the wait, and describe where it hung.
- But filter: human-readable tool names, no internal ids or secrets in the arguments, classified error reasons instead of raw stack traces.
答题要点
- 重试要带客户端首次生成的幂等键,后端用唯一约束命中后把已有 run 的流接回来,不新建。
- 换不换键的判据是「内容变没变」:同一句话重试用同一个键,改了内容才换新键。
- 同一招在落库、定时任务、跨服务调用、前端重试四处复用,最终裁判永远是数据库的唯一约束。
- 工具调用要可视化:用户才能判断要不要打断、等待变得可忍受、出问题时说得清卡在哪一步。
- 但要过滤:工具名用人话、参数里的内部 id 与密钥不显示、错误显示归类原因而不是原始堆栈。
D26 System Design Deep Dive: Agent Platforms / Customer-Support Agents / Multi-Tenancy / Cost Control
You get 35 to 40 minutes for a system design round. How do you budget that time, and why is drawing the architecture not step one?系统设计环节只有 35 到 40 分钟,你会怎么分配时间?为什么第一步不是画架构图?
Common in ChinaCommon overseasBasic#system-design#interview-processHow to reason about it · think before answering
- This question tests pacing, not knowledge. Interviewers ask it because the previous candidate spent 25 minutes on the architecture diagram and left five each for deep dives and trade-offs — which is exactly where the rubric puts most of the weight.
- Give the structure with explicit time boxes: 5 minutes clarifying requirements, 3 minutes on capacity and cost estimation, 8 minutes sketching the architecture, 15 minutes going deep on two or three areas, 5 minutes on trade-offs. Naming actual minute counts is itself worth points, because it shows you have rehearsed against a clock.
- Then answer the 'why not draw first' half head on: a one-line prompt leaves five things unknown — daily actives, latency budget, cost budget, multi-tenancy, and failure tolerance — and every one of them changes the architecture materially. Drawing first means at best you guessed right, at worst the interviewer realizes twenty minutes in that you solved a different problem. An analogy lands it: the client said 'we need an office building' and you unrolled construction drawings before hearing whether the budget is twenty million or two hundred million.
- Add the situation that comes up almost every time: you start asking and the interviewer says 'just assume something'. That is not permission to skip clarification, it is an invitation to state a number and its justification. The right reply is 'then I will assume 10k daily actives at five turns each, and I will flag in the final step what changes at 100k'. You keep the pacing and turn the assumption into a traceable premise.
- Close by explaining how step four is prepared: those 15 minutes cannot be improvised. Have three deep-dive packages ready — state and ordering, cost and rate limiting, failure and retry — so any pick is covered. Saying you prepared three directions signals rehearsal better than winging one.
- Expect the follow-up: what if you run out of time? Cut step three, never step five. An unfinished sketch can be closed with 'the rest follows the standard pattern, happy to come back to it', but dropping the trade-off section makes you indistinguishable from someone who memorized an architecture.
分析过程 · 先想清楚再作答
- 这题考的不是知识,是节奏感。面试官问它,通常是因为上一位候选人在架构图上讲了 25 分钟,深入和权衡各剩五分钟——而评分表上分数最重的恰恰是后两步。
- 先给结构,五步加时间盒:需求澄清 5 分钟、容量与成本估算 3 分钟、架构草图 8 分钟、深入 2 到 3 个点 15 分钟、权衡与取舍 5 分钟。给得出具体分钟数本身就是分数,因为它说明你掐过表。
- 然后正面回答「为什么不先画图」:一句话的题干里,日活、延迟预算、成本预算、是否多租户、失败可容忍度这五件事全是未知的,而它们每一个都会实质改变架构。不问就画,最好的结果是运气好蒙对,最坏的结果是二十分钟后面试官发现你解的是另一道题。用一个类比说清:甲方只说「我要一栋办公楼」,你就展开施工图,而他连预算是两千万还是两个亿都没讲。
- 补一条几乎每次都会遇到的现场情况:你开始问,面试官说「你先自己假设一个」。这不是让你别问了,是让你自己给一个数并说出依据。正确接法是「那我按日活 1 万、人均 5 轮算,如果实际是十万级我会在最后一步说明哪里要改」——既守住了节奏,又把假设变成了可追溯的前提。
- 最后主动交代第四步的准备方式:深入的 15 分钟不能临场想,要提前备好三个「深入包」(状态与保序、成本与限流、失败与重试),面试官挑哪个都有货。说得出「我提前准备了三个方向」,比现场硬讲一个更能体现你练过。
- 可以预期的追问:如果时间不够怎么办?答案是砍第三步而不是砍第五步——草图讲不完可以说「其余按常规做,需要的话我们回头补」,但权衡那 5 分钟一旦砍掉,你就和一个只会背架构的人没有区别。
Key points
- Five steps with time boxes: clarify 5, estimate 3, sketch 8, deep dive 15, trade-offs 5
- Do not sketch first because DAU, latency budget, cost budget, multi-tenancy and failure tolerance all change the architecture
- When told to 'just assume something', state a number with its justification instead of skipping clarification
- Fill the 15-minute deep dive from three pre-prepared packages: state and ordering, cost and rate limiting, failure and retry
- If time runs short, cut the sketch, never the trade-offs — almost nobody does that section, so doing it stands out
答题要点
- 五步加时间盒:澄清 5 分钟、估算 3 分钟、草图 8 分钟、深入 15 分钟、权衡 5 分钟
- 不先画图,是因为日活、延迟预算、成本预算、是否多租户、失败可容忍度这五件事都会实质改变架构
- 面试官说「你先假设一个」时,要自己给数并说出依据,而不是跳过澄清
- 深入的 15 分钟要靠提前备好的三个「深入包」:状态与保序、成本与限流、失败与重试
- 时间不够时砍草图不砍权衡——权衡那 5 分钟几乎没人做,做了就是加分
System design: design an e-commerce customer support agent. It looks up orders and shipments, drafts refunds by policy, answers product and policy questions, and escalates to a human when it cannot resolve the issue.系统设计:请设计一个电商客服 Agent。它要能查订单和物流、按规则拟退款方案、回答商品与政策问题,并在搞不定时转人工。
Common in ChinaCommon overseasDeep dive#system-design#customer-support#escalationHow to reason about it · think before answering
- Start by separating this from 'design an agent platform', or you will answer an infrastructure question. The platform question is about running execution reliably; this one is about not trapping users inside a bot. The rubric lives in the business exits, not the message bus. So pin the thesis in your first sentence: every conversation must end in exactly one of three exits — self-served, handed to a human, or filed as a ticket.
- Clarify for 5 minutes, asking four things: daily actives and concurrent sessions (does execution need to be split out), whether human agents work nights (does exit three exist), whether the agent executes refunds or only drafts them (do you need an approval tier), and how large the knowledge base is and how often it changes (is retrieval the center of this problem). The third question matters most: it decides whether this system has irreversible side effects.
- Estimate for 3 minutes, out loud: 2000 input plus 500 output per turn, input is 2000 over a million times $0.15 which is $0.0003, output is 500 over a million times $0.60 which is also $0.0003, so about $0.0006 per turn. 10k daily actives at five turns is 50k turns, roughly $30 a day and $900 a month in model spend, machines excluded. For concurrency, a peak factor of 3 and 6 seconds per turn gives about 11 in-flight executions at peak, which is 3 worker replicas at 4 concurrent each. State the arithmetic before the result — the interviewer's next line is always 'where did that number come from'.
- Sketch for 8 minutes, four blocks: ingress does auth, rate limiting, persistence and publish, then returns immediately; execution pulls from the bus, runs the agent loop, and streams sequenced fragments back; storage is sessions, runs and messages plus a chunk table for the knowledge base; observability is tracing plus a cost ledger. Then mark on the diagram which node decides between the three exits — that single annotation tells the interviewer you are answering the support question rather than the generic platform one.
- Go deep for 15 minutes, starting with escalation because that is the crux. The criteria must be quantified, any one of four triggering a handoff: two consecutive unresolved turns, an explicit user request, an amount above the auto-execution ceiling (50 CNY in our setup), or a sentiment keyword hit. Then describe the handoff payload: not forty turns of raw transcript, but a structured summary — the user's ask in one line, verified facts, actions already taken, and the failure reason, with a link to the full transcript. Cover the knowledge base in one line (hybrid search, rerank, inline citations) and spend the weight on 'when the citation set comes back empty, take exit two or three rather than letting the model invent an answer' — that is the sentence they will push on. Cover multi-turn in one line too: compress once history passes 70% of budget, cut on a turn boundary, and merge a change of mind within 30 seconds into the same execution.
- Trade-offs for 5 minutes, three points: bias the escalation threshold toward escalating, because a false handoff costs one human conversation while trapping a user costs a churned customer and a bad review — different orders of magnitude. Drafting refunds instead of executing them trades one human approval for an entire class of irreversible incidents. And name what breaks the design: once the agent team is large enough to need skill-based routing and queueing, escalation stops being a boolean and becomes its own scheduling system.
- Expect, in rough order: does 'I want to file a complaint' count as a sentiment hit (yes, and track that class separately — it is a product signal); should the agent keep listening after handoff (yes, to summarize and prompt the human, but not to speak); how do you stop users being bounced repeatedly (allow one handoff per conversation, then file a ticket); and what happens to old answers when the knowledge base changes (cite chunk ids and versions so you can trace which revision was wrong).
分析过程 · 先想清楚再作答
- 先说这题和「设计一个 Agent 平台」的区别,否则你会把它答成一道基础设施题。平台题考的是怎么把执行跑稳,这题考的是**怎么保证不把用户困在机器人里**——面试官心里的评分点在业务出口上,不在消息总线上。所以主线要一开口就钉死:任何一通会话最后只能落到三条出口之一,自助解决、转人工、留工单。
- 第一步澄清 5 分钟,问四件事:日活与并发会话数(决定要不要拆执行层)、人工坐席有没有夜班(决定出口三存不存在)、退款是 Agent 直接执行还是只拟方案(决定要不要人工确认档)、知识库有多大且多久更新一次(决定检索是不是本题的重点)。第三个问题尤其关键,它直接决定这道题是不是带副作用。
- 第二步估算 3 分钟,现场算:单轮 2000 输入加 500 输出,输入 2000 除以一百万乘 0.15 等于 0.0003 美元,输出 500 除以一百万乘 0.60 也等于 0.0003 美元,一轮约 0.0006 美元;日活 1 万、人均 5 轮就是 5 万轮,一天约 30 美元、一个月约 900 美元,模型费不含机器。并发按峰谷比 3、单轮 6 秒算,峰值在途约 11 次执行,每个 worker 并发 4 就是 3 个副本。报数字之前先报算式,面试官插的那句一定是「这个数怎么来的」。
- 第三步草图 8 分钟,四块:接入层只做鉴权、限流、落库、投递并立刻返回;执行层从消息总线取活跑 Agent 循环、片段带序号回传;存储是会话、执行、消息三张表加一张知识库切块表;可观测是 tracing 加成本台账。在这张图上额外标出三条出口的分叉点在哪一个节点上——这是本题独有的一笔,画上去面试官立刻知道你答的是客服而不是通用平台。
- 第四步深入 15 分钟,优先讲转人工这一支,因为它是本题的题眼。判据必须量化,四条任一命中就转:连续 2 轮未解决、用户明确要求、涉及金额超过自动执行上限(本课口径 50 元)、情绪词命中。接着讲交接形状——不是把 40 轮原文丢给客服,而是一段结构化摘要:用户诉求一句、已核实事实几条、Agent 已做过的动作、失败原因,附原始对话链接。知识库那一支一句话带过混合检索加重排加引用,重点落在「引用为空时走出口二或三,而不是让模型编一个答案」,这是最容易被追的一句。多轮那一支同样一句话:历史超七成预算触发压缩且切口对齐到一轮开头,用户中途改口则 30 秒内合并进同一次执行。
- 第五步权衡 5 分钟,说三件事:转人工的判据宁可偏松,因为误转的代价是一次人工会话,把用户困住的代价是一个流失客户加一条差评,两者不在一个量级;退款只拟方案不直接执行,是拿一次人工点头换掉一整类不可逆事故;以及什么规模会推翻这个设计——坐席团队大到需要技能路由和排队策略时,转人工就不再是一个布尔判断,而是另一套调度系统。
- 可以预期的追问,按频率排:用户说「我要投诉」算不算情绪词命中(算,且这一类要单独统计,它是产品问题的信号);转人工之后 Agent 还要不要继续在旁边听(要,用来生成小结和给坐席提示,但不允许再发言);怎么防止用户被反复转来转去(同一通会话只允许转一次,第二次直接留工单);以及知识库更新后旧答案怎么办(回答里带引用编号和版本,出问题能倒查是哪一版说错的)。
Key points
- Thesis: every conversation ends in exactly one of three exits — self-served, escalated to a human, or filed as a ticket
- Clarify four things: concurrent sessions, whether humans cover nights, whether refunds are executed or only drafted, and knowledge base size and churn
- Estimate with arithmetic: about $0.0006 per turn, so 10k DAU at five turns is roughly $30/day and $900/month; peak concurrency about 11, meaning 3 worker replicas
- Quantify escalation: two consecutive unresolved turns, an explicit request, an amount over the auto-execution ceiling, or a sentiment keyword
- Hand over a structured summary — ask, verified facts, actions taken, failure reason — plus a transcript link, not forty raw turns
- When retrieval returns no citations, take exit two or three instead of letting the model improvise; answers carry citation ids
- Reuse compression and 30-second merge for multi-turn; draft refunds rather than executing them, trading one approval for a class of irreversible incidents
- Trade-off: bias toward escalating, because a false handoff and a trapped user cost different orders of magnitude
答题要点
- 主线一句话:任何一通会话只能落到三条出口之一——自助解决、转人工、留工单
- 澄清必问四件事:并发会话数、人工有没有夜班、退款是执行还是只拟方案、知识库规模与更新频率
- 估算带算式:单轮约 0.0006 美元,日活 1 万人均 5 轮约 30 美元一天、900 美元一月;峰值并发约 11、3 个 worker 副本
- 转人工判据必须量化,四条任一命中:连续 2 轮未解决、用户明确要求、金额超自动执行上限、情绪词命中
- 交接给人工的是结构化摘要(诉求、已核实事实、已做动作、失败原因)加原始对话链接,不是 40 轮原文
- 知识库检索不到时走出口二或三,绝不让模型自由发挥编答案;回答带引用编号
- 多轮沿用压缩与 30 秒打断合并,退款只拟方案不直接执行,用一次人工点头换掉一类不可逆事故
- 权衡:判据宁可偏松,因为误转和困住用户的代价不在一个量级
For a multi-tenant agent service, how do you design data isolation and billing isolation, and when do you move from a shared table to a dedicated database per tenant?一个多租户的 Agent 服务,数据隔离和计费隔离要怎么设计?什么时候该从共享表升级到独立库?
Common in ChinaCommon overseasIntermediate#system-design#multi-tenancy#isolationHow to reason about it · think before answering
- The hinge is that 'isolation' is plural. Plenty of candidates answer only data isolation, but the layer that actually breaks in production is resources: one tenant's spike starves everyone else, no rows leak, and users still complain. Open with all three — data, resources, billing — and note that each missing layer maps to its own class of incident.
- On data, one sentence separates people who shipped this from people who read about it: 'every query carries tenant_id' versus 'row-level security is the backstop'. The first eventually misses a query, and the one it misses is always the newest, least-tested feature. The correct framing is that RLS is the gate and the application-level where clause is just an optimization — the same reasoning as idempotency being adjudicated by a database unique constraint. Whatever the data layer can enforce should not depend on everyone remembering.
- On resources, give two concrete things: a rate-limit bucket per tenant, and workers sharded by a hash of the tenant id. That is the same sharding mechanism used to preserve per-user ordering, with a different hash input and a different purpose — containing spikes rather than serializing. Noisy neighbors hurt more in agent workloads because a single execution can run thirty seconds, so a thousand queued items from one tenant leaves everyone else waiting.
- Billing is the simplest and the most often forgotten: add a tenant column to the token usage ledger and tag every write. Invoicing, quotas and over-budget degradation all hang off it. Bring the cost figures too — roughly $0.0006 per turn at 2000 in and 500 out, about $900 a month at 10k daily actives and five turns each — because quoting per-tenant economics shows you actually ran the numbers.
- Then the escalation criteria, the second discriminator. Three tiers: shared table with a tenant column, schema per tenant, database per tenant. The trigger is not tenant count, it is whether a single tenant can starve the rest and whether there is a hard compliance requirement. 'Split the database past a hundred tenants' is guesswork: a hundred small tenants share a table happily, while one regulated enterprise customer may require physical separation on its own. State the cost too — a database per tenant looks clean, but migrations, backups, monitoring and connection pools all multiply by tenant count, so operational cost jumps rather than scaling linearly.
- Expect the sharpest follow-up: does the idempotency key change under multi-tenancy? The algorithm does not, but its scope must include the tenant id. Without it, two tenants whose clients independently produce the same string — both using order id order-1024 — collide, and the later request is rejected by the unique constraint as a duplicate. One tenant's write is swallowed by another tenant's history, both logs look perfectly normal, and it is the hardest class of multi-tenant bug to find.
分析过程 · 先想清楚再作答
- 这题的题眼在「隔离」是复数。只答数据隔离的候选人非常多,而多租户翻车最多的其实是资源那一层——一个租户的洪峰打穿别人的处理能力,数据一条都没串,用户照样投诉。所以第一句先把三层摆出来:数据、资源、计费,缺哪一层对应一类事故。
- 数据这一层,判断一个人有没有真做过就看一句话:他说「每条查询都带 tenant_id」还是「靠数据库的行级安全兜底」。前者迟早会漏一处,而漏掉的那处通常是最新加、最没被测过的功能。正确说法是行级安全是闸门,应用层那句 where 只是优化——这和幂等的最终裁判必须是数据库唯一约束,是同一种思路:能在数据层强制的,不要指望每个人写代码时都记得。
- 资源这一层给两件具体的东西:每个租户一个独立限流桶,以及 worker 按租户标识哈希分片。分片这一招和「按用户哈希保住同一用户顺序」是同一套机制,只是哈希的输入换了,目的从保序变成隔离洪峰。Agent 场景里噪声邻居格外突出,因为单次执行可能跑三十秒,一个租户灌一千条进来,别人就得排队。
- 计费这一层最简单也最容易漏:token 用量台账加一列租户标识,写入时打标。账单、配额、超支降级三件事全靠它。顺带说一句成本口径——单轮 2000 输入加 500 输出约 0.0006 美元,日活 1 万人均 5 轮约每月 900 美元,能报出这个量级说明你真的算过每租户成本。
- 然后回答升级判据,这是本题的第二个区分点。三档是共享表加租户列、schema 级、库级;**判据不是租户数量,是「有没有单个租户能把别人拖垮」和「有没有合规硬要求」**。答「超过一百个租户就该分库」是典型的凭感觉,因为一百个小租户共享一张表毫无问题,而一个受监管的大客户哪怕只有一个也可能必须物理隔离。代价要一起说:库级隔离看着干净,但迁移脚本、备份、监控、连接池全部乘以租户数,运维成本是陡增不是线性。
- 可以预期的追问,也是最见功力的一问:幂等键在多租户下要不要变?答案是键的算法不用变,但**作用域必须带上租户标识**。不带的话两个租户的客户端各自生成了同一个字符串(都用订单号 order-1024),后来那个会被唯一约束当成重复请求挡掉——一个租户的写入被另一个租户的历史请求吞掉,两边日志都完全正常,是多租户里最难查的一类 bug。
Key points
- Three parallel layers, each missing one causing its own class of incident: data, resources, billing
- Data isolation is backstopped by row-level security; the application where clause is only an optimization
- Resource isolation is a per-tenant rate-limit bucket plus sharding workers by tenant hash, aimed at noisy neighbors
- Billing isolation is a tenant column on the usage ledger, powering invoices, quotas and degradation
- Escalate to schema or database isolation based on starvation risk and compliance mandates, not tenant count
- Per-tenant databases multiply migrations, backups, monitoring and connection pools — operational cost jumps
- The idempotency key algorithm stays, but its scope must include the tenant id or identical keys across tenants collide
答题要点
- 三层隔离并列,缺一层对应一类事故:数据、资源、计费
- 数据靠行级安全兜底,应用层的 where 只是优化——能在数据层强制的不要靠人记得
- 资源是每租户独立限流桶加按租户标识哈希分片,防的是噪声邻居而不是数据串
- 计费是台账加一列租户标识,账单、配额、超支降级全靠它
- 升级到 schema 级或库级的判据是「单租户能否拖垮别人」与「有没有合规硬要求」,不是租户数量
- 库级隔离的代价是迁移、备份、监控、连接池全部乘以租户数,运维成本陡增
- 幂等键算法不变,但作用域必须带租户标识,否则两个租户的同名键会互相挡掉请求
An agent system's model spend is out of control. Which levers do you pull, in what order, and roughly how much does each save?一个 Agent 系统的模型成本失控了,你会从哪几个层面着手控制?每一层大概能省多少?
Common in ChinaCommon overseasIntermediate#system-design#cost#capacity-planningHow to reason about it · think before answering
- The reflex answer is 'switch to a cheaper model', and it is also the easiest one to get killed on: the follow-up is 'how do you know quality did not drop', and without an offline eval set and a comparison run you are exposed. The right opening is 'look at the ledger first' — slice by user, by day and by model to find which dimension is growing. Locate before you act.
- Second, put the baseline on the table, because cost talk without a baseline is noise. At 2000 input and 500 output tokens per turn, input is 2000 over a million times $0.15 which is $0.0003, output is 500 over a million times $0.60 which is also $0.0003, so about $0.0006 per turn. 10k daily actives at five turns is 50k turns, roughly $30 a day and $900 a month.
- Then give five layers ordered by the cost you pay, not by the savings: caching and prompt caching, tiered model routing, context compression, step and tool budget caps, rate limiting and degradation. The ordering is part of the answer, because it also communicates your rollout sequence.
- Attach a number derived from the baseline to each layer. Caching at a 15% hit rate takes $900 to roughly $765. For tiered routing, state the precondition honestly: it is the only layer that can change the order of magnitude, but only if your baseline runs a flagship model — if you already run the cheapest tier there is nothing left to squeeze. Saying that out loud is far more credible than inventing a savings percentage. Compression takes input from 2000 to 1200 tokens, so $0.00048 per turn, about $720 a month, a 20% cut.
- Layer four is usually mis-sold as savings; what it actually buys is predictability. With a cap of five tool calls per subtask, per-turn cost finally has a ceiling: five calls each feeding back 800 tokens pushes input to 6000, so $0.0012 per turn, exactly double the baseline — and with no cap there is no ceiling at all. The right phrasing is 'this does not save money, it makes the bill predictable'.
- Layer five is rate limiting and degradation, last because it costs the most: $900 across 10k daily actives is about $0.09 per user per month, so a $1 monthly hard cap is invisible to real users and only stops scripted abuse. The nuance is degrade before refusing — this is the only layer users can feel.
- Expect the follow-up: which layer first? Say layers one and three, because they only touch your own code, change no product promise, and need no quality re-validation, whereas tiered routing needs an eval set and rate limiting needs product buy-in.
分析过程 · 先想清楚再作答
- 这题最容易脱口而出的答案是「换个便宜模型」,也是最容易被追死的答案——面试官紧跟着就问「你怎么知道换了质量不掉」,答不出离线评估集和对比实验就露馅了。正确的第一句是「先看台账」:按用户、按天、按模型各切一刀,找出是哪一维在涨。先定位再动手,这是工程习惯。
- 第二步是把基准摆到桌上,没有基准的成本讨论全是废话。单轮 2000 输入加 500 输出,输入 2000 除以一百万乘 0.15 等于 0.0003 美元,输出 500 除以一百万乘 0.60 也等于 0.0003 美元,一轮约 0.0006 美元;日活 1 万、人均 5 轮就是 5 万轮,一天约 30 美元、一个月约 900 美元。
- 然后给五层,排序的依据是**你要付出的代价从小到大**,不是省钱多少:缓存与 prompt cache、模型分级路由、上下文压缩、步数与工具预算上限、限流与降级。这个顺序本身就是答案的一部分,因为它同时说明了你的落地顺序。
- 每层配一个从基准推出来的数字。缓存按一成半命中估,900 降到 765 左右。分级路由要诚实说清前提:它是唯一能改数量级的一层,但前提是你的基准用的是旗舰模型;基准已经是最便宜那档时这一层榨不出东西——主动说破这一条,比硬编一个省钱比例可信得多。上下文压缩把输入从 2000 压到 1200,单轮变成 0.00048 美元,一个月 720 美元,降两成。
- 第四层最容易被讲成「省钱」,其实它买的是**可预测**:给每个子任务设 5 次工具调用上限之后,单轮成本才有上界——调满 5 次、每次结果回灌 800 token,输入涨到 6000,单轮 0.0012 美元,正好是基准的两倍;没有上限时这个数字没有上界。这一层的正确说法是「我不是靠它省钱,我是靠它让账单可以被预测」。
- 第五层是限流与降级,代价最大所以放最后:900 美元摊到 1 万日活是每人每月 0.09 美元,给单用户设 1 美元硬顶,正常用户碰不到,挡的是脚本刷接口那种极端户。要点是超预算先降档再拒绝,而不是直接拒绝——它是五层里唯一用户能感觉到的一层。
- 可以预期的追问:这五层里哪一层最先做?答「第一层和第三层」,因为它们只改自己的代码、不动产品承诺、也不需要重新验证质量;而分级路由要配离线评估集,限流要配产品沟通,都不是当天能上的。
Key points
- Open with 'look at the ledger', not 'use a cheaper model': slice by user, by day and by model to locate the growth
- Set a baseline: about $0.0006 per turn, roughly $30/day and $900/month at 10k DAU and five turns
- Five layers ordered by cost to you: caching and prompt cache, tiered routing, context compression, step and tool budget caps, rate limiting and degradation
- Tiered routing is the only order-of-magnitude lever, but only if the baseline is a flagship model — say so when it is not
- Compression from 2000 to 1200 input tokens gives $0.00048 per turn, about $720/month, a 20% cut
- Tool budget caps buy predictability: with a cap the per-turn ceiling is $0.0012, without one there is no ceiling
- Rate limiting comes last because users feel it; degrade before refusing
答题要点
- 第一句不是「换便宜模型」,是「先看台账」:按用户、按天、按模型各切一刀定位是哪一维在涨
- 先立基准:单轮约 0.0006 美元,日活 1 万人均 5 轮约每天 30 美元、每月 900 美元
- 五层按代价从小到大:缓存与 prompt cache、模型分级路由、上下文压缩、步数与工具预算上限、限流与降级
- 分级路由是唯一能改数量级的一层,但前提是基准用的是旗舰模型;基准已经最便宜时要诚实说没得省
- 上下文压缩把输入从 2000 压到 1200,单轮 0.00048 美元、每月 720 美元,降两成
- 工具预算上限买的是可预测:有上限时单轮上界是 0.0012 美元,没上限时没有上界
- 限流降级放最后,因为它是唯一用户能感觉到的一层;超预算先降档再拒绝
D27 Resume and Project Packaging: STAR, README, Architecture Diagrams, a Demo Video, an English Resume
Walk me through a technical project of yours using the STAR framework.请用 STAR 法则讲一个你做过的技术项目。
Common in ChinaCommon overseasBasic#behavioral#star#resumeHow to reason about it · think before answering
- This question tests whether you can control information density, not whether you remember four letters. The interviewer has heard STAR dozens of times; what he is actually timing is how long you spend on background versus on what you personally did, and whether you land on a number he can probe.
- Decide the time split before you open your mouth: 20 seconds of situation, 15 of task, 90 of action, 25 of result. The classic failure is spending 90 seconds on situation — it feels safe because it says nothing about your ability, so people hide there.
- In those 90 seconds of action, say 'I', not 'we'. Summarize the team's work in one sentence, then cut straight back to 'my piece was X, and the way I did it was Y'. If your boundary with the rest of the team is unclear, the story is scored as unverifiable.
- The result has to be a before-and-after number, and you volunteer the measurement conditions with it: 'shard utilization went from 1 of 256 to all 256, and the largest bucket dropped from 2000 to 19 — measured locally with 2000 simulated users across 256 shards.' Naming the conditions is not hedging; it shows you know what you measured.
- If it is a personal or course project, say so inside the first 20 seconds rather than waiting to be asked. Volunteering the origin makes your numbers more credible, not less; being caught hiding it forces the interviewer to re-weigh everything you said before.
- Expect two follow-ups: 'how did you measure that?' and 'does this still hold at ten times the scale?' The first tests honesty, the second tests judgment — answer the second by naming the scale at which you would throw this design away.
分析过程 · 先想清楚再作答
- 这题在考「你会不会控制信息密度」,不是考你记不记得 STAR 四个字母。面试官已经听过几十遍 STAR,他真正在数的是:你花了多少时间讲背景、多少时间讲你自己做了什么、最后有没有一个能被追问的数字。
- 先定时间分配再开口,这是可以现场执行的一条纪律:情境 20 秒、任务 15 秒、行动 90 秒、结果 25 秒。绝大多数人的失败模式是情境讲了 90 秒——那部分听起来最安全,因为不涉及你的能力,所以人会不自觉地躲在那里。
- 行动那 90 秒里只讲你亲手做的部分,主语必须是「我」。团队做了什么用一句话带过,然后立刻切回「我负责的是其中的 X,我的做法是 Y」。说不清「我和别人的边界在哪」,这条经历在评分表上会被打成不可验证。
- 结果必须落到一个带前后对照的数字,并且主动补一句测量条件。比如「分片利用率从 256 个里只占 1 个变成全占满,最大桶从 2000 条降到 19 条,这是本地单机、2000 个模拟用户、256 个分片的自检结果」。补测量条件不是示弱——它把「我知道自己测的是什么」这件事直接摆出来了。
- 如果这段经历是学习项目或课程项目,在情境那 20 秒里就说清楚,不要等到被追问。主动交代来源的人,后面报的数字反而更容易被相信;藏着掖着被问出来,之前讲的全部要被重新掂量一遍。
- 可以预期的追问:「这个数字是怎么测的?」以及「如果规模再大十倍,这个做法还成立吗?」第一个考真实性,第二个考边界感——答第二个时要主动说出「在什么规模下我会推翻现在这个设计」,这一句几乎没人说,说了就是加分。
Key points
- Budget the time before speaking: 20s situation, 15s task, 90s action, 25s result — never let background eat half the answer
- Say 'I' in the action section and draw a clear line between your work and the team's
- End on a before-and-after number and volunteer how it was measured
- Disclose that it is a personal or course project up front, not under questioning
- Close by naming the scale at which the design would break — it signals judgment
答题要点
- 开口前先分配时间:情境 20 秒、任务 15 秒、行动 90 秒、结果 25 秒,别让背景吃掉一半时长
- 行动部分主语是「我」,明确说出自己和团队的边界
- 结果给一个带前后对照的数字,并主动补上测量条件
- 学习项目在情境阶段就主动交代,不等追问
- 结尾主动加一句「在什么规模下这个设计会失效」,把边界感摆出来
Tell me about the most challenging project you have worked on. What made it hard?讲讲你做过的最有挑战的一个项目,难在哪里?
Common in ChinaCommon overseasDeep dive#behavioral#project-storytellingHow to reason about it · think before answering
- The hinge is the word 'challenging', and almost everyone falls into the same trap: treating 'a lot of work' as a challenge. Three months of overtime proves stamina, not judgment. The interviewer wants to see how you decide under incomplete information.
- Pick the story first, because it caps everything after it: choose the project where you can name what you gave up. Hard test — if your answer is only 'I did A and it worked', with no 'I chose A over B and paid C for it', pick a different project.
- Order the answer as difficulty, decision, cost, outcome — not chronologically. Chronology drags the listener through a diary; leading with the difficulty pins their attention on the first sentence. 'With three replicas consuming in parallel, one user's messages arrived out of order' beats 'the project started in March'.
- When describing the difficulty, explain why it could not be solved by reading the docs. Real challenges carry conflicting constraints: parallel replicas for throughput versus strict per-user ordering. Surfacing that conflict is what makes the difficulty credible.
- Do not end on pure success. Volunteer one sentence on what you would change today — this is the highest-signal line available on this question, because it shows you kept thinking after shipping.
- Expect: 'did you consider alternatives?' It is nearly guaranteed, so prepare the option you rejected and an engineering reason for rejecting it — latency, cost, operational load — never 'it just felt wrong'.
分析过程 · 先想清楚再作答
- 这题的题眼在「挑战」这两个字上,而它有一个几乎所有人都会踩的陷阱:把「工作量大」当成挑战。加了三个月班、写了两万行代码,这些证明的是耐力,不是判断力。面试官想看的是你在信息不足的情况下怎么做决定。
- 先做选题,这一步决定了后面的天花板:选那个**你能说出「我放弃了什么」**的项目。判据很硬——如果你的答案里只有「我做了 A,效果很好」,没有「我在 A 和 B 之间选了 A,代价是 C」,那这个项目就不适合回答这道题,换一个。
- 组织顺序建议用「困难 → 我的判断 → 代价 → 结果」,而不是时间顺序。时间顺序会把听众拖进流水账;从困难切入,第一句话就把对方的注意力钉住了。比如「三个副本同时消费的时候,同一个用户的消息顺序会乱」,比「这个项目是三月份开始的」有效得多。
- 描述困难时要给出「为什么这不是查一下文档就能解决的」。真正的挑战都带着约束冲突:既要多副本并行提高吞吐,又要同一用户严格保序——这两个诉求天然打架,所以才需要设计而不是查资料。把这层冲突讲出来,难度就立住了。
- 结果那部分不要只报成功。**主动说一句「现在回头看,我会改哪里」**,这是这道题上区分度最大的一句话。它表明你在项目结束之后还继续想过这件事,而不是交付完就翻篇了。
- 可以预期的追问:「当时有没有考虑过别的方案?」这几乎是必问。所以准备答案时要备好那个被你放弃的方案,以及放弃它的具体理由——理由要是工程性的(延迟、成本、运维复杂度),不能是「感觉那样不好」。
Key points
- Challenge means judgment, not volume — overtime and lines of code are not difficulty
- Pick a story where you can say 'I chose A over B and paid C for it'
- Structure it as difficulty, decision, cost, outcome — never as a chronological diary
- Name the conflicting constraints (parallel replicas versus strict per-user ordering) to make the difficulty real
- Close with what you would change today, and have the rejected alternative plus an engineering reason ready
答题要点
- 挑战 = 判断力,不是工作量;别拿加班和代码行数当难度
- 选题判据:这个项目你能说出「我在 A 和 B 之间选了 A,代价是 C」
- 按「困难 → 判断 → 代价 → 结果」组织,不要按时间顺序讲流水账
- 把约束冲突讲出来(比如既要多副本并行、又要同一用户保序),难度才立得住
- 结尾主动说「现在回头看我会改哪里」,并备好那个被放弃的方案和工程性的理由
Suppose I open one of your GitHub projects — what should a good README show me?我们点开了你 GitHub 上的项目,你觉得一份好的 README 应该让我看到什么?
Common in ChinaCommon overseasIntermediate#behavioral#documentation#portfolioHow to reason about it · think before answering
- On the surface this is about documentation conventions; underneath it tests reader awareness. Reciting a list of headings sounds like a template. They want to hear that you know who the reader is, how much time he has, and what he is looking for.
- Define the reader before listing sections: someone skimming a README has about three minutes and has no intention of cloning the repo. So the first screen must answer 'what is this' and 'can it run'; everything deep goes below.
- Then give the structure, naming the reader each part serves: one-line positioning (the resume screener), architecture diagram (anyone building a mental model), quick start (anyone verifying it runs), key design decisions (the interviewer), known limitations (the interviewer), directory guide and license (people who will actually read the code).
- Put the weight on two sections. Quick start has a hard bar — running in three commands or fewer; more than that means hidden setup. Verify it on a machine that has never run the project, not on your own. Key design decisions must each state what you gave up, because that is the one part no template can supply.
- Known limitations deserve a sentence of their own: stating boundaries is not exposing weakness, it demonstrates self-awareness and honesty at once. And once you have said it, it is hard to use against you — at most they ask which gap you would close first, which you already prepared.
- Expect the follow-up: 'which section took you the longest?' Answer 'key design decisions' and then walk through one on the spot. The question is nominally about READMEs, but it is an invitation to talk about your project — take it.
分析过程 · 先想清楚再作答
- 这题表面在问文档规范,实际在考「你有没有读者意识」。答成一串小标题清单(简介、安装、使用、贡献指南)会显得像背模板;面试官想听的是你知道读者是谁、他有多少时间、他在找什么。
- 先把读者说清楚再列结构,这一步就能拉开差距:看 README 的人预算大约三分钟,而且不打算 clone 下来跑。所以第一屏必须解决「这是什么」和「能不能跑」,深入的东西往后放。
- 然后给结构,并且为每一段说出它服务的是哪个读者:一句话定位(筛简历的人)、架构图(想快速建立心智模型的人)、快速开始(想验证能不能跑的人)、关键设计决策(面试官)、已知限制(面试官)、目录导读与许可(真的要读代码的人)。
- 重点落在两段上。「快速开始」的硬指标是三条命令之内跑起来,超了说明有隐性依赖;判据是拿一台没跑过的机器照着敲一遍,而不是在自己机器上试。「关键设计决策」每条要含「放弃了什么」,因为这是唯一无法从模板抄来的部分。
- 「已知限制」值得单独说一句:主动写出边界不是暴露短板,而是同时证明了自我认知和诚信。而且你先说了,对方就很难再拿它当把柄,最多顺着问「上生产你会先补哪个」——那是你准备好的题。
- 可以预期的追问:「你的项目 README 里最花时间的是哪一段?」答「关键设计决策」,然后现场讲一条。这题问的是 README,落点其实是让你讲项目,别错过这个递过来的机会。
Key points
- Start from the reader: a three-minute budget and no intention of cloning, so the first screen answers what it is and whether it runs
- Seven sections: one-line positioning, architecture diagram, quick start, three key design decisions, known limitations, directory guide, license
- Quick start must work in three commands or fewer, verified on a machine that has never run it
- Every design decision states what was given up — the one part no template provides, and where interviewers pick their follow-up
- Use Mermaid rather than screenshots: native GitHub rendering, text diffs, and it does not go stale
答题要点
- 先说读者:三分钟预算、不会 clone 下来跑,所以第一屏解决「是什么」和「能不能跑」
- 七段结构:一句话定位、架构图、快速开始、关键设计决策 3 条、已知限制、目录导读、许可
- 快速开始的硬指标是三条命令之内,且要在一台没跑过的机器上验证
- 关键设计决策每条含「放弃了什么」,这是唯一抄不来的部分,也是面试官挑追问的地方
- 架构图用 Mermaid 而不是截图:GitHub 原生渲染、改动是文本 diff、不会过期
Have you applied overseas? How does an English tech resume differ from a Chinese one?你投过海外岗位吗?英文简历和中文简历在写法上有什么不同?
Common in ChinaCommon overseasBasic#behavioral#resume#global-marketHow to reason about it · think before answering
- This looks like a trivia question, but the signal is whether you have actually applied or only heard about it. 'English resumes should be concise' is hearsay; naming what must never appear, and why, sounds like experience.
- Answer in two halves, forbidden items first, style second — the first half is a hard constraint and the second is preference, and leading with the hard part shows you can tell them apart.
- Forbidden: no photo, no age or date of birth, no gender, no marital status, no national ID or household registration, no expected salary. Give the real reason — in the US, Canada and the UK, employers avoid this information to limit hiring-discrimination exposure. Framing it as the employer's compliance concern rather than 'that's just the local habit' is the highest-signal sentence in this answer.
- Style: one page, reverse chronological, every bullet starting with a verb, every bullet quantified, and the tech stack on its own line. Give both sides on verbs — Built, Designed, Reduced, Cut are right; Responsible for, Helped with and Familiar with describe a job description, an assist, and an awareness respectively, none of which is your contribution.
- Add the detail most people miss: always carry units and currency — 'p95 latency 320 ms', not 'latency 320'; '$0.0006 per turn', not '0.0006 per turn'. Overseas interviewers read magnitudes carefully and cannot judge a bare number. Keep tense consistent too: past tense for finished work, present for ongoing.
- Expect: 'did you write it yourself or translate it?' Say you wrote it, and name a concrete step you took — for instance deleting every adjective from the Chinese version before rewriting, because directly translated adjectives read as empty in English.
分析过程 · 先想清楚再作答
- 这题看着像常识题,区分度藏在「你是真投过还是听说过」。只答「英文简历要简洁」是听说过;答得出「哪些东西在英文简历里绝对不能出现,以及为什么」的,才像真做过。
- 拆成两半答,顺序是「不该有的」在前、「该怎么写」在后。因为前者是硬约束,后者是风格偏好,先说硬的显得你分得清轻重。
- 不该有的那一半:不放照片、不写年龄和出生日期、不写性别、不写婚姻状况、不写身份证与户籍、不写期望薪资。原因要说到点子上——在美加英等地,招聘方为了规避雇佣歧视方面的法律风险,收到这些信息反而为难。说出「这是对方的合规顾虑」而不是「国外习惯这样」,是这题最能体现认知深度的一句。
- 该怎么写的那一半是五条格式硬要求:一页、反向时序、每条动词开头、每条带量化结果、技术栈单列一行。动词开头要给正反例——Built / Designed / Reduced / Cut 是对的,Responsible for、Helped with、Familiar with 是三个要避开的开头,因为它们分别在描述职责、描述协助、描述认知,都不是你的贡献。
- 补一条很多人漏掉的:单位和货币要写全(写 p95 latency 320 ms 而不是「延迟 320」,写每轮 0.0006 美元而不是「一轮 0.0006」)。海外面试官对量纲敏感,缺单位的数字他判断不了好坏。时态上也要一致:结束的项目用过去时,在推进的用现在时。
- 可以预期的追问:「你的英文简历是自己写的还是翻译的?」老实答自己写的,并说出你为此做的一个具体动作——比如把中文那份里的形容词全删掉之后重写,因为直译过来的形容词在英文里会显得空。
Key points
- Lead with the hard constraints: no photo, age, gender, marital status, national ID or expected salary
- The reason is the employer's compliance exposure around hiring discrimination, not local custom
- Five format rules: one page, reverse chronological, verb-first bullets, quantified results, tech stack on its own line
- Avoid Responsible for, Helped with and Familiar with; use Built, Designed, Reduced, Cut
- Always carry units and currency, and keep tense consistent — past for finished work, present for ongoing
答题要点
- 先答硬约束:不放照片、年龄、性别、婚姻状况、身份证与户籍、期望薪资
- 原因是对方的合规顾虑(规避雇佣歧视方面的法律风险),不是「国外习惯这样」
- 格式五条:一页、反向时序、动词开头、量化结果、技术栈单列一行
- 动词开头避开 Responsible for、Helped with、Familiar with,改用 Built / Designed / Reduced / Cut
- 单位与货币写全,时态保持一致:结束的项目用过去时,在推进的用现在时
D28 Mock Interview Day: One Full China-Domestic-Style and One Full Overseas-Style Round, Self-Assessment
How do domestic Chinese and overseas tech interview loops differ structurally, and how would you prepare for each?国内和海外技术面试的流程差异主要在哪里?你会怎么分别准备?
Common in ChinaCommon overseasBasic#interview-process#careerHow to reason about it · think before answering
- This looks like trivia, but the discriminator is whether you actually rehearsed against a loop. Answering only 'overseas has behavioral, China has fundamentals drilling' sounds like hearsay.
- Lead with structure, because every other difference follows from it. A domestic loop is usually two or three rounds in a single day with the same people digging deeper each round, and one round of roughly 60 minutes splits into five segments: 3 minutes of self-introduction, 25 of project deep-dive, 20 of live coding, 10 of scenario and fundamentals, 5 of candidate questions. An overseas loop is five independent stages spread over weeks: a 30-minute recruiter screen, 60 minutes of technical/coding, 60 of system design, 45 of behavioral, then team match, each run by different people who score independently and vote at the end.
- Derive preparation from that structure, which is where the answer earns its keep. Same people digging deeper means the domestic loop is decided in that 25-minute deep-dive, so rehearse surviving three layers of follow-up. Independent stages plus a vote means any single overseas round can sink you, so weakest link beats strongest link, especially behavioral, which most engineers never rehearse.
- A third difference is how judgment is recorded: domestic outcomes lean on the interviewer's live impression, while most overseas companies use structured rubrics and written feedback. That makes behaviors which can be written down — narrating while coding, volunteering trade-offs and failure modes — worth more overseas.
- Correct a common misconception before they raise it: the difference is not that overseas skips algorithms. That 60-minute coding round is still an algorithm round; what changes is the explicit requirement to think out loud, where silence itself costs points.
- Expect the follow-up on time allocation: train the overlap first — project deep-dive and system design appear in both loops and give the best return — then specialize, adding two or three reusable STAR stories for overseas, or the habit of naming the edge of your knowledge for domestic rounds.
分析过程 · 先想清楚再作答
- 这题看着像常识题,区分度其实在于你有没有真的按流程准备过。只答「海外有 behavioral、国内有八股」是在复述听说,面试官听不出你排练过。
- 先给结构这条主线,其余差异都是它的推论:国内通常是一天之内两到三轮,同一批人越问越深,单轮 60 分钟出头切成五段——自我介绍 3 分钟、项目深挖 25 分钟、手撕代码 20 分钟、场景与八股 10 分钟、反问 5 分钟;海外是拉长到几周的五个独立环节——recruiter screen 30 分钟、technical/coding 60 分钟、system design 60 分钟、behavioral 45 分钟、team match,每一环由不同的人负责,各判各的,最后合票。
- 由结构推准备策略,这一步才是答案的价值所在:同一批人越问越深,意味着国内的胜负手在项目深挖那 25 分钟,要练的是被追问三层还答得上;独立环节合票意味着海外任何一轮都能单独把你否掉,所以短板比长板重要,尤其是多数人从没排练过的 behavioral。
- 第三条差异是评价载体:国内更依赖面试官当场的主观印象,海外多数公司有结构化的评分维度和书面反馈,所以「边写边讲」「主动说出取舍与失败模式」这类能被写进反馈的行为,在海外权重更高。
- 要主动澄清一个常见误区:差异不是「海外不考算法」。coding 那 60 分钟照样是算法题,区别在于它明确要求你全程出声,沉默本身就会被扣分。
- 可以预期的追问:那准备时间怎么分配?答共同部分先练——项目深挖和系统设计两套流程都要考,投入产出比最高;剩下的按目标市场补,投海外就补 2 到 3 个可复用的 STAR 故事,投国内就补知识的边界感(不知道就说不知道,再说出你会怎么查)。
Key points
- Structure is the through-line: domestic loops run two or three rounds in one day with the same panel going deeper; overseas loops are five independent stages over weeks, scored separately and voted on
- Domestic segments and time boxes: 3 minutes intro, 25 project deep-dive, 20 live coding, 10 scenario and fundamentals, 5 candidate questions
- Overseas stages: 30-minute recruiter screen, 60 coding, 60 system design, 45 behavioral, then team match
- Preparation follows from structure: domestic means surviving three layers of follow-up; overseas means fixing your weakest round, especially two or three reusable STAR stories
- Overseas relies on rubrics and written feedback, so narrating while coding and volunteering trade-offs count for more — but algorithms are still tested
答题要点
- 结构差异是主线:国内一天内两三轮、同一批人越问越深;海外五个独立环节跨几周,不同的人各判各的最后合票
- 国内单轮的五段与时间盒:自我介绍 3 分钟、项目深挖 25 分钟、手撕代码 20 分钟、场景与八股 10 分钟、反问 5 分钟
- 海外五轮:recruiter screen 30 分钟、coding 60 分钟、system design 60 分钟、behavioral 45 分钟、team match
- 准备策略由结构推出:国内练被追问三层,海外补短板(尤其 behavioral 的 2 到 3 个可复用故事)
- 海外更依赖结构化评分与书面反馈,所以边写边讲、主动说取舍这类可被记录的行为权重更高;但算法一样要考
What most commonly goes wrong in the self-introduction, and what does a good one look like?自我介绍环节最容易出的问题是什么?一段好的自我介绍应该长什么样?
Common in ChinaCommon overseasIntermediate#self-presentation#communicationHow to reason about it · think before answering
- Start from what the interviewer is doing during those three minutes: judging whether you can structure a piece of speech unaided, and deciding which project to spend the next twenty-five minutes on. Once you see the second one, the answer stops being 'keep it short'.
- The usual failures share one root cause: telling it chronologically. Starting at university and reading the resume top-down means the three minutes expire before you reach the recent work, which is the only part anyone wants to hear.
- That gives the correct shape: reverse order, three blocks only — what kind of engineer you are now, one or two signature pieces of work with a number attached, and why this role. Land the main line in ninety seconds and leave room for follow-up rather than filling the slot. The silence is leverage, not waste.
- The second frequent failure is adjectives with no numbers. 'I built a high-performance agent service' carries almost no information; a sentence with a constraint, a goal, an action, and a metric moved from X to Y is what makes someone ask the next question.
- The third is the subtlest: seeding things you do not want to be asked about. Every technology you name is an invitation, so leave unfamiliar stacks out — and conversely, plant the topics you want to be asked about, since this is the only moment in the loop where you set the agenda.
- Expect this follow-up: if the interviewer cuts in with 'just briefly', you have already run long or drifted. Rehearse two versions, sixty and ninety seconds, and switch between them rather than compressing live — live compression usually deletes the conclusion too.
分析过程 · 先想清楚再作答
- 先看清面试官在这 3 分钟里做什么:一是看你能不能自己组织一段有结构的表达,二是决定接下来 25 分钟挖你哪个项目。看懂第二件事,答案就不是「讲短一点」这么浅了。
- 最容易出的问题有一个统一的根因——按时间顺序讲。从大学讲起、顺着简历从上往下念,于是 3 分钟到点时你还没讲到最近、最有价值的那段经历,而那恰恰是唯一有人想听的部分。
- 由此推出正确形态:倒序,只留三块——你现在是什么方向的工程师、一到两个带数字的代表作、你为什么来面这个岗位。90 秒讲完主线,把剩下的时间让给对方追问,而不是把 3 分钟填满。留白是主动权,不是浪费。
- 第二个高频问题是通篇形容词、没有一个数字。「我做过一个高性能的 Agent 服务」几乎不携带信息;换成一句带约束和指标的话(在什么约束下、为了什么目标、做了什么、把哪个指标从多少改善到多少),才会让对方接着问下去。
- 第三个问题最隐蔽:自我介绍里埋了自己不想被问的东西。你说出口的每一个技术名词都是一张邀请函,不熟的栈别写也别说;反过来,希望被问的点要主动埋进去,这是全场唯一由你控制议题的机会。
- 可以预期的追问:面试官打断你说「再简单说一下」,说明你已经超时或跑题了。所以要提前排练两个版本,一个 60 秒、一个 90 秒,现场直接切,不要临场压缩——临场压缩的结果通常是把结论也一起删掉了。
Key points
- The interviewer is doing two things at once: assessing structure and choosing which project to dig into
- The common failure is chronological order, which burns the clock before reaching recent work; reverse it
- Keep three blocks: current engineering focus, one or two signature results with numbers, and why this role
- Landing the main line in ninety seconds and leaving room for follow-up beats filling all three minutes
- Every technology you name is an invitation: omit unfamiliar stacks, plant the topics you want asked, and rehearse a sixty-second and a ninety-second version
答题要点
- 面试官在这 3 分钟里同时做两件事:判断你的表达结构,决定接下来挖哪个项目
- 最常见的错是按时间顺序讲,时间用完还没讲到最近最有价值的经历;正确做法是倒序
- 结构只留三块:现在的技术方向、一到两个带数字的代表作、为什么来面这个岗位
- 90 秒讲完主线、主动留白给对方追问,比把 3 分钟填满更有利
- 每个说出口的技术名词都是邀请函:不熟的不提,想被问的主动埋进去;提前排练 60 秒和 90 秒两个版本
After a mock interview, how do you assess yourself objectively instead of settling for 'that felt okay'?一次模拟面试之后,你怎么做一次客观的自我评估,而不是停在「感觉还行」?
Common in ChinaCommon overseasIntermediate#self-assessment#deliberate-practiceHow to reason about it · think before answering
- This asks whether you have engineered your practice. 'Record it and listen again' is the passing floor; the discriminator is a repeatable rubric plus thresholds fixed in advance, because without a rubric two sessions are not comparable and improvement is unmeasurable.
- Objectivity requires reviewable evidence, so fix three things first: record audio or screen throughout, run the real time boxes, and score against the recording afterwards rather than on feeling at the buzzer. Self-assessment is at its most distorted in the minutes right after you finish.
- Then replace overall impression with fixed dimensions: self-introduction, project depth, coding, system design, and communication plus candidate questions, each scored 1 to 5 for a total of 25. What makes it work is writing anchor descriptions for what a 1, a 3, and a 5 look like — otherwise the same '4' means different things in different sessions.
- Set thresholds before scoring, which is the only defense against rationalizing afterwards: any dimension below 3 goes on the weakness list, and a total below 18 means rerunning the whole loop two days later instead of pressing on.
- The final step carries all the value: translate low scores into four columns — observation, root cause, smallest drill for tomorrow, and how to verify. An observation has to be a fact you can point at in the recording, with a timestamp and the actual words: 'explained it badly' does not qualify, 'eight minutes into the project story and still had not said what I personally did' does. The drill must fit in one day, and verification must be observable, ideally with a numeric bar.
- Expect the follow-up: how do you generate follow-up questions alone? Use a model as the interviewer, but write the interrogation rules into the instructions first — one question at a time, three consecutive layers of follow-up grounded in what you just said, and no praise, no evaluation, no supplying the answer. Without those rules it degrades into an encouraging assistant, which defeats the point.
分析过程 · 先想清楚再作答
- 这题在考你有没有把练习工程化。答「录下来多听几遍」只是及格线,真正的区分度在于有没有可重复的评分口径和事先定好的阈值——没有口径,两次模拟之间就没法比较,也就谈不上进步。
- 客观的前提是有可回放的证据,所以先固定三件事:全程录音或录屏、按时间盒计时、事后对着回放打分而不是结束时凭感觉打。刚讲完的十几分钟里自我评价偏差最大,讲得顺就全盘肯定,卡过一次就全盘否定。
- 然后用固定维度代替整体印象:自我介绍、项目讲解深度、编码、系统设计、沟通与反问,各 1 到 5 分,满分 25。关键是每个维度要写好 1 分、3 分、5 分各长什么样的锚点描述,否则同一个「4 分」在两次之间根本不是同一件事。
- 阈值要在打分之前定好,这是防止事后给自己找理由的唯一办法:任何单项低于 3 分就进弱项清单,总分低于 18 分就隔两天把整套流程重跑一次,而不是硬着头皮往下走。
- 最后一步才是全部价值所在——把低分翻译成四列:现象、根因、最小动作、怎么验证。现象必须是回放里能指着看的事实(带时间、带原话),「讲得不好」不算,「项目讲解到第 8 分钟还没说到我做了什么」才算;最小动作必须一天内做得完;验证必须可观察,最好带数字门槛。
- 可以预期的追问:一个人怎么产生追问?用大模型当面试官,但必须先把追问纪律写进指令——一次只问一个问题、基于我的回答连追三层、全程不评价不夸奖不给答案。不写纪律,它会退化成一个不停鼓励你的助手,那就失去了模拟的意义。
Key points
- Evidence before judgment: record throughout, run real time boxes, and score against the replay rather than on feeling at the buzzer
- Use five fixed dimensions (intro, project depth, coding, system design, communication and candidate questions) scored 1 to 5 out of 25, each with anchors for what 1, 3 and 5 look like
- Fix thresholds before scoring: any dimension below 3 goes on the weakness list, a total below 18 means rerunning the loop two days later
- Translate low scores into four columns — observation, root cause, smallest drill, verification — where the observation is a pointable fact and the drill fits in one day
- Practicing alone, use a model as interviewer but write the rules first: one question at a time, three layers of follow-up, no praise, no evaluation, no answers
答题要点
- 先有证据再有判断:全程录音或录屏、按时间盒计时、事后对着回放打分,不在结束当场凭感觉打
- 用固定五个维度(自我介绍、项目讲解深度、编码、系统设计、沟通与反问)各 1 到 5 分、满分 25,并给每个维度写 1/3/5 分的锚点描述
- 阈值先定后打:任何单项低于 3 分进弱项清单,总分低于 18 分隔两天重跑整套流程
- 把低分翻译成四列:现象、根因、最小动作、怎么验证;现象必须是回放里能指着看的事实,动作必须一天内做得完
- 一个人练时用大模型当面试官,但要先写死追问纪律:一次一问、连追三层、不评价不夸奖不给答案
D29 Shoring Up Weak Points + a Coding Warm-Up: Rate Limiter, LRU, Concurrency Control, Streaming JSON Parsing
What are the common rate limiting algorithms, what are their trade-offs, and which one would you actually ship?限流器有哪几种常见算法?各自的优缺点是什么?如果只能落地一种,你选哪个?
Common in ChinaCommon overseasBasic#rate-limiting#concurrencyHow to reason about it · think before answering
- This question tests whether you know rate limiting has several distinct semantics, not whether you can write a counter. Naming only one algorithm reads as never having run real traffic.
- Lay the four out by complexity and attach a weakness to each: fixed window is cheapest but has the boundary burst; sliding window log is exact but its memory grows with request count; sliding window counter is an approximation with constant memory; token bucket allows bursts with constant memory. That ordering is the skeleton of a good answer.
- Make the boundary burst concrete, because it is the standard follow-up: with a 100-per-minute limit, a client can spend 100 at 12:00:59 and another 100 the instant the counter resets at 12:01:00 — 200 requests inside two seconds, double the quota.
- Pick the token bucket and justify it by traffic shape: real traffic is bursty, and the bucket gives you two independent knobs — refill rate caps the long-run rate, capacity caps the burst. Implement it with lazy refill: compute the top-up from the elapsed time when a token is requested, never run a timer per user.
- Production angle: the in-memory version only holds for a single instance. Across gateway replicas, read-compute-write has a race and two replicas can both see 'one token left' and both allow. Fix it with a Redis Lua script so refill and deduction happen in one atomic step — Lua is not for speed here, it is for gluing three commands into one.
- Expect the follow-up: why not read the clock inside the script? Because that makes the script non-deterministic. Pass the timestamp in from the caller, and say the cost out loud — replica clocks now have to be roughly aligned.
分析过程 · 先想清楚再作答
- 这题在考「你知不知道限流有多种语义」,而不是「你会不会写计数器」。只答出一种算法的人,会被默认没做过真正的流量治理。
- 先把四种按复杂度排开再逐个给弱点:固定窗口最省内存但有边界双倍;滑动窗口日志最精确但内存和请求数同阶;滑动窗口计数是近似解、内存回到常数;令牌桶允许突发、内存常数。这个排列顺序本身就是答案的骨架。
- 边界双倍要用具体数字讲,它是本题最常见的追问:限每分钟 100 次,用户在 12:00:59 打满 100 次,12:01:00 计数器清零又能打 100 次,跨边界的这 2 秒实际放行了 200 次。说不出这个例子,等于没答第一问。
- 结论选令牌桶,理由要落在业务形状上:真实流量本来就是突发的,令牌桶同时约束了长期速率(补充速度)和瞬时突发(桶容量),两个旋钮分别对应两个业务问题。实现上必须是惰性补充——取的时候按时间差现算,不要给每个用户起一个定时器,十万用户就是十万个定时器。
- 生产视角:单机内存版只在单实例下成立。多个网关实例共享配额时,「读余额 → 算补充 → 写回」三步之间一定有竞态,两个实例都读到「还剩 1 个」就会双双放行。修法是把三步塞进一段 Redis Lua 脚本,靠单线程执行整段脚本拿到原子性——用 Lua 不是为了快,是为了把三条命令粘成一条。
- 可以预期的追问:脚本里为什么不直接取当前时间?因为那会让脚本变得不确定,时间戳应该由调用方传进来;代价是各实例的时钟要大致对齐,这个取舍要主动说出口。
Key points
- Four algorithms: fixed window (cheap, boundary burst), sliding window log (exact, memory grows with requests), sliding window counter (approximate, constant memory), token bucket (bursty, constant memory)
- The fixed-window boundary burst lets twice the quota through in the two seconds around a window edge, which is enough to overload a database or model API
- Ship the token bucket: refill rate bounds the long-run rate and capacity bounds the burst, two knobs for two real constraints
- Use lazy refill — top up from elapsed time on access instead of running one timer per key
- For the distributed version, put refill and deduction in one Redis Lua script; a GET followed by a SET always races. Pass the timestamp in to keep the script deterministic
答题要点
- 四种算法:固定窗口(省内存但边界双倍)、滑动窗口日志(精确但内存与请求数同阶)、滑动窗口计数(近似、常数内存)、令牌桶(允许突发、常数内存)
- 固定窗口的边界双倍:跨窗口交界的 2 秒内可以放行两倍配额,下游是数据库或模型 API 时足以打穿
- 落地选令牌桶:补充速度管长期速率、桶容量管瞬时突发,两个旋钮对应两个真实业务约束
- 必须用惰性补充:取令牌时按时间差现算,不要为每个 key 起定时器
- 分布式版把补充与扣减写进一段 Redis Lua 脚本,先 GET 再 SET 一定有竞态;时间戳由调用方传入以保持脚本确定性
How would you build a scheduler that caps in-flight async tasks, and why is Promise.all or asyncio.gather not enough?怎么实现一个限制并发数的调度器?为什么不能直接用 Promise.all 或者 asyncio.gather?
Common in ChinaCommon overseasIntermediate#concurrency#asyncHow to reason about it · think before answering
- The hinge is the second half. They are checking whether you separate 'await a batch' from 'cap how many run at once' — similar API names, unrelated semantics.
- Name the wrong answer first: mapping 500 items to promises and awaiting them together runs at concurrency 500. Creating the promise already fired the request; awaiting only collects results. gather and CompletableFuture.allOf are the same trap in other accents.
- Then give the two correct shapes: a fixed set of workers pulling from a shared cursor (the JS idiom, where a worker is the slot), or a semaphore gating task start (asyncio.Semaphore, java.util.concurrent.Semaphore). Swift needs a manual window over a TaskGroup — fill limit slots, then add one task per result received.
- The real failure mode is slot leakage: release must happen in a finally, or the error must be collapsed into a result value inside the task. Code that misses this looks perfect on the happy path and only degrades once the downstream starts failing, which makes it one of the hardest bugs to trace.
- Tie it to agents: batch embedding, parallel tool calls, fan-out subtasks. The benefit is not only sparing the downstream — peak memory now scales with the concurrency limit instead of the task count.
- Expect the follow-up: what if tasks retry? Retries must happen inside the slot, otherwise a retry storm bypasses the limiter entirely. One level deeper: add jitter so failed tasks do not all come back at the same instant.
分析过程 · 先想清楚再作答
- 题眼在后半句。面试官在确认你分不分得清「等待一批任务」和「限制同时运行的任务数」——这两件事在 API 名字上很像,在语义上毫无关系。
- 先说破错误答案为什么错:把 500 个任务全部映射成 Promise 再一起 await,这段代码的并发度是 500。Promise 一被创建,它内部的请求就已经发出去了,await 只是在等结果;gather 和 CompletableFuture.allOf 是同一个坑的另外两种口音。
- 再给正确形状的两条路:固定数量的工人从同一个游标取任务(JS 的惯用法,槽位就是工人本身),或者用信号量挡在任务启动之前(Python 的 asyncio.Semaphore、Java 的 Semaphore)。Swift 要用 TaskGroup 自己开滑动窗口,先塞满 limit 个、每收一个结果补一个。
- 本题真正的失分点是槽位泄漏:acquire 之后必须在 finally 里 release,或者把错误在任务内部收敛成结果值。忘了这一步的代码在 happy path 上完全正常,只有下游开始报错时才会一点点变慢直到彻底卡死——这是最难查的那类 bug,因为症状出现在故障之后而不是之中。
- 落到 Agent 场景说收益:批量 embedding、并行工具调用、多路子任务都靠它。收益不只是「不打爆下游」,还有同时驻留的内存与并发度同阶而不是与任务数同阶。
- 可以预期的追问:如果任务本身还要重试呢?答案是重试要在槽位内部完成(占着槽位退避重试),否则重试风暴会绕过限流;再追一层就是给重试加抖动,避免所有失败任务在同一时刻一起回来。
Key points
- Promise.all and asyncio.gather only wait; the work started when each promise was created, so concurrency equals the task count
- Two correct shapes: a fixed worker set pulling from a shared cursor, or a semaphore gating task start
- The slot must be returned on every exit path — finally in Java, async with in Python, error-to-value inside a Swift task, try/catch inside the JS loop
- A leaked slot shows up as gradual slowdown to a full stall once the downstream starts erroring, and is invisible on the happy path
- The payoff is peak memory scaling with the concurrency limit rather than the task count; retries must stay inside the slot and carry jitter
答题要点
- Promise.all 与 asyncio.gather 只负责等待,任务在被创建的那一刻就已经启动了,并发度等于任务总数
- 两种正确形状:固定数量的工人从共享游标取任务,或者用信号量挡在任务启动之前
- 槽位必须在任何退出路径上归还:Java 写在 finally 里,Python 用 async with,Swift 把错误收敛成结果值,JS 在循环里 try 与 catch
- 槽位泄漏的症状是「下游一开始报错就越来越慢直到卡死」,happy path 完全看不出来
- 收益是同时驻留的内存与并发度同阶,而不是与任务总数同阶;重试要占着槽位做,并加抖动
Why can't you just call JSON.parse in a streaming response, and how would you parse incrementally?为什么流式场景下不能直接用 JSON.parse?你会怎么做增量解析?
Common in ChinaCommon overseasDeep dive#streaming#json-parsingHow to reason about it · think before answering
- Almost all the signal is in your first sentence. Whoever starts writing a state machine will spend twenty-plus minutes on something probably buggy; whoever first asks 'is it one complete JSON per line, or one big object split across chunks?' has already won half the question.
- Answer the why first: network chunking ignores syntax boundaries, so a single read often holds half a JSON document. Handing that to a parser only throws, and the exception carries nothing you can recover from.
- Then draw the distinction. Case (a) is SSE: each event is one line prefixed with data, holding one complete JSON object. This covers 99% of LLM work, and the fix is line buffering plus per-line parsing — keep the trailing fragment in the buffer and stitch it onto the next chunk.
- Case (b) — one large object arriving in pieces — is the only case needing real incremental parsing, and it rests on three state variables: bracket depth (back to zero means a complete object), whether you are inside a string (brackets in text must not count), and whether the previous character was a backslash (an escaped quote must not toggle string state). Drop any one and text containing brackets breaks the depth count.
- One trap almost nobody volunteers: chunks are split on bytes, and a CJK character takes three bytes in UTF-8, so a boundary can land mid-character. Use a streaming decoder — TextDecoder with the stream option, an incremental decoder in Python, InputStreamReader in Java — or you get a replacement character you can never recover. It is the half-line problem one layer down.
- Expect the follow-up: what about the terminator line? It is not JSON, so check for it and return before parsing. Feeding it to the parser is the single most common one-line bug in this question.
分析过程 · 先想清楚再作答
- 这题的区分度几乎全在你开口的第一句话。听到「流式 JSON 解析」就动手写状态机的人,会花二十多分钟写一个大概率有 bug 的东西;先反问一句「是一行一个完整 JSON,还是一个大对象被切成很多片」的人,已经赢了一半。
- 先回答为什么不能直接解析:网络分包不认语法边界,一次读取拿到的很可能是半个 JSON。直接扔给解析器只会抛异常,而且这个异常没有任何可恢复的信息。
- 然后做那个关键区分。情况 a 是 SSE:每条事件是一行以 data 开头的文本,行内是完整 JSON,LLM 场景 99% 是这一种,解法是行缓冲加逐行解析,二十行代码——把切分出来的最后一段(可能是半行)留在缓冲区里,等下一次读到更多数据再拼。情况 b 是单个大对象跨分片到达,才需要真正的增量解析。
- 情况 b 的核心是三个状态变量:括号深度(深度归零说明一个完整对象结束)、是否在字符串内部(字符串里的括号不能计入深度)、前一个字符是不是反斜杠(转义中的引号不切换字符串状态)。三者缺一不可,少一个遇到含括号的文本就算错深度。
- 还有一条几乎没人主动说、但一说就加分的坑:分片是按字节切的,一个汉字在 UTF-8 里占三个字节,边界可能落在中间。必须用流式解码器(TextDecoder 的 stream 选项、Python 的增量解码器、Java 的 InputStreamReader),否则会拿到一个永远补不回来的乱码字符。这是「半行缓冲」在字节层的同款问题。
- 可以预期的追问:那结尾那个终止标记怎么办?答案是它不是 JSON,必须在解析前先判断并直接返回,拿它去解析必然抛异常——这是这道题里最常见的一行 bug。
Key points
- Network chunking ignores syntax boundaries, so a read can hold half a document; parsing it throws an unrecoverable error
- Ask which case it is first: one complete JSON per line (SSE, the overwhelming majority of LLM work) or one large object split across chunks
- The first case only needs line buffering plus per-line parsing, keeping the trailing partial line for the next chunk
- Only the second case needs a state machine, tracking bracket depth, inside-string, and escaped-previous-character
- One layer down, a multi-byte UTF-8 character can be split across chunks, so use a streaming decoder; and the terminator line is not JSON, so check for it before parsing
答题要点
- 网络分包不认语法边界,一次读取可能拿到半个 JSON,直接解析必然抛异常且不可恢复
- 先问清是哪一种:一行一个完整 JSON(SSE,占 LLM 场景的绝大多数)还是一个大对象跨分片到达
- 前者只需行缓冲加逐行解析:把最后一段可能的半行留在缓冲区,等下一次读到更多数据再拼
- 后者才需要状态机,核心是括号深度、是否在字符串内部、前一个字符是否为转义反斜杠三个状态
- 字节层还有一个同款坑:UTF-8 多字节字符可能被分片切开,必须用流式解码器;结尾的终止标记不是 JSON,解析前要先判断
D30 Full Retrospective and Application Kickoff: a Complete Pass Over the Interview Bank, a Knowledge Map, Month-Two Application Cadence, Public Launch of the Site
With only one week left before your interviews, how would you plan your review?如果只剩最后一周准备面试,你会怎么安排复盘节奏?
Common in ChinaCommon overseasBasic#interview-prep#prioritizationHow to reason about it · think before answering
- This sounds casual but it tests prioritization. The interviewer wants judgment, not diligence: the week is fixed, so how do you decide where it goes? 'Eight hours a day, start from the top' shows no judgment at all.
- Offer a reusable rule: the marginal value of reviewing a topic depends on how far you currently are from being able to explain it, so step one of any plan is measurement, not study. Planning without measuring is allocating a budget blindfolded.
- Concretely: day one is triage only — say every answer out loud and tag it green (can explain unaided), yellow (can explain with a glance at notes), or red (cannot). Skip reds immediately. The output of that pass is a distribution, not knowledge. Days two and three hit yellow and red, day four hits what is still red, and the last days go to mock interviews and delivery.
- Name the discipline and its failure mode: fixing the first red question on the spot burns thirty minutes, so by question twenty the day is gone and most of the set was never assessed. That detail is what proves you have actually done this.
- Add a falsifiable bar for 'I know it': out loud, ninety seconds, no notes. The fluency you feel while reading silently belongs to the author, not to you.
- Expect the follow-up: what if the reds cluster in one area? Fix the upstream concept first rather than the individual questions — clustered reds usually share one missing prerequisite, and repairing it lights up five questions at once.
分析过程 · 先想清楚再作答
- 这题看着像闲聊,其实在考「你会不会做优先级」。面试官想听的不是勤奋,是判断:一周时间是固定的,你怎么决定把它花在哪。答「每天复习八小时,从头过一遍」就是没有判断。
- 先给一条可复用的推导:复习的边际收益取决于「这一块你现在离能讲清有多远」,所以任何计划的第一步都必须是**测量**,而不是学习。没测量就排计划,等于闭着眼睛分配预算。
- 落到具体做法:第一天只做分诊——把所有题目出声过一遍,按「能讲清 / 看一眼能讲 / 讲不出」标三种颜色,看到不会的立刻跳过。这一遍的产出是一张分布图,不是知识。第二、三天只碰后两类,第四天只碰仍然讲不出的,最后两三天留给模拟和表达。
- 要主动说出「只标记不纠结」这条纪律和它的失败模式:碰到第一道不会的题当场去补,一道题吃掉半小时,做到第 20 道今天就没了,剩下的题连颜色都没有。这个细节最能证明你真的这样练过。
- 再补一个判据:判断「会」的标准必须可证伪——出声、限时 90 秒、不看提纲。默读产生的流畅感是题库给的,不是你的。
- 可预期的追问:如果分诊发现红题集中在同一块怎么办?答案是先补那一块的**上游**概念,而不是逐题补——同一块里的题往往共用一个没吃透的前置,补上游一道题能带亮五道。
Key points
- Start by measuring, not studying: one spoken pass over everything, tagging only, no on-the-spot fixes
- Three shrinking passes: tag everything, then only yellow and red, then only what is still red, leaving the tail for delivery practice
- Make 'I know it' falsifiable: spoken, under ninety seconds, no notes — silent reading does not count
- The triage pass produces a distribution that tells you whether the remaining days go to technique or to delivery
- When reds cluster, repair the shared upstream concept rather than each question
答题要点
- 第一步是测量不是学习:先出声过一遍全部题目,只做三色标记,不当场补漏
- 三遍递减:第一遍全量标记,第二遍只刷黄和红,第三遍只刷仍然红的,最后留时间给表达与模拟
- 「会」的判据必须可证伪:出声讲、90 秒内讲完、不看提纲,默读不算
- 分诊的产出是一张分布图,它决定后面几天该补技术还是补表达
- 红题扎堆时先补共同的上游概念,比逐题补效率高得多
How do you turn scattered knowledge into a map that is actually useful for review?怎么把零散的知识点组织成一张便于复习的知识地图?
Common in ChinaCommon overseasIntermediate#knowledge-organization#interview-prepHow to reason about it · think before answering
- The load-bearing phrase is 'useful for review'. Most answers become 'group things by module and draw a mind map', which produces a table of contents, not a map — a contents page cannot tell you what to fix first. That is where candidates separate.
- Break it down: a graph has nodes and edges. Grouping nodes is cheap and almost everyone does it correctly; the information lives in the edges. So ask yourself how many edges your diagram has and what each one means. No answer means you drew a contents page.
- Give an operational rule for edges: draw A to B only when not understanding A blocks understanding B. 'Both are about message queues' does not qualify — that is sibling grouping. 'You cannot understand context compression without the context window' does. Course order does not qualify either; that is a calendar, not a dependency.
- Conclusion: use the map by painting your weak spots onto it. If a node is shaky, check whether its upstream is shaky too — repair upstream and several downstream nodes light up at once. That is the map's one advantage over a checklist: a checklist says what is broken, a map says where to start.
- Production angle: the same habit pays off at work. When debugging an incident, the dependency graph in your head decides whose logs you open first; without it you probe services one by one. Saying this shows the map is a working tool, not an exam prop.
- Expect the follow-up: how big should it be? Small enough to redraw on a whiteboard in five minutes. Past that you start maintaining the map instead of using it — merge nodes into themes and leave the detail in your question bank.
分析过程 · 先想清楚再作答
- 题眼在「便于复习」四个字。绝大多数人答成「按模块分类、画个思维导图」,那产出的是目录不是地图——目录任何一本书的前几页都有,它不能告诉你先补哪里。区分度就在这儿。
- 怎么拆:一张图有两种元素,节点和边。分层(节点怎么分组)是廉价的、几乎人人做得对;真正的信息量在边上。所以先问自己一个问题——我这张图上有几条边,每条边的含义是什么?答不上来就说明画的是目录。
- 给一条可操作的连边判据:只有当「不懂 A 就学不懂 B」时才连 A 指向 B。「A 和 B 都属于消息队列」不算,那是同层并列;「不理解上下文窗口就理解不了为什么要压缩」算。课程的先后顺序也不算——那是日历,不是依赖。
- 结论:地图的用法是把你的弱点涂上去。某个节点讲不清,先看它的上游是不是也红——是的话补上游,一次带亮一串。这就是地图相对清单的唯一优势:清单说哪里错了,地图说该从哪儿开始。
- 生产视角:这套东西在工作里同样有用。排查一个线上问题时,你脑子里那张「谁依赖谁」的图决定了你先看哪个服务的日志;没有这张图的人只能一个个试。面试时把这个类比说出来,会显得你不是为了背题才画图。
- 可预期的追问:那张图应该多大?答案是能在白板上 5 分钟画完——超过这个规模你会开始维护它而不是使用它,节点合并成主题,细节留在题库里。
Key points
- Grouping into layers is what any table of contents does; a map's information is in its edges
- One rule for edges: draw one only when A is a genuine prerequisite for B — sibling topics and course order do not count
- Paint your weak spots on the nodes and fix upstream first when reds cluster; one fix lights up several downstream nodes
- Long cross-layer edges are the valuable ones — following them in an interview shows a system, isolated nodes only produce fragments
- Keep it redrawable on a whiteboard in five minutes; finer detail belongs in the question bank, not the map
答题要点
- 分层只是分组,任何目录都做得到;地图的信息量全部在边上
- 连边的判据只有一条:不懂 A 就学不懂 B 才连边,同类并列和课程顺序都不算
- 把弱项涂到节点上,红点扎堆时优先补上游节点,一次带亮一串下游
- 跨层的长边最值钱,面试时顺着长边讲能体现体系,孤立节点只能给出零碎答案
- 规模控制在白板 5 分钟能画完,再细的内容留在题库里而不是图上
Walk me through what you have been working on recently and why you moved toward agent engineering, in three to five minutes.用 3 到 5 分钟讲一下你最近这段时间的成长路径,以及为什么转向 Agent 工程。
Common in ChinaCommon overseasDeep dive#self-introduction#storytellingHow to reason about it · think before answering
- This opens almost every interview and it is the one question you can fully pre-write. It tests selection, not history: three minutes cannot hold a month, so which three things you pick reveals what you think matters. A week-by-week recital is the common failure — it hands the judgment back to the interviewer.
- Structure it as origin, turn, evidence, direction. Origin: one sentence on where you were and what capped you. Turn: the concrete problem that pushed you toward agents, not 'I believe in the space'. Evidence: whichever of your projects maps best onto this role, framed as an engineering problem you solved. Direction: the kind of team and problem you want next.
- The evidence part has a hard requirement: give something checkable. A repository link, numbers you measured yourself, and the conditions you measured them under. 'I built an agent platform' and 'I split gateway from worker behind a message bus, and with three local replicas, killing the lease holder lets another worker take over once the lease expires' differ by an order of magnitude in credibility.
- Hold the integrity line yourself: these are learning projects, say so, and attach measurement conditions to every number (single machine, mock mode). Never present them as company work or quote scale you never ran — two follow-up questions expose it, and that kind of exposure is unrecoverable.
- Common mistake: spending the three minutes on technical depth. The opener's job is not to explain anything fully, it is to shape which threads the interviewer pulls over the next forty minutes — so end each part on a deliberate hook, such as 'lease renewal had to be atomic, which took a script', and stop there.
- Expect the follow-up: why not stay on your previous track? Answer with a concrete blocker you kept hitting, not with industry trends. Everyone can recite a trend; naming a specific problem shows you reasoned your way here.
分析过程 · 先想清楚再作答
- 这是几乎每场面试的第一题,也是唯一一道你能完全预写的题。它考的不是经历,是**取舍**:3 分钟装不下一个月,你选了讲哪三件事,直接暴露你认为什么重要。流水账式的「第一周我学了……第二周我学了……」是最常见的失败,它把判断权交回给了面试官。
- 怎么拆:套一条「起点 - 转折 - 证据 - 去向」的四段结构。起点一句话说清你原来的位置和它的天花板;转折说清是什么具体问题把你推向 Agent,不要用「看好这个方向」这种空话;证据是三个产出物中最能对上这个岗位的那一个,讲清楚它解决了什么工程问题;去向说清你想在什么样的团队继续解决什么问题。
- 证据那一段有个硬要求:**给出可被验证的东西**。仓库链接、你实测出来的数字、以及数字的测量条件。同一句话讲成「做了一个 Agent 平台」和讲成「gateway 和 worker 拆开、用消息总线解耦,本地三副本下杀掉持有租约的 worker,另一个能在租约到期后接手」,可信度差一个量级。
- 红线要自己守住:这三个是学习项目,说的时候就要说明是个人项目,数字要带测量条件(本地单机、模拟模式压测)。**不要把它讲成公司经历,也不要报没跑过的规模数**——面试官追问两句就穿帮,而且是不可挽回的那种。
- 常见误区:把这 3 分钟用来讲技术细节。开场白的目标不是讲透任何东西,是让面试官在后面 40 分钟里想问哪几个点——所以每段末尾都要故意留一个可追问的钩子,比如「租约续约那里我们用了一个脚本保证原子性」,停在这儿别展开。
- 可预期的追问:为什么不是继续做原来的方向?答案要落到具体问题上(原来的场景里你反复遇到什么做不了的事),而不是行业趋势——讲趋势的人到处都是,讲具体问题的人显得是自己想清楚的。
Key points
- Build the three minutes from origin, turn, evidence and direction — never a week-by-week recital
- Ground the turn in one concrete thing you could not do before, not in a belief about the market
- Make the evidence checkable: repository links, numbers you measured, and the conditions behind them
- State plainly that these are personal learning projects; never dress them as company work or quote unmeasured scale
- End each part on a deliberate hook so the next forty minutes land where you are strongest
答题要点
- 用「起点 - 转折 - 证据 - 去向」四段撑起 3 分钟,不要按周流水账
- 转折要落到一个具体的做不了的问题上,而不是「看好这个方向」
- 证据段给可验证的东西:仓库链接、自己实测的数字、以及测量条件
- 明确说明这是个人学习项目,绝不包装成公司经历、绝不报没跑过的规模
- 每段末尾留一个可追问的钩子,把后面 40 分钟引到你准备最充分的地方
Prompt Engineering From Scratch in 5 Days
D1 What a Prompt Is, and Isn't: How the Model Reads Instructions; the Four Elements of Role / Task / Format / Constraints
What exactly is being engineered in prompt engineering, and how does it differ from writing a requirements doc or a design spec?提示词工程到底在工程什么?它和写需求文档、写技术方案有什么本质区别?
Common in ChinaCommon overseasBasic#prompt-basics#mental-modelHow to reason about it · think before answering
- The screen here is whether the candidate knows the model completes text rather than executes commands. 'Clever wording that makes the model obey' signals chat-app experience only.
- Start from the reader: a spec is read by people who share project context; a prompt is read by a completer with zero context that never asks a clarifying question, so every implicit default must be spelled out.
- Then justify the word engineering: reproducibility, testability, versioning. A prompt should run against a test set, live in the repo, and diff cleanly between versions.
- Conclusion: prompt engineering is making implicit context explicit and managing that text like code; phrasing tricks are a small part.
- Likely follow-up: how is that different from a brief for an outsourced team? The team pushes back with questions; the model does not, so a prompt must carry its own completion criteria.
分析过程 · 先想清楚再作答
- 这题在筛「有没有理解模型是在补全而不是执行」。答成「用巧妙的措辞让模型听话」会被判为只会用聊天产品;答出「系统性补齐模型缺少的上下文」才算入门。
- 拆法:先问自己「读者是谁」。需求文档的读者是有项目背景的人,可以依赖共享默认;提示词的读者是一个没有任何项目背景、也不会停下来提问的补全器,所有默认信息都得显式写出。
- 再落到「工程」二字:可复现、可测试、可版本化。提示词写完要能跑测试集、要进仓库、要能对比两版差异——这才是它区别于「写一段话」的地方。
- 结论:提示词工程是把隐性上下文显式化、并把这段文本当代码一样管理的工程活动;措辞技巧只是其中很小的一部分。
- 可预期的追问:那和写给外包团队的需求说明有什么区别?答案是外包会反问,模型不会,所以提示词对完整性的要求更高,且要在没有反馈回路的前提下自带完成标准。
Key points
- The model completes text rather than executing commands; a prompt is context, and specificity narrows the plausible continuations
- What gets engineered is the missing information: perspective, completion criteria, output shape, boundaries with reasons
- Unlike a spec, the reader shares no background and never asks back, so completeness and explicit done-criteria matter more
- Engineering implies testable, versioned, comparable artifacts, not one-off clever phrasing
答题要点
- 模型在补全一段文本而不是执行命令,提示词是给它的上下文,写得越具体可能的下文越窄、输出越稳
- 工程的对象是「模型缺的信息」:视角、完成标准、输出形状、边界与理由,也就是四要素
- 区别于需求文档:读者没有共享背景、不会反问,所以完整性要求更高、必须自带完成标准
- 「工程」意味着可测试、可版本化、可对比,而不是一次性的巧妙措辞
What problem does each of the four prompt elements — role, task, format, constraints — solve? If you could keep only three, which would you drop and why?角色、任务、格式、约束四要素各解决什么问题?如果只能保留三个,你会砍掉哪个,为什么?
Common in ChinaCommon overseasIntermediate#prompt-basics#four-elementsHow to reason about it · think before answering
- The first half is a warm-up; the second half tests whether you can map each element to a specific way the model would otherwise guess, and rank the cost of each wrong guess.
- Map them: role fixes perspective and focus, task fixes the finish line, format decides whether downstream code can consume the output, constraints bound the change surface.
- To pick the one to drop, ask whether its absence makes results unstable or unusable. Missing role skews focus but stays usable; missing done-criteria means the model never knows when to stop; missing format breaks parsers; missing constraints lets edits sprawl.
- Conclusion: in most engineering settings role is the most droppable, because a specific task plus a strict format already imply the perspective — provided the task states what to care about.
- Expect the follow-up 'then why does everyone write a role?' Because it is cheap and compresses many implicit preferences into one line, which pays off in chat-style use where the task cannot be fully specified.
分析过程 · 先想清楚再作答
- 前半句是送分,后半句才有区分度:它在考你是否知道每个要素对应模型的哪一种「猜」,以及哪种猜错的代价最小。
- 拆法:把每个要素映射到一个「模型会猜错的地方」——角色对应视角与关注点,任务对应终点在哪,格式对应输出能否被程序消费,约束对应改动范围与不可碰的边界。
- 判断哪个可砍:看缺了之后是「结果不稳定」还是「结果不可用」。缺角色多半是关注点偏了但仍可用;缺任务的完成标准会让模型不知何时停;缺格式会让下游解析失败;缺约束会让改动面失控。
- 结论:多数工程场景下角色最可砍,因为任务与格式写得足够具体时视角已经被隐含;但要说明前提是任务里已经写清了关注点。
- 追问几乎必然是「那为什么大家还都写角色」——答案是它便宜且能一句话压缩大量隐性偏好,在任务没法写得很细的对话场景里性价比最高。
Key points
- Role sets perspective; task sets the goal and done-criteria; format makes output mechanically checkable; constraints bound scope with reasons
- Each element removes one kind of guess the model would otherwise make
- Role is the most droppable once task and format are specific enough to imply the perspective
- Done-criteria and checkable format are the least negotiable because downstream code depends on them
答题要点
- 角色定视角与关注点;任务定做什么与完成标准;格式定输出形状是否可机械核对;约束定不可碰的边界与理由
- 每个要素对应模型的一种「猜」,缺哪个就多一种不稳定
- 可砍的是角色:任务与格式足够具体时视角已隐含,但前提是任务里写清了关注点
- 任务的完成标准与格式的可核对性最不能省,因为它们直接决定输出能不能被程序消费
Why do rules belong in the system prompt rather than the user message? Cover adherence, control, and cost, and name what the system prompt cannot guarantee.为什么规则要放系统提示而不是用户消息?请从遵从度、管控和成本三个角度说明,并指出系统提示做不到什么。
Common in ChinaCommon overseasIntermediate#system-prompt#prompt-basicsHow to reason about it · think before answering
- The tell is whether you cover all three angles and name a limitation. 'System prompts carry more weight' alone reads as memorized.
- Adherence: user turns get diluted as the conversation grows, the system prompt stays in force. Control: the backend assembles the system prompt and users cannot touch it, so rules apply uniformly. Cost: prompt caching matches on prefixes, and the system prompt is the most stable prefix.
- The limitation is the differentiator: higher adherence is not a guarantee, prompt injection can still steer the model, so security boundaries need code-level enforcement outside the model.
- Conclusion: rules go in the system prompt for stability, consistency and cost, but it is a strong suggestion, not a hard constraint.
- Follow-ups: can the system prompt go last? Possible but unwise — models weight early instructions more and it breaks the cache prefix. What should stay out? Per-request task details, which would bust the cache and hurt reuse.
分析过程 · 先想清楚再作答
- 题眼在「三个角度」和「做不到什么」。只答「系统提示权重高」是背概念,面试官要看的是你有没有在生产里拼过系统提示。
- 拆法:遵从度看多轮稀释——用户消息会被后续对话淹没,系统提示全程生效;管控看谁能改——系统提示由后端统一拼装、用户碰不到,规则放这里才能对所有用户一致;成本看缓存——提示缓存按前缀命中,系统提示是最稳定的前缀。
- 「做不到什么」是区分度所在:系统提示遵从度高不等于绝对,提示注入可以让模型跑偏,所以安全边界不能只靠系统提示,要在模型外用代码兜底。
- 结论:规则进系统提示是为了稳定、一致、省钱;但它是「强建议」不是「硬约束」,硬约束必须在代码层实现。
- 追问方向:系统提示可以放在对话末尾吗?可以但不推荐——多数模型对靠前指令更敏感,且会破坏缓存前缀;另一个追问是「哪些内容不该进系统提示」,答案是每次都变的任务细节,放进去会让缓存失效且难以复用。
Key points
- Adherence: user turns get diluted over a long conversation, the system prompt stays in force
- Control: the backend assembles it and users cannot edit it, so rules apply to everyone
- Cost: prompt caching matches prefixes, so stable content in the system prompt maximizes cache hits
- Limit: it is not a security boundary; prompt injection can bypass it, so enforce hard rules in code
答题要点
- 遵从度:用户消息会被多轮对话稀释,系统提示全程生效
- 管控:系统提示由后端统一拼装,用户碰不到,规则才能对所有人一致
- 成本:提示缓存按前缀命中,系统提示是最稳定的前缀,不变的内容集中在这里最省钱
- 做不到的:它不是安全边界,提示注入可以绕过,硬约束必须在代码层兜底
D2 Few-Shot, Chain of Thought, Step-by-Step, and Self-Checks; When None of These Work
Why does few-shot prompting work, what goes wrong when you give too many examples, and how do you decide how many to include?few-shot 为什么有效?示例给多了会出什么问题?你怎么决定给几个?
Common in ChinaCommon overseasBasic#few-shot#prompt-techniquesHow to reason about it · think before answering
- The screen is whether you treat examples as signals for format and boundaries rather than as magic that makes the model smarter. 'More examples, better model' reads as untested.
- Mechanism first: the model completes text, and examples show the continuation directly, which is harder to misread than prose describing a format or an edge rule. Examples are the strongest format signal.
- Then the cost: each example consumes context and money; too many cause overfitting to surface features such as length, wording and order, and amplify accidental bias — three bug examples out of four nudges everything toward bug.
- Conclusion: the count follows the number of distinct cases you need to cover, typically two to five, each a different case, with at least one boundary sample.
- Follow-ups: does order matter? Yes, models weight the last example more, so place the one closest to the target input last. And if examples contradict the instructions, the model usually follows the examples, so they must match the format spec exactly.
分析过程 · 先想清楚再作答
- 这题在筛「有没有把示例当成格式与边界的信号,而不是当成让模型变聪明的魔法」。答成「示例越多模型越懂」会暴露没在生产里调过提示词。
- 拆法:先答原理——模型在补全,示例直接展示了「下文该长什么样」,比文字描述格式和边界规则更不容易被误读;示例是最强的格式信号。
- 再答代价:每个示例都占上下文与费用;示例过多会让模型过拟合示例的表面特征(长度、措辞、顺序),还会把示例里无意带进去的偏见放大,比如四个示例里三个是 bug,它就更倾向判 bug。
- 结论:数量由「要覆盖几种类型」决定而不是越多越好,通常两到五个,每个覆盖一种不同的情况,并且至少一个是边界样本。
- 可预期的追问:示例的顺序有影响吗?有,多数模型对最后一个示例更敏感,所以把最像目标输入的放最后;另一个追问是示例和说明冲突时模型听谁的,答案是多半听示例,所以示例必须与格式栏逐字一致。
Key points
- Examples show the continuation directly, which beats prose for conveying format and edge rules
- Too many examples cost context and money, overfit surface features, and amplify class bias
- Pick the count by how many distinct cases need coverage, typically two to five with one boundary case
- Order matters — put the closest match last; when examples and instructions conflict the model follows the examples
答题要点
- 示例直接展示下文该长什么样,比文字描述格式和边界规则更不容易被误读
- 示例过多的代价:占上下文与费用、过拟合表面特征、放大示例里的类别偏见
- 数量按「要覆盖几种不同情况」定,通常两到五个,至少一个边界样本
- 顺序有影响,最像目标输入的放最后;示例与说明冲突时模型多半听示例
Where does chain-of-thought prompting help most, where is it a waste, and how does it differ from splitting a task into steps?思维链在什么任务上提升明显,在什么任务上是浪费?它和「分步」有什么区别?
Common in ChinaCommon overseasIntermediate#chain-of-thought#prompt-techniquesHow to reason about it · think before answering
- The discriminators are 'waste' and 'difference'. Anyone can say thinking first helps; the interviewer wants to hear when you deliberately skip it.
- Its value comes from intermediate results checking the next step, so it shines on multi-step reasoning, arithmetic and judgments with distractors; on single-step tasks such as classification, extraction or format conversion it adds latency, cost and length with little gain.
- Difference: chain-of-thought keeps everything in one call and one output budget; splitting uses several calls, each with its own budget and format, and later steps can consume earlier outputs. Use CoT when the model thinks too shallowly, split when one answer cannot hold the work.
- Conclusion: ask whether the task needs multi-step reasoning at all; if yes, ask whether one output can hold it — CoT if so, split if not.
- Follow-ups: with reasoning models that think internally, do you still write 'think step by step'? Usually no, but you still pin the answer format and position. And can you trust the written reasoning? It is a plausible narrative, not the actual computation — use it as a check, not as proof.
分析过程 · 先想清楚再作答
- 题眼在「浪费」和「区别」。只会说「让模型先思考再回答效果更好」的候选人没有算过账,面试官想听的是你什么时候会主动不用它。
- 拆法:思维链的价值来自「中间结果可以校验下一步」,所以它在多步推理、算术、需要排除干扰项的判断上提升明显;在单步判断(分类、抽取、格式转换)上几乎没有增益,只有更慢更贵更长的输出。
- 区别:思维链是一次调用内让模型写出中间过程,输出预算还是同一份;分步是拆成多次调用,每步有独立的预算、独立的格式、并且后一步可以拿前一步的输出做输入。任务是「想得不够细」用思维链,任务是「一次装不下」用分步。
- 结论:先问任务是否需要多步推理,不需要就不用;需要的话再问一次输出装不装得下,装得下用思维链,装不下拆步。
- 追问:推理类模型内置了思考过程,还要写思维链吗?多数情况不用再写「一步步想」,但仍要指定最终答案的格式与位置,否则解析会很痛苦;另一个追问是思维链的内容能不能信,答案是它是「看起来合理的过程」而非真实的内部计算,只能当辅助校验不能当证据。
Key points
- CoT helps because intermediate results check the next step; strong on multi-step reasoning and arithmetic, wasted on single-step judgments
- Cost is longer output, higher latency and spend, so skip it when no reasoning is needed
- Versus splitting: CoT stays in one call with one budget; splitting uses multiple calls with independent budgets that can chain outputs
- With reasoning models you rarely need 'think step by step' but still pin the answer format and location
答题要点
- 思维链的价值是中间结果校验下一步,多步推理与算术上提升明显,单步判断上是浪费
- 代价是更长的输出、更高的延迟与费用,所以不需要推理的任务要主动不用
- 与分步的区别:思维链是一次调用内写过程,输出预算不变;分步是多次调用,每步独立预算且可传递输出
- 推理模型内置思考后一般不必再写「一步步想」,但仍要指定答案格式与位置
What are the signs of a task that no amount of prompt engineering will fix, and what do you do when you hit one?提示词写得再好也做不对的任务有哪些特征?遇到这类任务你会怎么办?
Common in ChinaCommon overseasIntermediate#prompt-limits#failure-modesHow to reason about it · think before answering
- This tests whether you know where prompting ends. Piling techniques onto a hopeless task signals poor judgment; saying 'this is not a prompting problem' signals maturity.
- Three failure classes. Missing knowledge: the fact postdates training or lives in your private data, and the model may fabricate a well-formatted answer. Missing tools: the task needs an action or query against the world. Wrong task: the requirement is contradictory or you actually want something else.
- One test each: could the model plausibly know something that appeared yesterday? Could a person who can only type complete this? Would two readers of the requirement do opposite things?
- Conclusion: paste the material or add retrieval for missing knowledge; add tool use or compute in code for missing tools; fix the requirement for a wrong task. None of these is a prompt change.
- Follow-ups: stricter formats make fabrications look more credible — require sources or verifiable identifiers and validate in code. And to catch these early, seed the test set with a few unknowable items and check that the model admits it does not know.
分析过程 · 先想清楚再作答
- 这题考的是「知道提示词的边界在哪」。一直往提示词上堆技巧的候选人会被判为缺乏判断力;能说出「这题不该用提示词解」才是成熟的信号。
- 拆法:把失效分三类。缺知识——信息在模型训练截止之后或本来就在你的私有数据里,模型不可能知道,还可能编出格式正确的假答案;缺工具——任务需要对外部世界做动作或查询(跑命令、查库、发请求),文字生成做不到;任务写错——需求本身自相矛盾或者你要的其实是另一件事。
- 每类给一个判据:缺知识问「这信息是昨天才出现的,模型有可能知道吗」;缺工具问「一个只能打字的人能完成这件事吗」;任务写错问「两个人读这个需求会不会得出相反的做法」。
- 结论:缺知识就把资料贴进上下文或接检索;缺工具就接工具调用或在代码里做完再让模型解读;任务写错回去改需求。三种都不是提示词层面的解法。
- 追问几乎必然是「格式越严格假答案越像真的怎么办」——答案是对事实类输出要求带出处或可验证的标识,并在代码里校验;以及「怎么在评估里提前发现这类任务」,答案是测试集里放几条模型不可能知道的样本,看它是否老实说不知道。
Key points
- Three failure classes: missing knowledge, missing tools, and a wrongly specified task
- Missing knowledge is dangerous because the model fabricates well-formatted answers, and stricter formats make them more convincing
- Fixes live outside the prompt: paste material or add retrieval, add tool use or compute in code, or fix the requirement
- Seed the test set with unknowable items to check the model admits ignorance
答题要点
- 三类失效:缺知识(截止日期之后或私有数据)、缺工具(需要对外部世界做动作)、任务写错(需求自相矛盾)
- 缺知识的危险在于模型会编出格式正确的假答案,格式越严越像真的
- 解法都在提示词之外:贴资料或接检索、接工具调用或代码先算、回去改需求
- 测试集里放几条模型不可能知道的样本,检查它会不会老实说不知道
D3 Structured Output: JSON Schema, Templates and Variables, Multilingual Output
Why should structured output be enforced with a schema instead of a 'please respond in JSON' instruction, and do you still need validation once the schema passes?结构化输出为什么要用 schema 约束,而不是在提示词里写「请输出 JSON」?schema 通过之后还需要校验吗?
Common in ChinaCommon overseasBasic#structured-output#json-schemaHow to reason about it · think before answering
- This screens for whether the candidate has ever wired model output into code. People who only read output with their eyes think 'respond in JSON' is enough.
- List what that instruction cannot prevent: prose wrapped around the JSON, inconsistent key spelling, numbers as strings, extra keys, missing keys when an array is empty. Each maps to a schema keyword: required, enum, type, additionalProperties.
- Then the mechanism: the schema constrains generation itself, the model can only produce that shape, so the gain is qualitative rather than incremental.
- The second half is the differentiator: schemas constrain shape, not content — integer is not 4xx, string is not non-empty. Business rules still need code-level validation that returns an error list for retries.
- Follow-ups: strict-mode limits — every property in required, additionalProperties false, a supported subset of JSON schema, a first-use compile cost; and how to express optional fields — allow null in the type rather than dropping the key from required.
分析过程 · 先想清楚再作答
- 这题在筛「有没有真的把模型输出接进过程序」。只在聊天窗口里用过模型的人会觉得「请输出 JSON」够了,因为他们是用眼睛读的。
- 拆法:列出「请输出 JSON」挡不住的几种踩空——外面包一段解释、字段名拼法不一致、数字变字符串、多出字段、空数组时省掉键。每一种都对应 schema 里的一个关键字:required、enum、type、additionalProperties。
- 再答原理:schema 在生成时约束形状,模型不是「生成完再检查」而是「只能生成这个形状」,所以稳定性是质变而不是量变。
- 后半句是区分度:schema 只能约束形状,不能约束内容——整数不等于 4xx,字符串不等于非空。业务规则必须在代码里再查一遍,校验函数返回错误列表供重试使用。
- 可预期的追问:严格模式有什么限制?所有字段都要进 required、要写 additionalProperties false、只支持 schema 子集、首次编译有开销;以及「可选字段怎么表达」——类型允许 null 而不是从 required 里去掉。
Key points
- 'Respond in JSON' only guarantees JSON, not which JSON: wrapper prose, key spelling, stringified numbers, extra or missing keys all slip through
- A schema constrains generation itself; required, enum, type and additionalProperties each block one failure class
- Validation is still needed after the schema passes because correct shape does not mean correct content
- Strict mode needs every property in required and additionalProperties false; express optional fields by allowing null
答题要点
- 「请输出 JSON」只约束「是 JSON」,挡不住包解释文字、字段名不一致、数字变字符串、多字段、省键这几种踩空
- schema 在生成时约束形状:required、enum、type、additionalProperties 各挡一种错误
- schema 通过之后仍要校验业务规则,因为形状正确不等于内容正确
- 严格模式要求所有字段进 required 且 additionalProperties 为 false;可选字段用允许 null 表达
In a prompt template, what should become a variable and what should stay constant, and what goes wrong when you have too many variables?提示词模板里哪些内容该做成变量,哪些该写死?变量多了会有什么问题?
Common in ChinaCommon overseasIntermediate#prompt-template#structured-outputHow to reason about it · think before answering
- It looks like a design question but really asks whether you have maintained a prompt in production. The untested instinct is to parameterize everything; experience teaches that every variable is a test dimension.
- One rule: what stays the same on the next call is a constant, what may differ is a variable. Role, task, constraints and schema are usually constants; input text, output language and team defaults are variables.
- Then the cost: each variable adds a dimension to the space of prompts, doubling the combinations a test set must cover, and variables can interact. Fewer is better; a variable that only ever took one value should become a constant.
- Conclusion: the template is a named function with a signature; variables are parameters, constants are the body. That identity is what makes versioning and testing possible.
- Follow-up: is multi-language a variable or separate templates? A variable — one template, language affects only human-facing fields, enums and identifiers never change; separate copies drift within months.
分析过程 · 先想清楚再作答
- 这题看起来是设计题,实际在考「有没有维护过一份跑在生产里的提示词」。没维护过的人会把所有能变的都做成变量,觉得灵活;维护过的人知道每个变量都是一条测试维度。
- 拆法:判据只有一条——下一次调用还会一样的是常量,可能不一样的是变量。角色、任务、约束、schema 通常是常量;输入文本、输出语言、团队默认值是变量。
- 再答代价:每多一个变量,提示词的可能形态多一个维度,测试集要覆盖的组合翻倍;变量之间还可能互相影响(语言变量与格式说明冲突)。所以变量越少越好,只取过一个值的「变量」应该变回常量。
- 结论:模板是一个有名字、有参数签名的函数,变量是它的参数,常量是函数体;这样提示词才有身份,才能版本化、才能写测试。
- 追问:多语言应该是变量还是多份模板?变量——只有一份模板,语言只影响给人读的字段,枚举与标识符不跟着变;否则改一条规则要改多份,三个月后一定分叉。
Key points
- Rule: same on the next call means constant, may differ means variable
- Role, task, constraints and schema are constants; input text, output language and defaults are variables
- Every variable is a test dimension, so keep them minimal and fold single-valued ones back into constants
- Multi-language is one variable affecting only human-facing fields; enums and identifiers stay fixed
答题要点
- 判据:下一次调用还一样的是常量,可能不一样的是变量
- 角色、任务、约束、schema 是常量;输入文本、输出语言、默认值是变量
- 每个变量都是一条测试维度,变量越少越好,只取过一个值的变回常量
- 多语言是一个变量,只影响给人读的字段,枚举与标识符不变
When the model's JSON fails to parse or validate, how do you design the fallback — how many retries, how do you retry, and what happens after the last failure?模型返回的 JSON 解析或校验失败时,你会怎么设计兜底?重试几次、怎么重试、失败之后怎么办?
Common in ChinaCommon overseasIntermediate#structured-output#error-handlingHow to reason about it · think before answering
- A production question that checks whether you have seen a model misbehave. 'Wrap it in try/catch and retry three times' is the novice answer — it says nothing about what you resend or what happens at the end.
- Three layers. Validation returns an error list, not a boolean. Retry appends that list to the user message so the model knows what to fix; resending verbatim mostly reproduces the error. Degradation returns null and logs, leaving skip-or-human to the caller.
- Retry count: one is enough. Persistent failure means the prompt or schema lacks coverage, so fix the template instead of retrying; each retry costs a full call.
- The key conclusion: do not throw on degradation, and do not use a near-miss result. Extraction failure is a normal branch; half-correct structured data is worse than none because downstream code trusts it.
- Follow-ups: how to tell flakiness from a prompt bug? Failure rate — sporadic is flakiness, a stable failing input class is missing coverage and belongs in the test set. And does retrying inflate cost? Cap it and monitor the retry rate.
分析过程 · 先想清楚再作答
- 这题是生产题,考的是「有没有见过模型抽风」。答「加个 try catch 重试三次」是新手答案,它没回答重试时发什么、也没回答最后怎么办。
- 拆法:分三层。校验层返回错误列表而不是布尔值;重试层把错误列表拼进用户消息,让模型知道上一次错在哪,原样重发大概率同样的错;降级层返回空值并记录,交调用方决定跳过还是人工处理。
- 重试次数:一次就够。两次以上还不对说明问题不在这条输入而在提示词或 schema,应该修模板而不是继续重试;每次重试都是一次完整调用的钱和延迟。
- 结论里最重要的一条:降级不要抛异常,也不要把「差一点」的结果凑合着用。抽取失败是正常业务分支;半对的结构化数据比没有数据更危险,因为下游会把它当真的。
- 追问方向:怎么区分「模型抽风」和「提示词有问题」?看失败率——偶发是抽风,某类输入稳定失败是提示词或 schema 缺覆盖,应该把那类输入加进测试集;另一个追问是重试会不会放大成本,答案是要有预算上限并监控重试率。
Key points
- Three layers: validation returns an error list, one retry carries those errors back, then degrade to null and log
- Retries must include the error list in the user message; verbatim resends reproduce the error
- One retry is enough; persistent failure means the template or schema lacks coverage
- Never throw on degradation or use near-miss output; monitor retry rate and add failing inputs to the test set
答题要点
- 三层:校验返回错误列表、带着错误原因重试一次、失败后返回空值并记录
- 重试时必须把错误列表拼回用户消息,原样重发大概率同样的错
- 重试一次足够,稳定失败说明模板或 schema 缺覆盖,该修模板不该继续重试
- 降级不抛异常、不用半对的结果;监控重试率,稳定失败的输入加进测试集
D4 Iteration and Evaluation: Small Test Sets, A/B Testing, Version Control, Common Anti-Patterns
How do you build a test set for a prompt? How would you choose ten samples, and where do the expected answers come from?怎么给一个提示词建测试集?十条样本该怎么挑,标准答案从哪来?
Common in ChinaCommon overseasBasic#evaluation#test-setHow to reason about it · think before answering
- This screens for whether the candidate has actually built one. 'Collect some inputs and run them' means no; people who have start with distribution, because prompt errors cluster at the edges.
- Three classes with three or four each: normal inputs guard the baseline; edge inputs (missing defaults, optional fields, informal phrasing) test whether default rules are explicit; adversarial inputs (distractors, mid-sentence corrections, unrelated asks) test focus. Add one or two unknowable items to check honesty.
- Expected answers are labeled by hand, no shortcut; one mislabeled case skews the whole evaluation and sends you chasing a phantom prompt bug. Re-read each input after labeling to confirm the answer is unique.
- Conclusion: ten is enough to start, value lies in distribution not count, and the best source is every real 'it failed again' input from the past week.
- Follow-ups: how does the set grow? Add the triggering input before every prompt change. And leakage — test cases must not double as few-shot examples, or you are measuring memorization rather than generalization.
分析过程 · 先想清楚再作答
- 这题在筛「有没有真的建过测试集」。答「多找一些输入跑一跑」的人没建过;建过的人第一句会说分布——因为提示词的错误全集中在边界上。
- 拆法:三类各占三四条。正常输入守底线,新版弄坏它们就是严重回退;边界输入(没写默认值、可选字段、不规范写法)测默认规则说清没说清;刁难输入(干扰信息、中途改口、夹带无关要求)测能不能抓住重点。可以再放一两条模型不可能知道的样本,看它是否老实说不知道。
- 标准答案只能人工标,这一步没有捷径;标错一条整份评估就偏,而且你会误以为是提示词的问题去反复改。每条写完再读一遍输入确认答案唯一。
- 结论:十条够起步,价值在分布不在数量;最好的来源是过去每一次「它又错了」的真实输入,一周就能攒出比想象出来的更真实的测试集。
- 可预期的追问:测试集怎么增长?每次想改提示词先把触发的那条输入加进去再改;以及「测试集会不会泄漏进提示词」——用例不能直接当 few-shot 示例,否则是在测记忆而不是泛化。
Key points
- Distribution over count: three or four each of normal, edge and adversarial, plus a couple of unknowable items
- Normal cases guard the baseline, edge cases test defaults, adversarial cases test focus
- Expected answers are hand-labeled and re-checked; one wrong label skews everything
- Best source is real failures; add the triggering input before each prompt change
答题要点
- 价值在分布不在数量:正常、边界、刁难三类各三四条,再放一两条模型不可能知道的
- 正常输入守底线,边界测默认规则,刁难测抓重点
- 标准答案人工标注、逐条复核,标错一条整份评估就偏
- 最好的来源是真实出错的输入;每次想改提示词先把那条加进测试集
How do you version prompts? How does it differ from versioning code, and what is your first move when production misbehaves?提示词版本化怎么做?它和代码版本管理有什么不同?线上出问题你先做什么?
Common in ChinaCommon overseasIntermediate#prompt-versioning#evaluationHow to reason about it · think before answering
- One of the most frequent prompt-engineering interview questions abroad; it tests whether you have managed prompts as production assets. 'Put it in git' is the floor; the interviewer wants the changelog contents and why prompts differ from code.
- Shape first: prompt text in its own file, an id per version, decoupled from business code. Then the changelog's four items: what changed, why (which test cases failed), the measured pass rate, known regressions. The pass rate must come from the script.
- The difference is the differentiator: code changes are usually local, prompt changes are global — one added sentence can shift behavior on every input, so 'known regressions' is mandatory where commit messages have no such field. Also rollback is nearly free, just swap a string.
- Conclusion: when production misbehaves, roll back to the previous version first, then add the triggering input to the test set and investigate — which only works if you have version ids and a test set.
- Follow-ups: bind prompt versions to model versions? Yes — pass rates shift across model versions, so record which model was used. And canarying: route by version id and compare live metrics, same as code.
分析过程 · 先想清楚再作答
- 这题是海外面试里提示词工程方向出现频率最高的一道,考的是「有没有把提示词当成生产资产管理过」。答「放进 git」是最低分,面试官要听的是变更记录里写什么、以及为什么和代码不一样。
- 拆法:先说形态——提示词正文独立成文件、每版有 id、与业务代码解耦;再说变更记录四件事——改了什么、为什么改(对应哪几条测试失败)、跑出来的通过率、已知回退。通过率必须是脚本跑出来的数字。
- 不同点是区分度所在:代码改动通常是局部的,提示词改动是全局的——加一句话可能改变所有输入的行为,所以「已知回退」是必填项而代码提交信息里没有这一栏;另一点是回滚成本几乎为零,只是换一个字符串。
- 结论:线上出问题第一步是回滚到上一版,再拿触发问题的输入补进测试集慢慢查——前提是你有版本号可回、有测试集可跑。
- 追问:提示词版本要不要和模型版本绑定?要——同一份提示词在不同模型版本上通过率会变,记录里要写清是在哪个模型上测的;另一个追问是多环境怎么灰度,答案是按版本 id 分流并对比两版的线上指标,跟代码灰度一样。
Key points
- Prompt text lives in its own file with a version id; the changelog records change, reason, measured pass rate, known regressions
- Unlike code, prompt changes are global, so known regressions are mandatory; rollback is nearly free
- On a production issue, roll back first, then add the triggering input to the test set
- Record which model version was tested, since pass rates shift across models
答题要点
- 提示词正文独立成文件、每版有 id,变更记录写改动、原因、脚本跑出的通过率、已知回退
- 与代码的不同:改动是全局的,所以「已知回退」必填;回滚成本几乎为零
- 线上出问题先回滚上一版,再把触发输入加进测试集查
- 版本要记录在哪个模型上测的,换模型版本通过率会变
Is using a model to grade another model's output reliable? When is it acceptable, and when must a human look?用模型给模型打分靠谱吗?什么时候可以用,什么时候必须人工看?
Common in ChinaCommon overseasIntermediate#evaluation#llm-as-judgeHow to reason about it · think before answering
- This tests whether you know the judge is fallible too. 'Use a stronger model as the judge' means you never calibrated one; 'prefer field comparison whenever possible' shows judgment.
- Split by task: structured output gets field-by-field code comparison, no judge needed; free text (summaries, emails, explanations) has no fields, and a judge is the only scalable option.
- Judge biases: longer and prettier answers score higher, stylistic similarity gets rewarded, vague rubrics produce noisy scores, factual errors are under-penalized. So the rubric must be concrete — list the information points, one point each — not 'rate this summary 1 to 10'.
- Conclusion: usable once calibrated against ten human-scored samples with an agreement rate you accept; spot-check regularly; anything involving facts, safety or money still gets human review.
- Follow-ups: same vendor for judge and judged? Expect self-preference, so switch vendor or at least version. And cost — every judgment is a full call, so let code handle whatever it can first.
分析过程 · 先想清楚再作答
- 这题在考「知道裁判也会错」。答「用更强的模型当裁判就行」的人没校准过裁判;答出「能用字段比对就不用裁判」才说明有判断力。
- 拆法:先分任务。输出是固定字段就用代码逐字段比对,不需要裁判;输出是自由文本(摘要、邮件、解释)才没有字段可比,这时裁判是唯一能规模化的办法。
- 再说裁判的偏差:偏向长的、格式漂亮的、和自己风格接近的回答;评分标准含糊时打分随意;对事实性错误不敏感。所以评分标准要像便签一样具体——列出信息点、每点一分——而不是「给这段摘要打 1 到 10 分」。
- 结论:裁判可以用,前提是先拿十条人工打过分的样本校准它,看它和人的一致率;上线后定期抽样复核;对涉及事实、安全、金额的输出必须人工看。
- 追问方向:裁判和被评的模型是同一家会怎样?会有自我偏好,尽量换一家或至少换一个版本;另一个追问是「裁判的成本」,每条评估都是一次完整调用,测试集大了要算钱,所以能用代码判的部分先用代码判掉。
Key points
- Prefer field comparison; reserve the judge for free text with nothing to compare
- Judges favor long, well-formatted answers and score noisily on vague rubrics, so rubrics must list concrete points
- Calibrate against ten human-scored samples first, then spot-check regularly
- Facts, safety and money always get human review; use a different vendor or version to avoid self-preference
答题要点
- 能用字段比对就不用裁判;裁判只用于没有字段可比的自由文本
- 裁判偏向长的、格式漂亮的回答,评分标准含糊就打得随意,所以标准要具体到信息点
- 先用十条人工打分样本校准裁判,上线后定期抽样复核
- 涉及事实、安全、金额的输出必须人工看;裁判尽量换一家或换版本以避免自我偏好
D5 Migrating Across Models: Differences Between Claude / GPT / Domestic Chinese Models, Organizing the System Prompt; Where to Go Next — the Claude Course or the Codex Course
When you move a prompt from one model vendor to another, where does it break most often, and how do you tell a prompt problem from a genuine capability gap?同一份提示词从一家模型迁到另一家,最常坏在哪里?你怎么区分是提示词的问题还是模型能力的问题?
Common in ChinaCommon overseasIntermediate#model-migration#cross-modelHow to reason about it · think before answering
- This screens for whether you have actually migrated a prompt. 'The other model is just worse' means no; people who have know failures cluster in four places and are rarely capability gaps.
- Four breakage points, each with a detection method: format markers — do your structural symbols leak into the output; instruction strength — does an edge case get followed literally or embellished; refusal boundaries — do adversarial cases trigger new refusals or disclaimers; length habits — compare output length and item counts on normal inputs.
- To separate prompt from capability: map failing fields to one of the four; if they match, fix the vendor block. If not, check whether failures are on normal or edge cases — capability gaps show on normal cases too, while edge-only regressions are almost always vendor-specific defaults hiding in the prompt.
- Conclusion: nine out of ten regressions are unisolated 'dialect' fixed in the vendor block; genuine capability gaps are rare and surface on normal cases.
- Follow-up: if the SDK is API-compatible, is migration free? No — compatible requests do not mean compatible interpretation, and the four breakages are easier to miss precisely because nothing crashed.
分析过程 · 先想清楚再作答
- 这题在筛「有没有真的迁过」。没迁过的人会说「换个模型效果就差了」;迁过的人知道退步几乎都落在四处,而且多数不是能力差异。
- 拆法:四处断裂各配一个识别方法。格式标签——看输出里有没有出现你用来做结构的符号;指令强度——跑边界用例看是照办还是发挥;拒答边界——跑刁难用例看有没有新的拒答或多余说明;长度习惯——对比正常输入的输出长度与条目数。
- 区分提示词问题与能力问题:先看失败用例的字段能不能对上四处之一,能对上就改厂商适配块;对不上再看失败的是正常用例还是边界用例——能力差异通常在正常用例上也会体现,而边界用例上的退步几乎都是提示词里藏着只对某一家成立的默认。
- 结论:迁移退步十有九是「方言」没隔离,改厂商适配块就能恢复;真正的能力差异少见且会在正常用例上现形。
- 可预期的追问:接口兼容(同一份 SDK 调通)是不是就不用管了?不是——接口兼容只说明请求格式一样,四处断裂照样出现,而且更容易被忽略。
Key points
- Four usual suspects: format markers, instruction strength, refusal boundaries, length habits, each with a detection method
- Map failing fields to one of the four first; a match means fix the vendor block
- Capability gaps show on normal cases; edge-only regressions are almost always prompt dialect
- API compatibility is not behavioral compatibility — rerun the test set even when no code changed
答题要点
- 四处最常坏:格式标签、指令强度、拒答边界、长度习惯,各有识别方法
- 先把失败字段对四处对号,对上就改厂商适配块
- 能力差异会在正常用例上现形;只在边界用例上退步几乎都是提示词的方言
- 接口兼容不等于行为兼容,代码没改也要跑测试集
How should a system prompt be organized so it ports across models, and how do you decide which block a given sentence belongs to?系统提示应该怎么组织才方便跨模型复用?怎么判断某一句该放哪一块?
Common in ChinaCommon overseasIntermediate#system-prompt#model-migrationHow to reason about it · think before answering
- It looks structural but tests whether you have maintained one prompt across vendors. 'Just write it clearly' means no; experienced people start with blocks.
- Three blocks, one question each. Common rules — would this sentence still hold on another vendor? Role, task, done-criteria, reasoned constraints, schema. Vendor adaptation — is this true for one vendor only? Input wrapping, example style, length hints, refusal wording, structured-output switch; one per vendor, swapped wholesale. Task variables — does this change per call? Make it a parameter.
- Two self-checks are the differentiator: delete the vendor block entirely and see if what remains is still a readable prompt; grep the common block for any vendor-specific token — tag names, API parameter names, style preferences.
- Conclusion: the payoff goes beyond migration — the common block is the longest, most stable prefix, so leading with it maximizes prompt-cache hits; vendor block fixed per vendor; variables last. It extends the D1 principle of keeping stable content in the system prompt.
- Follow-up: where do few-shot examples go? Their content is common, their rendering (wrapping tags, code-block style) is vendor-specific, so split examples into content plus rendering, or at least keep vendor tags out of them.
分析过程 · 先想清楚再作答
- 这题看似问结构,实际在考「有没有维护过多家模型共用的一份提示词」。答「写清楚一点就能通用」的人没维护过;维护过的人会先说分块。
- 拆法:三块各回答一个问题。通用规则——这一句换一家模型还成立吗,成立放这里(角色、任务、完成标准、带理由的约束、schema);厂商适配——这一句是不是只对某一家成立,是的放这里(输入包裹方式、示例风格、长度提示、拒答边界表述、结构化输出开关),每家一份整块替换;任务变量——每次调用都在变吗,是的做成参数。
- 两条自检是区分度:把厂商块整块删掉,剩下的还是不是一份能读懂的提示词;通用块里搜有没有任何一家的专属词(标签名、API 参数名、风格偏好)。
- 结论:分块的收益不只是迁移——通用块是最长最稳定的前缀,放最前面缓存命中最高;厂商块每家固定;任务变量放最后。这跟 D1 讲系统提示要放稳定内容是同一条原则的延伸。
- 追问:few-shot 示例算哪一块?示例的内容属于通用规则,示例的书写风格(包裹标签、代码块风格)属于厂商适配,所以示例最好也拆成「内容 + 渲染」两层,或者至少不带厂商专属标签。
Key points
- Three blocks: common rules that hold across vendors, a per-vendor adaptation block swapped wholesale, and per-call task variables
- Two deciding questions: does it still hold on another vendor; does it change every call
- Self-checks: the prompt stays readable with the vendor block removed; no vendor-specific tokens in the common block
- Order common, vendor, variables so the most stable prefix leads and cache hits are maximized
答题要点
- 三块:通用规则(换模型仍成立)、厂商适配(每家一份整块替换)、任务变量(每次调用的参数)
- 判据是两个问题:换一家还成立吗;每次调用都在变吗
- 自检:删掉厂商块剩下的仍可读;通用块里没有任何一家的专属词
- 顺序通用、厂商、变量,最稳定的前缀在前,缓存命中最高
After migrating a prompt, how do you verify nothing regressed, and if the pass rate drops, what is your debugging order?迁移之后怎么验证效果没有退步?如果通过率降了,你的排查顺序是什么?
Common in ChinaCommon overseasDeep dive#model-migration#evaluationHow to reason about it · think before answering
- A combined D4/D5 question testing whether verification is a process. 'Run a few and see' is the floor; the interviewer wants baseline, identical cases, field-level triage.
- Verification needs a baseline: the same test set run on the source model beforehand with pass rate and failing cases recorded. Then run the identical cases on the target and put the columns side by side.
- Debugging order: normal versus edge failures first — edge failures point at the prompt; map failing fields to the four breakages and fix the vendor block, leaving the common block untouched; rerun; remaining failures that overlap the source model's are the prompt's own known regressions, unrelated to migration, handled through the D4 changelog.
- Conclusion: migration is done when the target matches the source pass rate with zero edits to the common block; editing the common block is a new prompt version and must be re-run on the source too.
- Follow-ups: single-run noise — run each case three times and take the majority, or use temperature zero. And keep dual-running for a while, because vendor model updates drift pass rates and the test set is the only thing that catches drift early.
分析过程 · 先想清楚再作答
- 这题是 D4 与 D5 的合题,考的是「验证有没有流程」。答「多跑几条看看」是最低分;面试官要听的是基线、同一批用例、逐字段对号。
- 拆法:验证的前提是基线——迁移前在源模型上跑过同一份测试集并记录通过率与失败用例;没有基线就没有「退步」可言。迁移后用完全相同的用例在目标模型上跑,两列并排。
- 排查顺序:先看失败用例是正常还是边界——边界优先怀疑提示词;再把失败字段对四处断裂对号,改厂商适配块,通用块不动;再跑一遍;仍失败的用例看是否与源模型的失败重合——重合的是提示词自身的已知回退,与迁移无关,按 D4 的变更记录处理。
- 结论:达到与源模型相同的通过率、且通用规则块一个字没改,迁移才算完成;改了通用块就等于改了提示词版本,要重新在源模型上跑。
- 追问:真模型有随机性,一次运行的通过率能信吗?每条跑三次取多数或温度设 0;另一个追问是要不要在两家上长期并跑,答案是至少保留一段时间的双跑对比,因为模型版本更新会让通过率漂移,测试集是唯一能及时发现漂移的工具。
Key points
- Verification requires a baseline: the same test set run on the source model before migrating
- Run identical cases on the target and compare pass rates and failing cases side by side
- Triage: normal versus edge, map failing fields to the four breakages, fix the vendor block only, rerun, and treat failures shared with the source as known regressions
- Any edit to the common block is a new version that must be re-run on the source; tame randomness with majority-of-three or temperature zero
答题要点
- 验证前提是基线:迁移前在源模型跑过同一份测试集
- 同一批用例在目标模型上跑,两列并排看通过率与失败用例
- 排查顺序:正常还是边界 → 失败字段对四处断裂 → 改厂商块不动通用块 → 重跑 → 与源模型重合的失败是已知回退
- 通用块改了就是新版本,要回源模型重跑;随机性用多次取多数或温度 0 压住
Mastering Claude: From Conversation to Claude Code in 5 Days
D1 Advanced Prompting and Claude's "Personality": System Prompt, XML Tags, Letting the Model Think First, Structured Output
What belongs in a system prompt and what doesn't? If the model keeps ignoring one rule, what do you check first?system prompt 应该放什么、不该放什么?如果一条规则模型总是不遵守,你会先检查什么?
Common in ChinaCommon overseasBasic#system-prompt#prompt-designHow to reason about it · think before answering
- The question tests boundaries, not writing skill. Naming what to exclude, and why, is what separates a strong answer.
- Give the rule: the system prompt is a fixed premise resent on every request, so it holds only what is true for the whole conversation — role, constraints as prohibitions, output style. Anything that varies per turn belongs in the user message.
- Then the anti-patterns: obvious conventions, pasted API docs, and per-turn material dilute the important rules and also invalidate the prompt-cache prefix on every call.
- Debug order for an ignored rule: check length first and prune, then check for ambiguity or conflicting rules, and only then add emphasis. If the rule is a must-run action, move it to a deterministic gate instead of adding more words.
- Likely follow-up: can system go last? Possible but unwise — earlier instructions carry more weight and a moving prefix breaks caching.
分析过程 · 先想清楚再作答
- 这题考的是「职责边界」而不是「会不会写」。答成「放角色和要求」是及格线,能说出「不该放什么」以及「为什么」才有区分度。
- 先给一条判据:system prompt 是每次请求都重发的固定前提,所以只放整场对话都成立的东西——角色、边界(禁止项)、输出风格;每次都变的(时间、用户名、本轮材料)放 user 消息。
- 再说反面:把模型本来就知道的常识(「写干净的代码」)、大段 API 文档、每轮都不一样的材料塞进 system,只会稀释真正重要的规则,还会让 prompt caching 的前缀每次都变。
- 「规则总是不遵守」的排查顺序:先看 system 是不是太长导致规则被淹没(删到不能再删),再看规则是否含糊或与别的规则冲突,最后才考虑加强调;如果是「每次必须执行」的动作,应该改成程序层面的门禁而不是继续加规则。
- 可预期的追问:system 放最后行不行?可以但不推荐——模型对靠前的指令更敏感,且会破坏缓存前缀。
Key points
- Include role, prohibitions, and output style — premises that hold for the whole conversation
- Exclude volatile facts, common sense the model already has, and long pasted docs
- The system prompt is resent every request: longer means costlier and rules get buried
- For an ignored rule: prune first, disambiguate second, emphasize last; must-run actions become deterministic gates
答题要点
- 放:角色、边界(写禁止项)、输出风格;整场对话都成立的固定前提
- 不放:会变的信息(时间、用户名、本轮材料)、模型本来就知道的常识、大段文档
- system 每次请求重发,越长越贵,也越容易让关键规则被淹没
- 规则不被遵守先删再改再强调;「每次必须做」的动作改成程序门禁
Why does organizing long prompts with XML tags work so well for Claude, and how does it differ from using Markdown sections?为什么用 XML 标签组织长提示词对 Claude 特别有效?和用 Markdown 分段比有什么区别?
Common in ChinaCommon overseasIntermediate#xml-tags#long-contextHow to reason about it · think before answering
- The keyword is why. Citing the docs is not an answer; explain it in terms of how the model detects content boundaries.
- Breakdown: the core risk in a long prompt is mixing material, instructions, and examples. Paired tags give each part an explicit start, end, and name, so the model separates them reliably and can reference a specific section in its reply.
- Versus Markdown: headings and fences delimit but have no explicit closing marker, so pasted material that itself contains Markdown breaks the structure. XML tags are paired, nestable, and freely named, and the benefit grows with messier input.
- Add the engineering habits: consistent tag names, instructions after the material, and asking the model to cite tags in its answer. Three to six top-level tags is typical.
- Follow-ups: is there a fixed tag vocabulary? No — structure and semantics matter, consistency within one prompt matters. What if the material contains XML? Pick non-colliding names.
分析过程 · 先想清楚再作答
- 题眼在「为什么」。只答「官方推荐」等于没答;要能从「模型如何分辨内容边界」这个角度解释。
- 拆法:长提示词的核心风险是不同性质的内容(材料、指令、示例)混在一起,模型分错边界就会把材料里的句子当指令执行、或把示例当成事实。成对的标签给每一段一个明确的起止和名字,模型分辨边界的准确率更高,也能在回答里精确引用「哪一段」。
- 与 Markdown 的区别:Markdown 靠标题和围栏分段,但没有显式的结束标记;当贴进去的材料本身含 Markdown(比如一份 README)时容易串位。XML 标签成对、可嵌套、名字自定义,材料越杂优势越大。
- 补一条工程习惯:标签名前后一致,指令放在材料之后,回答时要求引用标签名。三到六个顶层标签是常态,不要过度包装。
- 可预期的追问:标签名有没有固定词表?没有,模型看的是结构和语义,但同一个提示词内要一致;另一个追问是「材料本身含 XML 怎么办」——换一个不会撞的标签名,或用 CDATA 式的转义说明。
Key points
- Long prompts mix material, instructions, and examples; paired tags give each an explicit boundary and name
- Boundary detection becomes reliable and the model can cite a specific section
- Markdown has no closing marker and breaks when pasted material contains Markdown; XML tags are paired, nestable, and freely named
- Habits: consistent names, instructions after material, ask for tag citations, three to six top-level tags
答题要点
- 长提示词的风险是材料、指令、示例混在一起;成对标签给每段明确的起止和名字
- 模型分辨边界更准,也能在回答里精确引用某一段
- Markdown 没有显式结束标记,材料含 Markdown 时会串位;XML 标签成对、可嵌套、可自定义
- 习惯:标签名一致、指令放材料之后、要求引用标签、顶层标签三到六个
When should you have the model write its analysis before answering, and when should you go straight to structured output? Can you have both?什么时候该让模型先写分析再回答,什么时候直接要结构化输出?两者能同时要吗?
Common in ChinaCommon overseasIntermediate#structured-output#reasoningHow to reason about it · think before answering
- This is a trade-off question; the criteria are who consumes the output and what an error costs. Give a decision rule, not 'it depends'.
- Chain: the analysis is output tokens, billed at output rates and adding latency, in exchange for higher accuracy on complex tasks and an auditable trace. The more complex, high-stakes, or human-reviewed the task, the more you want it; bulk, simple, machine-consumed tasks go straight to structured output.
- Can you have both? Once a JSON schema is passed the output is constrained to JSON, so free-text analysis has nowhere to go. Two options: add a reasoning field placed before the other fields, or rely on the model's internal thinking, whose depth you control but not its content.
- Production nuance: structured output fixes parsing reliability, not judgment quality; the schema cannot express numeric ranges or string lengths, so validate those yourself.
- Follow-ups: can the analysis leak into downstream code? Yes — separate analysis and answer with tags and parse only the answer. Thinking versus a written analysis: internal versus visible, depth versus content.
分析过程 · 先想清楚再作答
- 这题考取舍,判据是「谁消费输出」和「错误的代价」。答「都用」或「看情况」没有信息量,要给出可执行的判断句。
- 推导链:分析段是输出 token,按输出价计费,且会让响应变长;它换来的是复杂任务上更高的准确率与可核对的推理过程。所以任务越复杂、错误代价越高、越需要人审计,越该要分析段;批量、简单、程序直接消费的任务,直接要结构化输出。
- 「能不能同时要」:一旦传了 JSON Schema,输出被约束成 JSON,自由文本的分析段没地方放。两条路:在 schema 里加一个 reasoning 字段放在其他字段前面(模型会先生成它),或者依赖模型内部的 thinking——它是内部推理,你控制深度不控制内容。
- 生产视角:结构化输出解决的是「解析可靠性」,不是「判断正确性」;schema 不支持数值范围与字符串长度约束,这些校验要自己补。
- 可预期的追问:分析段会不会被程序误用?会,所以要用标签把分析段与答案段分开,程序只取答案段;另一个追问是 thinking 与分析段的区别——一个内部一个外显,一个控深度一个控内容。
Key points
- Written analysis costs output tokens and latency but raises accuracy and gives an auditable trace — use for high-stakes, human-reviewed work
- Structured output is consumed directly by code with zero parse failures — use for bulk, simple extraction and classification
- To combine: put a reasoning field first in the schema, or rely on internal thinking
- Structured output guarantees shape, not correctness; add range and length validation yourself
答题要点
- 分析段:输出 token 计费、更慢,但复杂任务更准、过程可核对;适合高风险、需人审的任务
- 结构化输出:程序直接消费、解析零失败;适合批量、简单、明确的抽取与分类
- 同时要:在 schema 里加靠前的 reasoning 字段,或依赖内部 thinking
- 结构化输出保证的是格式不是正确性;范围与长度校验要自己补
D2 Long Documents, Multimodal Input, and a First Look at the API: Using Large Context, Saving Money With Prompt Caching, PDF and Image Input, Citation-Backed Answers; a Minimal Messages API Call
Why is the context window called the scarcest resource in LLM applications? With million-token windows, does that still hold?为什么说上下文窗口是 LLM 应用里最稀缺的资源?窗口已经有一百万 token 了,这个说法还成立吗?
Common in ChinaCommon overseasBasic#context-window#costHow to reason about it · think before answering
- The second sentence is the point. 'The window has a limit' is a dated answer; explain why scarcity survives large windows.
- Three causal chains: models are stateless so every request re-reads the whole input and bills it, a big window only solves fitting, not re-sending; longer context means more latency and diluted attention, so adherence to early instructions degrades as the window fills; and in agent workflows every file read and command output lands in the same window, filling it far faster than chat does.
- Conclusion: scarcity shifted from 'won't fit' to 'every token costs money and attention', so the discipline becomes active management — include only what is needed, cache the stable prefix, delegate research to subagents with their own context, and clear between tasks.
- Production math: a 60-page PDF is roughly 100k tokens; ten questions about it are a million input tokens; caching versus not caching is an order of magnitude apart.
- Follow-up: when should context accumulate? While deep in one complex problem where the history is still load-bearing; the test is whether the next step will use it.
分析过程 · 先想清楚再作答
- 题眼在第二句。只答「窗口有上限」已经过时了,面试官想听的是「窗口变大之后为什么还稀缺」。
- 从三条因果链推:一、模型无状态,每次请求都把全部输入重读一遍,输入 token 按次计费——窗口大只解决了放得下,没解决每次都要重搬;二、上下文越长,延迟越高、注意力越稀释,模型对早期指令的遵守度会下降,也就是「性能随填充度下降」;三、Agent 场景里每读一个文件、每跑一条命令的输出都进同一个窗口,填得比聊天快得多。
- 结论:窗口大了,稀缺性从「放不下」变成了「每一 token 都在花钱和稀释注意力」,所以管理手段变成了主动管:只放必要的、把不变的缓存起来、把查资料的活派给独立上下文的子代理、该清就清。
- 生产视角:算一笔账——60 页 PDF 约 10 万 token,围着它问 10 个问题就是 100 万输入 token;不用缓存和不用缓存的差价是一个量级。
- 可预期的追问:那什么时候应该让上下文积累?在一个复杂问题里深挖时历史是有价值的;判据是「这段历史下一步还会不会用到」。
Key points
- Models are stateless: every request re-reads and bills the full input; a large window solves fitting, not re-sending
- Longer context raises latency and dilutes attention; adherence to early instructions drops
- Agent workflows dump every file read and command output into the same window
- Tactics: include only what's needed, cache the stable prefix, isolate research in subagents, clear between tasks
答题要点
- 模型无状态,每次请求重读全部输入并计费;窗口大只解决放得下,不解决每次重搬
- 上下文越长延迟越高、注意力越稀释,早期指令遵守度下降
- Agent 场景每次读文件、跑命令的输出都进窗口,填得比聊天快得多
- 对策:只放必要的、缓存不变前缀、用子代理隔离查资料、任务之间清空
Where does prompt caching save money, when does it cost more, and how do you debug a zero cache-hit rate in production?prompt caching 省在哪?什么情况下反而不省?线上发现缓存命中率是零,你怎么排查?
Common in ChinaCommon overseasIntermediate#prompt-caching#costHow to reason about it · think before answering
- Three questions, three layers: mechanism, boundaries, debugging. The third layer is what shows production experience.
- Mechanism: the cache matches the exact byte prefix from the start of the request to the cache_control marker (tools, then system, then messages). A hit bills that prefix at 0.1x input price; the write costs 1.25x (2x for the one-hour TTL).
- When it costs more: a prefix used only once (+25%); volatile content inside the prefix — timestamps, random ids, unsorted JSON, user names — so every call writes a cache nothing will read; a prefix below the minimum (1024 tokens on current flagship models, 4096 on Haiku 4.5) that silently never caches; requests spaced beyond the TTL.
- Debug order by likelihood: dynamic content at the head of system or tool definitions; model id mismatch between calls; prefix under the minimum; gap over five minutes; unstable tool ordering. The single signal is usage.cache_read_input_tokens greater than zero.
- Follow-up: where do breakpoints go? At the end of stable sections — tools, system, the long document, the second-to-last message in a multi-turn chat — at most four; a breakpoint on per-turn content is a wasted write.
分析过程 · 先想清楚再作答
- 三问对应三层:原理、边界、排查。只答第一层是背文档,第三层才体现有没有真的上过线。
- 原理一句话:缓存匹配的是请求开头到 cache_control 标记为止的精确前缀(顺序是工具、system、messages),命中时这段只收正常输入价的 0.1 倍;代价是写入那一次收 1.25 倍(1 小时档 2 倍)。
- 不省的情况由此推出:同一前缀只用一次(多付 25%);前缀里有每次都变的内容(时间戳、随机 id、未排序 JSON、用户名),导致每次都在写永远用不上的缓存;前缀短于最小门槛(主力模型 1024 token,Haiku 4.5 是 4096)根本不会缓存;两次请求间隔超过 TTL。
- 排查清单按发生概率排:一看 system 或工具定义开头有没有动态内容;二看两次请求的模型 id 是否一致;三看前缀长度是否过门槛;四看间隔是否超 5 分钟;五看工具列表顺序是否稳定。判据只有一个字段:usage.cache_read_input_tokens 是否大于 0。
- 可预期的追问:断点应该打在哪?不变的末尾——工具定义末尾、system 末尾、长文档末尾、多轮对话倒数第二条消息,最多四个;打在每轮都变的内容上等于白写。
Key points
- Matches the exact prefix (tools → system → messages up to the marker); hits bill 0.1x, writes 1.25x
- Costs more when the prefix is used once, contains volatile content, is under the minimum length, or requests exceed the TTL
- Debug: dynamic content, model mismatch, length, gap, tool ordering; verify via cache_read_input_tokens
- Place breakpoints at the end of stable sections, at most four
答题要点
- 匹配精确前缀(工具 → system → messages 到标记为止);命中 0.1 倍,写入 1.25 倍
- 不省:前缀只用一次、前缀含动态内容、前缀短于最小门槛、间隔超过 TTL
- 排查:动态内容、模型不一致、长度不够、间隔太久、工具顺序变了;看 cache_read_input_tokens
- 断点打在不变部分的末尾,最多四个
How do API citations fundamentally differ from prompting the model to quote sources with page numbers, and when can't you use them?citations 和在提示词里要求模型「引用原文并注明页码」有什么本质区别?什么场景下不能用 citations?
Common in ChinaCommon overseasIntermediate#citations#groundingHow to reason about it · think before answering
- The question is about where trust comes from. 'Citations are more convenient' is surface; the real difference is who guarantees the quote is real.
- With prompting, both the quote and the page number are free text the model generates — it may paraphrase, it may misremember the page, and you cannot tell a real quote from an imagined one. With citations, the model emits citation intent in a standard format, the API parses and verifies it server-side, cited_text is guaranteed to exist in the document, and page_location comes from the API. Fidelity is enforced by the API rather than promised by the model.
- Two side benefits: cited_text does not count toward output tokens, so it is cheaper than asking the model to copy; and the result is structured content blocks your UI can highlight and jump to without regex guessing.
- When you can't: citations are incompatible with structured outputs (JSON Schema) — enabling both returns a 400. Either drop API-level citations and add a page field to the schema (one notch less reliable), or split into two calls: citations for facts, structured output for shaping.
- Follow-ups: page semantics — start_page_number is 1-indexed and end_page_number is exclusive; document_index distinguishes sources. Can you audit citation quality? Yes — string-match cited_text against the source, or sample manually.
分析过程 · 先想清楚再作答
- 这题考的是「可信度从哪来」。答成「citations 更方便」是表面;本质区别是谁来保证引用的真实性。
- 拆法:提示词方案里,引用和页码都是模型生成的自由文本——它可能顺手改写原文、可能记错页码,你无法区分「真引用」和「自以为引用」。citations 方案里,模型内部以标准格式输出引用意图,API 在服务端解析并核对,返回的 cited_text 一定是文档里真实存在的段落,page_location 的页码由 API 给出。真实性由 API 保证而不是由模型自觉保证。
- 附带的两点好处:cited_text 不计入输出 token,比让模型抄原文便宜;返回是结构化的内容块,程序可以直接高亮、跳转,不用正则去猜「第 3 页」出现在哪。
- 不能用的场景:与结构化输出(JSON Schema)不兼容,二者同开会报 400;此时要么放弃 API 级引用、在 schema 里留 page 字段让模型自己填(可靠性差一档),要么分两步:先 citations 拿事实,再用结构化输出整理。
- 可预期的追问:页码字段的语义?start_page_number 从 1 开始,end_page_number 不包含;多文档时 document_index 区分来源。再追问「能否验证引用质量」——能,用 cited_text 与原文做字符串比对,或抽样人工核对。
Key points
- Prompted quotes are free text the model generates — it may paraphrase or misplace pages, and you can't tell
- Citations are parsed and verified server-side; cited_text is guaranteed to exist and page numbers come from the API
- cited_text is free of output-token cost and the structured blocks enable highlighting and navigation
- Mutually exclusive with structured outputs; split into two calls or add a page field to the schema
答题要点
- 提示词引用是模型生成的自由文本,可能改写、记错页码,无法区分真假
- citations 由 API 在服务端解析核对,cited_text 一定存在于文档中,页码由 API 给出
- cited_text 不计输出 token,返回结构化便于高亮跳转
- 与结构化输出互斥;需要两者时分两步或在 schema 留 page 字段
D3 Getting Started With Claude Code and Managing Context: Install, Writing CLAUDE.md and "Trim Until You Can't", Permission Modes, Plan Mode's "Explore, Then Plan, Then Write", /clear /compact /rewind, Giving Claude a Verifiable Check
How do CLAUDE.md and skills divide responsibilities, what goes where, and what goes wrong when CLAUDE.md grows to 500 lines?CLAUDE.md 和 skill 的分工是什么?什么内容该放哪边?一份 CLAUDE.md 写到 500 行会出什么问题?
Common in ChinaCommon overseasBasic#claude-md#skills#contextHow to reason about it · think before answering
- This tests context-cost awareness. 'CLAUDE.md holds rules, skills hold procedures' is the conclusion; derive it from how each is loaded.
- Start from load timing: CLAUDE.md enters context in full every session — a fixed cost; a skill keeps only its one-line description resident and loads its body on invocation — a variable cost. Hence short facts that always apply go in CLAUDE.md, occasional multi-step procedures go in skills.
- Give the table: commands, non-default style, repo etiquette, environment quirks, and the definition of done belong in CLAUDE.md; deployment runbooks, issue-fixing steps, document generators belong in skills. Multi-step procedures in CLAUDE.md or always-on rules inside a skill are both misplacements.
- At 500 lines the failure is dilution, not capacity: important rules drown, adherence drops, and every turn pays for the bloat. Fixes: prune ruthlessly (would removing this cause a mistake?), move occasional content to skills, split path-scoped rules into .claude/rules/ so they load only when matching files are touched.
- Follow-ups: a rule that keeps being ignored — prune, then disambiguate, then emphasize; anything that must run every time should be a hook, not a sentence.
分析过程 · 先想清楚再作答
- 这题考的是「上下文成本意识」。答成「CLAUDE.md 放规则、skill 放流程」只是结论,面试官想听你从加载方式推出这个结论。
- 拆法从加载时机入手:CLAUDE.md 每次会话整份进入上下文,是固定成本;skill 只有描述那一行常驻,正文在被触发(模型判断相关或用户输入 /name)时才加载,是按需成本。所以「每次都成立的短事实」放 CLAUDE.md,「偶尔才用、一用就是多步」的流程放 skill。
- 给判据表:命令、风格差异、仓库礼仪、环境怪癖、完成的判据进 CLAUDE.md;部署流程、修 issue 的固定步骤、某类文档的生成方法进 skill。反过来,CLAUDE.md 里出现了多步流程,或 skill 里放了「每次都要遵守」的规则,都是放错了。
- 500 行的问题不是「太长跑不动」,而是稀释:重要规则被淹没,模型的遵守度反而下降,还白白吃掉每轮的窗口。对策是「删到不能再删」(删掉会不会让它犯错?不会就删)、把偶尔用的挪进 skill、按路径拆进 .claude/rules/ 只在碰到匹配文件时加载。
- 可预期的追问:「规则它老是不听怎么办」——先删再改再强调;「必须每次执行」的动作根本不该靠 CLAUDE.md,要改成 hook。
Key points
- CLAUDE.md loads in full every session — fixed cost; a skill keeps one line resident and loads on demand
- CLAUDE.md: short always-true facts — commands, style deltas, etiquette, definition of done; skills: occasional multi-step procedures
- Bloat dilutes: key rules drown, adherence drops, every turn pays
- Fixes: prune, move occasional content to skills, split path-scoped rules; must-run actions become hooks
答题要点
- CLAUDE.md 每次会话整份加载,是固定成本;skill 只常驻一行描述,正文按需加载
- CLAUDE.md 放每次都成立的短事实:命令、风格差异、规矩、完成判据;skill 放偶尔用的多步流程
- 写长的后果是稀释:重要规则被淹没、遵守度下降、每轮白付窗口
- 对策:删到不能再删、偶尔用的进 skill、按路径拆进 rules;必须每次做的改成 hook
Why does the context window need active management in Claude Code, and when do you use /clear, /compact, and /rewind respectively?在 Claude Code 里为什么上下文窗口需要主动管理?/clear、/compact、/rewind 分别在什么时候用?
Common in ChinaCommon overseasIntermediate#context-window#claude-codeHow to reason about it · think before answering
- The keyword is active. Waiting for auto-compaction works, but the interviewer wants to hear that performance degrades before the window is full.
- Why: every file read, command output, and turn lands in one window; a single debugging pass can be tens of thousands of tokens; as it fills the model forgets earlier instructions and errs more, so the discipline is controlling what enters from the start.
- Then the three commands, keyed on whether the history is still useful: switching tasks with useless history — /clear; mid-task with useful history but a filling window — /compact, optionally with instructions on what to keep; wrong direction — /rewind (Esc Esc) to restore conversation or code to a checkpoint, or summarize just one span.
- Two rules of thumb: after two failed corrections, /clear and rewrite the prompt — failed attempts keep polluting; use /btw for side questions that shouldn't enter history; delegate research to a subagent with its own window.
- Follow-ups: when should context accumulate? While deep in one problem where history is still referenced. Limits of rewind: it tracks only edits made through Claude's editing tools, not Bash-driven changes, and is no substitute for git.
分析过程 · 先想清楚再作答
- 题眼是「主动」。被动等自动压缩也能用,面试官想知道你是否理解「窗口填满之前性能就已经在下降」。
- 先说为什么:Claude Code 读的每个文件、跑的每条命令输出、每轮对话都进同一个窗口,一次调试就是几万 token;窗口越满模型越容易忘掉早先的指令、越容易出错,所以不是满了才处理,而是从一开始就控制进什么。
- 再分三个命令,判据是「这段历史还有没有用」:任务切换且历史无用——/clear 清零;任务未完但窗口快满、历史有用——/compact 压缩成摘要,可带指令指定保留什么;走错了方向、想回到某个点——/rewind(Esc Esc)恢复对话或代码到检查点,也能只对某一段做摘要。
- 补两条经验规则:同一问题纠正两次还不对就 /clear 重开,失败的尝试留在窗口里只会继续污染;旁枝问题用 /btw,答案不进历史;查资料派给 subagent,让它在自己的窗口里翻。
- 可预期的追问:什么时候应该让上下文积累?深挖一个复杂问题、历史仍在被引用时;判据是下一步还会不会用到这段历史。再追问 rewind 的边界:只追踪 Claude 用编辑工具做的改动,Bash 改的文件不在其中,不替代 git。
Key points
- Every read, output, and turn shares one window; fullness degrades adherence, so control inputs from the start
- /clear between unrelated tasks or after two failed corrections
- /compact mid-task when history matters but space runs low; pass instructions on what to keep
- /rewind to a checkpoint for conversation or code, or summarize a span; not a git replacement
答题要点
- 所有文件读取、命令输出、对话都进同一窗口;越满越容易忘指令、出错,要从一开始控制
- /clear:切换任务、历史无用时清零;两次纠正无效也清
- /compact:任务未完、历史有用但窗口快满;可带指令指定保留内容
- /rewind:回到检查点恢复对话或代码,或只对一段做摘要;不替代 git
Why is 'give Claude a check it can run' the dividing line for using agents well, and what levels of enforcement can that check have?为什么说「给 Claude 一个可验证的检查」是用好 Agent 的分水岭?检查可以有哪几档硬度?
Common in ChinaCommon overseasIntermediate#verification#agent-loopHow to reason about it · think before answering
- This tests understanding of the agent loop. 'Tests matter' is common sense; explain where the loop closes without a check.
- Chain: an agent works in a do–observe–adjust loop and stops on 'looks done'. Without a runnable check, 'looks done' is the only signal and the verification step falls on you — every mistake waits to be noticed; present, it is a tool, absent, it is a risk. With a check (tests, build exit code, lint, diff-against-fixture, screenshot compare) the loop closes inside the machine: it works, runs, reads, and iterates to green while you review evidence.
- Four levels: in the prompt ('run the tests until they pass') — usable today; as a /goal — an independent evaluator re-checks every turn; as a Stop hook — the turn cannot end until the check passes, deterministic; as a reviewer subagent — the one who did the work is not the one grading it. Each step trades setup for attention.
- Production nuance: demand evidence, not claims — test output, commands and return values, screenshots; reviewing evidence beats re-running.
- Follow-up: can the check itself be gamed? Yes — the model might edit tests to pass. Counter with a deny rule on the test directory or a reviewer specifically checking for test tampering.
分析过程 · 先想清楚再作答
- 这题考对 Agent 循环的理解。答成「测试很重要」是常识;要说清没有检查时循环在谁那里闭合。
- 推导:Agent 在「做、看结果、改」的循环里工作,停下来的信号是「看起来做完了」。没有可运行的检查,「看起来做完了」是唯一信号,验证环落在人身上——每个错误都要等你注意到,你在场它是工具,你不在场它是风险。有了检查(测试、构建退出码、lint、比对脚本、截图对照),循环在机器里闭合:它做、它跑、它读结果、它改到通过,你只审证据。
- 硬度分四档:写进提示词(「实现后跑 pnpm test 直到全过」)——今天就能用;设为 /goal——独立评估器每轮复核直到达成;写成 Stop hook——测试不过不允许结束,确定性门禁;交给另一个 subagent 复核——做的人和判的人分开。每升一档多一点配置,换来少一点盯着。
- 生产视角:要求展示证据而不是宣布成功——贴测试输出、贴命令与返回值、贴截图;审证据比自己重跑快。
- 可预期的追问:检查本身会不会被绕过?会——模型可能改测试让它过。对策是把测试目录放进禁改清单,或让 reviewer subagent 专门核对「有没有为了过而改测试」。
Key points
- Without a check the loop closes on you; with one it closes inside the machine
- A check is anything with a pass/fail signal: tests, build, lint, fixture diff, screenshot compare
- Four levels: prompt instruction, /goal re-evaluation, Stop hook gate, independent reviewer subagent
- Demand evidence over claims; guard against test tampering with deny rules or a dedicated reviewer
答题要点
- 没有检查时循环在人身上闭合,每个错误都等你发现;有检查时循环在机器里闭合
- 检查可以是测试、构建、lint、比对脚本、截图对照,任何能产生通过/失败信号的东西
- 四档硬度:提示词里要求、/goal 每轮复核、Stop hook 确定性门禁、subagent 独立复核
- 要证据不要宣言;防止改测试作弊要靠禁改清单或专门的复核
D4 Extending Claude Code: Hooks (Deterministic) vs. CLAUDE.md (Advisory), Skills, Subagents, Plugins, Wiring Up an MCP Server, CLI Tools First
Why are hooks more reliable than rules in CLAUDE.md? What belongs in each? Give one rule you would move from CLAUDE.md to a hook.为什么 hooks 比 CLAUDE.md 里的规则更可靠?各适合放什么?举一个你会从 CLAUDE.md 挪到 hook 的例子。
Common in ChinaCommon overseasBasic#hooks#claude-mdHow to reason about it · think before answering
- This tests the systemic position of advisory versus deterministic, not feature recall. 'Hooks are scripts that run automatically' is a description; explain why model adherence is not program execution.
- Breakdown: CLAUDE.md enters the model's context as text and the model decides after reading — adherence is high but not total, drops as the file grows, and can be lost after compaction. A hook is a script Claude Code itself runs unconditionally at fixed lifecycle points (PreToolUse, PostToolUse, Stop), with the exit code deciding whether to block, independent of the model's judgment.
- One-line rule: actions that allow zero exceptions become hooks; preferences that usually apply stay in CLAUDE.md. The inverse also holds — delete rules the model follows by default, convert must-always rules into hooks, and the file shrinks.
- Make the example concrete: 'run lint and tests before committing' is occasionally skipped as text; as a Stop hook, failing tests exit 2 and the model receives the summary and keeps fixing. 'Never edit migrations/' becomes a PreToolUse hook matching Edit|Write that exits 2 on a path hit.
- Follow-ups: risks? Hooks are code running on your machine — a cloned repo's hooks execute, and headless mode shows no trust dialog; a Stop hook is overridden after 8 consecutive blocks to prevent loops.
分析过程 · 先想清楚再作答
- 这题考的是「建议 vs 确定性」的系统位置,不是背功能名。答「hooks 是自动执行的脚本」只是描述,要说清为什么模型的遵守率不等于程序的执行率。
- 拆法:CLAUDE.md 的内容作为文字进入模型上下文,由模型读后决定怎么做——遵守率高但不是百分之百,文件越长越低,压缩后还可能丢失。hook 是 Claude Code 程序在固定生命周期点(PreToolUse / PostToolUse / Stop 等)无条件运行的脚本,由退出码决定拦不拦,与模型的判断无关。
- 判据一句话:一次例外都不能有的动作做成 hook;通常应该这样的偏好写进 CLAUDE.md。反向操作也成立:CLAUDE.md 里模型已经默认遵守的删掉,必须百分之百的换成 hook,文件就短了。
- 例子要具体:「提交前跑 lint 与测试」——作为文字它偶尔会被跳过;做成 Stop hook,测试不过 exit 2,模型收到失败摘要继续修,直到通过;「不许改 migrations/」做成 PreToolUse hook 匹配 Edit|Write,路径命中就 exit 2。
- 可预期的追问:hook 有没有风险?有——它是代码,跑在你机器上,clone 陌生仓库时别人的 hook 会执行,无头模式没有信任对话框;Stop hook 连续 8 次阻止后会被放行防死循环。
Key points
- CLAUDE.md is text the model reads and then decides on — high but not total adherence
- A hook is a script the program runs unconditionally at lifecycle points; the exit code decides, not the model
- Zero-exception actions become hooks; usual preferences stay in CLAUDE.md
- Examples: pre-commit tests as a Stop hook; a migrations deny as a PreToolUse hook
答题要点
- CLAUDE.md 是送进上下文的文字,由模型读后决定,遵守率高但不是百分之百
- hook 是程序在固定生命周期点无条件跑的脚本,退出码决定拦不拦,与模型判断无关
- 一次例外都不能有的做 hook;通常应该这样的写 CLAUDE.md
- 例:提交前测试改成 Stop hook;禁改 migrations 改成 PreToolUse hook
What is progressive loading for skills, why does it save context, and how should the description be written?skill 的渐进式加载是怎么回事?为什么能省上下文?description 应该怎么写?
Common in ChinaCommon overseasIntermediate#skills#contextHow to reason about it · think before answering
- This tests the on-demand loading idea and whether you have actually written a skill. The third part separates candidates: a poorly written description makes the skill dead weight.
- Mechanism: at session start only each skill's one-line description from the frontmatter is resident; the body loads when the model judges the task relevant or the user types /name. Body length therefore barely affects daily cost, so it can hold long procedures, examples, and caveats.
- Contrast with CLAUDE.md: loaded in full every session, a fixed cost; a procedure used twice a week wastes the window the rest of the time. Moving such content into skills is how CLAUDE.md keeps shrinking after pruning.
- Writing the description: state what it does plus the phrases a user would say, about a hundred words; too broad triggers on unrelated tasks, too narrow never triggers. Add disable-model-invocation: true for side-effecting workflows so only /name invokes them; $ARGUMENTS takes parameters; allowed-tools pre-approves commands.
- Follow-ups: how do you test triggering? Try several natural phrasings and check whether the body loaded. Skill versus subagent: a skill loads a manual into the current context; a subagent opens a separate context to do work; they compose.
分析过程 · 先想清楚再作答
- 这题考的是「按需加载」这个设计思想,以及你有没有真写过 skill。第三问是区分度:description 写不好,skill 就形同虚设。
- 机制:会话开始时只有每个 skill 的 frontmatter 里那一行 description 常驻上下文;当模型判断当前任务相关、或用户输入 /name 时,正文才被读进来。所以正文长短几乎不影响日常成本,可以放几十步的流程、示例、注意事项。
- 对比 CLAUDE.md:它整份每次加载,是固定成本;一周只用两次的流程放进去等于其余时间白占窗口。把这类内容挪到 skill,是「删到不能再删」之后 CLAUDE.md 还能继续变短的主要手段。
- description 的写法:说清做什么 + 用户会怎么说(触发词),一百来字;太泛会被无关任务误触发,太窄永远触发不到。有副作用的流程(部署、发消息)加 disable-model-invocation: true 只允许手动 /name 触发。$ARGUMENTS 接参数,allowed-tools 预授权命令。
- 可预期的追问:怎么测 skill 有没有被触发?用几个自然语言说法试,看模型是否读了正文;再追问「skill 与 subagent 的区别」——skill 是在当前上下文里加载一份说明书,subagent 是另起一个上下文去做事,两者可以组合。
Key points
- Only the description is resident; the body loads on invocation, so body length barely costs
- CLAUDE.md loads in full each time; moving occasional procedures to skills keeps it short
- Write the description as what it does plus how users phrase it, about a hundred words
- Side-effecting workflows get disable-model-invocation; $ARGUMENTS carries parameters
答题要点
- 只有 description 常驻,正文在被触发时才加载;正文长短几乎不影响日常成本
- CLAUDE.md 整份每次加载;偶尔用的流程挪进 skill 是让它继续变短的手段
- description 写「做什么 + 用户会怎么说」,一百来字,不泛不窄
- 副作用流程加 disable-model-invocation;$ARGUMENTS 接参数
What problem do subagents solve? Do they see the main conversation's history? When should you not use one?subagent 解决了什么问题?它看得到主会话的历史吗?什么时候不该用?
Common in ChinaCommon overseasIntermediate#subagents#contextHow to reason about it · think before answering
- The key is the problem solved: protecting the main conversation's context window, not the side benefits of parallelism or specialization. The second part is a common misconception; the third tests judgment.
- Chain: research and review tasks read a lot and keep little — thirty files for one conclusion. Done in the main session, all thirty land in the window and crowd out the actual implementation. A subagent has its own context window, reads everything, and returns only a summary; the main session pays only for the summary.
- Second part: no. A subagent starts with the system prompt, the task you delegated, CLAUDE.md, and a git status snapshot — not the main history, your earlier file reads, or previously loaded skills. That is both a limit and a strength: a reviewer without the memory of having just written the code finds more faults, which is what adversarial review in D5 relies on.
- Configuration: .claude/agents/<name>.md with tools restricting what it may use (no Edit for a reviewer) and model to pick a cheaper or stronger model; built-ins are Explore (read-only), Plan (plan mode research), and general-purpose.
- When not to: tasks needing multi-turn back-and-forth (every dispatch re-explains), phases that share heavy context, and one-line fixes where dispatch plus summary costs more than the work. Follow-up: subagent versus /compact — one keeps content out of the window, the other compresses it afterward; the former is cheaper.
分析过程 · 先想清楚再作答
- 题眼是「解决了什么问题」——答案是保护主会话的上下文窗口,而不是「并行」或「专业化」这些附带好处。第二问是常见误区,第三问考边界感。
- 推导:查资料、审代码这类任务的特征是「读很多、留很少」——读三十个文件只为一段结论。放在主会话里做,三十个文件全进窗口,真正的实现反而没地方放。subagent 拥有独立的上下文窗口,读完只把总结带回来,主会话只付总结的成本。
- 第二问:看不到。subagent 起步时只有系统提示、你派给它的任务描述、CLAUDE.md、git 状态快照;主会话的历史、你之前读过的文件、之前加载的 skill 都不在。这是限制也是优点:一个没有「刚写完这段代码」记忆的审查者更容易挑出毛病,D5 的对抗式审查就靠这个性质。
- 配置:.claude/agents/<name>.md,frontmatter 的 tools 限定它能用什么(审查者不给 Edit)、model 可以配更便宜或更强的模型;内置的 Explore 只读、Plan 用于计划模式、general-purpose 全能。
- 不该用的场景:需要多轮来回讨论的活(每次派出去都要重新交代)、几个阶段要共享大量上下文的活、一句话就能改完的活(交代 + 总结的开销大于任务本身)。可预期的追问:subagent 与 /compact 的关系——一个是不让东西进窗口,一个是进了以后压缩,前者更省。
Key points
- Solves the main window being flooded by read-heavy, keep-little tasks; a subagent has its own window and returns a summary
- It does not see the main history — only the task, CLAUDE.md, and a git snapshot — which makes its review more objective
- tools restricts permissions, model picks the model; built-ins are Explore, Plan, general-purpose
- Avoid for multi-turn discussion, heavy shared context across phases, and one-line fixes
答题要点
- 解决的是主会话上下文被「读很多留很少」的任务撑满;subagent 独立窗口,只带回总结
- 看不到主会话历史,只有任务描述、CLAUDE.md、git 快照;因此审查更客观
- tools 限定权限、model 选模型;内置 Explore / Plan / general-purpose
- 不该用:多轮讨论、多阶段共享上下文、一句话能改完的小活
D5 Automation and Scale: Headless -p Into CI, Parallel Sessions and Worktrees, Writer/Reviewer Dual Sessions, Adversarial Review, Common Failure Modes; a 20-Line Minimal Agent SDK Agent
What three things must you control when running claude -p in CI, with which flags, and why is --bare recommended?把 claude -p 放进 CI 时要控制哪三件事?具体用哪些参数?为什么推荐加 --bare?
Common in ChinaCommon overseasIntermediate#headless#ci#permissionsHow to reason about it · think before answering
- This tests awareness of unattended risk. 'Add an API key and run it' reads as no production experience; the interviewer wants the three locks — permissions, budget, reproducibility — each with its flags.
- Permissions: nobody answers 'allow?' unattended, so either allowlist tools (--allowedTools "Read,Grep" or "Bash(git diff *)", mind the space before *) or set a baseline (--permission-mode dontAsk denies anything outside the allowlist; acceptEdits permits file edits), plus --permission-prompts none to deny anything that would have prompted. -p starts in Manual on every plan, so pass the mode explicitly.
- Budget: --max-turns caps turns, --max-budget-usd caps spend; both stop with an error. Without them a looping task can drain your quota; with a Stop hook, allow enough turns or the run ends on 'max turns' rather than 'tests pass'.
- Reproducibility: --bare skips auto-discovery of hooks, skills, plugins, MCP, and CLAUDE.md so every runner behaves the same and starts faster — and it is a security measure, since a cloned repo's hooks would otherwise run silently under -p (no trust dialog). Add --no-session-persistence and keep the prompt and --append-system-prompt in version control.
- Follow-ups: authentication under --bare — it ignores subscription login, so set ANTHROPIC_API_KEY. Judging success — is_error, subtype, and total_cost_usd from --output-format json; fail the job on a non-zero exit.
分析过程 · 先想清楚再作答
- 这题考无人值守的风险意识。答「加个 API key 就能跑」会被判没上过线;面试官想听权限、预算、可复现三道锁,以及每道锁对应的参数。
- 权限:无人值守时没人回答「允许吗」,所以要么白名单放行(--allowedTools "Read,Grep" 或 "Bash(git diff *)",注意 * 前的空格),要么定基线(--permission-mode dontAsk 一律拒绝白名单外的动作;acceptEdits 允许改文件),再加 --permission-prompts none 把本来要问人的动作直接拒掉。-p 模式的起始档位是 Manual,必须显式传。
- 预算:--max-turns 限轮数、--max-budget-usd 限花费,到了就停并报错。没有它们,一个卡在循环里的任务能耗尽额度;有 Stop hook 时要给足轮数,否则会以「轮数耗尽」而不是「测试通过」结束。
- 可复现:--bare 跳过 hooks、skills、插件、MCP、CLAUDE.md 的自动发现,让每台 runner 结果一致、启动更快;同时也是安全措施——不加它,clone 下来的陌生仓库里别人写的 hook 会在 -p 下无提示地执行(无头模式没有信任对话框)。配合 --no-session-persistence 不落盘,提示词与 --append-system-prompt 进版本控制。
- 可预期的追问:--bare 之后怎么认证?它不读订阅登录,必须设 ANTHROPIC_API_KEY;再追问怎么判断成败——--output-format json 的 is_error / subtype / total_cost_usd,退出码非零脚本就 fail。
Key points
- Permissions: --allowedTools allowlist plus --permission-mode dontAsk or acceptEdits and --permission-prompts none; -p defaults to Manual
- Budget: --max-turns and --max-budget-usd stop the run; leave headroom for Stop hooks
- Reproducibility: --bare skips local auto-discovery and keeps a cloned repo's hooks from running in CI; --no-session-persistence
- --bare requires ANTHROPIC_API_KEY; judge success from is_error in the JSON and the exit code
答题要点
- 权限:--allowedTools 白名单 + --permission-mode dontAsk / acceptEdits + --permission-prompts none;-p 默认 Manual 必须显式传
- 预算:--max-turns 与 --max-budget-usd,到了就停;有 Stop hook 时给足轮数
- 可复现:--bare 跳过本机配置自动发现,也防陌生仓库的 hook 在 CI 上跑;--no-session-persistence
- --bare 需要 ANTHROPIC_API_KEY;成败看 --output-format json 的 is_error 与退出码
Why is a Writer / Reviewer two-session review more effective than self-review in one session, and should you fix everything the reviewer reports?为什么 Writer / Reviewer 双会话的审查比同一个会话自查更有效?审查者报出来的问题要全改吗?
Common in ChinaCommon overseasIntermediate#review#subagentsHow to reason about it · think before answering
- The point is the why and the second half. 'A second pair of eyes' is common sense; explain the role of context and the side effect of review.
- Chain: the session that wrote the code has a context full of its own reasoning; asked to review, it tends to confirm rather than challenge — a context bias, not an attitude problem. A Reviewer in a fresh context sees only the diff and your criteria, not the Writer's reasons, so it critiques the code itself. Same principle as non-author code review among humans.
- Three shapes: two terminals passing output by hand; a subagent doing adversarial review (its isolated context is the memoryless reviewer, and findings land back in the main session for immediate fixing); the built-in /code-review that reviews the current diff in a fresh subagent. The idea also inverts: one session writes tests, another writes the implementation to pass them.
- The second half separates candidates: don't fix everything. A reviewer told to find gaps will report some even in sound code; accepting all of it leads to over-engineering — extra abstraction, defensive code for impossible cases, tests for unreachable paths. Tell it to flag only gaps affecting correctness or stated requirements, and let a human decide.
- Follow-ups: what does the Reviewer need? The diff, the plan or requirements, explicit criteria; feeding it the Writer's reasoning weakens independence. Can it be automated? Yes — run the Reviewer via -p and post results to the PR.
分析过程 · 先想清楚再作答
- 题眼是「为什么」和后半句。答「多一双眼睛」是常识;要说清上下文在这里扮演的角色,以及审查的副作用。
- 推导:写完实现的会话,上下文里装满了「我为什么这么写」的推理;让它自审,它倾向于确认而不是质疑——这不是态度问题,是上下文偏置。Reviewer 换一个全新的上下文,只看到 diff 和你给的标准,不知道 Writer 的理由,所以挑的是代码本身的毛病。这和人类 code review 要求「非作者审」是同一个道理。
- 形态有三种:两个终端手动传递输出;一个 subagent 做对抗式审查(独立上下文天然就是无记忆的审查者,而且结果直接回到主会话可以立刻修);内置的 /code-review 在新 subagent 里审当前 diff。同样的思路可以反过来用:一个会话写测试,另一个写实现去通过。
- 后半句是区分度:不要全改。被要求找问题的审查者一定会报出问题来,哪怕代码没毛病;照单全收会导致过度工程——多余抽象、防御不存在情况的代码、测不可能发生的用例。审查提示词里要写「只报告影响正确性或明确需求的差距,其余视为可选」,最终由人判断。
- 可预期的追问:Reviewer 需要什么输入?diff、计划或需求(PLAN.md)、明确的判据;给它 Writer 的推理过程反而会削弱独立性。再追问「能不能自动化」——能,-p 模式里一条命令跑 Reviewer,结果贴回 PR。
Key points
- Self-review suffers context bias: a session full of its own reasoning confirms rather than challenges
- A Reviewer in a fresh context sees only the diff and criteria, so it critiques the code itself
- Shapes: two terminals, an adversarial subagent, built-in /code-review; invert for test-first
- Don't fix everything: reviewers always report something; limit findings to correctness and stated requirements
答题要点
- 自审受上下文偏置:装满自己推理的会话倾向于确认而非质疑
- Reviewer 用全新上下文,只看 diff 与判据,挑的是代码本身的毛病
- 形态:双终端、subagent 对抗式审查、内置 /code-review;反向可用于测试先行
- 不要全改:审查者必报问题,照单全收导致过度工程;限定只报影响正确性的差距
When do you use the Claude Agent SDK versus the Messages API directly, and how do both relate to claude -p?Agent SDK 和直接调 Messages API 各适合什么场景?它们和 claude -p 是什么关系?
Common in ChinaCommon overseasBasic#agent-sdk#messages-apiHow to reason about it · think before answering
- This tests layered understanding: all three entry points share one model; the difference is who supplies the loop and the tools. 'The SDK is higher level' says nothing.
- Messages API (@anthropic-ai/sdk / anthropic): one request, one response; you define tools, write the loop, manage context. Fits Q&A, extraction, classification, structured output, cited document Q&A, and custom agents where you want full control of the loop.
- Agent SDK (@anthropic-ai/claude-agent-sdk / claude-agent-sdk): Claude Code packaged as a library — built-in Read/Edit/Bash/Glob/Grep, the full agent loop, context management, permissions, hooks, subagents, sessions. You pass a task and options (allowedTools, permissionMode, maxTurns, systemPrompt) and it works in the filesystem. Fits embedding a code-editing agent in your own program.
- claude -p: the CLI form of the same Claude Code capabilities, for shell scripts and CI; the Agent SDK is its library form and the docs present them together. One-line rule: model call → API; filesystem agent → Agent SDK; quick scripted call → -p.
- Production nuance: with the Agent SDK you still own deployment (it supplies the harness, not hosting); auth is ANTHROPIC_API_KEY, and claude.ai subscription login can't be offered to third-party products. Follow-up: is the Agent SDK the same as the Messages API tool runner? No — the tool runner loops over tools you define and has no built-in file tools.
分析过程 · 先想清楚再作答
- 这题考的是分层认知:三个入口底下是同一个模型,差别在于「谁提供循环和工具」。答成「SDK 更高级」没有信息量。
- Messages API(@anthropic-ai/sdk / anthropic):一次请求一次响应,工具由你定义、循环由你写、上下文由你管。适合问答、抽取、分类、结构化输出、带引用的文档问答,以及你想完全掌控循环的自定义 Agent。
- Agent SDK(@anthropic-ai/claude-agent-sdk / claude-agent-sdk):把 Claude Code 打包成库——内置 Read / Edit / Bash / Glob / Grep 等工具、完整的 agent 循环、上下文管理、权限系统、hooks、subagent、会话。你给一句任务和一组选项(allowedTools、permissionMode、maxTurns、systemPrompt),它在文件系统里干活。适合「在自己的程序里嵌一个会改代码的 agent」。
- claude -p:同一套 Claude Code 能力的命令行形态,适合 shell 脚本与 CI;Agent SDK 就是它的库形态,官方文档把两者放在同一页讲。判据一句话:要模型调用用 API,要文件系统里的 agent 用 Agent SDK,只想在脚本里调一下用 -p。
- 生产视角:Agent SDK 的部署仍是你自己的(它只提供循环,不提供托管),密钥走 ANTHROPIC_API_KEY,不能复用 claude.ai 的订阅登录给第三方产品。可预期的追问:Agent SDK 和 Messages API 里的 tool runner 是不是一回事?不是——tool runner 只帮你跑「你自己定义的工具」的循环,没有内置文件工具。
Key points
- Messages API: request/response, you write tools and the loop; for Q&A, extraction, structured output, custom agents
- Agent SDK: Claude Code as a library with built-in file/Bash tools, loop, permissions, hooks; for embedding a code-editing agent
- claude -p is the CLI form of the same capabilities, for scripts and CI
- You still own deployment; auth via ANTHROPIC_API_KEY; the tool runner is not the Agent SDK
答题要点
- Messages API:一问一答,工具与循环自己写;适合问答、抽取、结构化输出、自定义 Agent
- Agent SDK:Claude Code 的库形态,内置文件与 Bash 工具、循环、权限、hooks;适合嵌入会改代码的 agent
- claude -p 是同一能力的命令行形态,适合脚本与 CI
- 部署仍归自己,认证用 ANTHROPIC_API_KEY;tool runner 不是 Agent SDK
Mastering Codex and the OpenAI Agents SDK in 5 Days
D1 Getting Started With the Codex CLI: Install, AGENTS.md, Approval Modes and the Sandbox, Common Commands
What belongs in a project instruction file for a coding agent (such as Codex's AGENTS.md), what does not, and why is there a size limit?给 coding agent 写的项目说明文件(比如 Codex 的 AGENTS.md)应该写什么、不该写什么?为什么它要有大小上限?
Common in ChinaCommon overseasBasic#coding-agent#context#agents-mdHow to reason about it · think before answering
- This probes whether you treat context as a scarce resource, not whether you know the file format; answering with a project overview signals inexperience.
- Use one test: can the agent discover this by opening files? If yes, leave it out (directory layout, framework); if no, write it down (conventions, no-go areas, environment facts, test commands).
- Add the lookup rules: a global file in the home directory, then project files concatenated from the repo root down to the current directory, so closer files override earlier ones.
- The size cap (32 KiB by default in Codex) forces prioritization: a long manual crowds out the task and dilutes adherence to every rule.
- Expect the follow-up: will the model always obey the file? No, it is prompt text and fades over long sessions; hard limits belong to the sandbox and approvals.
分析过程 · 先想清楚再作答
- 这题考的不是文件格式,而是你对「上下文是有限资源」有没有工程直觉。把它答成「写项目介绍」会被判为没真用过。
- 拆法是一个判断句:这条信息 agent 打开文件自己能不能发现?能发现的不写(目录结构、用了什么框架),发现不了的才写(约定、禁区、环境事实、测试命令)。
- 再补一层查找规则:全局层在用户目录,项目层从根目录到当前目录依次拼接,越靠近当前目录越靠后、越优先,所以子目录可以覆盖根规则。
- 大小上限(Codex 默认 32 KiB)的意义是逼你做取舍:手册太长会挤占任务本身的上下文,还会让模型对每一条规则的遵守度下降。
- 可预期的追问:写在说明文件里的规则模型一定会遵守吗?不一定,它是提示词的一部分,会被长对话稀释;硬约束要靠沙箱与审批,不是靠文字。
Key points
- Write conventions, no-go areas, environment facts and verification commands; skip anything discoverable from the files
- Lookup goes global first, then project files concatenated root-down, with closer files taking precedence
- The size cap forces you to keep only high-value guidance so the task itself keeps its context budget
- Instruction files are advisory; hard limits come from the sandbox and approval policy
答题要点
- 写约定、禁区、环境事实和验证命令;不写 agent 自己打开文件就能发现的内容
- 查找顺序是全局文件在前、项目文件从根到当前目录拼接,越靠近当前目录越优先
- 大小上限逼你只保留高价值信息,避免挤占任务上下文、降低规则遵守度
- 文字规则是建议性的,真正不能越的线交给沙箱与审批
Codex splits 'when to ask the user' and 'what can be touched' into two independent settings, approval_policy and sandbox_mode. Why separate them, and what does each solve?Codex 把「什么时候问用户」和「能碰到什么」拆成 approval_policy 和 sandbox_mode 两组独立开关。为什么要拆?各自解决什么问题?
Common in ChinaCommon overseasIntermediate#coding-agent#security#sandboxHow to reason about it · think before answering
- The discriminating part is 'why separate'; reciting the values without explaining orthogonality earns little.
- Define both: approval policy is process control, whether a human must nod before an action; sandbox is permission control, whether the OS allows the action at all.
- Then justify orthogonality with combinations a single slider cannot express: 'do not interrupt me but never leave the workspace' versus 'ask every time but read-only'.
- Ground it in implementation: the sandbox uses OS mechanisms (Seatbelt on macOS, bubblewrap on Linux) rather than model goodwill, so it is a hard limit, while approval is the one human checkpoint.
- Expect the follow-up: why is network off by default? Because network is the channel for code leaving or entering the machine, a different risk class from local edits.
分析过程 · 先想清楚再作答
- 题眼是「为什么拆」。只背出每组的取值等于没答,面试官要听的是两者正交带来的好处。
- 先给定义:审批策略是流程控制,决定动作执行前要不要人点头;沙箱是权限控制,决定即使模型想做、操作系统允不允许。
- 再说为什么正交:你可能想要「不打扰我,但绝不许出工作区」(on-request 加 workspace-write),也可能想要「每步都问,但只让它读」(untrusted 加 read-only);合成一个滑杆就表达不了这两种组合。
- 落到实现:沙箱靠操作系统机制(macOS Seatbelt、Linux bubblewrap),不是靠模型自觉,所以它是硬约束;审批则是唯一由人把关的环节。
- 可预期的追问:为什么网络默认关?因为联网是把内部代码送出去或把外部代码拉进来的通道,风险等级和改本地文件不同,需要单独授权。
Key points
- approval_policy governs process: untrusted / on-request / on-failure / never decide whether a human confirms first
- sandbox_mode governs permission: read-only / workspace-write / danger-full-access decide what the OS allows
- Orthogonality lets you express 'no interruptions but stay in the workspace' and 'ask each step but read-only'
- The sandbox is an OS-level hard limit, approval is the human checkpoint, and network is off by default
答题要点
- approval_policy 管流程:untrusted / on-request / on-failure / never 决定动作前是否要人确认
- sandbox_mode 管权限:read-only / workspace-write / danger-full-access 决定操作系统放行什么
- 两者正交才能表达「不打扰但不越界」和「步步问但只读」这类组合
- 沙箱是操作系统级硬约束,审批是唯一的人工把关点;网络默认关闭需单独放开
You want to introduce a coding agent that runs commands locally. How do you explain its risk boundary to skeptical teammates?你要在团队里引入一个能在本地执行命令的 coding agent,怎么向不放心的同事解释它的风险边界?
Common in ChinaCommon overseasIntermediate#coding-agent#security#communicationHow to reason about it · think before answering
- This tests communication as much as engineering: state the technical boundary in terms the listener can verify, not just 'it is safe'.
- Present three layers of defense: written rules (AGENTS.md) shape habits; the sandbox limits capability to read-only or workspace-only writes with network off; approvals gate every exception.
- Offer verifiable guarantees: every change lands in the git working tree, visible via diff and revertable via checkout; unattended runs stay on throwaway branches or containers.
- Name the residual risk yourself: the model can misread a requirement and produce wrong but passing code, so review and tests remain mandatory, and secrets stay out of readable files.
- Expect the follow-up: can network be fully blocked? Yes, the sandbox is offline by default; approve installs case by case or configure an allow-list of domains.
分析过程 · 先想清楚再作答
- 这题考的是沟通加工程两层:既要说清技术上的边界,又要用对方能验证的方式说,不能只说「它很安全」。
- 拆成三层防线来讲:第一层文字规则(AGENTS.md)管习惯;第二层沙箱管能力,只读或只能写工作区、网络默认关;第三层审批管例外,越界的每一步都要人批。
- 给出可验证的承诺:所有改动都在 git 工作区里,`git diff` 能看、`git checkout` 能撤;脱手运行只跑在一次性分支或容器里。
- 主动说出剩余风险:模型可能误读需求写出错误但能通过的代码,所以审查和测试不能省;密钥不要放在它能读到的文件里。
- 可预期的追问:能不能完全禁止它联网?可以,沙箱默认就不通网,需要装依赖时逐次批准,或在配置里给一个允许的域名清单。
Key points
- Three layers: written rules for habits, the sandbox for capability, approvals for exceptions
- All edits live in the git working tree and are diffable and revertable; unattended runs use throwaway branches or containers
- State residual risks yourself: wrong-but-passing code and secret exposure, hence mandatory review and tests
- Network is off by default; approve per request or configure an allow-list
答题要点
- 三层防线:文字规则管习惯、沙箱管能力、审批管例外
- 改动全在 git 工作区,可 diff 可撤销;脱手运行只在一次性分支或容器
- 主动说明剩余风险:错误但能通过的代码、密钥暴露,所以审查与测试不能省
- 网络默认关闭,联网按次批准或配置允许域名清单
D2 Codex, Level Up: Cloud Tasks, Code Review, MCP Integration, Custom Instructions, IDE Integration
A cloud coding agent can run many tasks in parallel with nobody around to approve steps. Where should its approval boundary sit?云端 coding agent 能同时跑很多任务,但没有人在旁边点头。它的审批边界应该画在哪里?
Common in ChinaCommon overseasIntermediate#coding-agent#cloud#approvalsHow to reason about it · think before answering
- This checks whether you noticed the approval model changed: local means step-by-step approval, cloud means authorize upfront and review afterwards.
- Split the boundary across three moments: before the task (environment config decides network, variables, dependencies), during (container isolation), after (a human reviews the diff before any PR).
- Conclude that the cloud boundary is two gates, environment config plus pre-PR human review, with nobody in between; hence no production secrets, network off by default, merge rights stay human.
- Add the engineering angle: draw boundaries between parallel tasks too; tasks that touch the same files should not run concurrently.
- Expect the follow-up: can it auto-merge? Only in low-risk repos for fully green PRs, with rollback in place, and treat enabling auto-merge as a change that itself needs approval.
分析过程 · 先想清楚再作答
- 这题考的是你有没有意识到「审批模型变了」:本地是逐步审批,云端只能事先授权、事后审阅。答成「跟本地一样弹窗」说明没用过。
- 拆法是把边界分成三个时间点:任务开始前(环境配置决定能联网什么、有哪些变量、装什么依赖)、任务执行中(容器隔离,改动只在容器里)、任务结束后(人审 diff 再决定开不开 PR)。
- 结论是:云端的审批边界就是「环境配置 + PR 前人工审阅」这两道门,中间不再有人;所以生产密钥不能进环境、公网默认关、合并权限保留在人手里。
- 补一条工程视角:并行任务之间的边界也要画——互相会改同一批文件的任务不要同时派,否则合并成本吃掉并行收益。
- 可预期的追问:能不能让它自动合并?可以在低风险仓库对通过全部测试的 PR 这么做,但要保留回滚手段,并且把「自动合并」本身当成一个需要审批的配置变更。
Key points
- No step-wise approval in the cloud; the boundary becomes upfront environment config plus post-hoc human review
- Keep production secrets out, network off by default, merge rights with humans
- Draw boundaries between parallel tasks: never run file-overlapping tasks concurrently
- Auto-merge only for low-risk repos with fully green PRs, with rollback ready
答题要点
- 云端没有逐步审批,边界变成事前的环境配置与事后的人工审阅两道门
- 环境里不放生产密钥、公网默认关、合并权限保留给人
- 并行任务之间也要画边界:会改同一批文件的任务不同时派
- 自动合并只适用于低风险仓库且全绿的 PR,并保留回滚
If the same model both writes and reviews code, is the review still meaningful? How do you make it more independent?让同一个模型既写代码又审代码,审查还有意义吗?怎么让审查更独立?
Common in ChinaCommon overseasIntermediate#code-review#coding-agent#workflowHow to reason about it · think before answering
- The crux is 'still meaningful'; a flat yes or no fails. Explain what it catches and what it misses.
- What it catches: the input changes (diff instead of requirements) and the stance changes (find faults instead of finish the job), which surfaces missed edge cases, unsynced callers and style violations.
- What it misses: reviewer and author share one understanding of the requirement, so a misread requirement passes; they share blind spots too.
- Conclude with three independence levers: review with a different vendor's model, feed the reviewer different information (original requirement plus acceptance criteria, not just the diff), and run deterministic checks first.
- Expect the follow-up: auto-apply review comments? No; review is input, not verdict, and both false positives and misses exist.
分析过程 · 先想清楚再作答
- 题眼在「还有意义吗」——直接答「没意义」或「有意义」都不及格,要说清它能抓什么、抓不到什么。
- 先说能抓的:审查时输入变了(看 diff 而不是需求)、立场变了(找问题而不是完成任务),这种角色切换能抓出漏掉的边界情况、没同步的调用方、明显的风格违规。
- 再说抓不到的:审查者和生成者共享同一份对需求的理解,需求理解错了两边一起错;也共享同样的盲区与偏好。
- 结论给三条提高独立性的手段:换一家模型审、给审查者不同的信息(需求原文加验收标准而不是只给 diff)、用确定性工具(测试、lint、类型检查)做第一道审查。
- 可预期的追问:审查意见要不要自动应用?不要,审查是输入不是判决,误报与漏报都存在,最终判断留给人。
Key points
- Yes: the switch of input and stance catches edge cases, unsynced callers and style issues
- It misses requirement misreads because author and reviewer share one understanding
- Increase independence: a different vendor's model, richer reviewer context, deterministic checks first
- Treat comments as input, never auto-apply
答题要点
- 有意义:输入与立场的切换能抓出边界情况、未同步的调用方、风格违规
- 抓不到与需求理解相关的错误,因为审查者与生成者共享同一份理解
- 提高独立性:换一家模型审、给审查者需求原文与验收标准、先跑确定性检查
- 审查意见是输入不是判决,不要自动应用
MCP servers and skills both extend a coding agent. When do you reach for each, and what goes in the project instruction file instead?MCP server 和 skill 都是在给 coding agent 加能力,什么时候该用哪一个?项目说明文件又放什么?
Common in ChinaCommon overseasBasic#mcp#skills#coding-agentHow to reason about it · think before answering
- This tests separation of abstraction levels, the tooling-side version of the increasingly common 'function calling vs MCP vs skills' question.
- Ask what is being added: access to an external system (tickets, databases, internal services) is MCP, a protocol-level tool; a multi-step procedure (release checklist, migration flow) is a skill, a prompt-level workflow package; conventions to obey every session belong in the instruction file.
- Contrast triggers: MCP tools are invoked by the model when it needs data; skills are invoked explicitly by name or matched by description; instruction files are loaded unconditionally at session start.
- Conclude: rules in the instruction file, external systems via MCP, procedures as skills; keep each fact in one place to avoid contradictions.
- Expect the follow-up: can a skill use MCP tools? Yes; a skill's steps can call for a tool, the layers are orthogonal, not substitutes.
分析过程 · 先想清楚再作答
- 这题考的是抽象层次的区分,是国内面试开始高频出现的「Function Call / MCP / Skills 三者区别」的工具侧版本。
- 拆法是问「加的是什么」:加的是访问外部系统的能力(查工单、读数据库、调内部服务)就是 MCP,它是协议层的工具;加的是一套多步骤的做法(发版检查、迁移流程)就是 skill,它是提示词层的流程包;每次会话都要遵守的约定就是项目说明文件。
- 再给触发方式的差别:MCP 工具由模型在需要数据时调用;skill 由用户显式点名或由模型按描述匹配;说明文件每次会话开头无条件读入。
- 结论落到一句话:规矩归说明文件、外部系统归 MCP、流程归 skill;同一件事只放一处,避免三处互相矛盾。
- 可预期的追问:skill 里能不能调 MCP 工具?可以,skill 的步骤里可以要求使用某个工具,两者是正交的层次,不是替代关系。
Key points
- MCP adds tools that reach external systems, invoked by the model on demand
- Skills add multi-step procedures, triggered by name or matched by description
- The instruction file holds conventions, no-go areas and environment facts read every session
- The three are orthogonal: rules, external systems, procedures each live in one place; a skill may call for an MCP tool
答题要点
- MCP 加的是访问外部系统的工具,由模型按需调用
- skill 加的是多步骤流程,由用户点名或按描述匹配触发
- 项目说明文件放每次会话都要遵守的约定、禁区与环境事实
- 三者正交:规矩、外部系统、流程各放一处,skill 里可以要求用某个 MCP 工具
D3 The Responses API and Built-in Tools: Function Calling, Web Search / File Search / Computer Use, Structured Output
How does the Responses API differ from Chat Completions, and what are the common pitfalls when migrating?Responses API 和 Chat Completions 的区别是什么?从 Chat Completions 迁移过去最容易踩什么坑?
Common in ChinaCommon overseasBasic#responses-api#openai#migrationHow to reason about it · think before answering
- This tests whether you have actually migrated code, not whether you can recite field names.
- Split into three axes: input shape (messages array becomes input plus top-level instructions), output shape (choices becomes typed output items with an output_text helper), and state (stateless becomes store by default plus previous_response_id).
- Explain the motivation: a chat-transcript model cannot hold tool actions; items give search, function calls and their outputs distinct types, which is what makes built-in tools possible.
- Name three pitfalls: store defaults to true so compliance-sensitive apps must disable it; output is an array, so read output_text or walk message items; tool results move from role tool messages to function_call_output items keyed by call_id.
- Expect the follow-up: previous_response_id versus self-managed history? Prototypes take the former; production usually keeps its own history for audit and recovery, or mixes both.
分析过程 · 先想清楚再作答
- 这题考的是你有没有真迁移过,而不是能不能背出字段名。只答「新接口更强」会被判为看过文档没写过代码。
- 拆成三个维度:输入形态(messages 数组变成 input 加顶层 instructions)、输出形态(choices 变成按类型排列的 output items,SDK 给 output_text 助手)、状态管理(无状态变成默认 store 加 previous_response_id)。
- 再说为什么要改:聊天记录模型装不下工具动作;items 让搜索、函数调用、回填各有自己的类型,这是内置工具能接进来的前提。
- 迁移坑给三条:默认 store 为 true 意味着数据会被存下来,合规场景要显式关掉;output 是数组不是单个消息,取文本要用 output_text 或遍历 message item;函数调用的回填从 role 为 tool 的消息变成 function_call_output item,call_id 要对上。
- 可预期的追问:previous_response_id 和自己维护历史怎么选?原型用前者省事,生产多半自己落一份历史做审计与恢复,或两者混用。
Key points
- Input: messages become input plus top-level instructions; output: choices become typed output items plus output_text
- State: store defaults to true and previous_response_id chains turns without resending history
- The motivation is distinct item types for tool actions, enabling built-in tools
- Pitfalls: store on by default, output is an array, tool results go back as function_call_output keyed by call_id
答题要点
- 输入:messages 变 input 加顶层 instructions;输出:choices 变按类型排列的 output items 与 output_text
- 状态:默认 store 为 true,用 previous_response_id 接上一轮,不再每轮重发历史
- 改的动机是给工具动作独立的 item 类型,内置工具由此接入
- 迁移坑:store 默认开、output 是数组、回填要用 function_call_output 且 call_id 对上
When do you use platform built-in tools (web search, file search, computer use) versus your own function tools, and why does computer use deserve special treatment?平台内置的工具(web search、file search、computer use)和自己写的函数工具,各适合什么场景?为什么 computer use 要单独对待?
Common in ChinaCommon overseasIntermediate#tools#responses-api#securityHow to reason about it · think before answering
- Two cruxes: who executes the tool, and how large its side effects are; comparing features alone signals no production experience.
- Executor test: built-in tools run server-side, you declare but never fill results and cannot steer the search; function tools run in your code, more work but full control.
- Map to scenarios: external, generic data (the web, your uploaded documents) fits built-ins; data inside your systems (databases, internal services, business logic) needs functions; production mixes both.
- Order by side effects: web search reads the public web, file search reads your files, function calls have whatever side effects your code allows, computer use lets the model act directly; more capability demands heavier isolation.
- Computer use is special because it can click anything, type anything and be steered by on-screen content, so the starting point is an isolated environment, a restricted account and an allow-list, not code.
- Expect the follow-up: built-in file search versus your own RAG? The built-in is a managed pipeline that skips chunking, embedding and retrieval work at the cost of control and observability; build your own when you need custom chunking or reranking.
分析过程 · 先想清楚再作答
- 题眼有两个:一是「谁来执行」,二是「副作用有多大」。只答功能对比不谈执行方与风险,就是没做过工程。
- 先给执行方的判据:内置工具由平台在服务端执行,你只声明、不回填、也控制不了它怎么搜;函数工具由你执行,样样自己写,但每一步都在你手里。
- 落到场景:数据在外面且通用(公网、你上传的文档)用内置工具;数据在你系统里(数据库、内部服务、业务逻辑)写函数;生产系统几乎总是混用。
- 再按副作用排一条光谱:web search 只读公网,file search 只读你给的文件,函数调用的副作用由你的代码决定,computer use 由模型直接产生副作用——越往右能力越强,需要的隔离越重。
- computer use 单独对待的原因:它能点任何按钮、输任何文字,还可能被页面内容诱导,所以正确起点是隔离环境、受限账号和站点与动作白名单,不是代码。
- 可预期的追问:内置的 file search 和自己搭 RAG 怎么选?前者是托管版,省掉切分、向量化、检索三步,代价是可控性与可观测性弱,需要自定义切分或重排时才自己搭。
Key points
- Built-ins run on the platform with no result filling and no steering; functions run in your code with full control
- External generic data suits built-ins, in-system data and business logic need functions, production mixes both
- Rank by side effects: web search, file search, function calls, computer use; more power needs more isolation
- Computer use starts with an isolated environment and an allow-list, not with code
答题要点
- 内置工具由平台执行、不用回填、不可干预;函数工具由你执行、全部可控
- 外部通用数据用内置工具,系统内数据与业务逻辑写函数,生产混用
- 按副作用排序:web search、file search、函数调用、computer use,能力越强隔离越重
- computer use 的起点是隔离环境与白名单,不是代码
What does strict mode in structured outputs solve, what does it not solve, and what must your code still do after receiving the output?结构化输出的 strict 模式解决了什么问题,没解决什么问题?拿到输出之后代码里还要做什么?
Common in ChinaCommon overseasIntermediate#structured-output#responses-api#validationHow to reason about it · think before answering
- This tests the distinction between well-formed and correct; claiming strict mode removes the need for validation is the classic mistake.
- What it solves: strict plus json_schema guarantees the output validates against the schema, so enums are always allowed values, required fields exist and types are right; parsing-layer try/catch and retries can largely go.
- What it does not solve: semantics. The verdict is one of two enums but may be wrong; a number is a number but may be invented. Business validation stays.
- Then refusals: a safety refusal comes back as a refusal content block, not malformed JSON; unhandled, downstream code crashes on an empty parse, handled, you can separate unwilling from incorrect.
- Give the order in code: check refusal, read output_parsed, run business checks (ranges, referenced entities exist, consistency with context), then persist or act.
- Expect the follow-up: schema restrictions under strict? Every object needs additionalProperties false and all fields in required, optional fields become nullable; these constraints are exactly what makes the guarantee possible.
分析过程 · 先想清楚再作答
- 这题考的是对「格式正确」与「内容正确」的区分,答成「有了 strict 就不用校验了」是典型的错误。
- 先说解决了什么:strict 加 json_schema 保证输出一定能通过 schema 校验——枚举只会是给定值、必填字段一定在、类型不会错,解析层的 try/catch 与重试基本可以删掉。
- 再说没解决什么:schema 管不了语义。verdict 一定是两个枚举之一,但判断可能是错的;数字一定是数字,但可能是编的。业务层校验一行不能省。
- 然后是拒答分支:模型因安全原因拒绝时返回 refusal 类型的内容块,而不是硬塞一个不合法的 JSON;不处理它,下游会拿到空的解析结果直接崩,处理了才能区分「不愿意」与「没做对」。
- 给出代码里的顺序:先查 refusal,再读 output_parsed,再做业务校验(范围、引用是否存在、与上下文是否一致),最后才落库或执行。
- 可预期的追问:strict 对 schema 有什么限制?每个对象都要 additionalProperties 为 false、字段都要在 required 里,可选字段用可空类型表达;这些限制正是它能给出保证的原因。
Key points
- Solves: output is guaranteed to match the schema, so parsing defenses can go
- Does not solve: semantic correctness, so business validation stays
- Check refusal first, then output_parsed, then business checks, then persist
- Strict requires additionalProperties false and all fields required, optional fields become nullable
答题要点
- 解决:输出保证符合 schema,解析层防御代码可以删
- 没解决:语义正确性,业务校验一行不能省
- 先查 refusal 再读 output_parsed,再做业务校验,最后落库
- strict 要求 additionalProperties 为 false、字段全在 required 里,可选用可空类型表达
D4 The OpenAI Agents SDK: Agents, Handoffs, Guardrails, Sessions, Tracing
When should you split one agent into several connected by handoffs, and when is a single agent with many tools the better design?什么时候该把一个 Agent 拆成多个、用 handoff 交接?什么时候「一个大 Agent 加很多工具」反而更好?
Common in ChinaCommon overseasIntermediate#agents-sdk#handoffs#architectureHow to reason about it · think before answering
- This tests your splitting criterion, not API fluency; 'split when there are many tools' is the common wrong answer.
- First separate handoffs from tools: a tool call fetches an answer and returns; a handoff transfers the whole conversation so the receiving agent owns it, even though it is implemented as a transfer_to_xxx tool.
- The criterion is whether instructions conflict: when two task groups need independent, clashing background, constraints and tone, one instruction block forces constant context switching, longer prompts and more errors, so split; many tools sharing one background do not justify a split.
- Name the costs: an extra model call for triage, possible misrouting, input guardrails only on the first agent, and history trimming across agents via inputFilter.
- Expect the follow-up: what if triage misroutes? Use RECOMMENDED_PROMPT_PREFIX, assert on lastAgent in regression tests, inspect the handoff turn in tracing, and allow experts to hand back.
分析过程 · 先想清楚再作答
- 这题考的是拆分判据,不是会不会用 API。答「工具多了就拆」是最常见的错误,工具数量不是判据。
- 先说清 handoff 与工具的区别:调工具是替你去问一句再回来,handoff 是把对话整个交给另一个 Agent,之后由它负责;实现上 handoff 也是一个名为 transfer_to_xxx 的工具,但语义是转移控制权。
- 判据是「指令会不会互相打架」:两组任务需要的背景知识、约束、语气彼此独立且冲突时,塞进一份 instructions 会让模型反复切换上下文、提示越长越贵、出错率上升,这时拆;工具虽多但共享同一套背景的,不拆。
- 补拆分的代价:多一次模型调用(分诊那一跳)、路由可能错、输入护栏只在第一个 Agent 上跑、跨 Agent 的历史要靠 inputFilter 裁剪。
- 可预期的追问:分诊错了怎么办?用 RECOMMENDED_PROMPT_PREFIX 提高交接准确率,用 lastAgent 做回归断言,用 tracing 看交接发生在哪一轮,必要时让专家 Agent 也能交接回分诊台。
Key points
- A handoff transfers conversational control; a tool call only fetches a result
- Split on conflicting instructions, not on tool count
- Costs: an extra hop, possible misrouting, input guardrails only on the first agent
- Control routing quality with the recommended prefix, lastAgent assertions and tracing
答题要点
- handoff 转移的是对话控制权,工具调用只是取一次结果
- 拆分判据是指令是否互相打架,不是工具数量
- 拆的代价:多一跳、可能路由错、输入护栏只在第一个 Agent 生效
- 用前缀提示、lastAgent 断言与 tracing 控制路由质量
Should guardrails sit on the input side or the output side? What does each cost, what does it catch, and what slips through?guardrail 应该放在输入侧还是输出侧?各自的成本、能拦住什么、拦不住什么?
Common in ChinaCommon overseasDeep dive#agents-sdk#guardrails#safetyHow to reason about it · think before answering
- The crux is 'what slips through'; saying 'use both' without naming each side's blind spot signals no production incidents survived.
- Division of labor: input guardrails decide whether to act at all (off-topic, obvious injection, out of scope) and are cheapest early; output guardrails decide whether the answer may be said (leaks, format, policy) and can only run after generation.
- Cost: input guardrails run in parallel with the main agent and cancel its expensive run on a tripwire, so a cheap classifier there saves money; output guardrails wait for the full run and only prevent incidents.
- Blind spots: input cannot catch a normal question with a drifting answer; output cannot undo a side-effecting tool already called, hence a third layer of tool-level guardrails around each function call.
- Add the SDK constraint: input guardrails run only on the first agent, output guardrails only on the agent producing the final answer; misplaced guardrails never execute.
- Expect the follow-up: the common failure mode? Too strict, not too loose; regex blocklists over-block real users, so keep a regression set of legitimate requests and watch the false-block rate.
分析过程 · 先想清楚再作答
- 题眼在「拦不住什么」。只说两边都要放而不说各自的漏网情况,就是没在生产里被漏网案例打过脸。
- 先给分工:输入侧管「该不该做」——话题越界、明显注入、超出服务范围,越早拦越省;输出侧管「能不能说」——泄露敏感信息、格式不合规、违反业务规则,只有模型说完才能查。
- 再说成本:输入护栏与主 Agent 并行跑,警报一响就取消主 Agent 的昂贵运行,所以用便宜小模型做输入护栏是省钱手段;输出护栏必须等主 Agent 跑完,省不了钱,只能防事故。
- 漏网情况:输入侧拦不住「问题正常但回答跑偏」;输出侧拦不住「模型已经调了有副作用的工具」——所以有副作用的工具需要第三层,围着每次函数调用跑的工具级护栏。
- 补一条 SDK 约束:输入护栏只在链条第一个 Agent 上跑,输出护栏只在产出最终回答的 Agent 上跑,挂错位置等于没挂。
- 可预期的追问:护栏最常见的失败模式是什么?太严而不是太松——正则黑名单误拦正常用户;上线前要有正常请求的回归集,误拦率是必看指标。
Key points
- Input side decides whether to act and is cheapest early; output side decides what may be said and only runs afterwards
- Input guardrails run in parallel and cancel the main run, so cheap models save money there; output guardrails only prevent incidents
- Input misses drifting answers, output misses side effects already taken; tool-level guardrails add the third layer
- Input guardrails run only on the first agent; the common failure is over-blocking, so keep a regression set
答题要点
- 输入侧管该不该做,越早拦越省;输出侧管能不能说,只能事后查
- 输入护栏与主 Agent 并行、触发即取消,便宜模型在此省钱;输出护栏省不了钱只防事故
- 输入侧漏「回答跑偏」,输出侧漏「已调有副作用的工具」,需工具级护栏补第三层
- 输入护栏只在第一个 Agent 生效;常见失败是太严,需正常请求回归集
Both Agents SDK sessions and the Responses API's previous_response_id remember multi-turn state. How do you choose, and what role does tracing play?Agents SDK 的 session 和 Responses API 的 previous_response_id 都能记住多轮,怎么选?tracing 在这里起什么作用?
Common in ChinaCommon overseasIntermediate#agents-sdk#sessions#tracingHow to reason about it · think before answering
- This probes your sensitivity to who holds the state, the SDK-level echo of 'you carry the history yourself'.
- Ask three questions: can the history be audited, trimmed or replayed, and kept within data-residency rules? previous_response_id keeps history server-side with minimal requests but answers all three poorly; sessions keep it in your store and answer all three, at the cost of managing storage.
- Conclude: prototypes and internal tools take previous_response_id; user-facing production keeps its own copy, for which sessions are the ready-made path; both can coexist.
- Of the four session operations, pop_item deserves mention: removing the last turn to honor a user's undo is only possible when you own the history.
- Tracing makes multi-agent behavior explainable: on by default, one trace per run recording turns, tool calls, handoffs and guardrail results; group a conversation with withTrace or group_id; disable via env var or swap in your own exporter for sensitive data.
- Expect the follow-up: does tracing ship user data out? By default it goes to the platform dashboard, so regulated settings must disable it or replace the processors.
分析过程 · 先想清楚再作答
- 这题考的是对「状态放在谁手里」的敏感度,是 30 天课 D1「历史靠你自己搬」在 SDK 层的翻版。
- 拆法是问三件事:历史能不能审计、能不能裁剪或重放、能不能满足数据驻留要求。previous_response_id 的历史在服务端,请求最小、代码最简,但三个问题都答不好;session 的历史在你手里(内存、SQLite、Redis),三个都能做,代价是自己管存储。
- 结论:原型与内部工具用 previous_response_id 省事;面向用户的生产系统至少自己落一份历史,session 是现成的落法;两者可以同时用。
- session 的四个接口(取、追加、弹出最后一条、清空)里 pop_item 值得点出:用户撤回上一句时把最后一轮拿掉再重跑,这是自己持有历史才能做的事。
- tracing 的作用是让多 Agent 系统的行为可解释:默认开启,每次 run 一条,记录每轮、每次工具调用、交接与护栏判断;用 withTrace 或 group_id 把一段对话归到一起;敏感数据场景用环境变量关掉或换成自己的导出器。
- 可预期的追问:tracing 会不会把用户数据传出去?默认会传到平台面板,所以合规场景要么关、要么 setTraceProcessors 换成自己的后端。
Key points
- previous_response_id keeps history server-side, small and simple, but weak on audit, trimming and residency
- Sessions keep history in your store, auditable and replayable, with pop_item for undo; production keeps its own copy
- Tracing is on by default, one trace per run, capturing turns, tools, handoffs and guardrails, grouped via group_id
- For sensitive data disable it with OPENAI_AGENTS_DISABLE_TRACING or swap in your own exporter
答题要点
- previous_response_id 历史在服务端,请求小代码简,但难审计、难裁剪、难满足数据驻留
- session 历史在自己手里,可审计可重放,pop_item 支持撤回;生产至少自己落一份
- tracing 默认开、每次 run 一条,记录每轮工具、交接与护栏,用 group_id 归组
- 敏感数据场景用 OPENAI_AGENTS_DISABLE_TRACING 关掉或换成自己的导出器
D5 Choosing and Combining Claude and Codex: A Real Side-by-Side on the Same Task, a Write-One-Review-One Mixed Workflow
Your team must pick between two coding agents. How do you propose a comparison that teammates can both understand and verify?团队要在两家 coding agent 之间选一个,你怎么给出一套可以向团队解释、也能被验证的对比维度?
Common in ChinaCommon overseasIntermediate#coding-agent#evaluation#decision-makingHow to reason about it · think before answering
- This tests methodology, not a verdict; leading with 'I prefer X' signals weak engineering judgment. Show how you make the comparison reproducible.
- Give the dimensions: instruction effort (prompt and instruction-file size), approvals (how many interruptions and why), verification (does it run tests unprompted, what happens on red), cost (time, tokens, money). All are measurable in your own repo.
- State the preconditions for comparability: same starting commit, identical requirement text, identical instruction-file content, default permissions, and 'run tests before reporting' on both sides.
- Then the reading order: check comparability, then structural differences (permission model, placement and wording of rules), and only then capability differences, which need several runs and a median.
- For the team: label every differing row as 'workflow' or 'capability'; workflow gaps are closed by configuration, capability gaps drive the choice.
- Expect the follow-up: why not benchmarks? They score standard problems with one number, while teams change legacy repos and care about four dimensions.
分析过程 · 先想清楚再作答
- 这题考的是方法论而不是结论。上来就说「我觉得 X 好」会被判为没有工程判断;面试官想听的是你怎么让比较可复现。
- 先给维度:交代(写多少需求、准备多少说明文件)、审批(中断几次、为了什么)、验证(是否主动跑测试、红了怎么办)、成本(时间、token、钱)。这四项都能在自己的仓库里量出来。
- 再给可比性的前置条件:同一个起点 commit、同一段需求文字、说明文件同内容、默认权限、都要求跑完测试再汇报;有一项不同,差异就说不清来源。
- 然后是读数的顺序:先查可比性,再看结构性差异(权限模型、说明文件的位置与措辞导致的行为差别),最后才看能力差异,而且能力差异要多次运行取中位数。
- 落到团队沟通:报告里每一行差异都标「来自工作方式还是能力」,工作方式的差异靠配置弥补,能力差异才影响选型。
- 可预期的追问:榜单为什么不够?榜单测标准题,团队干的是有历史包袱的仓库里的改动,且榜单只给一个分数、不给四个维度。
Key points
- Four measurable dimensions: instruction effort, approvals, verification, cost, all measured in your own repo
- Comparability first: same commit, same prompt, same instruction file, default permissions, tests required
- Read in order: comparability, structural differences, then capability, with medians over several runs
- Label each gap as workflow or capability; only capability gaps should drive the decision
答题要点
- 四个可量维度:交代、审批、验证、成本,全部在自己仓库里测
- 可比性前置:同起点、同需求、同说明文件、默认权限、都要求跑测试
- 读数顺序:可比性、结构性差异、能力差异;能力差异要多次运行取中位数
- 每行差异标「工作方式还是能力」,前者靠配置弥补,后者才决定选型
Where does the 'one vendor writes, the other reviews' workflow pay off, and when is it not worth it?「一家写、另一家审」的混用工作流收益在哪?什么情况下不值得?
Common in ChinaCommon overseasIntermediate#code-review#workflow#coding-agentHow to reason about it · think before answering
- The crux is 'not worth it'; listing benefits without costs reads as never having sat in front of a budget.
- Source of value: when one model both writes and reviews, they share one reading of the requirement, so misreads slip through; a second vendor catches exactly those, plus complementary blind spots.
- How to make it pay: the reviewer needs the original requirement and acceptance criteria, not just the diff, or it degrades into lint; demand structured output so acceptance can be measured; a human makes the final call.
- Not worth it when the task is smaller than the review, when only one vendor's quota exists and the extra bill outweighs extra findings, or when nobody reads review comments carefully.
- Most worth it when a change touches many callers, the requirement is ambiguous, or the change ships to production; one caught misread pays for it.
- Expect the follow-up: can it be automated? Both vendors have headless modes so writing and reviewing can be scripted, but the human adjudication step cannot be removed.
分析过程 · 先想清楚再作答
- 题眼在「不值得」。只讲收益不讲代价,是没在预算表前坐过的人的答法。
- 先说收益的来源:同一家模型写与审共享同一份对需求的理解,需求理解偏差抓不出来;换一家审,最大的增量正是这类偏差,其次是不同模型的盲区互补。
- 再说怎么做才有收益:审查方必须拿到需求原文与验收标准而不只是 diff,否则退化成 lint;必须要求结构化输出,否则无法统计采纳率;最后一步必须由人裁决。
- 不值得的三种情况:任务小到审查成本高于任务本身;团队只有一家的额度,跨家意味着双份账单且多抓出的问题不值这笔钱;审查意见没人认真看,多一家只是多一层噪音。
- 最值的三种情况:改动影响多个调用方、需求本身有歧义、改动要上生产——抓出一个理解偏差就回本。
- 可预期的追问:能不能自动化?两家都有脱手模式,写与审都能脚本化,但「人裁决」这一步不能省,否则前两步就是浪费。
Key points
- Value comes from an independent reading of the requirement, catching misreads a same-vendor review misses
- The reviewer needs the requirement and acceptance criteria, must output structured findings, and a human adjudicates
- Not worth it for tiny tasks, single-vendor budgets, or teams that do not read reviews
- Most valuable for multi-caller changes, ambiguous requirements and production deploys
答题要点
- 收益来自独立的需求理解:换一家审能抓出同家审查抓不到的理解偏差
- 审查方要拿到需求原文与验收标准、输出结构化意见,最后由人裁决
- 不值得:任务太小、只有一家额度、没人认真看意见
- 最值:影响多个调用方、需求有歧义、要上生产
How do you judge the quality of a coding agent's output on a task, beyond whether it ran?怎么评价一个 coding agent 这次任务的输出质量,而不只是看它跑没跑通?
Common in ChinaCommon overseasDeep dive#coding-agent#evaluation#qualityHow to reason about it · think before answering
- This tests whether you treat green tests as the finish line; 'check the tests' is the pass mark, differentiation lies beyond it.
- Four layers: correctness (do the tests cover the requirement's edges such as overly long titles or a string for done), contract (does the error shape match the spec exactly or did it improvise), scope (did it touch forbidden files, add dependencies or change defaults silently), maintainability (constants extracted, tests isolated, naming consistent with the repo).
- How to measure: correctness by adding your own counterexamples beyond its tests; contract and scope by diffing against the requirement line by line; maintainability via a structured review by a second model or a person.
- Add variance: one run proves nothing; run the same requirement three times and treat high variance as a quality signal in itself.
- Expect the follow-up: can you trust its 'done, tests pass'? Only what you can reproduce; rerun tests and read the diff yourself, the agent's report is a lead, not evidence.
分析过程 · 先想清楚再作答
- 这题考的是你有没有把「测试绿了」当终点。答「看测试」是及格线,区分度在测试之外。
- 拆成四层:正确性(测试是否覆盖了需求里的边界,比如 title 超长、done 传字符串)、契约(错误响应形状是否与需求一字不差,还是它自作主张改了)、范围(有没有改不该改的文件、有没有偷偷加依赖或改默认值)、可维护性(校验规则是否抽成常量、测试是否隔离、命名是否与仓库一致)。
- 再说怎么量:正确性看它写的测试之外你再补的反例能不能过;契约与范围看 diff 与需求逐条对照;可维护性交给第二家模型或人做结构化审查。
- 补一条随机性:单次结果不能下结论,同一需求跑三次看方差,方差大本身就是一个质量信号。
- 可预期的追问:它自己说「已完成并通过测试」能信吗?只信你能复现的部分——在你的机器上重跑测试、看 diff,agent 的汇报是线索不是证据。
Key points
- Four layers: correctness, contract, scope, maintainability; green tests cover only part of correctness
- Verify correctness with your own counterexamples, contract and scope by diffing against the spec, maintainability via structured review
- Run the same requirement several times; high variance is itself a quality signal
- The agent's report is a lead, not evidence; trust only what you reproduce
答题要点
- 四层:正确性、契约、范围、可维护性,测试绿只是正确性的一部分
- 正确性用自己补的反例验证,契约与范围对照需求逐条看 diff,可维护性做结构化审查
- 同一需求跑多次看方差,方差大本身是质量信号
- agent 的汇报是线索不是证据,只信自己能复现的部分
MCP in 7 Days: Wire Tools Into Any Agent
D1 Why a Protocol: the Host/Client/Server Triangle, JSON-RPC Messages, and Three Primitives
How is MCP actually different from a model's built-in function calling, and when should you not use MCP?MCP 和模型自带的函数调用到底差在哪?什么情况下你不该用 MCP?
Common in ChinaCommon overseasBasic#mcp-basics#architectureHow to reason about it · think before answering
- The screen is whether you have actually wired tools yourself. Calling MCP an upgraded function call fails, because the two sit at different layers.
- Separate the layers first: function calling is a model API feature — you pass tool definitions in the request and the model replies with which one to invoke. MCP governs where that definition and its executor live and how they are exchanged.
- They compose rather than compete: an MCP client still translates tools/list output into the model API's tool parameters, so the final hop is ordinary function calling.
- Conclusion: MCP turns an M-applications-by-N-tools wiring problem into M plus N, at the cost of an extra process, an extra serialization boundary, and an extra place to debug.
- Skip MCP when the tool has exactly one consumer, when calls are hot and latency-sensitive (a remote round trip is tens to hundreds of milliseconds, five per turn is noticeable), or when the decision does not need a model at all.
- Likely follow-up: local stdio is cheap, so why not use it everywhere? Because the cost is not only transport — it is one more process to deploy, monitor, and authorize.
分析过程 · 先想清楚再作答
- 这题在筛「有没有真正接过工具」。把 MCP 说成「函数调用的升级版」就露馅了,因为两者根本不在同一层,答对的人第一句就会先把层次拆开。
- 拆法:问自己「这一步是模型 API 的事,还是工具从哪来的事」。函数调用是模型 API 的能力——你把工具定义放进请求,模型回一个要调谁;MCP 管的是那份定义和执行体住在哪个进程里、用什么语言交换。
- 接着点出两者是叠加而非替代:MCP 客户端拿到 tools/list 之后,还要把它翻译成模型 API 的工具参数,最终仍然走函数调用那条路。
- 结论:MCP 解决的是 M 个应用乘 N 个工具的重复接线,把乘法变成加法;它换来的代价是多一层进程、一层序列化、一层要排查的地方。
- 不该用的三种情况:工具只有自己这一个程序用;调用极频繁且对延迟敏感(远程一次往返几十到几百毫秒,一轮连调五次用户就有感);这件事根本不需要模型决定,产品逻辑本来就是确定的。
- 可预期的追问:那本机 stdio 的开销很小,是不是就可以随便用?答案是开销不只在传输,还在多一个要部署、要监控、要授权的进程上。
Key points
- Function calling is a model API capability; MCP is a distribution protocol for tool definitions and executors — they stack, not compete
- MCP converts M-by-N adapters into M plus N, paying with an extra process and serialization hop
- Skip it for single-consumer tools, latency-sensitive hot paths, and flows that are deterministic by design
- The test is whether a second program will ever need this capability; if yes, the protocol cost amortizes
答题要点
- 函数调用是模型 API 的能力,MCP 是工具定义与执行体的分发协议,两者叠加而不是替代
- MCP 的价值是把 M 乘 N 的适配器数量变成 M 加 N,代价是多一层进程与序列化
- 单一消费者、延迟敏感的热路径、以及本来就确定的产品流程,这三种情况不该用 MCP
- 判据是「这个能力要不要给第二个程序用」,只要答案是要,协议的成本就摊得开
Why does the MCP spec require one client per server instead of multiplexing many servers over one connection?MCP 规范为什么规定一个客户端只连一个服务端?多路复用不是更省资源吗?
Common in ChinaCommon overseasIntermediate#architecture#securityHow to reason about it · think before answering
- It reads like a performance question but is really about security boundaries. Answering only in terms of connection count signals you never read the design principles.
- Ask who can see whom once a channel is shared. The spec fixes two principles: servers should not read the whole conversation, and should not see into other servers. One-to-one is the most direct way to enforce both.
- Concrete consequence: with isolation, a third-party weather server sees only the city you passed. On a shared channel it could observe traffic between you and an internal database server — a data leak.
- Conclusion: full history stays with the host, each server receives only the arguments this call needs, and the host is the single place where boundaries are enforced and cross-server orchestration happens.
- State the cost yourself: N servers means N connections and N lifecycles, and that is where most client complexity lives, not in sending messages.
- Likely follow-up: how do you handle tool name collisions across servers? Aggregation and disambiguation belong to the host; the spec suggests prefixing with a server identifier and explicitly warns against relying on the server's self-reported name, which is neither unique nor verified.
分析过程 · 先想清楚再作答
- 这题看着在问性能,其实在问安全边界。只从连接数和资源占用切入的回答会被判为没读过设计原则那一节。
- 拆法:先问「共享一条通道之后,谁能看见谁」。规范写死了两条原则——服务端不应该读到整段对话,也不应该看得见别的服务端;一对一是实现这两条最直接的手段。
- 举一个具体后果:接一个第三方天气服务端时,一对一隔离让它只能看到你传的城市名;共享通道则可能让它读到你和内部数据库服务端之间的往来,那就是一次数据泄露。
- 结论:完整对话历史留在宿主,服务端只拿到这次真正需要的参数;宿主是唯一的安全边界执行者,也是唯一做跨服务端编排的地方。
- 代价要主动说:接 N 个服务端就有 N 条连接、N 套生命周期要管,客户端实现的复杂度大头正是在这里,而不是在发报文上。
- 可预期的追问:那多个服务端的工具重名怎么办?答案是聚合与消歧是宿主侧的职责,规范建议加服务端标识前缀,并且明确说不要依赖服务端自报的名字,因为它不保证唯一也未经验证。
Key points
- One-to-one is a security decision, not a performance one: servers cannot read the conversation or see peers
- Full history stays in the host; a server receives only the arguments for the current call
- Aggregation, disambiguation, and authorization all happen in the host, so there is a single boundary to harden
- The cost is connection and lifecycle management, which dominates client implementation complexity
答题要点
- 一对一是安全设计而非性能设计:服务端读不到整段对话,也看不见别的服务端
- 完整历史留在宿主,服务端只收到本次调用真正需要的参数
- 跨服务端的聚合、消歧、授权都由宿主统一做,边界只有一处需要加固
- 代价是连接与生命周期管理,这是客户端实现复杂度的主要来源
The 2026-07-28 revision made MCP stateless and removed the initialize handshake. What does that cost, and how should a server that still needs state handle it?2026-07-28 这一版把 MCP 改成了无状态协议,删掉了 initialize 握手。这么改的代价是什么?服务端还想保存状态该怎么办?
Common in ChinaCommon overseasDeep dive#protocol-versions#statelessnessHow to reason about it · think before answering
- The discriminator is whether you know what this revision changed. Anyone answering from memory about handshakes, session IDs, or stream resumption exposes themselves — all three were removed.
- State the change first: no initialize and no notifications/initialized; every request carries its protocol version and client capabilities in _meta, and a new server/discover method, which servers MUST implement, returns versions, capabilities, and identity in one call.
- Weigh it as saved versus paid. You pay in payload size, repeating the version and capability block on every request. You save three things: any replica can serve any request so scaling needs no sticky routing, unrelated requests can interleave on one connection, and after a restart in-flight requests simply get resent.
- Conclusion: it trades bandwidth for scalability — near-invisible on local stdio, valuable for multi-replica remote deployments.
- For state, use explicit handles: a creation tool returns a server-minted id, and later calls pass it back as an ordinary argument while the server keys its own storage on it and documents the lifetime in the tool description.
- Likely follow-up: is a handle safe? Say it unprompted — a handle is a name, not a credential. Re-authorize the caller on every call, generate handles with a secure random source, bind them to the authenticated principal, and expire them.
分析过程 · 先想清楚再作答
- 这题的区分度在「知不知道这一版改了什么」。凭旧记忆答握手、会话标识、断流续传的人会当场暴露,因为这三样在这一版全被删了。
- 先说改了什么:没有 initialize 与 notifications/initialized,每条请求在 _meta 里自带协议版本与客户端能力;新增 server/discover 供客户端一次性取回版本、能力与身份,服务端必须实现它。
- 拆代价的角度是「省了什么、贵了什么」。贵的是报文:每条请求都要重复带版本与能力块。省的是三件事——任意副本都能处理请求所以扩容不用粘性路由、一条连接可以穿插无关请求、进程重启后在途请求重发即可。
- 结论:这是一次拿带宽换可伸缩性的交易,对本机 stdio 几乎无感,对多副本的远程部署收益很大。
- 状态怎么办:显式句柄。创建工具返回一个服务端铸造的 id,后续调用把它当普通参数传回来;服务端把状态按这个 key 存在自己的库里,并在工具描述里写清有效期。
- 可预期的追问:句柄安全吗?必须补一句——句柄是名字不是凭证,服务端每次都要重新校验调用者身份,句柄要用安全随机数生成、绑定到已认证的主体、并设过期时间。
Key points
- The revision removed the initialize handshake, protocol-level sessions, the GET stream, and stream resumption; each request now carries version and capabilities
- It added server/discover, which servers must implement, letting clients fetch versions, capabilities, and identity up front
- The cost is larger payloads; the payoff is sticky-free horizontal scaling, interleaved unrelated requests, and cheap retry after restarts
- Cross-call state moves to server-minted explicit handles passed as ordinary tool arguments, and a handle is never authentication
答题要点
- 这一版删掉了 initialize 握手、协议级会话、GET 长连接与断流续传,改为每条请求自带版本与能力
- 新增 server/discover,服务端必须实现,客户端可在任何请求前一次性取回版本、能力与身份
- 代价是报文变胖,收益是无粘性路由的横向扩容、连接上可穿插无关请求、重启后重发即可
- 跨调用状态改用服务端铸造的显式句柄,作为普通工具参数传递,并且句柄不等于身份认证
D2 Writing Your First MCP Server: stdio Transport, the Official SDK, Parameter Schemas, Tool Annotations, and Debugging With Inspector
Who is a tool's description actually written for, and what concretely goes wrong in production when it is too vague?工具的 description 到底写给谁看?写得太泛,在生产里会造成什么具体后果?
Common in ChinaCommon overseasBasic#tool-design#prompt-surfaceHow to reason about it · think before answering
- The screen is whether you have ever debugged a tool the model refuses to call. Answering 'write it clearly so colleagues understand' reveals doc-thinking; the point is that the description is the model's only evidence.
- Ask what the model has when it makes the decision: the tool name, this one description, and the parameter schema. It cannot see your wiki, comments, or spec. The description is a decision input, not documentation.
- Split vagueness into two failure directions. Under-calling: the model never realizes the tool solves the current problem, so the task silently fails with no error. Over-calling: fuzzy boundaries make the model invoke it when it should not, which is a real incident if the tool has side effects.
- Conclusion: a usable description answers three things — what it does, what the parameters look like with an example, and when it should be used. The third is the one people omit, and it is the gate that prevents over-calling.
- Add the engineering view: a description is an external contract, so changing it changes behavior, and the same wording performs differently across models. It belongs in version control with an eval set, not in post-launch eyeballing.
- Likely follow-up: is longer always better? No. Descriptions consume context budget and crowd out the actual conversation once you have many tools. Keep the summary short and push detail into each parameter's own description.
分析过程 · 先想清楚再作答
- 这题在筛「有没有真的排查过模型不调工具」。答成「写清楚一点,方便别人理解」就落到文档思维了;面试官想听的是描述是模型唯一的判断依据这件事。
- 拆法:先问自己「模型做这个决定时手上有什么」。它看不到你的 wiki、代码注释、需求文档,只有工具名加这一句描述加参数 schema。所以描述不是文档,是决策依据。
- 把「太泛」拆成两个方向的后果:一是**漏调**,模型不知道这个工具能解决当前问题,任务默默做不成,而且不会报错;二是**误调**,描述边界不清,模型在不该调的时候调它——如果这个工具有副作用,那就是一次真实的线上事故。
- 结论:一句合格的描述要回答三件事——做什么、参数长什么样(给例子)、什么情况下才该用。第三条最常被漏掉,也最要命,因为它才是防误调的那道闸。
- 补一条工程视角:描述是对外契约,改它等于改行为。同一段描述在不同模型上表现还不一样,所以描述要进版本管理、要有评估集,不能靠上线后人肉观察。
- 可预期的追问:那把描述写得越长越好吗?不是。描述会占上下文预算,工具一多就挤掉真正的对话内容;正确做法是短而准,把细节放进每个参数各自的 description 里。
Key points
- The description is read by the model and is its only basis for deciding whether to call the tool
- Vagueness causes silent under-calling or dangerous over-calling of side-effecting tools
- A good description states what it does, what the parameters look like with an example, and when it applies
- Treat it as an external contract with version control and evals; push detail into per-parameter descriptions to save context
答题要点
- 描述是给模型看的,是它决定调不调这个工具的唯一依据,不是给同事看的文档
- 写得太泛有两类后果:漏调导致任务静默失败,误调则可能触发有副作用的操作
- 合格描述回答三件事:做什么、参数长什么样并给例子、什么情况下才该用
- 描述是对外契约,要进版本管理并配评估集;细节放进每个参数的 description,总描述保持短而准
When should a tool return a JSON-RPC error versus a result with isError set to true? Give me a decision rule.什么时候该返回 JSON-RPC 的 error,什么时候该返回 isError 为真的工具结果?给我一个判据。
Common in ChinaCommon overseasIntermediate#error-handling#tool-designHow to reason about it · think before answering
- This is close to a pass/fail line for MCP server work. Reciting 'two kinds of errors' is baseline; the discriminator is producing an actionable rule and knowing who the error text is written for.
- Ask who can fix it. If the request itself is invalid — unknown tool, arguments failing the call-tool schema, an internal server fault — no amount of parameter tweaking helps, so return a JSON-RPC error, typically -32602. If the tool ran but the business case failed — downstream API error, bad date format, amount out of range — a different argument might work, so return isError in the result.
- The rule in one line: could the model succeed by changing an argument? If yes use isError, if no use error. Note that isError is still a successful JSON-RPC response with resultType complete.
- Carry the text requirement into the conclusion: the spec says clients should hand execution errors to the model for self-correction, so the message is written for the model. List allowed values, the correct format, the boundary. 'Invalid parameter' just makes it guess.
- The production trap is not misclassifying but returning neither — skipping validation so an illegal input yields NaN or an empty result that is silently returned. The model then uses a wrong answer with no trace. Leaning on output-schema validation is not handling it either, since the model receives a schema stack trace.
- Likely follow-up: should clients feed protocol errors to the model too? The spec permits it but it rarely helps, because the model cannot fix them. Log and alert instead — that one is your bug.
分析过程 · 先想清楚再作答
- 这题几乎是 MCP 服务端的入门分水岭。能背出「两类错误」只算及格,区分度在于能不能给出一条可执行的判据,以及知不知道错误文案是写给谁的。
- 拆法:问「谁能修好这个错」。请求本身不合法——工具名不存在、参数不满足调用工具的 schema、服务端内部异常——模型再怎么改参数都没用,这类走 JSON-RPC 的 error,典型是 -32602。工具跑了但业务没成——下游 API 失败、日期格式不对、金额越界——模型换个参数就可能成功,这类走 result 里的 isError。
- 判据一句话:**模型换个参数有没有可能成功?有就用 isError,没有就用 error。** 注意 isError 仍然是一个成功的 JSON-RPC 响应,resultType 照样是 complete。
- 结论要带上文案要求:规范说客户端应当把执行错误交给模型自我纠正,所以文案是写给模型看的,要列出可选值、正确格式、边界条件。写「参数错误」等于让模型瞎猜。
- 生产视角的坑:最危险的不是分错类,而是**两类都不返回**——不做校验,让非法输入算出 NaN 或空结果静默返回。模型会把错误答案当正确答案用下去,且不留痕迹。靠输出 schema 校验去兜底也不算处理,因为模型拿到的是一段 schema 堆栈。
- 可预期的追问:客户端要不要把协议错误也喂给模型?规范说可以,但基本没用,因为模型改不了;更该做的是记日志报警,那是你的 bug 不是模型的。
Key points
- Protocol errors use the JSON-RPC error field: unknown tool, schema-invalid request, internal fault — unfixable by the model
- Execution errors use isError true in the result and remain a successful JSON-RPC response
- The rule: if a different argument could succeed, use isError; otherwise use error
- Write execution-error text for the model with allowed values and formats; the worst case is neither, silently returning a wrong result
答题要点
- 协议错误走 JSON-RPC 的 error:未知工具、请求不满足 schema、服务端内部错,模型改参数也无济于事
- 执行错误走结果里的 isError 为真:下游失败、业务校验不过,它仍是成功的 JSON-RPC 响应
- 判据是模型换个参数有没有可能成功,有就 isError,没有就 error
- 执行错误的文案写给模型看,要列出可选值与正确格式;最危险的是两类都不返回、静默给出错误结果
What is the most common way a stdio MCP server breaks, and how do you prevent it in code and in process?一个 stdio 的 MCP 服务端最常见的翻车原因是什么?你会在代码和流程上分别怎么堵住它?
Common in ChinaCommon overseasDeep dive#stdio-transport#debuggingHow to reason about it · think before answering
- This checks whether you have actually run one. People who have not will say 'the process did not start' or 'wrong path'; anyone who has been bitten leads with stdout contamination.
- Restate the hard rules first: messages are newline-delimited JSON, one per line, with no embedded newlines, and the server must not write anything to stdout that is not an MCP message. Logging goes to stderr. Once the rules are stated the failure mode is obvious.
- Call out the symptom, because that is the discriminator: the client only reports a JSON parse failure and cannot point at your console.log, and the polluter is often a third-party library printing a banner or deprecation warning at import time rather than your own code.
- Conclusion in two layers. In code: wrap a stderr-only logger, ban direct printing, vet third-party libraries for stdout writes, and ensure serialized JSON carries no raw newlines. In process: add a self-test entry point that links a client and server over an in-memory transport in one process, asserts, and exits with a real status code, then run it in CI so contamination is caught before merge.
- Add the adjacent one: graceful shutdown. The spec makes closing stdin and exiting on EOF the primary and only portable shutdown signal; ignoring it leaves orphan processes that show up locally as mysteriously held ports and file locks.
- Likely follow-up: if it is this fragile, why use stdio? Zero configuration, zero network attack surface, and process isolation for free — the tradeoff is clearly worth it locally. You switch transports when you need team sharing or multiple replicas.
分析过程 · 先想清楚再作答
- 这题在验有没有真跑过。没实际接过的人会答「进程没起来」「路径不对」这类泛泛的,真踩过的人第一句就会说标准输出被污染。
- 拆法:先复述 stdio 的硬规矩——消息是一行一条换行分隔的 JSON、内部不许有裸换行,服务端不得往标准输出写任何不是 MCP 消息的东西,日志一律走标准错误。规矩一说完,翻车原因就自明了。
- 现场特征值得单独说,因为它是这题的区分点:客户端只会报一句 JSON 解析失败,指不到你哪一行 console.log;而且污染源常常不是你自己的代码,而是某个第三方库在启动时打的横幅或弃用警告。
- 结论分两层。代码上:封一个只写标准错误的日志函数并全局禁用直接打印,接第三方库之前先确认它不往标准输出写东西,把 JSON 序列化后确保不含裸换行。流程上:加一个自测入口,用内存传输在同一个进程里把客户端和服务端接起来跑断言,有明确退出码,进持续集成——这样污染一出现就会在合并前被拦下。
- 再补一条相关的:优雅停机。规范说客户端关掉输入流、服务端读到文件结束就应尽快退出,这是主要且唯一可移植的停机信号;不处理它就会留下孤儿进程,本机开发时表现为端口和文件锁莫名被占。
- 可预期的追问:既然这么脆,为什么还用 stdio?因为它零配置、零网络攻击面、进程隔离天生就有,本机场景收益远大于代价;要给团队共享或多副本才需要换成远程传输。
Key points
- Stdout contamination: stdio reserves stdout for MCP messages, so a single console.log breaks the client's parser
- The symptom is only a JSON parse failure with no line number, and the culprit is often a third-party library's startup banner
- In code, use a stderr-only logger, ban direct printing, and vet dependencies for stdout writes
- In process, add an in-memory-transport self-test with a real exit code in CI, and exit promptly on stdin EOF to avoid orphan processes
答题要点
- 最常见的是标准输出被污染:stdio 规定 stdout 只能有 MCP 消息,一行 console.log 就让客户端解析失败
- 现场只报 JSON 解析失败,指不到具体行,污染源常常是第三方库启动时打的横幅或警告
- 代码上封一个只写标准错误的日志函数并禁用直接打印,接库之前先验它不写 stdout
- 流程上加一个用内存传输的自测入口,有明确退出码并进持续集成;同时处理 stdin 关闭时的优雅退出
D3 Resources and Prompts: URI Templates, Change Notifications, Progress and Logging, Pagination, and Client Capabilities
For the same data, what is the difference between exposing it as an MCP resource versus a tool, and how do you choose?同一份数据,做成 MCP 资源和做成工具有什么区别?你按什么标准选?
Common in ChinaCommon overseasBasic#primitives#server-designHow to reason about it · think before answering
- This screens for real server design experience. Saying resources are read-only and tools mutate scores a pass at best, because read-only search still belongs in a tool.
- Reframe it: do not ask what the data is, ask who decides to use it this time. The spec makes resources application-driven, picked by the host or the user, while tools are model-controlled. Fixing the controller also fixes who is accountable when it goes wrong.
- Add the practical test: enumerability. A resource has to appear in a paginated list a human can pick from, so a code search with an unbounded input space must be a tool even though it never writes anything.
- Conclusion: read-only, enumerable, user-selectable becomes a resource; side-effecting, model-timed, or non-enumerable becomes a tool.
- Bring up cost unprompted: tool definitions ship on every turn whether used or not, while an unselected resource costs zero tokens. Three thousand documents as three thousand tools blows up the context window; as resources they are pay-per-use.
- Likely follow-up: where do prompts fit? They are the third primitive, user-selected and usually surfaced as slash commands — the three differ only by who controls them.
分析过程 · 先想清楚再作答
- 这题在筛「有没有真的设计过服务端」。答成「资源是只读的、工具会改数据」只能算及格,因为只读的检索照样该做成工具,区分度全在这一步。
- 拆法:不要问「它是什么」,问「这一次由谁决定用不用它」。规范把资源定成应用驱动——由宿主应用或用户挑;工具是模型控制——模型看着描述自己调。控制方定了,出错时该找谁负责也就定了。
- 再补一条更实用的判据:能不能被枚举。资源要出现在一张可翻页的清单里让人挑,所以「搜索代码」这种输入空间无限的能力,哪怕完全只读也必须做成工具。
- 结论:只读、可枚举、希望用户在界面上挑的做成资源;有副作用、或需要模型自己判断时机、或无法枚举的做成工具。
- 生产视角要主动加一句成本:工具定义不管用不用,每轮都要塞进请求;资源不被选中就一个 token 都不占。三千篇文档做成三千个工具会直接撑爆上下文,做成资源则按需付费。
- 可预期的追问:那提示模板算第几种?答案是第三种,由用户显式选中,典型形态是斜杠命令——三种原语的差别只在控制方,不在能力。
Key points
- Resources are application-driven and picked by host or user; tools are model-controlled and chosen from their descriptions
- Enumerability is the practical dividing line: unbounded-input capabilities like search stay tools even when read-only
- Cost-wise tool definitions occupy context every turn while unselected resources cost nothing, so large corpora must be resources
- The controller determines accountability: bad tool choice means bad descriptions, bad prompt choice means bad naming, bad resource injection is a product problem
答题要点
- 资源是应用驱动的,由宿主或用户挑;工具是模型控制的,由模型看描述自己调
- 能不能枚举是最实用的分界线:搜索这类输入空间无限的能力即使只读也做成工具
- 成本上工具定义每轮都占上下文,资源不被选中就不花钱,大规模知识库必须走资源
- 控制方决定了出错时找谁负责:模型选错是描述问题,用户选错是命名问题,应用塞错是产品问题
Why must MCP pagination cursors be opaque, and what breaks if a client parses them?MCP 的分页游标为什么必须是不透明的?如果客户端去解析它,会出什么问题?
Common in ChinaCommon overseasIntermediate#pagination#api-designHow to reason about it · think before answering
- It looks like a spec-recitation question but really tests whether you have shipped a paginated public API. Quoting the rule earns nothing; naming the concrete failure does.
- Start from what a cursor holds. A server may encode an offset, a primary key, a timestamp, or encrypted state, and it may change that at any time. A client that parses one format breaks everywhere the day the server switches, because parsing turned an internal detail into a public contract.
- Second failure is forgery. A client that fabricates offset:9999 bypasses the server's control over paging range, and if the cursor encodes filters or permissions, forging it is a privilege escalation.
- Third and nastiest: treating an empty string as the end. The spec is explicit that only a missing nextCursor ends the sequence; an empty string is a valid cursor. Getting this wrong silently drops the last page with no error, which tests rarely catch.
- Conclusion: a client may make exactly one judgment about a cursor — whether nextCursor is present. Page size likewise must not be assumed fixed. Servers should reject invalid cursors with -32602 rather than silently returning page one, which would loop the client forever.
- Likely follow-up: what bites the server side? Offset cursors require a stable ordering, since an insertion shifts everything after it, so either sort first or encode the last item's key instead.
分析过程 · 先想清楚再作答
- 这题表面考规范条文,实际考「有没有做过带分页的对外接口」。只背出「规范说不透明」拿不到分,要能说出解析之后具体哪一步会崩。
- 拆法:先问游标里到底装的是什么。服务端可以装偏移量、主键、时间戳、甚至一段加密状态,而且**换实现时它随时会变**。客户端一旦按某种格式解析,服务端从偏移量换成主键那天,所有客户端一起挂——这是把服务端的内部实现变成了公开契约。
- 第二个坑是伪造。客户端自己造一个 offset:9999 递给服务端,等于绕过了服务端对翻页范围的控制;如果游标里编了权限或过滤条件,伪造它就是一次越权。
- 第三个坑最阴:把空字符串当成结束。规范写死了只有 nextCursor **缺失**才代表没有下一页,空串是完全合法的游标。判错的表现是最后一页数据被静默丢掉,而且不报错,测试也很难发现。
- 结论:客户端对游标只允许做一个判断——nextCursor 在不在。页大小同理不得假设固定值,服务端随时可以改。非法游标服务端应当回 -32602,而不是静默返回第一页,否则客户端会陷进死循环。
- 可预期的追问:那服务端这边有什么坑?偏移量式游标要求列表顺序稳定,中途插入一条会让后面全部错位,所以要么先排序、要么把游标编成上一条的主键。
Key points
- Cursor contents are server internals; parsing them turns an implementation detail into a public contract that breaks on any change
- Forged cursors bypass server-side paging control, and become privilege escalation if the cursor encodes filters or permissions
- Only a missing nextCursor ends the sequence — an empty string is valid, and getting it wrong silently drops the last page
- Page size is server-decided and must not be assumed fixed; invalid cursors should return -32602 rather than silently resetting
答题要点
- 游标内容是服务端的内部实现,解析它等于把实现细节变成公开契约,服务端换实现时客户端全挂
- 伪造游标可以绕过服务端对翻页范围的控制,游标里若编了过滤或权限条件就是越权
- 只有 nextCursor 缺失才代表结束,空字符串是合法游标,判错会静默丢掉最后一页
- 页大小由服务端决定不得假设固定,非法游标服务端应回 -32602 而不是静默回第一页
On HTTP both subscription streams and in-request progress notifications ride SSE, so why does the 2026-07-28 spec split them into two channels?订阅流和请求内的进度通知在 HTTP 上都走 SSE,为什么 2026-07-28 规范要把它们分成两个通道?
Common in ChinaCommon overseasDeep dive#subscriptions#notificationsHow to reason about it · think before answering
- The discriminator is version awareness. Anyone still describing resources/subscribe and a standalone GET stream exposes themselves — both were replaced by subscriptions/listen in this revision.
- Get the facts straight first: subscriptions/listen is an ordinary request whose response is a stream that stays open. The client explicitly opts into toolsListChanged, promptsListChanged, resourcesListChanged, and resourceSubscriptions; the server must not push unselected types; the first message must be the acknowledgment, and every later notification carries subscriptionId in _meta.
- Then compare lifetimes. Progress and log notifications belong to one specific request and should stop when it ends. List changes and resource updates span the whole connection and relate to no single request. Mixing different lifetimes into one stream wrecks cancellation semantics, because closing a response stream on HTTP is the cancel signal — you do not want cancelling a tool call to kill your subscriptions.
- The second reason is statelessness and routability. In-request notifications naturally ride their own response stream so any replica can serve them; isolating the one genuinely long-lived connection is what lets every other request avoid sticky routing.
- Conclusion: the subscription stream answers has the world changed, spanning requests; the response stream answers how far along is my request, living and dying with it. The spec states outright that progress and message notifications never appear on the listen stream.
- Likely follow-up: how do you enable log notifications now? logging/setLevel was removed in favor of a per-request logLevel in _meta, and servers must not emit message notifications for requests that omit it. Logging is also deprecated alongside Roots and Sampling, with stderr or OpenTelemetry as the suggested migration.
分析过程 · 先想清楚再作答
- 这题的区分度在版本认知。还在讲 resources/subscribe 和一条独立 GET 长连接的人会当场暴露——这两样在这一版被合并替换成了 subscriptions/listen。
- 先把事实摆清:subscriptions/listen 本身是一条普通请求,只是它的响应是一条一直开着的通知流;客户端在 notifications 过滤器里显式勾选 toolsListChanged、promptsListChanged、resourcesListChanged、resourceSubscriptions,服务端不得推送没勾选的类型;第一条消息必须是 acknowledged,之后每条通知在 _meta 里带 subscriptionId。
- 拆法:问两类通知的生命周期一样吗。进度和日志属于某一次具体请求,请求结束它们就该停;列表变更、资源更新属于整个连接期,跟任何单次请求都无关。生命周期不同的东西混在一条流里,取消语义就说不清——HTTP 上关闭响应流就是取消该请求,你不会希望取消一次工具调用顺带把订阅也掐了。
- 第二个理由是无状态与可路由。请求内通知天然跟着那条请求的响应流走,任意副本都能处理;订阅是唯一一条长活连接,把它单独隔出来,剩下的请求才能真正做到无粘性路由。
- 结论:订阅流回答「世界变了吗」,跨请求、长期存在;响应流回答「我这一单做到哪了」,随请求生随请求死。规范明确写了进度与日志通知不在订阅流上出现。
- 可预期的追问:日志通知现在怎么开?logging/setLevel 已删除,改为每请求在 _meta 的 logLevel 里指定,且服务端不得对没带这个字段的请求发日志通知;而且 Logging 连同 Roots、Sampling 一起已被标记弃用,建议迁到 stderr 或 OpenTelemetry。
Key points
- This revision replaced resources/subscribe and the standalone GET stream with subscriptions/listen, where clients explicitly opt into notification types
- The two kinds have different lifetimes: progress and logs live and die with a request, list and resource changes span the connection
- Merging them breaks cancellation, since closing a response stream on HTTP cancels that request and must not kill subscriptions
- Isolating the single long-lived stream is what lets every other request route without stickiness, enabling horizontal scaling
答题要点
- 这一版用 subscriptions/listen 取代了 resources/subscribe 与独立的 GET 长连接,客户端显式勾选通知类型
- 两类通知生命周期不同:进度日志随请求生灭,列表与资源变更跨请求长期存在
- 混在一条流里会让取消语义失效,HTTP 上关闭响应流即取消该请求,不该顺带掐掉订阅
- 隔离出唯一的长活连接,其余请求才能无粘性路由,这是无状态设计能横向扩容的前提
D4 Remote MCP: the Streamable HTTP Binding, the Stateless Model and Request Metadata, OAuth 2.1 Authorization, Container Deployment
The 2026-07-28 revision removed protocol-level sessions. How should a remote server that needs cross-call state — a shopping cart, a database transaction — be designed?2026-07-28 去掉了协议级会话。那一个需要跨调用保存状态的远程服务端——比如购物车、数据库事务——应该怎么设计?
Common in ChinaCommon overseasIntermediate#statelessness#api-designHow to reason about it · think before answering
- The screen is whether you treat statelessness as a design constraint. Answering 'use Mcp-Session-Id' fails immediately — that header was removed. So does 'keep it in server memory keyed by connection', since clients are not required to reuse connections.
- Give the structure first: state must travel with the client, and the server trusts only what arrives in the request. Two concrete shapes — a server-minted explicit handle returned by a creation tool and passed back as an ordinary tool argument, or a signed opaque blob like the requestState used by multi round-trip requests.
- The difference is who stores the data. A handle is just a primary key into server-side storage; a requestState encodes the context itself, so the server stores nothing. Handles suit long-lived business objects, requestState suits continuing a single interaction.
- Conclusion: either way the server keeps nothing per client in memory, so any replica can serve any request and scaling needs no sticky routing — which is exactly what the change was buying.
- Volunteer the security half: a handle is a name, not a credential. Generate it from a secure random source, bind it server-side to the authenticated principal (key storage as user id plus handle), expire it, and re-authorize on every call — the spec says possession of a handle must not be treated as authentication. requestState passes through the client, so it is attacker-controlled input and must be integrity-protected with HMAC or AEAD, carrying the principal, an originating-request identifier, and a short expiry.
- Likely follow-up: what about requestState across replicas? Share the signing key; it is still stateless because the state lives with the client and replicas only verify. A second follow-up is single use — signing bounds the replay window but does not guarantee one-time consumption, which needs a server-side redemption record.
分析过程 · 先想清楚再作答
- 这题在筛「有没有把无状态当成设计约束」。答「用 Mcp-Session-Id 头」的当场出局,那个头这一版已经删了;答「存在服务端内存里按连接查」的同样出局,因为客户端根本不保证复用连接。
- 先给结构:状态必须由客户端携带,服务端只认请求里带来的东西。落地成两种形态——一是服务端铸造的显式句柄,创建工具返回一个 id,后续调用把它当普通工具参数传回来;二是签过名的不透明状态串,比如多轮请求里的 requestState,服务端把上下文签进去,重试时原样收回。
- 两者的差别在于「谁存数据」:句柄背后的购物车内容还是存在服务端的库里,句柄只是主键;requestState 是把上下文本身编码进字符串,服务端零存储。前者适合长期存在的业务对象,后者适合一次交互内的续接。
- 结论:不管哪种,服务端内存里都不为某个客户端留东西,所以任何副本都能处理任何请求,扩容不需要粘性路由——这正是这次改动想换来的东西。
- 安全是必须主动补的一句:句柄是名字不是凭证。要用安全随机数生成、绑定到已认证的主体(按 user_id 加 handle 做键)、设过期时间,并且每次调用重新校验调用者身份。规范明确写了服务端不得把持有句柄当成身份认证。requestState 同理,它经客户端转手,是攻击者可控输入,必须 HMAC 或 AEAD 验签,并把主体、原请求标识、短过期签进去。
- 可预期的追问:多副本时 requestState 怎么办?答案是所有副本共享签名密钥即可,这仍然是无状态的——状态在客户端手里,副本只负责验签。追问二可能是「怎么保证一次性」,答案是签名只能缩小重放窗口,真要单次消费得自己在服务端加一层消费记录。
Key points
- State travels with the client: the server mints an explicit handle that later calls pass back as an ordinary tool argument
- Within one interaction, a signed opaque blob works with zero server storage; replicas just share the signing key
- A handle is not a credential: securely random, bound to the authenticated principal, expiring, re-authorized on every call
- The payoff is that any replica serves any request, so scaling needs no sticky routing and retries are cheap
答题要点
- 状态必须由客户端携带:服务端铸造显式句柄,作为普通工具参数在后续调用里传回
- 一次交互内的续接可以用签名的不透明状态串,服务端零存储,多副本共享签名密钥即可
- 句柄不是凭证:安全随机生成、绑定已认证主体、设过期,每次调用重新鉴权
- 收益是任何副本能处理任何请求,扩容不需要粘性路由,重启后重发即可
Streamable HTTP requires the Mcp-Method header to match the method in the request body. Why mirror it at all, and what breaks if the server does not validate the match?Streamable HTTP 要求 Mcp-Method 头必须和请求体里的 method 一致。为什么要抄一遍?不校验会有什么风险?
Common in ChinaCommon overseasIntermediate#transport#securityHow to reason about it · think before answering
- The real question is the second half. 'It helps gateways route' is half an answer; the interviewer is waiting for a concrete attack, which separates having read the spec from having understood it.
- Why mirror: intermediaries should not parse the body to make decisions. A load balancer routing by method, a rate limiter capping tools/call, an observability probe tagging spans — all can read a header instead of deserializing tens of kilobytes. The same applies to Mcp-Name (from params.name or params.uri) and MCP-Protocol-Version.
- Then derive the risk: if intermediaries decide on the header and the server executes on the body, there are two sources of truth. Concretely, a gateway configured as 'tools/list is unauthenticated, tools/call is authenticated' is bypassed by sending the header as tools/list and the body as tools/call. The same trick evades rate limits, audit tagging, and per-parameter regional isolation.
- Conclusion: the spec therefore requires any server that processes the body to validate the match and reject with 400 plus -32020 (HeaderMismatch). It is not pedantry — it collapses two sources of truth back into one.
- Volunteer the implementation trap: header values are visible ASCII only, so non-ASCII tool names or resource URIs use the =?base64?...?= sentinel, and the server must decode before comparing or its own check will reject valid requests. Integer values should be compared numerically, not as strings.
- Likely follow-up: should intermediaries validate too? The spec advises that any intermediary enforcing policy from mirrored headers first confirm MCP-Protocol-Version names a revision that mandates header-body validation, and otherwise reject rather than trust unvalidated headers.
分析过程 · 先想清楚再作答
- 这题的题眼在后半句。只答「方便网关路由」是答了一半,面试官等的是「不一致会怎样」——能不能自己举出攻击场景,是区分「读过规范」和「理解规范」的地方。
- 先说为什么镜像:中间层不该为了做决策去解析请求体。负载均衡想按方法分流、限流器想给 tools/call 单独设阈值、可观测探针想打标签,只看头就够了,不用把几十 KB 的 body 反序列化一遍。同理还有 Mcp-Name(取自 params.name 或 params.uri)和 MCP-Protocol-Version。
- 再推风险:既然中间层按头决策、服务端按体执行,两个事实来源就分叉了。举个具体的:网关配了「tools/list 免鉴权、tools/call 要鉴权」,攻击者把头写成 tools/list、体写成 tools/call,鉴权就被绕过去了。同样的套路可以绕限流、绕审计、绕按参数值做的地域隔离。
- 结论:所以规范规定处理请求体的服务端必须校验头体一致,不一致必须回 400 加 -32020(HeaderMismatch)。这不是格式洁癖,是把「两个事实来源」重新合并成一个。
- 实现上有个坑值得主动说:头值只能是可见 ASCII,非 ASCII 的工具名或资源 URI 要用 =?base64?...?= 哨兵格式编码,服务端必须先解码再比对,否则自己的校验会把正常请求判成不一致。整数值应当按数值比较而不是按字符串比较。
- 可预期的追问:中间层自己要不要校验?规范建议按头做策略的中间层先确认 MCP-Protocol-Version 指向的是一个要求头体校验的版本,版本更老或头缺失时应当直接拒绝,而不是信任未经校验的头值。
Key points
- Mirroring lets gateways, rate limiters, and probes route and tag without parsing the body
- Skipping validation creates two sources of truth: header tools/list with body tools/call bypasses per-method auth and limits
- The spec requires any body-processing server to validate the match and return 400 with -32020 on mismatch
- Non-ASCII values use the base64 sentinel, so decode before comparing; compare integers numerically
答题要点
- 镜像是为了让网关、限流器、探针不用解析请求体就能路由和打标签
- 不校验就有两个事实来源:头写 tools/list、体写 tools/call 可以绕过按方法配置的鉴权与限流
- 规范要求处理请求体的服务端必须校验一致性,不一致回 400 与 -32020
- 非 ASCII 值用 base64 哨兵格式,服务端必须先解码再比对;整数按数值比较
Why must an MCP server never forward the client's access token straight to a downstream API?为什么 MCP 服务端绝对不能把客户端给的访问令牌直接转发给下游 API?
Common in ChinaCommon overseasDeep dive#oauth#securityHow to reason about it · think before answering
- This probes your instinct for trust boundaries. 'It is insecure' is empty; the spec names this anti-pattern token passthrough and forbids it, so you need the three concrete failure modes.
- Set up the premise: in the authorization model an MCP server is an OAuth 2.1 resource server. It must validate that tokens were issued with itself as the audience — clients make that possible via the RFC 8707 resource parameter — and must accept only tokens valid for its own resources, accepting or transiting nothing else.
- Derive the harm by asking whose assumption breaks. First, security controls are circumvented: rate limiting, request validation, and traffic monitoring hang off 'this token was issued to me', and a token minted elsewhere makes them no-ops. Second, the audit trail breaks: the server cannot distinguish clients when the upstream token is opaque to it, downstream logs show an identity that is not the forwarding server, and a thief of a stolen token can use the server as an exfiltration proxy. Third, the trust boundary is punctured: downstream grants trust on the assumption that only the upstream service holds the token, so one compromise travels sideways.
- Conclusion: to call downstream, the server must obtain its own credential as an OAuth client, fully isolated from the token the client presented to it.
- Give the correct pattern too: for third-party access on the user's behalf, use URL-mode elicitation so the user authorizes the third party directly in a browser, and the server stores those tokens bound to the authenticated user identity. The spec requires third-party credentials never to transit the MCP client.
- Likely follow-up: how does this relate to the confused deputy? Token passthrough is the downstream consequence of failed audience validation, while the confused deputy is authorization-code hijacking caused by a proxy server combining a static client id with skipped per-client consent. Both come from a server acting for someone without confirming who that someone is.
分析过程 · 先想清楚再作答
- 这题在考安全边界的直觉。答「不安全」「会泄露」是空话;规范给这个反模式起了名字叫令牌转发(token passthrough),并明令禁止,能说出它坏在哪三处才算过关。
- 先把前提说清:MCP 服务端在授权体系里是 OAuth 2.1 的资源服务器,它必须校验收到的令牌受众就是自己(客户端靠 RFC 8707 的 resource 参数让授权服务器把受众写进令牌),并且必须只接受对自己资源有效的令牌,不得接受或转接其它令牌。
- 拆危害的角度是「谁的假设被打破了」。第一,绕过安全控制:限流、请求校验、流量监控往往挂在「这个令牌是发给我的」这个前提上,客户端拿着别处的令牌直连或经服务端转发,这些控制全空转。第二,审计链断裂:服务端分不清是哪个客户端在调(上游令牌对它可能是不透明的),下游日志里的身份又不是真正在转发的那个服务端,出事之后没人能还原现场;持有失窃令牌的人还能把服务端当成数据外泄的代理。第三,信任边界被打穿:下游是按「只有上游那个服务能拿到这个令牌」授信的,一旦某个服务被攻破,同一个令牌就能横着走。
- 结论:服务端要访问下游,就得自己作为 OAuth 客户端去拿一份属于自己的凭证,和客户端给自己的令牌完全隔离。
- 正确做法要一起说:需要代表用户访问第三方时走 URL 模式的补充输入,让用户在浏览器里直接和第三方完成授权,服务端把第三方令牌存在自己这边并绑定到已认证的用户身份。规范要求第三方凭证不得经由 MCP 客户端传输。
- 可预期的追问:那和混淆代理是什么关系?令牌转发是受众校验失败的下游后果,混淆代理是代理型服务端用静态 client id 加上跳过按客户端的同意确认造成的授权码劫持——两者都源于「服务端替别人做决定却没确认这个别人是谁」。这一条第 6 天会展开。
Key points
- An MCP server is an OAuth 2.1 resource server: it must validate that it is the token audience and must not accept or transit other tokens
- Forwarding bypasses rate limiting, request validation, and monitoring that assume audience-bound tokens
- The audit trail breaks: the server cannot identify callers, downstream sees the wrong identity, and the server can become an exfiltration proxy
- The correct pattern is for the server to obtain its own downstream credential as an OAuth client, with third-party credentials never transiting the MCP client
答题要点
- MCP 服务端是 OAuth 2.1 资源服务器,必须校验令牌受众是自己,不得接受或转接其它令牌
- 转发会绕过挂在受众上的限流、请求校验与流量监控
- 审计链断裂:服务端分不清调用方,下游看到的身份也不是真正的转发者,还可能被当成外泄代理
- 正确做法是服务端自己作为 OAuth 客户端取下游凭证,第三方凭证绝不经由 MCP 客户端
D5 Writing an MCP Client: Discovering and Calling Tools Inside Your Own Agent Loop, Multi-Server Aggregation and Name Collisions
Your client is connected to five MCP servers and two of them expose a tool called search. How do you merge them into one tool list for the model, and why can't you just prefix with the server's name?你的客户端同时连了五个 MCP 服务端,其中两个都有一个叫 search 的工具。合并成一张工具表给模型时,重名该怎么处理?为什么不能直接拿服务端名做前缀?
Common in ChinaCommon overseasIntermediate#client#tool-namingHow to reason about it · think before answering
- The screen is whether you have actually aggregated multiple servers. 'Add a prefix' is half the answer; the real question is the second half, why the server's own name will not do.
- Set the premise straight: the spec guarantees tool-name uniqueness only within a single server, and explicitly says clients or proxies that aggregate multiple servers may hit collisions and should implement a disambiguation strategy. Collisions are permitted by design, and disambiguation is the client's job.
- Now the second half: the name a server reports in serverInfo is not guaranteed to be unique across servers, and the spec says it should not be relied upon for disambiguation. The server fills it in itself, two unrelated servers may both call themselves github, and worse, it is untrusted input, so a malicious server can impersonate another. The prefix must come from the client's own configuration, a local alias the user assigns per server, with duplicate aliases rejected at startup as a configuration error.
- Then the naming mechanics. MCP allows letters, digits, underscore, hyphen and dot with a suggested 128-character limit; model APIs are usually stricter, often letters, digits, underscore and hyphen with a 64-character cap. Take the intersection, and on overflow truncate plus append a short hash — say why: truncation itself creates new collisions, and the hash restores uniqueness.
- The conclusion, and the most likely follow-up: keep a reverse map from the prefixed name back to server plus original tool name. The call sent to the server must carry the original name, since the server has never heard of the prefixed one. Never recover it by string splitting, because original names may legitimately contain underscores and truncated names cannot be split back at all.
- Likely follow-ups: is the prefix enough? No, the model chooses by description, so put the source in the description too. And what about list changes? Servers declaring listChanged send a notification, on which the client refetches and rebuilds the map, keeping in mind that churning the tool list invalidates prompt caching because the tool array sits in the cached prefix.
分析过程 · 先想清楚再作答
- 这题在筛「有没有真的聚合过多个服务端」。只答「加个前缀」能拿一半分,题眼在后半句——为什么不能用服务端自报的那个名字。
- 先把前提摆正:规范只保证工具名在**单个服务端内**唯一,并且明确说聚合多个服务端的客户端或代理可能遇到重名,应当实现一套消歧策略。也就是说重名不是异常情况,是设计上就允许的,消歧责任在客户端这一层,服务端管不着。
- 再答后半句:服务端在 serverInfo 里自报的 name **不保证跨服务端唯一**,规范明说不应当拿它来消歧。它由服务端自己填,两个不相干的服务端都叫 github 完全合法;更糟的是它是不可信输入,一个恶意服务端可以故意把自己报成别人的名字,让模型把请求发到错的地方。所以前缀必须来自客户端自己的配置——用户在配置文件里给每个服务端起的本地别名,别名重复时在启动阶段直接报错,因为那是配置错误。
- 接着说名字怎么拼。MCP 允许字母数字下划线连字符和点、长度建议 128 以内;模型 API 那边通常更严,比如只允许字母数字下划线连字符、最长 64。取交集,超长就截断并缀一段短哈希——要主动说出为什么加哈希:截断本身会制造新的重名,哈希是把唯一性补回来的。
- 结论也是最容易被追问的一条:客户端必须留一张反查表,从带前缀的名字映射回「哪个服务端 + 原来的工具名」。调用时发给服务端的必须是**原名**,服务端根本不认识带前缀的那个。绝不能靠切字符串反推,因为工具原名里本来就允许有下划线,截断过的名字更是拆不回来。
- 可预期的追问一:光靠名字前缀够不够?答不够,模型选工具看的是描述,所以还应当把来源写进描述里。追问二:工具列表变了怎么办?服务端支持 listChanged 时会发通知,客户端收到就重新拉取并重建反查表;同时注意频繁增删工具会打掉提示缓存,因为工具表在缓存前缀里。
Key points
- Uniqueness holds only within one server; collisions are expected on aggregation and the client owns disambiguation
- The prefix must come from a client-configured local alias, since the server-reported name is neither unique nor trustworthy
- Build names from the intersection of MCP and model-API charset and length limits; truncate plus a short hash on overflow
- Keep a reverse map and send the original name on calls; never split the prefixed string, as original names contain underscores
答题要点
- 唯一性只在单个服务端内成立,聚合时重名是设计允许的,消歧责任在客户端
- 前缀必须来自客户端配置的本地别名,服务端自报的 name 不保证唯一且是不可信输入
- 名字取 MCP 与模型 API 的字符集与长度交集,超长截断并缀短哈希补回唯一性
- 留一张反查表,调用时发原名;不能靠切字符串反推,工具原名里本来就有下划线
One of your MCP servers times out in production. How should your agent loop react?线上一个 MCP 服务端超时了。你的 Agent 循环应该怎么反应?
Common in ChinaCommon overseasIntermediate#client#reliabilityHow to reason about it · think before answering
- This probes engineering instinct: can you separate 'one dependency is down' from 'this turn fails'. Answering 'retry three times' just moves the problem, and the interviewer will ask what the user is staring at meanwhile.
- Split by phase first. A timeout happens at two very different moments: discovery (server/discover or tools/list) and invocation (tools/call). The correct reaction differs, and blurring them shows you have not built this.
- Discovery: wrap each server in its own try/catch, record the failure with its reason in a down list, and continue to the next server. The tool table loses a few entries but the loop still starts. Record the reason, not a boolean, because afterwards you must be able to say what is missing and why.
- Invocation: translate the failure into a tool result with isError true and feed it back to the model rather than throwing. MCP already uses isError for 'the tool failed but the protocol succeeded', so the model can switch tools or arguments; throwing kills the turn and leaves the user with no explanation.
- Then three supporting points. Every request needs a timeout, because on stdio a silent server is silent forever. Idempotency decides whether a retry is safe, and the idempotentHint annotation is a hint, not a guarantee, so writes need a client-side dedup key. And outages must be visible, surfaced in the UI or in the system prompt, or the model will behave as if the capability never existed and confidently report nothing found.
- Conclusion: the worst outcome of one server timing out should be a few missing tools plus an explicit note, never a failed turn.
- Likely follow-ups: should you add a circuit breaker? Yes, after consecutive failures mark the server unusable for a while so you stop paying a timeout every turn, with recovery by health check or reconnect on the next session. And how do you set the timeout? Per tool rather than per server, since a thirty-second analysis tool and a cache lookup should not share a threshold.
分析过程 · 先想清楚再作答
- 这题看的是工程直觉:能不能把「一个依赖挂了」和「这一轮对话失败」分开。答「重试三次」是把问题往后推了一步,面试官会立刻追问重试期间用户在等什么。
- 先分阶段。超时发生在两个完全不同的时刻:发现阶段(server/discover 或 tools/list)和调用阶段(tools/call)。两个阶段的正确反应不一样,混着答就会露怯。
- 发现阶段:逐个服务端 try/catch,失败的记进一张掉线表并继续下一个。整张工具表少几个工具,但循环照常起得来。记的必须是原因而不是一个布尔值,因为事后你要能回答少了什么、为什么少。
- 调用阶段:把失败翻译成一条 isError 为真的工具结果喂回模型,不要抛。理由是 MCP 本来就用 isError 表达「工具执行失败但协议是成功的」,模型看得见这句话就有机会换个工具或换个参数;抛出去只会把整轮对话打断,而且用户什么解释都得不到。
- 接着补三件配套的事。一是**每条请求都必须有超时**,stdio 上服务端不回你就永远不回;二是**幂等性决定能不能重试**,工具注解里的 idempotentHint 是提示不是保证,写操作的重试要靠客户端自己的去重键;三是**掉线要让用户看得见**,把掉线的服务端标在界面上或写进系统提示,否则模型会表现得像那个能力从来不存在,一本正经地说查不到。
- 结论:一个服务端超时,最坏的后果应该是少几个工具加一条明确的说明,而不是这一轮对话失败。
- 可预期的追问:要不要熔断?连续失败到阈值就把这个服务端标记为不可用一段时间,避免每一轮都白等一次超时;恢复用探活或下一次会话重连。再追问会问到超时值怎么定——按工具而不是按服务端定,一个跑三十秒的分析工具和一个查缓存的工具不该共用一个阈值。
Key points
- Split by phase: per-server try/catch during discovery with a recorded reason, and never throw during invocation
- Translate call failures into isError tool results so the model can switch tools or arguments
- Every request needs a timeout; retry safety depends on idempotency, and the annotation is a hint, not a guarantee
- Outages must be visible to user and model, otherwise silent degradation makes the model deny the capability ever existed
答题要点
- 分阶段:发现阶段逐个服务端 try/catch 记进掉线表并继续,调用阶段一律不抛
- 调用失败翻译成 isError 为真的工具结果喂回模型,让它换工具或换参数
- 每条请求必须设超时;能不能重试取决于幂等性,注解只是提示不是保证
- 掉线必须对用户和模型可见,否则会变成静默降级,模型会假装那个能力不存在
When translating an MCP tool definition into a model API's tool parameters, what is most easily lost, and what goes wrong when it is?把 MCP 的工具定义翻译成模型 API 的工具参数时,最容易丢掉的是什么?丢了会怎样?
Common in ChinaCommon overseasDeep dive#client#tool-schemaHow to reason about it · think before answering
- This checks whether you know the step is lossy. Anyone answering 'just map the field names' has probably not written a client, because several parts of an MCP tool definition have no home on the model-API side.
- List first, then consequences. Three things go missing: annotations (readOnlyHint, destructiveHint, idempotentHint), outputSchema, and title. A fourth, often overlooked, is pagination — taking only the first page of tools/list silently drops whole batches of tools.
- Consequences one by one. Without annotations the model cannot tell which tool is destructive and the client has nothing to base a confirmation prompt on, so confirmation logic must live in the client and read the annotations directly. Without outputSchema, downstream code parses natural language, and code-mode generation cannot produce accurate return types. Without title, the UI can only show a prefixed machine name.
- Volunteer the most important caveat: annotations are untrusted input. The spec requires clients to treat tool annotations as untrusted unless they come from trusted servers. readOnlyHint being true is not proof of safety, only the server's own claim, so annotations may drive whether you ask the user, never whether the caller is authorized.
- Conclusion: the right posture is to know exactly what you dropped and compensate in the client — confirmation driven by annotations, structured results validated by you, title for the UI and description for the model, and pagination followed until nextCursor disappears.
- Likely follow-ups: may you rewrite the description? Light augmentation is fine, such as prefixing the source to help disambiguate collisions, but do not rewrite the meaning, since the description is what the server author tuned and their only lever on model choice. And what if outputSchema is absent? The official guidance is to accept a generic type and move on, or extract a typed result with a fast model outside loops and validate it.
分析过程 · 先想清楚再作答
- 这题在考「你知不知道这一步是有损的」。答「字段名对一下就行」的人多半没写过客户端,因为 MCP 的工具定义里有好几样东西在模型 API 那边根本没有对应位置。
- 先列清单再讲后果。丢的主要是三样:annotations(readOnlyHint、destructiveHint、idempotentHint 这类行为提示)、outputSchema(结构化返回的形状)、title(给人看的名字)。另外还有一样常被忽略的是分页——只取 tools/list 第一页等于把后面的工具整批丢掉。
- 逐条讲后果。annotations 丢了,模型不知道哪个工具是破坏性的,客户端也就没法自动决定要不要弹确认框——所以确认逻辑必须由客户端按注解自己做,不能指望模型自觉。outputSchema 丢了,下游只能靠解析自然语言拿数据,而且做代码模式(让模型写代码调工具)时生成不出准确的返回类型。title 丢了,界面上只能显示一串带前缀的机器名。
- 这里必须主动补一句最重要的:**注解本身是不可信输入**。规范要求客户端把工具注解当成不可信的,除非来自可信服务端。readOnlyHint 为真不是「这个工具安全」的证明,它只是服务端的自我声明。所以注解可以用来决定 UI 上要不要多问一句,但不能拿它当权限判据。
- 结论:翻译这一步的正确心态是「知道自己丢了什么,并在客户端补回来」。补法是——确认与拦截由客户端按注解做、结构化返回自己校验、界面用 title 而给模型用 description、分页翻到 nextCursor 消失为止。
- 可预期的追问:description 要不要改写?可以适度加工,比如在前面缀一句来源说明帮助模型在重名时选对,但不要重写语义——描述是服务端作者调过的,也是他们唯一能影响模型选择的地方。再追问可能是「outputSchema 缺失怎么办」,官方建议先用泛型接住往下游传,真需要类型时用一个小模型做一次抽取并校验,别在循环里做。
Key points
- Annotations, outputSchema and title are lost, plus every tool past the first page if pagination is ignored
- Without annotations there is nothing to drive a confirmation prompt, so that logic must live in the client
- Annotations are untrusted input: they may drive UI prompts, never authorization decisions
- Losing outputSchema forces downstream natural-language parsing; you may prefix the description but must not rewrite its meaning
答题要点
- 丢的是 annotations、outputSchema、title,外加只取第一页时整批丢掉的工具
- annotations 丢了就没法决定要不要弹确认框,确认逻辑必须由客户端按注解自己做
- 注解是不可信输入,只能驱动 UI 提示,不能当权限判据
- outputSchema 丢了下游只能解析自然语言;description 可以缀来源但不要重写语义
D6 Security and Governance: Prompt Injection in Tool Descriptions, the Confused Deputy, Least Privilege, Audit Logs, and a Tool Allowlist
Why is an MCP tool's description untrusted input, and what protections would you build as a client author?为什么说 MCP 工具的描述是不可信输入?作为客户端作者,你会做哪些防护?
Common in ChinaCommon overseasIntermediate#prompt-injection#clientHow to reason about it · think before answering
- The screen is whether you treat the model's context as a data ingress. Answering 'filter for keywords' collapses under follow-up, because text filters do not survive paraphrase.
- Establish why it is untrusted. The description is written by the server author and lands verbatim in the tool list handed to the model, at the same trust level as your own system prompt, with no quoting, boundary, or provenance. The precondition is absurdly low: no credentials, no man in the middle, no user click, just the ability to influence text that will be read into context. Three real paths are publishing a server and waiting for installs, taking over an already-trusted server's release rights and changing one field in a patch, or a clean server whose descriptions embed database content. The second is hardest to defend, since users audit only at install time and list-changed notifications say that something changed, not which sentence.
- Fold annotations in: the spec requires clients to treat tool annotations as untrusted unless they come from trusted servers. readOnlyHint being true is not proof of safety, only the server's own claim.
- Then the defenses, and the ordering is the point: block consequences first, entry second, because every text-based defense is probabilistic while the consequence layer is deterministic.
- Consequences: require human confirmation before destructive tools and show the actual arguments (the spec recommends showing tool inputs to the user precisely to catch an innocuous-looking tool exfiltrating via its arguments); render every tool call in the UI, or the injected 'do not tell the user' genuinely works; and decide what is destructive from local policy first, using annotations only to catch extra cases, never to waive one.
- Entry: render descriptions as external data with provenance and boundary markers, escaping the markers themselves; apply the same treatment plus a length cap to tool results; and on list changes show the user a diff of the descriptions rather than a bare 'the tool list changed'.
- Likely follow-ups: can you just instruct the model to ignore instructions in descriptions? That lowers the probability but cannot guarantee, so it must not be the only line. And do tool results count? Yes, and worse, because they change every call and are larger; official guidance also notes that one server's results are untrusted input to another.
分析过程 · 先想清楚再作答
- 这题在筛「有没有把模型上下文当成一条数据入口来看」。答「加个过滤器拦关键词」会被追问到崩,因为基于文本的过滤挡不住改写。
- 先讲清为什么不可信。工具描述由服务端作者写,会原封不动进入给模型的工具表,和你自己写的系统提示处在同一个信任层级——没有引号、没有边界、没有来源标注。它的前置条件低到离谱:攻击者不需要凭证、不需要中间人、不需要用户点任何东西,只要能影响一段会被读进上下文的文本。三条现实路径是发一个服务端等人装、拿下已被信任服务端的发布权限在小版本里改一个字段、或者服务端本身干净但描述里嵌了从数据库读出来的内容。第二条最难防,因为用户只在安装时审过一遍,清单变更通知只说变了、不说哪句话变了。
- 顺手把注解也归进来:规范要求客户端必须把工具注解当成不可信输入,除非来自可信服务端。readOnlyHint 为真不是安全证明,只是服务端的自我声明。
- 然后是防护,关键是**给出顺序**:先挡后果,再挡入口。因为所有基于文本的防御都是概率性的,没有一条能保证挡住,而后果那一层是确定性的。
- 挡后果的三条:破坏性工具执行前一律向人确认,且确认框展示**实际参数**(规范建议把工具输入展示给用户,正是为了挡住工具名人畜无害但参数在外发数据这一类);界面上必须显示每一次工具调用,否则注入里那句「不要告诉用户」是真的会生效的;判据用本地策略为主、注解为辅——注解只能用来多拦一个,不能用来放行。
- 挡入口的三条:把描述当外部数据渲染,加来源标注与边界标记,并把边界符本身转义掉;工具返回同样处理,还要加长度上限;服务端清单变更时把描述的 diff 展示给用户复核,而不是只提示「工具列表变了」。
- 可预期的追问一:那能不能干脆让模型别听描述里的指令?只能降低概率,不能保证,所以它不能是唯一防线。追问二:工具返回算不算同一类问题?算,而且更严重,因为它每次都不一样、量更大;多服务端场景里官方还专门说过,一个服务端的结果对另一个服务端来说是不可信输入。
Key points
- Descriptions are author-written, land verbatim in context at system-prompt trust level, and need no credentials to exploit
- Annotations are equally untrusted: the spec says treat them as such, and readOnlyHint proves nothing
- Order matters: block consequences first with human confirmation showing actual arguments, plus visible tool calls
- At the entry, wrap descriptions and results with provenance and escaped boundary markers, and diff descriptions on list changes
答题要点
- 描述由服务端作者写、原样进上下文,和系统提示同一个信任层级,前置条件低到不需要任何凭证
- 注解同样不可信:规范要求客户端把注解当不可信输入,readOnlyHint 不是安全证明
- 防护顺序是先挡后果再挡入口:破坏性操作人工确认(展示实际参数)、界面显示每次调用
- 入口侧给描述与返回加来源标注与边界标记并转义边界符;清单变更时展示描述的 diff
How does the confused deputy attack play out in an MCP setting, and what does the spec require to prevent it?混淆代理攻击在 MCP 场景里具体是怎么发生的?规范要求怎么防?
Common in ChinaCommon overseasDeep dive#oauth#confused-deputyHow to reason about it · think before answering
- This screens for hands-on OAuth. Reciting 'a deputy tricked into using its own authority' is entry level; the interviewer wants the concrete chain as it appears in MCP.
- Fix the roles first: the vulnerable party is a proxy server, which is a server to the MCP client and an OAuth client to the third-party API. It is not compromised, it is used.
- List the four conditions that must all hold: the proxy uses a static client id with the third party; the proxy lets MCP clients register dynamically, each with its own client id; the third-party authorization server sets a consent cookie after the first approval; and the proxy performs no per-client consent before forwarding.
- Then the chain: the attacker dynamically registers a client with their own redirect_uri, sends the user a crafted authorization link, the browser carries the old consent cookie to the third party, which recognizes the static client id plus cookie and skips the consent screen, the code returns to the proxy, the proxy mints an MCP authorization code and redirects to the attacker's registered URI, and the attacker exchanges it for tokens. The user consented to nothing in this flow; the cookie came from a legitimate earlier one.
- Answer the mitigation in the spec's own terms: proxy servers MUST implement per-client consent, and that consent must happen before forwarding to the third party. Four supporting requirements: store consent keyed by user plus client_id rather than 'this user consented'; match redirect_uri by exact string with no wildcards and require re-registration on change; make state cryptographically random, single use, short lived, and set its cookie or session only after consent is approved, since setting it earlier renders the consent screen ineffective; and protect the consent page with CSRF defenses and frame-ancestors or X-Frame-Options.
- Likely follow-ups: how does this relate to token passthrough? Passthrough is the downstream consequence of failed audience validation, while the confused deputy is code hijacking from missing consent; both stem from a server deciding on someone's behalf without confirming who that someone is. And how do I know whether this applies? One test: has my server ever obtained third-party authorization on a user's behalf.
分析过程 · 先想清楚再作答
- 这题在筛 OAuth 的实战经验。能背出「混淆代理就是代理被骗着用自己的权限做事」只算入门,面试官要的是这条链在 MCP 里的具体形状。
- 先把角色摆清:出事的是**代理型服务端**——它对 MCP 客户端是服务端,对第三方 API 是一个 OAuth 客户端。它自己不是被攻破的那个,它是被利用的那个。
- 然后列四个必须同时成立的条件,少一个就打不成:代理对第三方用**静态 client id**(所有用户共用一个);代理允许 MCP 客户端**动态注册**,各自拿到自己的 client id;第三方授权服务器在用户首次同意后**设了同意 cookie**;代理在转给第三方之前**没有做按客户端的同意确认**。
- 再串攻击链:攻击者先向代理动态注册一个客户端,redirect_uri 填自己的地址;把构造好的授权链接发给用户;用户浏览器带着上次留下的同意 cookie 去第三方,第三方认出静态 client id 加 cookie,**跳过同意页**直接发授权码;授权码回到代理,代理换成 MCP 授权码,按注册时那个恶意 redirect_uri 回跳,码落到攻击者手里;攻击者拿它换令牌,冒充用户访问。**整条链上用户什么都没同意过**——那个 cookie 是他上次正常授权时留下的。
- 防法要按规范的措辞答:代理型服务端**必须**实现按客户端的同意,而且这次同意必须发生在**转给第三方之前**。配套四条:同意记录按「用户加 client id」存,不是只记「这个用户同意过」;redirect_uri 精确字符串匹配、不做通配、改了就要重新注册;state 用安全随机数、单次使用、短过期,并且**同意通过之后才落 cookie 或会话**(提前落等于同意页形同虚设);同意页要有 CSRF 防护并禁止被 iframe 内嵌。
- 可预期的追问一:这和令牌转发什么关系?令牌转发是受众校验失败的下游后果,混淆代理是同意确认缺失造成的授权码劫持,根子都是「服务端替别人做了决定却没确认这个别人是谁」。追问二:我怎么知道自己要不要管这一节?判据一句话——我的服务端有没有替用户去第三方要过授权。没有就整节不适用,有就是必须做。
Key points
- The victim is a proxy server: a server to the MCP client, an OAuth client to the third party
- Four conditions must coincide: static client id, dynamic registration, a third-party consent cookie, and no per-client consent
- The pivot is the third party skipping consent on the cookie, sending the code to the attacker's redirect_uri
- Per-client consent must precede forwarding; redirect_uri matched exactly; state single use, short lived, and stored only after approval
答题要点
- 受害者是代理型服务端:对客户端是服务端,对第三方是一个 OAuth 客户端
- 四个条件同时成立才打得成:静态 client id、允许动态注册、第三方有同意 cookie、缺少按客户端的同意
- 攻击链的关键一步是第三方认出 cookie 跳过同意页,授权码按恶意 redirect_uri 落到攻击者手里
- 必须在转给第三方之前做按客户端的同意;redirect_uri 精确匹配;state 单次短过期且同意后才落
Since the protocol is stateless, a server that needs state mints a handle for the client to carry back. What attack surface does that create, and how do you close it?2026-07-28 之后协议是无状态的,服务端要保存状态就得铸一个句柄让客户端带回来。这会带来什么新的攻击面?怎么防?
Common in ChinaCommon overseasIntermediate#statelessness#securityHow to reason about it · think before answering
- This checks whether you re-derived the threat model after the mechanism changed. Everyone knows session hijacking from the previous revision; sessions are gone now, so many assume the problem left with them. It only got renamed to state handle hijacking.
- Describe the attack in four steps: the server mints a handle for an authenticated user and returns it in a tool result; the attacker obtains or guesses it; the attacker sends it back as an ordinary tool argument; the server never checks whether the handle belongs to the caller and operates on the original user's state.
- Unpack 'obtains or guesses', because it decides where the defense goes. Guessing means the handle is predictable, such as a sequential id, a timestamp, or too little entropy. Obtaining has many paths: the handle appears in a tool result, so it enters the model context, the logs, possibly another server's view, and it can be coaxed out by a prompt injection. The assumption that handles stay secret is not available to you.
- Answer the defenses in the spec's tiers. Mandatory: servers implementing authorization MUST verify all inbound requests and MUST NOT treat possession of a handle as authentication. That is the crux, a handle is a name, not a credential. Recommended: generate handles from a secure random source, avoid predictable or sequential identifiers, and expire them. The most effective recommendation is binding: key server-side storage as user id plus handle, with the user id derived from the verified token rather than supplied by the client, and reject a handle presented by any other principal, so guessing it still buys nothing.
- Volunteer that requestState belongs to the same family: a server-signed opaque blob carried back through the client in multi round-trip requests, which the spec requires you to treat as attacker-controlled input, protect with HMAC or AEAD, verify with a constant-time comparison, and bind to the authenticated principal, an originating-request identifier, and a short expiry, covering cross-user, cross-request, and timeout replay.
- One-line conclusion: statelessness did not remove state, it moved it into the client's hands, so 'who can present it' and 'who is allowed to use it' must be judged separately.
- Likely follow-ups: does signing guarantee single use? No, it only bounds the replay window; true one-time consumption needs a server-side redemption record. And what about replicas? The data behind a handle already lives in shared storage, and requestState only needs a shared signing key, which is still stateless because nothing per client sits in a replica's memory.
分析过程 · 先想清楚再作答
- 这题在考「换了机制之后有没有重新想过威胁模型」。上一版的会话劫持大家都熟,这一版会话没了,很多人就默认问题跟着消失了——其实只是换了个名字叫状态句柄劫持。
- 先描述攻击,四步很短:服务端为已认证用户铸一个句柄并放在工具结果里返回;攻击者拿到或猜到这个句柄;攻击者把它当成普通工具参数发过来;服务端没检查这个句柄属不属于调用者,于是操作了原用户的状态。
- 拆「拿到或猜到」这一层很关键,因为它决定了防线该架在哪。猜到,说明句柄可预测(自增 id、时间戳、短随机数);拿到,路径就多了——它出现在工具结果里,而工具结果会进模型上下文、会进日志、可能被另一个服务端看到,也可能被一次提示注入骗着吐出来。所以「句柄不会泄漏」这个假设不能要。
- 防线按规范分三层答。硬性的:实现了授权的服务端**必须**校验所有入站请求,并且**绝不能**把持有句柄当成身份认证——这是整题的题眼,句柄是名字不是凭证。应当层:用安全随机数生成,避免可预测或连续的标识,并设过期。最管用的一层也是应当:**在服务端把句柄绑定到已认证的主体**,比如存储的键做成「用户 id 加句柄」,用户 id 从校验过的令牌里取而不是客户端传,别的主体拿着同一个句柄来就查不到。这样即使猜中也冒充不了别人。
- 然后主动把 requestState 归到同一类:它是多轮请求里由服务端签发、经客户端转手带回的不透明状态,规范要求把它当成攻击者可控输入,用 HMAC 或 AEAD 做完整性保护、验签用定长比较,并把认证主体、原请求标识、短过期一起签进去,分别挡跨用户、跨请求和超时三种重放。
- 结论一句话:无状态没有消灭状态,只是把状态挪到了客户端手里,于是「谁能出示它」和「谁有权用它」必须被分开对待。
- 可预期的追问一:签名能不能保证一次性?不能,签名只缩小重放窗口,真要单次消费得在服务端加一层消费记录。追问二:多副本部署怎么办?句柄背后的数据本来就在共享存储里,requestState 只需要各副本共享签名密钥——这仍然是无状态的,因为服务端内存里没有为某个客户端留东西。
Key points
- The new surface is state handle hijacking: anyone who obtains or guesses a handle can act on another user's state
- Handles surface in tool results, model context and logs, so secrecy is not a safe assumption
- Mandatory: verify every inbound request and never treat possession of a handle as authentication
- Use secure randomness, expiry, and server-side binding keyed by principal plus handle; requestState needs signing bound to principal and a short expiry
答题要点
- 新攻击面叫状态句柄劫持:拿到或猜到句柄的人可以操作别人的状态
- 句柄会出现在工具结果、上下文与日志里,不能假设它不泄漏
- 硬性要求:必须校验所有入站请求,绝不能把持有句柄当成身份认证
- 做法:安全随机、设过期、按「主体加句柄」在服务端绑定;requestState 同理,验签并签进主体与短过期
D7 Productionizing and Retrospective: Writing Evals for Tools, Versioning, Publishing to npm and a Registry, Observability, and a Capstone Project
How do you evaluate whether an MCP tool is any good, and what categories of test cases would you design?怎么评估一个 MCP 工具做得好不好?你会设计哪几类测试用例?
Common in ChinaCommon overseasIntermediate#eval#toolingHow to reason about it · think before answering
- The screen is whether you have shipped tools. 'Write unit tests' answers the wrong question: unit tests check that given arguments produce the right output, while the first thing to break on an MCP tool is the model not selecting it at all, so the arguments never reach your function.
- Define the target: an eval measures selection accuracy, meaning given a user utterance and a tool list, does the model pick the right tool. Correctness of the tool itself belongs to unit tests, and the two layers should not be blurred.
- Then the three categories, which is the direct answer. Happy path: unambiguous intent, such as 'find me the release process doc'. Edge: intent carried by semantics rather than keywords, such as 'what does release-process say', which has no verb cue and only a slug-shaped token, and is most often misread as a search. Traps: cases where nothing should be called, such as 'thanks, no need to look it up', 'how do you say document in English', and 'what can you do'. I usually weight them four, three, three.
- Stress that the third category is the dividing line: an eval set of only happy paths reports a comfortable hundred percent while measuring nothing about over-triggering, which is what most production complaints actually are, and over-triggering has side effects.
- Then the assertion, where the classic bug lives: treating an expected value of null as 'anything goes', which makes negatives permanently green. The correct assertion judges both directions. I also rerun the whole set with a deliberately wrong selector that always picks the same tool and require every negative to fail, which validates the assertion rather than the selector.
- Finally the engineering constraints: one command, under a minute, on a cheap small model, because a slow eval is no eval, since whoever edits a description will not wait. And report per category rather than one number: happy-path drops mean a vague description, edge drops mean the sentence distinguishing two similar tools is missing, trap drops mean the description over-claims.
- Likely follow-ups: when do you run it? On any change to descriptions, schemas, or the tool set, wired into CI. And what if the model changes? The eval set is a cross-model asset, so you rebaseline on a model switch, which is part of why it pays for itself.
分析过程 · 先想清楚再作答
- 这题在筛「有没有真的上线过工具」。答「写单元测试」是答错了赛道——单元测试测的是给定参数输出对不对,而 MCP 工具最先出问题的地方是模型压根没选它,参数根本到不了你的函数。
- 先把要评估的对象说清:评估评的是**选中率**,也就是给一句用户的话和一张工具表,模型会不会选中该选的那个。工具本身的正确性归单元测试,两层不要混。
- 然后给三类用例,这是本题的正面回答。正例:意图明确时选得中,比如「帮我搜一下发布流程文档」。边界:意图靠语义而不是关键词,比如「release-process 这篇讲了什么」——没有任何动词提示,只有一个像 slug 的词,最容易被误判成搜索。诱导误选(负例):不该调的时候一个都不调,比如「谢谢,不用查文档了」「文档这个词英文怎么说」「你都能干什么」。占比我一般给四三三。
- 第三类是分水岭,要主动强调:只有正例的评估集会给你一个 100% 的假象,它测不出过度触发,而线上大多数投诉恰恰是过度触发——用户随口一句否定,助手转头就去干了,还带副作用。
- 接着讲断言,这里有个最常见的写法错误:expected 为 null 的用例被当成「随便都行」,于是负例永远绿。正确的断言两个方向都判:期待某个工具时选中它才算过,期待不调用时什么都不选才算过。我会额外拿一个「总是选同一个工具」的假选择器再跑一遍,要求负例全部失败——这验的不是选择器,是我的断言真的在起作用。
- 最后是工程约束:评估集要能一条命令跑完,一分钟以内,用便宜的小模型跑。跑得慢的评估集等于没有,因为改描述的人不会等。结果也不要只看总分,按三类分开看才有行动价值:正例掉了说明描述写糊了,边界掉了说明缺了区分相似工具的那句话,负例掉了说明描述写得太热情。
- 可预期的追问:什么时候跑?描述、schema、工具增删这三类改动都必须跑,把它挂进 CI。再追问会问到「模型换了怎么办」,答案是评估集是跨模型的资产,换模型时先跑一遍拿到基线,这也是它值得投入的原因之一。
Key points
- Evals measure selection accuracy, not tool correctness; the latter is unit-tested and the layers must not blur
- Three categories are mandatory: happy path, edge, and traps, weighted roughly four three three
- Assert both directions: an expected null must mean nothing was called, and validate the assertion with a deliberately wrong selector
- One command, under a minute, scored per category, and triggered by any description or schema change
答题要点
- 评估评的是选中率,不是工具正确性;后者归单元测试,两层不能混
- 三类用例缺一不可:正例、边界、诱导误选,建议四三三
- 断言两个方向都判:期待 null 时必须什么都不选;再用故意选错的选择器验证断言本身
- 一条命令一分钟内跑完,按类别分开看分数,描述与 schema 改动必须触发
When versioning an MCP server, what counts as a breaking change, and why is editing a tool description more dangerous than renaming a field?给一个 MCP 服务端做版本管理时,什么样的改动算破坏性变更?为什么说改一句工具描述比改一个字段名更危险?
Common in ChinaCommon overseasIntermediate#versioning#toolingHow to reason about it · think before answering
- The first half is common knowledge; the second half is the filter. Anyone who can explain that the description is part of the interface has actually operated tools in production.
- Give the four conventional categories, which the official guidance on extension evolution defines directly: removing or renaming fields, changing field types, altering the semantics of existing behavior, and adding new required fields. All four make existing implementations fail or behave incorrectly.
- Then add the MCP-specific fifth: editing a tool description. The description is the model's only basis for selecting a tool, so changing a sentence is a behavior change. Concretely, a server shortened 'search the internal doc library by keyword' to 'search documents', shipped no code changes, and two weeks later users reported the assistant had gotten dumber, because the model stopped choosing it.
- Now the crux, why it is more dangerous. Renaming a field makes callers fail loudly and someone finds you within five minutes. Editing a description raises nothing: unit tests pass, the server reports zero errors, logs are clean, and selection accuracy quietly drops a few points, surfacing weeks later as an undiagnosable 'it got worse'. Loud failures are far easier than silent degradation, so description changes need the stronger gate.
- Name the gate: an eval set with all three case categories, run on every description change, blocking the merge when selection accuracy drops. That is also why the eval must run fast from one command.
- Add the compatibility techniques: new fields must be optional, since old clients will not send them; to change semantics, introduce a new tool name rather than mutating in place, mark the old one deprecated with the replacement named in its description, and remove it only after a grace period, because you cannot know how many prompts hardcode that name.
- Likely follow-ups: how does the protocol version itself? MCP uses YYYY-MM-DD marking the last breaking change, backwards-compatible updates do not bump it, and deprecated features stay for at least twelve months before removal. And can you fix metadata after publishing to the registry? No: versions are unique and immutable once published, a typo costs a new version, and range-looking version strings are rejected outright.
分析过程 · 先想清楚再作答
- 这题的前半句是常识题,后半句才是筛子。能把「描述也是接口」说明白的人,基本都真的运维过工具。
- 先答常规的四类,官方在讲扩展演进时给过定义,直接可用:删除或重命名字段、改字段类型、改变现有行为的语义、新增必填字段。这四类的共同点是会让已有实现直接失败或者行为不正确。
- 然后补 MCP 特有的第五类:**改工具描述**。理由是描述是模型选工具的唯一依据,改一句描述就是一次行为变更。举个具体的:某个服务端把描述从「在内部文档库里按关键词搜索」精简成「搜索文档」,代码一行没动,两周后用户反馈助手变笨了——模型不再选它了。
- 接着讲为什么它**更**危险,这是题眼:改字段名会让调用方立刻报错,错误是响亮的,五分钟内就有人来找你;改描述不报任何错,单元测试全绿、服务端零错误、日志干净,它只会让选中率悄悄掉几个点,最后以「最近变笨了」这种没法定位的形式浮上来。响亮的错误比安静的退化好处理得多,所以描述改动反而更需要闸门。
- 闸门是什么要说出来:一份三类齐全的评估集,描述改了必须跑一遍,选中率掉了就别合。这也是评估集要能一条命令快速跑完的原因。
- 顺带把兼容技巧补上:加字段要加成可选的,因为老客户端不会传新参数;要改语义就换个工具名而不是原地改,旧的标弃用、描述里写明替代品、留一段时间再删,因为你不知道多少人的提示词里写死了那个名字。
- 可预期的追问一:协议自己怎么做版本?MCP 用 YYYY-MM-DD,标的是最后一次破坏性变更的日期,向后兼容的改动不递增版本;弃用的特性至少保留十二个月才可能移除。追问二:发到注册表之后怎么改?改不了——版本号唯一且发布后元数据不可变,打错字只能往上加一个版本,而且范围形式的版本号会被直接拒收。
Key points
- The four usual categories: removing or renaming fields, changing types, altering semantics, adding required fields
- The MCP-specific fifth is editing a tool description, since the description is the model's only selection signal
- It is more dangerous because nothing fails: tests pass and logs are clean while selection accuracy silently drops
- The gate is the eval set; new fields must be optional, and semantic changes need a new tool name plus a deprecation window
答题要点
- 常规四类:删除或重命名字段、改字段类型、改变现有行为语义、新增必填字段
- 第五类是 MCP 特有的:改工具描述,因为描述是模型选工具的唯一依据
- 它更危险是因为不报错:测试全绿、日志干净,只有选中率悄悄下滑,几周后才浮上来
- 闸门是评估集;加字段要可选,改语义要换新工具名并给旧的一段弃用期
A user reports that one of your MCP tools is 'always getting it wrong' in production. In what order do you investigate?线上有人反馈某个 MCP 工具「总是调不对」。你按什么顺序排查?
Common in ChinaCommon overseasIntermediate#observability#debuggingHow to reason about it · think before answering
- This tests ordering, not breadth. Anyone who opens with logs and stack traces gets asked how they know the problem is server-side at all.
- Step zero is translating 'getting it wrong' into three mutually exclusive symptoms, without which everything after is guesswork: it was never called, it was called with wrong arguments, or it was called correctly and returned the wrong thing. Asking whether it did nothing or did the wrong thing, or simply checking whether a call was logged, separates them.
- Each symptom has its own path. Never called means the description is at fault: run the eval set and see whether happy paths or edges dropped, since happy-path drops mean a vague description and edge drops mean the sentence distinguishing similar tools is missing. Wrong arguments means the schema is at fault: ambiguous field names, unstated formats, wrong required markers. The signal is a persistently high tool-execution error rate, which usually means the schema is unclear rather than the model being dumb. Only a wrong result is a code problem, and only then do unit tests and logs matter.
- State the metric ordering too: error rate first, selection accuracy second. A normal error rate with unhappy users almost always means the tool is not being chosen; a spiking error rate sends you to the code and upstream. Watch P95, not the mean, because a remote tool degrading from 200 milliseconds to 8 seconds barely moves an average.
- Volunteer two commonly missed causes. Aggregation collisions: the client is connected to several servers, two tools share a name, and the model picked the other one, so your server was never called and investigating it will never find anything. And version or caching: list results carry ttlMs cache hints, so the client may hold a stale tool list, and refresh depends on a listChanged notification.
- Conclusion: the chain has four links, description, schema, aggregation and caching, and implementation. Walk it in the order the model sees it, because the earliest links produce no error logs and are therefore the ones people skip.
- Likely follow-up: how do you keep evidence? Emit one structured log per call with tool name, an argument digest plus field names, the isError flag, duration, and whether a human confirmed the call, that last column being the only way to distinguish user intent from the model acting on its own.
分析过程 · 先想清楚再作答
- 这题考的是排查的**顺序**,不是知识点的多少。上来就贴日志和堆栈的人会被追问「你怎么知道问题在服务端」。
- 第零步是把「调不对」翻译成三种互斥的现象,这一步不做后面全是猜:一是**没被调**(模型压根没选这个工具);二是**调了但参数错**;三是**调了参数也对,但结果不对**。问一句「那次它是没动,还是动了但做错了」,或者直接去日志里看有没有这条调用记录,就能分开。
- 对应三条不同的路。没被调,问题在**描述**:去跑评估集,看正例还是边界掉了;正例掉说明描述写糊,边界掉说明缺了区分相似工具的那句话。参数错,问题在 **schema**:看字段名是不是有歧义、描述里有没有写清格式、必填项是不是标对了;这类问题的信号是错误率里工具执行错误持续偏高——那通常不是模型笨,是 schema 没说清。结果不对才是代码问题,这时候才轮到单元测试和日志。
- 指标层面的顺序也说一下:**先看错误率,再看选中率**。错误率正常但用户说不好用,八成是选不中;错误率飙了才去看代码和上游。耗时看 P95 不看平均值,远程服务端上一个工具从 200 毫秒退化到 8 秒,平均值可能只动一点点。
- 还有两条容易被忽略但很常见的原因,要主动提。一是**聚合冲突**:客户端连了多个服务端,两个工具重名,模型选中的是另一个服务端的那个——这时候「你的工具」根本没被调,查你的服务端永远查不出来。二是**版本或缓存**:列表结果带 ttlMs 缓存提示,客户端可能拿着旧的工具清单;工具清单变了要靠 listChanged 通知才会重新拉。
- 结论:这条链上有四个环节——描述、schema、聚合与缓存、实现。**按模型看得见的顺序从前往后查**,因为越靠前的环节越不产生错误日志,也就越容易被跳过。
- 可预期的追问:怎么留证据?每次调用记一条结构化日志,字段里要有工具名、参数摘要与字段名、是否 isError、耗时、以及这次调用有没有经过人工确认;最后那一栏是事后区分「用户授意」和「模型自作主张」的唯一依据。
Key points
- First split 'getting it wrong' into never called, wrong arguments, or wrong result; the split decides where to look
- Never called points at the description and the eval set; wrong arguments at the schema; only a wrong result at the code
- Check error rate before selection accuracy, and read P95 rather than the mean
- Do not miss aggregation collisions, where another server's same-named tool was chosen, or a stale cached tool list
答题要点
- 先把「调不对」分成没被调、参数错、结果错三种互斥现象,再决定查哪里
- 没被调查描述并跑评估集;参数错查 schema;结果错才轮到代码与日志
- 指标顺序是先错误率再选中率;耗时看 P95 不看平均值
- 别漏掉聚合重名(选中的是别的服务端的同名工具)和工具清单缓存这两类原因
Agent Skills in 7 Days: Turn Experience Into Reusable Capability
D1 What Skills Are: the SKILL.md Spec, Directory Layout, and Three-Stage Progressive Disclosure
What problem do Agent Skills solve, and how are they different from putting every convention into one big instruction file?Agent Skills 解决的是什么问题?它和把所有规范写进一个大的提示词文件有什么区别?
Common in ChinaCommon overseasBasic#agent-skills#context-engineeringHow to reason about it · think before answering
- The discriminator is whether you say on demand. Answering skills are reusable prompts says nothing, because that is equally true of a prompt template.
- Start with the split: tools fill a capability gap the model cannot cross on its own; skills fill an experience gap where the model can do the task but not the way your team does it.
- Then the mechanism: a persistent instruction file enters context in full every session, while a skill exposes only name and description until something matches and its body is loaded.
- Quantify the cost: twenty conventions at six thousand tokens of system prompt bill three hundred thousand tokens over a fifty-turn session, and the attention dilution costs more than the money.
- Close with the rule of thumb interviewers want: if the guidance applies every single time, it belongs in the persistent instruction file; otherwise make it a skill.
- Expected follow-up: what about prompt templates? The difference is who chooses. You pick a template; the model picks a skill by reading descriptions.
分析过程 · 先想清楚再作答
- 这题的区分度在你有没有说出「按需」两个字。只答「skill 是可复用的提示词」的人,等于没答,因为那句话对提示词模板同样成立。
- 先给分工:工具补的是能力缺口,模型本来做不到的事;技能补的是经验缺口,模型做得到但不知道你们这儿怎么做。这一刀切下去,后面的论证才站得住。
- 再给机制差异:常驻指令文件每次会话全量进上下文,skill 平时只露 name 与 description,命中才展开正文。前者的成本是固定的,后者的成本是按需的。
- 接着算代价:二十条规范写满六千 token 的系统提示,五十轮会话要重复计费三十万 token;更贵的是注意力被不相干的规则稀释,做第三件事时被第十七条干扰。
- 最后给判据,这是面试官真正想听的一句:这条经验是不是每次都用得上?是就写进常驻指令文件,不是就做成 skill。
- 可预期的追问是「那提示词模板呢」。答案是谁来挑:模板是你手动选的,skill 是模型读着 description 自己选的,触发权在模型手里。
Key points
- Tools close capability gaps, skills close experience gaps. Do not blur the two.
- A persistent instruction file costs the same tokens every turn; a skill body only enters context when it matches.
- Dumping unrelated conventions into the system prompt both costs money and dilutes attention.
- The test is whether the guidance applies every time: if yes it stays resident, if no it becomes a skill.
- Unlike a prompt template, a skill is selected by the model itself from its description.
答题要点
- 工具补能力缺口,技能补经验缺口,这是两件事,不要混着答。
- 常驻指令文件成本固定且每轮重发,skill 的正文只在命中时才进上下文。
- 把不相干的规范全塞进系统提示,除了花钱还会稀释注意力,让模型被无关规则干扰。
- 判据是「是不是每次都用得上」:是就常驻,不是就做成 skill。
- 和提示词模板的关键差别是触发权在模型手里,靠的是 description。
What does each of the three progressive disclosure stages load, and why not just load every skill up front?渐进式加载的三个阶段分别加载什么?为什么不能一次性把所有 skill 全加载进去?
Common in ChinaCommon overseasIntermediate#agent-skills#progressive-disclosureHow to reason about it · think before answering
- This tests both recall precision and engineering sense. Naming the three stages is not enough; say which fields and which files each stage pulls in.
- Order them by granularity: stage one loads only name and description, roughly fifty to a hundred tokens per skill; stage two loads the full SKILL.md body, recommended under five thousand tokens and five hundred lines; stage three loads individual scripts, references and assets.
- Answer the why with a number: twenty skills at three thousand tokens of body plus reference files is well over a hundred thousand tokens, past many context windows, and resent every turn. Progressive loading lands around ten thousand.
- Add the deeper reason: what you save is window space, not just money, and that space belongs to the actual task.
- Expected follow-up: how does stage three fire? The body must state the loading condition. See the references folder is useless; read the error-code reference when the API returns a non-200 hands the timing to the model.
分析过程 · 先想清楚再作答
- 这题在考你对机制的记忆精度,同时也在考工程感。只背出三个阶段的名字拿不到分,要说出每一阶段加载的**是哪些字段、哪些文件**。
- 拆法很简单,按加载的粒度从粗到细数:阶段一只加载 name 与 description,量级是每个 skill 五十到一百个 token;阶段二加载整份 SKILL.md 正文,建议不超过五千 token 与五百行;阶段三按文件粒度加载脚本、引用与资源。
- 回答「为什么不全加载」时给一个具体的数:二十个 skill 各三千 token 的正文加上引用文件,全量是十几万 token,超过很多模型的窗口,而且每一轮都要重发。渐进式加载后总量落在一万上下。
- 补一条更本质的理由:省下来的不只是钱,是窗口位置。腾出来的空间要留给真正在做的这件事的代码和数据,这就是上下文工程的核心取舍。
- 可预期的追问是「阶段三怎么触发」。答案是正文里必须写明读取条件——写「细节见 references 目录」等于没写,写「接口返回非 200 时读 references 里的错误码文件」才真正把时机交给了模型。
Key points
- Discovery: only name and description, about fifty to a hundred tokens per skill.
- Activation: the full SKILL.md body, ideally under five thousand tokens and five hundred lines.
- Execution: individual files from scripts, references or assets, loaded one at a time on demand.
- Loading everything up front blows the window and is resent every turn; progressive loading cuts it to roughly a tenth.
- Stage three only fires if the body spells out which file to read under which condition.
答题要点
- 阶段一发现:只加载 name 与 description,每个 skill 约五十到一百 token。
- 阶段二激活:读入完整 SKILL.md 正文,建议不超过五千 token 与五百行。
- 阶段三执行:按需读取 scripts、references、assets 里的单个文件,不是整目录倒进来。
- 全量加载会撑爆窗口且每轮重发,渐进式加载能把量级压到十分之一左右。
- 阶段三能不能被触发,取决于正文有没有写清「什么条件下读哪个文件」。
What hard constraints does the spec put on the name and description fields, and why is name so tightly constrained?SKILL.md 的 name 与 description 有哪些硬性约束?规范为什么要把 name 卡得这么死?
Common in ChinaCommon overseasIntermediate#agent-skills#specHow to reason about it · think before answering
- It looks like spec recall, but the real question is the why. Listing the constraints is a pass; explaining which engineering problem they prevent is the differentiator.
- Name has five constraints: one to sixty-four characters, lowercase letters digits and hyphens only, no leading or trailing hyphen, no consecutive hyphens, and it must match the parent directory name.
- Description has two: one to one thousand twenty-four characters, and it must convey both what the skill does and when to use it.
- The reason name is strict: it is the skill's identity across the ecosystem, feeding directory lookup, namespacing, slash-command invocation and collision precedence. One casing mismatch becomes an installed but uncallable skill.
- Mention the real-world wrinkle: many clients deliberately relax the name-matches-directory rule and only warn, so a skill can work locally and vanish under a stricter implementation.
- Expected follow-up: what if the description runs to a thousand characters? You pay for it every session. Twenty maxed-out descriptions cost eight thousand tokens of catalog, so shorten the text rather than dropping skills.
分析过程 · 先想清楚再作答
- 这题看着像背规范,其实题眼在后半句「为什么」。能把约束背全只算及格,能说出这些约束是为了解决什么工程问题才是加分项。
- 先把 name 的五条约束数完:长度一到六十四个字符、只能用小写字母数字和连字符、不能以连字符开头或结尾、不能有连续两个连字符、必须与父目录名一致。
- 再给 description 的两条:长度一到一千零二十四个字符;内容上要同时说清做什么和什么时候用,而不是只说做什么。
- 解释「为什么卡这么死」:name 是这个 skill 在整个生态里的唯一标识,要拼进目录名、命名空间、斜杠命令,还要在两个 skill 撞名时用来判优先级。任何一处大小写或分隔符不一致,都会变成一个很难查的「装了却调不到」。
- 补一个真实的坑:很多客户端在实现时故意放宽了「name 等于目录名」这条,不一致只打警告仍然加载。于是你本地一切正常,换个严格实现就整个消失。
- 可预期的追问是「description 写到一千个字符会怎样」。答案是它每次会话都要付一遍,二十个 skill 都写满上限,光目录就要八千 token,这时候该做的是把描述写短而不是删 skill。
Key points
- Name: one to sixty-four characters, lowercase alphanumerics and hyphens, no leading or trailing hyphen, no double hyphens, must equal the directory name.
- Description: one to one thousand twenty-four characters, stating both what it does and when to use it.
- Name is strict because it is the skill's identity for lookup, namespacing, invocation and collision precedence.
- Many clients validate name leniently, so working locally does not guarantee working elsewhere.
- The description is a fixed per-session cost, so keep it as short as it can be while still triggering.
答题要点
- name:一到六十四字符、小写字母数字与连字符、首尾不能是连字符、不能有连续连字符、必须等于父目录名。
- description:一到一千零二十四字符,必须同时说清做什么与什么时候用。
- name 卡死是因为它是唯一标识,要参与目录查找、命名空间、命令调用与撞名优先级。
- 很多客户端对 name 做宽松校验,本地能跑不代表换个客户端也能跑。
- description 是每次会话都要付的固定开销,能短则短。
D2 Writing Your First Skill: How to Write description's Trigger Words, How to Layer the Structure, How to Install It Into a Client
What goes wrong when a skill description is too broad, and what goes wrong when it is too narrow? How do you find the middle?skill 的 description 写得太泛会怎样?太窄又会怎样?你怎么找到中间那个点?
Common in ChinaCommon overseasIntermediate#agent-skills#skill-descriptionHow to reason about it · think before answering
- The hinge word is cost. Saying too broad misfires and too narrow never fires just restates the question; the interviewer wants to know what a misfire actually costs.
- Give three layers of cost for over-broad descriptions: the body wastes context, its instructions interfere with the current task, and once the model has committed to one skill it is less likely to reach for the right one. One over-broad skill degrades the whole library.
- For too narrow: it only fires when the user phrases things exactly as you imagined, and real users never do. Such a skill is usually not bad, it is simply never exercised, so you never learn that it is bad.
- Give the middle as a procedure, not a feeling: cover phrasings rather than keywords, add a boundary clause that excludes adjacent capabilities, then measure trigger rate against positives and near-miss negatives and revise from the data.
- Add the often-missed fact that agents typically only consult skills for tasks beyond what they handle alone, so a trivially easy task will not trigger no matter how well the description matches.
- Expected follow-up: how do you avoid overfitting when revising? Never paste the failing query verbatim; generalize to the category it represents, and hold out a validation split.
分析过程 · 先想清楚再作答
- 这题的题眼在「代价」两个字。只说「太泛会误触发、太窄会不触发」是把题目复述了一遍,面试官等的是后面那句:误触发到底损失了什么。
- 先说太泛的代价,而且要说满三层:这个 skill 的正文白占了上下文位置;它的指令会干扰当前任务;更麻烦的是模型一旦选定了一个 skill,就更不容易再去选真正对的那个。**一个太泛的 skill 会拖累整个技能库**,这一句是拿分点。
- 再说太窄的代价:它只在用户按你预想的说法提问时才触发,而真实用户几乎不会那样说话。太窄的 skill 通常不是不好用,是根本没被用过,所以你连它不好用都不知道。
- 找中间点的方法要给成一套动作而不是感觉:写覆盖多种说法而不是多个关键词,末尾补一句边界排除相邻能力,然后用一组正例加近似负例把触发率量出来,按结果改描述。
- 补一个容易被忽略的事实:有些任务简单到模型觉得自己就能干,这时候描述写得再匹配也不会触发。判断描述好不好之前,先确认这个任务值不值得一个 skill。
- 可预期的追问是「改描述时怎么避免过拟合」。答案是不要把失败查询的原话抄进描述,要归纳出它代表的那一类说法,并留一部分查询不参与优化、只用来验证。
Key points
- Three costs of over-broad: wasted context, interference with the current task, and crowding out the correct skill.
- Over-narrow means it never fires, which hides the problem rather than surfacing it.
- Cover phrasings rather than keywords, and add a closing boundary clause that excludes adjacent capabilities.
- Measure trigger rate with positives and near-miss negatives, then revise from the data.
- A task simple enough for the model alone will not trigger any skill; that is not a description problem.
答题要点
- 太泛的三层代价:占上下文、干扰当前任务、挤掉真正该用的那个 skill。
- 太窄的代价是根本没被触发过,问题被掩盖,你连它好不好用都测不出来。
- 写法上覆盖「多种说法」而不是「多个关键词」,末尾补一句边界排除相邻能力。
- 用正例加近似负例量出触发率,按数据改描述,不靠手感。
- 任务本身太简单时不会触发任何 skill,这不是描述的问题。
What belongs in a skill body and what does not, and why must the gotchas stay in SKILL.md rather than move to a reference file?skill 的正文应该写什么、不应该写什么?为什么「坑」那一段必须留在 SKILL.md 里而不是挪到引用文件?
Common in ChinaCommon overseasIntermediate#agent-skills#skill-authoringHow to reason about it · think before answering
- This separates people who have written skills from people who have read about them. The untested answer is write clear steps; the tested answer starts with a test.
- The test is one sentence: would the model get this wrong without this line? If not, cut it. Explaining what a PDF is only dilutes attention.
- Three things belong: project-specific conventions, non-obvious edge cases, and which tool or API to use. All three are absent from the model's general knowledge.
- Call out output format specifically: a concrete template beats prose, because models pattern-match against structures far better than they parse a described format.
- Gotchas cannot move because of ordering: the model must know a trap exists before it will look it up. Putting them in a reference file assumes it can predict a collision it has not hit yet.
- Expected follow-up: what does belong in references? Long material whose need has a clear trigger condition, and the body must state that condition, such as read the error-code file when the API returns a non-200.
分析过程 · 先想清楚再作答
- 这题考的是你有没有真写过 skill。没写过的人会答「写清楚步骤」,写过的人会先给一条判据。
- 判据只有一句:**不写这一条,模型会不会做错?** 不会就是废话,删掉。解释什么是 PDF、什么是数据库迁移,模型本来就知道,写进去纯粹在稀释注意力。
- 该写的三类是:项目特有的约定、非显然的边界情况、以及指定用哪个工具或接口。这三类的共同点是模型的通用知识里没有。
- 输出格式那一段要单独强调:给模板比用文字描述可靠,因为模型对具体结构做模式匹配的能力远强于读一段散文式的格式说明。
- 「坑」为什么不能挪走,答案是一个先后顺序问题:**模型得先知道有坑,才会去查坑**。放进引用文件就要求它在还没撞上的时候预判自己会撞上,这个前提不成立。引用文件适合放「我知道会用到,只是现在还不需要」的材料。
- 可预期的追问是「那什么该挪进 references」。答案是长、且用不用得上有明确判断条件的材料,并且正文里必须写出那个条件,比如「接口返回非 200 时读错误码文件」。
Key points
- The test: would the model get this wrong without the line? If not, delete it.
- Include project conventions, non-obvious edge cases, and the specific tool or API to use.
- Give a template for output format instead of describing it in prose.
- Gotchas stay in the body because the model must know a trap exists before looking it up.
- References hold long material, and the body must state the condition for loading each one.
答题要点
- 判据是「不写这一条模型会不会做错」,不会就删。
- 该写:项目特有约定、非显然的边界、指定的工具与接口。
- 输出格式给模板,不要用文字描述格式。
- 坑必须留在正文,因为模型要先知道有坑才会去查坑。
- 引用文件放长材料,且正文必须写出「什么条件下读它」。
How should a client resolve a name collision between a project-level and a user-level skill, and why do clients differ here?项目级和用户级的 skill 同名时该怎么处理?为什么各家客户端在这一点上会有不同的选择?
Common in ChinaCommon overseasDeep dive#agent-skills#client-integrationHow to reason about it · think before answering
- It looks like trivia but it tests whether you have actually installed skills. There is a real disagreement here, and naming it marks you as someone who has hit it.
- Start with the common convention: project-level overrides user-level, because configuration closer to the code at hand is more specific. Within one scope, first-found or last-found are both acceptable as long as you pick one and stay consistent.
- Then the divergence: Claude Code documents enterprise, then personal, then project, so personal wins over project, so that a cloned repository cannot silently shadow the skill you configured yourself.
- Explain the trade-off, which is where the marks are: project-first buys automatic team conventions, user-first buys protection from hijacking by an unfamiliar repository.
- Bring in trust: project-level skills can arrive with a freshly cloned repository and inject instructions into your session, which is why most clients gate them behind a folder-trust check.
- Expected follow-up: how do you notice a collision? Clients normally log a shadowed-skill warning, and implementers should record diagnostics rather than dropping the skill silently.
分析过程 · 先想清楚再作答
- 这题看起来是细节题,实际在考你有没有真的装过、有没有踩过。标准答案背后有一个分歧,能说出分歧的人一眼就是实操过的。
- 先给通行约定:跨客户端的普遍做法是**项目级压过用户级**,理由是离手头这份代码越近的配置越具体,理应赢。同一作用域内两个目录撞名,先找到还是后找到都行,但必须固定一种并保持一致。
- 再给分歧:具体客户端可以有自己的层级。Claude Code 的文档给出的顺序是企业级、个人级、项目级由高到低——**个人级压过项目级**,理由是不希望一个仓库带进来的 skill 悄悄覆盖掉你自己配的同名 skill。
- 把两种设计的取舍讲清楚,这是本题真正的区分度:项目优先换来的是「团队约定自动生效」,用户优先换来的是「不被陌生仓库劫持」。它们各自解决的是不同的风险。
- 顺势带出信任问题:项目级 skill 可能来自一个你刚 clone 的陌生仓库,等于让它往你的会话里注入指令。所以多数客户端把项目级加载挂在「信任这个目录」的开关后面。
- 可预期的追问是「撞名了怎么发现」。答案是客户端一般会打一条被遮蔽的警告,那条日志是排查的第一现场;实现方也应该在这种时候记录诊断信息而不是静默丢弃。
Key points
- The common convention is project over user, with a fixed, consistent rule inside a single scope.
- Clients may differ: Claude Code documents enterprise, then personal, then project.
- Project-first gives automatic team conventions; user-first prevents hijacking by an unfamiliar repository.
- Project-level skills can come from untrusted repositories, so gate them behind a folder trust check.
- Log a warning and record diagnostics on a collision instead of silently shadowing.
答题要点
- 通行约定是项目级压过用户级,同作用域内固定一种顺序并保持一致。
- 具体客户端可以不同,比如 Claude Code 的顺序是企业级、个人级、项目级。
- 项目优先换来团队约定自动生效,用户优先换来不被陌生仓库劫持。
- 项目级 skill 可能来自不可信仓库,加载应挂在目录信任检查后面。
- 撞名要打警告并记录诊断,不能静默遮蔽。
D3 A Design Method: Distilling From Repeated Tasks, Checklist Style vs. Reference-Manual Style, Four Anti-Patterns, and Trigger Testing
Which tasks are worth turning into a skill and which are not? Give me a test I can apply on the spot.什么样的任务适合做成 skill,什么样的不适合?给我一套能当场用的判断标准。
Common in ChinaCommon overseasBasic#agent-skills#skill-designHow to reason about it · think before answering
- The lazy answer is repetitive and complex tasks, which anyone can say. The interviewer wants a falsifiable test plus the reasoning behind each part.
- Give three criteria and insist all three must hold: repetition (done at least three times and will recur), correction (you interrupted the model the first time), and checkable results (you can tell afterwards whether it was right).
- Explain each. Correction is the strongest, because it simultaneously proves the model does not know and that you do. Without correction history you produce generic filler like handle errors appropriately.
- Checkability is the one people skip, and it decides not whether you can write the skill but whether you can iterate on it. If correctness only surfaces in three months, you are guessing.
- Then state the failure modes: without repetition nobody uses it, without correction it is filler, without checkability you cannot improve it.
- Expected follow-up: how wide should one skill be? Scope it like a function: one coherent unit that composes with others. Two skills always activated together were one skill; if the description needs and so on, the scope is too wide.
分析过程 · 先想清楚再作答
- 这题最容易答成「重复的、复杂的任务」,那是所有人都会说的话,没有区分度。面试官想听的是一套能证伪的判据,以及每一条判据背后的道理。
- 给三条,并且强调三条都要成立:重复(干过至少三次且还会干)、有纠正(模型第一次做时你打断过它)、结果可检验(做完能判断对错)。
- 逐条解释为什么。「有纠正」是最硬的一条,因为它同时证明模型确实不会、你确实会——没有纠正记录的 skill 写出来大概率是「妥善处理错误」这类正确的废话。
- 「可检验」这条常被忽略但很关键:它决定的不是这个 skill 能不能写,而是**你能不能迭代它**。对错要三个月后才知道的任务,你写完只能凭感觉觉得有用。
- 然后给反面:不满足这三条会怎样——不重复的没人用,没纠正的是废话,不可检验的没法改进。这一句把判据从清单变成了论证。
- 可预期的追问是「那范围多大合适」。答案是像拆函数一样:一个内聚的工作单元,且能与别的 skill 组合。两个总是一起激活的 skill 本来就是一个;描述里忍不住写「等等」说明范围太大了。
Key points
- All three must hold before you start: repetition, correction, checkable results.
- Correction is the strongest signal because it proves both the gap and your expertise.
- Checkability decides whether you can iterate, not whether you can write it.
- Scope to one coherent unit; two skills that always activate together should be merged.
- If the description needs and so on, the scope is already too wide.
答题要点
- 三条判据全部成立才动手:重复、有纠正、结果可检验。
- 有纠正是最硬的一条,它同时证明模型不会而你会。
- 可检验决定的不是能不能写,而是能不能迭代。
- 范围按内聚工作单元切,总是一起激活的两个 skill 应该合并。
- 描述里出现「等等」「以及相关的」,说明范围已经太大,该拆。
How do you evaluate whether a skill description is good? Is trying a few prompts yourself enough?怎么测一个 skill 的 description 好不好?自己试几句话够吗?
Common in ChinaCommon overseasIntermediate#agent-skills#evaluationHow to reason about it · think before answering
- The hinge is the second half. Saying a few prompts is enough fails immediately, but saying write a test set is not enough either; the interviewer wants the design.
- Explain why spot checks fail: fine with one skill, useless at ten, because you cannot hold ten descriptions in your head nor tell whether an edit helped or hurt.
- Then give three ingredients. First, a labeled query set of about twenty, balanced positive and negative. Vary positives along phrasing, explicitness, detail and complexity; the most valuable positives are the ones where the skill applies but the wording does not say so.
- Negatives are where the design effort goes: unrelated sentences test nothing. Near-misses that share keywords but need something else are what matters, such as editing Excel formulas or loading CSV rows into a database for a CSV-analysis skill.
- Second, repeat runs for a trigger rate, since model behavior is nondeterministic: three runs per query with a 0.5 threshold. Third, a roughly sixty-forty train and validation split with the validation set untouched.
- Expected follow-up: how do you decide whether a query triggered? Build the catalog of names and descriptions, hand it plus the query to the model and ask which skill applies. That is exactly what a client does at discovery, run standalone.
分析过程 · 先想清楚再作答
- 题眼在后半句。答「自己试几句就行」直接出局,但只答「要写测试集」也不够——面试官要看你知不知道这个测试集该怎么设计。
- 先说为什么抽查不够:一个 skill 时够用,装到第十个就不行了,因为你既记不住十个描述之间会不会互相抢,也没法在改完一句话后判断是改好了还是改坏了。
- 然后给三件东西。第一是带标注的查询集,约 20 条,正负各半。正例要在措辞、显式程度、详略、复杂度四个维度上铺开;**最有价值的正例是那些确实该用但字面看不出来的**,字面已经念了一遍功能的查询任何描述都能命中,测不出区别。
- 负例是设计的重点:毫无重叠的句子测不出任何东西,真正有用的是近似负例——共享关键词或概念但目标动词不同。对 CSV 分析 skill,「改 Excel 预算表的公式」和「把 CSV 每行写进数据库」都是好负例。
- 第二是重复跑取触发率:模型是不确定的,每条跑三次算命中比例,阈值取 0.5。第三是训练验证拆分,六比四,验证集全程不看。
- 可预期的追问是「怎么判断一条查询触发了没有」。答案是把所有 skill 的名字与描述拼成目录,连同这句话交给模型问它该用哪一个——这正是客户端在发现阶段做的事,只是单独拎出来跑。
Key points
- Spot checks work for one skill and break down once several skills compete.
- About twenty labeled queries, balanced, with positives varied by phrasing, explicitness, detail and complexity.
- Negatives must be near-misses that share keywords but need a different action.
- Three runs per query for a trigger rate with a 0.5 threshold, because behavior is nondeterministic.
- Split roughly sixty-forty; train guides revision, validation picks the winning version.
答题要点
- 抽查在一个 skill 时够用,多个 skill 互相干扰时完全不够。
- 约 20 条带标注查询,正负各半,正例在措辞、显式程度、详略、复杂度四维上铺开。
- 负例必须是近似负例:共享关键词但目标动词不同,无关句子测不出东西。
- 每条跑三次取触发率,阈值 0.5,因为模型行为不确定。
- 训练验证六四拆分,训练集指导改写,验证集只用来选版本。
When optimizing a description, how do you avoid overfitting to the very queries you wrote?优化 description 的时候怎么避免过拟合到你自己写的那几条测试查询?
Common in ChinaCommon overseasDeep dive#agent-skills#evaluationHow to reason about it · think before answering
- This is a familiar machine learning idea in a new setting. Saying validation set is only the start; the discriminator is describing the exact wrong move.
- Name what overfitting looks like here: a query fails, you paste its wording into the description, that query passes, and a synonymous one fails. Pasting the wording is the overfitting act itself.
- The right move is to generalize: identify the category the failing query represents and cover that. If a casual phrasing failed, cover casual phrasings, not that sentence.
- Structurally, rely on the split: roughly sixty-forty, revise only from train-set failures, keep the validation set out of the loop, preserve label balance in both, and freeze the split across iterations.
- Two practical rules: pick the version by validation pass rate rather than by recency, since later rounds tend to overfit, and stop after about five iterations if nothing moves, because the problem is then in the queries.
- Expected follow-up: how do you know the queries are the problem? Look at items that pass or fail in every configuration. Always-pass items carry no information; always-fail items are mislabeled or beyond the model.
分析过程 · 先想清楚再作答
- 这题是机器学习的老概念换了个场景,考的是你能不能把它迁移过来。能说出「验证集」三个字只是起点,真正的区分度在你怎么描述那个具体的错误动作。
- 先点明过拟合在这里长什么样:一条查询没触发,你把它的原话抄进描述,于是这一条过了,换一句同义的又不过。**抄原话就是过拟合的动作本身。**
- 正确做法是归纳:找出这条失败查询代表的**那一类说法**,然后把这一类补进去。比如「这几个文件我要提交了」失败了,该补的不是这句话,是「不含专业词的口语提交请求」这一类。
- 结构上靠拆分兜底:查询集按六比四拆成训练与验证,只用训练集的失败项指导改写,验证集全程不参与优化过程,两份都要保持正负比例接近,拆完固定不再洗牌。
- 还有两条实操经验。**挑版本按验证集通过率挑,不是按迭代顺序挑**——后面几轮往往在往训练集上过拟合,最好的可能是第三版而不是第五版。改五轮左右还不动就该停,问题多半在查询集本身而不在描述。
- 可预期的追问是「怎么知道是查询集的问题」。答案是看那些在两种配置下都失败或都成功的条目:都成功说明这条太容易、没有信息量,都失败说明要么标注错了要么要求超出模型能力,两类都该换掉。
Key points
- The overfitting move is pasting a failing query verbatim; generalize to its category instead.
- Split roughly sixty-forty and revise only from train-set failures.
- Keep label balance in both splits and freeze the split across iterations.
- Select the version by validation pass rate; the best is not always the last.
- If five rounds change nothing, inspect the query set for triviality, impossibility or mislabeling.
答题要点
- 过拟合的具体动作是把失败查询的原话抄进描述,要改成补它代表的那一类说法。
- 查询集六四拆分,只用训练集指导改写,验证集全程不看。
- 两个集合都要保持正负比例接近,拆完固定,不要每轮重洗。
- 按验证集通过率挑版本,最好的那版不一定是最后一版。
- 五轮不动就停,去查查询集本身是不是太容易、太难或标注错了。
D4 Skills With Scripts: Executable Attachments, Dependencies and Sandboxing, Cross-Platform Support, and Breaking Down Document-Handling Skills
Which logic belongs in a skill's scripts directory and which belongs in the SKILL.md body?什么逻辑该写成脚本放进 skill 的 scripts 目录,什么该留在 SKILL.md 正文里?
Common in ChinaCommon overseasBasic#agent-skills#scriptsHow to reason about it · think before answering
- This tests a sense of division of labor. Saying complex logic goes in scripts says nothing, because complex has no boundary. The interviewer wants decidable signals.
- Give three: the same logic gets reinvented a third time across execution traces; the result must be byte-identical (validation, format conversion, hashing); or a command is complex enough to be hard to get right first try.
- Expand the second into the core principle: deterministic work goes to code, judgment work stays with the model. Following instructions leaves room for drift; running a script does not.
- Give the other side: invoking an existing tool with two or three flags belongs inline in the body. Many ecosystems offer install-free one-off runners, and versions must be pinned or an upstream release silently changes your skill's behavior.
- Add the cost view: a script is a long-lived asset that must be maintained and kept in sync. When none of the three signals fire, prose is cheaper.
- Expected follow-up: how do you notice reinvention? Read execution traces rather than final outputs; the same helper appearing across runs is the signal.
分析过程 · 先想清楚再作答
- 这题在考分工感。答「复杂的写脚本」等于没答,因为复杂是个没有边界的词。面试官要听的是可判定的信号。
- 给三条信号,命中任意一条就写脚本:同一段逻辑在执行轨迹里被重新发明了第三次;结果必须逐字一致(校验、格式转换、哈希);一条命令复杂到第一次很难敲对。
- 把第二条展开成分工原则,这是本题的核心句:**确定性任务交给代码,判断性任务留给模型**。让模型「按指令做」意味着每次都有偏移的可能,让它跑脚本意味着结果确定。
- 再给反面:只是调一个现成工具加两三个参数,直接在正文写这条命令就行,不必建 scripts 目录。很多生态有免安装的一次性运行方式,用它们时**版本必须钉死**,否则上游一发版你的 skill 行为就变了。
- 补一条成本视角:脚本是长期资产,要维护、要跟模板同步、要有人看得懂。三条信号一条都不命中的时候,写正文更划算。
- 可预期的追问是「怎么发现模型在重新发明轮子」。答案是读执行轨迹而不是只看最终产出——同一个辅助函数在几次运行里反复出现,就是该沉淀成脚本的信号。
Key points
- Write a script when any of three fire: third reinvention, byte-identical results required, or a command hard to get right first try.
- Deterministic work to code, judgment work to the model.
- A tool invocation with a couple of flags stays inline, with the version pinned.
- Scripts are long-lived assets with maintenance cost; if no signal fires, write prose.
- Spot reinvention by reading execution traces, not final outputs.
答题要点
- 三条信号命中任一条就写脚本:重复发明第三次、结果必须逐字一致、命令复杂到难以一次敲对。
- 分工原则是确定性任务交给代码,判断性任务留给模型。
- 只加两三个参数调现成工具的,直接在正文写命令,但版本要钉死。
- 脚本是长期资产,有维护成本,三条都不命中就写正文。
- 发现重复发明要靠读执行轨迹,不是看最终产出。
How does designing a command-line script for an agent differ from designing one for a human?给 Agent 用的命令行脚本,接口设计上和给人用的有什么不同?
Common in ChinaCommon overseasIntermediate#agent-skills#scripts#cli-designHow to reason about it · think before answering
- The hinge is the difference. Many can list CLI best practices; few can say which ones exist specifically because the caller is a model.
- State the root difference: humans read docs, experiment and guess from experience; an agent has only the lines you printed before deciding the next move.
- From that: never prompt interactively. This is a hard requirement, not a nicety, because agents run in non-interactive shells and will hang until timeout.
- Help output is the interface documentation, but it must be short, since it enters the context window and competes with everything else. A human CLI never faces this constraint.
- Error messages decide the next attempt: say what failed, what was expected, what was received, and which values are allowed. Error messages are effectively prompts for the model.
- Then: structured output with data on stdout and diagnostics on stderr, and bounded output size because many harnesses truncate silently. Add idempotency, meaningful exit codes, and a dry-run flag for destructive work.
- Expected follow-up: how do you validate the design? Hand the help text and one error message to someone who has never seen the skill; if they can act on it, the model probably can too.
分析过程 · 先想清楚再作答
- 题眼是「不同」。能列出五条通用 CLI 最佳实践的人很多,能说清哪几条是因为「使用者是模型」才成立的人少。
- 先给根本差异:人会读文档、会试错、会凭经验猜;Agent 只能读你打印的那几行字然后决定下一步。**它的全部信息就是你的输出**。
- 由此推出五条。绝对不能交互,这是硬要求不是最佳实践,Agent 在非交互终端里回答不了提示,会一直挂到超时。
- 帮助信息就是接口文档,但要短——这段输出原样进上下文,跟别的东西抢位置,这是给人用的 CLI 完全不必考虑的约束。
- 错误信息决定它下一次会不会做对:写清哪一项错了、期望什么、实际是什么、可选值有哪些。**错误信息本质上是给模型的提示词**,这一句是拿分点。
- 剩下两条:输出结构化并把数据与诊断分流到标准输出与标准错误;输出体量要可控,因为很多 Agent 环境会静默截断超长输出。再补幂等、有意义的退出码、危险操作给预演开关。
- 可预期的追问是「怎么验证接口设计得好」。答案是把帮助输出和一条错误信息单独发给一个没看过这个 skill 的人,他能照着敲对改对,模型大概率也能。
Key points
- The agent's only information is what you printed; it does not read docs or experiment.
- Never prompt interactively; a non-interactive shell will hang until timeout.
- Help text is the interface documentation and must be short because it consumes context.
- Error messages must state the field, the expectation, the actual value and the allowed set; they are prompts for the model.
- Emit structured data on stdout and diagnostics on stderr, bound output size, and offer a dry-run for destructive operations.
答题要点
- 根本差异:Agent 的全部信息就是你打印的输出,它不会读文档也不会试错。
- 绝不能交互,否则在非交互终端里会挂到超时。
- 帮助信息就是接口文档,但必须短,因为它原样占用上下文。
- 错误信息要写清哪项错、期望什么、实际什么、可选值有哪些,它本质是给模型的提示词。
- 结构化输出并分流标准输出与标准错误,输出体量要可控,危险操作给预演开关。
What security risks come with bundling scripts in a skill, how would you contain them, and why should document tasks follow plan, validate, then execute?skill 里带脚本会带来哪些安全风险?你会怎么限制它?另外,为什么文档处理这类任务要先规划再校验后执行?
Common in ChinaCommon overseasDeep dive#agent-skills#security#workflow-designHow to reason about it · think before answering
- Two halves; answer both. Security is about boundaries, the three-step flow is about process, and both come down to putting a gate before an irreversible action.
- Cover security in four layers. Source: a third-party skill's scripts are someone else's code, so read the scripts directory before installing, exactly as you would skim a package. Skills invite less scrutiny because they look like documentation.
- Permissions: pre-approve at command granularity, allowing read-only git subcommands rather than arbitrary shell, and remember the field is experimental with uneven support, so do not rely on it alone.
- Input: files and API responses are untrusted. A script that does not execute them is not directly exploitable, but script output enters the model's context, so echoing a large blob of external content effectively speaks it to the model. Actions: gate delete, overwrite and publish behind a dry run or explicit flag, because agents retry.
- For the second half, give the three steps and stress that the value is in the middle one: analysis produces ground truth, validation compares plan against it with self-correctable errors, and only the fill step writes files.
- Name two disciplines: validation never mutates and fill never validates, or the model loses its pause between planning and execution; and intermediate artifacts must be written to disk so the validator can read them.
- Expected follow-up: why not validate while filling? Filesystems have no transactions, and a half-written document is worse than none because it looks complete.
分析过程 · 先想清楚再作答
- 这题有两半,别只答一半。前半考安全边界,后半考流程设计,两者的共同点是「在不可逆的动作之前留一道闸门」。
- 安全这一半按来源、权限、输入、动作四层说。来源:第三方 skill 里的脚本就是别人的代码,装之前要读 scripts 目录,跟装一个包之前看两眼是一回事——skill 更容易被当成文档而放松警惕。
- 权限:预批工具要卡到命令级,写「允许 git 的只读子命令」而不是「允许任意 shell」;而且这个字段还是实验性的,各家支持不一,不要把安全性全押在它上面。
- 输入:脚本处理的外部文件与接口返回是不可信输入。脚本不把它当代码执行就不会被直接利用,但**脚本的输出会进模型上下文**,原样回显一大段外部内容等于把那段话讲给模型听。动作:删除覆盖发布要给预演开关或确认参数,因为 Agent 会重试。
- 第二半给三步流程,并强调价值全在中间那步:分析脚本产出的是真值,模型不该凭记忆猜字段;校验脚本比对计划与真值,错误信息要够模型自己改对;填充脚本才落盘。
- 两条设计纪律要点出来:校验脚本不改数据、填充脚本不做校验,混在一起模型就没法在计划和执行之间停下来;中间产物要落盘成文件,否则校验脚本读不到,你也没法打开看。
- 可预期的追问是「为什么不能边填边校验」。答案是文件系统没有事务,写了一半的文档比完全没写更麻烦——它看起来是完整的。
Key points
- Third-party skill scripts are someone else's code; read the scripts directory before installing.
- Pre-approve tools at command granularity, and do not rely on an experimental field for safety.
- External input is untrusted, and script output enters context, so never echo large external blobs verbatim.
- The value of the three-step flow is the middle step: ground truth, self-correctable errors, then writing.
- Validation never mutates, fill never validates, intermediates go to disk, and never validate while writing.
答题要点
- 第三方 skill 的脚本就是别人的代码,装之前要读一遍 scripts 目录。
- 预批工具按最小权限、卡到命令级;该字段仍是实验性的,不能全押在它上面。
- 外部输入不可信,且脚本输出会进上下文,不要原样回显大段外部内容。
- 三步流程的价值全在中间那步校验:分析出真值、校验给可自纠的错误、执行才落盘。
- 校验不改数据、填充不做校验、中间产物落盘;不要边填边校验,半成品文档看起来是完整的。
D5 Hand-Building a Skill Runtime: Scanning, Frontmatter Parsing, Injecting the System Prompt, Reading the Body on Demand
If you implemented skill support in your own agent, what happens in the discovery stage versus the activation stage, and why split them?如果让你自己给一个 Agent 实现 skill 支持,发现阶段和激活阶段各要做什么?为什么要分成两步?
Common in ChinaCommon overseasIntermediate#agent-skills#runtime#progressive-disclosureHow to reason about it · think before answering
- This tests whether progressive disclosure is a mechanism you could build, not a slogan. Repeating the three stage names is not enough; say what each stage reads and where it writes.
- Discovery: scan the conventional directories, find every folder containing SKILL.md, parse out name and description, and assemble a catalog injected into the system prompt. No body text enters here; each entry carries only name, description and location.
- Activation: once the model judges that a task matches a description, read that full SKILL.md into context, along with the skill directory path and a list of bundled resource files.
- The reason for the split is an asymmetry in cost: disclosure is paid every turn, activation is paid once. The system prompt is resent with every request, so each extra character in the catalog is multiplied by the number of turns.
- That asymmetry also explains the spec's hard limits: descriptions are capped and bodies are not, and descriptions must state trigger conditions rather than usage instructions, because the description is the part that keeps costing money.
- Expected follow-up: can the location field be dropped? No. The model needs it to know which file to read, and its parent directory is the base for every relative path in the body.
分析过程 · 先想清楚再作答
- 这题在考你有没有把渐进式加载当成一个可实现的机制,而不是一句口号。只复述「发现、激活、执行」三个词是不够的,要落到每一步读了什么、写进了哪里。
- 发现:扫描约定目录,把所有含 SKILL.md 的文件夹找出来,解析出名字与描述,拼成一份清单注入系统提示。**这一步正文一个字都不进来**,清单里只有名字、描述、位置三样。
- 激活:模型判断当前任务命中了某条描述,才去读那一份完整的 SKILL.md,把正文放进上下文,同时告诉它技能目录在哪、附带哪些资源文件。
- 分两步的理由是成本结构不对称,这是本题的核心句:**披露的成本每一轮都要付,激活的成本只付一次。** 系统提示随每次请求重发,清单每多一个字都要乘会话轮数;正文只在被激活的那一轮进上下文,之后作为历史消息留着。
- 由这条不对称性可以顺手解释规范里的硬约束:为什么描述有长度上限而正文没有,为什么描述必须写触发条件而不是使用说明——描述是每轮都在花钱的那一段。
- 可预期的追问是「位置这一项能不能省」。不能:模型要靠它知道去读哪个文件,而且它的父目录是正文里所有相对路径的解析基准。
Key points
- Discovery scans directories, parses name and description, and injects a catalog into the system prompt with no body text.
- Activation reads the full SKILL.md and adds the skill directory plus a list of bundled resource filenames.
- The split exists because disclosure is paid every turn while activation is paid once.
- That asymmetry explains why descriptions are length-capped and must state triggers rather than usage.
- The location field is required: it is both the read target and the base for relative paths.
答题要点
- 发现阶段扫描目录、解析名字与描述、拼成清单注入系统提示,正文不进来。
- 激活阶段才读完整 SKILL.md,并附上技能目录与资源文件名清单。
- 分两步的根据是披露每轮付费、激活只付一次这条不对称性。
- 这条不对称性解释了描述为什么有长度上限、为什么要写触发条件而不是使用说明。
- 清单里位置字段不能省,它既是读取目标也是相对路径的解析基准。
When your runtime parses a SKILL.md that violates the spec, do you refuse to load it or degrade gracefully? And how do you handle a name collision across scopes?你的运行时解析到一份不合规范的 SKILL.md,是拒绝加载还是降级加载?另外,两个作用域里有同名 skill 时你怎么处理?
Common in ChinaCommon overseasIntermediate#agent-skills#runtime#error-handlingHow to reason about it · think before answering
- Both halves share one stance: a runtime exists to get work done, not to validate. State that first.
- For loose loading, give a decidable boundary. The only hard rejection is a missing description: without it the skill has no trigger surface, can never be selected, and only wastes catalog tokens.
- Everything else warns and still loads: a name that differs from the directory, a name using capitals or underscores, an over-long description. These hurt quality but not usability.
- Cite the most common malformation as evidence: an unquoted colon inside a YAML value makes a strict parser reject the whole file. The right fallback order is full YAML parsing first, then a line-wise field reader that extracts only the scalar fields you know.
- For collisions, the direction matters less than the handling. The cross-client convention is project over user, while Claude Code orders enterprise, personal, then project. Both are defensible; pick one and stay consistent.
- The worst handling is silent discard. The user edits the project copy, nothing changes, and they suspect caching or a failed save rather than a same-named skill elsewhere. Always log a warning that prints both paths.
- Expected follow-up: does loose loading let bad skills in? These are different layers. Looseness is format tolerance; safety comes from source trust and tool permissions, not from schema validation.
分析过程 · 先想清楚再作答
- 两个小问共用一个立场:**运行时是给人干活的,不是校验器。** 先把这句说出来,后面两半都好答。
- 宽松加载这一半要给出可判定的边界,不能只说「尽量宽松」。**唯一的硬性淘汰是缺 description**——少了它这个 skill 在发现阶段没有触发面,永远不会被选中,留在清单里只是白占 token。
- 其余一律只告警仍然加载:名字与目录名不一致、名字用了大写或下划线、描述超过上限。它们影响质量,不影响能不能用。
- 举一个最常见的畸形做证据:YAML 值里没加引号的冒号会让正规解析器判整行非法,进而拒绝整个文件。正确的兜底顺序是先用完整 YAML 解析,失败了再退回按行取值,只抠出认识的那几个标量字段。
- 同名冲突这一半,方向不是重点,**处理方式才是**。跨客户端通行约定是项目级压过用户级,但 Claude Code 的顺序是企业级、个人级、项目级由高到低,两种都合理,关键是固定一种并保持一致。
- 最糟的做法是静默丢弃:用户改了项目里那份,行为一点没变,他会去怀疑缓存和保存,就是不会想到别处有个同名的。**必须留一条警告并把两个路径都打出来**,那条日志是排查这类问题的第一现场。
- 可预期的追问是「宽松会不会把坏 skill 放进来」。答案是这两件事的层次不同:宽松说的是格式容错,安全靠的是来源信任与工具权限,不能拿格式校验当安全边界。
Key points
- A runtime is not a validator; degrade by default.
- The only hard rejection is a missing description, which leaves no trigger surface.
- Name mismatches, invalid names and over-long descriptions warn but still load.
- Parse with full YAML first, then fall back to line-wise field reading for unquoted colons.
- Fix one collision priority, keep it consistent, and never discard silently: log both paths.
答题要点
- 立场是运行时不是校验器,默认降级加载。
- 唯一硬性淘汰是缺 description,因为它没有触发面、永远不会被选中。
- 名字不一致、名字不合规、描述超长都只记诊断仍然加载。
- 解析顺序是先完整 YAML、失败再按行取值兜底,专治值里没加引号的冒号。
- 同名冲突要固定一种优先级并保持一致,绝不静默丢弃,警告里要带上两个路径。
Once a skill body is in context, how do you keep it effective across a long session? And would you activate skills by file read or by a dedicated tool?skill 的正文进了上下文之后,长会话里怎么保证它不失效?激活方式上文件读取和专用工具你会选哪个?
Common in ChinaCommon overseasDeep dive#agent-skills#runtime#long-sessionHow to reason about it · think before answering
- This is about the gap between a working demo and something you can ship. The first half is long-session failure modes, the second is the activation mechanism trade-off.
- Two long-session problems. Duplicate activation: the model forgets it already read the skill and selects it again, so the same instructions appear twice, wasting tokens and creating conflicts where the wording differs. Fix it with a set of already-activated names.
- The worse problem is compaction. Summarizing early messages can drop the skill body, and nothing errors: the model quietly reverts to its behavior without the skill. Users report that it stopped following the convention later in the conversation, and it is the hardest failure here to diagnose.
- The fix is to mark the activated message as protected so compaction preserves it, or to re-inject it afterward. The marker is trivial; remembering to set it is not.
- For the second half give criteria, not a preference. File-read activation adds no new mechanism, so any agent that can read files supports skills immediately, which is why the format spread across dozens of clients. The cost is no clean hook for dedup or protection, and the model can read the wrong path.
- A dedicated tool turns activation into an observable, interceptable call where you can dedupe, check permissions, and return the skill directory and resource list together. The cost is another tool definition and host cooperation. The criterion is whether you control the host.
- Expected follow-up: should resource files be read during activation? No, list filenames only. The value of three stages is that the third usually never happens.
分析过程 · 先想清楚再作答
- 这题考的是「演示能跑」和「上线能用」之间那段距离。前半是长会话的失效模式,后半是激活机制的取舍。
- 长会话有两个问题。第一个是重复激活:模型忘了自己读过,第二次又选中同一个 skill,同一段指令出现两遍既浪费又容易在措辞出入时互相干扰。修法是维护一个已激活集合,命中就直接返回。
- 第二个问题更要命——**被压缩掉**。压缩会把早期消息换成摘要,skill 正文落在那个区间里**不会报任何错**,模型只是悄悄退回没有这个 skill 的行为。用户看到的现象是「聊到后面它又不按规范写了」,这是这套机制里最难查的一类问题。
- 解法是给激活出来的那条消息打一个受保护标记,压缩时整段保留,或者在压缩后重新注入一次。标记本身很简单,难的是记得给它。
- 后半的取舍要给判据而不是偏好。文件读取式零新增机制,任何有读文件能力的 Agent 都能立刻支持,这正是这个格式能在几十家客户端铺开的原因;代价是没有明确钩子做去重和保护,模型还可能读错路径。
- 专用工具式把激活变成一次可观测可拦截的调用,能在这一步做去重、权限检查、连技能目录与资源清单一起返回;代价是多一个工具定义,且要求宿主愿意开这条通路。**判据是你控不控得住宿主**:自己写 Agent 用工具式,做通用实现用文件读取式。
- 可预期的追问是「资源文件要不要在激活时一起读进来」。不要,只列文件名。三阶段的全部价值就在于第三阶段大多数时候不会发生。
Key points
- Dedupe with a set of activated skills or the same instructions appear twice and conflict.
- Losing a skill body to compaction raises no error; the model silently reverts, which is the hardest failure to spot.
- Mark the activated message as compaction-protected, or re-inject after compaction.
- File-read activation adds no mechanism and has the best compatibility but offers no hook for dedup or protection.
- A dedicated tool is observable and interceptable; choose by whether you control the host, and in both cases list resource filenames without reading them.
答题要点
- 重复激活要靠已激活集合去重,否则同一段指令会出现两遍并互相干扰。
- 压缩掉 skill 正文不会报错,模型只会悄悄退回原行为,是最难查的失效。
- 激活出来的消息要打受保护标记,压缩时保留或事后重新注入。
- 文件读取式零新增机制、兼容性最好,但没有去重与保护的钩子。
- 专用工具式可观测可拦截,判据是你控不控得住宿主;两者都只列资源文件名,不预读内容。
D6 Organizing and Distributing: Plugins and Marketplaces, Versioning and Team Sharing, and the Division of Labor Between Function Calling, MCP, and Skills
How do function calling, MCP and Agent Skills relate, and when do you use which?函数调用、MCP 和 Skills 三者的关系是什么?什么时候用哪个?
Common in ChinaCommon overseasIntermediate#agent-skills#mcp#tool-calling#architectureHow to reason about it · think before answering
- The most common question in this course. The classic mistake is framing the three as competitors and saying skills are lighter than MCP, when they do not solve the same problem.
- Lead with the one-line division: MCP handles wiring, Skills handle experience, and function calling is the shortest wire of all.
- Then name the gaps. Function calling and MCP supply capability: the model cannot reach your database or file a ticket until you give it a tool. Skills supply experience: the model can already write a commit message, it just does not know your format.
- Give the two most informative contrasts. Context cost: tool definitions are resent every turn, while a skill costs only its name and description per turn with the body loaded on demand. Degradation: tools and protocols are binary, but a skill that fails to install is still readable Markdown, which is exactly why the format spread across dozens of clients. It requires the host to read files, not to implement a protocol.
- For selection give a runnable decision path. First separate missing capability from missing method. For capability, choose by reuse surface: one application means function calling, several agents justify an MCP server. For method, choose by determinism: instructions go in the skill body, byte-identical results go in a bundled script.
- Close on composition. The normal case stacks them: an MCP server exposes the ticket system as a tool, and a skill body says to pull this week's tickets with that tool and then group them by a template. Tools give hands, skills give procedure.
- Expected follow-up: when should you not use MCP? When only one application needs it and there are just two or three actions. Standing up a server is over-engineering.
分析过程 · 先想清楚再作答
- 这是本课最高频的一题。答错的典型是把三者摆成竞争关系,说「Skills 比 MCP 更轻量所以更好」——它们解决的根本不是同一个问题。
- 先给一句能背下来的分工:**MCP 管接线,Skills 管经验**,而函数调用是接线之前那根最短的线。
- 再落到缺口上。函数调用与 MCP 补的是**能力**:模型本来读不到你的数据库、发不出工单,给它工具它就能了。Skills 补的是**经验**:模型本来就会写提交信息,只是不知道你们这儿的格式。能力的缺口用工具补,经验的缺口用技能补。
- 然后给两条对比里最有信息量的差异。第一,上下文成本:工具定义每一轮都要重发,而 skill 每轮只有名字与描述,正文按需加载。第二,装不上时的降级:工具与协议是二值的,接不上就没有;**一个 skill 装不上仍然是一份人能读的 Markdown**,这正是它能在几十家客户端铺开的原因——它不要求宿主实现协议,只要求宿主会读文件。
- 选型给一条能当场走的流程:先分缺能力还是缺做法。缺能力时按复用面选,只有这一个应用要用就写函数调用,多个 Agent 都要用才值得做成 MCP 服务端。缺做法时按确定性选,靠指令说清楚就写进 skill 正文,结果必须逐字一致就配脚本。
- 最后一定要说配合。三者常态是叠着用:MCP 服务端把工单系统接进来成为工具,skill 的正文里写「先用工单查询工具拉出本周工单,再按这份模板归类」。**工具给它手,skill 给它章法。**
- 可预期的追问是「那什么时候不该用 MCP」。答案是只有一个应用要用、动作又只有两三个的时候——为它起一个服务端是过度设计,直接写函数调用更短。
Key points
- MCP is wiring, Skills are experience, function calling is the shortest wire.
- Capability gaps need tools or a protocol; experience gaps need skills. They do not compete.
- Tool definitions cost every turn; a skill costs only name and description until activated.
- A skill that fails to install is still readable Markdown, which is why it spread across clients.
- Choose by capability versus method: capability by reuse surface, method by determinism, and expect to combine all three.
答题要点
- 分工是 MCP 管接线、Skills 管经验,函数调用是接线之前最短的线。
- 能力的缺口用工具或协议补,经验的缺口用技能补,三者不是竞争关系。
- 工具定义每轮重发,skill 每轮只有名字与描述,正文按需加载。
- skill 装不上仍是一份人能读的 Markdown,这是它跨客户端铺开的根本原因。
- 选型先分缺能力还是缺做法:能力按复用面选,做法按确定性选;常态是三者叠着用。
A team needs to share more than a dozen skills. How would you organize and distribute them?一个团队要共享十几个 skill,你会怎么组织和分发?
Common in ChinaCommon overseasIntermediate#agent-skills#distribution#team-governanceHow to reason about it · think before answering
- This tests governance, not commands. The interviewer wants your criteria for splitting packages and choosing a distribution path.
- Organization first. The criterion is whether they are adopted and retired together. Skills orbiting the same team convention belong in one package; a team convention and your personal habit do not, because bundling forces people to take the half they did not want. A dozen skills usually becomes three or four packages.
- Name two hard rules. The package name is the namespace, so skills are prefixed as package colon skill, which is where collisions are resolved; pick the name once. And component directories must sit at the plugin root, never inside the manifest directory, which is the documented top mistake.
- Then the three distribution paths with criteria. Ship with the repository: commit the skills alongside code, zero infrastructure, reviewed through the existing pull request flow, but scoped to that repository. Choose it for conventions tied to one codebase.
- Use a marketplace: a repository plus a catalog JSON, added once per person, then installed on demand with automatic updates. One place to maintain, real versions and upgrade notes, at the cost of getting everyone to add it. Private simply means a private repository; there is no central server.
- Organization-managed distribution: pushed centrally and not easily disabled, with guaranteed coverage and auditability, but heavy process and slow iteration. Reserve it for rules that must be enforced, such as security and compliance.
- Close by noting the three combine: compliance centrally managed, cross-repository conventions via a marketplace, project quirks with the repository.
- Expected follow-up: will a dozen skills blow up the catalog? Discovery cost scales with total description length, so governance means auditing description length and mutual exclusivity, not capping the count.
分析过程 · 先想清楚再作答
- 这题考工程治理,不是考命令。面试官想听的是你按什么切包、按什么选分发路径,而不是背几条安装命令。
- 先讲组织。判据是**它们是否一起被采纳、一起被淘汰**:都围着同一套团队规范转、谁装了都得装全套,那就是一个包;一个是团队规范一个是你的个人习惯,凑在一起只会逼别人接受不想要的那半边。十几个 skill 通常应该切成三四个包,不是一个巨包也不是十几个碎包。
- 包的两条硬规矩要点出来:**包名就是命名空间**,包里的技能会被前缀成「包名冒号技能名」,撞名问题在这一层解决,所以包名要一次想好;组件目录必须在插件根下,不能塞进放清单的那个目录里,这是官方标出来的最常见错误。
- 再讲分发,给三条路径和各自的判据。随仓库走:直接放进项目目录跟着代码提交,零基础设施、评审走原来的流程,但只对这个仓库成立——**只跟某一个代码库有关的规范就选它**。
- 走市场:一个仓库加一份清单 JSON,成员各自添加一次,之后按需安装并自动收更新。一处维护多处生效、有版本、有升级说明,代价是要推动每个人添加一次。跨仓库的团队规范选它。**私有就是把市场仓库设成私有,没有中心服务器这回事。**
- 走组织托管:管理侧统一下发,不能随便关掉,覆盖率有保证、可审计,但流程重迭代慢,只有必须强制且不装就出事的规范才值得,比如安全合规那几条。
- 最后说三条不互斥,稳定组合是安全合规走托管、跨仓库规范走市场、项目独有的怪癖随仓库走。
- 可预期的追问是「十几个 skill 会不会把目录撑爆」。答案是发现阶段的开销只和描述总长有关,所以治理重点是**审描述的长度与互斥性**,而不是限制数量。
Key points
- Split by whether skills are adopted and retired together; a dozen usually becomes three or four packages.
- The package name is the namespace where collisions are resolved, and component directories live at the plugin root.
- Repository-scoped conventions ship with the repository: no infrastructure, no cross-repository reuse.
- Cross-repository conventions go through a marketplace, which is just a repository plus a catalog JSON; private repo means private marketplace.
- Mandatory compliance rules go through organization-managed distribution, and the three paths combine.
答题要点
- 切包的判据是它们是否一起被采纳、一起被淘汰,十几个通常切成三四个包。
- 包名就是命名空间,撞名在这一层解决;组件目录必须在插件根下。
- 只跟一个仓库有关的规范随仓库走,零基础设施但不跨仓库复用。
- 跨仓库的团队规范走市场,市场就是一个仓库加一份清单 JSON,私有仓库即私有市场。
- 必须强制的合规规范走组织托管,三条路径可以组合使用。
Should a skill package be versioned, and what goes wrong most often on upgrade?skill 包要不要做版本管理?升级时最容易出什么问题?
Common in ChinaCommon overseasDeep dive#agent-skills#versioning#distributionHow to reason about it · think before answering
- It looks procedural but really asks what a skill's interface is. Answer that and the rest follows.
- Should you version? Internally you can be loose; for public distribution you must pin a version. With a version, users update only when it changes. Without one, git sources use the resolved commit, so every push updates everyone, which is tolerable inside a team and out of control outside it.
- Add an easily missed detail: do not set the version in both the plugin manifest and the marketplace catalog. The plugin manifest wins, and a mismatch leaves a state you cannot explain.
- Then the criterion. What requires a bump is not whether a file changed but whether user-visible behavior changes. A changed description, changed body steps, or changed script flags all require a bump; typos and comments do not. It is the same as releasing a library, except the interface is not a function signature.
- The scoring point: a skill's interface is its description and body. Everyone remembers to bump for script changes but treats a slightly sharper description as cosmetic. The description is the only trigger surface: widen it and the skill starts stealing tasks, narrow it and it silently stops firing. Every description change is a behavior change and belongs in the upgrade notes.
- Give two concrete upgrade traps. Renaming the package changes the namespace, so every skill's invocation name changes and any hard-coded reference breaks. Moving a skill between packages looks to users like a capability disappearing, so the upgrade notes must spell out the migration.
- Expected follow-up: how do you know an upgrade did not break things? Run the day-three trigger tests as a regression, comparing hit rates on the same labeled queries before and after.
分析过程 · 先想清楚再作答
- 这题看着像流程题,实际考的是「skill 的接口到底是什么」。想清楚这一点,答案自然出来。
- 先答要不要:对内可以宽松,**对外发布必须写死版本**。写了版本,用户只在这个值变化时才收到更新,这是可控的;不写的话 Git 来源会拿提交哈希当版本,你每推一次内容用户就更一次,团队内部尚可,对外就是失控。
- 补一条容易忽略的细节:版本不要在包清单和市场清单两处都写,包自己的清单优先级更高,两边不一致会得到一个你自己都解释不清的状态。
- 接着答判据。什么改动要升版本?不是「改没改文件」,而是「**用户的行为会不会因此变化**」。描述改了、正文步骤改了、脚本参数改了都要升;修错别字、补注释不用。这跟给库发版一个道理,只不过这里的接口不是函数签名。
- 本题的拿分点在这里:**skill 的接口是描述与正文**。大家都记得改脚本要升版本,却常觉得「我就是把描述改得更准了一点」不算变更。但描述是唯一的触发面,改宽了会开始抢别的任务,改窄了会突然不触发。**描述的每一次改动都是行为变更**,都要在升级说明里单独写一行。
- 再给两个升级期的具体坑。一是改包名:包名是命名空间,改名等于把包里所有技能的调用名全改了,用户那边所有写死调用名的地方一起断。二是拆包与合包:一个 skill 从 A 包挪到 B 包,对用户来说是「装了 A 的人突然少了一个能力」,必须在升级说明里显式写迁移步骤。
- 可预期的追问是「怎么知道升级没升坏」。答案是把第三天那套触发测试当回归跑:改描述前后各跑一次同一组正负例,比触发率而不是凭感觉。
Key points
- Loose internally, pinned for public release; without a version, git sources update on every commit.
- Never set the version in both the plugin manifest and the marketplace catalog; the plugin manifest wins.
- Bump when user-visible behavior changes, not when a file changes.
- A skill's interface is its description and body, and every description change is a behavior change.
- Renaming the package rewrites every invocation name, moving a skill across packages needs migration notes, and trigger tests serve as upgrade regression.
答题要点
- 对内可宽松,对外发布必须写死版本;不写版本时 Git 来源按提交更新,等于失控。
- 版本不要在包清单与市场清单两处都写,包清单优先。
- 升不升版本看用户行为会不会变,不看改没改文件。
- skill 的接口是描述与正文,描述的每一次改动都是行为变更,最容易被漏掉。
- 改包名会改掉全部调用名,跨包挪动 skill 要写迁移步骤;用触发测试做升级回归。
D7 Capstone and Retrospective: Turning a Team's Conventions Into a Skill Pack and Driving a Subagent Through a Real Task
How do you prove a skill actually helps rather than just feeling better?你怎么证明一个 skill 真的有用,而不是感觉上更好?
Common in ChinaCommon overseasDeep dive#agent-skills#evaluation#methodologyHow to reason about it · think before answering
- This tests evaluation skill and honesty. Saying it felt better ends the answer; the interviewer wants a reproducible comparison.
- Give the structure first: one set of cases, two arms differing in exactly one variable, per-assertion judging, and a pass-rate comparison. The conclusion is a single number, the delta.
- Then explain how to keep the comparison clean, the half most people skip. Never test in the session you spent two hours debugging: that context is littered with convention snippets you typed and corrections you made, so good output reflects you, not the skill. Use a fresh subagent, with the two definitions differing only in which skills are preloaded.
- Describe the case mix: positive, boundary and negative roughly five to three to two. Negatives are non-negotiable because they measure whether the trigger surface is too wide, which is the most common way a skill goes wrong. Without them, a skill that grabs everything scores perfectly.
- Assertions are the core. Decidable means checking facts, not quality: the type field is one of six values, the scope equals a real directory in the repository, the first line is under fifty characters. Written clearly is not decidable. One assertion checks one thing so failures point somewhere.
- Close on honesty: some judgments resist reliable assertions, such as whether a review comment found the real problem. Forcing an assertion yields false green. Mark those as human-judged, sample a few, and say so in the conclusion.
- Expected follow-up: does a small sample support the claim? Be candid. A small sample supports a claim about that batch of tasks only, so every number carries its measurement conditions and is never extrapolated into a general efficiency gain.
分析过程 · 先想清楚再作答
- 这题在考评估能力,也在考诚实。答「我试了几次感觉好多了」直接出局,面试官要的是一个可复现的对照。
- 先给整体结构:同一批用例、两组只差一个变量、逐条判定、比通过率。**结论只有一个数:通过率差值。**
- 然后讲对照怎么做干净,这是本题最容易被忽略的一半。**绝对不要在你调试了两小时的那个会话里试**——那个上下文里散落着你手打的规范片段和你纠正过的措辞,模型产出得好是因为你自己把答案说了一遍。要用一个上下文干净的子代理,两份定义只差「预加载哪几个 skill」这一行,模型、工具集、提示词全部一致。
- 再讲用例集怎么配:正例、边界例、负例大约五比三比二。负例不能省,它测的是触发面有没有过宽,而**过宽是 skill 最常见的坏法**——少了负例,一个什么都抢的 skill 也能拿满分。
- 断言是全部重点。可判定的意思是不看好坏、只看事实成不成立:「类型字段取自那六个值之一」「范围等于仓库里真实存在的目录名」「首行不超过 50 个字符」是可判定的;「写得清楚」不是。一条断言只查一件事,失败时才知道是哪一条挂了。
- 最后补诚实这一层:有些判断写不出可靠断言,比如「这条评审意见有没有抓住真问题」。硬凑只会得到假绿,老实标成人工判定、抽查几条、并在结论里注明有几条是人工判的。**一份诚实的部分自动化评估远好过一份全绿的假评估。**
- 可预期的追问是「样本量这么小,结论站得住吗」。答话要坦率:小样本只能支撑「在这一批任务上」的结论,所以每个数字都要带测量条件,不要外推成通用效率提升。
Key points
- Same cases, two arms differing in one variable, judged per assertion, compared by pass rate.
- The comparison needs a context-clean subagent, never the session you debugged in.
- The two subagent definitions differ only in preloaded skills; model, tools and prompt are identical.
- Include negative cases: they measure an over-wide trigger surface, the most common failure.
- Assertions must be decidable and single-purpose; mark human-judged cases honestly in the conclusion.
答题要点
- 同一批用例、两组只差一个变量、逐条判定、比通过率差值。
- 对照必须用上下文干净的子代理,不能在调试过的会话里试。
- 两份子代理定义只差预加载 skill 那一行,模型、工具、提示词全部一致。
- 用例要含负例,它测触发面有没有过宽,过宽是最常见的坏法。
- 断言要可判定、一条只查一件事;判不了的老实标人工判定并在结论里注明。
How many skills should a thirty-page team convention document become, and how do you split it?一份三十页的团队规范文档要拆成几个 skill,按什么切?
Common in ChinaCommon overseasIntermediate#agent-skills#design#decompositionHow to reason about it · think before answering
- It sounds open-ended but has a clear wrong answer. Splitting by chapter is almost always wrong, and explaining why is where the points are.
- Chapter structure serves a human reading order, usually concept then detail. A skill boundary must serve the trigger moment, because the model decides whether to open it right after the user speaks. The two structures rarely coincide.
- Give three steps. First, read the document recording only when someone would need each passage. Record situations, not content; thirty pages usually yields a dozen situations.
- Second, cluster situations by shared moment. Commit message format, allowed types and body content may sit in three chapters but all apply at the moment of committing, so they are one skill. Writing a commit message and splitting commits share a chapter but are two moments, so they split.
- Third, write one description per cluster and test mutual exclusivity with three triggering phrases and two near-miss non-triggers each. If they compete, the clustering is not clean; go back to step two.
- Raise something interviewers probe: most of the document belongs in no skill. Background and history matter to people and are pure overhead for a model. The test remains whether omitting a line would make the model get it wrong. Thirty pages compressing to a few hundred lines is normal.
- Add the special case: deterministic rules such as an allowed type set or a version format belong in a validation script, leaving the body to say run the validator.
- Expected follow-up: how many exactly? The count follows the clustering. Seven or eight that still compete usually means the situations were recorded too finely; exactly one means you were still thinking about the document as a whole.
分析过程 · 先想清楚再作答
- 这题看着开放,其实有明确的对错。答「按章节切」几乎必错,能说清为什么错才是拿分点。
- 先给错的那条:**章节结构是为人的阅读顺序服务的**,通常从概念讲到细节;而 skill 的边界必须为触发场景服务——模型是在「用户刚说了一句话」这个时刻决定要不要翻开它。这两种结构几乎从不重合。
- 然后给正确的三步。第一步通读文档,只记「什么时候有人会用到这一段」,记场景不记内容,三十页通常能压出十来个场景。
- 第二步把场景按**同一个时刻**聚类。提交信息的格式、类型的取值、正文写什么,可能分散在三章里,但都在「我要提交了」这一刻被用到,它们是一个 skill;同一章里的「怎么写提交信息」和「怎么拆提交」是两个时刻,要拆开。
- 第三步为每个聚类写一句描述并检查互斥:各写三句会触发的话、两句形似但不该触发的话,跑一遍看有没有互相抢。**抢了说明聚类没聚干净,回第二步。**
- 还要主动说一件面试官爱追问的事:**文档里有一大半内容不该进任何 skill**。背景、沿革、当初为什么这么定,对人有价值,对模型是纯负担。判据仍是「不写这条,模型会不会做错」。三十页压成三四百行是正常的。
- 最后补一类特殊内容:确定性的规则(类型只能是这六个、版本号必须匹配某个格式)更适合沉淀成校验脚本,正文只留一句「写完跑一次校验」。
- 可预期的追问是「到底该切几个」。答案是数量由聚类结果决定而不是先定,但如果切出七八个还互相抢,通常是场景记得太细了;如果只切出一个,说明你还是按文档整体在想。
Key points
- Do not split by chapter: chapters serve reading order, skill boundaries serve trigger moments.
- Three steps: record situations, cluster by shared moment, write descriptions and test with positive and negative examples.
- Competing descriptions mean bad clustering; go back rather than patching the wording.
- Most of the document enters no skill; the test is whether omitting it would cause a mistake.
- Deterministic rules become a validation script, leaving one line in the body.
答题要点
- 不能按章节切,章节服务人的阅读顺序,skill 边界服务触发时刻。
- 三步:只记使用场景、按同一个时刻聚类、写描述并用正负例查互斥。
- 互相抢说明聚类没聚干净,要退回重聚,不是改描述糊过去。
- 文档里一大半内容不进任何 skill,判据是不写这条模型会不会做错。
- 确定性规则沉淀成校验脚本,正文只留一句跑校验。
What is the difference between running a task in a subagent with skills and running it in the main session?让子代理带着 skill 去执行任务,和在主会话里执行有什么区别?
Common in ChinaCommon overseasDeep dive#agent-skills#subagent#evaluationHow to reason about it · think before answering
- This tests the value of context isolation. A shallow answer reduces it to opening a new session. Name three effects and the problem each solves.
- First, a clean context. A subagent does not inherit the main conversation, so it knows nothing you said or corrected. This is decisive for evaluation: testing a skill in a session you debugged for two hours usually measures your own hints, the most common self-deception here.
- Second, precisely bounded capability. A subagent definition can declare its tools, its model, and which skills to preload. For a controlled comparison the two definitions differ only in that line, because any second difference makes the result unattributable.
- Third, noise stays out. File reading, trial and error and script runs live in the subagent's own context, and only the conclusion comes back, leaving the main window for the thread that must stay coherent.
- Name the costs too. Without the main context, the handoff prompt must be explicit, and a vague task description sends a subagent off course faster than the main session. It also pays for its own system prompt and skill catalog.
- An implementation detail shows real experience: skills reach a subagent either by preloading in the definition, which injects the full body at startup, or by letting it discover and activate them during execution. Use preloading for controlled comparisons and discovery for real work.
- Expected follow-up: when should you not use one? When the task needs back-and-forth with the user or depends heavily on dozens of earlier turns. There, isolation is the defect rather than the feature.
分析过程 · 先想清楚再作答
- 这题考的是上下文隔离的价值,答得浅会变成「子代理就是开个新会话」。要说清它带来的三件事,以及每一件对应什么问题。
- 第一件是**上下文干净**。子代理不继承主会话的对话历史,你说过什么、纠正过什么它一概不知道。这一条在做评估时是决定性的:在调试了两小时的会话里试 skill,模型产出得好往往是因为你自己在会话里把答案说了一遍,这是评估 skill 时最常见的自欺。
- 第二件是**能力可以精确限定**。子代理定义里能声明可用工具、模型,也能直接声明预加载哪几个 skill。做对照时两份定义只差这一行,其它完全一致——任何第二个差异都会让结论说不清是谁的功劳。
- 第三件是**噪音不进主会话**。翻文件、试错、跑脚本这些过程留在子代理自己的上下文里,只把结论交回来。主会话的窗口因此能留给真正要连贯推进的那条线。
- 还要说清代价,只说好处会显得没做过。子代理拿不到主会话的上下文,意味着**交接摘要要写清楚**,任务描述含糊时它比主会话更容易跑偏;而且它多跑一遍系统提示与技能目录,不是免费的。
- 补一个实现细节能显出实感:skill 进子代理有两条路,一是在定义里预加载、启动时就注入完整正文,二是让它在执行中自己发现并激活。做对照实验用预加载,因为它把变量固定住了;做真实任务用自动发现,更接近日常。
- 可预期的追问是「什么时候不该用子代理」。答案是任务需要跟用户来回确认、或强依赖前面几十轮的上下文时——隔离带来的干净,这时候正好是缺陷。
Key points
- A subagent has its own context window and no inherited history, which is what makes a clean comparison possible.
- Its definition bounds tools, model and preloaded skills, so a controlled pair differs in one line.
- Process noise stays inside the subagent; only the conclusion returns.
- The costs are an explicit handoff prompt, more drift on vague tasks, and paying for another system prompt.
- Preload for controlled experiments, discovery for real work, and skip isolation when the task needs user back-and-forth.
答题要点
- 子代理有独立上下文窗口,不继承主会话历史,这是做干净对照的前提。
- 定义里能限定工具、模型与预加载的 skill,对照时两份定义只差那一行。
- 过程噪音留在子代理里,只把结论交回主会话。
- 代价是交接摘要必须写清楚,任务含糊时更容易跑偏,且多付一次系统提示的开销。
- 预加载适合做对照实验,自动发现更接近真实使用;需要与用户反复确认的任务不适合隔离。
Context Engineering in 5 Days
D1 Context Is the Scarcest Resource: the Window, Attention Decay, and Cost — From Prompt Engineering to Context Engineering
What actually goes into the context of a single agent request, and which part is most likely to blow up?一次 Agent 请求的上下文里都有什么?哪一块最容易失控,为什么?
Common in ChinaCommon overseasBasic#context-window#token-budgetHow to reason about it · think before answering
- This question separates people who have measured from people who have read. Naming the four parts is easy; describing how each one grows is where the signal is.
- Classify the four by growth pattern: system prompt and tool definitions are resent verbatim every turn at roughly constant size; conversation history grows linearly by tens of tokens per turn; tool results grow in steps, often thousands of tokens per call.
- Conclusion: tool results are the most likely to blow up, because a single increment is one to two orders of magnitude larger than the others and its size is decided by an external system you do not control. Tool definitions come second since they scale with a tool count that only ever goes up.
- Add the subtlety: tool results live inside user-role messages but they are data, not dialogue. Bucketing by message role folds them into history and ruins the breakdown, so bucket by content block type instead.
- Expect the follow-up: what numbers did you actually see? A concrete figure lands best, for example tool results at 72.9 percent of a customer-support session while most people had guessed history.
分析过程 · 先想清楚再作答
- 这题在考你有没有真的量过。能背出「系统提示、工具定义、历史、工具结果」四块的人很多,能说出各自增长方式的人很少,区分度全在后半句。
- 怎么拆:按「每一轮会怎么变」给四块归类。系统提示和工具定义是每轮原样重发、长度基本不变;对话历史是线性增长,每轮加几十个 token;工具结果是阶梯增长,一次调用就能加两千。
- 结论:最容易失控的是工具结果,因为它的单次增量比其它三块大一到两个数量级,而且完全由外部系统决定,你写代码的时候看不到它会有多大。工具定义排第二,它随工具数量线性增长,而工具是最容易被顺手加上去的东西。
- 补一个容易被忽略的点:工具结果虽然写在 user 角色的消息里,但它是数据不是对话。按消息角色统计会把它算进历史,那张表就废了——要按内容块的类型拆。
- 可预期的追问:那你实际量出来是多少?给一个具体数字最有说服力,比如一次电商客服会话里工具结果占 72.9%,而大多数人事先都猜的是对话历史。
Key points
- Four parts: system prompt, tool definitions, conversation history, tool results.
- Group them by growth: the first two are resent every turn at near-constant size, history grows linearly, tool results grow in steps.
- Tool results blow up first because a single call can add thousands of tokens and its size is set externally; tool definitions are second, scaling with tool count.
- Bucket by content block type, not by message role, or tool results get miscounted as history.
答题要点
- 四块:系统提示、工具定义、对话历史、工具结果。
- 按增长方式分:前两块每轮重发且基本恒定,历史线性增长,工具结果阶梯增长。
- 最容易失控的是工具结果,单次增量最大且由外部系统决定;其次是工具定义,随工具数量增长。
- 统计时要按内容块类型拆,不能按消息角色拆,否则工具结果会被算进对话历史。
Context windows keep growing. Why not just put everything potentially relevant into the prompt?窗口越来越大了,为什么不能把所有可能有用的资料都塞进去?
Common in ChinaCommon overseasIntermediate#context-rot#attention-budget#costHow to reason about it · think before answering
- The hinge is whether you treat the window as capacity or attention as a budget. Answering only with cost reads as inexperience, since cost is the easiest and least dangerous of the three bills.
- Split into capability and cost. On capability, name context rot: recall accuracy degrades as context grows, rooted in the n-squared pairwise relationships a transformer maintains over n tokens, plus the fact that long-range parameters are underrepresented in training.
- Stress that this is a gradient, not a cliff. No specific length breaks; every thousand irrelevant tokens shaves a little accuracy. That phrasing distinguishes people who read primary sources.
- On cost, give three bills: money (the model is stateless, so every turn resends everything and the total is cumulative, not the last call), latency (slower time to first token), and accuracy (irrelevant content dilutes attention). The third is worst because it never raises an error, it just returns a plausible answer that violates a stated constraint.
- Expect the follow-up: how do you decide whether a given chunk earns its place? Give an operational test: if you cannot name the specific decision it changes, it does not go in.
分析过程 · 先想清楚再作答
- 这题的题眼是「你知不知道窗口是容量、注意力是预算」。只回答「太贵了」的人会被判成没做过工程,因为成本是三笔账里最容易想到、也最不致命的一笔。
- 怎么拆:分成能力和代价两条线。能力这条线要点出上下文腐烂——随着上下文变长,模型准确回忆其中信息的能力会下降,根源在于 Transformer 里 n 个 token 有 n 平方级别的两两关系,注意力被摊薄;而且训练语料里长序列本来就少,处理长距离依赖的参数不够多。
- 关键是要强调它是一条缓坡不是一道悬崖:没有哪个长度会突然崩掉,每多塞一千个不相干的 token,正确率就低一点点。这个措辞能立刻区分读过一手材料的人。
- 代价这条线给三笔账:钱(模型无状态,每轮全量重发,总输入是累加值不是最后那次的值)、延迟(首字返回变慢)、正确率(无关内容稀释注意力)。第三笔最贵,因为它不会报错,只会给出看起来合理但违反了约束的回答。
- 可预期的追问:那你怎么判断某段内容该不该加?给一条可执行的判据——说不出它会改变模型哪一个具体决定,就不该加。
Key points
- The window is capacity; attention is the budget. Fitting is not the same as being used well.
- Context rot: recall degrades as context grows, as a gradient rather than a hard cliff.
- Three bills: money (stateless models resend everything each turn, so cost is cumulative), latency, and accuracy.
- Accuracy is the dangerous one because it fails silently with plausible answers that break stated constraints.
- Test: if you cannot name the specific decision a chunk changes, leave it out.
答题要点
- 窗口是容量,注意力是预算;容量够不代表模型用得好。
- 上下文腐烂:上下文越长,准确回忆的能力越差,是渐进的性能梯度而不是一道悬崖。
- 三笔账:钱(每轮全量重发,成本是累加值)、延迟、正确率。
- 正确率那一笔最危险,因为它不报错,只会给出看似合理却违反约束的回答。
- 判据:说不出这段内容会改变哪一个具体决定,就不该放进去。
Where is the line between prompt engineering and context engineering, and when do you switch?提示词工程和上下文工程的分界在哪?什么时候该从前者切换到后者?
Common in ChinaCommon overseasIntermediate#prompt-engineering#context-engineering#scopingHow to reason about it · think before answering
- The trap is answering that context engineering is just prompt engineering leveled up. Interviewers want a rule they can apply to classify a live problem.
- Start with the object of each. Prompt engineering shapes content: how to phrase one instruction precisely. Context engineering allocates budget: how much of the window each part gets and when to drop things. One optimizes inside a single call, the other manages state across turns.
- Then give a symptom-based test. Wrong once but right after rephrasing means a prompt problem. Fine for five turns and violating the original constraints by turn twenty means a context problem. Retrieving the data and then claiming it does not exist is also a context problem: the information is in the window but buried.
- Conclusion: you switch not when the prompt is good enough, but when the cause moves from single-turn phrasing to multi-turn accumulation. Adding tools, adding retrieval, or running long sessions each trigger the switch.
- Expect the follow-up: does context engineering subsume prompt engineering? The system prompt is one of the four parts, so prompt engineering is a subproblem, but it cannot touch tool definitions, history, or tool results.
分析过程 · 先想清楚再作答
- 这题最容易答成「上下文工程是提示词工程的升级版」,那是营销话术。面试官想听的是一条能当场用来分类问题的判据。
- 怎么拆:先给对象的差别。提示词工程处理的是内容——一段话怎么写才准确;上下文工程处理的是预算分配——整只箱子里各块占多少、什么时候该扔。前者是单次调用内的优化,后者是跨多轮的状态管理。
- 再给一条现场可用的分类法,用症状反推:同一个问题问一次答错、换个说法就对,是提示词问题;前五轮正常、第二十轮开始违反最初约束,是上下文问题;明明查到了数据模型却说没有,也是上下文问题——信息在窗口里,只是被淹没了。
- 结论:切换的时机不是「提示词写得够好了」,而是「问题的成因从单次表达变成了多轮累积」。加了工具、加了检索、开始多轮长跑,这三件事任何一件发生,都意味着该切换了。
- 可预期的追问:那上下文工程包含提示词工程吗?答:系统提示是上下文四块里的一块,所以提示词工程是上下文工程的一个子问题,但它解决不了另外三块——工具定义、历史和工具结果都不是靠把话写好能管住的。
Key points
- Prompt engineering shapes content; context engineering allocates budget across turns.
- Classify by symptom: fixed by rephrasing is a prompt issue; drifting after many turns is a context issue; retrieved but reported missing is also a context issue.
- Switch when the cause moves from single-turn phrasing to multi-turn accumulation, typically after adding tools, retrieval, or long-running sessions.
- The system prompt is one of four parts, so good phrasing alone cannot control the other three.
答题要点
- 提示词工程处理内容,上下文工程处理预算分配;一个在单次调用内,一个跨多轮。
- 症状分类法:换个说法就对是提示词问题;跑久了开始违反约束是上下文问题;查到了却说没有也是上下文问题。
- 切换时机是问题成因从单次表达变成多轮累积,通常发生在加工具、加检索、开始长跑之后。
- 系统提示只是上下文四块之一,所以写好提示词管不住另外三块。
D2 System Prompts and the Instruction Hierarchy: the Right Altitude, Persistent Instruction Files, Progressive Disclosure, Less Is More
How do you calibrate the altitude of a system prompt, and what goes wrong at each extreme?系统提示的高度怎么把握?写太具体和写太笼统各会出什么问题?
Common in ChinaCommon overseasIntermediate#system-prompt#altitudeHow to reason about it · think before answering
- This tests whether you have an operational yardstick. Answering that it should be specific but not too specific fails, because that sentence cannot guide a single concrete edit.
- Name both failure modes. Too low means business logic hardcoded into prose: seven order states become seven branches, every new state forces a prompt edit, and no test tells you when you missed one. Too high means text that reads well and changes nothing if deleted.
- Give the yardstick: can you write an automated assertion that checks whether the rule was followed? If not, the rule is too high. If the assertion needs to enumerate seven cases, the rule is too low. A short assertion means the altitude is right.
- Then the fixes. For too low, relocate rather than shorten: keep the entry rule and push branch detail into a reference file loaded on demand. For too high, translate the incident that produced it into a checkable rule instead of just deleting it, or the incident recurs.
- Expect the follow-up: how do you know why a rule was added? Record the failing case beside the rule when you add it. Without that note, nobody will dare delete anything six months later.
分析过程 · 先想清楚再作答
- 这题在考你有没有一把可操作的尺子。凡是答「要恰到好处」「要具体但不要太具体」的,都会被归到没做过工程那一类,因为这句话不能指导任何一次具体修改。
- 怎么拆:先把两端的病症说清楚。写太具体是把业务逻辑硬编码进了自然语言——七种订单状态写成七条分支,加一个状态就要改提示词,而且没有任何测试会告诉你改漏了。写太笼统是一段读起来无可指摘、删掉之后模型行为却完全不变的话。
- 给尺子:你能为这条规则写出一个自动检查它有没有被遵守的断言吗。写不出来说明飞太高;写得出来但断言要列举七种情况说明飞太低;写得出来且断言很短,高度就合适。这把尺子的好处是能当场逐条判定,不需要争论。
- 结论加修法:太低的修法是挪走而不是缩写——留下入口规则,把分支细节搬进引用文件按需读取;太高的修法是把它当初对应的那次事故翻译成可核对的规则,而不是直接删掉,否则同一个事故会再来一次。
- 可预期的追问:那怎么知道一条规则当初是为什么加的?答:加规则的时候就在旁边记下它是为哪个失败案例加的。没有这句注释,半年后没人敢删任何一条。
Key points
- Too low hardcodes business branches into prose: brittle, and silent when it goes stale. Too high is text that changes nothing when removed.
- Test: can you write an automated assertion for the rule? No assertion, or one that enumerates seven cases, means the altitude is wrong.
- Fix too low by relocating detail into on-demand reference files and keeping only the entry rule; fix too high by translating the originating incident into a checkable rule.
- Record the failing case beside every rule you add; it is the only basis for deleting it later.
答题要点
- 高度太低是把业务分支硬编码进自然语言,脆且改漏无人知;太高是删掉也不改变行为的废话。
- 判据:能不能为这条规则写出一个自动断言,断言写不出或要列举七种情况都是高度不对。
- 太低的修法是把细节挪进引用文件、主提示只留入口规则;太高的修法是把对应事故翻译成可核对的规则。
- 每加一条规则就记下它对应的失败案例,这是将来敢不敢删它的唯一依据。
What belongs in the system prompt, what belongs in a persistent instruction file, and what should be loaded on demand?哪些内容该进系统提示,哪些该进持久指令文件,哪些该按需加载?
Common in ChinaCommon overseasIntermediate#instruction-hierarchy#progressive-disclosureHow to reason about it · think before answering
- This tests layering. Answering that frequently used content goes in the system prompt is circular, since defining frequently used is the actual question. Give an ordered decision procedure instead.
- Three questions, first yes wins. Would the model get it wrong without this rule? If not, delete. Is it needed for every class of task? If not, push it into a reference file and leave a one-line index. Whatever remains stays, rewritten as a mechanically checkable sentence.
- Add the category people miss: runtime facts that change over time, such as whether it is currently night hours, whether holiday shipping delays apply, or this session's order id. They look like rules but start lying to users hours later. They belong in tool output or in the user message.
- Conclusion: sort by stability and frequency. The more stable, the earlier; the rarer, the later; anything that changes every call never enters the system prompt. Bonus: this ordering is what makes a cache prefix hittable, so editing a task variable does not invalidate everything.
- Expect the follow-up: is pushing content down safer than deleting it? No. Reference files still have to be maintained and still consume context when read, just later and less often. Keeping things just in case is the main cause of prompt bloat.
分析过程 · 先想清楚再作答
- 这题在考分层意识。只回答「常用的放系统提示」是循环论证——问题恰恰是怎么定义常用。面试官想听的是一条排序明确的判定流程。
- 怎么拆:给三问,第一个答是就落定。第一问,模型不看这条会做错吗,不会就删除;第二问,是不是每一类任务都用得上,不是就下沉到引用文件、主提示里只留一句索引;剩下的保留,并重写成能被机械核对的一句话。
- 补一层常被漏掉的划分:还有一类内容根本不属于以上三者——随时间变化的运行时事实,比如当前是不是夜间、是不是节假日延迟期、本次会话的订单号。它们看着像规则,写死在系统提示里就会在半天之后开始骗用户,应该由工具返回或每次拼进用户消息。
- 结论:判据是稳定程度加使用频率。越稳定越靠前,越少用越靠后;而每次都变的东西根本不进系统提示。附带一个工程收益——按稳定程度排序之后,缓存前缀才有机会命中,改一条任务变量不会打掉整段缓存。
- 可预期的追问:下沉是不是比删除安全?不是。搬进引用文件的内容仍然要维护、仍然会在需要时占上下文,只是晚一点少一点。真正没用的条目要删,「先留着以防万一」正是提示词膨胀的主因。
Key points
- Three questions decide placement: would the model err without it (no means delete), is it needed by every task class (no means push down), and the rest stays as a checkable sentence.
- A fourth category is runtime fact (time of day, holiday delays, this session's order id); it belongs in tool output or the user message, not the system prompt.
- Order layers by stability so the cache prefix stays hittable.
- Pushing down is not free: reference files still cost maintenance and context, so genuinely useless rules should be deleted.
答题要点
- 三问定去处:模型不看会做错吗(不会就删)、每类任务都用得上吗(不是就下沉)、剩下的保留并重写成可核对的一句话。
- 第四类是运行时事实(时间、节假日、本次订单号),不属于系统提示,应由工具返回或拼进用户消息。
- 分层顺序按稳定程度排,稳定的在前,这样缓存前缀才有机会命中。
- 下沉不是免罪符:引用文件仍要维护、仍会占上下文,没用的要删掉。
How do system prompts keep growing, and how would you stop it?系统提示越写越长是怎么发生的?你会怎么止住这个过程?
Common in ChinaCommon overseasBasic#prompt-bloat#maintenanceHow to reason about it · think before answering
- It sounds like a complaint prompt but it tests process thinking. Many can name the cause; few offer a mechanism that actually stops the growth.
- The cause is a one-way ratchet. Every production incident is fastest to patch by appending a sentence to the system prompt. The person who added it knew why but did not write it down. Six months later nobody dares delete it, because if the incident recurs the blame lands on whoever deleted it.
- Name the subtle layer too: many rules exist to work around a specific model generation's quirks. After a model upgrade they are useless yet still consume input budget every turn, and nothing signals that they expired.
- Give three mechanisms. Record the failing case beside each rule when adding it. Start minimal and add rules only for observed failures rather than writing everything imaginable before launch. Periodically re-audit rule by rule using the can-you-write-an-assertion test, and re-run that audit after every model upgrade.
- Expect the follow-up: how do you de-risk deletion? Turn each rule's originating failure into a regression case and run it before deleting. A rule with no supporting case never earned its place.
分析过程 · 先想清楚再作答
- 这题看着像吐槽题,其实在考流程意识。能答出成因的人不少,能给出一条可执行的止损机制的人很少。
- 怎么拆:先讲成因,它是一条单向棘轮。每次线上出问题,最快的止血手段就是往系统提示里加一句;加的人当时知道为什么加,但没写下来;半年后没人敢删,因为删了万一那个事故重来一次,责任在删的人身上。于是只进不出。
- 再指出成因里最隐蔽的一层:很多规则是为了绕过某一代模型的具体毛病写的。模型换代之后它们不但没用,还在继续消耗每一轮的输入预算,而且没有任何信号提示你它们已经过期。
- 结论给三条机制:一是加规则时强制记录它对应的失败案例,这是将来敢删的唯一依据;二是最小起步——先用最少的规则跑一批真实用例,按观察到的失败逐条加,而不是上线前把能想到的都写上;三是定期做一次逐条判定,用「能不能写出断言」当尺子,并在换模型之后重跑一次。
- 可预期的追问:删规则的风险怎么控?答:把每条规则对应的失败案例沉淀成回归用例,删之前先跑一遍。没有用例支撑的规则,本来就没有资格待在那里。
Key points
- The cause is a ratchet: incidents are patched by appending a line, the reason is never recorded, and nobody dares delete it later.
- Subtle layer: many rules work around one model generation's quirks and silently expire after an upgrade.
- Three fixes: record the originating failure with each rule, start minimal and add only for observed failures, and re-audit periodically with the assertion test.
- Control deletion risk with regression cases derived from each rule's originating failure.
答题要点
- 成因是单向棘轮:出事就加一句,加的理由没记录,之后没人敢删。
- 隐蔽的一层:很多规则是为绕过某代模型的毛病写的,换代后过期却没有任何信号。
- 止损三招:加规则时记录对应失败案例、最小起步按失败驱动增加、定期用断言尺子逐条重判。
- 删除风险靠回归用例控制:每条规则对应的失败案例应沉淀成用例,删前先跑。
D3 Managing Context for Tool Results and Retrieval: Loading on Demand, Summarizing and Pruning, Structured Returns
An agent has thirty tools mounted and it is clearly struggling. How do you cut the list, and on what basis?Agent 挂了三十个工具,明显吃不消了。你会怎么裁?依据是什么?
Common in ChinaCommon overseasIntermediate#tool-design#tool-budgetHow to reason about it · think before answering
- The question separates people who see only the token bill from people who also see the selection cost. Answering with token count alone covers half the problem.
- Two distinct costs. Budget: tool definitions are resent every turn, roughly 308 tokens for three tools and 731 for eight, so thirty tools burn a couple thousand tokens per turn. Selection: more tools means more wrong choices, and the sharp test is that if a human engineer cannot say which tool applies, the model cannot either.
- Give four ordered criteria: overlapping responsibility (merge, or write the boundary into the description), call frequency across the last hundred real sessions (zero calls means remove), whether the task type can be determined up front (if so, mount per task), and whether several query tools can collapse into one parameterized tool.
- Conclusion: the third usually wins biggest. Most agents mount the full set at startup and keep it for the whole session, while any given session belongs to only two or three task classes, so per-task mounting typically halves the definitions immediately.
- Expect the follow-up on side effects: tool definitions sit at the very front of the cache prefix, so changing them invalidates everything after. Mount by coarse task class once at session start rather than recomputing every turn.
分析过程 · 先想清楚再作答
- 这题在考你能不能区分两类完全不同的代价。只答「工具定义占 token」的人只看到了一半,面试官真正在意的是另一半——选择成本。
- 怎么拆:先分两类问题。预算问题是工具定义每一轮都重发,3 个工具约 308 token、8 个约 731,挂三十个就是两千多,每轮都在花。选择问题是工具越多模型越容易选错,判据很硬:如果一个人类工程师都说不清什么时候该用哪个工具,那模型也做不到。
- 给四条可执行的裁剪判据,按顺序问:职责有没有重叠(有就合并或把边界写进描述)、过去一百次真实会话里被调用过几次(零次直接摘掉)、任务类型能不能提前判断(能就按任务动态挂载)、能不能把几个查询合并成一个带参数的工具。
- 结论:第三条通常收益最大。多数 Agent 的工具清单是静态的,启动时挂全集挂到会话结束,而一次会话往往只属于两三类任务中的一类,按任务大类动态挂载一步就能砍掉一半。
- 可预期的追问:动态挂载有什么副作用?工具定义排在缓存前缀最前面,改它会让整段前缀失效,所以只能按任务大类切几档,不能每轮重算——会话开始时定一次,中途除非任务类型真变了否则不动。
Key points
- Two costs: budget (definitions resent every turn, growing with count) and selection (overlapping tools make the model waver at decision points).
- Test: if a human cannot say which tool applies, neither can the model.
- Four cuts in order: merge overlaps, drop never-called tools, mount per task type, collapse several queries into one parameterized tool.
- Per-task mounting pays most but invalidates the cache prefix, so switch by coarse task class once per session.
答题要点
- 两类代价:预算(工具定义每轮重发,随数量线性增长)与选择(重叠工具让模型在决策点上摇摆)。
- 判据:人类说不清该用哪个,模型也做不到。
- 四条裁剪顺序:合并职责重叠的、摘掉零调用的、按任务类型动态挂载、把多个查询合并成带参数的一个。
- 动态挂载收益最大,但会打掉缓存前缀,所以按任务大类切档、会话内不再变。
How do you choose between just-in-time loading and pre-inference retrieval, and how would you combine them?按需加载和预先检索怎么选?混合策略应该怎么搭?
Common in ChinaCommon overseasIntermediate#retrieval#just-in-time#hybridHow to reason about it · think before answering
- This tests situational judgment. Calling just-in-time more advanced reads as trend-following, because pre-inference retrieval is genuinely better in many cases.
- Separate with three questions: can the needed material be scoped in advance (yes favors pre-retrieval), how fast does the material change (fast means indexes go stale, favoring just-in-time), and can it be fetched in one shot (multi-hop exploration forces just-in-time).
- Name the underlying difference: pre-retrieval hands the what-to-fetch decision to a retrieval algorithm and settles it before inference; just-in-time hands it to the model and spreads it across the run. The first is faster and more predictable, the second handles not knowing in advance.
- Conclusion is hybrid: preload the small, stable, always-relevant slice such as a project's standing instruction file, and use runtime search primitives for the rest. That gives a fast start without stale indexing, which is what coding agents converge on.
- Expect the follow-up on cost: just-in-time adds round trips and latency, and every fetched body stays in context consuming budget, so it must be paired with trimming.
分析过程 · 先想清楚再作答
- 这题在考场景判断。答「按需加载更先进」的会被当成跟风,因为预先检索在很多场景里就是更好的选择,说不出它好在哪说明没做过。
- 怎么拆:用三个问题分开。需要的资料范围事先能不能确定(能就预先检索)、资料变化快不快(变得快索引一建就旧,偏按需加载)、一次能不能取完(要顺着线索翻好几层就只能按需)。
- 把两者的本质差别点出来:预先检索把「取什么」的决定权交给检索算法,在推理之前一次性做完;按需加载把这个决定权交给模型自己,在推理过程中分多次做。前者延迟低、可预测,后者能应付事先不知道要什么的情况。
- 结论是混合:把最稳定最常用的一小部分预先放进去(比如项目的常驻说明文件),其余靠运行时的搜索原语现取。这样既有起步速度,又不会被过期索引拖住。这也是编码类 Agent 的主流做法。
- 可预期的追问:按需加载的成本在哪?多了几轮往返,延迟更高,而且每一次取回的正文都会留在上下文里继续占预算——所以它必须和裁剪配套,取回来的东西该扔的时候要扔。
Key points
- Three questions: can scope be fixed in advance, how fast does the data change, and can it be fetched in one shot.
- Pre-retrieval delegates the fetch decision to an algorithm before inference; just-in-time delegates it to the model during the run.
- Most real systems are hybrid: preload the stable core, use runtime search for the rest, avoiding stale indexes.
- Just-in-time costs round trips and latency, and fetched bodies keep consuming budget, so pair it with trimming.
答题要点
- 三个判断:范围能不能事先确定、资料变化快不快、一次能不能取完。
- 预先检索把取什么的决定交给检索算法并在推理前做完;按需加载把它交给模型并分多次做。
- 多数真实项目是混合:稳定常用的一小部分预加载,其余靠运行时搜索原语现取,避开索引过期。
- 按需加载的代价是多轮往返与延迟,且取回的正文会继续占预算,必须和裁剪配套。
Why are tool results untrusted input, and does field whitelisting make the concern go away?为什么说工具返回的内容是不可信输入?做了字段白名单裁剪之后还需要担心吗?
Common in ChinaCommon overseasDeep dive#prompt-injection#trust-boundaryHow to reason about it · think before answering
- There is a trap here: many answer it as a context question and claim trimming cleans the data. It actually tests whether you separate budget problems from security problems.
- Explain the untrust first. Tool results come from third-party APIs, user-uploaded files, or scraped pages. You did not write them, yet the model reads them in the same context as your system prompt, with no inherent privilege boundary. A line saying to ignore prior instructions can ride along; that is prompt injection.
- The key point: field whitelisting does nothing about this and can make it worse by creating a feeling of sanitization. The four surviving fields still carry externally controlled free text. Trimming governs volume, not trustworthiness.
- Give the right layering: context engineering decides what goes in; security decides what the content is allowed to cause. That means least privilege, tool allowlists, structurally separating external content from instructions, and confirmation on side-effecting actions.
- Expect the follow-up: can the trimming layer filter too? Cheap hygiene like stripping control characters or wrapping external content in explicit delimiters is fine, but keyword filtering is close to useless against injection. The real boundary is the permission layer.
分析过程 · 先想清楚再作答
- 这题有个陷阱:很多人会把它当成上下文工程题来答,说「裁剪之后就干净了」。它其实在考你分不分得清预算问题和安全问题。
- 怎么拆:先说清楚为什么不可信。工具返回的内容来自第三方接口、用户上传的文件、网页抓取的结果,不是你写的;而模型读到它的时候,和读你的系统提示是在同一个上下文里,没有天然的权限分层。里面可以藏一句「忽略之前的所有指令」,这就是提示注入。
- 关键结论:字段白名单一点都不解决这个问题,甚至更危险——你把字段裁到只剩四个,会产生一种「已经清理过了」的错觉,但那四个字段的值仍然是外部可控的自由文本,注入照样能进来。裁剪管的是体积,不是内容的可信度。
- 给出正确的分层:上下文工程负责决定装什么进去,安全机制负责决定装进来的东西能做什么。后者要靠最小权限、工具白名单、把外部内容和指令在结构上分开、以及对有副作用的操作加确认,而不是靠裁剪。
- 可预期的追问:那能不能在裁剪层顺手做过滤?可以做一些低成本的(比如剥掉控制字符、给外部内容加明确的包裹标记),但不要把它当成防线——基于关键词的过滤对提示注入几乎无效,真正的边界在权限层。
Key points
- Tool results originate outside your system yet share a context with your instructions, with no built-in privilege boundary.
- Field whitelisting reduces volume only; it does not change trustworthiness and can create a false sense of sanitization.
- Correct layering: context engineering decides what enters, security decides what it may cause.
- Defenses live in least privilege, tool allowlists, structural separation of external content, and confirmation on side effects, not keyword filters.
答题要点
- 工具结果来自外部系统,模型读它和读系统提示在同一个上下文里,没有天然的权限分层。
- 字段白名单只减体积,不改变内容的可信度,反而容易造成已清理的错觉。
- 正确分层:上下文工程决定装什么,安全机制决定装进来的东西能做什么。
- 防线在最小权限、工具白名单、结构上隔离外部内容、有副作用的操作加确认,不在关键词过滤。
D4 Long-Running Sessions: Compression, Notes and Memory Files, Subagent Isolation and Handoff Summaries
When should you compact the context, and when should you just move to a larger context window?什么时候该压缩上下文,什么时候该直接换一个更大的窗口?
Common in ChinaCommon overseasIntermediate#compaction#context-rotHow to reason about it · think before answering
- This checks whether you treat the window as capacity and attention as a budget. Answering only that you compact when it does not fit misses half the cases, since plenty of sessions should be compacted while the window is still mostly empty.
- Separate the two problems. Not fitting is capacity, and a bigger window fixes it. But a bigger window does not fix context rot: recall degrades as context grows, on a gradient, so a large nominal window is not a promise of stable behavior at that length. Fitting and being used well are different.
- Give the test: watch two signals, not one. High window occupancy means compact for capacity. Low occupancy with a low share of actually-useful tokens also means compact, for attention. The second is the one people miss because nothing looks urgent.
- Then order the tactics. Compaction is not the first move. Clear stale tool results first, since that needs no model call, is deterministic, and is reversible. Only then summarize history. Doing it the other way costs an extra call and risks losing information while usually treating the smaller bucket.
- Expect the follow-up: when is compaction itself not enough? When the state is an accumulating ledger rather than a summarizable conclusion, such as exact tallies, maps, or a long-term plan. Those belong in files outside the window.
分析过程 · 先想清楚再作答
- 这题在考你有没有把窗口当容量、把注意力当预算。只回答「窗口不够就压缩」的人漏掉了一半——很多时候窗口还很空,但已经该压了。
- 怎么拆:先把两个问题分开。窗口不够是容量问题,换大窗口确实能解决;但换大窗口解决不了上下文腐烂——上下文越长模型准确回忆的能力越差,这是一条缓坡,标称窗口大不等于在那个长度上表现稳定。所以「装得下」和「用得好」是两件事。
- 给判据:看两个指标而不是一个。窗口占用率高就该压(容量问题);占用率不高但有效信息占比很低,也该压(注意力问题)——后者最容易被忽略,因为看起来毫无压力。
- 再给顺序上的结论:压缩不是第一手段。先清掉旧的工具结果(不用调模型、确定、可逆),不够再摘要历史。反过来做的人很多,因为摘要听起来更高级,但摘要要多花一次调用、要承担丢信息的风险,而它治的往往不是大头。
- 可预期的追问:那什么时候压缩也不够?当状态是渐进积累的账本而不是可总结的结论时——比如精确计数、地图、长期计划。这类东西要写到窗口外面的文件里,不能靠摘要保住。
Key points
- Not fitting is capacity and a larger window solves it; context rot is attention and a larger window does not.
- Two triggers: high window occupancy, or low occupancy with a low share of useful tokens.
- Clear stale tool results first (no model call, deterministic, reversible), then summarize history.
- If the state is an accumulating ledger such as tallies, maps, or a plan, use external files instead of compaction.
答题要点
- 窗口不够是容量问题,换大窗口能解决;上下文腐烂是注意力问题,换大窗口解决不了。
- 两个触发信号:窗口占用率高,或占用率不高但有效信息占比很低。
- 顺序上先清旧工具结果(不调模型、确定、可逆),不够再摘要历史。
- 如果状态是渐进积累的账本(计数、地图、长期计划),压缩救不了,要写到窗口外的文件里。
What does compaction lose most easily, and how do you verify that a given compaction kept what mattered?压缩最容易丢什么?你怎么验证一次压缩没有丢掉关键信息?
Common in ChinaCommon overseasDeep dive#compaction#verificationHow to reason about it · think before answering
- The signal is entirely in the second half. Saying you keep the important parts is empty; interviewers want an executable verification step and evidence it has actually caught something.
- Explain why compaction is riskier than trimming. Trimming changes structure: you know which field you removed and you can fetch it back. Compaction changes language: what got dropped is the model's choice, and nothing marks the loss.
- Name the fragile categories. First, whatever is currently in flight in the last few turns, which loses its referent the moment it is folded. Second, hard requirements that do not look like conclusions, such as user-stated deadlines, emotional demands, or verbal commitments. Third, identifiers, which summaries happily rewrite from an order number into the relevant order.
- Conclusion: preset a list of facts that must remain findable after compaction and check them every time, rejecting the compaction or widening the verbatim window on failure. Support it with three measures: keep recent turns verbatim, give hard requirements their own section in the summary prompt, and demand verbatim preservation of identifiers.
- Expect the follow-up asking whether it ever caught you. A concrete case lands best: dropping the verbatim window from six turns to one raised the compression ratio from 68 to 87 percent but silently deleted a user's stated Friday deadline, because it lived only in recent turns and did not read like a conclusion.
分析过程 · 先想清楚再作答
- 这题的区分度全在后半句。谈「要保留重要信息」是空话,面试官想听的是一个可执行的验证机制,以及你有没有真的被它拦下来过。
- 怎么拆:先说清压缩为什么比裁剪危险。裁剪动结构,删掉一个字段你知道删了什么、也能取回来;压缩动语言,丢掉了什么是模型决定的,而且丢完不留任何标识。
- 点出最脆弱的两类内容:一是最近几轮正在进行的事,一折叠就失去指代对象,模型下一句就会问你说的是哪一单;二是形式上不像结论的硬性要求,比如用户提出的时间点、情绪化诉求、口头承诺——它们在语言上不重要,在业务上是全部。还有一类是标识符,摘要很容易把订单号写成「相关订单」。
- 结论给验证机制:预置一组「压缩后必须还能找到」的关键事实,每次压完逐条核对,不通过就拒绝这次压缩或调大保留轮数。三条配套措施是:保留最近若干轮原文、在摘要提示词里让硬性要求单独成段、明确要求逐字保留标识符。
- 可预期的追问:你被这个检查拦下来过吗?给一个具体例子最有力,比如把保留轮数从 6 调到 1 时压缩率从 68% 涨到 87%,但「用户要求周五 18:00 前答复」这条直接消失——因为它只活在最近几轮原文里,而且它不像一条结论。
Key points
- Compaction is riskier than trimming: structure is recoverable, language loss is silent.
- Three fragile categories: what is in flight in recent turns, hard requirements that do not look like conclusions, and identifiers.
- Verification: preset facts that must survive and check each one after every compaction, rejecting it on failure.
- Support with a verbatim recent window, a dedicated section for hard requirements, and explicit verbatim preservation of identifiers.
答题要点
- 压缩比裁剪危险:裁剪动结构可回溯,压缩动语言且丢失无标识。
- 最容易丢的三类:最近几轮正在进行的事、不像结论的硬性要求、标识符。
- 验证机制:预置一组必须存活的关键事实,每次压完逐条核对,不通过就不采纳这次压缩。
- 配套三招:保留最近若干轮原文、硬性要求在摘要里单独成段、要求逐字保留标识符。
Why does a subagent return only a summary instead of its full transcript, and how should the lead agent specify the handoff?子代理为什么只回传摘要而不回传全过程?主代理该怎么写交接要求?
Common in ChinaCommon overseasDeep dive#subagents#handoff#costHow to reason about it · think before answering
- This tests architectural intent. Saying it saves tokens is only half right, and the lesser half: multi-agent setups are more expensive overall, not cheaper.
- State the purpose. Subagent isolation buys a clean main context, not a smaller bill. A subagent can burn tens of thousands of tokens exploring in its own window while the main thread gains only a condensed result of one or two thousand tokens. It is separation of concerns applied to context.
- Put the cost on the table, which is where engineering experience shows: agentic applications use roughly four times the tokens of chat, and multi-agent systems roughly fifteen times. So the fit is narrow: heavy exploration with a condensable result, independent parallelizable subtasks, and a main thread that genuinely does not need the intermediate steps. Missing any one, fall back to compaction.
- Conclusion: specify a handoff contract of three sections (settled conclusions, open items, hard constraints), with every conclusion carrying a source identifier such as a file path, order id, or URL. Without identifiers the main thread can only trust everything or redo everything; with them it can spot-check the one claim it doubts. Require the constraints section even when empty, or the main thread cannot tell absent from forgotten.
- Expect the follow-up about the lead agent's own plan: write it outside the window too, since truncation or compaction late in a long task tends to eat the original plan first.
分析过程 · 先想清楚再作答
- 这题在考架构意图。答「为了省 token」只对了一半,而且是次要的那一半——子代理架构整体上是更贵的,不是更省的。
- 怎么拆:先说清目的。子代理隔离买的不是省钱,是主线上下文的干净。子代理可以在自己独立的窗口里烧掉几万 token 反复探索,主线只多了一两千 token 的浓缩结论,中间过程一个字都没进主线。这是关注点分离在上下文层面的落地。
- 把代价摆出来,这是最能体现做过工程的地方:Agent 类应用本来就比聊天多用约 4 倍 token,多 Agent 系统约 15 倍。所以适用面很窄——探索量大但产出能浓缩、子任务彼此独立可并行、主线确实不需要看中间过程,三条缺一就该退回压缩。
- 结论给交接契约:三段式(已定结论、待办事项、硬约束),且每条结论必须带来源标识(文件路径、订单号、URL)。原因是没有标识的结论不可复查,主线只能全盘相信或全盘重做;带标识之后主线可以只对存疑的那条做定点核实。硬约束那一段没有也要写「无」,不能省略,否则主线分不清是没有还是忘了写。
- 可预期的追问:主代理自己的计划怎么办?也该写到窗口外面。长任务后期一旦触发截断或压缩,最先丢的往往就是最初那份计划,而它恰恰最不该丢。
Key points
- Isolation buys a clean main context, not savings; multi-agent is more expensive overall.
- Magnitudes: agents use about four times chat tokens, multi-agent about fifteen times.
- Fits when exploration is heavy but condensable, subtasks are independent and parallel, and the main thread does not need intermediate steps.
- Handoff contract in three sections, every conclusion carrying a source identifier, and an explicit none when constraints are empty.
- Persist the lead agent's plan outside the window, since it is the first casualty of truncation late in long tasks.
答题要点
- 隔离买的是主线上下文的干净,不是省钱;多 Agent 整体更贵。
- 代价数量级:Agent 约为聊天的 4 倍 token,多 Agent 约 15 倍。
- 适用三条:探索量大且产出可浓缩、子任务独立可并行、主线不需要中间过程;缺一就退回压缩。
- 交接契约三段式,每条结论必须带来源标识,硬约束段即使为空也要显式写「无」。
- 主代理自己的计划也要写到窗口外,长任务里它最容易被截断或压缩吃掉。
D5 Measuring and Tuning: the Token Bill, Context Utilization, Failure-Mode Triage, and a Comprehensive Interview Deep Dive
How do you compute the token bill for one agent task, and which parts can be cached away?怎么给一个 Agent 算一次任务的 token 账单?哪些部分是可以被缓存掉的?
Common in ChinaCommon overseasDeep dive#token-accounting#prompt-cachingHow to reason about it · think before answering
- The first trap is the phrase one task. Many people quote a single request's input size, which is a weight reading, not a bill. Stateless models resend everything each turn, so the bill is the sum of every turn's input.
- Sum the four buckets by their growth patterns. The stable prefix (system prompt plus tool definitions) is resent verbatim, so multiply by turn count. History grows linearly, so it is an arithmetic series. Tool results grow in steps, so estimate calls times size. For scale: a twenty-turn support task whose final request is 11256 tokens totals 133502 across the session, nearly twelve times larger.
- Then caching. The cacheable part is the stable prefix, ordered tools, system, messages, where editing anything earlier invalidates everything after. Writes cost about 1.25 times base (about 2 times for a one-hour lifetime) and hits about 0.1 times, so twenty full-price prefixes become one write plus nineteen hits, an eighty percent saving.
- State the threshold: the prefix must reach the model's minimum cacheable length or caching silently does nothing. That produces the counterintuitive result where halving your system prompt lowers token count but raises the bill, because the prefix fell below the threshold.
- Expect the follow-up on whether to trim anyway. Yes, but report two numbers: the raw token reduction and the cache-adjusted effective reduction, and check whether the prefix crossed the threshold. If it did, add stable reference content back into the prefix or move to a model with a lower threshold.
分析过程 · 先想清楚再作答
- 这题的第一个坑在「一次任务」四个字。很多人报的是单次请求的输入量,那是称重不是账单——模型没有记忆,每一轮都要把前面全部重发,账单是整场会话每轮输入的累加值。
- 怎么拆:按四块各自的增长方式分别求和。稳定前缀(系统提示加工具定义)每轮原样重发,乘轮数;对话历史线性增长,是等差数列求和;工具结果阶梯增长,按调用次数与每次体积估。举个量级:一个 20 轮的客服任务,最后一轮单次输入 11256,整场累加是 133502,差了将近 12 倍。
- 再谈缓存。可缓存的是稳定前缀这一段,顺序是工具定义、系统提示、消息,改前面的会让后面全部失效。经济学是写入约 1.25 倍原价(一小时存活期约 2 倍)、命中约 0.1 倍,所以 20 轮的前缀从 20 次全价变成一次写入加十九次命中,能便宜八成以上。
- 结论要带上那条门槛:前缀必须达到模型的最小可缓存长度才生效,达不到既不报错也不告警。这直接导致一个反直觉现象——把系统提示精简掉一半,token 数降了,账单反而可能涨,因为前缀掉到门槛以下、缓存静默失效。
- 可预期的追问:那还该不该精简?该,但要同时报两个数——不含缓存的 token 降幅与含缓存的等效开销降幅,并检查前缀有没有跨过门槛。跨过了就把稳定的引用内容放回前缀抬回去,或者换一个门槛更低的模型。
Key points
- The bill is the sum of every turn's input across the session, not the last request's size.
- Sum by growth pattern: prefix times turns, history as an arithmetic series, tool results by call count.
- The cacheable part is the stable prefix ordered tools, system, messages; editing earlier segments invalidates later ones.
- Writes cost about 1.25 times base and hits about 0.1 times, but only above the model's minimum cacheable length, which fails silently.
- So trimming can lower tokens while raising cost; always report both cached and uncached figures.
答题要点
- 账单是整场会话每轮输入的累加值,不是最后一次请求的输入量。
- 按四块的增长方式分别求和:前缀乘轮数、历史等差求和、工具结果按调用次数估。
- 可缓存的是稳定前缀,顺序是工具定义、系统提示、消息,改前面会让后面全失效。
- 写入约 1.25 倍、命中约 0.1 倍;但前缀必须达到最小可缓存长度,否则静默失效。
- 所以精简可能让 token 降而账单涨,必须同时报含缓存与不含缓存两个口径。
When context is the problem, how do you localize which of the four buckets is at fault?上下文出问题的时候,你怎么定位是四块里的哪一块?
Common in ChinaCommon overseasIntermediate#diagnostics#metricsHow to reason about it · think before answering
- This tests a diagnostic path. Answering with check the logs or try again reads as having no method; interviewers want a fixed chain from symptom to metric to change.
- Start with two metrics. Window occupancy is per-turn input over the window limit and governs whether you will overflow. Useful-token share is the tokens later steps actually use over total tokens and governs whether the spend is worth it. Approximate the latter with your trimmer: whatever survives trimming is the numerator.
- Then four failure modes with their fingerprints: overstuffed (early instructions ignored, high occupancy), buried (the fact is in the window yet the model denies it, low useful share), underspecified (answers waver across identical questions, low occupancy but high error rate), and drifting (original constraints violated late in the session, retention checks failing after compaction).
- Highlight the two most misdiagnosed. Buried is routinely blamed on model capability; the test is to print the context and search for the fact by hand, and if it is there the problem is context, not the model. Underspecified is blamed on instability, when it usually means contradictory rules in the system prompt or two overlapping tools making the model waver.
- Expect the follow-up on conflicting metrics. Low occupancy with a low useful share is the dangerous combination, because nothing looks urgent while you pay full price to move noise and dilute attention. Trust the useful-token share there.
分析过程 · 先想清楚再作答
- 这题在考排查路径。答「先看日志」「多试几次」的会被判成没有方法论,面试官想听的是从现象到指标再到改动的一条固定链路。
- 怎么拆:先给两个指标。窗口占用率是单轮输入除以窗口上限,管的是会不会撑爆;有效信息占比是后续步骤真正用到的 token 除以总 token,管的是值不值。后者可以用裁剪器近似量:裁完还剩的那部分就是分子。
- 再给四种失败模式与各自的指纹:塞太满(漏读早期指令,占用率高)、找不到(信息在窗口里但模型说没有,有效信息占比低)、说不清(同类问题答法摇摆,占用率不高但错误率高)、越走越偏(跑久了违反最初约束,压缩前后的保留检查出现失败项)。
- 结论给最容易误判的两种。「找不到」常被误判成模型能力不足,判据是把上下文打印出来人肉搜一遍那条信息在不在——在就是上下文问题,不是模型问题。「说不清」常被误判成模型不稳定,实际多半是系统提示里有互相矛盾的规则,或者两个职责重叠的工具让模型在决策点上横跳。
- 可预期的追问:两个指标冲突时听谁的?答:占用率低但有效信息占比也低的情况最危险,因为看起来毫无压力却在按原价搬运垃圾,同时还在稀释注意力。这时应该以有效信息占比为准。
Key points
- Two metrics: occupancy for overflow risk, useful-token share for whether the spend earns its place, approximated with a trimmer.
- Each mode has a fingerprint: occupancy for overstuffed, useful share for buried, error rate for underspecified, post-compaction retention checks for drifting.
- Buried is most often misdiagnosed as model capability; print the context and search by hand.
- Underspecified usually means contradictory rules or overlapping tools; hunt the contradiction rather than swapping models.
答题要点
- 两个指标:窗口占用率管会不会撑爆,有效信息占比管值不值,后者可用裁剪器近似量。
- 四种模式各有指纹:塞太满看占用率、找不到看有效信息占比、说不清看错误率、越走越偏看压缩后的保留检查。
- 找不到最容易被误判成模型能力问题,判据是把上下文打印出来人肉搜一遍。
- 说不清多半是规则互相矛盾或工具职责重叠,去搜矛盾比换模型有用。
How much context engineering is enough, and how do you know when to stop?上下文工程做到什么程度算够?你怎么知道该停手了?
Common in ChinaCommon overseasIntermediate#tuning#stopping-criteriaHow to reason about it · think before answering
- This is open-ended but has a clear right shape. Saying more optimization is always better reads as lacking cost awareness, because context work is unbounded and will be overdone without a stopping rule.
- Name the concrete cost of overdoing it rather than stopping at wasted time. Trim too hard and you cut fields needed later; compact too hard and you lose hard requirements that do not read like conclusions; cut tools too far and the agent cannot finish the task. None of these raise errors; they show up only in accuracy, the most expensive bill.
- Give at least three checkable stopping conditions: useful-token share stable in a healthy band such as above fifty percent with no headroom across several measurements; per-turn occupancy under fifty percent on your longest case; and the last change delivering less than a five percent bill reduction.
- Land on the third: a sub-five-percent gain means what remains is necessary overhead, and squeezing further trades accuracy for money. It matters most because it is the only condition that transfers across projects unchanged.
- Expect the follow-up on preventing regression. Freeze the measurement into a regression suite: fixed cases, rerun on every change, bill and both metrics under monitoring. Model upgrades, tool churn, and downstream field changes each degrade it again.
分析过程 · 先想清楚再作答
- 这题是开放题,但它有明确的好坏。答「越优化越好」的人会被判成没有成本意识,因为上下文工程是个能无限做下去的活,不定停手判据就一定会做过头。
- 怎么拆:先说清过度优化的具体代价,不要停在「浪费时间」。裁得太狠会把后面才用得上的字段裁掉,压得太狠会丢掉不像结论的硬性要求,工具裁得太少会让模型没法完成任务。这些都不报错,只在正确率上体现,而正确率是最贵的一笔账。
- 给可核对的停手条件,至少三条:有效信息占比稳定在一个合理区间(比如 50% 以上)且连续几次测量没有上升空间;最长那条用例上的单轮窗口占用率不超过 50%;最近一次改动带来的账单降幅低于 5%。
- 结论落在第三条:降幅低于 5% 说明剩下的都是必要开销,继续压就是在拿正确率换钱。这条比前两条更重要,因为它是唯一一条与具体项目无关、可以直接复用的判据。
- 可预期的追问:那怎么保证停手之后不退化?把这套度量固化成回归:一批固定用例、每次改动都重跑、账单与两个指标进监控。上下文工程不是一次性项目,模型换代、工具增减、下游接口改字段,任何一件都会让它重新变差。
Key points
- Overdoing it fails silently in accuracy: fields needed later get cut, hard requirements get summarized away, and too few tools leave the task unfinishable.
- Three stopping conditions: a stable useful-token share with no headroom, per-turn occupancy under fifty percent on the longest case, and a last change worth under five percent of the bill.
- The third transfers best: under five percent means what remains is necessary overhead and further squeezing trades accuracy for money.
- After stopping, freeze it into regression: fixed cases, rerun on every change, and monitor the bill plus both metrics.
答题要点
- 过度优化的代价不报错,只在正确率上体现:裁掉后面才用的字段、压掉不像结论的硬性要求、工具少到做不完任务。
- 三条停手判据:有效信息占比稳定且无上升空间、最长用例的单轮占用率不超过 50%、最近一次改动账单降幅低于 5%。
- 第三条最通用:降幅低于 5% 说明剩下的是必要开销,再压就是拿正确率换钱。
- 停手后要固化成回归:固定用例、每次改动重跑、账单与两个指标进监控。
Frontend Agent UX in 7 Days: From Streaming to Generative UI
D1 What Makes Agent Frontends Hard: an Event Protocol and Your First Stream in the Browser
You need to consume a streaming response in the browser and the request carries a body. Would you use EventSource, WebSocket, or fetch with ReadableStream? Why?浏览器里要读一条带请求体的流式响应,EventSource、WebSocket、fetch 加 ReadableStream 你选哪个?为什么?
Common in ChinaCommon overseasBasic#streaming#sse#browser-apiHow to reason about it · think before answering
- The tell is 'the request carries a body'. Candidates who miss that half of the sentence answer EventSource, since it looks purpose-built for SSE, and the interview largely ends there.
- Lay out the hard constraints first: EventSource is GET-only, cannot carry a body, and cannot set custom headers. WebSocket is a full-duplex long-lived connection. Fetch can do anything but hands you nothing.
- Map the constraints onto the scenario: an agent request POSTs a full conversation history, often tens to hundreds of KB, plus an Authorization header. EventSource fails on all three counts and is out.
- WebSocket would work but is the wrong tool: the interaction is request-response with a streamed reply, not bidirectional realtime. You would own connection lifecycle, heartbeats, and reconnection, and still have to layer request-response semantics on top.
- Land on fetch with ReadableStream, and volunteer the cost: SSE parsing, reconnection, and error handling are all yours to write.
- Expect the follow-up on reconnection: implement it yourself, track the last event id, send it on reconnect so the server can resume, and dedupe by message id rather than by content.
分析过程 · 先想清楚再作答
- 题眼在「带请求体」四个字。没读到这半句的人会答 EventSource,因为它看起来天生就是读 SSE 的,这一答基本就结束了。
- 先把三个候选各自的硬约束摆出来:EventSource 只支持 GET、不能带请求体、不能自定义请求头;WebSocket 是全双工长连接;fetch 什么都能做但什么都要自己写。
- 再把约束对到场景上:Agent 请求要 POST 一整份对话历史(几十上百 KB)并带 Authorization 头,EventSource 的三条限制条条踩中,直接出局。
- WebSocket 能做但不该做:Agent 的交互是「发一个请求、流式收一串事件」,这是请求响应语义,不是双向实时。用长连接意味着要自己管连接生命周期、心跳、重连,还要把请求响应架在长连接上,为一个单向场景引入一堆状态。
- 结论是 fetch 加 ReadableStream,并主动说出它的代价:SSE 解析、断线重连、错误处理全都要自己实现,这正是要付的学费。
- 可预期的追问是「那断线重连怎么办」——答案是自己实现,记录最后一条事件的标识,重连时带上它让服务端续播,并且要按消息标识去重而不是按内容去重。
Key points
- Pick fetch with ReadableStream: EventSource is GET-only, takes no request body, and allows no custom headers, which rules it out for agent requests.
- WebSocket is technically possible but semantically wrong here: this is request-response with a streamed reply, not bidirectional realtime.
- The cost is owning SSE parsing, reconnection, and error handling yourself.
- Decode with TextDecoderStream rather than per-chunk TextDecoder, or multi-byte characters split across chunk boundaries will come out garbled.
答题要点
- 选 fetch 加 ReadableStream,因为 EventSource 只支持 GET、不能带请求体、不能自定义头,三条都挡住 Agent 场景。
- WebSocket 技术上可行但语义不匹配:这是请求响应加流式回复,不是双向实时,用长连接要多管一堆状态。
- 代价是 SSE 解析、重连、错误处理全部自己实现,这是换来灵活性必须付的成本。
- 解码时要用 TextDecoderStream 而不是逐块 TextDecoder,否则多字节字符被切在块边界上会解出乱码。
Why do protocols like AG-UI split one message into START, CONTENT, and END events instead of sending a complete message?AG-UI 这类协议为什么要把一条消息拆成 START、CONTENT、END 三个事件,而不是直接发一条完整消息?
Common in ChinaCommon overseasIntermediate#protocol-design#streaming#ui-stateHow to reason about it · think before answering
- This probes whether you have actually built a streaming UI. Answering 'because it streams' just restates the question; the signal is whether you can name what the split buys the frontend.
- Start from the constraint: streaming means rendering before the sentence is finished, so the frontend must be able to represent 'this message is still being generated'. A single complete message cannot express that.
- Each of the three parts buys a concrete capability: START lets the UI create and reserve the message container so layout does not jump when content arrives; CONTENT carries only the delta, saving bandwidth and client-side work; END is an unambiguous completion signal.
- END is the most underrated: it is the only trigger for a pile of finishing work such as applying syntax highlighting, starting screen-reader announcement, revealing copy and regenerate actions, and persisting the message. Without it you are guessing with timeouts.
- Equally important, every event carries a message id. An agent may produce several messages concurrently, and without the id you cannot route deltas to the right one.
- Expect the follow-up about convenience CHUNK events that collapse all three: they spare a simple server from running a three-state machine, at the cost of precise control over start and end timing.
分析过程 · 先想清楚再作答
- 这题考的是「你有没有真做过流式界面」。只答「因为要流式」是复述题干,区分度全在你能不能说出这个拆分让前端多做成了哪几件事。
- 推导的起点是一个约束:流式的本质是「话还没说完就得显示」,所以前端必须能表达「这条消息正在生成中」这个状态。一条完整消息做不到这件事。
- 拆成三段之后,每一段都换来一个具体能力:START 让界面先把消息容器建出来并占好位置,避免内容到达时布局跳动;CONTENT 只带增量,省带宽也省客户端拼接成本;END 是一个明确的完成信号。
- END 的价值最容易被低估,它是很多收尾动作的唯一触发点:补语法高亮、启动屏幕阅读器播报、显示复制与重新生成按钮、把消息落库。没有 END,前端只能靠超时猜,猜早了内容还没完,猜晚了界面一直显示在打字。
- 同样重要的是每个事件都带消息标识:Agent 可能并发产出多条消息(比如同时跑几个子任务),没有标识就无法把增量归到正确的那条上。
- 可预期的追问是「那为什么还要有 CHUNK 这种把三段合一的便利事件」——因为简单场景下服务端不想维护三段状态机,协议给了个捷径,代价是失去了对开始和结束时机的精确控制。
Key points
- Streaming means rendering before generation finishes, so the UI needs an explicit in-progress state.
- START reserves the container so layout does not jump, CONTENT carries only deltas, END gives an unambiguous completion signal.
- END is the only reliable trigger for finishing work: syntax highlighting, screen-reader announcement, copy actions, persistence.
- The message id on every event is what lets you route deltas correctly when several messages stream concurrently.
答题要点
- 流式的本质是内容没生成完就要显示,所以界面必须能表达「正在生成中」这个中间状态。
- START 让界面先建好容器避免布局跳动,CONTENT 只传增量省带宽,END 给出明确的完成信号。
- END 是补高亮、启动朗读、显示复制按钮、落库这些收尾动作的唯一可靠触发点,没有它只能靠超时猜。
- 每个事件带消息标识,才能在并发产出多条消息时把增量归到正确的那一条上。
Your frontend receives an event type the protocol does not define. Should it throw, ignore it, or pass it through? What drives your decision?你的前端收到一个协议里没定义过的事件类型,应该报错、忽略,还是透传给上层?说出你的判断依据。
Common in ChinaCommon overseasDeep dive#forward-compatibility#protocol-design#error-handlingHow to reason about it · think before answering
- It looks like a small API design question but really tests whether you think about version skew. Answering 'throw, be strict' usually means you have never watched a backend release take down old frontends.
- There is one deciding question, and you can say it out loud: are the two sides released together? Inside one repo, strict failure is right and surfaces bugs early. Across teams with independent release cadences, strict failure turns 'backend added an event' into 'every older client crashes'.
- Agent protocols are the second case and are evolving fast, so be liberal in what you accept: ignore unknown event types and keep reading the stream, never throw.
- Ignoring is not pretending nothing happened. Log it in development so someone notices the upstream has moved; stay silent in production rather than polluting the user's console.
- The same rule applies at field level: keep unrecognized fields on a known event instead of stripping them. That is exactly what passthrough means in the schema.
- Expect the follow-up on what validation is still for: validate what you send, and validate required fields on events you do know. Tolerate unknown types, not malformed known ones.
分析过程 · 先想清楚再作答
- 这题看起来是个 API 设计小问题,实际考的是你有没有版本演进的意识。答「报错,因为要严格校验」的人,通常没经历过后端升级把前端打挂的线上事故。
- 判断依据只有一条,而且可以直接问出口:这两端是同步发布的吗。同一个仓库里一起打包上线的,严格报错是对的,它能在开发期暴露问题;而协议两端由不同团队、不同节奏发布时,严格报错等于把「后端加了个新事件」变成「所有老前端崩溃」。
- Agent 协议属于后者,而且演进极快,上游随时在加新事件。所以正确策略是宽进:未知事件类型忽略掉,让流继续读完,绝不 throw。
- 但「忽略」不等于「装作没发生」。开发模式下要打一条日志,让开发者知道上游出了新东西该跟进了;生产环境静默即可,不要污染用户控制台。
- 这条原则在字段层面同样成立:一个事件里多出来没见过的字段也要原样保留而不是剥掉。这正是协议规范里 passthrough 的含义,也是所谓「宽进严出」在前端的落地。
- 可预期的追问是「那校验还有什么用」——校验用在你自己发出去的数据上,以及用在已知事件的必填字段上(比如文本增量事件缺了 delta 就该报错)。宽容的是未知类型,不是已知类型的坏数据。
Key points
- Ignore it and keep reading; never throw. With independent release cadences, strict failure breaks every older client each time the backend adds an event.
- The deciding question is whether both sides ship together: same repo can be strict, independently evolving sides must be tolerant.
- Log it in development so the drift gets noticed, stay silent in production.
- Same at field level: preserve unrecognized fields instead of stripping them, which is what passthrough means.
- Be liberal about unknown types, not about malformed known ones: a text delta event missing its delta should still fail.
答题要点
- 忽略并继续读流,绝不 throw:协议两端独立发布时,严格报错会让后端每次加事件都打挂老前端。
- 判断依据是两端是否同步发布——同仓库一起上线可以严格,跨团队独立演进必须宽容。
- 开发模式打一条日志提示上游有新东西,生产环境静默,不污染用户控制台。
- 字段层面同理:未知字段原样保留而不是剥掉,这就是协议里 passthrough 的含义。
- 宽容的对象是未知类型,不是已知类型的坏数据;已知事件缺必填字段仍然该报错。
D2 Streaming Without Jank: Render Storms, Incremental Markdown, and Scroll Anchoring
A model is emitting 80 deltas per second, your chat page drops frames, and the input box lags. In what order do you diagnose and fix it?模型每秒吐 80 个增量,你的聊天页开始掉帧、输入框也变卡,你按什么顺序排查和优化?
Common in ChinaCommon overseasDeep dive#react-performance#streaming#profilingHow to reason about it · think before answering
- This tests your diagnostic order, not your list of optimizations. Reciting 'memo, virtualization, debounce' invites the follow-up 'how do you know that is the cause', and the answer runs dry.
- Step one is always measure, never change. Record a profile and find which layer the time goes to: React's render and commit, Markdown parsing, or layout. Different bottlenecks need entirely different fixes.
- Once you confirm excessive renders, cut at the source with per-frame batching. The screen refreshes 60 times a second, so renders beyond that never reach the user. Buffer deltas and flush once per requestAnimationFrame, and the render ceiling becomes the refresh rate.
- Input lag is a **separate problem** that batching does not fix: React treats all updates as equally urgent, so streaming competes with keystrokes for the main thread. Use useTransition to mark streaming updates low-priority and interruptible. Separating these two concerns is most of the signal in this question.
- With headroom left, keep going: memo so finished history does not re-render, two-pass rendering to defer syntax highlighting until a code block closes, and virtualization last. Virtualization goes last because it complicates scroll anchoring, jump-to-message, and search.
- Expect the follow-up on why it does not reproduce locally: mock data arrives almost instantly, so React's automatic batching collapses updates within one event loop turn and hides the problem. Over a real network deltas arrive across turns and batching cannot help. Performance tests must mimic real arrival pacing.
分析过程 · 先想清楚再作答
- 这题考的是排查顺序,不是优化手段的清单。上来就背「memo、虚拟化、防抖」的人会被追问「你怎么知道是这个原因」,然后就答不下去了。
- 第一步永远是量,不是改。打开性能面板录一段,看时间花在哪一层:是 React 的渲染提交,还是 Markdown 解析,还是布局重排。不同的瓶颈解法完全不同,猜错了做的全是无用功。
- 确认是渲染次数过多之后,第一刀砍在源头:按帧合并。屏幕每秒只刷新 60 次,超出的渲染画面根本来不及显示,所以把增量攒到 requestAnimationFrame 里每帧统一更新一次,渲染次数的上限就被钉在刷新率上。
- 输入卡顿是**另一个独立问题**,不会被按帧合并解决:React 默认认为所有更新同样紧急,流式渲染会和键盘输入抢主线程。这一刀用 useTransition,把流式更新标成低优先级、可中断,让输入插队。能把这两件事分开说,基本就过了。
- 还有余量再往下做:memo 让已完成的历史消息不跟着重渲染,两趟渲染把语法高亮推迟到代码块闭合之后,最后才轮到虚拟化。虚拟化要放最后,因为它会让滚动锚定、跳转、搜索全部变复杂,是成本最高的一步。
- 可预期的追问是「为什么本地测不出来」——因为本地 mock 数据几乎瞬间到齐,React 的自动批处理会把同一轮事件循环里的多次更新合并掉,问题被藏起来了。真实网络下增量跨事件循环陆续到达,批处理帮不上忙。性能测试必须模拟真实到达节奏。
Key points
- Measure first: profile to see whether the cost is rendering, parsing, or layout, since the fixes differ completely.
- For excessive renders, batch per animation frame so update frequency tracks the refresh rate rather than the model's output speed.
- Input lag is a separate issue: use useTransition to make streaming updates low-priority and interruptible.
- Then memo to skip finished history, two-pass rendering to defer highlighting, and virtualization last.
- It does not reproduce locally because automatic batching collapses instantly-arriving updates; tests must mimic real pacing.
答题要点
- 先量后改:用性能面板确认瓶颈在渲染、解析还是布局,不同瓶颈解法完全不同。
- 渲染次数过多用按帧合并,把更新频率钉在屏幕刷新率上而不是模型吐字速度上。
- 输入卡顿是独立问题,用 useTransition 把流式更新降为可中断的低优先级。
- 再往下依次是 memo 跳过历史消息、两趟渲染推迟语法高亮,虚拟化放最后做。
- 本地测不出来是因为自动批处理把瞬间到齐的更新合并了,测试要模拟真实到达节奏。
Your streaming Markdown renderer receives a code fence that has opened but not yet closed. How should it handle that?流式 Markdown 渲染到一半,代码块的围栏只来了一半,你的渲染器应该怎么处理?
Common in ChinaCommon overseasIntermediate#markdown#streaming#renderingHow to reason about it · think before answering
- The signal here is whether you have actually built streaming rendering. People who have not say 'wait until it closes', which is the worst option for the user.
- Name what breaks in the naive approach: hand unclosed text to a normal parser and it either swallows everything after into the code block or refuses the block and renders plain text. Either way, **the moment the fence closes the layout jumps** as a paragraph abruptly becomes a code block.
- The right approach is to render in-progress syntax as what it **will eventually become**: detect the unclosed fence, emit a code block anyway, and flag it as incomplete. The UI uses that flag for an in-progress treatment such as a highlighted left border. When content finishes, the border goes away and nothing reflows.
- That incomplete flag has a second use: it is exactly the signal for whether to apply syntax highlighting. Highlighting is expensive, and on unfinished code it is wrong anyway, so the industry approach is two passes — plain text immediately, color once the block closes.
- Interestingly, inline markers go the **other** way: treat an unclosed bold marker as plain text rather than bolding early, because half-bolded text flickers with every arriving character while plain-to-bold jumps once. The criterion is which choice flickers less, not a fixed rule.
- Expect a follow-up about tables and lists: same principle, but tables are safer rendered a full row at a time since column count can still change.
分析过程 · 先想清楚再作答
- 这题的区分度在于你有没有真做过流式渲染。没做过的人会说「等它闭合再渲染」,而这恰恰是体验最差的做法。
- 先说清楚朴素做法坏在哪:把未闭合的文本交给普通解析器,它要么把后面所有内容吞进代码块,要么不认这个块当普通文本。无论哪种,**围栏闭合的那一刻画面都会突然重排**——一段文字忽然变成代码块,位置跳动,非常刺眼。
- 正确思路是让「正在生成中」的语法以它**最终会变成的样子**渲染:识别出未闭合的围栏,照样产出一个代码块,只是额外标一个未完成的标记。界面用这个标记显示「还在写」的样式,比如左侧一道高亮边。内容写完时只是去掉那道边,结构不变,所以不重排。
- 这个未完成标记还有第二个用途:它正好是「该不该上语法高亮」的判据。高亮很贵,而且代码没写完时高亮结果本来就是错的,所以工业做法是两趟——先出纯文本保证零延迟,闭合后再上色。
- 有意思的是行内标记的结论**相反**:只开了口的粗体应该当普通文本,不要提前加粗。因为加粗一半的文字会随着每个字到达反复横跳,而普通文本转粗体只跳一次。判据不是规则而是「哪种跳动更少」,能说出这一层说明你是在权衡而不是背结论。
- 可预期的追问是「那表格和列表呢」——同理,按「补全后是什么样」渲染,但表格要注意列数可能还会变,通常等整行到齐再渲染那一行更稳。
Key points
- Do not wait for the fence to close; that produces a jarring reflow at the moment it does.
- Detect the unclosed fence, emit a code block anyway, and flag it incomplete so the UI can show an in-progress treatment.
- Getting the structure right early means closing only removes a style, with no reflow.
- That flag also decides whether to highlight: two passes, text first, color after the block closes.
- Inline markers invert the rule: leave unclosed bold as plain text, since early bolding flickers. The criterion is which flickers less.
答题要点
- 不能等闭合再渲染,那会让围栏闭合的瞬间发生一次刺眼的重排。
- 识别未闭合围栏并照样产出代码块,额外标一个未完成标记供界面显示「还在写」的样式。
- 结构提前正确,闭合时只是去掉样式,所以不重排。
- 未完成标记同时是「该不该上语法高亮」的判据,两趟渲染:先出字,闭合后上色。
- 行内标记结论相反,未闭合时当普通文本,因为提前加粗会反复横跳;判据是哪种跳动更少。
When should a chat message list auto-scroll to the bottom, and when should it not? State your rule.聊天消息列表什么时候该自动滚到底部,什么时候不该?说出你的判定规则。
Common in ChinaCommon overseasBasic#scroll-behavior#ux#chat-uiHow to reason about it · think before answering
- An easy question that a surprising number of products get wrong, which is why interviewers like it. The wrong answer is 'scroll down whenever a message arrives', which ignores the user reading back through history.
- There is one rule and you should be able to state it in a sentence: **whether to follow depends on where the user currently is, not on whether new content arrived.**
- In practice, compute distance from the bottom as scrollHeight minus scrollTop minus clientHeight. Under a threshold counts as pinned and follows; otherwise stay put. Do not use zero as the threshold, since line height, zoom, and subpixel rounding make it imprecise. Leave a few dozen pixels.
- When not following you owe the user a way back, normally a jump-to-bottom button, optionally with an unread indicator. Stopping without offering a return path is its own failure.
- An easily missed detail: when following, assign scrollTop directly rather than smooth-scrolling. During streaming you may scroll every frame, and overlapping smooth animations interrupt each other and read as jank. Save smooth scrolling for the explicit jump-to-bottom click.
- Expect a follow-up about a user parked exactly on the threshold: add hysteresis by using different thresholds for entering and leaving the pinned state.
分析过程 · 先想清楚再作答
- 这是道送分题,但答错的产品非常多,所以面试官爱问。错误答案是「有新消息就滚到底」,一句话就暴露了没考虑用户正在往回看的情况。
- 判定规则只有一条,而且要能一句话说出来:**跟不跟随取决于用户当前在不在底部,而不是取决于有没有新内容。**
- 落到实现上就是算距底距离:scrollHeight 减 scrollTop 再减 clientHeight。小于一个阈值就算贴底,跟随;否则不动。阈值不要设成 0,行高、缩放、亚像素都会让判断不精确,留个几十像素的余量。
- 不跟随的时候必须给用户一个回去的入口,通常是一个「回到底部」按钮,有新消息时还可以带个未读提示。只停不给回路是另一种体验事故。
- 一个容易漏的实现细节:跟随时用 scrollTop 直接赋值,不要用平滑滚动。流式期间每帧都可能滚一次,多个平滑滚动动画会互相打断,看起来反而像卡顿。平滑滚动只用在用户主动点「回到底部」那一次。
- 可预期的追问是「用户正好停在阈值边界上反复抖动怎么办」——加一点迟滞,比如进入贴底态和离开贴底态用不同的阈值,避免在边界上反复切换。
Key points
- The rule: follow based on where the user is, not on whether new content arrived.
- Compute distance from the bottom with a threshold of a few dozen pixels, never zero.
- When not following, provide a jump-to-bottom affordance, optionally with an unread badge.
- Assign scrollTop directly when following; smooth scrolling every frame interrupts itself and reads as jank.
- Add hysteresis with different enter and leave thresholds to avoid flapping at the boundary.
答题要点
- 规则是跟不跟随取决于用户当前在不在底部,不取决于有没有新内容。
- 算距底距离判断是否贴底,阈值留几十像素余量,不要用 0。
- 不跟随时必须提供「回到底部」入口,可以带未读提示。
- 跟随时直接赋值 scrollTop,不要用平滑滚动,否则每帧的动画互相打断会像卡顿。
- 边界抖动用迟滞解决:进入和离开贴底态使用不同阈值。
Your streaming chat never janks locally but users report lag in production. What could explain that?你的流式聊天在本地怎么测都不卡,一上线用户就抱怨界面卡顿。可能是什么原因?
Common in ChinaCommon overseasDeep dive#react-performance#testing#debuggingHow to reason about it · think before answering
- This probes your understanding of React's batching boundaries, and it has a very specific answer. 'Production machines are slower' is not wrong but scores nothing; the interviewer wants the mechanism.
- The mechanism is **automatic batching** in React 18 and later: multiple state updates within one event loop turn collapse into a single render.
- Against a local mock, the whole stream often arrives in one or two chunks, so the client issues its updates within a single turn and React merges them all. The naive per-delta setState therefore produces no storm locally. Measured while building this course: with zero spacing, 174 deltas produced only 6 renders.
- Over a real network the deltas arrive **across event loop turns**, each landing in its own, so batching cannot help and renders track deltas one to one. Same code: 6 renders locally, 174 in production.
- Two conclusions follow: the code should batch per frame regardless, and **performance tests must mimic real arrival pacing**. A mock that fires everything at once gives you systematically false green.
- Expect a follow-up on what else behaves this way: anything timing-dependent, such as race conditions, ineffective debouncing, and load-order bugs that only appear on slow networks. The common thread is a local environment fast enough to hide the problem.
分析过程 · 先想清楚再作答
- 这题在考你对 React 批处理边界的理解,而且它有一个非常具体的答案。泛泛答「线上机器差、网络慢」不算错但拿不到分,面试官想听的是机制。
- 关键机制是 React 18 之后的**自动批处理**:同一个事件循环轮次里的多次状态更新会被合并成一次渲染。
- 本地连 mock 服务时,整条流往往在一两个数据块里就到齐了,客户端在同一轮事件循环里连续调用状态更新,React 把它们全并成一次——于是「每个增量一次 setState」这个写法在本地根本不产生风暴。我自己写课程实验时实测过:间隔设成 0 时,174 个增量只渲染了 6 次。
- 真实网络下增量是**跨事件循环陆续到达**的,每个增量各自落在不同的轮次里,自动批处理帮不上忙,于是渲染次数就和增量数一比一了。同一份代码,本地 6 次、线上 174 次。
- 所以结论有两层:一是这个写法本来就该改成按帧合并,二是**性能测试必须模拟真实的到达节奏**,mock 服务要在增量之间留真实的微小间隔,否则你的测试在系统性地给你假绿。
- 可预期的追问是「还有哪些问题有同类特征」——凡是依赖时序的问题都有,比如竞态、防抖失效、以及只在慢网络下暴露的加载顺序问题。共同点是本地环境太快,把问题藏起来了。
Key points
- React 18's automatic batching merges state updates that occur within one event loop turn.
- Local mock data arrives almost instantly, so updates land in the same turn and collapse, hiding the storm.
- Over a real network deltas arrive across turns, batching cannot help, and renders track deltas one to one.
- Fix by batching per frame, and add realistic spacing to the mock so tests stop reporting false green.
- Race conditions, broken debouncing, and slow-network load ordering share this shape: a too-fast local environment hides them.
答题要点
- React 18 的自动批处理会合并同一个事件循环轮次里的多次状态更新。
- 本地 mock 数据几乎瞬间到齐,更新落在同一轮里被全部合并,风暴不会出现。
- 真实网络下增量跨事件循环陆续到达,批处理失效,渲染次数与增量数一比一。
- 解法是按帧合并,同时让 mock 在增量之间留真实的微小间隔,避免测试给出假绿。
- 同类特征的问题还有竞态、防抖失效、慢网络下的加载顺序,都是被过快的本地环境藏起来的。
D3 Tool Call Visualization and Humans in the Loop: Interruption Is Not Local Interception
Why implement human-in-the-loop approval as run interruption and resumption rather than a frontend modal that blocks the request?为什么人在回路的审批要做成运行中断加恢复,而不是前端弹窗拦住请求?
Common in ChinaCommon overseasDeep dive#human-in-the-loop#security#protocol-designHow to reason about it · think before answering
- This separates candidates sharply, because almost everyone's first instinct is a frontend modal. That answer is not entirely wrong, but it does not survive the first follow-up: what if the user calls your API directly?
- Name the two independent problems with local interception. First, timing: the decision to call a dangerous tool happens on the server, and by the time the event stream reaches the browser the server may already be executing. A modal cannot stop it.
- Second, and more serious, security: the dangerous-tool list lives in frontend code, so editing the request or calling the endpoint from a terminal makes the confirmation vanish. **Treating the frontend as a security boundary is a classic mistake**; the frontend can advise, not enforce.
- The correct model makes approval part of the protocol: before a dangerous tool, the server deliberately ends the run with an interrupt outcome carrying an interrupt id. The frontend renders an approval UI. Once the user decides, the frontend issues a **new request** carrying that decision in a resume entry, and only then does the server decide whether to execute.
- This buys three things: the server is the sole executor so the gate is trustworthy; interrupt state can be persisted so the user can approve tomorrow; and each decision is a real request carrying an id, so the audit trail exists by construction.
- Expect the follow-up on what the frontend approval UI is still for: explaining consequences and collecting the decision, both of which the server cannot do. Worth adding that approval buttons should have no default selection, since a default decides for the user.
分析过程 · 先想清楚再作答
- 这题的区分度极高,因为绝大多数人的第一直觉就是「前端弹个框」。答这个不算完全错,但接不住第一个追问:那用户绕过前端直接调你的接口呢。
- 先说清楚本地拦截错在哪,而且是两个独立的问题。第一个是时序:模型决定调用危险工具这件事发生在服务端,等事件流到前端时,服务端那边随时可能已经执行了,前端的框拦不住它。
- 第二个是安全,而且更致命:危险工具清单写在前端代码里,用户改改请求、或者直接用命令行调接口,那个确认框就完全不存在了。**把前端当安全边界是典型错误**,前端能做的只是提示,不是管控。
- 正确模型是把审批变成协议的一部分:服务端执行到危险工具前主动结束这次运行,结局标成中断并带上一个中断标识;前端据此渲染审批界面;用户决定后前端发起**新的一次请求**,在恢复条目里带上这个决定;服务端读到之后才决定要不要执行。
- 这个模型顺带解决了三件事:服务端是唯一执行方所以门是可靠的;中断状态可以持久化,用户关了浏览器明天再批也行;每个决定都是一次带标识的真实请求,审计记录天然就有了。
- 可预期的追问是「那前端的审批界面还有什么用」——它负责两件服务端做不了的事:把后果解释清楚,以及采集决定。还可以顺带提一句界面细节:审批按钮不该有默认选中项,默认值等于替用户做了决定。
Key points
- Local interception has two separate problems: the run is already executing server-side, and frontend checks can be bypassed.
- The right model ends the run with an interrupt outcome; the frontend renders approval and sends the decision back as a resume entry on the next request.
- The server is the sole executor and therefore the only trustworthy gate; the frontend explains consequences and collects the decision.
- Interrupt state can persist so approval can happen later, and each decision is an identified request, giving audit for free.
- UI detail: no default selection on approval buttons, since a default decides for the user.
答题要点
- 本地拦截有两个独立问题:运行已经在服务端跑起来了,以及前端判断可以被绕过。
- 正确模型是服务端主动以中断结局结束运行,前端渲染审批界面,决定随下一次请求的恢复条目发回。
- 服务端是唯一执行方,所以它才是可靠的门;前端只负责解释后果与采集决定。
- 中断状态可持久化,用户可以晚些再批;每个决定是一次带标识的请求,审计记录天然具备。
- 界面细节:审批按钮不设默认选中项,默认值等于替用户做了决定。
Tool call arguments arrive as streamed fragments. What should the UI display before they are complete?工具调用的参数是一段段流式拼出来的,界面在参数还没拼完时应该显示什么?
Common in ChinaCommon overseasIntermediate#tool-calling#streaming#ui-stateHow to reason about it · think before answering
- It looks simple but tests whether you have handled streaming tool calls for real. People who have not say 'show the arguments', which is exactly how half-formed JSON ends up on screen.
- State the fact first: the delta on an arguments event is a **fragment of a JSON string**, not an object. You may hold `{"path":"reports/20`, which will throw if parsed.
- So rule one is accumulate the raw text and parse only once the end event arrives, with error tolerance. Models do occasionally emit invalid JSON; do not let the UI crash. Mark the call failed and show the raw text, which beats a blank screen.
- As for what to display: **never show the half-formed JSON**. Seeing `{"path":"repo` makes users think the app broke. Show something like 'preparing arguments', optionally with a character count — honest without being alarming.
- A related state design point: keep 'arguments still arriving' and 'tool executing' as separate states rather than one spinner. The first is usually hundreds of milliseconds, the second can be tens of seconds, and they mean different things to the user.
- Expect a follow-up on testing this: assert on **what the user can see**, not internal fields. My first version asserted that the parsed field stayed null while incomplete, which turned out to be vacuously true since parsing partial JSON fails and returns null anyway. Asserting the displayed text made it meaningful.
分析过程 · 先想清楚再作答
- 这题看着简单,考的其实是你有没有真处理过流式工具调用。没做过的人会答「显示参数」,而这恰恰会在界面上露出半截 JSON。
- 先说清楚事实:参数事件的增量是**一段 JSON 字符串的片段**,不是对象。你可能收到 `{"path":"reports/20` 这种东西,它 parse 一定抛异常。
- 所以第一条规则是**边收边存原文,不要边收边 parse**,等结束事件到了再一次性解析。而且解析要容错——模型偶尔真的会吐出不合法的 JSON,这时候不要让界面崩,把状态标成失败并把原文留给用户看,比白屏有用得多。
- 回到题目问的显示:**绝对不要把半截 JSON 原文显示出来**。`{"path":"repo` 出现在界面上,用户会以为程序崩了。合理的做法是显示「正在准备参数」,可以带一个已收到的字符数,既诚实又不制造恐慌。
- 顺带一个状态设计:「参数还在传」和「工具正在执行」要分成两个状态,不要合并成一个加载中。前者通常几百毫秒,后者可能几十秒,对用户的含义完全不同。
- 可预期的追问是「那怎么测这件事」——测试要盯**用户看得见的输出**,而不是内部变量。我自己写这段的测试时第一版断言的是「参数没收齐时内部字段为 null」,结果它恒真:提前 parse 半截 JSON 本来就失败返回 null。改成断言界面文案之后才真正有效。
Key points
- Argument deltas are fragments of a JSON string, not objects, so mid-stream parsing always throws.
- Accumulate raw text and parse once at the end event, tolerating failure rather than crashing.
- Never render partial JSON; show 'preparing arguments' with a character count instead.
- Keep 'arguments arriving' and 'tool executing' as distinct states; their durations and meanings differ.
- Assert on user-visible output, since asserting internal intermediate state easily produces vacuously true tests.
答题要点
- 参数增量是 JSON 字符串的片段而不是对象,中途 parse 必然抛异常。
- 边收边存原文,结束事件到了再一次性解析,且解析失败要容错不要崩。
- 界面绝不显示半截 JSON 原文,改显示「正在准备参数」加已收字符数。
- 「参数在传」和「工具在跑」要分成两个状态,两者的时长量级和含义都不同。
- 测试要断言用户可见的输出,断言内部中间状态容易写出恒真的假绿。
The user rejects a tool call. What should your system do next?用户拒绝了一个工具调用,你的系统接下来应该怎么处理?
Common in ChinaCommon overseasBasic#ux#error-handling#human-in-the-loopHow to reason about it · think before answering
- This tests product judgment rather than technical difficulty. Many implementations fail the run outright, which is a UX failure.
- The deciding principle fits in a sentence: **the user declined an action, they did not ask for the conversation to break**. They most likely want the agent to propose something else. Turning refusal into failure punishes the user for exercising the control you gave them.
- The right move is to return the refusal as a **tool result**, stating that the user rejected it and why. The model can then understand what happened and offer an alternative, such as archiving the reports instead of deleting them.
- The run therefore still ends successfully rather than in error, and that distinction is visible in the events: a run finished with a success outcome, not a run error.
- Reflect it in the UI too: do not render rejection as a red error. Users read that as having done something wrong, when they merely made a choice. Give it a distinct neutral state.
- State the transferable rule out loud: **use the error state only when the system actually failed. A user's choice, whatever it is, is not a failure.** The same applies to form validation, permission denials, and cancelled payments.
分析过程 · 先想清楚再作答
- 这题考产品判断,不是技术难点。很多实现直接让这次运行报错结束,而这是个体验事故。
- 判断依据一句话就能说清:**用户只是不想执行这个操作,不是想让对话崩掉**。他多半希望 Agent 换个方案继续。把拒绝做成失败,等于惩罚用户行使了你给他的权利。
- 正确做法是把拒绝也当成一个**工具结果**回给模型,内容写明被用户拒绝以及原因。模型拿到这个结果就能理解发生了什么,并给出替代方案,比如从「删除这批报告」改成「先归档这批报告」。
- 整个运行因此仍然是成功结束的,不是错误结束。这一点在事件上是有区别的:应该是带成功结局的运行结束,而不是运行错误。
- 界面上也要区分:拒绝状态不要渲染成红色报错。用户会以为自己做错了什么,而他只是做了个选择。给它一个独立的中性状态。
- 一条可迁移的判断标准,值得主动说出来:**只有系统真的出故障时才用错误态。用户的选择,无论是什么,都不是故障。** 这条在表单校验、权限拒绝、支付取消等场景同样适用。
Key points
- Rejection is not failure: the user declined an action, not the conversation.
- Return the refusal as a tool result with the reason so the model can propose an alternative.
- The run still ends with a success outcome rather than emitting a run error.
- Give rejection its own neutral state in the UI instead of a red error.
- General rule: reserve the error state for actual system failures; a user's choice is not one.
答题要点
- 拒绝不是失败:用户只是不想执行这个操作,不是想让对话崩掉。
- 把拒绝作为工具结果回给模型,写明被拒绝与原因,让它给出替代方案。
- 整个运行仍以成功结局结束,而不是发出运行错误事件。
- 界面上给拒绝一个独立的中性状态,不要渲染成红色报错。
- 通用判据:只有系统真出故障才用错误态,用户的选择不是故障。
An agent fires three tool calls concurrently and their events interleave on arrival. How do you keep them straight?Agent 并发发起了三个工具调用,事件交错着到达前端,你怎么保证它们各自归位?
Common in ChinaCommon overseasIntermediate#streaming#state-management#tool-callingHow to reason about it · think before answering
- This checks whether you know not to rely on arrival order in a streaming protocol. Pushing to an array works perfectly with one tool call and breaks entirely under concurrency, in a way that only shows up in production.
- The answer is direct: **group by tool call id**, using a map keyed on that id rather than an array. Every event carries the id, so any argument fragment or result can find its card.
- Worth generalizing: yesterday message deltas merged by message id, today tool events merge by tool call id, tomorrow subagent events merge by subagent run id. **Everything in a streaming protocol merges by identifier, never by order** — that is the transferable lesson.
- A protocol detail worth mentioning: tool events also carry a parent message id, which lets the UI place the call under the right message in the timeline instead of piling every tool card at the end.
- Implementation-wise, mind the timeline itself: messages and tool cards interleave, so alongside the two maps you usually need an ordered list of what appeared when, or you have the data but no rendering order.
- Expect the follow-up about the same tool being called twice: each call has its own id, so you get two cards naturally. The real risk is keying on the tool name yourself.
分析过程 · 先想清楚再作答
- 这题在考你有没有意识到「流式协议里不能依赖到达顺序」。按数组顺序 push 的实现在单个工具调用时完全正常,一并发就全乱,而且是那种线上才复现的乱。
- 答案本身很直接:**按工具调用标识归组**,用一个以该标识为键的表,而不是数组。每个事件都带这个标识,所以任何一片参数、任何一个结果都能找到自己的卡片。
- 值得多说一句的是这条规则的普适性:昨天处理消息增量用消息标识归并,今天处理工具用工具调用标识归并,明天处理子 Agent 用子运行标识归并。**流式协议里的一切归并都靠标识,不靠顺序**——这是一条能迁移的判断。
- 顺带提一个协议设计细节:工具事件上还带父消息标识,这让界面能知道这次调用挂在哪条消息下面,从而在时间线上正确排版,而不是把所有工具卡片堆在末尾。
- 实现上还要注意时间线本身:消息和工具卡片是混排的,所以除了两张表之外通常还需要一个记录出现顺序的列表,否则你有数据但不知道该按什么顺序渲染。
- 可预期的追问是「同一个工具被调用两次怎么办」——每次调用有各自独立的标识,所以天然是两张卡片,不需要特殊处理。真正需要小心的是你自己生成键的时候不要用工具名当键。
Key points
- Group by tool call id using a map, never an array, and never rely on arrival order.
- It generalizes: streaming protocols merge by identifier — message id for messages, subagent run id for subagents.
- The parent message id on tool events places the card under the right message instead of at the end.
- Messages and tool cards interleave, so keep an ordered timeline list alongside the maps.
- Two calls to the same tool naturally produce two cards since each has its own id; never key on the tool name.
答题要点
- 按工具调用标识归组,用以标识为键的表而不是数组,绝不依赖到达顺序。
- 这是通用规则:流式协议里的归并一律靠标识,消息靠消息标识,子 Agent 靠子运行标识。
- 工具事件带的父消息标识用来把卡片排到正确的消息下面,而不是堆在末尾。
- 消息与工具卡片混排,所以还需要一个记录出现顺序的时间线列表。
- 同一工具调用两次天然是两张卡片,因为每次调用有独立标识;不要用工具名当键。
D4 Reasoning and Shared State: a Collapsible Thinking Panel and Incremental Sync
Should the model's reasoning be shown to users? Collapsed or expanded by default? Justify your choice.模型的推理过程要不要展示给用户?默认收起还是默认展开?说出你的理由。
Common in ChinaCommon overseasIntermediate#reasoning-ui#ux#protocol-designHow to reason about it · think before answering
- There is no single right answer here; the question tests whether you can articulate the criteria. Answering 'collapsed' or 'expanded' without reasoning scores nothing.
- Establish a fact first: reasoning contains false starts and self-correction, things like 'wait, that basis is wrong' or 'assume calendar quarters for now'. That alone rules out mixing it with the final answer, or the user reads an assistant that keeps contradicting itself.
- The case for collapsed by default: reasoning is process, not conclusion, and most users most of the time just want the answer. Expanding by default pushes the actual answer off-screen so they have to scroll for the thing they came for. General-purpose products should pick this.
- The case for expanded: debugging tools, or products where the reasoning is the value — math tutoring, code review, diagnostics. There the derivation is what the user is paying for.
- Either way, two things are mandatory. First, a live indicator during streaming: reasoning is often the longest silence in a run, easily ten seconds or more, and a static collapsed header reads as frozen. Show 'thinking... (N characters)' with a number that climbs. Second, visual separation from the final answer via lighter text or a left border, so nobody mistakes it for the conclusion.
- Expect the follow-up on how the protocol prevents mixing: AG-UI gives reasoning its own event group rather than a flag on text messages, so there is no code path that lets reasoning flow into a message bubble. **Errors made structurally impossible beat errors avoided by discipline.**
分析过程 · 先想清楚再作答
- 这题没有唯一答案,考的是你能不能说出判断依据。直接答「收起」或「展开」而不给理由,等于没答。
- 先确立一个事实:推理内容里常有试错和自我否定,比如「等等,这个口径不对」「先假设是自然季度」。这决定了它不能和正式回答混在一起显示,否则用户读到的是一个来回改口的助手。
- 默认收起的理由:推理是过程不是结论,多数用户多数时候只想要答案。默认展开会把正式回答挤到屏幕外,用户还得往下滚才能看到自己真正要的东西。通用产品应该选这个。
- 默认展开的适用场景:调试工具,或者推理本身就是产品价值的一部分——数学解题、代码审查、诊断类工具,用户买的就是那个推导过程。
- 不管选哪个,有两件事都要做。一是流式期间必须给一个活的提示:推理往往是整次运行里最长的一段静默,可能十几秒,折叠标题一动不动用户会以为卡住了,所以要显示「正在思考…(已 N 字)」让那个数字涨起来。二是视觉上必须和正式回答明确区分,用更浅的颜色或一道左边框,让人一眼看出这不是最终答案。
- 可预期的追问是「协议层面怎么保证不混」——AG-UI 给推理单独开了一组事件而不是在文本消息上加标记。这样渲染代码里根本没有路径能让推理流进消息气泡,**结构上做不到的错误比靠纪律避免的错误可靠**。
Key points
- Reasoning contains false starts, so it must be separated from the final answer.
- Collapse by default in general products: reasoning is process, and expanding pushes the answer off-screen.
- Expand by default for debugging tools or products where the derivation is the value.
- Always show a live indicator while streaming, or a ten-second silence reads as frozen.
- Use a separate event group rather than a flag, making the mixing error structurally impossible.
答题要点
- 推理含试错和自我否定,必须与正式回答分开,否则用户读到一个来回改口的助手。
- 通用产品默认收起:推理是过程不是结论,默认展开会把答案挤到屏幕外。
- 调试工具或推理本身即价值的产品(解题、审查、诊断)可以默认展开。
- 流式期间必须有活的提示(正在思考加字数),否则十几秒静默会被当成卡住。
- 协议层用独立事件组而不是加标记,让「推理流进消息气泡」在结构上不可能发生。
Shared state can be sent as full snapshots or as incremental patches. How do you decide which?共享状态既可以每次发完整快照,也可以发增量补丁,你怎么决定用哪种?
Common in ChinaCommon overseasIntermediate#state-sync#protocol-design#architectureHow to reason about it · think before answering
- This asks for a decision rule, not a symmetric list of pros and cons. 'Snapshots are simple, patches save bandwidth' leaves the interviewer unsure what you would actually do.
- Offer an actionable principle: **patches are an optimization, snapshots are the correctness guarantee**. When in doubt, an extra snapshot is always safe, while a missing one can leave the two sides permanently out of sync.
- Then split by situation: session start and first load must be a snapshot, since there is nothing to patch; routine progress updates use patches because resending a large state is wasteful; and detecting a failed patch or reconnecting after a refresh calls for a snapshot to realign.
- One easily missed case worth raising unprompted: **receiving a patch with no local snapshot at all**. That means you missed the beginning. The right response is to flag desync and request a snapshot, not to invent an empty object and patch onto it, which fabricates state the server has never seen.
- If you want to go deeper on cost: patches cost more than bandwidth, they add implementation complexity and a whole class of failure modes (reordering, loss, version drift). When state is small, sending snapshots exclusively is a perfectly sound engineering choice.
- Expect the follow-up on detecting divergence: a patch that fails to apply is the most direct signal, which is why failure handling has to be right.
分析过程 · 先想清楚再作答
- 这题考的是你会不会给出判据,而不是罗列两者的优缺点。「快照简单、增量省流量」这种对称的罗列,面试官听完不知道你到底会怎么选。
- 给一条能落地的原则:**增量是优化,快照是正确性的保障**。拿不准的时候多发一次快照永远是安全的,而少发一次快照可能让两端永久性地对不上。
- 然后按场合分:会话开始、界面第一次拿到状态,必须用快照——没有底就没法打补丁;工作推进中的常规更新用增量,状态可能很大,每次重发浪费带宽;检测到补丁打不上、或者用户刷新页面重新连接,用快照重新对齐。
- 还有一个容易漏的场景值得主动提:**收到增量但本地根本没有快照**。这说明漏了开头,正确反应是标记不同步并请求快照,而不是凭空造一个空对象往上打补丁——那样会造出一份服务端从没见过的数据。
- 如果要展开成本讨论:增量的代价不只是带宽,还有实现复杂度和一整类新的失败模式(乱序、丢包、版本漂移)。状态本身很小的时候,一律发快照是完全合理的工程选择,不要为了显得先进而引入增量。
- 可预期的追问是「怎么知道两端对不上了」——补丁打不上就是最直接的信号,这也是为什么补丁的失败处理必须做对,见下一题。
Key points
- The rule: patches optimize, snapshots guarantee correctness; an extra snapshot is always safe.
- Snapshot on session start and reconnect; patch for routine progress updates.
- Use a snapshot to realign whenever a patch fails or divergence is detected.
- A patch with no local snapshot means you missed the start: request a snapshot rather than inventing empty state.
- For small state, snapshots only is a sound choice; patches add reordering, loss, and drift as failure modes.
答题要点
- 原则是增量为优化、快照为正确性保障;拿不准时多发快照永远安全。
- 会话开始与重连用快照,工作推进中的常规更新用增量。
- 补丁打不上或检测到不一致时,用快照重新对齐。
- 收到增量但本地没有快照,说明漏了开头,要请求快照而不是凭空造一个空状态。
- 状态本身很小时一律发快照是合理选择,增量会带来乱序丢包漂移这一整类失败模式。
Your frontend receives a JSON Patch that cannot be applied. What does that tell you, and how do you recover?前端收到一个打不上的 JSON Patch,说明发生了什么?你的恢复策略是什么?
Common in ChinaCommon overseasDeep dive#state-sync#error-handling#json-patchHow to reason about it · think before answering
- The discriminator is whether you treat a failed patch as a bug or as an expected event. People who see it as a bug answer 'add logging' and then have no recovery story.
- Characterize it first: failed patches are **expected**, not programming errors. Packet loss, reordering, and version drift after a server restart all cause them. The meaning is singular — **the two sides have diverged**.
- So requirement one: `applyPatch` must **return a result rather than throw**, letting the caller decide. Throwing escalates a recoverable sync problem into a crashed interface.
- Requirement two is **all or nothing**: if the third operation in a batch fails, the first two must leave no trace. Apply to a deep-cloned draft and discard the whole draft on failure, returning the original. A half-applied state is worse than none, because it is data neither side has ever seen, and every subsequent patch builds on that fiction.
- For recovery, weigh three options: throwing and crashing (the user loses the whole session over one patch); silently ignoring and showing stale data (the most dangerous, since the user sees possibly wrong data and does not know it); and **flagging desync while awaiting a fresh snapshot** (the right answer). Keep the old data visible but add a clear notice, because showing possibly wrong data is worse than showing 'syncing'.
- Expect the follow-up on whether `replace` to a missing key should be leniently treated as `add`: no. The server sending replace means it believes the key exists and you do not have it, which is itself the divergence signal. Quietly patching over it hides the problem until it surfaces somewhere harder to debug.
分析过程 · 先想清楚再作答
- 这题的区分度在于你把打不上当成 bug 还是当成可预期事件。当成 bug 的人会答「加日志排查」,然后就没有恢复策略了。
- 先定性:补丁打不上是**可预期的**,不是程序错误。丢包、乱序、服务端重启导致的版本漂移都会造成这种情况。它的含义只有一个——**两端状态已经不一致了**。
- 所以实现上第一条要求是:`applyPatch` 失败时**返回结果而不是抛异常**,让调用方决定怎么办。抛异常等于把一个可恢复的同步问题升级成界面崩溃。
- 第二条要求是**全有或全无**:一组补丁里第三条失败了,前两条也不能留下痕迹。做法是在深拷贝的副本上执行,失败就整个丢弃、返回原状态。半应用的状态比不应用更糟——你会得到一份服务端和客户端谁都没见过的数据,之后所有补丁都建立在这份幻觉上。
- 恢复策略在三个选项里选:抛异常崩掉(用户丢掉整个会话,代价远大于一条补丁);静默忽略继续显示旧数据(最危险,用户看到可能错的数据而且不知道它错了);**标记不同步并等服务端补发快照重新对齐**(正确答案)。界面上保留旧数据但加一条明确提示,因为显示一份可能是错的数据比显示「正在同步」更糟。
- 可预期的追问是「replace 到不存在的键要不要宽容处理成 add」——不要。服务端发 replace 说明它认为那个键存在而本地没有,这本身就是不一致的信号,悄悄补上等于把问题藏到更难查的时候。严格失败反而把隐蔽的数据问题变成可立刻恢复的明确信号。
Key points
- A failed patch is an expected event, not a bug; it means the two sides have diverged.
- applyPatch should return a failure result rather than throw, so a sync issue does not become a crash.
- All or nothing: apply to a clone, discard the whole batch on failure, never leave half-applied data.
- Recover by flagging desync and awaiting a snapshot, keeping old data visible with a clear notice.
- Silent ignoring is the most dangerous option, since users see possibly wrong data unknowingly.
答题要点
- 打不上是可预期事件而非 bug,含义是两端状态已经不一致。
- applyPatch 失败要返回结果而不是抛异常,别把同步问题升级成界面崩溃。
- 必须全有或全无:在副本上执行,失败整组丢弃返回原状态,不留半应用数据。
- 恢复策略是标记不同步并等服务端补发快照,界面保留旧数据但加明确提示。
- 静默忽略是最危险的选项,因为用户看到可能错的数据却不知道它是错的。
What belongs in a shared state panel versus in the conversation itself?什么样的信息该放进共享状态面板,什么样的该留在对话里?
Common in ChinaCommon overseasIntermediate#information-architecture#ux#agent-uiHow to reason about it · think before answering
- It looks like a product question but rests on a sharply technical criterion, and stating it shows you understand the underlying difference.
- The rule in one line: **chat appends, state overwrites**. Information that updates in place belongs in the state panel; information that happens once belongs in the conversation.
- Apply it and it works cleanly: a workflow on step three, a metrics set filling in, a form being completed — all update in place, so they belong in the panel. As chat messages they become a dozen 'step 1 done' and 'step 2 done' posts, leaving the user to reconstruct the current state mentally.
- Conversely, the model's explanations, questions, and final conclusions are one-time utterances and belong in the conversation. Forcing them into a panel destroys temporal order, which is precisely what the transcript is for.
- There is a middle ground worth raising: tool calls. They have both process and result, and this course places them as cards on the **conversation timeline**, because a call is an action initiated at a moment and its position in time is meaningful, while its state changes happen inside the card. So a third form exists: mutable cards on a timeline.
- Expect the follow-up on whether the state panel should show history: usually not. Its value is 'what things are now'. When history matters, build a separate timeline or diff view rather than mixing two mental models on one screen.
分析过程 · 先想清楚再作答
- 这题看着像产品问题,其实有一条很技术的判据,答出来就说明你理解了两者的本质差别。
- 判据一句话:**聊天是追加的,状态是覆盖的**。会原地更新的信息放状态面板,只发生一次的信息放对话。
- 套上去很好用:一个走到第三步的流程、一份逐步补全的指标、一个正在被填写的表单,都会原地更新,所以属于状态面板。如果做成聊天消息,你会得到十几条「已完成第 1 步」「已完成第 2 步」的刷屏,用户还得自己在脑子里拼出当前状态。
- 反过来,模型的解释、提问、最终结论都是一次性的表达,属于对话。硬塞进状态面板会丢掉时间顺序,而对话的价值恰恰在于它记录了「什么时候说了什么」。
- 有个中间地带值得主动提:工具调用。它既有过程(参数准备、执行中)又有结果,本课的做法是把它作为一张卡片放在**对话时间线**上,因为它是「某个时刻发起的一次动作」,时间位置有意义;而它的状态变化发生在卡片内部,不影响时间线。这说明第三种形态是存在的——时间线上的可变卡片。
- 可预期的追问是「那状态面板要不要显示历史」——通常不要。面板的价值就是「当前是什么样」,需要历史时应该是一个独立的时间线视图或者版本对比,不要把两种心智模型混在一块屏幕里。
Key points
- The rule: chat appends, state overwrites. In-place updates go to the panel; one-time events go to the conversation.
- Workflow steps, accumulating metrics, and forms belong in the panel; as messages they spam the transcript.
- Explanations, questions, and conclusions are one-time utterances and belong in the conversation.
- Tool calls are the middle form: mutable cards on the timeline, since when they were initiated matters.
- Panels generally should not show history; build a separate timeline or diff view when history matters.
答题要点
- 判据是聊天追加、状态覆盖:会原地更新的进状态面板,只发生一次的进对话。
- 流程步骤、逐步补全的指标、被填写的表单属于状态面板,做成消息会刷屏。
- 模型的解释、提问、结论是一次性表达,属于对话,塞进面板会丢掉时间顺序。
- 工具调用是中间形态:作为可变卡片放在对话时间线上,因为发起时刻有意义。
- 状态面板通常不显示历史,需要历史应另做时间线或版本对比视图。
D5 Three Paradigms of Generative UI: From Component Mapping to Declarative Interfaces
How much control do the three generative UI paradigms hand to the model, and which would you choose in production?生成式界面的三种范式分别把多少控制权交给模型?你在生产里会选哪种?
Common in ChinaCommon overseasIntermediate#generative-ui#architecture#decision-makingHow to reason about it · think before answering
- You need to describe all three, but the real signal is whether you have a criterion for classifying new approaches rather than three memorized names.
- The taxonomy: static generative, where the frontend owns components and the model only picks one and fills data; declarative, where the model returns a UI description tree that the frontend renders against a whitelist and its own styling; and open-ended, where the model emits UI code directly. Freedom increases, control decreases.
- Then the criterion, which is the heart of the answer: **does the model produce data or code?** The first two produce data, which can be validated, whitelisted, and degraded when unrecognized. The third produces code, and executing code has no middle ground — you cannot partially run a script. This criterion classifies any new approach, whatever it is branded.
- For production, give a clear escalation path: **start static, move to declarative when you need layout freedom, and avoid open-ended unless you have a strong reason and a complete sandbox.** Committing to a recommendation shows more judgment than listing pros and cons three times.
- Add the cost view: generative UI is not free. Registry upkeep, prop validation, fallback copy, and tests are ongoing costs, justified only when the range of interfaces genuinely varies.
- Expect the follow-up on when not to use it at all: if you can enumerate every possible interface and the list is short, hardcode the mapping. Also be careful with forms, since a model-assembled form leaves validation, submission, and error handling for you to catch.
分析过程 · 先想清楚再作答
- 这题要能把三种范式说清楚,但真正的区分度在于你有没有一条能判断新方案的判据,而不是背下三个名字。
- 先给分类:静态生成式,前端定组件、模型只选哪个并填数据;声明式,模型返回一棵界面描述树、前端按白名单和自己的样式渲染;开放式,模型直接产出界面代码。自由度递增,可控性递减。
- 然后给判据,这是答案的核心:**模型产出的是数据还是代码**。前两种是数据,可以校验、可以过白名单、可以在不认识时降级;第三种是代码,一旦执行就没有中间地带,你没法部分执行一段脚本。这条判据能用来归类任何新出现的方案,包括那些起了新名字的。
- 生产选型给一条明确的递进路径:**从静态开始,需要布局自由度时升到声明式,除非有非常强的理由并准备好完整沙箱,否则不用开放式。** 敢给出明确建议比罗列三种各有优劣更能体现判断力。
- 补一句成本视角:生成式界面不是免费的,注册表维护、props 校验、兜底文案、测试都是持续成本。只有界面形态真的多变时才划算。
- 可预期的追问是「什么时候根本不该用」——如果你能列出全部可能的界面形态而且这个列表不长,那就写死映射,不要引入生成式。另外表单类交互要特别小心,模型临时组合出来的表单,它的校验、提交、错误处理都要你自己接住。
Key points
- The three are static generative, declarative, and open-ended, with rising freedom and falling control.
- The criterion is whether the model emits data or code: data can be validated and degraded, code cannot be partially executed.
- Production path: start static, escalate to declarative for layout freedom, avoid open-ended without a full sandbox.
- Generative UI carries ongoing costs and only pays off when interface shapes genuinely vary.
- If you can enumerate every interface and the list is short, hardcode the mapping instead.
答题要点
- 三种范式是静态生成式、声明式、开放式,自由度递增而可控性递减。
- 判据是模型产出的是数据还是代码:数据能校验能降级,代码一旦执行没有中间地带。
- 生产路径:从静态开始,需要布局自由度升到声明式,没有完整沙箱不用开放式。
- 生成式有持续成本(注册表、校验、兜底、测试),只有形态真多变时才划算。
- 能列全所有界面形态且列表不长时,直接写死映射,不要用生成式。
The model returns a component type you never registered. How should the interface handle it?模型返回了一个你没注册过的组件类型,界面应该怎么处理?
Common in ChinaCommon overseasIntermediate#generative-ui#error-handling#resilienceHow to reason about it · think before answering
- It looks like a detail but tests your habits around partial failure. 'Throw' or 'render nothing' are too coarse and do not survive follow-ups.
- Principle one: **degrade that block, do not fail the whole response**. An unrecognized component should not blank out the entire reply, since the rest is usually still useful. The same holds in the declarative paradigm: degrade the unknown node to plain text and render its siblings normally.
- Principle two: **distinguish the two failure modes**. A component missing from the registry and a registered component with invalid data point in completely different directions — the first suggests unclear prompt documentation or an upstream addition the frontend has not caught up with, the second means the fields are wrong. Collapsing both into 'render failed' leaves you nowhere to start.
- Principle three is copy: the fallback is for **users**, not a place to dump technical errors. 'This version does not support X yet; the rest is unaffected' beats 'Unknown component type', and it hints that upgrading may help.
- One more guard worth raising unprompted: **a nesting depth limit**. Declarative trees can be deep enough to blow the stack, and that needs no malice, only a recursive generation that wanders. Cap it, truncate, and leave a visible note.
- Expect the follow-up on reducing occurrences: put the registry descriptions into the system prompt and write them well. Models usually pick the wrong component because that documentation was vague — an easily overlooked debugging entry point.
分析过程 · 先想清楚再作答
- 这题看着是个小细节,实际考的是你对「部分失败」的处理习惯。答「报错」或者「不渲染」都太粗糙,接不住追问。
- 第一条原则:**降级这一块,而不是让整个回答失败**。模型给了一个不认识的组件,不该让整条回复变成空白——其余内容通常仍然有用。这一条在声明式范式里同样成立:树里一个节点不认识,就把那个节点降级成纯文本,其余节点照常渲染。
- 第二条:**两种失败要分开**。组件名不在注册表里,和组件名认识但数据不合法,对排查的指向完全不同。前者说明 prompt 里的说明不清楚或者上游加了新组件而前端没跟上,后者说明字段错了。合并成一个「渲染失败」会让排查无从下手。
- 第三条是文案:兜底界面是给**用户**看的,不是把技术错误抛给用户。「这个版本还不支持某某,其余内容不受影响」比「Unknown component type」有用得多,而且前者暗示升级可能解决,后者什么都没说。
- 还有一个容易漏的防护值得主动提:**嵌套深度上限**。声明式的树可能深到爆栈,这不需要恶意,递归生成跑偏就够了。设个上限,超了截断并留一句可见说明。
- 可预期的追问是「怎么减少这种情况」——把注册表说明写进 system prompt 并写清楚,模型选错组件多半是那段说明写得不好。这是个容易被忽略的调试入口。
Key points
- Degrade that block rather than failing the whole response; the rest is usually still useful.
- Report unknown-component and invalid-props separately, since they point to different causes.
- Write fallback copy for users, describing the effect and its scope, not a raw technical error.
- Declarative trees also need a depth cap, since runaway recursive generation can blow the stack.
- Reduce occurrences by writing clear registry descriptions into the system prompt.
答题要点
- 降级这一块而不是让整个回答失败,其余内容通常仍然有用。
- 未注册组件与数据不合法要分开报,两者对排查的指向完全不同。
- 兜底文案写给用户看,说明现象与影响范围,不要抛技术错误。
- 声明式树里还要设嵌套深度上限,递归生成跑偏就能产出爆栈的树。
- 减少这类情况的入口是把注册表说明写清楚并放进 system prompt。
What are the risks of having a model generate frontend code that runs in the user's browser, and how would you bound them?让模型直接生成前端代码并在用户浏览器里执行,有哪些风险?你会怎么设边界?
Common in ChinaCommon overseasDeep dive#security#generative-ui#prompt-injectionHow to reason about it · think before answering
- A security question where the signal is naming the attack path, not saying 'it is unsafe'.
- Start with the core: **the generated code runs with your page's privileges**. It can read cookies and localStorage, issue arbitrary requests, and modify any DOM. That is not theoretical; any script on the page has those powers.
- Then the step most candidates miss: **the model is influenced by whatever it reads**. An email, a web page, or a user-uploaded document can carry instructions to emit malicious code. So the attack path **does not require the model to turn malicious, only to be obedient** — it faithfully follows instructions it found in a document an attacker wrote. Naming this shows you understand prompt injection compounded by generative UI.
- On bounding it: the preferred answer is not to use the paradigm at all and use declarative instead, where the model emits data rather than code and you can validate, whitelist, and degrade. That is the only approach that truly removes the risk.
- If you must, a sandbox is the floor: a separate-origin iframe with a strict CSP, no same-origin access, and no credentials passed in. Be honest about the two costs: sandbox escapes have a long history so the risk is reduced rather than eliminated, and the sandbox strips the generated UI of the ability to interact with the host app — **which was the reason to want open-ended in the first place**.
- Expect the follow-up on why anyone ships it: mostly internal tools and demos, where inputs are controlled and the audience is trusted. The deciding question is whether the model's inputs can come from untrusted sources.
分析过程 · 先想清楚再作答
- 这是道安全题,区分度在于你能不能说出攻击路径,而不是泛泛地说「不安全」。
- 先说清楚风险的核心:**模型产出的代码会以你的页面权限运行**。它能读 cookie、读 localStorage、发任意请求、改任意 DOM。这不是理论风险,页面里的脚本本来就有这些能力。
- 然后是关键的一步,也是多数人答不出来的:**模型是可以被它读到的内容影响的**。一封邮件、一个网页、一份用户上传的文档,里面都可能藏着让它产出恶意代码的指令。所以这条攻击路径**不需要模型变坏,只需要它听话**——它忠实执行了它在文档里读到的指令,而那份文档是攻击者写的。能说出这一层,就说明你理解了提示注入与生成式界面叠加起来的后果。
- 边界怎么设:首选是根本不用这种范式,改用声明式——模型给的是数据不是代码,能校验能白名单能降级。这是唯一能真正消除风险的做法。
- 如果非用不可,沙箱是最低要求:独立 origin 的 iframe 加严格 CSP、禁用同源访问、不传任何凭据进去。但要诚实说出它的两个代价:沙箱逃逸的历史很长,不能算完全消除;而且沙箱会让生成的界面失去与主应用交互的能力,**而那恰恰是当初想用开放式的理由**。
- 可预期的追问是「那业界为什么还有人做」——做的多是内部工具或者演示场景,那里输入可控、受众可信。判断依据是模型读到的内容是不是可能来自不可信来源。
Key points
- The core risk is that generated code runs with page privileges: cookies, storage, arbitrary requests, full DOM access.
- The attack needs no malicious model, only an obedient one, since instructions can hide in emails, pages, or uploaded documents.
- The preferred boundary is switching to declarative so the model emits data that can be validated, whitelisted, and degraded.
- If unavoidable, the floor is a separate-origin iframe with a strict CSP and no credentials.
- Be honest that sandboxing reduces rather than removes risk, and costs the interaction that motivated open-ended in the first place.
答题要点
- 核心风险是生成的代码以页面权限运行,能读 cookie 与本地存储、发任意请求、改任意 DOM。
- 攻击路径不需要模型变坏只需要它听话:它读到的邮件、网页、文档里可能藏着指令。
- 首选边界是改用声明式范式,让模型产出数据而非代码,可校验可白名单可降级。
- 必须用时的最低要求是独立 origin 的 iframe 加严格 CSP 且不传凭据。
- 要诚实承认沙箱的代价:不能完全消除风险,且会让生成界面失去与主应用交互的能力。
Structured output also streams. How should the interface render a JSON payload that is only half complete?结构化输出也是流式生成的,界面在 JSON 只到一半时应该怎么渲染?
Common in ChinaCommon overseasIntermediate#streaming#generative-ui#ui-stateHow to reason about it · think before answering
- Not a hard question, but a good check on whether you see incomplete data as the norm in streaming systems rather than an exception.
- The direct answer: render if it parses, show a placeholder if it does not, and **never display the partial JSON**. Half-formed JSON on screen reads as a crash.
- Worth abstracting one level: this pattern recurs. In this course it appears three times — partial message deltas, partial tool arguments, partial generative payloads. **Every place that receives streaming data must answer the same question: what do you show when it is half there?**
- Placeholder choice matters: if you already know which component is coming (because the component field arrived first), show that component's skeleton so the user sees 'a table is being generated' rather than a generic spinner. Field order in the payload is therefore designable — put the shape-determining fields first.
- An implementation detail: do not attempt a parse on every character, which is pure waste. Throttle per frame or wait for a clear boundary such as the end event. Same thinking as the per-frame batching from day two.
- Expect the follow-up on making partial JSON usable: streaming JSON parsers can emit partial objects so completed fields render early. The costs are complexity and having to handle fields that later change. For most cases, placeholder then full render is enough.
分析过程 · 先想清楚再作答
- 这题本身不难,但它是个很好的检验:你有没有意识到「不完整的数据」在流式系统里是常态而不是异常。
- 直接答案:能 parse 就渲染,不能就显示占位,**绝不把半截 JSON 原文显示给用户**。半截 JSON 出现在界面上,用户会以为程序崩了。
- 值得往上抽一层的是这个模式的重复性。本课里它出现了三次:消息增量拼到一半、工具参数拼到一半、生成式载荷拼到一半。**每一处接收流式数据的地方都要回答同一个问题:只到一半时显示什么。**
- 占位内容的选择有讲究:如果知道要渲染的是什么组件(比如 component 字段已经先到了),可以显示那个组件的骨架屏,用户看到的是「表格正在生成」而不是一个通用转圈。载荷字段的顺序因此是可以设计的——把决定形态的字段放前面。
- 另一个实现细节:不要每来一个字符就试着 parse 一次,那是纯粹的浪费。可以按帧节流,或者等到明显的边界(比如收到结束事件)再解析。这一条和 D2 的按帧合并是同一个思路。
- 可预期的追问是「有没有办法让部分 JSON 也能用」——有,流式 JSON 解析器可以产出部分对象,让已经到齐的字段先渲染。代价是实现复杂度,而且要处理「字段后来又变了」的情况。多数场景下先占位再整体渲染就够了。
Key points
- Render if it parses, otherwise show a placeholder, and never display the raw partial JSON.
- This is the general streaming pattern: message deltas, tool arguments, and generative payloads all pose the same question.
- Placeholders can be component-specific skeletons once the component field arrives, so payload field order is worth designing.
- Do not parse on every character; throttle per frame or wait for the end event, mirroring per-frame batching.
- Streaming JSON parsers can render completed fields early, at the cost of complexity and fields that may later change.
答题要点
- 能 parse 就渲染,不能就显示占位,绝不显示半截 JSON 原文。
- 这是流式系统的通用模式:消息增量、工具参数、生成式载荷都要回答同一个问题。
- 占位可以按已到达的组件字段显示对应骨架屏,所以载荷字段顺序是可设计的。
- 不要每个字符都试着 parse,按帧节流或等结束事件,思路同按帧合并。
- 流式 JSON 解析器能让已到齐的字段先渲染,代价是复杂度与字段可能回改。
D6 Session Control and Long Tasks: Stopping, Retrying, Branching, and Subagents
The user clicks stop. Is calling AbortController enough on the frontend? What does the server still need to do?用户点了停止,前端调用中止控制器就够了吗?服务端还需要做什么?
Common in ChinaCommon overseasIntermediate#abort-controller#cost-control#streamingHow to reason about it · think before answering
- The discriminator is whether you see 'the UI stopped' and 'the bill stopped' as two different things. Answering only 'call abort' usually means you have not worked on a cost-sensitive product.
- State the fact: `AbortController.abort()` closes **the frontend's side** of the connection. The server's run keeps going and keeps burning tokens. On a three-minute run stopped at ten seconds, an abort-only implementation still pays for the remaining two minutes fifty.
- So do both: abort for immediate UI response, plus an explicit cancel request so the server actually ends the run, typically by flagging it so the run loop exits at its next check.
- Some runtimes detect a closed connection and terminate on their own, but **do not rely on it**: behind a gateway, a load balancer, or server-side buffering, that signal may never reach the run loop. Explicit cancellation is the only reliable path.
- Raise the easy trap unprompted: the cancel request **must not reuse the already-aborted signal**, or it is cancelled before it is sent. The bug is nearly invisible because the UI behaves correctly — only the bill reveals it.
- Expect the follow-up on what the UI shows after stopping: aborting `fetch` throws an AbortError, and **it must not be rendered as an error**. The user pressed stop; showing a red failure is wrong. Same judgment as 'a rejected tool call is not a failure': a user's choice is not a fault.
分析过程 · 先想清楚再作答
- 这题的区分度在于你有没有意识到「界面停了」和「账单停了」是两件事。只答「调 abort」的人通常没做过成本敏感的项目。
- 先说清楚事实:`AbortController.abort()` 关闭的是**前端这一侧**的连接。服务端那边的运行还在跑,token 还在烧。一个跑三分钟的 Agent,用户第十秒点停止,只做 abort 的话剩下两分五十秒的费用照付。
- 所以正确做法是两件事一起做:abort 让界面立刻响应,另外发一个显式的取消请求让服务端真的结束这次运行。服务端收到后给那个运行打取消标记,由运行循环在下一步检查时退出。
- 有些运行时能感知连接关闭并自动终止,但**不能依赖它**:经过网关、负载均衡、或者服务端有缓冲时,连接关闭的信号可能根本传不到运行循环。显式取消是唯一可靠的。
- 一个很容易踩的坑值得主动说:取消请求**不能复用同一个已经 abort 的 signal**,否则它在发出前就被取消掉了。这个 bug 极隐蔽,因为界面表现完全正常(确实停了),只有账单会告诉你真相。
- 可预期的追问是「打断后界面该显示什么」——`fetch` 被 abort 会抛 AbortError,**不要把它当错误渲染**。用户只是点了停止,弹一个红色报错说「出错了」是错的。这和「用户拒绝工具调用不是失败」是同一条判断:用户的选择不是故障。
Key points
- Abort closes only the client side; the server run continues and keeps consuming tokens.
- Do both: abort for instant UI feedback, plus an explicit cancel request so the server actually stops.
- Do not rely on the server noticing a closed connection; gateways and buffering can swallow that signal.
- Never reuse the aborted signal for the cancel request, or it is cancelled before sending.
- Do not render AbortError as a failure; a deliberate stop is not a fault.
答题要点
- abort 只关闭前端这一侧的连接,服务端的运行还在跑、token 还在烧。
- 正确做法是两件事一起做:abort 让界面立刻停,显式取消请求让服务端真的结束。
- 不能依赖服务端自动感知连接关闭,经过网关或有缓冲时那个信号可能传不到。
- 取消请求不能复用已 abort 的 signal,否则它在发出前就被取消,界面正常但账单不停。
- AbortError 不要当错误渲染,用户主动打断不是故障。
Should conversation history be an array or a tree? How do edit-and-resend and branching affect that choice?会话历史用数组还是用树?编辑重发和分支这两个需求会怎么影响你的选择?
Common in ChinaCommon overseasIntermediate#data-structure#session-management#uxHow to reason about it · think before answering
- This tests deriving a data structure from requirements. Answering 'a tree' without the why does not survive 'why not an array?'
- Name the array's dead end: if the user wants to revise something three turns back, an array leaves exactly one option — **discard everything after it**. One edited sentence costs a dozen turns.
- But the user's actual intent is usually to **compare two phrasings**, not to throw away what followed. An array fundamentally cannot express that.
- A tree makes it natural: edit-and-resend branches from that turn's **parent**, leaving the old branch intact under the same parent. The UI shows '2 / 3' with arrows, and rendering walks only the path from root to the active leaf.
- Call out the line most easily got wrong: when attaching the new node, append to the parent's children rather than replacing them. Replacing silently degrades the tree back to linear history with no visible symptom, and silent degradation is the worst kind.
- Expect the follow-up on what a tree complicates: scroll position, search highlighting, and exporting all have to answer 'which path?'. So the deciding factor is whether the product needs branching at all. For one-shot Q&A, an array is fine, and adopting a tree early buys complexity you will not use.
分析过程 · 先想清楚再作答
- 这题考的是从需求推数据结构的能力。直接答「用树」而不说为什么,接不住「数组不行吗」的追问。
- 先把数组方案的死角说清楚:用户想改三轮前的一句话重新问,数组方案只有一个选择——**丢掉后面的全部内容**。用户改一句话,代价是十几轮对话没了。
- 而用户的真实意图往往是**对比两种问法的结果**,不是把后面的都不要了。数组结构从根本上表达不了这个意图。
- 树的模型下这件事很自然:编辑重发等于从这一轮的**父节点**分叉出一个新分支,旧分支完整保留挂在同一个父节点下。界面上用「2 / 3」加左右箭头切换。渲染时只画从根到当前活动叶子的那条路径。
- 实现上最容易写错的一行值得说出来:把新节点挂到父节点下时,父节点原有的 children 不能覆盖只能追加。写成覆盖就悄悄退化成线性历史了,而界面上什么异常都看不出来——这种静默的退化最危险。
- 可预期的追问是「树会不会让别的功能变复杂」——会。滚动定位、搜索高亮、导出会话都要处理「哪条路径」的问题。所以判断依据是产品到底要不要分支:只做一次性问答的场景,数组完全够用,不要为了显得完备提前上树。
Key points
- With an array, edit-and-resend must discard everything after it, though users usually just want to compare phrasings.
- A tree branches from the parent, keeping the old branch intact and switchable.
- Render only the path from root to the active leaf; other branches sit in the background.
- Append rather than replace the parent's children, or the tree silently degrades to linear history.
- Trees complicate scrolling, search, and export; arrays are fine for one-shot Q&A.
答题要点
- 数组方案下编辑重发只能丢掉后面全部内容,而用户往往只是想对比两种问法。
- 树的模型下编辑重发是从父节点分叉,旧分支完整保留可切换对比。
- 渲染只画从根到活动叶子的那条路径,其余分支在背景待着。
- 实现要点是挂新节点时追加而不是覆盖父节点的 children,覆盖会静默退化成线性。
- 树会让滚动定位、搜索、导出变复杂,只做一次性问答时数组完全够用。
A stream breaks midway and the client reconnects to resume. How do you prevent duplicate messages?流断在一半,前端重连续播,怎么保证不出现重复的消息?
Common in ChinaCommon overseasDeep dive#resumption#deduplication#streamingHow to reason about it · think before answering
- The key is not the dedup mechanism but recognizing that **duplication is inevitable**, and what choosing the wrong dedup key costs you.
- Why it is inevitable: on resume the client says where it got to, but the server's recorded progress cannot be byte-exact — it may have logged 'event 12' while you received half of event 12. To avoid losing content it must replay a few extra. Deduplication is therefore unavoidably the client's job.
- The mechanism: give every event a **monotonically increasing sequence number** produced by the server. The client tracks the highest it has seen, sends it on resume, and drops anything at or below it.
- Then the critical rule: **never deduplicate by content**. Serializing events and skipping ones you have seen looks convenient, but a model can legitimately emit two identical deltas — two spaces, a repeated punctuation mark, the same word twice. Content-based dedup eats those, producing text that mysteriously loses characters, nearly impossible to reproduce.
- One more easily missed layer: **delta events cannot be deduplicated by identifier either**, since all deltas of a message share one messageId and you would keep only the first character. So classify your keys: idempotent events like start, end, and tool results dedupe by identifier; deltas rely on sequence numbers and otherwise pass through.
- Expect the follow-up on server support: persist an event log and support replay from a sequence number. Failing that, make the whole run idempotently re-runnable, though that is expensive for long tasks.
分析过程 · 先想清楚再作答
- 这题的关键不在「怎么去重」,而在你有没有意识到**重复是必然的**,以及去重的口径选错会造成什么。
- 先说为什么必然重复:续播时前端告诉服务端「我收到哪了」,但服务端记录的进度不可能精确到字节——它记的可能是第 12 条事件,而你实际收到的是第 12 条的一半。为了不丢内容,它只能往前多发几条。所以去重是前端跑不掉的责任。
- 做法是给每个事件一个**单调递增的序号**,由服务端产生。前端记住收到的最大序号,续播时带上它,收到序号小于等于它的事件就跳过。
- 然后是最关键的一条:**绝不按内容去重**。把事件序列化成字符串、见过就跳过,看起来很省事,但模型完全可能连续吐出两个一模一样的增量——两个空格、重复的标点、同一个词说两遍。按内容去重会把这些合法的重复吃掉,表现是正文莫名其妙少字,而且极难复现,排查成本极高。
- 还有一层容易漏:**增量类事件也不能按标识去重**。同一条消息的所有增量共用一个 messageId,按标识去重会让整条消息只剩第一个字。所以去重键要分类——开始、结束、工具结果这类幂等事件按标识去重,增量类靠序号,没有序号时一律放行。
- 可预期的追问是「服务端该怎么配合」——保存事件日志并支持从某个序号之后重放。如果做不到,退而求其次是让整次运行可重跑且结果幂等,但那对长任务代价太大。
Key points
- Duplication is inevitable since the server's replay boundary cannot be exact and it must over-send to avoid loss.
- Deduplicate with a server-issued monotonic sequence number, tracked client-side and sent on resume.
- Never dedupe by content: models legitimately emit identical consecutive deltas, and content dedup silently drops text.
- Deltas also cannot dedupe by identifier, since all deltas of one message share a messageId.
- Classify keys: identifier for idempotent events, sequence numbers for deltas, pass through when neither applies.
答题要点
- 重复是必然的:服务端的重放边界不可能精确,为了不丢内容只能多发几条。
- 用服务端产生的单调递增序号去重,前端记住最大序号并在续播时带上。
- 绝不按内容去重:模型会合法地连续吐出相同增量,按内容去重会让正文莫名少字。
- 增量类事件也不能按标识去重,同一条消息的增量共用 messageId。
- 去重键要分类:幂等事件按标识,增量靠序号,没有序号时放行。
An agent runs a three-minute task. How do you present progress in the UI?Agent 跑一个三分钟的长任务,界面上你会怎么表现进度?
Common in ChinaCommon overseasIntermediate#long-running-tasks#progress-ui#uxHow to reason about it · think before answering
- It looks open-ended but has one clearly wrong answer that costs you points: a percentage progress bar.
- Why not: **you do not know how many steps there are**. The agent may decide on two more tool calls, or converge early. Pretending otherwise yields a bar that stalls at 90%, which is worse than no bar — users conclude it hung, and stop trusting your progress indicators generally.
- The right approach shows **completed steps plus the current one**. The protocol has run-stage events with names; render them as a row where finished steps are ticked, the current one is highlighted, and nothing ahead is promised. That is honest and far more informative than a number, since the user can see whether it is 'gathering data' or 'writing the summary'.
- If the task spawns subagents, surface their individual progress too, grouped by subagent run id, since multiple subagents' events interleave and order-based assembly will cross the wires.
- One addition worth raising: **long tasks must account for an absent user**. Three minutes is plenty of time to switch away, so either support background continuation with a completion notification, or restore state correctly when the tab returns to the foreground. Foreground-only progress assumes the user is watching.
- Expect a follow-up on estimated time remaining: same answer, avoid it unless you have reliable historical data. An inaccurate ETA does the same damage as an inaccurate percentage.
分析过程 · 先想清楚再作答
- 这题看着开放,其实有一个明确的错误答案,答了就减分:画百分比进度条。
- 为什么不能画:**你不知道总共有几步**。Agent 可能中途决定多做两轮工具调用,也可能提前收敛。假装知道的结果是一个走到 90% 就卡住的进度条,而那比没有进度条更伤信任——用户会觉得程序挂了,而且以后再也不信你的进度条。
- 正确做法是显示**已完成的步骤加当前步骤**。协议层有运行阶段事件,每个阶段有名字,前端渲染成一行:已完成的打勾,当前的高亮,后面的不预告。这是诚实的,而且信息量比一个数字大得多——用户知道它在「收集数据」还是在「汇总成文」。
- 如果任务会派子 Agent,还要把它们各自的进展显示出来。归组靠子运行标识,因为多个子 Agent 的事件是交错到达的,按顺序拼一定串台。
- 有一个补充手段值得提:**长任务要考虑用户不在场的情况**。三分钟足够用户切走去干别的,所以要么支持后台继续并在完成时通知,要么在页面回到前台时能正确恢复显示。只做前台可见的进度,等于假设用户会盯着看。
- 可预期的追问是「那要不要显示预计剩余时间」——同理不要,除非你有可靠的历史数据做估算。不准的剩余时间和不准的百分比是同一类伤害。
Key points
- Avoid percentage bars: you do not know the step count, and stalling at 90% is worse than no bar.
- Show completed plus current steps from the protocol's run-stage events, promising nothing ahead.
- Surface each subagent's progress grouped by subagent run id, since their events interleave.
- Account for the user leaving: continue in the background with a notification, or restore correctly on return.
- Skip unreliable time estimates for the same reason as unreliable percentages.
答题要点
- 不要画百分比进度条:你不知道总共几步,卡在 90% 比没有进度条更伤信任。
- 显示已完成步骤加当前步骤,用协议的运行阶段事件,后面的不预告。
- 有子 Agent 时各自显示进展,按子运行标识归组,因为事件是交错到达的。
- 长任务要考虑用户切走:支持后台继续并通知,或回前台时正确恢复。
- 同理不要显示不准的预计剩余时间,除非有可靠的历史数据。
D7 Accessibility and Performance Budgets: Making Streaming Interfaces Work for Everyone
Why is pointing a live region at the streaming element wrong, and what should you do instead?为什么把实时区域直接指向流式渲染的元素是错的?正确做法是什么?
Common in ChinaCommon overseasDeep dive#accessibility#aria-live#streamingHow to reason about it · think before answering
- A strong discriminator, because the wrong approach is nearly everyone's first instinct and looks entirely reasonable — content is changing, so add aria-live and let the screen reader know.
- The problem is that both configurations fail, and you should name both: with aria-atomic true, every delta **re-announces the whole passage**, producing a stutter of restarts; with false, dozens of DOM mutations per second exceed the screen reader's pacing and it **skips them entirely**, so the user hears nothing.
- One more aggravating factor: NVDA, JAWS, and VoiceOver each handle high-frequency changes differently. 'It read fine on my machine' is especially unreliable here — you verified one third of the field.
- The right approach **decouples visual from auditory presentation**: visually the text keeps appearing character by character, while audibly nothing is announced until a sentence completes, at which point that sentence is pushed to the live region. Sighted users read progressively, screen reader users hear sentence by sentence, comparable pacing and neither is flooded.
- Two implementation details matter: keep a cursor of what has already been announced and push only the new portion, and add a character-count fallback for long text without terminal punctuation, or an unpunctuated passage never announces at all.
- Expect the follow-up on the live region element itself: three things. Visual hiding must not use display none or visibility hidden, since screen readers ignore those; use the standard absolutely-positioned clipped pattern. The region must be empty on page load, because a region that appears with content in it is not announced. And use polite rather than assertive, since assertive interrupts what the user is currently hearing, and an agent's reply is information, not an alarm.
分析过程 · 先想清楚再作答
- 这题的区分度极高,因为那个错误做法是几乎所有人的第一反应,而且它看起来完全合理——内容在变,加个 aria-live 让屏幕阅读器知道,有什么问题?
- 问题在于两种配置都不行,要能把两种都说出来:aria-atomic 为 true 时,每来一个增量就**重念整段**,用户听到的是不断从头开始的噪音;为 false 时,每秒几十次的 DOM 变化超出了屏幕阅读器的处理节奏,它会**直接跳过**,用户什么都听不到。
- 还有一层加重了问题:NVDA、JAWS、VoiceOver 三家对高频变化的处理各不相同。所以「我在我电脑上试过能读」在这里特别不可靠——你只验证了三分之一。
- 正确做法是把**视觉呈现与听觉呈现解耦**:视觉上文字继续逐字出现,听觉上流式期间完全静默,等一个句子完整了才把这一句推进实时区域。视觉用户逐字看,屏幕阅读器用户按句子听,节奏相当而且都不被淹没。
- 实现上有两个必须做对的细节:用一个已播报位置的游标,只推新增部分绝不重复;以及给没有句末标点的长文本一个字符数兜底,否则一段没有句号的文字会一直不播报,用户干等着。
- 可预期的追问是「实时区域元素本身有什么讲究」——三个:视觉隐藏不能用 display 为 none 或 visibility 为 hidden(那样屏幕阅读器也读不到),要用绝对定位加裁剪的标准写法;页面加载时必须是空的,带着内容出现的区域不会被播报;用 polite 不用 assertive,因为 assertive 会打断用户正在听的内容,而 Agent 的回复是信息不是警报。
Key points
- aria-atomic true re-announces everything per delta; false gets skipped entirely. Neither works.
- The three major screen readers differ on high-frequency changes, so single-machine verification is unreliable.
- Decouple visual from auditory: stay silent while streaming and announce sentence by sentence.
- Track what has been announced to avoid repeats, and add a character-count fallback for unpunctuated text.
- The region itself: never hide with display none, keep it empty on load, and use polite rather than assertive.
答题要点
- atomic 为 true 会每个增量重念整段,为 false 会被屏幕阅读器整个跳过,两种都不行。
- 三家屏幕阅读器对高频变化处理各不相同,单机验证不可靠。
- 正确做法是视觉与听觉解耦:流式期间静默,按句子完成时分段播报。
- 实现要点是只推新增部分不重复,以及给没有标点的长文本加字符数兜底。
- 实时区域本身:视觉隐藏不能用 display none,加载时必须为空,用 polite 不用 assertive。
What performance budgets would you set for an agent chat interface, and how would you measure each?你会给一个 Agent 聊天界面设哪几条性能预算?分别怎么测?
Common in ChinaCommon overseasIntermediate#performance#metrics#agent-uiHow to reason about it · think before answering
- This tests whether you choose metrics for the scenario rather than reciting a generic web performance list. Answering LCP, FID, and CLS suggests you have not considered what makes agent interfaces different.
- Start with why budgets exist: **without numbers there is no criterion**, and every 'is it fast enough' discussion degenerates into competing impressions. With a line drawn, over is over.
- Four budgets chosen for actual agent bottlenecks: time to first token (users are waiting for the model to speak, the single most important one, measured from click to first delta); longest single blocking task (beyond about 50ms typing feels laggy, measured with a long-task observer or the profiler); state updates per second (a direct signal of whether per-frame batching works, counted and divided by elapsed time); and DOM node count (memory and render cost in long sessions, a threshold for adopting virtualization).
- Worth volunteering: **first contentful paint is not on this list**. The metric traditional web performance cares most about matters far less here, because the user's anxiety is about when the model starts talking, not when the page finishes painting. Saying this shows you reason from the scenario.
- A small but important detail: metrics you have not measured should display as 'no data', never zero, since zero reads as passing.
- Expect the follow-up on exceeding budget: follow day two's order — profile first to locate the cost in rendering, parsing, or layout, then apply per-frame batching, lowered update priority, memoization, and two-pass rendering, leaving virtualization last since it complicates scroll anchoring and search.
分析过程 · 先想清楚再作答
- 这题在考你会不会按场景选指标,而不是背一份通用 Web 性能清单。直接答 LCP、FID、CLS 那几个,说明没想过 Agent 界面特殊在哪。
- 先说为什么要有预算:**没有数字就没有判据**,每次关于「够不够快」的讨论都会变成主观感受之争。定下线之后,超了就是超了。
- 四条按 Agent 界面实际瓶颈选的:首字延迟(用户等的是模型开口,这是最关键的一条,用点击到第一个增量到达的时间差测);最长单次阻塞(超过 50ms 用户就能感到输入卡顿,用长任务观察器或性能面板测);每秒状态更新次数(按帧合并有没有生效的直接指标,自己计数除以耗时);DOM 节点数(长会话的内存与渲染成本,超了说明该上虚拟化)。
- 值得主动说出来的是**首屏渲染时间不在这个表里**。传统 Web 最看重的指标在这里远不如首字延迟重要,因为用户的等待焦虑来自模型什么时候开口,不是页面什么时候画完。这一句能说明你是按场景思考的。
- 实现上有个小而重要的细节:没测到的指标要如实显示「没数据」而不是 0,显示 0 会让人误以为达标。
- 可预期的追问是「预算超了怎么办」——按 D2 的顺序处理:先量清楚瓶颈在渲染、解析还是布局,再依次上按帧合并、降低更新优先级、memo、两趟渲染,虚拟化放最后因为它会让滚动锚定和搜索全部变复杂。
Key points
- State the principle: without numbers there is no criterion, and budgets end arguments from impression.
- Four budgets: time to first token, longest blocking task, state updates per second, DOM node count.
- First contentful paint is deliberately absent: users await the model speaking, not the paint.
- Unmeasured metrics show 'no data', never zero, which would read as passing.
- When over budget, measure before tuning, and leave virtualization last since it complicates other features.
答题要点
- 先说原则:没有数字就没有判据,预算的价值是终结主观感受之争。
- 四条是首字延迟、最长单次阻塞、每秒状态更新次数、DOM 节点数。
- 首屏渲染刻意不在表里:用户等的是模型开口,不是页面画完。
- 没测到的指标显示「没数据」而不是 0,显示 0 会被误读成达标。
- 超标时按先量后调的顺序处理,虚拟化放最后因为它会让别的功能变复杂。
When a new message streams in, should keyboard focus follow it? Justify your answer.新消息流式到达时,键盘焦点应该跟着走吗?说出你的判断和理由。
Common in ChinaCommon overseasIntermediate#accessibility#keyboard#focus-managementHow to reason about it · think before answering
- A trap question, because 'move focus to new content' sounds like an accessibility improvement while actually doing harm.
- The answer is **no**, and one sentence suffices: the user may be typing in the input, and stealing focus is hostile. Worse, streaming content changes constantly, so focus chasing it makes keyboard operation impossible.
- The right approach is to **inform** via a live region rather than **compel** via focus. That generalizes: live regions for notification, focus only for navigation the user initiated.
- The one case that warrants moving focus is **opening a modal dialog**, since the user's other interactions are already blocked; moving focus in is then required, as is restoring it to the triggering element on close.
- Two related keyboard requirements earn extra credit: focus must be **visible** — many projects remove the outline for aesthetics and leave keyboard users lost, whereas focus-visible shows it only for keyboard interaction — and a skip link as the first focusable element spares keyboard users from tabbing through the whole navigation.
- Expect the follow-up on where keyboard access matters most in an agent UI: **approval**. If the approve and reject buttons cannot be reached by keyboard, keyboard-only users cannot authorize or decline anything, which is functional exclusion rather than an inconvenience.
分析过程 · 先想清楚再作答
- 这题是个陷阱题,因为「让焦点跟随新内容」听起来像是在做无障碍优化,实际上是帮倒忙。
- 答案是**不该**,理由一句话就够:用户可能正在输入框里打字,抢走焦点是很粗暴的。而且流式内容每秒都在变,焦点跟着跑会让键盘用户完全无法操作。
- 正确做法是用实时区域**告知**,而不是用焦点**强迫**。这是一条通用原则:通知用途用实时区域,焦点只用于用户主动发起的导航。
- 唯一该主动移焦点的情况是**打开了模态对话框**——因为那时用户的其余操作本来就被阻断了,把焦点移进去反而是必须的(还要记住关闭时把焦点还回原来的触发元素)。
- 顺带说两件相关的键盘要求会加分:焦点必须**看得见**,很多项目为了好看去掉 outline,那会让纯键盘用户彻底迷路,用 focus-visible 可以只在键盘操作时显示;以及给一个跳转链接作为页面第一个可聚焦元素,让键盘用户不必每次穿过整个导航。
- 可预期的追问是「Agent 界面里键盘可达最关键的是哪里」——**审批**。如果审批按钮 Tab 不到,纯键盘用户就无法批准或拒绝任何操作,这不是体验问题而是功能性排除。
Key points
- No: the user may be typing, stealing focus is hostile, and streaming content changes constantly.
- Inform with a live region rather than compelling with focus; focus is for user-initiated navigation.
- The one exception is a modal dialog, where focus should move in and be restored to the trigger on close.
- Focus must be visible; focus-visible shows the outline only for keyboard interaction.
- Approval is the critical keyboard path in an agent UI; unreachable buttons are functional exclusion.
答题要点
- 不该跟随:用户可能正在打字,抢焦点很粗暴,而且流式内容每秒都在变。
- 用实时区域告知,不要用焦点强迫;通知用实时区域,焦点只用于用户主动发起的导航。
- 唯一例外是打开模态框,那时该移焦点进去,关闭时还要把焦点还回触发元素。
- 焦点必须看得见,用 focus-visible 可以只在键盘操作时显示 outline。
- Agent 界面里键盘可达最关键的是审批,按钮 Tab 不到等于功能性排除。
Your streaming UI is green across automated tests, yet users report problems with screen readers. What does that tell you?你的流式界面在自动化测试里全绿,但用户报告说屏幕阅读器上有问题。这说明什么?
Common in ChinaCommon overseasDeep dive#testing#accessibility#engineering-practiceHow to reason about it · think before answering
- This probes your understanding of testing boundaries rather than a specific technique, and doubles as a self-check on whether you have ever actually turned a screen reader on.
- The root cause is that **automated tests verify DOM structure while screen reader behavior is temporal**. You can assert the live region exists, has the right attributes, and changed content, but not what the user actually heard — whether announcements were throttled away, repeated, or spaced far enough apart. None of that is visible in the DOM.
- A concrete example from building this course's lab: the logic-layer selftest was fully green, yet the second sentence came out as a fragment with its opening swallowed. The announcer tracked its position per message and was not reset when a new message began, so it reused the previous offset. Sentence splitting within one message was perfectly correct, which is why logic tests missed it.
- The second cause is **inconsistency between screen readers**. NVDA, JAWS, and VoiceOver handle high-frequency changes differently, so passing on one says little about the other two.
- The conclusion: interfaces like this require a **manual acceptance checklist** that is actually executed. It should include listening to announcement pacing with a real screen reader, completing the critical flow with keyboard only, and deliberately breaking a key attribute once to hear the difference.
- Expect the follow-up on what automation is still worth: it guards structural regressions — the region exists, starts empty, keeps its attributes, and announces far fewer times than there are deltas. It is necessary but not sufficient. **Automate what can be automated, and list the rest honestly rather than pretending it is covered.**
分析过程 · 先想清楚再作答
- 这题考的是对测试边界的认识,不是某个具体技术点。它也是个很好的自我检验:你有没有真的打开过屏幕阅读器。
- 根本原因是**自动化测试验的是 DOM 结构,而屏幕阅读器的行为是时序性的**。你可以断言实时区域存在、属性正确、内容变了,但断言不了「用户实际听到了什么」——播报会不会被节流吃掉、会不会重复、两段之间有没有留够间隔,这些 DOM 上都看不出来。
- 我自己写这门课的 lab 时就踩到过一个具体例子:逻辑层自检全绿,浏览器里听到的第二句却是「速高于行业均值」——开头被吞了。原因是播报器按消息追踪已播报位置,第二条消息开始时没重置,沿用了第一条的偏移量。单条消息的分段完全正确,所以逻辑测试发现不了。
- 第二个原因是**屏幕阅读器之间行为不一致**。NVDA、JAWS、VoiceOver 对高频变化的处理各不相同,在一个上验过不代表另外两个也行。
- 所以结论是:这类界面必须有**手动验收清单**,而且要真的执行。清单上该有的项目包括真开一次屏幕阅读器听播报节奏、纯键盘走完一遍关键流程、把关键属性改错一次感受差别。
- 可预期的追问是「那自动化测试还有什么用」——有用,它守住的是结构层的回归:区域存在、加载时为空、属性没被改错、播报段数远少于增量数。它是必要不充分条件。**能自动验的尽量自动验,验不了的要诚实列进手动清单,而不是假装覆盖到了。**
Key points
- Automation checks DOM structure, but screen reader behavior is temporal and what was heard cannot be asserted.
- Concrete case: per-message splitting was correct but the offset was not reset across messages, swallowing the second message's opening while tests stayed green.
- The three major screen readers differ on high-frequency changes, so one passing proves little.
- You need a manual acceptance checklist that is actually run, including a real screen reader and a keyboard-only pass.
- Automation still guards structural regressions; automate what you can and list the rest honestly.
答题要点
- 自动化测试验的是 DOM 结构,而屏幕阅读器行为是时序性的,听到什么断言不了。
- 具体例子:单条消息分段正确但跨消息没重置偏移,第二条开头被吞,逻辑测试全绿。
- 三家屏幕阅读器对高频变化处理不同,在一个上验过不代表另外两个也行。
- 结论是必须有真正执行的手动验收清单,包括真开屏幕阅读器和纯键盘走一遍。
- 自动化仍然有用,它守结构层回归;能自动验的自动验,验不了的诚实列进清单。
RAG in 14 Days: From Retrieval to Trustworthy Answers
D1 Why Retrieve at All: Hallucination, Knowledge Cutoffs, and the Cost of Long Context; a Minimal Keyword-Only RAG
When should you use retrieval-augmented generation, when should you fine-tune, and when is stuffing the documents into the context window good enough?什么时候该用检索增强生成,什么时候该微调,什么时候直接把文档塞进上下文就够了?
Common in ChinaCommon overseasBasic#rag-basics#fine-tuning#long-contextHow to reason about it · think before answering
- This question shows up in almost every loop. The differentiator is not reciting three definitions, it is offering a decision rule the interviewer can reuse.
- Lead with the rule: is the model missing knowledge, or missing a way of speaking? Missing knowledge means retrieval; missing style or output shape means fine-tuning. That single cut covers most cases.
- Then line up the three options against three costs: cost of updating knowledge, cost per request, and whether the answer can be traced back to a source. Retrieval updates by editing a file, fine-tuning takes a retraining cycle, and long-context pays for the whole corpus on every call.
- Give long-context its fair case: when the corpus is small, changes rarely, and request volume is low, stuffing it in is the cheapest engineering decision you can make. It stops being cheap once the corpus grows or the same material is queried thousands of times a day.
- Close by naming when none of this applies: if the answer does not depend on any external document (rewriting, translating, reformatting), retrieval only adds noise, latency and cost.
- Expected follow-up: can you do both? Yes, and it is common. Fine-tuning controls format and refusal behavior, retrieval supplies the facts.
分析过程 · 先想清楚再作答
- 这题几乎每场都问,区分度不在能不能背出三条定义,而在你会不会给一条判据。只说「RAG 适合动态知识、微调适合特定风格」的人一抓一大把,面试官等的是下一句。
- 先给一条能当场套用的判据:模型缺的是「知道什么」还是「怎么说」。缺知识走检索,缺风格与输出格式走微调,这一刀切下去能分掉八成场景。
- 再拿三笔账把三条路排开:知识更新的代价(改文件立刻生效 / 重训以天计 / 改文件立刻生效)、单次成本(只付取回的几段 / 只付推理 / 每次都付全量材料)、能不能归因(能 / 不能 / 能但材料一多定位会飘)。
- 把上下文直塞的适用边界说清楚:材料总量小、更新不频繁、对单次成本不敏感的场景它最划算,因为工程量近乎为零。一旦材料涨到几百篇,或者同一批材料每天要被问上万次,成本曲线立刻反超。
- 最后主动补一句「什么时候都不该用检索」——任务的答案不依赖任何外部文档时(改写、翻译、格式转换),加检索只会引入噪声、延迟和成本。能主动划出不该用的边界,比会背适用场景更能证明你做过。
- 可预期的追问:能不能既微调又检索?答案是可以,而且常见——微调管输出格式与拒答口径,检索管事实,两者解决的不是同一个问题。
Key points
- One rule: retrieval for missing knowledge, fine-tuning for a missing way of speaking.
- Retrieval updates instantly by editing files, supports citation, and costs scale with the retrieved passages rather than the corpus.
- Fine-tuning is good at locking in style and output schema, poor at loading facts, and offers no traceability.
- Long-context stuffing wins when the corpus is small, stable and queried infrequently; it loses on cost and on locating facts once the corpus grows.
- If the answer does not depend on any document, use none of them.
答题要点
- 一条判据:缺「知道什么」用检索,缺「怎么说」用微调。
- 检索改文件即时生效、可归因、成本只跟取回的几段有关,代价是要自己建一套会出错的检索系统。
- 微调擅长固化风格与输出格式,不擅长灌事实:数据一变就要重训,而且没法归因。
- 长上下文直塞在小型、低频、少变的语料上最划算,材料变多或调用量变大之后成本与定位稳定性都会恶化。
- 任务答案不依赖外部文档时三条路都不该用,直接调模型。
In BM25, what problems do term-frequency saturation and document length normalization each solve? What happens if you set both k1 and b to zero?BM25 里的词频饱和与文档长度归一化分别在解决什么问题?把 k1 和 b 都设成 0 会发生什么?
Common in ChinaCommon overseasIntermediate#bm25#ranking#information-retrievalHow to reason about it · think before answering
- This checks whether you have actually read the formula rather than merely called a library. The test is whether you can map k1 and b onto specific terms and name the failure each one prevents.
- Start with the two holes in raw term frequency: keyword stuffing lets one document dominate by repeating a word, and long documents win by accident because they contain more words overall.
- k1 closes the first hole. Term frequency appears in both numerator and denominator, so the ratio approaches a ceiling instead of growing linearly. Fifty mentions are more relevant than five, but not ten times more relevant. A smaller k1 saturates sooner.
- b closes the second. The normalization factor is one minus b plus b times document length over average length: at b equal to zero length is ignored entirely, at one it is fully penalized, and 0.75 is the conventional compromise.
- Now the trap in the question: k1 equal to zero collapses the ratio to a constant, so one occurrence scores the same as a hundred and matching becomes boolean. b equal to zero removes length entirely. Set both to zero and BM25 degenerates into a plain sum of inverse document frequencies.
- Expected follow-up: can you drop the IDF term? No. Without it, ubiquitous words drown everything else, and it is precisely IDF that lets BM25 work without a stopword list.
分析过程 · 先想清楚再作答
- 这题考的是你有没有真的读过公式,而不是有没有调过库。判据很明确:能不能把 k1 和 b 各自对应到公式里的哪一项,并说出去掉之后会被什么样的文档钻空子。
- 先说朴素词频的两个漏洞:一是重复刷词,一篇文章把关键词写五十遍就能霸榜;二是长文占便宜,文档越长越容易蒙中查询里的词。这两个漏洞正好对应两个修正。
- k1 管第一个漏洞。分子分母里都有词频 f,所以词频涨上去之后整个分式趋近一个上界而不是线性增长——写五十遍确实比写五遍相关,但绝不该相关十倍。k1 越小饱和越快。
- b 管第二个漏洞。归一化项是 1 减 b 加上 b 乘以本文长度除以平均长度,b 等于 0 时完全不看长度,b 等于 1 时完全按长度比例惩罚,0.75 是长期折中的默认值。
- 回到题干那个陷阱:k1 设成 0 会让分式退化成常数,词出现一次和一百次得分完全一样,等于只剩「有没有出现过」的布尔匹配;b 设成 0 则长度信息彻底消失。两个一起设成 0,BM25 就退化成对逆文档频率求和,跟词频再无关系。
- 可预期的追问:那逆文档频率去掉行不行?答案是不行,去掉之后「的」「我们」这类高频词会淹没一切——而且要顺带说明 BM25 因此天然不需要停用词表,这一句最能体现你读懂了公式。
Key points
- k1 controls saturation and prevents keyword stuffing: the score approaches a ceiling rather than growing linearly with frequency.
- b controls length normalization and stops long documents from winning by sheer word count.
- Setting k1 to zero degenerates the scorer into boolean matching; one occurrence scores the same as a hundred.
- Setting b to zero removes document length from the equation entirely; both at zero leaves only a sum of IDF terms.
- IDF is the third component: it up-weights rare terms and removes the need for a stopword list.
答题要点
- 词频饱和由 k1 控制,防的是重复刷词:词频涨大后得分趋近上界而非线性增长。
- 长度归一化由 b 控制,防的是长文档靠词多蒙中查询,用本文长度比平均长度把它压回去。
- k1 设 0 会退化成布尔匹配,词出现一次和一百次同分;b 设 0 则完全不考虑文档长度。
- 两者都设 0 时 BM25 只剩逆文档频率求和,等于放弃了词频信息。
- 逆文档频率是第三块,让稀有词权重更高,也让 BM25 天然不需要停用词表。
A retrieval-augmented generation system gave a wrong answer. How do you determine whether retrieval or generation is at fault?一个检索增强生成系统答错了,你怎么定位是检索的锅还是生成的锅?
Common in ChinaCommon overseasIntermediate#debugging#failure-modes#evaluationHow to reason about it · think before answering
- The question asks how you localize the fault, not what the possible causes are. Listing causes loses; the interviewer wants an ordered procedure that ends in concrete actions.
- Give the cheapest first step: print the retrieved passages verbatim and read them. If the correct answer is not in there, retrieval is at fault. If it is in there and the model ignored it, generation is at fault. Thirty seconds, and it removes most of the guesswork.
- Then lay out the five stages — chunking, indexing, retrieval, context assembly, generation — with the rule: diagnose right to left, fix left to right. You see the generated answer first, but an error on the left is amplified by everything to its right.
- Add symptoms that pin down a stage: half-correct answers usually mean a rule was split across chunks; obviously irrelevant hits usually mean dirty parsing; the model ignoring the supplied material usually means the prompt never said it must; citation numbers that do not match their content point at generation.
- Land it in engineering terms: to run this procedure repeatedly you must log the retrieved hits, the passages that entered the context, and the final answer together, otherwise production issues are unreproducible. At scale this becomes a fixed question set with metrics rather than case-by-case reading.
- Expected follow-up: if retrieval missed the document, will prompt tuning help? No. Nothing in the prompt can conjure material that was never supplied.
分析过程 · 先想清楚再作答
- 题眼在「怎么定位」,不在「有哪些原因」。答成一串可能原因的罗列就输了,面试官想听的是一个有先后顺序、能落到具体动作的排查流程。
- 先给最省时间的第一步:把这次检索出来的几段原文原样打印出来,自己读一遍。正确答案不在里面就是检索的锅,在里面而模型没用上才是生成的锅。这一步三十秒,能省掉大半天的瞎猜。
- 然后把链路展开成五个环节——切块、建索引、检索、组装上下文、生成——并给出「排查从右往左、修复从左往右」这条口径:从右往左是因为你最先看到的是生成结果,从左往右是因为左边的错会被右边放大。
- 补充几个能把环节钉死的症状:答案「半对」多半是切块把一条完整规则切断了;检索结果里混着一眼不相干的东西多半是解析没做干净;模型无视材料用先验知识作答,通常是提示词里少了「只能依据资料回答」;引用编号和内容对不上,那是生成侧漏读或串了行。
- 最后落到工程做法:这套排查要能重复做,就必须把每次请求的检索结果、进上下文的段落、最终回答一起记下来,否则线上出问题时你根本复现不了。到了要批量做的时候,就得换成一批固定问题加指标,而不是一条条人工看。
- 可预期的追问:如果检索确实没捞到,改提示词有没有用?答案是没用——材料里没有的东西,再好的指令也只能换一种编法。这句话最能证明你分清了两层。
Key points
- Always start by printing the retrieved passages and checking whether the correct answer is present at all.
- Split the pipeline into chunking, indexing, retrieval, context assembly and generation; diagnose right to left, fix left to right.
- Use symptoms to pin the stage: half-correct answers point at chunking, irrelevant hits at parsing, ignored material at the prompt, mismatched citations at generation.
- If retrieval missed the document, prompt changes cannot help; the material simply is not there.
- Log retrieved hits, the passages that entered the context, and the final answer together, or production failures are unreproducible.
答题要点
- 第一步永远是把检索出来的原文打印出来读一遍,判断正确答案在不在里面。
- 把链路拆成切块、建索引、检索、组装上下文、生成五个环节,排查从右往左、修复从左往右。
- 用症状钉环节:半对多半是切块问题,混入无关结果多半是解析问题,无视材料多半是提示词缺约束,引用与内容对不上是生成问题。
- 检索没捞到时改提示词没有意义,材料里没有的东西模型只能编。
- 要能重复排查就必须把检索结果、进上下文的段落和最终回答一起记录下来。
Context windows are now in the millions of tokens. Does that make the retrieval step obsolete?上下文窗口已经做到上百万 token 了,检索这一步会被淘汰吗?
Common in ChinaCommon overseasDeep dive#long-context#cost#system-designHow to reason about it · think before answering
- This is a position question and it is easy to answer as a binary. The signal is whether you separate what fits technically from what is worth paying for on every request.
- Concede the valid half first: bigger windows genuinely absorb part of the use case. For an internal tool over a few dozen stable documents with low traffic, stuffing everything in is the right call and building a retrieval stack would be over-engineering.
- Then give three reasons it does not absorb the rest. Cost is the first: context is billed per request, so the same corpus is paid for on every one of ten thousand queries, whereas retrieval only pays for the passages it returns. Prompt caching softens this but does not remove it.
- Scale is the second: enterprise corpora run to hundreds of thousands of documents and no window holds them. Attribution and access control are the third: pointing an answer at a specific passage, and showing each user only what they are permitted to see, both have to happen before the material reaches the model.
- Add the empirical point: as the supplied material grows, models become less reliable at locating the one relevant fact inside it. More context is not automatically better; fewer and more precise passages often win.
- Expected follow-up: does retrieval change shape? Yes. Larger windows allow bigger chunks and more of them, which relieves pressure on reranking and compression. Retrieval gets coarser, it does not disappear.
分析过程 · 先想清楚再作答
- 这是一道立场题,容易答成非黑即白。判断你有没有做过的地方在于:会不会区分「技术上能不能塞进去」和「工程上该不该每次都塞」,只谈前者的答案一听就是纸上谈兵。
- 先承认对方有道理的部分:窗口变大确实吃掉了检索的一部分场景。几十篇文档、更新不频繁、调用量不大的内部工具,直接全塞是最省事的选择,为它建一套检索系统是过度设计。
- 再给三条它吃不掉的理由。第一是成本:材料是按次计费的,同一份材料被问一万次就要付一万次,而检索只付取回的那几段;预填充缓存能缓解但不能消除,缓存也有有效期和命中率。
- 第二是规模:企业知识库动辄几十万篇,再大的窗口也塞不下,检索是唯一的入口。第三是归因与权限:答案要指回具体某一段,以及不同的人只能看到自己有权访问的材料——这两件事必须在把材料喂给模型之前完成,窗口再大也不解决。
- 还要补一条经验事实:材料变多之后,模型在长上下文里定位关键信息的稳定性会下降,出现「读了但没读到」。所以「全塞」并不总是等于「效果更好」,很多时候少而准反而更好。
- 可预期的追问:那检索的形态会不会变?会——窗口变大之后,取回的块可以更大、条数可以更多,重排与压缩的压力变小,检索从「精挑几句」变成「粗筛一批」。趋势是检索的粒度变粗,不是检索消失。
Key points
- Separate whether it fits from whether it is worth paying for on every request.
- Small, stable, low-traffic corpora can legitimately be stuffed whole; building retrieval for them is over-engineering.
- Three reasons retrieval survives: per-request cost, corpora too large for any window, and attribution plus access control that must happen before the model sees the material.
- More supplied context reduces the reliability of locating a single fact, so stuffing everything is not automatically better.
- The trend is coarser retrieval — bigger chunks, more of them, less reranking pressure — not the removal of retrieval.
答题要点
- 先区分「能不能塞进去」和「该不该每次都塞」,前者是技术问题,后者是成本问题。
- 小规模、低频、少变的语料确实可以直接全塞,为它建检索系统是过度设计。
- 检索不会被淘汰的三个理由:按次计费的成本、几十万篇塞不下的规模、必须在喂给模型之前完成的归因与权限过滤。
- 材料越多,模型定位关键信息的稳定性越差,全塞不等于效果更好。
- 趋势是检索粒度变粗——块更大、条数更多、重排压力变小,而不是检索消失。
D2 Embeddings and Vector Search: Similarity, Dimensionality, and Model Choice; Storing Text in pgvector
When are cosine similarity and inner product equivalent? What goes wrong if you rank by inner product on vectors that are not normalized?余弦相似度和内积什么时候等价?如果向量没有归一化,用内积排序会出什么问题?
Common in ChinaCommon overseasBasic#embeddings#similarity#normalisationHow to reason about it · think before answering
- This starts as a giveaway, but the second half is where candidates separate. Many can say 'they are equivalent after normalization'; few can describe what breaks without it.
- State the definition: cosine similarity is the inner product divided by the product of the two magnitudes. When both magnitudes are 1, the divisor is 1 and cosine reduces to the inner product. That is the whole argument.
- Then the failure mode: an un-normalized inner product mixes 'how aligned' with 'how long'. Longer texts tend to produce larger-magnitude vectors, so ranking drifts systematically toward long documents, the same bias BM25's b parameter exists to counter.
- Stress that this bug is silent. Nothing throws, results still look plausible, and only an offline evaluation reveals the drift. Hence the engineering rule: normalize once at the embedding boundary, never at each call site.
- Add Euclidean distance for completeness: on normalized vectors, squared L2 equals 2 minus twice the inner product, a monotone function of cosine distance, so all three metrics produce the same ranking.
- Expected follow-up: which pgvector operator should you use? Since the vectors are normalized, `<=>` and `<#>` rank identically; prefer `<=>` for readability and because it stays correct if someone later forgets to normalize.
分析过程 · 先想清楚再作答
- 这题是送分题,但区分度藏在后半句。只答「归一化之后两者等价」的人很多,面试官真正想听的是「没归一化会怎么坏」,因为那是线上真的会发生的事。
- 先把定义摆出来:余弦相似度等于内积除以两个向量模长的乘积。模长都是 1 时除数就是 1,所以余弦相似度就是内积——这一句话就是等价的全部理由,不需要额外的假设。
- 再说没归一化的后果:内积里混着「方向有多一致」和「向量有多长」两层信息。文本越长,模型输出的向量模长往往越大,于是排序会系统性地偏向长文档——这跟 BM25 里 b 参数要压的是同一个毛病,只是换了个地方冒出来。
- 点出这类 bug 的性质:它不报错。程序照常跑、结果照常出,只是名次悄悄偏了,你要跑一轮离线评估才可能发现。所以工程上的做法是在 embedding 的出口统一归一化一次,而不是靠每个调用点自觉。
- 补一句欧氏距离:向量都归一化之后,欧氏距离的平方等于 2 减去 2 倍内积,也就是余弦距离的单调函数,三种距离排出来的名次完全一致。这一句能说明你理解的是关系而不是三条并列的规则。
- 可预期的追问:那 pgvector 里该用哪个运算符?答案是既然已经归一化,`<=>`(余弦距离)和 `<#>`(负内积)名次一样,选 `<=>` 的理由是可读性和「就算哪天有人漏了归一化也不至于错」。
Key points
- Cosine equals inner product divided by both magnitudes; with unit magnitudes the divisor is 1, so they coincide.
- Without normalization the inner product carries magnitude, and longer documents usually have larger magnitudes, biasing the ranking.
- The failure is silent, so normalize once at the embedding boundary and verify with offline evaluation.
- On normalized vectors L2 and cosine are monotonically related, so all operators rank the same.
- In pgvector the operators are `<->` for L2, `<#>` for negative inner product and `<=>` for cosine distance.
答题要点
- 余弦相似度 = 内积 / 两个模长之积,模长为 1 时除数为 1,两者等价。
- 没归一化时内积混入模长信息,长文档的向量模长普遍更大,排序会系统性偏向长文档。
- 这类错误不报错,只能靠离线评估发现,所以要在 embed 出口统一归一化。
- 归一化之后欧氏距离与余弦距离互为单调函数,三种运算符名次一致。
- pgvector 里对应 `<->`(L2)、`<#>`(负内积)、`<=>`(余弦距离)三个运算符。
What do you lose when you cut embedding dimensions from 1536 to 512, and when is that loss acceptable?把 embedding 维度从 1536 降到 512,你会损失什么?什么场景下这个损失可以接受?
Common in ChinaCommon overseasIntermediate#embeddings#dimensions#costHow to reason about it · think before answering
- This is a cost-modeling question. 'Lower dimensions are cheaper but less accurate' earns nothing; the interviewer wants a cost model and a decision order.
- Lay out three costs: storage and memory (vector count times dimensions times bytes per dimension, which an ANN index must hold in RAM), query latency (roughly linear in dimensions), and retrieval quality, whose returns diminish sharply at the high end.
- Explain why truncation works at all: models trained with Matryoshka representations pack the most important information into the leading dimensions, so truncating and re-normalizing keeps the vector usable. It is still lossy, and how lossy is an empirical question on your own data.
- Give the decision order: derive a dimension ceiling from your memory budget, then step down two or three notches and measure the metric drop. Choosing the largest model first and optimizing cost later usually means redoing the work.
- Name the acceptable cases: large corpora of low individual value, pipelines where a reranker recovers some of the loss, and latency-critical online paths. Be conservative where a single miss is expensive, such as legal or clinical retrieval.
- Expected follow-up: can different documents use different dimensions? No. Every vector in an index must share one dimension, so changing it means rebuilding the whole index, the same migration cost as changing models.
分析过程 · 先想清楚再作答
- 这题考的是你会不会算账。只说「维度越低越省、精度越低」的答案没有区分度,面试官在等一个具体的成本模型和一个决策顺序。
- 先把三笔账列出来:存储与内存(向量数量乘维度乘每维字节数,近似最近邻索引要把它放进内存,所以基本等于机器预算)、检索延迟(每次比较就是一轮乘加,维度大致线性影响耗时)、检索质量(收益递减,低维段每加一档提升明显,高维段加倍只换来很小的改善)。
- 再说清降维为什么可行:主流模型用套娃式表示训练,重要信息压在靠前的维度上,所以直接截短再归一化仍然可用,这不是另训了一个小模型。截短必然有损失,损失多少只能在自己的数据上跑评估才知道。
- 给出决策顺序:先按存储与内存预算倒推一个维度上限,再从上限往下试两三档,看指标掉多少,掉得能接受就用低的。反过来「先选最高维再想办法省钱」基本都会返工。
- 点出可接受的典型场景:库很大而单条价值不高(比如日志、工单)、召回之后还有重排兜底(重排能把粗排的损失补回来一部分)、或者对延迟极敏感的在线场景。反过来法务、医疗这类一条都不能漏的场景就要谨慎。
- 可预期的追问:能不能不同文档用不同维度?不能——同一个索引里所有向量必须同维,改维度等于全库重建,这跟换模型是同一类迁移成本。
Key points
- Three costs: storage and index memory, query latency, and retrieval quality; the first two scale with dimensions, the third has diminishing returns.
- Matryoshka representations make truncation viable, but it is lossy and the loss must be measured on your own data.
- Decide by deriving a ceiling from the memory budget, then stepping down and measuring.
- Truncation pays off for large corpora, low-value items, latency-sensitive paths, and pipelines with a reranker.
- All vectors in one index share a dimension, so changing it forces a full rebuild.
答题要点
- 三笔账:存储与索引内存、检索延迟、检索质量,前两笔随维度近似线性,第三笔收益递减。
- 套娃式表示让截短再归一化仍然可用,但一定有损失,损失多少要在自己的数据上评估。
- 决策顺序是先按内存预算定上限,再往下试档位看指标掉多少。
- 库大、单条价值低、后面还有重排兜底、对延迟敏感的场景,降维划算。
- 同一索引里维度必须一致,改维度等于全库重建。
Why do some embedding models require different prefixes for queries and documents? What happens if you skip them, and how would you catch it before shipping?为什么有些 embedding 模型要求查询和文档加不同的前缀?不加会怎样,你怎么在上线前发现这个问题?
Common in ChinaCommon overseasIntermediate#embeddings#model-selection#evaluationHow to reason about it · think before answering
- The core of this question is silent failure. Reciting 'e5 needs query: and passage: prefixes' is the baseline; explaining why nothing errors out and how you would catch it is what shows experience.
- The reason: these models are trained on pairs, short questions on one side and longer passages on the other, two genuinely different distributions. The prefix is a role marker learned during training. Omit it at inference and you are off-distribution.
- The consequence: the model still returns vectors, distances still compute, results still have an order, quality just degrades. Nothing throws, exactly like forgetting to normalize.
- How to catch it: run a small labeled question set against the same corpus twice, with and without prefixes, and compare hit rate. That is the evaluation gate built on day 8, and catching silent regressions is precisely what it is for.
- Mention the sneakier variant: prefixing at index time but not at query time, or using the same prefix on both sides. Everything sits in one coordinate space and looks healthier, yet the query-document alignment is wrong and the loss is just as invisible. Encapsulate prefixes in the embedding call convention rather than hand-writing them everywhere.
- Expected follow-up: do OpenAI models need prefixes? No, they are not in that family, so this is not a universal rule but a per-model detail you re-check on the model card every time you switch.
分析过程 · 先想清楚再作答
- 这题的题眼是「静默失效」。会背「e5 要加 query 和 passage 前缀」只能拿基础分,能说清它为什么不报错、以及怎么在上线前抓住它,才是做过的人。
- 先讲原因:这一族模型是拿成对数据训练的,一侧是短问句、一侧是长段落,两者的分布本来就不一样。前缀是训练时给模型的角色标记,告诉它这一段该按查询编码还是按文档编码。推理时不给,模型就落在了训练分布之外。
- 再讲后果的性质:不加前缀模型照样输出向量、照样能算距离、名次照样有先后,只是整体质量下滑。**没有任何报错**——这跟忘了归一化是同一类问题:错误不会自己浮出来。
- 怎么发现:唯一可靠的办法是一小份标注问题集,用同一批文档跑两遍(加前缀与不加前缀),比命中率。这就是第 8 天要做的评估闸门,它的价值恰恰在于抓这类静默错误。上线前跑一遍,比读十遍文档管用。
- 补一个更容易踩的变体:**建库时加了前缀、查询时忘了加**,或者两边加成同一个前缀。这种情况下所有向量都在同一个坐标系里,看起来更「正常」,但查询与文档的对齐关系是错的,掉分同样查不出来。所以前缀应该封装在 embed 的调用约定里,而不是散在各处手拼。
- 可预期的追问:OpenAI 的模型要不要加前缀?不需要——它不属于这一族。所以这不是一条普遍规则,而是**每换一个模型都要重新读模型卡片确认**的事。
Key points
- These models are trained on question-passage pairs; the prefix marks which role a text plays, and omitting it puts you off-distribution.
- Skipping prefixes never errors, it only degrades quality, so the failure is silent.
- The reliable detection is an A/B run over a small labeled question set, comparing hit rate.
- A subtler bug is mismatched or identical prefixes on both sides, which looks healthier but misaligns queries and documents.
- Keep prefixes inside the embedding call convention, and re-read the model card whenever you switch models.
答题要点
- 这类模型用问句与段落的成对数据训练,前缀是区分两种角色的标记,缺了就落在训练分布之外。
- 不加前缀不会报错,只会整体掉分,属于静默失效。
- 唯一可靠的发现方式是拿一份标注问题集跑 A/B 对比命中率。
- 更隐蔽的错法是两边前缀不一致或用了同一个前缀,看起来更正常但对齐是错的。
- 前缀应封装在 embed 的调用约定里;换模型必须重读模型卡片,它不是普遍规则。
Can vector search fully replace keyword search? Give a query where vectors are bound to fail, and say how you would fix it.向量检索能完全取代关键词检索吗?举一个向量必然失手的查询,并说说你会怎么补。
Common in ChinaCommon overseasIntermediate#hybrid-search#embeddings#retrieval-failureHow to reason about it · think before answering
- This is a stance question where the stance matters less than the counter-example. Without a concrete, reproducible failing query, the rest of the answer reads as theory.
- Enumerate the failure classes up front: error and status codes, version numbers and SKUs, names and employee IDs, order or document identifiers, and negation. The first four share one property: their value lies in exact literal identity, which embeddings deliberately blur into semantic neighbourhoods.
- Give a reproducible example: ask whether rate limiting returns 429. BM25 lands on the API document that literally contains 429, while vector search may rank a topically similar product manual that never mentions the code.
- Call out negation separately: 'supports PDF export' and 'does not support PDF export' sit almost on top of each other because they discuss the same thing. Vectors cannot carry that distinction; the generation step reading the source has to.
- The fix: run both retrievers and fuse the rankings, BM25 on the lexical side and nearest neighbor on the vector side, combined with reciprocal rank fusion. That is hybrid search, covered on day 9. Fusion helps precisely because the two systems fail on different queries.
- Expected follow-up: could you drop the keyword path and rewrite queries instead? Rewriting helps with vocabulary mismatch, but it cannot rescue exact identifiers, since there is no paraphrase of 429.
分析过程 · 先想清楚再作答
- 这题是典型的「立场题」,答「能」或「不能」都不重要,重要的是你能不能举出一个具体到能复现的反例。举不出例子,前面说得再漂亮也会被判成没做过。
- 先给失手的类型,一次给全:错误码与状态码(429、E1032)、版本号与型号(v2.3.1、X20 Pro)、人名与工号、订单号与文档编号、以及否定表达。前四类的共同点是**这些词的价值在于字面唯一,而向量只保留语义邻近**,模型会把 429 和「限流」「超时」这些话题相近的东西编到一起,反而把真正写着 429 的那篇挤下去。
- 拿一个能复现的例子说:问「限流超了返回 429 吗」,BM25 稳稳命中写着 429 的接口文档,向量却可能把话题相近但没提 429 的产品手册排在前面。这个现象在本课第 2 天的实验里就能亲眼看到。
- 否定表达要单独强调:「支持导出 PDF」和「不支持导出 PDF」在向量空间里几乎重合,因为它们谈的是同一件事。指望向量区分肯定与否定一定翻车,这一层要靠生成侧读原文来判断。
- 怎么补:两路并行跑再融合,关键词一路用 BM25、向量一路用最近邻,用倒数排名融合把两个名次合成一个。这就是混合检索,本课第 9 天展开。要点是**两套的错法不一样**,所以合起来才有增益——如果两套错在同一批查询上,融合是白做的。
- 可预期的追问:那关键词一路能不能扔掉、改成让模型改写查询?可以缓解一部分(第 10 天的查询改写),但改写救不了字面唯一的标识符——你没法把 429 改写成别的说法。
Key points
- No: codes, version numbers, names and IDs matter as exact literals, which embeddings blur into neighbourhoods.
- Concrete example: asking whether rate limiting returns 429, where BM25 hits the document containing 429 and vectors surface a topically similar one that never mentions it.
- Negation is a second failure class, since affirmative and negative statements sit almost on top of each other.
- The remedy is hybrid retrieval: run both paths and merge with reciprocal rank fusion.
- Fusion pays off because the two paths fail differently; query rewriting helps vocabulary mismatch but not exact identifiers.
答题要点
- 不能取代:错误码、版本号、人名、单号这类词的价值在于字面唯一,向量只保留语义邻近。
- 具体反例:问「限流超了返回 429 吗」,BM25 命中写着 429 的文档,向量把话题相近却没提 429 的文档排前面。
- 否定表达是另一类失手:肯定句与否定句在向量空间里几乎重合。
- 补法是混合检索:两路并行再用倒数排名融合合并名次。
- 融合有增益的前提是两套的错法不同;查询改写能缓解词汇不匹配,但救不了字面唯一的标识符。
D3 Getting Documents In: Parsing PDF and HTML, Tables and Scans, Cleaning Rules, and Metadata You Must Keep
The text extracted from a PDF comes out in the wrong order. How do you diagnose and fix it?一份 PDF 解析出来的文字顺序是乱的,你会怎么排查和修复?
Common in ChinaCommon overseasIntermediate#pdf-parsing#ingestion#data-qualityHow to reason about it · think before answering
- This checks whether you have actually parsed a PDF yourself. The first sentence is the differentiator: a PDF has no reading order at all, only drawing instructions with coordinates.
- Start with the diagnostic step: dump the extracted fragments together with page, x, y and font size instead of looking at the concatenated string. The cause is always in the coordinates.
- Then classify the symptom. Lines alternating between left and right means multi-column layout was not detected. Fragments with y jumping backwards means the content stream was written in drawing order. Clean text sprinkled with a repeated short line is not disorder at all, it is a header or footer that was never stripped.
- Match the fix to the symptom. For columns, rebuild the order: sort the left edges of the fragments on each page, take the widest gap as the column boundary, then sort by column, then y descending, then x ascending. For headers and footers, cut fixed bands at the top and bottom and print how many fragments you dropped so you can confirm you did not cut into the body.
- Add the production-grade part: the fix needs a regression signal, not an eyeball check. Compute an out-of-order score by walking the sorted fragments and counting backward jumps within a column plus right-to-left column jumps. It needs no ground truth, so it can run on every ingest.
- Expected follow-up: what if column detection is wrong? Keep the detector conservative, treating a narrow gap or a lopsided split as single column, and make sure the assertion still fires when a two-column page is misread as one. Missing a fix is better than silently corrupting the order.
分析过程 · 先想清楚再作答
- 这题在考你有没有真的动手解析过 PDF。区分度在第一句:能不能说出「PDF 里根本没有阅读顺序」这个前提。答不出这句的人,后面只会说「换个库试试」。
- 先给排查顺序:把抽出来的文本片段连同页码、坐标、字号一起打印出来,别只看拼好的字符串。乱序的原因几乎都藏在坐标里,看纯文本永远看不出来。
- 然后按现象分三类。左右两栏一行一行地交替,是多栏没识别;同一段话被拆成很多短片段且 y 值有回跳,是内容流按绘制顺序写的;文字整体没问题但夹着重复出现的短句,那不是乱序,是页眉页脚没剔。
- 修法对应着来:多栏就重建阅读顺序——把每页文字块的左边界排序找最大空隙当分栏线,再按「栏号、y 从大到小、x 从小到大」重排;页眉页脚按固定的 y 值带切掉,并打印剔除条数确认没误伤。
- 补一条能证明你在生产里干过的话:修完要有可回归的判据,不能靠肉眼。用乱序疑似度——顺着排好的顺序走一遍,统计「同栏内往回跳」和「从右栏跳回左栏」的比例,它不需要标准答案,可以挂进流水线天天跑。
- 可预期的追问:多栏识别错了怎么办?回答分两头——把分栏判定做保守(空隙不够宽、或者一侧内容占比太低就按单栏处理),并且让断言在双栏被误判成单栏时同样会报警,宁可漏修也不要悄悄改错。
Key points
- State the premise: a PDF stores only drawing instructions, so paragraphs and reading order are inferred, not read.
- Debug by dumping fragments with page, coordinates and font size; plain text hides the cause.
- Three common causes: undetected multi-column layout, content stream written in drawing order, and headers or footers left in.
- Fix columns by finding the widest gap between left edges and sorting by column, then y descending, then x ascending.
- Add a ground-truth-free regression metric such as an out-of-order score so the fix stays fixed.
答题要点
- 前提先说清:PDF 只存「在某页某坐标画某段文字」,段落和阅读顺序都是解析时推出来的。
- 排查时把片段连同页码、坐标、字号一起打印,纯文本看不出乱序的原因。
- 三种典型成因:多栏没识别、内容流按绘制顺序写、页眉页脚没剔除。
- 多栏的修法是找最大 x 空隙定分栏线,再按「栏号、y 降序、x 升序」重排。
- 修完要有不依赖标准答案的回归指标,比如乱序疑似度,能挂进摄取流水线。
Which metadata should a document parsing stage preserve, and which downstream feature breaks if you drop each one?文档解析阶段应该保留哪些元数据?少了其中某一项会在哪个环节出问题?
Common in ChinaCommon overseasIntermediate#metadata#ingestion#access-controlHow to reason about it · think before answering
- The trap here is answering with a bare list. The differentiator is pairing every field with a concrete downstream feature. Listing eight fields without naming who consumes them shows you never designed one.
- Give the selection rule first: can this be recovered from the original file later? If not, it must be captured at parse time. Formatting and whitespace can be dropped because the original still has them.
- Then map fields to consumers: a stable chunk id makes citations verifiable, a heading path tells the user which section a sentence came from and enables structure-aware chunking, page numbers make citations land on the right page, an access-control label enables filtering inside retrieval, an updated-at date resolves conflicting sources, and a content hash enables incremental sync.
- Take two of them all the way to cost. Without the access label you must re-parse the whole corpus when access control lands, and worse, people work around it by filtering at generation time, which means the content already reached the context and the leak already happened.
- Without a content hash, every sync is a full rebuild: re-parse, re-chunk, re-embed. For a few thousand documents synced daily, the embedding bill alone settles the argument.
- Expected follow-up: what about a field you are unsure of? Be conservative. Storage is the cheapest part of the pipeline, and adding a field costs far less than re-running a full parse.
分析过程 · 先想清楚再作答
- 这题最容易答成列清单。区分度不在你能列出几个字段,而在能不能给每个字段配一个具体的下游功能——列了八个字段却说不出谁在用,等于没设计过。
- 用一条判据把字段选出来:删掉之后还能不能从原件重新恢复。不能恢复的,解析时就必须留;能恢复的(比如格式、空白)可以放心丢。
- 然后一一对应地说:块编号支撑可验证的引用,没有它引用就只能靠模型自觉;标题路径支撑「这句话出自哪一节」和按结构切块;页码支撑引用精确到页;权限标签支撑检索层过滤;更新时间支撑材料冲突时的取舍;内容指纹支撑增量同步。
- 挑两个讲透代价。权限标签少了,等到要做访问控制时只能全量重新解析一遍;更糟的是有人会图省事在生成阶段过滤,那等于内容已经进了上下文,泄露已经发生。
- 内容指纹少了,每次同步都是全量重建:重新解析、重新切块、重新向量化。一份几千篇的知识库每天重算一次,光 embedding 的账单就够说服任何人。
- 可预期的追问:字段拿不准要不要留怎么办?答保守——存储是整条链路上最便宜的一环,加一个字段的代价远小于重跑一次全量解析。
Key points
- The rule is recoverability: if it cannot be recovered from the original later, capture it at parse time.
- Chunk ids back verifiable citations, heading paths back localization and structure-aware chunking, page numbers make citations land precisely.
- Access-control labels must be attached during parsing, otherwise enabling ACL means re-parsing everything, and teams end up filtering at generation time where the leak has already occurred.
- Updated-at lets you present conflicting sources side by side; a content hash enables incremental sync instead of full rebuilds.
- When unsure, keep the field: storage is far cheaper than a full re-parse.
答题要点
- 判据是「删了还能不能从原件恢复」,不能恢复的必须在解析时留下。
- 块编号服务于可验证的引用,标题路径服务于定位与按结构切块,页码服务于引用精确到页。
- 权限标签必须在解析时打上,否则做访问控制时要全量重解析,且容易被错误地放到生成阶段过滤。
- 更新时间用于材料冲突时并列两种说法,内容指纹用于增量同步,少了它每次都要全量重建。
- 拿不准就保守保留:加一个字段的成本远低于重跑一次全量解析。
OCR output from scanned documents carries a non-trivial error rate. How does that noise propagate into retrieval and generation, and how do you mitigate it?扫描件走光学字符识别之后错字率不低,这些噪声会怎样影响检索和生成?怎么缓解?
Common in ChinaCommon overseasDeep dive#ocr#data-quality#hybrid-searchHow to reason about it · think before answering
- This tests whether you can trace propagation rather than recite that OCR makes mistakes. The differentiator is separating how retrieval fails from how generation fails, because the two failure modes are entirely different.
- Retrieval first. Chinese OCR errors are mostly visually similar characters. Keyword search is literal, so one wrong character makes the term unmatchable, and bigram tokenization makes it worse because a single wrong character corrupts two adjacent tokens. Recall drops quietly and nothing raises an error.
- Generation second. The model usually reads through minor noise, but when the corrupted token is a key entity such as a name, a model number, an amount or a date, it answers confidently with the wrong value. Citation checking degrades too: verifying against a source that is itself wrong proves nothing.
- Mitigate in three layers. At ingest, use an empty-text assertion to decide whether the PDF even needs OCR, and keep a link to the original image so a human can verify.
- At retrieval, hybrid search absorbs some of the damage because dense retrieval is less sensitive to a single wrong character than literal matching. At generation, mark low-confidence pages so the answer can state that the source came from a scan and may contain recognition errors.
- Expected follow-up: can you auto-correct? Yes, but carefully. Dictionary or model based post-processing fixes some errors and breaks correct proper nouns. Restrict correction to low-confidence spans and keep the raw text so you can fall back.
分析过程 · 先想清楚再作答
- 这题考的是你会不会顺着链条推传导,而不是背「OCR 会有错字」这句废话。判据是有没有分别说清「检索侧怎么错」和「生成侧怎么错」——它们的失效方式完全不同。
- 先说检索侧。中文 OCR 的错主要是形近字,「已」认成「己」、「板」认成「版」。关键词检索是字面匹配,一个字错了这个词就查不到;更隐蔽的是二元组分词会连带毁掉相邻两个词元,一个错字影响的其实是两处。这一路的表现是召回悄悄掉下去,而且不报错。
- 再说生成侧。错字进了上下文,模型往往能读懂大意,但一旦是关键实体(人名、型号、金额、日期)出错,它会照着错的答,而且答得很自信。更麻烦的是引用校验也会跟着失效——原文本身就是错的,校验通过了也没意义。
- 缓解按三层说。入口层:先用空文本比例这类断言判断这份 PDF 有没有文本层,有就别走 OCR;真要走,保留原图链接以便人工复核。
- 检索层:靠混合检索兜底,向量一路对个别错字不敏感,能补上关键词一路的失手,这是 D9 那套东西在这里的具体价值。生成层:把低置信度的页面标出来,让模型在引用它们时明确提示「该材料来自扫描件,可能有识别误差」。
- 可预期的追问:能不能自动纠错?可以但要克制——用词典或模型做后处理会修好一批,也会「修」坏一批原本正确的专有名词。稳妥的做法是只对置信度低的片段做纠错,并且保留原文以便回退。
Key points
- Retrieval: visually similar characters break literal matching, and bigram tokenization lets one bad character corrupt two tokens, so recall drops silently.
- Generation: the model reads through general noise but confidently repeats corrupted entities, and citation verification against a corrupted source proves nothing.
- At ingest: check for a text layer before running OCR at all, and keep the source image for human verification.
- At retrieval: hybrid search helps because dense retrieval tolerates a single wrong character better than literal matching.
- At generation: flag low-confidence sources in the answer, and restrict auto-correction to low-confidence spans while keeping the raw text.
答题要点
- 检索侧:形近字让字面匹配直接查不到,二元组分词还会让一个错字毁掉相邻两个词元,表现是召回悄悄下降且不报错。
- 生成侧:模型能读懂大意,但关键实体出错时会自信地答错,引用校验也失去意义。
- 入口层缓解:先判断有没有文本层再决定要不要 OCR,并保留原图链接供人工复核。
- 检索层缓解:混合检索里的向量一路对个别错字不敏感,能兜住关键词一路的失手。
- 生成层缓解:标出低置信度来源,让回答显式提示可能存在识别误差;自动纠错只对低置信片段做并保留原文。
Why is parsing quality the ceiling on retrieval quality? Walk through one concrete chain of propagation.为什么说解析质量决定了检索质量的上限?举一个具体的传导链条。
Common in ChinaCommon overseasBasic#ingestion#data-quality#failure-analysisHow to reason about it · think before answering
- This is a giveaway question that many people answer with a slogan. The only test is whether you produce a chain that lands on a concrete symptom instead of repeating garbage in, garbage out.
- Place it first: parsing sits before chunking, indexing, retrieval, context assembly and generation. Its errors are amplified by every later stage, and none of those stages can detect the problem because each is faithfully processing text that is already wrong.
- Give the chain: a pricing table in a PDF loses one column separator and comes out with cells shifted. Chunking splits on those wrong boundaries, so a plan name ends up next to the neighboring column value. The index records the wrong term pairing. A user asks about that plan's storage quota, the corrupted chunk scores highest, and the model, faithfully answering only from the provided material, returns a wrong answer carrying a correct-looking citation.
- Name the nastiest part: nothing on that chain raises an error, and the answer even comes with a source, so it looks more trustworthy than usual. Parsing errors cannot be caught after the fact, only by assertions at ingest.
- Explain the word ceiling: every later optimization, dense retrieval, hybrid search, reranking, query rewriting, improves how well you pick from the candidates. If the material itself is wrong, picking better still returns something wrong, so parsing caps all of them.
- Expected follow-up: how do you prove parsing is at fault? Reuse the habit from day one. Diagnose right to left and print the retrieved passages verbatim. If the source text is already scrambled, there is no point looking at the generation side.
分析过程 · 先想清楚再作答
- 这是一道送分题,但很多人答成口号。判据只有一个:有没有给出一条能落到具体现象上的链条,而不是重复一遍「垃圾进垃圾出」。
- 先说清位置:解析在切块、建索引、检索、组装、生成这五环之前,是第零环。它的错误会被后面每一环放大,而且后面每一环都无法察觉——它们只是在忠实地处理一段已经错了的文字。
- 给一条具体链条:一张套餐配额表在 PDF 里丢了一列分隔符,抽出来串了行;切块照着错误的边界切,「专业版」和隔壁那一栏的值被切进同一块;索引把错误的词对记进倒排表;用户问「专业版存储配额多少」,这一块分数很高被排到第一;模型只依据给定材料回答,于是给出一个错误但带着正确引用编号的答案。
- 点破最要命的一句:这条链上没有任何一环会报错,回答甚至是带出处的,看起来比平时更可信。所以解析的错误不能靠事后发现,只能靠入口处的断言拦。
- 反过来说明「上限」二字:后面所有优化——向量、混合检索、重排、查询改写——优化的都是「从候选里挑得更准」。材料本身错了,挑得再准也是错的,所以它们的天花板由解析封死。
- 可预期的追问:那怎么证明是解析的锅?答案接回 D1 那条习惯——排查从右往左看,把检索出来的原文打印出来自己读一遍,如果原文本身就是串行的,那就不用再往生成侧查了。
Key points
- Parsing is stage zero, before the five-stage pipeline; its errors are amplified downstream and invisible to every later stage.
- Concrete chain: a shifted table, chunking on wrong boundaries, wrong term pairs in the index, that chunk ranked first, and a wrong answer delivered with a citation.
- The dangerous part is that nothing errors out and the answer carries a source, so it looks more credible than usual.
- Later techniques only improve selection from candidates; if the material is wrong, better selection still returns something wrong.
- Diagnose right to left: print the retrieved passages first, and if the source text is already broken, stop looking at the generation side.
答题要点
- 解析是五个环节之前的第零环,它的错误会被后面每一环放大,而后面每一环都察觉不到。
- 具体链条:表格串行 → 切块按错误边界切 → 倒排表记进错误词对 → 检索把它排第一 → 模型据此给出带引用的错误答案。
- 最危险的是全程零报错,且答案带着出处,看起来比平时更可信。
- 后面所有优化解决的是「挑得更准」,材料本身错了就都无效,所以上限由解析封死。
- 定位方法是排查从右往左:先把检索到的原文打印出来读一遍,原文错了就不必再查生成侧。
D4 Chunking Strategies: Five Approaches — Fixed, Recursive, Structure-Based, Parent-Child, and Semantic — and Choosing by Evaluation, Not Intuition
How do you decide on chunk size? Name two metrics you would look at, and one counterexample.你怎么决定切块大小?说出你会看的两个指标和一个反例。
Common in ChinaCommon overseasIntermediate#chunking#evaluationHow to reason about it · think before answering
- The question is about method, not about a number. Answering with a specific default (512 tokens, 1000 characters) already loses it — the interviewer wants to hear that you have a procedure.
- State the tension first: large chunks dilute the signal and cost context; small chunks lose the surrounding meaning so the model cannot use them. The two metrics you name should map onto those two failure modes.
- Metric one is retrieval-side hit rate: did a document that actually answers the question make it into the context. Metric two is generation-side usability, cheaply proxied by the fraction of chunks that end mid-sentence, and more seriously by faithfulness and whether citations resolve.
- Add the point that separates candidates: both metrics must be compared under the same token budget, never under a fixed top-k. With fixed k, bigger chunks simply buy more text and win for the wrong reason.
- Make the counterexample concrete: raising chunk size from 400 to 1200 characters can lift hit rate purely because whole short documents now fit in one chunk, which means retrieval stopped doing anything and you are back to stuffing full documents. The metric improved while the system got worse.
- Expect the follow-up: where do you start on day one. Pick the strategy from the document type first (structural splitting whenever headings exist), start around 300 to 500 characters with 10 to 20 percent overlap, then build a golden set immediately and iterate. A starting point is not a conclusion.
分析过程 · 先想清楚再作答
- 这题的题眼是「怎么决定」,不是「多大合适」。答一个具体数字(512 token、1000 字符)就已经输了——面试官想看的是你有没有一套定法,而不是你记得住哪个默认值。
- 先把矛盾摆出来:块大则信噪比低、上下文贵,块小则单块缺语境、模型答不出所以然。切块大小就是在这两头之间找位置,所以两个指标必须分别对应这两头。
- 第一个指标是检索侧的命中率——答案文档有没有进上下文。第二个是生成侧的可用性,最省事的代理指标是切碎率,也就是有多少块结尾停在半句话上;再往前一步就是忠实度和引用是否可定位。
- 关键补一句:两个指标必须在**同一个 token 预算**下比,不能按「取前 k 块」比。k 固定时块越大塞进去的字越多,大块切法会赢在买得多而不是切得准上。这一句往往是这道题的区分点。
- 反例要具体。最好用的一个是:把块从 400 字调到 1200 字,命中率不降反升——但那是因为一整篇短文档被当成一块塞了进去,检索其实什么都没做,等于退化成了全文投喂。指标涨了,系统更差了。
- 可预期的追问是「那你第一次上手时从哪个数字起步」。答:先按文档类型选切法(有标题层级就按结构切),块长从 300 到 500 字起步、重叠取一到两成,然后立刻建一组标准问题跑评估,用两三轮迭代把它调到位。起步值是起步值,不是结论。
Key points
- Choose the strategy from the document type first, then tune length: split on headings whenever the structure survives parsing.
- Watch two metrics: retrieval hit rate on one side, mid-sentence break rate (then faithfulness and citation resolvability) on the other.
- Compare under an equal token budget, never a fixed top-k, or larger chunks win by buying more text.
- Counterexample: hit rate rises after enlarging chunks because whole documents now fit in one chunk and retrieval has effectively stopped working.
- Start near 300 to 500 characters with 10 to 20 percent overlap, then iterate against a fixed question set instead of guessing.
答题要点
- 先按文档类型选切法,再调长度:有标题层级就按结构切,没有结构才谈固定长度或语义。
- 看两个指标:检索侧的命中率,生成侧的切碎率(进一步是忠实度与引用可定位性)。
- 两个指标必须在同一个 token 预算下比,不能按「取前 k 块」比,否则大块只是买得更多。
- 反例:块调大后命中率上升,但那是因为整篇被当成一块,检索退化成全文投喂。
- 起步值 300 到 500 字、重叠一到两成,然后靠一组固定问题迭代,不靠直觉定稿。
What does parent-child chunking buy you, and when does it slow the system down instead?父子切块的收益是什么?它在什么情况下反而会拖慢系统?
Common in ChinaCommon overseasIntermediate#chunking#parent-childHow to reason about it · think before answering
- This question checks whether you know that the retrieval unit and the context unit can be two different things. Without that sentence, everything else is recitation.
- State the benefit compactly: small chunks go into the index so they are easy to match, and once a child is hit you follow the parent pointer and hand the model the whole section. You stop trading precision against completeness.
- Derive the slowdown from the costs. First, the context budget: every new child may drag in an entire parent, so an equal budget holds fewer distinct pieces and result diversity drops.
- Second, the write path: two levels to maintain, both recomputed on every document update, and chunk ids become harder to keep stable, which makes incremental sync noticeably more complex.
- Third, the condition under which the benefit disappears: when sections are already short, the parent and the child are nearly the same text, so you paid for two indexes and bought nothing. Parent-child suits long sections and deep hierarchies, not already fine-grained knowledge bases.
- Expect the follow-up: how is this different from simply using bigger chunks. Bigger chunks put the noise into the index; parent-child puts the noise only into the context. What gets matched stays short and clean.
分析过程 · 先想清楚再作答
- 这题考的是你有没有意识到「检索单位」和「上下文单位」可以是两个东西。答不出这句话,后面说什么都是复述。
- 收益一句话说清:小块进索引,信噪比高、容易被找到;命中之后顺着父指针把整节回填给模型,语境完整。精度和完整度这次不用二选一。
- 拖慢的场景要从代价一条条推。第一条是上下文预算:每命中一个新子块可能拖进来一整个父节,同样的 token 预算装不下几条,检索结果的多样性反而变差。
- 第二条是写入侧:父子两套都要维护,文档更新时两边都要重算,块 id 的稳定性也更难保证,增量同步的复杂度明显上升。
- 第三条是收益消失的条件:当文档本身的小节就不长时,父块和子块差不多大,你付了两套索引的钱,什么也没多买到。所以父子切块适合长节、深层级的文档,不适合结构本来就细碎的知识库。
- 可预期的追问是「那和直接把块切大有什么区别」。答:切大是把噪声一起放进索引,父子是只把噪声放进上下文、不放进索引——被检索的那一段始终是干净的短文本,这是本质区别。
Key points
- The core idea is decoupling the retrieval unit from the context unit: small chunks get found, large chunks get understood.
- The payoff is precision and completeness at the same time instead of trading one for the other.
- Cost one: a single hit can drag in a whole parent, so an equal context budget holds fewer distinct results and diversity suffers.
- Cost two: two index levels to maintain and recompute, which makes incremental sync on document updates considerably harder.
- It stops paying off when sections are already short, because parent and child are nearly identical and you bought nothing for the extra cost.
答题要点
- 核心是把检索单位和上下文单位拆开:小块负责被找到,大块负责被读懂。
- 收益是精度与完整度同时拿到,不用在信噪比和语境之间二选一。
- 代价一:一次命中可能拖进整个父节,同样的上下文预算装得下的条数变少,结果多样性下降。
- 代价二:父子两套索引都要维护与重算,文档更新时增量同步的复杂度明显上升。
- 失效场景:文档小节本来就短时父子块差不多大,多付一套成本却没多买到东西。
What overlap ratio would you use, and what concretely goes wrong when the overlap is too large?重叠区设成块长的百分之多少合适?重叠过大会带来什么具体问题?
Common in ChinaCommon overseasBasic#chunking#overlapHow to reason about it · think before answering
- This is a giveaway question, but the marks are in the second half, not the percentage. Stopping at 'usually ten to twenty percent' reads like someone who has never run it.
- Say what overlap is patching: fixed-length splitting cuts sentences in half, and overlap guarantees the broken sentence survives intact in at least one of the two neighbors. It is a patch for careless splitting, not an optimization of its own.
- That yields the first conclusion: with structural or recursive splitting the boundaries already land on semantic positions, so the need for overlap drops sharply and can legitimately be zero. The ratio question is meaningless without naming the strategy.
- Give three concrete costs. Storage and tokens: at 400-character chunks, moving overlap from 0 to 80 grows total index tokens by roughly fifteen percent, which is storage cost in the vector store and comparison work at query time.
- Retrieval redundancy: the more neighbors overlap, the more likely the top results are three versions of the same passage. You think you handed the model three pieces of evidence; you handed it one, three times. Nothing fixes this before reranking.
- Citation resolution: when a sentence lives in two chunks, which one does the model cite. Expect the follow-up on deduplication: merge at the result layer using a content fingerprint or longest common substring, not by tweaking the chunker.
分析过程 · 先想清楚再作答
- 这是一道送分题,但送分点不在那个百分比上,而在后半句。只答「一般一到两成」就停住的人,面试官会认为他没跑过。
- 先说清重叠在补救什么:固定长度切法会把句子从中间切开,重叠让被切开的那句话至少在相邻两块之一里是完整的。它是给「乱切」打的补丁,不是一个独立的优化。
- 由此推出第一个结论:如果你用的是按结构切或递归切,边界本来就落在语义位置上,重叠的必要性会大幅下降,甚至可以是零。**重叠比例这个问题的前提是切法**,脱开切法谈比例就是背数字。
- 过大的代价要说三笔,越具体越好。存储与 token:块长 400、重叠从 0 加到 80,索引 token 会涨一成半左右,这笔钱在向量库是存储费、在检索时是比对量。
- 检索冗余:相邻块越像,前几名越可能是同一段话的三个版本,你以为给了模型三条证据,其实是一条说了三遍。这一条在重排之前基本无解。
- 引用定位:同一句话出现在两个块里,模型标出处该标哪一个,这会直接变成引用校验环节要处理的边界情况。可预期的追问就是「那你怎么去重」,答按内容指纹或最长公共子串在结果层合并,而不是在切块层想办法。
Key points
- Ten to twenty percent of chunk length is the working range, but that number assumes fixed-length splitting.
- With structural or recursive splitting the boundaries are already semantic, so overlap can be small or zero.
- Cost one: index tokens and storage grow noticeably; at 400-character chunks, an 80-character overlap adds roughly fifteen percent.
- Cost two: neighboring chunks become near-duplicates, so the top results are several versions of one passage and the evidence diversity is illusory.
- Cost three: a sentence spanning two chunks complicates citation attribution and forces result-level deduplication.
答题要点
- 经验区间是块长的一到两成,但这个数字的前提是你用的是固定长度切法。
- 按结构或递归切时边界本来就在语义位置上,重叠可以很小甚至为零。
- 过大代价一:索引 token 与存储明显上涨,块长 400 时重叠加到 80 大约涨一成半。
- 过大代价二:相邻块高度相似,检索前几名变成同一段话的多个版本,证据多样性是假的。
- 过大代价三:同一句话跨块出现,引用标注和去重都要额外处理。
Semantic chunking costs considerably more than recursive splitting. How would you prove to your team that the money is well spent?语义切分比递归切分贵不少,你怎么向团队证明这笔钱值得花?
Common in ChinaCommon overseasDeep dive#chunking#evaluation#costHow to reason about it · think before answering
- This looks like a technical question but it tests whether you can run a controlled technical argument. Launching into how semantic chunking works answers a different question.
- Step one is to concede that it may well not be worth it. The gain comes from documents that have no usable structure; if your knowledge base is well-formed documents, the authors' heading hierarchy already did the semantic split for free and the money is likely wasted.
- Step two is translating 'worth it' into three measurable numbers: how much the metric moved (hit rate on the same golden set under the same token budget), how much latency moved (chunking is offline, but the end-to-end update path changes), and how much it costs (the initial full embedding pass plus recomputation amortised over update frequency).
- Step three is the control. Recursive splitting is the baseline, semantic chunking the treatment, and they must share the corpus, the questions, the context budget and the retriever. Change one variable only; a two-variable experiment proves nothing.
- Step four is a decision threshold rather than an impression. For example: below three points of hit-rate gain, no; above five points with recomputation inside the monthly budget, yes; in between, roll it out on one document class first. Fix the threshold before you run the numbers, or you will quietly bend it to fit them.
- Expect the follow-up: is there a cheaper way to the same gain. Yes — try structural splitting first, since it is free and often nearly as good, and if the structure really is unusable, apply semantic chunking only to the high-value subset rather than the whole corpus.
分析过程 · 先想清楚再作答
- 这题表面问技术,实际考的是你会不会做一次带对照组的技术论证。上来就讲语义切分原理的人,答的是另一道题。
- 第一步是先承认它可能不值。语义切分的收益来自「文档没有可用的结构」;如果知识库是结构良好的文档,作者的标题层级已经免费替你做完了语义切分,这时候花的钱大概率打水漂。**先说清适用前提,再谈证明,这一步就把大多数候选人区分开了。**
- 第二步是把「值不值」翻译成可测的三笔账:指标涨了多少(同一批标准问题、同一个 token 预算下的命中率)、延迟涨了多少(切块是离线的,但更新链路的端到端时间会变)、钱涨了多少(首次全量 embedding 的费用,加上按更新频率折算的重算费用)。只报第一笔的论证不成立。
- 第三步是设计对照。递归切分是基线,语义切分是实验组,两组必须用同一份语料、同一批问题、同一个上下文预算、同一个检索器,只改切法这一个变量。改两个变量的实验,结论一文不值。
- 第四步是给决策一个门槛,而不是给一个感想。比如:命中率相对基线提升低于三个百分点就不上;提升超过五个百分点且重算成本在月度预算内就上;中间地带先在一类文档上灰度。**门槛要在跑数字之前定好**,否则你会不自觉地去迁就已经跑出来的结果。
- 可预期的追问是「有没有更便宜的办法拿到同样的收益」。答有:先试按结构切,它零成本且效果常常接近;结构确实不可用时,再考虑只对高价值的那一部分文档做语义切分,而不是全量上。
Key points
- Start with the precondition: the gain comes from documents without usable structure, so on well-formed documents it usually is not worth it.
- Translate 'worth it' into three numbers — hit rate, latency, and cost. Reporting only the first is not an argument.
- Run a controlled comparison: same corpus, same golden set, same context budget, same retriever, with the splitting strategy as the only variable.
- Fix the decision threshold before running the numbers so you cannot bend it to fit the result afterwards.
- Try free structural splitting first, and if semantic chunking is genuinely needed, apply it to the high-value subset rather than the entire corpus.
答题要点
- 先讲适用前提:语义切分的收益来自文档没有可用结构,结构良好的文档上它大概率不值。
- 把「值不值」翻译成三笔账:命中率涨多少、延迟涨多少、钱涨多少,只报第一笔不算论证。
- 做对照实验:同语料、同问题集、同上下文预算、同检索器,只改切法一个变量。
- 决策门槛必须在跑数字之前定好,避免事后迁就结果。
- 先试零成本的按结构切;确需语义切分时也优先只覆盖高价值文档,而不是全量上。
D5 Vector Indexes and Store Selection: HNSW vs. Inverted File, Quantization to Save Memory, Filtered Queries and Multi-Tenant Isolation
How do you choose between an HNSW index and an IVFFlat index? Give one scenario that forces each choice, and name the parameter you would tune first in each.分层可导航小世界图和倒排文件索引你会怎么选?各说一个必须选它的场景,以及各自最该调的参数。
Common in ChinaCommon overseasIntermediate#vector-index#hnsw#ivfflatHow to reason about it · think before answering
- The differentiator is not describing both structures, it is naming the condition that forces one over the other. Saying 'HNSW is faster, IVFFlat is cheaper' is what everyone says.
- Describe the structures in one line each: HNSW is a layered neighbor graph you navigate from sparse upper layers down to dense lower ones; IVFFlat clusters vectors into lists and only scans the lists closest to the query.
- Map the knobs: HNSW builds with m and ef_construction and queries with ef_search; IVFFlat builds with lists and queries with probes. Tune the query-side knob first, because it needs no rebuild and is the only one you can still move after launch.
- Give two forcing scenarios in opposite directions. Minute-level write traffic with tight memory and a short build window forces IVFFlat, since an HNSW graph keeps growing and is expensive to rebuild. A largely static corpus with a hard latency SLA forces HNSW, since it hits the same recall at lower latency.
- Add the operational detail people forget: IVFFlat clusters reflect the data at build time, so recall degrades silently as the distribution drifts and you need a scheduled rebuild. HNSW avoids that but its index is often larger than the table.
- Expected follow-up: what are the defaults? probes is 1 and ef_search is 40. Volunteer that leaving probes at 1 means scanning a single list, which is the single most common IVFFlat mistake.
分析过程 · 先想清楚再作答
- 这题的区分度不在能不能背出两种结构,而在你会不会给出触发条件。只说「HNSW 快、IVFFlat 省内存」的人一抓一大把,面试官等的是「什么情况下我必须选另一个」。
- 先用两句话把结构说清:HNSW 是分层的邻居图,查询从稀疏的上层跳到稠密的下层,逐步逼近;IVFFlat 是先聚类成若干个列表,查询时只在最近的几个列表里扫。一个是图上导航,一个是分区搜索。
- 再把参数对应上去:HNSW 建图有 m 与 ef_construction,查询有 ef_search;IVFFlat 建索引有 lists,查询有 probes。**先调查询侧参数**,因为它不用重建索引、能逐次查询调整,是唯一一个上线之后还能动的旋钮。
- 给两个反向的必须场景:数据分钟级高频写入、且内存和建索引窗口都紧张时必须选 IVFFlat,因为 HNSW 的图会持续膨胀、重建代价高;反过来,数据相对静态、查询延迟有硬性 SLA 时必须选 HNSW,因为同等召回下它的延迟更低。
- 补一条容易被忽略的工程细节:IVFFlat 的聚类是建索引那一刻的数据决定的,数据分布漂移之后召回会悄悄下滑,所以它需要一条定期重建的运维流程;HNSW 没有这个包袱,但它的索引往往比表本身还大。
- 可预期的追问:probes 和 ef_search 的默认值分别是多少?答 1 和 40,并且要主动说出 IVFFlat 默认 probes = 1 意味着只看一个列表,建完索引不设 probes 基本等于没调过——这是新手最常见的事故。
Key points
- HNSW is a layered neighbor graph; IVFFlat clusters first and scans a subset of lists. HNSW favors query quality, IVFFlat favors build cost and memory.
- Tune the query-side knob first: ef_search for HNSW, probes for IVFFlat. Neither needs a rebuild.
- Heavy write traffic with tight memory and build windows points to IVFFlat; a static corpus with a hard latency SLA points to HNSW.
- IVFFlat clusters drift with the data and need scheduled rebuilds; HNSW does not, but its index is often larger than the table.
- Know the defaults: probes 1, ef_search 40. Leaving probes at 1 wastes the index.
答题要点
- HNSW 是分层邻居图,IVFFlat 是先聚类再局部扫描;前者查询质量优先,后者建索引与内存开销优先。
- 先调查询侧参数:HNSW 调 ef_search,IVFFlat 调 probes,两者都不需要重建索引。
- 高频写入、内存与建索引窗口紧张选 IVFFlat;数据相对静态、延迟有硬性要求选 HNSW。
- IVFFlat 的聚类会随数据漂移失真,需要定期重建;HNSW 没这个问题但索引常常比表还大。
- 默认值要记住:probes 是 1、ef_search 是 40,建完索引不调 probes 等于没用上索引的能力。
Why does a vector search with a WHERE clause return fewer results than expected, and what are the fixes and their costs?为什么加了 WHERE 条件的向量检索会漏结果?有哪几种修法,代价分别是什么?
Common in ChinaCommon overseasDeep dive#filtering#iterative-scan#recallHow to reason about it · think before answering
- This is the question that separates people who ran a demo from people who ran this in production. The tell is whether you distinguish missing rows from mis-ordered rows.
- State the mechanism in one sentence: with approximate indexes, filtering is applied after the index scan. The index first collects ef_search candidates by distance, and only then applies the WHERE clause to that batch.
- Do the arithmetic out loud: a condition matching 1% of rows against a default candidate list of 40 leaves well under one row on average. That is why the query looks broken even though the rows exist.
- Split the failure into two kinds. Too few rows returned is one; enough rows but the wrong ones ranked first is the other. They have different fixes, and conflating them signals inexperience.
- Fix one is iterative scanning, available since pgvector 0.8.0: when too many candidates are filtered out, keep scanning more of the index until enough results are found. Strict ordering keeps exact distance order, relaxed ordering trades slight reordering for better recall, and both cost latency.
- Fix two is making the filter apply first: a plain index on the filter column for highly selective conditions, a partial index when there are only a few distinct values, list partitioning when there are many. The costs are losing the approximate speedup, index count exploding per value, and DDL plus operational complexity.
- Expected follow-up: how do you pick? Check the returned row count first. Too few means iterative scanning; enough rows with low recall means raising probes or ef_search, or switching to pre-filtering.
分析过程 · 先想清楚再作答
- 这题是本天的核心,也是最能筛掉「只跑过 demo」的人的一题。题眼在「漏」这个字:能不能说清楚漏的是条数还是排序,直接决定你被归到哪一档。
- 先讲机制,一句话就够:近似索引的过滤发生在索引扫描之后。索引先按距离取回 ef_search 个候选,然后才拿 WHERE 去筛这一批。条件命中率越低,活下来的越少——命中 1% 的条件配默认的 40 个候选,平均只剩零点几条。
- 然后把漏召回拆成两类,这是拿分点:一类是**结果条数不够**,十条只给了一两条;另一类是**条数够但排序不对**,十条都在只是排错了。两类的修法完全不同,混为一谈说明没真跑过。
- 修法一是迭代扫描(pgvector 0.8.0 起):候选被过滤掉太多时自动回索引里继续扫,直到凑够。它只解决第一类。两种模式的取舍要说清楚——严格顺序保证结果按距离排好,宽松顺序允许略微乱序换更高召回,代价都是延迟明显上升。
- 修法二是预过滤,即让过滤条件先生效:条件很挑剔时给过滤列建普通索引走精确检索,取值只有少数几个时建部分索引,取值很多时按值做列表分区。代价分别是失去近似索引的加速、索引数量随取值爆炸、以及 DDL 与运维复杂度上升。
- 可预期的追问:怎么判断该用哪一种?给一条可执行的判据——先看返回条数够不够。不够是第一类,先试迭代扫描;够了但召回低是第二类,只能加大 probes 或 ef_search,或者干脆改成预过滤。
Key points
- With approximate indexes the filter runs after the index scan, so a selective condition wipes out most candidates and the query returns too few rows.
- There are two failure modes: too few rows, and enough rows in the wrong order. Always check the returned count first.
- Iterative scanning fixes only the first. Strict ordering preserves distance order, relaxed ordering gives better recall, and both raise latency noticeably.
- Pre-filtering is the alternative: index the filter column for exact search, use a partial index for a few distinct values, partition by value for many. Costs are losing the approximate speedup, index sprawl, and operational complexity.
- The second failure mode is only fixed by raising probes or ef_search; iterative scanning does nothing for it.
答题要点
- 近似索引的过滤发生在索引扫描之后,条件命中率低时候选几乎被筛光,所以返回条数不够。
- 漏召回分两类:条数不够,和条数够但排序不对。判断顺序永远是先看返回条数。
- 迭代扫描只修第一类,严格顺序保序、宽松顺序召回更高,代价是延迟明显上升。
- 预过滤是另一条路:过滤列建索引走精确检索、取值少建部分索引、取值多按值分区,代价依次是失去索引加速、索引数量爆炸、运维复杂度上升。
- 第二类只能靠加大 probes 或 ef_search,迭代扫描对它完全无效。
If you switch your vectors from full precision to half precision or binary quantisation, how do you verify that recall has not dropped materially?把向量从全精度换成半精度或二值量化,你会用什么方法确认召回没有明显下降?
Common in ChinaCommon overseasIntermediate#quantization#evaluation#recallHow to reason about it · think before answering
- The question looks like it is about quantisation, but it is really about whether you know how to evaluate. Answering 'try a few queries and eyeball it' fails immediately.
- Pin down ground truth first: it must come from an exhaustive scan with the index disabled. Using index results as ground truth is the classic self-deception, because recall then looks close to 100% no matter what you changed.
- Give the procedure: fix a query set of at least a few dozen covering short and long queries across topics, compute ground truth at full precision, rerun with the quantised representation, and report recall at k. Report index size, build time, and median plus p95 latency alongside it, because recall alone is not a decision.
- Add the judgment rule: quantisation loss depends on your vector distribution, so published numbers do not transfer. Sparse vectors suffer badly under binary quantisation because only the sign bit survives and zeros collapse together.
- Land on something actionable: half precision is usually near lossless and raises the indexable dimension ceiling from 2000 to 4000, so it is a safe first step. Binary quantisation loses real recall and should be used as a cheap first pass, re-ranked with the original vectors over a wider candidate window.
- Expected follow-up: how much loss is acceptable? It depends on what comes next. With a re-ranker downstream, a couple of points off first-stage recall is usually invisible; if retrieval feeds the prompt directly, one point means one more unanswerable question per hundred. Tie the threshold to a product metric, not to a number you made up.
分析过程 · 先想清楚再作答
- 这题表面问量化,实际问的是你会不会做评估。只回答「跑几个问题看看结果对不对」的人会被直接判为没做过——面试官想听的是一套可复现的量法。
- 先把真值这件事说死:真值必须来自暴力全量比对,也就是把索引关掉、全表算距离取前 k。拿索引结果当真值是最常见的自欺,因为那样量出来的召回永远接近 100%,你会以为量化无损。
- 然后给流程:固定一批查询(几十条起步,覆盖长短查询和不同主题),先用全精度算出真值,再换量化重跑,计算召回率@k。同时记录三件事——索引大小、建索引耗时、查询延迟的中位数与 p95,只报召回是不够的。
- 补一条判据:量化损失有多大取决于向量分布,别人的数字不能抄。稀疏向量对二值量化尤其不友好,因为二值化只保留符号位,零和负数会被压成同一个值,信息几乎被抹平。所以换方案必须在自己的数据上重新量一次。
- 结论要给可操作的建议:半精度通常近乎无损,还能把建索引维度上限从 2000 提到 4000,是默认可以先上的一档;二值量化损失明显,标准用法是拿它粗筛一批候选,再用原始向量在这一小批里精排,粗筛窗口越宽召回补得越多、延迟也越高。
- 可预期的追问:召回掉了多少算可以接受?答这取决于下游——后面还有重排时,粗排召回掉两三个点通常无感;如果检索结果直接进提示词,掉一个点就意味着每一百次回答里多一次缺材料。要把这个判断挂到业务指标上,而不是拍一个阈值。
Key points
- Ground truth must come from an exhaustive scan with indexes disabled; using index output as truth pins recall near 100%.
- Run one fixed query set before and after, report recall at k together with index size, build time and latency percentiles.
- Quantisation loss depends on your own vector distribution, so measure it on your data instead of quoting benchmarks.
- Half precision is usually near lossless and raises the indexable dimension limit from 2000 to 4000, making it a safe default.
- Binary quantisation loses real recall; use it as a cheap first pass and re-rank with the original vectors over a wider window.
答题要点
- 真值必须来自关掉索引的暴力全量比对,拿索引结果当真值会让召回永远接近 100%。
- 固定一批查询,量化前后跑同一批,报召回率@k,同时报索引大小、建索引耗时和延迟分位数。
- 量化损失取决于向量分布,别人的数字不能抄,必须在自己的数据上重新量。
- 半精度通常近乎无损,还能把索引维度上限从 2000 提到 4000,可以作为默认第一档。
- 二值量化损失明显,正确用法是粗筛加原始向量重排,粗筛窗口越宽召回补得越多、延迟越高。
When should you move your vectors out of PostgreSQL into a dedicated vector database? Give measurable triggers, and also make the case for staying.什么时候应该把向量搬出 PostgreSQL?给出可量化的触发条件,也说说不该搬的理由。
Common in ChinaCommon overseasIntermediate#vector-database#architecture#trade-offsHow to reason about it · think before answering
- This tests engineering judgment, not tooling preference. Opening with 'dedicated vector databases are better' invites follow-ups you cannot answer.
- State the default position and justify it: keep the first version in PostgreSQL, because transactions, backups, point-in-time recovery, permissions, joins with business tables and the tooling your team already knows all come free. A second datastore adds synchronization, a consistency surface and an on-call burden that selection documents rarely price in.
- Then give four measurable triggers: data volume (the test is whether the index still fits in memory, not the raw row count), write frequency (minute-level streaming updates distort clusters and inflate graphs), filter complexity (arbitrary combinations of a dozen attributes defeat both partial indexes and partitioning), and operational capacity.
- Expand on filter complexity, because it is most often the real reason: dedicated vector databases push filtering into the index structure instead of applying it after the scan, which is a mechanical advantage rather than a reputational one.
- Volunteer the alternative people skip: many 'vector search is not good enough' problems are actually solved by hybrid retrieval plus re-ranking, not by a new database. Add the keyword path and a re-ranker first, then decide.
- Expected follow-up: how would you migrate? Dual-write, compare recall and latency on shadow traffic, shift read traffic gradually, and only then retire the old path. Stop at any step where the metrics regress.
分析过程 · 先想清楚再作答
- 这题考的是工程判断,不是技术偏好。开口就说「专用向量库更专业」的人会被追问到答不上来;面试官想看的是你有没有把迁移成本算进去。
- 先给默认立场并给出理由:第一版留在 PostgreSQL,因为事务、备份、时间点恢复、权限、跟业务表 JOIN 和现成的运维工具全是白送的。多一个数据库就多一份同步、一份一致性问题、一份值班负担,这些成本很少被写进选型文档。
- 然后给四条可量化的触发线:数据量(判据不是行数而是索引还塞不塞得进内存)、写入频率(分钟级流式更新会让聚类失真、让图持续膨胀)、过滤复杂度(十几个属性的任意组合让部分索引和分区都排列组合不过来)、团队运维能力(没人愿意长期照看第二个数据库,前三条再成立也别搬)。
- 第三条要展开一点,因为它最常是真正的原因:专用向量库把过滤做进了索引结构本身,而不是扫完索引再筛,所以在复杂过滤下天然占优。把这一点说出来,说明你理解的是机制而不是口碑。
- 还要主动给一条常被忽略的替代路径:很多「向量检索不够用」的问题,真正的解法是混合检索加重排,而不是换数据库。先把关键词一路加回来、把重排接上,再决定要不要搬——顺序搞反了会白搬一次。
- 可预期的追问:真要搬怎么迁?答分三步——先双写并在影子流量上比对两边的召回与延迟,再把读流量按比例切过去,最后才停掉旧路径。中间任何一步指标不达标就停下,这比一次性切换安全得多。
Key points
- Default to staying in PostgreSQL: transactions, backups, recovery, permissions, joins and familiar tooling are free, and a second store adds sync and on-call cost.
- Trigger one is data volume, measured by whether the index still fits in memory rather than by row count.
- Trigger two is write frequency: minute-level streaming updates distort clusters and inflate graphs.
- Trigger three is filter complexity: dedicated stores push filtering into the index structure, a mechanical advantage under complex predicates.
- Trigger four cuts the other way: without people to run a second database, do not move even if the first three hold. Often hybrid retrieval plus re-ranking is the real fix.
答题要点
- 默认留在 PostgreSQL:事务、备份、恢复、权限、JOIN 和现成运维都是白送的,多一个库就多一份同步与值班成本。
- 触发线一是数据量,判据是索引还塞不塞得进内存,而不是行数本身。
- 触发线二是写入频率,分钟级流式更新会让聚类失真、让图持续膨胀。
- 触发线三是过滤复杂度,专用库把过滤做进索引结构,复杂过滤下有机制上的优势。
- 触发线四反过来看:没有长期运维第二个数据库的人手,前三条成立也不该搬;很多问题的真正解法是混合检索加重排。
D6 The Generation Side: Ordering Context, Labeling Citations, When You Must Refuse to Answer, and Streaming Responses
How do you make sure a model's citations are real rather than fabricated? Describe a scheme that does not rely on the model behaving well.怎么让模型的引用是真的而不是编的?说出一个不依赖模型自觉的方案。
Common in ChinaCommon overseasIntermediate#citation-verification#grounding#hallucinationHow to reason about it · think before answering
- The phrase to catch is 'not relying on the model behaving well'. Any answer that boils down to 'tell the model to be accurate in the prompt' fails, because the prompt is exactly the part that cannot enforce this.
- Split the problem in two. Verifiability requires that a citation be a symbol from a closed set, not free text. So step one is numbering the blocks at assembly time and telling the model it may only cite the numbers it was given. 'According to the storage handbook' cannot be checked, because the title is a string the model can invent.
- Step two is post-hoc checking, with two gates. Gate one is existence: you handed out 1 through 5, so an 8 is fabricated, and that is a one-line check. Gate two is substantive overlap, which catches the sneakier case where the number is real but the block says something else. Measure what fraction of the sentence's terms appear in the cited block and reject below a threshold.
- Mention the trap in the overlap metric: drop terms that appear in most blocks first, otherwise generic words let any citation pass. It is the same reasoning behind inverse document frequency in BM25.
- On failure, feed the specific reason back and regenerate once, not repeatedly. Two fabricated drafts in a row means the material does not support the question, so refuse instead. Also verify against the original chunk text, never against a compressed or rewritten version, otherwise 'verified' says nothing about what the user sees.
- Expected follow-up: why not ask the model to self-check? Self-checking shares the generator's bias and has no independent source of truth, whereas number checking is deterministic, essentially free, and reproducible.
分析过程 · 先想清楚再作答
- 题眼在「不依赖模型自觉」这半句。回答里只要出现「在提示词里强调请确保引用准确」,这题就答砸了——面试官问的正是提示词管不住的那部分。
- 先把问题拆成两半:引用要能验证,前提是它是一个**闭集里的符号**,不是一段自由文本。所以第一步是组装上下文时给每块材料一个编号,提示词里明确只能引用发出去的编号。让模型写「根据《某某手册》」是没法验证的,标题是它可以随口生成的字符串。
- 第二步是事后核对,两道闸缺一不可。第一道查编号存在性:发出去的是 1 到 5,出现 8 就一定是编的,一行代码判掉。第二道查实质重合:编号是真的、内容却对不上,这类更隐蔽,要算这句话的词元有多大比例能在被引块原文里找到,低于阈值判不通过。
- 算重合度时有个坑要主动说出来:先剔掉在多数块里都出现的高频词元,否则「文件」「系统」这种词会让随便哪一块都及格。这跟 BM25 用逆文档频率压常见词是同一个道理。
- 校验不过怎么办:把具体原因写成反馈打回去重生成一次,只给一次机会;连着两版都编说明材料本来就不支持,该走拒答而不是第三次重试。另外校验必须拿原文比对,不能拿压缩或改写过的材料比对,否则「校验通过」保证不了用户点开看到的东西。
- 可预期的追问:为什么不让模型自己再检查一遍?因为自检和生成是同一个模型的同一种倾向,它对自己编的东西没有独立信息源;而编号核对是一个确定性判断,成本几乎为零、结果可复现,这两点自检都做不到。
Key points
- Citations must be closed-set symbols such as block numbers, not free-text titles: verifiability comes from the closed set, not from wording.
- Two gates: the number must exist, and the sentence must substantively overlap the cited block's original text, which is what catches real-number-wrong-content fabrication.
- Strip terms that occur in most blocks before scoring overlap, or any citation will pass.
- On failure, regenerate once with the concrete reason fed back; two bad drafts means refuse instead.
- Always verify against the original text the user can open, never against a compressed or rewritten copy.
答题要点
- 引用必须是块编号这种闭集符号,不能是自由文本的文档标题——可验证性来自闭集,不来自措辞。
- 两道闸:编号存在性,以及这句话与被引块原文的实质重合度,后者才拦得住「编号是真的、内容对不上」。
- 算重合度前剔掉在多数块里都出现的高频词元,否则随便引哪一块都能及格。
- 校验不过就带着具体原因打回重生成一次,只给一次机会,两版都编就转拒答。
- 校验对象必须是用户能点开看到的原文,不是压缩或改写后的材料。
Does the ordering of retrieved passages in the context affect answer quality? If so, how would you order them?上下文里材料的排列顺序会影响回答质量吗?如果会,你会怎么排?
Common in ChinaCommon overseasBasic#context-assembly#prompt-engineering#orderingHow to reason about it · think before answering
- This is a warm-up question, but 'sort by relevance descending' only earns half the credit. The interviewer wants to know whether you treat position itself as a variable.
- State the conclusion first: it does matter. Models attend more reliably to material at the start and the end of the context, and are most likely to miss what sits in the middle. Plain descending order therefore parks your second-best passage in the worst spot.
- Give the ordering: rank one first, rank two last, rank three second, rank four second-to-last, folding inward. Whatever ends up in the middle is by construction the least important, so the cost of it being skipped is smallest.
- Round it out with the other assembly steps, which shows you have written this code: a deterministic tiebreaker (otherwise block numbers drift between runs and your logs stop matching), dedupe on normalized text, and a token budget that skips rather than stops when a block does not fit.
- Expected follow-up: how would you verify this? Do not guess. Hold the question set fixed, vary only the ordering, and measure. Position effects differ by model and context length, so treat it as a parameter to measure on your own data rather than a universal law.
分析过程 · 先想清楚再作答
- 这是一道送分题,但答成「按相关性从高到低排」就只拿到一半分。面试官想听的是你知不知道位置本身是个变量。
- 结论先说:会影响。模型对上下文开头和结尾的材料明显更敏感,正中间的最容易被读漏。所以简单按分数从高到低顺排,等于把第二重要的材料放进了最不容易被读到的位置。
- 给出排法:第 1 名放开头、第 2 名放结尾、第 3 名放第二位、第 4 名放倒数第二位,依次往里收。这样按分数排下来越靠中间的块本来就越不重要,被读漏的代价最小。
- 顺带把排序之外的三道手续说全,显得你真的写过这段代码:同分要有决胜键(否则块编号会在两次运行之间飘,日志对不上)、要按归一化文本去重(同一段话常在手册和问答里各出现一次)、要有 token 预算并且塞不下时不要直接停。
- 可预期的追问:这个结论怎么验证?答案是别猜——固定一批问题,只改排列顺序跑对照,看指标差多少。位置效应在不同模型、不同上下文长度上强弱不一样,把它当成一个要在自己数据上量的参数,而不是一条普适定律。
Key points
- Yes: material at the head and tail is used more reliably, the middle is most often skipped.
- Put the strongest at both ends: rank one first, rank two last, rank three second, folding inward.
- Assembly also needs a deterministic tiebreaker for stable numbering, dedupe on normalized text, and a token budget that skips oversized blocks instead of stopping.
- The strength of the effect varies by model and context length, so measure it on your own data instead of quoting it as a law.
答题要点
- 会影响:开头和结尾的材料更容易被用上,正中间的最容易被读漏。
- 排法是最重要的放两端:第 1 名开头、第 2 名结尾、第 3 名第二位,依次往里收。
- 组装还要做三件事:同分给决胜键保证编号稳定、按归一化文本去重、控 token 预算且塞不下时跳过而不是终止。
- 位置效应的强弱因模型与上下文长度而异,要在自己的数据上做对照实验量出来,不能当普适定律照搬。
How do you set the refusal threshold for a knowledge-base assistant, and what does it cost you when the threshold is too high or too low?知识库问答的拒答阈值怎么定?定高了和定低了各自的代价是什么?
Common in ChinaCommon overseasDeep dive#refusal#thresholds#evaluationHow to reason about it · think before answering
- What is really being tested: do you know that refusal is several rules rather than one threshold, and do you set thresholds from data. An answer that mentions only a score cutoff shows you have only touched the surface.
- Break refusal into three rules with different timing. Score too low: decidable before generation, saving a model call. Sources conflict: also decidable before generation, by finding differing numbers about the same thing across blocks. You then either present both with their update dates, or pick the newer one when an authoritative signal backs it, such as meeting notes that flagged the discrepancy. Which of the two is a product decision, but silently letting the model pick is never an option. Question outside coverage: only decidable after generation, when citation verification leaves you with zero verified citations.
- Stress that the three responses must read differently. 'Nothing relevant in the knowledge base, try rephrasing or check whether the document was ingested' is a different instruction to the user than 'we found related documents but none of them answers this'. Collapsing both into 'sorry, I don't know' throws away information.
- Then the cost half. Too high: answerable questions get blocked, the user is told nothing was found while the material is in fact indexed. That is the most trust-damaging failure and it is nearly invisible in logs. Too low: weak passages enter the context and the model answers from irrelevant material, which is worse because the answer still looks cited.
- How to set it: run a set of questions with known answers and known non-answers, look at where the two score distributions separate, and pick a point according to which error you fear more. Scores have no absolute scale, so the deliverable is the procedure, not the number.
- Expected follow-up: what if one score threshold is not enough? Add signals rather than tuning the number: the gap between top and second score, the number of hits above threshold, and the post-generation verification result are all steadier than the raw score.
分析过程 · 先想清楚再作答
- 这题真正在考的是:你有没有意识到拒答不是一个阈值,而是好几条判据;以及你定阈值靠不靠数据。只谈一个分数阈值的回答,说明只做过最浅的一层。
- 先把拒答拆成三条线,它们的触发时机完全不同。检索分数太低:生成之前就能判,省一次模型调用。材料互相矛盾:也在生成之前判,代码在块之间找同一件事的不同数字,检出后要么并列两种说法与各自的更新日期,要么在有权威信号(比如一份点破了这条不一致的会议纪要)时按更新日期择一——选哪条是产品决策,但无论如何不能让模型自己悄悄挑一个。问题超出材料覆盖范围:只能在生成之后判,判据是跑完引用校验一条有效引用都没有。
- 强调三种话术必须不同。第一种要说「库里没有相关材料,换个说法或确认资料是否入库」,第三种要说「找到了相关文档但里面没有能直接回答的内容」——用户的下一步动作完全不同,混成一句「抱歉我不知道」等于把信息扔了。
- 再答代价这一半。定高了:能答的问题被挡在门外,用户看到查不到而材料其实在库里,这是最伤信任的一种错,而且它在日志里几乎不可见。定低了:低分噪声材料进上下文,模型拿着不相关的东西硬答,错误反而更隐蔽,因为回答看起来还带着引用。
- 怎么定:拿一批已知有答案和已知没答案的问题跑一遍,看两组的分数分布在哪里分开,按你更怕哪种错来取点。分数是没有绝对量纲的,换语料、换检索方式都要重定,所以真正要交付的是这套定阈值的流程,不是那个数字。
- 可预期的追问:单一分数阈值不够怎么办?答案是加判据而不是调数字——最高分与次高分的差、命中块数、以及生成后的引用校验结果,都是比原始分数更稳的信号。
Key points
- Refusal is three rules, not one: low score and source conflict decided before generation, out-of-coverage decided after generation from the verification result.
- On conflict, presenting both versions versus picking the newer one is a product decision; picking only holds up when an authoritative signal backs it.
- The three responses must be worded differently because each implies a different next action for the user.
- Too high blocks answerable questions; the user is told nothing exists while it does, which is the most damaging and least visible failure.
- Too low lets weak passages in, producing errors that are harder to spot because the answer still carries citations.
- Set it by comparing score distributions over answerable and unanswerable question sets, then choose based on which error is worse; re-tune whenever the corpus or retriever changes.
答题要点
- 拒答不是一条线而是三条:分数过低、材料冲突(都在生成前判)、超出材料覆盖范围(只能生成后按引用校验结果判)。
- 冲突检出后并列两说还是按更新日期择一,是产品决策;只有在有权威信号背书时择一才站得住,否则老实并列。
- 三种情况的话术必须不同,因为它们给用户的下一步动作不同。
- 定高了会把能答的问题挡住,用户看到查不到而材料其实在库里,最伤信任且日志里看不见。
- 定低了会让噪声材料进上下文,错误更隐蔽,因为回答看起来仍然带着引用。
- 定法是拿已知有答案与已知没答案的两组问题跑分数分布,按更怕哪种错取点;换语料或换检索方式都要重定。
In a streaming setup, how do you make sure nothing you have already sent needs to be retracted because its citation failed verification?流式输出的场景下,你怎么保证吐出去的内容不会因为引用校验失败而需要撤回?
Common in ChinaCommon overseasDeep dive#streaming#citation-verification#api-designHow to reason about it · think before answering
- This tests a real architectural conflict: streaming wants the first token out early, citation verification cannot run until a statement is complete. Listen for whether the candidate names the trade-off and prices it.
- Name the conflict: once a token reaches the browser you cannot take it back. Discovering at the end that the third sentence cited a fabricated block leaves you posting 'please ignore that last sentence', which is worse than not streaming at all.
- Give the solution: buffer by sentence. As soon as a complete sentence lands, verify it, and only then emit it together with its verified citations; drop the whole sentence otherwise. The cost is that time-to-first-token becomes time-to-first-sentence, typically a few hundred milliseconds, which users barely notice, whereas a bad citation on screen costs trust.
- Add two implementation details that prove you have built it. Streaming cannot use JSON output because JSON is only parseable once closed, so switch to plain text with inline markers, while keeping exactly the same verifier as the non-streaming path. Strip the markers out of the prose and send the numbers as structured data after verification.
- Add the ordering point: the two rules decidable before generation, low score and source conflict, should be emitted before the stream starts, so the user never sees half an answer being withdrawn. The rule that needs generation shows up as 'no sentence was ever emitted', so close the stream with a refusal event.
- Expected follow-up: does this kill the streaming feel? No. Sentence-level streaming is still visibly progressive on long answers. If you need finer granularity, stream a 'checking sources' placeholder, but never stream unverified prose.
分析过程 · 先想清楚再作答
- 这题在考一个真实的架构矛盾:流式要尽早出字,引用校验要等话说完才能核对。看回答里有没有出现「取舍」两个字,以及有没有把代价说清楚。
- 先说清矛盾在哪:一旦一个 token 发到了浏览器就撤不回来,你在末尾才发现第三句引用是编的,那句话已经在用户屏幕上了,只能补一句「刚才那句请忽略」,体验比不流式还糟。
- 给方案:按句缓冲。攒够一个完整句子就立刻校验一次,通过了才把这句连同已核实的引用发出去,没通过就整句丢掉。代价是首字延迟从一个 token 变成一句话,通常两三百毫秒,用户几乎察觉不到,而错误引用一旦上屏赔的是信任。
- 补两个实现细节,它们能证明你写过:流式模式没法用 JSON 输出(要等右花括号闭合才能解析),所以改成纯文本加行内标记,但校验必须和非流式共用同一套;标记要从正文里剥掉,正文保持干净,编号单独走校验再作为结构化数据发出去。
- 再补一条顺序上的讲究:生成前就能判的两条拒答线(分数过低、材料冲突)要在流开始之前发出去,用户不会先看到半句回答再被收回;生成后才能判的那条,在按句缓冲之下表现为一句都没发出来,收尾补一个拒答事件即可。
- 可预期的追问:那用户体验上的流式感是不是就没了?没有,句级流式在中文长回答里仍然是明显的渐进呈现;真要更细,可以在句子发出前先流一个「正在核对」的占位态,但不要流未校验的正文。
Key points
- The conflict: emitted text cannot be recalled, while a citation can only be checked once its sentence is complete.
- The fix is sentence-level buffering: verify each completed sentence, emit only if it passes, drop the whole sentence if it does not.
- The cost is time-to-first-sentence instead of time-to-first-token, which is affordable and worth paying.
- Streaming cannot use JSON, so use inline markers in plain text while sharing one verifier with the non-streaming path; strip markers from the prose and send numbers as structured data.
- Emit pre-generation refusals before the stream opens; the post-generation one manifests as an empty stream and is closed with a refusal event.
答题要点
- 矛盾在于发出去的内容撤不回来,而引用只有一句说完才能核对。
- 解法是按句缓冲:攒够一句校验一次,通过才发,没通过整句丢掉。
- 代价是首字延迟从一个 token 变成一句话,这个代价必须付也付得起。
- 流式用不了 JSON,改纯文本加行内标记,但校验逻辑与非流式共用同一套;标记从正文剥出,编号作为结构化数据单独发。
- 生成前能判的拒答要在流开始之前发出去,生成后才能判的那条以「一句都没发」的形式收尾补事件。
D7 Week One Capstone: Assembling Six Days of Parts Into a One-Command Question-Answering Service, and a Retrospective
How would you draw the module boundaries of a RAG system, and which layer most needs to be swappable? Why?你会怎么划分一个检索增强生成系统的模块边界?其中哪一层最应该做成可替换的,为什么?
Common in ChinaCommon overseasBasic#architecture#modularity#embeddingsHow to reason about it · think before answering
- This question separates people who have maintained such a system from people who have only built a demo. Reciting the pipeline diagram is not an answer; where you cut it is.
- Offer a reusable criterion first: cut where a layer is most likely to be replaced wholesale, not by lines of code or by tidy functional names.
- Apply it. Embedding models change several times a year, and each change invalidates every stored vector, so that layer must be an interface. Storage may move from PostgreSQL to a dedicated vector database, and both ingestion and query talk through it, so it is the single shared boundary. Chunking changes daily during tuning, so it belongs in config, not in code.
- Conclusion: the embedding layer is the one that must be swappable, because the swap is both likely and expensive, not because interfaces are good style.
- Name the cost of abstraction too: every indirection is one more hop while debugging, so the test is whether the change will actually happen.
- Expected follow-up: should the generation model be abstracted as well? Yes, but at lower priority, because swapping it does not force recomputation of stored data and rollback is cheap. It is a config value, not a layer.
分析过程 · 先想清楚再作答
- 这题考的是你有没有真的维护过这类系统。只按「解析、切块、检索、生成」复述一遍流程图,面试官会判定你只搭过 demo——流程图人人都会画,切口画在哪才是经验。
- 给一条可复用的判据再往下推:切口应该落在「将来最可能被整个换掉」的地方,而不是按代码量或者功能名称均分。
- 用它过一遍:embedding 一年会换好几次,换一次库里所有向量作废、必须全量重算,所以它必须是接口;存储可能从 PostgreSQL 换成专用向量库,而且摄取和查询都要通过它,所以它是两条链路的唯一交界;切块策略在调优期天天改,所以它必须是配置项而不是硬编码。
- 结论:最该做成可替换的是 embedding 那一层,理由不是「设计模式」,而是「换模型这件事真的会发生,且发生时代价极高」。
- 顺手点出抽象的代价:每多一层间接就多一次跳转和一份心智负担,所以判据是「那件事会不会真的发生」,不会发生的别抽象。
- 可预期的追问:那生成模型要不要也抽象?答案是要,但优先级低——换生成模型不需要重算任何存量数据,回滚也便宜,所以它是配置项而不是一层接口。
Key points
- Lead with the criterion: cut where a layer is most likely to be replaced wholesale.
- The embedding layer is the one to abstract: swapping models invalidates every stored vector and forces a full recompute.
- Storage is the single boundary shared by ingestion and query, so define its interface before either implementation.
- Chunking and retrieval routes belong in configuration because they change most often during tuning.
- Abstraction costs indirection, so only abstract changes that will actually happen.
答题要点
- 先给判据:切口落在最可能被整体替换的那一层,不按代码量或功能名称均分。
- embedding 是最该抽象的一层:换模型意味着存量向量全部作废、必须全量重算,代价高且真的会发生。
- 存储层是摄取与查询唯一的交界,接口要先定下来再谈两边实现。
- 切块与检索路数做成配置项,因为它们在调优期改动最频繁,改一次不该动代码。
- 抽象有成本,判据是那件事会不会真的发生;不会发生的抽象就是过度设计。
What should the ingestion path and the query path share, and what concretely goes wrong when you over-share?摄取链路和查询链路应该共享哪些代码?强行复用会带来什么具体问题?
Common in ChinaCommon overseasIntermediate#architecture#ingestion#retrievalHow to reason about it · think before answering
- The word to notice is 'over-share'. The interviewer wants the boundary, not a recital of DRY.
- Start from how the two paths differ. Ingestion is batch: tens of seconds, and a failure just means rerunning it. Query is online: hundreds of milliseconds, and a failure is visible to the user immediately. Error handling, timeouts and concurrency are simply not the same problem.
- Hence the rule: share the interface, not the flow. The only genuinely shared thing is the storage interface, plus the embedding function signature.
- Name the symptom of over-sharing: the extracted module fills up with isIngest branches, every change has to be verified on both paths, and eventually nobody dares touch it.
- Add the one thing that truly must match: chunks and queries must be embedded by the same model. That is shared configuration, not shared code, and the model name belongs in the vector table so a silent mismatch is detectable.
- Expected follow-up: what about chunking? The query path never chunks. Even when it needs a parent block, it reads it back through storage rather than importing the chunker.
分析过程 · 先想清楚再作答
- 题眼在「强行」两个字。面试官想看的是你能不能说出复用的边界,而不是背诵「不要重复自己」。
- 先说清两条链路的性质差异:摄取是批处理,几十秒跑完,失败重跑一遍就行;查询是在线请求,几百毫秒要出结果,失败用户当场看到。错误处理、超时、并发策略天然不同。
- 所以结论是:**共享接口,不共享流程**。两边唯一该共享的是存储层的那个接口,以及 embedding 的函数签名——注意后者共享的是签名和模型选择,不是调用流程。
- 给出强行复用的具体症状:抽出来的公共模块里开始出现 isIngest 这类分支,一个改动要同时验证两条链路,最后没人敢动它。
- 补一条真正必须一致的东西:给块算向量和给问题算向量必须用同一个模型。这不是复用代码,是复用配置——而且要把模型名写进向量表,否则模型换了没人发现,检索会静默地返回垃圾。
- 可预期的追问:那切块逻辑呢?查询侧压根不切块,所以它只属于摄取链路;真要在查询侧用到(比如 D11 的父子回填),走的也是存储层读回大块,不是把切块器搬过来。
Key points
- Share the interface, not the flow: storage is the only boundary, plus the embedding signature.
- The two paths have different error handling and latency budgets; batch can rerun, online must fail fast.
- Over-sharing shows up as isIngest branches and changes that must be verified twice.
- What must match is the model choice, not the code: record the model name alongside every stored vector.
- Chunking belongs to ingestion only; the query path reads larger units back through storage.
答题要点
- 共享接口不共享流程:唯一的交界是存储层,加上 embedding 的函数签名。
- 两条链路的错误处理与延迟约束根本不同,批处理可以重跑,在线请求必须快速失败。
- 强行复用的症状是公共模块里长出 isIngest 分支,改一次要验两条链路。
- 必须一致的是模型选择而不是代码:块与查询要用同一个 embedding 模型,并把模型名记进向量表。
- 切块只属于摄取;查询侧需要大块时通过存储层读回,而不是把切块器搬过去。
What three checks would you run before shipping a retrieval QA service, and why those three?一个检索问答服务上线前你会做哪三项检查?为什么偏偏是这三项?
Common in ChinaCommon overseasIntermediate#production-readiness#citations#refusalHow to reason about it · think before answering
- The discriminator is not how many checks you list but whether you can justify the three. Ten items with no ranking suggests you have never had to prioritize.
- Derive them by consequence: the failures that are invisible to users and most damaging go first.
- First, citations must be verifiable: every cited id resolves to a real chunk, and that chunk genuinely overlaps the sentence citing it. This ranks first because a wrong citation is undetectable by the user, and citations are the only source of trust this system has.
- Second, refusal must actually fire: ask a question the corpus cannot answer and confirm the system says so instead of inventing. Also invisible, and one discovered fabrication zeroes out trust in the whole product.
- Third, ingestion-to-retrieval consistency: freshly ingested documents are retrievable immediately, and the keyword and vector paths cover the same set. This guards against the 'one route finds it, the other does not' failure, which is the hardest to diagnose.
- Expected follow-up: why not latency and cost? Because those failures are visible. Users complain about slowness and the bill reports overspending; nobody will ever report the three above.
分析过程 · 先想清楚再作答
- 这题的区分度不在你能列几项,而在你能不能说清「为什么是这三项」。列十项而每项都不给理由,反而说明你没有排过优先级。
- 推导方式是按后果排序:哪种故障用户看不出来、又损失最大,哪一项就该排在前面。
- 第一项是引用可查证:每条引用的编号都能回查到真实存在的块,且那一块确实与该句有实质重合。这一项排第一是因为引用错了用户根本发现不了,而它恰恰是这类系统唯一的信任来源。
- 第二项是该拒答时真的拒答:构造一个语料里没有答案的问题,看它是回那句拒答话术还是开始编。这一项也属于用户看不出来的故障,且一旦编造被发现,整个系统的可信度归零。
- 第三项是摄取到检索的一致性:摄取完之后新文档立刻能被检索到,且关键词与向量两路的覆盖数量对得上。这一项防的是「一路能查一路查不到」这种最难排查的故障。
- 可预期的追问:为什么延迟和成本不在前三?因为它们是**看得见**的故障——慢了用户会抱怨,贵了账单会告诉你;而上面三项不检查就永远不会有人告诉你。
Key points
- State the ranking rule first: prioritize failures users cannot see but that cost the most.
- Check one, verifiable citations: every id resolves to a real chunk that overlaps the sentence citing it.
- Check two, refusal actually fires on a question the corpus cannot answer.
- Check three, ingestion and retrieval agree: new documents are immediately retrievable on both routes.
- Latency and cost matter but rank lower because those failures announce themselves.
答题要点
- 先给排序依据:优先检查用户发现不了、但后果最重的故障。
- 第一项引用可查证:编号能回查到真实的块,且该块与被引的那句话有实质重合。
- 第二项拒答生效:用一个语料里没有答案的问题验证系统会说查不到,而不是开始编。
- 第三项摄取与检索一致:新入库的文档立刻可检索,关键词与向量两路覆盖对得上。
- 延迟和成本重要但排在后面,因为它们是看得见的故障,会自己找上门。
What is the biggest risk in the RAG service you just assembled, and how would you prove that judgment?你刚拼出来的这个检索问答系统,现在最大的风险在哪里?你打算怎么证明这个判断?
Common in ChinaCommon overseasDeep dive#evaluation#risk-assessment#retrospectiveHow to reason about it · think before answering
- There are two halves here and the second is the real question. Naming a risk is easy; giving a method that could falsify your own claim is what separates answers from opinions.
- Rule out two common wrong answers: 'hallucination' is too vague to act on, and 'latency' mistakes a visible problem for the biggest one.
- The biggest risk is the absence of evaluation. Chunk size, top-k, thresholds and route weights were all guessed, and that makes every other risk unverifiable: you cannot even say whether a change helped.
- How to prove it: build a question set from the corpus with known answer documents, deliberately including unanswerable and multi-hop questions; implement recall and ranking metrics; produce a baseline for the current configuration; then move one parameter back and forth and watch whether the metrics move. If they do not move at all, the evaluation set is wrong, not the system.
- Add the accounting rule: every optimization reports three numbers, metric gain, latency added and cost added. A claim with only the first is not usable.
- Expected follow-up: how large must the set be? Start with roughly twenty questions covering the main question types to catch obvious regressions, then grow toward the real distribution once you have actual user questions. Chasing size first only yields questions you invented yourself.
分析过程 · 先想清楚再作答
- 这题有两半,后半句才是题眼。说出一个风险不难,难的是给出一个能证伪你自己判断的方法——答不出后半句,前半句就只是意见。
- 先排除两个常见的错误答案:说「幻觉」太笼统,没有指向任何可动的地方;说「延迟」则是把看得见的问题当成最大风险。
- 真正的最大风险是**没有评估**:切块大小、取几条、门槛定多少、两路怎么加权,全是拍出来的。它最重要的地方在于它让所有其他风险都无法验收——你连「改了之后变好还是变坏」都说不出口。
- 怎么证明:先从语料反向出一份带标准答案文档的问题集,刻意掺进无答案问题和需要跨文档的多跳问题;再实现召回率与排序指标,给当前配置跑出一个基线;然后把一个参数来回改两次,看指标动不动。如果指标对参数完全不敏感,说明是评估集有问题,不是系统没问题。
- 补一句成本口径:每一项优化都要同时报三笔账——指标涨了多少、延迟涨了多少、钱涨了多少。只报第一笔的结论不能用。
- 可预期的追问:评估集多大才够?先做二十题能覆盖主要问题类型的小集,用它挡住明显的退步;等真实用户问题攒起来,再按真实分布扩到几百题。一上来就追求规模,只会得到一堆自己出的、跟真实用法无关的题。
Key points
- The biggest risk is having no evaluation: every parameter was guessed, so no change can be judged.
- Prove it by building a golden set with known answer documents, including unanswerable and multi-hop questions, then baseline the current configuration.
- Validate the set itself by perturbing parameters: metrics that never move mean the questions are wrong.
- Report three numbers per optimization: metric gain, added latency, added cost.
- Start small but well covered, then grow toward the real question distribution.
答题要点
- 最大的风险是没有评估:所有参数都是拍的,导致任何改动的好坏都无法判断。
- 证明方式是先建标准答案集,刻意包含无答案问题与多跳问题,再跑出当前配置的基线。
- 用参数扰动反过来验证评估集本身:指标对参数完全不敏感,说明题出得有问题。
- 每项优化同时报三笔账:指标、延迟、成本;只报指标的结论不能用。
- 评估集先小而全,覆盖问题类型即可,等真实问题攒起来再按真实分布扩大。
D8 Evaluation First: Building a Golden Set, Computing Recall and Ranking Metrics, Using a Model as Judge for Faithfulness
You need to build an evaluation set from scratch for a RAG system over a company knowledge base. How would you do it, and how many questions are enough?让你从零给一个公司知识库的 RAG 系统建评估集,你会怎么做?多少题才算够用?
Common in ChinaCommon overseasIntermediate#evaluation#golden-set#ragHow to reason about it · think before answering
- The discriminator here is the direction you generate questions in, and whether you can justify a size rather than name one.
- Go corpus-first: read each document and write the questions it can answer. The answer document is fixed at authoring time, so labeling is nearly free. Question-first gives you items whose answers nobody can locate.
- Give the schema: question, answer document ids, and a type. At minimum three types - single-document, multi-hop, and unanswerable. Multi-hop counts as a hit only when every answer document makes it into the context; unanswerable items are scored on abstention, not recall.
- Justify the size: 20 items separate 'broken' from 'usable' and are enough for a smoke gate; 100 to 200 are needed before a two-point delta means anything. Then grow the set - every production failure becomes a new item.
- Mention cost and decay: roughly two hours for 20 items, and answer labels must be rechecked whenever the corpus changes, or the set rots and you misread the drop as a system regression.
- Expected follow-up: how do you avoid overfitting to the eval set? Keep a held-out slice that never informs tuning, and refresh it from real production questions.
分析过程 · 先想清楚再作答
- 这题的区分度在「出题方向」和「规模的理由」两处。开口就说「找几百个用户真实问题」的,多半没真做过——真实问题的答案在哪篇文档里,没人标得出来。
- 先给方向:从语料反向出题,打开每一篇读它能回答什么,出题的那一刻答案文档就已经确定了,标注成本几乎为零。反方向(先想问题再找答案)会得到一堆自己都不知道答案的题。
- 再给结构:每题记问题、答案文档列表、类型三个字段;类型至少分单文档、多跳、无答案三类,并说明多跳必须全部答案文档命中才算命中,无答案不参与召回率而是考拒答。
- 规模的理由要给出来,不能只报一个数字:20 题能把「完全不能用」和「基本能用」分开,够做冒烟;100 到 200 题才有资格判断「涨了两个点」是真的还是噪声。上线之后每次线上出问题就把那个问题补进集合——评估集是长出来的。
- 补一句成本与保鲜:出题是人力活,20 题两小时是正常量级;语料更新后要复核答案文档还在不在,否则集合会悄悄腐烂,指标下跌你会误以为是系统坏了。
- 可预期的追问是「怎么防止评估集被过拟合」。答案是留一份不参与调优的保留集,并且定期从线上真实问题里补充新题,只用来验收不用来调参。
Key points
- Author corpus-first so the answer document is known at authoring time.
- Label every item with a type: single-document, multi-hop, unanswerable.
- Multi-hop requires all answer documents; unanswerable items score abstention, not recall.
- 20 items for a smoke gate, 100 to 200 to trust small deltas, and keep growing it from production failures.
- Hold out a slice that never informs tuning to avoid overfitting the set.
答题要点
- 从语料反向出题,出题时答案文档就已确定,标注成本最低。
- 每题标类型:单文档、多跳、无答案,三类缺一不可。
- 多跳要求全部答案文档命中;无答案不算召回率,考的是拒答。
- 20 题够冒烟,100 到 200 题才能判断小幅变化;线上故障持续补题。
- 留一份不参与调优的保留集,防止对评估集过拟合。
Recall, mean reciprocal rank, and normalized discounted cumulative gain - which failure mode does each one catch first, and what do you miss by watching only one?召回率、平均倒数排名、归一化折损累计增益,这三个检索指标分别在什么故障下会先掉下来?只盯一个会漏掉什么?
Common in ChinaCommon overseasIntermediate#retrieval-metrics#evaluation#rankingHow to reason about it · think before answering
- This tests whether you know each metric's blind spot, not whether you can recite definitions. Layer them as 'did it show up / how high / how good overall' and you are halfway there.
- Recall is boolean: is the answer document in the final context. It catches 'never retrieved', but it does not move when the answer slips from rank 1 to rank 8, as long as it still fits the budget.
- MRR looks only at the rank of the first relevant hit, so ranking degradation shows up immediately. Its blind spot: one relevant item in the top ten scores exactly the same as five.
- nDCG discounts every relevant hit in the top k by its position, so it tracks overall ranking quality and is the direct optimization target for reranking. Its blind spot is existence - it is zero both when nothing was retrieved and when ranking is terrible.
- Conclusion: together they localize the failure. Recall drops means retrieval or chunking; recall flat but MRR down means ranking degraded, reach for a reranker; both stable but nDCG down means more noise crept into the top results.
- Expected follow-up: what if a metric saturates? Make the questions harder - a saturated metric means the eval set lost its discriminative power, and further tuning is blind.
分析过程 · 先想清楚再作答
- 这题考的是「知不知道指标之间的盲区」,不是背定义。能把三者按「有没有 / 靠不靠前 / 整体好不好」分层的,基本就答对了一半。
- 推导链是这样的:召回率是布尔的——答案文档在不在最终上下文里。它对「压根没捞到」最敏感,但答案从第 1 名掉到第 8 名它一动不动,只要还在预算内。
- 倒数排名只看第一条相关结果的名次,所以「答案还在但被挤到后面」它立刻掉。反过来它有个盲区:前十条里有一条命中还是五条命中,它给的分完全一样。
- 归一化折损累计增益把前 k 名里每一条相关结果都按名次折算再累加,所以它对「整体排序质量」敏感,是重排最直接的优化目标。它的盲区是不告诉你「有没有」——召回率为零时它也是零,看不出是没捞到还是排得差。
- 结论:三个一起看才能定位故障层。召回率掉说明检索或切块出了问题,要动召回策略;召回率不动而倒数排名掉,说明排序退化,该上重排;两者都稳而 nDCG 掉,说明前几名里混进了更多噪声。
- 可预期的追问是「指标顶格了怎么办」。真实答案是把题目做难:指标撞天花板说明评估集失去区分度,这时候继续优化系统是在瞎调。
Key points
- Recall answers 'did it make it into the context', sensitive to total misses, blind to rank shifts.
- MRR answers 'how high is the first hit', sensitive to ranking degradation, blind to how many hits there are.
- nDCG answers 'how good is the top k overall', the direct target for reranking, blind to existence.
- Only the combination localizes the failure to retrieval, ranking, or noise.
- State the hit criterion: context is packed against a token budget, not a fixed top-k.
答题要点
- 召回率管「有没有进上下文」,对完全没捞到最敏感,对名次变化不敏感。
- 平均倒数排名管「第一条排第几」,对排序退化最敏感,但分不清命中一条还是五条。
- 归一化折损累计增益管「前 k 名整体质量」,是重排的直接优化目标,但看不出有没有。
- 三者组合才能定位故障在召回层、排序层还是噪声层。
- 命中口径要说清:按 token 预算装上下文,不是按固定条数取前 k。
What systematic biases does an LLM judge have when scoring RAG faithfulness, and how do you detect them and prove your judge is trustworthy?用模型当裁判来评 RAG 的忠实度,有哪些系统性偏差?你怎么发现它们、又怎么证明你的裁判可信?
Common in ChinaCommon overseasDeep dive#llm-as-judge#evaluation#faithfulnessHow to reason about it · think before answering
- The second half of the question is the discriminator. Plenty of people can name position, length, and self-preference bias; few can say how they prove the judge is trustworthy.
- Pair each bias with its mitigation: position bias - score pointwise instead of pairwise, and if you must compare, swap the order and call disagreement a tie; length bias - decompose into claims and score a ratio, so a longer answer grows its own denominator; self-preference - judge with a different vendor or tier than the generator.
- Add two prompt-level requirements: fixed rubric anchors (spell out what 1.0, 0.6 and 0.3 mean, or the same input scores differently on different days) and forced structured output that quotes the unsupported sentences verbatim, which is what makes human review possible.
- Proving trust has exactly one route: human spot-checks and an agreement rate. Stratify ten to thirty items across types, hits and misses, high and low judge scores; answer one binary question only - is anything here not in the material - and compare. Below 0.8 the judge's scores cannot gate a merge.
- A detail that scores points: a very high agreement rate may mean your spot-check was too easy. If all ten sampled answers copy the material verbatim, agreeing is trivial and 100% says nothing about the judge.
- Expected follow-up: can the judge itself break? Add probes - fixed inputs with known verdicts, one faithful and one obviously fabricated, checked on every run. An evaluation system fails silently: the numbers keep coming, they just stop meaning anything.
分析过程 · 先想清楚再作答
- 这题的题眼在后半句。能背出「位置偏好、长度偏好、自我偏好」三个名词的人很多,能说出「怎么证明可信」的很少——面试官要的是后者。
- 先把三个偏差和各自的缓解手段一一对应:位置偏好用逐条独立打分代替两两比较,非要比较就交换顺序跑两遍、结论不一致判平局;长度偏好用逐句判定加比例计分,写得越长分母越大,长度红利自动消失;自我偏好用跨供应商或跨档位的模型评判,生成和评判不同源。
- 再补两条提示词层面的:给死评分锚点,1.0 / 0.6 / 0.3 各自是什么必须写明,否则同一份输入不同天给的分都不一样;强制结构化输出并要求把没支撑的句子原样列出,这是人工复核的抓手。
- 证明可信只有一条路:人工抽检算一致率。分层抽十到三十条——各类型都要有、命中和没命中都要有、裁判给高分和低分都要有,只判一个二元问题(有没有材料外的内容),跟裁判的结论比对。低于 0.8 就不能拿它的分数做拦合并这类决策。
- 一个能加分的细节:一致率很高不一定是好消息。如果抽的十条都是「答案原样抄自材料」的简单题,判对是理所当然的,这时候 100% 说明的是抽检没难度,不是裁判可靠。
- 可预期的追问是「裁判本身会不会坏」。答案是给裁判写探针:喂几组已知正确答案的输入(照抄材料的、明显编造的),每次跑评估都验一遍——评估系统坏掉的方式最阴险,分数照常输出,只是不再有意义。
Key points
- Three biases: position, verbosity, and self-preference, each with a matching mitigation.
- Score pointwise rather than pairwise; decompose into claims and score a ratio to kill the length premium; never let the generator judge itself.
- Pin rubric anchors in the prompt and force structured output that quotes unsupported sentences.
- Establish trust through stratified human spot-checks and an agreement rate; below 0.8 the judge cannot gate merges.
- Add probes with known verdicts so a broken judge is caught on every run.
答题要点
- 三个偏差:位置偏好、偏爱长答案、自己评自己,各自有对应的缓解手段。
- 逐条独立打分代替两两比较;逐句判定按比例计分抵消长度红利;生成与评判不同源。
- 提示词要给死评分锚点,并强制结构化输出、列出没支撑的句子。
- 可信度靠人工分层抽检算一致率,低于 0.8 不能用它做拦合并的决策。
- 给裁判本身写探针,每次跑评估都验一遍它有没有坏。
Why must a RAG evaluation set include questions the corpus cannot answer, and what does leaving them out hide?RAG 的评估集里为什么一定要放语料里没有答案的问题?不放会掩盖什么?
Common in ChinaCommon overseasBasic#evaluation#abstention#golden-setHow to reason about it · think before answering
- It looks easy but really asks whether you have considered that the eval set itself can lie. 'To test the refusal path' is a pass; 'without them the worst failure is invisible in the report' is a full mark.
- The derivation is one step: a system that always answers scores well on a set of answerable questions only. It stuffs context in, the model writes something, and the set has no column for 'should have refused'. The most dangerous failure simply does not appear.
- Conclusion: unanswerable questions are the only thing that makes fabrication visible. They are excluded from recall and scored on abstention instead - did retrieval gate out every weak candidate, and did generation actually say the material does not cover this.
- One authoring detail worth stating: unanswerable questions need strong distractor terms. Ask which browsers the web client supports when the corpus only says 'attach your browser and version when filing a ticket'. Without distractors retrieval returns nothing and you are testing your tokenizer, not your system.
- Expected follow-up: what if the abstention rate is low? Check two layers - whether the retrieval score gate is effectively a no-op, and whether the generation prompt carries an explicit refusal instruction. You need both; a prompt alone is not a reliable gate.
分析过程 · 先想清楚再作答
- 这题看着简单,实际是在问「你有没有想过评估集本身也会说谎」。答成「为了测试拒答功能」只算及格,答出「不放会让某个故障在报表上完全不可见」才是满分。
- 推导只有一步:一个只会硬答的系统,在只有可答问题的评估集上能拿到很高的分——它每次都塞材料给模型,模型每次都编一段话,而评估集根本没有「应该拒答」这一栏。于是最危险的故障在报表上是不存在的。
- 结论:无答案问题是唯一能让「乱编」显形的东西。它不参与召回率,它的指标是拒答率——检索侧有没有把不够格的候选全挡下来,生成侧有没有真的说出「资料里没有」。
- 出题上有个必须说的细节:无答案问题必须留强干扰词,比如问「网页端支持哪些浏览器」而语料里恰好有一句「提交工单请附上浏览器与版本」。没有干扰词的无答案题检索器一条都捞不到,你测出来的是分词器不是系统。
- 可预期的追问是「拒答率低怎么办」。分两层查:先看检索侧的门槛是不是形同虚设(分数阈值定得太低,不相干的块也过关),再看生成侧的提示词有没有明确的拒答指令,两层都要有,只靠提示词兜是不牢的。
Key points
- An all-answerable eval set makes 'answers confidently when it should not' completely invisible.
- Unanswerable items are scored on abstention, not recall, and you check both the retrieval gate and the generation refusal.
- Author them with strong distractor terms, or retrieval returns nothing and you are testing the tokenizer.
- Keep them at roughly 15% or more of the set, alongside multi-hop items, as the coverage floor.
- A low abstention rate splits into two causes: a no-op retrieval score gate, or a missing refusal instruction in the prompt.
答题要点
- 只有可答问题的评估集,会让「不知道也硬答」这个故障完全不可见。
- 无答案问题不算召回率,它的指标是拒答率,检索侧和生成侧各看一层。
- 出题必须留强干扰词,否则检索器一条都捞不到,测的是分词器。
- 建议无答案题占比不低于评估集的一成五,跟多跳题一起构成覆盖度底线。
- 拒答率低要分两层查:检索门槛是否形同虚设,生成提示词有没有拒答指令。
D9 Hybrid Search and Reranking: Two-Path Retrieval, Reciprocal Rank Fusion, Then Re-Ranking the Top Results With a Cross-Encoder
Why do hybrid retrieval systems usually use reciprocal rank fusion instead of normalizing both scores and adding them with weights? When does the weighted approach break down?混合检索为什么普遍用倒数排名融合,而不是把两路分数归一化之后加权相加?加权那条路在什么情况下会失控?
Common in ChinaCommon overseasIntermediate#hybrid-search#rank-fusionHow to reason about it · think before answering
- The hinge word is `scores`. Answering `RRF is simpler` is reciting a concept; the interviewer wants to hear that you know why the two scores are not comparable in the first place.
- Start with scale: BM25 is an unbounded sum of log terms, and on one index the top hit can range from 5 to 50 depending on the query; cosine is pinned between -1 and 1. Adding those two readings is meaningless.
- Then name the silent failure of normalization: dividing by the per-route maximum makes the denominator float with the query. For a question with no answer in the corpus, the vector route's best hit may score 0.09 and still normalize to a perfect 1.0, entering the fusion at full weight. You think you are comparing relevance; you are comparing `tallest among the short`.
- Then the maintenance cost of weights: a 1-to-0.6 ratio has to be tuned against an eval set, tuning two routes is a 2-D search, adding multi-query retrieval makes it 4-D or 5-D, and swapping the embedding model invalidates all of it. RRF has a single k, and the default of 60 rarely needs touching.
- Conclusion: rank is the only thing the two routes share. RRF throws the scores away on purpose so that an incomparable quantity cannot mislead it.
- Expected follow-up: what does k do? It flattens — the larger k is, the smaller the gap between the top few ranks, so `ranked well by both routes` outweighs `ranked first by one route`, which is exactly the cross-validation effect hybrid retrieval is after. A second follow-up on ties: you must fall back to sorting by document id, or ranks drift between runs and every eval number wobbles with them.
分析过程 · 先想清楚再作答
- 这题的题眼在「分数」两个字。只答「RRF 更简单」是背概念,面试官想听的是你知道分数为什么不可比。
- 先给量纲差异:BM25 是一堆对数项累加,没有上界,同一套索引里不同查询的第一名可以从 5 分到 50 分;余弦被钉死在负一到正一。两个读数相加没有意义。
- 再点出归一化的静默失败:除以本路最高分之后,分母随查询浮动。一个语料里根本没有答案的问题,向量那一路最高分只有 0.09,归一化之后照样是满分 1.0 带权重进融合——你以为在比相关性,其实在比「本路矮子里有多高」。
- 然后是权重的维护成本:1 比 0.6 这个配比要靠跑评估调出来,两路是二维搜索,加上多路查询就是四维五维,而且换一个 embedding 模型全部作废。RRF 只有一个 k,而且 60 这个默认值几乎不用动。
- 结论:名次是两路唯一可比的东西。RRF 主动扔掉分数,是为了不被不可比的量误导。
- 可预期的追问:那 k 是干什么的?答 k 是压平器——k 越大,头几名之间的差距越小,于是「两路都排进前列」比「一路排第一」更有分量,这正是混合检索想要的交叉验证效果。再追问同分怎么办,答必须按文档 id 兜底排序,否则跨次运行名次会飘、评估数字跟着抖。
Key points
- BM25 is unbounded, cosine is bounded; the two scales are not comparable, so adding them is meaningless.
- Per-route max normalization has a denominator that floats with the query, so the least relevant hit of an unanswerable query still normalizes to 1.0.
- Weights must be tuned against an eval set, the search is high-dimensional once you add routes, and swapping models invalidates it; RRF has a single constant k.
- RRF consumes only the ordered id list from each route, because rank is the one thing the routes share.
- Larger k rewards `ranked well by both routes`; ties must fall back to document id so results are reproducible.
答题要点
- BM25 无上界、余弦有界,两个量纲不可比,直接相加没有意义。
- 按本路最高分归一化的分母随查询浮动,无答案的查询里最不相干的结果也能拿到满分。
- 权重要跑评估调,路数一多就是高维搜索,换模型还得重来;RRF 只有一个常数 k。
- RRF 只吃每一路的有序 id 列表,名次是两路唯一可比的东西。
- k 越大越奖励「两路都排进前列」;同分必须按 id 兜底排序才可复现。
Why is a cross-encoder more accurate than a bi-encoder? And if it is more accurate, why not just use it to search the whole corpus directly?交叉编码器为什么比双编码器准?既然更准,为什么不干脆拿它直接检索全库?
Common in ChinaCommon overseasBasic#cross-encoder#bi-encoderHow to reason about it · think before answering
- This is a giveaway question, but the discriminating half is the second part. Saying `cross-encoders are slow` is not enough; you have to point at the structural reason.
- Start with the structure: a bi-encoder encodes query and document **separately** into vectors that never meet until a single dot product at the end; a cross-encoder concatenates query and document into one sequence, so every attention layer lets query tokens attend to document tokens.
- That yields the accuracy gap: a bi-encoder must compress a document into one fixed-length vector, and compression loses information — the binding between `Zhou Min` and `platform team lead` may not survive. A cross-encoder does not compress; it aligns them on the spot.
- The answer to the second half hides in the same structure: bi-encoder document vectors can be computed **offline** and indexed, so query time is just a vector search. A cross-encoder has nothing to precompute — N documents means N forward passes. Reranking a 100k-chunk corpus means pushing the entire corpus through a model on every question.
- So the engineering split is a division of labor: recall pulls a small batch out of the whole corpus (cheap, indexable), reranking fixes the order of that batch (expensive, accurate). The default is to rerank only the top 20 after fusion.
- Expected follow-up: is there a middle path? Yes — late interaction, where token-level document representations are precomputed and the interaction happens at query time. Accuracy and cost land between the two, at the price of a much larger index.
分析过程 · 先想清楚再作答
- 这是一道送分题,但送分题的区分度在第二问。只答「交叉编码器慢」是不够的,要说清慢在结构上的哪一处。
- 先给结构差异:双编码器把查询和文档**各自**编码成向量,两者从头到尾没有见过面,最后只靠一次内积凑到一起;交叉编码器把查询和文档拼成一段文本一起过模型,每一层注意力都能让查询的词去看文档的词。
- 由此推出准确率差异的来源:双编码器要把一篇文档压成一个固定长度的向量,压缩必然丢信息,「周敏是平台组组长」里两个词的绑定关系未必留得下来;交叉编码器不压缩,它当场对齐。
- 第二问的答案就藏在同一个结构里:双编码器的文档向量**可以离线算好**,查询时只做向量检索;交叉编码器没有任何东西能预先算好,N 篇文档就要跑 N 次前向。十万块的语料重排一遍,等于每次提问都把整个库过一遍模型。
- 所以工程上的定位是分工:召回负责在全库里捞出一小批(便宜、可索引),重排负责把这一小批的顺序改对(贵、准)。默认只重排融合后的前 20 条。
- 可预期的追问:有没有中间路线?答有——后期交互(late interaction)那一类,文档侧提前算好词级表示、查询侧当场做交互,精度和成本都在两者之间,代价是索引体积大得多。
Key points
- A bi-encoder encodes both sides separately and joins them with one dot product; a cross-encoder concatenates them so attention can align across the pair.
- The accuracy gap comes from compression: a bi-encoder squeezes a whole document into one vector and loses bindings; a cross-encoder does not compress.
- Bi-encoder document vectors can be computed offline and indexed; a cross-encoder has nothing to precompute.
- Reranking the full corpus means running every chunk through a model on every question, so cost scales linearly with corpus size.
- The standard split is recall plus rerank, with reranking applied only to the top few dozen after fusion.
答题要点
- 双编码器各自编码、最后一次内积;交叉编码器把查询和文档拼在一起过模型,注意力可以跨两者对齐。
- 准确率差异来自压缩:双编码器把整篇文档压成一个向量,绑定关系会丢;交叉编码器不压缩。
- 双编码器的文档向量能离线算好并建索引,交叉编码器没有任何东西可以预先算好。
- 全库重排等于每次提问把整个语料过一遍模型,成本随语料规模线性增长。
- 标准分工是召回加重排,重排只作用于融合后的前几十条。
You replaced pure vector retrieval with hybrid search plus reranking, and after shipping it your eval metrics went down. How do you investigate?你把纯向量检索换成了混合检索加重排,上线之后评估指标反而掉了。你会怎么排查?
Common in ChinaCommon overseasDeep dive#hybrid-search#evaluationHow to reason about it · think before answering
- This question tests whether you have actually done stage-by-stage attribution. Answering `I would tune the weights and see` loses — that is guessing, not investigating.
- Step one is to run the stages apart, not to change code: pure keyword, pure vector, hybrid, and hybrid plus rerank, all on the **same eval set with the same context budget**. Whichever stage the drop appears in is where you look, and this alone separates `fusion is broken` from `reranking is broken`.
- Step two asks a specific question: did recall drop, or did the ranking metrics drop? A recall drop means the answer never entered the context at all — a candidate-pool or budget problem. Ranking metrics dropping while recall holds means the answer is still there but pushed down — a fusion-weight or rerank-model problem. The two failures have completely different fixes.
- A third common root cause is recall depth. This knob runs against intuition: going deeper is not safer, it lets noise vote too. On a 134-chunk corpus I measured that narrowing each route from 50 to 5 took hybrid recall from 87.5% back to 93.8% and multi-hop from 50% to 75%, while nDCG fell by almost 0.1. The metrics fight each other, so decide which one the product needs first.
- A fourth root cause is that the eval protocol quietly changed. Touch the context budget, the hit rule, or the candidate depth, and the old and new numbers stop being comparable — in which case the `drop` may not be a drop at all.
- Expected follow-up: how do you avoid this next time? Make the four-way comparison a single command, store the previous report as a baseline, and fail the build with a non-zero exit code on regression. That is precisely why evaluation comes before optimization.
分析过程 · 先想清楚再作答
- 这题考的是你有没有真的做过分阶段归因。答「调一下权重再看看」就输了——那是在猜,不是在查。
- 第一步是拆档跑,不是改代码:纯关键词、纯向量、混合、混合加重排四档在**同一份评估集、同一个上下文预算**下各跑一遍。指标掉在哪一档就在哪一档找原因,这一步能立刻区分「融合坏了」和「重排坏了」。
- 第二步问一个具体问题:掉的是召回率还是排序指标?召回率掉说明答案根本没进上下文,是候选池或者预算的问题;排序指标掉而召回率没动,说明答案还在、只是被挤到了后面,那是融合权重或重排模型的问题。这两类故障的解法完全不同。
- 第三个常见根因是召回深度。每路取多少条这个旋钮方向反直觉:取深了不是更保险,是把噪声也一起投了票。我在一份 134 块的语料上实测过,每路从取 50 收到取 5,混合那一档的召回率从 87.5% 回到 93.8%、多跳档从 50% 回到 75%,而 nDCG 反而掉了近 0.1——两个指标会打架,先想清楚业务要哪个。
- 第四个根因是评估口径被悄悄改了。上下文预算、命中判定、候选池深度只要动过一个,新旧数字就不可比,这时候「掉了」可能根本不是真的掉了。
- 可预期的追问:怎么防止下次再踩?答把四档对照做成一条命令、把上一版报告存成基线、指标退步就以非 0 退出码拦住合并——这就是评估要先于优化的原因。
Key points
- Run all four configurations separately for attribution, on one eval set with one context budget, before touching any parameter.
- Separate a recall drop from a ranking drop: the first is a candidate-pool or budget issue, the second is a fusion or rerank issue.
- Check recall depth: taking too many per route lets noise vote, and narrowing it can bring recall back.
- Confirm the eval protocol did not change; touching budget, hit rule, or candidate depth makes old and new numbers incomparable.
- Freeze the four-way comparison into one command plus a baseline report, and block merges on regression.
答题要点
- 先拆档跑四种配置,在同一份评估集和同一个上下文预算下归因,不要一上来就调参。
- 区分召回率掉与排序指标掉:前者是候选池或预算问题,后者是融合或重排问题。
- 查召回深度:每路取太深会把噪声也投进融合,收窄反而可能救回召回率。
- 确认评估口径没被改:预算、命中判定、候选池深度动过一个,新旧数字就不可比。
- 把四档对照固化成一条命令加一份基线报告,指标退步直接拦住合并。
Adding a reranker costs you 200 ms of extra latency per question plus a per-search fee. How do you decide whether that spend is worth it?加上重排之后每次提问多了两百毫秒延迟,还多了一笔按次计费的开销。你怎么判断这笔钱该不该付?
Common in ChinaCommon overseasDeep dive#rerank#cost-tradeoffHow to reason about it · think before answering
- This question tests whether you can translate a technical choice into a business judgment. Answering `check whether the metrics went up` covers only a third of it.
- Split it into three ledgers: how much the metrics moved, how much latency grew, and how much money it costs. All three must be reported together; a proposal with only the first will not survive review.
- For the first ledger, be specific about **which** metric reranking improves. Reranking changes the order, not the candidate set — it cannot fix `the answer was never retrieved`. If your recall is the bottleneck, add a retrieval route or adjust recall depth first; the 200 ms buys nothing.
- For the second, ask where those 200 ms land. They sit synchronously between retrieval and generation, with the user waiting; but if a streaming generation follows and time-to-first-token is already a second or two, the relative cost is small. In an as-you-type search box, 200 ms is fatal.
- For the third, note the billing unit: rerankers usually charge per search rather than per token, so sending a few more candidates barely changes the bill — what is expensive is the number of questions. That points optimization at reducing query volume (caching, intent routing) rather than at trimming the candidate list.
- Expected follow-up: what if you simply cannot afford it? Three paths — rerank only queries classified as hard (intent routing), cache results, or self-host an open-weights cross-encoder to convert per-call fees into fixed compute cost.
分析过程 · 先想清楚再作答
- 这题考的是你会不会把技术选择翻译成业务判断。只答「看指标涨没涨」只答了三分之一。
- 先把账拆成三笔:指标涨了多少、延迟涨了多少、钱涨了多少。三笔必须一起报,只报第一笔的方案在评审会上过不去。
- 第一笔要问清楚重排改善的是**哪个**指标。重排改的是顺序,不是候选集合——它救不了「答案压根没被召回」这种故障。如果你的召回率本来就不够,先去加召回路数或者调召回深度,重排这两百毫秒是白花的。
- 第二笔要看这两百毫秒落在哪。它是同步卡在检索之后、生成之前的,用户全程在等;但如果后面接的是一个流式生成、首字节本来就要一两秒,这两百毫秒的相对占比就小得多。反过来,如果这是一个自动补全式的即时搜索框,两百毫秒就是致命的。
- 第三笔要注意计价单位:重排普遍按检索次数计价而不是按 token,所以「多送几条给它排」几乎不涨钱,真正贵的是提问次数本身。这直接决定了优化方向是压提问量(缓存、意图路由)而不是压候选数。
- 可预期的追问:如果就是付不起怎么办?答三条路——只对判定为复杂的查询走重排(意图路由)、把结果缓存起来、或者换成自部署的开源交叉编码器把按次付费变成固定的算力成本。
Key points
- Report all three ledgers together: metric gain, latency growth, cost growth; a proposal missing one is incomplete.
- Confirm whether the bottleneck is ordering or recall first; reranking only reorders and cannot rescue an answer that was never retrieved.
- Judge the latency by where it lands: it is small relative to a streaming generation, but fatal in an as-you-type search box.
- Rerankers bill per search rather than per token, so cost scales with question volume, not candidate count.
- If it is unaffordable: route only hard queries to the reranker, cache results, or self-host an open-weights cross-encoder.
答题要点
- 三笔账一起报:指标增量、延迟增量、成本增量,缺一笔方案就不完整。
- 先确认瓶颈是排序还是召回:重排只改顺序,救不了没被召回的答案。
- 延迟要看落在哪:流式生成场景下相对占比小,即时搜索框里两百毫秒就是致命的。
- 重排按检索次数计价而不是按 token,涨钱的是提问量而不是候选条数。
- 付不起时的三条路:意图路由只对难查询重排、结果缓存、换自部署的开源交叉编码器。
D10 Query-Side Optimization: Rewriting, Hypothetical Document Embeddings, Multi-Query, Step-Back Prompting, and Intent Routing
Why does HyDE (hypothetical document embeddings) work, and when does it steer retrieval in the wrong direction?假设文档嵌入(HyDE)为什么有效?它在什么情况下会把检索带偏?
Common in ChinaCommon overseasIntermediate#hyde#query-transformation#retrieval-qualityHow to reason about it · think before answering
- The tell is in the second half. Anyone can recite why HyDE works; only someone who has run it on real data can say when it hurts.
- Give the mechanism first: dense retrieval compares semantic similarity, but a user's question and a policy paragraph differ in register, syntax and vocabulary. HyDE has the model draft a fake passage that looks like the target document, then retrieves with that vector — effectively moving the query into the documents' register.
- Then kill the common misreading: the factual accuracy of the draft does not matter, because it is never shown to the user. It only contributes a direction in embedding space.
- Two failure modes. The model invents an over-specific field or process name that does not exist in the corpus, and the vector chases something imaginary. Or the corpus genuinely has no answer, and the fabricated passage finds plausible-looking neighbors anyway — abstention rate drops and hallucination rate climbs.
- Pair the risk with a mitigation: gate admission on each retriever's raw score, never on the fused score (fused scores are relative, so even the worst batch tops out at 1.0); and treat the hypothetical document as a second query fused with the original rather than a replacement, so a bad draft can only dilute the signal, not erase it.
- Expect the follow-up on cost. The draft runs to a hundred-plus output tokens, an order of magnitude more than a rewrite, and it doubles retrieval calls. That is why it belongs in an A/B queue, not in the default config.
分析过程 · 先想清楚再作答
- 这题的题眼在后半句。前半句网上到处都能抄到,能不能说清「什么时候不该用」才是区分度所在——只答前半句的人,多半没在真实语料上跑过。
- 先给机制:向量检索比的是语义相似度,而用户的疑问句和文档里的制度条文在文体、句式、用词上都不同类。HyDE 先让模型编一段「长得像目标文档」的假文本,用它的向量去找邻居,等于把查询搬进了文档所在的那个语域。
- 紧接着点破一个常见误解:这段假文本的**事实对不对根本不重要**,因为它不给用户看,只贡献一个向量方向。理解到这一层,才算真懂它为什么不怕模型瞎编。
- 带偏有两种典型情况。一是模型编得太具体,给出语料里根本不存在的字段名或流程名,向量朝着一个不存在的方向去了;二是语料里压根没有答案,本该拒答的问题被编出来的假文档匹配到几个「看起来挺像」的邻居,拒答率掉下去、瞎编率涨上来。
- 说完风险要给对策,这一步最见工程经验:门槛卡在**每一路检索器的原始分**上而不是融合分上(融合分是相对的,最不相干的一批也能拿最高分);以及把假设文档当成**第二个检索式与原问题融合**,而不是直接替换原问题——替换在模型编歪时会把原问题的信号一起丢掉。
- 可预期的追问是「它多花多少钱」。答:假设文档要写上百字,输出 token 是查询改写的十几倍,是查询侧四种手法里最贵的一次调用,而且检索次数翻倍。所以它通常不该默认打开,应该进 A/B 队列。
Key points
- It works by register alignment: a question and a policy paragraph sit in different neighbourhoods, and the fake passage moves the query into the document's.
- The draft's factual accuracy is irrelevant — it only supplies a direction and is never shown to the user.
- It misfires when the model invents over-specific details, or when the corpus has no answer and the fabrication finds plausible neighbors anyway.
- Two guardrails: gate on raw per-route scores, not fused ones; fuse the hypothetical document with the original query instead of replacing it.
- It is the most expensive query-side technique (long output plus doubled retrievals), so keep it off by default and A/B it.
答题要点
- 有效的原因是语域对齐:疑问句和制度条文本来不在一个语义邻域,假设文档把查询搬到了文档那一侧。
- 假文本的事实对错不重要,它只贡献一个向量方向,不展示给用户。
- 带偏的两种情况:编得太具体,追一个语料里不存在的方向;本该拒答的问题被假文档匹配上,拒答率下降。
- 两条护栏:门槛卡原始分不卡融合分;把假设文档当第二个检索式融合,而不是替换原问题。
- 成本上它是查询侧最贵的一项(长输出加检索次数翻倍),默认关闭、按场景 A/B。
How do you handle coreference in multi-turn RAG, and what is the classic failure when you skip it?多轮对话里怎么处理指代?不做指代消解最典型的翻车场景是什么?
Common in ChinaCommon overseasBasic#coreference#multi-turn#query-rewritingHow to reason about it · think before answering
- This is a warm-up question, but there is still a gap between answers. Saying "just concatenate the history into the query" invites a follow-up about growing histories that most candidates cannot handle.
- State the mechanism: insert a short rewrite call before retrieval that takes the last few turns plus the current question and returns one retrieval-ready line. Set temperature to 0 so the same input always yields the same query, and forbid the model from answering the question in the prompt.
- Explain why concatenation is worse: history grows without bound, filler words dilute inverse document frequency, and the previous answer leaks in — you end up retrieving an answer with an answer. The rewriter emits one sentence, not a transcript.
- Make the failure concrete. Turn one: "who must sign off on this operation?" Answer: "the platform team lead." Turn two: "what is that person's name?" Retrieved unresolved, not a single candidate clears the admission gate and the system refuses — even though the corpus contains the answer. The failure is not a wrong answer, it is a false "not found" right after the user's own question.
- Add the ordering trap: rewrite before intent routing. A pronoun is a classic multi-hop signal, so an unresolved query gets routed to the expensive path for nothing; after rewriting it is an ordinary single-hop question. Multi-query and step-back must also sit downstream of the rewrite, or one unresolved pronoun becomes three.
- Expect "how do you decide when to rewrite?" Trigger on short queries, pronouns and elliptical follow-ups; skip on a clearly new topic. The check is nearly free and removes most of the calls.
分析过程 · 先想清楚再作答
- 这是一道送分题,但送分题也有高下之分:只说「把历史拼进查询里」的答案,会被追问一句「历史越拼越长怎么办」就卡住。
- 先把做法说清:在检索之前加一次很短的改写调用,输入是最近几轮对话加本轮问题,输出是一行可以直接检索的检索式;温度设 0 保证同一句话每次改成同一个结果,并在提示词里明确禁止模型顺手回答问题。
- 为什么不是「把历史整个拼进查询」:历史越拼越长,噪声词把逆文档频率摊薄,检索反而更差;而且历史里包含上一轮的答案,等于拿答案去检索答案。改写的产出是一句话,不是一段历史。
- 最典型的翻车场景要举实例:上一轮问「这个操作必须由谁审批」,答「必须由某某组组长审批」;这一轮问「这个人叫什么名字」。不消解直接检索这七个字,实测是**一条候选都过不了门槛,系统只能拒答**。注意失败方式不是答错,是「明明语料里有答案却说找不到」,用户体验是崩塌式的。
- 补一条顺序上的坑:改写必须在意图路由**之前**。「这个人」是典型的多跳信号词,路由看到它会判成多跳、白跑一轮;改写之后它只是个普通单跳问题。同理,多路查询、后退提问也都要建立在改写后的那句话上,否则错误被放大好几倍。
- 可预期的追问是「怎么知道要不要改写」。答:短问题、含指代词、含省略(「那审计日志呢」)时才触发,纯新话题跳过——这一步很便宜,但能省掉一大半调用。
Key points
- Add a short rewrite call before retrieval: last few turns plus current question in, one retrieval line out, temperature 0, answering explicitly forbidden.
- Do not splice the whole history into the query — it grows unbounded, dilutes IDF, and leaks the previous answer into the search.
- Classic failure: an unresolved pronoun means no candidate clears the gate, so the system refuses a question the corpus can answer.
- That false "not found" hurts more than a wrong answer, since the user just asked about the same thing.
- Order matters: rewrite first, then route; multi-query and step-back both build on the rewritten query.
答题要点
- 在检索前加一次短改写调用,输入最近几轮加本轮问题,输出一行检索式,温度 0,禁止模型回答问题。
- 不要把历史整段拼进查询:越拼越长、噪声稀释逆文档频率,还会拿上一轮的答案去检索。
- 典型翻车:上一轮的「这个人 / 他 / 那个」不消解,检索一条都过不了门槛,系统在有答案的情况下拒答。
- 失败方式是「假的查不到」,比答错更伤体验,因为用户刚刚才问过同一件事。
- 顺序:先改写、再路由,多路查询与后退提问都建立在改写后的查询上。
Query rewriting adds a model call per question and doubles end-to-end latency. How do you decide whether it is worth paying?上线查询改写之后每问多了一次模型调用,端到端延迟涨了一倍,你怎么判断这笔开销值不值?
Common in ChinaCommon overseasDeep dive#cost-tradeoff#latency#query-rewritingHow to reason about it · think before answering
- This question is about turning an engineering judgment into numbers. "Rewriting obviously helps quality" is a fail — the candidate never measured the gain.
- Start with one question that nearly settles it: which slice of traffic does the gain land on? Query rewriting buys almost nothing on single-turn questions (we measured identical metrics with it on and off across 20 single-turn items); the entire payoff is in follow-up turns. So step one is to pull the share of multi-turn sessions from production logs.
- Step two is to lay out all three ledgers, because one alone cannot support a decision: how much the metrics moved on a fixed golden set, how much latency grew (a rewrite is a short-output task, so a cheap fast model often costs a few hundred milliseconds rather than doubling anything), and how many extra calls were added — one model call for rewriting versus one call plus several retrievals for multi-query is a completely different cost shape.
- Step three is to price the cheaper variants before deciding: rewrite only when a trigger fires (short query, pronoun, ellipsis), cache rewrites per session, and run a small model instead of the main one. These usually remove most of the cost while keeping the gain.
- Land on a usable rule: gain times affected traffic share, divided by added latency and cost, ranked against your other candidate optimizations. Rewriting usually ranks high because its failure mode is a false "not found" immediately after the user's own question — an abandonment-grade experience bug, not a few metric points.
- Expect "what if the latency genuinely is unacceptable?" Fire the rewrite and the first retrieval in parallel: search with the raw query immediately, search again when the rewrite returns, and fuse both rankings. You pay a max instead of a sum, at the cost of one extra retrieval.
分析过程 · 先想清楚再作答
- 这题考的是「能不能把工程判断落到数字上」。凡是回答「改写当然要做,能提升效果」的,一律判为没做过——他连收益是多少都没量。
- 先问自己一句:**收益出现在哪一类流量上**。这一条几乎决定了答案。查询改写在单轮问答上的收益接近零(我们在 20 道单轮题上实测开关它指标一模一样),收益全在多轮追问。所以第一步是去线上日志里查多轮会话占比,占比很低的话这笔钱不该花在全量流量上。
- 第二步是把三笔账摆齐,缺一笔就不能下判断:指标涨了多少(用固定的标准答案集跑,不要用感觉)、延迟涨了多少(改写是短输出任务,可以换便宜快的那一档模型,往往只多两三百毫秒而不是翻倍)、多了几次调用(改写是一次,多路查询是一次调用加几次检索,成本结构完全不同,别混着算)。
- 第三步是找**便宜的替代路径**再比一次:只在命中触发条件时才改写(短问题、含指代词、含省略),纯新话题直接跳过;改写结果按会话缓存;用小模型跑改写而不是主模型。这三招通常能把这笔开销压掉一大半,而收益几乎不掉。
- 结论要落成一条可执行的判据:**收益乘以受影响流量占比,除以增加的延迟与成本**,跟你手上其他候选优化排个序。改写通常能排到很前面,因为它的失败方式是「用户明明追问同一件事却被告知查不到」,那是会直接导致弃用的体验故障,不只是指标掉几个点。
- 可预期的追问是「延迟真的不能接受怎么办」。答:把改写和第一次检索**并行发**,用原查询先检索一路,改写回来后再补一路,两路用倒数排名融合合起来——延迟只多一个 max 而不是一个加法,代价是多一次检索。
Key points
- Locate the gain first: rewriting is near-zero on single-turn traffic and pays off on follow-ups, so start from the share of multi-turn sessions.
- All three ledgers are mandatory: metric delta on a golden set, added latency, added calls and token cost.
- Try the cheap variants before deciding: conditional triggering, per-session caching, and a small model for the rewrite.
- Decide on gain times affected traffic share over added latency and cost, then rank it against your other optimizations.
- If latency is a hard constraint, fire the rewrite in parallel with the first retrieval and fuse both rankings, turning a sum into a max.
答题要点
- 先定位收益落在哪一类流量:改写在单轮上接近零收益,价值全在多轮追问,先查多轮会话占比。
- 三笔账缺一不可:标准答案集上的指标变化、增加的延迟、增加的调用次数与 token 成本。
- 先试便宜的替代路径:条件触发、按会话缓存、用小模型跑改写,通常能压掉大半开销。
- 判据是「收益 × 受影响流量占比 ÷ 增加的延迟与成本」,再和其他候选优化排序。
- 延迟真的卡死时,把改写与首次检索并行发,两路名次用倒数排名融合,延迟从加法变成取最大值。
What happens when intent routing misclassifies, and how would you design the fallback?意图路由判错了会怎样?你会怎么设计兜底?
Common in ChinaCommon overseasIntermediate#intent-routing#fallback#observabilityHow to reason about it · think before answering
- This tests whether you have thought about the direction of the error. A router is a classifier and classifiers misfire; "add more training data" is not a fallback design.
- Break the errors down by direction — that is the backbone of the answer. Across three routes (direct answer, single-hop, multi-hop) the six confusions carry wildly asymmetric costs. Routing a retrieval-worthy question to a direct answer leaves the model with no material at all, so it fabricates: the most expensive error. Routing chit-chat to single-hop merely wastes one retrieval. Routing multi-hop to single-hop just yields an incomplete answer.
- The conclusion follows: bias the fallback toward spending a little more, and default to single-hop retrieval whenever the classifier is unsure. Single-hop is the cheapest error to make, and it is recoverable — with partial material the model can still say it only found half the answer; with no material it can only invent one.
- Add a runtime fallback, which beats better up-front classification: after a direct-answer routing, if the draft reply contains figures, amounts or dates that need a source, fall back to retrieval and answer again; after a single-hop routing, if no candidate clears the admission gate, escalate to multi-hop or abstain. Correcting the earlier decision with the later observation is the single most useful pattern in routing systems.
- Mention observability: log every routing decision with the raw question, the label, and whether a fallback fired. Without that log you know neither how accurate the router is nor what to train the next version on.
- Expect "when should you skip routing entirely?" When chit-chat is a small share of traffic and multi-hop questions are rare, the classification call costs more than it saves. In our 30-document lab the real gain from routing was not saved retrievals but the ability to give recognized multi-hop questions a larger context budget.
分析过程 · 先想清楚再作答
- 这题在考「有没有想过错误的方向」。路由是分类器,分类器一定会错;只答「多加训练数据提高准确率」的,等于没回答兜底怎么设计。
- 先把错误按方向拆开,这一步是整题的骨架:三条路(直接回答、单跳检索、多跳检索)两两误判,代价完全不对称。把该检索的判成直接回答,模型手里一点材料都没有,只能编,这是最贵的一种错;把闲聊判成单跳,只是白花一次检索;把多跳判成单跳,只是少查一轮、答得不全。
- 结论顺势就出来了:**兜底方向要偏向「多花一点钱」,判不出来一律退回单跳检索。** 单跳是三条路里错得最轻的一条,而且它的错误是可恢复的——材料不全模型还能说「资料里只查到一半」,材料为空它就只能编。
- 再补一层运行时兜底,比事前分类更管用:分类成直接回答之后,如果模型的回答里出现了具体数字、金额、日期这类需要出处的内容,就回退去检索一次再答;分类成单跳之后,如果检索侧一条都没过门槛,就升级走多跳或直接拒答。**用后一步的观测结果纠正前一步的判断**,这是路由系统最实用的一条设计。
- 还要提一句可观测性:路由的每一次判定都要落日志,带上原始问题、判定结果、后续是否发生了兜底升级。没有这份日志,你既不知道路由准不准,也没法攒出下一版的训练集。
- 可预期的追问是「什么时候干脆别做路由」。答:流量里闲聊占比很低、且多跳问题很少时,路由省下的钱还不够付分类调用的钱,这时候直接全部走单跳更划算——我们在 30 篇语料的实验里就看到,路由真正的收益并不在省检索,而在于认出多跳之后给它更高的上下文预算。
Key points
- The three routes have asymmetric error costs: sending a retrieval-worthy question to a direct answer is the worst, while routing chit-chat to single-hop only wastes one retrieval.
- Bias the fallback toward spending more: default to single-hop whenever the classifier is unsure, since that error is the mildest and is recoverable.
- Add runtime fallbacks: re-retrieve if a direct answer contains figures that need a source; escalate or abstain if no single-hop candidate clears the gate.
- Log every routing decision — raw question, label, whether a fallback fired — for both monitoring and the next training set.
- When chit-chat and multi-hop are both rare, the classification call costs more than it saves; route everything to single-hop instead.
答题要点
- 三条路的误判代价不对称:把该检索的判成直接回答最贵(模型没材料只能编),把闲聊判成单跳只是白花一次检索。
- 兜底方向偏向多花钱:判不出来一律退回单跳检索,它是错得最轻且可恢复的一条路。
- 加运行时兜底:直接回答里出现需要出处的数字就补一次检索;单跳检索一条都没过门槛就升级或拒答。
- 每一次路由判定都落日志(原始问题、判定结果、是否触发兜底),既用于监控也用于攒下一版训练集。
- 闲聊与多跳占比都很低时,路由省的钱付不起分类调用,直接全走单跳更划算。
D11 Advanced Indexing: Parent-Child Documents, Summary Indexes, Contextual Retrieval, and the Trade-Offs of Tree Aggregation vs. Graph Retrieval
Parent-child indexing and contextual retrieval both patch the same problem — chunks losing their context. What actually distinguishes them?父子索引和上下文检索都在补『块被切碎』这个问题,它们的差别到底在哪?
Common in ChinaCommon overseasIntermediate#indexing#contextual-retrieval#chunkingHow to reason about it · think before answering
- The hinge is which half of the pipeline each one fixes. Answering 'one is a chunking trick, the other adds a prompt' just describes implementations; the interviewer wants to know where each acts.
- Split the pipeline in two and ask separately: what does the retriever see, and what does the generator see. Parent-child changes the generation side — retrieval still runs on small chunks, but a hit is swapped for its parent. Contextual retrieval changes the retrieval side — the header exists so the chunk can be found at all, and the generator does not need it.
- Conclusion: parent-child fixes 'found it but can't read it'; contextual retrieval fixes 'readable but never found'. Neither changes what the other changes, so they compose.
- That difference also dictates which metric can see each one. Contextual retrieval moves rank, so recall and nDCG catch it. Parent-child moves 'is the evidence sufficient to answer', which a binary recall metric cannot see. Our 20-question set is already saturated at 100% on single-document questions, so parent-child comes out level with the baseline — that is the ruler failing, not the technique.
- That difference yields a free optimization: since the header only serves retrieval, keep it out of the context window. Leaving it in pays rent on every single query. Flipping that one switch in our lab freed 36 tokens inside a 600-token budget with every metric unchanged.
- The costs differ too. Parent-child costs index entries and a bigger context unit. Contextual retrieval costs one model call per chunk up front plus a permanently larger index. One is space; the other is time and space.
- Expect the follow-up 'why not both'. Look at the failure logs first: are you mostly seeing incomplete evidence, or nothing retrieved at all? Without the matching failure mode, neither is worth its price.
分析过程 · 先想清楚再作答
- 这题的题眼是『补的是哪一半』。答成『一个是切块技巧、一个是加提示词』就是在描述实现,面试官想听的是它们各自作用在检索管道的哪一段。
- 拆的办法是把管道分成两段问:检索时看到什么、生成时看到什么。父子索引改的是**生成侧**——检索单位还是小块,只是命中之后把上下文单位换成大块;上下文检索改的是**检索侧**——块头拼进去是为了让这一块能被检索到,模型生成时并不需要它。
- 结论:父子索引解决『找到了但看不全』,上下文检索解决『看得全但找不到』。前者不改变谁被检索到,后者不改变模型看到多少。它们正交,可以叠加。
- 这个差别还决定了它们各自要用什么指标去量:上下文检索动的是名次,用召回率和 nDCG 量得到;父子索引动的是『材料够不够答』,召回率这种二值指标量不出来。我们那份 20 题评估集单文档档已经 100% 饱和,父子索引在表里跟基线持平——那不是它没用,是尺子量不了它。
- 顺着这条差异能推出一个立刻能用的优化:既然块头只服务检索,就不该进上下文。它进了上下文就是在每一次查询里白占预算,而且这笔钱是长期的。我们的实验里把这个开关一改,五列指标一个不变,600 token 的预算里多装进了 36 个 token。
- 代价也不同:父子索引的代价是索引条目变多、每次装进上下文的东西变大;上下文检索的代价是一次性要给每块调一次模型,加上索引 token 永久变大。前者是空间,后者是时间加空间。
- 可预期的追问是『那我全都上』。答案是先看失败案例:日志里是『材料不完整』多,还是『压根没检索到』多。没有对应的失败模式就不该上,这两个手法都不是免费的。
Key points
- Parent-child acts on the generation side: retrieve small, swap in the parent for context. It fixes 'found but unreadable'.
- Contextual retrieval acts on the retrieval side: the header makes the chunk findable. It fixes 'readable but never found'.
- They are orthogonal and compose; keep the header in the index only, never in the context window.
- Parent-child costs more index entries and a larger context unit; contextual retrieval costs one call per chunk plus a permanently larger index.
- Pick based on the observed failure: incomplete evidence points to the former, zero retrieval to the latter.
答题要点
- 父子索引作用在生成侧:检索单位是小块,上下文单位换成父块,解决『找到了但看不全』。
- 上下文检索作用在检索侧:块头让块能被检索到,解决『看得全但找不到』。
- 两者正交可叠加;块头只该进索引不该进上下文,否则每次查询都在为它付钱。
- 父子索引的代价是索引条目与上下文单位变大;上下文检索的代价是一次性建索引调用加永久变大的索引。
- 选哪个看失败案例:材料不完整选前者,压根没检索到选后者。
Contextual retrieval needs one model call per chunk. How do you estimate that one-off cost, and what levers bring it down?上下文检索要给每个块调一次模型,这笔一次性成本怎么估?有哪些办法能压下来?
Common in ChinaCommon overseasDeep dive#contextual-retrieval#prompt-caching#costHow to reason about it · think before answering
- This checks whether you have actually done the arithmetic. Saying 'prompt caching makes it cheap' without knowing which line item it touches is a tell.
- Split the bill first: one-off = per-chunk input + output + full re-embedding; per-query = the header read twice, once by the reranker and once in the context. Keep them separate, because they scale with completely different things.
- The dominant term on the one-off side is how many times the same document is re-read. A doc split into n chunks is read n times. Prompt caching attacks exactly that: put the whole document first and mark it cacheable, pay a cache write once, then cache reads for the remaining n-1, typically an order of magnitude cheaper than input.
- Order matters. Caching is prefix-matched, so the document must come first and the chunk after. Put the varying part first and the prefix changes every call — zero cache hits. This is the most common way people get it wrong.
- Our measurement: 30 docs, 134 chunks. Without caching, 103017 input tokens; with caching, 17340 written plus 60137 read, cutting the one-off cost by roughly 29%. The finer the chunks, the bigger the saving, because re-reads multiply.
- The counter-intuitive part is the useful part: the one-off cost amortizes below 10% of per-query cost after about 217 queries. The lasting bill is the extra tokens every query carries (we measured +12.3%). So the first lever is not cheaper index building — it is keeping the header out of the context, keeping it short, and not generating it for the whole corpus indiscriminately.
- A bonus point: before spending any of it, confirm your evaluation setup can actually detect the benefit. In our offline harness the vector route contributed exactly zero unique answer documents, so it cannot answer whether headers help embeddings at all — an A/B run there hands you a wrong conclusion that looks numerically supported.
分析过程 · 先想清楚再作答
- 这题考的是你有没有真的算过账。只会说『用提示词缓存就便宜了』属于听过没做过——面试官会追问缓存到底省在哪一项上。
- 先把成本拆开:一次性 = 每块的输入 + 输出 + 全量 embedding;每次查询 = 块头在重排和上下文里各被读一遍。**这两笔要分开记**,因为它们随业务量的增长方式完全不同。
- 一次性那笔的主项是『同一篇文档被重复读了多少遍』。一篇切成 n 块就要读 n 遍,这是成本的大头。提示词缓存省的正是这一项:把整篇放在提示词最前面并标记为可缓存,第一块付一次缓存写入,后面 n-1 块只付缓存读取,而读取价通常比输入价低一个数量级。
- 顺序不能反:缓存按前缀匹配,整篇必须在前、块内容在后。把变化的块放前面,前缀次次都变,缓存一次都不会命中——这是最常见的翻车点。
- 我们的实测:30 篇、134 块,不开缓存输入 103017 token,开缓存后拆成写入 17340 加读取 60137,一次性成本降约 29%。**块切得越碎这个比例越高**,因为重复读的次数更多。
- 结论反直觉但很实用:一次性那笔是小钱,摊到 217 次查询就降到每次查询成本的一成以下;真正的长期账是每次查询多出来的那几十个 token(我们量到 +12.3%)。所以压成本的第一优先级不是压建索引,而是让块头别进上下文、别过长、别对全库无差别地生成。
- 最后一条是加分项:花这笔钱之前先确认你的评估环境**测得出**收益。我们的离线环境里向量路对召回的独立贡献实测为 0,所以它根本没法回答『块头对向量侧有没有用』——在这种环境里做的 A/B 会给你一个看起来有数字支撑的错误结论。
Key points
- Split into one-off (per-chunk input/output plus re-embedding) and per-query (header read by both reranker and generator).
- The one-off is dominated by re-reading each document n times; caching turns that into one write plus n-1 reads.
- Caching is prefix-matched: the full document must come first, the chunk after, or you get zero hits.
- Measured on 30 docs / 134 chunks, caching cut the one-off cost by about 29%, and finer chunks save more.
- The lasting cost is per query: keep headers out of the context window, keep them short, and generate them selectively.
答题要点
- 把账拆成一次性(每块的输入输出 + 全量 embedding)和每次查询(块头在重排与上下文里各读一遍)两笔。
- 一次性的大头是同一篇被重复读 n 遍;提示词缓存把它压成一次写入加 n-1 次读取。
- 缓存按前缀匹配,整篇必须放在提示词最前面,块内容在后,顺序反了一次都不会命中。
- 实测 30 篇 134 块,一次性成本降约 29%,块越碎省得越多。
- 长期账在每次查询:块头别进上下文、控制长度、只对真正需要的文档生成。
What kind of question actually requires graph retrieval? Give one concrete case where it is justified and one where it is not.什么样的问题必须上图检索?给一个该上的具体例子和一个不该上的例子。
Common in ChinaCommon overseasDeep dive#graph-rag#multi-hop#costHow to reason about it · think before answering
- This one tests whether you reach for tools you don't need. If the answer is 'multi-hop questions need a graph', the interviewer knows you haven't shipped one — multi-hop is necessary, nowhere near sufficient.
- Anchor the criterion on something observable: does the second required document share any lexical or semantic overlap with the query? If it does, ordinary hybrid retrieval will surface it and the hop is illusory. If it shares nothing, only a relation edge gets you there — that is graph territory.
- Justified case: 'who must sign off on a production failover, and what is that person's name?' One doc says the platform lead must approve; another says who the platform lead is. The second shares not one term with the query. Across all five index structures we tested, it never once appeared in a 20-item candidate pool — rechunking, headers and parent backfill all failed.
- Unjustified case: 'which process covers a capacity change, and how many working days ahead must the ticket be filed?' Also two documents, but both overlap the query lexically; hybrid retrieval ranked them second each, and one pass collected both. Building a graph for this buys a solved problem at several times the cost.
- Then state the cost, which is what makes the answer sound operational: graph building is not one extraction call. Entities need disambiguation, relations need dedup, updates force recomputing affected subgraphs, and you now run a graph store and its update pipeline.
- Expect 'what else could you do instead'. Hand multi-hop to agentic retrieval: let the model retrieve the intermediate entity first, then issue a second query with it. Near-zero build cost, paid back in latency and call count per query. Try that before you build a graph.
分析过程 · 先想清楚再作答
- 这题在考你会不会为了用而用。只要答案里出现『多跳问题就要上图检索』,面试官基本就知道你没落地过——多跳只是必要条件,远不是充分条件。
- 判据要落在一个可观察的现象上:**答案的第二篇文档和查询之间,有没有字面或语义上的重合**。有重合,普通的混合检索就能捞到它,多跳是假的;完全没有重合,只能靠一条关系边走过去,这才是图检索的领地。
- 该上的例子:问『生产库主备切换必须谁书面审批、这个人叫什么』。一篇写着须平台组组长审批,另一篇写着平台组组长是某人。第二篇跟查询一个词都不重合,我们在五种索引结构下测了一遍,它在 20 条候选池里一次都没出现过——换切法、加块头、父子回填全都无效。
- 不该上的例子:问『扩容要走哪个流程、最晚提前几个工作日提单』。同样跨两篇文档,但两篇都跟查询有明显字面重合,混合检索把它们分别排在第 2 名,一次检索就凑齐了。为它建图是拿几倍成本买一个已经解决的问题。
- 然后说代价,这一段决定了你像不像做过:建图不止一次抽取调用,实体要消歧、关系要去重、文档更新时受影响的子图要重算,还要多维护一套图存储和一套更新链路。
- 可预期的追问是『不上图检索还有什么办法』。答案是把多跳交给 Agentic 检索:让模型先查出中间实体,再拿这个实体发起第二次检索。它的一次性成本几乎为零,代价换成了每次查询的延迟与调用次数——先试这条,试不通再考虑建图。
Key points
- The test is not 'is it multi-hop' but 'does the second document overlap the query at all' — only zero overlap earns a graph.
- Justified: the approver question, where an intermediate entity is the only bridge and the second doc never enters the candidate pool.
- Not justified: a multi-hop question whose documents both overlap the query — hybrid retrieval collects them in one pass.
- Real graph cost is entity disambiguation, relation dedup, incremental subgraph recomputation and a whole extra store — not a single extraction call.
- Try two-pass agentic retrieval first; build the graph only when that fails.
答题要点
- 判据不是『是不是多跳』,而是『第二篇文档跟查询有没有字面或语义重合』——没有重合才轮得到图检索。
- 该上:审批人那类问题,中间实体是唯一的桥,第二篇文档在候选池里一次都不出现。
- 不该上:两篇都跟查询有重合的多跳题,混合检索一次就能凑齐。
- 建图的真实成本是实体消歧、关系去重、增量重算和一套额外的图存储,不是一次抽取调用。
- 先试 Agentic 检索的两次查询,走不通再考虑建图。
You have built three different indexes over the same corpus. How do you decide which one a query goes to?同一份语料建了三套索引,检索时你怎么决定走哪一套?
Common in ChinaCommon overseasIntermediate#index-routing#evaluation#architectureHow to reason about it · think before answering
- Whether this is an easy point or a lost one depends on whether you first ask 'do we actually need three?'. Jumping straight to routing accepts an unverified premise.
- Step one is admitting the answer is usually 'none of them — use the default'. Across 30 documents we measured five index structures and every one landed at 93.8% recall, none beating the baseline. The only metric that moved was nDCG@10, which headers lifted from 0.6438 to 0.7218, while the two-stage summary index fell to 87.5%. Each structure patches one specific weakness; without that weakness it is pure overhead.
- Step two is routing, and the criterion is not 'which index is more accurate' — that is an offline evaluation question, not something you know at request time. What you do have at request time is the shape of the question: detail-seeking, summarizing, or entity-chaining. Those map onto the chunk index, the tree-summary index and the graph index.
- Implementation is a lightweight intent classifier — the same one from the previous day's intent routing, no need to invent another. Carry the decision as request metadata so you can replay it later.
- Spell out the fallback: on a misclassification, fall back to the default index rather than fanning out across all three and fusing. Fan-out looks safe but multiplies latency and cost by the number of indexes, and the extra routes usually never make it into the context budget anyway.
- Expect 'how do you know the classifier is right'. Log every routing decision and replay the golden set periodically: run each question through all three indexes and check whether the classifier picked the best-scoring one. It is a standing offline job that needs no human labeling.
分析过程 · 先想清楚再作答
- 这题是送分还是丢分,取决于你有没有先反问一句『真的需要三套吗』。上来就答路由策略的人,默认了一个没被验证的前提。
- 第一步是承认多数情况下答案是『都不走,走默认那套』。我们在 30 篇语料上把五种索引结构各测一遍,**召回率全部停在 93.8%,没有一种跑赢基线**;唯一动了的是 nDCG@10(块头把它从 0.6438 抬到 0.7218),而两段式的摘要索引还掉到了 87.5%。每种结构补的都是一个特定短板,你没有那个短板时它只带来成本。
- 第二步才是路由,而判据不是『哪套准』——那是离线评估该回答的问题,不是运行时能知道的。运行时能拿到的只有**问题的形状**:细节型(答案落在某一段)、概括型(要全库的一个概括)、多跳型(要跨实体串联)。按形状分流,正好对应块级索引、树状聚合索引、图索引。
- 实现上就是一个轻量意图分类器,跟前一天的意图路由是同一套东西,不必再造一个。分类结果作为元数据带进请求,方便事后拿评估集回看分错了多少。
- 兜底策略要说清楚:分类错了**回落到默认那一套**,不要并行全查一遍再融合。并行看着稳,实际上把延迟和成本按索引套数翻倍,而多出来的那两路大概率一条都进不了上下文预算。
- 可预期的追问是『怎么知道分类器分对了』。答案是把路由决策记进日志,定期拿标准答案集回放:对每个问题分别走三套索引,看分类器选的那套是不是指标最好的那套。这是一个能持续跑的离线作业,不需要人工标注。
Key points
- First challenge the premise: all five index structures landed at the same 93.8% recall in our measurement, so an index without a matching weakness is pure cost.
- At request time the usable signal is question shape — detail, summary, or entity-chaining — mapping to chunk, tree-summary and graph indexes.
- Reuse the previous day's intent router for classification and record the routing decision as request metadata.
- Fall back to the default index on misclassification instead of fanning out and fusing, which multiplies latency and cost.
- Replay the golden set periodically to check whether the classifier picks the best-scoring index.
答题要点
- 先反问是否真需要三套:实测五种索引结构召回率全部持平在 93.8%,没有对应短板就是纯成本。
- 运行时的判据是问题的形状——细节型、概括型、多跳型,分别对应块级、树状摘要、图索引。
- 复用前一天的意图路由做分类,把路由决策记进请求元数据。
- 分类错了回落到默认索引,不要并行全查再融合——延迟和成本按套数翻倍。
- 用标准答案集定期回放,检验分类器选的那套是不是指标最好的那套。
D12 Agentic RAG: Turning Retrieval Into a Tool So the Model Decides Whether to Search, How Many Times, and Whether to Start Over
You are exposing retrieval to a model as a tool. How do you write the tool description, and what concrete failure modes appear when you write it badly?把检索包成一个工具交给模型,这个工具的描述该怎么写?写不好会导致哪些具体的错误行为?
Common in ChinaCommon overseasBasic#tool-design#agentic-rag#promptingHow to reason about it · think before answering
- The discriminator is whether you can name concrete failure modes. Reciting 'the description should be clear' signals you have never shipped one.
- Give the structure first: a usable description answers four things - what is and is not in the corpus, when the tool must be called, when it must not be called, and what shape the query string should take.
- Attach a failure to each: no scope and the model treats it as a web search; no 'must call' and it answers policy questions from memory, convincingly; no 'must not call' and greetings or translations each burn a retrieval; no query shape and the model pastes the raw user sentence in, dragging interrogative words into the index.
- The query-shape line is the cheapest win: one sentence saying 'keyword phrase, no question words' beats ten heuristics for query cleaning on the retrieval side.
- Production angle: optional filter parameters such as department need an explicit 'only set this when you are certain'. Models like to fill optional fields, and a wrong filter hides the correct answer while the logs only show 'no results'.
- Expected follow-up: how do you verify the description works? Run a negative suite - small talk, translation, arithmetic, follow-ups already answered in the conversation - and assert the tool was not called. That regression is automatable.
分析过程 · 先想清楚再作答
- 这题的题眼是「具体的错误行为」。只会背「描述要写清楚工具的用途」的,一句话就暴露了没上过线——面试官想听的是描述里少一句话,线上就多一类工单。
- 先给结构:一段合格的工具描述要回答四件事——库里有什么和没有什么、什么时候必须用、什么时候不要用、查询串写成什么形状。四条各对应一类事故,逐条挂钩着说最有说服力。
- 逐条挂钩:不写范围,模型拿它当搜索引擎,问天气也去查;不写「必须用」,涉及公司制度的问题被模型凭记忆编答案,而且编得非常像真的;不写「不要用」,闲聊和翻译都触发一次无谓检索,成本和延迟白涨;不写查询形状,模型把用户整句问话塞进 query,「叫什么名字」这种疑问词进了检索,纯噪声。
- 最后一条最值钱也最容易漏:在描述里加一句「写成关键词短语,不要带疑问词」,比在检索侧做十种查询清洗都管用——问题在源头,就在源头修。
- 补一个生产视角:参数里的过滤字段(比如部门)要写明「只在确定时才填」。模型倾向于把可选参数填满,填错一个部门就把正确答案挡在库外,而这种错误在日志里看不出来,表现是「检索没结果」。
- 可预期的追问是「怎么验证描述写对了」。答案是拿一批负样本跑:闲聊、翻译、算术、以及答案已在对话里的追问,看模型有没有多调一次工具;这类回归是能自动化的。
Key points
- The description is a prompt for the model, not a code comment: scope, when to call, when not to call, query shape.
- Missing scope turns it into a web search; missing 'must call' produces confident answers from memory.
- Missing 'do not call' makes small talk trigger retrieval, paying cost and latency for nothing.
- Stating 'keyword phrase, no question words' fixes query pollution at the source.
- Optional filters need 'only set when certain' - a wrong filter silently hides the right answer.
- Regression-test with a negative suite and assert the tool was not invoked.
答题要点
- 描述是写给模型看的提示词,不是注释;四段式:范围、什么时候用、什么时候不用、查询写成什么形状。
- 不写范围会被当成搜索引擎;不写「必须用」会导致凭记忆编答案。
- 不写「不要用」会让闲聊也触发检索,成本和延迟白涨。
- 写明查询要用关键词短语、不带疑问词,比在检索侧清洗查询更根本。
- 可选过滤参数要写「只在确定时才填」,填错会静默地把正确答案挡在外面。
- 用一批负样本(闲聊、翻译、算术)做回归,断言工具没有被调用。
Self-reflective retrieval rewrites the query and retries. How do you guarantee it terminates instead of spinning on the same query forever?自反思式检索会反复改写查询重试。你怎么保证它一定会停下来,而不是在同一个查询上原地打转?
Common in ChinaCommon overseasIntermediate#agentic-rag#self-reflection#reliabilityHow to reason about it · think before answering
- This checks whether you have actually run such a loop. 'Set a max iteration count' is half an answer: it stops one failure mode and lets two others through.
- Split runaway behavior into three shapes and give each its own brake. Progress that never completes is capped by max rounds. Per-round budgets that pass individually but blow up in aggregate need a cumulative token budget - four rounds of 600 tokens each never trips a per-round check yet quadruples what reaches the model. Spinning in place needs duplicate-query detection.
- Two implementation details prove you have written it: the duplicate check belongs before the retrieval call, otherwise you pay for a call to learn you are looping; and queries must be normalized to a set of terms, or 'failover approval' and 'approval failover' count as two distinct queries and the loop keeps turning.
- Say what happens after it stops: stop reasons must be recorded as distinct categories - satisfied, gave up, hit round cap, hit token budget, duplicate query. Collapsing them into 'loop finished' hides how often the system simply surrendered.
- An easy miss: installing a brake is not testing it. If the default token budget sits far above real usage it never fires, which is the same as not having one. Every brake needs a case that trips it.
- Expected follow-up: what if the model says 'not enough' when it actually is? Make the assessment structured - which elements are covered, which are missing - and treat an empty missing list as sufficient, so the decision is auditable rather than a bare boolean.
分析过程 · 先想清楚再作答
- 这题在考「有没有真让循环跑过」。只答「设一个最大轮数」的能拿一半分,因为最大轮数只拦住了一类失控,剩下两类照样漏出去。
- 怎么拆:把失控分成三种形态,每种配一道闸。一是「每轮都在推进但永远推进不完」,用最大轮数拦;二是「每轮都不超标但累计爆掉」,用累计 token 预算拦——四轮各读 600 token 没有一轮超标,可送进模型的材料已经是单轮的四倍;三是「原地打转」,用重复查询检测拦。
- 重复查询检测有两个实现细节,答出来就说明真写过:一是要放在检索之前,否则要白花一次调用才发现自己在转圈;二是判重要对查询做归一化,只看词的集合,否则「主备切换 审批」和「审批 主备切换」会被当成两个不同的查询,圈照转不误。
- 还要说清停下来之后怎么办:停止原因必须分类记录,「查够了」「主动认输」「撞到轮数」「撞到预算」「原地打转」是五种不同的结局。把它们混成一个「循环结束」,你就永远看不见系统在多大比例的问题上其实是放弃了。
- 一个容易被忽略的点:闸门装了不等于验过。默认预算如果比实际用量高一大截,跑多少遍都踩不响它,等于没装。每一道闸都要构造一个用例把它踩响,这是验收的一部分。
- 可预期的追问是「模型自己说不够,但其实已经够了怎么办」。答案是自评要给结构化输出(覆盖了哪些要素、缺哪些),缺失项为空却仍判不够时按「够了」处理——让判断可审计,而不是信一个布尔值。
Key points
- Three brakes, none optional: max rounds, cumulative token budget, duplicate-query detection.
- The cumulative budget catches rounds that each pass but blow up together - the round cap cannot see that.
- Check for duplicates before retrieving, and normalize the query to a term set before comparing.
- Record stop reasons as distinct categories rather than one 'finished' bucket.
- Every brake needs a case that actually trips it; an untested brake is no brake.
- Have the assessor emit covered and missing elements so 'not enough' is auditable.
答题要点
- 三道闸缺一不可:最大轮数、累计 token 预算、重复查询检测。
- 累计预算拦的是「每轮都不超但加起来爆掉」,轮数闸看不见这件事。
- 重复查询检测要放在检索之前,且查询要归一化成词的集合再判重。
- 停止原因分类记录:查够了、主动认输、撞轮数、撞预算、原地打转是五种结局。
- 每一道闸都要构造用例踩响,装了没验过等于没装。
- 自评输出结构化的覆盖与缺失项,让「不够」这个判断可审计。
In multi-hop retrieval a wrong first hop poisons every hop after it. How would you design for that?多跳检索里第一跳查错了,后面全跟着错。你会怎么设计容错?
Common in ChinaCommon overseasDeep dive#multi-hop#error-propagation#agentic-ragHow to reason about it · think before answering
- This is about error propagation. 'Add a retry' is not enough - retries help when one path fails, but the multi-hop problem is walking confidently down the wrong path.
- Separate two failure kinds first, because the fixes are opposite. Either the answer document never entered the candidate pool - no amount of loosening helps, only a new query term does, which is the multi-hop path - or it was retrieved and then dropped by your own admission threshold, where extra hops are useless and only relaxing the gate recovers it. Coverage plus the answer slot tells them apart: low coverage means wrong direction (broaden), high coverage with an empty slot means halfway there (hop).
- Then give the mechanism. Do not let the model freestyle the next query: pick a bridge phrase from the sentence that best matches the question - a concrete noun the question never mentioned that also appears in another document. 'Not in the question' makes it new information; 'appears elsewhere' guarantees there is somewhere to hop to.
- Fault tolerance has three layers: keep the earlier hop's material, so a bad second hop does not destroy the evidence you already had; trace each hop separately so you can locate where it went wrong; and surrender explicitly when there is no lead left, handing 'insufficient evidence' to the generation-side refusal.
- The overlooked trap is worth points: the hop succeeds but the metric does not move. The second hop really did retrieve the target document, yet if you merge both hops' candidates and pack by score, the first hop's higher lexical overlap fills the budget and the target never enters the context. Allocate the context budget round-robin across hops - that is where multi-hop gains are actually realized.
- Expected follow-up: how do you know the first hop was wrong? From the structured self-assessment, not from the final answer. By the time the answer is wrong the chain is three hops deep and much more expensive to debug.
分析过程 · 先想清楚再作答
- 这题考的是错误传播意识。只答「加个重试」是不够的——重试只在「同一条路走不通」时有用,而多跳的问题是走上了错误的路还越走越远。
- 先把两类失败分开,这是整题的骨架:一类是根本没捞到(答案文档在候选池里一次都没出现,放宽门槛毫无用处,只能靠新的查询词重查,也就是多跳),一类是捞到了却被自己的过滤器扔了(排在第二名但没过准入门槛,这一类跳多少跳都没用,只能降级放宽门槛重判)。判据是要素覆盖率加答案槽位:覆盖率低是方向错了走放宽,覆盖率高但槽位空是只查到半路走多跳。判错类型,容错就完全用反了。
- 然后给具体机制。判断下一跳查什么,不能凭模型自由发挥,要有可解释的判据:从最贴题的那一句里挑出问题没提过、且在别的文档里也出现过的具体名词当作桥接短语。「问题没提过」保证它是新信息,「别的文档里也有」保证真的有下一跳可跳——只在这一篇里出现的短语,查了只会把同一篇再捞回来。
- 结论层面,容错有三层:不要丢掉上一跳的材料(第二跳查错了,第一跳的证据还在);每一跳独立记录轨迹,事后能定位是哪一跳歪的;追不动时主动认输,把「材料不足」交给生成侧的拒答,而不是硬凑一个答案。
- 有一个非常容易被忽略的坑,说出来会加分:跳成功了,命中却没变。第二跳确实把目标文档检索回来了,但如果把两跳的候选混在一起按名次装上下文,第一跳的材料字面重合度更高,会把预算占满,目标文档根本挤不进去。上下文预算必须按跳轮转分配——多跳的收益是在这一步兑现的,不是在检索那一步。
- 可预期的追问是「怎么知道第一跳错了」。答案是靠自评的结构化输出,而不是靠最终答案对不对;等到答案错了再回头找,链路已经断了三跳,定位成本高得多。
Key points
- Classify first: never retrieved needs a new query term (a hop); retrieved-then-filtered needs a relaxed gate. The fixes are opposite.
- Derive the next query from a bridge phrase - a concrete noun absent from the question that also appears in another document.
- Keep the previous hop's material so a failed hop does not discard existing evidence.
- Trace every hop separately so you can pinpoint which one drifted.
- Allocate context budget round-robin across hops, or a successful hop still fails to change the metric.
- Surrender explicitly when no lead remains and hand it to the generation-side refusal.
答题要点
- 先分类:根本没捞到只能靠多跳换查询词,捞到了被门槛扔了只能靠降级放宽,两者修法相反。
- 下一跳的查询用桥接短语:问题没提过、且别的文档里也出现过的具体名词。
- 保留上一跳的材料,第二跳失败时第一跳的证据仍在。
- 每一跳独立记轨迹,能定位是哪一跳歪的。
- 上下文预算按跳轮转分配,否则跳成功了命中也不会变。
- 追不动时主动认输,把材料不足交给生成侧拒答,不硬凑答案。
When would you refuse to make a RAG system agentic, and what data would you use to convince your team?什么情况下你会拒绝把一个 RAG 系统做成 Agentic 的?拿什么数据说服你的团队?
Common in ChinaCommon overseasIntermediate#agentic-rag#cost#engineering-judgementHow to reason about it · think before answering
- This tests engineering judgment and whether you can do arithmetic. Anyone who says 'agentic is more advanced so we should ship it' is out. The interviewer wants you to name the cost and draw the boundary with numbers.
- Decompose it: identify which question types actually benefit, then check how much of your traffic they represent. Agentic gains concentrate in multi-hop questions and retrieval retries; single-document questions are answered by one lookup and every extra round is waste.
- So the criterion is the evaluation set, not intuition. On a 20-item set we measured multi-hop recall going from 75% to 100% while overall answerable recall moved only from 93.8% to 100%, at the cost of average retrieval calls going from 1 to 1.75 plus the same number of assessment calls - you pay for 100% of traffic so that 5% of it improves.
- Three clear refusals: latency-sensitive surfaces, where each round adds a retrieval plus a model round trip and roughly doubles time to first token; fixed question patterns, where nine in ten questions are single-document and the gain is near zero; and tight cost budgets, where a real model is less disciplined than an offline stand-in and the variance, not the mean, is what breaks your capacity plan.
- Finish with the alternative: route. Use one cheap check to decide whether a question looks multi-hop, and only then enter the loop. Nine tenths take a single retrieval, one tenth loops, and the economics change completely. Looping is a capability, not a default.
- Expected follow-up: how do you know which questions look multi-hop? Mine the eval set and production logs for patterns - two facts requested in one sentence, or a question about the person behind a role - start with rules, and reach for a small classifier only when rules stop working.
分析过程 · 先想清楚再作答
- 这题在考工程判断力,也在考你会不会算账。凡是答「Agentic 更先进所以要上」的,直接出局;面试官想听的是你能主动说出它的代价,并且用数字划出适用边界。
- 怎么拆:先承认收益来自哪一类问题,再看这类问题在你的流量里占多大比例。Agentic 的收益几乎全部集中在多跳和检索失败重试上,单文档可答的问题一次检索就够了,多查一轮纯属浪费。
- 所以判据不是感觉,是评估集:跑一遍,看 multi 那一档占多少题、涨了多少个点,再对照总调用次数涨了多少倍。在一份 20 题的集合上,我们量到的是多跳召回从 75% 涨到 100%,可答题整体只从 93.8% 涨到 100%,代价是平均检索调用从 1 次涨到 1.75 次、外加同样次数的自评调用——为 100% 的问题付钱,只有 5% 的问题拿到好处。
- 三类明确不上:延迟敏感(每多一轮就是一次检索加一次模型往返,首字延迟拉长一到两倍);问题模式固定(九成是单文档可答,收益接近零);成本吃紧(真实模型不像离线替身那样老实,成本方差比均值更难受,按均值做的容量规划会在长尾上被打穿)。
- 给出替代方案才算完整:分流。先用一次便宜的判断看这一问像不像多跳,像才进循环,不像走固定流程。九成走一次检索、一成走循环,账完全不一样。这也说明循环是一种能力,不是默认值。
- 可预期的追问是「那你怎么知道哪些问题像多跳」。答案是从评估集和线上日志里找模式(问句里同时问了两个事实、问的是某个角色背后的人),先用规则跑,跑不动再上小模型分类——顺序不要反。
Key points
- Gains concentrate in multi-hop and retry cases; single-document questions gain almost nothing.
- Settle it with the evaluation set: multi-hop delta against the multiplier on total calls.
- One measured set: multi-hop recall 75% to 100%, overall 93.8% to 100%, retrieval calls 1 to 1.75 plus the same number of assessment calls.
- Refuse when latency-sensitive, when question patterns are fixed, or when cost is tight - variance hurts more than the mean.
- Route instead: a cheap check up front, and only multi-hop-looking questions enter the loop.
- Looping is a capability, not a default.
答题要点
- 收益集中在多跳与检索失败重试,单文档可答的问题上收益接近零。
- 用评估集算账:multi 档涨了多少点,对照总调用次数涨了多少倍。
- 实测过的一组数字:多跳召回 75% 到 100%,整体 93.8% 到 100%,检索调用 1 次到 1.75 次外加等量自评调用。
- 三类不上:延迟敏感、问题模式固定、成本吃紧(方差比均值更难受)。
- 替代方案是分流:便宜的判断先过滤,像多跳才进循环。
- 循环是一种能力,不是默认值。
D13 Going to Production: Incremental Sync and Deduplication, Permission-Based Filtering, Cache Layering, Tracing, and the Cost-Latency Ledger
After a document changes, how do you recompute only the affected chunks? And how do you guarantee a deleted document really disappears from the index?文档更新之后,你怎么做到只重算受影响的块?被删掉的文档又怎么保证一定从索引里消失?
Common in ChinaCommon overseasIntermediate#incremental-sync#content-hash#index-maintenanceHow to reason about it · think before answering
- There are two halves here and the second one separates candidates. Almost everyone can say 'hash it and compare'; the score comes from bringing up deletion yourself, because it is the one asymmetric case in the whole mechanism.
- Give the skeleton first: a three-way reconciliation between the full set from the source and the full set in the index. In source but not indexed is an add; in both but with different content hashes is a modify; indexed but absent from the source is a delete. A modify must replace the document wholesale, deleting old chunks before writing new ones, otherwise a shortened document leaves a tail behind in the index.
- Then the fingerprint itself, which is where points are won: sha256 truncated, but normalize line endings and trim before hashing. The same file uploaded from Windows and from macOS differs byte-wise but not in content; skip normalization and every re-upload counts as a change, which is a full rebuild in disguise. It never raises an error, it only shows up on the bill.
- The key insight in the second half: a deletion is not an event, it is an absence. Change feeds tell you what changed; nobody ever sends 'I no longer exist'. So deletion detection has to run in the opposite direction — walk the index and find ids the source no longer has. A synchronizer that only listens to change events will wait forever.
- At the storage layer, cascade the foreign keys across documents, chunks and embeddings so deleting a document is a single statement and the database does the rest. Hand-written three-step deletes eventually miss one, and the one they miss is a ghost in the index. Close with a verifiable invariant: chunk count must equal embedding count, and a mismatch means orphans.
- Expected follow-up: what if the source system itself is unreliable and a pull comes back incomplete? Make pull completeness a precondition for deletion: on a partial pull, apply adds and modifies only, or one failed fetch wipes half your index. Also soft-delete with a retention window so a mistake is recoverable.
分析过程 · 先想清楚再作答
- 这题有两半,区分度全在后半。前半几乎人人答得出「算个哈希比一比」,能不能拿到分取决于你有没有主动讲删除——那是同一套机制里唯一不对称的一种变更。
- 先给增量的骨架:拿来源的全集和索引的全集做三向对账。来源有、索引没有是新增;两边都有但内容指纹不同是修改;索引有、来源没有是删除。修改的处理是整篇替换,先删旧块再写新块,不能只追加——不然改短了的文档会在索引里留下一截尾巴。
- 接着讲指纹本身,这是给分点:sha256 取前若干位,但**算之前必须先做换行归一化再去首尾空白**。同一份文件从 Windows 传一次、从 Mac 传一次,字节不同内容相同,不归一化就每次都判成变了,等于天天在做全量重建。这个 bug 不报错,只体现在账单上。
- 然后是删除这一半的关键判断:**删除不是一个事件,是一个缺席**。文件变动类的通知只告诉你哪些东西变了,永远不会有人发一条「我不存在了」。所以删除检测必须反着来——遍历索引,找出来源里已经没有的 id。只监听变更事件的同步器永远等不到这条消息。
- 落到存储上:文档、块、向量三张表用外键级联删除,删文档只写一条语句,剩下的交给数据库。手写三条删除的版本迟早会漏掉一条,而漏掉的那条就是索引里的幽灵。收尾时报一个可验证的指标:块数与向量数必须相等,不等就说明有孤儿。
- 可预期的追问:来源系统本身就不可靠、拉不全怎么办?那就把「本次拉取是否完整」当成删除检测的前置条件——拉取不完整时只做新增和修改,不做删除,否则一次拉取失败会把半个索引清空。另外给删除加软删标记和保留期,误删还能回滚。
Key points
- Three-way reconciliation covering adds, modifies and deletes; a modify replaces the whole document, old chunks first.
- Normalize line endings and trim before hashing, or cross-platform re-uploads look like edits and you are doing a full rebuild every night.
- Deletion is an absence, not an event: walk the index for ids the source no longer has instead of waiting on a change feed.
- Cascade deletes from documents to chunks to embeddings so one statement suffices; assert chunk count equals embedding count to catch orphans.
- On an incomplete pull, apply adds and modifies only, and soft-delete with a retention window so mistakes are reversible.
答题要点
- 三向对账:新增、修改、删除,缺一不可;修改是整篇替换,先删旧块再写新块。
- 内容指纹算之前必须先做换行归一化再 trim,否则跨系统重传会被误判为修改,等于天天全量重建。
- 删除是缺席不是事件,必须反过来遍历索引找出来源里已消失的 id,不能只监听变更通知。
- 文档、块、向量用外键级联删除,删文档只写一条语句;用「块数等于向量数」当可验证的收尾指标。
- 来源拉取不完整时只做新增与修改、跳过删除,并给删除加软删与保留期以便回滚。
Why can't access control be applied at the generation step? What exactly leaks if you put it there?为什么权限过滤不能放在生成阶段做?放在那里会泄露什么?
Common in ChinaCommon overseasDeep dive#access-control#filter-pushdown#multi-tenancyHow to reason about it · think before answering
- This checks whether you think about RAG as a system. 'Because it's insecure' scores nothing; the interviewer wants what specifically leaks, and what else goes wrong besides the leak.
- Anchor the position with an image: the archivist spreads every file on the table, you pick nine, and only then does he pull three back saying you may not read those. You have already seen the titles. Filtering at generation time is that gesture.
- Then split the consequences, and note the second one is what shows engineering experience. First, exposure: the unauthorized documents were retrieved, ranked, read into process memory, and almost certainly written to retrieval logs and traces, even if none of their text reaches the answer. Second, dilution: you take the top 8, three are off-limits, the user gets five, and the legitimate results ranked ninth and tenth never get promoted. The user experiences 'it can't find anything' while your logs show a perfectly normal retrieval.
- State the fix: put the permission predicate in the same query as the ordering and the LIMIT, so the database prunes rows before ranking and unauthorized vectors are never compared. Cover both shapes: row-level filtering is one index plus a predicate; index isolation is a separate index per boundary.
- Give the selection criterion: the number and stability of the isolation boundaries. A handful of departments that rarely change makes isolation worthwhile; tens of thousands of per-user private document sets leave you with row-level filtering, because that many indexes is unmanageable. Add the shared-index side effect: a large tenant degrades everyone else's retrieval quality because candidate slots are shared.
- Expected follow-up: what about caching? It is the same bug's second crime scene. The answer cache key must include the permission scope, or one user's answer will be served to another, and that leak leaves no trace in the retrieval log at all.
分析过程 · 先想清楚再作答
- 这题在考你有没有真的把 RAG 当系统看。答成「因为不安全」拿不到分,面试官要的是「具体泄露了什么」和「除了泄露还有什么后果」两件事。
- 先用一个画面把位置说清楚:档案管理员先把全部档案摊在桌上让你挑,你挑完他再抽走三份说这些不能看——你已经看见标题了。在生成阶段过滤就是这个动作。
- 然后拆后果,两条,第二条更能显出做过工程:一是**泄露面**,越权文档已经进过检索、参与过排序、被进程读进过内存、大概率写进了检索日志和链路追踪,哪怕最终答案里没有它的内容;二是**结果被稀释**,取前 8 条里有 3 条不该看,筛掉只剩 5 条,而本该补位的第 9、10 名合法结果永远没机会上来——用户体感是「查不到」,你的日志里却是一次正常检索。
- 给正确做法:把权限谓词和排序、LIMIT 写进同一条查询,数据库先裁行再排序取前 k,越权的行一次都没被比较过。两种落法要都讲:行级过滤是一份索引加一个谓词,索引隔离是按边界各建各的索引。
- 选型判据要给出来:看隔离边界的数量和稳定性。部门这种个位数且几乎不变的边界,隔离划算;几万个用户各自的私有文档就只能行级过滤,否则运维扛不住。补一句共用索引的副作用——数据量大的租户会拖慢别人的检索质量,因为候选名额是共享的。
- 可预期的追问:缓存怎么办?这是同一个问题的第二现场——答案缓存的 key 里必须带上权限范围,否则一个用户的答案会被另一个用户命中,而且这条泄露路径连检索日志都不会留下痕迹。
Key points
- Filtering at generation time means unauthorized documents were already retrieved, ranked, held in memory and written to logs and traces; the exposure is far wider than 'did the text reach the answer'.
- The second consequence is dilution: filtered-out slots are not backfilled, so users see 'nothing found' while the log shows a normal retrieval.
- The fix is to put the permission predicate in the same statement as ordering and LIMIT so the database prunes before ranking.
- Choose between row-level filtering and index isolation by the count and stability of the boundaries; a shared index lets a large tenant crowd out a small one's candidate slots.
- Caching is the same bug's second crime scene: the answer cache key must carry the permission scope or answers leak across users without a trace.
答题要点
- 在生成阶段过滤时,越权文档已经被检索、排序、读进内存并写进日志与追踪,泄露面比「答案里有没有」大得多。
- 第二个后果是结果被稀释:筛掉之后名额空着不补,用户体感是查不到,日志里却是一次正常检索。
- 正确做法是把权限谓词和排序、LIMIT 写进同一条查询,让数据库先裁行再排序取前 k。
- 行级过滤与索引隔离的选型判据是隔离边界的数量与稳定性;共用索引时大租户会挤占小租户的候选名额。
- 缓存是同一个漏洞的第二现场:答案缓存的 key 必须包含权限范围,否则会跨用户串答案且不留痕迹。
What can be cached in a RAG system, and what are the invalidation conditions for each?RAG 系统里有哪些东西可以缓存?各自的失效条件是什么?
Common in ChinaCommon overseasIntermediate#caching#invalidation#cost-optimizationHow to reason about it · think before answering
- This looks like a giveaway and is actually a filter. 'Cache the question and answer' earns a third of the credit; the interviewer is waiting for the layering and the per-layer invalidation rules.
- Lead with a transferable rule: 'when must this be invalidated' is the same question as 'is that thing part of the key'. Leave something out of the key and changes to it will never invalidate the entry. With that rule the three layers derive themselves.
- Then go layer by layer. The answer layer maps a question to a final answer; its key needs the question, the permission scope, the index version, and the model plus prompt version. The retrieval layer maps a query to a hit list; its key needs the question, scope, topK, index version and embedding backend, but not the generation model. The embedding layer maps text to a vector; its key is just the text and the backend.
- Emphasize the counterintuitive part of the embedding layer: it is content-addressed, so the index version must not be in its key. Put it there and a single sync invalidates tens of thousands of vectors, which is exactly the full rebuild you added caching to avoid. This is the one layer that can live a long time, even on disk.
- Offer a concrete invalidation mechanism: version numbers rather than targeted deletion. Bump an index version whenever a sync actually changes something and old keys simply stop being computed. Targeted deletion would require enumerating which questions a change affected, and that list cannot be produced.
- Expected follow-up: can you give a real 'should have expired but didn't' case? Yes: an answer cache keyed only on the question. A document's limit changes from 200 MB to 500 MB, the index is updated, and the same question still returns 200 MB. Nothing errors; the log shows a clean cache hit. The same key also serves one department's answer to a user from another.
分析过程 · 先想清楚再作答
- 这题看起来是送分题,实际是筛人题。答成「把问答结果缓存起来」只拿到三分之一,面试官等着听的是「分几层」和「各自什么时候失效」。
- 先给一条能迁移到别的题上的判断依据:**「什么时候必须失效」这个问题,等价于「key 里有没有把那样东西算进去」。** key 少放一样,那样东西变了缓存就不会失效。有了这条,三层的答案自己就长出来了。
- 然后逐层给:答案层缓存问题到最终答案,key 要有问题、权限范围、索引版本、模型与提示词版本;检索层缓存检索式到命中块列表,key 要有问题、权限范围、topK、索引版本、向量后端,但不需要模型;向量层缓存文本到向量,key 只有文本和向量后端。
- 重点讲向量层的反直觉之处:它是**内容寻址**的,文本没变、模型没变,向量就不会变,所以**不能把索引版本放进它的 key**。放进去的话一次同步就作废几万条向量,正好绕回全量重建——你加缓存想省的那笔钱又花回去了。这一层可以放很久甚至持久化。
- 给一个具体的失效手法:用**索引版本号**而不是精确删除。同步只要真的改动了索引就把版本号加一,旧 key 再也算不出来,自然没人读得到。精确删除要求你能列出「这次改动影响了哪些问题」,而那是列不出来的。
- 可预期的追问:能举一个「该失效却没失效」的真实例子吗?答:答案缓存的 key 只放了问题本身,文档里的上限从 200 MB 改成 500 MB、索引已经更新,再问同一个问题仍然返回 200 MB。它不报错,日志上是一次漂亮的缓存命中;同一个 key 还会让另一个部门的用户直接命中别人的答案。
Key points
- Three layers — answer, retrieval, embedding — with lifetimes orders of magnitude apart; treating them as one thing is the mistake.
- The rule is that 'when must it expire' equals 'is it in the key'; anything left out of the key can never invalidate the entry.
- The answer key carries question, permission scope, index version, model and prompt version; the retrieval key drops the model and adds topK and the embedding backend.
- The embedding layer is content-addressed and keyed only on text plus backend; adding an index version turns every sync back into a full rebuild.
- Version-based invalidation beats targeted deletion because you cannot enumerate which questions a given change affected.
答题要点
- 分三层:答案、检索、向量,三者的寿命差着数量级,不能当成一件事。
- 判断依据是「什么时候必须失效」等价于「key 里有没有算进那样东西」,key 少一样就永远失效不了。
- 答案层 key 要有问题、权限范围、索引版本、模型与提示词版本;检索层去掉模型、加上 topK 与向量后端。
- 向量层是内容寻址的,key 只有文本与后端;把索引版本放进去会让每次同步都退化成全量重建。
- 用索引版本号做失效比精确删除可靠,因为「这次改动影响了哪些问题」根本列不出来。
You need to switch embedding models. How do you migrate a live system without downtime and without losing recall?要换一个 embedding 模型,线上系统怎么迁移才能不停机也不掉召回?
Common in ChinaCommon overseasDeep dive#embedding-migration#zero-downtime#rolloutHow to reason about it · think before answering
- The crux is why you cannot swap in place. Jumping straight to the steps without establishing that reads like reciting a runbook.
- Set up the premise: vectors from different models are not comparable. Dimensions may differ, and even at equal dimensions the coordinate spaces are unrelated, so encoding the query with the new model and comparing against documents encoded with the old one yields noise. Switching models therefore means re-embedding the entire corpus.
- Then the four steps: add a nullable second vector column; backfill it with a background job while the old column is untouched and still serves live traffic; canary a slice of traffic onto the new column while running the golden set against both columns to compare recall and faithfulness; cut over fully once the numbers hold, and drop the old column only after a week or two of observation.
- Name the payoff explicitly, because this is where the points are: the value of the whole procedure is the rollback cost. Cutover is a config change naming which column to read, so reverting takes a second rather than re-running an eight-hour rebuild. A migration plan with no rollback path is not a plan.
- Add two engineering details: build the approximate-nearest-neighbor index on the new column after the backfill, not during it, since concurrent building is slow and prone to locking; and make the backfill resumable and rate-limited, or it will exhaust the embedding API quota and drag live queries down with it.
- Expected follow-up: how do you prove the new model is actually better? Not from an offline metric alone — run an A/B on the same golden set with identical retrieval parameters and report four numbers: recall, faithfulness, latency and cost. A conclusion resting on the first number only does not hold. Note also that switching models is the one moment when the embedding cache genuinely must be invalidated.
分析过程 · 先想清楚再作答
- 这题的题眼是「为什么不能就地换」。没有先说清这一点就直接讲步骤,会显得是在背流程。
- 先给前提:不同模型的向量之间**没有可比性**。维度可能不同,即使维度相同坐标系也完全不是一回事,用新模型编码问题去和旧模型编码的文档比距离,算出来的相似度是纯噪声。所以「换模型」实质上等于「把整个知识库重新向量化一遍」。
- 然后给四步:加一列新向量、允许为空;后台任务慢慢回填新列,旧列一个字节不动,线上仍走旧列;小流量灰度到新列,同时用标准答案集在两列上各跑一遍比召回率与忠实度;数字站得住再全量切换,旧列观察一两周后才删。
- 把这套流程的价值点破,这是给分点:**它的价值全在回滚成本上**。切换只是改一个配置项「走哪一列」,出问题时切回去是一秒钟的事,而不是重跑一遍八小时的重建任务。凡是拿不出回滚路径的迁移方案都不算方案。
- 补两个工程细节:新列的近似最近邻索引要在回填完之后再建,边写边建又慢又容易锁表;回填要能断点续传并限速,否则会把 embedding 接口的配额打满,把线上查询一起拖垮。
- 可预期的追问:怎么证明新模型确实更好?答:不能只看离线指标涨没涨,要在同一份标准答案集、同一套检索参数下跑 A/B,报召回率、忠实度、延迟、花费四笔账;只报第一笔的结论不成立。另外注意换模型会让缓存里的向量全部作废,那是这次迁移唯一该作废向量缓存的时刻。
Key points
- Vectors from different models are not comparable, so a model switch is equivalent to re-embedding the entire corpus.
- Four steps: add a nullable second vector column, backfill in the background, canary with the golden set scored on both columns, then cut over once the numbers hold.
- The whole value lies in rollback cost: cutover is a config change, so reverting takes a second instead of another full rebuild.
- Build the ANN index on the new column after the backfill; make the backfill resumable and rate-limited so it does not exhaust the embedding quota and stall live queries.
- Validate with an A/B on one golden set reporting recall, faithfulness, latency and cost; a model switch is also the only time the embedding cache truly must be invalidated.
答题要点
- 不同模型的向量之间没有可比性,所以换模型等价于把整个知识库重新向量化一遍。
- 四步:加一列可空的新向量、后台回填、小流量灰度并用标准答案集在两列上对比、数字站得住再全量切换。
- 这套流程的价值全在回滚成本上:切换是改一个配置项,回滚是一秒钟的事而不是重跑一次重建。
- 新列的近似最近邻索引在回填完成后再建;回填要可断点续传并限速,别把接口配额打满拖垮线上查询。
- 验证要在同一份标准答案集上跑 A/B,同时报召回率、忠实度、延迟与花费四笔账;换模型也是唯一该作废向量缓存的时刻。
D14 Capstone Project and Retrospective: A Multi-Tenant Enterprise Knowledge-Base Q&A, a RAG Decision Map, and an Interview Deep Dive
You are handed a knowledge base of five million documents that must answer in about a second, with accuracy as the top priority. How would you design it?给你一个五百万文档、要求秒级响应、准确率优先的知识库场景,你会怎么设计这套系统?
Common in ChinaCommon overseasDeep dive#system-design#scaling#latency-budgetHow to reason about it · think before answering
- The real subject here is not which technologies you know, it is whether you have a repeatable way to derive a configuration from constraints. Opening with an architecture diagram reads as a memorized answer; the way to score is to turn each constraint into a number first, then let every choice be forced by one of those numbers.
- Quantify the three constraints. Five million documents at roughly four or five chunks each is over twenty million chunks; at 1536 float dimensions that is hundreds of gigabytes, so the index does not fit in one machine's memory — that alone settles storage. A one-second budget to first token, with generation typically eating seven or eight hundred milliseconds, leaves only two or three hundred for retrieval. Accuracy first means you may trade latency and money for metrics, but only within that remaining budget.
- Now derive each knob from one of those numbers: a dedicated vector store or partitioning, plus half precision (its recall loss usually sits inside run-to-run noise while the index shrinks by about forty percent — essentially free); keep both keyword and vector routes with reciprocal rank fusion, because exact matches on document ids, error codes and names are a permanent blind spot for embeddings; rerank only the top twenty after fusion, since it buys ranking quality at the cost of one synchronous round trip, and a one-second budget affords exactly one.
- Then state two things you deliberately do not build, which is the part that reads as field experience. Agentic retrieval is not the default path: its gains concentrate on multi-hop questions while its cost is spread over every question, and it blows a one-second budget outright — the right move is a cheap classifier that routes only the multi-hop minority into the loop. Contextual chunk headers and similar tricks also wait, because they dilute the keyword route while helping the vector route; the directions are opposite, so measure on your own embeddings before committing.
- Accuracy first has to become something you can sign off on. That means a golden set of at least a hundred questions with multi-hop and unanswerable each above ten percent, recall and ranking quality read separately, abstention rate on unanswerable questions as its own column, and citations verified by code rather than trusted from the model. Reporting the ugliest column alongside the headline number is far more credible than reporting a single score.
- Expected follow-up: how do you build the first index over five million documents? It is a one-off large expense, so batch it, make it resumable, and put content-hash incremental sync in from day one, or every config change means buying the whole corpus again. Push further and you get to rollout: dual-write the new embeddings into a second column, evaluate both columns on the same golden set, then shift traffic, so rollback is a config flip rather than an eight-hour rebuild.
分析过程 · 先想清楚再作答
- 这题的题眼不在「你会用什么技术」,而在「你有没有一套从约束推配置的方法」。开口就报架构图和技术栈的答案会被判成背方案;拿到分的答法是先把约束翻译成数字,再让每个选择被某个数字逼出来。
- 先把三个约束量化:五百万文档按一篇四五块估,是两千多万块,单精度 1536 维就是上百 GB,**索引塞不进单机内存**,这一条直接决定了存储选型;秒级响应意味着从收到问题到第一个字的预算大约一秒,而生成本身通常就吃掉七八百毫秒,检索侧只剩两三百毫秒;准确率优先意味着可以拿延迟和钱换指标,但只能换到那两三百毫秒为止。
- 然后逐项落地,每一项都挂在上面某个数字上:存储上专用向量库或分区加半精度量化(半精度的召回损失通常落在重跑噪声里,索引却小四成,这是白捡的);检索保留关键词与向量两路加倒数排名融合,因为精确匹配的文档号、错误码、人名是向量的固定盲区;重排只作用于融合后的前二十条——它买的是排序质量,一次同步往返,秒级预算里放得下一次,放不下两次。
- 接着讲两个「不上」的决定,这一段比上面更能显出做过工程:**Agentic 检索不作为默认路径**,它的收益集中在多跳题上而代价摊给全部问题,秒级预算下更是直接超支——正确做法是先用一次便宜的分类把多跳分流出来,只让那一小部分进循环;**上下文块头之类的手法先不上**,因为它对关键词一路是稀释、对向量一路才是补位,方向相反,得在自己的真实 embedding 上测过再说。
- 准确率优先必须落成可验收的东西,否则是空话:一份不少于一百题的标准答案集(其中多跳与无答案各占一成以上)、召回率与排序质量分开看、无答案题的拒答率单独一栏、引用由代码回查而不是靠提示词自觉。**报数字时把最难看的那一栏也报出来**,比只报总分可信得多。
- 可预期的追问:五百万文档怎么建第一版索引?答案是这笔钱是一次性大额支出,要按批做、可断点续跑,并且从第一天就上基于内容指纹的增量同步——否则每次改配置都等于把整个知识库重买一遍。再追问就谈灰度:新旧两套向量双写在两列上,用同一份标准答案集在两列上各跑一遍再切流量,回滚只是改一个配置项。
Key points
- Translate constraints into numbers first: twenty million chunks means the index will not fit one machine, and a one-second budget leaves retrieval two to three hundred milliseconds.
- Dedicated store or partitions plus half precision; keep keyword and vector routes with RRF, and rerank only the top twenty after fusion.
- Name the two things you will not ship: agentic only for a routed multi-hop minority, and chunk headers only after measuring on your own embeddings.
- Turn accuracy-first into a hundred-plus question golden set, abstention rate as its own column, and code-verified citations.
- First index build is a one-off large expense: batch it, make it resumable, add incremental sync on day one, and dual-write columns for model swaps.
答题要点
- 先把约束翻译成数字:两千多万块决定索引塞不进单机内存,一秒预算里检索侧只剩两三百毫秒。
- 存储用专用库或分区加半精度;检索保留关键词与向量两路加倒数排名融合,重排只作用于前二十条。
- 明确说出「不上」的两项:Agentic 只对分流出来的多跳开,块头这类方向相反的手法先测再说。
- 准确率优先要落成一百题以上的标准答案集、拒答率单独一栏、引用由代码回查。
- 第一版建索引是一次性大额支出:分批可续跑,并从第一天就上增量同步;换模型走双写切列。
Looking back at the RAG project you built, which decision would you change now, and why?你做过的这个 RAG 项目里,哪个决定你现在会改?为什么?
Common in ChinaCommon overseasDeep dive#retrospective#evidence#chunkingHow to reason about it · think before answering
- This looks like a soft question but it separates people sharply. Saying 'nothing yet' admits you never ran a retrospective; a long list of self-criticism reads as poor judgment. What the interviewer is listening for is whether you can chain four things together: the decision, the evidence you had then, the evidence you got later, and your current call.
- How to pick: choose a decision that was justified at the time and later overturned by data, not one you always knew was a shortcut. The first proves you measure; the second only proves you were behind schedule. So the answer has a fixed four-part shape — what you chose, on what basis, what you measured later, and what you now believe.
- This course supplies a ready example. One day measured that prepending a heading-path header to every chunk left hit rate unchanged, grew index tokens by about ten percent, and pushed the answer document's mean rank from 2.88 to 3.25 — hence 'headers hurt keyword retrieval'. A later day re-ran the same comparison under structure-aware chunking and the rank regression did not reproduce. The reason was the chunker: with fixed-length cuts, chunk boundaries do not line up with section boundaries, so the header injects heading terms that do not belong to that chunk; with structure-aware cuts, each chunk already sits inside one section and the header largely restates what is already there. The correct statement is therefore not 'headers hurt' but 'headers hurt when chunk boundaries are misaligned with document structure'.
- The value of the chain is that it demonstrates a reusable habit: attach the premises to every conclusion. Change a premise and you owe a re-run; you may not pair new settings with an old conclusion. The same reasoning yields a second example: an earlier claim that 'the vector route is clearly a net gain' collapsed once the two routes were counted separately before fusion — the vector-only candidates were a small share and contained the answer document zero times, so the improvement was never semantic at all.
- Expected follow-up: how will you avoid this class of error in future? Give two concrete practices. Write the bound premises next to every number — corpus, question set, budget, chunker. And before publishing any conclusion, ask whether it was measured by this experiment or forced by the structure of the implementation; the first needs its boundaries stated, only the second can be asserted flatly.
分析过程 · 先想清楚再作答
- 这题看着是软性问题,其实区分度极高。答「暂时没有」等于承认没做过复盘;答成一长串自我批评又会显得没有判断力。面试官真正在听的是:你能不能把一个决定、它当时的依据、后来的证据、以及新的判断,四样东西串成一条链子说清楚。
- 怎么拆:挑一个**当时有理由、后来被数据推翻**的决定,而不是一个「当时就知道是凑合」的决定。前者证明你有量化的习惯,后者只证明你赶过工期。所以答案的骨架固定是四段——当时选了什么、依据是什么、后来量到了什么、现在的判断是什么。
- 本课里有一个现成的样本:某一天先量到「给每个块拼上标题块头之后,命中率不变、索引 token 涨一成、答案文档平均名次从 2.88 退到 3.25」,据此写下「块头对关键词检索是负收益」。后来换成按文档结构切块再复核,这条名次退化**没有复现**。原因是切法变了:固定长度硬切时块边界跟小节边界不对齐,块头会把不属于这一块的标题词塞进来;按结构切时块本身就落在一个小节里,块头补的信息跟块里已有的高度重合。所以正确的表述不是「块头有害」,而是「**块头在块边界与结构不对齐时才有害**」。
- 这条链子的价值在于它演示了一个可复用的动作:**给每个结论标出它绑定的前提**。前提变了就要重跑,不能拿新配置去配旧结论。顺着这个思路还能给出第二个例子:曾经写过「向量侧确实是正收益」,后来把融合前的两路拆开数了一遍才发现,向量路独有的候选只占很小一部分,其中含答案文档的次数是零——那个「涨」根本不是语义检索带来的,于是这条结论被自己推翻。
- 可预期的追问:那你以后怎么避免这类错误?答两条具体的:一是每个数字旁边写清它绑定了哪几个前提(语料、题集、预算、切法),二是报结论前先问自己一句「这是这次实验测出来的,还是这个结构必然导致的」——前者要标边界,后者才能直接讲。
Key points
- Structure the answer in four beats: the choice, the evidence then, the evidence later, the call now.
- Pick a decision that was defensible at the time and later overturned by data, not one you knew was a shortcut.
- Worked example: 'headers hurt keyword retrieval' was corrected to 'headers hurt when chunk boundaries misalign with structure', because the chunker premise changed.
- Record the premises bound to every number; when a premise changes you owe a re-run rather than a reinterpretation.
- Classify before asserting: measured by this experiment, or forced by the implementation's structure — the former needs its boundaries stated.
答题要点
- 答案要串成四段:当时选了什么、依据是什么、后来量到了什么、现在的判断是什么。
- 挑一个当时有理由、后来被数据推翻的决定,而不是一个当时就知道在凑合的决定。
- 样本:块头从「对关键词检索有害」修正成「块边界与结构不对齐时才有害」,因为切法这个前提变了。
- 每个数字旁边写清它绑定的前提;前提变了就必须重跑,不能新配置配旧结论。
- 报结论前先分类:这是实验测出来的,还是实现结构必然导致的——前者要标边界。
How do you convince a non-technical stakeholder that your retrieval system actually got better?怎么向不懂技术的业务方证明你的检索系统真的变好了?
Common in ChinaCommon overseasBasic#evaluation#stakeholder-communication#abstentionHow to reason about it · think before answering
- This is a communication question whose scoring hinges on technical judgment: which numbers you choose to show reveals whether you understand the metrics yourself. Dumping recall, nDCG and MRR on a business stakeholder reads as tone-deaf; saying 'user feedback improved' reads as unmeasured.
- Start from a principle: show them something they can adjudicate themselves. They cannot judge normalized discounted cumulative gain, but they can absolutely judge 'out of these hundred real questions, how many did it answer correctly, how many wrongly, and how many did it honestly decline'. So the external framing is three numbers — correct, wrong, declined — and they sum to one hundred.
- The crucial move is separating wrong from declined, and it is the fastest way to earn trust: saying 'not found' is a correct output, not a failure; the failure is inventing an answer when nothing was found. Teams that report a single 'accuracy' number can be gamed by a system that learns to decline everything, which is why all three must appear side by side.
- Then supply checkable evidence rather than only numbers: take ten real questions and show before-and-after answers with clickable citations on every claim. A stakeholder who opens the source and verifies one claim is more convinced than by any percentage, and the exercise doubles as the human spot-check you need anyway to calibrate whether your model judge is trustworthy.
- There is a lesson from this course worth volunteering: a column of perfect scores means the ruler is broken. Our questions were written backwards from the corpus, lexical overlap is unusually high, and mean reciprocal rank sits at exactly 1.0000. Showing that to a stakeholder only invites the misreading that you are already perfect, when in fact the metric has saturated. When a metric hits the ceiling, the response is to make the questions harder.
- Expected follow-up: how do you get the business side involved? One very practical answer: let them supply questions. Every production miss gets appended to the golden set, so the evaluation set grows rather than being built once. Then each release can point at 'the question you raised last month now answers correctly', which lands better than any status report.
分析过程 · 先想清楚再作答
- 这题在考沟通,但拿分点在技术判断上:你选哪几个数字给业务方看,暴露了你自己有没有看懂这些指标。把召回率、nDCG、MRR 一股脑摊出去的答法会被判成不懂受众;只说「用户反馈变好了」又会被判成没有度量。
- 先立一条原则:**给业务方看的必须是他们能自己判断对错的东西**。归一化折损累计增益他们没法判断,而「这一百个真实问题里,系统答对了多少、答错了多少、老老实实说查不到了多少」他们一眼就能判断。所以对外的口径应该是三个数:答对率、答错率、拒答率,而且三个加起来是一百。
- 关键是把**答错和拒答分开**。这一条最能建立信任:查不到就说查不到不是故障,是正确输出;真正的故障是查不到还编一段。很多团队只报「准确率」,结果一个学会了一直拒答的系统能刷出满分——所以这三个数必须并排出现,缺一个都能被骗。
- 然后给可核对的证据,而不是只给数字:**挑十条真实问题做前后对照**,各贴出改动前和改动后的回答,每句结论后面挂着可点开的引用。业务方点开原文核对一遍,比看任何百分比都有说服力,而且这个动作顺带完成了一次人工抽检——你自己也需要它来校准模型裁判靠不靠谱。
- 本课里有一条要主动说的教训:**一列全是满分说明尺子坏了**。我们的题目是从语料反向出的,字面重合度过高,平均倒数排名恒为 1.0000。这个数字拿给业务方看,只会换来一次「那你们已经完美了」的误会,而它其实是指标饱和。指标撞天花板时该做的是把题目出难一点。
- 可预期的追问:那怎么让业务方参与进来?答一条很实用的:让他们提供题目。把线上答错的问题一条条补进标准答案集,评估集是长出来的,而不是一次性造好的;这样每一次改进都能指着「你上次提的那个问题现在答对了」,比任何汇报都直接。
Key points
- Externally report three numbers they can adjudicate: correct, wrong, declined — summing to one hundred.
- Keep wrong and declined separate; a single accuracy number is gamed by a system that learns to decline everything.
- Pair it with ten before-and-after real questions, every claim carrying a citation they can open and verify.
- Volunteer the saturation caveat: a column of perfect scores means a broken ruler, and the fix is harder questions.
- Let stakeholders contribute questions; append every production miss to the golden set so it grows over time.
答题要点
- 对外只用三个他们能自己判断的数:答对率、答错率、拒答率,三者相加为一百。
- 答错和拒答必须分开——查不到就说查不到是正确输出,只报一个准确率会被「一直拒答」刷满分。
- 配十条真实问题的前后对照,每句结论挂可点开的引用,让他们自己核对原文。
- 主动说明指标饱和:某一列恒为满分是尺子坏了,不是系统完美,该做的是把题目出难一点。
- 让业务方提供题目,把线上答错的问题补进标准答案集——评估集是长出来的。
Users report that your live RAG system 'answers inaccurately'. What is your triage order?RAG 系统上线后用户反馈「答得不准」,你的排查顺序是什么?
Common in ChinaCommon overseasIntermediate#debugging#failure-modes#observabilityHow to reason about it · think before answering
- This one is almost guaranteed to be asked, and most people answer with a flat list of possibilities: maybe chunking, maybe the prompt, maybe the model. A list is not triage. Triage means an order, a decision rule at each step, and each step eliminating half the search space.
- First decompose the complaint. 'Inaccurate' hides at least four distinct failures whose fixes do not transfer: off-topic answers, partial answers, misaligned citations, and stale content. So the first action is not to change a setting, it is to obtain the specific question and answer and classify it into one of those four.
- Then give the order along with its justification: read the pipeline right to left, fix it left to right. Right to left because the generated answer is what you see first; left to right because upstream errors are amplified downstream — no prompt can recover a document retrieval never fetched. Concretely: dump the candidate pool and the final context for that question, and check whether the answer document is in the pool at all. Absent means a retrieval debt; present but below the admission gate means a gate debt; admitted but never packed into the context budget means chunks too large or budget too small; all present and still unused means it is finally a generation problem.
- One detail worth volunteering because it is easy to get wrong: for multi-hop questions, diagnose the documents that are missing, not whether any one of them was retrieved. In our experiment one question needed two documents; the first ranked first every time and the second never entered the candidate pool at all. Judging by 'any of them' labels it a budget problem, and you can spend a full day tuning budgets to no effect. This distinction only occurs to someone who has actually triaged question by question.
- The fourth class, stale content, happens outside the question path and has its own rule: first check whether reconciliation even noticed the edit (was the content hash computed after line-ending normalization?), then check whether the cache key includes the index version and the permission scope. 'When must this expire' is equivalent to 'is that thing part of the key' — leave something out of the key and changes to it will never invalidate the entry.
- Expected follow-up: how do you stop relying on manual triage? Build the classification into the evaluation panel so every missed question is automatically labeled with one of the four classes, and report it per tenant. A global average dilutes one customer's collapse across the whole population, and that customer is exactly the one who will file the complaint.
分析过程 · 先想清楚再作答
- 这题几乎是必考题,而绝大多数人答成一堆并列的可能性:可能是切块问题、可能是提示词问题、可能是模型不行。并列不是排查,排查的意思是**有顺序、有判据、每一步能把可能性砍掉一半**。
- 先把「答得不准」这四个字拆开——它至少塞了四种病,而且修法互不通用:答非所问、只答得出片段、引用错位、更新不生效。所以第一个动作不是改配置,是**拿到具体的问题和回答,把它归到这四类里的一类**。
- 然后给顺序,而且要说清顺序的理由:**排查从右往左看、修复从左往右修**。从右往左是因为你最先看到的是生成结果;从左往右是因为上游的错会被下游放大——检索没捞到的东西,再好的提示词也救不回来。具体走法是:打印这一问的候选池和最终上下文,先看答案文档在不在候选池里。不在,是检索的债;在候选池但没过准入门槛,是门槛的债;过了门槛却没装进上下文预算,是块太大或预算太小;都进了而模型没用上,才轮到生成侧。
- 这里有一个容易写错的细节值得主动讲:**多跳题的诊断对象是缺的那几篇,不是「有没有捞到任意一篇」**。我们实验里有一道题要同时命中两篇,第一篇稳稳排第一、第二篇一次都没进候选池;用「任意一篇」去判会把它归成预算问题,然后你去调预算,调一整天也没用。这一条区分度很高,因为它只有真的按题排查过才想得到。
- 第四类「更新不生效」发生在问答之外,判据是另一条:先看对账认没认出这篇改了(内容指纹算之前有没有做换行归一化),再看缓存的 key 里有没有把索引版本和权限范围算进去。「什么时候必须失效」等价于「key 里有没有把那样东西算进去」,key 少放一样,那样东西变了缓存就不会失效。
- 可预期的追问:怎么让这套排查不靠人肉?答案是把分类做进评估面板——每一道没中的题自动标出它属于四类中的哪一类,并按租户分开统计。全局平均会把单个客户的塌方按人头摊薄,而线上会投诉的恰恰是那个客户。
Key points
- Classify the complaint into four failures first — off-topic, partial, misaligned citation, stale — because their fixes do not transfer.
- Read right to left, fix left to right: dump the candidate pool and final context and find which layer the answer document stalls at.
- The four rules in order: never retrieved, retrieved but below the gate, admitted but squeezed out of the budget, packed but unused by the model.
- For multi-hop, diagnose only the missing documents; judging by 'any one retrieved' mislabels a never-retrieved case as a budget problem.
- For stale content, check reconciliation and the cache key: what must expire is exactly what the key must contain.
答题要点
- 先把「答得不准」归类成四种病:答非所问、只答得出片段、引用错位、更新不生效——修法互不通用。
- 排查从右往左看、修复从左往右修:先打印候选池与最终上下文,看答案文档卡在哪一层。
- 四层判据依次是:没进候选池、进了没过门槛、过了没装进预算、都进了模型没用上。
- 多跳题只诊断缺的那几篇;用「有没有捞到任意一篇」会把「根本没捞到」误判成预算问题。
- 「更新不生效」查对账与缓存 key:什么时候必须失效,等价于 key 里有没有算进那样东西。
If you could only fund three changes to improve an existing RAG system, which three would you pick and why those three?如果预算只够做三件事来提升一个已有 RAG 系统的效果,你选哪三件?为什么是这三件?
Common in ChinaCommon overseasIntermediate#prioritization#evaluation#abstentionHow to reason about it · think before answering
- This tests prioritization, not breadth. Answering with a list of techniques — add reranking, add hybrid retrieval, add query rewriting — almost always loses points, because it skips a prerequisite: how do you know those three help your system? That is precisely the sentence the interviewer is waiting for.
- So the first item has to be building evaluation, with a reason specific enough to be unarguable: without a scale, you cannot tell whether the other two helped or hurt; with one, every subsequent spend has a measurable return. It is also cheap — the three retrieval metrics are pure local computation, run in seconds, cost nothing, and can gate every commit; the only real effort is labeling answer documents once. Include the composition rule: multi-hop and unanswerable each above ten percent, because without the unanswerable class a system that only ever guesses scores perfectly on your report.
- Second, move abstention out of the prompt and into code — usually the best return per unit of effort, and the item most often skipped. Writing 'say you don't know' ten times in a prompt buys almost nothing. Citation numbers are a closed set, so checking existence is one line, and adding a substantive-overlap check catches the harder forgery where the number is real but the content is not. Our baseline abstention rate was 0.0 percent: four questions with no answer in the corpus, zero of them declined — a defect that is completely invisible on a report that only shows recall.
- Third, look at the failure cases before deciding, which is the actual answer to this question. After reading the panel you land on one of a few branches: a high share of multi-hop means bridging retrieval or a different index structure; queries that miss when phrased differently mean you need the vector route or hybrid retrieval; answers retrieved but never packed into context means reranking or budget. Failure cases first, technique second — we tried five advanced index structures and not one beat the baseline, because our system simply did not have the weakness they address.
- Why not the flashier options: agentic retrieval concentrates its gains on multi-hop while spreading cost across every question, and in our measurements turning on every query-side technique produced exactly the same recall as the default configuration while using 2.5 times the model calls and 4.3 times the retrievals. Stacking techniques is easy; explaining why you switched several off is the skill.
- Expected follow-up: once the three are done, how do you prove the money was well spent? Toggle each one individually and report three ledgers — how much the metric moved, how much latency moved, how much cost moved. A proposal that reports only the first should not be approved, including your own.
分析过程 · 先想清楚再作答
- 这题在考优先级判断,而不是知识面。答成「上重排、上混合检索、上查询改写」这类手法清单几乎必然掉分——因为它跳过了一个前提:**你凭什么知道这三件对你的系统有用?** 面试官等的就是这句话。
- 所以第一件必须是**建评估**,而且理由要具体到不可反驳:没有秤,剩下两件做完你也说不清是变好还是变坏;有了秤,后面每一笔钱都能算回报。而且它便宜——检索侧三个指标是纯本地计算、几秒钟、零成本,能挂进每次提交;花时间的只是给题目标答案文档那一次。顺带说清评估集的配比:多跳与无答案各占一成以上,缺了无答案那一类,一个只会硬答的系统在报表上就是满分。
- 第二件是**把拒答从提示词搬进代码**,这一件的性价比通常最高而最容易被跳过。提示词里写十遍「找不到就说找不到」增益接近于零;而引用编号是一个闭集,判它存不存在只要一行代码,再加一道「这句话与被引块的实质重合度」就能拦住「编号是真的、内容是假的」那一类。我们实验里的基线拒答率是 0.0%——四道语料里根本没有答案的题一道都没闭嘴,这类缺陷在只报召回率的报表上完全不可见。
- 第三件要**先看失败案例再决定**,这才是这道题真正的答案。看完面板你会落到其中之一:多跳题占比高就补桥接检索或改索引结构;换个说法就捞不到,说明该上向量那一路或混合检索;答案捞到了却排不进上下文,那是重排或者预算的活。**先有失败案例,再有手法**——我们试过五种高级索引结构,没有一种跑赢基线,因为我们的系统压根没有那些结构要补的短板。
- 为什么不选那些看起来更亮的:Agentic 检索的收益集中在多跳题上而代价摊给全部问题;「全开」所有查询侧手法在我们的实测里召回率和默认配置一模一样,模型调用却是 2.5 倍、检索次数 4.3 倍。**堆手法很容易,说清楚为什么关掉某几项才是本事。**
- 可预期的追问:三件做完怎么证明钱花对了?答:每一项单独开关各跑一遍,报三笔账——指标涨了多少、延迟涨了多少、钱涨了多少。只报第一笔的提案不该被批准,包括你自己的。
Key points
- First, build evaluation: without a scale the other two changes are unverifiable, and the retrieval metrics are cheap enough to gate every commit.
- The golden set must include unanswerable questions, or a system that only ever guesses scores perfectly on your report.
- Second, move abstention from the prompt into code: citation numbers are a closed set, and a substantive-overlap check catches real-number-fake-content forgeries.
- Third is chosen by the failure cases, not by a list of techniques — failure cases first, index structure or retrieval trick second.
- Toggle each change individually and report three ledgers: metric, latency, cost. A proposal reporting only the first should not be approved.
答题要点
- 第一件是建评估:没有秤,另外两件做完也说不清变好还是变坏;检索侧指标零成本可挂进每次提交。
- 评估集必须含无答案那一类,否则一个只会硬答的系统在报表上就是满分。
- 第二件是把拒答从提示词搬进代码:编号是闭集,再加实质重合度就能拦住「编号真、内容假」。
- 第三件由失败案例决定,不由手法清单决定——先有失败案例,再有索引结构或检索手法。
- 每一项单独开关跑一遍并报三笔账:指标、延迟、钱。只报第一笔的提案不该被批准。
Build an AI Short-Drama Production Pipeline With Agents in 14 Days
D1 What an AI Short-Drama Production Pipeline Looks Like: Breaking Down the Stages, a Task-Graph Architecture, and Choosing Among Four Categories of Generation Models
Why wrap a vendor SDK in your own provider interface, and when does that layer become a liability?为什么要在厂商 SDK 之上再套一层自己的 provider 接口?什么时候这层反而是负担?
Common in ChinaCommon overseasBasic#provider-abstraction#architectureHow to reason about it · think before answering
- The screen is whether you have ever actually swapped a vendor. Answering only decoupling and easy replacement is what everyone says; the signal is naming what the layer buys and what it costs.
- How to break it down: ask what you lose without the layer. Three concrete things — offline runnability (you can only stub when network egress is funneled into one place), multi-vendor coexistence (business code expresses an action, not one vendor's four-step flow), and metering (every call's cost must be recorded in exactly one place).
- Then place the abstraction: define it by business action, not by the vendor's HTTP request. Submit, poll, retrieve, download for an async video job is one generate to the caller; leaking those four steps upward defeats the purpose.
- Conclusion and cost: the layer sands off vendor-specific capabilities, such as first-and-last-frame conditioning or structured camera parameters. The fix is not a wider interface but one optional passthrough field, so the single call site explicitly admits it is vendor-bound.
- When it is a liability: single vendor forever and no offline path. Two warning signs — adding a vendor forced a signature change across the other implementations, or a vendor-only parameter name appeared in the interface. Both mean you abstracted the least common multiple of vendor features.
- Likely follow-up: why not just use an aggregation gateway or SDK? You still need your own interface, because aggregators normalize protocols but not your on-disk artifact contract or your cost ledger.
分析过程 · 先想清楚再作答
- 这题在筛「有没有真的换过一次厂商」。只答「解耦、方便替换」的人,说的是一句所有人都会说的话,区分度在于你能不能给出「这层带来了什么、又赔上了什么」的具体清单。
- 怎么拆:先问自己「如果不套这层,哪些能力会散掉」。答案有三样,而且都能落到具体文件上——离线可跑(网络出口收敛到一处才可能打桩)、多厂商并存(业务代码写的是动作而不是某家的四步流程)、计量收口(每次调用的花费必须有唯一一处记账)。
- 接着说抽象的位置:接口要按业务动作定义,不按厂商的 HTTP 请求定义。异步视频任务的提交、轮询、取件、下载四步,对业务代码来说是一个 generate;把这四步漏到业务层,抽象就白做了。
- 结论与代价:这层会磨掉各家的独有能力(某家支持首尾帧、某家支持结构化运镜参数)。正确处理不是把接口撑大,而是留一个可选透传字段,让需要它的那一处显式承认自己绑定了某一家。
- 什么时候是负担:你只会用一家、也永远不会离线跑的时候;以及出现两个信号时——为加一个厂商改了接口签名让另外三个实现跟着改,或者接口里出现了只有一家有的参数名。这两个信号说明抽象抽在了厂商能力的最小公倍数上,位置错了。
- 可预期的追问:那要不要直接用某个统一网关或聚合 SDK?可以,但你仍然需要自己的接口,因为聚合层解决的是协议差异,解决不了你自己的落盘契约与记账口径。
Key points
- Name three concrete reasons: offline runnability, multi-vendor coexistence, and a single metering point
- Define the interface by business action; submit-poll-retrieve-download stays inside the implementation
- Put the output file path in the contract, because vendor image and video URLs are short-lived temporary links
- The cost is losing vendor-specific features; handle it with one optional passthrough field, not a fatter interface
- Two signs you abstracted wrong: adding a vendor changes the signature, or a vendor-only parameter leaks into the interface
答题要点
- 三个理由要说具体:离线可跑、多厂商并存、计量收口,每一个都对应一处真实代码
- 接口按业务动作定义,异步任务的提交轮询取件下载四步必须关在实现里
- 把落盘路径写进接口契约,因为厂商返回的图片与视频链接都是会失效的临时链接
- 代价是磨掉独有能力,用可选透传字段处理,而不是撑大公共接口
- 两个「抽错了」的信号:加厂商要改签名、接口里出现厂商专有参数名
What does modeling a multi-step generation pipeline as a task graph buy you over a chain of sequential awaits, and what does it cost?把一条多步生成流程建成任务图,比一串顺序 await 多拿到了什么?代价是什么?
Common in ChinaCommon overseasIntermediate#task-graph#pipeline-designHow to reason about it · think before answering
- The question is what you gain, not what a DAG is. Reciting the definition scores nothing; name three capabilities the sequential version cannot have, each with a concrete scenario.
- Break it down by inverting the three pains of sequential code. First, parallelism is expressed by the graph itself — voice-over depends only on the lines, yet a sequential run queues it behind forty video jobs. Second, resumability — each node writes artifacts to a fixed path, so shot 37 failing does not destroy the first 36. Third, observability — you can say which node is stuck, not merely that some await is pending.
- Add the higher-signal point: cycle detection. Topological sort throws when dependencies form a cycle, and that is the only thing enforcing the acyclic part. Without it, a wrong dependency silently skips a step or reorders execution, which is painful to debug.
- Conclusion and cost: a task graph is not free. Every node needs a declared input and output artifact set, otherwise the graph is decorative. That artifact contract is also the precondition for idempotency and resume later on.
- Likely follow-up: should you adopt a workflow engine instead? Judge by node count and failure rate — worth it at a dozen-plus nodes with high failure and human review; for three to five nodes a hand-written graph plus topological sort is cheaper than another system to operate.
分析过程 · 先想清楚再作答
- 这题的题眼在「多拿到了什么」,不在「什么是 DAG」。背出有向无环图定义的人拿不到分,答对的人会给出三样顺序版拿不到的能力,并各配一个具体场景。
- 怎么拆:把顺序版的三个痛点倒过来说。第一,并行的可能性被图结构直接表达——配音只依赖台词、和画面无关,顺序版里它却要排在四十次视频生成后面。第二,有断点——每个节点的产物落在磁盘固定位置,第三十七个镜头失败时前三十六个还在。第三,可观测——你能回答「现在卡在哪个节点」,顺序版只能回答「卡在某个 await」。
- 补一条区分度更高的:环检测。拓扑排序在发现依赖成环时抛错,这是「无环」两个字唯一的执行者;没有它,依赖写错只会表现成漏跑一步或者顺序错乱,非常难查。
- 结论与代价:任务图不是免费的,你必须为每个节点定义清楚输入产物与输出产物,否则它只是一张漂亮的依赖声明。这份产物契约同时也是后面做幂等与断点续跑的前提。
- 可预期的追问:那是不是应该直接上工作流引擎?判据是节点数与失败率——十几个节点、失败率高、需要人工介入时才值得;三五个节点的流程用一张手写的图加拓扑排序就够,引入引擎反而多一套要运维的东西。
Key points
- Three things sequential code cannot give: parallelism expressed by structure, resumability after failure, and knowing which node is stuck
- Topological sort also detects cycles, the only mechanism enforcing the acyclic property
- The cost is declaring input and output artifacts per node; without that the graph is decorative
- That artifact contract is the precondition for idempotency and resume
- Adopt a workflow engine based on node count and failure rate; a hand-written graph wins for three to five nodes
答题要点
- 三样顺序版拿不到的:并行由图结构表达、失败后有断点、能说清卡在哪个节点
- 拓扑排序顺带做环检测,这是「有向无环」里「无环」的唯一执行者
- 代价是必须为每个节点声明输入产物与输出产物,否则图只是装饰
- 这份产物契约同时是后续做幂等与断点续跑的前提
- 上不上工作流引擎按节点数与失败率判断,三五个节点手写图更划算
For a system that depends heavily on paid third-party generation APIs, how do you make it developable and testable without keys — and how do you prove the offline mode is not fooling you?一个重度依赖付费第三方生成接口的系统,怎么做到没有密钥也能开发和测试?怎么证明这套离线模式没有骗自己?
Common in ChinaCommon overseasDeep dive#offline-testing#test-strategyHow to reason about it · think before answering
- All the signal is in the second half. Everyone says mock it; only people who have done it can say how they prove the mock is honest, because most mocks only guarantee the program does not crash.
- Break it down by first fixing where the stub goes: only at the network egress, inside each provider's one method. No environment check belongs in business code — the moment business logic branches, offline runs a different program and your testing says nothing about production.
- Then fix the quality of the stub: the offline implementation should emit artifacts of the real shape rather than a constant. For a media pipeline, actually generate placeholder files locally (solid-color frames, a test pattern with an audio track, a sine-wave clip); for retrieval, return well-formed fake documents; for streaming, emit chunks with realistic pacing. The point is to force downstream parsing, state machines, and timeline math to execute.
- The one test that proves it is honest: change an input and the output must change. Placeholder color tracks the shot description, placeholder audio length tracks the line length, total runtime tracks shot count. If every input yields identical artifacts, you only verified that nothing crashed.
- State the payoff too: a real run costs tens of minutes and real money, so an off-by-one takes half an hour to surface. Offline collapses that loop to seconds, which is what makes continued refactoring affordable. This is an engineering requirement, not a toy.
- Likely follow-up: who then covers the real path? Layer it — offline covers business logic and regression, while a small set of smoke tests exercises the real vendors on a schedule. They verify different things and do not substitute for each other.
分析过程 · 先想清楚再作答
- 这题的区分度全在后半句。前半句人人都会答「打 mock」,能答出「怎么证明它没骗自己」的才是真做过——因为绝大多数 mock 的实际效果是「保证程序不崩」,而不是「保证逻辑正确」。
- 怎么拆:先定桩的位置。桩只打在网络出口上,也就是每个 provider 的那一个方法里;业务代码里一个环境变量判断都不该有。一旦业务逻辑分叉,离线跑的就是另一个程序,你验的东西和线上没关系。
- 再定桩的质量:离线实现要产出真实形态的产物,而不是返回一个常量。做媒体流水线就用本地工具真的生成占位文件(纯色图、测试画面加音轨、正弦波音频),做检索就返回结构完整的假文档,做流式就按节奏一段段吐。目的是让下游的解析、状态机、时间轴计算真的被执行一遍。
- 证明它没骗自己的判据只有一条:**改一个输入,输出必须跟着变**。占位图的颜色随镜头描述变、占位音频时长随台词字数变、总时长随分镜数变——这说明中间的业务逻辑跑过了。如果换什么输入产物都一样,你验的只是没崩。
- 还要说收益:一次真跑几十分钟、上百块钱,一个下标写错就要等半小时才看得到;离线把这个反馈循环压到几秒,团队才会愿意持续重构这段代码。这是工程要求,不是玩具。
- 可预期的追问:那真实路径谁来保证?答案是分层——离线模式覆盖业务逻辑与回归测试,真实路径靠少量的冒烟用例定期跑,两者验的是不同的东西,不能互相替代。
Key points
- Stub only at the network egress; business code contains no offline branch
- The offline implementation must emit real-shaped artifacts so downstream parsing, state machines, and timeline math actually run
- The single acceptance test is that changing an input changes the output; otherwise you only verified it did not crash
- The payoff is collapsing a tens-of-minutes, real-money feedback loop into seconds, which is what makes refactoring affordable
- Cover the real path with a small scheduled smoke suite; it verifies something different from the offline mode
答题要点
- 桩只打在网络出口,业务代码里不出现任何离线判断分支
- 离线实现要产出真实形态的产物,让下游解析、状态机、时间轴计算真的执行
- 唯一的验收判据是「改一个输入,输出跟着变」,做不到就只验了没崩
- 收益是把几十分钟上百块的反馈循环压到几秒,团队才敢持续重构
- 真实路径靠少量定期冒烟用例覆盖,与离线模式验的是不同的东西
D2 The Script Agent: Turning a Single Sentence Into Structured Data — Character Cards, Scenes, and Shots
How do you get a model to emit valid structured data reliably, and what do you do when schema validation fails?怎么让模型稳定输出合法的结构化数据?schema 校验失败时你会怎么处理?
Common in ChinaCommon overseasBasic#structured-output#schema-validationHow to reason about it · think before answering
- The real question is the second half. Answering only use JSON mode signals you have never run this in production, because all the work happens after validation fails.
- Lay out three paths: prompt constraints plus local validation; a vendor's JSON mode or structured-output parameter; or defining the data structure as a tool's parameter schema. Vendor support and field names differ, so the latter two bind that code to one vendor.
- State the selection rule: cross-vendor or offline-capable means path one, paying with your own JSON extraction and validator; single-vendor and success-rate-driven means use their structured output. Extraction must handle code fences and surrounding chatter — parsing the whole reply directly breaks often.
- Handle failure as a ladder, not just a retry: feed the path-annotated issues back and ask it to fix only those (more effective than upgrading the model); then degrade to a minimal required-fields-only structure; then fail the round and persist the artifact for a human — never swallow the error and return an empty array.
- High-signal point: validate in two layers. Type and range checks catch malformed data but not wrong references — a nonexistent scene id or a duplicate shot number passes typing and explodes downstream. Referential integrity needs its own pass.
- Likely follow-up: how many retries? Two. The first covers a disobedient model; if it still fails with concrete issues in hand, the prompt or the schema itself is wrong and more retries just buy the same error.
分析过程 · 先想清楚再作答
- 这题的题眼在后半句。前半句答「用 JSON 模式」就结束的人,等于说自己没在生产里跑过——真正的活儿全在校验失败之后。
- 先把三条路摆开:提示词约束加本地校验;厂商提供的 JSON 模式或结构化输出参数;把数据结构定义成工具的参数 schema 让模型去调。各家对后两条的支持程度和字段名都不一样,选它就等于把这段代码绑在某一家上。
- 给出选择依据:要跨厂商、要能离线跑,就选第一条,代价是自己写抠 JSON 与校验;只服务一家且追求成功率,就用那一家的结构化输出。抠 JSON 这一步必须处理围栏与前后寒暄,直接解析整段回复在真实模型上很容易炸。
- 校验失败的处理是一条阶梯,别只答重试:把带路径的问题原样喂回去让它只修这些(比换更大的模型有效);仍不过就降级到只要必填字段的最小结构;再不过就整轮失败并留档,让人来看,而不是吞掉异常返回一个空数组。
- 还有一条区分度很高:校验要分两层。判类型与范围只能挡住格式错,挡不住写错对象——引用了不存在的场景 id、镜号重复,这类稿子能通过类型检查,然后在下游某一步才爆。引用完整性必须单独查一遍。
- 可预期的追问:重试几次合适?两次。第一次是模型没听话,第二次带着具体问题还改不对,说明是提示词或 schema 本身有问题,再重试只是花钱买同一个错误。
Key points
- Three paths: prompt plus local validation, vendor structured output, or tool parameter schema — the latter two bind you to a vendor
- JSON extraction must handle code fences and surrounding prose; never parse the whole reply directly
- Failure handling is a ladder: feed back path-annotated issues, degrade to a minimal structure, then fail the round and persist for a human
- Validate in two layers — types and ranges, then referential integrity and id uniqueness
- Cap retries at two; beyond that the prompt or schema is wrong, not luck
答题要点
- 三条路:提示词加本地校验、厂商结构化输出参数、工具参数 schema,后两条会绑定厂商
- 抠 JSON 要处理围栏与前后寒暄,不能直接解析整段回复
- 失败处理是阶梯:带路径的问题喂回去只修这些、降级到最小结构、整轮失败留档给人
- 校验分两层,类型与范围之外必须单独查引用完整性与 id 唯一性
- 重试上限两次,再不过说明是提示词或 schema 的问题,不是运气问题
In a generator-plus-reviewer loop, how do you define convergence so it does not burn budget indefinitely?生成加评审这种双角色循环,收敛条件该怎么定才不会一直烧钱?
Common in ChinaCommon overseasDeep dive#agent-loop#cost-controlHow to reason about it · think before answering
- This screens whether you have ever made such a loop actually terminate. Answering only set a max round count scores nothing — that prevents an infinite loop, it is not convergence design. The signal is naming three exits plus how the reviewer itself is built.
- Start with the reviewer: it should not be one model but two layers. Machine-checkable defects (missing fields, out-of-range numbers, length limits, invalid references) go to code; only the judgment calls go to the model. This decides score stability — a pure-model reviewer can swing by ten-plus points on the same draft, and then convergence is meaningless.
- Then the three exits: stop on threshold (the threshold means good enough, not perfect — chasing the last few points costs far more than it returns); stop at the round cap, handing back the highest-scoring draft rather than the last one, because review scores fluctuate; and escalate to a human once only judgment-call issues remain, since a model reviewing and revising itself just circles.
- Also cover the scoring weights: hard defects should dominate, say seventy percent, with the model's soft score at thirty. Otherwise one flattering model review outweighs five real field errors and the loop declares success on round one.
- Conclusion and cost: all three exits need parameters, and parameters need empirical tuning. Too high a threshold burns every round; too low ships an unusable draft. Plot the score curve before shipping and confirm it rises monotonically.
- Likely follow-up: how do you know it is improving rather than oscillating? Track the hard-defect count — it is deterministic, while the score jitters. If hard defects do not fall, the writer is not acting on feedback, and the fix is feedback granularity: tag each issue with a category so the model knows which class to repair.
分析过程 · 先想清楚再作答
- 这题在考「你有没有让这种循环真的停下来过」。只答「设一个最大轮数」拿不到分,那只是防死循环,不是收敛设计。区分度在于你能不能说出三个出口以及评审本身该怎么构造。
- 先拆评审:评审不该是一个模型,而是两层——能被程序判定的硬伤用代码查(字段缺失、数值越界、长度超限、引用不合法),程序判不了的软伤才交给模型。这一步决定了分数稳不稳定:全交给模型,同一份稿子两次评分能差十几分,循环就没有收敛可言。
- 再说三个出口:达标就停(阈值是「够用」不是「完美」,追最后几分成本远高于收益);到轮数上限就停,而且要交出历史最高分那一稿而不是最后一稿,因为评审有波动;剩下的问题全是软伤时转人工,因为让模型自己评自己改只会原地打转。
- 还要说计分方式:硬伤应该占大头(比如七成),软分占小头。否则模型一句好评就能盖过五条实打实的字段问题,循环会在第一轮就假装达标。
- 结论加代价:三个出口都需要参数,而参数必须实测调。阈值高了轮数用满,低了稿子不能看;上限大了烧钱,小了永远差一口气。上线前要把分数曲线画出来看它是不是单调上升。
- 可预期的追问:怎么知道循环真的在变好而不是在抖动?看硬伤条数,它是确定性的;分数会抖,硬伤条数不会。硬伤降不下去就说明写手根本没在按意见改,问题出在意见的粒度上——意见要带分类标签,模型才知道该改哪一类。
Key points
- Split the reviewer: code judges hard defects, the model judges only judgment calls — otherwise scores are unstable and nothing converges
- Three exits: stop on threshold, stop at the round cap returning the best draft, escalate to a human when only soft issues remain
- Weight hard defects heavily so a flattering model review cannot mask real field errors
- Tag each review issue with a category so the writer repairs one class at a time
- Measure convergence by hard-defect count, not score — the score jitters, the count does not
答题要点
- 评审分两层:硬伤用代码判,软伤才交给模型,否则分数不稳定、循环无从收敛
- 三个出口:达标就停、到轮数上限交历史最高分那一稿、只剩软伤时转人工
- 计分让硬伤占大头,避免模型一句好评盖过实打实的字段问题
- 评语必须带分类标签,写手才能只改那一类,改稿才是收敛的
- 观测收敛看硬伤条数而不是分数,分数会抖、硬伤条数是确定的
To keep character definitions consistent across many episodes, where do you store that state and how do you use it?多集内容要保持人物设定一致,你会把这份设定放在哪、怎么用?
Common in ChinaCommon overseasIntermediate#state-management#consistencyHow to reason about it · think before answering
- The crux is that models have no memory. Answering just concatenate previous episodes into the context invites a fatal follow-up: context grows linearly with episode count, so by episode five you pay repeatedly for four full episodes, and the model may still miss details.
- Break it down by separating what is invariant across episodes from what is recomputed each time. Invariant: the world, each character's appearance, personality, voice id, and a few hard rules. Recomputed: scenes and shots. Extract the invariant part into its own file and load it verbatim before generating each episode.
- Add the commonly missed point: the fields in that file are not only lore, they are downstream input parameters. Appearance text goes straight into image prompts, the voice id goes straight into the speech API. Keeping them beside the name means consistency is solved in one file rather than restated in three places.
- Choose the storage boundary by write frequency: the profile is written once and read many times, while the shot list is rewritten on every run. Mixing lifetimes in one file makes it impossible to rerun one episode without disturbing the others.
- Conclusion and cost: the profile itself can drift. Change a character's appearance mid-season and previously generated assets no longer match, so version the profile and include that version in the asset cache key — editing the profile then invalidates exactly the affected assets. That is only possible because it lives on its own.
- Likely follow-up: should you use a vector store? Usually not. Cross-episode canon is small, structured, and must be injected in full; retrieval risks dropping the one line that matters. Retrieval fits large corpora where only a few relevant items are needed.
分析过程 · 先想清楚再作答
- 这题的题眼是「模型没有记忆」。答成「把前一集的输出拼进上下文」的人会被追问到崩——上下文会随集数线性膨胀,第五集时你在为前四集的全文反复付费,而且模型仍然可能漏读。
- 怎么拆:先分辨哪些是「跨集不变」的,哪些是「每集重算」的。不变的是世界观、人物外貌、性格、音色与几条硬规则;每集重算的是场景与分镜。把不变的那部分抽成单独的档案文件,每一集生成前原样读进去。
- 接着说一个容易被忽略的点:档案里的字段不只是设定,还是**下游的输入参数**。外貌描述要原样进图像提示词,音色 id 要原样进语音接口。所以它们必须和名字放在同一份档案里,一致性问题才是在一个文件里解决的,而不是散在三处各写一遍。
- 存放位置的判据是写入频率:档案一次生成、多次读取,分镜每跑一次就重写。生命周期不同的数据放同一个文件,你就没法只重跑一集而不动其他集。按写入频率切分文件,是这类流水线最省事的一条习惯。
- 结论与代价:档案本身也会漂——中途改了人物外貌,之前生成的资产就对不上了。所以档案要有版本,且资产的缓存键要包含档案版本,改档案等于让相关资产失效。这条也是把它单独存放才做得到的。
- 可预期的追问:那要不要上向量库做检索?多数情况下不需要。跨集共享的设定是**有限的、结构化的、必须全量注入的**,检索反而可能漏掉关键一条。检索适合的是「素材库很大且只需要相关几条」的场景。
Key points
- Models are stateless; cross-episode consistency comes from an external profile, not from stuffing prior episodes into context
- Split by invariant versus recomputed: world and character profiles persist, scenes and shots are regenerated per episode
- Appearance text and voice id are downstream input parameters, so they belong beside the character's name
- Split files by write frequency — a read-mostly profile versus a rewritten shot list — or you cannot rerun one episode alone
- Version the profile and fold that version into the asset cache key so edits invalidate exactly the affected assets
答题要点
- 模型没有记忆,跨集一致性靠外部档案而不是把前几集拼进上下文
- 按「跨集不变」与「每集重算」切分:世界观与人物卡是档案,场景与分镜每集重来
- 档案里的外貌与音色 id 同时是下游的输入参数,所以必须和名字放在一起
- 按写入频率切分文件,档案读多写少,分镜每次重写,混在一起就没法只重跑一集
- 档案要有版本并进资产缓存键,改设定才能精确地让相关资产失效
D3 Character Consistency: Character Sheets, Reference Images, and Style Locking — Keeping the Same Person the Same Person in Every Shot
Where does the character consistency problem in image generation come from, and what engineering mitigations exist, with what trade-offs?生成模型的角色一致性问题是怎么来的?工程上有哪几种缓解手段,代价分别是什么?
Common in ChinaCommon overseasBasic#image-generation#consistencyHow to reason about it · think before answering
- The differentiator is your first sentence. Saying 'the prompt wasn't detailed enough' reads as a user, not an engineer; the answer they want is that each request is an independent sample with no memory across calls.
- Follow the mechanism: a prompt only constrains the degrees of freedom you actually wrote down, and everything unwritten gets re-sampled — while face recognizability lives exactly in the details text cannot exhaust.
- Present the mitigations in three layers by what each one actually locks: a prompt template locks style and framing at near-zero cost; a fixed seed locks reproducibility for one identical prompt and stops helping the moment the prompt changes; a reference image locks the face, but only one per request, so two faces in one frame cannot both be locked.
- The trade-off discussion is where candidates separate: using a reference image means you must first produce a base image, which forces a human 'pick the reference sheet' step into an otherwise unattended pipeline.
- Volunteer the counter-intuitive rule: every derived image must reference the same base image, never the previous one. Chaining references accumulates drift, and by the fifth image it is a different person.
- Expect the follow-up: what if consistency still fails? The answer is cinematography — split two-character frames into reverse-angle singles and push secondary characters to wider shots, working around the API's limits with shot design.
分析过程 · 先想清楚再作答
- 这题的区分度在第一句。答「提示词写得不够细」就掉到了使用者视角;面试官想听的是「模型每次请求都是独立采样、没有跨请求记忆」这个机制层面的原因。
- 顺着机制往下推就有了完整答案:提示词只约束了你写出来的那些自由度,没写的部分每次重新掷一遍;而人脸的辨识度恰好集中在脸型、眼距、鼻梁这些你没法用文字穷尽的细节上。
- 手段按「锁得住什么」分三层说,不要混在一起:提示词模板锁风格与构图,成本几乎为零;随机种子锁同一提示词的可复现性,换提示词即失效;参考图锁人脸,但每次请求只能带一张,双人同框锁不了两个人。
- 代价这一段才是拉开差距的地方:参考图要求你先有一张基准图,于是流程里必须插入一次「定妆并由人挑一张」的环节,这是整条自动化流水线上少数值得保留的人工卡点。
- 还要主动说一个反直觉的做法:派生图必须都参考同一张基准图,不能参考上一张。参考上一张会让偏差逐张累积,第五张已经不是同一个人了。
- 可以预期的追问:一致性做不到怎么兜底?答案是改镜头语言——把双人同框拆成正反打的单人镜头、次要角色用更远的景别,用拍法回避接口能力的边界。
Key points
- The root cause is that each request is an independent sample with no cross-request memory, so unconstrained degrees of freedom get re-rolled
- A prompt template locks style and framing at near-zero cost but cannot lock facial detail
- A fixed seed locks reproducibility for one identical prompt and stops helping once the prompt changes
- A reference image locks the face, but you must first produce a base image and only one reference is allowed per request
- Derive every variant from the same base image rather than chaining off the previous one, or drift accumulates image by image
答题要点
- 根因是模型每次请求独立采样、没有跨请求记忆,提示词没约束到的自由度会被重新掷一遍
- 提示词模板锁风格与构图,成本几乎为零,但锁不住五官
- 随机种子锁的是同一提示词的可复现性,提示词一变就失效
- 参考图锁人脸,代价是必须先有基准图,且每次请求只能带一张,双人同框锁不了两个人
- 派生图统一参考同一张基准图,不要链式参考上一张,否则偏差会逐张累积
Does fixing the random seed solve character consistency? What does a seed actually lock?固定随机种子能解决角色一致性吗?它到底锁住了什么?
Common in ChinaCommon overseasIntermediate#image-generation#reproducibilityHow to reason about it · think before answering
- This is a yes/no trap dressed as a concept question; answering 'yes' ends it. The hinge is 'what does it actually lock' — they are testing whether you separate reproducibility from consistency.
- Define it first: a seed is the random starting point of sampling. With the model, prompt and other parameters unchanged, the same seed returns the same image, so what it locks is reproducibility.
- Then explain why that is not enough here: every shot has a different prompt because action, scene and shot size all change. Change the prompt and the sampling path changes with it, so the same seed yields a different person. A seed is a reproducibility switch, not a consistency switch.
- Do not dismiss it though. It earns its place twice: single-variable debugging, where you change one word and watch the image move; and stacked with a reference image, where the reference holds the face and the seed holds the remaining degrees of freedom so a whole set looks shot on the same day.
- One production note: for a seed to actually reproduce anything, turn the prompt optimizer off. It defaults to on, rewrites your prompt server-side, and you never see the rewrite — which destroys reproducibility.
- Expect the follow-up: is seed semantics the same across vendors? No guarantee — switching vendor or even model version can make the same seed produce something else, which is one more reason to keep a provider abstraction layer.
分析过程 · 先想清楚再作答
- 这是一道判断题伪装成的概念题,答「能」直接出局。题眼是「到底锁住了什么」——面试官在测你有没有把复现和一致这两件事分开。
- 先给定义:seed 是采样的随机起点。在模型、提示词、其余参数都不变的前提下,同一个 seed 会给出同一张图,所以它锁住的是**可复现性**。
- 再说为什么在短剧场景里不够用:每一镜的提示词天然不同,动作、场景、景别都在变。提示词一变,采样路径就换了,同一个 seed 出来的是完全不同的人。所以 seed 是复现开关,不是一致性开关。
- 但不要把它说成没用。它在两个地方非常值钱:调试时做单变量对照,只改一个词看画面怎么变;以及跟参考图叠加使用,参考图管脸,seed 管其余自由度的采样起点,两者一起才让整组图像同一天在同一个棚里拍的。
- 生产视角补一句:想让 seed 真的可复现,必须把提示词优化开关关掉。那个开关默认是开的,它会在服务端改写你的提示词,改写结果你看不到,可复现性也就没了。
- 可以预期的追问:那不同厂商的 seed 语义一样吗?答案是不保证,换厂商甚至换模型版本都可能让同一个 seed 出别的图,所以 seed 不能作为跨厂商的一致性依据——这也是要有一层 provider 抽象的原因之一。
Key points
- No. A seed locks reproducibility: same model, same prompt, same other parameters plus same seed returns the same image
- Every shot in a drama has a different prompt, and a changed prompt voids the seed, so it is not a consistency mechanism
- Its real value is single-variable debugging, and stacking with a reference image — the reference holds the face, the seed holds the rest
- For a seed to reproduce anything you must disable the server-side prompt optimizer, which is on by default and rewrites your input
- Seed semantics do not carry across vendors or model versions, so a seed cannot underpin cross-provider consistency
答题要点
- 不能。seed 锁的是可复现性:模型、提示词与其余参数都不变时,同一个 seed 给出同一张图
- 短剧每一镜的提示词天然不同,提示词一变 seed 就失效,所以它不是一致性手段
- 它真正的用处是单变量调试,以及与参考图叠加——参考图管脸,seed 管其余自由度的采样起点
- 要让 seed 可复现,必须关掉服务端的提示词优化开关,它默认开启且会改写你的输入
- seed 语义不跨厂商也不跨模型版本,不能作为跨 provider 的一致性依据
How would you design the cache key for reusing generated assets so that you save money without serving the wrong asset?生成类资产要做复用,缓存键你会怎么设计,才能既省钱又不会串戏?
Common in ChinaCommon overseasDeep dive#caching#cost#image-generationHow to reason about it · think before answering
- This question is about two kinds of cache error with wildly asymmetric cost. A miss only costs money; a wrong hit puts last episode's prop into this one. The first is a number, the second is a content incident.
- The derivation is one sentence: the key must be computed from every input that changes the artifact, and nothing else. Include something irrelevant, like the output path, and one directory refactor invalidates everything and you pay again; omit something relevant, like the prompt, and a changed description silently serves the old image.
- Concretely, hash the asset kind, the owning entity id, the variant name, the full prompt, the reference image identity and the seed. Take a short digest as the id, and store those fields verbatim in the metadata so any artifact can be reproduced.
- Then name the boundaries yourself: does the model id and version belong in the key? Yes. What if the style template changes? It is part of the prompt, so it invalidates everything by construction — which is why templates should carry a version number, letting you choose the blast radius.
- One more production note: never cache failed generations, or you will faithfully reuse an empty result that safety review rejected. Cache hits also belong in the cost ledger, flagged as hits, otherwise you cannot report how much caching saved.
- Expect the follow-up: should the cache expire? Content assets usually should not expire on time; invalidate explicitly by version instead, because a time-based expiry regenerates a whole episode at the least convenient moment.
分析过程 · 先想清楚再作答
- 这题考的是缓存的两类错误,而且两类的代价完全不对称。少命中只是多花钱,错命中会把上一集的道具塞进这一集——前者可量化,后者是内容事故。
- 推导链只有一句:**键必须由所有会改变产物的输入算出来,一项不多一项不少。** 多算了不该算的(比如输出路径),改一次目录结构缓存全部失效,白花一遍钱;少算了该算的(比如提示词),换了描述还命中老图,就是串戏。
- 落到这个场景,参与哈希的是:资产类别、归属对象、变体名、完整提示词、参考图标识、随机种子。用 sha1 之类取个短摘要当 id,元数据里再把这几项原样存一份,出问题能照着复现。
- 然后主动把边界说清楚,这是加分项:模型 id 与版本要不要进键?要。风格模板改了怎么办?它是提示词的一部分,进键之后天然全部失效——所以模板要谨慎改,或者给它一个版本号,让你能决定失效的范围。
- 生产视角还有一条:失败的生成不要写进缓存,否则你会稳定复用一张被审核拦下的空结果。命中缓存的那条路径也要记台账并标成命中,不然你算不出缓存到底省了多少钱。
- 可以预期的追问:缓存要不要过期?答案是内容型资产通常不设时间过期,而是靠版本号显式失效;时间过期会在你毫无预期的时候让一整集重新生成一遍。
Key points
- Derive the key from everything that changes the artifact: asset kind, owner id, variant, full prompt, reference image identity, seed, plus model id and version
- Keep output paths and filenames out of the key, or one directory refactor invalidates the whole cache and you pay twice
- Omitting inputs like the prompt causes wrong hits, which are content incidents and far costlier than misses
- Store the hashed fields verbatim in metadata so any artifact is reproducible, and never cache failed generations
- Record cache hits in the cost ledger flagged as hits, and invalidate explicitly by version rather than by time
答题要点
- 键由所有会改变产物的输入算出:资产类别、归属对象、变体名、完整提示词、参考图标识、随机种子,再加模型 id 与版本
- 不要把输出路径或文件名放进键,改目录结构会让缓存整体失效,白付一遍钱
- 少算提示词这类输入会导致错命中,那是内容事故,代价远高于少命中
- 元数据里原样保存参与哈希的各项,出问题能复现;失败的生成不写缓存
- 命中缓存也要记台账并标成命中,否则算不出缓存省了多少;失效靠显式版本号而不是时间过期
D4 From Shot to Footage: Image-to-Video, Polling Async Tasks, and Retrying Failures
You are asked to implement a client for an asynchronous generation task. Which failure cases would you cover?让你实现一个异步生成任务的客户端,你会考虑哪些失败情况?
Common in ChinaCommon overseasIntermediate#async-task#error-handlingHow to reason about it · think before answering
- The differentiator here is coverage, not code. Answering 'wrap it in try/catch and retry' usually means you have never run this kind of API in production.
- Describe the shape first so the failures have somewhere to hang: submit and get an id, poll for status, retrieve a URL, download to disk — four steps, four families of failure.
- Then enumerate: at submit, rate limiting, auth failure, invalid parameters, content moderation; at poll, the query endpoint rate limiting you, a status that never advances, or a terminal failure; at retrieve, a valid id that yields no URL; at download, an expired link, a stream cut halfway, a disk write error.
- Then the two that span the whole flow: timeout and process restart. A timeout is not a failure, it is 'I don't know' — you must look up the idempotency key before resubmitting. A restart means in-memory task ids are gone, so the id has to be persisted before or immediately after the request, or you will have paid-for tasks you can never reclaim.
- Close with a line that shows judgment: of the four steps, only the download is safely retryable on its own; a retry at any other step can create a new billable job.
- Expect the follow-up: if the vendor offers callbacks, do you still poll? Yes. Callbacks get lost to restarts, network blips and unreachable endpoints, so the standard is callback-first with a low-frequency sweep for tasks stuck without a terminal state.
分析过程 · 先想清楚再作答
- 这题的区分度不在代码,在你能列出多少种失败。只答「加个 try catch 和重试」的人,通常没在生产上跑过这类接口。
- 先把任务的形状说清楚,失败点才有地方挂:提交拿标识、轮询查状态、取件换地址、下载落盘,四步是四类不同的失败。
- 然后逐步列:提交阶段有限流、鉴权、参数无效、内容审核;轮询阶段有查询接口自己限流、状态一直不前进、任务返回失败终态;取件阶段有标识存在但取不到地址;下载阶段有地址过期、下到一半断流、写盘失败。
- 接着说横跨全程的两类:超时与进程重启。超时的关键在于它不是失败而是「不知道成没成」,必须先按幂等键查一遍再决定要不要重提;进程重启意味着内存里的任务标识没了,所以标识必须先落盘再发请求,否则你会有一批花了钱却找不回来的任务。
- 最后给一句能体现工程判断的话:这四步里只有下载是可以无脑重试的,其余每一步的重试都可能产生一次新的计费。
- 可以预期的追问:厂商提供回调了还需要轮询吗?需要。回调会因为服务重启、网络抖动、地址不可达而丢失,生产上的标准做法是回调为主、低频轮询兜底扫描长时间没有终态的任务。
Key points
- Break failures down by the four steps: submit (rate limit, auth, invalid params, moderation), poll (query rate limit, stalled status, terminal failure), retrieve (no URL), download (expired link, cut stream, disk error)
- A timeout means unknown, not failed: look up the idempotency key for an existing artifact before resubmitting, or you pay twice
- Persist the task id promptly so in-flight tasks survive a process restart
- Only the download is safely retryable on its own; retries at the other steps can create new billable jobs
- Keep a low-frequency polling sweep even when callbacks exist, because callbacks get lost
答题要点
- 按四步拆失败:提交(限流、鉴权、参数无效、内容审核)、轮询(查询限流、状态停滞、终态失败)、取件(拿不到地址)、下载(地址过期、断流、写盘失败)
- 超时不是失败而是状态未知,重试前必须先按幂等键查一遍已有产物,否则会为同一个任务付两次钱
- 任务标识要及时落盘,进程重启后才能把在途任务认回来
- 四步里只有下载可以无脑重试,其余每一步的重试都可能产生新的计费
- 有回调也要保留低频兜底轮询,回调会丢
How do you choose a polling interval, and why is a fixed interval a bad default?轮询间隔怎么定?为什么不能一直用固定间隔死等?
Common in ChinaCommon overseasBasic#async-task#backoffHow to reason about it · think before answering
- This looks like a giveaway, but it has three layers and only the first one is obvious. They want to know whether you have actually written this loop.
- Layer one is cost: a task queued for five minutes polled every second is three hundred wasted requests. The query endpoint has its own rate limit, so you can throttle yourself and then misread 'rate limited' in the logs as a generation problem.
- Layer two is capping the backoff: multiply without a cap and you end up polling every few minutes, sleeping long after the task finished. Pick the cap from what extra wait a user tolerates — usually in the ten-to-twenty-second range.
- Layer three is where people actually get it wrong: the timeout check belongs before the sleep, and the test is whether sleeping would cross the deadline. Sleeping first means overshooting the budget by a full interval, which at a twenty-second backoff is twenty wasted seconds.
- Mention ordering too: check terminal states before the timeout. Discarding a task that just succeeded on the final poll means paying for an artifact you then throw away.
- Expect the follow-up: how do you pick the initial interval? From the typical duration of this class of task, a bit above a tenth of it; and the very first poll can be delayed slightly, since a just-submitted task is almost never done.
分析过程 · 先想清楚再作答
- 这是一道送分题,但它有三个层次,只答出第一层拿不到高分。面试官想看的是你有没有真的写过这个循环。
- 第一层是成本:一个排队五分钟的任务,用一秒的固定间隔就是三百次无效请求。查询接口自己也有速率限制,你很可能自己把自己打到限流,然后在日志里看到「生成失败:限流」,还以为是生成接口的问题。
- 第二层是退避要封顶:只乘不封顶的话,退到后面已经是几分钟查一次,任务早就好了你还在睡。上限的选法是「用户能忍受的额外等待」,一般十几到二十秒。
- 第三层最容易写错,也是这题真正的区分点:超时判断必须放在睡觉之前,判据是「睡下去会不会越过截止时间」。先睡再判会让你在预算之外多睡整整一轮,退避到二十秒时就是白等二十秒。
- 另外提一条顺序:先判终态再判超时。任务恰好在最后一次查询里成功却被当成超时扔掉,等于付了钱还丢了产物。
- 可以预期的追问:起步间隔怎么定?按这类任务的典型耗时定,比典型耗时的十分之一略大即可;再往细说就是首次查询可以稍微延后一点,因为刚提交的任务几乎不可能立刻完成。
Key points
- A fixed interval is either too tight, wasting requests and throttling yourself, or too loose, adding dead time after completion
- Use exponential backoff starting near a tenth of the task's typical duration
- Cap the backoff, choosing the cap from the extra wait a user will tolerate
- Check the timeout before sleeping, testing whether the sleep would cross the deadline, or you overshoot the budget by a full interval
- Check terminal states before the timeout so a task that just succeeded is not discarded
答题要点
- 固定间隔要么太密造成大量无效请求并把自己打到限流,要么太疏让完成后的等待过长
- 用指数退避:从接近典型耗时十分之一的间隔起步,每轮乘一个系数
- 退避必须封顶,上限按用户能忍受的额外等待来定
- 超时判断放在 sleep 之前,判据是「睡完会不会越过截止时间」,否则会在预算之外多睡一轮
- 先判终态再判超时,避免把最后一次查询里刚成功的任务误杀
When a generation API returns a failure, how do you decide whether to retry, and what happens after the retries run out?生成类接口返回失败,你怎么判断该不该重试?重试几次之后该做什么?
Common in ChinaCommon overseasDeep dive#error-handling#retry#costHow to reason about it · think before answering
- The hinge is 'decide'. Bucketing by the leading digit of the HTTP status is the classic wrong answer, because generation APIs often return HTTP 200 with a business error code in the body.
- Give a reusable test instead of reciting a code table: ask three questions — will waiting help, will changing the input help, or does a human have to step in? They map onto three dispositions: back off and retry, fix the request, alert immediately.
- Concretely: rate limits and server errors are the first bucket and the program handles them; invalid parameters and content moderation are the second, where retrying repeats the same error and burns rate-limit budget that genuinely retryable tasks needed; auth failure and insufficient balance are the third, where retrying only delays the alert.
- Handle timeout separately — this is the line that signals experience. A timeout is unknown, not failed: the job may still be running, or may have finished. So never resubmit blindly; look up the idempotency key for an existing artifact first.
- When retries are exhausted, do three things: mark the item failed with the last error code and the exact request parameters, keep processing the rest of the batch instead of aborting it, and aggregate the failures into one readable alert rather than one per item.
- Expect the follow-up: how many retries? Scale it by unit price. The more expensive the call, the fewer automatic retries, and expensive failures should go to a human for review before being redone.
分析过程 · 先想清楚再作答
- 这题的题眼是「判断」。按状态码首位数字一刀切是最常见的错误答案,因为生成类接口的业务错误码往往和 HTTP 状态码不在一个层面上——很多厂商的失败是 HTTP 200 加一个响应体里的业务码。
- 给一条可复用的判据,比背错误码表有用:问三个问题——等一等会不会好、改输入会不会好、还是必须叫人来。三个问题对应三种处置:退避重试、修请求、立刻告警。
- 落到具体:限流和服务端故障属于第一类,程序自己扛;参数无效与内容审核属于第二类,重试一万次都是同一个错,而且会挤占限流额度让真正该重试的排不上号;鉴权失败与余额不足属于第三类,重试只会延迟告警。
- 然后单独处理超时,这是最能体现经验的一条:超时不是失败,是状态未知,对方队列里那个任务可能还在跑甚至已经成了。所以超时之后不能直接重提,要先按幂等键查一遍已有产物。
- 重试用尽之后要做三件事,缺一不可:把这一条标成失败并记下最后一次的错误码与请求参数、继续跑批次里剩下的任务不要中断、把失败清单汇总成一次可读的告警而不是每条发一次。
- 可以预期的追问:重试次数怎么定?按单价定。单价越高,允许的重试次数越少,而且高单价的失败更应该先送人复核再决定要不要重做。
Key points
- Do not bucket by the leading HTTP digit; generation APIs often hide the business error code inside an HTTP 200 body
- Use three questions — will waiting help, will changing the input help, or is a human required — mapping to back off, fix the request, alert
- Rate limits and server errors are retryable; invalid parameters and moderation blocks are not and waste rate-limit budget; auth and balance failures need an alert
- A timeout is unknown rather than failed: check the idempotency key for an existing artifact before resubmitting, or you pay twice
- When retries run out, mark the item failed with its error code and request parameters, keep the batch running, and aggregate failures into one alert; scale retry counts by unit price
答题要点
- 不要按状态码首位一刀切,生成类接口的业务错误码常常藏在 HTTP 200 的响应体里
- 判据是三个问题:等一等会不会好、改输入会不会好、还是必须叫人来,分别对应退避重试、修请求、立刻告警
- 限流与服务端故障可重试;参数无效与内容审核重试无用且会挤占限流额度;鉴权失败与余额不足必须告警
- 超时是状态未知不是失败,重试前先按幂等键查一遍已有产物,否则会重复计费
- 重试用尽后:标记失败并留下错误码与请求参数、不中断整批、把失败汇总成一次可读告警;重试次数按单价定
D5 Voiceover, Subtitles, and Audio Tracks: Multi-Character Voices, Timeline Alignment, and Subtitle Files
When the synthesized speech and the shot duration disagree, which side do you adjust, and why?语音时长和画面时长对不上,你会调哪一边?为什么?
Common in ChinaCommon overseasIntermediate#timeline#tts#pipeline-designHow to reason about it · think before answering
- Answering 'stretch the shot' alone scores nothing; the hinge is 'why'. They want the reasoning for which side yields, and whether you see that this choice fixes the order of the whole pipeline.
- State the criterion: which distortion does the audience notice? Clipped or sped-up dialogue is audible immediately; a shot running 0.8 seconds long is not. So the picture yields.
- Derive the pipeline order from that: generate video at the planned duration, synthesize speech, write the measured duration back onto the shot, and let the editor pad the picture. Why not synthesize first and generate video to fit? Because video APIs expose discrete duration options — you cannot ask for exactly 6.34 seconds.
- Add the engineering detail that cannot be skipped: the timeline must use durations measured from the rendered files, never character-count estimates. Estimation error accumulates line by line, and by the tenth line the subtitles visibly race the picture.
- Then the exception, which earns points: if a shot has intrinsic rhythm — a beat cut, a transition, an action match — the picture cannot simply be stretched, and the right fix is a shorter line in the script. That is why stretched shots should be flagged for human review rather than silently rewritten.
- Expect the follow-up: can't you just nudge the speaking rate? You can, but it costs you — rate changes affect timbre and delivery, and they change duration again, turning a one-way flow into a loop. Make the lead-in and tail padding adjustable and spend that budget before touching the rate.
分析过程 · 先想清楚再作答
- 这题只答「调画面」拿不到分,题眼在「为什么」——面试官要的是让步理由,以及你有没有意识到这个选择会决定整条流水线的排列顺序。
- 先给判断依据:哪一边的失真观众察觉得到。台词被切掉、或者被加速到语气变形,观众立刻听得出来;一镜比原计划长零点八秒,观众感觉不到。所以让步的是画面。
- 由这条判断反推流水线顺序:画面先按计划时长生成,语音合成完之后由真实时长回写镜头时长,剪辑台再去补足画面。为什么不倒过来先合成语音再按语音时长生成视频?因为视频接口的时长是有限档位的,你没法要求它精确生成 6.34 秒。
- 补一条不能省的工程细节:写进时间轴的必须是从落盘文件量出来的真实时长,不能是字数估算。估算误差是逐句累加的,第一句差两百毫秒,第十句就差两秒,成片上表现为字幕跟画面赛跑。
- 再说例外,这是加分项:如果这一镜的画面本身有强节奏(比如卡点、转场、动作衔接),画面就不能被随意拉长,这时候要回头改剧本把台词写短,而不是硬拉画面。所以被顶长的镜头应该被标记出来交给人复核,而不是程序默默改掉。
- 可以预期的追问:那不能微调语速吗?可以,但语速是有代价的——语速改变会同时改变音质与情绪表现,而且它会反过来再改一次时长,等于把一个单向流程变成了循环。留一点余量的做法是给留白参数一个可调区间,先动留白再动语速。
Key points
- Stretch the picture: clipped or sped-up dialogue is instantly audible, while a fraction of a second of extra shot length is not
- That fixes the pipeline order: generate video at planned duration, synthesize speech, write measured duration back, pad in the edit
- You cannot invert it and generate video to match speech, because video APIs only expose discrete durations
- The timeline must use durations measured from rendered files; character-count estimates accumulate error line by line
- Shots with intrinsic rhythm are the exception, so flag stretched shots for human review instead of silently rewriting them
答题要点
- 调画面:台词被切或被加速观众立刻察觉,镜头长零点几秒观众感觉不到
- 由此定下流水线顺序:画面按计划时长生成,语音合成后回写真实时长,剪辑台补足画面
- 不能倒过来按语音时长生成视频,因为视频接口的时长只有有限档位
- 时间轴必须用落盘文件量出的真实时长,字数估算的误差会逐句累加
- 画面有强节奏的镜头是例外,这类冲突应标记出来交人复核而不是程序默默改掉
Where do you get subtitle timestamps from, and what do you do when the API does not provide them?字幕的时间戳你会怎么拿?接口不给时间戳时有什么替代方案?
Common in ChinaCommon overseasIntermediate#subtitles#timelineHow to reason about it · think before answering
- This tests whether you would take a dependency on an optional vendor field. Name both paths and their costs; giving only one invites a follow-up you will not enjoy.
- Path one is the API: TTS endpoints often expose a subtitle flag returning sentence- or word-level timestamps. Three problems — it costs an extra request to fetch, the timestamps are relative to that single audio segment, and the field structure varies by vendor. The third is the worst, because it welds your subtitle module to one provider.
- Path two is local alignment: you already hold every clip's measured duration and every shot's start time, so accumulating them gives the episode timeline. Zero extra requests, zero vendor coupling, and you control segmentation — one line of dialogue per cue, which is exactly the rhythm short drama wants.
- The key insight is that path one does not free you from path two: API timestamps are segment-relative, so you still add the shot's offset within the episode. Since you must write the alignment code anyway, make it the single source of truth.
- The implementation has one rule: the subtitle cursor and the shot cursor share one origin and advance together. Add a check that every cue falls inside its own shot — overflow raises no error, it just floats the previous shot's line over the next shot's picture.
- Expect the follow-up: what about karaoke-style word-level subtitles? That genuinely requires word-level timestamps from the API. Treat it as an optional enhancement over a local-alignment main path, degrading to sentence level when word data is unavailable.
分析过程 · 先想清楚再作答
- 这题在考你会不会为一个可有可无的厂商字段引入依赖。两条路都要说得出来,还要说清各自的代价,只答一条会被追问到底。
- 第一条是接口给:语音合成接口通常有一个字幕开关,返回按句或按词的时间戳。它的问题有三个——要多发一次请求去取内容、时间戳是相对单段音频的、字段结构随厂商变化。第三条最要命,因为它让你的字幕模块和某一家厂商绑死了。
- 第二条是本地对齐:你手里已经有每段音频的真实时长和每一镜的起始时刻,累加就是整集时间轴。它零额外请求、零厂商依赖,而且断句由你自己控制——按台词行断,一句一条,天然符合短剧节奏。
- 关键在于**就算用第一条也逃不掉第二条**:接口给的是段内相对时间,你仍然要加上这一镜在整集里的偏移。所以本地对齐这套代码无论如何都要写,那不如让它成为唯一的真相来源。
- 对齐的实现只有一个要点:字幕游标和镜头游标必须共用同一个原点,逐镜推进。再配一个自检——每条字幕必须落在它所属的那一镜内,越界不会报错,只会让上一镜的台词飘到下一镜的画面上。
- 可以预期的追问:那按词级时间戳做卡拉OK式字幕呢?那种效果确实必须依赖接口的词级时间戳,本地对齐做不了。这时的正确做法是把它做成一个可选增强,主链路仍然走本地对齐,拿不到词级数据就降级成句级。
Key points
- Two sources: timestamps returned by the API, and local alignment accumulated from measured audio durations
- The API path costs an extra request, gives segment-relative timestamps, and couples you to one vendor's field structure
- Local alignment needs no extra request and no vendor coupling, and lets you segment per line of dialogue
- Even with API timestamps you must add each shot's offset within the episode, so the alignment code is unavoidable anyway
- The implementation rule is one shared origin for the subtitle and shot cursors, plus a check that each cue stays inside its own shot
答题要点
- 两条来源:接口返回的时间戳,以及由音频真实时长本地累加对齐
- 接口那条的代价是多一次请求、时间戳只相对单段音频、字段结构跟厂商绑定
- 本地对齐零额外请求零厂商依赖,断句按台词行控制,符合短剧节奏
- 即使用接口时间戳也仍要自己加上这一镜在整集里的偏移,所以本地对齐代码无论如何都得写
- 实现要点是字幕游标与镜头游标共用同一原点,并自检每条字幕是否落在它所属的镜头内
In a multi-character pipeline, how do you guarantee the same character keeps the same voice across episodes?多角色配音里,怎么保证同一个角色跨集用的是同一个声音?
Common in ChinaCommon overseasBasic#tts#consistency#provider-abstractionHow to reason about it · think before answering
- This looks like a voice question but is really about where state lives. 'Hardcode it in config' is not wrong, but stopping there shows no engineering judgment.
- Name the risk first: voice is part of a character's identity, and audiences are about as sensitive to it as to a face. Inconsistency across episodes has three usual causes — running each episode as an independent pipeline, picking voices from an ad-hoc or random mapping, and someone tweaking a character's global parameters while fixing the delivery of one line.
- The fix is to file the voice in the character record rather than in code: the record carries a voice id, and the dubbing step only reads it. Consistency then holds regardless of episode, run or operator — the same pattern as pinning appearance to a base reference image.
- Storing the voice id alone is not enough. Perceived sameness also depends on the baseline emotion and the speaking rate; the same voice at two different rates sounds like a different state of a person. Keep all three in the record, and allow per-line overrides of emotion only, never of rate.
- Add a defensive layer: record the voice id together with the model name in the artifact metadata. Vendors do retire and rename voices, and you want to be able to answer 'why does season two sound different' from data rather than memory.
- Expect the follow-up: what if the vendor retires that voice? Make voice selection part of the provider abstraction — the record stores the character's voice archetype, and the mapping to a concrete vendor voice lives in the adapter, so swapping vendors never touches the character records.
分析过程 · 先想清楚再作答
- 这题看着像配音问题,其实考的是状态该存在哪里。答「配置里写死」不算错,但只答到这一层看不出工程判断。
- 先说清楚风险来自哪:声音是角色身份的一部分,观众对它的敏感度不低于脸。跨集不一致的典型成因有三个——每集独立跑一次流程、音色靠临时映射或随机挑选、以及某次为了改一句台词的语气顺手改了这个角色的全局参数。
- 解法是把音色归档而不是归代码:角色档案里带一个音色字段,配音环节只读不写。这样一致性由档案保证,跟哪一集、哪一次运行、谁跑的都无关。这跟角色形象靠基准图归档是同一套思路。
- 但只存音色标识还不够,跨集听感一致还依赖另外两项:基调情绪与语速。同一个音色用两种语速念,听起来像两个人的状态。所以档案里要一起存这三项,单条台词只允许覆盖情绪,不允许覆盖语速。
- 再补一层防御:把音色标识连同模型名一起记进产物元数据。厂商下线或重命名一个音色是会发生的,你要能查出「第二季为什么听起来不一样」,而不是只能凭记忆猜。
- 可以预期的追问:如果厂商真的下线了那个音色怎么办?答案是把音色选择也做成 provider 抽象的一部分:档案里存的是角色的音色角色定位,映射到具体厂商音色的表放在适配层,换厂商或补映射时不动档案。
Key points
- Store the voice in the character record and have the dubbing step read it only, so consistency is independent of episode, run or operator
- Keep voice id, baseline emotion and speaking rate together; allow per-line emotion overrides but never rate overrides
- Write the voice id and model name into artifact metadata so you can explain why a later season sounds different
- Typical causes of drift are per-episode independent runs, ad-hoc mappings, and global tweaks made while fixing one line
- Fold voice selection into the provider abstraction: records hold the archetype, the adapter maps it to a concrete vendor voice
答题要点
- 把音色存进角色档案,配音环节只读不写,一致性与集数、运行次数、操作人无关
- 档案里要同时存音色标识、基调情绪与语速;单条台词只允许覆盖情绪,不允许覆盖语速
- 把音色标识与模型名一起写进产物元数据,便于回答「为什么这一季听起来不一样」
- 跨集不一致的典型成因是每集独立跑、临时映射、以及改一句台词时顺手改了全局参数
- 音色选择应纳入 provider 抽象:档案存角色的音色定位,具体厂商音色的映射放在适配层
D6 The Editing Bay: Assembling Footage Into One Vertical Cut With ffmpeg
What are the risks of letting an LLM generate ffmpeg command lines directly, and how would you redesign it?如果让大模型直接生成 ffmpeg 命令来合成视频,会有什么风险?你会怎么改造这个设计?
Common in ChinaCommon overseasDeep dive#prompt-injection#pipeline-design#reproducibilityHow to reason about it · think before answering
- This probes where you draw the line between model judgment and real execution. Saying 'injection risk' is the passing bar; missing reproducibility and debuggability signals you have not run a generative pipeline in production.
- The chain is short: model output is untrusted input, untrusted input into a shell is command injection, model output is also nondeterministic, nondeterministic commands mean the same input yields different files, and debugging then requires guessing what the model was thinking.
- The fix is not 'validate and forward'. Move the model: let it emit only structured choices drawn from an enum you fixed in advance (transition type, crop strategy, which shot the cover comes from), and compute the command yourself from the timeline with a pure function.
- Add the concrete detail: invoke external binaries with an argument array (execFile, not exec) so escaping stops being a class of bug, then whitelist-validate the model's choices and fall back to a default instead of erroring.
- Expect the follow-up 'so what is the model still good for here'. Answer: taste calls — tone, cover selection, whether to use a transition. Judgment to the model, execution to the program. That boundary generalizes to any agent with side effects.
分析过程 · 先想清楚再作答
- 这题考的是「Agent 到底能不能碰真实执行」这条边界,区分度在于你会不会主动说出安全之外的两条。只答「有注入风险」是及格线,答不出可复现与可调试就说明没在生产里跑过生成式流水线。
- 推导链很短:模型的输出是不可信输入 → 不可信输入进 shell 就是命令注入 → 而且模型输出天然不确定 → 不确定的命令意味着同样的输入产出不同的文件 → 排查时你还得先猜模型当时为什么那么写。三条风险分别对应安全、可复现、可调试。
- 改造的方向不是「加一层校验就放行」,而是把模型挪到另一个位置:让它只输出结构化的选择项,且每一项都从你定死的枚举里选(转场类型、裁切策略、封面取哪一镜),命令本身由你自己的纯函数从时间轴算出来。
- 补一句更硬的落地细节:调用外部程序不要走 shell 字符串,用参数数组(execFile 而不是 exec),从根上消掉转义问题;再加一层白名单校验,模型给出枚举外的值就退回默认值而不是报错。
- 可预期的追问是「那模型在这一环还有什么用」。答:用在需要审美判断的地方——情绪偏冷还是偏暖、封面选哪一镜、要不要转场。判断交给模型,执行留给程序,这是所有会产生副作用的 Agent 场景的通用分界。
Key points
- Three risks in order: command injection, non-reproducible output, undebuggable failures. Naming only the first is not enough.
- Turn the model into a parameter filler: structured choices constrained to a predefined enum.
- Generate the command from the timeline with a pure function, invoked via an argument array rather than a shell string.
- Whitelist-validate and fall back to defaults for out-of-enum values instead of surfacing an error.
- One-line boundary: the model decides, the program executes.
答题要点
- 三条风险按严重度排:命令注入、结果不可复现、报错不可调试;只说第一条不够。
- 改造成参数填充器:模型输出结构化选择项,取值必须落在预定义枚举里。
- 命令由程序的纯函数从时间轴生成,用参数数组调用而不是拼 shell 字符串。
- 白名单校验兜底,枚举外的值退回默认,而不是把错误抛给用户。
- 分界线一句话:模型负责判断,程序负责执行。
An auto-generated episode comes out with audio and video out of sync. What is your debugging order, and why that order?一集自动生成的短剧成片出现音画不同步,你的排查顺序是什么?为什么是这个顺序?
Common in ChinaCommon overseasIntermediate#debugging#av-sync#timelineHow to reason about it · think before answering
- The question is about ordering, not about listing causes. The interviewer wants to see you rank checks by hit rate divided by cost, not enumerate everything you can think of.
- Ask yourself first: where does time come from in this pipeline? If the answer is 'a structured timeline table', then step one is comparing planned durations in that table against the real durations of the media files. Highest hit rate, lowest cost, one ffprobe call.
- Step two is the upstream artifacts: when the voice track is longer than the shot, the line gets cut off. It sounds almost identical to drift but the root cause is different, and it should have been caught with a warning when the timeline was built.
- Step three is the compose stage: stream-copy concatenation requires identical parameters across segments, and misaligned timestamps shift things; adding crossfades shortens the final cut, so subtitles drift progressively unless their timecodes are recomputed.
- Also mention a general move: when all three fail, stop staring at the final cut and play the normalized per-shot segments to narrow the problem to one shot. Always shrink the search space before guessing.
- Expect the follow-up 'how do you stop relying on human ears'. Answer: assert at timeline-build time when planned and actual durations diverge beyond a threshold, and automatically verify that the final cut's duration matches the timeline total.
分析过程 · 先想清楚再作答
- 这题的题眼在「顺序」两个字,不在「有哪些原因」。面试官想看的是你会不会按「命中率乘以排查成本」来排,而不是把想到的原因罗列一遍。
- 先问自己一个问题:这条流水线上,时间是从哪里来的?如果答案是「一张结构化的时间轴表」,那么第一步必然是拿表里的计划时长和素材文件的真实时长去对——这一步命中率最高、成本最低,一条 ffprobe 就能查完。
- 第二步查上游的产物本身:配音时长超过镜头时长时,台词会被截断,听感和不同步几乎一样,但根因完全不同。这类冲突应该在生成时间轴时就打警告,而不是留到成片阶段靠耳朵发现。
- 第三步才查合成环节:流拷贝拼接要求各段参数一致,时间戳对不齐就会错位;加了转场则成片整体变短,字幕若没跟着重算,表现为越到后面偏得越多。
- 还有一条通用招式值得说出来:三步都查不出来时,不要在成片里死磕,去播归一化之后的单镜片段,把问题缩小到某一镜身上。排查多段合成的问题永远优先缩小范围。
- 可预期的追问是「怎么让这类问题不再靠人耳发现」。答:在时间轴生成阶段加断言(计划时长与素材真实时长的偏差超过阈值就失败),并把成片时长与时间轴总时长的一致性做成自动校验。
Key points
- Start with the timeline table: compare planned durations against the media files' real durations. Highest hit rate, cheapest check.
- Then check whether the voice track exceeds the shot duration and truncates the line. That should be warned about at timeline-build time.
- Only then look at compose: concat method, timestamp alignment, and crossfades shortening the cut without recomputed subtitle timecodes.
- General move: play the per-shot normalized segments to isolate one shot instead of guessing on the final cut.
- Long term, turn duration consistency into assertions and automated checks rather than relying on ears.
答题要点
- 先查时间轴表里的计划时长与素材真实时长是否一致,这一步命中率最高、成本最低。
- 再查配音是否超出镜头时长导致台词被截断,这类问题应在生成时间轴时就报警告。
- 最后查合成环节:拼接方式、时间戳对齐、转场是否让成片变短而字幕没重算。
- 三步之外的通用招式:播单镜片段把问题缩小到某一镜,不要盯着最终产物猜。
- 长期方案是把时长一致性做成断言与自动校验,不靠人耳兜底。
When can you concatenate video segments without re-encoding, and when must you re-encode?把多个视频片段拼成一条完整的视频,什么时候可以不重新编码,什么时候必须重编码?
Common in ChinaCommon overseasBasic#ffmpeg#encoding#media-pipelineHow to reason about it · think before answering
- This is a giveaway concept question, but it only gives points to people who state the precondition. 'Just use concat' and 'stream copy requires identical parameters' read as two different levels.
- There is exactly one criterion: does concatenation only need to move packets into a new container in order? If yes, stream copy works. If even one frame has to be newly computed, you must re-encode.
- Be able to recite the preconditions: resolution, frame rate, pixel format, codec, audio sample rate and channel layout must all match. Miss one and you get corruption, dropped audio, or a broken duration.
- Cases that force re-encoding: transitions (those frames are new), scaling and padding to a common canvas, mixing in a new audio track, or changing encoding parameters. AI-generated material varies in size and often lacks audio, so normalization is almost always required in practice.
- The conclusion is a combination: normalize each shot with its own filter graph pass, then stream-copy the now-identical segments together. Total re-encoding is still one pass, but you gain full control over each shot.
- Expect the follow-up 'how do you know whether the parameters match'. Answer: read the key fields of each segment with ffprobe and compare. That precheck belongs in any automated pipeline.
分析过程 · 先想清楚再作答
- 这是一道概念送分题,但送分的是「说出前提」的人。答「用 concat 就行」和答「参数一致才能流拷贝」,在面试官眼里是两个水平。
- 判据只有一条:拼接是不是只需要把数据包按顺序搬进新容器。只搬不算,就能流拷贝;只要有任何一帧画面是新算出来的,就必须重编码。
- 流拷贝的前提要能背出来:分辨率、帧率、像素格式、编码器、音频采样率、声道数全部一致。差一项,产物要么花屏掉音,要么时长错乱。
- 必须重编码的典型场景:转场(那几帧是新画面)、缩放补边到统一画布、混入新的音轨、改变编码参数。AI 生成的素材尺寸和音轨天然不一致,所以实际工程里几乎总要先归一化。
- 结论落在一个组合拳上:每一镜单独走一次滤镜图做归一化,然后用流拷贝把参数已经一致的片段拼起来。重编码的总量还是一遍,但换来了对每一镜的完全控制。
- 可预期的追问是「怎么判断素材参数一不一致」。答:用 ffprobe 把每段的关键字段读出来做一次比对,不一致就走归一化,这一步也是自动化流水线里必须有的前置检查。
Key points
- The criterion is whether concatenation only moves packets: if so, stream copy; if any frame is newly computed, re-encode.
- Stream-copy preconditions: identical resolution, frame rate, pixel format, codec, sample rate and channel layout.
- Transitions, scale-and-pad, mixing a new audio track, and changing encoding parameters all force re-encoding.
- The practical combination: normalize per shot first, then stream-copy concatenate. Total re-encoding stays at one pass.
- Use ffprobe to compare segment parameters as a pipeline precheck.
答题要点
- 判据是拼接是否只需要搬数据包:只搬就能流拷贝,有新算出来的帧就必须重编码。
- 流拷贝的前提:分辨率、帧率、像素格式、编码器、采样率、声道数全部一致。
- 转场、缩放补边、混入新音轨、改编码参数,这几类一定要重编码。
- 实践中的组合拳:先逐镜归一化,再流拷贝拼接,重编码总量仍是一遍。
- 用 ffprobe 比对各段参数,作为流水线里的前置检查。
D7 One Episode Wrapped: Stringing Six Stages Into an End-to-End Pipeline and Tallying the First Bill
In a multi-step generation pipeline, one step fails. What behavior do you want the system to have?一条多步骤的生成流水线,中间某一步失败了,你希望系统有什么行为?
Common in ChinaCommon overseasIntermediate#pipeline-reliability#idempotency#error-handlingHow to reason about it · think before answering
- The discriminator is whether you answer in layers. People who just say 'retry' assume all failures are transient. Anyone who has run one of these asks first: is this failure retryable, because that decides everything downstream.
- Split the behavior into three layers: what to do immediately, what to do for this run, and what to do for the next run. Immediately: classify the error and retry with bounds. Only rate limits, timeouts and 5xx deserve backoff; auth failures, insufficient balance and content-policy rejections will fail a hundred more times.
- For this run: preserve the value already produced. Persist artifacts, elapsed time and spend for every completed step, including the money the failing step itself already burned. An implementation that just rethrows loses exactly the data a post-mortem needs.
- For the next run: do not pay twice. Give every node an idempotency key, store artifacts content-addressed, and make a rerun a set difference — skip what is done, redo only what is not. The bar is hard: the second run should make zero paid API calls.
- This matters more in generative pipelines than in ordinary backends because per-step cost is extreme. Measured on one episode in this course, the video step is 98 percent of total spend, so a full rerun burns over ten yuan, predictably rather than occasionally.
- Expect the follow-up 'what goes into the idempotency key'. Answer: model id, prompt, duration and resolution — anything that changes the artifact — plus an implementation version and the fingerprints of all dependencies. Never the run id, a timestamp or a random value.
分析过程 · 先想清楚再作答
- 这题的区分度在于你会不会分层回答。只说「重试」的人默认失败都是瞬时的;真正做过的人会先问一句:这次失败是可重试的还是不可重试的,因为这一条决定了后面所有动作。
- 先把行为拆成三层:立刻要做的、这一次运行要做的、下一次运行要做的。立刻要做的是错误分类与有界重试,只有限流、超时、五开头这类瞬时错误才值得退避重试,鉴权失败、余额不足、内容审核不通过重试一百次也是白烧钱。
- 这一次运行要做的是保住已经产生的价值:把已完成步骤的产物、耗时、花费全部落盘,包括失败那一步自己已经花掉的钱。一个直接向上抛的实现会把这些一起丢掉,而它们恰恰是复盘时最该看的。
- 下一次运行要做的是不重复花钱:每个节点算一个幂等键,产物按内容寻址落盘,重跑时先做一次差集,已完成的跳过、只补做没做完的。判据非常硬——第二次运行的付费接口调用次数应当是 0。
- 在生成式流水线里这一条比传统后端更要紧,因为单步成本高得离谱:本课量过一集的账,视频那一环占了全部花费的九成八,从头重跑一次就是白烧十块多,而且是必然的,不是偶然的。
- 可预期的追问是「幂等键里该放什么」。答:模型 id、提示词、时长分辨率这类会影响产物的输入,加上实现版本号和全部依赖的指纹;绝不能放运行标识、时间戳、随机数,放了就永远不命中。
Key points
- Classify errors first: only retryable ones get backoff. Auth, balance and content-policy failures gain nothing from retries.
- On failure, preserve completed steps' artifacts, timings and spend, including what the failing step itself already cost.
- The next run uses idempotency keys and content-addressed artifacts to compute a set difference and redo only what is missing.
- The acceptance bar is zero paid API calls on the second run, not 'no errors in the log'.
- Per-step cost is extreme in generative pipelines, so this work converts directly into money on the bill.
答题要点
- 先做错误分类:可重试的才退避重试,鉴权、余额、内容审核这类重试没有意义。
- 失败时保住已完成步骤的产物、耗时与花费,失败那一步自己花的钱也要记。
- 下一次运行靠幂等键与内容寻址的产物做差集,只补做没做完的部分。
- 验收判据是第二次运行的付费接口调用次数为 0,而不是「日志里没报错」。
- 生成式流水线单步成本极高,这一条的收益能直接换算成账单上的金额。
How do you measure the cost of a generation pipeline, and what besides money should you measure?怎么度量一条生成流水线的成本?除了钱还要量什么?
Common in ChinaCommon overseasBasic#observability#cost-accounting#pipeline-designHow to reason about it · think before answering
- This looks like a giveaway, but the real question is 'besides money'. Anyone who reports a single total cannot make an optimization decision, because a total does not say where to act.
- First decide the granularity: break it down per stage. One number carries no information; a per-stage table immediately shows where the money and the time went. Measured on one episode here: five images cost 0.125 yuan, voice under two cents, three video shots 10.5 yuan — video is 98 percent. You only see that broken down.
- Second, measure three things besides money: elapsed time decides how many episodes per day, call count decides whether you hit provider rate limits, and artifact count is the crudest completeness check — four shots should yield four clips, and a missing one means something failed silently.
- Third, separate estimates from real spend. Offline or in load tests you have no real amounts, so derive them from published unit prices — but label them as estimates, and never mix the two on one code path or the books will never reconcile.
- Also worth flagging: offline timing rankings are usually fake. With the APIs stubbed, local encoding becomes the biggest slice, and optimizing against that chart targets the wrong thing.
- Expect the follow-up 'what do you optimize first'. Answer: whatever has a number attached. Here it is waste from failed reruns, because it equals money on the bill. Concurrency comes second — before output is stable, concurrency only burns money faster.
分析过程 · 先想清楚再作答
- 这题看着是送分题,题眼其实在「除了钱」。只报一个总金额的人,做不出任何优化决策,因为总金额不告诉你该动哪里。
- 第一步是确定度量的粒度:**按环节摊开**。一个总数没有信息量,一张按环节分列的表能立刻告诉你钱花在哪、时间花在哪。本课量过一集:五张图一毛二五、配音不到两分、三个镜头的视频十块五,视频占了九成八——这个结论只有摊开才看得见。
- 第二步是把「钱」之外的三样一起量:耗时决定一天能出几集;调用次数决定会不会撞上厂商的速率限制;产物数是最朴素的完整性校验,四个镜头就该有四个视频,少一个说明某处静默失败了。
- 第三步是把估算和真实分开。离线或压测时拿不到真实金额,可以按公开单价折算,但**必须标明它是折算值**,而且折算逻辑和真实金额不能混在一条路径上算,否则账永远对不上。
- 还要提醒一句常被忽略的:离线模式下的耗时排名往往是假的。接口被打了桩,本地的编码步骤反而成了大头,照着这张图做优化会优化错地方。
- 可预期的追问是「量完之后先优化哪一项」。答:先优化能被数字证明收益的那一项。这个场景里是失败重跑造成的浪费,因为它直接等于账单上的金额;并发排第二,因为在产出还不稳定时并发只会让你更快地烧钱。
Key points
- Break the cost down per stage; a single total cannot tell you where to act.
- Besides money, measure elapsed time, call count and artifact count — throughput, rate limits and completeness.
- Keep estimated and real spend on separate paths, and always label estimates as estimates.
- Offline timing rankings are unreliable; do not optimize against a stubbed profile.
- Prioritize by which improvement has a number attached, not by intuition.
答题要点
- 按环节摊开,不要只给一个总数,否则无法定位该优化哪里。
- 除了金额还要量耗时、调用次数、产物数,各自对应吞吐、限流、完整性。
- 估算与真实金额分开计算,估算必须标明是折算值。
- 注意离线模式下耗时排名不可信,别照着假图做优化。
- 优化顺序按「收益能不能被数字证明」排,不按直觉排。
After chaining several individually working steps into one pipeline, which problems appear that single-step debugging never shows?把多个已经各自跑通的环节串成一条流水线之后,哪些问题是单独调试时看不见的?
Common in ChinaCommon overseasDeep dive#integration#pipeline-design#observabilityHow to reason about it · think before answering
- This tests integration instinct. If the answer is only 'interfaces do not line up', you have only integrated synchronous pure functions. In generative pipelines the integration problems live in state and artifacts, not in signatures.
- The framing question is: during single-step debugging, who does the gluing? Your head does. You know where the last script wrote its files and which blob to feed forward. Chaining forces that implicit knowledge into code, and whatever you fail to move becomes an integration bug.
- That yields three concrete classes. First, artifact paths and naming: a fixed output path is fine in isolation, but the second run overwrites the first, and on failure you cannot tell which files belong to which attempt. The fix is a run id that every artifact hangs under.
- Second, partial intermediate state: a step produces incomplete output without erroring, the next step accepts it, and the error propagates until it explodes far from its origin. The fix is a completeness assertion after every step, such as an expected artifact count.
- Third, observability: six stages each log their own way, hundreds of lines scroll past, and you cannot tell which stage failed. The fix is one log contract — a scannable progress table on the terminal, details pushed to files.
- Expect the follow-up 'how do you catch these earlier'. Answer: agree on three things before chaining — the artifact directory layout, each step's input/output contract, and the log format. Fix those and most integration bugs never get written.
分析过程 · 先想清楚再作答
- 这题考的是系统集成的直觉。回答里如果只有「接口对不上」,说明你只集成过同步的纯函数;生成式流水线的集成问题主要出在状态和产物上,不在接口签名上。
- 拆解的角度是:单独调试时,是谁在做衔接?答案是你的脑子。你知道上一个脚本把文件写到哪、知道该拿哪份数据喂下一步。串起来之后这些隐式知识必须搬进代码,而搬漏的地方就是集成问题的来源。
- 由此可以推出三类具体问题。第一类是产物路径与命名:单独跑时随手写一个固定输出路径没问题,串起来跑第二遍就把第一遍覆盖了,失败时也分不清哪些文件属于哪一次。解法是每次运行分配一个运行标识,所有产物挂在它下面。
- 第二类是中间态:某一步的产物不完整但没报错,下一步照单全收,错误一路往下传,最后在离源头很远的地方炸掉。解法是每一步产出后做完整性校验,比如按数量断言。
- 第三类是可观测性:六个环节各打各的日志,几百行滚过去,出了事看不出是哪一环。解法是统一日志规格,终端上只留一张能一眼扫完的进度表,细节压到文件里。
- 可预期的追问是「怎么提前发现这些问题」。答:串联之前先约定三件事——产物目录布局、每一步的输入输出契约、日志规格。这三件事定下来,绝大多数集成问题在写代码时就被挡住了。
Key points
- In isolation a human does the gluing; chaining means moving that implicit knowledge into code.
- Artifact paths and naming: assign a run id and hang every artifact under it to avoid overwrites and confusion.
- Incomplete intermediate state that does not error propagates far before exploding; assert completeness after every step.
- Log flooding: adopt one log contract, keep a progress table on the terminal and push details to files.
- Prevent it by agreeing on directory layout, per-step I/O contracts and log format before chaining anything.
答题要点
- 单独调试时是人脑在做衔接,串联的本质是把隐式知识搬进代码。
- 产物路径与命名:每次运行一个运行标识,所有产物挂在它下面,避免覆盖与混淆。
- 中间态不完整却不报错,错误会传到很远的地方才炸;每一步产出后做完整性校验。
- 日志淹没:统一日志规格,终端只留进度表,细节压到文件。
- 预防手段是串联之前先定好目录布局、输入输出契约与日志规格三件事。
D8 A Workflow Engine: Turning the Pipeline Into a Resumable Task Graph
How do you make a node that calls a paid generation API idempotent? What belongs in the cache key and what does not?怎么让一个会调用付费接口的生成节点是幂等的?缓存键里该放什么、不该放什么?
Common in ChinaCommon overseasIntermediate#idempotency#caching#workflow-engineHow to reason about it · think before answering
- The discriminator is the second half: what must not go in. People who only say 'hash the inputs' have usually never been burned by a cache. The two failure modes point in opposite directions: never hitting, and hitting when it should not.
- State the criterion first: include everything that changes the artifact, exclude everything that changes every run without affecting the artifact. Both lists fall out of that.
- Include four things: node id, implementation version, this node's own inputs (model id, prompt, duration, resolution), and the fingerprints of all dependencies. The version and the dependency fingerprints are the two people forget — miss the version and new code reads old artifacts; miss the dependencies and an upstream script change never propagates.
- Exclude: run id, timestamps, random values, absolute paths, and anything carrying a hostname or temp directory. Any of those makes every key new, and you will blame the cache instead of the key.
- Two implementation details worth volunteering: decide 'is it done' by checking the artifacts on disk, not the state file, because files get deleted by hand; and think about granularity — four shots in one node means one failed shot redoes all four, while finer granularity saves money at the cost of a much larger graph.
- Expect the follow-up 'does hashing dependency keys over-invalidate'. Yes. An upstream wording change that produces an identical artifact still invalidates downstream. Hashing the dependency's artifact content instead is tighter but requires reading the artifact every time — worth it for small files, not for large videos.
分析过程 · 先想清楚再作答
- 这题的区分度全在「不该放什么」那一半。只答「把输入哈希一下」的人,通常没在真实项目里被缓存坑过——缓存的两种病方向相反,一种是永远不命中,一种是命中了不该命中的。
- 先给判据:键里应该出现的,是所有会改变产物的东西;不该出现的,是所有每次都会变但不影响产物的东西。这一条能直接推出下面两张清单。
- 该放的四样:节点标识、实现版本号、本节点的输入(模型 id、提示词、时长、分辨率)、以及全部依赖的指纹。版本号和依赖指纹是最容易漏的两样——漏了版本号,改完代码读到旧产物;漏了依赖指纹,上游换了剧本你还在用旧的镜头。
- 不该放的:运行标识、时间戳、随机数、绝对路径、以及任何带机器名或临时目录的东西。放进去等于每次都是新键,你会以为缓存写坏了,其实是键设计错了。
- 还有两条落地细节值得主动说:判断「做没做完」要看磁盘上产物齐不齐,不能只信状态文件,因为文件可能被手删;以及幂等的粒度要想清楚,一个节点里跑四个镜头,第三镜失败就是四镜全重做,粒度更细更省钱但任务图会大很多。
- 可预期的追问是「依赖指纹会不会失效得太狠」。答:会。上游只是文案改了、产物其实一样,下游也会跟着重做。更省的做法是对依赖的产物内容做哈希而不是对它的键做哈希,代价是每次都要把产物读一遍——小文件划算,大视频不划算,这是要自己量的一笔账。
Key points
- One criterion: include what changes the artifact, exclude what changes every run without affecting it.
- Must include: node id, implementation version, the node's own inputs, and all dependency fingerprints.
- Must exclude: run id, timestamps, random values, absolute paths and host-specific data.
- Decide cache hits by checking artifacts on disk, not by trusting the state file.
- Choose the idempotency granularity explicitly: per node is simpler, per shot saves more but grows the graph.
答题要点
- 判据一句话:会改变产物的进键,每次都变但不影响产物的不进键。
- 必放四样:节点标识、实现版本号、本节点输入、全部依赖的指纹。
- 禁放:运行标识、时间戳、随机数、绝对路径与机器相关信息。
- 命中判定看磁盘上产物是否齐全,不能只信状态文件。
- 幂等粒度要显式选择:节点粒度实现简单,镜头粒度更省钱但图更大。
What state must you persist to support resuming a workflow? Is per-node completion status enough?要支持断点续跑,你需要持久化哪些状态?只存每个节点的完成状态够不够?
Common in ChinaCommon overseasDeep dive#workflow-engine#state-persistence#resumeHow to reason about it · think before answering
- The words 'is it enough' hint that it is not. A system storing only completion status knows a node ran, but not which version ran, so it happily skips after you change the code.
- Frame it as three questions a resume must answer: which nodes are done, are they the version I want now, and are their artifacts still there? Each maps to something you must persist.
- So beyond status you need the fingerprint and the artifact location. The fingerprint answers 'same version?', the location answers 'still there?'. Storing artifacts in a content-addressed directory named by the fingerprint collapses the third question into a file-existence check.
- Also separate two layers: the artifact cache is global and shared across runs, providing idempotency; node state is per run, providing resume. Collapse them and a new run id costs you full price again.
- Write timing is part of the answer: persist state right after each node completes, not once at the end. Hard kills, power loss and container eviction are not rare during ten-minute video jobs.
- Expect the follow-up 'do you delete a failed node's partial artifacts'. No. Keep them, and make the hit condition 'every declared output exists'. Missing one means redo, so partials are never mistaken for success.
分析过程 · 先想清楚再作答
- 题眼在「够不够」三个字,它在暗示你答案是不够。只存完成状态的系统,重跑时只知道「这个节点做过」,却答不出「做的是哪一版」——于是改完代码重跑,它照样跳过。
- 拆的角度是:续跑要回答三个问题。哪些节点做完了?它们做的是不是我现在要的那一版?它们的产物还在不在?三个问题分别对应三样要持久化的东西。
- 所以除了状态,还要存指纹和产物位置。指纹回答「是不是同一版」,产物位置回答「东西还在不在」。本课的做法是把产物按指纹落进内容寻址的目录,这样第三个问题退化成一次文件存在性检查,连记都不用记。
- 还要区分两层:产物缓存是全局的,跨运行共享,它提供的是幂等;节点状态是每次运行一份,它提供的是断点续跑。混成一层的话,换个运行标识就得重花一次钱。
- 落盘时机也是这题的一部分:状态必须在每个节点跑完之后立刻写,而不是整个流程结束再写一次。进程被强杀、机器掉电、容器被驱逐,在跑十几分钟的视频任务时并不罕见。
- 可预期的追问是「失败节点的残产物要不要删」。答:不删。留着它,下一次跑到这里判断产物齐不齐就直接得到结论;但判定必须是「outputs 里每个文件都在」才算命中,缺一个就重做,否则残产物会被当成成功的。
Key points
- Completion status alone is not enough: persist the fingerprint and artifact location to answer 'which version' and 'still present'.
- Content-addressed artifact directories reduce 'still present' to a file-existence check.
- Keep two layers: a global cache for idempotency, per-run node state for resume.
- Persist state immediately after each node, not once at the end of the run.
- Keep failed nodes' partial artifacts, but only count a hit when every declared output exists.
答题要点
- 只存完成状态不够,还要存指纹和产物位置,分别回答「哪一版」和「还在不在」。
- 产物按指纹落进内容寻址目录后,「还在不在」退化成一次文件存在性检查。
- 两层分开:缓存全局共享提供幂等,节点状态每次运行一份提供断点续跑。
- 状态要在每个节点跑完后立刻落盘,不能等整个流程结束再写。
- 失败节点的残产物保留,但命中判定必须是全部产物齐全才算数。
When should you write your own scheduler, and when should you adopt an off-the-shelf workflow engine?什么时候该自己写调度,什么时候该直接上现成的工作流引擎?
Common in ChinaCommon overseasBasic#architecture#build-vs-buy#workflow-engineHow to reason about it · think before answering
- This tests selection maturity. Both extremes lose points: building everything yourself shows no sense of leverage, adopting a framework for everything shows no judgment. The interviewer wants your switching signals.
- Give a general criterion: writing it yourself buys understanding and fit; a framework buys you past problems you have not hit yet. So the decision hinges on how much of what you need overlaps with the framework's core.
- Writing your own pays off when: single machine, a handful of nodes, a path you fixed yourself, and you only need topological ordering plus idempotency plus state persistence. That is under three hundred lines, and the understanding transfers to any engine you adopt later.
- Three signals to switch: you need cross-machine scheduling, where rolling your own scales in complexity exponentially; you need human-in-the-loop nodes, so runs suspend for hours or days and state must live in a database rather than a JSON file; or non-engineers need to see and operate it, in which case you need a product with a UI, not an engine.
- Conversely, adopting a heavy framework too early has a concrete cost: every business change must route around its abstractions, while its benefits only land at scale. Cost up front, payoff deferred.
- Expect the follow-up 'can you migrate off your own version cleanly'. Yes, if nodes were declarative from the start — dependencies, inputs, outputs, body — with scheduling and state kept out of the business code. Then migration replaces the engine, not the nodes.
分析过程 · 先想清楚再作答
- 这题考的是技术选型的成熟度。两个极端都会被扣分:什么都自己写显得不懂杠杆,什么都上框架显得没判断力。面试官想听的是你的切换信号是什么。
- 先给一条通用判据:自己写的收益是理解和贴合,框架的收益是省掉你还没遇到的那些问题。所以决策取决于「你现在需要的功能有多少落在框架的核心能力上」。
- 自己写划算的情形:单机、节点数是个位数、路径是你定死的、需要的只是拓扑排序加幂等加状态落盘这几件事。这时候自己写不到三百行,而且换来的理解是通用的——你会彻底搞懂幂等键为什么要包含依赖指纹、状态为什么必须每步落盘。
- 该换的三个信号:一是开始需要跨机器调度,自己实现分布式调度的复杂度是指数级上升的;二是开始需要人工介入节点,流程要挂起几小时甚至几天,状态必须外置到数据库而不是一个 JSON 文件;三是开始需要给非工程师看和操作,那你需要的其实是一个带界面的产品。
- 反过来说,过早引入重型框架的代价很具体:每一个业务改动都要先绕过它的抽象,而它的收益要等规模上来才兑现。这是典型的成本前置、收益后置。
- 可预期的追问是「自己写的那一套能不能平滑迁走」。答:能,前提是你从一开始就把节点定义成纯声明(依赖、输入、产物、执行体),调度和状态不侵入业务。这样迁移时改的是引擎,不是六个节点。
Key points
- Decide by how much your needs overlap the framework's core, not by a build-versus-buy stance.
- Rolling your own wins on a single machine with few nodes and a fixed path, needing only topo order, idempotency and state persistence.
- Three switching signals: cross-machine scheduling, human-in-the-loop suspension, and non-engineers needing to operate it.
- Adopting a heavy framework early costs a detour around its abstractions on every change, with benefits deferred to scale.
- Keep nodes declarative and scheduling non-invasive so a later migration replaces the engine, not the nodes.
答题要点
- 判据是你需要的功能与框架核心能力的重叠度,不是「自研还是选型」的立场。
- 自己写划算:单机、节点数少、路径固定,只需要拓扑排序加幂等加状态落盘。
- 该换的三个信号:跨机器调度、人工介入导致流程长时间挂起、非工程师要操作。
- 过早上重型框架的代价是每次业务改动都要绕过它的抽象,收益却要等规模。
- 把节点写成纯声明,调度与状态不侵入业务,将来迁移改的是引擎而不是节点。
D9 Concurrency and Quotas: Starting Multiple Episodes at Once Without Blowing Through Any Provider's Limits
Many jobs share one vendor's quota. How would you design the rate limiting?多个任务共用一家厂商的额度,你会怎么设计限流?
Common in ChinaCommon overseasIntermediate#rate-limiting#concurrency#schedulingHow to reason about it · think before answering
- The hinge is the word shared. Naming a token bucket only answers half of it; the interviewer wants your bucketing dimension and what else sits around the bucket.
- Dimension first: bucket per vendor and per API family, never one global bucket. Image and speech quotas at the same vendor are separate pools, and merging them lets the tight one throttle the loose one.
- Then the parts: one bucket is not enough. A token bucket caps rate (calls per minute, a number the vendor sets); a semaphore caps concurrency (how many are in flight, a number you set to protect memory and spend). Rate alone lets a fast endpoint fire hundreds per minute; concurrency alone lets twenty downloads pile up.
- The engineering detail that decides everything: admission must be non-blocking. If a job is dispatched first and then waits for a token inside the worker, low-priority work pins every worker slot and priority silently stops working. Ask the gate before dispatch, and skip to the next candidate when it says no.
- Finally the numbers: concurrency should not track CPU cores, since this pipeline is almost all network wait. Derive it from vendor quota and from how much work one failure forces you to redo.
- Expected follow-up: where does bucket state live. In-memory is fine for one process; across processes it belongs in Redis behind an atomic token-take script, or each process limits itself and the sum still blows the quota.
分析过程 · 先想清楚再作答
- 这题的题眼是「共用」两个字。只回答一个令牌桶算答了一半,面试官想听的是你按什么维度分桶、以及桶之外还需要什么。
- 先给维度:限流要按「厂商 + 接口类别」分桶,不能全局一个桶。同一家的图像和语音是两个独立的配额池,混在一起会让紧的那个把松的那个也拖住。
- 再给部件:一个桶不够,要两个。令牌桶管速率(一分钟发几次,数字是厂商定的),信号量管并发(同一时刻挂着几个,数字是你自己定的用来保护内存和钱包)。只有速率控制的话,快速返回的接口一分钟能发几百次;只有并发控制的话,二十个请求同时在飞会把内存挂满。
- 然后是关键的工程细节:拿许可的动作必须是非阻塞的。如果任务先被派出去、再在执行流里等令牌,工作槽会被一批低优先任务占死,优先级就静默失效了。正确结构是调度器派活之前先问闸门要许可,拿不到就跳过它去看下一个候选。
- 最后落到取值:并发上限不该按 CPU 核数定,这条线几乎没有本地计算,全在等网络;它该按「一次失败要重跑多少东西」和厂商配额来定。
- 可预期的追问是「桶的状态放哪」。单进程放内存就够;多进程要放 Redis,用一个原子脚本取令牌,否则每个进程各限各的,加起来照样超。
Key points
- Bucket per vendor and per API family; image and speech at one vendor get separate gates.
- Each gate has two parts: a token bucket for rate (the vendor's RPM) and a semaphore for in-flight concurrency (your own number).
- Admission is non-blocking: if the gate says no, the job stays queued instead of holding a worker slot.
- Take the concurrency slot before the token, or a rejected admission silently burns quota.
- Across processes, move bucket state to Redis and take tokens atomically.
答题要点
- 按「厂商 + 接口类别」分桶,一家的图像和语音各一道闸门。
- 每道闸门两个部件:令牌桶控速率(厂商给的 RPM),信号量控并发(自己定的在飞上限)。
- 准入必须非阻塞,拿不到许可就把任务留在队列里,绝不占着工作槽干等。
- 先抢并发票再取令牌,顺序反了会白白扣掉配额。
- 多进程部署时桶的状态要外置到 Redis,用原子操作取令牌。
Beyond backing off and retrying, what else should happen when you get rate limited?收到限流响应之后,除了退避重试还该做什么?
Common in ChinaCommon overseasDeep dive#rate-limiting#error-handling#retryHow to reason about it · think before answering
- This question separates people who have actually been throttled in production. Exponential backoff with jitter is only the first half of the answer.
- Frame it correctly: throttling is a signal, not an error. It says your current send rate exceeds what the vendor will accept right now, so it deserves a feedback action, not just a retry.
- Action one is to slow down on purpose: penalize the bucket so the next window or two issues half the tokens. Without that, you finish the backoff and hit the same wall at the same speed.
- Action two is to not hold an execution slot while waiting. Requeue the job with a not-before timestamp and hand the slot back immediately.
- Action three is classification. Throttling and server errors are retryable; auth failure, insufficient balance, invalid parameters and content-policy rejections are not, and retrying them just repeats one mistake five times while consuming quota. At MiniMax, 1002 is rate limiting and 1039 is the token-per-minute variant, while 1004 is auth, 1008 is balance, 2013 is bad parameters and 1026 or 1027 are content rejections.
- Action four is to record throttle counts as a metric. That number is the only evidence you have when you later retune the gate.
- Expected follow-up: how to cap the backoff. Cap it at what the business can wait for, then degrade instead of retrying: smaller resolution, shorter duration, or push the job into the next batch.
分析过程 · 先想清楚再作答
- 这题在考你有没有真在生产里被限流打过。只答「指数退避加抖动」是标准答案的前半段,面试官等的是后半段。
- 先把限流摆正位置:它不是错误,是信号。它告诉你此刻的发送速率超过了厂商愿意接受的速率。既然是信号,就该有反馈动作,而不只是重试。
- 第一个动作是主动降速:把令牌桶罚一档,接下来一两个窗口只发一半令牌。不降速的话,退避结束后你会用同样的速度再撞一次,重试次数越多越糟。
- 第二个动作是别在退避里占着执行流。正确做法是把任务重新入队并记一个「不早于」时间戳,槽位立刻还回去给别的任务。
- 第三个动作是分类:限流和服务端错误可以重试,鉴权失败、余额不足、参数错误、内容审核不通过一次都不该重试——重试只会让你在一分钟里把同一个错误犯五遍,还白占配额。MiniMax 这边 1002 是限流、1039 是 TPM 维度的限流,1004 鉴权、1008 余额、2013 参数、1026 和 1027 是内容审核。
- 第四个动作是把限流次数记进指标。撞得多说明闸门配小了或者配大了,这个数字是你回头调参数的唯一依据。
- 可预期的追问是「退避上限怎么定」。定在业务能等的时间上,超过就转降级:换更小的分辨率、更短的时长,或者干脆排到下一批。
Key points
- Treat throttling as a signal: back off and also penalize the bucket so the next window issues fewer tokens.
- Requeue with a not-before timestamp instead of sleeping inside the worker slot.
- Add jitter, or everything throttled together wakes together and collides again.
- Separate retryable from non-retryable: auth, balance, bad parameters and content rejections get zero retries.
- Emit a throttle counter as a metric, and switch to degradation once backoff hits its ceiling.
答题要点
- 把限流当信号:退避的同时给令牌桶降档,接下来的窗口只发一半令牌。
- 退避期间把任务重新入队并记一个不早于时间戳,工作槽立刻还回去。
- 退避要带抖动,否则同时被限的任务会同时醒来再撞一次。
- 严格区分可重试与不可重试:鉴权、余额、参数、内容审核一次都不重试。
- 把限流次数记成指标,它是回头调闸门参数的唯一依据;退避到上限就转降级而不是继续重试。
Priority queues starve low-priority work. How do you prevent that?优先级队列容易出现饿死,你会怎么防?
Common in ChinaCommon overseasBasic#scheduling#priority-queue#fairnessHow to reason about it · think before answering
- This is the easy one, and most candidates stop after saying aging. The signal is in the two conditions they forget to attach.
- The mechanism first: aging, where effective priority rises with waiting time, one step per threshold crossed, with first-in-first-out inside a tier.
- Condition one: cap the promotion, and never let it reach the top tier. Otherwise after half an hour every queued job is top priority and the tier means nothing. Our rule is that low may rise to normal, and the top tier stays reserved for human escalation.
- Condition two is the one people miss: if a job waits for resources after dispatch, priority silently stops working, because worker slots are pinned by low-priority jobs waiting on quota and the urgent job is never picked up. Admission must happen before dispatch.
- Close with the observable: track average and maximum wait per tier plus a promotion counter. Those two numbers tell you directly whether the aging threshold is right.
- Expected follow-up: alternatives to aging. Reserved shares work too, where every fourth dispatch must go to a low-priority job. That is weighted fair queuing, more controllable but noisier to implement.
分析过程 · 先想清楚再作答
- 这题是送分题,但很多人只答一个「老化」就停了,拿不到区分度。区分度在两个补充条件上。
- 先说机制:老化,也就是等待越久有效优先级越高,每等过一个阈值就升一档。同档内按入队时间先来先服务。
- 第一个补充条件是升档要封顶,而且不许升进最高那一档。否则跑上半小时,队列里全是最高优先级,这一档就名存实亡了。本课的口径是低优先最多升到普通,最高档只留给人工插队。
- 第二个补充条件更容易被忽略:如果任务被派出去之后才开始等资源,优先级会静默失效——工作槽被一批低优先任务占着等资源,高优先任务连被取走的机会都没有。所以准入要在调度之前完成。
- 结论里要给出可观测量:按优先级统计平均等待与最长等待,再加一个升档次数。这两组数字能直接告诉你老化阈值配得对不对。
- 可预期的追问是「除了老化还有别的办法吗」。有:给低优先级预留一部分固定配额(比如每四次调度必须让一个低优先的过),这是加权公平调度的思路,比老化更可控但实现更啰嗦。
Key points
- Use aging: effective priority rises with wait time, first-in-first-out within a tier.
- Cap promotion and never let it reach the top tier, or the top tier stops meaning anything.
- Admit before dispatch, otherwise worker slots pinned on quota make priority silently useless.
- Track per-tier average wait, max wait and promotion count, and tune the aging threshold from those.
- The alternative is weighted fair queuing with a reserved share for low priority: more controllable, more code.
答题要点
- 用老化:等待时间越长有效优先级越高,同档内先来先服务。
- 升档要封顶,绝不能升进最高那一档,否则最高档形同虚设。
- 准入要放在调度之前,否则工作槽被低优先任务占着等资源,优先级会静默失效。
- 按优先级统计平均等待、最长等待与升档次数,用它来校准老化阈值。
- 备选方案是给低优先级预留固定份额的加权公平调度,比老化更可控但实现更复杂。
D10 The Review Room: A Human-in-the-Loop Backend for Previewing, Editing Lines, and Regenerating a Single Shot
Where would you place human review checkpoints in an automated pipeline, and why there?一条自动化流水线要插入人工审核,你会把卡点放在哪几步?为什么?
Common in ChinaCommon overseasBasic#human-in-the-loop#pipeline-design#costHow to reason about it · think before answering
- This one tests cost awareness. Saying a human should look at every step marks someone who has not run this in production: humans are the expensive resource, and too many gates turn a pipeline back into handwork.
- Offer a reusable rule: put the gate immediately before the most expensive downstream step. To decide whether a position deserves a gate, ask how much money is wasted if something is wrong here.
- Applied to a generative pipeline that yields three positions: after the script is locked (free to change, yet it steers every asset that follows), after the first frame but before video generation (the frame is the cheapest step and the clip is the most expensive, one to two orders of magnitude apart), and after the final cut but before publishing (this one gates risk, not quality).
- Add the production view: a checkpoint is not necessarily blocking. The first two can auto-continue on timeout; only the compliance gate must hard-block, because you cannot let a legal check pass by timing out.
- State the counterintuitive part: the first gate is the one people skip, because there are no visuals yet and it looks like there is nothing to review, while it is the only gate where changes cost nothing.
- Expected follow-up: what if reviewers cannot keep up. Tier it. Machines score everything, humans only see the low scores, and human attention goes where the machine is unsure.
分析过程 · 先想清楚再作答
- 这题在考你有没有成本意识。答「每一步都让人看一眼」是没做过工程的回答——人是最贵的资源,卡点多了流水线就退化成手工作坊。
- 给一条可复用的判据:**卡点放在「下游最贵的那一步」之前**。判断某个位置该不该设卡,只问一句「如果这里错了,往后要白花多少钱」。
- 按这条判据落到生成式流水线上,会得到三个位置:剧本定稿之后(此时零成本,却决定了后面所有素材的方向)、首帧出来之后视频生成之前(首帧是最便宜的一档,视频是最贵的一档,同一个镜头差出一到两个数量级)、成片合成之后发布之前(这一道拦的不是质量而是合规风险)。
- 补一条生产视角:卡点不等于阻塞。第一和第二道可以做成「默认放行、超时自动继续」,只有第三道必须硬卡——合规问题不能靠超时放行。
- 结论里要点出一个反直觉的事实:最容易被跳过的恰恰是第一道,因为这时候还没有画面,看起来没什么可审的;但它是唯一一道改起来零成本的闸门。
- 可预期的追问是「人来不及审怎么办」。答案是分级:机器先打分,只把低分的推给人,人的时间花在机器拿不准的那部分上。
Key points
- Rule: place the gate right before the most expensive downstream step, judged by wasted spend if this step is wrong.
- Three positions: after script lock, after first frame and before video, after final cut and before publish.
- The first-frame gate pays best: the frame is the cheapest step and the clip the most expensive, one to two orders of magnitude apart.
- The first two gates can auto-continue on timeout; only the compliance gate hard-blocks.
- When reviewers are the bottleneck, tier it: machines score everything, humans only see low scores.
答题要点
- 判据是「卡点放在下游最贵的那一步之前」,问的是这里错了往后白花多少钱。
- 三个位置:剧本定稿后、首帧出来后视频生成前、成片合成后发布前。
- 首帧那一道性价比最高:首帧是最便宜的一档,视频是最贵的一档,同一个镜头差出一到两个数量级。
- 前两道可以默认放行加超时继续,只有合规那一道必须硬卡。
- 人力不够就分级:机器先打分,人只看低分的那些。
A user edits an intermediate input. How do you compute which downstream steps must rerun?用户改了中间一步的输入,怎么算出哪些下游需要重做?
Common in ChinaCommon overseasIntermediate#dag#incremental-recompute#costHow to reason about it · think before answering
- The signal lives at the two ends. Most candidates produce the middle part, propagation over a dependency graph, and drop both the direction and the finish.
- Direction: propagate forward along who-depends-on-me from the edited node, not backward to its dependencies. Getting it backward is insidious, because upstream nodes rerun, the output is still correct, the bill doubles, and no test catches it.
- Implementation: seed a set, sweep the graph repeatedly adding any node with a dependency already in the set until it stops growing, then return in topological order so the caller can just walk the array.
- The finish is what people forget: unaffected nodes must have their artifacts copied from the previous version, not regenerated. A perfect radius saves nothing without that copy.
- Then verification. Do not compare file hashes, because identical inputs often produce byte-identical output and a matching hash proves nothing. Count API calls instead; that evidence holds both offline and against a real vendor.
- Expected follow-up: what about forcing a rerun when nothing changed. Keep an explicit force flag and account for it separately, or you lose the ability to tell system-decided reruns from human-triggered ones.
分析过程 · 先想清楚再作答
- 这题的区分度在方向和收尾两处,很多人只答出中间那段「沿依赖图传播」,前后都丢了。
- 方向:从被改的节点**沿着「谁依赖我」正向传播**,不是往上游找依赖。写反的后果很隐蔽——上游会被一起重跑,结果是对的,钱多花了一倍,测试也发现不了。
- 落到实现:把种子节点放进集合,反复扫一遍图,只要某个节点的依赖里有一个已经在集合里就把它也加进来,跑到不动点为止;最后按拓扑序返回,调用方顺着数组跑就不会先跑下游后跑上游。
- 收尾这一步最容易漏:**没受影响的节点,产物要从上一版复制过来,不是重新生成**。半径算得再准,少了复制这一步就一分钱没省。
- 然后是怎么验证。不要比文件哈希——同样的输入很可能生成逐字节相同的结果,哈希相同证明不了没重跑。要数**接口调用次数**,这才是硬证据,而且在离线与真实两种模式下都成立。
- 可预期的追问是「输入没变但你想重跑怎么办」。留一个强制重跑的开关,并且把它和自动判定分开记账,否则你会分不清一次重跑是系统判的还是人手动点的。
Key points
- Propagate forward along who-depends-on-me from the edited node, never backward.
- Sweep to a fixed point and return in topological order so execution never runs downstream first.
- Copy artifacts for unaffected nodes from the previous version, or the computed radius saves nothing.
- Verify by counting API calls, not by comparing file hashes, since identical inputs can produce byte-identical output.
- Keep a separate force-rerun switch and account for it apart from automatic decisions.
答题要点
- 从被改的节点沿着「谁依赖我」正向传播,不是反向找依赖。
- 扫图到不动点,结果按拓扑序返回,保证执行顺序不会颠倒。
- 没受影响的节点要从上一版复制产物,否则半径算得再准也没省钱。
- 验证要数接口调用次数,不要比文件哈希——同样的输入可能产出逐字节相同的结果。
- 另留一个强制重跑开关,并与自动判定分开记账。
What must a version record hold for rollback? Are the final artifacts enough?版本回滚要存什么?只存最终产物够不够?
Common in ChinaCommon overseasIntermediate#versioning#rollback#data-modelingHow to reason about it · think before answering
- The hinge is are they enough, which signals the answer is no. Restate it as a claim: a version is not a backup. Saying that sentence gets you half the credit.
- The semantics differ. A backup means restore after an incident and only needs the latest good state. A version means both exist, side by side, switchable, with a human choosing. Review workflows need the latter.
- So each version stores three things: the artifacts themselves, kept in per-version directories with nothing deleted; the inputs that produced them, the line and the visual description, or nobody can explain the difference three days later; and which nodes reran plus why.
- The current version should be a pointer, not a copy. Rollback moves the pointer without touching files, which makes it instant and reversible, and makes switching forward again equally natural.
- Mention the knock-on effect, because it shows you have actually shipped this: rolling back one shot changes the whole episode timeline. If the new take of the voice is a second longer, every later shot shifts, so rollback must recompute the timeline. That part is cheap local computation.
- Expected follow-up: how long to keep versions. Scale it by artifact size and business value: keep small text forever, put a retention window on video, and after expiry keep only metadata and inputs so the artifact can be regenerated on demand.
分析过程 · 先想清楚再作答
- 题眼在「够不够」三个字,它在提示答案是否定的。先把问题重述成一句判断:**版本不是备份**,这句话说出来这题就答对了一半。
- 两者的语义不一样。备份是「出事了拿回来」,只需要保留最近一份好状态;版本是「两个都在」,要能并排对比、来回切换,最终选哪个由人定。审核场景要的是后者。
- 所以每个版本要存三类东西:产物本身(按版本分目录,一个文件都不删)、产生它的输入(那一版的台词与画面描述,否则三天后没人说得清两版差在哪)、以及这一版重跑了哪些节点与原因。
- 当前版本要设计成一个指针,不是一份拷贝。回滚就是把指针挪回去,不搬文件,因此是瞬时且可逆的;这也让「再切回新版本」变成理所当然的操作。
- 有一个连带影响必须提到,提了就说明你真做过:**回滚一镜会改变整集的时间轴**。新版配音比旧版长一秒,切回去之后后面所有镜头的起止时间都要重排。所以回滚之后要重算一次时间轴,好在这是纯本地计算,很便宜。
- 可预期的追问是「版本存多久」。按产物体积和业务价值定:小文本无限存,视频这种大件设一个保留期,过期只留元数据和输入,需要时可以按同样的输入重跑出来。
Key points
- A version is not a backup: backups keep the latest good state, versions keep old and new side by side.
- Store three things per version: artifacts in per-version directories, the inputs that produced them, and which nodes reran and why.
- Make the current version a pointer, not a copy, so rollback is instant and reversible.
- Rolling back one shot shifts the episode timeline, so recompute it after rollback; it is cheap local work.
- Set retention by size: keep text forever, expire large video and retain metadata plus inputs for regeneration.
答题要点
- 版本不是备份:备份只要最近一份好状态,版本要求新旧同时存在、能并排对比。
- 每版要存三类:产物(按版本分目录、不删)、产生它的输入、重跑的节点与原因。
- 当前版本是指针不是拷贝,回滚只挪指针,瞬时且可逆。
- 回滚一镜会改变整集时间轴,回滚后要重算一次——这是纯本地计算,很便宜。
- 保留策略按体积分级:文本长期留,大视频设保留期,过期只留元数据与输入以便按需重跑。
D11 Quality Control and Compliance: Machine Review, Content Safety, Generated-Content Labeling, and Copyright Boundaries
How do you turn a subjective judgment like visual quality into an automatable check?怎么把画面质量这种主观判断变成可自动判定的检查?
Common in ChinaCommon overseasIntermediate#quality-check#evaluation#multimodalHow to reason about it · think before answering
- This tests decomposition. Answering just use a multimodal model to score it covers only the lazy half; the interviewer wants to see you turn a non-falsifiable statement into checkable ones.
- Step one is classification: split checks into locally measurable and must-be-seen-by-a-model. Resolution, audio-video duration delta, subtitle length and reading rate, and loudness all have deterministic answers from ffprobe plus arithmetic. Character consistency and visual breakdown have no reliable local proxy.
- The classification pays off in accounting: a failing objective check means the file really is wrong, while a failing subjective one might just mean the model misread. Merge them into one score and you cannot tell whether to fix the file or the prompt.
- Step two gives every check three things: what is measured, the threshold, and the corrective action. The third is the one people skip and the one that matters, because a check that fails without a prescribed fix is decoration.
- Step three handles model-side uncertainty: demand a structured verdict, and when it cannot be parsed mark the item as no-conclusion, needs-human, never as a pass. Conflating the model said fine with the model did not answer is the classic automated-QC incident.
- Expected follow-up: how to set thresholds. Backtest against human-reviewed samples and pick the threshold where machine and human verdicts agree most. Without that data you are guessing.
分析过程 · 先想清楚再作答
- 这题在考拆解能力。直接答「让多模态模型打分」只答了一半,而且是偷懒的那一半——面试官想看你怎么把一个不可判真假的命题拆成可判定的。
- 第一步是分类:把检查项分成「本地量得出来的」和「必须让模型看图的」。分辨率、音画时长差、字幕字数与每秒字数、配音响度,这四类用 ffprobe 加几行算术就有确定答案;角色一致性、画面崩坏则本地没有可靠代理指标。
- 分类的价值是账算得清:客观项出问题一定是文件真有毛病,主观项出问题可能是模型看错了。混成一个总分,事故来的时候分不清该修文件还是修提示词。
- 第二步是给每一项配齐三样:测量对象、阈值、**修正动作**。第三样最容易漏也最关键——一项检查不合格却说不出该怎么办,它就是摆设,你只能记一行日志继续往下走。
- 第三步是处理模型那一侧的不确定性:要求它只返回结构化结论,并且**解析不出来时标成无结论、需人工,绝不当成通过**。把「模型说没问题」和「模型没答上来」混为一谈,是自动质检里最常见的事故。
- 可预期的追问是「阈值怎么定」。用人工审核攒下来的带结论的样本回测,看阈值定在几分时机器结论与人的重合度最高;没有这份数据就只能拍脑袋。
Key points
- Classify first: objective local measurements (resolution, av delta, subtitle density, loudness) versus model-only judgments (character consistency, visual breakdown).
- Give every check a measurement, a threshold and a corrective action; a check with no action is decoration.
- Require a structured verdict from the model, and treat unparseable output as needs-human, never as a pass.
- Set thresholds by backtesting against human-reviewed samples.
- An objective failure means the file is wrong; a subjective failure may mean the model misread. That split drives triage.
答题要点
- 先分类:本地量得出来的客观项(分辨率、音画差、字幕密度、响度)与必须看图的主观项(角色一致性、画面崩坏)分开记账。
- 每一项配齐三样:测量对象、阈值、修正动作;没有修正动作的检查项是摆设。
- 模型评审要求返回结构化结论,解析失败标成需人工,绝不默认通过。
- 阈值靠人工审核样本回测确定,不拍脑袋。
- 客观项失败说明文件有问题,主观项失败可能是模型看错——这个区分决定了排查方向。
Should content safety checks run before generation or after? Why both?内容安全审核放在生成前还是生成后?为什么两边都要有?
Common in ChinaCommon overseasBasic#content-safety#moderation#pipeline-designHow to reason about it · think before answering
- The answer is both, but the marks come from explaining that the two gates defend against different things. Saying defense in depth is safer earns nothing.
- The pre-check inspects the prompt you are about to send, and it saves money and account standing: a violating prompt gets rejected by the vendor's own moderation (1026 or 1027 at MiniMax), wasting a round trip, and repeated hits can trip risk controls. It is a local word list plus rules, milliseconds, and each catch saves a call.
- The post-check inspects what the vendor returned, and it matters more, because a clean prompt does not imply a clean result. Generative models improvise: you ask for a convenience store and get a shelf of branded packaging. The pre-check only proves you did not ask for it; the post-check protects the viewer.
- When the pre-check fires, do not just throw. Offer a replacement and keep going: swap the matched fragment for safe wording, print it, and record it so a human can see which line was changed and how.
- Call out the common misconception: vendor moderation does not replace yours. The vendor moderates its own risk, with different boundaries, and publishing liability sits with you.
- Expected follow-up: what to do when the post-check fails. Triage by severity: auto-fixable issues get fixed and only that node reruns; anything else blocks publishing and goes to a human. Never wave it through because the money is already spent.
分析过程 · 先想清楚再作答
- 这题的正确答案是「两边都要」,但拿分的关键不在结论,而在你能不能说清两道关**防的是不同的事**。答成「双重保险更稳妥」就是没答。
- 前置那道查的是你要发出去的提示词,省的是钱和账号:违规提示词发过去会命中厂商审核被拒(MiniMax 这边返回 1026 或 1027),白等一轮,严重的会触发风控。它是一层本地词表加规则,几毫秒,拦一条省一次调用。
- 后置那道查的是厂商还给你的成片,它更重要,理由是**提示词干净不代表结果干净**——生成模型会自己加戏,你写便利店门口,它可能给你摆一整面货架的品牌包装。前置只保证你没主动要,后置才保证观众看到的没问题。
- 前置被拦下之后不能只抛异常,要给替代方案并让流程继续:把命中片段替换成安全表述、打印出来、记进报告,人回头能看到哪一句被改成了什么。
- 还要点破一个常见误解:**厂商的审核不能替代你的审核**。厂商审的是它自己的合规风险,边界跟你的业务不同;而且发布责任在你,出事找的是发布者。
- 可预期的追问是「后置发现问题怎么办」。按严重程度分流:能自动修的(比如字幕里的词)就修完重跑那一个节点,修不了的直接拦住不许发布并推给人工,绝不能因为已经花了钱就放行。
Key points
- Both, because they defend different things: the pre-check saves spend and account standing, the post-check protects viewers and compliance.
- The pre-check is a local rule pass in milliseconds; on a hit, substitute safe wording instead of throwing and halting the line.
- The post-check matters more, because a clean prompt does not guarantee a clean result.
- Vendor moderation covers the vendor's risk, not yours; publishing liability stays with you.
- Triage post-check failures: auto-fix and rerun that node, or hard-block and escalate.
答题要点
- 两道都要,因为防的事不同:前置省钱与账号,后置保护观众与合规。
- 前置是本地词表加规则,几毫秒,拦下一条就省一次调用;命中要给替代写法而不是抛异常停线。
- 后置更重要:提示词干净不代表结果干净,模型会自己加戏。
- 厂商的审核只兜它自己的风险,不能替代你的,发布责任在你。
- 后置发现问题按严重度分流:能自动修的修完重跑该节点,修不了的硬拦并推人工。
Before publishing AI-generated video, what compliance work is mandatory?AI 生成的视频对外发布,合规上你必须做哪几件事?
Common in ChinaDeep dive#compliance#labeling#copyrightHow to reason about it · think before answering
- In China-market roles this is a hard requirement, and missing the explicit-plus-implicit labeling pair usually ends the interview. It also tests whether you read the source text rather than someone's summary.
- Give the legal coordinates: the Measures for Labeling AI-Generated Synthetic Content, issued jointly by four authorities, in force from 1 September 2025, with the mandatory national standard GB 45438-2025 on labeling methods taking effect the same day.
- Then define both labels close to the source text. An explicit label is added in the content or the interaction interface, presented as text, sound or graphics, and clearly perceivable by the user. An implicit label is added by technical means into the content file data and is not easily perceivable. Both are required, not either-or, and providers are expected to add the implicit label in file metadata.
- Show you can ship it: write the implicit label into container metadata with ffmpeg's metadata option and read it back with ffprobe, because writing without verifying is the same as not writing. The explicit label is safest burned into the picture, but burning text needs ffmpeg's drawtext filter, which minimal builds often lack, so detect the capability, degrade deliberately, and log the degradation.
- Add the three items beyond labeling: do not maliciously delete, alter, forge or hide labels; do not generate the likeness of real people; and keep licensed sources for background music and reference assets. Short-form drama additionally needs tiered review by budget, with the license or filing number shown in the opening.
- Expected follow-up: which metadata fields exactly. The honest answer is to follow the GB 45438-2025 text itself rather than field names circulating in third-party summaries, and that answer scores far better than inventing a schema.
分析过程 · 先想清楚再作答
- 这题在国内岗位上是硬考点,答不出「显式与隐式两类标识」基本就出局了。它同时也在考你是不是真读过原文,而不是转述别人的解读。
- 先给法规坐标:《人工智能生成合成内容标识办法》由四部门联合发布,自 2025 年 9 月 1 日起施行;配套的强制性国标是 GB 45438-2025《网络安全技术 人工智能生成合成内容标识方法》,同日实施。
- 然后给两类标识的定义,尽量贴原文:显式标识是在生成合成内容或者交互场景界面中添加的、以文字声音图形等方式呈现并可以被用户明显感知到的标识;隐式标识是采取技术措施在生成合成内容文件数据中添加的、不易被用户明显感知到的标识。**两者都要做,不是二选一**,并且服务提供者应当在文件元数据中添加隐式标识。
- 落地上要能说出具体做法:隐式标识写进容器元数据,用 ffmpeg 的 metadata 参数写、用 ffprobe 读回来验证,写了不读等于没写;显式标识最稳是烧进画面,但烧字依赖 ffmpeg 的 drawtext 滤镜,很多最小编译版本没有,所以要先探测能力再降级,并且把降级这件事明确打印出来。
- 还要补上标识之外的三条:不得恶意删除篡改伪造隐匿标识;不得生成真实人物形象;背景音乐与参考素材必须有授权来源。做微短剧还要按投资额分级审核,上线前片头标注许可证号或备案号。
- 可预期的追问是「元数据具体写哪些字段」。诚实的回答是以 GB 45438-2025 正式文本为准,第三方解读里流传的字段名不能直接照抄——这个回答比编一串字段名得分高得多。
Key points
- The labeling Measures take effect 1 September 2025, alongside mandatory national standard GB 45438-2025.
- Explicit labels are clearly perceivable by users; implicit labels live in the file data. Both are required.
- Providers add the implicit label into the content file metadata, and nobody may maliciously delete, alter, forge or hide labels.
- In practice: verify metadata by reading it back, prefer burned-in explicit labels, and log any capability-driven degradation.
- Also: no likenesses of real people, licensed music and source assets, and for short-form drama tiered review plus a license or filing number in the opening.
答题要点
- 《人工智能生成合成内容标识办法》2025 年 9 月 1 日起施行,配套强制性国标 GB 45438-2025 同日实施。
- 显式标识是用户能明显感知到的(文字声音图形),隐式标识加在文件数据里,两者都要做。
- 服务提供者应当在生成合成内容的文件元数据中添加隐式标识;不得恶意删除篡改伪造隐匿标识。
- 落地:元数据写入后必须读回验证;显式标识优先烧录,能力不足时降级并明确记录。
- 另外三条:不得生成真实人物形象、背景音乐与素材要有授权、微短剧按投资额分级审核且片头标注许可证号或备案号。
D12 Cost and Model Routing: Choosing a Model per Stage, Caching, Degradation, and a Budget Circuit Breaker
How do you break down the cost of a content-generation pipeline, and which stage would you optimize first?一条内容生成流水线的成本要怎么拆?拆完你会先优化哪一环,为什么?
Common in ChinaCommon overseasBasic#cost-analysis#observabilityHow to reason about it · think before answering
- This question checks whether you have actually read a bill. Answering with generic advice like use more caching signals you never ran this in production; naming the breakdown dimensions and rough ratios signals you did.
- Establish the dimensions first: by stage (script, image, video, speech), by billing unit (per second, per item, per character, per token), and by billable status (succeeded, cache hit, failed and not charged). Drop any one of them and a whole class of spend becomes invisible.
- Then give orders of magnitude. Video is billed per second, so a dozen seconds already costs a few yuan, while images are cents per item, speech is fractions of a cent per character, and text is lower still. Video typically dominates at over ninety percent.
- So the priority is driven by what is expensive, not by what is easy to change. Attack video first, cheapest lever to most expensive: caching and idempotency, tiered routing with a cheap draft tier, degradation across resolution, duration and shot count, and only then vendor negotiation.
- Add a credibility note: never put an unverified unit price in the table. Mark derived prices as estimates and leave unpublished ones blank while still counting usage. Reporting an estimate as an official price is how these projects lose trust.
- Expect the follow-up: how do you prove the optimization worked? Run the same input twice and compare the per-stage panel, not the monthly invoice, which mixes in traffic you did not cause.
分析过程 · 先想清楚再作答
- 这题在考你有没有真的看过账单。凭感觉答「多用缓存、少调模型」的人一听就没做过;能说出「按什么维度拆、拆出来大概什么比例」的才是。
- 拆的维度要先立住:按环节(脚本、图像、视频、语音)、按计价单位(按秒、按张、按字符、按 token)、按是否计费(成功、命中缓存、失败未扣费)。三个维度缺一个,报表就会有一类花费永远看不见。
- 然后给数量级。多媒体生成这类流水线里视频按秒计价,一集十几秒就是几块钱;图像按张几分钱、语音按字符几厘钱、文本更低。结论是视频通常占九成以上,其余全是零头。
- 所以优化顺序不是「哪一环最容易优化」,而是「哪一环最贵」。先优化视频,手段按代价从低到高排:缓存与幂等(不重复调)、分档路由(草稿档用便宜规格)、降级(清晰度、时长、镜头数)、最后才是换厂商谈价。
- 补一句可信度:不确定的单价不要写进表。官方只给资源包价的档位要标明是折算值,官方没公开的档位就留空只统计用量——把估算值当官方价报上去,是这类项目最常见的翻车点。
- 可预期的追问是「那怎么证明优化生效了」。答案是同一份输入跑两遍,对照面板上按环节的金额与调用次数,而不是看月账单——月账单里混着别人的流量,归因不到你这次改动。
Key points
- Break it down three ways: by stage, by billing unit, and by whether the call was actually charged
- Lead with the ratio: video is billed per second and usually exceeds ninety percent of per-episode cost
- Optimize expensive first: caching and idempotency, tiered routing, degradation, vendor negotiation last
- Leave unknown unit prices blank while still counting usage, and label derived prices as estimates
- Validate by running the same input twice and diffing the per-stage panel, not the monthly invoice
答题要点
- 按三个维度拆:环节、计价单位、是否真的计费(成功 / 缓存命中 / 失败未扣费)
- 先给比例再给结论:视频按秒计价,通常占单集成本九成以上,其余是零头
- 优化顺序由贵到便宜:缓存与幂等、分档路由、降级、最后才谈价换厂商
- 拿不到的单价宁可留空只统计用量,折算出来的要标明是折算值
- 验证靠同一份输入跑两遍对照面板,不看混杂的月账单
When should you degrade instead of retry, and which dimensions can you degrade first?什么情况下该降级而不是重试?如果决定降级,你有哪些维度可以降,怎么排先后?
Common in ChinaCommon overseasIntermediate#degradation#retry-strategyHow to reason about it · think before answering
- The pivot is the word instead. This tests whether you separate two failure classes: retry addresses bad luck this time, degradation addresses cannot finish under this configuration. Answering retry three times then degrade misses the point.
- Give a reusable rule: retry fixes transient, configuration-independent problems such as rate limits, timeouts and server errors. Degradation fixes persistent, constraint-driven ones such as running out of budget, quota or time. Retrying the second class just burns resources faster.
- Name the class most people get wrong: a content-safety block should be neither retried nor degraded, it needs a changed input. Conflating the three is the biggest scoring mistake here.
- Order degradation dimensions by how noticeable they are, least to most: resolution, duration, then count of items. Touch the one that changes the content itself only as a last resort.
- Add an engineering rule: validate every degradation step against the cost model. If a step saves nothing on your rate card, degrading quality buys you nothing and should be skipped.
- Expect the follow-up: when do you decide? Project the cost with a pure function before the run starts and degrade up front. Cutting mid-run leaves a half-finished artifact and wastes everything already spent.
分析过程 · 先想清楚再作答
- 题眼在「而不是」三个字。它考的是你能不能区分两类失败:重试针对的是「这次不巧」,降级针对的是「按当前配置根本跑不完」。答成「先重试三次再降级」就落进了套路。
- 给一条可复用的判据:重试解决的是**瞬时**且**与配置无关**的问题(限流、超时、服务端 5xx),降级解决的是**持续**且**由约束导致**的问题(预算不够、配额见底、截止时间快到了)。前者重试有效,后者重试只会把资源烧得更快。
- 顺带点出最容易被答错的一类:内容安全拦截既不该重试也不该降级,它要改输入。把三类混在一起是这题最大的失分点。
- 降级的维度要按「用户察觉难度」排,从低到高:清晰度、时长、数量(镜头数 / 条数)。先降察觉不到的,最后才动会影响内容本身的那一档。
- 还有一条工程判据:每一步降级都要拿成本模型验证一遍。如果某一档在你的单价表上省不出钱(比如更低的清晰度和当前档同价),那这一步降了只有损失,应该直接跳过。
- 可预期的追问是「降级要在什么时候决定」。答案是开跑之前先用纯函数预估一遍,算不过就降完再跑——跑到一半再砍,会留下半成品,前面花的钱全打水漂。
Key points
- Retry transient configuration-independent failures; degrade when the constraint makes completion impossible
- Content-safety blocks are a third class: change the input rather than retrying or degrading
- Order degradation by noticeability: resolution, duration, item count, content last
- Validate each degradation step against the rate card and skip steps that save nothing
- Decide before the run starts; cutting mid-run leaves a half-finished artifact and wastes prior spend
答题要点
- 重试针对瞬时且与配置无关的失败,降级针对持续且由约束导致的不可完成
- 内容安全拦截是第三类:既不重试也不降级,要改输入
- 降级维度按察觉难度排:清晰度、时长、数量,最后才动内容本身
- 每一步降级都要拿成本模型验证,省不出钱的那一步直接跳过
- 降级要在开跑前决定,跑到一半再砍会留下半成品且前面的钱白花
How would you design a budget circuit breaker for a pipeline that calls paid APIs, and what makes the stop safe?给一条会调用付费接口的流水线加预算熔断,你会怎么设计?做到什么程度才算安全停机?
Common in ChinaCommon overseasDeep dive#budget-control#circuit-breakerHow to reason about it · think before answering
- The discriminator is the word safe. Most candidates can say stop when over budget; what the interviewer wants is the state the system is left in afterwards.
- Rule one: the check happens before you spend. Use reservation-style accounting, projecting each paid call with a pure function and deducting it from the budget before issuing the request. After-the-fact accounting only tells you that the money is already gone.
- Rule two: the two thresholds do different jobs. A soft limit warns once so a human can decide whether to continue or downgrade; a hard limit must actually stop. Setting both to the same value means you have no soft limit.
- Rule three defines a safe stop, and all three parts are required: keep every finished artifact, persist the ledger and the point of interruption, and write the cache. Miss any one and the next run with a higher budget pays again for work already paid for, turning the breaker into a waste amplifier. Calling exit is therefore wrong.
- Rule four is refunds: if the vendor does not charge for failed or safety-blocked calls, the reserved amount must be released, otherwise you overstate the bill and silently consume headroom. Mark those ledger rows separately and show them as their own line on the panel.
- Two follow-ups to expect. Under concurrency the reservation must be atomic, so a shared counter needs a single owner or an atomic operation or you will oversell. And the limits themselves should be derived from historical usage through the same projection function, not guessed.
分析过程 · 先想清楚再作答
- 这题的区分度全在「安全」两个字。多数人能答出「超预算就停」,但停完之后系统处在什么状态,才是面试官真正想听的。
- 先立第一条:判断必须发生在花钱之前。做法是预留式记账——每次调用付费接口前用一个纯函数预估这笔花费,从预算里扣,扣得动才发请求。事后统计只能告诉你已经超了,那时钱已经出去了。
- 第二条是两级上限的分工:软上限只提醒且只提醒一次,作用是让人在还有余地时决定继续还是降档;硬上限必须真的停。把两者做成同一个阈值,等于没有软上限。
- 第三条才是「安全停机」的定义,三个都要满足:已完成的产物一个不删、账本与停在哪一步落盘、缓存写入。少了任何一条,下一次带更高预算重跑就要把已经花掉的钱再花一遍——熔断反而成了浪费的放大器。所以直接退出进程是错的。
- 第四条是退款口径:失败或被内容安全拦下的调用如果厂商不计费,预扣的额度必须退回来,否则你会一边高估账单一边白占预算。台账上这类记录要单独标出来,面板上单独一行。
- 可预期的追问有两个。一是「并发下怎么保证不超」——预留必须是原子的,多个 worker 共享一个计数器时要走单点或原子操作,否则会超卖。二是「上限设多少」——用同一个预估函数按历史用量反推,而不是拍脑袋。
Key points
- Reserve before you spend: project the cost, deduct it, and skip the call if it does not fit
- The soft limit warns once for a human decision; the hard limit must actually stop, with different thresholds
- A safe stop keeps artifacts, persists the ledger and resume point, and writes the cache; never just exit
- Release reservations for calls the vendor does not charge for, and show them as a separate ledger line
- Make reservations atomic under concurrency and derive limits from historical usage via the same projector
答题要点
- 预留式记账:调用付费接口前先预估并扣减,扣不动就不发请求
- 软上限只提醒一次供人决策,硬上限必须真的停,两者阈值必须不同
- 安全停机三条:产物保留、账本与断点落盘、缓存写入,绝不直接退出进程
- 厂商不计费的失败调用要退回预扣额度,并在台账与面板上单独标出
- 并发下预留必须原子;上限用同一个预估函数按历史用量反推
D13 Distribution: Adapting to Multiple Platform Specs, Generating Covers and Titles, Batch Export, and Feeding Data Back
The same video has to be published to several platforms with different specs. How do you design the export flow to minimize transcoding?同一条视频要发多个平台,每个平台规格不同,你会怎么设计导出流程才能少转码?
Common in ChinaCommon overseasIntermediate#media-pipeline#ffmpegHow to reason about it · think before answering
- This question tests whether you distinguish transcoding from remuxing. Rendering once per platform is not a coding failure, it is a failure to notice that every lossy re-encode costs quality.
- Separate the two: transcoding decodes and re-encodes, so the picture data is genuinely recompressed; remuxing just moves an already-encoded bitstream into another container without touching a byte. One takes seconds and loses quality, the other takes milliseconds and is lossless.
- Then give the flow: render one master using the most conservative parameters that satisfy every target, then run each platform through a decision function and stream-copy whenever possible. Container changes, faststart and duration trims all stay within stream copy.
- Know the cases that truly require re-encoding: out-of-range resolution, an unaccepted codec, a frame rate outside the allowed band, and a file that exceeds the size cap. Duration is the one people misjudge most, since -t with stream copy already trims it.
- Add an engineering rule: the decision function should return a list of reasons, not just a boolean. When someone asks why a platform got re-encoded, you answer from the log rather than rereading the code.
- Expect the follow-up: how do you prove it? Print an encode counter alongside the count the naive approach would have produced. A number without a baseline convinces nobody.
分析过程 · 先想清楚再作答
- 这题在考你分不分得清转码与封装。答成「按每个平台各渲染一遍」的人不是不会写代码,是没意识到有损编码每转一次就掉一次画质。
- 先把两个词分开:转码是重新解码再编码,画面数据真的被压了一遍;封装只是把已编好的码流换个容器,一个字节都没动。前者要几秒到几十秒并且掉画质,后者几十毫秒且无损。
- 然后给流程:先渲染一份母版,参数取所有目标平台的交集里最保守的一档;之后每个平台走一次判定函数,能流复制就流复制。换容器、加 faststart、按时长截断都属于流复制的范围。
- 必须重编码的情况要能背出来:分辨率越界要缩放、编码格式不被接受、帧率超范围、文件大小超限要降码率。除此之外都不该重编码——尤其时长超限这一条最容易被误判,其实 -t 配流复制就能切。
- 补一条工程判据:判定函数要返回理由列表,不只是布尔值。出片之后有人问「为什么这个平台转了码」,你要能拿日志回答,而不是重新读一遍代码。
- 可预期的追问是「怎么证明真的少转了」。答案是打印一个编码次数计数器,并同时给出朴素做法的次数做对照——没有对照的数字说服不了任何人。
Key points
- Separate transcode from remux: container swaps, faststart and duration trims are all stream copies
- Render one master using the most conservative intersection of all target constraints
- Re-encode only for out-of-range resolution, unaccepted codec, out-of-band frame rate, or oversize files
- Have the decision function return reasons so every re-encode can be explained
- Print an encode counter next to the naive baseline to prove the saving
答题要点
- 分清转码与封装:换容器、加 faststart、按时长截断都可以流复制
- 一次渲染母版,参数取所有目标平台约束的最保守交集
- 只有分辨率越界、编码不被接受、帧率超范围、体积超限才必须重编码
- 判定函数返回理由列表,让每次重编码都能被解释
- 打印编码次数计数器并与朴素做法做对照,才算证明少转了码
When a model generates creative content such as titles and cover copy, how do you guarantee a quality floor?让模型生成标题、封面文案这类创意内容,怎么保证质量下限?
Common in ChinaCommon overseasIntermediate#llm-output-quality#candidate-selectionHow to reason about it · think before answering
- The pivot is the word floor. The question is not how to make output better but how to keep it from being bad, and those two goals need different techniques.
- Start from one criterion: is this stage expensive? Expensive slow stages such as video generation must get it right once by constraining the input. Cheap fast stages such as copywriting should generate several variants and converge. A three-order-of-magnitude price gap justifies opposite strategies.
- So the shape is: generate one candidate per preset angle, then converge with a deterministic scoring function. The floor comes from the scorer, not from the model, because model variance is the normal case and the scorer has none.
- Three requirements for the scorer: emit a reason per rule, since an unexplained score cannot be iterated on; penalize banned wording heavily rather than filtering it, because filtering can leave you with nothing; and always define a tie-breaker, or two runs pick different winners and you will blame the model and start tuning temperature.
- Add a structural guard: model output may be too long, prefixed with explanation, or carry debug markers. Validate the shape and fall back to a local template when it fails. That layer handles uncontrollable structure, which is a different problem from uncontrollable quality.
- Expect the follow-up: why not let the model score itself? Because it is unstable and unexplainable. The same batch can be ranked differently twice, and you cannot justify the choice to anyone. Model judgment can be one input to the scorer, never the only judge.
分析过程 · 先想清楚再作答
- 题眼是「下限」两个字。它问的不是怎么让输出更好,而是怎么保证输出不会太差——这两个目标的手段完全不同,混起来答就散了。
- 先给一条判断依据:这个环节贵不贵。贵而慢的环节(比如视频生成)要「一次做对」,靠约束输入;便宜而快的环节(文案)应该「多做几版再挑」,靠收敛输出。价格差三个数量级,策略就该完全不同。
- 于是形态是:按几个预设角度各生成一版候选,再用一个确定性的打分函数收敛成前几名。下限由打分函数保证,而不是由模型保证——模型不稳定是常态,打分函数不会。
- 打分函数的三条要求:每一项都写出理由(分数不解释就没法迭代规则)、违规词用扣重分而不是过滤(过滤在极端情况下会一条不剩)、同分必须有决胜键(否则两次运行挑出不同结果,你会误以为是模型不稳定去调温度)。
- 还要有一道兜底:模型返回的东西不一定能直接用,可能太长、带解释性前缀、夹着调试符号。加一个格式校验,不通过就回落到本地模板。这一层挡的是「输出结构不可控」,和打分挡的「输出质量不可控」是两件事。
- 可预期的追问是「为什么不让模型自己评分」。答案是不稳定且不可解释:同一批候选问两次可能给出不同答案,而且你无法向任何人说明为什么选了第三条。模型评分可以作为打分函数的一项输入,但不能是唯一的裁判。
Key points
- Pick the strategy by stage cost: constrain input when expensive, converge output when cheap
- Generate one candidate per preset angle, then rank with a deterministic scoring function
- The scorer must emit reasons, penalize banned wording instead of filtering, and define a tie-breaker
- Add a separate structural fallback for malformed output, distinct from quality scoring
- Do not let the model judge itself; use it at most as one signal inside the scorer
答题要点
- 按环节的价格选策略:贵的一次做对靠约束输入,便宜的多做几版靠收敛输出
- 形态是按预设角度批量出候选,再用确定性打分函数挑前几名
- 打分函数必须输出理由、对违规词扣重分而非过滤、同分给决胜键
- 另加一道结构兜底:格式不合格就回落本地模板,与质量打分是两件事
- 不让模型给自己评分,它不稳定也不可解释,最多作为打分的一项输入
How do you feed post-publication metrics back into the production pipeline? Describe a concrete path.内容发布之后的数据要怎么回流到生产流程里?说一条具体可落地的路径。
Common in ChinaCommon overseasDeep dive#feedback-loop#analyticsHow to reason about it · think before answering
- The easy wrong answer is build a dashboard and review it regularly, which is spectating rather than feedback. The discriminator is whether you can map a metric to a concrete action.
- Set the rule first: every conclusion must land on a specific pipeline stage. A metric that maps to no stage cannot be acted on, so it does not belong in the feedback path at all.
- Then give a concrete mapping. Retention curves fit naturally because their x axis is time and your timeline table records the start and end of every shot. Early drop maps to cover, title and the first frame; the steepest mid-curve drop is looked up in the timeline to a specific shot id and maps to that shot's duration and camera move; a low completion rate maps to the script's closing hook.
- Landing on a stage pays twice: it narrows the edit from a whole episode to a single shot, and the regeneration cost narrows with it. Say this out loud, it connects analytics to cost control and is the differentiating point of the answer.
- Keep the rules deliberately dumb and explainable, starting with hand-set thresholds. Replace them with learned ones once you have dozens of episodes, but never give up explainability, because you must be able to justify each recommendation from the log.
- Expect the follow-up: how do you merge data across platforms? You do not. Diagnose each platform separately, because the difference in how the same episode performs is itself the signal, and merging erases it.
分析过程 · 先想清楚再作答
- 这题最容易答成「建个数据看板,定期复盘」——那是看热闹,不是回流。区分度在于你能不能给出一条从指标到具体动作的映射。
- 先立判据:每一条结论必须落到流水线上一个具体的环节上。落不到环节的指标,看了也改不了,所以它根本不该出现在回流路径里。
- 然后给一条真实可落地的映射。留存曲线天然适合,因为横轴是时间,而你的时间轴表里记着每一镜的起止时间:开头几秒的掉幅映射到封面与标题、以及第一镜的首帧;中段掉幅最大的那一段用时间轴反查出具体镜头 id,映射到那一镜的时长与运镜;完播率整体偏低映射到剧本的结尾钩子。
- 落到环节的收益是双份的:修改范围从一整集缩到一个镜头,成本也跟着缩到几分之一。这一点要主动说,它把「数据分析」和「成本控制」连起来了,是这题的加分项。
- 判据要写得笨且可解释,先用手写阈值。等积累了几十集真实数据再换成从数据里学出来的,但可解释这条不能丢——你必须能对着日志说清为什么建议改这一环。
- 可预期的追问是「多平台数据怎么合并」。答案是不要合并,分平台各诊断一次:同一集在不同平台的表现差异本身就是信息,合并会把它抹掉。
Key points
- One rule: every conclusion must land on a concrete stage, otherwise it does not belong in the loop
- Map the retention curve in three segments: opening drop to cover and first frame, steepest mid drop to a shot id via the timeline, low completion to the script hook
- Landing on a stage shrinks both the edit scope and the regeneration cost to a single shot
- Start with hand-set thresholds for explainability and learn them later once data allows
- Diagnose platforms separately; the divergence between them is itself signal
答题要点
- 判据只有一条:每条结论必须落到流水线上一个具体环节,落不到就不该进回流路径
- 留存曲线三段映射:开头掉幅到封面标题与首帧,中段掉幅用时间轴反查到具体镜头,完播率到剧本钩子
- 落到环节同时缩小了修改范围与重做成本,只重生成一镜而不是重跑一集
- 先用手写阈值保证可解释,数据够了再换成学出来的规则
- 多平台数据分别诊断不合并,平台间的差异本身就是信息
D14 A Five-Episode Season: Batch Production, Portfolio Packaging, and a Short-Drama Pipeline Interview Deep Dive
Walk me through the AI content pipeline you built. What was the hardest part?介绍一下你做的这条 AI 内容生产线,它最难的地方在哪?
Common in ChinaCommon overseasBasic#project-storytelling#system-designHow to reason about it · think before answering
- This is an open question that tests convergence. Narrating two weeks of work chronologically loses the interviewer in three minutes; delivering one through-line in thirty seconds is what counts as telling a project well.
- Open with positioning and scale: an automated pipeline from a one-line premise to publish-ready vertical episodes, one run producing a five-episode season, with humans stepping in only where judgment is required. Numbers first, detail second.
- Then answer hardest. That word should not be spent on debugging pain; spend it on a judgment that generates every downstream decision: video generation is the most expensive, slowest and most failure-prone stage at over ninety percent of per-episode cost, so the whole design revolves around issuing one fewer video call.
- Attach the chain of consequences in one sentence: idempotency and caching avoid duplicate calls, reference-image reuse reduces retries, the draft tier makes experimentation cheap, and the budget breaker stops a runaway. The chain proves your choices are derived rather than collected.
- Leave a deliberate hook for follow-up, such as saying the async task client turned out far harder than expected. That steers the interviewer toward your strongest material instead of a corner you never considered.
- Expect the follow-up: do you have real numbers? Keep four from every run: wall time, spend, failure rate and manual interventions. If spend is estimated, say so, rather than letting them assume you pasted a real invoice.
分析过程 · 先想清楚再作答
- 这是一道开放题,考的是收敛能力。把十四天的东西按时间顺序流水账讲一遍,面试官三分钟后就走神了;能在三十秒内给出一条主线,才算会讲项目。
- 开头两句要立住定位与规模:从一句话选题到多平台可发布成片的自动化流水线,一次运行产出一季五集,人只在需要判断的地方介入。数字先给,细节后给。
- 然后回答「最难」。这个词不该答成「调试很麻烦」,要答成一条能推出后续所有设计的判断:这条线上最贵、最慢、最容易失败的是视频生成,占单集成本九成以上,所以整套工程都是围着「怎么少调一次视频接口」转的。
- 接着一句话挂上推论链:幂等与缓存是为了不重复调,参考图复用是为了少试几次,草稿档路由是为了试错时用便宜规格,预算熔断是为了失控时能停住。这条链子证明你的技术选择不是攒来的最佳实践。
- 最后主动留一个可被追问的钩子,比如「异步任务的客户端比我预想的复杂得多」——把面试官引到你准备最充分的地方去,而不是等他随机挑一个你没想过的角落。
- 可预期的追问是「有真实数据吗」。所以复盘时必须留下四个数字:耗时、花费、失败率、人工介入次数。花费是估算的就要主动说明是估算,别让人以为你贴了张真实账单。
Key points
- Position first: from a one-line premise to multi-platform episodes, one run per five-episode season
- Frame the hardest part as a judgment: video dominates cost and is the slowest, most failure-prone stage
- Show the derivation chain: idempotency and caching, reference reuse, draft tier, budget breaker
- Bring four numbers: wall time, spend, failure rate, manual interventions, flagging estimates as estimates
- Plant a follow-up hook that steers the conversation to your strongest area
答题要点
- 先定位再展开:从一句话到多平台成片,一次运行产出一季五集
- 把最难点答成一条判断:视频占单集成本九成以上且最慢最易失败
- 用推论链证明设计是导出来的:幂等缓存、参考图复用、草稿档、预算熔断
- 带上四个数字:耗时、花费、失败率、人工介入次数,估算值要主动标注
- 主动留一个追问钩子,把话题引向准备最充分的部分
How do you keep characters and visual style consistent across many generated episodes, and what breaks at a hundred episodes?多集连续生成时,人物与画风的跨集一致性你是怎么保证的?如果要做一百集会遇到什么新问题?
Common in ChinaCommon overseasIntermediate#consistency#prompt-assemblyHow to reason about it · think before answering
- The discriminator is whether you rely on discipline or on structure. Writing the prompt the same way every time drifts by episode five, because every copy is another chance for a human edit.
- The right shape makes inconsistency structurally impossible: keep one archive (character cards with appearance and voice id, style tokens, scene list) and enforce one rule, that every shot's prompt is assembled from the archive plus that shot's description, never hand-written.
- Then turn the rule into decidable checks: are all characters in the archive, did any voice id change across episodes, is the appearance fragment verbatim from the archive, does every shot carry the style tokens, and does each episode's hook match the next one's pick-up. These five inspect inputs only; picture quality belongs to the automated review pass, and the two are complementary.
- At a hundred episodes three new problems appear. First the archive itself evolves as characters restyle and new ones appear, so it needs versions and each episode must record which version it used, or you cannot explain why episode thirty differs from episode ten.
- Second, the hook chain gets long and manual maintenance fails, so hook validation has to be a hard gate before the run starts. Third, the asset library bloats, so reference sheets need an index and deduplication or one character accumulates dozens of contradictory base images.
- Expect the follow-up: does consistency fight variety? Separate the layers. The archive locks identity traits such as appearance, voice and style, while randomness lives in camera movement, framing and lighting. Locking the wrong layer gives you a hundred identical episodes.
分析过程 · 先想清楚再作答
- 这题的区分度在于你是靠自律还是靠结构。答「每次都把提示词写得一样」的人做到第五集就会漂,因为每复制一次提示词就多一次人为改动的机会。
- 正确形态是把一致性变成结构上做不到不一致:建一份唯一的档案(人物卡含外貌与音色、风格词、场景表),再立一条硬规矩——每一镜的提示词只能由「档案加本镜描述」拼出来,不允许手写。
- 然后把这条规矩做成可判定的检查:角色是不是都在档案里、音色跨集有没有变、外貌片段是不是逐字来自档案、风格词每一镜有没有带上、集间钩子有没有首尾相接。注意这五条只看输入不看画面——画面质量是机器审片的职责,两道检查互补,谁也替代不了谁。
- 一百集会冒出三类新问题。第一是档案本身会演化:人物换了造型、加了新角色,需要给档案做版本,并记录每一集用的是哪个版本,否则回头没法解释第三十集为什么和第十集不一样。
- 第二是钩子链变长之后容易断,人工维护五条还行、维护九十九条一定出错,得让钩子校验成为开跑前的硬闸门。第三是资产库膨胀,定妆图与参考图要有索引与去重,否则同一个角色会攒出几十张互相矛盾的基准图。
- 可预期的追问是「一致性和多样性冲突吗」。答案是把两者分开:档案锁死的是身份特征(外貌、音色、风格),随机性留给运镜、构图与光线——锁错层就会得到一百集一模一样的片子。
Key points
- Rely on structure: one archive plus a rule that prompts may only be assembled from it
- Five decidable input-only checks: cast membership, voice stability, verbatim appearance, style tokens, hook chain
- Input checks complement automated picture review; neither replaces the other
- At scale add archive versioning, a hard pre-run hook gate, and an indexed deduplicated asset library
- Lock identity traits in the archive and leave randomness to camera, framing and lighting
答题要点
- 靠结构不靠自律:唯一档案加一条硬规矩,提示词只能从档案拼出来
- 五条只看输入的可判定检查:角色、音色、外貌逐字、风格词、集间钩子
- 输入检查与机器审片互补,一个查有没有漂,一个查画面好不好
- 上百集会新增三类问题:档案要版本化、钩子校验要变成硬闸门、资产库要索引去重
- 档案锁身份特征,随机性留给运镜构图光线,锁错层会一百集雷同
If you rebuilt this pipeline from scratch, what would you change architecturally?如果让你重做一遍这条生产线,架构上你会怎么改?
Common in ChinaCommon overseasDeep dive#architecture-review#trade-offsHow to reason about it · think before answering
- This tests the quality of your self-critique. Saying nothing needs changing ends the conversation; listing trendy technologies is just as bad, because it shows you learned nothing from the build. A good answer is specific, has a cost analysis, and traces back to a concrete stumble.
- Set a filter first: only discuss places where you actually got tripped up and now know the right approach. Say plainly where you are still unsure, because naming the limits of your solution proves more than the solution itself.
- First, recomputation scope. Editing one line of dialogue currently recomputes the whole downstream subgraph. Better would be for each node to declare which input fields it depends on, so a dialogue edit triggers only speech and subtitles, never video. The cost is more complex node definitions; the benefit is redoing one synthesis instead of a whole shot.
- Second, the cost model. The rate card today mixes derived prices with deliberate blanks, which is fine for projection but useless for reconciliation. Once real invoices exist, back out measured unit prices from them and keep a drift alert that fires when projection and reality diverge past a threshold, which beats after-the-fact reconciliation.
- Third, the concurrency model. Gates are currently keyed by provider, but modeling them as quota buckets matches reality better, since different endpoints from one vendor have independent quotas while different vendors are fully independent. Rate-limit diagnosis gets far more precise.
- Expect the follow-up: why not build it that way originally? Answer honestly that you shipped the smallest working version and spent complexity only where data justified it. That sentence is itself an architectural judgment, and distinguishing necessary complexity from premature complexity is exactly what the interviewer is listening for.
分析过程 · 先想清楚再作答
- 这题在考自我批判的质量。答「没什么要改的」直接出局;答一堆花哨的新技术也不行,因为那说明你没从这次的实践里学到东西。好答案是具体的、有代价分析的、并且能追溯到某一次踩坑。
- 先立一个筛选标准:只讲那些我这次真的被绊过、而且知道正确做法的地方。不确定的部分坦白说不确定——说得出方案的边界,比说得出方案本身更能证明你做过。
- 第一处可以讲重算范围。审核台上改一句台词,现在是按节点依赖整体重算下游,粒度偏粗;更好的做法是让每个节点声明自己依赖输入的哪几个字段,改台词只触发配音与字幕,不碰视频。代价是节点定义变复杂,收益是重做成本从一整镜降到一次语音合成。
- 第二处是成本模型。现在的单价表里有折算值和留空项,估算够用但不能对账;接了真实账单之后应该改成从账单反推实测单价,并保留一个偏差告警——估算和实际差超过阈值就报警,这比事后对账有用得多。
- 第三处是并发模型。现在闸门是按 provider 分类做的,更贴近现实的做法是按「配额桶」建模,因为同一家厂商的不同接口配额独立,而不同厂商之间又完全独立。改了之后限流的定位会准很多。
- 可预期的追问是「为什么当初不那样做」。诚实回答:当时先做能跑通的最小版本,把复杂度留给已经被数据证明值得的地方。这句话本身就是架构判断——面试官想听的正是你会不会区分「必要的复杂度」和「过早的复杂度」。
Key points
- Only discuss stumbles you actually hit and now know how to fix; admit what you are unsure about
- Move recomputation to field-level dependencies so a dialogue edit skips video regeneration
- Back out measured unit prices from real invoices and add a projection-versus-actual drift alert
- Model concurrency gates as quota buckets rather than per provider, matching per-endpoint quotas
- Explain the original choice: ship the smallest working version and spend complexity only where data justifies it
答题要点
- 只讲真的踩过且知道正确做法的地方,不确定的坦白说不确定
- 重算范围改成按字段级依赖,改台词只触发配音与字幕而不重生成视频
- 成本模型接真实账单后反推实测单价,并加一个估算与实际的偏差告警
- 并发闸门从按 provider 改成按配额桶建模,贴合各接口配额独立的现实
- 解释当初为何没这么做:先做最小可跑版本,把复杂度留给数据证明值得的地方
Build Your Own Coding Agent in 21 Days
D1 What Layers Make Up a Coding Agent: the REPL Skeleton, the Command System, and a Provider-Neutral Gateway Abstraction
How would you layer a terminal coding agent, and which layer must never be polluted by a vendor SDK?把一个终端 Coding Agent 分层,你会怎么切?哪一层最不该让厂商 SDK 渗进来?
Common in ChinaCommon overseasBasic#architecture#agent-loopHow to reason about it · think before answering
- This screens for having actually built one. Saying there is a loop that calls the model and then tools is what everyone says; the signal is naming each layer's inputs and outputs and the cost of replacing it.
- How to break it down: count bottom-up by dependency. The gateway layer takes messages plus a tool list and emits deltas; the tool layer takes an unparsed JSON string and returns text to feed back; the loop wires the two and emits semantic events; the session layer persists those events and can replay them; the interface layer subscribes to events only, for rendering and interruption. Five layers, each with inputs and outputs you can state in one sentence.
- Then the second half: the loop must stay clean. Once vendor SDK types appear inside it, swapping vendors means editing the loop, and the loop is the one place that knows about tools, approval, compaction and cancellation, so it is the most expensive file to touch. The fix is that the loop only knows two of your own types, and the gateway layer translates between them.
- Conclusion: the test for a layering is not tidiness, it is independent replaceability. The renderer can become a web UI without touching the loop, the gateway can move to another vendor without touching tools, and a new tool can be added without touching rendering.
- Likely follow-up: why can't the renderer read gateway deltas directly? Because tool calls and approvals are not expressible as gateway deltas. Rendering a reading-file card requires semantic events; reading raw deltas couples the UI to every vendor's wire format.
分析过程 · 先想清楚再作答
- 这题在筛「有没有自己写过一个」。只答「有个循环调模型再调工具」的人,说的是所有人都会说的一句话;区分度在于你能不能说出每一层的输入输出,以及换掉某一层要付多少代价。
- 怎么拆:按「谁依赖谁」自下往上数。网关层的输入是消息与工具清单、输出是增量分片;工具层的输入是一段未解析的 JSON 文本、输出是一段回灌文本;循环层把这两者接起来,输出是语义事件;会话层把事件落盘并能重放;交互层只订阅事件,负责渲染与打断。五层,每一层的输入输出都能一句话说清,说不清就是切错了。
- 接着答第二问:最不该被渗透的是循环层。厂商 SDK 的类型一旦出现在循环里,换厂商就要改循环,而循环是唯一同时懂工具、审批、压缩、取消的地方,改它的成本最高。做法是循环只认自己定义的两套类型——网关吐出来的分片形状,和循环对外发出的语义事件——网关层负责在这两者之间翻译。
- 结论:分层的判据不是「看起来整齐」,是「能不能单独替换」。渲染层能换成 Web 前端而不动循环、网关层能从一家换到另一家而不动工具、工具层能加一个工具而不动渲染,三条都成立,分层才是真的。
- 可预期的追问:那渲染层为什么不能直接读网关的分片?因为工具调用与审批不是网关分片能表达的东西。渲染要显示「正在读文件」这张卡片,它订阅的必须是语义事件;直接读分片会让渲染层跟着每家网关的报文格式变。
Key points
- State inputs and outputs for five layers: gateway, tools, loop, session, interface
- The loop must stay vendor-free because it is the only place that knows tools, approval, compaction and cancellation
- The loop knows only two of your own types: gateway deltas and semantic events; translation lives in the gateway layer
- The test of a layering is independent replaceability, not tidiness
- The renderer subscribes to semantic events, not gateway deltas, or it tracks every wire format
答题要点
- 五层各说清输入输出:网关、工具、循环、会话、交互
- 最不该被渗透的是循环层,因为它是唯一同时懂工具、审批、压缩、取消的地方
- 循环只认两套自己的类型:网关分片与语义事件,翻译放在网关层
- 分层的判据是能不能单独替换,而不是看起来整齐
- 渲染层订阅语义事件而不是网关分片,否则会跟着报文格式变
Vendors ship official SDKs, so why write your own gateway layer? When does that layer become a liability?模型厂商都提供了官方 SDK,为什么还要自己写一层网关抽象?什么时候这层是负担?
Common in ChinaCommon overseasIntermediate#provider-abstraction#architectureHow to reason about it · think before answering
- This checks whether you have actually swapped models. Answering decoupling scores nothing; give a concrete list of what the layer buys and what it costs.
- How to break it down: ask what falls apart without it. Three things, each traceable to a file. First, offline runnability, because only when network egress is funneled into one place can you stub the whole thing. Second, the model id stops being a constant, since the same model has different names on different gateways. Third, metering, because tokens and cost per call must be recorded in exactly one place.
- Then place the abstraction: define it by business action, not by HTTP request. One method, streaming only, because non-streaming has no use in a coding agent, and every extra method is another branch to maintain.
- Conclusion and cost: the layer sands off vendor-specific features such as thinking blocks or cache-control fields. The fix is not a wider interface but one optional passthrough field, so a single call site explicitly admits it is vendor-bound.
- When it is a liability: one vendor forever and no offline path; plus two warning signs, namely adding a gateway forced a signature change, or a vendor-only parameter name leaked into the interface.
- Likely follow-up: why not just use an aggregation gateway? It normalizes protocols but not your event contract, on-disk format, or metering, and those are what the next weeks of work depend on.
分析过程 · 先想清楚再作答
- 这题看的是你有没有真的换过一次模型。答「解耦」拿不到分,要给出「这层买到了什么、赔上了什么」的具体清单。
- 怎么拆:先问「不写这层会散掉什么」。三样,而且都能落到具体文件上。第一,离线可跑——网络出口收敛到一处才可能整体打桩,测试与 CI 才能不花钱地跑完整循环。第二,模型 id 不再是常量——同一个模型在不同网关上叫不同名字,有的要带厂商前缀有的不带,写死在代码里就换不动。第三,计量收口——每次调用的 token 与花费必须有唯一一处记账,否则后面算成本要满仓库找调用点。
- 接着讲抽象放在哪:接口按业务动作定义,不按 HTTP 请求定义。它只有一个方法、只有流式那一种,因为非流式在 Coding Agent 里没有用处;多留一个方法就多一处要在整个项目里维护的分支。
- 结论与代价:这层会磨掉各家的独有能力,比如某家的思考块、某家的缓存控制字段。正确处理不是把接口撑大,而是留一个可选透传字段,让需要它的那一处显式承认自己绑定了某一家。
- 什么时候是负担:只用一家、也永远不需要离线跑的时候;以及出现两个信号时——为了加一家网关改了接口签名、或者接口里出现了只有一家有的参数名。这两个信号说明抽象抽在了厂商能力的最小公倍数上,位置错了。
- 可预期的追问:直接用聚合网关不就行了?聚合网关解决协议差异,解决不了你自己的事件契约、落盘格式与计量口径,而这三样才是你后面十几天要反复用的东西。
Key points
- Three concrete gains: offline runnability, model id as configuration, and a single metering point
- Define the interface by business action with a single streaming method; non-streaming is useless here
- The cost is losing vendor-specific features; handle it with an optional passthrough field, not a fatter interface
- Two signs you abstracted wrong: adding a gateway changes the signature, or a vendor-only parameter leaks in
- Aggregation gateways normalize protocols, not your event contract or metering
答题要点
- 三个具体收益:离线可跑、模型 id 变成配置、计量收口到一处
- 接口按业务动作定义,只留流式一个方法,非流式在 Coding Agent 里没用处
- 代价是磨掉厂商独有能力,用可选透传字段处理而不是撑大接口
- 两个抽错了的信号:加网关要改签名、接口里出现厂商专有参数
- 聚合网关解决协议差异,解决不了自己的事件契约与计量口径
How do you make a CLI that leans heavily on a paid model API developable and testable without any API key?一个重度依赖付费模型 API 的命令行工具,怎么做到没有密钥也能开发和测试?
Common in ChinaCommon overseasIntermediate#testing#developer-experienceHow to reason about it · think before answering
- This probes engineering habits, not tricks. Answering mock out fetch in unit tests solves testing but not development; the signal is making the whole chain produce real behavior offline.
- How to break it down: decide where the stub goes. Stub your own interface, not fetch or HTTP. Stubbing fetch means stubbing wire format, which must track every vendor; stubbing your provider interface means stubbing semantics, which stays stable for weeks.
- Then stub quality. A canned one-liner only proves the process did not crash. A useful stub follows a script, so offline runs really read files, really edit them, really run tests. Script branches must be chosen by the last user message and existing tool results, never by turn counter, or injected failures desynchronize the script.
- Conclusion: offline mode is not about saving money, it is about reproducible behavior. Real models differ every run, while teaching, regression tests and CI all need a deterministic phenomenon. It also becomes the natural injection point for failures.
- Likely follow-up: how do you keep the stub from drifting from reality? The stub and the real implementation share one interface and one set of types, so shape drift fails at compile time; and keep a verification script that runs against a real key periodically.
分析过程 · 先想清楚再作答
- 这题在考工程习惯,不在考技巧。答「写单元测试 mock 掉 fetch」的人只解决了测试,没解决开发;区分度在于你能不能让整条链路在离线状态下产生真实现象。
- 怎么拆:先定打桩位置。桩要打在自己的接口上,不是打在 fetch 或 HTTP 层——打在 fetch 上你桩的是报文,得跟着每家网关的格式改;打在自己的 provider 接口上,桩的是语义,二十天都不用动。
- 然后是桩的质量。假回复一句话的桩只能证明程序没崩;有用的桩是「按剧本吐分片」——先说两句话、再请求调某个工具、拿到结果后再说结论,于是离线也会真的读文件、真的改文件、真的跑测试。桩的分支要按最后一条用户消息与已有工具结果选,不能按轮数递增,否则注入故障后剧本就错位。
- 结论:离线不是省钱,是让「现象可复现」。真实模型每次输出都不一样,教学、回归测试与 CI 都需要一个确定的现象;而且离线模式还顺带成了故障注入的入口——限流、超时、非法参数、流中断都可以在这一层制造。
- 可预期的追问:怎么保证桩不和真实行为偏离?两条。一是桩与真实实现共用同一个接口与同一套类型,编译期就挡住形状漂移;二是留一个用真实密钥跑的验证脚本,把「真实响应长什么样」定期核一遍,偏差立刻改桩。
Key points
- Stub your own provider interface, not fetch or the HTTP layer
- Make the stub emit scripted deltas so offline runs really call tools, edit files and run tests
- Choose script branches by the last user message and existing tool results, not a turn counter
- The real value of offline mode is reproducible behavior, and it doubles as the failure-injection point
- Prevent drift by sharing types with the real implementation and keeping a real-key verification script
答题要点
- 桩打在自己的 provider 接口上,不打在 fetch 或 HTTP 层
- 桩要按剧本吐分片,让离线也真的调工具、改文件、跑测试
- 剧本按最后一条用户消息与已有工具结果选分支,不按轮数递增
- 离线的真正价值是现象可复现,并顺带成为故障注入的入口
- 防漂移两招:桩与真实实现共用类型;留一个用真实密钥的验证脚本定期核对
D2 Streaming Output and Terminal Rendering: Hand-Writing an SSE Parser, Incremental Markdown, and an Interruptible Typewriter
What edge cases bite you when hand-writing an SSE parser, and why can't you just read line by line?手写一个 SSE 解析器,有哪些容易踩的边界条件?为什么不能直接按行读?
Common in ChinaCommon overseasBasic#sse#streamingHow to reason about it · think before answering
- This separates people who used a library from people who wrote a parser. Splitting on newlines and JSON-parsing whatever follows data works on most gateways by luck; the signal is knowing when it fails silently.
- How to break it down: question every split between bytes and semantics. The first split is network chunking, decided by TCP and gateway buffering and unrelated to protocol boundaries. The second is the event boundary, which the spec defines as a blank line. The third is the field lines inside one event. Three different rules, and collapsing them into one is the bug.
- So four edge cases: chunking can cut one event in half or pack five into one chunk, so you need a buffer; multi-byte characters get split, so decode in streaming mode rather than per chunk; one event may carry several data lines that must be joined into a single payload; comment lines starting with a colon (usually heartbeats) and the terminator are not JSON and will throw.
- The second half follows from the third case: reading line by line does not crash, it loses content. It is correct forever on single-data-line gateways, until you switch vendors — and that bug is brutal to find because the wire text looks perfectly normal.
- Add one engineering point: the parser must distinguish a clean finish from the byte stream simply ending. Only a terminator or finish event means done; otherwise it is truncation and should surface as a retryable error, not a silent close.
- Likely follow-up: why not use EventSource? It is GET-only, cannot set headers, and hides non-2xx bodies, while model endpoints are authenticated POSTs. Browsers get a built-in client; CLIs usually parse it themselves.
分析过程 · 先想清楚再作答
- 这题在筛「用过库」和「写过解析器」。答「按换行切、取 data 后面的 JSON」的人,写的是一个在多数网关上碰巧能跑的版本;区分度在于你能不能说出它在什么情况下会静默出错。
- 怎么拆:把「字节到语义」这条路上的每一次切分都问一遍——谁保证这一刀切在正确的位置。第一刀是网络分块,它由 TCP 与网关的缓冲决定,和协议边界毫无关系;第二刀是事件边界,规范定的是空行;第三刀是事件内部的字段行。三刀的依据完全不同,混成一刀就会出错。
- 于是四个边界条件:一,网络分块可能把一条事件劈成两半,也可能一块里塞五条事件,所以必须留缓冲区;二,多字节字符会被切开,解码器要按流式模式解码,不能每块单独解;三,一条事件可以有多行 data,按规范要拼成一个载荷,按行读会把它当成两条事件,内容就丢了;四,冒号开头的注释行(通常是心跳)和结束标记都不是 JSON,扔进解析函数会抛异常。
- 第二问的答案就藏在第三个边界里:**按行读的错误不是崩,是丢内容**。它在只发单行 data 的网关上永远正确,直到你换一家、或者对方开始返回多行 data——这类 bug 上线以后极难定位,因为报文肉眼看着完全正常。
- 还要补一条工程判断:解析器必须区分「正常结束」与「字节流没了」。见过结束事件才算说完,否则是断流,要报成可重试的错误。只靠迭代自然结束来判断,用户会看到半句话加一个正常提示符。
- 可预期的追问:为什么不用 EventSource?因为它只支持 GET、不能自定义请求头、也拿不到非 2xx 的响应体,而模型接口是带鉴权头的 POST。所以服务端推送在浏览器里可以用现成的,在客户端与命令行里通常得自己解析。
Key points
- Four edge cases: chunking versus event boundaries, split multi-byte characters, multi-line data payloads, and non-JSON comment lines and terminators
- Frame on blank lines, not newlines; line-based reading loses content silently instead of failing loudly
- Decode in streaming mode and keep a buffer for the unfinished tail
- Distinguish a clean finish from truncation, and surface truncation as a retryable error
- EventSource is GET-only with no custom headers, so CLIs usually hand-roll the parser
答题要点
- 四个边界:网络分块与事件边界无关、多字节字符被切开、多行 data 要拼成一个载荷、注释行与结束标记不是 JSON
- 分帧依据是空行不是换行,按行读的后果是静默丢内容而不是报错
- 解码要用流式模式,缓冲区留住没凑齐的尾巴
- 必须区分正常结束与断流:没见过结束事件就报成可重试错误
- EventSource 只支持 GET 且不能自定义头,所以命令行里通常自己解析
A streaming response dies halfway through. How should the client handle it, and what does retrying actually cost?流式响应在中途断开,客户端该怎么处理?重试的代价是什么?
Common in ChinaCommon overseasDeep dive#streaming#error-handlingHow to reason about it · think before answering
- The signal is in the second half. Almost everyone says retry; what separates candidates is naming the cost, which usually means they have actually retried a streaming generation in production.
- How to break it down: detect the truncation first, then decide. The test is whether you ever saw a finish event; a byte stream that just ends without one is truncation. Skip this and the rest is theory, because many clients treat truncation as a clean close and leave the user staring at half a sentence.
- Then three costs. Money and time: text generation cannot resume, so a retry regenerates from the first token and re-bills the whole prompt. The half-rendered output: what the user already saw cannot vanish, and it cannot be concatenated with the retry either, because the second wording will differ and the result reads as self-contradictory. Side effects: if tools already ran this turn, retrying the whole turn runs them again, and non-idempotent writes happen twice.
- Conclusion as policy: auto-retry once only when nothing has had a side effect and very little text was produced. Once a long answer is on screen or files have been touched, stop and hand the partial result to the user. The unit of retry is one gateway call, never one agent turn.
- Production angle: truncation and rate limiting share the retryable path but not the backoff. Rate limits should honor the response header and add jitter, while truncation is usually a connection issue where one immediate retry often succeeds. Put the decision on the error object, not on string-matching the message.
- Likely follow-up: can you resume like a file download? No — models have no resume-from-token-200 semantics. You can feed the partial text back as context and ask it to continue, but that is a new generation with different wording, acceptable for long-form writing and not for tool calls.
分析过程 · 先想清楚再作答
- 这题的题眼在后半句。前半句几乎所有人都能答「重试」,区分度全在「代价」上——说不出代价的人,通常没有真的在生产里重试过一次流式生成。
- 怎么拆:先把「断开」判出来,再决定做什么。判据是「见过结束事件吗」:字节流结束但没有结束标记就是断流。这一步做不到,后面全是空谈——很多客户端把断流当成正常收尾,用户看到半句话却没有任何提示。
- 然后是三个代价,一个都不能少。第一,钱和时间:文本生成不能续写,重试就是从第一个字重新生成,输入 token 重新算一遍,前面已经生成的部分白花。第二,屏幕上的半段:已经打出去的字不能凭空消失,也不能和重试的新内容拼在一起(模型第二次的措辞几乎肯定不同,拼起来会前后矛盾)。第三,副作用:如果这一轮里已经执行过工具,重试整轮就会把工具再执行一次——非幂等的写操作会被做两遍。
- 结论落到策略上:只对「还没产生任何副作用、且已生成内容很短」的情况自动重试一次;已经吐了一大段或已经动过文件,就停下来把半成品留给用户,让他决定继续还是重来。重试的正确单位是「一次网关调用」,不是「一轮 Agent 循环」。
- 生产视角再加一条:断流和限流要走同一个可重试通道,但退避策略不同——限流要按响应头等待并加抖动,断流通常是连接问题,立刻重试一次的成功率就不低。判断依据放在错误对象上,而不是靠字符串匹配错误信息。
- 可预期的追问:能不能像下载那样断点续传?文本生成不行,模型没有「从第 200 个 token 继续」这个语义;能做的是把已生成的部分作为上下文让它接着写,但那是新的一次生成,措辞会变,只适合长文写作类场景,不适合工具调用。
Key points
- Detect truncation first: without a finish event the turn is not done, so raise a retryable error
- Three costs: regeneration from scratch with the prompt re-billed, the half-rendered output that can neither vanish nor be concatenated, and already-executed tools running twice
- Auto-retry once only when there are no side effects and little output; otherwise hand the partial result to the user
- The unit of retry is a single gateway call, not a whole agent turn
- Truncation and rate limits share the retryable path but need different backoff, decided by a field on the error object
答题要点
- 先判断断流:见过结束事件才算说完,否则报成可重试错误
- 三个代价:重试是从头重新生成并重算输入 token、屏幕上的半段不能丢也不能拼、已执行的工具会被重复执行
- 只在无副作用且已生成内容很短时自动重试一次,否则把半成品留给用户决定
- 重试的单位是一次网关调用,不是一轮 Agent 循环
- 断流与限流共用可重试通道但退避不同,判定依据放在错误对象上而不是错误字符串
What makes incremental Markdown rendering in a terminal hard, and how do you trade correctness against responsiveness?在终端里做增量 Markdown 渲染,难点在哪?正确性与实时性冲突时你怎么权衡?
Common in ChinaCommon overseasIntermediate#terminal-ui#streamingHow to reason about it · think before answering
- This checks whether flicker has ever hurt you. Answering just use a Markdown library misses the problem: every Markdown parser expects a complete document, and streaming input is incomplete by definition.
- How to break it down: ask which Markdown constructs need lookahead to be meaningful. Nearly all of them — backticks must pair for inline code, three of them make a fence, asterisks must pair for bold, a table needs its delimiter row. So the real question is what to display while the syntax is still open.
- Two strategies with different costs. Re-render the whole answer on every chunk: perfectly correct, but it repaints large regions, jumps the cursor, and gets slower as the answer grows. Or pick a commit unit, show raw text inside it, and style it once it commits. I take the second with the line as the unit, because terminals scroll by lines and repainting one line is constant cost.
- Then add a test for whether a line can still change meaning. Only three cases need raw display: the line is just one or two backticks (it may become a fence), the backtick count is odd, or the line ends on asterisks. Without that test, two backticks get colored as inline code and flip back when the third arrives, which flickers a dozen times per answer.
- One production detail people miss: pipes have no cursor. Carriage returns and clear-line escapes are noise in non-interactive output and ruin logs, so the renderer branches on whether stdout is a TTY — repaint in a terminal, append-only in a pipe. The cost is that the typewriter effect is invisible in a pipe, so automated checks must assert on reassembled text and mis-styled frame counts rather than on colors.
- Likely follow-up: what about tables and lists? For structures larger than one line, show them raw while streaming and reflow once the turn ends, or simply accept that they do not form until then. Do not widen the commit unit to a whole block just to make tables live, because that is re-rendering everything again.
分析过程 · 先想清楚再作答
- 这题在考「你有没有被闪烁折磨过」。答「用一个 Markdown 库渲染」的人没意识到问题:所有 Markdown 解析器都要求输入是完整文档,而流式输入天生不完整。
- 怎么拆:先问一句「Markdown 的哪些语法需要看到后面才能确定含义」。答案是几乎全部——反引号要配对才是行内代码,三个反引号才是围栏,星号要配对才是加粗,表格要看到分隔行才是表格。所以增量渲染的本质问题是:**在语法还没闭合的时候,这几个字符该按什么显示。**
- 两个可选策略,各有代价。一是每来一片就整段重渲染:正确性满分,但屏幕会大面积重画、光标乱跳,长回答还会越来越慢。二是选一个定型单位,单位内先按原样显示、定型后再上样式:这就是我选的做法,定型单位取「行」,因为终端本来就按行滚动,重画一行的代价是常数。
- 结论加一条判据:定型之前要判断这一行「还可能变吗」。只有三种情况需要按原样显示——整行只有一两个反引号(可能长成围栏)、反引号总数是奇数、行尾停在星号上。不加这个判据,收到两个反引号时会先按行内代码上色,第三个到达再改回围栏,一段回答里能闪十几次。
- 工程视角还有一条经常被漏掉的:管道里没有光标。回到行首与清行这两个转义在非交互终端里是纯噪音,会毁掉日志,所以渲染层要按标准输出是不是终端分两条路径——真终端重画,管道纯追加。代价是打字机效果在管道里看不见,于是自动化验收只能靠断言(分片拼接是否逐字一致、误上色帧数是否为零),不能靠看颜色。
- 可预期的追问:那表格和列表怎么办?超出「一行」这个定型单位的结构,正确做法是流式期间只按原样显示,整轮结束后再重排一次;或者干脆接受它在流式期间不成型。**不要为了让表格实时成型而把定型单位放大到整段**,那等于回到每片重渲染。
Key points
- The core difficulty is that Markdown needs closed syntax to have meaning while streaming input is inherently incomplete
- Two strategies: full re-render is correct but repaints widely and degrades, while a commit unit keeps repaint cost constant
- Use the line as the commit unit and display uncommitted lines raw, tested by backtick parity, one-or-two-backtick prefixes, and trailing asterisks
- Branch on whether stdout is a TTY: repaint the current line in a terminal, append only in a pipe with no cursor escapes
- Structures larger than a line do not form while streaming; reflow after the turn instead of widening the commit unit
答题要点
- 根本难点是 Markdown 语法需要闭合才能确定含义,而流式输入天生不完整
- 两种策略:每片整段重渲染正确但会大面积重画且越来越慢;选定型单位则代价是常数
- 定型单位取「行」,未定型的行按原样显示,判据是反引号奇偶、是否只有一两个反引号、行尾是否停在星号
- 按标准输出是不是终端分两条路径:真终端重画当前行,管道纯追加不发光标控制符
- 大于一行的结构(表格、列表)流式期间不成型,整轮结束后重排,不要为它放大定型单位
D3 The Tool Protocol and the Read-Only Trio: Schema Design, Chunk Merging, and Result Truncation
How do you write a tool's description and JSON Schema so the model gets it right more often?工具的描述与 JSON Schema 该怎么写,才能让模型少犯错?
Common in ChinaCommon overseasBasic#tool-design#json-schemaHow to reason about it · think before answering
- It looks like a giveaway, but answering write it clearly scores nothing. The signal is knowing what the model actually sees: only the name, the description, and the parameter schema. Implementation is invisible to it, so this is an interface design question, not a writing question.
- How to break it down: derive the rules from the mistakes. The model picks the wrong tool when the description never says when to use it; it writes bad syntax when there is no example; it passes junk parameters when defaults are unstated and extra properties are allowed; it misuses one tool half the time when that tool does two jobs.
- Conclusion as four rules: name it verb plus noun in lower snake case; describe when to use it, how to fill the parameters, and what the limits are, with one concrete example; document every parameter including its default; keep one tool to one job and split rather than branch.
- Add the point most people miss: the schema also carries fields for yourself. A read-only flag is invisible to the model but is what an approval gate uses to decide whether to pause. Define such fields in the first version, because retrofitting fifteen tools later is far more expensive.
- Likely follow-ups: how many tools? Selection accuracy degrades past a couple dozen, and the fix is grouping and on-demand loading rather than longer descriptions. How long should a description be? Delete a sentence and ask whether the model would now misuse the tool; if not, delete it, because every description is resent in the prompt on every turn.
分析过程 · 先想清楚再作答
- 这题看着像送分题,但答「写清楚一点」就没分了。区分度在于你知不知道模型看得见什么——只有名字、描述、参数 schema 三样,实现细节它一无所知。所以这是一道接口设计题,不是文档写作题。
- 怎么拆:把「模型会怎么犯错」倒推成「描述里该写什么」。它会用错工具(描述没说清什么时候用它)、会写错语法(没给例子)、会传多余参数(没写默认值,也没关掉额外属性)、会一半时间用错同一个工具(这个工具承担了两件事)。四种错误各对应一条写法。
- 结论落成四条可执行的规则:名字用动词加名词、全小写下划线分隔;描述写「什么时候用、参数怎么填、有什么限制」,并给一个真实例子;每个参数都写说明并把默认值写进去;一个工具只做一件事,宁可拆成两个。
- 再补一条别人常漏的:schema 里要有给自己看的字段。比如一个「是不是只读」的标记,模型看不见,但审批门要靠它区分「直接放行」还是「先问一句」。这类字段要在第一版就定下来,等有了十五个工具再回来补一遍,成本高得多。
- 可预期的追问一:工具该有多少个?超过二三十个之后模型的选择准确率会掉,处理办法是分组按需加载(渐进披露),而不是把描述写得更长。追问二:描述该多长?判据是「删掉这句话,模型会不会用错」——不会就删掉,因为每个工具的描述都占系统提示的预算,每一轮都要重发。
Key points
- The model sees only the name, the description, and the parameter schema, so this is interface design
- Verb-plus-noun lower snake case names; descriptions cover when to use it, how to fill parameters, and limits, with an example
- Document every parameter and its default, disallow extra properties, and keep one tool to one job
- The schema also carries self-facing fields such as a read-only flag for the approval gate, defined in version one
- Group and lazily load tools when there are many; test description length by whether deleting a line causes misuse
答题要点
- 模型只看见名字、描述、参数 schema 三样,所以这是接口设计问题
- 名字用动词加名词、小写下划线;描述写「什么时候用、参数怎么填、有什么限制」并给例子
- 每个参数写说明与默认值,关掉额外属性;一个工具只做一件事
- schema 里还要有给自己看的字段,例如只读标记,供审批门使用,第一版就定下来
- 工具太多要分组按需加载,描述长度的判据是「删掉它模型会不会用错」
How do you reassemble streamed tool-call arguments, and which assumptions are off-limits?流式返回的工具调用参数怎么归并?有哪些假设是不能做的?
Common in ChinaCommon overseasDeep dive#tool-calling#streamingHow to reason about it · think before answering
- This is one of the most discriminating questions in the course: people who have done it answer in two sentences, and people who have not can only say concatenate the arguments. The signal is the second half, the list of forbidden assumptions.
- How to break it down: describe the real shape first. The first delta carries the call id and function name with empty arguments; later deltas carry argument fragments only; one call was measured to arrive in six to twelve pieces; the stream ends with a tool-calls finish reason. So merging means finding the slot by index, writing id and name only when present, and appending arguments.
- Then the forbidden assumptions, each tied to a real incident. You cannot assume the index starts at zero: on 2026-09-07 the same model on the same gateway produced starts of 1 and 2 in one run and 0 and 1 in another, so use a dictionary rather than array positions. You cannot merge by arrival order, because parallel calls interleave and you would stitch two argument strings together. You cannot JSON-parse each fragment, because splits land inside quotes and escapes. And you cannot trust the finish reason alone, since a model was observed reporting stop while still emitting tool deltas.
- Conclusion: keep a dictionary keyed by index, append argument text, drain sorted by index, parse only once everything has arrived, and treat the turn as a tool turn if either the finish reason says so or at least one call was merged.
- One more engineering point: the id can be missing. Generate a stable one, because every tool result message must point back to a call, and a missing pairing makes the next request invalid.
- Likely follow-up: how do you test it? The bug shows up maybe half the time against a real gateway, so the offline script should deliberately start indexes at 1, chop arguments finely, and interleave two calls. Turning an intermittent failure into a certain one is the only reliable way to test protocol code.
分析过程 · 先想清楚再作答
- 这题几乎是本课最有区分度的一道:自己归并过的人两句话说清,没写过的人只能答「把参数拼起来」。题眼在后半句——「不能做的假设」,那是踩过坑才有的清单。
- 怎么拆:先描述真实形状。第一片带调用 id 与函数名、参数是空串;后面每片只带一段参数文本;一次调用实测能切成六到十二片;结束时给一个「要调工具」的结束原因。所以归并的动作是:按序号找到槽位,id 与名字「有值才写」,参数累加。
- 接着列不能做的假设,每一条都有对应的事故:一,不能假设序号从 0 开始——2026 年 9 月 7 日实测同一个模型在同一家网关的两次运行分别给出 1 和 2、以及 0 和 1 两种起点,所以要用字典而不是数组下标;二,不能按到达顺序拼,因为并行调用的分片是交错到达的,按顺序拼会把两个调用的参数缝成一个;三,不能拿到一片就试着解析 JSON,切分点可能在引号或转义符中间;四,不能只信结束原因这一个字段,实测有模型报「正常停止」却仍然给了工具分片。
- 结论:归并的正确形状是「按序号建字典、累加参数、按序号升序取出」,解析放在全部分片到齐之后,判据用「结束原因是工具调用,或者归并出了至少一个调用」的并集。
- 工程视角补一条:id 也可能缺。缺了要自己造一个稳定的标识,因为工具结果消息必须能指回某个调用,少一条对应关系,下一轮请求就不合法。
- 可预期的追问:怎么测这个逻辑?真实网关上这个 bug 有一半概率不出现,所以要在离线剧本里故意让序号从 1 开始、把参数切得很碎、并让两个调用交错到达。把偶发变成必然,是这类协议代码唯一可靠的测法。
Key points
- Real shape: the first delta carries id and name, later deltas carry argument fragments, six to twelve pieces per call in practice
- Merge by keeping a dictionary keyed by index, writing id and name only when present, appending arguments, and draining sorted by index
- Four forbidden assumptions: zero-based indexes, arrival-order merging, parsing each fragment, and trusting the finish reason alone
- Ids can be missing, so synthesize a stable one or the tool result cannot point back and the next request is invalid
- Test it by making the offline script start at index 1, split arguments finely, and interleave two calls
答题要点
- 真实形状:第一片带 id 与函数名,后面每片只带参数增量,一次调用实测六到十二片
- 归并动作:按序号建字典、id 与名字有值才写、参数累加、按序号升序取出
- 四个不能做的假设:序号从 0 起、按到达顺序拼、每片都解析一次、只信结束原因字段
- id 可能缺,要自己造一个稳定标识,否则工具结果指不回调用,下一轮请求不合法
- 测法是在离线剧本里让序号从 1 起、参数切碎、两个调用交错,把偶发变必然
When a tool result blows past your context budget, what is your truncation strategy, and how do you avoid cutting the part that matters?工具结果远超上下文预算时,你的截断策略是什么?怎么保证不截掉关键信息?
Common in ChinaCommon overseasIntermediate#context-budget#tool-designHow to reason about it · think before answering
- This checks whether you did the arithmetic. Answering cut it to some length misses the shape of the problem: tool results are fed back into the message array, and the whole array is resent every turn, so one oversized result costs once per remaining turn, not once.
- How to break it down: price it, then design. A search hitting two thousand lines is roughly sixty thousand characters, billed again on every subsequent turn, and worse, it crowds out what matters — the user's request and the code you already read. Hence a hard per-result cap.
- Then how to cut. Head-only is the common mistake, because the tail usually holds the conclusion: the last line of a stack trace, a test summary, the export list at the end of a file. So cut from the middle and keep both ends, weighting the head slightly since the most relevant content tends to come first.
- Third, say that you cut. Insert a line stating how many characters were removed, the full length, and what to do to get the middle, such as reading again with an offset. The model cannot interpret an ellipsis but can follow an instruction, and this line is the most commonly omitted yet most effective part of the strategy.
- Also decide where truncation lives: in the single tool-execution entry point, not in each tool. Tool authors get the content right and one place owns the budget, otherwise twenty tools grow twenty different truncation rules. Keep the truncated flag in render-only metadata rather than feeding it back, which saves tokens too.
- Likely follow-ups: is there something better? Yes — give tools real pagination with offset and count and document it in the description; truncation is the last line of defense. And how is the overall budget split? That belongs to the compaction layer; this layer only guarantees no single result is unbounded.
分析过程 · 先想清楚再作答
- 这题在考「有没有算过账」。答「截到一定长度」的人没意识到问题的真实形状:工具结果要回灌进消息数组,而消息数组每一轮都整体重发一次——所以一次超长结果的成本不是一次,是剩下所有轮次乘以一次。
- 怎么拆:先算清代价,再定策略。一次命中两千行的搜索大约六万字符,转五圈就付五次;更糟的是它挤掉了真正重要的上下文——用户的需求、之前读到的关键代码。于是结论很自然:单个结果必须有硬上限。
- 然后是「怎么截」。只留头是最常见的错法,因为尾部往往有结论性的信息:报错的最后一行、测试的汇总行、文件末尾的导出清单。所以从中间截、头尾都留,头可以多分一点,因为最相关的内容通常在前面。
- 第三步是「截了要说」。中间必须插一行说明:截掉了多少字符、全文多少字符、想看中间那段该怎么做(带偏移量再读一次)。模型看不懂省略号,但看得懂一条指令。这一行是策略里最容易被漏掉、却最有效的部分。
- 结论加一条位置判断:截断放在工具执行的统一入口,不放在每个工具里。工具作者只管把内容做对,预算由一处统一管——不然二十个工具会有二十份截断逻辑,且各不相同。另外「已被截断」这个标记只放在给渲染看的元数据里,不回灌给模型,省下的也是 token。
- 可预期的追问一:更好的做法有没有?有——让工具自己支持分页(偏移量与条数),并在描述里告诉模型怎么用,比事后截断优雅得多,截断是最后一道保险。追问二:整体预算怎么分?那是压缩那一层的题目,本层只保证单个结果不无限大。
Key points
- Price it first: results are resent every turn, so one oversized result costs once per remaining turn
- Cut from the middle keeping both ends with a heavier head; head-only loses conclusions like final error lines and test summaries
- Insert a note with how much was removed, the total length, and how to fetch the middle, since the model follows instructions rather than ellipses
- Truncate at the single tool-execution entry point, and keep the truncated flag in render-only metadata
- Better still, give tools real pagination documented in the description; truncation is the last resort, and overall budget split belongs to compaction
答题要点
- 先算代价:结果回灌后每一轮都整体重发,一次超长结果的成本是剩余轮次乘以一次
- 从中间截、头尾都留,头多分一点;只留头会丢掉报错末行与汇总行这类结论信息
- 必须插一行说明:截掉多少、全文多少、想看中间怎么做,模型看不懂省略号但看得懂指令
- 截断放在工具执行的统一入口,不放在每个工具里;已截断标记只给渲染看不回灌
- 更好的做法是工具自带分页并写进描述,截断是最后一道保险;整体预算分配属于压缩那一层
D4 File Editing and Shell Execution: Exact-Match Replacement, Conflict Detection, and a Timeout-Killable Subprocess
To let a model edit files, do you pick whole-file rewrite, exact string replacement, or patches? Why?让模型改文件,你会选整文件重写、精确替换还是补丁?为什么?
Common in ChinaCommon overseasBasic#file-editing#tool-designHow to reason about it · think before answering
- This checks whether you have actually shipped model-driven edits. Answering use diffs, they are cheaper compares only cost, not failure modes — and failure recovery is where these three really differ.
- How to break it down: attach a failure story to each. Whole-file rewrite forces the model to reproduce untouched code verbatim, so it quietly improves things you never asked about, and those edits hide inside a large diff; long files can also be cut off by output limits. Patches are the cheapest but require exact line numbers and context, so one miscounted line voids the hunk, and the failure message leaves the model repeating the same miscalculation. Exact replacement sends only the target snippet and its replacement, and fails with not found or found three times.
- Conclusion: pick exact replacement, not because it is cheaper but because it fails in a teachable way. Not found translates to you misremembered, read the file again; found three times translates to include more context. A tool's failure message is the model's behavior spec, and only messages that map to a next action are worth anything.
- Add the boundary: exact replacement does not cover large mechanical changes such as project-wide renames. The right answer there is not a different edit format but running a command — a codemod or a regex batch replace — and then running the tests. The test is whether the change is a mechanizable transformation.
- Likely follow-up: why not offer both and let the model choose? Every extra tool is another place it can choose wrong, and two tools with different failure semantics produce conflicting feedback. Prefer one tool that does one thing correctly.
分析过程 · 先想清楚再作答
- 这题在考「有没有真的让模型改过代码」。答「用 diff,更省 token」的人只比较了成本,没比较失败模式——而这三种做法的真正差别在失败之后能不能救回来。
- 怎么拆:给每种做法配一个失败故事。整文件重写:模型要把没改的部分一字不差地重新输出,于是它会顺手「优化」你没让它动的地方,这种改动混在大 diff 里人审不出来,长文件还可能被输出上限截成半个。补丁:最省 token,但行号与上下文行必须完全对上,算错一行整块作废,而且失败原因是「第 42 行上下文不匹配」,模型只能重试一遍它刚才那套算错的逻辑。精确替换:只发要改的那一小段与替换成什么,失败原因是「找不到」或「出现了 3 次」。
- 结论:选精确替换,理由不是省 token,而是**它失败得更可教**。「找不到」直接翻译成「你记错了,先重新读一遍」,「出现了 3 次」直接翻译成「把上下文写长一点」。工具的失败信息就是模型的行为规范,能翻译成下一步动作的失败信息才有价值。
- 补充一条边界:精确替换也有它不能覆盖的场景——大规模重命名、跨文件的机械改动。这类活正确的做法不是换编辑方式,而是让它去跑一条命令(比如代码修改器或者带正则的批量替换),然后跑测试验证。**判据是「这个改动是不是一个可以被工具化的机械变换」。**
- 可预期的追问:那为什么不干脆两种都提供,让模型自己选?因为多一个工具就多一处它会选错的地方,而且两个工具的失败语义不一样,回灌的提示会互相干扰。宁可一个工具做对一件事。
Key points
- Each approach has its own failure mode: rewrites drift and truncate, patches void on a miscounted line, exact replacement only fails as not-found or not-unique
- Choose exact replacement because it fails teachably, with reasons that map to the model's next action
- A tool's failure message is the model's behavior spec; a message without a next action is useless
- Large mechanical edits belong in a command plus test verification, not a different edit format
- Do not ship two editing tools; inconsistent failure semantics confuse the model
答题要点
- 三种做法各有失败模式:重写会顺手改无关代码且可能被截断,补丁行号一错整块作废,精确替换只会「找不到」或「不唯一」
- 选精确替换的真正理由是失败得可教:失败原因能直接翻译成模型的下一步动作
- 工具的失败信息就是模型的行为规范,写不出下一步动作的失败信息等于没写
- 大规模机械改动不该换编辑方式,而该走命令加测试验证
- 不要同时提供两种编辑工具,失败语义不一致会互相干扰
How does an edit tool detect conflicts, and what should it tell the model when detection fails?编辑工具怎么做冲突检测?检测失败时该给模型什么信息?
Common in ChinaCommon overseasIntermediate#file-editing#conflict-detectionHow to reason about it · think before answering
- This screens for having thought about where the model's snippet came from: a read several turns earlier. In between, the file may have been edited in an IDE, changed by the previous replacement, or swapped by a branch switch. Conflicts are not rare here, they are routine.
- How to break it down: ask what proves the world is still the one you read. Two kinds of evidence. Content matching requires old_string to appear exactly once, so not finding it means the world moved. Version comparison records an mtime or content hash at read time and re-checks before writing.
- The trade-off is the heart of the answer. Version comparison is stricter, catching even changed-and-changed-back, but its failure reason is the file was modified externally, which the model can only respond to by re-reading everything. Content matching is weaker but fails actionably: that snippet is not there, read the file again, or it appears three times, add more context. So use content matching for the model, and optionally layer hashing for human-facing audit.
- Then what to say on failure, three parts: state explicitly that nothing was modified, because models readily assume partial success; give the likely cause, such as inconsistent indentation or a file already changed; and give the next action.
- Finally the discipline: no fuzzy matching. Ignoring whitespace or guessing which occurrence was meant is gambling, and losing means a silent overwrite that tests may not catch for weeks. One extra turn of tokens is far cheaper than a corrupted file.
- Likely follow-up: what about multi-site replacement? Require repeated calls, one site each, or add an explicit replace-all flag that defaults to off and reports how many sites changed. Global replace by default is the most dangerous design here.
分析过程 · 先想清楚再作答
- 这题在筛「有没有想过模型手里那段原文是从哪来的」。它来自几轮之前的一次读取,而在这几轮之间文件可能被编辑器改过、被上一次替换动过、被切分支换掉。所以冲突不是并发编程里的稀有事件,它在 Agent 里是日常。
- 怎么拆:先问「我拿什么证明世界还是我读到的那个世界」。两种证据。一是内容匹配:要求 old_string 在文件里出现且只出现一次,找不到就说明世界变了。二是版本比对:读文件时记下修改时间或内容哈希,写之前再比一遍。
- 两者的取舍是这题的答案核心:版本比对更严格(连「改了又改回来」都能发现),但失败原因是「文件被外部修改」,模型对此无能为力,只能整个重读;内容匹配稍弱,但**失败原因天然可操作**——「找不到那段内容,请先重新读一遍」「那段内容出现了 3 次,请把上下文写长一点」。所以给模型看的那一层用内容匹配,给人看的审计与告警可以叠加哈希比对。
- 然后是「失败时说什么」,三条都不能少:一,明确说出没有改动任何内容(模型很容易误以为部分成功);二,给出可能的原因(缩进或换行不一致、文件已被改过);三,给出下一步动作(先读一遍再重试,或者把上下文写长一点)。
- 最后强调一条纪律:**不许模糊匹配。** 忽略空白、忽略缩进、猜「它大概想改哪一处」都是在赌,赌错的后果是静默覆盖——测试可能还是绿的,问题两周后才浮出来。宁可让它失败一次再重读一次,多花一轮的 token 比改坏一个文件便宜得多。
- 可预期的追问:多处替换怎么办?要求模型多次调用,一次改一处;或者显式加一个「替换全部」的开关,但默认关闭,并在结果里回报改了几处。默认全局替换是最容易出事的设计。
Key points
- The model's snippet comes from a read several turns ago, so conflicts are routine rather than rare
- Two kinds of evidence: content matching with a unique exact snippet, and version comparison via mtime or hash
- Use content matching for the model because its failures are actionable; hashing is stricter but leaves the model nothing to do
- Failure messages need three parts: nothing was changed, the likely cause, and the next action
- Never fuzzy-match; require repeated calls or an explicit opt-in flag for multi-site replacement
答题要点
- 模型手里的原文来自几轮前的读取,所以冲突在 Agent 里是日常而非稀有事件
- 两种证据:内容匹配(原文唯一且完全一致)与版本比对(修改时间或哈希)
- 给模型看的用内容匹配,因为失败原因天然可操作;哈希比对更严格但模型无从下手
- 失败信息三件套:明说没有改动、给出可能原因、给出下一步动作
- 不许模糊匹配;多处替换要求多次调用或显式开关,默认不做全局替换
What constraints do you put on running arbitrary shell commands from an agent, and how do you actually kill one after a timeout?在 Agent 里执行任意 shell 命令,你会加哪些约束?超时之后怎么真正把它杀掉?
Common in ChinaCommon overseasDeep dive#shell-execution#process-managementHow to reason about it · think before answering
- The first half is a checklist; the second half is where most candidates fall apart. Nearly everyone says add a timeout, and almost nobody explains why the process survives the kill.
- For the checklist, organize by what each constraint prevents. Timeouts prevent hangs, with a default and a hard ceiling. Output caps prevent context blowout, counted per stream and applied while reading. A fixed working directory prevents escaping the sandbox. A dangerous-command denylist prevents fat fingers — and say out loud that a denylist is fat-finger protection, not security, since real isolation needs containers, throwaway directories, and no network. Saying that earns points because it shows you know how thin that layer is.
- For the kill, talk about the process tree. You start a shell, the shell starts the real program, and that program may start more children. Killing only the shell orphans the grandchildren, which keep running and keep holding the output pipes, so the close event never fires and the call never settles — the agent hangs.
- The fix has two parts: give the child its own process group at spawn time (detached in Node, setsid semantics on POSIX) and kill the group, using a negative pid or killpg. Send the terminate signal first with a short grace period before the hard kill, since the former lets it clean up temporary files.
- Two more production details people miss: give the child's stdin a null device, or interactive commands hang until the timeout; and keep draining the pipes even after hitting your output cap, because a full pipe blocks the writer and the symptom is chatty commands mysteriously freezing.
- Likely follow-up: how do you prove your kill works? Make the test command keep a shell as parent, for example by backgrounding and waiting, so the difference between killing the shell and killing the group shows up reliably. A single simple command will not reveal it, because the shell often execs itself into the program.
分析过程 · 先想清楚再作答
- 前半句是清单题,后半句是本课最容易露馅的一处:几乎所有人都答得出「加超时」,答不出「为什么杀了却没停」。
- 怎么拆前半句:按「这个约束防的是什么」分四道闸。超时防挂死(默认三十秒,参数可放宽但要有上限),输出上限防上下文被顶满(两条流分别算、边收边截),工作目录防跑出边界,危险命令黑名单防手滑。第四道要主动说清它的性质——**黑名单是防手滑不是防攻击**,绕过办法有一百种,真正的隔离靠容器、临时目录、无网络。主动说这句话是加分项,因为它说明你知道自己那道闸有多厚。
- 后半句要讲进程树。你启动的是一个 shell,shell 再启动真正干活的程序,那个程序还可能启动更多子进程。只杀 shell,孙子进程会变成孤儿继续跑,而且还持着输出管道,于是「子进程关闭」这个事件永远不到,你这次调用的 Promise 永远不 resolve——现象就是「杀了却没停」,整个 Agent 挂在这里。
- 正确做法两步:启动时让子进程自成一个进程组(Node 里是 detached 选项,POSIX 语义是 setsid),杀的时候杀整个进程组(传负的进程号,或者用 killpg)。而且要先发终止信号、留一小段宽限期再发强杀信号——前者让它有机会清理临时文件,后者不给这个机会。
- 生产视角再补两条容易漏的:一,子进程的标准输入要给「忽略」,否则等输入的交互命令会一直挂到超时;二,即使输出超了上限也要继续读管道,不读的话管道满了子进程会被写阻塞,现象是「输出多的命令莫名其妙卡住」。
- 可预期的追问:怎么验证你的杀进程真的有效?让被杀的命令故意留一个 shell 当父进程(例如命令末尾加后台执行再等待),这样「只杀 shell」和「杀进程组」的差别才会稳定出现——只跑一条简单命令是测不出来的,因为 shell 常常直接把自己替换成那个程序。
Key points
- Four gates, each preventing one thing: timeouts for hangs, output caps for context blowout, a fixed cwd for escapes, and a denylist for fat fingers
- A denylist is fat-finger protection, not security; real isolation means containers, throwaway directories, and no network
- Survival after a kill comes from the process tree: orphans still hold the pipes, the close event never fires, and the call never settles
- Spawn the child in its own process group, kill the group, and allow a grace period before the hard kill
- Two easy misses: null out stdin, and keep draining pipes past your cap or the writer blocks
答题要点
- 四道闸各防一件事:超时防挂死、输出上限防上下文顶满、工作目录防越界、黑名单防手滑
- 黑名单是防手滑不是防攻击,真正的隔离靠容器、临时目录与断网
- 杀不掉的根因是进程树:孤儿进程还持着输出管道,close 事件永不到达,调用永不返回
- 做法是启动时让子进程自成进程组,杀时杀整个进程组,并留一段宽限期后再强杀
- 两个易漏点:标准输入给忽略;超了上限也要继续读管道,否则子进程会被写阻塞
D5 Permissions and Approval: a Three-State Rule Set, Matching by Tool and Path, and the Boundaries of Auto Mode
Designing an agent's permission model: which states do you need, what do rules match on, and which rule wins when several match?设计一个 Agent 的权限模型,你需要哪几种状态?规则按什么匹配、命中多条时听谁的?
Common in ChinaCommon overseasIntermediate#permissions#agent-designHow to reason about it · think before answering
- This screens for having actually gated real tools. Answering allow and deny misses the third case; the signal is justifying ask and explaining conflict resolution.
- How to break it down: ask whether some operations depend on what you are doing right now. They do, and they are most write operations — whether editing a file is fine depends on which file, what change, and which task. A rule table cannot answer that but a human glance can, hence a third state.
- Spell out the semantics, especially that deny differs from ask in kind, not just degree: deny means the path is closed, stop asking, because asking pushes the decision cost onto the user. Without deny the system fails in both directions: users get prompt fatigue and click through, or the rules get widened until nothing is enforced.
- Then matching: a tool name is not enough, because editing source and editing tests are the same tool, so path patterns and command prefixes are first-class conditions. You also need a will-this-mutate flag on every tool so read-only tools pass by default, and that flag must exist in the first version of the tool protocol, or externally loaded tools end up either all allowed or all prompted.
- Finally conflicts, the half most people get wrong: do not take the first matching rule in table order, because order becomes implicit semantics and a newly added specific rule gets swallowed by a broad one, with no error at all. The right rule is most specific wins, strictest wins on ties, implemented as a comparable sort key. And with no rule matched, a write must default to ask, because a default-allow gate is not a gate.
- Likely follow-up: where do rules come from? At least three places — built-in defaults, user config, and in-session approvals. Merge them into one table and let specificity rather than origin decide, so adding a source needs no change to the decision logic.
分析过程 · 先想清楚再作答
- 这题在筛「有没有把门装在真用的东西上」。答「允许和禁止两种」的人没考虑过第三种情况;区分度在于你能不能说出 ask 存在的理由,以及规则冲突怎么解。
- 怎么拆:先问「有没有一类操作,答案取决于当时在干什么」。有,而且它是绝大多数写操作——改某个文件该不该允许,取决于改的是哪个文件、改成什么、当时在做什么任务。规则表答不了这种问题,人看一眼能答,所以必须有第三态 ask。
- 三态的语义要说清,尤其是 deny 与 ask 的差别不只是严格程度:deny 是「这条路封了,别再问」,问了等于把判断成本转嫁给用户。少了 deny,系统会向两个极端失效——要么把人问烦(他一路按同意,门就没了),要么被放宽到没有约束。
- 然后是匹配维度:工具名不够用,必须还能按路径模式与命令前缀匹配,因为「改代码」和「改测试」是同一个工具。另外要有一个「这个工具会不会改东西」的标记,让只读工具默认放行——这个字段必须在工具协议第一版就留,否则外部接进来的工具(MCP、Skills)要么全放行要么全问一遍。
- 最后是冲突解决,这是最容易答错的一半:不要按规则表顺序取第一条命中的。顺序会变成隐含语义,你在末尾加一条更具体的规则却被上面某条宽泛规则吃掉,而现象是「什么也没发生」。正确口径是「更具体的赢,具体度相同时更严格的赢」,实现上就是把优先级写成一个可比较的排序键。还有一条:写操作没有规则命中时默认必须是 ask,默认放行的门等于没有门。
- 可预期的追问:规则从哪来?至少三处——内置默认、用户配置、本次会话里的临时批准。三处合成一张表,靠具体度而不是来源决定优先级,这样加一处来源不需要改判定逻辑。
Key points
- Three states, not two: ask exists for operations whose answer depends on the current task
- Deny versus ask is about whether to interrupt the user, not merely strictness
- Match on tool name, path pattern, and command prefix, plus a read-only flag so reads pass by default
- Resolve conflicts by specificity rather than table order, most specific then strictest, as a sort key
- Unmatched writes default to ask, and rules from defaults, config, and the session merge into one table
答题要点
- 三态而不是两态:ask 是给「取决于当时在干什么」的操作留的位置
- deny 与 ask 的差别是「要不要打扰人」,不只是严格程度
- 匹配维度:工具名、路径模式、命令前缀,另加一个只读标记让只读工具默认放行
- 冲突解决按具体度而不是表顺序:更具体的赢、同具体度更严格的赢,写成一个排序键
- 写操作无规则命中时默认 ask;规则可以来自默认表、配置与本次会话,靠具体度统一裁决
The user rejects a tool call. How does the loop continue, and why not just throw?用户拒绝了一次工具调用,循环该怎么继续?为什么不能直接抛错?
Common in ChinaCommon overseasDeep dive#approval#agent-loopHow to reason about it · think before answering
- This looks like an exception-handling question but really asks whether you have pictured what a rejected call looks like in the message array. Answering catch it and tell the user misses that the problem is structural.
- How to break it down: start from the constraint. When the model requests tools, an assistant message carrying the call list is appended, and the protocol requires exactly one result message per call, or the next request is invalid and most gateways reject it outright. So a rejection must become a result message one way or another.
- Conclusion: design the gate so that returning a tool result means the call was not executed. Return nothing to allow, or a failed result explaining why. A rejected call then looks exactly like a tool failure, and the model reroutes as it would after any failure, with no new message type and only a few lines of change in the loop.
- Price the alternative: throwing forces you to catch outside the loop, decide whether the turn continues, and still synthesize a result message, piling complexity onto the error path, which is the hardest path to test. With parallel calls it is worse, since one rejection should not void two calls that already succeeded.
- One behavioral detail: write the rejection in three parts — what was blocked, why, and which alternative exists. Permission denied makes the model retry the same call; testing is the acceptance criterion, change the code under test makes it reroute. A tool's failure text is the model's behavior spec.
- Likely follow-up: does waiting for approval block the loop? Yes, and it should — that is the pause. What matters is the no-channel case, in CI, pipes, or another agent's shell, where you must treat ask as deny, since auto-approving turns the most dangerous environment fully autonomous.
分析过程 · 先想清楚再作答
- 这题表面在问异常处理,实际在问「你有没有想过被拒的那次调用在消息数组里长什么样」。答「捕获异常、提示用户」的人没意识到问题出在消息结构上。
- 怎么拆:先看约束。模型请求调工具时,消息数组里会多一条带调用清单的助手消息;而协议要求**每个调用都必须有且只有一条对应的结果消息**,否则下一轮请求不合法,大多数网关会直接报错。所以「拒绝」这件事必须以某种形式变成一条结果消息,逃不掉。
- 结论:把审批门做成「返回一条工具结果就等于不执行」。放行返回空,不放行返回一条 ok 为 false 的结果,内容是为什么不执行。于是被拒的调用和「工具执行失败」在消息数组里长得一模一样,模型会像处理任何一次失败那样自己改道——不需要为审批发明新的消息类型,循环里的改动只有几行。
- 反过来算抛异常的代价:你要在循环外面接住它、判断这一轮还要不要继续、还得补一条工具结果消息,复杂度全堆在错误路径上——而错误路径是最难测的地方。而且并行调用时更糟:一个被拒不该让另外两个已经执行完的调用作废。
- 还有一条决定行为的细节:拒绝文本要写成模型能改道的样子,三段——被拒的是什么、为什么、可以换哪条路。写「permission denied」它会原地重试同一个调用;写「测试是验收标准,请改被测代码」它会换一条路。工具的失败信息就是模型的行为规范。
- 可预期的追问:那审批的等待会不会阻塞循环?会,而且应该会——它就是循环里的一次暂停,模型在等、工具没执行、什么都没发生。要注意的是没有提问渠道的场景(CI、管道、别的 Agent 的 shell):此时必须按拒绝处理,自动同意等于把最危险的环境变成全自动。
Key points
- Protocol constraint: exactly one result message per tool call, or the next request is invalid
- Shape the gate so returning a result means not executed, and returning nothing means allowed
- Rejections then look like tool failures, so the model reroutes on its own and the loop barely changes
- Throwing piles complexity onto the error path and, with parallel calls, voids calls that already succeeded
- Rejection text has three parts: what was blocked, why, and the alternative; with no channel, treat ask as deny
答题要点
- 协议约束:每个工具调用必须有且只有一条结果消息,否则下一轮请求不合法
- 门的形状是「返回一条结果就等于不执行」,放行返回空
- 于是被拒调用与工具失败同构,模型按处理失败的方式自己改道,循环几乎不用改
- 抛异常会把复杂度堆到错误路径上,并行调用时还会牵连已经成功的调用
- 拒绝文本三段:被拒的是什么、为什么、可以换哪条路;没有提问渠道时按拒绝处理
When is it acceptable to run an agent fully autonomously, and what preconditions do you require?什么情况下可以给 Agent 开全自动模式?你会要求哪些前置条件?
Common in ChinaCommon overseasIntermediate#autonomy#safetyHow to reason about it · think before answering
- This tests engineering judgment and honesty. Answering never, too risky scores nothing, and answering sure, I always run it is worse. The signal is offering checkable conditions instead of an attitude.
- How to break it down: translate autonomy into who absorbs the mistakes. Nobody is watching, so the environment must absorb them, and three preconditions map to three ways of absorbing: a sandbox bounds the damage, rollback undoes what happened, and logs make it auditable. Missing any one means do not enable it.
- Make each criterion concrete. Sandbox: the worst thing it can damage is something you accept losing — throwaway directory or container, limited file scope, network cut where it is not needed. Rollback: one action returns you to the previous state, and having version control is not enough, because uncommitted work must survive too, which is why file snapshots are required. Observability: a complete, replayable record of what it did, which is what an append-only event log provides.
- State the counterintuitive conclusion: autonomy is not a switch, it is the output of infrastructure. Turning off the approval gate without the three preconditions is not autonomy, it is unsupervised. Conversely, once all three hold, the marginal value of prompting drops, because prompting existed to let a human stop irreversible actions.
- One production point people miss: do not judge a gate by how often it asks. A gate that asks on every step is often less safe, because it trains users to approve without reading. The two right metrics are whether any irreversible action slipped through and how many times a typical task interrupts the user.
- Likely follow-up: does a dangerous-command denylist count as isolation? No, it is fat-finger protection. There are a hundred ways around a regex, and real isolation only comes from the sandbox. Volunteering that distinction shows you know how thin that layer is.
分析过程 · 先想清楚再作答
- 这题在考工程判断力,也在考诚实。答「不能开,太危险」得不到分,答「可以开,我平时都开」更糟。区分度在于你能不能给出可检查的条件,而不是一个态度。
- 怎么拆:把「自动」翻译成「出错时谁来兜」。人不在场兜,所以必须让环境兜。三条前置条件正好对应三种兜法:沙盒兜住损失范围、回滚兜住已经造成的改动、日志兜住事后追责。少一条就不该开。
- 三条各自的判据要具体。沙盒:最坏情况下它能损坏的东西你能接受——临时目录或容器、有限的文件范围、切断不该有的网络。可回滚:出错后能一键退回,而且判据不是「有版本控制就行」,未提交的改动也要保得住,所以文件快照是必需的。可观测:它做过什么有完整记录、能一条条回放,这就是只追加的事件日志的用处。
- 结论要点出一个反直觉的地方:**自动模式不是一个开关,而是一整套基础设施的结果。** 只把审批门关掉、不做前三条,那不叫自动,叫无人监督。反过来,三条都具备时,审批门的价值会自然下降——因为「问一句」的收益本来就来自「人能拦住不可逆的事」。
- 生产视角补一条常被忽略的:衡量一道门的好坏不要用「问的次数」。每步都问的门实际安全性往往更低,因为它训练用户不看内容直接同意。正确的两个指标是「不可逆操作有没有漏过去」和「一个典型任务里用户被打断几次」。
- 可预期的追问:命令黑名单算不算隔离?不算,它是防手滑的最后一道。正则绕过的办法有一百种,真正的隔离只能靠第一条。这个区分要主动讲,它说明你知道自己那道闸有多厚。
Key points
- Translate autonomy into who absorbs errors: with no human present, the environment must
- Three preconditions: a sandbox bounding damage, rollback that covers uncommitted work, and replayable observability
- Missing any one means do not enable it; disabling the gate without them is unsupervised, not autonomous
- Judge a gate by irreversible actions that slipped through and interruptions per typical task, not prompt count
- A command denylist is fat-finger protection, not isolation; only the sandbox provides that
答题要点
- 把「自动」翻译成「出错时谁来兜」:人不在场,所以环境必须兜
- 三个前置条件:沙盒(损失范围可接受)、可回滚(含未提交改动)、可观测(完整可回放的记录)
- 少一条就不该开;只关掉审批门不做这三条,叫无人监督不叫自动
- 衡量门的好坏用「不可逆操作有没有漏过去」与「典型任务被打断几次」,不用问的次数
- 命令黑名单是防手滑不是隔离,真正的隔离只能靠沙盒
D6 Error Handling and Self-Correction: Feeding Failures Back, Backoff Retries, Loop Detection, and Cancellation
How do you classify errors at agent runtime? Which ones go back to the model to self-correct, and which surface to the user?Agent 运行时的错误怎么分类?哪些该回灌给模型让它自纠,哪些该直接报给用户?
Common in ChinaCommon overseasBasic#error-handling#agent-loopHow to reason about it · think before answering
- This checks whether you have an actionable rule. Splitting errors into network, business, and system sounds tidy but does not help you write code, because it says nothing about handling.
- How to break it down: classify by handling, not by origin. The one-line rule is: can the model fix this itself? If yes, feed it back. If not, but a later attempt would succeed, retry. Only if neither holds, surface it to the user.
- Then place the common failures. Feedback is the biggest bucket: tool failures, arguments that are not valid JSON, command timeouts, and the model looping — all fixable in its next turn. Retry covers only rate limits and transient network errors. Surfacing is reserved for a broken environment (no key, gateway persistently down) and for hard limits firing.
- One detail that shows depth: the same error can land in different buckets depending on when it happened. If a stream breaks before any chunk was emitted, retrying is safe; if half a sentence is already on the user's screen, retrying prints it twice, so you must surface instead. That is why the classifier takes a count of chunks already emitted.
- Close with an implementation discipline: the feedback text is the model's spec for its next move, so it must state whether there were side effects and what to do next. Write failed, file unchanged, retry as-is versus write failed sends the model down two completely different paths.
- Likely follow-up: can feedback loop forever? Yes, so self-correction is the first line and hard limits are the last. In one injected run where arguments were always malformed, the model tried four different tools, failed all four, and the round limit ended it. Self-correction without a limit hands your control flow to luck.
分析过程 · 先想清楚再作答
- 这题在看你有没有一条可执行的判据。按「网络错误 / 业务错误 / 系统错误」分类的答案听着整齐,但对写代码毫无帮助——因为它没告诉你每一类该怎么处理。
- 怎么拆:分类要按**处理方式**来分,不按错误来源来分。一句话的判据是:这个错误模型自己改得动吗?改得动就回灌;改不动但换个时机能好就重试;两条都不成立才向用户抬头。
- 然后把常见故障套进去。回灌那一档最大:工具执行失败、参数不是合法 JSON、命令超时、模型在打转,全是模型下一轮能改的。重试那一档只有网关限流与网络抖动。抬头那一档只留给环境坏了(没有密钥、网关一直不通)与硬上限触发。
- 有一个能显出深度的细节:**同一个错误可以落在不同的档里,取决于它发生的时机。** 流在中途断开这件事,如果一个分片都还没吐出来,重试是安全的;如果已经吐了半段话到屏幕上,重试会让用户看到同一段话说两遍——那时候必须抬头。所以分类函数的参数里要带上「这次尝试已经吐出去几个分片」。
- 结论还要带上一条实现纪律:回灌的文本就是模型下一步的行为规范,所以它必须写清有没有副作用、给出下一步动作。「写入失败,文件没有任何改动,请原样重试」和「写入失败」会让模型走两条完全不同的路。
- 可预期的追问:那回灌会不会永远转不出来?会,所以自纠是第一道、硬上限是最后一道。我实测过一段「参数一直发不对」的注入:模型连换四个工具、四次都失败,最后是靠轮数上限收场的。只有自纠没有上限,等于把无限循环交给运气。
Key points
- Three buckets by handling: feed back for self-correction, retry with backoff, or surface to the user
- One rule: feed back what the model can fix, retry what a later attempt fixes, surface the rest
- Tool failures, bad arguments, command timeouts, and looping all go back to the model; rate limits and transient network errors are retried
- The same error splits by timing: a stream that breaks before any output is retryable, after output it must surface
- Feedback text must state side effects and the next action; self-correction is the first line, hard limits the last
答题要点
- 按处理方式分三类:回灌自纠、退避重试、向用户抬头
- 判据一句话:模型自己改得动就回灌,换个时机能好就重试,都不成立才抬头
- 工具失败、参数非法、命令超时、模型打转都属于回灌;限流与网络抖动属于重试
- 同一个错误按时机分档:断流在吐字前可重试,吐字后必须抬头
- 回灌文本要写清有没有副作用与下一步动作;自纠是第一道,硬上限是最后一道
How would you implement retries and backoff? Why add jitter, and which errors must never be retried?重试与退避你会怎么实现?为什么要加抖动?哪些错误绝对不该重试?
Common in ChinaCommon overseasIntermediate#retry#backoffHow to reason about it · think before answering
- The signal is not the phrase exponential backoff, which everyone says. It is two things: which layer the retry wraps, and whether you can name the errors that must never be retried.
- Answer the layering first, the half most people get wrong: retries wrap the gateway call, not the whole turn. Retrying requires idempotence, and write tools are not idempotent. Retrying a turn means a half-edited file gets edited again, and if the first edit actually succeeded and only the response was lost, the second attempt fails with no such text. So tool failures always go back to the model and only gateway errors are retried.
- Then jitter, with the arithmetic out loud: if fifty sessions hit a rate limit at once and everyone waits one second, a second later fifty requests arrive together, are rejected together, and wait together. The limit is not relieved, it is turned into a periodic stampede. Jitter does one thing: it spreads those requests across a window so the rhythm disappears.
- The jitter shape matters too: do not sample uniformly from zero to the window, since a few milliseconds is effectively no backoff. Half fixed plus half random keeps a floor while breaking the rhythm. Cap the window, because four or five doublings reach minutes and the user should decide by then, and treat a retry-after header as a floor since it beats your guess.
- Then the three never-retry cases, which is what marks real experience: failures where a write already had an effect, because retrying writes twice; failures caused by user cancellation, which is not a fault at all and shows up as the agent firing another request after you cancelled; and a stream that breaks after output was already shown, because retrying repeats the same sentence. Auth and malformed-argument 4xx responses are equally pointless to retry.
- Likely follow-up: how many attempts? The count matters less than a cap on total wait and whether the user can interrupt mid-wait. In my implementation the backoff sleep also listens to the cancel signal, otherwise pressing cancel still waits out a full backoff.
分析过程 · 先想清楚再作答
- 这题的区分度不在「指数退避」四个字——那个人人都会说。区分度在两个地方:重试包在哪一层,以及你能不能说出不该重试的那几类。
- 先答层次,这是最容易答错的一半:**重试包在网关调用这一层,不包整轮。** 理由是重试的前提是这一步幂等,而写工具天生不幂等。重试整轮意味着「已经改了一半的文件」会被再改一遍;如果第一次的编辑其实成功了、只是响应丢了,第二次会撞上「找不到那段原文」。所以工具失败一律走回灌,只有网关错误走重试。
- 再答抖动,要能算给面试官听:假设五十个会话同时撞上限流,大家都「等一秒再试」,一秒后五十个请求同时打上去、同时被拒、再同时等一秒——限流没被缓解,而是被重试拖成了一场周期性雪崩。抖动做的事只有一件:把这些请求摊到一个时间窗里,让节拍消失。
- 抖动的写法也有取舍:不要用「0 到窗口之间随机」,那有可能抽到几毫秒,等于没退避;用「窗口的一半确定、一半随机」既有下限又打散了节拍。另外窗口要有上限,指数涨四五次就到分钟级,那时候该让用户自己决定;网关回了 retry-after 就把它当下限,它比你的猜测准。
- 然后是绝对不该重试的三类,答出来才算做过:① 写操作已经产生了副作用的失败,重试会写重;② 用户按下取消导致的失败——它不是故障,重试的现象是「按了取消它却又发了一次请求」;③ 已经有内容吐到屏幕上之后的断流,重试会让同一段话说两遍。另外 4xx 里的鉴权与参数错误重试一百次也是同样的结果。
- 可预期的追问:重试几次合适?次数不是重点,重点是「总等待时间的上限」与「用户能不能中途打断」。我的实现里退避的等待也接了取消信号,否则按下取消还要干等一次退避。
Key points
- Retries wrap only the gateway call, never the whole turn, since write tools are not idempotent
- Jitter exists to break the rhythm so rate-limited clients do not stampede in lockstep
- Prefer equal jitter over full jitter to keep a floor, cap the window, and treat retry-after as a lower bound
- Never retry: writes that already had an effect, failures caused by cancellation, or a break after output was shown
- The metrics that matter are total wait cap and interruptibility, so the backoff sleep must be cancellable too
答题要点
- 重试只包网关调用这一层,不包整轮:写工具不幂等,重试整轮会重复副作用
- 抖动的作用是打散节拍,避免同时被限流的一批请求变成周期性雪崩
- 用等量抖动而不是全抖动(保住下限),窗口要有上限,retry-after 当下限
- 绝对不重试:已产生副作用的写、用户取消引发的失败、已有输出之后的断流
- 关键指标是总等待时间上限与可中断性,退避的等待本身也要能被取消
When the user presses cancel, which layers must the cancellation signal reach before things have really stopped?用户按下取消,你的取消信号要穿过哪几层才算真的停下来?
Common in ChinaCommon overseasDeep dive#cancellation#subprocessHow to reason about it · think before answering
- This is almost a binary test of hands-on experience. People who have not built it stop at set a flag and check it in the loop; people who have go straight to child processes.
- How to break it down: translate stopping into which resources are still held. A turn holds three: an in-flight streaming HTTP connection, a running child process, and the loop itself. Cancellation must reach the first two; the loop only cleans up.
- So the chain has four links: the keypress event from the terminal, an AbortController broadcasting in-process, the signal handed to fetch so the connection actually closes, and the same signal handed to the command runner so it kills the whole process group. Missing any link looks like cancel did nothing, but differently: without the third the traffic keeps flowing, without the fourth the command keeps running.
- The fourth link is the messy one and worth volunteering: spawn the child in its own process group and kill the group (negative PID), send a terminate signal first, allow a couple of seconds to wind down, then force kill. The command being run spawns its own children, so killing only the outer shell leaves orphans running while they still hold the pipes, which means your promise never settles. That is what cancelled but not stopped actually is.
- Three more details people miss: cancellation is scoped to one turn, not the session, so each turn gets a fresh controller; a failure caused by cancellation is not a fault and must not trigger a retry; and content already received must be kept in history, because what the user has seen cannot vanish.
- Likely follow-up: what about non-interactive environments? Pipes and CI have no keypress events, so the first link does not exist and cancellation can only come from a signal or the program itself. That is why which keys count as cancel belongs in a small pure function you can unit test, while the other three links are verified by triggering the controller directly.
分析过程 · 先想清楚再作答
- 这题几乎是一道「做过没做过」的判别题。没做过的人答到「设一个标志位,循环里检查它」就停了;做过的人会立刻说到子进程。
- 怎么拆:把「停下来」翻译成「哪些资源还在占着」。一次 Agent 的轮次里占着资源的有三处——一个正在流的 HTTP 连接、一个正在跑的子进程、还有循环自己。取消要落到前两处上,最后一处只是收尾。
- 于是链路是四环:① 按键,终端的 keypress 事件;② 一个 AbortController,进程内广播;③ 把 signal 交给 fetch,连接才会真的断开;④ 把同一个 signal 交给子进程的执行器,杀掉整个进程组。缺任何一环的现象都是「按了没用」,但表现不同:缺 ③ 是流量还在跑,缺 ④ 是命令还在跑。
- 第四环最脏,值得主动展开:子进程要用独立进程组启动,杀的时候杀整个组(负号 PID),先发终止信号、留两秒收尾、到点还没退就强杀。原因是被调的命令自己还会拉起子进程,只杀最外层的 shell,孙子进程会变成孤儿继续跑,而且还持着管道——于是你的 Promise 永远不会完成,这就是「杀了却没停」。
- 还有三个容易漏的细节:取消的粒度是「这一轮」而不是整个会话,所以每轮一个新的控制器;取消导致的失败不是故障,不许触发重试;已经收到的内容要保留下来落进历史,用户看过的东西不能凭空消失。
- 可预期的追问:无交互环境怎么办?管道与 CI 里没有 keypress 事件,第一环不存在,取消只能来自信号或程序自己。所以「哪些键算取消」要抽成一个可单测的纯函数,后面三环则用直接触发控制器的方式来验。
Key points
- Translate stopping into which resources remain held: the streaming connection, the child process, and the loop
- Four links: keypress, AbortController, the signal passed to fetch, and the signal passed to the command runner which kills the whole process group
- Kill children via their own process group and a negative PID, terminate then force kill, or orphans holding the pipes make the call never settle
- Cancellation is scoped to one turn, not the session, so use a fresh controller per turn, and never retry a cancellation-induced failure
- Keep whatever was already received; non-interactive environments have no keypress link, so make the key check a testable pure function
答题要点
- 把「停下来」翻译成「哪些资源还占着」:流式连接、子进程、循环本身
- 四环:按键 → AbortController → 传给 fetch 的 signal → 传给子进程执行器并杀整个进程组
- 杀子进程要用独立进程组加负号 PID,先终止后强杀,否则孙子进程持着管道让调用永不返回
- 取消的粒度是一轮而不是会话,每轮一个新控制器;取消引发的失败不许重试
- 已收到的内容要保留;无交互环境没有按键这一环,把按键判定抽成纯函数来单测
D7 Session Persistence and Recovery: an Append-Only Event Log, Resume and Forking, Week One Retrospective
For session persistence, would you use an append-only event log or store a session snapshot? What does each cost?会话持久化你选事件日志还是存会话快照?各自的代价是什么?
Common in ChinaCommon overseasBasic#persistence#event-logHow to reason about it · think before answering
- This checks whether you choose by failure mode. Saying event logs are more professional scores nothing, and neither does snapshots are simpler. The signal is making the two costs comparable.
- How to break it down: ask what happens if the write is interrupted halfway. A snapshot is a whole-file overwrite with unbounded failure modes: a truncated JSON does not parse at all, and worse, when the new content is shorter than the old, the tail of the previous file survives and you get a syntactically valid, semantically corrupt file. Append-only has exactly one failure mode: the last line may be incomplete. The single mode is the one you can actually design for.
- Second dimension: a snapshot holds the present, not the past. It cannot answer which round made that edit or how many tokens the last round cost, and postmortems and cost accounting always ask. A log carries history by construction, because it records what happened rather than what is.
- Third dimension: forking. Forking a log is copying the first N lines; forking a snapshot means deep-copying an object and deciding which fields should follow.
- State the log's costs too, or it sounds like a sales pitch: you must replay on read, the file only grows so long sessions need archiving, and you must decide which event types participate in reconstruction versus which are display only, or the replay logic forks. In my implementation only message events rebuild state; tool metadata, usage, errors, and lineage are for humans.
- Likely follow-up: event types will grow, so what about old logs? Two rules — never crash on an unknown type (treat it as display only), and never change the meaning of a field you already wrote. The second is rewriting history, which is worse than incompatibility.
分析过程 · 先想清楚再作答
- 这题在看你会不会按「失败模式」选方案。答「事件日志更专业」拿不到分,答「快照简单够用」也拿不到——区分度在于你能不能把两者的代价说成可比较的东西。
- 怎么拆:先问一句「这个文件写到一半崩掉会怎样」。快照是整体覆盖,失败模式无穷多:截断的 JSON 整个解析不了;更阴险的是新内容比旧内容短时,尾部还留着旧文件的残渣,于是你拿到一个语法合法、语义错乱的文件。只追加的失败模式只有一种——最后一行可能不完整。**唯一的那种,才是你能事先想清楚的那种。**
- 再看第二个维度:快照只有现状,没有历史。「那次编辑是第几轮做的」「上一轮花了多少 token」这类问题它答不了,而事后复盘与算成本一定会问。事件日志天然带历史,因为它记的是「发生了什么」而不是「现在是什么」。
- 第三个维度是分叉:日志分叉就是复制前 N 行,快照分叉要深拷贝整个对象并想清楚哪些字段该跟着走。
- 结论要给出日志的代价,否则听起来像在推销:读的时候要自己重放(多一层代码);文件只增不减,长会话要另配归档;而且要定清楚「哪些事件参与重建、哪些只用于展示」,否则重放逻辑会分叉。我的实现里只有 message 事件参与重建,工具调用的 meta、用量、错误、血缘都只给人看。
- 可预期的追问:事件类型以后会加,旧日志怎么办?两条纪律——读到不认识的类型不要崩(当成不参与重建的那一类),已经写出去的字段含义不许改。后者等于篡改历史,比不兼容更糟。
Key points
- Choose by failure mode: append-only has one, whole-file overwrite has unbounded ones
- The nastiest overwrite case is shorter new content leaving old bytes in the tail, giving a valid but corrupt file
- A snapshot holds only the present, so it cannot answer which round or how many tokens
- The log's costs: replay on read, a file that only grows, and a clear rule on which events rebuild state
- Evolution rules: never crash on unknown event types, never change the meaning of a field already written
答题要点
- 按失败模式选:追加写只有「最后一行不完整」一种,整体覆盖的失败模式无穷多
- 覆盖写最阴险的情况是新内容比旧内容短,尾部残留旧数据,文件语法合法语义错乱
- 快照只有现状没有历史,答不了「第几轮做的」「花了多少 token」这类复盘问题
- 日志的代价是要重放、文件只增不减、必须定清楚哪些事件参与重建
- 演进纪律:不认识的事件类型不要崩,已写出去的字段含义不许改
How does an append-only log handle a last line that was cut off mid-write?只追加的日志怎么处理写到一半崩掉的最后一行?
Common in ChinaCommon overseasIntermediate#durability#event-logHow to reason about it · think before answering
- This looks like the smallest question and filters the most. It is not about writing a try/catch, it is about whether you treat a torn last line as normal. Answers like add a checksum or use transactions try to eliminate it, and it cannot be eliminated.
- How to break it down: state the framing first — a torn last line is the normal case, not an exception. Processes get Ctrl+C'd, killed, and have their terminals closed; over a year of sessions this happens many times. Since it is normal, handling it belongs on the main path, not hidden in an error branch.
- The conclusion is one sentence: stop at the line before the bad one, do not skip it and do not repair it. Stop, because nothing exists after the bad line. Do not skip, because skipping assumes corruption can appear mid-file, which append-only writes do not produce — if it really did, the file was touched by something else and guessing is even worse.
- Then why not repair, the half people get wrong: the tempting move is to delete the bad line and rewrite the file. That is an overwrite, exactly what append-only exists to avoid, and if that rewrite is interrupted you lose the good lines too. The right behavior is to stop on read and to keep appending after the last complete event on write. The half line stays in the file forever; it is harmless and it is evidence of where the last crash happened.
- Do not validate only that the JSON parses. Check at least two more things: sequence numbers must strictly increase, since non-increasing means two processes overwrote each other or the file was concatenated, and the type must be recognized. When reading line by line there is an even earlier signal: the line without a trailing newline is the unfinished one, which beats waiting for a parse error.
- Likely follow-up: do you tell the user? Yes, with the line number and the reason. Recovering partially and staying silent makes the user think the model is guessing; saying it stopped at line N with the first M events intact lets them decide whether to keep using that session.
分析过程 · 先想清楚再作答
- 这题看着最小,实际最能筛人。它问的不是「你会不会写 try catch」,而是「你有没有把这件事当成常态」。答「加个校验和」「用事务」的人,都是在试图消灭它,而它消灭不掉。
- 怎么拆:先给出定性——**最后一行写坏是常态,不是异常。** 进程会被 Ctrl+C、被 kill、被关掉终端,一个会话跑一年,这种情况会发生很多次。既然是常态,处理它就该是主路径的一部分,不该藏在错误分支里。
- 结论只有一句:**读到坏行就停在前一行,不跳过、不修补。** 停在前一行是因为坏行之后不存在东西;不跳过是因为跳过它等于假设文件中间也会坏,而追加写不会产生那种情况——一旦真的产生,说明文件被别的东西动过,那时候更不该猜。
- 然后是「不修补」为什么重要,这是最容易答错的一半:常见的自作聪明是发现最后一行坏了就删掉它再写回去。**那是一次改写,而改写正是只追加想避免的事**——如果这次改写本身被中断,你会连前面那些好行一起弄坏。正确做法是读的时候停在前一行,写的时候从最后一条完整事件之后继续追加。那半行会一直留在文件里,它无害,而且它是「上次崩在这里」的证据。
- 校验别只校验 JSON 能不能解析。至少再查两件事:序号必须严格递增(不递增说明有两个进程在互相覆盖或者文件被拼过),类型必须认识。逐行读的时候还有一个更早的信号——**没有换行符结尾的那一行,就是没写完的那一行**,比等解析失败更准。
- 可预期的追问:那要不要告诉用户?要,而且要给行号与原因。恢复得不完整而不说,用户会以为模型在瞎猜;说清「停在第几行、前面 N 条是完整的」,他自己就能判断要不要接着用这条会话。
Key points
- Frame it first: a torn last line is normal, so handling it belongs on the main path
- Stop at the line before the bad one and never skip it, since skipping assumes mid-file corruption
- Never repair it: deleting and rewriting is an overwrite that can destroy the good lines if interrupted
- On write, keep appending after the last complete event and leave the half line as crash evidence
- Validate strictly increasing sequence numbers and known types; a missing trailing newline is the earliest signal
答题要点
- 定性先说:最后一行写坏是常态,处理它属于主路径而不是错误分支
- 读到坏行停在前一行,不跳过——跳过等于假设文件中间也会坏
- 绝不「顺手修好」:删掉坏行再写回去是一次改写,中断时会连好行一起弄坏
- 写入时从最后一条完整事件之后继续追加,那半行留着当崩溃证据
- 校验要加上序号严格递增与类型可识别;逐行读时「没有换行符结尾」是更早的信号
What is the difference between forking a new session at some point in history and rolling back to that point?从历史某一步分叉出一条新会话,和回滚到那一步有什么区别?
Common in ChinaCommon overseasDeep dive#fork#session-stateHow to reason about it · think before answering
- This tests semantic precision and whether you have realized there is more than one kind of state. Anyone who says they are basically the same will ship a feature that corrupts the user's files.
- How to break it down: name what each one touches. A fork grows a new branch from that point and leaves the original untouched, implemented by copying the first N events into a new file. A rollback moves the current branch back, implemented either by rewriting the log or by invalidating everything after that point. One is addition, the other subtraction, and subtraction forces you to ask who still references what you dropped.
- Then the real point of the question: session state is not one thing. The event log governs the conversation, not the files already modified on disk. So after forking, the edited source file does not travel back with you — you get an old conversation paired with new files, and if you do not say so, the model keeps reasoning from a false premise. Rolling files back needs file snapshots, a separate mechanism.
- Conclusion: the two mechanisms are independent, you can roll back only one of them, and the user must be told which one they rolled back. In my implementation the log restores the conversation, snapshots restore files, and forking touches only the former.
- Two implementation points worth volunteering: fork by copying into a new file rather than truncating in place, since truncation is an overwrite that can lose both branches if interrupted; and do not renumber sequence numbers in the fork, because keeping them lets you map the fork point straight back to the parent.
- Likely follow-up: can you fork at any event? No. Only after a user message or an assistant message without tool calls. Truncating right after an assistant message that requested tools yields a message array with a call and no result, which makes the next request invalid, since every call needs exactly one result. So list the valid points for the user instead of making them count sequence numbers.
分析过程 · 先想清楚再作答
- 这题在考语义精确度,也在考你有没有想过「状态不止一份」。答「差不多,都是回到某一步」的人,接下来一定会做出一个把用户文件搞坏的功能。
- 怎么拆:先分清两件事各自动了谁。分叉是「从那一步长出一条新的,旧的原封不动」,实现是复制前 N 条事件到新文件;回滚是「让当前这条退回到那一步」,实现要么是改写现有日志,要么是在语义上把后面的作废。前者是加法,后者是减法——**加法几乎不会出事,减法要考虑清楚被丢掉的东西还有没有人在引用。**
- 然后是这题真正的题眼:**会话状态不止一份。** 事件日志管的是对话,磁盘上被改过的文件不在它管辖范围内。所以分叉出一条新会话之后,那个已经被改过的源文件不会跟着回去——你得到的是「一段旧对话 + 一份新文件」,如果不说清楚,模型会基于错误的前提继续推理。文件的回滚要靠文件快照,那是另一套机制。
- 结论:两套机制互相独立,可以只回滚一边,而且要让用户知道自己回滚的是哪一边。我的实现里日志恢复对话、快照恢复文件,分叉只碰前者。
- 实现上还有两条值得主动讲:分叉要复制到新文件而不是原地截断(原地截断是改写,写到一半崩了会同时失去母会话和分叉);分叉的序号不重新编号(保留原编号才能拿分叉点直接对回母会话)。
- 可预期的追问:能从任意一条事件分叉吗?不能。只有用户消息与不带工具调用的助手消息之后才是干净边界。在一条带工具调用的助手消息之后截断,会得到「请求了工具却没有结果」的消息数组,下一轮请求直接不合法——每个调用必须有且只有一条结果消息。所以要把可选位置列给用户,别让他自己数序号。
Key points
- Forking is additive: copy the first N events into a new session and leave the parent untouched; rollback is subtractive and must handle what it drops
- The real point is that state is plural: the log governs the conversation, not the files already changed on disk
- So after a fork you have an old conversation with new files; rolling files back needs snapshots, an independent mechanism
- Fork by copying into a new file rather than truncating in place, and keep the original sequence numbers so the fork point maps back
- Only user messages and assistant messages without tool calls are clean fork points, so list the valid ones for the user
答题要点
- 分叉是加法:复制前 N 条事件长出新会话,母会话原封不动;回滚是减法,要处理被丢掉的东西
- 题眼是状态不止一份:日志管对话,磁盘上改过的文件不在它管辖范围内
- 所以分叉之后是「旧对话 + 新文件」,文件回滚要靠快照,两套机制互相独立
- 分叉要复制到新文件而不是原地截断;序号不重新编号,才能对回母会话
- 只有用户消息与不带工具调用的助手消息之后才是干净分叉点,要把可选位置列给用户
D8 Reference Injection: Parsing @ Files, Directories, URLs, and Images, and Accounting for What Got Injected
Implementing file-reference injection (an @ syntax, say), how would you handle directories, binary files, and very large files?实现文件引用注入(比如 @ 语法),你会怎么处理目录、二进制文件和超大文件?
Common in ChinaCommon overseasBasic#context-injection#file-handlingHow to reason about it · think before answering
- The easy failure is answering just skip them for all three. The signal is giving each a different treatment and explaining why they differ.
- How to break it down: ask what the model will do with the material. For a directory it will pick one or two files to read, so give it a listing. For a binary it will try to interpret garbage, so give it nothing. For an oversized file the answer is the important one: it will draw a confident conclusion from truncated material without noticing anything is missing.
- So: directories inject a file listing only, because a directory is a scope, not material; binaries are rejected, detected by a null byte; oversized files are also rejected, but the message must carry the next action — read it in ranges with an offset, or reference only the relevant part.
- Why not truncate the oversized file is the real dividing line. Truncating a log gives the model the head and tail while the one crucial error line sits in the removed middle. Rejecting gives it a path to the part it actually needs. Truncation is acceptable only for files that already passed the size check, where the magnitude is bounded and the cut is annotated with how much was removed and how to fetch it. The line is between off by a little and off by an order of magnitude.
- Volunteer the check order too: size first without reading, then bytes to detect binary, and only then decode text. Reversed, a two-hundred-megabyte video is fully loaded into memory first. One security invariant as well: the resolved absolute path must stay inside an allowed root, since string-scanning for dot-dot is never reliable.
- Likely follow-up: what about images? Parsing treats them like files, but they travel on the multimodal message path, which is a different feature. Until that path exists, reject them as binary — making the tool not pretend beats returning garbage.
分析过程 · 先想清楚再作答
- 这题最容易答浅:三种情况各说一句「跳过就好」就完了。区分度在于你能不能对每一种给出**不同的**处理,并且说清为什么不同。
- 怎么拆:先问「模型拿到这份材料之后会做什么」。目录的答案是「它会挑一两个文件读」,那就给它清单;二进制的答案是「它会试着理解乱码」,那就一个字节都不给;超大文件的答案最关键——它会**基于被截断的材料给出很自信的结论**,而它看不出自己被骗了。
- 所以三种处理是:目录只给文件树不给内容(目录是「范围」不是「材料」);二进制直接拒绝,判据是读到空字节;超大文件也拒绝,但要在提示里给出下一步——用分段读的工具带偏移量取,或者只引用其中一段。
- 「超大文件为什么不截断」是这题的真正分水岭。截断一个日志,模型拿到首尾各一段,中间那句关键报错正好在被截掉的地方。拒绝反而让它有机会拿到真正需要的那一段。**能截断的只有已经通过大小检查的文件**——量级可控,而且截断处要写清截了多少、怎么补读。分界线是「差一点」还是「差一个数量级」。
- 还要主动讲检查顺序:先看文件大小(不用读文件),再读字节判二进制,最后才转文本。倒过来写,一个两百兆的视频会先被完整读进内存。安全上还有一条不变量:解析出来的绝对路径必须落在允许的根里面,字符串里查不查两个点都不可靠。
- 可预期的追问:图片呢?解析层它和文件没区别,但它要走多模态那条消息通路,是另一件事。能力没就位之前就按二进制拒绝——**让它别假装可以**,比返回一堆乱码有用。
Key points
- Three kinds of material, three treatments, decided by what the model would do with each
- Directories inject a listing, not contents: a directory is a scope, and the model will pick files itself
- Binaries are rejected on a null byte; oversized files are rejected too, with a ranged-read next step
- Rejecting beats truncating: the model cannot see the removed middle and answers confidently anyway
- Check size, then bytes, then decode; and the resolved absolute path must stay inside an allowed root
答题要点
- 三种材料三种处理,判据是「模型拿到它之后会做什么」
- 目录只给文件清单不给内容:目录是范围不是材料,模型自己会挑文件读
- 二进制读到空字节直接拒绝;超大文件也拒绝,但要给出分段读的下一步
- 拒绝比截断诚实:被截掉的中间段模型看不出来,会给出很自信的错结论
- 检查顺序是大小、字节、文本;解析出的绝对路径必须落在允许的根里面
When injected context exceeds its budget, how do you trim it, and should the trimming rules be visible to the user?注入的上下文超过预算怎么裁?裁剪规则要不要让用户可见?
Common in ChinaCommon overseasIntermediate#context-budget#trimmingHow to reason about it · think before answering
- This tests whether you have thought about the consequence of dropping things. Answering truncate to fit misses the important half: which source you drop matters more than how much.
- How to break it down: tier the sources. Anything the user named individually — a specific file, a URL — is first tier; anything a glob or directory pulled in is second tier. The criterion is whether the user explicitly expected it to be there, and it drives every rule that follows.
- Then three rules, in priority order: never silently drop a first-tier source, truncate it instead and say how much was cut; cap any single source at half the budget, or one large file crowds out everything else; if the total still exceeds, drop second-tier sources starting from the last. Implementation is two passes — enforce the per-source cap, then drop from the tail.
- The answer to the second half is a clear yes, and not for user-experience reasons: invisible trimming makes the model and the user reason from different facts. The user believes all five files are in context, the model sees two, and its answer looks unreasonable. So report three numbers — characters, estimated tokens, and share of that lane's budget — and report dropped sources separately. Silent dropping is the red line.
- One more implementation discipline: the accounting goes to the terminal, not to the model, since those numbers exist for human decisions and would just spend budget again. Likewise, inject the material as its own message rather than concatenating it into the user's sentence, so compaction can drop the injection while keeping the original words.
- Likely follow-up: how do you split the total budget across system prompt, instruction files, memory, history, and injections? That is a layer up, and its precondition is exactly this per-lane accounting. Typical policy: reserve fixed allowances for the stable lanes and allocate the rest to history and injections with a recency preference.
分析过程 · 先想清楚再作答
- 这题在考「你有没有想过丢东西的后果」。答「按长度截到预算以内」的人漏掉了最要紧的一半:**丢哪一条比丢多少更重要。**
- 怎么拆:先给来源分级。用户逐个点名的(一个具体文件、一个网址)是一级;通配符或目录带出来的是二级。分级依据是「用户有没有明确指望它在里面」——这一条决定了后面所有规则。
- 然后是三条规则,顺序就是优先级:一级来源永不静默丢弃,宁可截断也要留一段并说清截了多少;单个来源不超过预算的一半,否则一个大文件就能把别的挤光;总量还不够就丢二级,从最后一条开始丢。实现上就是「先各自服从单源上限,再从尾部往前丢二级」两遍扫描。
- 第二问的答案是明确的**要可见**,而且理由不是「体验好」,是「不可见的裁剪会让模型和用户各自基于不同的事实说话」。用户以为那五个文件都在上下文里,模型只看到两个,于是它的回答在用户看来毫无道理。所以报账要打三个数:字符数、估算 token、占本路预算的百分比;被丢掉的要单独报数。**静默丢弃是红线。**
- 还有一条实现纪律值得讲:报账打在终端,不打给模型——那些数字是给人做决策的,塞给模型只是又花一遍预算。同理,注入内容要作为**单独一条消息**而不是拼进用户那句话,这样压缩上下文时可以只丢注入、保留原话。
- 可预期的追问:那总预算怎么在系统提示、指令文件、记忆、历史、引用之间分?那是另一层的问题,而它的前提正是「每一路都自己报账」——没有各路的账,总预算无从分配。分配策略通常是给固定的那几路(系统提示、指令文件)留死额度,剩下的按「越近越优先」给历史与注入。
Key points
- Tier the sources first: individually named ones are first tier, glob- or directory-derived ones second
- Three rules: never silently drop first tier, cap any single source at half the budget, drop second tier from the tail
- Trimming must be visible, or the model and the user end up reasoning from different facts
- Report three numbers — characters, estimated tokens, share of the lane budget — and count dropped sources separately
- Accounting goes to the human, not the model; keep injections as their own message so compaction can drop them selectively
答题要点
- 先给来源分级:用户逐个点名的是一级,通配符与目录带出来的是二级
- 三条规则:一级永不静默丢弃(宁可截断)、单源不超预算一半、超限从尾部丢二级
- 裁剪必须可见,否则模型与用户会基于不同的事实说话
- 报账三个数:字符数、估算 token、占本路预算比例;被丢掉的单独报数
- 报账打给人不打给模型;注入自成一条消息,便于压缩时只丢注入保留原话
When should you push content into the context yourself, and when should you let the model fetch it with tools?什么时候该主动把内容塞进上下文,什么时候该让模型自己调工具去取?
Common in ChinaCommon overseasDeep dive#context-strategy#retrievalHow to reason about it · think before answering
- This tests architectural judgment and has no single right answer, so answering both, unconditionally invites follow-ups until you break. The signal is producing an actionable criterion.
- How to break it down: compare the cost structures. Pushing content costs zero round trips and lets the user decide what to look at. Letting the model retrieve usually costs two to four round trips and lets the model decide. Since every round trip resends the whole message array, saving two of them compounds.
- The criterion lands on who knows where to look: push when the user knows which file matters, retrieve when the user is also searching. That covers almost every case and explains why both paths must coexist rather than one replacing the other.
- Then volunteer the two cases where you should not push, which is the deep end: first, when you are not actually sure which file matters and you paste five in on instinct — that is not guidance, that is noise, and it may crowd out the one useful source; second, when the material changes during the conversation, such as a file being edited, since an injection is a snapshot while a tool read is the present state, and the model will otherwise reason from stale content.
- A production note: tell the model in the system prompt not to re-read files that were already pasted in. Without that line it has a real chance of reading the same file again, and the same content appearing twice both costs money and makes it hesitate between the two copies.
- Likely follow-up: where does retrieval — vector search, a code index — fit? It is a stronger version of let the model fetch, differing only in retrieval quality, so the criterion is unchanged. Its real value shows up precisely when neither the user nor the model knows where to look.
分析过程 · 先想清楚再作答
- 这题在考架构判断,而且它没有唯一答案——所以答「都要有」不加条件的人会被追问到底。区分度在于你能不能给出一条可执行的判据。
- 怎么拆:把两者的成本结构摊开对比。主动注入是零次往返、用户决定看什么;让模型自己检索通常是两到四轮往返、模型决定看什么。而每一轮往返都要把整个消息数组重发一次,所以省掉两轮的收益是复利的。
- 判据就落在「谁知道该看哪儿」:**用户知道该看哪个文件就注入,用户也在找就让模型自己搜。** 这一条几乎能覆盖全部情况,而且它解释了为什么两条路径必须并存,而不是选一条。
- 然后要主动说两种**不该**注入的情况,这是这题的深水区:一、你其实不确定该看哪个文件,凭感觉贴了五个进去——那不叫指路,那叫把噪音塞进上下文,而且挤掉的可能正是有用的那一份;二、材料会在对话过程中变化(比如一个正在被改的文件),注入的是一份快照而工具读到的是当下,这种情况必须让它自己读,否则它会拿着旧内容做判断。
- 生产视角补一条:注入过的东西要在系统提示里说明「已经贴进来的文件不要再读一遍」。少了这句,它有不小的概率把同一个文件再读一次——同一份内容在上下文里出现两遍,既花钱又容易让它在两份之间纠结。
- 可预期的追问:那检索类的东西(向量检索、代码索引)算哪一类?算「让模型自己取」的加强版——区别只在检索质量,判据没变。真正的取舍仍然是「谁更知道该看哪儿」,而检索的价值恰恰在于用户和模型都不知道的时候。
Key points
- Compare cost structures: pushing is zero round trips and user-decided, retrieval is two to four and model-decided, and every trip resends all history
- One criterion: push when the user knows which file matters, retrieve when the user is searching too
- Two cases not to push: pasting several files on instinct, and material that changes mid-conversation since an injection is a snapshot
- Tell the model not to re-read what was already pasted, or the same content shows up twice
- Vector search and code indexes are a stronger form of model-side retrieval, and the criterion does not change
答题要点
- 对比成本结构:注入零往返、用户决定;检索两到四轮、模型决定,而每轮都重发全部历史
- 判据一句话:用户知道该看哪个文件就注入,用户也在找就让模型自己搜
- 两种不该注入:自己也不确定就贴一堆(是噪音)、材料会在对话中变化(注入是快照)
- 注入过的内容要在系统提示里说明不用再读一遍,否则同一份内容会出现两遍
- 向量检索与代码索引属于「让模型自己取」的加强版,判据不变
D9 Project Instruction Files: Three-Tier Loading, Import Expansion, and the System Prompt's Merge Order
For layered project instruction files, how do you decide precedence, and who wins on a conflict?分层的项目指令文件,优先级怎么定?冲突时谁赢?
Common in ChinaCommon overseasBasic#layered-config#system-promptHow to reason about it · think before answering
- This tests whether you have actually implemented layered configuration. Stopping at more specific wins invites two follow-ups immediately: how do you measure specific, and does the losing rule still appear in the final result?
- How to break it down: enumerate the layers and their scopes. Usually three — user level (my own preferences, valid in any repository), project level (this repository's rules, binding on everyone who enters), directory level (special rules for this patch of code). Scope narrows as you go, and that order is the precedence. You do not need a numeric priority field; the moment one exists, someone writes 999 and nobody dares touch it.
- The conclusion is two sentences: more specific goes later, and on a conflict the later one wins. Three non-obvious decisions follow. First, sort during discovery (directory layers outermost first) so merging never re-sorts. Second, the overridden rule must actually be removed from the final prompt — leaving both and letting the model choose means you do not know which it took, while your report says the later one won; a report that disagrees with what you actually sent is worse than no report. Third, conflicts you resolve automatically must be machine-recognizable in shape, such as single-line key-value directives; two contradictory prose paragraphs are not recognizable, so do not pretend to handle them — those can only be caught by a human reading the source listing.
- Volunteer two discovery pitfalls, because they are the implemented-it-before kind. The repository root must be found by walking up for a marker file (a VCS directory or a package manifest), stopping at the first hit, and falling back to the start directory — walking all the way to the filesystem root means a user running in a temp directory makes you read a world-writable path whose content lands in the system prompt. And discovery results must be deduplicated by real path, because symlinking the project file into a subdirectory is common; without dedup the same content appears twice and the model hesitates between two equally authoritative copies.
- Likely follow-up: why override rather than merge? Because instructions are natural language, and two natural-language rules have no reliable merge semantics. List-shaped things (a set of forbidden directories, say) can be unioned, but that is a field explicitly declared as a list, not a sentence. Override what should be overridden, union what you first defined as a list, and never blend the two mechanisms.
分析过程 · 先想清楚再作答
- 这题在考「你有没有真的实现过分层配置」。答「越具体的优先」就停下来的人会立刻被追问:那『具体』是怎么量的?冲突了之后,输的那条还在不在最终结果里?
- 怎么拆:先把层数与作用域列清楚。一般是三层——用户级(我这个人的偏好,走到哪个仓库都成立)、项目级(这个仓库的规矩,进来的人都该遵守)、目录级(这一片代码的特殊规矩)。作用域从宽到窄,**顺序就是优先级**,不需要再引入一个 priority 数字字段——数字字段一旦有了,就会有人写 999,然后谁都不敢改。
- 结论是两句话:**越具体的越靠后,冲突时后者赢。** 落地时有三个不那么显然的决定。一、发现阶段就把顺序排好(目录级要外层在前),合并阶段不再排第二次。二、**被覆盖的那条必须真的从最终结果里删掉**,不能两条都留着让模型自己挑——它同时看到两条矛盾的规矩时,你不知道它挑了哪一条,而你给用户的报告写的是『后者赢』,报告和实际发出去的东西不一致比没有报告更糟。三、能自动消解的冲突必须是机器能认出来的形状(比如只认独占一行的『键:值』),两段散文互相矛盾机器认不出来,就不该假装能处理,那种只能靠人看一眼来源清单发现。
- 还要主动讲两个发现阶段的坑,因为它们是「实现过才知道」的:**仓库根必须靠标记文件往上找**(`.git` 或包清单),找到第一个就停,没找到就把启动目录当根——一路上溯到文件系统根的话,用户在临时目录里跑一次,你就会去读一个谁都能写的路径,而它的内容会进 system prompt。**发现结果要按真实路径去重**,因为把项目级文件软链到子目录很常见,不去重同一份内容会在 prompt 里出现两遍,模型会在两份「同样权威」的副本之间犹豫。
- 可预期的追问:为什么不做成「合并」而是「覆盖」?因为指令是自然语言,两条自然语言没有可靠的合并语义。数组类的东西(比如禁止访问的目录清单)可以取并集,但那是一个明确声明为列表的字段,不是一句话——**该覆盖的覆盖、该取并集的先把它定义成列表**,混在一起做才是灾难。
Key points
- Three scopes from broad to narrow — user, project, directory — and the order is the precedence; no numeric priority field
- Two rules: more specific goes later, and the later one wins on conflict
- The overridden rule must be removed from the final prompt, not left alongside for the model to pick
- Auto-resolved conflicts must have a machine-recognizable shape; do not pretend to resolve contradictory prose
- Find the repo root by walking up for a marker, falling back to the start directory; dedupe discoveries by real path
答题要点
- 三层作用域从宽到窄:用户级、项目级、目录级;顺序就是优先级,不引入 priority 数字字段
- 规则两句话:越具体越靠后,冲突时后者赢
- 被覆盖的那条必须真的从最终 prompt 里删掉,不能两条都留着让模型挑
- 能自动消解的冲突必须是机器认得出的形状;散文矛盾不假装能处理
- 仓库根靠标记文件往上找、找不到就用启动目录;发现结果按真实路径去重
If instruction files can import one another, what safety limits would you add?指令文件支持互相导入,你会加哪些安全限制?
Common in ChinaCommon overseasIntermediate#imports#safety-limitsHow to reason about it · think before answering
- The signal here is naming four limits and explaining why two of them are commonly collapsed into one. Answering only prevent cycles misses three.
- How to break it down: enumerate by what can go wrong — runaway depth, cycles, duplicates, and escaping the allowed root — one limit each.
- Depth limit: three levels is enough; deeper means the author should refactor rather than you should support it, and with no limit a malicious or accidental long chain makes loading scale with chain length on every single request. Cycle detection: the test is whether the target is in the current import chain, and the chain pops when a level exits. Duplicate imports: the test is whether it was imported anywhere in this expansion, a set that never pops, and a hit is skipped with a note. Root boundary: the resolved absolute path must stay inside the allowed root, and a user-level file may only import from the home directory — a repository's instruction file must not be able to pull in files from the user's home, which turns the project into a springboard.
- Separating cycles from duplicates is the real dividing line. Using one set is the easy wrong version, and its symptom is concrete: a diamond import — A imports B and C, both import D — gets reported as a cycle, while a genuine cycle only produces a message that explains nothing. Two sets, two jobs: one ordered so you can print the whole trail, one that only answers membership.
- Also cover failure handling, since it decides whether this is usable by others: one bad line must not invalidate the whole instruction file. Replace the offending line with a short parenthetical note in place, keep everything else in effect, and report the problem explicitly. Instruction files are written by other people and you cannot guarantee every line is right, and one typo bricking a repository is the worst design; but silently skipping is also wrong, because its symptom is the model quietly failing to honor a rule.
- Likely follow-up: cap the size of a single file? Yes, and more strictly than for one-off injections, because instructions are resident: two thousand characters of instructions resent over twenty rounds is twenty times the cost. Refusing to load with a stated reason beats truncating, since truncation makes the model miss a rule it believes it has seen.
分析过程 · 先想清楚再作答
- 这题的区分度在于你能不能说出**四条**,而且能说清其中两条为什么容易被写成同一条。只答「防循环」的人漏了三条。
- 怎么拆:按「会出什么事」列。深度失控、循环、重复、越界,各对应一条限制。
- **深度上限**:三层足够。再深说明作者该重构文件,而不是你该支持它;而且没有上限时一条恶意或手滑的长链会让加载变成 O(链长),每一轮请求都付一次。**循环检测**:判据是「当前这条导入链里有没有它」,链要在退出一层时弹出。**重复导入**:判据是「整次展开里导入过没有」,这个集合永不弹出,命中就跳过并说明。**根边界**:解析成绝对路径之后必须还在允许的根里面,而且用户级文件只能导入主目录里的东西——一个仓库的指令文件不该能把用户主目录里的文件拉进来,那是把项目当成了跳板。
- 循环与重复的区分是这题真正的分水岭。只用一个集合是最容易写出来的版本,它的症状很具体:菱形导入(甲同时导入乙和丙,乙丙都导入丁)会被报成循环,而真正的循环反而只得到一句说不清发生了什么的提示。**两个集合两件事:一个有顺序(能打出整条链),一个只回答在不在。**
- 还要讲失败处理,因为它决定这套东西能不能给别人用:**坏一行不许让整份指令失效。** 出问题的那一行换成一句括号说明留在原地,其余内容照常生效,但问题要显式报出来。指令文件是别人写的,你没法保证每一行都对,而「一个路径写错整个仓库不能用」是最糟的设计;反过来,静默跳过也不行——它的表现是模型莫名其妙少守了一条规矩。
- 可预期的追问:单个文件要不要限大小?要,而且要比一次性的引用严得多——指令是常驻的,一段两千字符的指令二十轮就重发了二十遍。超了直接不加载并说明原因,比截断诚实:截断会让模型少看到一条它以为自己看到了的规矩。
Key points
- Four limits: depth cap, cycle detection, duplicate skip, root boundary
- Cycles test the current import chain (ordered, popped on exit); duplicates test a set that never pops
- One shared set misreports diamond imports as cycles while giving real cycles a useless message
- The path test is that the resolved absolute path stays inside the root; user-level files must not be reachable from a project
- A bad line becomes an inline note and everything else stays in effect, but the problem must be reported explicitly
答题要点
- 四条限制:深度上限、循环检测、重复跳过、根边界
- 循环看「当前导入链」(有序、退出即弹出),重复看「整次展开里导入过没有」(永不弹出)
- 只用一个集合会把菱形导入误报成循环,而真正的循环得不到有用的提示
- 路径判据是解析成绝对路径后仍在根内;用户级文件不许从项目里被拉进来
- 坏一行换成一句说明留在原地,其余照常生效,但问题必须显式报出来
How do you make the final system prompt explainable to the user, and why is that worth doing?怎么让最终的 system prompt 对用户可解释?为什么这件事值得做?
Common in ChinaCommon overseasDeep dive#observability#prompt-assemblyHow to reason about it · think before answering
- This tests product-level engineering judgment and is easily answered as just log it. The signal is articulating the cost of being unexplainable and deriving from it which numbers to print.
- How to break it down: establish the system prompt's unique position — it is the only part of the context that the user never sees yet takes effect on every single request. Injections are things the user typed, history is what they said; only the system prompt is assembled behind their back.
- So when it is wrong, the symptom is the model inexplicably not following instructions, and the user has nothing to inspect: how many layers exist, which one won, whether an import silently failed. The essence of the cost is that the model and the user reason from different facts — the user believes all four layers are in effect, the model saw two, so its behavior looks unreasonable, and there is no vantage point from which to observe the gap.
- That derivation makes the output obvious: one command that prints the number of segments, each segment's source file, its characters and estimated tokens, its share of this lane's budget, plus a conflict table saying which directive finally applies and what it overrode, and finally one line per broken import. Resident context also deserves printing automatically at startup rather than hiding behind a command the user must think to type.
- Volunteer three implementation decisions. First, the accounting goes to the terminal, not the model — these numbers exist for human decisions and would just spend budget again. Second, the budget excludes the built-in base prompt, which is a fixed cost we wrote ourselves; counting it makes the percentage meaningless as an answer to can the user add two more rules. Third, report only this lane; how the total budget is divided across system prompt, instructions, memory, history, and injections is a layer up, and its precondition is exactly this per-lane accounting.
- Finally, verification, which is the bonus here: how do you prove the rules took effect rather than the model merely claiming so? Look at tool-call arguments. Give each of the three layers a different test command and assert that the command it ran carries the most specific layer's argument — arguments are hard evidence, prose is not. Turning rule took effect into an assertable argument value is the only dependable way to accept a feature like this.
- Likely follow-up: should users be able to read the full system prompt verbatim? Yes, but as a second-level view. The first level should be this source-annotated summary, because reading a long block of prose still leaves the user unable to tell which sentence came from which file and what overrode what.
分析过程 · 先想清楚再作答
- 这题在考产品级的工程判断,而且很容易答成「打个日志就行」。区分度在于你能不能说清**不可解释的代价**,再倒推出该打哪几个数。
- 怎么拆:先说清 system prompt 的特殊地位——它是整个上下文里唯一一段「用户看不见、但每一轮都在生效」的内容。引用是用户自己写的 @,历史是他说过的话,只有 system prompt 是拼出来的。
- 所以它错了的表现是「模型莫名其妙不听话」,而用户手里没有任何东西可查:不知道有几层文件、不知道哪一层赢了、不知道有没有一个导入悄悄失败了。**代价的本质是模型和用户基于不同的事实说话**——用户以为四层规矩都在,模型只看到两层,于是它的行为在用户看来毫无道理,而这个 gap 没有任何入口可以观测。
- 倒推出该打的东西就很清楚了:一条命令(本课叫它 `/context`),打出段数、每段来自哪个文件、多少字符、约多少 token、占本路预算的百分之几,以及冲突表——哪个键最终生效的是哪条、被覆盖的各是什么,最后是每一条坏导入的提示。常驻的东西还值得在**启动时自动打一遍**,而不是藏在一条要用户主动敲的命令后面。
- 三个实现决定要主动说:一、**报账打在终端,不打给模型**,这些数字是给人做决策的,塞给模型只是再花一遍预算。二、**预算不含内置基座**,基座是我们自己写死的固定成本,算进「用户还能不能多写两行规矩」这笔账里,那个百分比就看不懂了。三、**只报自己这一路**,总预算怎么在系统提示、指令、记忆、历史、引用之间分配是另一层的问题,但它的前提正是每一路都自己报账。
- 最后是验证问题,也是这题的加分项:怎么证明规矩真的生效了,而不是模型嘴上说说?**看工具调用的参数值。** 让三层各写一条不同的测试命令,然后断言它执行的那条带着最具体那一层的参数——参数是硬的,模型说什么都不算。把「规则生效了」变成一个可断言的参数值,是这类功能唯一靠得住的验收方式。
- 可预期的追问:那要不要允许用户直接看到完整的 system prompt 原文?要,但那是第二层入口。第一层应该是这份带来源的摘要——原文一大段读下来,用户仍然不知道哪句话来自哪个文件、谁覆盖了谁。
Key points
- The system prompt is the only context the user cannot see yet applies every round; being unexplainable makes model and user reason from different facts
- One command printing segment count, each source, characters, estimated tokens, share of the lane budget, plus conflicts and broken imports
- Print resident context automatically at startup instead of hiding it behind a command
- Accounting goes to the human, not the model; the budget excludes the base prompt; report only this lane
- Verify with tool-call arguments: give each layer a different test command and assert the most specific one ran
答题要点
- system prompt 是唯一「用户看不见但每一轮都生效」的上下文,不可解释的代价是模型与用户基于不同事实说话
- 一条命令打出段数、每段来源、字符数、估算 token、占本路预算比例,以及冲突表与坏导入提示
- 常驻的东西启动时自动打一遍,不要只藏在一条要用户主动敲的命令后面
- 报账打给人不打给模型;预算不含内置基座;只报自己这一路
- 验证靠工具调用的参数值:让三层写不同的测试命令,断言它执行的是最具体那一条
D10 Task Lists and Self-Planning: the Todo Tool, Progress Rendering, and Early Detection of Spinning
What does letting the model maintain its own task list buy you over writing the plan in the prompt, and what does it cost?让模型自己维护任务清单,比在提示词里写计划好在哪?代价是什么?
Common in ChinaCommon overseasBasic#self-planning#tool-designHow to reason about it · think before answering
- This tests whether you have thought about why the feature works. Answering it makes the model more organized is empty and invites follow-ups until you break. The signal is a mechanism-level reason plus volunteering the cost.
- How to break it down: start from the basic fact that the model has no memory — every round it sees only the text in the context. So a plan that exists only inside one round's reasoning does not exist in the next. The list externalizes the plan into context text, turning where am I from something to recall into something to read. In one line: externalized state is more stable than an implied plan.
- Why not decompose it ourselves? Because our list and its actual steps diverge. You break the work into three steps up front, it reaches step two and finds reality differs — the list is now a wrong map it has no permission to fix, so it either pretends to follow it (the list is fake) or ignores it (the list is useless). Letting the model write it adds two more benefits: the plan becomes an observable write, so you see the intent before the mistake; and progress gains a tool-agnostic metric, which the third question builds on.
- You must volunteer the cost, and that is the dividing line: every list update is a tool round trip. Maintaining a three-item list can cost three or four extra rounds, and each round resends the whole message array. So the feature has a clear boundary — do not use a list for work under three steps, or a thirty-second task becomes five round trips. Conversely, it pays off most when steps have dependencies and can fail mid-way (you need to know which step to fall back to) and when the task is long enough to survive a context compaction, where the list preserves where am I for a few dozen tokens.
- Cite a concrete design tradeoff to show you built it: keep only three states — pending, in progress, done. Adding blocked looks more complete but hands the model a respectable escape hatch: a task it cannot finish gets marked blocked and legitimately skipped while the list looks fine. With only three states it has to say it is stuck, and only then can you help.
- Likely follow-up: should the list be persisted? No. It is this task's working surface, neither a fact nor a record of process; once the task ends it is meaningless, and persisting it just shows the next conversation a stale to-do list it must first spend a round evaluating.
分析过程 · 先想清楚再作答
- 这题在考「你有没有想过这个功能为什么有效」。答「让它更有条理」是空话,会被追问到底。区分度在于你能不能说出一个机制层面的理由,再主动交出代价。
- 怎么拆:先回到一条最基本的事实——**模型没有记忆,它每一轮看到的只有上下文里的那些字。** 所以「计划」如果只存在于它这一轮的推理里,下一轮就等于不存在。清单的作用是把计划**外化成上下文里的一段文字**,于是「我做到哪了」从一个需要回忆的问题变成了一个可以直接读的事实。一句话概括:**外化的状态比隐含的计划稳。**
- 为什么不是我们替它拆?因为我们拆的清单和它执行的步骤会对不上。你在开始之前拆三步,它执行到第二步发现根本不是那样——清单成了一份错的地图,而它没有权限改;它只能假装照着走(清单是假的)或者不管清单自己干(清单没用)。让它自己写还多两个好处:**计划变成一次可观测的写操作**(你能在它做错之前看见它打算怎么做),以及**进度有了一个与具体工具无关的度量**(第三题会用到)。
- 代价必须主动说,这是这题的分水岭:**每一次更新清单都是一次工具往返。** 一张三条的清单光维护它就可能多花三四轮,而每一轮都要把整个消息数组重发一次。所以这个功能有明确的适用边界——三步以内的活儿不要用清单,加了就是把一件三十秒的事变成五轮往返。反过来两种情况价值最大:步骤之间有依赖且中间会失败(失败要退回上一步,有清单才知道退到哪),以及任务长到会跨过一次上下文压缩(清单用几十个 token 保住了「整件事到哪了」)。
- 还要讲一个具体的设计取舍来证明你真做过:**状态只留三种(待做、进行中、已完成)。** 加第四种「阻塞」看着更完备,实际是给模型一个体面的逃跑出口——一条做不下去的任务标成阻塞就能名正言顺地绕过去,而清单上看起来一切正常。只有三种状态时,它做不下去就只能说出来,而说出来你才能帮它。
- 可预期的追问:清单要不要落盘?不要。它是这一次任务的工作面,不是事实也不是过程;任务结束就没有意义了,存下来只会让下一次对话看到一份过期的待办,然后先花一轮判断这份待办还算不算数。
Key points
- The mechanism: the model has no memory, so the list externalizes the plan into readable context text
- Do not decompose for it: our steps diverge from its execution, leaving a wrong map it cannot edit
- Extra upside: planning becomes an observable write, and progress gains a tool-agnostic metric
- Cost: every update is a round trip, so skip lists under three steps; they pay off on dependent, failure-prone, long tasks
- Only three states: a fourth blocked state is an escape hatch; the list is ephemeral and not persisted
答题要点
- 机制理由:模型没有记忆,清单把计划外化成上下文里可读的一段文字
- 不该我们替它拆:我们拆的步骤会与它的执行对不上,清单会变成一份它无权修改的错地图
- 额外收益:规划变成可观测的写操作;进度有了与具体工具无关的度量
- 代价:每次更新都是一次工具往返,所以三步以内不用清单;依赖多、会失败、会跨压缩的任务价值最大
- 状态只留三种:第四种「阻塞」是给模型的逃跑出口;清单易失不落盘
With streaming text and a fixed progress area in the same terminal, how do you manage the cursor and repaints?终端里同时有流式文本和固定的进度区域,你怎么管光标与刷新?
Common in ChinaCommon overseasIntermediate#terminal-rendering#cursor-controlHow to reason about it · think before answering
- This one is hard to fake, because its pitfalls only show up once you have written it. There are three levels of signal: naming the real conflict, giving an implementable scheme, and volunteering the degradation path.
- How to break it down: the conflict is that a terminal has one cursor and two things now want it. Streaming text keeps growing downward while the progress area must stay put. Print the progress directly and the next chunk pushes it up, so within seconds the screen holds a dozen stale copies.
- The first decision is to put the panel at the bottom, not the top. It is counterintuitive but firm: the top requires knowing how many lines the body has scrolled, and the body soft-wraps, so its line count depends on terminal width — you would need the window width and would have to handle the user resizing mid-run. The bottom only requires knowing how many lines the panel itself has, which is the one number you actually know.
- The scheme is four actions: draw (save the cursor, newline, paint the panel, leaving the cursor at its end); erase (restore to the saved position, then clear from there to the end of the screen — the panel is gone and the half-written body line survives); write body text (sandwiched between erase and draw); and repaint on change by erasing and drawing again. Use the DEC save/restore cursor rather than counting lines: there is only one slot but you only need one, and it naturally handles which column the half-written line stopped at, a number you cannot compute.
- Then the bonus: make this a wrapper function rather than scattering it through render code. Take a write-string function in, hand a hide/show-sandwiched one back. Done right, the render layer needs no changes at all — it never learns there is a panel below. That is what layering buys, as opposed to vague talk about cleaner code.
- Volunteer the degradation path: a non-TTY destination (a pipe, CI, another agent's shell) has no usable cursor, so emit no cursor control sequences at all and degrade to reprinting the block whenever it changes. The test is the isTTY flag. Emitting control sequences into a pipe turns them into garbage, which is worse than not having the feature — and it is exactly why acceptance must be a human looking at the screen rather than an exit code.
- Likely follow-up: why not use the alternate screen buffer? Because the session scrollback is then gone and the user cannot scroll back to the previous task's output. A CLI taking over the whole screen costs far more than it gains; consider it only for a genuine full-screen TUI.
分析过程 · 先想清楚再作答
- 这题很难靠背答案过,因为它的坑只有动手写过才知道。区分度有三层:能不能说清冲突的本质、能不能给出一个可实现的方案、能不能主动讲退化路径。
- 怎么拆:先说冲突的本质——**一个终端只有一条光标,而现在有两个东西要用它。** 流式文本一直在往下长,进度区域要待在一个不动的地方。直接把进度打出去,下一片文本就会把它挤上去,几秒之后屏幕上是十几个不同版本的进度。
- 第一个决定是**面板放底部,不是顶部**。这一条反直觉但很硬:顶部要算「正文已经滚了多少行」,而正文会自动换行,行数由终端宽度决定——你得知道用户窗口有多宽,还得处理他中途拉窗口。**底部只需要知道面板自己有几行,这是唯一一个你真的知道的数字。**
- 方案就是四个动作:画出来(保存光标位置 → 换行 → 画面板,光标停在面板末尾);擦掉(恢复到保存的位置 → 清掉从这里到屏幕末尾的一切,面板没了而正文那半行还在);写正文(夹在擦掉与画出来之间);内容变了就擦掉再画一次。用 DEC 的保存/恢复光标而不是自己数行,是因为它只有一个槽位但你也只需要一个,而且它天然处理了「正文那半行停在第几列」这个你算不出来的问题。
- 然后是这题的加分项:**把这一层做成一个包装函数,而不是散在渲染代码里。** 拿一个「写字符串」的函数进来,还一个夹了 hide/show 的函数出去。做对之后渲染层一行都不用改——它完全不知道屏幕下方有块面板。这就是分层的价值,而不是「代码更整洁」这种空话。
- 退化路径必须主动说:**非 TTY(管道、CI、别的 Agent 的 shell)没有光标可用,就一个光标控制符都不发**,退化成「内容变了就整块打一遍」。判据是 `isTTY`。把控制符发出去让它变成一串乱码,比不做这个功能更糟——而这正是「验收判据是肉眼看到现象、而不是 exit code」的原因。
- 可预期的追问:为什么不用 alt-screen(整屏接管)?因为终端会话的历史就没了,用户翻不回上一个任务的输出;一个 CLI 工具占掉整屏,代价远大于收益。真要做全屏 TUI 才考虑它。
Key points
- The conflict: one cursor, with streaming text growing downward and a panel that must stay put
- Put the panel at the bottom: the top needs the body's scrolled line count, which soft-wrapping makes width-dependent
- Four actions: save cursor and paint, restore cursor and clear below, write body in between, repaint on change
- Make hide/show a wrapper function so the render layer needs no changes and never learns the panel exists
- On a non-TTY emit no control sequences and reprint the block; the test is isTTY, and acceptance is visual
答题要点
- 冲突本质:一个终端一条光标,流式文本往下长而面板要不动
- 面板放底部:顶部要算正文滚了多少行,而软换行让行数取决于窗口宽度;底部只需知道面板自己几行
- 四个动作:保存光标画面板、恢复光标清到屏幕末尾、正文夹在中间写、变化时擦掉重画
- 把 hide/show 做成一个包装函数,渲染层一行都不用改,也不知道面板存在
- 非 TTY 一个控制符都不发,退化成整块重打;判据是 isTTY,验收靠肉眼看现象
How do you detect from runtime data that an agent is spinning in place, and what do you do once you detect it?怎么从运行数据里发现 Agent 已经在打转?发现之后怎么干预?
Common in ChinaCommon overseasDeep dive#stall-detection#agent-observabilityHow to reason about it · think before answering
- The easy half-answer is detect repeated calls. That is one kind of spinning, but the easiest to catch and the less common one. The signal is naming the other shape and explaining why the first detector misses it.
- How to break it down: two shapes. Mechanical repetition — calling the same tool with byte-identical arguments over and over. And circling in place — doing something different every round while finishing nothing. For the first, key on tool name plus raw argument text and interrupt once consecutive hits reach a threshold. Three rulings matter: compare raw arguments, not meaning (a one-space difference means it is at least trying something new); count only consecutive hits (an intervening different call resets, because read, edit, read again to confirm is a healthy rhythm); and interrupt by feeding a result back, not by escalating to the user.
- The second shape is the common and hard one in long tasks: it read A, then B, then C, arguments differ every time, the repetition detector never fires, and it is still exactly where it started. It is hard to spot because it looks busy the whole time.
- So you need a tool-agnostic progress metric, and a task list supplies one: how many rounds the list's revision number has not changed. Add a more serious signal — how many times a single item has been reopened from done, which means it thought it was finished and then found it was not; twice in a row is close to proof of circling. Those two signals plus the round counter you already keep for resource limits are enough; no new instrumentation required.
- Intervene in two stages, giving it a chance before escalating: on the first hit, feed back a reminder (the list has not moved for N rounds, update it or state where you are stuck) and let the round continue; only on the second hit stop the turn, through the same exit as the hard limits. Two implementation details are worth mentioning: feed the reminder as a user-role message, not assistant — an assistant message reads to the model as something it said itself, so it continues the same line of thought, while a user message reads as someone prodding it. And reuse the existing retryable-error signal rather than adding a new event type, since all the render layer needs to know is that the loop will go around again.
- The real deep end is the cost of false positives. Stall detection needs an exemption: do not judge staleness when the list has never been written at all. Without it, any short conversation that legitimately skips the list gets nagged by round three, and users quickly learn to ignore every warning. A guard that misfires is no guard, and that judgment matters more than the exact threshold.
- Likely follow-up: do the three hard limits — rounds, wall time, tokens — count as spin detection? No, they are resource backstops. Limits watch how much you spent; stall detection watches whether you moved. A task that burns its token budget in five rounds and a task that spins for eight rounds doing nothing are different failures and each needs its own gate.
分析过程 · 先想清楚再作答
- 这题最容易只答一半:「检测重复调用」。那确实是一种打转,但它是最容易抓也最不常见的那一种。区分度在于你能不能说出另一种形态,以及为什么第一种检测抓不到它。
- 怎么拆:把打转分成两种形态。**机械重复**——一字不差地反复调同一个工具同一个参数;**原地兜圈**——每一轮都在做不一样的事,但一件都没做完。第一种用「工具名 + 参数原文」当键,连续命中到阈值就打断,实现很朴素;关键是三条口径:比参数原文不比语义(只差一个空格就算在尝试新东西),只看连续(中间插过别的调用就重新计数,因为「读了改了再读一次确认」是正常节奏),以及打断的方式是回灌而不是抬头。
- 第二种才是长任务里最常见、也最难发现的:读了 A 又读了 B 又读了 C,参数每次都不同,重复检测一次都不会响,而它其实一直在原地。它最难发现的原因是**它看起来一直很忙**。
- 所以你需要一个**与具体工具无关的进展度量**,而任务清单正好提供了一个:清单的版本号多少轮没变。再加一个更严重的信号:同一条任务被从「已完成」重新打开了几次——它的意思是「它以为做完了,又发现没做完」,连续两次基本可以断定在兜圈子。两个信号加上轮数(本来就有的资源记账)就够了,不需要新的埋点。
- 干预分两级,**先给机会再抬头**:第一次命中回灌一条提醒(清单多少轮没动、请更新清单或说清卡在哪),这一轮照常继续;第二次命中才停下这一轮,走和硬上限完全一样的出口。两个实现细节值得讲:提醒要用 **user 角色**回灌而不是 assistant——assistant 消息会被它当成自己说过的话,于是它顺着原思路继续;user 消息才是「有人在催」。以及提醒事件复用现有的「可重试错误」那一位,**不给事件协议加新类型**,因为渲染层要知道的只是「循环接下来还会转一圈」。
- 最后是这题真正的深水区——**假警报的成本**。停滞检测必须有一条豁免:清单一次都没被写过时不判停滞。少了它,任何一次不用清单的短对话转到第三轮都会被提醒,而用户很快就学会无视所有提醒了。**一个会误报的守卫等于没有守卫**,这条判断比阈值调多少更重要。
- 可预期的追问:那三条硬上限(轮数、时长、token)算不算打转检测?不算,它们是资源兜底:**上限看的是花了多少,停滞看的是有没有进展。** 一个五轮就把 token 烧光的任务和一个转了八轮啥也没干的任务,是两种不同的失控,各需要一条闸。
Key points
- Two shapes: mechanical repetition (same tool, same arguments) and circling (different each round, no progress)
- Three rulings for repetition: compare raw arguments, count only consecutive hits, interrupt by feeding back
- Circling needs a tool-agnostic progress metric: rounds since the list's revision changed, plus reopen counts
- Two-stage intervention: feed back a reminder first (as a user-role message, not assistant), stop only on the second hit
- An exemption is mandatory: never judge staleness when the list was never used — a guard that misfires is no guard
答题要点
- 打转有两种形态:机械重复(同工具同参数)与原地兜圈(每轮都不同但没进展)
- 重复检测的三条口径:比参数原文、只看连续、打断方式是回灌而不是抬头
- 兜圈要靠与工具无关的进展度量:清单版本号多少轮没变,加上同一条被重开几次
- 干预两级:先回灌提醒(用 user 角色,不用 assistant),再命中才停下这一轮
- 必须有豁免:清单没被用过就不判停滞——会误报的守卫等于没有守卫
D11 Cross-Session Memory: Explicit Memory, Automatic Memory, and Three Criteria for Retrieval Injection
What problem does an agent's memory solve, versus conversation history and project instruction files?Agent 的记忆和会话历史、项目指令文件分别解决什么问题?
Common in ChinaCommon overseasBasic#agent-memory#context-designHow to reason about it · think before answering
- This tests whether you have actually built a memory system. Saying memory lets it remember things is empty — all three let it remember things. The signal is an operational classification rule, not three separate definitions.
- How to break it down: give the one-line split first — one stores facts, one stores process, one states rules. Memory holds facts that still hold across sessions (which command this repo's tests need); the session event log holds the process of this particular run, used for resume and fork; the project instruction file holds the rules, is committed, and applies to everyone. Their lifecycles differ completely: memory is long-lived but expires, a log is one per session and append-only, instruction files travel with the code.
- The more interesting difference is how each is consumed. Instruction files go into the system prompt in full every round, so they need a hard budget and truncation. The session log is replayed only on resume. Memory is the only one of the three that is retrieved by relevance and injected a few entries at a time — because it grows forever and most of it is irrelevant to the sentence in front of you. That retrievability is the essential difference.
- Then offer three tests to show you can actually sort things: will this still be true next time (if not it is process, and belongs in the log); is this learned or mandated (mandated goes in the instruction file, learned goes in memory); could you learn it by reading the code once (if so it is not worth remembering — memory should hold traps you hit, verbal conventions, approaches you tried that failed, and user preferences).
- Be concrete about the cost of mixing them. Put process in memory and three days later the model carries a long-dead now into the context without being able to say when that now was. Put learned guesses into the instruction file and an unreviewed assumption starts governing the whole team. Conversely, keep a real rule only in local memory and it vanishes on the next machine.
- Likely follow-up: should the memory directory be committed? My answer is no — it may contain things that should not be shared, and more fundamentally a fact that can influence everyone without review is dangerous. Experience worth sharing should be moved into the instruction file by a human and go through code review. Implementations differ here, so the point is articulating the tradeoff rather than reciting one product's behavior.
分析过程 · 先想清楚再作答
- 这题在考「你有没有真做过一个记忆系统」。答「记忆让它记住东西」是空话——三样东西都让它记住东西。区分度在于你能不能给出一条可操作的分类判据,而不是三段各自的定义。
- 怎么拆:先给一句话的分工——**一个记事实,一个记过程,一个是规定。** 记忆存的是跨会话还成立的事实(这个仓库的测试得用哪条命令);会话事件日志存的是这一次干活的过程,用来恢复与分叉;项目指令文件存的是规矩,进版本库、对所有人生效。它们的生命周期完全不同:记忆长期保留但会过期,日志一条会话一份且只追加不改写,指令文件跟着代码走。
- 更值得说的是**用法不同**:指令文件每一轮全量进系统提示(所以它有硬预算,一超就得截断);会话日志只在恢复的时候整段重放;记忆是三者里唯一**按相关性检索、只注入几条**的——因为它会一直长,而它绝大多数条目和眼前这句话无关。**能不能按需检索,才是记忆和另两者最本质的差别。**
- 然后给三条判据证明你分得清:①「这句话下次还成立吗」不成立的是过程,归日志(「刚才那次测试是红的」不该进记忆);②「这是学到的还是规定的」规定的进指令文件,学到的进记忆;③「读一次代码能知道吗」能知道的不必记——记忆该存的是踩过的坑、口头约定、试过但不行的做法、用户的偏好。
- 混在一起的代价要具体:把过程写进记忆,三天后模型带着一条早就不成立的「现在」进上下文,而它自己说不清那是什么时候的现在;把学到的东西写进指令文件,等于让一条没人评审的猜测对全组生效;反过来把规矩只记在本机记忆里,换台机器就没了。
- 可预期的追问:记忆目录该不该进版本库?我的答案是不该——它可能含有不该共享的东西,而更根本的是**一条没经过评审就能影响所有人的「事实」很危险**。值得全组共享的经验应该被人手动搬进指令文件、走一次代码评审。(这一条各家实现不一样,重点是能说出取舍,而不是背某个产品的行为。)
Key points
- One line: memory holds facts, the session log holds process, instruction files state rules
- The essential difference is consumption: only memory is retrieved by relevance a few entries at a time
- Three sorting tests: still true next time / learned or mandated / knowable by reading the code once
- Concrete cost of mixing: process in memory carries a stale now; guesses in instruction files skip review
- Memory is not committed: an unreviewed fact that governs everyone is dangerous; promote it to instructions
答题要点
- 一句话分工:记忆记事实、会话日志记过程、指令文件是规定
- 最本质的差别是用法:只有记忆按相关性检索、只注入几条;另两者一个全量进提示、一个整段重放
- 三条分类判据:下次还成立吗 / 学到的还是规定的 / 读一次代码能知道吗
- 混起来的具体代价:过程进记忆会带着过期的「现在」;猜测进指令文件会没评审就影响全组
- 记忆不进版本库:没经过评审就能影响所有人的事实很危险,要共享就搬进指令文件
How do you retrieve memories so that only the relevant ones get injected, and how would you define relevance?记忆怎么检索才能只注入相关的?相关性判据你会怎么定?
Common in ChinaCommon overseasIntermediate#memory-retrieval#context-injectionHow to reason about it · think before answering
- The easy answer is put it in a vector store and do semantic search. That is not wrong, but it moves the problem: a vector store solves ranking, while the hard parts here are how much you inject after ranking and how the user finds out what was injected. Those two are the signal.
- How to break it down: set the default first — the default is not to inject, not to inject everything. Fully injecting a directory of three hundred entries turns memory into a second system prompt, and two hundred ninety of them are unrelated to this sentence; they not only waste budget, they pull the model off course. So anything scoring zero gets in at all: prefer missing one over blurring everything. That sentence is the first dividing line.
- Then give the criteria, ordered by reliability. I use three: path hits (the file named in this sentence is exactly the file a memory is attached to — highest weight, because paths are exact and do not collide the way words do), keyword hits (score per hit, but with a mandatory cap), and source weighting (on a tie, what the user wrote by hand beats what the model sedimented). The third is not because humans are always right; it is accountability — the user remembers and can delete his own entry, while nobody owns the model's.
- The keyword cap is the second dividing line, because you only learn it by getting burned: without it, a memory with many keywords beats a memory with an exact path hit just by colliding on seven or eight words. That is not relevance, it is keyword stuffing — and the stuffing comes either from your fallback extractor (Chinese has no spaces, so without a tokenizer you slice adjacent character pairs and one sentence yields a dozen) or from the model itself, which will cheerfully hand you twelve keywords.
- You need both limits: a count limit against blur and a character limit against budget. The count should be counterintuitively small — two or three — because injected value falls with each extra entry while interference rises. How much of the total budget this lane deserves is a separate question about overall context allocation; this layer only needs to report how many characters and roughly how many tokens it took.
- The real deep end: hit reasons must be shown to the user and must not be sent to the model. Memory is the only context the user never mentioned that still shapes this answer — when it is wrong, the symptom is the model inexplicably insisting on something untrue, and the user has nothing to inspect. Printing this entry scored N because of path A and keyword B turns guessing into looking. The model does not need the scores; those are for a human decision. And when nothing matches, print a line saying so — it proves that not injecting was a decision, not a broken feature.
- Likely follow-up: when should you move to vector retrieval? Once you have thousands of entries and the user's phrasing often misses the memory's wording — synonyms, cross-language. But settle two things first: an embedding model is a new external dependency and a new cost, and semantic similarity is a continuous value, so you still have to pick your own threshold and count limit. Neither limit here goes away; only the ranking layer changes implementation.
分析过程 · 先想清楚再作答
- 这题最容易答成「上向量库做语义检索」。那不是错,但它把问题换了个地方放:向量库解决的是「怎么排序」,而这题真正的难点是**排完之后注入多少、以及注入之后用户怎么知道注了什么**。区分度就在这两处。
- 怎么拆:先立默认值——**默认是不注入,不是全注入。** 一个记了三百条的目录全量注入等于把记忆变成第二份系统提示,而其中两百九十条和这句话无关;它们不只浪费额度,还会把模型带偏。所以打分为零的一条都不进,**宁可漏也不要糊**。这一句是这题的第一个分水岭。
- 然后给判据,并按可靠性排序。我用三个:**路径命中**(这句话里提到的文件正好是某条记忆挂着的文件,权重最高——路径是精确的,不像词那样撞车)、**关键词命中**(命中几个算几分,但**必须有上限**)、**来源加权**(同分时用户亲手写下的压过模型自动沉淀的)。第三条的理由不是「人一定对」,是责任:用户写的那条他自己记得、能自己删,模型沉淀的那条没人认领。
- 关键词上限那条是第二个分水岭,因为它是只有踩过才知道的:**少了它,一条关键词写得多的记忆只要撞上七八个词就能压过一条路径精确命中的记忆。** 那不叫相关,那叫关键词堆砌——而堆砌它的既可能是兜底的抽词器(中文没有空格,不引分词表就只能切相邻两字组合,一句话能切出十几个),也可能是模型自己:让它给关键词,它会很热心地给十二个。
- 两个上限都要有:**条数管「别糊」,字符管「别把额度吃光」**。条数要小得反直觉(一次两三条),因为注入的价值随条数递减而干扰随条数递增。至于这一路总共该占多少额度,那属于上下文总预算的分配,是另一个题目;这一层只需要报出「我占了多少字符、约多少 token」。
- 最后是这题真正的深水区:**命中原因必须打给用户看,而且不发给模型。** 记忆是唯一一种「用户没提、却影响了这一轮回答」的上下文——它错了的表现是「模型莫名其妙地坚持一件不成立的事」,而用户手里没有任何东西可查。打出「这条命中几分、因为路径 A 与关键词 B」,问题就从猜变成看一眼。至于模型,它不需要知道分数,那是给人做决策用的。**而且一条都没命中的时候也要打一行「没命中」**:它证明「没注入」是判断的结果,不是功能坏了。
- 可预期的追问:什么时候该上向量检索?条目上千、且用户的说法和记忆的措辞经常对不上(同义词、跨语言)的时候。但换之前先想清楚两件事:嵌入模型是新的外部依赖与新的一笔成本,而且**语义相似度是个连续值,你仍然要自己定阈值与条数上限**——这题里的两个上限一个都省不掉,只有排序的那一层换了实现。
Key points
- Default to not injecting: anything scoring zero stays out; prefer missing one over blurring everything
- Three criteria by reliability: exact path hits, keyword hits scored per hit, and source weighting
- Keyword hits need a cap, or keyword stuffing outranks an exact path match
- Both limits matter: count against blur, characters against budget; the count should be small
- Show hit reasons to the user, not the model; print an explicit no-match line when nothing hits
- Vector retrieval only replaces the ranking layer — the limits and the hit reporting still apply
答题要点
- 默认不注入:0 分的一条都不进上下文,宁可漏也不要糊
- 三个判据按可靠性排:路径命中最硬、关键词命中按个数计分、来源加权(用户写下的压过模型沉淀的)
- 关键词必须有命中上限,否则关键词堆砌能压过路径精确命中
- 两个上限都要:条数管别糊、字符管别把额度吃光;条数要小得反直觉
- 命中原因打给用户不发给模型;一条都没命中也要明确说「没命中」
- 上向量检索只换掉排序那一层,两个上限与命中原因该打给谁一个都省不掉
What must never go into long-term memory, and how do you detect that a memory has gone stale?哪些内容绝不该写进长期记忆?记忆过期了怎么发现?
Common in ChinaCommon overseasDeep dive#memory-hygiene#secret-redactionHow to reason about it · think before answering
- Two parts. The first is easy — everyone knows not to store secrets. The second is where the signal is: most implementations never handle memory going stale at all, and those that do often implement it as automatic deletion. Part one needs concrete implementation details to prove you built the gate; part two needs a verifiable mechanism.
- How to break part one down: three classes, and their natures differ completely, so the handling differs too. Secrets are a security problem: the memory directory is plaintext, resident, and sent to the model every round, so one remembered secret is copied into every request — gate it hardest. Transient conclusions are a classification problem: that test is red right now is process, not fact, and belongs in the session log; store it and three days later it carries a long-dead now into the context without being able to say when that now was. Things already written in the code are a budget problem: not wrong, just not worth it — something you learn by reading a file once occupies resident budget and is resent every round.
- The secret class has three details only a builder would mention. Gate by shape, not by variable name: in my key is sk plus a long string, the long string is the target, while credentials belong in environment variables, never hardcoded is a good memory. Never echo a single character of the blocked value in the rejection message — copying it into an error writes it into another log: terminal scrollback, the session log, CI output. And saying you cannot store this is not enough: that credential already appeared in this input, so tell the user to rotate it. Add an honest boundary too: blocking the write is not the whole job, since the tool-call card and approval prompt may still echo it, and stopping that needs a second redaction in the render layer.
- The other two classes share a property: they will produce false positives, so the rejection must offer a fix. This repo is still on CommonJS right now is actually a decent memory that merely carries a time word; tell the user to drop the time word and rewrite it and he will, whereas a flat refusal makes him abandon the feature. The already-in-the-code gate has a trickier knob: too small a minimum fragment length causes wide false positives (at four characters, tests must use node --test gets matched by test inside package.json), and you must only read a file when the sentence actually names it — full-repo search hits so often that the gate degrades into nothing may be remembered.
- All three gates belong in the write layer, not duplicated at each entry point. The explicit command and the model tool both funnel into one write function, because invariants belong to the data: miss one place and that place is the hole — and here the hole leaks secrets. The rejection reason is fed back to the model verbatim, and it corrects itself, replacing the secret with the name of the environment variable.
- Part two: give every memory a verifiable anchor. A memory saying tests need the built-in runner should be able to point at go look at package.json, that command is written there — and you check it before injecting. That yields three states: verified, so inject; anchor broken (file gone, or the string no longer present), so do not inject but list it in the terminal; no anchor, so fall back to age — lacking an anchor does not mean it expired, only that nobody can judge whether it did.
- The deep end has two counterintuitive rules. First, do not auto-delete on failure: a broken anchor does not mean the memory is wrong, perhaps the file was just renamed, and deletion is irreversible while the test is a single substring check. Second, not injecting must be announced: stay silent and the user assumes it is still in effect, so the next time the model is wrong he checks the directory and the memory is plainly still there — silent failure is harder to debug than failure. The same applies to deletion: report it when the id you were told to delete does not exist.
- Likely follow-up: what about memories with neither an anchor nor an expiry rule? Three fallbacks: always keep the write time and the source (when something goes wrong you need to know who wrote it), stamp written N days ago onto the injection, and provide one command that shows every memory with its verification status. Maintainability of a memory system lives on the can I see it and delete it side, not on the write side.
分析过程 · 先想清楚再作答
- 这题两问,前一问容易答(谁都知道别记密钥),后一问才是区分度所在——**「记忆会过期」这件事绝大多数实现根本没做,做了的也常常做成自动删。** 前一问要靠具体的实现细节证明你真做过闸,后一问要靠一条可核对的机制。
- 第一问怎么拆:三类,而且三类的**性质完全不同**,所以处理方式也不一样。① **密钥类,安全问题**:记忆目录是明文的、常驻的、每一轮都往模型发一遍,一条记住的密钥等于抄进了每一次请求,拦得最死。② **临时结论,分类问题**:「现在那个用例是红的」是过程不是事实,属于会话日志;写进记忆的后果是三天后它带着一条早就不成立的「现在」进上下文,而它自己说不清那是什么时候的现在。③ **代码里已经写着的事,预算问题**:这一类不是错的,是不值得——读一次文件就知道的事占着常驻额度每一轮重发。
- 密钥那一类有三个只有做过才说得出的细节:**按形状拦不按变量名拦**(「我的 key 是 sk 加一长串」里那个长串才是要拦的,而「凭据要放进环境变量不要写死」是条好记忆);**拒绝话术里一个字的原文都不许回显**(把它抄进错误信息,等于把它写进了另一份日志——终端历史、会话日志、CI 输出);**只说不能记是没尽到责任的**,那个凭据已经出现在这次输入里了,要提醒用户去轮换。再加一句诚实的边界:拦住写入不等于万事大吉,模型请求的工具卡片与审批提示可能还在回显它,真要挡住得在渲染层再脱敏一次。
- 后两类的共同点是**一定会误杀**,所以拒绝话术必须给出改法。「这个仓库现在还在用 CommonJS」其实是条不错的记忆,只是带了个时间词;告诉用户「去掉时间词重写一遍就能记」,他就会改,只说「不许记」他会直接放弃这个功能。而「代码里已经写着」这条闸的旋钮更刁:判断片段的长度下限给小了会大面积误杀(给四个字符,「测试只能用 node --test」会被 test 命中 package.json),而且**必须只在这句话点了某个文件名时才去读那个文件**——全仓库搜索的命中率高得离谱,那条闸会直接变成「什么都不许记」。
- 三类都要拦在**写入那一层**,不是在两个入口各写一遍。显式命令与模型工具都汇到同一个写入函数,理由是不变量属于数据:少写一处,那一处就是漏洞——而这里漏掉的是密钥。拒绝的理由则原样回灌给模型,它读了理由自己就会改(把密钥改成记住那个环境变量名)。
- 第二问:**给每条记忆一个可核对的锚点。** 一条记忆说「测试要用内置运行器」,那它就该能指出「去看 package.json,里面写着那条命令」;注入之前去核对一眼。于是有三种状态:核对通过就注入;锚点失效(文件没了、那段字不在了)**不注入,但要在终端列出来**;没有锚点的只能靠年龄提醒——没有锚点不代表它过期了,只代表没人能替你判断它过没过期。
- 最后是这题真正的深水区,两条都反直觉。一、**失效了不要自动删**:锚点失效不等于这条记忆错了,也可能只是文件改了名,而**自动删是不可逆的,判据却只是一次字符串包含检查**。二、**不注入必须说出来**:悄悄不注入的话,用户会以为它还在生效,下次模型答错时他去查记忆目录,那条记忆明明还在文件里——**静默失效比失效更难查。** 同一条道理适用于删除:删了个不存在的编号也要报出来。
- 可预期的追问:那没有锚点又没过期机制的记忆怎么办?靠三件事兜底——写下时间和来源永远保留(出问题时你要知道是谁写的)、注入时带上「写于多久之前」、以及一条能让用户一眼看完全部记忆与核对状态的命令。**记忆系统的可维护性不在写入侧,在「能不能看见并且删掉」这一侧。**
Key points
- Three classes, three natures: secrets are security, transient conclusions are classification, already-in-code is budget
- Gate secrets by shape, never echo the value, and tell the user to rotate it; admit the render layer may still echo
- The latter two will misfire, so rejections must offer a fix; keep both the length floor and the named-file-only rule
- All three gates live in the single write function; the reason is fed back so the model corrects itself
- Staleness needs a verifiable anchor: verified injects, broken does not inject but is listed, no anchor falls back to age
- Never auto-delete on a broken anchor, and always announce a non-injection — silent failure is the harder bug
答题要点
- 三类各有不同性质:密钥是安全问题、临时结论是分类问题、代码里已有的是预算问题
- 密钥按形状拦不按变量名拦;拒绝话术不回显原文,还要提醒轮换;并承认渲染层可能仍在回显
- 后两类必然误杀,所以拒绝必须给出改法;「代码里已有」的长度下限与「只读被点名的文件」两条都不能省
- 三类都拦在唯一的写入函数里,理由原样回灌给模型让它自己改
- 过期靠锚点核对:通过则注入、失效则不注入但要列出来、没锚点的靠年龄提醒
- 失效不自动删(不可逆,而判据只是一次包含检查);不注入必须说出来,静默失效比失效更难查
D12 Context Compression: How to Count Tokens, What to Compress and What to Keep, and How to Verify Nothing Was Lost
Without pulling in a tokenizer, how would you estimate the token count of a context? How large is the error, and when must you not rely on an estimate?不引入分词器,你怎么估算一段上下文有多少 token?误差有多大,什么时候不能用估算?
Common in ChinaCommon overseasBasic#token-counting#calibrationHow to reason about it · think before answering
- This looks like a trick question but it really tests whether you separate three different numbers: the estimate, the reported usage, and an exact count. Answering only four characters per token will not survive the follow-ups.
- How to break it down: ask what the number is for. Decisions — should I compact before this request — can only use an estimate, because the decision happens before the call. Reconciliation uses the usage the gateway returns, which is accurate but only available afterwards. An exact count is needed only when you bill by token.
- Give the shape of the estimator: one coefficient for CJK characters plus one for everything else. The important move is not hardcoding those coefficients — the prompt_tokens in each response tells you what that text really cost, so a handful of (text, real count) samples plus one least-squares fit (two unknowns, two normal equations) recovers the pair. Collect samples along the way; never fire extra requests just to measure.
- Be honest about the error: the estimate is fine for prose and clearly optimistic for code, since indentation, brackets, snake_case names and the quotes and commas in JSON all split into more tokens — and code is most of what a coding agent carries. So use the estimate only for decisions with headroom, never to approach the limit. A trigger at seventy percent leaves the remaining thirty to absorb the estimator's own error.
- One detail that shows you actually built it: when the samples all have the same script mix (all-ASCII, say), the two coefficients are mathematically inseparable and the determinant approaches zero. You must admit it cannot be solved and fall back to a single per-character coefficient. Forcing a solution typically yields one positive and one negative coefficient — worse than no calibration, because it looks calibrated.
- Likely follow-up: why not just install a tokenizer library? Because an exact count is bound to a specific encoding table, and a gateway-agnostic program faces any model from any vendor. You would compute precisely — over someone else's tokenization. When you truly need precision, use the official tokenizer or counting endpoint of the model you are actually calling.
分析过程 · 先想清楚再作答
- 这题看着像脑筋急转弯,其实在考「你分不分得清估算、真实用量、精确计数」。只答一个「四个字符一个 token」就结束的人,接下来一定接不住追问。
- 怎么拆:先问「这个数拿来干什么」。做决策(这次要不要先压一压)只能用估算,因为决策发生在请求**之前**;对账要用网关回传的 usage,它准但**是事后的**;真要按 token 出账单才需要精确计数。三种数各有各的时机,混用就会出错。
- 估算器的形状要给出来:中日韩字符与其余字符两项系数相加。而关键的一步是**别把系数拍死**——每次请求末尾的用量回传就是「这段文本真实是多少 token」,把若干条(文本, 真实值)当样本解一次最小二乘(两个未知数、两条正规方程)就能回归出这一对系数,样本顺路从每一轮请求收,不额外发请求去测。
- 误差要老实说:估算对自然语言够用,**对代码明显偏乐观**——缩进、括号、下划线命名、JSON 里成串的引号逗号都会被切成更多 token。而代码恰恰是 Coding Agent 上下文里最多的东西。所以估算只能用于有余量的决策,绝不能拿它去逼近上限:触发线留在七成,那三成余量里就包含了估算自己的误差。
- 还有一个能显出你真写过的细节:样本的中英比例太单一时(比如全是英文),两个系数在数学上分不开,行列式接近 0。这时候必须**承认解不出来**,退回一个统一的每字符系数——硬解常常给出一正一负的荒唐系数,比不校准更糟,因为它看起来像是校准过的。
- 可预期的追问:为什么不干脆装一个分词器包?因为精确计数必须绑定具体的编码表,而一个能换网关的程序面对的是任何一家的任何一个模型,分词表根本不唯一——装了包你算得很准,但算的是别家的分词。真要精确就用你所用模型官方的分词器或计数接口。
Key points
- Three numbers, three jobs: estimate for decisions before the call, reported usage for reconciliation after, exact counts only for billing
- The estimator is two terms: one coefficient for CJK characters, one for everything else
- Fit the coefficients by least squares against the real usage, sampling along the way rather than firing probe requests
- The estimate is optimistic on code, so use it only where there is headroom and leave the trigger a thirty percent margin
- When the samples share one script mix the determinant collapses; admit it and fall back to a single coefficient instead of forcing a solve
答题要点
- 三个数三种用途:估算做决策(请求之前)、回传用量对账(请求之后)、精确计数才用来结算
- 估算式是两项相加:中日韩字符数与其余字符数各一个系数
- 系数用 usage 回传的真实值做一次最小二乘回归,样本顺路收集,不额外发请求去测
- 误差对代码偏乐观,所以估算只用于有余量的决策,触发线留三成余量吸收误差
- 样本比例单一时行列式接近 0,必须承认解不出来并退回统一系数,不能硬解
When compacting context, which kinds of messages must be kept verbatim, and which are the safest to compress away?做上下文压缩时,哪几类消息必须原样保留?压掉哪些最安全?
Common in ChinaCommon overseasIntermediate#context-compaction#keep-listHow to reason about it · think before answering
- Two things separate answers here: whether your keep list is a whitelist, and what your compression priority is ordered by. Drop the oldest is the common mistake — age has nothing to do with value.
- Keeping first: it must be a whitelist. State which classes survive verbatim, and only what is left over is compressible. Enumerating what may be compressed will eventually miss a newly introduced message type, and the consequence is silent information loss — harder to notice than a trimmed injection, because compaction prints no accounting line.
- Four classes, each with a concrete failure mode: the system prompt (drop it and you have a different program); the todo list (the only record of how far the work got, and without it the agent redoes finished steps); unfinished tool calls (fewer tool results than tool calls means the previous turn was interrupted, and a summary line saying it called a tool cannot replace the missing result); and the last few groups (the current task lives there, a summary is a conclusion while the model needs the raw text, and compressing them makes it repeat work it just did).
- Then what to compress, with one transferable criterion: order by whether it can be fetched again, not by age. Files are still on disk and commands can be rerun, so long runs of tool round trips are the best target — they are the largest block, their information was already digested by the assistant message right after them, and they can be re-read on demand. Conversely, what the user said, one-shot responses from external systems, and randomly generated ids cannot be recovered, so compress them last.
- One structural rule outranks the list: an assistant message with tool calls and all of its tool results form an indivisible group. Split them and you get a message array that requested tools without results, which makes the next request invalid outright. So the first step of compaction is grouping, not picking; then mark the keepers, and only contiguous runs of what remains are compressible, split into segments at user messages.
- Likely follow-up: how do you keep the summary itself from making things worse? Fix the headings in the prompt — confirmed facts, changes already made, unfinished work, key paths and commands — and let the model only fill them in. A free-form summary turns already edited that file into discussed how to fix it: the action becomes a topic, and the next turn the model does not know it already acted.
分析过程 · 先想清楚再作答
- 这题的区分度在两处:你的清单是不是**白名单**,以及你压缩的优先级是按什么排的。答「压最旧的」是最常见的错——年龄和价值没有关系。
- 先说保留:必须写成白名单,先说清哪几类原样留下、剩下的才是可压区。反过来列举「哪些可以压」,迟早会漏掉一类新出现的消息,而漏掉的后果是**静默丢信息**——比注入被裁剪更难发现,因为压缩不会给你一行报账。
- 清单四条,每条都有一个具体的失效现象:系统指令(压掉等于换了个程序);待办清单(那是「我做到哪一步」的唯一记录,压掉之后它会重做已经做完的事);未完成的工具调用(工具结果条数少于调用条数,说明上一轮被打断,摘要里写一句「调用了某个工具」替代不了那条缺失的结果);最近几组(模型正在做的那件事全在这里,摘要是结论而它此刻需要原文,压掉的现象是它开始重复刚做过的事)。
- 再说压什么,判据是一句可以迁移的话:**按「能不能重新取」排优先级,不是按新旧排。** 文件还在磁盘上、命令还能再跑一次,所以连续的工具往返最值得压——它占得最多、信息已经被紧随其后的助手消息消化过、而且需要时能重新读一遍。反过来,用户说过的话、外部系统的一次性响应、随机产生的 id 取不回来,最后压。
- 还有一条比清单更硬的结构规则:**一条带工具调用的助手消息与它的全部工具结果是一个不可分割的整体。** 拆开会得到「请求了工具却没有结果」的消息数组,下一轮请求直接不合法。所以压缩的第一步不是挑消息,是分组;分完组再标保留项,剩下的连续几组才是可压区,可压区再按用户消息切段。
- 可预期的追问:怎么保证摘要本身不添乱?提示里写死小标题(已确认的事实 / 已做过的改动 / 未完成的事情 / 关键路径与命令),让模型只填内容。自由发挥的摘要会把「已经改过某个文件」写成「讨论了如何修复」——**动作变成了话题**,下一轮它就不知道自己动过手了。
Key points
- The keep list must be a whitelist: define what survives verbatim, and only the remainder is compressible
- Four classes always survive: system prompt, todo list, unfinished tool calls, and the last few groups
- An assistant message with tool calls plus all of its results is indivisible, or the next request becomes invalid
- Prioritize by whether it can be fetched again, not by age: tool round trips first, user statements last
- Constrain the summary with fixed headings so actions taken do not degrade into topics discussed
答题要点
- 保留清单必须是白名单:先定原样保留的类别,剩下的才是可压区,否则会静默丢信息
- 四类必留:系统指令、待办清单、未完成的工具调用、最近几组
- 带工具调用的助手消息与它的全部工具结果不可分割,拆开会让下一轮请求不合法
- 压缩优先级按「能不能重新取」排,不按新旧排:工具往返先压,用户的话最后压
- 摘要要用固定小标题约束,防止把「做过的动作」写成「讨论过的话题」
How do you prove that a context compaction did not lose critical information?怎么证明一次上下文压缩没有丢掉关键信息?
Common in ChinaCommon overseasDeep dive#compaction-verification#probesHow to reason about it · think before answering
- This is the question with the most signal, because most candidates stop at let the model judge or eyeball the summary. The word prove is the hinge: you need a reproducible criterion, not a feeling.
- How to break it down: say why proof is required. Compaction fails silently — the model never says it lost a fact, it confidently continues with a wrong or invented one, and you infer the loss several turns later from a strange result. Unobservable failures have to be surfaced by an active check.
- The technique is probes: before compacting, record a few facts that exist only in the early context — a user saying remember: the build command is … is a natural probe — and look for them afterwards. The criterion is whether the fact is still in the context, not whether the model answers correctly. The latter is a random variable: a correct answer may be a lucky guess and a wrong one may be this turn's noise, and testing a deterministic mechanism with a random variable proves nothing.
- Go one step further: a single run proves little, so build a control. Same conversation, same keep list, but replace write a summary with keep only the last few groups and drop the rest. Both save a similar number of tokens, yet the truncating side loses the early fact. That contrast is the actual evidence that compaction differs from truncation, and the justification for the extra summarization request.
- Two engineering backstops: the summary must be checkable — every fixed heading present, and if one is missing the whole compaction is abandoned with the context untouched, since a corrupted context is far worse than an uncompacted one and the loss is irreversible once the originals leave the array. And implement it in two phases, collecting every segment summary before rebuilding the array once, so abandoning has a clean path.
- Likely follow-up: what about production? Run both layers — the deterministic check above, plus actually asking the probe question. They test different things: the first tests compaction, the second tests the model, and only the first is something you can fix. Above both sits an evaluation set: run the same task before and after compaction and compare pass rates.
分析过程 · 先想清楚再作答
- 这题是今天最有区分度的一道,因为大多数人只能答到「让模型自己判断」或者「人工看一眼摘要」。题眼在「证明」两个字:你要给出一个**可复现的判据**,而不是一种感觉。
- 怎么拆:先说清为什么必须证明。压缩的失败是**静默的**——模型不会说「我丢了一条事实」,它会很自信地拿一条错的、或者凭空补的事实继续干活,你要在几轮之后从一个奇怪的结果里倒推。不可观测的失败必须靠主动检查暴露。
- 做法是探针:压缩之前记下几条只有早期上下文里才有的事实(用户说过的「记住:构建命令是……」这类话天然就是探针),压缩之后回头查一遍。而**判据是「这条事实还在不在上下文里」,不是「模型答得对不对」**——后者是随机变量:答对可能是猜对的,答错也可能是这一轮的运气。用随机变量去验一个确定的机制,验不出任何东西。
- 更进一步:单看一次压缩说明不了什么,要有**对照**。同一段会话、同一份保留清单,把「写摘要」换成「只留最近几组直接扔」,两者省下的 token 差不多,但截断式那一边早期那条事实就没了。这个对照才是「压缩」与「截断」区别的证据,也是你多发一次摘要请求的理由。
- 工程上还要有两条兜底:一是摘要必须**可校验**——固定小标题一个都不许少,缺了就整次放弃、上下文一个字都不动(压坏的上下文比没压的糟得多,而且不可逆,原文已经不在数组里了);二是实现上先把全部段落的摘要都拿到手,再一次性重建数组,这样「放弃」才有干净的退路。
- 可预期的追问:那真实环境里怎么办?两层一起上——第一层是上面这个确定性检查,第二层是把探针问题真的问一遍模型。两层验的不是同一件事:第一层验压缩,第二层验模型,**而只有第一层是你能修的**。再往上还有一层是第二十天的基准集:把「压缩前后同一个任务的通过率」当指标跑一遍。
Key points
- Compaction fails silently, so it must be surfaced by an active check rather than by looking fine
- Use probes: record facts that exist only in the early context, then look for them after compacting
- The criterion is whether the fact is still in the context, not whether the model answers it correctly
- Include a control: the same conversation truncated instead of summarized saves similar tokens but loses the fact
- The summary must be checkable; if the format fails, abandon the whole compaction and touch nothing
答题要点
- 压缩的失败是静默的,必须靠主动检查暴露,不能靠「看起来还行」
- 探针:压缩前记下只有早期上下文才有的事实,压缩后回头查一遍
- 判据是「事实还在不在上下文里」,不是「模型答得对不对」——后者是随机变量
- 要有对照:同一段会话换成截断式压缩,省下的 token 差不多但那条事实丢了
- 摘要必须可校验,格式不合格就整次放弃、上下文一个字都不动(先全拿到再重建)
D13 Ask Before Acting: a Structured Question Tool, a Read-Only Exploration Mode, and Plan Approval
When should an agent stop and ask the user a question? What does asking too much cost, and what does never asking cost?什么时候 Agent 该停下来反问用户?问得太多和不问各有什么代价?
Common in ChinaCommon overseasBasic#clarification#human-in-the-loopHow to reason about it · think before answering
- This looks like a product question but really tests whether you have a criterion you could put in code. Answering ask when you are unsure says nothing — the model is unsure about everything.
- How to break it down: give the criterion. Ask only when two reasonable readings would lead to genuinely different edits; anything the agent can determine by reading the code is off limits. Make divide safer is the first kind — throwing versus returning null produce different code, and which one the test expects is also unsettled. Where does divide live is the second kind; a grep answers it. The value of this criterion is that it is decidable, so it can go straight into the tool description.
- Then the two costs, asymmetric but both real. Asking too much: users quickly learn to hit enter on everything, which voids every question — a question that gets rubber-stamped is worse than no question, because you believe you confirmed something. Never asking: you pay in rework, and the cost of rework is not the edit, it is re-reading what it changed and pushing it back.
- Conclusion: ask rarely, but only at real forks — and make asking cheap. Offer options, offer a default, accept a bare enter, so answering costs one keystroke. The default is the piece people forget: hitting enter is the most common thing a user does in a terminal, and what they mean is use the default you suggested.
- Likely follow-up: what about unattended runs, in CI or when another agent drives you? There is no channel to ask, and you must neither pretend to have asked nor silently pick the first option. Feed back an explicit result: this environment cannot ask the user, so proceed with the safest option and state which assumption you made on their behalf. That matches the approval gate's rule — never auto-approve when nobody is watching.
分析过程 · 先想清楚再作答
- 这题看着是产品题,其实在考「你有没有一条能写进代码的判据」。答「不确定的时候就问」等于没答——模型对什么都不确定。
- 怎么拆:先给判据。**只有当两种合理读法会导致完全不同的改动时才问**,能自己读代码查明的事不许问。「让 divide 更安全一点」是前者(抛错和返回 null 改出来的代码不一样,而且测试期望的是哪一种也不确定);「divide 在哪个文件里」是后者,grep 一下就有。这条判据的好处是它可判定,能直接写进工具的 description 里约束模型。
- 再说两端的代价,它们不对称但都真实。问太多:用户很快学会一路回车,于是那些提问全部失效——**一个被无脑通过的问题比不问更糟**,因为你以为自己确认过了。不问:你付的是返工,而返工的成本不在改代码,在于你要重新读一遍它改了什么再把它推回去。
- 结论:宁可少问,但每一问都要是真的岔路口;而且**提问必须便宜**——给选项、给默认值、支持直接回车,把回答的成本压到一次按键。默认值这一项最容易被忽略:用户在终端里最常做的动作就是直接回车,他心里想的是「按你说的默认那个来」。
- 可预期的追问:无人值守(CI、别的 Agent 调你)时怎么办?这时候没有提问渠道,**不许假装问过、也不许自动选第一个**。正确做法是回灌一条明确的结果:「当前环境无法向用户提问,请按最稳妥的一种做法继续,并在回答里说明你替用户做了哪个假设。」这条和审批门的口径一致——无人值守时不许自动同意。
Key points
- The criterion is two reasonable readings leading to different edits; never ask what code reading can settle
- Asking too much makes users rubber-stamp everything, voiding the questions — worse than not asking, because you think you confirmed
- Never asking costs rework, and rework is expensive because you must re-read the changes and push back
- Asking must be cheap: options, a default, and a bare enter, so answering is one keystroke
- Unattended, never fake an answer or auto-pick; feed back that nobody can be asked and require the assumption be stated
答题要点
- 判据是「两种合理读法会导致完全不同的改动」,能自己查明的事不许问
- 问太多的代价是用户一路回车,于是提问全部失效——比不问更糟,因为你以为确认过了
- 不问的代价是返工,而返工贵在你要重读它改了什么并把它推回去
- 提问必须便宜:给选项、给默认值、支持直接回车,把回答压到一次按键
- 无人值守时不许假装问过也不许自动选第一个,要明确回灌「这里问不了人,请说明你的假设」
How would you implement plan mode, and what is its relationship to the permission system?计划模式怎么实现?它和权限系统是什么关系?
Common in ChinaCommon overseasIntermediate#plan-mode#permissionsHow to reason about it · think before answering
- This has the most signal, because most people answer add a mode flag and check it where needed — and how many places need it is exactly what the question is probing.
- How to break it down: lead with the conclusion. Plan mode is not a new permission system; it is a preset of the existing rule table. Entering the mode does one thing: swap the table for two rules — deny every tool, plus ask for the single submit-a-plan tool. Read-only tools never reach the table at all, because the decision function's first line passes anything read-only, so reading files, searching, and asking the user all keep working with no new branch.
- Why not a mode flag checked everywhere: you end up checking it in the loop, in the approval gate, and inside every write tool — three judgments that can disagree. The day they do, the symptom is it quietly edited a file in plan mode, and that bug is nearly impossible to cover in tests because it depends on which path hits first. There must be exactly one place where permission is decided.
- A detail that shows you built it: swapping the table must replace, not append. If the matcher is most specific rule wins — as any good rule table should be — then an existing rule like allow running the test command carries a tool name and a command pattern and is far more specific than an unconditional deny. Append the deny and tests still run in plan mode, silently. Save the old table and restore it on exit.
- That leads to a stance worth stating: should plan mode carve out exceptions for harmless commands? I say no. Which command counts as read-only cannot be enumerated — does running tests write snapshots, or touch a cache? — and this mode's entire value is a boundary you can state in one sentence: read-only tools pass, everything else is blocked.
- Likely follow-up: does approval need a new protocol event, like awaiting_approval? No. Make submit-a-plan a non-read-only tool and the existing gate stops it to ask the user. Approval means the tool simply runs; rejection means feeding back a was-not-executed tool result so the model reroutes. Event types, loop and renderer stay untouched; only what is shown to the user needs a special case, and that was always the renderer's job.
分析过程 · 先想清楚再作答
- 这题区分度最高,因为多数人会答「加一个 mode 字段,然后在该判的地方判一下」——而「该判的地方」有多少处,正是这题真正在问的。
- 怎么拆:先说结论。**计划模式不是一套新的权限系统,它是既有权限规则表的一个预设。** 进入模式只做一件事:把规则表整体换成两条——所有工具一律 deny,外加「交计划」这一个工具判 ask。只读工具根本走不到规则表(判定函数第一行就是「只读直接放行」),所以读文件、搜内容、反问用户全部照常可用,一个新分支都不用写。
- 为什么不能加 mode 字段到处判:你会在循环里判一次、在审批门里判一次、在每个写工具里再判一次,得到三处可能不一致的判断。它们不一致的那天,表现是「计划模式下它偷偷改了文件」——这种 bug 极难在测试里覆盖,因为它取决于哪条路径先命中。**权限的判定入口只能有一个**,这是比任何功能都硬的一条。
- 一个能显出你真写过的细节:**换规则表要整体替换,不能追加。** 如果匹配规则是「更具体的规则赢」(一个好的规则表都该这样),那么原表里那条「跑测试允许」带工具名带命令模式,比无条件的 deny 具体得多;把 deny 追加上去,计划模式下测试照跑,而且没有任何提示。原表存起来、退出时放回去即可。
- 顺着这条还有一个口径要表态:**计划模式该不该给「无害的命令」开口子。** 我的答案是不开——「哪条命令算只读」无法穷举(跑测试会不会写快照?会不会碰缓存?),而这个模式的全部价值就在于边界一句话说得清:只读工具全过,其余全挡。
- 可预期的追问:审批那一步要不要给协议加事件类型(比如一个 awaiting_approval 事件)?不要。把「交计划」做成一个**非只读工具**,既有的审批门就会自动拦住它去问用户;批准就是工具照常执行,拒绝就是回灌一条「没有被执行」的工具结果让模型改道。事件类型、循环、渲染层一个字都不用动,只有「摆给用户看的内容」要特判——而那本来就是渲染层的职责。
Key points
- Plan mode is a preset of the existing permission rule table, not a separate system
- Entering it does one thing: swap the table for deny-everything plus ask on the submit-plan tool
- Read-only tools never reach the table, so exploration comes for free
- Replace the table rather than appending, or a more specific existing rule beats the unconditional deny
- There must be one permission decision point; approval reuses the existing gate with no new event types
答题要点
- 计划模式是既有权限规则表的一个预设,不是新的权限系统
- 进入模式只做一件事:规则表整体换成「全部 deny + 交计划判 ask」两条
- 只读工具走不到规则表(判定第一行就放行),所以探索能力是白拿的
- 换表要整体替换不能追加,否则原表里更具体的规则会打败无条件的 deny
- 权限判定入口只能有一处;审批走既有的门,不给事件协议加类型
After a plan is approved, how do you ensure the execution did not drift from it?批准了一份计划之后,怎么保证执行没有偏离?
Common in ChinaCommon overseasDeep dive#plan-verification#drift-detectionHow to reason about it · think before answering
- The hinge is ensure. Most answers stop at have the model follow the plan and report back, which is asking the inspected party to write the inspection report. This question wants a mechanism that does not rely on the model's good faith.
- How to break it down: describe what drift looks like and why it hides. The two typical forms are edited files outside the plan and declared success without running the verification, and what they share is that neither is an error: every tool succeeded, the loop ended normally, the terminal is full of checkmarks. Silent failures only surface through an active check.
- So step one is the plan format: a plan must be checkable or there is nothing to check against. Four fields — goal, which files each step touches, how to verify, what the risks are — where the file list is the handle for reconciliation and the verification is the hard criterion for done. A prose plan like improve the boundary handling is satisfied by editing one file or five, which is the same as having no plan. And an unqualified plan must be returned whole rather than patched up: a verification step we invented is ours, and after approval nobody owns it.
- Step two is the authorization scope: on approval, grant exactly the files the plan lists, no more. Granting everything means approving arbitrary edits; granting nothing means every file is asked about again during execution, and the user learns to approve reflexively. What was approved is this plan, not unlimited authority. Writes outside the plan then hit the approval gate naturally — a first line of defense that blocks rather than merely reports.
- One easily missed red line: a plan-based grant must not override a deny. If the table has a hard rule — say, never edit the test directory, because you cannot make tests pass by editing tests — then blanket-granting every planned file lets the model smuggle a test file into the plan and launder it through one approval. Re-evaluate each file against the original table first, keep the denials, and tell the model explicitly.
- Step three is reconciliation after the fact: a transparent wrapper that passes events through while recording which files were actually written and which commands actually ran, compared against the plan on three axes — planned and touched, touched but unplanned, planned but untouched — plus whether the verification ran. Only successful writes count; gate-blocked and failed calls are not real changes.
- Likely follow-up: how do you prove the reconciliation is not decorative? Build a control. Run the same plan down two branches, one following it and one deliberately touching an extra file and skipping verification, and see whether the table catches both. A reconciliation that catches nothing is an ornament. Also draw the boundary: reconciliation answers what this execution did, not whether the change is correct — the latter is the verification step's job.
分析过程 · 先想清楚再作答
- 题眼在「保证」。多数人答到「让模型按计划执行、最后让它自己汇报」就停了,而那是让被检查的人写检查报告。这题要的是一个不依赖模型自觉的机制。
- 怎么拆:先说清偏离长什么样,以及为什么它抓不住。两种典型偏离是「多改了计划外的文件」和「没跑验证就宣布成功」,而它们的共同点是**都不是错误**:工具全部成功、循环正常结束、终端上一片对勾。静默的失败只能靠主动检查暴露。
- 所以第一步在**计划的格式**上:计划必须可核对,否则后面无从对起。四个字段——目标、每步改哪几个文件、怎么验证、有什么风险——其中「改哪几个文件」是核对的抓手,「怎么验证」是判断「做成了」的硬判据。自然语言计划(「优化一下边界处理」)改一个文件和改五个文件都算符合,等于没有计划。而且不合格的计划要**整份退回、不要就地补全**:我们替它补的验证方式是我们编的,用户批准之后没人对它负责。
- 第二步是**授权范围**:批准之后按计划里那几个文件逐个放行,一个不多。全开等于批准「随便改」;什么都不放行,则每个文件执行时还要再问一遍,用户很快学会一路按同意。**批准的是这份计划,不是无限授权。** 于是计划外的写入天然会撞回审批门——这是第一层防线,而且它是拦住的,不是事后发现的。
- 还有一条容易被忽略的红线:**按计划放行不能越过 deny。** 规则表里若有硬规则(例如不许改测试目录,因为不能靠改测试让它变绿),批准时无脑给计划里每个文件加放行,模型只要把测试文件写进计划就能借一次审批绕开硬规则。所以放行前要用原规则表判一次,本来 deny 的保持 deny 并明确告知模型。
- 第三步是**事后核对**:用一层透明包装(把事件原样传下去、路过时记一笔)采集这一轮真的成功写过哪些文件、真的跑过哪些命令,然后与计划对三列——计划内改了哪些、有哪些计划外的、有哪些计划里写了却没动的,再加一条「验证方式跑过没有」。只记成功的写入:被门挡下的、执行失败的不算实际改动。
- 可预期的追问:怎么证明这套核对不是摆设?做对照。同一份计划走两条支线,一条按计划执行,另一条故意多改一个文件且不跑验证,看核对表能不能抓到那两处。抓不到的核对表就是个装饰。另外要划清边界:核对只回答「这一次执行做了什么」,不回答「改动对不对」——后者是验证方式(跑测试)的职责。
Key points
- Drift is not an error — tools succeed and the terminal looks clean — so it only surfaces via an active check
- The plan must be checkable: the file list is the handle, the verification is the hard criterion; return unqualified plans whole instead of patching them
- On approval grant exactly the planned files — what was approved is this plan, not unlimited authority
- A plan-based grant must never override a deny, or hard rules can be laundered through one approval
- Afterwards, collect actual writes and commands with a transparent wrapper and reconcile planned, unplanned and untouched plus whether verification ran, proving it with a control branch
答题要点
- 偏离不是错误(工具全成功、终端一片对勾),静默失败只能靠主动检查暴露
- 计划必须可核对:四个字段里「改哪些文件」是抓手、「怎么验证」是硬判据;不合格整份退回不要补全
- 批准之后按计划里的文件逐个放行,一个不多——批准的是这份计划,不是无限授权
- 按计划放行不能越过 deny,否则模型能把硬规则里的文件写进计划来洗白
- 事后用透明包装采集实际写入与实际跑过的命令,核对计划内 / 计划外 / 漏做三列加一条验证跑没跑,并用对照支线证明它真能抓到偏离
D14 Checkpoints and Rewind: File Snapshots, Conversation Rollback, and Why the Two Must Stay Independent
How would you design an agent's file snapshots, and why not just make git commits?Agent 的文件快照怎么设计?为什么不直接用 git 提交?
Common in ChinaCommon overseasBasic#snapshots#content-addressingHow to reason about it · think before answering
- This tests whether you separate two different ledgers. Anyone answering just use git has not considered that the user's commit history is theirs, and inserting commits they did not write is work they must rebase away.
- Start with the division of labor. A git commit is the user's ledger, recording this is a version I endorse, decided by the user. A snapshot is the agent's own ledger, recording what things looked like before and after I acted, decided automatically by the program. Timing, granularity and lifetime all differ: one task may write files ten times, producing a dozen snapshots, while the user wants a single commit. Mixing them pollutes history, and it also makes snapshots hostage to git state — a populated index, a rebase in progress, or a directory that is not a repo at all would each break snapshotting.
- Then what to store: only files a tool actually modified, and one snapshot before and one after each write. Miss either and a state is unreachable — with only after you cannot get back to before the first edit, which is exactly what undo this task needs; with only before you lose the most recent change. You also want a baseline taken when the session opens, or the earliest reachable state is just before the first write to one file, while a task usually touched several.
- How to avoid storing duplicates: content addressing — where a blob lives is determined by its own hash. Deduplication then comes for free with no table of what equals what, because the hash is that table. The saving is real: the after of one edit is the before of the next, so four records often map to three distinct contents.
- Two details that show you built it. First, store whole files, not diffs. Increments save space but a diff needs a base, bases form a chain, and a broken link ruins the rest — precisely wrong for something whose job is recovery after things break; source files are kilobytes, so trading disk for an entire failure class is worth it. Second, write a temp file and rename atomically: after a crash mid-write the store must never hold a file whose content does not match its hash, which is worse than nothing because a rewind would copy it over your work.
- Likely follow-up: how is the index kept? A hash identifies content, not a name, so the object store cannot know which hash belongs to which file; that lives in an append-only index recording file, time, hash, size and mtime. Storage deduplicates, the index carries meaning. The index must also allow a null hash, meaning the file did not exist at that moment — so rewinding there means deleting it.
分析过程 · 先想清楚再作答
- 这题在考「你能不能分清两种账本」。答「用 git 最省事」的人没想过一件事:用户的提交历史是他的东西,往里塞不是他写的提交,是要他花时间 rebase 掉的。
- 先说分工。**git 提交是用户的账本,记的是「这是我认可的一个版本」,由用户决定;快照是 Agent 自己的账本,记的是「我动手前后的样子」,由程序自动决定。** 两者的时机、粒度、生命周期都不一样:Agent 一次任务可能写十次文件,对应十几张快照,而用户可能只想提交一次。混在一起的直接后果是历史被污染,间接后果是快照受 git 状态摆布(暂存区里有东西、处于 rebase 中间态、仓库根本没初始化 git,快照就都拍不了了)。
- 再说存什么。**只存被工具改过的文件**,而且**写工具执行前后各拍一次**。少任何一张都会缺一个状态:只拍「改之后」就回不到第一次改之前(而那恰恰是「撤销这次任务」最常要的那一个);只拍「改之前」就丢掉最新一次改动。另外要有一张**基线**(会话开始时把仓库拍一遍),否则最早能回到的只是「第一次写入之前的那一个文件」,而任务往往动了好几个。
- 怎么存不重复:**内容寻址**——一份内容存在哪儿由它自己的哈希决定。去重于是是白拿的,不需要任何一张「谁和谁一样」的表,因为哈希本身就是那张表。而它省的量很实在:一次 edit 的「改之后」就是下一次的「改之前」,四条记录常常只对应三份内容。
- 两个实现细节能显出你写过:一、**存全文不存 diff**。增量最省空间,但 diff 要有基准、基准要有链,链断了整串都恢复不了——而快照的用途恰恰是「出事要能恢复」,那是最不该有链式依赖的时候;源码文件只有几 KB,磁盘换掉一整类失败模式很值。二、**先写临时文件再原子改名**:崩在半路时,对象库里绝不能留下一个哈希对不上内容的文件,那种文件比没有更糟,因为回滚会拿它去覆盖。
- 可预期的追问:索引怎么记?哈希只认内容不认文件名,所以对象库自己不知道哪个哈希属于哪个文件——那件事记在一份只追加的索引里(哪个文件、什么时候、哪个哈希、多大、mtime 多少)。**存储层负责去重,索引层负责语义。** 索引里还要允许哈希为空,它表示「那一刻这个文件不存在」,回到那一刻就意味着把文件删掉。
Key points
- A git commit is the user's ledger decided by the user; a snapshot is the agent's own, produced automatically — mixing them pollutes history
- Store only files a tool modified, one snapshot before and one after each write, plus a baseline at session start
- Content addressing makes deduplication free: the hash is the table of what equals what
- Store whole files, not diffs — recovery is the worst place for chained dependencies, and source files cost almost nothing
- Write to a temp file and rename atomically; hashes identify content, so the file-to-hash mapping lives in an append-only index
答题要点
- git 提交是用户的账本、由用户决定;快照是 Agent 自己的账本、自动产生,混在一起会污染历史
- 只存被工具改过的文件,写工具执行前后各拍一次,再加一张会话开始时的基线
- 内容寻址让去重白拿:哈希本身就是「谁和谁一样」那张表
- 存全文不存 diff:恢复场景最不该有链式依赖,源码文件的磁盘代价不值一提
- 先写临时文件再原子改名;哈希只认内容,文件与哈希的对应记在只追加的索引里
Why must file rollback and conversation rewind be separate? What happens when you rewind only one of them?文件回滚和对话回退为什么要分开?只回一边会发生什么?
Common in ChinaCommon overseasIntermediate#rewind#two-timelinesHow to reason about it · think before answering
- This carries the most signal, because most people answer rewinding both together is simplest and safest. But together is not a default — it deletes two of the four use cases.
- How to break it down: lay out the four combinations and what each is for. Files only — it edited wrongly but the investigation was useful, so keep the conversation and let it retry with that context. Conversation only — the edits are right but the dialogue has drifted through ten wasted turns, so keep the result and pull the thread back. Both — this whole path was wrong, restart from the fork. Neither — look first at what a rewind would do, which is a dry run rather than a no-op.
- The second is the most common and most overlooked case in long tasks. Edits are often correct within the first few turns, while the following ten are the model second-guessing itself, re-reading the same file and bloating the context. What you want is to truncate the conversation while the change on disk stays byte-identical. An implementation that only rewinds both forces you to throw away work that was already correct.
- Then the implementation requirement: the two timelines must not reference each other. The session log records what was said, the snapshot log records what the files are, each with its own sequence. Put a session sequence into snapshot records and rewinding one side has no representation left in the data model. Guessing by timestamp — the snapshot nearest this message — is worse: rewinding becomes nondeterministic, and rewinding is where determinism matters most.
- Acknowledge the cost: decoupled, rewinding both takes two arguments, a checkpoint id and a message sequence, which reads worse than one. But a turn may have touched five files or none, so the lines were never one-to-one. Awkward beats uncertain.
- Worth a sentence on each side's mechanics: for files, take the last record at or before the target for each path, then restore what differs and delete what did not exist at that moment — deletion is the commonly missed third case, since files created during the task must disappear or the leftovers look like something you made. For the conversation it is exactly the event log's fork: copy to a new file, truncate at a sequence, record a lineage event, leave the parent untouched, and reuse the existing rule that only clean boundaries are forkable.
- Likely follow-up: what if the compute-state-at-a-moment function is wrong? Forget the sequence comparison and rewinding to record N always equals keep everything — no error, no exception, nothing happens, while the user believes they rewound. That is the nastiest failure mode here, which is why that function should be pure and pinned by tests.
分析过程 · 先想清楚再作答
- 这题最有区分度,因为多数人会答「一起回最省事、也最不容易乱」。而「一起回」不是一个默认值,它是**把四种用法砍掉两种**。
- 怎么拆:先把四种组合和各自的用途摆出来。只回文件——它改错了,但那段排查过程有用,留着对话让它接着上文重试;只回对话——文件改对了,但对话已经跑偏(绕了十轮弯路),留着成果把话头拉回去;都回——这条路整个走错了,从岔路口重新开始;都不回——先看一眼「如果回滚会发生什么」再决定(这一支是**预演**,不是空操作)。
- **第二种是长任务里最常用也最容易被忽略的那一种。** 一次长任务里改动往往在前几轮就对了,后面十轮全是模型在自我怀疑、反复读同一个文件、把上下文撑大。这时候你要的是把对话截回去,而磁盘上那份改动一个字节都别动。只支持「一起回」的实现,在这个最常见的场景里只能让你把已经改对的东西也一起丢掉。
- 再说实现上的要求:**两条时间线不能互相引用。** 会话日志记「说过什么」,快照日志记「文件是什么」,各有自己的 seq。只要在快照记录里塞一个 sessionSeq 把两者绑起来,「只回一边」在数据结构层面就没有表达方式了。替代方案是按时间戳猜「离这条消息最近的那张快照」——那更糟,回滚从此是一件不确定的事,而回滚恰恰最需要确定。
- 代价要承认:解绑之后「都回」需要两个参数(一个快照编号、一个消息 seq),界面上不如一个参数漂亮。但一轮对话里可能改了五个文件也可能一个都没改,两条线本来就不一一对应——**不好用胜过不确定。**
- 两边各自怎么实现也值得说一句:文件那边是「对每个文件取 seq 小于等于目标的最后一条记录」,然后内容不同的还原、那一刻不存在的**删掉**(第三种最容易漏,任务里新建的文件必须消失,否则残留看起来像是你自己建的);对话那边直接就是事件日志的分叉——复制到新文件、截断到某个 seq、记一条血缘事件,母会话一个字节不动,连「只有干净的边界才能分」那条规则都是现成的。
- 可预期的追问:回到某一刻这个计算错了会怎样?如果忘了比较 seq,「回到第 N 条」就永远等于「保持现状」——**不报错、不抛异常,什么都没发生,而用户以为自己已经回退了。** 这是这一类功能最阴的失效方式,所以那个函数应该是纯函数并被测试钉死。
Key points
- Each of the four combinations has a real use, so rewinding both together removes two of them
- Conversation-only is the most common case in long tasks: the edits were right early, the last ten turns were detours
- The two timelines must not reference each other; binding them with a session sequence makes one-sided rewind inexpressible
- Matching them by timestamp is worse — it makes rewinding nondeterministic, exactly where determinism matters
- File rollback must delete files that did not exist at that moment; conversation rewind is just the event log's fork
答题要点
- 四种组合各有真实用途,「一起回」等于砍掉其中两种
- 只回对话是长任务里最常用的一种:改动早就对了,后面十轮全是绕弯路
- 两条时间线不能互相引用;塞一个 sessionSeq 绑死之后「只回一边」就没法表达了
- 按时间戳猜对应关系更糟:回滚从此不确定,而回滚最需要确定
- 回滚文件要包含「删掉那一刻不存在的文件」;回退对话直接复用事件日志的分叉
Before a rewind you find uncommitted changes in the workspace, or a file that was modified externally. How do you handle it?回滚前发现工作区有未提交改动,或者文件被外部改过,你怎么处理?
Common in ChinaCommon overseasDeep dive#workspace-safety#dirty-stateHow to reason about it · think before answering
- This tests whether you have thought about what makes rewinding special. Answering warn and continue misses the key point: a rewind is the only action in the whole program that actively overwrites the user's files.
- State that specialness first, because every later conclusion rests on it. String-replacement write tools have a natural guardrail — the old content must match verbatim or the call fails. A rewind is a whole-file overwrite: it does not fail, it succeeds at erasing the paragraph the user typed, silently. So a rewind needs a check in front of it, answering one question: are the files on disk still the way I recorded them?
- The check has two levels, separated by whether a rewind would cause unrecoverable loss. Content differs from the last snapshot: refuse the whole rewind. Someone edited it outside the agent, that content was never in any snapshot, and after overwriting nobody can recover it. So refuse first, list the files, then give the next step — commit or save, or explicitly pass a force flag after accepting the loss. Content matches but mtime is newer: warn only. The file was touched but matches the record, most often edited and edited back, so the rewind is harmless and blocking the user is unjustified.
- Those two levels also explain why a snapshot record needs both hash and mtime: the hash answers what the content is, the mtime answers who last touched it. With only hashes the second check is invisible.
- Scope it too: check only recorded files. Files the agent never touched are out of scope because the rewind will not touch them either. Include the whole repository and the first run drowns the user in irrelevant warnings, after which they learn to ignore warnings — exactly the outcome to avoid.
- On git: in a real repository uncommitted changes are usually detected with git status, but that is one special case of the broader criterion, which is the workspace contains changes the agent never recorded. A robust implementation checks both: git status catches editor changes, snapshot hashes catch what git does not know about, such as untracked or ignored files. Either way the handling is the same — refuse first, never overwrite quietly.
- Likely follow-up: does the user get stuck? No. Offer three ways out, all named in the refusal message: save or commit the work; pass an explicit force flag meaning I accept the loss; or run a dry run first that prints exactly what a rewind would do while touching nothing. The third is the one that should always exist: since a rewind is the only action that overwrites the user's files, it deserves a look-before-you-leap entry point.
分析过程 · 先想清楚再作答
- 这题在考「你有没有想过回滚这个动作的特殊性」。答「提示一下然后继续」的人漏了最关键的一点:**回滚是整个程序里唯一一个会主动覆盖用户文件的动作。**
- 先把这个特殊性说清楚,它是后面所有结论的前提。精确替换那类写工具有一条天然护栏——旧内容必须逐字匹配,匹配不上就失败;而回滚是整文件覆写,它**不会失败**,它会成功地把用户手写的那一段冲掉,而且没有任何提示。所以回滚前面必须有一道检查,而它要回答的问题是:**磁盘上现在这些文件,是不是我记录过的样子?**
- 检查分两级,判据是「回滚会不会造成不可恢复的损失」。**内容与最后一次快照不一致 → 拒绝整次回滚**:有人在 Agent 之外改过它,而那份内容从没进过任何快照,覆盖之后谁都恢复不了。所以先拒绝、再列出是哪几个文件、再给出下一步(先提交或保存,或者确认可以丢之后显式加 force)。**内容一致但修改时间更新 → 只警告**:文件被人碰过,但内容和记录一样(最常见的是改了又改回来),回滚是无害的,拦住用户没道理。
- 这两级也解释了为什么快照记录里**哈希与 mtime 两个都要**:哈希回答「内容是什么」,mtime 回答「谁最后碰过它」。只比哈希,第二级检查是隐形的。
- 范围也要划:**只检查记录过的文件。** Agent 从没碰过的文件不在检查范围里,因为回滚也不会去动它们。把整个仓库都纳进来,第一次跑就会被一堆无关警告淹没,然后人学会忽略警告——而那正是要避免的结果。
- 关于 git:真实仓库里「未提交改动」通常用 git status 判定,但那只是这条判据的一个特例——更普适的说法是「工作区里存在 Agent 没有记录过的改动」。所以健壮的实现**两条都查**:git status 抓编辑器里的改动,快照哈希抓 git 也不知道的那些(未跟踪文件、被 ignore 的文件)。而且不管哪一条命中,处理方式都一样:先拒绝,别悄悄覆盖。
- 可预期的追问:那用户就被卡住了?不。给三条出路,而且都要写在拒绝的那句话里:让他自己保存或提交;显式加一个 force 表示「我知道会丢」;或者先跑一次**预演**——把「如果回滚会发生什么」全打出来而一个字节都不动。第三条是最该有的那一条:回滚既然是唯一会主动覆盖用户文件的动作,就值得给它一个「先看看再决定」的入口。
Key points
- A rewind is the only action that actively overwrites user files, and it cannot fail, so it needs a pre-check
- Refuse the whole rewind when content differs from the snapshot — that content is in no snapshot and cannot be recovered; refuse first, then explain
- Warn only when content matches but mtime is newer, since the rewind is harmless and blocking is unjustified
- Hence record both hash and mtime; check only recorded files so users do not learn to ignore warnings
- git status is one special case of unrecorded changes — check both; and offer save, force, or dry-run as ways forward
答题要点
- 回滚是唯一会主动覆盖用户文件的动作,而且它不会失败,所以前面必须有检查
- 内容与快照不一致就拒绝整次回滚(那份内容不在任何快照里,冲掉无法恢复),先拒绝再提示
- 内容一致但修改时间更新只警告——回滚无害,没理由拦住用户
- 所以快照要同时记哈希与 mtime;只检查记录过的文件,避免用户学会忽略警告
- git status 只是「未记录的改动」的一个特例,健壮实现两条都查;拒绝时要给出保存 / force / 预演三条出路
D15 Wiring Up MCP: a Hand-Written JSON-RPC Client, Two Transports, and a Tool Namespace
If you hand-write an MCP client without the official SDK, which protocol details do you have to handle yourself?不用官方 SDK 手写一个 MCP 客户端,你必须自己处理哪些协议细节?
Common in ChinaCommon overseasBasic#json-rpc#mcp-clientHow to reason about it · think before answering
- What is tested is whether you have actually written a protocol client. People who only used an SDK answer "connect, list tools, call tools"; people who wrote one start from the edge cases.
- How to break it down: protocol shape, request/response correlation, pagination, and error classification — each has a decision you must make yourself.
- Protocol shape - the 2026-07-28 revision is stateless. There is no handshake; version, identity and capabilities travel in the _meta of every single request, and _meta belongs on params, not at the top level of the envelope.
- Correlation needs four things - allocate ids, match responses by id, enforce a timeout, and fail every in-flight request at once when the transport dies. The key judgment is that a message without an id is a notification, not a response; matching by arrival order will drift.
- Pagination has exactly one criterion - you are done when there is no next cursor. Do not guess from a short page, and never parse the cursor; it is opaque to clients.
- Error classification splits in two - an error field in the response is a protocol error the model cannot fix, while a result flagged as a failed execution is meant for the model and must be fed back verbatim. Throwing on the latter turns a self-healing call into a hard failure.
- Likely follow-ups - why a late response is dropped instead of raised; how you pick a timeout; what happens if you declare an empty client capability set.
分析过程 · 先想清楚再作答
- 这题在考「你是不是真写过一个协议客户端」。只用过 SDK 的人会答「连上、列工具、调工具」三步,写过的人会先说边界。
- 怎么拆:把它拆成协议形状、请求应答、分页、错误分类四块,每块各有一个必须自己做的决定。
- 协议形状这一块,2026-07-28 版是无状态的:没有握手,版本、身份、能力写在每一条请求的 _meta 里,而且是加在 params 上不是加在报文顶层。
- 请求应答这一块有四件事:发号、按 id 配对、超时、传输死掉时把所有在等的请求一次性失败掉。关键判断是「没有 id 的是通知不是响应」,按到达顺序配对一定会错位。
- 分页这一块判据唯一:没有下一页游标才是结束。不能用「这一页比上一页少」去猜,也不能解析游标——它对客户端不透明。
- 错误分类这一块要分两类:响应里带 error 的是协议错误,模型改不了;结果里标了执行失败的是给模型看的,要原样回灌让它换个参数。把后者也抛成异常,一次本来能自愈的调用就变成一次失败。
- 可预期的追问:为什么迟到的响应要丢掉而不是报错;超时值怎么定;客户端能力声明成空对象会有什么后果。
Key points
- Stateless protocol - no handshake; version and capabilities ride in the _meta of every request, attached to params
- Four correlation duties - allocate ids, match by id, time out, fail all pending requests when the transport dies; a message without an id is a notification
- Pagination ends only when there is no next cursor; the cursor is opaque and must be echoed back unchanged
- Raise protocol errors; feed tool execution errors back to the model verbatim
- Drop late responses silently; having no timeout at all is always wrong
答题要点
- 无状态协议:没有握手,版本与能力写在每条请求的 _meta 里,且挂在 params 上
- 请求应答四件事:发号、按 id 配对、超时、传输死掉时一次性收摊;没有 id 的是通知
- 分页只认「没有下一页游标」,游标不透明、原样带回
- 协议错误抛出去,工具执行错误原样回灌给模型
- 迟到的响应丢掉但不报错;不设超时一定是错的
When would you use the stdio transport versus Streamable HTTP, and how do their failure modes differ?stdio 与 Streamable HTTP 两种传输分别适合什么场景?它们的失败方式有什么不同?
Common in ChinaCommon overseasIntermediate#transports#stdio-vs-httpHow to reason about it · think before answering
- This tests whether you have actually wired up both. People who used only one start from performance; people who used both start from who draws the message boundary.
- How to break it down - fit first, then message framing, then failure modes side by side. The third part is where candidates separate.
- Fit - stdio for local servers that travel with the user's machine and need no auth; HTTP for cross-machine, multi-user servers that need auth and rate limiting.
- Framing - on stdio the bytes are one endless stream and you must split on newlines yourself, which means buffering partial lines. That bug only shows up once a result is large enough to be chopped, so small payloads never reveal it. Over HTTP the boundary comes from HTTP, but a response has two shapes, and the event-stream shape interleaves notifications, so the rule is that only a message carrying a matching id is the response.
- Failure modes - stdio fails by a process that will not start, a server writing logs to stdout and corrupting the frames, and orphaned children after the parent exits. HTTP fails by refused connections, streams cut mid-flight, and middleboxes rewriting or buffering.
- Easy to miss - the 2026-07-28 revision removed stream resumption, so a broken stream means that request is lost and must be re-sent under a brand new id. The protocol layer does no compensating delivery.
- Likely follow-ups - why server logs must go to stderr; why the end of a stream does not imply success; whether one client implementation can serve both transports (yes, by abstracting the transport down to moving bytes).
分析过程 · 先想清楚再作答
- 这题在考「你有没有两种都真的接过」。只接过一种的人会从性能答起,两种都接过的人会先说边界由谁划。
- 怎么拆:先说适用场景,再说消息边界,最后把失败方式对照着列——第三块才是区分度所在。
- 适用场景:本地的、跟着用户机器走的、不需要鉴权的用 stdio;跨机器的、多人共用的、要鉴权与限流的用 HTTP。
- 消息边界:stdio 上字节是一条永不结束的流,边界要自己按换行切,所以必须留缓冲区处理半行报文——这个 bug 只在结果大到被切开时才暴露,小报文测不出来。HTTP 上边界由 HTTP 划,但响应有两种形状,事件流那一支里混着通知,判据是「带 id 且 id 对得上的那条才是响应」。
- 失败方式:stdio 是进程起不来、服务端把日志打进标准输出污染报文、父进程退出留下孤儿进程;HTTP 是连不上、流中途断开、被中间层改写或缓冲。
- 一条容易漏的:2026-07-28 删掉了断流续传,流断了这次请求就是丢了,必须换一个新 id 重发,协议这一层不做补偿投递。
- 可预期的追问:为什么服务端的日志只能走 stderr;流结束为什么不等于请求成功;两种传输能不能共用同一个客户端实现(能,把传输抽成只管收发字节的接口)。
Key points
- stdio fits local, single-user, no-auth servers; HTTP fits cross-machine, multi-user servers needing auth and rate limits
- On stdio you draw the boundaries yourself and must buffer partial lines; the bug only surfaces on large results
- HTTP responses come in two shapes; the event stream interleaves notifications, and only a matching id marks the response
- Typical stdio failures - process will not start, logs poison stdout, orphaned children
- Typical HTTP failures - refused connection, stream cut mid-flight, middlebox rewriting; a broken stream must be re-sent under a new id
答题要点
- stdio 适合本地、单用户、无鉴权;HTTP 适合跨机器、多用户、要鉴权与限流
- stdio 的边界要自己划,必须处理半行报文;这个 bug 只在大结果上暴露
- HTTP 的响应有两种形状,事件流里混着通知,判据是 id 对得上
- stdio 的典型失败:起不来、日志污染标准输出、孤儿进程
- HTTP 的典型失败:连不上、流中断、被中间层改写;流断了必须换新 id 重发
What governance would you put in place before aggregating third-party MCP server tools into your own agent?把第三方 MCP server 的工具聚合进自己的 Agent,你会加哪些治理措施?
Common in ChinaCommon overseasDeep dive#trust-boundary#tool-governanceHow to reason about it · think before answering
- This tests trust boundaries, not features. Most people only say "prefix the names to avoid collisions", which is the shallowest layer of governance.
- How to break it down - naming, trust, failure, budget. Give one concrete measure and one counter-example per layer.
- Naming - the prefix must come from your own configured alias, because the spec only guarantees tool-name uniqueness within a single server and explicitly says a server's self-reported name is not unique across servers and must not be used to disambiguate. Duplicate aliases should fail at startup. Reverse lookup goes through a table, never string splitting, since original names may contain underscores and truncated-plus-hashed names cannot be split back. The name sent back to the server must be the original one.
- Trust is the crux - a readOnly hint in the tool annotations cannot be used as a permission decision. It is a pass the callee issued to itself, and the spec requires clients to treat tool annotations as untrusted input. The safe stance is to treat every remote tool as state-changing and route all of them through the approval gate. By the same logic, tool descriptions are text written by someone else that lands in the model's context, so they are prompt-injection surface.
- Failure - wrap discovery per server in try/catch and record the reason; never throw during a call, translate every failure into a failed tool result. Make outages visible, because silent degradation makes the model say "I could not find anything" instead of "that system is unreachable".
- Budget - remote results share the same truncation budget as local ones, and when there are too many tools, mount on demand while knowing that adding or removing tools mid-session invalidates the prompt cache.
- Likely follow-ups - how to stop one server from blowing up the whole tool table; whether tool descriptions count as untrusted input; whether remote calls deserve their own audit log.
分析过程 · 先想清楚再作答
- 这题在考信任边界,而不是功能。多数人只答「加前缀防重名」,那只是治理里最浅的一层。
- 怎么拆:分成命名、信任、失败、预算四层,每层给一条具体措施和一条反例。
- 命名层:前缀必须来自客户端自己的配置别名,因为规范只保证工具名在单个 server 内唯一,而且明说服务端自报的名字不保证跨 server 唯一、不该拿它消歧。别名撞了要在启动时报错。反查只能走一张表,不能切字符串——原名里本来就允许有下划线,截断加过哈希的更是拆不回来。调用时发回 server 的必须是原名。
- 信任层是这题的题眼:服务端在工具注解里声明的只读提示不能当权限用,那是被调用方给自己发的通行证,规范要求客户端把工具注解当成不可信输入。稳妥口径是远端工具一律按「会改东西」对待,全部过审批门。同理,工具描述是别人写的文本,会进模型上下文,属于提示注入面。
- 失败层:发现阶段逐个 try/catch 并记下原因,调用阶段一律不抛、全部翻译成一条失败的工具结果;掉线要让用户看得见,静默降级会让模型说「我查不到」而不是「那个系统连不上」。
- 预算层:远端结果和本地结果共用同一份截断预算;工具太多时按需挂载,但要知道动态增删工具会打掉提示缓存。
- 可预期的追问:怎么防止一个 server 把整个工具表撑爆;工具描述算不算不可信输入;要不要给远端调用单独记审计日志。
Key points
- Namespace prefixes come from client-side aliases; duplicate aliases fail at startup; reverse lookup uses a table, and the original name is what goes back to the server
- A server's self-declared read-only annotation is untrusted; treat every remote tool as state-changing and route it through the approval gate
- Tool descriptions are third-party text that enters the model's context, so they are prompt-injection surface and must be treated as untrusted input
- Wrap discovery per server and record reasons; never throw during a call; make outages visible instead of degrading silently
- Remote results share the local truncation budget; on-demand mounting saves context but invalidates the prompt cache
答题要点
- 命名空间前缀来自客户端配置的别名,别名冲突在启动时报错;反查走表不切字符串;发回 server 的是原名
- 服务端自报的只读注解不可信,远端工具一律按会改东西对待、全部过审批门
- 工具描述是别人写的文本且会进上下文,属于提示注入面,要当成不可信输入
- 发现阶段逐个 try/catch 记原因,调用阶段一律不抛;掉线要让用户看得见,不做静默降级
- 远端结果共用本地那份截断预算;按需挂载可以省上下文,但会打掉提示缓存
D16 Loading Skills: Scanning, Progressive Disclosure, and Trigger Judgment — Bringing Experience to the Table on Demand
How many stages does progressive disclosure for skills have, and what triggers each one?技能的渐进披露具体分几个阶段?每个阶段的判据是什么?
Common in ChinaCommon overseasBasic#progressive-disclosure#skillsHow to reason about it · think before answering
- This tests whether you have implemented a loader yourself. People who only used one answer "summary first, then full text"; people who built one start from the fact that each stage has a different trigger, because the trigger determines the shape of the cost.
- How to break it down - describe each stage as when it happens, what it injects, and how it is billed, then point out that the three triggers are unrelated to each other.
- Stage one is the resident summary. Its trigger is what is installed on this machine, independent of what the user says, so it lives in the system prompt and is paid for on every request. It carries only the name and a one-line description, and that line is the model's entire basis for deciding whether the skill is relevant.
- Stage two is the body on demand. Its trigger is what this particular sentence matched, so it is billed per turn and costs nothing when nothing matches. The injected message should have the user role, not system - it is material for this turn, not a standing rule, and a system role would make it carry equal weight on every later turn.
- Stage three is attachments on demand. Its trigger is the model deciding to use one, so it is not an injection at all but a tool call. The body should list a directly runnable command line plus one sentence of explanation and never the script's contents - given only a path, the model will read the script first and stage three collapses back into stage two.
- Easy to get wrong - progressive disclosure saves context, not disk IO. Scanning actually reads the whole file, because reaching the frontmatter means reading the head of it; what is saved is the cost of shipping that text with every request.
- Likely follow-ups - which turns carry most of the savings; how the arithmetic changes as skill count grows; whether the stage-two injection should persist in conversation history.
分析过程 · 先想清楚再作答
- 这题在考「你是不是自己实现过一个加载器」。只用过的人会答「先读摘要再读全文」,实现过的人会先说每个阶段的判据不同,因为判据决定了成本的形状。
- 怎么拆:三个阶段各说一遍「什么时候发生、注入什么、成本怎么算」,最后点一句三者的判据互不相同。
- 阶段一是摘要常驻:判据是「这台机器上装了什么」,与用户说什么无关,所以它拼在系统提示里、每一次请求都付钱。内容只有名字加一行 description,这一行是模型判断要不要用它的全部依据。
- 阶段二是全文按需:判据是「这一句话命中了什么」,所以按轮计费,没命中就是零。注入的消息角色应该是 user 不是 system——它是本轮的资料不是长期规则,写成 system 会让它在后面每一轮都被当成同等权重的指令。
- 阶段三是附件按需:判据是「模型自己决定要用」,所以它根本不是注入而是一次工具调用。注入全文时只给能直接跑的命令行加一句说明,脚本内容一个字都不读——只给路径的话模型会先读一遍,阶段三就退化回阶段二了。
- 一条容易说错的:渐进披露省的是上下文不是磁盘 IO。扫描时整份文件其实都读出来了,因为要拿 frontmatter 就得读文件头;省的是「把这段文字塞进每一次请求」那部分成本。
- 可预期的追问:收益大头在哪些轮次上;技能数量增长时这套账怎么变;阶段二的注入要不要进会话历史。
Key points
- Three stages - resident summary, body on demand, attachment on demand - triggered by what is installed, what this sentence matched, and what the model decides to run
- Stage one is one line per skill in the system prompt, paid on every request; it is the model's entire basis for judging relevance
- Stage two is billed per turn and costs nothing when nothing matches; inject it as a user message, not a system one
- Stage three is a tool call rather than an injection - hand over a command line, never the script body
- What is saved is context, not disk IO; most of the saving comes from turns that match nothing at all
答题要点
- 三个阶段:摘要常驻、全文按需、附件按需,判据分别是「装了什么」「这句命中了什么」「模型决定要用」
- 阶段一每技能一行,拼进系统提示,每次请求都付;它是模型判断相关性的全部依据
- 阶段二按轮计费,没命中就是零;注入用 user 角色而不是 system
- 阶段三不是注入而是一次工具调用,只给命令行不给脚本内容
- 省的是上下文不是磁盘 IO;收益大头在「这一轮什么也没命中」的那些轮次上
Where is the line between a skill and a tool, and how do you decide which one a given capability should be?技能和工具的边界在哪?同一件事你怎么决定做成技能还是做成工具?
Common in ChinaCommon overseasIntermediate#skills-vs-tools#agent-designHow to reason about it · think before answering
- This is an architecture judgment question. Saying "a skill is a document and a tool is a function" covers the form but not the cost; candidates who can articulate that a skill cannot be guaranteed to execute have clearly made the trade-off in practice.
- How to break it down - give one criterion, then one benefit and one cost for each, then a counter-example that must be a tool.
- The criterion in one line - a tool is a capability, a skill is a procedure. A tool lets the agent do something it otherwise cannot (without a shell tool it simply cannot run tests); a skill lets it do correctly what it already can (it knows how to run tests, but not which command this repo uses or which line of output to read first).
- The benefit of a skill is that it does not occupy a slot in the tool list. The tool table ships with every request, and past a few dozen tools the odds of picking the wrong one rise noticeably. A skill's resident cost is one line of description and its body is not resident at all, so skill count is practically unbounded.
- The cost of a skill is that execution is not guaranteed. A tool call has defined semantics - the model emits it, we really run it, and bad arguments are pushed back. A skill is just text; the model may read it and not comply, and nothing you own can stop that.
- So the counter-example is clear - a rule that must be enforced should not live only in a skill. Snapshot before writing, approval before dangerous commands, truncation of oversized results: all of those belong in code. The card governs judgment, the code governs discipline.
- Worth mentioning a middle case - executable scripts bundled with a skill. Formally they are attachments of the skill, but running one goes through the tool-call path, so the skill carries "when to run it" while the tool carries "running it".
- Likely follow-ups - what wins when a skill contradicts the system prompt; whether skills deserve a mandatory flag; whether skill description text counts as untrusted input.
分析过程 · 先想清楚再作答
- 这题在考架构判断。答「技能是文档、工具是函数」只说到了形式,说不到代价;能说出「技能不能保证被执行」的人明显是做过取舍的。
- 怎么拆:先给一句判据,再各说一条好处与一条代价,最后给一个必须做成工具的反例。
- 判据一句话:工具是能力,技能是流程。工具让 Agent 做到它本来做不到的事(没有跑命令的工具它就是跑不了测试),技能让它把已经能做的事做对(它会跑测试,但不知道这个仓库跑哪条命令、输出先看哪一行)。
- 技能的好处是不占工具清单的位置:工具表要随每次请求发出去,几十个工具之后选错工具的概率会明显上升;技能的常驻成本只有一行描述,全文根本不常驻,所以技能数量几乎没有上限。
- 技能的代价是不能保证被执行:工具调用有确定语义,模型发出调用就真的会执行、参数不对还会被挡回来;技能只是一段文字,模型可以读了不照做,而你没有任何机制拦得住。
- 所以反例很清楚:一条必须被执行的纪律不该只写成技能。写文件前拍快照、危险命令要审批、结果超长要截断,这些都得写进代码。手艺卡管的是判断,代码管的是纪律。
- 还有一类中间态值得提:技能带的可执行脚本。它形式上是技能的附件,实际执行时走的是工具调用那条路,等于用技能承载「什么时候该跑」,用工具承载「跑起来」。
- 可预期的追问:技能里写的规矩和系统提示里写的规矩冲突了听谁的;要不要给技能加一个「强制执行」标记;技能的描述文本算不算不可信输入。
Key points
- A tool is a capability and a skill is a procedure - one enables what was impossible, the other makes the already-possible correct
- Skills take no slot in the tool list and scale almost without limit; the tool table ships on every request and a bloated one causes mis-selection
- The cost of a skill is that execution is not guaranteed - the model may read it and ignore it, and nothing stops that
- Rules that must be enforced belong in code - snapshots, approval gates and truncation should never be skills alone
- Bundled executable scripts are the middle case - the skill says when to run, the tool call does the running
答题要点
- 工具是能力、技能是流程:前者让它做到本来做不到的事,后者让它把已经能做的事做对
- 技能不占工具清单的位置,数量几乎没有上限;工具表随每次请求发出,多了会让模型选错
- 技能的代价是不能保证被执行,模型可以读了不照做,没有任何机制拦得住
- 必须被执行的纪律要写进代码:拍快照、审批、截断都不该只写成技能
- 技能附带的可执行脚本是中间态:技能管「什么时候跑」,工具调用管「跑起来」
How would you design skill trigger conditions, and which is harder to diagnose - over-triggering or under-triggering?技能的触发条件怎么设计?过度触发和漏触发哪个更难查?
Common in ChinaCommon overseasDeep dive#trigger-design#false-positivesHow to reason about it · think before answering
- The crux is "which is harder to diagnose". Most people answer "write precise trigger words", which dodges the trade-off. The real answer is that the two failure modes have asymmetric costs, so the default stance should itself lean one way.
- How to break it down - answer the harder one first, derive the design stance from it, and only then discuss condition types and implementation traps.
- Under-triggering is harder, and not by a small margin. Over-triggering costs a little context and is visible - the injection report gains a line and you know immediately. Under-triggering means the model answers from generic knowledge, plausibly, with nothing anywhere hinting that a convention went unread; you find out at code review when it once again returns infinity on divide-by-zero.
- That yields the stance - be generous at the matching layer and put the real brake in the budget layer. This is the opposite of most people's instinct, which is to tighten trigger words until the skill never fires, ending in the conclusion that the whole mechanism is useless.
- Condition types rank by trustworthiness - an explicit mention by the user is the most reliable, a path match is next and rarely false-positives, and keywords are the most error-prone. Scores should only order and break ties; an author-declared priority must never decide whether something matched, or a high-priority skill will surface in a completely unrelated sentence.
- One implementation trap catches everyone - ASCII trigger words must match on word boundaries, or "test" fires on "latest" and "contest"; Chinese can only match as a substring because there is no whitespace tokenization. And that distinction must not be left to the skill author, who is thinking about what the skill does rather than where the word might otherwise appear.
- Finally observability - match reasons must be printed. When something fails to trigger, those lines are the user's only evidence for whether the word was too narrow, the path was wrong, or the skill never loaded at all. Without them the mechanism cannot be tuned.
- Likely follow-ups - what to do when two contradictory skills both match; whether the model should choose which to load; whether matching could be delegated to a small model call.
分析过程 · 先想清楚再作答
- 这题的题眼是「哪个更难查」。多数人会答「触发词要写准」,那是在回避取舍;真正的答案是两类误判的代价不对称,所以默认姿态本身就该是偏向某一边的。
- 怎么拆:先答那个更难查的,再由它推出设计姿态,最后才说具体的条件类型与实现坑。
- 漏触发更难查,而且难得不是一个量级。过度触发的代价是花掉一点上下文,而且看得见——注入报账里会多出一行,你当场就知道它上桌了。漏触发是模型按通用知识回答,答得像模像样,而没有任何东西提示你有一份约定没被读到;你只有在代码评审时才会发现它又把除零写成了返回无穷大。
- 由此推出设计姿态:判定层宁可多上一张,真正的刹车放在预算层。这和多数人的直觉相反——第一反应是把触发词写严,结果技能常年不触发,最后得出「这套东西没用」的结论。
- 条件类型按可信度分三类:用户显式点名最可信、路径命中次之(很难误报)、关键词最容易误报。分数只用来排序与打破平局,作者声明的优先级绝不该参与「有没有命中」,否则一个高优先级技能会在完全不相干的话里上桌。
- 实现上有一个人人都踩的坑:英文触发词必须按词边界匹配,否则 test 会被 latest、contest 命中;中文只能按子串,因为没有空格分词。而且这条分界不能交给技能作者自己选——他写触发词时想的是「我这个技能是干嘛的」,不是「这个词会不会出现在别的句子里」。
- 最后是可观测性:命中原因必须打出来。漏触发时用户唯一的依据就是那几行——到底是词写窄了、路径写错了,还是这条技能压根没装上。没有这几行,这套机制就没法调。
- 可预期的追问:同时命中两条互相矛盾的技能怎么办;要不要让模型自己决定加载哪一条;触发判定能不能交给一次小模型调用。
Key points
- Under-triggering is harder - over-triggering shows up in the injection report, while a missed trigger leaves no trace and only surfaces at review
- Hence the stance - be generous when matching and put the brake in the budget layer instead
- Rank conditions by trustworthiness - explicit mention, path match, keyword; an author's priority only breaks ties and never decides a match
- Match ASCII trigger words on word boundaries (otherwise "test" fires on "latest") and Chinese as substrings; this must not be left to the skill author
- Always print match reasons, or a missed trigger gives no way to tell a narrow word from a wrong path from a skill that never loaded
答题要点
- 漏触发更难查:过度触发在注入报账里看得见,漏触发没有任何提示,只能在评审时发现
- 由此定姿态:判定层宁可多上一张,刹车放在预算层而不是判定层
- 三类条件按可信度排:显式点名、路径命中、关键词;作者声明的优先级只打破平局,不决定是否命中
- 英文触发词按词边界匹配(否则 test 命中 latest),中文按子串;这条不能交给技能作者自己选
- 命中原因必须打出来,否则漏触发时无从判断是词写窄了、路径写错了还是技能没装上
D17 Subagents and Parallelism: Independent Context, a Tool Allowlist, Worktree Isolation, and Result Aggregation
What should a subagent inherit from the main session, and what must it never inherit?子 Agent 该继承主会话的什么、绝不该继承什么?
Common in ChinaCommon overseasBasic#subagent#context-boundaryHow to reason about it · think before answering
- This tests whether you have actually dispatched one. People who only read docs answer "a subagent has its own context"; people who built one first separate what is reused from what must be rebuilt, because the two categories have different criteria.
- How to break it down - give one criterion, sort things into two piles by it, then show what breaks when the wrong thing is inherited.
- The criterion in one line - capability can be inherited, situation cannot. Tool implementations, truncation rules, the approval gate and loop detection are capabilities; they hang off the tool interface, so reusing the same tool objects carries them over with no rewriting.
- The first thing never to inherit is the message array. Seven or eight round trips for one small task is normal, they are worthless to the main session, they consume its window, and they drag it off course - it was reasoning about how to split the work and its context is now full of indentation on line 37.
- The second is the system prompt. The main one is full of things true only of the main session - history may have been compacted, the user pastes files with @, skills appear automatically, ask the user when unsure. None of that applies to a subagent, and inheriting it does not add knowledge - it makes the subagent wait for something that will never happen, such as an answer to a question nobody will read.
- The third is the budget. A shared allowance means one looping subagent burns the whole task's quota while some unrelated piece of work quietly fails, and the session that dispatched it never learns why. Reuse the same accounting class with tighter numbers; do not write a second set of books.
- One more thing must be cut off explicitly - the dispatch capability itself. The dispatch tool must never appear in a subagent's allow-list, or the dispatch tree is unbounded, the budget cannot be attributed, and after an incident you cannot say which level edited the file.
- Likely follow-ups - whether a subagent should see the main todo list or plan; whether its events belong in the main event log; when multi-level dispatch is genuinely needed.
分析过程 · 先想清楚再作答
- 这题在考「你有没有真的派过」。只看过文档的人会答「子 Agent 有自己的上下文」,实现过的人会先说清哪几样东西是复用的、哪几样是必须重造的,因为这两类的判据不同。
- 怎么拆:先给一条判据,再按这条判据把东西分成两堆,最后举一个继承错了会怎样的例子。
- 判据一句话:**能力可以继承,情境不能继承**。工具实现、截断规则、审批门、打转检测这些是能力,它们挂在 ToolDef 这个接口上,子 Agent 复用同一批工具对象就等于原样带走,一行都不用重写。
- 绝不该继承的第一样是**消息数组**。子 Agent 一趟往返七八轮很正常,那些往返对主会话毫无价值,却会实打实占掉它的窗口,还会把主会话带偏——它本来在想需求怎么拆,上下文里却堆满了第 37 行的缩进。
- 绝不该继承的第二样是**系统提示**。主会话那份里写满了只对主会话成立的事:对话可能被压缩过、用户会用 @ 贴资料、有技能会自动出现、不清楚可以提问。子 Agent 一条都不适用,继承过去的后果不是多知道一点,而是它会去等一件永远不会发生的事——比如问一个没人会答的问题。
- 绝不该继承的第三样是**预算**。共用一份额度意味着一个打转的子 Agent 能把整次任务的钱烧光,而另一件事莫名其妙没做完,派它出去的那个会话毫不知情。复用同一个记账器的类、但给一组更紧的数字,不要另造一套账。
- 还有一样必须显式截断的是**派发能力本身**:派发工具绝不能出现在子 Agent 的白名单里,否则派发树没有边界,预算算不出来,出了事也说不清是第几层在改文件。
- 可预期的追问:子 Agent 要不要拿到主会话的任务清单或计划;它的事件要不要写进主会话的日志;多层派发什么时候真的需要。
Key points
- The criterion - capability is inheritable (tool objects, truncation, approval, loop detection); situation is not
- Never inherit the message array - those round trips are worthless to the main session, consume its window and derail it
- Never inherit the system prompt - it asserts things true only of the main session, and the subagent ends up waiting on what will never happen
- Never inherit the budget - a shared quota lets one looping subagent starve unrelated work; reuse the tracker with tighter numbers
- The dispatch tool itself must never be in a subagent's allow-list, or the dispatch tree becomes unbounded
答题要点
- 判据:能力可以继承(工具对象、截断、审批、打转检测),情境不能继承
- 不继承消息数组:子 Agent 的七八轮往返对主会话没有价值,还会占窗口并带偏它
- 不继承系统提示:主会话那份写满只对主会话成立的事,继承过去它会去等一件不会发生的事
- 不继承预算:共用额度会让一个打转的子 Agent 连坐掉另一件事,复用记账器但给更紧的数字
- 派发工具本身绝不进子 Agent 的白名单,否则派发树没有边界
How do you isolate parallel agents editing one repository, and how are conflicts detected and handled?并行改同一个仓库,你用什么手段隔离?冲突怎么发现和处理?
Common in ChinaCommon overseasIntermediate#worktree#merge-conflictHow to reason about it · think before answering
- The crux is the second half. Naming worktrees is a pass; what separates candidates is how conflicts are detected, because most people assume merging is trivial and that is exactly the step that fails silently.
- How to break it down - say why a shared working directory is unacceptable, then the isolation mechanism, then the merge rules one by one.
- The problem with a shared directory is not that conflicts sometimes happen but that you cannot know what happened. Two agents read the same file; one writes back, the other does an exact replacement against stale content. The rule that old content must match uniquely saves you once, but next time it edits a different span, matches, and the file now carries two edits that were never designed together. Green tests only mean those two edits happened not to collide.
- The isolation mechanism is git worktree - one repository checked out into several directories, each with its own HEAD and index while the object store is shared, so opening a workspace is constant cost rather than copying a two-gigabyte tree. The four commands are worktree add --detach, work in that directory, diff HEAD to collect, worktree remove --force to clean up.
- One timing detail is easy to get wrong - the baseline must be taken at dispatch time, not by comparing against the main workspace at collection time. The main workspace may have moved during those seconds, and a stale baseline mixes other people's edits into this agent's diff.
- Detection is a three-way comparison - the baseline, this side's version, the other side's version. The instinctive implementation writes each agent's changes back in order, which means last writer wins - both receipts say done, only one change survives, and nothing on screen says so. That is the classic silent data loss of parallel agents.
- Three rules - one editor, apply; several editors producing identical content, apply once and do not call it a conflict (it is the same work done twice, and crying conflict devalues the signal); different content, report a conflict and apply none. Plus one that is easy to miss - the main workspace drifts too, and when the baseline no longer matches, writing would silently overwrite what the user just saved.
- Why not auto-pick on conflict - auto-picking presupposes you can tell which version is right, and you cannot. Real git merges line-level when edits do not overlap, which needs line information and an algorithm; a degraded implementation must at least never lose work silently. A conflict a human can read beats a quietly produced version nobody designed.
- Likely follow-ups - how deletes and renames merge; whether the model should resolve conflicts; what happens when workspaces are left uncollected.
分析过程 · 先想清楚再作答
- 这题的题眼在后半句。隔离说得出 worktree 就算及格,真正分出高下的是「冲突怎么发现」——多数人默认合并是件顺手的事,而那恰恰是并行里最容易静默出错的一步。
- 怎么拆:先说共享工作区为什么不可接受,再说隔离手段,最后把合并规则一条条摆出来。
- 共享工作区的问题不是「有时候会冲突」,而是**你无法知道发生了什么**。两个 Agent 各自读了同一个文件,一个改完写回,另一个拿旧内容做替换:精确替换那条「旧内容必须唯一匹配」的规则会救你一次,但下一次它改的是另一段、匹配成功,于是文件里同时有了两处**从未被一起设计过**的改动。测试绿了只说明这两处恰好没打架。
- 隔离手段是 git worktree:同一个仓库签出到多个目录,每个目录有自己的 HEAD 与索引,而对象库共享——所以开一间工位是常数级开销,不会因为仓库两个 G 就复制两个 G。四条命令是 worktree add --detach、在那个目录里干活、diff HEAD 收产物、worktree remove --force 收摊。
- 一个时机很容易错:**基线要在派单那一刻取**,不能等回收时拿主工作区去比。子 Agent 干活那几秒里主工作区可能已经被别人动过,拿一个变了的现场当基线,差异里会混进不是它改的东西。
- 冲突发现靠**三方比较**:基线、这一边改成什么、另一边改成什么。直觉写法是按顺序把每个人的改动写回去,那等于后写的赢——两份回执都写着已完成,主工作区里却只剩一份改动,而且屏幕上没有任何提示。这是并行 Agent 最典型的静默数据丢失。
- 规则三条:只有一个人改就落盘;多个人改成相同内容落一次且不算冲突(那只是同一件事做了两遍,报成冲突会让告警变廉价);改成不同内容就报冲突、一份都不落。再加一条容易漏的:主工作区自己也会漂移(用户在编辑器里存了盘),基线对不上时落盘会无声覆盖他刚写的东西。
- 冲突时为什么不自动挑一份:能自动挑的前提是你有办法判断哪份对,而你没有。真 git 的行级三方合并能在改动不重叠时自动合,那需要行信息与一套算法;退化实现至少要保证不静默丢东西——报一个人看得懂的冲突,好过悄悄产出一个没人设计过的版本。
- 可预期的追问:删除与重命名怎么合;要不要让模型自己解冲突;工位残留没收会怎样。
Key points
- The real problem with a shared directory is not knowing what happened - two edits never designed together end up in one file
- Isolate with git worktree - several directories each with their own HEAD and index over a shared object store, so each one costs a constant
- Take the baseline at dispatch time, or other people's edits leak into this agent's diff
- Detect with a three-way comparison; writing changes back in order means last writer wins, the classic silent data loss
- Rules - single editor applies, identical content applies once, differing content is a conflict with nothing applied; a drifted main workspace is also refused
答题要点
- 共享工作区的真问题是「无法知道发生了什么」:两处从未被一起设计过的改动会同时留在文件里
- 隔离用 git worktree:多个目录各有 HEAD 与索引,对象库共享,开一间是常数级开销
- 基线必须在派单那一刻取,否则差异里会混进别人的改动
- 冲突靠三方比较;直觉的顺序写回等于后写的赢,是最典型的静默数据丢失
- 规则:单人改落盘、多人改成相同内容落一次、改成不同内容报冲突且一份都不落;主工作区漂移同样拒绝落盘
Which tasks are worth dispatching to subagents, and which get worse when parallelized?什么任务适合派给子 Agent,什么任务并行反而更差?
Common in ChinaCommon overseasDeep dive#parallelism-cost#agent-designHow to reason about it · think before answering
- This tests cost awareness. Answering "parallelize anything separable" just restates the definition; naming the three costs and offering an actionable self-check shows you have done the arithmetic.
- How to break it down - lay out the three costs, derive the three kinds of work not to dispatch, then give a one-sentence test.
- The first cost is tokens, and it genuinely multiplies - every subagent pays again for the system prompt and the tool table. Context cannot be shared; that is a consequence of isolation, not a shortcoming. Worse, the main session cannot see that spend - the screen shows one line saying two subagents were dispatched - so the tool needs a command that opens the books, or nobody knows what they spent.
- The second cost is debugging. Serially you follow one timeline; in parallel there are N interleaved ones and the log is written in arrival order, so the two outputs are braided together. That is why the receipt must carry the workspace id - it is the only attribution you get.
- The third cost is merging - conflict rules, renames, deletes, binary files. You pay all of it the moment you decide to parallelize; the invoice just arrives later.
- From those, three kinds of work not to dispatch - anything with ordering dependencies (the second step needs the first, so it waits and you end up serial having paid twice); anything touching the same files (guaranteed to hit the conflict rule, and a failed merge means redoing the work); anything done in one step (the fixed cost of dispatching exceeds the work, since explaining who you are and what you may use costs more than the edit).
- For the tasks that do fit, one test works well - if these two pieces of work went to two people at the same time, would they need to talk to each other? If yes, do not dispatch. It beats "can it be split" because it asks about coupling rather than form.
- One positive case gets overlooked - read-only exploration. Dispatching three subagents into three corners of a large repository, each returning a summary, has no write conflicts and blocks context pollution most effectively; it is the cleanest win parallelism offers.
- Likely follow-ups - how to pick the degree of parallelism and what caps it; whether a failed subagent should be redispatched; whether the user should see per-subagent progress.
分析过程 · 先想清楚再作答
- 这题在考成本感。答「能拆开的就并行」是在复述定义;能把三笔成本报出来、并给一条可操作的自查问题的人,明显算过这笔账。
- 怎么拆:先把三笔成本摆出来,再由成本反推出不该派的三类活儿,最后给一条一句话的自查判据。
- 第一笔成本是 token,而且是实打实翻倍的:每个子 Agent 都要重新付一遍系统提示与工具表的钱。上下文不能共享是隔离的必然结果,不是实现没做好。更糟的是这笔钱**主会话看不见**——屏幕上只滚过一行「已派出 2 个子 Agent」,所以工具里必须有一条命令把这笔账摊开,否则没人知道自己花了多少。
- 第二笔成本是调试。串行时顺着一条时间线看就行,并行之后有 N 条线交错发生,而日志是按到达顺序写的,两边的输出彼此穿插。这也是回执里必须带工位号的理由——那是唯一的归属线索。
- 第三笔成本是合并:冲突规则、重命名、删除、二进制文件,这些代价在你决定并行的那一刻就付了,只是账单晚一点到。
- 由此反推三类不该派的活儿:有先后依赖的(后一步要前一步的结果,派出去只能干等,最后还是串行还多花一份钱);要改同一批文件的(必然撞冲突规则,合并失败还得重做);一步就能做完的(派发的固定成本比自己顺手做完还高——给它讲清楚「你是谁、能用什么」就比活儿本身贵)。
- 适合的那一类有一条很好用的自查问题:**这两件事如果交给两个真人同时做,他们需要互相说话吗?** 需要就别派,不需要才是并行该出场的地方。它比「能不能拆开」准得多,因为它问的是耦合而不是形式。
- 还有一个容易被忽略的正面场景:**只读的探查**。派三个子 Agent 分头去大仓库的三个角落找线索,各自只回一段摘要——没有写冲突、上下文污染也最严重地被挡在外面,这是并行收益最干净的一类。
- 可预期的追问:并行度该设多少、上限该按什么定;失败的那一个要不要重派;要不要让用户看得到每个子 Agent 的进度。
Key points
- Three costs - tokens genuinely multiply and stay invisible to the main session, debugging means attributing across N interleaved timelines, and merging carries its own complexity
- Three kinds not to dispatch - ordered dependencies, work touching the same files, and anything done in a single step
- The fixed cost is the crux - telling a subagent who it is and what it may use can cost more than the edit itself
- The self-check - if two people did these at the same time, would they need to talk? If yes, do not dispatch
- The cleanest win is read-only exploration - no write conflicts, and the strongest block on context pollution
答题要点
- 三笔成本:token 实打实翻倍且主会话看不见、调试要在 N 条交错时间线里找归属、合并规则的复杂度
- 不该派的三类:有先后依赖的、要改同一批文件的、一步就能做完的
- 固定成本是关键:给子 Agent 讲清「你是谁、能用什么」可能比活儿本身还贵
- 自查问题:这两件事交给两个真人同时做,他们需要互相说话吗?需要就别派
- 最干净的正面场景是只读探查:没有写冲突,而且最有效地挡住了上下文污染
D18 Hooks and Background Tasks: Lifecycle Hooks, Deterministic Checks, and Notifications That Don't Interrupt the Conversation
Which constraints belong in hooks and which belong in the prompt? What is the criterion?哪些约束该用钩子实现,哪些该写进提示词?判据是什么?
Common in ChinaCommon overseasBasic#hooks#prompt-engineering#agent-designHow to reason about it · think before answering
- This tests whether you know the reliability ceiling of a prompt. People who have not built one answer "important things go in the prompt, very important things go in code", which is circular; people who have built one carry a criterion they can apply on the spot.
- How to break it down - state the criterion, then the cost on each side, then separate out a third mechanism people tend to conflate with hooks.
- The criterion - if a rule can be decided by a single if statement, it should not be left to the model to remember. A prompt makes the model usually right; a hook makes it always right, and the gap between those two words is the entire reason hooks exist.
- Name the cost on both sides. A hook can only express what can be written as code, and "is this code any good" never can, so prompts cannot be replaced. Meanwhile every hook slows down every tool call, and that bill is multiplied by the number of calls.
- Separate hooks from permission rules, which are the easiest thing to confuse them with. Permission rules answer who is allowed to do this and are a static table matched on tool name and path; hooks answer whether this is the right thing to do right now, and may key off session state or the content being written. Whether this file has been read in this session is something a permission table cannot express.
- A concrete contrast carries the point - read the file before editing it is usually obeyed when it lives in the prompt, and always obeyed when it is a hook, at a cost of a dozen lines.
- Likely follow-ups - how to choose when a rule could be either; what happens when a hook itself is wrong (there must be a master switch); whether to delete a rule from the prompt once it becomes a hook.
分析过程 · 先想清楚再作答
- 这题在考「你知不知道提示词的可靠性上限」。没实现过的人会答「重要的写提示词,非常重要的写代码」,这是同义反复;实现过的人手里有一条能当场判的判据。
- 怎么拆:先给判据,再说两边各自的代价,最后补一条容易被混进来的第三方(权限规则)。
- 判据:一条规则如果你能用一个 if 判断出来,它就不该交给模型去记。提示词让模型「通常」做对,钩子让它「总是」做对,这两个词之间的差距就是钩子存在的全部理由。
- 两边的代价要都说:钩子只能表达能被写成代码的东西,「这段代码写得好不好」永远写不成代码,所以提示词不可能被替代;而钩子多了会拖慢每一次工具调用,这笔账要乘以调用次数。
- 还要跟权限规则划界,它们最容易混:权限规则回答「谁有权做这件事」,是一张按工具名与路径匹配的静态表;钩子回答「这件事此刻做得对不对」,判据可以依赖会话状态与写入内容。「这个文件本轮读过没有」就是权限规则表达不了的。
- 举一个具体的对照最能说明问题:「改文件之前先读一遍原文」写在提示词里是大部分时候遵守,做成钩子是百分之百,而实现代价只有十几行。
- 可预期的追问:一条规则同时能写成两者时怎么选;钩子写错了怎么办(要有总开关);提示词里已经写过的规矩做成钩子之后要不要从提示词里删掉。
Key points
- The criterion - a rule decidable by one if statement should not be left to the model; prompts give you usually, hooks give you always
- Hooks have limited expressive power; anything requiring a judgment of quality still needs the prompt - they divide work rather than replace each other
- Hooks versus permission rules - permissions answer who may act and are a static table; hooks answer whether the act is right now and may key off state and content
- The cost of a hook is that it slows every tool call, multiplied by the number of calls
- Usually keep the sentence in the prompt too, so the model knows the rule exists instead of discovering it by being blocked
答题要点
- 判据:能用一个 if 判断出来的规则就不该交给模型去记;提示词给「通常」,钩子给「总是」
- 钩子的表达力有限,凡是需要判断好坏的仍然只能靠提示词,两者是分工不是替代
- 钩子与权限规则的分界:权限管「谁有权做」,是静态表;钩子管「此刻做得对不对」,判据可依赖状态与内容
- 钩子的代价是拖慢每一次工具调用,而且乘以调用次数
- 做成钩子之后提示词里那句通常仍要留着,让模型知道有这条规矩,免得它撞上去才知道
How do you design hooks so they can veto a call without deadlocking the agent?钩子怎么设计才能既能否决又不至于把 Agent 卡死?
Common in ChinaCommon overseasDeep dive#hooks#reliability#failure-handlingHow to reason about it · think before answering
- This tests whether your own hooks have ever bitten you. People who have not built them stop at "add a timeout"; people who have answer in three layers - veto semantics, failure posture, and what the execution layer must catch.
- How to break it down - say where a veto is even possible, then how a veto should be expressed, then the two things the execution layer must guarantee.
- Layer one - a veto is only possible before the tool runs. Afterwards the file is already changed and the command already executed, so saying no is self-deception. A post-tool hook's block can only mean marking the call as failed so the model cleans up after itself; it undoes nothing. Conflating the two is the most common mistake in this protocol.
- Layer two - express a veto as a failed tool result, not a thrown exception. A throw gets flattened into "execution failed" by the registry, the model cannot see what happened, and it retries verbatim. Returning a result that states the reason and the next step lets the self-correction loop take over. That message must say that retrying identically changes nothing, because retry is exactly the default behavior the model has learned.
- Layer three - two guarantees against deadlock. Every hook needs a timeout, and a timeout must count as failure rather than success; counting it as success silently disables the check precisely when it matters most. And exceptions must be caught inside the hook layer, so a broken hook costs you one check rather than the whole agent. Also know that a race-style timeout does not stop the work in flight - only the timeout handed to the child process does.
- The last layer is posture - blocking should not be the default. A hook that blocks readily turns the agent into something that refuses to move, and that failure is the hardest to diagnose because all the user sees is that it will not do anything today. So there must also be a master switch to turn hooks off and keep working.
- Likely follow-ups - serial or parallel execution; whether later hooks still run after a blocking one fails; what to do when a hook itself needs to call a model.
分析过程 · 先想清楚再作答
- 这题在考「你有没有被自己写的钩子坑过」。没实现过的人答「加个超时」就没了;实现过的人会分三层答:否决语义、失败姿态、执行层的兜底。
- 怎么拆:先说清否决只在哪个触发点成立,再说否决怎么表达,最后说执行层必须兜住的两件事。
- 第一层,否决只在工具执行之前成立。执行之后文件已经改了、命令已经跑了,那时候说「不行」是自欺——执行后钩子的「阻断」只能是「把这次调用判成失败」,让模型自己回头收拾,它撤销不了任何东西。把这两种阻断混为一谈是设计这套协议最常见的错误。
- 第二层,否决要表达成一条失败的工具结果而不是抛异常。抛异常会被注册表兜成一句「执行失败」,模型看不出发生了什么就会原样重试;返回一条写清原因与下一步的结果,自纠机制就自动接手了。失败信息里必须写「原样重试不会有不同结果」——模型学到的默认动作就是重试一次。
- 第三层是防卡死的两件事:每个钩子必须有超时,而且超时要算失败不算通过(算通过等于在最需要检查的时候悄悄关掉检查);钩子抛的异常必须兜在钩子这一层,一个写坏的钩子最坏的后果应该是少一个检查。还要知道 race 之类的写法停不住正在跑的东西,真正能停的是传给子进程的那个超时。
- 最后一层是姿态问题:默认不该是阻断。一个动不动就阻断的钩子会让 Agent 变成走不动路的东西,而那种故障最难查——用户看到的只是「它今天什么都不肯做」。所以还要有一个总开关,让人当场关掉再继续干活。
- 可预期的追问:多个钩子是串行还是并行;阻断型钩子失败之后后面的钩子还跑不跑;钩子自己需要调模型时怎么办。
Key points
- A veto is only possible before execution; blocking afterwards only marks the call failed and undoes nothing
- Express a veto as a tool result with ok false rather than a throw, so the self-correction loop takes over
- The failure message must say that an identical retry changes nothing, or the model will retry by default
- Every hook needs a timeout that counts as failure, and exceptions must be caught at the hook layer so one broken hook costs one check
- Default to warning rather than blocking, and keep a master switch so a bad hook can be turned off on the spot
答题要点
- 否决只在工具执行前成立;执行后的「阻断」只是把这次调用判成失败,撤销不了任何东西
- 否决表达成一条 ok 为 false 的工具结果,不要抛异常,让自纠机制接手
- 失败信息里必须写清「原样重试不会有不同结果」,否则模型会照默认动作重试
- 每个钩子必须有超时,超时算失败不算通过;异常兜在钩子这一层,坏一个钩子只损失一个检查
- 默认姿态是只警告不阻断,并且要留一个总开关,钩子写错时能当场关掉
When a long-running command goes to the background, how do its status and result get back into the session?长时间运行的命令放到后台,状态与结果怎么回到会话里?
Common in ChinaCommon overseasIntermediate#background-tasks#async#terminal-uxHow to reason about it · think before answering
- This tests whether you have actually handled async completion. People who have not answer "print a line when it finishes"; people who have know the hard parts are when to print and who to print it for.
- How to break it down - the three pieces a background task needs, then the timing of the notification, then the fact that humans and the model are two separate channels.
- The three pieces are process, log, and status. The process gets its own process group and the parent does not wait on it. The log must go to disk, because nobody is watching - output may run to tens of thousands of lines or arrive half an hour later. And there must be a queryable status table; this is the piece people forget, and without it a background task is something you can start and never find again.
- Be explicit about the return value - the model gets an acknowledgment, not a result, and that sentence must be in the tool description. Almost every tool the model has seen returns an answer on call, so without saying so it will wave the receipt around and tell the user the tests are done.
- Notification timing is the real difficulty. A terminal is only ever in one of three states - streaming output, where interrupting tears the typewriter in half; waiting on an approval or a question, where a stray line reads as part of the question; and waiting for input, where interrupting is fine only if the input line is still empty, or you wipe out what the user half typed. So the default is to queue and drain at a safe moment.
- The most commonly missed piece - queue notifications for the human and for the model separately. Text on screen is not in the message array, so the model cannot see it. Without that, the user sees the task complete and then hears the model say it is still running.
- Progress and completion travel differently - progress is pulled, because the user asks for it, while completion is pushed exactly once and must reach the user. Pushing progress three times a second floods the terminal.
- Likely follow-ups - whether a failed task should interrupt the current turn; what to do with tasks still running at exit; how to isolate parallel tasks (a different day's topic).
分析过程 · 先想清楚再作答
- 这题在考「你有没有真的做过异步收尾」。没做过的人答「跑完打印一行」,做过的人知道难点全在「什么时候打印」和「打给谁看」。
- 怎么拆:先说后台任务三件套,再说通知的时机,最后说给人看与给模型看是两条路。
- 三件套是进程、日志、状态查询。进程要自成进程组,父进程不等它;日志必须落盘,因为没人看着它,输出可能有几万行也可能半小时后才产生;状态要有一张能查的表——这件最容易漏,没有它,一个后台任务就是启动完就再也找不到的东西。
- 工具返回值要说清:模型拿到的是受理回执不是执行结果,而且这句话必须写进工具描述里。模型见过的工具几乎都是调用完就拿到答案,不明说它会拿着回执告诉用户「已经跑完了」。
- 通知时机是真正的难点。终端在任何时刻只有三种状态:正在流式输出(绝对不能插,会把打字机撕成两半)、正在等审批或等回答(不能插,用户会当成问题的一部分)、正在等用户输入(能插,但只有输入行为空时才行,否则会冲掉他敲了一半的字)。所以默认动作是攒着,只在安全时刻倒出来。
- 还有一条最容易漏的:给人看的通知和给模型看的通知要分开攒。屏幕上的字不在消息数组里,模型看不见——少了这一条,用户明明看到「任务完成」,一问模型却说「还在跑」。
- 进度与完成的路子也不一样:进度是拉的(用户主动查),完成是推的(只发生一次,必须送到眼前)。每秒推三次进度会把终端刷爆。
- 可预期的追问:任务失败了要不要打断当前对话;退出时还在跑的任务怎么办;多个任务并行时怎么隔离(那是另一天的题目)。
Key points
- Three pieces - a process in its own group, a log on disk, and a queryable status table; without status the task cannot be found again
- The tool returns an acknowledgment, not a result, and the description must say so or the model treats the receipt as the outcome
- Queue notifications by default; the only terminal state that permits interrupting is waiting for input with an empty input line
- Queue separately for the human and for the model, or the model will insist the task is still running
- Progress is pulled and completion is pushed; pushing every progress update floods the terminal
答题要点
- 三件套:自成进程组的进程、落盘的日志、可查询的状态表,缺状态那件任务就找不回来
- 工具返回的是受理回执不是结果,这句话必须写进工具描述,否则模型会拿回执当结论
- 通知的默认动作是攒着;终端只有「正在等输入且输入行为空」这一种状态允许插话
- 给人看的通知与给模型看的通知分两条路攒,否则模型会说「还在跑」
- 进度是拉的、完成是推的;每次进度都推通知会把终端刷爆
D19 Multimodal Input: Pasting Screenshots, Image Validation, and Fixing Code From a Screenshot
How would you add images to an existing text-only message schema without breaking compatibility?在已有的纯文本消息结构上加图片,你会怎么改才不破坏兼容?
Common in ChinaCommon overseasBasic#multimodal#message-schemaHow to reason about it · think before answering
- This tests whether you have ever changed a protocol that has been running for a while. People who only read docs answer "make content an array"; people who have done it first ask how many call sites that type has and whether they all break at once.
- How to break it down - first how the internal protocol leaves room, then what actually changes at the outbound edge, then why not normalize every message into the new shape.
- The internal answer is a union - content is either a string or an array of content parts, and an image is one kind of part (media type plus base64). What you reserve is a shape, not a field: reserving a field such as imageUrl falls apart the moment you need several images, or text and images interleaved.
- State the cost too - from that day on, every site that wants the message as a string must go through a to-plain-text helper. You pay that small tax many times, and in exchange the day images arrive the protocol does not move.
- What really changes is the outbound edge - the function that translates the internal protocol into gateway wire format. On an OpenAI-compatible endpoint a text part is type text and an image part is type image_url wrapping a data URL. The loop, approval gate, truncation, snapshots and compaction all stay untouched, because they touch the protocol rather than the wire format.
- A bonus point - do not normalize plain-text messages into arrays. Both forms are accepted, but prompt caching matches the prefix byte for byte, so rewriting the shape of every historical message drops the cache entirely on release day. Use the array form only for the message that actually carries an image.
- Likely follow-ups - how multiple images and text are ordered; what happens when that message hits the session log; what compaction does with images.
分析过程 · 先想清楚再作答
- 这题在考「你改没改过一个已经跑了很久的协议」。只读过文档的人会答「content 改成数组就行」,改过的人会先问一句:这个类型有多少处调用方,它们会不会一起红。
- 怎么拆:先说内部协议怎么留形状,再说出口那一层改什么,最后说为什么不把所有消息都统一成新形状。
- 内部协议的正解是把 content 定成「字符串或内容块数组」的联合类型,图片是其中一种块(媒体类型加 base64)。留的是形状不是字段:留字段(比如加一个 imageUrl)会在需要多张图、或者图文交替时立刻不够用。
- 代价要说出来:从留形状那天起,每一处想把消息当字符串用的地方都得先过一个「取纯文本」的函数。这个小麻烦要付很多次,换来的是加图片那天不用动协议。
- 真正要改的只有出口:内部协议翻译成网关报文的那一段。OpenAI 兼容口是文字段 type text、图片段 type image_url 里套一条 data URL。循环、审批、截断、快照、压缩一行都不用改,因为它们碰的是内部协议不是报文。
- 最后一条是加分项:纯文本消息不要统一成数组。两种写法对面都认,但提示缓存按前缀逐字命中,改一遍历史消息的字节形状等于让缓存当天全部落空。只有真的带图那一条用数组。
- 可预期的追问:多张图和文字怎么排序;这条消息进会话日志时怎么处理;压缩摘要时图片怎么办。
Key points
- Make the internal content type a union of string and an array of content parts, with image as one part kind - reserve the shape, not a field
- The cost is a to-text helper at every read site; the payoff is that the protocol does not move on the day images arrive
- Only the outbound edge changes - parts translated into wire format (text parts, and image parts as image_url with a data URL)
- Loop, approval, truncation, snapshots and compaction need no change because they depend on the protocol, not the wire format
- Keep plain-text messages as strings; normalizing them for tidiness costs you the prompt cache
答题要点
- 内部协议用「字符串或内容块数组」的联合类型,图片是其中一种块——留形状不留字段
- 代价是每处取文本都要过一个转换函数,收益是加图片那天协议不动
- 真正改的只有出口那一层:内部块翻译成网关报文(文字段 text、图片段 image_url 套 data URL)
- 循环、审批、截断、快照、压缩都不用改,因为它们依赖的是协议不是报文
- 纯文本消息保持字符串形态,别为统一而统一——提示缓存按前缀命中
What validation does image input need, and why would a very small image be rejected?图片输入要做哪些校验?为什么小图片会被拒绝?
Common in ChinaCommon overseasIntermediate#input-validation#multimodalHow to reason about it · think before answering
- This tests whether you have actually shipped images to a model. People who have not say "check the size"; people who have start from the least intuitive rule - decide format from the bytes, never from the extension.
- How to break it down - three gates: format, dimensions, then size and count, each with its criterion and what the failure message must say.
- Gate one is format, decided by the magic bytes. Screenshot tools saving a PNG under a .jpg name is common, and the media type goes into the request: get it wrong and the other side either returns an opaque error code or silently discards the data, both hard to debug. Also, recognized but unsupported beats unrecognised - the former can say what the format is and which ones you accept.
- Gate two is a minimum dimension, and there is measured evidence for it: on 2026-09-07 a 4x4 PNG was rejected as an unsupported image while a 64x64 image from the same run went through. So the floor is real, not fussiness; catching it locally yields a human sentence, while sending it yields an error code. Reading dimensions is easy for PNG at fixed offsets; JPEG requires walking markers to the SOF segment and skipping the Huffman table whose marker number falls inside the SOF range - miss that and nothing errors, you just read a fake size.
- Gate three is byte size and image count, and it is local policy. Say this out loud - do not hardcode a number copied from any one gateway's docs. Limits differ between vendors and change over time, so a hardcoded one is a false fact that expires silently.
- Three reasons to validate client side, the third being the important one - you save a wasted round trip; the other side's error codes are unreadable to humans; and some models do not reject at all, they invent an answer. Do not outsource a judgment you can make yourself.
- The shape of the failure message is also part of the answer - it must state what is wrong with this image, what the threshold is, and what to do next. Drop any one of those and the user is left guessing.
- Likely follow-ups - whether to auto-compress when over budget; how to split a budget across several images; whether pasted data URLs need a length cap.
分析过程 · 先想清楚再作答
- 这题在考你有没有真的把图发出去过。没发过的人只会说「查一下大小」;发过的人会先讲一条最反直觉的:格式要按字节判,不能按扩展名判。
- 怎么拆:按三道闸讲——格式、尺寸、体积与张数,每道说清判据与失败提示该写什么。
- 第一道是格式,判据是文件头的魔数。截图工具存成 jpg 实际是 PNG 很常见,而媒体类型是要写进报文的:写错了对面要么回一个看不懂的错误码,要么把它当坏数据默默丢掉,两种都难查。另外「认得出但不收」要好过「不认识」——前者能说清是什么格式、我们只收哪几种。
- 第二道是尺寸下限,而且它有实测依据:2026-09-07 实测一张 4x4 的 PNG 被模型判成无效图片,同一批里 64x64 正常。所以下限不是洁癖,是真的会被拒;挡在本地能给一句人话,发出去再被拒只能给一条错误码。读尺寸时 PNG 偏移固定,JPEG 必须沿 marker 走到 SOF,而且要跳过编号落在 SOF 区间里的霍夫曼表——漏了这条不会报错,只会读出一个假尺寸。
- 第三道是体积与张数,它是本地策略。这里要主动说一句:这些数字不该照抄任何一家网关的文档写死在代码里,各家不一样而且会变,写死等于埋一个会悄悄过期的假事实。
- 校验放客户端的理由有三条,第三条最重要:省一次白花的往返;对面的错误码人看不懂;以及有些模型压根不会拒绝,它会编一个答案。能自己判的事别指望对面替你判。
- 失败提示的规格也是考点:必须同时说清「这张图哪里不合格、合格线是多少、下一步该干什么」,缺一样用户就只能猜。
- 可预期的追问:超预算时要不要自动压缩;多张图怎么分配预算;用户贴进来的 data URL 要不要做长度上限。
Key points
- Three gates - format by magic bytes rather than extension, a minimum dimension, and byte size plus per-turn count
- Small images really are rejected - a measured 4x4 failure against a working 64x64 - so enforce the floor locally
- Reading JPEG dimensions means walking markers to SOF and skipping the Huffman table inside the SOF range, or you silently read a fake size
- Size and count limits are local policy; never hardcode one vendor's published ceiling
- The decisive reason to validate client side - some models do not reject bad input, they invent an answer
- A failure message must state what is wrong, what the threshold is, and what to do next
答题要点
- 三道闸:格式(按魔数不按扩展名)、尺寸下限、体积与单轮张数
- 小图真的会被拒——实测 4x4 被判无效、64x64 正常,所以下限要挡在本地
- JPEG 读宽高要沿 marker 走到 SOF,并跳过编号落在 SOF 区间里的霍夫曼表,否则读出假尺寸且不报错
- 体积与张数是本地策略,不要把任何一家的上限写死进代码
- 校验放客户端的关键理由:有些模型不会拒绝,它会编一个答案
- 失败提示必须同时说清「哪里不合格、合格线多少、下一步干什么」
How do you design the fallback when a model cannot see images, and how do you find out that it cannot?模型不支持图片时的降级策略怎么设计?你怎么先知道它不支持?
Common in ChinaCommon overseasDeep dive#capability-detection#graceful-degradationHow to reason about it · think before answering
- The dividing line is whether you focus on the first half or the second. Most candidates jump to the fallback, but the hard part is detection, because the three most common detection strategies are all wrong.
- How to break it down - name why each of the three is wrong, give the verifiable-criterion answer, and only then describe what the fallback must say.
- Wrong approach one - send an image and treat the absence of an error as support. On 2026-09-07 a solid red image went to four models; all four returned normally with no error, and one of them called it blue-green. That is not a rejection, it is an invented answer.
- Wrong approach two - look for the word vision in the model id. Model id shape varies by gateway (the same model may or may not carry a vendor prefix), so it was never safe to treat as a constant.
- Wrong approach three - maintain your own support list. The list will go stale, and when it does nothing errors; the symptom is that one day it quietly starts inventing answers.
- The right answer is a verifiable criterion - send an image whose answer you already know and check the reply. It works because you generate the image yourself: a solid color you chose, so the correct answer is a known fact rather than another thing to trust. Two details - randomize the color, or a guessing model has a decent chance of being right; and skip detection against an offline or scripted provider, where it measures nothing.
- The fallback has three requirements - send no image at all; in the replacement text state only what you actually know (format, dimensions, source path) and never describe the picture on the model's behalf, which would be you inventing; and explicitly require it to say it cannot see and ask the user for a description. The real danger is not weak capability but a fallback that hides itself - a vague note lets the model carry an invented visual impression into the code it writes.
- Two engineering notes - cache the detection result per session and probe only on turns that actually carry an image, since spending a request on image-free turns makes no sense; and give the user a manual switch to force text mode when they already know the model is blind, saving both the probe and the payload.
- Likely follow-ups - whether a failed probe should auto-switch models; how to invalidate the cache when the model changes mid-process; how to account for the cost of the probe request itself.
分析过程 · 先想清楚再作答
- 这题的分水岭在前半句还是后半句。多数人直接讲降级,而真正的难点是探测——因为最常见的三种探测写法全是错的。
- 怎么拆:先说三种错的写法各错在哪,再给可验证判据这个正解,最后才讲降级要写成什么样。
- 错法一,发一张图没报错就算支持。2026-09-07 实测把一张纯红图发给四个模型,四个都正常返回、都没报错,其中一个说它是蓝绿色——它不是拒绝,是编了一个答案。
- 错法二,看模型 id 里有没有 vision 字样。模型 id 的形状随网关变(同一个模型在不同网关下带不带厂商前缀都不一样),它本来就不该当常量用。
- 错法三,维护一张自己的支持清单。清单一定会过期,而过期时没有任何东西会报错,表现是某天开始悄悄编答案。
- 正解是可验证的判据:发一张答案已知的图,核对它答得对不对。之所以能成立是因为那张图由我们自己生成——纯色、颜色由我们指定,所以正确答案是已知事实而不是另一个要相信的东西。两个细节:颜色要随机换,否则瞎蒙有概率蒙对;离线或桩 provider 下别做探测,那测不出任何东西。
- 降级的规格有三条:一张图都不发;说明里只写我们真的知道的事(格式、尺寸、来源路径),绝不替模型描述图里有什么,那就是我们自己在编;以及明确要求它说出「我看不见」并向用户要文字描述。最怕的不是能力弱,是假装自己没降级——含糊的说明会让模型带着一个编出来的视觉印象继续改代码。
- 工程上还要补两笔:探测结果按会话缓存一次,而且只有带图的那一轮才去探,没图的轮次多花一次请求毫无道理;再给用户一个手动开关,明知模型看不见时直接关掉,省下那次探测与那份流量。
- 可预期的追问:探测失败要不要自动换模型;同一进程里换了模型怎么让缓存失效;探测这一次请求本身的成本怎么算。
Key points
- Do not detect by absence of error - a model without vision was measured inventing an answer, calling a solid red image blue-green
- Do not rely on keywords in the model id or a hand-maintained support list; both expire silently
- Use a verifiable criterion - send a self-generated solid-color image whose answer you know and check the reply, randomizing the color
- Skip detection against offline scripts or stub providers, where it proves nothing
- On fallback send no image, and in the replacement text state only known facts, never a description of the picture
- Make the model say it cannot see and ask the user for words - the worst outcome is a fallback that hides itself
- Cache the result per session, probe only on turns carrying an image, and give the user a manual force-text switch
答题要点
- 探测不能靠「没报错」——实测不支持视觉的模型会编一个答案,把纯红图说成蓝绿色
- 也不能靠模型 id 里的关键字或一张自己维护的支持清单,两者都会静默过期
- 正解是可验证判据:发一张自己生成、答案已知的纯色图,核对它答得对不对;颜色要随机换
- 离线剧本或桩 provider 下跳过探测,那测不出任何东西
- 降级时一张图都不发,说明里只写已知事实,绝不替模型描述图里有什么
- 必须让模型说出「我看不见」并向用户要文字描述——最怕的是假装自己没降级
- 探测结果按会话缓存,只在带图的轮次触发,再给用户一个手动强制降级的开关
D20 Evaluation and Cost: Designing a Benchmark Set, Computing Pass Rate, and Reading Token Usage and Cache Hits
How do you design a benchmark set for a coding agent, and should the grader look at the end state or at the process?怎么给一个 Coding Agent 设计基准集?判分函数该看终态还是过程?
Common in ChinaCommon overseasBasic#benchmark-design#gradingHow to reason about it · think before answering
- This tests whether you have actually assembled a benchmark set. People who only read papers answer with words like coverage and diversity; people who built one start from the shape of the grader, because that is the only thing that decides whether the conclusion is true.
- How to break it down - first the criteria for admitting a task, then the end-state-versus-process question, then how the grader itself gets checked.
- Four criteria for a task - a well-defined initial state (rebuild the sandbox before every attempt, or leftovers from the previous task make results unexplainable); an end state a piece of code can judge true or false; a spread of difficulty including at least one negative task; and offline runnability where possible, otherwise the evaluation degrades into a manual pre-release run.
- The negative task is the one people skip. If every task is fix this, all you measure is willingness to act, and an agent that edits everything it sees scores full marks. And asserting the tests are still red is not enough - the model could have deleted the test file and they would still be red; you also need the untouched files to be byte-for-byte identical.
- The answer is end state. Three reasons - many different routes are equally correct, so grading the process enshrines the one route you happened to think of; a failure inside the process does not mean the task failed (in the task whose starting point is already fixed, the first exact-replace necessarily misses, yet the end state is correct); and worst of all, process grading easily degenerates into taking the model's word for it, and models almost never admit they failed.
- A related discipline - the grader runs the tests itself rather than reusing the agent's own command tool. Judging a system with the system under test means any flaw in the tool layer corrupts the verdict invisibly. The grader also needs a reverse assertion - feed it an input that must fail and confirm it fails, otherwise an always-true grader stays green forever.
- Likely follow-ups - if process metrics do not decide the grade, should you still collect them (yes, they explain rather than conclude); how to score genuinely subjective tasks; how to keep the benchmark from being overfitted.
分析过程 · 先想清楚再作答
- 这题在考「你有没有自己攒过一套基准集」。只读过论文的人会答「多样性、覆盖度」这类词;攒过的人第一句就会说判分函数的形状,因为那是唯一会决定结论真假的东西。
- 怎么拆:先给出题的几条判据,再单独回答终态还是过程,最后说判分器自己怎么被检查。
- 出题四条:每道题有明确的初始状态(跑之前必须清场重建,否则上一题的残留会让结果无法解释);终态必须能被一段代码判真假;难度要有梯度而且必须有负例;能离线跑的尽量离线跑,否则评估最后会退化成上线前手动跑一次。
- 负例这一条最容易被跳过。全是「把它修好」的题,量出来的只是「它敢不敢动手」,一个见什么改什么的 Agent 会拿满分。而负例题光断言「测试还是红的」不够——模型可能把测试文件删了,测试照样红;必须再加一条「不该动的文件逐字节没被动过」。
- 结论是看终态。三条理由:同一件事有很多条路都对,按过程判等于把你想到的那条路当成唯一答案;过程里的失败不等于任务失败(起点已经是好的那道题,第一次精确替换必然失配,但终态达标);最危险的是过程判分很容易退化成「模型说它做完了就算做完」,而模型几乎从不承认自己没做到。
- 还有一条同源纪律:判分器自己跑测试,不借用被测对象的工具。用被测对象的工具去判它自己,工具层一有毛病判分会跟着一起错。判分器也要配反向断言——拿一个应该失败的输入喂给它,确认它真的判失败,否则一个恒真的判分器可以一路绿到底。
- 可预期的追问:过程指标既然不判分那还要不要收(要,它们是解释不是结论);怎么给「答得好不好」这类主观题打分;基准集自己怎么防止被过拟合。
Key points
- Four admission criteria - defined initial state, automatically decidable end state, a spread of difficulty, and at least one negative task
- Rebuild the initial state before every attempt, or leftovers make results unexplainable
- Grade the end state - many routes are correct, in-process failures are not task failures, and process grading degenerates into trusting the model's own report
- A negative task needs a byte-for-byte unchanged assertion, not just still red tests
- The grader runs tests itself rather than through the agent's tools, and needs a reverse assertion so it cannot be always-true
答题要点
- 出题四条:明确的初始状态、可自动判定的终态、有难度梯度、必须有负例
- 每次尝试都从头重建初始状态,否则上一题的残留会让结果无法解释
- 判分看终态:多条路都对、过程失败不等于任务失败、过程判分易退化成「模型说做完了就算做完」
- 负例题不能只断言「测试还是红的」,要加逐字节未改动这条判据
- 判分器自己跑测试不借用被测对象的工具,并且要配反向断言防止它恒真
Which metrics would you use to judge a coding agent, and why is pass rate not enough?你会用哪几个指标衡量一个 Coding Agent 的好坏?为什么通过率不够?
Common in ChinaCommon overseasIntermediate#metrics#cost#cacheHow to reason about it · think before answering
- This tests whether you have actually made decisions from these numbers. Reciting four names is easy; the hard part is saying which one is the conclusion, which ones only explain it, and which one may not exist at all.
- How to break it down - layer them first (one conclusion, three explanations), then say what each answers and cannot answer, then spend time on the cache metric's traps.
- Pass rate is the only conclusion, but alone it misleads. Two runs both at a hundred percent, one averaging three rounds and the other seven and a half, are not the same agent - the second costs more than twice as much and is far likelier to hit the round limit halfway through a real repository. Average rounds answers how smoothly, tokens answer how much.
- Cache hit rate is the only one of the four that may not exist. Two traps - the denominator must be input tokens, not total tokens, because caching only applies to input and a total denominator drifts with how talkative the model was; and an all-zero hit count has two possible causes, genuinely no hits or a gateway that does not return the field, so when code cannot tell them apart it must report unavailable rather than 0.0 percent.
- On cost - only tokens are facts, money is something the operator fills in. Unit prices vary by gateway and over time, so hard-coding them bakes an expiring fact into the code, and the default must visibly distinguish no price configured from zero cost, otherwise a 0.00 reads as free. Cache billing rules differ between providers, so defer to the documentation of whichever gateway you use.
- One engineering point - keep a single ledger. The loop already accounts for usage to enforce its hard limits, so cost statistics should reuse that same object rather than counting again; otherwise the two numbers eventually disagree and nobody can explain why.
- Likely follow-ups - how to turn these metrics into a CI gate; how to judge a change where cost rose but so did pass rate; whether average or p95 rounds is the more useful number.
分析过程 · 先想清楚再作答
- 这题在考「你有没有真的拿这些数做过决定」。背得出四个名词不难,难的是说清哪个是结论、哪几个是解释,以及哪一个可能根本取不到。
- 怎么拆:先分层(一个结论加三个解释),再逐条说它回答什么、解释不了什么,最后专门讲缓存那一条的坑。
- 通过率是唯一的结论,但它单独看会骗人:两次评估都是百分之百通过,一次平均三轮、一次平均七轮半,后者多花一倍多的钱,而且在真实仓库里更容易撞上轮数上限半途而废。所以平均轮数是「顺不顺」,token 是「花了多少」。
- 缓存命中率是这四个里唯一可能不存在的。两个坑:分母必须是输入 token 而不是总 token,因为缓存只对输入生效,拿总量当分母会得到一个随模型这次话多话少乱晃的数;命中数全是 0 有两种可能——真的没命中,或者这个网关不回传这个字段,代码分不清就该报「不可用」而不是 0.0%。
- 成本这一条的口径:只有 token 是事实,钱是使用者自己填进去的。单价随网关与时间变,写进代码等于给代码加一个会过期的事实;默认值必须显式区分「没配单价」与「零成本」,否则一个 0.00 会被当成不要钱。缓存怎么计费各家规则不同,以所用网关的文档为准。
- 工程上还有一条:这些数只记一本账。循环里本来就有一套用量记账(硬上限靠它),成本统计应该复用同一个对象而不是另数一遍,否则迟早出现两处口径对不上、谁也说不清的情况。
- 可预期的追问:怎么把这些指标做成持续集成的门禁;成本涨了但通过率也涨了该怎么判;平均轮数和 p95 轮数哪个更该看。
Key points
- Pass rate is the only conclusion; rounds, tokens and cache hit rate merely explain it
- Two agents both at a hundred percent, one at three rounds and one at seven and a half, differ in cost and in risk of stalling
- Cache hit rate's denominator is input tokens, not total; report unavailable rather than 0.0 percent when the hit count is zero
- Only tokens are facts - prices come from the operator, the default must distinguish unset from zero, and cache billing follows the gateway's own docs
- Keep one usage ledger by reusing the loop's accounting instead of counting again
答题要点
- 通过率是唯一的结论,轮数、token、缓存命中率都是解释
- 同样百分之百通过,平均三轮与平均七轮半不是一个东西:代价与半途而废的风险都不同
- 缓存命中率的分母是输入 token 不是总 token;命中数全为 0 时报「不可用」而不是 0.0%
- 只有 token 是事实:单价由使用者填、默认值要区分「没配」与「零成本」,缓存计费规则以网关文档为准
- 用量只记一本账,复用循环里那套记账,别另数一遍
Results for the same task vary between runs. How do you tell whether a change actually helped, given that noise?同一道题多次运行结果不稳定,你怎么在这种噪声下判断一次改动是不是真的有效?
Common in ChinaCommon overseasDeep dive#variance#ab-testing#evaluationHow to reason about it · think before answering
- This tests statistical instinct and experiment design, and it separates candidates sharply. Most answer run it a few times and average, which solves half the problem - the average itself has spread, and without knowing that spread you cannot interpret a difference.
- How to break it down - measure the noise first, then make the change, then the discipline around the experiment.
- Step one is measuring the baseline's own noise - with the code untouched, run the whole benchmark three to five times and see how far pass rate and rounds move. If it swings eight points on its own, a four-point improvement is just noise pointing the other way. Skip this step and every later comparison is void.
- Step two is the change, and only one change at a time. Alter the prompt and the truncation limit together and a better result tells you nothing about which one helped; next time you carry both forward, possibly including one that hurts.
- Step three is the criterion - the difference must clearly exceed the baseline spread to count. When unsure, add repetitions or add tasks rather than re-reading the same run. Task count is itself part of the noise - a five-task benchmark has a pass-rate granularity of twenty percent and cannot resolve anything smaller than one task.
- A counterintuitive point - near-zero variance in an offline evaluation is not good news. It means the run is not testing the model at all, only your own code. That makes it a good regression test but not a model evaluation. Real evaluation is slow, noisy and costs money per run, which is exactly why the grader and the reporting code should be debugged offline first.
- Likely follow-ups - how to judge a change where both cost and pass rate rose; whether fixing a random seed removes this noise (with most gateways it does not); how to avoid overfitting to the benchmark over time.
分析过程 · 先想清楚再作答
- 这题在考统计直觉与实验设计,区分度很高。多数人会答「多跑几次取平均」,那只解决了一半——平均值本身也有散布,不知道散布多大就没法判断差值。
- 怎么拆:先量噪声,再谈改动,最后谈实验设计上的几条纪律。
- 第一步是量基线自己的噪声:同一套代码原地不动,把基准集连跑三到五遍,看通过率与轮数在多大范围里晃。如果它自己就能晃出八个百分点,那四个点的「提升」只是噪声换了个方向。这一步没做,后面所有对比都不成立。
- 第二步才是改动,而且一次只改一处。同时改了提示词和截断上限,结果变好了你也不知道是哪一处起的作用,下一次会把两处一起带走,其中可能有一处是负作用。
- 第三步是判据:改动带来的差值要明显超过基线的散布才算数;不确定就加大重复次数或者加题,而不是反复盯着同一次结果解读。题目数量本身也是噪声的一部分——五道题的通过率颗粒度是 20%,天然分辨不出小于一题的差别。
- 还有一条反直觉的:离线评估里抖动接近零不是好消息。那说明这一轮根本没在测模型,只在测你自己的代码——它是一个好的回归测试,但不是一次模型评估。真实评估慢、有噪声、每跑一次都要花钱,所以更要先在离线下把判分器与统计代码调对,别拿真实调用去调试自己的报表。
- 可预期的追问:怎么判「成本涨了但通过率也涨了」;固定随机种子能不能消掉这种噪声(多数网关消不掉);怎么防止长期照着基准集调导致过拟合。
Key points
- Measure the baseline's own spread first - run the same code three to five times and see how far pass rate and rounds move
- A change only counts when its difference clearly exceeds that spread; otherwise it is noise pointing the other way
- Change one thing at a time, or you cannot tell which one mattered
- When unsure, add repetitions or tasks - a five-task benchmark resolves pass rate only in twenty-point steps
- Near-zero offline variance means you are testing your code, not the model - a regression test rather than an evaluation
答题要点
- 先量基线自己的散布:同一套代码连跑三到五遍,看通过率与轮数晃多大
- 改动带来的差值必须明显超过那个散布才算数,否则是噪声换了个方向
- 一次只改一处,否则分不清是哪一处起的作用
- 不确定就加重复次数或加题;五道题的通过率颗粒度是 20%,分辨不出更小的差别
- 离线抖动接近零说明没在测模型,只在测自己的代码——它是回归测试不是模型评估
D21 Packaging and Release: a Global Command, a Config Directory, Versioning and Updates — a Twenty-One-Day Retrospective
What is easy to overlook when shipping a command line tool as a globally installable package?把一个命令行工具发布成可全局安装的包,有哪些容易忽略的坑?
Common in ChinaCommon overseasIntermediate#packaging#cli#distributionHow to reason about it · think before answering
- This tests whether you have actually shipped one. People who have not answer "remember the bin and files fields"; people who have open with the real point - locally you run sources, what you ship is build output, and those two paths differ, so you must install it and run it again.
- How to break it down - two items each under "missing from the package", "looking in the wrong place at runtime", and "verifying the wrong way", then finish with a concrete verification chain.
- Missing from the package - a file list that enumerates files instead of directories goes stale the day someone adds a file; the compiler only moves files it compiles, so scripts spawned as subprocesses, templates and static pages never reach the output; and an entry script without a shebang fails because on Unix-like systems a shell, not the runtime, executes it, producing a syntax error that points nowhere near the cause.
- Wrong place at runtime - after a global install, "where the code lives", "which repository the user is working in" and "where personal config lives" are three directories, while during local development they happen to be one, so this class of bug never shows up locally. The rule is to locate bundled assets by walking up from the module's own location, and never to look for user data inside the package.
- Verifying the wrong way - a clean build is not proof it installs. Real verification is pack it, install it into a temporary prefix, then run a version command and one real task from a freshly created empty directory. The temporary prefix keeps the user's global environment untouched; the empty directory is the point, because cwd having nothing to do with the package is exactly what is under test.
- Two more - the version number must have a single source of truth in package metadata, since a duplicated constant makes every bug report carry the wrong version; and decide runtime dependencies deliberately, because dragging a build tool into them is the most common kind of bloat.
- Likely follow-ups - whether build output belongs in version control; line endings and executable bits across platforms; how to run this verification chain in CI.
分析过程 · 先想清楚再作答
- 这题在考「你有没有真的发过一次」。没发过的人会答「记得配入口字段和包清单」,发过的人第一句会是:本地跑的是源码、发出去的是产物,这两条路径不一样,所以必须装完再跑一遍。
- 怎么拆:按「打包时漏了什么」「运行时找错了地方」「验证方式不对」三类各说两条,最后给一条可执行的验证链。
- 第一类,打包时漏东西:包清单按文件枚举而不是按目录,加一个新文件的那天就悄悄过期;编译器只搬它会编译的文件,要被子进程起起来的脚本、模板、静态页一个都不会进产物;入口脚本少了 shebang,类 Unix 上执行它的是 shell 不是运行时,报的是一句指不到病根的语法错误。
- 第二类,运行时找错地方:全局安装之后「代码在哪」「用户在哪个仓库干活」「个人配置在哪」是三个目录,本地开发时它们恰好是同一个,所以这类 bug 本地一次都不会出现。规矩是随包分发的资产从模块自身地址向上找包根,用户的东西一律不从包里找。
- 第三类,验证方式不对:构建没报错不等于装得上。真正的验证是打包、装到一个临时前缀、在一个新建的空目录里跑一次版本命令和一次真实任务。用临时前缀是为了不碰使用者的全局环境,用空目录是因为「当前目录和包毫无关系」正是要验的那件事。
- 还有两条配套的:版本号只能有一个真源(包元数据),代码里另写一个常量会让所有 issue 都带着错的版本;发布前先想清楚运行时依赖,把构建工具拖进运行时依赖是最常见的一种膨胀。
- 可预期的追问:产物要不要进版本库;跨平台的换行与可执行位怎么处理;怎么让这条验证链在 CI 里跑。
Key points
- Core point - locally you run sources but you ship build output, so a clean build is not proof of a working install; install it and run it again
- List directories, not files, in both the package manifest and the asset copy step, or it silently goes stale the day a file is added
- The compiler does not move non-source assets, and the entry script's first line must be a shebang
- After a global install the package root, project root and user directory are three different places; find bundled assets by walking up from the module's own location
- Verification chain - pack, install into a temporary prefix, run the version command and one real task from an empty directory, then clean up without touching the global environment
- One source of truth for the version; never drag a build tool into runtime dependencies
答题要点
- 核心一句:本地跑源码、发出去是产物,构建通过不等于装得上,必须装完再跑一遍
- 包清单与资产复制都要按目录而不是按文件枚举,否则加一个新文件就悄悄过期且不报错
- 编译器不搬非源码资产;入口脚本第一行必须是 shebang
- 全局安装后包根、项目根、用户目录三者分开,随包资产从模块自身位置上溯去找
- 验证链:打包 → 临时前缀安装 → 空目录里跑版本命令与一次真实任务 → 清理,全程不碰全局环境
- 版本号只有一个真源;别把构建工具拖成运行时依赖
How do you decide the layering and override order of configuration, and where should secrets live?配置的分层与覆盖顺序你怎么定?密钥该放哪里?
Common in ChinaCommon overseasIntermediate#configuration#secrets#cliHow to reason about it · think before answering
- This tests whether you can derive the priority order rather than recite it, and whether you have a clear position on where secrets live. Answering only "flags beat env vars beat files" is recitation, with no reason attached.
- How to break it down - state the ordering principle, then two implementation points, then secrets on their own.
- The principle in one line - the narrower the scope, the higher the priority. A flag applies to this one run, an environment variable to this shell, project config to this repository, user config to this machine, and defaults to everything. A narrower scope means the user is being more specific about "just this time, just here". Reversing it produces the symptom "I passed the flag and nothing happened", with no error anywhere.
- Implementation point one - express the layers as an ordered array applied in sequence rather than a chain of conditionals, so adding a layer means inserting an element instead of rearranging logic.
- Implementation point two - every value must remember which layer it came from, and there must be a command that prints all of it. The most common support question about layered config is not "the value is wrong" but "I do not know why it is this value", so printing provenance is not a debug feature, it is what makes the layering usable.
- Be explicit about secrets - they do not go in config files. Project config is committed, user config sits in plaintext in the home directory, and both get carried off by backups, sync folders, screenshots and a casual "send me your config". Secrets come from environment variables or a local file that is explicitly never committed, and above that from a system keychain or a managed secret store.
- One more fork that is easy to get wrong - a secret found in a config file should be rejected with a warning, not silently ignored. Silent ignoring leaves a user who did write a key being told there is none, and they will conclude the tool is broken. Likewise, mask secrets whenever configuration is printed.
- Likely follow-ups - what to do when the same key has different types in two layers; whether a broken config file should stop the tool from starting; how to make team config work for a new hire with zero setup.
分析过程 · 先想清楚再作答
- 这题在考你能不能把优先级推导出来而不是背下来,以及你对密钥的位置有没有明确立场。只答「命令行大于环境变量大于配置文件」是背的,加不上一句为什么。
- 怎么拆:先给排序的判据,再说实现上的两个要点,最后单独说密钥。
- 判据一句话:作用范围越窄的优先级越大。命令行参数只管这一次、环境变量只管这个终端、项目配置只管这个仓库、用户配置只管这台机器、内置默认值管所有情况——范围越窄说明用户越明确地在说「就这一次、就这里」。倒过来排的症状是「我加了参数但没生效」,而且没有任何报错。
- 实现要点一:把层排成一个有序数组依次覆盖,不要写成一串条件判断。加一层只要插一个元素,不用重排任何判断。
- 实现要点二:每个值都要记住自己来自哪一层,并提供一条把它们全摊开的命令。配置分层最常见的支持问题不是「值错了」而是「我不知道它为什么是这样」,所以打印来源不是调试功能,是这套分层可用的前提。
- 密钥的立场要明确:不进配置文件。项目配置要进版本库,用户配置明文躺在家目录里,两者都会被备份、同步盘、截屏和一句「把配置发我看看」顺走。密钥只从环境变量或一个明确不提交的本地文件来,再往上是系统钥匙串或云上的密钥管理。
- 还有一个容易做错的分叉:在配置文件里读到密钥应该拒绝并警告,不是静默忽略。静默忽略会让用户明明写了却一直被告知没有,他会以为工具坏了。同理打印配置时密钥必须打码。
- 可预期的追问:同一个键在两层里类型不同怎么办;配置文件坏了要不要让工具起不来;怎么让团队配置对新人零成本生效。
Key points
- Order by narrowness of scope - flags over environment variables over project config over user config over built-in defaults
- Implement as an ordered array applied in sequence, not a chain of conditionals; adding a layer is inserting an element
- Every value carries its source, and one command prints all sources - a usability prerequisite, not a debug feature
- Secrets stay out of config files - project config is committed and user config sits in plaintext at home, and both leak
- Reject and warn when a secret appears in a config file rather than ignoring it silently, and always mask secrets when printing config
答题要点
- 排序判据是「作用范围越窄优先级越大」:命令行 大于 环境变量 大于 项目配置 大于 用户配置 大于 内置默认值
- 实现成有序数组依次覆盖,而不是一串条件判断;加一层只插一个元素
- 每个值都要带来源,并提供一条打印全部来源的命令——这是可用性前提不是调试功能
- 密钥不进配置文件:项目配置要进版本库,用户配置明文在家目录,都会被顺走
- 读到密钥要拒绝并警告,不能静默忽略;打印配置时必须打码
If you put this hand-built coding agent on your resume, how would you convey its technical substance in three sentences?把这个自己实现的 Coding Agent 写进简历,你会怎么用三句话说清它的技术含量?
Common in ChinaCommon overseasDeep dive#portfolio#communication#agent-engineeringHow to reason about it · think before answering
- On the surface this tests communication; underneath it tests self-assessment - do you know which parts of your own project were hard and which were just legwork. "Built a coding agent" carries zero information, because someone who wired up a framework can say the same sentence.
- How to break it down - one sentence on boundaries and constraints, one on the two or three hardest mechanisms, one on verification and honest limitations. The order matters, because the mechanisms only carry weight once the constraints are on the table.
- Sentence one states the boundary - no framework and no SDK, only an OpenAI-compatible endpoint, with streaming parsing, the tool loop, the approval gate and the protocol all written by hand, and exactly one runtime dependency. The constraint is itself information, because it rules out gluing libraries together.
- Sentence two picks the mechanisms with the most signal rather than listing features. Candidates - hand-written incremental parsing and merging of tool-call fragments, where the index cannot be assumed to start at zero so merging must key off a dictionary; an append-only event log that supports resume, fork and rewind at once; content-addressed snapshots giving file-level rollback plus detection of unrecorded edits; progressive skill loading that reduces the cost of unrelated turns to a single summary line. Pick two or three and attach one reason each.
- Sentence three covers verification and limits - how you proved it runs (an end-to-end self test on an offline script, plus packing it, installing into a temporary prefix and running a real task again) and an honest account of where it falls short of a production tool. Volunteering the limits beats being asked, and being able to name them precisely is itself evidence of depth.
- Be able to name the anti-patterns - a pile of feature nouns, quoting model sizes or benchmark scores, or claiming to be close to some shipped product. All three collapse at the first follow-up question.
- Likely follow-ups - which of those mechanisms did you rewrite once and why; if you could keep only three features which three; what would break first if you used it in anger.
分析过程 · 先想清楚再作答
- 这题表面考表达,实际考自我评估:你知不知道自己做的东西里哪部分难、哪部分只是体力活。答「实现了一个 Coding Agent」信息量为零,因为这句话调一个框架也能说。
- 怎么拆:一句讲边界与约束、一句讲最难的那两三个机制、一句讲验证与诚实的局限。顺序不能反——先说约束,后面的机制才有分量。
- 第一句给边界:零框架零 SDK,只依赖一个 OpenAI 兼容接口,流式解析、工具循环、审批、协议全部手写,运行时依赖只有一个读环境文件的库。约束本身就是信息,它排除了「调库拼起来」这种可能。
- 第二句挑最有区分度的机制,不要罗列功能清单。可选的有:手写增量解析与工具调用分片归并(分片索引的起点不可假设,所以归并必须按索引建字典);只追加的事件日志同时支撑会话恢复、分叉与回退;内容寻址快照做文件级回滚并能识别未记录的改动;渐进披露的技能加载把不相干轮次的开销降到只剩一行摘要。挑两三个,每个都带一句「为什么这么设计」。
- 第三句说验证与局限:怎么证明它真的能跑(离线剧本下的端到端自检、打包后装到临时前缀再跑一次真实任务),以及诚实地说清它和产品级的差距在哪几块。主动说局限比等人问出来强,而且能说清局限本身就是懂行的证据。
- 反面示范要能指出来:堆一串功能名词、引用参数量或跑分、宣称「接近某某产品」。这三种写法都会在追问第一层就塌。
- 可预期的追问:这些机制里哪一个你重写过一次、为什么;如果只能保留三个功能你留哪三个;线上用它时最先会坏在哪里。
Key points
- State constraints before mechanisms - no framework, no SDK, one generic endpoint, one runtime dependency - because the constraint rules out the "glued libraries" reading
- Pick two or three high-signal mechanisms and give one design reason each instead of listing feature nouns
- Candidate mechanisms - merging streamed tool-call fragments where the index start cannot be assumed, an append-only event log serving resume, fork and rewind, content-addressed snapshots, and progressive disclosure for skills
- Third sentence is verification - an offline end-to-end self test plus packing, installing into a temporary prefix and running a real task again
- Volunteer the gap to production tools - editing capability, semantic retrieval, real isolation, unknown error shapes, security boundaries - naming limits precisely is what reads as depth
- Avoid three collapsing patterns - piling up feature nouns, quoting benchmark numbers, or claiming to be close to a shipped product
答题要点
- 先说约束再说机制:零框架零 SDK、只认一个通用接口、运行时依赖只有一个——约束排除了「拼库」这种解释
- 挑两三个有区分度的机制并各给一句设计理由,不要罗列功能名词
- 可选机制:流式分片归并(索引起点不可假设)、只追加事件日志支撑恢复与分叉与回退、内容寻址快照、渐进披露的技能加载
- 第三句讲验证:离线端到端自检 + 打包装到临时前缀后再跑一次真实任务
- 主动说清与产品级的差距(编辑能力、语义检索、真实隔离、未知错误形状、安全边界),说得出局限才显得懂
- 避开三种塌方写法:堆功能名词、引用跑分、宣称接近某个产品
Agent Evals and Observability in 7 Days: Turning "Seems Fine" Into Numbers You Can Reproduce
D1 Why You Cannot Ship an Agent on "I Tried It a Few Times"
You run the same task five times and get three passes and two failures. How do you report this agent's quality to your manager?同一个任务跑五次,三次对两次错。你会怎么向老板汇报这个 Agent 的质量?
Common in ChinaCommon overseasIntermediate#evaluation#metrics#non-determinismHow to reason about it · think before answering
- This tests whether you can characterize a non-deterministic system, not whether you can divide. Answering 'a 60% success rate' earns half credit at best: the number is arithmetically right but carries no confidence interval and no context.
- Start with sample size. Three out of five is a point estimate with a very wide spread; five trials cannot distinguish a true rate of 40% from one of 80%. The honest report is 'we only have five trials, this number is not yet decision-grade'.
- Then separate two different questions: the probability of succeeding at least once, and the probability of succeeding every time. With a human reviewing the output, a 60% per-run rate means three attempts will almost certainly yield something usable. If it runs unattended, three consecutive successes happen with probability 0.6 cubed, about 21.6% - broken most of the time. The same 60% supports opposite conclusions in the two settings.
- Give a next action rather than stopping at the number: raise the trial count to something decision-grade, read the transcripts of both failures and attribute them, and freeze this task into the benchmark set so the next change has a baseline.
- Close on comparability: the number must carry the model version, prompt version, task-set version, and random seed. Without those, next week's number cannot be compared with this one.
- Expected follow-up: how many trials are enough? There is no fixed answer - it depends on the difference you need to detect. Separating 60% from 65% needs far more trials than separating 60% from 90%. State the question first, then size the sample.
分析过程 · 先想清楚再作答
- 这题考的是「会不会把一个非确定性系统的表现讲清楚」,不是算术。张口就报「成功率 60%」的只能拿一半分——那个数字本身没错,但它既没有置信度,也没有说清适用场景。
- 第一步先把样本量的问题摆出来:五次里成三次,60% 这个点估计的波动范围很大。真实成功率是 40% 还是 80%,五次样本根本分不开。所以汇报时要说的是「目前只有五次样本,这个数字还不能用来做决策」,而不是直接把 60% 报上去。
- 第二步要区分两个问题:至少成一次的概率,和每次都成的概率。如果这个 Agent 后面有人 review,60% 的单次成功率意味着跑三次几乎一定能拿到一个可用结果;如果它是自动执行的,那么连成三次的概率只有 0.6 的三次方,约 21.6%,等于绝大多数时候是坏的。**同一个 60% 在两个场景下的结论完全相反。**
- 第三步给出下一步动作,而不是停在报数:把样本量加到足够判断的规模、把两次失败的轨迹读一遍做归因、并把这条任务固化进基准集,这样下次改动才有得比。
- 最后补一句可比较性:这个数字要带上模型版本、提示词版本、任务集版本和随机种子,否则下周再报一个数,没人知道是系统变了还是环境变了。
- 可预期的追问是「那你要跑多少次才够」。答案不是一个固定数字,而是取决于你要分辨多大的差异:想区分 60% 和 65% 需要的样本量,远大于区分 60% 和 90%。先说清楚要回答什么问题,再定样本量。
Key points
- Lead with sample size: five trials cannot pin the true rate to a useful interval.
- Distinguish 'at least once' from 'every time' and map each to a deployment scenario.
- For unattended execution compute the consecutive rate: 0.6 cubed is about 21.6%, a very different conclusion.
- Propose next actions: more trials, read both failure transcripts, freeze the task into the benchmark set.
- Always report model, prompt, task-set version and random seed, or the number is not comparable.
答题要点
- 先说样本量不足:五次样本无法把真实成功率定位到一个有用的区间。
- 区分「至少成一次」与「每次都成」,并说明两者适用于不同场景。
- 自动执行场景要算连续成功率:0.6 的三次方约 21.6%,结论与 60% 完全不同。
- 给下一步动作:加样本、读失败轨迹做归因、把任务固化进基准集。
- 报数必须带模型版本、提示词版本、任务集版本与随机种子,否则不可比。
Why should you evaluate an agent against the final state of the environment rather than what its last message says?为什么评估 Agent 时要看环境的最终状态,而不是看它最后一条回复说了什么?
Common in ChinaCommon overseasBasic#evaluation#graders#outcomeHow to reason about it · think before answering
- This looks easy; the discriminator is whether you can name the concrete mechanism by which a correct-sounding reply accompanies no action, rather than just saying 'models hallucinate'.
- The mechanism: the model has seen enormous amounts of customer-service dialogue and knows exactly what to say after a refund request. It can therefore produce 'your refund has been processed, expect it in three business days' without ever calling the refund tool. Textually that sentence is identical to the one it produces when the refund really happened.
- So a text-matching grader is blind here - it cannot separate the two cases. Checking the environment can: whether a row exists in the refunds table is a binary, settled fact.
- This is also the cleanest illustration of 'if code can judge it, do not ask a model'. Reading one database row is fast, cheap and objective; asking a second model to judge the reply's truthfulness is expensive and injects fresh uncertainty.
- Add the more serious consequence: a text grader is not merely wrong, it is systematically optimistic. An agent that learned to talk well without acting scores highly, and optimizing against that score trains it to talk even better.
- Expected follow-up: what about open-ended outputs such as a research report? Layer it - whatever can be reduced to state (do the cited links resolve, are the required points covered) stays with code, and only the genuinely subjective remainder goes to a model judge, which is Day 3.
分析过程 · 先想清楚再作答
- 这题看着简单,区分度在于能不能举出「回复正确但事情没做」的具体机制,而不是只说一句「模型会幻觉」。
- 机制是这样的:模型在训练里见过大量客服对话,它非常清楚在「用户要求退款」之后应该说什么。于是它可以在**完全没有调用退款工具**的情况下,流畅地说出「已为您办理退款,预计三个工作日到账」。这句话与真正退了款时说的那句,在文本层面可以一模一样。
- 所以基于文本比对的评分器在这里是失效的:它判不出这两种情况的区别。而查环境可以——退款记录表里有没有这一行,是一个二值的、确定的事实。
- 反过来说,这也是「能用代码判就别用模型判」这条原则的最佳例证:查一行数据库记录既快又便宜又客观,而让另一个模型去读回复判断真假,既贵又会引入新的不确定性。
- 还要补一个更严重的后果:文本评分器不只是判错,它是**系统性地偏向乐观**。一个学会了说漂亮话但不干活的 Agent,在文本评分下会拿高分。如果你再拿这个分数去做优化,就是在训练它更会说漂亮话。
- 可预期的追问是「那开放式的回答怎么办,比如一份研究报告」。答案是分层:能落到结果态的部分(引用的链接是否真实存在、要覆盖的要点是否都在)仍然用代码判,剩下真正主观的部分才交给模型裁判,那是 D3 的内容。
Key points
- A model can produce a reply identical to the successful case without calling any tool.
- Text comparison cannot separate the two; checking state can, because it is a binary fact.
- Best illustration of 'prefer code graders': faster, cheaper, and objective.
- Text graders are systematically optimistic; optimizing against them rewards better-sounding lies.
- For open-ended output, layer it: state-checkable parts to code, the subjective remainder to a model judge.
答题要点
- 模型能在不调用工具的情况下说出与真正执行时一模一样的回复。
- 文本比对判不出这两种情况,查环境可以——它是二值的确定事实。
- 这是「能用代码判就别用模型判」的最佳例证:更快、更便宜、更客观。
- 文本评分器会系统性偏向乐观,拿它做优化等于训练模型更会说漂亮话。
- 开放式输出要分层:可落到状态的用代码判,剩余主观部分才交给模型裁判。
When should you report pass@k, and when must you report pass^k? Give an example of each and state the cost.什么场景该用 pass 的 at k,什么场景必须用 pass 的 k 次方?各举一个例子并说明代价。
Common in ChinaCommon overseasIntermediate#evaluation#metrics#pass-at-kHow to reason about it · think before answering
- This tests whether you match metrics to product shape. Reciting definitions earns nothing; give the test: is there a human backstop?
- With a backstop, use pass@k. Code completion or generating image candidates: produce ten, a human picks one, and the interaction succeeds if any one of them works. Reporting pass@1 here badly understates the system's value.
- Without a backstop, pass^k is mandatory. Automated refunds or automated ops commands: nobody checks each one, and a single error is real money or a real incident. The question is not 'can it succeed' but 'will it ever fail'.
- State the costs. pass@k hides instability: a 30% agent scores 97% at pass@10, which looks great but means users retry three times on average. pass^k is harsh - it collapses as k grows, and teams may dismiss it as unachievable and stop tracking it.
- In practice report both, and always label k. A report with only one of them is incomplete, and pass^k without a stated k is not a number at all.
- Expected follow-up: how do you choose k? From real usage. Set k for pass@k to how many times users actually retry, and k for pass^k to how many consecutive runs the business requires to be clean. Do not default to 10.
分析过程 · 先想清楚再作答
- 这题考的是「指标与产品形态的匹配」。只背定义拿不到分,要给出判据:**后面有没有人兜底。**
- 有人兜底的场景用 pass@k。典型例子是代码补全或生成图片候选:一次生成十个方案,人来挑一个,只要十个里有一个能用,这次交互就是成功的。这时候报 pass@1 会严重低估系统的实际价值。
- 没人兜底的场景必须用 pass 的 k 次方。典型例子是客服自动退款、自动执行运维命令:没有人逐条检查,错一次就是一笔真钱或一次事故。这时候你关心的不是「它能不能做对」,而是「它会不会有一次做错」。
- 代价要说清楚。pass@k 的代价是它会掩盖不稳定性:一个成功率 30% 的 Agent 在 pass@10 下能拿到 97%,看起来很好,但它意味着用户平均要试三次以上。pass 的 k 次方的代价是它非常严厉,k 稍微一大数字就塌下去,容易让团队觉得「怎么努力都没用」而放弃这个指标。
- 所以实践里两个一起报,并且明确标注 k 是多少。只报一个的评估报告都是不完整的,报的时候不写 k 更是没有意义——脱离 k 的 pass 的 k 次方不是一个数。
- 可预期的追问是「那 k 取多少」。答案是按真实使用形态取:用户平均会重试几次,就把 pass@k 的 k 设成几;业务要求连续多少次不出错,pass 的 k 次方就取几。不要随手取一个 10。
Key points
- The test is whether a human backstop exists: reviewed output takes pass@k, unattended execution requires pass^k.
- Backstopped: code drafts, candidate generation. Unattended: automated refunds, automated ops.
- pass@k hides instability - a 30% agent reports 97% at k equals 10.
- pass^k is harsh and collapses as k grows, so teams tend to abandon it.
- Report both with k labeled, and choose k from real retry behavior or the business continuity requirement.
答题要点
- 判据是后面有没有人兜底:有人 review 用 pass at k,无人值守用 pass 的 k 次方。
- 有兜底例子:代码草稿、生成候选;无兜底例子:自动退款、自动运维。
- pass at k 的代价是掩盖不稳定性:30% 的 Agent 在 k 等于 10 时能报到 97%。
- pass 的 k 次方的代价是过于严厉,k 一大就塌,容易被团队放弃。
- 两个一起报并标注 k;k 要按真实重试次数或业务连续性要求来取。
A newly written evaluation task has a 0% pass rate over one hundred trials. What is your first reaction?一个新写的评估任务,跑一百次通过率是零。你的第一反应是什么?
Common in ChinaCommon overseasIntermediate#evaluation#debugging#task-qualityHow to reason about it · think before answering
- This is a trap question about whether you will suspect your own evaluation. Answering 'the agent cannot do it, go optimize the model' starts in the wrong place.
- The correct first reaction is that the task itself is probably broken. A hundred straight zeros is an extreme signal - even a hard but solvable task usually gets lucky at least once in a hundred. Zero looks like a wall, not a slope.
- Debug from the evaluation side toward the model side. Start with grading: a mistyped expected value, a strict equality comparison on floats, a case or whitespace mismatch. These are extremely common - one brittle string comparison can fail a perfectly correct answer.
- Then the task description: is it ambiguous enough that the agent solved a different problem, or does it reference something absent from the environment, such as an order id never seeded in?
- Then reproducibility: does the task contain randomness that changes the correct answer each run while the grader compares against one fixed answer?
- Only last comes 'the agent genuinely cannot do it'. The standard way to establish that is a reference solution - do the task correctly by hand, feed it to the grader, and see whether it passes. If the reference solution fails, the problem is one hundred percent in the evaluation. That practice is the core of Day 2.
- Expected follow-up: what about a task that passes 100% immediately? Usually the task is too easy or the criterion too loose; it carries no information and needs fixing too.
分析过程 · 先想清楚再作答
- 这题是个陷阱题,考的是「会不会怀疑自己的评估」。回答「说明 Agent 做不到这个任务,要去优化模型」的,方向就错了。
- 正确的第一反应是:**这条任务本身多半是坏的。** 一百次全零是个极端信号——即便是很难的任务,如果它确实可解,一百次里通常会蒙对至少一次。全零更像是一堵墙,而不是一个斜坡。
- 排查顺序应该是从评估侧往模型侧走。先看判分:是不是判据写错了,比如期望值拼错、浮点数做了严格相等比较、大小写或空格不一致。这类问题非常常见,一个字符串比对写死就能让一个完全正确的答案判失败。
- 再看任务描述:是不是有歧义,导致 Agent 理解成了另一件事;是不是依赖了环境里不存在的东西,比如引用了一个没有被 seed 进去的订单号。
- 然后看可复现性:任务里有没有随机成分,导致每次的正确答案都不一样,评分器却拿着一个固定答案在比。
- 最后才是「确实是 Agent 做不到」。而验证这一点的标准做法是写一个参考解——人工把这条任务正确地做一遍,喂给评分器,看它判不判通过。参考解都过不了,那 100% 是评估的问题。这个动作是 D2 的核心内容。
- 可预期的追问是「反过来呢,通过率一上来就是 100%」。那通常说明题目太简单或者判据太松,这条任务提供不了任何信息,同样需要修。
Key points
- Suspect the task first, not the model.
- A hundred straight zeros is extreme: a solvable hard task usually succeeds at least once.
- Debug evaluation-side first: broken grading, ambiguous description, missing environment fixtures, irreproducible randomness.
- Strict equality comparison on floats or strings is the most common grading defect.
- Validate with a reference solution: if a hand-crafted correct answer fails the grader, the fault is in the evaluation.
答题要点
- 第一反应应该是怀疑任务本身,而不是去优化模型。
- 一百次全零是极端信号:真正可解的难任务通常会蒙对至少一次。
- 排查顺序从评估侧到模型侧:判分写错、任务描述有歧义、依赖了环境里没有的东西、随机性不可复现。
- 严格相等比较(尤其是浮点与字符串)是最常见的判分缺陷。
- 用参考解验证:人工做对一遍喂给评分器,过不了就一定是评估的问题。
D2 Benchmark Sets: Turning the Incidents You Already Had Into Reproducible Tasks
Your manager gives you two days to build an evaluation for an agent that is already in production. Where do you get the test tasks?老板让你两天内给一个已上线的 Agent 建评估,你会从哪里找测试任务?
Common in ChinaCommon overseasIntermediate#evaluation#golden-set#task-designHow to reason about it · think before answering
- This tests whether you can mine existing field evidence, not your creativity. Answering 'run a brainstorming session' earns half credit: brainstormed cases are exactly the ones you already thought of, and therefore mostly already handled.
- Start with sources. A production system ships with four free question banks: support tickets and complaints (each one is a human label from a real user), failure logs where the agent threw or a tool errored, escalation-to-human transcripts (every handoff means it could not finish), and the concrete bad example behind each hotfix or rollback.
- Then the translation method. Turning a ticket into a task requires three things: a pinned initial state (orders, inventory, balances written into the task's own seed rather than read from live data), the user's actual words as input, and a criterion expressed as environment state (does a refund row exist, is the amount right) rather than something a human must read and judge.
- Then scale and cadence: you do not need hundreds. Twenty to fifty tasks drawn from real failures are enough to start. In two days, finishing twenty tasks with paired negatives and one clean baseline run beats drafting two hundred.
- Then validation: before handing it over, run a reference solution across every task to confirm each one is solvable and correctly paired with its grader. Skip this and perhaps a third of your two days' output is broken tasks, and broken tasks produce numbers that mislead every later decision.
- Expected follow-up: what if there are no tickets? Fall back to sampled production traffic - label a batch pass/fail by hand and draft tasks from the failures. The invariant holds: tasks must come from the real distribution, not from imagination.
分析过程 · 先想清楚再作答
- 这题考的是「会不会用已有的现场证据」,不是考创造力。回答「组织团队头脑风暴一批用例」只能拿一半分——那批用例恰好是你已经想得到、因而多半已经处理好的场景。
- 第一步先说清来源。一个已上线的系统自带四座免费题库:工单与用户投诉(每一条都是真实用户做的一次人工标注)、Agent 抛异常与工具报错的失败日志、转人工的会话记录(每一次转人工都意味着它没搞定)、以及历次热修与回滚背后的那个具体坏例子。
- 第二步说翻译方法:一条工单要变成一条任务,必须补齐三样东西——锁死的初始状态(订单、库存、账户余额,全部写进任务自带的 seed,不能依赖当天的真实数据)、用户的原话当输入、以及一个落在结果态上的判据(退款记录有没有、金额对不对),而不是「回复得体」这种要靠人读的判据。
- 第三步给规模和节奏:不必等攒够几百条,二十到五十条来自真实失败的任务就足够开工。两天的时间里,把这二十条写完、配上反向任务、跑通一次基线,比写出两百条草稿有用得多。
- 第四步补上验证:交付之前先用参考解跑一遍,确认每条任务本身可解、评分器配对正确。跳过这一步,你两天的成果里可能有三分之一是坏题,而坏题产出的分数会误导后面所有决策。
- 可预期的追问是「线上没有工单怎么办」。那就退到灰度日志:采样真实请求,人工标注一批通过与不通过,再从不通过的那批里出题。关键点不变——**任务要来自真实分布,而不是来自想象**。
Key points
- Four ready-made sources: tickets, failure logs, human escalations, and the bad example behind each hotfix.
- Brainstormed cases cover what you already anticipated, so they carry the least information.
- Translation requires a pinned seed state, the user's own words, and an outcome-state criterion.
- Twenty to fifty tasks from real failures are enough to start; do not wait to accumulate hundreds.
- Before shipping, run a reference solution to confirm every task is solvable and correctly graded.
答题要点
- 四个现成来源:工单与投诉、失败日志、转人工记录、历次热修与回滚。
- 头脑风暴出来的用例覆盖的是你已经想得到的场景,价值最低。
- 翻译成任务要补齐:锁死的初始状态、用户原话、落在结果态上的判据。
- 二十到五十条真实失败任务就足够开工,不必等攒够几百条。
- 交付前用参考解跑一遍,确认任务可解且评分器配对正确。
What is one-sided optimization? Give an example of where a suite containing only positive cases drives a system.什么叫单向优化?举例说明只写正向用例会把系统带到什么地方。
Common in ChinaCommon overseasIntermediate#evaluation#golden-set#negative-testsHow to reason about it · think before answering
- The dividing line: people who answer 'incomplete coverage' have not been bitten by this; people who answer 'it skews the optimization direction' have. The defect is not a missing scenario - the evaluation is actively rewarding wrong behavior.
- Spell out the mechanism. Suppose the suite contains only positive tasks of the form 'refund when a refund is due'. One policy then scores a perfect result: refund everyone. An extra refund costs nothing in this suite, while every missed refund costs a point. So as soon as anyone tunes against the score, the system slides steadily toward a lower refusal threshold.
- The crucial part is that this defect is invisible on positive tasks - there it earns points. It never shows up as a falling number in the report; everything looks fine until finance notices the refund total.
- The fix is pairing: every positive task gets a negative twin that tests 'it did not act when it should not'. In-window order refunded pairs with out-of-window order refused; normal order refunded pairs with an already-refunded order that must be escalated instead. Pairing also forces you to pin down the boundary value.
- The principle is general. Testing only 'catches spam' yields a classifier that marks everything as spam; testing only 'blocks attacks' yields a filter that blocks legitimate traffic. Any one-sided criterion pushes the system to that extreme.
- Expected follow-up: what ratio? There is no universal number, but a workable floor is one negative per positive, with extra negatives for capabilities where acting wrongly costs far more than failing to act - refunds, deletions, sends, payments.
分析过程 · 先想清楚再作答
- 这题的分水岭是:答「覆盖不全」的人没被这件事咬过,答「优化方向被带偏」的人被咬过。缺陷不是漏测了某个场景,而是**评估在主动奖励一个错误的行为**。
- 机制要讲清楚。假设基准集里全是「该退款时退了」这类正向任务。此时有一个策略能拿满分:见谁都退款。因为每一次「多退了一笔」在这套评估里不扣分,而每一次「该退没退」都扣分。于是只要有人按分数调优,系统就会稳定地朝「降低拒绝门槛」滑过去。
- 关键在于这个缺陷在正向任务上是**看不见**的——它在那边反而加分。所以它不会在评估报告里表现为一个下降的数字,而是表现为一切正常,直到财务发现退款金额异常。
- 解法是配对:每一条正向任务都要有一条反向任务,测「不该做的时候确实没做」。有效期内的订单该退,配一条超出有效期的不能退;正常订单该退,配一条已经退过款的不能再退。配对还有个额外好处——它逼你把边界值写清楚。
- 这条原则不限于 Agent。只测「能识别垃圾邮件」会得到一个把所有邮件都判成垃圾的分类器,只测「能拦住攻击」会得到一个把正常请求也拦掉的防护。**任何单向的评价标准都会把系统推到那一端的极端。**
- 可预期的追问是「正反比例多少合适」。没有普适数字,但一个可操作的下限是:每一条正向任务至少配一条反向任务;对那些误做代价远高于漏做的能力(退款、删除、发送、支付),反向任务应该更多。
Key points
- One-sided optimization means the suite rewards only one direction, pushing the system to that extreme.
- With only positive refund cases, 'refund everyone' scores perfectly: extra refunds are free, missed ones cost.
- The defect is invisible on positive tasks, so the report never dips until the business notices.
- Fix with paired negatives that test inaction, which also forces the boundary value to be pinned down.
- Capabilities where wrong action costs more than inaction deserve extra negative cases.
答题要点
- 单向优化指评估只奖励一个方向的行为,从而把系统推向那一端的极端。
- 只测正向退款用例时,「见谁都退款」能拿满分,多退不扣分、漏退扣分。
- 这个缺陷在正向任务上看不见,报告不会下降,直到业务侧发现异常。
- 解法是正反配对,反向任务测「不该做的时候确实没做」,并逼出边界值。
- 误做代价远高于漏做的能力(退款、删除、支付)应该配更多反向任务。
What makes an evaluation task well-formed, and how do you verify the task itself is not broken?一条评估任务应该满足什么条件才算合格?你怎么验证它本身没写错?
Common in ChinaCommon overseasDeep dive#evaluation#task-quality#reference-solutionHow to reason about it · think before answering
- This tests whether you treat the task as an artifact that itself needs testing. Most candidates only discuss what tasks should cover; the interviewer wants to hear how you prove a task is correct.
- Give one criterion: two domain experts, looking independently at the same run, reach the same pass-or-fail conclusion. Its virtue is that you can actually perform it - have two people judge, and disagreement means ambiguity.
- Disagreement usually comes from three places: a criterion that is not binary ('declines politely' versus 'produces no refund row and escalates'); an input that is not pinned ('today' or 'recently' makes the same task behave differently on different dates); or multiple legitimate answers (when both refunding and escalating are acceptable, the two experts split). Anchoring the criterion in environment state makes agreement nearly free.
- Then the verification method, which is the heart of the question: introduce a reference solution - a deterministic fake agent with the correct policy hard-coded - and run it across every task. If it fails, either the task is wrong or the grader is mismatched. Either way that task must not judge any agent until it is fixed.
- Guard the other end too: a task everyone passes is equally broken. Add two obviously wrong degenerate agents, one that always acts and one that never acts. If all three pass, the task has no discriminating power. The sneakiest case is a task whose order id is absent from the seed: nobody finds it, nobody acts, and the expectation is exactly to not act - a permanent perfect score carrying zero information.
- Expected follow-up: may the reference solution read the task's positive/negative label? No. A reference that peeks verifies only that you copied the label correctly, not that the task is solvable under the real policy.
分析过程 · 先想清楚再作答
- 这题考的是「有没有把任务当成一个需要被测试的工件」。多数人只谈任务该覆盖什么,而面试官想听的是你怎么证明这条任务是对的。
- 先给判据,而且只给一条:**两位领域专家分别独立看一次运行记录,会给出同一个通过或不通过的结论。**这条判据的好处是它可以真的去做——找两个人各判一遍,不一致就说明有歧义。
- 不一致通常出在三处:判据没写死(「礼貌地拒绝」不是二值事实,「不产生退款记录并升级人工」才是);输入没锁死(任务里出现「今天」「最近」,不同日期跑出不同结果);有多个合法解(既可退款又可升级人工时,两位专家会各站一边)。把判据落在结果态上,一致性基本是白送的。
- 再说验证手段,这是本题的核心:引入一个**参考解**,把正确规则写死在代码里的确定性假 Agent,逐条跑一遍。参考解过不了,只有两种可能——任务本身写错了,或者评分器和任务没配对上。无论哪种,这条任务在修好前都不能用来评判任何 Agent。
- 还要防另一头:**一条谁都能通过的任务同样是坏题**。做法是再加两个明显错误的退化解(一个什么都做,一个什么都不做)。三个全过,说明这条任务没有区分度。最隐蔽的一类是任务提到的订单号在初始状态里根本不存在,于是谁都查不到、谁都不动手,而期望恰好就是别动手——它永远满分,也永远没信息。
- 可预期的追问是「参考解能不能读任务上的正反标记」。不能。偷看了标记的参考解验证的只是「我抄对了答案」,而不是「这条任务在真实规则下有解」。
Key points
- Single criterion: two experts judging the same run independently reach the same verdict.
- Three sources of ambiguity: non-binary criteria, unpinned relative-time inputs, multiple valid answers.
- Anchoring criteria in environment state makes inter-rater agreement nearly automatic.
- Run a reference solution per task: failure means a broken task or a mismatched grader, so quarantine it.
- Add two degenerate agents to check discriminating power: if all three pass, the task carries no information.
答题要点
- 唯一判据:两位领域专家独立看同一次运行,会给出同一个通过或不通过。
- 歧义三大来源:判据不是二值事实、输入含相对时间未锁死、存在多个合法解。
- 判据落在结果态上(有没有那条记录、金额对不对),一致性基本是白送的。
- 用参考解逐条跑:过不了说明任务写错或评分器没配对,修好之前不能用。
- 再用两个退化解查区分度:三个全过说明这条任务谁都能过,没有信息量。
What pass rates do you expect from a capability suite versus a regression suite, and why are they different?能力评估和回归评估的通过率,你分别期望它们是多少?为什么不一样?
Common in ChinaCommon overseasIntermediate#evaluation#capability-vs-regression#reportingHow to reason about it · think before answering
- This tests whether you know the two suites answer different questions. Reciting 'capability measures ability, regression measures decay' earns nothing; state the expected values and what a drop means in each.
- A capability suite answers 'what can it do' and should start at a low pass rate. A new capability suite that opens at 95% is too easy: there is no headroom, so it cannot point you anywhere. Thirty to fifty percent is a healthy start, climbing over iterations.
- A regression suite answers 'can it still do what it used to do' and should sit near 100% indefinitely. Its tasks come from fixed production incidents and stable core paths, so a drop means regression - someone broke something that was already fixed - and should block the merge.
- The alerting semantics are therefore opposite: a low capability score is the normal state, a low regression score is an incident. Merging them into one total loses both properties - two regression failures drown among a dozen capability failures, and a rising total cannot tell you whether new capability landed or an old bug got fixed.
- Implementation is cheap: tag each task with its set and print two lines in the report. When a task matures and passes consistently it graduates from capability to regression, and that graduation is precisely what 'this capability is done' means.
- Expected follow-up: what threshold for regression? Not a fixed percentage but 'no drop against the previous baseline', with tolerance for noise from non-determinism. How to judge and when to re-run is the Day 6 gating material.
分析过程 · 先想清楚再作答
- 这题考的是「知不知道这两类评估回答的是两个不同的问题」。只背「能力评估测能力、回归评估测退化」拿不到分,要能说出期望值和它们各自的报警含义。
- 能力评估回答「它能做到什么」,应该**从低通过率起步**。一套新的能力评估一上来就 95 分,说明题出得太简单,它没有爬坡空间,也就没法告诉你下一步该往哪走。合理的起点是三到五成,随着迭代慢慢往上爬。
- 回归评估回答「它还能做到它以前做得到的事吗」,应该**长期贴近 100%**。它的任务来自修过的线上故障和已经稳定的核心路径,掉下来就是退化,就是有人改坏了修好过的东西,应该直接拦住合并。
- 所以两者的报警语义正好相反:能力集的分数低是**正常状态**,回归集的分数低是**事故**。把它们混进一个套件报一个总分,这个数字会同时失去两种能力——回归的两条失败被能力集的十几条淹没(不能报警),分数涨了也说不清是新能力上去了还是回归修好了(不能指方向)。
- 落地做法很轻:给每条任务打一个集合标签,报告分两行出。一条任务成熟并稳定通过之后,可以从能力集迁到回归集——这个迁移本身就是「这个能力做完了」的定义。
- 可预期的追问是「回归集应该设多少阈值」。不是一个固定百分比,而是「相对上一次基线不许下降」,并且要能容忍非确定性带来的噪声——具体怎么判、怎么重跑,是 D6 门禁那一天的内容。
Key points
- Capability suites start low (thirty to fifty percent); an immediate perfect score means the tasks are too easy.
- Regression suites sit near 100%, built from fixed incidents and stable core paths.
- Their alerting semantics are opposite: a low capability score is normal, a low regression score is an incident.
- Merging them into one total destroys both alerting and direction, as the two failure kinds mask each other.
- Implement with a set tag per task and two report lines; graduate stable tasks from capability into regression.
答题要点
- 能力评估从低通过率起步(三到五成),一上来满分说明题太简单、没有爬坡空间。
- 回归评估长期贴近 100%,任务来自修过的故障与稳定核心路径。
- 两者报警语义相反:能力集分数低是正常,回归集分数低是事故。
- 混成一个总分会同时失去报警能力与方向感,两类失败互相掩盖。
- 落地是给每条任务打集合标签、报告分两行;任务稳定后从能力集迁进回归集。
D3 Let a Model Judge, Then Put the Judge Through an Exam
When would you refuse to use a model as the judge and reach for code or a human instead? State your decision order.什么情况下你会拒绝用模型当裁判,改用代码或人工?说出你的判断顺序。
Common in ChinaCommon overseasIntermediate#evaluation#graders#llm-as-judgeHow to reason about it · think before answering
- This tests selection discipline, not model knowledge. Jumping straight to prompt design misses the point: the interviewer wants to hear when you would NOT use a model judge.
- There is one ordering rule: if code can decide it, never hand it to a model. Whenever the criterion reduces to a settled fact, use code. Whether a refund happened is one database row; whether a file exists is one stat call; whether a status code matches is one comparison. Deterministic, free, instant.
- Only genuinely open-ended criteria reach the model tier: was it explained clearly, is the tone right, did it invent material that was not in the source. There is no fact to look up, so code cannot decide it.
- Human grading is the last tier, and it is not for running the full suite. Its one correct use is judging the judge: a few dozen human labels calibrate the model judge, and the model judge then runs at scale. Doing it the other way round spends the most expensive resource on the cases that need judgment least.
- State the cost in both directions - that is where candidates separate. Using a model where code would do buys you a noisy verdict that well-phrased lies can pass: an agent that calls no tool and merely says 'your refund has been processed' can score highly. Using code where a model is needed forces you to rewrite the criterion as keyword matching, so the grader starts measuring wording instead of quality, and the agent gets optimized toward hitting keywords nobody can read.
- Expected follow-up: what if half of an open-ended output is machine-checkable? Layer it rather than choosing. Whatever reduces to state goes to code (do the cited links resolve, are the required points present) and only the subjective remainder goes to the model. Attach both graders to the same task, each owning its half.
分析过程 · 先想清楚再作答
- 这题考的是选型纪律,不是模型知识。上来就聊提示词怎么写的,方向已经偏了——面试官想听的是你在什么时候**不**用它。
- 判断顺序只有一条:**能用代码判的绝不交给模型**。判据只要能落到一个确定的事实上,就该用代码。退款有没有发生是一行数据库记录,文件有没有生成是一次 stat,接口返回码对不对是一次比较,这些一律代码判——确定、免费、瞬时。
- 只有当判据本身是开放式的,才轮到模型:讲清楚了没有、语气合不合适、有没有编造材料里没有的内容。这类判据没有可查的事实,代码判不了。
- 人工型是最后一档,而且**不是拿来跑全量的**。它的正确用法只有一个:当裁判的裁判。用几十条人工标注去校准模型裁判,然后让模型裁判去跑全量。反过来做就是把最贵的资源浪费在最不需要判断力的地方。
- 两个方向的代价要分别说清楚,这是区分度所在。**该用代码却用了模型**:你花钱买了一个会抖、会被漂亮话骗过去的判定——一个什么工具都不调、只会说「已为您办理退款」的 Agent,在模型裁判下可能拿高分。**该用模型却用了代码**:为了让代码判得了,你会把判据改成关键词匹配,于是评分器考的是措辞而不是质量,Agent 会朝这个方向被优化,最后你得到一个句句踩关键词、没人看得懂的系统。
- 可预期的追问是「开放式输出里有一半能用代码判怎么办」。答案是**分层而不是二选一**:能落到结果态的部分交给代码(引用的链接解不解析得开、必须覆盖的要点在不在),剩下真正主观的部分才交给模型。同一个任务上挂两个评分器,各管各的那一半。
Key points
- The order is fixed: never give a model what code can decide; humans only calibrate the other two.
- If the criterion reduces to a settled fact, use code: outcome state, fields, status codes - deterministic and free.
- Model where code would do: a noisy, paid verdict that an agent producing only nice words can pass.
- Code where a model is needed: the criterion degrades into keyword matching and the grader measures wording, not quality.
- Layer open-ended output instead of choosing: checkable parts to code, subjective remainder to the model.
答题要点
- 顺序是固定的:能用代码判的绝不交给模型,人工只用来校准前两者。
- 判据能落到确定事实上就用代码:结果态、字段、返回码,确定且免费。
- 该用代码却用模型:判定会抖、会花钱、会被不干活只说漂亮话的 Agent 骗过。
- 该用模型却用代码:判据被迫退化成关键词匹配,评分器开始考措辞而不是质量。
- 开放式输出要分层,不是二选一:可查的部分给代码,主观的剩余部分给模型。
How would you prove your model judge has no position bias? Describe an experiment someone could actually run.你怎么证明自己的模型裁判没有位置偏好?请描述一个可执行的实验。
Common in ChinaCommon overseasDeep dive#evaluation#llm-as-judge#biasHow to reason about it · think before answering
- The question asks for a runnable experiment, so 'I would watch out for ordering effects' scores zero. Specify the data, the judging runs, the statistics, and the decision rule.
- Design: take a set of answer pairs and judge each pair twice, once with A first and once with B first, everything else identical. One pair yields two verdicts; N pairs yield 2N.
- The core statistic is the front-slot win rate: among all decisive single rounds, the fraction where the winner happened to be presented first. With no position bias it should sit at 50%. Significantly above is position bias, and significantly below is too - some models favor the second option, which is equally systematic.
- The second statistic is the flip rate: the fraction of pairs whose two rounds contradict each other. It is not a detector but a loss estimate - a 40% flip rate means that judging once leaves 40% of your conclusions determined by position, and the report will not say which ones.
- Sampling matters: deliberately include evenly matched pairs. Position bias only shows up when quality is close; a set of lopsided pairs buries the bias under the quality gap and you will measure a comforting 50%. This is the easiest place for the experiment to lie to you.
- Close with the decision rule and the control. Use a statistical test rather than eyeballing: a binomial test of the front-slot rate against 50%, noting that a small sample gives an interval too wide to conclude anything. For the control, run the same data through a judge known to be unbiased and confirm the harness reports 50% - without that step you cannot distinguish 'measured a real bias' from 'the harness always reports bias'.
- Expected follow-up: what do you do once bias is confirmed? In order: switch to independent per-item scoring to avoid pairwise entirely; if pairwise is required, run the swap and call flips ties; only then consider changing the judge model.
分析过程 · 先想清楚再作答
- 这题问的是「可执行的实验」,所以答「我会注意顺序的影响」是零分。必须给出数据怎么造、判决怎么跑、统计什么数字、判据是什么。
- 实验设计:取一批成对的答案,每一对**正反各评一次**。第一轮把 A 放前面,第二轮把 B 放前面,其余条件完全相同。一对答案得到两条判决,N 对得到 2N 条。
- 要统计的核心数字是**前排胜率**:在所有分出胜负的单轮判决里,赢家恰好被摆在前面的比例。没有位置偏好时它应该是 50%。显著高于 50% 就是位置偏好,显著低于 50% 也是(少数模型会偏向后一个,同样是系统性偏差)。
- 第二个数字是**结论翻转率**:两轮结论互相矛盾的对数占比。它的用途不是检测,而是估算损失——翻转率是 40% 就意味着,只评一轮的话你的结论里有四成是位置决定的,而报告不会告诉你是哪四成。
- 样本怎么造是有讲究的:**要故意包含一批势均力敌的答案对**。位置偏好只在质量接近时发作,全拿一优一劣的对子去测,偏差被质量差距盖住,你会测出一个漂亮的 50% 并得出错误结论。这也是这个实验最容易做假的地方。
- 最后要说判据与对照。判据用统计检验而不是眼看:前排胜率对 50% 做二项检验,样本量不够时区间会宽到什么都说明不了。对照组的做法是拿一个**已知无偏**的裁判跑同一批数据,确认实验代码本身会输出 50%——不做这一步,你无法区分「量出了偏差」和「实验代码恒定报告有偏差」。
- 可预期的追问是「测出来有偏差之后怎么办」。顺序是:先换成逐条独立打分绕开成对比较;必须成对时用交换实验并把翻转的判平;两者都不行才考虑换裁判模型。
Key points
- Judge each pair twice with the order swapped; N pairs give 2N verdicts under otherwise identical conditions.
- Primary metric: front-slot win rate, which should be 50% for an unbiased judge.
- Secondary metric: flip rate, estimating how many single-round conclusions position would have decided.
- Deliberately include evenly matched pairs, or the quality gap masks the bias.
- Decide with a binomial test, and run a known-unbiased judge as a control to confirm the harness reports 50%.
答题要点
- 同一对答案正反各评一次,N 对得到 2N 条判决,其余条件完全相同。
- 主指标是前排胜率:赢家恰好排在前面的比例,无偏时应为 50%。
- 辅指标是翻转率,用来估算「只评一轮」会让多少结论由位置决定。
- 样本必须故意包含势均力敌的对子,否则质量差距会把偏差盖住。
- 判据用二项检验,并拿一个已知无偏的裁判做对照,确认实验本身会输出 50%。
Why measure judge quality with an agreement coefficient rather than accuracy? Give a scenario where accuracy lies.为什么评估裁判质量要用一致性系数而不是准确率?举一个准确率会骗人的场景。
Common in ChinaCommon overseasDeep dive#evaluation#metrics#kappaHow to reason about it · think before answering
- The discriminator is not whether you know kappa, but whether you can state the conditions and the magnitude by which accuracy lies. 'Accuracy is unreliable on imbalanced data' is common knowledge, not an answer.
- Lead with a concrete scenario. You send thirty samples for human labeling; humans mark twenty-seven acceptable and three not. That ratio is realistic - most production samples sent for labeling are fine. Now compare a judge that looks at nothing and always says pass: it is right twenty-seven times, for 90% accuracy. A zero-information constant scores 90%.
- And this is not a contrived extreme. The more imbalanced the labels, the higher it goes: on ninety-nine to one data the same constant reports 99%. On imbalanced data accuracy is systematically optimistic, not occasionally wrong.
- Kappa applies one correction: compute pe, the agreement two labelers would reach by chance given their own label distributions, and subtract it from the observed agreement po, as (po - pe) over (1 - pe). For the constant judge po is 0.9 and pe is also 0.9, so kappa is exactly 0. Kappa of zero means the judge is equivalent to guessing from the label distribution, regardless of how high its accuracy looks.
- Resolution makes the case even better. In a measured comparison, removing a judge's length bias moved accuracy from 86.7% to 93.3%, about six and a half points, while kappa jumped from 0.259 to 0.714 - from nearly useless to over the admission line. The same improvement, an order of magnitude apart in signal. And before the fix, the always-pass constant scored 90%, higher than the real judge. Gate on accuracy and you eliminate the only judge carrying information.
- Close with thresholds and edge cases. Rules of thumb: below 0.2 is nearly useless, 0.4 to 0.6 is marginal, 0.6 to 0.8 is pipeline-grade. Two edges are worth naming: when both labelers assign every sample to one class kappa is undefined and the implementation must return 0 rather than 1; and with more than two labelers you switch to Fleiss' kappa, same idea.
分析过程 · 先想清楚再作答
- 这题的区分度不在「知不知道 kappa」,而在能不能把准确率骗人的**条件与幅度**说具体。只说「数据不均衡时准确率不可靠」,答的是常识。
- 先把场景摆出来,越具体越好:你送了三十条样本去人工标注,人工判定二十七条合格、三条不合格。这个比例接近真实——线上送去标注的样本本来就大多数是正常的。现在拿一个**什么都不看、一律判通过**的裁判来比:它判对了二十七条,准确率 90%。**一个零信息量的常量,拿到了 90% 的准确率。**
- 而且这不是极端构造。标签越不均衡,这个数字越高:九十九比一的数据上,同一个常量裁判能报出 99%。准确率在不均衡数据上是一个**系统性乐观**的指标,不是偶尔失灵。
- kappa 的修正只有一步:先算出「随机也能一致」的比例 pe,再从观察一致率 po 里扣掉它,公式是 (po - pe) 除以 (1 - pe)。那个常量裁判的 po 是 0.9,pe 也是 0.9,kappa 正好是 0。**kappa 为 0 的含义是「这个裁判等价于按标签分布瞎猜」,与它的准确率有多高完全无关。**
- 更能说明问题的是分辨率。实测过一组对照:同一个裁判修掉长度偏好之后,准确率从 86.7% 升到 93.3%、涨了 6.6 个点,而 kappa 从 0.259 跳到 0.714——从「几乎没用」直接跨过准入线。同一个改进,两个指标的反应差了一个数量级。而在修之前,那个一律判通过的常量裁判准确率是 90%,**比真裁判还高**。用准确率做准入,你会淘汰掉唯一一个带信息的裁判。
- 最后给阈值与边界:经验上 0.2 以下几乎没用,0.4 到 0.6 勉强,0.6 到 0.8 可以进流水线。两个边界情形要提:两人都把所有样本判成同一类时 kappa 无定义,实现里必须返回 0 而不是 1;标注者多于两人时换 Fleiss' kappa,思路一样。
Key points
- Scenario: with twenty-seven of thirty acceptable, an always-pass constant already scores 90% accuracy.
- The more imbalanced the labels, the higher it climbs - accuracy is systematically optimistic here.
- Kappa subtracts pe, the chance agreement, and the constant judge lands at exactly 0.
- Kappa resolves far better: one fix moved accuracy 6.6 points but kappa from 0.259 to 0.714.
- 0.6 and up is pipeline-grade; kappa is undefined when both labelers pick one class and must return 0; use Fleiss for more labelers.
答题要点
- 场景:三十条里二十七条合格时,一律判通过的常量裁判准确率就有 90%。
- 标签越不均衡这个数字越高,所以准确率是系统性乐观,不是偶尔失灵。
- kappa 把「随机也能一致」的 pe 扣掉,那个常量裁判的 kappa 正好是 0。
- kappa 的分辨率高得多:同一次修复准确率涨 6.6 个点,kappa 从 0.259 到 0.714。
- 阈值 0.6 起可进流水线;两人同判一类时 kappa 无定义必须返回 0,多人换 Fleiss。
In a pairwise comparison the two rounds disagree. How do you handle that data point, and why not just pick one at random?成对比较时两轮结论不一致,你会怎么处理这条数据?为什么不是随机取一个?
Common in ChinaCommon overseasIntermediate#evaluation#llm-as-judge#pairwiseHow to reason about it · think before answering
- This looks like a detail but tests whether you will write noise into a report as if it were signal. Answering 'take the first round because it matches real usage order' has already fallen in.
- The correct handling is to call it a tie. A flip means precisely one thing: that conclusion was decided by position, not by quality. Its information content is zero, and a tie is the honest way to say 'these two cannot be separated'. A tie is not a failure to conclude; it is a conclusion.
- Why not pick at random: that writes noise into the report, and half the time it will coincidentally match your expectation. The coincidental half increases your confidence in the judge, so the error gets locked in. The cost of treating noise as signal is not one missing data point but one fabricated one.
- Why not always take the first round either: that swallows the position bias wholesale. The entire purpose of the swap was to remove positional influence, so resolving by position undoes the experiment.
- Also specify what happens downstream. A tie is neither a pass nor a fail; it belongs in its own bucket in the report, alongside the count of decisive conclusions. A high tie rate is itself a finding - it says your rubric cannot separate these answers, either because it is too coarse or because the two systems really are comparable.
- Expected follow-up: what if ties leave too few data points? Fix it on the data side - more samples, or finer criteria so the rubric can discriminate - not by relaxing the merge rule. Relaxing it trades conclusion quality for conclusion count, which is backwards.
分析过程 · 先想清楚再作答
- 这题看着是个细节,实际上考的是「会不会把噪声当成信号写进报告」。答「取第一轮的结果,因为它更接近真实使用顺序」的,已经掉进坑里了。
- 正确处理是**判平局**。翻转的含义很明确:这条结论是由位置决定的,不是由质量决定的。它携带的信息量是零,而平局恰恰是「分不出来」的如实表达。平局不是认输,是一个有内容的结论。
- 为什么不能随机取一个:随机取等于把噪声写进了报告,而且它有一半的概率碰巧和你的预期一致。碰巧一致的那一半会让你更相信这个裁判,于是错误被固化。**把噪声当成信号的代价,不是少了一条数据,而是多了一条假数据。**
- 为什么也不能固定取第一轮:那等于把位置偏好整个吞了下去。你测这个实验的全部目的就是消除位置的影响,最后却按位置取结论,等于没做。
- 顺带要说清楚平局的下游处理。平局不能当成失败,也不能当成通过,它应该单独成一档进报告:多少条有效结论、多少条判平。平局比例过高本身就是一个结论——它说明这批答案在你的量表下分不出差别,要么量表太粗,要么这两个系统确实旗鼓相当。
- 可预期的追问是「平局太多导致样本量不够怎么办」。答案不是放宽合并规则,而是从数据侧解决:加样本量、或者把判据拆细让量表分得动。放宽合并规则等于用降低结论质量来换结论数量,方向反了。
Key points
- Call every flipped pair a tie: it was decided by position, not quality, and carries zero information.
- Picking at random writes noise into the report and half the time appears to confirm your expectation, locking in the error.
- Always taking the first round swallows the position bias and wastes the swap entirely.
- Report ties as their own bucket - neither a pass nor a fail.
- Too many ties is a data-side problem: more samples or finer criteria, not a looser merge rule.
答题要点
- 翻转的结论一律判平:它由位置而不是由质量决定,信息量是零。
- 随机取一个是把噪声写进报告,还会有一半概率碰巧印证你的预期,固化错误。
- 固定取第一轮等于吞掉位置偏好,交换实验白做。
- 平局单独成一档进报告,不能算失败也不能算通过。
- 平局过多要从数据侧解决:加样本、拆细判据,而不是放宽合并规则。
D4 Trajectory Evaluation: It Reached the Destination, but How Many Things Did It Hit
An agent ships with all outcomes correct, but its average cost has gone up tenfold. How would your evaluation system catch that?一个 Agent 上线后结果全对,但平均成本翻了十倍。你的评估体系怎么才能发现这件事?
Common in ChinaCommon overseasIntermediate#evaluation#trajectory#costHow to reason about it · think before answering
- This tests whether you understand that outcome-based evaluation has a structural blind spot. Answering 'add a cost alert' earns half credit - that is remediation after the fact, while the question asks why the evaluation itself missed it.
- State the mechanism: an outcome grader reads the final state of the environment, and cost is not part of that state. The refunds table holds an order id and an amount, not the tokens spent or the number of tool calls. No matter how strict your outcome assertions are, they cannot in principle detect a tenfold cost increase. That is a coverage gap, not an oversight.
- The fix is to assert on the trajectory itself: total tool calls per trajectory below N, total spend below M. These sit in a layer parallel to the outcome grader, and either breach fails the trial. It must be a failure rather than a warning - warnings in CI are equivalent to nothing.
- Where the thresholds come from: the current baseline. Measure the present distribution over a batch of trials and set the ceiling slightly above today's 95th percentile rather than picking a round number. That tolerates normal jitter while going red as soon as the mean shifts up.
- Add why this class of regression slips into production so easily: cost regressions change nothing a user can see. A new prompt, one more reflection round, longer tool descriptions - the pass rate is unchanged, steps go from three to eight, and the bill surfaces at month end. Without budget assertions the pipeline stays green the whole way.
- Expected follow-up: steps or spend as the gate? Both, because they diverge. Swapping in a pricier but smarter model lowers steps and raises unit price. Watching only steps misses the price increase; watching only spend misses the extra wandering caused by weaker reasoning.
分析过程 · 先想清楚再作答
- 这题考的是「知不知道结果态评估有结构性盲区」。回答「加一个成本监控告警」只能拿一半分——那是发现之后的补救,题目问的是评估体系本身为什么漏掉了它。
- 先把机制说清楚:结果态评分器的输入是环境的最终状态,而成本不在最终状态里。退款记录表里只有订单号和金额,没有「这次花了多少 token、调了几次工具」。所以无论你把结果态断言写得多严,它在原理上都判不出成本翻十倍这件事。**这是覆盖不到,不是写漏了。**
- 正确的做法是给轨迹本身写断言:一条轨迹的工具调用总数不超过 N、总花费不超过 M。这两条挂在与结果态平行的一层,任一超标就判这次试次失败。注意它必须是**失败**而不是警告——警告在 CI 里等于没有。
- 阈值从哪来:从当前基线来。先跑一批试次统计出现在的分布,取一个略高于当前 p95 的数作为上限,而不是拍一个整数。这样它既能容忍正常抖动,又能在均值整体上移时立刻报红。
- 还要补一句为什么这类退化特别容易溜进生产:**成本回归不改变任何用户可见的行为。** 换个提示词、多加一轮反思、把工具描述写长一点,成功率一点没掉,步数从 3 涨到 8,账单要到月底才有人看。没有预算断言的话,评估流水线全程报绿。
- 可预期的追问是「那步数和花费该选哪个当闸门」。答案是两个都要,因为它们会分叉:模型换成一个更贵但更聪明的,步数会降而单价会升。只看步数会漏掉换模型带来的涨价,只看花费会漏掉逻辑变笨带来的绕路。
Key points
- Cost is absent from the outcome state, so an outcome grader cannot detect this class of regression at all.
- Add a parallel layer of trajectory assertions: a step ceiling and a spend ceiling, failing the trial rather than warning.
- Derive thresholds from the current baseline distribution, slightly above p95, not from a round number.
- Cost regressions change nothing user-visible, so without budget assertions the pipeline stays green.
- Track both steps and spend: a pricier model moves them in opposite directions and either alone leaves a hole.
答题要点
- 结果态里根本不含成本,所以结果态评分器在原理上覆盖不到这类退化。
- 给轨迹写平行的一层断言:步数上限与花费上限,超标判失败而不是告警。
- 阈值从当前基线的分布取,略高于 p95,而不是拍一个整数。
- 成本回归不改变任何用户可见行为,所以没有预算断言时流水线会全程报绿。
- 步数与花费都要盯:换更贵的模型会让两者反向变化,只看一个都会漏。
Would you assert that an agent's tool-call sequence must exactly match a reference workflow? Give your reasoning.你会把「工具调用顺序必须与参考流程完全一致」写进评分器吗?说出你的理由。
Common in ChinaCommon overseasIntermediate#evaluation#trajectory#gradersHow to reason about it · think before answering
- This is a position question, but the credit is not in the position. Either answer can score; what matters is naming the concrete mechanism of false failures and proposing a workable middle ground.
- Why a total-order assertion is wrong: part of an agent's value is finding solutions the designer did not anticipate - one extra order lookup to confirm the amount (more careful), two independent lookups issued concurrently (faster), reading a cached policy conclusion and skipping a call (cheaper). A strict sequence assertion fails all three, and all three are improvements. A grader that punishes improvement is the worst kind of defect: it pushes the team toward a dumber but more obedient implementation.
- There is a subtler cost too. Total-order assertions go red en masse on every model upgrade for reasons unrelated to quality. The team then either spends days updating reference workflows or disables the whole class of assertion, losing the real problems it would have caught.
- You still cannot assert nothing, or a refund issued without a policy check goes unnoticed. The middle ground is to assert only necessary precedence, and as a partial order: before the first issue_refund, check_policy must have appeared at least once. Intervening calls are fine, three policy checks are fine, and if no refund happened the rule is simply skipped.
- A simple test for whether a precedence rule belongs in the policy: if it were violated, would a real loss follow? Refunding without a policy check loses money, so it belongs. Whether the order lookup precedes the policy check has no consequence, so it does not. More than about five precedence rules in one policy almost certainly means overreach.
- Expected follow-up: if the order was wrong but the outcome was right, does the trial pass? Report them separately - outcome passed, sequence failed, each recorded on its own. Collapsing them into one score destroys exactly the information you need, which is that this run got lucky.
分析过程 · 先想清楚再作答
- 这是一道立场题,但分不在立场上——答「会」或者「不会」都能拿分,关键是能不能说出误杀的**具体机制**,以及给出一个能落地的中间方案。
- 先说为什么不该写死全序。Agent 的价值有一部分正来自它会找到设计者没想到的解法:为了确认金额多查一次订单(更谨慎)、把两次独立查询并发发出(更快)、从缓存里直接读到结论省掉一次调用(更省)。这三种在全序断言下**全部判失败**,而它们全是改进。你的评分器在惩罚改进,这是最坏的一种评估缺陷——它会把团队推向一个更笨但更听话的实现。
- 还有一个更隐蔽的代价:全序断言会在模型升级时大面积变红,而红的原因与质量无关。于是团队要么花大量时间逐条更新参考流程,要么干脆把这类断言整体关掉,连同它本来能抓到的真问题一起。
- 但也不能一条都不判,否则「没查政策就退款」这种真问题没人管。中间方案是**只判必要的前置关系,而且判偏序**:断言「第一次 issue_refund 之前,check_policy 至少出现过一次」。中间夹了别的调用不算违规,查了三次也不算,压根没退款时这条规则直接跳过。
- 判断一条前置关系该不该写进去,有个简单标准:**如果它被违反,会不会导致一次真实的损失?** 不查政策就退款会退错钱,该写;先查订单再查政策还是反过来,没有任何后果,不该写。一份策略里超过五条前置关系,基本可以确定写多了。
- 可预期的追问是「那顺序错了但结果对了,到底算不算通过」。答案是分开报:结果态通过、序列不通过,两个分数各自记录。合成一个总分会丢掉信息——你需要知道的恰恰是「这次是蒙对的」。
Key points
- Do not assert a total order: an extra lookup, concurrent calls, or a cache shortcut all get failed, and all are improvements.
- Total-order assertions also go red wholesale on model upgrades and end up disabled, taking the real findings with them.
- The middle ground is necessary precedence as a partial order: a policy check somewhere before the first refund.
- The test is whether a violation causes real loss; more than about five precedence rules means overreach.
- Report outcome and sequence as separate scores - merging them hides the fact that a run got lucky.
答题要点
- 不写死全序:多查一次、并发查询、走缓存捷径都会被误杀,而它们全是改进。
- 全序断言还会在模型升级时大面积变红,最终被整体关掉,真问题一起丢掉。
- 中间方案是只判必要前置且判偏序:第一次退款之前查过政策即可。
- 取舍标准是「违反了会不会造成真实损失」,超过五条前置关系基本是写多了。
- 结果态与序列两个分数分开报,合成总分会丢掉「这次是蒙对的」这条关键信息。
How do you automatically detect that an agent is stuck in a loop? Give your rule and its false-positive risks.怎么自动判定一个 Agent 陷入了死循环?给出你的规则,以及它的误判风险。
Common in ChinaCommon overseasIntermediate#evaluation#trajectory#loop-detectionHow to reason about it · think before answering
- The discrimination here is all in the details. 'More than three calls to the same tool' is the most common answer and it is wrong - it fails a large class of perfectly normal tasks.
- The correct criterion is the same tool with identical arguments occurring more than N times. The arguments clause is essential: looking up ten different orders is ten lookup calls with ten different argument sets, which is normal batch work; four lookups of the same order id is a loop.
- One implementation detail you must hit: sort the argument keys before serializing them into a signature. Serializing the object directly means the same call written with two key orders produces two different signatures, and the loop goes undetected - a silent failure that raises no error.
- N must be set per task, and that is the real point of the question. A task that resolves in one lookup is looping if it repeats three times; polling an asynchronous job legitimately requires a dozen checks. With both kinds in one suite, a single global threshold forces a choice between missing half the loops and failing half the normal tasks.
- Three main false-positive risks: legitimate retries after a transient failure, which argues for including success/failure in the signature; idempotent polling, handled by per-task thresholds; and arguments containing timestamps or random ids, which make every signature unique and hide loops entirely - those volatile fields must be stripped when computing the signature.
- Expected follow-up: what other shapes of looping exist? Semantic loops, where tools and arguments differ but the agent oscillates between two states. Those are caught by the step budget instead - you cannot prove it is a loop, but you can prove it blew the budget, and for evaluation purposes the conclusion is the same: this trial does not pass.
分析过程 · 先想清楚再作答
- 这题的区分度全在细节上。答「同一个工具调用超过三次就是循环」是最常见的答案,也是错的——它会把一大批正常任务误判成死循环。
- 正确的判据是**同一个工具加上完全相同的参数**出现超过 N 次。参数这半句不能省:依次查询十个不同的订单,是十次 lookup 调用但参数各不相同,它是正常的批量操作;连续四次查同一个订单号,才是循环。
- 实现上有个必须踩到的细节:参数要**按键排序后**再序列化成签名。直接对参数对象做 JSON 序列化的话,同一次调用写成两种键顺序会算出两个不同签名,于是循环恰好检测不出来——而这是一个不会报错的静默失效。
- N 的取值必须**按任务定**,这是这题真正的考点。查一次就有结果的任务,重复三次一定是循环;而轮询一个异步任务的状态,本来就要查十几次才等到完成。同一套评估里两种任务共存,一个全局阈值只能在「漏掉一半死循环」和「误伤一半正常任务」之间挑一个。
- 误判风险主要有三类:① 合法的重试——网络失败后重试同一个调用是正确行为,所以理想情况下签名里应该带上返回是否成功;② 幂等的轮询,靠按任务调阈值解决;③ 参数里带了时间戳或随机 id,导致每次签名都不同,循环被完全漏掉,这一类要在算签名时显式剔除易变字段。
- 可预期的追问是「除了重复调用,还有什么循环形态」。答案是语义层面的循环:工具和参数都不同,但 Agent 在 A 和 B 两个状态之间来回横跳。这种要靠步数预算兜底——判不出它是循环,但能判出它超了预算,而对评估来说结论是一样的:这次试次不合格。
Key points
- The rule is the same tool plus identical arguments exceeding N, not the same tool exceeding N.
- Sort argument keys when computing the signature, or differing key order silently hides the loop.
- Set the threshold per task: one-shot lookups and polling workflows cannot share a number.
- Three false-positive sources: legitimate retries, idempotent polling, and volatile fields such as timestamps or random ids.
- Semantic loops that oscillate between states are not detectable this way; the step budget catches them instead.
答题要点
- 判据是「同一工具 + 完全相同参数」超过 N 次,不是「同一个工具」超过 N 次。
- 算签名时参数必须按键排序,否则键顺序不同会让循环静默漏检。
- 阈值按任务定:一次查询就有结果的任务与需要轮询的任务不能共用一个数。
- 三类误判:合法重试、幂等轮询、参数里带时间戳或随机 id 导致签名永不重复。
- 语义循环(在两个状态间横跳)检测不出来,靠步数预算兜底。
How do you automate evaluation of multi-turn conversations, and what problems does a simulated user introduce?多轮对话场景怎么做自动化评估?模拟用户会带来什么问题?
Common in ChinaCommon overseasDeep dive#evaluation#multi-turn#simulated-userHow to reason about it · think before answering
- The question has two halves and the second is the discriminator. Almost everyone can answer 'write a simulated user'; the score depends on naming the systematic biases it introduces.
- Why it is necessary: real users open with incomplete requests and then push back, and the pushback is where agents break - context grows, constraints get diluted, tools get called repeatedly. Single-turn evaluation structurally cannot reach that path, because nobody says 'check again' in their opening sentence.
- Two implementations exist. A rule-based user keys off phrases in the agent's last reply, giving perfect reproducibility at zero cost. A model-based user has another model play the customer, giving varied phrasing that is closer to reality.
- The rule-based bias is uniformity: it pushes back with the same sentence every time, so the evaluation surfaces exactly one failure mode. Real users express the same intent a hundred ways, and some of those phrasings take entirely different paths.
- The model-based bias is subtler - leakage. The model playing the user holds the full task setup and readily gives the answer away in its follow-up ('check clause three of the policy table'), so the agent under test is taking an open-book exam. Both kinds share a third bias: they are too patient. They never hang up, change their mind, or get angry, and abandonment in real conversations is itself an important signal.
- Crucially all three biases point the same way: they inflate the score. So multi-turn evaluation is a tool for finding failures, not for estimating success rates. Do not publish the simulated-user pass rate, but do go fix the loop it uncovered. Reporting a rate requires calibration against replayed real conversations or human sampling.
- Expected follow-up: how do you make multi-turn evaluation reproducible? Three things - seed the simulated user's randomness, hard-cap the number of turns as a termination condition, and rebuild the environment between trials. Note that within a single conversation the environment is shared across turns; the isolation boundary is the trial, not the turn.
分析过程 · 先想清楚再作答
- 这题分两半,后半半才是考点。前半半答「写一个模拟用户」几乎人人会答,能不能说出它引入的系统性偏差才分高下。
- 先讲为什么非做不可:真实用户第一句话往往不完整,会追问,而追问才是最容易翻车的地方——上下文变长、约束被冲淡、工具被反复调用。**单轮评估在结构上碰不到这条路径**,因为没有人会在第一句话里说「你再查一次」。
- 实现上有两种模拟用户。规则型看 Agent 上一句回复里的关键词决定下一句说什么,优点是完全可复现、零成本;模型型让另一个模型扮演用户,优点是表达多样、更接近真实。
- 规则型的偏差是**表达过于一致**:它每次都用同一句话追问,于是评估只能发现一种失败模式。真实用户会用一百种说法表达同一个意思,其中某些说法会触发完全不同的路径。
- 模型型的偏差更隐蔽,是**泄题**:扮演用户的模型拿到的是任务的完整设定,它很容易在追问里把答案说出来(「你去查一下政策表第三条」),于是被评的 Agent 在开卷考试。另外两种模拟用户共有一条偏差:它们**太有耐心**,不会挂电话、不会改主意、不会骂人,而真实对话里的放弃行为本身是一个重要信号。
- 关键是这三条偏差**方向一致,都让分数偏高**。所以结论是:多轮评估是发现失败的工具,不是估计成功率的工具。模拟用户跑出的成功率不要直接对外报,但它抓出来的那条死循环可以直接拿去修。要报成功率就得用真实对话回放或人工抽查来校准。
- 可预期的追问是「怎么让多轮评估可复现」。答案是三件事:模拟用户的随机源要固定种子、终止条件要写死最大轮数、每次试次之间环境必须重建;而**同一次会话内部的轮次之间环境是共享的**——隔离的边界是试次,不是轮次。
Key points
- It is necessary because the follow-up path is structurally unreachable in single-turn evaluation and is where agents break.
- A rule-based simulated user is reproducible but too uniform, surfacing only one failure mode.
- A model-based simulated user leaks the answer, since it holds the full task setup.
- Both are too patient: they never abandon or change their mind, erasing a real signal.
- All biases inflate the score, so use multi-turn evaluation to find failures, not to report success rates.
答题要点
- 必须做的理由:追问路径在单轮评估里结构性地碰不到,而它正是最容易翻车的地方。
- 规则型模拟用户可复现但表达过于一致,只能发现一种失败模式。
- 模型型模拟用户会泄题:它拿着完整任务设定,容易在追问里把答案说出来。
- 两者共有的偏差是太有耐心:不会放弃、不会改主意,抹掉了真实的放弃信号。
- 三条偏差方向一致地抬高分数,所以多轮评估用于发现失败,不用于报成功率。
D5 Observability: Putting a Flight Recorder on Every Run
Why instrument an agent with a public semantic convention instead of a field-name scheme your team invents?为什么给 Agent 埋点要用公开的语义约定,而不是团队自己定一套字段名?
Common in ChinaCommon overseasBasic#observability#opentelemetry#semantic-conventionsHow to reason about it · think before answering
- This tests whether you have ever actually migrated an observability backend. 'For standardization' is an empty answer; give three reasons that map to concrete cost.
- First, migration cost. Backends get swapped often - self-hosted to commercial, one vendor to another, or two running side by side for comparison. With a public convention you change one export endpoint; with private field names you change every instrumentation site plus every dashboard query. At scale that difference is an order of magnitude.
- Second, ready-made views. Backends ship built-in panels keyed on these fields: spend by model, error rate by tool, traces by conversation. Match the names and the charts exist for free; miss them and you rebuild each one, and new teammates cannot read your private schema.
- Third, and most overlooked: the convention is a checklist someone already thought through for you. Left to yourself you record model, latency and token counts. The convention also has cache-read tokens, cache-creation tokens, reasoning tokens, finish reasons, tool call ids. Omitting them raises no error, but when someone asks how much caching saved last month, the history simply is not there - telemetry gaps cannot be backfilled.
- State the boundary too: not everything belongs in the public namespace. Your own dimensions - task id, tenant, experiment arm - go under your own prefix. Do not smuggle private fields under gen_ai, or you will not be able to tell yours from theirs when the convention moves.
- Expected follow-up: what if the field you need is not in the convention? Check carefully that it really is absent, put it in your own namespace, and watch upstream discussions - renaming once after it lands is cheaper than inventing your own forever.
分析过程 · 先想清楚再作答
- 这题考的是「有没有真的换过可观测后端」。只答「为了标准化」是一句空话,要给出三条能落到具体成本的理由。
- 第一条是换后端的成本。可观测后端是换起来很频繁的东西——自建换商业的、商业的换一家、或者双跑做对比。用公开约定时,换后端改的是导出地址一处;用自定字段时,改的是每一处埋点,外加每一张面板的查询语句。这个差别在系统大起来之后是数量级的。
- 第二条是现成视图。各家后端都按这套约定做了开箱即用的面板:按模型看花费、按工具看错误率、按会话串链路。字段名对上了这些图不用配就有;对不上就得一张张自己拼,而且新同事看不懂你那套私有字段。
- 第三条最容易被忽略,也是最值钱的:**约定本身是一份别人替你想好的清单**。自己定字段多半只记模型、耗时、token 数三样;约定里还有缓存命中的 token 数、推理 token 数、完成原因、工具调用 id。这些不记不会报错,但等到要回答「上个月缓存省了多少钱」的时候,历史数据里没有就是永远没有了——**遥测的坑是补不回来的**。
- 反过来也要说清边界:不是所有字段都塞进公开命名空间。业务自己的维度(任务 id、租户、实验分组)应该放在自己的命名空间里,不要往 gen_ai 前缀底下塞私货,否则升级约定时你分不清哪些是自己的、哪些是人家的。
- 可预期的追问是「那约定里没有你要的字段怎么办」。答案是先查一遍确认真的没有,然后放进自己的命名空间,并留意上游有没有在讨论同名字段——真加进约定之后做一次改名,比一直自造要划算。
Key points
- Swapping backends changes one export endpoint instead of every instrumentation site and dashboard query.
- Vendor-provided default views work out of the box instead of being rebuilt chart by chart.
- The convention is a ready-made field checklist covering cache tokens, reasoning tokens and finish reasons you would not think of.
- Telemetry gaps are unrecoverable: a field you did not record cannot be reconstructed later.
- Keep business dimensions under your own namespace rather than inside the public prefix.
答题要点
- 换后端时只改导出地址,不用改每一处埋点和每一张面板查询。
- 各家后端的开箱即用视图直接可用,不必逐张自己拼图。
- 约定是一份现成的字段清单,缓存 token、推理 token、完成原因这些自己多半想不到。
- 遥测的坑补不回来:当时没记的字段,事后无法从历史数据里恢复。
- 业务自有维度放进自己的命名空间,不要塞进公开前缀底下。
Would you adopt a standard that is still in development and whose attribute names may change? How do you control the upgrade risk?一个仍在开发中、属性名还会改的标准,你会现在就用吗?怎么控制升级风险?
Common in ChinaCommon overseasDeep dive#observability#opentelemetry#dependency-riskHow to reason about it · think before answering
- On the surface this is about a standard; really it is about how you handle an unstable dependency. Both extremes score poorly: 'wait for stable' forfeits benefits available today, while 'just adopt and fix later' shows you never priced the fix.
- Get the facts right first, which alone separates candidates: nothing in the GenAI semantic conventions is marked Stable - everything is Development, there is no 1.0 - and the conventions have moved out of the main semantic-conventions repository into a dedicated one that has not cut a single release tag yet.
- Add the most misread detail: the registry pages in the main repository now mark every one of those attributes as deprecated and moved. That is a page relocation, not a deprecation of the attributes. Deleting instrumentation because of that red text has been a common mistake this year; pointing it out shows you read the primary source.
- Then give the plan, whose core is shrinking the blast radius: every attribute name appears exactly once, in one constants file, and everywhere else imports it. Add a guard asserting that every emitted name comes from that table, so nobody hand-writes a string - a hand-written typo raises no error, it just silently drops a column from the dashboard.
- Second, pin the version into the data itself: freeze the convention version in the constants file and emit it as an attribute on every trace. A year later you can tell which revision a batch of data was recorded under instead of guessing.
- Finally, the upgrade move: on a rename, dual-write both names for a transition window, cut dashboards and alerts over to the new one, then drop the old. Expected follow-up - should the rename live in the data pipeline instead? It can, but that moves the debt into the pipeline; dual-writing with a stated removal date is cleaner.
分析过程 · 先想清楚再作答
- 这题问的表面是标准,实际是**你处理不确定依赖的工程习惯**。两个极端答案都拿不到分:「等它稳定了再说」会白白丢掉现在就能拿到的好处,「用,有问题再改」则暴露你没算过改的成本。
- 先把事实说准,这一步就能拉开差距:生成式 AI 的那套语义约定至今没有任何条目标记为 Stable,全部处于 Development,没有 1.0;而且它已经从主语义约定仓库搬进了一个独立仓库,那个仓库到现在一个发布 tag 都没打过。
- 还要补一句最容易被误读的现状:主仓库的属性登记页上,每一条相关属性现在都带着「已弃用,已移至新仓库」的标记。**那是页面搬家,不是属性被废弃。** 看到红字就把埋点删掉是这一年最常见的误操作,能主动指出这一点,说明你看的是一手页面而不是二手文章。
- 然后给方案,核心是**把改动面收敛**:全部属性名只在一个常量文件里出现一次,别处一律引用;再写一条护栏断言所有用到的名字都来自这张常量表,防止有人图省事手写字符串——手写的那个拼错了不会报错,只会在面板上少一列。
- 第二件事是把版本钉进数据本身:常量文件里写死约定版本,并把这个版本号作为属性写进每一条链路。一年后翻历史数据时,你能立刻知道那批数据是按哪一版记的,而不是靠猜。
- 最后给升级动作:属性改名时做双写过渡(一段时间内新旧名都写),等面板和告警都切到新名再撤掉旧的。可预期的追问是「那要不要在数据管道里做改名映射」——可以,但那是把债转移到了管道上,双写加一个明确的下线日期更干净。
Key points
- State the facts: nothing is Stable, everything is Development, and it has moved to a repository with no release tag yet.
- The deprecation banners in the main repository mean the pages moved, not that the attributes died - do not delete instrumentation over them.
- Adopt it, but confine changes to a single constants file that everything else imports.
- Add a guard asserting every emitted attribute name comes from that table, since a hand-typed typo silently drops a column.
- Pin the convention version in code and emit it as an attribute; on renames dual-write, migrate dashboards, then retire the old name.
答题要点
- 先把事实说准:至今无任何条目为 Stable,全部 Development,且已搬进一个尚无发布 tag 的独立仓库。
- 主仓库页面上的「已弃用」是页面搬家,不是属性被废弃,不能照着它删埋点。
- 用,但把改动面收敛到一个常量文件,别处一律引用。
- 加一条护栏断言所有用到的属性名都来自常量表,防止手写字符串拼错后静默少一列。
- 把约定版本钉进常量文件并作为属性写进链路;改名时双写过渡,切完面板再下线旧名。
How would you layer the trace tree for one agent run, and what attributes go on each layer?一次 Agent 运行的链路树你会怎么分层?每层各记哪些属性?
Common in ChinaCommon overseasIntermediate#observability#tracing#span-designHow to reason about it · think before answering
- This tests whether you have actually drawn one. 'Record the model call' earns nothing; give the layers, the operation name per layer, and the rule that decides nesting.
- Three layers is the common skeleton. Outermost is the agent invocation, operation invoke_agent, carrying provider, request model, conversation id, plus your own business dimensions in your own namespace. The middle layer is each model turn, operation chat, carrying request parameters, response model and id, finish reasons, and input/output token usage. Innermost is each tool call, operation execute_tool, carrying tool name, tool type, call id, arguments and result.
- Operation names cannot be invented. The convention defines an enum of exactly nine values: chat, create_agent, embeddings, execute_tool, generate_content, invoke_agent, invoke_workflow, retrieval, text_completion. An off-enum value is equivalent to not instrumenting at all: backends facet on the enum, and unknown values land in an 'other' bucket that never surfaces in a chart.
- Explain the nesting rule: tool spans hang under the model turn that requested them, not as siblings of it, because that is the only way to attribute tool latency to a specific turn. Flatten them in a multi-turn agent and you can no longer say which turn issued the third tool call. Both layouts exist in the wild; what matters is that one company picks one, or hierarchy-based aggregation stops agreeing across services.
- Mention two commonly missed fields. Time to first chunk must be stamped when the first streamed chunk arrives and cannot be derived from total duration - for a streaming UI it is the latency the user actually feels. And the evaluation quartet - grader name, score, pass label, explanation - belongs on the root span so the dashboard's failure rate has a correct source.
- Finally, payload control: truncate and redact messages, tool arguments and results. Dumping whole conversations into spans is the classic way to blow up storage in one afternoon and the most common path for leaking personal data. Expected follow-up - how do you debug without the full text? Keep it in your own logs and put only a correlating id on the span.
分析过程 · 先想清楚再作答
- 这题考的是「有没有真的画过一棵树」。只说「记一下模型调用」的拿不到分,要给出层级、给出每层的操作名、并解释分层的判据。
- 三层是最常用的骨架:最外层是一次 Agent 调用,操作名 invoke_agent,记提供方、请求模型、会话 id,再加上你自己命名空间里的业务维度;中间层是每一轮模型调用,操作名 chat,记请求参数、响应模型与 id、完成原因、输入输出 token 用量;最里层是每一次工具调用,操作名 execute_tool,记工具名、工具类型、调用 id、参数与结果。
- 操作名不能自己造。约定里它是一个枚举,一共九个合法取值(chat、create_agent、embeddings、execute_tool、generate_content、invoke_agent、invoke_workflow、retrieval、text_completion)。写一个枚举外的值等于没埋:后端按枚举分面,不认识的值会掉进 other 桶,永远出不了图。
- 分层判据要说清楚:工具跨度挂在它所属的那一轮模型调用下面,而不是与模型调用平级。因为工具是那一轮决定要调的,挂进去才能把工具耗时归因到具体某一轮;多轮 Agent 一旦平铺,你就说不清第三次工具调用是第几轮发起的。**两种画法真实世界里都有,重点是全公司统一**,否则按层级做的聚合查询在两个服务之间对不上。
- 还要提两个容易漏的字段。一是流式首块延迟,它必须在读到第一个分片时打点,**不能从总耗时推算**,对流式界面来说它才是用户感知的快慢。二是评估结果四件套,把评分器名字、分数、通过标签与理由挂在根跨度上,这样面板上的失败率才有正确来源。
- 最后是负载控制:输入输出消息和工具参数结果都要截断并考虑脱敏,整轮对话原样进链路是把存储一次性写爆的经典方式,也是泄漏个人信息最常见的路径。可预期的追问是「那出了问题要看全文怎么办」——把全文留在你自己的日志里,链路只留一个能关联回去的 id。
Key points
- Three layers: invoke_agent wraps chat, chat wraps execute_tool, with tools nested under the turn that requested them.
- Operation names must come from the nine-value enum; anything else lands in the backend's 'other' bucket.
- Per layer: root carries provider, model and conversation id; chat carries request parameters, response id and token usage; tool spans carry name, type, call id, arguments and result.
- Time to first chunk must be stamped on arrival of the first chunk, never derived; the evaluation quartet goes on the root span.
- Truncate and redact messages and tool payloads; keep full text in logs and put only a correlating id on the span.
答题要点
- 三层:invoke_agent 包 chat,chat 包 execute_tool;工具挂在发起它的那一轮模型调用下面。
- 操作名只能取九个合法值之一,自造值会掉进后端的 other 桶,等于没埋。
- 各层属性:根层记提供方、模型与会话 id;chat 层记请求参数、响应 id 与 token 用量;工具层记工具名、类型、调用 id、参数与结果。
- 首块延迟必须在第一个分片到达时打点,不能从总耗时推算;评估四件套挂在根跨度上。
- 消息与工具参数结果要截断并脱敏,全文留在日志里、链路只放关联 id。
Recording every trace in production is too expensive. How do you design sampling so that you do not throw away the failures you actually need?线上全量记录太贵,你怎么设计采样策略才不会把真正的故障样本丢掉?
Common in ChinaCommon overseasDeep dive#observability#sampling#productionHow to reason about it · think before answering
- The trap is that the word 'sampling' makes people reach for a random percentage drop. The real question is when the decision is made.
- Head-based sampling rolls the dice as the request arrives. It is cheap and simple, but the cost is fatal: the decision happens before you know whether this trace will fail. A failure mode that hits 1% of requests is discarded 99% of the time before it goes wrong - the samples you most need are precisely the ones most likely to be dropped.
- Tail-based sampling is the right shape: wait until the trace finishes and the outcome is known, then decide. The rules can be blunt - keep every failure, keep everything slower than p99, keep a small proportion of fast successes as a baseline. The baseline matters: keep only failures and you cannot compute a failure rate or see what healthy looks like.
- For agents, say explicitly what 'failure' means: not the HTTP status code. A request that refunded the wrong amount still returns 200. The signal should come from the evaluation attributes - put the grader's pass label on the root span and let the sampling rule read it. That is a direct payoff of stitching offline evaluation into online telemetry.
- One implementation detail separates people who have done this: use a stable hash of the trace id, not a random number. Randomness keeps some spans of a trace and drops others, producing a truncated tree that is worse than nothing - it makes a stage look as if it never happened.
- Name the cost too: tail sampling must buffer all spans of a trace until the verdict is known, so the collector has to survive memory pressure and out-of-order arrival, and long traces need a timeout that forces a flush. Expected follow-up - how do you keep spend bounded? Budget the post-sampling write volume and, when over budget, lower the retention of successful traces first; the retention of failures and slow traces never moves.
分析过程 · 先想清楚再作答
- 这题的陷阱在于「采样」这个词会让人下意识想到按比例随机丢。真正的考点是**在什么时刻做这个决定**。
- 头部采样是请求一进来就掷骰子决定记不记。它便宜、实现简单,但代价是致命的:这个决定是在你还不知道这条链路会不会出问题的时候做的。于是一条命中率 1% 的故障链路,有 99% 的概率在它出问题之前就已经被丢掉了——**线上最需要的那批样本,恰好是最容易被采样掉的那批**。
- 正确的做法是尾部采样:等链路跑完、结果已知,再决定留不留。规则可以写得很直白——失败的一条不落,超过 p99 的慢链路全留,成功且不慢的按比例留一小部分当基线。留基线很重要,全丢掉的话你手里只剩故障样本,没法算失败率,也看不出正常态是什么样。
- 对 Agent 来说「失败」的判据要特意说清楚:不是 HTTP 状态码。一个退错了钱的请求照样是 200。判据应该来自评估结果属性——把评分器的通过标签挂在根跨度上,采样规则直接读它。这也是离线评估与线上监控缝在一起之后的直接收益。
- 还有一个实现细节能看出有没有真做过:决定留不留要用 traceId 的稳定哈希,不要用随机数。随机数会让一条链路的一部分跨度被留下、另一部分被丢掉,拼出来是一棵残树,比完全没有更糟——它会让人以为某一段根本没发生。
- 代价也要主动说:尾部采样必须先把一条链路的全部跨度缓存到能判定结果为止,所以收集端要扛住内存与乱序到达,长链路还要设超时强制出清。可预期的追问是「那怎么保证成本可控」——给采样后的写入量设预算,超预算时先降成功样本的留存比例,**失败与慢链路的留存率永远不动**。
Key points
- The question is not how much to drop but when to decide: head-based sampling rolls the dice before the outcome is known.
- Its fatal flaw: a low-frequency failure mode is almost always discarded before it goes wrong.
- Tail-based rules: keep all failures, keep everything above p99 latency, keep a small sampled baseline of fast successes.
- Failure for an agent comes from evaluation attributes, not HTTP status - a wrong refund still returns 200.
- Use a stable hash of the trace id, never a random number, or you get truncated trees; under budget pressure lower only the retention of successful traces.
答题要点
- 关键不是丢多少,而是在什么时刻决定:头部采样在结果未知时就掷骰子。
- 头部采样的致命问题:低命中率的故障链路极大概率在出问题之前已被丢掉。
- 尾部采样规则:失败全留、超 p99 的慢链路全留、成功且不慢的按比例留一小部分当基线。
- Agent 的失败判据来自评估属性而不是 HTTP 状态码,退错钱的请求同样返回 200。
- 用 traceId 的稳定哈希而不是随机数,否则会产出残缺的链路树;预算紧张时只降成功样本留存率。
D6 The Regression Gate: Blocking a Drop Before It Merges
Evaluation scores fluctuate on every run. How do you set a gate threshold that neither misses regressions nor fires false alarms every day?评估分数每次都在抖,你怎么定一个既不漏报也不天天误报的门禁阈值?
Common in ChinaCommon overseasDeep dive#ci#regression#thresholdsHow to reason about it · think before answering
- This tests whether you know thresholds must be measured. Answering 'block if it drops more than five points' earns half credit regardless of the number, because the number was guessed.
- Step one is to measure the noise: hold the task set, trial count, model and prompt fixed, vary only the random seed, run twenty or thirty rounds, and record the maximum, minimum and mean pass rate. The width of that band is how much you can lose to luck alone.
- Step two places the thresholds: a warning line near the band width and a blocking line clearly above it, say one and a half to two times the width. The point is the justification - the threshold must sit outside the noise, or you are blocking bad luck rather than regressions.
- Step three notes that a threshold is a local property of this task set and this trial count. Change the tasks, the k, or the model and you must measure again. Copying somebody else's threshold is the same as having none. A bonus point: trial count drives noise width directly, so a small smoke tier is noisier and needs a wider threshold.
- Step four adds the third state: rather than forcing a verdict in the gray band, rerun that one run with more trials and decide afterwards. Pass, warn and block fit the shape of noisy data better than a binary gate.
- Close on the cost of false alarms, which is the real discriminator: too tight is more dangerous than too loose. Daily false alarms train the team to ignore the gate, and the end state is identical to having no gate while everyone believes they are protected.
分析过程 · 先想清楚再作答
- 这题考的是「知不知道阈值要量出来」。回答「下降超过 5 个百分点就拦」的,无论那个数字是多少都只能拿一半分——问题不在数值,在于它是拍脑袋来的。
- 第一步是**量噪声**:固定任务集、试次数、模型、提示词,只换随机种子,连跑二三十轮,记下通过率的最高值、最低值和均值。区间宽度就是「什么都不改、纯靠运气能掉多少」。
- 第二步才是定位置:警告线压在区间宽度附近,拦截线明显更高(比如区间宽度的一点五到两倍)。关键是要说出判据——阈值要在噪声之外,否则拦的是运气不是退化。
- 第三步要点出「阈值是局部属性」:它属于你这套任务集和这个试次数,换任务集、换 k、换模型都要重新量。**抄别人的阈值等于没定阈值。** 顺带能说出「试次数直接决定噪声宽度,任务少的冒烟档噪声更大、阈值必须更宽」,就是加分项。
- 第四步给出两态之外的做法:落在灰区的那一次不强行下结论,而是加大样本复跑一次再判,三态(通过/警告/拦截)比两态更贴合噪声的真实形态。
- 最后要说清楚误报的代价,这是这题真正的区分点:**太紧比太松更危险**。天天误报会把团队训练成无视门禁的人,最终结果和没有门禁一样,但过程中所有人都以为自己有防护。
Key points
- Measure noise first: vary only the seed across twenty or thirty runs to get the pass-rate band.
- Put thresholds outside the band: warning near the band width, block clearly above it.
- A threshold belongs to this task set and this trial count; re-measure after any change, never copy.
- Use three states, resolving the gray band by rerunning with more trials instead of forcing a verdict.
- A high false-alarm rate teaches the team to bypass the gate; too tight is worse than too loose.
答题要点
- 先量噪声:固定一切只换种子连跑二三十轮,得到通过率的波动区间。
- 阈值放到区间之外:警告线贴近区间宽度,拦截线明显更高。
- 阈值是这套任务集与这个试次数的属性,换任何一项都要重新量,不能照抄。
- 做三态而不是两态,灰区靠加大样本复跑来判,而不是强行下结论。
- 误报率过高会让团队学会跳过门禁,太紧比太松更危险。
Beyond the pass rate, what else goes into a baseline snapshot? What breaks if you leave something out?基线快照里除了通过率,你还会记录什么?少记了会出什么问题?
Common in ChinaCommon overseasIntermediate#ci#baseline#reproducibilityHow to reason about it · think before answering
- The answer is not a list of fields but what each field rules out. Reciting names collapses under the follow-up 'what happens if the prompt version is missing'.
- Start with the shape: a snapshot has two halves. The environment fingerprint - model, prompt version, task-set version, eval framework version, trial count, tier - and the scores, overall plus per task. The fingerprint decides whether two results are comparable at all; the scores decide by how much they differ.
- Go through the costs one by one. No model field and a model swap reads as a regression. No prompt version and a one-sentence edit produces a drop with no commit to blame. No task-set version and two newly added hard tasks look like a worse agent. No trial count and changing k from five to three silently changes the noise structure. Every case has the same shape: the number moved and nobody can say who moved it.
- Call out the prompt version specifically. It is a string, editing it carries none of the ceremony of code review, and many teams keep it in a config console. Versioning it costs almost nothing and converts a whole class of unexplainable drift into something traceable.
- The random seed is the interesting exception: record it, but keep it out of the comparability check. Its purpose is reproduction - when the gate fires you must be able to replay that exact run - while scores from different seeds are precisely the samples you use to estimate noise. Putting the seed in the fingerprint makes every seed change incomparable, which deletes non-determinism from the evaluation.
- Finish with the correct behavior on a fingerprint mismatch: refuse to compare and block, rather than computing a delta and annotating that the model changed. The latter produces a meaningless result that looks entirely normal. Requiring a human to re-record the baseline is the safe path.
分析过程 · 先想清楚再作答
- 这题的答案不是罗列字段,而是「每一项各自排除了哪一种解释」。只背字段名的回答,在追问「那少记提示词版本会怎样」的时候会卡住。
- 先说骨架:快照分两半。一半是**环境指纹**,包括模型、提示词版本、任务集版本、评估框架版本、试次数与档位;另一半才是分数(总体通过率加逐任务通过率)。指纹决定两次结果**能不能比**,分数决定**比出来是多少**。
- 少记的代价可以逐项说:少了模型,换了模型的分数会被当成退化;少了提示词版本,改一句话导致的下降查不到任何对应的提交;少了任务集版本,加了两条难题会被当成 Agent 变差;少了试次数,k 从 5 改成 3 会让分数的噪声结构完全不同却看不出来。**四个问题的共同点是:分数变了,但没人能说出是谁动的。**
- 提示词版本要单独强调:它是一个字符串,改它没有代码评审的仪式感,很多团队还放在配置后台里随时可改。给它编个版本号成本几乎为零,收益是把一整类查不出原因的波动变成可查的。
- 随机种子是个有意思的例外:**要记,但不参与可比性判断**。它的用途是复现——门禁红了要能一字不差重跑那一次;但换种子跑出来的分数恰恰是估计噪声的样本,把它塞进指纹会让每次换种子都被判成不可比,等于把非确定性从评估里删掉了。
- 最后说指纹不一致时的正确行为:**拒绝比较,判拦截**,而不是「算个差值再标注一下模型变了」。后者会产出一个毫无意义却看起来很正常的结论,要求人明确表态重新记录基线,才是安全的。
Key points
- Two halves: environment fingerprint (model, prompt version, task-set version, framework version, trial count, tier) plus scores.
- Each field rules out one explanation; omitting any leaves you with a moved number and no suspect.
- Prompt version is the most commonly missed and the most damaging, because editing it bypasses code review.
- Record the random seed but keep it out of the fingerprint: it exists for reproduction, and including it makes seed changes incomparable.
- On a fingerprint mismatch, refuse to compare and block rather than computing a meaningless delta.
答题要点
- 快照分两半:环境指纹(模型、提示词版本、任务集版本、框架版本、试次数、档位)与分数。
- 每一项各排除一种解释,少记任何一项都会变成「分数变了但说不清是谁动的」。
- 提示词版本最容易漏也最容易出事,因为改它不需要代码评审。
- 随机种子要记但不进指纹:它用于复现,进指纹会让换种子被判成不可比。
- 指纹不一致时正确行为是拒绝比较并拦截,不是硬算一个差值。
Running the full evaluation on every commit is too expensive. How would you tier it, and on what basis?每次提交都跑全量评估太贵,你会怎么分层?依据是什么?
Common in ChinaCommon overseasIntermediate#ci#cost#strategyHow to reason about it · think before answering
- This probes cost awareness and how you express trade-offs. Answering 'sample a random subset each time' misses: random subsets cover different failure classes each run, so the gate becomes intermittently blind.
- The basis for tiering is coverage divided by cost, not importance. The smoke tier should cover the most failure classes with the fewest tasks, and the full tier carries the long tail. So the smoke tier is curated, not sampled.
- A workable three-tier split: smoke on every commit to catch major breakage in minutes; full on tags, pre-release, or a nightly schedule to catch small regressions and the long tail; expensive human review quarterly on a sample, to calibrate the automated graders themselves.
- State the cost of tiering, which is the discriminator here: fewer tasks means fewer trials, and fewer trials means wider noise. For the same agent, going from eight tasks to three can widen the noise band from twelve points to twenty. The smoke tier can therefore only catch large failures; small regressions are invisible to it, and a green smoke run does not mean no regression.
- That implies a practice: measure thresholds separately per tier. Sharing one threshold either makes the smoke tier alarm constantly or makes the full tier far too insensitive.
- Expected follow-up: how do you pick the smoke tasks? By failure-class coverage - one representative per known class, plus the task corresponding to the most recent production incident - and revisit the list periodically, because failure classes shift as the product changes.
分析过程 · 先想清楚再作答
- 这题考成本意识与取舍表达。回答「随机抽一部分任务跑」的方向就偏了——随机抽样每次覆盖的故障类别都不一样,门禁会变得时灵时不灵。
- 分层的依据不是「任务重不重要」,而是**覆盖面除以成本**:冒烟档要用最少的任务盖住最多的故障类别,全量档负责长尾。所以冒烟档是挑出来的,不是抽出来的。
- 一条可用的分层是三档:冒烟档每次提交跑,几分钟内拦住大事故;全量档在打 tag、发版前或每晚定时跑,抓小幅退化与长尾;昂贵的人工评审按季度抽样跑,用来校准自动评分器本身。
- 必须主动说出分层的**代价**,这是这题的区分点:任务变少意味着试次变少,试次变少意味着噪声变宽。同一个 Agent,任务从八条减到三条,噪声区间可能从十二个百分点涨到二十个。**所以冒烟档只能抓大事故,小幅退化它根本看不见**,冒烟档绿了不等于没有退化。
- 由此还能推出一条实践:两档的阈值要分别测,不能共用一套。共用一套的后果要么是冒烟档天天误报,要么是全量档过于迟钝。
- 可预期的追问是「怎么决定哪些任务进冒烟档」。答案是按故障类别覆盖去选:每一类已知的故障至少留一条代表,加上最近一次线上事故对应的那条;并且这份名单要定期复核,因为故障类别会随产品变化。
Key points
- Tier by coverage divided by cost; the smoke tier is curated, never randomly sampled.
- Three tiers: smoke per commit, full on release or nightly, human review quarterly on a sample.
- Fewer tasks means fewer trials and wider noise, so a green smoke run does not prove there is no regression.
- Measure thresholds per tier; a shared threshold either alarms constantly or goes blind.
- Select smoke tasks for failure-class coverage and include the task from the latest production incident.
答题要点
- 分层依据是覆盖面除以成本,冒烟档要挑选而不是随机抽样。
- 三档:冒烟档每次提交、全量档发版或每晚、人工评审按季度抽样。
- 任务少则试次少、噪声更宽,冒烟档只能抓大事故,绿了不等于没退化。
- 两档的阈值必须分别测量,共用一套要么天天误报要么过于迟钝。
- 冒烟档按故障类别覆盖来选,并把最近一次线上事故对应的任务放进去。
Your evaluation gate keeps firing, and the team has learned to skip it. How would you change the process?评估门禁红了,团队却越来越习惯直接跳过。你会怎么改这条流程?
Common in ChinaCommon overseasDeep dive#ci#process#cultureHow to reason about it · think before answering
- This is barely a technical question; it asks whether you have actually maintained such a gate. Answering 'communicate more' or 'mandate investigation before merge' earns nothing - that asks people to fight the incentives instead of changing them.
- First, admit that skipping is rational. If eight of ten red runs go green on a rerun, skipping is the probabilistically correct move. So measure the false-alarm rate; it is usually the root cause, and it is a technical problem, not an attitude problem.
- Second, lower the cost of fixing. A red gate must not emit only a number: give the seed that reproduces the run, which tasks dropped, and which failing transcripts to read first. Cutting investigation from thirty minutes to five removes most of the incentive to skip.
- Third, raise the cost of skipping while keeping a legitimate exit. Intentional behavior changes really do need the bar moved, and the correct expression is 'update the baseline and say why', not 'ignore this run'. Keep the baseline file in version control so every loosening shows up in a diff and gets reviewed. Never ship a one-click skip button - one that exists eventually becomes the default path.
- Fourth, fix the verdict logic itself: three states with a gray-band rerun stop the genuinely ambiguous run from being blocked outright. That removes most false alarms without giving up sensitivity to real regressions.
- Close with the counterintuitive one: if false alarms truly cannot be brought down, demote the block to a warning rather than keep a blocking gate everyone routes around. A bypassed gate supplies false confidence, which is worse than a gate that honestly says it only warns.
分析过程 · 先想清楚再作答
- 这题几乎不考技术,考的是有没有真的维护过这类闸门。答「加强宣导」「规定必须查清楚才能合并」的拿不到分——那是在要求人对抗激励,而不是改激励。
- 第一步要承认一件事:**跳过是理性行为**。如果门禁十次红里有八次重跑就绿,那么跳过在概率上是对的。所以要先量一下误报率——它多半就是根因,而且它是一个技术问题,不是态度问题。
- 第二步是**降低修的成本**。门禁红的时候不能只丢一个数字,要直接给出:用哪个种子能复现、哪几条任务掉了、先读哪几条失败试次的轨迹。排查成本从半小时降到五分钟,跳过的诱因就少了一大半。
- 第三步是**提高跳过的成本,但保留合法出口**。有意的行为变更确实需要放宽标准,它的正确表达是「更新基线并说明原因」,而不是「本次忽略」。把基线文件放进版本库,放宽就会出现在 diff 里、需要有人评审。**绝不提供一键跳过按钮**——存在的跳过按钮最终一定会变成默认路径。
- 第四步是把判定本身修对:引入三态与灰区复跑,让真正模棱两可的那一次不再强行拦截。这直接砍掉大部分误报,而且不牺牲对真实退化的灵敏度。
- 最后要说一条反直觉的:如果误报确实压不下来,**宁可先把拦截线放宽成警告**,也不要留着一条大家都在绕过的拦截。一条被绕过的门禁提供的是虚假的安全感,比一条明确说「我只警告」的门禁更危险。
Key points
- Accept that skipping is rational and measure the false-alarm rate; it is usually the root cause and it is technical.
- Lower the cost of fixing: emit the reproducing seed, the tasks that dropped, and which failing transcripts to read.
- Raise the cost of skipping but keep a legitimate exit: baseline updates go through version control and review, never a one-click skip.
- Adopt three states with a gray-band rerun to remove ambiguous false alarms without losing sensitivity.
- If false alarms persist, demote blocking to warning rather than keep a gate everyone bypasses.
答题要点
- 先承认跳过是理性行为,去量误报率——它通常就是根因,且是技术问题。
- 降低修的成本:红的时候给出复现种子、掉分的任务、该读哪几条失败轨迹。
- 提高跳过的成本但保留合法出口:更新基线要进版本库、要被评审,绝不做一键跳过。
- 引入三态与灰区复跑,砍掉模棱两可那部分误报而不牺牲灵敏度。
- 压不下误报时宁可把拦截降级成警告,也不要留一条大家都在绕过的门禁。
D7 Examining the Suite Itself: Saturation, Broken Tasks and Grading Defects
Your evaluation suite is passing at 98 percent. What do you do next?你们的评估套件通过率已经百分之九十八了,接下来你会做什么?
Common in ChinaCommon overseasIntermediate#evaluation#saturation#suite-healthHow to reason about it · think before answering
- This tests whether you recognize saturation. Answering 'great, the system is high quality' admits you have never thought of an evaluation suite as having a lifecycle.
- State what 98 percent means: this suite has exhausted its improvement signal. Moving from 98 to 98.5 is indistinguishable from noise, and a genuine capability jump might show up as a single point. All that remains is regression value.
- Then give actions, and the first one is not deleting tasks: move the saturated ones into the regression set, where they keep guarding against backsliding. Deleting them forfeits regression protection for that capability, which is the most common mishandling.
- Second, write harder tasks, and say where they come from: recent production incidents, user complaints, scenarios the current agent clearly handles badly but the business genuinely needs. A new capability set should start at a low pass rate, because that is what leaves room to climb.
- Third, check that the ruler measures the right dimension at all. A real counterexample: a team evaluated a new model with one-shot single-turn evals, concluded the gains were unremarkable, and only saw the real benefit after building a multi-step evaluation with real tools. The model had improved; the ruler could not see that dimension.
- Expected follow-up: how do you know the 98 percent is real rather than a loose criterion? Sample the transcripts of passing trials - looking only at failures can never surface a criterion that is too lenient.
分析过程 · 先想清楚再作答
- 这题考的是「能不能识别饱和」。回答「很好,说明系统质量高」的,等于承认自己从没想过评估也有生命周期。
- 先说清楚 98% 意味着什么:这个套件的**改进信号已经耗尽**。分数从 98 涨到 98.5,你分不清是真进步还是噪声;而一次真正的能力飞跃,在这个套件上可能只体现为一个百分点。它现在只剩回归价值。
- 然后给动作,而且第一个动作不是删题:**把饱和的任务移进回归集**,它们继续守着「不要退步」这条线。删掉等于放弃了对这项能力的回归保护,这是最常见的错误处置。
- 第二个动作是出更难的新题,而新题从哪来要说得出来:从最近的线上故障、从用户抱怨、从当前 Agent 明显做不好但业务真的需要的场景。新的能力集应该**从低通过率起步**,那才是有爬坡空间的尺子。
- 第三个动作是检查尺子本身量的维度对不对。有个真实的反例:某团队用一次性的单轮评估测新模型,结论是提升不明显;改成多步、带真实工具的评估之后,才看到它在长任务上的真实收益。**不是模型没进步,是尺子量不到那个维度。**
- 可预期的追问是「那你怎么知道 98% 是真的,不是判据太松」。答案是抽样读通过样本的轨迹——只看失败样本永远发现不了判据太松这类问题。
Key points
- 98 percent means the improvement signal is exhausted; small deltas are indistinguishable from noise.
- First action is moving saturated tasks into the regression set, not deleting them.
- Write harder tasks sourced from incidents, complaints, and business-critical weak spots.
- A new capability set should start at a low pass rate to leave room to climb.
- Verify the ruler measures the right dimension - single-turn evals miss multi-step gains.
答题要点
- 98% 说明改进信号已耗尽,只剩回归价值,小幅变化无法与噪声区分。
- 第一个动作是把饱和任务移入回归集,**不是删掉**——删掉会放弃回归保护。
- 出更难的新题,来源是线上故障、用户抱怨、业务需要但当前做不好的场景。
- 新能力集应从低通过率起步,才有爬坡空间。
- 检查尺子量的维度对不对:单轮评估可能量不到多步能力的进步。
A model suddenly drops a lot of points on your suite. How do you tell a real regression from a broken grader?一个模型在你的套件上突然掉了很多分,你怎么区分是模型退化还是判分写错了?
Common in ChinaCommon overseasDeep dive#evaluation#debugging#grading-defectHow to reason about it · think before answering
- This tests your debugging order. Saying 'roll back the model' loses half the credit - you have not yet shown the problem is on the model side.
- First look at the shape of the drop. A genuine model regression is usually diffuse: everything sags a little. A grading defect is typically concentrated: one class of tasks goes to zero while the rest is untouched. The shape alone is a strong signal.
- Second, read the failure reasons. Grading defects have a recognizable fingerprint: two numbers differing in the last digits yet judged unequal (strict equality, especially after a float conversion), case or whitespace mismatches, and the 'expected X, got X' pattern that reads identical yet fails. In one public case a model scored 42 because '96.12' did not equal '96.124991...', and the same model on the same suite scored 95 once grading was fixed - fifty-three points entirely from the evaluation.
- Third, validate with a reference solution: feed a hand-verified correct answer to the grader. If the reference solution fails, the fault is one hundred percent in the evaluation. This is the cleanest test.
- Fourth, check whether the environment moved: dependency upgrades, an upstream API changing its response shape, test data someone edited. These masquerade as model regressions and cause the most finger-pointing.
- Only then read transcripts to confirm the model really did worse. The order runs from the evaluation side toward the model side, because evaluation-side faults are both easier to check and more common. Expected follow-up: how do you make this faster? Record a complete environment fingerprint in the baseline - model version, prompt version, task-set version, random seed. Every missing field is one more variable you cannot rule out.
分析过程 · 先想清楚再作答
- 这题考排查顺序。直接说「回滚模型」的丢一半分——你还没证明问题在模型那边。
- 第一步是看**掉分的形状**。真实的模型退化通常是弥散的:各类任务普遍降一点。判分缺陷往往是**集中的**:某一类任务集体归零,而其他类纹丝不动。形状本身就是很强的线索。
- 第二步读失败理由。判分缺陷有很典型的指纹:两个数字只差一点点却判不相等(严格相等比较,尤其是浮点换算之后)、大小写或空格差异、以及「期望 X 实际 X」这种看起来一模一样却判失败的情形。有个公开案例,某模型因为「96.12」不等于「96.124991…」初评 42 分,判分修好后同一套题得 95 分——**五十三个百分点全部来自评估缺陷**。
- 第三步用参考解验证:把一条人工做对的答案喂给评分器。参考解都过不了,那 100% 是评估的问题,与模型无关。这是最干净的判据。
- 第四步检查环境是否变了:依赖升级、上游 API 改了返回格式、测试数据被人动过。这些会伪装成模型退化,而且在团队里最容易互相甩锅。
- 最后才是读轨迹确认模型确实做错了。整个顺序是**从评估侧走向模型侧**,因为评估侧的问题更容易排查、也更常见。可预期的追问是「怎么让这个排查变快」——答案是基线快照里记全环境指纹(模型版本、提示词版本、任务集版本、随机种子),少记一样就多一个没法排除的变量。
Key points
- Check the shape: real regressions are diffuse, grading defects are concentrated.
- Read failure reasons for grading fingerprints: strict equality, float conversion, case and whitespace.
- Validate with a reference solution - if a known-correct answer fails, the fault is in the evaluation.
- Check environment changes: dependencies, upstream response shapes, edited fixtures.
- Debug evaluation-side first; a complete environment fingerprint makes variables ruleable-out.
答题要点
- 先看掉分形状:真实退化弥散,判分缺陷集中在某一类任务。
- 读失败理由找判分指纹:严格相等、浮点换算、大小写与空格差异。
- 用参考解验证:人工做对的答案过不了评分器,就一定是评估的问题。
- 检查环境变更:依赖升级、上游返回格式变化、测试数据被改。
- 排查顺序从评估侧到模型侧;基线快照记全环境指纹才能快速排除变量。
Why should you not trust an evaluation score before someone has read transcripts? And how do you make transcript reading a team habit?为什么说没读过轨迹就不该相信评估分数?你会怎么把读轨迹变成团队习惯?
Common in ChinaCommon overseasIntermediate#evaluation#transcripts#processHow to reason about it · think before answering
- The first half asks why, the second asks how. Answering only the first scores poorly - 'we should read more transcripts' carries no information, everyone agrees with it, and nobody does it.
- Be concrete about what a score cannot show: the grader may be wrong (the score is still a number), the task may be ambiguous (the agent solved a different reasonable problem), the agent may have exploited the criterion (editing tests so tests pass), the detector itself may be vacuous (always firing or never firing). None of these four surface in the score; all of them are visible in a specific run.
- Call out the exploitation case, because the attribution is easy to get backwards. An agent finding a shortcut is not cheating - it is evidence of a gap between your criterion and your actual intent. Every exploit is a free audit that points at exactly where the gap is.
- For adoption, the key move is turning the practice into an artifact. Have the report emit a reading list for this round, with a reason attached to each entry. People tick off a list; they do not act on an exhortation.
- Spell out the sampling rule, and it must not be purely random: failures always go in because they carry the most information, and a few passing trials go in too - looking only at failures breeds the illusion that nothing works, and a criterion that is too lenient is visible only among passes.
- Expected follow-up: how many is enough? Honestly there is no universal number, but there is an operational test: once you start seeing the same failure cause repeat, the marginal value of this round has dropped. Also mandate a re-read after any change to graders, model, or task set, because that is exactly when new problems appear.
分析过程 · 先想清楚再作答
- 这题前半问原因,后半问落地。只答前半段拿不到高分——「应该多读轨迹」这句话本身毫无信息量,所有人都同意,然后没有人做。
- 原因要具体列出分数看不见的东西:判分可能写错(分数照样是个数)、任务描述可能有歧义(Agent 做了另一件合理的事)、Agent 可能钻了判据的空子(改测试让测试通过)、检测器本身可能恒真(永远报警或永远沉默)。**这四类没有一类会体现在分数上**,它们只在具体的运行记录里看得见。
- 特别要提钻空子这一类,因为它的归因容易搞反:Agent 找到捷径不是它作弊,是**你的判据和你的真实意图之间有差距**。每一次钻空子都是一次免费的评估审计,精确指出了差距在哪。
- 落地部分的关键是**把它从建议变成产物**。做法:让评估报告自动输出一份「本轮该读的轨迹」清单,附上每条为什么被选中。人对着清单打勾,比对着一句倡议要可执行得多。
- 抽样规则要说清楚,而且不能是纯随机:失败样本必须进(信息密度最高),**通过样本也要抽几条**——只看失败会形成「它什么都做不对」的错觉,而且判据太松这类问题**只有在通过样本里才看得见**。
- 可预期的追问是「读多少条才够」。诚实的答案是没有普适数字,但可以给操作性判据:读到你开始重复看到同一类失败原因,这一轮的边际收益就下来了。另外每次改判据、换模型、加新任务之后必须重读,因为那正是新问题最可能出现的时候。
Key points
- Scores hide four failure classes: broken grading, ambiguous tasks, criterion exploitation, vacuous detectors.
- An exploit reveals a gap between criterion and intent - it is a free audit, not cheating.
- Make it an artifact: have the report emit a reading list with a reason per entry.
- Sampling is not random: always include failures, and include some passes, since a lenient criterion is only visible there.
- Mandate a re-read after any grader, model, or task-set change.
答题要点
- 分数看不见四类问题:判分写错、任务有歧义、Agent 钻空子、检测器恒真。
- Agent 钻空子说明判据与真实意图有差距,是免费的评估审计,不是作弊。
- 落地要把它变成产物:评估报告自动输出「本轮该读的轨迹」清单并附选中理由。
- 抽样不能纯随机:失败样本必进,通过样本也要抽——判据太松只在通过样本里可见。
- 改判据、换模型、加新任务之后必须重读,那是新问题最可能出现的时候。
Build your own evaluation framework or adopt an existing one? Give your decision criteria, and say what you check in the license.自建评估框架还是用现成的?说出你的判断依据,以及选型时会看许可的哪些方面。
Common in ChinaCommon overseasDeep dive#evaluation#tooling#licensingHow to reason about it · think before answering
- Two things are being tested and most people only answer the first. The front half is engineering judgment; the back half is licensing awareness, which is unavoidable in real procurement and where candidates most often come up short.
- For the engineering half, give criteria rather than a verdict: how unusual your tasks and criteria are (the more bespoke the business rule, the costlier the adaptation), whether you need self-hosting, whether anyone will maintain it, and the decisive one - are you buying a framework or a platform? The hard part of evaluation was never the code that loops over test cases; it is high-quality tasks and graders, and you are writing those yourself regardless. Framework choice matters less than most people assume.
- The sensible compromise: adopt something for running batches and rendering reports, write tasks and graders yourself. Pick a framework quickly and spend the saved effort on test-case quality.
- For licensing, say what you check: whether you may self-host; whether you may offer it to third parties as a service, which disqualifies a good number of source-available projects; whether different directories carry different terms (an open core with a separately licensed enterprise directory is a common structure); and whether any copyleft term reaches your proprietary code.
- Stress one operational detail: read the LICENSE file in the repository, not the badge on the hosting platform. That field is auto-detected and reports 'not specified' whenever detection fails - and detection fails precisely on the projects with unusual arrangements. Three verified examples: an observability platform widely described as MIT has LICENSE text carving out three enterprise directories; an evaluation platform widely called open source ships under Elastic License 2.0, which explicitly forbids offering it as a hosted service and is not OSI-approved; and a framework commonly attributed to a model vendor actually comes from a different organization.
- Expected follow-up: what if the project stops being maintained? Make that a selection criterion too - does the license let you fork and maintain it, how many organizations depend on it, can your team read the core logic? A dependency you cannot take over is a dependency that will eventually block you.
分析过程 · 先想清楚再作答
- 这题有两个考点,很多人只答得出第一个。前半是工程判断,后半是法务意识——后者恰恰是真实选型里绕不过去的一关,也是最容易露怯的地方。
- 工程判断部分先给判据而不是结论:任务与判据的特殊程度(业务判据越独特,现成框架的适配成本越高)、要不要自托管、团队有没有人维护、以及最关键的一条——**你要的是框架还是平台**。评估的难点从来不在跑测试用例的那段代码,而在**高质量的任务和评分器**,那部分无论如何都得自己写。所以框架选型的权重其实没有多数人以为的那么高。
- 合理的折中是:跑批与报告用现成的,任务与评分器自己写。先快速选一个框架,把精力压在测试用例质量上。
- 许可部分要说清楚看什么:① **能不能自托管**;② **能不能作为服务提供给第三方**——这一条会卡住相当一部分「源码可见」的项目;③ 有没有**分目录的差异化许可**(核心开源、企业版目录另有条款是常见结构);④ 传染性条款会不会影响你的闭源部分。
- 特别要强调一个操作细节:**看许可要落到仓库里的 LICENSE 文件,不要只看代码托管平台页面上那个标签**。那个字段是自动识别的,识别不出来就报「未指定」,而它识别不出来的恰恰是那些做了特殊安排的项目。实测过三个例子:一个被广泛称作 MIT 开源的可观测平台,LICENSE 原文写明有三个企业版目录另有许可;一个被普遍称作开源的评估平台用的是 Elastic License 2.0,明文禁止作为托管服务提供给第三方,并不是 OSI 认可的开源;还有一个常被误认为出自某家模型厂商的框架,其实来自另一个机构。
- 可预期的追问是「这个项目停止维护了怎么办」。答案是把它当作选型判据之一:许可允许你 fork 并自行维护吗?有多少组织在依赖它?核心逻辑你的团队读得懂吗?一个你接不了手的依赖,就是一个将来会卡住你的依赖。
Key points
- Criteria: how bespoke your rules are, self-hosting needs, maintenance ownership, framework versus platform.
- The hard part is tasks and graders, which you write either way, so framework choice carries less weight.
- License checks: self-hosting, offering as a service, per-directory terms, copyleft reach.
- Read the repository's LICENSE file - the auto-detected badge is often wrong on projects with unusual terms.
- Treat abandonment as a criterion: can you fork it, who else depends on it, can your team take it over?
答题要点
- 判据:判据的特殊程度、是否要自托管、有没有人维护、要的是框架还是平台。
- 评估的难点在任务与评分器,那部分必须自己写,所以框架选型权重没那么高。
- 许可看四件事:能否自托管、能否作为服务提供给第三方、是否分目录差异化、有无传染性。
- **必须读仓库里的 LICENSE 文件**,平台上那个自动识别的标签在特殊安排的项目上经常不准。
- 把「停止维护怎么办」纳入选型:许可是否允许 fork、依赖它的组织多不多、团队能否接手。
Agent Security in 5 Days
D1 Threat modeling: the lethal trifecta, trust boundaries, and how to use two OWASP lists as checklists
What is the lethal trifecta, and why is it a compositional risk rather than a single-point defect?什么是致命三件套?为什么说它描述的是组合风险而不是单点缺陷?
Common in ChinaCommon overseasBasic#threat-modeling#lethal-trifectaHow to reason about it · think before answering
- This question separates people who hunt for bugs from people who read structure. Listing the three legs is easy; explaining why none of your existing quality gates catches it is where the signal is.
- Name the three legs first: access to private data, exposure to untrusted content, and the ability to communicate externally. Then state immediately that they are ANDed, not ORed, and that all three must hold before a successful prompt injection becomes data exfiltration.
- Show why it is compositional by testing each leg alone. Querying the customer database is a product requirement, reading tickets and linked web pages is a product requirement, and sending notifications is a product requirement. None is a defect, so the risk exists only in the combination and lives in no single line of code.
- That leads to a strong conclusion: code review, dependency scanning and unit tests all miss it. The first two inspect one snippet or one library, the third inspects one function's inputs and outputs, while this risk is a property of the tool inventory as a whole.
- Land it on how the criterion is used: it prescribes removal, not defense. Breaking any one leg breaks the chain, and the three legs rarely cost the same. In most systems the cheapest cut is downgrading free-form outbound sending to drafting something a human then sends.
- Expect the follow-up: which leg do you cut? Give the mechanical rule, cut the leg backed by the fewest tools, and stress that the test is capability, not habit. Saying you never send customer data out is not an argument, because the attacker gets the tool inventory, not your habits.
分析过程 · 先想清楚再作答
- 这题在考你把安全当成「找 bug」还是当成「看结构」。能背出三条边的人很多,能说清「为什么现有的质量闸门一条都拦不住它」的人很少,区分度全在后半句。
- 先给三条边:访问私有数据、接触不可信内容、能对外通信。然后立刻补一句它们是「与」的关系——三者同时具备,一次成功的提示注入才能升级成一次数据外泄。
- 怎么拆「为什么是组合风险」:逐条问「这一条单独存在算不算缺陷」。查客户库是产品需求,读工单和网页是产品需求,发邮件通知也是产品需求,三条都不是 bug。风险只在拼起来的时候才出现,所以它不在任何一行代码里。
- 由此推出一个很有说服力的结论:代码审查、依赖扫描、单元测试都发现不了它——前两个看的是单段代码和单个依赖,第三个看的是单个函数的输入输出,而这条风险是工具清单的整体属性。
- 结论落到用法上:判据的价值是给拆法不是给防法。三条边拆掉任意一条链就断,而三条的代价通常差很远,多数系统里最便宜的一刀是把「自由对外发送」降级成「写回草稿由人点发送」。
- 可预期的追问:那你怎么判断该拆哪条?答机械办法——数每条边涉及的工具数,最少的那条最省;同时强调判的是能力不是行为,「我们从来不往外发客户资料」不构成理由,攻击者用得上的是工具清单。
Key points
- Three legs: private data access, untrusted content, and external communication. Only all three together form a complete exfiltration path.
- Each leg alone is a legitimate product requirement, not a defect. The risk is created by the combination.
- That is why code review, dependency scanning and unit tests miss it: it is a property of the whole tool inventory, not of any single line of code.
- Use it to remove, not to defend. Cutting any one leg breaks the chain, and the cheapest cut is usually turning free-form sending into a human-confirmed draft.
- The test is capability, not behavior. If the tool is in the inventory, the capability exists regardless of how you normally use it.
答题要点
- 三条边:访问私有数据、接触不可信内容、能对外通信;三者同时具备才构成完整的外泄链路。
- 每一条单独看都是正常产品需求,没有任何一条是 bug,风险是组合出来的。
- 因此代码审查、依赖扫描、单元测试都发现不了它——它是工具清单的整体属性,不在任何一行代码里。
- 判据的用法是拆不是防:拆掉任意一条边攻击链就断,最便宜的通常是把自由对外发送降级成人工确认。
- 判的是能力不是行为:工具清单里有就算具备,跟你平时用不用无关。
What is the difference between direct and indirect prompt injection, and which is harder to defend against?直接注入和间接注入有什么区别?哪一种更难防,为什么?
Common in ChinaCommon overseasIntermediate#prompt-injection#attack-surfaceHow to reason about it · think before answering
- The hinge is the second half. Answering that indirect injection is harder because it is stealthier reads as marketing. The interviewer wants to know which concrete step gets harder.
- Start with the definitional difference. Direct injection means the attacker talks to your agent, so the payload enters through user input. Indirect injection means the attacker writes into something your agent will eventually read: a ticket comment, a web page, an email, a third-party API response.
- Break down the difficulty in three layers. First, entry count: user input is one channel, while tool returns give you one channel per tool and a new one with every tool you add. Second, identity: an indirect attacker needs no account and leaves no trace in your access logs, so attribution is nearly impossible. Third, timing: the payload can sit on a page for weeks until someone happens to paste that link.
- Add the criterion most people miss. Anything the agent reads into context is attack surface, including fields in your own database whenever those fields are filled in by external users. This is what decides which arrows get marked untrusted on the trust-boundary diagram.
- Conclusion: indirect is harder, and the hard part is attribution rather than detection. You cannot even say who wrote the text, so defenses have to be structural rather than attacker-identifying.
- Expect the follow-up: can you just filter untrusted content? Name the ceiling. Unlike SQL injection there is no syntactic boundary; in natural language instructions and data look identical, so the working assumption is that injection will succeed and the job is to make success worthless.
分析过程 · 先想清楚再作答
- 这题的题眼在第二问。只答「间接注入更难防,因为更隐蔽」是营销话术,面试官想听的是「难在哪个具体环节」。
- 先给定义差:直接注入是攻击者自己跟你的 Agent 说话,载荷经过用户输入这个入口;间接注入是攻击者把话写进一份你的 Agent 迟早会去读的东西里——工单备注、网页正文、邮件、第三方接口返回。
- 怎么拆「难在哪」:分成三层说。第一层是入口数量,用户输入只有一个口子,而工具返回有多少个工具就有多少个口子,而且每加一个工具就多一个。第二层是身份,间接注入的攻击者不需要你系统的账号,也不会出现在你的访问日志里,事后溯源极难。第三层是时间差,载荷可以先躺在一个网页上,等哪天有人贴了这个链接才生效。
- 还要点出一个很多人漏掉的判据:凡是 Agent 会读进上下文的地方都是攻击面,包括你自己数据库里的字段,只要那个字段是外部用户填的。这一条决定了信任边界图上该把哪些箭头标成 untrusted。
- 结论:间接注入更难防,但难的不是检测而是归责——你连「谁写的这段话」都答不上来,所以防御必须落在结构上而不是落在识别攻击者上。
- 可预期的追问:那能不能把不可信内容都过滤一遍?要答出上限——注入不像 SQL 注入那样有明确的语法边界,自然语言里指令和数据长得一模一样,所以业界共识是假设它一定会成功,然后让它成功了也没用。
Key points
- Direct injection enters through user input; indirect injection plants the payload in content the agent will read on its own.
- Indirect is harder: entry points scale with tool count, the attacker needs no account and leaves no log trace, and the payload can be planted long before it fires.
- The working rule is that anything read into context is attack surface, including your own database fields when external users fill them in.
- The genuinely hard part is attribution, not detection, so defenses must be structural rather than attacker-identifying.
- Filtering has a ceiling: natural language has no syntactic boundary between instruction and data, so injection is not a bug that gets fixed.
答题要点
- 直接注入走用户输入这个口子,攻击者自己跟 Agent 说话;间接注入把载荷写进 Agent 迟早会读的内容里。
- 间接注入更难防:入口随工具数量增长、攻击者不需要账号也不进日志、载荷可以提前埋好等待触发。
- 判据是「凡是会被读进上下文的地方都是攻击面」,包括自己数据库里由外部用户填写的字段。
- 真正难的是归责而不是检测,所以防御必须落在结构上而不是落在识别攻击者上。
- 过滤有上限:自然语言里指令和数据没有语法边界,注入不是一个能被彻底修好的 bug。
Given an agent already in production, how would you map its trust boundary and find its highest-risk path in half an hour?给你一个已经上线的 Agent,你怎么在半小时内画出它的信任边界并找出最高危的那条链路?
Common in ChinaCommon overseasDeep dive#threat-modeling#trust-boundaryHow to reason about it · think before answering
- This question tests process, not knowledge. Someone who can recite the trifecta but cannot give executable steps is exposed here; the interviewer wants evidence you have actually done it.
- Fix the scope first. Half an hour is not enough for a full architecture diagram, so draw only three things: every source that injects content into the context, every source that can read private data, and every exit that produces an externally observable side effect. Everything else waits.
- Derive entries and exits from the tool inventory rather than by asking people. For each tool ask two questions: can an outsider write what it returns, and after it runs can anyone outside see a change? Two questions classify every tool as entry, exit, both or neither.
- Call out two rookie traps, which immediately separates you from the pack. First, marking only user input as untrusted and forgetting tool return values. Second, reading exit as sending a message, when writing back to a ticket counts, and so does embedding data in a URL you fetch, since even a failed request leaves the domain and path in someone else's logs.
- Conclusion: look for a path from an untrusted entry to an exit that passes through private data. If one exists, log a critical item whose mitigation names the cheapest leg to cut, chosen by counting the tools behind each leg. Then walk both OWASP lists asking what each item looks like in this specific system.
- Expect the follow-up: why two lists? Explain the split. The LLM Top 10 covers the model-application layer, while the Agentic Top 10 covers what only an acting agent has, such as memory poisoning and tool misuse. A question-answering bot needs only the first; an agent that calls tools and changes state loses a whole class of risk without the second.
分析过程 · 先想清楚再作答
- 这题在考流程而不是知识。会背三件套但给不出可执行步骤的人,到这一步就露馅了;面试官想确认的是你真的干过这件事。
- 先把范围钉死:半小时内不可能画完整架构图,所以只画三样东西——所有会往上下文里灌内容的入口、所有能读到私有数据的来源、所有能产生外部可观察副作用的出口。其余一律先不画。
- 怎么拆:入口和出口都从工具清单里读,不要靠问人。每个工具问两句话——它的返回值是不是外部可写的,它执行完之后外面有没有人能看见变化。两句话就能把一个工具归进入口、出口或者两者都不是。
- 这里要主动点出两个新手陷阱,能立刻拉开差距:一是只把用户输入标成不可信而忘了工具返回值,二是把出口理解成「发消息」——其实写回工单、拼进 URL 去请求一个外部地址,哪怕请求失败,域名和路径也已经进了对方的日志,这些都是出口。
- 结论:画完看有没有一条线能从不可信入口走到出口,并且中途经过私有数据。有就记一条 critical,处置写「拆哪条边最省」,判据是数每条边涉及的工具数。然后再用 OWASP 的两张清单逐条问「这一条在我这儿长什么样」,把漏网的补上。
- 可预期的追问:为什么是两张清单不是一张?答分工——LLM Top 10 管模型应用这一段,Agentic Top 10 管会自己动手的 Agent 才有的东西,比如记忆投毒和工具滥用。只会回答问题的机器人用前一张够了,能调工具改状态的 Agent 缺了后一张会整类漏掉。
Key points
- Scope it: draw only untrusted entries, private data sources and external exits, not a full architecture diagram.
- Derive them from the tool inventory by asking, per tool, whether outsiders can write its return value and whether its effects are externally visible.
- Two common mistakes: forgetting that tool return values are untrusted entries, and treating exits as messaging only, when ticket writebacks and data-bearing URLs also qualify.
- Look for a path from an untrusted entry through private data to an exit. If one exists it is critical, and the mitigation names the cheapest leg to cut.
- Close with both OWASP lists, asking what each item looks like in this system, and deliver a risk table with evidence rather than a prose document.
答题要点
- 限定范围:只画不可信入口、私有数据源、外部出口三样,不画完整架构图。
- 从工具清单推导:每个工具问「返回值是不是外部可写」和「执行后外面看不看得见」两句话。
- 两个易错点:工具返回值也是不可信入口;出口不只是发消息,写回工单和拼进 URL 的请求都算。
- 找链路:有没有一条线从不可信入口经过私有数据走到出口,有就是 critical,处置写拆哪条边最省。
- 最后用 LLM Top 10 与 Agentic Top 10 逐条问「这条在我这儿长什么样」补漏,产出是一张带证据的风险表而不是一份文档。
D2 An injection range and the input side: what four tiers of defense stop, and where each one fails
How many injection surfaces does an agent have, and do tool results count as untrusted input?一个 Agent 的注入面一共有几条?工具返回的内容算不算不可信输入?
Common in ChinaCommon overseasBasic#prompt-injection#threat-modelHow to reason about it · think before answering
- The hinge is the second half. Anyone who answers 'user input' will pile every defense onto the input box, and that is not where real incidents come from.
- Give a reusable enumeration rule: split the final prompt by provenance and ask of each segment, who can write into this? Any segment whose answer is not 'only us' is an injection surface.
- By that rule a typical ticket assistant has at least three: the user-submitted ticket body, web pages the agent fetches itself, and tool results (retrieval hits, API responses, file contents).
- So tool results absolutely count, and they are the surface people miss, because they look like data from our own systems. If anyone can write into that data source, it is equivalent to external input — a poisoned knowledge base or an internal API that carries one imperative sentence in its payload.
- Raise the conclusion one level: an injection surface is not about who is speaking, it is about who holds write access to that text. That is why the attacker never needs to talk to your agent — controlling one page that gets read is enough.
- Expect the follow-up: do messages between agents count? Yes — if an upstream agent read untrusted content, its output inherits that taint. Trust labels must travel with the data flow rather than be assigned by component identity.
分析过程 · 先想清楚再作答
- 这题的题眼在第二句。只答「用户输入」的人,防御一定全堆在输入框那一条路上,而真实事故基本不从那里来。
- 给一条可复用的枚举判据:把最终送进模型的那串文本按来源拆开,问每一段「谁能往里写字」。凡是答案不是「只有我们自己」的,就是一条注入面。
- 按这条判据数,一个典型的工单助手至少有三条:用户提交的工单正文、Agent 自己去抓的网页正文、以及工具返回的内容(检索结果、API 响应、文件内容)。
- 所以工具返回**算**不可信输入,而且是最容易被漏掉的一条:它长得像「我们自己系统给的数据」,但只要有人能往那个数据源里写东西,它就等价于外部输入。被投毒的知识库、内部 API 里夹带的一行祈使句,都是这个形态。
- 把结论升一级:注入面的本质不是「谁在说话」,是「这段文本的写入权限属于谁」。这也解释了为什么攻击者根本不需要跟你的 Agent 说话——他只要控制一个会被读到的页面就够了。
- 可以预期的追问:那多 Agent 之间互相传的消息算不算?算——上游 Agent 的输出如果它自己读过不可信内容,那它的输出就继承了那份不可信度。信任标签要跟着数据流传递,而不是按组件身份判定。
Key points
- Enumerate by who can write the text, not by who is talking to the agent
- A typical agent has at least three surfaces: submitted content, fetched web pages, and tool results
- Tool results are untrusted input and the most commonly missed one — poisoned knowledge bases and instruction-carrying API responses are the same shape
- An upstream agent's output inherits whatever taint it read, so trust labels must follow the data flow
答题要点
- 枚举判据是「这段文本谁能写」,不是「谁在跟 Agent 说话」
- 典型 Agent 至少三条注入面:用户提交的正文、Agent 抓取的网页、工具返回的内容
- 工具返回算不可信输入,且最易被漏掉——被投毒的知识库和夹带指令的 API 响应是同一形态
- 上游 Agent 的输出会继承它读过的不可信度,信任标签必须跟着数据流走
Why are prompt delimiters and detectors not security boundaries, and how far does each actually get you?为什么说提示词里的分隔符和检测器都不能当作安全边界?它们各自能做到什么程度?
Common in ChinaCommon overseasIntermediate#prompt-injection#input-defense#adaptive-attackHow to reason about it · think before answering
- This question is about the gap between 'effective' and 'a boundary'. Saying they are useless reads as never having measured; saying they work reads as never having been attacked.
- Start with the definition: a security boundary means that even when the attacker knows it exists and knows how it is implemented, they still cannot do the thing. No prompt-level rule meets that bar, because the thing enforcing it is a probabilistic model, not an if statement — you are negotiating, not enforcing.
- Then give the tiers with numbers, which is where the signal is. Plain code fences buy you roughly nothing: a fence has meaning for a renderer, not a verifiable meaning for a model, and the attacker simply wraps their own payload in a fence too. Explicit provenance markers plus a line saying the region is data is the first real gain — it stops the 'ignore all previous instructions' family. Above that, spotlighting rewrites characters inside the data region, which kills forged structure markers, because after the rewrite no text inside the region can be byte-identical to a real marker.
- Detectors fail differently from the tiers below. Those fail because the model may not comply; a detector fails because its feature can be taken apart. It watches observable signals, so splitting the send verb and the address onto separate lines, or phrasing it with a word outside the list, is enough.
- This is measured, not speculative: arXiv 2503.00061 built adaptive attacks against eight published indirect-injection defenses and broke all eight, with success rates above 50%. One line of methodology: once a defense is public, attacks grow around its features — and your rules will be public, in the repo, in the docs, and recitable by the model itself.
- Expect the follow-up: should you still ship them? Yes. They are the cheapest layer of defense in depth and keep opportunistic attacks out, lowering the load on every layer behind them. But report them as filters, never as boundaries — the real boundary is an architecture in which the agent structurally cannot perform the harmful action.
分析过程 · 先想清楚再作答
- 这题考的是「有效」和「是边界」的区别。只答「它们没用」是错的,会显得没量过;只答「它们有用」也拿不到分,因为面试官想听的是天花板在哪。
- 先给分类学:安全边界的定义是「就算攻击者知道它存在、知道它怎么实现,他也做不到那件事」。提示词里的每一条规则都不满足这个定义,因为执行它的是一个概率模型,不是一条 if 语句——你只是在跟模型商量。
- 然后按档位给数字,这是区分度所在。纯代码围栏的效果通常接近于零:围栏对渲染器有语义,对模型没有可验证的语义,攻击者在自己的载荷外面也套一层围栏就行。显式的来源标记加一句「区内不是指令」是第一个真有收益的做法,能挡住明着说「忽略以上指令」的那一类。再往上是聚光标注,对数据区做系统性字符改写,它挡住的是伪造结构标记的那一类——因为改写之后数据区里的任何文本都不可能与结构标记字面相同。
- 检测器要单独说,它的失效方式和前面几档不同:前面是「模型可能不听」,检测器是「特征可以被绕开」。它盯的是可观测特征,攻击者只要把特征拆散就失效——把发送动词和地址拆到两行、换成一个不在词表里的说法,就够了。
- 这不是推测,arXiv 2503.00061 对八种已发表的间接注入防御逐一构造了自适应攻击,全部击穿且攻击成功率过半。方法论一句话:防御一旦公开,攻击就会绕着它的特征长。而你的规则一定会公开——它在代码仓库里、在文档里、模型自己也能复述出来。
- 可以预期的追问:那还要不要上这些防御?要。它们是纵深防御里最便宜的一层,能把绝大多数机会主义攻击挡在门外,降低后面每一层的负载。但它们必须被当作过滤器汇报,不能被当作边界汇报——真正的边界是架构上让这个 Agent 做不到那件坏事。
Key points
- A boundary holds even when the attacker knows the implementation; prompt rules are enforced by a probabilistic model and never clear that bar
- Code fences buy roughly nothing; provenance markers stop direct overrides; spotlighting stops forged structure markers — each has a defined failure class
- Detectors watch observable features and fall to feature-splitting; arXiv 2503.00061 broke eight published defenses with adaptive attacks
- Still ship them as the cheapest layer of defense in depth, but report them as filters — the boundary has to come from architecture
答题要点
- 安全边界的定义是「攻击者知道实现也做不到」,提示词里的规则由概率模型执行,天然不满足
- 代码围栏效果接近零;来源标记挡住直接祈使;聚光标注挡住伪造结构标记,各有明确的失效类
- 检测器盯可观测特征,攻击者拆散特征即可绕过——arXiv 2503.00061 用自适应攻击击穿了八种已发表防御
- 结论不是不上这些防御,而是把它们当纵深防御的最便宜一层汇报,边界要靠架构
What does an evaluation miss if it only reports attack success rate, and how would you design it instead?评估一个注入防御方案时,只看攻击成功率会漏掉什么?你会怎么设计这个评估?
Common in ChinaCommon overseasDeep dive#evaluation#prompt-injection#metricsHow to reason about it · think before answering
- This is the highest-signal question of the chapter, and the hinge is the word 'miss'. It tests whether you have actually run an evaluation; people who have open with the anti-pattern.
- Lead with that anti-pattern, it is the fastest proof: make the agent refuse everything and attack success rate is 0%. A single-metric report cannot distinguish itself from that degenerate solution, so an ASR-only result is never trustworthy.
- The design follows: every safety metric must be reported paired with its capability cost. The minimum pair is attack success rate (share of the attack set where the attacker's goal was actually achieved) and utility under attack (completion rate of the benign task set under the same defense). AgentDojo uses three; the extra one is benign utility with no attack present, which separates damage caused by the defense from an agent that was simply bad at the task.
- The second thing people miss is the judging criterion. It must be whether the attacker's goal was achieved — did the data actually leave — not whether the model said something suspicious. Judge by text and a run where the model silently called the send tool while saying nothing about it gets scored as safe, which is exactly what real exfiltration looks like.
- Third is the composition of the sets. Group attack cases by family instead of piling up counts, or one defense that happens to stop a single family will inflate the headline number. Include at least one adaptive case built against the current defense, otherwise you are measuring performance against yesterday's attacks. And seed the benign set with requests that look like attacks — a compliance rule asking to copy an internal mailbox — so false positives are actually measurable.
- Expect the follow-up: how does this go into CI? A two-threshold gate — fail if attack success rate rises above the ceiling or task completion drops below the floor. A single threshold is defeated by simply making the agent more conservative.
分析过程 · 先想清楚再作答
- 这题是本章区分度最高的一道,题眼在「漏掉」。它考的不是防御知识,是你有没有真的做过评估——做过的人第一句就会说反模式。
- 先把反模式甩出来,这是最快的证明:**把 Agent 改成什么都不做,攻击成功率就是 0%**。任何一个只报 ASR 的方案都无法把自己和这个退化解区分开,所以只报 ASR 的结论一律不可信。
- 由此推出设计:安全指标必须和能力代价成对报出。最小可用的一对是攻击成功率(攻击集里攻击者目标真正达成的比例)与受攻击下的任务完成率(同一套防御下正常任务集的完成率)。AgentDojo 用的是三个指标,多出来的那个是无攻击时的基线完成率,用来分离「防御造成的损失」和「这个 Agent 本来就做不好」。
- 第二个容易漏的是判据本身。判据必须是「攻击者的目标达成了没有」——数据有没有真的被送出去——而不是「模型说过什么话」。按后者写,模型偷偷调了发信工具但正文里只字不提的那次会被判成安全,而真实外泄恰恰长这样。
- 第三个是评估集的构成。攻击用例要按家族分组而不是堆数量,否则一档防御恰好挡住某一族就会让总数字虚高;还必须有一条针对当前防御的自适应用例,否则你量的是「防御对旧攻击的效果」。正常任务集里要故意放几条**长得像攻击的正常请求**(比如合规要求抄送某个内部邮箱),误伤才量得出来。
- 可以预期的追问:这套评估怎么进 CI?答案是双阈值门禁——攻击成功率超标或任务完成率跌破基线都判失败,单阈值会被「把 Agent 调保守」这个动作直接骗过去。
Key points
- An ASR-only report cannot be told apart from an agent that refuses everything, so pair it with task completion under attack
- Judge by whether the attacker's goal was achieved — whether data actually left — not by what the model said
- Group attack cases by family and include an adaptive case against the current defense; seed the benign set with legitimate requests that resemble attacks
- Gate CI on two thresholds: fail if ASR rises or completion drops below the floor
答题要点
- 只报 ASR 无法与「把 Agent 改成什么都不做」的退化解区分开,所以必须与任务完成率成对报出
- 判据必须是攻击者目标是否达成(数据有没有真的送出去),不是模型说了什么话
- 攻击集按家族分组并包含一条针对当前防御的自适应用例,正常任务集要放几条长得像攻击的真实请求
- 进 CI 时用双阈值门禁:ASR 超标或完成率跌破基线都算失败
D3 Architectural defense: six design patterns, and how plan-then-execute drives attack success to zero
Why does plan-then-execute stop indirect prompt injection, and what does it fail to stop?先定计划后执行为什么能挡住间接注入?它挡不住什么?
Common in ChinaCommon overseasIntermediate#prompt-injection#architecture#plan-then-executeHow to reason about it · think before answering
- The real question is the second half. Answering only the first half reads as paper-deep: anyone who has shipped this pattern has been bitten by the capability it removes.
- First half, one step of reasoning: injection works because untrusted content participates in deciding which actions to take. Plan-then-execute moves the action list before ingestion, and the planner's signature simply does not include the ticket body, so the text can only change the content of each step, never the steps themselves.
- So what it blocks is the whole class of 'add a new action', such as an outbound send that was never planned.
- The second half has two layers. The capability cost: once the plan is fixed, a legitimate 'please cc risk control' in the ticket gets dropped too. That is the definition of the pattern, not a bug. More importantly, it does not stop the content of planned actions from being poisoned: if the plan already writes a conclusion back to the ticket, injection can make that conclusion wrong or misleading.
- Land it in one sentence: risk shrinks from 'any action' to 'parameters of planned actions'. That is containment, not elimination, and the remainder is covered by runtime egress allowlists and confirmation gates.
- Expected follow-up: how do you buy the lost capability back? Not by loosening the plan, but by changing the source of trust — let the user pre-authorize the recipient list so the ticket has no say. Capability returns, attack success rate stays at zero.
分析过程 · 先想清楚再作答
- 这题的题眼在后半句。只答前半句的人一律被判成「读过论文没落地过」——因为任何一个真上过线的人,都被它砍掉的能力咬过一次。
- 前半句的推导只有一步:注入之所以能得手,是因为不可信内容参与了「做哪几件事」的决策;先定计划后执行把动作清单挪到了摄入之前,计划器的参数表里根本没有工单正文,那段文字再巧妙也只能改变每一步的参数内容,改不了步骤本身。
- 所以它挡住的是「新增一个动作」这一类,比如凭空多出一次对外发送。
- 后半句同样有两层。第一层是能力代价:计划定死之后,工单里那句合理的「请抄送风控」也会被一起丢掉——这不是 bug,是这个模式的定义。第二层更要紧:它挡不住计划内动作的**内容**被污染,比如计划里本来就有一步写回工单,注入就可以让写回去的那段结论是错的、误导人的。
- 结论给成一句话:它把风险从「任意动作」压缩到「计划内动作的参数」,是收缩不是消除。剩下的那部分要靠运行时的出口白名单与确认门去兜。
- 可预期的追问是「那怎么把被砍掉的能力买回来」。答案不是放宽计划,而是换信任来源:把收件人白名单改成由用户预授权,工单说了不算——能力回来了,攻击成功率仍然是 0。
Key points
- The action list is fixed before any untrusted content is ingested; the planner never sees the ticket body, so injection cannot add actions.
- It does not stop the parameters or output of planned actions from being poisoned, such as the conclusion written back to the ticket.
- The cost is that it cannot change its mind based on what it reads, so legitimate ad-hoc requests get dropped too.
- Frame it as containment, not elimination; residual risk is handled at runtime by egress allowlists and confirmation gates.
- Buy the lost capability back by changing the source of trust: let the user pre-authorize the allowlist instead of the untrusted content.
答题要点
- 它把动作清单定死在摄入不可信内容之前,计划器看不到工单正文,注入因此无法新增动作。
- 它挡不住计划内动作的参数与产出被污染,比如写回工单的那段结论本身被带偏。
- 它的代价是无法根据读到的内容改主意,合理的临时请求也会被一起丢掉。
- 正确的定位是风险收缩而不是消除,剩余风险交给运行时的出口白名单与确认门。
- 被砍掉的能力靠换信任来源买回来:白名单由用户预授权,而不是由不可信内容指定。
What is the fundamental difference between dual-LLM isolation and adding a detector?双模型隔离和加一个检测器,本质区别在哪?
Common in ChinaCommon overseasDeep dive#dual-llm#detector#threat-modelingHow to reason about it · think before answering
- This probes whether you separate probabilistic defenses from structural ones. Answering 'detectors are not accurate enough, dual-LLM is more thorough' turns it into a question of degree, but the difference is one of kind.
- Unpack it by asking: does this defense still hold after the attacker rewrites the payload? A detector depends on bad input having a detectable signature, and once the signature is public, attacks grow around it — adaptive attacks have done this systematically. Dual-LLM isolation depends on no signature at all: the model that reads the raw text holds no tools, so what it wants is irrelevant.
- A reusable formulation: a detector guesses whether the input is good or bad; isolation limits what can happen after you are fooled. The former fails by false negatives, the latter by orchestration bugs such as dereferencing a symbol too early.
- Volunteer the counterintuitive observation: under isolation the model still gets fooled. On the range, the quarantined model was hijacked five times and genuinely intended to send the customer list out — it simply had no key in its hand. 'Our model resisted the attack' is a different kind of safety, and teams that conflate the two get burned on the first model upgrade.
- Conclusion: keep the detector as a noise filter and an alerting signal, but it is not a boundary. Only structure can be a boundary.
- Expected follow-up: what does dual-LLM cost? Orchestration complexity — the symbol reference table, cross-model data flow, and the rules for when a reference may be dereferenced are all yours to maintain; a full implementation like CaMeL additionally tags every value with its provenance.
分析过程 · 先想清楚再作答
- 这题在考你区不区分「概率性防御」和「结构性防御」。答成「检测器准确率不够高、双模型更彻底」就落进了程度之争,而这两者的差别不是程度,是种类。
- 拆的方式是问一句:攻击者换一种写法之后,这道防御还成立吗?检测器的有效性建立在「坏输入有可被识别的特征」上,特征一旦公开,攻击就会绕着它长——自适应攻击已经系统性地做过这件事。双模型隔离不依赖任何特征:看原文的那个模型手上没有工具,它想做什么都不重要。
- 所以一个可复用的判断句是:**检测器是在猜输入是好是坏,隔离是在限制被骗之后能干什么。** 前者的失败模式是漏报,后者的失败模式是编排写错了、把引用解开了。
- 这里要主动讲一个反直觉的实测现象:隔离结构下的模型**照样会上当**。在靶场里隔离模型被劫持了五次,它真的打算把客户名单发出去,只是它手上没有那把钥匙。「我们的模型抗住了攻击」和这是两种完全不同的安全性,把它们混为一谈的团队会在第一次模型换版时翻车。
- 结论:检测器可以留着当降噪层与告警源,但它不是边界;边界只能由结构给。
- 可预期的追问是「双模型隔离的代价」。答:编排复杂度——符号引用表、跨模型数据流、以及什么时候允许解开引用的规则,都要你自己维护;CaMeL 那套完整实现还要给每个值挂来源标签。
Key points
- A detector is probabilistic and assumes bad input has a detectable signature; once published, adaptive attacks grow around it.
- Dual-LLM isolation is structural: the model reading raw text has no tools, so being fooled does not change what it can cause.
- Different failure modes: detectors fail by false negatives, isolation fails by orchestration bugs or dereferencing a symbol too early.
- Under isolation the model still gets fooled; 'no consequence' and 'not fooled' are different kinds of safety.
- Keep detectors for noise reduction and alerting, never as the boundary; the boundary is structural, and it costs orchestration complexity.
答题要点
- 检测器是概率性的,依赖坏输入有可识别特征;特征公开后自适应攻击会绕着它长。
- 双模型隔离是结构性的,看原文的模型没有工具,被骗与否不改变它能造成的后果。
- 两者失败模式不同:检测器失败于漏报,隔离失败于编排写错或过早解开符号引用。
- 隔离下模型照样会上当,「没造成后果」和「没上当」是两种完全不同的安全性。
- 检测器可以留作降噪与告警,但不能当边界;边界只能由结构给,代价是编排复杂度。
When would you decline to apply these patterns and accept the risk instead?什么情况下你会拒绝使用这些模式,宁可接受风险?
Common in ChinaCommon overseasBasic#risk-tradeoff#lethal-trifecta#architectureHow to reason about it · think before answering
- This is asked in reverse, and it tests whether you treat security as engineering. Anyone who immediately says 'security first, apply them all' fails: every one of the six patterns trades capability for safety, and applying all of them ships a useless product.
- Unpack it from the lethal trifecta: remove any one of the three edges and the chain is broken. So the first case for declining is an incomplete trifecta — the agent touches no private data, or has no outbound channel at all. Adding dual-LLM isolation there is pure cost.
- Second case: consequences are reversible and auditable. If every action is something like writing a conclusion back to a ticket, you can roll back and trace it, so spend the budget on audit logs and rollback rather than isolation orchestration.
- Third case: the value structure does not hold. An internal tool where the operator is the only data subject and the untrusted content comes from that same person — attacker and user are the same party, so the threat model does not apply.
- Be precise about the shape of the refusal: you decline a specific pattern, not defense itself. Still run the selection rule — does this task need to change actions based on untrusted content? Usually no, and then plan-then-execute costs almost nothing. There is no reason to decline a defense that cheap.
- Expected follow-up: how do you report this decision upward? Write it as a conditional record: the justification is an incomplete trifecta or reversible consequences, and the moment someone adds an outbound tool to this agent the record expires and must be re-evaluated. A security decision has to hang on a condition that will actually fire, not on a one-time verbal judgment.
分析过程 · 先想清楚再作答
- 这题是反着问的,考的是你有没有真的把安全当工程做。张口就说「安全无小事、必须全上」的人会被直接判掉——六个模式每一个都在用能力换安全,全上等于把产品做废。
- 拆的方式是先回到致命三件套:三条边缺任意一条,这条攻击链就断了。所以第一类可以拒绝的场景是**三件套不全**——Agent 压根碰不到私有数据,或者它没有任何对外通信能力,那么为它引入双模型隔离就是纯成本。
- 第二类是**后果可逆且可审计**:动作全是写回工单这种可逆操作,出事能回滚、日志能定位,那么把预算花在审计与回滚上比花在隔离编排上更划算。
- 第三类是**收益结构不成立**:内部工具、使用者本人就是唯一的数据主体、并且不可信内容只来自他自己。这时「攻击者」和「用户」是同一个人,威胁模型不成立。
- 但要说清拒绝的正确形式:拒绝的不是「防」,是「这一个模式」。选型判据仍然要走一遍——先问这个任务需不需要根据不可信内容改变动作,多数答案是不需要,那用先定计划后执行几乎没有能力损失,这种便宜的防御没有理由拒绝。
- 可预期的追问是「那你怎么向上汇报这个决定」。答:写成一条带前提的记录——拒绝的依据是三件套不全或后果可逆,一旦哪天给这个 Agent 加了对外发送工具,这条记录就自动失效、必须重评。**安全决定必须挂在一个会被触发的条件上,不能只是一次口头判断。**
Key points
- All six patterns trade capability for safety, so declining one is a legitimate engineering choice rather than negligence.
- Decline when the lethal trifecta is incomplete: no private data or no outbound channel means the chain is already broken.
- Decline when consequences are reversible and auditable; rollback and audit logging are the better use of budget.
- You decline a specific pattern, not defense in general — plan-then-execute is cheap enough that refusing it is rarely justified.
- Record the decision with an expiry condition: the moment an outbound capability is added, the assessment must be redone.
答题要点
- 六个模式都在用能力换安全,全上等于把产品做废,所以拒绝本身是合法的工程选择。
- 三件套不全时可以拒绝:没有私有数据或没有对外通信,攻击链本来就断了。
- 后果可逆且可审计时可以拒绝,把预算花在回滚与审计日志上更划算。
- 拒绝的是某一个模式而不是防御本身,便宜的先定计划后执行几乎没有理由拒绝。
- 决定要写成带失效条件的记录:一旦给这个 Agent 加了对外发送能力,必须重新评估。
D4 Runtime controls: capabilities, approval gates, egress allowlists, sandbox layers, secrets and tenancy
Why must a human-in-the-loop confirmation gate live inside the server-side tool executor? What breaks if you put it in the frontend?人在回路的确认门,为什么必须放在服务端的工具执行器里?放在前端会出什么事?
Common in ChinaCommon overseasIntermediate#human-in-the-loop#tool-execution#trust-boundaryHow to reason about it · think before answering
- The hinge is whether the interception point sits on the path where the action actually happens. Answering in terms of UX misses the question, which is about trust boundaries.
- Ask who initiates the tool call: the server-side agent loop. A frontend dialog is therefore an after-the-fact notification, off the call path, and cannot stop a run that is already executing.
- The second gap is request tampering: a request carrying a confirmed flag can simply be replayed. No assertion supplied by the client can ever serve as authorization, which is the same old rule as never enforcing permissions in the browser.
- Conclusion: the gate belongs in the same function as policy evaluation. On a confirm verdict the run suspends, and the approval arrives through the session, never through the model output or a flag in the request body.
- Add a design rule: check the destination before asking for human approval. An action that should be denied outright must never be shown to a human, or you are training people to click approve reflexively.
- Expected follow-up: does the gate get overused? Only the irreversible outbound tier goes through it; everything else is covered by allowlists and audit logs. Approvals also need a short lifetime rather than lasting for the whole session.
分析过程 · 先想清楚再作答
- 这题的题眼是「拦截点在不在动作发生的那条路径上」。答成「前端体验不好」就跑偏了,面试官想听的是信任边界。
- 先问自己一个问题:这次工具调用是谁发起的?是服务端的 Agent 循环。那么前端的弹框就只是一次事后通知,它不在调用路径上,自然拦不住已经跑起来的运行。
- 第二条攻击面是请求可改:带着「已确认」标记的那次请求能被原样重放,客户端传来的任何断言都不能当授权用。这和「不要在前端做权限校验」是同一条老规矩。
- 结论:确认门要和策略判定在同一个函数里,裁决出「需要确认」之后运行就地挂起,批准信号走会话而不是走模型输出或请求体里的标记。
- 顺带给一条设计判据:判定顺序上,先判目的地再判要不要人点头。一个本来就该被拒的动作不该拿去问人,否则你是在训练人麻木地按同意。
- 可预期的追问:确认门会不会被用滥?答案是只有不可逆的对外动作那一档才过门,其余靠白名单与审计兜;再补一句批准要有有效期,不能点一次头就对整个会话生效。
Key points
- The interception point must sit on the path where the action executes; a frontend dialog is only a notification
- A confirmed flag from the client can be replayed or forged, so no client assertion counts as authorization
- Put the gate next to policy evaluation: suspend the run on a confirm verdict and take approval from the session
- Check the destination first and only then ask a human, so actions that should be denied never reach a person
- Only irreversible outbound actions go through the gate, and approvals expire instead of covering a whole session
答题要点
- 拦截点必须在动作发生的那条路径上,前端弹框不在路径上,只是事后通知
- 客户端送来的「已确认」标记可被重放或伪造,任何来自客户端的断言都不是授权
- 确认门与策略判定同一处:裁出需要确认则运行挂起,批准信号来自会话
- 先判目的地再判要不要人点头,该拒的动作不拿去问人,避免确认疲劳
- 只有不可逆的对外动作过门,批准要有有效期,不能一次点头覆盖整个会话
Which data exfiltration paths does a network egress allowlist actually block, and which ones does it miss?网络出口白名单能挡住哪些数据外泄路径?哪些是它挡不住的?
Common in ChinaCommon overseasDeep dive#egress-control#ssrf#data-exfiltrationHow to reason about it · think before answering
- This question tests boundary awareness. Being able to say what a control does not stop is a stronger signal than reciting what it does.
- Start with why it is cheap: on the outbound edge of the lethal trifecta the attacker needs far more freedom than the business does, because legitimate recipients are a handful of internal addresses.
- What it blocks: mail to unregistered domains, webhooks to unregistered hosts, and SSRF-style requests to internal ranges or cloud metadata endpoints, which often hand out short-lived machine credentials.
- What it misses, stated honestly: writing the data into an allowed destination that the attacker can later read, covert channels through an allowed destination by encoding content into a path or subdomain query, and the model simply telling the private data to the user who is already in front of it.
- There are implementation traps too: suffix matching lets an attacker-controlled subdomain that ends with your domain slip through, and validating only the hostname while ignoring redirects and DNS resolution.
- Expected follow-up: how do you cover the gap? Route traffic through an egress proxy, constrain payload shape and size even for allowed destinations, and watch audit logs for unusual destinations and rates. The real fix is still to cut another edge of the trifecta, such as denying this run access to private data at all.
分析过程 · 先想清楚再作答
- 这题考的是边界意识:能说清一个防御「挡不住什么」,比背下它挡得住什么更有区分度。只说前半截的人,通常没在生产里被绕过过。
- 先说它为什么划算:致命三件套里「对外通信」这条边上,攻击者需要的自由度远大于业务需要的自由度——业务的收件人就那几个内部地址,白名单的成本因此极低。
- 挡得住的部分:往未登记域名发邮件、往未登记主机发 webhook、以及让 Agent 去敲内网与云上元数据端点这类 SSRF。最后一条尤其值钱,元数据端点里往往就是这台机器的临时凭据。
- 挡不住的部分要老老实实列:数据写进一个合法目的地再由别人取走(把名单写回工单、提交到允许的仓库)、通过允许的目的地做隐蔽信道(把内容编码进 URL 路径或子域名查询里)、以及模型直接把私有数据说给当前这个本来就有权看结果的用户。
- 还有一类实现层的坑:白名单写成后缀匹配,攻击者用一个以你的域名结尾的子域就能骗过去;以及只校验域名不校验重定向与 DNS 解析结果。
- 可预期的追问:那怎么补?答案是配合出口代理集中流量、对允许的目的地也限制载荷形状与体积、再加上审计日志看异常的目的地与频率——但根本解法仍然是拆三件套里的另一条边,比如让这次运行根本拿不到私有数据。
Key points
- Blocks mail and webhooks to unregistered destinations, plus SSRF to private ranges and cloud metadata endpoints
- Misses data parked in an allowed destination for later pickup, and covert channels encoded into allowed paths or subdomains
- Misses the model simply telling private data to the user who already has the result in front of them
- Match the full domain exactly; suffix matching is defeated by an attacker subdomain ending in your domain
- Complement it with an egress proxy, payload limits and audit review, but the real fix is cutting another trifecta edge
答题要点
- 挡得住:发往未登记域名的邮件与 webhook,以及指向内网与云上元数据端点的 SSRF 请求
- 挡不住:写入合法目的地后由他人取走,以及把内容编码进允许目的地的路径或子域的隐蔽信道
- 挡不住:模型把私有数据直接说给当前这个已经有权看结果的用户
- 实现上必须精确匹配整个域名,后缀匹配会被以你的域名结尾的子域骗过
- 补法是出口代理集中流量、限制载荷形状与体积、审计异常目的地,根本解法是拆三件套的另一条边
In a multi-tenant agent service, how should credentials and logs be isolated?一个多租户的 Agent 服务里,凭据和日志分别该怎么隔离?
Common in ChinaCommon overseasIntermediate#multi-tenancy#secrets-management#audit-loggingHow to reason about it · think before answering
- This question separates people who have actually run multi-tenant systems. The hinge is one sentence: where does tenant identity come from, the session or the model output.
- Credentials first. The classic mistake is reading the tenant id out of tool-call arguments because the model asked for that customer. One injected line walks straight through it, and nothing looks broken. The rule is that credentials and tenant come only from the session; the model may request work but never decides who it acts as.
- Three rules follow for the secrets themselves: do not hand long-lived keys to the agent process when short-lived tokens will do; issue per-user credentials instead of one service-wide key, which would kill both least privilege and after-the-fact audit; and never let a secret enter the context window, since that means it entered the logs and caches too.
- Now logs. The tension is that they must be detailed enough for forensics while being a fresh copy of the data, usually with looser access and longer retention. The intersection is to record structure, not content: run id, user and tenant, tool name, decision and reason, but not contact details or contract amounts.
- Redaction should be weighed by information value rather than applied bluntly. Keeping the email domain while dropping the local part is the good example: forensics needs to know where the data was headed, while the recipient name adds risk and no information.
- Expected follow-up: how do you isolate the logs themselves? Partition storage by tenant, force a tenant predicate on every query path, and expose only redacted aggregates across tenants. Also keep denied entries, since they are the only trace an attempted attack leaves.
分析过程 · 先想清楚再作答
- 这题在考你有没有真做过多租户。区分度在一句话上:租户身份到底从哪来——从会话来还是从模型的输出里来。
- 先讲凭据。最常见的错法是从工具调用参数里取租户 id,因为模型说要查哪家就查哪家;这条路被一句注入就能走通,而且功能表现完全正常,没有任何报错。规矩是凭据与租户只来自会话,模型可以提要求,但代表谁这件事不归它决定。
- 顺着往下是密钥本身的三条:别把长期密钥交给 Agent 进程,能换短期令牌就换;按用户发凭据而不是给服务一把万能令牌,否则最小权限和事后审计同时失效;密钥永远不进上下文,进过上下文等于进过日志与缓存。
- 再讲日志。它的两难是必须记得够细才有复盘价值,同时它又是一份新的数据副本,权限通常更松、保留期更长。交集是「记结构不记内容」:记运行 id、用户与租户、工具名、裁决与理由,不记客户联系方式与合同金额。
- 脱敏要按信息价值权衡而不是一刀切。邮箱保留域名去掉本地部分就是个好例子:复盘要回答的是数据想去哪家,收件人叫什么不增加信息只增加风险。
- 可预期的追问:日志本身怎么隔离?按租户分区存储、查询接口强制带租户条件、跨租户的聚合视图只给脱敏后的统计;再补一句被拒的记录也要留,它是攻击尝试的唯一痕迹。
Key points
- Tenant and credentials come only from the session, never from model output or tool-call arguments
- Prefer short-lived tokens over long-lived keys, issue per-user credentials, and keep secrets out of the context window
- Log structure, not content: run, user, tenant, tool, decision and reason in full, customer data out
- Redact by information value, for example keep the email domain and drop the local part, since forensics needs the destination
- Partition logs per tenant, force a tenant predicate on queries, and always retain denied entries
答题要点
- 租户与凭据只来自会话,绝不从模型输出或工具调用参数里取
- 用短期令牌替代长期密钥,按用户而不是按服务发凭据,密钥永不进上下文
- 日志口径是记结构不记内容:运行、用户、租户、工具、裁决与理由要全,客户数据不要
- 脱敏按信息价值权衡,例如邮箱保留域名去掉本地部分,复盘要的是数据想去哪家
- 日志本身按租户分区、查询强制带租户条件,被拒的记录必须保留
D5 Red teaming: keeping an attack suite alive, wiring it into CI, and what to do after an incident
Once red-teaming runs in CI, how do you set thresholds that are both meaningful and not a daily false alarm?红队测试进 CI 之后,阈值该怎么定才既有效又不会天天误报?
Common in ChinaCommon overseasDeep dive#red-teaming#ci-gatingHow to reason about it · think before answering
- This tests whether you have actually run red-teaming inside a pipeline. Answering just set an ASR threshold invites an immediate follow-up, because a single threshold has an obvious defeat.
- Explain why it must be two thresholds. Gating only on attack success rate lets a version with every tool disabled pass instantly at zero percent while being useless; gating only on task completion ignores security entirely. Both numbers must be gated together, which is the point of the AgentDojo three-metric design.
- Give each threshold its own rationale, because their sources differ. The ceiling on attack success rate should be zero: indirect injection has no acceptable small amount, since an attacker can retry indefinitely. Concretely, an intermediate configuration with only an egress allowlist sits at eight percent, so a twenty percent ceiling would wave it straight into production even though it is genuinely breakable. The floor on task completion can only be measured from the business: run an unattacked baseline first, then leave one band of normal variance below it.
- Add a third criterion: regression. Fixed holes stay in the case set with their expected result marked as blocked, so any change that makes one succeed again gets named explicitly by the gate. This catches someone quietly removing a defense, a change that looks nothing like a security change in review.
- Close on false alarms. The real risk is not a gate that is too loose but one that is too strict. A gate that goes red daily will eventually get a skip condition or be commented out, and a disabled gate is worse than no gate because the team still believes someone is watching. So widen the utility floor before you ever loosen the attack success ceiling.
- Expect the follow-up about the suite growing slow. Split by case origin: research-derived cases that stay green across several releases can run less often, while cases hardened from real incidents stay in the always-run set.
分析过程 · 先想清楚再作答
- 这题在考「你有没有真的把红队跑进过流水线」。只答「设一个 ASR 阈值」会被立刻追问,因为单阈值有一个人人都能想到的破法。
- 先说为什么必须是双阈值:只卡攻击成功率的话,把所有工具关掉的版本 ASR 立刻归零,门禁全绿而 Agent 已经没用了;只卡任务完成率则完全不管安全。两个数字必须一起卡,这也是 AgentDojo 那套三指标设计的核心意思。
- 再分别给判据,因为两个阈值的来源完全不同。攻击成功率的上限就该是 0——间接注入没有「可以接受的一点点」,因为攻击者可以无限重试;给个具体感受:某个只有出口白名单的中间形态 ASR 是 8%,把上限放到 20% 它就一路绿灯进生产,而它是真的会被打穿的。任务完成率的下限只能从业务里量:先跑一遍无攻击的基线,再往下留一档正常波动空间。
- 还要加第三条判据——回归。修好的洞要留在用例集里,把它的期望结果标成「应被挡住」,以后任何一次改动让它重新得手,门禁就指名道姓地报出来。这条管的是「有人悄悄拆了某个防御」,而那种改动在代码审查里看起来完全不像安全改动。
- 结论落到误报上:真正的风险不是门禁太松,是门禁太严。一条天天变红的门禁最后一定会被加上跳过条件或者注释掉,而门禁被关掉比门禁不存在更坏——团队会以为还有人在把关。所以宁可把任务完成率的下限定宽一点,也绝不放宽攻击成功率的上限。
- 可预期的追问:用例集越来越大导致 CI 变慢怎么办?答:按用例来源分频。公开研究形态的用例如果连续多个版本稳稳是绿的可以降频跑,线上事故固化下来的那些永远留在每次都跑的主集合里。
Key points
- Two thresholds are mandatory: gating only on attack success passes a do-nothing build, gating only on utility ignores security.
- The attack success ceiling should be zero, because indirect injection has no acceptable small amount and attackers retry freely.
- The utility floor comes from a measured business baseline plus one band of normal variance.
- Add regression as a third criterion: fixed holes stay in the suite and get named if they succeed again.
- Widen the utility floor before loosening the attack ceiling; a disabled gate is worse than no gate.
答题要点
- 必须双阈值:只卡攻击成功率会被「什么都不做」的版本通过,只卡完成率则不管安全。
- 攻击成功率的上限就该是 0,因为间接注入没有可以接受的一点点,攻击者可以无限重试。
- 任务完成率的下限从业务基线量出来,再往下留一档正常波动空间。
- 加第三条回归判据:修好的洞留在集合里,重新得手就点名报出。
- 宁可放宽完成率下限也不放宽攻击成功率上限;门禁被关掉比门禁不存在更坏。
How do you tell an agent has gone rogue, and who gets to pull the kill switch?怎么判断一个 Agent 已经失控?断路开关该由谁来拉?
Common in ChinaCommon overseasIntermediate#rogue-agent#kill-switchHow to reason about it · think before answering
- This tests two things: your taste in choosing signals, and whether you have thought past the trip itself. Answering monitor for anomalies says nothing, because the word anomaly is exactly the hard part.
- Separate it from every earlier defense. Policy engines and egress allowlists stop one bad action; rogue detection addresses something else entirely, namely that someone is probing persistently. Blocking once is success; being probed a hundred times and happening to block each one is gambling.
- Then explain signal selection. Pick the hardest to fake, not the cleverest. Two blunt ones work well: the count of denied actions within a session exceeding a budget, and the number of distinct destinations those denials targeted. They are hard to fake because normal use almost never hits an egress allowlist. A session doing honest work hits it zero times; one hit may be a misconfiguration; four hits each aimed somewhere new has only one explanation. Semantic anomaly detection, by contrast, is easily diluted by slow attacks.
- Then what happens after the trip. What to do next is a process question, not a code question. Hardcoding automatic bans or rollbacks creates a new attack surface, because whoever can trigger the trip also gains the ability to shut your service down. Code should only trip and record; humans follow a pre-agreed process afterward.
- On who pulls it: scope it at two levels, per tenant and global, since most incidents need only the former. Authority must sit with a role that can act without a release cycle, such as the on-call engineer, because waiting for a deploy defeats the purpose of stopping the bleeding. The three hard requirements are one-click, immediately effective, and the pull itself logged.
- Expect the follow-up on false trips interrupting real work. Start in alert-only mode and calibrate the budget against real traffic, and make the trip scope narrowable to one tenant or one session rather than global only.
分析过程 · 先想清楚再作答
- 这题在考两件事:你挑指标的品味,以及你有没有想过跳闸之后的流程。只答「监控异常行为」等于没答,因为异常这个词本身就是问题所在。
- 先划清它和前面所有防御的分工。策略引擎、出口白名单这些管的都是「挡住一次坏动作」;失控检测管的是另一件事——**有人正在持续地试**。挡住一次是成功,被试一百次而每次都恰好挡住了,那是在拿运气赌。
- 再说指标怎么挑。判据是挑最难骗的,不是挑最聪明的。两条很笨但很好用:一次会话里被拒动作的数量超过预算,以及被拒动作换了几个不同的目标地址。它们难骗的原因是正常使用几乎撞不上出口白名单——老老实实干活的会话一次都撞不上,撞一次可能是配置写错了,连撞四次而且每次换一个目的地就只有一种解释。相比之下基于语义的异常检测很容易被慢速攻击稀释掉。
- 然后谈跳闸之后。结论是:跳闸之后做什么是流程问题,不是代码问题。自动封禁、自动回滚这类补救写死在代码里反而会变成新的攻击面——能触发跳闸的人,就顺带获得了让你的服务自己停掉的能力。所以代码只负责跳闸和记录,后续由事先约定好的人按流程走。
- 谁来拉:范围上分租户级和全局两档,多数事故只需要前者;权限上必须是不需要走发布流程就能立刻生效的角色(值班工程师),因为等走完发布才停机,止血就已经晚了。三条硬要求是能一键拉、拉了立刻生效、拉的动作本身进日志。
- 可预期的追问:怎么防误报把正常业务打断?答:先只做告警不做自动跳闸,用真实流量跑一段时间校准预算值;另外跳闸的范围要能收窄到单个租户或单个会话,而不是只有全局这一档。
Key points
- Rogue detection is a different job from per-action blocking: it catches sustained probing, not a single bad action.
- Choose the hardest-to-fake signals: denied actions per session over budget, and the number of distinct destinations denied.
- They resist faking because normal use rarely hits an egress allowlist; one hit is an accident, several in a row is not.
- Post-trip remediation is process, not code; automatic bans hand anyone who can trigger a trip the power to shut you down.
- Give the authority to an on-call role that needs no release cycle, scope it per tenant and globally, and log the pull itself.
答题要点
- 失控检测与单次拦截分工不同:前者管的是有人在持续地试,不是挡住一次。
- 指标挑最难骗的:一次会话里被拒动作数超预算、被拒动作换了几个不同目标。
- 这两条难骗是因为正常使用几乎撞不上出口白名单,撞一次是意外,连撞几次只有一种解释。
- 跳闸后的补救是流程不是代码;自动封禁会让能触发跳闸的人顺带获得停掉服务的能力。
- 权限给不需要走发布流程的值班角色,范围分租户级与全局两档,拉的动作本身要进日志。
After an agent data exfiltration incident, what are your first three steps?一次 Agent 数据外泄事故发生后,你的前三步分别做什么?
Common in ChinaCommon overseasIntermediate#incident-response#forensicsHow to reason about it · think before answering
- This question tests ordering, not knowledge. Most people can name the three steps, but swapping one destroys evidence, and that is what the interviewer is watching for.
- First, stop the bleeding: pull the kill switch and halt the affected scope. The criterion is no new loss, not understanding what happened. Investigating while still running usually does neither well, and every additional turn during an incident may leak more data.
- Second, preserve evidence: copy the audit log, the inputs at the time, and the configuration version to somewhere else, untouched. The key is not to investigate and edit inside the live environment, since every change overwrites evidence and you later cannot prove you did not alter the data yourself. Whether this step is even possible depends entirely on the log fields: whose identity initiated it, which destination the action targeted, whether it was allowed or denied, and which policy decided. Missing any one turns the postmortem into guesswork.
- Third, run the postmortem, answering four questions: which injection surface the attacker entered through, which layers were bypassed, what was obtained, and how we found out. The last matters most, because if the answer is a customer told us, the first thing to fix is detection, not the hole.
- Add the step most people omit: the postmortem ends by hardening the incident into a regression case, tagged as incident-origin and kept in the red-team suite forever. The lasting value of an incident lives in that case, not in the document, because nobody reads the document six months later while the case runs on every CI build.
- Expect the follow-up on whether logs become a second leak. They can, so audit logs must be redacted. A log that copies customer data verbatim is itself the thing you were protecting, and what you kept in order to investigate a leak ends up amplifying it.
分析过程 · 先想清楚再作答
- 这题考的是顺序,不是知识点。三步本身很多人都能说出来,但顺序说反一步就会把证据毁掉,面试官主要在看这个。
- 第一步先止血:拉断路开关把受影响范围停掉。这一步的判据是「不再产生新的损失」,不是「搞清楚发生了什么」。想边查边跑通常两件事都做不好,而且事故期间每多跑一轮就可能多外泄一批数据。
- 第二步再取证:把审计日志、当时的输入、当时的配置版本原样保存一份到别处。关键是不要在原环境里边查边改——你改的每一下都在覆盖证据,而且事后无法证明数据不是被你自己改的。这一步能不能做成,完全取决于日志里有没有该有的字段:谁的身份发起的、动作打向哪个目的地、被放行还是被拒、依据的是哪条策略,少一条复盘就得靠猜。
- 第三步后复盘:回答四个问题——攻击者从哪条注入面进来、绕过了哪几层、拿到了什么、我们是怎么发现的。最后一问最关键,如果答案是「客户告诉我们的」,那第一件要修的不是那个洞,是检测能力。
- 结论要补一条很多人会漏的:复盘的最后一步是把这次事故固化成一条回归用例,来源标成线上事故,永远留在红队集合里。一次事故的长期价值不在那份复盘文档里,在那条用例里——文档半年后没人看,用例每次 CI 都会跑。
- 可预期的追问:日志本身会不会成为第二个泄露源?会,所以审计日志必须脱敏。一份把客户数据完整抄进去的日志,出事之后自己就是要保护的东西,你为了查泄露而保留的东西反而放大了泄露。
Key points
- The order is fixed: stop the bleeding, preserve evidence, then run the postmortem; reversing it destroys evidence.
- The stop-the-bleeding criterion is no new loss, not understanding what happened.
- Preserve logs and configuration elsewhere, untouched, and never investigate-and-edit in the live environment.
- The postmortem answers four questions; how we found out matters most, and a customer telling you means fix detection first.
- End by hardening the incident into an incident-origin regression case, and confirm the audit log is redacted.
答题要点
- 顺序固定:先止血、再取证、后复盘,顺序反了会毁掉证据。
- 止血的判据是不再产生新损失,不是搞清楚发生了什么。
- 取证要把日志与配置原样存到别处,绝不在原环境边查边改。
- 复盘回答四问,其中「我们是怎么发现的」最关键,答案是客户告知就先修检测。
- 复盘的最后一步是固化成一条来源标为线上事故的回归用例,并确认审计日志已脱敏。
Agent Harness in 7 Days: Let an Agent Run Unattended Through the Night
D1 Why an Agent Starts Degrading Once It Runs Past the Second Context Window
What is the difference between the context window and state? Why can a long-horizon agent's authoritative progress not live only in the window?上下文窗口和状态有什么区别?为什么长时程 Agent 的权威进度不能只存在窗口里?
Common in ChinaCommon overseasIntermediate#long-horizon#state-management#context-windowHow to reason about it · think before answering
- This tests whether you have actually run a long task. Answering only 'the window has a token limit, so compact it' treats it as a capacity question. The discriminator is whether you can name the window's volatility and say who covers for it when it disappears.
- Offer a transferable test: ask 'if this piece of information vanished right now, could I look it up anywhere else?' If yes it is a volatile copy; if no it is the authoritative record and must hit disk. The window is the former, state is the latter. The distinction is ownership, not size.
- Then note that the window dies in four ways and none of them raise an error: the framework auto-compacts it, it gets truncated, the process restarts and resets it, and a long task spans several windows by construction. The first three are decisions the runtime makes for you, the fourth is arithmetic. What they share is silence - no exception, no log line saying your progress was discarded. You only see the agent behaving inexplicably.
- Now the consequence, which is what the interviewer is waiting for. If progress lives only in the window, the next window contains no evidence of prior work, so the agent restarts from the top of the checklist. That is not the model getting dumber - the evidence is gone, and the same rule on the same input must produce the same output. The cost is double: half the compute is wasted, and duplicate writes dirty the workspace so later dependent tasks behave unpredictably.
- Interactive chat hides this because the human *is* the state layer: you can always add 'that one was finished yesterday'. Unattended, that human is absent, so an authoritative record on disk has to stand in for them.
- Expected follow-up: can you just save the whole night's transcript and replay it into the next window? No, for two reasons. It will not fit, and replaying it pays for a night of reasoning twice. The goal of rebuilding context is equivalence, not restoration - you need what is done, what is in flight, and how many attempts have been made. Intermediate reasoning and one-shot tool receipts do not belong on disk.
分析过程 · 先想清楚再作答
- 这题考的是「有没有真的跑过长任务」。只答「窗口有 token 上限,所以要压缩」是把它当成一道容量题——区分度在于你能不能说出窗口的**易失性**,以及易失之后谁来兜底。
- 先给一个能迁移的判据:问一句「这条信息如果现在丢了,还有别的地方能查到吗」。查得到的是易失副本,查不到的就是权威记录,必须落盘。窗口是前者,状态是后者,两者的区别不在容量而在**归属**。
- 然后点出窗口有四种死法,而且它们都不报错:被框架自动压缩、被截断、进程重启后重置、以及长任务本来就要跨好几个窗口。前三种是运行环境替你做的决定,第四种是任务长度的必然。共同点是没有任何异常、没有任何日志说「你的进度被扔了」,你只会看到 Agent 的行为忽然变得莫名其妙。
- 接着说清后果,这一段是面试官真正想听的。进度只活在窗口里,窗口一换,Agent 在新上下文里找不到任何做过的证据,于是从清单第一条重新开始。**这不是模型变笨了,是证据没了**——同一条规则、同一个输入,必然同一个输出。代价是双份的:算力白烧一半,而且重复写入会把工作区弄脏,后面依赖它的任务会给出你预料不到的结果。
- 交互式对话之所以感觉不到这个问题,是因为人就是那个兜底的状态层:你随时能补一句「那条昨天做完了」。无人值守时这个人不在场,所以必须有一个磁盘上的权威记录替他站着。
- 可预期的追问是「那把整夜的对话原样存下来、下个窗口再喂回去行不行」。不行,两个理由:一是窗口上限本来就装不下,二是那样做等于把一夜的推理再烧一遍钱。重建上下文的目标是**等效**而不是还原——只需要「做完了什么、正在做什么、试了几次」,中间推理和一次性的工具回执不该持久化。
Key points
- The window is a volatile copy; state is the authoritative record on disk. The difference is ownership, not size.
- The window dies four ways - compaction, truncation, reset, replacement - and none of them raise an error.
- The test: if this vanished now, could I look it up elsewhere? If not, it must be persisted.
- With progress only in the window, the next window has no evidence and restarts from the top. The model did not get dumber.
- In interactive chat the human is the state layer; unattended you need a disk record to stand in.
- Rebuild for equivalence, not restoration: what is done, what is in flight, how many attempts.
答题要点
- 窗口是易失副本,状态是磁盘上的权威记录;区别在归属而不在容量。
- 窗口有四种死法:压缩、截断、重置、整个换掉,而且一行都不报错。
- 判据:这条信息现在丢了还能不能在别处查到,查不到就必须落盘。
- 进度只活在窗口里,换窗口后 Agent 找不到证据就从头重做——不是变笨,是证据没了。
- 交互式对话里人就是那个状态层,无人值守时必须有磁盘记录替他站着。
- 重建的目标是等效不是还原:只留做完了什么、正在做什么、试了几次。
Why should the model-facing interface not know which harness features are switched on? What happens if it does?为什么给模型的接口不该知道当前开了哪些 harness 功能?如果知道了会发生什么?
Common in ChinaCommon overseasDeep dive#experiment-design#interface-boundary#false-greenHow to reason about it · think before answering
- The real question is the second half. Plenty of candidates can say 'keep the interface clean'; few can say 'the moment it knows, every experimental conclusion you drew is void'. That is the whole discriminator.
- Set up the scenario. You are running a controlled experiment to show some harness mechanism - a state layer, a verification gate, a loop detector - actually helps. You run once with it on and once with it off and compare completion. The design assumes that apart from that one mechanism, everything else about the two runs is identical.
- Now add a config parameter to the model-facing signature. The assumption collapses: the system under test can see the experimental condition, so it can play along - work properly when the state layer is on, play dumb when it is off. The curves look beautiful and 'the state layer helps' appears to be empirically confirmed, but you measured cooperation, not effect. There is a name for that result: a false green.
- The nastier part is that a false green raises no error. It looks exactly like a real finding, and it leans in the direction you were hoping for. A bug that crashes is luck; a bug that makes you more confident is a disaster.
- So narrow the signature to the bone: one string in, one string out. The model sees only the context you fed it and learns nothing about harness configuration. Then the phenomenon can only emerge for real - the second window repeats work because its context genuinely holds no completion evidence, not because something told it to act forgetful.
- Generalize it into a principle worth stating out loud: in any controlled experiment, the system under test must not know it is being tested. Evaluation, A/B testing and security red-teaming are all the same sentence, and naming that earns credit.
- Expected follow-up: how do you inject model-side behavior then, such as a worse script? Separate model-side data from harness configuration. Swapping the script changes what code the model would write, which belongs to the model side. Whether the state layer is currently enabled belongs to the harness side and must stay outside the signature.
分析过程 · 先想清楚再作答
- 这题的题眼在后半句。能说出「接口要干净」的人很多,能说出「一旦知道了,你的全部实验结论都作废」的人很少——区分度全在这里。
- 先把场景摆清楚:你在做一个对照实验,想证明某个 harness 机关(状态层、验证闸门、打转检测)有用。做法是开一次、关一次,比两次运行的完成度。这个设计的隐含前提是:**除了那个机关,两次运行的其他一切都相同。**
- 现在假设模型接口的签名里多了一个配置参数。那个前提立刻不成立了——被测系统能看见实验条件,于是它可以配合演出:开了状态层就好好干,关了就装傻。跑出来的曲线非常漂亮,结论「状态层有用」看起来被实测证实了,但你测的不是机关的效果,是模型有多配合。这类结果有个专门的名字:**假绿**。
- 更麻烦的是假绿不会报错,它长得跟真结论一模一样,而且是往你想要的方向偏。一个会自己报错的 bug 是运气,一个让你更自信的 bug 才是灾难。
- 所以正解是把签名收到最窄:一个字符串进,一个字符串出,模型只能看到喂给它的那段上下文,harness 开了什么一个字都拿不到。这样一来现象就只能真实涌现——第二个窗口之所以重复劳动,是因为它的上下文里确实没有完成证据,而不是因为有人告诉它「现在状态层关了,你装傻吧」。
- 这条判据可以抽象成一句通用原则:**任何一次对照实验,被测系统都不该知道自己正在被测。** 它在评估、A/B 实验、安全红队里是同一句话,答的时候点出来能显著加分。
- 可预期的追问是「那模型的行为数据怎么传进去,比如换一份更差的剧本」。答案是区分**模型侧数据**与**harness 配置**:换剧本改的是「模型会写出什么代码」,那本来就属于模型这一侧;而「当前开没开状态层」属于 harness 侧,前者可以传,后者必须挡在签名外。
Key points
- A controlled experiment assumes everything but the mechanism is identical; a config parameter breaks that immediately.
- Once the model can see the condition it can play along - work when the feature is on, act dumb when it is off.
- The resulting curves are a false green: you measured cooperation, not the mechanism.
- False greens raise no error and lean the way you hoped, which is worse than a crash.
- Fix: narrow the signature to one string in, one string out, so phenomena can only emerge for real.
- General principle: the system under test must not know it is under test. Model-side data may vary; harness configuration stays out of the signature.
答题要点
- 对照实验的隐含前提是除被测机关外其他一切相同,配置参数会直接打破它。
- 模型一旦感知实验条件就能配合演出:开了好好干、关了装傻。
- 那样跑出来的曲线是假绿——测的不是机关效果,是模型有多配合。
- 假绿不报错、还往你想要的方向偏,比会崩的 bug 危险得多。
- 正解是签名收窄到一个字符串进一个字符串出,现象只能真实涌现。
- 通用原则:任何对照实验,被测系统都不该知道自己正在被测。模型侧数据可以换,harness 配置必须挡在签名外。
An agent that ran unattended overnight tells you in the morning that every task is complete. How do you verify that claim?一个无人值守跑了一夜的 Agent 早上告诉你全部任务已完成。你会怎么验证这句话?
Common in ChinaCommon overseasIntermediate#verification#long-horizon#reportingHow to reason about it · think before answering
- This is about whether you distrust completion claims. 'I'd spot-check a few' earns half credit - spot-checking is a tactic, not a method, and you will most likely sample the tasks it faked best. The interviewer wants to hear how you turn 'it says it is done' into a criterion that does not rely on its own account.
- First, separate two things: the status it claims and the actual state of the environment. The first is a line it wrote itself; the second is a fact code can query. All verification must land on the second. Skip this and everything after it is auditing a self-report.
- Second, make the claim verifiable in form. Every checklist item must be end-to-end verifiable, meaning you can write a check that ignores all agent output and simply drives the running system. That constrains how items are written: 'improve search' is unverifiable; 'title search does case-insensitive substring matching' is verifiable.
- Third, re-run the whole suite rather than the last item. A night unattended produces dozens of changes and later work commonly breaks earlier work, so verifying the tail verifies nothing. Re-run the entire checklist, and check whether the pass flags were flipped by verification results or by the agent's own assertion. The machine form of that discipline: only a passing end-to-end verification may flip an item to passing.
- Fourth, look for a dirtied environment: the same code appended twice, leftover temp files and debug switches, progress notes that fell behind. None of these turn a check red, but they detonate for whoever picks the work up next.
- Finally, state the limits of your own method - this shows more experience than a perfect answer. End-to-end checks that go through the interface layer cannot see rendering or look-and-feel defects. Either add browser automation or write explicitly in the report that this class is uncovered. Never quietly count it as covered.
- Expected follow-up: what if it edited the verification scripts themselves? Verification definitions must not sit in the same hands as the implementation. The checklist and check scripts belong to the harness, live in version control, and are reconciled before each run; the agent may only touch what is being implemented.
分析过程 · 先想清楚再作答
- 这题考的是对完成声明的怀疑态度。答「我去抽查几条」只能拿一半分——抽查是手段不是方法,而且抽到的大概率是它做得最像的那几条。面试官想听的是你怎么把「它说完成了」变成一个不依赖它自述的判据。
- 第一步先把两件事分开:**它声称的状态**和**环境的真实状态**。前者是它自己写的一行字,后者是可以用代码去查的事实。所有验证都必须落在后者上。这一步做不到,后面所有努力都在验一份自述。
- 第二步给出可验证的形式。清单里的每条 feature 必须是端到端可验证的,也就是能写出一段不看 Agent 任何输出、只对着运行中的系统发请求就能判通过与否的检查。这一条决定了清单该怎么写:「优化一下搜索」不可验证,「搜索标题时不区分大小写地做子串匹配」可验证。
- 第三步是回归地跑,不是只跑最后那条。无人值守一夜会做几十条,后做的很容易踩坏先做的——只验最后一条等于没验。正确做法是把清单**整套**重跑一遍,并且看清单的通过标记是不是由验证结果翻的,而不是由 Agent 自己声称翻的。这条纪律的机器形式是:只有端到端验证通过才允许把那条改成通过。
- 第四步查一遍环境有没有被弄脏:同一段代码有没有被写两遍、有没有留下临时文件与调试开关、进度笔记有没有跟上。这一类问题不会让任何验证转红,但会在下一个人接手时炸。
- 最后要承认能力边界,这一点比全都答对更能显出工程经验:端到端验证走的是接口层(发请求看响应),测不到渲染类与观感类的缺陷。这类缺陷要么接浏览器自动化,要么就明确写进报告说「这一类没覆盖」——不许悄悄当成覆盖了。
- 可预期的追问是「那它把验证脚本本身改坏了怎么办」。答案是验证的定义不能和实现放在同一只手里:清单与验证脚本属于 harness 侧、进版本控制、每次运行前核对;Agent 只被允许改被实现的那部分。
Key points
- Separate the claimed status from the real state of the environment and verify only the latter.
- Every checklist item must be end-to-end verifiable by driving the running system, ignoring agent output.
- Re-run the whole suite, not just the last item - later work routinely breaks earlier work.
- Only a verification result may flip an item to passing, never the agent's own assertion.
- Check for a dirtied environment: duplicate writes, leftover temp files, stale progress notes.
- State the limits: interface-level checks miss rendering defects, so add browser automation or declare the gap in the report.
答题要点
- 先分开它声称的状态与环境的真实状态,所有验证只落在后者上。
- 清单每条都必须端到端可验证:不看 Agent 输出、只对运行中的系统发请求就能判。
- 整套回归重跑,不只验最后一条——后做的很容易踩坏先做的。
- 通过标记只能由验证结果翻,不能由 Agent 自己声称翻。
- 查环境有没有被弄脏:重复写入、临时文件、进度笔记是否跟上。
- 承认能力边界:接口层验证测不到渲染类缺陷,要么接浏览器自动化,要么在报告里写明未覆盖。
Same model, same prompts, but swapping the harness lifts the benchmark score by more than ten points. What does that tell you?同一个模型、同一套提示词,换一个 harness 成绩提升十几个点。这说明了什么?
Common in ChinaCommon overseasIntermediate#harness-engineering#benchmarks#model-capabilityHow to reason about it · think before answering
- This is open-ended and tests whether you carry a mental map dividing model capability from scaffolding. 'It shows the harness matters' is a non-answer; say which part matters, and where the conclusion does and does not generalize.
- Lead with the fact and the magnitude: LangChain published a run where, with no model change and harness changes only, Terminal Bench 2.0 went from 52.8 to 66.5, a gain of 13.7 points, moving from Top 30 into the Top 5. Thirteen points is typically a model generation, and not a line of the model changed.
- First conclusion: a model's capability and the capability it demonstrates on a benchmark are two different things. The score is capability multiplied by how well the scaffolding lets that capability out. If the scaffolding term is 0.7, a stronger model only buys you 0.7 of the gain - while lifting the scaffolding from 0.7 to 0.9 costs nothing like re-evaluating a model generation. The two paths have completely different cost structures.
- Second conclusion: look at what actually changed. None of the three effective changes made the model smarter. Each put a gate where the model was known to fail - force a verification pass before exit (against false completion), track repeated edits to one file and suggest a new approach (against looping), map the directory and available tooling up front (against flailing in an unfamiliar environment). The shape is always 'known failure mode plus a gate aimed at it', which is exactly this course's structure.
- Third, the limits - do not over-generalize. The size of the win depends on how bad the previous scaffolding was and how long the task is. In interactive turn-taking the human is the harness, so you will not find ten points there. The wins live in unattended long-horizon runs, where no failure mode has a human backstop. Also note that the fourth change in that experiment was reasoning-budget allocation, which is model tuning rather than harness structure; folding it into 'the harness did it' conflates two things.
- Then land it somewhere actionable, which is the most valuable sentence in the answer: before locating the win, ask whether anyone is watching this agent. For unattended systems, first check for authoritative state, a completion gate, and stall intervention. Miss any of the three and the money spent on a better model is probably wasted.
- Expected follow-up: how would you prove the harness gets the credit? Controlled-experiment discipline - change exactly one variable, freeze model and prompts verbatim, and make sure the model side cannot perceive which mechanisms are enabled, or what you have is a false green.
分析过程 · 先想清楚再作答
- 这题是道开放题,考的是你心里有没有一张「模型能力与脚手架」的分工图。答「说明 harness 很重要」等于没答,得说清它重要在哪一段、以及这个结论能推到哪里、不能推到哪里。
- 先给事实和量级:LangChain 公开过一次实测,不换模型只改 harness,Terminal Bench 2.0 从 52.8 提到 66.5,涨了 13.7 点,排名从 Top 30 进了 Top 5。十三点七分通常是一代模型的差距,而这次一行模型都没换。
- 第一层结论:**模型的能力和它在基准上表现出来的能力,是两件事。** 分数是「模型能力 × 脚手架能不能把这份能力用出来」的乘积。脚手架那一项如果是 0.7,换一个更强的模型也只能拿到 0.7 倍的增益,而把脚手架从 0.7 提到 0.9 却不需要重新评测一整代模型——**这是成本结构完全不同的两条路**。
- 第二层结论是看它到底改了什么。那三处有用的改动都不是让模型更聪明,而是**在模型犯已知错误的地方加一道机关**:退出前强制跑一遍验证(治谎报完成)、追踪文件编辑次数太多就建议换思路(治打转)、一进来先映射目录与可用工具(治摸索环境)。共同形状是「已知失败模式 + 一道针对它的闸门」,这也正是本课七天的结构。
- 第三层是这个结论的边界,别过度推广。换 harness 的收益大小取决于原来的脚手架有多差、以及任务有多长:交互式的一问一答里人就是那个 harness,涨不了十几个点;真正的收益出现在无人值守的长任务里,因为那里每一个失败模式都没有人兜底。同时那次实验里第四个改动是推理预算分配,那属于模型调参而不是 harness 结构——把它一起算进「harness 的功劳」就是把两件事混了。
- 最后给出可执行的落点,面试里这一句最值钱:**定位收益之前先问这个 Agent 有没有人在旁边看着。** 无人值守的系统,先查它有没有权威状态、有没有完成闸门、有没有停滞干预;这三样缺一样,换模型的钱大概率白花。
- 可预期的追问是「那你怎么证明是 harness 的功劳而不是别的」。答案是对照实验的纪律:只动一个变量、模型与提示词逐字冻结,而且被测的模型侧不能感知当前开了哪些机关——否则你拿到的是假绿。
Key points
- Give the magnitude: harness-only changes moved Terminal Bench 2.0 from 52.8 to 66.5, a 13.7 point gain, Top 30 into Top 5.
- A score is capability times scaffolding; demonstrated capability is not the same as capability.
- Improving scaffolding and swapping models have different cost structures - the former needs no model re-evaluation.
- All three effective changes share a shape: a known failure mode plus a gate aimed at it, not a smarter model.
- Limits: the win lives in unattended long-horizon work; in interactive use the human is the harness, and reasoning-budget tuning is not harness structure.
- Actionable close: ask whether anyone is watching, then check for authoritative state, a completion gate, and stall intervention.
答题要点
- 给量级:不换模型只改 harness,Terminal Bench 2.0 从 52.8 到 66.5,涨 13.7 点,Top 30 进 Top 5。
- 分数是模型能力与脚手架的乘积,模型能力与它表现出来的能力是两件事。
- 改脚手架和换模型的成本结构完全不同,前者不需要重新评测一整代模型。
- 三处有用的改动形状相同:已知失败模式加一道针对它的闸门,而不是让模型更聪明。
- 边界:收益出现在无人值守的长任务里;交互式场景人就是 harness,涨不了这么多;推理预算属模型调参不算 harness。
- 落点:先问有没有人在旁边看着,再查权威状态、完成闸门、停滞干预这三样。
D2 The Line Between State and Context: Moving the Authoritative State Out of the Window
Designing the state layer for a long-horizon agent, what goes to disk and what stays out of it?设计一个长时程 Agent 的状态层,你会把哪些东西写进磁盘,哪些坚决不写?
Common in ChinaCommon overseasIntermediate#state-management#long-horizon#persistenceHow to reason about it · think before answering
- This tests whether you have a criterion, not whether you can recite a list. Plenty of people can rattle off 'progress, logs, context, tool results'. The discriminator is giving a rule someone else could apply to their own project.
- Lead with the criterion: what does the next window absolutely need in order to choose the right next action? Then add the sharper inverse test: can this information be recomputed? If yes, do not store it - fetch it fresh when needed. If no, it must be stored, because it is history, and history cannot be replayed.
- Filtered through those, only three classes survive. First, what is done - store feature ids, not descriptions, because ids are stable and descriptions drift with requirements. Second, what is in flight, whose value is easy to underrate: if the process dies mid-task, disk holds a dangling 'working on F07'. That is not dirty data; it is precisely what tells the next window that F07 may or may not have landed, so verify before assuming. Drop it and the next window either redoes the work or skips something that was never finished. Third, how many attempts, whose value comes entirely from accumulating across windows - one failed attempt in one window is noise, twelve failures across five windows is a signal that must fire. Loop detection and budget cutoffs are both built on that counter.
- Two classes stay out. Intermediate reasoning: once the conclusion has landed, the reasoning has done its job, and keeping it both bloats the file and makes the next window spend attention reading a stale plan. One-shot tool receipts: the output of some git status, the contents of some file read. Their defining trait is that you can fetch them again and the fresh copy is the correct one. Feeding a three-hour-old git status into a new window is worse than feeding nothing - it will act on a stale snapshot.
- Raise the placement question too, since many candidates miss it: the state file belongs outside the repository being worked on. Putting it inside means it lands in the agent's own commits, and rolling back to the last good point rolls the ledger back with it. Rollback targets code; it should not also erase the lesson 'I have already tried this three times'. One-line test: deliverables belong to the repo, harness bookkeeping belongs to the state directory.
- Expected follow-up: why store structured data when it all gets rendered to text for the model anyway? Because the harness itself reads it - how many attempts on F23, is the completed count over budget, which item did the last crash land on. Against free text those become regexes, and a regex breaks silently when someone rewords one line. As JSON they are field accesses. The split is: structured on disk, rendered to text before the model, with rendering as a pure function.
分析过程 · 先想清楚再作答
- 这题考的是有没有判据,不是能不能列清单。照着「进度、日志、上下文、工具结果」背一串东西的人很多,区分度在于你能不能给出一条**别人可以拿去套自己项目**的判断规则。
- 先把判据摆出来:**下一个窗口要做出正确的下一步,非知道不可的是什么?** 再补一条更好用的反向判据:**这条信息能不能重新算出来?** 能重算的不存,用的时候现取;不能重算的必须存,因为那是历史,历史不可重放。
- 按这两条筛,必须落盘的只有三类。一是**做完了什么**,存 feature 编号而不是描述文本——编号稳定,描述会随需求改。二是**正在做什么**,它的价值容易被低估:进程崩在半路时磁盘上会留下一条悬空的「正在做 F07」,那不是脏数据,它恰恰告诉下一个窗口「F07 成没成不知道,先确认,别当成做完了」。丢掉它,下一个窗口要么重做一遍,要么直接跳过一条根本没做完的。三是**试了几次**,这类信息的价值完全来自跨窗口累计——单看一个窗口「试了一次没成」是噪声,跨五个窗口「试了十二次都没成」是一个必须响的信号,打转检测和预算熔断全建在这个计数上。
- 坚决不写的有两类。**中间推理**:结论落地之后它的使命就结束了,留着既占地方又会让下一个窗口花注意力读一段已作废的思路。**一次性的工具回执**:某次 git status 的输出、某次读文件的内容——它们的特点是随时能重新获取,而且重新获取的那份才是对的。把三小时前的 git status 存下来喂给新窗口,比不给还糟,它会拿着一份过期快照做决定。
- 还有一个位置问题值得主动提,很多人答不到:**状态文件放在被操作的仓库之外**。写进去会有两个后果——它会进 agent 自己的 commit,而且回滚到上一个可用点时账本会被一起回滚掉。回滚的目标是代码,不该把「我已经试过三次」这种教训也一起忘掉。一句话判据:交付给用户的属于仓库,harness 自己的簿记属于状态目录。
- 可预期的追问是「为什么状态存结构化数据,反正最后都要拼成文本喂模型」。因为 harness 自己要读它:F23 试了几次、已完成条数超预算没有、上次崩在哪条上——对着自由文本只能用正则去抠,而正则会在文案改一个字时悄悄失效。存 JSON,这些都是字段访问。分工是磁盘上存结构化数据,喂模型前渲染成文本,渲染是一个纯函数。
Key points
- Test one: what must the next window know? Test two: can this be recomputed?
- Three classes persist: what is done (ids, not descriptions), what is in flight, how many attempts.
- The dangling 'in flight' record is useful: it makes the next window verify instead of assume.
- Attempt counts matter only when accumulated across windows; loop detection and budget cutoffs rest on them.
- Two classes stay out: intermediate reasoning, and one-shot tool receipts that can be refetched.
- Keep the state file outside the worked repo, or it lands in the agent's commits and dies on rollback.
- Structured on disk, rendered to text for the model, because the harness queries it by field.
答题要点
- 判据一:下一个窗口非知道不可的是什么。判据二:这条信息能不能重新算出来。
- 必存三类:做完了什么(存编号不存描述)、正在做什么、试了几次。
- 「正在做什么」的悬空记录是有用信息:它让下一个窗口去确认而不是假设。
- 「试了几次」的价值来自跨窗口累计,打转检测与预算熔断都建在它上面。
- 不存两类:中间推理(结论落地即作废)、一次性工具回执(能重取,且重取的才对)。
- 状态文件放在被操作的仓库之外,否则会进 agent 的 commit、并被回滚一起抹掉。
- 磁盘存结构化数据、喂模型前渲染成文本,因为 harness 自己要按字段查询它。
After a window reset, is replaying the full prior transcript into the model a good idea?窗口重置后,把之前的完整对话历史重新喂给模型是个好主意吗?
Common in ChinaCommon overseasIntermediate#context-engineering#long-horizon#costHow to reason about it · think before answering
- This looks like a cost question, and answering only 'too expensive, will not fit' caps your score. The interviewer wants the second layer: a restored transcript is not merely more expensive, it is harder to act on. Few candidates get there.
- Cover cost first for baseline credit. A measured figure you can quote: three steps produce roughly 960 characters of raw transcript, while the rebuilt summary is about 175 - more than fivefold. Over a night that is hundreds of thousands of tokens against hundreds. Restoration is also self-defeating: you switched windows because the context did not fit, so stuffing it back in overflows immediately.
- Now the real discriminator. Replay the night's stream and the model has to re-derive, from hundreds of steps, which items are actually done. That inference is lossy, costly and entirely unnecessary, because the harness already holds that conclusion in state. Making the model recompute something the caller knows is a design waste.
- Give a concrete case where restoring is worse. In window one the model attempts F07, writes broken code, fails verification, then switches approach and completes F08. Replay that verbatim and the new context contains both the failed F07 code and the later conclusion. The model may well treat the failed code as the current implementation and keep editing it - in context it looks identical to working code. The summary keeps one line: F08 done, F07 attempted once, failed. The ambiguity is gone. Less information, better decisions.
- So the answer is to rebuild an equivalent context rather than restore the original: lossy with respect to transcript, lossless with respect to the decision - drop intermediate reasoning and tool receipts, keep everything the next choice depends on.
- Expected follow-up: how do you know what you dropped was not decision-critical? Offer an operational check - run the rebuilt opening on its own and see whether the model picks the expected next action. The course lab asserts exactly this: rebuild from disk state alone and assert the model proceeds to F10 rather than looping back to F01.
- Another follow-up worth preparing: is this just context compaction? Not quite. Compaction shortens the transcript itself; rebuilding skips the transcript entirely and renders from structured state. One takes history text as input, the other takes fields. They compose, but they are not the same thing.
分析过程 · 先想清楚再作答
- 这题看起来是道成本题,但只答「太贵了、装不下」拿不到高分。面试官想听的是另一层:**还原出来的上下文不只是更贵,它还更难用。** 能说出这一层的人很少。
- 先把成本说清楚,它是基础分。实测过一个数字可以直接用:跑三步产生的原始对话约 960 字符,重建出来的摘要约 175 字符,五倍多的差。跑一整夜是几十万 token 对几百 token。而且还原是不可持续的——上下文本来就是因为装不下才换窗口的,把装不下的东西原样塞回去,新窗口会立刻再次撑爆。
- 然后是真正的区分点。把一晚上的流水原样喂回去,模型得**自己从几百步里重新推断**出「我到底做完了哪几条」。这是一次有误差、有成本、而且完全没必要的重新推断——这个结论 harness 手上明明已经有了,它就记在状态里。让模型去重算一件调用方已知的事,是设计上的浪费。
- 举一个还原反而更糟的具体情形,这是拉开差距的地方:窗口 1 里模型试着实现 F07,写了一段有问题的代码,验证没过,于是改用另一种写法做完了 F08。把这段对话原样还原,新窗口的上下文里就**同时存在**那段失败的 F07 代码和后来的结论。模型很可能把那段失败代码当成现有实现去接着改——在上下文里它和成功的代码长得一模一样。摘要则只留一行「F08 已完成,F07 试过 1 次未成」,歧义消失了。**信息少了,决策反而更准。**
- 所以正解是重建一个**等效上下文**而不是还原原上下文:做一次有损压缩,但对决策无损——丢掉中间推理与工具回执(有损),保留下一步决策所需的全部输入(无损)。
- 可预期的追问是「那怎么判断压掉的东西是不是决策必需的」。给一条可操作的验收方式:拿重建出来的开场单独跑一遍,看模型选的下一步是否与预期一致。本课 lab 里有一条断言就是这么写的——只拿磁盘状态重建上下文,断言模型接着做的是 F10 而不是回头做 F01。
- 还有一个追问值得准备:「那不就是上下文压缩吗」。不完全是。压缩是把对话本身变短,重建是**根本不用对话**,直接从结构化状态渲染。前者的输入是历史文本,后者的输入是字段。两者可以叠加,但不是一回事。
Key points
- No. On cost: three steps of raw transcript measure about 960 characters versus about 175 rebuilt.
- Restoration is self-defeating - you switched windows because it did not fit, so it overflows again.
- The bigger issue is usability: the model re-derives a conclusion the harness already holds.
- Concrete case: failed code and the later conclusion coexist, and the model may keep editing the failed code.
- Rebuild an equivalent context - lossy on transcript, lossless on the next decision.
- Acceptance check: run the rebuilt opening alone and assert the model picks the expected next action.
- Rebuilding is not compaction: one takes history text as input, the other takes structured fields.
答题要点
- 不是好主意。成本上:实测三步的原始对话约 960 字符,重建摘要约 175 字符。
- 还原不可持续:上下文本来就是装不下才换窗口的,塞回去会立刻再次撑爆。
- 更关键的是还原更难用:模型要自己从几百步里重新推断出 harness 已知的结论。
- 具体情形:失败的旧代码与后来的结论并存,模型可能把失败代码当现有实现接着改。
- 正解是重建等效上下文——对对话有损,对下一步决策无损。
- 验收方式:拿重建出的开场单独跑,断言模型选的下一步符合预期。
- 重建不等于压缩:压缩的输入是历史文本,重建的输入是结构化字段。
You added a state layer and the metrics improved. How do you show the state layer caused it, rather than coincidence?你加了一个状态层,跑下来指标变好了。怎么证明是它起的作用,而不是碰巧?
Common in ChinaCommon overseasDeep dive#evaluation#mutation-testing#state-managementHow to reason about it · think before answering
- This is the daily work of the role and the most commonly underrated question. Answering 'I ran an A/B and the enabled arm did better' earns baseline credit only - it shows the two arms differ, not that the difference came from your change.
- Layer one is the precondition for a control: apart from the thing under test, everything must match - same code, same model, same script, same target, only the switch differs. Raise the trap proactively: the switch must never reach the model side. Once the model can sense whether the feature is on, it can play along - work properly when enabled, act forgetful when not. The curve looks great but measures cooperation, not effect. The course's model interface takes a single string precisely to hold this line.
- Layer two is the mutation check, and this is the real discriminator: do not only observe that enabling it improved things - turn it off and confirm the old behavior returns unchanged. With only the first half, a change that never took effect could still coincide with better numbers. Both halves close the causal loop.
- Layer three separates strong answers: mutate the smallest, most load-bearing point rather than disabling the whole layer. Disabling everything is easy but proves less. The lab example is instructive - leave the state layer entirely intact and change only the agreed marker word in the summary from 'done' to a synonym. Completed items drop from nine back to three and wasted steps go from zero back to six. That pins the causal chain to 'the model read progress evidence it recognizes in context', not to 'we wrote an extra file'.
- Layer four is writing assertions in both directions, a tautology trap that is easy to fall into. Asserting only 'the duplication disappeared' is insufficient - an implementation that does nothing at all also satisfies 'no duplicates'. Assert the other half too: the first window contains no repeats and did complete its three items. Both of this course's first-draft assertions were tautologies, caught only by mutation testing.
- Close with the risk specific to this kind of change: state-layer failure is silent. No exception, no warning, the state file looks normal, code review sees nothing - you just find half the expected progress in the morning. So acceptance cannot rest on 'the metric looks good'; you need an assertion that pins the agreed format itself.
- Expected follow-up: what if a real project cannot give you this clean a control? Answer in two parts. Pin down what you can - same task set, same model version, fixed seeds. For what you cannot - real model nondeterminism - repeat and report a distribution, and state where your confidence comes from instead of concluding from a single run.
分析过程 · 先想清楚再作答
- 这题是本岗位每天的功课,也是最容易被轻视的一道。答「跑了 A/B 对比,开的那组更好」只能拿基础分——那只证明了两组有差,没证明差来自你改的那个东西。
- 第一层要说的是**对照的前提**:除了被测的那一处,两次运行的其他一切必须相同。同一份代码、同一个模型、同一份剧本、同一个靶子,只有开关不同。这里有个必须主动提的坑:**开关绝不能传进模型侧**。一旦模型能感知当前开没开,它就能配合演出——开了好好干、关了装傻,曲线很漂亮,但你测的是配合度不是效果。本课的模型接口只收一个字符串,就是为了守住这条。
- 第二层是**变异检验**,这才是真正的区分点:不只看「开了之后变好」,还要**关掉它确认旧现象原样回来**。只有前半句时,一个根本没生效的改动也可能因为别的原因让指标变好。两边都做到,因果链才闭合。
- 第三层最能拉开差距:**变异检验要挑那个最小的、最关键的点去改,而不是整层关掉。** 整层关掉容易,但它证明的东西比较弱。本课 lab 里那个例子很典型:状态层其它部分一个字不动,只把摘要里「已完成」这个约定词换成「做好了」——完成条数立刻从 9 掉回 3,白干步数从 0 回到 6。这说明效果确实来自「模型在上下文里读到了它认得的进度证据」这条因果链,而不是来自「多写了一个文件」。
- 第四层是**断言必须双向写**,这是个容易踩的恒真陷阱。只断言「重复现象消失了」是不够的——一个什么活都不干的实现也能让「无重复」成立。所以要同时断言反向那一半:第一个窗口内部没有重复,**并且确实做满了三条**。本课两条断言的第一版都是恒真的,靠变异检验才发现。
- 最后提一句这类改动特有的风险:状态层的失效是**静默**的。不抛异常、不打警告、状态文件看着完全正常、代码评审也看不出问题,只是早上进度比预期少了一半。所以验收不能只看「指标好不好」,要有一条断言直接钉住那个约定的格式本身。
- 可预期的追问是「真实项目里没法做这么干净的对照怎么办」。分两步答:能控制的部分(同一批任务、同一个模型版本、固定随机种子)尽量控死;控不住的部分(真实模型的随机性)用重复多次取分布,并且**明确说出置信度的来源**,而不是拿单次运行下结论。
Key points
- An A/B shows the arms differ, not that your change caused it. Baseline credit only.
- A control requires everything matched but the tested point, and the switch must never reach the model.
- Mutation check: also turn it off and confirm the old behavior returns, closing the causal loop.
- Stronger: mutate the smallest load-bearing point - change only the agreed marker and completions fall from nine to three.
- Write assertions both ways, or 'the symptom disappeared' is a tautology satisfied by doing nothing.
- State-layer failure is silent, so assert the agreed format itself, not just the metric.
- When real runs cannot be controlled, repeat for a distribution and state where confidence comes from.
答题要点
- A/B 只证明两组有差,不证明差来自你改的那处,这只是基础分。
- 对照的前提是除被测点外一切相同,而且开关绝不能传进模型侧。
- 变异检验:不只看开了变好,还要关掉确认旧现象原样回来,因果链才闭合。
- 更强的做法是改最小的关键点:只换掉约定词,完成数从 9 掉回 3。
- 断言双向写,否则「现象消失」是恒真的——什么都不做也能成立。
- 状态层失效是静默的,所以要有断言直接钉住约定格式本身。
- 真实项目里控不住随机性时,重复取分布并说清置信度来源,不拿单次下结论。
What happens if the process is killed while the state file is being written?状态文件在写入过程中进程被杀了怎么办?
Common in ChinaCommon overseasDeep dive#crash-safety#persistence#state-managementHow to reason about it · think before answering
- This tests imagination about failure shapes. 'Wrap it in try/catch' or 'validate after writing' both miss - the question is not whether writing errors, it is what a mid-write death leaves on disk.
- Name the defect first. Overwriting in place means truncate-then-write: the file is emptied, then filled. Die between those and disk holds a half-written JSON. That is worse than no state at all - with no state the next run knows to start over, while a half file either explodes at parse time or, worse, parses into a partial progress record and redoes finished work.
- The fix is write-temp-then-atomic-rename, in two steps: write the complete content to a temp file in the same directory and fsync it, then rename it onto the real name. Rename within one filesystem is atomic, so a reader at any instant sees either the old complete state or the new complete state, never something in between. Dying between the steps leaves the old complete state plus an unclaimed temp file - garbage, not a trap.
- Three details show field experience and are worth volunteering. The temp file must be in the same directory: rename is atomic only within a filesystem, and writing to a temp dir then moving degrades to a copy - an error that cannot reproduce on one local disk and only bites in a container with separate mounts. The fsync is not optional: without it rename stays atomic but the bytes may sit in page cache, so a power loss can leave the name pointing at an empty block. And the temp name needs a counter: timestamps alone collide within a millisecond, and exclusive-create mode throws on collision, producing a crash that only appears on fast machines.
- The other half matters equally: do not swallow errors on read. Wrapping the parse in try and returning empty is a harmless fallback interactively; unattended it is a silent failure that converts one corruption into a full restart nobody witnesses. You just see half the progress in the morning with no way to trace why. Bad state must be loud.
- Finish by naming your limits, which scores well: strictly, the parent directory needs fsync after rename before the metadata is durable, and the semantics differ across platforms. Stopping at file-level fsync plus atomic rename is a stated trade-off, not an oversight - being able to say where your solution ends shows more judgment than adding another layer.
- Expected follow-up: is writing on every step too slow? Measure before arguing: the state file is a few hundred bytes, while each step contains a model call and process startup, so the write disappears into the noise. If it ever mattered, batch consecutive small writes rather than abandoning crash safety - unattended, that trade costs a whole night.
分析过程 · 先想清楚再作答
- 这题考的是**故障形态的想象力**。答「加个 try catch」或者「写完校验一下」都没打到点上——问题不在写的时候会不会报错,在于死在中间会在磁盘上留下什么。
- 先说清楚坏在哪。直接覆盖写是「截断后再写」:先把文件清空,再往里写内容。进程死在这两件事之间,磁盘上留下的是一个**半截的 JSON**。这比没有状态更糟——没有状态时下一次运行知道自己要从头开始,有个半截文件时它要么在解析上炸掉,要么更坏:解析出一份缺了一半的进度,然后把已经做完的事再做一遍。
- 正解是**先写临时文件再原子改名**,两步:第一步把完整内容写进**同目录**的临时文件并 fsync,第二步 rename 成正式文件。同一文件系统上的 rename 是原子的,所以任何时刻去读,看到的要么是旧的完整状态、要么是新的完整状态,不存在中间态。死在第一步之后第二步之前,磁盘上是「旧的完整状态加一个没人认的临时文件」——那个临时文件是垃圾,不是陷阱。
- 三个细节能体现实战经验,值得主动说。**临时文件必须同目录**:rename 只有在同一文件系统上才原子,写到临时目录再搬过来就退化成拷贝了,而这个错在本机测不出来(同一块盘),只有到了容器里挂载不同卷时才炸。**fsync 不能省**:少了它 rename 仍然是原子的,但内容可能还在页缓存里,断电后会出现「文件名指向一个内容为空的块」。**临时文件名要带自增序号**:只用时间戳会在同一毫秒内撞名,而排他创建模式撞名直接抛错,那会变成一个只在快机器上偶发的崩。
- 另一半同样重要:**读的时候不许吞错。** 把 JSON 解析包在 try 里、失败返回空,是交互式场景的无害兜底,在无人值守场景里它是一类静默故障——一次数据损坏被悄悄变成一次从头重跑,没人看得见,早上只看到进度少了一半,而且完全查不出为什么。坏状态必须响。
- 最后主动交代能力边界,这一步很加分:严格地说 rename 之后还要 fsync 父目录,元数据才算真落盘;各平台语义还不一样。做到文件级 fsync 加原子 rename 是一个明确的取舍,不是忘了——**能说出自己方案的边界在哪,比多做一层更能体现判断力。**
- 可预期的追问是「每一步都写盘不会太慢吗」。先量再说:状态文件是几百字节量级,而一步里有模型调用和进程启动,写盘那点开销在噪声里。真要优化也是先合并连续的小写,不是放弃崩溃安全——无人值守场景里这一条的代价是一整夜。
Key points
- In-place overwrite is truncate-then-write; dying mid-way leaves a half JSON, worse than no state.
- The fix is two steps: write a temp file in the same directory with fsync, then rename it into place.
- Rename within one filesystem is atomic, so readers see old-complete or new-complete, never partial.
- Same directory (cross-volume degrades to copy and cannot reproduce locally), fsync is required, and add a counter to the temp name.
- Do not swallow read errors: catching and returning empty turns corruption into a silent full restart.
- State your limits: parent-directory fsync is omitted deliberately, not forgotten.
- On performance, measure: a few hundred bytes disappears next to a model call.
答题要点
- 直接覆盖写是截断后再写,死在中间会留下半截 JSON,比没有状态更糟。
- 正解是两步:写同目录临时文件加 fsync,再 rename 成正式文件。
- 同一文件系统上 rename 原子,读到的要么是旧的完整状态要么是新的,没有中间态。
- 临时文件同目录(跨卷会退化成拷贝,本机测不出来)、fsync 不能省、文件名带自增序号防撞名。
- 读的时候不许吞错:catch 掉返回空会把一次损坏静默变成一次从头重跑。
- 能力边界要明说:父目录 fsync 没做,是取舍不是遗漏。
- 性能追问先量:状态文件几百字节,开销淹没在模型调用里。
D3 The Initializer Agent: init.sh, a Progress File and the First Commit
Before letting an agent develop a project unattended overnight, what do you have it do first?让一个 Agent 无人值守地开发一个项目,开工前你会先让它做哪几件事?
Common in ChinaCommon overseasIntermediate#initialization#failure-modes#long-horizonHow to reason about it · think before answering
- This tests whether you treat pre-flight as a design object at all. Most people answer with a better prompt or a fuller toolset, which is the answer to an interactive-agent question: when a human is sitting there, environment gaps get patched on the spot. Run unattended overnight and every unpatched gap gets rediscovered by every window that follows.
- The right approach is to work backwards from failure modes rather than listing habits. The primary source groups long-horizon failures into four classes, two of which can be eliminated in the first minute: time wasted figuring out how to run the app, and defects left in the environment with no documentation. Each gets its own mechanism. The other two - declaring victory early, and marking work done without really testing it - belong to the checklist and the end-to-end gate later, not to initialization.
- Against the first failure the mechanism is an `init.sh`. State the trade-off explicitly: why must it be an executable script rather than a paragraph in the README? Because a paragraph can only be read, while a script can be verified - the harness can actually run it once, and if it fails you know the workspace is not ready. You cannot do that to prose, and unattended there is nobody to read the prose anyway.
- Against the second failure the mechanism is a progress note plus an initial commit, two halves of one thing. The note answers where things stand and what comes next. The commit puts both artifacts under version control, and its value is not fully visible on day one - it gives every later rollback a trustworthy floor. Without that floor, rolling back to the last good point has nowhere to land.
- So the answer is three artifacts: `init.sh`, a progress note, a commit. What is worth saying out loud is what they share - the deliverable of initialization is not an explanation, it is three things somebody else can open. Offer a portable test alongside it: for any harness practice, ask which failure it blocks. A practice with no answer was usually copied from somewhere.
- Expected follow-up: is three too thin - should it also run the tests first, or read the whole codebase? Quantity is not the point; verifiability is. Add ten more artifacts and if nobody has run them their credibility is still zero. Volunteer the discipline here: initialization artifacts are generated by an agent, and agents produce scripts that look entirely correct and do not run. So the harness must execute what was generated. The model saying it works does not count.
分析过程 · 先想清楚再作答
- 这题考的是有没有把「开工前」当成一个独立的设计对象。多数人会答「写个好提示词」「把工具配齐」——那是在答交互式 Agent 的问题:人在旁边时,环境有什么坑你当场就补上了。人不在场跑一整夜,没补上的每一个坑都会被后面每个窗口重新踩一遍。
- 正确的拆法是**从失败模式倒推**,而不是凭经验列清单。一手资料把长时程 Agent 的失败归成四类,其中两类在开工第一分钟就能被消灭:**浪费时间摸索怎么把应用跑起来**,以及**在环境里留下缺陷且没有文档**。这两类各配一个机关,其余两类(过早宣布胜利、标记完成但没真测)要靠后面的清单与端到端闸门,不属于初始化。
- 对着第一类失败,机关是一个 `init.sh`。这里有个必须讲清楚的取舍:**为什么必须是可执行脚本,而不是 README 里的一段话?** 因为一段话只能被读懂,脚本可以被**验证**——harness 能真的去跑它一次,跑不起来就知道工作区还没准备好。这件事在一段文字上做不到,而无人值守场景里没有人会替你读那段文字。
- 对着第二类失败,机关是**进度笔记加一个初始 commit**,两件事是一半一半。笔记回答「跑到哪了、下一步做什么」;commit 把前两样放进版本库,它的意义在第一天还看不全——它是给后面所有回滚一个**可靠的地面**,没有这个地面,「回到上一个可用点」就无处可回。
- 所以结论是三件套:`init.sh`、进度笔记、一个 commit。而更值得说出口的是它们的共性——**初始化的交付物不是一段说明,是三件能被别人打开的东西**。顺带给出一条可以带走的反问式判据:遇到任何一条 harness 的做法,问一句「它挡的是哪种失败」,答不上来的做法通常是抄来的。
- 可预期的追问是「三件套是不是太单薄了,要不要再让它先跑一遍测试、先读一遍全部源码」。数量不是重点,**能不能被验证**才是:再加十件产物,只要没人跑过它们,它们的可信度都是零。这里有一条纪律必须主动说——初始化产物是**由 agent 生成的**,而 agent 会写出看起来完全正确却跑不起来的脚本,所以 harness 必须自己跑一遍生成物,模型说写好了不算数。
Key points
- Three artifacts: init.sh, a progress note, an initial commit - all things someone else can open.
- Derive them from failure modes: init.sh blocks time wasted figuring out how to run the app.
- The progress note plus the initial commit block defects left with no documentation.
- It must be an executable script, not prose: prose can only be read, a script can be verified.
- The initial commit gives every later rollback a trustworthy floor to land on.
- Portable test: every harness practice must name the failure mode it blocks.
- The artifacts are agent-generated, so the harness runs them itself. The model's word does not count.
答题要点
- 三件套:init.sh、进度笔记、一个初始 commit,三件都是能被别人打开的东西。
- 按失败模式倒推:init.sh 挡「浪费时间摸索怎么把应用跑起来」。
- 进度笔记加初始 commit 挡「在环境里留下缺陷且没有文档」。
- 必须是可执行脚本而不是一段话:一段话只能被读懂,脚本可以被验证。
- 初始 commit 的意义是给后面所有回滚一个可靠的地面。
- 通用判据:任何一条 harness 做法都要答得出它挡的是哪种失败。
- 产物是 agent 生成的,harness 必须自己跑一遍——模型说写好了不算数。
What belongs in a progress file, and how does a human-readable log differ from a summary written for the next window?进度文件应该写什么?写成给人看的日志和给下一个窗口看的摘要有什么区别?
Common in ChinaCommon overseasIntermediate#progress-file#state-management#long-horizonHow to reason about it · think before answering
- This tests audience awareness. Answering 'record what was done' merely describes a log and earns nothing. The interviewer wants to hear that the file has two kinds of reader, and that the same facts get organized differently for each - not different wording, but a different position for the conclusion and a different way of referring to work.
- Lay out the content first; it is short. Five questions, five answers: how to run it, how many items are complete, which item is next, what counts as complete, and which pitfalls are already known. The last two get skipped most often. 'What counts as complete' defines the acceptance standard - without it the next window declares success on its own terms. 'Known pitfalls' carries the lessons that only mean anything across windows, such as an item already attempted three times without success, so stop retrying it unchanged.
- Now the reader difference. The summary for the next window puts conclusions first, refers to work by feature id rather than description (ids are stable, descriptions drift with requirements), and states outright that earlier conversation no longer exists - omit that and the model assumes it missed something and goes hunting for history that is not there. The human log has to be readable start to finish by someone just picking the work up, so it can afford transitions and background. Same numbers, different register - that sentence alone is a good answer.
- The real discriminator is the next conclusion: the progress file must be regenerated every time, never maintained incrementally. A hand-maintained file will drift - nobody remembers to update it every time, and when they do nobody checks it against reality. Regeneration means it cannot lie, because every number in it comes from the same authoritative state.
- Pin down why that matters with one line: a progress file with wrong content is worse than no progress file at all. With no file, the next window knows it has to go look. With a stale file, it acts on what it reads. The first wastes minutes; the second produces a night of wrong work, and nothing anywhere raises an error.
- Give a verifiable implementation: rewrite the whole file at every window close, then write one assertion - hand-edit a line, trigger another close, and that line must be gone. The assertion pins the regeneration property itself rather than judging whether one particular output reads nicely.
- Expected follow-up: does a full rewrite throw away history? History lives in the commit log, which is append-only and does not drift. Making one repeatedly rewritten text carry both current state and historical record is exactly where it starts lying. Split the two responsibilities across two media and each one can be correct.
分析过程 · 先想清楚再作答
- 这题考的是**读者意识**。答「记录做了什么」只是在描述日志,拿不到区分度。面试官想听的是:你知道这份文件有两类读者,而同一批事实要按读者换一种组织方式——不是换一套文案,是换结论的位置和引用的方式。
- 先把内容摆出来,它其实很短,五问五答:怎么把它跑起来、现在完成了几条、下一步做哪一条、怎么才算一条做完了、已知的坑有哪些。注意最后两条容易被漏掉:「怎么算做完」定义的是验收口径,没有它,下一个窗口会按自己的理解宣布通过;「已知的坑」承载的是跨窗口才有意义的教训,比如某条已经试过三次仍然没成,别再原样重试。
- 然后是读者的差别。**给下一个窗口看的摘要**:结论前置、用 feature 编号而不是描述文本(编号稳定、描述会随需求改)、并且要明说更早的对话已经不存在——不说这句,模型会以为自己漏读了什么而去翻不存在的历史。**给人看的日志**:要能被一个刚接手的人从头读到尾,可以有过渡句、可以解释背景。**两者数字同源、口吻不同**,这是一句可以直接答出去的总结。
- 真正拉开差距的是下一条结论:**进度文件必须每次重新生成,不能增量维护。** 手工增量维护的文件一定会漂移——没人记得每次都改,改了也没人核对它和真实进度是否一致。重新生成意味着它不可能说谎,因为它的每个数字都来自同一份权威状态。
- 为什么这么严重,要用一句话钉死:**一份内容错误的进度文件比没有进度文件更糟。** 没有文件时,下一个窗口知道自己得去查;有一份过期文件时,它会照着做。前者浪费几分钟,后者产出一整夜的错误工作,而且没有任何东西会报错。
- 落到实现上给一条可验收的做法:每次窗口收尾把这份文件整个重写,然后配一条自检——手改其中一行,再触发一次收尾,那一行必须被覆盖掉。这条断言的好处是它直接钉住「重新生成」这个性质本身,而不是去评判某一次输出写得好不好看;输出的措辞可以随时改,性质不能丢。
- 可预期的追问是「整个重写不就把历史丢了吗」。历史在提交历史里,而且那份历史是 append-only、不会漂移的。让一份会被反复重写的文本同时承担「当前状态」和「历史记录」两个职责,正是它开始说谎的起点——两个职责分给两个介质,各自都能做对。
Key points
- Five questions: how to run it, how many done, what is next, what counts as done, known pitfalls.
- Summary for the next window: conclusions first, feature ids, and say earlier conversation is gone.
- Human log: readable end to end, transitions allowed. Same numbers, different register.
- The file must be regenerated each time; incremental maintenance always drifts.
- A wrong progress file is worse than none: with none you go look, with a stale one you act on it.
- Verifiable assertion: hand-edit a line, close a window, and that line must be overwritten.
- Rewriting loses nothing: history belongs to the commit log, one text should not carry both jobs.
答题要点
- 五问五答:怎么跑起来、完成几条、下一条做什么、怎么算做完、已知的坑。
- 给下一个窗口的摘要:结论前置、用 feature 编号、明说更早的对话已不存在。
- 给人看的日志:能从头读到尾,可以有过渡与背景。两者数字同源、口吻不同。
- 进度文件必须每次重新生成,增量维护一定漂移。
- 一份内容错误的进度文件比没有更糟:没有时会去查,过期时会照着做。
- 可验收的断言:手改一行再触发收尾,那一行必须被覆盖掉。
- 整个重写不丢历史:历史归提交历史,一份文本别兼两个职责。
Why make initialization its own phase instead of letting the first pass of the main loop handle it?为什么初始化要独立成一个阶段,而不是让主循环第一轮顺手做掉?
Common in ChinaCommon overseasDeep dive#architecture#initialization#failure-handlingHow to reason about it · think before answering
- This probes structural judgment, not recall, so 'cleaner' and 'more modular' are non-answers - they hold for any split whatsoever. You need the consequence specific to this split, ideally backed by a measurable difference.
- The reason compresses to one sentence worth delivering verbatim: mixed into the main loop, its output lands in the window; as its own phase, its output lands on disk. What lands in a window does not survive the window boundary, so the next window probes again, and the one after that probes again, and every probe returns exactly the same answer.
- The first concrete problem is repeated probing, and it is measurable. Same code, same offline model, the only difference being where initialization lives: probe count drops from three to one, completed items rise from six to nine. Note where that gap comes from - both runs discovered identical facts; only the destination differed. Eight windows a night, each spending half an hour re-establishing the same thing, means half your compute went into repetition.
- The second problem is subtler and is where this question actually separates people: initialization failure and feature failure do not mean the same thing. Initialization failing means the premise of the whole run does not hold, and the correct response is to stop and raise an alarm. One feature failing means just that one did not land, and the correct response is to record it and move to the next. Share one loop and both take the same error path, so 'the environment never came up' gets treated as 'this feature is a bit hard' and the harness cheerfully starts the second one. In the morning you have forty attempted items and a service that never started.
- The third benefit comes free but yields a transferable test: initialization runs exactly once and is idempotent, so it can be retried, verified and cached on its own. A step inside the main loop can do none of those three. Inverted: anything that runs once and must be redone wholesale on failure usually deserves to be its own phase - the same call holds in CI, in data pipelines, in deployment.
- Expected follow-up: is reading init.sh at every window opening not also repetition? Not the same kind. Reading a file on disk is one deterministic constant-cost action; probing is open-ended trial and error whose cost and conclusion both vary. Volunteer one implementation discipline here too - that opening section must be read from disk, not assembled from an in-memory copy of the probe result, or the boundary loses its right to become a real process restart later.
- Another follow-up: what actually happens when initialization fails? The semantics above answer it - halt and alarm, never enter the main loop. That is where the split pays for itself: only a separate phase is allowed separate error handling. Merged, you do not even have a place to express the distinction.
分析过程 · 先想清楚再作答
- 这题考的是结构判断力,不是知识点,所以答「更清晰」「更模块化」等于没答——那两句对任何拆分都成立,换成把日志抽出来、把配置抽出来同样说得通。要给出的是**这个拆分特有的后果**:不这么拆会具体坏在哪里,而且最好能用一个可测量的差把它顶起来。
- 理由可以压成一句话,值得原样答出去:**混在主循环里,它的产物会落在窗口里;独立成阶段,它的产物落在磁盘上。** 落在窗口里的东西活不过窗口边界,于是下一个窗口再探一次、再下一个窗口再探一次,而每次探出来的答案完全一样。
- 第一个具体问题是**重复摸索**,而且是可测的。同一份代码、同一个离线模型,唯一的差是初始化放在哪:探测次数 3 降到 1,完成条数 6 升到 9。注意这个差的来源——两趟探出来的结论一模一样,差别只在它被写到了哪里。一夜八个窗口,每个窗口花半小时重新搞清楚同一件事,就是一半算力花在了重复上。
- 第二个问题更隐蔽,也是这题真正的分水岭:**初始化失败与 feature 失败的语义根本不同。** 初始化失败意味着整个运行的前提不成立,正确处理是停下来报警;某一条 feature 失败只是这一条没做成,正确处理是记下来换下一条。混在同一个循环里,两者会走同一条错误处理路径——于是「环境根本没起来」被当成「这条 feature 有点难」,harness 兴高采烈地接着做第二条。早上你会拿到四十条「已尝试」和一个从来没启动过的服务。
- 第三个好处是顺带的,但它给了一条能迁移的判据:初始化**只跑一次而且幂等**,所以它可以被单独重试、单独验证、单独缓存;主循环里的一步这三件事一件都做不到。反过来说,一个东西只要满足「只跑一次、失败了要整个重来」,它通常就该是一个独立阶段——这条在 CI、在数据管道、在部署流程里同样成立。
- 可预期的追问是「每个窗口开场都去读一次 init.sh,不也是重复吗」。不是同一种重复:读一个磁盘上的文件是一次确定的、O(1) 的动作,摸索是不确定的多轮试错,代价和结论都不稳定。这里还有一条实现纪律值得主动提——开场那段上手信息必须**从磁盘读**,不能拼内存里的探测结果副本,否则这条边界在后面就没资格变成一次真正的进程重启。
- 另一个追问是「初始化失败了到底怎么办」。按上面的语义就有答案:停机报警,不进主循环。这正是把它独立出来的收益兑现的地方——**独立的阶段才允许有独立的错误处理**,混在一起时你连表达这个区别的位置都没有。
Key points
- One-line reason: mixed in, output lands in the window; as a phase, output lands on disk.
- Problem one is repeated probing, measurable: probes three to one, completions six to nine.
- Both runs discover identical facts; only the destination differs.
- Problem two is failure semantics: init failure means halt and alarm, feature failure means move on.
- On one error path, a dead environment reads as a hard feature - forty attempts and no running service.
- Third benefit: it runs once and is idempotent, so it can be retried, verified and cached alone.
- Transferable test: run-once, redo-wholesale work usually belongs in its own phase.
答题要点
- 一句话理由:混在主循环里产物落在窗口里,独立成阶段产物落在磁盘上。
- 问题一是重复摸索,可测:探测次数 3 降到 1,完成条数 6 升到 9。
- 两趟探出来的结论完全一样,差别只在它被写到了哪里。
- 问题二是失败语义不同:初始化失败该停机报警,feature 失败只换下一条。
- 混在一条错误处理路径上,环境没起来会被当成这条有点难,早上拿到四十条已尝试。
- 第三个好处:只跑一次且幂等,所以能被单独重试、验证、缓存。
- 可迁移判据:只跑一次、失败要整个重来的事,通常就该独立成阶段。
How do you judge whether a workspace is friendly enough for a newly arrived agent? Give an executable test.怎么判断一个工作区对新来的 Agent 足够友好?给一个可执行的检验方法。
Common in ChinaCommon overseasDeep dive#acceptance-criteria#verification#developer-experienceHow to reason about it · think before answering
- This asks whether you can turn a correct platitude into a criterion. 'Good docs, clear structure' is effectively a non-answer - two adjectives, neither of which anyone can go run. The interviewer wants a test someone else can follow, with an unambiguous result at the end. The same habit applies to evaluation, to acceptance, to writing SLOs.
- State the criterion: any new session can get going within three minutes. Then immediately decompose it into three executable actions, or it stays a slogan. One, run a single command and know how to start the thing, without guessing from source. Two, read a single file and know where the work stands and what is next. Three, glance at the commit history and know what just happened. Each maps to one artifact, so a failure tells you which artifact is missing.
- Now the real point of the question: acceptance means actually doing it, not nodding at the artifacts. Concretely: spawn a real process running the startup script, poll the port, send a real request, and require a response before it passes. The summary line is answerable as is - an artifact existing does not count, an artifact running does.
- Explain why you need to be this strict or it sounds like fastidiousness: these artifacts were generated by an agent, and agents write scripts that look entirely correct and do not run. Reading them finds nothing, because at the text level there is nothing to find. The same holds anywhere a model generates configuration, scripts or migrations - the harness must execute the output itself. The model's claim that it works does not count.
- Give a concrete pitfall proving that reading cannot replace running, which is where field experience shows: the executable bit. Invoke the startup script through the shell explicitly and it needs no execute permission; invoke it as a path and it does. So forgetting chmod may well be untestable on your own machine, because you happen to use the first form. That class of defect surfaces only when you really run it, and run it the way a new session actually will.
- One more layer worth volunteering: the criterion itself can be a tautology. This course has a ready example - the first assertion checked whether the window opening contained the getting-started section. Change the prefix on the usage lines and both commands vanish, yet the assertion stays green, because the section is still there and merely empty. Rewritten to check that each of the two commands is actually in the context, the same mutation turns exactly one item red. Ask yourself while writing assertions: am I asserting the consequence, or the packaging?
- Expected follow-up: where does three minutes come from? Answer honestly - the number itself does not matter and no experiment fixes a universal three minutes. Its job is to force the standard into a sequence of actions you can time. The real criterion is those three actions, not the number. Swap in five minutes and not one action changes, which is precisely the proof that the criterion rests on the actions.
分析过程 · 先想清楚再作答
- 这题问的是**把一句正确的废话变成判据**的能力。「文档齐全、结构清晰」基本等于没答——两个形容词,没有一个能被别人拿去跑一遍。面试官等的是一条**别人能照着做、做完有明确结果**的检验方法。这个思路在评估、在验收、在写 SLO 时是同一套。
- 先给判据本身:**任何新会话三分钟之内能上手。** 然后立刻把它拆成三个可执行的动作,否则它还是一句口号。一,跑一条命令就知道怎么把它跑起来,不用读源码猜;二,读一个文件就知道做到哪了、下一步做什么;三,看一眼提交历史就知道刚才发生了什么。三条各对一件产物,缺哪一条就知道该补哪件。
- 接着是这题真正的重点:**验收要真的做一遍,不是看着产物点头。** 具体做法是 spawn 一个真进程跑起手脚本、poll 端口、发一个真实请求,拿到响应才算过。一句话总结可以直接答出去:**产物存在不算数,产物跑得起来才算数。**
- 为什么非得这么狠,理由要说清楚,否则听起来像洁癖:**这些产物是 agent 生成的**,而 agent 会写出看起来完全正确却跑不起来的脚本。你读它读不出问题,因为它在文本层面确实没问题。同理,任何「让模型生成配置、脚本、迁移」的设计里,harness 都必须自己跑一遍生成物——模型说写好了不算数。
- 给一个具体的坑来证明「读」代替不了「跑」,这一步最能体现实战经验:**可执行位**。起手脚本如果用 `bash init.sh` 调,不需要执行位;用 `./init.sh` 调才需要。于是漏掉 chmod 这件事在你自己的机器上**很可能测不出来**——你恰好一直用前一种调法。这类缺陷只有真跑、并且按新会话真实的调用方式跑,才会暴露。
- 还有一层值得主动提:**判据本身也可能是恒真的。** 本课有个现成的例子——最初那条断言查的是「窗口开场里有没有上手这个段落」,把用法行的前缀改掉之后两条命令一条都不剩,而断言照样全绿,因为段落确实还在,只是里面空了。改成逐条查那两条命令真的在上下文里,注入同样的变异才恰好一项转红。写断言时问自己一句:我断言的是那件事的**后果**,还是那件事的**包装**?
- 可预期的追问是「三分钟这个数字怎么定出来的」。老实答:数字本身不重要,也没有实验能定出一个普适的三分钟。它的作用是**逼你把标准翻译成一串能计时的动作**——真正的判据是那三个动作,不是那个数。换成五分钟,三个动作一条都不用改,这恰好说明判据落在动作上而不落在数字上。
Key points
- Criterion: any new session gets going within three minutes, decomposed into three executable actions.
- The three: one command to start it, one file for where things stand, one glance at history for what just happened.
- Acceptance means really doing it: spawn the script, poll the port, send a real request.
- An artifact existing does not count, an artifact running does - because an agent generated it.
- The executable bit is the ready example: shell invocation needs none, path invocation does, so a missing chmod hides locally.
- The criterion can be a tautology too: asserting the section heading stayed green after the commands vanished.
- Three minutes is not the point; it forces the standard into actions you can time.
答题要点
- 判据:任何新会话三分钟之内能上手,必须拆成三个可执行动作才有用。
- 三个动作:跑一条命令知道怎么起、读一个文件知道做到哪、看一眼提交历史知道刚发生了什么。
- 验收要真做一遍:spawn 真进程跑起手脚本、poll 端口、发真实请求。
- 产物存在不算数,产物跑得起来才算数——因为产物是 agent 生成的。
- 可执行位是现成的例子:用 bash 调不需要执行位,用路径调才需要,漏掉 chmod 本机测不出来。
- 判据本身也可能恒真:断言盯了段落标题而不是那两条命令,变异之后照样全绿。
- 三分钟这个数不重要,它的作用是逼你把标准翻译成能计时的动作。
D4 One Thing at a Time: The Feature List and git Discipline
When may an agent run git commit on its own, and when must it never?什么情况下 Agent 可以自己执行 git commit,什么情况下绝对不行?
Common in ChinaCommon overseasIntermediate#git#state-management#boundariesHow to reason about it · think before answering
- On the surface this is a question about git hygiene. What it actually tests is whether you can see a premise overturn an engineering conclusion. Reciting either 'agents must never touch git' or 'of course the agent should commit' gets shot down by a counterexample, because each has an explicit ruling behind it: the course where you build a coding agent by hand rules out git commits in favor of content-hash snapshots, while unattended runs treat the commit as the authoritative record of progress. The interviewer wants the criterion that separates the two, not a side.
- Break it open with two questions: whose repository is this, and who will read its history? An interactive coding agent works inside the user's repository. That git log belongs to the user, and stuffing machine-generated entries into it is overreach - and skipping the commits costs nothing, since content-hash snapshots roll back just as well. Zero upside, real downside: do not commit.
- Unattended, the agent works in its own workspace. Nobody else reads that history all night; its only readers are the next window and you in the morning. Here a commit is not pollution but bookkeeping - the commit point is the progress. And not committing has a very concrete cost: no point to fall back to, so any incident means redoing a whole stretch.
- The conclusion compresses into one deliverable line: the criterion is not whether committing is allowed, it is whose repository this is and who reads its history. The same action has opposite correct answers under two premises, which is not a contradiction - it is two premises. Someone who names the premise unprompted usually transfers the judgment to other situations too.
- One design stance worth volunteering: since the commit history is already the authoritative record, do not keep a second list of completed points inside your own state file - query the log by message prefix instead. Two records means two ways to disagree, and git is already a reliable, ordered, rewindable ledger. The portable rule: if an existing authoritative record can answer the question, do not build a second record to answer it.
- Expected follow-up: what if the agent really does have to work in the user's repository? The answer is not a compromise, it is restoring the premise - give it its own copy of the workspace, or fall back to snapshots. And name the consequence of letting that boundary blur: resolve the working directory wrongly once and a commit meant for the sandbox lands in the real repository's history, which is precisely the thing the other course forbids.
分析过程 · 先想清楚再作答
- 这题表面在问 git 规范,实际考的是**一条工程结论能不能被前提推翻**。直接背「Agent 不许碰 git」或者「Agent 当然该自己提交」都会被反例打穿,因为两种说法各有一门课的明确裁定撑着:手搓 Coding Agent 那门课裁定过不能用 git 提交、改用内容哈希做快照,而无人值守场景里 commit 就是进度的权威记录。面试官等的不是站队,是那个能把两边分开的判据。
- 拆法是先问两句话:**这个仓库是谁的?谁会读它的历史?** 交互式 Coding Agent 操作的是**用户的仓库**,那份 git log 是用户自己的东西,工具往里塞机器生成的记录属于越权;而且不提交并没有代价——内容哈希快照一样能回退。既然收益为零、代价是污染别人的历史,结论自然是不提交。
- 反过来,无人值守时 agent 操作的是**自己的工作区**。一整夜没有第二个人会读那份历史,读它的只有下一个窗口和第二天早上的你。这时 commit 不是污染而是记账:提交点就是进度本身,而不提交的代价非常实在——没有可回退的点,出事只能整段重来。
- 所以结论可以压成一句话直接答出去:**判据不是「能不能提交」,是「这个仓库是谁的、谁会读它的历史」。** 同一个动作在两个前提下有两个相反的正解,这不是矛盾,是前提不同。能主动指出前提的人,通常也能把这套判断迁移到别的场景。
- 顺带一条值得主动说的设计取向:既然提交历史已经是权威记录,就不要在自己的状态文件里再存一份「已完成点列表」,直接按提交信息前缀查 git log 就行。两份记录就有两份不一致的可能,而 git 本身已经是一个可靠的、带顺序的、可回退的账本。通用判据是——**能让现成的权威记录回答的问题,不要另建一套记录。**
- 可预期的追问是「那 agent 就是要在用户的仓库里干活呢」。答案不是折中,是把前提改回来:给它一个自己的工作区副本,或者干脆退回快照方案。同时要点出这条边界一旦模糊的后果——工作目录只要解析错一次,本该进沙盒的 commit 就打进了真实仓库的历史,那一瞬间你做的就是另一门课明令禁止的事。
Key points
- The criterion is not whether committing is allowed but whose repository it is and who reads its history.
- The user's repository: committing is overreach, and skipping it costs nothing since snapshots roll back fine.
- The agent's own workspace: committing is bookkeeping, and not committing costs you every rollback point.
- Two courses reach opposite conclusions and both are right, because the premises differ.
- Since the commit history is already authoritative, do not keep a second list of completed points in state.
- Portable rule: if an existing authoritative record answers the question, do not build a second record.
- Blur the boundary and one mis-resolved working directory puts commits into a real repository's history.
答题要点
- 判据不是能不能提交,是这个仓库是谁的、谁会读它的历史。
- 用户的仓库:提交属于越权,而且不提交没有代价——快照一样能回退。
- Agent 自己的工作区:提交就是记账,不提交才有代价——没有可回退的点。
- 两门课结论相反而都对,因为前提不同,不是其中一边写错了。
- 既然提交历史已是权威记录,就不要在状态文件里另存一份完成点列表。
- 通用判据:能让现成的权威记录回答的问题,不要另建一套记录。
- 边界模糊的后果:工作目录解析错一次,commit 就打进真实仓库的历史。
How do you force an agent to do one thing at a time? Is putting it in the prompt enough?怎么强制一个 Agent 一次只做一件事?写在提示词里够吗?
Common in ChinaCommon overseasIntermediate#feature-list#constraints#harness-designHow to reason about it · think before answering
- This question is about where a constraint lives. The usual answer - write 'do one feature at a time' in the prompt, and write it emphatically - is also the least reliable one. A prompt is a request, not a constraint. The model can ignore it, and when it does nothing raises an error: what you find in the morning is a pile of half-done items, not an error message.
- Start by asking who is choosing the task right now. The common design pastes the whole feature list into the context and lets the model pick the first item without evidence of completion. It works day to day because the evidence happens to be correct - but the basis for the decision lives in the context. Compact it once, drop one line from a summary, change the shape of the evidence, and the model picks wrong and quietly builds something it should not have.
- The fix is to take task selection back from the model and give it to the harness: the harness reads the checklist on disk, picks the next item, and renders only that one item into the context. The target in this course has forty items; the model sees exactly one per step and the other thirty-nine are not in front of it. Skipping ahead is not forbidden - there is no longer any way to express it. That is what enforcement means.
- The conclusion is deliverable as is: move the basis for the decision from the context to disk and the constraint goes from discouraged to impossible. A prompt can only reach the first of those. The pattern transfers to any rule you do not want the model improvising around - ask first whether that rule currently lives in the context or in the code.
- One consequence is worth volunteering because it looks like earlier work was wasted: once selection moves to the harness, the duplicated work that the state layer used to prevent no longer reappears even with the state layer switched off, because selection does not consult the context any more. Nothing was wasted - that regression was replaced by a stronger structure. The cross-window summary still earns its place (it tells the model where the project stands and it is readable by a human), but correctness no longer depends on it.
- Two follow-ups to expect. Should the prompt still say it? Yes, but demoted from guarantee to explanation; the real gate is in the loop. Does one-at-a-time slow things down? The constraint limits how many tasks appear in a step's context, not how much code a step may write. Step count and pace are unchanged - what changed is that choosing the next item now rests on something that cannot drift.
分析过程 · 先想清楚再作答
- 这题考的是**约束写在哪里**。最常见的答案是「在提示词里把『一次只做一条』写清楚、写重一点」,它也是最不可靠的答案:提示词是一个请求,不是一个约束。模型可以不照办,而且不照办之后没有任何东西会报错——你早上拿到的是一堆做了一半的条目,而不是一条错误信息。
- 拆的第一步是问:**现在是谁在挑任务?** 常见做法是把整张 feature 清单贴进上下文,让模型按「第一条没有完成证据的」自己挑。这条路平时不出错,靠的是证据一直是对的;但判断依据活在上下文里,压缩一次、摘要漏写一条、某次证据格式对不上,模型就会挑错,而且它只是安静地做了一条不该做的。
- 正解是把任务选取从模型手里收回到 harness:harness 读**磁盘上**的清单挑出下一条,然后**只把这一条渲染进上下文**。本课靶子有四十条,模型每一步只看得见一条,另外三十九条根本不在它眼前。于是「跳着做」不是被禁止了,而是没有表达它的入口——这才叫强制。
- 结论值得原样答出去:**判断依据从上下文搬到磁盘,约束就从「不被鼓励」变成「不可能发生」。** 提示词能做到的上限是前者。这个句式可以迁移到任何一条你不想让模型自由发挥的规则上:先问它现在活在上下文里还是活在代码里。
- 有一个连带后果值得主动说,因为它看起来像是前面的设计白做了:任务选取上收之后,**之前靠状态层挡住的重复劳动,现在把状态层关掉也不会重现**——选谁做已经不看上下文了。这不是前面白学了,是那个退化被一个更强的结构取代。跨窗口摘要仍然有用(让模型知道整体进度、让人能读),但它不再是正确性的依赖。
- 可预期的追问有两个。一是「那提示词里还要不要写」:要写,但它的角色降级成解释而不是保证,真正的闸门在循环里。二是「一次只做一条会不会把进度拖慢」:这条约束限制的是**每一步上下文里放几条任务**,不是每步能写多少代码,步数与完成节奏都没变——变的只是每一步挑谁做这件事有了一个不会漂移的依据。
Key points
- A prompt is a request, not a constraint: when it is ignored, nothing raises an error.
- Ask who picks the task: pasting the whole list puts the decision basis in the context.
- Contexts get compacted and summaries drop lines, so a bad basis silently builds the wrong item.
- The fix: the harness reads the checklist on disk and renders only the chosen item.
- One of forty items is visible per step, so skipping ahead has no way to be expressed.
- Move the decision basis from context to disk and the constraint goes from discouraged to impossible.
- Side effect: the regression no longer depends on the state layer; summaries serve readability, not correctness.
答题要点
- 提示词是请求不是约束:模型不照办时没有任何东西会报错。
- 先问谁在挑任务:贴整张清单等于把判断依据放在上下文里。
- 上下文会被压缩、摘要会漏写,依据一坏模型就安静地做错一条。
- 正解是 harness 读磁盘上的清单挑一条,只把这一条渲染进上下文。
- 四十条里模型只看得见一条,跳着做没有表达它的入口,这才叫强制。
- 判断依据从上下文搬到磁盘,约束就从不被鼓励变成不可能发生。
- 连带后果:退化不再依赖状态层挡着,摘要降级为可读性而非正确性依赖。
An agent botches one feature - do you roll back or let it keep fixing? On what basis?Agent 做坏了一条 feature,你会让它回滚还是继续修?依据是什么?
Common in ChinaCommon overseasDeep dive#rollback#green-point#risk-exposureHow to reason about it · think before answering
- This tests the criterion for rolling back, not the act of rolling back. 'It depends' and 'fix it if you can, otherwise revert' earn nothing, because they hold for every failure equally. The interviewer wants two things: which quantity you compare, and why the point you revert to can be trusted. The second half is the one people skip, and it is where this question actually separates candidates.
- Pin the target first: the only legal rollback target is a green point - a point that passed end-to-end verification and has been committed. A hard reset discards uncommitted changes, which is exactly what you want, because the purpose is to restore the workspace to a state that was verified. Revert to an unverified commit instead and you have promoted an unknown state to a known one, which is worse than not reverting.
- The quantity to compare is not output, it is risk exposure. This course measured two commit granularities side by side: one commit per feature versus one commit per window. Both finished the same nine items - identical. What differed was the peak amount of completed-but-uncommitted work, which dropped from three items to zero. Judged on output alone the whole day looks pointless, which is precisely why it makes a good question.
- Stage a disaster and it becomes visible: eight items done, the ninth in flight, and the workspace gets corrupted. Each side reverts to its most recent fully verified commit. The per-window side lands on 'window 2 wrap-up, 6 items done' and keeps six, losing two items of finished, verified work. The per-feature side lands on the commit for item eight and keeps all eight, losing nothing. Note that per-window is not without rollback points - its wrap-up commits are perfectly revertible. What it lacks is granularity: a rollback point every three items means reverting drags two neighbors down with it.
- So the criterion: first ask whether this failure contaminated already-verified work. If it did not, keep fixing - reverting would throw away neighboring results for nothing. Once the workspace state cannot be trusted, revert to the nearest green point. What makes that decision cheap is green points being dense, which is why one-at-a-time is not a style preference: it is the only way to drive that loss to zero.
- One honest boundary is worth stating unprompted: before end-to-end verification is wired into the loop, these commit points do not yet qualify as green points. A green point is verified and committed; while verification is still a shallow check - the patch landed, therefore it passed - the first half is empty. Distinguishing commit point from green point in an interview reads far better than using the terms interchangeably.
- Expected follow-up: what happens to the checklist after a rollback? The checklist must ride in the same commit as the code change, so it returns to that point too and the two are correct together at every commit. Split them across two commits and a rollback produces the hardest inconsistency to trace - the code reverted while the checklist still claims the work is done - and the next item gets chosen from a checklist that is lying.
分析过程 · 先想清楚再作答
- 这题考的是**回滚的判据**,不是回滚这个动作。答「看情况」「能修就修、修不好就退」没有任何区分度,因为它对任何一次失败都成立。面试官想听的是两件事:你拿什么量去做这个比较,以及你回退过去的那个点凭什么可信。后一半比前一半更容易被忽略,而它恰恰是这题真正的分水岭。
- 先把回退目标定死:**回退的目标只能是绿点**——通过端到端验证**并且**已提交的那个点。硬回退会丢掉未提交的改动,这正是我们要的效果,因为回退的目的就是把工作区恢复成一个已经验证过的样子。但反过来,退到一个没验证过的提交,等于把一个未知状态当成已知状态,比不退更糟。
- 比较用的量不是产出,是**风险敞口**。本课实测跑了两种提交粒度的对照:一条一提交与一个窗口提交一次,最后完成条数都是 **9**,一模一样;差别在「未提交工作的最大暴露量」,从 **3 条**降到 **0 条**。如果只盯产出看,这一天的设计会显得毫无意义——这正是它值得考的原因。
- 把它变成一场灾难就看得见了:已完成 8 条、正在做第 9 条时工作区被写坏,两边各退到自己最近一个全部验证过的提交。按窗口提交的那边退到「窗口 2 收尾,已完成 6 条」,保住 6 条丢掉 2 条已经做完并验证过的工作;一条一提交的那边退到第 8 条对应的那个提交,保住 8 条丢 0 条。注意**按窗口提交并不是没有可回退点**,它的收尾提交同样能退,差的是**粒度**:可回退点每三条才有一个,退回去就要连累旁边两条。
- 于是判据出来了:先看这一条的失败有没有污染已经验证过的工作。没污染就继续修(回退会连带丢掉旁边的成果);一旦工作区状态不可信,就退到最近的绿点。而让这个决定变得廉价的前提是**绿点足够密**——「一次只做一件事」不是风格偏好,它是把这个损失压到零的唯一办法。
- 还有一条诚实的边界值得主动交代:在把端到端验证接进循环之前,这些提交点严格说**还没有资格叫绿点**。绿点的定义是「通过端到端验证并且已提交」,验证还是浅检查(补丁写进去就算过)时,前半句是空的。面试里主动区分「提交点」与「绿点」,比把两者混着叫更有说服力。
- 可预期的追问是「退回去之后清单怎么办」。清单必须和代码改动进**同一个 commit**,所以它跟着一起回到那个点,两者在任何一个提交上都同时正确。如果它们分在两个提交里,回退就会退出「代码退了、清单还记着已完成」这种最难查的不一致——下一步做什么会从一份错的清单里挑出来。
Key points
- The only rollback target is a green point: verified end to end and committed.
- Compare risk exposure, not output: both granularities finished the same nine items.
- What differs is peak uncommitted work: three items down to zero.
- Same disaster at eight items done: per-window loses two, per-feature loses none.
- Per-window does have rollback points; it lacks granularity, so reverting drags neighbors down.
- Criterion: keep fixing if verified work is uncontaminated, revert once the workspace is untrustworthy.
- Honest boundary: while verification is shallow these are commit points, not yet green points.
答题要点
- 回退目标只能是绿点:通过端到端验证并且已提交,退到未验证的点更糟。
- 比较的量是风险敞口不是产出:两种粒度完成条数都是 9,一模一样。
- 差的是未提交工作的最大暴露量:3 条降到 0 条。
- 同一场灾难:已完成 8 条时出事,按窗口提交丢 2 条,一条一提交丢 0 条。
- 按窗口提交不是没有可回退点,差的是粒度——退一次连累旁边两条。
- 判据:没污染已验证的工作就继续修,工作区不可信就退到最近绿点。
- 诚实边界:验证还是浅检查时,那些点只是提交点,还不配叫绿点。
What can go wrong when an agent drives git, and how do you prevent it in code?让 Agent 操作 git 有哪些具体的危险?你会怎么在代码层面防住?
Common in ChinaCommon overseasDeep dive#git#safety-invariants#assertionsHow to reason about it · think before answering
- This asks for the dangers and the defenses, and you owe both halves - giving only one is an unfinished answer. 'Be careful' and 'add human review' score nothing, because the whole premise of unattended work is that nobody is there to review, so any plan resting on a human catching it in the moment is void here. What you should produce is a handful of invariants that can be written into code and pinned by assertions.
- The biggest danger is also the easiest to overlook: resolving the working directory wrongly. An agent committing for itself is fine as long as it commits into its own workspace, but let the cwd be inherited from somewhere else and those commits land in your real repository's history - which is exactly the overreach the interactive case forbids. The defense: funnel every git call through one function, make the working directory a required first argument, pass it explicitly on every invocation, never rely on an inherited cwd, and add a static check asserting the literal git appears nowhere outside that file.
- The second danger is splitting the code change and the checklist change across two commits. This course ran the mutation: remove the single line that writes the checklist back to disk when an item is marked passed, change nothing else, and the program still runs, still completes nine items, and every earlier assertion stays green - while the checklist on disk sits at zero passed. In the rollback experiment that side becomes nine patches against zero checklist entries. The defense is small: bind updating the checklist and committing into one action, and assert that the on-disk checklist agrees with authoritative state.
- The shape shared by this class of failure is worth memorizing on its own: right in memory, wrong on disk. Nothing errors, nothing crashes; it surfaces only when you roll back, restart, or hand the repository to somebody else. So anywhere a fact lives both in memory and on disk, something must be responsible for noticing divergence. Divergence is not the danger - divergence nobody can see is.
- The third danger is choosing the wrong target for a hard reset. It discards uncommitted changes, which is the point, but it also means there is no second chance. The defense is to make 'there is no rollback target at all' an explicit error rather than silently resetting to the current head and reporting success - that turns a failed rollback into something that looks like a successful one.
- One more design stance, which lowers several of these risks at once: read green points straight out of the commit history instead of keeping a second copy in your own state file. Two records means two ways to disagree, and git is already a reliable, ordered, rewindable ledger. A small implementation trap worth mentioning: when filtering by commit-message prefix, do not switch on extended regular expressions - the opening parenthesis becomes a grouping operator and git simply reports unbalanced parentheses.
- Expected follow-up: how do these invariants avoid decaying over time? Through assertions, not discipline. Each danger gets a runnable check - a static scan for the single git exit, a disk-versus-state comparison for consistency, and a deliberate corruption to see where a rollback actually lands. Then run the mutation: switch the defense off and confirm the matching check really turns red, or what you pinned may just be a tautology.
分析过程 · 先想清楚再作答
- 这题问的是危险**加**防法,两半都要给,只给其中一半都算没答完。只答「小心一点」「加人工 review」拿不到分——无人值守的前提就是没有人在旁边 review,任何依赖人当场把关的方案在这里都不成立。要拿出的是几条能写进代码、并且能被断言钉住的不变量。
- 第一个危险最大也最容易被忽略:**工作目录解析错。** agent 自己提交本身没问题,前提是它提交到自己的工作区;可一旦 cwd 被继承成别的目录,那些 commit 就打进了你真正的仓库历史,那一瞬间它做的正是交互式场景里明令禁止的越权。防法:所有 git 调用收口到一个函数,工作目录是**必填**的第一个参数,每次显式传 `-C`,禁止依赖继承的 cwd;再配一条静态检查,断言 git 这个字面量不出现在那个文件之外。
- 第二个危险是**代码改动与清单改动分在两个 commit 里**。本课做过一次变异实验:把标记通过时那一行写盘去掉,其它一个字不动,程序照样跑、照样做完九条、前几天的断言照样全绿,只有磁盘上的清单一直停在零条已通过;回退实验里那一边直接变成「9 条补丁 / 0 条清单」。防法很简单——把改清单与提交绑成同一个动作,并加一条断言核对磁盘清单与权威状态对不对得上。
- 这类故障的共同形状值得单独记住:**内存里对、磁盘上错。** 它不报错也不崩,只在你回退、重启、或者换个人来看这个仓库的时候才暴露。所以凡是「内存里有一份、磁盘上也有一份」的地方,都得有人负责发现两者分叉——分叉本身不可怕,**没人看得见的分叉**才可怕。
- 第三个危险是硬回退的目标选错。它会丢掉未提交的改动,这是回退想要的效果,但也意味着没有第二次机会。防法是把「一个可回退目标都没有」写成显式抛错,而不是默默退到当前 HEAD 假装成功——后者会让一次失败的回退看起来像一次成功的回退。
- 再给一条设计取向,它同时降低了前面几类风险:绿点直接查提交历史,**不在自己的状态文件里另存一份**。两份记录就有两份不一致的可能,而 git 已经是一个可靠的、带顺序的、可回退的账本。实现上还有个能提的小坑——按提交信息前缀过滤时不要开扩展正则,左括号会被当成分组符,git 直接报括号不配对。
- 可预期的追问是「这些不变量怎么保证不随时间退化」。靠断言,不靠纪律:每一条危险配一条能跑的检查——收口那条用静态扫描,一致性那条用磁盘清单与状态对照,回退那条用「人为弄坏一条再看它退到哪」。写完之后一定要做变异检验,把防线关掉确认对应的检查真的转红,否则你钉住的可能只是一条恒真的断言。
Key points
- The biggest danger is a mis-resolved working directory: commits land in the real repository.
- Defense: one git entry point, a required explicit working directory, plus a static scan asserting it.
- Second danger: code and checklist in separate commits leaves the hardest inconsistency to trace.
- Measured mutation: drop the write-to-disk line and everything stays green, yet rollback gives nine patches and zero checklist entries.
- Shared shape: right in memory, wrong on disk - silent until a rollback, a restart, or a new pair of eyes.
- A hard reset with no target must raise an explicit error, never quietly reset to head and claim success.
- Read green points from the commit history; and do not enable extended regex when filtering message prefixes.
答题要点
- 最大危险是工作目录解析错:commit 会打进真实仓库的历史。
- 防法:git 调用收口到一个函数,工作目录必填、每次显式传,并加静态扫描断言。
- 第二个危险是代码与清单分在两个 commit:回退会留下最难查的不一致。
- 实测变异:去掉写盘那一行,程序照跑照绿,回退实验变成 9 条补丁 / 0 条清单。
- 共同形状是内存里对、磁盘上错——不报错,只在回退或换人接手时暴露。
- 硬回退没有目标时必须显式抛错,不能默默退到 HEAD 假装成功。
- 绿点查提交历史不另存一份;过滤提交信息前缀时别开扩展正则。
D5 Self-Verification: The End-to-End Gate, Premature Completion Claims and Automatic Intervention on Doom Loops
An agent reports a task done - what evidence should your harness use to decide whether to believe it?Agent 报告任务完成,你的 harness 该用什么依据决定信不信?
Common in ChinaCommon overseasIntermediate#self-verification#false-completion#gatesHow to reason about it · think before answering
- This question is about who owns the completion criterion. 'Have it double-check' and 'tell it in the prompt to run the tests first' both score zero, because the evidence still comes from the party under review. An inspector does not read the contractor's self-assessment. The shallow check used for the first four days has exactly this shape: the patch landed in the file, therefore it passed. It can only prove something was written, never that it is right.
- Start by dropping the word 'lying'. The model is not lying - it can only see its context, it has never run that service, so how would it know that what it wrote does not work. False completion is not a character flaw in the model, it is a direct consequence of the constraint that it only sees context. Which means any fix built on prompt wording, including 'please report honestly', is void from the start.
- Separate the three shapes of a false completion: only the shell was written (the route is registered, it returns 200, the side effect never happens), the tests were not run (it says they were), and the tests ran but the result was misread. All three slip through code review easily, and they share one property - only actually starting the service and sending a real request catches them.
- So the evidence has to be produced by the harness itself, which in practice means three gates in a fixed order: syntax, then it boots, then the cases. Syntax is the cheapest, so it runs first. The three fail for entirely different reasons, and in the report a human reads, 'the syntax is broken' and 'the behavior is wrong' are two different things. Collapsing them into one 'verification failed' leaves you with nowhere to start in the morning.
- One threshold people skip is worth volunteering: if not a single case covers this feature, it cannot count as passed. Absence of evidence is not a pass - a gate that returns success on an empty suite looks permanently green while verifying nothing. That is the most common entrance to a tautological gate.
- The measured numbers are worth memorizing because they run against intuition. Same code, same model: on the shallow-check run the checklist claimed 33 items, 24 actually worked, 9 were false. On the end-to-end run it claimed 20, 20 actually worked, 0 were false. The shallow numbers look better. In the morning you see the prettier progress first and only then discover the service misbehaves - with nothing on the checklist flagged as suspect.
- Expected follow-up: why re-run everything already passed instead of just this item? Because one class of defect passes its own case. F07 in this course forgets the guard for 'no tag supplied means pass the list through'; queried with a tag it filters perfectly, and what it breaks is everybody else - eight features that were built correctly, measured. A gate that only checks the current item is blind to it; the regression suite is what catches it.
分析过程 · 先想清楚再作答
- 这题考的是**完成判据归谁**。答「让它再自查一遍」「提示词里要求它先跑测试」都拿不到分,因为这两条给出的证据仍然由被审查的一方生产。监理不看施工队的自我评价。前四天的浅检查就是这种形态:补丁写进文件就算过,**它只能证明「写了」,证明不了「对」**。
- 拆的第一步是把「谎报」这个词先摘掉。**模型不是在撒谎**——它只能看见上下文,它没有跑过那个服务,凭什么知道自己写的东西不 work。所以谎报完成不是模型品行问题,是「只能看见上下文」这条约束的必然结果,任何靠改提示词、加一句「请如实汇报」的方案都从根上不成立。
- 谎报有三种形态要分开:只写了壳(路由注册了、返回 200,副作用根本没发生)、测试没跑(它说跑了)、跑了但看错了结果。三种在代码 review 时都很容易漏掉,共同点是**只有把服务真的起起来发一个真请求才拦得住**。
- 于是判据只能是 harness 自己产出的证据,落地是三道门:**语法 → 起得来 → 用例**,顺序不能换。语法那道最便宜先跑;三道门的失败原因完全不同,给人看的报告里「语法坏了」和「行为不对」是两件事,混成一句「验证失败」会让早上排查时无从下手。
- 有一条容易被跳过的门槛要主动说:**一条用例都没覆盖到这条 feature 时不能算通过**。没有证据不等于通过——一个「套件为空就返回通过」的闸门看起来永远绿,而它什么都没验。这是恒真闸门最常见的入口。
- 实测数字值得原样背下来,因为它反直觉:同一份代码、同一个模型,浅检查那趟清单**声称 33 条、实测真能用 24 条、谎报 9 条**;端到端闸门那趟**声称 20 条、实测 20 条、谎报 0 条**。**浅检查的数字更好看。** 早上你会先看到一个更漂亮的进度,然后才发现服务的行为是错的,而清单上没有任何一条写着可疑。
- 可预期的追问是「验这一条就行了,为什么要把已通过的全部重跑」。因为有一类缺陷自己的用例是过的:本课的 F07 忘了「没传 tag 就原样放行」的守卫,带 tag 查时它筛得好好的,**被它弄坏的是别人**——实测连累了八条本来做对的 feature。只验当前这一条的闸门对它完全无能为力,抓住它的是回归套件。
Key points
- The completion criterion cannot be produced by the party under review: self-reports are not evidence.
- A shallow check proves something was written, never that it is right.
- False completion is not lying: the model never ran the service, so it cannot know.
- The only trustworthy evidence is booting the service and sending real requests: syntax, boots, cases.
- The three gates fail for different reasons, so the report must keep them apart.
- No case covering the feature means no pass - absence of evidence is not a pass.
- Measured: shallow claims 33 / 24 usable / 9 false; end-to-end 20 / 20 / 0 - and shallow looks better.
答题要点
- 完成判据不能由被审查的一方生产:模型的自我报告不是证据。
- 浅检查只能证明写了,证明不了对:补丁落地就算过是故意留的破洞。
- 谎报不是撒谎:模型没跑过那个服务,凭什么知道自己写的不 work。
- 唯一可信的依据是真把服务起起来发真请求,三道门顺序固定:语法、起得来、用例。
- 三道门失败原因不同,报告里语法坏了和行为不对必须分开写。
- 没有用例覆盖到就不能算通过——没有证据不等于通过。
- 实测:浅检查声称 33 / 真能用 24 / 谎报 9,端到端 20 / 20 / 0,浅检查数字更好看。
Browser automation versus HTTP-only end-to-end verification - what does each buy and cost?端到端验证用浏览器自动化和只打 HTTP 接口,各有什么得失?
Common in ChinaCommon overseasIntermediate#end-to-end#trade-offs#capability-boundaryHow to reason about it · think before answering
- This is about choosing a verification layer, not picking a winner. 'Browsers are obviously more realistic' and 'API tests are faster and more stable' each cover one half. The interviewer wants to hear what each layer cannot see, and which conditions forced the choice. Stating the boundary out loud says more about engineering maturity than which side you land on.
- Break it open with two conditions: does the target have a user interface, and where does this gate have to run? The target here is a pure HTTP service, and the repository imposes a hard rule on labs - they must run offline without asking the reader to install docker or a browser driver. Together those leave HTTP as the only option: a trade-off forced by constraints, not a preference.
- Name the cost before you are asked: rendering defects are entirely invisible at the HTTP layer. A blank page, a collapsed layout, a button that does not respond - the endpoint still returns 200, the probes still go green, the gate still lets it through. The Anthropic article uses browser automation precisely to cover that layer. Choosing HTTP means accepting that this class of defect survives until morning.
- The browser side has real costs too: another runtime dependency, slower runs, more flakiness - and that cost multiplies by item count, because the end-to-end gate runs a regression suite on every single feature, not once a night. At that multiplier, flakiness turns into a genuine problem, and the next question covers why it is worse than having no gate at all.
- The conclusion compresses into one line: switching to a browser driver changes nothing about the structure taught here - set up the environment, run the cases, and only a pass may flip the state. All three steps hold verbatim; only how the cases execute inside step three changes. These are not two designs, they are two execution layers of one gate, and which layer you pick depends on which layer your defects show up in.
- One more boundary, stated plainly: a probe may speak only through requests - no spawning processes, no reading source files, no touching git. A 'probe' that greps the source to see whether some function exists proves nothing about the service's behavior; it quietly degrades the gate you just built back into a shallow check - end-to-end in form, day one in substance.
- Expected follow-up: how do you combine them in a real project? In layers. HTTP carries the full regression (cheap enough to run per item), the browser covers a small set of critical paths (too expensive to run per item). The criterion is not which is more realistic but which layer a given class of defect becomes visible in - split that way, the two layers cover things that do not overlap.
分析过程 · 先想清楚再作答
- 这题考的是**验证层次的取舍**,不是选型站队。答「当然浏览器更真实」或者「接口测试更快更稳」都只说了半边;面试官想听的是你能不能把各自**测不到什么**说清楚,以及这个选择被什么条件逼出来。能力边界说在明处,比选了哪一边更能说明工程成熟度。
- 拆法是先问两个条件:**靶子有没有界面**,以及**这套闸门要在什么环境里跑**。本课的靶子是纯 HTTP 服务,而本仓库对 lab 有一条硬约束——必须离线可跑、不依赖读者装 docker 或浏览器驱动。两个条件叠起来,HTTP 层是唯一选项,这是被约束逼出来的取舍而不是偏好。
- 代价必须主动交代,不能等对方问:**渲染类缺陷在 HTTP 层一个都测不到。** 页面白屏、样式塌掉、按钮点不动——接口照样返回 200,探针照样全绿,闸门照样放行。Anthropic 那篇原文用的是浏览器自动化,正是为了盖住这一层。你选了 HTTP 就等于承认这一类缺陷会一路漏到早上。
- 反过来浏览器那侧也有实打实的代价:多一套运行时依赖、慢、更容易偶发失败,而这个成本要乘以条数——端到端闸门在**每一条 feature** 上都跑一次回归套件,不是整晚跑一次。偶发失败在这个量级上会被放大成一个真正的麻烦,下一题会讲它为什么比没有闸门更糟。
- 结论可以压成一句话答出去:**换成浏览器驱动时,这一天讲的结构一个字都不用改**——起环境、跑用例、只有过了才允许翻状态,三步原样成立,变的只是第三步里用例的执行方式。所以这不是两套设计,是同一个闸门的两种执行层,选哪一层取决于你的缺陷会落在哪一层。
- 还有一条边界顺带说清楚:**探针只许通过发请求说话**,不 spawn 进程、不读源码、不碰 git。一条会去 grep 源码看某个函数在不在的「探针」证明不了服务的行为,它只会把刚建好的闸门重新退化成浅检查——形式上还是端到端,实质上回到了第一天。
- 可预期的追问是「真实项目里怎么配」。分层:HTTP 层做全量回归(便宜、可以每条都跑),浏览器只在少数关键路径上跑(贵、跑不起全量)。判据不是哪个更真实,而是**这一类缺陷会在哪一层显形**——照这个判据分,两层各自负责的东西是不重叠的。
Key points
- It is a choice of verification layer; the point is naming what each cannot see.
- HTTP was forced here by a hard rule: labs run offline, with no docker or browser driver.
- State the cost: rendering defects are invisible - blank pages and dead buttons still return 200.
- The browser side costs dependencies, speed and flakiness, multiplied by a per-feature regression suite.
- Swapping in a browser driver changes only how cases execute, not the structure of the gate.
- Probes may speak only through requests; one that reads source code reverts the gate to a shallow check.
- In practice, layer them: HTTP for full regression, browser for a few critical paths.
答题要点
- 这是验证层次的取舍,重点是说清各自测不到什么,而不是站队。
- 本课选 HTTP 是被硬约束逼出来的:lab 必须离线可跑、不装 docker 与浏览器驱动。
- 代价明写:渲染类缺陷全盲——白屏、样式塌、按钮点不动,接口照样 200。
- 浏览器侧的代价是依赖、慢与偶发失败,而闸门在每条 feature 上都跑一次回归套件。
- 换成浏览器驱动时结构一个字不用改,变的只是用例的执行方式。
- 探针只许通过发请求说话:去读源码的探针会把闸门退回浅检查。
- 真实项目分层:HTTP 跑全量回归,浏览器只覆盖少数关键路径。
How do you detect an agent spinning in place, and what do you do once you have?你怎么发现一个 Agent 在原地打转?发现之后该做什么?
Common in ChinaCommon overseasDeep dive#stall-detection#intervention#rollbackHow to reason about it · think before answering
- The second half is where this question separates people. Detecting a spin is already-solved ground - the course where you build a coding agent by hand has a stall signal for a checklist that has not moved in ten rounds, but it only detects, because a human is sitting right there and will take over. Unattended, detection without intervention is worthless: who is the alarm for? The hard part is the automatic decision that follows.
- Still answer the detection half properly: the checklist stops moving, the same feature keeps being reopened, the same file keeps being edited. All three point at one thing - steps going up while progress does not. Note that each is a fact the harness can observe on its own, with no cooperation from the model, which is the same principle as the previous question.
- There are exactly three interventions: retry, skip to another item, halt and alert. The criterion is whether the workspace is dirty, not how many times it failed. This is the one people get backwards. An empty patch can fail three times with the workspace still spotless, and reverting there throws away verified neighboring work. One line to remember: a rollback is not a punishment for failure, it is a repair of the foundation - if the foundation is intact, do not revert.
- Retries need a ceiling, and a low one. The reasoning is blunt: feed the same input to the same model and why would the second attempt differ? A retry only means something when this input differs from the last one, because a failure reason has been added to the context. Unlimited retries are not resilience, they are burning a whole night on one feature. By the same logic, once the number of skipped items passes a threshold, halt - constant skipping means the premise is wrong and continuing just burns money.
- This course measured what happens when the rollback step is removed, and the numbers sting: the end-to-end run drops from 20 completed items to 6, and the skipped set changes from F07, F18, F23 to F07, F08, F09. The last two are innocent. F07's bad code stays in the workspace filtering the whole list down to empty, F08 and F09 then fail verification one after another, and the harness skips two correctly built features as if they were impossible. The third skip hits the ceiling and the run halts. One uncleaned bad patch leaves six items for the night.
- That experiment also answers a common objection: 'reverting is too aggressive, just let it keep fixing'. Continuing to fix presumes a clean foundation. Once the foundation is dirty, every failure signal the harness receives no longer refers to the feature it thinks it does, and it starts punishing the innocent. So the criterion is not how aggressive you are, it is whether this failure contaminated already-verified work.
- Expected follow-up: should 'skipped' be a separate field in the state? No - derive it from the attempt counts: anything at the attempt ceiling that is still not in the completed set was skipped. It then persists alongside the state for free, and a crash-resume run needs no second record. If an existing authoritative record can answer the question, do not build a second record.
分析过程 · 先想清楚再作答
- 这题的分水岭在后半句。**发现打转是已经被解决过的问题**——手搓 Coding Agent 那门课有「停滞信号:清单十轮不动」,但它**只做发现**,因为人就坐在旁边,发现了你会接手。本课人不在场,**发现而不干预等于白发现**:报警报给谁看?所以难的不是检测,是检测之后那个自动决定。
- 发现这一半仍然要答完整:清单长时间不动、同一条 feature 反复重开、同一个文件被反复改。三个信号指向同一件事——**步数在涨,进度不涨**。注意它们都是 harness 侧能独立观察到的事实,不需要模型配合,这一点和上一题是同一条原则。
- 干预只有三种:再试一次、换一条、停机报警。**判据是工作区脏不脏,不是失败了几次。** 这是最容易做反的一条:空补丁失败三次,工作区仍然是干干净净的,这时回退会把旁边已经验证过的工作一起丢掉。一句话记住——**回退不是对失败的惩罚,是对地基的修复。地基没坏就不该退。**
- 重试必须有上限,而且不该大。理由很直白:**同一条输入喂给同一个模型,凭什么第二次会不一样。** 重试有意义的前提是这次的输入和上次不同(上下文里多了一条失败原因)。无限重试不是韧性,是把一整夜烧在同一条 feature 上。同理,被换掉的条数超过阈值就该停机——一直在换说明前提出了问题,继续跑只是烧钱。
- 把回退这一步去掉会怎样,本课做过变异实测,数字很刺眼:端到端那趟完成条数从 **20 条掉到 6 条**,而且被换掉的从 F07 F18 F23 变成 **F07 F08 F09**。**后两条是无辜的**——F07 那段坏代码留在工作区里把整张列表过滤成了空,F08 F09 的验收接连失败,harness 把两条本来做对的 feature 当成做不出来的换掉了,第三条到顶就停机。一条没清干净的坏补丁让整晚只剩六条。
- 这个实验还顺带回答了一个常见反驳:「回退太激进了,不如让它接着修」。接着修的前提是**地基是干净的**;地基已经脏了还接着修,harness 收到的每一个失败信号都不再指向它以为的那条 feature,于是它开始惩罚无辜者。判据因此不是激进不激进,而是这次失败有没有污染已经验证过的工作。
- 可预期的追问是「被换掉这件事要不要在状态里单独存一个字段」。不用:它从每条的尝试次数推导——到了上限却仍然不在已完成集合里的,就是被换掉的那些。这样它天然跟着状态一起落盘,崩溃续跑时不用再补一份记录。**能让现成的权威记录回答的问题,不要另建一套记录。**
Key points
- Detection is solved; intervention is the hard part - unattended, detecting without acting is worthless.
- Signals: the checklist stops moving, one item keeps reopening, one file keeps changing.
- Exactly three interventions: retry, skip, halt and alert.
- The criterion is a dirty workspace, not a failure count: an empty patch fails clean, so do not revert.
- A rollback is not punishment for failure, it repairs the foundation; intact foundations stay put.
- Retries need a ceiling: same input to the same model has no reason to behave differently.
- Measured mutation: removing the rollback drops 20 items to 6 and skips two innocent features.
答题要点
- 发现是已解决的问题,难的是干预:人不在场时发现而不干预等于白发现。
- 发现信号:清单长时间不动、同一条反复重开、同一文件反复改——步数在涨进度不涨。
- 干预只有三种:再试一次、换一条、停机报警。
- 判据是工作区脏不脏,不是失败了几次:空补丁失败三次仍然干净,就不该回退。
- 回退不是对失败的惩罚,是对地基的修复;地基没坏就不该退。
- 重试必须有上限:同一条输入喂给同一个模型,凭什么第二次会不一样。
- 变异实测:去掉回退,20 条掉到 6 条,被换掉的从 F07 F18 F23 变成 F07 F08 F09,后两条无辜。
A verifier built to catch false completions - how do you confirm it is not tautological itself?一个防谎报的验证器,你怎么确认它自己不是恒真的?
Common in ChinaCommon overseasDeep dive#assertions#mutation-testing#probe-designHow to reason about it · think before answering
- This question is about the credibility of the gate itself. A verifier that always returns pass is worse than no verifier: without one you at least know you have no evidence, whereas an always-green gate hands you an authoritative-looking false report that you then act on. So the gate must itself be verified before it goes live - who verifies the verifier is the real subject here.
- Split it into two directions, both required. First, prove it is not always green: run once with the gate switched off and nine false completions surface immediately (F02, F08, F09, F10, F11, F12, F15, F18, F29), while the run with the gate on reports zero. The phenomenon appears and disappears with the switch, which is what proves the gate does something. Second, prove it is not always red: on a complete target with all forty patches applied, all forty probes must pass; any red one means the probe itself is wrong.
- Always-green has three common entrances, all worth naming. One is passing on an empty suite - if a feature no case covers counts as passed, the gate is permanently green for every new feature. Two is verifying only the current item: F07 in this course passes its own case while breaking other people's (eight features, measured), and a single-item gate is blind to it. Three is a probe that reads source code, which reverts end-to-end into a shallow check - still verifying in form, back to 'it was written, therefore it passed' in substance.
- Always-red, and especially intermittent false red, is just as fatal and better hidden: a flaky red is worse than no gate at all, because it teaches people to ignore red lights. Once a team defaults to re-running whatever turns red, that habit swallows the genuine reds too. Hence the discipline that every probe is self-sufficient: it creates its own data, never asserts on global counts, cleans up after itself, and depends on neither execution order nor anything another probe left behind.
- This course hit a real instance worth retelling. Three probes originally used tag filtering to pick their own data out of the whole store, which looked clean - until the run loop skipped the tag-filtering feature because it failed verification. That code was simply not in the workspace, the filter parameter was ignored, the isolation evaporated, and all three probes went red because somebody else's feature was missing, halting the run at item eight. The rule tightened into one line: a filter may appear in a probe only when that feature is the filter, never because the probe needs to pick out its own data.
- The portable conclusion: assertions must be written in both directions. Asserting only that false completions are absent is a tautology trap, because an implementation that does nothing satisfies it too; it must be paired with confirming that the false completions really come back once the defense is switched off. Other courses in this repository have hit the same trap, so this is not a local quirk.
- Expected follow-up: how do you know the mutation test itself works? Look at how specific the red is. With the rollback disabled, the self-check does not merely fail - it names the unrunnable item that slipped onto the checklist. An assertion that says 'something is wrong' and one that says which thing is wrong differ by an order of magnitude in value at eight in the morning.
分析过程 · 先想清楚再作答
- 这题考的是**闸门自己的可信度**。一个永远返回通过的验证器比没有闸门更糟:没有闸门时你至少知道自己没有证据,而一个恒绿的闸门会给你一份带着权威感的假报告,然后你照着它做决定。所以闸门上线前必须先被验一次——**谁来验验证器**,这是这题真正的题面。
- 拆成两个方向,缺一不可。一是证明它**不会恒绿**:把闸门关掉跑一趟,九条谎报当场冒出来(`F02 F08 F09 F10 F11 F12 F15 F18 F29`),开上闸门那一趟谎报是 0 条——现象随着开关出现和消失,说明它真的在起作用。二是证明它**不会恒红**:在一个四十条补丁全部打好的完整靶子上跑全部四十条探针,必须全绿;任何一条红了都是探针自己写错了。
- 恒绿有三个常见入口,都值得点名。第一个是**空套件返回通过**——没有任何用例覆盖到这条 feature 时判它通过,这条闸门对新增的 feature 永远是绿的。第二个是**只验当前这一条**:本课的 F07 自己的验收用例是过的,被它弄坏的是别人(实测连累八条),只验一条的闸门对它全盲。第三个是**探针去读源码**,那等于把端到端退回浅检查,形式上还在验、实质上又回到了「写了就算过」。
- 恒红和偶发假红同样致命,而且更隐蔽:**偶发的假红比没有闸门更糟,它会教人学会忽略红灯。** 一旦团队默认「红了先重跑一次」,真正的红也会被这个习惯吞掉。所以探针的纪律是每条**自给自足**:自己造数据、不断言全局条数、跑完收拾干净,不依赖执行顺序,也不依赖别的探针留下的东西。
- 本课在这里踩过一个真实的坑,值得原样讲出去:有三条探针最初用标签筛选把自己造的数据从全库里挑出来,看起来很干净——直到运行循环把**验不过的标签筛选那条 feature 换掉了**,工作区里根本没有那段代码,筛选参数无人理会,隔离当场失效,三条探针一起因为**别人的 feature 缺席**而变红,整个运行在第八条就停机。判据由此收紧成一句:筛选出现在探针里,只能是因为**这条 feature 本身就是它**,不能是因为我需要把自己的数据挑出来。
- 结论是一句可迁移的话:**断言必须双向写。** 只断言「谎报没有出现」是恒真陷阱,因为一个什么都不做的实现同样能让它成立;必须配上「关掉防线之后谎报确实回来了」。这条在本仓库的另外几门课上也被反复踩到过,不是本课特有的。
- 可预期的追问是「怎么知道变异检验本身有效」。看它红得**具体不具体**:关掉回退之后自检不只是转红,它直接点出清单里混进了跑不通的那一条。一条只会说「有问题」的断言,和一条能指出是哪一条出了问题的断言,在早上排查时的价值差一个数量级。
Key points
- An always-green gate is worse than none: it hands you an authoritative-looking false report.
- Prove both directions: false completions return when the gate is off, and all forty probes pass on a complete target.
- Three entrances to always-green: passing an empty suite, verifying only the current item, probes reading source.
- Intermittent false reds are just as fatal - they teach people to ignore red lights.
- Every probe is self-sufficient: own data, no global counts, no dependence on order or leftovers.
- A real trap: using another feature as an isolation tool, which failed once that feature was skipped.
- Write assertions both ways, and make the red specific enough to name the offending item.
答题要点
- 恒绿的闸门比没有闸门更糟:它给你一份带权威感的假报告。
- 两个方向都要证:关掉闸门谎报回来(不恒绿),完整靶子上四十条探针全绿(不恒红)。
- 恒绿的三个入口:空套件返回通过、只验当前这一条、探针去读源码。
- 偶发假红同样致命——它会教人学会忽略红灯。
- 每条探针必须自给自足:自己造数据、不断言全局条数、不依赖执行顺序或别人留下的数据。
- 真实踩过的坑:拿别的 feature 当隔离工具,那条被换掉后三条探针一起假红、运行第八条停机。
- 断言必须双向写,而且要红得具体——能指出是哪一条出了问题。
D6 Crash Resume, the Budget Circuit Breaker and Governance Decay
An agent process that had been running for six hours gets killed - how should it come back on the next start?一个跑了六小时的 Agent 进程被杀了。你希望它下次启动时怎么恢复?
Common in ChinaCommon overseasDeep dive#crash-resume#checkpoint#idempotenceHow to reason about it · think before answering
- This question is about the process boundary. 'Serialize the context and load it back next time' misses: that is replay recovery within one session (the territory of D07 in the course where you build a coding agent by hand), and it restores what was said. Here you restore what was done. Every 'read it from disk' of the previous five days was laying track for this moment: once everything at a window boundary comes from disk, that boundary is entitled to become a process restart.
- Start by listing what new things need persisting today - the answer is almost nothing. The ledger, the onboarding trio, the checklist and green points, the skipped set (derived from attempt counts) are already on disk. Today's work is reconnecting them correctly, and all the difficulty lives in that word. All three ways of getting it wrong fail silently.
- Mistake one: never re-seed. Seeding wipes the workspace and rebuilds it, which is right on a fresh run - reproducibility is the whole point - and catastrophic on a resume: one rmSync erases a full night's work, with no error output whatsoever. So the first step of a resume is deciding whether to seed at all, not seeding and then thinking about it.
- Mistake two: a dangling current must be re-verified. The crash can land exactly between 'the patch was written' and 'verification ran', leaving a current pointing at F07 that is neither in the done set nor in any failure record. That is not dirty data, it is the single most important piece of information. Treat it as done and the checklist starts lying; treat it as untouched and a patch already in the files gets written twice. Re-verification is the only correct handling.
- Mistake three: uncommitted changes must be discarded. The rule is commit only after verification passes, so anything uncommitted in the workspace is by definition unverified. Keeping it carries an unknown state into the next round dressed as a known one. Resetting to HEAD is the only correct opening - the same rule as 'a rollback may only target a green point', restated at the process boundary.
- The measurement is worth memorizing: a fixed budget of 24 steps for the night, with the first three segments killed by SIGKILL at step 5. Starting over each time finishes 9 items; resuming from disk finishes 24. One demo discipline goes with it: use SIGKILL, not process.exit(), because the latter runs finally blocks and exit hooks - that is a graceful shutdown, and it demonstrates a path a real crash never takes.
- Expected follow-up: surely half a resume beats none? The mutation test says no. Move the seed ahead of the resume decision, so a resume re-seeds too, and the resuming run drops from 24 items to 5 - worse than the 9 of a run that cannot resume at all. The ledger says the work was done while the workspace is empty, and the two diverge on the spot: the harness carries a ledger claiming N completed items into a workspace holding nothing. Half a resume is more dangerous than none.
分析过程 · 先想清楚再作答
- 这题考的是**进程边界**。答「把上下文序列化存下来、下次原样 load 回去」是跑偏的——那是**同一会话的重放恢复**(手搓 Coding Agent 那门课 D07 的地盘),它还原的是「说过什么」;这里要还原的是「做成了什么」。前五天每一次「从磁盘读」都是在为这一刻铺路:窗口边界上的东西**只要全部从磁盘读**,它就有资格升级成一次进程重启。
- 拆的第一步是盘点今天要新增哪些需要持久化的东西,答案是**几乎没有**。账本、上手三件套、清单与绿点、被换掉的集合(从尝试次数推导)早就都在磁盘上了。今天做的只是把它们**正确地**接回来——难的全在「正确」这两个字里,而且这三件做错了都不会报错。
- 第一件容易做错的:**不许重新 seed**。seed 每次都先把工作区删干净再重建,从头跑时这是对的(反复可重建是它的价值),续跑时它是灾难——一行 rmSync 抹掉一整夜的工作,而且没有任何错误输出。所以续跑的第一步是**判断该不该 seed**,不是先 seed 再说。
- 第二件:**悬空的 current 必须重验**。崩溃可能正好发生在「补丁写进去了、验证还没跑」那一瞬,磁盘上会留下一条 current 指向 F07,它既不在已完成集合里、也没有失败记录。**这不是脏数据,它是最重要的一条信息**:当成做完了,清单就开始说谎;当成没做过,那段已经写进文件的补丁会被再写一遍。正确的处理只有重验。
- 第三件:**未提交的改动必须丢掉**。规矩是通过验证才提交,所以工作区里任何未提交的东西**按定义就是没通过验证的**。把它留着等于把一个未知状态当成已知状态带进下一轮,reset 到 HEAD 是唯一正确的开场——这就是「回退的目标只能是绿点」那条规矩在进程边界上的同一句话。
- 实测值得背下来:一夜固定 24 步预算,前三段各在第 5 步被 SIGKILL。**每次从头重来最终完成 9 条,从磁盘接着做完成 24 条。** 顺带一个演示纪律:**用 SIGKILL 而不是 process.exit()**,后者会跑 finally 与退出钩子,那是优雅退出,演示不出崩溃——你测的会是一条根本不会发生的路径。
- 可预期的追问是「做一半的续跑总比没有强吧」。变异实测说不:把 seed 挪到续跑判断之前(即续跑时也重新 seed),续跑组从 24 条掉到 **5 条**,**比完全不会续跑的 9 条还差**。因为账本说做过了、工作区却是空的,两者当场分叉——harness 拿着一份声称完成了 N 条的账本去一个什么都没有的工作区上接着做。**半套续跑比没有续跑更危险。**
Key points
- Restore what was done, not what was said - this is not replay recovery inside one session.
- The window boundary becomes a process boundary: everything was already on disk, so almost nothing new is persisted.
- Never re-seed: seeding wipes the workspace, and on a resume one rmSync erases the night without an error.
- A dangling current must be re-verified: neither in done nor in any failure record, it is the key piece of information.
- Discard uncommitted changes: commit follows verification, so uncommitted means unverified by definition.
- Measured: 24-step budget, three SIGKILLs - starting over finishes 9 items, resuming from disk finishes 24.
- Mutation: re-seeding on resume drops 24 to 5, worse than the 9 of no resume at all.
答题要点
- 要恢复的是「做成了什么」,不是「说过什么」——与同一会话的重放恢复不是一回事。
- 窗口边界升级成进程边界:前几天已经把该落盘的都落了盘,今天几乎不新增持久化的东西。
- 不许重新 seed:seed 会先删工作区,续跑时一行 rmSync 抹掉一整夜且不报错。
- 悬空的 current 必须重验:它既不在已完成里也没有失败记录,是最重要的一条信息。
- 未提交的改动必须丢掉:通过验证才提交,所以没提交的按定义就是没通过验证的。
- 实测:24 步固定预算、三次 SIGKILL,从头重来 9 条,从磁盘接着做 24 条。
- 变异实测:续跑时也重新 seed,24 条掉到 5 条,比不会续跑的 9 条还差——半套比没有更危险。
Are a budget circuit breaker and a post-hoc cost report two different things, and what does each solve?预算熔断和事后成本报表是两件事吗?分别解决什么问题?
Common in ChinaCommon overseasIntermediate#budget#circuit-breaker#real-time-controlHow to reason about it · think before answering
- This is about the line between stopping losses online and judging quality offline. 'Both control cost' scores zero. Post-hoc measurement is offline and retrospective: after a batch finishes you compute pass rates, tokens and cache hit rates to judge whether an agent is any good - that belongs to the evaluation course. A circuit breaker is online and forward-looking, and its purpose is not judgment but stopping the bleeding. They share no metric, and should not.
- One line nails the split: however precisely you compute a post-hoc metric, it cannot recover a night already burned. The reverse holds too - the breaker's three numbers say nothing about quality, only how long it ran and how much it spent. One decides whether to stop, the other whether to change anything.
- Break it open by asking what decision a number feeds and when it must be in hand. A breaker's inputs must be computable on the spot, so it prefers coarse; post-hoc metrics can wait for the whole batch, so they can be precise. Putting a metric that only exists after the run into a breaker's criterion means it will never fire.
- The three lines each guard against a different runaway. A step ceiling guards against infinite loops but is blind to every step being slow. A wall-clock ceiling guards against a stuck step or a slowing dependency, because the step count may be nowhere near its limit while the time is gone. A yield floor guards against the third and most hidden kind.
- That third one deserves elaboration: N steps spent with fewer than M green points means the agent can work but produces nothing. The first two watch for running too long, the third for running pointlessly - everything looks busy and in the morning the progress bar has not moved. Only this line watches output rather than consumption, which is exactly why the other two cannot replace it.
- The yield line needs a warm-up period: in the first few steps of a run the yield is naturally zero because nothing has finished verification yet, and tripping there means the run can never start. Promote that into a portable conclusion: any ratio-based threshold needs a minimum sample size. Saying that sentence in an interview is worth more than reciting all three lines.
- Expected follow-up: how do you set the thresholds? Not by guessing. Decide what a night is worth in money and hours, convert that into wall-clock and step ceilings, and derive the yield floor from the green-point density of a healthy run with a margin. If you cannot derive them, you have not decided what the night is worth. Also, the breaker must halt at a consistent point and record which line tripped - the three causes point at completely different investigations, and the next resume starts exactly there.
分析过程 · 先想清楚再作答
- 这题考的是**在线止损与离线评价的边界**。答「都是控成本」拿不到分。事后度量是**离线的、回顾性的**,一批运行跑完之后算通过率、算 token、算缓存命中率,用来判断一个 Agent 好不好,那是评估那门课的地盘;熔断是**在线的、前瞻性的**,唯一目的不是评价而是**止损**。两者一个指标都不共享,也不该共享。
- 一句话把分工钉死:**事后指标算得再准,也救不回已经烧掉的一夜。** 反过来也成立——熔断那三个数字拿去做质量评价毫无意义,它们只说明这次跑了多久、烧了多少,不说明结果好不好。一个管**停不停**,一个管**改不改**。
- 拆法是问「这个数字用来做什么决定、什么时候必须拿到」。熔断要的量必须**当场就能算出来**,所以它宁可粗糙;事后指标可以等整批跑完再慢慢算,所以它可以精确。把一个要跑完才有的指标塞进熔断判据,等于永远不会熔断。
- 熔断的三条线各防一种失控:**步数上限**防无限循环,但它对「每步都很慢」完全无感;**墙钟上限**防单步卡死与外部依赖变慢,因为步数可能还早得很、时间已经烧光;**产出率下限**防的是第三种,也是最隐蔽的一种。
- 第三条最值得展开:已经跑了 N 步、绿点却少于 M 个,这是「**干得动但干不出东西**」。前两条盯的是「跑太久」,第三条盯的是「跑得没意义」——一切看起来都在动,早上打开一看进度条没挪。只有它盯的是**产出**而不是消耗,这也是它不可被前两条替代的理由。
- 产出率那条必须带**热身期**:一次运行的头几步里产出率天然是 0(第一条还没验完),此时熔断等于永远跑不起来。这条可以直接升级成一句通用结论带走:**任何基于比率的阈值都需要一个最小样本量。** 面试里说出这一句,比把三条线背全更值钱。
- 可预期的追问是「阈值怎么定」。不是拍脑袋:先定这一夜愿意烧掉的钱与时间,折算成墙钟与步数上限,产出率那条按正常运行的绿点密度打个折。定不出来说明还没想清楚这一夜值多少钱。另外熔断必须**停在一个一致的点上**并写清是哪条线到线——三种原因对应的排查方向完全不同,而下一次续跑接的就是这个点。
Key points
- One decides whether to stop (online loss control), the other whether to change (offline judgment); no shared metrics.
- However precise a post-hoc metric is, it cannot recover a night already burned.
- The criterion is what decision the number feeds and when: a breaker prefers coarse but computable on the spot.
- Three lines: steps for infinite loops, wall clock for stuck steps, yield for working without producing.
- The yield line watches output rather than consumption - the only defense against a busy-looking night with no progress.
- Ratio thresholds need a warm-up: any ratio-based threshold needs a minimum sample size.
- Derive thresholds from what the night is worth; halt at a consistent point and record which line tripped.
答题要点
- 一个管停不停(在线止损),一个管改不改(离线评价),一个指标都不共享。
- 事后指标算得再准,也救不回已经烧掉的一夜。
- 判据是这个数字用来做什么决定、什么时候必须拿到:熔断宁可粗糙也要当场能算。
- 三条线:步数防无限循环、墙钟防单步卡死、产出率防干得动但干不出东西。
- 产出率那条盯的是产出不是消耗,它是「一切都在动、进度条没挪」的唯一解。
- 比率阈值必须有热身期——任何基于比率的阈值都需要一个最小样本量。
- 阈值从「这一夜值多少钱」倒推;熔断必须停在一致点上并写清是哪条线到线。
Repeated context compaction makes an agent drift away from its original safety constraints - how would you defend against that?反复压缩上下文会让 Agent 逐渐不遵守最初的安全约束。你会怎么防?
Common in ChinaCommon overseasDeep dive#governance-decay#constraint-pinning#compactionHow to reason about it · think before answering
- First establish that you know this is a phenomenon already quantified by primary research, not an imagined risk: it has a name, governance decay. Before answering 'put the constraints in the system prompt', answer this - at the moment the context is rebuilt, is that copy of the constraints still there? Without the mechanism, any defense is a slogan.
- The mechanism is architecturally inevitable, not a bug. At a window boundary you rebuild an equivalent context that keeps only three categories: what is done, what is in progress, how many attempts were made. Constraints are in none of them, so the rebuild naturally leaves them out - they appear in the first window's opening and never again.
- This is the price of 'lossy compression that loses nothing decision-relevant': when that judgment was made, only what to do next was considered, never what must not be done next. The first is state, the second is policy, and a compaction that keeps only state necessarily drops policy. Saying that out loud is worth far more than naming the phenomenon.
- The defense is the paper's Constraint Pinning: restate the constraints verbatim at the opening of every window. It is almost free - measured here, that block is 174 of 717 characters in the opening - and it buys a night in which the rules stay in force. Note what it defends against: constraints disappearing, not a model resisting them. The next question separates those two.
- It needs a measurement, not just a claim that you restate them. The metric used here is how many constraints survive in each window's opening: without pinning it is 3, 0, 0, 0; with pinning it is 3, 3, 3, 3. With that number, 'is the restatement actually wired in' becomes an automatically assertable fact instead of an unverified good intention.
- Which constraints to pin also matters: prefer the rules whose violation does not fail loudly. A rule that fails immediately gets corrected by reality anyway - the next step simply will not work - while the quiet ones are exactly what compaction erases and what is hardest to diagnose afterwards. All three used here (one feature at a time, append only without rewriting existing registration code, never hand-edit the checklist) are of that kind.
- Close with the capability boundary, volunteered rather than extracted: a single-purpose offline model neither sees constraints nor violates them, so the lab here can measure whether the constraints are still present, not whether the model still obeys. The latter is only cited, and always with its condition: the ConstraintRot benchmark, 1323 episodes across 7 model families, violation rates rising from 0 percent with the policy fully visible to 30 percent after compaction, reaching 59 percent in some families, with Constraint Pinning pushing it back to 0 percent. Quoting 30 percent without 'after compaction' is simply wrong.
分析过程 · 先想清楚再作答
- 这题第一步是确认你知道这是一个**已经被一手论文量化过的现象**而不是想象出来的风险,它有名字:**governance decay**。答「把约束写进系统提示词就完事了」之前要先回答一个问题——重建上下文的那一刻,那份约束到底还在不在。机制没说清,任何防法都只是一句口号。
- 机制在架构里是**必然的**,不是 bug。窗口边界处重建的是**等效上下文**,只保留「做完了什么、正在做什么、试了几次」三类。约束不在这三类里,所以重建天然不会带上它——它只出现在第一个窗口的开场,之后再也没有出现过。
- 它是「有损压缩,但对决策无损」那句话的代价:当时判断「对决策无损」时,只考虑了**下一步做什么**,没考虑**下一步不许做什么**。前者是状态,后者是策略,而一个只保留状态的压缩必然丢掉策略。把这层说出来,比背出现象名字有用得多。
- 防法就是论文里的 **Constraint Pinning**:**每个窗口开场都把约束原样复述一遍。** 它便宜得几乎不用算——本课实测那段只占开场 **174/717 字符**,换的是整晚的规矩不失效。注意它防的是「约束消失」,不是「模型抗拒」,这两件事下一题会分开讲。
- 必须**配一个度量**,不能只写一句「我们复述了」。本课的口径是数各窗口开场里还剩几条约束:不复述是 **3 → 0 → 0 → 0**,每窗口复述是 **3 → 3 → 3 → 3**。有了这个数字,「复述有没有真的接上」就变成一个能自动断言的事实,而不是一段谁也没验过的好意。
- 选哪几条进复述也有讲究:优先选**违反了不会立刻报错**的那种规矩。会立刻报错的规矩不靠复述也会被现实纠正(下一步就跑不通了),不报错的那些才是压缩里悄悄失效、失效之后最难查的一类——本课那三条(一次只做一条、只追加不改写已有注册代码、不手改清单)全是这一类。
- 能力边界必须主动说:离线的单用途模型**看不见约束也不会违规**,所以自己的 lab 能测的是「**约束还在不在**」,测不到「**模型还听不听**」。后者只引用一手证据,而且引用时必须带条件:ConstraintRot 基准 **1323 个 episode、7 个模型家族**,违规率从策略完整可见时的 **0%** 升到**压缩后**的 **30%**,某些家族达 **59%**,Constraint Pinning 压回 **0%**。脱离「压缩后」这个条件单说 30% 是错的。
Key points
- The phenomenon has a name - governance decay, quantified by primary research; explain the mechanism before the defense.
- The mechanism is architectural: the rebuild keeps done, in progress and attempt counts; constraints are in none of them.
- 'Nothing decision-relevant lost' only considered what to do next, never what must not be done - policy is dropped, not state.
- The defense is Constraint Pinning: restate verbatim at each window opening, measured at 174 of 717 characters.
- Pair it with a metric: 3, 0, 0, 0 without pinning versus 3, 3, 3, 3 with it.
- Pin the rules that fail quietly; the ones that fail loudly get corrected by reality anyway.
- Capability boundary: you can measure presence, not obedience; always cite the paper's numbers with the 'after compaction' condition.
答题要点
- 现象有名字:governance decay,已被一手论文量化,先说机制再说防法。
- 机制是架构必然:重建只保留做完了什么、正在做什么、试了几次,约束不在这三类里。
- 「对决策无损」当初只考虑了下一步做什么,没考虑下一步不许做什么——丢的是策略不是状态。
- 防法是 Constraint Pinning:每个窗口开场原样复述,实测只占开场 174/717 字符。
- 必须配度量:不复述是 3 → 0 → 0 → 0,每窗口复述是 3 → 3 → 3 → 3。
- 优先复述那些违反了不会立刻报错的规矩,会立刻报错的靠现实就能纠正。
- 能力边界:自己能测「约束还在不在」,测不到「模型还听不听」;引用论文数字必须带「压缩后」这个条件。
You already have probes that check factual fidelity after compaction - why do they miss constraints being erased?你已经有一套压缩后的事实校验探针了,为什么它发现不了约束被擦掉?
Common in ChinaCommon overseasDeep dive#probe-blind-spot#constraint-fidelity#capability-boundaryHow to reason about it · think before answering
- This question is about two kinds of fidelity, and it exists to filter for people who know what their checks actually cover. The probe method (D12 of the course where you build a coding agent by hand) takes facts mentioned early, asks about them again after compaction, and treats a correct answer as proof that compaction was safe. That is factual fidelity - whether it remembers. Constraint fidelity is a different thing: whether it still complies.
- One line makes it plain: an agent that can still recite the order number perfectly may well have stopped obeying 'never delete a file without confirmation'. Remembering is not the same as heeding. All of the probe method's evidence lands on the first half, and it is structurally blind to the second - not blind for lack of coverage, not fixable by adding more probes.
- Why structurally: a probe asks whether a piece of information is still in the context, while constraints fail along two paths - the text is still there but its weight has dropped, or, as here, the rebuild never carried it at all. The first is entirely invisible to probes (the model answers beautifully and violates anyway); the second is in principle catchable, but nobody thinks to use a rule as a factual probe.
- There is a further layer people miss: probes sample. Facts are homogeneous, so a few representative ones suffice. Constraints are not homogeneous - each is irreplaceable, and finding C1 intact says nothing about C2. Using a sampling method on an object that cannot be sampled fails as a method, regardless of how well the individual probes are written.
- The fix is cheap and points the right way: treat constraints as first-class and count them directly, checking id by id how many survive in each window's opening. What matters is that it does not ask the model - it counts them in the context text itself. This is the same principle as the self-verification day: a check that does not depend on the cooperation of the party under review is the only trustworthy check.
- Volunteer the ceiling of that fix too: it proves the constraints are still in the context, not that the model still obeys them. Obedience needs a behavioural metric - a violation rate - and that needs a model that genuinely violates. The offline model used here is single-purpose: it neither sees constraints nor breaks them, so that layer only cites the primary numbers (0 percent rising to 30 percent after compaction, 59 percent in some families, Constraint Pinning back to 0 percent) rather than manufacturing its own.
- Expected follow-up: why not turn the constraints into probes as well? You can, but the criterion has to change. A factual probe passes on a correct answer; a constraint probe must require a verbatim restatement plus a refusal in a scenario engineered to invite the violation. Asking 'do you remember the rules?' still yields a memory test - and memory is precisely the thing already shown to be insufficient.
分析过程 · 先想清楚再作答
- 这题考的是**两种保真的区别**,而且它是一道专门用来筛「知道自己验了什么」的题。探针法(手搓 Coding Agent 那门课 D12 的做法)是拿早期提过的事实当探针、压完再问一遍,答得上来就说明压缩安全。它验的是**事实保真**——记不记得。约束保真是另一回事:**还听不听得进。**
- 一句话就能点破:**一个还能准确复述订单号的 Agent,完全可能已经不再遵守「未经确认不许删文件」。记得住不等于听得进。** 探针法的全部证据都落在前半句上,它对后半句是**结构性**的盲,不是覆盖率不够、多加几条探针就能补上的那种盲。
- 为什么是结构性的:探针问的是「这条信息还在不在上下文里」,而约束失效有两条路径——信息还在但权重掉了,以及像本课这样在重建时根本就没被带上。前一条探针完全抓不到(它会答得很好然后照样违规);后一条探针原则上抓得到,但没有人会想到拿一条规矩去当事实探针问。
- 还有一层常被忽略的:**探针是抽样的**。事实之间是同质的,抽几条有代表性的就够了;**约束不是同质的**——每一条都不可替代,抽到 C1 还在推不出 C2 还在。用一个抽样的方法去验一个不可抽样的对象,方法本身就不成立,与探针写得好不好无关。
- 补法很便宜,而且方向是对的:把约束当成**一等公民**直接数——按 id 逐条核对各窗口开场里还剩几条。关键在于它**不问模型**,是在上下文文本里自己数。这与自验证那天是同一条原则:**不依赖被审查方配合的检查,才是可信的检查。**
- 但要主动说清这个补法的**上限**:它证明的是「约束还在上下文里」,不是「模型还在遵守」。后者要用行为指标(违规率)去量,而那需要一个真会违规的模型;本课的离线模型是单用途的,看不见约束也不会违规,所以这一层只引用一手论文的数字(**压缩后** 0% 升到 30%、某些家族 59%、Constraint Pinning 压回 0%),不自己造数据。
- 可预期的追问是「那把约束也做成探针不就行了」。可以,但**判据必须换**:事实探针的判据是「答得上来」,约束探针的判据得是「**一字不差地复述**」外加「**在一个会诱导违规的场景里仍然拒绝**」。只问一句「你还记得有哪些规矩吗」,得到的仍然是一个记忆测试——而记忆正是那个已经被证明不够的东西。
Key points
- Probes verify factual fidelity (does it remember); constraint fidelity is another thing (does it still comply).
- An agent that still recites the order number perfectly may have stopped obeying 'no deletion without confirmation'.
- The blind spot is structural: text can survive with lost weight, or never be carried by the rebuild at all.
- Facts can be sampled, constraints cannot: each is irreplaceable, and C1 surviving says nothing about C2.
- The fix is counting constraints by id in the context and not asking the model - checks that need no cooperation are the trustworthy ones.
- Its ceiling: it proves presence, not obedience; obedience is only cited from primary research.
- Constraint probes need a different criterion: verbatim restatement plus refusal under an inviting scenario, not 'do you remember'.
答题要点
- 探针法验的是事实保真(记不记得),约束保真是另一回事(还听不听得进)。
- 一个还能准确复述订单号的 Agent,完全可能已经不再遵守「未经确认不许删文件」。
- 盲区是结构性的:约束可以信息还在而权重掉了,也可以在重建时根本没被带上。
- 事实可以抽样,约束不可以:每条约束不可替代,抽到 C1 还在推不出 C2 还在。
- 补法是按 id 逐条数上下文里还剩几条约束,而且不问模型——不依赖被审查方配合的检查才可信。
- 补法的上限:它证明约束还在,不证明模型还在遵守;后者只引用一手论文数字。
- 把约束做成探针要换判据:一字不差复述 + 在诱导场景里仍然拒绝,而不是「你还记得吗」。
D7 Putting It Together: Let It Run All Night, Then Check the Work in the Morning
Six modules developed independently get merged into one system - what worries you most, and how would you find it?六个独立开发的模块合并进一个系统,你最担心出现什么问题?怎么发现它?
Common in ChinaCommon overseasDeep dive#silent-failure#module-merge#switch-experimentHow to reason about it · think before answering
- Start by naming the right worry: a crash is not the dangerous case. A crash gives you an error, a stack and a timestamp - it is the cheapest kind of failure because you cannot miss it. The expensive kind is silent failure: one layer has stopped doing anything, while the whole thing still runs, the report still comes out, and nothing reports an error. 'I would write integration tests' is not wrong but not enough - what breaks on a merge is precisely the path where every module passes its own tests and nobody covers their interaction.
- Why merges are especially prone to this deserves its own sentence: while developed separately, each module's tests rest on the assumptions of its own day. After the merge, a newly added gate can block an older flow while that older flow's report stays green - what a gate blocks does not become an error, it becomes a smaller number, and nobody knows what that number should have been. This class of problem first exists at the moment of the merge, so no amount of separate testing finds it.
- Only one discovery method really works: switch each module off one at a time and see whether the difference comes back. Looking at the total score is useless - the total is the combined effect of six layers, any one of which can fail while another masks it, and the masked result often looks better. A switch experiment turns 'is this layer doing anything' into an observable delta instead of an assumption.
- The checkup table in this course's D7 was built exactly that way, and five measured rows are worth memorizing: switch off D3's initialization stage and environment probing rises from 1 to 4 times; switch off D4's commit-per-feature and peak uncommitted work rises from 0 to 4 items; switch off D5's end-to-end gate and false completions rise from 0 to 7; switch off D6's constraint restatement and the windows that keep the rules drop from 4/4 to 1/4; tighten the budget and the outcome becomes a breaker trip after 5 steps against a baseline of 16. Every row is a delta that comes back.
- A layer that makes no difference when switched off is a finding, not noise. In this checkup, D2's state layer is exactly that case: switch it off and the completed count is still 16, identical to the baseline. Digging in shows it is not broken but superseded by D4 - once task selection moved into the harness (pick the next item by reading the checklist off disk), 'redo the work because the context holds no evidence of progress' went from not happening to being impossible. That has been written into the self-test as a positive assertion, so nobody later 'fixes' it without realizing what they are undoing.
- So the habit to take away is one sentence: every layer you add needs a switch that turns it off. A layer without a switch leaves you permanently unable to tell whether it stopped working long ago. That is also why this course runs a mutation check every single day - switch off the layer that day introduced and confirm the symptom returns. D7 merely collects seven days of mutation checks into one table, so every layer is worth a delta you can reproduce on the spot.
- Expected follow-up: does that not mean running everything seven times? Yes, and one checkup costs far less than one wasted night, and it automates naturally. Also the switch must be a real switch - a configuration flag, not a few lines commented out by hand. A checkup that depends on commented-out code is itself a thing somebody has to remember, and remembering is what this whole course argues against.
分析过程 · 先想清楚再作答
- 这题第一步是把担心的对象说对:**最危险的不是崩。** 崩了有报错、有栈、有时间点,是最便宜的一类失败,你一定会知道。真正贵的是**静默失效**——某一层其实已经不起作用了,而整体照样跑、报告照样出、一个错都不报。答「我会写集成测试」不算错但不够:合并之后出问题的恰恰是每个模块单独测都绿、而互相影响的那条路径谁也没覆盖到。
- 为什么合并特别容易出这个,值得单独说一句:各模块单独开发时,它们的测试都建立在自己那一天的前提上。合并之后**新加的闸门可能让旧流程过不去,而旧流程的报告仍然是绿的**——闸门拦下来的东西不会变成错误,只会变成一个更小的数字,而没有人知道那个数字本来该是多少。这类问题在合并那一刻才第一次存在,所以单独测多少遍都测不出来。
- 发现方法只有一条真正管用:**逐个模块开关一遍,看差别回不回来。** 只看总分没用——总分是六层共同作用的结果,任何一层失效都可能被别的层掩盖,而且掩盖之后总分往往更好看。开关实验把「这一层有没有在起作用」变成一个可观测的差值,而不是一句「应该在吧」。
- 本课 D7 的体检表就是这么做出来的,五行实测可以直接背:关掉 D3 的初始化阶段,环境探测从 1 次涨到 4 次;关掉 D4 的一条一提交,未提交工作峰值从 0 条涨到 4 条;关掉 D5 的端到端闸门,谎报从 0 条涨到 7 条;关掉 D6 的约束复述,保住规矩的窗口从 4/4 掉到 1/4;把预算收紧,结局变成熔断、5 步就停(基准 16 步)。每一行都是一个能回来的差值。
- **一个关掉之后没差别的层,要当成发现而不是当成噪声。** 本课体检里 D2 的状态层就是这种情况:关掉它完成条数仍然是 16 条,与基准一模一样。查下去发现它不是坏了,是**被 D4 取代了**——任务选取上收到 harness 之后(读磁盘清单挑下一条),「上下文里没有进度证据就重做」这件事从「不再发生」变成了「不可能发生」。这条已经写成一条**正向断言**进了自检,防止将来有人把它「修好」而不自知。
- 所以带走的习惯是一句话:**每加一层,就要有一个关掉它的开关。没有开关的层,你永远不知道它是不是早就不干活了。** 这也是这门课每天都做变异检验的原因——关掉当天新增的那层,确认现象重现;D7 只是把七天的变异检验汇总成同一张表,让每一层的价值都变成一个可以当场复现的差值。
- 可预期的追问是「那不是每次都要跑七遍吗」。是的,但一次体检的成本远低于一夜白跑的成本,而且它天然可以自动化。另外开关必须是**真开关**(配置项,而不是临时注释掉几行代码)——靠注释做的体检本身就是一件要靠人记住的事,而这门课从头到尾都在反对靠人记住。
Key points
- The worry is not a crash but silent failure: a layer stops working while everything runs, reports and raises nothing.
- Merges invite it: a new gate blocks an older flow while that flow's report stays green.
- Find it by switching each module off and checking whether the difference returns - far better than reading the total.
- Measured checkup: probing 1 to 4, uncommitted peak 0 to 4, false completions 0 to 7, constraints 4/4 to 1/4, tightened budget trips the breaker.
- A layer with no difference is a finding: D2's state layer was superseded by D4 - degradation went from not happening to impossible.
- That was written into the self-test as a positive assertion so nobody silently 'fixes' it later.
- Habit: every layer needs an off switch, and it must be a config flag rather than commented-out lines.
答题要点
- 最担心的不是崩,是静默失效:某一层不起作用了,而整体照样跑、报告照样出、一个错都不报。
- 合并特别容易出这个:新加的闸门让旧流程过不去,而旧流程的报告仍然是绿的。
- 发现方法是逐个模块开关一遍看差别回不回来,比只看总分有用得多。
- 实测体检表:初始化 1→4 次探测、一条一提交 0→4 条未提交峰值、闸门 0→7 条谎报、约束复述 4/4→1/4、预算收紧则熔断。
- 关掉之后没差别的层要当成发现:D2 状态层被 D4 取代了,退化从不再发生变成不可能发生。
- 那条已写成正向断言进自检,防止将来有人把它「修好」而不自知。
- 习惯:每加一层就要有一个关掉它的开关,而且必须是配置项不是注释掉几行。
Design the morning report for an unattended run - what goes in, and what gets cut?设计一份无人值守运行的早间报告,你会放哪些内容、砍掉哪些内容?
Common in ChinaCommon overseasIntermediate#report-design#human-readable#single-sourceHow to reason about it · think before answering
- State the criterion before the contents: one glance should show what got done and where it is stuck. Once the criterion is fixed, the contents follow. 'Format the log nicely' misses - a log's reader is someone already debugging, while this report's reader is a person who just woke up, is not yet in context, and only wants to know whether to step in. The same material written for different readers has a completely different structure.
- This course puts it side by side with the window-rebuild summary, and all four properties differ: reader (the model in the next window versus the person doing the morning check), purpose (make the right next move versus decide in thirty seconds whether to intervene), style (conclusion first, one thing per line versus conclusion then evidence, bad news first), length (as short as possible, since every window pays for it, versus long enough, since it is read once). Yet both draw their numbers from the same source - the same authoritative state and the same checklist - so they cannot contradict each other.
- Discipline one: bad news first. Writing a chronological play-by-play is the most common error, and the easiest to fall into because a log is already in that order. 'Step 1 did F01' contributes nothing to the decision of whether to intervene. Putting the whole 'where it is stuck' section ahead of 'what got done' is the only real structural decision in this report.
- Discipline two: every piece of bad news must carry the next step. 'F07 failed' is useless; 'F07 was attempted twice, rolled back to the last green point, and skipped' is useful. The test is handy: after reading this line, does the person know what to do next? If not, the line is unfinished - it has merely moved a question from the machine to the human.
- Discipline three: give an executable way to reproduce. The final section must answer how to resume the run and how to inspect the scene, otherwise the reader has to go hunting for the state files and the repository path. This one is almost free to add, and leaving it out costs five minutes no matter how good the rest of the report is.
- Discipline four is the one most often missed: no number may exist only in the report. Every figure must be traceable to the state or the checklist, or it becomes a third record that will eventually diverge. The last line of this course's report is a recheck: the checklist claims 40 items, and re-verifying each one finds 0 that fail. The report is not a new record, only a view over the authoritative state and the checklist, so it cannot fight with them.
- Say what gets cut too: the step-by-step play-by-play, everything the model said, and the intermediate reasoning - all of it. Expected follow-up: then how do you debug? Debugging happens in the logs and at the scene, and the report's job is only to lead the person there, which is exactly what discipline three buys. Stuffing debugging material into the report makes the just-woken reader read logs on the machine's behalf, and the three-minute standard dies on the spot.
分析过程 · 先想清楚再作答
- 先把判据说出来,再谈内容:**一眼能看出干了什么、卡在哪里。** 判据定了,内容自己就排出来了。答「把日志格式化一下输出」是跑偏的——日志的读者是正在排查问题的人,报告的读者是**一个刚睡醒、还没进入状态、只想知道要不要介入的人**。同一份材料写给不同的人,结构完全不一样。
- 本课把它与窗口重建摘要摆在一起做对照,四项全都不同:读者(下一个窗口里的**模型** 对比 早上来验收的**人**)、目的(让它做对下一步 对比 让人在三十秒内决定要不要介入)、写法(结论前置、一行一件事 对比 先结论再证据、坏消息放前面)、长度(越短越好,每个窗口都要付一次钱 对比 够用就行,只读一次)。但两者**数字同源**——都来自同一份权威状态与同一份清单,所以它们不可能互相矛盾。
- 第一条纪律:**坏消息放前面。** 按时间顺序写流水账是最常见的错误,也是最容易犯的,因为日志天然就是那个顺序。「第 1 步做了 F01」对「我要不要介入」这个决定毫无帮助。把「卡在哪里」整节排在「做完了什么」前面,是这份报告唯一真正的结构决定。
- 第二条:**每条坏消息都要带得起下一步。**「F07 失败」没用;「F07 试了 2 次、已回退到上一个绿点、已跳过」才有用。判据很好用:读完这一条,人知不知道下一步该做什么。不知道,这条就还没写完——它只是把一个疑问从机器转移给了人。
- 第三条:**给可执行的复现方式。** 最后一节必须回答「怎么接着跑」和「怎么看现场」,否则人读完报告还得自己去翻目录找状态文件和仓库路径。这一条便宜到几乎不用想,但漏掉它会让前面写得再好的报告都多花五分钟。
- 第四条也是最容易被忽略的:**不许出现报告独有的数字。** 报告里每个数字都要能在状态或清单里找到出处,否则它就是**第三份会分叉的记录**。本课那份报告最后一行是一句复核:清单声称 40 条,逐条重验之后跑不通的有 0 条——报告不是新记录,它只是权威状态与清单的一个视图,所以它不可能和它们打架。
- 砍掉什么同样要说清:逐步流水账、模型说过的话、中间推理,全砍。可预期的追问是「那出了问题怎么排查」——排查看日志和现场,报告只负责把人**导到**现场,这正是第三条纪律的用处。把排查材料塞进报告,等于让那个刚睡醒的人替机器读日志,三分钟标准立刻作废。
Key points
- One criterion: a glance shows what got done and where it is stuck; the reader just woke up and is not in context.
- It differs from the rebuild summary on reader, purpose, style and length, yet shares its numbers, so the two cannot contradict.
- Bad news first: a chronological play-by-play is the most common mistake.
- Each piece of bad news must carry a next step: 'F07 failed' is useless, 'F07 tried twice and skipped' is useful.
- Give an executable way to resume the run and inspect the scene.
- No number may exist only in the report, or it becomes a third diverging record; the real report ends with an item-by-item recheck.
- Cut the play-by-play, the model's utterances and the intermediate reasoning; debugging lives in the logs, the report only points there.
答题要点
- 判据一句话:一眼能看出干了什么、卡在哪里;读者是一个刚睡醒、还没进入状态的人。
- 与窗口重建摘要四项全不同(读者/目的/写法/长度),但数字同源,所以不可能互相矛盾。
- 坏消息放前面:按时间顺序写流水账是最常见的错误。
- 每条坏消息要带得起下一步:「F07 失败」没用,「F07 试了 2 次、已跳过」才有用。
- 给可执行的复现方式:怎么接着跑、怎么看现场。
- 不许出现报告独有的数字,否则它就是第三份会分叉的记录;实测报告最后一行是逐条复核。
- 砍掉逐步流水账、模型说过的话、中间推理;排查看日志,报告只负责把人导到现场。
An agent ran overnight and finished 18 of 30 items - is that a good result or a bad one?Agent 跑了一夜完成了三十条里的十八条。这个结果算好还是不好?
Common in ChinaCommon overseasDeep dive#acceptance-criteria#false-progress#stop-reasonHow to reason about it · think before answering
- The right answer is to ask three questions before concluding, not to hand over a verdict. Any reply that opens with 'not bad' or 'pretty poor' is already wrong, because the ratio 18 of 30 carries no information you can act on. Judging by completion count alone is exactly what this course argues against from D1 through D7 - a better-looking number often corresponds to a worse reality.
- Question one: are the remaining twelve items untouched, or attempted and failed? On a dashboard the two look identical, both simply incomplete, but they mean opposite things. Untouched means the budget ran out, and another night or a bigger budget continues the work. Attempted and failed means something is stuck, and any additional budget burns in the same place. What separates them is the attempt counts and the skipped set, not the completion count.
- Question two: are those eighteen genuinely usable, or merely marked done on the checklist? D5 of this course answers that with a measurement: same code, same model, same script, with the only difference being what counts as verification. The shallow-check run claimed 33 items, re-verification found only 24 actually working, and 9 were false. The end-to-end gate run claimed 20, had 20 working, and 0 false. The run that claimed more produced less.
- Those numbers carry a further lesson: of the 9 false completions, only 2 came from genuinely bad patches - the other 7 were correct work dragged down by an earlier item. False completions are therefore not isolated events; one unverified feature contaminates a stretch of the items after it, which means 'how many of the eighteen are fake' cannot be answered by spot-checking one or two.
- Question three: why did it stop? The four outcomes call for completely different handling. Finished (the checklist is complete - go and accept the work), budget exhausted (it stopped at the line, so the next step is more budget or less scope), halted (too many items were skipped, so the harness is saying a premise is wrong and continuing only burns money), and crashed (look at where it crashed before deciding to resume). Only the first two are safe to resume blindly; the other two need a human look first. That is why the first line of the report must answer why it stopped.
- Only after all three questions does a conclusion mean anything, and it lands at one of two extremes. If the twelve were untouched, the eighteen all survive re-verification, and it stopped at the budget line, this is a good result and the next step is simply more budget. If the twelve were attempted and failed, several of the eighteen were never really verified, and it halted on too many skips, then a premise is broken and more budget only burns more money. One 18 of 30, two opposite next steps.
- Expected follow-up: which numbers should you actually look at? Three. The count that still works after item-by-item re-verification, which is the only trustworthy progress figure; the skipped set, which shows what class of problem it is stuck on; and the stop reason, which decides whether the next step belongs to a person or a machine. All three belong in a decent overnight report, and all three trace back to the authoritative state and the checklist.
分析过程 · 先想清楚再作答
- 这题的正解是**先问三个问题再下结论**,不是直接给一个答案。任何张口就说「不错」或「太差」的回答都已经错了,因为 18/30 这个比值本身不携带任何可用于决策的信息。只看完成数,正是这门课从 D1 到 D7 一直在反对的事——**更好看的数字往往对应更差的现实。**
- 问题一:**剩下那十二条是没轮到,还是试过没成?** 这两种在报表上长得完全一样(都是「未完成」),但含义天差地别。没轮到只是预算不够,加一夜或加预算就能继续;试过没成说明有东西卡住了,再给多少预算都会烧在同一个地方。能区分它们的是尝试次数与被换掉的集合,不是完成数。
- 问题二:**那十八条是真的能用,还是清单上写着完成?** 本课 D5 有一组实测正好回答这个:同一份代码、同一个模型、同一份剧本,唯一的差是拿什么当验证——浅检查那趟清单声称 33 条、逐条重验只有 24 条真的能用、谎报 9 条;端到端闸门那趟声称 20 条、能用 20 条、谎报 0 条。**声称得多的那趟,真实产出反而少。**
- 这组数字还有一层值得说:那 9 条谎报里只有 2 条是真正的坏补丁,另外 7 条本来都是做对的,是被前面一条连累的。所以谎报不是孤立事件——**一条没真验的 feature 会污染它后面的一串**,这意味着「18 条里有几条是假的」这个问题不能靠抽查一两条回答。
- 问题三:**它为什么停下来?** 四种结局的处理完全不同:跑完了(清单全部完成,直接验收)、预算熔断(到线就停,下一步是加预算或砍范围)、停机(被换掉的条目太多,harness 在说前提出了问题,继续跑只是烧钱)、崩了(先看崩在哪,再决定要不要续跑)。只有跑完与熔断适合无脑续跑,另外两种都要人先看一眼。所以报告的第一行必须回答「为什么停」。
- 三个问题问完,结论才有意义,而且会落到两端之一:如果十二条是没轮到、十八条逐条重验都能用、停因为预算到线——那是一个**很好的结果**,下一步只是加预算;如果十二条是试过没成、十八条里有若干没真验、停机是因为换掉太多——那是**前提出了问题**,加预算只会烧更多的钱。同一个 18/30,两种完全相反的下一步。
- 可预期的追问是「那到底该看哪几个数」。三个:**逐条复核之后仍然能用的条数**(唯一可信的进度)、**被换掉的集合**(卡在哪类问题上)、**停机原因**(决定下一步是人做还是机器做)。这三个数都在一份合格的夜跑报告里,而且都能在权威状态与清单里找到出处。
Key points
- The right answer is three questions before a verdict; the ratio itself carries nothing you can act on.
- One: are the remaining twelve untouched or attempted and failed - budget shortfall versus something stuck.
- Two: are the eighteen usable or merely marked done - D5 measured 33 claimed, 24 usable, 9 false.
- False completions cluster: only 2 of the 9 were bad patches, 7 were dragged down by an earlier item, so spot checks cannot answer it.
- Three: why it stopped - finished, budget, halt or crash; only the first two are safe to resume blindly.
- Then conclude: usable items plus a budget stop is a good night; stuck items plus false completions plus a halt means a broken premise.
- The three numbers to read: items surviving re-verification, the skipped set, and the stop reason.
答题要点
- 正解是先问三个问题再下结论,18/30 这个比值本身不携带可用于决策的信息。
- 问题一:剩下十二条是没轮到还是试过没成——前者只是预算不够,后者是有东西卡住了。
- 问题二:十八条是真的能用还是清单上写着完成——D5 实测浅检查声称 33 条、实际只有 24 条能用、谎报 9 条。
- 谎报不是孤立的:那 9 条里只有 2 条是坏补丁,另外 7 条是被前面一条连累的,所以抽查一两条回答不了。
- 问题三:为什么停——跑完 / 熔断 / 停机 / 崩了,四种处理完全不同,只有前两种适合无脑续跑。
- 问完才有结论:都能用且停在预算线上就是好结果;有卡点又有谎报还因换掉太多而停机,就是前提出了问题。
- 该看的三个数:逐条复核后仍能用的条数、被换掉的集合、停机原因。
Looking back at the whole harness, if you could keep only one module, which one and why?回顾整套 harness,如果只能保留一个模块,你留哪个?为什么?
Common in ChinaCommon overseasDeep dive#tradeoff-reasoning#trust-vs-throughput#course-recapHow to reason about it · think before answering
- There is no standard answer here, but there are better and worse ways to reason, and the reasoning is what the interviewer wants. Naming a module and justifying it with 'it matters most' scores nothing whichever one you pick, because that sentence is true of all six. What the question really asks is: on what basis do you rank these layers?
- Give the criterion before the answer, never the other way round. The criterion is one sentence: which module, when absent, makes the other modules' output untrustworthy rather than merely smaller? Smaller can be fixed with budget - run another night, allow more steps, and the number climbs back. Untrustworthy cannot be fixed, and it is worse than having no number at all, because plausible fake progress stops people from doubting until much later, when the whole line has to be redone.
- By that criterion, the first answer is to keep the end-to-end gate: without it, everything the other modules produce is untrustworthy progress. The state layer faithfully records a batch of fake completions, the green points on the checklist are fake green points, and the report computes a better-looking number from that data - which is exactly the shape of the D5 measurement (33 claimed, 24 usable, 9 false, with false completions dragging down items that were originally correct). The gate is the one module that decides whether the numbers are real.
- The second answer, to keep the state layer, works just as well: without it the run does not survive a single night. The measured contrast is 3 items against 9, and 6 wasted steps against 0 - swap the window and it guesses from scratch, so running longer only spins in place. Trustworthy but unfinished versus finished but untrustworthy: which is worse depends on the setting, which is precisely why this question can be answered either way.
- So the right move is to state the conditions rather than hedge. If the night's output goes straight into use, keep the gate, because wrong output costs far more than no output. If this is an exploratory long run whose output a person will review item by item anyway, keep the state layer, because getting it to finish comes first and correctness is backstopped downstream. Saying under which condition you would switch answers shows more thinking than the answer itself.
- It is worth citing the seven-day thread, which explains why these six modules are not interchangeable: what gets moved out of the window is progress (D2), environment knowledge (D3), the basis for choosing (D4), the criterion for done (D5), the rules (D6) and the conclusion (D7) - six different things, each given a carrier somebody else can open. The through line stays the same: context is volatile, state is authoritative.
- Expected follow-up: what is still missing from this stack? Volunteer the boundaries: sandboxing and permissions belong to the security course, evaluation metrics to the evaluation course, and context compaction techniques themselves to the context engineering course - name the owner, do not expand. Being able to state where your own stack ends is far more convincing than reciting one more module name.
分析过程 · 先想清楚再作答
- 这题没有标准答案,但**推理方式有好坏**,而且面试官要的就是推理方式。上来就报一个模块名、理由是「它最重要」,无论选哪个都拿不到分——因为那句话对任何一个模块都成立。这题真正在问的是:你凭什么给这六层排序。
- 先给判据,再给答案,顺序不能反。判据是一句话:**哪一个模块缺席时,其余模块的产出会变成不可信,而不是变少?** 变少还能靠加预算补——多跑一夜、多给几步,数字会涨回来;**不可信没法补**,而且比没有数字更糟,因为一个看起来合理的假进度会让人停止怀疑,直到很久以后才发现整条线都要重来。
- 按这个判据,第一个答案是**留端到端闸门**:没有它,别的模块产出的都是不可信的进度。状态层会忠实地记下一批假的完成,清单上的绿点是假的绿点,报告会基于这些数据算出一个更好看的数字——D5 那组实测正是这个形状(声称 33 条、实际能用 24 条、谎报 9 条,而且谎报会连累后面本来做对的条目)。闸门是那唯一一个决定「数字是不是真的」的模块。
- 第二个答案是**留状态层**,同样说得通:没有它连一夜都跑不下来。实测对照是完成条数 3 条对 9 条、白干步数 6 步对 0 步——窗口一换就从头乱猜,跑得再久也只是在原地打转。可信但跑不完,与跑得完但不可信,哪个更糟取决于场景,这正是这题可以两边答的原因。
- 所以正确的做法是**给出场景条件而不是骑墙**:如果这一夜的产出会被人直接拿去用,选闸门(错误的产出比没有产出贵得多);如果这是一个探索性的长跑、产出无论如何还会被人逐条过一遍,选状态层(先让它能跑完,正确性由后面的人兜底)。**说清楚在什么条件下换答案,比答案本身更能说明你想清楚了。**
- 回答里值得顺带引一条七天的线索,它能说明这六个模块为什么不是可互相替代的:搬出窗口的分别是**进度**(D2)、**环境知识**(D3)、**判断依据**(D4)、**完成的判据**(D5)、**规矩**(D6)、**结论**(D7)——六样不同的东西,各自有一个能被别人打开的载体。主线一句话:**上下文是易失的,状态才是权威的。**
- 可预期的追问是「那这套东西还缺什么」。要能主动说清边界:沙箱与权限归安全那门课,评估指标归评估那门课,上下文压缩技术本身归上下文工程那门课——**只说归属,不展开**。能说出自己这套东西的边界在哪,比多背一个模块名有说服力得多。
Key points
- No standard answer, but the reasoning is judged: give the criterion first, then the pick.
- Criterion: which module's absence makes the rest untrustworthy rather than smaller - smaller is fixable with budget, untrustworthy is not.
- Answer A, keep the end-to-end gate: without it the state records fake completions, the checklist holds fake green points, and the report computes a prettier lie.
- Answer B, keep the state layer: without it the night does not survive at all - 3 items against 9, 6 wasted steps against 0.
- State the condition instead of hedging: output used directly favors the gate, an exploratory run reviewed afterwards favors the state layer.
- The seven-day thread: progress, environment knowledge, the basis for choosing, the criterion for done, the rules, the conclusion - context is volatile, state is authoritative.
- Volunteer the boundaries: sandboxing to the security course, evaluation metrics to the evaluation course, compaction itself to the context engineering course - owner only, no expansion.
答题要点
- 这题没有标准答案,但推理方式有好坏:先给判据再给答案,顺序不能反。
- 判据:哪个模块缺席时其余模块的产出会变成不可信,而不是变少——变少能靠加预算补,不可信没法补。
- 答案 A 留端到端闸门:没有它,状态层记的是假完成、清单是假绿点、报告算出更好看的假数字。
- 答案 B 留状态层:没有它连一夜都跑不下来,实测完成 3 条对 9 条、白干 6 步对 0 步。
- 给场景条件而不是骑墙:产出直接拿去用就选闸门,探索性长跑且后面有人逐条过就选状态层。
- 七天线索:搬出窗口的是进度、环境知识、判断依据、完成的判据、规矩、结论;主线是上下文易失、状态权威。
- 主动说边界:沙箱与权限归安全那门课、评估指标归评估那门课、压缩技术本身归上下文工程那门课,只说归属不展开。