【LangChain】6.聊天模型-工具调用

6. 聊天模型-工具调用

工具调用(Tool Calling / Function Calling)让大语言模型突破自身知识局限,与外部世界交互。


1. 工具调用概述

1.1 为什么需要工具调用?

大语言模型本身是一个封闭的知识系统,其能力受限于:

  • 训练数据滞后性------只能回复截止日期前的知识,无法获取实时信息
  • 内在文本生成逻辑------无法执行精确计算、查询数据库或调用外部 API

工具调用打破了这层壁垒,其核心作用:

作用 说明 示例
扩展能力边界 让模型完成自身无法完成的任务 数学计算、网络搜索、数据库查询
保证信息实时性 获取训练数据中不存在的最新信息 实时天气、股票价格、新闻动态
处理复杂任务 将复杂请求分解为多步骤,依次调用不同工具 "分析上月消费趋势" → 查数据 → 分析 → 生成图表
连接现有系统 将企业 API 和数据库封装成工具 自然语言驱动的统一接口

1.2 工具调用的三步流程

#mermaid-svg-QIFGmzHVv1CXxvPN{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-QIFGmzHVv1CXxvPN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QIFGmzHVv1CXxvPN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QIFGmzHVv1CXxvPN .error-icon{fill:#552222;}#mermaid-svg-QIFGmzHVv1CXxvPN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QIFGmzHVv1CXxvPN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QIFGmzHVv1CXxvPN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QIFGmzHVv1CXxvPN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QIFGmzHVv1CXxvPN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QIFGmzHVv1CXxvPN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QIFGmzHVv1CXxvPN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QIFGmzHVv1CXxvPN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QIFGmzHVv1CXxvPN .marker.cross{stroke:#333333;}#mermaid-svg-QIFGmzHVv1CXxvPN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QIFGmzHVv1CXxvPN p{margin:0;}#mermaid-svg-QIFGmzHVv1CXxvPN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-QIFGmzHVv1CXxvPN .cluster-label text{fill:#333;}#mermaid-svg-QIFGmzHVv1CXxvPN .cluster-label span{color:#333;}#mermaid-svg-QIFGmzHVv1CXxvPN .cluster-label span p{background-color:transparent;}#mermaid-svg-QIFGmzHVv1CXxvPN .label text,#mermaid-svg-QIFGmzHVv1CXxvPN span{fill:#333;color:#333;}#mermaid-svg-QIFGmzHVv1CXxvPN .node rect,#mermaid-svg-QIFGmzHVv1CXxvPN .node circle,#mermaid-svg-QIFGmzHVv1CXxvPN .node ellipse,#mermaid-svg-QIFGmzHVv1CXxvPN .node polygon,#mermaid-svg-QIFGmzHVv1CXxvPN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QIFGmzHVv1CXxvPN .rough-node .label text,#mermaid-svg-QIFGmzHVv1CXxvPN .node .label text,#mermaid-svg-QIFGmzHVv1CXxvPN .image-shape .label,#mermaid-svg-QIFGmzHVv1CXxvPN .icon-shape .label{text-anchor:middle;}#mermaid-svg-QIFGmzHVv1CXxvPN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QIFGmzHVv1CXxvPN .rough-node .label,#mermaid-svg-QIFGmzHVv1CXxvPN .node .label,#mermaid-svg-QIFGmzHVv1CXxvPN .image-shape .label,#mermaid-svg-QIFGmzHVv1CXxvPN .icon-shape .label{text-align:center;}#mermaid-svg-QIFGmzHVv1CXxvPN .node.clickable{cursor:pointer;}#mermaid-svg-QIFGmzHVv1CXxvPN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QIFGmzHVv1CXxvPN .arrowheadPath{fill:#333333;}#mermaid-svg-QIFGmzHVv1CXxvPN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QIFGmzHVv1CXxvPN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QIFGmzHVv1CXxvPN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QIFGmzHVv1CXxvPN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QIFGmzHVv1CXxvPN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QIFGmzHVv1CXxvPN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QIFGmzHVv1CXxvPN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QIFGmzHVv1CXxvPN .cluster text{fill:#333;}#mermaid-svg-QIFGmzHVv1CXxvPN .cluster span{color:#333;}#mermaid-svg-QIFGmzHVv1CXxvPN 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-QIFGmzHVv1CXxvPN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QIFGmzHVv1CXxvPN rect.text{fill:none;stroke-width:0;}#mermaid-svg-QIFGmzHVv1CXxvPN .icon-shape,#mermaid-svg-QIFGmzHVv1CXxvPN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QIFGmzHVv1CXxvPN .icon-shape p,#mermaid-svg-QIFGmzHVv1CXxvPN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QIFGmzHVv1CXxvPN .icon-shape .label rect,#mermaid-svg-QIFGmzHVv1CXxvPN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QIFGmzHVv1CXxvPN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QIFGmzHVv1CXxvPN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QIFGmzHVv1CXxvPN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 定义工具
绑定工具到模型
模型调用工具
返回结果

  1. 定义工具 ------用 @tool 装饰器将 Python 函数声明为工具
  2. 绑定工具------将工具绑定到聊天模型
  3. 调用工具------模型根据用户输入决定是否调用以及调用哪个工具

2. 使用 @tool 装饰器定义工具

2.1 基础用法

LangChain 提供了 @tool 装饰器,这是自定义工具最简单的方式:

python 复制代码
from langchain_core.tools import tool

@tool
def multiply(a: int, b: int) -> int:
    """Multiply two integers.

    Args:
        a: First integer
        b: Second integer
    """
    return a * b

@tool 装饰器自动将函数属性映射为工具属性:

函数要素 工具属性 说明
函数名 tool.name 工具的名称
文档字符串 tool.description 工具的用途描述
类型提示 + 文档参数 tool.args 工具的输入参数 Schema
python 复制代码
print(multiply.name)        # 输出: multiply
print(multiply.description)  # 输出: Multiply two integers. ...
print(multiply.args)         # 输出: {'a': {'title': 'A', 'type': 'integer'}, ...}

# 工具也是 Runnable 接口实例,可直接调用
result = multiply.invoke({"a": 2, "b": 3})
print(result)  # 输出: 6

Warning: 文档字符串是必需的

@tool 装饰的函数没有文档字符串,调用会报错:ValueError: Function must have a docstring if description not provided.

2.2 理解 Schema

Schema 是描述其他数据结构的声明格式,用于自动验证数据。以 JSON Schema 为例:

json 复制代码
{
    "type": "object",
    "properties": {
        "surname": { "type": "string" },
        "given_name": { "type": "string" },
        "birthday": { "type": "string", "format": "date" },
        "address": {
            "type": "object",
            "properties": {
                "district": { "type": "string" },
                "city": { "type": "string" },
                "province": { "type": "string" },
                "country": { "type": "string" }
            }
        }
    }
}

工具 Schema 同理------从函数名、类型提示和文档字符串中提取属性,声明工具的名称描述输入参数结构,供大模型理解如何调用该工具。

2.3 工具属性的作用

定义良好的工具属性,本质上是在为 LLM 编写提示词

  1. 工具名称name)→ 告诉 LLM 存在哪些可用工具
  2. 工具描述description)→ 告诉 LLM 每个工具的用途,决定调用哪个
  3. 工具参数args)→ 告诉 LLM 如何正确传参

文档字符串写得越清晰,LLM 调用工具的准确度越高。

2.4 Google 风格文档字符串

Google 风格是 Python 文档字符串的主流规范,LangChain 工具 Schema 默认解析此格式:

python 复制代码
def fetch_data(url: str, retries: int = 3) -> dict:
    """从给定的 URL 获取数据。

    Args:
        url (str): 要从中获取数据的 URL。
        retries (int, optional): 失败时重试的次数。默认为 3。

    Returns:
        dict: 从 URL 解析的 JSON 响应。
    """
    # ... 函数实现

3. @tool 的三种定义模式

3.1 模式一:基础模式(依赖文档字符串)

仅依赖 @tool + 函数文档字符串,由装饰器自动提取工具属性:

python 复制代码
@tool
def add(a: int, b: int) -> int:
    """两数相加

    Args:
        a: 第一个整数
        b: 第二个整数
    """
    return a + b

此模式要求:

  • 函数必须有文档字符串 (提取为 description
  • 参数必须有类型提示 (提取为 argstype
  • 建议使用 Google 风格文档提供参数描述

3.2 模式二:Pydantic 模式(依赖 args_schema)

当不希望文档字符串过长,或需要更精确的 Schema 控制时,可使用 Pydantic 类定义参数 Schema:

python 复制代码
from pydantic import BaseModel, Field
from langchain_core.tools import tool

class AddInput(BaseModel):
    """两数相加"""
    a: int = Field(..., description="第一个整数")
    b: int = Field(..., description="第二个整数")

@tool(args_schema=AddInput)
def add(a: int, b: int) -> int:
    return a + b

简单介绍一下pydanticPydantic 是 Python 生态中一个非常流行的数据验证和序列化库 。它的核心思想是:用 Python 类型注解来定义数据结构,然后在运行时自动校验数据是否符合定义

在这个类中有一些关键的组件:

组件 说明
BaseModel Pydantic 的基石类,继承它即可获得自动类型校验、序列化(.model_dump())、JSON Schema 导出(.model_json_schema())等能力
Field() 字段元数据函数,支持 description(字段描述)、default(默认值)、... / Ellipsis(必填标记)、ge/le(数值范围)、pattern(正则)等约束
类型系统 利用 Python 类型注解声明字段类型------strintfloatboollist[str]Optional[T]Literal[...]、嵌套 BaseModel
类型校验 构造实例时自动校验每个字段的类型和约束,不合法数据直接抛出 ValidationError,无需手写 if isinstance(...)
序列化 .model_dump()dict.model_dump_json() → JSON 字符串;最重要的 .model_json_schema() → JSON Schema → LangChain 用它注入提示词,告诉 LLM 工具参数格式

在本例中,这些组件协同工作的流程是:
#mermaid-svg-wuK93kGTNyj35Bn2{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-wuK93kGTNyj35Bn2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wuK93kGTNyj35Bn2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wuK93kGTNyj35Bn2 .error-icon{fill:#552222;}#mermaid-svg-wuK93kGTNyj35Bn2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wuK93kGTNyj35Bn2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wuK93kGTNyj35Bn2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wuK93kGTNyj35Bn2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wuK93kGTNyj35Bn2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wuK93kGTNyj35Bn2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wuK93kGTNyj35Bn2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wuK93kGTNyj35Bn2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wuK93kGTNyj35Bn2 .marker.cross{stroke:#333333;}#mermaid-svg-wuK93kGTNyj35Bn2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wuK93kGTNyj35Bn2 p{margin:0;}#mermaid-svg-wuK93kGTNyj35Bn2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wuK93kGTNyj35Bn2 .cluster-label text{fill:#333;}#mermaid-svg-wuK93kGTNyj35Bn2 .cluster-label span{color:#333;}#mermaid-svg-wuK93kGTNyj35Bn2 .cluster-label span p{background-color:transparent;}#mermaid-svg-wuK93kGTNyj35Bn2 .label text,#mermaid-svg-wuK93kGTNyj35Bn2 span{fill:#333;color:#333;}#mermaid-svg-wuK93kGTNyj35Bn2 .node rect,#mermaid-svg-wuK93kGTNyj35Bn2 .node circle,#mermaid-svg-wuK93kGTNyj35Bn2 .node ellipse,#mermaid-svg-wuK93kGTNyj35Bn2 .node polygon,#mermaid-svg-wuK93kGTNyj35Bn2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wuK93kGTNyj35Bn2 .rough-node .label text,#mermaid-svg-wuK93kGTNyj35Bn2 .node .label text,#mermaid-svg-wuK93kGTNyj35Bn2 .image-shape .label,#mermaid-svg-wuK93kGTNyj35Bn2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-wuK93kGTNyj35Bn2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wuK93kGTNyj35Bn2 .rough-node .label,#mermaid-svg-wuK93kGTNyj35Bn2 .node .label,#mermaid-svg-wuK93kGTNyj35Bn2 .image-shape .label,#mermaid-svg-wuK93kGTNyj35Bn2 .icon-shape .label{text-align:center;}#mermaid-svg-wuK93kGTNyj35Bn2 .node.clickable{cursor:pointer;}#mermaid-svg-wuK93kGTNyj35Bn2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wuK93kGTNyj35Bn2 .arrowheadPath{fill:#333333;}#mermaid-svg-wuK93kGTNyj35Bn2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wuK93kGTNyj35Bn2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wuK93kGTNyj35Bn2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wuK93kGTNyj35Bn2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wuK93kGTNyj35Bn2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wuK93kGTNyj35Bn2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wuK93kGTNyj35Bn2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wuK93kGTNyj35Bn2 .cluster text{fill:#333;}#mermaid-svg-wuK93kGTNyj35Bn2 .cluster span{color:#333;}#mermaid-svg-wuK93kGTNyj35Bn2 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-wuK93kGTNyj35Bn2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wuK93kGTNyj35Bn2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-wuK93kGTNyj35Bn2 .icon-shape,#mermaid-svg-wuK93kGTNyj35Bn2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wuK93kGTNyj35Bn2 .icon-shape p,#mermaid-svg-wuK93kGTNyj35Bn2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wuK93kGTNyj35Bn2 .icon-shape .label rect,#mermaid-svg-wuK93kGTNyj35Bn2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wuK93kGTNyj35Bn2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wuK93kGTNyj35Bn2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wuK93kGTNyj35Bn2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} BaseModel

定义数据结构
Field

附加描述与约束
类型注解

声明字段类型
model_json_schema()

自动生成 JSON Schema
@tool(args_schema=...)

注入为工具参数定义
LLM 根据 Schema

决定如何调用工具

逐行拆解:

代码 作用
class AddInput(BaseModel) 继承 Pydantic 的 BaseModel,声明这是一个数据模型类,自动获得类型验证能力
"""两数相加""" 类的文档字符串 → 充当 工具的描述tool.description),因为函数体没有文档字符串了
a: int Pydantic 字段的类型注解 → 声明 a 必须是整数,传字符串会报错
= Field(...) Pydantic 的 Field() 函数,给字段附加元信息
...(三个点) 表示该字段为必填 (Pydantic 中的 Ellipsis 字面量),没有默认值
description="第一个整数" 字段的描述 → 成为工具参数 Schema 中 adescription,LLM 借此理解参数含义
python 复制代码
@tool(args_schema=AddInput)   # 告诉 @tool:用 AddInput 代替函数签名来生成 Schema
def add(a: int, b: int) -> int:
    return a + b              # 函数体可以只关注逻辑,不需要文档字符串

用图示来看数据流动:
#mermaid-svg-SfarXW5q9Z2IryN6{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-SfarXW5q9Z2IryN6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SfarXW5q9Z2IryN6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SfarXW5q9Z2IryN6 .error-icon{fill:#552222;}#mermaid-svg-SfarXW5q9Z2IryN6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SfarXW5q9Z2IryN6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SfarXW5q9Z2IryN6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SfarXW5q9Z2IryN6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SfarXW5q9Z2IryN6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SfarXW5q9Z2IryN6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SfarXW5q9Z2IryN6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SfarXW5q9Z2IryN6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SfarXW5q9Z2IryN6 .marker.cross{stroke:#333333;}#mermaid-svg-SfarXW5q9Z2IryN6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SfarXW5q9Z2IryN6 p{margin:0;}#mermaid-svg-SfarXW5q9Z2IryN6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SfarXW5q9Z2IryN6 .cluster-label text{fill:#333;}#mermaid-svg-SfarXW5q9Z2IryN6 .cluster-label span{color:#333;}#mermaid-svg-SfarXW5q9Z2IryN6 .cluster-label span p{background-color:transparent;}#mermaid-svg-SfarXW5q9Z2IryN6 .label text,#mermaid-svg-SfarXW5q9Z2IryN6 span{fill:#333;color:#333;}#mermaid-svg-SfarXW5q9Z2IryN6 .node rect,#mermaid-svg-SfarXW5q9Z2IryN6 .node circle,#mermaid-svg-SfarXW5q9Z2IryN6 .node ellipse,#mermaid-svg-SfarXW5q9Z2IryN6 .node polygon,#mermaid-svg-SfarXW5q9Z2IryN6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SfarXW5q9Z2IryN6 .rough-node .label text,#mermaid-svg-SfarXW5q9Z2IryN6 .node .label text,#mermaid-svg-SfarXW5q9Z2IryN6 .image-shape .label,#mermaid-svg-SfarXW5q9Z2IryN6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-SfarXW5q9Z2IryN6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-SfarXW5q9Z2IryN6 .rough-node .label,#mermaid-svg-SfarXW5q9Z2IryN6 .node .label,#mermaid-svg-SfarXW5q9Z2IryN6 .image-shape .label,#mermaid-svg-SfarXW5q9Z2IryN6 .icon-shape .label{text-align:center;}#mermaid-svg-SfarXW5q9Z2IryN6 .node.clickable{cursor:pointer;}#mermaid-svg-SfarXW5q9Z2IryN6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-SfarXW5q9Z2IryN6 .arrowheadPath{fill:#333333;}#mermaid-svg-SfarXW5q9Z2IryN6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SfarXW5q9Z2IryN6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SfarXW5q9Z2IryN6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SfarXW5q9Z2IryN6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SfarXW5q9Z2IryN6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SfarXW5q9Z2IryN6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-SfarXW5q9Z2IryN6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SfarXW5q9Z2IryN6 .cluster text{fill:#333;}#mermaid-svg-SfarXW5q9Z2IryN6 .cluster span{color:#333;}#mermaid-svg-SfarXW5q9Z2IryN6 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-SfarXW5q9Z2IryN6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-SfarXW5q9Z2IryN6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-SfarXW5q9Z2IryN6 .icon-shape,#mermaid-svg-SfarXW5q9Z2IryN6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SfarXW5q9Z2IryN6 .icon-shape p,#mermaid-svg-SfarXW5q9Z2IryN6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-SfarXW5q9Z2IryN6 .icon-shape .label rect,#mermaid-svg-SfarXW5q9Z2IryN6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SfarXW5q9Z2IryN6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-SfarXW5q9Z2IryN6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-SfarXW5q9Z2IryN6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} AddInput 类

文档串 : 工具描述

Field() : 各参数描述
@tool 提取 Schema
LLM 看到:

add(a: 第一个整数, b: 第二个整数)

描述:两数相加
LLM 知道何时调用以及怎么传参

关键点:

  • args_schema 参数指定输入参数的 Pydantic 模型
  • 函数体可以省略文档字符串(通过 Pydantic 类的文档字符串提供工具描述)
  • Field(..., description=...) 中的 ... 表示该字段为必填(无默认值),比如上面的ab都有...说明a,b必填
  • Pydantic 在运行时提供数据类型验证

3.3 模式三:Annotated 模式(依赖类型注解)

最简洁的方式------直接在类型注解中嵌入参数描述:

python 复制代码
from langchain_core.tools import tool
from typing_extensions import Annotated

@tool
def add(
    a: Annotated[int, ..., "第一个整数"],
    b: Annotated[int, ..., "第二个整数"],
) -> int:
    """两数相加

    Args:
        a: 第一个整数
        b: 第二个整数
    """
    return a + b

Annotated 是 Python 的类型注解扩展,格式为 Annotated[类型, 默认值, 描述],LangChain 会自动提取其中的描述信息填充到工具参数的 description 字段。

三种模式生成的工具效果相同,选择哪种取决于编码风格偏好和参数复杂度。


4. 使用 StructuredTool 类定义工具

除了 @tool 装饰器,LangChain 还提供了 StructuredTool 类的 from_function() 类方法来创建工具。这种方式更加灵活,适合在工具需要更多配置时使用。

4.1 from_function() 基础用法

StructuredTool.from_function() 的核心参数:

参数 说明
func 要包装为工具的 Python 函数
name 工具名称,默认为函数名
description 工具描述,默认为函数文档字符串
args_schema 输入参数的 Pydantic Schema,默认为 None
response_format 响应格式,"content""content_and_artifact"
parse_docstring 是否解析文档字符串,默认 False

最基本的用法是将一个普通 Python 函数包装为工具:

python 复制代码
from langchain_core.tools import StructuredTool

def multiply(a: int, b: int) -> int:
    """Multiply two numbers."""
    return a * b

calculator_tool = StructuredTool.from_function(func=multiply)
print(calculator_tool.invoke({"a": 2, "b": 3}))  # 输出: 6

使用 from_function() 同样要求函数具备:

  • 函数名tool.name
  • 文档字符串tool.description
  • 类型提示 + 文档参数tool.args

这三个属性是工具 Schema 的核心组成,缺一不可。

4.2 自定义名称、描述与参数

可以通过参数显式指定工具的名称、描述和输入参数 Schema:

python 复制代码
from langchain_core.tools import StructuredTool
from pydantic import BaseModel, Field

class CalculatorInput(BaseModel):
    a: int = Field(description="first number")
    b: int = Field(description="second number")

def multiply(a: int, b: int) -> int:
    return a * b

calculator_tool = StructuredTool.from_function(
    func=multiply,
    name="calculator",           # 自定义工具名
    description="两数相乘",       # 自定义工具描述
    args_schema=CalculatorInput, # 自定义参数 Schema
)

print(calculator_tool.name)        # 输出: calculator
print(calculator_tool.description) # 输出: 两数相乘
print(calculator_tool.args)        # 输出参数 Schema(含 description)

Tip: @tool vs StructuredTool

@tool 装饰器本质上是 StructuredTool 的语法糖。选择哪种方式取决于编码风格------装饰器更简洁,StructuredTool.from_function() 在需要动态创建工具或更多配置项时更灵活。

4.3 响应格式:content_and_artifact

默认情况下,工具的输出是一个 ToolMessagecontent 属性。但在某些场景下,我们希望区分:

  • content ------ 给 LLM 阅读的结构化文本(简洁、精炼)
  • artifact ------ 给后续组件使用的原始数据(完整、详细)

例如,一个天气查询工具:

  • content:"今天北京晴,气温 25°C~32°C"
  • artifact:搜索引擎 API 返回的完整 JSON 响应(含多条搜索结果、链接、摘要等)

这样设计的优势:

  1. 调试与追溯 ------ content 不符合预期时,可通过 artifact 分析是工具解析问题还是 API 问题
  2. 来源可查 ------ 需要引用具体链接或来源时,artifact 保留了完整数据
  3. 数据可复用 ------ 后续 Chain 中的组件可读取 artifact 做进一步分析

启用方式是在 from_function() 中设置 response_format="content_and_artifact",并让工具函数返回 (content, artifact) 元组:

python 复制代码
from langchain_core.tools import StructuredTool
from pydantic import BaseModel, Field
from typing import Tuple, List

class CalculatorInput(BaseModel):
    a: int = Field(description="first number")
    b: int = Field(description="second number")

def multiply(a: int, b: int) -> Tuple[str, List[int]]:
    nums = [a, b]
    content = f"{nums}相乘的结果是{a * b}"
    return content, nums

对于函数multiply定义的核心是 返回一个 (content, artifact) 元组,背后是两个不同的消费对象:

返回部分 类型 给谁看 内容特点
content str LLM 阅读,生成最终回复 简洁、自然语言,如 "[2, 3]相乘的结果是6"
artifact List[int] 开发者/下游组件 使用 原始、完整,如 [2, 3],供调试或后续处理

再看具体的代码行:

python 复制代码
nums = [a, b]                          # 保留原始输入,作为 artifact
content = f"{nums}相乘的结果是{a * b}"  # 组装 LLM 易读的文本
return content, nums                   # 返回 (给LLM看的, 给开发者留的)

函数签名 -> Tuple[str, List[int]] 明确声明了返回的就是这样一个双用途元组 。当 StructuredTool.from_function(response_format="content_and_artifact") 检测到这个模式时,会自动将元组的第一个元素作为 ToolMessage.content,第二个元素放入 ToolMessage.artifact,两者互不干扰。

然后是绑定工具的代码,与普通用法一致:

python 复制代码
calculator_tool = StructuredTool.from_function(
    func=multiply,
    name="Calculator",
    description="两数相乘",
    args_schema=CalculatorInput,
    response_format="content_and_artifact"
)

直接调用工具时,只会返回 content 部分:

python 复制代码
print(calculator_tool.invoke({"a": 2, "b": 3}))
# 输出: [2, 3]相乘的结果是6

若需同时看到 content 和 artifact,需要模拟 LLM 的调用姿态(传入 nameargstypeid):

python 复制代码
result = calculator_tool.invoke({
    "name": "Calculator",
    "args": {"a": 2, "b": 3},
    "type": "tool_call",    # 必填,表示这是一个工具调用请求
    "id": "123",            # 必填,关联请求与结果
})
print(result)
# ToolMessage(content='[2, 3]相乘的结果是6', artifact=[2, 3], ...)

可以看到,返回的 ToolMessagecontent 供 LLM 阅读理解,artifact 则保留原始数据供后续组件或人工分析使用。

Note: @tool 也支持 response_format

@tool 装饰器同样接受 response_format 参数,用法与 StructuredTool.from_function() 一致。


5. 绑定工具到聊天模型

工具定义完成后,需要通过 bind_tools() 方法将工具绑定到聊天模型。

5.1 bind_tools() 方法

python 复制代码
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from typing_extensions import Annotated

@tool
def add(a: Annotated[int, ..., "第一个整数"],
        b: Annotated[int, ..., "第二个整数"]) -> int:
    """两数相加"""
    return a + b

@tool
def multiply(a: Annotated[int, ..., "第一个整数"],
             b: Annotated[int, ..., "第二个整数"]) -> int:
    """两数相乘"""
    return a * b

model = ChatOpenAI(model="gpt-4o-mini")
model_with_tools = model.bind_tools(tools=[add, multiply])

bind_tools() 返回一个新的 Runnable 实例(而非修改原模型),支持与 invoke() 链式调用。其重要参数:

参数 说明
tools 工具定义列表,支持 BaseTool、Pydantic 类、Python 函数、字典
tool_choice 控制工具选择行为
strict 是否保证模型输出与工具 Schema 完全匹配
parallel_tool_calls 是否允许并行工具调用(默认启用)

5.2 tool_choice 参数

tool_choice 控制模型在何时调用工具:

取值 行为
"auto"(默认) 模型根据语义自动决定是否调用工具
"none" 不调用任何工具
"any""required"True 强制调用至少一个工具
"<tool_name>" 强制调用指定名称的工具
FalseNone 无效果,使用模型默认行为

自动选择(默认)------模型根据问题语义判断是否需要调用工具:

python 复制代码
# 与计算相关 → 会调用工具
result = model_with_tools.invoke("2乘3等于多少?")
# AIMessage 中包含 tool_calls 信息

# 与计算无关 → 不会调用工具
result = model_with_tools.invoke("你是谁?")
# AIMessage.content 直接返回文本回答

强制选择------无论是否相关,都要求模型调用工具:

python 复制代码
model_forced = model.bind_tools(tools=[add, multiply], tool_choice="any")
result = model_forced.invoke("你是谁?")
# 即使问题与工具无关,模型也会强制选择一个工具调用

6. 完整工具调用流程

工具调用的完整流程分为以下步骤:
#mermaid-svg-wBp83afbxdw22fSR{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-wBp83afbxdw22fSR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wBp83afbxdw22fSR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wBp83afbxdw22fSR .error-icon{fill:#552222;}#mermaid-svg-wBp83afbxdw22fSR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wBp83afbxdw22fSR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wBp83afbxdw22fSR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wBp83afbxdw22fSR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wBp83afbxdw22fSR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wBp83afbxdw22fSR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wBp83afbxdw22fSR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wBp83afbxdw22fSR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wBp83afbxdw22fSR .marker.cross{stroke:#333333;}#mermaid-svg-wBp83afbxdw22fSR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wBp83afbxdw22fSR p{margin:0;}#mermaid-svg-wBp83afbxdw22fSR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wBp83afbxdw22fSR .cluster-label text{fill:#333;}#mermaid-svg-wBp83afbxdw22fSR .cluster-label span{color:#333;}#mermaid-svg-wBp83afbxdw22fSR .cluster-label span p{background-color:transparent;}#mermaid-svg-wBp83afbxdw22fSR .label text,#mermaid-svg-wBp83afbxdw22fSR span{fill:#333;color:#333;}#mermaid-svg-wBp83afbxdw22fSR .node rect,#mermaid-svg-wBp83afbxdw22fSR .node circle,#mermaid-svg-wBp83afbxdw22fSR .node ellipse,#mermaid-svg-wBp83afbxdw22fSR .node polygon,#mermaid-svg-wBp83afbxdw22fSR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wBp83afbxdw22fSR .rough-node .label text,#mermaid-svg-wBp83afbxdw22fSR .node .label text,#mermaid-svg-wBp83afbxdw22fSR .image-shape .label,#mermaid-svg-wBp83afbxdw22fSR .icon-shape .label{text-anchor:middle;}#mermaid-svg-wBp83afbxdw22fSR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wBp83afbxdw22fSR .rough-node .label,#mermaid-svg-wBp83afbxdw22fSR .node .label,#mermaid-svg-wBp83afbxdw22fSR .image-shape .label,#mermaid-svg-wBp83afbxdw22fSR .icon-shape .label{text-align:center;}#mermaid-svg-wBp83afbxdw22fSR .node.clickable{cursor:pointer;}#mermaid-svg-wBp83afbxdw22fSR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wBp83afbxdw22fSR .arrowheadPath{fill:#333333;}#mermaid-svg-wBp83afbxdw22fSR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wBp83afbxdw22fSR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wBp83afbxdw22fSR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wBp83afbxdw22fSR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wBp83afbxdw22fSR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wBp83afbxdw22fSR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wBp83afbxdw22fSR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wBp83afbxdw22fSR .cluster text{fill:#333;}#mermaid-svg-wBp83afbxdw22fSR .cluster span{color:#333;}#mermaid-svg-wBp83afbxdw22fSR 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-wBp83afbxdw22fSR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wBp83afbxdw22fSR rect.text{fill:none;stroke-width:0;}#mermaid-svg-wBp83afbxdw22fSR .icon-shape,#mermaid-svg-wBp83afbxdw22fSR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wBp83afbxdw22fSR .icon-shape p,#mermaid-svg-wBp83afbxdw22fSR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wBp83afbxdw22fSR .icon-shape .label rect,#mermaid-svg-wBp83afbxdw22fSR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wBp83afbxdw22fSR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wBp83afbxdw22fSR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wBp83afbxdw22fSR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户问题
调用绑定工具的模型
AIMessage 含 tool_calls
解析 tool_calls
根据 name 匹配实际工具
调用工具生成 ToolMessage
组装消息列表
再次调用模型
最终自然语言回答

6.1 获取工具选择结果

当模型决定调用工具时,返回的 AIMessage 中会包含 tool_calls 字段:

python 复制代码
ai_msg = model_with_tools.invoke("2乘3等于多少?6加6等于多少?")
print(ai_msg.tool_calls)
# [
#   {'name': 'multiply', 'args': {'a': 2, 'b': 3}, 'id': 'call_xxx', 'type': 'tool_call'},
#   {'name': 'add', 'args': {'a': 6, 'b': 6}, 'id': 'call_yyy', 'type': 'tool_call'}
# ]

调用 model_with_tools.invoke 之后返回的 AIMessage 中,tool_calls 字段描述了大语言模型调用工具的情况

每个 tool_call 包含:

  • name ------ 要调用的工具名称
  • args ------ 工具参数
  • id ------ 工具调用标识符,用于关联请求与结果
  • type ------ 固定为 "tool_call"

6.2 构造 ToolMessage

模型可能一次请求就触发多个工具调用(比如"2乘3等于多少?6加6等于多少?"会同时触发 multiplyadd),因此需要用 循环 逐个执行:

根据 tool_calls 中的信息,调用对应的实际工具,并将结果包装为 ToolMessage

python 复制代码
# 名称到工具的映射字典
tool_map = {"add": add, "multiply": multiply}

for tool_call in ai_msg.tool_calls:                      # 遍历每个工具调用
    selected_tool = tool_map[tool_call["name"].lower()]  # 按名称找到实际工具函数
    tool_msg = selected_tool.invoke(tool_call)           # 执行工具,拿到结果
    message.append(tool_msg)                             # 把结果追加到消息列表
    # tool_msg 是 ToolMessage 类型
    # - content: 工具执行结果(如 "6")
    # - name: 工具名称
    # - tool_call_id: 与请求 id 对应

逐条拆解:

代码 作用
for tool_call in ai_msg.tool_calls 遍历 tool_calls 列表(可能有多条),每次取出一条工具调用
tool_map[tool_call["name"].lower()] 从映射字典中,根据工具名称(如 "multiply")找到对应的实际 Python 函数
selected_tool.invoke(tool_call) 真正执行工具 ------把 tool_call(含 nameargsid)传给函数,得到 ToolMessage 结果
message.append(tool_msg) 将执行结果追加到消息列表,后续模型需要看到所有 ToolMessage 才能给出最终回答

6.3 消息列表与模型最终回复

将整个对话过程组装为消息列表,重新发送给模型,让模型给出自然语言回答:

python 复制代码
from langchain_core.messages import HumanMessage

# 1. 用户问题 -> HumanMessage
message = [HumanMessage("2乘3等于多少?6加6等于多少?")]

# 2. 模型选择 -> AIMessage(含 tool_calls)
ai_msg = model_with_tools.invoke(message)
message.append(ai_msg)

# 3. 执行工具 -> ToolMessage
for tool_call in ai_msg.tool_calls:
    selected_tool = tool_map[tool_call["name"].lower()]
    tool_msg = selected_tool.invoke(tool_call)
    message.append(tool_msg)

# 4. 最终回复 -> 模型总结生成自然语言
final_answer = model_with_tools.invoke(message)
print(final_answer.content)
# 输出: "2乘3等于6,6加6等于12。"

Question: 为什么模型不直接调用工具?

这是 LangChain 的一个核心设计原则模型只负责"思考",不负责"执行"

原因有三:

  1. 安全------如果模型能直接执行代码,它就可以删除文件、发送邮件、调用付费 API。把执行权交给你,你就能控制实际发生的操作。
  2. 模型本质上不能执行 ------LLM 是一个文本预测引擎,它没有权限访问你的文件系统、数据库或网络。它只能告诉你它想调用什么工具、传什么参数,由你的代码去落地执行。
  3. 可观测性 ------把"模型选择"和"工具执行"拆成两步,你可以在中间插入日志、调试、权限检查。如果模型一步到位,你就完全失控了。
    可以理解为:模型是下指令的将军 ,你是执行命令的士兵------将军说"调用 multiply,参数是 (2,3)",你去算出 6,把结果报告给将军,他再根据结果做下一步决策。

6.4 完整代码示例

python 复制代码
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from typing_extensions import Annotated

# 1. 定义工具
@tool
def add(a: Annotated[int, ..., "第一个整数"],
        b: Annotated[int, ..., "第二个整数"]) -> int:
    """两数相加"""
    return a + b

@tool
def multiply(a: Annotated[int, ..., "第一个整数"],
             b: Annotated[int, ..., "第二个整数"]) -> int:
    """两数相乘"""
    return a * b

# 2. 绑定工具
model = ChatOpenAI(model="gpt-4o-mini")
model_with_tools = model.bind_tools(tools=[add, multiply])

# 3. 准备消息并调用
message = [HumanMessage("2乘3等于多少?6加6等于多少?")]
ai_msg = model_with_tools.invoke(message)
message.append(ai_msg)

# 4. 执行工具
tool_map = {"add": add, "multiply": multiply}
for tool_call in ai_msg.tool_calls:
    selected_tool = tool_map[tool_call["name"].lower()]
    tool_msg = selected_tool.invoke(tool_call)
    message.append(tool_msg)

# 5. 获取最终回答
final = model_with_tools.invoke(message)
print(final.content)
# 输出: "2乘3等于6,6加6等于12。"

Warning: 注意 tool_choice 的影响

如果在绑定工具时设置了 tool_choice="any" 强制调用工具,在最后一步(将消息列表发给模型生成最终回答)时,模型可能仍会强制调用工具而非给出自然语言回复。因此,最终回答步骤应使用未强制绑定 tool_choice 的模型实例,或单独创建一个不带 tool_choice 限制的绑定。


总结

本节完整学习了 LangChain 工具调用的全流程:

  1. 工具调用的价值------突破 LLM 封闭知识系统,实现实时查询、数据处理和系统集成
  2. 工具的三要素 ------名称(name)、描述(description)、参数(args)构成工具 Schema
  3. @tool 装饰器------三种模式(基础 / Pydantic / Annotated)将 Python 函数转换为工具
  4. StructuredTool.from_function() ------在不使用装饰器时创建工具的另一种方式,支持自定义配置
  5. response_format="content_and_artifact" ------分离 LLM 可读的 content 和留给后续组件的 artifact
  6. bind_tools() ------将工具绑定到聊天模型,通过 tool_choice 控制调用策略
  7. 完整调用流程------模型选择工具 → 执行工具 → 组装消息列表 → 模型生成最终回答
相关推荐
爱昏羔13 小时前
下篇:从检索到交互 — 物流RAG问答系统与WebUI全实现
python·langchain·agent
_Jimmy_13 小时前
Agent引用数据库知识过时的增量同步方案
人工智能·python·langchain
爱昏羔1 天前
上篇:从PDF到向量库 — 物流行业RAG系统的知识库构建全解析
python·langchain·pdf·agent·rag
可涵不会debug1 天前
【LangChain系列】 零基础入门实战:从环境搭建到 LCEL 链式完整 Demo 详解
运维·服务器·langchain·vibe coding
phltxy1 天前
LangChain_Agent中间件实战
人工智能·python·深度学习·语言模型·中间件·langchain
xian_wwq1 天前
【学习笔记】框架层坍缩——LangChain 们正在被重新定义-14/15
笔记·学习·langchain
梦想三三1 天前
LangChain模型调用与多轮对话完整实战
阿里云·langchain·大模型·api
mapengfei1 天前
LangChain学习背景
langchain·llm
chaors2 天前
DeepResearchSystem 0x02:Graph 构建
langchain·openai·ai编程