什么是 ‘Dynamic Prompt Templating’?利用 Jinja2 模板实现具备逻辑判断(If/Else)的复杂提示词

各位编程爱好者、AI工程师们,大家好!

今天,我们齐聚一堂,共同探讨一个在构建智能应用,尤其是基于大型语言模型(LLM)的应用中日益重要的主题——“动态提示词模板化”(Dynamic Prompt Templating)。在当前LLM技术飞速发展的时代,如何高效、灵活地与这些模型交互,已成为决定应用质量与用户体验的关键。静态的、硬编码的提示词早已无法满足我们日益复杂的业务需求。我们需要一种机制,能够让我们的提示词像代码一样,具有生命力,能够根据上下文、用户输入、系统状态等动态地调整自身。

而这,正是动态提示词模板化所要解决的核心问题。我们将深入剖析其原理,并以强大的Python模板引擎Jinja2作为我们的工具,手把手地构建出能够实现复杂逻辑判断(If/Else)、循环、模块化等高级功能的动态提示词系统。

第一章:理解提示词工程与动态化的必然性

在深入技术细节之前,我们首先需要对“提示词工程”(Prompt Engineering)有一个清晰的认知。简单来说,提示词工程就是设计和优化与大型语言模型交互的输入文本(即“提示词”),以引导模型生成我们期望的、高质量的输出。一个好的提示词,如同给模型下达了一份清晰、明确、无歧义的任务说明书。

1.1 静态提示词的局限性

早期或简单的LLM应用中,我们可能习惯于使用静态提示词。例如:

“请总结以下文章:[文章内容]”
“请帮我生成一个关于[主题]的营销文案。”

这种方式在需求固定、变化较少时尚可接受。然而,一旦我们的应用场景变得复杂,静态提示词的局限性便会立即显现:

  1. 缺乏灵活性: 无法根据不同的用户、不同的数据或不同的业务逻辑动态调整提示词内容。
  2. 维护困难: 每次需求变更,都需要手动修改代码中的字符串,容易出错且效率低下。
  3. 可读性差: 复杂的业务逻辑通过字符串拼接实现时,代码会变得难以理解和维护。
  4. 难以复用: 相似的提示词结构需要重复编写,导致代码冗余。
  5. 用户体验受限: 无法提供个性化、上下文感知的交互。

举例来说,一个电商客服机器人,如果只是简单地回复“您好,请问有什么可以帮助您的?”,那么它将无法处理“我上周购买的订单号为XYZ的商品,现在想退货,我的会员等级是黄金会员,请问有什么特殊政策吗?”这样的复杂请求。我们需要提示词能够感知到“退货”、“订单号”、“会员等级”等多个维度信息,并据此生成一个包含所有相关细节的、针对性的回复指令。

1.2 动态提示词模板化应运而生

动态提示词模板化正是为了解决上述痛点而诞生的。它的核心思想是:将提示词的内容与业务逻辑和数据分离。我们不再直接编写最终的提示词,而是设计一个“提示词模板”,其中包含占位符和逻辑结构。然后,在运行时,根据实际的数据和条件,将这些占位符填充,并执行逻辑,最终生成完整的、定制化的提示词。

这种方式带来的好处是显而易见的:

  • 高度灵活性: 提示词能够根据任何运行时数据进行调整。
  • 易于维护: 模板集中管理,修改逻辑或内容更加方便。
  • 提高可读性: 模板语言通常比纯代码字符串拼接更具可读性。
  • 代码复用: 可以定义可复用的模板片段和宏。
  • 强大的个性化能力: 为用户提供更智能、更贴心的服务。

在众多的模板引擎中,Jinja2以其强大的功能、Python原生的支持以及类似于Django模板的熟悉语法,成为了Python生态系统中最受欢迎的选择之一。

第二章:Jinja2:动态提示词的利器

Jinja2是一个功能丰富、性能优越的Python模板引擎。它被广泛应用于Web开发(如Flask框架),但其通用性使其同样适用于任何需要将数据与文本分离并生成动态文本的场景,包括我们的动态提示词。

2.1 Jinja2的核心特性

Jinja2的语法简洁而强大,主要包含以下几个核心概念:

  1. 变量(Variables): 使用 {{ variable_name }} 包裹,用于输出传入的数据。
  2. 语句(Statements/Blocks): 使用 {% statement %} 包裹,用于控制模板的逻辑流,如 if/elsefor 循环、宏定义等。
  3. 注释(Comments): 使用 {# comment #} 包裹,用于在模板中添加不输出的注释。
  4. 过滤器(Filters): 通过 | 符号作用于变量,用于修改变量的显示格式,如 {{ name | upper }} 将名字转换为大写。
  5. 宏(Macros): 类似于函数,用于定义可复用的模板片段。
  6. 继承与包含(Inheritance and Includes): 支持模板的继承和片段的包含,便于模板的模块化和复用。

2.2 Jinja2环境设置与基本使用

在Python中使用Jinja2非常简单。首先,需要安装它:

pip install Jinja2

然后,在Python代码中,我们可以创建一个Jinja2环境,并加载或渲染模板。

from jinja2 import Environment, FileSystemLoader, select_autoescape

# 示例1:直接从字符串加载模板
template_string = "Hello, {{ name }}! Today is {{ day }}."
env = Environment(
    loader=FileSystemLoader("."), # 可以指定模板文件所在的目录,这里为当前目录
    autoescape=select_autoescape(['html', 'xml']) # 自动转义,对提示词来说通常不需要,可以关闭或按需配置
)
template = env.from_string(template_string)

# 渲染模板,传入数据上下文
data = {"name": "Alice", "day": "Monday"}
rendered_prompt = template.render(data)
print(f"渲染结果 (字符串模板):n{rendered_prompt}n")

# 示例2:从文件加载模板
# 假设我们有一个名为 'greeting.j2' 的文件,内容如下:
# Hello, {{ user.name }}! Your role is {{ user.role | default('Guest') }}.

# 创建一个模板文件
with open("greeting.j2", "w", encoding="utf-8") as f:
    f.write("Hello, {{ user.name }}! Your role is {{ user.role | default('Guest') }}.")

# 重新初始化环境,或使用同一个环境加载文件
env = Environment(loader=FileSystemLoader("."), autoescape=select_autoescape(['html', 'xml']))
template_from_file = env.get_template("greeting.j2")

user_data = {"user": {"name": "Bob", "role": "Admin"}}
rendered_prompt_file = template_from_file.render(user_data)
print(f"渲染结果 (文件模板):n{rendered_prompt_file}n")

user_data_no_role = {"user": {"name": "Charlie"}}
rendered_prompt_no_role = template_from_file.render(user_data_no_role)
print(f"渲染结果 (文件模板,无角色):n{rendered_prompt_no_role}n")

输出示例:

渲染结果 (字符串模板):
Hello, Alice! Today is Monday.

渲染结果 (文件模板):
Hello, Bob! Your role is Admin.

渲染结果 (文件模板,无角色):
Hello, Charlie! Your role is Guest.

通过这个简单的例子,我们已经看到了Jinja2的强大之处:通过传递不同的data字典,我们可以生成不同的提示词。| default('Guest') 这是一个过滤器,它为user.role提供了一个默认值,当user.role未定义或为空时,它会显示’Guest’。

第三章:核心应用:逻辑判断(If/Else)构建复杂提示词

现在,我们进入本讲座的核心部分:如何利用Jinja2的逻辑判断能力,构建真正动态、智能的提示词。if/elif/else结构是实现条件分支逻辑的基础,它允许我们根据不同的条件包含或排除特定的提示词片段。

3.1 场景一:基于用户角色的指令调整

假设我们正在构建一个面向不同用户角色的AI助手。管理员可能需要更详细的配置指令,普通用户则需要更简洁的操作指南,而访客可能只能获取有限的信息。我们可以通过if/elif/else来动态生成这些差异化的指令。

数据上下文示例:

键名 值类型 描述
user_role 字符串 当前用户的角色,如 "admin", "user", "guest"
username 字符串 用户名

Jinja2 模板 (role_based_prompt.j2):

你是一名多功能AI助手,正在与用户 {{ username }} 交流。

{% if user_role == 'admin' %}
作为管理员,你拥有系统所有权限。请提供详细的技术指导,并特别关注安全性与数据完整性。
如果需要,你可以访问系统日志并执行诊断操作。
{% elif user_role == 'user' %}
作为普通用户,你可以执行日常任务。请提供清晰、直接的步骤说明。
避免提及底层系统细节,除非用户明确要求。
{% elif user_role == 'guest' %}
作为访客,你只能获取公开信息。请提供基本信息和常见问题解答。
不要泄露任何敏感数据或提供操作权限。
{% else %}
系统未能识别你的角色。请提供通用帮助信息,并建议用户登录或联系支持。
{% endif %}

请根据以上指令和用户的具体请求,开始你的回复。

Python 渲染代码:

from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader("."), trim_blocks=True, lstrip_blocks=True)
# trim_blocks 和 lstrip_blocks 选项可以移除块级标签(如 {% if %})周围的空白行,使生成的提示词更整洁。
template = env.get_template("role_based_prompt.j2")

# 渲染管理员角色
admin_data = {"username": "Alex", "user_role": "admin"}
admin_prompt = template.render(admin_data)
print("--- 管理员提示词 ---n" + admin_prompt + "n")

# 渲染普通用户角色
user_data = {"username": "Ben", "user_role": "user"}
user_prompt = template.render(user_data)
print("--- 普通用户提示词 ---n" + user_prompt + "n")

# 渲染访客角色
guest_data = {"username": "Chloe", "user_role": "guest"}
guest_prompt = template.render(guest_data)
print("--- 访客提示词 ---n" + guest_prompt + "n")

# 渲染未知角色
unknown_data = {"username": "David", "user_role": "unknown"}
unknown_prompt = template.render(unknown_data)
print("--- 未知角色提示词 ---n" + unknown_prompt + "n")

输出示例:

--- 管理员提示词 ---
你是一名多功能AI助手,正在与用户 Alex 交流。
作为管理员,你拥有系统所有权限。请提供详细的技术指导,并特别关注安全性与数据完整性。
如果需要,你可以访问系统日志并执行诊断操作。
请根据以上指令和用户的具体请求,开始你的回复。

--- 普通用户提示词 ---
你是一名多功能AI助手,正在与用户 Ben 交流。
作为普通用户,你可以执行日常任务。请提供清晰、直接的步骤说明。
避免提及底层系统细节,除非用户明确要求。
请根据以上指令和用户的具体请求,开始你的回复。

--- 访客提示词 ---
你是一名多功能AI助手,正在与用户 Chloe 交流。
作为访客,你只能获取公开信息。请提供基本信息和常见问题解答。
不要泄露任何敏感数据或提供操作权限。
请根据以上指令和用户的具体请求,开始你的回复。

--- 未知角色提示词 ---
你是一名多功能AI助手,正在与用户 David 交流。
系统未能识别你的角色。请提供通用帮助信息,并建议用户登录或联系支持。
请根据以上指令和用户的具体请求,开始你的回复。

3.2 场景二:可选信息的动态包含

在很多情况下,提示词中的某些信息可能是可选的。例如,一个任务描述可能包含截止日期、优先级、相关负责人等,但这些信息并非总是存在。我们不希望LLM因为缺失信息而“瞎编”,也不希望提示词中出现“截止日期:无”这样的冗余内容。Jinja2的if条件可以完美解决这个问题。

数据上下文示例:

键名 值类型 描述
task_name 字符串 任务名称
description 字符串 任务描述
due_date 字符串 任务截止日期,可选
priority 字符串 任务优先级,可选
assignee 字符串 任务负责人,可选
is_critical 布尔值 任务是否紧急,可选

Jinja2 模板 (task_prompt.j2):

请作为项目经理,协助我处理以下任务:

任务名称: {{ task_name }}
描述: {{ description }}

{% if due_date %}
截止日期: {{ due_date }}
{% endif %}

{% if priority %}
优先级: {{ priority }}
{% endif %}

{% if assignee %}
负责人: {{ assignee }}
{% endif %}

{% if is_critical %}
**注意:这是一个关键任务,请务必优先处理。**
{% endif %}

请根据上述信息,为该任务制定一份详细的执行计划,包括关键步骤和潜在风险。

Python 渲染代码:

from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader("."), trim_blocks=True, lstrip_blocks=True)
template = env.get_template("task_prompt.j2")

# 任务1:包含所有可选信息
task_data_full = {
    "task_name": "完成季度报告",
    "description": "收集所有部门数据并汇总,生成最终报告。",
    "due_date": "2023-12-31",
    "priority": "高",
    "assignee": "张三",
    "is_critical": True
}
prompt_full = template.render(task_data_full)
print("--- 完整任务提示词 ---n" + prompt_full + "n")

# 任务2:只包含部分可选信息
task_data_partial = {
    "task_name": "更新项目文档",
    "description": "修订技术规范和用户手册。",
    "due_date": "2024-01-15",
    # priority, assignee, is_critical 未提供
}
prompt_partial = template.render(task_data_partial)
print("--- 部分信息任务提示词 ---n" + prompt_partial + "n")

# 任务3:不包含任何可选信息
task_data_minimal = {
    "task_name": "安排团队会议",
    "description": "预定会议室并发送邀请。"
}
prompt_minimal = template.render(task_data_minimal)
print("--- 最少信息任务提示词 ---n" + prompt_minimal + "n")

输出示例:

--- 完整任务提示词 ---
请作为项目经理,协助我处理以下任务:

任务名称: 完成季度报告
描述: 收集所有部门数据并汇总,生成最终报告。
截止日期: 2023-12-31
优先级: 高
负责人: 张三
**注意:这是一个关键任务,请务必优先处理。**
请根据上述信息,为该任务制定一份详细的执行计划,包括关键步骤和潜在风险。

--- 部分信息任务提示词 ---
请作为项目经理,协助我处理以下任务:

任务名称: 更新项目文档
描述: 修订技术规范和用户手册。
截止日期: 2024-01-15
请根据上述信息,为该任务制定一份详细的执行计划,包括关键步骤和潜在风险。

--- 最少信息任务提示词 ---
请作为项目经理,协助我处理以下任务:

任务名称: 安排团队会议
描述: 预定会议室并发送邀请。
请根据上述信息,为该任务制定一份详细的执行计划,包括关键步骤和潜在风险。

可以看到,if due_date 等条件判断,只有当due_date变量在传入的数据上下文中存在且其值为真(非空字符串、非None等)时,其内部的文本才会被渲染。这极大地提高了提示词的整洁性和精确性。

3.3 场景三:布尔标志的运用

布尔标志(True/False)在提示词中非常有用,可以控制某些特定指令的开关。例如,我们可能希望在某些情况下要求模型进行“思维链”(Chain-of-Thought, CoT)推理,而在另一些简单场景下则不需要。

数据上下文示例:

键名 值类型 描述
query 字符串 用户查询内容
is_complex 布尔值 查询是否复杂,是否需要CoT
include_examples 布尔值 是否包含示例

Jinja2 模板 (query_prompt.j2):

你是一名高级分析师,请回答以下问题:

问题: {{ query }}

{% if is_complex %}
请采用分步思考(Chain-of-Thought)的方式,详细阐述你的推理过程,然后再给出最终答案。
每个步骤都要清晰明确。
{% endif %}

{% if include_examples %}
以下是一些相关示例供你参考:
- 示例1:[具体示例内容]
- 示例2:[具体示例内容]
{% endif %}

请确保你的回答准确、全面且易于理解。

Python 渲染代码:

from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader("."), trim_blocks=True, lstrip_blocks=True)
template = env.get_template("query_prompt.j2")

# 查询1:复杂问题,需要CoT和示例
query_data_complex_with_examples = {
    "query": "解释量子纠缠的物理原理及其在量子计算中的潜在应用。",
    "is_complex": True,
    "include_examples": True
}
prompt_complex_with_examples = template.render(query_data_complex_with_examples)
print("--- 复杂问题带CoT和示例 ---n" + prompt_complex_with_examples + "n")

# 查询2:简单问题,不需要CoT和示例
query_data_simple = {
    "query": "什么是牛顿第一定律?",
    "is_complex": False, # 或者不提供,Jinja2会将其视为False
    "include_examples": False # 或者不提供
}
prompt_simple = template.render(query_data_simple)
print("--- 简单问题无CoT和示例 ---n" + prompt_simple + "n")

输出示例:

--- 复杂问题带CoT和示例 ---
你是一名高级分析师,请回答以下问题:

问题: 解释量子纠缠的物理原理及其在量子计算中的潜在应用。
请采用分步思考(Chain-of-Thought)的方式,详细阐述你的推理过程,然后再给出最终答案。
每个步骤都要清晰明确。
以下是一些相关示例供你参考:
- 示例1:[具体示例内容]
- 示例2:[具体示例内容]
请确保你的回答准确、全面且易于理解。

--- 简单问题无CoT和示例 ---
你是一名高级分析师,请回答以下问题:

问题: 什么是牛顿第一定律?
请确保你的回答准确、全面且易于理解。

这些示例充分展示了if/elif/else在动态提示词中的强大控制力。通过精心设计数据上下文和模板逻辑,我们可以构建出极其灵活和智能的提示词生成系统。

第四章:超越If/Else:循环、宏与模块化

除了条件判断,Jinja2还提供了其他高级功能,能够进一步增强动态提示词的表达能力和可维护性。

4.1 列表与循环(For Loops)

当我们需要在提示词中包含一个列表或一组重复结构时,for循环是不可或缺的。例如,列出用户的偏好、一系列待办事项、或模型需要遵循的多个约束。

数据上下文示例:

键名 值类型 描述
user_name 字符串 用户名
preferences 字符串列表 用户的偏好列表
constraints 字典列表 需要遵循的约束,每个约束包含iddescription

Jinja2 模板 (list_prompt.j2):

你是一名AI助手,正在为用户 {{ user_name }} 服务。

用户的偏好如下:
{% if preferences %}
{% for pref in preferences %}
- {{ pref }}
{% endfor %}
{% else %}
(用户未设置任何偏好。)
{% endif %}

在生成内容时,请务必遵守以下约束:
{% if constraints %}
{% for constraint in constraints %}
- 约束{{ constraint.id }}: {{ constraint.description }}
{% endfor %}
{% else %}
(当前没有特定的约束条件。)
{% endif %}

请根据上述信息,生成一个关于用户兴趣的个性化推荐列表。

Python 渲染代码:

from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader("."), trim_blocks=True, lstrip_blocks=True)
template = env.get_template("list_prompt.j2")

data_with_lists = {
    "user_name": "Emma",
    "preferences": ["科幻小说", "历史纪录片", "编程教程"],
    "constraints": [
        {"id": 1, "description": "推荐内容需为中文"},
        {"id": 2, "description": "避免政治敏感话题"},
        {"id": 3, "description": "每条推荐不超过30字"}
    ]
}
prompt_with_lists = template.render(data_with_lists)
print("--- 包含列表的提示词 ---n" + prompt_with_lists + "n")

data_empty_lists = {
    "user_name": "Frank",
    "preferences": [], # 空列表
    "constraints": []  # 空列表
}
prompt_empty_lists = template.render(data_empty_lists)
print("--- 包含空列表的提示词 ---n" + prompt_empty_lists + "n")

输出示例:

--- 包含列表的提示词 ---
你是一名AI助手,正在为用户 Emma 服务。

用户的偏好如下:
- 科幻小说
- 历史纪录片
- 编程教程

在生成内容时,请务必遵守以下约束:
- 约束1: 推荐内容需为中文
- 约束2: 避免政治敏感话题
- 约束3: 每条推荐不超过30字

请根据上述信息,生成一个关于用户兴趣的个性化推荐列表。

--- 包含空列表的提示词 ---
你是一名AI助手,正在为用户 Frank 服务。

用户的偏好如下:
(用户未设置任何偏好。)

在生成内容时,请务必遵守以下约束:
(当前没有特定的约束条件。)

请根据上述信息,生成一个关于用户兴趣的个性化推荐列表。

4.2 过滤器(Filters)

过滤器用于转换变量的值。Jinja2提供了许多内置过滤器,例如upper(转大写)、lower(转小写)、capitalize(首字母大写)、trim(去除首尾空白)、default(设置默认值)等。这些在处理用户输入或确保提示词格式一致性时非常有用。

Jinja2 模板 (filter_prompt.j2):

尊敬的 {{ user_name | capitalize }},

您的请求ID是 {{ request_id | upper }}。
我们已收到您的反馈:
"{{ feedback | trim }}"

{% if additional_info is defined %}
附加信息: {{ additional_info | default('无') }}
{% endif %}

请确保模型理解:用户名为首字母大写,请求ID为大写,反馈内容去除首尾空白。

Python 渲染代码:

from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader("."), trim_blocks=True, lstrip_blocks=True)
template = env.get_template("filter_prompt.j2")

data_with_filters = {
    "user_name": "greg",
    "request_id": "req-12345",
    "feedback": "  我非常满意你们的服务!  ",
    "additional_info": None # 演示default过滤器
}
prompt_filtered = template.render(data_with_filters)
print("--- 包含过滤器的提示词 ---n" + prompt_filtered + "n")

data_without_additional_info = {
    "user_name": "helen",
    "request_id": "req-67890",
    "feedback": " 还有改进空间。  ",
    # additional_info 未定义
}
prompt_filtered_no_info = template.render(data_without_additional_info)
print("--- 包含过滤器,无附加信息 ---n" + prompt_filtered_no_info + "n")

输出示例:

--- 包含过滤器的提示词 ---
尊敬的 Greg,

您的请求ID是 REQ-12345。
我们已收到您的反馈:
"我非常满意你们的服务!"

附加信息: 无

请确保模型理解:用户名为首字母大写,请求ID为大写,反馈内容去除首尾空白。

--- 包含过滤器,无附加信息 ---
尊敬的 Helen,

您的请求ID是 REQ-67890。
我们已收到您的反馈:
"还有改进空间。"

请确保模型理解:用户名为首字母大写,请求ID为大写,反馈内容去除首尾空白。

这里additional_info is defined用于检查变量是否存在,而| default('无')则在变量存在但值为None或为空时提供默认值。

4.3 宏(Macros):实现可复用的代码块

宏是Jinja2中非常强大的特性,它允许我们定义可复用的模板片段,类似于编程语言中的函数。这对于那些在多个提示词中重复出现的指令块或格式化结构特别有用。

Jinja2 模板 (macros_prompt.j2):

{% macro system_instruction(persona, task) %}
你是一名 {{ persona }}。你的任务是 {{ task }}。
请严格遵循以下原则:准确、简洁、负责。
{% endmacro %}

{% macro add_example(title, content) %}
--- 示例开始 ---
标题: {{ title }}
内容: {{ content }}
--- 示例结束 ---
{% endmacro %}

{{ system_instruction('专业的软件工程师', '审查并改进给定的代码片段') }}

请处理以下代码:
```python
def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n-1)

请提供优化建议。

{{ system_instruction(‘富有创意的文案专家’, ‘为新产品撰写引人注目的广告词’) }}

新产品是一款智能家居中枢。请为其撰写3条广告词。

{% if include_demos %}
以下是一些相关演示:
{{ add_example(‘产品演示’, ‘通过语音控制智能灯光。’) }}
{{ add_example(‘用户案例’, ‘老年用户通过简单语音指令控制家中设备。’) }}
{% endif %}


**Python 渲染代码:**

```python
from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader("."), trim_blocks=True, lstrip_blocks=True)
template = env.get_template("macros_prompt.j2")

data_with_macros_and_demos = {"include_demos": True}
prompt_with_macros = template.render(data_with_macros_and_demos)
print("--- 包含宏和演示的提示词 ---n" + prompt_with_macros + "n")

data_with_macros_no_demos = {"include_demos": False}
prompt_with_macros_no_demos = template.render(data_with_macros_no_demos)
print("--- 包含宏但无演示的提示词 ---n" + prompt_with_macros_no_demos + "n")

输出示例:

--- 包含宏和演示的提示词 ---
你是一名 专业的软件工程师。你的任务是 审查并改进给定的代码片段。
请严格遵循以下原则:准确、简洁、负责。

请处理以下代码:
```python
def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n-1)

请提供优化建议。

你是一名 富有创意的文案专家。你的任务是 为新产品撰写引人注目的广告词。
请严格遵循以下原则:准确、简洁、负责。

新产品是一款智能家居中枢。请为其撰写3条广告词。

以下是一些相关演示:
— 示例开始 —
标题: 产品演示
内容: 通过语音控制智能灯光。
— 示例结束 —
— 示例开始 —
标题: 用户案例
内容: 老年用户通过简单语音指令控制家中设备。
— 示例结束 —

— 包含宏但无演示的提示词 —
你是一名 专业的软件工程师。你的任务是 审查并改进给定的代码片段。
请严格遵循以下原则:准确、简洁、负责。

请处理以下代码:

def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n-1)

请提供优化建议。

你是一名 富有创意的文案专家。你的任务是 为新产品撰写引人注目的广告词。
请严格遵循以下原则:准确、简洁、负责。

新产品是一款智能家居中枢。请为其撰写3条广告词。


宏极大地提高了模板的复用性和可维护性。我们可以将通用的系统指令、示例格式等封装为宏,然后在不同的提示词中多次调用。

### 4.4 包含(Includes):分解大型模板

随着提示词逻辑的复杂化,单个模板文件可能会变得非常庞大。Jinja2的`include`指令允许我们将一个模板分解成多个小文件,然后在主模板中按需包含它们。这对于组织模板结构、实现模块化开发至关重要。

假设我们有以下文件结构:

.
├── main_prompt.j2
├── _system_instructions.j2
└── _examples.j2


**`_system_instructions.j2`:**

```jinja2
你是一名高级AI助理。你的目标是高效地完成用户指定的任务。
请始终保持专业、准确和礼貌。

{% if persona %}
你被赋予了 {{ persona }} 的角色。
{% endif %}

_examples.j2

{% if examples %}
以下是一些帮助你理解任务的示例:
{% for example in examples %}
--- 示例 {{ loop.index }} ---
输入: {{ example.input }}
输出: {{ example.output }}
{% endfor %}
{% endif %}

main_prompt.j2

{% include '_system_instructions.j2' %}

用户请求:
{{ user_request }}

{% include '_examples.j2' %}

请根据以上信息,生成一个详细的响应。

Python 渲染代码:

from jinja2 import Environment, FileSystemLoader
import os

# 创建模板文件
os.makedirs("templates", exist_ok=True) # 确保有templates目录

with open("templates/_system_instructions.j2", "w", encoding="utf-8") as f:
    f.write("""
你是一名高级AI助理。你的目标是高效地完成用户指定的任务。
请始终保持专业、准确和礼貌。

{% if persona %}
你被赋予了 {{ persona }} 的角色。
{% endif %}
""")

with open("templates/_examples.j2", "w", encoding="utf-8") as f:
    f.write("""
{% if examples %}
以下是一些帮助你理解任务的示例:
{% for example in examples %}
--- 示例 {{ loop.index }} ---
输入: {{ example.input }}
输出: {{ example.output }}
{% endfor %}
{% endif %}
""")

with open("templates/main_prompt.j2", "w", encoding="utf-8") as f:
    f.write("""
{% include '_system_instructions.j2' %}

用户请求:
{{ user_request }}

{% include '_examples.j2' %}

请根据以上信息,生成一个详细的响应。
""")

# Jinja2 环境需要知道模板文件的位置
env = Environment(
    loader=FileSystemLoader("templates"), # 指定模板目录
    trim_blocks=True,
    lstrip_blocks=True
)
template = env.get_template("main_prompt.j2")

data_with_includes = {
    "persona": "数据分析师",
    "user_request": "分析最近一个月的销售数据,找出增长最快的三个产品。",
    "examples": [
        {"input": "销售数据:A产品100,B产品50", "output": "A产品增长最快"},
        {"input": "销售数据:C产品200,D产品150", "output": "C产品增长最快"}
    ]
}
prompt_with_includes = template.render(data_with_includes)
print("--- 包含的模板渲染结果 ---n" + prompt_with_includes + "n")

data_no_examples = {
    "persona": "创意写作助手",
    "user_request": "为一段故事提供一个令人惊喜的结局。",
    "examples": [] # 没有示例
}
prompt_no_examples = template.render(data_no_examples)
print("--- 包含的模板,无示例 ---n" + prompt_no_examples + "n")

输出示例:

--- 包含的模板渲染结果 ---
你是一名高级AI助理。你的目标是高效地完成用户指定的任务。
请始终保持专业、准确和礼貌。
你被赋予了 数据分析师 的角色。

用户请求:
分析最近一个月的销售数据,找出增长最快的三个产品。

以下是一些帮助你理解任务的示例:
--- 示例 1 ---
输入: 销售数据:A产品100,B产品50
输出: A产品增长最快
--- 示例 2 ---
输入: 销售数据:C产品200,D产品150
输出: C产品增长最快

请根据以上信息,生成一个详细的响应。

--- 包含的模板,无示例 ---
你是一名高级AI助理。你的目标是高效地完成用户指定的任务。
请始终保持专业、准确和礼貌。
你被赋予了 创意写作助手 的角色。

用户请求:
为一段故事提供一个令人惊喜的结局。

请根据以上信息,生成一个详细的响应。

通过include,我们可以将通用指令、角色定义、示例集等作为独立的模块来管理,使主模板更专注于任务本身,提高了可读性和可维护性。

第五章:高级策略与最佳实践

掌握了Jinja2的基本和高级功能后,我们还需要考虑如何在实际项目中更有效地应用动态提示词模板化。

5.1 动态Chain-of-Thought (CoT)

CoT是一种提示技术,通过要求LLM逐步思考来提高其推理能力。动态模板化允许我们根据任务的复杂性或模型的推理能力,选择性地引入CoT指令。

例如,在模板中添加一个布尔变量use_cot

{% if use_cot %}
请分步思考,详细解释你的推理过程,然后再给出最终答案。
步骤:
1. ...
2. ...
{% endif %}

task_complexity较高时,将use_cot设置为True

5.2 Few-Shot示例的动态管理

Few-Shot Learning通过在提示词中包含少量输入-输出示例来引导LLM。动态模板可以根据用户需求、模型性能或可用数据,动态地选择和插入这些示例。

我们可以维护一个示例库,然后根据num_examples参数或example_category参数,从库中选择合适的示例渲染到提示词中。

# Python代码
all_examples = {
    "math": [
        {"input": "2+2", "output": "4"},
        {"input": "5*3", "output": "15"}
    ],
    "qa": [
        {"input": "首都是哪里?", "output": "北京"},
        {"input": "地球的形状?", "output": "椭球体"}
    ]
}

data = {
    "task": "数学问题",
    "examples_to_include": all_examples["math"][:1] # 只包含一个数学示例
}
请解决以下任务:{{ task }}

{% if examples_to_include %}
以下是一些相关示例:
{% for ex in examples_to_include %}
输入: {{ ex.input }}
输出: {{ ex.output }}
{% endfor %}
{% endif %}

5.3 模板与代码的清晰分离

一个好的实践是将所有的Jinja2模板文件存放在一个专门的目录中(例如 templates/),并通过FileSystemLoader加载。Python代码只负责准备数据上下文并调用渲染方法,而模板文件则专注于文本结构和逻辑。

5.4 版本控制与测试

将提示词模板视为代码的一部分,对其进行版本控制(Git)。同时,为关键的提示词模板编写单元测试,确保在不同数据上下文下能够生成预期的提示词。这能有效防止因模板修改导致的意外行为。

测试项 描述 预期结果
变量填充 传入不同值,检查变量是否正确渲染。 {{ var }} 应显示正确的值。
if/else分支 切换条件,检查对应的分支是否被选中。 仅渲染符合条件的分支。
for循环 传入空列表、单项列表、多项列表,检查循环。 正确处理各种列表情况。
宏调用 传入不同参数,检查宏输出。 宏内容正确生成。
默认值和空值处理 检查|defaultis defined是否按预期工作。 提供默认值或跳过空值部分。

5.5 Jinja2环境配置优化

trim_blockslstrip_blocks是Jinja2环境的两个有用参数,它们可以帮助我们消除模板标签(如{% if %}{% for %})引入的空白行,使最终的提示词更加紧凑和整洁。

env = Environment(
    loader=FileSystemLoader("."),
    trim_blocks=True,    # 移除块级标签后的第一个换行符
    lstrip_blocks=True   # 移除块级标签前的所有空格和制表符
)

尾声:构建智能LLM应用的核心能力

动态提示词模板化并非仅仅是一种技术技巧,它更是构建灵活、可维护、高性能LLM应用的核心能力。通过将提示词从静态字符串提升为具有生命力、能够响应上下文的动态实体,我们极大地扩展了LLM的应用边界。Jinja2作为这一过程中的强大工具,以其简洁的语法和丰富的功能,为我们提供了实现这一目标的坚实基础。掌握并精通动态提示词模板化,将使您在LLM应用开发的道路上如虎添翼,构建出更加智能、更加人性化的未来系统。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注