paint-brush
Como usar gráficos de conhecimento para geração aumentada de recuperação - sem um banco de dados gráficopor@datastax
2,896 leituras
2,896 leituras

Como usar gráficos de conhecimento para geração aumentada de recuperação - sem um banco de dados gráfico

por DataStax12m2024/04/23
Read on Terminal Reader

Muito longo; Para ler

Esta postagem explora o uso de gráficos de conhecimento para RAG, usando DataStax Astra DB para armazenamento. O código para os exemplos está neste notebook usando algum código de protótipo para armazenar e recuperar gráficos de conhecimento usando Astra DB
featured image - Como usar gráficos de conhecimento para geração aumentada de recuperação - sem um banco de dados gráfico
DataStax HackerNoon profile picture

Geração aumentada de recuperação (RAG) refere-se a uma variedade de técnicas para recuperar informações e usá-las para fornecer informações contextuais para IA generativa. A forma mais comum opera em blocos de texto e envolve:


  1. Extrair o texto dos documentos originais (HTML, PDF, Markdown, etc.).


  2. Segmentar o texto em tamanhos específicos com base na estrutura e na semântica do documento.


  3. Armazenar pedaços em um banco de dados vetorial codificado por uma incorporação do pedaço.


  4. Recuperar os pedaços relevantes para uma pergunta para usar como contexto ao gerar a resposta.


No entanto, o RAG baseado na similaridade vetorial apresenta alguns pontos fracos. Como se concentra em informações semelhantes à pergunta, é mais difícil responder a perguntas que envolvem vários tópicos e/ou que exigem vários saltos – por exemplo. Além disso, limita o número de pedaços recuperados.


Cada pedaço vem de uma fonte distinta, portanto, nos casos em que existem informações muito semelhantes em vários lugares, é necessário escolher entre recuperar múltiplas cópias da informação (e possivelmente perder outras informações) ou escolher apenas uma cópia para obter mais. pedaços diferentes, que então perdem as nuances das outras fontes.


Gráficos de conhecimento pode ser usado como uma alternativa ou complemento à recuperação de blocos baseada em vetores. Em um gráfico de conhecimento, os nós correspondem a entidades específicas e as arestas indicam relacionamentos entre as entidades. Quando usado para RAG, as entidades relevantes para a questão são extraídas e, em seguida, o subgráfico de conhecimento que contém essas entidades e as informações sobre elas são recuperados.


Esta abordagem tem vários benefícios em relação à abordagem baseada em similaridade:

  1. Muitos fatos podem ser extraídos de uma única fonte e associados a uma variedade de entidades no gráfico de conhecimento. Isto permite a recuperação apenas dos factos relevantes de uma determinada fonte, em vez de todo o bloco, incluindo informações irrelevantes.


  2. Se múltiplas fontes disserem a mesma coisa, elas produzirão o mesmo nó ou aresta. Em vez de tratá-los como fatos distintos (e recuperar múltiplas cópias), eles podem ser tratados como o mesmo nó ou aresta e recuperados apenas uma vez. Isso permite recuperar uma variedade maior de fatos e/ou focar apenas em fatos que aparecem em múltiplas fontes.


  3. O gráfico pode ser percorrido através de vários passos – não apenas recuperando informações diretamente relacionadas às entidades em questão, mas também recuperando coisas que estão a 2 ou 3 passos de distância. Numa abordagem RAG convencional, isto exigiria múltiplas rondas de consulta.


Além dos benefícios de usar um gráfico de conhecimento para RAG, os LLMs também facilitaram a criação de gráficos de conhecimento. Em vez de exigir que especialistas no assunto elaborem cuidadosamente o gráfico de conhecimento, um LLM e um prompt podem ser usados para extrair informações de documentos.


Esta postagem explora o uso de gráficos de conhecimento para RAG, usando Banco de dados DataStax Astra para armazenamento. O código para os exemplos está neste caderno usando algum código de protótipo para armazenar e recuperar gráficos de conhecimento usando Astra DB de este repositório . Faremos uso do “LLMGraphTransformer” da LangChain para extrair gráficos de conhecimento de documentos, gravá-los no Astra e discutir técnicas para ajustar o prompt usado para extração de conhecimento.


Em seguida, criaremos executáveis LangChain para extrair entidades da questão e recuperar os subgráficos relevantes. Veremos que as operações necessárias para implementar RAG usando gráficos de conhecimento não requerem bancos de dados de gráficos ou linguagens de consulta de gráficos, permitindo que a abordagem seja aplicada usando um armazenamento de dados típico que você já esteja usando.

Gráfico de conhecimento

Conforme mencionado anteriormente, um gráfico de conhecimento representa entidades distintas como nós. Por exemplo, um nó pode representar “Marie Curie” a pessoa ou “Francês” o idioma. No LangChain, cada nó possui um nome e um tipo. Consideraremos ambos ao identificar exclusivamente um nó, para distinguir “francês” a língua de “francês” a nacionalidade.


Os relacionamentos entre entidades correspondem às arestas do gráfico. Cada aresta inclui a fonte (por exemplo, Marie Curie, a pessoa), o alvo (Prêmio Nobel, o prêmio) e um tipo, indicando como a fonte se relaciona com o alvo (por exemplo, “ganhou”).


Um exemplo de gráfico de conhecimento extraído de um parágrafo sobre Marie Curie usando LangChain é mostrado abaixo:


Dependendo de seus objetivos, você pode optar por adicionar propriedades a nós e arestas. Por exemplo, você poderia usar uma propriedade para identificar quando o Prêmio Nobel foi ganho e a categoria. Eles podem ser úteis para filtrar arestas e nós ao percorrer o gráfico durante a recuperação.

Extração: Criando o Gráfico de Conhecimento

As entidades e relacionamentos que compõem o gráfico de conhecimento podem ser criados diretamente ou importados de fontes de dados existentes e conhecidas. Isto é útil quando você deseja selecionar o conhecimento cuidadosamente, mas torna difícil incorporar novas informações rapidamente ou lidar com grandes quantidades de informações.


Felizmente, os LLMs facilitam a extração de informações do conteúdo, para que possamos usá-los para extrair o gráfico de conhecimento.


Abaixo, eu uso o LLMGraphTransformador do LangChain para extrair um gráfico de algumas informações sobre Marie Curie. Isso usa um prompt para instruir um LLM a extrair nós e arestas de um documento. Ele pode ser usado com qualquer documento que o LangChain possa carregar, facilitando a adição a projetos LangChain existentes.


LangChain oferece suporte a outras opções, como DiffBot , e você também pode consultar alguns dos modelos de extração de conhecimento disponíveis, como Rebelde .


 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}")


Isso mostra como extrair um gráfico de conhecimento usando LLMGraphTransformer da LangChain. Você pode usar o render_graph_document encontrado no repositório para renderizar um LangChain GraphDocument para inspeção visual.


Em uma postagem futura, discutiremos como você pode examinar o gráfico de conhecimento em sua totalidade, bem como o subgráfico extraído de cada documento e como você pode aplicar engenharia imediata e engenharia de conhecimento para melhorar a extração automatizada.

Recuperação: Respondendo com o Gráfico de Subconhecimento

Responder a perguntas usando o gráfico de conhecimento requer várias etapas. Primeiro identificamos onde começar nossa travessia do gráfico de conhecimento. Para este exemplo, solicitarei que um LLM extraia entidades da pergunta. Em seguida, o gráfico de conhecimento é percorrido para recuperar todos os relacionamentos dentro de uma determinada distância desses pontos iniciais. A profundidade de passagem padrão é 3. Os relacionamentos recuperados e a pergunta original são usados para criar um prompt e um contexto para o LLM responder à pergunta.

Extraindo Entidades da Pergunta

Assim como na extração do gráfico de conhecimento, a extração das entidades de uma questão pode ser feita por meio de um modelo especial ou de um LLM com prompt específico. Para simplificar, usaremos um LLM com o seguinte prompt que inclui a pergunta e as informações sobre o formato a ser extraído. Usamos um modelo Pydantic com o nome e tipo para obter a estrutura adequada.

 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"]]) )


Executando o exemplo acima podemos ver as entidades extraídas:

 # Example showing extracted entities (nodes) extract_entities(llm).invoke({ "question": "Who is Marie Curie?"}) # Output: [Marie Curie(Person)]


Claro, um LangChain Runnable pode ser usado em uma cadeia para extrair as entidades de uma pergunta.


No futuro, discutiremos maneiras de melhorar a extração de entidades, como considerar propriedades de nós ou usar incorporações de vetores e pesquisa por similaridade para identificar pontos de partida relevantes. Para manter esta primeira postagem simples, seguiremos o prompt acima e passaremos a percorrer o gráfico de conhecimento para recuperar o knowledge-subgraph e incluí-lo como o contexto no prompt.

Recuperando o gráfico de subconhecimento

A cadeia anterior nos dá os nós em questão. Podemos usar essas entidades e o armazenamento gráfico para recuperar os triplos de conhecimento relevantes. Assim como no RAG, nós os colocamos no prompt como parte do contexto e geramos respostas.

 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 )


A cadeia acima pode ser executada para responder a uma pergunta. Por exemplo:

 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', ... } )

Atravesse, não consulte

Embora possa parecer intuitivo usar um banco de dados gráfico para armazenar o gráfico de conhecimento, na verdade não é necessário. Recuperar o gráfico de subconhecimento em torno de alguns nós é uma simples travessia do gráfico, enquanto os bancos de dados gráficos são projetados para consultas muito mais complexas, procurando caminhos com sequências específicas de propriedades. Além disso, a travessia geralmente ocorre apenas até uma profundidade de 2 ou 3, uma vez que os nós que estão mais distantes tornam-se irrelevantes para a questão muito rapidamente. Isso pode ser expresso como algumas rodadas de consultas simples (uma para cada etapa) ou uma junção SQL.


Eliminar a necessidade de um banco de dados gráfico separado facilita o uso de gráficos de conhecimento. Além disso, o uso do Astra DB ou do Apache Cassandra simplifica as gravações transacionais no gráfico e em outros dados armazenados no mesmo local e provavelmente tem melhor escalabilidade. Essa sobrecarga só valeria a pena se você estivesse planejando gerar e executar consultas gráficas, usando Gremlin ou Cypher ou algo semelhante.


Mas isso é simplesmente um exagero para recuperar o gráfico de subconhecimento e abre a porta para uma série de outros problemas, como consultas que saem dos trilhos em termos de desempenho.


Essa travessia é fácil de implementar em Python. O código completo para implementar isso (tanto de forma síncrona quanto assíncrona) usando CQL e o driver Cassandra pode ser encontrado no repositório . O núcleo da travessia assíncrona é mostrado abaixo para ilustração:


 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


Conclusão

Este artigo mostrou como construir e usar a extração e recuperação de gráficos de conhecimento para responder perguntas. A principal conclusão é que você não precisa de um banco de dados gráfico com uma linguagem de consulta gráfica como Gremlin ou Cypher para fazer isso hoje. Um ótimo banco de dados como o Astra, que lida com eficiência com muitas consultas em paralelo, já pode lidar com isso.


Na verdade, você poderia simplesmente escrever uma sequência simples de consultas para recuperar o gráfico de subconhecimento necessário para responder a uma consulta específica. Isso mantém sua arquitetura simples (sem dependências adicionais) e permite que você comece imediatamente !


Usamos essas mesmas ideias para implementar padrões GraphRAG para Cassandra e Astra DB. Vamos contribuir com eles para o LangChain e trabalhar para trazer outras melhorias no uso de gráficos de conhecimento com LLMs no futuro!


Por Ben Chambers, DataStax