-: Let's dive in and have a look
at LangChain expression language.
To start with, what we're gonna look at is something called
the runnable protocol.
All of LangChain expression language is built on something
called a runnable protocol syntax.
And so by understanding a lot of the primitives behind this,
you'll gain a thorough understanding
of how you can apply LangChain expression language.
So let's have a look inside of this notebook.
The first thing that we're gonna introduce
is something called the runnable lambda.
Now, a runnable lambda is a little bit like
a lambda function that you know in Python.
So for example, I can make a Lambda function
and I can do something like this where the value
of X is equal to X plus one, right?
So I could assign this to a word like function,
and then I can say function
one plus one would be equal to two.
Now, in LangChain expression language,
we can't necessarily use a standardized Lambda function
inside a Python to start with at least.
So each time we have to start
with a LangChain expression language syntax,
you'll probably use something like a runnable Lambda.
So in this scenario, you can see
we have our runnable Lambda,
and inside there all we are doing is doing
the exact same thing that I did above,
which is basically runnable Lambda X, X plus one, right?
So we can make a chain outta this,
so we can just save that to a variable.
And then if we have a look here,
you'll see we've got this runnable Lambda.
Now because LangChain expression language
comes with a lot of different functionality,
you get some functions inside of this by default.
So for example, you've got .batch, .Abatch, .invoke,
and you've also got streaming capabilities as well.
Now if we do for example, .invoke,
you'll see that we get back,
if we put one in, then it goes into our Lambda function
and it will get one plus one added to make two.
Or we could do a chain .evoke.
You'll notice that we don't have
to have runnable Lambda on the second expression.
You only need a runnable Lambda on the first expression
before you can start using Lambda functions.
Also notice that we are using something
called the pipe operator.
Now the pipe operator basically states
that we're creating a runnable Lambda,
and then the output of that runnable Lambda
will get piped into this function, this Lambda here.
So what ends up happening is when we have this line here,
sequence invoke, that will then do one plus one,
and then afterwards we then pipe that in, which is two,
and then we times X here by two to get four.
So you'll see if I've run this and you've got four,
and it's also possible to do this
with the .batch functionality.
So the .batch will do a list of results
and it'll run it through the runnable sequence.
So in this scenario, we've got one, two
and three, which will appropriately get put into this first
runnable Lambda each at a time.
And then also they will get
piped out into this second lambda.
So then those will become four, six and eight.
So the .batch and the .invoke are our general ways
of calling these Lambdas.
There's also something called the Runnable parallel.
Now, the Runnable parallel is slightly different
in the sense of it allows you
to have something that goes into two.
We've got an example here we've got sequence,
runnable Lambda, X plus X is plus one,
and then that goes into two dictionary keys.
Notice the pipe operator again.
So again, we've got this pipe operator,
which is saying this is the input
into our runnable Lambda.
The output then goes into a dictionary with two keys,
mul two and mul five,
and that is gonna be a Lambda function.
So this is Runnable Lambda,
and that will be timesing the value by two
and also timesing the value by five.
So I can do something like this
and I can say, take the one which then gets added.
So that's two.
And then after that, then I will both times two by two
and times two by five at the same time
and put those into separate dictionary keys.
Now there's another way of writing the Runnable parallel.
So if we just scroll down here
and have a look, this is the equivalent
of making a runnable parallel.
So you can either use a dictionary with the keys
or you can actually define it upfront
as a runnable parallel.
Let's have a look at this example up above
where we have a runnable Lambda,
which is saying, take X and add one.
The output of that should go into two dictionary keys,
mul two and mul five.
So the output of this will then get times by two
and times by five, but you don't just have to stop there.
You could finally pipe
that dictionary into a new Lambda function
and take the addition of the mul two of the mul five key.
And this shows you that we can easily pipe
between not only a Lambda function,
but also into a Python dictionary
and then back into a Lambda function as well.
You'll see the same thing happening here,
which is just to demonstrate
that a runnable parallel and a parallel to,
although they are different
in terms of how you construct them,
they basically are the same thing
where we have this dictionary
and it's doing several things from the previous output,
and the same is true of the Runnable parallel.
We can then set up our parallel like this.
So we have the runnable parallel
with the two dictionary keys,
mul two and mul five, which will then get piped
into a new Runnable Lambda,
remembering that the dictionary keys
are mul two and mul five.
We can then add those keys together and invoke those those.
This here is basically saying, take in my dictionary,
which contains two keys.
And you can see what these are would be input one
and input two.
And so basically what I'm saying is get the keys of this
input one and input two, assign those,
and then after those have become mul two
and mul five, by timing them by two
and five, then pipe them into this runnable Lambda
expression here, and then add those together.
So they're slightly different, but very similar.
This one is allowing us to index on a dictionary.
Whilst this one is basically just saying,
let's do something twice to the data,
and then let's output that on two separate keys,
which we then add.
Whilst on this example, we're actually saying,
let's take a dictionary.
And every time we get a dictionary is the input
in our .invoke, what we really want to do,
get the input one key, get the input two key,
and then make these new dictionary keys,
which we then at this point would then do
a mul two plus mul five edition
inside of a Runnable Lambda.
So you can see all the different types of patterns
that you can use when it comes to building this.
Now, you do need a runnable at the start
of a LangChain expression language expression.
So for example, if we try
and do this where we have a Lambda, which is XX plus one,
and we try and pipe that into a runnable parallel,
you'll see that when we try to run the dot invoke,
it will actually fail here.
And the reason why is that this Lambda
doesn't have an invoke method.
It's basically just a Lambda function.
So what you have to do is at least to start with,
you need either a runnable parallel or a Runnable Lambda
or any kind of runnable that would work.
And after that, you can then start
to use standard Lambda functions inside a Python.
So you'll see in this expression we've got a Lambda,
which has X, X plus one,
and then piping that into parallel,
which we can see here is just a dictionary with two keys,
mul two and mul five, and then timesing it by two
and five respectably.
When we call the .invoke, you'll then see
that we get back our Python dictionary
with both mul two and mul five.
We can also combine several steps into a runnable.
So you can see here we've got item one and item two,
and then what we're doing here is then on the fly
doing a Lambda X hello and a Lambda X world.
What we can then do is we can make a function,
which takes an X, which will be a dictionary,
and we can return item one and item two.
So item one would be here, item two would be here,
and we can add those two together.
Now, we can say, take our runnable parallel
and pipe that into this combined function.
So the output of the runnable parallel,
which is a dictionary
with two keys, gets piped and becomes X.
Once it becomes X, then what we do is we then say,
okay, right, that's great.
So because we didn't use anything from the previous step,
that's why we're just doing X here and for hello and world.
So we didn't actually pass any data in.
So when I run this code,
you'll see that we just get Hello world.
So what's really happening here is we have a parallel,
which is an item one and item two,
it doesn't have any current inputs.
I could also have an input here, for example,
so I could have the name.
(person typing)
So if we were to change this slightly, let's have a go.
We could say X, and we could say,
we want to take in the name,
hello, and then we'll put a space,
and then now it will complain about a key error,
which is missing with name.
So I can then fill that in with James.
And now you'll see I get, hello James world.
You can say, welcome to the world, something like this.
So basically you can use inputs,
but the important point that we have here
is we are basically saying, take this name,
use it once in this runnable parallel.
And then in the item two,
we're just gonna say, welcome to the world.
When we do the .invoke,
that will then get past the parallel,
the input for the name.
And then after that we then combine that together,
these two dictionary keys,
which is the item one and item two keys.
So you can see how this starts to build up.
What I would recommend doing is spending
another five minutes now
before moving on to the next video,
and just try and create your own chains
using a mixture of runnable parallels.
And also try and use a mixture of runnable Lambdas,
and python dictionaries.
Try piping some data to forward steps
to see how does this work.
And then once you feel a bit more comfortable with it,
then move on to the next video.