Course Content
LangChain Mastery
7 sections · 109 lessons
Write a function to create a LangChain chain for question answering.
What you need to know
1from operator import itemgetter2from langchain_core.prompts import ChatPromptTemplate3from langchain_core.output_parsers import StrOutputParser4from langchain_core.runnables import RunnablePassthrough56def format_docs(docs):7 return "\n\n".join(f"[{d.metadata['source']}] {d.page_content}" for d in docs)89def build_qa_chain(retriever, llm):10 prompt = ChatPromptTemplate.from_messages([11 ("system", "Answer using ONLY the context. Cite sources in [brackets]. "12 "If the context does not contain the answer, say you don't know.\n\n"13 "Context:\n{context}"),14 ("human", "{question}"),15 ])16 answer = prompt | llm | StrOutputParser()17 return (18 RunnablePassthrough.assign(docs=itemgetter("question") | retriever)19 | RunnablePassthrough.assign(context=lambda x: format_docs(x["docs"]))20 | RunnablePassthrough.assign(answer=answer)21 ).pick(["answer", "docs"])2223qa = build_qa_chain(vectorstore.as_retriever(search_kwargs={"k": 4}), llm)24result = qa.invoke({"question": "How long do refunds take?"})25result["answer"], [d.metadata["source"] for d in result["docs"]]Step by step:
itemgetter("question") | retrieversends only the question to the retriever, which returns a list ofDocumentobjects.format_docsjoins them into one string, each tagged with its source file so the model can cite it.- The answer chain fills the prompt and calls the model.
.pickreturns onlyansweranddocs, so the caller can show both.
Edge cases
- Nothing relevant found: the "say you don't know" instruction handles it; you can also skip the model call when
docsis empty and return a fixed message. - Too much context: cap
k, and trim each chunk; long context costs money and can bury the right passage. - Follow-up questions ("what about for UPI?") need the chat history to rewrite the question before retrieval.
- Stale documents: the answer is only as fresh as the index.
Legacy name
RetrievalQA.from_chain_type(llm, retriever=...) did this in the old API. It is deprecated and in langchain_classic.chains. For agents, the current pattern is to give the retriever to create_agent as a tool, so the model can decide when and how often to search.
A real-life example
A support bot over a payments company's help-centre docs indexes 1,400 articles split into about 9,000 chunks. With k=4, the average prompt is about 1,800 tokens. In the first week, 12% of answers were "I don't know"; reviewing them showed half were real gaps in the help centre, which the content team then wrote articles for, and half were retrieval misses on short questions like "refund?". Adding a step that rewrites short questions into full ones before retrieval cut "I don't know" to 7%. Showing the cited article links under each answer also lowered escalations to human agents, because customers could read the full policy.
Follow-up questions to expect
- "How do you return sources?" — Keep the
docsin the output withassign, as above, and render their metadata as links. - "How do you stop the model using outside knowledge?" — An explicit "only from the context" instruction, low temperature, and an evaluation that checks answers against the retrieved text.
- "How would you evaluate this chain?" — Retrieval metrics such as recall at k on labelled questions, plus answer faithfulness and correctness scored on a test set.