Реалізація Parent Document Retriever для RAG

Проектуємо та впроваджуємо системи штучного інтелекту: від прототипу до production-ready рішення. Наша команда поєднує експертизу в машинному навчанні, дата-інжинірингу та MLOps, щоб AI працював не в лабораторії, а в реальному бізнесі.
Показано 1 з 1Усі 1566 послуг
Реалізація Parent Document Retriever для RAG
Середній
від 1 дня до 3 днів
Часті запитання

Напрямки AI-розробки

Етапи розробки AI-рішення

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1284
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1196
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    901
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1119
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    586
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    853

Реалізація Parent Document Retriever для RAG

Parent Document Retriever — архітектурний паттерн RAG, який вирішує фундаментальне протиріччя: для точного retrieval потрібні маленькі фрагменти (краща семантична точність), але для якісної генерації потрібен широкий контекст (повний розділ, а не 3 речення). Рішення: індексуємо маленькі «дочірні» фрагменти, а в LLM передаємо їхні «батьківські» великі документи.

Архітектура Parent Document Retriever

Індексування:
├── Документ (2000 токенів)
│   ├── Child chunk 1 (128 токенів) → embedding → індекс
│   ├── Child chunk 2 (128 токенів) → embedding → індекс
│   ├── Child chunk 3 (128 токенів) → embedding → індекс
│   └── Child chunk 4 (128 токенів) → embedding → індекс

Пошук:
├── Query → пошук за child embeddings
├── Знайден child_chunk_3 (висока релевантність)
└── Повертаємо parent document (2000 токенів) → в LLM

Реалізація з LangChain

from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryByteStore, LocalFileStore
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Qdrant
from langchain_openai import OpenAIEmbeddings

embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

# Сховище батьківських документів (persistent)
store = LocalFileStore("./parent_docs_store")

# Спліттери: child дрібний, parent крупний
child_splitter = RecursiveCharacterTextSplitter(
    chunk_size=200,
    chunk_overlap=20,
)
parent_splitter = RecursiveCharacterTextSplitter(
    chunk_size=2000,
    chunk_overlap=100,
)

vectorstore = Qdrant.from_texts(
    texts=[],  # Порожній — заповнюється через retriever
    embedding=embeddings,
    collection_name="child_chunks",
    url="http://localhost:6333",
)

retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,
    docstore=store,
    child_splitter=child_splitter,
    parent_splitter=parent_splitter,
)

# Індексування
retriever.add_documents(documents, ids=None)

# Запит — повертає батьківські документи
relevant_docs = retriever.invoke("процедура узгодження закупки")
print(f"Знайдено {len(relevant_docs)} батьківських документів")
print(f"Розмір першого: {len(relevant_docs[0].page_content)} символів")

Трьохрівнева ієрархія

Для складних документів можна використовувати три рівні: документ → секція → параграф:

from langchain.retrievers import ParentDocumentRetriever

# Sub-chunk (для індексування) → chunk (parent) → section (grandparent)
sub_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=10)
chunk_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)

retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,
    docstore=store,
    child_splitter=sub_splitter,
    parent_splitter=chunk_splitter,
)

Практичне порівняння підходів

Набір даних: технічні регламенти (середній документ 3500 слів, 20–40 розділів).

Підхід Chunk у індексі Контекст в LLM Context Recall Faithfulness
Стандартний (512 токенів) 512 512×5=2560 0.69 0.81
Стандартний (256 токенів) 256 256×5=1280 0.74 0.78
Parent Doc (child=200, parent=1500) 200 1500×3=4500 0.88 0.91
Parent Doc + Reranker 200 1500×3=4500 0.88 0.94

Parent Document Retriever значно поліпшує context recall (+19%) при високій достовірності: дочірні фрагменти точно знаходять потрібний розділ, батьківські документи надають повний контекст.

Кешування батьківських документів

При високому QPS батьківські документи варто кешувати в Redis:

import redis
import json

redis_client = redis.Redis(host="localhost", port=6379)

class CachedParentDocumentRetriever:
    def __init__(self, base_retriever, ttl: int = 3600):
        self.retriever = base_retriever
        self.ttl = ttl

    def invoke(self, query: str) -> list:
        # Пошук дочірніх фрагментів
        child_docs = self.retriever.vectorstore.similarity_search(query, k=5)

        # Завантажуємо батьків з кешем
        parent_docs = []
        for child in child_docs:
            parent_id = child.metadata.get("doc_id")
            cache_key = f"parent:{parent_id}"

            cached = redis_client.get(cache_key)
            if cached:
                parent_docs.append(json.loads(cached))
            else:
                parent = self.retriever.docstore.mget([parent_id])[0]
                if parent:
                    redis_client.setex(cache_key, self.ttl, json.dumps(parent.dict()))
                    parent_docs.append(parent)

        return parent_docs

Часові рамки

  • Налаштування Parent Document Retriever: 2–3 дні
  • Підбір оптимальних розмірів фрагментів: 2–3 дні
  • Тестування та оцінка: 2–3 дні
  • Всього: 1 тиждень