Integrating LangGraph for Graph-Based AI Agents

When developing complex AI agents, we often encounter situations where linear LangChain Expression Language (LCEL) chains stop being effective. When cyclical processing, conditional transitions based on intermediate step results, or pauses for human confirmation are required, LCEL falls short. That'

AI Development Areas

Frequently Asked Questions

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1284
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1240
  • image_logo-advance_0.webp
    B2B Advance company logo design
    696
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    982
  • image_logo-aider_0.webp
    AIDER company logo development
    917
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    1031

When developing complex AI agents, we often encounter situations where linear LangChain Expression Language (LCEL) chains stop being effective. When cyclical processing, conditional transitions based on intermediate step results, or pauses for human confirmation are required, LCEL falls short. That's exactly where LangGraph comes in—a library that extends LangChain and allows building agents as directed graphs with explicit state. Our experience deploying LangGraph in production includes projects for fintech, logistics, and legal services. We guarantee a fault-tolerant architecture and full documentation.

LangGraph implements the StateGraph concept, where each node is a function and edges are transitions. The agent's state is described with a TypedDict and can automatically merge messages. This makes it easy to implement loops and multi-agent systems.

Why agents need a graph, not a chain?

Linear LCEL chains are fine for simple pipelines: take input, apply a sequence of steps, get output. But real agent scenarios often require going back to a previous step, running parallel checks, or pausing execution for manual control. LangGraph's graph model solves these problems naturally: cycles are edges leading back; parallelism is multiple nodes executing simultaneously; human-in-the-loop is built-in interruptions.

Basic graph structure

from langgraph.graph import StateGraph, END from langgraph.checkpoint.memory import MemorySaver from langgraph.prebuilt import ToolNode from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage, AIMessage from typing import TypedDict, Annotated import operator class AgentState(TypedDict): messages: Annotated[list, operator.add] # Automatically concatenated user_id: str iteration_count: int llm = ChatOpenAI(model="gpt-4o") def agent_node(state: AgentState) -> AgentState: response = llm.bind_tools(tools).invoke(state["messages"]) return {"messages": [response], "iteration_count": state["iteration_count"] + 1} def should_continue(state: AgentState) -> str: last_msg = state["messages"][-1] if last_msg.tool_calls: return "tools" return END # Build the graph graph = StateGraph(AgentState) graph.add_node("agent", agent_node) graph.add_node("tools", ToolNode(tools)) graph.set_entry_point("agent") graph.add_conditional_edges("agent", should_continue, {"tools": "tools", END: END}) graph.add_edge("tools", "agent") # Loop: after tools, back to agent app = graph.compile(checkpointer=MemorySaver()) 

Persistent state and interrupts

LangGraph supports checkpoint saving between runs and pauses for human approval:

from langgraph.checkpoint.postgres import PostgresSaver from psycopg import Connection # Persistence in PostgreSQL conn = Connection.connect("postgresql://user:pass@localhost/langgraph_db") checkpointer = PostgresSaver(conn) # Interrupt: graph stops before the specified node app = graph.compile( checkpointer=checkpointer, interrupt_before=["execute_payment"], # Requires human confirmation ) config = {"configurable": {"thread_id": "order_12345"}} # Run until the break point result = app.invoke({"messages": [HumanMessage("Pay the invoice for 50000 RUB")]}, config) # Graph stopped before execute_payment # After human review, continue app.invoke(None, config) # None = resume from current state 

Multi-agent: Supervisor pattern

from langgraph.graph import StateGraph, END from typing import Literal class SupervisorState(TypedDict): messages: Annotated[list, operator.add] next_agent: str AGENTS = ["researcher", "analyst", "writer"] supervisor_prompt = f"""You are a supervisor of a multi-agent system. Based on the request and current progress, choose the next agent: {AGENTS} Or return FINISH if the task is complete. """ def supervisor_node(state: SupervisorState): response = llm.with_structured_output( {"next": {"type": "string", "enum": AGENTS + ["FINISH"]}} ).invoke([{"role": "system", "content": supervisor_prompt}] + state["messages"]) return {"next_agent": response["next"]} def route_to_agent(state: SupervisorState) -> str: if state["next_agent"] == "FINISH": return END return state["next_agent"] # Create agents def make_agent_node(name: str, system_prompt: str): agent_llm = ChatOpenAI(model="gpt-4o").bind_tools(get_tools_for(name)) def node(state): result = agent_llm.invoke( [{"role": "system", "content": system_prompt}] + state["messages"] ) return {"messages": [result]} return node graph = StateGraph(SupervisorState) graph.add_node("supervisor", supervisor_node) graph.add_node("researcher", make_agent_node("researcher", "Research the topic and find facts")) graph.add_node("analyst", make_agent_node("analyst", "Analyze data and draw conclusions")) graph.add_node("writer", make_agent_node("writer", "Formulate the final answer")) graph.set_entry_point("supervisor") graph.add_conditional_edges("supervisor", route_to_agent) for agent in AGENTS: graph.add_edge(agent, "supervisor") multi_agent = graph.compile() 

Streaming and real-time output

# Streaming events from the graph async for event in app.astream_events( {"messages": [HumanMessage("Analyze Q1 sales")]}, config={"configurable": {"thread_id": "analysis_001"}}, version="v2", ): kind = event["event"] if kind == "on_chat_model_stream": print(event["data"]["chunk"].content, end="", flush=True) elif kind == "on_tool_start": print(f"\n[Tool call: {event['name']}]") elif kind == "on_tool_end": print(f"[Tool result received]") 

How nested graphs simplify modularity?

LangGraph supports SubGraphs—nested graphs. You can define a graph for document processing and then include it as a regular node in the parent graph. This allows decomposing complex systems into reusable components.

# Subgraph for document processing doc_graph = StateGraph(DocumentState) doc_graph.add_node("extract", extract_text) doc_graph.add_node("classify", classify_document) doc_graph.add_node("validate", validate_structure) # ... build the subgraph doc_subgraph = doc_graph.compile() # Include subgraph in parent main_graph = StateGraph(MainState) main_graph.add_node("process_document", doc_subgraph) # Subgraph as node main_graph.add_node("send_result", send_to_crm) main_graph.add_edge("process_document", "send_result") 

Practical case: contract review system (from our practice)

Task: one of our clients—a legal department of a large company—received 30–50 contracts daily. Each contract required 1–2 hours of lawyer time. We built a graph agent on LangGraph that automated the review.

Graph:

  1. extract_node — parse PDF, extract structure
  2. classify_node — contract type (supply, services, lease, NDA)
  3. risk_check_node — parallel checks: financial terms, duration, liability
  4. legal_rules_node — check against corporate list of prohibited clauses
  5. human_review — interrupt for contracts with risk_score > 7
  6. finalize_node — generate conclusion and recommendations
app = graph.compile( checkpointer=PostgresSaver(conn), interrupt_before=["human_review"], # Pause only for risky ones ) 

Routing: low risk → automatic approval; high risk → pause for lawyer with agent's draft conclusion. Results:

  • Standard contract review time: 90 min → 8 min
  • Automatic approval without lawyer: 61% of contracts
  • Missed non-standard clauses: 0 (vs ~3% manual due to fatigue)
  • Legal department workload: -58%

Comparison of LangGraph and LCEL

Criteria LCEL LangGraph
Structure Linear chain Arbitrary graph
Loops No Yes
State Passed via pipe TypedDict with merge strategy
Checkpoint No PostgreSQL, Redis, SQLite
Human-in-the-loop No interrupt_before/after
Use case Simple pipelines Agents, multi-agent systems

LangGraph components

Component Purpose Example
StateGraph Defines state and nodes StateGraph(AgentState)
Node Processing function agent_node
Edge Connection between nodes graph.add_edge("tools", "agent")
Conditional Edge Conditional transition should_continue
Checkpointer State persistence MemorySaver, PostgresSaver
Interrupt Pause for HITL interrupt_before=["node"]

What our LangGraph integration includes

  • Architecture session: analyze the task, select graph topology, define human-in-the-loop points.
  • Code development: implement nodes, edges, state, integrate with your LLM and tools.
  • Persistence setup: PostgreSQL, Redis, or SQLite for checkpoints.
  • Environment integration: deploy via Docker/Kubernetes, connect monitoring (LangSmith).
  • Testing: unit tests, load testing with latency recording (p99).
  • Documentation: full graph description, API, operation guide.
  • Team training: workshop on LangGraph for your developers.

Estimated timelines

  • Basic ReAct agent on LangGraph: 3 to 5 days.
  • Multi-agent system with supervisor: 2 to 3 weeks.
  • Human-in-the-loop workflow with persistence: 1 to 2 weeks.
  • Production integration with PostgreSQL checkpoint: +3–5 days.

Pricing is determined individually—we assess the project free of charge within one business day. Contact us for a consultation: our certified engineers will help choose the architecture for your task. Request a preliminary assessment, and we will prepare a commercial proposal with a quality guarantee and transparent timelines.