Skip to content

个人研究 Agent

本页说明个人研究 Agent 如何在公开网页上做多来源检索与交叉核对,并把用户确认过的偏好和结论沉淀为跨会话记忆。读完本页,你能理解这个场景为什么以 Web Agent 和 GUMem 为核心、什么时候才需要交互式委托,以及哪些内容应该写入长期 Memory。

适用场景

用户经常研究同一类主题,例如硬件选型、竞品动态、论文资料或投资信息,也包括购物决策、择校比较、医疗信息收集这类个人深度调研。这类研究靠单次搜索很难做扎实:来源要交叉核对,结论要带出处,下一轮研究还应该接着上次确认过的结论继续,而不是从零开始。

典型触发时机:

  • 大额购物决策前,需要跨评测站、论坛和官方页面交叉核对参数与真实口碑。
  • 择校或选课前,需要汇总多方信息并区分官方口径和第三方评价。
  • 就诊前后,需要把公开医疗信息整理成带来源的阅读清单供本人和医生参考。

工程挑战

  • 来源可靠性参差:评测站可能有商业倾向,论坛口碑真假混杂,官方页面只讲优点。不做多来源交叉核对,孤证很容易被当成结论交付。
  • 信息时效难以判断:价格、型号、政策随时在变,去年的结论今天可能已经失效。结果不带来源 URL 和采集时间,用户就无法判断该不该信。
  • 长期保存范围不清:预算、判断标准、确认过的结论值得跨会话记住;整页抓取内容和未经确认的说法只属于本次任务。边界不清会让 Memory 变成过期网页的存放处。
  • 交互式确认只在少数情况出现:所有产品调用都需要 GenAuth 委托令牌,但公开网页研究用静默委托即可;只有当研究需要登录订阅数据库、私有论坛等登录态来源时,才需要用户交互式确认"谁在代表用户访问"。

模块组合

模块角色说明
GenAuth核心运行时以静默委托签发短时效凭证(所有产品调用必需);本场景默认无需交互式确认,接入登录态来源或写动作(如发帖、下单)时才升级 interactive。
Web Agent核心通过 WebSearch 做多来源检索与交叉核对,每条结果保留来源 URL 与采集时间,孤证明确标注。
GUMem核心保存个人偏好(预算、地区、关注指标)、用户确认过的结论和研究主题历史——真正需要跨会话延续的记忆。

个人研究 Agent 场景架构

何时需要交互式确认

所有 Web Agent 和 GUMem 产品调用都需要 GenAuth 委托令牌;公开只读的网页研究用静默委托签发即可,不需要企业式的授权确认流程。静默签发的运行时凭证已经提供这个场景需要的约束:凭证短时效、用户可随时撤销、每次调用都带 grantIdauditId 可供追溯。

只有两类情况需要升级为 mode: 'interactive' 交互式委托,让用户在 Qoni Console 明确确认:

  • 登录态来源:研究需要打开订阅数据库、付费评测或私有论坛等用户登录后才可见的页面。
  • 写动作:任务涉及发帖、评论、下单等改变外部状态的操作——本场景默认不包含。

注意:本页示例申请的是产品级委托(products: ['webSearch'])与 GUMem 记忆读写 scope。域名清单这类细粒度边界由 GenAuth 的 Agent Profile 或策略层配置强制执行,不由任务 prompt 承担。完整语义见 Delegate Token 与缩权

工作流程

个人研究 Agent 工作流程

  1. 用户提交研究问题,例如"两万元预算内的相机选型"。

  2. 应用为本次任务获取静默运行时凭证,范围限定在网页搜索与 GUMem 记忆读写。

  3. GUMem 召回相关偏好和同主题的已确认结论,例如预算、地区、关注指标和上次研究的结果。

  4. Web Agent 检索多组查询,覆盖官方页面、评测与社区讨论,每条结果保留来源 URL 与采集时间。

    检查点:单一来源的关键事实应标注"未交叉核对";来源之间互相矛盾时如实呈现分歧,不硬猜。

  5. Agent 汇总结果,为每条结论附引用来源和置信标注;医疗、法律等专业领域的输出注明仅为信息汇总,不构成专业建议。

  6. 用户复查报告,确认哪些结论和偏好值得长期保留。

    检查点:只有用户确认过的偏好和结论才写回长期 Memory;未经确认的网页内容停留在本次任务内。

示例代码

下面的示例使用官方 Qoni SDK@qoniai/qoni)把这个场景接到你的服务端:静默运行时凭证 → 召回偏好与已确认结论 → 一次 webSearch.run() 完成多来源检索 → 应用侧按 JSON 契约校验 → 用户确认后写回 Memory。

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

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

interface ResearchFinding {
  finding: string
  sourceUrls: string[]
  capturedAt: string
  confidence: 'high' | 'medium' | 'low'
  caveats: string[]
}

// 应用侧校验:解析 prompt 约定的 JSON 契约,
// 缺来源 URL 或采集时间的条目直接丢弃
function parseFindings(output: string): { kept: ResearchFinding[]; dropped: number } {
  const items = JSON.parse(output) as ResearchFinding[]
  const kept = items.filter(
    (item) =>
      item.finding &&
      Array.isArray(item.sourceUrls) &&
      item.sourceUrls.length > 0 &&
      item.capturedAt,
  )
  return { kept, dropped: items.length - kept.length }
}

export async function runResearch(userId: string, question: string) {
  // 1. 静默委托签发运行时凭证:所有产品调用必需;
  //    公开网页研究不需要交互式确认
  const { data: grant } = await qoni.delegateToken({
    user: { id: userId },
    agent: 'personal-research',
    products: ['webSearch'],
    scopes: [QoniScopes.GUMEM_MEMORY_READ, QoniScopes.GUMEM_MEMORY_WRITE],
  })

  // 2. 任务前:召回偏好和同主题的已确认结论
  //    首次使用时,先调用 qoni.gumem.createSession 创建这个 sessionId
  const { data: prior } = await qoni.gumem.recall({
    token: grant.token,
    sessionId: `user-${userId}`,
    query: 'budget, region, key metrics, confirmed conclusions on this topic',
  })

  // 3. 一次 WebSearch 完成多来源检索:prompt 明确 JSON 输出契约
  const search = await qoni.webSearch.run({
    token: grant.token,
    prompt: `
      Research: ${question}.
      Cross-check key facts across multiple sources. Return ONLY a JSON
      array of findings, each shaped as
      { "finding": string, "sourceUrls": string[], "capturedAt": string,
        "confidence": "high" | "medium" | "low", "caveats": string[] }.
      Flag single-source facts with a "not cross-checked" caveat and
      present conflicting sources as-is. For medical or legal topics,
      add a caveat that the output is an information digest, not
      professional advice.
      Prior confirmed context: ${JSON.stringify(prior)}
    `,
    maxResultsPerQuery: 8,
  })
  const result = await search.wait()

  // 4. 应用侧校验:无来源的条目丢弃,报告中标注丢弃数量
  const { kept: findings, dropped } = parseFindings(result.output)

  return {
    findings,
    droppedCount: dropped,
    audit: { auditId: grant.auditId, permissionBoundary: grant.grantedScopes },
  }
}

// 5. 由用户在界面复查并确认后调用:只写入白名单字段
//    (确认过的偏好与结论文本),不写原始检索输出
export async function confirmAndRemember(
  grantToken: string,
  sessionId: string,
  confirmed: { preferences: string[]; conclusions: string[] },
) {
  await qoni.gumem.addMessages({
    token: grantToken,
    sessionId,
    messages: [
      {
        role: 'user',
        content: [
          ...confirmed.preferences.map((p) => `Confirmed preference: ${p}`),
          ...confirmed.conclusions.map((c) => `Confirmed conclusion: ${c}`),
        ].join('\n'),
      },
    ],
  })
}

报告的输出结构由 prompt 中的 JSON 契约约定,parseFindings 在应用侧强制执行:缺来源 URL 或采集时间的条目直接丢弃,并在返回值中标注丢弃数量。写回 Memory 由独立的 confirmAndRemember 在用户确认后调用,只接受白名单字段。SDK 顶层只返回通用的 RunResultrunIdstatusoutputartifacts 等)。

记忆策略

  • 进 Memory:用户确认的偏好(预算、地区、关注指标)、已确认结论(带来源指针、置信与时间)和研究主题历史;偏好类记忆按季度衰减,避免旧口味支配新决策。
  • 不进 Memory:整页抓取内容、未经确认的网页说法和一次性比较数据——随任务结束丢弃,需要时重新采集。
  • 更正:结论被新证据推翻时(例如某型号被曝质量问题),把旧结论标记为已失效并指向新记忆,而不是物理删除。

失败处理

情况推荐处理
来源页面需要登录或出现验证码跳过该来源并记录;确需登录访问时另行发起交互式委托,不静默绕过。
关键事实只有单一来源支撑明确标注"未交叉核对",不把孤证当作已核实结论。
结果条目缺少来源或采集时间应用侧校验直接丢弃该条目,并在报告中标注丢弃数量。
召回的历史结论与新证据冲突以新证据为准,把旧结论标记为已失效并写回 GUMem。

生产注意点

不要把网页内容整页写入长期 Memory:只有用户确认的偏好、结论或长期约束才写回,其余抓取内容随任务结束丢弃。医疗、法律等专业领域的研究输出必须注明仅为信息汇总,不构成专业建议,最终决策应交由用户和执业人士完成。报告中的时效敏感数据(价格、政策)应保留采集时间,超过合理时间窗后重新采集而不是直接复用。

下一步