跳到内容

基于语法的结构化生成

您可以传递任何EBNF格式的无上下文语法,Outlines将生成符合该语法的有效输出。

from outlines import models, generate

arithmetic_grammar = """
    ?start: expression

    ?expression: term (("+" | "-") term)*

    ?term: factor (("*" | "/") factor)*

    ?factor: NUMBER
           | "-" factor
           | "(" expression ")"

    %import common.NUMBER
"""

model = models.transformers("WizardLM/WizardMath-7B-V1.1")
generator = generate.cfg(model, arithmetic_grammar)
sequence = generator(
  "Alice had 4 apples and Bob ate 2. "
  + "Write an expression for Alice's apples:"
)

print(sequence)
# (8-2)
免责声明

实验性

Outlines 当前社区贡献的CFG结构化生成实现是实验性的。这并不反映 .txt 产品的性能,在该产品中,我们已将基于语法的结构化生成优化到与基于正则表达式的结构化生成一样快。此外,除了使用增量/部分解析外,它与我们在技术报告中描述的方法并未完全一致。此功能仍在开发中,需要进行性能增强和错误修复才能实现理想的实现。更多详细信息,请参阅我们在 GitHub 上与语法相关的开放问题

贪婪

为了缓解性能问题,CFG结构化生成将使用拒绝采样,并首先遍历对数最高(highest logit)的候选token,一旦选择单个有效token ID即完成。这实际上是贪婪生成。

可直接使用的语法

Outlines 包含一个(小型)语法库,可以直接导入和使用。我们可以将前面的示例改写为

from outlines import models, generate

arithmetic_grammar = outlines.grammars.arithmetic

model = models.transformers("WizardLM/WizardMath-7B-V1.1")
generator = generate.cfg(model, arithmetic_grammar)
sequence = generator(
  "Alice had 4 apples and Bob ate 2. "
  + "Write an expression for Alice's apples:"
)

print(sequence)
# (8-2)

当前可用的语法如下

  • 通过 outlines.grammars.arithmetic 使用算术语法
  • 通过 outlines.grammars.json 使用 JSON 语法

如果您希望将更多语法添加到仓库中,请提交 议题拉取请求

语法指南

语法是规则和终结符的列表,用于定义一种语言

  • 终结符定义了语言的词汇;它们可以是字符串、正则表达式或这些及其他终结符的组合。
  • 规则定义了该语言的结构;它们是终结符和规则的列表。

Outlines 使用 Lark 库 使大型语言模型能够生成符合特定语法的文本,因此它使用 Lark 可理解的语法格式,该格式基于 EBNF 语法。请阅读 Lark 文档 以获取更多语法详细信息,以下是一个小入门指南,希望能帮助您开始。

在下文中,我们将为 Python 的 turtle 库 定义一种类似 LOGO 的玩具语言

终结符

一个 turtle 可以执行 4 种不同的 MOVEMENT 移动指令:向前 (f)、向后 (b)、向右转 (r) 和向左转 (l)。它可以在每个方向移动 NUMBER 步,并以指定的 COLOR 绘制线条。这些定义了我们语言的词汇。

MOVEMENT: "f"|"b"|"r"|"l"
COLOR: LETTER+

%import common.LETTER
%import common.INT -> NUMBER
%import common.WS
%ignore WS

% 开头的行称为“指令”。它们允许导入预定义的终结符和规则,例如 LETTERNUMBERLETTER+ 是一个正则表达式,表示 COLOR 由至少一个 LETTER 组成。最后两行指定我们将忽略语法中的空白字符 (WS)。

规则

现在我们需要定义我们的规则,通过分解我们可以通过 Python 程序发送给 turtle 的指令。在程序的每一行,我们可以选择一个方向并执行给定的步数,或者改变用于绘制图案的颜色。我们还可以选择开始填充、进行一系列移动,然后停止填充。我们还可以选择重复一系列移动。

我们可以轻松写出前两条规则

instruction: MOVEMENT NUMBER   -> movement
           | "c" COLOR [COLOR] -> change_color

其中 movementchange_color 表示规则的别名。空格表示连接元素,| 表示选择其中一个元素。fillrepeat 规则稍微复杂一些,因为它们应用于代码块,而代码块由指令组成。因此,我们定义一个新的 code_block 规则,它引用 instruction 并完成我们的规则实现

instruction: MOVEMENT NUMBER            -> movement
           | "c" COLOR [COLOR]          -> change_color
           | "fill" code_block          -> fill
           | "repeat" NUMBER code_block -> repeat

code_block: "{" instruction "}"

现在我们可以编写完整的语法

start: instruction+

instruction: MOVEMENT NUMBER            -> movement
            | "c" COLOR [COLOR]          -> change_color
            | "fill" code_block          -> fill
            | "repeat" NUMBER code_block -> repeat

code_block: "{" instruction+ "}"

MOVEMENT: "f"|"b"|"l"|"r"
COLOR: LETTER+

%import common.LETTER
%import common.INT -> NUMBER
%import common.WS
%ignore WS

注意 start 规则,它定义了语法的起点,即程序必须从哪个规则开始。这个完整的语法允许我们解析如下程序

c red yellow
    fill { repeat 36 {
        f200 l170
    }}

解析结果,即解析树,可以轻松地转换为使用 turtle 库绘制图案的 Python 程序。

下一步

本节提供了语法及其可能性的简要概述。请查阅 Lark 文档 以获得更详细的解释和更多示例。