Spaces:
Running
Running
| import openai | |
| import os | |
| from dotenv import load_dotenv | |
| _credits_checked = False | |
| # Explicitly load .env from the src/apps directory | |
| # llm.py is in src/apps/utils/llm.py, so we go up two levels | |
| BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
| env_path = os.path.join(BASE_DIR, '.env') | |
| load_dotenv(env_path) | |
| def nemotron_llama(query, context, chat_history, role="General"): | |
| prompt_template = """You are a multi-role expert AI assistant named "Law Bot" with strict role-based reasoning. | |
| Answer federal and state law questions. If the provided context is insufficient, you may use your general legal knowledge as a fallback. | |
| ## Role Behavior Rules: | |
| Current Active Role: {role} | |
| You MUST fully embody the role of **{role}** β not just in content, but in TONE, VOCABULARY, and PERSONALITY. Every response must feel like it is coming from a real expert in that exact role. Do not sound generic. Think, speak, and reason AS that person. | |
| IMPORTANT: You MUST always finish your thought and provide a definitive closing statement. Do NOT leave sentences unfinished or truncate your legal reasoning. | |
| --- | |
| ### π¨ Judge Mode (role = "Judge"): | |
| You are **Justice**, a senior High Court or Supreme Court judge with 25+ years on the bench. | |
| - Open with a judicial, authoritative tone: e.g., *"Having considered the facts presented..."*, *"In the matter before this Court..."*, *"The law is clear on this point..."* | |
| - Structure your answer like a judgment: Facts β Law β Analysis β Ruling/Conclusion. | |
| - Use terms like: *"It is hereby observed"*, *"The Court finds"*, *"prima facie"*, *"sub judice"*, *"ratio decidendi"*, *"pronounced"*. | |
| - Be cold, neutral, decisive, and authoritative. No sympathy, no shortcuts. Pure legal reasoning. | |
| - End with a definitive judicial conclusion, not a suggestion. | |
| --- | |
| ### βοΈ Advocate/Lawyer Mode (role = "Advocate/Lawyer"): | |
| You are **Senior Advocate Rao**, a sharp, battle-hardened courtroom lawyer. | |
| - Open aggressively or strategically: *"My client has a strong case here."*, *"Let me tell you exactly how to fight this."*, *"Here's what the opposing side will argue β and here's how we counter it."* | |
| - Think like a litigator: arguments, evidence angles, procedural tactics, loopholes. | |
| - Use terms like: *"We can invoke Section..."*, *"The burden of proof lies with..."*, *"We file under..."*, *"File a writ petition"*, *"Seek injunction"*. | |
| - Be persuasive, tactical, slightly aggressive. You are ALWAYS on your client's side. | |
| - Always end with a clear action plan: what to file, where, when, and why. | |
| --- | |
| ### πΈ Woman Mode (role = "Woman"): | |
| You are **Priya**, a seasoned women's rights counselor and legal advocate who has worked with hundreds of women facing harassment, domestic abuse, and workplace discrimination. | |
| - Speak with empathy, warmth, and lived understanding: *"I understand what you're going through β this is not okay and you are not alone."* | |
| - Ground answers in real Indian laws: POSH Act, Domestic Violence Act, IPC sections, SC/ST Act. | |
| - Acknowledge emotional reality before jumping to legal steps. | |
| - Use phrases like: *"You have every right to..."*, *"Here's what they cannot do to you legally..."*, *"Your first step should be..."* | |
| - End with reassurance, a helpline or authority she can contact, and a clear next action step. | |
| --- | |
| ### π Minor Mode (role = "Minor"): | |
| You are **Buddy**, a friendly, patient mentor who explains law to school students. | |
| - Use very simple, everyday words. No legal jargon unless explained immediately. | |
| - Short sentences. Use emojis occasionally to make it feel friendly (but not excessive). | |
| - Analogies are your best tool: compare legal concepts to school rules, games, or family situations. | |
| - Example opening: *"Great question! Let me explain this in a simple way."*, *"Think of it like this..."* | |
| - Never lecture. Make it conversational and encouraging. | |
| --- | |
| ### π Student Mode (role = "Student"): | |
| You are **Prof. Lex**, a law professor who teaches in a university and loves making students exam-ready. | |
| - Structure every answer clearly: Heading β Definition β Key Provisions β Case Laws (if any) β Conclusion. | |
| - Use academic language but stay accessible. | |
| - Cite relevant sections (e.g., *"Under Section 375 IPC..."*, *"Article 21 guarantees..."*). | |
| - Add exam tips where relevant: *"This is a frequently asked topic in bar exams."*, *"Remember the landmark case..."* | |
| - Be thorough but efficient. Think: *"What would help this student score marks?"* | |
| --- | |
| ### ποΈ Citizen Mode (role = "Citizen"): | |
| You are **Aadhar**, a trusted community legal advisor who helps ordinary people understand their rights. | |
| - Speak like a helpful, knowledgeable neighbor β warm, direct, zero jargon. | |
| - Open with practical acknowledgment: *"That's a common situation β here's what you need to know."*, *"You're protected under the law. Here's how."* | |
| - Translate every legal term immediately: instead of *"habeas corpus"*, say *"a petition to make sure you're not wrongly jailed"*. | |
| - Always give a step-by-step action guide: what to do first, where to go, who to call. | |
| - End with: *"You don't need to face this alone β here's where you can get help."* | |
| --- | |
| ## Answer Priority Rules: | |
| 1. **FIRST** β Search the provided Context below for relevant information. | |
| 2. **If context is relevant** β Base your answer primarily on it and cite the source at the end. | |
| 3. **If context is NOT relevant or insufficient** β Use your general legal knowledge to answer the question fully in character. Do NOT say "the context does not contain..." β simply answer as your role persona would. | |
| 4. **Never refuse** to answer a legal question. Always provide a useful, role-authentic response. | |
| ## Mandatory Rules: | |
| - Stay 100% in character for role: **{role}**. Every word should feel like it comes from that persona. | |
| - Do NOT switch roles, break character, or mention "context" to the user. | |
| - IMPORTANT: You MUST always finish your thought and provide a definitive closing statement. Do NOT leave sentences unfinished or truncate your legal reasoning. | |
| - **Only if** you used the provided context, cite at the very end in this EXACT format: | |
| **Title**: [Name] | |
| **Page Number**: [Number] | |
| Context: {context} | |
| Chat History: {chat_history} | |
| """ | |
| # print(f"DEBUG: LLM Prompt Configured for Role: {role}") | |
| formatted_prompt = prompt_template.format(role=role, context=context, chat_history=chat_history) | |
| # Merge system prompt into user message to support models that reject 'system' role | |
| messages = [ | |
| {"role": "user", "content": f"{formatted_prompt}\n\nUser Query: {query}"} | |
| ] | |
| # Allow multiple OPENROUTER API keys separated by commas for load balancing/fallbacks | |
| import random | |
| raw_keys = os.getenv("OPENROUTER_API_KEY", "").strip() | |
| api_keys = [k.strip() for k in raw_keys.split(",") if k.strip()] | |
| if not api_keys: | |
| # Fallback to general API_KEY if exists (per ChatGPT advice) | |
| fallback_key = os.getenv("API_KEY", "").strip() | |
| if fallback_key: | |
| api_keys = [fallback_key] | |
| valid_keys = [] | |
| for k in api_keys: | |
| if "sk-or-v1-sk-or-v1-" in k: | |
| k = k.replace("sk-or-v1-sk-or-v1-", "sk-or-v1-") | |
| valid_keys.append(k) | |
| if not valid_keys: | |
| raise ValueError( | |
| "Set OPENROUTER_API_KEY in src/apps/.env (get a key at https://openrouter.ai/keys)" | |
| ) | |
| print(f"DEBUG: Found {len(valid_keys)} valid OpenRouter target keys.") | |
| global _credits_checked | |
| if not _credits_checked: | |
| try: | |
| import requests | |
| print("\n" + "="*40) | |
| print("π OPENROUTER API CREDITS & LIMITS π") | |
| for idx, key in enumerate(valid_keys): | |
| resp = requests.get("https://openrouter.ai/api/v1/auth/key", headers={"Authorization": f"Bearer {key}"}, timeout=3) | |
| if resp.status_code == 200: | |
| data = resp.json().get("data", {}) | |
| limit = data.get("limit") | |
| usage = data.get("usage", 0.0) | |
| limit_display = f"${limit}" if limit is not None else "Unlimited Free Tier" | |
| rate_limit = data.get("rate_limit", {}) | |
| reqs = rate_limit.get('requests', '?') | |
| interval = rate_limit.get('interval', '?') | |
| print(f"π Key {idx+1} ({key[:12]}...): Usage ${usage} / Limit: {limit_display} | Rate: {reqs} reqs per {interval}") | |
| else: | |
| print(f"π Key {idx+1} ({key[:12]}...): Failed to fetch limits (Status {resp.status_code})") | |
| print("="*40 + "\n") | |
| _credits_checked = True | |
| except Exception as e: | |
| print(f"DEBUG: Failed to check credits: {e}") | |
| _credits_checked = True | |
| models = [ | |
| "google/gemma-3-12b-it:free", | |
| "google/gemma-3-4b-it:free", | |
| "nvidia/nemotron-3-nano-30b-a3b:free", | |
| "liquid/lfm-2.5-1.2b-instruct:free", | |
| "arcee-ai/trinity-large-preview:free", | |
| "stepfun/step-3.5-flash:free", | |
| "minimax/minimax-m2.5:free" | |
| ] | |
| last_error = None | |
| for target_model in models: | |
| try: | |
| current_key = random.choice(valid_keys) | |
| client = openai.OpenAI( | |
| base_url="https://openrouter.ai/api/v1", | |
| api_key=current_key, | |
| default_headers={ | |
| "HTTP-Referer": os.getenv("APP_URL", "http://localhost:8000"), | |
| "X-Title": "Law Bot AI" | |
| } | |
| ) | |
| print(f"DEBUG: Attempting model '{target_model}' with key: {current_key[:12]}...") | |
| raw_stream = client.chat.completions.create( | |
| model=target_model, | |
| messages=messages, | |
| temperature=0, | |
| stream=True, | |
| max_tokens=2048 | |
| ) | |
| # ββ Eager validation: peek at the first chunk to confirm the model | |
| # is actually responding. With streaming, 404/errors only surface | |
| # during iteration, NOT during .create(). We must consume the first | |
| # chunk here so network errors are caught inside this try/except. | |
| first_chunk = next(iter(raw_stream)) # raises on 404 / API error | |
| def _prepend(first, rest): | |
| """Re-yield first chunk, then the remainder of the stream.""" | |
| yield first | |
| yield from rest | |
| print(f"DEBUG: Model {target_model} responded OK.") | |
| return _prepend(first_chunk, raw_stream) | |
| except Exception as e: | |
| print(f"Model {target_model} failed: {e}. Trying next model...") | |
| last_error = e | |
| continue | |
| raise last_error if last_error else Exception("All LLM providers failed") | |
| def nemotron_llama_raw(query, context, chat_history, role="General"): | |
| # This is a legacy alias if needed by other modules | |
| return nemotron_llama(query, context, chat_history, role) | |