fixed_text_splitter.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. """Functionality for splitting text."""
  2. from __future__ import annotations
  3. from typing import Any, Optional
  4. from core.model_manager import ModelInstance
  5. from core.model_runtime.model_providers.__base.tokenizers.gpt2_tokenizer import GPT2Tokenizer
  6. from core.rag.splitter.text_splitter import (
  7. TS,
  8. Collection,
  9. Literal,
  10. RecursiveCharacterTextSplitter,
  11. Set,
  12. Union,
  13. )
  14. class EnhanceRecursiveCharacterTextSplitter(RecursiveCharacterTextSplitter):
  15. """
  16. This class is used to implement from_gpt2_encoder, to prevent using of tiktoken
  17. """
  18. @classmethod
  19. def from_encoder(
  20. cls: type[TS],
  21. embedding_model_instance: Optional[ModelInstance],
  22. allowed_special: Union[Literal["all"], Set[str]] = set(), # noqa: UP037
  23. disallowed_special: Union[Literal["all"], Collection[str]] = "all", # noqa: UP037
  24. **kwargs: Any,
  25. ):
  26. def _token_encoder(texts: list[str]) -> list[int]:
  27. if not texts:
  28. return []
  29. if embedding_model_instance:
  30. return embedding_model_instance.get_text_embedding_num_tokens(texts=texts)
  31. else:
  32. return [GPT2Tokenizer.get_num_tokens(text) for text in texts]
  33. def _character_encoder(texts: list[str]) -> list[int]:
  34. if not texts:
  35. return []
  36. return [len(text) for text in texts]
  37. return cls(length_function=_character_encoder, **kwargs)
  38. class FixedRecursiveCharacterTextSplitter(EnhanceRecursiveCharacterTextSplitter):
  39. def __init__(self, fixed_separator: str = "\n\n", separators: Optional[list[str]] = None, **kwargs: Any):
  40. """Create a new TextSplitter."""
  41. super().__init__(**kwargs)
  42. self._fixed_separator = fixed_separator
  43. self._separators = separators or ["\n\n", "\n", " ", ""]
  44. def split_text(self, text: str) -> list[str]:
  45. """Split incoming text and return chunks."""
  46. if self._fixed_separator:
  47. chunks = text.split(self._fixed_separator)
  48. else:
  49. chunks = [text]
  50. final_chunks = []
  51. chunks_lengths = self._length_function(chunks)
  52. for chunk, chunk_length in zip(chunks, chunks_lengths):
  53. if chunk_length > self._chunk_size:
  54. final_chunks.extend(self.recursive_split_text(chunk))
  55. else:
  56. final_chunks.append(chunk)
  57. return final_chunks
  58. def recursive_split_text(self, text: str) -> list[str]:
  59. """Split incoming text and return chunks."""
  60. final_chunks = []
  61. separator = self._separators[-1]
  62. new_separators = []
  63. for i, _s in enumerate(self._separators):
  64. if _s == "":
  65. separator = _s
  66. break
  67. if _s in text:
  68. separator = _s
  69. new_separators = self._separators[i + 1 :]
  70. break
  71. # Now that we have the separator, split the text
  72. if separator:
  73. if separator == " ":
  74. splits = text.split()
  75. else:
  76. splits = text.split(separator)
  77. splits = [item + separator if i < len(splits) else item for i, item in enumerate(splits)]
  78. else:
  79. splits = list(text)
  80. splits = [s for s in splits if (s not in {"", "\n"})]
  81. _good_splits = []
  82. _good_splits_lengths = [] # cache the lengths of the splits
  83. _separator = "" if self._keep_separator else separator
  84. s_lens = self._length_function(splits)
  85. if separator != "":
  86. for s, s_len in zip(splits, s_lens):
  87. if s_len < self._chunk_size:
  88. _good_splits.append(s)
  89. _good_splits_lengths.append(s_len)
  90. else:
  91. if _good_splits:
  92. merged_text = self._merge_splits(_good_splits, _separator, _good_splits_lengths)
  93. final_chunks.extend(merged_text)
  94. _good_splits = []
  95. _good_splits_lengths = []
  96. if not new_separators:
  97. final_chunks.append(s)
  98. else:
  99. other_info = self._split_text(s, new_separators)
  100. final_chunks.extend(other_info)
  101. if _good_splits:
  102. merged_text = self._merge_splits(_good_splits, _separator, _good_splits_lengths)
  103. final_chunks.extend(merged_text)
  104. else:
  105. current_part = ""
  106. current_length = 0
  107. overlap_part = ""
  108. overlap_part_length = 0
  109. for s, s_len in zip(splits, s_lens):
  110. if current_length + s_len <= self._chunk_size - self._chunk_overlap:
  111. current_part += s
  112. current_length += s_len
  113. elif current_length + s_len <= self._chunk_size:
  114. current_part += s
  115. current_length += s_len
  116. overlap_part += s
  117. overlap_part_length += s_len
  118. else:
  119. final_chunks.append(current_part)
  120. current_part = overlap_part + s
  121. current_length = s_len + overlap_part_length
  122. overlap_part = ""
  123. overlap_part_length = 0
  124. if current_part:
  125. final_chunks.append(current_part)
  126. return final_chunks