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。 |
何时需要交互式确认
所有产品调用都需要 GenAuth 委托令牌;公开只读场景用静默委托即可。本场景的默认路径只做公开网页调研:应用用与 Qoni 凭证绑定的 GenAuth 用户 ID 直接换取运行时凭证,不需要用户跳转确认。凭证是显式、短时效、可撤销的,只覆盖公开读取与任务执行;修改内部资料、导出客户明细和对外发布不在任何委托范围内。
需要升级为交互式确认(mode: 'interactive')的情况:
- 需要 Agent 在受控会话中读取内部资料库或文档系统的授权可见页面,而不是由应用注入摘要。
- 调研要接入登录态页面或付费数据源。
升级方式与其他场景相同:mode: 'interactive' + redirectUri,用户在 Qoni Console 确认后由服务端 completeDelegateToken 兑换凭证,完整流程见 快速开始;受控读取内部资料的完整示例见 产品发布 Messaging Agent。
工作流程
用户选择产品、受众和 campaign 目标。
应用以静默委托换取运行时凭证(公开调研,无需用户跳转确认)。
应用从 docs、CRM 和 campaign 记录读取产品资料、客群画像和历史经验,作为显式输入注入任务。
检查点:内部资料只作为输入使用;未发布产品信息不应出现在后续任何公开网页任务的查询内容中。
Web Agent 通过 WebSearch 扫描行业趋势和公开报告,每条结果保留来源 URL 和发布时间。
Web Agent 逐页调研竞品页面,交叉核对市场信息;调研进度以事件流实时返回。
Agent 汇总内外部输入,生成 brief 草稿:目标、受众、核心信息、渠道建议、假设和风险边界。
Agent 输出 brief 草稿、来源清单和待确认问题,附 audit id;应用侧校验后交负责人确认。
检查点:brief 中每条外部事实都应能回溯到具体来源;无来源支撑的结论应标记为假设,不作为事实进入交付物。
示例代码
下面的示例使用官方 Qoni SDK(@qoniai/qoni)把这个场景接到你的服务端:静默委托 → webSearch.run() 扫描市场 → 一次 doAnything.run() 深入竞品页面并汇总 brief,用 run.events() 事件流实时转发调研进度 → 解析并校验事实与假设。
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 记录中读取并显式注入,结论写回同一套业务存储。
- 审计记录:
grantId与auditId构成的委托与行为链——由 GenAuth 维护。 - 用户 Memory(可选):只有用户明确确认的长期个人偏好才属于 GUMem;客群画像和 campaign 经验是团队级业务数据,不是个人记忆,本场景默认不召回也不写回。
失败处理
| 情况 | 推荐处理 |
|---|---|
| 内部输入读取失败 | 按缺失输入处理并提示用户补充,不降级为猜测。 |
| 公开来源之间市场信息互相矛盾 | 并列列出冲突来源和时间,标记为待确认,不擅自裁决。 |
| 事件流断线 | SDK 按 sseMaxRetries 自动重连续传;超出上限时按失败处理并回放已收到的事件。 |
| 输出条目标为事实却缺少来源 | 应用侧校验降级为假设或直接丢弃,并在 brief 中标注处理数量。 |
生产注意点
不要把未发布产品信息发送到公开网页任务中。对外 claims 必须标记为待人工确认。brief 中的市场规模、竞品动态等外部数据应保留采集时间,避免过期信息被当作现状引用。内部输入的读取范围应限制在本次 campaign 需要的资料,不要把整个 CRM 导出注入任务。
下一步
- 阅读 快速开始 跑通 Agent 身份与委托的最短路径。
- 阅读 授权与浏览器沙盒 了解受控会话的安全边界。
- 继续查看 产品发布 Messaging Agent 了解相邻场景。