Extracción del texto de los documentos originales (HTML, PDF, Markdown, etc.).
Dividir el texto en tamaños específicos según la estructura y la semántica del documento.
Almacenar fragmentos en una base de datos vectorial codificada mediante una incrustación del fragmento.
Recuperar los fragmentos relevantes de una pregunta para utilizarlos como contexto al generar la respuesta.
Sin embargo, el RAG basado en la similitud de vectores tiene algunas debilidades. Dado que se centra en información similar a la pregunta, es más difícil responder preguntas que involucran múltiples temas y/o requieren múltiples saltos, por ejemplo. Además, limita la cantidad de fragmentos recuperados.
Cada fragmento proviene de una fuente distinta, por lo que en los casos en los que existe información muy similar en varios lugares, es necesario elegir entre recuperar varias copias de la información (y posiblemente perderse otra información) o elegir solo una copia para obtener más. diferentes fragmentos, lo que luego pierde los matices de las otras fuentes.
Este enfoque tiene varios beneficios sobre el enfoque basado en similitudes:
Se pueden extraer muchos datos de una única fuente y asociarlos con una variedad de entidades dentro del gráfico de conocimiento. Esto permite recuperar sólo los hechos relevantes de una fuente determinada en lugar de recuperar toda la información, incluida la información irrelevante.
Si varias fuentes dicen lo mismo, producen el mismo nodo o borde. En lugar de tratarlos como hechos distintos (y recuperar múltiples copias), pueden tratarse como el mismo nodo o borde y recuperarse solo una vez. Esto permite recuperar una variedad más amplia de hechos y/o centrarse solo en hechos que aparecen en múltiples fuentes.
El gráfico se puede recorrer a través de múltiples pasos, no solo recuperando información directamente relacionada con las entidades en la pregunta, sino también retirando cosas que están a 2 o 3 pasos de distancia. En un enfoque RAG convencional, esto requeriría múltiples rondas de consultas.
Además de los beneficios de utilizar un gráfico de conocimiento para RAG, los LLM también han facilitado la creación de gráficos de conocimiento. En lugar de requerir que expertos en la materia elaboren cuidadosamente el gráfico de conocimiento, se puede utilizar un LLM y un mensaje para extraer información de los documentos.
Esta publicación explora el uso de gráficos de conocimiento para RAG, utilizando
Luego crearemos ejecutables de LangChain para extraer entidades de la pregunta y recuperar los subgráficos relevantes. Veremos que las operaciones necesarias para implementar RAG utilizando gráficos de conocimiento no requieren bases de datos de gráficos ni lenguajes de consulta de gráficos, lo que permite aplicar el enfoque utilizando un almacén de datos típico que quizás ya esté utilizando.
Como se mencionó anteriormente, un gráfico de conocimiento representa entidades distintas como nodos. Por ejemplo, un nodo puede representar “Marie Curie” la persona o “francés” el idioma. En LangChain, cada nodo tiene un nombre y un tipo. Consideraremos ambos al identificar de forma única un nodo, para distinguir el idioma "francés" de la nacionalidad "francesa".
Las relaciones entre entidades corresponden a los bordes del gráfico. Cada borde incluye la fuente (por ejemplo, Marie Curie la persona), el objetivo (el Premio Nobel, el premio) y un tipo, que indica cómo se relaciona la fuente con el objetivo (por ejemplo, "ganó").
A continuación se muestra un gráfico de conocimiento de ejemplo extraído de un párrafo sobre Marie Curie usando LangChain:
Dependiendo de sus objetivos, puede optar por agregar propiedades a nodos y bordes. Por ejemplo, podría utilizar una propiedad para identificar cuándo se ganó el Premio Nobel y la categoría. Estos pueden resultar útiles para filtrar bordes y nodos al atravesar el gráfico durante la recuperación.
Las entidades y relaciones que componen el gráfico de conocimiento se pueden crear directamente o importar desde fuentes de datos existentes y conocidas. Esto es útil cuando desea seleccionar el conocimiento con cuidado, pero dificulta incorporar nueva información rápidamente o manejar grandes cantidades de información.
Afortunadamente, los LLM facilitan la extracción de información del contenido, por lo que podemos usarlos para extraer el gráfico de conocimiento.
A continuación utilizo el
LangChain admite otras opciones como
from langchain_experimental.graph_transformers import LLMGraphTransformer from langchain_openai import ChatOpenAI from langchain_core.documents import Document # Prompt used by LLMGraphTransformer is tuned for Gpt4. llm = ChatOpenAI(temperature=0, model_name="gpt-4") llm_transformer = LLMGraphTransformer(llm=llm) text = """ Marie Curie, was a Polish and naturalised-French physicist and chemist who conducted pioneering research on radioactivity. She was the first woman to win a Nobel Prize, the first person to win a Nobel Prize twice, and the only person to win a Nobel Prize in two scientific fields. Her husband, Pierre Curie, was a co-winner of her first Nobel Prize, making them the first-ever married couple to win the Nobel Prize and launching the Curie family legacy of five Nobel Prizes. She was, in 1906, the first woman to become a professor at the University of Paris. """ documents = [Document(page_content=text)] graph_documents = llm_transformer.convert_to_graph_documents(documents) print(f"Nodes:{graph_documents[0].nodes}") print(f"Relationships:{graph_documents[0].relationships}")
Esto muestra cómo extraer un gráfico de conocimiento utilizando LLMGraphTransformer
de LangChain. Puede utilizar render_graph_document
que se encuentra en el repositorio para representar un LangChain GraphDocument
para inspección visual.
En una publicación futura, analizaremos cómo puede examinar el gráfico de conocimiento tanto en su totalidad como también el subgráfico extraído de cada documento y cómo puede aplicar ingeniería rápida e ingeniería del conocimiento para mejorar la extracción automatizada.
Responder preguntas utilizando el gráfico de conocimiento requiere varios pasos. Primero identificamos dónde comenzar nuestro recorrido del gráfico de conocimiento. Para este ejemplo, le pediré a un LLM que extraiga entidades de la pregunta. Luego, se recorre el gráfico de conocimiento para recuperar todas las relaciones dentro de una distancia determinada de esos puntos de partida. La profundidad transversal predeterminada es 3. Las relaciones recuperadas y la pregunta original se utilizan para crear un mensaje y un contexto para que el LLM responda la pregunta.
Al igual que con la extracción del gráfico de conocimiento, la extracción de las entidades de una pregunta se puede realizar utilizando un modelo especial o un LLM con un mensaje específico. Para simplificar, usaremos un LLM con el siguiente mensaje que incluye tanto la pregunta como información sobre el formato a extraer. Usamos un modelo Pydantic con el nombre y tipo para obtener la estructura adecuada.
QUERY_ENTITY_EXTRACT_PROMPT = ( "A question is provided below. Given the question, extract up to 5 " "entity names and types from the text. Focus on extracting the key entities " "that we can use to best lookup answers to the question. Avoid stopwords.\n" "---------------------\n" "{question}\n" "---------------------\n" "{format_instructions}\n" ) def extract_entities(llm): prompt = ChatPromptTemplate.from_messages([keyword_extraction_prompt]) class SimpleNode(BaseModel): """Represents a node in a graph with associated properties.""" id: str = Field(description="Name or human-readable unique identifier.") type: str = optional_enum_field(node_types, description="The type or label of the node.") class SimpleNodeList(BaseModel): """Represents a list of simple nodes.""" nodes: List[SimpleNode] output_parser = JsonOutputParser(pydantic_object=SimpleNodeList) return ( RunnablePassthrough.assign( format_instructions=lambda _: output_parser.get_format_instructions(), ) | ChatPromptTemplate.from_messages([QUERY_ENTITY_EXTRACT_PROMPT]) | llm | output_parser | RunnableLambda( lambda node_list: [(n["id"], n["type"]) for n in node_list["nodes"]]) )
Al ejecutar el ejemplo anterior podemos ver las entidades extraídas:
# Example showing extracted entities (nodes) extract_entities(llm).invoke({ "question": "Who is Marie Curie?"}) # Output: [Marie Curie(Person)]
Por supuesto, se puede utilizar un LangChain Runnable en una cadena para extraer las entidades de una pregunta.
En el futuro, discutiremos formas de mejorar la extracción de entidades, como considerar las propiedades de los nodos o usar incrustaciones de vectores y búsqueda de similitudes para identificar puntos de partida relevantes. Para que esta primera publicación sea simple, seguiremos con el mensaje anterior y pasaremos a recorrer el gráfico de conocimiento para recuperar el knowledge-subgraph
e incluirlo como contexto en el mensaje.
La cadena anterior nos da los nodos en cuestión. Podemos usar esas entidades y el almacén de gráficos para recuperar los triples de conocimiento relevantes. Al igual que con RAG, los colocamos en el mensaje como parte del contexto y generamos respuestas.
def _combine_relations(relations): return "\n".join(map(repr, relations)) ANSWER_PROMPT = ( "The original question is given below." "This question has been used to retrieve information from a knowledge graph." "The matching triples are shown below." "Use the information in the triples to answer the original question.\n\n" "Original Question: {question}\n\n" "Knowledge Graph Triples:\n{context}\n\n" "Response:" ) chain = ( { "question": RunnablePassthrough() } # extract_entities is provided by the Cassandra knowledge graph library # and extracts entitise as shown above. | RunnablePassthrough.assign(entities = extract_entities(llm)) | RunnablePassthrough.assign( # graph_store.as_runnable() is provided by the CassandraGraphStore # and takes one or more entities and retrieves the relevant sub-graph(s). triples = itemgetter("entities") | graph_store.as_runnable()) | RunnablePassthrough.assign( context = itemgetter("triples") | RunnableLambda(_combine_relations)) | ChatPromptTemplate.from_messages([ANSWER_PROMPT]) | llm )
La cadena anterior se puede ejecutar para responder una pregunta. Por ejemplo:
chain.invoke("Who is Marie Curie?") # Output AIMessage( content="Marie Curie is a Polish and French chemist, physicist, and professor who " "researched radioactivity. She was married to Pierre Curie and has worked at " "the University of Paris. She is also a recipient of the Nobel Prize.", response_metadata={ 'token_usage': {'completion_tokens': 47, 'prompt_tokens': 213, 'total_tokens': 260}, 'model_name': 'gpt-4', ... } )
Si bien puede parecer intuitivo utilizar una base de datos gráfica para almacenar el gráfico de conocimiento, en realidad no es necesario. Recuperar el gráfico de subconocimiento alrededor de algunos nodos es un simple recorrido del gráfico, mientras que las bases de datos de gráficos están diseñadas para consultas mucho más complejas que buscan rutas con secuencias específicas de propiedades. Además, el recorrido suele ser sólo hasta una profundidad de 2 o 3, ya que los nodos que están más alejados se vuelven irrelevantes para la pregunta con bastante rapidez. Esto se puede expresar como unas cuantas rondas de consultas simples (una para cada paso) o una unión SQL.
La eliminación de la necesidad de una base de datos de gráficos independiente facilita el uso de gráficos de conocimiento. Además, el uso de Astra DB o Apache Cassandra simplifica las escrituras transaccionales tanto en el gráfico como en otros datos almacenados en el mismo lugar, y probablemente se escala mejor. Esa sobrecarga solo valdría la pena si planeara generar y ejecutar consultas de gráficos, utilizando Gremlin o Cypher o algo similar.
Pero esto es simplemente excesivo para recuperar el gráfico de subconocimiento y abre la puerta a una serie de otros problemas, como consultas que se descarrilan en términos de rendimiento.
Este recorrido es fácil de implementar en Python. El código completo para implementar esto (tanto de forma síncrona como asíncrona) usando CQL y el controlador Cassandra se puede encontrar en el
def fetch_relation(tg: asyncio.TaskGroup, depth: int, source: Node) -> AsyncPagedQuery: paged_query = AsyncPagedQuery( depth, session.execute_async(query, (source.name, source.type)) ) return tg.create_task(paged_query.next()) results = set() async with asyncio.TaskGroup() as tg: if isinstance(start, Node): start = [start] discovered = {t: 0 for t in start} pending = {fetch_relation(tg, 1, source) for source in start} while pending: done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED) for future in done: depth, relations, more = future.result() for relation in relations: results.add(relation) # Schedule the future for more results from the same query. if more is not None: pending.add(tg.create_task(more.next())) # Schedule futures for the next step. if depth < steps: # We've found a path of length `depth` to each of the targets. # We need to update `discovered` to include the shortest path. # And build `to_visit` to be all of the targets for which this is # the new shortest path. to_visit = set() for r in relations: previous = discovered.get(r.target, steps + 1) if depth < previous: discovered[r.target] = depth to_visit.add(r.target) for source in to_visit: pending.add(fetch_relation(tg, depth + 1, source)) return results
Este artículo mostró cómo crear y utilizar la extracción y recuperación de gráficos de conocimiento para responder preguntas. La conclusión clave es que no necesita una base de datos de gráficos con un lenguaje de consulta de gráficos como Gremlin o Cypher para hacer esto hoy. Una gran base de datos como Astra que maneja eficientemente muchas consultas en paralelo ya puede manejar esto.
De hecho, podría simplemente escribir una secuencia simple de consultas para recuperar el gráfico de subconocimiento necesario para responder una consulta específica. Esto mantiene su arquitectura simple (sin dependencias agregadas) y le permite
Hemos utilizado estas mismas ideas para implementar patrones GraphRAG para Cassandra y Astra DB. ¡Vamos a contribuir con ellos a LangChain y trabajaremos para aportar otras mejoras al uso de gráficos de conocimiento con LLM en el futuro!
Por Ben Chambers, DataStax