@@ -962,3 +962,96 @@ def generate_chat_response(user_message: str, conversation_history: list = []) -
962962 ]
963963 import random
964964 return random .choice (variations )
965+
966+ # =========================================================================
967+ # VIBE-CHECK ENDPOINT LOGIC
968+ # Implements strict validation and caching per PR #903 feedback.
969+ # =========================================================================
970+
971+ import hashlib
972+ from cache_service import cache_service
973+
974+ def generate_vibe_check (raw_vibe_prompt : str , raw_count : int = 5 ) -> Optional [dict ]:
975+ """
976+ Generate an AI vibe check with strict server-side validation and caching.
977+
978+ Args:
979+ raw_vibe_prompt: The raw emotional/vibe query from the user.
980+ raw_count: The requested number of book recommendations.
981+
982+ Returns:
983+ Dict containing the AI response and recommendations, or None on failure.
984+ """
985+
986+ # ---------------------------------------------------------
987+ # 1. VALIDATION: Clamp the Count (Security & Cost Control)
988+ # ---------------------------------------------------------
989+ try :
990+ count = int (raw_count )
991+ if count < 1 :
992+ count = 1
993+ elif count > 10 : # Hard limit to prevent API abuse
994+ count = 10
995+ except (ValueError , TypeError ):
996+ count = 5 # Safe default
997+
998+ # ---------------------------------------------------------
999+ # 2. VALIDATION: Vibe Prompt Length (Prompt Injection Prevention)
1000+ # ---------------------------------------------------------
1001+ if not raw_vibe_prompt or not isinstance (raw_vibe_prompt , str ):
1002+ logger .warning ("generate_vibe_check: Empty or invalid prompt received." )
1003+ return None
1004+
1005+ safe_vibe_prompt = raw_vibe_prompt .strip ()
1006+
1007+ # Restrict prompt to 400 characters to prevent huge payload bills
1008+ if len (safe_vibe_prompt ) > 400 :
1009+ logger .warning (f"generate_vibe_check: Prompt truncated from { len (safe_vibe_prompt )} chars." )
1010+ safe_vibe_prompt = safe_vibe_prompt [:400 ]
1011+
1012+ # ---------------------------------------------------------
1013+ # 3. CACHING: Prevent Expensive Redundant LLM Calls
1014+ # ---------------------------------------------------------
1015+ # Create a deterministic hash of the sanitized inputs
1016+ prompt_hash = hashlib .md5 (f"{ safe_vibe_prompt } _{ count } " .encode ('utf-8' )).hexdigest ()
1017+ cache_key = f"vibe_check_{ prompt_hash } "
1018+
1019+ cached_result = cache_service .get (cache_key )
1020+ if cached_result :
1021+ logger .info (f"generate_vibe_check: Serving cached response for key { cache_key } " )
1022+ return cached_result
1023+
1024+ # ---------------------------------------------------------
1025+ # 4. LLM GENERATION
1026+ # ---------------------------------------------------------
1027+ if not llm_service .is_available ():
1028+ logger .error ("generate_vibe_check: No LLM service available." )
1029+ return None
1030+
1031+ # Construct a specific prompt for the vibe check
1032+ system_prompt = (
1033+ f"You are a literary vibe-checker. The user has described their current mood: '{ safe_vibe_prompt } '. "
1034+ f"Give them a short, poetic reading diagnosis (under 50 words) and recommend exactly { count } books "
1035+ "that perfectly match this specific emotional frequency. Format your response clearly."
1036+ )
1037+
1038+ try :
1039+ response_text = llm_service .generate_text (
1040+ prompt = system_prompt ,
1041+ max_tokens = llm_service .config .get ('recommendation_max_tokens' , 250 )
1042+ )
1043+
1044+ if response_text :
1045+ result = {
1046+ "vibe_diagnosis" : response_text ,
1047+ "sanitized_query" : safe_vibe_prompt ,
1048+ "count_returned" : count
1049+ }
1050+
1051+ # Cache the successful result for 24 hours
1052+ cache_service .set (cache_key , result , timeout = 86400 )
1053+ return result
1054+
1055+ except Exception as e :
1056+ logger .error (f"generate_vibe_check failed: { str (e )} " , exc_info = True )
1057+ return None
0 commit comments