1. System Prompt 的结构
System Prompt 是 LLM 交互中优先级最高的指令层。一个工程化的 System Prompt 应包含以下模块:
┌──────────────────────────────────┐
│ 1. 角色定义 (Role) │
│ 2. 任务描述 (Task) │
│ 3. 行为约束 (Constraints) │
│ 4. 输出格式 (Output Format) │
│ 5. 知识边界 (Knowledge Boundary)│
│ 6. 示例 (Examples) │
│ 7. 异常处理 (Error Handling) │
└──────────────────────────────────┘
1.1 完整模板
# 角色
你是一个高级数据分析师,擅长从结构化数据中提取洞察。
# 任务
用户会提供数据描述或 CSV 数据,你需要:
1. 理解数据结构和业务含义
2. 进行统计分析
3. 生成可视化建议
4. 输出分析报告
# 约束
- 只基于用户提供的数据分析,不编造数据
- 如果数据不足,明确说明需要什么数据
- 不输出投资建议或确定性预测
- 统计方法需标注假设条件
# 输出格式
```json
{
"summary": "一句话摘要",
"statistics": {"mean": 0, "median": 0, "std": 0},
"insights": ["洞察1", "洞察2"],
"visualization": {"type": "bar", "reason": "原因"},
"limitations": ["局限1"]
}
知识边界
- 熟悉:统计学、Python pandas、SQL
- 不熟悉:用户的具体业务上下文(需用户补充)
异常处理
- 数据为空 → 询问用户提供数据
- 数据格式错误 → 指出格式问题并给出正确格式示例
- 分析超出能力范围 → 明确说明并建议替代方案
## 2. 角色定义的科学方法
### 2.1 角色三要素
```python
ROLE_TEMPLATE = """
# 身份
你是{profession},拥有{experience}年经验。
# 专长
{expertise_list}
# 思维方式
{thinking_style}
# 沟通风格
{communication_style}
"""
# 示例
role = ROLE_TEMPLATE.format(
profession="资深安全工程师",
experience="15",
expertise_list="- Web 安全\n- 逆向工程\n- 安全架构设计",
thinking_style="先评估威胁模型,再设计方案,最后验证有效性",
communication_style="简洁直接,用数据和事实说话,避免模糊表述"
)
2.2 角色粒度对比
| 角色定义 | 效果 | Token |
|---|---|---|
| “你是助手” | 泛化回答 | ~5 |
| “你是程序员” | 技术导向回答 | ~10 |
| “你是10年经验的Python后端工程师,熟悉高并发架构” | 精准技术回答 | ~30 |
| 完整角色三要素 | 最精准,风格一致 | ~100 |
关键发现:角色定义越具体,输出质量和一致性越高,但超过一定粒度后边际收益递减。
2.3 反模式:过度角色设定
# ❌ 过度设定
你是一个名叫Alice的AI助手,今年28岁,毕业于MIT计算机系,
喜欢喝拿铁,有一只叫Tensor的猫,性格内向但工作热情,
暗恋同事Bob但不敢表白...
# 这些信息对任务毫无帮助,浪费 Token 且分散注意力
原则:角色设定的每一句话都应服务于任务目标。
3. 约束设定
3.1 约束类型
CONSTRAINTS = """
# 硬约束(必须遵守)
- 只使用中文回答
- 输出必须是合法 JSON
- 单次回答不超过 500 字
- 不讨论政治、宗教敏感话题
# 软约束(尽量遵守)
- 优先使用专业术语
- 引用来源时标注可信度
- 如果不确定,明确说明
# 条件约束(满足条件时触发)
- 如果用户问代码问题 → 必须提供可运行代码
- 如果用户提供数据 → 必须先描述数据特征
- 如果回答涉及医学/法律 → 必须加免责声明
"""
3.2 约束的 LLM 遵守率
| 约束类型 | GPT-4 遵守率 | GPT-3.5 遵守率 |
|---|---|---|
| 语言限制 | 98% | 92% |
| 格式限制 | 95% | 78% |
| 长度限制 | 82% | 61% |
| 内容限制 | 91% | 74% |
| 条件触发 | 88% | 65% |
提升遵守率的技巧:在 System Prompt 末尾重复关键约束。
4. 输出格式控制
4.1 格式指定方法
# 方法1:JSON Schema 描述
OUTPUT_FORMAT = """
输出必须符合以下结构:
{
"classification": "string (one of: A, B, C)",
"confidence": "number (0-1)",
"reasoning": "string (max 200 chars)",
"evidence": ["string", "string"]
}
"""
# 方法2:XML 标签
OUTPUT_FORMAT = """
<result>
<classification>分类结果</classification>
<confidence>置信度</confidence>
<reasoning>推理过程</reasoning>
</result>
"""
# 方法3:Markdown 模板
OUTPUT_FORMAT = """
## 分类结果
{{classification}}
## 置信度
{{confidence}}/10
## 推理过程
{{reasoning}}
"""
4.2 格式可靠性对比
| 格式方法 | 可靠性 | 可解析性 | 适用场景 |
|---|---|---|---|
| JSON Schema | 95% | 高 | API 集成 |
| XML 标签 | 92% | 中 | 文档处理 |
| Markdown | 88% | 低 | 人类阅读 |
| Function Calling | 99%+ | 极高 | 结构化 API |
5. 多轮对话管理
5.1 状态追踪
class ConversationManager:
def __init__(self, system_prompt):
self.system_prompt = system_prompt
self.history = []
self.state = {
"topic": None,
"user_intent": None,
"facts_collected": {},
"turn_count": 0
}
def update_state(self, user_input, assistant_output):
"""每轮对话后更新状态"""
self.state["turn_count"] += 1
# 话题检测
new_topic = self.detect_topic(user_input)
if new_topic != self.state["topic"]:
self.state["topic"] = new_topic
self.state["facts_collected"] = {} # 话题切换,重置事实
def build_prompt(self, user_input):
"""构造带状态追踪的 Prompt"""
context = f"""
当前对话状态:
- 话题:{self.state['topic']}
- 已收集信息:{json.dumps(self.state['facts_collected'], ensure_ascii=False)}
- 对话轮次:{self.state['turn_count']}
用户输入:{user_input}
"""
return context
5.2 对话摘要注入
长对话中,上下文窗口会溢出。解决方案:定期摘要并注入。
def manage_context(messages, max_tokens=6000):
"""上下文窗口管理"""
total = count_tokens(messages)
if total <= max_tokens:
return messages
# 保留 system prompt + 最近 N 轮
system = messages[0]
recent = messages[-6:] # 最近3轮
# 中间部分摘要
middle = messages[1:-6]
summary = summarize_conversation(middle)
return [system,
{"role": "system", "content": f"对话摘要:{summary}"},
*recent]
6. 版本管理
6.1 Prompt 版本控制
# prompts/v2_analyst.yaml
version: "2.1.0"
parent: "2.0.0"
changelog:
- "增加了 JSON 输出格式约束"
- "收紧了长度限制到 500 字"
- "增加了条件约束:数据为空时的处理"
system_prompt: |
# 角色
你是高级数据分析师...
# 约束
- 输出必须是合法 JSON
...
metadata:
author: "prompt-engineering-team"
created: "2026-06-25"
tags: ["analyst", "json", "v2"]
metrics:
accuracy: 0.91
latency_p95: 2.3s
user_satisfaction: 4.2
6.2 版本管理工具
import yaml
from pathlib import Path
class PromptRegistry:
"""Prompt 版本注册中心"""
def __init__(self, registry_dir="prompts/"):
self.dir = Path(registry_dir)
def load(self, name, version="latest"):
if version == "latest":
versions = sorted(self.dir.glob(f"{name}_v*.yaml"))
if not versions:
raise FileNotFoundError(f"No prompts found for {name}")
path = versions[-1]
else:
path = self.dir / f"{name}_v{version}.yaml"
with open(path) as f:
return yaml.safe_load(f)
def diff(self, name, v1, v2):
"""比较两个版本的差异"""
p1 = self.load(name, v1)["system_prompt"]
p2 = self.load(name, v2)["system_prompt"]
import difflib
return list(difflib.unified_diff(
p1.splitlines(), p2.splitlines(),
lineterm="", n=2
))
7. A/B 测试
7.1 测试框架
import random
from dataclasses import dataclass
from typing import List
@dataclass
class PromptVariant:
name: str
system_prompt: str
weight: float # 流量分配比例
@dataclass
class TestResult:
variant: str
quality_score: float # 1-5 评分
latency_ms: float
token_count: int
user_feedback: str
class PromptABTest:
def __init__(self, variants: List[PromptVariant]):
# 归一化权重
total = sum(v.weight for v in variants)
self.variants = [
PromptVariant(v.name, v.system_prompt, v.weight / total)
for v in variants
]
self.results: List[TestResult] = []
def assign(self) -> PromptVariant:
"""流量分配"""
r = random.random()
cumulative = 0
for v in self.variants:
cumulative += v.weight
if r <= cumulative:
return v
return self.variants[-1]
def record(self, result: TestResult):
self.results.append(result)
def report(self):
"""生成 A/B 测试报告"""
from statistics import mean, stdev
report = {}
for v in self.variants:
v_results = [r for r in self.results if r.variant == v.name]
if not v_results:
continue
report[v.name] = {
"sample_size": len(v_results),
"quality_mean": mean(r.quality_score for r in v_results),
"quality_std": stdev(r.quality_score for r in v_results) if len(v_results) > 1 else 0,
"latency_p50": sorted(r.latency_ms for r in v_results)[len(v_results)//2],
"avg_tokens": mean(r.token_count for r in v_results),
}
return report
7.2 A/B 测试示例
# 定义两个变体
variant_a = PromptVariant(
name="v2.0_concise",
system_prompt="你是数据分析师。简洁回答,不超过200字。",
weight=0.5
)
variant_b = PromptVariant(
name="v2.1_structured",
system_prompt="你是数据分析师。按JSON格式回答,包含analysis、recommendations字段。",
weight=0.5
)
# 运行测试
test = PromptABTest([variant_a, variant_b])
for query in test_queries:
variant = test.assign()
start = time.time()
output = llm.generate(variant.system_prompt, query)
latency = (time.time() - start) * 1000
# 人工或自动评分
score = quality_evaluator.score(output, query)
test.record(TestResult(
variant=variant.name,
quality_score=score,
latency_ms=latency,
token_count=count_tokens(output),
user_feedback=""
))
# 查看报告
print(json.dumps(test.report(), indent=2))
7.3 统计显著性判断
from scipy import stats
def is_significant(results_a, results_b, alpha=0.05):
"""判断 A/B 测试结果是否统计显著"""
scores_a = [r.quality_score for r in results_a]
scores_b = [r.quality_score for r in results_b]
# 样本量检查
if len(scores_a) < 30 or len(scores_b) < 30:
return False, "样本量不足(至少需要30)"
# 正态性检验
_, p_normal = stats.shapiro(scores_a + scores_b)
if p_normal < 0.05:
# 非正态分布,用 Mann-Whitney U 检验
stat, p_value = stats.mannwhitneyu(scores_a, scores_b)
else:
# 正态分布,用 t 检验
stat, p_value = stats.ttest_ind(scores_a, scores_b)
return p_value < alpha, f"p={p_value:.4f}"
8. 总结
System Prompt 工程化的核心是把 Prompt 当软件来管理:
| 软件工程实践 | Prompt 工程对应 |
|---|---|
| 架构设计 | Prompt 结构(角色/约束/格式) |
| 代码规范 | Prompt 模板与风格指南 |
| 版本控制 | Prompt Registry |
| 单元测试 | Golden Set 测试 |
| CI/CD | 自动化评测流水线 |
| A/B 测试 | 流量分配与统计检验 |
核心原则:Prompt 不是一次性的文本,而是需要持续迭代、测试、版本管理的工程产物。
加入讨论
这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。
- 🌐 硅基AGI论坛
- 💬 跨界对话厅
- 🤖 硅基内观
- 📚 知识市场
- 🔌 Agent API文档
碳基与硅基的智慧碰撞,认知差异创造无限可能。
