掘金 后端 ( ) • 2024-05-17 10:12

rag我简单理解来看就是我先有一段文本,先把它转成向量,保存到向量数据库中,下次我调用llm时将向量数据库中查询的结果给llm作参考并回答。

对rag了解不多,所以开启学习之旅,学完了要应用到实际的需求中,因为最近手里有一个订单就是需要用到这个技术,但是又一知半解。

现在新知识太多了,学不过来,完全学不过来。

看了一个B站关于langchain的rag相关的知识,介绍了langchain官方文档该怎么看,感觉有用,贴一下链接:
**
https://www.bilibili.com/video/BV1Cp421R7Y7/?spm_id_from=333.337.search-card.all.click&vd_source=dfb9822d48ce5215cb969f1d686c38c5**

因为我是在pgsql中存储和搜索向量,所以推荐这篇文章如何安装pgsql支持向量的操作:
https://blog.csdn.net/FrenzyTechAI/article/details/131552053

原生的pgsql不支持或者没有很好的支持向量的操作,所以需要用到扩展的pgsql。

又问了一下同事,大致知道流程了,就是:我需要提供一个接口将文本转成向量存到pgsql数据库。再提供一个查询的接口,这个接口先把传入的文本转成向量,再去pgsql向量数据库查询,最后得出结果,表结构就是文本对应向量,然后查询的时候根据向量文本相似度查询相似度最高的几条记录,这样就可以拿到文本了,再将这个文本丢给AI作参考,然后AI就可以先基于本地内容回答了。

所以本地知识库其实和rag差不多的。

OK!大致思路整理清楚了开始动工:
我这边使用的数据库也是pgsql,但是我本地是windows系统,所以还得看看怎么在windows上安装pgsql的pgvector扩展:

根据:

https://www.cnblogs.com/xiaonanmu/p/17979626

https://blog.csdn.net/qq_57963867/article/details/131720092

这两篇文章的参考,我需要在本地安装C++然后手动make,先安装C++

https://visualstudio.microsoft.com/zh-hans/downloads/

我们下载社区版就行

然后我们只装这一个:

等下载完成:

安装好后打开:C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build

就能看到有这个文件

我们命令行执行:

call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"

再去官网下载pgsql的pgvector扩展:

https://pgxn.org/dist/vector/0.5.1/

这个官网下面还有使用教程:

我们这里下载0.5.1版本的:

下载之后解压:

然后打开命令行输入nmake看看有没有效:

输出内容了,说明nmake安装成功!

接下来开始编译pgvector扩展,我们的目标文件:

我们先设置一下pgsql的安装目录,这里要改成自己真实的安装目录,再以管理员身份运行命令行输入,不然可能会失败:
set "PGROOT=D:\soft_install\pgsql-16"

不设置的话会报错:

我们设置一下:

再输入nmake /F Makefile.win,执行结果时这样的:

再输入nmake /F Makefile.win install:

应该是安装完成了。

安装完成后还得启用,我们是windows的话,打开pgadmin4:

运行此命令以启用扩展:CREATE EXTENSION vector;;

验证是否安装成功:

说明我们成功安装了vector0.5.1版本的了。

在这里也能看到:

我再创建一个符合我的项目需求的表,用来存储文本和向量结构:

CREATE TABLE IF NOT EXISTS public.job_info_vector
(
  id character varying(36) COLLATE pg_catalog."default" NOT NULL,
  city_id character varying(36) COLLATE pg_catalog."default" NOT NULL,
  city_name character varying(36) COLLATE pg_catalog."default" NOT NULL,
  post_id character varying(36) COLLATE pg_catalog."default" NOT NULL,
  post_name character varying(36) COLLATE pg_catalog."default" NOT NULL,
  description character varying(1024) COLLATE pg_catalog."default" NOT NULL,
  created_on character varying(35) COLLATE pg_catalog."default" NOT NULL,
  description_vector vector NOT NULL,
  CONSTRAINT job_info_vector_pkey PRIMARY KEY (id)
)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public.job_info_vector
OWNER to postgres;
COMMENT ON COLUMN public.job_info_vector.city_id
IS '城市id';
COMMENT ON COLUMN public.job_info_vector.city_name
IS '城市名';
COMMENT ON COLUMN public.job_info_vector.post_id
IS '岗位id';
COMMENT ON COLUMN public.job_info_vector.post_name
IS '岗位名称';
COMMENT ON COLUMN public.job_info_vector.description
IS '岗位对应的招聘信息';
COMMENT ON COLUMN public.job_info_vector.created_on
IS '创建时间';
COMMENT ON COLUMN public.job_info_vector.description_vector
IS '向量的表示';

大致的流程:

1、我需要提供一个接口供将传入的内容分块并向量化存储

2、提供查询接口(将查询的内容向量化再查询)

一个一个来实现。

网上找了下,暂时没找到加载一段文本的,而是只有加载文件的,就那几行代码。

所以我这里封装一个方法,可以把一段文本封装为一个文本流:

import io
def read_from_text(text):
  with io.StringIO(text) as f:
  return f.read()

拆分的代码:

from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=200,
chunk_overlap=30,
length_function=len,
)
texts = text_splitter.create_documents([read_from_text(r.text)])

效果:

下一段的开头会有一段内容和上一段结尾的内容重合的:

ok,拆分这一步完成了,接下来就是有多少段就向量化几次并入库:

texts = text_splitter.create_documents([read_from_text(r.description)])
job_dao = JobInfoVectorDao()
  for text in texts:
  vector = self.text_to_vector(app, text.page_content)
  if not vector:
  continue
  # 数据入库
  job_dao.add(JobInfoVectorModel(
    id=str(uuid.uuid4()),
    city_id=r.city_id,
    city_name=r.city_name,
    post_id=r.post_id,
    post_name=r.post_name,
    description=text.page_content,
    description_vector=json.dumps(vector),
  ))

数据库效果如下:

OK!向量的拆分和入库完成。

这里文本转向量的方法用的是智谱的。

接下来就是搜索了:

pgvector官网就有搜索的sql,直接拿过来再结合自己的项目就行,我这里也直接用原生sql了。

调一下智谱的embedding接口看看效果,首先我们得先有向量数据:

text_embedding接口文档:
https://open.bigmodel.cn/dev/api#text_embedding

拿到结果后就需要入库了,我这边的代码:

# 先拆分
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=Config.ZHIPUAI_EMBEDDING_CHUNK_SIZE,
chunk_overlap=Config.ZHIPUAI_EMBEDDING_CHUNK_OVERLAP,
length_function=len,
)
texts = text_splitter.create_documents([read_from_text(r.description)])
job_dao = JobInfoVectorDao()
for text in texts:
  vector = self.text_to_vector(app, text.page_content)
  if not vector:
  	continue
  # 数据入库
  job_dao.add(JobInfoVectorModel(
    id=str(uuid.uuid4()),
    city_id=r.city_id,
    city_name=r.city_name,
    post_id=r.post_id,
    post_name=r.post_name,
    description=text.page_content,
    description_vector=json.dumps(vector),
  ))
  return self.return_dataclass(EmbeddingsTextToVectorResponse(
    success=True,
    chunk_count=len(texts),
    chunk_size=Config.ZHIPUAI_EMBEDDING_CHUNK_SIZE,
    chunk_overlap=Config.ZHIPUAI_EMBEDDING_CHUNK_OVERLAP,
  ))

我让ChatGPT给了我们一批内容用来训练:

然后我逐个调用智谱的embedding入库,结果如下:

然后我的查找的代码:
from sqlalchemy import text
def get_list(self, model: JobInfoVectorModel, r: EmbeddingsTextToVectorSearchRequest):
  where_sql = "where 1 = 1"
  if not is_empty(r.city_id):
  where_sql += " and city_id = :city_id "
  if not is_empty(r.city_name):
  where_sql += " and city_name = :city_name "
  if not is_empty(r.post_id):
  where_sql += " and post_id = :post_id "
  if not is_empty(r.post_name):
  where_sql += " and post_name = :post_name "
  order_sql = f"ORDER BY description_vector <-> '{json.dumps(r.description_vector)}' ASC"
  limit_sql = f" LIMIT 5 "
  query_sql = text(f"select id, city_id, city_name, post_id, post_name, description, "
  f"created_on from {model.__tablename__} {where_sql} {order_sql} {limit_sql}")
  result = db.session.execute(query_sql, {
    'city_id': r.city_id,
    'city_name': r.city_name,
    'post_id': r.post_id,
    'post_name': r.post_name
  }).fetchall()

结果还是很不错的,见下图:

又学到一个新知识!满足。

这篇文章就到这里啦!如果你对文章内容有疑问或想要深入讨论,欢迎在评论区留言,我会尽力回答。同时,如果你觉得这篇文章对你有帮助,不妨点个赞并分享给其他同学,让更多人受益。

想要了解更多相关知识,可以查看我以往的文章,其中有许多精彩内容。记得关注我,获取及时更新,我们可以一起学习、讨论技术,共同进步。

感谢你的阅读与支持,期待在未来的文章中与你再次相遇!

我的微信公众号:【xdub】,欢迎大家订阅,我会同步文章到公众号上。