Spaces:
Sleeping
Sleeping
# generation_service_gemini.py | |
import google.generativeai as genai | |
import os | |
import re | |
import traceback | |
from typing import List, Dict, Generator # Use standard Generator | |
# --- Attempt to Import Shared Functions --- | |
try: | |
from generation_service_anthropic import clean_source_text | |
print("Successfully imported clean_source_text from generation_service_anthropic.") | |
except ImportError: | |
print("Warning: Could not import clean_source_text. Using fallback cleaner.") | |
def clean_source_text(text): # Fallback | |
if not text: return "" | |
cleaned = text; cleaned = re.sub(r'@\d+', '', cleaned); cleaned = re.sub(r'<HAL>', '', cleaned, flags=re.IGNORECASE); cleaned = cleaned.replace('<br>', ' ').replace('<br />', ' '); cleaned = re.sub(r'\s+', ' ', cleaned).strip() | |
return cleaned | |
# --- End Fallback Definitions --- | |
# --- Configuration --- | |
GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") | |
GENERATION_MODEL = "gemini-2.5-pro-preview-03-25" # Your model | |
# --- End Configuration --- | |
# --- Client Initialization --- | |
genai_client = None | |
if GOOGLE_API_KEY: | |
try: | |
genai.configure(api_key=GOOGLE_API_KEY) | |
genai_client = genai.GenerativeModel(GENERATION_MODEL) | |
print(f"Google AI client initialized for Gemini generation (Model: {GENERATION_MODEL}).") | |
except Exception as e: print(f"Error initializing Google AI client: {e}"); traceback.print_exc() | |
else: print("GOOGLE_API_KEY not found. Gemini generation service will not function.") | |
def check_gemini_generator_status(): | |
if not genai_client: return False, f"Gemini generator client not initialized." | |
return True, f"Gemini generation service ready (Model: {GENERATION_MODEL})." | |
# --- MODIFIED format_context_for_prompt (Keep ID attribute) --- | |
def format_context_for_prompt(documents: List[Dict]) -> str: | |
if not documents: return "" | |
formatted_docs = [] | |
language_key = 'hebrew_text' | |
id_key = 'original_id' | |
for index, doc in enumerate(documents): | |
full_text_original = doc.get(language_key, '') | |
paragraph_id = doc.get(id_key, f'unknown_id_{index+1}') | |
try: full_text_cleaned = clean_source_text(full_text_original) | |
except NameError: full_text_cleaned = full_text_original # Fallback | |
if full_text_cleaned: | |
formatted_docs.append( | |
f'<source index="{index + 1}" id="{paragraph_id}">\n' # Keep id attribute | |
f'<full_text>{full_text_cleaned}</full_text>\n' | |
f'</source>' | |
) | |
return "\n\n".join(formatted_docs) | |
# --- END MODIFIED format_context_for_prompt --- | |
# --- *** NEW SIMPLIFIED EXAMPLE_RESPONSE_HEBREW *** --- | |
EXAMPLE_RESPONSE_HEBREW = """<example response hebrew> | |
במקורות שהובאו מצאנו כמה נקודות בנוגע לרדיפת פרעה אחר בני ישראל. ראשית, היה זה רצון השי"ת להביא את המצריים לים סוף "כדי שיטבעו" (ID: cc792519-8a96-4c2e-96a7-e940a3d6688f) ויתפרסם כבודו יתברך בעולם. הקב"ה סיבב זאת על ידי שהטעה את פרעה לחשוב שבני ישראל "נבוכים הם בארץ סגר עליהם המדבר" (ID: 2e0227b5-f359-4a60-ab51-2ba9f6c3fca5), מה שעורר אותו לרדוף אחריהם. | |
עוד מבואר כי נס קריעת ים סוף נועד להורות "הוראה מפורסמת היות בו יתברך פעולת ההפכים" (ID: cde20ae5-0374-4023-9f15-e721b4920db8), דהיינו שבאותו רגע שהיטיב לישראל וקרע לפניהם את הים, הוא הרע למצרים והטביעם בתוכו. | |
בנוגע לשאלה מדוע הים לא נקרע מיד, מובא שהיו טענות שונות, כגון שעדיין לא הושלם זמן הגלות של ת' שנה, וכן טענת המקטרג ש"הללו עובדי עבודה זרה והללו עובדי עבודה זרה" (ID: [Could be a different ID if cited]). טענות אלו נדחו, בין היתר, משום שהשעבוד הקשה השלים את הזמן, וכן מפני שעבודתם של ישראל היתה "באונס ושוגג" (ID: [Could be a different ID if cited]). | |
</example response hebrew>""" | |
# --- *** END NEW SIMPLIFIED EXAMPLE_RESPONSE_HEBREW *** --- | |
# --- Synchronous Generation Function --- | |
def generate_response_stream_gemini( | |
query: str, | |
context_documents: List[Dict] | |
) -> Generator[str, None, None]: | |
global genai_client | |
ready, msg = check_gemini_generator_status() | |
if not ready or genai_client is None: yield f"שגיאה: ..."; return | |
if not query: yield "שגיאה: ..."; return | |
try: | |
formatted_context = format_context_for_prompt(context_documents) | |
except Exception as format_err: yield f"שגיאה ...: {format_err}"; return | |
has_context = bool(formatted_context) | |
if not has_context: yield "לא סופקו מקורות לעיון."; return | |
# --- *** REVISED System Instruction Content for Simple Output *** --- | |
system_instruction_content = f"""<instructions> | |
You are a highly knowledgeable assistant acting as a learned scholar specializing in Chassidic texts, particularly Divrei Yoel. Your function is to answer the user's Hebrew question based *strictly and exclusively* on the provided Hebrew source text passages found in the <context> section. | |
**Response Requirements:** | |
1. **Language:** Respond ONLY in formal, traditional Rabbinic/Torah Hebrew (עברית תורנית, לשון הקודש). ABSOLUTELY NO MODERN HEBREW. Use only Hebrew letters and standard punctuation. | |
2. **Content:** Base your answer *solely* on information present in the `<source>` passages provided in the context. Do not add external knowledge or opinions. | |
3. **Structure:** Write a clear, coherent answer to the user's question. | |
4. **Citations:** When directly quoting or closely paraphrasing a specific point from a source to support your answer, incorporate a **short, relevant snippet** of the source text directly into your sentence. Immediately following the snippet or the sentence containing it, you MUST add the paragraph ID in the format `(ID: <id_value>)`. Extract the `<id_value>` from the `id` attribute of the corresponding `<source>` tag in the context. | |
5. **Conciseness:** Keep quoted snippets brief and directly relevant to the point you are making. | |
6. **Irrelevant Sources:** If the provided sources do not contain information to answer the question, state this clearly (e.g., "על פי המקורות שסופקו, לא נמצאה תשובה לשאלה זו."). Do not invent answers. | |
7. **Format:** Output *only* the final Hebrew answer with embedded citations as described. Do not include greetings, apologies, the original question, or any meta-commentary about the process. | |
8. **Example Adherence:** Follow the style, language, and citation format shown in the example response below. | |
</instructions> | |
{EXAMPLE_RESPONSE_HEBREW}""" # Use the NEW simplified example | |
# --- *** END REVISED System Instruction Content *** --- | |
# --- Prepare User Prompt Content --- | |
user_prompt_content = f"<context>\n{formatted_context}\n</context>\n\nBased *exclusively* on the source text provided in the context above, please answer the following question according to the detailed instructions:\n\nQuestion:\n{query}" | |
print(f" -> Sending request to Gemini (Model: {GENERATION_MODEL})...") | |
print(f" -> Context size: ~{len(formatted_context)} characters") | |
print(f" -> System Instruction Length: ~{len(system_instruction_content)} characters") | |
print(f" -> User Prompt Length: ~{len(user_prompt_content)} characters") | |
print(f" -> Query: '{query[:50]}...'") | |
# --- API Call Block (Compatible with v0.8.5) --- | |
try: | |
generation_config = genai.types.GenerationConfig(temperature=0.2, max_output_tokens=8192) # Keep large output for now | |
safety_settings=[ # Keep safety settings | |
{"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"}, | |
{"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"}, | |
{"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"}, | |
] | |
contents_for_api = [ system_instruction_content, user_prompt_content ] # Instructions first | |
response_stream = genai_client.generate_content( | |
contents=contents_for_api, # Pass combined list | |
generation_config=generation_config, | |
safety_settings=safety_settings, | |
stream=True | |
) | |
print(f" -> Gemini SYNC stream iterator created successfully...") | |
chunk_count = 0 | |
# --- Stream Handling Loop (Keep existing robust logic) --- | |
for chunk in response_stream: | |
try: | |
chunk_text = chunk.text | |
block_reason_str = chunk.safety_block_reason | |
if block_reason_str: | |
print(f" -> Gemini BLOCKED chunk: {block_reason_str}") | |
yield f"שגיאה: {block_reason_str}" | |
break # Stop processing if blocked | |
if chunk_text: | |
chunk_count += 1 | |
yield chunk_text # Yield to app.py | |
except AttributeError as ae: | |
print(f" -> Gemini chunk error: {ae}") | |
yield f"שגיאה: {ae}" | |
break | |
# --- Final Check if No Chunks Yielded --- | |
if chunk_count == 0: yield "(לא התקבלה תשובה טקסטואלית מ-Gemini)" | |
# --- General Error Handling --- | |
except Exception as e: | |
# ... (print error, yield error message) ... | |
error_type = type(e).__name__; error_msg = str(e) | |
print(f" <- Error during Gemini SYNC stream ({error_type}): {error_msg}"); traceback.print_exc() | |
yield f"\n\n--- שגיאה ביצירת התשובה מ-Gemini ({error_type}): {error_msg} ---" | |
# --- END API Call Block --- | |
# --- Test function (Synchronous) --- | |
# Commenting out as manual check is better now | |
# def run_gemini_generation_test_sync(): | |
# print("\n--- Running Gemini SYNC Generation Test (Manual Check Needed) ---") | |
# ... | |
# --- Main Execution Block --- | |
if __name__ == "__main__": | |
pass # Or run test if adapted | |
# if GOOGLE_API_KEY: run_gemini_generation_test_sync() | |
# else: print("\nError: GOOGLE_API_KEY environment variable not set.") |