Prompt 版本如何管理?从手工修改到可追踪的配置系统
系列:Python + FastAPI 大模型应用基础(第 16 篇)
1. 为什么 Prompt 必须版本化
Prompt 是程序行为的一部分。一次标点、示例或字段调整都可能改变输出。如果只在数据库中覆盖一段文本,将无法回答:
- 某次请求实际用了哪个版本?
- 线上指标下降前改了什么?
- 能否回滚到旧版本?
- 测试报告对应哪份内容?
第一性原则是:已发布版本不可变,新改动产生新版本,请求保存精确身份。
2. 内容哈希保证"名实一致"
python
from dataclasses import dataclass
from hashlib import sha256
@dataclass(frozen=True)
class PromptVersion:
name: str
version: str
content: str
content_hash: str
@classmethod
def create(cls, name: str, version: str, content: str) -> "PromptVersion":
normalized = content.replace("\r\n", "\n").strip()
if not name or not version or not normalized:
raise ValueError("name、version、content 均不能为空")
digest = sha256(normalized.encode("utf-8")).hexdigest()
return cls(name, version, normalized, digest)
哈希不是加密,也不能证明内容安全;它用于检测内容是否被意外更改。
3. 一个不可变的内存仓库
python
class PromptRegistry:
"""演示版本约束;生产可替换为数据库实现。"""
def __init__(self) -> None:
self._items: dict[tuple[str, str], PromptVersion] = {}
self._active: dict[str, str] = {}
def publish(self, item: PromptVersion) -> None:
key = (item.name, item.version)
existing = self._items.get(key)
if existing and existing.content_hash != item.content_hash:
raise ValueError("已发布版本不可覆盖")
self._items[key] = item
def activate(self, name: str, version: str) -> None:
if (name, version) not in self._items:
raise KeyError("待激活版本不存在")
self._active[name] = version
def resolve(self, name: str, version: str | None = None) -> PromptVersion:
selected = version or self._active.get(name)
if selected is None:
raise KeyError("Prompt 尚无活动版本")
return self._items[(name, selected)]
activate 只是切换指针,旧版本仍保留。因此可以回滚,但发布系统还应记录操作者、审批单、时间和测试报告。
4. 请求日志保存什么
python
from dataclasses import asdict
from datetime import datetime, timezone
def build_prompt_audit(
prompt: PromptVersion,
request_id: str,
) -> dict[str, str]:
"""不记录用户原文,只记录复现行为需要的元数据。"""
return {
"request_id": request_id,
"prompt_name": prompt.name,
"prompt_version": prompt.version,
"prompt_hash": prompt.content_hash,
"used_at": datetime.now(timezone.utc).isoformat(),
}
是否记录输入摘要要依据业务合规要求,不能为了调试默认保存个人信息。
5. 可复验测试
python
def test_published_version_is_immutable() -> None:
registry = PromptRegistry()
original = PromptVersion.create("summary", "1.0.0", "总结:{text}")
registry.publish(original)
changed = PromptVersion.create("summary", "1.0.0", "详细总结:{text}")
try:
registry.publish(changed)
except ValueError:
pass
else:
raise AssertionError("相同版本号不应覆盖不同内容")
def test_activate_and_rollback() -> None:
registry = PromptRegistry()
v1 = PromptVersion.create("summary", "1.0.0", "简要总结")
v2 = PromptVersion.create("summary", "1.1.0", "结构化总结")
registry.publish(v1)
registry.publish(v2)
registry.activate("summary", "1.1.0")
assert registry.resolve("summary").version == "1.1.0"
registry.activate("summary", "1.0.0")
assert registry.resolve("summary").version == "1.0.0"
6. 推荐发布流程
text
提交新内容 → 自动校验变量 → 离线测试集 → 人工抽检
→ 灰度流量 → 指标观察 → 全量或回滚
配置系统不能只提供"编辑并保存"按钮。发布和激活应是两个动作,生产权限也应与编辑权限分离。
7. 对抗性审查
- Prompt 文件进入代码审查,不接受无记录的线上热改;
- 活动版本指针变更要写审计日志;
- 哈希计算前统一换行符,避免跨平台差异;
- 版本回滚要同时考虑输出 Schema 和下游兼容性;
- 日志记录模型版本、参数和工具版本,仅有 Prompt 版本仍不足以复现;
- 模板中的密钥即使有版本控制也不安全,应使用秘密管理系统。
8. 总结
Prompt 版本管理不是"给文件名加 v2",而是不可变内容、可追踪发布、可复现请求和可回滚指针组成的系统。