当前位置:首页>python>RAG工作机制详解+Python构建RAG系统

RAG工作机制详解+Python构建RAG系统

  • 2026-09-02 19:05:46
RAG工作机制详解+Python构建RAG系统

RAG应用场景(Retrieval Augmented Generation)

  1. 靠谱的智能客服

  2. 回答问题的知识库

RAG流程

  1. 先从资料库里检索相关内容

  2. 在基于这些内容来生成答案

产品手册太长所带来的问题

  1. 模型无法读取所有内容

  2. 模型推理成本过高

  3. 模型推理慢

RAG是如何解决这个问题的?

简单来讲RAG就是把文档进行分片,然后根据用户提出的问题进行相关性搜索片段,然后将搜索出来的片段喂给大模型,模型来给输出答案。

RAG的基本流程

  1. 准备(提问前):分片、索引

    • 分片的方式:字数分、段落分、章节分、页码分

    • 索引:通过Embedding将片段文本转换为向量、将片段文本和片段向量存入向量数据库中,将片段1-片段n所有的片段都处理完成之后,索引就结束了

    • 向量:有大小有方向的量

    • RAG里面用到的向量维度通常情况下都会比较大,比如几百甚至几千,一般维度越大,每个向量所包含的信息也就会越丰富,用这些向量做各种工作的可靠性也就越强

    • Embedding:把文本转换为向量的过程,含义相近的文本在进行Embedding之后,他们对应的向量也是相近的,Embedding是通过模型来完成的(Embedding模型),

    • 向量数据库:用来存储和查询向量的数据库,为存储向量做了很多优化,并且还提供了计算向量相似度等相关的函数,Embedding后的向量就可以放到向量数据库中,方便后续查询,向量数据库中会有向量和文本两部分内容

  2. 回答(提问后):召回、重排、生成

    • 召回:搜索与用户问题相关的片段,选出10个片段(片段数量可以定义)

    • 向量相似度计算方法:余弦相似度(cos夹角小)、欧氏距离(距离短)、点积(垂直距离的乘积越大)

    • 重排:重新排序,和召回一样,从召回的10个片段中,在选择3个片段,一次召回3个也可以,不过不如先召回在重排效果更好,因为重排与召回阶段使用的文本相似度计算逻辑不一样

    • 重排与召回的区别:

召回重排
方法向量相似度cross-encoder模型
特点成本低、耗时短、准确率低成本高、耗时长、准确率高
适合场景初步筛选精挑细选
  • 生成:我们现在有了用户问题,也有了与用户问题相关的3个片段,我们就可以把这两部分一起发送给大模型,让它根据片段内容来回答问题。

  1. 整体流程:

    • 准备阶段(提问前)

    • 知识库构建已经完毕,等待用户提问

    • 回答部分(提问后)

Python实现RAG系统

  1. 添加项目依赖

    uv add sentence_transformers chromadb OpenAI python-dotenv

    • sentence_transformers: 加载embedding和cross-encoder模型

    • chromadb: 一个非常流行的向量数据库

    • OpenAI: DeepSeek模型调用

    • python-dotenv: 将Gemini API Key 映射到环境变量中

  2. 实战代码

    importosfromtypingimportListimportchromadbfromsentence_transformersimportSentenceTransformer, CrossEncoderfromopenaiimportOpenAIembedding_model = SentenceTransformer('shibing624/text2vec-base-chinese')# 文件型向量数据库,默认存储在当前目录下的 chromadb 目录中,可以通过参数指定存储路径# chromadb_client = chromadb.PersistentClient("./chroma.db")chromadb_client = chromadb.EphemeralClient()  # 内存型向量数据库# collection是chromadb的概念,相当于一个表,里面存储着向量数据和对应的元数据,可以通过collection来进行增删改查等操作chromadb_collection = chromadb_client.get_or_create_collection(name="default")defsplit_into_chunks(doc_file: str) ->List[str]:"""分片"""withopen("doc.md", "r", encoding="utf-8") asf:content = f.read()return [chunkforchunkincontent.split("\n\n")]defembed_chunk(chunk: str) ->List[float]:"""生成向量"""embedding = embedding_model.encode(chunk)returnembedding.tolist()defsave_embeddings(chunks: List[str], embeddings: List[List[float]]) ->None:"""将所有的片段内容和对应的向量保存到向量数据库中"""# chromadb要求每一条记录都有一个唯一的id,可以使用索引或者其他方式生成唯一id,这里使用索引作为idids = [str(i) foriinrange(len(chunks))]chromadb_collection.add(documents=chunks,embeddings=embeddings,ids=ids,    )defretrieve(query: str, top_k: int = 5) ->List[str]:"""召回"""query_embedding = embed_chunk(query)results = chromadb_collection.query(query_embeddings=[query_embedding],n_results=top_k,    )returnresults["documents"][0]defrerank(query: str, retrieved_chunks: List[str], top_k: int) ->List[str]:"""重排"""# 创建CrossEncoder模型,使用预训练的中文模型,可以根据需要选择不同的模型cross_encoder = CrossEncoder("cross-encoder/mmarco-mMiniLMv2-L12-H384-v1")pairs = [(query, chunk) forchunkinretrieved_chunks]# CrossEncoder模型会对每一对(query, chunk)进行打分,分数越高表示query和chunk的相关性越高scores = cross_encoder.predict(pairs)# 将召回的片段和分数放到一起chunk_with_score_list = [(chunk, score) forchunk, scoreinzip(retrieved_chunks, scores)]# 根据分数进行排序,分数高的排在前面chunk_with_score_list.sort(key=lambdax: x[1], reverse=True)return [chunkforchunk, _inchunk_with_score_list][:top_k]defgenerate(query: str, retrieved_chunks: List[str]) ->str:"""生成答案"""# 将query和retrieved_chunks拼接成一个prompt,作为输入给语言模型prompt = f"""你是一位知识助手,请根据用户的问题和下列片段生成准确的回答。    用户的问题:{query}    相关片段:    {"\n\n".join(retrieved_chunks)}        请基于以上内容作答,不要编造信息。    """print(f"{prompt}\n\n---\n")client = OpenAI(api_key=os.environ.get('DEEPSEEK_API_KEY'),base_url="https://api.deepseek.com"    )response = client.chat.completions.create(model="deepseek-chat",messages=[            {"role": "user", "content": prompt},        ],temperature=0,stream=False    )returnresponse.choices[0].message.contentif__name__ == '__main__':# 分片chunks = split_into_chunks("doc.md")# 索引embeddings = [embed_chunk(chunk) forchunkinchunks]save_embeddings(chunks, embeddings)query = "哆啦A梦使用的3个秘密道具是什么?"# 召回retrieved_chunks = retrieve(query)# 重排ret = rerank(query, retrieved_chunks, 3)# 生成答案answer = generate(query, ret)print("答案:", answer)
  3. 打印结果(回答结果是非常准确的,即回答了三个道具,还说出了它们的三个特性)

    你是一位知识助手,请根据用户的问题和下列片段生成准确的回答。用户的问题:哆啦A梦使用的3个秘密道具是什么?相关片段:三件秘密道具分别是:可以临时赋予超级战力的“复制斗篷”,能暂停时间五秒的“时间停止手表”,以及可在一分钟中完成一年修行的“精神与时光屋便携版”。大雄被推进精神屋内,在其中接受密集的训练,虽然只有几分钟现实时间,他却经历了整整一年的苦修。刚开始他依旧软弱,想放弃、想逃跑,但当他想起静香、父母,还有哆啦A梦那坚定的眼神时,他终于咬牙坚持了下来。出来之后,他的身体与精神都焕然一新,眼神中多了一份成熟与自信。最终战在黑暗赛亚人的空中要塞前爆发,特兰克斯率先出击,释放全力与敌人正面对决。哆啦A梦则用任意门和道具支援,从各个方向制造混乱,尽量压制敌人的时空能力。但黑暗赛亚人太过强大,仅凭特兰克斯一人根本无法压制,更别说击败。就在特兰克斯即将被击倒之际,大雄披上复制斗篷、冲破恐惧从高空跃下。他的拳头燃烧着金色光焰,目标直指敌人心脏。战后,未来世界开始恢复,植物重新生长,人类重建家园。特兰克斯告别时紧紧握住大雄的手,说:“你是我见过最特别的战士。”哆啦A梦也为大雄感到骄傲,说他终于真正成长了一次。三人站在山丘上,看着远方重新明亮的地平线,心中感受到从未有过的安宁。随后,哆啦A梦与大雄乘坐时光机返回了属于他们的那个年代,一切仿佛又恢复平静。请基于以上内容作答,不要编造信息。---答案: 根据提供的片段,哆啦A梦使用的三个秘密道具分别是:1. **复制斗篷**(可临时赋予超级战力)2. **时间停止手表**(能暂停时间五秒)3. **精神与时光屋便携版**(可在一分钟内完成一年修行)
  4. 知识库文档:doc.md

    # 哆啦A梦与超级赛亚人:时空之战在一个寻常的午后,大雄依旧坐在书桌前发呆,作业堆得像山,连第一页都没动。哆啦A梦在一旁翻着漫画,时不时叹口气,觉得这孩子还是一如既往的不靠谱。正当他们的生活照常进行时,一道强光突然从天而降,整个房间震动不已。光芒中走出一名金发少年,身披战甲、气势惊人,他就是来自未来的超级赛亚人——特兰克斯。他一出现便说出了惊人的话:未来的地球即将被黑暗势力摧毁,他来此是为了寻求哆啦A梦的帮助。哆啦A梦与大雄听后大惊,但也从特兰克斯坚定的眼神中读出了不容拒绝的决心。特兰克斯解释说,未来的敌人并非普通反派,而是一个名叫“黑暗赛亚人”的存在,他由邪恶科学家复制了贝吉塔的基因并加以改造,实力超乎想象。这个敌人不仅拥有赛亚人战斗力,还能操纵扭曲的时间能量,几乎无人可敌。特兰克斯已经独自战斗多年,但每一次都以惨败告终。他说:“科技,是我那个时代唯一缺失的武器,而你们,正好拥有它。”于是,哆啦A梦带着特兰克斯与大雄启动时光机,穿越到了那个即将崩溃的未来世界。眼前的景象令人震撼:城市沦为废墟,大地裂痕纵横,天空中浮动着压抑的黑雾。特兰克斯说,这正是黑暗赛亚人带来的结果,一切生命几乎都被抹杀,只剩他在苦苦支撑。大雄虽感到恐惧,但看到无辜的人类遭殃,内心逐渐燃起斗志。哆啦A梦则冷静地分析局势,决定使用他最强的三样秘密道具来对抗黑暗势力。三件秘密道具分别是:可以临时赋予超级战力的“复制斗篷”,能暂停时间五秒的“时间停止手表”,以及可在一分钟中完成一年修行的“精神与时光屋便携版”。大雄被推进精神屋内,在其中接受密集的训练,虽然只有几分钟现实时间,他却经历了整整一年的苦修。刚开始他依旧软弱,想放弃、想逃跑,但当他想起静香、父母,还有哆啦A梦那坚定的眼神时,他终于咬牙坚持了下来。出来之后,他的身体与精神都焕然一新,眼神中多了一份成熟与自信。最终战在黑暗赛亚人的空中要塞前爆发,特兰克斯率先出击,释放全力与敌人正面对决。哆啦A梦则用任意门和道具支援,从各个方向制造混乱,尽量压制敌人的时空能力。但黑暗赛亚人太过强大,仅凭特兰克斯一人根本无法压制,更别说击败。就在特兰克斯即将被击倒之际,大雄披上复制斗篷、冲破恐惧从高空跃下。他的拳头燃烧着金色光焰,目标直指敌人心脏。时间停止装置在关键时刻启动,世界陷入静止,大雄用这个短短五秒接近了敌人的盲点。他集中全力,一记重拳击穿了黑暗赛亚人的能量核心,引发巨大的能量反冲。黑暗赛亚人尖叫着化为碎光,天空中的黑雾瞬间散去,阳光重新洒落大地。特兰克斯倒在地上,看着眼前这个曾经懦弱的少年,露出了欣慰的笑容。他知道,这一次,是大雄救了世界。战后,未来世界开始恢复,植物重新生长,人类重建家园。特兰克斯告别时紧紧握住大雄的手,说:“你是我见过最特别的战士。”哆啦A梦也为大雄感到骄傲,说他终于真正成长了一次。三人站在山丘上,看着远方重新明亮的地平线,心中感受到从未有过的安宁。随后,哆啦A梦与大雄乘坐时光机返回了属于他们的那个年代,一切仿佛又恢复平静。回到现代后,大雄仿佛变了一个人,不再轻易抱怨、不再逃避责任。他认真写完作业,帮妈妈买菜,甚至主动练习体育,哆啦A梦惊讶得说不出话来。他知道,这不是一时兴起,而是大雄真正内心成长的结果。大雄有时会望着天空出神,仿佛还能看见未来世界的那一片废墟与重生的希望。他不会说出来,但他心中永远铭记那一战。几天后,电视新闻中突然出现一则画面:一位金发少年在街头击退了失控的机器人,引发市民围观与猜测。大雄放下手中的课本,望向哆啦A梦,两人心照不宣地笑了。也许,特兰克斯又回来了,也许,新的敌人正在逼近。冒险从未真正结束,而他们,早已准备好了。无论时空如何动荡,他们将永远并肩作战。

最新文章

随机文章