LangChain Mastery

Course Content

LangChain Mastery

7 sections · 109 lessons

Write a function to create a LangChain chain for question answering.


Retrieval QA that keeps its sourcesQuestionRetrieverreturnstop 4 chunksformat_docstags eachwith its sourcePrompt:answer onlyfrom contextOutput: answerplus the docsShort questions like 'refund?' were rewritten before retrieval.
Returning the documents alongside the answer is what lets you tell a retrieval miss from a model mistake.

What you need to know

Python
from operator import itemgetterfrom langchain_core.prompts import ChatPromptTemplatefrom langchain_core.output_parsers import StrOutputParserfrom langchain_core.runnables import RunnablePassthroughdef format_docs(docs):    return "\n\n".join(f"[{d.metadata['source']}] {d.page_content}" for d in docs)def build_qa_chain(retriever, llm):    prompt = ChatPromptTemplate.from_messages([        ("system", "Answer using ONLY the context. Cite sources in [brackets]. "                   "If the context does not contain the answer, say you don't know.\n\n"                   "Context:\n{context}"),        ("human", "{question}"),    ])    answer = prompt | llm | StrOutputParser()    return (        RunnablePassthrough.assign(docs=itemgetter("question") | retriever)        | RunnablePassthrough.assign(context=lambda x: format_docs(x["docs"]))        | RunnablePassthrough.assign(answer=answer)    ).pick(["answer", "docs"])qa = build_qa_chain(vectorstore.as_retriever(search_kwargs={"k": 4}), llm)result = qa.invoke({"question": "How long do refunds take?"})result["answer"], [d.metadata["source"] for d in result["docs"]]

Step by step:

  1. itemgetter("question") | retriever sends only the question to the retriever, which returns a list of Document objects.
  2. format_docs joins them into one string, each tagged with its source file so the model can cite it.
  3. The answer chain fills the prompt and calls the model.
  4. .pick returns only answer and docs, 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 docs is 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 docs in the output with assign, 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.