主题
第 13 周 · Day 7:阶段三里程碑验收——五周的东西串成一条链才算数
对应手册任务:学习「阶段三里程碑验收 + 周复盘」,动手「确认:Python + LLM API + 结构化输出 + FastAPI + LangGraph + ReAct + 工具调用 + 多 Agent + HITL 全链路」,当日产出:里程碑项目 v3 + 周记。 本篇解决的问题只有一个:第 9 到第 13 周攒下的家当,能不能从用户一句话进、高危操作卡一道人工审批、回答逐字出,全程不断链。断在哪,今天找出来,今天修掉。
今日目标
- 逐项过完 8 条验收清单,每条都是真实动作加留档证据,全绿才算阶段三毕业
- 画架构图 v3:在 Day 1 的客服系统图上标注 FastAPI 边界、checkpoint 存储、审批流
- 写一份 STAR 记录 v3 和一篇 300 字周记,git 打双 tag 收口
概念讲解:里程碑验收和周复盘差在哪
先搬老规矩(出处见第 1 周 Day 7):识别不等于提取。「这些我都学过」这句话从来不可信,可信的只有动作和证据。今天方法不变,变的只是规模。
差别在容错空间。周复盘允许留尾巴:这周没吃透的,写进「还含糊」,下周补上。里程碑验收没有这个出口,因为下一个阶段要踩着这一阶段的成果往上盖:FastAPI 的流式接口、结构化输出的约束、LangGraph 的图和 checkpointer,往后每周都在用。哪一环是虚的,暴露得越晚,修起来越贵。
所以验收标准只有一种写法:动作加证据。每项清单都长这样——执行什么操作、应该观察到什么、留下什么证据。「我感觉没问题」不算数,一段日志、一张截图、一个 tag 才算数。
| 产出 | 验什么 | 对应今天 |
|---|---|---|
| 验收清单 | 全链路每一环真实可跑 | 8 项逐项打勾 |
| 架构图 v3 | 系统结构是否在脑中成形 | 标注三处的新图 |
| STAR 加周记 | 决策依据和认知变化是否沉淀 | 两篇文字 |
核心知识
1. 阶段三验收清单:8 项,逐项留证据
项 1:CLI 多轮对话记忆正确(Python + LLM API)。 翻出阶段三最早那个命令行对话脚本,不重启进程连问两句:第一句「我叫 Jerry,帮我记着订单号 A-1024」,第二句「我叫什么?刚才说的是哪单?」。通过标准:第二句不用你自我介绍,名字和订单号都答对。模型是无状态的,历史全靠客户端每轮整包塞回 messages,这条验的就是你最早写的那个拼历史的循环没丢。
项 2:结构化输出 10 次全 parse 成功。 把第 11 周的结构化输出脚本套进循环跑 10 次,每次结果过同一个 Pydantic 模型校验:
python
ok = 0
for _ in range(10):
text = structured_call(...) # 你的结构化输出调用
Route.model_validate_json(text) # 裸 API 版这样验;SDK 版已解析,直接用
ok += 1
print(f"{ok}/10") # 打出 10/10 才算过通过标准:10/10。低于 10 说明约束层有洞:要么没走严格模式,要么校验失败没接重试。补齐再跑,连着 10 次全绿为止。
项 3:SSE 网页逐字渲染(FastAPI 分层 + SSE)。 启动你的 FastAPI 服务,浏览器开对话页,另开一个终端用 curl -N 打同一个流式接口(路径按你的项目来)。通过标准:浏览器里回答一个字一个字往外蹦,不是憋几秒整段弹出;curl 侧能看到 data: 分片一行一行往下走。两头都对,说明分层没把流堵死——逐字就是流式透传没被缓冲的直接证据。
项 4:裸 ReAct 三工具自主调度(LangGraph + ReAct + 工具调用)。 把第 12 周的裸 ReAct 跑起来,挂上三个工具,问一个需要连续用上两三个工具的问题,比如「A-1024 到哪了?顺便看看它还在不在退款期」,用 stream_mode="updates" 打日志。通过标准:日志里 agent → tools → agent 的循环至少转两圈,每圈选的工具是对的,最后一跳不带 tool_calls 出 END。选工具是模型自己定的,你全程没写一个 if。
项 5:同 thread 二轮对话记得上文。 用同一个 thread_id 连问两轮:先「我的订单 A-1024 到哪了」,答完再发「这单我要退款」,第二句绝不带订单号。通过标准:退款这轮张口就是 A-1024,没让你重复。这条验的是 checkpointer 按 thread_id 存取状态那套机制,Day 2 的三问日志里你已经见过它工作。
项 6:意图路由三种意图各一例(多 Agent)。 跑本周的 Supervisor 图,三种意图各发一句:查订单类、退款类、纯闲聊。通过标准:三句分别命中 order、refund、FINISH(或你图里对应的出口),一句不串。串道的先别怀疑模型,回 Day 2 对照提示词,多半是三个选项的边界没写清。
项 7:退款必须出审批卡,且拒绝路径正常(HITL)。 发一条退款请求,看到 interrupt 吐出的审批单——接了 Web 页的就是那张审批卡,没接的控制台里 result["__interrupt__"][0].value 就是它。然后两条路都走:先点一次拒绝,再换一单(新 thread_id)点批准。通过标准:批准路径退款工具真实执行、审计日志记上一笔;拒绝路径工具一次没被碰,Agent 改口说「未通过、已转人工」。只验批准等于验了一半,拒绝才是事故率低的那半。
项 8:断工具自愈日志(容错)。 故意把一个工具改坏——实现里手动 raise,或把它的请求地址改错——再发一条会用到它的消息。通过标准:进程不崩、接口不 500,日志里异常被 try/catch 接住,Agent 要么换个姿势重试,要么老实告诉用户「查询暂时不可用」。错误信息喂回模型让它自己拿主意,这是 Day 6 的核心打法,这条验收就是确认它真的接住了。
8 项的顺序有讲究:前 3 项验上半场(Python、FastAPI、LLM API),后 5 项验下半场(LangGraph 一族)。先地基后楼,哪一项红了别跳过,先修它。
2. 不达标排查表
哪项红了,先对这张表再动手。四个根因覆盖八成的红灯,每个都只要两分钟就能确认或排除:
| 症状 | 高频根因 | 排查与修复 |
|---|---|---|
| 401、超时、Connection error,一项都跑不起来 | API key 没进当前终端的环境变量,或 base_url 拼错、网络被代理拦了 | 先在终端 echo $env:OPENAI_API_KEY(PowerShell)确认 key 在;再用 curl 直接打一次 base_url,通不了就是网络层的事,跟代码无关 |
| 多轮历史只剩最后一条,或同一轮多节点写 messages 直接抛 InvariantError | State 的 messages 裸写了 list,忘了 Annotated[list, add_messages] | 打开 State 定义看那一行。reducer 一缺,字段走覆盖语义,历史被后写冲掉。补上 Annotated 就好,第 12 周 Day 7 自测第 2 题考的就是它 |
| 每轮都像初次见面,第二句「这单退款」无所指 | thread_id 每次现造,uuid4 写在了 invoke 里头 | 全局搜 uuid。同一用户同一场会话从头到尾复用同一个 thread_id,换会话才换新值。checkpointer 按它取档案,id 一换等于开新档 |
interrupt() 不暂停,或恢复时报错 | compile 时没挂 checkpointer;或恢复那次换了 thread_id | interrupt 的现场存在 checkpointer 里,没挂它整个机制不工作。检查 compile(checkpointer=...) 传了没有;恢复必须用触发中断时的同一个 config |
对完表还没解决的,再回对应 Day 的教程,那才是真·新问题,记进周记第二段。
3. 架构图 v3:在客服系统图上标三处
底图不动,就是 Day 1 那张 Supervisor 星型图。今天在它上面加三处标注,把阶段三上半场的服务层和下半场的编排层拼进同一张图:
text
新增标注一:FastAPI 边界(一个大虚线框,把整张图圈住)
框上只开两个口:上口进 HTTP 请求,下口出 SSE 流
框旁写一句职责:请求在这里分层,路由层接流量,编排层调图
新增标注二:checkpoint 存储(一个圆柱,画在框外下方)
从框内牵一条虚线出来指向它,再从它牵一条虚线回框内
标注:每走一步存一次档,按 thread_id 区分
里面住两样东西:对话历史(多轮记忆)和断点现场(待审批单)
新增标注三:审批流(退款分支上的一条绕行线)
退款 Agent 到「发起退款」那条线中间断开,插入一个闸门:
interrupt() 出审批卡 → 审批人拍板 → Command(resume=...) 回流
批准线通向工具,拒绝线绕回 Agent 改口告知用户画完按四条自查:FastAPI 框闭合吗,只有 HTTP 进、SSE 出两个口;checkpoint 谁在读谁在写,标注里写清「每步存、恢复取、按 thread_id」了吗;审批流是绕行线不是直线,闸门卡在工具执行之前;底图和 Day 1 一致吗,星型的纪律没走样。
还是先凭记忆画
底图先默画,卡住再翻 Day 1 确认,合上继续。对着 Day 1 抄一遍再标三处,验的是复制能力,不是结构记忆。
4. STAR 记录 v3:这次的关键决策
STAR 四格:Situation 情境、Task 任务、Action 行动、Result 结果。每个里程碑留一条,写的是「当时为什么这么定」,面试和日后复盘都直接取用。v3 记本周最值钱的决策——调度层怎么搭:
text
S:阶段三收官项目是客服系统,一个入口要接住查订单、退款、闲聊三类
诉求,其中退款动钱,必须可审计、可拦截。
T:定调度架构——意图谁来判、工具怎么挂、高危操作怎么管,一次定下
来不返工。
A:对比关键词 if/else、全能单 Agent、Supervisor 三条路,选 Supervisor
星型。判意图用小模型加结构化输出的硬路由:出口写死在
Literal["order", "refund", "FINISH"] 里,模型做选择题不做问答题。工具
按职责分家:订单 Agent 只挂只读工具,退款 Agent 独占发起退款,工具
前面插 interrupt() 审批。
R:三种意图路由全对;退款工具退款 Agent 之外谁也碰不到,审批卡一张
没漏;后来加闲聊 Worker,只动 Literal、节点、条件边、提示词、回边五
处,改动没扩散。重点解读 Action 里两个「为什么」,这是面试官最可能追问的地方。
为什么 Supervisor 用小模型硬路由:路由是三选一的分类题,不是开放问答。合法出口写进 Literal,经 with_structured_output 变成 schema 下发,模型想发明第四个去处都没有出口,Pydantic 校验再兜一层底。分类这件事用不着大模型:小模型便宜、快、行为可测,大模型做软路由(Command(goto=...) 那条路)贵、慢,行为还随版本漂。判断口诀就一句:能枚举就硬,枚举不了再软。
为什么 Worker 按职责分工具:两个理由,一个效率一个安全。工具全挂一张提示词下,候选池越大模型选错率越高,Day 1 的拆分信号二说的就是它;更关键的是权限隔离——只读工具和高危工具分开挂,退款 Agent 之外谁也调不动钱,出了问题追责一条链。权限这层收益,光看 demo 看不出来,出一次事故就懂了。
5. 300 字周记:第 13 周版
四段规矩不变:最大收获、卡得最久、还含糊、下周前补。本周示例,照这个密度写:
text
① 本周最大收获:多 Agent 没有新 API,就是第 12 周那张 StateGraph 多挂
几个节点。Supervisor 把「派活」变成 Literal 锁死的选择题,Worker 各管
一摊,权限隔离才是拆分真正的红利。
② 卡得最久:interrupt 恢复后日志里 refund_node 打印了两次,以为是
bug,查了半天才知道「节点从头重跑」本来就是语义,interrupt 前的代码
执行两遍。把工具调用挪到它后面,日志就干净了。
③ 还含糊:checkpointer 的快照里到底存了什么结构;Day 6 的重试次数
拍脑袋定的 2 次,说不出依据。
④ 下周前补:把验收项 4 的三工具日志再跑一遍,数清楚每个 superstep 状
态怎么变;翻 persistence 文档的快照结构一节。动手任务:完成验收加复盘
五步,预计 90 到 120 分钟。里程碑日比平常周长一半,正常。先说清一件事:里程碑项目 v3 不是今天新写的东西,它就是本周一路搭起来的客服系统,今天做的是发版验收。v3 的 v 是阶段编号,前两个版本在你自己仓库里躺着,今天交第三个。
第一步:过验收清单(40 分钟)。 8 项按顺序跑,每项留证据:日志贴进验收记录 md,截图存笔记目录。红了的先查排查表,修完重跑该项,别攒到最后一起修。
第二步:画架构图 v3(20 分钟)。 关掉教程,凭记忆默画 Day 1 底图,再补三处标注。导出 PNG,命名 milestone-v3-architecture.png,.excalidraw 源文件一起留。
第三步:写 STAR 记录 v3(15 分钟)。 照上面的格子写。Result 里必须有可验证的事实——「路由全对」「改五处」「审批没漏」,写不出这种句子的 Result 是感觉,不是结果。
第四步:写 300 字周记(15 分钟)。 四段一段不少,「还含糊」那段照实写,它是阶段四开卷前的补课清单。
第五步:git 收口(15 分钟)。 代码和文档分开提交,打双 tag:
bash
# 代码一个提交,文档一个提交:
git add week13/
git commit -m "feat(week13): 多 Agent 客服系统,含 HITL 审批与容错"
git add docs/
git commit -m "docs(week13): 阶段三验收记录、架构图 v3 与周记"
# 双 tag 收口:
git tag week13-done
git tag stage3-done第二个 tag 是军令状
week13-done 收本周,stage3-done 收整个阶段。8 项验收没全绿之前,第二个 tag 别打——打下去的意思是「这一层我担保」,下一个阶段踩着它往上盖。
常见踩坑
验收清单变成「看过清单」。 8 项都眼熟,逐项打勾了事,一项没真跑。判断标准还是那条:证据在哪?日志、截图、tag,至少留一样。没证据的勾等于没验。
红了不查表直接改代码。 一项红了就慌,随手改提示词、换模型、调参数。四个根因覆盖八成红灯,先对排查表,两分钟省两小时。表解决不了的才是新问题,值得进周记。
架构图 v3 画成一张新图。 兴致上来把底图重画一遍,FastAPI 框圈漏了 Supervisor,或者审批流忘了画绕行。v3 的要点是「在旧图上标注」:底图走样,说明 Day 1 的结构没吃透,先回去补。
STAR 写成功能列表。 「实现了 Supervisor、实现了审批、实现了容错」——这是 changelog,不是 STAR。Action 里要有「对比过哪几条路、为什么落这条」,Result 里要有可验证的事实。面试官问的从来不是你做了什么,是你为什么这么做。
里程碑日跳过周记。 觉得系统都验收了,周记可写可不写。验收验的是系统能跑,周记记的是认知变化,两回事。第四段「下周前补」恰恰是下一个阶段的启动输入。
延伸阅读
- 第 13 周索引:本周全部日程,验收哪项卡住就回翻对应 Day 的教程
- 第 1 周 Day 7:复盘方法论的源头,四段周记和「输出倒逼输入」的规矩从这来
- Anthropic:Building Effective Agents:拿你 STAR 里的架构决策再对照一遍这篇,看自己的选择和它的建议差在哪
- LangGraph persistence 文档:周记第三段「快照里存了什么」的答案在这里,阶段三最后一块含糊点,今天顺手清掉
下周开始前,把周记第四段留的补课动作清掉。阶段三到此收口,下一个阶段踩着这张验收单开张。本周全部日程见第 13 周索引。