前面几个版本里,AI FaultLab 已经把故障模拟、Metrics、Trace、规则诊断、Runbook RAG 和 Diagnosis Agent 串了起来。
但做到这里之后,我越来越觉得,仅仅输出一份"看起来合理"的诊断报告还不够。
比如 Agent 告诉我:
Retry Storm 的问题来自重试放大,建议限制重试次数并增加 jitter。
这句话本身并不难生成。
真正难回答的是:
这个建议真的有效吗?
很多 AI 运维、故障诊断类项目最后都会停在这里:
Fault
↓
Metrics / Trace
↓
RAG
↓
LLM
↓
Diagnosis Report
↓
"建议你增加消费者"
"建议你增加互斥锁"
"建议你限制重试"
至于建议执行之后系统会发生什么,没有后续验证。
所以在 AI FaultLab v0.15.0 里,我没有继续加新的故障场景,也没有急着上 ReAct、Multi-Agent,而是沿着另外一条线继续往下做:
Diagnosis
↓
Structured Remediation Plan
↓
Counterfactual Replay
↓
Before / After Metrics
↓
Remediation Validation
目标很明确:
不只让 Agent 给出修复建议,还要让系统自己验证这条建议在受控故障环境里到底有没有效果。
一、先解决一个问题:自然语言建议没法执行
最初 Diagnosis Agent 输出的建议本质上还是自然语言:
Enable retry limit.
Add retry jitter.
Enable fallback.
Use mutex protection for hot-key rebuild.
这种结果适合人看,但机器很难继续处理。
所以 v0.15.0 第一件事,就是把 suggestion 变成一个稳定的结构化协议。
例如 RETRY_STORM:
{
"scenarioCode": "RETRY_STORM",
"status": "PROPOSED",
"actions": [
{
"actionType": "ENABLE_RETRY_LIMIT",
"parameterPatch": {
"enableRetryLimit": true
}
},
{
"actionType": "ENABLE_RETRY_JITTER",
"parameterPatch": {
"enableJitter": true
}
}
],
"expectedEffects": [
{
"metricName": "downstream.retry.amplification.factor",
"direction": "DECREASE"
}
]
}
这里最核心的字段不是 description,而是:
parameterPatch
expectedEffects
一个描述:
准备修改什么。
另一个描述:
修改之后,预期哪些指标应该发生什么变化。
这样 RemediationPlan 才真正具备后续执行和验证的可能。
二、为什么 Remediation Plan 第一版没有交给 LLM 自由生成
这里我刻意没有增加新的 LLM 调用。
目前的规划规则是 deterministic mapping。
例如:
CACHE_BREAKDOWN
→ enableMutex = true
DB_CONNECTION_POOL_EXHAUSTION
→ enableFastRelease = true
DOWNSTREAM_TIMEOUT
→ enableFallback = true
RETRY_STORM
→ enableRetryLimit = true
→ enableJitter = true
原因很简单。
如果这一阶段直接让模型输出任意:
{
"parameterPatch": {
"xxx": "xxx"
}
}
后面会立即遇到两个问题。
第一,模型可能生成根本不存在的参数。
第二,更危险的是它可能直接修改故障条件。
比如一个 RETRY_STORM:
{
"failureRatio": 0.7,
"enableRetryLimit": false
}
如果所谓的"修复方案"是:
{
"failureRatio": 0.1
}
指标当然会变好。
但这不是修复。
这是把实验难度调低了。
因此这里做了一层明确的 Remediation Policy。
以 RETRY_STORM 为例,目前只允许:
enableRetryLimit
enableJitter
retryBackoffMs
而:
requestCount
concurrency
failureRatio
scenarioCode
experimentId
都不能被 RemediationPlan 修改。
三、Counterfactual Replay:真正有意思的部分开始了
有了结构化 RemediationPlan 以后,下一步就是重新执行实验。
我给这一阶段的定义是:
Counterfactual Remediation Replay。
它不是 Production Auto Fix,也不是 Self-Healing。
它做的事情更克制:
在同一个故障模型、同一组实验条件下,只修改修复参数,再执行一次实验。
例如原始 RETRY_STORM:
{
"requestCount": 100,
"concurrency": 20,
"failureRatio": 0.7,
"maxRetries": 3,
"retryBackoffMs": 20,
"enableRetryLimit": false,
"enableJitter": false
}
Agent 给出的 patch:
{
"enableRetryLimit": true,
"enableJitter": true
}
Replay 以后必须是:
{
"requestCount": 100,
"concurrency": 20,
"failureRatio": 0.7,
"maxRetries": 3,
"retryBackoffMs": 20,
"enableRetryLimit": true,
"enableJitter": true
}
这里最重要的不是"重新跑了一遍"。
而是:
Original Conditions
=
Replay Conditions
-
Remediation Variables
我把这个约束叫做:
Counterfactual Invariant。
四、为了支持 Replay,实验参数必须真正持久化
做到这里时暴露了一个之前并不明显的问题。
原来的 fault_experiment 只需要记录实验本身,所以并没有完整持久化启动参数。
但 Replay 要回答:
这个实验当时到底是怎么跑的?
如果原始参数没存下来,就没办法构造可信的对照实验。
所以 fault_experiment 增加了两个字段:
params_json
source_experiment_id
params_json 保存启动实验时的原始参数。
普通实验:
source_experiment_id = null
Replay 实验:
source_experiment_id = originalExperimentId
这样 Original 和 Replay 之间就形成了明确关系:
exp_original
↑
│ source_experiment_id
│
exp_replay
旧数据如果没有 params_json,系统不会尝试猜测原始参数,而是直接拒绝:
ORIGINAL_EXPERIMENT_PARAMS_UNAVAILABLE
我比较认可这种处理。
无法重现,就不要假装能够重现。
五、Replay 不能维护两套实验执行逻辑
还有一个实现上的问题。
最简单的写法当然是:
ExperimentService
RemediationReplayService
然后在 RemediationReplayService 里面重新复制一份:
创建 experiment
初始化 Trace
找到 FaultScenario
执行 Scenario
写 Metrics
更新状态
但这样很快就会出现两套逻辑漂移。
所以最后把普通启动和 Replay 都收敛到同一个内部执行路径:
Normal Start ─────┐
├── executeExperiment(...)
Remediation Replay┘
ReplayService 只负责:
加载 Original
↓
恢复 Params
↓
校验 Patch
↓
Clone Params
↓
Apply Patch
↓
创建 Replay Experiment
↓
交给统一执行流程
至于具体 FaultScenario 怎么执行、Metrics 怎么记录、Trace 怎么建立,继续走原有链路。
六、为什么 Python 校验过了,Java 还要再校验一次
RemediationPlan 在 Python Agent 侧已经有 Policy 了。
理论上参数已经经过校验。
但真正执行 Replay 的是 Java Backend。
所以 Backend 不能直接认为:
Python 发过来的肯定合法。
最终做成了两层:
Diagnosis Agent
↓
Python Remediation Policy
↓
Structured RemediationPlan
↓
Java RemediationReplayPolicy
↓
Controlled Replay
Java 侧目前允许的参数包括:
CACHE_BREAKDOWN
- enableMutex
- enableLogicalExpire
DB_CONNECTION_POOL_EXHAUSTION
- enableFastRelease
DOWNSTREAM_TIMEOUT
- enableFallback
RETRY_STORM
- enableRetryLimit
- enableJitter
- retryBackoffMs
像 failureRatio 这种故障条件,即使 Agent 真传过来了,Backend 仍然会拒绝。
这个设计本质上和常规后端开发是一样的:
不要把 AI 当成可信调用方。
七、真正的闭环:Before / After Validation
Replay 做完以后,系统现在已经有两个 Experiment:
Original Experiment
Replay Experiment
而两个实验都有独立:
Metrics
Trace
experimentId
于是第三阶段开始真正比较结果。
接口设计成:
GET /api/experiments/{replayExperimentId}/remediation-validation
不需要再传 originalExperimentId。
因为 Replay 本身已经通过:
source_experiment_id
知道自己来自哪个 Original。
完整流程大概是:
replayExperimentId
↓
load replay experiment
↓
source_experiment_id
↓
load original experiment
↓
restore both params
↓
derive actual patch
↓
verify invariant
↓
load before / after metrics
↓
compare
↓
generate validation report
八、Validation 不接受 Agent 提交"评分标准"
这里还有一个很容易踩的坑。
RemediationPlan 里已经有:
expectedEffects
例如:
downstream.retry.amplification.factor
DECREASE
那 Validation 能不能直接使用 Agent 提交的 expectedEffects?
我最后选择了:
不能。
因为这样 Agent 相当于既是考生,又负责出题。
比如真实修复应该让:
retry count ↓
如果 Replay 以后 retry count 反而上升,客户端完全可以重新提交:
retry count INCREASE
然后告诉系统:
看,验证成功了。
所以 Java Backend 又维护了一层:
RemediationValidationPolicy
它根据:
scenarioCode
+
original params
+
replay params
+
实际发生的 parameter diff
自行确定需要检查什么指标。
客户端不能指定 Validation 规则。
九、Applied Patch 也是服务器自己算出来的
类似地,Validation 也不信任调用方重新提交:
{
"parameterPatch": {
"enableRetryLimit": true
}
}
因为数据库里真正跑的东西才是事实。
所以 Applied Patch 直接来自:
diff(originalParams, replayParams)
例如:
original.enableRetryLimit = false
replay.enableRetryLimit = true
最终推导:
{
"enableRetryLimit": true
}
如果此时发现:
failureRatio:
0.7 -> 0.3
Validation 不会继续算"修复是否成功"。
而是直接判断:
COUNTERFACTUAL_INVARIANT_VIOLATION
因为这两个实验已经失去可比性。
十、Metrics 才是验证的主证据
现在 Validation 主要依赖真实 fault_metric 数据。
比如 RETRY_STORM 当前会关注:
downstream.retry.count
downstream.retry.amplification.factor
downstream.total.call.count
downstream.retry.exhausted.count
api.avg.latency.ms
一个 Metric Comparison 大概包含:
{
"metricName": "downstream.retry.amplification.factor",
"beforeValue": 2.8,
"afterValue": 1.4,
"absoluteChange": -1.4,
"changePercent": -50.0,
"expectedDirection": "DECREASE",
"matchedExpectation": true
}
方向判断第一版非常简单:
INCREASE:
after > before
DECREASE:
after < before
STABLE:
abs(after - before) <= tolerance
我没有在这一版引入所谓统计显著性。
因为现在 FaultLab 本身就是 deterministic simulation。
这一阶段真正要验证的是:
修复方案是否让指标沿预期方向变化。
而不是:
这个变化是否能代表真实生产环境里的统计因果关系。
这是两个完全不同的问题。
十一、一个细节:before = 0 怎么算变化率
这类比较代码还有一个很实际的问题。
例如:
before = 0
after = 10
公式:
(after - before) / before
会直接除零。
所以现在的策略是:
changePercent = null
但:
expectedDirection = INCREASE
matchedExpectation = true
方向判断和百分比计算分开。
这样既不会产生:
Infinity
NaN
也不会因为 baseline 是 0 就丢掉一个明显有效的比较结果。
十二、四种 Validation 状态
最终没有简单设计成:
SUCCESS
FAILED
因为真实情况没那么二元。
目前状态是:
VERIFIED
所有 required expected effects 都满足。
3 / 3 matched
PARTIALLY_VERIFIED
部分满足、部分不满足。
2 / 3 matched
NOT_VERIFIED
数据完整,可以比较,但所有关键指标都没有按预期变化。
INCONCLUSIVE
数据不足,无法可靠判断。
例如:
required metric missing
这里我觉得 INCONCLUSIVE 很重要。
缺数据不应该被解释成失败,更不能被解释成成功。
十三、为什么 Trace 不参与最终 VERIFIED 判断
Replay 本身已经有完整 Trace。
所以 Validation 也会比较:
span count
error span count
total duration
operation counts
例如 Retry Storm 可以观察:
retry.attempt
Downstream Timeout 可以观察:
fallback.execute
但第一版里 Trace 只作为:
Supporting Evidence
而不是:
Primary Validation Evidence
也就是说:
Metrics
→ 决定 VERIFIED / PARTIALLY_VERIFIED / NOT_VERIFIED
Trace
→ 帮助解释为什么变成这样
否则很容易陷入另一个问题:
一个 span 少了,到底意味着修复成功,还是埋点路径变了?
所以目前没有让 Trace 直接左右 Validation Status。
十四、为什么没有新增 remediation_validation 表
Validation Report 现在不持久化。
因为所有事实数据本来就已经存在:
Original Experiment
Replay Experiment
Original Params
Replay Params
Metrics
Trace
而 Validation 又是 deterministic 的。
所以:
GET remediation-validation
直接现算即可。
这样避免再维护一份:
Derived State
也避免出现:
Metrics 已经变化
但 Validation Report 还是旧版本
的问题。
后面如果真的需要审计历史,再考虑快照持久化。
但现在没有必要。
十五、最终得到的不是"自动修复",而是"可验证修复"
v0.15.0 做完以后,我更愿意把这套东西描述成:
Fault Injection
↓
Observability
↓
Diagnosis
↓
Remediation Planning
↓
Counterfactual Replay
↓
Remediation Validation
它和所谓"AI 自动修复"最大的区别在于:
Agent 没有权限直接改生产系统。
它只能提出:
Candidate Remediation
系统会把这个方案放到受控实验环境里执行。
然后再根据实际 Before / After 数据告诉你:
VERIFIED
PARTIALLY_VERIFIED
NOT_VERIFIED
INCONCLUSIVE
这也是我目前觉得 AI FaultLab 最有辨识度的一块。
十六、目前的边界
这套设计现在能证明的是:
在 FaultLab 当前的确定性故障模型和相同实验条件下,某个修复参数是否产生了预期方向的变化。
它不能证明:
真实生产系统一定有效
真实流量下一定有效
具有统计显著性
建立了严格因果关系
它也不是:
Chaos Engineering Platform
Production Self-Healing System
Automatic Code Repair System
这些边界我觉得反而应该明确写出来。
做工程系统,最怕的不是功能少,而是把能力说得比实际大。
十七、v0.15.0 之后,我怎么看这套设计
从代码量上看,这个版本增加的并不是特别夸张。
Python 侧核心增加的是:
RemediationPlan
Remediation Policy
RemediationPlanningTool
Java 侧主要是:
RemediationReplayPolicy
RemediationReplayService
RemediationValidationPolicy
RemediationValidationService
Metric Comparison
Trace Comparison
但它改变了整个项目的主线。
以前的终点是:
AI Report
现在的终点变成了:
Evidence-backed Validation
这两者的差别其实很大。
前者解决的是:
AI 能不能给出一个像样的答案?
后者开始回答:
AI 给出的建议,能不能被实验系统验证?
目前 Java Backend 最终测试为:
189 tests
0 failures
0 errors
Remediation Validation 也没有再引入额外 LLM 判断。
整个验证链路仍然是 deterministic 的。
最后
如果让我用一句话概括 v0.15.0,我会写:
AI FaultLab 不再把 LLM 生成的修复建议当作终点,而是将建议转成受约束的结构化参数,在相同故障条件下执行 Counterfactual Replay,并通过真实 Metrics 与 Trace 对修复效果进行可解释验证。
对我来说,这比再加一个 Agent、再套一个框架更有意思。
因为从这一版开始,AI FaultLab 真正开始处理 AI 系统里一个很实际的问题:
模型说得对不对,不应该只让模型自己回答。