进阶 lmql.ai 2026-10-11 18:10:44 · 30 阅读

第11章 Decorators(装饰器)

装饰器

Custom hooks into the query execution process 变量装饰器提供了一种简单机制,可在解码过程中执行自定义的变量值转换。它们支持对模型输出进行自定义预处理、后处理或流式处理。

后处理装饰器 装饰器的标准用途是对生成的变量值进行后处理:

```python def screaming(value): """装饰器:将字符串转换为大写""" return value.upper()

"Say 'this is a test':[@screaming TEST]" ``` promptdown: Say 'this is a test': TEST THIS IS A TEST

上述示例展示了一个简单的装饰器,它将生成的变量值转换为大写。默认情况下,装饰器在变量解码完成后应用于该变量值。

值与提示词表示 除了上述简单用法外,装饰器的函数签名也可以扩展,以获取更多变量上下文信息:

```python from lmql.runtime.program_state import ProgramState from typing import Any

def aslist(value: Any, prompt_value: str, context: ProgramState): """装饰器:将逗号分隔的字符串转换为 List[str]""" return value.split(", "), prompt_value

"A (comma-separated) list of pancake ingredients: [@aslist ANSWER]" ``` promptdown: A (comma-separated) list of pancake ingredients: ANSWER Flour, Eggs, Milk, Baking Powder, Salt, Butter

这里,装饰器 aslist() 有三个参数: - value: any: ANSWER 完全生成后经过后处理得到的值 - prompt_value: str: 模型生成 ANSWER 时的文本表示 - context: ProgramState: 当前程序状态

装饰器返回一个元组 (value, prompt_value),其中 value 是 ANSWER 更新后的程序值,prompt_value 是更新后的文本表示。 区分 value 和 prompt_value 使得装饰器能够修改 ANSWER 的程序值以及其在提示词中使用的文本表示。例如,在上述代码片段中,后续程序可以将 ANSWER 作为 Python List[str] 的配料列表进行操作,而模型仍然看到列表的逗号分隔字符串表示。

如果装饰器未以元组形式返回 value 和 prompt_value,则 prompt_value 默认为 str(value),即转换后值的字符串表示。

流式装饰器 另一种形式的装饰器是流式装饰器。流式装饰器在解码过程中应用于变量值,即当变量正在被解码时,装饰器会被调用以处理变量的中间值。这使得可以针对变量进行输出流式处理:

```python from lmql.runtime.program_state import ProgramState

@lmql.decorators.streaming def stream(value: str, context: ProgramState): """装饰器:流式输出变量值""" print("VALUE", [value])

"Enumerate the alphabet without spaces:[@stream TEST]" ```

在执行此查询期间,stream() 会为 TEST 的每个中间值调用,从而产生如上所示的输出。这允许在生成输出时进行流式传输,例如在聊天应用或命令行中显示部分响应。

bash 输出示例: ``` VALUE [''] VALUE [''] VALUE ['\n'] VALUE ['\n\n'] VALUE ['\n\nABC'] VALUE ['\n\nABCDEF'] ... ```

预处理装饰器 最后,装饰器还可以挂钩到查询执行过程中变量生成开始前的阶段。例如,考虑以下缓存装饰器函数:

```python from lmql.runtime.program_state import ProgramState from lmql.language.qstrings import TemplateVariable

cached_values = { "ITEM0": "A shopping cart" }

@lmql.decorators.pre def cache(variable: TemplateVariable, context: ProgramState): """装饰器:根据变量名称缓存变量值""" return cached_values.get(variable.name, variable)

"""A list of things not to forget when going to the supermarket: -[@cache ITEM0] -[@cache ITEM1] -[@cache ITEM0] """ where STOPS_BEFORE(ITEM0, "\n") and STOPS_BEFORE(ITEM1, "\n") ```

给定预定义的每个变量名称(如 ITEM0)的固定值列表,查询执行实际上仅为 ITEM1 调用模型。对于 ITEM0 的所有出现,使用缓存的值替代。

为此,预处理装饰器要么返回提供的 variable: TemplateVariable 对象,表示变量应按正常方式生成;要么返回字符串值,表示变量应被固定值替代。

高级装饰器行为 基于类的装饰器 更高级的装饰器行为可能需要挂钩到查询执行过程的多个阶段。为此,可以实现 lmql.runtime.decorators.LMQLDecorator 类,其实例可以作为装饰器函数在查询执行过程的多个阶段被调用。例如,你可以将变量注解为 "Hello [@DecoratorCls() WORLD]"。

多个装饰器 单个变量也可以链接多个装饰器,例如:

"Say 'this is a test':[@a @b @c TEST]"

在查询执行期间,预阶段(如果定义)依次调用 a()、b() 和 c(),将每个装饰器的结果传递给下一个。后阶段(如果定义)则以相反顺序调用 c()、b() 和 a(),将每个装饰器的结果传递给下一个。流式阶段的装饰器没有返回值,因此执行顺序没有特定影响。

装饰器参数 装饰器也可以提供参数。此类运算符的实现与标准 Python 中装饰器的实现类似,通过局部变量捕获实现:

```python def prefix(prefix: str): # 实际的装饰器函数 def decorator(value: str): return prefix + value.strip() return decorator

"Say 'this is a test':[@prefix('PREFIX: ') TEST]" ```

model-output:: Say 'this is a test': [TEST PREFIX: This is a test]

评论 (0)