本文首发至TiDB社区专栏:https://tidb.net/blog/7a8862d5
继上一次《TiDB Vector抢先体验之用TiDB实现以图搜图》后,就迫不及待的想做一些更复杂的应用。上一篇在 TiDB 社区专栏发布以后还是有很多社区朋友不明白向量的应用场景到底是什么,这次用一个更直观的场景来体现向量检索在 AI 应用开发的重要性。
知识库问答是目前 AGI 领域应用最多的场景之一,本次我基于 TiDB Vector 给 TiDB 搭建一个文档问答小助手。
上一篇提到把非结构化数据转化为向量表示需要用到 embedding 模型,但这种模型和大家所了解的 GPT 大语言模型(LLM)还不太一样,他们有着不同的作用。
text-embedding-ada-002
和 GPT(Generative Pre-trained Transformer)
是两种不同类型的模型,它们在设计和功能上有所不同,但都由 OpenAI 推出。
虽然 text-embedding-ada-002 和 GPT 都与文本处理相关,但它们的功能和用途不同。text-embedding-ada-002 主要用于文本嵌入,而 GPT 则是一个通用的自然语言处理模型,可以用于多种文本相关任务。因此,它们之间的关系是它们都由 OpenAI 推出,并且都是用于文本处理的模型,但它们的具体功能和设计是不同的。
注:以上解释由 ChatGPT 生成 。
本次实验中我用text-embedding-ada-002
对 TiDB 的中文文档做 embedding 转化存入到 TiDB Serverless,用 GPT-4 生成最终的问题答案。
在各种各样的信息渠道,相信大家已经被 RAG 这个词在视觉上轰炸了很长时间,但是我估计大部分 DBA 看了依然不明白到底是什么,我争取用一个小例子来讲明白。
众所周知,大语言模型都是预训练模型,也就是说他们的语料是不会及时更新的,类似于数据库里的snapshot
。比如我去问 ChatGPT 目前(24年5月14号) TiDB 的最新版本是多少,它的回答一定不让人满意。
如果我就想让 ChatGPT 告诉我 TiDB 的最新版本号,通常有两种办法:
snapshot
(超级烧钱)很明显第二种方法更切实际,工程实现上更容易,成本也更低。
我们从指定的文档、甚至是搜索引擎中找到相关的信息丢给 GPT ,借助 GPT 的推理能力并且限定它的上下文内容得到最终答案。通过临时喂给 LLM 的数据提升它的能力,这就构成了 RAG 的灵魂:检索(Retrieval)、增强(Augmented)、生成(Generation)。
这就是一个最基础的 RAG (也称之为朴素 RAG,Native RAG)流程,在此基础上如果继续优化提升准确度的话还可以引入 Rerank 等相关技术形成高级 RAG(Advance RAG)。
可以发现检索是 RAG 里非常重要的一个流程,因此 TiDB 的向量检索能力就能起到关键作用。目前市面上见到的绝大部分 AI 应用,都是用 RAG 架构来搭建的。
到这里不知道大家会不会有个疑问:
既然检索(Retrieval)就能得到想要的答案,为什么要多此一举再问一遍 LLM ?
基于前面介绍的 RAG 架构,下面我逐渐用代码实现让 GPT 能回答刚才那个 TiDB 版本号问题。
LangChain 官方已经对 TiDB Vector 做了集成,借助 LangChain 的 vectorstore 组件能够对 TiDB Vector 实现高效操作。
刚好我本地有一份之前下载的 TiDB v7.6.0
PDF 文档,先把文件处理后保存到 TiDB Serverless 中:
简单几行代码 LangChain 帮我们把 split、embedding、入库全部都做了,再看一下 TiDB Vector 中生成的表结构:
这里做 chunk 用最简单粗暴的形式,每页文档为一个 chunk,chunk 的文本内容存入 document
字段,向量化后的内容存入embedding
字段,这是一个1536维的向量同时创建了hnsw
索引,meta
字段保存的是 chunk 的一些元信息,如文件名、页号等。
文档预处理和 chunk 划分方式对召回质量有很大的影响,这些属于 RAG 调优范畴,简单起见本文不做讨论,重点关注整体实现流程。
知识库准备好以后就可以根据我们提出的问题在语义层面搜索相关内容,主要依赖 TiDB 的向量检索能力,这一步称为召回。
我们去 TiDB 中查询到相似度最高的 TOP 3信息,简单拼接后组装成上下文返回。
Prompt 是和 LLM 沟通的语言,通过 Prompt 我们可以引导大模型控制它的输出和准确度。就好比我们要去搜素引擎或 github上搜想要的内容时,如何提问是一门艺术。它有一套非常全面的方法论,这里不做过多赘述,用常用的格式构建一个 Prompt 即可:
下一步将得到的 Prompt 传给大模型并获得返回结果,再把整个 RAG 的调用链串起来封装成rag_invoke
方法:
为了方便对比,我把不使用 RAG 直接调用 GPT API 的结果也打印出来:
最后来看一下效果怎么样:
经过“增强”后,GPT 能准确的答出最新版本是7.6.0-DMR
,看起来变得更聪明了,“增强”两个字体现的恰到好处。
比如我再问一些 TiDB 比较新的特性:
标准版 GPT 甚至开始胡言乱语了。
前面提到为什么生成答案还要再调用一次 LLM ,不直接使用 TiDB Vector 中返回的结果?以最初的 TiDB 最新版本问题为例,我们看一下向量检索的结果是什么,打印context
变量的值:
可以发现这里面的信息非常乱也很杂,大部分都是不相关的内容,依靠人工找出想要的信息仍然是件费劲的事。但是借助大模型的语义理解、逻辑推理、归纳总结等能力,我们就能得到一个非常清晰准确的答案。
如果把 TiDB 相关的各种文档、博客、专栏文章、asktug问答等等内容全部放进 TiDB Vector 再加以调教,一个强大的问答机器人就诞生了。推荐阅读:https://tidb.net/blog/a9cdb8ec
到这里应该要结束了,但还有点意犹未尽。
用自然语言写 SQL 是目前数据领域很热门的一个方向,这个和本文主题并没有太大关系,纯好奇研究了一下,想给 TiDB 也做一个类似的工具。
其实 TiDB Cloud 很早就上线了 Chat2Query 功能:
比较容易想到的方案是把相关的表结构信息和问题组合成 Prompt 一起发给大语言模型,类似于这样:
落地到代码层面只需要简单调用 OpenAI 的接口即可:
如果每次问问题都要先把表结构查出来未免也太麻烦,有没有办法让大模型一次性把所有表结构都记住,我们只问问题就行,最好是能根据问题查出最终数据?这个就涉及到 Agent(智能体) 部分的内容了。
LangChain 提供了 SQL Agent 能力,只需要简单几行代码就可以把数据库和大模型打通。
再看稍微复杂点的例子:
程序执行过程中先分析了和问题相关要使用的表,再根据语义生成了 SQL,最后把 SQL 发送给 TiDB 得到最终结果,结果非常准确。
尽管看起来还不错,但是实际使用中局限性还比较大,比如:
show table regions
这种之前也体验过其他类似的产品,个人感觉现阶段实用性还略有欠缺,但不妨碍它是 AI4DB 的一个热门方向,相信未来会变得更好。
借助 TiDB 向量检索能力,可以非常轻松地和 AI 生态进行打通,这也意味着 TiDB 的使用场景变得更加丰富。可以预见的是 AI 浪潮会持续火热,可能以后向量检索就成了数据库的标配。前不久 Oracle 发布了集成向量特性的新版本,直接更名为Oracle 23 ai
炸响大半个数据库圈子,DBA 们拥抱 AI 也必须要安排起来了。