CoolFace
Apppublic

slugless/shouty-clean

sourceHugging Facemitupdated 8mo agoView on Hugging Face
0likes
app.py189 linesDownload Raw Back to root
1import json2import re3import torch4import openai5from fastapi import FastAPI, Request6from fastapi.responses import JSONResponse7from fastapi.middleware.cors import CORSMiddleware8from sentence_transformers import SentenceTransformer, util9import os10import time11from supabase import create_client12 13SUPABASE_URL = os.environ.get("SUPABASE_URL")14SUPABASE_KEY = os.environ.get("SUPABASE_KEY")15supabase = create_client(SUPABASE_URL, SUPABASE_KEY)16SUPABASE_TABLE = "Questions"17 18app = FastAPI()19 20app.add_middleware(21    CORSMiddleware,22    allow_origins=["*"],23    allow_methods=["*"],24    allow_headers=["*"]25)26 27openai.api_key = os.environ.get("openaikey")28 29data = torch.load("precomputed_embeddings.pt", map_location=torch.device("cpu"))30all_entries = data["entries"]31all_embeddings = data["embeddings"]32embedding_model = SentenceTransformer(33    "sentence-transformers/all-MiniLM-L6-v2"34)35 36DEFAULT_FIRST_PROMPT = (37    "You are a multi-perspective responder.\n\n"38    "For any user question, return exactly 5 short, one-sentence answers. Each answer must:\n"39    "- Be exactly one short sentence, no more.\n"40    "- Repeat the key phrase or subject of the original question verbatim.\n"41    "- Express a distinct point of view that could come from a different type of person, mind, or world.\n"42    "- Vary widely in tone: include at least one response that is emotional, one that is absurd or surreal, and one that is confrontational or intense.\n"43    "- At least one response must express strong certainty, and at least one must express strong uncertainty or doubt.\n"44    "- Responses may be angry, spiritual, naive, bizarre, sarcastic, sincere, alien, conspiratorial, or poetic—just not repetitive or safe.\n\n"45    "Format rules:\n"46    "- Output only the 5 sentences, each on its own line.\n"47    "- Do not include the question, headers, numbers, bullets, or commentary.\n"48    "- Do not explain anything before or after the 5 lines.\n\n"49    "Your goal is to simulate the raw inner monologues of 5 radically different beings reacting to the same question."50)51 52SELECTION_PROMPT = (53    "You will be given a question and a list of possible responses, each numbered from 0 to N-1.\n"54    "Your job is to choose the single best response based on two criteria:\n\n"55    "1. Conversational Reasonability — how naturally it could be said in response to the question. "56    "It does not need to be helpful or polite, just something that would make sense in any kind of conversation (even sarcastic or confrontational ones).\n"57    "2. Humor — how funny it is, whether dry, ironic, or absurd.\n\n"58    "Prefer witty or fitting deflections over wild absurdity or randomness.\n"59    "Return ONLY the index of the best response. Do not explain your reasoning.\n\n"60)61 62def get_multiperspective_responses(question, system_prompt):63    messages = [64        {"role": "system", "content": system_prompt},65        {"role": "user", "content": f"Question: {question}"}66    ]67    response = openai.ChatCompletion.create(68        model="gpt-3.5-turbo", messages=messages, temperature=069    )70    output = response['choices'][0]['message']['content']71    sentences = re.split(r'[.!?]', output)72    return [s.strip() for s in sentences if s.strip()]73 74def get_top_semantic_matches(text, entries, embeddings, model, top_k=10):75    query_embedding = model.encode(text, convert_to_tensor=True)76    cosine_scores = util.cos_sim(query_embedding, embeddings)[0]77    top_results = torch.topk(cosine_scores, k=top_k)78    return [entries[i]["text"] for i in top_results.indices.tolist()]79 80def remove_duplicate_lines(candidates):81    seen = set()82    unique = []83    for line in candidates:84        if line not in seen:85            unique.append(line)86            seen.add(line)87    return unique88 89def select_best_candidate(question, candidates):90    prompt = SELECTION_PROMPT + f"Question: {question}\n\nCandidates:\n"91    for i, candidate in enumerate(candidates):92        prompt += f"{i}: {candidate}\n"93    messages = [{"role": "system", "content": prompt}]94    response = openai.ChatCompletion.create(95        model="gpt-4.1", messages=messages, temperature=096    )97    output = response['choices'][0]['message']['content'].strip()98    try:99        return int(output)100    except:101        return None102 103def process_question(question):104    base_responses = get_multiperspective_responses(question, DEFAULT_FIRST_PROMPT)105    all_candidates = []106    for resp in base_responses:107        similar = get_top_semantic_matches(resp, all_entries, all_embeddings, embedding_model)108        all_candidates.extend(similar)109    unique_candidates = remove_duplicate_lines(all_candidates)110    unique_candidates.sort(key=len, reverse=True)111    best_index = select_best_candidate(question, unique_candidates)112    if best_index is None or best_index < 0 or best_index >= len(unique_candidates):113        return None, None, "No valid candidate was selected."114    best_candidate = unique_candidates[best_index]115    with open("matches_output_with_hash.json", "r") as f:116        hash_matches = json.load(f)117    for entry in hash_matches:118        if "text" in entry and entry["text"] == best_candidate:119            hash_val = entry.get("hash")120            if hash_val:121                return hash_val, best_candidate, None122    return None, best_candidate, "No matching hash found for the selected candidate."123 124@app.post("/lookup")125async def lookup(request: Request):126    try:127        data = await request.json()128        question = data.get("question", "")129        if not question:130            raise ValueError("No question provided")131 132        # Get real client IP if behind proxy133        client_ip = request.headers.get("x-forwarded-for", request.client.host)134        client_ip = client_ip.split(",")[0].strip()135 136        # Generate 5 base responses from multiperspective prompt137        base_responses = get_multiperspective_responses(question, DEFAULT_FIRST_PROMPT)138 139        # Collect all semantically similar lines140        all_candidates = []141        for resp in base_responses:142            similar = get_top_semantic_matches(resp, all_entries, all_embeddings, embedding_model)143            all_candidates.extend(similar)144 145        # Deduplicate + sort146        unique_candidates = remove_duplicate_lines(all_candidates)147        unique_candidates.sort(key=len, reverse=True)148 149        # Select best final line150        best_index = select_best_candidate(question, unique_candidates)151        if best_index is None or best_index < 0 or best_index >= len(unique_candidates):152            raise ValueError("No valid candidate was selected.")153        final_choice = unique_candidates[best_index]154 155        # Match to hash156        with open("matches_output_with_hash.json", "r") as f:157            hash_matches = json.load(f)158 159        matched_hash = None160        for entry in hash_matches:161            if "text" in entry and entry["text"] == final_choice:162                matched_hash = entry.get("hash")163                break164 165        if not matched_hash:166            raise ValueError("No matching hash found for the selected candidate.")167 168        # Prepare log payload169        log_payload = {170            "client_ip": client_ip,171            "question": question,172            "generated_answers": base_responses,173            "candidate_lines": unique_candidates,174            "final_line_choice": final_choice,175            "hash": matched_hash176        }177 178        # Insert and time it179        import time180        start = time.time()181        supabase.table(SUPABASE_TABLE).insert(log_payload, returning="minimal").execute()182        elapsed = time.time() - start183        print(f"✅ Supabase insert took {elapsed:.4f} seconds")184 185        return {"video": f"{matched_hash}.mp4"}186 187    except Exception as e:188        return JSONResponse(status_code=500, content={"error": str(e)})189