166 companion flashcards · AI-assisted study content · Open the deck →
The cards are best suited for developers, AI engineers, and technical learners who are actively working with or exploring LLM-powered applications. You'll get the most value if you already have some familiarity with Python and the basics of working with language models, since the questions assume you want to understand not just what each component is, but how to use it. If you're newer to AI development, you can still work through the deck to build vocabulary and intuition before diving into code.
To get the most out of studying, try to connect each concept to a small practical example as you go. LangChain is a hands-on tool, and its components really click once you see them working together in a chain or pipeline. Spacing your review sessions over several days will help the terminology and patterns move into long-term recall, and reviewing the Runnable cards together can be especially helpful since they describe a unified way of composing components in the framework.
LangChain is an open-source Python framework for composing applications powered by large language models. Rather than wrapping a single model call, it provides modular building blocks — prompts, models, output parsers, retrievers, tools, and memory — that you stitch together into chains or agents. The modern package layout splits responsibilities across langchain-core (the base abstractions such as Runnable, BaseMessage, BaseChatModel, BaseRetriever, and Document, with no third-party LLM dependencies), langchain as the orchestration layer, langchain-community for hundreds of community-maintained integrations, and dedicated partner packages such as langchain-openai, langchain-anthropic, langchain-chroma, and langchain-pinecone that host the stable, actively-maintained integrations. Installation follows the pattern of installing langchain, langchain-core, the relevant partner package, and optionally langchain-community, with API keys read automatically from environment variables via python-dotenv.
At the heart of the framework is the Runnable interface, the common contract that every component implements. A Runnable supports invoke for a single input returning a single output, an invoke for async execution, stream for an iterator of output chunks, astream, batch for processing a list of inputs, abatch, astream_log, and transform. This uniformity is what makes the LangChain Expression Language work: any two runnables can be joined with the pipe operator to form a RunnableSequence, where the output of one stage becomes the input of the next. A chain written as prompt piped to llm piped to parser is functionally identical to explicitly constructing a RunnableSequence with first, middle, and last runnables. Because LCEL chains are themselves runnables, they automatically inherit streaming, batching, and async execution without extra glue code, and tokens flow through the chain as the model produces them rather than only after the entire response has completed.
The base abstractions include BaseChatModel for chat-style models such as GPT-4o, Claude, and Gemini, and the legacy LLM class, which is now largely deprecated. Chat models accept a list of BaseMessage subclasses — SystemMessage, HumanMessage, AIMessage, ToolMessage, and FunctionMessage — and return an AIMessage. AIMessage carries a tool_calls attribute describing any tools the model wants to invoke, where each entry has an id, a name, parsed args, a type, and an optional artifact. The matching ToolMessage is what you append to the conversation to return tool results, identified by the tool_call_id from the originating AIMessage entry. Together, these abstractions form a uniform message protocol that any provider can implement, letting you swap models without rewriting application code.
Configuration flows through a chain via RunnableConfig, a TypedDict carrying callbacks, tags, metadata, run_name, configurable, and recursion_limit that propagates automatically to every nested step. For introspection, runnables expose methods such as get_input_schema, get_output_schema, get_graph (a drawable graph of the chain), and get_prompts (the list of prompt templates nested in the chain), all of which surface to debugging tools like LangSmith. The lc_namespace class attribute and a Pydantic model_config flag control how custom subclasses serialize and integrate with the rest of the framework.
Prompts are how you talk to the model, and LangChain offers a small family of templates that interpolate variables, hold few-shot examples, or inject dynamic message lists. The simplest is PromptTemplate, which formats a single string for completion-style models; you build one with the from_template factory and invoke it with a dict of variable values. ChatPromptTemplate is the chat equivalent: constructed with from_messages, it accepts a list of role and template tuples (such as system and human), where variables are wrapped in curly braces. FewShotPromptTemplate layers on top to inject a list of example input and output pairs at runtime, letting the model learn the desired pattern via in-context examples rather than fine-tuning. MessagesPlaceholder is a special template entry that inserts a dynamic list of messages — most often the chat history — into a chat prompt at invocation time. All of these ultimately produce a PromptValue, which exposes both to_string and to_messages methods so the same template can feed either a string LLM or a chat model without rewriting it.
Chat models are invoked with invoke on a list of messages or a prompt value, returning an AIMessage. Parameters like model name, temperature, max_tokens, and request_timeout are passed to the constructor, and bind lets you freeze per-call overrides — such as a higher temperature, a stop sequence, or a tools list — onto a new runnable without rebuilding the chain. For streaming, calling stream returns an iterator of chunks; for batched processing, batch accepts a list of inputs and supports a max_concurrency config option. Async variants integrate with Python's asyncio for parallel or long-running pipelines. For OpenAI-style models, with_structured_output is a unified wrapper that binds a Pydantic model or JSON schema to the LLM so the response comes back as validated structured data instead of free-form text, eliminating the need to write parsing prompts by hand.
Output parsers sit at the end of a chain to convert the model's raw output into a more useful type. StrOutputParser extracts the plain string from an AIMessage and is the most common parser in LCEL chains. JsonOutputParser and PydanticOutputParser parse the response into a Python dict or a validated Pydantic model instance; both expose get_format_instructions so you can inject a description of the expected schema into the prompt, and PydanticOutputParser adds type coercion and explicit error messages. Specialized parsers cover other shapes: CommaSeparatedListOutputParser returns a list of strings, DatetimeOutputParser uses a format string to produce a Python datetime, EnumOutputParser validates that the model picked one of a fixed set of values, XMLOutputParser traverses an XML schema into a dict, and StructuredOutputParser handles a list of named string response schemas returning a dict without strict type coercion.
When the LLM fails to follow a schema, OutputFixingParser wraps a base parser and uses a second LLM call to repair the output, while RetryWithErrorOutputParser re-prompts with both the original output and the parser's specific error message, giving the model tighter feedback. Both rescue a chain from total failure on the first malformed response. The format_instructions pattern — injecting the parser's get_format_instructions string into the prompt via partial_variables or prompt.partial — is the standard way to keep the model aware of the expected output shape, and it composes cleanly with with_structured_output for native provider support where available.
LCEL's real power lies in its composition primitives, which let you express branching, fan-out, and routing as dataflow. RunnableSequence chains runnables sequentially, while RunnableParallel runs multiple runnables concurrently and returns a dict of their results — useful when you want summary, translation, and keyword extraction produced in parallel from the same input. RunnablePassthrough forwards its input unchanged; it is the workhorse of retrieval-augmented generation pipelines because it keeps the original question alongside retrieved context in the same dict that flows into the next prompt. RunnableLambda wraps any Python function as a runnable so it can participate in the chain, and RunnableBranch routes an input to different chains based on a list of condition and runnable tuples plus a default, enabling if-then-else logic at the chain level.
Several auxiliary methods fine-tune chain behavior without rewriting it. The assign method on a RunnableParallel adds a new key to the output dict while preserving the original input, enabling map-step-style dataflow where each step sees the results of all prior steps. The pick method returns a runnable that extracts a single entry from a dict (or a single index from a tuple), short-circuiting upstream computation by skipping unselected branches. The bind method freezes keyword arguments onto every call, so you can pre-attach a stop sequence, a tools list, a response_format, or per-call parameters. For runtime flexibility, wrapping a parameter with a ConfigurableField lets you override it per call through config, and configurable_alternatives lets you swap whole sub-chains — different models or prompts — at invoke time without rebuilding the graph.
Reliability primitives attach to any runnable. with_retry retries the same runnable on transient errors such as rate limits and timeouts, with arguments like stop_after_attempt, wait_exponential, wait_exponential_jitter, and retry_if_exception_type controlling backoff and jitter. with_fallbacks runs an alternative runnable when the primary raises a non-retried exception — for example, a cheaper model as backup when the primary fails. The two are composable: wrap the primary with retries first, then wrap that result in fallbacks, so you get transient-error recovery plus graceful degradation. Other safeguards include a configurable recursion_limit on RunnableConfig to prevent infinite loops, and runtime caps enforced by guard runnables or routers for cost control.
Streaming and event inspection deserve special attention. In an LCEL chain, the pipe operator ensures tokens flow through as they are produced, so the final parser's stream method yields the model's text deltas instead of waiting for the whole response. The most granular streaming API is astream_events, which emits typed events such as on_chat_model_start, on_tool_start, and on_chain_end correlated by run_id and parent_run_id. Together, these primitives let you build deterministic, testable, production-grade chains that stream, batch, and fall over gracefully without per-component glue code.
Conversational applications need to remember what the user has already said, and LangChain offers a layered set of tools, ranging from legacy Memory classes to the modern RunnableWithMessageHistory pattern. ConversationBufferMemory stores the entire conversation as a list of messages — simple, but its token cost grows unboundedly. ConversationBufferWindowMemory keeps only the last k turns, a useful middle ground. ConversationSummaryMemory periodically asks an LLM to compress the running transcript into a summary, while ConversationSummaryBufferMemory keeps recent messages in full and summarizes older ones once a token limit is reached, balancing fidelity with token efficiency. ConversationEntityMemory extracts named entities mentioned in the conversation and stores facts about each so the assistant can recall user-specific details, and VectorStoreRetrieverMemory stores each turn in a vector database and pulls the most semantically similar past turns at query time, which scales to very long histories without saturating the context window.
The recommended modern pattern is to manage messages yourself and inject them with structured primitives. ChatMessageHistory is a simple in-memory message store with helpers to add user messages, AI messages, or arbitrary BaseMessage instances; you can swap it for persistent implementations such as RedisChatMessageHistory, PostgresChatMessageHistory, or SQLChatMessageHistory without changing the surrounding code. To plug history into a chain, insert a MessagesPlaceholder into your ChatPromptTemplate (typically named chat_history) and wrap the chain with RunnableWithMessageHistory, supplying a get_session_history factory keyed on a session_id in the config. This wrapper handles loading, appending, and saving the history automatically, so the chain's invoke signature stays clean and free of memory-management boilerplate.
A subtle distinction is between session history and the long-term store — a cross-session BaseStore for facts like user preferences or accumulated knowledge. LangChain exposes both through separate callbacks on RunnableWithMessageHistory, and you can use them together: per-thread conversation on one side, persistent user facts on the other. The legacy Memory classes in langchain.memory remain available but are slated for deprecation; new code should prefer ChatMessageHistory plus RunnableWithMessageHistory for transparency, testability, and straightforward persistence swaps.
The retrieval stack begins with the Document class, a tiny data wrapper with two fields: page_content as a string and metadata as a dict. Every loader, splitter, retriever, and vector store in LangChain works with Documents, so understanding this class is foundational. DocumentLoader implementations exist for dozens of formats — TextLoader for plain text, PyPDFLoader for PDFs, WebBaseLoader for HTML pages — and each returns a list of Documents. Once loaded, large documents are typically split into smaller chunks with a TextSplitter because embedding models and LLM context windows have finite capacity. RecursiveCharacterTextSplitter is the workhorse: it tries a list of separators in order — paragraph breaks, then newlines, then spaces, then empty string — and picks the first that yields pieces under chunk_size, with chunk_overlap characters carried over between chunks to avoid losing context at boundaries. Other splitters specialize in code (using a Language enum to respect function and class boundaries), HTML (grouping by header tags), Markdown (preserving header paths and code blocks as metadata), tokens (using tiktoken for token-precise splits), and semantics (detecting breakpoints by embedding distance between consecutive sentences).
Embeddings convert text into numerical vectors, and LangChain's Embeddings interface is intentionally asymmetric: embed_documents returns a list of vectors (where providers can cache), while embed_query returns a single vector for a search query. OpenAI's third-generation embeddings, text-embedding-3-small with 1536 dimensions and text-embedding-3-large with 3072 dimensions, support a dimensions parameter that lets you truncate to a smaller size for cheaper storage with minimal recall loss. To build a custom integration, subclass the Embeddings base and implement both methods; the protocol is intentionally small so any vector-capable model can plug in.
Vector stores index these embeddings for similarity search. Common choices include FAISS — fast, in-memory, with save_local and load_local for on-disk persistence — Chroma (open-source with persistent local storage via a persist_directory), InMemoryVectorStore (numpy-based, zero extra dependencies, good for tests and small corpora), and managed services such as Pinecone, Weaviate, and Qdrant, plus PGVector for staying inside Postgres. To build a custom VectorStore, subclass the base and implement from_texts, add_texts, similarity_search, and optionally delete and the async variants. Vector stores expose a uniform Retriever via as_retriever, which works in LCEL chains and supports search_type configurations of plain similarity, mmr (maximal marginal relevance, picking documents that are relevant and diverse), and similarity_score_threshold (dropping documents below a normalized cosine score).
More sophisticated retrievers solve specific retrieval problems. MultiQueryRetriever has an LLM generate several paraphrased queries, retrieves documents for each, and merges the results to improve recall. SelfQueryRetriever translates a natural-language question into a structured query with both semantic and metadata filters, requiring a vector store that supports metadata filtering. ParentDocumentRetriever indexes small chunks for precise matching but returns the larger parent document for richer LLM context. EnsembleRetriever combines results from multiple retrievers — typically a BM25 keyword retriever and a vector retriever — using reciprocal rank fusion with configurable weights, which is a powerful default for hybrid search. You can also subclass BaseRetriever to implement custom retrieval logic, setting model_config to allow arbitrary types and lc_namespace for serialization.
A retrieval-augmented generation pipeline combines a retriever, a prompt, and an LLM so the model answers questions grounded in your own data. The canonical LCEL pattern is to build a dict with the retriever piped to a format function and the original question carried through RunnablePassthrough, then pipe that dict into a prompt that fills the context and question slots, then into an LLM, and finally into an output parser. RunnablePassthrough is what carries the user's original question into the prompt alongside the retrieved context; the format function is usually a RunnableLambda that joins the page_content strings of the retrieved documents with double newlines. For chat with history, add a question rewriter step before the retriever that uses the chat history to produce a standalone search query — implemented as a RunnableLambda or a small prompt-chain whose output becomes the retriever's input. Beyond the basic retriever, ContextualCompressionRetriever wraps a base retriever with a DocumentCompressor — an LLM extractor like LLMChainExtractor, an EmbeddingsFilter that scores each chunk by similarity, or an EmbeddingsRedundantFilter that drops near-duplicates — to keep only the most relevant content.
Document processing strategies define how multiple retrieved chunks are combined into a single prompt. The stuff strategy, exposed via create_stuff_documents_chain, concatenates every retrieved document into one prompt as the context variable; it is the simplest and highest-quality option when the total context fits the window. Map-reduce generates an answer per document in parallel, then summarizes all answers; it scales to large corpora but loses cross-document reasoning. Refine processes documents sequentially, using each new document to update a running answer; it produces high-quality long-form synthesis but is linear in the number of documents. Map-rerank asks the LLM for an answer and a confidence score per document and returns the highest scorer — cheap and effective when the answer is concentrated in a single source. A useful post-processor in any of these pipelines is LongContextReorder, which rearranges documents so the most relevant ones sit at the start and end of the context, exploiting the lost-in-the-middle effect where LLMs pay more attention to context extremes.
Several retrieval enhancements complement these strategies. Cross-encoder rerankers such as CohereRerank and FlashrankRerank score each query and document pair jointly and are more accurate than bi-encoder similarity, so they are typically applied to the top-K candidates from a fast first-stage retriever. HyDE — Hypothetical Document Embeddings — asks the LLM to write a hypothetical answer, embeds that, and uses the resulting vector for retrieval; it often improves recall because the hypothetical answer lives in the same embedding region as the real documents. Finally, the older RetrievalQA and ConversationalRetrievalChain classes still work but are considered legacy; in modern code you express the same flows in LCEL or in a LangGraph graph, which gives you more control over the steps and the state.
Tools are how an LLM takes real action in the world. The tool decorator turns any Python function into a LangChain tool — the function's name becomes the tool's name, and its docstring becomes the description that the model reads when deciding whether to call it. You invoke the tool like any other runnable. StructuredTool.from_function is the heavier-duty variant: you supply an explicit Pydantic schema for the arguments, which gives you richer validation and a more reliable schema for the model. Both wrap the abstract BaseTool, which defines _run, _arun, args_schema, name, and description; subclassing it directly is the path for tools that need custom validation, async-only behavior, or dynamic schemas.
Once you have tools, you can either bind them to a chat model with bind_tools to get the model to emit tool_calls, or hand them to an agent. An agent is different from a chain: a chain has a predetermined sequence of steps, while an agent uses the LLM at runtime to decide which tools to call, in what order, and how many times. The legacy AgentExecutor runs an agent plus its tools in a loop with safety caps like max_iterations, a configurable early_stopping_method of force or generate, and handle_parsing_errors for malformed tool calls. Modern LangChain prefers LangGraph for agent loops: create_react_agent from langgraph.prebuilt is the canonical tool-calling agent factory that works with any chat model supporting tool calls, including OpenAI, Anthropic, Gemini, and Mistral. For OpenAI models specifically, the OpenAI Functions Agent uses native function calling rather than parsing a ReAct text trace, which is more reliable on those models.
Tool execution has its own conventions. tool_choice set to any forces the model to call a tool, while a dict value forces a specific named tool — useful for routing decisions. After the model emits an AIMessage with tool_calls, you look up each tool by name, invoke it with the parsed args, and append a ToolMessage whose tool_call_id matches the originating call. A tool with return_direct set to True returns its output to the user immediately, skipping the LLM summary step — ideal for deterministic lookups like get_current_time. To handle failures gracefully, raising ToolException inside a tool tells the agent to retry with the exception's message as feedback, and handle_tool_error on a tool does the same declaratively. A tool can also return a tuple of content and artifact so the LLM sees a clean content string while the raw artifact stays in the run state for LangSmith inspection.
For specialized workloads, the SQL Agent writes and executes queries against a SQLDatabase wrapper, the Pandas DataFrame Agent generates Python in a PythonREPL subprocess (only run in sandboxes because arbitrary code execution is a security risk), and create_stuff_documents_chain plus friends form the modern replacement for RetrievalQA. LangGraph extends the agent story into a general framework for stateful, multi-step workflows. Its core primitive is StateGraph, a directed graph where nodes are functions or runnables that read and update a shared typed state, with edges defining the flow. Conditional edges route to different nodes based on a function that inspects the current state. Checkpointers like MemorySaver, SqliteSaver, and PostgresSaver snapshot the state at each step, so you can pause, resume, rewind, and branch conversations — a thread_id passed via configurable scopes each independent conversation. For human-in-the-loop, set interrupt_before or interrupt_after on a node to pause execution for inspection and editing before resuming. To stream results, app.stream yields state deltas per node, while app.astream_events provides token-level streaming with typed events.
LangChain's observability story centers on callbacks and LangSmith. A BaseCallbackHandler subclass implements lifecycle hooks — on_llm_start, on_chat_model_start, on_llm_new_token, on_llm_end, and on_llm_error for model runs; on_chain_start and on_chain_end for any runnable; on_tool_start and on_tool_end for tools. Nested runnables emit nested start and end pairs correlated by run_id and parent_run_id, so you can trace a single invoke call all the way down. Handlers are passed via config per call or attached globally. LangSmith is the hosted platform that consumes these callbacks: setting the LANGSMITH_TRACING flag and the API key automatically logs every LangChain run, and the result is a hierarchical Run Tree you can browse, replay, and compare on smith.langchain.com. The free tier includes limited traces per month; paid plans add longer retention, datasets, evaluators, and team collaboration features.
Evaluation reuses the same primitives. LangChain's evaluation module defines a StringEvaluator interface — subclasses implement _evaluate_strings and return a dict with a score and optional reasoning. Concrete examples include exact-match and regex-match evaluators, embedding-distance metrics such as cosine, euclidean, and Manhattan, and LLM-judged evaluators like CriteriaEvalChain (does the prediction satisfy a custom criterion?) and PairwiseStringEvaluator (compare two outputs head-to-head, returning A, B, or tie). You create a dataset with the LangSmith Client (a list of input and reference pairs) and run an evaluation with a target chain, a dataset, and an evaluator to score your chain. Writing a custom evaluator is straightforward: subclass StringEvaluator, set requires_input, requires_reference, and evaluation_name, and implement _evaluate_strings. To control LLM cost in production, combine with_retry for transient errors with a callback that records token usage and a guard runnable that short-circuits when a per-user or daily budget is exceeded.
Deployment and caching round out the picture. LangServe takes any Runnable and exposes it as a REST API with FastAPI; add_routes automatically creates invoke, batch, stream, stream_log, and playground endpoints, generates an input and output JSON schema, and accepts a RunnableConfig factory for runtime overrides. The caching layer avoids redundant LLM calls: set_llm_cache accepts InMemoryCache for tests, SQLiteCache or RedisCache for local persistence, and RedisSemanticCache for similarity-based caching where two semantically equivalent prompts return the same stored response (configured with a score_threshold). Caches are keyed by the serialized prompt plus model parameters. LangChain Templates, the older gallery of pre-built project templates, has been archived in favor of the langgraph new CLI for new projects. Together, callbacks, LangSmith, LangServe, and caching give you the operational scaffolding to take a prototype into production with traceability, evaluation, and cost controls.
pip install langchain langchain-openaiRunnableBranch routes input to different chains based on conditions. Pass tuples of (condition, runnable) plus a default.from langchain_core.runnables import RunnableBranch
branch = RunnableBranch(
(lambda x: "code" in x["topic"], code_chain),
(lambda x: "math" in x["topic"], math_chain),
general_chain, # default
)
result = branch.invoke({"topic": "code review"})import os
os.environ["LANGSMITH_TRACING"] = "true"
os.environ["LANGSMITH_API_KEY"] = "your-key"
os.environ["LANGSMITH_PROJECT"] = "my-project"
# All chain invocations are now traced
result = chain.invoke({"topic": "LangSmith"})
# View traces at smith.langchain.comlangchain-community is a community-maintained package containing hundreds of loaders, vector stores, retrievers, and integrations contributed by the community. Quality varies, and stable production code is increasingly being moved to dedicated partner packages.runnable.get_input_schema() and get_output_schema() (Pydantic models). get_graph() returns a drawable graph; get_prompts() lists prompts nested in the chain — useful for LangSmith inspection.VectorStore and implement from_texts, add_texts, similarity_search, and optionally delete, from_documents, and the async variants. The base class also requires __init__ accepting an embedding function.SemanticChunker uses embeddings to detect natural breakpoints: it splits where the cosine distance between consecutive sentences exceeds a threshold. Chunks align with semantic topic shifts rather than fixed character counts.Client.create_dataset to define examples (input + expected output), then Client.evaluate with an evaluator (e.g., label_score, cot_qa, or custom) to score your chain. Runs are logged under a project name for comparison.max_iterations is hit: "force" returns the partial answer, "generate" asks the LLM one more time to summarize. Use "generate" for better graceful degradation.pd.DataFrame by generating and executing Python code via a tool that runs in a PythonREPL. Use create_pandas_dataframe_agent. Enable only in sandboxed environments — arbitrary code execution is a security risk.Drill this topic
166 flashcards on LangChain — free, no signup needed to start.
Study LangChain flashcardsLearnWiki pages are generated with AI assistance from LearnCoachAssist's reviewed study catalog and may contain errors — verify anything critical against your course materials.