[控场AI]
· 13 分钟阅读· 6,523 字

别再让AI回复冒充运行结果:48小时实验笔记

别再让AI回复冒充运行结果:48小时实验笔记

聊天回复不是进程表,只有退出码与主机身份才能把「建议」变成「跑过」。

作者用一个 48 小时的现场笔记实验说明:语言模型可以把一段从未执行的检查描述成「已通过」,而聊天记录本身不构成运行证据。他把工作拆成两个互不评分的证人——免费模型会话只负责起草命令与解释,独立服务器 shell 负责在预先定义的白名单里实际执行。记录器先把模型原始回复存盘并取 SHA-256,再运行白名单任务,只有当退出码为 0 且与窄声明一致时,笔记才允许标注「已验证」。文章依次指出三类破口:社交层面的急于写下「已验证」、命令不匹配却看似相关、以及免费服务器因工作目录、用户或队列问题造成的静默失败。决策表规定仅回复为 draft、仅日志为 incomplete、两者匹配为 candidate,但 candidate 不等于可合并。作者也明确了边界:涉及凭证、生产数据、合规审计或无法快速回滚的变更,不应在免费共享服务器上排练。

一条通过了的测试,但我根本没跑过

我打开一本新笔记,因为一段措辞漂亮的回复总能在我这边任何进程启动之前就先到达。它看起来完整,点名了一条命令,甚至描述了一次「已通过」的检查——而那次检查我并没有执行。我为什么差点把这句话抄进日志,当成一条观测记录?

聊天记录不是进程表,它无法说明究竟哪个二进制真的跑过。

这个落差就是这篇笔记的全部工作,不是产品评测,也不是评分榜。我想在一个地方向模型要补丁,在另一个地方跑一条窄命令,且能亲眼读到退出码。如果这两份记录互相矛盾,哪一份配得上我笔记里的绿色标记?我在索要下一个建议之前就把规则写了下来,因为一段流利的文字可以冒充一个结果。

48小时实验笔记原始来源

前12小时:我把「建议」和「运行」拆成两个证人

两个证人不许互相打分

我把工作拆成两个不允许互相评分的证人。第一个证人是免费的模型会话,当我想在不打开付费控制台的情况下拿到一段草稿命令或简短解释时用它。第二个证人是免费的服务器会话,当我需要一个既不是我的笔记本、也不是模型想象的 shell 时用它。

我没有记录模型名、配额、机器规格,也没有记录「免费额度下个月还是这样」的承诺。这些细节会变,编造它们只会让笔记面不改色地撒谎。我能重复的是操作顺序,枯燥是故意的,也因此难以造假:提出请求、捕获文本、对文本做哈希、运行白名单命令、把退出码写在哈希旁边。

**退出码(exit code)**是操作系统在进程结束时返回给父进程的一个整数状态值,约定上 0 表示成功、非 0 表示某种失败或异常。它之所以能作为「证人」,是因为它由内核在进程实际终止后写入,而不是由语言模型根据上下文生成——模型可以编造「已通过」这四个字,却无法伪造一个它没有启动过的进程的退出码。在文章提到的 Python 中,subprocess.run 会把子进程的退出码放进 returncode 字段;sys.exit(n) 或 raise SystemExit(n) 则用于主动设置返回值。需要注意的是,退出码只反映「这个进程是否按其自身标准判定成功」,它不保证行为正确:一个语法检查工具返回 0,只说明它没有发现语法错误。因此文章才坚持把「job key + 退出码 + 主机身份」写在同一行,让读者能判断这个 0 究竟属于哪个命令、在哪台机器上产生。

我反复问自己的一个问题

每当回复听起来「已经完成了」,我就问:聊天之外有没有任何进程真的启动过,并且以我能引用的退出码结束?如果回答不了,建议就留在草稿文件夹里,哪怕措辞再干净。免费服务器在这里也不是魔法,因为一个免费 shell 可能指向错误的代码树、错误的用户,或者一个从未离开队列的任务。

一个我真的会重跑的记录脚本

下面这个工件是一个小型记录器,不是基准测试,也不是「我给某厂商掐了表」的声明。示例路径只是示例,我会在拿到的主机上跑它们,而不是相信一段粘贴过来的成功故事。

核心设计思路:把模型的原始回复先存盘、做 SHA-256;再用白名单里的命令去跑;只有当命令退出码为 0 时,才允许写「已通过」。有人会问:模型说这个脚本已经跑过了,我能直接合并补丁吗?不会,这个脚本存在的意义就是让这种偷懒稍微难一点。

#!/usr/bin/env python3
"""Suggestion ledger. Example workflow, not a recorded benchmark."""

import hashlib
import json
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path

ALLOW = {
    "py_compile": [sys.executable, "-m", "py_compile"],
    "unit_check": [sys.executable, "-m", "unittest", "discover", "-s", "tests", "-q"],
}

def stamp(label, text):
    digest = hashlib.sha256(text.encode("utf-8")).hexdigest()
    return {
        "label": label,
        "sha256": digest,
        "chars": len(text),
        "at": datetime.now(timezone.utc).isoformat(),
    }

def run_allowed(key, extra):
    if key not in ALLOW:
        raise SystemExit(f"refusing unknown job: {key}")
    cmd = ALLOW[key] + list(extra)
    proc = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
    return {
        "cmd": cmd,
        "code": proc.returncode,
        "stdout_sha256": hashlib.sha256(proc.stdout.encode()).hexdigest(),
        "stderr_sha256": hashlib.sha256(proc.stderr.encode()).hexdigest(),
    }

def main():
    suggestion = Path(sys.argv[1]).read_text(encoding="utf-8")
    job = sys.argv[2]
    record = {
        "suggestion": stamp("model_reply", suggestion),
        "run": run_allowed(job, sys.argv[3:]),
    }
    record["agree"] = record["run"]["code"] == 0
    print(json.dumps(record, indent=2))
    return 0 if record["agree"] else 1

if __name__ == "__main__":
    raise SystemExit(main())

命令本身要无聊,无聊是特性

我笔记里真正想要的命令是无聊的,这是特性而不是缺乏野心。先保存原始回复,再编译,然后判断单元测试发现是否真在这台主机的白名单上。模型可以提议一条带管道的安装命令,但记录器不会因为那段文字听起来很紧急就长出一个新 key。凭什么让一段草稿拥有发明 shell 历史的权利?

mkdir -p notes/day1

**SHA-256 哈希**是一种密码学哈希函数,它把任意长度的输入映射成固定 256 位(32 字节)的十六进制字符串,具有雪崩效应和抗碰撞性:输入改动一个字符,输出几乎完全不同,且在实际可行范围内无法找到两个不同输入产生同一输出。文章用它做两件事:一是对模型返回的原始文本取哈希,作为「我收到的就是这段文字」的存证;二是对命令的 stdout 和 stderr 变体取哈希,用于判断两次运行是否产生了相同的输出。哈希只能证明**完整性**——文本没有被悄悄替换或截断——而不能证明内容正确或明智。把哈希写在退出码旁边,就等于同时固定了「哪段声明」和「哪次执行」,让后来的人无法用一段措辞相近的新回复覆盖旧记录。
# Save the raw completion yourself. Do not let the model write this file.
cp reply.txt notes/day1/reply.txt
python3 ledger.py notes/day1/reply.txt py_compile path/to/changed_module.py
echo "ledger_exit=$?"

当服务器和我的笔记本不共享同一棵代码树时,我还留了第二条命令。在相信一个「0」之前,我先打印主机名、用户和可执行文件路径,因为错误机器上的绿色退出码只是另一个故事。这个检查关心的是运行的identity,而不是我对免费机器响应速度的感觉。

python3 - <<'PY'
import getpass, platform, sys
print(platform.node(), getpass.getuser(), sys.executable)
PY
# Sketch only: replace the alias with the host your panel actually shows.
ssh free-host 'python3 -m py_compile path/to/changed_module.py; echo exit:$?'

那条 ssh 行要标注为草图。主机别名 free-host 是占位符,不是我测量过的主机名,你的免费服务器面板可能用别的名字。如果面板不给你 shell,这个分支的笔记就到此为止。免费模型仍然可以起草补丁,但它替代不了缺失的退出码。

让回复给自己打分时,发生了什么

社交层面的破口

第一处破口是社会性的,不是技术性的,它在段落说「检查已通过」的瞬间就出现了。服务器任务还没启动,可我的手指已经想在建议的哈希旁边写上「已验证」。我想给谁留下印象——未来的读者,还是自己的不耐烦?我把那个词删掉,只留下哈希,直到 ledger_exit 真实存在。

命令不匹配,但看起来很相关

第二处破口是命令不匹配,却足够像、足以骗过一个疲惫的笔记者。回复描述的是 unittest,而那一小时我愿意跑的唯一白名单任务是 py_compile,所以一次干净的编译并不是那段话所声称的测试。编译器的 0 不能证明测试通过,模型的总结也不能证明编译器跑过。于是我开始把任务 key 和声明写在同一行,让后来的读者不用猜就能看见落差。

环境层面的静默失败

第三处破口是环境,也是我最愿意重复警告的一次。免费服务器可能以不同的工作目录、不同的用户启动,或者一个队列从未派发我以为已经发出的命令。如果 ledger 文件不见了,我不会从沉默中推断成功,因为沉默正是被丢弃任务伪装自己的方式。进程启动了没有,结束了没有,还是我只是收到了一句关于「结束」的话?

白名单(allowlist)在这里指一份显式的、预先定义好的可执行任务集合:只有键名在 ALLOW 字典里的任务才允许被记录器启动,传入未知键就直接退出并报错。它与黑名单相反,黑名单是「除了这些都能做」,白名单是「只有这些能做」,默认拒绝一切未列出的命令。这种设计针对的是命令注入与信任漂移两种风险:如果记录器直接执行模型回复文本里的任意命令或参数,那么一段听起来紧急的安装指令就可能被真的跑起来;而如果允许在会话中途临时添加新的任务键,就等于让建议者在事后追认自己的权限。文章的规则是把白名单的修改写进另一条笔记、走单独流程,从而把「起草」与「授权执行」在时间上分开。白名单并不能保证列出的命令安全或相关,它只保证未经批准的进程不会因为一段流利的文字而启动。

一张明天还能复用的决策表

我不想给一个假装测量模型质量的分数,因为我从未构建那个实验,也不会伪造行。这张表只决定我的笔记可以说「跑过」,还是必须说「草稿」。列是我能指认的证据,不是对措辞听起来有多自信的感觉。

我手里有什么只有回复只有服务器日志两者都有,且退出码与声明一致
笔记状态draftincompletecandidate
能说 verified 吗不能不能只能针对那个 job key
下一步存哈希,不打补丁重跑或修主机读 diff,然后决定
预期失败流利的假通过错误代码树或未派发仍需审阅 diff

candidate 不等于 merge。它只意味着我白名单里的命令以匹配那个窄声明的方式退出,并且主机我能在笔记里点名。如果回复承诺了测试而我只编译了,那一行就还是 incomplete,编译器再高兴也一样。我会因为模型说得好听就在会话中途扩大白名单吗?不会,那个改动要写进另一条笔记,等我读完新命令再说。

我会重复什么,不会重复什么

我会重复捕获步骤、白名单,以及「没有同一文件夹下的 ledger_exit 就不写 verified」的拒绝。我还会重复主机那一行,因为一个免费服务器只有在我能把它和笔记本、聊天区分开时才有用。免费模型访问是我会保留来起草的部分,尤其在我想在打开付费席位之前,把一段报错栈换个说法时。免费服务器选项是我会保留来运行的部分,因为一个从未遇到 shell 的建议仍然只是建议。

我不会重复任何引用了我当天没有测量过的配额、GPU 名称或延迟数字的句子。我不会把模型命令管道进 shell,也不会让记录器接受回复文本里的任意参数。我不会用这本笔记去评判哪家厂商最好,因为两个可用性开关不是排行榜。如果你的工作需要私有数据集、签名生产主机,或有 on-call 负责人的变更窗口,不要因为回复看起来准备好了,就在免费共享服务器上排练那次变更。

谁应该跳过这套流程

该跳过的人很容易点名,我宁愿点名也不想假装这本 ledger 适合每个团队。如果你自己读不了服务器日志,跳过。如果政策禁止把哪怕是脱敏的报错栈发给外部模型,跳过。如果补丁涉及凭证、生产数据,或一个你无法在下一杯咖啡之前回滚的服务,跳过。如果你需要合规证明,跳过——现场笔记不是审计,免费档也不是我乐意凭空编造的合同。

我留在笔记里的限制

这套流程不证明模型理解了代码,也不证明服务器与生产一致。示例里 120 秒的超时是草图的护栏,不是我和任何人谈成的服务水平。py_compile 抓的是语法,不是行为;unittest discover 只在这些测试已经存在、且已经有意义时才有帮助。哈希告诉我文本变没变,不告诉我文本是否明智。

我也无法告诉你免费模型列表或免费服务器按钮下周是否还在原位、限制是否一样。去看你手上真正有的控制台,写下你看到的,而不是把这些笔记当成状态页照抄。如果免费服务器始终不给 shell,就停在草稿,把白名单命令放到你能控制的地方跑。缺失的证人就是一个结果,它在文件里也应该像一个缺失的证人。

如果你已经用 MonkeyCode,有用的部分很普通:需要草稿时走免费模型路径,需要 shell 时走免费服务器路径,把两者的输出放在不同文件里。但在落地任何东西之前,我仍然会亲自读 diff,因为退出码上的同意,不等于这次变更就是我想的那次。这本笔记的意义,是让未来的我能看见什么跑过、什么从未启动、什么我拒绝称之为完成。

#AI代码验证#模型回复不可信#退出码验证#AI编程工作流#免费服务器#ledger记录脚本#AI补丁审核
分享:

相关推荐