Course Content
LangChain Mastery
7 sections · 109 lessons
What are best practices for structuring LangChain code?
What you need to know
A layout that holds up
Text
app/ config.py settings from environment (models, k, URLs, flags) models.py init_chat_model / embeddings factories — the only place providers appear prompts/ prompt templates as files, versioned in git retrieval/ loaders, splitters, index job, retriever factories tools/ @tool functions, one module per external system chains/ LCEL pipelines, one factory per use case agents/ create_agent factories and middleware api.py FastAPI routes — thin: validate, call, returntests/ unit/ fake models, no network evals/ LangSmith datasets and evaluatorsThe rules that matter
- Factories with injected dependencies.
build_support_agent(model, tools, checkpointer)instead of creating clients at import time. Tests pass a fake model; staging passes a cheap one. - One place for providers.
init_chat_model("openai:gpt-5.4-mini")orinit_chat_model(settings.chat_model)inmodels.py. Switching providers then touches one file. - Prompts are assets. Keep them in files (or a prompt registry such as LangSmith's prompt hub) with a comment on why each rule exists.
- Tools are small and typed. Type hints or a Pydantic
args_schema, a docstring that says when to use the tool and when not to, and no hidden global state. - Chain or agent on purpose. A fixed sequence (retrieve → answer) is an LCEL chain: faster, cheaper, predictable. Use
create_agentonly when the model must decide which tools to call and how many times. - Cross-cutting concerns as middleware or config. Retries, fallbacks, PII redaction, call limits and human approval go in middleware; tracing goes in config and environment — not copied into each tool.
Legacy vs current
| Legacy pattern | Current pattern |
|---|---|
LLMChain, SequentialChain, custom Chain subclasses | LCEL runnables joined with the pipe operator (prompt, model, parser) |
initialize_agent, AgentExecutor | create_agent (runs on LangGraph) |
ConversationBufferMemory | Checkpointer + thread_id, trimming or summarisation middleware |
Global langchain.debug = True | LangSmith tracing, langchain_core.globals.set_debug for local work |
A real-life example
A travel-booking startup's first agent was one 900-line main.py: the model client created at import, prompts as f-strings, and a search_flights tool that read an API key from a global. Testing needed real API keys, and switching providers for a cost experiment took two days.
The refactor followed the layout above. models.py reads CHAT_MODEL from config; tools/flights.py exposes search_flights and book_flight with Pydantic schemas; agents/booking.py builds the agent with HumanInTheLoopMiddleware so bookings over ₹50,000 need confirmation. The provider experiment then became a config change, and 140 unit tests run in 6 seconds with fake models.
Follow-up questions to expect
- "Where does business logic go?" — In plain Python functions and tools, not inside prompts. The model decides what to call; your code decides how it is done.
- "How do you share code between chains?" — Small runnables (formatters, retrievers, validators) that compose; avoid deep class hierarchies.
- "How do you deploy it?" — Any Python web stack (FastAPI is common); LangGraph's own server is an option for long-running agents that need persistence and streaming.