Spaces:
Sleeping
Sleeping
# app.py - LangSmith enabled, designed for Replit + Anthropic + OpenAI | |
import os | |
import streamlit as st | |
import time | |
import traceback | |
import json | |
import asyncio | |
import nest_asyncio | |
from typing import List, Dict | |
from dotenv import load_dotenv | |
load_dotenv() | |
# ----- SETUP SECRETS AND ENV ----- | |
# Hardcoded (safe): you never need these in secrets! | |
os.environ["LANGSMITH_ENDPOINT"] = "https://api.smith.langchain.com" | |
os.environ["LANGSMITH_TRACING"] = "true" | |
# The following must exist in your Replit secrets: | |
# OPENAI_API_KEY, ANTHROPIC_API_KEY, LANGSMITH_API_KEY, LANGSMITH_PROJECT | |
os.environ["OPENAI_API_KEY"] = os.environ["OPENAI_API_KEY"] | |
os.environ["ANTHROPIC_API_KEY"] = os.environ["ANTHROPIC_API_KEY"] | |
os.environ["LANGSMITH_API_KEY"] = os.environ["LANGSMITH_API_KEY"] | |
os.environ["LANGSMITH_PROJECT"] = os.environ["LANGSMITH_PROJECT"] | |
# ---------------------------------- | |
from langsmith import traceable | |
nest_asyncio.apply() | |
from retriever_pinecone import find_similar_paragraphs, check_retriever_status | |
from analysis_service_anthropic import ( | |
analyze_source_relevance_async, | |
check_analyzer_status, | |
ANALYSIS_MODEL as ANTHROPIC_ANALYSIS_MODEL, | |
) | |
from generation_service_anthropic import ( | |
generate_response_stream_async as generate_anthropic, | |
check_generator_status as check_anthropic_generator, | |
GENERATION_MODEL as ANTHROPIC_GENERATION_MODEL, | |
) | |
from generation_service_gemini import ( | |
generate_response_stream_gemini as generate_gemini, | |
check_gemini_generator_status, | |
GENERATION_MODEL as GEMINI_GENERATION_MODEL, | |
) | |
from validation_service_openai import ( | |
validate_paragraph_relevance_gpt4o, | |
check_openai_validator_status, | |
VALIDATION_MODEL as GPT4O_VALIDATION_MODEL, | |
) | |
try: | |
from generation_service_anthropic import format_context_for_prompt | |
print("Format context function potentially available.") | |
except ImportError: | |
print("Warning: format_context_for_prompt not imported.") | |
st.set_page_config(page_title="Divrey Yoel AI Chat", layout="wide") | |
st.markdown( | |
"""<style> | |
.rtl-text { direction: rtl; text-align: right; } | |
.hebrew-text { font-family: 'Arial Hebrew', 'David', sans-serif; direction: rtl; text-align: right; font-size: 1.1em; margin-bottom: 5px; } | |
.source-info { font-size: 0.85em; color: #666; margin-bottom: 8px; } | |
.expander-content > div { border-bottom: 1px solid #eee; padding-bottom: 15px; margin-bottom: 15px; } | |
.expander-content > div:last-child { border-bottom: none; margin-bottom: 0; padding-bottom: 0; } | |
.stChatMessage .stExpander { margin-top: 15px; border-left: 3px solid #ddd; padding-left: 10px; } | |
.stStatus div[data-testid="stStatusContent"] p { direction: rtl; text-align: right; } | |
.stButton > button[kind="header"] { direction: rtl; text-align: right; } | |
.stExpander div[data-testid="stVerticalBlock"] code { display: block; text-align: right; direction: rtl; } | |
.alert-warning { padding: 0.75rem 1.25rem; margin-bottom: 1rem; border: 1px solid transparent; | |
border-radius: 0.25rem; color: #856404; background-color: #fff3cd; border-color: #ffeeba;} | |
</style>""", | |
unsafe_allow_html=True, | |
) | |
st.markdown("<h1 class='rtl-text'>Divrey Yoel AI Chat</h1>", unsafe_allow_html=True) | |
st.markdown("<p class='rtl-text'>חיפוש בטקסטים חסידיים באמצעות RAG</p>", unsafe_allow_html=True) | |
# --- Status Checks & Sidebar --- | |
retriever_ready, retriever_msg = check_retriever_status() | |
anthropic_analyzer_ready, anthropic_analyzer_msg = check_analyzer_status() | |
anthropic_generator_ready, anthropic_generator_msg = check_anthropic_generator() | |
gemini_generator_ready, gemini_generator_msg = check_gemini_generator_status() | |
openai_validator_ready, openai_validator_msg = check_openai_validator_status() | |
st.sidebar.markdown("<h3 class='rtl-text'>מצב המערכת</h3>", unsafe_allow_html=True) | |
st.sidebar.markdown( | |
f"<p class='rtl-text'><strong>מאחזר (Pinecone):</strong> {'✅' if retriever_ready else '❌'}</p>", | |
unsafe_allow_html=True, | |
) | |
if not retriever_ready: | |
st.sidebar.markdown( | |
f"<div class='alert alert-warning rtl-text' role='alert'>{retriever_msg}</div>", unsafe_allow_html=True | |
) | |
st.markdown( | |
"<p class='rtl-text' style='color: red;'><strong>שירות האחזור (Pinecone) אינו זמין. לא ניתן להמשיך.</strong></p>", | |
unsafe_allow_html=True, | |
) | |
st.stop() | |
st.sidebar.markdown("<hr>", unsafe_allow_html=True) | |
st.sidebar.markdown( | |
f"<p class='rtl-text'><strong>מנתח (Anthropic):</strong> {'✅ <small>(נדרש לשיטת Anthropic)</small>' if anthropic_analyzer_ready else '❌ <small>(נדרש לשיטת Anthropic)</small>'}</p>", | |
unsafe_allow_html=True, | |
) | |
st.sidebar.markdown( | |
f"<p class='rtl-text'><strong>מאמת (GPT-4o):</strong> {'✅ <small>(נדרש לשיטת GPT-4o)</small>' if openai_validator_ready else '❌ <small>(נדרש לשיטת GPT-4o)</small>'}</p>", | |
unsafe_allow_html=True, | |
) | |
st.sidebar.markdown( | |
f"<p class='rtl-text'><strong>מחולל (Anthropic):</strong> {'✅ <small>(נדרש לשיטות Anthropic/GPT-4o)</small>' if anthropic_generator_ready else '❌ <small>(נדרש לשיטות Anthropic/GPT-4o)</small>'}</p>", | |
unsafe_allow_html=True, | |
) | |
st.sidebar.markdown( | |
f"<p class='rtl-text'><strong>מחולל (Gemini):</strong> {'✅ <small>(נדרש לשיטת Gemini)</small>' if gemini_generator_ready else '❌ <small>(נדרש לשיטת Gemini)</small>'}</p>", | |
unsafe_allow_html=True, | |
) | |
st.sidebar.markdown("<hr>", unsafe_allow_html=True) | |
st.sidebar.markdown("<h3 class='rtl-text'>הגדרות RAG</h3>", unsafe_allow_html=True) | |
pipeline_method = st.sidebar.selectbox( | |
"בחר שיטת עיבוד:", | |
options=[ | |
"Anthropic (ניתוח וסינון פרטני)", | |
"Gemini (אחזור ויצירה ישירה)", | |
"GPT-4o Paragraph Validator + Claude Synthesizer", | |
], | |
index=2, | |
) | |
is_anthropic_pipeline = pipeline_method == "Anthropic (ניתוח וסינון פרטני)" | |
is_gemini_pipeline = pipeline_method == "Gemini (אחזור ויצירה ישירה)" | |
is_gpt4o_para_pipeline = pipeline_method == "GPT-4o Paragraph Validator + Claude Synthesizer" | |
n_retrieve = st.sidebar.slider( | |
"מספר פסקאות לאחזור (Retrieve)", 1, 300, 100, | |
help="כמה פסקאות לאחזר ראשונית (משותף לכל השיטות)." | |
) | |
n_analyze = st.sidebar.slider( | |
"מספר פסקאות לניתוח (Anthropic בלבד)", 1, min(n_retrieve, 50), min(21, n_retrieve, 50), | |
help="כמה פסקאות יישלחו לניתוח רלוונטיות פרטני ע'י Claude.", | |
disabled=not is_anthropic_pipeline | |
) | |
relevance_thresh = st.sidebar.slider( | |
"סף רלוונטיות (Anthropic בלבד)", 1, 10, 5, | |
help="הציון המינימלי (1-10) שפסקה צריכה לקבל מ-Claude כדי להיחשב רלוונטית.", | |
disabled=not is_anthropic_pipeline | |
) | |
n_validate = st.sidebar.slider( | |
"מספר פסקאות לאימות (GPT-4o בלבד)", 1, min(n_retrieve, 100), min(50, n_retrieve), | |
help="כמה מהפסקאות שאוחזרו יישלחו לאימות רלוונטיות פרטני ע'י GPT-4o.", | |
disabled=not is_gpt4o_para_pipeline | |
) | |
n_final_context = st.sidebar.slider( | |
"פסקאות מקסימום להקשר סופי (Gemini/Anthropic)", 1, n_retrieve, min(21, n_retrieve), | |
help="Gemini/Anthropic: כמה מהפסקאות הטובות ביותר יישלחו ליצירה. GPT-4o: לא בשימוש ישיר (הקשר נקבע ע'י האימות).", | |
disabled=is_gpt4o_para_pipeline | |
) | |
services_ready = ( | |
retriever_ready and | |
((anthropic_analyzer_ready and anthropic_generator_ready) if is_anthropic_pipeline else True) and | |
(gemini_generator_ready if is_gemini_pipeline else True) and | |
((openai_validator_ready and anthropic_generator_ready) if is_gpt4o_para_pipeline else True) | |
) | |
if not services_ready and retriever_ready: | |
st.markdown( | |
f"<div class='alert alert-warning rtl-text' role='alert'>שירות(ים) חסרים. ודא שכל השירותים דרושים זמינים.</div>", | |
unsafe_allow_html=True, | |
) | |
def run_rag_pipeline(pipeline_prompt: str, selected_pipeline_method: str, status_container=None): | |
is_anthropic_pipeline = selected_pipeline_method == "Anthropic (ניתוח וסינון פרטני)" | |
is_gemini_pipeline = selected_pipeline_method == "Gemini (אחזור ויצירה ישירה)" | |
is_gpt4o_para_pipeline = selected_pipeline_method == "GPT-4o Paragraph Validator + Claude Synthesizer" | |
result = { | |
"full_response": "", "final_docs_data": [], "status_updates": [], | |
"error": None, "analysis_flow": selected_pipeline_method | |
} | |
current_status_label = "מתחיל עיבוד..." | |
message_placeholder = st.empty() | |
try: | |
current_status_label = f"1. מאחזר עד {n_retrieve} פסקאות מ-Pinecone..." | |
start_retrieval = time.time() | |
if status_container: status_container.update(label=current_status_label) | |
retrieved_docs = find_similar_paragraphs(query_text=pipeline_prompt, n_results=n_retrieve) | |
retrieval_time = time.time() - start_retrieval | |
status_msg = f"אוחזרו {len(retrieved_docs)} פסקאות ב-{retrieval_time:.2f} שניות." | |
result["status_updates"].append(f"1. {status_msg}") | |
current_status_label = f"1. {status_msg}" | |
if status_container: status_container.update(label=current_status_label) | |
if not retrieved_docs: | |
result["full_response"] = "<div class='rtl-text'>לא אותרו מקורות רלוונטיים לשאילתה.</div>" | |
if status_container: status_container.update(label="לא נמצאו מסמכים.", state="complete") | |
message_placeholder.markdown(result["full_response"], unsafe_allow_html=True) | |
return result | |
docs_for_generator = [] | |
generator_name = "" | |
if is_anthropic_pipeline: | |
generator_name = "Anthropic" | |
analysis_count = min(len(retrieved_docs), n_analyze) | |
current_status_label = f"2. [Anthropic] מנתח רלוונטיות פרטנית ({analysis_count} פסקאות)..." | |
analysis_start_time = time.time() | |
if status_container: status_container.update(label=current_status_label) | |
async def run_anthropic_analysis(): | |
docs_to_analyze_local = retrieved_docs[:analysis_count] | |
tasks = [analyze_source_relevance_async(d.get('hebrew_text',''), '', pipeline_prompt) for d in docs_to_analyze_local] | |
analysis_results = await asyncio.gather(*tasks, return_exceptions=True) | |
return docs_to_analyze_local, analysis_results | |
try: | |
loop = asyncio.get_event_loop_policy().get_event_loop() | |
if loop.is_running(): nest_asyncio.apply(); loop = asyncio.get_event_loop_policy().get_event_loop() | |
docs_analyzed, analysis_raw_results = loop.run_until_complete(run_anthropic_analysis()) | |
except Exception as loop_err: raise | |
processed_for_filter = []; analysis_success_count = 0; analysis_fail_count = 0; | |
for i, doc in enumerate(docs_analyzed): | |
res = analysis_raw_results[i] | |
if isinstance(res, dict) and 'relevance' in res: | |
doc['analysis'] = res; processed_for_filter.append(doc); analysis_success_count += 1 | |
elif isinstance(res, Exception): analysis_fail_count += 1; | |
else: analysis_fail_count += 1; | |
analysis_time = time.time() - analysis_start_time | |
status_msg = f"ניתוח Anthropic פרטני הושלם ({analysis_success_count} הצלחות, {analysis_fail_count} כשלונות) ב-{analysis_time:.2f} שניות." | |
result["status_updates"].append(f"2. {status_msg}") | |
current_status_label = f"2. {status_msg}" | |
if status_container: status_container.update(label=current_status_label) | |
current_status_label = "3. [Anthropic] סינון לפי ציון רלוונטיות..." | |
if status_container: status_container.update(label=current_status_label) | |
filtered_docs = [] | |
for doc in processed_for_filter: | |
try: | |
score = int(doc.get('analysis', {}).get('relevance', {}).get('relevance_score', '0')) | |
doc['analysis']['relevance']['numeric_score'] = score | |
if score >= relevance_thresh: filtered_docs.append(doc) | |
except Exception as filter_err: pass | |
filtered_docs.sort(key=lambda d: d.get('analysis',{}).get('relevance',{}).get('numeric_score', 0), reverse=True) | |
docs_for_generator = filtered_docs[:n_final_context] | |
status_msg = f"נבחרו {len(docs_for_generator)} פסקאות לאחר סינון Anthropic (סף: {relevance_thresh}, מקס': {n_final_context})." | |
result["status_updates"].append(f"3. {status_msg}") | |
current_status_label = f"3. {status_msg}" | |
if status_container: status_container.update(label=current_status_label) | |
if not docs_for_generator: | |
result["full_response"] = "<div class='rtl-text'>לא נמצאו פסקאות רלוונטיות מספיק לאחר סינון Anthropic פרטני.</div>" | |
if status_container: status_container.update(label="לא נמצאו פסקאות מסוננות.", state="complete") | |
message_placeholder.markdown(result["full_response"], unsafe_allow_html=True) | |
return result | |
elif is_gemini_pipeline: | |
generator_name = "Gemini" | |
status_msg = "2. דילוג על שלב ניתוח/סינון (שיטת Gemini)."; result["status_updates"].append(status_msg) | |
current_status_label = status_msg; | |
if status_container: status_container.update(label=current_status_label) | |
docs_for_generator = retrieved_docs[:n_final_context] | |
status_msg = f"3. נבחרו {len(docs_for_generator)} פסקאות מובילות (לפי אחזור) להקשר עבור Gemini (מקס': {n_final_context})." | |
result["status_updates"].append(status_msg) | |
current_status_label = status_msg | |
if status_container: status_container.update(label=current_status_label) | |
if not docs_for_generator: | |
result["full_response"] = "<div class='rtl-text'>לא אותרו מסמכים כלל (שגיאה פנימית).</div>" | |
if status_container: status_container.update(label="שגיאה בבחירת הקשר.", state="error") | |
message_placeholder.markdown(result["full_response"], unsafe_allow_html=True) | |
return result | |
elif is_gpt4o_para_pipeline: | |
generator_name = "Anthropic" | |
docs_to_validate = retrieved_docs[:n_validate] | |
num_to_validate = len(docs_to_validate) | |
if not docs_to_validate: | |
result["full_response"] = "<div class='rtl-text'>שגיאה: אין מסמכים לאימות (לאחר אחזור).</div>" | |
if status_container: status_container.update(label="שגיאה לפני אימות.", state="error") | |
message_placeholder.markdown(result["full_response"], unsafe_allow_html=True) | |
return result | |
status_msg = f"2. נבחרו {num_to_validate} פסקאות מובילות לאימות פרטני (מתוך {len(retrieved_docs)})." | |
result["status_updates"].append(status_msg) | |
current_status_label = status_msg | |
if status_container: status_container.update(label=current_status_label) | |
current_status_label = f"3. [GPT-4o] מתחיל אימות מקבילי של {num_to_validate} פסקאות..." | |
validation_start_time = time.time() | |
if status_container: status_container.update(label=current_status_label) | |
tasks = [validate_paragraph_relevance_gpt4o(doc, pipeline_prompt, i) for i, doc in enumerate(docs_to_validate)] | |
validation_results = [] | |
try: | |
loop = asyncio.get_event_loop_policy().get_event_loop() | |
if loop.is_running(): nest_asyncio.apply(); loop = asyncio.get_event_loop_policy().get_event_loop() | |
validation_results = loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True)) | |
except Exception as gather_err: | |
result["error"] = f"שגיאה בביצוע האימות המקבילי: {gather_err}" | |
result["full_response"] = f"<div class='rtl-text'>אירעה שגיאה קריטית בשלב אימות המידע.</div>"; | |
if status_container: status_container.update(label="שגיאה באימות!", state="error") | |
message_placeholder.markdown(result["full_response"], unsafe_allow_html=True) | |
return result | |
validation_time = time.time() - validation_start_time | |
passed_count = 0; failed_count = 0; filtered_paragraphs = [] | |
current_status_label = "4. [GPT-4o] סינון פסקאות לפי תוצאות האימות..." | |
if status_container: status_container.update(label=current_status_label) | |
for i, res in enumerate(validation_results): | |
para_num = i + 1 | |
if isinstance(res, Exception): failed_count += 1; | |
elif isinstance(res, dict) and res.get("validation"): | |
if res["validation"].get("contains_relevant_info") is True: | |
passed_count += 1; filtered_paragraphs.append(res.get("paragraph_data", {})) | |
else: failed_count += 1; | |
filtered_paragraphs = [p for p in filtered_paragraphs if p] | |
status_msg_val = f"אימות GPT-4o פרטני הושלם ({passed_count} עברו, {num_to_validate - passed_count - failed_count} נדחו, {failed_count} נכשלו) ב-{validation_time:.2f} שניות." | |
result["status_updates"].append(f"3. {status_msg_val}") | |
status_msg_filter = f"נאספו {len(filtered_paragraphs)} פסקאות רלוונטיות לאחר אימות." | |
result["status_updates"].append(f"4. {status_msg_filter}") | |
current_status_label = f"4. {status_msg_filter}" | |
if status_container: status_container.update(label=current_status_label) | |
if not filtered_paragraphs: | |
result["full_response"] = "<div class='rtl-text'>לא נמצא מידע רלוונטי בפסקאות שנבדקו ע'י GPT-4o.</div>" | |
if status_container: status_container.update(label="לא נמצא מידע רלוונטי.", state="complete") | |
message_placeholder.markdown(result["full_response"], unsafe_allow_html=True) | |
return result | |
docs_for_generator = filtered_paragraphs | |
else: | |
raise ValueError(f"שיטת עיבוד לא ידועה: {selected_pipeline_method}") | |
current_status_label = f"5. מכין הקשר ({len(docs_for_generator)} פסקאות) ומחולל תשובה סופית ({generator_name})..." | |
result["status_updates"].append(f"5. מכין הקשר ומחולל תשובה ({generator_name})...") | |
if status_container: status_container.update(label=current_status_label) | |
start_generation = time.time() | |
final_response_text = "" | |
generation_error_details = None | |
result["final_docs_data"] = docs_for_generator | |
try: | |
if generator_name == "Gemini": | |
generator_stream = generate_gemini(query=pipeline_prompt, context_documents=docs_for_generator) | |
response_chunks = [] | |
for chunk in generator_stream: | |
if isinstance(chunk, str) and chunk.strip().startswith("--- שגיאה"): | |
generation_error_details = chunk.strip() | |
break | |
response_chunks.append(str(chunk)) | |
temp_stream_response = "".join(response_chunks) | |
message_placeholder.markdown(f"<div class='rtl-text'>{temp_stream_response}▌</div>", unsafe_allow_html=True) | |
if generation_error_details is None: final_response_text = "".join(response_chunks) | |
elif generator_name == "Anthropic": | |
async def consume_anthropic_stream(): | |
history = [{"role": "user", "content": pipeline_prompt}] | |
local_chunks = [] | |
async for chunk in generate_anthropic(messages=history, context_documents=docs_for_generator): | |
if isinstance(chunk, str) and chunk.strip().startswith("--- שגיאה"): | |
raise RuntimeError(f"Error yielded from Anthropic generator: {chunk.strip()}") | |
local_chunks.append(str(chunk)) | |
temp_response = "".join(local_chunks) | |
message_placeholder.markdown(f"<div class='rtl-text'>{temp_response}▌</div>", unsafe_allow_html=True) | |
return "".join(local_chunks) | |
try: | |
loop = asyncio.get_event_loop_policy().get_event_loop() | |
if loop.is_running(): nest_asyncio.apply(); loop = asyncio.get_event_loop_policy().get_event_loop() | |
final_response_text = loop.run_until_complete(consume_anthropic_stream()) | |
except Exception as consume_err: | |
generation_error_details = f"{type(consume_err).__name__}: {str(consume_err)}" | |
else: | |
raise RuntimeError(f"Generator name '{generator_name}' not recognized.") | |
except Exception as gen_err: | |
generation_error_details = f"{type(gen_err).__name__}: {str(gen_err)}" | |
generation_time = time.time() - start_generation | |
if generation_error_details: | |
result["error"] = f"שגיאה במהלך יצירת התשובה ({generator_name}): {generation_error_details}" | |
result["full_response"] = f"<div class='rtl-text'><strong>שגיאה ביצירת התשובה.</strong><br>פרטים: {generation_error_details}</div>" | |
message_placeholder.markdown(result["full_response"], unsafe_allow_html=True) | |
else: | |
lines_to_remove = ["יהי רצון שנזכה לגאולה השלמה במהרה בימינו אמן.", "יהי רצון שנזכה...", "הכותב וחותם לכבוד התורה ולומדיה", "הכותב וחותם לכבוד התורה...", "בכבוד רב,", "בברכה,"] | |
response_lines = final_response_text.strip().split('\n'); cleaned_lines = response_lines[:] | |
while cleaned_lines: | |
last_line = cleaned_lines[-1].strip() | |
if any(last_line.lower() == ltr.lower() or last_line.lower().startswith(ltr.lower().replace('...','')) for ltr in lines_to_remove): cleaned_lines.pop() | |
else: break | |
final_response_text = "\n".join(cleaned_lines).strip() | |
result["full_response"] = final_response_text | |
message_placeholder.markdown(f"<div class='rtl-text'>{final_response_text}</div>", unsafe_allow_html=True) | |
except Exception as e: | |
pipeline_error_type = type(e).__name__; pipeline_error_msg = str(e) | |
result["error"] = f"שגיאה בזמן הריצה: {pipeline_error_type}: {pipeline_error_msg}" | |
result["full_response"] = f"<div class='rtl-text'><strong>שגיאה במהלך העיבוד ({pipeline_error_type})</strong><br>אנא נסה שוב מאוחר יותר.<details><summary>פרטים טכניים</summary><pre>{traceback.format_exc()}</pre></details></div>" | |
message_placeholder.markdown(result["full_response"], unsafe_allow_html=True) | |
if status_container: status_container.update(label="שגיאה בעיבוד!", state="error") | |
return result | |
if "messages" not in st.session_state: | |
st.session_state.messages = [] | |
for message in st.session_state.messages: | |
with st.chat_message(message["role"]): | |
content_display = message['content'] | |
if not content_display.strip().startswith(('<div', '<p', '<strong', '<details')): | |
content_display = f"<div class='rtl-text'>{content_display}</div>" | |
st.markdown(content_display, unsafe_allow_html=True) | |
if message["role"] == "assistant" and "final_docs" in message and message["final_docs"]: | |
final_docs_data = message.get("final_docs", []) | |
pipeline_flow_used = message.get("analysis_flow", "לא ידוע") | |
if final_docs_data: | |
st.expander("מסמכים שנמצאו", expanded=False).write(final_docs_data) | |
expander_title_text = f"הצג {len(final_docs_data)} פסקאות מקור שנשלחו למחולל" | |
if pipeline_flow_used == "Anthropic (ניתוח וסינון פרטני)": | |
expander_title_text += " (לאחר סינון Anthropic פרטני)" | |
elif pipeline_flow_used == "Gemini (אחזור ויצירה ישירה)": | |
expander_title_text += " (ללא סינון נוסף)" | |
elif pipeline_flow_used == "GPT-4o Paragraph Validator + Claude Synthesizer": | |
expander_title_text += " (לאחר אימות GPT-4o פרטני)" | |
else: | |
expander_title_text += " (לאחר עיבוד)" | |
expander_title = f"<span class='rtl-text'>{expander_title_text}</span>" | |
with st.expander(expander_title, expanded=False): | |
st.markdown("<div class='expander-content'>", unsafe_allow_html=True) | |
for i, doc in enumerate(final_docs_data): | |
score_info = "" | |
source_name = doc.get('source_name', 'לא ידוע') | |
original_id = doc.get('original_id', 'N/A') | |
hebrew_text = doc.get('hebrew_text', 'טקסט המקור חסר') | |
st.markdown( | |
f"<div class='source-info rtl-text'><strong>מקור {i+1}:</strong> ספר: {source_name}, ID: {original_id}{score_info}</div>", | |
unsafe_allow_html=True, | |
) | |
st.markdown(f"<div class='hebrew-text'>{hebrew_text}</div>", unsafe_allow_html=True) | |
st.markdown("</div>", unsafe_allow_html=True) | |
if prompt := st.chat_input("שאל שאלה בענייני חסידות...", disabled=not services_ready, key="chat_input"): | |
st.session_state.messages.append({"role": "user", "content": prompt}) | |
with st.chat_message("user"): | |
st.markdown(f"<div class='rtl-text'>{prompt}</div>", unsafe_allow_html=True) | |
with st.chat_message("assistant"): | |
status_control_asst = None | |
rag_result_asst = None | |
try: | |
status_label = f"<span class='rtl-text'>מעבד בקשה באמצעות '{pipeline_method}'...</span>" | |
with st.status(status_label, expanded=True) as status: | |
status_control_asst = status | |
rag_result_asst = run_rag_pipeline( | |
pipeline_prompt=prompt, | |
selected_pipeline_method=pipeline_method, | |
status_container=status_control_asst, | |
) | |
if rag_result_asst and isinstance(rag_result_asst, dict): | |
pipeline_error_value = rag_result_asst.get("error") | |
final_docs_value = rag_result_asst.get("final_docs_data", []) | |
final_docs_to_store = [] | |
if pipeline_error_value is None: | |
final_docs_to_store = final_docs_value | |
flow_to_store = rag_result_asst.get("analysis_flow", "Error") | |
if pipeline_error_value is not None: | |
flow_to_store = "Error" | |
st.session_state.messages.append({ | |
"role": "assistant", | |
"content": rag_result_asst.get("full_response", "..."), | |
"final_docs": final_docs_to_store, | |
"analysis_flow": flow_to_store, | |
}) | |
if rag_result_asst.get("status_updates"): | |
expander_label = "<span class='rtl-text'>הצג שלבי עיבוד</span>" | |
with st.expander(expander_label, expanded=False): | |
for update in rag_result_asst["status_updates"]: | |
st.markdown(f"<div class='rtl-text'><code>- {update}</code></div>", unsafe_allow_html=True) | |
else: | |
fallback_err_msg_html = "<div class='rtl-text'><strong>שגיאה בלתי צפויה בתקשורת עם מנגנון העיבוד (fallback).</strong></div>" | |
st.session_state.messages.append({ | |
"role": "assistant", | |
"content": fallback_err_msg_html, | |
"final_docs": [], | |
"analysis_flow": "Error", | |
}) | |
except Exception as e: | |
error_display_html = f"<div class='rtl-text'><strong>שגיאה קריטית!</strong><br><pre>{traceback.format_exc()}</pre></div>" | |
st.error(error_display_html, icon="🔥") | |
st.session_state.messages.append({ | |
"role": "assistant", | |
"content": error_display_html, | |
"final_docs": [], | |
"analysis_flow": "Critical Error", | |
}) | |