🧠 从零理解 LangChain + LangGraph 的工具调用机制

目标:掌握如何使用 LangChain 定义函数工具(tool),并通过 LangGraph 构建一个能自动调用工具的智能体(Agent-like)流程。


一、代码整体结构概览

这段代码实现了以下功能:

  1. 安全设置环境变量OPENAI_API_KEYOPENAI_BASE_URL
  2. 初始化一个支持工具调用的大语言模型(LLM)
  3. 定义一个 Python 函数作为“工具”multiply
  4. 将工具绑定到 LLM 上,使其能“理解并调用”该函数
  5. 使用 LangGraph 构建一个状态图(StateGraph),实现单节点推理流程
  6. 测试两种输入:普通问候 vs 工具调用请求

最终,当用户说 “Multiply 2 and 3” 时,模型应返回一个包含 tool_callsAIMessage,指明要调用 multiply(a=2, b=3)


二、关键组件详解

1. 环境变量安全设置

1
2
3
def _set_env(var: str):
if not os.environ.get(var):
os.environ[var] = getpass.getpass(f"{var}: ")
  • 使用 getpass.getpass() 避免 API Key 明文显示在终端。
  • 设置 OPENAI_API_KEY 是必须的;OPENAI_BASE_URL 则用于代理(如你提供的 https://api.apiyi.com/v1)。

最佳实践:永远不要把密钥写死在代码中!


2. 初始化支持工具调用的 LLM

1
2
3
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="deepseek-chat")
llm_with_tools = llm.bind_tools([multiply])
  • ChatOpenAI 是 LangChain 对 OpenAI 兼容 API 的封装。
  • bind_tools([multiply]) 告诉模型:“你可以调用这些函数”,并自动生成 OpenAI 格式的 function calling schema
  • 注意:deepseek-chat 必须支持工具调用(即 function/tool calling 能力),否则会失败。

🔍 深度提示:bind_tools 会自动从函数的 docstring 和类型注解中提取参数信息,生成 JSON Schema。

例如,multiply 会被转换为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"type": "function",
"function": {
"name": "multiply",
"description": "Multiply a and b.",
"parameters": {
"type": "object",
"properties": {
"a": {"type": "integer"},
"b": {"type": "integer"}
},
"required": ["a", "b"]
}
}
}

3. 定义工具函数

1
2
3
def multiply(a: int, b: int) -> int:
"""Multiply a and b."""
return a * b
  • 函数必须有 类型注解docstring,LangChain 才能正确解析。
  • 返回值类型不影响工具调用(调用发生在模型输出阶段,不执行函数本身)。

⚠️ 注意:当前代码 只让模型“提议”调用工具,但 并未实际执行 multiply。若要执行,需额外添加工具调用节点(见后文扩展)。


4. 使用 LangGraph 构建状态图

1
2
3
4
5
6
7
8
9
10
11
12
13
from langgraph.graph import MessagesState, StateGraph, START, END

class MessagesState(MessagesState):
pass

def tool_calling_llm(state: MessagesState):
return {"messages": [llm_with_tools.invoke(state["messages"])]}

builder = StateGraph(MessagesState)
builder.add_node("tool_calling_llm", tool_calling_llm)
builder.add_edge(START, "tool_calling_llm")
builder.add_edge("tool_calling_llm", END)
graph = builder.compile()
  • MessagesState 是 LangGraph 内置的状态类,只包含 messages: list[BaseMessage]
  • tool_calling_llm 节点:接收消息历史,调用 LLM(带工具),返回新消息。
  • 整个图是 单节点线性流程:输入 → LLM → 输出。

📌 这是一个“非完整 Agent”——它能生成工具调用,但不会自动执行或响应结果。


5. 测试与输出

1
2
3
4
5
# 测试 1:普通对话
messages = graph.invoke({"messages": HumanMessage(content="Hello!")})

# 测试 2:触发工具调用
messages = graph.invoke({"messages": HumanMessage(content="Multiply 2 and 3")})
  • 第一次:模型返回普通 AIMessage
  • 第二次:模型应返回 AIMessage,其中 tool_calls 字段非空,包含:
    1
    2
    3
    4
    {
    "name": "multiply",
    "args": {"a": 2, "b": 3}
    }

通过 m.pretty_print() 可直观看到消息结构。


三、为什么这很重要?——工具调用的意义

大模型本身不能直接计算 2*3,但通过 工具调用机制,它可以:

  1. 识别用户意图(“需要计算乘法”)
  2. 结构化表达需求(生成标准 tool call)
  3. 交由外部系统执行(你的 Python 函数、数据库、API 等)

这是构建 智能体(Agent) 的核心基础。


四、执行流程可视化

流程图

五、下一步:如何真正“执行”工具?

当前代码只到“提议调用”。要完成闭环,需:

  1. 添加一个 工具执行节点
  2. 在图中判断是否有 tool_calls
  3. 若有,则调用对应函数,并将结果以 ToolMessage 形式回传

示例扩展逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from langchain_core.messages import ToolMessage

def call_tool(state: MessagesState):
last_msg = state["messages"][-1]
if last_msg.tool_calls:
tool_call = last_msg.tool_calls[0]
func = globals()[tool_call["name"]]
result = func(**tool_call["args"])
return {
"messages": [
ToolMessage(
content=str(result),
tool_call_id=tool_call["id"]
)
]
}
return {"messages": []}

然后构建更复杂的图(如:LLM → 工具调用?→ 是 → 执行 → 返回 → LLM 继续回答)。


六、总结:关键知识点记忆卡

概念 说明
bind_tools 将 Python 函数注册为 LLM 可调用的工具
MessagesState LangGraph 中管理对话历史的标准状态
tool_calls AIMessage 中的字段,表示模型希望调用的工具
StateGraph 用于构建多步骤、条件分支的智能体流程
工具调用 ≠ 工具执行 模型只“提议”,需开发者实现“执行+反馈”

七、延伸思考

  • 如果模型同时调用多个工具怎么办?
  • 如何防止模型滥用工具(如无限循环)?
  • 能否结合 ReAct 或 Plan-and-Execute 框架?

这些问题正是 LangGraph 设计要解决的——用图结构编排复杂智能体行为


希望这篇文章帮你不仅“看懂代码”,更能建立系统性认知。动手尝试添加真正的工具执行逻辑,你会对 LangChain + LangGraph 的能力有更深体会!🚀