Skip to content

Commit dfa332b

Browse files
feat: Add Gemini vibe-check AI endpoint (Fixes #759)
1 parent 80c59e7 commit dfa332b

2 files changed

Lines changed: 100 additions & 1 deletion

File tree

‎backend/ai_service.py‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# AI service logic with LLM integration (OpenAI/Gemini)
22
# Implements 'generate_book_note' and 'get_ai_recommendations'. All recommendations MUST be AI-based.
3+
34
# Enhanced with comprehensive caching for expensive operations
45

56

@@ -219,6 +220,25 @@ def get_category_books_prompt(category: str, vibe_description: str, count: int =
219220
- Output the JSON array only.
220221
"""
221222

223+
@staticmethod
224+
def get_vibe_check_prompt(vibe_prompt: str, count: int = 3) -> str:
225+
"""
226+
Prompt for generating hyper-personalized vibe-based book recommendations.
227+
"""
228+
return f"""You are Elara, an emotionally intuitive bookseller.
229+
A reader is looking for books matching this exact hyper-specific vibe or mood: "{vibe_prompt}"
230+
231+
Return exactly {count} real, verifiable books that perfectly capture this energy.
232+
Output ONLY a valid JSON array. No markdown fences. No text before or after.
233+
Schema:
234+
[
235+
{{
236+
"title": "Exact book title",
237+
"author": "Author full name",
238+
"reason": "One brilliant, atmospheric sentence explaining why it fits the requested vibe perfectly."
239+
}}
240+
]
241+
"""
222242

223243
class LLMService:
224244
"""
@@ -779,6 +799,46 @@ def get_category_books(category: str, vibe_description: str, count: int = 5) ->
779799
logger.info("get_category_books: %d books returned for '%s'", len(valid_books), category)
780800
return valid_books
781801

802+
def get_vibe_recommendations(vibe_prompt: str, count: int = 3) -> list:
803+
"""
804+
Generate a list of real books based on a highly specific natural-language vibe.
805+
Returns a list of dicts: [{"title": ..., "author": ..., "reason": ...}, ...]
806+
"""
807+
if not llm_service.is_available():
808+
logger.warning("get_vibe_recommendations: no LLM configured")
809+
return []
810+
811+
prompt = PromptTemplates.get_vibe_check_prompt(vibe_prompt, count)
812+
813+
# Utilizing the existing LLMService pipeline
814+
raw = llm_service.generate_text(
815+
prompt,
816+
max_tokens=llm_service.config.get('category_books_max_tokens', 600),
817+
)
818+
819+
if not raw:
820+
logger.error("get_vibe_recommendations: LLM returned None")
821+
return []
822+
823+
# Use the file's existing robust JSON extractor
824+
parsed = _extract_json(raw)
825+
826+
if not isinstance(parsed, list):
827+
logger.error("get_vibe_recommendations: expected JSON array")
828+
return []
829+
830+
# Validate and clean the output
831+
valid_books = []
832+
for item in parsed:
833+
if isinstance(item, dict) and "title" in item and "author" in item:
834+
valid_books.append({
835+
"title": item["title"],
836+
"author": item["author"],
837+
"reason": item.get("reason", "This perfectly matches your requested vibe."),
838+
})
839+
840+
logger.info("get_vibe_recommendations: %d books returned for vibe '%s'", len(valid_books), vibe_prompt)
841+
return valid_books
782842

783843
@cache_mood_tags
784844
def get_book_mood_tags_safe(title: str, author: str = "") -> list:

‎backend/app.py‎

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141

4242
# Environment variables are now loaded centrally in backend/config.py
4343
from config import app_config, setup_logging, validate_required_env_vars
44-
from ai_service import generate_book_note, get_ai_recommendations, get_category_books, get_book_mood_tags_safe, generate_chat_response, llm_service
44+
from ai_service import generate_book_note, get_ai_recommendations, get_category_books, get_book_mood_tags_safe, generate_chat_response, llm_service, get_vibe_recommendations
4545
from models import db, User, Book, ShelfItem, BookNote, ReadingGoal, ReadingStats, Collection, CollectionItem, PriceHistory, PriceAlert, Review, register_user, login_user
4646
from price_tracker import get_price_tracker
4747
from cache_service import cache_service
@@ -839,6 +839,45 @@ def handle_mood_search():
839839
return internal_error(str(e))
840840

841841

842+
@app.route('/api/v1/vibe-check', methods=['POST'])
843+
@rate_limit('vibe_check')
844+
def handle_vibe_check():
845+
"""
846+
Generate hyper-personalized book recommendations based on a specific vibe.
847+
"""
848+
try:
849+
data = request.get_json()
850+
851+
# Safely validate input
852+
if not data or 'vibe_prompt' not in data:
853+
return missing_fields_error(["vibe_prompt"])
854+
855+
vibe_prompt = data['vibe_prompt']
856+
count = data.get('count', 3)
857+
858+
# Call the Gemini pipeline
859+
books = get_vibe_recommendations(
860+
vibe_prompt=vibe_prompt,
861+
count=count
862+
)
863+
864+
if not books:
865+
return service_unavailable_error(
866+
"Could not generate vibe recommendations right now. Please try again shortly."
867+
)
868+
869+
return success_response(
870+
data={
871+
"vibe_prompt": vibe_prompt,
872+
"books": books,
873+
}
874+
)
875+
876+
except Exception as e:
877+
logger.error(f"Error in handle_vibe_check: {str(e)}", exc_info=True)
878+
return internal_error(str(e))
879+
880+
842881
@app.route('/api/v1/category-books', methods=['POST'])
843882
@rate_limit('category_books')
844883
def handle_category_books():

0 commit comments

Comments
 (0)