解锁聊天模型的隐藏能力:工具调用全指南

就算无人问津也好,技不如人也好,千万别让烦躁和焦虑毀了你本就不多的热情和定力。 别贪心,我们不可能什么都有,我们不可能什么都没有。

LangChain 实现了用于定义工具、将工具传递给 LLM 以及表示工具调用的标准接口。本文将介绍如何将工具绑定到 LLM,然后调用 LLM 以生成这些参数,以及将工具结果传递回模型。

一、定义工具模式

为了使模型能够调用工具,我们需要传入工具模式,描述工具的功能及其参数。支持工具调用功能的聊天模型实现了 .bind_tools() 方法,用于将工具模式传递给模型。工具模式可以是: Python 函数(带有类型提示和文档字符串)、Pydantic 模型TypedDict 类LangChain Tool 对象。后续的模型调用会将这些工具模式与提示一起传入。

1、Python 函数

python 复制代码
# The function name, type hints, and docstring are all part of the tool
# schema that's passed to the model. Defining good, descriptive schemas
# is an extension of prompt engineering and is an important part of
# getting models to perform well.
def add(a: int, b: int) -> int:
    """Add two integers.

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


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

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

2、LangChain 工具

LangChain 还实现了一个 @tool 装饰器,允许进一步控制工具模式,例如工具名称和参数描述。

python 复制代码
from typing import List

from langchain_core.tools import InjectedToolArg, tool
from typing_extensions import Annotated

@tool("multiply_by_max")
def multiply_by_max(
    a: Annotated[int, "scale factor"],
    b: Annotated[List[int], "list of ints over which to take maximum"],
) -> int:
    """Multiply a by the maximum of b."""
    return a * max(b)


print(multiply_by_max.args_schema.model_json_schema())
css 复制代码
{'description': 'Multiply a by the maximum of b.', 'properties': {'a': {'description': 'scale factor', 'title': 'A', 'type': 'integer'}, 'b': {'description': 'list of ints over which to take maximum', 'items': {'type': 'integer'}, 'title': 'B', 'type': 'array'}}, 'required': ['a', 'b'], 'title': 'multiply_by_max', 'type': 'object'}

3、Pydantic 类

使用 Pydantic 等效地定义没有伴随函数的模式。

请注意,除非提供默认值,否则所有字段都是 required

python 复制代码
from pydantic import BaseModel, Field


class add(BaseModel):
    """Add two integers."""

    a: int = Field(..., description="First integer")
    b: int = Field(..., description="Second integer")


class multiply(BaseModel):
    """Multiply two integers."""

    a: int = Field(..., description="First integer")
    b: int = Field(..., description="Second integer")

4、TypedDict 类

使用 TypedDicts 和注解

需要 langchain-core>=0.2.25

python 复制代码
from typing_extensions import Annotated, TypedDict


class add(TypedDict):
    """Add two integers."""

    # Annotations must have the type and can optionally include a default value and description (in that order).
    a: Annotated[int, ..., "First integer"]
    b: Annotated[int, ..., "Second integer"]


class multiply(TypedDict):
    """Multiply two integers."""

    a: Annotated[int, ..., "First integer"]
    b: Annotated[int, ..., "Second integer"]

要实际将这些模式绑定到聊天模型,我们将使用 .bind_tools() 方法。这将处理将 addmultiply 模式转换为模型所需的正确格式。然后,每次调用模型时都会传入工具模式。

python 复制代码
pip install -qU "langchain[openai]"
python 复制代码
from pydantic import SecretStr
import os

os.environ["OPENAI_BASE_URL"] = "https://api.siliconflow.cn/v1/"
os.environ["OPENAI_API_KEY"] = "sk-xxx"

from langchain.chat_models import init_chat_model

llm = init_chat_model("Qwen/Qwen3-8B", model_provider="openai")
python 复制代码
tools = [add, multiply]

llm_with_tools = llm.bind_tools(tools)

query = "What is 3 * 12?"

llm_with_tools.invoke(query)
python 复制代码
AIMessage(content='\n\n', additional_kwargs={'tool_calls': [{'id': '01973f7fdcfbed8c2dc9f064923e99df', 'function': {'arguments': ' {"a": 3, "b": 12}', 'name': 'multiply'}, 'type': 'function', 'index': 0}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 113, 'prompt_tokens': 263, 'total_tokens': 376, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 98, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': None}, 'model_name': 'Qwen/Qwen3-8B', 'system_fingerprint': '', 'id': '01973f7fc80ee8b94699261784ba7f78', 'service_tier': None, 'finish_reason': 'tool_calls', 'logprobs': None}, id='run--93e4cc48-7caf-46c3-bd8b-8d54949449cf-0', tool_calls=[{'name': 'multiply', 'args': {'a': 3, 'b': 12}, 'id': '01973f7fdcfbed8c2dc9f064923e99df', 'type': 'tool_call'}], usage_metadata={'input_tokens': 263, 'output_tokens': 113, 'total_tokens': 376, 'input_token_details': {}, 'output_token_details': {'reasoning': 98}})

从输出可见,LLM 生成了工具的参数!

二、工具调用

如果在 LLM 响应中包含了工具调用,则它们将作为 messagemessage chunk 的一部分附加到对应的 消息消息块 中,作为 .tool_calls 属性中的 工具调用 对象列表。

请注意,聊天模型可以一次调用多个工具。

ToolCall 是一个类型化字典,包括工具名称、参数值字典和(可选)标识符。没有工具调用的消息的此属性默认为空列表。

python 复制代码
query = "What is 3 * 12? Also, what is 11 + 49?"

llm_with_tools.invoke(query).tool_calls
css 复制代码
[{'name': 'multiply',  'args': {'a': 3, 'b': 12},  'id': '01973f80578e9e4d2c3a6363becbcfd2',  'type': 'tool_call'}, {'name': 'add',  'args': {'a': 11, 'b': 49},  'id': '01973f805ab59392fcd7e939dffaa615',  'type': 'tool_call'}]

.tool_calls 属性应包含有效的工具调用。请注意,有时模型提供商可能会输出格式错误的工具调用(例如,不是有效 JSON 的参数)。当在这些情况下解析失败时,InvalidToolCall 的实例将填充在 .invalid_tool_calls 属性中。InvalidToolCall 可以具有名称、字符串参数、标识符和错误消息。

三、解析

如果需要,输出解析器 可以进一步处理输出。例如,我们可以使用 PydanticToolsParser.tool_calls 上填充的现有值转换为 Pydantic 对象

python 复制代码
from langchain_core.output_parsers import PydanticToolsParser
from pydantic import BaseModel, Field


class add(BaseModel):
    """Add two integers."""

    a: int = Field(..., description="First integer")
    b: int = Field(..., description="Second integer")


class multiply(BaseModel):
    """Multiply two integers."""

    a: int = Field(..., description="First integer")
    b: int = Field(..., description="Second integer")


chain = llm_with_tools | PydanticToolsParser(tools=[add, multiply])
chain.invoke(query)
csharp 复制代码
[multiply(a=3, b=12), add(a=11, b=49)]

四、将工具结果传递回模型

一些模型能够进行 工具调用 - 生成符合特定用户提供的模式的参数。本指南将演示如何使用这些工具调用来实际调用函数,并将结果正确传递回模型。

定义工具:

python 复制代码
from langchain_core.tools import tool


@tool
def add(a: int, b: int) -> int:
    """Adds a and b."""
    return a + b


@tool
def multiply(a: int, b: int) -> int:
    """Multiplies a and b."""
    return a * b


tools = [add, multiply]

llm_with_tools = llm.bind_tools(tools)

现在,让我们让模型调用一个工具。我们将其添加到消息列表中,我们将其视为对话历史记录

python 复制代码
from langchain_core.messages import HumanMessage

query = "What is 3 * 12? Also, what is 11 + 49?"

messages = [HumanMessage(query)]

ai_msg = llm_with_tools.invoke(messages)

print(ai_msg.tool_calls)

messages.append(ai_msg)
css 复制代码
[{'name': 'multiply', 'args': {'a': 3, 'b': 12}, 'id': '01973f810322941900f35c0e2504e4b6', 'type': 'tool_call'}, {'name': 'add', 'args': {'a': 11, 'b': 49}, 'id': '01973f810925180e894dce7520408055', 'type': 'tool_call'}]

接下来,让我们使用模型填充的参数来调用工具函数!方便的是,如果我们使用 ToolCall 调用 LangChain Tool,我们将自动获得一个可以反馈给模型的 ToolMessage

兼容性问题

  • 此功能在 langchain-core == 0.2.19 中添加。
  • 如果使用的是早期版本的 langchain-core,则需要从工具中提取 args 字段并手动构建 ToolMessage。
python 复制代码
for tool_call in ai_msg.tool_calls:
    selected_tool = {"add": add, "multiply": multiply}[tool_call["name"].lower()]
    tool_msg = selected_tool.invoke(tool_call)
    messages.append(tool_msg)

messages
python 复制代码
[HumanMessage(content='What is 3 * 12? Also, what is 11 + 49?', additional_kwargs={}, response_metadata={}),
 AIMessage(content='\n\n', additional_kwargs={'tool_calls': [{'id': '01973f810322941900f35c0e2504e4b6', 'function': {'arguments': ' {"a": 3, "b": 12}', 'name': 'multiply'}, 'type': 'function', 'index': 0}, {'id': '01973f810925180e894dce7520408055', 'function': {'arguments': ' {"a": 11, "b": 49}', 'name': 'add'}, 'type': 'function', 'index': 1}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 283, 'prompt_tokens': 250, 'total_tokens': 533, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 253, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': None}, 'model_name': 'Qwen/Qwen3-8B', 'system_fingerprint': '', 'id': '01973f80c69dd034cd5aa95ad1bc6f54', 'service_tier': None, 'finish_reason': 'tool_calls', 'logprobs': None}, id='run--94a101a3-abe6-4444-9485-7543da920ee5-0', tool_calls=[{'name': 'multiply', 'args': {'a': 3, 'b': 12}, 'id': '01973f810322941900f35c0e2504e4b6', 'type': 'tool_call'}, {'name': 'add', 'args': {'a': 11, 'b': 49}, 'id': '01973f810925180e894dce7520408055', 'type': 'tool_call'}], usage_metadata={'input_tokens': 250, 'output_tokens': 283, 'total_tokens': 533, 'input_token_details': {}, 'output_token_details': {'reasoning': 253}}),
 ToolMessage(content='36', name='multiply', tool_call_id='01973f810322941900f35c0e2504e4b6'),
 ToolMessage(content='60', name='add', tool_call_id='01973f810925180e894dce7520408055')]

最后,我们将使用工具结果调用模型。模型将使用此信息来生成对我们原始查询的最终答案

python 复制代码
llm_with_tools.invoke(messages)
python 复制代码
AIMessage(content='\n\nThe results are as follows:\n\n- **3 * 12 = 36**\n- **11 + 49 = 60**', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 253, 'prompt_tokens': 325, 'total_tokens': 578, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 221, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': None}, 'model_name': 'Qwen/Qwen3-8B', 'system_fingerprint': '', 'id': '01973f812cff79b796f4a4d4987fc750', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--fd5b96f8-97a4-4785-afd7-4f4217a027d3-0', usage_metadata={'input_tokens': 325, 'output_tokens': 253, 'total_tokens': 578, 'input_token_details': {}, 'output_token_details': {'reasoning': 221}})

请注意,每个 ToolMessage 都必须包含一个 tool_call_id,它与模型生成的原始工具调用中的 id 匹配。这有助于模型将工具响应与工具调用匹配。

五、如何将运行时值传递给工具

有时候我们可能需要将值绑定到 工具,这些值仅在运行时才已知。 例如,工具逻辑可能需要使用发出请求的用户的 ID

大多数情况下,这些值不应由 LLM 控制。 实际上,允许 LLM 控制用户 ID 可能会导致安全风险。

相反,LLM 应该只控制工具的参数,这些参数旨在由 LLM 控制,而其他参数(例如用户 ID)应由应用程序逻辑固定。

接下来我们探索如何阻止模型生成某些工具参数并在运行时直接注入它们。

1、从模型中隐藏参数

我们可以使用 InjectedToolArg 注解来标记我们 Tool 的某些参数,例如 user_id,将其标记为在运行时注入,这意味着它们不应由模型生成

python 复制代码
from typing import List

from langchain_core.tools import InjectedToolArg, tool
from typing_extensions import Annotated

user_to_pets = {}


@tool(parse_docstring=True)
def update_favorite_pets(
    pets: List[str], user_id: Annotated[str, InjectedToolArg]
) -> None:
    """Add the list of favorite pets.

    Args:
        pets: List of favorite pets to set.
        user_id: User's ID.
    """
    user_to_pets[user_id] = pets


@tool(parse_docstring=True)
def delete_favorite_pets(user_id: Annotated[str, InjectedToolArg]) -> None:
    """Delete the list of favorite pets.

    Args:
        user_id: User's ID.
    """
    if user_id in user_to_pets:
        del user_to_pets[user_id]


@tool(parse_docstring=True)
def list_favorite_pets(user_id: Annotated[str, InjectedToolArg]) -> None:
    """List favorite pets if any.

    Args:
        user_id: User's ID.
    """
    return user_to_pets.get(user_id, [])

如果我们查看这些工具的输入模式,我们会看到 user_id 仍然被列出

python 复制代码
update_favorite_pets.get_input_schema().schema()
arduino 复制代码
{'description': 'Add the list of favorite pets.',
 'properties': {'pets': {'description': 'List of favorite pets to set.',
   'items': {'type': 'string'},
   'title': 'Pets',
   'type': 'array'},
  'user_id': {'description': "User's ID.",
   'title': 'User Id',
   'type': 'string'}},
 'required': ['pets', 'user_id'],
 'title': 'update_favorite_pets',
 'type': 'object'}

但是,如果我们查看工具调用模式(传递给模型用于工具调用的模式),user_id 已被删除

python 复制代码
update_favorite_pets.tool_call_schema.schema()
arduino 复制代码
{'description': 'Add the list of favorite pets.',
 'properties': {'pets': {'description': 'List of favorite pets to set.',
   'items': {'type': 'string'},
   'title': 'Pets',
   'type': 'array'}},
 'required': ['pets'],
 'title': 'update_favorite_pets',
 'type': 'object'}

因此,当我们调用我们的工具时,我们需要传入 user_id

python 复制代码
user_id = "123"
update_favorite_pets.invoke({"pets": ["lizard", "dog"], "user_id": user_id})
print(user_to_pets)
print(list_favorite_pets.invoke({"user_id": user_id}))
css 复制代码
{'123': ['lizard', 'dog']}
['lizard', 'dog']

但是当模型调用该工具时,不会生成 user_id 参数

python 复制代码
tools = [
    update_favorite_pets,
    delete_favorite_pets,
    list_favorite_pets,
]
llm_with_tools = llm.bind_tools(tools)
ai_msg = llm_with_tools.invoke("my favorite animals are cats and parrots")
ai_msg.tool_calls
css 复制代码
[{'name': 'update_favorite_pets',  'args': {'pets': ['cats', 'parrots']},
  'id': '01973f821d182fe8b7b4d5fc4fb0b0a3',
  'type': 'tool_call'}]

2、在运行时注入参数

如果我们想使用模型生成的工具调用来实际执行我们的工具,我们需要自己注入 user_id

python 复制代码
from copy import deepcopy

from langchain_core.runnables import chain


@chain
def inject_user_id(ai_msg):
    tool_calls = []
    for tool_call in ai_msg.tool_calls:
        tool_call_copy = deepcopy(tool_call)
        tool_call_copy["args"]["user_id"] = user_id
        tool_calls.append(tool_call_copy)
    return tool_calls


inject_user_id.invoke(ai_msg)
css 复制代码
[{'name': 'update_favorite_pets',  'args': {'pets': ['cats', 'parrots'], 'user_id': '123'},
  'id': '01973f821d182fe8b7b4d5fc4fb0b0a3',
  'type': 'tool_call'}]

现在我们可以将我们的模型、注入代码和实际工具链接在一起,以创建一个工具执行链

python 复制代码
tool_map = {tool.name: tool for tool in tools}


@chain
def tool_router(tool_call):
    return tool_map[tool_call["name"]]


chain = llm_with_tools | inject_user_id | tool_router.map()
chain.invoke("my favorite animals are cats and parrots")
css 复制代码
[ToolMessage(content='null', name='update_favorite_pets', tool_call_id='01973f82f807a4fe19bc9bd2ca9ee83d')]

查看 user_to_pets 字典,我们可以看到它已更新为包含猫和鹦鹉

python 复制代码
user_to_pets
arduino 复制代码
{'123': ['cats', 'parrots']}

3、注释参数的其他方法

以下是注释工具参数的其他几种方法。

方式一:

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


class UpdateFavoritePetsSchema(BaseModel):
    """Update list of favorite pets"""

    pets: List[str] = Field(..., description="List of favorite pets to set.")
    user_id: Annotated[str, InjectedToolArg] = Field(..., description="User's ID.")


@tool(args_schema=UpdateFavoritePetsSchema)
def update_favorite_pets(pets, user_id):
    user_to_pets[user_id] = pets


update_favorite_pets.get_input_schema().schema()
arduino 复制代码
{'description': 'Update list of favorite pets',
 'properties': {'pets': {'description': 'List of favorite pets to set.',
   'items': {'type': 'string'},
   'title': 'Pets',
   'type': 'array'},
  'user_id': {'description': "User's ID.",
   'title': 'User Id',
   'type': 'string'}},
 'required': ['pets', 'user_id'],
 'title': 'UpdateFavoritePetsSchema',
 'type': 'object'}
python 复制代码
update_favorite_pets.tool_call_schema.schema()
arduino 复制代码
{'description': 'Update list of favorite pets',
 'properties': {'pets': {'description': 'List of favorite pets to set.',
   'items': {'type': 'string'},
   'title': 'Pets',
   'type': 'array'}},
 'required': ['pets'],
 'title': 'update_favorite_pets',
 'type': 'object'}

方式二:

python 复制代码
from typing import Optional, Type


class UpdateFavoritePets(BaseTool):
    name: str = "update_favorite_pets"
    description: str = "Update list of favorite pets"
    args_schema: Optional[Type[BaseModel]] = UpdateFavoritePetsSchema

    def _run(self, pets, user_id):
        user_to_pets[user_id] = pets


UpdateFavoritePets().get_input_schema().schema()
arduino 复制代码
{'description': 'Update list of favorite pets',
 'properties': {'pets': {'description': 'List of favorite pets to set.',
   'items': {'type': 'string'},
   'title': 'Pets',
   'type': 'array'},
  'user_id': {'description': "User's ID.",
   'title': 'User Id',
   'type': 'string'}},
 'required': ['pets', 'user_id'],
 'title': 'UpdateFavoritePetsSchema',
 'type': 'object'}
python 复制代码
UpdateFavoritePets().tool_call_schema.schema()
arduino 复制代码
{'description': 'Update list of favorite pets',
 'properties': {'pets': {'description': 'List of favorite pets to set.',
   'items': {'type': 'string'},
   'title': 'Pets',
   'type': 'array'}},
 'required': ['pets'],
 'title': 'update_favorite_pets',
 'type': 'object'}

方式三:

python 复制代码
class UpdateFavoritePets2(BaseTool):
    name: str = "update_favorite_pets"
    description: str = "Update list of favorite pets"

    def _run(self, pets: List[str], user_id: Annotated[str, InjectedToolArg]) -> None:
        user_to_pets[user_id] = pets


UpdateFavoritePets2().get_input_schema().schema()
rust 复制代码
{'description': 'Use the tool.\n\nAdd run_manager: Optional[CallbackManagerForToolRun] = None\nto child implementations to enable tracing.',
 'properties': {'pets': {'items': {'type': 'string'},
   'title': 'Pets',
   'type': 'array'},
  'user_id': {'title': 'User Id', 'type': 'string'}},
 'required': ['pets', 'user_id'],
 'title': 'update_favorite_pets',
 'type': 'object'}
python 复制代码
UpdateFavoritePets2().tool_call_schema.schema()
arduino 复制代码
{'description': 'Update list of favorite pets',
 'properties': {'pets': {'items': {'type': 'string'},
   'title': 'Pets',
   'type': 'array'}},
 'required': ['pets'],
 'title': 'update_favorite_pets',
 'type': 'object'}

参考文档

相关推荐
都叫我大帅哥16 小时前
🚀 LangGraph终极指南:从入门到生产级AI工作流编排
python·langchain
showyoui18 小时前
LangChain vs LangGraph:从困惑到清晰的认知之路(扫盲篇)
langchain·ai编程
_一条咸鱼_1 天前
LangChain记忆序列化与持久化方案源码级分析(37)
人工智能·面试·langchain
数据智能老司机1 天前
构建由 LLM 驱动的 Neo4j 应用程序——揭开 RAG 的神秘面纱
langchain·llm·aigc
数据智能老司机1 天前
构建由 LLM 驱动的 Neo4j 应用程序——构建智能应用的知识图谱基础理解
langchain·llm·aigc
数据智能老司机1 天前
构建由 LLM 驱动的 Neo4j 应用程序——使用电影数据集构建你的Neo4j图数据库
langchain·llm·aigc
数据智能老司机1 天前
构建由 LLM 驱动的 Neo4j 应用程序——LLM、RAG 与 Neo4j 知识图谱简介
langchain·llm·aigc
ClouGence2 天前
RAG 青铜升级:知识库召回率优化实战攻略
后端·langchain
老周聊大模型2 天前
大模型如何突破“认知茧房”?RAG+MCP构建外部脑接口
langchain·agent·mcp
小森( ﹡ˆoˆ﹡ )2 天前
LangChain聊天机器人教程
大数据·langchain·机器人