Most samples of using LangChain's Expression Language (LCEL) look like this:
chain = setup_and_retrieval | prompt | model | output_parser
How can I access the source_documents
in a RAG application when using this expression language?
This works well for me:
rag_chain = (
RunnablePassthrough.assign(source_documents=condense_question | retriever)
| RunnablePassthrough.assign(context=lambda inputs: format_docs(inputs["source_documents"]) if inputs["source_documents"] else "")
| RunnablePassthrough.assign(prompt=qa_prompt)
| RunnablePassthrough.assign(response=lambda inputs: llm(inputs["prompt"].messages))
)
It's called like this:
response_dict = rag_chain.invoke({"question": question, "chat_history": chat_history})
ai_msg = response_dict["response"]
source_documents = response_dict["source_documents"]
The way that helped me understand how to do it was this:
question
and chat_history
).RunnablePassthrough.assign
, you can ADD stuff to that dictionary and then pass that on to the next step.RunnablePassthrough.assign
always RETURNS a dictionary.This is what happens in my code example:
RunnablePassthrough.assign
to add a new source_documents
key to the dictionary. Its value is the result of calling the condense_question
function (defined elsewhere) that builds and returns a condenser chain. Its condensed result is passed into our retriever (also defined elsewhere).RunnablePassthrough.assign
to add a new context
key to the dictionary. Its value is the result of calling a format_docs
method (defined elsewhere) that combines the source_documents into a single context string.RunnablePassthrough.assign
to add a new prompt
key to the dictionary. Its value is the result of calling qa_prompt
, which is defined as qa_prompt = ChatPromptTemplate.from_messages(...)
.RunnablePassthrough.assign
one more time to add a new response
key to the dictionary. Its value is the result of actually calling the llm with the messages from our prompt.response
key contains the LLM's response as an AIMessage
, and the source_documents
key contains the source documents.I'm sure this can be done in a more concise way, but this worked for me and I can understand it :)