Skip to content

09|先跑通两个实验,再接真实世界 ​

本章对应随书交付的 prototype/ 源码。它解决一个比“自动写一篇文章”更具体的问题:一个建议从哪里来,怎样获得执行资格,执行后如何留下证据,结果不合适时能否恢复原来的页面。

这次实际跑通的是完全离线、使用合成数据的本地预览闭环。第一轮保留一个标题变化,第二轮撤销另一个标题变化。这里的保留与撤销由预设规则和样例指标触发,不代表真实用户更喜欢某个标题,更不是搜索排名、广告收入或因果实验的证明。

9.1 需要什么,运行后会得到什么 ​

准备 Python 3.12 或更新版本,以及一个可写的专用目录。程序只使用 Python 标准库;不必安装依赖,也不必准备 API key。实际验收环境为 Linux、Python 3.12。代码没有把开发机的绝对路径写进导入语句,因此可以把整个项目复制到其他目录再运行。

进入 prototype 文件夹,执行:

sh
python -m seo_loop demo --workdir ./demo-output

终端输出 JSON 报告;工作目录新增数据库、静态站和两份报告。第一次运行应看到两个终态决策:cycle-1 为 keep,cycle-2 为 rollback。actions_used 应为 2,audit_valid 应为 true,live_seo_gain_measured 应为 false。

再执行同一条命令。程序会识别已有的实验结果,跳过已完成的动作,不增加动作计数。这一步验证的是幂等:重复接到同一个任务时,不把同一项修改执行两遍。

静态站需要通过本地服务器查看:

sh
python -m http.server 8000 --bind 127.0.0.1 --directory ./demo-output/site

打开 http://127.0.0.1:8000/。绑定地址限定在本机;用完后按 Ctrl+C 退出。站点包括解释秒/毫秒的指南,以及在浏览器内运行的时间戳转换器。输入不会被发送给统计脚本或广告系统。默认页面禁止索引,示例域是 https://preview.example.invalid;它不是已发布的网站。

构建器用同级版本目录和符号链接切换站点,避免读者看到新旧文件混合的半成品。Linux 已做实际测试。Windows 下符号链接可能需要开发者模式或相应权限;这部分没有实机验收。缺少权限时使用 Linux/WSL,而不是随意删掉一致性保护。

9.2 一次 demo 到底做了哪些事 ​

演练时钟固定在 2026 年的合成时间窗口内。日期固定的目的是让重跑结果一致,不是让过去的批准在真实系统里继续有效。真实接入必须使用可信的当前 UTC 时间,不能由调用者随意回拨时间来延长批准。

  1. 程序建立两个词种子和候选查询,保存用户任务、初始证据强度、竞争成本等合成特征。
  2. 用初始权重计算机会分数,建立 timestamps 主题和两个页面计划。
  3. fixture AI 角色对机会、页面计划和反馈提供结构化分析。输出留在数据库供审查;它们是可重复的模拟响应,没有发生真实模型调用。
  4. 程序把预先写好的教学内容渲染成两个页面,保存内容版本哈希。
  5. 第一轮先保存修改前指标,再产生一个 update_title 建议。通过规则检查后,生成仅限本地合成演练的批准记录。
  6. 执行器再次核对批准、页面版本、证据、预算和冷却时间,只改标题与更新时间,并保存旧版本。
  7. 程序读取第一轮修改后指标。CTR 相对变化超过样例门槛,任务成功率没有超过允许的下降幅度,于是保留。
  8. 第二轮重复同样流程。指标触发撤销规则,程序恢复第一轮保留后的页面对象,并重新构建静态站。
  9. 最后导出决策、机会反馈和审计快照。

这里的 AI 主导体现在多个分析节点共用结构化输入输出,而写入权限始终由确定性程序掌握。原型没有让模型从零搜索互联网、核验事实、写完整长文再自动生产发布。页面正文是教学脚手架;机会评分也是透明的固定公式。这样能独立测试决策和执行边界,不把“模型写出了看似合理的文字”混同于系统已经可靠。

初始机会分数为 0.5 × task_fit + 0.3 × evidence_strength + 0.2 × (1 − competition_cost)。这三个输入都是 0 到 1 的合成特征,没有查询真实搜索量。该公式仅用于演示排序如何进入后续流程;它不预测收入,也不是推荐给所有站点的通用评分器。

9.3 三条命令及其边界 ​

命令输入与动作产物与验收失败时怎么处理
demo --workdir DIR使用固定合成数据,建立状态并执行两轮本地实验两个终态决策、静态站、report.json、audit.json保留整个目录,查看错误,不要反复创建新目录掩盖问题
status --workdir DIR读取状态并验证审计链JSON 当前状态;不会自动恢复待渲染文件审计链失败时停止后续操作,保留数据库调查
recover --workdir DIR若 render_pending 为 true,从数据库当前页面重新渲染待渲染标记清除,页面与数据库恢复一致修复磁盘空间、写权限或站点目录问题,确认并移除 STOP 后重试

这三条命令都没有生产发布、提交索引、配置广告、读取真实账户或调用付费模型的步骤。CLI 也没有通用“执行任意模型动作”命令。可选真实适配器由 Python 代码单独调用,接入前仍要经过本书第十章的权限和数据验收。

工作目录应只放本次演练内容。site 是指向同级发布目录的链接,不能只复制这个链接就当作站点备份。要带走可直接阅读的静态文件,可以复制其指向的完整目录;要恢复整个实验,还要保留数据库与审计信息。

9.4 数据库如何把动作连成一条证据链 ​

SQLite 是本地权威状态,JSON 报告只是某一刻的导出。数据库使用 WAL 日志和完整同步设置;引擎在工作目录持有操作系统文件锁,拒绝另一个引擎同时修改同一目录。进程异常退出后锁由操作系统释放,锁文件可以继续存在,不要仅凭“文件还在”判断任务仍在运行。

表核心字段用途
entitieskind, id, data, hash不可原地改写的关键词、证据、事实、页面计划、建议、决策等 JSON 对象
pagesid, version, data每个页面当前生效的对象
versionshash, page_id, data页面历史对象,供核对与回滚
metricsid, revision, page_id, data, hash同一观测窗口的不同数据修订版
approvalsid, proposal_hash, page_id, action, version, expires_at, scope, used_by批准绑定与单次消费记录
actionsid, proposal_hash, page_id, before_hash, after_hash, status, data执行过什么、改前改后是什么、目前结果如何
experimentsid, page_id, status, data观察中或已终结的实验
auditseq, event, at, data, previous_hash, hash按顺序关联的事件哈希链
metakey, value动作预算、冷却时间、待渲染标记等运行状态

entities 中同一种对象和同一个 ID 如果出现不同内容,程序会拒绝覆盖。指标则允许增加 revision 保存修订;相同 ID、相同修订号却改变数据会被拒绝。哈希基于稳定排序的 JSON 计算,便于重复核验。

哈希链能发现部分不一致或意外篡改,不能替代外部可信日志。拥有数据库写权限的人也可能重建整条链。生产环境仍需权限隔离、异地备份、保留策略和独立审计;原型没有把 SQLite 变成不可伪造的公证系统。

页面状态的流转很短:动作记录先是 applied,相应实验是 observing。数据够用后,二者进入 kept 或 rolled_back。如果数据缺失、过旧或不足,判定为 wait,实验仍留在 observing;程序不会用零补齐,再装作观察到了下降。

9.5 模型建议为什么不能直接变成写入 ​

执行器只认识如下字段,不能增加路径、脚本、任意 URL 或部署参数:

json
{
  "id": "cycle-1",
  "page_id": "timestamp-tool",
  "action": "update_title",
  "expected_version": "由当前页面计算的64位SHA-256值",
  "title": "Unix timestamp converter: seconds, milliseconds and UTC",
  "evidence_ids": ["evidence-timestamp-tool"],
  "hypothesis": "Explicit task and unit wording may help fixture users choose the relevant page."
}

上面是字段说明,不是可直接执行的合法样例,因为 expected_version 必须是实际哈希。examples/proposal.json 是演练中导出的合法实例。

校验按几层进行:先确认严格 JSON 没有重复键、非有限数值或额外字段,再检查字段类型、ID 格式和动作白名单。标题必须是受限长度的纯文本;不能带标签、换行或空字符。随后读取数据库,确认页面存在、目标版本没有变化、证据属于同一页面且标明合成性质。

批准绑定的是整个建议内容的哈希,另保存目标、动作、版本、失效时间和范围。只要标题、假设或证据 ID 中任何一项改变,原批准都不能用于这份新建议。批准过期、已消费或指向别的页面时,同样停止。完全相同的动作 ID 重试返回已记录结果;相同 ID 配上不同内容则拒绝。

这些检查防止建议越权,但不能判断一个标题是否误导,或引用是否真的支持结论。模型分析里的“为什么”仍要接受编辑核验。真实发布前,应让具备权限的人看到目标、差异、证据和风险,并由可信服务签发批准;不要沿用 demo 自动生成本地批准的方法。

9.6 什么样的数据才允许作判断 ​

核心指标对象包含 id、page_id、version_hash、observed_at、period_start、period_end、final、revision、impressions、clicks、task_successes、task_attempts、synthetic。时间必须带时区,次数必须是非负整数,点击不能超过展示,任务成功次数不能超过尝试次数。

原型把 final 当作上游已经完成的数据成熟度判断。在真实来源中,不同 API 对最终状态的支持不同;不能因为这个字段存在,就认为 GA4 天然提供同样的最终状态标志。真实适配层还要保留抽样、隐私阈值、分页截断、来源时区、查询过滤和数据修订信息。第七章说明了这些差异;核心演练没有把所有外部质量标志平铺进指标对象。

开始修改之前,程序要求修改前数据属于当前页面版本、标明最终状态、没有来自未来、窗口足够长且有足够样本。修改后评估还要确认两组窗口等长且不重叠、版本一致、修改后窗口不跨过修改时间。数据过旧、低样本、分母为零或仍在修订时,不自动保留或撤销。

这份初始策略使用:至少 100 次展示、20 次任务尝试、7 天窗口,数据窗口结束距判断时间不超过 14 天。同页动作间隔至少 7 天。保留要求 CTR 相对变化至少 10%,任务成功率下降不超过 2 个百分点。这些数字是让演练分支可测试的初始门槛,不是统计功效计算,也没有季节性校正或对照组。

因此,“达到门槛”只能说明这次合成输入经过了规则,不说明真实变化由标题造成。生产中的低流量站点可能需要更长观察窗口;快速修改也可能让归因更难。应预先选择主要目标、保护指标、最小可检测变化和停止规则,而不是见到数据后再挑有利门槛。

修改前指标若出现新修订,原实验暂停,要求重新建立基线并审查。已结束的实验不会被悄悄改写;如果后来的指标修订使当时依据变化,status 的 data_quality_alerts 会提示人工复核。这保护了历史决策的可解释性,也避免把“当时合理”混成“永远正确”。

9.7 故障演练:确认程序停在哪里 ​

先运行全部自动测试:

sh
python -m unittest discover -s tests -v

只运行核心测试时使用:

sh
python -m unittest discover -s tests -p test_core.py -v

测试在临时目录中制造故障,不需要修改你的正式演练数据库。以下是核心用例和可观察的结果:

故障用例应看到什么
重复触发同一工作流test_double_run_idempotency两次报告一致,动作仍为 2
同一动作 ID 换了内容test_changed_id_is_rejected拒绝,不覆盖旧动作
批准后改标题test_approval_binds_content哈希不一致,拒绝写入
批准过期/页面已变test_expired_approval、test_stale_page_version拒绝陈旧批准或陈旧版本
缺少、暂定、过旧指标test_missing_metrics_wait、test_provisional_metrics_wait、test_stale_metrics_wait留在观察态,不伪造零值
样本过少/分母为零test_low_volume_wait、test_zero_denominators_wait暂停判断,不出现除零或假结论
修改前数据被修订test_revised_baseline_wait要求重新审查基线
完成后数据被修订test_terminal_revision_reported历史结论不改写,报告新增告警
目录穿越、脚本、额外动作test_unsafe_path、test_shell_action_and_extra_fields模式或白名单校验拒绝
证据不存在/格式错误test_missing_evidence、test_invalid_evidence_type_rejected无法得到执行资格
冷却期、预算、停止开关test_cooldown、test_budget、test_kill_switch禁止新的实验动作
修改入库后构建崩溃test_crash_recovery保留待渲染标记,恢复后不重复扣预算
需要回滚test_rollback_restores_old_artifacts页面哈希和全站文件字节恢复到实验前
同目录启动第二个引擎test_workdir_lock第二个引擎停止,避免状态互相覆盖
审计数据被改写test_audit_tamper_detected验证失败,报告不能继续冒充完整证据链
重试一直超时test_retry_bounded达到次数上限后返回错误

停止开关也可以手动演练。在 demo-output 目录创建一个空文件,名称必须是 STOP。再运行 demo,程序应明确报告停止。确认后删除这个文件,再运行即可恢复。它阻止实验操作和待渲染恢复,并不会卸载已启动的 HTTP 服务;如果你还开着服务器,需要另外按 Ctrl+C。

恢复演练的核心问题是:数据库提交和文件写入不是同一个事务。原型先把希望生效的页面写入数据库,同时标记 render_pending=true;构建器完成原子目录切换后,再清除标记。若进程在中间退出,下次恢复会以数据库为准重新渲染。它不能解决生产 CDN、远程发布和搜索引擎缓存的分布式一致性;那些需要独立的发布状态与回滚协议。

重试只用于有明确幂等性的读取,最多五次。不要把这个工具函数套在付费请求、发信或结果不确定的外部写入上。真实适配器还应区分“确定未执行”和“可能已经成功但响应丢失”,再决定是否重试。

9.8 预算到底限制了什么 ​

核心默认最多四个本地标题动作;演练用了两个。计数与动作入库在同一 SQLite 事务里完成,重复动作不会重复扣减。输入策略中的无穷值、NaN、负数和非法类型会被拒绝,避免门槛被特殊数值绕过。

这个预算没有覆盖真实 token 成本、第三方数据费用、服务器账单或历史站点版本占用的磁盘空间。可选 provider 的调用限制与核心动作预算是不同层。生产系统应将费用、请求额度、任务时长、输出数量和磁盘保留策略分开控制,并把“还有钱”与“还有权限”分别判断。

examples/policy.json 用于说明默认参数;CLI 不自动读取它。需要做程序级演练时,可以读取 JSON 并传给 Engine(workdir, policy=...)。不要改低门槛只为让实验出现“成功”。

9.9 已实测部分与仍然缺少的部分 ​

已实测的范围是:离线运行、SQLite 持久化、严格对象校验、两轮本地实验、重复执行、版本与批准绑定、缺失/修订/低样本处理、停止开关、预算、受限重试、构建恢复,以及按字节核对的回滚。源码旁的自动测试和 qa/core/ 保存了可复核的执行结果。

未实测真实账户登录、API 计费、真实数据导入、生产域名、DNS、CDN、索引提交、搜索收录、广告审批、收入或排名变化。真实适配器的 mock HTTP 测试证明的是接口处理分支,没有证明外部服务在你的账号上可用。没有真实事实语义核验、关键词市场需求验证、随机实验、统计显著性分析或全自动长文生产。

原型也不是多租户系统。虽然同目录锁能防止两个本地引擎同时写入,它没有队列、分布式锁、RBAC、审批签名、跨机器恢复、秘密管理或备份调度。不要把函数可导入理解成这些生产控制已经存在。

下一步应当从一份真实、只读、授权清楚的数据快照开始:保留来源与质量元数据,让规则给出“可判断”或“不能判断”,由人核对后再扩大范围。先验证这些连接是否可信,再考虑增加自动生成数量。

保留各书来源与许可 · 示例运行条件以正文为准