Transcript for:
Lang Chain Expression Language Overview

today we're going to be talking about Lang chain expression language which is a pretty interesting idea that essentially allows us to write very minimalist code to build chains within line chain and for sure I think we'll see from this video we can use a lot of L chains more advanced features like parallel execution async and streaming very easily using the expression language rather than just the more typical approach to build Lang chain chains and in my opinion it's worth trying just for that I think we'll see that just using this you can build stuff very quickly that's not to say it doesn't have its cons but we'll dive into those later so let's just begin with what this expression language actually is so there's a page here in the line train dos talking about this expression language right so it's LC for short and yeah they just explain a few things you know we streaming acing parel execution so on and so on right but let's just jump into this notebook and we'll see more of how this actually works so there will be a link to this notebook as they usually is at the top of the video right now and I've WR all this in collab so you can do the same it's pretty straightforward we have a few prerequisites we're going to be using line chain of course we're going to be using anthropic the new Claude 2.1 model for our llm we're going to be using cave the embeddings and we're going to be using a dock array just so I can give you an example of parallel retrieval later on which is super interesting now the main things I think we would want to use the Expression language for is these three items here so we have super fast development of chains we have those Advanced features streaming acing parallel execution just work out of the box with these super fast and easy to set up and there's also easy integration with the other Lang chain products so Lang Smith and Lang serve if you are using those now let's take a look at what it actually looks like so to get started with this we're going to need a anthropic API key and you can get that by going to console anthropic tocom you'd come into here hopefully you have an account already and you can click get API keys and you're just going to get your API keys from there if you don't have an anthropic account I think there's still a like a very minor weight list so one I just recommend you sign up and you you'll get access pretty soon but so that you're not waiting you can also just use open AI so you would just swap chat anthropic here with chat openai and swap anthropic API key for openai API key and if you do do that you will also want to drop just drop these two arguments it'll make things easier so looking at this let's see we'll put our API key in here and once we have that we now have these three components we have a prompt a Model A chat model and a output passer okay now in typical L chain we would chain these together using the llm chain okay so you can see llm chain your prompt the L and the output passer okay what I'm going to do is take this prompt where're asking to give me a small report about a particular topic okay so the the input to that is going to be topic and you can see that here so we have topic artificial intelligence and it's obviously just going to Output a small report on that okay so let's run that and see what we get so it's running uh we create our chain running chain. run and we'll just print that output and we'll get this small like rort thing on on AI okay so all looks pretty good now how would we do that with the expression language well we use this this pipe operator and I'm going to go into detail as to how this actually functions because I think that's understanding how this pipe operator functions allows us to just understand what is actually happening here okay so that we can actually understand this abstraction rather than just blindly using it so we string things together right so we have our prompt followed by the model followed by output parer and rather than putting them into an llm chain or some other chain we just string them together with this pipe operator so I mean it's like for sure if I look at this it's kind of it's simpler than this right if you compare those two it's I would say also more flexible because we can just string things together but it's you know I think it's it's not so pythonic as to what we're used to whether or not that is a good or bad thing I'm undecided on like I really I like the minimalist approach here it looks great but it it's maybe hard to understand like if you if you don't understand the syntax and you on python very well this is going to be pretty confusing anyway let's run that so we create our chain using this new this expression language syntax and then we just rather than running run we run invoke and we pass a dictionary of input variables into there so we run this and yeah it's going to do the exact same thing we or very similar output to what we saw before okay so it gives us little report again okay looks cool so these two things this and this doing the exact same thing just different syntax now I think when you see that syntax of the pipe operator for the first time at least for me I was quite confused and I think most people would be confused the way that it works is pretty simple at least the idea behind how it works can be explained very easily what we see on the left of each pipe operator the output from that gets passed to what is on the right of the pipe operator okay and then the output from this is passed into this so it's it's literally piping things from the left of the pipe operators all the way through to the right of the pipe operators that's that's all it's really doing now how that pipe operator actually works is more not necessarily complicated it's probably a little bit hacky in my opinion but it's it's kind of interesting so this pipe operator when we apply it to an object in Python what it actually looks for within that object is this or method here right so if I come down to here we have this kind of confusing class called runnable but let's break it down a little bit okay so I'm going to do class and we're going to call it what still going to call it runable now when we initialize this class we run I'll see the init method here and within that we're going to pass a function right because the way that we're going to implement this is we're going to give a function into this class and we're going to use this class to transform this function into something that we can use this pipe operator on so we want to save that function within our runable class or object and then the next thing you see this is the part that makes the the pipe operator work okay so when a pipe operator is applied to an object it's going to look for the objects all method now the or method that needs to contain another function that we call other here now the way that you can think of this the funk and the other arguments here is that funk is kind of what is on the left of our pipe and other is what is on the right of our pipe okay so what we do is we create this chain function here which is going to consume a set arguments and keyword arguments so we can call it chain Funk as we do there our arguments and we have our keyword arguments now the reason that we set up with args and keyword arguments like this is because we don't know the names of the parameters that I going to be input into our function right so by doing this we can you know those parameter names can vary we can have more or less and this chain function will be able to handle those so we would do return other so our basically this function here that consumes the output from our function okay and again that function is going to take those ARs and keyword arguments okay so from that we would then return the the runnable here so this is going to be our like runnable version of that chain function so basically by doing that we're putting the uh this ability to run chains within each one of the functions that we pass through this actual chain okay so we can do multiple of these so we could have you know other two other three so on and so on now the final thing that we need to have here is a method that allows us to call and and begin this chain now I'm going to implement it with this we will see that line chain actually uses I think they use invoke so rather than call they would have invoke here and that starts to ch but I'm I'm just going to do call because I think it's simpler so that is our runnable function we can run that and I also have it here maybe I'll just run this one and what we want to do is use this Runner board to kind of wrap around different functions that we would like to run with this pipe operator approach to do that we're going to Define two very simple functions here one is add five one is multiply by two okay so let's run those and I'm going to wrap those with this runnable object that we've created and then using this approach right so we have uh we have the chain we're going to do add five and then rather than using the PIP operator I'm going to use the the all method directly and then within that all method I'm going to pass our multiply by two runnable okay so we have those and then we can just call our train so three to it and we get the value 16 which is that's correct so we do 3 + 5 take both those gives us eight and multiply those by two okay so it's correct it's run in the correct order now we can use this syntax or now that we use this or method we can also use the syntax that we see here with the pipe operator so let's try that okay you can you see that we we now have this so yeah that's that's pretty interesting so we can you know we can build our own pipe operator functions using using this and this is what line chain is doing okay so when we see this line chain expression language this is what we're actually looking at which is an interesting way of putting things together now that's how it works let's have a look at how we actually use the Expression language itself so we saw already we can use the or operators or the pipe operators now let's put it together in an actual use case so I'm going to be using the coher embedding model you know if you you can also use open a eyes embedding model it's up to you but to get that API key I don't think there's a weight list for coh here so you can you should be able to jump straight into it you can go to dashboard. here.com you'd go to API keys and from the API Keys page you can you can create either a trial key or production key and you just use that so I'm going to add mine in here and I'm going to be using the cair embedding model so the the newest one from there which is very high performance embedding model I'm going to be using that to create two kind of like document stores that we have here okay so we have you know they're very small it's just for an example we have one where we have half the information in Vector sore document sore a and half the information in saw or do saw B you'll see why soon but for now what we're going to do is just use the first one okay so we're going to use a right so it contains information about me when my birthday is the one contains the year of my birthday so let's try putting information into the Vex store or retrieving information my vase store and then feeding that alongside the original query into a chain using the expression language now when we do this there's one important thing that we need to be aware of which is when we use this syntax just using this syntax and nothing else we we have like one input and one output to each of these items right each of these components so how you know how does that work when we have you know we have a context that we need to use here and also a question that we need to feed into our prompt and the way that we do that is by using this runnable parallel object so I've imported those here we have runable parallel and runable pass through the runable parallel which we have here first it allows us to run multiple chains or components in parallel and also extract multiple values from them right so here we're going to run retriever a and then for this question we're using this runnable pass through item what runnable pass through does is whatever was input into the retrieval or the runable parallel object it's just going to return that okay so it's literally a pass through for values that you pass into here so let's run all of that okay so we have our retriever a here that we're using we have our prompt template so on and so on right we have our retrieval that happens first so we have a query when was when was I born we're going to invoke that and this value is being passed into our retriever it's doing a search getting the context it's also being passed through here and going straight through to our prompt okay so then our prompt gets formatted with the question we have when was James born with the context we have the record we will have the records from here okay so V saw a so my birthday the actual date now what we will get here is unfortunately I do not have enough context to definitively State when James was born and it tells me what it found it found this little bit of information so it knows that my birthday is z but it does not specify the year that I was born okay so it can't actually fully answer the question but we can see that this chain is working it's going to do retrieval comparing soon our prompt model Alpha Passa whatever else it's going through everything now the cool thing with runnable parallel you might have guessed with what we have here is that it can run many things in parallel not just a retriever and you know passing through a question we can actually run multiple Retrievers in parallel or we can run multiple different components in parallel at the same time and this is one of the things that is very cool about the expression language is that it you know we we set these things up in parallel and like runnable parallel here is just going to do them in parallel right it's going to run those in parallel we don't have to deal with you know building or writing any of that code ourselves which is I think pretty cool so let's come down to here what I'm going to do is now that we're going to be retrieving information from two places I'm going to create a context a and a context B we're going to run that or we're going to initialize the The Prompt then our runnable parallel now we need to modify a little bit we need to add so we have retriever a we're now mapping that to context a and we have retriever B which we're going to map over to context B and then as before we have our question which is the runable pass through now the chain itself is exactly the same we still just have one like retrieval component there now because you know both our retrievals are being run in parallel within that abstraction so we're going to run that and now I'm going to say the same the same question when was I born okay so now it it knows based on the context provider James was born in 1994 okay stated in the second document with the page content James born in 1994 and maybe if I want to kind of say okay give me the date as well i' say um what date exactly which spawn and we actually get this which is odd because so it it says unfortunately the given context does not provide definitive information to answer the question what dat exactly was James born but then then it actually it gives us here so we have I don't know that there's a little bit of a lack of reasoning ability with Claude in this case clearly so my birthday is 7th December and I was born in 1994 I don't know why it's kind of surprising to me that I didn't get that but interesting but at least we can see that our chain is working correctly we can see that it's pulling in information from both our retrievers there which is cool and we're almost done with what I think are the essentials of the expression language there's just one more thing that I think is super important and it's basically line chains abstraction of doing what I showed you earlier where we created our own sort of runnable class and fed functions into it to create these you know things that we can run with the pipe operator so to do that in line chain they have these runable lambdas okay and this is why earlier on I called that class A runnable because here they they call them runnable lambdas so we have our our add five and our multiply by two I'm going to just come up here and show you what we we had earlier so yeah we have these two functions let's take those okay we can see runable it's what we were doing before so that we could use this let's do it again here all right so we have our add five and I'll multiply by two let's run this this time we're doing runnables but we're just doing them through Line train so our train is going to be at five multiply by two as we did before and as I mentioned you know line chain we have to use infol rather than just calling the object directly so we run that and yes as before we get 16 so yeah we can wrap our own functions using Lang chains runable Lambda here now when would we use that I mean there there are definitely different scenarios why we might want to use that but let me just show you something here which you know kind of bothers me a little bit and it's a good example where we might want to use this either use this or we'd probably want to adjust the output parer as well so we have let's run both of these what we see when we run this is one there's some leading white space here that we could do removing but it also starts each answer with this here's a short fact about artificial intelligence and then we have two double new line characters maybe I don't want that and I just want it to get straight to the fact so what I can do is use this runnable Lambda abstraction to to do that right so I'm going to define a function which is going to look within this string for a double new line within the string if that is in there we're going to split by double new lines and we're going to take everything that occurs after the double new lines now in the case that maybe there are multiple double new lines we're taking everything you know one from one to the end of the list that we would get from this and then we're joining everything back here okay so we're basically just dropping that first one the first part here so let's run that I'm going to wrap that within a runable line and then I'm going to put all those things together and I'm going to add the get fact runable to the end of my chain now let's invoke again and see what we get okay so there's no weird sarting text here and yeah we see with both of those it know it works so our a little runnable Lambda here works well okay so that is really everything I wanted to cover with the expression language you know I think there's there's other things that we can talk about and more to cover but this is I think pretty much everything you need to really get started with it and just understand what this abstraction is actually doing which like I said at the start it's important to understand because then at least we know what we're doing rather than just kind of you putting in these pipe operators and kind of thinking they should work when maybe we're doing something that doesn't make sense so I hope this has been useful for understanding the expression language you know there's pros and there's cons to using this now on the pros obviously there's the Min andless style of the code which is kind of nice it's very clean and the out of the box support for different features like streaming and the parallel execution that we saw but there are also some cons and you know there's plenty of people that are less fond of the expression language as you know it's a big change it's to be expected now the things that people point to when they're like this doesn't make sense is that it makes things more abstract L chain is a you know abstractions already so we're kind of adding another abstraction to the abstractions and that the syntax is it's definitely not common syntax of python and that kind of goes against the Zen of python which is that kind of shouldn't make special cases for things and of course it's a new syntax it's especially when you first look at it I think once you've explored it a little bit it makes sense but when you first get started with it it's it's definitely confusing so in my honest opinion I think both of those viewpoints are entirely valid there's pros and cons for for sure but I like it I I think it it's definitely worth learning and experimenting with and it can definitely speed things up when particularly when you're prototyping and maybe in production code you know it's going to depend on what you're wanting to what you're wanting to do there anyway that's it for this video I hope all this has been useful in understanding the expression language so thank you very much for watching and I will see you again in the next one bye