1
00:00:00,440 --> 00:00:00,800
出色地。

2
00:00:00,800 --> 00:00:08,680
我非常高兴地欢迎您来到我们一起踏上特工之地之旅的第二周。

3
00:00:08,840 --> 00:00:18,360
另外，您知道，第二周的主题是开放 AI 代理 SDK（以前称为 swarm），已发布

4
00:00:18,360 --> 00:00:25,040
最近，我们最新的成员进入了代理框架领域，我已经等不及了

5
00:00:25,040 --> 00:00:26,360
告诉你一切。

6
00:00:27,000 --> 00:00:34,200
但是首先，不是总有一个但是首先但是首先，在我们可以做开放的AI代理SDK之前，我有

7
00:00:34,200 --> 00:00:40,840
一个侧边栏，一个我要跟你讲的话题，一个非常重要的话题，就是要和你谈谈

8
00:00:40,840 --> 00:00:44,840
关于异步 Python 异步 IO。

9
00:00:45,160 --> 00:00:50,320
这是所有代理框架中共有的东西。

10
00:00:50,320 --> 00:00:57,080
所以它们都使用异步Python，而且你不需要真正的就可以做到这一点

11
00:00:57,080 --> 00:00:58,120
理解。

12
00:00:58,120 --> 00:01:02,540
您可以学习一些规则，然后始终遵循这些规则。

13
00:01:02,540 --> 00:01:04,660
这会起作用，没关系。

14
00:01:05,060 --> 00:01:12,860
但我在这里告诉你，这并不令人满意，最好只花半个小时

15
00:01:12,860 --> 00:01:19,140
深入了解这个问题，真正理解它，仔细研究它，直到你觉得，好吧，

16
00:01:19,180 --> 00:01:24,420
我得到了异步Python，我理解它，如果你这样做，如果你花半个小时，你

17
00:01:24,420 --> 00:01:25,500
会感谢我的。

18
00:01:25,660 --> 00:01:30,340
你会一次又一次地遇到这个，你会完全适应

19
00:01:30,340 --> 00:01:30,540
它。

20
00:01:30,540 --> 00:01:31,780
而且只有半个小时。

21
00:01:31,780 --> 00:01:36,420
因此，我提供了一份指南，以及将引导您完成此过程的指南，以便您可以

22
00:01:36,420 --> 00:01:41,260
您对编写异步 Python 意味着什么毫无疑问。

23
00:01:41,540 --> 00:01:45,900
我现在也将在高层次上非常简短地介绍它。

24
00:01:45,900 --> 00:01:48,100
我们来谈谈异步IO。

25
00:01:48,940 --> 00:01:52,900
嗯，看，首先有一个简短的版本，一个简单的版本。

26
00:01:52,900 --> 00:01:56,260
这就是我说即使不真正理解也能过得去的意思。

27
00:01:56,260 --> 00:02:05,840
所以async IO是一种编写Python代码的方式，它是多线程的一种轻量级版本。

28
00:02:05,840 --> 00:02:11,520
许多具有软件工程背景的人都会熟悉多线程的概念。

29
00:02:11,520 --> 00:02:17,480
当您编写可以并发运行的代码时，可以有多个线程，每个线程都在执行

30
00:02:17,480 --> 00:02:23,480
一起编码，通常会带来很多包袱，比如某种框架的东西

31
00:02:23,480 --> 00:02:24,560
随之而来的。

32
00:02:24,840 --> 00:02:32,240
嗯，异步 IO，我认为是在 Python 3.5 中引入的，是一种非常轻量级的做法

33
00:02:32,240 --> 00:02:36,760
它实际上并不涉及操作系统级别的线程。

34
00:02:36,760 --> 00:02:42,720
而且它也不涉及所谓的多处理，即生成多个 Python

35
00:02:42,760 --> 00:02:44,720
进程并且它们都一起运行。

36
00:02:44,760 --> 00:02:48,280
这是另一种超轻量级的实现方法。

37
00:02:48,280 --> 00:02:53,680
而且因为它超轻，这意味着您可以拥有数千或数万个这样的

38
00:02:53,680 --> 00:02:57,340
一切都在不消耗太多资源的情况下运行。

39
00:02:57,340 --> 00:03:03,780
因此，这是一种简单、轻松、轻量级的编写可以并发运行的代码的方法。

40
00:03:03,780 --> 00:03:10,660
尤其是当您的代码使用输入输出（例如等待网络）时，它尤其有用。

41
00:03:10,820 --> 00:03:13,060
它允许其他事物运行。

42
00:03:13,060 --> 00:03:19,340
当一小部分代码在网络上等待时，当您运行 LM 请求时，您大部分时间都在等待。

43
00:03:19,340 --> 00:03:25,140
如果您使用 OpenAI 等付费 API，则大部分时间都花在等待反馈上

44
00:03:25,140 --> 00:03:27,300
来自在云上运行的模型。

45
00:03:27,300 --> 00:03:31,940
所以网络上有很多等待，很多 IO 绑定等待。

46
00:03:32,220 --> 00:03:36,340
因此，异步代码非常好用。

47
00:03:36,340 --> 00:03:41,700
当您考虑多代理框架时，您可能会遇到很多这样的情况

48
00:03:41,700 --> 00:03:45,300
不同的 API，使用它非常有意义。

49
00:03:45,300 --> 00:03:50,540
这就是我们将要讨论的所有框架都使用异步 IO 的原因。

50
00:03:51,020 --> 00:03:51,540
好的。

51
00:03:51,780 --> 00:03:54,900
这是序言的简短版本。

52
00:03:55,040 --> 00:03:56,920
简短的版本是异步的。

53
00:03:56,920 --> 00:03:58,440
IO实际上是两个东西。

54
00:03:58,440 --> 00:04:06,200
这是一个可以导入的名为 async IO 的包，它也是一些烘焙的语言结构

55
00:04:06,200 --> 00:04:07,360
进入Python。

56
00:04:07,680 --> 00:04:12,960
这些语言结构包括您在此处看到的两个关键字。

57
00:04:13,240 --> 00:04:16,440
这些关键字之一是“异步”一词。

58
00:04:16,840 --> 00:04:24,280
任何时候你有一个函数，它可能能够以允许的方式运行

59
00:04:24,320 --> 00:04:32,520
其他同时发生的事情，你只需将 async 这个词放在它前面，就像 async def 那样

60
00:04:32,520 --> 00:04:37,600
一些处理，所以 def do someprocessing 变成 async def do someprocessing。

61
00:04:37,640 --> 00:04:42,200
这就是你所说的这是一个可以异步运行的函数。

62
00:04:42,920 --> 00:04:47,880
当您调用该函数时，您不只是像以前那样调用进行一些处理。

63
00:04:48,080 --> 00:04:50,480
您必须在其前面加上“await”一词。

64
00:04:50,480 --> 00:04:56,820
所以你说等待，做一些处理，如果做一些处理返回一个字符串，那么你不能只是

65
00:04:56,820 --> 00:04:59,380
说结果等于做一些处理。

66
00:04:59,620 --> 00:05:02,140
你必须说结果等于等待。

67
00:05:02,340 --> 00:05:03,460
做一些处理。

68
00:05:03,900 --> 00:05:06,180
但除此之外都是一样的。

69
00:05:06,340 --> 00:05:07,700
这就是简短的版本。

70
00:05:07,700 --> 00:05:12,140
如果你想在没有真正理解它的情况下度过难关，你可以遵循这些规则并

71
00:05:12,140 --> 00:05:14,220
继续下去，可能就足够了。

72
00:05:14,380 --> 00:05:16,860
但现在让我告诉你更大的故事。

73
00:05:17,860 --> 00:05:18,380
好的。

74
00:05:18,420 --> 00:05:23,500
现在我们将更深入地讨论异步 IO 的真实故事。

75
00:05:23,740 --> 00:05:29,740
所以正如我所说，它是多线程或多处理的轻量级替代方案。

76
00:05:29,940 --> 00:05:33,140
诸如多线程之类的东西是在操作系统级别实现的。

77
00:05:33,140 --> 00:05:39,340
并且它支持不同CPU、CPU级别的程序的并发执行

78
00:05:39,340 --> 00:05:44,500
设法在这两个程序之间进行切换，并将它们视为同时运行

79
00:05:44,500 --> 00:05:45,020
时间。

80
00:05:45,500 --> 00:05:48,660
这在异步 IO 的情况下有所不同。

81
00:05:48,700 --> 00:05:50,780
所以有一些特殊的关键词。

82
00:05:50,780 --> 00:05:52,360
有这个关键字async。

83
00:05:52,680 --> 00:05:59,560
如果您将函数定义为 async def，然后定义该函数，而不是仅定义该函数，那么

84
00:05:59,560 --> 00:06:02,080
这个东西实际上已经不叫函数了。

85
00:06:02,080 --> 00:06:04,280
它被称为协程。

86
00:06:04,560 --> 00:06:06,360
现在大多数人仍然使用函数这个词。

87
00:06:06,720 --> 00:06:11,360
你会听到我说函数，但严格来说，每当你看到 async def 时，你应该说，

88
00:06:11,400 --> 00:06:13,560
好吧，这不是一个函数，它是一个协程。

89
00:06:14,080 --> 00:06:18,320
这意味着 Python 可以暂停和恢复，这是很特别的。

90
00:06:19,360 --> 00:06:25,320
当您调用协程时，它不会像您调用的任何其他运行的函数那样运行。

91
00:06:25,320 --> 00:06:34,360
当你调用协程时，它只是返回一个协程对象，但实际上并没有执行任何操作

92
00:06:34,360 --> 00:06:36,160
运行该协程对象。

93
00:06:36,160 --> 00:06:40,000
有几种方法可以做到这一点，但最常见的一种，也是我们将一直使用的一种

94
00:06:40,360 --> 00:06:42,160
就是等待它。

95
00:06:42,160 --> 00:06:48,320
所以你说await，然后是协程对象并安排它执行。

96
00:06:48,800 --> 00:06:51,310
计划执行是什么意思？

97
00:06:51,310 --> 00:06:57,390
嗯，异步 IO 库中有一段代码，它执行所谓的事件

98
00:06:57,390 --> 00:07:04,310
循环，这意味着它是一种迭代的 while 循环，它采用这个协程并开始

99
00:07:04,310 --> 00:07:08,230
执行它，并且它一次只能执行一个协程。

100
00:07:08,230 --> 00:07:09,710
它不像多线程。

101
00:07:09,710 --> 00:07:18,270
它将执行该协程，除非该协程到达停止点

102
00:07:18,270 --> 00:07:21,670
它正在等待类似等待 IO 的东西。

103
00:07:21,830 --> 00:07:22,590
也许。

104
00:07:22,630 --> 00:07:26,710
也许它已向 OpenAI 发出了呼叫，并且正在等待 OpenAI 响应。

105
00:07:27,230 --> 00:07:35,630
此时，事件循环可以暂停该事件并开始执行另一个协程

106
00:07:35,630 --> 00:07:38,550
它位于等待运行的协程列表中。

107
00:07:38,990 --> 00:07:44,630
如果在等待 IO 时达到某一点，那么事件循环可以运行另一个事件循环或者它

108
00:07:44,630 --> 00:07:46,110
可以继续第一个。

109
00:07:46,670 --> 00:07:50,050
所以这是一种处理多线程的手动方法。

110
00:07:50,250 --> 00:07:55,450
这是一种实现多线程的手动方法，但只有在某些内容被阻塞时才真正起作用。

111
00:07:55,450 --> 00:07:56,450
等待我o。

112
00:07:56,970 --> 00:08:02,090
正因为如此，因为它是在代码级别编写的，并且非常简单且非常容易

113
00:08:02,090 --> 00:08:05,090
要理解，它也是轻量级的。

114
00:08:05,090 --> 00:08:08,650
你可以拥有数千或数万个这样的东西。

115
00:08:08,850 --> 00:08:13,050
而且，运行起来非常简单快捷。

116
00:08:13,210 --> 00:08:15,930
这就是它如此受欢迎的原因。

117
00:08:15,930 --> 00:08:21,130
这就是为什么它在遗传框架中如此普遍地使用。

118
00:08:21,610 --> 00:08:26,490
所以我希望显然我在这里只给了你一些细节，但我希望这能给你一些观点

119
00:08:26,490 --> 00:08:27,610
关于正在发生的事情。

120
00:08:27,810 --> 00:08:34,090
当我们现在回顾这一点时，你会发现虽然简单的解释只是

121
00:08:34,090 --> 00:08:38,650
好吧，每当我使用 async 时，我就必须使用 wait。

122
00:08:39,130 --> 00:08:45,610
更深入的解释是要理解当我说 async def 做一些不再是的处理时

123
00:08:45,610 --> 00:08:49,110
一个函数，它现在是一个协程，做一些处理。

124
00:08:49,110 --> 00:08:53,630
我们需要调用await，进行一些处理以安排该协程。

125
00:08:53,630 --> 00:09:00,150
现在正在阻塞，直到完成并且结果将进入该变量结果。

126
00:09:01,070 --> 00:09:05,270
我希望这有一定道理，但如果没有，正如我所说，这里有一个完整的指南。

127
00:09:05,270 --> 00:09:07,190
你现在应该去看看指南。

128
00:09:08,030 --> 00:09:12,470
为了彻底解决这个问题，我再举几个例子。

129
00:09:12,470 --> 00:09:14,670
然后希望你真的明白了。

130
00:09:15,030 --> 00:09:16,110
所以再看看这个。

131
00:09:16,110 --> 00:09:20,590
这又回到async def做一些处理了。

132
00:09:20,790 --> 00:09:21,950
它将做一些工作。

133
00:09:21,950 --> 00:09:24,190
然后它将返回完成的字符串。

134
00:09:24,750 --> 00:09:31,390
如果我只是说我的协程等于做一些处理，你可能，你知道，如果你不知道

135
00:09:31,390 --> 00:09:34,190
已经了解了这一点，您可能会认为这将运行一些处理。

136
00:09:34,190 --> 00:09:35,590
但当然不会。

137
00:09:35,630 --> 00:09:41,870
它将返回一个协程对象，这就是变量（我的协程）中的内容。

138
00:09:42,390 --> 00:09:45,590
为了实际运行它，你必须调用await。

139
00:09:45,590 --> 00:09:49,010
所以你会说我的结果等于等待我的协程。

140
00:09:49,010 --> 00:09:50,330
现在它要运行了。

141
00:09:50,530 --> 00:09:51,490
结果呢？

142
00:09:51,490 --> 00:09:55,170
完成的字符串将进入我的结果。

143
00:09:55,690 --> 00:09:57,170
这就是重点。

144
00:09:57,170 --> 00:09:57,970
这就是它的工作原理。

145
00:09:57,970 --> 00:09:59,450
当然你不需要这样做。

146
00:09:59,690 --> 00:10:00,330
比那更简单。

147
00:10:00,330 --> 00:10:02,970
你可以说我的结果等于等待。

148
00:10:03,010 --> 00:10:05,210
做一些处理，这是我们一直做的事情。

149
00:10:06,130 --> 00:10:10,850
然后只是希望将这些点连接起来，让这真正适合你。

150
00:10:10,850 --> 00:10:14,490
还有其他构造，您不必只调用 wait。

151
00:10:14,610 --> 00:10:19,130
然后是协程的名称，因为如果这就是你所能做的，你可能会想，你可能

152
00:10:19,130 --> 00:10:20,650
对我说，好吧，坚持住。

153
00:10:20,650 --> 00:10:24,610
但在这种情况下，这里就没有真正发生任何并发事件。

154
00:10:24,650 --> 00:10:28,690
每次我调用await，然后调用协程时，它都会阻塞，直到完成，然后它就会继续

155
00:10:28,690 --> 00:10:29,610
继续下一步。

156
00:10:29,810 --> 00:10:36,170
嗯，异步 IO 包中还有一些其他构造，可以让我们做得更多

157
00:10:36,170 --> 00:10:40,290
有趣的事情，这是其中之一，也是我们本周将在代码中使用的事情。

158
00:10:40,490 --> 00:10:47,110
在这里你可以说结果，复数结果等于等待。

159
00:10:47,110 --> 00:10:50,750
然后调用这个函数Asyncio dot Gather。

160
00:10:51,150 --> 00:10:54,430
然后你可以传入多个协程。

161
00:10:54,790 --> 00:10:56,630
我相信你可以想象会发生什么。

162
00:10:56,630 --> 00:11:00,350
但事件循环将安排这三个事件。

163
00:11:00,510 --> 00:11:05,230
一旦其中一个阻塞，其他的就会开始运行，或者其他的一个会运行

164
00:11:05,230 --> 00:11:07,430
直到它被阻塞，然后第三个将运行。

165
00:11:07,430 --> 00:11:13,390
因此，所有这三个协程将、将、将运行、将执行。

166
00:11:13,390 --> 00:11:20,550
结果作为列表将进入变量 results，即来自的三个结果中的每一个的列表

167
00:11:20,550 --> 00:11:23,030
这三个协程中的每一个。

168
00:11:23,710 --> 00:11:26,590
所以在某些方面它是一种假多线程。

169
00:11:26,870 --> 00:11:29,550
这是一种强力多线程。

170
00:11:29,550 --> 00:11:35,470
它不是操作系统级别的多线程，而是几乎手动实现的多线程

171
00:11:35,470 --> 00:11:41,390
使用此事件循环，只需处理 IO 阻塞之类的事情并能够安排下一个

172
00:11:41,390 --> 00:11:41,750
一。

173
00:11:42,190 --> 00:11:43,470
就是这样想的。