-: All right, welcome back.
So in this video we're gonna have a look at
how we can do conversation history
and also memory inside of LangChain Expression Language.
We've got a range of different imports at the top here,
some that you'll be probably quite familiar with,
and there's also this format document
that allows us to take in a document
and a prompt and return a string as well,
and that one's probably new.
We have a vector database
that we've set up from our previous lesson
and so what we are doing is basically just having
two documents where I currently work for James
and also his age and we're using the OpenAIEmbeddings
for that and setting up our rag retriever.
We then have a template that says,
"Given the following conversation
"and a follow-up question, rephrase the follow-up question
"to be a standalone question in its original language."
We have a chat history and so what we're gonna be covering
on this lesson is how do we get the chat history in?
And then we have the follow-up input, which is the question,
and we return, basically, just the standalone question.
So this is our condensed question prompt.
We then have an answer that uses the context
and that context is gonna come from our retriever
from the question that we got back from this prompt here.
We've also got a default document template,
which will take in the page content
and it will then combine multiple pages
using the document prompt.
So you can see here, it will format each document
to get document strings,
and then after that we join those together
and then we've got a prompt which just has the page content.
So basically all of this combined documents gets associated
with a variable of page content.
Now looking at the chat history, what we've decided to do
is we have our chat history, which is a list of messages
that could either be a human message, a system message,
or an AI message.
And so we can create a buffer,
which is just an empty string,
and we're just gonna loop through each message
and check to see what type of message that is
and we'll add the appropriate bit of string at the prefix
of each individual message, whether it's a human,
an assistant, or a system message
and then we'll return the buffer.
Now it's important to do this step
using LangChain Expression Language
because you do have to make sure
that all of the types of eventually converge
to a string type when you're using your prompt formatting.
We set up something called a runnable map,
which is exactly the same
as something called the runnable parallel.
But I thought to demo that sometimes you might experience
a runnable map and it's basically just a runnable parallel.
We have the standalone question which says
take the chat history and format that chat history.
We also have the condensed question prompt,
so we take the chat history,
push that into the condensed question prompt.
We then get the chat OpenAI model,
and then we get it to answer the question.
We also have the context here
and you can see that's getting the standalone question.
It's piping that into the retriever,
and then those retrieve documents are then being combined
and we have the question here, which is then
the standalone question from before.
And this makes up our conversational Q&A chain
where we have our inputs.
We then have our context
and all of that ends up getting piped
into the answer prompt, which you can see here.
We then pass that to the chat model.
So let's have a look and see what happens.
We just make sure that all of these have been run.
And I've gotta go and get the retriever too.
So now we can invoke our Q&A chain
with the question "Where did James work?"
and the chat history.
Now the problem is at the moment
in LangChain Expression Language,
you do have to add the chat history manually
and they haven't done a way for automating that.
So the best way at the moment
is to use something like memory,
where you would set a conversational buffer memory
with an output key
and the input would be associated with the question.
You can return the messages to be true, which you should do
for the above function to work.
And then what we do is we say load the memory,
so RunnablePassThrough.assign,
and we get the chat history
and we make a runnable lambda that gets the memory.load,
load memory variables,
and it's using that function on the item getter history.
After that, then we set our standalone question key
to have a question and a chat history.
We then have the condensed question prompt,
the Chat OpenAI, the string passer,
and then basically we get the retrieved documents
which get passed into the retriever.
The standalone question and our final inputs
where we have the combined documents
as the context and the question as the item
get difficult question from a previous step.
And then after that, then what we do is we take
the final inputs, pipeline into the answer prompt,
pipeline to the model and get the docs.
And so the chain does a couple of things.
So the first thing is we get the loaded memory
and then we go into the standalone question.
Which will get asked a single question
given the chat history.
After that, then we go and get the retrieved documents.
So using the standalone question, we then pass that
into the retriever to get some relevant documents.
And after that then we get the standalone question
and reassign it with a key of question, right.
And then after we've got that,
then we pass it into the answer which has the final inputs,
which is the context.
So those documents, we then get those as the context key.
And also then we get the question
from the previous step here, you can see
and then we pipe that in to the answer prompt
and get the chat model to respond.
And we also pass in the docs.
So sometimes it can be good to read this in the start,
but also you saw me sort of going, well,
we've got this final inputs
but it's not really its own chain.
It's sort of inside the answer
and the answer is sort of using the final inputs,
which then get passed into an answer prompt,
which then get passed into Chat OpenAI.
So it's important to just sort of see
where do the dictionaries flow, like can you see
how the chain is being created?
And then obviously this would, you know,
you would just pass into question, where did I work?
And then it'll pass the answer
and the relevant documents that are used to answer that.
Now the thing is, if we run this, you'll see that
at the moment it doesn't have any chat history.
So what you would normally do is you would do
memory.loadvariables.
And so the next time this function would run,
the loaded memory, if we scroll back up here, is a function
that will basically run and it will assign
and pipe that to the the item get it or history basically.
So that's basically it in a nutshell.
So you can see now that we've done the load variables,
there's actually a bit of history in there.
So when this line gets executed,
we will actually have some chat history
the next time it was run.
And that's basically how you add conversational history
and memory inside of LangChain Expression Language.