GraphRAG ( GraphRAG ) กำลังได้รับความนิยมและกลายเป็นส่วนเสริมที่มีประสิทธิภาพสำหรับวิธีการค้นหาเวกเตอร์แบบดั้งเดิม แนวทางนี้ใช้ประโยชน์จากลักษณะที่มีโครงสร้างของฐานข้อมูลกราฟ ซึ่งจัดระเบียบข้อมูลเป็นโหนดและความสัมพันธ์ เพื่อเพิ่มความลึกและบริบทของข้อมูลที่ดึงมา
กราฟนั้นยอดเยี่ยมสำหรับการแสดงและจัดเก็บข้อมูลที่หลากหลายและเชื่อมโยงกันในลักษณะที่มีโครงสร้าง โดยสามารถจับภาพความสัมพันธ์และคุณลักษณะที่ซับซ้อนในประเภทข้อมูลที่หลากหลายได้อย่างง่ายดาย ในทางตรงกันข้าม ฐานข้อมูลเวกเตอร์มักจะประสบปัญหาในการจัดการข้อมูลที่มีโครงสร้างดังกล่าว เนื่องจากจุดแข็งของฐานข้อมูลประเภทนี้อยู่ที่การจัดการข้อมูลที่ไม่มีโครงสร้างผ่านเวกเตอร์ที่มีมิติสูง ในแอปพลิเคชัน RAG คุณสามารถรวมข้อมูลกราฟที่มีโครงสร้างกับการค้นหาเวกเตอร์ผ่านข้อความที่ไม่มีโครงสร้างเพื่อให้ได้สิ่งที่ดีที่สุดจากทั้งสองโลก นั่นคือสิ่งที่เราจะสาธิตในโพสต์บล็อกนี้
การสร้างกราฟความรู้ถือเป็นขั้นตอนที่ท้าทายที่สุด โดยเกี่ยวข้องกับการรวบรวมและจัดโครงสร้างข้อมูล ซึ่งต้องมีความเข้าใจอย่างลึกซึ้งในโดเมนและการสร้างแบบจำลองกราฟ
เพื่อลดความซับซ้อนของกระบวนการนี้ เราได้ทดลองใช้ LLM ด้วยความเข้าใจอย่างลึกซึ้งเกี่ยวกับภาษาและบริบท LLM จึงสามารถทำให้ส่วนสำคัญของกระบวนการสร้างกราฟความรู้เป็นแบบอัตโนมัติได้ ด้วยการวิเคราะห์ข้อมูลข้อความ โมเดลเหล่านี้สามารถระบุเอนทิตี เข้าใจความสัมพันธ์ และแนะนำวิธีที่ดีที่สุดในการแสดงเอนทิตีเหล่านั้นในโครงสร้างกราฟ
จากผลการทดลองเหล่านี้ เราจึงได้เพิ่มโมดูลการสร้างกราฟเวอร์ชันแรกลงใน LangChain ซึ่งเราจะสาธิตให้เห็นในโพสต์บล็อกนี้
โค้ดนี้มีอยู่บน GitHub
คุณต้องตั้งค่าอินสแตนซ์ Neo4j ทำตามตัวอย่างในโพสต์บล็อกนี้ วิธีที่ง่ายที่สุดคือเริ่มใช้อินสแตนซ์ฟรีบน Neo4j Aura ซึ่งเสนออินสแตนซ์คลาวด์ของฐานข้อมูล Neo4j อีกวิธีหนึ่งคือตั้งค่าอินสแตนซ์ภายในเครื่องของฐานข้อมูล Neo4j โดยดาวน์โหลดแอปพลิเคชัน Neo4j Desktop และสร้างอินสแตนซ์ฐานข้อมูลภายในเครื่อง
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()
นอกจากนี้ คุณต้องระบุ คีย์ OpenAI เนื่องจากเราจะใช้โมเดลของพวกเขาในโพสต์บล็อกนี้
สำหรับการสาธิตนี้ เราจะใช้หน้า Wikipedia ของสมเด็จพระราชินีนาถเอลิซาเบธที่ 1 เราสามารถใช้ ตัวโหลด LangChain เพื่อดึงและแยกเอกสารจาก Wikipedia ได้อย่างราบรื่น
# 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])
ถึงเวลาสร้างกราฟโดยอิงจากเอกสารที่เรียกค้นมา เพื่อจุดประสงค์นี้ เราได้นำ LLMGraphTransformermodule มาใช้ ซึ่งทำให้การสร้างและจัดเก็บกราฟความรู้ในฐานข้อมูลกราฟง่ายขึ้นอย่างมาก
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 )
คุณสามารถกำหนด LLM ที่คุณต้องการให้เชนการสร้างกราฟความรู้ใช้ ปัจจุบัน เรารองรับเฉพาะโมเดลการเรียกฟังก์ชันจาก OpenAI และ Mistral อย่างไรก็ตาม เราวางแผนที่จะขยายการเลือก LLM ในอนาคต ในตัวอย่างนี้ เราใช้ GPT-4 รุ่นล่าสุด โปรดทราบว่าคุณภาพของกราฟที่สร้างขึ้นนั้นขึ้นอยู่กับโมเดลที่คุณกำลังใช้เป็นอย่างมาก ในทางทฤษฎี คุณจะต้องการใช้โมเดลที่มีความสามารถสูงสุดเสมอ ตัวแปลงกราฟ LLM จะส่งคืนเอกสารกราฟ ซึ่งสามารถนำเข้าสู่ Neo4j ได้โดยใช้เมธอด add_graph_documents พารามิเตอร์ baseEntityLabel จะกำหนดเพิ่มเติม
คุณสามารถตรวจสอบกราฟที่สร้างขึ้นได้ใน Neo4j Browser
โปรดทราบว่ารูปภาพนี้แสดงเพียงส่วนหนึ่งของกราฟที่สร้างขึ้นเท่านั้น
หลังจากสร้างกราฟแล้ว เราจะใช้แนวทางการเรียกค้นแบบไฮบริดที่รวมดัชนีเวกเตอร์และคำสำคัญเข้ากับการเรียกค้นกราฟสำหรับแอปพลิเคชัน RAG
ไดอะแกรมแสดงขั้นตอนการค้นหาโดยเริ่มจากผู้ใช้ตั้งคำถาม จากนั้นระบบจะค้นหาข้อมูลด้วย RAG เครื่องมือค้นหานี้ใช้การค้นหาด้วยคำสำคัญและเวกเตอร์เพื่อค้นหาข้อมูลข้อความที่ไม่มีโครงสร้าง และรวมข้อมูลดังกล่าวเข้ากับข้อมูลที่รวบรวมจากกราฟความรู้ เนื่องจาก Neo4j มีทั้งดัชนีคำสำคัญและเวกเตอร์ คุณจึงสามารถใช้งานตัวเลือกการค้นหาทั้งสามแบบได้ด้วยระบบฐานข้อมูลเดียว ข้อมูลที่รวบรวมจากแหล่งเหล่านี้จะถูกป้อนเข้าสู่ LLM เพื่อสร้างและส่งมอบคำตอบสุดท้าย
คุณสามารถใช้เมธอด Neo4jVector.from_existing_graph เพื่อเพิ่มการค้นหาทั้งคำสำคัญและเวกเตอร์ลงในเอกสาร เมธอดนี้จะกำหนดค่าดัชนีการค้นหาคำสำคัญและเวกเตอร์สำหรับแนวทางการค้นหาแบบไฮบริด โดยกำหนดเป้าหมายไปที่โหนดที่มีป้ายกำกับว่า Document นอกจากนี้ เมธอดนี้ยังคำนวณค่าการฝังข้อความหากไม่มีค่าดังกล่าว
vector_index = Neo4jVector.from_existing_graph( OpenAIEmbeddings(), search_type="hybrid", node_label="Document", text_node_properties=["text"], embedding_node_property="embedding" )
จากนั้นสามารถเรียกใช้ดัชนีเวกเตอร์ได้โดยใช้เมธอด Similarity_search
ในทางกลับกัน การกำหนดค่าการเรียกค้นกราฟนั้นซับซ้อนกว่าแต่ก็ให้อิสระมากกว่า ตัวอย่างนี้จะใช้ดัชนีข้อความเต็มเพื่อระบุโหนดที่เกี่ยวข้องและส่งคืนบริเวณใกล้เคียงโดยตรง
โปรแกรมค้นหากราฟเริ่มต้นด้วยการระบุเอนทิตีที่เกี่ยวข้องในอินพุต เพื่อความเรียบง่าย เราจะสั่งให้ LLM ระบุบุคคล องค์กร และสถานที่ เพื่อให้บรรลุสิ่งนี้ เราจะใช้ LCEL พร้อมวิธี 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)
มาทดสอบกันดู:
entity_chain.invoke({"question": "Where was Amelia Earhart born?"}).names # ['Amelia Earhart']
เยี่ยมเลย ตอนนี้เราสามารถตรวจจับเอนทิตีในคำถามได้แล้ว ลองใช้ดัชนีข้อความเต็มเพื่อแมปเอนทิตีเหล่านั้นกับกราฟความรู้กันก่อน ขั้นแรก เราต้องกำหนดดัชนีข้อความเต็มและฟังก์ชันที่จะสร้างแบบสอบถามข้อความเต็มซึ่งอนุญาตให้สะกดผิดได้เล็กน้อย ซึ่งเราจะไม่ลงรายละเอียดมากนักในที่นี้
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()
มาเอาทุกอย่างมารวมกันตอนนี้
# 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
ฟังก์ชัน structured_retriever เริ่มต้นด้วยการตรวจจับเอนทิตีในคำถามของผู้ใช้ จากนั้นจะทำการวนซ้ำเอนทิตีที่ตรวจพบและใช้เทมเพลต Cypher เพื่อดึงข้อมูลบริเวณใกล้เคียงของโหนดที่เกี่ยวข้อง มาทดสอบกันเลย!
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...
ตามที่กล่าวไว้ในตอนต้น เราจะรวมตัวดึงข้อมูลแบบไม่มีโครงสร้างและแบบกราฟเพื่อสร้างบริบทขั้นสุดท้ายที่ส่งผ่านไปยัง 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
เนื่องจากเรากำลังใช้งาน Python เราสามารถต่อเอาต์พุตโดยใช้สตริง f ได้อย่างง่ายดาย
เราได้นำส่วนประกอบการเรียกค้นของ RAG มาใช้สำเร็จแล้ว ต่อไป เราจะแนะนำคำแนะนำที่ใช้ประโยชน์จากบริบทที่จัดเตรียมโดยตัวเรียกค้นไฮบริดแบบบูรณาการเพื่อสร้างการตอบสนอง ซึ่งทำให้การนำ RAG chain ไปใช้งานเสร็จสมบูรณ์
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() )
ในที่สุดเราก็สามารถทดสอบการใช้งาน 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.'
นอกจากนี้ ฉันยังรวมคุณลักษณะการเขียนคำถามใหม่ ซึ่งทำให้ RAG chain สามารถปรับให้เข้ากับการตั้งค่าการสนทนาที่อนุญาตให้ถามคำถามเพิ่มเติมได้ เนื่องจากเราใช้การค้นหาเวกเตอร์และคำหลัก เราจึงต้องเขียนคำถามเพิ่มเติมใหม่เพื่อปรับกระบวนการค้นหาของเราให้เหมาะสมที่สุด
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.'
คุณจะสังเกตได้ว่า When was she born? ถูกเขียนใหม่เป็น When was Elizabeth I born? ครั้งแรก จากนั้นจึงใช้คำถามที่เขียนใหม่เพื่อค้นหาบริบทที่เกี่ยวข้องและตอบคำถาม
ด้วยการนำ LLMGraphTransformer มาใช้ กระบวนการสร้างกราฟความรู้ควรจะราบรื่นและเข้าถึงได้ง่ายขึ้น ทำให้ทุกคนที่ต้องการปรับปรุงแอปพลิเคชัน RAG ของตนด้วยความลึกและบริบทที่กราฟความรู้มอบให้เป็นเรื่องง่ายขึ้น นี่เป็นเพียงจุดเริ่มต้นเท่านั้น เนื่องจากเรามีแผนการปรับปรุงมากมาย
หากคุณมีข้อมูลเชิงลึก ข้อเสนอแนะ หรือคำถามเกี่ยวกับการสร้างกราฟด้วย LLM ของเรา โปรดอย่าลังเลที่จะติดต่อเรา
โค้ดมีให้ใช้งานบน