Skip to content

工具智能体实战

上一章我们用 StateGraph 手搓了一个 ReAct 智能体,代码大约 40 行。其实 LangGraph 提供了一个预置函数 create_react_agent,能把同样的事压缩到 3 行。本章讲怎么用它快速构建一个"会查天气、会算数、会记笔记"的实用助手。

一、create_react_agent:一行构建智能体

create_react_agent 是 LangGraph 在 langgraph.prebuilt 里提供的工厂函数,它内部帮我们做了上一章手搓的全部工作:建 agent 节点、建 tools 节点、连条件边。

最简用法只要两个参数:

python
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI

# model + tools,就这么简单
agent = create_react_agent(
    model=ChatOpenAI(model="gpt-4o-mini", temperature=0),
    tools=[],
)

等价于上一章手搓的整个图。它返回的就是一个编译好的 CompiledStateGraph,调用方式完全一样:

python
result = agent.invoke({"messages": [{"role": "user", "content": "你好"}]})
print(result["messages"][-1].content)

手搓 vs 预置对比

维度手搓 StateGraphcreate_react_agent
代码量~40 行~5 行
灵活性高,可任意改图中,常用配置都支持
适合场景学习/深度定制快速出产品
可加 memory自己接 checkpointer内置 checkpointer 参数

结论:先学手搓懂原理,干活用预置提效率

二、准备三个工具

我们做一个"天气+算数+笔记"助手。其中"查天气"用模拟数据(真要联网需要单独的天气 API):

python
from langchain_core.tools import tool
import json
from pathlib import Path

NOTE_FILE = Path("notes.json")

@tool
def get_weather(city: str) -> str:
    """查询指定城市的天气。返回类似 '北京 晴 25°C' 的字符串。
    支持:北京、上海、广州、深圳。其他城市返回未知。"""
    data = {
        "北京": "晴 25°C",
        "上海": "多云 28°C",
        "广州": "雷阵雨 31°C",
        "深圳": "多云 30°C",
    }
    return data.get(city, f"{city} 暂无天气数据")

@tool
def calculate(expression: str) -> str:
    """计算一个数学表达式,如 '1+2*3'。仅支持 + - * / 和数字。"""
    # 限制字符,防止 eval 注入
    allowed = set("0123456789+-*/.() ")
    if not set(expression) <= allowed:
        return "表达式包含不支持的字符"
    try:
        return str(eval(expression))
    except Exception as e:
        return f"计算失败: {e}"

@tool
def save_note(content: str) -> str:
    """把一段文字保存到本地笔记文件 notes.json。返回保存确认。"""
    notes = []
    if NOTE_FILE.exists():
        notes = json.loads(NOTE_FILE.read_text(encoding="utf-8"))
    notes.append({"content": content})
    NOTE_FILE.write_text(json.dumps(notes, ensure_ascii=False, indent=2), encoding="utf-8")
    return f"已保存第 {len(notes)} 条笔记"

tools = [get_weather, calculate, save_note]

三、加 system_message 设定人设

create_react_agent 接受 state_modifierprompt 参数(0.2+ 版本推荐用 prompt),可以塞一段 system 提示,给智能体设定人设和行为规范:

python
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI

SYSTEM_PROMPT = """你是一个贴心的中文生活助手,名字叫小助。
- 用户问天气就用 get_weather。
- 涉及计算就用 calculate。
- 用户说"记一下""保存笔记"就用 save_note。
- 回答用简体中文,简洁友好。"""

agent = create_react_agent(
    model=ChatOpenAI(model="gpt-4o-mini", temperature=0),
    tools=tools,
    prompt=SYSTEM_PROMPT,
)

四、给智能体加上记忆

默认情况下,每次 invoke 都是"失忆"的——智能体记不住上一句说过什么。加上 checkpointer + thread_id 就能跨轮次记忆。

python
from langgraph.checkpoint.memory import MemorySaver

memory = MemorySaver()  # 进程内存储,重启丢失

agent = create_react_agent(
    model=ChatOpenAI(model="gpt-4o-mini", temperature=0),
    tools=tools,
    prompt=SYSTEM_PROMPT,
    checkpointer=memory,   # 关键:注入检查点存储
)

调用时通过 config 指定一个 thread_id,同一个 thread 里的消息会自动累积:

python
config = {"configurable": {"thread_id": "user-001"}}

# 第一轮
agent.invoke({"messages": [{"role": "user", "content": "北京天气怎么样?"}]}, config=config)
# 第二轮:能记住上面问的是北京
agent.invoke({"messages": [{"role": "user", "content": "那上海呢?"}]}, config=config)

第二个问题里没提"天气",但智能体会从历史消息推断出意图。这就是短期记忆。如果要跨会话长期记忆,看智能体记忆一章。

五、流式输出思考过程

invoke 是一次性返回,体验不好。用 stream 能看到智能体每一步在干什么:

python
for chunk in agent.stream(
    {"messages": [{"role": "user", "content": "查下广州天气,再算 (32+18)*2"}]},
    config=config,
    stream_mode="updates",   # 每个节点更新时输出
):
    for node_name, update in chunk.items():
        print(f"--- 节点: {node_name} ---")
        # 最后一条消息的简短预览
        last = update["messages"][-1]
        if hasattr(last, "tool_calls") and last.tool_calls:
            print(f"  调用工具: {[c['name'] for c in last.tool_calls]}")
        else:
            print(f"  内容: {last.content[:80]}")

stream_mode 有几种常用值:

  • "values":每次输出完整状态。
  • "updates":只输出每个节点的增量。
  • "messages":逐 token 输出 LLM 的文本,适合打字机效果。

六、完整可运行示例

把上面拼起来:

python
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import MemorySaver
import json
from pathlib import Path

NOTE_FILE = Path("notes.json")

@tool
def get_weather(city: str) -> str:
    """查询指定城市天气。支持:北京、上海、广州、深圳。"""
    data = {"北京": "晴 25°C", "上海": "多云 28°C",
            "广州": "雷阵雨 31°C", "深圳": "多云 30°C"}
    return data.get(city, f"{city} 暂无天气数据")

@tool
def calculate(expression: str) -> str:
    """计算数学表达式,如 '1+2*3'。仅支持 + - * / 和数字。"""
    if not set(expression) <= set("0123456789+-*/.() "):
        return "表达式包含不支持的字符"
    try:
        return str(eval(expression))
    except Exception as e:
        return f"计算失败: {e}"

@tool
def save_note(content: str) -> str:
    """把一段文字保存到本地笔记 notes.json。"""
    notes = []
    if NOTE_FILE.exists():
        notes = json.loads(NOTE_FILE.read_text(encoding="utf-8"))
    notes.append({"content": content})
    NOTE_FILE.write_text(json.dumps(notes, ensure_ascii=False, indent=2), encoding="utf-8")
    return f"已保存第 {len(notes)} 条笔记"

SYSTEM_PROMPT = """你是一个贴心的中文生活助手,名字叫小助。
- 用户问天气就用 get_weather。
- 涉及计算就用 calculate。
- 用户说'记一下'就用 save_note。
- 回答用简体中文,简洁友好。"""

agent = create_react_agent(
    model=ChatOpenAI(model="gpt-4o-mini", temperature=0),
    tools=[get_weather, calculate, save_note],
    prompt=SYSTEM_PROMPT,
    checkpointer=MemorySaver(),
)

config = {"configurable": {"thread_id": "demo-001"}}

# 多轮对话演示
for user_msg in ["广州天气咋样?", "顺便算下 (32+18)*2", "记一下:今天广州雷阵雨"]:
    print(f"\n用户: {user_msg}")
    result = agent.invoke(
        {"messages": [{"role": "user", "content": user_msg}]},
        config=config,
    )
    print(f"小助: {result['messages'][-1].content}")

预期输出:智能体会分别调 get_weathercalculatesave_note,最后每轮给出一句中文回复,并能在 notes.json 里看到写入的笔记。

七、常见踩坑

1. 工具过多导致模型选错

工具超过 10 个时,模型容易"乱选"或漏选。解决办法:

  • 按场景分组,用多智能体分管不同工具集。
  • 工具命名加前缀,如 weather_getnote_save,让模型更容易匹配。
  • 在 system prompt 里明确"什么情况用什么工具"。

2. 上下文越来越长

有记忆后,messages 会无限累积,token 消耗越来越大,甚至超模型上限。两种处理:

  • MessagesState 时主动裁剪,只保留最近 N 条。
  • 在节点里加一个"摘要"节点,把老消息压缩成一段摘要。LangGraph 提供 RemoveMessage 工具来删除旧消息:
python
from langchain_core.messages import RemoveMessage

def trim(state):
    # 只保留最近 10 条
    return {"messages": [RemoveMessage(id=m.id) for m in state["messages"][:-10]]}

3. prompt 参数版本差异

旧版本叫 state_modifier,0.2+ 改名为 prompt。如果报参数不存在的错,升级 langgraph:

bash
pip install -U langgraph -i https://pypi.tuna.tsinghua.edu.cn/simple

4. 工具异常没被捕获

工具内部抛异常时,ToolNode 默认会把异常信息作为 ToolMessage 返回给模型,模型可能据此重试。但如果是 KeyError 这种,模型看不懂。建议工具内部 try/except,返回人类可读的错误字符串。

八、小结

  • create_react_agent(model, tools) 一行建智能体,等价于手搓的 ReAct 图。
  • prompt 参数设人设,用 checkpointer 加记忆,用 stream 看思考过程。
  • 实战示例:天气+算数+笔记助手,支持多轮对话。
  • 踩坑重点:工具别太多、上下文要裁剪、注意 API 版本差异。

下一章我们升级到多个智能体协作,让不同 agent 各司其职。