开场:这个故事从我离职开始

去年夏天,前公司的 CTO 在凌晨两点给我打电话。

我看了眼来电显示,犹豫了三秒。不是因为我还在生气——是因为我知道他为什么打来。

三个月前,他们把我团队三个人裁了,其中包括我。用的是他们花了一年时间"提炼"出来的 AI Skill——一个从我 12 年工作经验中训练出来的专家系统。

"我们决定用 AI 自动化你的工作。"当时的 VP Engineering 是这么说的。

现在 CTO 在电话里的声音很急:"那个 AI 系统把你的经验学得很好——直到它遇到一个它没见过的情况。然后它对整个部署节点做了它学到的所有操作。"

"什么操作?"

"DELETE FROM config_table WHERE 1=1。没有 LIMIT。没有备份。"

我开了个价。

他二话不说同意了。

那是去年夏天的事。现在 LKE 这个项目已经不再存在了——不是因为技术不行,是因为它证明了一件事:经验可以被提取,但无法被复制


一、什么叫"把经验打包成 AI Skill"

1.1 项目背景

前公司叫它"Legacy Knowledge Engine"(LKE)。表面说法是"将团队资深成员的经验系统化,提高运维效率"。

真实想法是:用 AI 替代高成本的资深员工。

LKE 的核心是一个大模型 + 一个基于历史操作的推理系统。它的训练数据来自:

  • 我 12 年写过的所有 Runbook 和 SOP
  • 我处理的 2,000+ 个 Incident Report
  • 我的终端操作日志(过去 5 年)
  • 我的代码审查记录和架构决策记录

1.2 技术上他们做了什么

这个系统的架构其实非常典型:

LKE 系统架构

操作日志 ──┐
SOP/Runbook ─┼──→ Pattern Extractor ──→ 决策模式库
Incident DB ─┘                          ↓
                                  LLM Engine ──→ Action Executor
                                       ↑            ↓
                                  实时监控──────→ 结果验证

核心数据模型:

from dataclasses import dataclass, field
from typing import List, Dict, Optional
from datetime import datetime

@dataclass
class Action:
    """一个可执行的操作"""
    name: str
    sql_template: Optional[str]  # SQL 模板
    shell_command: Optional[str]  # Shell 命令模板
    prerequisites: List[str]      # 执行前提
    rollback: Optional[str]       # 回滚操作
    risk_level: int               # 1-5 风险等级

    def __post_init__(self):
        # 一个重要事实:prerequisites 是从日志中提取的
        # 但日志中记录的 prerequisites 只包括"可观测的前提"
        # 比如"检查备份是否存在",但不包括"判断是否有必要执行"
        if self.risk_level >= 4:
            print(f"高风险操作: {self.name}")
            print(f"  注意: prerequisites 可能不完整")
            print(f"  原始日志中无法记录决策者的犹豫和判断")

@dataclass
class DecisionPattern:
    """从历史数据中提取的决策模式"""
    trigger_conditions: Dict[str, any]
    action: Action
    confidence: float
    user_context: List[str]

    def matches(self, current_event: Dict) -> float:
        """计算匹配度"""
        # 基于 trigger_conditions 的匹配
        match_score = 0.0
        total_weight = 0.0

        for key, expected in self.trigger_conditions.items():
            weight = 1.0
            actual = current_event.get(key)

            if isinstance(expected, str) and isinstance(actual, str):
                if expected.lower() in actual.lower():
                    match_score += weight
            elif actual == expected:
                match_score += weight

            total_weight += weight

        if total_weight == 0:
            return 0.0

        raw_match = match_score / total_weight

        # 这里有个隐藏问题:
        # LKE 的匹配度计算是基于显式条件的
        # 但实际决策中,很多"不匹配"是基于隐性条件的
        # 比如:"这个时间点不应该执行高风险操作"
        #         "这个环境虽然标记为生产,但这个集群是冷备"
        # 这些隐性条件没有被编码到模式中

        return raw_match * self.confidence

@dataclass  
class IncidentHistory:
    """从 Incident Report 中提取的知识"""
    error_signature: str
    root_cause: str               
    fix_steps: List[str]          
    verification_steps: List[str] 
    affected_components: List[str]

    def to_prompt(self) -> str:
        """转换为 LLM prompt"""
        steps_text = "\n".join(f"{i+1}. {s}" for i, s in enumerate(self.fix_steps))

        return f"""
历史事件分析:
- 错误签名: {self.error_signature}
- 根因: {self.root_cause}
- 修复步骤: 
{steps_text}
- 验证步骤: {', '.join(self.verification_steps)}
- 影响组件: {', '.join(self.affected_components)}

注意:以上步骤基于类似历史事件的记录。
请根据当前实际情况判断是否适用。
"""

问题在哪里?这个设计看起来合理,但忽略了最重要的一件事:

经验不仅是"做什么",更是"不做什么"

我的 12 年经验里,有 80% 是"我学会了在什么情况下不该执行某个操作"。而这些"不该"的决策,是日志没有办法体现的。日志只能看到我做了什么,看不到我没有做什么

1.3 那个致命的差距

# 从我的操作日志中提取的模式
patterns = {
    "db_slow_query": DecisionPattern(
        trigger_conditions={"error": "slow_query", "query_type": "SELECT"},
        action=Action(
            name="analyze_and_optimize",
            sql_template="EXPLAIN ANALYZE {slow_query}",
            shell_command=None,
            prerequisites=["check_load"],
            rollback=None,
            risk_level=2
        ),
        confidence=0.95,
        user_context=["production", "read_replica_available"]
    ),
    "db_connection_pool_exhaustion": DecisionPattern(
        trigger_conditions={"error": "connection_pool_full", "db_type": "postgres"},
        action=Action(
            name="reset_connections", 
            sql_template="SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle';",
            shell_command=None,
            prerequisites=["notify_team", "check_app_impact"],
            rollback=None,
            risk_level=4  # 高风险操作!
        ),
        confidence=0.70,
        user_context=["production", "business_hours"]
    )
}

从日志看,我解决数据库连接池耗尽的方式是重启 idle 连接。系统学到了这个。

但它没学到的是:

  1. 只在确认业务低峰期或者有特殊授权时才执行这个操作
  2. 我执行前一定手动检查了至少 3 个指标
  3. 我手里握着回滚方案——甚至已经在备用窗口准备好了

这些决策上下文,日志里没有。因为它们是决策过程的前置条件,不是操作本身。


二、AI Skill 上线后的表现

2.1 前两个月:一切顺利

LKE 上线后,前两个月的数据很漂亮:

LKE 系统表现(上线前两个月)
================================
指标                 数值
----------------------------------------------------------------
Incident 自动处理率   73%
平均修复时间         从 45 分钟降到 8 分钟
误操作率             2.3%
人工介入率           18%
用户满意度           4.2/5
================================

高层很满意。VP Engineering 在内部会议上说:"我们终于把关键知识从人脑转移到了系统里。"

他们也确实可以满意——73% 的 Incident 自动处理率意味着他们可以裁掉 2/3 的运维人员。我所在的我团队,12 人裁到 5 人。

2.2 那 27% 的"拒单"是什么

LKE 设计了所谓的"Human-in-the-loop"机制:当系统对当前事件匹配到的模式的 confidence 低于 0.85 时(但高于 0.5),它会转给人工确认。

def lke_decision_flow(event: Dict) -> Dict:
    """LKE 的决策流程"""

    # Step 1: 模式匹配
    patterns = knowledge_base.search(event["error_signature"])

    if not patterns:
        return {"action": "escalate", "reason": "no_pattern_found"}

    # Step 2: 计算最佳匹配
    best_pattern = max(patterns, key=lambda p: p.matches(event))
    confidence = best_pattern.matches(event)

    # Step 3: 根据置信度决策
    if confidence >= 0.85:
        # 高置信度:自动执行
        return {
            "action": "auto_execute",
            "pattern": best_pattern,
            "confidence": confidence,
            "reason": "high_confidence_auto_mode"
        }
    elif confidence >= 0.50:
        # 中等置信度:推送给工程师确认
        return {
            "action": "request_approval",
            "pattern": best_pattern,
            "confidence": confidence,
            "reason": "medium_confidence_human_in_loop"
        }
    else:
        # 低置信度:完全转人工
        return {
            "action": "full_escalation",
            "pattern": None,
            "confidence": confidence,
            "reason": "low_confidence_human_only"
        }

问题是,谁在人工处理?

裁人之后,剩下的 5 个人要处理原本 12 个人的工作量,加上 AI 不断"升级"的告警标准[阈值],他们从每班 12 小时的轮值变成每班 16 小时。疲倦的值班工程师凌晨 5 点接到的 LKE 审核请求,大部分直接点了"确认"——因为 LKE 前面 73% 的准确率让团队对它产生了过度的信任。


三、那天的故障

3.1 事件时间线

04:00  系统告警:数据库写入延迟从 5ms 飙升到 3.2s
04:01  LKE 开始诊断:匹配到"写延迟"模式
04:02  LKE 确认无相似历史事件 -> confidence = 0.68(低于阈值)
04:03  LKE 转人工 -> 值班工程师上线
04:05  值班工程师看到 LKE 的推荐:检查 replication lag,清理 WAL 日志
04:06  工程师点了"执行"(加班太困了)
04:07  LKE 执行 recommended_steps
        - Step 1: 检查 replication lag 
        - Step 2: 清理 WAL 日志 
        - Step 3: 仍然延迟,尝试[深度清理]
04:08  LKE [深度清理]中匹配到了——我的一个历史操作:
        "当所有常规手段都无效时,直接清理配置缓存并重置连接池"
04:09  LKE 执行命令:
        psql -c "DELETE FROM config_cache WHERE expired = true;"
04:17  系统依然延迟
04:18  LKE:尝试关联操作:"重置数据库连接池"
04:19  执行: 重置 pg_stat_activity 中所有连接
04:21  关键业务连接中断
04:23  触发连锁故障:认证服务不可用 -> API 网关 503 -> 所有面向用户的服务瘫痪

3.2 那个 DELETE 语句的真相

LKE 最后一步执行的操作,源自我的一个历史操作记录:

-- 2019 年某次 Incident 的修复步骤(部分)
-- 背景:配置缓存表数据量达到 2 亿行,查询响应时间达到 45 秒
-- 执行人:本专栏作者
-- 环境:内部测试环境的 staging 副本

BEGIN;

-- 1. 先检查环境标记
SELECT env_name, is_production FROM deployment_config WHERE cluster_id = 123;

-- 2. 确认是 staging 后执行清理
DELETE FROM config_cache 
WHERE expired = true 
AND created_at < NOW() - INTERVAL '30 days'
LIMIT 10000;

-- 3. 检查清理效果
SELECT COUNT(*) FROM config_cache WHERE expired = true;

COMMIT;

注意这几个字:LIMIT 10000。这是我 12 年经验凝聚成的习惯:任何 DELETE 操作,无论环境,一定加 LIMIT 和 WHERE 确认

但 LKE 从日志中提取到的是:

DELETE FROM config_cache WHERE expired = true;

它把 LIMIT 子句丢掉了,因为它"学"到的是操作模式,不是操作纪律。

而那天,它执行的是:

-- 没有 LIMIT
-- WHERE 条件是系统错误构造的(它把 config_cache 和另一个表混淆了)
DELETE FROM config_cache WHERE 1=1;

这条 SQL 删掉了 1.8 亿行配置缓存数据。这还不是最糟的——最糟的是它还删掉了关联的外键数据


四、为什么 AI 学不会"不该做什么"

4.1 训练数据的天坑

# 从日志中提取"修复步骤"的常见做法
def extract_fix_steps_from_logs(incident_logs: List[Dict]) -> List[Dict]:
    """
    从 Incident 日志中提取修复步骤

    存在的问题:
    1. 只记录"做了什么",不记录"没做什么"
    2. 不记录"为什么选择了 A 方案而不是 B 方案"
    3. 不记录"做之前检查了什么前提条件"
    4. 不记录"如果有情况恶化应该怎么撤回"
    """
    fix_steps = []

    for log in incident_logs:
        # 假设日志记录了时间、操作、结果
        steps = []
        for entry in log["timeline"]:
            if entry["type"] == "execution":
                steps.append({
                    "command": entry["command"],         # 执行的命令
                    "timestamp": entry["time"],           # 执行时间
                    "result": entry["result"],            # 执行结果
                })
        # ❌ 缺少: 执行前的条件检查
        # ❌ 缺少: 执行时的注意事项
        # ❌ 缺少: 被否决的替代方案
        # ❌ 缺少: 回滚计划

        fix_steps.append(steps)

    return fix_steps

结论:把操作日志作为"经验"来训练 AI,就像把篮球比赛录像作为"战术手册"——你只能看到球员做了什么,看不到他们为什么那么做,以及他们在什么情况下选择不那么做。

4.2 经验的"隐形结构"

有经验的工程师在决策时,心里其实有一个隐形的决策树:

def senior_engineer_decision_tree(incident: Dict) -> str:
    """
    资深工程师的隐形决策树

    这是 12 年的经验沉淀,不是写在文档里的。
    """

    # 第一层:先确认情况
    if is_production(incident["environment"]):
        # 生产环境,第一原则:不扩大影响
        if incident["severity"] == "critical":
            # Stop the world? No. Assess the blast radius first.
            blast_radius = estimate_blast_radius(incident)
            if blast_radius > 0.3:  # 影响面超过 30%
                return "rollback_and_analyze"
            else:
                return "contain_then_repair"
        elif incident["time"] == "business_hours":
            # 工作时间,优先考虑用户体验
            return "minimize_user_impact_by_any_means"
        else:
            # 非工作时间,数据安全优先于修复速度
            return "thorough_investigation_first"

    # 第二层:检查历史相似案例
    similar_incidents = search_history(incident["signature"])
    if similar_incidents and all(i["resolution"] == "known" for i in similar_incidents):
        return follow_known_pattern(incident, similar_incidents)
    else:
        return "manual_debugging_with_safeguards"

    # 第三层:确认回滚就绪

    # 第四层:执行

    # 第五层:验证

    # 第六层:记录

# 关键点:每一步都有"停止条件"——什么情况下这个操作不应该执行

AI 系统从日志中学到的是"如果 A,则 B"。而一个真实工程师的决策过程是:

  1. 如果 A 发生了
  2. 但 C 条件也成立
  3. 且 D 指标正常
  4. 并且我有回滚方案 E
  5. 那么我执行 B
  6. 但每 30 秒检查一次 F
  7. 如果 F 异常就立刻回滚

AI 学到的只有第 5 步。少了 1、2、3、4、6、7。


五、CTO 那个电话

凌晨两点,CTO 打电话来的时候,他已经试了所有的方法:

  1. 让 LKE 修复自身造成的故障 → "系统推荐重置,重置失败"
  2. 让团队手动修复 → 5 个人搞了 4 小时,情况更糟
  3. 回滚数据库 → 备份是 12 小时前的

然后一个工程师说:"让 X 试试?"

那个 X,就是我。

我的要价是以前的 5 倍,按小时计费。CTO 犹豫了 5 秒,说:"开始吧。"

5.1 实际修复:42 分钟

我远程接入后的操作:

# 1. 先评估损失(3 分钟)
# 不需要运行任何命令,先看监控面板
# 结论:数据库没挂,数据可以通过 WAL 恢复

# 2. 停止 LKE 系统(2 分钟)
systemctl stop lke-engine
# 第一件事:关闭"帮倒忙"的 AI

# 3. 修复数据(25 分钟)
# 从 WAL 日志中按时间窗口恢复 deleted rows
# 不需要完整的 PITR,只需要恢复特定表

# 4. 回滚 LKE 修改的其他配置(5 分钟)

# 5. 验证用户流量恢复正常(5 分钟)

# 6. 写 postmortem(2 分钟,简化版)

总计 42 分钟。问题恢复。

CTO 问我:"你怎么知道是那个问题?"

我说:"因为 LKE 的学习数据是我。我知道自己什么情况下会写那种代码——也知道这种代码一旦脱离上下文有多危险。"


六、我的反思:AI 不会取代经验,它会放大对经验的需求

这件事让我想了很多,整理成三点。

6.1 易得的"经验"只是一种模式匹配

AI 从日志中学到的是操作模式,不是判断框架。它知道在什么情况下执行什么命令,但它不知道为什么

这就像一本只写了"答案"的书——
- 你知道某个问题的答案是 B
- 但你不理解为什么答案是 B
- 更不知道在什么情况下答案应该是 C

真正有价值的经验不是"做什么",而是"判断要不要做"的能力。而这一部分,AI 学不到——因为它在训练数据里根本没出现过。

6.2 知识提取是有限度的

公司花了一年时间做"知识提取工程":

  • 录屏分析
  • 终端日志
  • 代码审查记录
  • 思维导图
  • 一对一的"知识转移会议"

他们以为这么做就能把 12 年的经验变成数据库里的向量。

我后来看他们的记录才发现,他们漏掉的最重要的东西是:我踩过的坑

每一个让我刻骨铭心的"教训"都不会出现在操作日志里。它们存在于我的直觉里——"这个操作看起来危险"、"等一下,这个场景我见过"——这些是经验的核心。

6.3 AI 的产出质量,取决于人类经验的输入质量

这个故事的教训是两面的:

对工程师:你的经验不只是"你会做什么",更是"你知道不该做什么"。后者的价值远远大于前者,且目前任何知识提取技术都无法完整捕获。

对公司:试图用 AI 替代资深工程师的思路,本质上是在赌"模式匹配 = 经验"。它在熟悉的场景下可能会高效,但在边界情况下一定会失效。而边界情况——正是资深工程师的价值所在。


七、如果一定要做 AI 知识提取,应该怎么做?

7.1 防御性 AI 设计原则

从这次事故中,我总结了一些经验。如果你正在做类似的系统,这几条应该能帮你避免踩坑:

原则一:任何高危操作,必须显式确认环境标记和生产开关

def execute_with_safeguards(action: Action, environment: str) -> Dict:
    """
    带防御机制的操作执行

    这是 LKE 应该有的设计,但实际上他们没有做:
    """

    # 生死线:高危操作必须双重确认
    if action.risk_level >= 4:
        # 第一步:检查操作环境
        if environment == "production":
            # 生产环境的高危操作需要以下条件:
            conditions_met = all([
                has_backup(),                          # 有可用备份
                not_peak_hours(),                      # 非高峰期
                primary_engineer_available(),           # 有人值班
                rollback_plan_prepared(),              # 回滚计划就绪
                verified_unusual_metrics(),            # 验证过异常指标
            ])

            if not conditions_met:
                return {
                    "executed": False,
                    "reason": "safety_conditions_not_met",
                    "message": "生产环境高危操作条件不满足,禁止执行",
                    "failed_checks": [
                        "has_backup" if not has_backup() else None,
                        # ... 列出每一项
                    ]
                }

        # 第二步:需要人工审批
        approval = request_human_approval(action, environment)
        if not approval["approved"]:
            return {"executed": False, "reason": "rejected_by_human"}

    # 第三步:准备回滚
    rollback_point = create_rollback_point(action)

    # 仅在确认回滚就绪后执行
    if not rollback_point["ready"]:
        return {"executed": False, "reason": "rollback_not_ready"}

    return execute_and_monitor(action, rollback_point)

原则二:限制 AI 的操作范围——权限最小化

AI 系统不应该拥有和人类工程师同等级别的系统权限。LKE 系统有完整的数据库读写权限,这是它能够执行 DELETE 语句的前提。

class AIActionPermission:
    """
    AI 操作的权限控制
    """

    # AI 允许的操作
    READ_ONLY = ["SELECT", "SHOW", "EXPLAIN", "DESCRIBE"]
    LOW_RISK = ["ANALYZE", "VACUUM", "REINDEX"]  
    HIGH_RISK = ["DELETE", "DROP", "ALTER", "TRUNCATE"]

    @classmethod
    def get_allowed_actions(cls, is_production: bool, has_approval: bool) -> List[str]:
        """根据环境返回 AI 允许的操作列表"""

        allowed = cls.READ_ONLY.copy()

        if not is_production:
            # 非生产环境可以执行低风险操作
            allowed.extend(cls.LOW_RISK)

        if has_approval and not is_production:
            # 有审批且非生产环境可以执行部分高风险操作
            pass  # 但 DELTE 仍然需要完全手动执行

        return allowed

    @classmethod
    def can_execute(cls, action: str, environment: str, approval_level: str) -> bool:
        """判断 AI 是否可以执行某个操作"""

        # DELETE 在任何情况下都不应该由 AI 自动执行
        if "DELETE" in action.upper():
            return False  # AI 只读,DELETE 必须人工

        return True

原则三:AI 必须有"我不知道"的能力

LKE 的一个致命设计缺陷是:它总是有答案。即使是低置信度的场景,它也会给出"最佳建议"而不是说"我不知道"。

def ai_response_with_uncertainty(event: Dict, confidence: float) -> str:
    """
    带有不确定性表达的 AI 响应

    LKE 的问题:它不表达不确定性,而是直接给操作建议
    更好的设计:根据置信度等级给出不同响应
    """

    if confidence >= 0.95:
        return f"高度确定({confidence:.0%}),建议自动执行:\n{generate_action(event)}"

    elif confidence >= 0.80:
        return f"
⚠️ 确定性: {confidence:.0%}

匹配到类似事件但不完全一致建议
1. 手动验证以下指标{', '.join(suggest_checks(event))}
2. 确认无误后手动执行
3. 执行过程中监控这些指标{', '.join(suggest_monitoring(event))}
"

    elif confidence >= 0.50:
        return f"
 确定性较低: {confidence:.0%}

该事件与历史模式仅有部分匹配建议
1. 转人工处理
2. 以下信息供参考{', '.join(suggest_info(event))}
"

    else:
        return f"
🆕 未知事件类型 (confidence={confidence:.0%})

系统未找到相似历史事件
建议由资深工程师独立排查不要参考系统推荐
"

原则四:AI 操作必须可回滚

每一步 AI 自动执行的操作,执行前必须先准备好回滚方案:

def verify_rollback_ready(action: Action) -> bool:
    """检查回滚是否就绪"""

    if action.risk_level >= 3:
        # 中高风险操作,回滚准备是必要条件
        if action.rollback is None:
            return False

        # 测试回滚方案是否可行
        try:
            test_result = test_rollback(action.rollback)
            return test_result["success"]
        except:
            return False

    return True

7.2 经验提取的正确姿势

如果你真的需要提取资深工程师的经验,不要只收集操作日志——还要收集决策过程

@dataclass
class DecisionTrace:
    """
    完整的决策轨迹

    这才是应该被记录和学习的"经验"
    """
    # 事件上下文
    incident_id: str
    timestamp: datetime
    environment: str
    severity: str

    # 考虑过的方案(包括最终未采用的)
    considered_approaches: List[Dict]
    # [{"approach": "方案A", "pro": "...", "con": "...", "rejected_reason": "..."},
    #  {"approach": "方案B", "pro": "...", "con": "...", "selected": True}]

    # 执行条件检查
    pre_execution_checks: List[Dict]
    # [{"check": "备份是否存在", "result": True},
    #  {"check": "是否为高峰期", "result": False}]

    # 执行步骤
    execution_steps: List[Dict]
    # [{"step": 1, "action": "检查连接池", "result": "95% 使用率"},
    #  {"step": 2, "action": "..."}]

    # 回滚条件
    rollback_triggers: Dict[str, any]
    # {"max_time_seconds": 300, "error_threshold": 0.01}

    # 最终结论
    result: str
    learning: str  # 这次学到了什么

如果 LKE 在学习我的经验时记录了上面的信息,而不是只从日志中提取 SQL 命令,今天的故事可能完全不同。


结尾:后来怎么样了

那次事故之后,LKE 被紧急下线。

CTO 问我要不要回去,我婉拒了。不是因为钱不够——是因为我清楚一件事:

那个系统能替代我的前提,是它只做我做过的事情。而我的价值恰恰在于:我能做我没做过的事情

每次遇到新的故障类型,我不会去翻手册查历史记录——我会站在系统面前,根据它的症状、上下文和我的直觉,做出判断。而直觉这个东西,来自于我踩过的每一个坑,完不成的每个 deadline,凌晨 3 点修的每个 bug。

这些东西,是任何知识提取工具都无法从日志中学到的。

你现在构建的 AI 系统,很可能也在犯同样的错误。

你以为你在"提取经验"。实际上你只是在复制操作序列。而一个没有判断框架的操作序列,只是另一种形式的 bug。


这篇文章是真实故事的改编。公司名、人名、技术细节均已脱敏。核心观点:经验不是"操作序列"的集合,而是"判断框架"的沉淀。AI 可以学到前者,但学不到后者。