Spaces:
Sleeping
Sleeping
Download BankingAgent.py from PrernaKu/RAGRecommendation: direct link, hf CLI and curl.
- Browser
- Download file 11.7 kB
-
https://huggingface.co/spaces/PrernaKu/RAGRecommendation/resolve/main/BankingAgent.py
- Command line
-
hf download hf://spaces/PrernaKu/RAGRecommendation/BankingAgent.py
-
curl -L -o BankingAgent.py https://huggingface.co/spaces/PrernaKu/RAGRecommendation/resolve/main/BankingAgent.py
11.7 kB
| import os | |
| import re | |
| from urllib.parse import urlparse | |
| import requests | |
| from bs4 import BeautifulSoup | |
| from groq import Groq | |
| from langchain_core.documents import Document | |
| from langchain_core.vectorstores import InMemoryVectorStore | |
| from langchain_huggingface import HuggingFaceEmbeddings | |
| from langchain_text_splitters import RecursiveCharacterTextSplitter | |
| # Set this in environment (recommended for Hugging Face Spaces secrets). | |
| # Fallback to empty string for local runs. | |
| GROQ_API_KEY = os.getenv("GROQ_API_KEY", "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").strip() | |
| BRANCH_LOCATOR_URL = "https://www.hdfcbank.com/personal/locators/branch-locator" | |
| ALLOWED_HOSTS = {"www.hdfcbank.com", "hdfcbank.com"} | |
| DEFAULT_APPROVED_URLS = [ | |
| "https://www.hdfcbank.com/personal/locators/branch-locator", | |
| "https://www.hdfcbank.com/personal/locators/atm-locator", | |
| "https://www.hdfcbank.com/personal/pay/cards/debit-cards", | |
| "https://www.hdfcbank.com/personal/pay/cards/credit-cards", | |
| "https://www.hdfcbank.com/personal/borrow/popular-loans/personal-loan", | |
| "https://www.hdfcbank.com/personal/save/accounts/savings-accounts", | |
| "https://www.hdfcbank.com/personal/pay/money-transfer", | |
| "https://www.hdfcbank.com/personal/help", | |
| ] | |
| TEXT_SPLITTER = RecursiveCharacterTextSplitter( | |
| chunk_size=900, | |
| chunk_overlap=150, | |
| separators=["\n\n", "\n", ". ", " ", ""], | |
| ) | |
| VECTOR_STORE = None | |
| def normalize_text(value): | |
| return re.sub(r"\s+", " ", str(value or "").strip().lower()) | |
| EMBEDDINGS = HuggingFaceEmbeddings( | |
| model_name="sentence-transformers/all-MiniLM-L6-v2", | |
| model_kwargs={"device": "cpu"}, | |
| encode_kwargs={"normalize_embeddings": True}, | |
| ) | |
| def clean_location_text(value): | |
| """Remove timing/bank filler words from extracted location phrases.""" | |
| text = normalize_text(value) | |
| if not text: | |
| return "" | |
| removable_words = { | |
| "hdfc", | |
| "bank", | |
| "branch", | |
| "branches", | |
| "time", | |
| "timing", | |
| "timings", | |
| "hours", | |
| "hour", | |
| "open", | |
| "close", | |
| "working", | |
| "in", | |
| "at", | |
| "near", | |
| "for", | |
| } | |
| tokens = [token for token in re.sub(r"[^a-z\s-]", " ", text).split() if token not in removable_words] | |
| return " ".join(tokens).strip() | |
| def get_approved_public_urls(): | |
| env_value = os.getenv("HDFC_APPROVED_URLS", "").strip() | |
| if not env_value: | |
| return DEFAULT_APPROVED_URLS | |
| urls = [value.strip() for value in env_value.split(",") if value.strip()] | |
| return urls if urls else DEFAULT_APPROVED_URLS | |
| def is_allowed_hdfc_url(url): | |
| parsed = urlparse(url) | |
| if parsed.scheme not in {"http", "https"}: | |
| return False | |
| return parsed.netloc.lower() in ALLOWED_HOSTS | |
| def html_to_clean_text(html_content): | |
| soup = BeautifulSoup(html_content, "html.parser") | |
| for tag in soup(["script", "style", "noscript"]): | |
| tag.decompose() | |
| return re.sub(r"\s+", " ", soup.get_text(separator=" ")).strip() | |
| def ingest_public_hdfc_documents(urls, request_timeout=12): | |
| documents = [] | |
| session = requests.Session() | |
| session.headers.update( | |
| { | |
| "User-Agent": "Mozilla/5.0 (compatible; HDFC-RAG-Bot/1.0; +https://www.hdfcbank.com)", | |
| "Accept": "text/html,application/xhtml+xml", | |
| } | |
| ) | |
| for url in urls: | |
| if not is_allowed_hdfc_url(url): | |
| continue | |
| try: | |
| response = session.get(url, timeout=request_timeout) | |
| response.raise_for_status() | |
| text = html_to_clean_text(response.text) | |
| title = urlparse(url).path.strip("/") or "hdfc-home" | |
| title = title.replace("-", " ").replace("/", " > ").strip() or "HDFC public page" | |
| base_document = Document( | |
| page_content=text, | |
| metadata={"title": title, "source_url": url}, | |
| ) | |
| for idx, chunk in enumerate(TEXT_SPLITTER.split_documents([base_document]), start=1): | |
| documents.append( | |
| { | |
| "title": f"{title} (chunk {idx})", | |
| "content": chunk.page_content, | |
| "source_url": url, | |
| } | |
| ) | |
| except Exception: | |
| continue | |
| return documents | |
| def initialize_knowledge_base(): | |
| global VECTOR_STORE | |
| retriever_documents = [] | |
| seed_documents = ingest_public_hdfc_documents(get_approved_public_urls()) | |
| for doc in seed_documents: | |
| content = str(doc.get("content", "")).strip() | |
| title = str(doc.get("title", "")).strip() or "HDFC public info" | |
| source_url = str(doc.get("source_url", "")).strip() | |
| if not content: | |
| continue | |
| retriever_documents.append( | |
| Document( | |
| page_content=content, | |
| metadata={ | |
| "title": title, | |
| "source_url": source_url, | |
| }, | |
| ) | |
| ) | |
| if not retriever_documents: | |
| VECTOR_STORE = None | |
| return | |
| VECTOR_STORE = InMemoryVectorStore(EMBEDDINGS) | |
| VECTOR_STORE.add_documents(retriever_documents) | |
| def retrieve_relevant_context(query, top_k=4): | |
| if not VECTOR_STORE: | |
| return "", [] | |
| top_docs = VECTOR_STORE.similarity_search(query, k=top_k) | |
| context_parts = [] | |
| citations = [] | |
| for doc in top_docs: | |
| context_parts.append( | |
| f"Title: {doc.metadata.get('title', 'HDFC public info')}\n" | |
| f"Source URL: {doc.metadata.get('source_url', 'N/A')}\n" | |
| f"Snippet: {doc.page_content}" | |
| ) | |
| citations.append( | |
| { | |
| "title": doc.metadata.get("title", "HDFC public info"), | |
| "source_url": doc.metadata.get("source_url", "N/A"), | |
| } | |
| ) | |
| return "\n\n".join(context_parts), citations | |
| def extract_branch_location(query): | |
| normalized = re.sub(r"\s+", " ", (query or "").strip()).lower() | |
| if not normalized: | |
| return None | |
| if "branch" not in normalized and "hdfc" not in normalized: | |
| return None | |
| patterns = [ | |
| r"\b(?:branch\s+)?(?:in|at|near)\s+([a-z][a-z\s\-]{2,40})", | |
| r"\bhdfc\s+(?:bank\s+)?(?:branch\s+)?([a-z][a-z\s\-]{2,40})", | |
| ] | |
| for pattern in patterns: | |
| match = re.search(pattern, normalized) | |
| if match: | |
| location = clean_location_text(match.group(1)) | |
| if location: | |
| return location.title() | |
| tokens = re.sub(r"[^a-z\s-]", " ", normalized).split() | |
| stop_words = { | |
| "hdfc", | |
| "bank", | |
| "branch", | |
| "branches", | |
| "timing", | |
| "timings", | |
| "open", | |
| "close", | |
| "hours", | |
| "hour", | |
| "for", | |
| "in", | |
| "at", | |
| "near", | |
| "what", | |
| "is", | |
| "the", | |
| "me", | |
| } | |
| candidates = [token for token in tokens if token not in stop_words] | |
| if candidates: | |
| location = clean_location_text(" ".join(candidates[:3])) | |
| if location: | |
| return location.title() | |
| return None | |
| def banking_chat_handler(message, history, model_name): | |
| if not GROQ_API_KEY: | |
| return "Configuration Error: Please set GROQ_API_KEY as an environment variable." | |
| branch_location = extract_branch_location(message) | |
| retrieved_context, citations = retrieve_relevant_context(message, top_k=4) | |
| def call_direct_llm_fallback(): | |
| fallback_prompt = ( | |
| "You are a helpful HDFC Bank support assistant. " | |
| "Answer user questions directly and clearly. " | |
| "Do not invent exact branch addresses, phone numbers, fees, or timings when uncertain. " | |
| "If exact details are unavailable, say EXACT_ANSWER_NOT_FOUND. " | |
| "Never provide personal account data or ask for OTP/PIN/CVV." | |
| ) | |
| fallback_messages = [{"role": "system", "content": fallback_prompt}] | |
| for user_msg, assistant_msg in history[-6:]: | |
| if user_msg: | |
| fallback_messages.append({"role": "user", "content": user_msg}) | |
| if assistant_msg: | |
| fallback_messages.append({"role": "assistant", "content": assistant_msg}) | |
| fallback_messages.append({"role": "user", "content": message}) | |
| fallback_completion = client.chat.completions.create( | |
| model=model_name, | |
| messages=fallback_messages, | |
| temperature=0.0, | |
| max_tokens=700, | |
| ) | |
| return fallback_completion.choices[0].message.content | |
| system_prompt = ( | |
| "You are a precise HDFC Bank support assistant using RAG context. " | |
| "Answer only from the retrieved context below. " | |
| "Do not invent branch addresses, phone numbers, fees, timings, or policies not present in context. " | |
| "If exact details are unavailable in context, respond with EXACT_ANSWER_NOT_FOUND. " | |
| "Avoid generic filler language, apologies, and disclaimers. " | |
| "For branch/location queries, mention the requested location explicitly if it is in context. " | |
| "Never provide personal account data or ask for OTP/PIN/CVV.\n\n" | |
| "Response format:\n" | |
| "1) Exact Answer\n" | |
| "2) Evidence: short bullets grounded in context\n\n" | |
| f"--- RETRIEVED CONTEXT ---\n{retrieved_context}\n" | |
| "-------------------------" | |
| ) | |
| if branch_location: | |
| system_prompt += ( | |
| f" The user likely needs branch details for {branch_location}. " | |
| "Prioritize this location in your response." | |
| ) | |
| try: | |
| client = Groq(api_key=GROQ_API_KEY) | |
| if not retrieved_context: | |
| return call_direct_llm_fallback() | |
| messages = [{"role": "system", "content": system_prompt}] | |
| # Keep recent turns so follow-up questions get complete context. | |
| for user_msg, assistant_msg in history[-6:]: | |
| if user_msg: | |
| messages.append({"role": "user", "content": user_msg}) | |
| if assistant_msg: | |
| messages.append({"role": "assistant", "content": assistant_msg}) | |
| messages.append({"role": "user", "content": message}) | |
| completion = client.chat.completions.create( | |
| model=model_name, | |
| messages=messages, | |
| temperature=0.0, | |
| max_tokens=700, | |
| ) | |
| answer_text = completion.choices[0].message.content | |
| if "EXACT_ANSWER_NOT_FOUND" in (answer_text or ""): | |
| return call_direct_llm_fallback() | |
| sources_block = "\n\nSources:\n" + "\n".join( | |
| f"- {item['title']}: {item['source_url']}" for item in citations | |
| ) | |
| return answer_text + sources_block | |
| except Exception as e: | |
| return f"Groq API Call Failed: {str(e)}" | |
| initialize_knowledge_base() | |
| if __name__ == "__main__": | |
| model_name = "llama-3.1-8b-instant" | |
| history = [] | |
| print("HDFC Bank Customer Support AI Agent (CLI)") | |
| print("Type your question and press Enter. Type 'exit' to quit.") | |
| while True: | |
| user_message = input("You: ").strip() | |
| if user_message.lower() in {"exit", "quit"}: | |
| print("Session ended.") | |
| break | |
| answer = banking_chat_handler(user_message, history, model_name) | |
| print(f"Agent: {answer}\n") | |
| history.append((user_message, answer)) | |