从"调prompt"到"工程化prompt"

大多数开发者的Prompt工作流起初是这样的:在Playground里反复调试,找到效果好的版本后复制粘贴到代码里。这种方式在原型阶段够用,但一旦进入生产环境,就会暴露一系列问题:

  • prompt散落在代码各处,修改困难
  • 无法追踪历史变更
  • 无法对比不同版本的效果
  • 无法在不同模型间迁移

Prompt工程化就是解决这些问题的系统性方法论。

Prompt模板引擎

模板分离原则

将prompt从代码中剥离,使用模板引擎管理。核心思路与后端的view模板一致:

from jinja2 import Environment, FileSystemLoader
import yaml
import json

class PromptTemplate:
    def __init__(self, template_dir: str):
        self.env = Environment(
            loader=FileSystemLoader(template_dir),
            trim_blocks=True,
            lstrip_blocks=True
        )
        self.env.filters['to_json_schema'] = lambda x: json.dumps(x, ensure_ascii=False)

    def render(self, template_name: str, **kwargs) -> str:
        template = self.env.get_template(template_name)
        return template.render(**kwargs)

# 模板文件: templates/summarizer.jinja2
# 你是一个专业摘要生成器。
# 输入文档:{{ document }}
# 要求:生成{{ max_words }}字以内的摘要
# 输出格式:{{ output_format | to_json_schema }}

分层模板架构

templates/
├── system/                  # 系统级prompt
│   ├── base_assistant.jinja2
│   └── safety_rules.jinja2
├── tasks/                   # 任务级prompt
│   ├── summarizer.jinja2
│   ├── extractor.jinja2
│   └── classifier.jinja2
└── components/              # 可复用片段
    ├── few_shot.jinja2
    ├── output_format.jinja2
    └── context_block.jinja2

通过include实现模块化组合:

{% include "system/base_assistant.jinja2" %}
{% include "system/safety_rules.jinja2" %}

# 任务说明
根据以下文档生成结构化摘要。

# 上下文
{% include "components/context_block.jinja2" %}

# 示例
{% include "components/few_shot.jinja2" %}

# 输出要求
{% include "components/output_format.jinja2" %}

版本管理与CI/CD

版本化策略

采用语义化版本号管理prompt变更:

变更类型 版本号 触发条件
MAJOR 1.0.0 → 2.0.0 语义重构,不兼容旧版
MINOR 1.0.0 → 1.1.0 新增参数/能力,向后兼容
PATCH 1.0.0 → 1.0.1 调优措辞,效果微调

评测门禁

# .github/workflows/prompt-ci.yml
name: Prompt CI
on:
  pull_request:
    paths: ["prompts/**"]

jobs:
  evaluate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run Prompt Regression Tests
        run: |
          python -m prompt_eval \
            --baseline prompts/v1.2.0 \
            --candidate prompts/pr-${{ github.event.pull_request.number }} \
            --test-suite tests/golden_set.json \
            --threshold 0.05

A/B测试框架

在线分流实验

import hashlib
import random

class PromptABTest:
    def __init__(self, experiment_id: str, variants: dict[str, str]):
        self.experiment_id = experiment_id
        self.variants = variants  # {"control": "v1.0", "treatment": "v1.1"}

    def assign(self, user_id: str) -> str:
        hash_val = int(hashlib.md5(
            f"{self.experiment_id}:{user_id}".encode()
        ).hexdigest(), 16)
        return "control" if hash_val % 100 < 50 else "treatment"

    def get_prompt_version(self, user_id: str) -> str:
        variant = self.assign(user_id)
        return self.variants[variant]

    def record_outcome(self, user_id: str, success: bool, latency: float):
        variant = self.assign(user_id)
        # 写入指标系统
        metrics.emit(f"prompt_ab_{self.experiment_id}_{variant}", {
            "success": success, "latency": latency
        })

关键指标选择

指标类别 具体指标 说明
质量 人工评分/LLM-as-Judge 1-5分制
准确性 任务完成率/格式合规率 客观可量化
效率 平均token数/延迟 成本相关
安全 拒答率/有害率 合规相关

监控与可观测性

Prompt运行时监控面板

每条prompt上线后需要监控的核心维度:

@dataclass
class PromptTelemetry:
    prompt_id: str
    version: str
    input_tokens: int
    output_tokens: int
    latency_ms: float
    success: bool
    error_type: str | None
    judge_score: float | None  # LLM-as-Judge评分
    user_feedback: int | None  # 用户点赞/点踩

    def to_metrics(self):
        return {
            f"prompt.{self.prompt_id}.tokens": self.input_tokens + self.output_tokens,
            f"prompt.{self.prompt_id}.latency": self.latency_ms,
            f"prompt.{self.prompt_id}.success_rate": int(self.success),
        }

漂移检测

Prompt的输出质量可能因模型更新而悄然下降。建议设置漂移检测

from collections import deque

class DriftDetector:
    def __init__(self, window_size: int = 100, threshold: float = 0.1):
        self.window = deque(maxlen=window_size)
        self.threshold = threshold

    def update(self, score: float) -> bool:
        self.window.append(score)
        if len(self.window) < self.window.maxlen:
            return False
        recent_avg = sum(list(self.window)[-20:]) / 20
        baseline_avg = sum(list(self.window)[:20]) / 20
        drift = (baseline_avg - recent_avg) / baseline_avg
        return drift > self.threshold

总结

Prompt工程化的本质是将隐式知识显式化、将个人经验系统化、将质量保障自动化。核心要点:

  1. 模板化:prompt与代码分离,分层复用
  2. 版本化:语义版本号 + Git追踪每次变更
  3. 评测化:CI门禁 + A/B测试驱动迭代
  4. 监控化:运行时指标采集 + 漂移检测

当你的prompt资产超过50条时,这套体系就不再是可选项,而是必须项。