Spaces:
Sleeping
Sleeping
| import os | |
| import shutil | |
| import json | |
| # Set custom cache directories to avoid permission issues. New | |
| os.environ["HF_HOME"] = "/tmp/huggingface" | |
| os.makedirs("/tmp/huggingface", exist_ok=True) | |
| os.environ["XDG_CACHE_HOME"] = "/tmp/.cache" | |
| os.makedirs("/tmp/.cache", exist_ok=True) | |
| from fastapi import FastAPI, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel | |
| from typing import List, Optional | |
| import torch | |
| from transformers import AutoModelForCausalLM, AutoTokenizer, AutoConfig | |
| import re | |
| # Hugging Face model config | |
| REPO_NAME = "jaydatech/phi3-finetuned-project" | |
| BASE_MODEL = "microsoft/Phi-3-mini-4k-instruct" | |
| HF_TOKEN = os.getenv("HF_TOKEN") # Load from Render environment variable | |
| # # Optional: Cleanup if corrupted config is detected | |
| # def check_and_cleanup_corrupt_cache(repo_name: str): | |
| # cache_dir = os.environ["HF_HOME"] | |
| # model_dir = os.path.join(cache_dir, f"models--{repo_name.replace('/', '--')}") | |
| # if os.path.exists(model_dir): | |
| # for root, dirs, files in os.walk(model_dir): | |
| # for file in files: | |
| # if file == "config.json": | |
| # path = os.path.join(root, file) | |
| # try: | |
| # with open(path, "r") as f: | |
| # json.load(f) | |
| # except json.JSONDecodeError: | |
| # print(f"Corrupted config file detected at {path}, cleaning up...") | |
| # shutil.rmtree(model_dir, ignore_errors=True) | |
| # return | |
| # check_and_cleanup_corrupt_cache(REPO_NAME) | |
| app = FastAPI() | |
| # Enable CORS for frontend access | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], # Or specify your frontend domain | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # Hugging Face model config | |
| # REPO_NAME = "jaydatech/phi3-finetuned-project" | |
| # BASE_MODEL = "microsoft/Phi-3-mini-4k-instruct" | |
| # HF_TOKEN = os.getenv("HF_TOKEN") # Load from Render environment variable | |
| config = AutoConfig.from_pretrained(REPO_NAME, token=HF_TOKEN) | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL, trust_remote_code=True) | |
| model = AutoModelForCausalLM.from_pretrained( | |
| REPO_NAME, | |
| config = config, | |
| token=HF_TOKEN, | |
| torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32, | |
| device_map="auto" | |
| ) | |
| # Message and request models | |
| class ChatMessage(BaseModel): | |
| role: str | |
| text: str | |
| class ChatRequest(BaseModel): | |
| message: str | |
| history: Optional[List[ChatMessage]] = [] | |
| def is_farewell(message: str) -> bool: | |
| farewells = ["bye", "goodbye", "see you", "farewell", "exit", "quit", "end"] | |
| message_lower = message.lower().strip() | |
| return any(re.search(rf"\b{re.escape(f)}\b", message_lower) for f in farewells) | |
| # ============================= Recognizes "attend" but with "end" in it... Fix is above ^ ============================= | |
| # farewells = ["bye", "goodbye", "see you", "farewell", "exit", "quit", "end"] | |
| # message_lower = message.lower().strip() | |
| # return any(farewell in message_lower for farewell in farewells) | |
| def clean_input_text(text: str) -> str: | |
| # Remove any "Instruction N: ..." or similar phrases | |
| return re.sub(r"Instruction\s*\d+\s*\(.*?\):", "", text, flags=re.IGNORECASE) | |
| async def chat(request: ChatRequest): | |
| try: | |
| history = request.history | |
| user_message = request.message | |
| if is_farewell(user_message): | |
| return { | |
| "response": "Goodbye! Feel free to chat again if you have more questions.", | |
| "terminate": True | |
| } | |
| conversation = ( | |
| "<|system|>\nYou are an AI assistant for the Federal Reserve Bank of St. Louis. " | |
| "Answer questions based ONLY on your knowledge of the Federal Reserve Bank of St. Louis. " | |
| "If the answer is NOT in the training data, respond with: 'I don't think this information is available. Maybe rephrase for me!'. " | |
| "Answer ONLY what the user asks. Do not volunteer information unless specifically requested. " | |
| "Do NOT ask: 'How can I assist you today?' or 'What can I do for you today?' after every response you give. " | |
| "Provide concise answers to the exact question asked and nothing more.\n" | |
| ) | |
| seen_user_messages = set() | |
| seen_model_messages = set() | |
| for msg in history: | |
| if msg.role == "user": | |
| cleaned_user_text = clean_input_text(msg.text.strip()) | |
| if cleaned_user_text and cleaned_user_text not in seen_user_messages: | |
| conversation += f"<|user|>\n{cleaned_user_text}\n" | |
| seen_user_messages.add(cleaned_user_text) | |
| elif msg.role == "model": | |
| cleaned_model_text = clean_input_text(msg.text.strip()) | |
| if cleaned_model_text and cleaned_model_text not in seen_model_messages: | |
| conversation += f"<|assistant|>\n{cleaned_model_text}\n" | |
| seen_model_messages.add(cleaned_model_text) | |
| # seen_messages = set() | |
| # for msg in history: | |
| # if msg.role == "user" and msg.text.strip() not in seen_messages: | |
| # cleaned_user_text = clean_input_text(msg.text.strip()) | |
| # conversation += f"<|user|>\n{cleaned_user_text}\n" | |
| # seen_messages.add(cleaned_user_text) | |
| # elif msg.role == "model": | |
| # cleaned_model_text = clean_input_text(msg.text.strip()) | |
| # conversation += f"<|assistant|>\n{cleaned_model_text}\n" | |
| #conversation += f"<|user|>\n{user_message.strip()}\n<|assistant|>" | |
| conversation += f"<|user|>\n{clean_input_text(user_message.strip())}\n<|assistant|>" | |
| inputs = tokenizer(conversation, return_tensors="pt", padding=True, truncation=True, max_length=4096).to(device) | |
| with torch.no_grad(): | |
| outputs = model.generate( | |
| **inputs, | |
| max_new_tokens=130, | |
| do_sample=True, | |
| temperature=0.1, | |
| top_k=5, | |
| pad_token_id=tokenizer.eos_token_id | |
| ) | |
| full_response = tokenizer.decode(outputs[0], skip_special_tokens=False) | |
| assistant_response = "" | |
| if "<|assistant|>" in full_response: | |
| assistant_sections = full_response.split("<|assistant|>") | |
| for section in reversed(assistant_sections): | |
| cleaned = section.strip() | |
| if cleaned: | |
| cleaned = re.split( | |
| r"(<\|user\|>|<\|system\|>|<\|assistant\|>|\nuser[:\s]|<\|endoftext\|>)", | |
| cleaned | |
| )[0] | |
| cleaned = re.sub(r"\n?(User|Assistant)\s*[::\-–]\s*.*", "", cleaned, flags=re.IGNORECASE).strip() | |
| assistant_response = cleaned | |
| break | |
| if not assistant_response: | |
| assistant_response = "⚠️ Sorry, I couldn't generate a response." | |
| assistant_response = re.sub(r'\*\* Instruction \*\*:.*?(?=\n\n|\n$|$)', '', assistant_response, flags=re.DOTALL) | |
| assistant_response = re.sub(r'\*\* Instruction \*\*.*?(?=\n\n|\n$|$)', '', assistant_response, flags=re.DOTALL) | |
| assistant_response = re.sub(r'\n{3,}', '\n\n', assistant_response).strip() | |
| assistant_response = re.sub(r"(How can I assist you today\?|What else can I help you with\?|How can I help you today\?)", "", assistant_response, flags=re.IGNORECASE).strip() | |
| def remove_repeated_sentences(response): | |
| sentences = response.split(". ") | |
| seen = set() | |
| cleaned = [] | |
| for sentence in sentences: | |
| if sentence not in seen: | |
| cleaned.append(sentence) | |
| seen.add(sentence) | |
| return ". ".join(cleaned) | |
| assistant_response = remove_repeated_sentences(assistant_response) | |
| return {"response": assistant_response} | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |