如何将运行时值传递给工具
📚Prerequisites
📦Compatibility
The code in this guide requires
langchain-core>=0.2.21
. Please ensure you have the correct packages installed.你可能需要在运行时将值绑定到一个工具上。例如,工具逻辑可能需要使用发出请求的用户的ID。
大多数情况下,这些值不应由LLM控制。事实上,允许LLM控制用户ID可能会导致安全风险。
相反,LLM 应该只控制那些应由 LLM 控制的工具参数,而其他参数(例如用户 ID)应由应用程序逻辑固定。
本操作指南向您展示如何防止模型生成某些工具参数并在运行时直接注入它们。
Using with LangGraph
如果您正在使用LangGraph,请参考此操作指南,该指南展示了如何创建一个代理来跟踪给定用户最喜欢的宠物。
我们可以将它们绑定到聊天模型如下:
Select chat model:
pip install -qU langchain-openai
import getpass
import os
if not os.environ.get("OPENAI_API_KEY"):
os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter API key for OpenAI: ")
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini")
隐藏模型中的参数
我们可以使用InjectedToolArg注解来标记我们工具的某些参数,比如user_id
,这些参数在运行时被注入,意味着它们不应该由模型生成。
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, [])
API Reference:InjectedToolArg | tool
如果我们查看这些工具的输入模式,我们会发现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_petsSchema',
'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': 'call_pZ6XVREGh1L0BBSsiGIf1xVm',
'type': 'tool_call'}]
在运行时注入参数
如果我们想要实际执行我们的工具使用模型生成的工具调用,我们需要自己注入user_id:
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)
API Reference:chain
[{'name': 'update_favorite_pets',
'args': {'pets': ['cats', 'parrots'], 'user_id': '123'},
'id': 'call_pZ6XVREGh1L0BBSsiGIf1xVm',
'type': 'tool_call'}]
现在我们可以将我们的模型、注入代码和实际工具链接在一起,以创建一个执行工具的链:
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")
[ToolMessage(content='null', name='update_favorite_pets', tool_call_id='call_oYCD0THSedHTbwNAY3NW6uUj')]
查看 user_to_pets 字典,我们可以看到它已经更新为包括猫和鹦鹉:
user_to_pets
{'123': ['cats', 'parrots']}
其他注释参数的方式
以下是其他几种注释我们工具参数的方法:
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()
API Reference:BaseTool
{'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, 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()
{'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] = pets
UpdateFavoritePets2().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_petsSchema',
'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'}