在langchain中的rag学习使用之旅-从半知半解到实现效果

发布于:2024-05-17 ⋅ 阅读:(78) ⋅ 点赞:(0)

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

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

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

看了一个B站关于langchain的rag相关的知识,介绍了langchain官方文档该这么看,感觉有用,贴一下链接:
LangChain实现RAG检索增强_哔哩哔哩_bilibili

因为我是在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

PostgreSQL安装扩展vector_pgvector安装-CSDN博客

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

下载 Visual Studio Tools - 免费安装 Windows、Mac、Linux

我们下载社区版就行

然后我们只装这一个:

等下载完成:

安装好后打开: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扩展:

vector 0.5.1: Open-source vector similarity search for Postgres / PostgreSQL Extension Network

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

我们这里下载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】,欢迎大家订阅,我会同步文章到公众号上。