Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import os | |
| import json | |
| from dotenv import load_dotenv | |
| from langchain_anthropic import ChatAnthropic | |
| load_dotenv() | |
| class LegionMariaAssistant: | |
| def __init__(self): | |
| # Router LLM - lightweight for section classification | |
| self.router_llm = ChatAnthropic( | |
| anthropic_api_key=os.getenv("ANTHROPIC_API_KEY"), | |
| model="claude-3-5-haiku-20241022", | |
| temperature=0.1 | |
| ) | |
| # Response LLM - for generating final answers | |
| self.response_llm = ChatAnthropic( | |
| anthropic_api_key=os.getenv("ANTHROPIC_API_KEY"), | |
| model="claude-3-5-haiku-20241022", | |
| temperature=0.1 # Lower temperature for more consistent, direct responses | |
| ) | |
| self.data_content = {} | |
| self.load_data() | |
| def load_data(self): | |
| """Load structured JSON data""" | |
| data_file = "./data.json" | |
| if os.path.exists(data_file): | |
| try: | |
| with open(data_file, 'r', encoding='utf-8') as f: | |
| self.data_content = json.load(f) | |
| print("Loaded Legion Maria JSON data successfully") | |
| print(f"Available sections: {list(self.data_content.keys())}") | |
| except Exception as e: | |
| print(f"Error loading data: {str(e)}") | |
| self.data_content = {} | |
| else: | |
| print("data.json not found") | |
| self.data_content = {} | |
| def route_query(self, message): | |
| """Router LLM decides which section of data to use""" | |
| available_sections = list(self.data_content.keys()) | |
| router_prompt = f"""You are a query router for the Legion Maria Youth Affairs system. | |
| Available data sections: {available_sections} | |
| DETAILED SECTION CONTENTS: | |
| ABOUT section contains: | |
| - organization, headquarters_location, head_office, contact info (phone, email, websites) | |
| - mission, vision, core_values (faithfulness, inclusivity, service, formation, community, integrity) | |
| - church_beliefs (core_belief, reach, membership, dress_code, sacred_practice) | |
| - worship (practices, prohibited, prayer_schedule) | |
| OFFICE section contains: | |
| - departments (30+ departments with officers and roles like administration, treasury, education, procurement, events, medical, auditing, communications, projects, ICT, diaspora, facilities, music, logistics, grants, data engineering, coordination, security, etc.) | |
| - completed_projects, pending_projects (washrooms, radio/TV station, hospital, mausoleum, refurbishments) | |
| - current_activities (festivals, sports, workshops, conventions, camps) | |
| - support_donations (mpesa, bank_account details) | |
| LEADERSHIP section contains: | |
| - supreme_leadership (patron_pope, matron_mother_superior, dean_of_cardinals) | |
| - youth_affairs_director (title, contact) | |
| - organizational_structure | |
| User query: "{message}" | |
| Respond with ONLY the most relevant section name. If the query spans multiple sections or is general, respond with "general". | |
| ROUTING EXAMPLES: | |
| - "Who is the director/pope/patron?" -> leadership | |
| - "What is your mission/vision/values?" -> about | |
| - "Where is headquarters/location/office?" -> about | |
| - "Contact info/phone/email/website?" -> about | |
| - "Tell me about projects/departments?" -> office | |
| - "How to donate/support/contribute?" -> office | |
| - "What activities do you do?" -> office | |
| - "Church beliefs/worship/practices?" -> about | |
| - "What do you do?" -> general | |
| Section:""" | |
| try: | |
| response = self.router_llm.invoke([{"role": "user", "content": router_prompt}]) | |
| section = response.content.strip().lower() | |
| # Validate section exists | |
| if section in available_sections: | |
| return section | |
| elif section == "general": | |
| return "general" | |
| else: | |
| return "about" # Default fallback | |
| except Exception as e: | |
| print(f"Router error: {str(e)}") | |
| return "about" # Default fallback | |
| def chat_response(self, message, history): | |
| """Two-tier LLM system: Router + Specialist response""" | |
| if not message.strip(): | |
| return "Please ask me something about our Legion Maria Youth Affairs!" | |
| try: | |
| if not self.data_content: | |
| return "I don't have that information available right now." | |
| # Step 1: Router LLM decides which section to use | |
| selected_section = self.route_query(message) | |
| print(f"Router selected section: {selected_section}") | |
| # Step 2: Get relevant data based on routing decision | |
| if selected_section == "general": | |
| # Use all data for general queries | |
| relevant_data = self.data_content | |
| else: | |
| # Use only the specific section | |
| relevant_data = {selected_section: self.data_content.get(selected_section, {})} | |
| # Build conversation context | |
| conversation_context = "" | |
| if history: | |
| conversation_context = "Previous conversation:\n" | |
| for user_msg, assistant_msg in history[-3:]: # Keep last 3 exchanges | |
| conversation_context += f"User: {user_msg}\nAssistant: {assistant_msg}\n\n" | |
| conversation_context += "Current conversation:\n" | |
| # Step 3: Response LLM generates answer using only relevant data | |
| response_prompt = f"""You are Santa Legion from the Legion Maria Directorate of Youth Affairs. IMPORTANT: You must ONLY use the information provided below. Do not use any external knowledge or make assumptions. | |
| ONLY USE THIS INFORMATION: | |
| {json.dumps(relevant_data, indent=2)} | |
| {conversation_context}User: {message} | |
| STRICT RULES: | |
| - You are Santa Legion, an assistant for the Legion Maria Youth Affairs (NOT the director yourself) | |
| - Speak as "I" when referring to yourself, "we/our" for the organization | |
| - ONLY answer using the information provided above | |
| - When asked about leadership, refer to them in third person (e.g., "Our director is...") | |
| - Keep responses SHORT (1-3 sentences maximum) | |
| - Do not invent or assume any information not in the provided data | |
| - Never mention being provided documents or data | |
| - If asked about something not in your data, say "I don't have that information" | |
| Answer based ONLY on the provided information:""" | |
| # Get response from specialist LLM | |
| response = self.response_llm.invoke([{"role": "user", "content": response_prompt}]) | |
| return response.content | |
| except Exception as e: | |
| print(f"Error generating response: {str(e)}") | |
| return "I'm sorry, I'm having trouble right now. Please try again." | |
| def main(): | |
| assistant = LegionMariaAssistant() | |
| # Initial greeting message | |
| initial_greeting = [ | |
| [None, "π Hello! I'm Santa Legion from the Legion Maria Youth Affairs. I'm here to help you learn about our mission, leadership, projects, and activities. What would you like to know?"] | |
| ] | |
| # Create mobile-optimized Gradio chat interface | |
| with gr.Blocks(title="Legion Maria Chat", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown("# π¬ Legion Maria YA") | |
| # Mobile-optimized chat interface | |
| chatbot = gr.Chatbot( | |
| value=initial_greeting, | |
| height=400, # Reduced for mobile | |
| show_label=False, | |
| container=True, | |
| bubble_full_width=True, # Better for mobile | |
| show_share_button=False | |
| ) | |
| # Mobile-friendly input layout | |
| with gr.Row(): | |
| msg = gr.Textbox( | |
| placeholder="Ask me anything...", | |
| show_label=False, | |
| scale=5, | |
| container=False, | |
| lines=1 | |
| ) | |
| send_btn = gr.Button("π€", variant="primary", scale=1, size="sm") | |
| clear = gr.Button("π New Chat", variant="secondary", size="sm") | |
| # Chat functionality | |
| def respond(message, history): | |
| if message.strip(): | |
| bot_response = assistant.chat_response(message, history) | |
| history.append([message, bot_response]) | |
| return history, "" | |
| # Event handlers | |
| msg.submit(respond, [msg, chatbot], [chatbot, msg]) | |
| send_btn.click(respond, [msg, chatbot], [chatbot, msg]) | |
| clear.click(lambda: initial_greeting, None, chatbot) | |
| # Launch with better settings for chat app | |
| demo.launch( | |
| share=False, | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| show_api=False | |
| ) | |
| if __name__ == "__main__": | |
| main() |