System Design Deep Dive: Agent Platforms / Customer-Support Agents / Multi-Tenancy / Cost Control
Prepare an interview-ready answer template for each of four frequently asked system-design topics: agent platforms, customer-support agents, multi-tenancy, and cost control.
今日目标
- 能给出一份 Agent 平台的系统设计答题模板(架构、扩展性、成本)
- 能给出一份客服 Agent 的系统设计答题模板(多轮对话、升级人工、知识库)
- 能给出多租户与成本控制两个专题的设计要点清单
从今天起这门课没有新技术了:昨天把后端接到浏览器上,四周的技术内容就此结束——接下来五天练的是表达,同样这些零件怎么在 40 分钟里讲成一个让面试官点头的答案。所以今天你会看到很多熟悉的名词,但重点不是"它是什么",而是"这一段该花几分钟、面试官会在哪里插话、哪句话说错了会当场丢分"。读完回到页面顶部把三条目标勾掉。
小白版讲解
甲方只给一句话,你不能直接掏施工图
一场建筑设计投标答辩。甲方开场只有一句话:"我们要一栋办公楼。"
落标的那一家当场展开一卷施工图,梁柱配筋、管线走向都画好了。图很漂亮,但甲方听到第三分钟就开始看手机——他还没说预算是两千万还是两个亿。
中标的那一家先花五分钟发问:预算区间、层数、消防等级、工期。然后掏出一张只有方框和箭头的总平面图,挑结构选型和消防疏散两处深入讲,其余一律说"按常规做"。最后五分钟专门讲权衡:为什么不做地下二层,预算涨到多少就该推翻这个方案。
上来就掏施工图的,一定落标。 系统设计面试是同一场答辩:一句话的题干、35 到 40 分钟、一位在心里打分的甲方。答题的形状是固定的五步,每一步都有时间盒:
| 步骤 | 时间 | 这一步必须交出什么 |
|---|---|---|
| 1 需求澄清 | 5 分钟 | 日活与并发会话数、单轮延迟预算、成本预算、是否多租户、失败可容忍度 |
| 2 容量与成本估算 | 3 分钟 | 一个带算式的数字,不是一个凭感觉报的数字 |
| 3 架构草图 | 8 分钟 | 四块:接入层 / 执行层 / 存储 / 可观测 |
| 4 深入 2 到 3 个点 | 15 分钟 | 从三个提前备好的"深入包"里现挑 |
| 5 权衡与取舍 | 5 分钟 | 放弃了什么、什么规模下会推翻这个设计 |
下面逐步说清面试官会在哪里插话。
第一步,需求澄清 5 分钟。 五个问题一个都不能省,它们每一个都会实质改变架构:日活决定要不要拆、延迟预算决定同步还是异步、成本预算决定模型分档、多租户与否决定表结构、失败可容忍度决定重试和降级的形状。面试官多半会插一句"你先自己假设一个"——那不是让你别问,是让你自己给一个数并说出依据:"那我按日活 1 万、人均 5 轮来算,如果实际是十万级我会在第五步说明哪里要改"。不问就开画的,二十分钟后他会发现你解的是另一道题。
第二步,估算 3 分钟。 只报结果不报算式是这一步最常见的死法。面试官插的那句一定是"这个数怎么来的"。
第三步,草图 8 分钟。 四个方框加箭头就够。面试官会指着某根箭头问"这里是同步还是异步"——把所有箭头都画成同步调用,是这一步唯一的致命伤。
第四步,深入 15 分钟。 这一步不能临场想,三个"深入包"要提前备好,面试官挑哪个你都有:状态与保序、成本与限流、失败与重试,分别对应你已经写过的 D11、D13、D9 加 D10。
第五步,权衡 5 分钟。 这一步几乎没人做,做了就是加分——它是"设计过"和"读过设计"之间最短的一道分界线。
结构会背不等于会讲。四类高频题各有一条主线,主线选错,五步走得再标准也是空架子。下面四份模板,每份先钉一句主线,再往上挂零件。
模板一:Agent 平台,主线是"无状态执行 + 有状态编排"
一句话主线:Gateway 无状态、水平扩;run 的状态落 Postgres;长任务靠消息总线解耦;编排在 worker。 这句话要在架构草图开始的第一秒说出来,它是后面所有细节的挂钩点。
这一题特有的澄清问题有三个:单次执行有多长(三百毫秒就别拆,三十秒就必须拆)、要不要流式(要,则接口从一个变成两个)、工具有没有副作用(有,则要人工确认档和参数上限)。
估算那 3 分钟,现场把这段写出来。它只有六行,但改任何一个输入,六个输出全都跟着变——面试官要的正是这个"跟着变"。
// 面试白板上就写这几行。价格口径:输入 0.15 美元 / 100 万 token,输出 0.60 美元 / 100 万 token
const PRICE_IN = 0.15 / 1_000_000
const PRICE_OUT = 0.6 / 1_000_000
function estimate({ dau, turnsPerUser, promptTokens, completionTokens, peakFactor, avgLatencySec }) {
const runsPerDay = dau * turnsPerUser
const peakQps = (runsPerDay / 86400) * peakFactor
// 利特尔法则:在途请求数 = 到达率 × 平均停留时间
const concurrency = Math.ceil(peakQps * avgLatencySec)
const costPerRun = promptTokens * PRICE_IN + completionTokens * PRICE_OUT
return {
runsPerDay,
peakQps: peakQps.toFixed(2),
concurrency,
workers: Math.ceil(concurrency / 4), // 一个 worker 同时跑 4 次执行
costPerDay: (costPerRun * runsPerDay).toFixed(2),
costPerMonth: (costPerRun * runsPerDay * 30).toFixed(2),
}
}
console.log(
estimate({
dau: 10000,
turnsPerUser: 5,
promptTokens: 2000,
completionTokens: 500,
peakFactor: 3,
avgLatencySec: 6,
}),
)
// runsPerDay 50000, peakQps 1.74, concurrency 11, workers 3,
// costPerDay 30.00, costPerMonth 900.00import math
from dataclasses import dataclass
PRICE_IN = 0.15 / 1_000_000
PRICE_OUT = 0.60 / 1_000_000
@dataclass
class Estimate:
dau: int
turns_per_user: int
prompt_tokens: int
completion_tokens: int
peak_factor: float
avg_latency_sec: float
@property
def runs_per_day(self) -> int:
return self.dau * self.turns_per_user
@property
def peak_qps(self) -> float:
return self.runs_per_day / 86_400 * self.peak_factor
@property
def concurrency(self) -> int:
# 利特尔法则:在途请求数 = 到达率 × 平均停留时间
return math.ceil(self.peak_qps * self.avg_latency_sec)
@property
def workers(self) -> int:
return math.ceil(self.concurrency / 4)
@property
def cost_per_day(self) -> float:
per_run = self.prompt_tokens * PRICE_IN + self.completion_tokens * PRICE_OUT
return per_run * self.runs_per_day
e = Estimate(10_000, 5, 2_000, 500, 3.0, 6.0)
print(f"轮次/天 {e.runs_per_day} 峰值 {e.peak_qps:.2f} QPS 并发 {e.concurrency} worker {e.workers} 个")
print(f"日 {e.cost_per_day:.2f} 美元 月 {e.cost_per_day * 30:.2f} 美元")// record 天生适合这种「一组输入推一组派生量」的估算器:改一个分量,所有方法的结果跟着变
public record Estimate(
int dau, int turnsPerUser, int promptTokens, int completionTokens,
double peakFactor, double avgLatencySec) {
private static final double PRICE_IN = 0.15 / 1_000_000;
private static final double PRICE_OUT = 0.60 / 1_000_000;
public int runsPerDay() {
return dau * turnsPerUser;
}
public double peakQps() {
return runsPerDay() / 86_400.0 * peakFactor;
}
// 利特尔法则:在途请求数 = 到达率 × 平均停留时间
public int concurrency() {
return (int) Math.ceil(peakQps() * avgLatencySec);
}
public int workers() {
return (int) Math.ceil(concurrency() / 4.0);
}
public double costPerDay() {
return (promptTokens * PRICE_IN + completionTokens * PRICE_OUT) * runsPerDay();
}
public static void main(String[] args) {
var e = new Estimate(10_000, 5, 2_000, 500, 3.0, 6.0);
System.out.printf("轮次/天 %d 峰值 %.2f QPS 并发 %d worker %d 个%n",
e.runsPerDay(), e.peakQps(), e.concurrency(), e.workers());
System.out.printf("日 %.2f 美元 月 %.2f 美元%n", e.costPerDay(), e.costPerDay() * 30);
}
}import Foundation
// 计算属性让派生量始终跟着输入走,不需要手动同步
struct Estimate {
let dau: Int
let turnsPerUser: Int
let promptTokens: Int
let completionTokens: Int
let peakFactor: Double
let avgLatencySec: Double
static let priceIn = 0.15 / 1_000_000
static let priceOut = 0.60 / 1_000_000
var runsPerDay: Int { dau * turnsPerUser }
var peakQps: Double { Double(runsPerDay) / 86_400 * peakFactor }
// 利特尔法则:在途请求数 = 到达率 × 平均停留时间
var concurrency: Int { Int((peakQps * avgLatencySec).rounded(.up)) }
var workers: Int { Int((Double(concurrency) / 4).rounded(.up)) }
var costPerDay: Double {
(Double(promptTokens) * Self.priceIn + Double(completionTokens) * Self.priceOut)
* Double(runsPerDay)
}
}
let e = Estimate(dau: 10_000, turnsPerUser: 5, promptTokens: 2_000,
completionTokens: 500, peakFactor: 3, avgLatencySec: 6)
print("轮次/天 \(e.runsPerDay) 峰值 \(String(format: "%.2f", e.peakQps)) QPS "
+ "并发 \(e.concurrency) worker \(e.workers) 个")
print(String(format: "日 %.2f 美元 月 %.2f 美元", e.costPerDay, e.costPerDay * 30))口头版这么说:单轮 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 个副本——正好是你在 D14 里真跑起来过的那个数字。
草图那 8 分钟画四块,每块一句话:接入层只做鉴权、限流、落库、投递,立刻返回 202(D8);执行层从消息总线取活、跑 Agent 循环、把片段带序号回传(D9、D11);存储是 sessions、runs、messages 三张表,幂等靠 runs 上的唯一约束而不是先查后插(D8);可观测是 tracing 加成本台账(D13、D21)。
深入 15 分钟随面试官挑。挑到状态与保序,你讲的是"消费组的分配单位是一条消息,业务要求的串行单位是一个用户,所以按 userId 哈希到 256 个分片、每个分片同一时刻只有一个 worker 持有租约"(D10),再补一句"seq 从 0 开始连续不跳号,断线重连才能按'最后收到的号加一'续上"(D11)。说错会怎样:随口说一句"用 Kafka 更专业"却讲不出保留期和第二类消费方这两条判据,下一个追问就把你钉在那儿。
权衡那 5 分钟说三句:放弃了跨用户的全局有序(顺序性和并行度是反比);单轮 300 毫秒的场景不该拆,拆了纯属自找麻烦;日活涨到十万级时第一个撞墙的不是 CPU,是数据库连接数。
模板二:客服 Agent,主线是"三条出口"
一句话主线:任何一通会话最后只能落到三条出口之一——自助解决、转人工、留工单。 面试官想听的从来不是"我接了个知识库",而是你怎么保证不把用户困在机器人里。把这句话当骨架,知识库和多轮对话都只是挂在出口上的零件。
转人工的判据必须量化,四条任一命中就转,这一段照着说:连续 2 轮未解决、用户明确要求(命中"转人工"这类说法)、涉及金额超过阈值(沿用工具那一档的自动执行上限 50 元)、情绪词命中。只说"识别到用户不满意就转人工"是空话,面试官会立刻问"怎么判断不满意"。
知识库这一块一句话回指就够:混合检索加重排,回答带引用编号(D24)。真正要讲透的是答不出的时候怎么办——如果检索出来的引用为空,正确行为是走出口二或出口三,而不是让模型自由发挥编一个答案。这一句是本题最容易被追的地方:主动说出"我把'引用为空'当成一个显式分支",比把检索流程讲得多细都管用。
多轮对话同样一句话回指:历史超过预算七成触发压缩、切口对齐到一轮开头(D6),用户在回复中途改口则 30 秒内合并进同一次执行而不是并发开两个(D11)。
面试官这时候一定会插一句:"转人工的时候,上下文怎么交给人工?" 答案不是"把对话记录发过去"——40 轮原文丢给客服,他得读两分钟才敢开口。正确形状是一段结构化摘要:用户诉求一句、已核实的事实几条(订单号、金额、物流状态)、Agent 已经做过的动作、失败原因,再附一个原始对话的链接备查。
出口三"留工单"是人工也不在线时的兜底(夜间、排队超时)。工单里必须带这次执行的 trace id,否则第二天接手的人无从查起(D21)。
权衡讲一句就够,但要讲得准:转人工的判据宁可偏松。 误转的代价是一次人工会话的成本,把用户困在机器人里的代价是一个流失的客户加一条差评——这两个代价不在一个量级上,所以阈值要往"容易转"那一侧偏。
模板三:多租户,主线是"三层隔离"
一句话主线:数据、资源、计费三层隔离,缺哪一层就对应一类事故。 答题时按这个顺序走,别把三件事混着说。
数据隔离:所有业务表加一列 tenant_id,并且开数据库的行级安全(row level security)。升级路径有三档——共享表加 tenant_id(绝大多数场景)、schema 级隔离(少数有定制字段的大客户)、库级隔离(合规要求数据物理分开)。判据不是租户数量,是"有没有单个租户能把别人拖垮"和"有没有合规硬要求"。
说错会怎样:只答"每条查询都带上 tenant_id",面试官下一句就是"漏一处呢"。 正确答案是把最终裁判交给数据库:行级安全是闸门,应用层那句 where 只是优化。这和 D8 那句"幂等的最终裁判必须是数据库的唯一约束"是同一种思路的第二次出现——能在数据层强制的,不要指望每个人写代码时都记得。
资源隔离:每个租户一个独立的限流桶,worker 按 tenantId 哈希分片。这就是 D10 那套分片加租约,只是把哈希的输入从 userId 换成 tenantId——目的从"保住同一个用户的顺序"变成"让一个租户的洪峰打不穿别人的处理能力",机制一行都不用改。噪声邻居问题在 Agent 场景里格外突出,因为单次执行可能跑三十秒,一个租户批量灌进来一千条,别人就得排在后面。
计费隔离:D13 那张成本台账表加一列 tenant_id,每一笔 token 用量写进去时打标。账单、配额、超支降级三件事全靠这一列。
这里要专门提一次幂等键,因为多租户会把它悄悄改错。D8 落库、D13 定时调度、D19 跨服务调用、D25 前端重试,这四处用的是同一招;到了多租户,键本身不用变,但它的作用域必须带上 tenantId。不带的话,两个租户的客户端各自生成了同一个字符串(比如都用订单号 order-1024),后来的那一个会被唯一约束当成重复请求挡掉——一个租户的写入被另一个租户的历史请求吞掉,这是多租户里最难查的一类 bug,因为两边的日志看上去都完全正常。
权衡两句:行级安全每条查询多一次策略判断,是有性能代价的;库级隔离看着最干净,但迁移脚本、备份、监控、连接池全部乘以租户数,运维成本是陡增而不是线性增长。
模板四:成本控制,五层手段从便宜到贵
一句话主线:五层手段,按"你要付出的代价"从小到大排——前三层改自己的代码,第四层改行为边界,第五层动产品承诺。 面试官问"成本失控了怎么办",你按这个顺序答,等于顺便告诉了他你的落地顺序。
先把基准立在桌上:单轮 0.0006 美元、日活 1 万人均 5 轮、一天 30 美元、一个月 900 美元。没有基准的成本讨论全是废话——你说不出任何一层"能省多少"。
第一层,缓存与 prompt cache。 高频问答命中缓存的那一轮一分钱不花;系统提示词加工具定义这段固定前缀(10 个工具约 1000 到 1500 token,D6 的口径)在重复请求里能走缓存计费。按一成半的轮次命中估,900 美元降到 765 美元左右。代价只有缓存失效的一致性问题。
第二层,模型分级路由。 简单意图(分类、路由、闲聊)走小模型,复杂推理才上强模型(D4)。这一层要诚实说清一个前提:它是五层里唯一能改变数量级的,但前提是你的基准用的是旗舰模型。 本课这个 900 美元的基准本身用的就是最便宜那档,再往下已经榨不出什么了——这句话主动说出来,比硬编一个省钱比例有说服力得多。
第三层,上下文压缩。 单轮那 2000 输入 token 里,有一半以上是系统提示词加工具清单,历史再一涨就更难看。做法是历史超过七成预算就摘要(D6),外加把低频工具改成按场景动态挂载。输入从 2000 压到 1200,单轮成本变成 1200 除以一百万乘 0.15 加上 500 除以一百万乘 0.60,等于 0.00048 美元,一天 24 美元、一个月 720 美元,降幅两成。代价是压缩本身要多调一次模型,而且信息丢失不可逆。
第四层,步数与工具预算上限。 每个子任务最多 5 次工具调用、最多打回 2 次(D17)。这一层买的不是省钱,是可预测。 调满 5 次工具、每次结果回灌 800 token,输入涨到 6000,单轮 0.0012 美元——正好是基准的两倍,而没有上限时这个数字没有上界。代价是预算耗尽要降级返回已有结果,不能抛错。
第五层,限流与降级。 每个用户或租户一个日预算,超了先降档、再超才拒绝(D13)。900 美元摊到 1 万日活是每人每月 0.09 美元,给单用户设 1 美元硬顶,正常用户一辈子碰不到,挡住的是脚本刷接口那种极端户。它是用户唯一能感觉到的一层,代价最大——所以放在最后。
源码导读
动手实验
今天的产出是文档不是程序,所以这个实验里没有 pnpm install、没有 MOCK=1、也没有自检脚本。solution/ 是用本课三个仓库(agent-service、mini-koda、mini-multi-agent)填好的示范样例——它是参照物不是答案,你要照着改成自己的项目细节,否则第一个追问就露馅。手机录音就够用,重点是回放时掐表。
- 打开
starter/agent-platform.md,把五步骨架逐段填成自己的话,估算那一步必须写下算式而不只是结果。 - 填
starter/customer-support-agent.md,三条出口和四条量化判据一条都不能少,尤其是"引用为空怎么办"那一分支。 - 填
starter/multi-tenant.md,三层隔离各写三到五条,并注明幂等键的作用域为什么要带上租户标识。 - 填
starter/cost-control.md,五层手段每层配一个从基准推出来的数字,写清各自的代价。 - 对着
agent-platform.md录一遍音,掐表 15 分钟,回放时用验收标准第 2 条逐项对照,把讲不顺的那一段单独再练三遍。
面试题
今天 4 道题在下方题库区:一道考答题过程本身,一道是完整的系统设计大题(客服 Agent),另外两道分别是多租户与成本控制。展开后先看"分析过程"再看要点——第 2 题的分析过程不是要点列表,是一份 35 分钟的讲稿骨架,别当成普通题目扫一眼就过。标注"国内高频 / 海外高频"方便按目标市场取舍。
检查清单与明日预告
- 能给出一份 Agent 平台的系统设计答题模板(架构、扩展性、成本)
- 能给出一份客服 Agent 的系统设计答题模板(多轮对话、升级人工、知识库)
- 能给出多租户与成本控制两个专题的设计要点清单
- 能背出五步的时间盒,并说出每一步"面试官会插哪句话"
- 能不看讲义算出单轮 0.0006 美元、每月 900 美元这两个数,并说得出算式
- 三个"深入包"任挑一个都能讲满 5 分钟
- 实验的 5 条验收标准全部通过,
agent-platform.md已经录音掐表讲过一遍 - 4 道面试题不看要点也能答出至少 3 道
明天(D27)我们往前挪一步:系统设计题考的是"你会不会设计",但面试官愿意花 40 分钟问你这道题之前,得先从简历上相信"这人真做过"。所以明天把 agent-service、mini-koda、mini-multi-agent 三个仓库包装成简历上的亮点:STAR 一句话公式、README 七段、Mermaid 架构图、demo 视频,外加一版英文简历。顺序是有意的:今天先知道面试官想听什么,明天写简历时才知道该往上放哪几个数字。
Interview questions
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 realises 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 memorised 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 centre 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 summarise 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 optimisation — 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 serialising. Noisy neighbours 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 optimisation
- Resource isolation is a per-tenant rate-limit bucket plus sharding workers by tenant hash, aimed at noisy neighbours
- 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 美元,没上限时没有上界
- 限流降级放最后,因为它是唯一用户能感觉到的一层;超预算先降档再拒绝
Comments
Sign in to join the discussion
No comments yet — be the first.