本页目录
L03 练习
改
code.py或 research-assistant 的代码,运行观察变化。本课零外部依赖。
练习 1:证明「字符串降级」会污染报告(设计实验类)
现状 web_search 超时返回 f"搜索 '{query}' 超时..." 字符串。
- 假设:这个字符串会混进 findings,被 summarize 当成「子题一的研究发现」写进报告。
- 实验设计:
- 在
code.py的demo_honest_vs_string里,把findings_string喂给一个假的 summarize(直接拼接前 100 字符)。 - 打印 summarize 结果,看「搜索超时」是否出现在摘要里。 - 预期:摘要里出现「搜索 '子题一' 超时(15s)」——证明 LLM 会把它当事实。
- 思考:如果用户看到报告里写着「搜索超时」,他会怎么想?(提示:他不知道这是工具失败还是真的查到了一个叫「搜索超时」的东西——这就是不诚实的代价。)
验收:能展示污染证据(摘要含「超时」字样),并说清 L03 的结构化降级为什么能避免。
练习 2:设计实验——fail_threshold 和 cooldown 怎么配(设计实验类)
熔断器的两个核心参数:fail_threshold(连续几次失败打开)和 cooldown(打开后多久试探)。
- 假设:fail_threshold=1 太紧(一点抖动就熔断),=10 太松(雪崩已发生);cooldown 太短频繁试探,太长恢复慢。
- 实验设计:
- 在
code.py的demo_state_machine里,改成「前 2 次失败、第 3 次成功」的抖动场景(模拟偶发故障)。 - 设 fail_threshold=1 / 3 / 5,看哪些值会误熔断(本该成功的第 3 次被熔断挡掉)。 - 再改成「连续失败」的持续故障场景,看 fail_threshold=10 时要等几次才熔断(雪崩窗口多大)。 - 预期:抖动场景下 fail_threshold=1 会误熔断(第 3 次成功被挡),=3 不误熔断。持续故障下 fail_threshold=10 要等 10 次失败(雪崩已放大)。
- 思考:为什么默认 fail_threshold=3?(提示:3 次连续失败通常意味着不是单次抖动。1 次可能是网络闪断,2 次可能是限流,3 次大概率是真挂了。)
验收:给出一个 (fail_threshold, cooldown) 推荐组合,说清依据(抖动不误熔断 + 持续故障及时熔断)。
练习 3:实现降级链的 browser→search→skip(设计实验类)
降级链:browser 失败 → 退 web_search → 再失败 → 跳过子题并声明。
- 假设:降级链让「能力递减但不中断」——高层工具失败不杀死整个研究,只降到低层。
- 实验设计:
- 在
code.py加一个demo_degradation_chain函数:模拟 browser 失败 → web_search 也失败 → 返回「跳过子题并声明」。 - 对比「无降级链」:browser 失败直接抛异常,整个 research_team 崩。 - 预期:降级链下,即使两个工具都挂了,researcher 仍返回
failed_subtopics(让 writer 声明),不抛异常。无降级链下整个研究崩。 - 思考:降级链和熔断器是什么关系?(提示:熔断器是「单个工具内部的快速失败机制」,降级链是「工具之间的后备机制」。browser 熔断了 → 触发降级到 search;search 也熔断了 → 触发降级到 skip。两层叠加。)
验收:降级链演示跑通;能说清熔断器(工具内)vs 降级链(工具间)的分工。
练习 4:思考题——为什么不直接 try/except 包一切(取舍类)
最简单的容错是「try/except 包一切,失败返回空」。为什么不够?
- 思考:try/except 失败返回空字符串,和 L03 的结构化降级
{"status":"failed", "content":""}有什么区别?(提示:前者下游不知道失败了(以为拿到了空内容),后者下游看 status 知道失败了可以声明。) - 思考:try/except 包一切 + 重试,遇到持续故障会怎样?(提示:雪崩——重试到天荒地老。需要熔断器。)
- 结论:用一句话说清「try/except 包一切」缺了哪两样东西(结构化状态 + 熔断器)。
验收:能指出 try/except 兜底缺「下游可感知的失败状态」和「持续故障的快速失败机制」,这正是 L03 补的两块。