好的,假设你已经开发了一个RAG系统,它能接受用户提问并通过自然语言界面生成回答。默认情况下,它只能处理单个问答,无法处理连续的问题或对话。
比如说,你正在为一个创业孵化器开发一个聊天机器人,并且有一份有关成员即将举行的活动的信息库。你的RAG系统可以回答诸如“与AI领域相关的即将举行的活动有哪些?”或“对于从事汽车行业的初创公司来说,有哪些活动值得参加?”等问题。不过,有时可能需要处理一些后续问题,比如:
Q: 对于在汽车行业工作的初创公司来说,哪些活动值得参加?
A: 以下活动……Q: 这些活动免费参加吗? ← 这也是后续问题
A: 我不知道Q: 这些活动在哪里举办? ← 这也是后续问题
A: 我不知道
如果这些问题单独处理,第二个和第三个答案可能不再相关,因为它们无法获得整个对话和先前答案的上下文。
在这篇短文中,我想介绍我是如何解决这个问题的。主要有三种方法来保持对话记录,
- 在提示中加入历史背景,同时包含指导方针和用户的相关询问。我称其为“单纯的方法”。
<…>
Q: 问题1?
A: 回答1。
Q: 问题2?
A: 回答2。
Q: 新用户的问题?
<…>
- 使用API自然对话的流程,就像这样:
3. 利用一种综合的方法,包括压缩对话历史记录,并将压缩后的对话记录作为用户提问的背景。我称这种方法为“智能模式”。
我们一个个来。
看看简单的方法让我们简要地探讨一些解决这个问题的方法。最常见的方法是:“我们就直接提供整段对话加上最后一个问题。”
优点:实现起来很简单。
缺点:简单的、容易出错的实现充满陷阱。我将在下面称这些问题为陷阱,这些问题。
一:输入的令牌过多。当你进行长时间对话或提供长答案时,这会向模型输入过多的令牌。因此,当对话很长且提供全面回复时,成本会变得很高。这个问题可以通过只保留最后两个问题和答案,或者使用总结来部分解决。
但是两者都会减少上下文,因此它们效率低下,并且带来了新的问题,即丢失细节。总结会压缩知识量,但代价是减少细节。相反,我们应该从对话中提取关键信息,即提取回答最近用户问题所需的关键数据。
但是等一等,API的自然对话流利度呢?
conversation_history = [
{"role": "system", "content": "你是一个乐于助人的帮手。"},
{"role": "user", "content": "对于一家在汽车行业工作的初创公司来说,哪些活动是有益的?"},
{"role": "assistant", "content": "以下是一些活动..."},
# 对话进行中继续添加消息
...
]
response = openai.ChatCompletion.create(
model="gpt-4o",
messages=conversation_history
)
这看来合理,考虑到最近引入的提示缓存技术(OpenAI,Anthropic),这些技术会缓存通过 API 调用提供的对话部分,节省令牌并加快响应生成。然而,另一个问题浮现了……对话越长,花费越多,这几乎不可避免,尤其是在多个用户很少使用的多轮对话中。
生成和利用缓存并不是免费的。你需要考虑用户一两天后返回长时间的对话的情况。到那时,缓存会丢失,导致对话需要重新开始,并且你需要为整个对话的缓存重新生成再次付费(顺便说一下,你可以通过消除过时的部分来优化它,比如,超过12小时的内容)。
无论如何,当我需要快速交流时,我总是用这种方法,因为这是我能迅速回应的唯一办法。
第二个问题:太多不必要的背景,想象一个用户先问了关于正在孵化的初创企业,然后问了关于导师的事情,还问了关于活动的事。所有问题都在同一个对话里,因此语境因此变得模糊且范围过广,可能让AI模型难以理解。
一个“低垂的果实”的解决方案是将历史记录缩减为仅保留最近的2-3条消息。然而,你可能不确定这些消息是否有意义,是否足够让大型语言模型理解上下文并给出一个好的回复。
最后,一个更聪明的策略在测试了前面提到的方法后,我开发了一种更智能的方法来准备问答的背景。
- 如果问题带有对话历史,我会提取最后N个问答对,这些问答对适合给定的上下文大小,但不超过5个最近的问答对(这在大多数情况下已经足够了)。这允许我根据每个答案的长度动态调整考虑的历史大小,在保持令牌经济的同时纳入最近最多的问答对。
- 对于这些提取出的问答对,我向大型语言模型发起一次请求,要求它针对这个问题提取所有可能对回答问题有用的相关信息,并以紧凑的形式返回。这类似于从文本中提取事实或功能,但针对特定问题。此时,我拥有了一个可以用于问题扩展的压缩(最小化)上下文。我们称这个为“压缩对话上下文”。
- 接下来,我将用户的问题从“你能提供它们的注册链接吗?”扩展为“你能提供与汽车行业相关的活动的注册链接吗?”这使得问题独立且包含所有必要信息。为了扩展问题,我添加压缩对话上下文,使大型语言模型能够理解上下文并相应地扩展问题。我将在稍后的另一篇文章中详细解释问题扩展过程。
- 最后,我找到回答问题所需的信息。此时,你可能会发现必要的信息已经在压缩对话上下文中,或者可能需要从知识库中补充更多的数据。如上述示例所示,你需要汇总相关的活动列表,并将其与压缩对话上下文一起放入提示中以合成答案。
我不会深入讲述从数据库中获取必要信息的过程,那是另一个话题了。重点是,你需要做的是:
- 将对话历史压缩成仅包含回答用户问题所需细节的形式(而不是整个对话的摘要)。
- 进一步解释用户的问题,使其更具体,并使用压缩后的对话背景和检索到的信息来完整回答问题(当对话背景不足以充分回答问题时)。
这种方法的好处有:
- 上下文只包含相关的信息
- 包含对话中的细节,而不仅仅是概括
- 以精简的形式呈现
- 当对话上下文足够丰富时(或之后,在补充之后)可以生成答案
不足:
- 它的速度较慢,不适合需要在5到7秒内给出回应的快速对话,因为它需要通过多次大语言模型请求来压缩对话历史、扩展查询范围并准备上下文以生成答案。
有时,对话的上下文可能已经足够回答问题,不需要额外从知识库查信息。因此,你可以优化这一过程,只需要一次请求:先提取上下文,再看看是否能直接回答,如果有足够的信息,就可以直接生成答案了。
通过采用这种方法,你可以在基于RAG的聊天系统中更好地回答后续问题,为用户提供更贴切且符合上下文的回复。
zh: 拜拜!
共同學習,寫下你的評論
評論加載中...
作者其他優質文章