paint-brush
Как использовать графы знаний для генерации с расширенным поиском — без базы данных графовк@datastax
2,896 чтения
2,896 чтения

Как использовать графы знаний для генерации с расширенным поиском — без базы данных графов

к DataStax12m2024/04/23
Read on Terminal Reader

Слишком долго; Читать

В этом посте рассматривается использование графов знаний для RAG с использованием DataStax Astra DB для хранения. Код для примеров находится в этой записной книжке с использованием некоторого прототипа кода для хранения и извлечения графиков знаний с использованием Astra DB.
featured image - Как использовать графы знаний для генерации с расширенным поиском — без базы данных графов
DataStax HackerNoon profile picture

Поисковая дополненная генерация (RAG) относится к множеству методов получения информации и ее использования для предоставления контекстной информации для генеративного ИИ. Наиболее распространенная форма работает с фрагментами текста и включает в себя:


  1. Извлечение текста из исходных документов (HTML, PDF, Markdown и т. д.).


  2. Разбиение текста на определенные размеры в зависимости от структуры и семантики документа.


  3. Сохранение фрагментов в векторной базе данных с ключом, встраиваемым в фрагмент.


  4. Извлечение фрагментов, относящихся к вопросу, для использования в качестве контекста при генерации ответа.


Однако RAG, основанный на сходстве векторов, имеет несколько недостатков. Поскольку он фокусируется на информации, аналогичной вопросу, труднее отвечать на вопросы, затрагивающие несколько тем и/или требующие, например, нескольких переходов. Кроме того, это ограничивает количество извлекаемых фрагментов.


Каждый фрагмент поступает из отдельного источника, поэтому в случаях, когда во многом схожая информация существует в нескольких местах, необходимо выбирать между получением нескольких копий информации (и, возможно, пропуском другой информации) или выбором только одной копии, чтобы получить больше разные фрагменты, в результате чего упускаются нюансы других источников.


Графики знаний может использоваться в качестве альтернативы или дополнения к векторному поиску фрагментов. В графе знаний узлы соответствуют конкретным объектам, а ребра указывают на отношения между объектами. При использовании для RAG извлекаются сущности, относящиеся к вопросу, а затем извлекается подграф знаний, содержащий эти сущности и информацию о них.


Этот подход имеет несколько преимуществ по сравнению с подходом, основанным на сходстве:

  1. Многие факты могут быть извлечены из одного источника и связаны с различными объектами в графе знаний. Это позволяет извлекать только важные факты из данного источника, а не весь фрагмент, включая нерелевантную информацию.


  2. Если несколько источников говорят одно и то же, они создают один и тот же узел или ребро. Вместо того, чтобы рассматривать их как отдельные факты (и получать несколько копий), их можно рассматривать как один и тот же узел или ребро и получать их только один раз. Это позволяет получить более широкий спектр фактов и/или сосредоточиться только на фактах, которые появляются в нескольких источниках.


  3. Граф можно пройти в несколько этапов – не только для получения информации, непосредственно связанной с объектами в вопросе, но и для возврата к вещам, которые находятся на расстоянии 2 или 3 шагов. При традиционном подходе RAG для этого потребуется несколько раундов запросов.


Помимо преимуществ использования графа знаний для RAG, программы LLM также упростили создание графиков знаний. Вместо того, чтобы требовать от профильных экспертов тщательного создания графика знаний, можно использовать LLM и подсказку для извлечения информации из документов.


В этом посте рассматривается использование графов знаний для RAG с использованием DataStax Astra БД для хранения. Код примеров находится здесь блокнот используя некоторый прототип кода для хранения и извлечения графиков знаний с использованием Astra DB из этот репозиторий . Мы будем использовать LangChain «LLMGraphTransformer» для извлечения графов знаний из документов, записи их в Astra и обсуждения методов настройки подсказки, используемой для извлечения знаний.


Затем мы создадим исполняемые файлы LangChain для извлечения сущностей из вопроса и получения соответствующих подграфов. Мы увидим, что операции, необходимые для реализации RAG с использованием графов знаний, не требуют графовых баз данных или графовых языков запросов, что позволяет применять этот подход с использованием типичного хранилища данных, которое вы, возможно, уже используете.

График знаний

Как упоминалось ранее, граф знаний представляет отдельные объекты в виде узлов. Например, узел может представлять человека «Марию Кюри» или «французский» язык. В LangChain каждый узел имеет имя и тип. Мы рассмотрим оба варианта при однозначной идентификации узла, чтобы отличить «французский» язык от «французской» национальности.


Отношения между сущностями соответствуют ребрам графа. Каждое ребро включает в себя источник (например, Мария Кюри — человек), цель (Нобелевская премия — награда) и тип, указывающий, как источник связан с целью (например, «выиграл»).


Ниже показан пример графика знаний, извлеченного из параграфа о Марии Кюри с использованием LangChain:


В зависимости от ваших целей вы можете добавить свойства к узлам и ребрам. Например, вы можете использовать свойство, чтобы определить, когда была выиграна Нобелевская премия, и ее категорию. Они могут быть полезны для фильтрации ребер и узлов при обходе графа во время поиска.

Извлечение: создание диаграммы знаний

Сущности и связи, составляющие граф знаний, можно создавать напрямую или импортировать из существующих источников данных. Это полезно, когда вы хотите тщательно хранить знания, но затрудняете быстрое внедрение новой информации или обработку больших объемов информации.


К счастью, LLM позволяют легко извлекать информацию из контента, поэтому мы можем использовать их для извлечения графа знаний.


Ниже я использую LLMGraphTransformer из LangChain, чтобы извлечь график из некоторой информации о Марии Кюри. При этом используется подсказка, дающая LLM указание извлечь узлы и ребра из документа. Его можно использовать с любым документом, который может загрузить LangChain, что упрощает добавление к существующим проектам LangChain.


LangChain поддерживает другие варианты, такие как ДиффБот , а также вы можете взглянуть на некоторые доступные модели извлечения знаний, например Мятежник .


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


Здесь показано, как извлечь граф знаний с помощью LLMGraphTransformer от LangChain. Вы можете использовать render_graph_document , найденный в репозитории, для визуализации LangChain GraphDocument для визуальной проверки.


В следующем посте мы обсудим, как можно изучить граф знаний как целиком, так и подграф, извлеченный из каждого документа, и как можно применить быстрое проектирование и инженерию знаний для улучшения автоматического извлечения.

Поиск: ответ с помощью графа подзнаний

Ответы на вопросы с использованием графика знаний требуют нескольких шагов. Сначала мы определяем, с чего начать обход графа знаний. В этом примере я предлагаю LLM извлечь сущности из вопроса. Затем граф знаний просматривается для извлечения всех связей на заданном расстоянии от этих начальных точек. Глубина обхода по умолчанию равна 3. Полученные связи и исходный вопрос используются для создания подсказки и контекста для LLM для ответа на вопрос.

Извлечение сущностей из вопроса

Как и при извлечении графа знаний, извлечение сущностей в вопросе может быть выполнено с использованием специальной модели или LLM с конкретной подсказкой. Для простоты мы будем использовать LLM со следующей подсказкой, которая включает в себя как вопрос, так и информацию о формате для извлечения. Мы используем модель Pydantic с именем и типом, чтобы получить правильную структуру.

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


Запустив приведенный выше пример, мы можем увидеть извлеченные объекты:

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


Конечно, LangChain Runnable можно использовать в цепочке для извлечения сущностей из вопроса.


В будущем мы обсудим способы улучшения извлечения сущностей, например, рассмотрение свойств узлов или использование векторных вложений и поиска по сходству для определения соответствующих отправных точек. Чтобы сделать этот первый пост простым, мы будем придерживаться приведенной выше подсказки и перейдем к обходу графа знаний, чтобы получить knowledge-subgraph и включить его в качестве контекста в подсказку.

Получение графа подзнаний

Предыдущая цепочка дает нам рассматриваемые узлы. Мы можем использовать эти сущности и хранилище графов для получения соответствующих троек знаний. Как и в случае с RAG, мы добавляем их в подсказку как часть контекста и генерируем ответы.

 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 )


Приведенную выше цепочку можно выполнить для ответа на вопрос. Например:

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

Пройдите, не спрашивайте

Использование графовой базы данных для хранения графа знаний может показаться интуитивно понятным, но на самом деле в этом нет необходимости. Получение графа подзнаний вокруг нескольких узлов представляет собой простой обход графа, в то время как базы данных графов предназначены для гораздо более сложных запросов, ищущих пути с определенными последовательностями свойств. Кроме того, обход часто осуществляется только на глубину 2 или 3, поскольку узлы, удаленные дальше, довольно быстро становятся нерелевантными для вопроса. Это может быть выражено в виде нескольких раундов простых запросов (по одному на каждый шаг) или соединения SQL.


Устранение необходимости в отдельной базе данных графов упрощает использование графов знаний. Кроме того, использование Astra DB или Apache Cassandra упрощает запись транзакций как в граф, так и в другие данные, хранящиеся в одном месте, и, вероятно, лучше масштабируется. Эти накладные расходы будут иметь смысл только в том случае, если вы планируете генерировать и выполнять запросы к графам, используя Gremlin, Cypher или что-то подобное.


Но это просто излишество для получения графа подзнаний, и это открывает дверь для множества других проблем, таких как запросы, которые выходят за рамки с точки зрения производительности.


Этот обход легко реализовать в Python. Полный код для реализации этого (как синхронно, так и асинхронно) с использованием CQL и драйвера Cassandra можно найти в документе репозиторий . Ядро асинхронного обхода показано ниже для иллюстрации:


 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


Заключение

В этой статье показано, как построить и использовать извлечение и извлечение графа знаний для ответов на вопросы. Ключевой вывод заключается в том, что сегодня вам не нужна графовая база данных с графовым языком запросов, таким как Gremlin или Cypher. Отличная база данных, такая как Astra, которая эффективно обрабатывает множество запросов параллельно, уже может справиться с этим.


Фактически, вы можете просто написать простую последовательность запросов для получения графа подзнаний, необходимого для ответа на конкретный запрос. Это сохраняет вашу архитектуру простой (без дополнительных зависимостей) и позволяет вам начать немедленно !


Мы использовали те же идеи для реализации шаблонов GraphRAG для Cassandra и Astra DB. Мы собираемся внести их в LangChain и работать над другими улучшениями использования графов знаний с LLM в будущем!


Бен Чемберс, DataStax