个人研究 Agent
本页说明个人研究 Agent 如何在公开网页上做多来源检索与交叉核对,并把用户确认过的偏好和结论沉淀为跨会话记忆。读完本页,你能理解这个场景为什么以 Web Agent 和 GUMem 为核心、什么时候才需要交互式委托,以及哪些内容应该写入长期 Memory。
适用场景
用户经常研究同一类主题,例如硬件选型、竞品动态、论文资料或投资信息,也包括购物决策、择校比较、医疗信息收集这类个人深度调研。这类研究靠单次搜索很难做扎实:来源要交叉核对,结论要带出处,下一轮研究还应该接着上次确认过的结论继续,而不是从零开始。
典型触发时机:
- 大额购物决策前,需要跨评测站、论坛和官方页面交叉核对参数与真实口碑。
- 择校或选课前,需要汇总多方信息并区分官方口径和第三方评价。
- 就诊前后,需要把公开医疗信息整理成带来源的阅读清单供本人和医生参考。
工程挑战
- 来源可靠性参差:评测站可能有商业倾向,论坛口碑真假混杂,官方页面只讲优点。不做多来源交叉核对,孤证很容易被当成结论交付。
- 信息时效难以判断:价格、型号、政策随时在变,去年的结论今天可能已经失效。结果不带来源 URL 和采集时间,用户就无法判断该不该信。
- 长期保存范围不清:预算、判断标准、确认过的结论值得跨会话记住;整页抓取内容和未经确认的说法只属于本次任务。边界不清会让 Memory 变成过期网页的存放处。
- 交互式确认只在少数情况出现:所有产品调用都需要 GenAuth 委托令牌,但公开网页研究用静默委托即可;只有当研究需要登录订阅数据库、私有论坛等登录态来源时,才需要用户交互式确认"谁在代表用户访问"。
模块组合
| 模块 | 角色 | 说明 |
|---|---|---|
| GenAuth | 核心 | 运行时以静默委托签发短时效凭证(所有产品调用必需);本场景默认无需交互式确认,接入登录态来源或写动作(如发帖、下单)时才升级 interactive。 |
| Web Agent | 核心 | 通过 WebSearch 做多来源检索与交叉核对,每条结果保留来源 URL 与采集时间,孤证明确标注。 |
| GUMem | 核心 | 保存个人偏好(预算、地区、关注指标)、用户确认过的结论和研究主题历史——真正需要跨会话延续的记忆。 |
何时需要交互式确认
所有 Web Agent 和 GUMem 产品调用都需要 GenAuth 委托令牌;公开只读的网页研究用静默委托签发即可,不需要企业式的授权确认流程。静默签发的运行时凭证已经提供这个场景需要的约束:凭证短时效、用户可随时撤销、每次调用都带 grantId 与 auditId 可供追溯。
只有两类情况需要升级为 mode: 'interactive' 交互式委托,让用户在 Qoni Console 明确确认:
- 登录态来源:研究需要打开订阅数据库、付费评测或私有论坛等用户登录后才可见的页面。
- 写动作:任务涉及发帖、评论、下单等改变外部状态的操作——本场景默认不包含。
注意:本页示例申请的是产品级委托(products: ['webSearch'])与 GUMem 记忆读写 scope。域名清单这类细粒度边界由 GenAuth 的 Agent Profile 或策略层配置强制执行,不由任务 prompt 承担。完整语义见 Delegate Token 与缩权。
工作流程
用户提交研究问题,例如"两万元预算内的相机选型"。
应用为本次任务获取静默运行时凭证,范围限定在网页搜索与 GUMem 记忆读写。
GUMem 召回相关偏好和同主题的已确认结论,例如预算、地区、关注指标和上次研究的结果。
Web Agent 检索多组查询,覆盖官方页面、评测与社区讨论,每条结果保留来源 URL 与采集时间。
检查点:单一来源的关键事实应标注"未交叉核对";来源之间互相矛盾时如实呈现分歧,不硬猜。
Agent 汇总结果,为每条结论附引用来源和置信标注;医疗、法律等专业领域的输出注明仅为信息汇总,不构成专业建议。
用户复查报告,确认哪些结论和偏好值得长期保留。
检查点:只有用户确认过的偏好和结论才写回长期 Memory;未经确认的网页内容停留在本次任务内。
示例代码
下面的示例使用官方 Qoni SDK(@qoniai/qoni)把这个场景接到你的服务端:静默运行时凭证 → 召回偏好与已确认结论 → 一次 webSearch.run() 完成多来源检索 → 应用侧按 JSON 契约校验 → 用户确认后写回 Memory。
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 顶层只返回通用的 RunResult(runId、status、output、artifacts 等)。
记忆策略
- 进 Memory:用户确认的偏好(预算、地区、关注指标)、已确认结论(带来源指针、置信与时间)和研究主题历史;偏好类记忆按季度衰减,避免旧口味支配新决策。
- 不进 Memory:整页抓取内容、未经确认的网页说法和一次性比较数据——随任务结束丢弃,需要时重新采集。
- 更正:结论被新证据推翻时(例如某型号被曝质量问题),把旧结论标记为已失效并指向新记忆,而不是物理删除。
失败处理
| 情况 | 推荐处理 |
|---|---|
| 来源页面需要登录或出现验证码 | 跳过该来源并记录;确需登录访问时另行发起交互式委托,不静默绕过。 |
| 关键事实只有单一来源支撑 | 明确标注"未交叉核对",不把孤证当作已核实结论。 |
| 结果条目缺少来源或采集时间 | 应用侧校验直接丢弃该条目,并在报告中标注丢弃数量。 |
| 召回的历史结论与新证据冲突 | 以新证据为准,把旧结论标记为已失效并写回 GUMem。 |
生产注意点
不要把网页内容整页写入长期 Memory:只有用户确认的偏好、结论或长期约束才写回,其余抓取内容随任务结束丢弃。医疗、法律等专业领域的研究输出必须注明仅为信息汇总,不构成专业建议,最终决策应交由用户和执业人士完成。报告中的时效敏感数据(价格、政策)应保留采集时间,超过合理时间窗后重新采集而不是直接复用。
下一步
- 阅读 快速开始 跑通 Agent 身份与委托的最短路径。
- 阅读 WebSearch 了解多来源检索的工作方式。
- 继续查看 行程规划 Agent 了解相邻场景。