Skip to content

认证竞品 Messaging Agent

本页说明认证竞品 Messaging Agent 如何在用户委托下访问授权可见的竞品、渠道或网店页面,并生成可复查的 messaging brief。读完本页,你能理解这个场景需要哪些模块、权限边界如何收窄,以及品牌定位和禁用 claims 为什么应该放在规则库而不是 Memory。

适用场景

Marketing、销售或渠道团队需要查看登录后可见的会员价、区域价、促销、库存、产品 claims 或 partner portal 内容。这类页面没有公开 API,人工巡检又慢又漏;直接把员工账号密码交给脚本,则会把完整账号权限一次性交出去,出了问题也说不清是谁做的。

典型触发时机:

  • 竞品调价或上新,需要在一个工作日内更新 battlecard。
  • 渠道促销窗口开启,需要核对多个网店的会员价和库存。
  • 新市场进入评估,需要汇总登录后才可见的产品 claims。

工程挑战

  • 目标数据在登录墙后且随时变动:会员价、区域促销和库存没有公开 API,页面结构和价格随促销窗口变化,人工核对跟不上 battlecard 的更新节奏。
  • 借用员工登录态风险集中:账号能看的它全能看,研究范围之外的客户数据、订单入口没有任何边界拦截;抓取行为还直接以员工身份留在对方站点日志里。
  • 事实必须带来源与时点:竞品定价随时会改,brief 里没有来源 URL 和抓取时间的"事实"过一周就无法验证,battlecard 的可信度会持续衰减。

模块组合

模块角色说明
GenAuth核心指定竞品域名的只读委托、撤销与审计链;委托只代表当前用户或 workspace 行动。
Web Agent核心受控浏览器会话中由用户完成登录(Profiles 可复用登录态),逐页抽取价格、促销、库存和 claims,每条事实留来源 URL 与截图。
GUMem不使用battlecard 与 messaging playbook(例如被验证有效的对比框架)属于团队知识库,随规则库或 battlecard 存储按版本管理;本场景没有真正的用户级长期记忆。

认证竞品 Messaging Agent 场景架构

权限与委托边界

Agent 本身不持有任何固有权限。每次研究任务的实际权限是三个集合的交集:用户真实权限 ∩ 本次显式委托范围 ∩ 企业批准边界。落到这个场景:

  • 委托范围只覆盖"读取指定竞品域名的页面内容",不包含表单提交、下单或账号设置。
  • 委托凭证短时效,单次研究任务建议分钟级有效期,过期是常态而不是异常。
  • 用户或管理员可随时撤销授权;撤销后新的页面抽取请求立即失败。
  • 越权尝试(例如访问委托范围外的客户隐私页面)会被拒绝并留痕——审计链覆盖的是全部尝试,不只是成功行为。

注意:SDK 示例申请的是产品级 scope(如 webagent.do_anything:read)。域名清单、页面类型和动作白名单这类细粒度边界由 GenAuth 的 Agent Profile 或策略层配置强制执行,不由任务 prompt 承担;本页示例未展示该配置。完整语义见 Delegate Token 与缩权

工作流程

认证竞品 Messaging Agent 工作流程

  1. 用户选择要研究的竞品、渠道或商品清单,并在 Qoni Console 确认本次交互式授权。

  2. GenAuth 为本次任务签发最小权限的委托凭证。

  3. 应用从规则库读取当前版本的品牌定位、竞品映射和禁用 claims,注入任务描述。

  4. Web Agent 打开目标页面;首次访问时用户在受控会话中完成登录,后续任务可通过 Profiles 复用登录态。

    检查点:登录墙、验证码或风控页出现时,Web Agent 应升级给人处理,而不是静默绕过。

  5. Web Agent 逐页抽取授权可见的价格、促销、库存和 claims,并保留来源 URL 与截图;公开补充资料可通过 WebSearch 交叉核对。

  6. 应用侧校验输出契约:缺少来源 URL 的事实直接丢弃。

  7. Agent 输出 messaging brief 与 battlecard 草稿,附来源清单、规则版本号和 audit id;核实后的事实归档到你的 battlecard 存储。

    检查点:brief 中每条竞品事实都应能回溯到具体页面来源;无来源的结论不应进入交付物。

示例代码

下面的示例使用官方 Qoni SDK@qoniai/qoni)把这个场景接到你的服务端:交互式委托(含完整回调)→ 从规则库读取品牌定位与禁用 claims → 一次 doAnything.run() 完成研究 → 解析并校验 brief 事实。

ts
import { Qoni, QoniScopes } from '@qoniai/qoni'

const qoni = new Qoni({
  accessKey: process.env.QONI_ACCESS_KEY!,
  secretKey: process.env.QONI_SECRET_KEY!,
})

// 1. 交互式委托:涉及站点登录,让用户在 Qoni Console 确认授权
export async function startCompetitiveResearch(userId: string, taskId: string) {
  const { data: authorization } = await qoni.delegateToken({
    mode: 'interactive',
    agent: 'competitive-messaging',
    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')!,
  })
  return runResearchTask(grant, await loadResearchTask(query.get('state')!))
}

async function runResearchTask(
  grant: { token: string; auditId: string; grantedScopes: string[] },
  task: { targets: string[] },
) {
  // 2. 任务前:从你的规则库读取当前版本的品牌定位与禁用 claims(不是 Memory)
  const policy = await loadMessagingPolicy() // 例如 { version: '2026-08', positioning: ..., competitorMap: ..., forbiddenClaims: [...] }

  // 3. 一次调用完成研究:登录、逐页抽取、保留来源
  const run = await qoni.doAnything.run({
    token: grant.token,
    prompt: `
      Research the following competitor storefront pages: ${task.targets.join(', ')}.
      Extract member pricing, promotions, inventory and product claims, and
      return facts as a JSON array of { competitor, fact, quote, sourceUrl }
      objects — keep the source URL for every fact. Do not buy, submit forms,
      or change any account settings.

      Messaging policy (version ${policy.version}):
      ${JSON.stringify(policy)}
    `,
    capture: { screenshots: true },
  })

  const result = await run.wait({
    // 登录墙 / MFA / 风控页:转发给用户,由人完成
    onInteraction: (interaction) => notifyUserActionRequired(interaction),
  })

  // 4. 应用侧校验输出契约:缺来源 URL 的事实直接丢弃
  const facts = parseFacts(result.output).filter((f) => f.sourceUrl)

  return {
    facts, // 核实后归档到你的 battlecard 存储
    artifacts: result.artifacts,
    policyVersion: policy.version,
    audit: { auditId: grant.auditId, permissionBoundary: grant.grantedScopes },
  }
}

输出结构由任务描述约定:这里约定返回 { competitor, fact, quote, sourceUrl } 数组,应用侧 parseFacts 负责解析与校验,任何缺少 sourceUrl 的条目被直接丢弃。SDK 顶层只返回通用的 RunResultrunIdstatusoutputartifacts 等)。

数据与记忆边界

这个场景涉及四类数据,没有一类属于 GUMem:

  • 版本化规则:品牌定位、竞品映射、禁用 claims——放在规则库(policy store)里按版本管理,brief 引用规则版本号。
  • 业务状态:messaging brief、battlecard、已核实的定价事实与截图——归档到你的 battlecard 存储,带来源指针和抓取时间。
  • 审计记录:grantIdauditId 构成的委托与行为链——由 GenAuth 维护。
  • 用户 Memory:本场景不涉及。团队确认的对比框架和 messaging 经验属于团队共享的 playbook / battlecard 知识库,随版本管理演进,不是用户级长期记忆。

失败处理

情况推荐处理
登录态失效任务挂起,通知用户重新登录,从断点继续。
目标页面结构变化导致抽取失败按失败处理并回放会话记录,不输出无证据的结论。
委托范围外的页面请求直接拒绝并记录,事后可在审计链中查到未遂访问。
输出事实缺少来源 URL应用侧校验直接丢弃,不进 brief 与 battlecard。

生产注意点

Agent 不应绕过访问控制、购买商品、提交表单、修改账号设置或复制超出授权范围的数据。用于研究的账号与登录态必须是企业有权使用的——不要用个人账号或来路不明的凭证做竞品研究。抽取行为应遵守目标站点的服务条款,超出 ToS 允许范围的抓取应在法务确认前停止;对研究对象的抓取频率应设置上限,避免对目标站点造成压力。

下一步