Python Prompt 工程实战——提示词模板、Few-shot、CoT 在工程中的实践
Python Prompt 工程实战——提示词模板、Few-shot、CoT 在工程中的实践
适读人群:正在做 AI 应用开发的工程师 | 阅读时长:约16分钟 | 核心价值:掌握 Prompt 工程的核心技术,把"玄学调参"变成可重复的工程实践
我认识一个叫小文的产品经理,去年转型做 AI 应用,写了三个月 Prompt,说这是他见过最奇怪的工作:昨天还好好的,今天换了一句话,AI 的回答就完全不一样了。他感觉自己在玄学占卜,不知道哪句话管用哪句话没用。
他来找我聊,说:"老张,Prompt 工程到底有没有方法论?感觉都是靠蒙。"
我说:有,而且很系统。之所以感觉像玄学,是因为你在用"写作直觉"在做"工程问题"。Prompt 工程有一套可以学习、可以复现的技术体系,今天我们把它讲清楚。
Prompt 工程的本质是通过精心设计的输入,引导模型产生符合预期的输出。它不是玄学,它是控制论。
一、Prompt 的基础结构
一个完整的 Prompt 通常包含四个部分:
- 角色(Role):告诉模型它是谁,确立专业身份
- 任务(Task):清晰描述要做什么
- 约束(Constraints):输出格式、长度、语气等限制
- 示例(Examples):可选,用 few-shot 示范期望的输出
from string import Template
from typing import Dict, List, Optional
class PromptTemplate:
"""
可管理的提示词模板类
支持变量替换、版本管理、输出验证
"""
def __init__(self, template: str, version: str = "1.0", description: str = ""):
self.template = template
self.version = version
self.description = description
self._required_vars = self._extract_variables()
def _extract_variables(self) -> List[str]:
"""提取模板中的变量名"""
import re
return re.findall(r'\{(\w+)\}', self.template)
def render(self, **kwargs) -> str:
"""渲染模板,填充变量"""
# 检查必需变量
missing = [v for v in self._required_vars if v not in kwargs]
if missing:
raise ValueError(f"缺少必需变量:{missing}")
result = self.template
for key, value in kwargs.items():
result = result.replace(f"{{{key}}}", str(value))
return result
def validate_output(self, output: str) -> bool:
"""验证输出是否符合预期(子类可重写)"""
return bool(output.strip())
# 代码审查 Prompt 模板
CODE_REVIEW_PROMPT = PromptTemplate(
template="""你是一个有15年经验的高级工程师,专门做 Python 代码审查。
## 审查任务
请对以下 {language} 代码进行专业审查。
## 代码
```{language}
{code}审查要求
- 安全性:检查 SQL 注入、XSS、路径遍历等安全漏洞
- 性能:找出潜在的性能瓶颈(N+1 查询、不必要的循环等)
- 可维护性:代码是否清晰、是否需要注释、命名是否规范
- 健壮性:异常处理是否完整,边界条件是否处理
输出格式
按照以下 JSON 格式输出,不要有任何额外文字:
{{
"severity": "high|medium|low",
"issues": [
{{
"type": "security|performance|maintainability|robustness",
"description": "问题描述",
"line_hint": "大概在哪行",
"suggestion": "具体改进建议",
"example": "改进后的代码片段(如果适用)"
}}
],
"overall_score": 0-100,
"summary": "总体评价,50字以内"
}}
```""",
version="2.1",
description="Python 代码审查,输出结构化 JSON"
)
# 使用
prompt_text = CODE_REVIEW_PROMPT.render(
language="Python",
code="""
def get_user(username):
sql = f"SELECT * FROM users WHERE name = '{username}'"
return db.execute(sql)
"""
)
print(prompt_text[:300] + "...")二、Few-shot Prompting——用示例教模型
Few-shot 是最有效的 Prompt 技巧之一,特别是当你需要特定格式或特定风格时。
2.1 情感分析的 Few-shot 示例
from openai import OpenAI
import json
client = OpenAI()
def create_few_shot_sentiment_prompt(user_review: str) -> list:
"""
用 few-shot 教模型进行多维度情感分析
比 zero-shot 准确率高很多
"""
few_shot_examples = [
{
"role": "user",
"content": "分析这条用户评论的情感:这款手机拍照效果挺好的,就是电池不耐用,两天就得充一次电"
},
{
"role": "assistant",
"content": json.dumps({
"overall": "mixed",
"score": 6,
"dimensions": {
"camera": {"sentiment": "positive", "score": 8, "mention": "拍照效果挺好"},
"battery": {"sentiment": "negative", "score": 3, "mention": "两天充一次电"}
},
"key_complaint": "电池续航差",
"key_praise": "拍照质量好"
}, ensure_ascii=False)
},
{
"role": "user",
"content": "分析这条用户评论的情感:客服响应很慢,等了半小时才有人,但问题最终解决了,还是挺满意的"
},
{
"role": "assistant",
"content": json.dumps({
"overall": "positive",
"score": 7,
"dimensions": {
"service_speed": {"sentiment": "negative", "score": 3, "mention": "等了半小时"},
"problem_resolution": {"sentiment": "positive", "score": 9, "mention": "问题最终解决了"}
},
"key_complaint": "响应速度慢",
"key_praise": "最终解决了问题"
}, ensure_ascii=False)
}
]
messages = [
{
"role": "system",
"content": "你是一个专业的情感分析助手。对用户评论进行多维度情感分析,"
"严格按照 JSON 格式输出,不要有其他文字。"
}
] + few_shot_examples + [
{
"role": "user",
"content": f"分析这条用户评论的情感:{user_review}"
}
]
return messages
# 使用
def analyze_sentiment(review: str) -> dict:
messages = create_few_shot_sentiment_prompt(review)
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=messages,
temperature=0, # 情感分析要确定性,temperature=0
response_format={"type": "json_object"} # 强制 JSON 输出
)
return json.loads(response.choices[0].message.content)
result = analyze_sentiment("价格有点贵,但是质量真的没话说,用了一年了还是很好")
print(json.dumps(result, ensure_ascii=False, indent=2))三、Chain of Thought(CoT)——让模型先思考后回答
CoT 是让模型在给出最终答案前,先一步步推理的技术。特别适合数学计算、逻辑推理、复杂分析等需要多步思考的任务。
3.1 Zero-shot CoT vs Few-shot CoT
def solve_with_cot(problem: str, cot_type: str = "zero_shot") -> str:
"""
使用 Chain of Thought 解决复杂问题
cot_type: "zero_shot" 或 "few_shot"
"""
if cot_type == "zero_shot":
# Zero-shot CoT:只加一句"一步一步思考"
messages = [
{
"role": "user",
"content": f"{problem}\n\n请一步一步地思考,然后给出最终答案。"
}
]
elif cot_type == "few_shot":
# Few-shot CoT:提供推理示例
messages = [
{
"role": "system",
"content": "你是一个擅长逻辑推理的分析师。对于每个问题,先拆分分析步骤,然后得出结论。"
},
# 示例推理过程
{
"role": "user",
"content": "一个项目团队有3个开发、2个测试、1个产品。开发效率是测试的2倍,"
"产品每天出3个需求,开发每人每天做2个需求,测试每人每天验证1个需求。"
"这个团队是否平衡?"
},
{
"role": "assistant",
"content": """让我逐步分析:
**第一步:计算每日产能**
- 产品输出:1人 × 3个/天 = 3个需求/天
- 开发产能:3人 × 2个/天 = 6个需求/天
- 测试产能:2人 × 1个/天 = 2个验证/天
**第二步:找出瓶颈**
- 需求流入:3个/天
- 开发消化:6个/天(超过需求,开发有余量)
- 测试验证:2个/天(低于需求产出,测试是瓶颈)
**第三步:量化不平衡**
- 需求堆积速率:3-2=1个/天
- 一周后积压:5个
- 两周后积压:10个(相当于开发2.5天的工作量)
**结论:不平衡。测试是瓶颈,建议增加1名测试或把测试产能提升到至少3个/天。**"""
},
# 实际问题
{
"role": "user",
"content": problem
}
]
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
temperature=0.3
)
return response.choices[0].message.content
# 测试
problem = """
我需要在一个月内完成以下任务:
- 开发一个 RAG 系统(预计 10 天)
- 写技术文档(预计 5 天)
- 做性能测试(预计 3 天)
- 修复已知 Bug(预计 2 天)
我每周工作5天,每天可用于这个项目的时间是6小时。
同时,每周有 2 天需要开会,开会不能工作。
请问我能在一个月内完成所有任务吗?
"""
print("=== Zero-shot CoT ===")
print(solve_with_cot(problem, "zero_shot"))四、结构化输出——让 AI 输出可编程的结果
from pydantic import BaseModel, Field
from typing import List, Literal
import instructor
from openai import OpenAI
# 使用 instructor 库强制结构化输出
# pip install instructor
client = instructor.from_openai(OpenAI())
class CodeIssue(BaseModel):
issue_type: Literal["security", "performance", "maintainability", "bug"]
severity: Literal["critical", "high", "medium", "low"]
description: str = Field(description="问题描述")
line_number: Optional[int] = Field(None, description="问题所在行号(如果能确定)")
fix_suggestion: str = Field(description="修复建议")
class CodeReviewResult(BaseModel):
overall_score: int = Field(ge=0, le=100, description="代码质量分(0-100)")
issues: List[CodeIssue] = Field(description="发现的问题列表")
summary: str = Field(max_length=200, description="总体评价")
is_production_ready: bool = Field(description="是否可以上生产环境")
def structured_code_review(code: str, language: str = "Python") -> CodeReviewResult:
"""返回结构化的代码审查结果,类型安全"""
result = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": "你是一个专业的代码审查专家,请对代码进行全面审查。"
},
{
"role": "user",
"content": f"请审查以下 {language} 代码:\n\n```{language}\n{code}\n```"
}
],
response_model=CodeReviewResult # instructor 会自动处理结构化提取
)
return result # 返回的是 CodeReviewResult 对象,有类型提示
# 使用
code_sample = """
import os
def read_config(env):
config_file = f"/configs/{env}/settings.conf"
with open(config_file) as f:
return f.read()
"""
result = structured_code_review(code_sample)
print(f"代码质量分:{result.overall_score}/100")
print(f"可以上线:{result.is_production_ready}")
print(f"\n发现问题:")
for issue in result.issues:
print(f" [{issue.severity.upper()}] {issue.issue_type}: {issue.description}")五、踩坑实录一:Prompt 版本管理混乱
现象:同事 A 改了 Prompt 效果更好,但同事 B 同时也在改,最后谁的改动生效了不知道,也不知道哪个版本的效果最好。
解法:
import json
from datetime import datetime
from pathlib import Path
class PromptRegistry:
"""Prompt 版本管理器"""
def __init__(self, storage_path: str = "./prompts"):
self.storage_path = Path(storage_path)
self.storage_path.mkdir(exist_ok=True)
def save(self, name: str, prompt: str, metadata: dict = None):
"""保存 Prompt 版本"""
version_data = {
"name": name,
"prompt": prompt,
"created_at": datetime.now().isoformat(),
"metadata": metadata or {}
}
# 自动递增版本号
existing = list(self.storage_path.glob(f"{name}_v*.json"))
version_num = len(existing) + 1
file_path = self.storage_path / f"{name}_v{version_num:03d}.json"
with open(file_path, "w", encoding="utf-8") as f:
json.dump(version_data, f, ensure_ascii=False, indent=2)
print(f"已保存:{file_path}")
return file_path
def load_latest(self, name: str) -> str:
"""加载最新版本"""
versions = sorted(self.storage_path.glob(f"{name}_v*.json"))
if not versions:
raise FileNotFoundError(f"未找到 Prompt:{name}")
with open(versions[-1], "r", encoding="utf-8") as f:
return json.load(f)["prompt"]
# 使用
registry = PromptRegistry("./prompt_store")
registry.save("code_review", CODE_REVIEW_PROMPT.template, {"author": "老张", "task": "代码审查"})
prompt_text = registry.load_latest("code_review")六、踩坑实录二:模型"假装"遵守了格式但实际没有
现象:要求模型输出 JSON,但有时返回的是 ````json\n{...}\n```(带代码块标记),有时直接返回文字,无法直接 json.loads()。
解法:
import re
import json
def robust_json_parse(response: str) -> dict:
"""鲁棒的 JSON 解析,处理各种格式"""
# 先尝试直接解析
try:
return json.loads(response)
except json.JSONDecodeError:
pass
# 尝试提取代码块中的 JSON
json_match = re.search(r'```(?:json)?\s*(\{[\s\S]*?\})\s*```', response)
if json_match:
try:
return json.loads(json_match.group(1))
except json.JSONDecodeError:
pass
# 尝试直接找第一个 { ... } 结构
brace_match = re.search(r'(\{[\s\S]*\})', response)
if brace_match:
try:
return json.loads(brace_match.group(1))
except json.JSONDecodeError:
pass
# 实在解析不了,返回原始文本
return {"error": "JSON parse failed", "raw": response}
# 同时,在 Prompt 中加强格式约束
STRICT_JSON_PROMPT = """你必须严格输出 JSON,规则:
1. 只输出 JSON,不要有任何其他文字
2. 不要有代码块标记(不要有 ```json)
3. 第一个字符必须是 {,最后一个字符必须是 }
输出格式:
{"key": "value"}"""七、踩坑实录三:Few-shot 示例数量过多适得其反
现象:加了10个 few-shot 示例后,模型变得更加"刻板",对一些边缘情况的回答反而变差了,有时甚至只能输出与示例极为相似的内容。
原因:过多的示例会压缩模型的"创造空间",让它过于拘泥于示例的格式,失去灵活性。
解法:
# Few-shot 最佳实践
# 1. 示例数量:3-5个通常最佳
# 2. 示例质量比数量重要
# 3. 示例要覆盖不同情况(正例、负例、边缘情况)
GOOD_FEW_SHOT_DESIGN = {
"guidelines": [
"示例1: 典型正向案例(最常见情况)",
"示例2: 典型负向案例(有问题的情况)",
"示例3: 边缘情况(两种都有的混合案例)",
# 3个通常够了,最多5个
],
"avoid": [
"不要用完全相同结构的多个示例(模型学不到泛化能力)",
"不要用示例覆盖每种可能的情况(示例太多反而干扰模型)",
"不要让所有示例都是正向的(模型不知道什么时候该拒绝或警告)"
]
}Prompt 工程从本质上讲是一种沟通工程——你在和一个极其聪明但需要明确指引的合作者沟通。掌握了角色设定、约束声明、示例引导、格式规范这几个核心技术,80% 的 Prompt 问题都能解决。剩下的 20% 靠实验和数据说话,不是玄学,是迭代。
