Spaces:
Sleeping
Sleeping
File size: 8,789 Bytes
590d9bb fd8a24a 590d9bb fd8a24a 590d9bb fd8a24a 590d9bb c5335db 590d9bb c5335db 590d9bb fd8a24a 590d9bb fd8a24a 590d9bb fd8a24a dc37fde fd8a24a dc37fde 590d9bb fd8a24a c5335db fd8a24a 590d9bb fd8a24a 590d9bb c5335db 590d9bb c5335db 590d9bb |
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 |
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() |