LLM - 使用 RAG (检索增强生成) 多路召回 实现 精准知识问答 教程
欢迎关注我的CSDN:https://spike.blog.csdn.net/
本文地址:https://spike.blog.csdn.net/article/details/142629289
免责声明:本文来源于个人知识与公开资料,仅用于学术交流,欢迎讨论,不支持转载。

RAG (Retrieval-Augmented Generation,检索增强生成) 的多路召回,包括向量召回和本文召回,可用于精准知识问答,减轻大模型的幻觉问题,即:
- 并行:同时使用文本召回和向量召回,合计获得 TopN 个样本,再使用重排序的方式,获得 TopK 个样本,作为最终的召回文本。
- 串行:优先使用文本召回,召回 TopN 个样本,再使用向量排序,获得 TopK 个样本,作为最终的召回样本。
启动 Ollama 服务:
# 配置 HOST
export OLLAMA_HOST="0.0.0.0:11434"
# 配置 模型路径
export OLLAMA_MODELS="ollama_models"nohup ollama serve > nohup.ollama.out &
RAG 使用 LangChain 框架,参考:LangChain - Quickstart
LangChain 的相关依赖包,即:
pip install langchain
pip install beautifulsoup4
pip install faiss-cpu
pip install jiebapip install langchain-community
pip install langchain-huggingface
pip install rank_bm25
pip install langchain_openai
准备编码模型 BGE,即:
# https://huggingface.co/BAAI/bge-large-zh-v1.5
modelscope download --model BAAI/bge-large-zh-v1.5 --local_dir BAAI/bge-large-zh-v1.5
导入 LangChain 的相关 Python 包:
from typing import List
import jiebafrom langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import FAISS
from langchain.document_loaders import TextLoader
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.retrievers import BM25Retriever
使用 LangChain 读取外部文档 medical_data.txt,即:
loader = TextLoader('medical_data.txt')
documents = loader.load()
text_splitter = RecursiveCharacterTextSplitter(chunk_size = 500,chunk_overlap = 0,length_function = len,separators=['\n']
)
docs = text_splitter.split_documents(documents)
其中 medical_data.txt (4999 条) 格式如下,已经组织成 question 与 answer 的内容:
# ...
{'question': '曲匹地尔片的用法用量', 'answer': '注意:同种药品可由于不同的包装规格有不同的用法或用量。本文只供参考。如果不确定,请参看药品随带的说明书或向医生询问。口服。一次50~100mg(1-2片),3次/日,或遵医嘱。'}
# ...
Docs 是 list 格式,单项如下:
metadata信息源page_content信息内容
即:
Document(metadata={'source': 'medical_data.txt'}, page_content="{'question': '曲匹地尔片的用法用量', 'answer': '注意:同种药品可由于不同的包装规格有不同的用法或用量。本文只供参考。如果不确定,请参看药品随带的说明书或向医生询问。口服。一次50~100mg(1-2片),3次/日,或遵医嘱。'}")
Query 是文档中已经问题,即:
query = "请问锁骨骨折多久能干活?"
使用 BM25Retriever 构建检索器,选择 TopK=10 个文档,因为是中文,预处理使用 Jieba 分词,即:
def preprocessing_func(text: str) -> List[str]:return list(jieba.cut(text))
retriever = BM25Retriever.from_documents(docs, preprocess_func=preprocessing_func, k=10)
bm25_res = retriever.invoke(query)
BM25 算法的核心,在于利用 词频(Term Frequency, TF) 和 逆文档频率(Inverse Document Frequency, IDF) 衡量文档与查询之间的相关性,同时引入文档长度信息,来调整相关性的计算。
构建向量 Embeddings 库:
embeddings = HuggingFaceEmbeddings(model_name='llm/BAAI/bge-large-zh-v1.5', model_kwargs = {'device': 'cuda:1'})
db = FAISS.from_documents(docs, embeddings)
其中 5000 条向量,构建 embeddings 需要 1min 15s,CPU 执行。
获取向量召回:
vector_res = db.similarity_search(query, k=10)
使用 RRF 算法,进行多路召回合并,10+10=20 选取最优的 10 个召回,即:
def rrf(vector_results: List[str], text_results: List[str], k: int=10, m: int=60):"""使用 RRF 算法对两组检索结果进行重排序params:vector_results (list): 向量召回的结果列表, 每个元素是专利IDtext_results (list): 文本召回的结果列表, 每个元素是专利IDk(int): 排序后返回前k个m (int): 超参数return:重排序后的结果列表,每个元素是(文档ID, 融合分数)"""doc_scores = {}# 遍历两组结果,计算每个文档的融合分数for rank, doc_id in enumerate(vector_results):doc_scores[doc_id] = doc_scores.get(doc_id, 0) + 1 / (rank+m)for rank, doc_id in enumerate(text_results):doc_scores[doc_id] = doc_scores.get(doc_id, 0) + 1 / (rank+m)# 将结果按融合分数排序sorted_results = [d for d, _ in sorted(doc_scores.items(), key=lambda x: x[1], reverse=True)[:k]]return sorted_resultsvector_results = [i.page_content for i in vector_res]
text_results = [i.page_content for i in bm25_res]
rrf_res = rrf(vector_results, text_results)
RRF (Reciprocal Rank Fusion, 倒数排名融合) 算法将多个检索结果合并一个聚合列表,通过每个列表中每个项目的排名取倒数,即 1 除以排名,将倒数排名在所有列表中相加,得到每个项目的最终得分。
提示词工程:
prompt = '''
任务目标:根据检索出的文档回答用户问题
任务要求:1、不得脱离检索出的文档回答问题2、若检索出的文档不包含用户问题的答案,请回答我不知道用户问题:
{}检索出的文档:
{}
'''
使用 Ollama 服务进行大模型推理,注意需要使用长 Token 模型,即:
from langchain_community.llms import Ollama
model = Ollama(model="qwen-2_5-32b-max-context:latest")
print(f"[Info] rrf_res: {len(rrf_res)}")
full_prompt = prompt.format(query, ''.join(rrf_res))
# print(f"[Info] prompt: {full_prompt}")
res = model.invoke(full_prompt) # RAG
print(f"[Info] response: {res}")res = model.invoke(query) # 非 RAG
print(f"[Info] response: {res}")
RAG 的输出,与文档高度一致,即:
锁骨骨折的恢复时间一般在3个月左右。虽然骨折刚刚愈合时可以进行轻微的工作,但若涉及重体力劳动,则通常需要大约半年的时间才能重新开始,最少也需要4-5个月。过早地从事重体力工作有可能导致骨折处再次受伤。因此,在这期间避免过度负重活动是十分重要的,以确保锁骨能完全恢复并维持愈合效果。
非 RAG 的输出:
锁骨骨折的恢复时间取决于骨折的严重程度以及治疗方法。一般来说,轻微到中度的锁骨骨折可能需要大约6-8周的时间来初步愈合,在这段时间内,患者可能会被建议限制肩部和上肢的活动以促进骨折部位的稳定与修复。
但是,能否重新开始工作还依赖于具体工作的性质。如果工作不需要使用受伤的手臂或肩膀进行高强度劳动,则在几周后可能就可以慢慢恢复工作。然而,如果是需要手臂大力操作的工作,则可能需要等待3个月甚至更长时间才能安全地返回工作岗位,并且最好等到医生确认骨折完全愈合为止。
因此,在考虑重返岗位之前,应该咨询主治医师的意见,确保不会对康复过程造成负面影响或导致二次伤害。
参考:https://github.com/wyf3/llm_related
全部源码:
from typing import Listimport jieba
from langchain.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.llms import Ollama
from langchain_community.retrievers import BM25Retriever
from langchain_community.vectorstores import FAISS
from langchain_huggingface import HuggingFaceEmbeddingsclass RagRetriever(object):"""RAG retriever"""def __init__(self):loader = TextLoader(db_path)documents = loader.load()text_splitter = RecursiveCharacterTextSplitter(chunk_size=500,chunk_overlap=0,length_function=len,separators=['\n'])docs = text_splitter.split_documents(documents)def preprocessing_func(text: str) -> List[str]:return list(jieba.cut(text))self.doc_retriever = BM25Retriever.from_documents(docs, preprocess_func=preprocessing_func, k=10)print("[Info] init doc done!")embeddings = HuggingFaceEmbeddings(model_name=bge_path,model_kwargs={'device': 'cuda:1'})self.db = FAISS.from_documents(docs, embeddings)print("[Info] init db done!")self.prompt = '''任务目标:根据检索出的文档回答用户问题任务要求:1、不得脱离检索出的文档回答问题2、若检索出的文档不包含用户问题的答案,请回答我不知道用户问题:{}检索出的文档:{}'''print("[Info] init all done!")@staticmethoddef rrf(vector_results: List[str], text_results: List[str], k: int = 10, m: int = 60):"""使用 RRF 算法对两组检索结果进行重排序params:vector_results (list): 向量召回的结果列表, 每个元素是专利IDtext_results (list): 文本召回的结果列表, 每个元素是专利IDk(int): 排序后返回前k个m (int): 超参数return:重排序后的结果列表,每个元素是(文档ID, 融合分数)"""doc_scores = {}# 遍历两组结果,计算每个文档的融合分数for rank, doc_id in enumerate(vector_results):doc_scores[doc_id] = doc_scores.get(doc_id, 0) + 1 / (rank + m)for rank, doc_id in enumerate(text_results):doc_scores[doc_id] = doc_scores.get(doc_id, 0) + 1 / (rank + m)# 将结果按融合分数排序sorted_results = [d for d, _ in sorted(doc_scores.items(), key=lambda x: x[1], reverse=True)[:k]]return sorted_resultsdef retrieve(self, query):bm25_res = self.doc_retriever.invoke(query)vector_res = self.db.similarity_search(query, k=10)vector_results = [i.page_content for i in vector_res]text_results = [i.page_content for i in bm25_res]rrf_res = self.rrf(vector_results, text_results)model = Ollama(model="qwen-2_5-32b-max-context:latest")print(f"[Info] rrf_res: {len(rrf_res)}")full_prompt = self.prompt.format(query, ''.join(rrf_res))# print(f"[Info] prompt: {full_prompt}")res1 = model.invoke(full_prompt)print(f"[Info] rag response: {res1}")res2 = model.invoke(query)print(f"[Info] n-rag response: {res2}")return res1, res2def main():query = "请问锁骨骨折多久能干活?"rr = RagRetriever()rr.retrieve(query)if __name__ == '__main__':main()
相关文章:
LLM - 使用 RAG (检索增强生成) 多路召回 实现 精准知识问答 教程
欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/142629289 免责声明:本文来源于个人知识与公开资料,仅用于学术交流,欢迎讨论,不支持转载。 RAG (R…...
编程语言图书创作要注意的事情有哪些?
编程语言图书的创作是一项复杂且具有挑战性的任务,需要作者深入理解技术、清晰表达,并考虑读者的学习体验。一本优秀的编程书籍不仅能够教授技术知识,更能引导读者逐步深入,激发他们的思考和实际应用能力。以下将详细探讨编程语言…...
主流高级编程语言的推出时间及年份
1.下表一些主流高级编程语言的推出时间及年份: 高级语言 推出时间 岁数 FORTRAN 1957 67 LISP 1959 65 COBOL 1961 63 BASIC 1964 60 Pascal 1970 54 C 1972 52 MATLAB 1978 46 SQL 1978 46 Objective-C 1983 41 C 1983 41 Perl …...
qt 模仿简易的软狗实现
我们在写软件的时候,希望我们的软件只在固定的机器上运行,其他机器上运行不了,那我们应该如何做呢? 1 首先我们需要得到运行机器的mac地址,这样可以简易的判断是否是我们授权的机器。 那我们首先定义一个授权mac机器…...
荣业食品销售费用每年上亿元:主要产品收入大降,电商占比过低
《港湾商业观察》黄懿 今年3月,广东荣业食品有限公司的控股公司Wing Yip Food Holdings Group Limited(下称“荣业食品”)向美国SEC递交了纳斯达克上市申请。 据悉,2023年11月,商务部宣布移除了一批共计55家因长期经…...
数据结构:并查集
数据结构:并查集 并查集原理实现框架初始化合并查询获取成员路径压缩其它 总代码 并查集 在生活中,经常会出现分组问题。比如一个班级分为多个小组,打篮球分为两方等等。在同一个组中的所有成员,就构成一个集合。对这种一个群体分…...
微信小程序实战教程:轻松实现列表批量选择功能
在许多场景下,用户需要对列表中的多项内容进行操作,如批量删除、批量下载等。为了满足这一需求,我们需要在微信小程序中实现列表批量选择功能。具体要求如下: 用户可以逐个选择列表项,也可通过全选按钮快速选择所有列表…...
企业微信:开启客户联系和配置
前言 客户联系是企业微信的一项非常实用且自定义化配置丰富的功能,使企业内的授权员工可以添加外部客户(企业微信联系人和微信联系人)进行工作沟通,并且还可以建立客户群,甚至发表内容到客户朋友圈! 由于功…...
Python发送邮件教程:如何实现自动化发信?
Python发送邮件有哪些方法?如何利用python发送邮件? 无论是工作汇报、客户通知还是个人提醒,邮件都能快速传递信息。Python发送邮件的自动化功能就显得尤为重要。AokSend将详细介绍如何使用Python发送邮件,实现自动化发信&#x…...
一周热门|苏姿丰:芯片行业不能只盯着 GPU;Gartner:GenAI 即将越过期望膨胀期
大模型周报将从【企业动态】【技术前瞻】【政策法规】【专家观点】四部分,带你快速跟进大模型行业热门动态。 01 企业动态 Open AI 计划从非营利组织向营利组织转型 日前,路透社报道称,OpenAI 正在制定一项计划,将其核心业务重…...
Failed to load WebView provider: No WebView installed
1、问题 使用webview加载网页,在应用运行时,报了如下错误:android.webkit.WebViewFactory$MissingWebViewPackageException: Failed to load WebView provider: No WebView installed2、分析 通过查看项目的修改记录,确实安装了We…...
java日志框架之Log4j
文章目录 一、Log4j简介二、Log4j组件介绍1、Loggers (日志记录器)2、Appenders(输出控制器)3、Layout(日志格式化器) 三、Log4j快速入门四、Log4j自定义配置文件输出日志1、输出到控制台2、输出到文件3、输出到数据库 五、Log4j自…...
C++ bitset(位图)的模拟实现
文章目录 一、bitset接口总览二、bitset模拟实现1. 构造函数2. set、reset、flip、test3. size、count4. any、none、all5. 打印函数 三、完整代码 一、bitset接口总览 成员函数功能set设置指定位或所有位为1(即设置为“已设置”状态)reset清空指定位或…...
Llama 3.2:利用开放、可定制的模型实现边缘人工智能和视觉革命
在我们发布 Llama 3.1 模型群后的两个月内,包括 405B - 第一个开放的前沿级人工智能模型在内,它们所产生的影响令我们兴奋不已。 虽然这些模型非常强大,但我们也认识到,使用它们进行构建需要大量的计算资源和专业知识。 我们也听到…...
解决R语言bug ‘sh‘ is not recognized as an internal or external command
安装源码包‘httr2’ trying URL ‘https://cran.rstudio.com/src/contrib/httr2_1.0.5.tar.gz’ Content type ‘application/x-gzip’ length 230632 bytes (225 KB) downloaded 225 KB installing source package ‘httr2’ … ** package ‘httr2’ successfully unpacked…...
记一次Mac 匪夷所思终端常用网络命令恢复记录
一天莫名奇妙发现ping dig 等基础命令都无法正常使用。还好能浏览器能正常访问,,,, 赶紧拿baidu试试^-^ ; <<>> DiG 9.10.6 <<>> baidu.com ;; global options: cmd ;; connection timed out; no serve…...
2024最新!!Java后端面试题(4)看这一篇就够了!!!!
七、异常 throw 和 throws 的区别? throw用来显式地抛出一个异常,而throws则用于在方法声明中指明该方法可能抛出的异常。简单来说,throw是抛出异常的实际动作,throws是告知调用者这个方法可能会抛出哪些异常的声明。 final、f…...
springboot整合sentinel和对feign熔断降级
一、准备 docker安装好sentinel-dashboard(sentinel控制台),参考docker安装好各个组件的命令启动sentinel-dashboard,我的虚拟机ip为192.168.200.131,sentinel-dashboard的端口为8858 二、整合sentinel的主要工作 在…...
遗传算法与深度学习实战——使用进化策略实现EvoLisa
遗传算法与深度学习实战——使用进化策略实现EvoLisa 0. 前言1. 使用进化策略实现 EvoLisa2. 运行结果相关链接 0. 前言 我们已经学习了进化策略 (Evolutionary Strategies, ES) 的基本原理,并且尝试使用 ES 解决了函数逼近问题。函数逼近是一个很好的基准问题&…...
HttpServletRequest简介
HttpServletRequest是什么? HttpServletRequest是一个接口,其父接口是ServletRequest;HttpServletRequest是Tomcat将请求报文转换封装而来的对象,在Tomcat调用service方法时传入;HttpServletRequest代表客户端发来的请…...
大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...
MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...
Java求职者面试指南:计算机基础与源码原理深度解析
Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...
STM32---外部32.768K晶振(LSE)无法起振问题
晶振是否起振主要就检查两个1、晶振与MCU是否兼容;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容(CL)与匹配电容(CL1、CL2)的关系 2. 如何选择 CL1 和 CL…...
前端中slice和splic的区别
1. slice slice 用于从数组中提取一部分元素,返回一个新的数组。 特点: 不修改原数组:slice 不会改变原数组,而是返回一个新的数组。提取数组的部分:slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...
uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)
UniApp 集成腾讯云 IM 富媒体消息全攻略(地理位置/文件) 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型,核心实现方式: 标准消息类型:直接使用 SDK 内置类型(文件、图片等)自…...
