back to index

LangChain Expression Language (LCEL)


Chapters

0:0 LangChain Expression Language
0:58 Traditional Chains in LangChain
3:2 LangChain LCEL
3:55 LCEL Pipe Operator
9:9 LangChain RunnableLambda
12:41 LCEL Runnable Parallel and Passthrough

Whisper Transcript | Transcript Only Page

00:00:00.000 | In this chapter, we're going to be taking a look at Lanchain's expression language.
00:00:03.820 | We'll be looking at the runnables, the serializable and parallel of those, the runnable pass-through,
00:00:10.340 | and essentially how we use LCL in its full capacity.
00:00:16.660 | Now, to do that well, what I want to do is actually start by looking at the traditional approach to building chains in Lanchain.
00:00:27.680 | So, to do that, we're going to go over to the LCL chapter and open that CoLab.
00:00:33.140 | Okay, so let's come down.
00:00:36.540 | We'll do the prerequisites.
00:00:38.600 | As before, nothing major in here.
00:00:41.140 | The one thing that is new is DocRay.
00:00:42.940 | That's because later on, as you'll see, we're going to be using this as an example of the parallel capabilities in LCL.
00:00:52.280 | If you want to use Lansmith, you just need to add in your Lanchain API key.
00:00:56.860 | Okay, and then let's, okay, so now let's dive into the traditional approach to chains in Lanchain.
00:01:04.680 | So, the Lanchain, I think it's probably one of the first things introduced in Lanchain, if I'm not wrong.
00:01:12.540 | So, let's take it to prompt and feed it into an LLM and that's it.
00:01:17.520 | You can also, you can add like output passing to that as well, but that's optional.
00:01:24.980 | I don't think we're going to cover it here.
00:01:26.580 | So, what that might look like is we have, for example, this prompt template here.
00:01:32.040 | Give me a small report on topic.
00:01:34.400 | Okay, so that would be our prompt template.
00:01:37.540 | We'd set up as we usually do with the prompt templates, as we've seen before.
00:01:42.880 | We'd then define our LLM.
00:01:46.440 | We'd need our OpenAI API key for this, which as usual, we would get from platform.openai.com.
00:01:54.960 | Then, we go ahead, I'm just showing you that you can invoke the LLM there.
00:02:00.140 | Then, we go ahead, actually define a output parser.
00:02:04.760 | So, we do do this, I wasn't sure we did, but we would then define our LLM chain like this, okay?
00:02:10.240 | So, LLM chain, we're adding our prompt, adding our LLM, adding our output parser, okay?
00:02:15.860 | This is the traditional approach.
00:02:21.500 | So, I would then say, okay, retrieve augmented generation, and what it's going to do is
00:02:25.720 | it's going to give me a little report back on RAG.
00:02:28.900 | Okay, takes a moment, but you can see that that's what we get here.
00:02:33.120 | We can format that nicely, as we usually do, and we get, okay, look, we get a nice little report.
00:02:40.480 | However, the LLM chain is, one, it's quite restrictive, right?
00:02:45.720 | We have to have, like, particular parameters that have been predefined as being usable,
00:02:50.680 | which is, you know, restrictive, and it's also been deprecated.
00:02:54.720 | So, you know, this isn't the standard way of doing this anymore, but we can still use it.
00:03:01.560 | However, the preferred method to building this and building anything else, really,
00:03:06.260 | or chains in general in LLM chain is using LSL, right?
00:03:10.380 | And it's super simple, right?
00:03:11.800 | So, we just actually take the prompt LLM now, put parser that we had before,
00:03:15.460 | and then we just chain them together with these pipe operators.
00:03:18.600 | So, the pipe operator here is saying, take what is output from here and input it into here.
00:03:24.080 | Take what is output from here and input it into here.
00:03:27.180 | That's all it does.
00:03:28.120 | It's super simple.
00:03:29.500 | So, put those together, and then we invoke it in the same way, and we'll get the same output.
00:03:34.760 | Okay?
00:03:36.880 | And that's what we get.
00:03:38.800 | There is actually a slight difference on what we're getting out from there.
00:03:43.600 | You can see here we got actually a dictionary, but that is pretty much the same.
00:03:49.240 | Okay, so we get that, and as before, we can display that in markdown with this.
00:03:55.580 | Okay, so we saw just now that we have this pipe operator here.
00:03:59.760 | It's not really standard Python syntax to use this, or at least it's definitely not common.
00:04:10.220 | It's an aberration of the intended use of Python, I think.
00:04:14.860 | But anyway, it does, it looks cool.
00:04:19.060 | And when you understand it, I kind of get why they do it, because it does make things quite simple
00:04:24.840 | in comparison to what it could be otherwise.
00:04:27.120 | So, I kind of get it.
00:04:28.720 | It's a little bit weird, but it's what they're doing, and I'm teaching it ourselves.
00:04:33.400 | That's what we're going to learn.
00:04:35.180 | So, what is that pipe operator actually doing?
00:04:40.500 | Well, it's, as I mentioned, it's taking the output from this, putting it as input into what
00:04:48.980 | is ever on the right.
00:04:50.060 | But how does that actually work?
00:04:52.160 | Well, let's actually implement it ourselves without linechain.
00:04:57.120 | So, we're going to create this class called runnable.
00:04:59.320 | This class, when we initialize it, it's going to take a function.
00:05:03.160 | Okay, so this is literally a Python function.
00:05:05.260 | It's going to take that, and it's going to essentially turn it into what we would call
00:05:11.340 | a runnable in linechain.
00:05:13.360 | And what does that actually mean?
00:05:15.020 | What doesn't really mean anything?
00:05:16.700 | It just means that when you run the invoke method on it, it's going to call that function
00:05:23.900 | in the way that you would have done otherwise.
00:05:26.440 | So, using just function, brackets, open, parameters, brackets, close, it's going to do that.
00:05:33.300 | But it's also going to add this method, this all method.
00:05:36.180 | Now, this all method in typical Python syntax, now this all method is essentially going to
00:05:44.880 | take your runnable function, the one that you initialize with.
00:05:48.820 | And then it's also going to take an other function.
00:05:52.400 | Okay, this other function is actually going to be a runnable, I believe.
00:05:56.900 | Yes, it's going to be a runnable, just like this.
00:05:59.800 | And what it's going to do is it's going to run this runnable based on the output of your
00:06:08.000 | current runnable.
00:06:09.540 | Okay, that's what this all is going to do.
00:06:11.740 | Seems a bit weird, maybe, but I'll explain in a moment.
00:06:15.560 | We'll see why that works.
00:06:17.160 | So, I'm going to chain a few functions together using this all method.
00:06:23.660 | So, first, we're just going to turn them all into runnables.
00:06:27.000 | Okay, so these are normal functions, as you can see, normal Python functions.
00:06:31.200 | We then turn them into this runnable using our runnable class.
00:06:35.880 | Then, look what we can do, right?
00:06:38.560 | So, we're going to create a chain that is going to be our runnable chained with another runnable,
00:06:47.140 | chained with another runnable.
00:06:49.020 | Okay, and let's see what happens.
00:06:50.660 | So, we're going to invoke that chain of runnables with three.
00:06:55.920 | So, what is this going to do?
00:06:57.760 | Okay, we'll start with five.
00:06:59.120 | We're going to add five to three.
00:07:01.420 | So, we'll get eight.
00:07:03.220 | Then, we're going to subtract five from eight to give us three again.
00:07:08.340 | And then, we're going to multiply three by five to give us 15.
00:07:15.720 | And we can invoke that.
00:07:17.240 | And we get 15.
00:07:19.220 | Okay?
00:07:19.980 | Pretty cool.
00:07:21.380 | So, that is interesting.
00:07:23.780 | How does that relate to the pipe operator?
00:07:26.940 | Well, that pipe operator in Python is actually a shortcut for the OR method.
00:07:33.180 | So, what we've just implemented is the pipe operator.
00:07:36.640 | So, we can actually run that now with the pipe operator here.
00:07:40.520 | And we'll get the same.
00:07:42.020 | We'll get 15.
00:07:42.800 | Right?
00:07:43.740 | So, that's what linechain is doing.
00:07:45.440 | Like, under the hood, that is what that pipe operator is.
00:07:49.480 | It's just chaining together these multiple runnables, as we'd call them, using their own internal OR operator.
00:07:56.600 | Okay?
00:07:57.680 | Which is cool.
00:07:58.560 | I will give them that.
00:07:59.920 | It's kind of a cool way of doing this.
00:08:02.560 | It's creative.
00:08:03.120 | I wouldn't have thought about it myself.
00:08:06.640 | So, yeah.
00:08:07.980 | That is a pipe operator.
00:08:09.100 | Then we have these runnable things.
00:08:11.200 | Okay?
00:08:11.600 | So, this is different to the runnable I just defined here.
00:08:15.100 | This is...
00:08:15.820 | We define this ourselves.
00:08:17.280 | It's not a linechain thing.
00:08:19.680 | We didn't get this from linechain.
00:08:21.280 | Instead, this runnable lambda object here, that is actually...
00:08:28.420 | It's actually the same as what we just defined.
00:08:30.240 | All right?
00:08:31.120 | So, what we did here, this runnable, this runnable lambda is the same thing, but in linechain.
00:08:39.560 | Okay?
00:08:40.300 | So, if we use that, okay, we use that to now define three runnables from the functions that we defined earlier.
00:08:48.000 | We can actually pair those together now using the pipe operator.
00:08:52.240 | You could also pair them together, if you want, with the OR operator.
00:08:57.500 | Right?
00:08:57.880 | So, we could do what we did earlier.
00:09:01.540 | We can invoke that.
00:09:02.560 | Okay?
00:09:03.680 | Or, as we were doing originally, we'd choose a pipe operator.
00:09:07.820 | Exactly the same.
00:09:09.860 | So, this runnable lambda from linechain is just what we just built with the runnable.
00:09:14.680 | Cool.
00:09:15.620 | So, we have that.
00:09:16.800 | Now, let's try and do something a little more interesting.
00:09:20.140 | We're going to generate a report, and we're going to try and edit that report using this functionality.
00:09:24.720 | Okay?
00:09:25.420 | So, give me a small report about topic.
00:09:27.620 | Okay?
00:09:28.360 | We'll zero through here.
00:09:30.200 | We're going to get our report on AI.
00:09:33.280 | Okay?
00:09:36.320 | So, we have this.
00:09:37.240 | You can see that AI is mentioned many times in here.
00:09:42.740 | Then, we're going to take a very simple function.
00:09:46.680 | All right?
00:09:47.680 | So, I'm going to do extract fact.
00:09:48.920 | This is basically going to take, what is it?
00:09:51.840 | See, taking the first.
00:09:56.120 | Okay?
00:09:56.420 | So, we're actually trying to remove the introduction here.
00:09:59.160 | I'm not sure if this actually will work as expected.
00:10:02.360 | So, we're going to replace it.
00:10:03.360 | So, we're going to replace an old word with a new word.
00:10:13.440 | So, we're going to replace an old word with a new word.
00:10:13.440 | Our old word is going to be AI.
00:10:14.620 | Our new word is going to be Skynet.
00:10:15.900 | Okay?
00:10:16.900 | So, we can wrap both of these functions as runnable lambdas.
00:10:22.120 | We can add those as additional steps inside our entire chain.
00:10:27.700 | All right?
00:10:28.060 | So, we're going to extract, try and remove the introduction.
00:10:30.700 | Although, I think it needs a bit more processing than just splitting here.
00:10:35.080 | And then, we're going to replace the word.
00:10:37.480 | We need that actually to be AI.
00:10:39.120 | Run that or run this.
00:10:40.620 | Okay.
00:10:44.040 | So, now we get artificial intelligence, Skynet.
00:10:47.500 | Refers to the simulation of human intelligence processed by machines.
00:10:50.420 | And then, we have narrow Skynet, weak Skynet, and strong Skynet.
00:10:55.260 | Applications of Skynet.
00:10:57.540 | Skynet technology is being applied in numerous fields, including all these things.
00:11:01.040 | Scary.
00:11:01.640 | Despite its potential, Skynet poses several challenges.
00:11:06.800 | Systems can perpetrate existing biases.
00:11:09.400 | It raises significant privacy concerns.
00:11:13.040 | It can be exploited for malicious purposes.
00:11:16.560 | Okay?
00:11:17.300 | So, we have all these.
00:11:18.380 | You know, it's just a silly little example.
00:11:21.540 | We can see also the introduction didn't work here.
00:11:23.660 | The reason for that is because our introduction includes multiple new lines here.
00:11:28.940 | So, I would actually, if I want to remove the introduction, we should remove it from here, I think.
00:11:35.660 | I would never actually recommend you do that because it's not very flexible.
00:11:41.900 | It's not very robust, but just so I show you that that is actually working.
00:11:48.700 | So, this extract fact runnable.
00:11:55.240 | Right?
00:11:56.240 | So, now we're essentially just removing the introduction, right?
00:11:56.940 | Why would we want to do that?
00:11:58.000 | I don't know, but it's sad.
00:12:00.000 | Just so you can see that we can have multiple of these runnable operations running, and they can be whatever you want them to be.
00:12:07.300 | Okay, it is worth noting that the inputs to our functions here were all single arguments.
00:12:14.420 | Okay, if you have a function that is accepting multiple arguments, you can do that in the way that I would probably do it, or you can do it in multiple ways.
00:12:23.700 | One of the ways that you can do that is actually write your function to accept multiple arguments, but actually do them through a single argument.
00:12:32.340 | So, just like a single x, which would be like a dictionary or something, and then just unpack them within the function and use them as needed.
00:12:39.380 | That's just one way you can do it.
00:12:41.140 | Now, we also have these different runnable objects that we can use.
00:12:46.340 | So, here we have runnable_parallel and runnable_passthrough.
00:12:50.340 | Kind of self-explanatory to some degree.
00:12:53.220 | So, let me just go through those.
00:12:55.060 | So, runnable_parallel allows you to run multiple runnable instances in parallel.
00:13:02.500 | Runnable_passthrough, maybe less self-explanatory, allows us to pass a variable through to the next runnable without modifying it.
00:13:10.820 | Okay, so let's see how they would work.
00:13:13.540 | So, we're going to come down here and we're going to set up these two docker arrays.
00:13:17.860 | Obviously, it's two sources of information.
00:13:20.660 | And we're going to need our lm to pull information from both of these sources of information in parallel,
00:13:28.900 | which is going to lie like this.
00:13:30.180 | So, we have these two sources of information, vector store a, vector store b.
00:13:35.060 | This is our docker array, a and docker array b.
00:13:38.100 | These are both going to be fed in as context into our prompt.
00:13:43.220 | And then our lm is going to use all of that to answer the question.
00:13:48.340 | Okay, so to actually implement that, we need an embedding model.
00:13:53.700 | So, we're just opening our embeddings.
00:13:54.900 | We have our vector store a, vector store b.
00:13:58.580 | They're not, you know, real vectors.
00:14:00.820 | They're not full-on vector stores here.
00:14:02.900 | We're just passing in a very small amount of information to both.
00:14:06.900 | So, we're saying, okay, we're going to create an in-memory vector store using these two bits of
00:14:13.060 | information.
00:14:13.620 | So, I'm going to say half the information is here.
00:14:15.380 | This would be an irrelevant piece of information.
00:14:17.620 | Then we have the relevant information, which is DeepSeq v3 was released in December 2024.
00:14:23.060 | Okay, then we're going to have some other information in our other vector store.
00:14:28.660 | Again, irrelevant piece here and relevant piece here.
00:14:32.100 | Okay, the DeepSeq v3 lm is a mixture of experts model with 671 billion parameters at its largest.
00:14:40.420 | Okay, so based on that, we're also going to build this prompt string.
00:14:45.620 | So, we're going to pass in both of those contexts into our prompt.
00:14:48.660 | Now, I'm going to ask a question.
00:14:50.900 | We don't actually need, we don't need that bit.
00:14:53.860 | And actually, we don't even need that bit.
00:14:56.740 | What am I doing?
00:14:57.860 | So, we just need this.
00:14:58.980 | So, we have both the contexts there.
00:15:01.140 | And we would run them through our prompt template.
00:15:04.420 | Okay, so we have our system from template, which is this.
00:15:08.020 | And then we're just going to have, okay, our questions going to go into here as a user message.
00:15:12.820 | Cool.
00:15:14.580 | So, we have that.
00:15:15.940 | And then, let me make this easier to read.
00:15:19.460 | We're going to convert both those vectors to retrievers, which just means we can retrieve stuff from them.
00:15:25.940 | And we're going to use this runnable parallel to run both of these in parallel, right?
00:15:34.900 | So, these are both being run in parallel.
00:15:37.140 | But then we're also running our question in parallel because this needs to be essentially passed through this component
00:15:43.780 | without us modifying anything.
00:15:45.860 | So, when we look at this here, it's almost like, okay, this section here would be our runnable parallel.
00:15:53.300 | And these are being run in parallel.
00:15:56.740 | But also, our query is being passed through.
00:15:59.940 | So, it's almost like there's another line there, which is our runnable pass through.
00:16:03.300 | Okay.
00:16:03.620 | So, that's what we're doing here.
00:16:04.660 | So, that's what we're doing here.
00:16:04.660 | These run in parallel.
00:16:06.660 | One of them is a pass through.
00:16:10.180 | Oh, I need to run here.
00:16:13.460 | I just realized here we're using the deprecated embeddings.
00:16:18.820 | Just switch it to this.
00:16:20.340 | So, Langchain OpenAI.
00:16:23.860 | We run that, run this, run that.
00:16:25.940 | And now, this is set up.
00:16:30.500 | Okay.
00:16:30.820 | So, we then put our initial.
00:16:36.340 | So, this is using our runnable parallel and runnable pass through.
00:16:40.180 | That is our initial step.
00:16:41.540 | We then have our prompt.
00:16:43.380 | Elnable parser, which is being chained together with the usual, you know, the usual type operator.
00:16:50.180 | Okay.
00:16:52.020 | And now, we're going to invoke a question.
00:16:53.700 | What architecture does the model DeepSeq release in December use?
00:16:57.140 | Okay.
00:16:57.940 | So, for the ELN to answer this question, it's going to need to tell us,
00:17:02.340 | well, it needs the information about the DeepSeq model that was released in December,
00:17:06.580 | which we have specified in one half here.
00:17:10.500 | And then it also needs to know what architecture that model uses,
00:17:14.100 | which is defined in the other half over here.
00:17:17.620 | Okay.
00:17:19.380 | So, let's run this.
00:17:20.580 | Okay.
00:17:22.580 | There we go.
00:17:23.060 | DeepSeq V3 model released in December 2024 is a mixture of experts model with 671 billion parameters.
00:17:31.460 | Okay.
00:17:31.620 | So, a mixture of experts and this many parameters.
00:17:34.900 | Pretty cool.
00:17:35.540 | So, we've put together our pipeline using ELSL, using the pipe operator,
00:17:42.180 | the runnables.
00:17:43.540 | Specifically, we've looked at the runnable parallel,
00:17:46.500 | runnable pass-through, and also the runnable lambs.
00:17:49.380 | So, that's it for this chapter on ELSL, and we'll move on to the next one.
00:17:54.260 | The end of the year.
00:18:08.900 | The end of the year.