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.
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_agentuses 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()
AgentExecutorcannot 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Coder Trainee
Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
