1
00:00:00,100 --> 00:00:06,432
本内容改编自小山学堂《学 AI 产品，从入门到精通》，为二次演绎配音版。

2
00:00:06,382 --> 00:00:14,603
这一集要解决的工程问题是，为什么一个能写好单个函数的模型，却做不完一个完整的应用。

3
00:00:14,603 --> 00:00:20,192
以及如果你真想让它连续跑上几十个小时，系统到底该怎么搭。

4
00:00:20,192 --> 00:00:22,031
先把结论摆出来。

5
00:00:22,031 --> 00:00:25,829
模型不缺写代码的能力，它缺的是交接。

6
00:00:25,829 --> 00:00:37,175
长任务真正难的地方从来不是动手做，而是上下文断了之后，下一个接手的人怎么知道上一个做到哪、为什么这么写、下一步该干什么。

7
00:00:37,175 --> 00:00:48,221
这一集我们把这条链路完整拆开，从失败模式讲到双角色分工，再讲到组件拆分、会话与上下文的分离，最后落到安全防线。

8
00:00:48,221 --> 00:00:59,579
如果你正在搭自己的智能体框架，或者正在评估某个长运行产品到底靠不靠谱，这一集给的那几条原则可以直接拿去当检查清单用。

9
00:00:59,579 --> 00:01:03,918
先说两个你在实际项目里一定会观察到的症状。

10
00:01:03,918 --> 00:01:11,598
一个是进度条卡住，智能体跑了一整天，功能数量却几乎没涨，日志里全是修复报错。

11
00:01:11,598 --> 00:01:19,002
另一个是它兴高采烈地告诉你做完了，你打开一看，页面能加载，但一半按钮点了没反应。

12
00:01:19,002 --> 00:01:27,235
这两个症状看起来完全不同，根子其实是同一件事，就是没有人告诉它现在到底走到哪一步了。

13
00:01:27,388 --> 00:01:29,489
先看失败长什么样。

14
00:01:29,439 --> 00:01:34,884
假设你给智能体一句高层指令，做一个聊天应用的完整克隆。

15
00:01:34,884 --> 00:01:36,542
这不是小活。

16
00:01:36,542 --> 00:01:44,006
认证、会话管理、流式输出、文件上传、多轮历史，加起来两百多个独立功能点。

17
00:01:44,006 --> 00:01:47,336
第一种失败模式叫一口气做太多。

18
00:01:47,336 --> 00:01:55,076
智能体试图在一次会话里把功能全写完，结果上下文窗口在实现到一半的时候被用完。

19
00:01:55,076 --> 00:02:00,941
下一个智能体接手，面对一堆半成品代码，只能猜前任做了什么。

20
00:02:00,941 --> 00:02:06,290
大量时间花在让基本功能重新跑起来，新功能反而没时间做。

21
00:02:06,290 --> 00:02:09,307
有人会说不是有上下文压缩吗。

22
00:02:09,307 --> 00:02:14,199
压缩后的指令往往不够清晰，新的智能体照样迷路。

23
00:02:14,199 --> 00:02:16,326
它的时间线是这样的。

24
00:02:16,326 --> 00:02:21,470
第一个智能体开始写，写了大概一半功能，上下文用完。

25
00:02:21,470 --> 00:02:26,206
第二个接手，先花时间修半成品，上下文又用完。

26
00:02:26,206 --> 00:02:29,078
第三个再接手，还是先修。

27
00:02:29,078 --> 00:02:32,768
每一轮都忙着修复，推进就停在那了。

28
00:02:32,768 --> 00:02:36,182
第二种失败模式叫过早宣布完成。

29
00:02:36,182 --> 00:02:44,379
智能体看到已经实现了一批功能，就认为项目差不多了，于是宣布完成，实际上只做了核心功能的三成。

30
00:02:44,379 --> 00:02:45,941
原因有两个。

31
00:02:45,941 --> 00:02:49,367
一是没有任务清单，它不知道还差什么。

32
00:02:49,367 --> 00:02:55,100
二是没有验证机制，它以为做完了，但没有任何端到端测试能证明。

33
00:02:55,100 --> 00:02:57,552
这里有个类比值得记住。

34
00:02:57,552 --> 00:03:05,977
没有交接机制的长运行智能体，就像一个软件项目里每个工程师只上一个班次，换班时完全失忆。

35
00:03:05,977 --> 00:03:10,533
不知道前一个人做了什么、为什么做、下一步做什么。

36
00:03:10,533 --> 00:03:15,196
每个人坐下打开一堆半成品，只能从零开始理解。

37
00:03:15,196 --> 00:03:21,110
顺带说一句，这也是为什么单纯加大上下文窗口并不能根治问题。

38
00:03:21,110 --> 00:03:25,160
窗口再大也有上限，而需求是可以无限加的。

39
00:03:25,160 --> 00:03:31,302
真正的分水岭不在于一次能装多少，而在于装不下的那部分去了哪里。

40
00:03:31,302 --> 00:03:36,939
如果答案是丢了，那窗口多大都是同一个结局，只是晚一点崩而已。

41
00:03:36,939 --> 00:03:42,540
所以核心洞察是，长任务的核心挑战是交接，动手做反而不难。

42
00:03:42,540 --> 00:03:45,965
解决交接，才能解决长运行。

43
00:03:46,108 --> 00:03:47,560
那怎么解决。

44
00:03:47,510 --> 00:03:54,301
第一个有效方案是把一个智能体拆成两个角色，一个管规划，一个管执行。

45
00:03:54,301 --> 00:03:58,387
第一个角色叫初始化智能体，只跑第一轮。

46
00:03:58,387 --> 00:04:03,087
它负责从零到一，搭环境、定计划、做第一次提交。

47
00:04:03,087 --> 00:04:04,938
具体做四件事。

48
00:04:04,938 --> 00:04:07,245
写一个环境搭建脚本。

49
00:04:07,245 --> 00:04:09,205
建一个进度文件。

50
00:04:09,205 --> 00:04:13,556
把用户那句高层指令展开成详细的功能清单。

51
00:04:13,556 --> 00:04:17,450
做第一次代码提交，保证仓库状态干净。

52
00:04:17,450 --> 00:04:21,248
第二个角色叫编码智能体，每轮都跑。

53
00:04:21,248 --> 00:04:24,409
它负责从一到多，逐个功能推进。

54
00:04:24,409 --> 00:04:32,895
读进度文件了解当前状态，一次只做一个功能，做完更新进度文件，然后提交并把做了什么写清楚。

55
00:04:32,895 --> 00:04:36,272
进度文件里该写什么，值得展开一下。

56
00:04:36,272 --> 00:04:38,099
至少要有三块。

57
00:04:38,099 --> 00:04:43,568
一是已经完成了哪些功能，每个功能的通过状态是真是假。

58
00:04:43,568 --> 00:04:47,774
二是当前代码处于什么状态，有没有已知的坑。

59
00:04:47,774 --> 00:04:51,248
三是下一步建议做什么，以及为什么。

60
00:04:51,248 --> 00:04:58,375
第三块最容易被漏掉，但它恰恰是交接的核心，因为前任的判断比代码本身更值钱。

61
00:04:58,375 --> 00:05:01,140
这里有个细节很多人踩坑。

62
00:05:01,140 --> 00:05:03,664
功能清单用什么格式记。

63
00:05:03,664 --> 00:05:08,892
用结构化的数据格式，比如 J S O N，不要用 Markdown。

64
00:05:08,892 --> 00:05:16,993
原因是，模型更不容易误改结构化的 J S O N，而 Markdown 很容易被模型顺手整段重写。

65
00:05:16,993 --> 00:05:21,705
你辛苦维护的清单，可能在某一次编辑里就被改没了。

66
00:05:21,705 --> 00:05:24,902
配套还有一条提示词措辞上的要求。

67
00:05:24,902 --> 00:05:30,370
必须明确告诉智能体，不允许删除或者修改已有的测试内容。

68
00:05:30,370 --> 00:05:34,457
否则它会为了让测试通过而降低测试标准。

69
00:05:34,457 --> 00:05:38,027
这是真实会发生的事，不是理论风险。

70
00:05:38,027 --> 00:05:40,983
为什么一次只做一个功能是关键。

71
00:05:40,983 --> 00:05:47,630
因为每次完成一个功能，代码就处于可合并状态，没有半成品也没有语法错误。

72
00:05:47,630 --> 00:05:53,435
代码提交提供了回滚点，下一轮搞坏了可以回到上一个干净状态。

73
00:05:53,435 --> 00:05:58,159
进度文件提供了上下文，新的智能体不用猜做到哪。

74
00:05:58,159 --> 00:06:03,640
而且每轮只需要装进一个功能的上下文，窗口不会积累到爆。

75
00:06:03,640 --> 00:06:05,214
最后是验证。

76
00:06:05,214 --> 00:06:09,048
智能体很容易以为做完了却没有端到端验证。

77
00:06:09,048 --> 00:06:13,435
它说实现了登录功能，实际上按钮根本点不动。

78
00:06:13,435 --> 00:06:21,993
解决办法是明确要求它用浏览器自动化做端到端测试，真正打开浏览器、点按钮、看结果。

79
00:06:21,993 --> 00:06:30,046
光有单元测试不够，要用 Puppeteer 或者 Playwright 这类工具写测试，作为功能是否真正通过的判定标准。

80
00:06:30,046 --> 00:06:41,608
这套方案的效果是，两百多个功能的聊天应用克隆被成功构建出来，每个功能都有对应的端到端测试，代码始终保持在可合并状态。

81
00:06:41,740 --> 00:06:50,343
第二个方案更底层，它把智能体的思考和执行拆成独立组件，让任何部分出错时系统都能优雅恢复。

82
00:06:50,293 --> 00:06:52,072
先借一个类比。

83
00:06:52,072 --> 00:06:54,512
操作系统虚拟化硬件。

84
00:06:54,512 --> 00:07:04,188
你调用一个读文件的接口时，并不关心底层是固态硬盘、机械硬盘还是网络磁盘，操作系统帮你抽象了硬件细节。

85
00:07:04,188 --> 00:07:14,115
脑手分离做的是同一件事，它虚拟化智能体的各个组件，让脑不关心手是哪个容器，让手不关心脑用的是哪个模型。

86
00:07:14,115 --> 00:07:15,486
三个组件。

87
00:07:15,486 --> 00:07:25,474
会话，是只增不减的事件流，持久化存储，记录所有发生过的事情，用户输入、工具调用、模型输出都在里面。

88
00:07:25,474 --> 00:07:34,476
harness 理解为大脑，是调用模型并路由工具调用的循环，负责思考，决定下一步做什么、怎么组织上下文。

89
00:07:34,476 --> 00:07:43,106
沙箱理解为手，是执行代码和编辑文件的容器环境，负责跑命令、写文件、跟外部系统交互。

90
00:07:43,106 --> 00:07:44,765
为什么要拆开。

91
00:07:44,765 --> 00:07:47,541
可以借用宠物和牛群的比喻。

92
00:07:47,541 --> 00:07:56,015
旧方案里三个组件都在同一个容器，容器挂了等于会话丢失，等于没法调试，等于任务彻底失败。

93
00:07:56,015 --> 00:07:59,140
就像养了一只宠物，挂了就完了。

94
00:07:59,140 --> 00:08:07,986
新方案里每个组件独立部署，容器挂了只是这次工具调用失败，大脑决定重试，新建一个容器接着干。

95
00:08:07,986 --> 00:08:12,421
就像牧场的牛，挂了再牵一头，系统照常运行。

96
00:08:12,421 --> 00:08:15,005
拆开之后的好处很具体。

97
00:08:15,005 --> 00:08:21,579
容器变成了一次普通的函数调用，对大脑来说就是调一下拿回一段字符串。

98
00:08:21,579 --> 00:08:28,983
容器挂了，就是这次调用返回错误，模型自己决定要不要重试，自动新建容器继续。

99
00:08:28,983 --> 00:08:37,733
而且大脑不需要等容器就绪就能开始处理，首字延迟因此明显下降，中位数和长尾都有改善。

100
00:08:37,733 --> 00:08:40,402
它还顺带解决了安全问题。

101
00:08:40,402 --> 00:08:47,926
旧方案里智能体生成的代码和密钥在同一个容器，提示词注入可以直接把密钥偷走。

102
00:08:47,926 --> 00:08:57,096
新方案里代码仓库的令牌在克隆仓库时注入容器环境变量，沙箱内可以用，但智能体看不到令牌的值。

103
00:08:57,096 --> 00:09:04,404
第三方服务的令牌存在外部的保险库里，通过代理转发调用，沙箱内根本拿不到。

104
00:09:04,404 --> 00:09:06,615
组件还能自由组合。

105
00:09:06,615 --> 00:09:10,414
多个大脑各自是无状态的，按需启动。

106
00:09:10,414 --> 00:09:14,777
多个沙箱各自是独立工具，可以传给不同的大脑。

107
00:09:14,777 --> 00:09:23,875
这意味着你可以让一个大脑同时操控多个沙箱并行执行，也可以让同一个沙箱在不同的大脑之间传递接力。

108
00:09:23,875 --> 00:09:26,303
组件之间完全解耦。

109
00:09:26,452 --> 00:09:33,000
第三个要点，是两个最容易搞混的概念，当前能看到的，和所有发生过的事。

110
00:09:32,950 --> 00:09:34,621
它们必须分开。

111
00:09:34,621 --> 00:09:42,445
上下文窗口是模型当前能看到的那些 token，就像人的工作记忆，容量有限，过了就忘。

112
00:09:42,445 --> 00:09:45,642
每次对话开始时构建，用完就丢。

113
00:09:45,642 --> 00:09:55,210
会话是所有发生过事件的持久日志，就像完整的录像，从头到尾什么都记着，永远可以倒回去看，而且只增不减。

114
00:09:55,210 --> 00:09:57,037
为什么必须分开。

115
00:09:57,037 --> 00:10:02,974
因为上下文压缩和裁剪都是不可逆操作，一旦压缩，原始细节就丢了。

116
00:10:02,974 --> 00:10:11,207
而压缩的时候你很难知道未来哪些 token 重要，今天看着无关的信息，可能是明天关键决策的依据。

117
00:10:11,207 --> 00:10:15,450
丢了就永远回不来了，这是信息论层面的基本约束。

118
00:10:15,450 --> 00:10:23,575
正确做法是，原始事件永久存在会话里，上下文窗口只是从会话中临时取景的一个视角。

119
00:10:23,575 --> 00:10:28,022
丢了上下文没关系，因为会话还在，随时可以重建。

120
00:10:28,022 --> 00:10:34,525
会话对外提供一个类似数据库的查询接口，大脑可以按需读取任意范围的事件。

121
00:10:34,525 --> 00:10:42,493
可以从某个位置往前读一百条，可以倒回到某个决策点前后，也可以只过滤出工具调用这一类事件。

122
00:10:42,493 --> 00:10:46,964
就像写代码查库一样，大脑能灵活地查询和过滤。

123
00:10:46,964 --> 00:10:51,099
而且从会话取出的事件可以做任意变换。

124
00:10:51,099 --> 00:11:02,698
保持前缀稳定来优化提示缓存命中率，根据当前任务类型做上下文工程，以及换不同的 harness 去适配不同的模型，这些都不影响会话本身。

125
00:11:02,698 --> 00:11:04,633
硬件类比最直观。

126
00:11:04,633 --> 00:11:09,741
上下文窗口是内存，速度快、容量小、断电即失。

127
00:11:09,741 --> 00:11:14,260
会话是硬盘，速度慢、容量大、断电不失。

128
00:11:14,260 --> 00:11:19,537
你不会把所有文件同时加载进内存，那样内存会爆。

129
00:11:19,537 --> 00:11:25,642
同理，你不该把所有历史事件都塞进上下文窗口，那样 token 会爆。

130
00:11:25,642 --> 00:11:31,015
正确做法是按需加载，会话存全量，上下文取子集。

131
00:11:31,015 --> 00:11:33,563
这里还有一个工程上的红利。

132
00:11:33,563 --> 00:11:46,472
因为会话是稳定的，而上下文是每次重新组装的，所以你可以对同一份会话试不同的组装策略，换模型、换压缩算法、换检索方式，都不用动底层数据。

133
00:11:46,472 --> 00:11:54,296
换句话说，把两者分开，等于把策略和状态解耦，后面做优化的空间一下就打开了。

134
00:11:54,436 --> 00:11:56,549
最后一个要点是安全。

135
00:11:56,499 --> 00:12:02,208
安全不是加一层提示词就够的，需要从架构层面做结构性设计。

136
00:12:02,208 --> 00:12:04,744
所有风险可以归入三类。

137
00:12:04,744 --> 00:12:06,715
第一类是用户滥用。

138
00:12:06,715 --> 00:12:16,126
用户故意让智能体做不该做的事，比如生成钓鱼邮件、诱导执行恶意代码、获取未授权数据、绕过安全限制。

139
00:12:16,126 --> 00:12:18,879
这是来自用户侧的主动威胁。

140
00:12:18,879 --> 00:12:20,982
第二类是模型失控。

141
00:12:20,982 --> 00:12:24,516
没人指使它，它自己做了不该做的事。

142
00:12:24,516 --> 00:12:29,083
比如用户只让查看文件，它却自作主张修改了。

143
00:12:29,083 --> 00:12:32,797
比如基于幻觉信息执行了真实操作。

144
00:12:32,797 --> 00:12:35,994
比如访问了不属于当前任务的资源。

145
00:12:35,994 --> 00:12:38,218
比如进入循环停不下来。

146
00:12:38,218 --> 00:12:40,393
第三类是外部攻击。

147
00:12:40,393 --> 00:12:47,557
第三方通过注入恶意内容来操控行为，攻击来自数据源，用户本身可能毫不知情。

148
00:12:47,557 --> 00:12:59,528
网页或者文档里嵌入攻击指令，恶意的第三方服务返回被篡改的数据，训练数据里植入后门，或者通过智能体读取的邮件和文件间接注入。

149
00:12:59,528 --> 00:13:01,643
对应的防线有两层。

150
00:13:01,643 --> 00:13:08,410
模型层靠训练让模型倾向于安全行为，就像培养一个有良好价值观的员工。

151
00:13:08,410 --> 00:13:15,537
环境层靠系统架构让危险操作根本执行不了，就像给仓库加锁，不依赖员工自觉。

152
00:13:15,537 --> 00:13:18,146
这两层互补，缺一不可。

153
00:13:18,146 --> 00:13:24,648
模型层是让智能体想做对的事，环境层是让智能体即使想做错也做不到。

154
00:13:24,648 --> 00:13:37,352
只靠提示词告诉模型别做坏事是不够的，你还需要在架构上让坏事不可能发生，比如沙箱隔离、按任务粒度授权、高危操作人工审批、限制网络访问范围。

155
00:13:37,352 --> 00:13:40,453
还有一个新兴攻击面要单独提醒。

156
00:13:40,453 --> 00:13:47,569
第三方工具协议会显著扩大攻击面，每多接入一个服务，就多了一个数据注入入口。

157
00:13:47,569 --> 00:13:56,667
不可信的服务可能通过修改工具描述来操控行为，智能体以为自己在用搜索文件的工具，实际在执行删除。

158
00:13:56,667 --> 00:14:02,088
即使服务本身没有恶意，它返回的数据里也可能藏着注入指令。

159
00:14:02,088 --> 00:14:08,855
所以产品设计者要像审核第三方开发包一样审核每一个集成，信任但要验证。

160
00:14:08,855 --> 00:14:11,102
最后给一组实践数据。

161
00:14:11,102 --> 00:14:16,126
自动模式靠两个组件实现高自主和低风险的平衡。

162
00:14:16,126 --> 00:14:23,891
分类器负责快速判断每个操作的风险等级，安全操作直接执行，可疑操作才弹窗询问。

163
00:14:23,891 --> 00:14:31,523
沙箱作为第二道防线，确保即使分类器误判，代码执行也不会对系统造成真实损害。

164
00:14:31,523 --> 00:14:37,833
两者组合，让用户减少了大约八成的确认弹窗，同时保持了安全性。

165
00:14:37,972 --> 00:14:42,164
最后我们把这一集落成几条可以直接用的原则。

166
00:14:42,114 --> 00:14:45,384
第一，先解决交接再优化能力。

167
00:14:45,384 --> 00:14:55,456
进度文件、功能清单、增量提交，这三样是让智能体能持续推进的最基本保障，一点都不花哨，但没有它们一切免谈。

168
00:14:55,456 --> 00:15:01,609
第二，一次只做一个功能，并且每次结束都要让代码处于可合并状态。

169
00:15:01,609 --> 00:15:05,540
这既是回滚点，也是天然的上下文边界。

170
00:15:05,540 --> 00:15:11,622
第三，功能清单用结构化格式，不要用容易被顺手重写的文档格式。

171
00:15:11,622 --> 00:15:17,811
同时在提示词里明确禁止修改已有测试，否则测试会被悄悄放水。

172
00:15:17,811 --> 00:15:20,552
第四，验证必须端到端。

173
00:15:20,552 --> 00:15:26,201
让智能体真的打开浏览器点一遍，而不是靠它自己声明完成。

174
00:15:26,201 --> 00:15:31,718
第五，把思考和执行拆开，让组件能独立失败、独立重建。

175
00:15:31,718 --> 00:15:35,347
任何部分挂了，都不应该等于任务失败。

176
00:15:35,347 --> 00:15:40,997
第六，原始事件永久存会话，上下文窗口只是临时视图。

177
00:15:40,997 --> 00:15:44,001
压缩的可以丢，原始数据不能丢。

178
00:15:44,001 --> 00:15:46,477
第七，安全至少两层。

179
00:15:46,477 --> 00:15:50,912
模型层让它想做对，环境层让它做错也做不到。

180
00:15:50,912 --> 00:15:55,227
新增的每一个外部集成，都按供应链风险来审。

181
00:15:55,227 --> 00:16:04,061
这七条里，前三条决定能不能跑完，中间两条决定跑多久会崩，最后两条决定崩了之后损失有多大。

182
00:16:04,061 --> 00:16:10,179
你可以拿它去对照任何一个长运行智能体产品，看它究竟做到了哪几层。

