# How to Build an AI Agent with LangChain

- 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

## TL;DR

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.

## 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.

## Before you start

- 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

### 1. 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.

```
pip install langchain langgraph
export ANTHROPIC_API_KEY="sk-ant-..."
python -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.

### 2. 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.

```
CATEGORIES = {"grocery": "Groceries", "uber": "Transport", "rent": "Housing"}

def categorize_transaction(description: str, amount: float) -> str:
    """Categorize a transaction by its description."""
    for key, label in CATEGORIES.items():
        if key in description.lower():
            return f"{label}: ${amount:.2f}"
    return f"Uncategorized: ${amount:.2f}"

def flag_for_review(description: str, amount: float, reason: str) -> str:
    """Flag a transaction as reviewed for the monthly report. Requires human approval for amounts over $1,000."""
    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.

### 3. 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.

```
from langchain.agents import create_agent
from langchain.agents.middleware import SummarizationMiddleware

agent = create_agent(
    model="anthropic:claude-sonnet-5-5",
    tools=[categorize_transaction, flag_for_review],
    middleware=[
        SummarizationMiddleware(model="anthropic:claude-haiku-5-5", max_tokens_before_summary=4000),
    ],
)
```

**Expected result:** An agent object that categorizes transactions normally; nothing visibly changes yet since no conversation has gotten long enough to summarize.

### 4. 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().

```
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langgraph.checkpoint.memory import InMemorySaver

agent = create_agent(
    model="anthropic:claude-sonnet-5-5",
    tools=[categorize_transaction, flag_for_review],
    middleware=[
        SummarizationMiddleware(model="anthropic:claude-haiku-5-5", max_tokens_before_summary=4000),
        HumanInTheLoopMiddleware(
            interrupt_on={
                "flag_for_review": True,
                "categorize_transaction": False,
            },
            description_prefix="Review needed before flagging",
        ),
    ],
    checkpointer=InMemorySaver(),
)
```

**Expected result:** Calling the agent with a transaction over $1,000 now pauses instead of completing; a small one still runs straight through.

### 5. 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).

```
config = {"configurable": {"thread_id": "march-close-1"}}

result = agent.invoke(
    {"messages": [{"role": "user", "content": "Categorize this $1,450 wire transfer labeled 'consulting fee' and flag it if it looks unusual."}]},
    config=config, version="v2",
)

if result.interrupts:
    for action in result.interrupts[0].value["action_requests"]:
        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.

### 6. 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.

```
from langgraph.types import Command

result = agent.invoke(
    Command(resume={"decisions": [{"type": "approve"}]}),
    config=config, version="v2",
)
print(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 code example

File: `finance_review_agent.py`

```python
"""Personal-finance agent: categorizes transactions, pauses for approval before flagging large ones.
Run: python finance_review_agent.py (pip install langchain langgraph; set ANTHROPIC_API_KEY)
"""
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware, SummarizationMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command

CATEGORIES = {"grocery": "Groceries", "uber": "Transport", "rent": "Housing"}


def categorize_transaction(description: str, amount: float) -> str:
    """Categorize a transaction by its description."""
    for key, label in CATEGORIES.items():
        if key in description.lower():
            return f"{label}: ${amount:.2f}"
    return f"Uncategorized: ${amount:.2f}"


def flag_for_review(description: str, amount: float, reason: str) -> str:
    """Flag a transaction as reviewed for the monthly report. Requires human approval for amounts over $1,000."""
    return f"Flagged '{description}' (${amount:.2f}) for review: {reason}"


agent = create_agent(
    model="anthropic:claude-sonnet-5-5",
    tools=[categorize_transaction, flag_for_review],
    middleware=[
        SummarizationMiddleware(model="anthropic:claude-haiku-5-5", max_tokens_before_summary=4000),
        HumanInTheLoopMiddleware(
            interrupt_on={
                "flag_for_review": True,
                "categorize_transaction": False,
            },
            description_prefix="Review needed before flagging",
        ),
    ],
    checkpointer=InMemorySaver(),
)


def review_transaction(description: str, thread_id: str) -> str:
    config = {"configurable": {"thread_id": thread_id}}
    result = agent.invoke(
        {"messages": [{"role": "user", "content": description}]},
        config=config, version="v2",
    )

    while result.interrupts:
        for action in result.interrupts[0].value["action_requests"]:
            print(f"Pending approval: {action['name']}({action['args']})")
        decision = input("Approve this flag? (approve/reject): ").strip().lower()
        result = agent.invoke(
            Command(resume={"decisions": [{"type": decision}]}),
            config=config, version="v2",
        )

    return result.value["messages"][-1].content


if __name__ == "__main__":
    print(review_transaction(
        "Categorize this $1,450 wire transfer labeled 'consulting fee' and flag it if it looks unusual.",
        thread_id="march-close-1",
    ))
```

## Common mistakes

- **Adding HumanInTheLoopMiddleware without a checkpointer** — undefined Fix: 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.
- **Reusing the same thread_id for unrelated conversations** — undefined Fix: 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.
- **Setting interrupt_on values to True for every tool 'to be safe'** — undefined Fix: 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.
- **Forgetting version='v2' on invoke when using interrupts** — undefined Fix: Without it you get the older result shape that doesn't expose .interrupts the way this guide's resume loop expects.
- **Running SummarizationMiddleware's summarizer model at the same size as the main agent model** — undefined Fix: 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.

## Use cases

### undefined



### undefined



### undefined



## 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.

---

Source: https://www.rapidevelopers.com/build-ai-agent/how-to-build-an-ai-agent-with-langchain
© RapidDev — https://www.rapidevelopers.com/build-ai-agent/how-to-build-an-ai-agent-with-langchain
