【大模型系列】实现一个简单的 RAG问答

搜索增强生成(RAG,Retrieval-Augmented Generation)

大模型固有知识不是实时更新的,rag技术成了大模型内容生成时获取外部知识的重要手段。

向量数据库安装

这里我们使用一个比较好用的向量库:milvus ,采用在docker里启动的方式运行;
详情参考文档: 【docker运行单节点milvus】

curl -sfL https://raw.githubusercontent.com/milvus-io/milvus/master/scripts/standalone_embed.sh -o standalone_embed.sh

bash standalone_embed.sh start

启动后会开启一个9091的web端口,可以对数据进行查看和管理;访问链接格式如下:

http://192.168.217.101:9091/webui/
在这里插入图片描述
启动完毕后,通过pip安装milvus模块,然后可以在代码里使用其提供的api操作数据库

pip install -U pymilvus
安装向量库可视化工具

这里推荐一个向量库可视化工具 : attu, https://zilliz.com.cn/attu

在这里插入图片描述

向量库collection初始化

我们设计一个库,包含三个字段:id ,vector,line分别表示主键,向量值和原始文本;使用以下脚本进行collection的创建:

from pymilvus import MilvusClient, DataType
from a_collection_name import collection_name_v # 指定一个集合的名称,字符串即可

class VectorDBUtil:

    def init_collection(self):
        """初始化向量数据库milvus集合"""
        client = MilvusClient(uri="http://192.168.217.101:19530", db_name="default")
        client.use_database("default")
        client.drop_collection(collection_name_v)

        schema = MilvusClient.create_schema(
            auto_id=False,
            enable_dynamic_field=True,
        )
        schema.add_field(
            field_name="id",
            datatype=DataType.INT64,
            is_primary=True,
            auto_id=True,
        )
        schema.add_field(field_name="vector", datatype=DataType.FLOAT_VECTOR, dim=1024)
        schema.add_field(field_name="line", datatype=DataType.VARCHAR, max_length=1024)

        index_params = client.prepare_index_params()
        index_params.add_index(field_name="id", index_type="STL_SORT")
        index_params.add_index(
            field_name="vector", index_type="AUTOINDEX", metric_type="COSINE"
        )
        client.create_collection(
            collection_name=collection_name_v, schema=schema, index_params=index_params
        )
        res = client.get_load_state(collection_name=collection_name_v)
        print(f"collection 创建结果:{res}")


if __name__ == "__main__":
    vectorDBUtil = VectorDBUtil()
    vectorDBUtil.init_collection()

执行完毕后效果如下,新增了一个collection: rag_demo_0505

在这里插入图片描述

文本提取并计算向量,数据入库

我们这里采用 pdfminer 对pdf内文本进行提取,安装 pdfminer :

pip install pdfminer.six

提取pdf文本内容代码:

from pdfminer.high_level import extract_pages, extract_text  # 按照页读
from pdfminer.layout import LTTextContainer  # 文本标记


class FileUtil(object):

    def extract_lines_from_file(self, file_path: str, min_line_length=1):
        """使用pdfminer读取文件内容
        把一个LTTextContainer内容作为一个元素
        """
        lines = []
        for i, page_layout in enumerate(extract_pages(file_path)):
            # print(f"当前页数:{i}")
            for element in page_layout:
                # 如果是文本
                if isinstance(element, LTTextContainer):
                    line = element.get_text().replace("\n", "").replace("\r", "")
                    lines.append(line)

        print("文件读取完毕")
        return lines

    def extract_page_lines_from_file(self, file_path: str, min_line_length=1):
        """使用pdfminer读取文件内容
        把最多300文字作为一个元素
        """
        page_lines = []
        for i, page_layout in enumerate(extract_pages(file_path)):
            page_line = ""
            for element in page_layout:
                # 如果是文本
                if isinstance(element, LTTextContainer):
                    line = element.get_text().replace("\n", "").replace("\r", "")
                    page_line += line
                    if len(page_line) > 300:
                        page_lines.append(page_line)
                        page_line = ""

            page_lines.append(page_line)

        print("文件读取完毕")
        return page_lines


if __name__ == "__main__":
    # 读取文件内容
    fileUtil = FileUtil()
    lines = fileUtil.extract_page_lines_from_file(
        file_path=r"F:\desktop20241201\软件架构设计/软件体系结构原理、方法与实践_第2版.pdf",
        min_line_length=2,
    )
    ls = lines

    for l in ls:
        print(l)
        print("\n")

文件内容入库
from c_file_read_util import *
from pymilvus import MilvusClient, DataType
from a_collection_name import collection_name_v

from openai import OpenAI
import os

os.environ["OPENAI_BASE_URL"] = "https://api.siliconflow.cn/v1"
# 填写自己硅基流动的api-key
os.environ["OPENAI_API_KEY"] = "sk-xxxx"


class VectorDBUtil:
    def getVextor(self, line):
        """使用硅基流动的在线embedding模型
        BAAI/bge-large-zh-v1.5 免费
        """
        client = OpenAI()
        response = client.embeddings.create(
            model="BAAI/bge-large-zh-v1.5",
            input=[line],
            encoding_format="float",
            # 模型可能不支持该参数
            dimensions=512,
        )
        return response.data[0].embedding

    def saveVextor(sels, line, vextor):
        """将文本和对应的向量保存数据库"""
        client = MilvusClient(uri="http://192.168.217.101:19530", db_name="default")
        client.use_database("default")
        data = [
            {
                "vector": vextor,
                "line": line,
            }
        ]
        res = client.insert(collection_name=collection_name_v, data=data)
        # print(f"数据保存结果:{res}")


if __name__ == "__main__":

    # 读取文件内容
    fileUtil = FileUtil()
    lines = fileUtil.extract_page_lines_from_file(
        file_path=r"F:\desktop20241201\软件架构设计/系统架构设计师教程第二版可搜索.pdf",
        min_line_length=2,
    )
    # print(lines)

    vectorDBUtil = VectorDBUtil()
    for line in lines:
        vector = vectorDBUtil.getVextor(line=line)
        vectorDBUtil.saveVextor(vextor=vector, line=line)

    print(f"获得向量值成功")

代码执行完毕后,可以通过attu查看入库后向量值和对应的文本数据:
在这里插入图片描述

向量库文本召回、向量化

这部分内容主要是根据用户的输入,进行向量计算后将向量值作为检索条件,从向量库里获得语义最近的文档原始值;
代码如下:

import os
from a_collection_name import collection_name_v
from openai import OpenAI
from pdfminer.high_level import extract_pages, extract_text  # 按照页读
from pdfminer.layout import LTTextContainer  # 文本标记
from pymilvus import MilvusClient, DataType

from f_llm_query import *

os.environ["OPENAI_BASE_URL"] = "https://api.siliconflow.cn/v1"
# 替换成你的api-key
os.environ["OPENAI_API_KEY"] = "sk-xxxx"


class VectorDBUtil:

    def searchVextor(self, line):
        """使用硅基流动的在线embedding模型"""
        client = OpenAI()
        response = client.embeddings.create(
            model="BAAI/bge-large-zh-v1.5",
            input=[line],
            encoding_format="float",
            # 模型可能不支持该参数
            dimensions=512,
        )
        embedding = response.data[0].embedding

        search_params_v = {
            "metric_type": "COSINE",  # 分区可以在params设置
            "params": {},
        }

        milvusclient = MilvusClient(
            uri="http://192.168.217.101:19530", db_name="default"
        )

        res = milvusclient.search(
            collection_name=collection_name_v,
            data=[embedding],
            limit=3,
            search_params=search_params_v,
            output_fields=["line"],
        )
        return res


if __name__ == "__main__":

    vectorDBUtil = VectorDBUtil()
    res = vectorDBUtil.searchVextor(line="软件架构,它影响软件开发的哪些阶段")
    print("从向量数据库获取到内容:")
    for r in res:
        print(r[0])
        print(r[1])
        print(r[2])

    model = MyModel()
    resp = model.query("软件架构,它影响软件开发的哪些阶段", str(r[0]))

    print("rag回答:")
    print(resp)


以上代码是从向量库中检索最多3条语义最近的内容;

提示词管理
prompt_template = """
你是一个问答机器人。
你的任务是根据下述给定的已知信息回答用户问题。
确保你的回复完全依据下述已知信息。不要编造答案。
如果下述已知信息不足以回答用户的问题,请直接回复"我无法回答您的问题"。

已知信息:
__ANSWER__

用户问:
__QUESTION__

请用中文回答用户问题。
"""
拼接提示词,调用大模型进行总结

封装一个请求大模型的工具类,包含了提示词的格式化,代码如下:

from openai import OpenAI
import os


class MyModel:

    os.environ["OPENAI_BASE_URL"] = "https://api.siliconflow.cn/v1"
    # 你的api-key
    os.environ["OPENAI_API_KEY"] = "sk-xxx"

    def query(self, query, answer):
        """
        拼接提示词,并将提示词发送给大模型进行问答
        这里采用在线大模型,后续采用本地部署的模型
        """

        prompt_template = """
        你是一个问答机器人。
        你的任务是根据下述给定的已知信息回答用户问题。
        确保你的回复完全依据下述已知信息。不要编造答案。
        如果下述已知信息不足以回答用户的问题,请直接回复"我无法回答您的问题"。

        已知信息:
        __ANSWER__

        用户问:
        __QUESTION__

        请用中文回答用户问题。
        """

        messages_str = prompt_template.replace("__QUESTION__", query).replace(
            "__ANSWER__", answer
        )

        # 格式化输入
        messages = [{"role": "user", "content": messages_str}]

        model = OpenAI()
        resp = model.chat.completions.create(
            model="Qwen/Qwen3-235B-A22B",
            messages=messages,
            temperature=0,
        )  # 模型输出的随机性,0 表示随机性最小

        return resp.choices[0].message.content

最终输出

大模型会根据提示词输出问答结果
在这里插入图片描述
待优化:
1、文本切分优化,由于向量参数有长度限制,代码里是按照字数长短切割的
2、向量模型和文本生成模型是调用在线模型,后需要调整成本地部署

over~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值