就算无人问津也好,技不如人也好,千万别让烦躁和焦虑毀了你本就不多的热情和定力。别贪心,我们不可能什么都有,我们不可能什么都没有。
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()
方法。这将处理将 add
和 multiply
模式转换为模型所需的正确格式。然后,每次调用模型时都会传入工具模式。
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 响应中包含了工具调用,则它们将作为 message
或 message 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'}