“影子”里的卫兵:影子模式调试与平行演练策略
What(本文讲什么)
影子模式(Shadow Mode)是一种“把真实生产输入拿来演练,但把副作用隔离掉”的上线前验证体系。它解决的问题不是“模型准不准”,而是“一个会调用工具、会修改状态的 agent 系统,如何在不伤到生产的前提下用真实数据做验证”。
这篇文章会把影子模式拆成三段工程对象:
- 入口镜像(traffic mirroring / shadowing):复制生产请求到影子环境。
- 动作拦截(action interception):影子路径严禁真实写入。
- 对比评估(diff + eval):用规则与数据集判断新旧版本是否退化。
并且强调它必须纳入你的一等风控机制:超时、重试、幂等、隔离、权限、审计、观测、降级。
Problem(要解决的工程问题)
agent 系统一旦接入工具,失败就不再是“答错一段文字”,而是“做错一次动作”:
- 写坏数据: 写文件、改配置、发请求、删资源。
- 重试制造副作用: 超时后重试,导致重复提交(幂等缺失)。
- 事故不可复现: 生产输入是动态的,工具输出是动态的,没有 trace 就无法复盘。
- 质量暗降: 改一行 prompt、改一个工具 schema,效果可能悄悄变差,直到线上爆炸。
影子模式的目标是把这些风险变成可观测、可评估、可阻断的工程流程,而不是靠“上线勇气”。
Principle(影子模式的定义:两条路径,一条真写,一条只读)
很多系统把影子模式误解成“跑两套模型看看谁好”。对 agent 来说不是这样:
- 生产路径(prod path):允许真实副作用,但必须在治理管线下执行(超时、重试上限、幂等、审计)。
- 影子路径(shadow path):读取真实输入、执行真实推理、调用真实工具的“只读版本”,但任何写操作都必须被拦截或模拟(隔离、权限)。
如果 shadow path 能真实写入,那它不是影子模式,而是“双写事故发生器”。
在微服务世界里,“traffic mirroring/shadowing”通常由网关或服务网格提供:复制请求到影子后端,但真实用户的响应仍然来自生产后端。官方文档对 shadowing 的描述就是这个语义。
参考:
- https://www.getambassador.io/docs/edge-stack/latest/topics/using/shadowing
- https://kgateway.dev/docs/envoy/latest/resiliency/mirroring/
Usage(怎么用:从入口镜像到回放评测)
1) 入口镜像(Mirroring):把真实输入复制出来
入口镜像的目标是“真实分布”:你要看到真实用户会给 agent 什么输入,而不是测试用例臆造。
工程注意点:
- 采样比例: 不必一上来 100%,可以 1% -> 5% -> 10%。
- 脱敏: 影子环境不需要用户隐私字段,必须脱敏(审计)。
- 关联 id: 复制时要保留 trace id / request id,方便对比(观测)。
2) 影子执行环境:把副作用变成“模拟提交”
影子路径的“动作拦截”必须做得像内核一样硬,不能靠提示词:
- 文件写入:只允许写
/tmp或影子 workspace,禁止写真实仓库(隔离)。 - 网络写操作:默认拒绝,只允许 GET/只读查询(权限)。
- 外部系统变更:禁止发工单、禁止发通知、禁止部署(权限)。
- tool registry:把工具分成 read-only 与 write,需要明确声明与强制检查。
一个常见的落地模式是“dry-run 工具”:工具不做真实提交,只返回“如果提交会发生什么”的结果。
3) 对比评估:影子不是为了看日志,是为了做结论
影子模式必须输出可执行结论,而不是“跑了很多次”:
- 等效性:新版本是否至少不比旧版本差(回归门禁)。
- 退化定位:退化发生在哪个层(检索?解析?工具选择?输出格式?)。
- 风险指标:是否出现更多超时/重试/异常退出(超时、重试)。
如果你没有评估层,影子模式只会制造更多日志噪声。
4) 离线回放(Replay):把历史 traces 变成回归测试
在线 shadow 的好处是“真实流量”,缺点是“不可控”。所以你还需要离线回放:
- 黄金数据集(Golden Dataset):从生产 trace 中抽取一批代表性任务,版本化存储。
- 回放 runner:在固定快照环境里重跑(同样的输入、同样的工具返回或模拟返回)。
- 门禁阈值:成功率、关键指标、失败原因分布必须达到阈值,否则不允许发布(降级/阻断)。
这类“golden dataset + replay”的工程方法在 agent 评测实践中经常被作为核心回归门禁。
参考: https://agents.siddhantkhare.com/26-agent-evaluation/
一个最小可行的影子镜像器(伪代码)
下面的实现重点不是 async,而是三个边界:
- prod 先返回,shadow 异步跑(避免影响生产延迟)。
- shadow 强制只读工具集(隔离/权限)。
- 两条路径都产出 trace,并可对比(观测/审计)。
import asyncio
from dataclasses import dataclass
@dataclass(frozen=True)
class RunResult:
output: dict
trace_id: str
error: str | None
class ShadowMirror:
"""
影子模式镜像器:
将生产输入复制到影子 agent,影子路径必须严格隔离副作用。
"""
def __init__(self, *, prod_agent, shadow_agent_factory, evaluator, logger):
self._prod_agent = prod_agent
self._shadow_agent_factory = shadow_agent_factory
self._evaluator = evaluator
self._logger = logger
async def handle(self, user_task: dict) -> RunResult:
prod = await self._prod_agent.run(user_task)
asyncio.create_task(self._run_shadow(user_task, prod))
return prod
async def _run_shadow(self, user_task: dict, prod: RunResult) -> None:
shadow_agent = self._shadow_agent_factory(read_only_tools=True)
shadow = await shadow_agent.run(user_task)
report = self._evaluator.diff_and_score(
baseline=prod,
challenger=shadow,
)
self._logger.save_shadow_report(
trace_id=prod.trace_id,
report=report,
prod_trace=prod.trace_id,
shadow_trace=shadow.trace_id,
)
Design(设计取舍:为什么要在入口镜像,而不是在业务里复制)
把 shadow 放在入口(网关/mesh)而不是业务里,通常有两个收益:
- 不污染业务逻辑:业务代码不需要知道“有影子环境”。
- 更容易覆盖真实分布:所有请求都能被镜像,而不仅是某个接口。
缺点是你需要更严格的脱敏、隔离与资源配额,否则影子环境可能被流量打爆,引发资源释放问题。
Pitfall(常见坑与防错)
- 影子路径偷偷写入: 没有硬隔离,只靠提示词,必翻车(隔离、权限)。
- 重试导致双写: shadow 也在重试写工具,等同重复提交(幂等、重试)。
- 没有超时: shadow 卡死导致资源泄露,拖垮影子集群(超时、资源释放)。
- 没有审计字段: 无法追踪“哪些请求被镜像到哪里、产生了什么结果”(审计、观测)。
- 没有退出条件: eval 不合格仍然上线,影子形同虚设(降级/阻断)。
Debug(影子模式排障手册)
遇到“影子模式跑起来但没价值”时,按这个顺序排查:
- 镜像是否真实: shadow 的输入分布是否与生产一致?采样是否正确?
- 隔离是否生效: shadow 是否绝对不能写入真实系统?(用强制测试验证)
- trace 是否完整: 是否记录了 tool 参数、超时、重试次数、失败原因?
- eval 是否可解释: 退化时能否定位到具体层(检索/解析/工具)?
- 回放是否可复现: 能否用同一 trace 在离线环境重跑得到同类结果?
Source(资料来源)
- Traffic shadowing: https://www.getambassador.io/docs/edge-stack/latest/topics/using/shadowing
- Mirroring(Envoy): https://kgateway.dev/docs/envoy/latest/resiliency/mirroring/
- Shadow mirroring 实践: https://blog.markvincze.com/shadow-mirroring-with-envoy/
- Golden dataset / replay: https://agents.siddhantkhare.com/26-agent-evaluation/
指标体系(没有指标,影子模式只会制造噪声)
影子模式至少要输出三类指标,并能按 task/agent/step 聚合:
1) 稳定性指标(先保证不炸)
- 超时率(超时)
- 重试次数分布(重试)
- 资源释放失败次数(资源释放)
- 影子环境拒绝写操作的次数(权限、隔离)
2) 质量指标(再谈变好)
- 任务成功率(是否完成目标)
- 首次成功率(不靠重试成功)
- 关键格式/契约通过率(schema parse / tool call 解析)
3) 风险指标(专盯“会出事故”的信号)
- 幂等冲突次数(幂等)
- 尝试访问敏感路径次数(权限)
- 尝试访问内网/元数据地址次数(隔离)
- 需要人工介入的比例(审计)
影子模式的“退出条件”(必须写死)
影子模式不是永远跑着。你必须定义退出条件,否则评估永远停留在“感觉还行”:
- 达到门禁阈值(连续 N 天不退化)
- 回放集覆盖达到阈值(黄金数据集覆盖主要任务类型)
- 失败原因分布稳定(没有新的高风险失败类型)
- 发布策略明确(灰度比例、回滚策略、报警阈值)
只有当退出条件满足,你才有资格把影子结果当作发布依据。