跳到内容

提示词模板

Outlines 提供了一种强大的领域特定语言,用于编写和管理提示词,我们称之为 提示词函数。提示词函数是 Python 函数,其文档字符串中包含提示词模板,其参数对应于提示词中使用的变量。调用时,提示词函数会返回使用参数值渲染后的模板。

提示词函数的目的是解决提示词设计中的几个常见问题:

  1. 快速构建复杂的提示词容易导致代码混乱。这个问题已经在 Web 开发社区通过模板化得到了解决,为什么不在这里使用它呢?
  2. 组合提示词很困难。为什么不直接组合函数呢?
  3. 将提示词与代码分离。函数封装允许提示词与代码之间实现清晰的分离。此外,就像任何函数一样,提示词函数可以从其他模块导入。

Outlines 使用 Jinja 模板引擎 来渲染提示词,这使得轻松组合复杂的提示词成为可能。

提示词渲染

提示词函数在渲染提示词方面有其自己的约定。这些约定旨在避免常见的提示词错误,但如果您正在进行一些不寻常的操作,可能会产生意想不到的后果。我们建议在使用提示词之前始终打印它。如果您想了解更多信息,还可以阅读参考部分。

你的第一个提示词

以下片段展示了一个非常简单的提示词。花括号 {{ }} 中的变量是您将传递给提示词函数的参数值的占位符。

greetings.py
from outlines import Template

prompt = """Hello, {{ name }}!
{{ question }}"""

greetings = Template.from_string(prompt)
prompt = greetings("user", "How are you?")
print(prompt)
Hello, user!
How are you?

如果函数的参数中缺少变量,Jinja2 将抛出 UndefinedError 异常

from outlines import Template

prompt = """Hello, {{ surname }}!"""
greetings = Template.from_string(prompt)
prompt = greetings("user")
Traceback (most recent call last):
  File "<stdin>", line 9, in <module>
  File "/home/remi/projects/normal/outlines/outlines/templates.py", line 38, in __call__
      return render(self.template, **bound_arguments.arguments)
  File "/home/remi/projects/normal/outlines/outlines/templates.py", line 213, in render
      return jinja_template.render(**values)
  File "/home/remi/micromamba/envs/outlines/lib/python3.9/site-packages/jinja2/environment.py", line 1301, in render
      self.environment.handle_exception()
  File "/home/remi/micromamba/envs/outlines/lib/python3.9/site-packages/jinja2/environment.py", line 936, in handle_exception
      raise rewrite_traceback_stack(source=source)
  File "<template>", line 1, in top-level template code
  jinja2.exceptions.UndefinedError: 'surname' is undefined

从文件中导入提示词

Outlines 允许您从文本文件中读取提示词模板。这样您就可以构建“空白精确”的提示词,并将它们与代码独立开来版本控制。自从 Outlines 发布以来,我们发现自己经常采用这种模式

"""Hello, {{ name }}!
{{ question }}
"""
from outlines import Template

greetings = Template.from_file("prompt.txt")
prompt = greetings("John Doe", "How are you today?")
Hello, John Doe!
How are you today?

少样本提示

少样本提示词可能导致代码混乱。提示词函数允许您在模板中遍历列表或字典。在下面的示例中,我们演示了如何通过将一个包含键 questionanswer 的字典列表传递给提示词函数来生成提示词

prompt.txt
{{ instructions }}

Examples
--------

{% for example in examples %}
Q: {{ example.question }}
A: {{ example.answer }}

{% endfor %}
Question
--------

Q: {{ question }}
A:
render.py
from outlines import Template

instructions = "Please answer the following question following the examples"
examples = [
    {"question": "2+2=?", "answer":4},
    {"question": "3+3=?", "answer":6}
]
question = "4+4 = ?"

few_shots = Template.from_file("prompt.txt")
prompt = few_shots(instructions, examples, question)
print(prompt)
Please answer the following question following the examples

Examples
--------

Q: 2+2=?
A: 4

Q: 3+3=?
A: 6

Question
--------

Q: 4+4 = ?
A:

条件、过滤器等

Jinja2 除了循环之外还有许多此处未描述的特性:条件判断、过滤、格式化等。有关模板语言语法的更多信息,请参阅 Jinja 文档。Jinja 语法功能强大,如果您正在构建复杂的提示词,我们建议您花一些时间阅读他们的文档。

工具

有几个项目(例如ToolformerViperGPTAutoGPT等)表明,通过在提示词中描述外部函数的功能,我们可以“教”语言模型使用这些函数。在这些项目中,相同的信息通常会重复两次:函数实现、名称、文档字符串或参数被复制粘贴到提示词中。这既繁琐又容易出错;您可以直接从 Outlines 提示词函数中提取这些信息。

from outlines import Template

def my_tool(arg1: str, arg2: int):
    """Tool description.

    The rest of the docstring
    """
    pass

prompt = """{{ question }}

COMMANDS
1. {{ tool | name }}: {{ tool | description }}, args: {{ tool | args }}

{{ tool | source }}
"""

tool_prompt = Template.from_string(prompt)
prompt = tool_prompt("Can you do something?", my_tool)
print(prompt)
Can you do something?

COMMANDS
1. my_tool: Tool description., args: arg1: str, arg2: int

def my_tool(arg1: str, arg2: int):
    """Tool description.

    The rest of the docstring
    """
    pass

JSON 响应格式

为了用语言模型构建可靠的链,我们通常需要指示它们以我们期望的格式返回响应。

如果没有提示词模板,创建解析函数(例如 Pydantic 模型)和在提示词中写入期望的 Schema 之间,信息会被重复两次。这可能导致难以调试的错误。

Outlines 允许您直接从 Outlines 提示词函数中提取 Pydantic 模型的 JSON Schema,或美观地打印字典。

```python from pydantic import BaseModel, Field

from outlines import Template

class MyResponse(BaseModel):
    field1: int = Field(description="an int")
    field2: str


my_prompt = Template.from_string("""{{ response_model | schema }}""")
prompt = my_prompt(MyResponse)
print(prompt)
# {
#   "field1": "an int",
#   "field2": "<field2>"
# }
```
response = {
    "field1": "<field1>",
    "field2": "a string"
}

my_prompt(MyResponse)
# {
#   "field1": "<field1>",
#   "field2": "a string"
# }

格式约定

提示词模板在渲染从字符串读取的模板时有其约定,这些约定旨在避免提示词错误并帮助格式化。

空白字符

如果您有使用三引号字符串的经验,您就会知道缩进会影响字符串的格式。提示词函数遵循一些约定,这样您在编写提示词时就不必考虑缩进。

首先,无论您是紧接在三引号之后开始提示词,还是在下一行开始,都不会影响格式化

from outlines import Template


prompt1 = Template.from_string(
"""My prompt
"""
)

prompt2 = Template.from_string(
"""
My prompt
"""
)

print(prompt1())
print(prompt2())
My prompt
My prompt

缩进是相对于文档字符串的第二行计算的,并且会移除开头的空格

from outlines import Template

example1 = Template.from_string(
"""First line
Second line
"""
)

example2 = Template.from_string(
"""
    Second line
    Third line
"""
)

example3 = Template.from_string(
"""
    Second line
      Third line
"""
)

print(example1())
print(example2())
print(example3())
First line
Second line

Second line
Third line

Second line
  Third line

不会移除尾随的空白字符,除非它们紧跟在换行符 \ 后面(参见换行)。

换行

您可以使用反斜杠 \ 来断开长文本行。它将渲染为单行

from outlines import Template

example = Template.from_string(
"""
Break in \
several lines \
But respect the indentation
    on line breaks.
And after everything \
Goes back to normal
"""
)

print(example())
Break in several lines But respect the indentation
    on line breaks.
And after everything Goes back to normal