Skip to course content
Free LangChain course

LangChain for LLM Applications and RAG

Unit 11.00: Testing a chain without calling a paid API

The whole trick is one line: build the chain in a function that takes the model as a parameter.

Inject the model, exercise everything else

Production passes the real model; tests pass a fake. Prompt, parser and wiring are exercised identically.

The code builds a chain that way and asserts on the result.

from langchain_core.language_models.fake_chat_models import FakeListChatModel
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate


def build_chain(model):
    """Take the model as a parameter -- this is the whole testability trick."""
    return (ChatPromptTemplate.from_messages([
        ("system", "Answer only from CONTEXT."),
        ("human", "CONTEXT: {context}\n\nQUESTION: {question}")])
        | model | StrOutputParser())


fake = FakeListChatModel(responses=["Refunds are allowed within 7 days. [c1]"])
chain = build_chain(fake)
result = chain.invoke({"context": "[c1] Refunds within 7 days.",
                       "question": "How long?"})

print("result:", result)
print("assertions:")
for name, ok in [("cites a chunk", "[c1]" in result),
                 ("mentions 7 days", "7 days" in result),
                 ("no hedging", "probably" not in result.lower())]:
    print(f"   {'PASS' if ok else 'FAIL'} {name}")

# The chain is built by a function taking the model as a parameter. In
# production you pass the real one; in tests you pass a fake. Everything else --
# prompt, parser, wiring -- is exercised identically.

Everything except the model itself is under test: the template renders, the parser runs, the composition works, and the assertions check the output's structure.

That covers the large majority of what actually breaks. Chains fail at boundaries, on missing template variables, on parser mismatches and on wiring errors - and none of those needs a real model to detect.

The mistake this prevents

The mistake is building the chain at module import with a real model, which makes it untestable without a key and often makes importing the module a network call. Build inside a function, take the model as an argument.

Takeaway

Build chains in functions that accept the model. Everything except the model's own behaviour is then testable with no key, no cost and no network.