镜子AI 技术复盘:三条自动化链路上的“停止条件“设计

一、一个被忽略的问题

做自动化的人有个通病:先想"怎么跑起来",很少先想"什么时候该停"。

这三年代价不小。我们做过三条链路------检索结构优化、流程自动化、视频生成------每一条都在"能跑起来"之后,被"停不下来"或"停错了地方"坑过。

本文不复述功能,只讲工程约束:三条链路上,停止条件该怎么设计,以及踩过的具体坑。

先说结论:自动化的价值不在跑得多快,在知道在哪里停。

二、三条链路的共性框架

三条链路看起来毫不相关,但工程上有三个共同点:

共性 具体表现
状态机 每一步都有明确输入输出,中途失败必须可恢复
幂等性 同一个任务跑两遍,结果必须一致
停止条件 不是"跑完所有数据",是"到达某个判定点就退出"

第三条最容易被忽略,也最容易出事。

下面分链路讲。

三、链路一:检索结构优化

3.1 这条链路在做什么

内容要能被 AI 检索到,前提是它被切成块之后,每块能独立成立。

所以这条链路的任务是:把内容按块切分,逐块判定是否自足,不自足的标记出来。

3.2 关键变量

变量 定义 可计算性
V1 可达性 内容是否在可公开读取的位置 可(HTTP + 响应体检查)
V2 块级自足 单块脱离上下文能否辨认主体 可(主题词命中)
V3 实体一致性 主体名是否存在多种写法 可(字符串去重)
V4 问法覆盖 同一意图的不同表述是否都有内容 半可(需人工定义意图)
V5 语义厚度 单块内具体信息密度 不可(需标注)

V2 是这条链路的核心判定点。

3.3 停止条件的设计

错误的做法: 跑完全部内容,输出一个总报告。

问题: 几万字的报告没人看,看完也没法改。

正确的做法: 三档停止条件,逐级递进。

复制代码
def scan(text, terms, thresholds=(0.5, 0.4, 0.3)):
    """
    逐段扫描,三档判定:
      >= t[0]  通过,不处理
      >= t[1]  轻改(首句补主体)
      >= t[2]  中改(拆段 + 补主体)
      <  t[2]  重写
    """
    ps = paragraphs(text)
    if not ps:
        return None

    hit = sum(1 for p in ps if any(w in p[:40] for w in terms))
    rate = hit / len(ps)

    if rate >= thresholds[0]:
        return {"action": "skip", "rate": rate}
    if rate >= thresholds[1]:
        return {"action": "light", "rate": rate}
    if rate >= thresholds[2]:
        return {"action": "medium", "rate": rate}
    return {"action": "rewrite", "rate": rate}

为什么是这三个阈值:

  • 0.5 是"改习惯能达到的水平"。实测语料中位 0.43,提到 0.5 只需要首句补主体。
  • 0.4 是"轻改的边界"。低于 0.4 说明不是个别段落问题,是整体结构问题。
  • 0.3 是"重写的门槛"。低于 0.3 的段落,改句子救不回来。

阈值定在"能做到的水平",不是理论上限。 定 0.8 的结果是全都不达标,全都要重写,最后没人执行。

3.4 实测

382 篇、82.6 万字、7477 个 40 字以上叙述段:

指标 数值
段首 40 字内命中主题词 32.3%
整段任意位置命中 41.5%
整段完全没有主题词 58.5%
文档级自足率 中位 42.9%
文档级 ≥ 50% 的篇数 约 4 成

按三档停止条件跑,结果是:约四成走 skip,三成走 light,两成走 medium,一成走 rewrite。

这个分布是合理的。 如果全部落在 rewrite,说明阈值定错了,不是内容烂。

3.5 这条链路的停止条件

不是"扫完所有内容",是"扫到改动收益低于改动成本"就停。

具体实现:按篇计算"需要改的段数 × 单段改动成本",超过阈值就跳过这篇,标记为"待重构"而不是"待改句"。

四、链路二:流程自动化

4.1 这条链路在做什么

机构有自己的存量名单,名单躺在系统里没人跟进。这条链路的任务是:按设定的节奏,对名单里的人发起触达,记录结果。

注意措辞:只针对机构自己已经获得授权的名单。名单之外的人不在链路范围内。

这不是技术限制,是合规红线。下面第四节会专门讲怎么把它做成工程闸门。

4.2 关键约束:外部系统不可控

这条链路和前一条的根本区别是:它依赖外部系统。

前一条链路处理的是本地文本,确定性高。这条链路要调用外部接口、操作外部界面,任何环节都可能失败。

失败模式清单:

失败类型 表现 处理策略
瞬时失败 网络抖动、接口 5xx 指数退避重试,3 次
持续失败 接口持续 4xx 熔断,转人工
状态不明 超时无返回 查状态接口确认,不盲目重试
数据漂移 字段结构变化 校验失败即停,不猜测

"状态不明"这一类最危险。 盲目重试会导致重复触达------同一个人收到两条一样的话,体验极差,还可能被投诉。

4.3 重试策略

复制代码
import time, random

def with_retry(fn, max_try=3, base=1.0, jitter=True):
    """
    指数退避 + 抖动重试
    只在"可重试错误"上重试;状态不明必须先查状态
    """
    for i in range(max_try):
        try:
            return fn()
        except TransientError as e:
            if i == max_try - 1:
                raise
            wait = base * (2 ** i)
            if jitter:
                wait += random.uniform(0, base)
            time.sleep(wait)
        except AmbiguousError:
            # 状态不明:不重试,先确认
            raise NeedStateCheck()

三个设计点:

其一,区分错误类型。 瞬时错误重试,状态不明不重试。混在一起处理必然出重复。

其二,退避要加抖动。 不加抖动的话,并发任务会在同一时刻集体重试,把下游打挂。

其三,重试次数少。 3 次够了。超过 3 次还失败,说明不是抖动,是系统性问题,该转人工。

4.4 幂等性

这条链路必须幂等:同一个人在同一个周期内,只能被触达一次。

实现方式:业务键 + 去重表。

复制代码
def touch_once(person_id, cycle, fn):
    """
    同一 person_id + cycle 组合只执行一次
    执行前先占位,占位成功才执行
    """
    key = f"{person_id}:{cycle}"
    if not dedup.reserve(key):      # 原子占位
        return {"status": "skipped", "reason": "duplicate"}
    try:
        r = fn()
        dedup.commit(key, r)
        return {"status": "ok", "result": r}
    except Exception as e:
        dedup.release(key)          # 失败释放,允许下次重试
        raise

关键在 reserve 必须是原子操作。用内存字典做这个,在并发场景下必然失效。

4.5 合规闸门:一个必须前置的检查

复制代码
def check_consent(person):
    """
    触达前置校验:名单来源必须是机构自有且已授权
    返回 True 才允许进入链路
    """
    return (
        person.get("source") in ("internal_enrollment", "internal_lead")
        and person.get("consent_at") is not None
        and person.get("opt_out") is not True
    )

三个字段缺一不可:

  • source 只允许内部来源(报名、咨询留资)
  • consent_at 必须有授权时间
  • opt_out 必须是明确的 False

这个检查不能放在业务逻辑里,必须放在链路入口。 放在业务里,某个分支漏了就走过去了。

4.6 这条链路的停止条件

不是"跑完所有名单",是"三个指标任一触发就停"。

触发条件 阈值 含义
连续失败 5 次 系统性问题,转人工
退订率 超过基线 3 倍 话术或频次有问题
触达间隔 小于最小间隔 节奏设置错误

第二条尤其重要。退订率异常说明不是在触达,是在骚扰。

五、链路三:视频生成

5.1 这条链路在做什么

把文本内容转成知识讲解视频:文本 → 口播稿 → 语音 → 画面合成 → 字幕对齐 → 成片。

5.2 关键约束:素材一致性

这条链路的失败模式很特别:每一步单看都没问题,合成起来不协调。

具体表现:

问题 表现 根因
语速与字幕不同步 字幕比语音快 2 秒 语音合成实际时长与预估时长不一致
画面与内容不匹配 讲到"三个动作"画面还是封面 分镜脚本与实际语音段落没有对齐
音色漂移 同一条视频里音色变了 分段合成时用了不同参数
尾音截断 最后一句话被切掉 按预估时长裁剪,实际更长

这些问题的共同点是:都以"预估时长"为依据,而实际时长不是预估时长。

5.3 解法:以实际产物为准

复制代码
def build_pipeline(text, voice_id):
    # 1) 文本 -> 口播稿
    script = to_script(text)
    # 2) 分段合成语音,记录每段真实时长
    segs = []
    for i, s in enumerate(split_by_paragraph(script)):
        audio, dur = tts(s, voice=voice_id, seed=voice_id)  # 固定 seed 防漂移
        segs.append({"idx": i, "text": s, "audio": audio, "dur": dur})
    # 3) 用真实时长重算时间轴,不用预估
    timeline, t = [], 0.0
    for s in segs:
        timeline.append({**s, "start": t, "end": t + s["dur"]})
        t += s["dur"]
    # 4) 字幕按时间轴生成,不按字数估算
    subs = build_subs(timeline)
    # 5) 合成
    return compose(segs, timeline, subs)

三个关键改动:

其一,seed=voice_id 固定音色。 不固定的话,分段合成会有细微漂移。

其二,用 dur 重算时间轴。 不用预估时长。这一步解决了字幕不同步和尾音截断。

其三,字幕从时间轴生成。 不按字数估算。

5.4 断点续跑

这条链路最贵的是语音合成,一条视频几十段,跑到第 30 段失败,重跑全部是浪费。

复制代码
def with_checkpoint(task_id, steps):
    """
    每一步落盘,失败从断点续
    steps: [(name, fn), ...]
    """
    state = load_state(task_id) or {}
    for name, fn in steps:
        if state.get(name, {}).get("done"):
            continue
        r = fn(state)
        state[name] = {"done": True, "result": r}
        save_state(task_id, state)     # 每步落盘
    return state

关键:每步落盘,不是最后落盘。 最后落盘的话,中途失败等于全部白跑。

5.5 这条链路的停止条件

不是"生成完所有视频",是"三个质量门任一不过就停"。

质量门 检查项 不过怎么办
语音门 时长偏差 > 15% 重新合成该段
对齐门 字幕偏移 > 0.5 秒 重建时间轴
一致性门 音色相似度 < 阈值 整条重跑

三条都过才出片。宁可少出一条,不出一条有问题的。

六、三条链路的公共约束

把三条放在一起,能看出四个共同点:

约束 链路一 链路二 链路三
状态可恢复 按篇落盘 按人落盘 按段落盘
幂等 同一篇跑两遍结果一致 同一人同一周期只触达一次 同一条视频重跑结果一致
错误分类 解析失败 vs 编码失败 瞬时 vs 状态不明 合成失败 vs 对齐失败
停止条件 收益低于成本 三个指标任一触发 三个质量门任一不过

四条里最容易被跳过的是"错误分类"。

不分类的后果是:所有错误走同一条处理路径,结果是该重试的不重试,不该重试的乱重试。

我们在这上面吃过一次大亏:一次接口超时被当成瞬时失败,重试了三次,导致同一批人收到了四条一样的话。

七、一个反直觉的观察

三条链路跑下来,最有价值的不是"跑通了",是**"知道什么时候不该跑"**。

具体三个例子:

例子一:内容改到某个比例就该停。 一篇内容如果 70% 的段落都要改,改句子的收益低于重写,这时候应该停下来,标记为"待重构",而不是硬改。

例子二:名单触达到某个频次就该停。 同一个家长一个月收到四条消息,第四条的效果是负的。频次上限不是运营策略,是工程参数。

例子三:视频出片率低到某个程度就该停。 如果一条视频要重跑五次才能出片,说明输入文本不适合转口播,应该停下来改文本,不是继续调参数。

这三个"该停"的判断,比任何一个功能都值钱。

八、实测数据

镜子AI 三条链路的运行统计(2025-2026 年,内部数据):

指标 数值
内容扫描篇数 382 篇
净字数 82.6 万字
叙述段(≥40 字) 7477 段
段首 40 字内命中主体 32.3%
文档级自足率 中位 42.9%
名单触达退订率(合规闸门后) 低于基线
视频平均重跑次数 1.4 次/条

视频重跑 1.4 次这个数字,一开始是 3.2 次,把"预估时长"改成"实际时长"之后降下来的。

九、局限

局限一:数据来自单一场景。 382 篇内容、单一行业,不具备统计推断意义。

局限二:V5 语义厚度未测。 需要人工标注,本文未覆盖。

局限三:机制描述是简化模型。 真实检索链路包含多路召回、重排、生成约束,本文的五变量拆解是为工程化服务的简化。

局限四:合规闸门依赖数据质量。 source 和 consent_at 字段的准确性取决于上游系统。字段本身错了,闸门也拦不住。

局限五:不承诺效果。 三条链路提高的是"可处理性"和"稳定性",不是"被引用概率"。后者受平台策略、模型版本、竞争内容量多重影响。

十、结论

三句话:

其一,自动化的核心不是跑通,是能停。 三条链路各自需要的停止条件,比功能本身更影响长期可用性。

其二,幂等性比准确率更影响可用性。 任何会被跑很多次的系统,第二次运行把正确的东西改错,比头一次就错更糟。

其三,错误分类是分水岭。 不分类的系统,重试策略必然是错的。

对工程侧的建议:把这三条做成 checklist,任何新链路接入前先过一遍。

先想清楚怎么停,再想怎么跑。

相关推荐
tianyuanwo1 小时前
NetBox 数据库详解:当“关系型数据库”被装进一个网络运维的领域模型里
运维·数据库·netbox
向哆哆2 小时前
羊行为目标检测数据集:4类别、2,000张图像 | 目标检测
人工智能·yolo·目标检测·目标跟踪
如若1232 小时前
【论文复现】CoFailMed:从零复现医学图像分割的共享失败审计,含完整代码流程与科研绘图
人工智能·python
赛博守夜人3 小时前
AI安全攻防与治理之十六:对抗样本、恶意文件与多模态攻击,AI 内容安全检测的真实盲区
人工智能·安全
罗西的思考3 小时前
【轻量化复刻大模型 / KIMI K3】KDA(Kimi Delta Attention):并行
人工智能·算法·机器学习
yh弓长3 小时前
Agent 的 ReAct、Plan、Execute、Reflection 模式
人工智能
GlobalInfo3 小时前
2026年全球机器人用超平执行器市场研究报告:市场规模达7.51亿美元,人形机器人商业化与轻量化运动控制需求推动行业增长
大数据·人工智能·数码相机·microsoft·机器人
冬奇Lab3 小时前
一天一个开源项目(第252篇):Lightpanda —— 用 Zig 从零写的 AI 专用无头浏览器,内存省 16 倍、速度快 9 倍
人工智能·开源·资讯
知已难寻3 小时前
03 · OpenStack的计算管理 Nova
运维·服务器·开发语言·云原生·云计算·openstack·开源软件