LangGraph 构建 AI 智能体的两种方式:手动搭建 vs 一行代码

在学习 LangGraph 多智能体系统时(项目来源:Multi-Agent-AI-System),遇到两种构建 ReAct Agent 的方式。一种是从零开始手动搭建图结构,另一种是用预构建函数一行搞定。

背景:什么是 ReAct Agent?

ReAct = Reasoning + Acting(推理 + 行动)。

一个 ReAct Agent 的工作循环很简单:

用户提问 → LLM 思考 → 决定调用工具 → 执行工具 → 拿到结果 → 继续思考 → ... → 给出最终回答

这个"思考-行动-观察"的循环会一直重复,直到 LLM 认为已经有足够信息来回答用户。


方式一:手动搭建(从零构建图结构)

这是项目中 音乐目录子智能体(music_catalog_subagent) 的构建方式。你需要自己定义图的每一个组成部分。

第一步:定义 State(状态)

State 是在图中流动的数据结构,相当于智能体的"工作记忆":

from typing_extensions import TypedDict
from typing import Annotated
from langgraph.graph.message import AnyMessage, add_messages
from langgraph.managed.is_last_step import RemainingSteps

class State(TypedDict):
    customer_id: str                                    # 客户 ID
    messages: Annotated[list[AnyMessage], add_messages] # 对话历史(自动追加)
    loaded_memory: str                                  # 从长期记忆加载的用户偏好
    remaining_steps: RemainingSteps                     # 防止无限循环的计数器

第二步:定义 Tools(工具)

工具是 LLM 可以调用的外部函数。这里定义了 4 个查询音乐数据库的工具:

from langchain_core.tools import tool

@tool
def get_albums_by_artist(artist: str):
    """根据艺术家名字查找专辑"""
    return db.run(f"""
        SELECT Album.Title, Artist.Name 
        FROM Album 
        JOIN Artist ON Album.ArtistId = Artist.ArtistId 
        WHERE Artist.Name LIKE '%{artist}%';
    """, include_columns=True)

@tool
def get_tracks_by_artist(artist: str):
    """根据艺术家名字查找曲目"""
    return db.run(f"""
        SELECT Track.Name as SongName, Artist.Name as ArtistName 
        FROM Album 
        LEFT JOIN Artist ON Album.ArtistId = Artist.ArtistId 
        LEFT JOIN Track ON Track.AlbumId = Album.AlbumId 
        WHERE Artist.Name LIKE '%{artist}%';
    """, include_columns=True)

@tool
def get_songs_by_genre(genre: str):
    """根据流派查找歌曲"""
    # ... 省略具体实现

@tool
def check_for_songs(song_title):
    """检查某首歌是否存在"""
    # ... 省略具体实现

# 把工具绑定到 LLM
music_tools = [get_albums_by_artist, get_tracks_by_artist, get_songs_by_genre, check_for_songs]
llm_with_music_tools = llm.bind_tools(music_tools)

第三步:定义 Nodes(节点)

节点是图中的处理单元。ReAct Agent 需要两个节点:

from langgraph.prebuilt import ToolNode
from langchain_core.messages import SystemMessage

# 节点 1:工具执行节点(自动处理工具调用)
music_tool_node = ToolNode(music_tools)

# 节点 2:LLM 推理节点(决定下一步做什么)
def music_assistant(state: State, config):
    memory = state.get("loaded_memory", "None")
    system_prompt = f"你是音乐目录助手...用户偏好:{memory}"
    
    response = llm_with_music_tools.invoke(
        [SystemMessage(system_prompt)] + state["messages"]
    )
    return {"messages": [response]}

第四步:定义 Edges(边)

边决定了节点之间的执行流向。关键是条件边——根据 LLM 的输出决定下一步:

def should_continue(state: State, config):
    last_message = state["messages"][-1]
    if not last_message.tool_calls:
        return "end"       # 没有工具调用 → 结束
    else:
        return "continue"  # 有工具调用 → 继续执行工具

第五步:组装并编译

把所有部件拼在一起:

from langgraph.graph import StateGraph, START, END

# 创建图
music_workflow = StateGraph(State)

# 添加节点
music_workflow.add_node("music_assistant", music_assistant)
music_workflow.add_node("music_tool_node", music_tool_node)

# 添加边
music_workflow.add_edge(START, "music_assistant")           # 入口 → LLM
music_workflow.add_conditional_edges(                        # LLM → 工具 or 结束
    "music_assistant",
    should_continue,
    {"continue": "music_tool_node", "end": END}
)
music_workflow.add_edge("music_tool_node", "music_assistant") # 工具 → 回到 LLM

# 编译
music_catalog_subagent = music_workflow.compile(
    name="music_catalog_subagent",
    checkpointer=checkpointer,
    store=in_memory_store
)

最终得到的图结构:

┌─────────────────┐
│     START        │
└────────┬────────┘
         ▼
┌─────────────────┐
│ music_assistant  │◄──────────┐
└────────┬────────┘            │
         │                     │
    ┌────┴────┐                │
    │ 有工具   │无工具调用       │
    │ 调用?   │───────► END    │
    └────┬────┘                │
         ▼                     │
┌─────────────────┐            │
│ music_tool_node  │───────────┘
└─────────────────┘

方式二:预构建函数(一行代码)

这是项目中 发票信息子智能体(invoice_information_subagent) 的构建方式。

只需要准备工具和提示词

@tool 
def get_invoices_by_customer_sorted_by_date(customer_id: str):
    """按日期排序获取客户的所有发票"""
    return db.run(f"SELECT * FROM Invoice WHERE CustomerId = {customer_id} ORDER BY InvoiceDate DESC;")

@tool 
def get_invoices_sorted_by_unit_price(customer_id: str):
    """按单价排序获取客户的发票明细"""
    # ... 省略

@tool
def get_employee_by_invoice_and_customer(invoice_id: str, customer_id: str):
    """获取发票关联的客服员工信息"""
    # ... 省略

invoice_tools = [
    get_invoices_by_customer_sorted_by_date, 
    get_invoices_sorted_by_unit_price, 
    get_employee_by_invoice_and_customer
]

invoice_subagent_prompt = "你是发票信息助手,负责处理客户的账单查询..."

一行代码创建 Agent

from langgraph.prebuilt import create_react_agent

invoice_information_subagent = create_react_agent(
    model=llm,                    # LLM 模型
    tools=invoice_tools,          # 工具列表
    name="invoice_subagent",      # 名称
    prompt=invoice_subagent_prompt, # 系统提示词
    state_schema=State,           # 状态定义
    checkpointer=checkpointer,    # 短期记忆
    store=in_memory_store         # 长期记忆
)

就这样,完成了。create_react_agent 内部自动帮你做了方式一中的所有步骤:定义节点、添加条件边、编译图。


对比总结

维度 手动搭建 create_react_agent
代码量 ~40 行(不含工具定义) 1 行调用
灵活性 完全自定义,可以加任意节点和边 标准 ReAct 循环,定制空间有限
学习价值 深入理解 LangGraph 底层原理 快速上手,适合生产使用
适用场景 需要非标准流程(如多分支、自定义路由) 标准的"思考-行动-观察"循环
最终效果 完全相同 完全相同

什么时候用哪种?

用手动搭建,当你需要:

  • 在 ReAct 循环中插入额外步骤(如审核节点、日志节点)
  • 自定义路由逻辑(不只是"有工具调用就执行")
  • 多个 LLM 节点协作
  • 学习和理解 LangGraph 的工作原理

create_react_agent,当你需要:

  • 快速创建一个标准的 ReAct Agent
  • 不需要自定义图结构
  • 作为多智能体系统中的子智能体(像本项目中的 invoice 子智能体)

补充:LangChain v1 的 create_agent

LangChain v1 引入了更高层的 create_agent 函数,它在 create_react_agent 基础上增加了 Middleware(中间件) 系统:

from langchain.agents import create_agent
from langchain.agents.middleware import PIIMiddleware, SummarizationMiddleware

agent = create_agent(
    model="claude-sonnet-4-6",
    tools=[search_web, send_email],
    middleware=[
        PIIMiddleware("email", strategy="redact"),          # 自动脱敏
        SummarizationMiddleware(trigger={"tokens": 500}),   # 对话过长自动摘要
    ]
)

三者的关系是:

手动搭建 StateGraph(最底层,最灵活)
    ↑ 封装
create_react_agent(标准 ReAct 循环)
    ↑ 封装
create_agent(加入中间件系统,LangChain v1 推荐)

层级越高越简洁,层级越低越灵活。根据你的需求选择合适的抽象层级即可。

posted @ 2026-05-25 10:37  江鸟Dev  阅读(16)  评论(0)    收藏  举报