Grafiekherwinning-vergrote generasie ( GraphRAG ) kry momentum en word 'n kragtige toevoeging tot tradisionele vektorsoektogherwinningmetodes. Hierdie benadering benut die gestruktureerde aard van grafiekdatabasisse, wat data as nodusse en verwantskappe organiseer, om die diepte en kontekstualiteit van opgespoorde inligting te verbeter.
Grafieke is uitstekend om heterogene en onderling gekoppelde inligting op 'n gestruktureerde wyse voor te stel en te stoor, en sonder moeite komplekse verwantskappe en eienskappe oor diverse datatipes vas te lê. Daarteenoor sukkel vektordatabasisse dikwels met sulke gestruktureerde inligting, aangesien hul krag lê in die hantering van ongestruktureerde data deur middel van hoë-dimensionele vektore. In jou JOOL-toepassing kan jy gestruktureerde grafiekdata kombineer met vektorsoektog deur ongestruktureerde teks om die beste van albei wêrelde te bereik. Dit is wat ons in hierdie blogpos sal demonstreer.
Die bou van 'n kennisgrafiek is tipies die mees uitdagende stap. Dit behels die insameling en strukturering van die data, wat 'n diepgaande begrip van beide die domein en grafiekmodellering vereis.
Om hierdie proses te vereenvoudig, het ons met LLM's geëksperimenteer. Met hul diepgaande begrip van taal en konteks, kan LLM's beduidende dele van die kennisgrafiekskeppingsproses outomatiseer. Deur teksdata te analiseer, kan hierdie modelle entiteite identifiseer, hul verwantskappe verstaan en voorstel hoe hulle die beste in 'n grafiekstruktuur voorgestel kan word.
As gevolg van hierdie eksperimente het ons die eerste weergawe van die grafiekkonstruksiemodule by LangChain gevoeg, wat ons in hierdie blogpos sal demonstreer.
Die kode is beskikbaar op GitHub .
Jy moet 'n Neo4j-instansie opstel. Volg saam met die voorbeelde in hierdie blogpos. Die maklikste manier is om 'n gratis instansie op Neo4j Aura te begin, wat wolkgevalle van die Neo4j-databasis bied. Alternatiewelik kan jy ook 'n plaaslike instansie van die Neo4j-databasis opstel deur die Neo4j Desktop- toepassing af te laai en 'n plaaslike databasisinstansie te skep.
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()
Daarbenewens moet jy 'n OpenAI-sleutel verskaf, aangesien ons hul modelle in hierdie blogpos sal gebruik.
Vir hierdie demonstrasie sal ons Elizabeth I se Wikipedia-bladsy gebruik. Ons kan LangChain-laaiers gebruik om die dokumente van Wikipedia naatloos te gaan haal en te verdeel.
# 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])
Dit is tyd om 'n grafiek te bou gebaseer op die opgespoorde dokumente. Vir hierdie doel het ons 'n LLMGraphTransformermodule geïmplementeer wat die konstruksie en berging van 'n kennisgrafiek in 'n grafiekdatabasis aansienlik vergemaklik.
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 )
Jy kan definieer watter LLM jy wil hê die kennisgrafiekgenereringsketting moet gebruik. Tans ondersteun ons slegs funksie-oproepmodelle van OpenAI en Mistral. Ons beplan egter om die LLM-keuse in die toekoms uit te brei. In hierdie voorbeeld gebruik ons die nuutste GPT-4. Let daarop dat die kwaliteit van die gegenereerde grafiek aansienlik afhang van die model wat jy gebruik. In teorie wil jy altyd die mees bekwame een gebruik. Die LLM-grafiektransformators gee grafiekdokumente terug, wat via die add_graph_documents-metode na Neo4j ingevoer kan word. Die baseEntityLabel-parameter ken 'n bykomende toe
U kan die gegenereerde grafiek in die Neo4j-blaaier inspekteer.
Let daarop dat hierdie prent slegs 'n deel van die gegenereerde grafiek verteenwoordig.
Na die grafiekgenerering sal ons 'n hibriede herwinningsbenadering gebruik wat vektor- en sleutelwoordindekse kombineer met grafiekherwinning vir JOOL-toepassings.
Die diagram illustreer 'n herwinningsproses wat begin met 'n gebruiker wat 'n vraag stel, wat dan na 'n JOOL-ontvanger gerig word. Hierdie herwinner gebruik sleutelwoord- en vektorsoektogte om deur ongestruktureerde teksdata te soek en kombineer dit met die inligting wat dit van die kennisgrafiek versamel. Aangesien Neo4j beide sleutelwoord- en vektorindekse bevat, kan u al drie herwinningsopsies met 'n enkele databasisstelsel implementeer. Die versamelde data uit hierdie bronne word in 'n LLM ingevoer om die finale antwoord te genereer en te lewer.
Jy kan die Neo4jVector.from_existing_graph-metode gebruik om beide sleutelwoord- en vektorherwinning by dokumente te voeg. Hierdie metode konfigureer sleutelwoord- en vektorsoekindekse vir 'n hibriede soekbenadering, wat nodusse gemerk Document teiken. Boonop bereken dit teksinbeddingwaardes as dit ontbreek.
vector_index = Neo4jVector.from_existing_graph( OpenAIEmbeddings(), search_type="hybrid", node_label="Document", text_node_properties=["text"], embedding_node_property="embedding" )
Die vektorindeks kan dan met die ooreenkoms_soekmetode genoem word.
Aan die ander kant is die opstel van 'n grafiekherwinning meer betrokke, maar bied meer vryheid. Hierdie voorbeeld sal 'n volteks-indeks gebruik om relevante nodusse te identifiseer en hul direkte omgewing terug te gee.
Die grafiekherwinner begin deur relevante entiteite in die invoer te identifiseer. Vir eenvoud gee ons die LLM opdrag om mense, organisasies en liggings te identifiseer. Om dit te bereik, sal ons LCEL gebruik met die nuut bygevoegde with_structured_output metode om dit te bereik.
# 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)
Kom ons toets dit uit:
entity_chain.invoke({"question": "Where was Amelia Earhart born?"}).names # ['Amelia Earhart']
Wonderlik, noudat ons entiteite in die vraag kan opspoor, kom ons gebruik 'n volteksindeks om hulle na die kennisgrafiek te karteer. Eerstens moet ons 'n volteks-indeks en 'n funksie definieer wat volteks-navrae sal genereer wat 'n bietjie spelfout toelaat, wat ons nie hier in baie besonderhede sal ingaan nie.
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()
Kom ons sit dit nou alles saam.
# 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
Die structured_retriever-funksie begin deur entiteite in die gebruikervraag op te spoor. Vervolgens herhaal dit oor die bespeurde entiteite en gebruik 'n Cypher-sjabloon om die omgewing van relevante nodusse te herwin. Kom ons toets dit uit!
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...
Soos aan die begin genoem, sal ons die ongestruktureerde en grafiekherwinner kombineer om die finale konteks te skep wat na 'n LLM oorgedra word.
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
Soos ons met Python te doen het, kan ons eenvoudig die uitsette aaneenskakel deur die f-string te gebruik.
Ons het die herwinningskomponent van die JOOL suksesvol geïmplementeer. Vervolgens stel ons 'n aansporing bekend wat die konteks wat deur die geïntegreerde hibriede retriever verskaf word, gebruik om die reaksie te produseer, wat die implementering van die JOOL-ketting voltooi.
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() )
Uiteindelik kan ons voortgaan en ons baster JOOL-implementering toets.
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.'
Ek het ook 'n navraagherskryffunksie ingesluit, wat die JOOL-ketting in staat stel om aan te pas by gespreksinstellings wat opvolgvrae moontlik maak. Aangesien ons vektor- en sleutelwoordsoekmetodes gebruik, moet ons opvolgvrae herskryf om ons soekproses te optimaliseer.
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.'
Jy kan sien dat Wanneer is sy gebore? is die eerste keer herskryf na Wanneer is Elizabeth I gebore? . Die hergeskrewe navraag is dan gebruik om relevante konteks te herwin en die vraag te beantwoord.
Met die bekendstelling van die LLMGraphTransformer behoort die proses om kennisgrafieke te genereer nou gladder en meer toeganklik te wees, wat dit makliker maak vir enigiemand wat hul JOOL-toepassings wil verbeter met die diepte en konteks wat kennisgrafieke verskaf. Dit is net 'n begin aangesien ons baie verbeterings beplan het.
As jy insigte, voorstelle of vrae het oor ons generering van grafieke met LLM'e, moet asseblief nie huiwer om uit te reik nie.
Die kode is beskikbaar op