adeena43 commited on
Commit
fece80e
·
verified ·
1 Parent(s): 0869f58

Update rag_engine.py

Browse files
Files changed (1) hide show
  1. rag_engine.py +179 -179
rag_engine.py CHANGED
@@ -1,180 +1,180 @@
1
- # rag_engine.py
2
-
3
- import os
4
- import warnings
5
- from typing import List
6
- from dotenv import load_dotenv
7
-
8
- # Suppress warnings
9
- warnings.filterwarnings('ignore')
10
-
11
- # Set environment variables before imports
12
- os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
13
- os.environ["USER_AGENT"] = "PythonicAI/1.0 (+adinafraz01@gmail.com)"
14
- os.environ["TOKENIZERS_PARALLELISM"] = "false"
15
-
16
- # Load env variables
17
- load_dotenv()
18
- openai_key = os.getenv("OPENAI_API_KEY")
19
-
20
- if not openai_key:
21
- raise ValueError("OPENAI_API_KEY not found in environment variables. Please create a .env file with your API key.")
22
-
23
- os.environ["OPENAI_API_KEY"] = openai_key
24
-
25
- from langchain.text_splitter import RecursiveCharacterTextSplitter
26
- from langchain_openai import OpenAIEmbeddings, ChatOpenAI
27
- from langchain_community.vectorstores import FAISS
28
- from langchain_community.document_loaders import WebBaseLoader
29
- from langchain_core.prompts import PromptTemplate
30
- from langchain.schema import Document
31
-
32
- # ------------------------------------------
33
- # DOCUMENT LOADING (URL ONLY)
34
- # ------------------------------------------
35
-
36
- def load_user_document(url: str) -> List[Document]:
37
- """Load URL into LangChain documents."""
38
- try:
39
- if not url:
40
- raise ValueError("No URL provided")
41
-
42
- # Clean URL
43
- if not url.startswith(('http://', 'https://')):
44
- url = f'https://{url}'
45
-
46
- # Use WebBaseLoader with custom headers
47
- headers = {
48
- 'User-Agent': os.environ.get("USER_AGENT", "PythonicAI/1.0")
49
- }
50
-
51
- loader = WebBaseLoader(
52
- web_paths=[url],
53
- requests_per_second=1,
54
- requests_kwargs={'headers': headers}
55
- )
56
- docs = loader.load()
57
-
58
- if not docs:
59
- raise ValueError("No content found in the URL")
60
-
61
- return docs
62
-
63
- except Exception as e:
64
- raise Exception(f"Error loading URL: {str(e)}")
65
-
66
- # ------------------------------------------
67
- # BUILD RETRIEVER FROM DOCUMENTS
68
- # ------------------------------------------
69
-
70
- def build_retriever_from_docs(docs: List[Document]):
71
- """Build FAISS vector store from documents."""
72
- if not docs:
73
- raise ValueError("No documents provided")
74
-
75
- # Use the same splitter settings as your example
76
- splitter = RecursiveCharacterTextSplitter(
77
- chunk_size=1000,
78
- chunk_overlap=200
79
- )
80
-
81
- chunks = splitter.split_documents(docs)
82
-
83
- # Use the same embedding model as your example
84
- embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
85
-
86
- # Build vector store
87
- vector_store = FAISS.from_documents(chunks, embeddings)
88
-
89
- return vector_store.as_retriever(
90
- search_type="similarity",
91
- search_kwargs={"k": 6}
92
- )
93
-
94
- # ------------------------------------------
95
- # PYTHON DOMAIN CHECK
96
- # ------------------------------------------
97
-
98
- _llm = None
99
-
100
- def get_llm():
101
- """Singleton pattern for LLM to avoid re-initialization."""
102
- global _llm
103
- if _llm is None:
104
- _llm = ChatOpenAI(
105
- model="gpt-4o-mini",
106
- temperature=0.2,
107
- max_tokens=1000,
108
- request_timeout=60
109
- )
110
- return _llm
111
-
112
- def is_python_related(question: str) -> bool:
113
- """Check if question is Python-related."""
114
- python_keywords = [
115
- 'python', 'numpy', 'pandas', 'matplotlib', 'seaborn',
116
- 'machine learning', 'deep learning', 'tensorflow', 'pytorch',
117
- 'scikit-learn', 'sklearn', 'dataframe', 'array', 'plot',
118
- 'import', 'def ', 'class ', 'function', 'method', 'library',
119
- 'package', 'module', 'pip install', 'conda install', 'jupyter',
120
- 'data', 'science', 'analysis', 'visualization', 'programming',
121
- 'code', 'script', 'algorithm', 'model', 'training', 'prediction'
122
- ]
123
-
124
- # Quick keyword check first
125
- question_lower = question.lower()
126
- for keyword in python_keywords:
127
- if keyword in question_lower:
128
- return True
129
-
130
- return False
131
-
132
- # ------------------------------------------
133
- # ANSWER QUESTION
134
- # ------------------------------------------
135
-
136
- # Define the prompt template (same as your example)
137
- prompt = PromptTemplate(
138
- template="""
139
- You are a helpful python assistant.
140
- Answer ONLY from the provided text context.
141
- Display answer in a beautiful and user understandable format with proper formatting.
142
- Use bullet points, numbered lists, or code blocks when appropriate.
143
- If the context is insufficient, just say you don't know.
144
-
145
- Context: {context}
146
- Question: {question}
147
- """,
148
- input_variables=['context', 'question']
149
- )
150
-
151
- def answer_question(question: str, retriever):
152
- """Answer question using RAG pipeline."""
153
- # Validate topic
154
- if not is_python_related(question):
155
- return "I can only answer Python and data science related questions based on the provided documentation."
156
-
157
- try:
158
- # Retrieve relevant documents
159
- docs = retriever.invoke(question)
160
-
161
- if not docs:
162
- return "I couldn't find relevant information in the documentation to answer this question."
163
-
164
- # Combine context from retrieved documents
165
- context = "\n\n".join(doc.page_content for doc in docs)
166
-
167
- # Create final prompt
168
- final_prompt = prompt.invoke({
169
- "context": context,
170
- "question": question
171
- })
172
-
173
- # Get answer from LLM
174
- llm = get_llm()
175
- answer = llm.invoke(final_prompt)
176
-
177
- return answer.content
178
-
179
- except Exception as e:
180
  return f"Error generating answer: {str(e)}"
 
1
+ # rag_engine.py
2
+
3
+ import os
4
+ import warnings
5
+ from typing import List
6
+ from dotenv import load_dotenv
7
+
8
+ # Suppress warnings
9
+ warnings.filterwarnings('ignore')
10
+
11
+ # Set environment variables before imports
12
+ os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
13
+ os.environ["USER_AGENT"] = "PythonicAI/1.0 (+adinafraz01@gmail.com)"
14
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
15
+
16
+ # Load env variables
17
+ load_dotenv()
18
+ openai_key = os.getenv("OPENAI_API_KEY")
19
+
20
+ if not openai_key:
21
+ raise ValueError("OPENAI_API_KEY not found in environment variables. Please create a .env file with your API key.")
22
+
23
+ os.environ["OPENAI_API_KEY"] = openai_key
24
+
25
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
26
+ from langchain_openai import OpenAIEmbeddings, ChatOpenAI
27
+ from langchain_community.vectorstores import FAISS
28
+ from langchain_community.document_loaders import WebBaseLoader
29
+ from langchain_core.prompts import PromptTemplate
30
+ from langchain.schema import Document
31
+
32
+ # ------------------------------------------
33
+ # DOCUMENT LOADING (URL ONLY)
34
+ # ------------------------------------------
35
+
36
+ def load_user_document(url: str) -> List[Document]:
37
+ """Load URL into LangChain documents."""
38
+ try:
39
+ if not url:
40
+ raise ValueError("No URL provided")
41
+
42
+ # Clean URL
43
+ if not url.startswith(('http://', 'https://')):
44
+ url = f'https://{url}'
45
+
46
+ # Use WebBaseLoader with custom headers
47
+ headers = {
48
+ 'User-Agent': os.environ.get("USER_AGENT", "PythonicAI/1.0")
49
+ }
50
+
51
+ loader = WebBaseLoader(
52
+ web_paths=[url],
53
+ requests_per_second=1,
54
+ requests_kwargs={'headers': headers}
55
+ )
56
+ docs = loader.load()
57
+
58
+ if not docs:
59
+ raise ValueError("No content found in the URL")
60
+
61
+ return docs
62
+
63
+ except Exception as e:
64
+ raise Exception(f"Error loading URL: {str(e)}")
65
+
66
+ # ------------------------------------------
67
+ # BUILD RETRIEVER FROM DOCUMENTS
68
+ # ------------------------------------------
69
+
70
+ def build_retriever_from_docs(docs: List[Document]):
71
+ """Build FAISS vector store from documents."""
72
+ if not docs:
73
+ raise ValueError("No documents provided")
74
+
75
+ # Use the same splitter settings as your example
76
+ splitter = RecursiveCharacterTextSplitter(
77
+ chunk_size=1000,
78
+ chunk_overlap=200
79
+ )
80
+
81
+ chunks = splitter.split_documents(docs)
82
+
83
+ # Use the same embedding model as your example
84
+ embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
85
+
86
+ # Build vector store
87
+ vector_store = FAISS.from_documents(chunks, embeddings)
88
+
89
+ return vector_store.as_retriever(
90
+ search_type="similarity",
91
+ search_kwargs={"k": 6}
92
+ )
93
+
94
+ # ------------------------------------------
95
+ # PYTHON DOMAIN CHECK
96
+ # ------------------------------------------
97
+
98
+ _llm = None
99
+
100
+ def get_llm():
101
+ """Singleton pattern for LLM to avoid re-initialization."""
102
+ global _llm
103
+ if _llm is None:
104
+ _llm = ChatOpenAI(
105
+ model="gpt-4o-mini",
106
+ temperature=0.2,
107
+ max_tokens=1000,
108
+ request_timeout=60
109
+ )
110
+ return _llm
111
+
112
+ def is_python_related(question: str) -> bool:
113
+ """Check if question is Python-related."""
114
+ python_keywords = [
115
+ 'python', 'numpy', 'pandas', 'matplotlib', 'seaborn',
116
+ 'machine learning', 'deep learning', 'tensorflow', 'pytorch',
117
+ 'scikit-learn', 'sklearn', 'dataframe', 'array', 'plot',
118
+ 'import', 'def ', 'class ', 'function', 'method', 'library',
119
+ 'package', 'module', 'pip install', 'conda install', 'jupyter',
120
+ 'data', 'science', 'analysis', 'visualization', 'programming',
121
+ 'code', 'script', 'algorithm', 'model', 'training', 'prediction'
122
+ ]
123
+
124
+ # Quick keyword check first
125
+ question_lower = question.lower()
126
+ for keyword in python_keywords:
127
+ if keyword in question_lower:
128
+ return True
129
+
130
+ return False
131
+
132
+ # ------------------------------------------
133
+ # ANSWER QUESTION
134
+ # ------------------------------------------
135
+
136
+ # Define the prompt template (same as your example)
137
+ prompt = PromptTemplate(
138
+ template="""
139
+ You are a helpful python assistant.
140
+ Answer ONLY from the provided text context.
141
+ Display answer in a beautiful and user understandable format with proper formatting.
142
+ Use bullet points, numbered lists, or code blocks when appropriate.
143
+ If the context is insufficient, just say you don't know.
144
+
145
+ Context: {context}
146
+ Question: {question}
147
+ """,
148
+ input_variables=['context', 'question']
149
+ )
150
+
151
+ def answer_question(question: str, retriever):
152
+ """Answer question using RAG pipeline."""
153
+ # Validate topic
154
+ if not is_python_related(question):
155
+ return "I can only answer Python and data science related questions based on the provided documentation."
156
+
157
+ try:
158
+ # Retrieve relevant documents
159
+ docs = retriever.invoke(question)
160
+
161
+ if not docs:
162
+ return "I couldn't find relevant information in the documentation to answer this question."
163
+
164
+ # Combine context from retrieved documents
165
+ context = "\n\n".join(doc.page_content for doc in docs)
166
+
167
+ # Create final prompt
168
+ final_prompt = prompt.invoke({
169
+ "context": context,
170
+ "question": question
171
+ })
172
+
173
+ # Get answer from LLM
174
+ llm = get_llm()
175
+ answer = llm.invoke(final_prompt)
176
+
177
+ return answer.content
178
+
179
+ except Exception as e:
180
  return f"Error generating answer: {str(e)}"