Cuando tus datos ya están limpios y listos, el siguiente paso crítico es recuperar los documentos más relevantes para responder las preguntas de tus usuarios. Aquí es donde entran en juego los retrievers o recuperadores, y uno de los más poderosos es el Parent Retriever, una estrategia que combina lo mejor de los fragmentos pequeños y los documentos grandes.
¿Por qué existe una dicotomía entre documentos pequeños y grandes?
Al fragmentar documentos, te enfrentas a una decisión importante [1:06]:
- Documentos pequeños: preservan la semántica y la precisión de oraciones y párrafos.
- Documentos largos: preservan el contexto general del texto.
Si optas por documentos cada vez más grandes, los embeddings comienzan a perder precisión. Pero si solo usas fragmentos diminutos, pierdes el contexto necesario para que tu modelo de lenguaje genere respuestas completas. Ninguna de las dos opciones es perfecta por sí sola.
¿Cómo funciona el Parent Retriever?
El Parent Document Retriever resuelve esta dicotomía con una estrategia de dos niveles [1:56]:
- Primero, tomas tu conjunto de documentos y los divides en fragmentos grandes llamados documentos padres (por ejemplo, capítulos de un libro).
- Después, cada documento padre se fragmenta en documentos hijos, que son porciones más pequeñas y semánticamente precisas.
- Cuando un usuario hace una query, el sistema busca coincidencias contra los documentos hijos, aprovechando su precisión semántica.
- Una vez identificados los hijos relevantes, el sistema recupera el documento padre completo al que pertenecen.
De esta forma obtienes dos ventajas simultáneas: los fragmentos pequeños hacen match preciso con la consulta del usuario, y los documentos grandes aportan el contexto necesario para que el modelo de lenguaje responda de manera profunda y completa.
¿Dónde se almacenan padres e hijos?
Los documentos hijos, convertidos en vectores, se guardan en tu vector store (en este caso se usa Chroma) [3:18]. Los documentos padre se almacenan en un doc store o in-memory store, que podría ser una base de datos como Redis o simplemente la memoria del sistema para fines de prototipado.
¿Cómo se implementa un Parent Retriever con código?
Para construir este recuperador necesitas importar varios componentes [3:55]:
ParentDocumentRetriever: orquesta la relación entre padres e hijos.
InMemoryStore: almacena los documentos completos sin corte.
RecursiveCharacterTextSplitter: fragmenta los documentos en hijos.
Language: permite especificar el tipo de contenido (por ejemplo, Markdown).
- Una vector store como Chroma para guardar los embeddings de los fragmentos pequeños.
El proceso de configuración sigue estos pasos:
- Cargar los documentos usando un loader de LangChain. En el ejemplo se obtienen 964 documentos [4:48].
- Definir un child splitter con
RecursiveCharacterTextSplitter usando from_language para especificar que el contenido es Markdown. Se configura un chunkSize de 100 tokens y un chunkOverlap de 10 [5:15].
- Crear la vector store con Chroma, asignándole una colección llamada full documents.
- Crear el store en memoria con
InMemoryStore para guardar los documentos completos.
- Construir el retriever pasando la vector store, el doc store y el child splitter como parámetros [6:25].
- Agregar los documentos llamando a
retriever.add_documents().
¿Qué diferencia hay entre buscar en la vector store y usar el Parent Retriever?
Una vez configurado, puedes verificar que los 964 documentos completos están almacenados usando store.yield_keys() [7:28]. Al hacer una consulta como "MultiQuery Retriever will be able to overcome some of the limitations of...", la diferencia es clara [8:10]:
- La búsqueda directa en la vector store devuelve cuatro fragmentos pequeños.
- El Parent Retriever devuelve solo tres documentos únicos, porque detecta que dos de esos fragmentos pequeños pertenecen al mismo documento padre.
En lugar de entregar fragmentos repetitivos del mismo origen, el retriever trae el contexto completo una sola vez. Esto significa que tu modelo de lenguaje recibe información más rica y sin redundancias, lo que produce respuestas más profundas y coherentes [9:06].
La clave está en entender que varios fragmentos pequeños pueden pertenecer a un mismo documento grande. Al recuperar el padre en lugar de múltiples hijos redundantes, potencias la capacidad de tu modelo para dar respuestas completas. ¿Ya has probado esta estrategia con tus propios datos? Comparte tu experiencia en los comentarios.