LangChain 工具调用(Tool Calling)实战指南

1. 引言

在构建大语言模型(LLM)应用时,模型本身无法执行实时查询、调用外部 API 或操作数据库。工具调用(Tool Calling / Function Calling)正是为了解决这一痛点而生的机制:它让模型在对话过程中识别出需要外部能力才能回答的问题,并结构化地输出一个"调用请求",由我们的应用代码去真正执行对应的函数,再把结果回传给模型,从而生成最终答案。

本文将基于 LangChain 0.3 系列,从零开始讲解工具调用的核心概念、定义方式、绑定与执行流程,并通过可运行的代码示例带你快速上手。

2. 什么是工具调用

工具调用是 LLM 的一种能力:模型在生成回复时,可以输出一个结构化的"工具调用指令",而不是直接给出最终文本。这个指令通常包含:

  • 工具名称(tool name)
  • 传给该工具的参数(arguments,JSON 格式)

应用侧拿到这个指令后,负责真正执行对应的函数,并把执行结果作为新的消息返回给模型,模型再基于结果继续推理,最终给出面向用户的回答。

需要特别注意的是:模型本身并不执行工具,它只负责"决定调用哪个工具、传什么参数"。真正的执行逻辑始终由我们的代码完成。

3. 环境准备

在开始之前,请确保你的环境中已安装 LangChain 及对应模型的依赖包:

bash 复制代码
pip install langchain langchain-openai

如果你使用 OpenAI 兼容接口(如国内大模型服务),通常还需要配置 API Key 和 Base URL:

python 复制代码
import os

os.environ["OPENAI_API_KEY"] = "your-api-key"
os.environ["OPENAI_BASE_URL"] = "https://your-endpoint/v1"

4. 定义一个工具

在 LangChain 中,最推荐的方式是使用 @tool 装饰器,把一个普通的 Python 函数声明为可供模型调用的工具。

python 复制代码
from langchain_core.tools import tool

@tool
def get_weather(city: str) -> str:
    """查询指定城市的当前天气。

    参数:
        city: 城市名称,例如 "北京"、"上海"。

    返回:
        该城市的天气描述字符串。
    """
    # 这里仅作演示,实际可替换为真实天气 API 调用
    return f"{city} 今天晴,气温 26℃,微风。"

要点说明:

  • 函数的**文档字符串(docstring)**会被 LangChain 自动提取,作为模型判断"何时调用该工具"的依据,务必写清楚用途和参数含义。
  • 函数的类型注解 (如 city: str)会被转换为工具的参数 schema,模型据此生成合法的参数 JSON。
  • 函数返回值会被作为工具执行结果回传给模型。

docstring 的写法与作用

docstring(文档字符串)是写在函数定义第一行的三引号字符串,它不仅是给开发者看的注释,更是 LangChain 生成工具描述的核心依据。模型正是通过 docstring 来理解"这个工具是做什么的、什么时候该调用它"。

一个规范的 docstring 通常包含三部分:

  • 功能描述:一句话说明工具的作用,例如"查询指定城市的当前天气"。
  • 参数说明:逐个列出参数的含义、类型和取值范围,帮助模型生成合法的参数 JSON。
  • 返回值说明:说明函数返回什么内容,让模型知道拿到结果后该如何使用。
python 复制代码
@tool
def get_weather(city: str) -> str:
    """查询指定城市的当前天气。

    参数:
        city: 城市名称,例如 "北京"、"上海"。

    返回:
        该城市的天气描述字符串。
    """
    return f"{city} 今天晴,气温 26℃,微风。"

docstring 的书写质量直接影响模型调用工具的准确率:

  • 描述越具体,模型判断越准 。如果只写"查询天气",模型可能不清楚该传什么参数;写清楚"城市名称,例如北京、上海"后,模型就能正确生成 {"city": "北京"} 这样的参数。
  • 参数说明要与类型注解一致。docstring 里说明的参数名必须和函数签名中的参数名完全一致,否则 LangChain 生成的 schema 会与 docstring 描述不一致,可能导致模型传错参数。
  • 避免歧义和冗余。docstring 应简洁明确,不要写与工具功能无关的内容,以免干扰模型的判断。

5. 绑定工具到模型

定义好工具后,需要把它"绑定"到聊天模型上,模型才会在推理时知道有这个工具可用。

python 复制代码
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

# 将工具绑定到模型
llm_with_tools = llm.bind_tools([get_weather])

bind_tools 接收一个工具列表,可以一次绑定多个工具。绑定后,模型在需要时就会输出工具调用指令。

6. 执行工具调用

下面是一个完整的调用流程示例:用户提问 → 模型决定调用工具 → 我们执行工具 → 把结果回传给模型 → 模型给出最终回答。

python 复制代码
from langchain_core.messages import HumanMessage, ToolMessage

# 第一步:用户提问
messages = [HumanMessage(content="北京今天天气怎么样?")]

# 第二步:模型决定调用工具
response = llm_with_tools.invoke(messages)
print("模型输出:", response)

# 第三步:检查是否有工具调用请求
if response.tool_calls:
    tool_call = response.tool_calls[0]
    print(f"需要调用工具: {tool_call['name']}")
    print(f"参数: {tool_call['args']}")

    # 第四步:真正执行工具函数
    tool_result = get_weather.invoke(tool_call["args"])

    # 第五步:把工具执行结果作为 ToolMessage 追加到消息列表
    messages.append(response)
    messages.append(ToolMessage(content=tool_result, tool_call_id=tool_call["id"]))

    # 第六步:让模型基于工具结果生成最终回答
    final_response = llm_with_tools.invoke(messages)
    print("最终回答:", final_response.content)

执行流程可以概括为下图:
#mermaid-svg-SUefrZvJzh9Vd8hv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SUefrZvJzh9Vd8hv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SUefrZvJzh9Vd8hv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SUefrZvJzh9Vd8hv .error-icon{fill:#552222;}#mermaid-svg-SUefrZvJzh9Vd8hv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SUefrZvJzh9Vd8hv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SUefrZvJzh9Vd8hv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SUefrZvJzh9Vd8hv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SUefrZvJzh9Vd8hv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SUefrZvJzh9Vd8hv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SUefrZvJzh9Vd8hv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SUefrZvJzh9Vd8hv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SUefrZvJzh9Vd8hv .marker.cross{stroke:#333333;}#mermaid-svg-SUefrZvJzh9Vd8hv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SUefrZvJzh9Vd8hv p{margin:0;}#mermaid-svg-SUefrZvJzh9Vd8hv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SUefrZvJzh9Vd8hv .cluster-label text{fill:#333;}#mermaid-svg-SUefrZvJzh9Vd8hv .cluster-label span{color:#333;}#mermaid-svg-SUefrZvJzh9Vd8hv .cluster-label span p{background-color:transparent;}#mermaid-svg-SUefrZvJzh9Vd8hv .label text,#mermaid-svg-SUefrZvJzh9Vd8hv span{fill:#333;color:#333;}#mermaid-svg-SUefrZvJzh9Vd8hv .node rect,#mermaid-svg-SUefrZvJzh9Vd8hv .node circle,#mermaid-svg-SUefrZvJzh9Vd8hv .node ellipse,#mermaid-svg-SUefrZvJzh9Vd8hv .node polygon,#mermaid-svg-SUefrZvJzh9Vd8hv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SUefrZvJzh9Vd8hv .rough-node .label text,#mermaid-svg-SUefrZvJzh9Vd8hv .node .label text,#mermaid-svg-SUefrZvJzh9Vd8hv .image-shape .label,#mermaid-svg-SUefrZvJzh9Vd8hv .icon-shape .label{text-anchor:middle;}#mermaid-svg-SUefrZvJzh9Vd8hv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-SUefrZvJzh9Vd8hv .rough-node .label,#mermaid-svg-SUefrZvJzh9Vd8hv .node .label,#mermaid-svg-SUefrZvJzh9Vd8hv .image-shape .label,#mermaid-svg-SUefrZvJzh9Vd8hv .icon-shape .label{text-align:center;}#mermaid-svg-SUefrZvJzh9Vd8hv .node.clickable{cursor:pointer;}#mermaid-svg-SUefrZvJzh9Vd8hv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-SUefrZvJzh9Vd8hv .arrowheadPath{fill:#333333;}#mermaid-svg-SUefrZvJzh9Vd8hv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SUefrZvJzh9Vd8hv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SUefrZvJzh9Vd8hv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SUefrZvJzh9Vd8hv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SUefrZvJzh9Vd8hv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SUefrZvJzh9Vd8hv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-SUefrZvJzh9Vd8hv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SUefrZvJzh9Vd8hv .cluster text{fill:#333;}#mermaid-svg-SUefrZvJzh9Vd8hv .cluster span{color:#333;}#mermaid-svg-SUefrZvJzh9Vd8hv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-SUefrZvJzh9Vd8hv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-SUefrZvJzh9Vd8hv rect.text{fill:none;stroke-width:0;}#mermaid-svg-SUefrZvJzh9Vd8hv .icon-shape,#mermaid-svg-SUefrZvJzh9Vd8hv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SUefrZvJzh9Vd8hv .icon-shape p,#mermaid-svg-SUefrZvJzh9Vd8hv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-SUefrZvJzh9Vd8hv .icon-shape .label rect,#mermaid-svg-SUefrZvJzh9Vd8hv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SUefrZvJzh9Vd8hv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-SUefrZvJzh9Vd8hv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-SUefrZvJzh9Vd8hv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否

用户提问
模型推理
是否需要调用工具?
直接返回回答
模型输出工具调用指令
应用代码执行工具函数
将结果回传给模型

调用流程:

7. 多工具与参数校验

实际项目中往往需要同时提供多个工具。LangChain 支持一次绑定多个工具,模型会根据问题自动选择合适的那个。

python 复制代码
@tool
def get_time(city: str) -> str:
    """查询指定城市的当前时间。

    参数:
        city: 城市名称。

    返回:
        该城市的当前时间字符串。
    """
    return f"{city} 当前时间为 14:30"

llm_with_tools = llm.bind_tools([get_weather, get_time])

此外,LangChain 会自动根据函数的类型注解和 docstring 生成 JSON Schema,并在调用时对模型生成的参数做校验。如果参数不合法,会抛出异常,便于我们及时发现并处理。

下面是一个更完整的示例:定义两个工具(查询天气、查询时间),绑定到模型,并演示模型如何根据问题自动选择工具、以及参数校验失败时的处理。

python 复制代码
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, ToolMessage

@tool
def get_weather(city: str) -> str:
    """查询指定城市的当前天气。

    参数:
        city: 城市名称,例如 "北京"、"上海"。

    返回:
        该城市的天气描述字符串。
    """
    return f"{city} 今天晴,气温 26℃,微风。"

@tool
def get_time(city: str) -> str:
    """查询指定城市的当前时间。

    参数:
        city: 城市名称。

    返回:
        该城市的当前时间字符串。
    """
    return f"{city} 当前时间为 14:30"

# 一次绑定多个工具
llm_with_tools = llm.bind_tools([get_weather, get_time])

# 模型会根据问题自动选择合适的工具
messages = [HumanMessage(content="北京现在几点了?")]
response = llm_with_tools.invoke(messages)

if response.tool_calls:
    tool_call = response.tool_calls[0]
    print(f"模型选择了工具: {tool_call['name']}")
    print(f"参数: {tool_call['args']}")

    # 根据工具名动态执行对应的函数
    tool_map = {"get_weather": get_weather, "get_time": get_time}
    selected_tool = tool_map[tool_call["name"]]
    tool_result = selected_tool.invoke(tool_call["args"])

    messages.append(response)
    messages.append(ToolMessage(content=tool_result, tool_call_id=tool_call["id"]))
    final_response = llm_with_tools.invoke(messages)
    print("最终回答:", final_response.content)

关于参数校验,LangChain 会根据函数的类型注解自动生成 JSON Schema。例如 get_weather(city: str) 会生成一个要求 city 为字符串的 schema。如果模型生成的参数不符合要求(例如缺少必填字段、类型错误),调用时会抛出异常:

python 复制代码
# 模拟参数校验失败:缺少必填参数 city
try:
    get_weather.invoke({})
except Exception as e:
    print("参数校验失败:", e)

通过这种方式,我们可以在开发阶段尽早发现模型生成的参数问题,从而及时修正 docstring 或类型注解,提升工具调用的稳定性。

8. 总结

本文介绍了 LangChain 工具调用的完整流程:

  • 使用 @tool 装饰器定义工具,docstring 和类型注解是模型理解工具的关键。
  • 通过 bind_tools 把工具绑定到模型。
  • 模型输出 tool_calls 后,由应用代码真正执行工具,并通过 ToolMessage 回传结果。
  • 支持一次绑定多个工具,模型会自动选择。

工具调用是构建 Agent、RAG 查询增强、自动化工作流等高级应用的基础能力。掌握它之后,你就可以让大模型"动手做事",而不仅仅是"动嘴说话"了。

相关推荐
QQ_216962909642 分钟前
【源码编号:project79475】SpringBoot校内二手交易平台:商品发布、分类检索、留言交流、订单管理全流程实战
java·spring boot·后端
软件聚导航1 小时前
「聚小软 AI 助手」技术升级:从数据库检索到 RAG 知识库问答
前端·数据库·mysql·微信小程序·小程序·ai编程·rag
恋猫de小郭2 小时前
看懂大模型架构术语,帮助你理解目前常见的大模型开源架构
前端·人工智能·ai编程
yma162 小时前
通过提示词实现GitHub Pages 和 Nginx 双部署竟然这么简单?
前端
前端 贾公子2 小时前
第09章:上下文与记忆 (2)
java·服务器·前端
可乐鸡翅yeah_2 小时前
第三方接口对接 M3U8 流媒体排坑实战,解决外部服务商流兼容难题
前端·javascript·python·django·html·m3u8·m3u8在线
2601_962203512 小时前
Java进阶07集合(续)
java·开发语言
GoppViper2 小时前
RDF资源描述框架深度解析:语义Web的数据基石与实战逻辑
前端·数据库
郑州光合科技余经理2 小时前
本地生活服务系统:模块边界与结算字段怎么拆
java·开发语言·前端·后端·系统架构·uni-app·php