近日,bilibili 知名科技 UP 主“Ele 实验室”发布了一个视频,标题为“当我开发出史料检索 RAG 应用,正史怪又该如何应对?” 。
视频连续三天被平台打上“热门”标签,并迅速登上科技板块全区排行榜前列。截至目前,视频的观看量近 70 万,评论区讨论异常激烈,很多技术爱好者、开发者都在评论区中提出了技术实现上的问题。作为该视频的技术支持方, Zilliz 最清楚其中的技术细节,今天我们就和大家聊聊这个「 B 站最火 RAG 视频中的应用」——Mr History 是如何炼成的。
01. Mr History RAG 应用的背景介绍
经历了大型语言模型(LLM)快速发展的 2023 年,业内越来越多地讨论起了检索增强生成(Retrieval-Augmented Generation,简称 RAG)技术,这个技术关键之处在于它结合了两个重要的元素:检索和生成。首先,它会通过搜索外部信息源(比如网页或数据库)来收集与问题相关的信息。然后,它会将这些信息巧妙地融入到它的回答中,生成一个更加准确、贴近实际情况的回应。
这意味着无论你提出的问题是关于最新新闻、特定领域知识还是其他任何内容,RAG 都能提供更全面、有深度的答案。RAG 讨论比较多的应用场景主要是在企业内部知识问答,但作为一个技术,我们想在它与人文的交叉点做一些有趣的探索——使用 RAG 进行史料回答的探索,一是探索当前技术在具体的实践中产生的应用场景, 二是想看一下 RAG 在应用时会遇到的具体挑战。
二十四史是从黄帝记载到明代崇祯十七年(1644)的二十四部史书的合称,如果能做一个关于二十四史的问答项目也是一个很有趣的事,毕竟喜欢历史的朋友也往往对一些史实知道个粗略,想去了解一些更加详细的细节。有一些朋友会说去找对应的人物传就好了,但是在纪传体中人物传记并不是主人公的全貌,他的事迹很有可能作为配角分散在他人的传记中。所以 RAG 的用处自然就是根据提问找到散布在全书中的线索,推理出问题的答案。
先看了一下二十四史的数据,就发现了几个很重要的特点:
-
文言文非常喜欢省略主语,比如“高贵乡公即尊位,赐爵关内侯。”这句话就会把主角钟会给漏掉。
-
文言文通常只称呼名,比如“表念同为皇族之情“,从这句话中就需要模型能推理出表是刘表,从而被召回。
-
现代的 embedding 模型大部分都在对齐白话文与白话文,白话文与文言文缺乏对齐训练。
经过以上的初步分析,我们决定先从白话文入手。目标和方法非常的明确,目前社区已经有了大量的 RAG 应用的教程,使用 LlamaIndex(完全可以是其他的 RAG 框架,比如 LangChain等),将文档导入向量数据库 Milvus(https://milvus.io/) 或者 Zilliz Cloud(https://zilliz.com.cn/cloud),接入 ChatGPT 的 API 作为 query_engine,就完成了一个教科书式的关于史料的 RAG 应用,结果如下:
显然,目前的 RAG 系统给出了一个出乎意料的答案。在完成了一个标准的 RAG 后,我们对最终呈现的结果并不满意。当然,对 RAG 的质量评测通常是一个很复杂的系统工程,尤其是在缺乏数据标注的情况下,所以我们采用的是针对个例具体分析的方法来进行调优,根据“奥卡姆剃刀”法则,如果我们针对的个例的调优是简洁优雅的情况下,那针对大部分其他案例都会带来改善,我们先试着把这个问题给处理好。
02.提高 embedding 对于细节的捕捉能力
在 RAG 中,文本首先会根据 chunksize 来进行切分,每一个 chunk 计算出向量来方便检索。典型的历史问题通常都是涉及到(人物)(时间)(地点)的行为,所以我们希望找回的语料内容能够与问题在这些点的语义重合度较高。
我们使用的 embedding 模型是在开源社区中比较热门的(BAAI/bge-base-zh-v1.5),它在大规模的中文数据集上经过训练,能够在效果和性能上取得一个不错的平衡,需要分析的第一个原因可能是由于 chunksize 设置的过大,导致 embedding 对于细节刻画得不是很好。所以我们采取了 LLamaIndex 中的 SentenceWindowNode, 按句来进行 embedding 的计算,但是最后返回给 LLM 来进行阅读的文章确实包含了更多的上下文的一个窗口,这样可以让 LLM进行信息分析时能够找到更多的线索。现在使用向量检索确实发现对于细节的相关性有了很好的改善,但是也包含了一些关于关羽的其他内容甚至是和关羽的无关内容。
03.重排序进一步提高召回文本的相关性
即使我们通过 SentenceWindowNode 很好地提高了 embedding 对于细节的捕捉程度, 依然会出现不理想的排序。这可能与 embedding 模型是在大规模通用语料上训练得到的有关。想要对此的改善通常是人工标注数据进行 embedding 模型的 finetune,但是我们希望使用更加通用的模型来进行处理。
我们使用 cross-encoder 方式设计的 rerank 模型(BAAI/bge-reranker-large)来进行文本相关性的计算,rerank 模型通过输入(问题,文档片段)直接生成分数,所以可以同时接收到二者的信息。用比喻来说,embedding 召回就是看简历留下大致印象,rerank 就是一对一的进行面试,所以通常的情况下都能进一步提高结果的相关程度。通过这个技术,我们找到了所有关羽杀死他人的文本,当然也包含了一些关羽被吴国杀死的信息。
04.大模型的选择
在我们提供了尽可能高质量的史料信息后,就到了大模型最后的阅读理解阶段,我们一开始采用的是 gpt-35-turbo-1106,发现在这个问题上表现并不是很理想(可能是由于语料都是比较碎片化的段落),非常出现容易幻觉。在经过了一定的prompt工程后(例如:告诉它需要忠实地参考原文),但最终还是无法达到期待的效果。刚好 OpenAI 年底发布了更便宜的 gpt4 版本 gpt4-turbo-1205, 无论是对于格式的要求,以及对于幻觉的克服,都有了显著的提升,我们选择了 gpt4-turbo 作为最后的 reader。
05.为段落加上引用
作为一个史料的 RAG 应用,我们希望能够在给出包含知识的原文同时能够给出它在原文中的具体传记名,由于语料中可以通过格式区分出来传记名和正文,所以通过简单的一些规则就可以提取出来每个段落对应的传记名,并且通过将其作为文本的 metadata。我们可以对 metadata 进行控制,只希望将传记名告诉给 LLM 让其来进行出处的标记,而不希望在 embedding 和 rerank 阶段带来影响,就可以控制它在只在 LLM 的 prompt 中呈现出来。
06.总结
现在,我们就可以产生出一个能够初步符合预期的 RAG 系统了,这个 RAG 能够捕捉到细粒度的相关性高的史料,并且可以准确地引用其对应出处。同时更强的 LLM 模型也可以从繁杂的史料片段中提取到对问题回答有帮助的知识。
我们在这个项目中首先分析了数据的特点,采取了更加适合现有模型的文本方案,接下来所采用的提升技术主要是尽可能地提高召回文本的与问题的相关性,并且通过让 embedding 的文本与展示给 LLM 的文本不同的技巧来达到提高检索精度,拥有足够上下文,获取引用信息的目的。我们尽可能地采取了通用的技巧,针对历史 RAG 的场景,我们也可以去识别“人名”然后进行片段的过滤处理,或者额外分析一下发生的朝代,根据朝代信息来将召回片段进行过滤。
归根结底就是当给出一个 query 后,怎么尽可能地找到最相关的知识,不浪费 LLM 中的 prompt 空间。话说到这里,必须要附上项目地址供感兴趣的小伙伴参考呀,欢迎 Clone 体验:https://github.com/wxywb/history_rag。
此外,在社区的使用过程中,也有不懂编程的小伙伴在使用 history_rag遇 到了算力不足,以及部署数据库的问题。目前,history_rag 除了需要本地部署数据库和向量模型的 Milvus 模式外,还支持了 Zilliz Pipeline 的模式。
Zilliz Pipeline 是 Zilliz Cloud 推出的一项云托管服务,可以将文本转换为向量插入到云数据库中。这样搭配着其他在云端部署的 LLM 可以使用户不需要高性能的算力设备,以及摆脱复杂的数据库管理。同时这个方案也可以与具体的 LLM 解耦,方便使用各种 LLM,社区也贡献了通义千问的方案可以一键切换。
所以如果你对历史非常感兴趣,那么你不需要会编程或计算机,直接来 GitHub 跟着文档走一遍就好。
相关链接:
-
LlamaIndex:https://github.com/run-llama/llama_index
-
Zilliz Cloud Pipelines: https://zilliz.com/zilliz-cloud-pipelines
-
history_rag:https://github.com/wxywb/history_rag