摘要:工具使用(Tool Use,常以 Function Calling 实现)让智能体突破纯文本生成,能够按需调用外部能力——查信息、算数据、调 API、执行代码等。本文说明该模式的动机、典型流程,以及如何用 LangGraph状态图 + Agent 节点 + 条件边 + ToolNode 实现一个可多步调用工具的研究助理,配套示例包含信息检索、股价查询与数学计算三类工具。

关键词:工具使用;Tool Use;Function Calling;LangGraph;ToolNode;bind_tools;状态图;条件边

配套代码目录:LangGraph 5. Tool Use 源代码


1 为什么需要「工具使用」?

前几章我们已经见过:链式(Chaining) 按顺序执行步骤,路由(Routing) 按条件选路径,并行(Parallelization) 同时跑多条分支,反思(Reflection) 通过「生成 → 评审 → 修订」提升质量。但所有这些都建立在「模型根据已有上下文生成文本」的基础上。大模型的知识来自训练数据,无法直接访问实时天气、数据库、计算器或外部 API;若没有与外部世界的接口,智能体就无法完成「查一下伦敦现在几度」「算 100 股 AAPL 市值」这类需要即时数据或确定计算的任务。

工具使用(Tool Use) 就是在智能体中引入可调用的外部能力:把「查天气」「查股价」「算表达式」等封装成工具,用名称、描述和参数告诉模型;模型根据用户问题决定是否调用、调用哪个、传什么参数;框架负责执行工具并把结果塞回对话,模型再据此生成最终回复。这样,智能体就从「只会说」变成「能查、能算、能动手」,真正与外部系统协作。

💡 理解要点:Tool Use = 给模型一双“手”:模型负责「想」(何时用、用哪个、传啥),执行层负责「做」(真正跑函数/API),结果再回馈给模型做总结或下一步决策。


2 工具使用在解决什么问题?

想象这些场景:

  • 信息检索:用户问「法国首都是哪?」「伦敦天气怎么样?」——模型训练数据里虽有答案,但若希望统一走“查一查”的流程(例如对接真实搜索或知识库),就需要一个「查信息」工具,由模型决定查询词并解析结果。
  • 数据 + 计算:用户问「AAPL 现在多少钱?我持有 100 股,总市值多少?」——需要先查股价(外部数据),再算 价格 × 100(精确计算)。模型不擅长精确算术,且股价实时变化,必须通过「股价工具 + 计算工具」两步完成。
  • 执行代码:用户给一段 Python 问「这段代码输出啥?」——需要在沙箱里执行代码并把输出返回给模型,再由模型解释。这同样依赖「代码执行」这一外部能力。

工具使用模式把「该不该用外部能力」和「用哪个、传什么」交给模型,把「真正执行」交给框架与工具实现。这样既保留了自然语言交互的灵活性,又补上了实时性、精确性与可执行性的短板。

🔍 实际例子:就像你问助理「明天北京飞上海的早班机有哪些?」——助理不会「硬想」,而是去查航班系统(工具),把结果拿回来再组织成你能听懂的回复。Tool Use 就是给 LLM 配好这样的「可查、可算、可执行」的接口。


3 工具使用的典型流程

工具使用通常包含以下步骤(可循环多轮):

  1. 工具定义(Tool Definition):将外部能力封装成「工具」,并给出模型能理解的名称、描述和参数(含类型与说明)。在 LangChain/LangGraph 中常用 @tool 装饰器,模型会看到函数的 docstring 与参数 schema。
  2. 模型决策(LLM Decision):模型收到用户请求与工具列表后,判断是否需要调用工具;若需要,则选择工具并从自然语言中抽取参数(如地点、股票代码、数学式子)。
  3. 工具调用生成(Function Call Generation):模型输出结构化请求(如 JSON),包含工具名与参数。支持 Function Calling 的接口会直接返回 tool_calls 列表,由框架解析。
  4. 工具执行(Tool Execution):框架根据 tool_calls 找到对应工具、传入参数并执行,得到返回值(字符串或可序列化结果)。
  5. 观察/结果(Observation):执行结果被包装成 ToolMessage(或等价结构),追加到对话历史中,作为「工具返回」的上下文。
  6. 模型再处理(Optional):模型看到用户问题 + 自己之前的 tool_calls + 工具结果,决定是再调其他工具(多步)还是生成最终回复给用户。

在多步场景下,步骤 3~6 会重复,直到模型不再发起 tool_calls,而是直接输出面向用户的回答。

💡 理解要点「想」和「做」分离——模型只负责「想」(选工具、填参数),执行与结果回传由框架和工具完成;LangGraph 的 ToolNode 就是「做」的那一环,条件边根据「有没有 tool_calls」决定是继续执行工具还是结束。


4 常见应用场景

场景 典型工具 收益
信息检索 搜索 API、知识库查询、天气 API 获取训练数据外或实时信息
数据库与 API 查库存、订单状态、支付接口 与业务系统联动,完成查询或操作
计算与数据分析 计算器、公式求值、统计/表格 精确计算、避免模型算术错误
发送通信 发邮件、发消息、通知 触发真实世界动作
执行代码 沙箱代码执行、Notebook 内核 运行用户代码并解释结果
控制设备/系统 智能家居、IoT、工作流触发 与物理世界或其他系统交互

工具使用是智能体从「纯对话」走向「可感知、可推理、可行动」的基础能力;与 Chaining、Routing、Reflection 等组合后,可以构建出非常强大的多步、多角色工作流。


5 LangGraph 中的工具使用:状态图 + Agent + 条件边 + ToolNode

LangGraph 用状态图描述「用户消息 → 模型(可能带 tool_calls)→ 按需执行工具 → 结果回传模型 → 直至无 tool_calls 结束」的流程:节点对应「模型推理」与「工具执行」,条件边根据最后一条消息是否包含 tool_calls 决定下一跳。

  • 状态(State):图中流转的共享数据。工具调用场景下通常包含 消息列表 messages(HumanMessage、AIMessage、ToolMessage),且使用 add_messages 归约:新消息追加而非覆盖,便于多轮对话与多步工具调用。
  • Agent 节点:将「系统提示 + 当前消息列表」送入 已绑定工具的 LLMllm.bind_tools(tools))。LLM 返回的 AIMessage 可能带 tool_calls,表示本轮要调用的工具及参数。
  • 条件边:若最后一条消息存在 tool_calls,则进入 工具节点;否则进入 END,表示模型已给出最终回复。
  • 工具节点(ToolNode):LangGraph 预构建的 ToolNode(tools) 会读取状态中最后一条 AIMessagetool_calls,依次执行对应工具,将每个结果封装为 ToolMessage 并追加到 messages,然后通常回到 Agent 节点,让模型根据工具结果继续推理或生成回复。

这样就把「谁在什么时候调用模型、谁在什么时候执行工具、何时停止」都固化在图结构里,便于维护和扩展。

START

Agent: LLM + bind_tools

有 tool_calls?

ToolNode 执行工具

END


6 配套代码结构概览

本示例实现一个研究助理:具备信息检索(模拟)、股票价格查询(模拟)与数学表达式计算三类工具,可回答「法国首都是哪」「伦敦天气」「AAPL 股价及 100 股市值」等问题。运行方式见 demo_codes/README.md

请添加图片描述

6.1 状态定义

图的状态仅包含消息列表,并使用 add_messages 归约,使每条新消息(AIMessage、ToolMessage)都追加到列表末尾(注意,不是覆盖,而是像List那样,新的消息会append在之前消息的后面):

# 摘自 demo_codes/tool_use_graph.py
from typing import Annotated
from langgraph.graph.message import add_messages

class ToolUseState(TypedDict):
    messages: Annotated[list[BaseMessage], add_messages]

6.2 工具定义(tools.py)

使用 LangChain 的 @tool 装饰器,让普通函数变成模型可见、可调用的工具;docstring 与参数会作为「工具描述」传给模型:

# 摘自 demo_codes/tools.py
from langchain_core.tools import tool

@tool
def search_information(query: str) -> str:
    """根据给定主题检索事实性信息。适用于诸如「法国首都是哪」「伦敦天气如何」等查询。"""
    # ... 模拟检索逻辑,返回字符串

@tool
def get_stock_price(ticker: str) -> float:
    """查询指定股票代码的最新(模拟)价格。输入为股票代码,如 AAPL、GOOGL。"""
    # ... 模拟价格字典,无效代码抛 ValueError

@tool
def calculate_expression(expression: str) -> str:
    """计算一个数学表达式的值。仅支持基本运算:+、-、*、/、**、括号。"""
    # ... 安全 eval,仅允许数字与运算符

💡 理解要点工具描述(docstring + 参数名/类型)是模型「选工具、填参数」的主要依据,写清楚用途和参数含义能显著提高调用准确率。

6.3 Agent 节点与 bind_tools

Agent 节点负责:在消息列表前注入系统提示(研究助理 + 工具使用规范),然后调用 llm.bind_tools(tools) 得到的可调用模型;返回的 AIMessage 可能带 tool_calls,由条件边决定是否进入 ToolNode:

def _agent_node(state: ToolUseState) -> dict:
    """
    Agent 节点:将系统提示与历史消息送入已绑定工具的 LLM,
    返回 AIMessage(可能包含 tool_calls)。
    """
    if STEP_VERBOSE:
        logger.info("[Agent] 调用 LLM(已绑定工具)...")
    llm = _build_llm()
    tools = get_all_tools()
    llm_with_tools = llm.bind_tools(tools)
    messages = state["messages"]
    # 若首条不是系统消息,则前置系统提示
    if not messages or not isinstance(messages[0], SystemMessage):
        messages = [SystemMessage(content=SYSTEM_PROMPT)] + list(messages)
    response = llm_with_tools.invoke(messages)
    if STEP_VERBOSE:
        if hasattr(response, "tool_calls") and response.tool_calls:
            for tc in response.tool_calls:
                name = tc.get("name", "?") if isinstance(tc, dict) else getattr(tc, "name", "?")
                args = tc.get("args", {}) if isinstance(tc, dict) else getattr(tc, "args", {})
                logger.info("[Agent] 本轮输出: 工具调用 %s(%s)", name, args)
        else:
            content_preview = (getattr(response, "content", None) or "")[:150]
            if len(str(getattr(response, "content", "") or "")) > 150:
                content_preview += "..."
            logger.info("[Agent] 本轮输出: 最终回复(无 tool_calls): %s", content_preview)
    return {"messages": [response]}

🔍 实际例子:用户问「AAPL 股价多少?100 股总市值多少?」时,模型可能先输出 tool_calls: [get_stock_price("AAPL")];ToolNode 执行后把价格(如 178.15)以 ToolMessage 形式追加;模型再次被调用时看到「用户问题 + 自己之前的 tool_calls + 工具返回 178.15」,再决定调用 calculate_expression("178.15 * 100"),最后用两次工具结果组织成一句回复。

6.4 条件边:是否有 tool_calls

路由函数检查最后一条消息是否为带 tool_calls 的 AIMessage:是的话,则进入 tools 节点,否则结束:

def _should_continue(state: ToolUseState) -> Literal["tools", "__end__"]:
    """
    条件边:若最后一条为 AIMessage 且含 tool_calls,则进入工具节点;否则结束。
    """
    messages = state["messages"]
    if not messages:
        if STEP_VERBOSE:
            logger.info("[路由] 无消息,结束。")
        return "__end__"
    last = messages[-1]
    if hasattr(last, "tool_calls") and last.tool_calls:
        if STEP_VERBOSE:
            logger.info("[路由] 进入工具节点(本轮回调 %s 个工具)", len(last.tool_calls))
        return "tools"
    if STEP_VERBOSE:
        logger.info("[路由] 结束,返回最终回复。")
    return "__end__"

6.5 图构建

将 Agent 节点、ToolNode、START、END 与条件边、工具→Agent 的边组装成图并编译:

# 摘自 demo_codes/tool_use_graph.py
def build_tool_use_graph():
    tools = get_all_tools()
    tool_node = ToolNode(tools, name="tools")
    workflow = StateGraph(ToolUseState)
    workflow.add_node("agent", _agent_node)
    workflow.add_node("tools", tool_node)
    workflow.add_edge(START, "agent")
    workflow.add_conditional_edges("agent", _should_continue, {"tools": "tools", "__end__": END})
    workflow.add_edge("tools", "agent")
    return workflow.compile()

完整实现(含系统提示、配置与入口)见源代码中 demo_codes/tool_use_graph.pydemo_codes/main.pydemo_codes/tools.pydemo_codes/prompt.py

最后,我们举个例子,看看输出日志中整个数据流是怎么运转的。比如,用户问了一个问题:“苹果公司股票 AAPL 的当前(模拟)股价是多少?如果我持有 100 股,总市值大约多少?请先查股价再计算。”

日志显示:

2026-03-12 15:19:21 [INFO] tool_use: [Agent] 调用 LLM(已绑定工具)...
问题: 苹果公司股票 AAPL 的当前(模拟)股价是多少?如果我持有 100 股,总市值大约多少?请先查股价再计算。
2026-03-12 15:19:22 [INFO] tool_use: [Agent] 本轮输出: 工具调用 get_stock_price({'ticker': 'AAPL'})
2026-03-12 15:19:22 [INFO] tool_use: [路由] 进入工具节点(本轮回调 1 个工具)
2026-03-12 15:19:22 [INFO] tool_use: [Tools] 执行工具: get_stock_price({'ticker': 'AAPL'})
2026-03-12 15:19:22 [INFO] tool_use: [Tools] 工具 get_stock_price 返回: 178.15
2026-03-12 15:19:22 [INFO] tool_use: [Agent] 调用 LLM(已绑定工具)...
2026-03-12 15:19:23 [INFO] tool_use: [Agent] 本轮输出: 工具调用 calculate_expression({'expression': '178.15 * 100'})
2026-03-12 15:19:23 [INFO] tool_use: [路由] 进入工具节点(本轮回调 1 个工具)
2026-03-12 15:19:23 [INFO] tool_use: [Tools] 执行工具: calculate_expression({'expression': '178.15 * 100'})
2026-03-12 15:19:23 [INFO] tool_use: [Tools] 工具 calculate_expression 返回: 17815.0
2026-03-12 15:19:23 [INFO] tool_use: [Agent] 调用 LLM(已绑定工具)...
2026-03-12 15:19:24 [INFO] tool_use: [Agent] 本轮输出: 最终回复(无 tool_calls): 苹果公司(AAPL)当前(模拟)股价为 **178.15 美元**。  
若您持有 100 股,则总市值约为 **17,815.00 美元**。
2026-03-12 15:19:24 [INFO] tool_use: [路由] 结束,返回最终回复。
回复: 苹果公司(AAPL)当前(模拟)股价为 **178.15 美元**。  
若您持有 100 股,则总市值约为 **17,815.00 美元**。

7 小结与延伸

  • 工具使用(Tool Use / Function Calling) 让智能体能够按需调用外部能力,获取实时数据、执行精确计算或触发外部系统,是构建「可感知、可推理、可行动」智能体的基础模式。
  • 流程上:工具定义 → 模型决策与生成 tool_calls → 框架执行工具 → 结果回传 → 模型再推理或回复,多步时可循环直至模型不再发起调用。
  • LangGraph 中:状态用带 add_messagesmessages 承载对话与工具结果;Agent 节点使用 bind_tools 绑定工具并产生可能带 tool_calls 的 AIMessage;条件边根据是否有 tool_calls 决定进入 ToolNodeENDToolNode 执行工具并追加 ToolMessage,再回到 Agent,结构清晰、易于扩展。
  • 工具描述(docstring + 参数)和系统提示中的「何时用哪个工具」会显著影响模型行为,建议写清用途与示例。
  • 若需多轮对话记忆,可引入 checkpointer(如 MemorySaver),让同一会话的多次 invoke 共享消息历史;若需更复杂的编排(如先路由再工具、多智能体分工),可在现有图上增加节点与条件边,或与 Reflection、Parallelization 等模式组合。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐