Upload 10 files
Browse files- agents/chat.py +68 -0
- agents/compare.py +37 -0
- app.py +3 -0
- data.py +39 -0
- intent_classification/fd_classification.py +21 -0
- intent_classification/retrieval_classification.py +80 -0
- recommender/graph_retrieval_vectordb.py +226 -0
- recommender/recommender.py +154 -0
- recommender/retrieval_ranking.py +215 -0
- ui/gradio_interface.py +160 -0
agents/chat.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import google.generativeai as genai
|
| 3 |
+
import os
|
| 4 |
+
from data import eligibility_df
|
| 5 |
+
|
| 6 |
+
#function for the chatbot functionality
|
| 7 |
+
eligibility_lookup = {}
|
| 8 |
+
for _, row in eligibility_df.iterrows():
|
| 9 |
+
card_name = row["Name"].strip()
|
| 10 |
+
eligibility_info = f"""
|
| 11 |
+
- Bank: {row['Bank']}
|
| 12 |
+
- Age: {row['Minimum Age']} to {row['Maximum Age']}
|
| 13 |
+
- Minimum Income: {row['Minimum Income (LPA)']} LPA
|
| 14 |
+
- Minimum Credit Score: {row['Minimum Credit Score']}
|
| 15 |
+
- Joining Fee: ₹{row['Joining fee']}
|
| 16 |
+
- Annual Fee: ₹{row['Annual fee']}
|
| 17 |
+
"""
|
| 18 |
+
eligibility_lookup[card_name] = eligibility_info.strip()
|
| 19 |
+
|
| 20 |
+
# Function to handle chat interaction with Gemini
|
| 21 |
+
def chat_with_gemini(user_query, user_message, chat_history, card_lookup):
|
| 22 |
+
genai.configure(api_key=os.environ.get("api_key_4"))
|
| 23 |
+
model4 = genai.GenerativeModel('gemini-1.5-flash-latest')
|
| 24 |
+
context = ""
|
| 25 |
+
for name, desc in list(card_lookup.items())[:5]:
|
| 26 |
+
eligibility_info = eligibility_lookup.get(name, "No eligibility or fee information available.")
|
| 27 |
+
full_desc = f"{desc}\n\nEligibility & Fees:\n{eligibility_info}"
|
| 28 |
+
context += f"{name}:\n{full_desc}\n\n"
|
| 29 |
+
recent_user_messages = [
|
| 30 |
+
msg["content"] for msg in chat_history if msg["role"] == "user"
|
| 31 |
+
][-5:]
|
| 32 |
+
|
| 33 |
+
conversation = f"""
|
| 34 |
+
You are a helpful financial assistant. A user has already shared their overall credit card preferences.
|
| 35 |
+
|
| 36 |
+
### User’s Requirements:
|
| 37 |
+
{user_query}
|
| 38 |
+
|
| 39 |
+
### Credit Card Options:
|
| 40 |
+
{context}
|
| 41 |
+
|
| 42 |
+
### User's Follow-up Question:
|
| 43 |
+
{user_message}
|
| 44 |
+
|
| 45 |
+
### Recent User Messages:
|
| 46 |
+
{recent_user_messages}
|
| 47 |
+
|
| 48 |
+
### Instructions:
|
| 49 |
+
1. Answer the user's current question clearly and concisely.
|
| 50 |
+
2. Always consider the user's overall requirements above.
|
| 51 |
+
3. Use only the card descriptions provided. Do not assume or invent any card benefits.
|
| 52 |
+
4. If the user asks which card is best or suitable for their needs, use the user’s requirements above to select and explain.
|
| 53 |
+
5. Don't ask the user to restate their requirements — they're already provided above.
|
| 54 |
+
"""
|
| 55 |
+
# print(conversation)
|
| 56 |
+
try:
|
| 57 |
+
response = model4.generate_content(conversation)
|
| 58 |
+
gemini_response = response.text
|
| 59 |
+
|
| 60 |
+
chat_history.append({"role": "user", "content": user_message})
|
| 61 |
+
chat_history.append({"role": "assistant", "content": gemini_response})
|
| 62 |
+
|
| 63 |
+
except Exception as e:
|
| 64 |
+
error_msg = "Error: Unable to retrieve response from Gemini. Please try again later."
|
| 65 |
+
chat_history.append({"role": "user", "content": user_message})
|
| 66 |
+
chat_history.append({"role": "assistant", "content": error_msg})
|
| 67 |
+
|
| 68 |
+
return chat_history, chat_history
|
agents/compare.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import google.generativeai as genai
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
#function to compare cards
|
| 6 |
+
def compare_selected_cards(selected_names, card_lookup):
|
| 7 |
+
genai.configure(api_key=os.environ.get("api_key_4"))
|
| 8 |
+
model4 = genai.GenerativeModel('gemini-1.5-flash-latest')
|
| 9 |
+
print(selected_names)
|
| 10 |
+
if not selected_names or len(selected_names) < 2:
|
| 11 |
+
return "<b style='color:red;'>Please select at least two cards to compare.</b>"
|
| 12 |
+
|
| 13 |
+
comparison_data = "\n\n".join([
|
| 14 |
+
f"{name}: {card_lookup.get(name)}" for name in selected_names
|
| 15 |
+
])
|
| 16 |
+
prompt = (
|
| 17 |
+
f"Do not include extra symbols like (* or #), just generate a textual response maybe with numbers for points."
|
| 18 |
+
f"Compare the following credit cards based on their benefits. "
|
| 19 |
+
f"Keep the output short, structured, and very readable:\n\n"
|
| 20 |
+
f"{comparison_data}\n\n"
|
| 21 |
+
f"Use markdown format with clear section headers like 'Comparison' and 'Recommendation'. "
|
| 22 |
+
f"Present key differences as bullet points without using '*' symbols — use '-' instead. "
|
| 23 |
+
f"Make it crisp and avoid lengthy explanations. "
|
| 24 |
+
f"Conclude with a recommendation on which card suits which type of user."
|
| 25 |
+
f"DO NOT INCLUDE ANY SYMBOLS LIKE # OR *"
|
| 26 |
+
)
|
| 27 |
+
print("comparing")
|
| 28 |
+
try:
|
| 29 |
+
response = model4.generate_content(prompt)
|
| 30 |
+
return f"""
|
| 31 |
+
<div style='background-color: #f9fbe7; padding: 15px; border-radius: 10px; font-family: sans-serif;'>
|
| 32 |
+
<pre style='white-space: pre-wrap; font-size: 13px; color: #333;'>{response.text}</pre>
|
| 33 |
+
</div>
|
| 34 |
+
"""
|
| 35 |
+
except Exception as e:
|
| 36 |
+
print("Comparison Error:", e)
|
| 37 |
+
return "<b style='color:red;'>Something went wrong while comparing. Please try again.</b>"
|
app.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from ui.gradio_interface import demo
|
| 2 |
+
|
| 3 |
+
demo.launch(share=True)
|
data.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import pandas as pd
|
| 3 |
+
|
| 4 |
+
#for adding bank name to the cards in the graph
|
| 5 |
+
eligibility_df = pd.read_csv("cards_eligibility_updated.csv")
|
| 6 |
+
card_to_bank = dict(zip(eligibility_df['Name'], eligibility_df['Bank']))
|
| 7 |
+
|
| 8 |
+
# Loading credit card data
|
| 9 |
+
df = pd.read_csv("credit_card_data_updated.csv")
|
| 10 |
+
card_descriptions = dict(zip(df["name"], df["description"]))
|
| 11 |
+
|
| 12 |
+
# Loading all 55 cards for comparison feature
|
| 13 |
+
df_all_cards = pd.read_csv("credit_card_data_updated.csv")
|
| 14 |
+
all_card_names = df_all_cards["name"].tolist()
|
| 15 |
+
all_card_lookup = dict(zip(df_all_cards["name"], df_all_cards["description"]))
|
| 16 |
+
|
| 17 |
+
with open('for_graph_construction_(expanded labels).json') as f:
|
| 18 |
+
card_feature_data = json.load(f)
|
| 19 |
+
|
| 20 |
+
card_features_lookup = {
|
| 21 |
+
card['card_name']: set(card['features'])
|
| 22 |
+
for card in card_feature_data
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
#function for the chatbot functionality
|
| 27 |
+
eligibility_lookup = {}
|
| 28 |
+
for _, row in eligibility_df.iterrows():
|
| 29 |
+
card_name = row["Name"].strip()
|
| 30 |
+
eligibility_info = f"""
|
| 31 |
+
- Bank: {row['Bank']}
|
| 32 |
+
- Age: {row['Minimum Age']} to {row['Maximum Age']}
|
| 33 |
+
- Minimum Income: {row['Minimum Income (LPA)']} LPA
|
| 34 |
+
- Minimum Credit Score: {row['Minimum Credit Score']}
|
| 35 |
+
- Joining Fee: ₹{row['Joining fee']}
|
| 36 |
+
- Annual Fee: ₹{row['Annual fee']}
|
| 37 |
+
"""
|
| 38 |
+
eligibility_lookup[card_name] = eligibility_info.strip()
|
| 39 |
+
|
intent_classification/fd_classification.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import google.generativeai as genai
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
#for intent classification
|
| 5 |
+
def find_intent(user_query: str) -> bool:
|
| 6 |
+
genai.configure(api_key=os.environ.get("api_key_2"))
|
| 7 |
+
model2 = genai.GenerativeModel('gemini-1.5-flash-latest')
|
| 8 |
+
prompt = f"""
|
| 9 |
+
You are a helpful assistant. A user has asked the following question or made the following request:
|
| 10 |
+
|
| 11 |
+
"{user_query}"
|
| 12 |
+
|
| 13 |
+
Determine ONLY whether this query is likely about FD-based (fixed deposit backed) credit cards.
|
| 14 |
+
These cards typically do not require a credit score, are suited for users with low income, users who are new to credit cards/beginners, who have no/low credit score or students.
|
| 15 |
+
|
| 16 |
+
Respond with just "true" or "false" depending on whether the user's query is about such cards.
|
| 17 |
+
No explanation, no extra words — just true or false.
|
| 18 |
+
"""
|
| 19 |
+
response = model2.generate_content(prompt)
|
| 20 |
+
result = response.text.strip().lower()
|
| 21 |
+
return result == "true"
|
intent_classification/retrieval_classification.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import google.generativeai as genai
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import os
|
| 4 |
+
import json
|
| 5 |
+
from data import df_all_cards
|
| 6 |
+
|
| 7 |
+
#handling intent classification for retrieval
|
| 8 |
+
def handle_query_classification(user_query):
|
| 9 |
+
genai.configure(api_key=os.environ.get("api_key_1"))
|
| 10 |
+
model1 = genai.GenerativeModel('gemini-1.5-flash-latest')
|
| 11 |
+
prompt = f"""
|
| 12 |
+
You are a smart financial assistant.
|
| 13 |
+
|
| 14 |
+
### User's Query:
|
| 15 |
+
{user_query}
|
| 16 |
+
|
| 17 |
+
### Task:
|
| 18 |
+
Classify the user's intent into one of the following categories:
|
| 19 |
+
1. "retrieve" → If the user is asking for card suggestions, recommendations, or showing cards (e.g., "suggest a card", "need a travel card") OR if they mention their lifestyle, income, spending, or needs (e.g., travel, shopping, fuel, rewards, luxury).
|
| 20 |
+
2. "specific" → If the user is asking about a particular credit card by name (even if the word "card" is not used). Examples: "Tell me about HDFC Regalia", "Is SBI Elite good?".
|
| 21 |
+
3. "no_retrieval" → ONLY if the query is generic (e.g., “What is credit score?”), casual chit-chat (e.g., “Hi”), or doesn’t mention any lifestyle, financial needs, or specific card names.
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
Respond ONLY in the following JSON format:
|
| 25 |
+
If intent is "no_retrieval", you MUST include a helpful 'response' field.
|
| 26 |
+
If intent is "retrieve" or "specific", do NOT include any response or explanation.
|
| 27 |
+
|
| 28 |
+
Respond in this exact format:
|
| 29 |
+
{{
|
| 30 |
+
"intent": "retrieve" | "specific" | "no_retrieval",
|
| 31 |
+
"response": "Only include this if intent is 'no_retrieval'"
|
| 32 |
+
}}
|
| 33 |
+
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
raw_response = model1.generate_content(prompt).text.strip()
|
| 37 |
+
|
| 38 |
+
# Clean any markdown formatting if present
|
| 39 |
+
if raw_response.startswith("```"):
|
| 40 |
+
raw_response = raw_response.strip("`").strip()
|
| 41 |
+
if raw_response.startswith("json"):
|
| 42 |
+
raw_response = raw_response[len("json"):].strip()
|
| 43 |
+
|
| 44 |
+
try:
|
| 45 |
+
parsed = json.loads(raw_response)
|
| 46 |
+
return parsed
|
| 47 |
+
except Exception as e:
|
| 48 |
+
print("JSON parsing error:", e)
|
| 49 |
+
print("Raw response from LLM:", raw_response)
|
| 50 |
+
raise
|
| 51 |
+
# result = handle_query_classification("Want to optimize my spending – travel often, premium hotels, and online shopping.")
|
| 52 |
+
# if result["intent"] == "no_retrieval":
|
| 53 |
+
# print(result['response'])
|
| 54 |
+
|
| 55 |
+
#passing the card mentioned in the user query
|
| 56 |
+
def find_matching_card(user_query):
|
| 57 |
+
lowered_query = user_query.lower()
|
| 58 |
+
for _, row in df_all_cards.iterrows():
|
| 59 |
+
if row["name"].lower() in lowered_query:
|
| 60 |
+
return row.to_dict()
|
| 61 |
+
return None
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
#for queries enquiring about a card
|
| 65 |
+
def generate_card_response_with_context(user_query, card_info):
|
| 66 |
+
genai.configure(api_key=os.environ.get("api_key_1"))
|
| 67 |
+
model1 = genai.GenerativeModel('gemini-1.5-flash-latest')
|
| 68 |
+
prompt = f"""
|
| 69 |
+
You are a helpful financial assistant. A user has asked about a specific credit card.
|
| 70 |
+
|
| 71 |
+
Card Name: {card_info.get('name')}
|
| 72 |
+
Description: {card_info.get('description')}
|
| 73 |
+
|
| 74 |
+
User's Question: {user_query}
|
| 75 |
+
|
| 76 |
+
Please provide a concise, relevant answer using the above card context.
|
| 77 |
+
"""
|
| 78 |
+
response = model1.generate_content(prompt)
|
| 79 |
+
return response.text.strip()
|
| 80 |
+
|
recommender/graph_retrieval_vectordb.py
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
recommender/recommender.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import ast
|
| 2 |
+
import re
|
| 3 |
+
import pd
|
| 4 |
+
import tempfile
|
| 5 |
+
import os
|
| 6 |
+
from intent_classification.fd_classification import find_intent
|
| 7 |
+
from intent_classification.retrieval_classification import handle_query_classification,find_matching_card,generate_card_response_with_context
|
| 8 |
+
from recommender.retrieval_ranking import generate_multi_queries,convert_to_direct_query_gradio,retrieve_and_rank_cards,generate_credit_card_recommendation_gemini,cross_encoder
|
| 9 |
+
from data import eligibility_lookup,card_features_lookup
|
| 10 |
+
from recommender.graph_retrieval_vectordb import generate_cypher,run_cypher_query,Neo4jConnectionError
|
| 11 |
+
|
| 12 |
+
#function to pass the retrieved cards and generated response to the UI
|
| 13 |
+
def recommend_cards_gradio(user_query, preferences, income, cibil, age,
|
| 14 |
+
min_joining_fee, max_joining_fee,
|
| 15 |
+
min_annual_fee, max_annual_fee,
|
| 16 |
+
use_eligibility=True,include_cobranded=True):
|
| 17 |
+
try:
|
| 18 |
+
# print(user_query)
|
| 19 |
+
if(user_query):
|
| 20 |
+
result = handle_query_classification(user_query)
|
| 21 |
+
|
| 22 |
+
if result["intent"] == "no_retrieval":
|
| 23 |
+
return (
|
| 24 |
+
f"<div style='background-color:#e3f2fd;padding:20px;border-radius:10px;'>"
|
| 25 |
+
f"<pre style='white-space:pre-wrap;font-size:13px;color:#212121;'>{result['response']}</pre></div>",
|
| 26 |
+
[["No retrieval required", "Answered using LLM"]],
|
| 27 |
+
None,
|
| 28 |
+
[],
|
| 29 |
+
{},
|
| 30 |
+
"Answered without retrieval"
|
| 31 |
+
)
|
| 32 |
+
elif result["intent"] == "specific":
|
| 33 |
+
matched_card = find_matching_card(user_query)
|
| 34 |
+
if matched_card:
|
| 35 |
+
gemini_answer = generate_card_response_with_context(user_query, matched_card)
|
| 36 |
+
card_name = matched_card["name"]
|
| 37 |
+
card_desc = matched_card["description"]
|
| 38 |
+
card_lookup = {card_name: card_desc}
|
| 39 |
+
|
| 40 |
+
# Constructing eligibility info if available
|
| 41 |
+
eligibility_info = eligibility_lookup.get(card_name, "No eligibility or fee information available.")
|
| 42 |
+
chat_history_entry = f"{card_name}:\n{card_desc}\n\nEligibility & Fees:\n{eligibility_info}"
|
| 43 |
+
|
| 44 |
+
return (
|
| 45 |
+
f"<div style='background-color:#fffde7;padding:20px;border-radius:10px;'>"
|
| 46 |
+
f"<pre style='white-space:pre-wrap;font-size:13px;color:#212121;'>{gemini_answer}</pre></div>",
|
| 47 |
+
[["Specific card detected", card_name]],
|
| 48 |
+
None,
|
| 49 |
+
[],
|
| 50 |
+
card_lookup,
|
| 51 |
+
user_query
|
| 52 |
+
)
|
| 53 |
+
else:
|
| 54 |
+
return (
|
| 55 |
+
"<b style='color:red;'>Card mentioned not found in database.</b>",
|
| 56 |
+
[["Card not found", "Try another card name."]],
|
| 57 |
+
None,
|
| 58 |
+
[],
|
| 59 |
+
{},
|
| 60 |
+
"Card not found"
|
| 61 |
+
)
|
| 62 |
+
direct_query = convert_to_direct_query_gradio(user_query, preferences)
|
| 63 |
+
queries = generate_multi_queries(direct_query)
|
| 64 |
+
|
| 65 |
+
if cibil < 700 and use_eligibility:
|
| 66 |
+
query_intent = True
|
| 67 |
+
else:
|
| 68 |
+
query_intent = find_intent(user_query)
|
| 69 |
+
print(query_intent)
|
| 70 |
+
cypher_query = generate_cypher(direct_query, query_intent,include_cobranded)
|
| 71 |
+
print("Generated Cypher:\n", cypher_query)
|
| 72 |
+
|
| 73 |
+
try:
|
| 74 |
+
faiss_index, filtered_mapping = run_cypher_query(
|
| 75 |
+
user_query, cypher_query, use_eligibility,
|
| 76 |
+
income, cibil, age,
|
| 77 |
+
min_joining_fee, max_joining_fee,
|
| 78 |
+
min_annual_fee, max_annual_fee
|
| 79 |
+
)
|
| 80 |
+
except Neo4jConnectionError as graph_err:
|
| 81 |
+
return (
|
| 82 |
+
"<b style='color:red;'>Graph database connection failed. Please try again later.</b>",
|
| 83 |
+
[["Graph database error", str(graph_err)]],
|
| 84 |
+
None,
|
| 85 |
+
[],
|
| 86 |
+
{},
|
| 87 |
+
"Graph DB connection error"
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
cards = retrieve_and_rank_cards(faiss_index, filtered_mapping, direct_query, queries, top_k=10)
|
| 91 |
+
gemini_summary = generate_credit_card_recommendation_gemini(user_query, direct_query, cards)
|
| 92 |
+
|
| 93 |
+
if not cards:
|
| 94 |
+
return (
|
| 95 |
+
"<b style='color:red;'>No eligible cards found.</b>",
|
| 96 |
+
[["No eligible cards found", "Please try a different query or check your input values."]],
|
| 97 |
+
None,
|
| 98 |
+
[],
|
| 99 |
+
{},
|
| 100 |
+
"No eligible card found"
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
match = re.search(r"f\.name IN (\[.*?\])", cypher_query)
|
| 104 |
+
query_features = set(ast.literal_eval(match.group(1))) if match else set()
|
| 105 |
+
|
| 106 |
+
card_rows = []
|
| 107 |
+
for score, card in sorted(
|
| 108 |
+
zip(cross_encoder.predict([[direct_query, card["description"]] for card in cards]), cards),
|
| 109 |
+
reverse=True,
|
| 110 |
+
key=lambda x: x[0]
|
| 111 |
+
):
|
| 112 |
+
card_name = card["name"]
|
| 113 |
+
card_desc = card["description"]
|
| 114 |
+
matched_features = query_features.intersection(card_features_lookup.get(card_name, set()))
|
| 115 |
+
feature_str = ", ".join(matched_features) if matched_features else "None"
|
| 116 |
+
card_rows.append([card_name, feature_str, card_desc])
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
card_names = [row[0] for row in card_rows]
|
| 120 |
+
card_lookup = {row[0]: row[2] for row in card_rows}
|
| 121 |
+
top_card_html = f"""
|
| 122 |
+
<div style="
|
| 123 |
+
background-color: #fff3e0;
|
| 124 |
+
color: #212121;
|
| 125 |
+
border-radius: 16px;
|
| 126 |
+
padding: 20px;
|
| 127 |
+
border: 2px solid #ffa726;
|
| 128 |
+
box-shadow: 2px 2px 8px rgba(0,0,0,0.1);
|
| 129 |
+
margin-bottom: 16px;
|
| 130 |
+
font-family: sans-serif;
|
| 131 |
+
font-size: 8px;
|
| 132 |
+
">
|
| 133 |
+
<pre style="white-space: pre-wrap; font-size: 13px; color: #212121;">{gemini_summary}</pre>
|
| 134 |
+
</div>
|
| 135 |
+
"""
|
| 136 |
+
|
| 137 |
+
df_cards = pd.DataFrame(card_rows, columns=["Card Name", "Matched Features", "Description"])
|
| 138 |
+
filename = "recommended_cards.csv"
|
| 139 |
+
temp_dir = tempfile.gettempdir()
|
| 140 |
+
file_path = os.path.join(temp_dir, filename)
|
| 141 |
+
df_cards.to_csv(file_path, index=False)
|
| 142 |
+
|
| 143 |
+
return top_card_html, card_rows, file_path, card_names, card_lookup, direct_query
|
| 144 |
+
|
| 145 |
+
except Exception as e:
|
| 146 |
+
print("Error:", e)
|
| 147 |
+
return (
|
| 148 |
+
"An unexpected error occurred. Please try again in a few minutes.",
|
| 149 |
+
[["Something went wrong", "Please try again."]],
|
| 150 |
+
None,
|
| 151 |
+
[],
|
| 152 |
+
{},
|
| 153 |
+
"Unexpected error occurred, please try again in a while"
|
| 154 |
+
)
|
recommender/retrieval_ranking.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import faiss
|
| 3 |
+
from collections import defaultdict
|
| 4 |
+
import os
|
| 5 |
+
import google.generativeai as genai
|
| 6 |
+
from sentence_transformers import CrossEncoder
|
| 7 |
+
|
| 8 |
+
# Function to generate direct query using Gemini
|
| 9 |
+
def convert_to_direct_query_gradio(user_query,preferences):
|
| 10 |
+
genai.configure(api_key=os.environ.get("api_key_2"))
|
| 11 |
+
model2 = genai.GenerativeModel('gemini-1.5-flash-latest')
|
| 12 |
+
|
| 13 |
+
preferences_text = ""
|
| 14 |
+
if preferences:
|
| 15 |
+
preferences_text = "User selected preferences: " + ", ".join(preferences) + "."
|
| 16 |
+
# Prompt with examples for indirect-to-direct conversion
|
| 17 |
+
prompt = f"""
|
| 18 |
+
You are an AI assistant that refines user queries to make them optimized for information retrieval while keeping the original intent.
|
| 19 |
+
|
| 20 |
+
Instructions:
|
| 21 |
+
- Identify the main focus from the query.
|
| 22 |
+
- Reformat the query in a structured way for better retrieval.
|
| 23 |
+
- Ensure the most important feature appears first.
|
| 24 |
+
- Use precise keywords that match credit card benefits.
|
| 25 |
+
- Include the preferences also in the final query.
|
| 26 |
+
- Do NOT introduce new benefits not mentioned by the user.
|
| 27 |
+
- If the user uses vague terms like "vacation", interpret it as travel-related benefits including: airport lounge access, international/domestic travel, hotel benefits, forex waiver.
|
| 28 |
+
- If the query includes terms like "beginner", "entry-level", or "low credit score", include essential features such as cashback, reward points, and basic offers.
|
| 29 |
+
- If the query is empty, generate a useful retrieval-focused query based solely on preferences.
|
| 30 |
+
|
| 31 |
+
Examples:
|
| 32 |
+
|
| 33 |
+
Example 1
|
| 34 |
+
- User Query: "I drive a lot for work and want a credit card with good fuel rewards and travel perks."
|
| 35 |
+
- Optimized Query: "Category: Fuel Rewards | Best credit cards for high fuel spending with maximum rewards & fuel surcharge waiver. Travel perks preferred but secondary."
|
| 36 |
+
|
| 37 |
+
Example 2
|
| 38 |
+
- User Query: "I mostly shop online and want a card that gives high cashback on e-commerce purchases. Food delivery perks would be nice."
|
| 39 |
+
- Optimized Query: "Category: Online Shopping | Credit cards with best cashback on e-commerce platforms like Amazon, Flipkart. Food delivery benefits secondary."
|
| 40 |
+
|
| 41 |
+
Example 3
|
| 42 |
+
- User Query: "I eat out a lot and also order food from Swiggy/Zomato. I want the best dining discounts and food delivery cashback."
|
| 43 |
+
- Optimized Query: "Category: Dining & Food Delivery | Top credit cards offering the best dining discounts at restaurants and cashback on Swiggy/Zomato orders."
|
| 44 |
+
|
| 45 |
+
Now, optimize the following preferences and user query:
|
| 46 |
+
|
| 47 |
+
Preferences: "{preferences_text}"
|
| 48 |
+
User Query: "{user_query}"
|
| 49 |
+
"""
|
| 50 |
+
|
| 51 |
+
print("rewriting")
|
| 52 |
+
response = model2.generate_content(prompt)
|
| 53 |
+
|
| 54 |
+
print(response.text)
|
| 55 |
+
return response.text
|
| 56 |
+
|
| 57 |
+
# Function to generate multiple focused subqueries from a user query
|
| 58 |
+
def generate_multi_queries(direct_query, n=3):
|
| 59 |
+
genai.configure(api_key=os.environ.get("api_key_2"))
|
| 60 |
+
model2 = genai.GenerativeModel('gemini-1.5-flash-latest')
|
| 61 |
+
prompt = f"""
|
| 62 |
+
The following is a detailed credit card search query:
|
| 63 |
+
"{direct_query}"
|
| 64 |
+
Generate {n} distinct subqueries that **collectively cover all the important features** from the original query.
|
| 65 |
+
Each subquery should emphasize a **different combination** of the features (e.g., lounge access, travel insurance, low foreign transaction fees, hotel discounts, etc.).
|
| 66 |
+
Keep the same format: "Category: ... | ...". Make sure all features from the original query are represented across the {n} queries.
|
| 67 |
+
Output only the subqueries, one per line. Do not include any explanations, numbering, or formatting — just plain queries separated by newline characters.
|
| 68 |
+
"""
|
| 69 |
+
response = model2.generate_content(prompt)
|
| 70 |
+
# print(response.text)
|
| 71 |
+
queries = [q.strip() for q in response.text.strip().split('\n') if q.strip()]
|
| 72 |
+
|
| 73 |
+
return queries
|
| 74 |
+
|
| 75 |
+
#retrieval
|
| 76 |
+
#Cross-Encoder Model
|
| 77 |
+
cross_encoder = CrossEncoder("cross-encoder/ms-marco-MiniLM-L6-v2")
|
| 78 |
+
|
| 79 |
+
def rerank_cards_mini(query, cards, top_n=5):
|
| 80 |
+
card_descriptions = [card["description"] for card in cards]
|
| 81 |
+
query_card_pairs = [[query, desc] for desc in card_descriptions]
|
| 82 |
+
scores = cross_encoder.predict(query_card_pairs)
|
| 83 |
+
|
| 84 |
+
#Normalizing
|
| 85 |
+
if len(scores) == 0 or (max(scores) - min(scores)) == 0:
|
| 86 |
+
scores = [1.0] * len(cards)
|
| 87 |
+
else:
|
| 88 |
+
scores = np.array(scores)
|
| 89 |
+
scores = (scores - scores.min()) / (scores.max() - scores.min())
|
| 90 |
+
|
| 91 |
+
# Sort cards by descending score
|
| 92 |
+
ranked_cards = sorted(zip(scores, cards), key=lambda x: x[0], reverse=True)
|
| 93 |
+
top_cards = [card for _, card in ranked_cards[:top_n]]
|
| 94 |
+
|
| 95 |
+
return top_cards
|
| 96 |
+
|
| 97 |
+
def retrieve_relevant_cards(user_query, index, chunk_name_mapping, df, top_k=15):
|
| 98 |
+
genai.configure(api_key=os.environ.get("api_key_2"))
|
| 99 |
+
model_name = "models/text-embedding-004"
|
| 100 |
+
print(f"\nUser Query: {user_query}")
|
| 101 |
+
|
| 102 |
+
# Generate and normalize query embedding
|
| 103 |
+
query_embedding = genai.embed_content(
|
| 104 |
+
model=model_name,
|
| 105 |
+
content=user_query,
|
| 106 |
+
task_type="RETRIEVAL_QUERY"
|
| 107 |
+
)["embedding"]
|
| 108 |
+
# query_embedding=model.encode(user_query)
|
| 109 |
+
query_embedding = np.array(query_embedding, dtype=np.float32)
|
| 110 |
+
faiss.normalize_L2(query_embedding.reshape(1, -1))
|
| 111 |
+
|
| 112 |
+
# Search FAISS index for top-k chunks
|
| 113 |
+
D, I = index.search(np.expand_dims(query_embedding, axis=0), top_k * 5)
|
| 114 |
+
similarity_scores = D[0]
|
| 115 |
+
|
| 116 |
+
# Map chunks back to unique card names with highest similarity per card
|
| 117 |
+
card_similarity = defaultdict(float)
|
| 118 |
+
card_dict = {card["name"]: card for card in df.to_dict(orient="records")}
|
| 119 |
+
unique_cards = {}
|
| 120 |
+
|
| 121 |
+
for i, chunk_idx in enumerate(I[0]):
|
| 122 |
+
if chunk_idx == -1:
|
| 123 |
+
continue
|
| 124 |
+
card_name = chunk_name_mapping[chunk_idx]
|
| 125 |
+
if card_name not in unique_cards or similarity_scores[i] > card_similarity[card_name]:
|
| 126 |
+
card_similarity[card_name] = similarity_scores[i]
|
| 127 |
+
unique_cards[card_name] = {
|
| 128 |
+
"name": card_name,
|
| 129 |
+
"description": card_dict[card_name]["description"],
|
| 130 |
+
"similarity": similarity_scores[i]
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
# Sort by similarity and take top_k
|
| 134 |
+
ordered_cards = sorted(unique_cards.values(), key=lambda x: x["similarity"], reverse=True)[:top_k]
|
| 135 |
+
|
| 136 |
+
print("\nTop Recommended Cards:")
|
| 137 |
+
for card in ordered_cards:
|
| 138 |
+
print(f"- {card['name']} (Similarity: {card['similarity']:.4f})")
|
| 139 |
+
|
| 140 |
+
return ordered_cards
|
| 141 |
+
|
| 142 |
+
def retrieve_and_rank_cards(faiss_index,filtered_mapping,direct_query, queries, top_k=10):
|
| 143 |
+
all_retrieved=[]
|
| 144 |
+
for query in queries:
|
| 145 |
+
results = retrieve_relevant_cards(
|
| 146 |
+
query, faiss_index, filtered_mapping, df, 10
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
reranked_cards = results[:5]
|
| 150 |
+
|
| 151 |
+
all_retrieved.extend(reranked_cards)
|
| 152 |
+
|
| 153 |
+
# Removing duplicates
|
| 154 |
+
seen = set()
|
| 155 |
+
unique_cards = []
|
| 156 |
+
for card in all_retrieved:
|
| 157 |
+
if card["name"] not in seen:
|
| 158 |
+
seen.add(card["name"])
|
| 159 |
+
unique_cards.append(card)
|
| 160 |
+
if not unique_cards:
|
| 161 |
+
return unique_cards
|
| 162 |
+
reranked_cards = rerank_cards_mini(direct_query, unique_cards, top_n=5)
|
| 163 |
+
return reranked_cards
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
def generate_credit_card_recommendation_gemini(indirect_query,user_query,retrieved_cards, max_new_tokens=500):
|
| 167 |
+
genai.configure(api_key=os.environ.get("api_key_3"))
|
| 168 |
+
model3 = genai.GenerativeModel('gemini-1.5-flash-latest')
|
| 169 |
+
print("using gemini")
|
| 170 |
+
if not retrieved_cards:
|
| 171 |
+
return "Unfortunately, no credit cards match your eligibility criteria."
|
| 172 |
+
for card in retrieved_cards:
|
| 173 |
+
print(f"Name: {card['name']}")
|
| 174 |
+
print(f"Description: {card['description']}\n")
|
| 175 |
+
|
| 176 |
+
# Formatting retrieved cards for input context
|
| 177 |
+
cards_info = "\n\n".join([
|
| 178 |
+
f"{card['name']}\n"
|
| 179 |
+
f"- Description: {card['description']}\n\n"
|
| 180 |
+
for card in retrieved_cards
|
| 181 |
+
])
|
| 182 |
+
|
| 183 |
+
# Gemini Prompt
|
| 184 |
+
gemini_prompt = f"""
|
| 185 |
+
You are a financial analyst specializing in credit cards and rewards optimization.
|
| 186 |
+
Your task is to analyze and select the best credit card based on user priorities.
|
| 187 |
+
|
| 188 |
+
### User's Query:
|
| 189 |
+
{indirect_query+user_query}
|
| 190 |
+
|
| 191 |
+
### Available Credit Cards:
|
| 192 |
+
{cards_info}
|
| 193 |
+
|
| 194 |
+
### Instructions:
|
| 195 |
+
1️ Analyze the user's need from the given query .
|
| 196 |
+
2️ Select the best card based on that primary need and only from the details present in the desctiption.Do not include details which are not explicitly mentioned.
|
| 197 |
+
3️ Explain why it's the best choice (list benefits concisely).
|
| 198 |
+
4 Do not assume and include benefits or features which is nor explicitly mentioned in the card description.
|
| 199 |
+
5 Mention the benefits only if it explicitly mentioned in the card description and if it is not mentioned just skip it and do not mention anything about it.
|
| 200 |
+
6 Do not include extra symbols like * or #, just generate a textual response maybe with numbers for points.
|
| 201 |
+
7️ If the user mentions they need FD-based cards or cards for students or if they have low credit score, assume all the provided cards are FD-based. Just compare them based on benefits and choose the best one.
|
| 202 |
+
8 If user asks for fd based cards and the descriptions do not mention it just pick a best card from the given list.
|
| 203 |
+
|
| 204 |
+
### Expected Output Format:
|
| 205 |
+
Best Card: [Card Name]
|
| 206 |
+
Why It’s the Best:
|
| 207 |
+
1️ [Primary Benefit] : [Explanation]
|
| 208 |
+
2️ [Additional Perks] : [Explanation]
|
| 209 |
+
3️ [Final Justification] : [Why it's the best fit]
|
| 210 |
+
"""
|
| 211 |
+
|
| 212 |
+
# Generate response using Gemini
|
| 213 |
+
response = model3.generate_content(gemini_prompt)
|
| 214 |
+
|
| 215 |
+
return response.text
|
ui/gradio_interface.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
from agents.chat import chat_with_gemini
|
| 3 |
+
from agents.compare import compare_selected_cards
|
| 4 |
+
from data import all_card_names,all_card_lookup
|
| 5 |
+
from recommender.recommender import recommend_cards_gradio
|
| 6 |
+
|
| 7 |
+
# Interface with Tabs
|
| 8 |
+
with gr.Blocks() as demo:
|
| 9 |
+
gr.Markdown("# Credit Card Recommender")
|
| 10 |
+
gr.Markdown("Get personalized credit card suggestions based on your lifestyle and eligibility.")
|
| 11 |
+
|
| 12 |
+
with gr.Tabs():
|
| 13 |
+
|
| 14 |
+
with gr.Tab(" Get Recommendations"):
|
| 15 |
+
with gr.Row():
|
| 16 |
+
user_query = gr.Textbox(
|
| 17 |
+
label="Enter your query",
|
| 18 |
+
info="E.g., 'Best cards for international travel' or 'I want cashback cards with lounge access'"
|
| 19 |
+
)
|
| 20 |
+
preferences = gr.CheckboxGroup(
|
| 21 |
+
choices=["Cashback", "Travel Rewards", "Fuel Benefits", "International Lounge access",
|
| 22 |
+
"Domestic Lounge access", "Railway benefits", "Dining", "Shopping"],
|
| 23 |
+
label="Credit card categories:",
|
| 24 |
+
info="Select the features or benefits you want from your credit card"
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
with gr.Accordion("Eligibility filters menu", open=False):
|
| 28 |
+
with gr.Row():
|
| 29 |
+
income = gr.Slider(
|
| 30 |
+
minimum=1, maximum=60, step=1,
|
| 31 |
+
label="Annual Income (LPA) Minimum requirement is 2.5",
|
| 32 |
+
info="Helps filter cards based on your income eligibility (in Lakhs Per Annum)"
|
| 33 |
+
)
|
| 34 |
+
cibil = gr.Slider(
|
| 35 |
+
minimum=300, maximum=900, step=10,
|
| 36 |
+
label="CIBIL Score",
|
| 37 |
+
info="Most of the cards requires a credit score of 700+"
|
| 38 |
+
)
|
| 39 |
+
age = gr.Slider(
|
| 40 |
+
minimum=18, maximum=75, step=1,
|
| 41 |
+
label="Age",
|
| 42 |
+
info="Some cards have minimum and maximum age eligibility"
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
with gr.Row():
|
| 46 |
+
min_joining_fee = gr.Number(
|
| 47 |
+
label="Min Joining Fee (₹)", value=0,
|
| 48 |
+
info="Minimum one-time fee to get the card"
|
| 49 |
+
)
|
| 50 |
+
max_joining_fee = gr.Number(
|
| 51 |
+
label="Max Joining Fee (₹)", value=150000,
|
| 52 |
+
info="Maximum one-time fee to get the card"
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
with gr.Row():
|
| 56 |
+
min_annual_fee = gr.Number(
|
| 57 |
+
label="Min Annual Fee (₹)", value=0,
|
| 58 |
+
info="Minimum yearly fee to be paid"
|
| 59 |
+
)
|
| 60 |
+
max_annual_fee = gr.Number(
|
| 61 |
+
label="Max Annual Fee (₹)", value=150000,
|
| 62 |
+
info="Maximum yearly fee to be paid"
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
with gr.Row():
|
| 66 |
+
use_eligibility = gr.Checkbox(
|
| 67 |
+
label="Apply Eligibility Filter", value=False,
|
| 68 |
+
info="Enable this to get recommendations of the cards only for which you are eligible for"
|
| 69 |
+
)
|
| 70 |
+
include_cobranded = gr.Checkbox(
|
| 71 |
+
label="Include Co-branded Cards", value=True,
|
| 72 |
+
info="Include cards that are co-branded with airlines, retailers, etc."
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
submit_btn = gr.Button("Recommend Cards", variant='primary')
|
| 76 |
+
|
| 77 |
+
top_card_html = gr.HTML()
|
| 78 |
+
card_df = gr.Dataframe(headers=["Card Name", "Matched Features", "Description"])
|
| 79 |
+
card_file = gr.File(label="Download Full Recommendations (CSV)")
|
| 80 |
+
|
| 81 |
+
with gr.Tab(" Compare Cards"):
|
| 82 |
+
gr.Markdown("### Compare Recommended Cards")
|
| 83 |
+
compare_checkboxes = gr.CheckboxGroup(
|
| 84 |
+
choices=[], label="Select 2 or more cards to compare",
|
| 85 |
+
info="Pick 2+ cards from the recommended list to see a comparison"
|
| 86 |
+
)
|
| 87 |
+
compare_output = gr.HTML(value="<div style='min-height:100px'></div>", visible=True)
|
| 88 |
+
compare_btn = gr.Button("Compare Selected Cards", variant='primary')
|
| 89 |
+
|
| 90 |
+
gr.Markdown("### Compare Any Cards from Full List")
|
| 91 |
+
full_compare_dropdown = gr.Dropdown(
|
| 92 |
+
choices=all_card_names, multiselect=True, label="Select any 2+ cards",
|
| 93 |
+
info="Manually compare any cards from the full database"
|
| 94 |
+
)
|
| 95 |
+
full_compare_btn = gr.Button("Compare Selected Cards", variant='primary')
|
| 96 |
+
full_compare_output = gr.HTML(value="<div style='min-height:100px'></div>", visible=True)
|
| 97 |
+
|
| 98 |
+
with gr.Tab(" Ask Follow-up Questions"):
|
| 99 |
+
gr.Markdown("### Ask any follow-up question ")
|
| 100 |
+
chatbot = gr.Chatbot(type='messages')
|
| 101 |
+
user_query_for_chat = gr.Textbox(
|
| 102 |
+
label="Enter your question",
|
| 103 |
+
info="Ask follow-ups like 'Which card has better travel insurance?' or 'Which card has less annual fee'"
|
| 104 |
+
)
|
| 105 |
+
submit_query_btn = gr.Button("Submit Query", variant='primary')
|
| 106 |
+
|
| 107 |
+
card_names_state = gr.State()
|
| 108 |
+
card_lookup_state = gr.State()
|
| 109 |
+
chat_history = gr.State([])
|
| 110 |
+
query = gr.State([])
|
| 111 |
+
|
| 112 |
+
def wrapped_recommend_cards(user_query, preferences, income, cibil, age, min_joining_fee, max_joining_fee,
|
| 113 |
+
min_annual_fee, max_annual_fee, use_eligibility,include_cobranded):
|
| 114 |
+
top_html, df, file, card_names, card_lookup, direct_query = recommend_cards_gradio(
|
| 115 |
+
user_query, preferences, income, cibil, age, min_joining_fee, max_joining_fee,
|
| 116 |
+
min_annual_fee, max_annual_fee, use_eligibility,include_cobranded
|
| 117 |
+
)
|
| 118 |
+
df_label = f"Found {len(card_names)} cards"
|
| 119 |
+
return top_html, gr.update(value=df, label=df_label), file, card_names, card_lookup, gr.update(choices=card_names, value=[]), direct_query
|
| 120 |
+
|
| 121 |
+
submit_btn.click(
|
| 122 |
+
fn=wrapped_recommend_cards,
|
| 123 |
+
inputs=[user_query, preferences, income, cibil, age, min_joining_fee, max_joining_fee,
|
| 124 |
+
min_annual_fee, max_annual_fee, use_eligibility,include_cobranded],
|
| 125 |
+
outputs=[top_card_html, card_df, card_file, card_names_state, card_lookup_state, compare_checkboxes,query]
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
compare_btn.click(
|
| 129 |
+
fn=compare_selected_cards,
|
| 130 |
+
inputs=[compare_checkboxes, card_lookup_state],
|
| 131 |
+
outputs=compare_output
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
full_compare_btn.click(
|
| 135 |
+
fn=lambda selected: compare_selected_cards(selected, all_card_lookup),
|
| 136 |
+
inputs=[full_compare_dropdown],
|
| 137 |
+
outputs=full_compare_output
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
submit_query_btn.click(
|
| 141 |
+
fn=chat_with_gemini,
|
| 142 |
+
inputs=[query,user_query_for_chat, chat_history, card_lookup_state],
|
| 143 |
+
outputs=[chatbot, chat_history]
|
| 144 |
+
).then(
|
| 145 |
+
lambda: gr.update(value=""),
|
| 146 |
+
inputs=[],
|
| 147 |
+
outputs=[user_query_for_chat]
|
| 148 |
+
)
|
| 149 |
+
|
| 150 |
+
#for submitting using enter button
|
| 151 |
+
user_query_for_chat.submit(
|
| 152 |
+
fn=chat_with_gemini,
|
| 153 |
+
inputs=[query, user_query_for_chat, chat_history, card_lookup_state],
|
| 154 |
+
outputs=[chatbot, chat_history],
|
| 155 |
+
show_progress=True
|
| 156 |
+
).then(
|
| 157 |
+
lambda: gr.update(value=""),
|
| 158 |
+
inputs=[],
|
| 159 |
+
outputs=[user_query_for_chat]
|
| 160 |
+
)
|