如何手撸一个自有知识库的RAG系统(非常详细)零基础入门到精通,收藏这一篇就够了

706 篇文章 36 订阅
56 篇文章 0 订阅

RAG通常指的是"Retrieval-AugmentedGeneration",即“检索增强的生成”。这是一种结合了检索(Retrieval)和生成(Generation)的机器学习模型,通常用于自然语言处理任务,如文本生成、问答系统等。

我们通过一下几个步骤来完成一个基于京东云官网文档的RAG系统

  • 数据收集

  • 建立知识库

  • 向量检索

  • 提示词与模型

数据收集

数据的收集再整个RAG实施过程中无疑是最耗人工的,涉及到收集、清洗、格式化、切分等过程。这里我们使用京东云的官方文档作为知识库的基础。文档格式大概这样:

{`    `"content": "DDoS IP高防结合Web应用防火墙方案说明\n=======================\n\n\nDDoS IP高防+Web应用防火墙提供三层到七层安全防护体系,应用场景包括游戏、金融、电商、互联网、政企等京东云内和云外的各类型用户。\n\n\n部署架构\n====\n\n\n[![\\\"部署架构\\\"](\\\"https://jdcloud-portal.oss.cn-north-1.jcloudcs.com/cn/image/Advanced%20Anti-DDoS/Best-Practice02.png\\\")](\\\"https://jdcloud-portal.oss.cn-north-1.jcloudcs.com/cn/image/Advanced%20Anti-DDoS/Best-Practice02.png\\\")  \n\nDDoS IP高防+Web应用防火墙的最佳部署架构如下:\n\n\n* 京东云的安全调度中心,通过DNS解析,将用户域名解析到DDoS IP高防CNAME。\n* 用户正常访问流量和DDoS攻击流量经过DDoS IP高防清洗,回源至Web应用防火墙。\n* 攻击者恶意请求被Web应用防火墙过滤后返回用户源站。\n* Web应用防火墙可以保护任何公网的服务器,包括但不限于京东云,其他厂商的云,IDC等\n\n\n方案优势\n====\n\n\n1. 用户源站在DDoS IP高防和Web应用防火墙之后,起到隐藏源站IP的作用。\n2. CNAME接入,配置简单,减少运维人员工作。\n\n\n",`    `"title": "DDoS IP高防结合Web应用防火墙方案说明",`    `"product": "DDoS IP高防",`    `"url": "https://docs.jdcloud.com/cn/anti-ddos-pro/anti-ddos-pro-and-waf"``}``   

每条数据是一个包含四个字段的json,这四个字段分别是"content":文档内容;“title”:文档标题;“product”:相关产品;“url”:文档在线地址

向量数据库的选择与Retriever实现

向量数据库是RAG系统的记忆中心。目前市面上开源的向量数据库很多,那个向量库比较好也是见仁见智。本项目中笔者选择则了clickhouse作为向量数据库。选择ck主要有一下几个方面的考虑:

  • ck再langchain社区的集成实现比较好,入库比较平滑

  • 向量查询支持sql,学习成本较低,上手容易

  • 京东云有相关产品且有专业团队支持,用着放心

文档向量化及入库过程

为了简化文档向量化和检索过程,我们使用了longchain的Retriever工具集
首先将文档向量化,代码如下:

from libs.jd_doc_json_loader import JD_DOC_Loaderfrom langchain_community.document_loaders import DirectoryLoaderroot_dir = "/root/jd_docs"loader = DirectoryLoader(    '/root/jd_docs', glob="**/*.json", loader_cls=JD_DOC_Loader)docs = loader.load()

langchain社区里并没有提供针对特定格式的装载器,为此,我们自定义了JD_DOC_Loader来实现加载过程

import json``import logging``from pathlib import Path``from typing import Iterator, Optional, Union``   ``from langchain_core.documents import Document``   ``from langchain_community.document_loaders.base import BaseLoader``from langchain_community.document_loaders.helpers import detect_file_encodings``   ``logger = logging.getLogger(__name__)``   ``   ``class JD_DOC_Loader(BaseLoader):`    `"""Load text file.``   ``   `    `Args:`        `file_path: Path to the file to load.``   `        ``encoding: File encoding to use. If `None`, the file will be loaded``        `with the default system encoding.``   `        `autodetect_encoding: Whether to try to autodetect the file encoding`            `if the specified encoding fails.`    `"""``   `    `def __init__(`        `self,`        `file_path: Union[str, Path],`        `encoding: Optional[str] = None,`        `autodetect_encoding: bool = False,`    `):`        `"""Initialize with file path."""`        `self.file_path = file_path`        `self.encoding = encoding`        `self.autodetect_encoding = autodetect_encoding``   `    `def lazy_load(self) -> Iterator[Document]:`        `"""Load from file path."""`        `text = ""`        `from_url = ""`        `try:`            `with open(self.file_path, encoding=self.encoding) as f:`                `doc_data = json.load(f)`                `text = doc_data["content"]`                `title = doc_data["title"]`                `product = doc_data["product"]`                `from_url = doc_data["url"]``   `                `# text = f.read()`        `except UnicodeDecodeError as e:`            `if self.autodetect_encoding:`                `detected_encodings = detect_file_encodings(self.file_path)`                `for encoding in detected_encodings:`                    `logger.debug(f"Trying encoding: {encoding.encoding}")`                    `try:`                        `with open(self.file_path, encoding=encoding.encoding) as f:`                            `text = f.read()`                        `break`                    `except UnicodeDecodeError:`                        `continue`            `else:`                `raise RuntimeError(f"Error loading {self.file_path}") from e`        `except Exception as e:`            `raise RuntimeError(f"Error loading {self.file_path}") from e`        `# metadata = {"source": str(self.file_path)}`        `metadata = {"source": from_url, "title": title, "product": product}`        `yield Document(page_content=text, metadata=metadata)``   

以上代码功能主要是解析json文件,填充Document的page_content字段和metadata字段。接下来使用langchain 的 clickhouse 向量工具集进行文档入库

import langchain_community.vectorstores.clickhouse as clickhouse``from langchain.embeddings import HuggingFaceEmbeddings``   ``model_kwargs = {"device": "cuda"}``embeddings = HuggingFaceEmbeddings(`    `model_name="/root/models/moka-ai-m3e-large", model_kwargs=model_kwargs)``   ``settings = clickhouse.ClickhouseSettings(`    `table="jd_docs_m3e_with_url", username="default", password="xxxxxx", host="10.0.1.94")``   ``docsearch = clickhouse.Clickhouse.from_documents(`    `docs, embeddings, config=settings)

入库成功后,进行一下检验

import langchain_community.vectorstores.clickhouse as clickhouse``from langchain.embeddings import HuggingFaceEmbeddings``   ``model_kwargs = {"device": "cuda"}~~~~``embeddings = HuggingFaceEmbeddings(`    `model_name="/root/models/moka-ai-m3e-large", model_kwargs=model_kwargs)``   ``settings = clickhouse.ClickhouseSettings(`    `table="jd_docs_m3e_with_url_splited", username="default", password="xxxx", host="10.0.1.94")``ck_db = clickhouse.Clickhouse(embeddings, config=settings)``ck_retriever = ck_db.as_retriever(`    `search_type="similarity_score_threshold", search_kwargs={'score_threshold': 0.9})``ck_retriever.get_relevant_documents("如何创建mysql rds")

有了知识库以后,可以构建一个简单的restful 服务,我们这里使用fastapi做这个事儿

from fastapi import FastAPI``from pydantic import BaseModel``from singleton_decorator import singleton``from langchain_community.embeddings import HuggingFaceEmbeddings``import langchain_community.vectorstores.clickhouse as clickhouse``import uvicorn``import json``   ``app = FastAPI()``app = FastAPI(docs_url=None)``app.host = "0.0.0.0"``   ``model_kwargs = {"device": "cuda"}``embeddings = HuggingFaceEmbeddings(`    `model_name="/root/models/moka-ai-m3e-large", model_kwargs=model_kwargs)``settings = clickhouse.ClickhouseSettings(`    `table="jd_docs_m3e_with_url_splited", username="default", password="xxxx", host="10.0.1.94")``ck_db = clickhouse.Clickhouse(embeddings, config=settings)``ck_retriever = ck_db.as_retriever(`    `search_type="similarity", search_kwargs={"k": 3})``   ``   ``class question(BaseModel):`    `content: str``   ``   ``@app.get("/")``async def root():`    `return {"ok"}``   ``   ``@app.post("/retriever")``async def retriver(question: question):`    `global ck_retriever`    `result = ck_retriever.invoke(question.content)`    `return result``   ``   ``if __name__ == '__main__':`    `uvicorn.run(app='retriever_api:app', host="0.0.0.0",`                `port=8000, reload=True)

返回结构大概这样:

[`  `{`    `"page_content": "云缓存 Redis--Redis迁移解决方案\n###RedisSyncer 操作步骤\n####数据校验\n```\nwget   https://github.com/TraceNature/rediscompare/releases/download/v1.0.0/rediscompare-1.0.0-linux-amd64.tar.gz\nrediscompare compare single2single --saddr \\\"10.0.1.101:6479\\\" --spassword \\\"redistest0102\\\" --taddr \\\"10.0.1.102:6479\\\" --tpassword  \\\"redistest0102\\\" --comparetimes 3\n\n```\n**Github 地址:** [https://github.com/TraceNature/redissyncer-server](\\\"https://github.com/TraceNature/redissyncer-server\\\")",`    `"metadata": {`      `"product": "云缓存 Redis",`      `"source": "https://docs.jdcloud.com/cn/jcs-for-redis/doc-2",`      `"title": "Redis迁移解决方案"`    `},`    `"type": "Document"`  `},`  `{`    `"page_content": "云缓存 Redis--Redis迁移解决方案\n###RedisSyncer 操作步骤\n####数据校验\n```\nwget   https://github.com/TraceNature/rediscompare/releases/download/v1.0.0/rediscompare-1.0.0-linux-amd64.tar.gz\nrediscompare compare single2single --saddr \\\"10.0.1.101:6479\\\" --spassword \\\"redistest0102\\\" --taddr \\\"10.0.1.102:6479\\\" --tpassword  \\\"redistest0102\\\" --comparetimes 3\n\n```\n**Github 地址:** [https://github.com/TraceNature/redissyncer-server](\\\"https://github.com/TraceNature/redissyncer-server\\\")",`    `"metadata": {`      `"product": "云缓存 Redis",`      `"source": "https://docs.jdcloud.com/cn/jcs-for-redis/doc-2",`      `"title": "Redis迁移解决方案"`    `},`    `"type": "Document"`  `},`  `{`    `"page_content": "云缓存 Redis--Redis迁移解决方案\n###RedisSyncer 操作步骤\n####数据校验\n```\nwget   https://github.com/TraceNature/rediscompare/releases/download/v1.0.0/rediscompare-1.0.0-linux-amd64.tar.gz\nrediscompare compare single2single --saddr \\\"10.0.1.101:6479\\\" --spassword \\\"redistest0102\\\" --taddr \\\"10.0.1.102:6479\\\" --tpassword  \\\"redistest0102\\\" --comparetimes 3\n\n```\n**Github 地址:** [https://github.com/TraceNature/redissyncer-server](\\\"https://github.com/TraceNature/redissyncer-server\\\")",`    `"metadata": {`      `"product": "云缓存 Redis",`      `"source": "https://docs.jdcloud.com/cn/jcs-for-redis/doc-2",`      `"title": "Redis迁移解决方案"`    `},`    `"type": "Document"`  `}``]

返回一个向量距离最小的list

结合模型和prompt,回答问题

为了节约算力资源,我们选择qwen 1.8B模型,一张v100卡刚好可以容纳一个qwen模型和一个m3e-large embedding 模型

  • answer 服务

  • from fastapi import FastAPI``from pydantic import BaseModel``from langchain_community.llms import VLLM``from transformers import AutoTokenizer``from langchain.prompts import PromptTemplate``import requests``import uvicorn``import json``import logging``   ``app = FastAPI()``app = FastAPI(docs_url=None)``app.host = "0.0.0.0"``   ``logger = logging.getLogger()``logger.setLevel(logging.INFO)``to_console = logging.StreamHandler()``logger.addHandler(to_console)``   ``   ``# load model``# model_name = "/root/models/Llama3-Chinese-8B-Instruct"``model_name = "/root/models/Qwen1.5-1.8B-Chat"``tokenizer = AutoTokenizer.from_pretrained(model_name)``llm_llama3 = VLLM(`    `model=model_name,`    `tokenizer=tokenizer,`    `task="text-generation",`    `temperature=0.2,`    `do_sample=True,`    `repetition_penalty=1.1,`    `return_full_text=False,`    `max_new_tokens=900,``)``   ``# prompt``prompt_template = """``你是一个云技术专家``使用以下检索到的Context回答问题。``如果不知道答案,就说不知道。``用中文回答问题。``Question: {question}``Context: {context}``Answer:` `"""``   ``prompt = PromptTemplate(`    `input_variables=["context", "question"],`    `template=prompt_template,``)``   ``   ``def get_context_list(q: str):`    `url = "http://10.0.0.7:8000/retriever"`    `payload = {"content": q}`    `res = requests.post(url, json=payload)`    `return res.text``   ``   ``class question(BaseModel):`    `content: str``   ``   ``@app.get("/")``async def root():`    `return {"ok"}``   ``   ``@app.post("/answer")``async def answer(q: question):`    `logger.info("invoke!!!")`    `global prompt`    `global llm_llama3`    `context_list_str = get_context_list(q.content)``   `    `context_list = json.loads(context_list_str)`    `context = ""`    `source_list = []``   `    `for context_json in context_list:`        `context = context+context_json["page_content"]`        `source_list.append(context_json["metadata"]["source"])`    `p = prompt.format(context=context, question=q.content)`    `answer = llm_llama3(p)`    `result = {`        `"answer": answer,`        `"sources": source_list`    `}`    `return result``   ``   ``if __name__ == '__main__':`    `uvicorn.run(app='retriever_api:app', host="0.0.0.0",`                `port=8888, reload=True)
    

代码通过使用Retriever接口查找与问题相似的文档,作为context组合prompt推送给模型生成答案。
主要服务就绪后可以开始画一张脸了,使用gradio做个简易对话界面

  • gradio 服务

  • import json``import gradio as gr``import requests``   ``   ``def greet(name, intensity):`    `return "Hello, " + name + "!" * int(intensity)``   ``   ``def answer(question):`    `url = "http://127.0.0.1:8888/answer"`    `payload = {"content": question}`    `res = requests.post(url, json=payload)`    `res_json = json.loads(res.text)`    `return [res_json["answer"], res_json["sources"]]``   ``   ``demo = gr.Interface(`    `fn=answer,`    `# inputs=["text", "slider"],`    `inputs=[gr.Textbox(label="question", lines=5)],`    `# outputs=[gr.TextArea(label="answer", lines=5),`    `#          gr.JSON(label="urls", value=list)]`    `outputs=[gr.Markdown(label="answer"),`             `gr.JSON(label="urls", value=list)]``)``   ``   ``demo.launch(server_name="0.0.0.0")
    

AI大模型学习福利

作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

2024最新版CSDN大礼包:《AGI大模型学习资源包》免费分享

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

img
因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

2024最新版CSDN大礼包:《AGI大模型学习资源包》免费分享

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

img

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

2024最新版CSDN大礼包:《AGI大模型学习资源包》免费分享

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

img
因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

2024最新版CSDN大礼包:《AGI大模型学习资源包》免费分享

四、AI大模型商业化落地方案

img

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

2024最新版CSDN大礼包:《AGI大模型学习资源包》免费分享

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量。

  • 25
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RAG(Retrieval-Augmented Generation)是一种结合了检索和生成技术的模型,它利用预训练的语言模型(如GPT)生成答案,并通过检索系统查找相关信息来增强其响应。RAG知识库需求文档是用于指导构建或维护一个能够支持RAG模型的知识库的文档。这样的文档通常会包含以下内容: 1. 知识库的目标和范围:明确知识库需要覆盖的主题范围、预期的数据量、更新频率等。 2. 数据采集:说明如何收集和整理数据,包括数据来源、数据格式、数据清洗和预处理等步骤。 3. 数据存储和管理:描述知识库的存储结构、索引机制、数据一致性保证、备份策略和安全性要求。 4. 检索系统设计:定义检索系统的工作原理,包括搜索引擎的选择、索引建立、检索算法、相关性评分和检索结果的展示方式。 5. 数据更新和维护:概述数据更新的流程,包括新数据的录入、旧数据的淘汰或更新、数据的验证和测试等。 6. 用户接口:如果知识库将直接与用户交互,需要定义用户接口的设计,包括搜索界面、结果展示、用户反馈机制等。 7. 性能和质量保证:描述如何评估知识库的性能和信息质量,包括准确度、响应时间、容错能力等。 8. 安全和合规性:确保知识库的设计遵循相关的数据保护法规和标准,包括用户数据的隐私保护、数据访问控制和审计日志记录。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值