跳到正文
原文
LlamaIndex:产品、工程与评测·· 2 小时前精选AI 评分71

LlamaIndex 发布 Data Agents 与 LlamaHub 工具仓库

Data Agents

AI 导读

LlamaIndex 发布 Data Agents,让 LLM 作为知识工作者对数据执行读写任务,支持自动检索、调用外部 API 和保存对话历史。

推荐理由

官方发布 Data Agents,给出 agent 抽象、ToolSpec 设计和 15+ 工具仓库,读者可了解在数据上构建智能体的完整方法。

正文 · AI 翻译

今天我们非常激动地宣布,LlamaIndex 推出了一项重要的新功能:Data Agents。

立即探索我们的免费和付费方案。

Data Agents 是由 LLM 驱动的知识工作者,能够智能地对你的数据执行各种任务,兼具“读”和“写”功能。它们能够做到以下几点:

  • 对不同类型的数——非结构化、半结构化和结构化数据——执行自动化搜索和检索。
  • 以结构化方式调用任何外部服务 API。它们可以立即处理响应,也可以将此数据索引/缓存以供将来使用。
  • 存储对话历史。
  • 利用以上所有能力来完成简单和复杂的数据任务。

我们努力在 agents 侧和 tools 侧提供抽象、服务和指南,以便构建 data agents。今天的发布包含以下关键组件:

完整详情见下文。我们将向你展示如何构建一个 Gmail agent,它能够在不到 10 行代码内自动创建/发送电子邮件!

背景

我们在 LlamaIndex 的核心使命是释放 LLM 在你的外部数据源上的全部能力。它提供了一套工具,用于定义“状态”(如何解析/结构化你的数据)和“计算”(如何查询你的数据)。到目前为止,我们的框架主要聚焦于搜索和检索用例。我们拥有一套出色的工具和能力,不仅能让你围绕向量数据库 + top-k 检索创建基础 RAG 栈,还能提供远超于此的更多功能不止于此。

其中许多技术过去存在于我们的查询引擎中。我们的目标是提升查询引擎回答各种不同查询的能力。为此,我们必须改进这些查询引擎的“推理”能力。因此,我们现有的一些查询能力包含“类 agent”组件:我们拥有能够进行思维链推理、查询分解和路由的查询引擎。在此过程中,用户可以从一系列查询引擎中进行选择,这些引擎的推理能力从更受限到更少受限不等。

但 LLM 有一个巨大的机会,可以与数据进行更丰富的交互;它们应该能够对任何工具集进行通用推理,无论是来自数据库还是 API。它们也应该同时具备“读”和“写”能力——不仅能够理解状态,还能够修改状态。因此,它们应该能够做的不仅仅是基于静态知识源进行搜索和检索。

一些现有的 服务、工具包 和 研究 论文 已经展示了由 LLM 驱动的“代理”与外部环境交互的可能性。以这些现有方法为灵感,我们看到了一个机会,可以构建一系列有原则的抽象,使任何人都能在他们的数据上构建知识工作者。

数据代理的核心组件

构建数据代理需要以下核心组件:

  • 推理循环
  • 工具抽象

在高层次上,数据代理被提供了一组 API 或工具来与之交互。这些 API 可以返回关于世界的信息,或执行修改状态的操作。每个工具暴露一个请求/响应接口。请求是一组结构化参数,响应可以是任何格式(至少在概念上,这里大多数情况下响应是某种形式的文本字符串)。

给定一个输入任务,数据代理使用一个推理循环来决定使用哪些工具、以什么顺序使用,以及调用每个工具的参数。“循环”在概念上可以非常简单(一步工具选择过程),也可以复杂(多步选择过程,每一步选择大量工具)。

这些组件将在下面更详细地描述。

代理抽象 + 推理循环

我们支持以下代理:

  • OpenAI Function 代理(构建在 OpenAI Function API 之上)
  • 一个 ReAct 代理(适用于任何聊天/文本补全端点)。

你可以像下面这样使用它们:

from llama_index.agent import OpenAIAgent, ReActAgent
from llama_index.llms import OpenAI


...

llm = OpenAI(model="gpt-3.5-turbo-0613")

agent = OpenAIAgent.from_tools(tools, llm=llm, verbose=True)

agent = ReActAgent.from_tools(tools, llm=llm, verbose=True)

response = agent.chat("What is (121 * 3) + 42?")

每个代理接收一组工具。我们工具抽象背后的细节在下面提供。每个代理还支持两个主要方法来接收输入任务——chat 和 query。注意,这些分别是我们在 ChatEngine 和 QueryEngine 中使用的核心方法。事实上,我们的基础代理类(BaseAgent)只是继承自 BaseChatEngine 和 BaseQueryEngine。chat 允许代理利用之前存储的对话历史,而 query 是无状态调用——历史/状态不会随时间保留。

推理循环取决于代理的类型。OpenAI 代理在 while 循环中调用 OpenAI 函数 API,因为工具决策逻辑已内置在函数 API 中。给定一个输入提示和之前的聊天历史(包括之前的函数调用),函数 API 将决定是否进行另一个函数调用(选择一个工具),或返回一条助手消息。如果 API 返回函数调用,那么我们负责执行该函数并在聊天历史中传递一条函数消息。如果 API 返回助手消息,则循环完成(我们假设任务已解决)。

ReAct 代理使用通用文本补全端点,因此可以与任何 LLM 一起使用。文本补全端点具有简单的输入 str → 输出 str 格式,这意味着推理逻辑必须编码在提示中。ReAct 代理使用受 ReAct 论文启发的输入提示(并改编成其他版本),以决定选择哪个工具。它看起来像这样:

...
You have access to the following tools:
{tool_desc}

To answer the question, please use the following format.

```
Thought: I need to use a tool to help me answer the question.
Action: tool name (one of {tool_names})
Action Input: the input to the tool, in a JSON format representing the kwargs (e.g. {{"text": "hello world", "num_beams": 5}})
```
Please use a valid JSON format for the action input. Do NOT do this {{'text': 'hello world', 'num_beams': 5}}.

If this format is used, you will receive a response in the following format:

```
Observation: tool response
```
...

我们在聊天提示上原生实现 ReAct;推理循环实现为一系列交替的助手和用户消息。Thought/Action/Action Input 部分表示为助手消息,Observation 部分实现为用户消息。

注意:ReAct 提示不仅期望选择工具的名称,还期望以 JSON 格式填写工具的参数。这使得其输出与 OpenAI Function API 的输出颇为相似——主要区别在于,对于 Function API,工具选择逻辑已内置于 API 本身(通过微调模型),而在这里则是通过显式提示来引导。

工具抽象

拥有恰当的工具抽象是构建数据代理的核心。定义一组工具类似于定义任何 API 接口,不同之处在于这些工具是供代理而非人类使用。我们允许用户既定义单个工具,也定义包含一系列底层函数的“ToolSpec”。

我们描述了基础工具抽象,以及如何轻松地在现有查询引擎和其他工具之上定义工具。

基础工具抽象

基础工具定义了一个非常通用的接口。__call__ 函数可以接收任意一系列参数,并返回一个可以捕获任何响应的通用 ToolOutput 容器。工具还具有包含其名称、描述和函数模式的元数据。

@dataclass
class ToolMetadata:
    description: str
    name: Optional[str] = None
    fn_schema: Optional[Type[BaseModel]] = DefaultToolFnSchema

class BaseTool:
    @property
    @abstractmethod
    def metadata(self) -> ToolMetadata:
        pass
    @abstractmethod
    def __call__(self, input: Any) -> ToolOutput:
        pass

函数工具

函数工具允许用户轻松地将任何函数转换为工具。它接收一个用户定义的函数(可以接受任何输入/输出),并将其包装成工具接口。如果事先未指定函数模式,它还可以“自动推断”该模式。

我们的 ToolSpec 类利用这种 FunctionTool 抽象,将工具规范中定义的函数转换为一组代理工具(见下文)。

下面是一个定义 FunctionTool 的简单示例。

from llama_index.tools.function_tool import FunctionTool

def multiply(a: int, b: int) -> int:
    """Multiple two integers and returns the result integer"""
    return a * b
multiply_tool = FunctionTool.from_defaults(fn=multiply)

QueryEngineTool

当然,我们也提供了工具抽象来包装现有的查询引擎。这实现了从处理查询引擎到处理代理的无缝过渡。我们的查询引擎可以被视为“受限”代理,专为读/写场景设计,并以检索目的为中心。这些查询引擎可以在整体代理设置中使用。

from llama_index.tools import QueryEngineTool

query_engine_tools = [
    QueryEngineTool(
        query_engine=query_engine, 
        metadata=ToolMetadata(
            name='<tool_name>', 
            description="Queries over X data source."
        )
    ),
 ...
]

工具规范

工具规范是一个 Python 类,代表代理可以交互的完整 API 规范,并且工具规范可以转换为代理初始化时可用的工具列表。

这个类允许用户定义整个服务,而不仅仅是执行单个任务的单个工具。每个工具规范可以包含读/写端点,使代理能够以有意义的方式与服务交互。例如,一个 Slack 工具规范可以允许用户既读取现有消息和频道(load_data、fetch_channels),也写入消息(send_message)。其大致定义如下:

class SlackToolSpec(BaseToolSpec):
    """Slack tool spec."""
    spec_functions = ["load_data", "send_message", "fetch_channels"]

    def load_data(
          self,
          channel_ids: List[str],
          reverse_chronological: bool = True,
      ) -> List[Document]:
          """Load data from the input directory."""
          ...
      def send_message(
          self,
          channel_id: str,
          message: str,
      ) -> None:
          """Send a message to a channel given the channel ID."""
          ...
      def fetch_channels(
          self,
      ) -> List[str]:
          """Fetch a list of relevant channels."""
          ...

如果工具规范被初始化,它可以转换为一个工具列表,并通过 to_tool_list 提供给代理。例如,

tool_spec = SlackToolSpec()
# initialize openai agent
agent = OpenAIAgent.from_tools(tool_spec.to_tool_list(), llm=llm, verbose=True)

定义工具规范与定义 Python 类差别不大。每个函数都会被转换为一个工具,并且默认情况下,每个函数的文档字符串会被用作工具描述(不过你可以在 to_tool_list(func_to_metadata_mapping=...) 中自定义名称/描述)。

我们还特意选择了输入参数和返回类型可以是任何内容。主要原因是保持工具接口的通用性,以适应代理的后续迭代。即使当前迭代的代理期望工具输出为字符串格式,未来也可能改变,我们不想任意限制工具接口的类型。

LlamaHub 工具仓库

我们发布的一大亮点是 LlamaHub 的全新成员:工具仓库。工具仓库包含 15+ 个工具规范,可供智能体使用。这些工具规范代表了一份初步精选的服务列表,智能体可以与这些服务交互,从而增强其执行不同操作的能力。

其中包括以下规范:

  • Gmail 规范
  • Zapier 规范
  • Google Calendar 规范
  • OpenAPI 规范
  • SQL + 向量数据库规范

我们还提供了一系列实用工具,帮助抽象化设计智能体与返回大量数据的不同 API 服务交互时的痛点。

例如,我们的 Gmail 工具规范允许智能体搜索现有邮件、创建草稿、更新草稿和发送邮件。我们的 Zapier 规范允许智能体通过其 Natural Language Actions 接口向 Zapier 执行任何自然语言查询。

最棒的是,你无需花费大量时间研究如何使用这些工具——我们有 10+ 个 notebook 展示如何为每项服务构建智能体,甚至构建使用多项服务组合(例如 Gmail、Google Calendar 和 Search)的智能体。

示例演示

让我们看几个示例!我们使用 Gmail 规范初始化一个 OpenAIAgent。如上所述,该规范包含搜索邮件、创建/更新草稿和发送邮件的工具。

现在让我们给智能体一系列命令,使其能够创建邮件草稿、对其进行几次编辑,然后发送出去。

首先,让我们创建一封初始邮件草稿。注意,智能体选择了 create_draft 工具,该工具接收“to”、“subject”和“message”参数。智能体能够在选择工具的同时推断出这些参数。

接下来,让我们对草稿进行轻微修改:

接下来,让我们展示草稿的当前状态。

最后,让我们发送邮件!

这是一个好的开始,但这仅仅是开始。我们正在积极地为这个仓库贡献更多工具,并且也向社区贡献开放。如果你有兴趣为 LlamaHub 贡献一个工具,请随时在这个仓库中提交 PR。

实用工具

通常,直接查询 API 可能会返回大量数据,这本身可能会溢出 LLM 的上下文窗口(或者至少会不必要地增加你使用的 token 数量)。

为了解决这个问题,我们在核心 LlamaIndex 仓库中提供了一组初始的“实用工具”——实用工具在概念上并不绑定于特定服务(例如 Gmail、Notion),而是可以增强现有工具的能力。在这个特定情况下,实用工具帮助抽象化需要缓存/索引和查询任何 API 请求返回数据的常见模式。

下面让我们介绍两个主要的实用工具。

OnDemandLoaderTool

该工具将任何现有的 LlamaIndex 数据加载器(BaseReader 类)转换为智能体可以使用的工具。该工具可以使用触发数据加载器中 load_data 所需的所有参数以及自然语言查询字符串来调用。在执行过程中,我们首先从数据加载器加载数据,对其进行索引(例如使用向量存储),然后“按需”查询它。所有这三个步骤都在一次工具调用中完成。

通常,这比你自己去研究如何加载和索引 API 数据更为可取。虽然这样做可能实现数据的可复用性,但用户往往只需要一个临时索引,以规避任何 API 调用时的提示窗口限制。

下面给出一个使用示例:

from llama_hub.wikipedia.base import WikipediaReader
from llama_index.tools.on_demand_loader_tool import OnDemandLoaderTool

tool = OnDemandLoaderTool.from_defaults(
 reader,
 name="Wikipedia Tool",
 description="A tool for loading data and querying articles from Wikipedia"
)

LoadAndSearchToolSpec

LoadAndSearchToolSpec 接受任何现有的 Tool 作为输入。作为一个工具规范,它实现了 to_tool_list,当该函数被调用时,会返回两个工具:一个 load 工具,然后是一个 search 工具。

load 工具的执行会调用底层的 Tool,并对输出进行索引(默认使用向量索引)。search 工具的执行会接受一个查询字符串作为输入,并调用底层的索引。

这对于任何默认会返回大量数据的 API 端点都很有帮助——例如,我们的 WikipediaToolSpec 默认会返回整个维基百科页面,这很容易超出大多数 LLM 的上下文窗口。

下面展示了使用示例:

from llama_hub.tools.wikipedia.base import WikipediaToolSpec
from llama_index.tools.tool_spec.load_and_search.base import LoadAndSearchToolSpec

wiki_spec = WikipediaToolSpec()

tool = wiki_spec.to_tool_list()[1]

agent = OpenAIAgent.from_tools(
 LoadAndSearchToolSpec.from_defaults(
    tool
 ).to_tool_list(), verbose=True
)

这是我们运行输入提示时的输出

agent.chat('what is the capital of poland')

输出:

=== Calling Function ===
Calling function: search_data with args: {
  "query": "capital of Poland"
}
Got output: Content loaded! You can now search the information using read_search_data
========================
=== Calling Function ===
Calling function: read_search_data with args: {
  "query": "What is the capital of Poland?"
}
Got output: 
The capital of Poland is Warsaw.
========================
AgentChatResponse(response='The capital of Poland is Warsaw.', sources=[])

请注意,代理会意识到它首先需要调用“load”工具(由工具的原始名称“search_data”表示)。这个 load 工具会在底层加载维基百科页面并建立索引。输出只是提到“内容已加载”,并告诉代理下一步是使用 read_search_data。然后代理会推断出它需要调用 read_search_data 工具,该工具将查询索引以获取正确答案。

常见问题

我应该使用数据代理进行搜索和检索,还是继续使用查询引擎?

简短回答:两者都可以。查询引擎让你能够在数据之上定义自己的工作流,既可以采用受限的推理方式,也可以采用不受限的方式。例如,你可能想用我们的 NLStructStoreQueryEngine 在文本到 SQL 上定义一个特定的工作流(受限),或者用一个路由模块来决定是进行语义搜索还是摘要(受限较少),或者使用我们的 SubQuestionQueryEngine 将一个问题分解到子文档中(受限更少)。

默认情况下,代理循环是不受限制的,理论上可以对你提供给它的任何工具集进行推理。这意味着你可以获得开箱即用的高级搜索/检索能力——例如,在我们的 OpenAI cookbook 中,我们展示了只需提供一个 SQL 查询引擎和一个向量存储查询引擎作为工具,就可以获得联合的文本到 SQL 能力。但另一方面,以这种方式构建的代理可能相当不可靠(更多见解请参阅我们的博客文章)。如果你使用代理进行搜索/检索,请注意 1)你选择的 LLM,以及 2)你选择的工具集。

LlamaIndex 数据代理与现有代理框架(LangChain、Hugging Face 等)有何不同?

这些核心概念大多并不新鲜。我们的整体设计从流行的代理构建工具和框架中汲取了灵感。但在我们的“数据代理”设计中,我们尽力很好地回答了以下关键问题:

  • 我们如何有效地预先索引/查询和检索数据?
  • 我们如何有效地即时索引/查询和检索数据?
  • 我们如何设计既丰富(可以接受结构化输入)又易于代理理解的读写 API 接口?
  • 我们如何正确地在引用中获取来源?

我们构建数据代理的目标是打造能够对数据进行推理和交互的自动化知识工作者。我们的核心工具包为正确索引、检索和查询数据提供了基础——这些可以轻松集成为工具。我们提供了一些额外的工具抽象,以应对你希望即时“缓存”API 输出的情况(见上文)。最后,我们提供了有原则的工具抽象和设计原则,使代理能够以结构化的方式与外部服务对接。

我可以在 LangChain 代理中使用工具吗? 你也可以轻松地将我们的任何工具与 LangChain 代理一起使用。

tools = tool_spec.to_tool_list()
langchain_tools = [t.to_langchain_tool() for t in tools]

查看我们的工具使用指南了解更多详情!

结论

总而言之,今天我们发布了两项关键内容:数据代理组件(包括代理推理循环和工具抽象)以及 LlamaHub 工具仓库。

资源

我们在文档中撰写了一个全面的章节——请在此查看:https://gpt-index.readthedocs.io/en/latest/core_modules/agent_modules/agents/root.html

查看我们的 LlamaHub 工具部分:https://llamahub.ai/

LlamaHub 工具的 Notebook 教程:https://github.com/emptycrown/llama-hub/tree/main/llama_hub/tools/notebooks

如果你有任何问题,请加入我们的 Discord:https://discord.gg/dGcwcsnxhU

来源:LlamaIndex:产品、工程与评测 · llamaindex.ai