Skip to course content
Free LangChain course

LangChain for LLM Applications and RAG

Unit 12.01: Building the retrieval chain

The assembled chain: scored retrieval, a threshold, a grounding prompt, and a refusal path that exists in code.

Every piece from the last eleven modules

Stable chunk ids, metadata with dates, scored retrieval, an explicit threshold, formatted context carrying ids, and a decline branch.

The code answers a covered question and an uncovered one.

from langchain_core.documents import Document
from langchain_core.embeddings import DeterministicFakeEmbedding
from langchain_core.language_models.fake_chat_models import FakeListChatModel
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.vectorstores import InMemoryVectorStore

REFUSAL = "The available documents do not cover that."
DOCS = [Document("Refunds are allowed within 7 days of purchase.",
                 metadata={"id": "support-policies-v4.md#refunds:2",
                           "updated": "2026-06-14"})]
store = InMemoryVectorStore.from_documents(DOCS, DeterministicFakeEmbedding(size=64))

prompt = ChatPromptTemplate.from_messages([
    ("system", "Answer only from CONTEXT. Cite the chunk id for every claim."),
    ("human", "CONTEXT:\n{context}\n\nQUESTION: {question}")])
model = FakeListChatModel(
    responses=["Refunds are allowed within 7 days. [support-policies-v4.md#refunds:2]"])
generate = prompt | model | StrOutputParser()


def answer(question, k=4, threshold=0.75):
    scored = store.similarity_search_with_score(question, k=k)
    keep = [(d, s) for d, s in scored if s >= threshold]
    if not keep:
        return {"text": REFUSAL, "cites": [], "refused": True}
    context = "\n".join(f"[{d.metadata['id']}] {d.page_content}" for d, _ in keep)
    return {"text": generate.invoke({"context": context, "question": question}),
            "cites": [d.metadata["id"] for d, _ in keep], "refused": False}


for q in ["What is the refund window?", "What is the office address?"]:
    out = answer(q)
    print(f"{q:32} refused={out['refused']}")
    print(f"   {out['text']}")

The refusal is a branch in answer, not an instruction in the prompt. That is what makes it testable: the function returns refused: True and an empty citation list, both assertable without reading English.

The chunk id is support-policies-v4.md#refunds:2 - document, section and position - so it survives a rebuild and a reader can follow it. It is verbose in the output and that verbosity is the feature.

The mistake this prevents

The mistake is building this with similarity_search and adding the threshold later. The unscored version has no refusal path at all, so every test you write against it is a test of the answering path, and the refusal arrives untested.

Takeaway

Assemble scored retrieval, an explicit threshold, ids in the context, and a refusal branch in code. The refusal must be a return value, not a prompt instruction.