Prompt模板引擎设计:从硬编码到工程化
当项目有几十个不同的 LLM 调用场景时,硬编码的 Prompt 会变成灾难——重复、难以维护、版本混乱。模板引擎是解决这一问题的工程化方案。
一、为什么要模板化?
1.1 硬编码的问题
# ❌ 硬编码方式:代码和Prompt混在一起,难以维护
def summarize_article(article):
return llm(f"""
请用中文总结以下文章:
注意:保持客观中立
字数限制:100字以内
输出格式:Markdown
文章内容:
{article}
总结:
""")
def translate_text(text):
return llm(f"""
请将以下英文翻译成中文:
要求:
- 保持原文风格
- 专业术语准确
- 不要添加解释
原文:
{text}
翻译:
""")
# ✅ 模板化方式:分离关注点
prompts = {
"summarize": """
请用中文总结以下文章:
注意:保持客观中立
字数限制:{{max_words}}字以内
输出格式:{{format}}
文章内容:
{{content}}
总结:
""",
"translate": """
请将以下{{source_lang}}翻译成{{target_lang}}:
要求:
- 保持原文风格
- 专业术语准确
- 不要添加解释
原文:
{{text}}
翻译:
"""
}
1.2 模板化的收益
| 维度 | 硬编码 | 模板化 | 提升 |
|---|---|---|---|
| 维护性 | 每次改Prompt都要改代码 | 改模板文件即可 | 10x |
| 复用率 | 低(大量重复) | 高(组合复用) | 5x |
| 版本控制 | 混在Git记录里 | 单独版本管理 | 清晰 |
| 团队协作 | 需要开发改代码 | 非技术人员可编辑 | 无障碍 |
| 测试覆盖 | 难(嵌入代码) | 容易(独立测试) | 3x |
二、模板引擎设计
2.1 核心架构
from jinja2 import Environment, FileSystemLoader, Template
from typing import Dict, List, Optional
import yaml
import json
class PromptTemplateEngine:
"""
Prompt 模板引擎
核心功能:
1. 变量注入
2. 条件渲染
3. 循环展开
4. 模板继承
5. 过滤器
"""
def __init__(self, template_dir="prompts/"):
self.env = Environment(
loader=FileSystemLoader(template_dir),
variable_start_string="{{",
variable_end_string="}}",
# 防止与MDX语法冲突
comment_start_string="{#",
comment_end_string="#}"
)
# 注册自定义过滤器
self.env.filters['json'] = json.dumps
self.env.filters['truncate'] = lambda s, n: s[:n] + '...' if len(s) > n else s
def render(self, template_name: str, **variables) -> str:
"""渲染模板"""
template = self.env.get_template(template_name)
return template.render(**variables)
def render_string(self, template_string: str, **variables) -> str:
"""直接渲染字符串模板"""
template = self.env.from_string(template_string)
return template.render(**variables)
2.2 模板文件组织
prompts/
base/
system.j2 # 基础System Prompt
format.j2 # 输出格式定义
tasks/
summarize.j2 # 总结任务
translate.j2 # 翻译任务
classify.j2 # 分类任务
extract.j2 # 提取任务
components/
few_shot.j2 # Few-shot 示例组件
constraints.j2 # 约束条件组件
safety.j2 # 安全规则组件
role.j2 # 角色设定组件
__init__.yaml # 模板元数据配置
2.3 模板示例
{# templates/base/system.j2 #}
{# Jinja2 模板 #}
你是{{ role_name }},拥有{{ experience_years }}年{{ domain }}经验。
你的特点:
{% for trait in traits %}
- {{ trait }}
{% endfor %}
{% if style == 'formal' %}
回答风格:
- 使用专业术语
- 结构严谨
- 引用权威来源
{% elif style == 'casual' %}
回答风格:
- 通俗易懂
- 善用比喻
- 轻松自然
{% endif %}
{% include 'components/constraints.j2' %}
{# templates/tasks/summarize.j2 #}
{% extends 'base/system.j2' %}
{% block content %}
## 任务
请总结以下{{ content_type }}。
## 要求
- 字数限制:{{ max_words | default(200) }}字
- 语言:{{ language | default('中文') }}
- 输出格式:{{ format | default('段落') }}
## 内容
{{ content }}
## 总结
{% endblock %}
三、高级特性
3.1 条件逻辑
{% if temperature > 0.7 %}
注意:高温度设置可能导致输出更有创意但可靠性降低。
{% elif temperature < 0.2 %}
注意:低温度设置可能导致输出更确定但可能缺乏多样性。
{% endif %}
3.2 动态组件组合
class DynamicPromptBuilder:
"""
动态组合多个模板组件
"""
def __init__(self, engine: PromptTemplateEngine):
self.engine = engine
def build_prompt(self, task: str, context: Dict) -> str:
"""根据任务类型动态组合Prompt"""
prompt_parts = []
# 1. 基础 System Prompt(所有任务共享)
prompt_parts.append(
self.engine.render('base/system.j2', **context.get('system', {}))
)
# 2. 任务特定 Prompt
prompt_parts.append(
self.engine.render(f'tasks/{task}.j2', **context)
)
# 3. 可选的 Few-shot 示例
if context.get('examples'):
prompt_parts.append(
self.engine.render('components/few_shot.j2',
examples=context['examples'])
)
# 4. 安全规则
prompt_parts.append(
self.engine.render('components/safety.j2',
safety_level=context.get('safety_level', 'standard'))
)
# 组合
return '\n\n'.join(prompt_parts)
3.3 多语言支持
{# 多语言模板 #}
{% if language == 'zh' %}
请用中文回答。
{% elif language == 'en' %}
Please answer in English.
{% elif language == 'ja' %}
日本語で答えてください。
{% endif %}
3.4 版本管理集成
# __init__.yaml
prompts:
summarize_v1:
file: tasks/summarize.j2
version: "1.0.0"
date: "2026-01-15"
description: "基础总结模板"
metrics:
accuracy: 0.85
consistency: 0.78
summarize_v2:
file: tasks/summarize_v2.j2
version: "2.0.0"
date: "2026-05-11"
description: "改进版总结模板(增加格式控制)"
metrics:
accuracy: 0.92
consistency: 0.91
四、生产实践
4.1 模板测试
def test_prompt_template(template_name, test_data):
"""
模板测试框架
"""
engine = PromptTemplateEngine()
failures = []
for case in test_data:
try:
rendered = engine.render(template_name, **case['variables'])
# 检查变量是否全部渲染
if '{{' in rendered or '{%' in rendered:
failures.append(f"未渲染的变量: {case['name']}")
# 检查模板长度
if len(rendered) > case.get('max_length', 8000):
failures.append(f"模板过长: {case['name']}")
except Exception as e:
failures.append(f"渲染失败: {case['name']}: {str(e)}")
return {
'passed': len(failures) == 0,
'total': len(test_data),
'failures': len(failures),
'details': failures
}
4.2 设计原则
| 原则 | 说明 | 示例 |
|---|---|---|
| 单一职责 | 每个模板只做一个任务 | 不要在一个模板里既总结又翻译 |
| 可组合 | 小的模板组件可以自由组合 | 角色组件+约束组件+任务组件 |
| 参数化 | 所有可变内容都是参数 | 用[object Object]替代硬编码 |
| 可测试 | 每个模板有对应的测试用例 | 至少 3 个测试场景 |
| 版本化 | 每次修改都更新版本号 | 使用语义化版本 |
总结
Prompt 模板引擎是从临时方案走向工程化的关键一步。通过将 Prompt 与代码分离、建立组件化体系和版本管理,可以让 Prompt 开发效率提升 5-10 倍,同时大幅降低维护成本。