Spaces:
Running
Running
Update main.py
Browse files
main.py
CHANGED
@@ -851,73 +851,105 @@ async def _call_gemini(text: str, summary_type: str, model_name: str) -> Tuple[O
|
|
851 |
logger.info(f"[Gemini {model_name}] Generating {summary_type} summary using {model_name}. Input length: {len(text)}")
|
852 |
|
853 |
prompt = PROMPT_PARAGRAPH if summary_type == "paragraph" else PROMPT_POINTS
|
|
|
|
|
854 |
MAX_INPUT_LENGTH_GEMINI = 900000
|
|
|
|
|
855 |
if len(text) > MAX_INPUT_LENGTH_GEMINI:
|
856 |
-
logger.warning(f"[Gemini {model_name}] Input length ({len(text)}) exceeds limit ({MAX_INPUT_LENGTH_GEMINI}). Truncating.");
|
857 |
text = text[:MAX_INPUT_LENGTH_GEMINI] + "... (Content truncated)"
|
858 |
full_prompt = f"{prompt}\n\n{text}"
|
859 |
|
860 |
-
#
|
861 |
-
#
|
862 |
try:
|
863 |
safety_settings = {
|
864 |
HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_NONE,
|
865 |
HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_NONE,
|
866 |
HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_NONE,
|
867 |
HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_NONE,
|
868 |
-
HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY: HarmBlockThreshold.BLOCK_NONE, #
|
869 |
}
|
870 |
logger.debug(f"[Gemini {model_name}] Using safety settings: { {k.name: v.name for k, v in safety_settings.items()} }")
|
871 |
-
except AttributeError:
|
872 |
-
|
873 |
-
|
874 |
-
logger.error(f"[Gemini {model_name}] Failed to define safety settings. HarmCategory/HarmBlockThreshold missing?")
|
875 |
-
return None, f"Sorry, an internal error occurred configuring the AI service ({model_name})."
|
876 |
|
877 |
try:
|
878 |
logger.debug(f"[Gemini {model_name}] Initializing model {model_name}")
|
879 |
model = genai.GenerativeModel(model_name)
|
880 |
logger.info(f"[Gemini {model_name}] Sending request to Gemini ({model_name})...")
|
881 |
-
request_options = {"timeout": 120}
|
882 |
response = await model.generate_content_async(
|
883 |
-
full_prompt, generation_config=genai.types.GenerationConfig(),
|
884 |
safety_settings=safety_settings, request_options=request_options )
|
885 |
logger.info(f"[Gemini {model_name}] Received response from Gemini.")
|
|
|
|
|
886 |
if response.prompt_feedback and response.prompt_feedback.block_reason:
|
887 |
block_reason_str = getattr(response.prompt_feedback.block_reason, 'name', str(response.prompt_feedback.block_reason))
|
888 |
logger.warning(f"[Gemini {model_name}] Request blocked by API. Reason: {block_reason_str}");
|
889 |
return None, f"Sorry, the AI model ({model_name}) blocked the request (Reason: {block_reason_str})."
|
|
|
890 |
summary = None; finish_reason_str = 'UNKNOWN'
|
|
|
891 |
if response.candidates:
|
892 |
candidate = response.candidates[0]
|
893 |
finish_reason_name = getattr(candidate.finish_reason, 'name', None)
|
894 |
-
finish_reason_str = finish_reason_name or 'N/A'
|
|
|
|
|
895 |
if finish_reason_name == 'SAFETY':
|
896 |
safety_ratings_str = ", ".join([f"{rating.category.name}: {rating.probability.name}" for rating in candidate.safety_ratings])
|
897 |
logger.warning(f"[Gemini {model_name}] Candidate blocked due to SAFETY. Finish Reason: {finish_reason_str}. Ratings: [{safety_ratings_str}]")
|
898 |
-
|
899 |
-
|
|
|
900 |
logger.warning(f"[Gemini {model_name}] Candidate finished with non-standard reason: {finish_reason_str}")
|
|
|
|
|
901 |
if candidate.content and candidate.content.parts:
|
902 |
summary = "".join(part.text for part in candidate.content.parts if hasattr(part, 'text'))
|
|
|
|
|
903 |
if summary is None:
|
904 |
try: summary = response.text
|
905 |
-
except ValueError as e: logger.warning(f"[Gemini {model_name}] Error accessing response.text (likely blocked): {e}"); summary = None
|
|
|
|
|
|
|
906 |
if summary:
|
907 |
logger.info(f"[Gemini {model_name}] Success generating summary. Finish Reason: {finish_reason_str}. Output len: {len(summary)}");
|
908 |
return summary.strip(), None
|
909 |
else:
|
|
|
910 |
logger.warning(f"[Gemini {model_name}] Gemini returned empty summary or content was blocked. Final Finish Reason: {finish_reason_str}");
|
911 |
return None, f"Sorry, the AI model ({model_name}) did not provide a summary (Finish Reason: {finish_reason_str})."
|
|
|
912 |
except AttributeError as ae:
|
|
|
913 |
logger.error(f"[Gemini {model_name}] AttributeError during Gemini response processing: {ae}. SDK might be incompatible or response structure unexpected.", exc_info=True);
|
914 |
-
return None, f"Sorry, there was an issue processing the response from the AI service ({model_name})."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
915 |
except Exception as e:
|
|
|
916 |
logger.error(f"[Gemini {model_name}] Unexpected error during Gemini API call: {e}", exc_info=True);
|
917 |
-
# Check if it's the specific InvalidArgument error we were seeing
|
918 |
-
if isinstance(e, google.api_core.exceptions.InvalidArgument) and "safety_settings" in str(e):
|
919 |
-
logger.error(f"[Gemini {model_name}] Safety settings InvalidArgument error persisted: {e}")
|
920 |
-
return None, f"Sorry, the AI service ({model_name}) reported an invalid configuration (Safety Settings). Please report this issue."
|
921 |
error_msg = f"Sorry, an unexpected error occurred while using the AI service ({model_name})."
|
922 |
return None, error_msg
|
923 |
|
@@ -984,80 +1016,96 @@ async def _call_openrouter(text: str, summary_type: str) -> Tuple[Optional[str],
|
|
984 |
|
985 |
async def generate_summary(text: str, summary_type: str) -> str:
|
986 |
"""
|
987 |
-
Generates summary using the specific model hierarchy (April 2025):
|
988 |
-
1.
|
989 |
-
2. Gemini
|
990 |
-
3. Gemini
|
991 |
4. OpenRouter (DeepSeek V3 Free)
|
|
|
992 |
Returns the summary text or a comprehensive error message.
|
993 |
"""
|
994 |
-
global
|
995 |
-
global
|
996 |
|
997 |
-
logger.info("[Summary Generation] Starting process with
|
998 |
summary: Optional[str] = None
|
|
|
999 |
errors: Dict[str, Optional[str]] = {
|
1000 |
-
|
1001 |
-
|
1002 |
-
|
1003 |
-
|
|
|
1004 |
}
|
1005 |
|
1006 |
-
# --- Attempt 1:
|
1007 |
-
if
|
1008 |
-
logger.info(f"[Summary Generation] Attempting 1:
|
1009 |
-
summary, errors[
|
1010 |
if summary:
|
1011 |
-
logger.info(f"[Summary Generation] Success with
|
1012 |
return summary
|
1013 |
else:
|
1014 |
-
logger.warning(f"[Summary Generation]
|
1015 |
else:
|
1016 |
-
logger.warning("[Summary Generation]
|
1017 |
-
errors[
|
|
|
|
|
1018 |
|
1019 |
# --- Attempt 2: Gemini 2.5 Pro Exp ---
|
1020 |
-
if
|
|
|
1021 |
logger.info(f"[Summary Generation] Attempting 2: Gemini ({GEMINI_PRO_EXP_MODEL})")
|
1022 |
-
summary, errors[
|
1023 |
if summary:
|
1024 |
logger.info(f"[Summary Generation] Success with Gemini ({GEMINI_PRO_EXP_MODEL}).")
|
1025 |
return summary
|
1026 |
else:
|
1027 |
-
logger.warning(f"[Summary Generation] Gemini 2.5 Pro Exp failed. Error: {errors[
|
1028 |
-
else
|
1029 |
-
logger.warning("[Summary Generation] Gemini API is disabled or unavailable. Skipping Gemini 2.5 Pro Exp & 2.0 Flash.")
|
1030 |
-
errors["GeminiProExp"] = "Service disabled/unavailable."
|
1031 |
-
errors["GeminiFlash"] = "Service disabled/unavailable."
|
1032 |
|
1033 |
# --- Attempt 3: Gemini 2.0 Flash ---
|
1034 |
-
|
|
|
1035 |
logger.info(f"[Summary Generation] Attempting 3: Gemini ({GEMINI_FLASH_MODEL})")
|
1036 |
-
summary, errors[
|
1037 |
if summary:
|
1038 |
logger.info(f"[Summary Generation] Success with Gemini ({GEMINI_FLASH_MODEL}).")
|
1039 |
return summary
|
1040 |
else:
|
1041 |
-
logger.warning(f"[Summary Generation] Gemini 2.0 Flash failed. Error: {errors[
|
1042 |
-
|
1043 |
-
logger.warning("[Summary Generation] Skipping Gemini 2.0 Flash (API was disabled).")
|
1044 |
-
errors["GeminiFlash"] = "Service disabled/unavailable."
|
1045 |
|
1046 |
-
# --- Attempt 4: OpenRouter (DeepSeek V3 Free
|
1047 |
if _openrouter_fallback_enabled:
|
1048 |
logger.info(f"[Summary Generation] Attempting 4: OpenRouter ({OPENROUTER_DEEPSEEK_MODEL})")
|
1049 |
-
summary, errors[
|
1050 |
if summary:
|
1051 |
logger.info(f"[Summary Generation] Success with OpenRouter ({OPENROUTER_DEEPSEEK_MODEL}).")
|
1052 |
return summary
|
1053 |
else:
|
1054 |
-
logger.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1055 |
else:
|
1056 |
-
logger.error("[Summary Generation]
|
1057 |
-
errors[
|
1058 |
|
1059 |
# --- All Attempts Failed ---
|
1060 |
logger.error("[Summary Generation] All summarization models failed.")
|
|
|
1061 |
error_details = "\n".join([f"- {model}: {err}" for model, err in errors.items() if err])
|
1062 |
return f"Sorry, I couldn't generate a summary after trying all available AI models.\nDetails:\n{error_details}"
|
1063 |
|
@@ -1219,12 +1267,13 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
|
|
1219 |
user = update.effective_user
|
1220 |
if not user or not update.message: return
|
1221 |
logger.info(f"User {user.id} ({user.username or 'no_username'}) used /help.")
|
|
|
1222 |
help_text = ( "🔍 **How to use this bot:**\n\n"
|
1223 |
"1. Send me any YouTube video link or website URL.\n"
|
1224 |
"2. I'll ask how you want it summarised (paragraph or points).\n"
|
1225 |
"3. Click the button for your choice.\n"
|
1226 |
"4. Wait while I fetch the content and generate the summary!\n\n"
|
1227 |
-
"⚙️ I try multiple methods to get content, especially for tricky websites or YouTube videos without standard transcripts. I then use a sequence of AI models (
|
1228 |
"**Commands:**\n"
|
1229 |
"`/start` - Display the welcome message\n"
|
1230 |
"`/help` - Show this help message" )
|
@@ -1388,48 +1437,59 @@ async def lifespan(app: Starlette):
|
|
1388 |
|
1389 |
async def health_check(request: Request) -> PlainTextResponse:
|
1390 |
"""Simple health check endpoint."""
|
1391 |
-
|
1392 |
-
global
|
|
|
1393 |
global _groq_enabled, _gemini_api_enabled, _openrouter_fallback_enabled
|
1394 |
global _apify_token_exists, _urltotext_key_exists, _rapidapi_key_exists, SUPADATA_API_KEY
|
1395 |
|
1396 |
-
# ... (rest of the bot status checking logic remains the same) ...
|
1397 |
bot_status = "Not Initialized"; bot_username = "N/A"
|
1398 |
if ptb_app:
|
1399 |
try:
|
|
|
1400 |
if ptb_app.running:
|
1401 |
-
|
|
|
|
|
|
|
|
|
|
|
1402 |
else:
|
1403 |
bot_status = "Initialized but Not Running"
|
|
|
1404 |
if ptb_app.bot:
|
1405 |
try: bot_info = await ptb_app.bot.get_me(); bot_username = f"@{bot_info.username}" if bot_info and bot_info.username else "Info Fetch Error"
|
1406 |
-
except
|
1407 |
-
|
1408 |
-
|
|
|
|
|
|
|
1409 |
else: bot_status = "Not Initialized"; bot_username = "N/A"
|
1410 |
|
1411 |
|
1412 |
-
# <<< Update response string with
|
1413 |
return PlainTextResponse(
|
1414 |
f"TG Bot Summariser - Status: {bot_status} ({bot_username})\n"
|
1415 |
f"---\n"
|
1416 |
-
f"Summarizer Priority (April 2025 -
|
1417 |
-
f"1.
|
1418 |
f"2. Gemini API: {GEMINI_PRO_EXP_MODEL if _gemini_api_enabled else 'DISABLED'}\n"
|
1419 |
f"3. Gemini API: {GEMINI_FLASH_MODEL if _gemini_api_enabled else 'DISABLED'}\n"
|
1420 |
f"4. OpenRouter API: {OPENROUTER_DEEPSEEK_MODEL if _openrouter_fallback_enabled else 'DISABLED'}\n"
|
|
|
1421 |
f"---\n"
|
1422 |
f"Content Fetching Status:\n"
|
1423 |
-
# ---
|
1424 |
f"YT Primary (Lib): Enabled\n"
|
1425 |
f"YT Fallback 1 (Apify Structured): {APIFY_STRUCTURED_YT_ACTOR_ID if _apify_token_exists else 'DISABLED'}\n"
|
1426 |
f"YT Fallback 2 (Supadata): {'Enabled' if SUPADATA_API_KEY else 'Disabled'}\n"
|
1427 |
f"YT Fallback 3 (Apify Default): {APIFY_ACTOR_ID if _apify_token_exists else 'DISABLED'}\n"
|
1428 |
-
# ---
|
1429 |
f"Web Scrape 1 (Direct+BS4): Enabled\n"
|
1430 |
f"Web Scrape 2 (urltotext): {'Enabled' if _urltotext_key_exists else 'Disabled'}\n"
|
1431 |
f"Web Scrape 3/4 (RapidAPI): {'Enabled' if _rapidapi_key_exists else 'Disabled'}\n"
|
1432 |
-
f"Web Scrape 5/6 (Apify Actors): {'Enabled' if _apify_token_exists else 'Disabled'}"
|
1433 |
)
|
1434 |
|
1435 |
async def telegram_webhook(request: Request) -> Response:
|
|
|
851 |
logger.info(f"[Gemini {model_name}] Generating {summary_type} summary using {model_name}. Input length: {len(text)}")
|
852 |
|
853 |
prompt = PROMPT_PARAGRAPH if summary_type == "paragraph" else PROMPT_POINTS
|
854 |
+
# Determine max input length based on model - Flash Preview is likely 1M like Flash 2.0, Pro Exp is 2M
|
855 |
+
# Use a conservative estimate for safety, but larger than Llama/Deepseek
|
856 |
MAX_INPUT_LENGTH_GEMINI = 900000
|
857 |
+
if 'pro-exp' in model_name.lower(): MAX_INPUT_LENGTH_GEMINI = 1800000 # Higher for Pro
|
858 |
+
|
859 |
if len(text) > MAX_INPUT_LENGTH_GEMINI:
|
860 |
+
logger.warning(f"[Gemini {model_name}] Input length ({len(text)}) exceeds estimated limit ({MAX_INPUT_LENGTH_GEMINI}). Truncating.");
|
861 |
text = text[:MAX_INPUT_LENGTH_GEMINI] + "... (Content truncated)"
|
862 |
full_prompt = f"{prompt}\n\n{text}"
|
863 |
|
864 |
+
# Define safety_settings explicitly with the 5 known categories set to BLOCK_NONE.
|
865 |
+
# This satisfies the requirement "make sure that all safety censors are set to none".
|
866 |
try:
|
867 |
safety_settings = {
|
868 |
HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_NONE,
|
869 |
HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_NONE,
|
870 |
HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_NONE,
|
871 |
HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_NONE,
|
872 |
+
HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY: HarmBlockThreshold.BLOCK_NONE, # Present in previous code
|
873 |
}
|
874 |
logger.debug(f"[Gemini {model_name}] Using safety settings: { {k.name: v.name for k, v in safety_settings.items()} }")
|
875 |
+
except (NameError, AttributeError): # Catch if HarmCategory/Threshold are not loaded (SDK issue)
|
876 |
+
logger.error(f"[Gemini {model_name}] Failed to define safety settings. HarmCategory/HarmBlockThreshold missing or SDK not fully available?")
|
877 |
+
return None, f"Sorry, an internal error occurred configuring the AI service ({model_name}). Safety settings unavailable."
|
|
|
|
|
878 |
|
879 |
try:
|
880 |
logger.debug(f"[Gemini {model_name}] Initializing model {model_name}")
|
881 |
model = genai.GenerativeModel(model_name)
|
882 |
logger.info(f"[Gemini {model_name}] Sending request to Gemini ({model_name})...")
|
883 |
+
request_options = {"timeout": 120} # Keep timeout reasonable
|
884 |
response = await model.generate_content_async(
|
885 |
+
full_prompt, generation_config=genai.types.GenerationConfig(), # Use default config
|
886 |
safety_settings=safety_settings, request_options=request_options )
|
887 |
logger.info(f"[Gemini {model_name}] Received response from Gemini.")
|
888 |
+
|
889 |
+
# Check for prompt blocking first
|
890 |
if response.prompt_feedback and response.prompt_feedback.block_reason:
|
891 |
block_reason_str = getattr(response.prompt_feedback.block_reason, 'name', str(response.prompt_feedback.block_reason))
|
892 |
logger.warning(f"[Gemini {model_name}] Request blocked by API. Reason: {block_reason_str}");
|
893 |
return None, f"Sorry, the AI model ({model_name}) blocked the request (Reason: {block_reason_str})."
|
894 |
+
|
895 |
summary = None; finish_reason_str = 'UNKNOWN'
|
896 |
+
# Check candidates for content and safety blocking
|
897 |
if response.candidates:
|
898 |
candidate = response.candidates[0]
|
899 |
finish_reason_name = getattr(candidate.finish_reason, 'name', None)
|
900 |
+
finish_reason_str = finish_reason_name or 'N/A' # Default if None
|
901 |
+
|
902 |
+
# Check if the *candidate* was blocked by safety
|
903 |
if finish_reason_name == 'SAFETY':
|
904 |
safety_ratings_str = ", ".join([f"{rating.category.name}: {rating.probability.name}" for rating in candidate.safety_ratings])
|
905 |
logger.warning(f"[Gemini {model_name}] Candidate blocked due to SAFETY. Finish Reason: {finish_reason_str}. Ratings: [{safety_ratings_str}]")
|
906 |
+
# Provide a more specific error message about response blocking
|
907 |
+
return None, f"Sorry, the AI model ({model_name}) generated a response that was blocked due to safety filters ({finish_reason_str})."
|
908 |
+
elif finish_reason_name not in ['STOP', 'MAX_TOKENS', None]: # Log other unusual finish reasons
|
909 |
logger.warning(f"[Gemini {model_name}] Candidate finished with non-standard reason: {finish_reason_str}")
|
910 |
+
|
911 |
+
# Extract content if available
|
912 |
if candidate.content and candidate.content.parts:
|
913 |
summary = "".join(part.text for part in candidate.content.parts if hasattr(part, 'text'))
|
914 |
+
|
915 |
+
# Fallback: try accessing response.text directly (might raise ValueError if blocked)
|
916 |
if summary is None:
|
917 |
try: summary = response.text
|
918 |
+
except ValueError as e: logger.warning(f"[Gemini {model_name}] Error accessing response.text (likely blocked/no content): {e}"); summary = None
|
919 |
+
except Exception as e: logger.warning(f"[Gemini {model_name}] Unexpected error accessing response.text: {e}"); summary = None
|
920 |
+
|
921 |
+
# Final check if we got a summary
|
922 |
if summary:
|
923 |
logger.info(f"[Gemini {model_name}] Success generating summary. Finish Reason: {finish_reason_str}. Output len: {len(summary)}");
|
924 |
return summary.strip(), None
|
925 |
else:
|
926 |
+
# If no summary after checking candidate and response.text, report failure
|
927 |
logger.warning(f"[Gemini {model_name}] Gemini returned empty summary or content was blocked. Final Finish Reason: {finish_reason_str}");
|
928 |
return None, f"Sorry, the AI model ({model_name}) did not provide a summary (Finish Reason: {finish_reason_str})."
|
929 |
+
|
930 |
except AttributeError as ae:
|
931 |
+
# This can happen if the response structure changes or expected fields are missing
|
932 |
logger.error(f"[Gemini {model_name}] AttributeError during Gemini response processing: {ae}. SDK might be incompatible or response structure unexpected.", exc_info=True);
|
933 |
+
return None, f"Sorry, there was an issue processing the response from the AI service ({model_name}). Attribute error."
|
934 |
+
except google.api_core.exceptions.InvalidArgument as iae:
|
935 |
+
# Catch specific InvalidArgument, often related to safety settings or model params
|
936 |
+
logger.error(f"[Gemini {model_name}] Invalid Argument error from Gemini API: {iae}", exc_info=False) # Don't need full stack trace often
|
937 |
+
error_detail = str(iae)
|
938 |
+
user_message = f"Sorry, the AI service ({model_name}) reported an invalid argument."
|
939 |
+
if "safety_settings" in error_detail: user_message += " (Safety Settings issue)."
|
940 |
+
elif "temperature" in error_detail: user_message += " (Generation Parameter issue)."
|
941 |
+
return None, user_message
|
942 |
+
except google.api_core.exceptions.GoogleAPIError as gae:
|
943 |
+
# Catch other potential Google API errors (like permission denied, quota exceeded, etc.)
|
944 |
+
logger.error(f"[Gemini {model_name}] Google API error during Gemini call: {gae}", exc_info=False)
|
945 |
+
status_code = getattr(gae, 'code', 'Unknown')
|
946 |
+
user_message = f"Sorry, the AI service ({model_name}) encountered an API error (Code: {status_code})."
|
947 |
+
if status_code == 429: user_message = f"Sorry, the AI model ({model_name}) is busy (Rate Limit/Quota Exceeded). Try again later."
|
948 |
+
elif status_code == 500: user_message = f"Sorry, the AI service ({model_name}) had an internal server error."
|
949 |
+
return None, user_message
|
950 |
except Exception as e:
|
951 |
+
# General catch-all for unexpected issues
|
952 |
logger.error(f"[Gemini {model_name}] Unexpected error during Gemini API call: {e}", exc_info=True);
|
|
|
|
|
|
|
|
|
953 |
error_msg = f"Sorry, an unexpected error occurred while using the AI service ({model_name})."
|
954 |
return None, error_msg
|
955 |
|
|
|
1016 |
|
1017 |
async def generate_summary(text: str, summary_type: str) -> str:
|
1018 |
"""
|
1019 |
+
Generates summary using the specific model hierarchy (April 2025 - Updated):
|
1020 |
+
1. Gemini 2.5 Flash Preview (NEW)
|
1021 |
+
2. Gemini 2.5 Pro Exp
|
1022 |
+
3. Gemini 2.0 Flash
|
1023 |
4. OpenRouter (DeepSeek V3 Free)
|
1024 |
+
5. Groq (Llama 4 Scout)
|
1025 |
Returns the summary text or a comprehensive error message.
|
1026 |
"""
|
1027 |
+
global _gemini_api_enabled, _openrouter_fallback_enabled, _groq_enabled
|
1028 |
+
global GEMINI_FLASH_PREVIEW_MODEL, GEMINI_PRO_EXP_MODEL, GEMINI_FLASH_MODEL, OPENROUTER_DEEPSEEK_MODEL, GROQ_LLAMA4_MODEL
|
1029 |
|
1030 |
+
logger.info("[Summary Generation] Starting process with updated April 2025 model hierarchy.")
|
1031 |
summary: Optional[str] = None
|
1032 |
+
# Use more descriptive keys matching the model variables
|
1033 |
errors: Dict[str, Optional[str]] = {
|
1034 |
+
GEMINI_FLASH_PREVIEW_MODEL: None,
|
1035 |
+
GEMINI_PRO_EXP_MODEL: None,
|
1036 |
+
GEMINI_FLASH_MODEL: None,
|
1037 |
+
OPENROUTER_DEEPSEEK_MODEL: None,
|
1038 |
+
GROQ_LLAMA4_MODEL: None,
|
1039 |
}
|
1040 |
|
1041 |
+
# --- Attempt 1: Gemini 2.5 Flash Preview (NEW) ---
|
1042 |
+
if _gemini_api_enabled:
|
1043 |
+
logger.info(f"[Summary Generation] Attempting 1: Gemini ({GEMINI_FLASH_PREVIEW_MODEL})")
|
1044 |
+
summary, errors[GEMINI_FLASH_PREVIEW_MODEL] = await _call_gemini(text, summary_type, GEMINI_FLASH_PREVIEW_MODEL)
|
1045 |
if summary:
|
1046 |
+
logger.info(f"[Summary Generation] Success with Gemini ({GEMINI_FLASH_PREVIEW_MODEL}).")
|
1047 |
return summary
|
1048 |
else:
|
1049 |
+
logger.warning(f"[Summary Generation] Gemini Flash Preview failed. Error: {errors[GEMINI_FLASH_PREVIEW_MODEL]}. Proceeding to Gemini 2.5 Pro Exp.")
|
1050 |
else:
|
1051 |
+
logger.warning("[Summary Generation] Gemini API is disabled or unavailable. Skipping all Gemini models.")
|
1052 |
+
errors[GEMINI_FLASH_PREVIEW_MODEL] = "Service disabled/unavailable."
|
1053 |
+
errors[GEMINI_PRO_EXP_MODEL] = "Service disabled/unavailable."
|
1054 |
+
errors[GEMINI_FLASH_MODEL] = "Service disabled/unavailable."
|
1055 |
|
1056 |
# --- Attempt 2: Gemini 2.5 Pro Exp ---
|
1057 |
+
# Only attempt if API is enabled AND the previous step didn't mark it as disabled
|
1058 |
+
if _gemini_api_enabled and errors[GEMINI_PRO_EXP_MODEL] is None:
|
1059 |
logger.info(f"[Summary Generation] Attempting 2: Gemini ({GEMINI_PRO_EXP_MODEL})")
|
1060 |
+
summary, errors[GEMINI_PRO_EXP_MODEL] = await _call_gemini(text, summary_type, GEMINI_PRO_EXP_MODEL)
|
1061 |
if summary:
|
1062 |
logger.info(f"[Summary Generation] Success with Gemini ({GEMINI_PRO_EXP_MODEL}).")
|
1063 |
return summary
|
1064 |
else:
|
1065 |
+
logger.warning(f"[Summary Generation] Gemini 2.5 Pro Exp failed. Error: {errors[GEMINI_PRO_EXP_MODEL]}. Proceeding to Gemini 2.0 Flash.")
|
1066 |
+
# No separate 'else' needed here, handled by the initial Gemini API check
|
|
|
|
|
|
|
1067 |
|
1068 |
# --- Attempt 3: Gemini 2.0 Flash ---
|
1069 |
+
# Only attempt if API is enabled AND it wasn't marked as disabled earlier
|
1070 |
+
if _gemini_api_enabled and errors[GEMINI_FLASH_MODEL] is None:
|
1071 |
logger.info(f"[Summary Generation] Attempting 3: Gemini ({GEMINI_FLASH_MODEL})")
|
1072 |
+
summary, errors[GEMINI_FLASH_MODEL] = await _call_gemini(text, summary_type, GEMINI_FLASH_MODEL)
|
1073 |
if summary:
|
1074 |
logger.info(f"[Summary Generation] Success with Gemini ({GEMINI_FLASH_MODEL}).")
|
1075 |
return summary
|
1076 |
else:
|
1077 |
+
logger.warning(f"[Summary Generation] Gemini 2.0 Flash failed. Error: {errors[GEMINI_FLASH_MODEL]}. Proceeding to OpenRouter DeepSeek V3.")
|
1078 |
+
# No separate 'else' needed here
|
|
|
|
|
1079 |
|
1080 |
+
# --- Attempt 4: OpenRouter (DeepSeek V3 Free) ---
|
1081 |
if _openrouter_fallback_enabled:
|
1082 |
logger.info(f"[Summary Generation] Attempting 4: OpenRouter ({OPENROUTER_DEEPSEEK_MODEL})")
|
1083 |
+
summary, errors[OPENROUTER_DEEPSEEK_MODEL] = await _call_openrouter(text, summary_type)
|
1084 |
if summary:
|
1085 |
logger.info(f"[Summary Generation] Success with OpenRouter ({OPENROUTER_DEEPSEEK_MODEL}).")
|
1086 |
return summary
|
1087 |
else:
|
1088 |
+
logger.warning(f"[Summary Generation] OpenRouter DeepSeek V3 failed. Error: {errors[OPENROUTER_DEEPSEEK_MODEL]}. Proceeding to Groq Llama 4 Scout.")
|
1089 |
+
else:
|
1090 |
+
logger.warning("[Summary Generation] OpenRouter fallback (DeepSeek V3) is disabled or unavailable. Skipping.")
|
1091 |
+
errors[OPENROUTER_DEEPSEEK_MODEL] = "Service disabled/unavailable."
|
1092 |
+
|
1093 |
+
# --- Attempt 5: Groq (Llama 4 Scout - Final Fallback) ---
|
1094 |
+
if _groq_enabled:
|
1095 |
+
logger.info(f"[Summary Generation] Attempting 5: Groq ({GROQ_LLAMA4_MODEL})")
|
1096 |
+
summary, errors[GROQ_LLAMA4_MODEL] = await _call_groq(text, summary_type)
|
1097 |
+
if summary:
|
1098 |
+
logger.info(f"[Summary Generation] Success with Groq ({GROQ_LLAMA4_MODEL}).")
|
1099 |
+
return summary
|
1100 |
+
else:
|
1101 |
+
logger.error(f"[Summary Generation] Groq Llama 4 Scout (Final Fallback) also failed. Error: {errors[GROQ_LLAMA4_MODEL]}")
|
1102 |
else:
|
1103 |
+
logger.error("[Summary Generation] Groq (Llama 4 Scout) is disabled or unavailable. Cannot proceed.")
|
1104 |
+
errors[GROQ_LLAMA4_MODEL] = "Service disabled/unavailable."
|
1105 |
|
1106 |
# --- All Attempts Failed ---
|
1107 |
logger.error("[Summary Generation] All summarization models failed.")
|
1108 |
+
# Use the model names as keys for clearer error reporting
|
1109 |
error_details = "\n".join([f"- {model}: {err}" for model, err in errors.items() if err])
|
1110 |
return f"Sorry, I couldn't generate a summary after trying all available AI models.\nDetails:\n{error_details}"
|
1111 |
|
|
|
1267 |
user = update.effective_user
|
1268 |
if not user or not update.message: return
|
1269 |
logger.info(f"User {user.id} ({user.username or 'no_username'}) used /help.")
|
1270 |
+
# Updated help text reflecting the new model order
|
1271 |
help_text = ( "🔍 **How to use this bot:**\n\n"
|
1272 |
"1. Send me any YouTube video link or website URL.\n"
|
1273 |
"2. I'll ask how you want it summarised (paragraph or points).\n"
|
1274 |
"3. Click the button for your choice.\n"
|
1275 |
"4. Wait while I fetch the content and generate the summary!\n\n"
|
1276 |
+
"⚙️ I try multiple methods to get content, especially for tricky websites or YouTube videos without standard transcripts. I then use a sequence of AI models (Gemini 2.5 Flash Preview, Gemini 2.5 Pro, Gemini 2.0 Flash, DeepSeek V3, Llama 4 Scout) to summarise.\n\n" # Updated model list here
|
1277 |
"**Commands:**\n"
|
1278 |
"`/start` - Display the welcome message\n"
|
1279 |
"`/help` - Show this help message" )
|
|
|
1437 |
|
1438 |
async def health_check(request: Request) -> PlainTextResponse:
|
1439 |
"""Simple health check endpoint."""
|
1440 |
+
# Include the new model variable
|
1441 |
+
global GEMINI_FLASH_PREVIEW_MODEL, GROQ_LLAMA4_MODEL, GEMINI_PRO_EXP_MODEL, GEMINI_FLASH_MODEL, OPENROUTER_DEEPSEEK_MODEL
|
1442 |
+
global APIFY_ACTOR_ID, APIFY_STRUCTURED_YT_ACTOR_ID
|
1443 |
global _groq_enabled, _gemini_api_enabled, _openrouter_fallback_enabled
|
1444 |
global _apify_token_exists, _urltotext_key_exists, _rapidapi_key_exists, SUPADATA_API_KEY
|
1445 |
|
|
|
1446 |
bot_status = "Not Initialized"; bot_username = "N/A"
|
1447 |
if ptb_app:
|
1448 |
try:
|
1449 |
+
# Check if bot is running and get info
|
1450 |
if ptb_app.running:
|
1451 |
+
# Wrap get_me in a try-except in case the bot disconnects during check
|
1452 |
+
try:
|
1453 |
+
bot_info = await ptb_app.bot.get_me(); bot_username = f"@{bot_info.username}" if bot_info and bot_info.username else "Info Fetch Error"; bot_status = "Running"
|
1454 |
+
except (TimedOut, NetworkError, TelegramError) as bot_err:
|
1455 |
+
logger.warning(f"Health check: Error getting bot info while running: {bot_err}")
|
1456 |
+
bot_status = "Running (Info Error)"; bot_username = "Fetch Error"
|
1457 |
else:
|
1458 |
bot_status = "Initialized but Not Running"
|
1459 |
+
# Try getting info even if not 'running' (might be initializing/shutting down)
|
1460 |
if ptb_app.bot:
|
1461 |
try: bot_info = await ptb_app.bot.get_me(); bot_username = f"@{bot_info.username}" if bot_info and bot_info.username else "Info Fetch Error"
|
1462 |
+
except (TimedOut, NetworkError, TelegramError) as bot_err:
|
1463 |
+
logger.warning(f"Health check: Error getting bot info while not running: {bot_err}")
|
1464 |
+
bot_username = "Info Fetch Error (Not Running)"
|
1465 |
+
except Exception as e: # Catch broader exceptions during status check
|
1466 |
+
bot_status = f"Error checking status: {type(e).__name__}"; logger.warning(f"Health check: General error getting bot status: {e}")
|
1467 |
+
bot_username = "Error"
|
1468 |
else: bot_status = "Not Initialized"; bot_username = "N/A"
|
1469 |
|
1470 |
|
1471 |
+
# <<< Update response string with the new model order >>>
|
1472 |
return PlainTextResponse(
|
1473 |
f"TG Bot Summariser - Status: {bot_status} ({bot_username})\n"
|
1474 |
f"---\n"
|
1475 |
+
f"Summarizer Priority (April 2025 - Updated):\n"
|
1476 |
+
f"1. Gemini API: {GEMINI_FLASH_PREVIEW_MODEL if _gemini_api_enabled else 'DISABLED'} (NEW)\n"
|
1477 |
f"2. Gemini API: {GEMINI_PRO_EXP_MODEL if _gemini_api_enabled else 'DISABLED'}\n"
|
1478 |
f"3. Gemini API: {GEMINI_FLASH_MODEL if _gemini_api_enabled else 'DISABLED'}\n"
|
1479 |
f"4. OpenRouter API: {OPENROUTER_DEEPSEEK_MODEL if _openrouter_fallback_enabled else 'DISABLED'}\n"
|
1480 |
+
f"5. Groq API: {GROQ_LLAMA4_MODEL if _groq_enabled else 'DISABLED'} (Last Fallback)\n"
|
1481 |
f"---\n"
|
1482 |
f"Content Fetching Status:\n"
|
1483 |
+
# --- YT Fallback List (Order already correct) ---
|
1484 |
f"YT Primary (Lib): Enabled\n"
|
1485 |
f"YT Fallback 1 (Apify Structured): {APIFY_STRUCTURED_YT_ACTOR_ID if _apify_token_exists else 'DISABLED'}\n"
|
1486 |
f"YT Fallback 2 (Supadata): {'Enabled' if SUPADATA_API_KEY else 'Disabled'}\n"
|
1487 |
f"YT Fallback 3 (Apify Default): {APIFY_ACTOR_ID if _apify_token_exists else 'DISABLED'}\n"
|
1488 |
+
# --- Web Scrape Fallback List (Order already correct) ---
|
1489 |
f"Web Scrape 1 (Direct+BS4): Enabled\n"
|
1490 |
f"Web Scrape 2 (urltotext): {'Enabled' if _urltotext_key_exists else 'Disabled'}\n"
|
1491 |
f"Web Scrape 3/4 (RapidAPI): {'Enabled' if _rapidapi_key_exists else 'Disabled'}\n"
|
1492 |
+
f"Web Scrape 5/6 (Apify Actors): {'Enabled' if _apify_token_exists else 'Disabled'}"
|
1493 |
)
|
1494 |
|
1495 |
async def telegram_webhook(request: Request) -> Response:
|