精通LangGraph-Tools call-03

文摘   2025-01-18 18:00   四川  
有时,我们希望让调用工具的 LLM 填充工具函数的参数子集,并在运行时为其他参数提供其他值。 
如果使用的是 LangChain 风格的工具,处理这种情况的简单方法是使用 InjectedArg 对函数参数进行注解。 这种注解会将该参数排除在 LLM 之外。
在 LangGraph 应用程序中,我们可能希望在运行时将graph state或共享内存(存储)传递给tool。 
当工具的输出受到过去agent 步骤的影响时(例如,如果我们将子代理用作工具,并希望将消息历史记录传递给子代理),或者当工具的输入需要根据过去代理步骤的上下文进行验证时,这种类型的有状态工具就非常有用。
下面我们将演示如何使用 LangGraph 的预构建 ToolNode 来实现这一点。
以下示例中的核心是将参数注释为“injected”,这意味着它将由程序注入,并且不应被 LLM 看到或填充。让以下代码片段作为 tl;dr:
from typing import Annotatedfrom langchain_core.runnables import RunnableConfigfrom langchain_core.tools import InjectedToolArgfrom langgraph.store.base import BaseStorefrom langgraph.prebuilt import InjectedState, InjectedStore
# 可以是同步或异步;不需要 @tool 装饰器async def my_tool(    # 这些参数由 LLM 填充    some_arg: str,    another_arg: float,    # config: 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)    # Config, some_other_arg, store 和 state 在传递给 bind_tools 或 with_structured_output 时    # 对 LangChain 模型是“隐藏”的    return "... some response"

将graph state传递给tool
首先让我们看看如何让我们的工具访问图形状态。我们需要定义图形状态:
from typing import List
this is the state schema used by the prebuilt create_react_agent we'll be using belowfrom langgraph.prebuilt.chat_agent_executor import AgentStatefrom langchain_core.documents import Document

class State(AgentState):    docs: List[str]

定义tools
我们希望我们的工具将graph state 作为输入,但我们不希望模型在调用该工具时尝试生成此输入(跟上面说的一样,也就是说我们想让LLM忽略这个输入去构造参数)。
我们可以使用 InjectedState 注释将参数标记为所需的图形状态(或图形状态的某些字段。这些参数不会由模型生成。
当使用 ToolNode 时,graph state 将自动传递到相关工具和参数。
在这个例子中,我们将创建一个返回文档的工具,然后创建另一个实际引用证明索赔的文档的工具。
from typing import ListTuplefrom typing_extensions import Annotatedfrom langchain_core.messages import ToolMessagefrom langchain_core.tools import toolfrom langgraph.prebuilt import InjectedState
@tooldef 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"])
如果我们查看这些工具的input_schema,我们会看到state 仍然别列出来了,能理解,因为tool的入参必须要这些参数:
print(get_context.get_input_schema().model_json_schema())
{'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'}

但是如果我们看一下tool-call schema(即传递给模型进行工具调用的内容),状态已被删除,因为我们不希望llm看到需要填充这些参数:

print(get_context.tool_call_schema.model_json_schema())
{'description''Get relevant context for answering the question.''properties': {'question': {'title''Question''type''string'}}, 'required': ['question'], 'title''get_context''type''object'}

定义graph
在此示例中,我们将使用预构建的 ReAct 代理。我们首先需要定义我们的模型和工具调用节点 (ToolNode):
from langchain_ollama import ChatOllamaimport base_conffrom langgraph.prebuilt import ToolNode, create_react_agentfrom langgraph.checkpoint.memory import MemorySavermodel = ChatOllama(base_url=base_conf.base_url,model=base_conf.model_name,temperature=0)tools = [get_context]# ToolNode 将自动负责将state注入tooltool_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 (e1eeaa88-b5f4-45ae-abf3-ed7fd861ce66) Call ID: e1eeaa88-b5f4-45ae-abf3-ed7fd861ce66  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! For reference, FooBar was founded in 2019.
可以看到,toolnode自动把graph state中的docs数据自动注入了!

将共享memory(store)传递到graph
您可能还想让工具访问在多个对话或用户之间共享的内存。我们可以通过使用不同的注释 InjectedStore 将 LangGraph Store 传递给工具来实现这一点。
让我们修改示例,将文档保存在内存存储中并使用 get_context 工具检索它们。我们还将根据用户 ID 访问文档,以便某些文档只有特定用户才能看到。然后,该工具将使用配置中提供的 user_id 来检索一组正确的文档。
from langgraph.store.memory import InMemoryStore
doc_store = InMemoryStore()
namespace = ("documents""1")  # user IDdoc_store.put(    namespace, "doc_0", {"doc""FooBar company just raised 1 Billion dollars!"})namespace = ("documents""2")  # user IDdoc_store.put(namespace, "doc_1", {"doc""FooBar company was founded in 2019"})
定义工具
from langgraph.store.base import BaseStorefrom langchain_core.runnables import RunnableConfigfrom langgraph.prebuilt import InjectedStore

@tooldef get_context(    question: str,    config: RunnableConfig,    store: Annotated[BaseStore, InjectedStore()],) -> Tuple[strList[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)
验证tool-calling 模型是否忽略 get_context 工具的存储参数:
print(get_context.tool_call_schema.model_json_schema())
{'description''Get relevant context for answering the question.''properties': {'question'{'title''Question''type''string'}}, 'required': ['question'], 'title''get_context''type''object'}
定义graph
tools = [get_context]# ToolNode 会自动把 Store 注入到 toolstool_node = ToolNode(tools)checkpointer = MemorySaver()graph = create_react_agent(model, tools, checkpointer=checkpointer, store=doc_store)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()
同样地,它获取到了store中的数据:
================================ Human Message =================================
what's the latest news about FooBar================================== Ai Message ==================================Tool Calls:  get_context (89b2d78c-6b6d-4c46-a5a7-cee513bff5cb) Call ID: 89b2d78c-6b6d-4c46-a5a7-cee513bff5cb  Args:    question: latest news about FooBar================================= Tool Message =================================Name: get_context
FooBar company just raised 1 Billion dollars!================================== Ai Message ==================================
The latest news is that FooBar company has just raised 1 Billion dollars!
该工具在store中查找信息时仅检索了用户“1”的正确文档。现在让我们为其他用户再试一次:
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 (817da555-c13e-4fa1-8bbe-3854713fc643) Call ID: 817da555-c13e-4fa1-8bbe-3854713fc643  Args:    question: latest news about FooBar================================= Tool Message =================================Name: get_context
FooBar company was founded in 2019================================== Ai Message ==================================
The information I have currently states that FooBar company was founded in 2019. However, this doesn't provide the latest news. Could you please specify a date range or give me some more time to fetch the most recent updates?
可以看到,他获取到的是user ID=2的document 内容!

半夏决明
读书,摄影,随笔
 最新文章