Where DataDesk stands
v1 chains tools, streams, and refuses to invent columns. But paste it a stakeholder email and it ships the sender's address straight to a cloud API; leave a thread running all afternoon and the context window fills; and if the Claude API hiccups, DataDesk simply dies. None of these are features of your agent - they are properties every production agent needs. That is exactly what middleware is for.
Cross-cutting concerns, composably 9 min live
A concern is cross-cutting when every agent needs it and no agent's business logic should contain it. The 1.x insight: model them all as layers a request passes through on its way to the model - and back through on the way out.
LiveWhy middleware exists at all3 min▶
In the 0.x era these concerns lived inside application code: a summarize-when-long branch here, a regex PII scrub there, retry logic copy-pasted around every model call. Three failures followed, everywhere, always:
- Duplication: five agents meant five slightly different retry implementations, four of them wrong in different ways.
- Tangling: business logic and plumbing in one function - unreviewable, untestable, and terrifying to touch.
- Gaps: the one code path someone forgot to scrub is, by law of nature, the one the auditor finds.
1.x moves them into declared layers on create_agent(middleware=[...]). Your agent code stays business-only; the concerns compose in a list you can read, reorder, and diff in review. If you have used WSGI/ASGI middleware or Express, this is that idea, aimed at the model call.
LiveThe built-in five3 min▶
All from langchain.agents.middleware, all attachable in one line each:
| Middleware | Concern | DataDesk use |
|---|---|---|
| SummarizationMiddleware | Long chats blow the context window | All-afternoon analysis threads keep working - Demo 1 |
| HumanInTheLoopMiddleware | Some tool calls need a human yes | Approval gates - the b7 deep dive builds on this |
| PIIMiddleware | Personal data must not reach the provider | Scrub stakeholder pastes before send - Demo 1 |
| Tool retry | Tools fail transiently | Flaky warehouse connection gets N retries, not a crash |
| Model fallback | Providers have bad days | Claude down → local Ollama engine - Demo 2 |
(Rate limiting ships as a built-in too - same shape, attach and forget.) The composition rule from the diagram: order is the list order, and privacy layers belong outermost.
The audit that took an afternoon instead of a quarter. A regulated team was asked to prove no customer emails reached their LLM vendor. Their answer was one line of code in review - PIIMiddleware("email", ...) outermost in the list - plus its tests. The sister team with hand-rolled scrubbing spent three months tracing code paths for the same question.
Self-studyCustom middleware - the three hooks3 min read▶
When no built-in fits, you write your own. Three hook styles, in order of power:
- before_model - runs before each model call. Inspect or edit the messages going in. Use for: injecting today's date, enforcing input budgets, custom redaction.
- after_model - runs on the way out. Inspect or edit the response. Use for: logging content_blocks (b2 pays off), blocking answers that violate policy, tagging outputs for evals.
- wrap-style - wraps the model call itself, so you control both sides AND whether the call happens at all. Retries, fallbacks, caching and circuit breakers live here - it is how the built-in fallback works.
Rule of thumb: reach for before/after first; wrap only when you need to own the call. And check the built-ins list again before writing anything - the whole point of this session is that someone already wrote the boring layer, tested it, and maintains it.
Trust, with a lockfile 6 min live
The middleware system is also where LangChain shipped its most instructive recent bug. This part is short, true, and worth more than most tutorials: it is why professionals pin.
LiveThe yanked-releases story4 min▶
In the 1.x line, releases 1.3.5 and 1.2.5 were yanked from PyPI - pulled after publication - because a change to SummarizationMiddleware's signature broke code that used it. The same layer you attach tonight.
- What yanking means: the files stay downloadable if explicitly pinned to that exact version, but installers skip yanked releases when resolving ranges. It is the ecosystem's recall notice: "we shipped this; do not take it."
- Why
pip install -Uon a Friday is a bad habit: an unpinned upgrade window is exactly how a yanked-grade regression walks into production between your last test run and your deploy. Nobody diffed anything; the resolver just grabbed newest. - The reflex: pin
langchain>=1.3,<2andlanggraph>=1.2,<2, upgrade deliberately, and re-run your own smoke tests after every bump. The no-breaking-changes-until-2.0 promise is real - and yanks are what "real but human" looks like.
Two teams, one bad window. When the regression shipped, the team with pinned ranges and a smoke-test script never noticed - their resolver skipped the yanked version on the next deliberate bump. The team with unpinned requirements rebuilt an image that Friday, picked up the broken release, and spent a weekend bisecting "middleware suddenly raises TypeError". Same ecosystem event, entirely different weekends.
Self-studyReading the changelog like an operator2 min read▶
A 10-minute monthly ritual that replaces upgrade anxiety:
- Skim the changelog at docs.langchain.com before any bump - middleware and agent-surface entries matter most to DataDesk; note anything touching classes you attach.
- Check PyPI for yank flags on versions between your pin and the target - yanked releases are labeled on the release history page.
- Keep a smoke-test file: one script that runs DataDesk's core flows (tool chain, structured output, each middleware) end to end. Green after upgrade = ship the new pin. This file grows into the b10 eval suite - same idea, more rigor.
- Upgrade on a Tuesday morning, not a Friday evening. Not a technology rule - a being-on-call rule.
DataDesk grows armor ★ 12 min · everyone builds
Two rings onto v1: a PII scrub for the stakeholder pastes DataDesk lives on, and summarization so long analysis threads stop dying at the context ceiling. Then the important part - we PROVE the scrub happened, on screen.
Import the layers and add a middleware=[...] list to your v1 create_agent call - PII outermost, exactly like the onion diagram. The rest of v1 is untouched.
Seed the test: invoke with a message containing a fake address - "Request from jane.doe@example.com: how many rows in the orders data?". Fake, always - we are demonstrating a scrubber, not feeding it.
Prove the scrub: print the message stack from the result and find the human message the model actually received. The address is redacted; the question survived. This screenshot is your future audit answer.
Now the summarizer: loop 15+ questions through one thread and watch the older turns collapse into a summary message instead of the context window overflowing. Ask a question that depends on an early turn - note what survives summarization and what blurs.
Review the diff from v1: the entire production upgrade is one list. Read it aloud in the words you would use in design review - that list IS the review.
"email", another for names or custom patterns, and stack them in the list. Start with email tonight; extend as homework.
The graceful degrade ★ 10 min · build your own
The two-engine setup you have maintained since b1 pays its dividend: model fallback from Claude to the local Ollama engine means a provider outage degrades DataDesk's answers instead of killing them. We simulate the outage live.
Add ModelFallbackMiddleware("ollama:llama3.1") to the middleware list. One line: if the primary model call fails, retry the call on the local engine.
Baseline run with the key present - confirm a normal Claude-quality answer to the v1 two-tool question.
Simulate the outage: in a fresh shell, unset ANTHROPIC_API_KEY and run the same script. The primary call fails, the fallback ring catches it, and DataDesk answers from llama3.1 - slower, plainer, alive.
Compare the two answers honestly: tool choice usually survives, nuance and phrasing degrade. Write one sentence on whether that trade is acceptable for YOUR users during an outage - that sentence is an SLA decision.
Land the reframe: the fallback is also the privacy mode. The same one line means "confidential-data path runs local" and "outage path runs local" - one architecture, two policies. Restore your key before b5.
The outage that nobody escalated. During a provider incident, a data team's assistant quietly fell back to its local model for two hours. Answers got terser; a banner said "running in degraded local mode". Zero pages, zero lost afternoon - and the incident review was one sentence. The neighboring team without a fallback ring filed the outage as a Sev-2.
Try it yourself - this week ◐ 30-45 min total
- Finish both demos if you did not complete them live - the redaction printout and the unset-key drill especially. Both are muscle memory you want before b7.
- Stack a second PIIMiddleware for a pattern your team actually handles (names, ticket IDs, account numbers) and prove it with a seeded fake input.
- Add tool retry to the list and point
csv_statsat a missing file - watch retries happen, then decide what the right N is for a warehouse call vs a local CSV. - Write your smoke-test file: one script, every DataDesk flow, run it green. Then check PyPI's release history for langchain and find the yanked releases yourself - know what the flag looks like.
- Optional reading: the middleware page at docs.langchain.com, including the custom-hooks section - you now know where each built-in sits in the onion.
Official sources covered
This track teaches from the official docs and the free LangChain Academy curricula (login required for lesson content; certificates stay with the Academy - all free). This page covers:
Three questions before you go 🎯 ◐ 90 seconds
1 · Why does middleware exist as a first-class concept in LangChain 1.x?
Duplication, tangling and gaps were the 0.x pattern. One middleware list per agent is the fix - and the list itself becomes your audit answer.
2 · langchain 1.3.5 and 1.2.5 were yanked from PyPI. What does that mean, and what is the lesson?
Yanking is the ecosystem's recall notice. Pin langchain>=1.3,<2, read the changelog, and never let Friday's pip install -U pick your production version.
3 · What does ModelFallbackMiddleware("ollama:llama3.1") buy DataDesk?
One line, two policies: outage resilience and a local path for confidential data. This is the b1 two-engine discipline paying its dividend.