云计算百科
云计算领域专业知识百科平台

AI项目从入门到上线28-同样用GPT为什么别人比我写得好?Prompt Engineering系统方法论

目录

一、Prompt Engineering四层体系

第一层:明确指令——把话说清楚

第二层:提供上下文——给AI安装"记忆芯片"

第三层:思维链(CoT)——教AI一步一步想

第四层:自我反思+输出校验——让AI自己找错误

二、Prompt模板体系设计

三、结构化输出控制

四、Few-shot示例选择策略

五、Prompt版本管理

六、A/B测试:Prompt的"灰度发布"

七、成本与质量的权衡


开篇:你和大神的差距,就差一个Prompt

同样接GPT-4的API,同事的输出像莎士比亚,你的输出像刚学会打字的小学生。 问题不(全)在模型,而在你怎么跟它说话。Prompt Engineering不是玄学,是一门正经的系统工程。

一个Prompt调一天,产出多出一个月工资——这事儿我干过,你也能干。

今天这篇文章,我从"把话说清楚"到"让AI自己反思自己",用一套四层方法论 + 可落地的代码,带你从Prompt小白到能面试别人Prompt的水平。


一、Prompt Engineering四层体系

别急着看模板,先搞懂这张图。Prompt能力的提升不是"多加点提示词",而是一层一层往上走:

graph TD
L1["🟢 第一层:明确指令<br/>Tell it WHAT to do"]
L2["🟡 第二层:提供上下文<br/>Tell it WHO it is & WHY"]
L3["🟠 第三层:思维链(CoT)<br/>Tell it HOW to think"]
L4["🔴 第四层:自我反思+校验<br/>Let it check ITSELF"]

L1 –> L2 –> L3 –> L4

L1 -.- E1["效果:勉强能用,经常跑偏"]
L2 -.- E2["效果:风格统一,减少幻觉"]
L3 -.- E3["效果:推理准确率显著提升"]
L4 -.- E4["效果:生产级可靠,自纠错"]

style L1 fill:#90EE90,stroke:#333
style L2 fill:#FFD700,stroke:#333
style L3 fill:#FFA500,stroke:#333
style L4 fill:#FF6347,stroke:#333

划重点: 大部分人的Prompt卡在L1和L2之间。花一天学了"你是xx专家"就以为自己Pro了,其实刚入门。


第一层:明确指令——把话说清楚

这一层最简单也最容易犯错。不信?来看三组对比:

# ❌ 烂Prompt:模糊得像你妈让你"去买点东西"
bad_prompt = "写一个Python函数处理数据"

# ✅ 好Prompt:精确得像外卖备注"不加香菜不放葱少辣"
good_prompt = """
编写一个Python函数 `def clean_data(df: pd.DataFrame) -> pd.DataFrame`:
– 删除所有包含NaN的行
– 将日期列"date"转为datetime格式
– 对数值列做Z-score标准化(mean=0, std=1)
– 返回处理后的DataFrame
– 添加docstring和类型注解
"""

你看,好的Prompt就是在写"技术需求文档"。几个关键要素:

⚠️ 避坑1:用"不要"不如用"要" 人类对否定句敏感(不要碰火🔥),LLM对否定指令的理解差很多。写"输出JSON格式"比"不要输出markdown"效果好10倍。原因?注意力机制里的负向token权重远低于正向——说人话就是:AI是"按你说的做"而不是"按你不说的做"。

# ❌ 否定式指令——AI可能忽略"不要"二字
bad = "不要使用markdown格式,不要添加多余解释"

# ✅ 肯定式指令——告诉它要什么
good = "仅输出纯JSON字符串,键名使用snake_case。不要附带任何解释文本。"


第二层:提供上下文——给AI安装"记忆芯片"

这一层是质的飞跃。上下文 = 角色 + 背景信息 + 输出受众。

# L2级别Prompt模板
prompt_with_context = """
【角色】你是一位拥有10年经验的Python后端架构师,擅长微服务设计和高并发优化。

【背景】我们有一个电商订单系统,日均订单量50万。目前订单查询接口P99延迟
达到了2.3秒,需要优化到200ms以内。

【任务】分析可能的性能瓶颈,给出3个优化方案,每个方案包含:
– 方案描述(50字以内)
– 预计延迟降低幅度
– 实现复杂度(高/中/低)
– 潜在风险

【受众】输出给技术经理阅读,要求技术准确但不失可读性。
"""

💡 效率技巧1:角色设定省掉80%的格式调整 与其写10行"你的输出应该用什么格式",不如一句"你是一位资深技术博客作者"——模型自己知道博客该长什么样。这叫内化知识触发(Internalized Knowledge Trigger),GPT系列在角色设定后会自动激活对应的写作风格神经元。


第三层:思维链(CoT)——教AI一步一步想

很多人问:“为什么加了’Let’s think step by step’效果就变好?”

因为LLM本质上是概率预测下一个token的机器。不给推理路径,它就"猜"答案;给了路径,它就"算"答案。

graph LR
A["问题输入"] –> B["❌ 无CoT<br/>直接蹦答案"]
A –> C["✅ 有CoT<br/>Step 1: 理解问题"]
C –> D["Step 2: 拆解子问题"]
D –> E["Step 3: 逐步推理"]
E –> F["Step 4: 整合答案"]
F –> G["输出最终结果"]

B -.- B1["正确答案概率: 60%"]
G -.- G1["正确答案概率: 90%+"]

style B fill:#FFB6C1
style G fill:#90EE90

来看代码实现一个完整的CoT Prompt模板:

def build_cot_prompt(question: str, domain: str = "general") -> str:
"""
构建思维链Prompt模板
"""
cot_templates = {
"code": """
【任务】{question}

请按以下步骤逐步推理:

第1步 – 需求分析:明确输入输出是什么?边界条件有哪些?
第2步 – 算法选择:哪种数据结构/算法最优?时间复杂度是多少?
第3步 – 伪代码:写出核心逻辑的伪代码
第4步 – 编码实现:给出可运行的Python代码
第5步 – 测试用例:提供3个测试用例及其期望输出
第6步 – 复杂度分析:时间和空间复杂度

⚠️ 每一步都必须输出显式标记,格式为「第X步:」,不可跳过任何一步。
""",
"analysis": """
【任务】{question}

请按以下框架分析:

1. 背景梳理:这件事的背景和上下文是什么?
2. 关键因素:影响结果的核心变量有哪些?
3. 多角度分析:至少从3个不同角度分析
4. 利弊权衡:列出每个方案的优缺点
5. 结论建议:给出最终建议并解释理由
"""
}
template = cot_templates.get(domain, cot_templates["analysis"])
return template.format(question=question)

# 使用示例
question = "设计一个分布式限流器,要求支持滑动窗口算法"
cot_prompt = build_cot_prompt(question, domain="code")
print(cot_prompt)

⚠️ 避坑2:CoT不是越长越好——"过度推理"陷阱 我见过有人给简单任务(如"翻译一句话")写8步CoT。结果呢?模型开始脑补不存在的问题,答案反而变差了。CoT的收益曲线是倒U型的——简单任务用L1/L2就够了,复杂推理(数学、代码、逻辑分析)才上CoT。

💡 效率技巧2:动态选择推理深度 用一个分类器判断任务复杂度,再决定用几层Prompt:

def select_prompt_level(task: str) -> int:
"""根据任务复杂度自动选择Prompt层级"""
complex_keywords = [
"设计", "优化", "对比分析", "debug", "架构",
"算法", "推理", "多步骤", "review", "权衡"
]
simple_keywords = [
"翻译", "总结", "提取", "格式化", "重写",
"缩写", "标签", "分类"
]

task_lower = task.lower()

if any(kw in task_lower for kw in complex_keywords):
return 4 # L4:完整CoT+自我反思
elif any(kw in task_lower for kw in simple_keywords):
return 2 # L2:上下文就够了
else:
return 3 # L3:标准CoT


第四层:自我反思+输出校验——让AI自己找错误

L4是生产环境的分水岭。核心逻辑:让模型生成 → 让模型自己评审 → 让模型修正。

class SelfReflectivePrompt:
"""L4自反思Prompt框架"""

def __init__(self, model_func):
self.model_func = model_func # 你的LLM调用函数

def generate(self, task: str) -> dict:
# Phase 1: 初始生成
gen_prompt = f"""
{task}

要求:
– 输出完整答案
– 在每个关键判断处用「【关键判断】」标记
– 在每个假设处用「【假设】」标记你的前提
"""
initial_output = self.model_func(gen_prompt)

# Phase 2: 自我评审
review_prompt = f"""
请严格审查以下输出,找出所有问题:

【原始输出】
{initial_output}

【审查标准】
1. 事实性错误:有没有与公认知识矛盾的地方?
2. 逻辑漏洞:推理链条有没有跳跃或矛盾?
3. 完整性:有没有遗漏关键信息?
4. 边界条件:极端情况是否处理?

请列出所有「发现的问题」,每条格式:
– [严重程度: 高/中/低] 问题描述 → 建议修正
"""
review = self.model_func(review_prompt)

# Phase 3: 基于评审修正
fix_prompt = f"""
根据以下评审意见修正你的原始输出:

【原始输出】
{initial_output}

【评审意见】
{review}

【修正要求】
– 逐条处理所有评审意见
– 输出完整的修正后版本
– 在文末列出「修正清单」
"""
final_output = self.model_func(fix_prompt)

return {
"initial": initial_output,
"review": review,
"final": final_output
}

L4的代价是显而易见的——一次生成变成三次调用,token消耗翻3倍。但如果你在做一个每天跑10万次的核心业务逻辑,这3倍成本换来的是从"90%准确率"到"99.5%准确率"的提升——值不值,你算一下出错的客诉成本就知道了。

⚠️ 避坑3:自评审不等于"自嗨"——模型很难发现自己的系统性偏见 换个比喻你就懂了:让一个广东人评审自己写的"正宗川菜菜谱",靠谱吗?所以L4的正确打开方式是:用不同模型做评审(如用Claude评审GPT的输出,或用GPT-4评审GPT-3.5的输出),这叫"交叉验证"。

def cross_model_review(generate_model, review_model, task: str) -> dict:
"""用不同模型做交叉评审"""
output = generate_model(task)
review = review_model(f"""
请作为独立评审员,审查以下AI输出:
【输出】
{output}
【评审要点】
逐条指出所有问题,不要顾及"同行面子"。
""")
return {"output": output, "review": review}


二、Prompt模板体系设计

搞懂四层之后,来看怎么把这些东西工程化。我踩了半年坑总结出的Prompt模板五要素:

graph TD
T["📋 Prompt模板五要素"]
T –> R["🎭 角色(Role)<br/>你是什么身份?"]
T –> TK["📝 任务(Task)<br/>具体要做什么?"]
T –> F["📐 格式(Format)<br/>输出长什么样?"]
T –> C["🔒 约束(Constraints)<br/>绝对不能干什么?"]
T –> E["🎯 示例(Examples)<br/>做得好是什么样?"]

R -.- R1["激活领域知识"]
TK -.- TK1["界定产出边界"]
F -.- F1["控制输出结构"]
C -.- C1["防止跑偏"]
E -.- E1["Few-shot对齐"]

style T fill:#4A90D9,color:#fff

直接上可用的Prompt模板管理类:

from dataclasses import dataclass, field
from typing import Optional
import json

@dataclass
class PromptTemplate:
"""Prompt模板数据类"""
name: str
role: str
task: str
format_spec: str
constraints: list[str] = field(default_factory=list)
examples: list[dict] = field(default_factory=list)
version: str = "1.0.0"

def build(self, **kwargs) -> str:
"""构建完整Prompt"""
parts = []

if self.role:
parts.append(f"【角色】{self.role}")

task_text = self.task.format(**kwargs) if kwargs else self.task
parts.append(f"【任务】{task_text}")

if self.format_spec:
parts.append(f"【输出格式】{self.format_spec}")

if self.constraints:
constraints_text = "\\n".join(f"- {c}" for c in self.constraints)
parts.append(f"【约束条件】\\n{constraints_text}")

if self.examples:
examples_text = ""
for i, ex in enumerate(self.examples, 1):
examples_text += f"\\n示例{i}:\\n输入:{ex['input']}\\n输出:{ex['output']}\\n"
parts.append(f"【参考示例】{examples_text}")

return "\\n\\n".join(parts)

# ===== 实际使用 =====
code_review_template = PromptTemplate(
name="代码审查模板",
role="你是一位资深Python代码审查专家,精通PEP 8规范和设计模式",
task="审查以下Python代码,给出详细的改进建议:\\n```python\\n{code}\\n```",
format_spec="""
输出JSON格式:
{
"overall_score": 0-100,
"issues": [
{"severity": "高/中/低", "line": 行号, "description": "问题描述",
"suggestion": "改进建议", "rule": "违反的规范"}
],
"strengths": ["优点1", "优点2"],
"refactored_code": "重构后的代码字符串"
}
""",
constraints=[
"不修改代码的业务逻辑",
"每个issue必须引用具体的PEP或设计模式",
"重构代码必须可运行",
"不要输出JSON之外的任何文本"
],
examples=[{
"input": "def f(x): return x*2",
"output": '{"overall_score": 45, "issues": […]}'
}]
)

# 构建Prompt
code = "def calc(a,b):\\n return a+b"
final_prompt = code_review_template.build(code=code)
print(final_prompt)

💡 效率技巧3:模板继承——子模板覆盖父模板的约束 你会发现不同项目的审查标准不一样。别复制粘贴,用继承:

class PromptTemplateLibrary:
"""Prompt模板库,支持继承和版本管理"""

def __init__(self):
self._templates: dict[str, PromptTemplate] = {}

def register(self, template: PromptTemplate):
self._templates[template.name] = template

def inherit(self, base_name: str, new_name: str,
overrides: dict) -> PromptTemplate:
"""基于已有模板创建变体"""
base = self._templates[base_name]
new_data = {
"name": new_name,
"role": base.role,
"task": base.task,
"format_spec": base.format_spec,
"constraints": list(base.constraints),
"examples": list(base.examples),
"version": f"{base.version}+{overrides.get('version_suffix', 'variant')}"
}
new_data.update({k: v for k, v in overrides.items()
if k != 'version_suffix'})
new_template = PromptTemplate(**new_data)
self.register(new_template)
return new_template

# 使用示例
lib = PromptTemplateLibrary()
lib.register(code_review_template)

security_review = lib.inherit(
"代码审查模板",
"安全审查模板",
{
"role": "你是一位应用安全专家,精通OWASP Top 10和CWE",
"constraints": [
"重点关注SQL注入、XSS、CSRF等安全漏洞",
"每个漏洞必须给出CWE编号",
"提供具体修复代码"
]
}
)


三、结构化输出控制

“给我输出JSON"和"给我输出符合这个JSON Schema的JSON”——效果天差地别。

import json
from pydantic import BaseModel, Field
from typing import Optional

class ProductReview(BaseModel):
"""结构化输出的Pydantic模型"""
product_name: str = Field(description="商品名称")
sentiment: str = Field(description="情感倾向", pattern="^(正面|负面|中性)$")
score: float = Field(description="评分", ge=0, le=5)
key_points: list[str] = Field(description="关键评价要点", min_items=1)
summary: str = Field(description="一句话总结", max_length=100)
category: Optional[str] = Field(None, description="商品分类")

def build_structured_prompt(task: str, schema_model) -> str:
"""
构建带JSON Schema约束的Prompt
核心思路:把Pydantic Schema转成自然语言约束 + JSON模板
"""
schema = schema_model.model_json_schema()
properties = schema.get("properties", {})

# 生成字段说明
field_descriptions = []
for field_name, field_info in properties.items():
desc = field_info.get("description", "")
field_type = field_info.get("type", "string")
required = field_name in schema.get("required", [])

# Pattern约束
pattern_str = ""
if "pattern" in field_info:
pattern_str = f" (必须匹配: {field_info['pattern']})"

# 数值约束
range_str = ""
if "minimum" in field_info:
range_str += f" >= {field_info['minimum']}"
if "maximum" in field_info:
range_str += f" <= {field_info['maximum']}"

req_mark = "【必填】" if required else "【可选】"
field_descriptions.append(
f" – {req_mark} {field_name} ({field_type}{range_str}): {desc}{pattern_str}"
)

prompt = f"""
{task}

严格按以下JSON Schema输出,不要添加任何解释文字:

{json.dumps(schema, ensure_ascii=False, indent=2)}

字段约束说明:
{"".join(field_descriptions)}

⚠️ 输出必须是纯JSON,能被 json.loads() 直接解析。
"""
return prompt

# 使用
task = "分析以下商品评论的情感:'这个手机拍照真不错,但电池续航太差了'"
structured_prompt = build_structured_prompt(task, ProductReview)
print(structured_prompt)

XML标签是另一个强大的约束工具——LLM对XML结构的理解出奇地好:

def xml_constrained_prompt(text: str) -> str:
"""使用XML标签强制约束输出格式"""
return f"""
分析以下文本的情感、主题和摘要。

<text>
{text}
</text>

严格按以下XML格式输出,不要有任何额外内容:

<analysis>
<sentiment>正面/负面/中性</sentiment>
<confidence>0到1之间的小数</confidence>
<topics>
<topic>主题1</topic>
<topic>主题2</topic>
</topics>
<summary>不超过50字的摘要</summary>
</analysis>

注意:XML标签必须完整闭合,不要遗漏任何标签。
"""


四、Few-shot示例选择策略

Few-shot不是随便扔几个例子。例子选错了,效果还不如Zero-shot。

关键问题:你手头100个示例,选哪3个放进Prompt? 答案是:选和当前输入语义最相似的。

import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from typing import Optional

class FewShotSelector:
"""
Few-shot示例选择器
策略:基于embedding的语义相似度自动选择最佳示例
"""

def __init__(self, examples: list[dict],
embedding_func: Optional[callable] = None):
"""
Args:
examples: [{"input": "…", "output": "…"}]
embedding_func: 文本 → 向量的函数
"""
self.examples = examples
self._embed = embedding_func or self._default_embed
self._example_embeddings = None
self._precompute()

def _default_embed(self, text: str) -> np.ndarray:
"""
默认embedding方法(生产环境请替换为OpenAI API调用)
这里用简单的字符级TF作为演示
"""
# 实际使用时替换为:
# response = openai.Embedding.create(
# model="text-embedding-3-small", input=text
# )
# return np.array(response.data[0].embedding)

# 演示用:简单的bag-of-chars
chars = set('abcdefghijklmnopqrstuvwxyz ')
vector = np.zeros(len(chars))
text_lower = text.lower()
for i, c in enumerate(chars):
vector[i] = text_lower.count(c) / max(len(text_lower), 1)
return vector

def _precompute(self):
"""预计算所有示例的embedding"""
self._example_embeddings = np.array([
self._embed(ex["input"]) for ex in self.examples
])

def select(self, query: str, k: int = 3,
diversity_weight: float = 0.2) -> list[dict]:
"""
选择k个最佳示例

Args:
query: 当前输入
k: 选择数量
diversity_weight: 多样性权重(0=纯相似度,1=纯多样性)
"""
query_embedding = self._embed(query).reshape(1, -1)
similarities = cosine_similarity(
query_embedding, self._example_embeddings
)[0]

# MMR (Maximal Marginal Relevance) 平衡相似度和多样性
selected_indices = []
remaining = list(range(len(self.examples)))

for _ in range(min(k, len(self.examples))):
if not selected_indices:
# 第一个选最相似的
best_idx = remaining[int(np.argmax(similarities[remaining]))]
else:
# 后续用MMR公式:λ*相似度 – (1-λ)*已选示例最大相似度
mmr_scores = []
for idx in remaining:
relevance = similarities[idx]
diversity = max(
cosine_similarity(
self._example_embeddings[idx].reshape(1, -1),
self._example_embeddings[selected_indices]
)[0]
) if selected_indices else 0
mmr = (1 – diversity_weight) * relevance – diversity_weight * diversity
mmr_scores.append(mmr)
best_idx = remaining[int(np.argmax(mmr_scores))]

selected_indices.append(best_idx)
remaining.remove(best_idx)

return [self.examples[i] for i in selected_indices]

# ===== 使用演示 =====
examples = [
{"input": "这个餐厅服务态度很差", "output": "负面"},
{"input": "菜品味道非常棒!", "output": "正面"},
{"input": "环境一般,价格还行", "output": "中性"},
{"input": "太难吃了,再也不会来了", "output": "负面"},
{"input": "性价比超高,推荐!", "output": "正面"},
{"input": "外卖送得很快但有点凉了", "output": "中性"},
]

selector = FewShotSelector(examples)
selected = selector.select("这家店口味绝了,下次还来", k=2)
for i, ex in enumerate(selected, 1):
print(f"示例{i}: {ex['input']} → {ex['output']}")


五、Prompt版本管理

Prompt是和代码一样重要的资产。不管理Prompt版本 = 不管理代码版本 = 等着出事故。

"""
Prompt版本管理系统
目录结构:
prompts/
├── code_review/
│ ├── v1.0.0.yaml
│ ├── v1.1.0.yaml
│ └── v2.0.0.yaml ← 当前生产版本
├── text_analysis/
│ └── v1.0.0.yaml
└── changelog.md
"""

import yaml
import hashlib
from datetime import datetime
from pathlib import Path

class PromptVersionManager:
"""基于文件系统的Prompt版本管理"""

def __init__(self, base_path: str = "./prompts"):
self.base_path = Path(base_path)
self.base_path.mkdir(parents=True, exist_ok=True)

def save(self, template: PromptTemplate, author: str = "system",
changelog: str = "") -> str:
"""保存新版本Prompt,自动生成版本号"""
prompt_dir = self.base_path / template.name
prompt_dir.mkdir(exist_ok=True)

# 自动递增版本号
existing = sorted(prompt_dir.glob("v*.yaml"))
if existing:
last_ver = existing[-1].stem.lstrip("v")
major, minor, patch = map(int, last_ver.split("."))
new_version = f"v{major}.{minor}.{patch + 1}"
else:
new_version = "v0.1.0"

# 序列化Prompt模板
data = {
"name": template.name,
"version": new_version,
"role": template.role,
"task": template.task,
"format_spec": template.format_spec,
"constraints": template.constraints,
"examples": template.examples,
"metadata": {
"author": author,
"created_at": datetime.now().isoformat(),
"content_hash": hashlib.sha256(
template.task.encode()
).hexdigest()[:8],
"changelog": changelog
}
}

filepath = prompt_dir / f"{new_version}.yaml"
with open(filepath, "w", encoding="utf-8") as f:
yaml.dump(data, f, allow_unicode=True, sort_keys=False)

# 更新changelog
self._update_changelog(template.name, new_version, changelog)

return new_version

def load(self, name: str, version: str = "latest") -> PromptTemplate:
"""加载指定版本的Prompt"""
prompt_dir = self.base_path / name

if version == "latest":
versions = sorted(prompt_dir.glob("v*.yaml"))
if not versions:
raise FileNotFoundError(f"No versions for: {name}")
filepath = versions[-1]
else:
filepath = prompt_dir / f"{version}.yaml"

with open(filepath, "r", encoding="utf-8") as f:
data = yaml.safe_load(f)

return PromptTemplate(
name=data["name"],
role=data.get("role", ""),
task=data["task"],
format_spec=data.get("format_spec", ""),
constraints=data.get("constraints", []),
examples=data.get("examples", []),
version=data["version"]
)

def diff(self, name: str, v1: str, v2: str) -> str:
"""对比两个版本差异"""
t1 = self.load(name, v1)
t2 = self.load(name, v2)

diff_lines = [f"# Diff: {name} {v1} → {v2}"]

if t1.task != t2.task:
diff_lines.append("\\n## 任务变更")
diff_lines.append(f"- {t1.task[:50]}…")
diff_lines.append(f"+ {t2.task[:50]}…")

if t1.constraints != t2.constraints:
diff_lines.append("\\n## 约束变更")
removed = set(t1.constraints) – set(t2.constraints)
added = set(t2.constraints) – set(t1.constraints)
for r in removed:
diff_lines.append(f"- [删除] {r}")
for a in added:
diff_lines.append(f"+ [新增] {a}")

return "\\n".join(diff_lines)

def _update_changelog(self, name: str, version: str, message: str):
changelog_path = self.base_path / "changelog.md"
with open(changelog_path, "a", encoding="utf-8") as f:
f.write(
f"\\n## [{version}] {name} – "
f"{datetime.now().strftime('%Y-%m-%d')}\\n"
f"{message}\\n"
)

配上git,这就是一套完整的Prompt CI/CD。每次改Prompt都走MR → Review → 合并 → 部署的标准流程。Prompt上线后效果变差?git revert 回滚到上一个版本,跟回滚代码一样简单。


六、A/B测试:Prompt的"灰度发布"

你怎么知道新Prompt真的比旧的好?凭感觉吗?不行——得跑实验。

import time
from collections import defaultdict
from statistics import mean, stdev

class PromptABTest:
"""Prompt A/B测试框架"""

def __init__(self, model_func):
self.model_func = model_func
self.results: dict[str, list[dict]] = defaultdict(list)

def run(self, variant: str, prompt: str,
test_cases: list[dict], metrics: list[str] = None):
"""
运行测试

Args:
variant: "A" 或 "B"
prompt: Prompt模板(含{input}占位符)
test_cases: [{"input": …, "expected": …}]
metrics: 需要计算的指标列表
"""
for case in test_cases:
start = time.time()
full_prompt = prompt.format(input=case["input"])
output = self.model_func(full_prompt)
elapsed = time.time() – start

self.results[variant].append({
"input": case["input"],
"expected": case.get("expected", ""),
"output": output,
"latency": elapsed
})

def compare(self, metrics: dict = None) -> dict:
"""对比A/B两组的差异"""
if metrics is None:
metrics = {"latency": lambda r: r["latency"]}

comparison = {}
for metric_name, metric_fn in metrics.items():
vals_a = [metric_fn(r) for r in self.results["A"]]
vals_b = [metric_fn(r) for r in self.results["B"]]

comparison[metric_name] = {
"A_mean": mean(vals_a),
"B_mean": mean(vals_b),
"A_std": stdev(vals_a) if len(vals_a) > 1 else 0,
"B_std": stdev(vals_b) if len(vals_b) > 1 else 0,
"delta": mean(vals_b) – mean(vals_a),
"delta_pct": (mean(vals_b) – mean(vals_a))
/ mean(vals_a) * 100 if mean(vals_a) != 0 else 0
}

return comparison

# ===== 使用示例 =====
def mock_model(prompt: str) -> str:
"""模拟模型调用(替换为真实的API调用)"""
import random
return f"Mock output for: {prompt[:30]}… (score: {random.randint(60,100)})"

ab_test = PromptABTest(mock_model)

test_cases = [
{"input": "这个产品怎么退换货?", "expected": "包含退换货流程"},
{"input": "物流到哪里了?", "expected": "包含物流查询方式"},
{"input": "下单后多久发货?", "expected": "包含发货时间承诺"},
]

# 版本A:简单Prompt
ab_test.run("A", "回答问题:{input}", test_cases)

# 版本B:结构化Prompt
ab_test.run("B", """
你是专业客服。请按以下格式回答:
问题:{input}
答案(50字以内):
""", test_cases)

result = ab_test.compare()
print(f"A/B测试结果:\\n{json.dumps(result, ensure_ascii=False, indent=2)}")

真正的A/B测试没那么简单——你需要定义"好"的标准。电商客服场景里,“好"可能是"用户不再追问”;代码生成场景里,“好"可能是"一次过编译”。先定义指标,再跑实验,否则数据毫无意义。


七、成本与质量的权衡

这是老板最关心但你最容易忽略的问题。

graph TD
Q["输入任务"] –> C{"复杂度分类器"}

C –>|"简单任务<br/>(翻译/提取/分类)"| S["Short Prompt<br/>L1-L2 级别<br/>Token: ~500"]
C –>|"中等任务<br/>(总结/改写)"| M["Medium Prompt<br/>L2-L3 级别<br/>Token: ~1500"]
C –>|"复杂任务<br/>(推理/代码/分析)"| L["Long Prompt<br/>L3-L4 级别<br/>Token: ~4000"]

S –> S_C["单次调用<br/>💰 成本: $0.002"]
M –> M_C["2-3次调用<br/>💰 成本: $0.01"]
L –> L_C["3-5次调用+评审<br/>💰 成本: $0.05"]

style S fill:#90EE90
style M fill:#FFD700
style L fill:#FF6347

核心决策框架——按任务价值匹配Prompt级别:

from enum import Enum

class TaskTier(Enum):
"""任务级别——决定了你愿意花多少钱"""
COST_SENSITIVE = 1 # 高频、低价值(如批量分类)
STANDARD = 2 # 常规业务
QUALITY_CRITICAL = 3 # 核心业务、不可出错

class CostQualityOptimizer:
"""成本-质量决策器"""

# 各级别的Prompt配置
TIER_CONFIG = {
TaskTier.COST_SENSITIVE: {
"max_tokens": 500,
"cot_enabled": False,
"self_reflection": False,
"model": "gpt-3.5-turbo",
"estimated_cost_per_1k": 0.001
},
TaskTier.STANDARD: {
"max_tokens": 1500,
"cot_enabled": True,
"self_reflection": False,
"model": "gpt-4o-mini",
"estimated_cost_per_1k": 0.003
},
TaskTier.QUALITY_CRITICAL: {
"max_tokens": 4000,
"cot_enabled": True,
"self_reflection": True,
"model": "gpt-4o",
"estimated_cost_per_1k": 0.03
}
}

@classmethod
def optimize(cls, task: str, tier: TaskTier,
expected_calls_per_day: int = 1000) -> dict:
"""
根据任务级别选择最优Prompt策略

Returns:
{
"strategy": Prompt策略说明,
"estimated_daily_cost": 预估日成本,
"prompt_template": 推荐的Prompt模板
}
"""
config = cls.TIER_CONFIG[tier]
tokens_per_call = config["max_tokens"]
cost_per_call = (tokens_per_call / 1000) * config["estimated_cost_per_1k"]

# 自反思额外成本
if config["self_reflection"]:
cost_per_call *= 3 # 生成+评审+修正

daily_cost = cost_per_call * expected_calls_per_day

return {
"tier": tier.name,
"model": config["model"],
"cot": config["cot_enabled"],
"self_reflection": config["self_reflection"],
"max_tokens_per_call": tokens_per_call,
"cost_per_call": round(cost_per_call, 5),
"estimated_daily_cost": round(daily_cost, 2),
"estimated_monthly_cost": round(daily_cost * 30, 2),
"strategy": cls._build_strategy_text(config)
}

@classmethod
def _build_strategy_text(cls, config: dict) -> str:
parts = []
parts.append(f"模型: {config['model']}")
parts.append(
"CoT: " + ("启用" if config['cot_enabled'] else "禁用"))
parts.append(
"自反思: " + ("启用" if config['self_reflection'] else "禁用"))
return " | ".join(parts)

# ===== 实际决策示例 =====
# 场景:每日10万条商品评论情感分类
result = CostQualityOptimizer.optimize(
task="商品评论情感分类",
tier=TaskTier.COST_SENSITIVE,
expected_calls_per_day=100000
)
print(f"""
成本分析:
– 策略:{result['strategy']}
– 单次调用成本:${result['cost_per_call']}
– 预估日成本:${result['estimated_daily_cost']}
– 预估月成本:${result['estimated_monthly_cost']}

💡 建议:10万条/天的批量分类,用GPT-3.5-turbo + L1级别Prompt就够了。
如果换成GPT-4o + 完整CoT + 自反思,月成本从$3,000飙升到$90,000。
质量提升5%,成本增加30倍——值不值?你自己判断。
""")


写在最后

回顾一下,我们从"把话说清楚"走到"让AI自己审自己",这是我从几十个项目里踩坑踩出来的体系。但这套体系最好的学习方式不是读完这篇文章——而是打开Colab,拿一个你工作中的真实任务,从L1开始一层一层往上加,对比每一层的输出质量变化。

你亲眼看到的变化,比读十篇文章都深刻。


声明

AI专业交流,信息共享,不构成投资建议。文中代码均为示例,使用前请充分测试。 笔者与文中提到的OpenAI、Anthropic等公司无利益关系。


福利时间

关注我,后台回复 “Prompt”,免费获取:

  • 本文全部代码的完整Python文件
  • 50+常用Prompt模板(中英文)
  • Prompt版本管理工具一键部署脚本

系列预告

下一篇:《AI项目评估——从准确率到用户满意度的多维度度量》

别再用"准确率"糊弄老板了。你会发现:

  • 准确率98%的模型,用户体验反而比95%的更差?(因为过度保守)
  • ROUGE/BLEU这些"经典"指标,在LLM时代已经过时了
  • 如何用LLM-as-Judge自动评估输出质量?比人工标注便宜100倍
  • 上线后监控:别等用户投诉才知道模型变差了

下一篇见。


标签: Prompt Engineering、LLM、思维链、Few-shot、模板设计、AI开发、GPT

赞(0)
未经允许不得转载:网硕互联帮助中心 » AI项目从入门到上线28-同样用GPT为什么别人比我写得好?Prompt Engineering系统方法论
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!