1、加载
Document Loader文档加载器
在 langchain_community. document_loaders 里有很多种文档加载器
from langchain_community. document_loaders import ***
1、纯文本加载器:TextLoader,纯文本(不包含任何粗体、下划线、字号格式)
loader = TextLoader("./demo.txt") # 创建TextLoader实例,参数是文件路径
docs = loader.load() # 加载文件内容
将文本文件的内容加载到
documents
列表中。每个文档是一个Document
对象,包含文本内容(page_content
属性)和元数据(metadata
属性)。
metadata
属性存储的是与文档相关的一些额外信息,如文件的来源、创建时间、文件大小、作者等。元数据在文档管理和检索中非常有用。例如,在构建文档检索系统时,你可以根据元数据(如文件来源、创建时间)对文档进行筛选和排序,从而快速定位到你需要的文档。此外,元数据还可以用于数据质量管理和版本控制等方面。
2、PDF加载器:PyPDFLoader
pip install pypdf (专门处理PDF文件的库)
PyPDFLoader会在这个库的基础上工作
loader = PyPDFLoader("./论文介绍.pdf")
docs = loader.load()
除了加载本地文件内容,还可以加载网络上的内容。
3、维基百科加载器:WikipediaLoader
pip install wikipedia
loader = Wikipedia(query="颐和园",lang="zh",loader_max_docs=3) #参数query传要搜索的词条名
docs = loader.load()
2、分割
pip install langchain-text-splitters
from langchain-text-splitters import RecursiveCharacterTextSplitter
RecursiveCharacterTextSplitter:字符递归分割器
可以指定根据什么符号分割
- chunk_size:默认值为
4000
,表示将文本分割成的每个块的最大长度。 - chunk_overlap:默认值为
200
,表示相邻文本块之间重叠的字符数。
重叠是为了保持文本的上下文连贯性和完整性。
如果没有重叠,就会丢失相邻文本块之间的连接,一些重要信息可能就在分界线处。
separators:字符列表,指定根据什么字符分割,排在前面的字符会先被选择来分割文本。
如果分割出来的文本块仍然超过了最大长度chunk_size,就选择列表里的下一个分隔符进行分割。
以此类推。
默认时:["\n\n", "\n", " ", ""]最后一个是空字符串,相当于哪处都可以分割。但是不太适合中文。
可以设置成这样:
分割时:text = text_splitter.split_documents(docs)
分割后仍然是Document组成的列表,但是每个Document的page_content的长度变短了。
3、嵌入
向量里要包含文本之间的语法语义等关系,
相似的文本所对应的嵌入向量在向量空间里距离会更近。
langchain可以借助嵌入模型,把文本转成向量。
以openai服务方提供的嵌入模型为例:
embed_documents传入字符串列表,返回向量列表。每个字符串都有对应的一个向量。向量维度由模型定。
如果希望嵌入向量的维度更小,可以指定dimensions参数。(有些模型在设计上可能支持不同维度的输出,以适应不同的应用场景和资源限制。虽然整体上模型有其默认的维度设定,但为了满足用户多样化的需求,允许在一定范围内指定维度。)
4、储存
传统数据库:
精准匹配,适合查询结构化信息(有预定义数据模型的信息)
向量数据库:
相似性搜索,根据向量之间的距离,找到语义上相似或相关的内容,即是关键词不完全匹配。
适合处理非结构化数据。(非结构化数据指数据结构不规则或不完整,没有预定义的数据模型,不方便用数据库二维表逻辑结构来表现的数据。)
向量数据库有很多种,以Faiss为例:
pip install faiss-cpu
FAISS.from_documents:把文档块转换成向量,并储存进向量库里
第一个参数传入切割后的文档列表,第二个参数传入嵌入模型的实例
返回值是向量数据库
5、检索
retriever = db.as_retriever() #得到检索器
retrieved_docs = retriever.invoke("***") #返回检索得到的文档列表,越相似的排在越前面
带记忆的检索增强生成对话链:ConversationalRetrievalChain
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai.embeddings import OpenAIEmbeddings
from langchain_community.vectorstores import FAISSfrom langchain_openai import ChatOpenAI
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChainimport os# 加载
loader = TextLoader("./京剧介绍.txt",encoding="utf-8") #加载器,可能要指定编码格式
docs = loader.load()
# 分割
text_splitter = RecursiveCharacterTextSplitter() #分割器
texts = text_splitter.split_documents(docs)
# 嵌入模型
embeddings_model = OpenAIEmbeddings(api_key=os.getenv("OPENAI_API_KEY"),base_url="https://api.gptsapi.net/v1") #注意:指定模型都要指定api_key,不是官方的还要加上base_url
# 嵌入并储存
db = FAISS.from_documents(texts,embeddings_model)
# 检索
retriever = db.as_retriever() #检索器# 模型
model = ChatOpenAI(model="gpt-3.5-turbo",api_key=os.getenv("OPENAI_API_KEY"),base_url="https://api.gptsapi.net/v1")# 记忆
memory = ConversationBufferMemory(return_messages=True,memory_key="chat_history",output_key="answer")# 创建对话链
chain = ConversationalRetrievalChain.from_llm(llm = model,retriever = retriever,memory = memory,return_source_documents=True
)result = chain.invoke({"chat_history":memory,"question":"介绍京剧中的旦角"}
)print(result)
print(result["answer"]) #不是result.answer,'dict' object has no attribute 'answer'
print(result["source_documents"])
1、memory = ConversationBufferMemory(return_messages=True,
memory_key="chat_history",
output_key="answer")
因为ConversationalRetrivalChain里储存历史消息的变量名叫chat_history,
AI的输出的变量名叫answer,所以更改
memory_key和output_key的默认值。
变量名一致,这样就能确保对话历史在不同组件间准确传递和使用。
2、
chain = ConversationalRetrivalChain.from_llm(
llm = model,
retriever = retriever,
memory = memory
)
result = chain.invoke(
{
"chat_history":memory,
"question":"介绍京剧中的旦角"
}
)
from_llm:“工厂模式”,允许做更多的定制化,适合更复杂的操作。
特别指定"chat_history":memory,允许用户在每次询问时灵活指定不同的对话历史,以便更好地处理复杂的问答场景。
返回值是包含chat-history、question和answer的字典:
如果希望返回结果里不仅AI的回复,还有参考的外部文档里的原片段,
可以设置参数return_source_documents=True
这可以帮我们判断模型的回答是真实可信有依据的,还是产自幻觉
把外部文档传递给模型的不同方式:
默认是所有片段一股脑塞给模型,即Stuff填充,但是当片段很长很多时,可能超过模型的上下文窗口限制。
1、Map-Reduce:
Map阶段,每个相关片段会单独传递给模型,让模型根据各个片段对查询分别作出回答。
Reduce阶段,各个回答会被整合起来,形成一个统一的信息合集,作为输入再传给模型。
最后得到一个连贯的、结合了多方面信息的回答。
综合分析理解
2、Refine 优化:
从第一个片段开始,得到模型针对查询的回答。再把这个回答,连带查询,以及第二个片段一块给模型,让模型对回答进行优化。以此类推。
每次模型都会结合下一片段,对已有的回答进行优化给出新的回答。
逐步深入分析、不断完善回答
3、Map-Rerank:
Map阶段同上。但除了让模型根据各个片段生成回答之外,还会要求模型对这些回答进行评估,即为每个回答打分。
Rerank阶段,系统会找出得分最高的一个回答或几个回答的合并结果作为最终回答。
缺点:不会整合不同片段之间的信息。
从不同角度生成回答,并找出最合理的那个
如何使用?
在from_llm中指定chain_type参数即可。默认为"stuff"。
chain_type(
对话检索链的类型)
参数决定了如何将对话中的问题与检索到的文档信息进行结合和处理,以生成最终的回答,所以是关于对话检索链的类型划分。