LangChain & LangGraph Deep Dive: Rewriting Hand-Rolled Agents for Production

This tutorial maps hand-rolled AI agent components to LangChain and LangGraph abstractions, demonstrates rewriting ReAct agents with tool decorators and state graphs, compares framework trade-offs against custom code, and provides criteria for choosing between single-agent workflows, multi-agent patterns, and manual implementations.

Coder Trainee
Coder Trainee
Coder Trainee
LangChain & LangGraph Deep Dive: Rewriting Hand-Rolled Agents for Production

1. Correcting Framework Misconceptions

The article begins by addressing two extreme attitudes toward frameworks: "frameworks solve everything" and "frameworks are useless bloat." The correct mental model: frameworks trade generality for development efficiency, handling 80% of repetitive work but adding abstraction layers that reduce low-level control. Three prerequisites for effective framework use: understand the abstraction model, map each abstraction to your hand-rolled code, and judge when to use or avoid the framework.

2. LangChain Core Concepts: Component Mapping

A detailed mapping of hand-rolled primitives to LangChain abstractions: call_llm() → ChatModel (unifies LLM APIs) messages list → PromptTemplate / ChatPromptTemplate (manages prompt templates) get_weather() functions → Tool (tool definition) TOOL_MAP → Toolkit (tool collections)

Agent loop in run_agent → AgentExecutor (execution engine) VectorStore class → VectorStore (vector DB abstraction) split_text() → TextSplitter (document chunking)

RAG retrieval + concatenation → Retriever + Chain (retrieval and orchestration)

LangChain's design philosophy: every agent development step becomes a swappable component. The cost of this generality is API complexity — supporting dozens of LLMs, vector stores, and tool formats.

3. Rewriting the Part-2 Agent with LangChain

3.1 Tool Definition with @tool Decorator

Hand-rolled version required manual JSON Schema ( TOOLS_SCHEMA). LangChain's @tool decorator auto-generates schema from type hints and docstrings. Docstrings become critical — they are the sole description source for the LLM.

from langchain_core.tools import tool

@tool
def get_weather(city: str) -> str:
    """查询指定城市的当前天气。

    Args:
        city: 城市名称,例如"北京"、"上海"
    """
    fake_weather = {
        "北京": "晴,气温 5°C,西北风 3 级",
        "上海": "多云,气温 12°C,东南风 2 级",
    }
    return fake_weather.get(city, f"抱歉,暂时查不到{city}的天气")

@tool
def calculate(expression: str) -> str:
    """计算一个数学表达式。

    Args:
        expression: 数学表达式,例如"2 + 3 * 4"
    """
    try:
        return str(eval(expression))
    except Exception as e:
        return f"计算失败:{e}"

3.2 Creating LLM and Agent

from langchain_openai import ChatOpenAI
from langchain.agents import create_react_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate

llm = ChatOpenAI(
    model="deepseek-chat",
    api_key="your-api-key",
    base_url="https://api.deepseek.com"
)
tools = [get_weather, calculate]

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个乐于助人的助手。你可以使用工具来解决问题。"),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}")  # Agent's thinking process placeholder
])

agent = create_react_agent(llm, tools, prompt)
agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=True,          # prints thinking process for debugging
    max_iterations=10,     # loop limit
    handle_parsing_errors=True  # auto-retry on parse failure
)

result = agent_executor.invoke({"input": "北京天气怎么样?如果气温低于 10 度,算一下 5 + 3"})
print(result["output"])

3.3 Hand-Rolled vs LangChain Comparison

Lines of code : Hand-rolled ~150 vs LangChain ~30

Tool definition : Hand-rolled manual JSON Schema vs LangChain decorator auto-generates

Loop management : Hand-rolled custom for loop vs LangChain AgentExecutor encapsulated

Error handling : Hand-rolled manual try/except vs LangChain handle_parsing_errors Observability : Hand-rolled custom print statements vs LangChain verbose=True Flexibility : Hand-rolled full control vs LangChain framework-constrained

Learning cost : Hand-rolled low vs LangChain high (many concepts)

Conclusion: for simple scenarios, hand-rolled is better; framework value appears in complex scenarios.

3.4 ReAct Prompt Format Trap

create_react_agent

uses the classic ReAct paradigm requiring specific prompt variables: {tools}, {tool_names}, {agent_scratchpad}. Missing any causes errors — a classic abstraction leak: the framework claims to encapsulate details but you must still know the underlying format.

4. LangChain Expression Language (LCEL)

LCEL uses pipe operator | to chain components:

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

prompt = ChatPromptTemplate.from_template("讲一个关于{topic}的笑话")
chain = prompt | llm | StrOutputParser()

result = chain.invoke({"topic": "程序员"})
print(result)

The | syntax sugar composes "input → prompt → LLM → parse output" into a single chain. Hand-rolled equivalent scatters these steps across functions. LCEL makes pipelines composable, reusable, async-capable, and streamable. Trade-off: deep stack traces make debugging harder.

5. LangGraph: Proper Handling of Complex Flows

5.1 Core Concept: State Graph

LangGraph models agent execution as a directed graph with four primitives:

State : shared data structure across the flow

Node : execution unit (LLM call, tool execution)

Edge : flow rules between nodes

Conditional Edge : routes based on state

Analogy: like Airflow but each node can be an LLM call.

5.2 Rewriting Agent with LangGraph

pip install langgraph

from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
from langchain_openai import ChatOpenAI

class AgentState(TypedDict):
    messages: Annotated[list, add_messages]  # add_messages is reducer, auto-merges messages

llm = ChatOpenAI(model="deepseek-chat", api_key="...", base_url="...")
tools = [get_weather, calculate]
llm_with_tools = llm.bind_tools(tools)

def call_model(state: AgentState):
    """Node that calls LLM"""
    response = llm_with_tools.invoke(state["messages"])
    return {"messages": [response]}

tool_node = ToolNode(tools)  # prebuilt tool execution node

def should_continue(state: AgentState):
    """Routing: call tools or end"""
    last_message = state["messages"][-1]
    if last_message.tool_calls:
        return "tools"
    return END

workflow = StateGraph(AgentState)
workflow.add_node("agent", call_model)
workflow.add_node("tools", tool_node)

workflow.set_entry_point("agent")
workflow.add_conditional_edges("agent", should_continue, {"tools": "tools", END: END})
workflow.add_edge("tools", "agent")  # tools return to agent

app = workflow.compile()

result = app.invoke({
    "messages": [("user", "北京天气怎么样?如果气温低于 10 度,算一下 5 + 3")]
})
print(result["messages"][-1].content)

5.3 Hand-Rolled Loop vs LangGraph Mapping

Hand-rolled for i in range(max_iterations) → LangGraph: graph edges auto-transition

Hand-rolled if not message.tool_calls: return → LangGraph: should_continue conditional edge

Hand-rolled messages.append(...) → LangGraph: add_messages reducer auto-merges

Hand-rolled manual state maintenance → LangGraph: State explicitly defined

LangGraph's value: turns "loop" into "graph," enabling insertion of new nodes (human review, logging, conditional branches) without touching core logic.

5.4 Implementing Human-in-the-Loop with interrupt()

AgentExecutor

cannot pause for human approval. LangGraph's interrupt() halts graph execution, returns control to caller. Caller resumes with Command(resume="approve").

from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import interrupt, Command

@tool
def send_email_tool(to: str, subject: str, body: str) -> str:
    """发送邮件。参数:收件人、主题、正文"""
    approval = interrupt({
        "action": "send_email",
        "to": to,
        "subject": subject,
        "body": body
    })
    if approval == "approve":
        return send_email(to, subject, body)
    else:
        return "用户取消了发送"

This is a production-grade capability — high-risk operations require human guardrails.

6. Multi-Agent Collaboration: When to Use, When to Avoid

6.1 Three Common Patterns

Manager-Worker : Manager delegates to specialized workers (search, write, review)

Sequential : Fixed pipeline (Researcher → Writer → Reviewer → END)

Debate : Multiple agents critique each other to reach consensus

6.2 LangGraph Sequential Writing Workflow

class WritingState(TypedDict):
    topic: str
    research: str
    draft: str
    review: str
    final: str

def researcher(state: WritingState):
    response = llm.invoke(f"请就以下主题搜集关键资料:{state['topic']}")
    return {"research": response.content}

def writer(state: WritingState):
    response = llm.invoke(f"基于以下资料,写一篇 500 字的文章:
{state['research']}")
    return {"draft": response.content}

def reviewer(state: WritingState):
    response = llm.invoke(f"请审校以下文章,指出事实错误和逻辑问题:
{state['draft']}")
    return {"review": response.content}

def reviser(state: WritingState):
    response = llm.invoke(f"根据以下审校意见修改文章:
原文:{state['draft']}
意见:{state['review']}")
    return {"final": response.content}

workflow = StateGraph(WritingState)
workflow.add_node("researcher", researcher)
workflow.add_node("writer", writer)
workflow.add_node("reviewer", reviewer)
workflow.add_node("reviser", reviser)

workflow.set_entry_point("researcher")
workflow.add_edge("researcher", "writer")
workflow.add_edge("writer", "reviewer")
workflow.add_edge("reviewer", "reviser")
workflow.add_edge("reviser", END)

app = workflow.compile()
result = app.invoke({"topic": "AI Agent 的发展趋势"})
print(result["final"])

6.3 Warning: Don't Over-Engineer with Multi-Agent

Costs of unnecessary multi-agent: token cost ×2, latency ×2 (serial), error accumulation, debugging hell. Decision criteria:

Fixed steps → use Workflow , not multi-agent

Different domain expertise needed → consider multi-agent

Parallel exploration needed → consider multi-agent

Just splitting a big task → single agent + planning suffices

7. Framework Selection: LangChain vs Alternatives

LangChain — All-rounder, largest ecosystem. Best for: rapid prototypes, standard RAG. Avoid when: complex state management.

LangGraph — State graphs, complex flows. Best for: multi-agent, human-in-loop, loops. Avoid when: simple tasks (over-engineering).

LlamaIndex — RAG specialist. Best for: document QA, knowledge bases. Avoid when: general-purpose agents.

AutoGen — Multi-agent conversation. Best for: research, debate scenarios. Avoid when: production environments.

CrewAI — Role-play multi-agent. Best for: quick team collaboration setup. Avoid when: fine-grained control needed.

Pydantic AI — Type-safe, lightweight. Best for: projects prioritizing code quality. Avoid when: need many prebuilt components.

OpenAI Agents SDK — OpenAI official. Best for: OpenAI-only ecosystem. Avoid when: multi-model support required.

Recommended learning path: 1) LangChain for ecosystem understanding, 2) LangGraph for complex flows, 3) Pick one specialist framework per project needs.

8. Five Common Framework Pitfalls

Pitfall 1: Version Hell

LangChain APIs change rapidly. Tutorials for 0.1 may not work on 0.3. Fixes: read official docs, pin versions ( langchain==0.3.0), verify with pip list.

Pitfall 2: Abstraction Leakage

Frameworks claim to hide details but errors expose internals (e.g., AgentExecutor "Could not parse LLM output" → ReAct prompt format issue). Solution: hand-roll first, then use framework — exactly this series' approach.

Pitfall 3: Debugging Difficulty

LCEL chain errors produce deep stack traces. Fixes: enable verbose=True, use LangSmith tracing (next article), break complex chains into testable segments.

Pitfall 4: Performance Overhead

Abstraction layers can add 2-3× latency in simple cases. Fix: hand-roll simple scenarios; don't use frameworks for the sake of using them.

Pitfall 5: Over-Engineering

Newcomers use complex frameworks for trivial tasks (e.g., LangGraph for a weather agent). Guiding principle:

If a Workflow solves it, don't use an Agent; if hand-rolled solves it, don't use a framework; if a simple framework solves it, don't use a complex one.

9. Practice Tasks

Task 1 (Required): Rewrite Part-2 Agent with LangChain

Define tools with @tool Create agent with create_react_agent Execute with AgentExecutor Enable verbose=True to observe reasoning

Compare: what did the framework save? What control was lost?

Task 2 (Required): Rewrite Part-3 RAG Agent with LangGraph

Define state with StateGraph Handle tool calls with ToolNode Implement "call tool or end" routing via conditional edges

Integrate RAG retrieval

Compare: how does state management differ from hand-rolled?

Task 3 (Challenge): Human-in-the-Loop Agent

Use interrupt() to pause before high-risk actions (e.g., sending email)

Resume with Command(resume="approve") after user confirmation

Understands production-grade agent capability

10. Summary & Next Episode Preview

Five Key Takeaways

Frameworks trade generality for efficiency — understand abstractions and their implementations.

LangChain's core is componentization: every agent step becomes a swappable component.

LangGraph's core is state graphs: model execution as directed graphs supporting conditional branches, loops, human intervention.

Don't multi-agent for the sake of it — single agent + planning often suffices.

Framework choice depends on scenario: LangChain for breadth, LangGraph for complex flows, LlamaIndex for RAG.

Capabilities You Now Have

Build agents quickly with LangChain

Manage complex flows with LangGraph

Implement human review and conditional branches

Judge when to use frameworks vs hand-rolled code

Next Episode: "Production-Ready: From Demo to Enterprise System"

Build evaluation systems (Evals): data-driven agent quality judgment

Add observability: trace every execution with LangSmith/LangFuse

Design safety guardrails: prevent prompt injection, privilege escalation

Optimize cost & latency: caching, parallelism, model tiering

Complete production-grade agent checklist

Why Episode 5 matters most: previous episodes built "working demos"; a chasm separates demos from production systems. Episode 5 bridges that gap.

Action required before Episode 5: Complete Tasks 1 and 2. Focus not on "making it run" but on comparison — place framework and hand-rolled versions side by side and ask: What did the framework save? What control did I lose? If production breaks, do I know where to look? Answering these three questions means you truly understand framework value and boundaries.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

AI agentsLangChainmulti-agent systemstool callingFramework SelectionLangGraphLCELReAct PatternRAG AgentsState Graphs
Coder Trainee
Written by

Coder Trainee

Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.