Appearance
工具智能体实战
上一章我们用 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 预置对比
| 维度 | 手搓 StateGraph | create_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_modifier 或 prompt 参数(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_weather→calculate→save_note,最后每轮给出一句中文回复,并能在 notes.json 里看到写入的笔记。
七、常见踩坑
1. 工具过多导致模型选错
工具超过 10 个时,模型容易"乱选"或漏选。解决办法:
- 按场景分组,用多智能体分管不同工具集。
- 工具命名加前缀,如
weather_get、note_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/simple4. 工具异常没被捕获
工具内部抛异常时,ToolNode 默认会把异常信息作为 ToolMessage 返回给模型,模型可能据此重试。但如果是 KeyError 这种,模型看不懂。建议工具内部 try/except,返回人类可读的错误字符串。
八、小结
create_react_agent(model, tools)一行建智能体,等价于手搓的 ReAct 图。- 用
prompt参数设人设,用checkpointer加记忆,用stream看思考过程。 - 实战示例:天气+算数+笔记助手,支持多轮对话。
- 踩坑重点:工具别太多、上下文要裁剪、注意 API 版本差异。
下一章我们升级到多个智能体协作,让不同 agent 各司其职。