137 句子窗口节点解析器的默认切割方式

句子窗口节点解析器:

node_parser = SentenceWindowNodeParser.from_defaults(
    window_size=3,
    window_metadata_key="window",
    original_text_metadata_key="original_text",
)

源码查看如何切割的

默认是 split_by_sentence_tokenizer 方法

class SentenceWindowNodeParser(NodeParser):
    """Sentence window node parser.

    Splits a document into Nodes, with each node being a sentence.
    Each node contains a window from the surrounding sentences in the metadata.

    Args:
        sentence_splitter (Optional[Callable]): splits text into sentences
        include_metadata (bool): whether to include metadata in nodes
        include_prev_next_rel (bool): whether to include prev/next relationships
    """

    sentence_splitter: Callable[[str], List[str]] = Field(
        default_factory=split_by_sentence_tokenizer,
        description="The text splitter to use when splitting documents.",
        exclude=True,
    )
    window_size: int = Field(
        default=DEFAULT_WINDOW_SIZE,
        description="The number of sentences on each side of a sentence to capture.",
        gt=0,
    )
    window_metadata_key: str = Field(
        default=DEFAULT_WINDOW_METADATA_KEY,
        description="The metadata key to store the sentence window under.",
    )
    original_text_metadata_key: str = Field(
        default=DEFAULT_OG_TEXT_METADATA_KEY,
        description="The metadata key to store the original sentence in.",
    )

    @classmethod
    def class_name(cls) -> str:
        return "SentenceWindowNodeParser"

    @classmethod
    def from_defaults(
        cls,
        sentence_splitter: Optional[Callable[[str], List[str]]] = None,
        window_size: int = DEFAULT_WINDOW_SIZE,
        window_metadata_key: str = DEFAULT_WINDOW_METADATA_KEY,
        original_text_metadata_key: str = DEFAULT_OG_TEXT_METADATA_KEY,
        include_metadata: bool = True,
        include_prev_next_rel: bool = True,
        callback_manager: Optional[CallbackManager] = None,
        id_func: Optional[Callable[[int, Document], str]] = None,
    ) -> "SentenceWindowNodeParser":
        callback_manager = callback_manager or CallbackManager([])

        sentence_splitter = sentence_splitter or split_by_sentence_tokenizer()

        id_func = id_func or default_id_func

        return cls(
            sentence_splitter=sentence_splitter,
            window_size=window_size,
            window_metadata_key=window_metadata_key,
            original_text_metadata_key=original_text_metadata_key,
            include_metadata=include_metadata,
            include_prev_next_rel=include_prev_next_rel,
            callback_manager=callback_manager,
            id_func=id_func,
        )

    def _parse_nodes(
        self,
        nodes: Sequence[BaseNode],
        show_progress: bool = False,
        **kwargs: Any,
    ) -> List[BaseNode]:
        """Parse document into nodes."""
        all_nodes: List[BaseNode] = []
        nodes_with_progress = get_tqdm_iterable(nodes, show_progress, "Parsing nodes")

        for node in nodes_with_progress:
            nodes = self.build_window_nodes_from_documents([node])
            all_nodes.extend(nodes)

        return all_nodes

    def build_window_nodes_from_documents(
        self, documents: Sequence[Document]
    ) -> List[BaseNode]:
        """Build window nodes from documents."""
        all_nodes: List[BaseNode] = []
        for doc in documents:
            text = doc.text
            text_splits = self.sentence_splitter(text)
            nodes = build_nodes_from_splits(
                text_splits,
                doc,
                id_func=self.id_func,
            )

            # add window to each node
            for i, node in enumerate(nodes):
                window_nodes = nodes[
                    max(0, i - self.window_size) : min(
                        i + self.window_size + 1, len(nodes)
                    )
                ]

                node.metadata[self.window_metadata_key] = " ".join(
                    [n.text for n in window_nodes]
                )
                node.metadata[self.original_text_metadata_key] = node.text

                # exclude window metadata from embed and llm
                node.excluded_embed_metadata_keys.extend(
                    [self.window_metadata_key, self.original_text_metadata_key]
                )
                node.excluded_llm_metadata_keys.extend(
                    [self.window_metadata_key, self.original_text_metadata_key]
                )

            all_nodes.extend(nodes)

        return all_nodes

utils中的split_by_sentence_tokenizer

用nltk中的PunktSentenceTokenizer的span_tokenize 方法

def split_by_sentence_tokenizer() -> Callable[[str], List[str]]:
    import nltk

    tokenizer = nltk.tokenize.PunktSentenceTokenizer()
    return lambda text: split_by_sentence_tokenizer_internal(text, tokenizer)
def split_by_sentence_tokenizer_internal(text: str, tokenizer) -> List[str]:
    """Get the spans and then return the sentences.

    Using the start index of each span
    Instead of using end, use the start of the next span if available
    """
    spans = list(tokenizer.span_tokenize(text))
    sentences = []
    for i, span in enumerate(spans):
        start = span[0]
        if i < len(spans) - 1:
            end = spans[i + 1][0]
        else:
            end = len(text)
        sentences.append(text[start:end])
    return sentences

nltk中的PunktSentenceTokenizer的span_tokenize

这段代码定义了一个名为 span_tokenize 的方法,用于将输入的文本分割成句子,并生成每个句子的起始和结束位置(即 span)。以下是对代码的详细解释:

方法签名

def span_tokenize(
    self, text: str, realign_boundaries: bool = True
) -> Iterator[Tuple[int, int]]:
  • self: 这是一个实例方法,self 表示调用该方法的实例对象。
  • text: 输入的文本字符串,类型为 str
  • realign_boundaries: 一个布尔值,默认为 True。如果设置为 True,则会对分割边界进行重新对齐。
  • 返回值: 返回一个生成器,生成每个句子的起始和结束位置的元组 (start, end)

方法实现

"""
Given a text, generates (start, end) spans of sentences
in the text.
"""
slices = self._slices_from_text(text)
if realign_boundaries:
    slices = self._realign_boundaries(text, slices)
for sentence in slices:
    yield (sentence.start, sentence.stop)
  1. 获取初始分割切片:

    slices = self._slices_from_text(text)
    

    调用 self._slices_from_text(text) 方法,该方法根据某种逻辑(可能是基于标点符号或其他分隔符)将文本分割成多个切片(即潜在的句子)。每个切片可能是一个 slice 对象,包含了句子的起始和结束位置。

  2. 重新对齐边界(可选):

    if realign_boundaries:
        slices = self._realign_boundaries(text, slices)
    

    如果 realign_boundariesTrue,则调用 self._realign_boundaries(text, slices) 方法,该方法会对分割边界进行重新对齐,以确保分割的准确性。例如,可能会处理一些特殊情况,如标点符号紧跟在单词后面等。

  3. 生成句子 span:

    for sentence in slices:
        yield (sentence.start, sentence.stop)
    

    遍历调整后的切片列表 slices,对于每个切片(即句子),生成一个包含起始和结束位置的元组 (sentence.start, sentence.stop)。这些元组通过 yield 关键字逐个返回,形成一个生成器。

总结

这个 span_tokenize 方法的主要功能是将输入的文本分割成句子,并生成每个句子的起始和结束位置。通过可选的边界重新对齐步骤,可以提高分割的准确性。最终,该方法返回一个生成器,逐个生成每个句子的 span。

_slices_from_text

这段代码定义了一个名为 _slices_from_text 的私有方法,用于将输入的文本分割成潜在的句子切片。以下是对代码的详细解释:

方法签名

def _slices_from_text(self, text: str) -> Iterator[slice]:
  • self: 这是一个实例方法,self 表示调用该方法的实例对象。
  • text: 输入的文本字符串,类型为 str
  • 返回值: 返回一个生成器,生成每个潜在句子的 slice 对象。

方法实现

last_break = 0
for match, context in self._match_potential_end_contexts(text):
    if self.text_contains_sentbreak(context):
        yield slice(last_break, match.end())
        if match.group("next_tok"):
            # next sentence starts after whitespace
            last_break = match.start("next_tok")
        else:
            # next sentence starts at following punctuation
            last_break = match.end()
# The last sentence should not contain trailing whitespace.
yield slice(last_break, len(text.rstrip()))
  1. 初始化变量:

    last_break = 0
    

    last_break 变量用于记录上一个句子的结束位置。初始值为 0,表示文本的起始位置。

  2. 遍历潜在的句子结束位置:

    for match, context in self._match_potential_end_contexts(text):
    

    调用 self._match_potential_end_contexts(text) 方法,该方法返回文本中潜在的句子结束位置的匹配对象和上下文。match 是一个匹配对象,context 是匹配的上下文。

  3. 检查是否包含句子分隔符:

    if self.text_contains_sentbreak(context):
    

    调用 self.text_contains_sentbreak(context) 方法,检查上下文是否包含句子分隔符(如句号、问号、感叹号等)。

  4. 生成句子切片:

    yield slice(last_break, match.end())
    

    如果上下文包含句子分隔符,则生成一个 slice 对象,表示从 last_breakmatch.end() 的切片,即当前句子的起始和结束位置。

  5. 更新 last_break:

    if match.group("next_tok"):
        # next sentence starts after whitespace
        last_break = match.start("next_tok")
    else:
        # next sentence starts at following punctuation
        last_break = match.end()
    

    根据匹配结果更新 last_break

    • 如果匹配对象包含 "next_tok" 组(表示下一个句子的起始位置在空白字符之后),则将 last_break 更新为 match.start("next_tok")
    • 否则,将 last_break 更新为 match.end(),即当前匹配的结束位置。
  6. 生成最后一个句子切片:

    yield slice(last_break, len(text.rstrip()))
    

    最后生成一个切片,表示从 last_break 到文本的最后一个非空白字符的位置。这样可以确保最后一个句子不包含尾随的空白字符。

总结

这个 _slices_from_text 方法的主要功能是将输入的文本分割成潜在的句子切片。通过遍历潜在的句子结束位置,并检查上下文是否包含句子分隔符,生成每个句子的 slice 对象。最终,该方法返回一个生成器,逐个生成每个句子的切片。

_match_potential_end_contexts

这段代码定义了一个名为 _match_potential_end_contexts 的私有方法,用于在输入文本中找到潜在的句子结束位置的匹配对象,并返回这些匹配对象及其上下文。以下是对代码的详细解释:

方法签名

def _match_potential_end_contexts(self, text: str) -> Iterator[Tuple[Match, str]]:
  • self: 这是一个实例方法,self 表示调用该方法的实例对象。
  • text: 输入的文本字符串,类型为 str
  • 返回值: 返回一个生成器,生成每个潜在句子结束位置的匹配对象及其上下文的元组 (Match, str)

方法实现

previous_slice = slice(0, 0)
previous_match = None
for match in self._lang_vars.period_context_re().finditer(text):
    # Get the slice of the previous word
    before_text = text[previous_slice.stop : match.start()]
    index_after_last_space = self._get_last_whitespace_index(before_text)
    if index_after_last_space:
        # + 1 to exclude the space itself
        index_after_last_space += previous_slice.stop + 1
    else:
        index_after_last_space = previous_slice.start
    prev_word_slice = slice(index_after_last_space, match.start())

    # If the previous slice does not overlap with this slice, then
    # we can yield the previous match and slice. If there is an overlap,
    # then we do not yield the previous match and slice.
    if previous_match and previous_slice.stop <= prev_word_slice.start:
        yield (
            previous_match,
            text[previous_slice]
            + previous_match.group()
            + previous_match.group("after_tok"),
        )
    previous_match = match
    previous_slice = prev_word_slice

# Yield the last match and context, if it exists
if previous_match:
    yield (
        previous_match,
        text[previous_slice]
        + previous_match.group()
        + previous_match.group("after_tok"),
    )
  1. 初始化变量:

    previous_slice = slice(0, 0)
    previous_match = None
    

    previous_slice 用于记录上一个匹配的切片,初始值为 slice(0, 0),表示空切片。previous_match 用于记录上一个匹配对象,初始值为 None

  2. 遍历潜在的句子结束位置:

    for match in self._lang_vars.period_context_re().finditer(text):
    

    调用 self._lang_vars.period_context_re().finditer(text) 方法,该方法返回文本中潜在的句子结束位置的匹配对象。

  3. 获取上一个单词的切片:

    before_text = text[previous_slice.stop : match.start()]
    index_after_last_space = self._get_last_whitespace_index(before_text)
    if index_after_last_space:
        # + 1 to exclude the space itself
        index_after_last_space += previous_slice.stop + 1
    else:
        index_after_last_space = previous_slice.start
    prev_word_slice = slice(index_after_last_space, match.start())
    
    • previous_slice.stopmatch.start() 获取 before_text
    • 调用 self._get_last_whitespace_index(before_text) 方法,找到 before_text 中最后一个空白字符的索引。
    • 根据 index_after_last_space 计算 prev_word_slice,表示上一个单词的切片。
  4. 检查切片是否重叠:

    if previous_match and previous_slice.stop <= prev_word_slice.start:
        yield (
            previous_match,
            text[previous_slice]
            + previous_match.group()
            + previous_match.group("after_tok"),
        )
    
    • 如果 previous_match 存在且 previous_slice.stop 小于等于 prev_word_slice.start,则表示上一个匹配和当前匹配不重叠,可以生成上一个匹配及其上下文。
    • 生成一个元组,包含上一个匹配对象和其上下文。
  5. 更新 previous_matchprevious_slice:

    previous_match = match
    previous_slice = prev_word_slice
    

    更新 previous_matchprevious_slice 为当前匹配对象和切片。

  6. 生成最后一个匹配及其上下文:

    if previous_match:
        yield (
            previous_match,
            text[previous_slice]
            + previous_match.group()
            + previous_match.group("after_tok"),
        )
    

    如果 previous_match 存在,生成最后一个匹配对象及其上下文。

总结

这个 _match_potential_end_contexts 方法的主要功能是在输入文本中找到潜在的句子结束位置的匹配对象,并返回这些匹配对象及其上下文。通过遍历潜在的句子结束位置,获取上一个单词的切片,并检查切片是否重叠,生成每个匹配对象及其上下文的元组。最终,该方法返回一个生成器,逐个生成每个匹配对象及其上下文。

这段代码定义了一个名为 text_contains_sentbreak 的方法,用于判断给定的文本是否包含句子分隔符。以下是对代码的详细解释:

text_contains_sentbreak

方法签名

def text_contains_sentbreak(self, text: str) -> bool:
  • self: 这是一个实例方法,self 表示调用该方法的实例对象。
  • text: 输入的文本字符串,类型为 str
  • 返回值: 返回一个布尔值,表示文本是否包含句子分隔符。

方法实现

"""
Returns True if the given text includes a sentence break.
"""
found = False  # used to ignore last token
for tok in self._annotate_tokens(self._tokenize_words(text)):
    if found:
        return True
    if tok.sentbreak:
        found = True
return False
  1. 初始化变量:

    found = False  # used to ignore last token
    

    found 变量用于标记是否找到了句子分隔符。初始值为 False

  2. 遍历标记:

    for tok in self._annotate_tokens(self._tokenize_words(text)):
    

    调用 self._tokenize_words(text) 方法将文本分割成单词,然后调用 self._annotate_tokens 方法对这些单词进行注释,返回一个包含注释信息的标记列表。

  3. 检查句子分隔符:

    if found:
        return True
    if tok.sentbreak:
        found = True
    
    • 如果 foundTrue,表示已经找到了句子分隔符,直接返回 True
    • 如果当前标记 tok 包含句子分隔符(即 tok.sentbreakTrue),则将 found 设置为 True
  4. 返回结果:

    return False
    

    如果遍历完所有标记后仍未找到句子分隔符,则返回 False

总结

这个 text_contains_sentbreak 方法的主要功能是判断给定的文本是否包含句子分隔符。通过遍历文本中的标记,检查每个标记是否包含句子分隔符,如果找到则返回 True,否则返回 False

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

需要重新演唱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值