Agent 安全 #06:Agent 灰度发布与回滚 —— 上线新 Prompt 炸了生产,你敢回滚吗?

系列:Agent 安全专题 | 编号:06 | 专栏:人工智能 Agent 从部署到生产

TL;DR

微服务的灰度发布你已经很熟了------切 5% 流量到新版本,看错误率,不对就回滚。Agent 的灰度发布不一样:你要切的不是容器镜像,是一个组合体------Prompt 版本、模型权重、工具配置、系统指令、安全策略------每一项都可能独立变更,每一项变更都可能让 Agent 的行为面目全非。

本文给出一个生产验证的灰度发布框架:配置版本化 → 分流策略 → 回滚不丢状态 → 监控对比。不是概念,是实际跑在生产环境里、每晚 Cron 切模型的那套方案。


1. Agent 的"发布"到底发布的是什么?

传统微服务:

复制代码
发布 = 新 Docker 镜像 → 健康检查 → 切流量

Agent 的发布:

复制代码
发布 = { prompt_v3, model_deepseek_v4, tool_allowlist_v2, system_instruction_r1c }

一组 Agent 配置包含至少 4 个维度:

维度 变更频率 破坏性 回滚难度
System Prompt / System Instruction 高频(每周 2-3 次) 高(行为彻底改变) 低(纯文本替换)
模型(provider + model name) 中频(每月 1-2 次) 极高(能力/速度/成本都有变化) 中(需 API key 配对)
Tool 配置(允许/禁止的工具列表) 低频 极高(删一个工具可能导致任务卡死)
安全策略(guardrails / safety checks) 低频 极高(放宽策略可能造成安全事件) 低(策略文件替换)

每一个维度的变更都是独立的风险载体。 如果你只是 vim system_prompt.md 然后 kill -HUP 重启------你就是在生产环境搞俄罗斯轮盘赌。


2. 配置版本化:你的 Agent 没有"上次跑了什么配置"的记忆,就是盲的

2.1 最小版本化方案

一个 Agent 的所有配置打包成一个版本快照。版本号格式:v{major}.{minor}.{patch}-{profile}

csharp 复制代码
~/.agent/profiles/production/configs/
├── v2.1.0-default/
│   ├── system.md          # System Prompt 全文
│   ├── model.yaml         # provider + model + params
│   ├── tools.yaml         # 启用工具列表 + 参数
│   └── safety.yaml        # guardrails 规则
├── v2.1.1-default/        # 加了一条 safety rule
├── v2.2.0-default/        # 换了模型,从 deepseek-v3 切 v4
└── v2.2.0-canary/         # 金丝雀版本,先切 5% 流量试跑

2.2 Prompt 版本管理是最大痛点

System Prompt 是最容易改、最经常改、最容易被轻视的维度。一个"小改动"------「帮用户更主动地给出建议」------可能让 Agent 从「只做被要求的事」变成「自己决定发邮件」。

生产经验:我们为 Prompt 单独做了三层版本控制:

python 复制代码
# prompt_version.py --- 每次改 Prompt 必须走这个

from dataclasses import dataclass
from datetime import datetime, timezone
from hashlib import sha256

@dataclass
class PromptVersion:
    version: str           # "v2.1.0"
    content: str           # 完整 Prompt 文本
    author: str            # 修改人
    reason: str            # 变更原因
    diff_from_previous: str  # git diff 摘要
    timestamp: str
    content_hash: str      # sha256(content)

    @classmethod
    def create(cls, version, content, author, reason, diff):
        return cls(
            version=version, content=content, author=author,
            reason=reason, diff_from_previous=diff,
            timestamp=datetime.now(timezone.utc).isoformat(),
            content_hash=sha256(content.encode()).hexdigest()[:16]
        )

# 每次 Agent Run 时,把 prompt_version 写入审计日志的 session_start 事件:
# {"event_type":"session_start", "prompt_version":"v2.1.0", "prompt_hash":"a3f2c8e1..."}

为什么 Prompt 的 sha256 要进审计日志? 因为出事后,你需要精确知道「当时 Agent 用的是什么 Prompt」。如果只记一个版本号 v2.1.0,而有人在 v2.1.0 发布后又偷偷改了文件------你就回不了根因。

2.3 模型配置版本化

模型切换不只是改一个 model 字段。不同模型对 prompt 的理解有微妙差异,temperature 的最佳值不同,max_tokens 的有效范围也不同:

yaml 复制代码
# model.yaml
model:
  provider: deepseek
  model_name: deepseek-v4-pro
  base_url: https://api.deepseek.com/v1
  parameters:
    temperature: 0.0
    max_tokens: 8192
    top_p: 1.0
  fallback:
    provider: openai
    model_name: gpt-4o
    base_url: https://api.openai.com/v1
    parameters:
      temperature: 0.0
      max_tokens: 4096
  cost:
    input_per_1k: 0.00014   # USD
    output_per_1k: 0.00028
    cache_hit_per_1k: 0.000014

关键细节cost 字段不是装饰------它让你的监控系统能精确算出每次 Agent Run 的成本。切模型时不只有能力变化,还有成本变化。从 deepseek-v3 切到 deepseek-v4-pro,单次 Run 的成本可能从 0.003涨到0.003 涨到 0.003涨到0.012。灰度期间必须对比。


3. 灰度流量控制:不是「5% 到 100%」,是多维分流策略

3.0 为什么随机分流对 Agent 不够?

微服务的灰度分流很简单------随机 5% 的请求打到新版本 Pod。但 Agent 的流量不是独立的------同一个用户的连续对话是一个 session,而 session 内的每一次 LLM 调用都依赖于前一轮的上下文。

如果你在 session 中途切换配置版本:第一轮 Agent 用 v2.2.0 的 prompt 思考、回复,第二轮你却用 v2.1.0 的 prompt------Agent 的行为会出现逻辑断层。用户说「继续刚才的工作」,但 Agent 已经不知道自己之前做了什么决策。

所以 Agent 的灰度分流不是请求级,而是 session 级。 一个 session 从创建到结束,始终使用同一个配置版本。

3.1 分流维度

传统的「切 5% 流量」对 Agent 来说维度太粗。Agent 的流量控制需要多维度分流:

python 复制代码
# router.py --- 灰度分流器

import hashlib

def route_config(session_id: str, user_id: str, task_type: str) -> str:
    """
    返回配置版本名:'v2.1.0-default' 或 'v2.2.0-canary'
    """
    # 维度 1:用户白名单 --- 内部测试用户全量走金丝雀
    if user_id in CANARY_USERS:
        return "v2.2.0-canary"

    # 维度 2:任务类型 --- 高风险任务只走稳定版
    if task_type in HIGH_RISK_TASKS:  # email/shell/delete
        return "v2.1.0-default"

    # 维度 3:session_id 哈希分流 --- 同 session 始终走同一版本
    # 哈希取模,确保 5% 流量
    hash_val = int(hashlib.md5(session_id.encode()).hexdigest()[:8], 16)
    if hash_val % 100 < CANARY_PERCENT:
        return "v2.2.0-canary"

    return "v2.1.0-default"

为什么用 session_id 哈希而不是随机数? 因为 Agent 的会话是有状态的。同一个 session 里的第 1 次和第 3 次 LLM 调用如果走到不同版本的 prompt,行为会割裂------上一轮 Agent 用 v2.2.0 给的推理,下一轮用 v2.1.0 来处理,就像两个人在接力讲话。

3.2 渐进式放量节奏

生产验证过的安全节奏:

sql 复制代码
Day 0:  0%  --- 发布金丝雀版本到配置存储,不回滚
Day 1:  5%  --- 内部测试用户 + session 哈希分流
Day 2:  20% --- 监控无异常后扩大
Day 3:  50% --- 成本对比通过后继续
Day 5:  100%--- 全量切换,金丝雀版本成为新的 default

每个阶段都有退出条件,不是时间到了就自动放量。 阶段门槛:

放量条件 检测方法 阈值
任务成功率无下降 tool_calls_total{status="success"}/total ≥ 基线 95%
错误率无上升 llm_calls_total{status="error"}/total ≤ 基线 × 1.5
平均延迟无显著恶化 agent_session_duration_seconds P95 ≤ 基线 × 1.3
成本可接受 agent_cost_dollars per run ≤ 基线 × 2.0
安全决策无异常 decision{type="safety_denied"} rate ≤ 基线 × 1.2

3.3 配置分发 ------ 不是代码,是配置文件

在 Agent 的生产架构中,配置分发不需要 K8s ConfigMap 或者 etcd。一种更简单的方案:配置文件 + Git + 符号链接。

bash 复制代码
# 金丝雀 Agent 实例启动时指定配置版本
hermes run \
  --profile production \
  --config-version v2.2.0-canary \
  --task "process daily reports"

# 或者通过环境变量控制
export AGENT_CONFIG_VERSION=v2.2.0-canary
hermes run --profile production --task "..."

关键原则:配置分发和 Agent 进程解耦。不要为了切 Prompt 就重启 Agent 进程------Agent 进程里可能正跑着一个多步任务,重启就丢了中间状态。


4. 回滚不丢状态:这是 Agent 灰度最难的部分

4.1 问题:Agent 的状态不在数据库里

微服务回滚:切流量到旧版本 Pod → 数据库里的数据还在 → 结束。

Agent 回滚:切配置到旧版本 → 但是当前会话里 Agent 已经按新 Prompt 思考了三轮、调了两个工具、写了三条记忆 → 这些中间产物怎么处理?

php 复制代码
Session ABC 的 timeline:
10:00  session_start  (config: v2.2.0-canary, prompt_hash: a3f2...)
10:01  llm_call       (model: deepseek-v4-pro, tokens_in: 420)
10:01  tool_call      (tool: web_search, status: success)
10:02  llm_call       (model: deepseek-v4-pro, tokens_in: 580)
10:02  tool_call      (tool: terminal, status: error, 权限不足)
10:03  llm_call       (model: deepseek-v4-pro, tokens_in: 620)
        ↑ 此时灰度告警触发:v2.2.0-canary 的 terminal 权限配置过紧
        ↑ 决定回滚到 v2.1.0-default
10:03  回滚动作:当前 session 标记为 abandoned,新配置从下次 session_start 生效

4.2 回滚策略矩阵

场景 回滚动作 状态处理
Session 中途回滚 当前 session 完成当前 tool_call 后优雅终止,给出 fallback 回复 保留已有审计日志和记忆写入;不丢历史
跨 session 回滚 下次 session_start 自动用旧配置 所有历史 session 保持不可变(只读)
紧急回滚(全量) 立即切换到旧的 default 版本,所有新 session 生效 已运行的 session 不中断(等自然结束)

4.3 回滚执行脚本

python 复制代码
# rollback.py --- 一键回滚,包含状态保护

import os
import json
from datetime import datetime, timezone
from pathlib import Path

def emergency_rollback(target_version: str, reason: str):
    """
    紧急回滚:将 default 符号链接指向指定版本。
    不销毁当前运行中的 session,只影响新 session。
    """
    config_dir = Path(os.path.expanduser("~/.agent/profiles/production/configs"))
    default_link = config_dir / "v2.2.0-default"
    target_path = config_dir / target_version

    if not target_path.exists():
        raise FileNotFoundError(f"回滚目标版本 {target_version} 不存在")

    # 步骤 1:记录回滚事件到审计日志
    audit_log = Path(os.path.expanduser("~/.agent/logs/rollback.log"))
    with open(audit_log, "a") as f:
        f.write(json.dumps({
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "event": "rollback",
            "from_version": os.readlink(default_link) if default_link.is_symlink() else "unknown",
            "to_version": target_version,
            "reason": reason,
            "active_sessions_count": count_active_sessions()
        }) + "\n")

    # 步骤 2:切换符号链接(原子操作)
    tmp_link = config_dir / ".default_link_tmp"
    tmp_link.symlink_to(target_path)
    tmp_link.rename(default_link)   # mv 是原子的

    # 步骤 3:记录到 Hindsight(记忆系统不丢回滚信息)
    print(f"✅ 已回滚到 {target_version}")
    print(f"   原因:{reason}")
    print(f"   当前活跃 session 数:{count_active_sessions()}(不受影响,自然结束)")

def count_active_sessions() -> int:
    """统计仍在运行中的 Agent 会话数"""
    pid_dir = Path(os.path.expanduser("~/.agent/run/"))
    return len(list(pid_dir.glob("*.pid"))) if pid_dir.exists() else 0

4.4 回滚不丢状态的关键设计

第一条:历史 session 不可变。 已经完成的 session 的审计日志、记忆、用户回复------全部只读。回滚只影响新的 session。

第二条:活跃 session 优雅降级。 如果当前 session 正在运行中,不要强制中断。给它一次机会------当前 LLM 调用结束后,用一个简洁的 fallback 回复收尾,然后标记 session 结束。

python 复制代码
# 优雅降级的 fallback prompt
GRACEFUL_SHUTDOWN_PROMPT = """
You are being rolled back to an earlier configuration version.
Do NOT start new tool calls or complex reasoning.
Acknowledge the current task status briefly, inform the user 
that the system is undergoing maintenance, and end your response.
Keep it under 100 words.
"""

第三条:配置文件本身纳入版本仓库。 用 Git 管理所有配置版本。回滚不是「删掉新文件」------是「切回旧版本符号链接」。新版本的文件保留在仓库里供事后分析。


5. 灰度监控:对比两组 Agent 的行为差异

5.1 灰度对照组设计

灰度期间,稳定版和金丝雀版产生的监控指标要能直接对比。核心是给每个指标打上 config_version 标签:

python 复制代码
from prometheus_client import Counter, Histogram

# 关键:所有指标按 config_version 分 label
tool_success = Counter(
    'agent_tool_calls_total',
    'Tool call results',
    ['tool', 'config_version', 'status']
)

llm_duration = Histogram(
    'agent_llm_duration_seconds',
    'LLM call duration',
    ['model', 'config_version']
)

# 用的时候
tool_success.labels(
    tool='terminal',
    config_version='v2.2.0-canary',  # ← 灰度对照组
    status='success'
).inc()

5.2 灰度监控面板的 5 个必看指标

ini 复制代码
Grafana Dashboard: Agent Canary Analysis

┌───────────────────────────────────────────────────────────┐
│  1. 任务成功率对比(折线图)                                │
│  rate(tool_calls_total{config_version="*-canary",          │
│       status="success"}[5m])                              │
│  / rate(tool_calls_total{config_version="*-canary"}[5m])  │
│  vs 稳定版同指标                                           │
├───────────────────────────────────────────────────────────┤
│  2. LLM 调用延迟 P95(柱状图)                              │
│  histogram_quantile(0.95,                                 │
│    rate(agent_llm_duration_seconds_bucket                 │
│         {config_version=~".*-canary"}[5m]))               │
│  vs 稳定版                                                │
├───────────────────────────────────────────────────────────┤
│  3. 每次 Run 的成本(时序图)                               │
│  rate(agent_cost_dollars_total                            │
│       {config_version=~".*-canary"}[1h])                  │
│  / rate(agent_session_count_total                         │
│         {config_version=~".*-canary"}[1h])                │
├───────────────────────────────────────────────────────────┤
│  4. 安全拒绝次数(计数器)                                  │
│  rate(decision_total{type="safety_denied",                 │
│        config_version=~".*-canary"}[5m])                  │
│  如果金丝雀的 safety_denied 显著高于稳定版                   │
│  → 新配置的行为更激进 → 回滚                                │
├───────────────────────────────────────────────────────────┤
│  5. 灰度流量比例(仪表盘)                                  │
│  agent_session_count_total{config_version=~".*-canary"}   │
│  / agent_session_count_total                              │
│  → 确认灰度比例是否符合预期                                │
└───────────────────────────────────────────────────────────┘

5.3 告警规则

yaml 复制代码
# prometheus_rules.yml --- 灰度期间的专项告警

groups:
  - name: agent_canary_alerts
    rules:
      # 规则 1:金丝雀版本任务成功率下降 > 10%
      - alert: CanarySuccessRateDrop
        expr: |
          (
            rate(agent_tool_calls_total{config_version=~".*-canary",status="success"}[5m])
            / rate(agent_tool_calls_total{config_version=~".*-canary"}[5m])
          )
          /
          (
            rate(agent_tool_calls_total{config_version!~".*-canary",status="success"}[5m])
            / rate(agent_tool_calls_total{config_version!~".*-canary"}[5m])
          ) < 0.9
        for: 15m
        labels:
          severity: critical
        annotations:
          summary: "金丝雀版本成功率显著低于稳定版"
          description: "当前比率 {{ $value }},低于阈值 0.9。建议立即回滚。"

      # 规则 2:金丝雀版本安全拒绝激增
      - alert: CanarySafetyDenySpike
        expr: |
          rate(decision_total{config_version=~".*-canary",type="safety_denied"}[5m])
          /
          rate(decision_total{config_version!~".*-canary",type="safety_denied"}[5m]) > 3.0
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "金丝雀版本安全拒绝次数是稳定版的 3 倍以上"
          description: "新配置可能过度放宽了安全策略。"

      # 规则 3:灰度流量比例漂移
      - alert: CanaryTrafficDrift
        expr: |
          abs(
            rate(agent_session_count_total{config_version=~".*-canary"}[15m])
            / rate(agent_session_count_total[15m])
            - 0.05
          ) > 0.02
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "灰度流量比例偏离了 5% 的目标"

6. 完整的灰度发布流水线

把上面的组件串起来,就是一套 Agent 灰度发布的完整流程:

python 复制代码
# deploy_canary.py --- Agent 灰度发布编排器

import subprocess
import time
import json
from pathlib import Path

class AgentCanaryDeploy:
    def __init__(self, config_version, canary_percent=5):
        self.config_version = config_version    # "v2.3.0-canary"
        self.canary_percent = canary_percent
        self.rollback_version = "v2.2.0-default"
        self.config_dir = Path("~/.agent/profiles/production/configs").expanduser()

    def deploy(self):
        print(f"📦 部署金丝雀版本: {self.config_version}")
        print(f"   灰度比例: {self.canary_percent}%")

        # 步骤 1:验证配置完整性
        self._validate_config()

        # 步骤 2:激活灰度分流
        self._activate_route()

        # 步骤 3:等待监控数据积累
        print(f"⏳ 等待 30 分钟积累监控数据...")
        time.sleep(1800)

        # 步骤 4:检查灰度健康
        if not self._check_health():
            print(f"❌ 健康检查失败,触发自动回滚")
            self.rollback()
            return False

        # 步骤 5:继续放量
        for pct in [20, 50, 100]:
            if not self._can_proceed(pct):
                print(f"⚠️  停止放量。当前: {pct}%,发现异常")
                return False
            self._scale_to(pct)
            print(f"✅ 已放量到 {pct}%")
            time.sleep(14400)  # 每档最少观察 4 小时

        print(f"✅ {self.config_version} 已全量上线")
        return True

    def _validate_config(self):
        """验证所有必需配置文件存在且格式正确"""
        required = ["system.md", "model.yaml", "tools.yaml", "safety.yaml"]
        version_dir = self.config_dir / self.config_version
        for f in required:
            if not (version_dir / f).exists():
                raise FileNotFoundError(f"缺少配置文件: {f}")
        # 验证 model.yaml 中的 fallback 可用
        import yaml
        with open(version_dir / "model.yaml") as fh:
            model_cfg = yaml.safe_load(fh)
        assert "fallback" in model_cfg["model"], "必须配置 fallback 模型"

    def _activate_route(self):
        """激活灰度路由------通过 Redis/环境变量"""
        import redis
        r = redis.Redis()
        r.set("agent:canary:version", self.config_version)
        r.set("agent:canary:percent", self.canary_percent)
        print(f"   路由已激活: {self.canary_percent}% → {self.config_version}")

    def _check_health(self):
        """查询 Prometheus,对比金丝雀 vs 稳定版指标"""
        # 伪代码------实际用 prometheus_client 查询
        metrics = self._query_prometheus(self.config_version)
        return all([
            metrics["success_rate"] >= 0.90,
            metrics["error_spike"] <= 1.5,
            metrics["safety_deny_ratio"] <= 3.0,
        ])

    def _can_proceed(self, target_pct):
        """进阶放量前的安全检查"""
        return self._check_health()

    def _scale_to(self, pct):
        import redis
        r = redis.Redis()
        r.set("agent:canary:percent", pct)

    def rollback(self):
        """一键回滚到稳定版本"""
        import redis
        r = redis.Redis()
        r.set("agent:canary:version", self.rollback_version)
        r.set("agent:canary:percent", 0)
        print(f"🔙 已回滚到 {self.rollback_version}(灰度比例: 0%)")

7. 真实踩坑记录

坑 1:切模型时忘了改 fallback

场景 :从 deepseek-v3 切换到 deepseek-v4-pro,配置了主模型但没更新 fallback。某天 deepseek API 短暂不可用,Agent 自动 fallback 到 deepseek-v3------但 v3 的 max_tokens 只有 4096,而新 prompt 产出的 reasoning 经常超 6000 token。

现象 :fallback 期间的 session 大量出现 context_length_exceeded 错误,但主模型正常。

教训 :切换主模型时 必须同步切换 fallback 模型。灰度监控要同时检查 fallback 的指标。

坑 2:Prompt 改动导致 tool_call 格式变化

场景:在 system prompt 里加了一句「调用工具前先简短解释你的意图」。新 prompt 下 Agent 的输出从:

json 复制代码
{"tool": "terminal", "command": "ls /tmp"}

变成了:

bash 复制代码
我理解你需要查看 /tmp 目录的内容。
{"tool": "terminal", "command": "ls /tmp"}

现象:tool_call JSON 解析失败率从 0% 飙升到 15%。

根因分析 :LLM 对 prompt 指令的理解有「过度执行」倾向------你让它「简短解释」,它不会只加一行,而是会插入整段自然语言。而大多数 Agent 框架的 tool parser 是按 { 出现的第一个位置来截取的------前面多了文本,整个解析逻辑就偏移了。

教训 :灰度期间必须按 tool_call 维度对比错误率------光看 LLM 调用的成功率不够,因为 LLM 可能返回了文本但 tool parser 解析不了。更关键的是:任何影响 LLM 输出格式的 prompt 改动必须先跑自动化格式测试------用 50 个典型用户输入 + 金丝雀配置,验证 tool_call 可解析率不低于稳定版的 95%。

坑 3:切模型后 prompt caching 行为变了

场景 :从 deepseek-v3 切到 deepseek-v4-pro。v3 的 prompt caching 命中率稳定在 60%,但 v4-pro 的缓存策略不同------v4-pro 要求 system prompt 放在 messages 数组的最后一个 user message 之后才能命中缓存,而 v3 的 system prompt 放在 messages0 就能命中。

现象:切完后缓存命中率从 60% 跌到 8%,单次 LLM 调用的输入 token 成本翻了 3 倍。

教训 :切换模型时务必验证 prompt caching 行为。缓存命中率的下降可能让月成本从 30涨到30 涨到 30涨到100------这比模型能力差异更直接影响预算。灰度监控中必须有一个 cache_hit_rate 指标专门追踪。

坑 4:回滚时忘恢复环境变量

场景 :金丝雀版本需要新的环境变量 NEW_MODEL_API_KEY。切回旧版本时,忘记清理这个变量。旧版本读到 NEW_MODEL_API_KEY 的存在,行为异常。

教训:配置版本化要包括环境变量快照。回滚时不仅要切配置文件,还要重置环境变量。


8. 与前五篇的衔接

安全专题 本文关联
#01 Prompt Injection 防御 灰度上线新的 prompt 防护规则,监控安全拒绝次数。如果新规则导致安全拒绝率上升 3 倍以上,自动触发回滚
#02 Tool Sandbox 安全边界 回滚时 sandbox 中的临时文件不能丢------灰度期间新旧版本 Agent 可能同时写 sandbox,文件命名必须带 session 前缀防止冲突
#03 多 Agent 协作 Kanban 架构 灰度时各 Worker 的配置版本可能不同步------Kanban 主控节点需要知道每个 Worker 当前运行的配置版本,任务分发时做版本匹配
#04 Credential Vault 凭据设计 切模型时 API key 的变更要以 vault 操作为单位------先创建新 key 条目 → 切配置引用 → 验证新 key 可调用 → 再归档旧 key
#05 审计与可观测性 灰度对比依赖 config_version 标签的指标。没有 #05 打下的监控基础,这整篇文章的灰度监控方案就无从落地

9. 落地清单

第一周能做的最小可灰度方案:

  1. 把所有配置打包到一个目录,用 Git 管理
  2. 每次改配置记一个版本文档(Prompt 的 sha256 + 变更原因)
  3. 跑一个新 Agent 实例用新配置,人工观察 10 个 session
  4. 没问题再全量切

第二周升级:

  1. 实现 session_id 哈希分流(prompt_version 标签打入审计日志)
  2. Prometheus 按 config_version 分 label
  3. Grafana 对比面板

第三周自动化:

  1. 告警规则(金丝雀 vs 稳定版对比)
  2. 一键回滚脚本

最重要的原则:不要从 Week 3 的方案开始。灰度发布的核心不是「自动化的程度」,而是「你对自己 Agent 基线行为的理解深度」------你不知道什么是正常波动、什么是真正的异常,任何自动化都只会放大错误。从简单的人工验证开始,每天跑 10 个 session 对比两组配置的输出差异,积累两周经验后再引入自动放量和告警。


下一篇预告:#07 Agent 安全测试 ------ 如何对你的 Agent 做渗透测试,在你上线之前自己先把漏洞找出来


本系列属于专栏《人工智能 Agent 从部署到生产》。专栏聚焦 Agent 工程化落地:安全防护、灰度发布、多 Agent 通信、成本优化。不是概念科普------每篇都能复现,每步都有代码。

相关推荐
starrysky8103 小时前
CPU 70% idle 但 load average 84?D 状态进程不可中断睡眠的深度排查
angular.js
界面开发小八哥3 天前
界面控件DevExtreme v26.1新版亮点——支持Angular 22
前端·javascript·angular.js·devexpress·ui开发·devextreme
巴勒个啦7 天前
Vue 3.6 Vapor Mode 实战:我把一个 Vue3 项目的渲染性能提升了 4 倍
前端·angular.js
shmily麻瓜小菜鸡10 天前
在 Angular 项目中实现国际化(i18n)和翻译 使用ngx-translate
前端·javascript·angular.js
starrysky81015 天前
一个 JSON 解析错误,搞垮了整个进程池?——requests JSONDecodeError + BrokenProcessPool 排障实录
angular.js
starrysky81015 天前
小模型反而爆显存?Qwen2.5-VL 3B vs 7B 的 vLLM 显存真相——多模态 Profiling 机制深度拆解
angular.js
starrysky81019 天前
THP 透明大页导致数据库延迟从 2ms 飙升到 2000ms——khugepaged 内存压缩风暴排查实录
angular.js
starrysky81022 天前
多Agent通信架构实战:从NATS消息总线到五大编排模式的生产落地——Agent通信协议篇
angular.js
starrysky81022 天前
RecursionError: maximum recursion depth exceeded —— 你的函数调用链,踩穿了 CPython 的安全气囊
angular.js