« Phase 29 README · Warmup · Deep Dive · Principal Deep Dive · Core Contributor · Staff Notes

Phase 29 — Hitchhiker's Guide (LangChain Core)

The compressed practitioner tour. Read the WARMUP for the mechanism; this is the stuff you say in the meeting.

30-second mental model

LangChain core = one interface taken seriously. Everything — prompt, model, parser, retriever, tool, your own function — is a Runnable (invoke/batch/stream + async twins), and LCEL's | composes Runnables into a Runnable. That closure is why prompt | model | parser gets streaming, batching, fallbacks, and tracing for free. LCEL builds pipelines (DAGs: sequence, parallel, branch — no cycles, no state). The moment you need a loop, durable state, or a human pause, you've left LCEL and entered LangGraph (Phase 18). The classic AgentExecutor was LangChain's loop — now deprecated in favor of exactly that graph.

The facts to tattoo on your arm

FactWhy
Runnable = invoke / batch / stream (+ ainvoke…)the one interface everything implements
composition is closed: a | b is a Runnablestreaming/fallbacks/tracing apply to whole chains
invoke == stream folded with +chunks are additive (AIMessageChunk.__add__)
dict in a pipe → RunnableParallel; fn → RunnableLambdawhy the RAG chain opens with a dict literal
RunnablePassthrough.assign(k=...)add a computed key, keep the rest — the RAG workhorse
a sequence streams only its last stream-capable stepmodel + parser go LAST or you lose streaming
retriever contract: invoke(query) -> list[Document]the chain depends on the contract, not the scorer
.with_structured_output constrains; parsers post-processdifferent mechanism, different failure mode
AgentExecutor default max_iterations=15, forced stop"Agent stopped due to iteration limit." is a real string
errors become observations, not crashesthe agent's repair loop (unknown tool, handle_tool_error)
LCEL = DAG; LangGraph = cycles + state + checkpointsthe whole decision framework in one line

Framework one-liners

  • ChatPromptTemplate.from_messages([...]) + MessagesPlaceholder("chat_history") — prompts.
  • {"context": retriever | format_docs, "question": RunnablePassthrough()} | prompt | model | StrOutputParser() — the canonical RAG chain.
  • model.bind_tools(tools)AIMessage.tool_calls → run tools → ToolMessage — tool calling.
  • model.with_structured_output(Schema) — typed output via the native channel.
  • chain.with_fallbacks([...]) / .with_retry() / .with_config(...) — resilience wrappers.
  • RunnableWithMessageHistory(chain, get_history) + session_id config — chat memory.
  • vectorstore.as_retriever(search_kwargs={"k": 4}) — store → retriever adapter.
  • langchain-core / langchain / langchain-openai etc. / LangGraph / LangSmith / LangServe — the package map. LANGCHAIN_TRACING_V2=true — the LangSmith on-switch.

The distinctions that signal seniority

  • LCEL vs LangGraph → pipeline vs runtime. One-way data flow → LCEL; anything with a back-edge (loops, retries-with-state, approval gates) → LangGraph. They compose — a compiled graph is a Runnable.
  • Parser vs .with_structured_output → post-processing text vs constraining generation through the provider's native channel. The second fails less; the first works everywhere.
  • Scratchpad vs chat history → per-run working memory (the action/observation trace) vs cross-run conversation memory. Different lifetimes, different bugs.
  • Retrieve-style vs RetrieveAndGenerate-style → in LangChain terms: a raw retriever you prompt yourself vs create_retrieval_chain doing prompt+generate+sources for you.
  • Deprecated vs deadAgentExecutor is deprecated for new work but very alive in prod; "maintain it, migrate it, don't start on it" is the senior posture.

War stories

  • The chain that stopped streaming after a one-line change. Someone appended a tidy post-processing lambda after the parser. It needed the whole string, so the UI went from token-by-token to a 9-second freeze. Fix: keep the model + stream-capable parser last (or make the step chunk-wise). "Streaming is a property of the pipeline shape" — learned in prod.
  • The RAG bot that answered confidently about a product that didn't exist. Empty retrieval, and the prompt didn't force grounding — the model freestyled. The fix was Lab 02's exact pattern: refuse on empty context, and return citations so every answer is attributable. Retrieval bugs masquerade as model bugs.
  • The agent that burned $400 overnight. A legacy AgentExecutor with a flaky tool: the error observation ("not a valid tool") made the model retry the same hallucinated tool forever — nobody had lowered max_iterations from 15 for a two-step task, and a cron kept re-invoking it. Budgets are a feature; the trace (return_intermediate_steps) is how it was diagnosed.

Vocabulary

Runnable (invoke/batch/stream) · LCEL / | / RunnableSequence · RunnableParallel (fan-out) · RunnablePassthrough / .assign · RunnableLambda (coercion) · RunnableBranch (routing) · with_fallbacks / with_retry / bind · PromptTemplate / ChatPromptTemplate / MessagesPlaceholder / few-shot · output parser / StrOutputParser / PydanticOutputParser / .with_structured_output · Document / loader / splitter / Embeddings / VectorStore / as_retriever · RAG chain / citations / grounded refusal · @tool / bind_tools / tool_calls / ToolMessage · AgentExecutor / AgentAction / AgentFinish / scratchpad / max_iterations / return_intermediate_steps · RunnableWithMessageHistory / session_id · LangSmith (tracing/eval) · LangServe (deploy) · LangGraph (the successor runtime — Phase 18).

Beginner mistakes

  1. Reaching for LangGraph for a one-pass RAG pipeline — or hacking loops into LCEL. Match the runtime to the control-flow shape.
  2. Consuming the retrieved docs instead of carrying them (assign), then wondering where citations should come from.
  3. Putting a whole-input step after the model and losing streaming silently.
  4. Writing new agents on AgentExecutor because the tutorial you found predates LangGraph.
  5. Testing chains against a live model API — inject a fake model (it's just a Runnable) and assert the wiring.
  6. Treating tool docstrings as comments. The model reads them to choose tools — they are prompts.
  7. Importing everything from the legacy monolith instead of langchain-core + per-provider integration packages, then fighting version conflicts.