搜索广告文案 Agent
本页说明搜索广告文案 Agent 如何在交互式委托下只读广告后台的 campaign 结构、关键词和 landing page,对照版本化的 claims 规范生成可审查的文案草稿。读完本页,你能理解这个场景需要哪些模块、出价和预算为什么必须留在委托范围之外,以及 claims 规范这类数据为什么应该放在规则库而不是 Memory。
适用场景
Growth 团队需要根据关键词组、竞品广告、landing page 和品牌规则批量生成广告标题与描述。广告后台里的 campaign 结构和关键词是必要输入,但账号同时握着出价、预算和投放开关;把账号交给脚本生成文案,等于把整个投放预算的控制权一并交出去。
典型触发时机:
- 新一批关键词组上线,需要在投放前产出多组标题与描述变体。
- landing page 改版后,现有广告文案与页面信息不再一致,需要批量重写。
- 竞品广告措辞变化,需要参考公开 SERP 更新自家文案方向。
工程挑战
- 变体量大且合规成本高:几十个关键词组 × 标题、描述多个变体,每条都要对照禁用 claims 和 landing page 能支撑的承诺核查,人工审查必然有遗漏,越界 claims 投出去就是合规和平台处罚风险。
- 文案依据难以追溯:每条变体基于哪个关键词组、哪个页面版本、哪条规则生成,散落的表格无法支撑投前审查和事后复盘。
- 借用投手账号权限过宽:广告账号天然带出价、预算和投放开关,而文案任务只需要"只读结构与关键词、产出草稿"这一件事,一次误操作就直接影响真金白银的投放。
模块组合
| 模块 | 角色 | 说明 |
|---|---|---|
| GenAuth | 核心 | 广告后台的只读委托、撤销与审计链;出价、预算和投放动作留在委托范围之外。 |
| Web Agent | 核心 | 受控会话登录广告平台(Profiles 可复用登录态)只读 campaign 结构,抽取 landing page 内容,并通过 WebSearch 查询公开 SERP 和竞品广告。 |
| GUMem | 不使用 | 禁用 claims 与品牌规范属于版本化规则,放在你的规则库(policy store)里按版本注入;历史表现数据属于业务状态,放在你的 analytics 存储——都不属于 Memory。本场景没有需要跨任务沉淀的用户个人偏好。 |
权限与委托边界
Agent 本身不持有任何固有权限。每次文案任务的实际权限是三个集合的交集:用户真实权限 ∩ 本次显式委托范围 ∩ 企业批准边界。落到这个场景:
- 委托范围只覆盖"只读指定账户的 campaign 结构、关键词列表和 landing page,创建文案草稿",不包含修改出价、预算、投放状态或定向设置。
- 委托凭证短时效,单次文案任务建议分钟级有效期,过期后需重新委托。
- 用户或管理员可随时撤销授权;撤销后新的读取或建稿请求立即失败。
- 越权尝试(例如触碰预算或投放开关)会被拒绝并留痕——审计链覆盖全部尝试,不只是成功行为。
注意:SDK 示例申请的是产品级 scope(如 webagent.do_anything:read)。域名清单、页面范围和动作白名单这类细粒度边界由 GenAuth 的 Agent Profile 或策略层配置强制执行,不由任务 prompt 承担;本页示例未展示该配置。完整语义见 Delegate Token 与缩权。
工作流程
用户选择广告账户、关键词组和目标 landing page。
GenAuth 发起交互式委托;用户在 Qoni Console 确认后,服务端回调兑换出最小权限凭证。
应用从规则库读取当前版本的禁用 claims、品牌规范和合规限制,注入任务描述。
Web Agent 登录广告平台,只读现有 campaign 结构和关键词列表。
检查点:本步骤只允许读取;任何指向出价、预算或投放状态的界面动作都应被拒绝并留痕。
Web Agent 抽取 landing page 内容,并通过 WebSearch 查询公开 SERP 和竞品广告作为参照。
Agent 按关键词组生成标题、描述和变体,每条变体对照禁用 claims 检查并标注依据的规则编号。
Agent 输出文案草稿、变体对比和审查备注,附 audit id;应用侧校验后进入 review queue 等待确认。
检查点:任何变体不应包含禁用 claims 或 landing page 无法支撑的承诺;不合规的变体应剔除并说明原因。
示例代码
下面的示例使用官方 Qoni SDK(@qoniai/qoni)把这个场景接到你的服务端:交互式委托(完整 callback)→ 从规则库读取 claims 规范 → 一次 doAnything.run() 只读取数并生成变体 → 解析并校验变体。
import { Qoni, QoniScopes } from '@qoniai/qoni'
const qoni = new Qoni({
accessKey: process.env.QONI_ACCESS_KEY!,
secretKey: process.env.QONI_SECRET_KEY!,
})
export async function startCopyTask(userId: string, taskId: string) {
// 1. 交互式委托:涉及广告后台登录,让用户在 Qoni Console 确认授权
const { data: authorization } = await qoni.delegateToken({
mode: 'interactive',
agent: 'paid-search-copy',
scopes: [QoniScopes.DO_ANYTHING_READ, QoniScopes.DO_ANYTHING_MANAGE],
redirectUri: 'https://app.example.com/qoni/callback',
state: taskId,
user: { id: userId },
expiresIn: 900, // 单次文案任务给分钟级有效期
})
redirectUserTo(authorization.authorizationUrl)
}
// GET /qoni/callback —— 用户同意后在服务端兑换 grant,继续执行任务
export async function handleQoniCallback(request: Request) {
const query = new URL(request.url).searchParams
const { data: grant } = await qoni.completeDelegateToken({
grantId: query.get('grantId')!,
code: query.get('code')!,
state: query.get('state')!,
})
const task = await loadCopyTask(query.get('state')!) // 你的任务存储
return runCopyTask(grant, task)
}
async function runCopyTask(
grant: { token: string; auditId: string; grantedScopes: string[] },
task: { adAccountId: string; landingPageUrl: string },
) {
// 2. 任务前:从你的规则库读取当前版本的 claims 规范(不是 Memory)
const policy = await loadClaimsPolicy() // 例如 { version: '2026-08', forbiddenClaims: [...], voice: ... }
// 3. 一次调用完成取数与建稿:只读结构、抽取页面、生成变体
const run = await qoni.doAnything.run({
token: grant.token,
prompt: `
Sign in to the ad platform and read — read-only — the campaign
structure and keyword lists for account ${task.adAccountId}. Extract
landing page ${task.landingPageUrl}; check public SERPs for competitor
ads. Generate headline and description variants per keyword group.
Return variants as a JSON array of
{ keywordGroup, headline, description, ruleId } objects.
Drop variants with forbidden claims or promises the landing page
cannot support, and note why. Do not change bids, budgets, serving
status, or targeting. Do not submit ads.
Claims policy (version ${policy.version}):
${JSON.stringify(policy)}
`,
capture: { screenshots: true },
})
const result = await run.wait({
// 登录墙 / 验证码 / 风控页:转发给用户,由人处理
onInteraction: (interaction) => notifyUserActionRequired(interaction),
})
// 4. 应用侧解析并校验输出契约:缺少关键词组或规则编号的变体不进 review queue
const variants = parseVariants(result.output).filter(
(v) => v.keywordGroup && v.ruleId,
)
return {
variants,
artifacts: result.artifacts, // 步骤截图,随草稿归档
policyVersion: policy.version,
audit: { auditId: grant.auditId, permissionBoundary: grant.grantedScopes },
}
}输出结构由任务描述约定:这里约定返回 { keywordGroup, headline, description, ruleId } 数组,应用侧 parseVariants 负责解析与校验,缺少 keywordGroup 或 ruleId 的条目被直接丢弃。SDK 顶层只返回通用的 RunResult(runId、status、output、artifacts 等)。
数据与记忆边界
这个场景涉及四类数据,本场景不使用 GUMem:
- 版本化规则:禁用 claims、品牌规范、合规限制——放在规则库(policy store)里按版本管理,每条变体引用规则编号和版本。
- 业务状态:文案变体、审查结论、各关键词组的历史表现——归档到你的投放记录和 analytics 存储,供复盘与追溯。
- 审计记录:
grantId与auditId构成的委托与行为链——由 GenAuth 维护。 - 用户 Memory(可选):只有用户或审阅者明确确认的长期个人偏好才属于 GUMem;本场景的输入都是团队级规则和业务数据,默认不召回也不写回。
失败处理
| 情况 | 推荐处理 |
|---|---|
| 广告平台登录态失效 | 任务挂起,通知用户重新登录,从断点继续。 |
| landing page 抽取失败或内容与关键词不匹配 | 按失败处理并回放会话记录,不基于猜测的页面内容写文案。 |
| 委托范围外的出价或预算修改请求 | 直接拒绝并记录,事后可在审计链中查到未遂动作。 |
| 输出变体缺少关键词组或规则编号 | 应用侧校验直接丢弃该条变体,并在审查备注中标注丢弃数量和依据的规则版本。 |
生产注意点
不要自动提交广告或修改预算。所有文案变体应先进入草稿或 review queue。对广告后台的读取频率应设置上限,避免触发平台风控;文案中的产品 claims 必须能被 landing page 或授权资料支撑,无依据的 claims 不进入草稿。claims 规范更新后应在规则库中发布新版本,避免旧规则继续约束新变体。
下一步
- 阅读 快速开始 跑通 Agent 身份与委托的最短路径。
- 阅读 授权与浏览器沙盒 了解受控会话的安全边界。
- 继续查看 SEO 内容规划 Agent 了解相邻场景。