Strategie dopasowane do dokumentów: kod i HTML
Stosuj wyspecjalizowane dzielenie: dla kodu Python używaj dzielników funkcji opartych na AST, dla HTML parserów uwzględniających tagi, a dla Markdown hierarchii nagłówków.
Strategie dopasowane do dokumentów: kod i HTML to bezpłatna lekcja AI Engineering Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Engineering Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.
Dlaczego ogólne dzielenie nie sprawdza się w dokumentach specjalistycznych
Dzielenie tekstu zostało zaprojektowane z myślą o prozie, ale dane z rzeczywistego świata obejmują kod źródłowy, strony HTML i dokumentację w Markdown. Dzielenie kodu na granicy o stałej liczbie znaków może przeciąć funkcję w połowie jej treści, przez co fragment stanie się bezużyteczny podczas pobierania. Dokumenty specjalistyczne wymagają splitterów rozumiejących ich wewnętrzną strukturę, a nie tylko długość.
Dzielenie kodu Python na podstawie AST
Abstrakcyjne drzewo składniowe (AST) pliku Python przedstawia każdą funkcję, klasę i moduł jako ustrukturyzowany węzeł. Przechodząc po AST, można wyodrębnić każdą funkcję lub metodę jako osobny fragment, zachowując razem sygnaturę, docstring i ciało. Klasa PythonCodeTextSplitter w LangChain wewnętrznie korzysta z tego podejścia.
import ast
import textwrap
def extract_functions(source_code: str) -> list[dict]:
tree = ast.parse(source_code)
chunks = []
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
start = node.lineno - 1
end = node.end_lineno
lines = source_code.splitlines()[start:end]
chunks.append({
'name': node.name,
'code': '\n'.join(lines),
'start_line': node.lineno,
})
return chunksDzielenie na granicach klas
W przypadku obiektowych baz kodu dzielenie na poziomie klasy często sprawdza się lepiej niż dzielenie na poziomie funkcji. Fragment klasy zachowuje relację między metodami a współdzielonym stanem, na którym operują. Można umieścić docstring klasy i treść wszystkich metod w jednym fragmencie, a następnie utworzyć osobne, bardziej szczegółowe fragmenty tylko dla długich metod.
from langchain_text_splitters import Language, RecursiveCharacterTextSplitter
python_splitter = RecursiveCharacterTextSplitter.from_language(
language=Language.PYTHON,
chunk_size=1000,
chunk_overlap=100,
)
with open('my_module.py', 'r') as f:
source = f.read()
chunks = python_splitter.create_documents([source])
print(f'Created {len(chunks)} code chunks')Dodawanie metadanych kodu do fragmentów
Surowe fragmenty kodu są tak użyteczne, jak ich metadane. Podczas przechowywania fragmentów kodu w bazie wektorowej należy uwzględnić ścieżkę pliku, nazwę funkcji, język programowania oraz zakres wierszy. Metadane te pozwalają retrieverowi filtrować dane według języka lub pliku, a LLM-owi wskazywać w odpowiedzi dokładną lokalizację źródła.
from langchain_core.documents import Document
def chunk_python_file(filepath: str) -> list[Document]:
with open(filepath) as f:
source = f.read()
functions = extract_functions(source) # from previous example
docs = []
for fn in functions:
docs.append(Document(
page_content=fn['code'],
metadata={
'source': filepath,
'function': fn['name'],
'language': 'python',
'start_line': fn['start_line'],
}
))
return docsHTML: struktura ważniejsza niż liczba znaków
Dokumenty HTML mają hierarchiczną strukturę obejmującą nagłówki, sekcje, akapity i listy. Dzielenie HTML według liczby znaków często przecina znaczniki, tworząc niepoprawne fragmenty. Właściwe podejście polega na analizowaniu HTML za pomocą odpowiedniego parsera, takiego jak BeautifulSoup, i wyodrębnianiu elementów o znaczeniu semantycznym, takich jak znaczniki <article>, <section> i <p>.
from bs4 import BeautifulSoup
def chunk_html_by_section(html: str) -> list[dict]:
soup = BeautifulSoup(html, 'html.parser')
chunks = []
for tag in soup.find_all(['h1', 'h2', 'h3', 'p', 'li']):
text = tag.get_text(separator=' ', strip=True)
if len(text) > 40: # skip trivial fragments
chunks.append({
'tag': tag.name,
'text': text,
})
return chunksHierarchiczne dzielenie HTML według nagłówków
Bardziej zaawansowana strategia dla HTML grupuje treść pod najbliższym nagłówkiem. Każdy akapit i każda lista występujące po nagłówku <h2> należą do tej sekcji. Grupowanie tekstu z nadrzędnym nagłówkiem pozwala zachować kontekst tematu, który samodzielny akapit w przeciwnym razie by utracił. Klasa HTMLHeaderTextSplitter w LangChain realizuje to automatycznie.
from langchain_text_splitters import HTMLHeaderTextSplitter
headers_to_split_on = [
('h1', 'Header 1'),
('h2', 'Header 2'),
('h3', 'Header 3'),
]
splitter = HTMLHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
with open('page.html') as f:
html = f.read()
sections = splitter.split_text(html)
for sec in sections[:3]:
print(sec.metadata)
print(sec.page_content[:200])
print('---')Markdown: zachowywanie hierarchii nagłówków
Dokumentacja Markdown jest uporządkowana za pomocą nagłówków #, ## i ###. Klasa MarkdownHeaderTextSplitter dzieli tekst na granicach nagłówków i zapisuje hierarchię nagłówków w metadanych. Dzięki temu każdy fragment zna pełną ścieżkę nagłówków, co znacznie poprawia trafność pobieranego kontekstu, gdy użytkownicy pytają o konkretne sekcje dokumentacji.
from langchain_text_splitters import MarkdownHeaderTextSplitter
headers_to_split_on = [
('#', 'H1'),
('##', 'H2'),
('###', 'H3'),
]
md_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
with open('README.md') as f:
markdown = f.read()
docs = md_splitter.split_text(markdown)
for doc in docs[:2]:
print('Metadata:', doc.metadata)
print('Content:', doc.page_content[:300])
print()Dodatkowe dzielenie po podziale według nagłówków
Po podziale według nagłówków poszczególne sekcje mogą nadal być zbyt długie dla limitu tokenów modelu embeddingowego. Zalecany wzorzec to dzielenie dwuetapowe: najpierw podział według hierarchii nagłówków w celu zachowania kontekstu semantycznego, a następnie zastosowanie splittera opartego na znakach do każdej sekcji przekraczającej ustalony limit rozmiaru fragmentu. Dzięki temu żaden fragment nie jest zbyt duży, a metadane nagłówków zostają zachowane.
from langchain_text_splitters import MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter
header_splitter = MarkdownHeaderTextSplitter(
headers_to_split_on=[('#', 'H1'), ('##', 'H2')]
)
char_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
)
with open('docs.md') as f:
md = f.read()
header_chunks = header_splitter.split_text(md)
final_chunks = char_splitter.split_documents(header_chunks)
print(f'{len(final_chunks)} final chunks produced')Dzielenie plików PDF z uwzględnieniem tabel
Pliki PDF wyodrębniane za pomocą narzędzi takich jak PyMuPDF lub pdfplumber często tracą strukturę tabel, w wyniku czego powstają zniekształcone wiersze tekstu. Aby sobie z tym poradzić, należy używać parserów PDF uwzględniających układ strony, które wykrywają obwiednie tabel i przed podziałem konwertują je do formatu Markdown lub CSV. Każdą tabelę należy traktować jako jeden fragment z ustrukturyzowanymi metadanymi wskazującymi, że jest to tabela, a nie proza.
import pdfplumber
def extract_pdf_chunks(pdf_path: str) -> list[dict]:
chunks = []
with pdfplumber.open(pdf_path) as pdf:
for page_num, page in enumerate(pdf.pages):
# Extract tables separately
for table in page.extract_tables():
rows = ['|'.join(str(c) for c in row) for row in table]
chunks.append({
'type': 'table',
'content': '\n'.join(rows),
'page': page_num + 1,
})
# Extract prose text
text = page.extract_text()
if text:
chunks.append({'type': 'text', 'content': text, 'page': page_num + 1})
return chunksWykrywanie typu pliku w mieszanych korpusach
Firmowe bazy wiedzy często zawierają różne typy plików: skrypty Python, dokumentację API w HTML, opisy architektury w Markdown oraz eksporty danych w CSV. Solidny potok dzielenia powinien wykrywać typ pliku na podstawie rozszerzenia lub typu MIME i kierować każdy dokument do odpowiedniego specjalistycznego splittera. Pozwala to uniknąć stosowania logiki dzielenia kodu do prozy i odwrotnie.
from pathlib import Path
def route_document(filepath: str) -> list[dict]:
ext = Path(filepath).suffix.lower()
if ext == '.py':
return chunk_python_file(filepath)
elif ext in ('.html', '.htm'):
with open(filepath) as f:
return chunk_html_by_section(f.read())
elif ext == '.md':
# use MarkdownHeaderTextSplitter
return chunk_markdown(filepath)
elif ext == '.pdf':
return extract_pdf_chunks(filepath)
else:
# fallback: plain text recursive splitter
return chunk_plain_text(filepath)Zachowywanie kontekstu za pomocą sąsiednich wierszy
Podczas dzielenia kodu według funkcji często warto uwzględnić kilka wierszy otaczającego kontekstu, na przykład instrukcje importu z początku pliku lub definicję klasy zawierającej daną metodę. Taki kontekst pomaga LLM-owi zrozumieć, jakie biblioteki są dostępne i jaka jest rola funkcji w szerszym kontekście klasy, co poprawia jakość generowanych odpowiedzi.
def chunk_with_imports(source_code: str, fn_node, lines: list[str]) -> str:
# Gather top-of-file imports (first block before first non-import)
import_lines = []
for line in lines:
stripped = line.strip()
if stripped.startswith('import ') or stripped.startswith('from '):
import_lines.append(line)
elif stripped and not stripped.startswith('#'):
break
fn_body = '\n'.join(lines[fn_node.lineno - 1:fn_node.end_lineno])
return '\n'.join(import_lines) + '\n\n' + fn_bodySzybki sprawdzian
Sprawdź swoją wiedzę na temat strategii dzielenia zależnych od rodzaju dokumentu z tego modułu.
Podsumowanie modułu
W tym module nauczysz się, że: dzielenie na podstawie AST zachowuje granice funkcji i klas w Pythonie, HTMLHeaderTextSplitter i MarkdownHeaderTextSplitter respektują hierarchię nagłówków, aby zachować kontekst sekcji, a podejście dwuetapowe (podział według nagłówków, a następnie podział według znaków) obsługuje zbyt duże sekcje bez utraty metadanych strukturalnych. Następnie omówimy wyszukiwanie hybrydowe łączące pobieranie gęste i rzadkie.
Ucz się Python dzięki korepetycjom AI — za darmo
Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.
- Kursy
- 30
- Lekcje
- 120
Często zadawane pytania
Czy lekcja „Strategie dopasowane do dokumentów: kod i HTML” jest bezpłatna?
Tak — pełny tekst „Strategie dopasowane do dokumentów: kod i HTML” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Engineering Academy, przejdź na CoddyKit PRO. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Strategie dopasowane do dokumentów: kod i HTML”?
Stosuj wyspecjalizowane dzielenie: dla kodu Python używaj dzielników funkcji opartych na AST, dla HTML parserów uwzględniających tagi, a dla Markdown hierarchii nagłówków. Ćwiczysz AI Engineering Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć AI Engineering Academy?
Nie wymagamy żadnego doświadczenia. AI Engineering Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.
Ile czasu zajmuje lekcja „Strategie dopasowane do dokumentów: kod i HTML”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji AI Engineering Academy?
Tak. Każda lekcja AI Engineering Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Dlaczego naiwne dzielenie na fragmenty pogarsza wyszukiwanie
- Dzielenie semantyczne na podstawie podobieństwa embeddingów
- Wyszukiwanie nadrzędny–podrzędny i od małego do dużego
- Strategie dopasowane do dokumentów: kod i HTML