《基于LangGraph构建具备多工具调用与自动化摘要能力的智能 Agent 》
1 导语
在构建复杂的 AI 代理时,如何让 Agent 不仅能"听懂"需求,还能"协调"多个工具并最终"专业"地汇总结果?本文将基于 LangGraph 实战案例,深度拆解一个具备实时联网搜索、天气查询、数据库入库以及最终结果自动化摘要能力的完整智能体闭环。
本文所有步骤亲测可复现,适配环境:Windows/macOS/Linux,Python >= 3.9。
2 技术栈清单
- Python == 3.11.x
- langgraph == 1.0.5
- langchain-openai == 1.1.7
- python-dotenv == 1.2.1
- Model: qwen-plus (DashScope API)
3 项目核心原理
本项目通过 StateGraph 构建了一个闭环工作流:
- 决策层 :由大模型判定是否需要调用工具(
chat_with_model)。 - 执行层 :手动实现工具执行节点(
execute_function),处理并行的tool_calls。 - 分流逻辑:利用条件边(Conditional Edges)实现"执行工具"与"直接回复"的动态切换。
- 汇总层 :所有路径最终汇聚于
natural_response节点,利用系统 Prompt 对多源信息进行专业化中文总结。
4 实战步骤
4.1 环境准备
前置条件:已配置 .env 文件,包含 DASHSCOPE_API_KEY。
bash
# 安装必要依赖
pip install langgraph==1.0.5 langchain-openai==1.1.7 pydantic==2.10.0
# 验证环境
python -c "import langgraph; print(langgraph.__version__)"
# 成功标识:输出 1.0.5
4.2 代码实现
4.2.1 状态定义与工具 Schema
使用 Pydantic 定义工具的参数约束,确保 LLM 输出的准确性。
python
from typing import TypedDict, Annotated, Optional
import operator
from pydantic import BaseModel, Field
from langchain_core.messages import AnyMessage
# 定义状态:使用 Annotated 和 operator.add 实现消息自动追加
class AgentState(TypedDict):
messages: Annotated[list[AnyMessage], operator.add]
# 定义工具参数模型
class UserInfo(BaseModel):
name: str = Field(description="用户姓名")
age: Optional[int] = Field(description="用户年龄")
email: str = Field(description="邮箱地址")
phone: Optional[str] = Field(description="电话号码")
@tool(args_schema = UserInfo)
def insert_db(name, age, email, phone):
"""Insert user information into the database, The required parameters are name, age, email, phone"""
session = Session() # 确保为每次操作创建新的会话
try:
# 创建用户实例
user = User(name=name, age=age, email=email, phone=phone)
# 添加到会话
session.add(user)
# 提交事务
session.commit()
return {"messages": [f"数据已成功存储至Mysql数据库。"]}
except Exception as e:
session.rollback() # 出错时回滚
return {"messages": [f"数据存储失败,错误原因:{e}"]}
finally:
session.close() # 关闭会话
4.2.2 核心节点与路由逻辑
手动实现工具执行逻辑,深入理解 ToolMessage 的构造。
python
def execute_function(state: AgentState):
# 获取模型生成的工具调用指令
tool_calls = state['messages'][-1].tool_calls
results = []
# 建立工具映射表
tools_map = {"insert_db": insert_db, "fetch_real_time_info": fetch_real_time_info, "get_weather": get_weather}
for t in tool_calls:
# 执行工具并将结果封装为 ToolMessage
result = tools_map[t['name']].invoke(t['args'])
results.append(ToolMessage(tool_call_id=t['id'], name=t['name'], content=str(result)))
return {'messages': results}
def exists_function_calling(state: AgentState):
# 判定是否存在工具调用请求
return len(state['messages'][-1].tool_calls) > 0
4.2.3 图编排与编译
python
graph = StateGraph(AgentState)
graph.add_node("chat_with_model", chat_with_model)
graph.add_node("execute_function", execute_function)
graph.add_node("natural_response", natural_response)
graph.set_entry_point("chat_with_model")
graph.add_conditional_edges(
"chat_with_model",
exists_function_calling,
{True: "execute_function", False: "final_answer"} # 条件分流
)
graph.add_edge("execute_function", "natural_response")
graph.set_finish_point("natural_response")
app = graph.compile()

4.3 功能测试
测试用户信息的自动入库与专业回复:
- 启动命令 :
python ./07-tool-05.py - 预期输出 :输入用户信息后,控制台应先打印 SQL 执行语句(若配置了
echo=True),最后输出中文专业总结。
5 核心代码深度解析
5.1 原始代码展示
python
# 行号 139-148
graph.add_conditional_edges(
"chat_with_model",
exists_function_calling,
{True: "execute_function", False: "final_answer"}
)
graph.add_edge("execute_function", "natural_response")
graph.add_edge("final_answer", "natural_response")
graph.set_finish_point("natural_response")
5.2 关联知识点讲解
- 知识点 1:ToolMessage 的手动构造 :
在execute_function中,我们没有使用预构建的ToolNode。语法/知识点名称 :ToolMessage(tool_call_id=...)。核心作用 :它必须携带tool_call_id才能让模型知道这条结果对应的是哪个请求,否则在多工具并行场景下会逻辑混乱。 - 知识点 2:Annotated 状态归并 :
语法/知识点名称 :Annotated[list, operator.add]。新手易错 :如果漏写了operator.add,后续节点返回的{"messages": [...]}会直接覆盖之前的消息,导致 Agent 丢失上下文记忆。
5.3 代码逻辑解析
- exists_function_calling :作为"分流器",它通过检查
AIMessage中的tool_calls列表长度。如果模型觉得需要查外部信息(如天气),则返回True强制流向执行节点。 - natural_response :这是系统的"润色员"。它不直接返回工具执行的原始字符串(通常很乱),而是通过一个
SYSTEM_PROMPT指令:"基于现有信息,生成专业中文回复"。这种设计保证了最终用户看到的输出具有统一的语气和格式。
6 效果验证
6.1 案例1
python
query="你好,请你介绍一下你自己"
input_message = {"messages": [HumanMessage(content=query)]}
result = graph.invoke(input_message)
result

6.2 案例2
python
query="小米汽车"
input_message = {"messages": [HumanMessage(content=query)]}
result = graph.invoke(input_message)
result

6.3 案例3
python
query="北京的天气怎么样?"
input_message = {"messages": [HumanMessage(content=query)]}
result = graph.invoke(input_message)
result

7 踩坑记录
7.1 Pydantic 模型描述缺失
- 错误现象:模型总是乱填参数,或者不调用工具。
- 根因分析 :
UserInfo中Field的description写得太简略,模型不理解什么时候该采集电话。 - 解决方案 :在
Field中明确标注"如果用户没有提供则不填"等引导语。
7.2 异步调用下的状态冲突
- 错误现象:数据库插入成功但回复里说失败。
- 根因分析 :由于
operator.add的原子性,在并发执行工具时,如果节点逻辑编写不当可能导致消息顺序错乱。 - 解决方案 :在
execute_function中使用列表收集所有ToolMessage后一次性返回。
7.3 环境配置导致的导入错误
- 错误现象 :
ModuleNotFoundError: No module named 'langgraph'。 - 根因分析:在虚拟环境外执行或未正确安装。
- 解决方案 :执行
pip install langgraph==1.0.5并确认which python指向正确环境。
8 总结与扩展
本文通过手动实现 ToolNode 的逻辑,带大家深入理解了 LangGraph 内部的消息交换机制。复现关键点在于:
- Schema 描述:给 LLM 的"说明书"必须精准。
- 状态追加 :必须使用
operator.add保证记忆连续。 - 结果润色:最后加入一个汇总节点,极大提升用户体验。
欢迎评论区留言讨论核心主题相关的问题,若复现失败可留言你的系统版本+报错日志,我会及时回复~