Prompt工程 进阶 模板引擎 Prompt工程 工程化 Jinja2

Prompt模板引擎设计:从硬编码到工程化

AIEng Hub
阅读约 15 分钟

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 倍,同时大幅降低维护成本。