文章目录
前言
LangChain 是一个基于语言模型开发应用程序的强大框架,旨在帮助开发人员简化与大模型交互、数据检索以及将不同功能模块串联起来以完成复杂任务的过程。而 RAG(Retrieval-Augmented Generation,检索增强生成)技术则是 LangChain 框架中一项重要的技术,它结合了检索和生成两种能力,以提高自然语言处理任务的效率和质量。 本文将对 LangChain RAG 技术从原理及应用进行详细介绍。
一、Langchain 框架概述
LangChain 是一个由 Lang.AI(语言人工智能)开发的开源框架,用于开发基于大语言模型(LLM)的应用程序。它提供了丰富的工具、组件和接口,使开发人员能够轻松构建上下文感知和具备逻辑推理能力的应用程序。LangChain 的核心架构主要包括基础层、能力层和应用层,其中基础层包括 Models、LLM、Index 三层,能力层包括 Chains、Memory、Tool 三部分,应用层则构建各种有价值的服务。
二、大模型-RAG技术原理
RAG
技术结合了 信息检索(Retrieval)和自然语言生成(Generation) 两种能力,以提高文本处理任务的效率和质量。其核心原理如下:
- 信息检索:
RAG
通过检索模块从大规模语料库中检索与查询相关的文本片段。这些文本片段是生成高质量输出的基础。 - 自然语言生成: 基于检索到的文本片段,
RAG
的生成模块利用自然语言生成模型(如大型语言模型)进行推理和生成,从而产生符合用户需求的自然语言文本。
RAG(检索增强生成技术)
通过整合外部信息检索来提升大型语言模型的回答质量和知识覆盖。RAG
技术可以解决大语言模型的幻觉问题、时效性问题和数据安全问题。
RAG技术的应用分为两个主要阶段:
- 数据准备阶段
- 检索生成阶段
数据准备阶段涉及将私有数据向量化并构建索引存入数据库;
检索生成阶段则包括问题向量化、查询匹配数据、获取索引数据、将数据注入Prompt和LLM生成答案。
RAG技术的优势
- 减少大模型的幻觉问题,提升生成文本的准确性和权威性。
- 即时更新检索库,提供最新的信息,满足高时效性的需求。
- RAG模型的答案来源可查,其回复具有高度的解释性和透明度,这有助于减少误解和提高用户信任。
- 可以通过私有化部署,确保企业敏感数据的安全。
三、应用示例
1.RAG案例一(私有文档直接读取-问答)
# 1.加载文档
from langchain_community.document_loaders import TextLoader
# 2.导入分割类
from langchain.text_splitter import CharacterTextSplitter
# 3.引入向量化的类
from langchain_community.vectorstores import Chroma
from langchain.embeddings.dashscope import DashScopeEmbeddings
# 5.检索问答
from langchain.chains import RetrievalQA
from langchain_community.llms import Tongyi
# 1.加载文档
# 实例化TextLoader对象
loader = TextLoader("SJDJYBM-01-data.txt",encoding="utf-8")
#loader = TextLoader("NBA新闻.txt",encoding="utf-8")
# 加载文档
docs = loader.load()
print(docs)
# 2.分割文档
# 实例化
text_splitter = CharacterTextSplitter(separator="\n",chunk_size=200, chunk_overlap=0)
doc = text_splitter.split_documents(docs)
print(doc)
# 3.实现文档向量化
# 实例化
embeddings = DashScopeEmbeddings()
# 4.构建知识库
# 创建向量数据库,向量化文档
db = Chroma.from_documents(doc,embeddings, persist_directory="./chroma123")
db.persist()
# 5.引入大模型 - TongYi
llm = Tongyi()
# 6.基于知识库,使用大模型问答
# 实例化
qa = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=db.as_retriever())
ret = qa.invoke("火龙果 SJDJYBM-01 风格密文是多少?")
# ret = qa.invoke("2025年NBA冠军是哪支队伍,介绍一下情况?")
# ret = qa.invoke("2025年NBA获胜的队伍是哪个,介绍一下情况?")
#ret = qa.invoke("2025年NBA获胜的队伍是哪个,最有价值球员MVP 是谁,简短回答,不要回答其他的?")
print(ret)
# 输出:
#{'query': '火龙果 SJDJYBM-01 风格密文是多少?', 'result': '火龙果的密文是 huolongguo689。根据给定的格式,火龙果 SJDJYBM-01 风格密文表示为:\n\n火龙果 - huolongguo689'}
SJDJYBM-01-data.txt
这是一个物品的加密字典对照表,以"明文-密文"的形式呈现。
以下的加密风格为SJDJYBM-01形式。
比如:
橙子加密后的密文是chengzi102,表示为:橙子-chengzi102
苹果加密后的密文是pingguo357,表示为:苹果-pingguo357
苹果-pingguo357
香蕉-xiangjiao849
橙子-chengzi102
西瓜-xigua561
葡萄-putao473
柠檬-ningmeng298
......
2.RAG案例二(Vue上传文件+结合文件内容回答问题)
上传文件配置步骤:
1.在项目根目录下创建media文件夹
2.settings.py配置#settings.py ... import os MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = '/media/'
3.urls.py配置
from django.contrib import admin from django.urls import path,include from django.conf import settings from django.conf.urls.static import static urlpatterns = [ path('admin/', admin.site.urls), path('', include('app.urls')), path('', include('news.urls')) ] urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
核心逻辑代码:
class RagView(APIView):
def post(self, request):
print(request.FILES)
description = request.data.get('description')
if 'filename' in request.FILES:
myfile = request.FILES['filename']
# 将文件保存到MEDIA_ROOT目录下
# file_path = os.path.join('uploads', myfile.name)
with open(f'{
settings.MEDIA_ROOT}/{
myfile.name}', 'wb+') as destination:
for chunk in myfile.chunks():
destination.write(chunk)
# 获取文件的绝对路径
file_path = f'{
settings.MEDIA_ROOT}/{
myfile.name}'
print(file_path)
# 1.加载文档
# 实例化TextLoader对象
loader = TextLoader(file_path, encoding="utf-8")
# 加载文档
docs = loader.load()
print(docs)
# 2.分割文档
# 实例化
text_splitter = CharacterTextSplitter(separator="\n", chunk_size=200, chunk_overlap=0)
doc = text_splitter.split_documents(docs)
print(doc)
# 3.实现文档向量化
# 实例化
embeddings = DashScopeEmbeddings()
# 4.构建知识库
# 创建向量数据库,向量化文档
db = Chroma.from_documents(doc, embeddings, persist_directory="./chroma123")
db.persist()
# 5.引入大模型 - TongYi
llm = Tongyi()
# 6.基于知识库,使用大模型问答
# 实例化
qa = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=db.as_retriever())
ret = qa.invoke(description)
print(ret)
return Response({
'file_path': file_path,'result':ret}, status=200)
else:
return Response({
'error': 'No file was uploaded.'}, status=400)
Vue页面:
<template>
<div>
<input type="file" @change="uploadFile" />
<input type="text" v-model="description" placeholder="请输入文件描述" />
<button @click="submitFile">上传</button>
</div>
{
{ret}}
</template>
<script>
import http from "../http";
export default {
data() {
return {
myfile: null,
description: "",
ret:'',
};
},
methods: {
uploadFile(event) {
this.myfile = event.target.files[0];
},
submitFile() {
if (!this.myfile) {
alert("请选择一个文件");
return;
}
const formData = new FormData();
formData.append("filename", this.myfile);
formData.append("description", this.description);
http.post("/upload/", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((response) => {
alert("文件上传成功");
this.ret = response.data.result;
})
.catch((error) => {
alert("文件上传失败");
});
},
},
};
</script>
效果图:
3.RAG案例三(Vue秒传文件+结合文件内容回答问题)
通过比对哈希值判断是否是已经上传过的文件,是-则不需要重新上传,否-正常上传文件,以此来实现用户层面的秒传效果。
核心逻辑代码:
class RAGMCView(APIView):
def post(self, request):
print(request.FILES)
description = request.data.get('description')
hash_code = request.data.get('hashcode')
myfile = FileModel.objects.filter(hash_code=hash_code).first()
if myfile:
print("111")
# 获取文件的绝对路径
file_path = myfile.file_path
loader = TextLoader(file_path, encoding="utf-8")
docs = loader.load()
text_splitter = CharacterTextSplitter(separator="\n", chunk_size=200, chunk_overlap=0)
doc = text_splitter.split_documents(docs)
embeddings = DashScopeEmbeddings()
db = Chroma.from_documents(doc, embeddings, persist_directory="./chroma123")
db.persist()
llm = Tongyi()
qa = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=db.as_retriever())
ret = qa.invoke(description)
print(ret)
return Response({
"message":"RAG实现秒传效果,hash值相同不必重新上传文件","result":ret,"code":200})
else:
print("222")
if 'filename' in request.FILES:
file_name = request.FILES['filename']
with open(f'{
settings.MEDIA_ROOT}/{
file_name.name}', 'wb+') as destination:
for chunk in file_name.chunks():
destination.write(chunk)
# 获取文件的绝对路径
file_path = f'{
settings.MEDIA_ROOT}/{
file_name.name}'
# 存到数据库
file_model = FileModel.objects.create(file_path=file_path, hash_code=hash_code, name=file_name.name)
file_model.save()
loader = TextLoader(file_path, encoding="utf-8")
docs = loader.load()
text_splitter = CharacterTextSplitter(separator="\n", chunk_size=200, chunk_overlap=0)
doc = text_splitter.split_documents(docs)
embeddings = DashScopeEmbeddings()
db = Chroma.from_documents(doc, embeddings, persist_directory="./chroma123")
db.persist()
llm = Tongyi()
qa = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=db.as_retriever())
ret = qa.invoke(description)
return Response({
"message":"RAG实现秒传效果,hash值不同要重新上传文件","result":ret,"code":200})
else:
print("333")
return Response({
"message": "No file was uploaded", "code": 400})
Vue页面:
<template>
<div>
<input type="file" @change="onFileChange" ref="fileInput">
<input type="text" v-model="description" placeholder="请输入文件描述" />
<button @click="calculateHash">计算哈希值</button>
<button style="margin-left: 10px;" @click="submitFile">上传</button>
<p v-if="hashResult">文件的SHA-256哈希值: {
{ hashResult }}</p>
</div>
</template>
<script>
import http from "../http";
export default {
data() {
return {
file: null,
description: "",
hashResult: null,
};
},
methods: {
onFileChange(e) {
this.file = e.target.files[0];
},
submitFile() {
if (!this.file) {
alert("请选择一个文件");
return;
}
console.log("this.file.size---->");
// 1364B / 1024 = 1.36KB,实际需要 2KB 大小的空间,规则盘块
console.log(this.file.size);
const formData = new FormData();
formData.append("filename", this.file);
// formData.append("filename", this.file.size);
formData.append("description", this.description);
formData.append("hashcode", this.hashResult);
http.post("/uploadmc/", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((response) => {
alert("文件上传成功");
this.ret = response.data.result;
})
.catch((error) => {
alert("文件上传失败");
});
},
async calculateHash() {
if (!this.file) {
alert('请选择一个文件');
return;
}
try {
const hash = await this.getFileSha256(this.file);
this.hashResult = hash;
} catch (error) {
console.error('Error calculating SHA-256 hash:', error);
}
},
async getFileSha256(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = async () => {
try {
const fileContent = new Uint8Array(reader.result);
const hashBuffer = await crypto.subtle.digest('SHA-256', fileContent);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => ('00' + b.toString(16)).slice(-2)).join('');
resolve(hashHex);
} catch (error) {
reject(error);
}
};
reader.readAsArrayBuffer(file);
});
}
}
};
</script>
效果图: