# LangGraph - Detailed Guide > This file contains the detailed procedure and reference material extracted from `SKILL.md` for focused loading. The root skill defines activation, examples, safety constraints, and limitations. ## Capabilities - Graph construction (StateGraph) - State management and reducers - Node and edge definitions - Conditional routing - Checkpointers and persistence - Human-in-the-loop patterns - Tool integration - Streaming and async execution ## Scope - 0: Examples here are Python; verify language/package version compatibility - 1: Learning curve for graph concepts - 2: State management complexity - 3: Debugging can be challenging ## Ecosystem ### Primary - LangGraph - LangChain - LangSmith (observability) ### Common_integrations - OpenAI / Anthropic / Google - Tavily (search) - SQLite / PostgreSQL (persistence) - Redis (state store) ### Platforms - Python applications - FastAPI / Flask backends - Cloud deployments Examples are fragments sharing imports and graph definitions; the search tool and `safe_math_evaluator` must be supplied and bounded by the application. Never use unrestricted eval. Checkpointer packages are separate installs; isolate authenticated tenant/thread access and redact retained state. A graph state flag is not independent user authorization. ## Patterns ### Basic Agent Graph Simple ReAct-style agent with tools **When to use**: Single agent with tool calling from typing import Annotated, TypedDict from langgraph.graph import StateGraph, START, END from langgraph.graph.message import add_messages from langgraph.prebuilt import ToolNode from langchain_openai import ChatOpenAI from langchain_core.tools import tool # 1. Define State class AgentState(TypedDict): messages: Annotated[list, add_messages] # add_messages appends new IDs and replaces messages sharing an existing ID # 2. Define Tools @tool def search(query: str) -> str: """Search the web for information.""" # Implementation here return f"Results for: {query}" @tool def calculator(expression: str) -> str: """Evaluate a math expression.""" return str(safe_math_evaluator(expression)) tools = [search, calculator] # 3. Create LLM with tools llm = ChatOpenAI(model="gpt-4o").bind_tools(tools) # 4. Define Nodes def agent(state: AgentState) -> dict: """The agent node - calls LLM.""" response = llm.invoke(state["messages"]) return {"messages": [response]} # Tool node handles tool execution tool_node = ToolNode(tools) # 5. Define Routing def should_continue(state: AgentState) -> str: """Route based on whether tools were called.""" last_message = state["messages"][-1] if last_message.tool_calls: return "tools" return END # 6. Build Graph graph = StateGraph(AgentState) # Add nodes graph.add_node("agent", agent) graph.add_node("tools", tool_node) # Add edges graph.add_edge(START, "agent") graph.add_conditional_edges("agent", should_continue, ["tools", END]) graph.add_edge("tools", "agent") # Loop back # Compile app = graph.compile() # 7. Run result = app.invoke({ "messages": [("user", "What is 25 * 4?")] }, config={"recursion_limit": 20}) ### State with Reducers Complex state management with custom reducers **When to use**: Multiple agents updating shared state from typing import Annotated, TypedDict from operator import add from langgraph.graph import StateGraph # Custom reducer for merging dictionaries def merge_dicts(left: dict, right: dict) -> dict: return {**left, **right} # State with multiple reducers class ResearchState(TypedDict): # New IDs append; matching IDs replace messages: Annotated[list, add_messages] # Research findings merge findings: Annotated[dict, merge_dicts] # Sources accumulate sources: Annotated[list[str], add] # Current step (overwrites - no reducer) current_step: str # Error count (custom reducer) errors: Annotated[int, lambda a, b: a + b] # Nodes return partial state updates def researcher(state: ResearchState) -> dict: # Only return fields being updated return { "findings": {"topic_a": "New finding"}, "sources": ["source1.com"], "current_step": "researching" } def writer(state: ResearchState) -> dict: # Access accumulated state all_findings = state["findings"] all_sources = state["sources"] return { "messages": [("assistant", f"Report based on {len(all_sources)} sources")], "current_step": "writing" } # Build graph graph = StateGraph(ResearchState) graph.add_node("researcher", researcher) graph.add_node("writer", writer) # ... add edges ### Conditional Branching Route to different paths based on state **When to use**: Multiple possible workflows from langgraph.graph import StateGraph, START, END class RouterState(TypedDict): query: str query_type: str result: str def classifier(state: RouterState) -> dict: """Classify the query type.""" query = state["query"].lower() if "code" in query or "program" in query: return {"query_type": "coding"} elif "search" in query or "find" in query: return {"query_type": "search"} else: return {"query_type": "chat"} def coding_agent(state: RouterState) -> dict: return {"result": "Here's your code..."} def search_agent(state: RouterState) -> dict: return {"result": "Search results..."} def chat_agent(state: RouterState) -> dict: return {"result": "Let me help..."} # Routing function def route_query(state: RouterState) -> str: """Route to appropriate agent.""" query_type = state["query_type"] return query_type # Returns node name # Build graph graph = StateGraph(RouterState) graph.add_node("classifier", classifier) graph.add_node("coding", coding_agent) graph.add_node("search", search_agent) graph.add_node("chat", chat_agent) graph.add_edge(START, "classifier") # Conditional edges from classifier graph.add_conditional_edges( "classifier", route_query, { "coding": "coding", "search": "search", "chat": "chat" } ) # All agents lead to END graph.add_edge("coding", END) graph.add_edge("search", END) graph.add_edge("chat", END) app = graph.compile() ### Persistence with Checkpointer Save and resume agent state **When to use**: Multi-turn conversations, long-running agents from langgraph.graph import StateGraph from langgraph.checkpoint.sqlite import SqliteSaver from langgraph.checkpoint.postgres import PostgresSaver # SQLite for development from contextlib import ExitStack resources = ExitStack() # Keep resources open for every graph invocation; close it on application shutdown. memory = resources.enter_context(SqliteSaver.from_conn_string("agent_state.db")) # PostgreSQL for production # memory = resources.enter_context(PostgresSaver.from_conn_string(DATABASE_URL)) # memory.setup() # initialize schema before use # Compile with checkpointer app = graph.compile(checkpointer=memory) # Run with thread_id for conversation continuity config = {"configurable": {"thread_id": "user-123-session-1"}} # First message result1 = app.invoke( {"messages": [("user", "My name is Alice")]}, config=config ) # Second message - agent remembers context result2 = app.invoke( {"messages": [("user", "What's my name?")]}, config=config ) # Agent knows name is Alice! # Get conversation history state = app.get_state(config) print(state.values["messages"]) # List all checkpoints for checkpoint in app.get_state_history(config): print(checkpoint.config, checkpoint.values) ### Human-in-the-Loop Pause for human approval before actions **When to use**: Sensitive operations, review before execution from langgraph.graph import StateGraph, START, END class ApprovalState(TypedDict): messages: Annotated[list, add_messages] pending_action: dict | None approved: bool def agent(state: ApprovalState) -> dict: # Agent decides on action action = {"type": "send_email", "to": "user@example.com"} return { "pending_action": action, "approved": False, "messages": [("assistant", f"I want to: {action}")] } def execute_action(state: ApprovalState) -> dict: action = state["pending_action"] if state.get("approved") is not True: raise PermissionError("Action has not been approved") # Illustration only: replace with an idempotent authorized adapter. result = f"Simulated: {action['type']}" return { "messages": [("assistant", result)], "pending_action": None } def should_execute(state: ApprovalState) -> str: return "execute" # interrupt_before pauses before the action node # Build graph graph = StateGraph(ApprovalState) graph.add_node("agent", agent) graph.add_node("execute", execute_action) graph.add_edge(START, "agent") graph.add_conditional_edges("agent", should_execute, ["execute", END]) graph.add_edge("execute", END) # Compile with interrupt_before for human review app = graph.compile( checkpointer=memory, interrupt_before=["execute"] # Pause before execution ) # Run until interrupt config = {"configurable": {"thread_id": "approval-flow"}} result = app.invoke({"messages": [("user", "Send report")]}, config) # Agent paused - get pending state state = app.get_state(config) pending = state.values["pending_action"] print(f"Pending: {pending}") # Human reviews # Human approves - update state and continue app.update_state(config, {"approved": True}) result = app.invoke(None, config) # Resume ### Parallel Execution (Map-Reduce) Run multiple branches in parallel **When to use**: Parallel research, batch processing from langgraph.graph import StateGraph, START, END from langgraph.types import Send class ParallelState(TypedDict): topics: list[str] results: Annotated[list[str], add] summary: str def research_topic(state: dict) -> dict: """Research a single topic.""" topic = state["topic"] result = f"Research on {topic}..." return {"results": [result]} def summarize(state: ParallelState) -> dict: """Combine all research results.""" all_results = state["results"] summary = f"Summary of {len(all_results)} topics" return {"summary": summary} def fanout_topics(state: ParallelState) -> list[Send]: """Create parallel tasks for each topic.""" return [ Send("research", {"topic": topic}) for topic in state["topics"] ] # Build graph graph = StateGraph(ParallelState) graph.add_node("research", research_topic) graph.add_node("summarize", summarize) # Fan out to parallel research graph.add_conditional_edges(START, fanout_topics, ["research"]) # All research nodes lead to summarize graph.add_edge("research", "summarize") graph.add_edge("summarize", END) app = graph.compile() result = app.invoke({ "topics": ["AI", "Climate", "Space"], "results": [] }) # Research runs in parallel, then summarizes ## Collaboration ### Delegation Triggers - crewai|role-based|crew -> crewai (Need role-based multi-agent approach) - observability|tracing|langsmith -> langfuse (Need LLM observability) - structured output|json schema -> structured-output (Need structured LLM responses) - evaluate|benchmark|test agent -> agent-evaluation (Need to evaluate agent performance) ### Production Agent Stack Skills: langgraph, langfuse, structured-output Workflow: ``` 1. Design agent graph with LangGraph 2. Add structured outputs for tool responses 3. Integrate Langfuse for observability 4. Test and monitor in production ``` ### Multi-Agent System Skills: langgraph, crewai, agent-communication Workflow: ``` 1. Design agent roles (CrewAI patterns) 2. Implement as LangGraph with subgraphs 3. Add inter-agent communication 4. Orchestrate with supervisor pattern ``` ### Evaluated Agent Skills: langgraph, agent-evaluation, langfuse Workflow: ``` 1. Build agent with LangGraph 2. Create evaluation suite 3. Monitor with Langfuse 4. Iterate based on metrics ``` ## Related Skills Works well with: `crewai`, `autonomous-agents`, `langfuse`, `structured-output`