libs/text-splitters/langchain_text_splitters/character.py PYTHON 802 lines View on github.com → Search inside
1"""Character text splitters."""23from __future__ import annotations45import re6from typing import Any, Literal78from typing_extensions import override910from langchain_text_splitters.base import Language, TextSplitter111213class CharacterTextSplitter(TextSplitter):14    """Splitting text that looks at characters."""1516    def __init__(17        self,18        separator: str = "\n\n",19        is_separator_regex: bool = False,  # noqa: FBT001,FBT00220        **kwargs: Any,21    ) -> None:22        """Create a new TextSplitter."""23        super().__init__(**kwargs)24        self._separator = separator25        self._is_separator_regex = is_separator_regex2627    @override28    def split_text(self, text: str) -> list[str]:29        """Split into chunks without re-inserting lookaround separators.3031        Args:32            text: The text to split.3334        Returns:35            A list of text chunks.36        """37        # 1. Determine split pattern: raw regex or escaped literal38        sep_pattern = (39            self._separator if self._is_separator_regex else re.escape(self._separator)40        )4142        # 2. Initial split (keep separator if requested)43        splits = _split_text_with_regex(44            text, sep_pattern, keep_separator=self._keep_separator45        )4647        # 3. Detect zero-width lookaround so we never re-insert it48        lookaround_prefixes = ("(?=", "(?<!", "(?<=", "(?!")49        is_lookaround = self._is_separator_regex and any(50            self._separator.startswith(p) for p in lookaround_prefixes51        )5253        # 4. Decide merge separator:54        #    - if keep_separator or lookaround -> don't re-insert55        #    - else -> re-insert literal separator56        merge_sep = ""57        if not (self._keep_separator or is_lookaround):58            merge_sep = self._separator5960        # 5. Merge adjacent splits and return61        return self._merge_splits(splits, merge_sep)626364def _split_text_with_regex(65    text: str, separator: str, *, keep_separator: bool | Literal["start", "end"]66) -> list[str]:67    # Now that we have the separator, split the text68    if separator:69        if keep_separator:70            # The parentheses in the pattern keep the delimiters in the result.71            splits_ = re.split(f"({separator})", text)72            splits = (73                ([splits_[i] + splits_[i + 1] for i in range(0, len(splits_) - 1, 2)])74                if keep_separator == "end"75                else ([splits_[i] + splits_[i + 1] for i in range(1, len(splits_), 2)])76            )77            if len(splits_) % 2 == 0:78                splits += splits_[-1:]79            splits = (80                ([*splits, splits_[-1]])81                if keep_separator == "end"82                else ([splits_[0], *splits])83            )84        else:85            splits = re.split(separator, text)86    else:87        splits = list(text)88    return [s for s in splits if s]899091class RecursiveCharacterTextSplitter(TextSplitter):92    """Splitting text by recursively look at characters.9394    Recursively tries to split by different characters to find one95    that works.96    """9798    def __init__(99        self,100        separators: list[str] | None = None,101        keep_separator: bool | Literal["start", "end"] = True,  # noqa: FBT001,FBT002102        is_separator_regex: bool = False,  # noqa: FBT001,FBT002103        **kwargs: Any,104    ) -> None:105        """Create a new TextSplitter."""106        super().__init__(keep_separator=keep_separator, **kwargs)107        self._separators = separators or ["\n\n", "\n", " ", ""]108        self._is_separator_regex = is_separator_regex109110    def _split_text(self, text: str, separators: list[str]) -> list[str]:111        """Split incoming text and return chunks."""112        final_chunks = []113        # Get appropriate separator to use114        separator = separators[-1]115        new_separators = []116        for i, s_ in enumerate(separators):117            separator_ = s_ if self._is_separator_regex else re.escape(s_)118            if not s_:119                separator = s_120                break121            if re.search(separator_, text):122                separator = s_123                new_separators = separators[i + 1 :]124                break125126        separator_ = separator if self._is_separator_regex else re.escape(separator)127        splits = _split_text_with_regex(128            text, separator_, keep_separator=self._keep_separator129        )130131        # Now go merging things, recursively splitting longer texts.132        good_splits = []133        separator_ = "" if self._keep_separator else separator134        for s in splits:135            if self._length_function(s) < self._chunk_size:136                good_splits.append(s)137            else:138                if good_splits:139                    merged_text = self._merge_splits(good_splits, separator_)140                    final_chunks.extend(merged_text)141                    good_splits = []142                if not new_separators:143                    final_chunks.append(s)144                else:145                    other_info = self._split_text(s, new_separators)146                    final_chunks.extend(other_info)147        if good_splits:148            merged_text = self._merge_splits(good_splits, separator_)149            final_chunks.extend(merged_text)150        return final_chunks151152    @override153    def split_text(self, text: str) -> list[str]:154        """Split the input text into smaller chunks based on predefined separators.155156        Args:157            text: The input text to be split.158159        Returns:160            A list of text chunks obtained after splitting.161        """162        return self._split_text(text, self._separators)163164    @classmethod165    def from_language(166        cls, language: Language, **kwargs: Any167    ) -> RecursiveCharacterTextSplitter:168        """Return an instance of this class based on a specific language.169170        This method initializes the text splitter with language-specific separators.171172        Args:173            language: The language to configure the text splitter for.174            **kwargs: Additional keyword arguments to customize the splitter.175176        Returns:177            An instance of the text splitter configured for the specified language.178        """179        separators = cls.get_separators_for_language(language)180        return cls(separators=separators, is_separator_regex=True, **kwargs)181182    @staticmethod183    def get_separators_for_language(language: Language) -> list[str]:184        """Retrieve a list of separators specific to the given language.185186        Args:187            language: The language for which to get the separators.188189        Returns:190            A list of separators appropriate for the specified language.191192        Raises:193            ValueError: If the language is not implemented or supported.194        """195        if language in {Language.C, Language.CPP}:196            return [197                # Split along class definitions198                "\nclass ",199                # Split along function definitions200                "\nvoid ",201                "\nint ",202                "\nfloat ",203                "\ndouble ",204                # Split along control flow statements205                "\nif ",206                "\nfor ",207                "\nwhile ",208                "\nswitch ",209                "\ncase ",210                # Split by the normal type of lines211                "\n\n",212                "\n",213                " ",214                "",215            ]216        if language == Language.GO:217            return [218                # Split along function definitions219                "\nfunc ",220                "\nvar ",221                "\nconst ",222                "\ntype ",223                # Split along control flow statements224                "\nif ",225                "\nfor ",226                "\nswitch ",227                "\ncase ",228                # Split by the normal type of lines229                "\n\n",230                "\n",231                " ",232                "",233            ]234        if language == Language.JAVA:235            return [236                # Split along class definitions237                "\nclass ",238                # Split along method definitions239                "\npublic ",240                "\nprotected ",241                "\nprivate ",242                "\nstatic ",243                # Split along control flow statements244                "\nif ",245                "\nfor ",246                "\nwhile ",247                "\nswitch ",248                "\ncase ",249                # Split by the normal type of lines250                "\n\n",251                "\n",252                " ",253                "",254            ]255        if language == Language.KOTLIN:256            return [257                # Split along class definitions258                "\nclass ",259                # Split along method definitions260                "\npublic ",261                "\nprotected ",262                "\nprivate ",263                "\ninternal ",264                "\ncompanion ",265                "\nfun ",266                "\nval ",267                "\nvar ",268                # Split along control flow statements269                "\nif ",270                "\nfor ",271                "\nwhile ",272                "\nwhen ",273                "\nelse ",274                # Split by the normal type of lines275                "\n\n",276                "\n",277                " ",278                "",279            ]280        if language == Language.JS:281            return [282                # Split along function definitions283                "\nfunction ",284                "\nconst ",285                "\nlet ",286                "\nvar ",287                "\nclass ",288                # Split along control flow statements289                "\nif ",290                "\nfor ",291                "\nwhile ",292                "\nswitch ",293                "\ncase ",294                "\ndefault ",295                # Split by the normal type of lines296                "\n\n",297                "\n",298                " ",299                "",300            ]301        if language == Language.TS:302            return [303                "\nenum ",304                "\ninterface ",305                "\nnamespace ",306                "\ntype ",307                # Split along class definitions308                "\nclass ",309                # Split along function definitions310                "\nfunction ",311                "\nconst ",312                "\nlet ",313                "\nvar ",314                # Split along control flow statements315                "\nif ",316                "\nfor ",317                "\nwhile ",318                "\nswitch ",319                "\ncase ",320                "\ndefault ",321                # Split by the normal type of lines322                "\n\n",323                "\n",324                " ",325                "",326            ]327        if language == Language.PHP:328            return [329                # Split along function definitions330                "\nfunction ",331                # Split along class definitions332                "\nclass ",333                # Split along control flow statements334                "\nif ",335                "\nforeach ",336                "\nwhile ",337                "\ndo ",338                "\nswitch ",339                "\ncase ",340                # Split by the normal type of lines341                "\n\n",342                "\n",343                " ",344                "",345            ]346        if language == Language.PROTO:347            return [348                # Split along message definitions349                "\nmessage ",350                # Split along service definitions351                "\nservice ",352                # Split along enum definitions353                "\nenum ",354                # Split along option definitions355                "\noption ",356                # Split along import statements357                "\nimport ",358                # Split along syntax declarations359                "\nsyntax ",360                # Split by the normal type of lines361                "\n\n",362                "\n",363                " ",364                "",365            ]366        if language == Language.PYTHON:367            return [368                # First, try to split along class definitions369                "\nclass ",370                "\ndef ",371                "\n\tdef ",372                # Now split by the normal type of lines373                "\n\n",374                "\n",375                " ",376                "",377            ]378        if language == Language.R:379            return [380                # Split along function definitions381                "\nfunction ",382                # Split along S4 class and method definitions383                "\nsetClass\\(",384                "\nsetMethod\\(",385                "\nsetGeneric\\(",386                # Split along control flow statements387                "\nif ",388                "\nelse ",389                "\nfor ",390                "\nwhile ",391                "\nrepeat ",392                # Split along package loading393                "\nlibrary\\(",394                "\nrequire\\(",395                # Split by the normal type of lines396                "\n\n",397                "\n",398                " ",399                "",400            ]401        if language == Language.RST:402            return [403                # Split along section titles404                "\n=+\n",405                "\n-+\n",406                "\n\\*+\n",407                # Split along directive markers408                "\n\n.. *\n\n",409                # Split by the normal type of lines410                "\n\n",411                "\n",412                " ",413                "",414            ]415        if language == Language.RUBY:416            return [417                # Split along method definitions418                "\ndef ",419                "\nclass ",420                # Split along control flow statements421                "\nif ",422                "\nunless ",423                "\nwhile ",424                "\nfor ",425                "\ndo ",426                "\nbegin ",427                "\nrescue ",428                # Split by the normal type of lines429                "\n\n",430                "\n",431                " ",432                "",433            ]434        if language == Language.ELIXIR:435            return [436                # Split along method function and module definition437                "\ndef ",438                "\ndefp ",439                "\ndefmodule ",440                "\ndefprotocol ",441                "\ndefmacro ",442                "\ndefmacrop ",443                # Split along control flow statements444                "\nif ",445                "\nunless ",446                "\ncase ",447                "\ncond ",448                "\nwith ",449                "\nfor ",450                "\ndo ",451                # Split by the normal type of lines452                "\n\n",453                "\n",454                " ",455                "",456            ]457        if language == Language.RUST:458            return [459                # Split along function definitions460                "\nfn ",461                "\nconst ",462                "\nlet ",463                # Split along control flow statements464                "\nif ",465                "\nwhile ",466                "\nfor ",467                "\nloop ",468                "\nmatch ",469                # Split by the normal type of lines470                "\n\n",471                "\n",472                " ",473                "",474            ]475        if language == Language.SCALA:476            return [477                # Split along class definitions478                "\nclass ",479                "\nobject ",480                # Split along method definitions481                "\ndef ",482                "\nval ",483                "\nvar ",484                # Split along control flow statements485                "\nif ",486                "\nfor ",487                "\nwhile ",488                "\nmatch ",489                "\ncase ",490                # Split by the normal type of lines491                "\n\n",492                "\n",493                " ",494                "",495            ]496        if language == Language.SWIFT:497            return [498                # Split along function definitions499                "\nfunc ",500                # Split along class definitions501                "\nclass ",502                "\nstruct ",503                "\nenum ",504                # Split along control flow statements505                "\nif ",506                "\nfor ",507                "\nwhile ",508                "\ndo ",509                "\nswitch ",510                "\ncase ",511                # Split by the normal type of lines512                "\n\n",513                "\n",514                " ",515                "",516            ]517        if language == Language.MARKDOWN:518            return [519                # First, try to split along Markdown headings (starting with level 2)520                "\n#{1,6} ",521                # Note the alternative syntax for headings (below) is not handled here522                # Heading level 2523                # ---------------524                # End of code block525                "```\n",526                # Horizontal lines527                "\n\\*\\*\\*+\n",528                "\n---+\n",529                "\n___+\n",530                # Note that this splitter doesn't handle horizontal lines defined531                # by *three or more* of ***, ---, or ___, but this is not handled532                "\n\n",533                "\n",534                " ",535                "",536            ]537        if language == Language.LATEX:538            return [539                # First, try to split along Latex sections540                "\n\\\\chapter{",541                "\n\\\\section{",542                "\n\\\\subsection{",543                "\n\\\\subsubsection{",544                # Now split by environments545                "\n\\\\begin{enumerate}",546                "\n\\\\begin{itemize}",547                "\n\\\\begin{description}",548                "\n\\\\begin{list}",549                "\n\\\\begin{quote}",550                "\n\\\\begin{quotation}",551                "\n\\\\begin{verse}",552                "\n\\\\begin{verbatim}",553                # Now split by math environments554                "\n\\\\begin{align}",555                "$$",556                "$",557                # Now split by the normal type of lines558                " ",559                "",560            ]561        if language == Language.HTML:562            return [563                # First, try to split along HTML tags564                "<body",565                "<div",566                "<p",567                "<br",568                "<li",569                "<h1",570                "<h2",571                "<h3",572                "<h4",573                "<h5",574                "<h6",575                "<span",576                "<table",577                "<tr",578                "<td",579                "<th",580                "<ul",581                "<ol",582                "<header",583                "<footer",584                "<nav",585                # Head586                "<head",587                "<style",588                "<script",589                "<meta",590                "<title",591                "",592            ]593        if language == Language.CSHARP:594            return [595                "\ninterface ",596                "\nenum ",597                "\ndelegate ",598                "\nevent ",599                # Split along class definitions600                "\nclass ",601                "\nabstract ",602                # Split along method definitions603                "\npublic ",604                "\nprotected ",605                "\nprivate ",606                "\nstatic ",607                "\nreturn ",608                # Split along control flow statements609                "\nif ",610                "\ncontinue ",611                "\nfor ",612                "\nforeach ",613                "\nwhile ",614                "\nswitch ",615                "\nbreak ",616                "\ncase ",617                "\nelse ",618                # Split by exceptions619                "\ntry ",620                "\nthrow ",621                "\nfinally ",622                "\ncatch ",623                # Split by the normal type of lines624                "\n\n",625                "\n",626                " ",627                "",628            ]629        if language == Language.SOL:630            return [631                # Split along compiler information definitions632                "\npragma ",633                "\nusing ",634                # Split along contract definitions635                "\ncontract ",636                "\ninterface ",637                "\nlibrary ",638                # Split along method definitions639                "\nconstructor ",640                "\ntype ",641                "\nfunction ",642                "\nevent ",643                "\nmodifier ",644                "\nerror ",645                "\nstruct ",646                "\nenum ",647                # Split along control flow statements648                "\nif ",649                "\nfor ",650                "\nwhile ",651                "\ndo while ",652                "\nassembly ",653                # Split by the normal type of lines654                "\n\n",655                "\n",656                " ",657                "",658            ]659        if language == Language.COBOL:660            return [661                # Split along divisions662                "\nIDENTIFICATION DIVISION.",663                "\nENVIRONMENT DIVISION.",664                "\nDATA DIVISION.",665                "\nPROCEDURE DIVISION.",666                # Split along sections within DATA DIVISION667                "\nWORKING-STORAGE SECTION.",668                "\nLINKAGE SECTION.",669                "\nFILE SECTION.",670                # Split along sections within PROCEDURE DIVISION671                "\nINPUT-OUTPUT SECTION.",672                # Split along paragraphs and common statements673                "\nOPEN ",674                "\nCLOSE ",675                "\nREAD ",676                "\nWRITE ",677                "\nIF ",678                "\nELSE ",679                "\nMOVE ",680                "\nPERFORM ",681                "\nUNTIL ",682                "\nVARYING ",683                "\nACCEPT ",684                "\nDISPLAY ",685                "\nSTOP RUN.",686                # Split by the normal type of lines687                "\n",688                " ",689                "",690            ]691        if language == Language.LUA:692            return [693                # Split along variable and table definitions694                "\nlocal ",695                # Split along function definitions696                "\nfunction ",697                # Split along control flow statements698                "\nif ",699                "\nfor ",700                "\nwhile ",701                "\nrepeat ",702                # Split by the normal type of lines703                "\n\n",704                "\n",705                " ",706                "",707            ]708        if language == Language.HASKELL:709            return [710                # Split along function definitions711                "\nmain :: ",712                "\nmain = ",713                "\nlet ",714                "\nin ",715                "\ndo ",716                "\nwhere ",717                "\n:: ",718                "\n= ",719                # Split along type declarations720                "\ndata ",721                "\nnewtype ",722                "\ntype ",723                # Split along module declarations724                "\nmodule ",725                # Split along import statements726                "\nimport ",727                "\nqualified ",728                "\nimport qualified ",729                # Split along typeclass declarations730                "\nclass ",731                "\ninstance ",732                # Split along case expressions733                "\ncase ",734                # Split along guards in function definitions735                "\n| ",736                # Split along record field declarations737                "\n= {",738                "\n, ",739                # Split by the normal type of lines740                "\n\n",741                "\n",742                " ",743                "",744            ]745        if language == Language.POWERSHELL:746            return [747                # Split along function definitions748                "\nfunction ",749                # Split along parameter declarations (escape parentheses)750                "\nparam ",751                # Split along control flow statements752                "\nif ",753                "\nforeach ",754                "\nfor ",755                "\nwhile ",756                "\nswitch ",757                # Split along class definitions (for PowerShell 5.0 and above)758                "\nclass ",759                # Split along try-catch-finally blocks760                "\ntry ",761                "\ncatch ",762                "\nfinally ",763                # Split by normal lines and empty spaces764                "\n\n",765                "\n",766                " ",767                "",768            ]769        if language == Language.VISUALBASIC6:770            vis = r"(?:Public|Private|Friend|Global|Static)\s+"771            return [772                # Split along definitions773                rf"\n(?!End\s){vis}?Sub\s+",774                rf"\n(?!End\s){vis}?Function\s+",775                rf"\n(?!End\s){vis}?Property\s+(?:Get|Let|Set)\s+",776                rf"\n(?!End\s){vis}?Type\s+",777                rf"\n(?!End\s){vis}?Enum\s+",778                # Split along control flow statements779                r"\n(?!End\s)If\s+",780                r"\nElseIf\s+",781                r"\nElse\s+",782                r"\nSelect\s+Case\s+",783                r"\nCase\s+",784                r"\nFor\s+",785                r"\nDo\s+",786                r"\nWhile\s+",787                r"\nWith\s+",788                # Split by the normal type of lines789                r"\n\n",790                r"\n",791                " ",792                "",793            ]794795        if language in Language._value2member_map_:796            msg = f"Language {language} is not implemented yet!"797            raise ValueError(msg)798        msg = (799            f"Language {language} is not supported! Please choose from {list(Language)}"800        )801        raise ValueError(msg)

Code quality findings 7

Avoid unnecessary list conversions; use generators where possible
unnecessary-list
splits = list(text)
Ensure functions have docstrings for documentation
missing-docstring
def from_language(
Ensure functions have docstrings for documentation
missing-docstring
"\ndef ",
Ensure functions have docstrings for documentation
missing-docstring
"\n\tdef ",
Ensure functions have docstrings for documentation
missing-docstring
"\ndef ",
Ensure functions have docstrings for documentation
missing-docstring
"\ndef ",
Ensure functions have docstrings for documentation
missing-docstring
"\ndef ",

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.