第29章 LMQL与Python集成指南
Python 集成
用 Python 调用 LMQL。在现有应用中集成 LMQL 的首选方式就是使用 lmql Python 包。该包提供了一系列函数,让你可以直接在 Python 中定义和运行 LMQL 程序。
查询函数
在 Python 中,一段 LMQL 代码表现为标准的 Python 函数。这意味着你可以直接定义、参数化并调用 LMQL 代码,无需跳出现有的 Python 应用。
为实现这一目标,LMQL 提供了三个入口:
@lmql.query 装饰器:使用标准的 def 语法定义 LMQL 查询函数,支持捕获和访问周围的 Python 作用域。 lmql.run 函数:直接运行一段 LMQL 代码字符串作为查询,无需先定义函数。 lmql.F(...):将 LMQL 代码片段作为纯 lambda 表达式求值,这与 Python 的 lambda 关键字非常相似。
这三种方法在内部都会为提供的代码构建一个 LMQL 查询函数。本章首先讨论如何使用 lmql.query、lmql.run 和 lmql.F 定义和运行查询函数,随后介绍查询结果格式以及如何配置查询(例如指定模型、解码器和其他参数)。
lmql.query
@lmql.query 装饰器允许你将一段 LMQL 代码直接暴露为 Python 函数,并从现有代码中调用它:
```python import lmql
@lmql.query def chain_of_thought(question): '''lmql # Q&A 提示模板 "Q: {question}\n" "A: Let's think step by step.\n" "[REASONING]" "Thus, the answer is:[ANSWER]."
# 仅将 ANSWER 返回给调用者 return ANSWER '''
print(chain_of_thought('Today is the 12th of June, what day was it 1 week ago?')) ``` 输出: `5th of June.`
注意,实际的 LMQL 程序定义在函数的 docstring 中,这使得 LMQL 代码能够无缝嵌入。函数参数可以自由传入 LMQL,函数的返回值会传回给调用者。查询程序还可以捕获周围上下变量,从而在 LMQL 内部发挥 Python 的全部威力。
Jupyter 笔记本
你在 Jupyter Notebook 中可能会遇到与 @lmql.query 装饰器相关的问题,因为笔记本环境默认是异步的。为了解决这个问题,你可以将查询函数声明为 async,并在调用时使用 await 关键字。
或者,你也可以安装 nest_asyncio 包并调用 nest_asyncio.apply(),以在笔记本中启用嵌套事件循环。
变量捕获
@lmql.query 装饰器还允许你访问周围上下文的变量,并使其对查询程序可用。这是在定义时通过自动捕获函数闭包来实现的:
```python import re
a = 12
@lmql.query def query(): '''lmql # 访问全局命名空间中的 'a' "Tell me a fun fact about {a}: [FACT]" # 使用导入的 're' 模块 return re.sub(r'\d+', '[NUMBER]', FACT) '''
print(query()) ``` 输出: `[NUMBER] is the smallest number with exactly six divisors ([NUMBER], [NUMBER], [NUMBER], [NUMBER], [NUMBER], [NUMBER]).`
如图所示,在 @lmql.query 函数内部,你可以完全访问周围上下文,包括外部作用域中定义的任何变量和导入项。这使得 LMQL 能够灵活地集成到你现有的代码库中。
lmql.run
若要直接运行一段 LMQL 代码字符串,而不必先定义函数,可以使用 lmql.run 函数:
```python query_string = """ "Q: {question}\\n" "A: Let's think step by step.\\n" "[ANSWER]" """
await lmql.run(query_string, question="What is 2x3?") ```
注意,在使用 lmql.run 时,查询字符串中的换行符 \n 必须转义为 \\n,以避免语法错误。若要同步运行(不使用 await),可以使用 lmql.run_sync 函数代替。
转义 LMQL 专用控制字符
在直接从字符串构建查询时,务必正确转义 LMQL 专用的控制字符,以避免生成的查询程序出现语法错误。
预编译查询
使用 lmql.run 时,每次调用都会将查询字符串编译为查询函数。为了避免这种情况,你可以使用 lmql.query 函数配合字符串,创建一个只编译一次但可多次调用的函数:
```python chain_of_thought = lmql.query(""" "Q: {question}\\n" "A: Let's think step by step.\\n" "[ANSWER]" """, is_async=False)
chain_of_thought("What is 2x3?") ```
这里,设置 is_async=False 确保查询可以同步执行。
lmql.F
最后,lmql.F 提供了一种轻量级的方式来将 LMQL 代码片段作为简单的 lambda 表达式求值,这与 Python 的 lambda 关键字非常相似。
这为在代码中集成小型基于 LLM 的工具提供了一个轻量级入口,无需编写完整的 LMQL 程序:
```python summarize = lmql.F("Summarize the following in a \ few words: {data}: [SUMMARY]")
main_subject = lmql.F("What is the main subject (noun) \ of the following text? {data}: [SUBJECT]", "len(TOKENS(SUBJECT)) < 20")
text = "LMQL generalizes natural language prompting, ..."
summarize(data=text) # LMQL improves natural language prompting with # Python and fixed answer templates for better control over LLMs.
main_subject(data=text) # LMQL ```
从语法上看,lmql.F 表达式对应于单个 LMQL 提示语句,但不带 " 引号。lmql.F 函数返回一个可调用对象,可以像普通查询函数一样使用。
返回值:如果 lmql.F 只包含一个占位符变量,其生成的值将用作函数的返回值。否则,将返回一个包含所有占位符值的字典。
约束条件:要在 lmql.F 表达式中指定约束条件,可以传入 constraints=... 字符串参数,它将被解析并像普通 LMQL 提示语句中的 where 子句一样执行。
查询结果
通常,查询函数的结果由 return 语句的使用以及执行查询所用的解码算法决定:
带有 return 语句的查询函数
带有 return 语句的查询函数将返回该 return 语句的值。如果使用了具有多个输出序列的解码算法(例如 sample(n=2)),返回值将是所有返回值的列表。
```python number = lmql.F("A random number: [JOKE: int]")
# 采样两个结果 number(decoder="sample", n=2) ``` 结果: `[1, 25]`
不带 return 语句的查询函数
如果未指定 return 语句,查询函数的返回值将是一个指定的 lmql.LMQLResult 对象,其中包含查询程序中定义的所有变量的最后一次赋值:
```python class LMQLResult: # 替换了所有变量的完整提示词 prompt: str # 所有已赋值的模板变量的字典 variables: Dict[str, str] ```
这允许你在查询执行后检查完整的查询提示词以及单个变量的值:
```python joke = lmql.query(""" "Q: Tell me a joke about plants\\n" "A: [JOKE]" """, is_async=False)
joke() ``` 结果: `LMQLResult(prompt='Q: Tell me a joke about plants\nA: Why did the tomato turn red?\n\nBecause it saw the salad dressing!', variables={'JOKE': 'Why did the tomato turn red?\n\nBecause it saw the salad dressing!'}, distribution_variable=None, distribution_values=None)`
如果使用了具有多个输出序列的解码算法(例如 sample(n=2)),返回值将是一个包含所有此类 LMQLResult 对象的列表。
查询配置
为了进一步控制查询执行(例如设置模型、解码器等),你可以为任何 lmql.query、lmql.run 或 lmql.F 调用提供额外的配置参数:
```python @lmql.query(model=lmql.model("chatgpt"), decoder="argmax") def chain_of_thought(question): ... ```
支持以下参数:
model=
覆盖默认值
要在调用时覆盖这些配置参数,你还可以将它们作为额外关键字参数传递给查询函数调用:
```python # 使用 'gpt2-xl' 模型执行 'chain_of_thought' chain_of_thought("What is the meaning of life?", model="gpt2-xl", temperature=0.5) ```
结果对象是一个数据类,具有以下字段:
```python class LMQLResult: # 替换了所有变量的完整提示词 prompt: str # 所有已赋值的模板变量的字典 variables: Dict[str, str] ```