引言
Agent 系统的三个核心维度——Prompt、工具、模型——任何一个的变更都可能引发连锁反应。传统软件的版本管理主要针对代码,而 Agent 还需要管理自然语言"代码"(Prompt)、动态加载的工具和外部模型版本。本文将构建完整的 Agent 版本管理体系。
一、Agent 版本的复杂性
变更类型与风险
┌──────────────────────────────────────────────────────┐
│ Agent 变更类型与风险矩阵 │
├──────────────┬──────────┬──────────┬─────────────────┤
│ 变更类型 │ 频率 │ 风险等级 │ 影响范围 │
├──────────────┼──────────┼──────────┼─────────────────┤
│ Prompt 修改 │ 每周 │ 中-高 │ 输出质量/行为 │
│ 工具更新 │ 每月 │ 中 │ 工具调用/结果 │
│ 模型升级 │ 每季度 │ 高 │ 全局行为变化 │
│ System配置 │ 每周 │ 低-中 │ 性能/限制 │
│ Few-shot示例 │ 每月 │ 中 │ 输出风格/格式 │
│ 工作流变更 │ 每月 │ 高 │ 执行路径/延迟 │
└──────────────┴──────────┴──────────┴─────────────────┘
版本组合的笛卡尔积问题
Agent 行为是 Prompt版本 × 工具版本 × 模型版本 的组合。如果三者各自有 3 个版本,理论上存在 27 种组合。版本管理的目标就是确保任意组合的行为可预测、可回滚。
二、版本化数据模型
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
class VersionStatus(Enum):
DRAFT = "draft"
STAGING = "staging"
CANARY = "canary" # 灰度中
LIVE = "live" # 全量发布
ROLLED_BACK = "rolled_back"
ARCHIVED = "archived"
@dataclass
class AgentVersion:
version_id: str # 如 "v1.3.2"
agent_name: str
prompt_version: str # 引用 PromptVersion
tools_version: str # 引用 ToolsVersion
model_config_version: str # 引用 ModelConfigVersion
workflow_version: str # 引用 WorkflowVersion
status: VersionStatus
created_at: datetime
rollout_percentage: float # 灰度比例 0-100
parent_version: str | None # 上一个版本
changelog: str
metrics: dict # 质量指标快照
@dataclass
class PromptVersion:
version_id: str # "prompt-v3.1"
system_prompt: str
few_shot_examples: list[dict]
temperature: float
max_tokens: int
output_format: str | None
parent_version: str | None
diff: str # 与上一版本的 diff
created_at: datetime
author: str
review_status: str # pending/approved/rejected
@dataclass
class ToolsVersion:
version_id: str
tools: list[ToolConfig] # 工具列表及配置
added: list[str] # 新增工具
removed: list[str] # 移除工具
modified: list[str] # 修改工具
parent_version: str | None
@dataclass
class ModelConfigVersion:
version_id: str
primary_model: str # 模型名称+版本
fallback_model: str
parameters: dict # temperature, top_p, etc.
parent_version: str | None
三、版本仓库实现
class AgentVersionRepository:
"""Agent 版本仓库"""
def __init__(self, db, storage):
self.db = db
self.storage = storage # 对象存储(存 Prompt 大文本等)
async def create_version(
self,
agent_name: str,
changes: VersionChanges,
parent_version: str | None = None
) -> AgentVersion:
"""创建新版本"""
# 1. 解析变更
prompt_v = await self._version_prompt(changes.prompt, parent_version)
tools_v = await self._version_tools(changes.tools, parent_version)
model_v = await self._version_model(changes.model_config, parent_version)
workflow_v = await self._version_workflow(changes.workflow, parent_version)
# 2. 生成版本号
new_version_id = self._bump_version(parent_version, changes.severity)
# 3. 计算差异
diffs = await self._compute_diffs(parent_version, changes)
# 4. 创建版本记录
version = AgentVersion(
version_id=new_version_id,
agent_name=agent_name,
prompt_version=prompt_v,
tools_version=tools_v,
model_config_version=model_v,
workflow_version=workflow_v,
status=VersionStatus.DRAFT,
created_at=datetime.now(),
rollout_percentage=0,
parent_version=parent_version,
changelog=changes.description,
metrics={}
)
await self.db.save(version)
return version
def _bump_version(self, parent: str | None, severity: str) -> str:
"""语义化版本号"""
if not parent:
return "v1.0.0"
major, minor, patch = parent.lstrip("v").split(".")
if severity == "major": # 模型变更、工作流重构
return f"v{int(major)+1}.0.0"
elif severity == "minor": # 新增工具、Prompt 大改
return f"v{major}.{int(minor)+1}.0"
else: # patch: 小修改
return f"v{major}.{int(minor)}.{int(patch)+1}"
class PromptDiffCalculator:
"""Prompt 差异计算器"""
def calculate(self, old: str, new: str) -> PromptDiff:
lines_old = old.splitlines()
lines_new = new.splitlines()
import difflib
diff = list(difflib.unified_diff(
lines_old, lines_new,
lineterm='',
n=3
))
added = sum(1 for l in diff if l.startswith('+') and not l.startswith('+++'))
removed = sum(1 for l in diff if l.startswith('-') and not l.startswith('---'))
return PromptDiff(
diff_text='\n'.join(diff),
lines_added=added,
lines_removed=removed,
change_ratio=(added + removed) / max(len(lines_old), 1)
)
四、灰度发布引擎
class RolloutEngine:
"""灰度发布引擎"""
ROLLOUT_STAGES = [
{"percentage": 0, "duration_minutes": 0, "label": "init"},
{"percentage": 1, "duration_minutes": 30, "label": "canary"},
{"percentage": 5, "duration_minutes": 60, "label": "early"},
{"percentage": 25, "duration_minutes": 120, "label": "mid"},
{"percentage": 50, "duration_minutes": 240, "label": "late"},
{"percentage": 100, "duration_minutes": 0, "label": "complete"},
]
def __init__(self, db, metrics_collector, alert_manager):
self.db = db
self.metrics = metrics_collector
self.alerts = alert_manager
async def start_rollout(
self,
version_id: str,
auto_rollback: bool = True,
success_threshold: float = 0.95,
error_rate_threshold: float = 0.05
) -> RolloutSession:
"""启动灰度发布"""
session = RolloutSession(
version_id=version_id,
stage_index=0,
auto_rollback=auto_rollback,
success_threshold=success_threshold,
error_rate_threshold=error_rate_threshold,
started_at=datetime.now(),
baseline_metrics=await self._capture_baseline(version_id)
)
await self._advance_stage(session)
return session
async def _advance_stage(self, session: RolloutSession):
"""推进到下一灰度阶段"""
next_stage = self.ROLLOUT_STAGES[session.stage_index]
# 更新流量分配
await self._set_traffic_split(
session.version_id,
next_stage["percentage"]
)
session.current_stage = next_stage["label"]
session.stage_started_at = datetime.now()
logger.info(
f"Rollout {session.version_id}: "
f"stage={next_stage['label']}, "
f"traffic={next_stage['percentage']}%"
)
# 启动监控
asyncio.create_task(self._monitor_stage(session, next_stage))
async def _monitor_stage(self, session: RolloutSession, stage: dict):
"""监控灰度阶段指标"""
await asyncio.sleep(stage["duration_minutes"] * 60)
# 收集指标
current_metrics = await self._collect_metrics(session.version_id)
baseline = session.baseline_metrics
# 评估
assessment = self._assess_metrics(current_metrics, baseline, session)
if assessment.should_rollback:
await self._rollback(session, assessment.reason)
elif assessment.should_proceed:
session.stage_index += 1
if session.stage_index < len(self.ROLLOUT_STAGES):
await self._advance_stage(session)
else:
await self._complete(session)
else:
# 指标不确定,等待更多数据
await asyncio.sleep(600) # 再等10分钟
await self._monitor_stage(session, stage)
def _assess_metrics(
self,
current: dict,
baseline: dict,
session: RolloutSession
) -> RolloutAssessment:
"""评估灰度指标"""
# 错误率检查
if current["error_rate"] > session.error_rate_threshold:
return RolloutAssessment(
should_rollback=True,
reason=f"Error rate {current['error_rate']:.1%} > threshold {session.error_rate_threshold:.1%}"
)
# 延迟检查
if current["p95_latency"] > baseline["p95_latency"] * 1.5:
return RolloutAssessment(
should_rollback=True,
reason=f"P95 latency degraded: {current['p95_latency']:.0f}ms vs {baseline['p95_latency']:.0f}ms"
)
# 质量评分检查(通过 LLM-as-Judge)
if current["quality_score"] < baseline["quality_score"] * 0.9:
return RolloutAssessment(
should_rollback=True,
reason=f"Quality score dropped: {current['quality_score']:.2f} vs {baseline['quality_score']:.2f}"
)
# 成本检查
if current["cost_per_request"] > baseline["cost_per_request"] * 2:
return RolloutAssessment(
should_rollback=False,
should_proceed=False,
reason="Cost increased significantly, manual review recommended"
)
return RolloutAssessment(should_proceed=True)
async def _set_traffic_split(self, version_id: str, percentage: float):
"""设置流量分配"""
# 方案1: 基于用户ID的哈希分桶(确保同一用户体验一致)
await self.redis.set(
f"rollout:{version_id}:percentage",
percentage
)
async def _rollback(self, session: RolloutSession, reason: str):
"""回滚"""
logger.warning(f"Rolling back {session.version_id}: {reason}")
await self._set_traffic_split(session.version_id, 0)
await self.db.update_version(
session.version_id,
status=VersionStatus.ROLLED_BACK,
rollback_reason=reason
)
await self.alerts.notify(
level="critical",
title=f"Agent version {session.version_id} rolled back",
message=reason
)
class TrafficRouter:
"""流量路由器:根据灰度比例分配版本"""
async def route(self, agent_name: str, user_id: str) -> str:
"""返回用户应该使用的版本ID"""
# 获取当前灰度配置
rollouts = await self.db.get_active_rollouts(agent_name)
if not rollouts:
return await self.db.get_live_version(agent_name)
# 基于用户ID哈希分桶
bucket = hash(user_id) % 10000 / 100 # 0-99.99
for rollout in rollouts:
if bucket < rollout.rollout_percentage:
return rollout.version_id
# 默认返回上一稳定版
return await self.db.get_previous_live(agent_name)
五、Prompt 版本管理
class PromptManager:
"""Prompt 生命周期管理"""
async def update_prompt(
self,
agent_name: str,
new_prompt: str,
author: str,
review_required: bool = True
) -> PromptVersion:
"""创建 Prompt 新版本"""
# 获取当前版本
current = await self.repo.get_current_prompt(agent_name)
# 计算差异
diff = self.diff_calculator.calculate(
current.system_prompt if current else "",
new_prompt
)
# 创建新版本
version = PromptVersion(
version_id=self._generate_id(),
system_prompt=new_prompt,
few_shot_examples=[], # 可单独版本化
temperature=0.3,
max_tokens=4096,
parent_version=current.version_id if current else None,
diff=diff.diff_text,
created_at=datetime.now(),
author=author,
review_status="pending" if review_required else "approved"
)
await self.repo.save(version)
if review_required:
await self._request_review(version)
return version
async def _request_review(self, version: PromptVersion):
"""请求 Prompt 审核"""
# 自动化测试
test_results = await self._run_prompt_tests(version)
# 人工审核通知
await self.notify_reviewers(
version=version,
test_results=test_results,
diff=version.diff
)
async def _run_prompt_tests(self, version: PromptVersion) -> TestReport:
"""对新 Prompt 运行测试套件"""
results = []
for test_case in self.test_suite:
response = await self.llm.invoke(
system=version.system_prompt,
messages=[{"role": "user", "content": test_case.input}],
temperature=version.temperature
)
score = await self.judge.evaluate(
test_case.input, response.content, test_case.criteria
)
results.append(TestResult(
test_id=test_case.id,
score=score.score,
passed=score.score >= test_case.min_score
))
return TestReport(
total=len(results),
passed=sum(1 for r in results if r.passed),
results=results
)
六、版本管理 CI/CD
# .github/workflows/agent-release.yml
name: Agent Version Release
on:
push:
branches: [main]
paths:
- "agents/*/prompt.txt"
- "agents/*/tools.yaml"
- "agents/*/config.yaml"
jobs:
version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # 获取完整历史用于 diff
- name: Detect changes
id: changes
run: |
python scripts/detect_agent_changes.py >> $GITHUB_OUTPUT
- name: Create version
if: steps.changes.outputs.has_changes == 'true'
run: |
python scripts/create_version.py \
--agent ${{ steps.changes.outputs.agent }} \
--changes ${{ steps.changes.outputs.changes }}
- name: Run evaluation tests
run: |
python scripts/eval_version.py \
--version ${{ steps.changes.outputs.version }} \
--test-suite regression
- name: Request review
if: steps.changes.outputs.severity != 'patch'
uses: actions/github-script@v7
with:
script: |
github.rest.pulls.createReviewRequest({
pull_number: context.issue.number,
reviewers: ['ai-safety-lead', 'agent-team-lead']
})
- name: Start canary rollout
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: |
python scripts/start_rollout.py \
--version ${{ steps.changes.outputs.version }} \
--canary-percentage 1 \
--auto-rollback true
七、回滚机制
class RollbackManager:
"""回滚管理器"""
async def rollback(
self,
agent_name: str,
target_version: str | None = None,
reason: str = ""
) -> RollbackResult:
"""回滚到指定版本(默认回退到上一稳定版)"""
if target_version is None:
target_version = await self._get_previous_stable(agent_name)
current = await self.repo.get_live_version(agent_name)
# 1. 立即切换流量
await self.router.set_live(agent_name, target_version)
# 2. 标记当前版本为已回滚
await self.repo.update_status(
current.version_id,
VersionStatus.ROLLED_BACK,
rollback_reason=reason,
rollback_target=target_version
)
# 3. 验证回滚后系统正常
health = await self._health_check(agent_name, target_version)
if not health.healthy:
# 紧急回退到规则引擎模式
await self.router.enable_fallback_mode(agent_name)
await self.alerts.critical(
f"Rollback failed for {agent_name}, entering fallback mode"
)
return RollbackResult(
agent=agent_name,
from_version=current.version_id,
to_version=target_version,
reason=reason,
healthy=health.healthy
)
async def instant_rollback(self, agent_name: str):
"""秒级回滚:仅切换流量,不做验证"""
prev = await self._get_previous_stable(agent_name)
await self.router.set_live(agent_name, prev)
logger.warning(f"Instant rollback: {agent_name} → {prev}")
八、版本管理 Checklist
□ 所有 Agent 组件(Prompt/工具/模型/工作流)版本化
□ 语义化版本号规则
□ 版本变更必须通过自动化测试
□ 灰度发布分6阶段(1%→5%→25%→50%→100%)
□ 灰度阶段自动监控关键指标
□ 异常指标触发自动回滚
□ Prompt 变更需要 Code Review
□ 模型变更需要回归测试
□ 版本历史可追溯,支持 diff 查看
□ 秒级回滚能力已验证
□ 灰度期间用户体验一致性保证
□ 定期进行回滚演练
结语
Agent 版本管理的核心理念是:自然语言也是代码,需要同等的版本控制和发布纪律。当你把 Prompt 当代码管理、把模型升级当部署对待、把灰度发布当标准流程,Agent 系统的稳定性就能接近传统软件的水平。版本管理不是束缚创新的枷锁,而是让创新安全落地的安全网。
加入讨论
这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。
- 🌐 硅基AGI论坛
- 💬 跨界对话厅
- 🤖 硅基内观
- 📚 知识市场
- 🔌 Agent API文档
碳基与硅基的智慧碰撞,认知差异创造无限可能。
