muryshev's picture
update
1d40914
from typing import Tuple, List, Dict, Union
import faiss
import pandas as pd
import numpy as np
import torch
from business_transaction_map.common.constants import COLUMN_DOC_NAME
from business_transaction_map.common.constants import COLUMN_EMBEDDING
from business_transaction_map.common.constants import COLUMN_LABELS_STR
from business_transaction_map.common.constants import COLUMN_NAMES
from business_transaction_map.common.constants import COLUMN_TABLE_NAME
from business_transaction_map.common.constants import COLUMN_TYPE_DOC_MAP
class FaissVectorDatabase:
"""Класс для взаимодействия между векторами и информацией о них"""
def __init__(self, path_to_metadata: str):
self.path_to_metadata = path_to_metadata
self.__load_metadata()
self.__crate_index()
def __load_metadata(self):
"""Load the metadata file."""
self.df = pd.read_pickle(self.path_to_metadata)
self.df = self.df.where(pd.notna(self.df), None)
def __crate_index(self):
"""Create the faiss index."""
embeddings = np.array(self.df[COLUMN_EMBEDDING].tolist())
dim = embeddings.shape[1]
self.index = faiss.IndexFlatL2(dim)
self.index.add(embeddings)
def _paragraph_content2(self, pattern: str, doc_number: str, ind: int, shape: int) -> Tuple[List, int]:
"""
Функция возвращает контент параграфа. Если в параграфе были подпункты через "-" или буквы "а, б"
Args:
pattern: Паттерн поиска.
doc_number: Номер документа.
ind: Индекс строки в DataFrame.
shape: Размер DataFrame при котором будет возвращаться пустой список.
Returns:
Возвращает список подразделов.
Examples:
3.1. Параграф:
1) - Содержание 1;
2) - Содержание 2;
3) - Содержание 3;
"""
df = self.df[(self.df['DocNumber'] == doc_number) & (self.df['Pargaraph'].str.match(pattern, na=False))]
if self.df.iloc[ind]['Duplicate'] is not None:
df = df[df['Duplicate'] == self.df.iloc[ind]['Duplicate']]
if df.shape[0] <= shape:
return [], None
header_text = df.iloc[0]['Text']
start_index_paragraph = df.index[0]
paragraphs = []
for ind2, (_, row) in enumerate(df.iterrows()):
text = row['Text']
if ind2 == 0:
text = text.replace(f'{header_text}', f'{header_text}\n')
else:
text = text.replace(f'{header_text}', '') + '\n'
paragraphs.append(text)
return paragraphs, start_index_paragraph
def _paragraph_content(self, pattern: str, doc_number: str, ind: int, shape: int) -> Tuple[List, int]:
"""
Функция возвращает контент параграфа. Если в параграфе были подпункты через "-" или буквы "а, б"
Args:
pattern: Паттерн поиска.
doc_number: Номер документа.
ind: Индекс строки в DataFrame.
shape: Размер DataFrame при котором будет возвращаться пустой список.
Returns:
Возвращает список подразделов.
Examples:
3.1. Параграф:
1) - Содержание 1;
2) - Содержание 2;
3) - Содержание 3;
"""
df = self.df[(self.df['DocNumber'] == doc_number) & (self.df['Pargaraph'].str.match(pattern, na=False))]
if self.df.iloc[ind]['Duplicate'] is not None:
df = df[df['Duplicate'] == self.df.iloc[ind]['Duplicate']]
if df.shape[0] <= shape:
return [], None
header_text = df.iloc[0]['Text']
start_index_paragraph = df.index[0]
paragraphs = []
for ind2, (_, row) in enumerate(df.iterrows()):
text = row['Text']
if ind2 == 0:
text = text.replace(f'{header_text}', f'{header_text}\n')
else:
text = text.replace(f'{header_text}', '') + '\n'
paragraphs.append(text)
return paragraphs, start_index_paragraph
def _get_top_paragraph(self):
pass
def _search_other_info(self, ind, doc_number):
other_info = []
start_index_paragraph = []
if self.df.iloc[ind]['PartLevel1'] is not None:
if 'Table' in str(self.df.iloc[ind]['PartLevel1']):
return [], ind
if self.df.iloc[ind]['Appendix'] is not None:
df = self.df[(self.df['DocNumber'] == doc_number) & (self.df['Appendix'] == self.df.iloc[ind]['Appendix'])]
other_info.append(f'{df.iloc[0]["Text"]}')
return other_info, ind
else:
if self.df.iloc[ind]['Pargaraph'] is None:
pass
else:
pattern = self.df.iloc[ind]["Pargaraph"].replace(".", r"\.")
paragraph, start_index_paragraph = self._paragraph_content(fr'^{pattern}?$', doc_number, ind, 1)
if not paragraph and self.df.iloc[ind]['LevelParagraph'] != '0':
pattern = self.df.iloc[ind]["Pargaraph"]
pattern = pattern.split('.')
pattern = [elem for elem in pattern if elem]
pattern = '.'.join(pattern[:-1])
pattern = f'^{pattern}\\.\\d.?$'
paragraph, start_index_paragraph = self._paragraph_content2(pattern, doc_number, ind, 0)
else:
pattern = f'^{pattern}\\d.?$'
paragraph, start_index_paragraph = self._paragraph_content2(pattern, doc_number, ind, 0)
other_info.append(' '.join(paragraph))
return other_info, start_index_paragraph
def search(self, emb_query: torch.Tensor, k_neighbors: int, other_information: bool) -> dict:
"""
Метод ищет ответы на запрос
Args:
emb_query: Embedding вопроса.
k_neighbors: Количество ближайших ответов к вопросу.
other_information:
Returns:
Возвращает словарь с ответами и информацией об ответах.
"""
if len(emb_query.shape) != 2:
assert print('Не правильный размер вектора!')
distances, indexes = self.index.search(emb_query, k_neighbors)
answers = {}
for i, ind in enumerate(indexes[0]):
answers[i] = {}
answers[i][f'distance'] = distances[0][i]
answers[i][f'index_answer'] = ind
answers[i][f'doc_name'] = self.df.iloc[ind]['DocName']
answers[i][f'text_answer'] = self.df.iloc[ind]['Text']
doc_number = self.df.iloc[ind]['DocNumber']
if other_information:
other_info, start_index_paragraph = self._search_other_info(ind, doc_number)
answers[i][f'other_info'] = other_info
answers[i][f'start_index_paragraph'] = start_index_paragraph
return answers
def search_transaction_map(self, emb_query: torch.Tensor, k_neighbors: int) -> Dict[str, Union[str, int]]:
"""
Метод ищет ответы на запрос по картам проводок
Args:
emb_query: Embedding вопроса.
k_neighbors: Количество ближайших ответов к вопросу.
Returns:
Возвращает словарь с ответами и информацией об ответах.
Notes:
Будет возвращаться словарь вида
{
'distance': Дистанция между векторами
'index_answer': Индекс ответа как в df index
'doc_name': Наименование документа
'text_answer': Название таблицы / Названия файла
'labels': Метка для расчета метрик
'Columns': Наименования колонок в карте проводок
'TypeDocs': К кому разделу относится карта проводок (1С или SAP)
}
"""
if len(emb_query.shape) != 2:
assert print('Не правильный размер вектора!')
distances, indexes = self.index.search(emb_query, k_neighbors)
answers = {}
for i, ind in enumerate(indexes[0]):
answers[i] = {}
answers[i][f'distance'] = distances[0][i]
answers[i][f'index_answer'] = ind
answers[i][f'doc_name'] = self.df.iloc[ind][COLUMN_DOC_NAME]
answers[i][f'text_answer'] = self.df.iloc[ind][COLUMN_TABLE_NAME]
answers[i][COLUMN_LABELS_STR] = self.df.iloc[ind][COLUMN_LABELS_STR]
answers[i][COLUMN_NAMES] = self.df.iloc[ind][COLUMN_NAMES]
answers[i][COLUMN_TYPE_DOC_MAP] = self.df.iloc[ind][COLUMN_TYPE_DOC_MAP]
return answers