Spaces:
Runtime error
Runtime error
Update recommender/graph_retrieval.py
Browse files- recommender/graph_retrieval.py +121 -100
recommender/graph_retrieval.py
CHANGED
|
@@ -57,109 +57,130 @@ class Neo4jRetrievalTool(BaseTool):
|
|
| 57 |
description: str = "Runs Cypher and builds FAISS on filtered cards."
|
| 58 |
|
| 59 |
def generate_cypher(self, user_query: str, query_intent: bool, include_cobranded: bool) -> str:
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
"""
|
| 70 |
-
prompt = """
|
| 71 |
-
You are an expert Neo4j Cypher query generator.
|
| 72 |
-
|
| 73 |
-
Given a userβs question, graph schema, and **contextual flags**, generate the correct Cypher query. The query should return only the card names`c.name as name`.
|
| 74 |
-
|
| 75 |
-
ONLY output the Cypher query. Do NOT explain anything.
|
| 76 |
-
|
| 77 |
-
---
|
| 78 |
-
|
| 79 |
-
Graph Schema:
|
| 80 |
-
- Nodes:
|
| 81 |
-
- (Card): Properties = name, bank_name, card_type, premium, co_branded
|
| 82 |
-
- (Feature): Properties = name
|
| 83 |
-
- Relationships:
|
| 84 |
-
- (Card)-[:HAS_FEATURE]->(Feature)
|
| 85 |
-
|
| 86 |
-
Feature Inclusion Rules:
|
| 87 |
-
- Only include relevant features based on user query.
|
| 88 |
-
- Forex markup fee and foreign transaction fee are the same.
|
| 89 |
-
- If FD Card intent is true then include the features if the query contains any and also include βGeneral Cashbackβ or βGeneral Reward Pointsβ
|
| 90 |
-
- Donβt add βGeneral Cashbackβ or βGeneral Reward Pointsβ if it is not required.
|
| 91 |
-
- If fuel is mentioned, include both `Fuel Benefits` and `Fuel Surcharge Waiver`.
|
| 92 |
-
- **ALWAYS** match features using: `f.name IN [...]` β even if there is only **one** feature.
|
| 93 |
-
|
| 94 |
-
Valid values:
|
| 95 |
-
- card_type: 'FD Card' or 'Regular'
|
| 96 |
-
- premium: true (no concept of false β just include it if applicable)
|
| 97 |
-
- co_branded: true (no concept of false β just include it if applicable)
|
| 98 |
-
|
| 99 |
-
MANDATORY Condition Rules:
|
| 100 |
-
- If FD Card intent is true β include: `c.card_type = 'FD Card'`
|
| 101 |
-
- Else β include: `c.card_type = 'Regular'`
|
| 102 |
-
- If the query is based on beginners or students or people with no or low credit history then use FD Card.
|
| 103 |
-
- If the query uses words like "premium", "elite", "luxury", "exclusive", "infinia", "black", etc. β include: `AND c.premium = true`
|
| 104 |
-
- If the query includes low spending, without high spending or budget β include: `(c.premium IS NULL OR c.premium = false)`
|
| 105 |
-
- If include co-branded is false β include: `AND (c.co_branded IS NULL OR c.co_branded = false)`
|
| 106 |
-
- 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"]
|
| 107 |
-
|
| 108 |
-
---
|
| 109 |
-
|
| 110 |
-
Available features:
|
| 111 |
-
'Fuel Surcharge Waiver','Insurance','Shopping Benefits','Airport Lounge Access','Co-Branded',
|
| 112 |
-
'Daily Spends (Grocery)','Dining Benefits','Domestic Travel Benefits','Entertainment',
|
| 113 |
-
'General Reward Points','Movie Benefits','Rupay Network Support','Student',
|
| 114 |
-
'UPI Transaction Support','Welcome Bonus','International Travel Benefits','premium',
|
| 115 |
-
'Flight Discounts','Hotel Benefits','Travel Benefits','Railway Benefits','Railway Lounge',
|
| 116 |
-
'Utility','Beginners (Entry Level)','E-commerce Platform Benefits','Air Miles',
|
| 117 |
-
'Jewellery Spends','Concierge Services','Food Delivery Benefits','Lifestyle & Luxury Perks',
|
| 118 |
-
'Spa Access Benefits','Golf Access & Perks','Super Premium','Frequent Flyer Benefits',
|
| 119 |
-
'Health Benefits','Rent Payment Benefits','Education','Lifetime Free','Roadside Assistance',
|
| 120 |
-
'EMI Conversion Options','No Forex Markup Fee','Secured FD Based','Cashback','Fuel Benefits','Business'
|
| 121 |
-
---
|
| 122 |
-
Few-shot Examples:
|
| 123 |
-
|
| 124 |
-
User Query: Show premium cards with airport lounge access
|
| 125 |
-
Cypher:
|
| 126 |
-
MATCH (c:Card)-[:HAS_FEATURE]->(f:Feature)
|
| 127 |
-
WHERE f.name IN ["Airport Lounge Access"]
|
| 128 |
-
AND c.card_type = 'Regular'
|
| 129 |
-
AND c.premium = true
|
| 130 |
-
RETURN DISTINCT c.name AS name
|
| 131 |
-
|
| 132 |
-
User Query: I want FD cards with spa access and golf perks
|
| 133 |
-
Cypher:
|
| 134 |
-
MATCH (c:Card)-[:HAS_FEATURE]->(f:Feature)
|
| 135 |
-
WHERE f.name IN ["Spa Access Benefits", "Golf Access & Perks"]
|
| 136 |
-
AND c.card_type = 'FD Card'
|
| 137 |
-
RETURN DISTINCT c.name AS name
|
| 138 |
-
|
| 139 |
-
User Query: Cards that support UPI but are not co-branded
|
| 140 |
-
Cypher:
|
| 141 |
-
MATCH (c:Card)-[:HAS_FEATURE]->(f:Feature)
|
| 142 |
-
WHERE f.name IN ["UPI Transaction Support"]
|
| 143 |
-
AND c.card_type = 'Regular'
|
| 144 |
-
AND (c.co_branded IS NULL OR c.co_branded = false)
|
| 145 |
-
RETURN DISTINCT c.name AS name
|
| 146 |
-
|
| 147 |
-
---
|
| 148 |
-
|
| 149 |
-
{context_flags}
|
| 150 |
-
|
| 151 |
-
User Query: {user_query}
|
| 152 |
-
Cypher:
|
| 153 |
-
"""
|
| 154 |
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
|
| 164 |
def _run(
|
| 165 |
self,
|
|
|
|
| 57 |
description: str = "Runs Cypher and builds FAISS on filtered cards."
|
| 58 |
|
| 59 |
def generate_cypher(self, user_query: str, query_intent: bool, include_cobranded: bool) -> str:
|
| 60 |
+
debug_print("TOOL", f"generate_cypher called with query: '{user_query}'")
|
| 61 |
+
debug_print("TOOL", f"Parameters: query_intent={query_intent}, include_cobranded={include_cobranded}")
|
| 62 |
+
|
| 63 |
+
model = genai.GenerativeModel("gemini-2.0-flash")
|
| 64 |
+
|
| 65 |
+
context_flags = f"""
|
| 66 |
+
Contextual Flags:
|
| 67 |
+
- FD Card intent: {query_intent}
|
| 68 |
+
- Include co-branded cards: {include_cobranded}
|
| 69 |
+
"""
|
| 70 |
+
prompt = """
|
| 71 |
+
You are an expert Neo4j Cypher query generator.
|
| 72 |
+
|
| 73 |
+
Given a userβs question, graph schema, and **contextual flags**, generate the correct Cypher query. The query should return only the card names`c.name as name`.
|
| 74 |
+
|
| 75 |
+
ONLY output the Cypher query. Do NOT explain anything.
|
| 76 |
+
|
| 77 |
+
---
|
| 78 |
+
|
| 79 |
+
Graph Schema:
|
| 80 |
+
- Nodes:
|
| 81 |
+
- (Card): Properties = name, bank_name, card_type, premium, co_branded
|
| 82 |
+
- (Feature): Properties = name
|
| 83 |
+
- (Partner): Properties = name
|
| 84 |
+
- Relationships:
|
| 85 |
+
- (Card)-[:HAS_FEATURE]->(Feature)
|
| 86 |
+
- (Card)-[:PARTNER_WITH]->(Partner)
|
| 87 |
+
|
| 88 |
+
Feature Inclusion Rules:
|
| 89 |
+
- Only include relevant features based on user query.
|
| 90 |
+
- Forex markup fee and foreign transaction fee are the same.
|
| 91 |
+
- If FD Card intent is true then include the features if the query contains any and also include βGeneral Cashbackβ or βGeneral Reward Pointsβ
|
| 92 |
+
- Donβt add βGeneral Cashbackβ or βGeneral Reward Pointsβ if it is not required.
|
| 93 |
+
- If fuel is mentioned, include both `Fuel Benefits` and `Fuel Surcharge Waiver`.
|
| 94 |
+
- **ALWAYS** match features using: `f.name IN [...]` β even if there is only **one** feature.
|
| 95 |
+
|
| 96 |
+
Partner Inclusion Rules:
|
| 97 |
+
- If the user query mentions any available partner brand names, include:
|
| 98 |
+
MATCH (c)-[:PARTNER_WITH]->(p:Partner)
|
| 99 |
+
AND p.name IN [<matched partner names>]
|
| 100 |
+
- Always use `p.name IN [...]` β even if there is only one partner.
|
| 101 |
+
|
| 102 |
+
Valid values:
|
| 103 |
+
- card_type: 'FD Card' or 'Regular'
|
| 104 |
+
- premium: true (no concept of false β just include it if applicable)
|
| 105 |
+
- co_branded: true (no concept of false β just include it if applicable)
|
| 106 |
+
|
| 107 |
+
MANDATORY Condition Rules:
|
| 108 |
+
- If FD Card intent is true β include: `c.card_type = 'FD Card'`
|
| 109 |
+
- Else β include: `c.card_type = 'Regular'`
|
| 110 |
+
- If the query is based on beginners or students or people with no or low credit history then use FD Card.
|
| 111 |
+
- If the query uses words like "premium", "elite", "luxury", "exclusive", "infinia", "black", etc. β include: `AND c.premium = true`
|
| 112 |
+
- If the query includes low spending, without high spending or budget β include: `(c.premium IS NULL OR c.premium = false)`
|
| 113 |
+
- If include co-branded is false β include: `AND (c.co_branded IS NULL OR c.co_branded = false)`
|
| 114 |
+
- 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"]
|
| 115 |
+
|
| 116 |
+
---
|
| 117 |
+
|
| 118 |
+
Available features:
|
| 119 |
+
'Fuel Surcharge Waiver','Insurance','Shopping Benefits','Airport Lounge Access','Co-Branded',
|
| 120 |
+
'Daily Spends (Grocery)','Dining Benefits','Domestic Travel Benefits','Entertainment',
|
| 121 |
+
'General Reward Points','Movie Benefits','Rupay Network Support','Student',
|
| 122 |
+
'UPI Transaction Support','Welcome Bonus','International Travel Benefits','premium',
|
| 123 |
+
'Flight Discounts','Hotel Benefits','Travel Benefits','Railway Benefits','Railway Lounge',
|
| 124 |
+
'Utility','Beginners (Entry Level)','E-commerce Platform Benefits','Air Miles',
|
| 125 |
+
'Jewellery Spends','Concierge Services','Food Delivery Benefits','Lifestyle & Luxury Perks',
|
| 126 |
+
'Spa Access Benefits','Golf Access & Perks','Super Premium','Frequent Flyer Benefits',
|
| 127 |
+
'Health Benefits','Rent Payment Benefits','Education','Lifetime Free','Roadside Assistance',
|
| 128 |
+
'EMI Conversion Options','No Forex Markup Fee','Secured FD Based','Cashback','Fuel Benefits','Business'
|
| 129 |
+
|
| 130 |
+
Available partners:
|
| 131 |
+
"Marriott Bonvoy", "Accor", "Taj", "ITC", "The Postcard", "Indigo", "United Airlines", "Emirates", "Etihad", "Club Vistara", "Air India", "Turkish airlines"
|
| 132 |
+
|
| 133 |
+
---
|
| 134 |
+
Few-shot Examples:
|
| 135 |
+
|
| 136 |
+
User Query: Show premium cards with airport lounge access
|
| 137 |
+
Cypher:
|
| 138 |
+
MATCH (c:Card)-[:HAS_FEATURE]->(f:Feature)
|
| 139 |
+
WHERE f.name IN ["Airport Lounge Access"]
|
| 140 |
+
AND c.card_type = 'Regular'
|
| 141 |
+
AND c.premium = true
|
| 142 |
+
RETURN DISTINCT c.name AS name
|
| 143 |
+
|
| 144 |
+
User Query: I want FD cards with spa access and golf perks
|
| 145 |
+
Cypher:
|
| 146 |
+
MATCH (c:Card)-[:HAS_FEATURE]->(f:Feature)
|
| 147 |
+
WHERE f.name IN ["Spa Access Benefits", "Golf Access & Perks"]
|
| 148 |
+
AND c.card_type = 'FD Card'
|
| 149 |
+
RETURN DISTINCT c.name AS name
|
| 150 |
+
|
| 151 |
+
User Query: Cards that support UPI but are not co-branded
|
| 152 |
+
Cypher:
|
| 153 |
+
MATCH (c:Card)-[:HAS_FEATURE]->(f:Feature)
|
| 154 |
+
WHERE f.name IN ["UPI Transaction Support"]
|
| 155 |
+
AND c.card_type = 'Regular'
|
| 156 |
+
AND (c.co_branded IS NULL OR c.co_branded = false)
|
| 157 |
+
RETURN DISTINCT c.name AS name
|
| 158 |
|
| 159 |
+
User Query: Cards partnered with Indigo and Vistara that offer flight benefits
|
| 160 |
+
Cypher:
|
| 161 |
+
MATCH (c:Card)-[:PARTNER_WITH]->(p:Partner)
|
| 162 |
+
MATCH (c)-[:HAS_FEATURE]->(f:Feature)
|
| 163 |
+
WHERE f.name IN ["Flight Discounts"]
|
| 164 |
+
AND p.name IN ["Indigo", "Vistara"]
|
| 165 |
+
AND c.card_type = 'Regular'
|
| 166 |
+
RETURN DISTINCT c.name AS name
|
| 167 |
+
|
| 168 |
+
---
|
| 169 |
+
|
| 170 |
+
{context_flags}
|
| 171 |
+
|
| 172 |
+
User Query: {user_query}
|
| 173 |
+
Cypher:
|
| 174 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
|
| 176 |
+
cypher_prompt = prompt.format(context_flags=context_flags, user_query=user_query)
|
| 177 |
+
debug_print("TOOL", f"Calling Gemini to generate Cypher query, prompt length: {len(cypher_prompt)}")
|
| 178 |
+
|
| 179 |
+
cypher_code = model.generate_content(cypher_prompt).text.strip()
|
| 180 |
+
cleaned_cypher = cypher_code.strip("`").replace("cypher", "").strip()
|
| 181 |
+
|
| 182 |
+
debug_print("TOOL", f"Generated Cypher query: {cleaned_cypher}")
|
| 183 |
+
return cleaned_cypher
|
| 184 |
|
| 185 |
def _run(
|
| 186 |
self,
|