recommendation / app.py
sundaram22verma's picture
msid api bug fix
60572ef
raw
history blame
15 kB
# import gradio as gr
# from src.main import app as fastapi_app
# from src.core.recommender import recommender
# import logging
# from src.database.mongodb import mongodb
# logger = logging.getLogger(__name__)
# # Configure basic logging if not already set up elsewhere for Gradio context
# logging.basicConfig(level=logging.INFO)
# # Manually initialize and attach recommender if not already present
# if not hasattr(fastapi_app.state, "recommender"):
# recommender.load_components()
# fastapi_app.state.recommender = recommender
# def get_recommendations_gradio(query: str, k: int = 5):
# try:
# response = fastapi_app.state.recommender.get_recommendations(query, k)
# return response
# except Exception as e:
# return {"error": str(e)}
# def get_recommendations_by_id_gradio(msid: str, k: int = 5):
# try:
# # Corrected method name here
# response = fastapi_app.state.recommender.get_recommendations_by_id(msid, k)
# return response
# except Exception as e:
# return {"error": str(e)}
# def get_recommendations_user_feedback_gradio(user_id: str, msid: str, clicked_msid: str, k: int = 5):
# """
# Handles user feedback via Gradio:
# 1. (Optionally) Computes recommendations based on the feedback (mimicking FastAPI endpoint action).
# 2. Saves the feedback to MongoDB.
# 3. Returns a success message.
# """
# try:
# # Step 1: (Optional) Compute recommendations based on feedback, similar to FastAPI endpoint.
# # The result of this call is not the final output for this Gradio UI, per the request.
# # This ensures the Gradio function performs similar actions to the API endpoint.
# _ = fastapi_app.state.recommender.get_recommendations_user_feedback(user_id, msid, clicked_msid, k)
# logger.info(f"Gradio: (Computed recommendations for user '{user_id}' based on click, not shown in this UI)")
# # Step 2: Save feedback to MongoDB
# actual_clicked_msids = [s.strip() for s in clicked_msid.split(',') if s.strip()]
# if not actual_clicked_msids:
# logger.warning(f"Gradio: Invalid clicked_msid: {clicked_msid} for user {user_id}")
# return {"error": "clicked_msid parameter is invalid or does not contain valid MSIDs."}
# logger.info(
# f"Gradio: Saving feedback for user '{user_id}', context msid: '{msid}', clicked msids: {actual_clicked_msids}"
# )
# feedback_collection_name = "user_feedback_tracking"
# # Assuming mongodb.db is the PyMongo Database object, consistent with recommender.py
# feedback_collection = mongodb.db[feedback_collection_name]
# user_doc = feedback_collection.find_one({"user_id": user_id})
# if user_doc:
# # This update logic mirrors the one in routes.py
# feedback_collection.update_one(
# {"user_id": user_id},
# {"$addToSet": {"Articles": {"msid": msid, "Read": {"$each": actual_clicked_msids}}}}
# )
# else:
# feedback_collection.insert_one({
# "user_id": user_id,
# "Articles": [{"msid": msid, "Read": actual_clicked_msids}]
# })
# logger.info(f"Gradio: Successfully saved feedback for user {user_id}")
# return {"message": "Response saved successfully"}
# except Exception as e:
# logger.error(f"Gradio: Error in get_recommendations_user_feedback_gradio for user {user_id}: {e}", exc_info=True)
# return {"error": f"An error occurred: {str(e)}"}
# def get_recommendations_summary_gradio(msid: str, k: int = 5, summary: bool = True, smart_tip: bool = True):
# try:
# response = fastapi_app.state.recommender.get_recommendations_summary(msid, k, summary, smart_tip)
# return response
# except Exception as e:
# return {"error": str(e)}
# iface1 = gr.Interface(
# fn=get_recommendations_gradio,
# inputs=[
# gr.Textbox(label="Query", placeholder="Enter your search query..."),
# gr.Slider(minimum=1, maximum=10, value=5, step=1, label="Number of recommendations")
# ],
# outputs=gr.JSON(),
# title="Recommendation System",
# description="Enter a query to get personalized recommendations."
# )
# iface2 = gr.Interface(
# fn=get_recommendations_by_id_gradio,
# inputs=[
# gr.Textbox(label="MSID", placeholder="Enter the MSID..."),
# gr.Slider(minimum=1, maximum=10, value=5, step=1, label="Number of recommendations")
# ],
# outputs=gr.JSON(),
# title="Recommendations by MSID",
# description="Enter an MSID to get recommendations based on it."
# )
# iface3 = gr.Interface(
# fn=get_recommendations_user_feedback_gradio,
# inputs=[
# gr.Textbox(label="User ID", placeholder="Enter your user ID..."),
# gr.Textbox(label="MSID", placeholder="Enter the MSID..."),
# gr.Textbox(label="Clicked MSID", placeholder="Enter the clicked MSID..."),
# gr.Slider(minimum=1, maximum=10, value=5, step=1, label="Number of recommendations")
# ],
# outputs=gr.JSON(),
# title="User Feedback Recommendations",
# description="Enter your user ID, MSID, and clicked MSID to get recommendations based on user feedback."
# )
# iface4 = gr.Interface(
# fn=get_recommendations_summary_gradio,
# inputs=[
# gr.Textbox(label="MSID", placeholder="Enter the MSID..."),
# gr.Slider(minimum=1, maximum=10, value=5, step=1, label="Number of recommendations"),
# gr.Checkbox(label="Summary", value=True),
# gr.Checkbox(label="Smart Tip", value=True)
# ],
# outputs=gr.JSON(),
# title="Recommendations Summary",
# description="Enter an MSID to get a summary of recommendations."
# )
# demo = gr.TabbedInterface([iface1, iface2, iface3, iface4], ["Query Recommendations", "MSID Recommendations", "User Feedback Recommendations", "Recommendations Summary"])
# if __name__ == "__main__":
# demo.launch()
# app = fastapi_app
import logging
import uvicorn
from fastapi import HTTPException
from pydantic import BaseModel, Field
from typing import List
from src.main import app as fastapi_app # Existing FastAPI app instance
from src.core.recommender import recommender
from src.database.mongodb import mongodb
logger = logging.getLogger(__name__)
# Configure basic logging if not already set up elsewhere
if not logger.hasHandlers():
logging.basicConfig(level=logging.INFO)
# Manually initialize and attach recommender to the imported fastapi_app's state
# This will run once when the module is loaded.
if not hasattr(fastapi_app.state, "recommender") or fastapi_app.state.recommender is None:
logger.info("Recommender not found on fastapi_app.state, loading components...")
recommender.load_components()
fastapi_app.state.recommender = recommender
if hasattr(fastapi_app.state, "recommender") and fastapi_app.state.recommender is not None:
logger.info("Recommender loaded successfully onto fastapi_app.state.")
else:
logger.error("Failed to load recommender onto fastapi_app.state.")
# Depending on the application's needs, you might want to raise an error here
# or prevent the app from starting if the recommender is critical.
# Pydantic models for request/response bodies
class FeedbackPayload(BaseModel):
user_id: str
msid: str
clicked_msid: str # Comma-separated string of MSIDs
k: int = Field(default=5, ge=1, le=10)
class FeedbackResponse(BaseModel):
message: str
# API Endpoints
@fastapi_app.get("/recommendations/")
async def get_recommendations_api(query: str, k: int = 5):
"""
Get recommendations based on a textual query.
"""
try:
if not hasattr(fastapi_app.state, "recommender") or fastapi_app.state.recommender is None:
logger.error("Recommender is not available.")
raise HTTPException(status_code=503, detail="Recommender service not available")
response = fastapi_app.state.recommender.get_recommendations(query, k)
return response
except Exception as e:
logger.error(f"API Error in get_recommendations_api for query '{query}': {e}", exc_info=True)
raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}")
@fastapi_app.get("/recommendations/msid/")
async def get_recommendations_by_id_api(msid: str, k: int = 5):
"""
Get recommendations based on a given MSID.
"""
try:
# Validate input parameters
if not msid or not isinstance(msid, str):
raise HTTPException(status_code=400, detail="Invalid MSID provided")
if not isinstance(k, int) or k < 1 or k > 10:
raise HTTPException(status_code=400, detail="k must be an integer between 1 and 10")
# Check if recommender service is available
if not hasattr(fastapi_app.state, "recommender") or fastapi_app.state.recommender is None:
logger.error("Recommender is not available.")
raise HTTPException(status_code=503, detail="Recommender service not available")
# Get recommendations with error handling
try:
response = fastapi_app.state.recommender.get_recommendations_by_id(msid, k)
if not response:
raise HTTPException(status_code=404, detail=f"No recommendations found for MSID: {msid}")
return response
except ValueError as ve:
logger.error(f"Value error in get_recommendations_by_id for msid '{msid}': {ve}")
raise HTTPException(status_code=400, detail=str(ve))
except Exception as e:
logger.error(f"Error getting recommendations for msid '{msid}': {e}", exc_info=True)
raise HTTPException(status_code=500, detail="Internal server error while getting recommendations")
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Unexpected error in get_recommendations_by_id_api for msid '{msid}': {e}", exc_info=True)
raise HTTPException(status_code=500, detail="An unexpected error occurred")
@fastapi_app.post("/recommendations/feedback/user/", response_model=FeedbackResponse)
async def submit_user_feedback_api(payload: FeedbackPayload):
"""
Submit user feedback (e.g., clicked articles) and save it.
Optionally, this endpoint can also trigger re-computation of recommendations based on feedback,
though the primary response here is the status of feedback submission.
"""
try:
if not hasattr(fastapi_app.state, "recommender") or fastapi_app.state.recommender is None:
logger.error("Recommender is not available.")
raise HTTPException(status_code=503, detail="Recommender service not available")
# (Optional) Compute recommendations based on feedback, similar to Gradio function.
# The result of this call is not the primary output of this API endpoint.
_ = fastapi_app.state.recommender.get_recommendations_user_feedback(
payload.user_id, payload.msid, payload.clicked_msid, payload.k
)
logger.info(f"API: (Computed recommendations for user '{payload.user_id}' based on click, not part of this response)")
# Save feedback to MongoDB
actual_clicked_msids = [s.strip() for s in payload.clicked_msid.split(',') if s.strip()]
if not actual_clicked_msids:
logger.warning(f"API: Invalid clicked_msid: '{payload.clicked_msid}' for user '{payload.user_id}'")
raise HTTPException(status_code=400, detail="clicked_msid parameter is invalid or does not contain valid MSIDs.")
logger.info(
f"API: Saving feedback for user '{payload.user_id}', context msid: '{payload.msid}', clicked msids: {actual_clicked_msids}"
)
feedback_collection_name = "user_feedback_tracking"
# Assuming mongodb.db is the PyMongo Database object
if mongodb.db is None:
logger.error("MongoDB database connection is not available.")
raise HTTPException(status_code=503, detail="Database service not available")
feedback_collection = mongodb.db[feedback_collection_name]
user_doc = feedback_collection.find_one({"user_id": payload.user_id})
if user_doc:
feedback_collection.update_one(
{"user_id": payload.user_id},
{"$addToSet": {"Articles": {"msid": payload.msid, "Read": actual_clicked_msids}}}
)
else:
feedback_collection.insert_one({
"user_id": payload.user_id,
"Articles": [{"msid": payload.msid, "Read": actual_clicked_msids}]
})
logger.info(f"API: Successfully saved feedback for user '{payload.user_id}'")
return FeedbackResponse(message="Response saved successfully")
except HTTPException:
raise # Re-raise HTTPException directly
except Exception as e:
logger.error(f"API Error in submit_user_feedback_api for user '{payload.user_id}': {e}", exc_info=True)
raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}")
@fastapi_app.get("/recommendations/summary/")
async def get_recommendations_summary_api(msid: str, k: int = 5, summary: bool = True, smart_tip: bool = True):
"""
Get recommendations with optional summary and smart tip for a given MSID.
"""
try:
if not hasattr(fastapi_app.state, "recommender") or fastapi_app.state.recommender is None:
logger.error("Recommender is not available.")
raise HTTPException(status_code=503, detail="Recommender service not available")
try:
response = fastapi_app.state.recommender.get_recommendations_summary(msid, k, summary, smart_tip)
except RuntimeError as e:
# Catch the meta tensor error and return a fallback
if "meta tensor" in str(e):
logger.error("Summary model error: %s", e)
response = {
"msid": msid,
"recommendations": [],
"summary": [],
"smart_tip": [],
"error": "Summary model is not available on this server."
}
else:
raise
return response
except Exception as e:
logger.error(f"API Error in get_recommendations_summary_api for msid '{msid}': {e}", exc_info=True)
raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}")
# This makes fastapi_app (imported from src.main and extended here) available as 'app'
# for ASGI servers like Uvicorn.
app = fastapi_app
if __name__ == "__main__":
uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)