LangChain v1's create_agent gives you a working tool-calling agent in a few lines, and middleware is how you customize it without touching the core loop. This guide builds a personal-finance agent that categorizes transactions, summarizes long histories automatically, and pauses for human approval before flagging any transaction over $1,000 as reviewed. Stack: Python, langchain, langgraph (used internally). Budget 40-55 minutes.
| Fact | Value |
|---|---|
| Tool | LangChain |
| Difficulty | Beginner |
| Time required | 40-55 min |
| Compatibility | Python 3.10+, langchain 1.x, langgraph (installed as a dependency), Anthropic API key |
| Last updated | November 2026 |
What you'll build
You'll build a personal-finance agent that reads a list of transactions, categorizes each one, and flags anything unusual. Categorizing is routine and runs on its own; flagging a transaction over $1,000 as reviewed always pauses for a human to approve first, because that flag feeds into a monthly report someone's name goes on.
create_agent is LangChain v1's high-level entry point: hand it a model string, a list of tools, and it returns a runnable agent built on LangGraph underneath, with no graph code of your own. Middleware is where you customize behavior around that loop without forking it. SummarizationMiddleware condenses older messages once the conversation gets long, so a month of transactions doesn't quietly exceed the model's context. HumanInTheLoopMiddleware intercepts specific tool calls based on an interrupt_on mapping and pauses the whole agent (via the checkpointer LangGraph uses underneath) until you resume it with a decision.
Choose create_agent when you want a standard tool-calling agent fast and are fine working at the middleware layer instead of a custom graph. If you need an explicit multi-step pipeline with branching logic beyond what middleware covers, the LangGraph guide builds the same kind of approval pause directly into a graph you design yourself.
Prerequisites
- Python 3.10+ and a terminal
- An Anthropic API key (ANTHROPIC_API_KEY)
- pip install langchain langgraph
- Basic comfort with Python functions as tools
Step-by-step guide
Install LangChain and run a bare create_agent
Install langchain (langgraph comes along as a dependency since create_agent is built on it). Before adding tools or middleware, confirm the basics work: a model string and an empty tool list should still let the agent respond to a plain question.
1pip install langchain langgraph2export ANTHROPIC_API_KEY="sk-ant-..."3python -c "from langchain.agents import create_agent; a=create_agent(model='anthropic:claude-sonnet-5-5', tools=[]); print(a.invoke({'messages':[{'role':'user','content':'say hi'}]})['messages'][-1].content)"Expected result: A short greeting printed with no errors, confirming create_agent and your API key both work.
Define the finance tools
categorize_transaction is routine: it labels a transaction by matching its description against a small set of categories. flag_for_review is the sensitive one: it's what gets gated by middleware in the next step, so its job is only to record that a transaction was marked reviewed, nothing destructive on its own.
1CATEGORIES = {"grocery": "Groceries", "uber": "Transport", "rent": "Housing"}23def categorize_transaction(description: str, amount: float) -> str:4 """Categorize a transaction by its description."""5 for key, label in CATEGORIES.items():6 if key in description.lower():7 return f"{label}: ${amount:.2f}"8 return f"Uncategorized: ${amount:.2f}"910def flag_for_review(description: str, amount: float, reason: str) -> str:11 """Flag a transaction as reviewed for the monthly report. Requires human approval for amounts over $1,000."""12 return f"Flagged '{description}' (${amount:.2f}) for review: {reason}"Expected result: Two plain Python functions with docstrings create_agent will read as each tool's description.
Build the agent with SummarizationMiddleware
Pass both functions as tools=[...] directly; create_agent wraps plain functions into tools automatically from their type hints and docstring. SummarizationMiddleware watches the running conversation and condenses older turns once they pass a token threshold, so a long list of transactions processed in one session doesn't silently exceed the model's context window.
1from langchain.agents import create_agent2from langchain.agents.middleware import SummarizationMiddleware34agent = create_agent(5 model="anthropic:claude-sonnet-5-5",6 tools=[categorize_transaction, flag_for_review],7 middleware=[8 SummarizationMiddleware(model="anthropic:claude-haiku-5-5", max_tokens_before_summary=4000),9 ],10)Expected result: An agent object that categorizes transactions normally; nothing visibly changes yet since no conversation has gotten long enough to summarize.
Add HumanInTheLoopMiddleware and a checkpointer
interrupt_on maps each tool name to its approval rule: flag_for_review always pauses (True means every decision type is allowed), categorize_transaction never does (False). Human-in-the-loop middleware requires a checkpointer, since pausing and resuming relies on the same persistence layer LangGraph uses for interrupt().
1from langchain.agents.middleware import HumanInTheLoopMiddleware2from langgraph.checkpoint.memory import InMemorySaver34agent = create_agent(5 model="anthropic:claude-sonnet-5-5",6 tools=[categorize_transaction, flag_for_review],7 middleware=[8 SummarizationMiddleware(model="anthropic:claude-haiku-5-5", max_tokens_before_summary=4000),9 HumanInTheLoopMiddleware(10 interrupt_on={11 "flag_for_review": True,12 "categorize_transaction": False,13 },14 description_prefix="Review needed before flagging",15 ),16 ],17 checkpointer=InMemorySaver(),18)Expected result: Calling the agent with a transaction over $1,000 now pauses instead of completing; a small one still runs straight through.
Invoke the agent and read the interrupt
Every call needs a thread_id in config so the paused run can be found again. Call agent.invoke with version='v2' to get a GraphOutput with an .interrupts attribute; when it's non-empty, each interrupt's value includes the pending action_requests (the tool name and arguments Claude wants to call).
1config = {"configurable": {"thread_id": "march-close-1"}}23result = agent.invoke(4 {"messages": [{"role": "user", "content": "Categorize this $1,450 wire transfer labeled 'consulting fee' and flag it if it looks unusual."}]},5 config=config, version="v2",6)78if result.interrupts:9 for action in result.interrupts[0].value["action_requests"]:10 print(f"Pending: {action['name']}({action['args']})")Expected result: The agent categorizes the transfer immediately, then pauses with one pending action_request for flag_for_review.
Resume with a decision and harden for production
Resume the same thread_id with Command(resume={'decisions': [{'type': 'approve'}]}) (or 'reject') to continue. In production, swap InMemorySaver for a persistent checkpointer (Postgres, SQLite, Redis) since a human review can take hours, and keep the thread_id stable per transaction batch so a restart doesn't lose the pending approval.
1from langgraph.types import Command23result = agent.invoke(4 Command(resume={"decisions": [{"type": "approve"}]}),5 config=config, version="v2",6)7print(result.value["messages"][-1].content)Expected result: After approval, the agent completes the flag_for_review call and returns a final message confirming the transaction was flagged.
Complete working example
1"""Personal-finance agent: categorizes transactions, pauses for approval before flagging large ones.2Run: python finance_review_agent.py (pip install langchain langgraph; set ANTHROPIC_API_KEY)3"""4from langchain.agents import create_agent5from langchain.agents.middleware import HumanInTheLoopMiddleware, SummarizationMiddleware6from langgraph.checkpoint.memory import InMemorySaver7from langgraph.types import Command89CATEGORIES = {"grocery": "Groceries", "uber": "Transport", "rent": "Housing"}101112def categorize_transaction(description: str, amount: float) -> str:13 """Categorize a transaction by its description."""14 for key, label in CATEGORIES.items():15 if key in description.lower():16 return f"{label}: ${amount:.2f}"17 return f"Uncategorized: ${amount:.2f}"181920def flag_for_review(description: str, amount: float, reason: str) -> str:21 """Flag a transaction as reviewed for the monthly report. Requires human approval for amounts over $1,000."""22 return f"Flagged '{description}' (${amount:.2f}) for review: {reason}"232425agent = create_agent(26 model="anthropic:claude-sonnet-5-5",27 tools=[categorize_transaction, flag_for_review],28 middleware=[29 SummarizationMiddleware(model="anthropic:claude-haiku-5-5", max_tokens_before_summary=4000),30 HumanInTheLoopMiddleware(31 interrupt_on={32 "flag_for_review": True,33 "categorize_transaction": False,34 },35 description_prefix="Review needed before flagging",36 ),37 ],38 checkpointer=InMemorySaver(),39)404142def review_transaction(description: str, thread_id: str) -> str:43 config = {"configurable": {"thread_id": thread_id}}44 result = agent.invoke(45 {"messages": [{"role": "user", "content": description}]},46 config=config, version="v2",47 )4849 while result.interrupts:50 for action in result.interrupts[0].value["action_requests"]:51 print(f"Pending approval: {action['name']}({action['args']})")52 decision = input("Approve this flag? (approve/reject): ").strip().lower()53 result = agent.invoke(54 Command(resume={"decisions": [{"type": decision}]}),55 config=config, version="v2",56 )5758 return result.value["messages"][-1].content596061if __name__ == "__main__":62 print(review_transaction(63 "Categorize this $1,450 wire transfer labeled 'consulting fee' and flag it if it looks unusual.",64 thread_id="march-close-1",65 ))66Common mistakes
Why it's a problem: Adding HumanInTheLoopMiddleware without a checkpointer
How to avoid: Human-in-the-loop middleware relies on LangGraph's persistence layer to pause and resume. Always pass checkpointer=InMemorySaver() (or a persistent one) alongside it.
Why it's a problem: Reusing the same thread_id for unrelated conversations
How to avoid: Each thread_id is one resumable run. Give each independent transaction batch or user session its own, or a resume will land in the wrong context.
Why it's a problem: Setting interrupt_on values to True for every tool 'to be safe'
How to avoid: That pauses routine tool calls like categorize_transaction too, turning every run into a manual-approval slog. Reserve True for tools whose output actually matters if it's wrong.
Why it's a problem: Forgetting version='v2' on invoke when using interrupts
How to avoid: Without it you get the older result shape that doesn't expose .interrupts the way this guide's resume loop expects.
Why it's a problem: Running SummarizationMiddleware's summarizer model at the same size as the main agent model
How to avoid: Summarization doesn't need your most capable model. A cheaper model (Haiku-class) for the summarizer keeps costs down with no real quality loss for that specific job.
Best practices
- Start with create_agent and middleware before reaching for a custom LangGraph graph; most agents don't need the extra control.
- Set interrupt_on precisely per tool, not globally, so only genuinely consequential calls pause.
- Use a cheaper model for SummarizationMiddleware's own summarization calls than for the main agent loop.
- Keep thread_id stable and meaningful (per batch, per user, per session) so resumes always land in the right place.
- Use a persistent checkpointer in production; InMemorySaver loses all pending interrupts on restart.
- Read tool docstrings as what the model actually sees. Write them as clearly as you'd write a prompt, not as internal code comments.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I have a LangChain create_agent with categorize_transaction and flag_for_review tools, SummarizationMiddleware, and HumanInTheLoopMiddleware gating flag_for_review (code below). Add a third tool, export_monthly_report, that's also gated by HumanInTheLoopMiddleware, and only allow it to run once every flagged transaction for the month has been approved. [paste complete_code]
In Claude Code, open finance_review_agent.py and ask: 'Add a PIIRedactionMiddleware-style custom middleware that strips account numbers from tool arguments before they're logged, following the same middleware list pattern as the existing ones.'
Frequently asked questions
Do I need to know LangGraph to use create_agent?
No. create_agent is the high-level entry point specifically so you don't have to build a graph yourself. You only need LangGraph concepts like checkpointers and Command(resume=...) once you add human-in-the-loop middleware, as this guide does.
What's the difference between HumanInTheLoopMiddleware's approve, edit, and reject decisions?
approve runs the tool call as proposed, reject skips it and tells the model it was declined, and edit lets you change the tool's arguments before it runs (useful for correcting a wrong amount or category rather than rejecting outright).
How does SummarizationMiddleware decide what to summarize?
It watches the running token count and, once older messages push past max_tokens_before_summary, condenses the oldest portion of the conversation into a summary message, keeping recent turns intact. You set the threshold; there's no fixed default that fits every use case.
Can I stack more than two middleware together?
Yes, middleware is a list and order matters since each one wraps the next. A common stack adds a PII-redaction or logging middleware alongside summarization and human-in-the-loop, each handling one concern.
Does this work with OpenAI or Gemini models instead of Claude?
Yes. create_agent accepts any supported provider:model string, like openai:gpt-6.1-sol or google:gemini-3.8-flash, and the middleware stack works the same regardless of which model string you use.
Can someone build this kind of finance or ops agent for us?
Yes. RapidDev builds LangChain and LangGraph agents with the approval and audit layers finance and ops teams actually need. A free 30-minute consultation is the fastest way to scope yours.
Talk to an Expert
Our team has built 1,000+ apps. Get personalized help with your project.
Book a free consultation