Instructor-Marvin-Guardrails

生成式模型的能力让AI世界为之疯狂,它们能够将故事编织在一起,并模拟类似人类的文本生成。然而,在这种生成性的表面之下,还有另一种深刻的能力:结构化数据提取。

数据提取是(如今)比生成更常见的用例,尤其是对于企业而言。企业必须处理各种文档,交换它们等等。企业必须处理各种文档,交换它们,而 LLM 提供了一种解决此问题的简单方法。

这篇文章将向你展示 3 个关键内容:

  • 如何考虑使用 LLM 进行数据提取以及
  • 几个库如何处理该问题
  • 在 Instructor、Marvin 和 Guardrails AI 之间,哪个库最适合你

让我们开始吧!

1、为什么这很重要?

让我们介绍一些基本背景:

  • 自然信息(如文本或音频)包含有价值的信息。但处理这些信息是出了名的具有挑战性。例如,你可能需要一个 NLP 研究人员或 ML 工程师团队才能提取有价值的信息。这可能成本高昂,甚至无法开始构建某些类型的应用程序。
  • 大型语言模型已显示出生成非结构化文本的独特能力。更新颖的想法是从非结构化数据生成结构化数据的能力。

关键思想是:利用大型语言模型的强大功能,不仅可以生成信息,还可以将信息解析为可用于其他应用程序的结构化数据。

例如,你可能希望将 PDF 解析为可用于其他应用程序的结构化数据。

这之所以如此有趣,原因之一是对于企业而言,这是一个巨大的用例。有大量的非结构化 => 结构化数据提取用例,这些工具最终可能会对该用例的实际应用程度产生巨大影响。

这并不像使用 Dall-E 3 创建小猫和狗的图像那样令人兴奋,但很接近:

狗和小猫在街上奔跑

2、需要的技术

结构化数据提取和 LLM 需要哪些技术背景?

2.1 函数调用

这方面的基础是 OpenAI 函数调用 API。此 API 允许你调用 LLM 上的函数并返回结构化数据。这是我们将要讨论的所有库的基础。

函数调用功能的问题在于,构建这些类型的函数有点……繁琐,需要大量样板代码。例如,对于此函数:

def get_current_weather(location, unit="fahrenheit"):
    """Get the current weather in a given location"""
    weather_info = {
        "location": location,
        "temperature": "72",
        "unit": unit,
        "forecast": ["sunny", "windy"],
    }
    return json.dumps(weather_info)

你必须编写以下内容:


functions = [
    {
        "name": "get_current_weather",
        "description": "Get the current weather in a given location",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city and state, e.g. San Francisco, CA",
                },
                "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
            },
            "required": ["location"],
        },
    }
]
response = openai.ChatCompletion.create(
    model="gpt-3.5-turbo-0613",
    messages=messages,
    functions=functions,
    function_call="auto",  # auto is default, but we'll be explicit
)

以下库的承诺是你不必编写如此繁琐的提取或函数代码,只需编写一个简单的 pydantic 模型并返回结构化数据即可。

2.2 结构化数据

本教程中使用的另一个基础工具是 Pydantic

Pydantic 是一个数据验证库,它使用 Python 类型注解来验证数据。它允许你创建数据模型,并且它将自动验证传入的数据是否与模型匹配。在处理 API 或其他数据源时,这非常有用,因为你希望在开始使用数据之前确保数据的格式正确。

现在我们介绍了背景。让我们开始吧。

3、竞争者

我们将研究三个库,它们都是去年内创建的,而且都很新。

竞争者是:Marvin、Guardrails和Instructor。

让我们开始吧!

3.1 Marvin

Marvin 作为一个库,其抱负远不止是一个数据提取库。用创建者的话来说,

Marvin 是一个轻量级的 AI 工程框架,用于构建可靠、可扩展且易于信任的自然语言界面。

有时,使用生成式 AI 最具挑战性的部分是记住它不是魔法;它是软件。它是新的,它是不确定的,它非常强大 - 但仍然是软件。

Marvin 的目标是将构建可靠、可观察软件的最佳实践带入生成式 AI。作为为数据工程师做类似事情的 Prefect 背后的团队,我们将多年的开源开发工具经验和教训投入到 Marvin 的设计中。

在撰写本文时,Marvin 的 discord 不断增长,版本为 1.5.5(因此已准备好投入生产)。它还拥有我们竞争对手中最高的 github 星星以及最多的 fork。

Marvin 得到了 Prefect 的商业支持。

总体而言,Marvin 是一个感觉相当简单的库。你将在下面看到。

采用数据/社区数据:

  • Discord = 432
  • 版本 = 1.5.5
  • stars = ~3600
  • forks =367

3.2 Instructor

Instructor是一个帮助从 LLM 中获取结构化数据的库。它具有狭窄的依赖关系和简单的 API,不需要最终用户提供任何复杂的基本功能。

用作者的话来说:

Instructor 有助于确保你在使用 openai 的函数调用 API 时获得所需的确切响应类型。一旦你为所需的响应定义了 Pydantic 模型,Instructor 就会处理中间的所有复杂逻辑 - 从响应的解析/验证到无效响应的自动重试。这意味着我们可以“免费”构建验证器,并在提示和调用 openai 的代码之间明确分离关注点。

该库仍处于早期阶段,版本 0.2.9 的星数适中(1300),但由于基本上只有一个人在使用它,因此达到了超额的水平。

现在,仅仅因为它是一个人并不意味着它不好。事实上,我发现它非常容易使用。这只是意味着从长期维护的角度来看,它可能具有挑战性。需要注意的一点。

采用数据/社区数据

  • Discord = NA
  • 版本 = 0.2.9
  • Stars = ~1300
  • fork = 87

3.3 Guardrails AI

Guardrails 是一个似乎由公司支持的项目。该项目恰如其分地命名为 Guardrails,围绕以下概念:

什么是 Guardrails?

Guardrails AI 是一个开源库,旨在确保与大型语言模型 (LLM) 的可靠交互。它提供:

✅ 用于创建自定义验证器的框架 ✅ 提示、验证和重新提示的编排 ✅ 适用于各种用例的常用验证器库 ✅ 用于向 LLM 传达需求的规范语言

Guardrails 与 Instructor 和 Marvin 有着相似的理念。

Guardrails AI 使你能够定义和实施 AI 应用程序的保证,从结构化输出到质量控制。它通过创建一个“Guard”来实现这一点,Guard 是一个围绕 LLM 应用程序的防火墙式边界框,其中包含一组验证器。Guard 可以包括来自我们库的验证器或强制执行应用程序预期功能的自定义验证器。
—— 来自博客

目前,Guardrails 处于测试阶段,尚未正式发布,因此可能不适合生产用例。

但是,Guardrails 的使命更为广泛。他们旨在围绕 LLM 应用程序创建一个“边界框”来验证和确保质量。他们计划通过引入 .RAIL 文件类型(XML 的一种方言)来实现这一目标。这种雄心勃勃的方法超越了“使用 AI 提取数据”的简单概念。

这是他们对 RAIL 的解释。

🤖 什么是 RAIL?

.RAIL 是 XML 的一种方言,代表“可靠的 AI 标记语言”。它可用于定义:

1.  LLM 预期结果的结构。 (例如 JSON)
2. 预期结果中每个字段的类型。(例如字符串、整数、列表、对象)
3. 预期结果被视为有效的质量标准。(例如生成的文本应无偏差,生成的代码应无错误)
4. 如果未满足质量标准,应采取的纠正措施。(例如重新提出问题、筛选 LLM、以编程方式修复等)

警告...

我遇到的另一个挑战是依赖项,Guardrails 需要 pydantic 版本 1.10.9。Pydantic 的最新版本是 v.2.4.1。这在安装过程中给我带来了许多问题,我不得不为其设置专用环境。一个挑战是 pydantic 从 1.10 版本进行了彻底重写,因此对他们来说,重写所有内容可能具有挑战性。

采用数据/社区数据:

  • Discord = 689
  • 版本 = 0.2.4
  • Stars = ~2400
  • Forks = 158
用户体验

对于此用例,几个库大致相同。

你定义一个 pydantic 模型。然后可以:

  • 将其直接传递给 monkey-patched openai 函数调用(instructor),
  • 将其传递给带有你要进行的最终函数调用的包装函数(guardrails),或者
  • 在你的 pydantic 类上使用 python 装饰器函数并将你的函数传递给它。

让我们看看它们的实际效果!

4、测试应用程序 - 从播客记录中提取数据

我进行了一个简单的测试来比较这些不同的提取工具。我想看看它们会如何执行一项简单的任务 - 从播客记录中提取数据。

你可以看到此用例适用于广告、市场或竞争研究等许多领域。

4.1 预处理数据

我们需要做的第一件事是进行一些基本的预处理。 我们在其他教程中做过类似的事情,但我们将在这里再次进行。

首先,我们使用 RSS 源获取播客:

import feedparser
podcast_atom_link = "https://api.substack.com/feed/podcast/1084089.rss" # latent space podcastbbbbb
parsed = feedparser.parse(podcast_atom_link)
episode = [ep for ep in parsed.entries if ep['title'] == "Why AI Agents Don't Work (yet) - with Kanjun Qiu of Imbue"][0]
episode_summary = episode['summary']
print(episode_summary[:100])
<p><em>Thanks to the </em><em>over 11,000 people</em><em> who joined us for the first AI Engineer Su

现在我们解析 HTML:

from unstructured.partition.html import partition_html
parsed_summary = partition_html(text=''.join(episode_summary)) 
start_of_transcript = [x.text for x in parsed_summary].index("Transcript") + 1
print(f"First line of the transcript: {start_of_transcript}")
text = '\n'.join(t.text for t in parsed_summary[start_of_transcript:])
text = text[:3508] # shortening the transcript for speed & cost
First line of the transcript: 58

4.2 使用 Instructor 运行提取

现在让我们使用 Instructor 运行代码。如果你想跟随完整教程,请查看完整提取教程中的代码

from pydantic import BaseModel
from typing import Optional, List
from pydantic import Field
class Person(BaseModel):
    name: str
    school: Optional[str] = Field(..., description="The school this person attended")
    company: Optional[str] = Field(..., description="The company this person works for ")
class People(BaseModel):
    people: List[Person]
import openai
import instructor
instructor.patch()
response = openai.ChatCompletion.create(
    model="gpt-4",
    response_model=People,
    messages=[
        {"role": "user", "content": text},
    ]
)
print(response)
people=[Person(name='Alessio', school=None, company='Decibel Partners'), Person(name='Swyx', school=None, company='Smol.ai'), Person(name='Kanjun', school='MIT', company='Imbue'), Person(name='Josh', school=None, company='Imbue')]

总体而言,结果质量很高,我们得到了所有姓名和公司:

class Company(BaseModel):
    name:str
class ResearchPaper(BaseModel):
    paper_name:str = Field(..., description="an academic paper reference discussed")
    
class ExtractedInfo(BaseModel):
    people: List[Person]
    companies: List[Company]
    research_papers: Optional[List[ResearchPaper]]
response = openai.ChatCompletion.create(
    model="gpt-4",
    response_model=ExtractedInfo,
    messages=[
        {"role": "user", "content": text},
    ]
)
print(response)
people=[Person(name='Alessio', school=None, company='Decibel Partners'), Person(name='Swyx', school=None, company='Smol.ai'), Person(name='Kanjun', school='MIT', company='Imbue'), Person(name='Josh', school=None, company='Ember')] companies=[Company(name='Decibel Partners'), Company(name='Smol.ai'), Company(name='Imbue'), Company(name='Generally Intelligent'), Company(name='Ember'), Company(name='Sorceress'), Company(name='Dropbox'), Company(name='MIT Media Lab'), Company(name='OpenA')] research_papers=None

你可以看到它的效果非常好,几乎不用做任何工作就能为我们获取一大堆结构化数据。目前,我们只是在处理这些数据的摘录,但基本上没有进行任何 NLP 特定工作,我们就能获得一些非常强大的结果。

4.3 使用 Marvin 运行提取

现在让我们使用 Marvin 运行代码。如果你想跟随完整教程,请查看完整提取教程中的代码

!python --version
Python 3.11.5
import pydantic
print(pydantic.__version__)
import marvin
print(marvin.__version__)
2.4.2
1.5.5
from dotenv import load_dotenv
load_dotenv()
True
from marvin import ai_model
from pydantic import BaseModel
from typing import Optional, List
from pydantic import Field
class Person(BaseModel):
    name: str
    school: Optional[str] = Field(..., description="The school this person attended")
    company: Optional[str] = Field(..., description="The company this person works for")
@ai_model
class People(BaseModel):
    people: List[Person]
People(text)
People(people=[Person(name='Alessio', school=None, company='Residence at Decibel Partners'), Person(name='Swyx', school=None, company='Smol.ai'), Person(name='Kanjun', school='MIT', company='Imbue'), Person(name='Josh', school=None, company=None)])
class Company(BaseModel):
    name:str
class ResearchPaper(BaseModel):
    paper_name:str = Field(..., description="an academic paper reference discussed")
@ai_model(instructions="Get the following information from the text")
class ExtractedInfo(BaseModel):
    people: List[Person]
    companies: List[Company]
    research_papers: Optional[List[ResearchPaper]]
ExtractedInfo(text)
ExtractedInfo(people=[Person(name='Alessio', school=None, company='Residence at Decibel Partners'), Person(name='Swyx', school=None, company='Smol.ai'), Person(name='Kanjun', school='MIT', company='Imbue')], companies=[Company(name='Decibel Partners'), Company(name='Smol.ai'), Company(name='Imbue'), Company(name='Generally Intelligent'), Company(name='Ember'), Company(name='Sorceress')], research_papers=None)

注意:有趣的是,如果你不提供说明,您将不会获得那么好的结果。我从空白说明开始,结果很差。当我对instructor做同样的事情时,效果很好。

更改装饰器说明给了我更好的结果。这也允许你配置 LLM 以及温度等。

4.4 使用 Guardrails AI 运行提取

现在让我们使用 Marvin 运行代码。如果您想跟随完整教程,请查看完整提取教程中的代码

import guardrails as gd
from pydantic import BaseModel
from typing import Optional, List
from pydantic import Field
class Person(BaseModel):
    name: str
    school: Optional[str] = Field(..., description="The school this person attended")
    company: Optional[str] = Field(..., description="The company this person works for")
class People(BaseModel):
    people: List[Person]
guard = gd.Guard.from_pydantic(output_class=People, prompt="Get the following objects from the text:\n\n ${text}")
import openai
import os
raw_llm_output, validated_output = guard(
    openai.ChatCompletion.create,
    prompt_params={"text": text},
)
print(validated_output)
{'people': [{'name': 'Alessio', 'school': 'Residence at Decibel Partners', 'company': 'CTO'}, {'name': 'Swyx', 'school': 'Smol.ai', 'company': 'founder'}, {'name': 'Kanjun', 'school': 'Imbue', 'company': 'founder'}]}
class Company(BaseModel):
    name:str
class ResearchPaper(BaseModel):
    paper_name:str = Field(..., description="an academic paper reference discussed")
    
class ExtractedInfo(BaseModel):
    people: List[Person]
    companies: List[Company]
    research_papers: Optional[List[ResearchPaper]]
guard = gd.Guard.from_pydantic(output_class=ExtractedInfo, prompt="Get the following objects from the text:\n\n ${text}")
raw_llm_output, validated_output = guard(
    openai.ChatCompletion.create,
    prompt_params={"text": text},
)
print(validated_output)
/Users/williamchambers/miniconda3/envs/guardrails-extract/lib/python3.9/site-packages/guardrails/prompt/instructions.py:32: UserWarning: Instructions do not have any variables, if you are migrating follow the new variable convention documented here: https://docs.getguardrails.ai/0-2-migration/
  warn(
/Users/williamchambers/miniconda3/envs/guardrails-extract/lib/python3.9/site-packages/guardrails/prompt/prompt.py:23: UserWarning: Prompt does not have any variables, if you are migrating follow the new variable convention documented here: https://docs.getguardrails.ai/0-2-migration/
  warnings.warn(


incorrect_value={'people': [{'name': 'Alessio', 'school': 'Decibel Partners', 'company': 'CTO'}, {'name': 'Swyx', 'school': 'Smol.ai', 'company': 'founder'}, {'name': 'Kanjun', 'school': 'Imbue', 'company': 'founder'}], 'companies': [{'name': 'Decibel Partners'}, {'name': 'Residence'}, {'name': 'Smol.ai'}, {'name': 'Imbue'}]} fail_results=[FailResult(outcome='fail', metadata=None, error_message='JSON does not match schema', fix_value=None)]

注意:结果解析失败,而其他两个库则成功了。可能是实现中的问题,也可能是这个版本的错误,但它不是开箱即用的。

5、那么你应该选择哪个库呢?

总的来说,这些库在这个层面上感觉相似。他们试图做同样的事情并实现这一点。
如果我不得不推荐一个,我会推荐 Marvin。它很简单,有一个连贯的 API,使用起来感觉“流畅”。它还得到了一家老牌公司的商业支持,所以不太可能立即消失。这并不总是意味着成功,但至少是一个合理的迹象,表明这个库会存在一段时间。
如果你只是想把一些东西推出去,那就选择 Marvin。

如果这是一个你预计会花很多时间的领域,那么至少值得看看其他库。 Guardrails 凭借其 RAIL 抽象,试图做的远不止成为 AI 工程的框架 - 他们正试图以全新的视角来接近“结构化 LLM”。这一雄心意味着这个团队和这个项目可能会有更多成果,但现在还为时过早。

简而言之...

  • 易用性获胜者:instructor - instructor简单的 API 意味着没有太多移动部件,并且易于理解和上手。使用这些简单的构建块,你可以实现很多复杂性。
  • 通用性获胜者:Ask Marvin - Ask Marvin 似乎是一个很棒的库。它有用于提取、分类和复杂业务逻辑和流程的工具,甚至更大的 AI 应用程序。它是 AI 生态系统中的一个亮点,虽然它没有像 Langchain 这样的其他一些库那样大肆宣传,但在我看来它确实具有巨大的潜力。
  • 长期关注获胜者:Guardrails AI - Guardrails 是最难上手的库,但它们似乎最专注于提取。他们创建一种用于包装 LLM API 调用的语言/DSL 的雄心是一个雄心勃勃的愿景,我希望我们能看到更多来自团队的内容。我发现这个库有点难用,到目前为止,我花了最长的时间才弄清楚如何有效地使用它。这些示例有点过时,我无法弄清楚如何配置模型。最重要的是,它在我提供所有模型的第二个示例中失败了——这有点令人失望。

在考虑如何选型时,你和你的用例的一些关键考虑因素:

  • 成本效益:考虑使用这些库的成本。它们对于你的特定用例是否具有成本效益?请记住,使用 AI 模型的成本会迅速增加,因此评估这一方面很重要。
  • 预测的价值:预测对你的业务有什么价值?使用 LLM 进行提取对你来说是一种可行的业务策略吗?这在很大程度上取决于你的业务性质和具体用例。
  • 批量预测:你可以将多个预测批量化为一个以节省令牌数量和成本吗?批量预测可能是一种具有成本效益的策略,尤其是对于较大的项目。

6、结束语

总之,本教程带你了解了几个不同的基于 LLM 的数据提取库。我们希望你已经学到了很多关于如何在该领域使用不​​同库的知识,并希望你可以选择一个适合自己的库!


原文链接:Uncovering the Best LLM Data Extraction Library: Instructor vs. Marvin vs. Guardrails

汇智网翻译整理,转载请标明出处