File size: 18,258 Bytes
d401264 f416236 d401264 d4d3e9c d401264 2f43b11 d401264 0961cbb 1109957 d401264 45e9462 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 |
from langchain_core.messages import SystemMessage, HumanMessage
from typing import List, Dict, Any
import time
import requests
import gradio as gr
from data import debug_print,all_card_names,all_card_lookup,eligibility_lookup,df
from nodes.intent import get_pretty_state_string
from langgraph_pipeline import run_langgraph_pipeline,utility_app
#Gradio UI with the fucntion calls to invoke the graphs and pass the user inputs
custom_css="""
#compare_output_markdown, #full_compare_output_markdown, #top_card_markdown {
min-height: 100px;
border: 1px solid #e0e0e0;
padding: 10px;
overflow-y: auto;
}
#left_column_box, #right_column_box {
padding: 16px !important; /* Adds 16px of space INSIDE the bordered box */
}
"""
with gr.Blocks(title="Agentic Credit Card Recommender", css=custom_css, theme=gr.themes.Soft()) as demo:
gr.Markdown("""
# Credit Card Recommender
Discover and receive personalized recommendations for credit cards based on your needs and preferences
""")
with gr.Tab("Get Recommendations"):
with gr.Row():
query_input = gr.Textbox(
label="What kind of card are you looking for?",
placeholder="Best cards for online shopping with fuel benefits",
elem_id="query-textbox",
scale=2
)
preferences = gr.Dropdown(
choices=["Cashback", "Travel", "Fuel", "Airport Lounge access", "Railways", "Dining", "Online Spends", "Grocery"],
multiselect=True,
label="Select Preferred Card Categories",
scale=1
)
with gr.Accordion("Eligibility & Fee Filters", open=False):
with gr.Row():
income = gr.Slider(minimum=1, maximum=60, step=1, label="Annual Income (LPA)")
cibil = gr.Slider(minimum=300, maximum=900, step=10, label="CIBIL Score")
age = gr.Slider(minimum=18, maximum=75, step=1, label="Age")
with gr.Row():
with gr.Group():
gr.Markdown("<p style='text-align:center;'>Preferred Joining Fee (₹)</p>")
with gr.Row():
min_joining_fee = gr.Number(label="Min", value=0)
max_joining_fee = gr.Number(label="Max", value=150000)
with gr.Group():
gr.Markdown("<p style='text-align:center;'>Preferred Annual Fee (₹)</p>")
with gr.Row():
min_annual_fee = gr.Number(label="Min", value=0)
max_annual_fee = gr.Number(label="Max", value=150000)
with gr.Row():
use_eligibility = gr.Checkbox(label="Apply Eligibility Filter", value=False)
fd_checkbox = gr.Checkbox(label="Beginner / Student", value=False)
cobrand_checkbox = gr.Checkbox(label="Include Co-branded Cards", value=True)
run_button = gr.Button("Recommend Cards", variant='primary')
top_card_recommendation = gr.Markdown(value="", elem_id="top_card_markdown")
with gr.Row(visible=True) as results_container:
with gr.Column():
with gr.Group(elem_id="left_column_box"):
recommendation_heading = gr.Markdown("### Top-Ranked Cards")
card_table_markdown = gr.Markdown()
with gr.Column():
with gr.Group(elem_id="right_column_box"):
card_links_heading = gr.Markdown("### 🔗 Issuer Links")
card_links_html = gr.HTML()
with gr.Column(visible=False) as chat_container:
with gr.Accordion("💬 Ask Follow-up Questions", open=True):
chatbox = gr.Chatbot(type="messages", label="Chat")
followup_input = gr.Textbox(label="Enter Your question", placeholder="Compare the lounge access benefits of card X and card Y")
followup_submit = gr.Button("Submit", variant="primary")
with gr.Tab("Compare Cards"):
with gr.Column(visible=False) as compare_recommended_cards_container:
gr.Markdown("## Compare Recommended Cards")
compare_checkboxes = gr.CheckboxGroup(choices=[], label="Select 2 or more cards to compare", info="Pick from the recommended list to see a comparison.")
compare_btn = gr.Button("Compare Selected Cards", variant='primary')
compare_output = gr.Markdown(value="", elem_id="compare_output_markdown")
gr.Markdown("---")
gr.Markdown("## Compare Any Cards")
full_compare_dropdown = gr.Dropdown(choices=all_card_names, multiselect=True, label="Select 2 or more cards", info="Compare any cards from the entire database.")
full_compare_btn = gr.Button("Compare Selected Cards", variant='primary')
full_compare_output = gr.Markdown(value="", elem_id="full_compare_output_markdown")
def format_to_markdown(top_card_out, top_card_description_out):
debug_print("UI", f"Formatting top card output to markdown")
if not top_card_out and top_card_description_out:
message_block = "\n".join(f"- {desc}" for desc in top_card_description_out if desc)
return f"### Note\n\n{message_block}\n\n"
top_card_recommendation = f"### Best card: {top_card_out}\n\n"
if top_card_description_out:
for desc in top_card_description_out:
if isinstance(desc, str):
desc = desc.strip()
if desc:
top_card_recommendation += f"- {desc}\n"
return top_card_recommendation
def format_rows_to_markdown_table(card_rows):
"""Converts a list of card data into a markdown table string."""
if not card_rows:
return ""
markdown_table = "| Card Name | Joining Fee | Annual Fee |\n"
markdown_table += "|---|---|---|\n"
for row in card_rows:
name = row[0]
joining_fee = row[1]
annual_fee = row[2]
markdown_table += f"| {name} | {joining_fee} | {annual_fee} |\n"
return markdown_table
#main function that invokes the graph
async def recommend(query, preferences, fd_intent, include_cobranded, income, cibil, age, min_joining_fee, max_joining_fee, min_annual_fee, max_annual_fee, use_eligibility):
debug_print("UI", f"recommend called with query: '{query}'")
debug_print("UI", f"Preferences: {preferences}")
debug_print("UI", f"FD intent: {fd_intent}, Include cobranded: {include_cobranded}")
global chat_history
chat_history = []
global messages
messages = []
preferences_text=""
if preferences:
preferences_text = "User selected preferences: " + ", ".join(preferences) + "."
query = query.strip() if query else ""
if not query:
error_message = "Please enter a valid query."
debug_print("UI", f"recommend function error: {error_message}")
return error_message, gr.update(visible=True), gr.update(value=None), gr.update(visible=False), gr.update(visible=False), gr.update(value=None), gr.update(visible=False), [], {}, "", gr.update(visible=False),{}
try:
debug_print("UI", f"Calling run_langgraph_pipeline")
top_card_out, top_card_description_out, card_rows_out, card_names_out, card_lookup_out,card_links = await run_langgraph_pipeline(
query,
preferences_text,
query_intent=fd_intent,
include_cobranded=include_cobranded,
use_eligibility=use_eligibility,
income=income,
cibil=cibil,
age=age,
min_joining_fee=min_joining_fee,
max_joining_fee=max_joining_fee,
min_annual_fee=min_annual_fee,
max_annual_fee=max_annual_fee
)
debug_print("UI", f"Pipeline returned {len(card_rows_out)} card rows")
recommendation_visible = bool(top_card_out) or bool(top_card_description_out)
df_visible = bool(card_rows_out)
chat_container_visible = gr.update(visible=True if card_rows_out else False)
top_card_md = format_to_markdown(top_card_out, top_card_description_out)
card_table_md = format_rows_to_markdown_table(card_rows_out)
debug_print("UI", f"recommend function completed successfully")
initial_context = {
"query": query,
"top_cards": card_names_out[:5],
"recommendation_summary": top_card_md
}
card_links_section = "<div style='padding: 10px; border: 1px solid #444; border-radius: 8px;'>"
for name, link in zip(card_names_out, card_links):
card_links_section += f"<div style='margin-bottom: 8px;'><a href='{link}' target='_blank'>{name}</a></div>"
card_links_section += "</div>"
return top_card_md, gr.update(visible=recommendation_visible), card_table_md, gr.update(visible=df_visible), gr.update(visible=df_visible), card_names_out, card_lookup_out, query, chat_container_visible, initial_context,gr.update(value=card_links_section, visible=True), gr.update(value="### 🔗 Issuer Links", visible=True)
except Exception as e:
error_message = "### Service Unavailable\n The AI model server could not be reached or an error occurred. Please try again later."
debug_print("ERROR", f"recommend function error: {str(e)}")
return (
gr.update(value=error_message, visible=True),
gr.update(visible=True),
"",
gr.update(visible=False),
gr.update(visible=False),
[],
{},
"",
gr.update(visible=False),
{},
gr.update(value="", visible=False),
gr.update(visible=False)
)
#for comparison
async def compare_cards_via_graph(selected_cards, card_lookup):
state = {
"trigger_compare": True,
"trigger_chat": False,
"selected_cards": selected_cards,
"card_lookup": card_lookup
}
result = await utility_app.ainvoke(state)
return result.get("comparison_result", "")
async def compare_cards_wrapper(selected_cards):
return await compare_cards_via_graph(selected_cards, all_card_lookup)
#for chat
async def chat_with_agent(user_message: str, chat_history: List, messages: List, initial_context: Dict[str, Any]):
debug_print("UI", f"Entering chat_with_agent with user_message: '{user_message}'")
# Preparing the initial context message if this is the first question
if not messages:
user_query = initial_context.get("query", "No query provided")
top_cards = initial_context.get("top_cards", [])
context = "\n".join(top_cards) if top_cards else "No top cards available"
recommended_summary = initial_context.get("recommendation_summary", "No recommendations available")
recommended_card_info = ""
other_card_info = ""
for card_name in top_cards:
description = all_card_lookup.get(card_name, "Description not found.")
eligibility = eligibility_lookup.get(card_name, "No eligibility or fee information available.")
card_block = f"""Card: {card_name}
Description: {description}
Eligibility & Fees:
{eligibility}
---
"""
if card_name in recommended_summary:
recommended_card_info += card_block
else:
other_card_info += card_block
# System prompt with context for chat node
system_message = f"""
**Your Role: Secure Credit Card Expert**
You are a helpful and secure AI assistant. Your goal is to provide a comprehensive answer to the user's question using all available information.
Your knowledge base includes the following and <Newly_Fetched_Information> section if it is provided:
<Initial_Context>
<User_Requirements>{user_query}</User_Requirements>
<Top_Ranked_Cards_For_User_Query>{context}</Top_Ranked_Cards_For_User_Query>
<Recommended_Card_Info>
<Best_Recommended_Card>{recommended_card_info}</Best_Recommended_Card>
<Other_Recommended_Cards>{other_card_info}</Other_Recommended_Cards>
</Recommended_Card_Info>
</Initial_Context>
**CRITICAL RULES:**
1. **Synthesize All Information:** Base your answer on the `<Initial_Context>` and any `<Newly_Fetched_Information>` provided. If the user asks for a comparison, use details from both.
2. **Make a Recommendation:** If asked "which is best?" or "which is suitable?", analyze all available card info against the `<User_Requirements>` and provide a direct, reasoned recommendation.
3. **Be Direct:** Do not mention your internal processes. Just provide the final answer to the user.
**RESPONSE STYLE AND TONE:**
- **Be Direct and Concise:** Get straight to the point. Do not explain your internal thought process.
- **For Factual Questions (like "what are the fees?"):** Provide a direct, simple sentence as the answer.
- **For Recommendation Questions (like "which is best for me?"):** Start your response with the name of the recommended card, followed by a brief, bulleted list of the key features that make it the best fit for the user's requirements.
"""
# print(f"system message: {system_message}")
messages = [SystemMessage(content=system_message)]
else:
debug_print("UI", "Subsequent turn: using existing message history.")
top_cards = initial_context.get("top_cards", [])
user_query = initial_context.get("query", "No query provided")
current_turn_messages = messages + [HumanMessage(content=user_message)]
# Defining the state for the utility app
state = {
"messages": current_turn_messages,
"trigger_chat": True,
"trigger_compare": False,
"card_names": top_cards,
}
# invoking the graph
response_state = await utility_app.ainvoke(state)
updated_messages_from_agent = response_state.get("messages", [])
debug_print("UI", f"Chat agent response state: {get_pretty_state_string(updated_messages_from_agent)}")
final_response = updated_messages_from_agent[-1].content
chat_history.append({"role": "user", "content": user_message})
chat_history.append({"role": "assistant", "content": final_response})
return chat_history, updated_messages_from_agent, ""
card_names_state = gr.State([])
card_lookup_state = gr.State()
chat_history = gr.State([])
query = gr.State("")
initial_chat_context = gr.State({})
messages= gr.State([])
run_button.click(
fn=recommend,
inputs=[
query_input, preferences, fd_checkbox, cobrand_checkbox,
income, cibil, age,
min_joining_fee, max_joining_fee,
min_annual_fee, max_annual_fee,
use_eligibility
],
outputs=[
top_card_recommendation, top_card_recommendation,
card_table_markdown, card_table_markdown, recommendation_heading,
card_names_state,
card_lookup_state,
query, chat_container, initial_chat_context,card_links_html, card_links_heading
],
concurrency_limit=20
).then(
fn=lambda card_names: (gr.update(choices=card_names if card_names else [], value=[]), gr.update(visible=True if card_names else False)),
inputs=card_names_state,
outputs=[compare_checkboxes, compare_recommended_cards_container],
concurrency_limit=20
).then(
fn=lambda: ([], [], [], gr.update(value=""), gr.update(value="")),
outputs=[chat_history, chatbox, messages, compare_output, full_compare_output], # Clear both compare outputs
concurrency_limit=20
)
compare_btn.click(
fn=compare_cards_via_graph,
inputs=[compare_checkboxes, card_lookup_state],
outputs=compare_output,
show_progress=True,
concurrency_limit=20
)
full_compare_btn.click(
fn=compare_cards_wrapper,
inputs=[full_compare_dropdown],
outputs=full_compare_output,
show_progress=True,
concurrency_limit=20
)
followup_submit.click(
fn=chat_with_agent,
inputs=[followup_input, chatbox, messages, initial_chat_context],
outputs=[chatbox, messages, followup_input],
concurrency_limit=20
)
debug_print("APP", f"Credit Card Recommender Agent initialized with {len(df)} cards")
debug_print("APP", f"Launching Gradio UI at {time.strftime('%Y-%m-%d %H:%M:%S')}") |