Generación Aumentada por Recuperación: Búsqueda Inteligente de Documentos para Empresas
RAG combina búsqueda vectorial con IA generativa para ofrecer respuestas precisas a partir de repositorios privados de documentos. El sistema recupera contenido relevante de PDFs, archivos Excel, código y más, luego genera explicaciones basadas estrictamente en los materiales originales, reduciendo significativamente las falsedades.
El proceso comienza con el preprocesamiento: los documentos se analizan, se dividen en fragmentos inteligentes considerando su tipo de contenido y se convierten en embeddings almacenados en una base de datos vectorial.
Extracción de Texto de Formatos de Archivo
El sistema soporta múltiples formatos sin pérdida de datos. Cada formato utiliza un extractor específico.
SUPPORTED = {
'.pdf': lambda f: ''.join(p.extract_text() or '' for p in PyPDF2.PdfReader(f).pages),
'.docx': lambda f: '\n'.join(p.text for p in Document(f).paragraphs),
'.pptx': lambda f: '\n'.join(
shape.text
for slide in Presentation(f).slides
for shape in slide.shapes
if hasattr(shape, "text") and shape.text.strip()
),
'.xlsx': extract_xlsx,
'.csv': extract_csv,
# .txt, .json, .py, .js, etc.
}
La salida es texto limpio y listo para procesar.
Segmentación Inteligente por Tipo de Contenido
El texto no se divide por tamaño fijo; se segmenta semánticamente. Primero, se detecta el tipo de contenido:
def detect_content_type(text):
if re.search(r'<table|</td>|<tr>', text, re.IGNORECASE):
return 'table'
tab_lines = sum(1 for line in text.splitlines() if '\t' in line)
if tab_lines > max(3, len(text.splitlines()) * 0.4):
return 'table'
if re.search(r'function\s+\w+\s*\(|\bdef\s+\w+\s*\(|\bclass\s+\w+\b', text):
return 'code'
return 'prose'
- Tablas: Incluyen encabezados repetidos en cada fragmento
- Código: Se separa por funciones/clases sin romper la lógica
- Prosa: Límites semánticos usando embeddings de frases
Para tablas:
def split_table_by_rows(text, chunk_size):
lines = [l for l in text.splitlines() if l.strip()]
header = lines[0]
chunks = []
cur_lines = [header]
for line in lines[1:]:
if sum(len(l) for l in cur_lines) + len(line) >= chunk_size:
chunks.append('\n'.join(cur_lines))
cur_lines = [header, line]
else:
cur_lines.append(line)
if cur_lines:
chunks.append('\n'.join(cur_lines))
return chunks
La segmentación semántica usa similitud coseno entre frases adyacentes con umbral adaptativo (percentil 25).
Generación de Embeddings
Los fragmentos se vectorizan usando modelos como text-embedding-3-small (1536 dimensiones). Los vectores y metadatos se almacenan en la base de datos.
def get_embeddings(texts, batch_size=100):
embeddings = []
for i in range(0, len(texts), batch_size):
resp = requests.post(
"https://openrouter.ai/api/v1/embeddings",
headers=HEADERS,
json={
"model": "openai/text-embedding-3-small",
"input": texts[i:i + batch_size],
"encoding_format": "float"
},
).json()
embeddings.extend(d["embedding"] for d in resp.get("data", []))
return np.array(embeddings)
Búsqueda Multietapa
La consulta se transforma en un vector, se reformula en tres variaciones mediante LLM, y luego se procesa mediante búsqueda híbrida.
Búsqueda Híbrida: Vectores + BM25
- Búsqueda vectorial: Similitud coseno en espacio de embeddings
- BM25: TF-IDF con k1=1.5, b=0.75 para coincidencias exactas
Los resultados se fusionan usando RRF:
# Fusión RRF
for rank, idx in enumerate(sorted_by_vector):
rrf_scores[idx] += 1.0 / (k_rrf + rank)
for rank, idx in enumerate(sorted_by_bm25):
rrf_scores[idx] += bm25_normalized[idx] * (1.0 / (k_rrf + rank))
Reclasificación y Filtros
Se evalúan los 50 candidatos top mediante LLM, seleccionándose 5–10 mejores. Reglas de inclusión forzada:
- Citas entre "comillas"
- Números/IDs (regex: \b(№|N|number)\s*\d+\b)
quote_matches = re.findall(r'«([^»]+)»|"([^"+])"', query)
for match in quote_matches:
quote = (match[0] or match[1]).strip()
if len(quote) > 5:
for i, chunk in enumerate(chunks_data):
if quote in chunk['content']:
forced_indices.append(i)
break
Optimizaciones de Rendimiento
Búsqueda Jerárquica
Primero, se identifican los documentos principales mediante anotaciones; luego, se busca dentro de sus fragmentos.
doc_summaries = [chunks_data_by_doc[doc_id][0]['content'][:500] for doc_id in all_doc_ids]
doc_embs = get_embeddings(doc_summaries)
sims = np.dot(doc_embs_normalized, query_embedding_normalized)
top_doc_ids = [all_doc_ids[i] for i in np.argsort(sims)[::-1][:5]]
chunks_data = [ch for ch in chunks_data if ch['doc_id'] in top_doc_ids]
Búsqueda Iterativa
Para consultas complejas, múltiples rondas refinen los resultados usando retroalimentación de LLM.
Conclusiones Clave
- RAG minimiza las falsedades generando respuestas estrictamente a partir de fragmentos relevantes
- La búsqueda híbrida (vectores + BM25 + RRF) mejora la precisión entre un 20% y un 30% respecto a la búsqueda puramente vectorial
- La segmentación semántica preserva el contexto en tablas y código, reduciendo el ruido
- El enfoque jerárquico acelera la búsqueda en corpora grandes (>10k documentos)
- Los filtros forzados aseguran la recuperación de hechos que contienen números o texto entre comillas
— Editorial Team
Aún no hay comentarios.