📑 本页目录(点开跳转)
02 · 一个 AI 产品的解剖
⏱ 30 分钟 | 🗺️ 全板块的地图 | 看完你在后面任何一章都知道「自己站在哪」
🎯 一句话
把上一章那个两小时上线的东西剖开,里面是十来个零件——没有一个是 AI 特有的,但 AI 让其中四件事格外难。 这一章不教你做任何东西,它只做一件事:把零件摆开、标上章号,顺便说清哪四件事值得你后面一路提防。
🗺️ 一、跟着一个请求走一遍
用户在输入框敲完问题、按下回车。到他看见第一个字为止,中间经过这些地方:
⭐ 注意图上有两种箭头。实线是同一个请求真的走过去了;虚线是「这件事不跟着这个请求走」——队列里的活儿要过几秒甚至几分钟才有结果,日志则是每一步都往旁边掉一份。分不清这两种,你会把该异步的东西写成同步,然后一个长任务卡住所有人(11 章整章在讲这件事)。
🧩 二、每个零件:解决什么问题,不要它会怎样
| 零件 | 解决什么问题 | 不要它会怎样 | 在第几章 |
|---|---|---|---|
| 反向代理 / TLS | 收 HTTPS、发静态文件、把流量转给后端进程 | 你的应用进程要自己处理证书;⚠️ 而且它默认会缓冲响应,是流式头号杀手 | 14 |
| 鉴权 | 回答「你是谁」,把请求绑到一个用户和一个租户上 | 分不清谁的对话是谁的;越权只差一个查询参数 | 08 |
| 限流 · 配额 | 回答「你还能不能再来一次」 | 一个脚本十分钟刷爆你的额度,账单第二天才告诉你 | 12 |
| 业务编排 | 一次对话该做哪几步:取历史、检索、拼提示词、调模型、校验 | 这些步骤散落在路由函数里,改一处要在五个地方同步 | 03 |
| ⭐ 调用层 | 把「调模型」这件事包成一个你自己的函数:超时、重试、记账、缓存、可替换 | 换模型要改十处;出了事没有一行记录说这次花了多少钱 | 04 |
| ⭐ 流式往回吐 | 让用户在 1 秒内看到第一个字,而不是盯着空白等 20 秒 | 用户以为卡死了,5 秒就重刷——重刷还会再烧一次钱 | 05 · 10 |
| 落库 | 对话、用量、检索用的向量,得有地方放 | 刷新页面对话就没了;月底算不出这个用户花了多少 | 06 · 07 |
| 队列 · Worker | 把「跑几分钟才有结果」的活儿从请求里搬出去 | 一个长任务占着连接,别人全在排队等 | 11 |
| 日志 · 指标 · trace | 出事之后能查清楚「那一次到底发生了什么」 | 用户说「刚才报错了」,你查无可查——这是最先咬你的一个 | 15 |
| 配置与密钥 | 同一份代码在三个环境跑,且密钥不进代码库 | 换个 Key 要重新提交代码;测试环境不小心连了生产库 | 13 |
⭐ 这张表和 01 章末尾那张「缺什么」表是同一张地图的两面:那张说「不补会怎样」,这张说「补的是什么形状」。
⚠️ 三、⭐⭐ 这些零件都不是 AI 特有的
上面十个零件,随便找一本讲后端的书都会讲。那本板块存在的意义是什么?
答案是:AI 应用让其中四件事从「需要注意」变成了「主要矛盾」。
| 这四件事 | 普通 Web 应用 | AI 应用 | 后果 |
|---|---|---|---|
| 响应时长 | 几十到几百毫秒 | ⭐ 秒到分钟,慢一到两个数量级 | 所有默认超时、连接数、部署形态的假设全部失效 |
| 成本 | 一次请求的边际成本约等于 0 | ⭐ 每次请求真的花钱,且和输入输出长度成正比 | 限流从「保护服务器」变成「保护钱包」 |
| 流式 | 可有可无的优化 | ⭐ 必需品 | 返回方式、错误处理、代理配置全部要重做 |
| 不确定性 | 同样输入必然同样输出 | ⭐ 同样输入不同输出 | 缓存变难、测试变难、复现 bug 变难 |
四件事各自把哪些零件搞复杂了:
① 响应长且慢。 一次生成跑 30 秒是家常便饭。于是:Serverless 的执行时限直接把你踢出去(01 章第六节)、连接池和超时的默认值全都太短(04 章)、「同步等结果」这个最自然的写法在几分钟量级的任务上直接不成立(11 章)。⚠️ 最阴的一点:慢本身不报错,它表现为「偶尔有人说卡」。
② 每次请求真的花钱。 普通 Web 里限流是防打垮服务器,AI 里限流是防止某人一个下午花掉你一个月预算。而且账单是延迟到达的——你今天写的一个死循环重试,可能明天才在账单上出现。所以 04 章要求每次调用都记 token 和成本,12 章要求在花钱之前先扣配额。
③ 流式不是优化,是必需。 用户不会盯着空白屏幕等 20 秒。但流式一旦成为必需品,它就会往上游反噬:状态码在第一帧发出去时就定死了(05 章)、任何缓冲层都会把它悄悄破坏掉(05 章那三个凶手)、重试从「用户无感」变成「要推翻用户已经看见的字」(04 章)。
④ 同样输入不同输出。 这条最容易被低估。它意味着:缓存的前提没了(只有 temperature=0 且输入完全一致时才敢缓存,04 章)、测试断言写不出来(不能 assert reply == "...",只能断言结构和范围,15b 章)、线上 bug 常常复现不了(所以 15 章要求每次调用都留下能对上的指纹:输入的哈希 + 字符数 + 脱敏后的开头 —— ⚠️ 默认恰恰不要把 prompt 和回复原样打进日志,隐私、体积、合规三条理由任意一条都够。哈希已经够回答「是不是同一段输入在反复失败」,长度够排查截断;确实需要完整内容时另走一条通道:短留存期、独立权限、默认关闭、按比例采样)。
⭐⭐ 把这四条记住,这个板块的每一章你都能猜到它为什么存在。 后面每一章开头,你都可以先问一句:它在处理这四件事里的哪一件?
🧮 四、一次请求的时间和钱花在哪
⚠️ 我不会给你一组延迟数字——那要看模型、区域、长度和当天的负载,任何写死的数字都会骗你。能给的是结构:
| 环节 | 量级来自哪里 | 你能做什么 |
|---|---|---|
| 代理 + 鉴权 + 限流 | 本机 / 内网操作,毫秒级 | 基本不用管 |
| 取历史 + 检索 | 一到几次数据库往返 | 06 / 07 章:索引和连接池 |
| ⭐ 模型生成 | 跨网 + 逐 token 生成,秒级起 | 04 章:超时和重试;05 章:让用户提前看到字 |
| 落库 + 日志 | 毫秒级,且可以不挡着响应 | 15 章:别把日志写成同步阻塞 |
⭐ 结论是个数量级判断,不是实测:模型生成是跨网络、逐 token 产出的,其余环节是本机或内网的一次性操作——所以除非你的数据库写得很糟,否则优化其余环节的收益,会被模型生成那一段完全淹没。
那怎么知道自己的应用到底慢在哪?自己量,而且量的位置是固定的两处:在调用层记下每次模型调用的耗时(04 章),用同一个 trace_id 把一次请求里的各段串起来(03 章埋点、15 章看图)。💀 没有这两处,你只能靠猜——而人对「哪里慢」的直觉在 AI 应用里几乎总是错的,因为大家会本能地去优化自己写的代码,而时间其实全花在等别人。
🚧 五、什么不在这个板块里
这个板块只回答一类问题:「模型已经好了,但少了它产品就上不了线」。不是这类的,都在别处,而且都比这里讲得深:
| 你想问的 | 去哪 | 分界线 |
|---|---|---|
| 模型准不准、上线后有没有退化 | 《模型上线之后》 | ⭐ 本板块管「服务健不健康」,那边管「模型准不准」 |
| 推理引擎怎么选、怎么把吞吐榨出来 | 《AI基础设施》20 章 | 那边是「怎么跑模型」,这边是「怎么调它」 |
| RAG 怎么切块、怎么重排、怎么评 | 《大模型全景导论》08 章 | 那边讲策略,本板块 07 章讲怎么真的存进库里 |
| 向量检索的 HNSW / IVF 是什么原理 | 《推荐算法》10 章 | 本板块只讲工程选型,不讲索引结构 |
| 数据该不该留、怎么脱敏 | 《数据这一关》19 章 | 本板块 08 章只讲「怎么隔离」,不讲「该不该留」 |
🔄 六、换个栈怎么对应
零件是概念,换语言只换名字:
| 零件 | Python / FastAPI | Node(Express / Hono) | Go |
|---|---|---|---|
| 每请求都做的事 | Depends(...) |
中间件 | 中间件 + context.Context |
| 分批往外写 | StreamingResponse |
res.write() / ReadableStream |
w.Write() + ⭐ Flusher.Flush() |
| 后台跑长任务 | 队列 + Worker 进程 | 队列 + Worker 进程 | goroutine 也能扛一部分,但重启就丢 |
| 取消传播 | asyncio 的 CancelledError |
AbortSignal |
context.Context 的 Done() |
⭐ 反向代理那一层三个栈完全一样 —— Nginx / Caddy 和你用什么语言无关。
⭐ 这张表想说的是:最容易咬到你的那两个零件——反向代理的缓冲和取消传播——在三个栈里长得完全不同,但它们是同一个问题。换栈重写时,人们通常记得搬业务逻辑,忘了搬这两件。
🔗 这一章连到哪里
| 去哪 | 为什么 |
|---|---|
| 01 · 第一天:两小时上线 | 那一章末尾「缺什么」表的每一行,就是这张图上的一个零件。先跑通再看图,比先看图有用 |
| 03 · 后端骨架 | 图上「业务编排」那个框展开就是下一章:边界、async、依赖注入、错误形状 |
| 04 · 调用层 | 图上标了重点的那个框——四件难事里的成本和超时主要在这一层解决 |
| 05 · 流式输出 | 另一个标了重点的框——四件难事里的流式在这一层解决 |
| 智能体工程 16c · 接进真实产品 | ⭐ 最近的邻居。那边讲「为什么要流式、护栏怎么设计」,本板块讲「这条链路怎么搭、坑在哪」 |
| 模型上线之后 · 目录 | 第五节那条分界:本板块管服务健不健康,那边管模型准不准。两套监控都要有 |
| AI基础设施 20 · 推理服务化 | 图上「模型 API」那个框,在对面是一整个系统。想自建就得读那边 |
✅ 检查点
- 图上实线箭头和虚线箭头分别代表什么?分不清会写出什么 bug?
- 反向代理除了收 HTTPS,为什么在这个板块里格外危险?
- 「日志 · 指标 · trace」为什么被画成横贯全程的一条带,而不是链路上的一环?
- AI 应用让哪四件事从「需要注意」变成「主要矛盾」?
- 「每次请求真的花钱」让限流的目的发生了什么变化?账单为什么特别危险?
- 「同样输入不同输出」分别把哪三件事搞难了?
- 为什么说优化「取历史 + 落库」那几段的收益会被淹没?这个结论是实测还是推理?
- 「模型准不准」和「服务健不健康」分别归哪个板块管?
👀 答案
- 实线 = 同一个请求真的走过去了;虚线 = 这件事不跟着这个请求走(队列里的活要几秒到几分钟,日志是每步往旁边掉一份)。分不清就会把该异步的写成同步,一个长任务卡住所有人。
- 因为它默认会缓冲响应——本地逐字正常,上线变成「憋很久然后一次性全出现」。它是流式的头号杀手(05 章第四节)。
- 因为它不属于任何一步,而是每一步都要往旁边掉一份。它不在主链路上,但没有它,出事后你连「发生了什么」都答不出来。
- 响应长且慢(秒到分钟,慢一到两个数量级)、每次请求真的花钱、流式是必需品不是优化、同样输入不同输出。
- 从「防止打垮服务器」变成「防止某人一个下午花掉你一个月预算」。账单危险在于它是延迟到达的——今天写的死循环重试,可能明天才在账单上出现。
- 缓存(只有
temperature=0且输入完全一致才敢缓存)、测试(不能assert reply == "...",只能断言结构和范围 —— 15b 章)、复现 bug(所以 15 章要求每次都留下指纹:哈希 + 字符数 + 脱敏后的开头 —— ⚠️ 默认不把原文打进日志;完整内容只走短留存、独立权限、默认关闭、按比例采样的单独通道)。 - 因为模型生成是跨网 + 逐 token 产出(秒级起),其余环节是本机或内网的一次性操作(毫秒级)。⭐ 这是数量级推理,不是实测——你自己的数字要在调用层量(04 章)、用 trace_id 串起来(03 / 15 章)。
- 「模型准不准」归《模型上线之后》,「服务健不健康」归本板块。两套监控都要有,别互相替代。
🛑 可以停在这里
⚡ 走神救援
这一章是地图,不教做任何东西。一次请求的一生:浏览器 → 反向代理/TLS(14 章)→ 鉴权(08)→ 限流配额(12)→ 业务编排(03)→ 调用层(04)→ 模型 API → 流式往回吐(05,前端在 10)→ 落库(06/07)→ 浏览器逐字渲染;旁边挂两条虚线:慢任务甩进队列(11)、日志/指标/trace 贯穿全程(15)。⭐ 虚线的意思是「这件事不跟着这个请求走」——分不清实线虚线,就会把该异步的写成同步,一个长任务卡住所有人。十个零件没有一个是 AI 特有的,随便一本后端书都讲。本板块存在的理由是 ⭐⭐ AI 让其中四件事从「需要注意」变成「主要矛盾」:① 响应长且慢——秒到分钟,比普通 API 慢一到两个数量级,于是 Serverless 的执行时限、默认超时、连接池假设全部失效,而且慢本身不报错,只表现为「偶尔有人说卡」;② 每次请求真的花钱——限流从「防打垮服务器」变成「防某人一下午花光一个月预算」,且账单延迟到达,所以调用层必须每次记 token 和成本,配额必须在花钱之前扣;③ 流式是必需品不是优化——它会反噬上游:状态码在第一帧发出时就定死了、任何缓冲层都会悄悄破坏它、重试从「用户无感」变成「推翻用户已经看见的字」;④ 同样输入不同输出——缓存的前提没了(只有
temperature=0且输入完全一致才敢缓存)、测试断言写不出来(只能断结构和范围)、线上 bug 常常复现不了。时间和钱花在哪:代理/鉴权/限流是毫秒级本机操作,模型生成是秒级跨网逐 token 产出——所以优化其余环节的收益会被完全淹没;⚠️ 这是数量级推理不是实测,你自己的数字要在调用层量、用同一个 trace_id 串起来,因为人对「哪里慢」的直觉在 AI 应用里几乎总是错的。最后是边界:模型准不准归《模型上线之后》,推理引擎怎么选归《AI基础设施》20 章,RAG 策略归《全景导论》08 章——本板块只回答「模型已经好了,但少了它就上不了线」的那类问题。
下一节 👉 03-后端骨架.md