paint-brush
Odomknutie presnosti v aplikáciách RAG: Využitie znalostných grafov s Neo4j a LangChainpodľa@neo4j
201 čítania

Odomknutie presnosti v aplikáciách RAG: Využitie znalostných grafov s Neo4j a LangChain

podľa Neo4j9m2024/10/21
Read on Terminal Reader

Príliš dlho; Čítať

Tento blogový príspevok ukazuje, ako vytvoriť znalostný graf pomocou LangChain. Kód je dostupný na GitHub. Musíte nastaviť inštanciu Neo4j. Na túto ukážku použijeme stránku [Elizabeth I's] Wikipedia. Na načítanie a rozdelenie dokumentov z Wikipédie môžeme použiť [nakladače LangChain].
featured image - Odomknutie presnosti v aplikáciách RAG: Využitie znalostných grafov s Neo4j a LangChain
Neo4j HackerNoon profile picture
0-item
1-item


Generácia rozšírená o vyhľadávanie grafov ( GraphRAG ) naberá na sile a stáva sa silným doplnkom k tradičným metódam vyhľadávania vektorov. Tento prístup využíva štruktúrovaný charakter databáz grafov, ktoré organizujú údaje ako uzly a vzťahy, aby sa zvýšila hĺbka a kontext získavaných informácií.



Príklad znalostného grafu.

Grafy sú skvelé pri reprezentovaní a ukladaní heterogénnych a vzájomne prepojených informácií štruktúrovaným spôsobom, pričom bez námahy zachytávajú zložité vzťahy a atribúty naprieč rôznymi typmi údajov. Naproti tomu vektorové databázy často zápasia s takto štruktúrovanými informáciami, pretože ich sila spočíva v manipulácii s neštruktúrovanými dátami prostredníctvom vysokorozmerných vektorov. Vo svojej aplikácii RAG môžete kombinovať údaje zo štruktúrovaných grafov s vektorovým vyhľadávaním prostredníctvom neštruktúrovaného textu, aby ste dosiahli to najlepšie z oboch svetov. To si ukážeme v tomto blogovom príspevku.

Znalostné grafy sú skvelé, ale ako ich vytvoríte?

Vytvorenie znalostného grafu je zvyčajne tým najnáročnejším krokom. Zahŕňa zhromažďovanie a štruktúrovanie údajov, čo si vyžaduje hlboké pochopenie doménového aj grafového modelovania.


Na zjednodušenie tohto procesu sme experimentovali s LLM. Vďaka svojmu hlbokému pochopeniu jazyka a kontextu môžu LLM automatizovať významné časti procesu vytvárania znalostných grafov. Analýzou textových údajov môžu tieto modely identifikovať entity, porozumieť ich vzťahom a navrhnúť, ako by mohli byť najlepšie znázornené v grafovej štruktúre.


Výsledkom týchto experimentov je, že sme do LangChain pridali prvú verziu modulu konštrukcie grafov, čo si ukážeme v tomto blogovom príspevku.


Kód je dostupný na GitHub .

Nastavenie prostredia Neo4j

Musíte nastaviť inštanciu Neo4j. Postupujte podľa príkladov v tomto blogovom príspevku. Najjednoduchšie je spustiť bezplatnú inštanciu na Neo4j Aura , ktorá ponúka cloudové inštancie databázy Neo4j. Prípadne môžete tiež nastaviť lokálnu inštanciu databázy Neo4j stiahnutím aplikácie Neo4j Desktop a vytvorením lokálnej inštancie databázy.


 os.environ["OPENAI_API_KEY"] = "sk-" os.environ["NEO4J_URI"] = "bolt://localhost:7687" os.environ["NEO4J_USERNAME"] = "neo4j" os.environ["NEO4J_PASSWORD"] = "password" graph = Neo4jGraph()


Okrem toho musíte poskytnúť kľúč OpenAI , pretože ich modely použijeme v tomto blogovom príspevku.

Príjem údajov

Na túto ukážku použijeme stránku Alžbety I. na Wikipédii. Na bezproblémové načítanie a rozdelenie dokumentov z Wikipédie môžeme použiť nakladače LangChain .


 # Read the wikipedia article raw_documents = WikipediaLoader(query="Elizabeth I").load() # Define chunking strategy text_splitter = TokenTextSplitter(chunk_size=512, chunk_overlap=24) documents = text_splitter.split_documents(raw_documents[:3])


Je čas vytvoriť graf na základe získaných dokumentov. Na tento účel sme implementovali modul LLMGraphTransformer, ktorý výrazne zjednodušuje vytváranie a ukladanie znalostného grafu do grafovej databázy.


 llm=ChatOpenAI(temperature=0, model_name="gpt-4-0125-preview") llm_transformer = LLMGraphTransformer(llm=llm) # Extract graph data graph_documents = llm_transformer.convert_to_graph_documents(documents) # Store to neo4j graph.add_graph_documents( graph_documents, baseEntityLabel=True, include_source=True )


Môžete definovať, ktoré LLM chcete, aby reťazec generovania znalostných grafov používal. V súčasnosti podporujeme iba modely s volaním funkcií od OpenAI a Mistral. V budúcnosti však plánujeme rozšíriť výber LLM. V tomto príklade používame najnovšiu verziu GPT-4. Upozorňujeme, že kvalita vygenerovaného grafu výrazne závisí od modelu, ktorý používate. Teoreticky vždy chcete použiť ten najschopnejší. Grafové transformátory LLM vracajú grafické dokumenty, ktoré je možné importovať do Neo4j pomocou metódy add_graph_documents. Parameter baseEntityLabel priraďuje ďalšie Entita označenie každého uzla, čím sa zlepšuje indexovanie a výkon dotazov. Parameter include_source spája uzly s ich pôvodnými dokumentmi, čím uľahčuje sledovanie údajov a pochopenie kontextu.


Vygenerovaný graf si môžete prezrieť v prehliadači Neo4j.


Časť vygenerovaného grafu.


Upozorňujeme, že tento obrázok predstavuje iba časť vygenerovaného grafu.


Hybridné vyhľadávanie pre RAG

Po vygenerovaní grafu použijeme hybridný prístup vyhľadávania, ktorý kombinuje vektory a indexy kľúčových slov s vyhľadávaním grafov pre aplikácie RAG.


Kombinácia hybridných metód (vektor + kľúčové slovo) a metód vyhľadávania grafov. Obrázok od autora.


Diagram ilustruje proces vyhľadávania začínajúci používateľom položením otázky, ktorá je potom nasmerovaná na RAG retriever. Tento retriever využíva vyhľadávanie kľúčových slov a vektorov na vyhľadávanie v neštruktúrovaných textových údajoch a kombinuje ich s informáciami, ktoré zbiera z grafu znalostí. Keďže Neo4j obsahuje kľúčové aj vektorové indexy, všetky tri možnosti vyhľadávania môžete implementovať pomocou jediného databázového systému. Zozbierané údaje z týchto zdrojov sa vkladajú do LLM, aby sa vytvorila a poskytla konečná odpoveď.

Neštruktúrované Data Retriever

Pomocou metódy Neo4jVector.from_existing_graph môžete do dokumentov pridať vyhľadávanie kľúčových slov aj vektorov. Táto metóda konfiguruje kľúčové slová a indexy vyhľadávania vektorov pre prístup hybridného vyhľadávania so zameraním na uzly označené ako Document. Okrem toho vypočíta hodnoty vloženia textu, ak chýbajú.


 vector_index = Neo4jVector.from_existing_graph( OpenAIEmbeddings(), search_type="hybrid", node_label="Document", text_node_properties=["text"], embedding_node_property="embedding" )


Vektorový index je potom možné volať pomocou metódy podobnosti_vyhľadávanie.

Graph Retriever

Na druhej strane, konfigurácia načítania grafu je zložitejšia, ale ponúka väčšiu slobodu. Tento príklad použije fulltextový index na identifikáciu relevantných uzlov a vrátenie ich priameho susedstva.


Retriever grafov. Obrázok od autora.



Načítanie grafu začína identifikáciou relevantných entít vo vstupe. Pre jednoduchosť nariaďujeme LLM identifikovať ľudí, organizácie a miesta. Aby sme to dosiahli, použijeme LCEL s novo pridanou metódou with_structured_output.


 # Extract entities from text class Entities(BaseModel): """Identifying information about entities.""" names: List[str] = Field( ..., description="All the person, organization, or business entities that " "appear in the text", ) prompt = ChatPromptTemplate.from_messages( [ ( "system", "You are extracting organization and person entities from the text.", ), ( "human", "Use the given format to extract information from the following " "input: {question}", ), ] ) entity_chain = prompt | llm.with_structured_output(Entities)


Poďme to otestovať:


 entity_chain.invoke({"question": "Where was Amelia Earhart born?"}).names # ['Amelia Earhart']


Skvelé, teraz, keď môžeme detekovať entity v otázke, poďme použiť fulltextový index na ich mapovanie do znalostného grafu. Najprv musíme definovať fulltextový index a funkciu, ktorá bude generovať fulltextové dotazy umožňujúce trochu preklepov, ktorým sa tu nebudeme veľmi rozpisovať.


 graph.query( "CREATE FULLTEXT INDEX entity IF NOT EXISTS FOR (e:__Entity__) ON EACH [e.id]") def generate_full_text_query(input: str) -> str: """ Generate a full-text search query for a given input string. This function constructs a query string suitable for a full-text search. It processes the input string by splitting it into words and appending a similarity threshold (~2 changed characters) to each word, then combines them using the AND operator. Useful for mapping entities from user questions to database values, and allows for some misspelings. """ full_text_query = "" words = [el for el in remove_lucene_chars(input).split() if el] for word in words[:-1]: full_text_query += f" {word}~2 AND" full_text_query += f" {words[-1]}~2" return full_text_query.strip()



Poďme si to teraz dať dokopy.


 # Fulltext index query def structured_retriever(question: str) -> str: """ Collects the neighborhood of entities mentioned in the question """ result = "" entities = entity_chain.invoke({"question": question}) for entity in entities.names: response = graph.query( """CALL db.index.fulltext.queryNodes('entity', $query, {limit:2}) YIELD node,score CALL { MATCH (node)-[r:!MENTIONS]->(neighbor) RETURN node.id + ' - ' + type(r) + ' -> ' + neighbor.id AS output UNION MATCH (node)<-[r:!MENTIONS]-(neighbor) RETURN neighbor.id + ' - ' + type(r) + ' -> ' + node.id AS output } RETURN output LIMIT 50 """, {"query": generate_full_text_query(entity)}, ) result += "\n".join([el['output'] for el in response]) return result



Funkcia structured_retriever začína detekciou entít v používateľskej otázke. Ďalej iteruje cez zistené entity a používa šablónu Cypher na získanie okolia relevantných uzlov. Poďme to otestovať!


 print(structured_retriever("Who is Elizabeth I?")) # Elizabeth I - BORN_ON -> 7 September 1533 # Elizabeth I - DIED_ON -> 24 March 1603 # Elizabeth I - TITLE_HELD_FROM -> Queen Of England And Ireland # Elizabeth I - TITLE_HELD_UNTIL -> 17 November 1558 # Elizabeth I - MEMBER_OF -> House Of Tudor # Elizabeth I - CHILD_OF -> Henry Viii # and more...


Finálny retriever

Ako už bolo spomenuté na začiatku, skombinujeme neštruktúrovaný a graph retriever, aby sme vytvorili konečný kontext odovzdaný LLM.


 def retriever(question: str): print(f"Search query: {question}") structured_data = structured_retriever(question) unstructured_data = [el.page_content for el in vector_index.similarity_search(question)] final_data = f"""Structured data: {structured_data} Unstructured data: {"#Document ". join(unstructured_data)} """ return final_data


Keďže máme čo do činenia s Pythonom, môžeme jednoducho zreťaziť výstupy pomocou f-reťazca.

Definovanie RAG reťazca

Úspešne sme implementovali vyhľadávaciu zložku RAG. Ďalej uvádzame výzvu, ktorá využíva kontext poskytovaný integrovaným hybridným retrieverom na vytvorenie odpovede, čím sa dokončí implementácia reťazca RAG.


 template = """Answer the question based only on the following context: {context} Question: {question} """ prompt = ChatPromptTemplate.from_template(template) chain = ( RunnableParallel( { "context": _search_query | retriever, "question": RunnablePassthrough(), } ) | prompt | llm | StrOutputParser() )


Nakoniec môžeme pokračovať a otestovať našu hybridnú implementáciu RAG.


 chain.invoke({"question": "Which house did Elizabeth I belong to?"}) # Search query: Which house did Elizabeth I belong to? # 'Elizabeth I belonged to the House of Tudor.'


Začlenil som tiež funkciu prepisovania dotazov, ktorá umožňuje reťazcu RAG prispôsobiť sa konverzačným nastaveniam, ktoré umožňujú následné otázky. Vzhľadom na to, že používame metódy vyhľadávania vektorov a kľúčových slov, musíme prepísať následné otázky, aby sme optimalizovali proces vyhľadávania.


 chain.invoke( { "question": "When was she born?", "chat_history": [("Which house did Elizabeth I belong to?", "House Of Tudor")], } ) # Search query: When was Elizabeth I born? # 'Elizabeth I was born on 7 September 1533.'


Môžete si všimnúť, že Kedy sa narodila? bol prvýkrát prepísaný na Kedy sa narodila Alžbeta I.? . Prepísaný dotaz sa potom použil na získanie relevantného kontextu a zodpovedanie otázky.

Znalostné grafy sú jednoduché

So zavedením LLMGraphTransformer by teraz mal byť proces generovania znalostných grafov plynulejší a dostupnejší, čo uľahčí každému, kto chce vylepšiť svoje RAG aplikácie o hĺbku a kontext, ktoré znalostné grafy poskytujú. Toto je len začiatok, pretože máme naplánovaných veľa vylepšení.


Ak máte postrehy, návrhy alebo otázky týkajúce sa nášho generovania grafov pomocou LLM, neváhajte nás kontaktovať.


Kód je dostupný na GitHub .