{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 使用Embedding进行检索增强(RAG)回答\n", "这个Notebook主要用于使用Embedding进行文章搜索,并应用文心大模型进行回答。\n", "\n", "主要步骤有:\n", "1. 获取要使用的后端的鉴权参数,并设置ACCESS_TOKEN,请参考[认证鉴权文档](../../docs/authentication.md)。\n", "2. 读取准备好的文章(本地知识库文章),并且完成文本切片。\n", "3. 调用文心百中语义模型获得知识库文章的Embedding。\n", "4. 对生成好的知识库文章向量进行存储(Option)。\n", "5. 根据问题对知识库文章进行检索。\n", "6. 根据检索出来的段落进行回答。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 什么是RAG\n", "RAG(Retrieval-Augmented Generation:检索增强生成。结合了生成式模型和检索式模型的优势,旨在提高生成式模型的性能,特别是在处理与语境相关的任务时。\n", "\n", "RAG 基本思想是在生成文本的过程中利用检索到的信息。它包含两个主要组件:一个是生成式模型,通常是一个预训练的语言生成模型,此notebook中主要使用的是文心一言;另一个是检索模块,用于从大量的文本数据中检索相关信息。\n", "\n", "在使用 RAG 时,首先通过检索模块从大型文本语料库中检索与输入相关的信息。然后,生成模型使用这些检索到的信息来生成更加相关和具体的文本。这种方法可以帮助模型更好地理解输入的语境,并生成更加贴合上下文的内容。\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. 认证鉴权\n", "获取要使用的后端的鉴权参数,并设置ACCESS_TOKEN,请参考[认证鉴权文档](../../docs/authentication.md)。" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [], "source": [ "import time\n", "import math\n", "import erniebot\n", "import faiss\n", "\n", "from tqdm import tqdm\n", "from typing import List\n", "\n", "erniebot.api_type = 'aistudio'\n", "erniebot.access_token = ''\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. 文本切片(Chunk)\n", "您可以选择使用默认的按字数切分文章或者自定义函数对文本进行切分,如自定义切片函数输入需为知识库文章的集合List[str],输出为切片好的文章段落List[str]。" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [], "source": [ "# 我们准备了一篇文章,来源为中华人民共和国立法法。\n", "with open('./data/laws_rag.txt','r') as f:\n", " content = f.read()\n", "\n", "def split_by_len(texts:List[str], split_token:int =384) -> List[str]:\n", " split_token = split_token # 文心支持最大长度\n", " idx = 0\n", " chunk = []\n", "\n", " for text in texts:\n", " while idx + split_token < len(text):\n", " temp_text = text[idx:idx + split_token]\n", " next_idx = temp_text.rfind('。') + 1\n", " if next_idx != 0: # 如果该切片中没有句号,则直接添加整个句子\n", " chunk.append(temp_text[:next_idx])\n", " idx = idx + next_idx\n", " else:\n", " chunk.append(temp_text)\n", " idx = idx + split_token\n", "\n", " chunk.append(text[idx:])\n", " \n", " return chunk\n", "\n", "doc_chunk = split_by_len([content])\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. 获取知识库文本向量(Embedding)\n", "调用文心百中语义模型获得Embedding并进行标准化。" ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "100%|██████████| 4/4 [00:06<00:00, 1.68s/it]\n" ] } ], "source": [ "\n", "def get_embedding(word: List[str]) -> List[float]:\n", " \"\"\"\n", " 获取单词的embedding向量\n", "\n", " Args:\n", " word (List[str]): 需要获取embedding向量的单词列表\n", "\n", " Returns:\n", " List[float]: 单词的embedding向量列表\n", " \"\"\"\n", " if len(word) <= 16:\n", " embedding = erniebot.Embedding.create(\n", " model = 'ernie-text-embedding',\n", " input = word\n", " ).get_result()\n", " else:\n", " size = len(word)\n", " embedding = []\n", " for i in tqdm(range(math.ceil(size / 16))):\n", " embedding.extend(erniebot.Embedding.create(model = 'ernie-text-embedding', input = word[i*16:(i+1)*16]).get_result())\n", " time.sleep(1)\n", " return embedding\n", "\n", "doc_embedding = get_embedding(doc_chunk)\n", "assert len(doc_embedding) == len(doc_chunk),\"shape mismatch\"\n", "\n", "import numpy as np\n", "\n", "def l2_normalization(embedding:np.ndarray) -> np.ndarray:\n", " if embedding.ndim == 1:\n", " return embedding / np.linalg.norm(embedding).reshape(-1,1)\n", " else:\n", " return embedding/np.linalg.norm(embedding,axis=1).reshape(-1,1)\n", "\n", "doc_embedding = l2_normalization(np.array(doc_embedding))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. 向量存储(Vector DB)\n", "\n", "您可以选择直接存储为npy文件在本地,或者使用faiss存储(需先下载faiss `pip install faiss-cpu`)以便于之后重复调用。" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [], "source": [ "np.save('./data/knowledge_embedding.npy',doc_embedding)" ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [], "source": [ "index_ip = faiss.IndexFlatIP(len(doc_embedding[0]))\n", "index_ip.add(doc_embedding)\n", "faiss.write_index(index_ip, './data/knowledge_embedding.index')" ] }, { "cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [], "source": [ "# 切片文本也需同步存储\n", "with open('./data/knowledge.txt','w') as f:\n", " for chunk in doc_chunk:\n", " f.write(repr(chunk) + '\\n')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5. 检索(Search)\n", "文本相似度默认使用Cosine Similarity进行匹配,然后找到top_k个与问题最匹配的段落。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "query = '国务院如何制定行政法规'\n", "\n", "# 将存储的文本读出\n", "doc_chunk = []\n", "with open('./data/knowledge.txt','r') as f:\n", " for line in f:\n", " doc_chunk.append(eval(line))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 5.1 直接使用npy读取\n", "如果文本向量直接存储为npy文件,则直接读取之后进行搜索" ] }, { "cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Top 1:\n", "\n", "第七十一条 全国人民代表大会常务委员会工作机构加强立法宣传工作,通过多种形式发布立法信息、介绍情况、回应关切。\n", "\n", "第三章 行政法规\n", "\n", "第七十二条 国务院根据宪法和法律,制定行政法规。\n", "\n", "行政法规可以就下列事项作出规定:\n", "\n", "(一)为执行法律的规定需要制定行政法规的事项;\n", "\n", "(二)宪法第八十九条规定的国务院行政管理职权的事项。\n", "\n", "应当由全国人民代表大会及其常务委员会制定法律的事项,国务院根据全国人民代表大会及其常务委员会的授权决定先制定的行政法规,经过实践检验,制定法律的条件成熟时,国务院应当及时提请全国人民代表大会及其常务委员会制定法律。\n", "\n", "第七十三条 国务院法制机构应当根据国家总体工作部署拟订国务院年度立法计划,报国务院审批。国务院年度立法计划中的法律项目应当与全国人民代表大会常务委员会的立法规划和立法计划相衔接。\n", "--------------------------------------------------\n", "Top 2:\n", "\n", "应当制定地方性法规但条件尚不成熟的,因行政管理迫切需要,可以先制定地方政府规章。规章实施满两年需要继续实施规章所规定的行政措施的,应当提请本级人民代表大会或者其常务委员会制定地方性法规。\n", "\n", "没有法律、行政法规、地方性法规的依据,地方政府规章不得设定减损公民、法人和其他组织权利或者增加其义务的规范。\n", "\n", "第九十四条 国务院部门规章和地方政府规章的制定程序,参照本法第三章的规定,由国务院规定。\n", "\n", "第九十五条 部门规章应当经部务会议或者委员会会议决定。\n", "\n", "地方政府规章应当经政府常务会议或者全体会议决定。\n", "\n", "第九十六条 部门规章由部门首长签署命令予以公布。\n", "\n", "地方政府规章由省长、自治区主席、市长或者自治州州长签署命令予以公布。\n", "\n", "第九十七条 部门规章签署公布后,及时在国务院公报或者部门公报和中国政府法制信息网以及在全国范围内发行的报纸上刊载。\n", "--------------------------------------------------\n", "Top 3:\n", "\n", "第七十六条 行政法规的决定程序依照中华人民共和国国务院组织法的有关规定办理。\n", "\n", "第七十七条 行政法规由总理签署国务院令公布。\n", "\n", "有关国防建设的行政法规,可以由国务院总理、中央军事委员会主席共同签署国务院、中央军事委员会令公布。\n", "\n", "第七十八条 行政法规签署公布后,及时在国务院公报和中国政府法制信息网以及在全国范围内发行的报纸上刊载。\n", "\n", "在国务院公报上刊登的行政法规文本为标准文本。\n", "\n", "第七十九条 国务院可以根据改革发展的需要,决定就行政管理等领域的特定事项,在规定期限和范围内暂时调整或者暂时停止适用行政法规的部分规定。\n", "--------------------------------------------------\n", "Top 4:国务院法制机构应当及时跟踪了解国务院各部门落实立法计划的情况,加强组织协调和督促指导。\n", "\n", "国务院有关部门认为需要制定行政法规的,应当向国务院报请立项。\n", "\n", "第七十四条 行政法规由国务院有关部门或者国务院法制机构具体负责起草,重要行政管理的法律、行政法规草案由国务院法制机构组织起草。行政法规在起草过程中,应当广泛听取有关机关、组织、人民代表大会代表和社会公众的意见。听取意见可以采取座谈会、论证会、听证会等多种形式。\n", "\n", "行政法规草案应当向社会公布,征求意见,但是经国务院决定不公布的除外。\n", "\n", "第七十五条 行政法规起草工作完成后,起草单位应当将草案及其说明、各方面对草案主要问题的不同意见和其他有关资料送国务院法制机构进行审查。\n", "\n", "国务院法制机构应当向国务院提出审查报告和草案修改稿,审查报告应当对草案主要问题作出说明。\n", "--------------------------------------------------\n", "Top 5:\n", "\n", "经济特区法规根据授权对法律、行政法规、地方性法规作变通规定的,在本经济特区适用经济特区法规的规定。\n", "\n", "第一百零二条 部门规章之间、部门规章与地方政府规章之间具有同等效力,在各自的权限范围内施行。\n", "\n", "第一百零三条 同一机关制定的法律、行政法规、地方性法规、自治条例和单行条例、规章,特别规定与一般规定不一致的,适用特别规定;新的规定与旧的规定不一致的,适用新的规定。\n", "\n", "第一百零四条 法律、行政法规、地方性法规、自治条例和单行条例、规章不溯及既往,但为了更好地保护公民、法人和其他组织的权利和利益而作的特别规定除外。\n", "\n", "第一百零五条 法律之间对同一事项的新的一般规定与旧的特别规定不一致,不能确定如何适用时,由全国人民代表大会常务委员会裁决。\n", "\n", "行政法规之间对同一事项的新的一般规定与旧的特别规定不一致,不能确定如何适用时,由国务院裁决。\n", "--------------------------------------------------\n" ] } ], "source": [ "doc_embedding = np.load('./data/knowledge_embedding.npy')\n", "\n", "def find_related_doc(query:str, origin_chunk:List[str], doc_embedding, top_k:int=5) -> List[int]:\n", " rank_scores = l2_normalization(np.array(get_embedding([query]))) @ doc_embedding.T\n", " top_k_similar = rank_scores.argsort()[0][-top_k:].tolist()[::-1]\n", " res = ''\n", " for i in range(top_k):\n", " \n", " print(f\"Top {i+1}:{origin_chunk[top_k_similar[i]]}\")\n", " print('-'*50)\n", " res += f\"参考文章 {i+1}:{origin_chunk[top_k_similar[i]]}\" + '\\n\\n'\n", " return res\n", "\n", "related_doc = find_related_doc(query,doc_chunk,doc_embedding)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 5.2 使用faiss读取\n", "如果使用faiss存储,则使用以下方法读取向量库并进行匹配。" ] }, { "cell_type": "code", "execution_count": 68, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Top 1:\n", "\n", "第七十一条 全国人民代表大会常务委员会工作机构加强立法宣传工作,通过多种形式发布立法信息、介绍情况、回应关切。\n", "\n", "第三章 行政法规\n", "\n", "第七十二条 国务院根据宪法和法律,制定行政法规。\n", "\n", "行政法规可以就下列事项作出规定:\n", "\n", "(一)为执行法律的规定需要制定行政法规的事项;\n", "\n", "(二)宪法第八十九条规定的国务院行政管理职权的事项。\n", "\n", "应当由全国人民代表大会及其常务委员会制定法律的事项,国务院根据全国人民代表大会及其常务委员会的授权决定先制定的行政法规,经过实践检验,制定法律的条件成熟时,国务院应当及时提请全国人民代表大会及其常务委员会制定法律。\n", "\n", "第七十三条 国务院法制机构应当根据国家总体工作部署拟订国务院年度立法计划,报国务院审批。国务院年度立法计划中的法律项目应当与全国人民代表大会常务委员会的立法规划和立法计划相衔接。\n", "--------------------------------------------------\n", "Top 2:\n", "\n", "应当制定地方性法规但条件尚不成熟的,因行政管理迫切需要,可以先制定地方政府规章。规章实施满两年需要继续实施规章所规定的行政措施的,应当提请本级人民代表大会或者其常务委员会制定地方性法规。\n", "\n", "没有法律、行政法规、地方性法规的依据,地方政府规章不得设定减损公民、法人和其他组织权利或者增加其义务的规范。\n", "\n", "第九十四条 国务院部门规章和地方政府规章的制定程序,参照本法第三章的规定,由国务院规定。\n", "\n", "第九十五条 部门规章应当经部务会议或者委员会会议决定。\n", "\n", "地方政府规章应当经政府常务会议或者全体会议决定。\n", "\n", "第九十六条 部门规章由部门首长签署命令予以公布。\n", "\n", "地方政府规章由省长、自治区主席、市长或者自治州州长签署命令予以公布。\n", "\n", "第九十七条 部门规章签署公布后,及时在国务院公报或者部门公报和中国政府法制信息网以及在全国范围内发行的报纸上刊载。\n", "--------------------------------------------------\n", "Top 3:\n", "\n", "第七十六条 行政法规的决定程序依照中华人民共和国国务院组织法的有关规定办理。\n", "\n", "第七十七条 行政法规由总理签署国务院令公布。\n", "\n", "有关国防建设的行政法规,可以由国务院总理、中央军事委员会主席共同签署国务院、中央军事委员会令公布。\n", "\n", "第七十八条 行政法规签署公布后,及时在国务院公报和中国政府法制信息网以及在全国范围内发行的报纸上刊载。\n", "\n", "在国务院公报上刊登的行政法规文本为标准文本。\n", "\n", "第七十九条 国务院可以根据改革发展的需要,决定就行政管理等领域的特定事项,在规定期限和范围内暂时调整或者暂时停止适用行政法规的部分规定。\n", "--------------------------------------------------\n", "Top 4:国务院法制机构应当及时跟踪了解国务院各部门落实立法计划的情况,加强组织协调和督促指导。\n", "\n", "国务院有关部门认为需要制定行政法规的,应当向国务院报请立项。\n", "\n", "第七十四条 行政法规由国务院有关部门或者国务院法制机构具体负责起草,重要行政管理的法律、行政法规草案由国务院法制机构组织起草。行政法规在起草过程中,应当广泛听取有关机关、组织、人民代表大会代表和社会公众的意见。听取意见可以采取座谈会、论证会、听证会等多种形式。\n", "\n", "行政法规草案应当向社会公布,征求意见,但是经国务院决定不公布的除外。\n", "\n", "第七十五条 行政法规起草工作完成后,起草单位应当将草案及其说明、各方面对草案主要问题的不同意见和其他有关资料送国务院法制机构进行审查。\n", "\n", "国务院法制机构应当向国务院提出审查报告和草案修改稿,审查报告应当对草案主要问题作出说明。\n", "--------------------------------------------------\n", "Top 5:\n", "\n", "经济特区法规根据授权对法律、行政法规、地方性法规作变通规定的,在本经济特区适用经济特区法规的规定。\n", "\n", "第一百零二条 部门规章之间、部门规章与地方政府规章之间具有同等效力,在各自的权限范围内施行。\n", "\n", "第一百零三条 同一机关制定的法律、行政法规、地方性法规、自治条例和单行条例、规章,特别规定与一般规定不一致的,适用特别规定;新的规定与旧的规定不一致的,适用新的规定。\n", "\n", "第一百零四条 法律、行政法规、地方性法规、自治条例和单行条例、规章不溯及既往,但为了更好地保护公民、法人和其他组织的权利和利益而作的特别规定除外。\n", "\n", "第一百零五条 法律之间对同一事项的新的一般规定与旧的特别规定不一致,不能确定如何适用时,由全国人民代表大会常务委员会裁决。\n", "\n", "行政法规之间对同一事项的新的一般规定与旧的特别规定不一致,不能确定如何适用时,由国务院裁决。\n", "--------------------------------------------------\n" ] } ], "source": [ "import faiss\n", "\n", "index_ip = faiss.read_index('./data/knowledge_embedding.index') \n", "\n", "def find_related_doc(\n", " query:str, \n", " origin_chunk:List[str], \n", " index_ip:faiss.swigfaiss.IndexFlatIP, \n", " top_k:int=5\n", " ) -> List[int]:\n", " \n", " D, I = index_ip.search(np.array(get_embedding([query])),top_k)\n", " top_k_similar = I.tolist()[0]\n", " res = ''\n", " for i in range(top_k):\n", " \n", " print(f\"Top {i+1}:{origin_chunk[top_k_similar[i]]}\")\n", " print('-'*50)\n", " res += f\"参考文章 {i+1}:{origin_chunk[top_k_similar[i]]}\" + '\\n\\n'\n", " return res\n", "\n", "related_doc = find_related_doc(query,doc_chunk,index_ip)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 6. 回答(Answer)\n", "结合检索出来的文章片段对用户提问进行回答。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "6.1 结合知识库回答" ] }, { "cell_type": "code", "execution_count": 62, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "国务院根据宪法和法律制定行政法规。具体流程包括以下几个步骤:\n", "\n", "1. 立项:国务院有关部门认为需要制定行政法规的,应当向国务院报请立项。\n", "2. 起草:行政法规由国务院有关部门或者国务院法制机构具体负责起草。重要行政管理的行政法规草案由国务院法制机构组织起草。在起草过程中,应当广泛听取有关机关、组织、人民代表大会代表和社会公众的意见。听取意见可以采取座谈会、论证会、听证会等多种形式。\n", "3. 审查:行政法规草案完成后,起草单位应当将草案及其说明、各方面对草案主要问题的不同意见和其他有关资料送国务院法制机构进行审查。国务院法制机构应当向国务院提出审查报告和草案修改稿,审查报告应当对草案主要问题作出说明。\n", "4. 决定和公布:行政法规的决定程序依照中华人民共和国国务院组织法的有关规定办理。行政法规由总理签署国务院令公布。行政法规签署公布后,及时在国务院公报和中国政府法制信息网以及在全国范围内发行的报纸上刊载。在国务院公报上刊登的行政法规文本为标准文本。\n", "\n", "以上是国务院制定行政法规的基本流程,需要注意的是,行政法规的制定应当遵循宪法和法律的规定,同时也需要广泛听取各方意见,确保行政法规的科学性和民主性。\n" ] } ], "source": [ "PROMPT_TEMPLATE = \"基于以下已知信息,请简洁并专业地回答用户的问题。如果无法从中得到答案,请说 '根据已知信息无法回答该问题' 或 '没有提供足够的相关信息'。不允许在答案中添加编造成分。你可以参考以下文章:\\n{DOCS}\\n问题:{QUERY}\\n回答:\"\n", "\n", "chat_message = [\n", " {'role': 'user', 'content': PROMPT_TEMPLATE.format(DOCS=related_doc, QUERY=query)}\n", "]\n", "response = erniebot.ChatCompletion.create(model='ernie-bot-4', \n", " messages=chat_message)\n", "print(response.get_result())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "6.2 不结合知识库直接回答" ] }, { "cell_type": "code", "execution_count": 111, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "国务院制定行政法规,应当根据宪法和法律,不得与宪法、法律相抵触。\n", "国务院可以根据宪法和法律,制定行政法规。行政法规可以就下列事项作出规定:为执行法律的规定需要制定行政法规的事项;宪法第八十九条规定的国务院行政管理职权的事项。应当由全国人民代表大会及其常务委员会制定法律的事项,国务院根据全国人民代表大会及其常务委员会的授权决定先制定的行政法规,经过实践检验,制定法律的条件成熟时,国务院应当及时提请全国人民代表大会及其常务委员会制定法律。\n" ] } ], "source": [ "chat_message = [\n", " {'role': 'user', 'content':query}\n", "]\n", "response = erniebot.ChatCompletion.create(model='ernie-bot-4', \n", " messages=chat_message)\n", "print(response.get_result())" ] } ], "metadata": { "kernelspec": { "display_name": "ernie", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.13" } }, "nbformat": 4, "nbformat_minor": 2 }