第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(
多个装饰器 单个变量也可以链接多个装饰器,例如:
"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]