Spaces:
Sleeping
Sleeping
""" | |
Абстрактный базовый класс для стратегий чанкинга. | |
""" | |
import logging | |
from abc import ABC, abstractmethod | |
from ntr_fileparser import ParsedDocument | |
from ..models import DocumentAsEntity, LinkerEntity | |
from ..repositories import EntityRepository | |
from .models import Chunk | |
logger = logging.getLogger(__name__) | |
class ChunkingStrategy(ABC): | |
"""Абстрактный класс для стратегий чанкинга.""" | |
def chunk( | |
self, | |
document: ParsedDocument, | |
doc_entity: DocumentAsEntity, | |
) -> list[LinkerEntity]: | |
""" | |
Разбивает документ на чанки в соответствии со стратегией. | |
Args: | |
document: ParsedDocument для извлечения текста и структуры. | |
doc_entity: Сущность документа-владельца, к которой будут привязаны чанки. | |
Returns: | |
Список сущностей (чанки) | |
""" | |
raise NotImplementedError("Стратегия чанкинга должна реализовать метод chunk") | |
async def chunk_async( | |
self, | |
document: ParsedDocument, | |
doc_entity: DocumentAsEntity, | |
) -> list[LinkerEntity]: | |
""" | |
Асинхронно разбивает документ на чанки в соответствии со стратегией. | |
Args: | |
document: ParsedDocument для извлечения текста и структуры. | |
doc_entity: Сущность документа-владельца, к которой будут привязаны чанки. | |
Returns: | |
Список сущностей (чанки) | |
""" | |
logger.warning( | |
"Асинхронная стратегия чанкинга не реализована, вызывается синхронная" | |
) | |
return self.chunk(document, doc_entity) | |
def dechunk( | |
cls, | |
repository: EntityRepository, | |
filtered_entities: list[LinkerEntity], | |
) -> str: | |
""" | |
Собирает текст из отфильтрованных чанков к одному документу. | |
Args: | |
repository: Репозиторий (может понадобиться для получения доп. информации, | |
хотя в текущей реализации не используется). | |
filtered_entities: Список отфильтрованных сущностей (чанков), | |
относящихся к одному документу. | |
Returns: | |
Собранный текст из чанков. | |
""" | |
chunks = [e for e in filtered_entities if isinstance(e, Chunk)] | |
chunks.sort(key=lambda x: x.number_in_relation) | |
groups: list[list[Chunk]] = [] | |
for chunk in chunks: | |
if len(groups) == 0: | |
groups.append([chunk]) | |
continue | |
last_chunk = groups[-1][-1] | |
if chunk.number_in_relation == last_chunk.number_in_relation + 1: | |
groups[-1].append(chunk) | |
else: | |
groups.append([chunk]) | |
result = "" | |
previous_last_index = 0 | |
for group in groups: | |
if previous_last_index is not None: | |
missing_chunks = group[0].number_in_relation - previous_last_index - 1 | |
missing_string = f'\n_<...Пропущено {missing_chunks} фрагментов...>_\n' | |
else: | |
missing_string = '\n_<...>_\n' | |
result += missing_string + cls._build_sequenced_chunks(repository, group) | |
previous_last_index = group[-1].number_in_relation | |
return result.strip() | |
def _build_sequenced_chunks( | |
cls, | |
repository: EntityRepository, | |
group: list[Chunk], | |
) -> str: | |
""" | |
Строит текст для последовательных чанков. | |
Стоит переопределить в конкретной стратегии, если она предполагает сложную логику | |
""" | |
return " ".join([cls._build_chunk(chunk) for chunk in group]) | |
def _build_chunk(cls, chunk: Chunk) -> str: | |
"""Строит текст для одного чанка.""" | |
return chunk.text | |