环境源配置
提示:这里可以添加技术概要
在已完成 ChatGLM3 的部署基础上,还需要安装以下依赖包:
pip install langchain==0.0.292
pip install gradio==4.4.0
pip install chromadb==0.4.15
pip install sentence-transformers==2.2.2
pip install unstructured==0.10.30
pip install markdown==3.3.7
向量转化
文件处理方法
get_files 方法用于遍历指定目录 dir_path 及其所有子目录,寻找并收集所有以 .md 或 .txt 结尾的文件。它利用 os.walk 函数递归地访问每一个子目录,检查每个文件名是否符合指定的后缀名。如果符合,就使用 os.path.join 将目录路径和文件名组合成完整的文件路径,并将这个路径添加到 file_list 列表中。方法最终返回这个列表,包含了所有找到的符合条件的文件路径。这种方式使得能够系统地从复杂的文件系统中提取特定类型的文件,为后续的文件处理或数据加载提供基础。
def get_files(self,dir_path: str) -> List[str]:
file_list = []
for filepath, dirnames, filenames in os.walk(dir_path):
for filename in filenames:
if filename.endswith(".md") or filename.endswith(".txt"):
file_list.append(os.path.join(filepath, filename))
return file_list
文档到嵌入向量的转换
docs2embedding 方法将一组文档转换为嵌入向量,主要通过分批处理以优化效率和内存使用。首先,它将文档集每四个一组进行批处理,并使用 embed_documents 方法计算这些批次的嵌入向量,然后将这些向量追加到一个列表中。如果文档总数不是四的倍数,该方法将处理剩余的文档,通过添加空字符串以填满最后一批次到四个文档,并计算这些文档的嵌入向量。
def docs2embedding(self, docs):
emb = []
for i in tqdm(range(len(docs) // 4)):
emb += self.embeddings.embed_documents(docs[i * 4: i * 4 + 4])
if len(docs) % 4 != 0:
residue = docs[-(len(docs) % 4):] + [" " for _ in range(4 - len(docs) % 4)]
emb += self.embeddings.embed_documents(residue)[:len(docs) % 4]
return emb
保存和加载向量数据库
save_vector_db_to_local 方法用于将当前聊天机器人的向量数据库及相关数据保存到本地文件系统中。
def save_vector_db_to_local(self):
# 获取当前时间并格式化为特定字符串格式,用于创建唯一文件夹名
now = datetime.now()
folder_name = now.strftime("%Y-%m-%d_%H-%M-%S-%f")
os.mkdir(f"{self.db_base_path}/{folder_name}") # 在指定的基路径下创建新文件夹
# 保存向量索引文件
faiss.write_index(self.vector_db, f"{self.db_base_path}/{folder_name}/db.index")
# 将存储文本数据的string_db序列化并保存到文件
byte_stream = pickle.dumps(self.string_db)
with open(f"{self.db_base_path}/{folder_name}/db.string", "wb") as file:
file.write(byte_stream)
# 保存包含处理文件信息的文本文件
with open(f"{self.db_base_path}/{folder_name}/name.txt", "w", encoding="utf-8") as file:
file.write(self.files)
load_vector_db_from_local 方法用于从本地存储加载之前保存的向量数据库及其相关数据。
def load_vector_db_from_local(self, index_name: str):
# 从文件中读取并反序列化文本数据
with open(f"{self.db_base_path}/{index_name}/db.string", "rb") as file:
byte_stream = file.read()
self.string_db = pickle.loads(byte_stream)
# 加载向量索引
self.vector_db = faiss.read_index(f"{self.db_base_path}/{index_name}/db.index")
# 读取包含处理文件信息的文本文件
self.files = open(f"{self.db_base_path}/{index_name}/name.txt", 'r', encoding='utf-8').read()
查询和索引构建
文本加载和预处理
首先配置一个文本分割器以适应不同大小的文档,并为每种文档类型选择合适的加载器来处理文件,将文件的内容转化为纯文本格式,这确保了无论文档的格式如何,都能被正确加载并准备好进行下一步处理。
text_splitter = RecursiveCharacterTextSplitter(chunk_size=325, chunk_overlap=6, separators=["\n\n", "\n", "。", "!", ",", " ", ""])
docs = []
for file in file_list:
ext_name = os.path.splitext(file)[-1]
if ext_name == ".pptx":
loader = UnstructuredPowerPointLoader(file)
elif ext_name == ".docx":
loader = UnstructuredWordDocumentLoader(file)
elif ext_name == ".pdf":
loader = UnstructuredPDFLoader(file)
else:
loader = UnstructuredFileLoader(file)
doc = loader.load()
doc[0].page_content = self.filter_space(doc[0].page_content)
doc = text_splitter.split_documents(doc)
docs.extend(doc)
文档分割与向量数据库初始化
对加载的文档进行分割,并根据分割后的文档内容初始化或更新向量数据库。如果文档内容为空,则直接返回 False 表示初始化失败;否则,处理文档的嵌入向量,并将它们添加到向量数据库中。
if len(docs) == 0:
return False
if self.vector_db is None:
self.files = ", ".join([item.split("/")[-1] for item in file_list])
emb = self.docs2embedding([x.page_content for x in docs])
self.vector_db = faiss.IndexFlatL2(self.embeddings_size)
self.vector_db.add(np.array(emb))
self.string_db = docs
else:
self.files = self.files + ", " + ", ".join([item.split("/")[-1] for item in file_list])
emb = self.docs2embedding([x.page_content for x in docs])
self.vector_db.add(np.array(emb))
self.string_db += docs
return True