En este artículo, aprenderá cómo construir un sistema de generación de recuperación aumentada de varios niveles y determinista utilizando gráficos de conocimiento y bases de datos vectoriales.
Los temas que cubriremos incluyen:
Diseño de una jerarquía de recuperación de tres niveles para lograr precisión objetiva. Implementación de un gráfico de conocimiento ligero. Usar reglas aplicadas rápidamente para resolver conflictos de recuperación de manera determinista.
Más allá de la búsqueda de vectores: construcción de un sistema Graph-RAG determinista de 3 niveles
Imagen del editor
Introducción: los límites del Vector RAG
Las bases de datos vectoriales se han convertido desde hace mucho tiempo en la piedra angular de los procesos modernos de recuperación de generación aumentada (RAG), sobresaliendo en la recuperación de texto de formato largo basado en similitud semántica. Sin embargo, las bases de datos vectoriales tienen notoriamente "pérdidas" cuando se trata de hechos atómicos, números y relaciones estrictas entre entidades. Un sistema RAG vectorial estándar podría confundir fácilmente en qué equipo juega actualmente un jugador de baloncesto, por ejemplo, simplemente porque aparecen varios equipos cerca del nombre del jugador en el espacio latente. Para resolver esto, necesitamos una arquitectura federada de múltiples índices.
En este tutorial, presentaremos dicha arquitectura, utilizando un backend de tienda cuádruple para implementar un gráfico de conocimiento para hechos atómicos, respaldado por una base de datos vectorial para un contexto difuso de cola larga.
Pero aquí está el giro: en lugar de depender de un enrutamiento algorítmico complejo para elegir la base de datos correcta, consultaremos todas las bases de datos, volcaremos los resultados en la ventana de contexto y usaremos reglas de fusión aplicadas mediante indicaciones para forzar al modelo de lenguaje (LM) a resolver conflictos de manera determinista. El objetivo es intentar eliminar las alucinaciones relacionales y construir una previsibilidad determinista absoluta donde más importa: los hechos atómicos.
Descripción general de la arquitectura: la jerarquía de 3 niveles
Nuestro canal aplica una estricta jerarquía de datos utilizando tres niveles de recuperación:
Prioridad 1 (hechos gráficos absolutos): un gráfico de conocimiento simple de Python QuadStore que contiene verdades fundamentales verificadas e inmutables estructuradas en formato Sujeto-Predicado-Objeto más Contexto (SPOC). Prioridad 2 (datos del gráfico estadístico): un QuadStore secundario que contiene estadísticas agregadas o datos históricos. Este nivel está sujeto a la anulación de Prioridad 1 en caso de conflictos (por ejemplo, un dato del equipo actual de Prioridad 1 anula una estadística histórica del equipo de Prioridad 2). Prioridad 3 (documentos vectoriales): una base de datos vectorial densa estándar (ChromaDB) para documentos de texto generales, que solo se utiliza como alternativa si los gráficos de conocimiento carecen de la respuesta.
Configuración del entorno y requisitos previos
Para seguir adelante, necesitará un entorno que ejecute Python, una infraestructura LM local y un modelo servido (usamos Ollama con llama3.2) y las siguientes bibliotecas principales:
chromadb: para el nivel de base de datos vectorial spaCy: para el reconocimiento de entidades nombradas (NER) para consultar las solicitudes de gráficos: para interactuar con nuestro punto final de inferencia LM local QuadStore: para el nivel de gráficos de conocimiento (consulte el repositorio de QuadStore)
# Install required libraries
pip install chromadb spacy requests
# Download the spaCy English model
python -m spacy download en_core_web_sm
# Install required libraries
pip install chromadb spacy requests
# Download the spaCy English model
python – m spacy download en_core_web_sm
Puede descargar manualmente la implementación simple de Python QuadStore desde el repositorio de QuadStore y colocarla en algún lugar de su sistema de archivos local para importarla como un módulo.
⚠️ Nota: La implementación completa del código del proyecto está disponible en este repositorio de GitHub.
Una vez resueltos estos requisitos previos, profundicemos en la implementación.
Paso 1: construir una tienda cuádruple liviana (el gráfico)
Para implementar los datos de Prioridad 1 y Prioridad 2, utilizamos un gráfico de conocimiento en memoria ligero y personalizado llamado almacén cuádruple. Este gráfico de conocimiento se aleja de las incrustaciones semánticas hacia un esquema estricto de nodo-borde-nodo conocido internamente como SPOC (Sujeto-Predicado-Objeto más Contexto).
Este módulo QuadStore funciona como un motor de almacenamiento altamente indexado. Debajo del capó, asigna todas las cadenas a ID enteros para evitar la sobrecarga de la memoria, mientras mantiene un índice de diccionario de cuatro vías (spoc, pocs, ocsp, cspo) para permitir búsquedas en tiempo constante en cualquier dimensión. Si bien no profundizaremos aquí en los detalles de la estructura interna del motor, utilizar la API en nuestro script RAG es increíblemente sencillo.
¿Por qué utilizar esta implementación simple en lugar de una base de datos gráfica más sólida como Neo4j o ArangoDB? Sencillez y rapidez. Esta implementación es increíblemente liviana y rápida, además de tener el beneficio adicional de ser fácil de entender. Esto es todo lo que se necesita para este caso de uso específico sin tener que aprender una API de base de datos de gráficos compleja.
En realidad, solo hay un par de métodos de QuadStore que debes comprender:
agregar (sujeto, predicado, objeto, contexto): agrega un nuevo hecho al gráfico de conocimiento. consulta (sujeto, predicado, objeto, contexto): consulta el gráfico de conocimiento en busca de hechos que coincidan con el sujeto, predicado, objeto y contexto dados.
Inicialicemos QuadStore actuando como nuestro modelo de verdad absoluta de Prioridad 1:
from quadstore import QuadStore
# Initialize facts quadstore
facts_qs = QuadStore()
# Natively add facts (Subject, Predicate, Object, Context)
facts_qs.add(“LeBron James”, “likes”, “coconut milk”, “NBA_trivia”)
facts_qs.add(“LeBron James”, “played_for”, “Ottawa Beavers”, “NBA_2023_regular_season”)
facts_qs.add(“Ottawa Beavers”, “obtained”, “LeBron James”, “2020_expansion_draft”)
facts_qs.add(“Ottawa Beavers”, “based_in”, “downtown Ottawa”, “NBA_trivia”)
facts_qs.add(“Kevin Durant”, “is”, “a person”, “NBA_trivia”)
facts_qs.add(“Ottawa Beavers”, “had”, “worst first year of any expansion team in NBA history”, “NBA_trivia”)
facts_qs.add(“LeBron James”, “average_mpg”, “12.0”, “NBA_2023_regular_season”)
from quadstore import QuadStore
# Initialize facts quadstore
facts_qs = QuadStore ( )
# Natively add facts (Subject, Predicate, Object, Context)
facts_qs . add ( “LeBron James” , “likes” , “coconut milk” , “NBA_trivia” )
facts_qs . add ( “LeBron James” , “played_for” , “Ottawa Beavers” , “NBA_2023_regular_season” )
facts_qs . add ( “Ottawa Beavers” , “obtained” , “LeBron James” , “2020_expansion_draft” )
facts_qs . add ( “Ottawa Beavers” , “based_in” , “downtown Ottawa” , “NBA_trivia” )
[]para entidad en entidades: sujeto_factos = qs.query(subject=entity) object_facts = qs.query(object=entity) fact.extend(subject_facts + object_facts) # Deduplicar hechos y devolver return list(set(tuple(fact) para hecho en hechos))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
importar espacio
# Cargar nuestro modelo de PNL
PNL = espacioso . cargar ( "en_core_web_sm" )
def extraer_entidades ( texto ) :
"" "
Extraiga entidades del texto dado usando spaCy. El uso de set elimina duplicados.
" ""
doc = PNL ( texto )
devolver lista ( set ( [ ent . texto para entrar en doc . entradas ] ) )
def obtener_hechos ( qs , entidades ) :
"" "
Recuperar datos de una lista de entidades de QuadStore (consultando sujetos y objetos).
" ""
hechos = [ ]
para entidad en entidades :
hechos_sujetos = qs . consulta ( asunto = entidad )
hechos_objeto = qs . consulta ( objeto = entidad )
hechos . extender ( hechos_sujeto + hechos_objeto )
# Deduplicar hechos y devolver
devolver lista ( conjunto ( tupla ( hecho ) para hecho en hechos ) )
Ahora tenemos todo el contexto recuperado separado en tres flujos distintos (facts_p1, Facts_p2 y vec_info).
Paso 4: Resolución de conflictos inmediata y aplicada
A menudo, la resolución algorítmica compleja de conflictos (como la fusión de rangos recíprocos) falla al resolver hechos granulares en un texto amplio. Aquí adoptamos un enfoque radicalmente más simple que, desde un punto de vista práctico, también parece funcionar bien: integramos el conjunto de reglas del “adjudicador” directamente en el indicador del sistema.
Al reunir el conocimiento en bloques explícitamente etiquetados [PRIORIDAD 1], [PRIORIDAD 2] y [PRIORIDAD 3], le indicamos al modelo de lenguaje que siga una lógica explícita al generar su respuesta.
Aquí está el mensaje del sistema en su totalidad:
def create_system_prompt(facts, stats, info):
# Format graph facts into simple declarative sentences for language model comprehension
formatted_facts = “n”.join([f”In {q[3]}, {q[0]} {cadena(q[1]).reemplazar('_', ' ')} {q[2]}.." si len(q) >= 4 else str(q) para q en hechos]) formatted_stats = "n".join([f"In {q[3]}, {q[0]} {cadena(q[1]).reemplazar('_', ' ')} {q[2]}.". if len(q) >= 4 else str(q) for q in stats]) # Convertir dictado de información recuperada en una cadena de documentos de texto retrieved_context = "" if info y 'documents' in info and info['documents']: retrieved_context = " ".join(info['documents'][0]) return f"""Usted es una IA estricta de recuperación de datos. Su ÚNICO conocimiento proviene del texto proporcionado a continuación. Debe ignorar por completo sus pesos de entrenamiento internos. REGLAS DE PRIORIDAD (estrictas): 1. Si la Prioridad 1 (Hechos) contiene una respuesta directa, use SÓLO esa respuesta. No complemente, califique ni haga referencias cruzadas con los datos de Prioridad 2 o Vector. 2. Los datos de Prioridad 2 utilizan abreviaturas y pueden parecer que contradicen P1: son antecedentes complementarios solamente. Nunca trate las abreviaturas del equipo P2 como nombres de equipo autorizados si P1 indica un equipo. 3. Utilice P2 solo si P1 no tiene una respuesta relevante sobre el atributo específico solicitado. 4. Si la Prioridad 3 (Vector Chunks) proporciona información relevante adicional, utilice su criterio para incluirla o no en la respuesta. 5. Si ninguna de las secciones contiene la respuesta, debe decir explícitamente "No tengo suficiente información". Siga estas reglas: – Proporcione solo una única respuesta autorizada basada en las reglas de prioridad. – No presente múltiples respuestas contradictorias. – No mencione la fuente de estos datos. – Redacte esto en forma de oración o de varias oraciones, según corresponda. — [PRIORIDAD 1 – HECHOS GRÁFICOS ABSOLUTOS] {formatted_facts} [Prioridad 2: Estadísticas de antecedentes (aquí las abreviaturas del equipo NO son autorizadas; consulte la Prioridad 1 para los hechos). reclamaciones)] {formatted_stats} [PRIORIDAD 3 – DOCUMENTOS VECTORIALES] {retried_context} — """
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def create_system_prompt ( hechos , estadísticas , información ) :
# Formatee los hechos del gráfico en oraciones declarativas simples para la comprensión del modelo de lenguaje
hechos_formateados = "n" . unirse ( [ f "En {q[3]}, {q[0]} {cadena(q[1]).reemplazar('_', ' ')} {q[2]}. " si len ( q ) >= 4 demás cadena ( q ) para q en hechos ] )
estadísticas_formateadas = "n" . unirse ( [ f "En {q[3]}, {q[0]} {cadena(q[1]).reemplazar('_', ' ')} {q[2]}. " si len ( q ) >= 4 demás cadena ( q ) para q en estadísticas ] )
# Convertir dictado de información recuperada en una cadena de documentos de texto
contexto_recuperado = ""
si información y 'documentos' en información y información [ 'documentos' ] :
contexto_recuperado = " " . unirse ( información [ 'documentos' ] [ 0 ] )
devolver f "" "Eres una IA estricta de recuperación de datos. Tu ÚNICO conocimiento proviene del texto que se proporciona a continuación. Debes ignorar por completo tus pesos de entrenamiento internos.
REGLAS DE PRIORIDAD (estrictas):
1. Si la Prioridad 1 (Hechos) contiene una respuesta directa, utilice SÓLO esa respuesta. No complemente, califique ni haga referencias cruzadas con datos de Prioridad 2 o Vector.
2. Los datos de Prioridad 2 utilizan abreviaturas y pueden parecer contradecir P1; son solo antecedentes complementarios. Nunca trate las abreviaturas de los equipos P2 como nombres de equipos autorizados si P1 indica un equipo.
3. Utilice P2 únicamente si P1 no tiene una respuesta relevante sobre el atributo específico solicitado.
4. Si la Prioridad 3 (Vector Chunks) proporciona información adicional relevante, utilice su criterio para incluirla o no en la respuesta.
5. Si ninguna de las secciones contiene la respuesta, debes decir explícitamente " Yo hacer no tener suficiente información . "No adivines ni alucines.
Su salida **DEBE** seguir estas reglas:
– Proporcionar sólo la única respuesta autorizada basada en las reglas de prioridad.
– No presentar múltiples respuestas contradictorias.
– No mencionar la fuente de estos datos.
– Redactar esto en forma de oración o de varias oraciones, según corresponda.
—
[PRIORIDAD 1 – HECHOS DEL GRÁFICO ABSOLUTO]
{hechos_formateados}
[Prioridad 2: Estadísticas de antecedentes (aquí las abreviaturas del equipo NO tienen autoridad; remítase a la Prioridad 1 para afirmaciones fácticas)]
{estadísticas_formateadas}
[PRIORIDAD 3 – DOCUMENTOS VECTORIALES]
{recuperado_contexto}
—
" ""
Muy diferente a las indicaciones de "… y no cometas ningún error", que son poco más que cruzar los dedos y desear no tener alucinaciones, en este caso presentamos al LM hechos atómicos reales, posibles hechos contradictorios "menos recientes" y resultados de búsqueda de vectores semánticamente similares, junto con una jerarquía explícita para determinar qué conjunto de datos es correcto cuando se encuentran conflictos. ¿Es infalible? No, por supuesto que no, pero es un enfoque diferente que vale la pena considerar y agregar al conjunto de herramientas para combatir las alucinaciones.
No olvides que puedes encontrar el resto del código de este proyecto aquí.
Paso 5: unirlo todo y probarlo
Para resumir todo, el hilo de ejecución principal de nuestro sistema RAG llama a la instancia local de Llama a través de la API REST y le entrega el mensaje estructurado del sistema anterior junto con la pregunta base del usuario.
Cuando se ejecuta en la terminal, el sistema aísla nuestros tres niveles de prioridad, procesa las entidades y consulta el LM de manera determinista.
Consulta 1: Recuperación de hechos con QuadStore
Al consultar un hecho absoluto como "¿Quién es el jugador estrella del equipo Ottawa Beavers?", el sistema se basa completamente en hechos de Prioridad 1.
LeBron juega para los Castores de Ottawa
Debido a que la Prioridad 1, en este caso, establece explícitamente "Ottawa Beavers obtuvo a LeBron James", el mensaje indica al LM que nunca complemente esto con documentos vectoriales o abreviaturas estadísticas, con el objetivo de eliminar la alucinación tradicional de la relación RAG. Los documentos de base de datos de vectores de respaldo también respaldan esta afirmación, con artículos sobre LeBron y su paso por el equipo de la NBA de Ottawa. Compare esto con un mensaje de LM que arroja resultados de búsqueda semánticos conflictivos en un modelo y le pide, genéricamente, que determine cuál es verdadero.
Consulta 2: Más recuperación de hechos
¿Los castores de Ottawa, dices? No estoy familiarizado con ellos. Supongo que juegan desde Ottawa, pero ¿en qué parte de la ciudad tienen su sede exactamente? Los hechos de prioridad 1 nos lo pueden decir. Tenga en cuenta que estamos luchando contra lo que el modelo en sí ya sabe (los Beavers no son un equipo real de la NBA), así como contra el conjunto de datos de estadísticas generales de la NBA (que no incluye nada sobre los Ottawa Beavers).
La casa de los castores de Ottawa
Consulta 3: Cómo afrontar el conflicto
Al consultar un atributo tanto en el gráfico de hechos absolutos como en el gráfico de estadísticas generales, como "¿Cuál fue el MPG promedio de LeBron James en la temporada 2023 de la NBA?", el modelo se basa en los datos del nivel de Prioridad 1 sobre los datos de estadísticas de Prioridad 2 existentes.
Salida de consulta de MPG de LeBron
Consulta 4: Unir una respuesta sólida
¿Qué sucede cuando hacemos una pregunta no estructurada como "¿Qué lesión sufrió la estrella de los Ottawa Beavers durante la temporada 2023?" Primero, el modelo necesita saber quién es el jugador estrella de los Ottawa Beavers y luego determinar cuál fue su lesión. Esto se logra con una combinación de datos de Prioridad 1 y Prioridad 3. El LM fusiona esto suavemente en una respuesta final.
Resultado de la consulta sobre lesiones de LeBron
Pregunta 5: Otra respuesta sólida
Aquí hay otro ejemplo de cómo unir una respuesta coherente y precisa a partir de datos de varios niveles. “¿Cuántas victorias tuvo el equipo en el que jugó LeBron James cuando dejó la temporada?”
Resultado de la consulta n.º 2 sobre lesiones de LeBron
No olvidemos que para todas estas consultas, el modelo debe ignorar el hecho de que existen datos contradictorios (¡e inexactos!) en el gráfico de estadísticas de Prioridad 2 que sugieren (¡nuevamente, erróneamente!) que LeBron James jugó para los LA Lakers en 2023. Y tampoco olvidemos que estamos usando un modelo de lenguaje simple con solo 3 mil millones de parámetros (llama3.2:3b).
Conclusión y compensaciones
Al dividir sus fuentes de recuperación en distintas capas autorizadas y dictar reglas de resolución exactas mediante ingeniería rápida, la esperanza es reducir drásticamente las alucinaciones fácticas o la competencia entre datos que de otro modo serían igualmente verdaderos.
Las ventajas de este enfoque incluyen:
Previsibilidad: 100 % de previsibilidad determinista para hechos críticos almacenados en la Prioridad 1 (objetivo). Explicabilidad: si es necesario, puede forzar al LM a generar su cadena [REASONING] para validar por qué la Prioridad 1 anuló el resto. Simplicidad: no es necesario entrenar enrutadores de recuperación personalizados.
Las ventajas y desventajas de este enfoque incluyen:
Sobrecarga de tokens: volcar las tres bases de datos en la ventana de contexto inicial consume sustancialmente más tokens que la típica recuperación filtrada por algoritmos. Model Reliance: este sistema requiere un LM que cumpla con las instrucciones para evitar volver a caer en un comportamiento latente de peso de entrenamiento.
Para entornos en los que son obligatorias una alta precisión y una baja tolerancia a los errores, implementar una jerarquía factual de varios niveles junto con su base de datos vectorial puede ser el diferenciador entre prototipo y producción.