langchain-langgraph-agents
Build a correct LangGraph 1.0 ReAct agent with `create_react_agent` — typed tools, error propagation, recursion caps, and stop conditions that actually stop. Use when writing your first tool-calling agent, migrating from `AgentExecutor` / `initialize_agent`, or diagnosing an agent that loops on vague prompts. Trigger with "langgraph agent", "create_react_agent", "langgraph tool calling", "AgentExecutor migration", "agent loop cost".
What this skill does
# LangChain LangGraph Agents (Python)
## Overview
Two failure modes hit every team writing their first LangGraph 1.0 ReAct agent:
**Loop-to-cap on vague prompts (P10).** `create_react_agent` defaults to
`recursion_limit=25`. A prompt like "help me with my account" never converges —
the model calls a retrieval tool, gets irrelevant results, calls another tool,
and repeats until `GraphRecursionError: Recursion limit of 25 reached without
hitting a stop condition` fires. Cost dashboards show the damage *after* the
fact: $5-$15 per runaway loop on Sonnet with a 3-tool agent, assuming no tool
is itself expensive.
**Silent tool errors on legacy `AgentExecutor` (P09).** The legacy executor
defaults `handle_parsing_errors=True` and catches tool exceptions, feeding the
error string back as the next observation. When the error serializes to empty
(e.g., a `ValueError("")` or an HTTP 500 with no body), the loop continues with
no signal. The agent says "I couldn't find the answer" — which was actually a
silent crash three tool calls ago.
This skill walks through defining typed tools with `@tool` + Pydantic; building
an agent with `create_react_agent(model, tools, checkpointer=MemorySaver())`;
invoking with `{"messages": [...]}` and a thread-scoped config; setting
`recursion_limit` per expected agent depth (5-10 interactive, 20-30 planner);
adding middleware for a per-session token budget; and raise-by-default error
propagation. Pin: `langgraph >= 1.0, < 2.0`, `langchain-core >= 1.0, < 2.0`.
Pain-catalog anchors: P09, P10, P11, P32, P41, P42, P63.
## Prerequisites
- Python 3.10+
- `langgraph >= 1.0, < 2.0` and `langchain-core >= 1.0, < 2.0`
- At least one provider package: `pip install langchain-anthropic` or `langchain-openai`
- Completed skill: `langchain-langgraph-basics` (L25) — you already know `StateGraph`,
`MessagesState`, and checkpointers
- Provider API key: `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`
## Instructions
### Step 1 — Define tools with typed schemas and short docstrings
```python
from typing import Annotated
from pydantic import BaseModel, Field
from langchain_core.tools import tool
class LookupAccountArgs(BaseModel):
account_id: str = Field(..., description="Account UUID. No email addresses.")
@tool("lookup_account", args_schema=LookupAccountArgs)
def lookup_account(account_id: str) -> dict:
"""Fetch an account record by UUID. Returns status, plan, and owner email."""
if not account_id:
raise ValueError("account_id is required") # raised → agent sees real error
return {"id": account_id, "status": "active", "plan": "pro", "owner": "[email protected]"}
```
Two rules that catch teams off-guard:
1. **Docstring is the tool description the provider sees.** Keep it under
**1024 chars** (P11). Anthropic truncates at ~1024; OpenAI's effective cap is
softer but still bites on tool descriptions over ~2KB. Long docstrings with
examples should move into a system prompt, not the tool description.
2. **Raise real exceptions.** Unlike the legacy `AgentExecutor`, LangGraph's
`create_react_agent` does not silently swallow tool errors — the exception
propagates and surfaces in your observability layer. See Step 6.
For async tools, use `@tool` on an `async def` — LangGraph invokes it via
`await`. For structured return types, annotate the return with a Pydantic model.
See [Tool Definition Patterns](references/tool-definition-patterns.md) for the
`@tool` vs `tool()` decision, async tools, and the `args_schema` vs
auto-inferred trade-off.
### Step 2 — Build the agent with `create_react_agent`
```python
from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import MemorySaver
from langchain_anthropic import ChatAnthropic
model = ChatAnthropic(
model="claude-sonnet-4-6",
temperature=0,
timeout=30,
max_retries=2,
)
agent = create_react_agent(
model=model,
tools=[lookup_account],
checkpointer=MemorySaver(), # required for stateful invocations
)
```
`create_react_agent` is the LangGraph 1.0 replacement for the removed
`initialize_agent` factory (P41). Under the hood it builds a `StateGraph` with
a `model` node and a `ToolNode`, plus a conditional edge that routes to
`END` when the model emits no tool calls. The `checkpointer` persists state
per-thread — required for multi-turn conversations and for resuming after
interruption.
### Step 3 — Invoke with a thread-scoped config
```python
config = {"configurable": {"thread_id": "user-42"}}
result = agent.invoke(
{"messages": [{"role": "user", "content": "look up account uuid-abc"}]},
config=config,
)
print(result["messages"][-1].content)
```
Key contracts:
- **Input** is `{"messages": [...]}` — a list of message dicts or LangChain
`HumanMessage` / `SystemMessage` objects. You append to this list across turns.
- **`thread_id`** scopes the checkpointer. Reusing it resumes the conversation.
- **Output** is the full updated state. `result["messages"]` is the complete
message list; the final assistant message is at index `-1`.
### Step 4 — Set `recursion_limit` to your expected agent depth
`create_react_agent` defaults to `recursion_limit=25`. In LangGraph one
"recursion step" is one node visit, and each tool round-trip is two visits
(model node + tool node), so 25 means ~12 tool calls. For most workloads this
is too generous and hides bugs:
| Agent kind | Suggested `recursion_limit` | Rationale |
|---|---|---|
| Interactive chat with 1-3 tools | 5-10 | One tool call + one final answer is 3 visits. Cap low to expose loops. |
| Task-completion (e.g., booking flow) | 10-15 | 3-5 tool calls + final answer. |
| Planner / research agent | 20-30 | Expect multiple retrieval + synthesis rounds. |
| Multi-agent supervisor | 40+ | Coordinator + worker rounds. Budget tokens separately. |
Apply it on invocation, not at construction time:
```python
result = agent.invoke(
{"messages": [...]},
config={"configurable": {"thread_id": "user-42"}, "recursion_limit": 10},
)
```
When the limit fires, LangGraph raises `GraphRecursionError` — catch it and
surface a user-facing message; do not retry without a cost guard.
### Step 5 — Add a per-session token budget via middleware
`recursion_limit` alone does not bound cost. A single tool call that returns a
large document and triggers a long model response can cost more than 10 cheap
tool calls. Cap tokens explicitly:
```python
from langchain_core.callbacks import BaseCallbackHandler
class TokenBudget(BaseCallbackHandler):
def __init__(self, max_tokens: int = 50_000):
self.used = 0
self.max = max_tokens
def on_llm_end(self, response, **kwargs):
usage = getattr(response, "llm_output", {}).get("token_usage", {}) or {}
self.used += usage.get("total_tokens", 0)
if self.used > self.max:
raise RuntimeError(f"Token budget exceeded: {self.used}/{self.max}")
budget = TokenBudget(max_tokens=50_000)
result = agent.invoke(
{"messages": [...]},
config={
"configurable": {"thread_id": "user-42"},
"recursion_limit": 10,
"callbacks": [budget],
},
)
```
A per-session budget of 50K tokens on Sonnet is roughly $0.25 — a safe cap for
interactive agents. For background planners raise to 200K-500K. See
[Loop Caps and Budgets](references/loop-caps-and-budgets.md) for a
repeated-tool-call early-stop node and a middleware pattern that terminates on
the N-th identical call.
### Step 6 — Propagate tool errors; do not silently swallow
LangGraph's default is to raise. Legacy `AgentExecutor(handle_parsing_errors=True)`
swallowed everything. The new defaults are safer but different:
```python
# Tool raises → the exception propagates out of agent.invoke()
try:
result = agent.invoke({"messages": [{"role": "user", "content": "..."}]}, config=config)
except ValueError as e:
# Your tool's own ValueError — log + user-facing message
...
```
When you *want* tolerant behavior (e.g., the tool iRelated in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.