CarMaa_V1 / app.py
Ritvik
Updated app 16
40fa9ed
raw
history blame contribute delete
28.3 kB
import gradio as gr
from groq import Groq
from dotenv import load_dotenv
import os
import traceback
import json
import time
from collections import defaultdict
from duckduckgo_search import DDGS
import requests
import re
# Load .env environment variables
load_dotenv()
api_key = os.getenv("GROQ_API_KEY")
client = Groq(api_key=api_key)
MODEL_NAME = "llama-3.3-70b-versatile"
# In-memory cache for search results
search_cache = defaultdict(str)
cache_timeout = 3600 # 1 hour
# In-memory Q&A store for community simulation
community_qa = []
# Diagnostics knowledge base (simplified)
diagnostics_db = {
"Maruti Alto": {
"check engine light": {
"causes": ["Faulty oxygen sensor", "Loose fuel cap", "Spark plug issues"],
"solutions": ["Run OBD-II scan (₹500-₹1500)", "Tighten/replace fuel cap (₹100-₹500)", "Replace spark plugs (₹1000-₹2000)"],
"severity": "Moderate"
},
"poor fuel efficiency": {
"causes": ["Clogged air filter", "Tire underinflation", "Fuel injector issues"],
"solutions": ["Replace air filter (₹300-₹800)", "Check tire pressure (free)", "Clean/replace injectors (₹2000-₹5000)"],
"severity": "Low"
}
},
"Hyundai i20": {
"ac not cooling": {
"causes": ["Low refrigerant", "Faulty compressor", "Clogged condenser"],
"solutions": ["Refill refrigerant (₹1500-₹3000)", "Repair/replace compressor (₹5000-₹15000)", "Clean condenser (₹1000-₹2000)"],
"severity": "High"
}
}
}
# Maintenance tips
maintenance_tips = [
"Check tire pressure monthly to improve fuel efficiency.",
"Change engine oil every 10,000 km or 6 months for Indian road conditions.",
"Inspect brakes regularly, especially during monsoon seasons.",
"Keep your car clean to prevent rust in humid climates."
]
# Tool: DuckDuckGo web search with retry and structured output
def web_search_duckduckgo(query: str, max_results: int = 5, max_retries: int = 2):
cache_key = query.lower()
if cache_key in search_cache:
cached_time, cached_results = search_cache[cache_key]
if time.time() - cached_time < cache_timeout:
print(f"Using cached results for: {query}")
return cached_results
results = []
for attempt in range(max_retries):
try:
print(f"Attempting DuckDuckGo search for: {query} (Attempt {attempt + 1})")
with DDGS() as ddgs:
for r in ddgs.text(query, region="in-en", safesearch="Moderate", max_results=max_results):
results.append({"title": r['title'], "url": r['href']})
if not results:
raise ValueError("No results found")
formatted_results = "\n".join(f"- [{r['title']}]({r['url']})" for r in results)
search_cache[cache_key] = (time.time(), formatted_results)
print(f"Search results for {query}: {formatted_results}")
return formatted_results
except Exception as e:
print(f"Search attempt {attempt + 1} failed: {str(e)}")
if attempt + 1 == max_retries:
error_message = f"⚠️ Web search failed after {max_retries} attempts: {str(e)}"
search_cache[cache_key] = (time.time(), error_message)
return error_message
time.sleep(1)
# Trigger keywords for garage search
trigger_keywords = [
"garage near", "car service near", "repair shop in", "mechanic in", "car workshop near",
"tyre change near", "puncture repair near", "engine repair near", "car wash near",
"car ac repair", "suspension work", "car battery replacement", "headlight change",
"oil change near", "nearby service center", "wheel alignment near", "wheel balancing",
"car painting service", "denting and painting", "car insurance repair", "maruti workshop",
"hyundai service", "honda repair center", "toyota garage", "tata motors service",
"mahindra car repair", "nexa service center", "kia workshop", "ev charging near",
"ev repair", "gearbox repair", "clutch repair", "brake pad replacement",
"windshield repair", "car glass replacement", "coolant top up", "engine tuning",
"car noise issue", "check engine light", "dashboard warning light", "local garage",
"trusted mechanic", "authorized service center", "car towing service near me",
"car not starting", "flat battery", "jump start service", "roadside assistance",
"ac not cooling", "car breakdown", "pickup and drop car service", "service centers for car repair",
"interior car cleaning", "paint protection", "ceramic coating", "glass coating", "surface coating",
"full car detailing", "exterior car cleaning", "car dry cleaning", "car body polish", "full car wash",
"deep cleaning", "car decor"
]
# Detect language and extract car model from input
def detect_language_and_model(message):
hindi_keywords = ["namaste", "kaise", "hai", "mere", "pas", "hai"]
if any(keyword in message.lower() for keyword in hindi_keywords):
return "hindi", re.search(r'(maruti|alto|tata|nexon|mahindra|scorpio|hyundai|creta|honda|city)\s*(\d{4})?', message.lower())
return "english", None
# ReAct agent response with thought process
def respond(message, history, vehicle_profile):
try:
system_message = (
"You are CarMaa, a totally Indian car Doctor and mechanic,you are friendly, and highly knowledgeable AI Car Doctor specializing in Indian cars. Your mission is to assist users with detailed, practical advice on car care, maintenance, and troubleshooting, tailored for Indian driving conditions. "
"You have deep expertise in Indian car brands like Maruti Suzuki, Tata Motors, Mahindra, Hyundai India, and Honda Cars India, including popular models such as Maruti Alto, Tata Nexon, Mahindra Scorpio, Hyundai Creta, and Honda City. "
"You understand the unique challenges of Indian roads—monsoon flooding, potholes, extreme heat, and heavy traffic—and can provide advice on common issues like engine overheating, suspension wear, AC failures, and fuel efficiency problems. "
"You are also well-versed in Indian market trends (e.g., rise of EVs like Tata Nexon EV, government policies on BS6 norms), you can also differentiate between petrol, diesel, hybrids and EV and give answers to queries according to them, car features (e.g., CNG variants, AMT transmissions), and driving experiences (e.g., long drives on NH44, city driving in Delhi). "
"As an AI doctor, you can diagnose car problems based on user descriptions (e.g., 'weird noise from engine', 'check engine light on'), suggest solutions with estimated costs in INR (e.g., 'replace alternator, ~₹5000'), and recommend maintenance tips for Indian climates (e.g., 'check coolant levels before summer'). "
"For location-specific queries (e.g., garages, repair shops, authorized service centers in Mumbai), you MUST use the 'search' action to fetch real-time data using the web search tool. For general car conversations, engage users naturally with a casual, friendly tone, sharing insights about Indian cars and driving culture. "
"Always respond with a valid JSON object containing 'thought', 'observation', 'action', and 'response' or 'search_query'. Do NOT include any text outside the JSON object. Respond in the user's preferred language you can do English and Hindi(e.g., Hindi if they use Hindi phrases like 'Namaste Ji')."
)
max_tokens = 1024
temperature = 0.7
top_p = 0.95
# Detect language and car model from message
lang, model_match = detect_language_and_model(message)
if model_match:
make_model = model_match.group(0).replace(" ", "")
if "2000" in make_model and "alto" in make_model.lower():
vehicle_profile["make_model"] = "Maruti Alto"
elif make_model:
vehicle_profile["make_model"] = make_model.title()
# Initialize messages with system prompt
react_prompt = (
f"{system_message}\n\n"
"For each user query, follow these steps:\n"
"1. **Thought**: Assess if the query requires a web search (e.g., for garages or real-time data) or is a general car-related conversation.\n"
"2. **Observation**: Note the user's input, language preference, and vehicle profile context.\n"
"3. **Action**: Choose 'search' for location-specific queries, 'respond' for general conversation, or other actions as needed.\n"
f"User vehicle profile: {json.dumps(vehicle_profile)}\n"
f"Respond in {lang} if possible, matching the user's language preference."
"Use the LLM to generate a conversational response for general car topics, but prioritize web search for location-specific queries."
)
messages = [{"role": "system", "content": react_prompt}]
# Convert Gradio chat history to OpenAI-style format
for msg in history:
if msg["role"] == "user":
messages.append({"role": "user", "content": msg["content"]})
elif msg["role"] == "assistant":
messages.append({"role": "assistant", "content": msg["content"]})
messages.append({"role": "user", "content": message})
# Check diagnostics database
if vehicle_profile.get("make_model") and any(kw in message.lower() for kw in diagnostics_db.get(vehicle_profile["make_model"], {})):
for issue, details in diagnostics_db[vehicle_profile["make_model"]].items():
if issue in message.lower():
response = (
f"**Diagnosed Issue**: {issue}\n"
f"- **Possible Causes**: {', '.join(details['causes'])}\n"
f"- **Solutions**: {', '.join(details['solutions'])}\n"
f"- **Severity**: {details['severity']}\n"
f"Would you like to search for garages to address this issue or learn more?"
) if lang == "english" else (
f"**Problem**: {issue}\n"
f"- **Karan**: {', '.join(details['causes'])}\n"
f"- **Samadhan**: {', '.join(details['solutions'])}\n"
f"- **Sankat**: {details['severity']}\n"
f"Kya aap iska garage dhundhna chahte hain ya aur janna chahte hain?"
)
for i in range(0, len(response), 10):
yield response[:i + 10]
return
# Check for community Q&A keywords
if any(kw in message.lower() for kw in ["community", "forum", "discussion", "share advice", "ask community"]):
if "post" in message.lower() or "share" in message.lower():
community_qa.append({"question": message, "answers": []})
response = "Your question has been posted to the community! Check back for answers." if lang == "english" else "Aapka sawal community mein post kar diya gaya hai! Jawab ke liye wapas dekhein."
for i in range(0, len(response), 10):
yield response[:i + 10]
return
elif "view" in message.lower() or "see" in message.lower():
if community_qa:
response = "Community Q&A:\n" + "\n".join(
f"Q: {qa['question']}\nA: {', '.join(qa['answers']) or 'No answers yet'}"
for qa in community_qa
) if lang == "english" else "Community Q&A:\n" + "\n".join(
f"Sawal: {qa['question']}\nJawab: {', '.join(qa['answers']) or 'Abhi tak koi jawab nahi'}"
for qa in community_qa
)
else:
response = "No community questions yet. Post one with 'share' or 'post'!" if lang == "english" else "Abhi tak koi community sawal nahi. 'Share' ya 'post' se ek sawal daalein!"
for i in range(0, len(response), 10):
yield response[:i + 10]
return
# Check for trigger keywords to directly perform search
if any(keyword in message.lower() for keyword in trigger_keywords):
print(f"Trigger keyword detected in query: {message}")
search_query = message.replace("reapir", "repair")
if "car" not in search_query.lower():
search_query = f"car {search_query}"
if vehicle_profile.get("city"):
search_query = f"{search_query} {vehicle_profile['city']}"
uttarakhand_cities = ["dehradun", "haridwar", "roorkee", "haldwani", "nainital", "rudrapur"]
carmaa_recommendation = ""
if any(city in vehicle_profile['city'].lower() for city in uttarakhand_cities):
carmaa_recommendation = "- [CarMaa Car Care - Recommended](https://carmaacarcare.com/contactus/)\n"
else:
response = "I'd be happy to help you find repair workshops near you. However, I need to know your location. Could you please tell me your city or update your vehicle profile?" if lang == "english" else "Main aapko repair workshop dhundhne mein madad karunga, lekin mujhe aapka shahar pata hona chahiye. Kya aap apna shahar batayenge ya profile update karenge?"
for i in range(0, len(response), 10):
yield response[:i + 10]
return
print(f"Performing web search for: {search_query}")
search_results = web_search_duckduckgo(search_query)
print(f"Search Results:\n{search_results}")
final_response = f"🔍 Here are some car repair service centers I found:\n\n{carmaa_recommendation}{search_results}\n\n**Tip**: {maintenance_tips[hash(message) % len(maintenance_tips)]}" if lang == "english" else f"🔍 Ye kuch car repair service centers hain jo mujhe mile:\n\n{carmaa_recommendation}{search_results}\n\n**Tip**: {maintenance_tips[hash(message) % len(maintenance_tips)]}"
for i in range(0, len(final_response), 10):
yield final_response[:i + 10]
return
# General car conversation using LLM with ReAct debugging
max_iterations = 3
for iteration in range(max_iterations):
print(f"\n--- ReAct Iteration {iteration + 1} ---")
completion = client.chat.completions.create(
model=MODEL_NAME,
messages=messages,
temperature=temperature,
max_completion_tokens=max_tokens,
top_p=top_p,
stream=False,
)
raw_response = completion.choices[0].message.content
print(f"Raw LLM Response: {raw_response}")
try:
# Extract JSON from raw response, handling extra text
json_match = re.search(r'\{.*\}', raw_response, re.DOTALL)
if json_match:
react_step = json.loads(json_match.group(0))
else:
raise json.JSONDecodeError("No JSON object found", raw_response, 0)
thought = react_step.get("thought", "")
observation = react_step.get("observation", "")
action = react_step.get("action", "")
response = react_step.get("response", "Let's chat about cars! What do you think about the latest models or features?") if lang == "english" else react_step.get("response", "Gaadi ke baare mein baat karein! Aapko naye models ya features ke baare mein kya lagta hai?")
print(f"Thought: {thought}")
print(f"Observation: {observation}")
print(f"Action: {action}")
except json.JSONDecodeError:
react_step = {
"thought": "User input seems general, using LLM for conversation.",
"observation": f"Input: {message}",
"action": "respond",
"response": "Let's chat about cars! What do you think about the latest models or features?" if lang == "english" else "Gaadi ke baare mein baat karein! Aapko naye models ya features ke baare mein kya lagta hai?"
}
thought = react_step["thought"]
observation = react_step["observation"]
action = react_step["action"]
response = react_step["response"]
print(f"Thought: {thought}")
print(f"Observation: {observation}")
print(f"Action: {action}")
if action == "respond":
current_response = f"{response}\n\n**Tip**: {maintenance_tips[hash(message) % len(maintenance_tips)]}"
print(f"Final Response: {current_response}")
for i in range(0, len(current_response), 10):
yield current_response[:i + 10]
break
elif action == "search":
search_query = react_step.get("search_query", message)
if "car" not in search_query.lower():
search_query = f"car {search_query}"
if vehicle_profile.get("city"):
search_query = f"{search_query} {vehicle_profile['city']}"
uttarakhand_cities = ["dehradun", "haridwar", "roorkee", "haldwani", "nainital", "rudrapur"]
carmaa_recommendation = ""
if any(city in vehicle_profile['city'].lower() for city in uttarakhand_cities):
carmaa_recommendation = "- [CarMaa Car Care - Recommended](https://carmaacarcare.com/)\n"
else:
response = "I'd be happy to help you find repair workshops near you. However, I need to know your location. Could you please tell me your city or update your vehicle profile?" if lang == "english" else "Main aapko repair workshop dhundhne mein madad karunga, lekin mujhe aapka shahar pata hona chahiye. Kya aap apna shahar batayenge ya profile update karenge?"
for i in range(0, len(response), 10):
yield response[:i + 10]
return
print(f"Performing web search for: {search_query}")
search_results = web_search_duckduckgo(search_query)
print(f"Search Results:\n{search_results}")
final_response = f"🔍 Here are some car repair service centers I found:\n\n{carmaa_recommendation}{search_results}\n\n**Tip**: {maintenance_tips[hash(message) % len(maintenance_tips)]}" if lang == "english" else f"🔍 Ye kuch car repair service centers hain jo mujhe mile:\n\n{carmaa_recommendation}{search_results}\n\n**Tip**: {maintenance_tips[hash(message) % len(maintenance_tips)]}"
for i in range(0, len(final_response), 10):
yield final_response[:i + 10]
break
messages.append({"role": "assistant", "content": json.dumps(react_step)})
except Exception as e:
error_msg = f"❌ Error: {str(e)}\n{traceback.format_exc()}"
print(error_msg)
yield error_msg
# Gradio interface with enhanced, customer-centric UI
with gr.Blocks(
title="CarMaa - India's AI Car Doctor",
css="""
/* Overall layout and background */
.gradio-container {
background: linear-gradient(135deg, #e6f0fa, #b3d4fc);
font-family: 'Arial', sans-serif;
color: #003087;
padding: 20px;
min-height: 100vh;
}
/* Ensure the parent container allows growth */
.gradio-container > div {
height: auto !important;
min-height: 100vh !important;
}
/* Header styling with clean design */
.header {
background: #ffffff;
padding: 25px;
border-radius: 12px;
border-bottom: 4px solid #007bff;
text-align: center;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.header h1 {
font-size: 2.8em;
font-weight: 700;
color: #003087;
margin: 0;
}
.header p {
color: #003087;
font-size: 1.2em;
margin-top: 8px;
font-weight: 500;
}
/* Vehicle profile inputs with clean styling */
.vehicle-profile {
background: #ffffff;
padding: 25px;
border-radius: 10px;
margin: 20px 0;
border: 1px solid #b3d4fc;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1);
}
.vehicle-profile label {
color: #007bff;
font-weight: 600;
font-size: 1.1em;
margin-bottom: 8px;
display: block;
}
.vehicle-profile input {
background: #f9f9f9;
color: #003087;
border: 1px solid #b3d4fc;
border-radius: 6px;
padding: 12px;
font-size: 1em;
width: 100%;
transition: border-color 0.3s, box-shadow 0.3s;
}
.vehicle-profile input:focus {
border-color: #007bff;
outline: none;
box-shadow: 0 0 6px rgba(0, 123, 255, 0.3);
}
/* Save button with hover animation */
.save-btn {
background: #007bff !important;
color: #ffffff !important;
border: none !important;
padding: 14px !important;
border-radius: 6px !important;
font-weight: 600 !important;
font-size: 1.1em !important;
transition: transform 0.2s, background 0.3s !important;
width: 100% !important;
margin-top: 15px !important;
}
.save-btn:hover {
background: #0056b3 !important;
transform: scale(1.03);
}
/* Chat container with increased height */
.chatbot-container {
background: #ffffff;
border: 2px solid #b3d4fc;
border-radius: 10px;
padding: 20px;
max-height: 1000px !important;
min-height: 400px !important;
overflow-y: auto;
margin-bottom: 20px;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1);
height: auto !important;
}
/* Ensure the chat interface itself can grow */
.chatbot-container .gr-chatbot {
max-height: 1000px !important;
height: auto !important;
}
/* Chat messages with clean styling */
.chatbot-container .message-user div, .chatbot-container .message-assistant div {
padding: 12px !important;
border-radius: 8px !important;
margin-bottom: 12px !important;
max-width: 85% !important;
word-wrap: break-word !important;
font-size: 1em !important;
line-height: 1.5 !important;
}
.chatbot-container .message-user div {
background: #007bff !important;
color: #ffffff !important;
margin-left: auto !important;
border: 1px solid #0056b3 !important;
}
.chatbot-container .message-assistant div {
background: #f0f7ff !important;
color: #003087 !important;
margin-right: auto !important;
border: 1px solid #b3d4fc !important;
}
/* Chat input area */
.chatbot-container textarea {
background: #f9f9f9 !important;
color: #003087 !important;
border: 1px solid #b3d4fc !important;
border-radius: 6px !important;
padding: 12px !important;
font-size: 1em !important;
transition: border-color 0.3s, box-shadow 0.3s !important;
}
.chatbot-container textarea:focus {
border-color: #007bff !important;
box-shadow: 0 0 6px rgba(0, 123, 255, 0.3) !important;
}
/* Send button with clean design */
.chatbot-container button {
background: #007bff !important;
color: #ffffff !important;
border: none !important;
border-radius: 6px !important;
padding: 12px 24px !important;
font-weight: 600 !important;
font-size: 1em !important;
transition: transform 0.2s, background 0.3s !important;
}
.chatbot-container button:hover {
background: #0056b3 !important;
transform: scale(1.03);
}
/* Navigation tabs for quick access */
.nav-tabs {
display: flex;
justify-content: space-around;
background: #ffffff;
padding: 12px 0;
border-radius: 10px;
margin-bottom: 20px;
border: 1px solid #b3d4fc;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.nav-tabs button {
background: none;
border: none;
color: #003087;
font-weight: 600;
font-size: 1.1em;
padding: 12px;
transition: color 0.3s;
display: flex;
align-items: center;
gap: 6px;
}
.nav-tabs button:hover {
color: #007bff;
}
.nav-tabs button.active {
color: #007bff;
border-bottom: 3px solid #007bff;
}
/* Footer with subtle branding */
.footer {
text-align: center;
color: #003087;
font-size: 1em;
font-weight: 500;
margin-top: 25px;
padding: 10px;
background: rgba(255, 255, 255, 0.9);
border-radius: 8px;
}
"""
) as demo:
# Header with car-themed branding
with gr.Row():
gr.Markdown(
"""
<div class='header'>
<h1>🚗 CarMaa - India's AI Car Doctor</h1>
<p>Diagnose issues, find garages, and connect with the car community!</p>
</div>
"""
)
# Navigation tabs for quick access
with gr.Row():
gr.Markdown(
"""
<div class='nav-tabs'>
<button class='active'>🚘 Profile</button>
<button>🔧 Diagnostics</button>
<button>🗣️ Community</button>
</div>
"""
)
# Vehicle profile inputs
with gr.Row(variant="panel", elem_classes="vehicle-profile"):
make_model = gr.Textbox(
label="Vehicle Make and Model",
placeholder="e.g., Maruti Alto"
)
year = gr.Textbox(
label="Year",
placeholder="e.g., 2000"
)
city = gr.Textbox(
label="City",
placeholder="e.g., Delhi"
)
vehicle_profile = gr.State(value={"make_model": "", "year": "", "city": ""})
# Update vehicle profile
def update_vehicle_profile(make_model, year, city):
return {"make_model": make_model, "year": year, "city": city}
gr.Button("Save Vehicle Profile", elem_classes="save-btn").click(
fn=update_vehicle_profile,
inputs=[make_model, year, city],
outputs=vehicle_profile
)
# Chat interface with enhanced styling
chatbot = gr.ChatInterface(
fn=respond,
additional_inputs=[vehicle_profile],
title="",
description="Ask about car diagnostics, garage locations, or community advice.",
theme="soft",
textbox=gr.Textbox(placeholder="Ask about your car... 🚘"),
submit_btn="Send 🚀",
type="messages"
)
# Footer
gr.Markdown(
"""
<div class='footer'>
Powered by CarMaa © 2025 | Your Trusted Car Care Companion
</div>
"""
)
if __name__ == "__main__":
demo.launch(share=True)