跳到主要内容

上线,以及还缺的三层

这一章讲四件事: 做出来和上线之间隔着什么、 快速搭出来的东西会在哪儿撑不住、 用户随手拖进来一份 PDF 要花多少钱才处理得了、 以及交出去之后还缺哪三层。

它在全书链条上的位置:前面十七章的东西,到这里第一次被真实用户碰到。

1. 这一章讲什么

三句话:

  1. 这一章开篇就泼了一盆冷水,而且这盆冷水决定了全章的取舍: 「大多数生成式 AI 项目都始于实验,而且大多数从未上生产。」 ——所以原型阶段该优化的是前期投入,不是可扩展性;
  2. 它顺手给了全书最直白的一次反例:一个能跑的完整系统, 它的「检索器」是两次普通的接口调用,一个向量库都没有;
  3. 全书最后一个技术建议,是指回第 13 章那一章的—— 这就是我们在总纲里说「这本书的落点在 agent 那一章,不在最后一章」的第三条证据。

2. 顶层全景:从「能跑」到「有人用」隔着几步

几小时 几周 还缺三层
──────── ──────── ────────────────
一个脚本 → 打包成一个到处都能跑的东西 → ① 基础设施写成文件
界面 + 逻辑 推到按用量计费的托管运行时 ② 每次提交自动测试再部署
全在里面 每月 30 到 40 美元 ③ 密钥不进镜像
│ │ │
第 4、5 节 第 9 节 第 10 节

└─ 中间还有三个坑:状态在用户之间泄漏(第 6 节)
上传的 PDF 一律转成图片,贵 10 到 100 倍(第 7 节)
让模型写查询语句的三种失效(第 8 节)

图说:注意第一行的时间刻度 —— 书自己的话是
"你能在几小时内做出原型,但之后重构到生产基础设施要花几周"。
这一章的全部判断都建立在这个不对称上。

3. 先看现象:大多数项目从来没上过线

书这一章的第一句话是1:

「大多数生成式 AI 项目都始于实验,而且大多数从未上生产。」

这句话不是感慨,它是一条工程判断的前提。

如果十个项目里有九个活不到上线:

❌ 一开始就按"能扛一万人"去搭
→ 九个项目里,那份扩展性投入全部打了水漂

✅ 一开始就按"最快让人试用"去搭
→ 九个项目省下了那笔钱
→ 活下来的那一个,再花几周重构

图说:这就是"原型阶段该优化前期投入,不是可扩展性"的算术。
(九比一这个比例是我们为演示编的;"大多数从未上生产"是书里的。)

书还给了一句和第 01 章首尾呼应的提醒,值得单记2:

RAG 早期,很多应用默认做成聊天机器人,哪怕更简单的界面本来更合适。 仪表盘和搜索界面往往更快也更直观。 选聊天界面之前,先想清楚你的场景是不是真的从对话里获益。

4. 全书最直白的一次反例:检索器是两次接口调用

这一节很短,但它值得单独立一节,因为它纠正的是一个很普遍的误解。

书那个跑例是一个天气问答应用,而它的流程是3:

① 先用模型看一眼用户的问题,判断里面有没有地名
—— 没有地名就答不了,因为天气接口必须要一个地点

② 第一次接口调用:把地名转成经纬度
③ 第二次接口调用:拿这对经纬度取当前天气
④ 把天气数据连同用户的问题塞进提示,交给模型组织成人话

书自己的原话:"在这个系统里,检索器由两次接口调用构成。"

这套东西完整符合第 02 章那条定义:提问时先去外部资料里搜,再把搜到的东西连同问题一起交给模型。 它只是没有向量库、没有那 1 536 个数、没有相近程度。

判断(我们的,不是书里的):这个例子被放在了第 11 章一个不起眼的位置, 而它其实该出现在第 1 章。 前面十七章一路建下来,读者很容易把「检索」和「向量搜索」当成同一件事—— 而这个例子说明它们不是。检索只是「去外面取一份这次要用的材料」, 取的方式可以是搜库、可以是查表、也可以就是调两个接口。 如果错,会错在: 如果读者本来就没有把两者混同(比如做过一般的后端开发), 那这个例子只是一个平平无奇的演示,不值得特意强调。 判据是:问一句「不用向量库的 RAG 算不算 RAG」——答得犹豫,就说明这个例子该提前。

5. 快速搭界面那类工具:它省事的原因,也是它的天花板

这一节讲的是原型阶段最常用的那一类工具,书用的是其中最流行的一个。

它是什么,以及它凭什么快

书给的定义4:一个用 Python 快速做网页应用的框架—— 一个脚本就同时覆盖了前端和后端;它提供搭好的组件,你不必碰底层的网页开发。

代价也在同一句里:你被限制在它现有的那些组件里。

它的机制,一句话就说透了

这一句是这一节的钥匙5:

它在每一次用户交互时,都把你的整个脚本从头到尾重跑一遍。

用户在输入框里打了一个字、按了一次按钮、拖了一下滑块

你的整个脚本重跑一遍:导入、连库、渲染界面、算这一次的结果

页面刷新

为什么这让开发变简单:
你不用管"哪个组件该更新""状态放哪儿"——每次都是从头来一遍,不存在不同步。

为什么这撑不住人多:
每一个用户的每一次点击,都要付一遍整个脚本的开销。

那条量化的天花板

书给了三个很具体的数6:

并发用户数情况
几十个正常
超过 50 个这个模型就成了瓶颈
100 到 500有一个折中办法能撑到这里(见下),再往上就得整个重写

那个折中办法是6:把它当成一个轻量的前端, 把重活——认证、模型调用、数据库操作——卸载给专门的接口服务。

什么时候别用它

书给的三条反面判据7:

别用它,如果该用什么
面向几千用户的对外应用另一套接口框架 + 前端框架,扩展性更好
需要定制品牌外观另一套全功能框架,界面控制更多
需要细粒度的认证那套接口框架接认证更自然

但书的最终判断仍然偏向它7:

你能在几小时内把一个能跑的应用做出来原型,但之后重构到生产基础设施要花几周。 对于多半不会上生产的实验,这是更好的选择,因为它把前期投入降到最低。

6. 承重词一:会话状态 —— 以及那个只有两个窗口才测得出的坑

这一章第一个承重词。一句话先给结论:会话状态就是「这个用户这一次的来龙去脉」—— 他刚才问了什么、上传了什么;它必须在脚本重跑之间活下来,而它归谁所有,是这一节的全部要害。

为什么需要它

接上一节:脚本每次交互都重跑一遍。 那么「用户刚才问过的那三句话」放在哪里?

放在普通变量里不行——变量就是代码里用来临时存一个值的名字, 而脚本一重跑,所有变量都从头重新赋一遍值,上一轮存的东西就没了。 所以这类框架都提供一个专门的地方,让数据跨重跑活下来。

对话记录存成一个列表,每条是 {"role": ..., "content": ...}

每次脚本重跑:
先检查"这个地方里有没有 messages 这一项"
没有(第一次进来)→ 建一个空列表
有 → 原样拿来用,把新的一轮追加进去

↑ 那个"先检查"是必需的。少了它,第一次进来就会因为取不到这一项而报错。
(书专门提了这个坑。)

那个真正要命的坑

这是这一章最该记住的一条,而且它只有用两个窗口才测得出来8:

主要风险是状态在用户之间泄漏。 这类框架为每个浏览器标签页创建一个会话,但只用单标签页测试会漏掉多用户的问题。 如果用户 A 上传了一份文档、用户 B 刷新页面,B 会不会看到 A 的文档? 用两个浏览器窗口测,才能早点抓到这个问题。

为什么单窗口测永远测不出:

单窗口测试:
你上传文档 → 你提问 → 你看到自己的文档 → 一切正常 ✓

真实情况:
A 上传文档 → 变量存在哪里?
存在"会话"里 → B 看不到 ✓ 正确
存在模块级变量里 → **B 看得到** ✗ 泄漏
存在磁盘上某个固定路径 → **B 看得到** ✗ 泄漏

图说:这三种写法在单窗口下的表现**完全一样**。
所以这不是"测得不够仔细",是"这个测法在结构上就发现不了"。

三条工程纪律

书还给了三条9:

  1. 脚本控制在 100 行以内——接口调用和提示组装挪到单独的模块里;
  2. 重活(算那些数、调大模型)部署成独立的服务,别放在这个脚本里;
  3. 别用打印语句调试——脚本重跑时打印会乱序出现,要用编辑器的调试器

7. 拖一份 PDF 进来就地分析:一律转成图片的那条流水线

一句话先给结论:把 PDF 的每一页都渲染成图片、逐页交给多模态模型认字, 换来的是一条流水线通吃所有 PDF,付出的是 10 到 100 倍的钱。

先看现象:上传框接住的东西,你事先不知道它是哪一种

用户在聊天界面上拖进来一份 PDF,想当场问它。麻烦在于你不知道这份 PDF 是什么货色。

第 04 章讲过一条分水岭:数字生成的 PDF 里,字是字符,直接抽就有; 扫描件里字是像素,抽出来是空的。一个上传框同时接得住这两种, 而你的代码得先判断是哪一种,再分头处理。

书的做法:干脆不判断

四步,书画成了一张流程图10:

① 把 PDF 拆成一页一张图片
↓ 数字件和扫描件到这里长得一模一样了
② 每张图片各发给多模态模型一次,提示词只有一句:
"你是一个文字识别助手。把图片里所有文字都抽出来,
不要总结、不要跳过任何内容。"

③ 把每页抽出来的文字按页序拼成一份完整文本

④ 拿这份完整文本再做一次模型调用,按一张字段清单把要的东西读出来
(书那个例子读的是人:编号、姓名、年龄、城市)

图说:要看清的是第 ① 步。它把"这份 PDF 是哪一种"这个判断整个删掉了,
代价是数字件也被当成扫描件走了一遍。

第 ④ 步用的就是第 03 章那个把模型输出约束成带类型对象的做法—— 拿一张字段清单去要,而不是让它自由发挥再自己解析。

它凭什么比文字识别强:它看得见版面

书给的理由是11:多模态模型是把整张图一次看进去的,文字、表格、版面同时认。 所以它比第 05 章那个只认字的文字识别多懂一样东西——位置关系。 书给的两个例子很具体:「这个数字在『合计』那一列」「这个签名在批准栏下面」。

这正是第 05 章那个「多模态多买到的是版面」在这一章的第二次露面, 只是那里讲的是入库,这里讲的是一个用户当场上传的文件。

它和第 05 章那条分诊路线的关系:它是「不分诊」

第 05 章给的是一条分诊路线:纯文字文档走便宜的文字识别, 混合内容走贵的多模态,量大了再上一个自动分诊器。 这一节的做法是把那条分诊线整个拿掉,一律走贵的那条。

第 05 章的分诊这一节的做法
要不要判断这份文档是哪一种,判错了会白花钱或者一个字也抽不出来不判断
代码有几条路两条,外加一个分诊器一条
每页的钱便宜那条几乎不要钱一律按贵的那条算
什么时候它是对的量大、文档类型稳定、有人维护那个分诊器量小、类型杂、想尽快做出来

书自己的原话是11:

转成图片建立了一条统一的处理流水线。扫描件和数字件都变成图片, 所以一条代码路径通吃。这简化了开发,但增加了成本, 因为你处理的是像素而不是文本层。

「文本层」就是数字生成的 PDF 里那份现成的字符。 把它渲染成图片、再让模型把字认一遍,等于把现成的东西扔掉重做一遍

这条路的价钱:书在这里给了全书最具体的一组账

两个数,一个是倍数,一个是绝对值12:

纯文字 PDF 别走这条——它比直接抽文本层贵 10 到 100 倍。

一份 100 页的文档:抽文本层 0.05 美元,走多模态 0.50 到 2.00 美元。

这组数要和第 05 章那个「100 倍」分清楚,它们不是同一件事:

第 05 章那个 100 倍这里这一组
比的是谁和谁文字识别 对 多模态直接抽文本层 对 多模态
单位每页的单价比整份 100 页文档的总账
出自原书哪一章第 3 章第 11 章

书自己没有把这两处放在一起,所以它从没说破一件事: 从最便宜的一路走到最贵的一路,这两个倍数是叠着的。

什么时候走这条、什么时候别走

书给了两组判据,而反面那一组比正面那一组更值钱12:

走这条别走这条
文档里文字、表格、图片混在一起(发票、表单、论文)纯文字 PDF——贵 10 到 100 倍,而且什么也没多买到
扫描件,根本没有文本层可抽要求亚秒级响应的实时场景——每页一次模型调用,快不了
版面本身影响意思(哪个数落在哪一列)每天几千页——成本随页数线性涨,没有规模效应

还有一条书单列了:数字原生的文档(用文字处理软件、排版系统或者浏览器生成的), 如果里面只有段落和标题,走多模态一点好处都没有。

抽出来的东西必须校验,书给了三道闸

这一段是这一节最该带走的,因为它是全书唯一一处讲「抽取结果怎么验」13

先看现象: 多模态模型偶尔会编出一个字段值, 或者在页面被旋转、扫描质量差的时候整段跳过而抽取阶段编出来的错值,比回答阶段编出来的危险得多——它会被当成真的存进库里 (第 05 章讲过同一件事,那里说的是「抽文字的时候编数字」)。

三道闸,按书给的顺序:

它拦的是什么
拿一张字段清单去核(就是第 03 章那个做法)抽出来的东西根本不是那个形状:少字段、类型不对
字段存在性检查 + 取值范围检查形状对、值荒唐:年龄 220 岁、城市那一栏里是一串日期
低置信度的进人工复核队列上面两道都过了、但模型自己也没把握的那些,交给人看

书的原话里有半句最该记住13:「在写进下游系统之前, 永远要拿预期的字段清单去校验抽出来的数据。」 注意「写进下游系统之前」——它划的是一道闸的位置,不是一句提倡。

另起一处走查:一份 100 页的扫描合同走完这条流水线

这一节的机制落不到本章那条天气问答的主走查上——那个应用根本没有上传这一步, 所以另起一处。

(四步流程、10 到 100 倍、0.05 美元对 0.50 到 2.00 美元、三道校验都是书里的; 下面的页数分布、字段值、日期都是我们为演示编的。)

一份 100 页的供应商合同扫描件,拖进上传框:

① 拆图:100 页 → 100 张图片
其中 82 页是纯文字条款、15 页有表格、3 页是手写签字页
↑ 这一步不区分它们,那 82 页也照样被渲染成了图片

② 逐页认字:100 次模型调用
第 7 页(表格页)抽出来:
"服务项 | 单价 | 数量 | 合计
巡检 | 1 200 | 12 | 14 400"
← 文字识别在这一页上会把四列串成一行;这里列的归属保住了
第 63 页(手写签字页)抽出来:
"批准人:(手写签名) 日期:2025-03-11"
← 这一页抽文本层会得到空字符串,因为上面根本没有字符

③ 拼成一份完整文本:约 18 万字符(按一页约 1 800 字符算)

④ 一次模型调用,按字段清单读出来:
{"合同编号": "SUP-2025-0417", "对方": "Muster GmbH",
"年费": 172800, "解约通知期": 90, "自动续约": true}

⑤ 三道闸:
字段清单 → 五个字段齐全、类型都对 ✓ 过
取值范围 → 解约通知期 90 天,落在 0 到 365 之间 ✓ 过
年费 172 800,和第 7 页那张表对得上(每月合计 14 400 × 12 个月)✓ 过
置信度 → "自动续约: true" 模型自己标了低置信 ✗ 进人工队列

这一次的账:
走这条流水线 100 页 → 0.50 到 2.00 美元
直接抽文本层 100 页 → 0.05 美元
但那 3 页手写签字页会抽出 3 个空字符串

→ 取舍就在这两行之间:多付 0.45 到 1.95 美元,买的是那 3 页
加上第 7 页那张表的列归属。
如果这 100 页全是数字生成的纯文字条款,这笔钱就是白花的。

判断(我们的,不是书里的):这一节和第 05 章那条分诊路线,书没有当成一次冲突来处理, 而它其实是全书「先简后繁」那条原则的一个例外。 全书别处都是「先上最简单的一级,看到具体失败再加下一级」; 这一节反过来,一上来就用最贵的那一级,理由是省掉分诊那一层代码。 两边都对,只是适用面不同:分诊划算在量大,不分诊划算在量小、类型杂。 如果错,会错在: 如果你的上传量从一天几十份涨到一天几千份, 这一节的做法就会变成账单上最大的一项,而书的反面判据里正好写了这一条。 判据是:把「一天多少页 × 每页的钱」算出来,和加一个分诊器的开发时间比一比。

8. 让模型写查询语句:三种可预测的失效,和一条更好的路

这一节是全书最后一个技术话题,而它的结论指回了第 13 章。

做法

书的做法是14: 先把数据库的文档和数据模型全部换成那些数、存进向量库; 用户提问时先按意思搜出相关的那部分数据库文档, 用它让模型生成合适的查询语句;执行;再把取回的数据连同提示交给模型生成答案。

书顺手做了一处很好的术语纠偏14: 这个框架把上面第一步叫「训练」,而书明说:「这一步并不训练任何机器学习模型; 它只是把文档预处理之后存进向量库。」

喂给它的四类素材,书按有效性排了序15:

素材是什么
建表语句表、列、键的结构定义
文字说明人写的:这张表什么意思、这个字段的业务口径是什么、有什么约束和惯例
查询语句示例反映常见的访问模式
问题和查询语句的配对书说这是最有效的一种

三种可预测的失效

这一段很值钱,因为三种都不是「偶尔出错」,而是有固定形状的16:

失效具体长什么样
含糊的问题会猜错指标用户问「给我看看趋势」——模型没有业务语境,不知道你说的趋势是销量、毛利还是复购
跨五张以上表的连接经常超时,或者返回错误的结果
文档不足会编出不存在的字段模型编出 customer_lifetime_value 这种看着合理但不存在的列名,而实际的列叫 clv

第三条尤其危险,因为它的失败方式是「查询语句执行报错」还算好的—— 更糟的情况是它编出一个碰巧存在、但含义不同的列名,于是你拿到一个不报错的错答案。 (后半句是我们补的,书只说了编造列名这件事。)

什么时候别用

书给了三条17:

  • 用户不懂查询语句、又问得含糊时——他们没法验证生成出来的那条查询对不对;
  • 需要数据库里没有的业务逻辑时;
  • 需要确定性行为以满足合规或审计留痕时——模型的输出会变。

全书最后一个技术建议,指回了第 13 章

这一段是全书的收束,值得原样记住18:

第 8 章那个 agent 做法能更好地处理这些情况。 把 10 到 20 条查询模板定义成带参数的工具,让模型选用哪个模板、填什么参数。 这样你既有确定性的查询和清楚的审计留痕,又能用自然语言输入。

生产系统从 agent 做法开始。只有当用户明确要求、而且你的数据库文档扎实时, 才加上自由形式的让模型写查询。 混合做法——常见查询走模板、临时探索才让模型自由发挥——对分析团队很好用。

把这一段和第 13 章那一节并排看: 第 13 章讲的是「模型只负责决定调哪个函数、传什么参数,程序负责执行」。 而这里的「模板」就是那个函数,「填参数」就是那个参数。 同一个机制,在全书最后一节被当成了压轴的建议。

9. 承重词二:容器 —— 把「在我机器上能跑」固定下来

这一章第二个承重词。一句话先给结论:容器就是把你的程序连同它需要的一切 (语言运行时、所有依赖包、启动命令)打成一个包, 这个包在任何装了容器运行时的机器上跑起来都一模一样。

它解决什么

没有容器:
你的机器: Python 3.9,某个库是 2.1 版,某个系统组件装过
服务器: Python 3.12,那个库是 3.0 版,那个系统组件没有
→ "在我机器上能跑"

有了容器:
一份配置文件写死:从哪个基础镜像开始、装哪些依赖、开哪个端口、怎么启动
→ 打成一个镜像 → 这个镜像跑在哪儿都一样

书给的那份配置很短19:从一个精简的 Python 基础镜像开始 → 设定工作目录 → 装依赖 → 声明要开放的端口 → 指定启动命令。 然后一条命令构建成镜像,一条命令在本地跑起来验证。

「镜像」是那个打好的包,「容器」是这个包跑起来之后的那个实例—— 一个镜像可以同时跑起很多个容器。

推上云:五步,和一个每月的数

书给的路径是20:装云厂商的命令行工具 → 建一个镜像仓库 → 把镜像推上去 → 写一份任务定义指向那个镜像 → 部署到一个按用量计费的托管容器运行时。 书也说了另外两家云的对应产品是什么。

那个「按用量计费的托管运行时」的意思是:你不必自己管服务器, 只按容器实际占用的处理器和内存时长付钱。

而书给了一个很实在的月度成本21:

一个 1 核处理器、2 GB 内存的容器 7×24 跑着,每月大约 30 到 40 美元。

跟着的是一条警告21:

谨慎开自动扩缩容。流量突增时,不加限制的扩容会产生意料之外的账单。 在服务配置里设最大任务数上限。

这个数值得和第 03 章那些价目对照着看: 每月 30 到 40 美元是「机器一直开着」的固定成本,和你被问了多少次无关; 而模型调用是按次算的。一个没人用的原型,前者照付,后者为零。 (这个对照是我们做的,书没有把两处放在一起。)

10. 上线之后还缺的三层

这是全书正文的最后一段内容,而且三层各挡一种具体的事故22

三层各是什么

它是什么它挡的那种事故
基础设施即代码用配置文件把「云上开了哪些东西、怎么配的」写下来,并纳入版本管理配置漂移:你手工点控制台改了生产,开发环境就慢慢和它分岔了
自动构建与部署每次往主干提交,自动跑测试、构建镜像、推仓库、更新服务;测试不过就挡住部署手工部署的人为错误:忘了跑测试、从错的分支构建、跳过一个迁移脚本
密钥管理密钥不写进镜像,由运行时在启动时注入镜像泄漏 = 密钥泄漏,而且轮换要重建并重新部署每一个镜像

第三层值得多说两句

书给的推理链很完整,而且这是全书唯一一处认真谈安全的地方22:

① 镜像存在镜像仓库里,**任何有访问权的人都看得到**

② 如果镜像泄漏(仓库权限配错,或者构建流水线的凭据被攻破)

③ 攻击者直接拿到你的接口密钥

④ 而你要轮换密钥,**意味着重建并重新部署每一个镜像**

→ 用密钥管理服务,你在一个地方轮换就行。

图说:注意第 ④ 步 —— 它不是"泄漏之后的补救成本",
它是**日常的**成本。密钥定期轮换是基本要求,
硬编码等于把每次例行轮换都变成一次全量重新部署。

这一节兑现了总纲第 4 节那处交代:原书把「密钥放哪儿」放在第 1 章的环境搭建里, 我们特意挪到这里,和「硬编进镜像的后果」一起讲才完整。

收尾的分寸,书拿捏得很好

最后一句23:

对原型来说,手动部署没问题。 当你确信这个应用会长期运行、当多个开发者需要部署、或者合规要求审计留痕时,再加这三层。

这是全书那个句式的最后一次露面(第 10 章三次、第 11 章一次、第 12 章一次)。 第 19 章把九处收在一起,并给它起个名字。

11. 主走查:天气问答,从一次提问走到每月账单

这是本章的主走查,前面每个机制都在它上面占一步。 用的是书那个跑例3

(那条四步流程、「检索器是两次接口调用」、50 个并发、100 到 500、 每月 30 到 40 美元、三层的内容,都是书里的; 下面的具体问句、经纬度、温度、行数、并发数演算是我们为演示编的。)

第 1 步:一次提问,走完全程

用户在页面上打字:"我明天去慕尼黑,要带伞吗?"

① 模型看一眼这句话:里面有地名吗?
→ 有,抽出 {"city": "München", "country": "Germany"}
→ 如果没有地名,到这里就得停下并反问 —— 天气接口必须要一个地点

② 第一次接口调用(地名转坐标):
"München, Germany" → 纬度 48.1374,经度 11.5755

③ 第二次接口调用(取天气):
纬度 48.1374,经度 11.5755
→ {"temperature_2m": 14.3, "precipitation_probability": 78, ...}

④ 把这段数据连同原问题塞进提示,交给模型:
"慕尼黑明天气温约 14 度,降水概率 78%,建议带伞。"

↑ 整个过程里,"检索"就是第 ② ③ 步这两次接口调用。
没有向量库,没有那 1 536 个数,没有相近程度。

第 2 步:界面这一层发生了什么

用户每敲一次回车:
整个脚本从第一行重跑到最后一行
导入 → 读会话状态里的对话记录 → 渲染历史消息 →
跑上面那四步 → 把新的一问一答追加进会话状态 → 渲染

这就是为什么它开发起来简单:不存在"哪块该刷新"的问题,每次都全刷。

第 3 步:换成两个浏览器窗口再试一次

窗口 A:问"慕尼黑明天要带伞吗" → 得到答案
窗口 B:刷新页面

── 如果对话记录存在会话状态里 ──
B 看到的是空白 —— 正确

── 如果代码里图省事,把对话记录存成了模块级的一个全局列表 ──
B 看到 A 刚才问的那句话 ← **泄漏**
而这个 bug 在单窗口测试下**永远不会出现**

第 4 步:算一算它能撑多少人

假设每次交互脚本重跑一遍要占 0.4 秒的处理器时间,机器有 2 核:

每秒能处理的交互次数 ≈ 2 ÷ 0.4 = 5 次

50 个用户,每人每 10 秒操作一次 → 每秒 5 次 ← 刚好顶满
100 个用户 → 每秒 10 次 ← 排队,页面开始卡

→ 和书给的"超过 50 个并发就成瓶颈"对得上。

把模型调用和数据库操作卸载给独立服务之后,
这个脚本每次只剩渲染,重跑一次可能只要 0.05 秒
→ 每秒能处理 40 次 → 撑到几百人 ← 和书说的 100 到 500 对得上

(0.4 秒、0.05 秒、2 核都是为演示编的;50 和 100–500 是书里的。)

第 5 步:打包、推上云、算账

① 写一份容器配置:基础镜像 + 装依赖 + 开端口 + 启动命令
② 构建成镜像,在本地跑一遍验证:打开本地地址,界面出来了 → 可以部署
③ 建镜像仓库 → 推上去 → 写任务定义 → 部署到按用量计费的托管运行时

每月账单:
容器(1 核 / 2 GB,7×24) 30 到 40 美元 ← 固定,没人用也照付
模型调用(每次问答约 1 500 token) 按次算 ← 没人用就是 0
两个天气接口 免费额度内

→ 一个没人用的原型,每月大约 35 美元。
这个数值得记住:它是"把东西挂在线上"这件事本身的价钱。

第 6 步:还缺的三层,各自会在什么时候咬你

没有基础设施即代码:
三个月后要排查一个生产问题 → 你得手工点遍控制台,回忆当初怎么配的
而这三个月里,你手工改过的那几处让开发环境和生产慢慢分岔了

没有自动构建与部署:
某个周五下午手工部署 → 忘了跑测试 → 周一早上用户告诉你搜不到东西了

没有密钥管理:
密钥硬编在镜像里 → 例行轮换一次 → 要重建并重新部署每一个镜像

图说:三种事故的共同点是"上线当天不会发生"。
这就是书说"原型阶段先不加、确信要长期运行时再加"的原因。

12. 作者的判断与证据

说法它是什么
「大多数生成式 AI 项目从未上生产」作者的观察,没有数据来源。 但它是这一整章取舍的前提
「检索器就是两次接口调用」是代码事实,而且是全书对「检索不等于向量搜索」最直白的一次演示
「每次交互重跑整个脚本」是框架的实现事实
「超过 50 个并发就成瓶颈」「卸载后能撑 100 到 500」作者给的经验数,没有测量条件(什么机器、什么应用,一个字没有)
「几小时做原型,几周重构」作者的经验估算,没有依据
什么时候别用它(三条)作者的经验判断,和各框架的定位一致
「状态在用户之间泄漏」是机制事实,而且**「用两个浏览器窗口测」是全章最可操作的一条建议**
三条工程纪律(脚本 100 行、卸载重活、别用打印调试)作者的经验做法,没有依据
「转成图片建立了统一流水线,但处理的是像素不是文本层」是机制事实,而且是这本书对「统一」这件事最诚实的一次定价
「多模态模型同时认文字、表格和版面」是对这类模型的准确描述;那两个位置关系的例子是作者举的
「贵 10 到 100 倍」「100 页 0.05 对 0.50 到 2.00 美元」作者给的数,没有测量条件(哪一档模型、多大的页,一个字没有)。当量级看
抽取结果那三道闸是工程做法,不是判断;三道各拦一种具体的坏值,而且给了执行位置(写进下游系统之前)
「模型偶尔编字段值、或者在页面旋转时整段跳过」作者的经验观察,没有失败率
「这一步不训练任何机器学习模型」是准确的术语纠偏,难得
四类素材,以及「问题—查询配对最有效」作者的判断,没有对照实验
三种可预测的失效是机制推导 + 经验;第三条给了具体的编造列名例子,很实
「跨五张以上表的连接经常超时」作者的经验数,没有来源
「生产系统从 agent 做法开始」作者的判断,没有实验。 但它是全书最后一个技术建议,而且指回了第 8 章
每月 30 到 40 美元是按公开计价算的,可核对
「不加限制的扩容会产生意外账单」是事实,常见事故
三层各挡什么事故是事实描述,每一条都给了具体的失败场景
「原型手动部署没问题」作者的判断,和全章的取舍一致

13. 边界与局限

  • 50、100–500 这几个数没有测量条件。 什么机器、应用多重、每次交互算多久, 一个字没有,所以只能当量级看;
  • 没有讲怎么监控线上。 上线之后怎么知道它现在好不好—— 第 16 章讲了评测,但「跑在线上的时候拿什么盯着」这一章没接上;
  • 没有讲版本更新怎么办。 知识库换了、换算模型升级了,线上那套怎么平滑切过去, 全书没有;
  • 没有讲访问控制。 谁能看哪些文档这件事,第 09、10 章讲过它在查询语句里长什么样, 但上线之后用户身份从哪来、怎么和那道闸接上,这一章没写;
  • 成本只算了容器那一项。 向量库要不要钱、存储要不要钱、 一个真实系统每月总共多少钱,全书没有一处汇总;
  • 三层只给了「是什么」和「不做会怎样」,没给「怎么做」。 这是全书唯一一处点到即止的地方;
  • 上传那条流水线没有讲多大的文件会崩。 100 页要 100 次模型调用, 那 1 000 页呢?书说了「每天几千页别用」,但没说单份文档的上限在哪、 超了会超时还是会被截断;
  • 「低置信度」没有定义。 那三道闸里第三道说「低置信度的进人工复核队列」, 可这个置信度从哪儿来、多低算低,书一个字没有—— 而这恰恰是三道闸里唯一一道需要额外机制才做得出来的;
  • 那个天气例子里的反问逻辑没有展开。 书说了「没有地名就答不了」, 但「答不了的时候该怎么回」——直接报错、反问用户、还是给个默认值,书没讲。

14. 可带走的

  1. 「大多数生成式 AI 项目从未上生产」——所以原型阶段该优化的是前期投入,不是可扩展性;
  2. 选界面形式之前先想清楚:仪表盘和搜索界面往往比聊天界面更快也更直观;
  3. 检索器可以只是两次普通的接口调用。 那个天气应用完整符合这套做法的定义, 却没有向量库、没有那 1 536 个数;
  4. 快速搭界面那类工具省事的原因就是它的天花板:每次用户交互都把整个脚本重跑一遍。 不存在「哪块该刷新」的问题,代价是每次点击都付一遍全脚本的开销;
  5. 量化的天花板:超过 50 个并发就成瓶颈; 把重活卸载给独立服务能撑到 100 到 500;再往上要整个重写;
  6. 最要命的坑是状态在用户之间泄漏,而单窗口测试在结构上就发现不了它。 用两个浏览器窗口测;
  7. 三条工程纪律:脚本控制在 100 行以内、重活部署成独立服务、别用打印语句调试;
  8. 用户上传的 PDF,书的做法是一律拆成图片逐页交给多模态模型。 它买的是「一条代码路径通吃扫描件和数字件」,不必先判断这份是哪一种;
  9. 这条路的价钱:比直接抽文本层贵 10 到 100 倍;100 页文档 0.05 美元对 0.50 到 2.00 美元。 纯文字 PDF、亚秒级响应、每天几千页——这三种情形别走它;
  10. 它和第 05 章那条分诊路线是对立的两种选择,而两边都对: 分诊划算在量大、类型稳定;不分诊划算在量小、类型杂、想尽快做出来;
  11. 抽出来的东西必须过三道闸:拿字段清单核形状 → 字段存在性与取值范围核值 → 低置信度的进人工复核队列。 闸的位置是「写进下游系统之前」;
  12. 让模型写查询语句有三种可预测的失效: 含糊问题猜错指标 / 跨五张以上表的连接超时或出错 / 文档不足时编出看着合理但不存在的列名;
  13. 全书最后一个技术建议指回了 agent 那一章:把 10 到 20 条查询模板定义成带参数的工具, 让模型选模板、填参数——既有确定性和审计留痕,又能用自然语言输入;
  14. 容器 = 把程序连同它需要的一切打成一个到处都能跑的包。 镜像是那个包,容器是它跑起来的实例;
  15. 每月的钱有一个具体数:1 核 2 GB 的容器 7×24 跑着,约 30 到 40 美元。 这是「挂在线上」本身的价钱,和有没有人用无关;
  16. 自动扩缩容一定要设上限,否则流量突增会产生意外账单;
  17. 还缺的三层,各挡一种具体事故: 基础设施写成文件挡配置漂移; 自动构建与部署挡忘了跑测试、从错的分支构建; 密钥不进镜像挡「镜像泄漏 = 密钥泄漏」,并且让轮换不必重建每一个镜像;
  18. 三层什么时候加:确信应用会长期运行、多个开发者要部署、或者合规要求审计留痕的时候。

15. 原文地图

主题原书章原文位置
大多数项目从未上生产Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:4(搜「most never reach production」)
每次交互重跑整个脚本Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:8(搜「reruns your entire script on every user interaction」)
别默认做成聊天机器人Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:16(搜「Dashboards and search interfaces are often faster」)
50 个并发的瓶颈Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:57(搜「Beyond 50 concurrent users」)
什么时候别用它Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:59(搜「Don't use it for external apps with thousands of users」)
几小时对几周、卸载后撑 100–500Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:61(搜「prototype a working RAG app in hours」) · text/36-ch11-chapter-11-rag-web-apps.txt:63(搜「This hybrid approach works for 100–500 users」)
检索器是两次接口调用Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:161(搜「the retriever is represented by two API calls」)
状态泄漏那条坑Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:337(搜「state leaking between users」)
三条工程纪律与调试Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:339(搜「keep the Streamlit script under 100 lines」) · text/36-ch11-chapter-11-rag-web-apps.txt:341(搜「debugger」)
PDF 拆页成图那四步Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:361(搜「split the PDF into individual page images」) · text/36-ch11-chapter-11-rag-web-apps.txt:401(搜「You are an OCR assistant」)
按字段清单抽实体Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:447(搜「Extract all people entities」)
看得见版面、一条代码路径通吃Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:497(搜「recognize text, tables, and layout simultaneously」) · text/36-ch11-chapter-11-rag-web-apps.txt:499(搜「one code path handles everything」)
10 到 100 倍与 100 页那笔账Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:501(搜「10 to 100 times more expensive」) · text/36-ch11-chapter-11-rag-web-apps.txt:503(搜「$0.50–$2.00 for multimodal OCR」)
抽取结果那三道闸Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:506(搜「hallucinate field values or skip sections」)
让模型写查询的流程与术语纠偏Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:528(搜「semantic similarity search to find relevant」) · text/36-ch11-chapter-11-rag-web-apps.txt:551(搜「This does not train an ML model」)
四类素材Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:561(搜「DDL statements」)
三种可预测的失效Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:684(搜「hallucinated column names」)
什么时候别用它Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:686(搜「they can't validate generated queries」)
全书最后一个技术建议Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:688(搜「The agentic approach (Chapter 8) handles these cases better」) · text/36-ch11-chapter-11-rag-web-apps.txt:690(搜「start with the agentic approach」)
容器是什么、五步部署Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:709(搜「Docker is a containerization platform」) · text/36-ch11-chapter-11-rag-web-apps.txt:711(搜「Elastic Container Registry」)
每月 30 到 40 美元与扩容警告Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:779(搜「$30 to $40 per month」)
还缺的三层Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:785(搜「Production systems need three additional layers」) · text/36-ch11-chapter-11-rag-web-apps.txt:789(搜「IaC prevents configuration drift」) · text/36-ch11-chapter-11-rag-web-apps.txt:793(搜「Manual deployment introduces human error」) · text/36-ch11-chapter-11-rag-web-apps.txt:797(搜「Don't hardcode API keys in the Docker image」)
什么时候加这三层Chapter 11. RAG Web Appstext/36-ch11-chapter-11-rag-web-apps.txt:799(搜「For prototypes, manual deployment is fine」)

Footnotes

  1. 出处:「Chapter 11. RAG Web Apps」第 4 段(text/36-ch11-chapter-11-rag-web-apps.txt:4,搜「most never reach production」)。同一段还写了这一章的定位:让数据科学家在投入生产基础设施之前,先做出真实用户能试用的东西。

  2. 出处:同章第 16 段(text/36-ch11-chapter-11-rag-web-apps.txt:16,搜「Dashboards and search interfaces are often faster」)。这是一个提醒框,和第 1 章那条「先想清楚这套东西该不该上」是同一个方向的提醒。

  3. 出处:同章第 161 段(text/36-ch11-chapter-11-rag-web-apps.txt:161,搜「the retriever is represented by two API calls」),两次调用分列其后;先判断有没有地名那一步见第 168 段(text/36-ch11-chapter-11-rag-web-apps.txt:168,搜「A location is required for the weather API」)。走查里的经纬度、温度、降水概率是我们为演示编的。 2

  4. 出处:同章第 6 段(text/36-ch11-chapter-11-rag-web-apps.txt:6,搜「a single script that covers the frontend and backend」)。

  5. 出处:同章第 8 段(text/36-ch11-chapter-11-rag-web-apps.txt:8,搜「reruns your entire script on every user interaction」)。

  6. 出处:同章第 57 段(text/36-ch11-chapter-11-rag-web-apps.txt:57,搜「Beyond 50 concurrent users」)与第 63 段(text/36-ch11-chapter-11-rag-web-apps.txt:63,搜「This hybrid approach works for 100–500 users」)。书没有交代这两个数是在什么机器、什么应用上得出的。 2

  7. 出处:同章第 59 段(text/36-ch11-chapter-11-rag-web-apps.txt:59,搜「Don't use it for external apps with thousands of users」)与第 61 段(text/36-ch11-chapter-11-rag-web-apps.txt:61,搜「prototype a working RAG app in hours」)。 2

  8. 出处:同章第 337 段(text/36-ch11-chapter-11-rag-web-apps.txt:337,搜「state leaking between users」)。「三种写法在单窗口下表现一样」是我们补的推导,书只给了那个 A/B 两用户的问句。会话状态那个「先检查有没有」的坑见第 333 段(text/36-ch11-chapter-11-rag-web-apps.txt:333,搜「session_state」)。

  9. 出处:同章第 339 段(text/36-ch11-chapter-11-rag-web-apps.txt:339,搜「keep the Streamlit script under 100 lines」)与第 341 段(text/36-ch11-chapter-11-rag-web-apps.txt:341,搜「debugger」)。原文说打印语句在重跑时会乱序出现,所以要用编辑器自带的调试器,并给了具体的配置写法。

  10. 出处:「Chapter 11. RAG Web Apps」第 361 段(text/36-ch11-chapter-11-rag-web-apps.txt:361,搜「split the PDF into individual page images」)。四步顺序原文写在同一段里:拆页成图 → 逐页用多模态模型抽字 → 拼成一份完整文本 → 在完整文本上做一次实体抽取。那句只有一行的提示词见第 401 段(text/36-ch11-chapter-11-rag-web-apps.txt:401,搜「You are an OCR assistant」);第 ④ 步要的那张字段清单(编号、姓名、年龄、城市)见第 447 段(text/36-ch11-chapter-11-rag-web-apps.txt:447,搜「Extract all people entities」)。

  11. 出处:同章第 497 段(text/36-ch11-chapter-11-rag-web-apps.txt:497,搜「recognize text, tables, and layout simultaneously」),「合计那一列」「签名在批准线下面」两个例子也在这一段;「一条代码路径通吃」那一段见第 499 段(text/36-ch11-chapter-11-rag-web-apps.txt:499,搜「one code path handles everything」)。原文说模型内部处理图像的那部分结构叫 vision transformer,书只给了名字、没有解释,我们也不在这里展开——它不影响这一节任何一条判断。 2

  12. 出处:同章第 501 段(text/36-ch11-chapter-11-rag-web-apps.txt:501,搜「10 to 100 times more expensive」)给正反两组判据与那个倍数;100 页文档那笔账见第 503 段(text/36-ch11-chapter-11-rag-web-apps.txt:503,搜「$0.50–$2.00 for multimodal OCR」),「数字原生文档没好处」也在这一段。书没有交代这两个数是在哪一档模型、多大的页面上算的。 2

  13. 出处:同章第 506 段(text/36-ch11-chapter-11-rag-web-apps.txt:506,搜「hallucinate field values or skip sections」)。三道闸(拿字段清单校验、字段存在性与取值范围检查、低置信度进人工复核队列)全部写在这一段里。「抽取阶段编的比回答阶段编的危险」是我们在第 05 章下过的判断,书这里没有重复。 2

  14. 出处:同章第 528 段(text/36-ch11-chapter-11-rag-web-apps.txt:528,搜「semantic similarity search to find relevant」)讲流程;术语纠偏见第 551 段(text/36-ch11-chapter-11-rag-web-apps.txt:551,搜「This does not train an ML model」)。 2

  15. 出处:同章第 555 段起的表 11-1,四类分列其后;第一类那一行在第 561 段(text/36-ch11-chapter-11-rag-web-apps.txt:561,搜「DDL statements」),「问题—查询配对最有效」那一行在第 581 段(text/36-ch11-chapter-11-rag-web-apps.txt:581,搜「most effective training method」)。

  16. 出处:同章第 684 段(text/36-ch11-chapter-11-rag-web-apps.txt:684,搜「hallucinated column names」)。三种失效都写在这一段里,包括那个 customer_lifetime_valueclv 的例子。「更糟的是编出一个碰巧存在但含义不同的列名」是我们补的。

  17. 出处:同章第 686 段(text/36-ch11-chapter-11-rag-web-apps.txt:686,搜「they can't validate generated queries」)。

  18. 出处:同章第 688 段(text/36-ch11-chapter-11-rag-web-apps.txt:688,搜「The agentic approach (Chapter 8) handles these cases better」)与第 690 段(text/36-ch11-chapter-11-rag-web-apps.txt:690,搜「start with the agentic approach」)。这是原书正文里最后一个技术建议,而它指回的是第 8 章。

  19. 出处:同章第 709 段(text/36-ch11-chapter-11-rag-web-apps.txt:709,搜「Docker is a containerization platform」);那份配置文件与两条命令见第 730 到 760 段一带(text/36-ch11-chapter-11-rag-web-apps.txt:757,搜「docker run -p 8501:8501」)。补充(不在书里,来自通用知识):「镜像」是构建出来的那个只读的包,「容器」是这个包被启动之后的运行实例,一个镜像可以同时启动很多个容器。

  20. 出处:同章第 711 段(text/36-ch11-chapter-11-rag-web-apps.txt:711,搜「Elastic Container Registry」)。书还给了另外两家云的对应产品名。

  21. 出处:同章第 779 段(text/36-ch11-chapter-11-rag-web-apps.txt:779,搜「$30 to $40 per month」)。自动扩缩容那条警告和「设最大任务数上限」也在这一段。 2

  22. 出处:同章第 785 段(text/36-ch11-chapter-11-rag-web-apps.txt:785,搜「Production systems need three additional layers」);配置漂移见第 789 段(text/36-ch11-chapter-11-rag-web-apps.txt:789,搜「IaC prevents configuration drift」);手工部署的人为错误见第 793 段(text/36-ch11-chapter-11-rag-web-apps.txt:793,搜「Manual deployment introduces human error」);密钥那一段见第 797 段(text/36-ch11-chapter-11-rag-web-apps.txt:797,搜「Don't hardcode API keys in the Docker image」)。 2

  23. 出处:同章第 799 段(text/36-ch11-chapter-11-rag-web-apps.txt:799,搜「For prototypes, manual deployment is fine」)。