Dominando la Construcción de Grafos de Conocimiento con LLM en Minutos

Cuando escuché por primera vez el término "grafo de conocimiento", me pareció intimidante, aunque no era precisamente la complejidad técnica lo que me preocupaba, sino lo laborioso que parecía ser todo el proceso de construcción.

He intentado construir grafos de conocimiento anteriormente y no tuvo éxito.

Los grafos son una de las mejores herraminetas para representar relaciones complejas y se utilizan ampliamente en sistemas de recomendación, detección de fraude y otras áreas. Sin embargo, lo que más me interesó fue su potencial en la recuperación de información.

Más adelante comencé a experimentar con grafos de cooncimiento para mejorar el RAG (Generación Aumentada por Recuperación, por sus siglas en inglés).

En realidad, construir un sistema RAG no requiere necesariamente un grafo de conocimiento, ni siquiera una base de datos. Siempre que puedas extraer información relevante de grandes cantidades de datos para pasársela a un LLM, puedes implementar RAG.

Por ejemplo, puedes utilizar búsquedas web como estrategia de recuperación, o aprovechar la búsqueda semántica de bases de datos vectoriales para mejorar los resultados.

Si utilizas una base de datos de grafos para recuperar información, a esto se le llama GraphRAG, que es la aplicación específica de los grafos de conocimiento en RAG.

Este artículo no pretende ser una guía completa de GraphRAG, sino más bien una discusión sobre cómo construir grafos de conocimiento utilizando LLM. Sin embargo, dado que lo mencionamos, vale la pena explicar brevemente por qué los grafos de conocimiento pueden funcionar como almacenamiento de contenido para mejorar RAG.

¿Por qué RAG necesita grafos de conocimiento?

Los grafos de conocimiento ofrecen una forma más inteligente de recuperar información, algo que el almacenamiento vectorial por sí solo no siempre puede lograr.

La forma principal de obtener información de un almacenamiento vectorial es a través de la similitud semántica del texto codificado. El proceso funciona así:

Utilizamos un modelo de embedding vectorial, como el text-embedding-3 de OpenAI, para convertir el texto en representaciones vectoriales. Incluso si "Apple" y "Appam" (un alimento indio) comparten muchas letras en común, sus representaciones vectoriales serán muy diferentes. Estos vectores se almacenan posteriormente en una base de datos vectorial como Chroma.

Durante la fase de recuperación, codificamos la consulta del usuario con el mismo modelo de embedding y luego recuperamos información del almacenamiento vectorial mediante el cálculo de una matriz de distancias (como la similitud del coseno).

Este método es la única forma de recuperar información de almacenes vectoriales. Como probablemente habrás adivinado, la elección del modelo de embedding juega un papel crucial en la precisión de la recuperación. El tipo de base de datos generalmente tiene menor impacto en la precisión, aunque puede afectar otros aspectos como la concurrencia o la velocidad.

Un ejemplo:

Supongamos que tienes un documento grande sobre los equipos directivos de varias empresas.

Si la pregunta es "¿De qué empresa es CEO el señor John Doe?", un sistema de embedding vectorial puede manejarla fácilmente porque la respuesta generalmente se puede encontrar directamente en los fragmentos del documento.

Pero si la pregunta se vuelve más compleja, como "¿Con qué personas ha sido director el señor John Doe en varias juntas directivas?", la cosa se complica.

La recuperación por similitud vectorial depende de menciones explícitas en la base de conocimiento; solo la información explícitamente mencionada será recuperada. Por otro lado, los grafos de conocimiento pueden inferir más información a través de los datos generales.

p>Por ejemplo, la búsqueda de vecinos más cercanos requiere menciones directas en la base de conocimiento. Si falta este tipo de resumen directo, los sistemas basados en embeddings vectoriales tienen dificultades para razonar o integrar información a través de múltiples fuentes de datos. En contraste, los grafos de conocimiento pueden razonar a nivel de todo el conjunto de datos. Por ejemplo, pueden agrupar el "nodo País" y el "nodo Estrategia" por proximidad de relaciones, y con una simple consulta podemos obtener la información que necesitamos.

Ahora que entendemos la importancia de los grafos de conocimiento, veamos los desafíos que enfrentamos al construir uno.

Construir un grafo de conocimiento solía ser un problema (pero ya no)

Hace años, un colega me presentó el concepto de grafo de conocimiento. Quería crear un grafo unificado y buscable para todos nuestros proyectos.

Después de dedicar un fin de semana a aprender Neo4j, sentí que esta herramienta tenía un gran potencial.

Pero la dificultad en ese momento radicaba en cómo extraer nodos y relaciones (es decir, entidades y conexiones) de una gran cantidad de documentos PDF, presentaciones y archivos de Word.

Probamos algunos métodos, pero los resultados no fueron satisfactorios. Solo podíamos transformar manualmente estos documentos no estructurados en modelos de datos de grafos.

Intentamos usar PyPDF2 para leer archivos PDF y buscar palabras clave, pero no obtuvimos buenos resultados. Finalmente, tuvimos que abandonar este enfoque, considerándolo "que no valía la pena el esfuerzo".

Sin embargo, con la amplia aplicación de los LLM (modelos de lenguaje grandes), el panorama cambió.

En las siguientes secciones, utilizaremos un LLM para construir un grafo de conocimiento simple (o quizás el más simple posible).

Construir un grafo de conocimiento en minutos

Hoy en día, extraer información de texto e imágenes ya no es tan difícil como antes.

Aunque el procesamiento de datos no estructurados aún puede mejorarse, los últimos años, especialmente con el desarrollo de los LLM, nos han traído muchas oportunidades nuevas.

En esta sección, explicaremos cómo usar un LLM para construir un grafo de conocimiento simple y discutiremos cómo optimizarlo para aplicaciones empresariales.

Utilizaremos la función experimental de Langchain llamada LLMGraphTransformer, junto con Neo4j Aura como solución de almacenamiento en la nube para la base de datos de grafos.

p>Si estás usando LlamaIndex, puedes probar su KnowledgeGraphIndex, que es una API similar a la herramienta que usamos. Por supuesto, también puedes elegir otras bases de datos de grafos, usándolas de manera similar a Neo4j. A continuación, instalemos las dependencias necesarias:

pip install neo4j langchain-openai langchain-community langchain-experimental

En este ejemplo, mapearemos una lista que contiene información sobre ejecutivos y las organizaciones a las que pertenecen a la base de datos de grafos. Si quieres seguir el proceso, puedes encontrar los datos de ejemplo que usé aquí. Estos datos son ficticios que creé (usando, por supuesto, tecnología de IA).

El siguiente es el código simple para transformar documentos no estructurados en un grafo de conocimiento; en realidad, este código es muy conciso:

import os

from langchain_neo4j import Neo4jGraph
from langchain_openai import ChatOpenAI
from langchain_community.document_loaders import TextLoader
from langchain_experimental.graph_transformers import LLMGraphTransformer

grafo = Neo4jGraph(
    url=os.getenv("NEO4J_URL"),
    username=os.getenv("NEO4J_USERNAME", "neo4j"),
    password=os.getenv("NEO4J_PASSWORD"),
)

# ------------------- Importante --------------------
transformador_llm = LLMGraphTransformer(
    llm=ChatOpenAI(temperature=0, model_name="gpt-4-turbo")
)
# ------------------- Importante --------------------

documento = TextLoader("datos/ejemplo.txt").load()

documentos_grafo = transformador_llm.convert_to_graph_documents(documento)

grafo.add_graph_documents(documentos_grafo)

Este código es muy claro y directo.

He destacado la parte más crucial: cómo construir la base de datos de grafos. La clase LLMGraphTransformer utiliza el LLM que le pasamos para extraer los datos del grafo del documento.

Ahora puedes pasar cualquier tipo de documento de Langchain al método convert_to_graph_documents para extraer el grafo de conocimiento. La fuente de datos puede ser texto, archivos Markdown, contenido de páginas web, o incluso resultados de consultas a otras bases de datos.

Si esto se hiciera manualmente, esta tarea podría haber tomado meses de trabajo hace unos años.

También puedes acceder a la consola de Aura db para ver el grafo generado, que podría verse así:

[Imagen: Grafo generado a través del módulo LLMGraphTransformer en Neo4J Aura]

En segundo plano, la API utiliza el LLM para extraer información relevante y construir objetos Python de Neo4j para representar nodos y relaciones.

Como podemos ver, usar la API extractora para construir grafos de conocimiento se ha vuelto extremadamente simple. A continuación, discutamos cómo optimizarlo para aplicaciones empresariales.

Cómo adaptar los grafos de conocimiento para necesidades empresariales

En su momento abandonamos la idea de construir un grafo de conocimiento porque era demasiado complejo. Hoy, con la ayuda de los LLM, podemos construir grafos de conocimiento de manera más rápida y económica. Sin embargo, el grafo que acabamos de crear no esuitable para aplicaciones estables en producción.

Aunque la eficiencia para construir grafos de conocimiento ha mejorado enormemente, el grafo inicial generalmente no es suficiente para cumplir con los requisitos de aplicaciones empresariales. Aquí hay dos problemas clave y sus soluciones:

1. Mejor control del proceso de extracción del grafo

Si sigues este ejemplo, notarás que el grafo generado solo contiene nodos de tipo "Persona" y "Organización". De hecho, el contenido extraído se limita a estas personas y los puestos ejecutivos que ocupan en las empresas.

pNota: Usar LLM para extraer grafos es una técnica probabilística, por lo que los resultados pueden variar de los tuyos. Sin embargo, podemos extraer más información del archivo de texto. Por ejemplo, podemos identificar la universidad donde estudió cada ejecutivo y su experiencia laboral.

Entonces, ¿podemos especificar los tipos de entidades a extraer y sus relaciones? Afortunadamente, la clase LLMGraphTransformer soporta esta funcionalidad.

A continuación, otra forma de iniciar la instancia:

transformador_llm = LLMGraphTransformer(
    llm=modelo,
    allowed_nodes=["Persona", "Empresa", "Universidad"],
    allowed_relationships=[
        ("Persona", "CEO_DE", "Empresa"),
        ("Persona", "CFO_DE", "Empresa"),
        ("Persona", "CTO_DE", "Empresa"),
        ("Persona", "ESTUDIO_EN", "Universidad"),
    ],
    node_properties=True,
)

En la versión anterior, le indicamos explícitamente al Transformer que buscara entidades de tipo "Persona", "Empresa" y "Universidad", y especificamos las posibles relaciones entre ellas. Este es un paso clave para ayudar al LLM a extraer información.

Además, el tercer parámetro node_properties permite al Transformer obtener todas las propiedades de las entidades que no tienen relaciones.

Los grafos de conocimiento construidos especificando explícitamente nodos y relaciones suelen ser más completos y precisos. Sin embargo, para garantizar que la información extraída sea más precisa, los siguientes métodos pueden ser de ayuda.

2. Preprocesamiento antes de la transformación del grafo (Propuestas)

El texto es una forma compleja de datos en sí misma, y las personas que escriben estos textos tienen formas de pensar aún más complejas.

A veces, no expresamos todo directamente. Incluso en la escritura formal o técnica, a menudo se mencionan los mismos conceptos en diferentes lugares. Por ejemplo, en este artículo he usado varias veces los términos "Grafo de Conocimiento" y "GC".

p>Esta forma hace que sea difícil para el LLM entender correctamente el contexto. En realidad, el Graph Transformer divide el texto en varios fragmentos en segundo plano y procesa cada uno de forma independiente. Por lo tanto, la información de otras partes del texto no se puede asociar al fragmento actual después de la división.

Por ejemplo, al principio del artículo se menciona que el puesto de alguien es "Director de Inversiones (CIO)", pero al dividir el texto, esta definición podría perderse en fragmentos posteriores. En ese momento, el LLM no puede entender si la "I" en "CIO" se refiere a Inversiones, Información u otra cosa.

Para resolver este problema, utilizamos el preprocesamiento. Antes de dividir o procesar el texto, hacemos un preprocesamiento para garantizar que cada fragmento pueda entenderse correctamente.

A continuación se muestra cómo lograr este objetivo.

objeto = hub.pull("wfh/proposal-indexing")

# Puedes explorar la plantilla de prompt detrás de esto ejecutando lo siguiente:
# objeto.get_prompts()[0].messages[0].prompt.template

modelo = ChatOpenAI(model="gpt-4o")

# Un modelo Pydantic para extraer oraciones del pasaje
class Oraciones(BaseModel):
    oraciones: List[str]

modelo_extraccion = modelo.with_structured_output(Oraciones)

# Crear la cadena de extracción de oraciones
cadena_extraccion = objeto | modelo_extraccion

# Pruébalo
oraciones = cadena_extraccion.invoke(
    """
    El 20 de julio de 1969, el astronauta Neil Armstrong caminó sobre la Luna.
    Estaba liderando la misión Apolo 11 de la NASA.
    Armstrong dijo célebremente: "Es un pequeño paso para el hombre, un gran salto para la humanidad" al pisar la superficie lunar.
    """
)

>>['El 20 de julio de 1969, el astronauta Neil Armstrong caminó sobre la Luna.',
 'Neil Armstrong estaba liderando la misión Apolo 11 de la NASA.',
 'Neil Armstrong dijo célebremente: "Es un pequeño paso para el hombre, un gran salto para la humanidad" al pisar la superficie lunar.']

Este código utiliza las plantillas de prompts proporcionadas por Langchain. Como podemos ver en los resultados, cada fragmento de texto se explica por sí mismo; incluso sin hacer referencia a otros fragmentos de texto, el LLM puede entenderlo de forma independiente.

Realizar este preprocesameinto antes de crear el grafo de conocimiento puede ayudar al LLM a evitar perder nodos o relaciones debido a la pérdida de información de referencia.

Etiquetas: Neo4j LangChain GraphRAG RAG LLM

Publicado el 6-30 18:11