两个 AI 都说「没发出去」,其实都发了:判失败要连查两次

0 阅读6分钟

两个替我干活的 AI,同一分钟里各报了一句「没发出去」。「没发出去」指的是后台状态:文章列表里查不到带今天时间的条目,读者打不开正文,跟「我记得点过发布」无关。实际两篇都发出去十几分钟了。

下面这个函数是我事后补的,二十行,判据只有一条:说失败之前,隔五秒再查一次。

import time

def settled(check, tries=2, gap=5.0):
    """check() 返回 (ok, 证据)。连续 tries 次都拿不到 ok 才算失败。"""
    last = None
    for i in range(tries):
        ok, proof = check()
        if ok:
            return True, proof          # 判成功只要一条证据
        last = proof
        if i < tries - 1:
            time.sleep(gap)             # 假失败的代价:重做甚至重发
    return False, last                  # 判失败至少两条,不采信一次快照

事故时间线

那天早上发生了什么

公众号那篇 09:50 已经发出来了。九点五十二分这边去核对,看到的是没刷新的发表记录列表,于是报「在等扫码」。

CSDN 那边更绕:登录早就恢复了,但报的是「控制权在用户手上」。真正的链条是,它拿到的任务空间列表里少了那个名字,就判定空间没了、登录失效。而空间一直都在,用名字照样打得开,只是里面那个标签页被关成了新标签页。重新打开内容管理页,「已发布」原样都在。

两边都挂到十点一刻,直到有人问了一句「码我早扫完了,怎么还有在扫码的」,才查出来。

值得说明的是 CSDN 那边的推理链,因为它是每一步都合理、合起来是错的:接口返回了一个名字列表,它按名字去找,没找到;没找到就推断那个空间不存在;空间不存在就推断登录失效;登录失效就该等人扫码。四步每一步都有依据,可第一步的依据本身只是一次分页返回。

它没有问的问题是:这个名字会不会排在第二页?会不会因为最近没被用过而被排到后面?接口成功和列表完整,是两件事。

一屏怎么骗人

一屏为什么不可信

一句话:它们判的是「我眼前这一屏」,而事实在系统里。

屏幕是一次快照,三种方式骗人:

骗法长什么样它为什么像真的
缓存列表没刷新,条目还在旧状态页面结构完整、有数字、没有报错
标签被关空间里只剩一个新标签页查询确实返回了空,不是异常
列表被截断名字不在返回的前若干条里接口成功、状态码 200

第三种最阴,因为它把「我没查到」伪装成「它不存在」。一份列表证明不了「东西没了」,只能证明「这份列表里没有」。

三种卡住

三种卡住,别混着报

这套分类我现在强制写进报告里,因为混着报的结果是所有人都以为在等外部:

类型长什么样报告要写什么
等外部扫码、平台审核、等人等谁、等多久、等到什么信号算结束
我没查页面没刷、标签没了、我以为这不是卡住,是没做,不许写成「在等」
真断401 / 403 / 5xx报错原文加状态码

那天两边的错,都是把第二种写成了第一种。

判据函数

判据落到代码里

函数在上面,落地的关键是 check() 必须查会一直挂着的东西:后台列表、发表记录里带新时间的条目。弹窗、跳转、点了没报错,都不算证据。

再往下就两件事:

  1. 成功只要一条。列表里有它,收工。
  2. 失败至少要两条,而且最好换一个入口,比如从文章管理查而不是从发表记录查。

第二条不是洁癖。误判成功的代价是晚一点发现,误判失败的代价是重做一遍,甚至重复发布一次,两条同题内容互相抢流量。多查一次五秒,比这个便宜太多。

同题内容的重复发布不是虚惊。多数平台判重之后,处理方式是保留数据最差的那一篇、删掉其余,严重的还会给账号记一笔。也就是说,「以为没发出去,于是再发一遍」这个动作,最坏的结果比晚发一小时严重得多。这就是为什么判失败要多验一次,而不是判成功多验一次。

换成命令也是一样的写法

不想引函数的话,两个入口各查一遍、中间隔五秒,效果一样:

# 第一次查:文章管理列表里有没有带今天日期的条目
grep -c "$(date +%Y-%m-%d)" /tmp/list-1.txt || true
sleep 5
# 换一个入口再查一次:草稿箱必须为空,才算真的发出去了
grep -c "草稿" /tmp/drafts-1.txt || true

两个文件都是同一次页面读取留下的文本,不是手抄。这里的要点不是命令,是「两个入口、至少两次、中间隔开」,任何自动化里都能照抄这个形状。

我今天自己套了一遍

这篇稿子发出去之后,我按同一套判据核对:后台列表里有没有带今天时间的条目,草稿箱里是不是空的,匿名窗口能不能读到正文和四张图。第一条和第四条在同一个页面里,三秒能拿到;第二条和第三条需要换入口。我不打算再写「审核中就是发出去了」,那只是受理凭据。

顺带一个具体做法:读列表时不要只读首屏,滚动到底再读。图是懒加载的,不滚到位去量尺寸会读到零,我上周就因为这一点差点把一张正常的图判成挂了。「没读到」和「不存在」之间,差一次滚动。

它不管什么

得说清边界,不然这几条会被当成万能判据:

  • 平台延迟:有的站发出去两分钟才进列表,两次查询仍然可能都查不到。这时该等,不该判失败。
  • 没有列表接口的站:只能靠页面文字,慢一点,但同样要读两次。
  • 人工审核:审核中是一个明确状态,不是失败,报的时候把状态原文抄上。

收尾

那天真正值钱的不是「两个 AI 都犯了错」,是它们犯的是同一个错:把一次快照当成结论。

一个只会说「做完了」的系统,和一个会在说「失败了」之前多查一遍的系统,前者的错你会慢慢发现,后者的错你会立刻知道。

下一步我只做一件事:把两次查询写进发布脚本的退出码,查不到就返回非零,让「没发出去」变成一条命令的结果而不是一句话。先不做的是给每个站各写一套判据,模板不同,判据得是同一套。至于我自己,现在报卡住必须附一条刚跑的查询和输出,没有这条,就是没做。这篇里的时间戳、退出码和查询次数都来自 10 月 29 日那天的本地记录,不是复盘时补的。(本文含 AI 辅助创作,已人工核对)