掘金 人工智能 前天 13:47
解锁聊天模型的隐藏能力:工具调用全指南
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入探讨了如何在LangChain中将工具绑定到LLM,并实现工具的调用与结果反馈。通过定义工具模式,包括Python函数、LangChain工具、Pydantic类和TypedDict类,使LLM能够理解工具的功能和参数。文章详细介绍了工具调用的过程,以及如何解析和将工具结果传递回模型,最终让LLM基于工具结果生成最终答案。此外,还介绍了如何将运行时值传递给工具,并防止模型生成某些参数。

🛠️ 定义工具模式:为了使模型能够调用工具,需要传入工具模式,描述工具的功能及其参数。支持多种工具模式,包括Python函数、LangChain工具、Pydantic模型和TypedDict类。

⚙️ 工具调用:LLM 响应中包含的工具调用将作为消息或消息块的一部分附加到对应的 消息 或 消息块 中,作为 .tool_calls 属性中的 工具调用 对象列表。

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

🔄 将工具结果传递回模型:通过将工具结果传递回模型,模型可以基于这些结果生成对原始查询的最终答案。

🔑 运行时值传递:可以使用 InjectedToolArg 注解标记工具的某些参数,例如 user_id,将其标记为在运行时注入,这意味着它们不应由模型生成。

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

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

一、定义工具模式

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

1、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 + bdef multiply(a: int, b: int) -> int:    """Multiply two integers.    Args:        a: First integer        b: Second integer    """    return a * b

2、LangChain 工具

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

from typing import Listfrom langchain_core.tools import InjectedToolArg, toolfrom 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())
{'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

from pydantic import BaseModel, Fieldclass 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

from typing_extensions import Annotated, TypedDictclass 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 模式转换为模型所需的正确格式。然后,每次调用模型时都会传入工具模式。

pip install -qU "langchain[openai]"
from pydantic import SecretStrimport osos.environ["OPENAI_BASE_URL"] = "https://api.siliconflow.cn/v1/"os.environ["OPENAI_API_KEY"] = "sk-xxx"from langchain.chat_models import init_chat_modelllm = init_chat_model("Qwen/Qwen3-8B", model_provider="openai")
tools = [add, multiply]llm_with_tools = llm.bind_tools(tools)query = "What is 3 * 12?"llm_with_tools.invoke(query)
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 是一个类型化字典,包括工具名称、参数值字典和(可选)标识符。没有工具调用的消息的此属性默认为空列表。

query = "What is 3 * 12? Also, what is 11 + 49?"llm_with_tools.invoke(query).tool_calls
[{'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 对象

from langchain_core.output_parsers import PydanticToolsParserfrom pydantic import BaseModel, Fieldclass 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)
[multiply(a=3, b=12), add(a=11, b=49)]

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

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

定义工具:

from langchain_core.tools import tool@tooldef add(a: int, b: int) -> int:    """Adds a and b."""    return a + b@tooldef multiply(a: int, b: int) -> int:    """Multiplies a and b."""    return a * btools = [add, multiply]llm_with_tools = llm.bind_tools(tools)

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

from langchain_core.messages import HumanMessagequery = "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)
[{'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。
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
[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')]

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

llm_with_tools.invoke(messages)
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,将其标记为在运行时注入,这意味着它们不应由模型生成

from typing import Listfrom langchain_core.tools import InjectedToolArg, toolfrom typing_extensions import Annotateduser_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 仍然被列出

update_favorite_pets.get_input_schema().schema()
{'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 已被删除

update_favorite_pets.tool_call_schema.schema()
{'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

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}))
{'123': ['lizard', 'dog']}['lizard', 'dog']

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

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
[{'name': 'update_favorite_pets',  'args': {'pets': ['cats', 'parrots']},  'id': '01973f821d182fe8b7b4d5fc4fb0b0a3',  'type': 'tool_call'}]

2、在运行时注入参数

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

from copy import deepcopyfrom langchain_core.runnables import chain@chaindef 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_callsinject_user_id.invoke(ai_msg)
[{'name': 'update_favorite_pets',  'args': {'pets': ['cats', 'parrots'], 'user_id': '123'},  'id': '01973f821d182fe8b7b4d5fc4fb0b0a3',  'type': 'tool_call'}]

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

tool_map = {tool.name: tool for tool in tools}@chaindef 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")
[ToolMessage(content='null', name='update_favorite_pets', tool_call_id='01973f82f807a4fe19bc9bd2ca9ee83d')]

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

user_to_pets
{'123': ['cats', 'parrots']}

3、注释参数的其他方法

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

方式一:

from langchain_core.tools import BaseToolfrom pydantic import BaseModel, Fieldclass 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] = petsupdate_favorite_pets.get_input_schema().schema()
{'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'}
update_favorite_pets.tool_call_schema.schema()
{'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'}

方式二:

from typing import Optional, Typeclass 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] = petsUpdateFavoritePets().get_input_schema().schema()
{'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'}
UpdateFavoritePets().tool_call_schema.schema()
{'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'}

方式三:

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] = petsUpdateFavoritePets2().get_input_schema().schema()
{'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'}
UpdateFavoritePets2().tool_call_schema.schema()
{'description': 'Update list of favorite pets', 'properties': {'pets': {'items': {'type': 'string'},   'title': 'Pets',   'type': 'array'}}, 'required': ['pets'], 'title': 'update_favorite_pets', 'type': 'object'}

参考文档

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

LangChain LLM 工具绑定 工具调用
相关文章