GenAI 用于客户支持 — 第 5 部分:可观察性

作者:来自 Elastic Andy James

本系列将带你深入了解我们如何在客户支持中使用生成式人工智能。加入我们,实时分享我们的历程,本篇文章重点介绍支持助理的可观察性。

本博客系列揭示了我们的现场工程团队如何使用 Elastic stack 和生成式 AI 开发出一个可爱而有效的客户支持聊天机器人。如果你错过了本系列的其他文章,请务必查看:

  • 第 1 部分:构建我们的概念验证
  • 第 2 部分:构建知识库
  • 第 3 部分:为人类设计聊天机器人的聊天界面
  • 第 4 部分:调整 RAG 搜索的相关性
  • 发布博客:用于客户支持的 GenAI - 探索 Elastic 支持助手

我发现可观察性引人注目的地方在于,它在顺境和逆境中都有用。当一切顺利时,可观察性会为你提供指标,以展示你的工作所产生的影响。当你的系统出现问题时,可观察性会帮助你找到根本原因并尽快稳定下来。这就是我们注意到一个错误导致我们从服务器一遍又一遍地加载相同数据的原因。我们在 APM 数据中看到,我们的一个端点的吞吐量远远超过每分钟 100 笔交易,这对于我们的用户群规模来说是不合理的。当我们看到吞吐量降低到更合理的 1 TPM 时,我们可以确认修复已完成。这也是我知道我们在发布后 21 小时完成了第 100 次聊天的原因(你们喜欢看到它)。这篇文章将讨论成功发布的可观察性需求,然后讨论聊天机器人用例(例如我们的支持助理)的一些独特可观察性考虑因素。

关键可观察性组件

你需要三个主要部分。按顺序依次为状态仪表板、警报和里程碑仪表板。我们将进一步探讨这意味着什么,以及我为支持助手的启动所做的安排。这三个组件都需要一个要求:数据。因此,在我们深入研究如何处理数据以获得可操作的见解之前,让我们先看看我们如何为支持助手(以及通常为支持门户)收集这些数据。

可观测性数据收集

我们有一个专门用于监控目的的 Elastic Cloud 集群。我将要讨论的所有可观测性数据都存储和分析在这里。它与我们的生产和暂存数据 Elastic 集群是分开的,我们在那里管理应用程序数据(例如知识文章、爬取的文档)。

我们在为 API 提供服务的 Node 应用程序中运行 Elastic 的 Node APM 客户端,并运行 Filebeat 来捕获日志。我们有一个用于 console.log 和 console.error 调用的包装函数,它将 APM 跟踪信息附加到每条消息的末尾,这允许 Elastic APM 将日志数据与交易数据关联起来。有关此功能的更多详细信息,请参阅 APM 的日志页面。你将在那里找到的关键信息是 apm.currentTraceIds 的存在是为了提供你所需要的。从那里开始没有什么复杂的,只需要一点字符串格式。复制我们的。一份小礼物;来自我的团队,送给你们。

import apm from 'elastic-apm-node';/*** @param message The message to log.* @param args The arguments to forward to `console.log`.*/
export const log = (message: string, ...args: any[]): void => {let msg = message;const traceIds = apm.currentTraceIds.toString();if (traceIds) {msg = `${message} [${traceIds}]`;}console.log(msg, ...args);
};export const logError = (message: string, ...args: any[]): void => {let msg = message;const traceIds = apm.currentTraceIds.toString();if (traceIds) {msg = `${message} [${traceIds}]`;}console.error(msg, ...args);
};

我们使用 Elastic Synthetics 监控功能来检查应用程序和关键上游服务(例如 Salesforce、我们的数据集群)的活跃度。目前我们使用 HTTP 监控类型,但我们正在研究未来如何使用 Journey 监控。基本 HTTP 监控的优点在于,你只需配置要 ping 的 URL、要 ping 的频率以及从哪里 ping。在选择要检查的位置时,我们知道对于应用程序本身,我们想从世界各地的位置进行检查,并且由于有些调用直接从用户的浏览器调用到数据集群,因此我们还会从所有可用位置进行检查。但是,对于我们的 Salesforce 依赖项,我们知道我们只从我们的服务器连接到它,因此我们只从托管支持门户应用程序的位置对其进行监控。

我们还从应用程序数据 Elastic 集群发送 Stack 监控数据,并让 Azure OpenAI 集成通过运行在 GCP 虚拟机上的 Elastic Agent 从该服务发送日志和指标。

设置 Elastic APM

开始使用 Elastic APM 非常简单。让我们以支持门户的 API 服务的 APM 配置为例。

import apm from 'elastic-apm-node';/*** Initialize APM globally for this entire process.** Note: This must be the first thing done before configuring Express.** @param _apm The global `APM` instance.*/
export const initApm = (_apm: typeof apm = apm) => {// Avoid it being started multiple times (e.g., tests)if (!_apm.isStarted()) {// Other configuration related to APM is loaded in the environment_apm.start({serviceName: 'support-portal-api',});}
};

让我们来分析一下其中的一些情况。首先,我们允许自己在测试场景中注入模拟 APM 实例,并且还添加了一层保护以防止多次调用启动函数。接下来,你将看到我们正在使用环境变量来支持大多数配置选项。APM 将自动读取 ELASTIC_APM_ENVIRONMENT 环境变量来填充环境设置,ELASTIC_APM_SERVER_URL 用于 serverUrl 设置,ELASTIC_APM_SECRET_TOKEN 用于 secretToken 设置。

你可以在此处阅读完整的配置选项列表,其中包括可用于配置许多选项的环境变量的名称。我想强调设置 environment 的价值。它使我能够轻松区分来自不同环境的流量。即使你没有运行暂存环境(你确实应该这样做),在本地开发时收集 APM 也会派上用场,并且你将希望能够在大多数情况下单独查看生产和开发数据。能够通过 service.environment 进行过滤非常方便。

如果你在 Elastic Cloud 中运行,则可以按照以下步骤获取用于配置的 serverUrl 和 secretToken 的值。访问你的 Kibana 实例,然后导航到 Integrations 页面。找到 APM 集成。滚动到 APM 服务器部分以找到 APM 代理部分,你将看到包含连接信息的 Configure the agent 子部分。

状态仪表板

数据的实用性取决于你从中提取含义的能力,这就是仪表板的作用所在。使用 Elastic Cloud,默认运行 Kibana 和 Elasticsearch,因此我们的堆栈中已经有一个很棒的可视化层。那么我们想看到什么呢?使用情况、延迟、错误和容量是相当常见的数据类别,但即使在这些类别中,你的特定需求也会决定你想要为仪表板制作哪些特定的可视化效果。让我们来看看我为支持助手发布制作的状态仪表板,以用作示例。

你可能会惊讶地发现左上角的主要空间是文本的主机。Kibana 有一个 markdown 可视化,你可以使用它来添加说明,或者在我的情况下,添加一堆方便的链接,指向我们可能想要跟进仪表板中看到的内容的其他地方。顶行的其余部分显示一些摘要统计信息,例如仪表板时间范围内的聊天完成总数、唯一用户和错误。下一组可视化是时间序列图,用于检查延迟和随时间变化的使用情况。对于我们的支持助手用例,我们特别关注 RAG 搜索和聊天完成的延迟。对于使用情况,我感兴趣的是聊天完成的数量、唯一用户、回访用户以及助手用户与所有支持门户用户的比较。最后两个我留在了图片的折叠下方,因为它们包含我们决定不分享的详细信息。

我喜欢使用仪表板保存默认时间范围。它将其他用户锚定到默认视图,这在他们第一次加载仪表板时通常很有用。我将开始时间戳固定为发布上线时的大致时间,将结束时间戳固定为现在(now)。在发布窗口期间,能够看到该功能的整个生命周期真是太棒了。在某个时候,将存储的时间更新为最近的窗口(例如 “过去 30 天”)可能更有意义。

额外挑战:你能说出我们何时将模型从 GPT-4 升级到更强大的 GPT-4o 吗?

我在状态仪表板上增加了其他区域,重点关注使用最多或遇到最多错误的用户,然后还有一些随时间变化的 HTTP 状态和错误的时间序列视图。你的状态仪表板将有所不同,也应该如此。这种类型的仪表板也具有随着时间的推移而发展的趋势(在我起草这篇文章时,我的仪表板发生了明显的变化)。它的目的是成为回答有关你正在观察的功能或系统的一系列最重要的问题的答案。你将发现重要的新问题,这可能会为仪表板添加一些新的可视化效果。有时,问题变得不那么相关,或者你开始​​意识到它没有你预期的那么有意义,因此你可以将其删除或重新排列在其他项目之下。在我们离开此仪表板之前,让我们先绕道看看我们的聊天完成情况的 APM 跟踪,然后看看我如何使用 ES|QL 创建回访用户可视化。

APM 跟踪

如果你从未见过 Elastic APM 跟踪,那么该图像中可能有很多非常引人注目的事情。标头显示请求 URL、响应状态、持续时间、使用的浏览器。然后,当我们进入瀑布图时,我们可以看到涉及哪些服务和一些自定义跨度的细分。APM 了解此跟踪经过了我们的前端服务器(绿色跨度)和我们的 API 服务(蓝色跨度)。

自定义跨度是监控特定任务性能的好方法。在这种情况下,我们正在流式传输聊天完成,我想知道第一个生成的 token 到达需要多长时间,以及整个完成过程需要多长时间。这些跨度的平均持续时间在仪表板上绘制。这是聊天完成端点的精简片段,重点介绍自定义跨度的开始和结束。

import agent, { Span } from 'elastic-apm-node';const FIRST_GENERATION_SPAN_NAME ='Elastic Support Assistant First Generation';
const FULL_GENERATION_SPAN_NAME ='Elastic Support Assistant Full Generation';async (req, res, data): Promise<void> => {   let firstGenerationSpan: Span | undefined;let fullGenerationSpan: Span | undefined;if (agent.isStarted()) {firstGenerationSpan =agent.startSpan(FIRST_GENERATION_SPAN_NAME) ?? undefined;fullGenerationSpan =agent.startSpan(FULL_GENERATION_SPAN_NAME) ?? undefined;}try {for await (const event of chatStrategy.chat({ ...data })) {if (event.type === ChatbotStreamEventType.GENERATION) {if ((firstGenerationSpan && !firstGenerationSpan?.outcome) ||firstGenerationSpan?.outcome === 'unknown') {firstGenerationSpan.outcome = 'success';}firstGenerationSpan?.end();}writeEvent(res, requestId, event);}} catch (err) {// Error handling} finally {if ((fullGenerationSpan && !fullGenerationSpan?.outcome) ||fullGenerationSpan?.outcome === 'unknown') {fullGenerationSpan.outcome = 'success';}fullGenerationSpan?.end();res.end();}
};

使用 ES|QL 可视化回访用户

当我第一次尝试可视化回访用户时,我最初的目标是得到类似于每日堆积条形图的东西,其中条形的总大小应该是当天的唯一用户数量,细分是净新用户与回访用户。这里的挑战是,计算这个需要重叠窗口(overlapping windows),这与 Kibana 可视化中的直方图工作方式不兼容。一位同事提到 ES|QL 可能有一些工具可以提供帮助。虽然我最终没有得到我最初描述的可视化,但我能够使用它来帮助我处理数据集,我可以在其中生成用户电子邮件和请求日期的唯一组合,然后可以计算每个用户访问了多少个独特的天数。从那里,我可以可视化访问量的分布。这是支持我的图表的 ES|QL 查询。

FROM traces-apm*,logs-apm*,metrics-apm*
| WHERE `service.environment`=="production"
AND `transaction.name`=="POST /api/ai/chat/completions"
AND user.email IS NOT NULL
AND user.email != "some-test-user-to-filter-out@domain.com"
| KEEP @timestamp, user.email
| EVAL date = DATE_TRUNC(1 day, @timestamp)
| EVAL userDate = CONCAT(DATE_FORMAT("yyyy-MM-dd",date),":",user.email)
| STATS userVisits  = COUNT_DISTINCT(date) BY user.email
| STATS vistitCountDistro = COUNT(userVisits) BY userVisits
| SORT userVisits ASC

警报

有了状态仪表板,你就可以快速了解系统当前和一段时间内的状态。可视化中显示的指标本质上是你关心的指标,但你不能也不想整天盯着仪表板(也许发布后的头几天的兴奋让我一直盯着仪表板,但这绝对不是一个可持续的策略)。那么,让我们来谈谈警报如何让我们摆脱仪表板的束缚,同时让我们睡个好觉,因为我们知道如果出现问题,我们会收到通知,而不是在下次追逐盯着那个漂亮的仪表板的甜蜜感觉时才发现。

Elastic Observability 的一个非常方便的地方是,你在制作仪表板可视化时已经弄清楚了制作警报规则所需的细节。你应用的任何过滤器以及你可视化的特定索引中的特定字段都是配置警报规则所需的主要配置详细信息。你实际上是采用可视化定义的指标并添加阈值来决定何时触发警报。

我应该如何选择阈值?

对于某些警报,它可能是关于尝试实现团队定义的某种服务质量。在很多情况下,你希望使用可视化来建立某种预期基线,以便你可以根据你愿意容忍的与观察到的基线的偏差程度来选择阈值。

现在是时候提到你可能计划推迟集成 APM 直到开发过程结束,但我鼓励你尽早这样做。对于初学者来说,这不是一个很大的提升(正如我上面向你展示的那样)。尽早这样做的一大好处是,在开发过程中你可以捕获 APM 信息。通过捕获你可以在预期错误期间调查的详细信息,它可能有助于你在开发过程中调试某些内容,然后它还会捕获示例数据。这对于验证你的可视化(对于涉及计数的指标)以及为延迟等指标类别建立基线值都很有用。

我应该如何收到警报?

这实际上取决于警报的紧急程度。就此而言,有些警报可能需要配置不同阈值的多个警报。例如,在警告级别,你可能只会发送电子邮件,但也可能存在发送标记你团队的 Slack 消息的严重级别。我配置的仅用于电子邮件的非严重警报的示例,与我们接下来将讨论的里程碑仪表板一起使用。通过临时配置警报输出格式以使其立即触发,可以测试警报输出的格式,这是一个好主意。

确定哪些警报以被动方式通知(例如电子邮件)而不是要求立即关注(例如寻呼)的最佳做法是问自己 “是否有一套明确的步骤来响应此警报以解决它?” 如果没有明确的途径来调查或解决警报,那么寻呼某人不会增加太多价值,而只会增加噪音。坚持这一点可能很难,如果你刚刚意识到自己收到一堆无法采取行动的警报,也许可以看看能否想出一种不那么苛刻的方式来显示它们。你不希望的是,无意中训练你的团队忽略警报,因为它们通常无法采取行动。

里程碑仪表板

里程碑仪表板可能不需要与状态仪表板分开,可以将其安排为状态仪表板的一个区域,但我喜欢有单独的空间专注于突出成就。

我最想通过里程碑来强调的两个指标是独立用户和聊天完成量。我发现水平项目符号可视化适合显示具有设定范围和可选目标的仪表。我认为所有时间、过去 7 天和过去 30 天的时间窗口是标准的,但看起来很有趣,所以我并排设置了两列,每行代表不同的时间窗口。底行是按天汇总的条形图,这是一种很好的方式来查看随时间推移的增长情况。

支持助手的特殊注意事项

我们讨论了观察你启动的任何新功能或系统的基本知识,但每个项目都会有一些独特的可观察性机会,因此本博文的其余部分将讨论我们团队在开发支持助手时遇到的一些机会。如果你也在构建聊天机器人体验,其中一些可能直接适用于你的用例,但即使你的项目非常不同,这些想法也可能激发一些额外的可观察性选项和策略。

注意:我即将分享的大多数代码示例来自我们 API 层中的聊天完成请求处理程序,我们向 LLM 发送请求并将响应流回客户端。我将向你展示同一个处理程序几次,但经过编辑,仅包含与当时描述的功能相关的行。

首次生成超时

你可能还记得,在本系列的用户体验 (UX) 文章中,我们选择使用 LLM 的流式响应,以避免在生成完成之前用户需要等待全部内容显示。为了让我们的助手体验更具响应性,我们还设置了一个 10 秒的超时来获取生成文本的首个片段。了解这类错误的趋势对于判断我们的服务是否可靠或是否过载至关重要。我们注意到,在服务启动时,这种超时更容易在同时有较多用户的情况下发生。有时,这甚至会导致重试请求,超出我们为 LLM 服务配置的容量,从而给用户带来更多错误。

APM 代理在我们的服务器上运行,而首次生成的超时设置在运行于用户浏览器中的客户端代码中,因此我开始尝试监听服务器上的事件,以检测客户端发送中止信号的情况,以便可以通过 captureError 向 APM 发送错误报告。但我发现,服务器并未能察觉客户端已中止请求。我尝试监听请求、监听套接字,查阅了网络资源,最终得出的结论是,至少对我们的应用栈而言,服务器没有实用或内置的方法来识别客户端超时。

为了解决这个问题,我将超时和 AbortController 从客户端代码移到了直接与 LLM 通信的 API 层中。现在,当我们遇到超时时,服务器端可以发送错误给 APM,并提早关闭连接,从而能正常地将此情况传播给客户端。

以下是我们的请求处理程序的示例,展示了与首次生成超时相关的部分:

import agent from 'elastic-apm-node';async (req, res, data): Promise<void> => {const generationAbortController = new AbortController();const firstGenerationTimeoutId = setTimeout(() => {generationAbortController.abort();const errorMsg = '[LLM] First generation timed out';// This is the wrapper function I mentioned// that adds trace data to the logged messagelogError(errorMsg);agent.captureError(errorMsg, {// We associate the error with a custom APM Spanparent: streamingSpan,});res.end();}, FIRST_GENERATION_TIMEOUT_MS);// Write the Server-Side Events (SSE) response.try {for await (const event of chatStrategy.chat({...data,abortSignal: generationAbortController.signal,})) {clearTimeout(firstGenerationTimeoutId);writeEvent(res, requestId, event);}} catch (err) {// Clear the timeout on error or else we will also log a timeout// error incorrectly when the timeout expires.clearTimeout(firstGenerationTimeoutId);// Handle errors} finally {res.end();}
};

不幸的是,仅仅关闭来自服务器的连接就会导致客户端出现意外行为。由于没有发回正确的错误信号或任何生成的响应文本,客户端代码没有运行我们退出加载状态的代码部分。为了解决这个问题,我更新了服务器端超时,在对响应调用 end() 之前添加了一个额外的步骤。流式响应通过向客户端发送一系列与生成相关的事件来工作。有 4 种类型:Started、Generation、End 和 Error。通过在关闭连接之前添加一个额外的步骤来发送错误事件,客户端代码能够更新 UI 状态以反映错误。

让我们再次查看包含该内容的处理程序:

async (req, res, data): Promise<void> => {const generationAbortController = new AbortController();const firstGenerationTimeoutId = setTimeout(() => {generationAbortController.abort();const errorMsg = '[LLM] First generation timed out';// Send an error event to the clientwriteEvent(res, requestId, createErrorEvent(errorMsg));res.end();}, FIRST_GENERATION_TIMEOUT_MS);// Write the Server-Side Events (SSE) response.// From here on is the same.
};

首次超时错误是一种非常普通的错误,并且始终记录相同的消息。对于其他类型的错误,有许多不同的故障可能导致到达错误处理程序。为此,我们传入一个参数化的消息对象,以便 APM 将同一错误处理捕获的所有错误分组在一起,尽管错误消息会根据实际发生的错误而有所不同。我们有错误消息、错误代码以及我们使用的 LLM 的参数。

agent.captureError({message: '[LLM] Error generating response with model [%s]: %d %s',params: [model, e?.code, e?.message],
});

拒绝请求

支持助手的目标是提供帮助,但我们希望避免处理两大类输入。第一类是与获得 Elastic 产品技术支持无关的问题。我们认为,既然我们支付了 LLM 服务的账单,我们就不希望人们使用支持助手来起草电子邮件或写歌词,这是非常公平的。我们避免的第二大类是我们知道它无法很好地回答的话题。这方面的主要例子是账单问题。我们知道支持助手无法访问帮助准确回答账单问题所需的数据,当然对于像账单这样的话题,不准确的答案比没有答案更糟糕(销售团队、财务团队和律师都松了一口气 😉)。我们的方法是在用户输入之前向 prompt 添加说明,而不是单独调用第三方服务。随着我们强化需求的不断发展,我们可能会考虑添加一项服务,或者至少将决定是否尝试响应的任务拆分为专门用于做出该决定的单独 LLM 请求。

标准化响应

我不会分享很多关于我们的提示强化方法以及我们在提示中设置了哪些规则的细节,因为这篇博客是关于可观察性的,而且我觉得提示工程的现状还不能让你在不帮助恶意用户绕过它的情况下分享你的提示。话虽如此,我确实想谈谈我在制定 prompt 策略以避免上述两类问题时注意到的事情。

我成功地让它礼貌地拒绝回答某些问题,但它的回答方式并不一致。而且回复的质量也参差不齐。为了解决这个问题,我开始在提示中加入一个标准化的响应,用于拒绝请求。有了预定义的响应,聊天机器人在拒绝请求时可靠地使用了标准响应。预定义的响应存储为其自己的变量,然后在构建要发送到 LLM 的有效负载时使用该变量。让我们来看看为什么这很有用。

监控被拒绝的请求

回到可观察性,通过对被拒绝的请求进行预定义响应,它为我创造了一个机会来检查来自 LLM 的响应,并将其与包含标准化拒绝消息的变量进行比较。当我看到匹配时,我会使用 captureError 来记录它。密切关注被拒绝的请求对我们来说很重要,因为我们希望确保这些拒绝是出于正确的原因。拒绝次数激增可能表明某个用户或一组用户正在试图绕过我们的限制,以将聊天保持在 Elastic 产品技术支持的主题上。

import agent from 'elastic-apm-node';async (req, res, data): Promise<void> => {// Setup custom APM span to track streaming response// Keep track of the generated tokenslet generatedTokens: string[] = [];try {for await (const event of chatStrategy.chat({...data,abortSignal: generationAbortController.signal,})) {clearTimeout(firstGenerationTimeoutId);// Generation events build the array with the response tokensif (event.type === ChatbotStreamEventType.GENERATION) {generatedTokens.push((event.payload as StreamGenerationEvent).content);}writeEvent(res, requestId, event);}} catch (err) {// Handle errors} finally {// Check for a match betweem the generated tokens// and decline messageif (DECLINED_REQUEST_MESSAGE === generatedTokens.join('')) {captureDeclinedRequest(streamingSpan);}res.end();}
};const captureDeclinedRequest = (streamingSpan?: Span) => {const errorMsg = '[LLM] Request declined';logError(errorMsg);agent.captureError(errorMsg, {parent: streamingSpan,});
};

上面显示的策略将所有标记收集到 string[] 中,然后在响应完成时将其连接起来进行比较。我从一位同事那里听到了一个很好的优化建议。不是在流式传输期间收集标记,而是只需跟踪 DECLINED_REQUEST_MESSAGE 中的索引,然后当每个标记进入时,查看它是否与消息的下一个预期字符匹配。如果是,则继续跟踪,但如果没有匹配,你就知道这不是被拒绝的请求。这样,你就不必消耗额外的内存来缓冲整个响应。我们没有看到性能或内存问题,所以我没有更新我的策略,但这个想法太聪明了,不能不在这里提及。

减轻滥用

与上一节关于拒绝请求的内容密切相关,我们知道这些由 LLM 支持的聊天机器人系统可能成为那些想要免费访问 LLM 服务的人们的目标。因为你必须登录并拥有技术支持订阅(包含在 Elastic Cloud 中)才能访问支持助手,所以对于我们特定的发布来说,这不太令人担心,但我们希望以防万一,也许你的用例没有相同的前期限制。我们缓解滥用的两个​​方面是降低聊天完成端点的速率限制,以及功能标志系统,该系统具有灵活性,允许我们配置标志以阻止特定用户或组织访问特定功能。

速率限制

我们的应用程序已经对所有端点设置了通用速率限制,但是该速率限制应该非常宽松,只有在出现问题并导致大量垃圾邮件流量时才会触发。要使速率限制在应用于支持助手聊天完成端点时有意义,它必须是一个低得多的限制。同样重要的是,要让限制足够宽松,这样我们就不会惩罚热情的用户。除了我们与客户进行的 beta 测试的使用数据外,我们还为支持工程师提供了面向内部的支持助手版本,以帮助简化他们回答案例的工作流程。这给了我们一个可以作为使用预期的依据。

我查看了上周的数据,发现我们最繁忙的内部用户平均每天发送 10-20 条聊天消息,而顶级用户一天发送了超过 70 条。我还有延迟指标告诉我平均完成时间为 20 秒。如果不打开多个窗口或选项卡,单个用户一个接一个地快速提问,一分钟内发送的聊天消息不会超过 3 条。我们的应用会话在一小时后到期,因此我决定最好将我们的速率限制窗口与该长达一小时的会话窗口对齐。这意味着单个用户使用单个选项卡时,一小时内的理论最大聊天次数是 180 次。团队同意在一小时内限制 20 次聊天完成次数。这相当于我们最繁忙的内部用户在一小时内发送的聊天次数,同时将任何恶意用户的聊天次数限制为理论最大值的约 11%,具体取决于完全完成的延迟。

然后,我配置了一个警报,在聊天完成端点上查找 HTTP 429 响应,状态仪表板中还有一个表格,列出了触发限制的用户、触发次数以及最近一次示例的时间。我很高兴地报告,自发布以来的头几周,我们还没有任何人达到限制。下一节讨论了如果我们确实看到某些人似乎试图滥用该系统时我们为自己提供的应对选项。

禁用标记

在推出支持助手时,我们与一些精心挑选的客户进行了有限的 beta 测试。为了在开发过程中为部分用户启用支持助手,我们设置了一个功能标记系统来启用功能。随着发布的临近​​,我们意识到我们的功能标记需要进行一些升级。首先,我们希望有默认启用的功能概念(即已完全启动),其次是允许配置标记以阻止对某项功能的访问。这背后的驱动因素是,我们听说一些客户组织可能有兴趣阻止其员工使用支持助手,但我们也认识到,如果我们得出结论认为某个特定用户一直表现不佳,这也可能派上用场,我们可以切断该功能,同时让合适的 Elastic 代表尝试联系并进行对话。

上下文会产生大量有效负载

最后一部分是聊天机器人的特殊考虑,也是可观察性成功案例。在研究我们的状态仪表板时,我们开始看到 HTTP 413 状态代码返回,流量很小,但不可忽略。这意味着我们从浏览器发送的有效负载超过了我们服务器接受的配置大小。然后我们的一位开发人员偶然发现了一个可靠的聊天输入,它重现了它,这样我们就可以确认问题是我们的 RAG 搜索生成的上下文量,加上用户的输入超过了默认限制。我们增加了聊天完成端点接受的有效负载的大小,自从我们发布修复程序以来,我们再也没有看到任何具有 413 响应状态的事务。

值得注意的是,我们扩大接受的有效负载大小的修复实际上更像是一种短期补救措施,而不是长期解决方案。我们计划以更全面的方式解决这个问题,即重构我们编排 RAG 搜索和聊天完成的方式,这样我们就不会将 RAG 结果的全部内容发送回客户端以包含在完成负载中,而是只将 RAG 结果的有限元数据(如 ID 和标题)返回给客户端,然后将其与用户对完成端点的输入一起包含在请求中。完成端点将按 ID 获取搜索结果的内容,并将其与我们的提示和用户的输入相结合,以向 LLM 服务发出请求。

以下是我们为聊天完成端点配置 Express 路由的代码片段。它涉及速率限制、标志和增强的负载大小:

import express, { Express } from 'express';
import rateLimit from 'express-rate-limit';const MAX_CHAT_COMPLETIONS_PER_HOUR = parseInt(process.env.MAX_CHAT_COMPLETIONS_PER_HOUR || '20'
);const aiApis = (app: Express): void => {app.post('/api/ai/chat/completions',rateLimit({windowMs: 60 * 60 * 1000, // 1 hour// Number of requests allowed per IPmax: MAX_CHAT_COMPLETIONS_PER_HOUR,}),// This middleware enforces feature flagsensureHasAiEnabled,// Allow larger payloads for chatbot completionsexpress.json({ limit: '1mb' }), requestWithBodyAndRawResponseHandler);// Declare other AI route configurations
}

结论

理想情况下,可观察性不止一件事。它是多方面的,可以提供多个角度和观点,以创建更完整的理解。它可以并且应该随着时间的推移而发展,以填补空白或带来更深入的理解。

我希望你能从这篇博客中获得的收获是,如何为你的应用程序或功能建立可观察性的框架,Elastic Stack 如何提供实现这些监控目标的完整平台,以及如何将这些内容应用于支持助手的使用场景。遵循这些建议,那么事情就万无一失,你就能顺利推出!

准备好自己尝试一下了吗?开始免费试用。

想要获得 Elastic 认证吗?了解下一次 Elasticsearch 工程师培训何时开始!

原文:GenAI for Customer Support — Part 5: Observability - Search Labs

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/9233.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

python安装selenium,geckodriver,chromedriver,Selenium IDE

安装浏览器 找到浏览器的版本号 chrome 版本 130.0.6723.92&#xff08;正式版本&#xff09; &#xff08;64 位&#xff09; firfox 116.0.3 (64 位)&#xff0c;但是后面运行的时候又自动更新到了 127.0.0.8923 安装selenium > pip install selenium > pip show …

Docker部署SpringBoot项目(镜像部署)

目录 一、在pom.xml 文件中加入依赖 1.依赖内容 2.依赖说明和解释 3.使用流程 4.示例 5.注意 二、执行打包 1.使用命令打包 2.使用IDEA提供快捷方式 三、将jar包上传到服务器 四、创建相关配置 1.创建一个Dockerfile文件 2.添加配置 3.举例 五、生成Docker镜像 1.…

WPF+MVVM案例实战与特效(二十五)- 3D粒子波浪效果实现

文章目录 1、案例效果2、案例实现1、文件创建2. 功能代码实现3、粒子功能应用1、前端布局与样式2、代码解释2、 后端功能代码1、案例效果 2、案例实现 1、文件创建 打开 Wpf_Examples 项目、Models 文件夹下创建 3D粒子模型类 ParticleWaveEffectModel.cs 文件。在Tools 文件…

设计模式之建造者模式(各项装修物料组合套餐选配场景)

前言&#xff1a; 乱码七糟&#xff0c;我时常怀疑这个成语是来形容程序猿的&#xff01; 无论承接什么样的需求&#xff0c;是不是身边总有那么几个人代码写的烂&#xff0c;但是却时常有测试小姐姐过来聊天(求改bug)、有产品小伙伴送吃的(求写需求)、有业务小妹妹陪着改代码(…

ffmpeg视频滤镜:组合两个视频为立体视频- framepack

视频描述 framepack 官方网址 > FFmpeg Filters Documentation 这个滤镜会将两个视频进行组合&#xff0c;有个前提是这两个视频的帧率、分别率必须一样。比如输入的是两个852x480 视频&#xff0c;输出可能是1704*480&#xff08;左右拼接&#xff09;、852*960&#xf…

【K8S问题系列 | 8】K8S集群资源突然爆满导致 Pod 状态变为 Pending 详细解决方案

在 Kubernetes 集群中&#xff0c;当 CPU 突然爆满时&#xff0c;Pod 可能无法获得所需的资源&#xff0c;从而导致其状态变为 Pending。以下是更详细的解决方案描述&#xff0c;有效应对这一问题。 解决方案 1: 扩展集群资源 描述 当集群资源不足以支撑当前的工作负载时&…

第18篇 :深入剖析systemverilog中 randomize 失败案例启示录(一)

经过前面章节的理论学习&#xff0c;我们对systemverilog中的随机约束&#xff0c;有一定的了解&#xff0c;那么&#xff0c;今天开始&#xff0c;着重讲述一些工作中遇到的困惑。主要通过一些例子&#xff0c;层层递进&#xff0c;举一反三&#xff0c;源于实践&#xff0c;剖…

mac端mumu模拟器adb识别不了问题

1.在终端中输入&#xff1a;system_profiler SPUSBDataType,把0x05e3 (Genesys Logic, Inc.)复制 2. 1.cd ~/.android/ 2.open . 3.找到.android/adb_usb.ini文件 将以上格式的Wendor ID放入该文件 3.依次执行 * adb devices* adb kill-server* adb start-server* adb disco…

Ubuntu版本、ROS版本与Python 版本之间的关系

引言 在机器人开发中&#xff0c;ROS&#xff08;机器人操作系统&#xff09;广泛应用于科研和工业领域&#xff0c;支持多个Ubuntu和Python版本。然而&#xff0c;随着不同Ubuntu LTS版本的发布以及Python逐渐从2.x向3.x过渡&#xff0c;ROS的版本选择和兼容性要求也在不断变化…

Linux - 信号

文章目录 一、信号的定义二、查看信号三、产生信号1、指令2、系统调用3、由软件条件产生信号4、异常5、键盘输入 四、保存信号1、补充&#xff1a;信号其他相关概念2、信号保存在哪&#xff0c;怎么保存&#xff1f;3、信号集操作函数 五、捕获信号1、概念2、捕获信号的时机3、…

PMP–知识卡片--项目干系人

项目干系人主要分为两类&#xff1a;参与项目的人和受项目影响的人。按照由近及远&#xff0c;从项目经理、项目团队等逐渐扩充至供应商、客户、监管机构等。 项目往往死在被忽略的干系人手上&#xff0c;作为项目经理&#xff0c;要尽可能地识别出来所有可能影响项目以及受项目…

MATLAB - ROS 2 分析器

系列文章目录 前言 本主题介绍如何连接 ROS 2 网络&#xff0c;分析网络图中所有元素的基本信息&#xff08;如节点名称和节点之间的信息&#xff09;&#xff0c;以及可视化与 ROS 2 节点相关的参数&#xff08;如主题、服务和操作&#xff09;之间的交互。 一、连接并查看 RO…

分组校验在Spring中的应用详解

目录 前言1. 什么是分组校验2. 分组校验的基本原理3. 分组校验的实现步骤3.1 定义分组接口3.2 在校验项中指定分组3.3 校验时指定要校验的分组3.4 默认分组和分组的继承 4. 分组校验的优势和适用场景4.1 优势4.2 适用场景 5. 常见问题与解决方案5.1 校验未生效5.2 无法识别默认…

SDL打开YUV视频

文章目录 问题1&#xff1a;如何控制帧率&#xff1f;问题2&#xff1a;如何触发退出事件&#xff1f;问题3&#xff1a;如何实时调整视频窗口的大小问题4&#xff1a;YUV如何一次读取一帧的数据&#xff1f; 问题1&#xff1a;如何控制帧率&#xff1f; 单独用一个子线程给主线…

[MySQL]索引

索引介绍 索引是帮助数据库高效获取数据的数据结构。在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用数据&#xff0c; 这样就可以在这些数据结构上实现高级查找算法&#xff0c;这种数据结构就是索引。 假设我们有…

window 利用Putty免密登录远程服务器

1 在本地电脑用putty-gen生成密钥 参考1 参考2 2 服务器端操作 将公钥上传至Linux服务器。 复制上述公钥到服务器端的authorized_keys文件 mkdir ~/.ssh vi ~/.ssh/authorized_keys在vi编辑器中&#xff0c;按下ShiftInsert键或者右键选择粘贴&#xff0c;即可将剪贴板中的文…

【大数据技术基础 | 实验八】HBase实验:新建HBase表

文章目录 一、实验目的二、实验要求三、实验原理四、实验环境五、实验内容和步骤&#xff08;一&#xff09;启动HBase集群&#xff08;二&#xff09;编写项目java代码&#xff08;三&#xff09;将代码导出jar包 六、实验结果七、实验心得 一、实验目的 掌握HBase数据模型(逻…

密钥管理服务 (KMS) 故障排除指南

企业客户将密钥管理服务 (KMS) 设置为部署流程的一部分&#xff0c;因为通过该服务&#xff0c;他们可以使用简单、直接的过程在其环境中激活 Windows。 通常&#xff0c;一旦设置了 KMS 主机&#xff0c;KMS 客户端就会自动连接到主机并自行激活。 然而&#xff0c;有时该流程…

CSS的配色

目录 1 十六进制2 CSS中的十六进制2.1 十六进制颜色的基本结构2.2 十六进制颜色的范围2.3 简写形式2.4 透明度 3 CSS的命名颜色4 配色4.1 色轮4.2 互补色4.3 类似色4.4 配色工具 日常在开发小程序中&#xff0c;客户总是希望你的配色是美的&#xff0c;但是美如何定义&#xff…

基于 RNN 的语言模型

基于 RNN 的语言模型 循环神经网络&#xff08;Recurrent Neural Network, RNN&#xff09;是一类网络连接中包含环路的 神经网络的总称。 给定一个序列&#xff0c;RNN 的环路用于将历史状态叠加到当前状态上。沿着时间维度&#xff0c;历史状态被循环累积&#xff0c;并作为…