主题
概念
合适的工具抽象,是在 LlamaIndex 中构建智能体系统的核心。定义一组 Tools 与定义任何 API 接口类似,不同之处在于这些 Tool 是给 agent 用、而不是给人用的。我们允许用户既定义单个 Tool,也定义一个 ToolSpec(其内部封装一系列函数)。
在使用支持函数调用的 agent 或 LLM 时,选中了哪个工具(以及为该工具写出的参数)非常依赖 工具名 与描述工具用途、参数的 描述。在这两个参数上花时间去调优,会显著改变 LLM 调用这些工具的方式。
一个 Tool 实现的接口非常通用——只需定义 __call__,并返回一些基础元数据(名称、描述、函数 schema)。
我们提供几种不同类型的 Tool:
FunctionTool:函数工具,用户可以把任意自定义函数轻松转换成一个 Tool。它还能自动推断函数 schema,或让你定制其中的各个部分。QueryEngineTool:包装既有查询引擎的工具。注意:由于我们的 agent 抽象继承自BaseQueryEngine,这类工具也能包装其他 agent。- 社区贡献的
ToolSpecs,围绕某个单一服务(例如 Gmail)定义一个或多个工具 - 实用型工具(utility tools),用于包装其他工具,以处理工具返回的大量数据
FunctionTool
函数工具就是对任意既有函数的简单包装(同步与异步都支持!)。
from llama_index.core.agent.workflow import ReActAgent
from llama_index.core.tools import FunctionTool
def get_weather(location: str) -> str:
"""Usfeful for getting the weather for a given location."""
...
tool = FunctionTool.from_defaults(
get_weather,
# async_fn=aget_weather, # optional!
)
agent = ReActAgent(llm=llm, tools=tools)1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
想要更好的函数定义,你还可以借助 Annotated 类型来指定参数的描述。
from typing import Annotated
def get_weather(
location: Annotated[
str, "A city name and state, formatted like '<name>, <state>'"
],
) -> str:
"""Useful for getting the weather for a given location."""
...
tool = FunctionTool.from_defaults(get_weather)1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
默认情况下,工具名就是函数名,docstring 就是工具描述。不过这两者都可以覆写。
tool = FunctionTool.from_defaults(get_weather, name="...", description="...")1
QueryEngineTool
借助 QueryEngineTool,任何查询引擎都可以变成一个工具:
from llama_index.core.tools import QueryEngineTool
tool = QueryEngineTool.from_defaults(
query_engine, name="...", description="..."
)1
2
3
4
5
2
3
4
5
Tool Specs
我们还把丰富的 Tools 与 Tool Specs 作为独立的集成包提供。
可以把 tool spec 理解成一束要搭配使用的工具。它们通常覆盖某个接口/服务(例如 Gmail)上那些实用的工具组合。
要与 agent 一起使用,先安装对应的 tool spec 集成:
终端窗口
pip install llama-index-tools-google1
然后这样使用:
from llama_index.core.agent.workflow import FunctionAgent
from llama_index.tools.google import GmailToolSpec
tool_spec = GmailToolSpec()
agent = FunctionAgent(llm=llm, tools=tool_spec.to_tool_list())1
2
3
4
5
2
3
4
5
社区贡献的 tool spec 完整清单见 GitHub 上的 tools 目录。
实用型工具
很多时候,直接查询某个 API 会返回海量数据,这本身就可能撑爆 LLM 的上下文窗口(或者至少无谓地增加你消耗的 token 数)。
为应对这一点,我们在工具集成里提供了一组最初的“实用型工具”——它们在概念上并不绑定某个具体服务(如 Gmail、Notion),而是用于增强既有 Tool 的能力。就这里的情形而言,实用型工具把「对任意 API 请求返回的数据做缓存/索引并查询」这类常见模式抽象掉了。
下面逐个介绍我们两个主要的实用型工具。
OnDemandLoaderTool
这个工具把任意既有的 LlamaIndex 数据加载器(BaseReader 类)变成 agent 可用的工具。调用该工具时,需要传入触发数据加载器 load_data 所需的全部参数,以及一个自然语言查询串。执行时会先从数据加载器加载数据,再对它建立索引(例如用向量库),最后“按需”查询它。这三步都发生在一次工具调用之内。
比起自己想办法加载并索引 API 数据,这种方式往往更可取。虽然自建索引能让数据复用,但多数时候用户只需要一个临时索引,把任意 API 调用的提示词窗口限制问题抽象掉。
用法示例如下:
终端窗口
pip install llama-index-readers-wikipedia1
from llama_index.readers.wikipedia import WikipediaReader
from llama_index.core.tools.ondemand_loader_tool import OnDemandLoaderTool
tool = OnDemandLoaderTool.from_defaults(
reader,
name="Wikipedia Tool",
description="A tool for loading data and querying articles from Wikipedia",
)1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
LoadAndSearchToolSpec
LoadAndSearchToolSpec 以任意既有 Tool 作为输入。作为一个 tool spec,它实现了 to_tool_list;该函数被调用时会返回两个工具:一个 load 工具和一个 search 工具。
load 工具的执行会调用底层 Tool,并把输出建立索引(默认用向量索引);search 工具的执行则接收一个查询串并调用那个底层索引。
这对任何默认就返回大量数据的 API 端点都很有帮助——例如我们的 WikipediaToolSpec 默认会返回整篇维基百科页面,很容易超出大多数 LLM 的上下文窗口。
示例用法如下:
终端窗口
pip install llama-index-tools-wikipedia1
from llama_index.core.agent.workflow import FunctionAgent
from llama_index.core.tools.tool_spec.load_and_search import (
LoadAndSearchToolSpec,
)
from llama_index.tools.wikipedia import WikipediaToolSpec
wiki_spec = WikipediaToolSpec()
# Get the search wikipedia tool
tool = wiki_spec.to_tool_list()[1]
# Create the Agent with load/search tools
agent = FunctionAgent(
llm=llm, tools=LoadAndSearchToolSpec.from_defaults(tool).to_tool_list()
)1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
直接返回
你会注意到工具构造函数里有个 return_direct 选项。若把它设为 True,agent 的响应会直接返回,不再被 agent 解读和改写。这对缩短运行时间,或设计那些用于结束 agent 推理循环的工具,都很有帮助。
举个例子,假设你定义了这样一个工具:
tool = QueryEngineTool.from_defaults(
query_engine,
name="<name>",
description="<description>",
return_direct=True,
)
agent = FunctionAgent(llm=llm, tools=[tool])
response = await agent.run("<question that invokes tool>")1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
在上面这个例子里,查询引擎工具会被调用,其响应会直接作为最终回答返回,执行循环随之结束。
如果使用 return_direct=False,agent 就会借助聊天历史的上下文对响应做改写,甚至再发起一次工具调用。
我们还准备了一个使用 return_direct 的示例 notebook。
调试工具
很多时候,弄清楚到底把怎样的工具定义发给了 API 是很有用的。
调用底层的相应函数拿到当前工具 schema 就能大致看清——OpenAI、Anthropic 这类 API 正是使用这份 schema。
schema = tool.metadata.get_parameters_dict()
print(schema)1
2
2