Press "Enter" to skip to content

使用spacy-llm进行优雅的提示版本控制和LLM模型配置

使用 spacy-llm 简化提示管理并创建数据提取任务

整洁的办公桌,如果您使用spacy-llm,您的代码将是这样的哈哈

管理提示和处理 OpenAI 请求失败可能是一项具有挑战性的任务。幸运的是,spaCy 推出了强大的工具 spacy-llm,它简化了提示管理,并消除了需要从头开始创建自定义解决方案的需求。

在本文中,您将学习如何利用 spacy-llm 创建一个使用提示从文本中提取数据的任务。我们将深入了解 spacy 的基础知识,并探索一些 spacy-llm 的功能。

spaCy 和 spacy-llm 101

spaCy 是一个用于 Python 和 Cython 的高级自然语言处理库。在处理文本数据时,通常需要进行多个处理步骤,例如标记化和词性标注。为了执行这些步骤,spaCy 提供了 nlp 方法,该方法调用一个处理流水线。

spaCy v3.0 引入了 config.cfg,这是一个可以包含这些流水线详细设置的文件。

config.cfg 使用了 confection,这是一个允许创建任意对象树的配置系统。例如,confection 解析以下 config.cfg

[training]patience = 10dropout = 0.2use_vectors = false[training.logging]level = "INFO"[nlp]# This uses the value of training.use_vectorsuse_vectors = ${training.use_vectors}lang = "en"

为:

{  "training": {    "patience": 10,    "dropout": 0.2,    "use_vectors": false,    "logging": {      "level": "INFO"    }  },  "nlp": {    "use_vectors": false,    "lang": "en"  }}

每个流水线使用组件,而 spacy-llm 将流水线组件存储在目录中的注册表中。这个库也来自 Explosion,它引入了函数注册表,可以有效地管理这些组件。一个 llm 组件定义了两个主要设置:

  • 一个任务,定义要发送到 LLM 的提示以及解析结果响应的功能
  • 一个模型,定义模型以及如何连接到它

要在我们的流水线中包含使用 LLM 的组件,我们需要按照几个步骤进行。首先,我们需要创建一个任务并将其注册到注册表中。接下来,我们可以使用一个模型来执行提示并检索响应。现在是时候做所有这些事情,以便我们可以运行流水线了

创建一个从文本中提取数据的任务

我们将使用 https://dummyjson.com/ 上的引用,并创建一个从每个引用中提取上下文的任务。我们将创建提示,注册任务,最后创建配置文件。

1. 提示

spacy-llm 使用 Jinja 模板来定义说明和示例。 {{ text }} 将被我们提供的引用替换。这是我们的提示:

您是从文本中提取上下文的专家。您的任务是接受一个引用作为输入,并提供引用的上下文。此上下文将用于将引用分组在一起。在您的答案中不要加入任何其他文本,并在 3 个单词以内提供上下文。{# 空格 #}{# 空格 #}这里是需要分类的引用{# 空格 #}{# 空格 #}引用:'''{{ text }}'''上下文

2. 任务类

现在让我们为任务创建类。该类应该实现两个函数:

  • generate_prompts(docs: Iterable[Doc]) -> Iterable[str]:一个接受spaCy Doc对象列表并将其转换为提示列表的函数
  • parse_responses(docs: Iterable[Doc], responses: Iterable[str]) -> Iterable[Doc]:将LLM的输出解析为spaCy Doc对象的函数

generate_prompts将使用我们的Jinja模板,parse_responses将向我们的Doc添加属性上下文。这是QuoteContextExtractTask类:

from pathlib import Pathfrom spacy_llm.registry import registryimport jinja2from typing import Iterablefrom spacy.tokens import DocTEMPLATE_DIR = Path("templates")def read_template(name: str) -> str:    """读取模板"""    path = TEMPLATE_DIR / f"{name}.jinja"    if not path.exists():        raise ValueError(f"{name}不是有效的模板。")    return path.read_text()class QuoteContextExtractTask:  def __init__(self, template: str = "quotecontextextract.jinja", field: str = "context"):    self._template = read_template(template)    self._field = field  def _check_doc_extension(self):     """如有需要,添加扩展。"""     if not Doc.has_extension(self._field):         Doc.set_extension(self._field, default=None)  def generate_prompts(self, docs: Iterable[Doc]) -> Iterable[str]:    environment = jinja2.Environment()    _template = environment.from_string(self._template)    for doc in docs:        prompt = _template.render(            text=doc.text,        )        yield prompt    def parse_responses(      self, docs: Iterable[Doc], responses: Iterable[str]  ) -> Iterable[Doc]:    self._check_doc_extension()    for doc, prompt_response in zip(docs, responses):            try:        setattr(            doc._,            self._field,            prompt_response.replace("Context:", "").strip(),        ),      except ValueError:        setattr(doc._, self._field, None)              yield doc

现在我们只需要将任务添加到spacy-llm llm_tasks注册表中:

@registry.llm_tasks("my_namespace.QuoteContextExtractTask.v1")def make_quote_extraction() -> "QuoteContextExtractTask":    return QuoteContextExtractTask()

3. config.cfg文件

我们将使用OpenAI的GPT-3.5模型。spacy-llm已经有一个模型了,我们只需要确保将密钥设置为环境变量:

export OPENAI_API_KEY="sk-..."export OPENAI_API_ORG="org-..."

为了构建运行流水线的nlp方法,我们将使用spacy-llm的assemble方法。该方法从一个.cfg文件中读取。该文件应该引用GPT-3.5模型(它已经在注册表中)和我们创建的任务:

[nlp]lang = "en"pipeline = ["llm"]batch_size = 128[components][components.llm]factory = "llm"[components.llm.model]@llm_models = "spacy.GPT-3-5.v1"config = {"temperature": 0.1}[components.llm.task]@llm_tasks = "my_namespace.QuoteContextExtractTask.v1"

4. 运行流水线

现在我们只需要将所有内容组合在一起并运行代码:

import osfrom pathlib import Pathimport typerfrom wasabi import msgfrom spacy_llm.util import assemblefrom quotecontextextract import QuoteContextExtractTaskArg = typer.ArgumentOpt = typer.Optiondef run_pipeline(    # fmt: off    text: str = Arg("", help="要对其执行文本分类的文本。"),    config_path: Path = Arg(..., help="要使用的配置文件的路径。"),    verbose: bool = Opt(False, "--verbose", "-v", help="显示额外信息。"),    # fmt: on):    if not os.getenv("OPENAI_API_KEY", None):        msg.fail(            "找不到OPENAI_API_KEY环境变量。"            "运行'export OPENAI_API_KEY=...'设置它并重试。",            exits=1,        )    msg.text(f"从{config_path}加载配置", show=verbose)    nlp = assemble(        config_path    )    doc = nlp(text)    msg.text(f"引用:{doc.text}")    msg.text(f"上下文:{doc._.context}")if __name__ == "__main__":    typer.run(run_pipeline)

然后运行:

python3 run_pipeline.py "我们必须在显眼的消费和有意识的资本主义之间取得平衡。" ./config.cfg>>> 引用:我们必须在显眼的消费和有意识的资本主义之间取得平衡。背景:商业道德。

如果你想要改变提示语,只需创建另一个Jinja文件,并像我们创建第一个任务一样创建一个my_namespace.QuoteContextExtractTask.v2任务。如果你想要改变温度,只需在config.cfg文件中修改参数。很不错,对吧?

最后的思考

处理OpenAI REST请求的能力和其直观的存储和版本化提示的方法是spacy-llm中我最喜欢的东西。此外,该库提供了一个用于缓存提示和文档响应的缓存功能,提供少量示例的few-shot提示功能,以及日志记录等功能。

你可以在这里查看今天的全部代码:https://github.com/dmesquita/spacy-llm-elegant-prompt-versioning。

如常,感谢阅读!

Leave a Reply

Your email address will not be published. Required fields are marked *