可供调用的工具子集通常由model决定(尽管许多提供商也允许用户指定或限制工具的选择)。随着可用tool数量的增加,您可能希望限制 LLM 的选择范围,以减少token消耗并帮助管理 LLM 推理中的错误源。在这里,我们将演示如何动态调整模型可用的工具。首先要说的是:与RAG和类似方法一样,我们通过检索可用工具来为模型调用添加前缀。虽然我们演示了一种搜索工具描述的实现,但可以根据需要自定义工具选择的细节。让我们考虑一个小例子,其中我们为标准普尔 500 指数中的每一家上市公司提供了一个工具。每个工具根据作为参数提供的年份获取公司特定的信息。我们首先构建一个注册表,将每个工具的唯一标识符与架构关联起来。我们将使用 JSON 架构表示工具,该架构可以直接绑定到支持工具调用的聊天模型。import re
import uuid
from langchain_core.tools import StructuredTool
def create_tool(company: str) -> dict:
"""为占位工具创建模式。"""
# 删除非字母数字字符并将空格替换为下划线以生成工具名称
formatted_company = re.sub(r"[^\w\s]", "", company).replace(" ", "_")
def company_tool(year: int) -> str:
# 占位函数,返回公司和年份的静态收入信息
return f"{company} had revenues of $100 in {year}."
return StructuredTool.from_function(
company_tool,
name=formatted_company,
description=f"Information about {company}",
)
# 用于演示的标准普尔500指数公司简化列表
s_and_p_500_companies = [
"3M",
"A.O. Smith",
"Abbott",
"Accenture",
"Advanced Micro Devices",
"Yum! Brands",
"Zebra Technologies",
"Zimmer Biomet",
"Zoetis",
]
# 为每家公司创建一个工具,并使用唯一的UUID作为键将其存储在dict中
tool_registry = {
str(uuid.uuid4()): create_tool(company) for company in s_and_p_500_companies
}
我们将构建一个节点,根据状态中的信息(例如最近的用户消息)检索可用工具的子集。一般来说,此步骤可以使用所有检索解决方案。作为一种简单的解决方案,我们在向量存储中索引工具描述的嵌入,并通过语义搜索将用户查询与工具关联起来。from langchain_core.documents import Document
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_ollama import OllamaEmbeddings
import base_conf
tool_documents = [
Document(
page_content=tool.description,
id=id,
metadata={"tool_name": tool.name},
)
for id, tool in tool_registry.items()
]
vector_store = InMemoryVectorStore(embedding=OllamaEmbeddings(base_url=base_conf.base_url,
model=base_conf.embedding_model_name_en))
document_ids = vector_store.add_documents(tool_documents)
我们将使用典型的 React agent graph,并进行一些修改:- selected_tools我们向状态添加一个key,用于存储我们选择的工具子集;
- 我们将graph的入口点设置为一个select_tools节点,它填充状态的select_tools key;
- 我们将选定的工具子集绑定到节点内的聊天模型agent。
from typing import Annotated
from langchain_ollama import ChatOllama
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition
# 使用TypedDict定义状态结构。
# 它包括一个消息列表(由add_messages处理)
# 和一个选定工具ID的列表。
class State(TypedDict):
messages: Annotated[list, add_messages]
# 选定的工具ID列表
selected_tools: list[str]
builder = StateGraph(State)
# 从工具dictionary中检索所有可用工具。
tools = list(tool_registry.values())
llm = ChatOllama(base_url=base_conf.base_url,model=base_conf.model_name)
# 代理函数通过将选定的工具绑定到LLM来处理当前状态。
def agent(state: State):
# 根据状态的selected_tools列表将工具ID映射到实际工具。
selected_tools = [tool_registry[id] for id in state["selected_tools"]]
# 将选定的工具绑定到当前交互的LLM。
llm_with_tools = llm.bind_tools(selected_tools)
# 使用当前消息调用LLM并返回更新的消息列表。
return {"messages": [llm_with_tools.invoke(state["messages"])]}
# select_tools函数根据用户的最后一条消息内容选择工具。
def select_tools(state: State):
last_user_message = state["messages"][-1]
query = last_user_message.content
tool_documents = vector_store.similarity_search(query)
return {"selected_tools": [document.id for document in tool_documents]}
builder.add_node("agent", agent)
builder.add_node("select_tools", select_tools)
tool_node = ToolNode(tools=tools)
builder.add_node("tools", tool_node)
builder.add_conditional_edges("agent", tools_condition, path_map=["tools", "__end__"])
builder.add_edge("tools", "agent")
builder.add_edge("select_tools", "agent")
# 先从store中通过query找到相似的tools,将这些tools添加到state中的selected_tools中
builder.add_edge(START, "select_tools")
graph = builder.compile()
user_input = "Can you give me some information about AMD in 2022?"
result = graph.invoke({"messages": [("user", user_input)]})
print(result["selected_tools"])
for message in result["messages"]:
message.pretty_print()
['abd4721b-80b3-4e76-82b1-f59efbde9d9a', '26ef59cd-8e93-4915-88dc-b0e5e5c11471', 'a1bd1bae-dbfa-4894-b4a4-aa21985ac8a4', '48d1ccdf-c17c-405f-a127-b56f590c0fcb']
================================ Human Message =================================
Can you give me some information about AMD in 2022?
================================== Ai Message ==================================
Tool Calls:
Advanced_Micro_Devices (cbf17b76-4393-47db-b95a-70965dd25322)
Call ID: cbf17b76-4393-47db-b95a-70965dd25322
Args:
year: 2022
================================= Tool Message =================================
Name: Advanced_Micro_Devices
Advanced Micro Devices had revenues of $100 in 2022.
================================== Ai Message ==================================
In 2022, Advanced Micro Devices (AMD) reported revenues of $100 billion.
为了管理因工具选择不正确而导致的错误,我们可以重新访问 select_tools 节点。实现此功能的一个选项是修改 select_tools 以使用状态中的所有消息(例如,使用聊天模型)生成向量存储查询,并在graph添加从tools到 select_tools 的路由边。核心点在于我们要理解,一个node是可以反复去跑的,因为连接了LLM,经过多次尝试,它是有可能从错误中恢复过来的,这是跟传统开发不一样的一个思考问题的方式。