Skip to content

Campaign Brief Agent

本页说明 Campaign Brief Agent 如何以静默委托完成公开市场调研,把应用显式注入的产品资料、目标客群和历史 campaign 与带来源的市场事实组合成 brief 草稿,并以事件流形式返回调研进度。读完本页,你能理解这个场景什么时候才需要交互式确认、外部事实为什么必须带来源,以及产品资料这类数据为什么应该作为显式输入而不是 Memory。

适用场景

Marketing 团队准备新 campaign 时,需要快速整理目标、受众、核心信息、竞品背景、渠道建议和风险边界。内部资料分散在 docs、CRM 和 campaign workspace 中,公开市场信息又需要逐条核对来源;人工汇总一份 brief 往往要跨多个系统反复检索。

典型触发时机:

  • 新季度 campaign 规划启动,需要在一周内产出首版 brief。
  • 产品进入新市场或新客群,需要汇总竞品背景和渠道建议。
  • 上一次 campaign 复盘结束,需要把经验教训沉淀进下一份 brief。

工程挑战

  • 事实与假设混在一份文档里:brief 同时包含带来源的市场事实、内部判断和未经验证的假设,不区分三者的 brief 会把猜测当事实传给下游团队,输出必须逐条标注来源或标记为假设。
  • 公开信息过期快且互相矛盾:市场规模、竞品动态在不同来源之间经常不一致,每条外部数据都需要采集时间和来源 URL,冲突来源要并列呈现而不是擅自裁决。
  • 内部输入与公开查询必须隔离:产品资料、客群画像和未发布信息由应用显式注入任务,一旦任务把这些内容带进公开搜索词,就构成信息外泄。

模块组合

模块角色说明
GenAuth核心运行时以静默委托签发短时效凭证(所有产品调用必需);本场景默认无需交互式确认,接入登录态或写动作时才升级 interactive。凭证短时效、可撤销、留审计链。
Web Agent核心通过 WebSearch 扫描行业趋势与公开报告,再逐页调研竞品页面,每条外部事实保留来源 URL 与采集时间。
GUMem不使用产品资料、目标客群、历史 campaign 经验属于业务数据,由你的应用从 docs、CRM 和 campaign 记录中读取后显式注入任务,不属于 Memory。

Campaign Brief Agent 场景架构

何时需要交互式确认

所有产品调用都需要 GenAuth 委托令牌;公开只读场景用静默委托即可。本场景的默认路径只做公开网页调研:应用用与 Qoni 凭证绑定的 GenAuth 用户 ID 直接换取运行时凭证,不需要用户跳转确认。凭证是显式、短时效、可撤销的,只覆盖公开读取与任务执行;修改内部资料、导出客户明细和对外发布不在任何委托范围内。

需要升级为交互式确认(mode: 'interactive')的情况:

  • 需要 Agent 在受控会话中读取内部资料库或文档系统的授权可见页面,而不是由应用注入摘要。
  • 调研要接入登录态页面或付费数据源。

升级方式与其他场景相同:mode: 'interactive' + redirectUri,用户在 Qoni Console 确认后由服务端 completeDelegateToken 兑换凭证,完整流程见 快速开始;受控读取内部资料的完整示例见 产品发布 Messaging Agent

工作流程

Campaign Brief Agent 工作流程

  1. 用户选择产品、受众和 campaign 目标。

  2. 应用以静默委托换取运行时凭证(公开调研,无需用户跳转确认)。

  3. 应用从 docs、CRM 和 campaign 记录读取产品资料、客群画像和历史经验,作为显式输入注入任务。

    检查点:内部资料只作为输入使用;未发布产品信息不应出现在后续任何公开网页任务的查询内容中。

  4. Web Agent 通过 WebSearch 扫描行业趋势和公开报告,每条结果保留来源 URL 和发布时间。

  5. Web Agent 逐页调研竞品页面,交叉核对市场信息;调研进度以事件流实时返回。

  6. Agent 汇总内外部输入,生成 brief 草稿:目标、受众、核心信息、渠道建议、假设和风险边界。

  7. Agent 输出 brief 草稿、来源清单和待确认问题,附 audit id;应用侧校验后交负责人确认。

    检查点:brief 中每条外部事实都应能回溯到具体来源;无来源支撑的结论应标记为假设,不作为事实进入交付物。

示例代码

下面的示例使用官方 Qoni SDK@qoniai/qoni)把这个场景接到你的服务端:静默委托 → webSearch.run() 扫描市场 → 一次 doAnything.run() 深入竞品页面并汇总 brief,用 run.events() 事件流实时转发调研进度 → 解析并校验事实与假设。

ts
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 draftCampaignBrief(
  userId: string,
  input: { marketTopic: string; competitorPages: string[] },
) {
  // 1. 静默委托:以公开网页调研为主,不涉及站点登录
  //    (需要受控读取内部资料库时才升级 interactive)
  const { data: grant } = await qoni.delegateToken({
    user: { id: userId },
    agent: 'campaign-brief',
    products: ['webSearch'],
    scopes: [QoniScopes.DO_ANYTHING_READ, QoniScopes.DO_ANYTHING_MANAGE],
  })

  // 2. 显式输入:从 docs、CRM 和 campaign 记录读取内部上下文(不是 Memory)
  const internalInputs = await loadCampaignInputs(userId) // 例如 { product: ..., segments: [...], pastCampaigns: [...] }

  // 3. 先用 WebSearch 快速扫描行业趋势与公开报告
  const scan = await qoni.webSearch.run({
    token: grant.token,
    prompt: `
      Find recent market trends and public reports about
      ${input.marketTopic}. Record the source URL and publish date
      for every result.
    `,
    maxResultsPerQuery: 8,
  })
  const marketScan = await scan.wait()

  // 4. 再用一次 doAnything 深入竞品页面,交叉核对并汇总 brief 草稿
  const run = await qoni.doAnything.run({
    token: grant.token,
    prompt: `
      Research these competitor pages: ${input.competitorPages.join(', ')}.
      Cross-check the market scan below and draft a campaign brief:
      goals, audience, messaging, channel ideas, assumptions and risks.
      Return brief items as a JSON array of
      { section, statement, kind: "fact" | "assumption", sourceUrl } objects —
      facts must carry a source URL, unsourced items are assumptions.
      Never include unreleased product details in any search query or
      page visit. Do not edit or publish anything.

      Internal inputs: ${JSON.stringify(internalInputs)}
      Market scan: ${JSON.stringify(marketScan.output)}
    `,
    capture: { screenshots: true },
  })

  // 5. 事件流:调研进度、页面截图和需要人参与的步骤实时转发给前端
  let output: unknown
  for await (const event of run.events()) {
    if (event.type === 'progress') appendTrace(event.data) // 调研到哪个竞品页
    if (event.type === 'screenshot') renderScreenshot(event.image) // 逐页取证截图
    if (event.type === 'interaction') handleInteraction(event.data) // 风控页 / 确认请求升级给人
    if (event.type === 'done') output = event.data.output
  }

  // 6. 应用侧解析并校验输出契约:标为事实却没有来源的条目降级为假设
  const briefItems = parseBriefItems(output).filter(
    (item) => item.kind === 'assumption' || item.sourceUrl,
  )

  return {
    briefItems,
    audit: { auditId: grant.auditId, permissionBoundary: grant.grantedScopes },
  }
}

输出结构由任务描述约定:这里约定返回 { section, statement, kind, sourceUrl } 数组,应用侧 parseBriefItems 负责解析与校验,标为事实(kind: "fact")却缺少 sourceUrl 的条目被直接丢弃或降级为假设。事件流断线时 SDK 使用 Last-Event-ID 自动重连续传;事件类型常量见 QoniEventTypes

数据与记忆边界

这个场景涉及四类数据,本场景不使用 GUMem:

  • 版本化规则:品牌语气与禁用表达如果需要约束 brief 措辞,放在你的规则库(policy store)里按版本注入。
  • 业务状态:产品资料、客群画像、历史 campaign 经验和本次 brief 的结论——由你的应用从 docs、CRM 和 campaign 记录中读取并显式注入,结论写回同一套业务存储。
  • 审计记录:grantIdauditId 构成的委托与行为链——由 GenAuth 维护。
  • 用户 Memory(可选):只有用户明确确认的长期个人偏好才属于 GUMem;客群画像和 campaign 经验是团队级业务数据,不是个人记忆,本场景默认不召回也不写回。

失败处理

情况推荐处理
内部输入读取失败按缺失输入处理并提示用户补充,不降级为猜测。
公开来源之间市场信息互相矛盾并列列出冲突来源和时间,标记为待确认,不擅自裁决。
事件流断线SDK 按 sseMaxRetries 自动重连续传;超出上限时按失败处理并回放已收到的事件。
输出条目标为事实却缺少来源应用侧校验降级为假设或直接丢弃,并在 brief 中标注处理数量。

生产注意点

不要把未发布产品信息发送到公开网页任务中。对外 claims 必须标记为待人工确认。brief 中的市场规模、竞品动态等外部数据应保留采集时间,避免过期信息被当作现状引用。内部输入的读取范围应限制在本次 campaign 需要的资料,不要把整个 CRM 导出注入任务。

下一步