如何将运行时值传递给工具¶
有时,您希望让调用工具的LLM填充工具函数参数的一个*子集*,并在运行时提供其余参数的值。如果您使用的是LangChain风格的工具,可以通过对函数参数进行注入参数注解来轻松处理这种情况。此注解会将该参数排除在显示给LLM的内容之外。
在LangGraph应用程序中,您可能希望在运行时将图状态或共享内存(存储)传递给工具。这种有状态的工具在工具的输出受过去代理步骤影响(例如,如果您正在使用一个子代理作为工具,并希望将消息历史传递给子代理)或工具的输入需要根据过去的代理步骤上下文进行验证时非常有用。
在此指南中,我们将演示如何使用LangGraph的预构建ToolNode来实现这一点。
先决条件
此指南针对**LangChain工具调用**,假设熟悉以下内容:
您仍然可以使用LangGraph中的提供商SDK进行工具调用,而不会失去LangGraph的核心功能。以下示例中的核心技术是将一个参数标记为“注入”,这意味着它将由您的程序注入,并且不应被LLM看到或填充。以下代码片段可作为简要说明:
API Reference: RunnableConfig | InjectedToolArg | InjectedState
from typing import Annotated
from langchain_core.runnables import RunnableConfig
from langchain_core.tools import InjectedToolArg
from langgraph.store.base import BaseStore
from langgraph.prebuilt import InjectedState, InjectedStore
# 可以是同步或异步;@tool装饰器不需要
async def my_tool(
# 这些参数由LLM填充
some_arg: str,
another_arg: float,
# 配置:RunnableConfig始终在LangChain调用中可用
# 这不暴露给LLM
config: RunnableConfig,
# 下面三个特定于预构建的ToolNode
# (以及`create_react_agent`扩展)。如果您单独调用工具(在自己的节点中),则需要自己提供这些参数。
store: Annotated[BaseStore, InjectedStore],
# 这将传入完整状态。
state: Annotated[State, InjectedState],
# 您也可以从状态中注入单个字段
messages: Annotated[list, InjectedState("messages")]
# 以下与create_react_agent或ToolNode不兼容
# 您还可以排除其他参数,使其不显示给模型。
# 这些必须手动提供,并在您自己的节点中调用工具/函数时很有用
# some_other_arg=Annotated["MyPrivateClass", InjectedToolArg],
):
"""调用my_tool以对现实世界产生影响。
参数:
some_arg:一个非常重要的参数
another_arg:另一个由LLM提供的参数
""" # 文档字符串成为您的工具描述并传递给模型
print(some_arg, another_arg, config, store, state, messages)
# 配置、some_other_rag、store和状态都从
# LangChain模型隐藏当传递给bind_tools或with_structured_output
return "... some response"
环境搭建¶
首先我们需要安装所需的包
接下来,我们需要为OpenAI(我们将使用的聊天模型)设置API密钥。
import getpass
import os
def _set_env(var: str):
if not os.environ.get(var):
os.environ[var] = getpass.getpass(f"{var}: ")
_set_env("OPENAI_API_KEY")
为LangGraph开发设置LangSmith
注册LangSmith可以快速发现并解决您的LangGraph项目中的问题,并提高其性能。通过使用跟踪数据,您可以调试、测试和监控使用LangGraph构建的LLM应用程序——更多关于如何开始的信息,请参阅这里。
将图状态传递给工具¶
让我们首先看看如何让我们的工具能够访问图状态。我们需要定义图状态:
API Reference: AgentState | Document
from typing import List
# this is the state schema used by the prebuilt create_react_agent we'll be using below
from langgraph.prebuilt.chat_agent_executor import AgentState
from langchain_core.documents import Document
class State(AgentState):
docs: List[str]
定义工具¶
我们希望工具能够接受图状态作为输入,但不希望模型在调用工具时尝试生成该输入。我们可以使用 InjectedState
注解来标记所需的图状态参数(或图状态中的某个字段)。这些参数不会由模型生成。当使用 ToolNode
时,图状态会自动传递给相关的工具和参数。
在这个例子中,我们将创建一个返回文档的工具,然后创建另一个引用文档以证明某个主张的工具。
使用Pydantic与LangChain
此笔记本使用Pydantic v2 BaseModel
,这要求langchain-core >= 0.3
。使用langchain-core < 0.3
将会由于混合使用Pydantic v1和v2 BaseModels
而导致错误。
API Reference: ToolMessage | tool | InjectedState
from typing import List, Tuple
from typing_extensions import Annotated
from langchain_core.messages import ToolMessage
from langchain_core.tools import tool
from langgraph.prebuilt import InjectedState
@tool
def get_context(question: str, state: Annotated[dict, InjectedState]):
"""Get relevant context for answering the question."""
return "\n\n".join(doc for doc in state["docs"])
如果我们查看这些工具的输入模式,会发现state
仍然被列出:
{'description': 'Get relevant context for answering the question.',
'properties': {'question': {'title': 'Question', 'type': 'string'},
'state': {'title': 'State', 'type': 'object'}},
'required': ['question', 'state'],
'title': 'get_context',
'type': 'object'}
但是如果我们查看工具调用模式,即传递给模型以进行工具调用的数据模式,state
已经被移除:
{'description': 'Get relevant context for answering the question.',
'properties': {'question': {'title': 'Question', 'type': 'string'}},
'required': ['question'],
'title': 'get_context',
'type': 'object'}
定义图¶
在此示例中,我们将使用一个预构建的ReAct代理。首先,我们需要定义我们的模型以及一个工具调用节点(ToolNode):
API Reference: ToolNode | create_react_agent | MemorySaver
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import ToolNode, create_react_agent
from langgraph.checkpoint.memory import MemorySaver
model = ChatOpenAI(model="gpt-4o", temperature=0)
tools = [get_context]
# ToolNode will automatically take care of injecting state into tools
tool_node = ToolNode(tools)
checkpointer = MemorySaver()
graph = create_react_agent(model, tools, state_schema=State, checkpointer=checkpointer)
使用它!¶
docs = [
"FooBar company just raised 1 Billion dollars!",
"FooBar company was founded in 2019",
]
inputs = {
"messages": [{"type": "user", "content": "what's the latest news about FooBar"}],
"docs": docs,
}
config = {"configurable": {"thread_id": "1"}}
for chunk in graph.stream(inputs, config, stream_mode="values"):
chunk["messages"][-1].pretty_print()
================================ Human Message =================================
what's the latest news about FooBar
================================== Ai Message ==================================
Tool Calls:
get_context (call_UkqfR7z2cLJQjhatUpDeEa5H)
Call ID: call_UkqfR7z2cLJQjhatUpDeEa5H
Args:
question: latest news about FooBar
================================= Tool Message =================================
Name: get_context
FooBar company just raised 1 Billion dollars!
FooBar company was founded in 2019
================================== Ai Message ==================================
The latest news about FooBar is that the company has just raised 1 billion dollars.
将共享内存(存储)传递给图¶
您可能也希望让工具能够访问跨多个对话或用户的共享内存。我们可以通过使用不同的注释——InjectedStore
——将LangGraph Store传递给工具来实现这一点。
让我们修改示例,以将文档保存到内存存储中,并使用get_context
工具检索它们。我们还将根据用户ID使文档可访问,以便某些文档仅对特定用户可见。然后,该工具将使用配置中提供的user_id
来检索正确的文档集。
注意
Store
API 和 InjectedStore
,支持是在LangGraph v0.2.34
版本中添加的。
InjectedStore
注解要求使用 langchain-core >= 0.3.8
from langgraph.store.memory import InMemoryStore
doc_store = InMemoryStore()
namespace = ("documents", "1") # user ID
doc_store.put(
namespace, "doc_0", {"doc": "FooBar company just raised 1 Billion dollars!"}
)
namespace = ("documents", "2") # user ID
doc_store.put(namespace, "doc_1", {"doc": "FooBar company was founded in 2019"})
定义工具¶
API Reference: RunnableConfig
from langgraph.store.base import BaseStore
from langchain_core.runnables import RunnableConfig
from langgraph.prebuilt import InjectedStore
@tool
def get_context(
question: str,
config: RunnableConfig,
store: Annotated[BaseStore, InjectedStore()],
) -> Tuple[str, List[Document]]:
"""Get relevant context for answering the question."""
user_id = config.get("configurable", {}).get("user_id")
docs = [item.value["doc"] for item in store.search(("documents", user_id))]
return "\n\n".join(doc for doc in docs)
我们还可以验证该工具调用模型会忽略get_context
工具的store
参数:
{'description': 'Get relevant context for answering the question.',
'properties': {'question': {'title': 'Question', 'type': 'string'}},
'required': ['question'],
'title': 'get_context',
'type': 'object'}
定义图¶
让我们更新我们的ReAct代理:
tools = [get_context]
# ToolNode will automatically take care of injecting Store into tools
tool_node = ToolNode(tools)
checkpointer = MemorySaver()
# NOTE: we need to pass our store to `create_react_agent` to make sure our graph is aware of it
graph = create_react_agent(model, tools, checkpointer=checkpointer, store=doc_store)
使用它!¶
让我们尝试在配置中使用一个"user_id"
来运行我们的图。
messages = [{"type": "user", "content": "what's the latest news about FooBar"}]
config = {"configurable": {"thread_id": "1", "user_id": "1"}}
for chunk in graph.stream({"messages": messages}, config, stream_mode="values"):
chunk["messages"][-1].pretty_print()
================================ Human Message =================================
what's the latest news about FooBar
================================== Ai Message ==================================
Tool Calls:
get_context (call_ocyHBpGgF3LPFOgRKURBfkGG)
Call ID: call_ocyHBpGgF3LPFOgRKURBfkGG
Args:
question: latest news about FooBar
================================= Tool Message =================================
Name: get_context
FooBar company just raised 1 Billion dollars!
================================== Ai Message ==================================
The latest news about FooBar is that the company has just raised 1 billion dollars.
messages = [{"type": "user", "content": "what's the latest news about FooBar"}]
config = {"configurable": {"thread_id": "2", "user_id": "2"}}
for chunk in graph.stream({"messages": messages}, config, stream_mode="values"):
chunk["messages"][-1].pretty_print()
================================ Human Message =================================
what's the latest news about FooBar
================================== Ai Message ==================================
Tool Calls:
get_context (call_zxO9KVlL8UxFQUMb8ETeHNvs)
Call ID: call_zxO9KVlL8UxFQUMb8ETeHNvs
Args:
question: latest news about FooBar
================================= Tool Message =================================
Name: get_context
FooBar company was founded in 2019
================================== Ai Message ==================================
FooBar company was founded in 2019. If you need more specific or recent news, please let me know!