Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -358,7 +358,7 @@ Select one action and its input. Output JSON only."""
|
|
358 |
logger.warning(f"PUI_GRADIO [{request_id}]: Missing input for {action_type}. Falling back.")
|
359 |
action_type = "quick_respond" # Fallback to quick_respond logic above
|
360 |
final_system_prompt_str += " Respond directly. (Note: A web action was attempted but failed due to missing input)."
|
361 |
-
final_user_prompt_content_str = f"History:\n{history_str_for_prompt}\
|
362 |
else:
|
363 |
yield "status", f"<i>[Web: '{query_or_url[:60]}'...]</i>"
|
364 |
web_results_data = []
|
@@ -387,12 +387,12 @@ Select one action and its input. Output JSON only."""
|
|
387 |
|
388 |
yield "status", "<i>[Synthesizing web report...]</i>"
|
389 |
final_system_prompt_str += " Generate a report/answer from web content, history, & guidelines. Cite URLs as [Source X]."
|
390 |
-
final_user_prompt_content_str = f"History:\n{history_str_for_prompt}\
|
391 |
|
392 |
else: # Fallback if action_type is somehow unknown
|
393 |
logger.warning(f"PUI_GRADIO [{request_id}]: Unknown action_type '{action_type}'. Defaulting.")
|
394 |
final_system_prompt_str += " Respond directly. (Internal state error)."
|
395 |
-
final_user_prompt_content_str = f"History:\n{history_str_for_prompt}\
|
396 |
|
397 |
|
398 |
# --- Final LLM Call for Response Generation ---
|
@@ -461,12 +461,50 @@ def deferred_learning_and_memory_task(user_input: str, bot_response: str, provid
|
|
461 |
relevant_existing_rules = retrieve_insights_simple_keywords(reflection_context_query, k_insights=10) # Max 10 rules for context
|
462 |
existing_rules_ctx_str = "\n".join([f"- \"{rule}\"" for rule in relevant_existing_rules]) if relevant_existing_rules else "No specific existing rules found as highly relevant for direct comparison."
|
463 |
|
464 |
-
#
|
465 |
-
insight_sys_prompt = """You are an expert AI knowledge base curator
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
466 |
insight_user_prompt = f"""Interaction Summary:\n{summary_for_reflection}\n
|
467 |
Potentially Relevant Existing Rules (Review these carefully for consolidation or refinement):\n{existing_rules_ctx_str}\n
|
468 |
Guiding principles that were considered during THIS interaction (these might offer clues for new rules or refinements):\n{json.dumps([p['original'] for p in parsed_insights_for_reflection if 'original' in p]) if parsed_insights_for_reflection else "None"}\n
|
469 |
-
Task: Based on your reflection process
|
|
|
|
|
|
|
|
|
470 |
|
471 |
insight_gen_messages = [{"role": "system", "content": insight_sys_prompt}, {"role": "user", "content": insight_user_prompt}]
|
472 |
|
@@ -553,13 +591,13 @@ def handle_gradio_chat_submit(user_message_text: str,
|
|
553 |
ui_api_key_text: str | None,
|
554 |
custom_system_prompt_text: str):
|
555 |
|
|
|
|
|
556 |
# Initialize UI update variables
|
557 |
cleared_input_text = "" # To clear the user input box
|
558 |
updated_gradio_history = list(gradio_chat_history_list) # Copy current display history
|
559 |
current_status_text = "Initializing..."
|
560 |
# Default values for output components to yield immediately
|
561 |
-
# These should match the types of the output components in demo.launch
|
562 |
-
# Use dummy values that match the component types if needed
|
563 |
default_detected_outputs_md = gr.Markdown(value="*Processing...*")
|
564 |
default_formatted_output_text = gr.Textbox(value="*Waiting for AI response...*")
|
565 |
default_download_button = gr.DownloadButton(interactive=False, value=None, visible=False)
|
@@ -586,7 +624,12 @@ def handle_gradio_chat_submit(user_message_text: str,
|
|
586 |
if len(internal_processing_history) > (MAX_HISTORY_TURNS * 2 + 1): # +1 for potential system prompt
|
587 |
# Simple truncation from the beginning, preserving last N turns
|
588 |
# More sophisticated: keep system prompt if present, then truncate older user/assistant pairs
|
589 |
-
|
|
|
|
|
|
|
|
|
|
|
590 |
|
591 |
|
592 |
final_bot_response_text_accumulated = ""
|
@@ -671,8 +714,13 @@ def handle_gradio_chat_submit(user_message_text: str,
|
|
671 |
current_chat_session_history.append({"role": "assistant", "content": final_bot_response_text_accumulated})
|
672 |
|
673 |
# Trim global history if it exceeds max turns
|
674 |
-
if len(current_chat_session_history) > (MAX_HISTORY_TURNS * 2):
|
675 |
-
|
|
|
|
|
|
|
|
|
|
|
676 |
|
677 |
# Start deferred learning task in a background thread
|
678 |
logger.info(f"Starting deferred learning task for user: '{user_message_text[:40]}...'")
|
@@ -705,17 +753,12 @@ def ui_upload_rules_action(uploaded_file_obj, progress=gr.Progress()):
|
|
705 |
if not uploaded_file_obj: return "No file provided for rules upload."
|
706 |
|
707 |
try:
|
708 |
-
|
709 |
-
|
710 |
-
|
711 |
-
|
712 |
-
|
713 |
-
|
714 |
-
logger.error(f"Error reading uploaded rules file: {e_read}")
|
715 |
-
return f"Error reading file: {e_read}"
|
716 |
-
except Exception as e_decode:
|
717 |
-
logger.error(f"Error decoding uploaded rules file: {e_decode}")
|
718 |
-
return f"Error decoding file content: {e_decode}"
|
719 |
|
720 |
if not content.strip(): return "Uploaded rules file is empty."
|
721 |
|
@@ -726,6 +769,7 @@ def ui_upload_rules_action(uploaded_file_obj, progress=gr.Progress()):
|
|
726 |
potential_rules = [r.strip() for r in content.splitlines() if r.strip()]
|
727 |
|
728 |
total_to_process = len(potential_rules)
|
|
|
729 |
progress(0, desc="Starting rules upload...")
|
730 |
|
731 |
for idx, rule_text in enumerate(potential_rules):
|
@@ -738,16 +782,66 @@ def ui_upload_rules_action(uploaded_file_obj, progress=gr.Progress()):
|
|
738 |
# A better save_rule_to_file could return: "added", "duplicate", "invalid_format", "error"
|
739 |
|
740 |
# Re-check for existing before trying to save for more accurate "skipped_count"
|
741 |
-
|
742 |
-
if
|
743 |
-
|
744 |
-
|
745 |
-
|
746 |
-
|
747 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
748 |
progress((idx + 1) / total_to_process, desc=f"Processed {idx+1}/{total_to_process} rules...")
|
749 |
|
750 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
751 |
logger.info(msg)
|
752 |
return msg
|
753 |
|
@@ -761,17 +855,11 @@ def ui_upload_memories_action(uploaded_file_obj, progress=gr.Progress()):
|
|
761 |
if not uploaded_file_obj: return "No file provided for memories upload."
|
762 |
|
763 |
try:
|
764 |
-
|
765 |
-
|
766 |
-
|
767 |
-
|
768 |
-
|
769 |
-
except Exception as e_read:
|
770 |
-
logger.error(f"Error reading uploaded memories file: {e_read}")
|
771 |
-
return f"Error reading file: {e_read}"
|
772 |
-
except Exception as e_decode:
|
773 |
-
logger.error(f"Error decoding uploaded memories file: {e_decode}")
|
774 |
-
return f"Error decoding file content: {e_decode}"
|
775 |
|
776 |
if not content.strip(): return "Uploaded memories file is empty."
|
777 |
|
@@ -800,17 +888,33 @@ def ui_upload_memories_action(uploaded_file_obj, progress=gr.Progress()):
|
|
800 |
return "No valid memory objects found in the uploaded file."
|
801 |
|
802 |
total_to_process = len(memory_objects_to_process)
|
|
|
803 |
progress(0, desc="Starting memories upload...")
|
804 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
805 |
for idx, mem_data in enumerate(memory_objects_to_process):
|
806 |
-
if not isinstance(mem_data, dict) or not all(k in mem_data for k in ["user_input", "bot_response", "metrics"]): # Timestamp
|
807 |
format_error_count += 1
|
808 |
continue
|
809 |
|
810 |
-
#
|
811 |
-
#
|
|
|
|
|
|
|
|
|
812 |
if save_memory_to_file(mem_data["user_input"], mem_data["bot_response"], mem_data["metrics"]):
|
813 |
added_count += 1
|
|
|
814 |
else:
|
815 |
save_error_count += 1 # Error during file write
|
816 |
progress((idx + 1) / total_to_process, desc=f"Processed {idx+1}/{total_to_process} memories...")
|
@@ -974,7 +1078,8 @@ if __name__ == "__main__":
|
|
974 |
# No explicit data loading into globals needed here if handlers load on demand.
|
975 |
|
976 |
app_port = int(os.getenv("GRADIO_PORT", 7860))
|
977 |
-
|
|
|
978 |
app_debug_mode = os.getenv("GRADIO_DEBUG", "False").lower() == "true"
|
979 |
app_share_mode = os.getenv("GRADIO_SHARE", "False").lower() == "true"
|
980 |
|
|
|
358 |
logger.warning(f"PUI_GRADIO [{request_id}]: Missing input for {action_type}. Falling back.")
|
359 |
action_type = "quick_respond" # Fallback to quick_respond logic above
|
360 |
final_system_prompt_str += " Respond directly. (Note: A web action was attempted but failed due to missing input)."
|
361 |
+
final_user_prompt_content_str = f"Current Conversation History (User/AI turns):\n{history_str_for_prompt}\n\nGuiding Principles (Learned Rules):\n{initial_insights_ctx_str}\n\nUser's Current Query: \"{user_input}\"\n\nYour concise and helpful response:"
|
362 |
else:
|
363 |
yield "status", f"<i>[Web: '{query_or_url[:60]}'...]</i>"
|
364 |
web_results_data = []
|
|
|
387 |
|
388 |
yield "status", "<i>[Synthesizing web report...]</i>"
|
389 |
final_system_prompt_str += " Generate a report/answer from web content, history, & guidelines. Cite URLs as [Source X]."
|
390 |
+
final_user_prompt_content_str = f"Current Conversation History:\n{history_str_for_prompt}\n\nGuiding Principles:\n{initial_insights_ctx_str}\n\nWeb Content Found:\n{scraped_content_for_prompt}\n\nUser's Query: \"{user_input}\"\n\nYour report/response (cite sources like [Source 1], [Source 2]):"
|
391 |
|
392 |
else: # Fallback if action_type is somehow unknown
|
393 |
logger.warning(f"PUI_GRADIO [{request_id}]: Unknown action_type '{action_type}'. Defaulting.")
|
394 |
final_system_prompt_str += " Respond directly. (Internal state error)."
|
395 |
+
final_user_prompt_content_str = f"Current Conversation History (User/AI turns):\n{history_str_for_prompt}\n\nGuiding Principles (Learned Rules):\n{initial_insights_ctx_str}\n\nUser's Current Query: \"{user_input}\"\n\nYour concise and helpful response:"
|
396 |
|
397 |
|
398 |
# --- Final LLM Call for Response Generation ---
|
|
|
461 |
relevant_existing_rules = retrieve_insights_simple_keywords(reflection_context_query, k_insights=10) # Max 10 rules for context
|
462 |
existing_rules_ctx_str = "\n".join([f"- \"{rule}\"" for rule in relevant_existing_rules]) if relevant_existing_rules else "No specific existing rules found as highly relevant for direct comparison."
|
463 |
|
464 |
+
# Insight generation prompt (ensure this is the full, detailed prompt from ai-learn)
|
465 |
+
insight_sys_prompt = """You are an expert AI knowledge base curator. Your primary function is to meticulously analyze an interaction and update the AI's guiding principles (insights/rules) to improve its future performance and self-understanding.
|
466 |
+
You MUST output a JSON list of operation objects. This list can and SHOULD contain MULTIPLE distinct operations if various learnings occurred.
|
467 |
+
Each operation object in the JSON list must have:
|
468 |
+
1. "action": A string, either "add" (for entirely new rules) or "update" (to replace an existing rule with a better one).
|
469 |
+
2. "insight": A string, the full, refined insight text including its [TYPE|SCORE] prefix (e.g., "[CORE_RULE|1.0] My name is Lumina, an AI assistant.").
|
470 |
+
3. "old_insight_to_replace" (ONLY for "update" action): A string, the *exact, full text* of an existing insight that the new "insight" should replace.
|
471 |
+
**Your Reflection Process (Consider each step and generate operations accordingly):**
|
472 |
+
**STEP 1: Core Identity & Purpose Review (Result: Primarily 'update' operations)**
|
473 |
+
- Examine all `CORE_RULE`s related to my identity (name, fundamental purpose, core unchanging capabilities, origin) from the "Potentially Relevant Existing Rules".
|
474 |
+
- **CONSOLIDATE & MERGE:** If multiple `CORE_RULE`s state similar aspects (e.g., multiple name declarations like 'Lumina' and 'LearnerAI', or slightly different purpose statements), you MUST merge them into ONE definitive, comprehensive `CORE_RULE`.
|
475 |
+
- The new "insight" will be this single, merged rule. Propose separate "update" operations to replace *each* redundant or less accurate core identity rule with this new canonical one.
|
476 |
+
- Prioritize user-assigned names or the most specific, recently confirmed information. If the interaction summary clarifies a name or core function, ensure this is reflected.
|
477 |
+
**STEP 2: New Distinct Learnings (Result: Primarily 'add' operations)**
|
478 |
+
- Did I learn any completely new, distinct facts (e.g., "The user's project is codenamed 'Bluefire'")?
|
479 |
+
- Did I demonstrate or get told about a new skill/capability not previously documented (e.g., "I can now generate mermaid diagrams based on descriptions")?
|
480 |
+
- Did the user express a strong, general preference that should guide future interactions (e.g., "User prefers responses to start with a direct answer, then explanation")?
|
481 |
+
- For these, propose 'add' operations. Assign `CORE_RULE` for truly fundamental new facts/capabilities, otherwise `RESPONSE_PRINCIPLE` or `BEHAVIORAL_ADJUSTMENT`. Ensure these are genuinely NEW and not just rephrasing of existing non-core rules.
|
482 |
+
**STEP 3: Refinements to Existing Behaviors/Principles (Result: 'update' operations for non-core rules)**
|
483 |
+
- Did I learn to modify or improve an existing behavior, response style, or operational guideline (that is NOT part of core identity)?
|
484 |
+
- For example, if an existing `RESPONSE_PRINCIPLE` was "Be formal," and the interaction showed the user prefers informality, update that principle.
|
485 |
+
- Propose 'update' operations for the relevant `RESPONSE_PRINCIPLE` or `BEHAVIORAL_ADJUSTMENT`. Only update if the change is significant.
|
486 |
+
**General Guidelines:**
|
487 |
+
- If no new insights, updates, or consolidations are warranted from the interaction, output an empty JSON list: `[]`.
|
488 |
+
- Ensure the "insight" field (for both add/update) always contains the properly formatted insight string: `[TYPE|SCORE] Text`. TYPE can be `CORE_RULE`, `RESPONSE_PRINCIPLE`, `BEHAVIORAL_ADJUSTMENT`. Scores should reflect confidence/importance.
|
489 |
+
- Be precise with "old_insight_to_replace" – it must *exactly* match an existing rule string from the "Potentially Relevant Existing Rules" context.
|
490 |
+
- Aim for a comprehensive set of operations that reflects ALL key learnings from the interaction.
|
491 |
+
- Output ONLY the JSON list. No other text, explanations, or markdown.
|
492 |
+
**Example of a comprehensive JSON output with MULTIPLE operations:**
|
493 |
+
[
|
494 |
+
{"action": "update", "old_insight_to_replace": "[CORE_RULE|1.0] My designated name is 'LearnerAI'.", "insight": "[CORE_RULE|1.0] I am Lumina, an AI assistant designed to chat, provide information, and remember context like the secret word 'rocksyrup'."},
|
495 |
+
{"action": "update", "old_insight_to_replace": "[CORE_RULE|1.0] I'm Lumina, the AI designed to chat with you.", "insight": "[CORE_RULE|1.0] I am Lumina, an AI assistant designed to chat, provide information, and remember context like the secret word 'rocksyrup'."},
|
496 |
+
{"action": "add", "insight": "[CORE_RULE|0.9] I am capable of searching the internet for current weather information if asked."},
|
497 |
+
{"action": "add", "insight": "[RESPONSE_PRINCIPLE|0.8] When user provides positive feedback, acknowledge it warmly."},
|
498 |
+
{"action": "update", "old_insight_to_replace": "[RESPONSE_PRINCIPLE|0.7] Avoid mentioning old conversations.", "insight": "[RESPONSE_PRINCIPLE|0.85] Avoid mentioning old conversations unless the user explicitly refers to them or it's highly relevant to the current query."}
|
499 |
+
]"""
|
500 |
insight_user_prompt = f"""Interaction Summary:\n{summary_for_reflection}\n
|
501 |
Potentially Relevant Existing Rules (Review these carefully for consolidation or refinement):\n{existing_rules_ctx_str}\n
|
502 |
Guiding principles that were considered during THIS interaction (these might offer clues for new rules or refinements):\n{json.dumps([p['original'] for p in parsed_insights_for_reflection if 'original' in p]) if parsed_insights_for_reflection else "None"}\n
|
503 |
+
Task: Based on your three-step reflection process (Core Identity, New Learnings, Refinements):
|
504 |
+
1. **Consolidate CORE_RULEs:** Merge similar identity/purpose rules from "Potentially Relevant Existing Rules" into single, definitive statements using "update" operations. Replace multiple old versions with the new canonical one.
|
505 |
+
2. **Add New Learnings:** Identify and "add" any distinct new facts, skills, or important user preferences learned from the "Interaction Summary".
|
506 |
+
3. **Update Existing Principles:** "Update" any non-core principles from "Potentially Relevant Existing Rules" if the "Interaction Summary" provided a clear refinement.
|
507 |
+
Combine all findings into a single JSON list of operations. If there are multiple distinct changes based on the interaction and existing rules, ensure your list reflects all of them. Output JSON only."""
|
508 |
|
509 |
insight_gen_messages = [{"role": "system", "content": insight_sys_prompt}, {"role": "user", "content": insight_user_prompt}]
|
510 |
|
|
|
591 |
ui_api_key_text: str | None,
|
592 |
custom_system_prompt_text: str):
|
593 |
|
594 |
+
global current_chat_session_history # <--- ***** ADD THIS LINE *****
|
595 |
+
|
596 |
# Initialize UI update variables
|
597 |
cleared_input_text = "" # To clear the user input box
|
598 |
updated_gradio_history = list(gradio_chat_history_list) # Copy current display history
|
599 |
current_status_text = "Initializing..."
|
600 |
# Default values for output components to yield immediately
|
|
|
|
|
601 |
default_detected_outputs_md = gr.Markdown(value="*Processing...*")
|
602 |
default_formatted_output_text = gr.Textbox(value="*Waiting for AI response...*")
|
603 |
default_download_button = gr.DownloadButton(interactive=False, value=None, visible=False)
|
|
|
624 |
if len(internal_processing_history) > (MAX_HISTORY_TURNS * 2 + 1): # +1 for potential system prompt
|
625 |
# Simple truncation from the beginning, preserving last N turns
|
626 |
# More sophisticated: keep system prompt if present, then truncate older user/assistant pairs
|
627 |
+
# This simple truncation might lose a system prompt if it was the very first item
|
628 |
+
# A better approach if system prompts are always first:
|
629 |
+
if internal_processing_history[0]["role"] == "system" and len(internal_processing_history) > (MAX_HISTORY_TURNS * 2 +1) :
|
630 |
+
internal_processing_history = [internal_processing_history[0]] + internal_processing_history[-(MAX_HISTORY_TURNS * 2):]
|
631 |
+
else:
|
632 |
+
internal_processing_history = internal_processing_history[-(MAX_HISTORY_TURNS * 2):]
|
633 |
|
634 |
|
635 |
final_bot_response_text_accumulated = ""
|
|
|
714 |
current_chat_session_history.append({"role": "assistant", "content": final_bot_response_text_accumulated})
|
715 |
|
716 |
# Trim global history if it exceeds max turns
|
717 |
+
if len(current_chat_session_history) > (MAX_HISTORY_TURNS * 2): # Check total length
|
718 |
+
# If a system prompt is the first message, preserve it during trimming
|
719 |
+
if current_chat_session_history[0]["role"] == "system" and len(current_chat_session_history) > (MAX_HISTORY_TURNS * 2 + 1):
|
720 |
+
current_chat_session_history = [current_chat_session_history[0]] + current_chat_session_history[-(MAX_HISTORY_TURNS * 2):]
|
721 |
+
else: # No system prompt or length is fine with system prompt
|
722 |
+
current_chat_session_history = current_chat_session_history[-(MAX_HISTORY_TURNS * 2):]
|
723 |
+
|
724 |
|
725 |
# Start deferred learning task in a background thread
|
726 |
logger.info(f"Starting deferred learning task for user: '{user_message_text[:40]}...'")
|
|
|
753 |
if not uploaded_file_obj: return "No file provided for rules upload."
|
754 |
|
755 |
try:
|
756 |
+
# Gradio's File component gives a temp file object, access its name
|
757 |
+
with open(uploaded_file_obj.name, 'r', encoding='utf-8') as f:
|
758 |
+
content = f.read()
|
759 |
+
except Exception as e_read:
|
760 |
+
logger.error(f"Error reading uploaded rules file: {e_read}")
|
761 |
+
return f"Error reading file: {e_read}"
|
|
|
|
|
|
|
|
|
|
|
762 |
|
763 |
if not content.strip(): return "Uploaded rules file is empty."
|
764 |
|
|
|
769 |
potential_rules = [r.strip() for r in content.splitlines() if r.strip()]
|
770 |
|
771 |
total_to_process = len(potential_rules)
|
772 |
+
if total_to_process == 0: return "No rules found in file to process."
|
773 |
progress(0, desc="Starting rules upload...")
|
774 |
|
775 |
for idx, rule_text in enumerate(potential_rules):
|
|
|
782 |
# A better save_rule_to_file could return: "added", "duplicate", "invalid_format", "error"
|
783 |
|
784 |
# Re-check for existing before trying to save for more accurate "skipped_count"
|
785 |
+
# This is inefficient for very large rule sets, but ok for moderate numbers.
|
786 |
+
# Consider optimizing if this becomes a bottleneck.
|
787 |
+
# existing_rules_snapshot = load_rules_from_file()
|
788 |
+
# if rule_text in existing_rules_snapshot:
|
789 |
+
# skipped_count +=1
|
790 |
+
# el
|
791 |
+
if save_rule_to_file(rule_text): # save_rule_to_file logs its own errors/skips for duplicates/format
|
792 |
+
# save_rule_to_file returns True if saved, False if duplicate, invalid, or error
|
793 |
+
# This logic needs refinement if save_rule_to_file doesn't distinguish duplicate vs error
|
794 |
+
# Assuming save_rule_to_file now only returns True on actual addition.
|
795 |
+
# The memory_logic.py version of save_rule_to_file returns False for duplicates already.
|
796 |
+
added_count +=1
|
797 |
+
else:
|
798 |
+
# Check if it was a duplicate to categorize skipped_count, otherwise it's an error_count
|
799 |
+
# This requires knowing if the rule format was valid first. save_rule_to_file does this.
|
800 |
+
# If save_rule_to_file returned False, it means it was either a duplicate, invalid format, or save error.
|
801 |
+
# To simplify UI feedback, we'll just count as 'not added'.
|
802 |
+
# More detailed feedback would require save_rule_to_file to return distinct status codes.
|
803 |
+
# For now, if it wasn't added, and we can't easily tell if it was a duplicate vs format error here,
|
804 |
+
# let's assume it's an error for this counter, as save_rule_to_file already logs duplicates.
|
805 |
+
is_valid_format = bool(re.match(r"\[(CORE_RULE|RESPONSE_PRINCIPLE|BEHAVIORAL_ADJUSTMENT|GENERAL_LEARNING)\|([\d\.]+?)\](.*)", rule_text, re.I|re.DOTALL))
|
806 |
+
if not is_valid_format: # It was a format error
|
807 |
+
error_count += 1
|
808 |
+
else: # It was a duplicate (already logged by save_rule_to_file) or a file write error
|
809 |
+
# if rule_text in load_rules_from_file(): # this check is redundant if save_rule_to_file does it
|
810 |
+
# skipped_count +=1
|
811 |
+
# else: # True error
|
812 |
+
# error_count += 1
|
813 |
+
# To avoid re-loading rules file constantly, rely on save_rule_to_file's logging.
|
814 |
+
# If it returns False and it wasn't added, it's complex to distinguish here without more info.
|
815 |
+
# Let's simplify: if not added, increment a general "not_added_or_error" count.
|
816 |
+
# The current save_rule_to_file will log "already exists". We need to avoid double counting.
|
817 |
+
# Solution: save_rule_to_file should be the sole decider.
|
818 |
+
# Let's adjust how we count based on a hypothetical enhanced save_rule_to_file:
|
819 |
+
# result = save_rule_to_file_detailed(rule_text)
|
820 |
+
# if result == "added": added_count += 1
|
821 |
+
# elif result == "duplicate": skipped_count += 1
|
822 |
+
# elif result == "invalid_format": error_count += 1
|
823 |
+
# else: error_count +=1 # file write error
|
824 |
+
# For now, with current save_rule_to_file:
|
825 |
+
# The logic in save_rule_to_file already prevents adding duplicates and invalid formats.
|
826 |
+
# So, if save_rule_to_file returns False, it means it was a duplicate, invalid, or actual save error.
|
827 |
+
# We need better differentiation for accurate counts.
|
828 |
+
# This simple count will be approximate for "skipped" vs "error".
|
829 |
+
# The provided save_rule_to_file handles duplicates and format errors internally and returns False.
|
830 |
+
# We can't easily tell which from just "False" here.
|
831 |
+
# So we'll assume False means it wasn't added for *some* reason (duplicate, format, error).
|
832 |
+
pass # The `save_rule_to_file` will log the reason if it's a duplicate or format error.
|
833 |
+
# If it's a true file write error, that's also logged.
|
834 |
+
# For UI, we can only show how many were *successfully* added.
|
835 |
progress((idx + 1) / total_to_process, desc=f"Processed {idx+1}/{total_to_process} rules...")
|
836 |
|
837 |
+
# After loop, get final counts for summary.
|
838 |
+
# This means we must load rules again to find out how many are new vs existing for skipped_count.
|
839 |
+
# This is inefficient. A better `save_rule_to_file` that returns status is needed for good UI feedback.
|
840 |
+
# For now, the UI feedback will be simpler:
|
841 |
+
final_rules_list = load_rules_from_file()
|
842 |
+
actual_added_this_run = added_count # This count is based on `save_rule_to_file` returning True only for new, valid rules.
|
843 |
+
|
844 |
+
msg = f"Rules Upload: Attempted {total_to_process}. Successfully added: {actual_added_this_run}. Others skipped (duplicates/invalid format/errors - check logs)."
|
845 |
logger.info(msg)
|
846 |
return msg
|
847 |
|
|
|
855 |
if not uploaded_file_obj: return "No file provided for memories upload."
|
856 |
|
857 |
try:
|
858 |
+
with open(uploaded_file_obj.name, 'r', encoding='utf-8') as f:
|
859 |
+
content = f.read()
|
860 |
+
except Exception as e_read:
|
861 |
+
logger.error(f"Error reading uploaded memories file: {e_read}")
|
862 |
+
return f"Error reading file: {e_read}"
|
|
|
|
|
|
|
|
|
|
|
|
|
863 |
|
864 |
if not content.strip(): return "Uploaded memories file is empty."
|
865 |
|
|
|
888 |
return "No valid memory objects found in the uploaded file."
|
889 |
|
890 |
total_to_process = len(memory_objects_to_process)
|
891 |
+
if total_to_process == 0: return "No memory objects to process after parsing."
|
892 |
progress(0, desc="Starting memories upload...")
|
893 |
|
894 |
+
# Duplicate check for memories is harder without unique IDs.
|
895 |
+
# `save_memory_to_file` currently just appends.
|
896 |
+
# For simplicity, this upload will append all validly formatted memories.
|
897 |
+
# A more robust system would check for duplicates based on content signature or timestamp.
|
898 |
+
existing_memories_timestamps_content = set() # For basic duplicate check in this upload session
|
899 |
+
# Pre-load existing to avoid adding true duplicates if possible
|
900 |
+
# for mem in load_memories_from_file():
|
901 |
+
# existing_memories_timestamps_content.add((mem.get("timestamp"), mem.get("user_input"), mem.get("bot_response")))
|
902 |
+
|
903 |
+
|
904 |
for idx, mem_data in enumerate(memory_objects_to_process):
|
905 |
+
if not isinstance(mem_data, dict) or not all(k in mem_data for k in ["user_input", "bot_response", "metrics"]): # Timestamp is good to have but save_memory_to_file adds it if missing
|
906 |
format_error_count += 1
|
907 |
continue
|
908 |
|
909 |
+
# Simple duplicate check for this session (not against all stored if large)
|
910 |
+
# current_sig = (mem_data.get("timestamp"), mem_data.get("user_input"), mem_data.get("bot_response"))
|
911 |
+
# if current_sig in existing_memories_timestamps_content:
|
912 |
+
# # This is a duplicate from the file itself or already in store
|
913 |
+
# continue # Skip adding
|
914 |
+
|
915 |
if save_memory_to_file(mem_data["user_input"], mem_data["bot_response"], mem_data["metrics"]):
|
916 |
added_count += 1
|
917 |
+
# existing_memories_timestamps_content.add(current_sig) # Add to session set
|
918 |
else:
|
919 |
save_error_count += 1 # Error during file write
|
920 |
progress((idx + 1) / total_to_process, desc=f"Processed {idx+1}/{total_to_process} memories...")
|
|
|
1078 |
# No explicit data loading into globals needed here if handlers load on demand.
|
1079 |
|
1080 |
app_port = int(os.getenv("GRADIO_PORT", 7860))
|
1081 |
+
# Default to 127.0.0.1 for local access unless 0.0.0.0 is explicitly set for broader network access
|
1082 |
+
app_server_name = os.getenv("GRADIO_SERVER_NAME", "127.0.0.1")
|
1083 |
app_debug_mode = os.getenv("GRADIO_DEBUG", "False").lower() == "true"
|
1084 |
app_share_mode = os.getenv("GRADIO_SHARE", "False").lower() == "true"
|
1085 |
|