WEBVTT

00:00.300 --> 00:01.710
-: Let's dive in and have a look

00:01.710 --> 00:03.840
at LangChain expression language.

00:03.840 --> 00:06.180
To start with, what we're gonna look at is something called

00:06.180 --> 00:08.070
the runnable protocol.

00:08.070 --> 00:10.680
All of LangChain expression language is built on something

00:10.680 --> 00:13.350
called a runnable protocol syntax.

00:13.350 --> 00:17.520
And so by understanding a lot of the primitives behind this,

00:17.520 --> 00:19.170
you'll gain a thorough understanding

00:19.170 --> 00:22.110
of how you can apply LangChain expression language.

00:22.110 --> 00:24.600
So let's have a look inside of this notebook.

00:24.600 --> 00:26.220
The first thing that we're gonna introduce

00:26.220 --> 00:28.410
is something called the runnable lambda.

00:28.410 --> 00:30.270
Now, a runnable lambda is a little bit like

00:30.270 --> 00:32.580
a lambda function that you know in Python.

00:32.580 --> 00:35.100
So for example, I can make a Lambda function

00:35.100 --> 00:38.580
and I can do something like this where the value

00:38.580 --> 00:41.310
of X is equal to X plus one, right?

00:41.310 --> 00:44.160
So I could assign this to a word like function,

00:44.160 --> 00:46.380
and then I can say function

00:46.380 --> 00:49.740
one plus one would be equal to two.

00:49.740 --> 00:51.690
Now, in LangChain expression language,

00:51.690 --> 00:55.950
we can't necessarily use a standardized Lambda function

00:55.950 --> 00:58.710
inside a Python to start with at least.

00:58.710 --> 01:00.630
So each time we have to start

01:00.630 --> 01:03.990
with a LangChain expression language syntax,

01:03.990 --> 01:07.200
you'll probably use something like a runnable Lambda.

01:07.200 --> 01:09.373
So in this scenario, you can see

01:09.373 --> 01:11.040
we have our runnable Lambda,

01:11.040 --> 01:12.990
and inside there all we are doing is doing

01:12.990 --> 01:14.970
the exact same thing that I did above,

01:14.970 --> 01:19.530
which is basically runnable Lambda X, X plus one, right?

01:19.530 --> 01:21.360
So we can make a chain outta this,

01:21.360 --> 01:23.070
so we can just save that to a variable.

01:23.070 --> 01:25.387
And then if we have a look here,

01:25.387 --> 01:26.370
you'll see we've got this runnable Lambda.

01:26.370 --> 01:28.560
Now because LangChain expression language

01:28.560 --> 01:30.568
comes with a lot of different functionality,

01:30.568 --> 01:33.810
you get some functions inside of this by default.

01:33.810 --> 01:38.160
So for example, you've got .batch, .Abatch, .invoke,

01:38.160 --> 01:41.010
and you've also got streaming capabilities as well.

01:41.010 --> 01:43.364
Now if we do for example, .invoke,

01:43.364 --> 01:45.450
you'll see that we get back,

01:45.450 --> 01:48.330
if we put one in, then it goes into our Lambda function

01:48.330 --> 01:51.780
and it will get one plus one added to make two.

01:51.780 --> 01:54.090
Or we could do a chain .evoke.

01:54.090 --> 01:55.500
You'll notice that we don't have

01:55.500 --> 01:58.650
to have runnable Lambda on the second expression.

01:58.650 --> 02:01.830
You only need a runnable Lambda on the first expression

02:01.830 --> 02:04.247
before you can start using Lambda functions.

02:04.247 --> 02:06.930
Also notice that we are using something

02:06.930 --> 02:08.790
called the pipe operator.

02:08.790 --> 02:10.740
Now the pipe operator basically states

02:10.740 --> 02:12.270
that we're creating a runnable Lambda,

02:12.270 --> 02:15.270
and then the output of that runnable Lambda

02:15.270 --> 02:19.530
will get piped into this function, this Lambda here.

02:19.530 --> 02:23.138
So what ends up happening is when we have this line here,

02:23.138 --> 02:26.550
sequence invoke, that will then do one plus one,

02:26.550 --> 02:30.090
and then afterwards we then pipe that in, which is two,

02:30.090 --> 02:33.750
and then we times X here by two to get four.

02:33.750 --> 02:36.120
So you'll see if I've run this and you've got four,

02:36.120 --> 02:38.070
and it's also possible to do this

02:38.070 --> 02:39.960
with the .batch functionality.

02:39.960 --> 02:42.750
So the .batch will do a list of results

02:42.750 --> 02:45.150
and it'll run it through the runnable sequence.

02:45.150 --> 02:46.830
So in this scenario, we've got one, two

02:46.830 --> 02:50.040
and three, which will appropriately get put into this first

02:50.040 --> 02:52.110
runnable Lambda each at a time.

02:52.110 --> 02:54.300
And then also they will get

02:54.300 --> 02:56.520
piped out into this second lambda.

02:56.520 --> 02:59.010
So then those will become four, six and eight.

02:59.010 --> 03:02.670
So the .batch and the .invoke are our general ways

03:02.670 --> 03:04.680
of calling these Lambdas.

03:04.680 --> 03:07.470
There's also something called the Runnable parallel.

03:07.470 --> 03:09.510
Now, the Runnable parallel is slightly different

03:09.510 --> 03:11.310
in the sense of it allows you

03:11.310 --> 03:14.130
to have something that goes into two.

03:14.130 --> 03:16.110
We've got an example here we've got sequence,

03:16.110 --> 03:19.920
runnable Lambda, X plus X is plus one,

03:19.920 --> 03:23.130
and then that goes into two dictionary keys.

03:23.130 --> 03:25.500
Notice the pipe operator again.

03:25.500 --> 03:27.990
So again, we've got this pipe operator,

03:27.990 --> 03:30.690
which is saying this is the input

03:30.690 --> 03:32.160
into our runnable Lambda.

03:32.160 --> 03:36.810
The output then goes into a dictionary with two keys,

03:36.810 --> 03:38.940
mul two and mul five,

03:38.940 --> 03:41.377
and that is gonna be a Lambda function.

03:41.377 --> 03:42.330
So this is Runnable Lambda,

03:42.330 --> 03:45.030
and that will be timesing the value by two

03:45.030 --> 03:47.130
and also timesing the value by five.

03:47.130 --> 03:48.720
So I can do something like this

03:48.720 --> 03:51.450
and I can say, take the one which then gets added.

03:51.450 --> 03:52.473
So that's two.

03:54.008 --> 03:55.890
And then after that, then I will both times two by two

03:55.890 --> 03:58.200
and times two by five at the same time

03:58.200 --> 04:00.524
and put those into separate dictionary keys.

04:00.524 --> 04:04.620
Now there's another way of writing the Runnable parallel.

04:04.620 --> 04:05.970
So if we just scroll down here

04:05.970 --> 04:08.400
and have a look, this is the equivalent

04:08.400 --> 04:09.990
of making a runnable parallel.

04:09.990 --> 04:13.136
So you can either use a dictionary with the keys

04:13.136 --> 04:15.870
or you can actually define it upfront

04:15.870 --> 04:17.463
as a runnable parallel.

04:18.960 --> 04:21.660
Let's have a look at this example up above

04:21.660 --> 04:24.150
where we have a runnable Lambda,

04:24.150 --> 04:26.790
which is saying, take X and add one.

04:26.790 --> 04:31.410
The output of that should go into two dictionary keys,

04:31.410 --> 04:33.390
mul two and mul five.

04:33.390 --> 04:35.730
So the output of this will then get times by two

04:35.730 --> 04:39.090
and times by five, but you don't just have to stop there.

04:39.090 --> 04:40.860
You could finally pipe

04:40.860 --> 04:44.070
that dictionary into a new Lambda function

04:44.070 --> 04:48.870
and take the addition of the mul two of the mul five key.

04:48.870 --> 04:51.150
And this shows you that we can easily pipe

04:51.150 --> 04:53.040
between not only a Lambda function,

04:53.040 --> 04:55.380
but also into a Python dictionary

04:55.380 --> 04:57.810
and then back into a Lambda function as well.

04:57.810 --> 04:59.130
You'll see the same thing happening here,

04:59.130 --> 05:01.044
which is just to demonstrate

05:01.044 --> 05:04.143
that a runnable parallel and a parallel to,

05:05.687 --> 05:06.520
although they are different

05:07.560 --> 05:08.851
in terms of how you construct them,

05:08.851 --> 05:10.107
they basically are the same thing

05:10.107 --> 05:11.359
where we have this dictionary

05:11.359 --> 05:13.800
and it's doing several things from the previous output,

05:13.800 --> 05:15.660
and the same is true of the Runnable parallel.

05:15.660 --> 05:18.090
We can then set up our parallel like this.

05:18.090 --> 05:20.430
So we have the runnable parallel

05:20.430 --> 05:22.194
with the two dictionary keys,

05:22.194 --> 05:24.690
mul two and mul five, which will then get piped

05:24.690 --> 05:27.210
into a new Runnable Lambda,

05:27.210 --> 05:29.072
remembering that the dictionary keys

05:29.072 --> 05:30.060
are mul two and mul five.

05:30.060 --> 05:33.000
We can then add those keys together and invoke those those.

05:33.000 --> 05:36.030
This here is basically saying, take in my dictionary,

05:36.030 --> 05:38.488
which contains two keys.

05:38.488 --> 05:40.950
And you can see what these are would be input one

05:40.950 --> 05:42.000
and input two.

05:42.000 --> 05:44.698
And so basically what I'm saying is get the keys of this

05:44.698 --> 05:48.480
input one and input two, assign those,

05:48.480 --> 05:51.335
and then after those have become mul two

05:51.335 --> 05:52.650
and mul five, by timing them by two

05:52.650 --> 05:55.260
and five, then pipe them into this runnable Lambda

05:55.260 --> 05:57.810
expression here, and then add those together.

05:57.810 --> 06:00.630
So they're slightly different, but very similar.

06:00.630 --> 06:03.752
This one is allowing us to index on a dictionary.

06:03.752 --> 06:06.007
Whilst this one is basically just saying,

06:06.007 --> 06:08.584
let's do something twice to the data,

06:08.584 --> 06:11.310
and then let's output that on two separate keys,

06:11.310 --> 06:12.660
which we then add.

06:12.660 --> 06:14.880
Whilst on this example, we're actually saying,

06:14.880 --> 06:16.441
let's take a dictionary.

06:16.441 --> 06:19.650
And every time we get a dictionary is the input

06:19.650 --> 06:22.560
in our .invoke, what we really want to do,

06:22.560 --> 06:25.920
get the input one key, get the input two key,

06:25.920 --> 06:28.080
and then make these new dictionary keys,

06:28.080 --> 06:30.729
which we then at this point would then do

06:30.729 --> 06:34.290
a mul two plus mul five edition

06:34.290 --> 06:36.120
inside of a Runnable Lambda.

06:36.120 --> 06:38.850
So you can see all the different types of patterns

06:38.850 --> 06:41.266
that you can use when it comes to building this.

06:41.266 --> 06:44.100
Now, you do need a runnable at the start

06:44.100 --> 06:46.450
of a LangChain expression language expression.

06:46.450 --> 06:48.900
So for example, if we try

06:48.900 --> 06:52.590
and do this where we have a Lambda, which is XX plus one,

06:52.590 --> 06:54.955
and we try and pipe that into a runnable parallel,

06:54.955 --> 06:57.990
you'll see that when we try to run the dot invoke,

06:57.990 --> 06:59.730
it will actually fail here.

06:59.730 --> 07:01.950
And the reason why is that this Lambda

07:01.950 --> 07:03.990
doesn't have an invoke method.

07:03.990 --> 07:05.913
It's basically just a Lambda function.

07:05.913 --> 07:09.150
So what you have to do is at least to start with,

07:09.150 --> 07:13.140
you need either a runnable parallel or a Runnable Lambda

07:13.140 --> 07:15.960
or any kind of runnable that would work.

07:15.960 --> 07:17.580
And after that, you can then start

07:17.580 --> 07:20.739
to use standard Lambda functions inside a Python.

07:20.739 --> 07:23.700
So you'll see in this expression we've got a Lambda,

07:23.700 --> 07:25.920
which has X, X plus one,

07:25.920 --> 07:27.780
and then piping that into parallel,

07:27.780 --> 07:31.830
which we can see here is just a dictionary with two keys,

07:31.830 --> 07:34.560
mul two and mul five, and then timesing it by two

07:34.560 --> 07:36.360
and five respectably.

07:36.360 --> 07:38.250
When we call the .invoke, you'll then see

07:38.250 --> 07:39.900
that we get back our Python dictionary

07:39.900 --> 07:42.137
with both mul two and mul five.

07:42.137 --> 07:45.629
We can also combine several steps into a runnable.

07:45.629 --> 07:49.770
So you can see here we've got item one and item two,

07:49.770 --> 07:52.470
and then what we're doing here is then on the fly

07:52.470 --> 07:54.934
doing a Lambda X hello and a Lambda X world.

07:54.934 --> 07:58.620
What we can then do is we can make a function,

07:58.620 --> 08:01.110
which takes an X, which will be a dictionary,

08:01.110 --> 08:04.290
and we can return item one and item two.

08:04.290 --> 08:07.110
So item one would be here, item two would be here,

08:07.110 --> 08:08.970
and we can add those two together.

08:08.970 --> 08:11.520
Now, we can say, take our runnable parallel

08:11.520 --> 08:14.250
and pipe that into this combined function.

08:14.250 --> 08:16.230
So the output of the runnable parallel,

08:16.230 --> 08:17.380
which is a dictionary

08:18.523 --> 08:20.400
with two keys, gets piped and becomes X.

08:20.400 --> 08:24.690
Once it becomes X, then what we do is we then say,

08:24.690 --> 08:26.070
okay, right, that's great.

08:26.070 --> 08:28.860
So because we didn't use anything from the previous step,

08:28.860 --> 08:32.940
that's why we're just doing X here and for hello and world.

08:32.940 --> 08:35.610
So we didn't actually pass any data in.

08:35.610 --> 08:36.690
So when I run this code,

08:36.690 --> 08:38.640
you'll see that we just get Hello world.

08:38.640 --> 08:42.510
So what's really happening here is we have a parallel,

08:42.510 --> 08:44.730
which is an item one and item two,

08:44.730 --> 08:46.860
it doesn't have any current inputs.

08:46.860 --> 08:49.770
I could also have an input here, for example,

08:49.770 --> 08:52.032
so I could have the name.

08:52.032 --> 08:53.280
(person typing)

08:53.280 --> 08:55.834
So if we were to change this slightly, let's have a go.

08:55.834 --> 08:59.253
We could say X, and we could say,

09:00.435 --> 09:01.823
we want to take in the name,

09:04.353 --> 09:05.490
hello, and then we'll put a space,

09:05.490 --> 09:07.920
and then now it will complain about a key error,

09:07.920 --> 09:09.420
which is missing with name.

09:09.420 --> 09:12.083
So I can then fill that in with James.

09:12.083 --> 09:16.530
And now you'll see I get, hello James world.

09:16.530 --> 09:19.950
You can say, welcome to the world, something like this.

09:19.950 --> 09:22.953
So basically you can use inputs,

09:24.088 --> 09:25.620
but the important point that we have here

09:25.620 --> 09:28.890
is we are basically saying, take this name,

09:28.890 --> 09:31.170
use it once in this runnable parallel.

09:31.170 --> 09:32.970
And then in the item two,

09:32.970 --> 09:34.860
we're just gonna say, welcome to the world.

09:34.860 --> 09:37.080
When we do the .invoke,

09:37.080 --> 09:38.820
that will then get past the parallel,

09:38.820 --> 09:40.380
the input for the name.

09:40.380 --> 09:43.020
And then after that we then combine that together,

09:43.020 --> 09:44.730
these two dictionary keys,

09:44.730 --> 09:47.040
which is the item one and item two keys.

09:47.040 --> 09:49.821
So you can see how this starts to build up.

09:49.821 --> 09:51.900
What I would recommend doing is spending

09:51.900 --> 09:53.580
another five minutes now

09:53.580 --> 09:55.140
before moving on to the next video,

09:55.140 --> 09:57.150
and just try and create your own chains

09:57.150 --> 10:00.531
using a mixture of runnable parallels.

10:00.531 --> 10:05.430
And also try and use a mixture of runnable Lambdas,

10:05.430 --> 10:06.990
and python dictionaries.

10:06.990 --> 10:09.485
Try piping some data to forward steps

10:09.485 --> 10:12.240
to see how does this work.

10:12.240 --> 10:14.430
And then once you feel a bit more comfortable with it,

10:14.430 --> 10:16.053
then move on to the next video.
