Sulaiman8 commited on
Commit
f5fd80c
Β·
verified Β·
1 Parent(s): 0ce490e

Fix graph db connection failed bug

Browse files
Files changed (1) hide show
  1. recommender/graph_retrieval_vectordb.py +231 -225
recommender/graph_retrieval_vectordb.py CHANGED
@@ -1,226 +1,232 @@
1
- import google.generativeai as genai
2
- from neo4j import GraphDatabase
3
- import os
4
- import numpy as np
5
- import faiss
6
- from data import card_descriptions,eligibility_df
7
-
8
- #neo4j credentials
9
- NEO4J_URI = os.environ.get("NEO4J_URI")
10
- NEO4J_USER = os.environ.get("NEO4J_USER")
11
- NEO4J_PASS = os.environ.get("NEO4J_PASS")
12
- driver = GraphDatabase.driver(NEO4J_URI, auth=(NEO4J_USER, NEO4J_PASS))
13
-
14
- #generating cypher query
15
- def generate_cypher(user_query, query_intent, include_cobranded):
16
- genai.configure(api_key='AIzaSyAHoi9xbYAThtjXlyF_IKFtruoWYoUCjJQ')
17
- model3 = genai.GenerativeModel('gemini-1.5-flash-latest')
18
- print("inside cypher query gen")
19
-
20
- context_note = f"""
21
- Contextual Flags:
22
- - FD Card intent: {query_intent}
23
- - Include co-branded cards: {include_cobranded}
24
- """
25
-
26
- cypher_prompt = f"""
27
- You are an expert Neo4j Cypher query generator.
28
-
29
- Given a user’s question, graph schema, and **contextual flags**, generate the correct Cypher query. The query should return only the cards `c`.
30
-
31
- ONLY output the Cypher query. Do NOT explain anything.
32
-
33
- ---
34
-
35
- Graph Schema:
36
- - Nodes:
37
- - (Card): Properties = name, bank_name, card_type, premium, co_branded
38
- - (Feature): Properties = name
39
- - Relationships:
40
- - (Card)-[:HAS_FEATURE]->(Feature)
41
-
42
- Feature Inclusion Rules:
43
- - Only include relevant features based on user query.
44
- - Forex markup fee and foreign transaction fee are the same.
45
- - If FD Card intent is true then include the features if the query contains any and also include β€œGeneral Cashback” or β€œGeneral Reward Points”
46
- - Don’t add β€œGeneral Cashback” or β€œGeneral Reward Points” if it is not required.
47
- - If fuel is mentioned, include both `Fuel Benefits` and `Fuel Surcharge Waiver`.
48
- - **ALWAYS** match features using: `f.name IN [...]` β€” even if there is only **one** feature.
49
-
50
-
51
- Valid values:
52
- - card_type: 'FD Card' or 'Regular'
53
- - premium: true (no concept of false β€” just include it if applicable)
54
- - co_branded: true (no concept of false β€” just include it if applicable)
55
-
56
- MANDATORY Condition Rules:
57
- - If FD Card intent is true β†’ include: `c.card_type = 'FD Card'`
58
- - Else β†’ include: `c.card_type = 'Regular'`
59
- - If the query is based on beginners or students or people with no or low credit history then use FD Card.
60
- - If the query uses words like "premium", "elite", "luxury", "exclusive", "infinia", "black", etc. β†’ include: `AND c.premium = true`
61
- - If the query includes low spending, without high spending or budget β†’ include: `(c.premium IS NULL OR c.premium = false)`
62
- - If include co-branded is false β†’ include: `AND (c.co_branded IS NULL OR c.co_branded = false)`
63
- - Use exact values for `bank_name` as in the database: ["SBI", "HDFC", "Axis", "ICICI", "YES", "HSBC", "IDFC", "American Express", "SMB", "Federal Bank", "AU Bank", "IDBI", "Kotak Mahindra Bank","IndusInd","RBL"]
64
- - Do not add bank after the name of the bank if it is not mentioned in the datase list.
65
- - These conditions are **MANDATORY**. If they apply, include them in the `WHERE` clause. Do not skip them.
66
-
67
- ---
68
-
69
- Available features:
70
- "General Cashback", "Fuel Surcharge Waiver", "Fuel Benefits", "Welcome Bonus",
71
- "Airport Lounge Access", "General Reward Points", "Domestic Travel Benefits",
72
- "Movie Benefits", "Flight Discounts", "International Travel Benefits",
73
- "Hotel Benefits", "Dining Benefits", "Daily Spends (Grocery)", "Railway Benefits",
74
- "Travel Benefits", "Railway Lounge", "Insurance", "Utility",
75
- "E-commerce Platform Benefits", "Air Miles", "Spa Access Benefits",
76
- "Lifestyle & Luxury Perks", "Golf Access & Perks", "Online Shopping Benefits",
77
- "UPI Transaction Support", "Health Benefits", "EMI Conversion Options",
78
- "No Forex Markup Fee", "Roadside Assistance", "Rupay Network Support",Super Premium Cards
79
-
80
- ---
81
-
82
- Few-shot Examples:
83
-
84
- User Query: Show premium cards with airport lounge access
85
- Cypher:
86
- MATCH (c:Card)-[:HAS_FEATURE]->(f:Feature)
87
- WHERE f.name IN ["Airport Lounge Access"]
88
- AND c.card_type = 'Regular'
89
- AND c.premium = true
90
- RETURN c
91
-
92
- User Query: I want FD cards with spa access and golf perks
93
- Cypher:
94
- MATCH (c:Card)-[:HAS_FEATURE]->(f:Feature)
95
- WHERE f.name IN ["Spa Access Benefits", "Golf Access & Perks"]
96
- AND c.card_type = 'FD Card'
97
- RETURN c
98
-
99
- User Query: Cards that support UPI but are not co-branded
100
- Cypher:
101
- MATCH (c:Card)-[:HAS_FEATURE]->(f:Feature)
102
- WHERE f.name IN ["UPI Transaction Support"]
103
- AND c.card_type = 'Regular'
104
- AND (c.co_branded IS NULL OR c.co_branded = false)
105
- RETURN c
106
-
107
- ---
108
-
109
- {context_note}
110
-
111
- User Query: {user_query}
112
- Cypher:
113
- """
114
-
115
- response = model3.generate_content(cypher_prompt.strip())
116
- cypher_code = response.text.strip()
117
-
118
- if cypher_code.startswith("```cypher"):
119
- cypher_code = cypher_code[len("```cypher"):].strip()
120
- elif cypher_code.startswith("```"):
121
- cypher_code = cypher_code[len("```"):].strip()
122
- if cypher_code.endswith("```"):
123
- cypher_code = cypher_code[:-3].strip()
124
-
125
- return cypher_code
126
-
127
- #generating embeddings (run only once)
128
- def chunk_text(text, chunk_size=1):
129
- sentences = text.split("; ")
130
- return ["; ".join(sentences[i:i+chunk_size]) for i in range(0, len(sentences), chunk_size)]
131
-
132
- def get_gemini_embeddings(text_list):
133
- embeddings = []
134
- print("Generating embeddings with Gemini...")
135
- for text in text_list:
136
- response = genai.embed_content(
137
- model=model_name,
138
- content=text,
139
- task_type="RETRIEVAL_DOCUMENT")
140
- embeddings.append(np.array(response["embedding"], dtype=np.float32))
141
- return np.vstack(embeddings)
142
-
143
- # Chunk all card descriptions
144
- chunk_texts = []
145
- chunk_name_mapping = {}
146
- for card_idx, (card_name, desc) in enumerate(card_descriptions.items()):
147
- chunks = chunk_text(desc)
148
- for chunk in chunks:
149
- chunk_index = len(chunk_texts)
150
- chunk_texts.append(chunk)
151
- chunk_name_mapping[chunk_index] = card_name
152
-
153
- genai.configure(api_key=os.environ.get("api_key_2"))
154
- model_name = "models/text-embedding-004"
155
-
156
- #Generating embeddings
157
- chunk_embeddings = get_gemini_embeddings(chunk_texts)
158
- faiss.normalize_L2(chunk_embeddings)
159
-
160
- print(f"Prepared {len(chunk_texts)} total chunks and embeddings.")
161
-
162
- #eligibility filter
163
- def eligibility_filter(cards, user_income, user_cibil, user_age,min_joining_fee, max_joining_fee,
164
- min_annual_fee, max_annual_fee):
165
- eligible_cards = []
166
- print("inside filter")
167
- for card_name in cards:
168
- # print(eligibility_df.columns)
169
-
170
- eligibility = eligibility_df[eligibility_df["Name"] == card_name]
171
-
172
- if not eligibility.empty:
173
- min_income = eligibility.iloc[0]["Minimum Income (LPA)"]
174
- min_cibil = eligibility.iloc[0]["Minimum Credit Score"]
175
- min_age = eligibility.iloc[0]["Minimum Age"]
176
- max_age = eligibility.iloc[0]["Maximum Age"]
177
- joining_fee=eligibility.iloc[0]["Joining fee"]
178
- annual_fee=eligibility.iloc[0]["Annual fee"]
179
- if (user_income >= min_income and
180
- user_cibil >= min_cibil and
181
- min_age <= user_age <= max_age and
182
- min_joining_fee<=joining_fee<=max_joining_fee and
183
- min_annual_fee<=annual_fee<=max_annual_fee):
184
- eligible_cards.append(card_name)
185
-
186
- return eligible_cards
187
-
188
-
189
-
190
- #function for retrieving cards from knowledge graph
191
- class Neo4jConnectionError(Exception):
192
- pass
193
-
194
- def run_cypher_query(user_query, query, use_eligibility, user_income, user_cibil, user_age,
195
- min_joining_fee, max_joining_fee, min_annual_fee, max_annual_fee):
196
-
197
- try:
198
- with driver.session() as session:
199
-
200
- result = session.run(query)
201
- matched_cards = [record["c"] for record in result]
202
- filtered_cards = [card["name"] for card in matched_cards]
203
-
204
- except Exception as e:
205
- raise Neo4jConnectionError("Failed to connect to the Neo4j database.") from e
206
-
207
- if use_eligibility:
208
- filtered_cards = eligibility_filter(filtered_cards, user_income, user_cibil, user_age,
209
- min_joining_fee, max_joining_fee,
210
- min_annual_fee, max_annual_fee)
211
-
212
- # for card in filtered_cards:
213
- # print("error")
214
- # print(card["name"])
215
- relevant_indexes = [i for i, name in chunk_name_mapping.items() if name in filtered_cards]
216
- filtered_embeddings = chunk_embeddings[relevant_indexes]
217
- filtered_texts = [chunk_texts[i] for i in relevant_indexes]
218
- filtered_mapping = {i: chunk_name_mapping[idx] for i, idx in enumerate(relevant_indexes)}
219
-
220
- # Build FAISS index
221
- dim = filtered_embeddings.shape[1]
222
- faiss_index = faiss.IndexFlatIP(dim)
223
- faiss_index.add(filtered_embeddings)
224
-
225
- print(f"FAISS index created with {len(filtered_embeddings)} filtered chunks.")
 
 
 
 
 
 
226
  return faiss_index, filtered_mapping
 
1
+ import google.generativeai as genai
2
+ from neo4j import GraphDatabase
3
+ import os
4
+ import numpy as np
5
+ import faiss
6
+ from data import card_descriptions,eligibility_df
7
+
8
+ #neo4j credentials
9
+ NEO4J_URI = os.environ.get("NEO4J_URI")
10
+ NEO4J_USER = os.environ.get("NEO4J_USER")
11
+ NEO4J_PASS = os.environ.get("NEO4J_PASS")
12
+ _driver = None
13
+
14
+ def get_driver():
15
+ global _driver
16
+ if _driver is None:
17
+ _driver = GraphDatabase.driver(NEO4J_URI, auth=(NEO4J_USER, NEO4J_PASS))
18
+ return _driver
19
+
20
+ #generating cypher query
21
+ def generate_cypher(user_query, query_intent, include_cobranded):
22
+ genai.configure(api_key='AIzaSyAHoi9xbYAThtjXlyF_IKFtruoWYoUCjJQ')
23
+ model3 = genai.GenerativeModel('gemini-1.5-flash-latest')
24
+ print("inside cypher query gen")
25
+
26
+ context_note = f"""
27
+ Contextual Flags:
28
+ - FD Card intent: {query_intent}
29
+ - Include co-branded cards: {include_cobranded}
30
+ """
31
+
32
+ cypher_prompt = f"""
33
+ You are an expert Neo4j Cypher query generator.
34
+
35
+ Given a user’s question, graph schema, and **contextual flags**, generate the correct Cypher query. The query should return only the cards `c`.
36
+
37
+ ONLY output the Cypher query. Do NOT explain anything.
38
+
39
+ ---
40
+
41
+ Graph Schema:
42
+ - Nodes:
43
+ - (Card): Properties = name, bank_name, card_type, premium, co_branded
44
+ - (Feature): Properties = name
45
+ - Relationships:
46
+ - (Card)-[:HAS_FEATURE]->(Feature)
47
+
48
+ Feature Inclusion Rules:
49
+ - Only include relevant features based on user query.
50
+ - Forex markup fee and foreign transaction fee are the same.
51
+ - If FD Card intent is true then include the features if the query contains any and also include β€œGeneral Cashback” or β€œGeneral Reward Points”
52
+ - Don’t add β€œGeneral Cashback” or β€œGeneral Reward Points” if it is not required.
53
+ - If fuel is mentioned, include both `Fuel Benefits` and `Fuel Surcharge Waiver`.
54
+ - **ALWAYS** match features using: `f.name IN [...]` β€” even if there is only **one** feature.
55
+
56
+
57
+ Valid values:
58
+ - card_type: 'FD Card' or 'Regular'
59
+ - premium: true (no concept of false β€” just include it if applicable)
60
+ - co_branded: true (no concept of false β€” just include it if applicable)
61
+
62
+ MANDATORY Condition Rules:
63
+ - If FD Card intent is true β†’ include: `c.card_type = 'FD Card'`
64
+ - Else β†’ include: `c.card_type = 'Regular'`
65
+ - If the query is based on beginners or students or people with no or low credit history then use FD Card.
66
+ - If the query uses words like "premium", "elite", "luxury", "exclusive", "infinia", "black", etc. β†’ include: `AND c.premium = true`
67
+ - If the query includes low spending, without high spending or budget β†’ include: `(c.premium IS NULL OR c.premium = false)`
68
+ - If include co-branded is false β†’ include: `AND (c.co_branded IS NULL OR c.co_branded = false)`
69
+ - Use exact values for `bank_name` as in the database: ["SBI", "HDFC", "Axis", "ICICI", "YES", "HSBC", "IDFC", "American Express", "SMB", "Federal Bank", "AU Bank", "IDBI", "Kotak Mahindra Bank","IndusInd","RBL"]
70
+ - Do not add bank after the name of the bank if it is not mentioned in the datase list.
71
+ - These conditions are **MANDATORY**. If they apply, include them in the `WHERE` clause. Do not skip them.
72
+
73
+ ---
74
+
75
+ Available features:
76
+ "General Cashback", "Fuel Surcharge Waiver", "Fuel Benefits", "Welcome Bonus",
77
+ "Airport Lounge Access", "General Reward Points", "Domestic Travel Benefits",
78
+ "Movie Benefits", "Flight Discounts", "International Travel Benefits",
79
+ "Hotel Benefits", "Dining Benefits", "Daily Spends (Grocery)", "Railway Benefits",
80
+ "Travel Benefits", "Railway Lounge", "Insurance", "Utility",
81
+ "E-commerce Platform Benefits", "Air Miles", "Spa Access Benefits",
82
+ "Lifestyle & Luxury Perks", "Golf Access & Perks", "Online Shopping Benefits",
83
+ "UPI Transaction Support", "Health Benefits", "EMI Conversion Options",
84
+ "No Forex Markup Fee", "Roadside Assistance", "Rupay Network Support",Super Premium Cards
85
+
86
+ ---
87
+
88
+ Few-shot Examples:
89
+
90
+ User Query: Show premium cards with airport lounge access
91
+ Cypher:
92
+ MATCH (c:Card)-[:HAS_FEATURE]->(f:Feature)
93
+ WHERE f.name IN ["Airport Lounge Access"]
94
+ AND c.card_type = 'Regular'
95
+ AND c.premium = true
96
+ RETURN c
97
+
98
+ User Query: I want FD cards with spa access and golf perks
99
+ Cypher:
100
+ MATCH (c:Card)-[:HAS_FEATURE]->(f:Feature)
101
+ WHERE f.name IN ["Spa Access Benefits", "Golf Access & Perks"]
102
+ AND c.card_type = 'FD Card'
103
+ RETURN c
104
+
105
+ User Query: Cards that support UPI but are not co-branded
106
+ Cypher:
107
+ MATCH (c:Card)-[:HAS_FEATURE]->(f:Feature)
108
+ WHERE f.name IN ["UPI Transaction Support"]
109
+ AND c.card_type = 'Regular'
110
+ AND (c.co_branded IS NULL OR c.co_branded = false)
111
+ RETURN c
112
+
113
+ ---
114
+
115
+ {context_note}
116
+
117
+ User Query: {user_query}
118
+ Cypher:
119
+ """
120
+
121
+ response = model3.generate_content(cypher_prompt.strip())
122
+ cypher_code = response.text.strip()
123
+
124
+ if cypher_code.startswith("```cypher"):
125
+ cypher_code = cypher_code[len("```cypher"):].strip()
126
+ elif cypher_code.startswith("```"):
127
+ cypher_code = cypher_code[len("```"):].strip()
128
+ if cypher_code.endswith("```"):
129
+ cypher_code = cypher_code[:-3].strip()
130
+
131
+ return cypher_code
132
+
133
+ #generating embeddings (run only once)
134
+ def chunk_text(text, chunk_size=1):
135
+ sentences = text.split("; ")
136
+ return ["; ".join(sentences[i:i+chunk_size]) for i in range(0, len(sentences), chunk_size)]
137
+
138
+ def get_gemini_embeddings(text_list):
139
+ embeddings = []
140
+ print("Generating embeddings with Gemini...")
141
+ for text in text_list:
142
+ response = genai.embed_content(
143
+ model=model_name,
144
+ content=text,
145
+ task_type="RETRIEVAL_DOCUMENT")
146
+ embeddings.append(np.array(response["embedding"], dtype=np.float32))
147
+ return np.vstack(embeddings)
148
+
149
+ # Chunk all card descriptions
150
+ chunk_texts = []
151
+ chunk_name_mapping = {}
152
+ for card_idx, (card_name, desc) in enumerate(card_descriptions.items()):
153
+ chunks = chunk_text(desc)
154
+ for chunk in chunks:
155
+ chunk_index = len(chunk_texts)
156
+ chunk_texts.append(chunk)
157
+ chunk_name_mapping[chunk_index] = card_name
158
+
159
+ genai.configure(api_key=os.environ.get("api_key_2"))
160
+ model_name = "models/text-embedding-004"
161
+
162
+ #Generating embeddings
163
+ chunk_embeddings = get_gemini_embeddings(chunk_texts)
164
+ faiss.normalize_L2(chunk_embeddings)
165
+
166
+ print(f"Prepared {len(chunk_texts)} total chunks and embeddings.")
167
+
168
+ #eligibility filter
169
+ def eligibility_filter(cards, user_income, user_cibil, user_age,min_joining_fee, max_joining_fee,
170
+ min_annual_fee, max_annual_fee):
171
+ eligible_cards = []
172
+ print("inside filter")
173
+ for card_name in cards:
174
+ # print(eligibility_df.columns)
175
+
176
+ eligibility = eligibility_df[eligibility_df["Name"] == card_name]
177
+
178
+ if not eligibility.empty:
179
+ min_income = eligibility.iloc[0]["Minimum Income (LPA)"]
180
+ min_cibil = eligibility.iloc[0]["Minimum Credit Score"]
181
+ min_age = eligibility.iloc[0]["Minimum Age"]
182
+ max_age = eligibility.iloc[0]["Maximum Age"]
183
+ joining_fee=eligibility.iloc[0]["Joining fee"]
184
+ annual_fee=eligibility.iloc[0]["Annual fee"]
185
+ if (user_income >= min_income and
186
+ user_cibil >= min_cibil and
187
+ min_age <= user_age <= max_age and
188
+ min_joining_fee<=joining_fee<=max_joining_fee and
189
+ min_annual_fee<=annual_fee<=max_annual_fee):
190
+ eligible_cards.append(card_name)
191
+
192
+ return eligible_cards
193
+
194
+
195
+
196
+ #function for retrieving cards from knowledge graph
197
+ class Neo4jConnectionError(Exception):
198
+ pass
199
+
200
+ def run_cypher_query(user_query, query, use_eligibility, user_income, user_cibil, user_age,
201
+ min_joining_fee, max_joining_fee, min_annual_fee, max_annual_fee):
202
+
203
+ try:
204
+ with get_driver().session() as session:
205
+
206
+ result = session.run(query)
207
+ matched_cards = [record["c"] for record in result]
208
+ filtered_cards = [card["name"] for card in matched_cards]
209
+
210
+ except Exception as e:
211
+ raise Neo4jConnectionError("Failed to connect to the Neo4j database.") from e
212
+
213
+ if use_eligibility:
214
+ filtered_cards = eligibility_filter(filtered_cards, user_income, user_cibil, user_age,
215
+ min_joining_fee, max_joining_fee,
216
+ min_annual_fee, max_annual_fee)
217
+
218
+ # for card in filtered_cards:
219
+ # print("error")
220
+ # print(card["name"])
221
+ relevant_indexes = [i for i, name in chunk_name_mapping.items() if name in filtered_cards]
222
+ filtered_embeddings = chunk_embeddings[relevant_indexes]
223
+ filtered_texts = [chunk_texts[i] for i in relevant_indexes]
224
+ filtered_mapping = {i: chunk_name_mapping[idx] for i, idx in enumerate(relevant_indexes)}
225
+
226
+ # Build FAISS index
227
+ dim = filtered_embeddings.shape[1]
228
+ faiss_index = faiss.IndexFlatIP(dim)
229
+ faiss_index.add(filtered_embeddings)
230
+
231
+ print(f"FAISS index created with {len(filtered_embeddings)} filtered chunks.")
232
  return faiss_index, filtered_mapping