Hybrid search - Retrieve from Graph and Embeddings.
Introduction
In today's digital world, the ability to extract and interact with information from various documents is crucial for businesses and individuals alike. Leveraging cutting-edge technologies like Streamlit, LangChain, and Neo4j, we can build sophisticated chatbots that not only retrieve information from documents but also integrate with knowledge graphs for enhanced insights. This article explores two powerful implementations of such chatbots. In earlier article link, I have provided information on how to build the Streamlit chatbot and integrate with Neo4J.
Take away from the article:-
After this article and the earlier article, readers will understand the concept on:-
1. How to build Streamlit chatbot to create Neo4J graph:-
This following highlighted implementation is already covered in another article. Please read that article and video URL to implement the chatbot, setup Neo4J, create Graph and show graph in chatbot.
Video URL of the the first implementation:-
2. Advanced RAG Chatbot with Neo4j Knowledge Graph
The second implementation enhances the basic RAG chatbot by integrating a Neo4j knowledge graph. This allows for more sophisticated data retrieval and visualization. We will implement Hybrid search techniques on the already created graph in Neo4J. This approach will combine search technique of Cypher query and Embedding similarity search.
Setting Up the Environment
To start, we load necessary libraries and environment variables, and set up Neo4j credentials. This ensures that the environment is correctly configured for the subsequent operations.
import os
import tempfile
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain_community.graphs import Neo4jGraph
from dotenv import load_dotenv, find_dotenv
from langchain_groq import ChatGroq
class RAG_Graph:
load_dotenv(find_dotenv())
def __init__(self):
os.environ["NEO4J_URI"] = "bolt://localhost:7687"
os.environ["NEO4J_USERNAME"] = "neo4j"
os.environ["NEO4J_PASSWORD"] = "password"
self.graph = Neo4jGraph()
self.llm = ChatGroq(temperature=0.5, groq_api_key=os.getenv("GROQ_API_KEY"), model_name="llama3-70b-8192")
Embedding with Vector Index
The create_vector_index function retrieves unstructured content from the index name 'vector' using HuggingFace embeddings.
领英推荐
def create_vector_index(self):
model_name = 'sentence-transformers/all-mpnet-base-v2'
self.vector_index = Neo4jVector.from_existing_index(
HuggingFaceEmbeddings(model_name=model_name, model_kwargs={'device': 'cpu'}),
url=os.environ["NEO4J_URI"],
username=os.environ["NEO4J_USERNAME"],
password=os.environ["NEO4J_PASSWORD"],
index_name="vector",
)
Preparing the Chat Template
The prepare_chat_template function sets up a chat prompt template, defining how the chatbot should extract and present information from the documents.
def prepare_chat_template(self):
prompt = ChatPromptTemplate.from_messages(
[
("system", "You are extracting fields and business rules from the text"),
("human", "Use this given format to extract the information from the following input: {question}"),
]
)
self.entity_chain = prompt | self.llm.with_structured_output(Entities)
Retrieving Data
The retriever function handles user queries by retrieving both structured and unstructured data. It combines the results into a final response.
def retriever(self, question: str):
structure_data = self.structured_retriever(question)
unstructured_data = [el.page_content for el in self.vector_index.similarity_search(question)]
final_data = f"""Structured data:\n{structure_data}\nUnstructured data:\n{"#Document ".join(unstructured_data)}"""
return final_data
Structured Data Retrieval
The structured_retriever function queries the Neo4j knowledge graph to retrieve structured data based on the user's question.
def structured_retriever(self, question: str) -> str:
result = ""
entities = self.entity_chain.invoke({"question": question})
for entity in entities.names:
response = self.graph.query(
"""CALL db.index.fulltext.queryNodes('entity', $query, {limit:2})
YIELD node,score
CALL {
WITH node
MATCH (node)-[r:!MENTIONS]->(neighbor)
RETURN node.id + ' - ' + type(r) + ' -> ' + neighbor.id AS output
UNION ALL
WITH node
MATCH (node)<-[r:!MENTIONS]-(neighbor)
RETURN neighbor.id + ' - ' + type(r) + ' -> ' + node.id AS output
}
RETURN output LIMIT 50""",
{"query": self.generate_full_text_query(entity)},
)
result += "\n".join([el['output'] for el in response])
return result
Asking Questions
The ask_question_chain function orchestrates the entire process of creating vector indexes, preparing chat templates, and retrieving data to answer user questions. This function will be called from Streamlit UI python file.
def ask_question_chain(self, query):
self.graph.query("CREATE FULLTEXT INDEX entity IF NOT EXISTS FOR (e:__Entity__) ON EACH [e.id]")
self.create_vector_index()
self.prepare_chat_template()
template = """Answer the question based only on the following context\n{context}\n\nQuestion: {question}\nUser natural language and be concise.\nAnswer: """
prompt = ChatPromptTemplate.from_template(template)
chain = (
RunnableParallel({"context": self.retriever, "question": RunnablePassthrough()})
| prompt
| self.llm
| StrOutputParser()
)
result = chain.invoke(query)
return result
Video tutorial:-
The video tutorial has explanations and entire coding. While watching the video you can also implement in your favorite editor.
Conclusion
By combining Streamlit, LangChain, and Neo4j, we can create a powerful chatbot capable of extracting and interacting with information from documents. This approach leverages both structured and unstructured data retrieval to provide comprehensive answers to user queries. Explore the full code and customize it to fit your specific needs. Happy coding!
Machine Learning Operations practitioner
4 个月Thanks for sharing this. Very much useful to understand about RAG!