Skip to content

客服知识 Agent

本页说明客服知识 Agent 如何在客户级委托下查询公开文档和授权可见的工单系统,结合该客户的历史上下文生成带来源的答案草稿。读完本页,你能理解这个场景为什么三个模块都是核心、标准答案为什么以知识库为准而不是记忆,以及客户数据的裁剪边界。

适用场景

客服团队需要 Agent 回答产品问题、排查常见故障,或根据客户历史记录给出更贴合的处理建议。答案素材分散在产品文档、状态页、社区帖和工单系统中,人工逐个翻找拖慢响应;直接把客服账号交给脚本,则工单系统里客户数据的访问范围完全失控。Agent 的产出是给支持团队复核的答案草稿,不直接回复客户。

典型触发时机:

  • 高频问题涌入(例如版本发布后),需要快速产出带来源的标准答案草稿。
  • 疑难工单需要汇总历史相似工单、文档和社区讨论后再答复。
  • 产品公告或已知问题更新后,需要核对现有答案是否已经过期。

工程挑战

  • 答案素材新旧混杂:产品文档、状态页、社区帖各自的更新节奏不同,一条公告发布,散落各处的旧答案就过期了。草稿不带来源链接和时间,客服无法判断每条结论还可不可信。
  • 两类知识容易混淆:标准答案和产品知识是团队维护、按版本更新的知识库(canonical)内容;该客户的环境事实和历史处理记录才是客户上下文。把知识库内容抄进客户记忆,产品一更新就变成一批过期私货,且无法集中修正。
  • 客户数据的裁剪边界:客服账号能看到的工单远多于本次问题需要的。传给网页任务的上下文必须裁剪到本次问题所需,无关客户隐私一旦进入任务就无法收回。

模块组合

模块角色说明
GenAuth核心交互式委托限定在当前客户上下文;撤销与审计链覆盖全部访问尝试。
Web Agent核心查询公开文档、状态页和社区帖(WebSearch 定位来源),并在授权范围内检索相似工单。
GUMem核心只承载该客户的历史问题、处理记录要点和已确认环境事实;标准答案与产品知识属于知识库,不进 Memory。

客服知识 Agent 场景架构

权限与委托边界

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

  • 委托范围只覆盖"读取当前客户相关的工单、历史记录和公开知识来源",不包含回复客户、关闭工单或修改客户数据。
  • 委托凭证短时效,单次答疑任务建议分钟级有效期,过期后需重新委托。
  • 客服人员或管理员可随时撤销授权;撤销后新的工单读取请求立即失败。
  • 越权尝试(例如读取与本次问题无关的其他客户工单)会被拒绝并留痕——审计链覆盖全部尝试,不只是成功行为。

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

工作流程

客服知识 Agent 工作流程

  1. 客服人员打开客户会话并触发答疑任务。

  2. GenAuth 通过交互式委托让客服人员确认授权,签发限定在该客户上下文的委托凭证。

  3. GUMem 召回该客户的历史问题、处理记录要点和已确认环境事实——不召回知识库内容。

  4. Web Agent 查询最新产品文档、状态页和社区讨论,并在授权范围内检索相似工单。

    检查点:传给 Web Agent 的任务上下文必须经过裁剪,只含本次问题所需信息,不携带无关客户隐私。

  5. Agent 汇总各来源信息,生成答案草稿,每条关键结论附来源链接和时间。

  6. 客服人员复核草稿,修改后回复客户;Agent 不直接触达客户。

    检查点:草稿中每条结论都应能回溯到具体来源;引用的文档如与产品公告冲突,标注待核实而不是擅自取舍。

  7. 复核通过的标准答案更新到知识库(由团队维护);该客户的环境事实与处理要点写回 GUMem。

示例代码

下面的示例使用官方 Qoni SDK@qoniai/qoni)把这个场景接到你的服务端:交互式委托(完整回调)→ 召回客户历史 → 一次 doAnything.run() 汇总文档与工单证据 → 应用侧校验答案草稿 → 支持团队复核后写回客户事实。

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

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

interface AnswerDraftItem {
  conclusion: string
  sourceRef: string
  capturedAt: string
}

// 应用侧校验:解析 prompt 约定的 JSON 契约,
// 缺来源引用或采集时间的结论直接丢弃
function parseAnswerDraft(output: string): AnswerDraftItem[] {
  const items = JSON.parse(output) as AnswerDraftItem[]
  return items.filter((item) => item.conclusion && item.sourceRef && item.capturedAt)
}

export async function startAnswerDraft(supportUserId: string, ticketId: string) {
  // 1. 交互式委托:涉及工单系统登录,让客服人员在 Qoni Console 确认授权
  const { data: authorization } = await qoni.delegateToken({
    mode: 'interactive',
    agent: 'support-knowledge',
    scopes: [
      QoniScopes.DO_ANYTHING_READ,
      QoniScopes.DO_ANYTHING_MANAGE,
      QoniScopes.GUMEM_MEMORY_READ,
      QoniScopes.GUMEM_MEMORY_WRITE,
    ],
    redirectUri: 'https://app.example.com/qoni/callback',
    state: `ticket-${ticketId}`,
    user: { id: supportUserId },
    expiresIn: 900, // 单次答疑任务给分钟级有效期
  })
  redirectUserTo(authorization.authorizationUrl)
}

// 客服人员同意后,Qoni 回调你的服务端路由,在这里兑换 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')!,
  })
  const ticket = await loadTicket(parseTicketId(query.get('state')!))
  return draftAnswer(grant, ticket)
}

async function draftAnswer(
  grant: { token: string; auditId: string; grantedScopes: string[] },
  ticket: { id: string; customerId: string; question: string },
) {
  // 2. 任务前:召回该客户的历史问题与已确认环境事实——不是知识库内容
  const { data: customerContext } = await qoni.gumem.recall({
    token: grant.token,
    sessionId: `customer-${ticket.customerId}`,
    query: 'historical issues, confirmed environment facts, handling notes',
  })

  // 3. 一次调用完成汇总:查公开文档、检索授权可见的相似工单;
  //    prompt 明确 JSON 输出契约
  const run = await qoni.doAnything.run({
    token: grant.token,
    prompt: `
      Draft an answer for this support question: ${ticket.question}.
      Search the latest product docs, status pages and community posts,
      and retrieve similar tickets within the authorized scope only.
      Return ONLY a JSON array of draft items, each shaped as
      { "conclusion": string, "sourceRef": string, "capturedAt": string };
      if a doc conflicts with a product announcement, prefix the
      conclusion with "[to verify]" instead of picking a side.
      Draft only: do not reply to the customer, close tickets,
      or modify any customer data.
      Customer context from Memory: ${JSON.stringify(customerContext)}
    `,
    capture: { screenshots: true },
  })
  const result = await run.wait({
    // 工单系统登录态失效等交互:转发给客服人员处理
    onInteraction: (interaction) => notifySupportUserActionRequired(interaction),
  })

  // 4. 应用侧校验:每条结论必须带来源引用与采集时间,缺 sourceRef 或 capturedAt 的直接丢弃;
  //    写回 Memory 不在这里发生,由复核后的 afterReview 单独完成
  const draft = parseAnswerDraft(result.output)

  return {
    draft,
    artifacts: result.artifacts,
    audit: { auditId: grant.auditId, permissionBoundary: grant.grantedScopes },
  }
}

// 5. 支持团队复核工单后调用:只写该客户的环境事实与偏好,
//    不写知识库内容——复核通过的标准答案由团队更新到知识库
export async function afterReview(
  grantToken: string,
  sessionId: string,
  reviewedFacts: string[],
) {
  await qoni.gumem.addMessages({
    token: grantToken,
    sessionId,
    messages: [{ role: 'user', content: reviewedFacts.join('\n') }],
  })
}

答案草稿的结构由 prompt 中的 JSON 契约约定,parseAnswerDraft 在应用侧强制执行:缺来源引用(sourceRef)或采集时间(capturedAt)的结论直接丢弃。写回 Memory 由独立的 afterReview 在支持团队复核工单后调用,只接受复核确认的客户环境事实。SDK 顶层只返回通用的 RunResultrunIdstatusoutputartifacts 等)。

记忆策略

  • 进 Memory:该客户的历史问题、处理记录要点和已确认环境事实(带来源与时间)——只属于这个客户的上下文。
  • 不进 Memory:标准答案、产品知识和故障排查手册。它们是知识库(canonical)内容,由团队按版本维护、任务时注入;抄进 Memory 会在产品更新后变成无法集中修正的过期副本。
  • 更正:客户环境变化导致旧事实失效时(例如客户升级了版本),把旧事实标记为已失效并指向新记忆,而不是物理删除,保证历史答复可追溯。

失败处理

情况推荐处理
工单系统登录态失效任务挂起,通知客服人员重新登录,从断点继续。
各来源答案互相矛盾如实呈现分歧并标注置信度,交客服人员判断,不硬猜。
委托范围外的客户数据请求直接拒绝并记录,事后可在审计链中查到未遂访问。
召回的客户事实与最新工单冲突以本次工单确认的事实为准,把旧事实标记为已失效并写回 GUMem。

生产注意点

标准答案与产品知识以知识库为准:复核通过的答案更新到知识库供全团队复用,不写入单个客户的 Memory。客户隐私数据不要传给不需要的网页任务,Web Agent 只应接收经过裁剪的任务上下文。Agent 的产出始终是给支持团队的草稿,不应配置为直接回复客户;对外答复的最终责任在复核的客服人员。

下一步