WEBVTT

00:00.440 --> 00:00.800
Well.

00:00.800 --> 00:08.680
I am so excited to welcome you to week two of our time together in this journey into Agents Land.

00:08.840 --> 00:18.360
And as well, you know, week two is all about OpenAI agents SDK, formerly known as swarm, released

00:18.360 --> 00:25.040
very recently, the newest of our arrivals into the agents framework land, and one that I cannot wait

00:25.040 --> 00:26.360
to tell you all about.

00:27.000 --> 00:34.200
But first, isn't there always a but first, but first, before we can do OpenAI agents SDK, I have

00:34.200 --> 00:40.840
a sidebar, a topic that I have to cover with you, a very important topic, and it's to talk to you

00:40.840 --> 00:44.840
about asynchronous Python async IO.

00:45.160 --> 00:50.320
It's something which is common across all of the agent frameworks.

00:50.320 --> 00:57.080
So all of them make use of asynchronous Python, and it's something which you can get by without really

00:57.080 --> 00:58.120
understanding.

00:58.120 --> 01:02.540
There's a couple of rules that you can you can learn, and then you just follow those rules always.

01:02.540 --> 01:04.660
And it will just kind of work and that's okay.

01:05.060 --> 01:12.860
But I'm here to tell you that that's unsatisfactory and that it's much better to just take half an hour

01:12.860 --> 01:19.140
to get get to the bottom of this, to really understand it, to thrash it out until you're like, okay,

01:19.180 --> 01:24.420
I get a synchronous Python, I understand it, and if you do this, if you take the half an hour, you

01:24.420 --> 01:25.500
will thank me.

01:25.660 --> 01:30.340
It will again and again you will come across this and you'll you will be completely comfortable with

01:30.340 --> 01:30.540
it.

01:30.540 --> 01:31.780
And it's only half an hour.

01:31.780 --> 01:36.420
And so I've put a guide and the guides that will take you through this, so that you can be left with

01:36.420 --> 01:41.260
no questions in your mind at all about what it means to write asynchronous Python.

01:41.540 --> 01:45.900
And I'm also now going to cover it just very briefly at a high level.

01:45.900 --> 01:48.100
Let's talk Asyncio.

01:48.940 --> 01:52.900
Well, look, first of all there is like a short version, a simple version.

01:52.900 --> 01:56.260
This is what I mean when I say you can get by without really understanding it.

01:56.260 --> 02:05.840
So asyncio Io is a way of writing Python code, which is a kind of lightweight version of multithreading.

02:05.840 --> 02:11.520
So many people from a software engineering background will be familiar with the idea of multithreading.

02:11.520 --> 02:17.480
When you write code that can run concurrently, you can have multiple threads which are each executing

02:17.480 --> 02:23.480
code together, and there's often a lot of baggage that comes with that, some sort of framework stuff

02:23.480 --> 02:24.560
that comes with it.

02:24.840 --> 02:32.240
Well, async IO, which was introduced I think, in Python 3.5, is this very lightweight way of doing

02:32.240 --> 02:36.760
it, which doesn't actually involve threads at an operating system level.

02:36.760 --> 02:42.720
And it also doesn't involve what's called multiprocessing, which is when you spawn multiple Python

02:42.760 --> 02:44.720
processes and they all run together.

02:44.760 --> 02:48.280
This is another way of doing it that is super lightweight.

02:48.280 --> 02:53.680
And because it's super lightweight, it means that you can have thousands or tens of thousands of these

02:53.680 --> 02:57.340
things all running without consuming much resources at all.

02:57.340 --> 03:03.660
So it's a simple, easy peasy, lightweight way of writing code that can run concurrently.

03:03.780 --> 03:10.660
And particularly it's good when you have code that makes use of of input output, like waiting on networks.

03:10.820 --> 03:13.060
It allows other things to be running.

03:13.060 --> 03:19.340
Whilst one bit of code is waiting on network, and when you're running LM requests, you're mostly.

03:19.340 --> 03:25.140
If you're using paid APIs like OpenAI, most of the time is being spent waiting for stuff to feed back

03:25.140 --> 03:27.300
from a model running on the cloud.

03:27.300 --> 03:31.940
So there's a lot of waiting on networks, a lot of IO bound waiting.

03:32.220 --> 03:36.340
And as a result, asynchronous code is great to use.

03:36.340 --> 03:41.700
And when you're thinking of multi-agent frameworks, when potentially you have lots of these all hitting

03:41.700 --> 03:45.300
different paid APIs, it makes so much sense to be using it.

03:45.300 --> 03:50.540
And that is why all of the agent frameworks we'll look at use async IO.

03:51.020 --> 03:54.900
Okay, so with that preamble this is the short version.

03:55.040 --> 03:56.920
The short version is async.

03:56.920 --> 03:58.440
IO is actually two things.

03:58.440 --> 04:06.200
It's a a package called async IO that you can import, and it's also some language constructs baked

04:06.200 --> 04:07.360
into Python.

04:07.680 --> 04:13.000
And those language constructs include two keywords that you see right here.

04:13.240 --> 04:16.440
And one of those keywords is the word async.

04:16.840 --> 04:24.280
And anytime that you have a function which is potentially going to be able to run in a way that allows

04:24.320 --> 04:29.160
other things to happen concurrently, you simply put the word async before it.

04:29.160 --> 04:29.960
Like this.

04:30.280 --> 04:33.440
Async def do some processing.

04:33.720 --> 04:37.600
So def do some processing becomes async def do some processing.

04:37.640 --> 04:42.200
That's how you say this is a function which can run asynchronously.

04:42.920 --> 04:47.880
And when you call that function you don't just call do some processing like you're used to.

04:48.080 --> 04:50.480
You have to put the word await before it.

04:50.480 --> 04:56.820
So you say await, do some processing and if do some processing returns a string, then you can't just

04:56.820 --> 04:59.380
say result equals do some processing.

04:59.620 --> 05:02.140
You have to say result equals await.

05:02.340 --> 05:03.460
Do some processing.

05:03.900 --> 05:06.180
But otherwise it's just the same.

05:06.340 --> 05:07.700
So that's the short version.

05:07.700 --> 05:12.140
And if you wanted to get by without really understanding it, you could just follow those rules and

05:12.140 --> 05:14.220
keep going and it might be enough.

05:14.380 --> 05:16.860
But now let me tell you the bigger story.

05:17.860 --> 05:18.380
Okay.

05:18.420 --> 05:23.500
Now we're going to go a little bit deeper and talk about the real story with async IO.

05:23.740 --> 05:29.740
So look as I say, it's a lightweight alternative to multithreading or multiprocessing.

05:29.940 --> 05:33.140
Something like multithreading is implemented at the OS level.

05:33.140 --> 05:39.900
And it supports concurrent execution of different programs where the CPU or the CPU level has been managed

05:39.900 --> 05:45.020
to sort of switch between these two programs and treat them as if they're running at the same time.

05:45.500 --> 05:48.660
This is different in the case of async IO.

05:48.700 --> 05:50.780
So there are some some special keywords.

05:50.780 --> 05:52.360
There's this keyword async.

05:52.680 --> 05:54.760
If you define a function as.

05:54.800 --> 05:58.200
Async def and then the function instead of just def.

05:58.240 --> 05:58.920
The function.

05:59.160 --> 06:02.080
Then this thing is no longer actually called a function.

06:02.080 --> 06:04.280
It's known as a coroutine.

06:04.560 --> 06:06.360
Now most people still use the word function.

06:06.720 --> 06:11.360
You'll hear me saying function, but strictly speaking, anytime you see async def, you should say,

06:11.400 --> 06:13.560
okay, that's not a function, it is a coroutine.

06:14.080 --> 06:18.320
And that means it's something special that that Python can pause and resume.

06:19.360 --> 06:25.320
When you call a coroutine, it doesn't run unlike any other function that you call that does run.

06:25.320 --> 06:34.360
When you call a coroutine, it just returns a coroutine object, but nothing is executed to actually

06:34.360 --> 06:36.160
run that coroutine object.

06:36.160 --> 06:40.000
There are a few ways to do it, but the most common one, and the one that we will use all the time

06:40.360 --> 06:42.160
is to await it.

06:42.160 --> 06:48.320
So you say await, and then the coroutine object and that schedules it for execution.

06:48.800 --> 06:51.310
What does it mean to say it's scheduled for execution.

06:51.310 --> 06:57.390
Well, there is this piece of code written in the async IO library, which does what's known as an event

06:57.390 --> 07:03.590
loop, which means it's a kind of while loop that sort of iterates, and it takes this coroutine and

07:03.590 --> 07:08.230
it starts executing it, and it can only execute one coroutine at a time.

07:08.230 --> 07:09.710
It's not like multithreading.

07:09.710 --> 07:18.270
It's going to be executing this coroutine, except if this coroutine gets to a point when it's stopped

07:18.270 --> 07:21.670
and it's waiting for something like it's waiting for IO.

07:21.830 --> 07:22.590
Perhaps.

07:22.630 --> 07:26.710
Perhaps it's made a call to OpenAI and it's waiting for OpenAI to respond.

07:27.230 --> 07:35.630
At that point, the event loop can put that on pause and start executing a different one of the coroutines

07:35.630 --> 07:38.550
that's in its sort of list of coroutines waiting to be run.

07:38.990 --> 07:44.630
And if that hits a point when it's waiting on on IO, then the event loop can run another one or it

07:44.630 --> 07:46.110
can continue the first one.

07:46.670 --> 07:50.050
So it's a sort of manual way of handling multithreading.

07:50.250 --> 07:55.450
It's a manual approach to implementing multithreading, but only really working when something is blocked.

07:55.450 --> 07:56.410
Waiting on I o.

07:56.970 --> 08:02.090
And because of this, because it's sort of written at a code level and it's super simple and very easy

08:02.090 --> 08:03.090
to understand.

08:03.450 --> 08:05.090
It's it's also lightweight.

08:05.090 --> 08:08.650
You can have thousands or tens of thousands of these things.

08:08.850 --> 08:13.050
And, and so it's and it's really easy and quick to get this running.

08:13.210 --> 08:15.930
And that is why it's so popular.

08:15.930 --> 08:21.130
And that is why it's used so ubiquitously across a genetic frameworks.

08:21.610 --> 08:26.490
So I hope obviously I've only given you a bit of detail here, but I hope that gives you some perspective

08:26.490 --> 08:27.570
on what's going on.

08:27.810 --> 08:34.090
And when we look back now at at this, you can see that that whilst a simple interpretation is just

08:34.090 --> 08:38.650
to say, okay, whenever I use async I've got to use await.

08:39.130 --> 08:45.610
The deeper interpretation is to understand that when I say async def do some processing that's no longer

08:45.610 --> 08:49.110
a function, it's now a coroutine do some processing.

08:49.110 --> 08:53.630
We need to call await, do some processing in order to schedule that coroutine.

08:53.630 --> 09:00.150
And that is now blocking until that completes and the result will go into that variable result.

09:01.070 --> 09:05.270
I hope that made some sense, but if not, as I say, there's a whole guide.

09:05.270 --> 09:07.190
You should go and look at the guide now.

09:07.990 --> 09:12.470
And so just to beat this one to death, I'll give you a couple more examples.

09:12.470 --> 09:14.710
And then hopefully you've really got this.

09:15.030 --> 09:16.110
So look at this again.

09:16.110 --> 09:20.590
This is back to the async def do some processing.

09:20.790 --> 09:21.950
It's going to do some work.

09:21.950 --> 09:24.190
And then it's going to return the string done.

09:24.750 --> 09:31.390
If I just say like my coroutine equals do some processing, you might, you know, if you didn't know

09:31.390 --> 09:34.190
already about this, you might think that that will run do some processing.

09:34.190 --> 09:35.590
But of course it will not.

09:35.630 --> 09:41.870
It will return a coroutine object and that's what will go into the variable, my coroutine.

09:42.390 --> 09:45.590
In order to actually run it you have to call await.

09:45.590 --> 09:49.010
So you'd say my result equals await my coroutine.

09:49.010 --> 09:50.330
It's now going to run.

09:50.530 --> 09:51.490
And the result?

09:51.490 --> 09:55.170
The string done is going to go into my result.

09:55.690 --> 09:57.170
So that is the point.

09:57.170 --> 09:57.970
That's how it works.

09:57.970 --> 09:59.690
And of course you don't need to do you can.

09:59.690 --> 10:00.330
It's simpler than that.

10:00.330 --> 10:02.970
You can just say my result equals await.

10:03.010 --> 10:05.210
Do some processing, which is what we always do.

10:06.130 --> 10:10.850
And then just to hopefully sort of connect the dots to make this really click for you.

10:10.890 --> 10:14.450
There are other constructs you don't just have to call await.

10:14.610 --> 10:17.050
And then the name of of a coroutine.

10:17.050 --> 10:20.650
Because if that's all you could do, you might think, you might say to me, okay, hang on.

10:20.650 --> 10:24.610
But in that case there's no real there's nothing concurrent happening here.

10:24.650 --> 10:28.690
Every time I call await and then a coroutine, it will block until that finishes and then it will go

10:28.690 --> 10:29.610
on to the next.

10:29.810 --> 10:36.170
Well, there are a few other constructs in the async IO package that allow us to be more to do more

10:36.170 --> 10:40.290
interesting things, and this is one of them, and it's one that we will use this week in our code.

10:40.490 --> 10:47.110
And it's where you can say results, plural Results equals await.

10:47.110 --> 10:50.750
And then call this this function Asyncio dot gather.

10:51.150 --> 10:54.430
And you can then pass in multiple coroutines.

10:54.790 --> 10:56.630
And I'm sure you can imagine what happens.

10:56.630 --> 11:00.350
But the event loop is going to schedule all three.

11:00.510 --> 11:05.230
And as soon as one of them is blocking, the others will start running, or one of the others will run

11:05.230 --> 11:07.430
until it's blocking, and then the third one will run.

11:07.430 --> 11:13.390
And so all three of those coroutines will, will, will run, will execute.

11:13.390 --> 11:20.550
And the results as a list will go into the variable results, a list of each of the three results from

11:20.550 --> 11:23.030
each of those three coroutines.

11:23.710 --> 11:26.590
So in some ways it's kind of fake multithreading.

11:26.870 --> 11:29.550
It's sort of brute force multithreading.

11:29.550 --> 11:35.470
It's multithreading not at the operating system level, but multithreading implemented almost manually

11:35.470 --> 11:41.390
with this event loop, and just handling things like IO blocking and being able to schedule the next

11:41.390 --> 11:41.750
one.

11:42.190 --> 11:43.470
That's how to think about it.
