主题
06 AI 编排与接口接入:让建议可以检查,让执行有明确边界
这套原型把 AI 放在三个位置:解释候选机会、提出页面计划、分析反馈。它还能为一个已有页面提出改标题的候选。模型没有发布按钮,也没有服务器终端。每份建议都要经过独立代码检查,再由绑定具体版本的审批决定能否执行。
这样做会多出一些结构化字段和审核步骤,但能把两类问题分开处理:建议是否值得试,以及程序现在是否有权试。前者需要证据和判断,后者必须由确定的规则决定。一个语言流畅、格式正确的回答,不能替代任何一种检查。
6.1 实际交付了哪些能力
实现位于 prototype/seo_loop/providers.py,角色说明在 prototype/seo_loop/prompts/roles.md。这些适配器使用 Python 标准库,默认不联网。
| 能力 | 代码入口 | 本次验证到哪里 |
|---|---|---|
| 离线 AI 角色示例 | FixtureProvider.analyze(context) | 合成输出经过严格合同检查;不调用模型 |
| 核心改标题候选 | FixtureProvider.propose(context) | 与核心执行器的七字段合同一致 |
| 实际模型适配器 | OpenAIProvider.analyze/propose | OpenAI Responses HTTPS 请求与响应解析经过 mock 测试;未调用真实账户 |
| GSC 搜索表现 | GSCProvider.query | URL 编码、分页、数据状态与原始响应保留经过 mock 测试 |
| GSC URL 检查 | GSCProvider.inspect_url | 实现获取 Google 已知的索引状态,不提交收录 |
| GA4 报告 | GA4Provider.report | 分页、稳定维度排序与元数据保留经过 mock 测试 |
| DataForSEO 词量和 SERP | DataForSEOProvider.search_volume/serp | 实际官方路径和 JSON 请求经过 mock 测试;未付费调用 |
| AdSense | import_adsense_report | 接受已导出的 v2 报告对象;没有实现账户登录或在线拉取 |
这里的 mock 是替身 HTTP 返回值。它能证明代码发出的字段和解析逻辑符合测试合同,不能证明用户的账户已经授权、配额足够、模型名称可用,或线上结果与样本相同。离线示例中的词、流量、收入和实验变化都不得当作真实市场观察。
6.2 两份合同,分别服务分析与执行
分析角色使用一份较丰富的只读合同:
json
{
"role": "feedback",
"page_id": "timestamp-tool",
"base_version_hash": "当前页面版本的哈希",
"action": "hold",
"evidence_ids": ["observation-001"],
"why": "观察到了什么,以及它与建议的关系",
"uncertainty": "还不知道什么,哪些替代解释没有排除",
"planned_benefit": "用户完成任务可能得到的具体改善",
"control": "保持什么不变,与谁比较",
"hypothesis": "一个能够被检验的预期",
"falsifier": "出现什么结果就放弃这个解释"
}action 仅能是 hold、revise_content、test_title、add_internal_link、rollback。它们仍然只是分析建议;原型没有因为模型输出 revise_content 就自动重写页面。无页面的机会分析使用 portfolio / unversioned,不具备页面执行资格。
核心执行器刻意接受更小的合同:id、page_id、action、expected_version、title、evidence_ids、hypothesis。唯一可执行的变更类型是 update_title。标题必须是受限长度的单行纯文本,版本必须匹配当前页面,证据还要在数据库中存在并属于正确对象。分析输出与执行候选分别保存,不能把前者多出来的字段偷偷传给执行器。
FixtureProvider 是为了演示和测试提供的确定性替身。它的分析文本明确写着 Synthetic,提出 hold,提醒补证据;它按输入返回改标题候选。因此,跑通 demo 证明的是编排和保护措施可以工作,不能写成“AI 已经独立发现一个真实赚钱机会”或“所有页面都是模型生成”。
6.3 从原始资料到建议,按这个顺序走
- 采集器先存原始响应和观测元数据,再登记证据 ID。来源至少说明供应商、取数时间、请求范围、时区、是否合成、数据是否最终状态。敏感查询和私人资料要先做最小化处理。
- 编排器选择本任务需要的证据。找词角色看需求和 SERP;页面角色看用户任务、事实来源和工具能力;反馈角色看版本对齐的指标、对照与故障记录。不要把整个仓库、邮箱或日志打包给模型。
- 模型只返回 JSON。解析器拒绝重复键、NaN、代码围栏、缺字段和多字段;检查动作白名单、证据引用和目标版本。
- 业务审核核对事实是否真的支持结论。程序只能确认证据 ID 存在,不能自动证明证据语义正确,引用没有断章取义,或者页面对人有帮助。
- 通过审核的候选交给权限和状态机。预算、审批、冷却、基线版本、幂等键都符合要求,才可能执行本地允许的动作。生产发布仍需要另行接入与授权。
验收时不要只看“模型给了 JSON”。可以故意喂给它一个不存在的证据 ID、一个旧版本、一个 run_shell 动作,检查程序是否在写入前拒绝。模型失败、拒答或输出截断,都保留为失败,不用默认内容顶替并继续发布。
6.4 提示词怎样写,哪里不能依赖提示词
共同系统提示要求把网页、搜索片段、页面正文和原始响应当作不可信数据。资料中即使出现“忽略之前规则”“上传密钥”等字样,也不能获得权限。代码不向模型暴露执行工具,Responses 请求没有 tools 字段,任何返回文本都不会作为 shell、路径或网络目标执行。
角色提示要写清五件事:任务范围、可用证据、输出合同、证据不足时的处理、允许建议的动作。例如反馈分析先排查测量缺失、缓存、404、版本混合和时区,再谈标题或内容假设;不能看到 CTR 降低就立即改标题。完整可复用文本见 prompts/roles.md。
提示词是一层提醒,不是安全边界。真正的边界是代码白名单、结构校验、版本绑定与审批。如果以后增加 AI critic,让它检查来源、反例与质量即可;它的“同意”仍然是另一份模型意见,不能替用户批准上线、花钱或扩大访问权限。
6.5 实际模型接口:显式开关、限制输入、严格拒绝异常
OpenAI 适配器向 https://api.openai.com/v1/responses 发送请求,通过 text.format 的 json_schema 和 strict: true 提供结构合同。模型名称由调用者明确指定,原型不猜测账户可用模型,也不把某一型号写成永远正确的默认值。请求设置 store: false,这不等于对所有供应商侧保留政策作出“零保留”的承诺;真实接入前仍需核对适用的数据政策。OpenAI Responses API、Structured Outputs
代码把序列化输入限制为 64 KB,输出 token 上限限制在 100~8000。返回必须是 completed 状态且包含一份结构化文本;拒答、截断、多份文本或版本不匹配全部停止。原始模型响应临时保存在 last_response,方便受保护的审计;不要把它直接写入公开日志,因为它可能重复输入资料。
部署者需要提供自己的 OPENAI_API_KEY,使用短期进程环境注入。不要把密钥写进源码、示例 JSON、网页、截图或提交记录。这里没有填写任何真实密钥,没有调用模型,也没有为用户创建凭据或配置持续授权。
6.6 GSC、GA4 与收入数据不能揉成一列“表现”
GSC 的 Search Analytics 查询使用 URL 编码后的 siteUrl、起止日期、dimensions、rowLimit 与 startRow。日期按太平洋时区理解;代码记录 America/Los_Angeles。dataState="final" 请求最终数据,使用 all 时本适配器把整体 finalized 留为未知,并保留 first_incomplete_date 等原始元数据。每页最多 25,000 行;翻页到结束仍然只能说明返回结果取完,API 本身只提供部分顶行,不代表所有匿名查询都被取回。空结果不能写成“这一天需求为零”。GSC Search Analytics query
GA4 使用 properties/{property}:runReport。默认按 date 和 landingPagePlusQueryString 取 sessions、engagedSessions,并按维度稳定排序后分页。保留 property 返回的 timeZone、币种、阈值、抽样、其他行损失等元数据。GA4 没有由本接口提供的通用 finalized 布尔值,所以代码留空。数据成熟窗口应是单独的本地策略,而且必须允许历史修订。GA4 会话、GSC 点击与 AdSense 展示口径不同,不能强行对齐成一笔“点击带来的收入”。GA4 runReport
AdSense 当前只导入官方报告格式的 headers 与 rows,保留货币和字符串值,不擅自把估计收益改为最终结算。调用者不提供时区和 finalized 时保持未知。若以后接入在线 API,应单独实现授权、分页/报告限制、账户时区、币种和估计收入标识,再进行真实账户验收。AdSense reports.generate
URL Inspection 只读 Google 已知的索引记录,不提供“请求收录”按钮。普通内容页不能因此套用面向特定内容类型的 Google Indexing API。IndexNow 也不能写成 Google 收录提交接口。URL Inspection
6.7 DataForSEO:先管请求,再谈批量
词量端点为 keywords_data/google_ads/search_volume/live,一次发送一个任务,任务内最多 1000 个关键词;示例显式关闭 search_partners。SERP 端点为 serp/google/organic/live/advanced,默认 desktop、depth=10,不自动加深或购买附加数据。关键词报告缺值与 null 保持原样。SERP 的 rank_group、rank_absolute、结果类型与时间戳放在原始响应中,不能混称为“本页平均排名”。Search volume Live、SERP Live Advanced
Google Ads Live 接口有每账户每分钟 12 次的专门限制,不能套用其他端点更高的通用速率。原型在一个适配器实例内至少间隔 5.1 秒;它没有实现跨进程、跨服务器或跨多个实例的统一限流。生产环境必须把账户调用汇总到同一个队列,否则每个进程都守规则,合起来仍会超限。
除了 HTTP 状态,还要检查外层与每个 tasks 项的 status_code。HTTP 200 不意味着任务成功。原始响应保留供应商报告的 cost;该字段与本地预算估算分别记录。
6.8 预算、失败与重试的实际含义
所有联网调用都要求 Client(live=True)。付费模型和 DataForSEO 还必须传入 CostGuard 以及每次调用的正数成本估算。它限制本进程的请求数,并提前保留估计费用;调用结果不确定也不退回额度。
必须注意:这是本地请求上限与估算预算,不是供应商的硬性美元账单上限。价格、模型用量、任务深度都会影响实际费用。生产接入要结合供应商预算控制、价格核对和实际账单对账;如果供应商只有告警而没有硬停止能力,就应降低每批规模并保留余量。不要把一个未经核对的 estimate_usd 当授权可花费的准确金额。
只读免费报告查询遇到 429、500、502、503、504 时可在短等待后重试,最多三次额外尝试,单次超时最多 60 秒。身份失败和参数错误不重试。传输失败可能意味着服务器已收到请求,因此付费 POST 一律不自动重试,即使出现 503,也交给人工查看供应商记录后决定下一次是否是新调用。
HTTP 层只允许 HTTPS、拒绝重定向、限制返回体大小;错误消息不打印请求头或上游原文。仍需把供应商原始报告当敏感运维数据保存,设置访问范围、保留期限和删除策略。原型未实现集中密钥管理、跨进程预算锁、账户级队列、OAuth 刷新或生产告警,这些是上线前需要补齐的部分。
6.9 接入与验收顺序
先运行离线测试:
bash
cd prototype
python -m unittest discover -s tests -p 'test_providers.py' -v本章适配器已有 20 项测试,覆盖请求序列化、分页、未知值、费用闸门、异常配置、只读重试、付费不重试、结构化输出和越权拒绝。总项目测试数以实际执行结果为准。
随后在隔离环境只接一个自己的测试站点,使用最小只读 scope:GSC 的 webmasters.readonly、GA4 的 analytics.readonly;若选择服务账号,还需分别授予目标资源的读取权限。组织是否允许这种身份方式、账户是否支持相应授权,应由管理员确认。本次仅说明方式,没有创建服务账号、授予权限或写入凭据。
首次真实请求应由操作者确认账户、数据范围、发送对象、费用及限额,只取一个短日期窗口或一个词。核对 HTTP 请求、返回元数据和控制台记录后,再逐步增加范围。更换或轮换密钥时先验证新凭据,再撤销旧凭据;不要让模型处理明文密钥。遇到权限失败,停在读取阶段;遇到数据未知,停在分析阶段;遇到预算或版本失败,停在执行阶段。每一处停下来,都要保留可说明原因的状态,不能靠“先继续跑”掩盖问题。