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

2
00:00:06,382 --> 00:00:15,192
今天这一集聊两个问题，都跟时序有关，而且都是那种平时你根本不会去想、一旦出事就很难查的问题。

3
00:00:15,192 --> 00:00:16,514
第一个问题。

4
00:00:16,514 --> 00:00:20,036
模型还在打字的时候，工具已经开工了。

5
00:00:20,036 --> 00:00:26,826
你让 Agent 读三个文件再写摘要，屏幕上文字还在往外冒，读文件的声音已经响了。

6
00:00:26,826 --> 00:00:29,098
这时候你按了 Esc。

7
00:00:29,098 --> 00:00:32,031
界面停了，但历史里留下什么。

8
00:00:32,031 --> 00:00:36,430
你以为取消等于什么都没发生，运行时并不这么记账。

9
00:00:36,430 --> 00:00:37,824
第二个问题。

10
00:00:37,824 --> 00:00:44,122
Agent 正在改第三个文件，你看见方向偏了，补了一句，配置用 YAML。

11
00:00:44,122 --> 00:00:49,891
回车之后，这句话是立刻开工、插进当前这一轮，还是当场被拒。

12
00:00:49,891 --> 00:00:52,572
这个决定由谁做，什么时候做。

13
00:00:52,572 --> 00:00:56,430
这两个问题的答案，最后都指向同一件事。

14
00:00:56,430 --> 00:01:03,173
一个可靠的 Agent 运行时，必须把「什么时候写历史」和「什么时候决定」这两件事的时机钉死。

15
00:01:03,173 --> 00:01:07,608
时机一旦含糊，出事的时候你连日志都对不上。

16
00:01:07,608 --> 00:01:10,420
顺带说一句这一集的取材。

17
00:01:10,420 --> 00:01:15,612
我们直接对着源码讲，会提到具体的函数名和行号区间。

18
00:01:15,612 --> 00:01:18,750
行号不重要，重要的是那个顺序。

19
00:01:18,750 --> 00:01:22,920
因为顺序才是合同，函数名只是合同的名字。

20
00:01:22,920 --> 00:01:24,891
我们一个一个来。

21
00:01:25,040 --> 00:01:27,850
先看第一个问题里的核心机制。

22
00:01:27,800 --> 00:01:31,394
素材里把它叫请求先盖章，工具再开工。

23
00:01:31,394 --> 00:01:34,254
具体的触发点是这么一个事件。

24
00:01:34,254 --> 00:01:40,504
解析器在收到那一帧代表某个输出项完成的帧时，把它收成一个业务事件。

25
00:01:40,504 --> 00:01:45,949
这个事件一到，采样循环做两件事，而且顺序是固定的。

26
00:01:45,949 --> 00:01:53,377
先把这条函数调用写入会话历史和 rollout，再把工具执行包成一个 future，挂到有序队列上。

27
00:01:53,377 --> 00:01:55,300
顺序为什么重要。

28
00:01:55,300 --> 00:02:00,396
因为取消令牌用的是子令牌，父令牌一亮，这个工具跟着停。

29
00:02:00,396 --> 00:02:05,060
但取消来得再快，这一条函数调用已经进了历史。

30
00:02:05,060 --> 00:02:08,497
最多再多写一条被用户中止的标记。

31
00:02:08,497 --> 00:02:10,685
那它解决的是什么问题。

32
00:02:10,685 --> 00:02:12,548
设想两个场景。

33
00:02:12,548 --> 00:02:19,651
一个是，模型已经发出两个函数调用，第三个还在路上，流在结束帧到来之前就关掉了。

34
00:02:19,651 --> 00:02:24,807
下一次重试，该看见空历史，还是已经落盘的调用和结果。

35
00:02:24,807 --> 00:02:27,403
另一个是，你按了 Esc。

36
00:02:27,403 --> 00:02:33,966
这时候如果历史里只剩半截请求，模型和界面都会看到一个没闭合的调用。

37
00:02:33,966 --> 00:02:36,947
两种记账方式各有各的坏处。

38
00:02:36,947 --> 00:02:45,324
如果等结束帧再写入，提前关流会把已经完整的调用一起扔掉，重试就得让模型把同样的调用再发一遍。

39
00:02:45,324 --> 00:02:48,822
如果取消时跳过写入，历史就断了。

40
00:02:48,822 --> 00:02:51,418
所以这条合同是这么定的。

41
00:02:51,418 --> 00:02:53,990
先写请求，后写结果。

42
00:02:53,990 --> 00:02:55,901
历史始终闭合。

43
00:02:55,901 --> 00:02:58,978
素材里还给了一句很有分量的话。

44
00:02:58,978 --> 00:03:01,815
历史只能往上加，不能改写。

45
00:03:01,815 --> 00:03:05,468
工具已经读了磁盘，这条事实已经发生。

46
00:03:05,468 --> 00:03:10,132
取消树可以打断执行，但打断不了已经写下的请求。

47
00:03:10,132 --> 00:03:13,305
这条合同不依赖任何一门语言。

48
00:03:13,305 --> 00:03:18,557
换成别的实现，也是先追加一条记录，再挂一个 promise。

49
00:03:18,700 --> 00:03:23,782
既然工具在流还没结束的时候就开工了，那就必须回答一个问题。

50
00:03:23,732 --> 00:03:28,323
流提前结束的时候，那些已经开工的任务归谁收尾。

51
00:03:28,323 --> 00:03:31,809
素材给的答案是，每个出口都 drain。

52
00:03:31,809 --> 00:03:33,660
具体来说是这样。

53
00:03:33,660 --> 00:03:40,330
收流循环无论正常结束、提前关流，还是被取消，都只是离开那个循环。

54
00:03:40,330 --> 00:03:42,506
函数本身还没返回。

55
00:03:42,506 --> 00:03:49,080
随后固定调用一次收尾，按插入顺序等到每条 future 给出结果，再写入历史。

56
00:03:49,080 --> 00:03:51,424
然后才检查取消令牌。

57
00:03:51,424 --> 00:03:53,503
这个顺序也很关键。

58
00:03:53,503 --> 00:03:55,787
先等齐，再判取消。

59
00:03:55,787 --> 00:04:03,155
因为如果先判取消，那些还在跑的任务就成了没人认领的孤儿，工具还在跑，历史对不上。

60
00:04:03,155 --> 00:04:06,400
三条出口的结果不一样，我分开说。

61
00:04:06,400 --> 00:04:09,080
正常结束，没什么好说的。

62
00:04:09,080 --> 00:04:17,494
断流走的是可重试那条路，重试的时候从历史副本重建提示词，已经写下的调用和结果全都在。

63
00:04:17,494 --> 00:04:29,417
用户按 Esc 那条路走的是不可重试，正在跑的工具会写出中止文案，收尾时把它当成普通输出写进去，标记这一轮被中止，不会再打一轮。

64
00:04:29,417 --> 00:04:31,676
对照着看更有意思。

65
00:04:31,676 --> 00:04:39,417
如果工具是等流结束才开工的，那么断流的时候请求还没写下，重试只能从空历史开始。

66
00:04:39,417 --> 00:04:46,737
而流内开工的版本，已经落盘的请求和收尾出来的结果都在，重试直接读这份历史。

67
00:04:46,737 --> 00:04:50,270
素材最后给了一条非常朴素的工程原则。

68
00:04:50,270 --> 00:04:53,636
中途开工，就必须在每个出口等齐。

69
00:04:53,636 --> 00:04:57,590
成功、错误、取消，共用这一段收尾。

70
00:04:57,590 --> 00:05:04,862
换一门语言也一样，离开异步循环之后先等所有任务落定，再决定重试还是中止。

71
00:05:05,020 --> 00:05:08,383
第三个细节很短，但很容易被忽略。

72
00:05:08,333 --> 00:05:15,208
三个工具可以同时跑，但谁先跑完不重要，历史仍然按模型发出的顺序写结果。

73
00:05:15,208 --> 00:05:17,095
为什么要这么较真。

74
00:05:17,095 --> 00:05:24,547
因为按完成顺序写的话，同一段会话重放两次可能就对不上了，提示词缓存也会更脆。

75
00:05:24,547 --> 00:05:26,951
这里有一条很漂亮的解耦。

76
00:05:26,951 --> 00:05:30,905
有序队列把观测顺序和执行顺序拆开了。

77
00:05:30,905 --> 00:05:34,090
挂起顺序就是日后收尾的顺序。

78
00:05:34,090 --> 00:05:39,343
至于并发闸门开多大，那是另一层的责任，这里只保证顺序。

79
00:05:39,343 --> 00:05:44,078
换句话说，并发是性能优化，顺序是正确性要求。

80
00:05:44,078 --> 00:05:48,838
这两个目标放在同一层里解决，一定会互相打架。

81
00:05:48,838 --> 00:05:51,494
拆开之后各自都很简单。

82
00:05:51,494 --> 00:05:54,523
这条解耦还有个很实际的收益。

83
00:05:54,523 --> 00:05:55,569
重放。

84
00:05:55,569 --> 00:06:06,638
当你要把一段会话重新跑一遍来复现问题的时候，按发出顺序写意味着结果顺序是确定的，跟机器快慢、网络抖动、磁盘负载都没关系。

85
00:06:06,638 --> 00:06:10,569
而按完成顺序写，重放就变成了碰运气。

86
00:06:10,569 --> 00:06:16,170
这也是为什么很多 Agent 系统在做上下文缓存的时候会踩坑。

87
00:06:16,170 --> 00:06:22,924
缓存命中的前提是前缀稳定，而前缀稳不稳，取决于你写历史的顺序。

88
00:06:22,924 --> 00:06:30,737
顺序这东西平时看不见，一旦要做缓存、要做重放、要做审计，它就会立刻跳出来咬人。

89
00:06:30,737 --> 00:06:34,583
所以这一条虽然短，我建议你单独记下来。

90
00:06:34,583 --> 00:06:39,631
观测顺序和执行顺序，是两个可以也应该被分开的概念。

91
00:06:39,770 --> 00:06:43,674
横向对比一下，看看别人是怎么答这道题的。

92
00:06:43,624 --> 00:06:45,956
先看另一款主流产品。

93
00:06:45,956 --> 00:06:48,720
它的默认路径是等流结束。

94
00:06:48,720 --> 00:06:54,994
流式循环只负责收集工具调用块，等循环结束才真正开始跑工具。

95
00:06:54,994 --> 00:07:03,756
这条路的好处是显而易见的，流断了只需要丢掉已经收集的块就行，省掉了每个出口都要收尾的麻烦。

96
00:07:03,756 --> 00:07:09,020
代价也同样明显，工具的延迟和打字的延迟被串起来了。

97
00:07:09,020 --> 00:07:10,895
但它是有开关的。

98
00:07:10,895 --> 00:07:15,535
打开流式工具执行之后，行为就靠近 Codex 这一侧了。

99
00:07:15,535 --> 00:07:18,420
流内一收到工具就立刻开工。

100
00:07:18,420 --> 00:07:24,309
这时候失败回退要主动丢弃已经开工的工具，避免旧的编号漏进重试。

101
00:07:24,309 --> 00:07:27,819
对比之下，Codex 没有对等的丢弃操作。

102
00:07:27,819 --> 00:07:31,737
因为它选择了先持久化，重试直接读历史。

103
00:07:31,737 --> 00:07:38,131
这两种取舍没有对错，但你要知道自己选的是哪一种，以及它的代价在哪。

104
00:07:38,131 --> 00:07:40,511
再看另一个系统的做法。

105
00:07:40,511 --> 00:07:54,621
它的入口是一条已经成型的工具调用，走的是前置、守卫、环绕、后置这样一套三段瀑布加单调守卫的结构，回答的是谁能拒绝，以及拒绝之后结果还在不在。

106
00:07:54,621 --> 00:07:59,297
守卫只有拒绝理由或者弃权，没有放行这个选项。

107
00:07:59,297 --> 00:08:02,025
这里有个特别值得记住的提醒。

108
00:08:02,025 --> 00:08:05,559
两边词面很接近，但出口完全不同。

109
00:08:05,559 --> 00:08:10,655
一边护的是权限的单调性，一边护的是流式 transcript 的闭合。

110
00:08:10,655 --> 00:08:14,549
把守卫搬进 Codex，挡不住断流丢 transcript。

111
00:08:14,549 --> 00:08:20,631
把先持久化再收尾搬进那边，也回答不了插件能不能把拒绝改成放行。

112
00:08:20,631 --> 00:08:28,504
所以做架构借鉴的时候，先问清楚对方那套机制保护的到底是什么，别看着名字像就搬。

113
00:08:28,660 --> 00:08:30,376
第二个问题开始。

114
00:08:30,326 --> 00:08:33,343
你补的那句话，到底会去哪。

115
00:08:33,343 --> 00:08:34,773
先说结论。

116
00:08:34,773 --> 00:08:39,797
提交必须立刻回一个判定，回的是开工、插话，还是拒收。

117
00:08:39,797 --> 00:08:44,834
而且这个判定是在核心层当场拍板的，不等模型开口。

118
00:08:44,834 --> 00:08:46,949
素材里说得很明确。

119
00:08:46,949 --> 00:08:51,564
调用方选的是三种模式之一，而不是直接喊开始。

120
00:08:51,564 --> 00:08:57,346
核心层按现场忙闲和任务种类判定，立刻回结果，回完就结束。

121
00:08:57,346 --> 00:09:01,721
不等钩子，不等历史落盘，不等模型开始采样。

122
00:09:01,721 --> 00:09:03,632
为什么要求这么快。

123
00:09:03,632 --> 00:09:06,432
因为三种日常结局都不好受。

124
00:09:06,432 --> 00:09:11,588
立刻打断，前面两个文件的改动可能半成品留在磁盘上。

125
00:09:11,588 --> 00:09:17,310
排到下一轮，你只能眼睁睁看着它把剩下的文件按旧方向改完。

126
00:09:17,310 --> 00:09:25,435
塞进当前上下文却不叫醒循环，那你补的约束等于迟到，模型要到下一次自己开口才看得到。

127
00:09:25,435 --> 00:09:31,168
再看一下默认那条路径的执行顺序，顺序和函数名是一致的。

128
00:09:31,168 --> 00:09:32,862
先试着插话。

129
00:09:32,862 --> 00:09:40,879
只有当返回的是没有活动轮的情况下，才去走开工流程，先应用已启动状态，再拉起任务。

130
00:09:40,879 --> 00:09:45,374
其他的拒绝原因原样包装成拒收，不会偷偷开工。

131
00:09:45,374 --> 00:09:48,836
还有一条细节特别能体现设计的严谨。

132
00:09:48,836 --> 00:09:51,168
设置不能先改再判定。

133
00:09:51,168 --> 00:09:56,709
系统会先预览线程设置，预览失败就直接返回无效请求。

134
00:09:56,709 --> 00:10:01,096
真正的写入发生在开工或者插话成功的那一刻。

135
00:10:01,096 --> 00:10:04,064
被拒绝的输入，连设置都不改。

136
00:10:04,064 --> 00:10:09,425
插话成功之后只落持久设置，当前这一轮的上下文不换。

137
00:10:09,425 --> 00:10:12,971
为什么判定和写入要放在同一把锁里。

138
00:10:12,971 --> 00:10:21,312
因为必须先看有没有活动轮，再看任务种类，再写入待处理输入，中间不能让另一次提交把轮次换掉。

139
00:10:21,312 --> 00:10:28,319
拆成两次加锁，就有可能出现判定时还空闲、入队时已经有人占坑的窗口。

140
00:10:28,470 --> 00:10:32,506
第二个要点，不是所有任务都能接住你的插话。

141
00:10:32,456 --> 00:10:36,999
任务种类只有三个变体，常规、审查、压缩。

142
00:10:36,999 --> 00:10:39,607
其中只有常规任务收插话。

143
00:10:39,607 --> 00:10:44,427
审查和压缩当场拒收，返回的是活动轮不可插话。

144
00:10:44,427 --> 00:10:49,283
而且注意，默认路径也不会因为这次拒收就跑去开工。

145
00:10:49,283 --> 00:10:51,002
为什么这么设计。

146
00:10:51,002 --> 00:10:57,745
因为审查任务自己会再开一条一次性的子对话，压缩任务正在换上下文窗口。

147
00:10:57,745 --> 00:11:03,682
这时候你补一句用 YAML，根本没有当前轮的工具循环可以接住它。

148
00:11:03,682 --> 00:11:11,194
如果因此新开一轮普通对话，审查结果和压缩摘要会跟这条新对话抢同一个执行槽。

149
00:11:11,194 --> 00:11:14,295
素材里有一句话我特别想强调。

150
00:11:14,295 --> 00:11:19,523
调用方收到拒绝，这条输入不会被默默排进一条新对话。

151
00:11:19,523 --> 00:11:21,386
这半句很重要。

152
00:11:21,386 --> 00:11:26,122
拒收是明确的、可见的失败，不是悄悄换个地方重试。

153
00:11:26,122 --> 00:11:31,687
悄悄重试比明确失败危险得多，因为用户以为自己的话被听到了。

154
00:11:31,687 --> 00:11:34,920
另外还有个类型系统层面的好处。

155
00:11:34,920 --> 00:11:41,687
检查过关之后，用户输入被推进待处理输入，同时把信箱相位打回当前轮。

156
00:11:41,687 --> 00:11:43,982
这里的匹配是穷尽的。

157
00:11:43,982 --> 00:11:50,581
也就是说，将来新加一种任务类型，编译器会逼着你去回答它能不能插话。

158
00:11:50,581 --> 00:11:57,925
把业务规则写进类型系统，让编译器替你做代码审查，这是这一整讲反复出现的主题。

159
00:11:57,925 --> 00:12:00,701
再补一句这个方法论的边界。

160
00:12:00,701 --> 00:12:04,956
类型系统能逼你回答「能不能」，但回答不了「该不该」。

161
00:12:04,956 --> 00:12:11,434
它能保证你没漏掉新加的那种任务类型，却保证不了你给它的答案是正确的。

162
00:12:11,434 --> 00:12:17,925
所以穷尽匹配是下限保障，不是正确性证明，这两件事别混为一谈。

163
00:12:18,080 --> 00:12:22,513
第三个要点，也是最微妙的那个，叫一面翻牌。

164
00:12:22,463 --> 00:12:24,157
场景是这样的。

165
00:12:24,157 --> 00:12:31,285
主 Agent 已经在屏幕上打出一段看起来像最终答案的话，子 Agent 同时发回一条进度。

166
00:12:31,285 --> 00:12:35,035
并进去，用户已经看见的答案会被续写。

167
00:12:35,035 --> 00:12:40,419
一律等到下一轮，子 Agent 的结果可能要隔一次采样才进模型。

168
00:12:40,419 --> 00:12:41,633
怎么解决。

169
00:12:41,633 --> 00:12:44,254
素材里区分了两套存货。

170
00:12:44,254 --> 00:12:47,799
用户插话进的是轮次内的待处理输入。

171
00:12:47,799 --> 00:12:50,624
子 Agent 的信走会话级的信箱。

172
00:12:50,624 --> 00:12:55,191
这两套能不能并进当前轮，由一个投递相位决定。

173
00:12:55,191 --> 00:12:57,463
相位从当前轮起步。

174
00:12:57,463 --> 00:13:01,501
用户已经看见最终答案之后，切到下一轮。

175
00:13:01,501 --> 00:13:06,742
用户再插一句，或者模型又发出工具调用，相位就重开。

176
00:13:06,742 --> 00:13:08,821
这里有一条例外规则。

177
00:13:08,821 --> 00:13:15,335
待处理输入里只要还有一条不是那种排队不叫醒的子邮件，就保持当前相位。

178
00:13:15,335 --> 00:13:18,016
取信的时候也看这面牌。

179
00:13:18,016 --> 00:13:23,809
下一轮相位时，轮次内的待处理输入不拿，会话信箱更不掏。

180
00:13:23,809 --> 00:13:30,744
当前轮相位时，先拿走轮次内的待处理输入，再掏会话信箱，拼在后面。

181
00:13:30,744 --> 00:13:35,191
于是就有了一个看起来很反直觉但完全合理的结果。

182
00:13:35,191 --> 00:13:42,859
最终答案落地之后，子邮件可以躺在信箱里，而循环认为没有待处理输入，本轮收束。

183
00:13:42,859 --> 00:13:46,345
那什么算出用户已经看见的最终答案。

184
00:13:46,345 --> 00:13:52,823
规则是，助手正文算，注释阶段的内容不算，去掉空白之后为空的也不算。

185
00:13:52,823 --> 00:13:56,225
未打标的助手消息按最终答案处理。

186
00:13:56,225 --> 00:14:01,802
未打标的提供方默认走更安全的那条，先把信箱关到下一轮。

187
00:14:01,802 --> 00:14:04,951
还有一类东西完全不进这套信箱。

188
00:14:04,951 --> 00:14:13,028
审批、权限、提问、信息补全、动态工具，这是五张独立的单次表，各等各的回执。

189
00:14:13,028 --> 00:14:15,395
一句话概括这条边界。

190
00:14:15,395 --> 00:14:19,614
答案已经给用户看过，迟到的信默认不续写。

191
00:14:19,614 --> 00:14:24,061
这条边界本质上是一条产品边界，不是实现细节。

192
00:14:24,061 --> 00:14:30,876
用户已经在屏幕上读到一段答案，你再去续写它，那他读到的到底算不算最终答案。

193
00:14:30,876 --> 00:14:38,773
这个问题跟用什么语言、用什么信箱结构都没关系，任何交互式 Agent 迟早都要回答它。

194
00:14:38,910 --> 00:14:41,215
收尾，压成六条。

195
00:14:41,165 --> 00:14:44,110
第一条，先写请求，再开工。

196
00:14:44,110 --> 00:14:48,233
取消能打断执行，打断不了已经写下的历史。

197
00:14:48,233 --> 00:14:50,889
历史只能追加，不能改写。

198
00:14:50,889 --> 00:14:54,903
第二条，中途开工就必须在每个出口等齐。

199
00:14:54,903 --> 00:14:59,987
成功、错误、取消共用同一段收尾，避免孤儿任务。

200
00:14:59,987 --> 00:15:04,471
第三条，执行可以并行，历史必须按发出顺序写。

201
00:15:04,471 --> 00:15:09,326
并发是性能问题，顺序是正确性问题，两者要分层。

202
00:15:09,326 --> 00:15:12,379
第四条，提交必须立刻回判定。

203
00:15:12,379 --> 00:15:18,773
开工和插话在同一把锁里做完，被拒的输入不落地、不偷跑、不悄悄排队。

204
00:15:18,773 --> 00:15:24,507
第五条，能接住插话的任务和不能接住的任务，必须在类型上分开。

205
00:15:24,507 --> 00:15:29,891
让编译器逼你回答新类型能不能插话，比靠文档和自觉可靠得多。

206
00:15:29,891 --> 00:15:34,483
第六条，答案已经上屏，迟到的消息默认不续写。

207
00:15:34,483 --> 00:15:40,072
用户再插一句，或者模型再调一次工具，这扇门才重新打开。

208
00:15:40,072 --> 00:15:44,495
把这六条放在一起，你会看到一个反复出现的模式。

209
00:15:44,495 --> 00:15:51,069
这套系统把很多看起来是策略的选择，都下沉成了类型和顺序上的硬约束。

210
00:15:51,069 --> 00:15:54,038
顺序钉死了，边界就清楚了。

211
00:15:54,038 --> 00:15:57,427
边界清楚了，出事的时候才查得明白。

212
00:15:57,427 --> 00:16:00,023
最后留一个可以自查的问题。

213
00:16:00,023 --> 00:16:03,401
你的系统里，取消到底意味着什么。

214
00:16:03,401 --> 00:16:07,103
是抹掉一切，还是承认已经发生的事实。

215
00:16:07,103 --> 00:16:14,254
这两个答案没有绝对的对错，但你必须有意识地选一个，并且让历史记账和它保持一致。

216
00:16:14,254 --> 00:16:17,944
最怕的是嘴上说抹掉，账上却留着。

217
00:16:17,944 --> 00:16:21,454
再把源码位置补上，方便你回去核对。

218
00:16:21,454 --> 00:16:34,086
先盖章再开工的写法在 stream_events_utils 的第三百一十六到三百二十七行，不许重写历史这条规矩写在仓库规范文档的第九十一到一百行。

219
00:16:34,086 --> 00:16:45,516
每个出口收尾在 session 下 turn.rs 的第二千二百八十二到二千二百八十四行，三条出口的分岔在两千七百四十四到二千七百六十二行。

220
00:16:45,516 --> 00:16:55,276
提交判定在 turn_input 的第一百四十一到二百四十九行，只有常规任务收插话在第五百零七到五百一十九行。

221
00:16:55,276 --> 00:17:05,204
那一面翻牌在 state 下 turn.rs 的第三十七到五十六行，取信规则在 input_queue 的第二百零六到三百三十六行。

