fixed all bugs
Browse files- app.py +351 -340
- services/elevenlabs_service.py +13 -10
- services/llamaindex_service.py +36 -32
app.py
CHANGED
|
@@ -271,6 +271,14 @@ class ContentOrganizerMCPServer:
|
|
| 271 |
return {"success": False, "error": str(e)}
|
| 272 |
|
| 273 |
mcp_server = ContentOrganizerMCPServer()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
|
| 275 |
def get_document_list():
|
| 276 |
try:
|
|
@@ -345,6 +353,9 @@ def upload_and_process_file(file):
|
|
| 345 |
file_type = Path(file_path).suffix.lower().strip('.')
|
| 346 |
logger.info(f"Processing file: {file_path}, type: {file_type}")
|
| 347 |
result = mcp_server.run_async(mcp_server.ingest_document_async(file_path, file_type))
|
|
|
|
|
|
|
|
|
|
| 348 |
|
| 349 |
doc_list_updated = get_document_list()
|
| 350 |
doc_choices_updated = get_document_choices()
|
|
@@ -634,278 +645,278 @@ voice_conversation_state = {
|
|
| 634 |
"transcript": []
|
| 635 |
}
|
| 636 |
|
| 637 |
-
voice_conversation_state = {
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
}
|
| 642 |
|
| 643 |
-
def start_voice_conversation():
|
| 644 |
-
|
| 645 |
-
|
| 646 |
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
|
| 650 |
-
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
| 672 |
|
| 673 |
-
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
|
| 677 |
|
| 678 |
-
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
| 703 |
-
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
def stop_voice_conversation():
|
| 713 |
-
|
| 714 |
-
|
| 715 |
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
|
| 734 |
-
|
| 735 |
-
|
| 736 |
|
| 737 |
-
|
| 738 |
-
|
| 739 |
|
| 740 |
-
|
| 741 |
-
|
| 742 |
-
|
| 743 |
-
|
| 744 |
-
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
def send_voice_message_v6(message, chat_history):
|
| 757 |
-
|
| 758 |
-
|
| 759 |
|
| 760 |
-
|
| 761 |
-
|
| 762 |
-
|
| 763 |
|
| 764 |
-
|
| 765 |
-
|
| 766 |
-
|
| 767 |
-
|
| 768 |
-
|
| 769 |
-
|
| 770 |
-
|
| 771 |
-
|
| 772 |
-
|
| 773 |
-
|
| 774 |
-
|
| 775 |
|
| 776 |
-
|
| 777 |
-
|
| 778 |
-
|
| 779 |
|
| 780 |
-
|
| 781 |
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
|
| 786 |
-
|
| 787 |
|
| 788 |
-
|
| 789 |
-
|
| 790 |
-
|
| 791 |
-
|
| 792 |
-
|
| 793 |
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
|
| 799 |
-
|
| 800 |
-
|
| 801 |
|
| 802 |
-
|
| 803 |
-
|
| 804 |
-
|
| 805 |
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
|
| 812 |
-
|
| 813 |
-
|
| 814 |
-
|
| 815 |
-
|
| 816 |
-
|
| 817 |
-
|
| 818 |
-
|
| 819 |
-
|
| 820 |
-
|
| 821 |
-
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
|
| 827 |
-
|
| 828 |
-
|
| 829 |
|
| 830 |
-
|
| 831 |
|
| 832 |
-
|
| 833 |
-
|
| 834 |
|
| 835 |
-
|
| 836 |
-
|
| 837 |
-
|
| 838 |
|
| 839 |
-
|
| 840 |
-
|
| 841 |
-
|
| 842 |
-
|
| 843 |
-
|
| 844 |
-
|
| 845 |
-
def test_voice_connection():
|
| 846 |
-
|
| 847 |
-
|
| 848 |
|
| 849 |
-
|
| 850 |
-
|
| 851 |
-
|
| 852 |
-
|
| 853 |
-
|
| 854 |
-
|
| 855 |
-
|
| 856 |
|
| 857 |
-
|
| 858 |
-
|
| 859 |
-
|
| 860 |
-
|
| 861 |
-
|
| 862 |
-
|
| 863 |
-
|
| 864 |
-
|
| 865 |
-
|
| 866 |
-
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
|
| 870 |
-
|
| 871 |
-
|
| 872 |
-
|
| 873 |
-
|
| 874 |
-
|
| 875 |
-
|
| 876 |
-
|
| 877 |
-
|
| 878 |
-
|
| 879 |
-
|
| 880 |
-
|
| 881 |
-
|
| 882 |
-
|
| 883 |
-
def get_conversation_stats():
|
| 884 |
-
|
| 885 |
-
|
| 886 |
|
| 887 |
-
|
| 888 |
-
|
| 889 |
-
|
| 890 |
-
|
| 891 |
-
|
| 892 |
-
|
| 893 |
|
| 894 |
-
|
| 895 |
-
|
| 896 |
-
|
| 897 |
|
| 898 |
-
|
| 899 |
-
|
| 900 |
-
|
| 901 |
-
|
| 902 |
-
|
| 903 |
-
|
| 904 |
-
|
| 905 |
-
|
| 906 |
-
|
| 907 |
-
|
| 908 |
-
|
| 909 |
|
| 910 |
def generate_podcast_ui(doc_ids, style, duration, voice1, voice2):
|
| 911 |
"""UI wrapper for podcast generation"""
|
|
@@ -1340,120 +1351,120 @@ def create_gradio_interface():
|
|
| 1340 |
outputs=[content_output_display]
|
| 1341 |
)
|
| 1342 |
|
| 1343 |
-
with gr.Tab("🎙️ Voice Assistant"):
|
| 1344 |
-
|
| 1345 |
-
|
| 1346 |
|
| 1347 |
-
|
| 1348 |
-
|
| 1349 |
-
|
| 1350 |
-
|
| 1351 |
-
|
| 1352 |
-
|
| 1353 |
-
|
| 1354 |
-
|
| 1355 |
-
|
| 1356 |
-
|
| 1357 |
-
|
| 1358 |
|
| 1359 |
-
|
| 1360 |
-
|
| 1361 |
-
|
| 1362 |
-
|
| 1363 |
-
|
| 1364 |
-
|
| 1365 |
|
| 1366 |
-
|
| 1367 |
-
|
| 1368 |
-
|
| 1369 |
-
|
| 1370 |
-
|
| 1371 |
-
|
| 1372 |
|
| 1373 |
-
|
| 1374 |
-
|
| 1375 |
-
|
| 1376 |
-
|
| 1377 |
-
|
| 1378 |
|
| 1379 |
-
|
| 1380 |
|
| 1381 |
-
|
| 1382 |
-
|
| 1383 |
-
|
| 1384 |
-
|
| 1385 |
-
|
| 1386 |
-
|
| 1387 |
-
|
| 1388 |
|
| 1389 |
-
|
| 1390 |
-
|
| 1391 |
-
|
| 1392 |
-
|
| 1393 |
-
|
| 1394 |
-
|
| 1395 |
-
|
| 1396 |
-
|
| 1397 |
-
|
| 1398 |
-
|
| 1399 |
-
|
| 1400 |
-
|
| 1401 |
|
| 1402 |
-
|
| 1403 |
-
|
| 1404 |
-
|
| 1405 |
-
|
| 1406 |
-
|
| 1407 |
-
|
| 1408 |
-
|
| 1409 |
-
|
| 1410 |
-
|
| 1411 |
-
|
| 1412 |
-
|
| 1413 |
-
|
| 1414 |
-
|
| 1415 |
-
|
| 1416 |
-
|
| 1417 |
-
|
| 1418 |
|
| 1419 |
-
|
| 1420 |
-
|
| 1421 |
-
|
| 1422 |
-
|
| 1423 |
-
|
| 1424 |
|
| 1425 |
-
|
| 1426 |
-
|
| 1427 |
-
|
| 1428 |
-
|
| 1429 |
-
|
| 1430 |
|
| 1431 |
-
|
| 1432 |
-
|
| 1433 |
-
|
| 1434 |
-
|
| 1435 |
|
| 1436 |
-
|
| 1437 |
-
|
| 1438 |
-
|
| 1439 |
-
|
| 1440 |
-
|
| 1441 |
|
| 1442 |
-
|
| 1443 |
-
|
| 1444 |
-
|
| 1445 |
-
|
| 1446 |
-
|
| 1447 |
|
| 1448 |
-
|
| 1449 |
-
|
| 1450 |
-
|
| 1451 |
-
|
| 1452 |
|
| 1453 |
-
|
| 1454 |
-
|
| 1455 |
-
|
| 1456 |
-
|
| 1457 |
|
| 1458 |
with gr.Tab("🎧 Podcast Studio"):
|
| 1459 |
gr.Markdown("""
|
|
@@ -1636,7 +1647,7 @@ def create_gradio_interface():
|
|
| 1636 |
show_copy_button=True
|
| 1637 |
)
|
| 1638 |
|
| 1639 |
-
all_dropdowns_to_update = [delete_doc_dropdown_visible, doc_dropdown_content,
|
| 1640 |
|
| 1641 |
refresh_outputs = [document_list_display] + [dd for dd in all_dropdowns_to_update]
|
| 1642 |
refresh_btn_library.click(fn=refresh_library, outputs=refresh_outputs)
|
|
|
|
| 271 |
return {"success": False, "error": str(e)}
|
| 272 |
|
| 273 |
mcp_server = ContentOrganizerMCPServer()
|
| 274 |
+
try:
|
| 275 |
+
print("⏳ Initializing LlamaIndex Service...")
|
| 276 |
+
# Use the fixed run_async method to safely initialize
|
| 277 |
+
mcp_server.run_async(mcp_server.llamaindex_service.initialize())
|
| 278 |
+
print("✅ LlamaIndex Initialized Successfully!")
|
| 279 |
+
except Exception as e:
|
| 280 |
+
print(f"⚠️ Warning during LlamaIndex init: {e}")
|
| 281 |
+
|
| 282 |
|
| 283 |
def get_document_list():
|
| 284 |
try:
|
|
|
|
| 353 |
file_type = Path(file_path).suffix.lower().strip('.')
|
| 354 |
logger.info(f"Processing file: {file_path}, type: {file_type}")
|
| 355 |
result = mcp_server.run_async(mcp_server.ingest_document_async(file_path, file_type))
|
| 356 |
+
if result["success"]:
|
| 357 |
+
logger.info("Syncing LlamaIndex with new document...")
|
| 358 |
+
mcp_server.run_async(mcp_server.llamaindex_service.sync_on_demand())
|
| 359 |
|
| 360 |
doc_list_updated = get_document_list()
|
| 361 |
doc_choices_updated = get_document_choices()
|
|
|
|
| 645 |
"transcript": []
|
| 646 |
}
|
| 647 |
|
| 648 |
+
# voice_conversation_state = {
|
| 649 |
+
# "session_id": None,
|
| 650 |
+
# "active": False,
|
| 651 |
+
# "transcript": []
|
| 652 |
+
# }
|
| 653 |
|
| 654 |
+
# def start_voice_conversation():
|
| 655 |
+
# """
|
| 656 |
+
# Start a new voice conversation session
|
| 657 |
|
| 658 |
+
# Returns:
|
| 659 |
+
# Tuple of (status_message, start_button_state, stop_button_state, chatbot_history)
|
| 660 |
+
# """
|
| 661 |
+
# try:
|
| 662 |
+
# # Check if service is available
|
| 663 |
+
# if not mcp_server.elevenlabs_service.is_available():
|
| 664 |
+
# return (
|
| 665 |
+
# "⚠️ Voice assistant not configured.\n\n"
|
| 666 |
+
# "**Setup Instructions:**\n"
|
| 667 |
+
# "1. Get API key from: https://elevenlabs.io/app/settings/api-keys\n"
|
| 668 |
+
# "2. Create an agent at: https://elevenlabs.io/app/conversational-ai\n"
|
| 669 |
+
# "3. Add to .env file:\n"
|
| 670 |
+
# " - ELEVENLABS_API_KEY=your_api_key\n"
|
| 671 |
+
# " - ELEVENLABS_AGENT_ID=your_agent_id\n"
|
| 672 |
+
# "4. Restart the application",
|
| 673 |
+
# gr.update(interactive=True), # start button enabled
|
| 674 |
+
# gr.update(interactive=False), # stop button disabled
|
| 675 |
+
# []
|
| 676 |
+
# )
|
| 677 |
|
| 678 |
+
# # Create new session
|
| 679 |
+
# session_id = str(uuid.uuid4())
|
| 680 |
+
# result = mcp_server.run_async(
|
| 681 |
+
# mcp_server.elevenlabs_service.start_conversation(session_id)
|
| 682 |
+
# )
|
| 683 |
|
| 684 |
+
# if result.get("success"):
|
| 685 |
+
# voice_conversation_state["session_id"] = session_id
|
| 686 |
+
# voice_conversation_state["active"] = True
|
| 687 |
+
# voice_conversation_state["transcript"] = []
|
| 688 |
|
| 689 |
+
# # Initialize chatbot with welcome message
|
| 690 |
+
# initial_message = {
|
| 691 |
+
# "role": "assistant",
|
| 692 |
+
# "content": "👋 Hello! I'm your AI librarian. Ask me anything about your documents!"
|
| 693 |
+
# }
|
| 694 |
|
| 695 |
+
# return (
|
| 696 |
+
# "✅ Voice assistant is ready!\n\n"
|
| 697 |
+
# "You can now ask questions about your uploaded documents.",
|
| 698 |
+
# gr.update(interactive=False), # start button disabled
|
| 699 |
+
# gr.update(interactive=True), # stop button enabled
|
| 700 |
+
# [initial_message]
|
| 701 |
+
# )
|
| 702 |
+
# else:
|
| 703 |
+
# error_msg = result.get("error", "Unknown error")
|
| 704 |
+
# return (
|
| 705 |
+
# f"❌ Failed to start: {error_msg}\n\n"
|
| 706 |
+
# "**Troubleshooting:**\n"
|
| 707 |
+
# "• Check your API key is valid\n"
|
| 708 |
+
# "• Verify agent ID is correct\n"
|
| 709 |
+
# "• Check internet connection",
|
| 710 |
+
# gr.update(interactive=True),
|
| 711 |
+
# gr.update(interactive=False),
|
| 712 |
+
# []
|
| 713 |
+
# )
|
| 714 |
+
# except Exception as e:
|
| 715 |
+
# logger.error(f"Error starting voice conversation: {str(e)}", exc_info=True)
|
| 716 |
+
# return (
|
| 717 |
+
# f"❌ Error: {str(e)}",
|
| 718 |
+
# gr.update(interactive=True),
|
| 719 |
+
# gr.update(interactive=False),
|
| 720 |
+
# []
|
| 721 |
+
# )
|
| 722 |
+
|
| 723 |
+
# def stop_voice_conversation():
|
| 724 |
+
# """
|
| 725 |
+
# Stop active voice conversation
|
| 726 |
|
| 727 |
+
# Returns:
|
| 728 |
+
# Tuple of (status_message, start_button_state, stop_button_state, chatbot_history)
|
| 729 |
+
# """
|
| 730 |
+
# try:
|
| 731 |
+
# if not voice_conversation_state["active"]:
|
| 732 |
+
# return (
|
| 733 |
+
# "ℹ️ No active conversation",
|
| 734 |
+
# gr.update(interactive=True),
|
| 735 |
+
# gr.update(interactive=False),
|
| 736 |
+
# voice_conversation_state["transcript"]
|
| 737 |
+
# )
|
| 738 |
|
| 739 |
+
# session_id = voice_conversation_state["session_id"]
|
| 740 |
+
# if session_id:
|
| 741 |
+
# mcp_server.run_async(
|
| 742 |
+
# mcp_server.elevenlabs_service.end_conversation(session_id)
|
| 743 |
+
# )
|
| 744 |
|
| 745 |
+
# # Get conversation stats
|
| 746 |
+
# message_count = len(voice_conversation_state["transcript"])
|
| 747 |
|
| 748 |
+
# voice_conversation_state["active"] = False
|
| 749 |
+
# voice_conversation_state["session_id"] = None
|
| 750 |
|
| 751 |
+
# return (
|
| 752 |
+
# f"✅ Conversation ended\n\n"
|
| 753 |
+
# f"📊 Stats: {message_count} messages exchanged",
|
| 754 |
+
# gr.update(interactive=True),
|
| 755 |
+
# gr.update(interactive=False),
|
| 756 |
+
# voice_conversation_state["transcript"]
|
| 757 |
+
# )
|
| 758 |
+
# except Exception as e:
|
| 759 |
+
# logger.error(f"Error stopping conversation: {str(e)}")
|
| 760 |
+
# return (
|
| 761 |
+
# f"❌ Error: {str(e)}",
|
| 762 |
+
# gr.update(interactive=True),
|
| 763 |
+
# gr.update(interactive=False),
|
| 764 |
+
# voice_conversation_state["transcript"]
|
| 765 |
+
# )
|
| 766 |
+
|
| 767 |
+
# def send_voice_message_v6(message, chat_history):
|
| 768 |
+
# """
|
| 769 |
+
# Send message in voice conversation - Gradio 6+ format
|
| 770 |
|
| 771 |
+
# Args:
|
| 772 |
+
# message: User's text message
|
| 773 |
+
# chat_history: Current chat history (list of message dicts)
|
| 774 |
|
| 775 |
+
# Returns:
|
| 776 |
+
# Tuple of (updated_chat_history, cleared_input_box)
|
| 777 |
+
# """
|
| 778 |
+
# try:
|
| 779 |
+
# # Validate state
|
| 780 |
+
# if not voice_conversation_state["active"]:
|
| 781 |
+
# chat_history.append({
|
| 782 |
+
# "role": "assistant",
|
| 783 |
+
# "content": "⚠️ Please start a conversation first by clicking 'Start Conversation'"
|
| 784 |
+
# })
|
| 785 |
+
# return chat_history, ""
|
| 786 |
|
| 787 |
+
# # Validate input
|
| 788 |
+
# if not message or not message.strip():
|
| 789 |
+
# return chat_history, message
|
| 790 |
|
| 791 |
+
# session_id = voice_conversation_state["session_id"]
|
| 792 |
|
| 793 |
+
# # Add user message to display
|
| 794 |
+
# chat_history.append({
|
| 795 |
+
# "role": "user",
|
| 796 |
+
# "content": message
|
| 797 |
+
# })
|
| 798 |
|
| 799 |
+
# # Show typing indicator
|
| 800 |
+
# chat_history.append({
|
| 801 |
+
# "role": "assistant",
|
| 802 |
+
# "content": "🤔 Thinking..."
|
| 803 |
+
# })
|
| 804 |
|
| 805 |
+
# # Get AI response
|
| 806 |
+
# result = mcp_server.run_async(
|
| 807 |
+
# mcp_server.voice_tool.voice_qa(message, session_id)
|
| 808 |
+
# )
|
| 809 |
|
| 810 |
+
# # Remove typing indicator
|
| 811 |
+
# chat_history = chat_history[:-1]
|
| 812 |
|
| 813 |
+
# # Add response
|
| 814 |
+
# if result.get("success"):
|
| 815 |
+
# answer = result.get("answer", "No response")
|
| 816 |
|
| 817 |
+
# # Add helpful context if RAG was used
|
| 818 |
+
# if "document" in answer.lower() or "file" in answer.lower():
|
| 819 |
+
# footer = "\n\n💡 *Answer based on your documents*"
|
| 820 |
+
# else:
|
| 821 |
+
# footer = ""
|
| 822 |
|
| 823 |
+
# chat_history.append({
|
| 824 |
+
# "role": "assistant",
|
| 825 |
+
# "content": answer + footer
|
| 826 |
+
# })
|
| 827 |
+
# else:
|
| 828 |
+
# error_msg = result.get("error", "Unknown error")
|
| 829 |
+
# chat_history.append({
|
| 830 |
+
# "role": "assistant",
|
| 831 |
+
# "content": f"❌ Error: {error_msg}\n\n"
|
| 832 |
+
# "**Suggestions:**\n"
|
| 833 |
+
# "• Try rephrasing your question\n"
|
| 834 |
+
# "• Make sure you have uploaded relevant documents\n"
|
| 835 |
+
# "• Check if the question is about your document library"
|
| 836 |
+
# })
|
| 837 |
|
| 838 |
+
# # Update conversation state
|
| 839 |
+
# voice_conversation_state["transcript"] = chat_history
|
| 840 |
|
| 841 |
+
# return chat_history, ""
|
| 842 |
|
| 843 |
+
# except Exception as e:
|
| 844 |
+
# logger.error(f"Error in voice message: {str(e)}", exc_info=True)
|
| 845 |
|
| 846 |
+
# # Remove typing indicator if present
|
| 847 |
+
# if chat_history and chat_history[-1]["role"] == "assistant" and "Thinking" in chat_history[-1]["content"]:
|
| 848 |
+
# chat_history = chat_history[:-1]
|
| 849 |
|
| 850 |
+
# chat_history.append({
|
| 851 |
+
# "role": "assistant",
|
| 852 |
+
# "content": f"❌ An error occurred: {str(e)}\n\nPlease try again."
|
| 853 |
+
# })
|
| 854 |
+
# return chat_history, ""
|
| 855 |
+
|
| 856 |
+
# def test_voice_connection():
|
| 857 |
+
# """
|
| 858 |
+
# Test voice assistant connection
|
| 859 |
|
| 860 |
+
# Returns:
|
| 861 |
+
# Status message with test results
|
| 862 |
+
# """
|
| 863 |
+
# try:
|
| 864 |
+
# result = mcp_server.run_async(
|
| 865 |
+
# mcp_server.voice_tool.test_connection()
|
| 866 |
+
# )
|
| 867 |
|
| 868 |
+
# if result.get("success"):
|
| 869 |
+
# return (
|
| 870 |
+
# "✅ **Connection Test Passed**\n\n"
|
| 871 |
+
# f"• API Status: Connected\n"
|
| 872 |
+
# f"• Voices Available: {result.get('voices_available', 0)}\n"
|
| 873 |
+
# f"• RAG Tool: {'✓ Working' if result.get('rag_tool_working') else '✗ Failed'}\n"
|
| 874 |
+
# f"• Client Tools: {'✓ Registered' if result.get('client_tools_registered') else '✗ Not Registered'}\n\n"
|
| 875 |
+
# "🎉 Voice assistant is ready to use!"
|
| 876 |
+
# )
|
| 877 |
+
# else:
|
| 878 |
+
# return (
|
| 879 |
+
# "❌ **Connection Test Failed**\n\n"
|
| 880 |
+
# f"Error: {result.get('message', 'Unknown error')}\n\n"
|
| 881 |
+
# "**Troubleshooting:**\n"
|
| 882 |
+
# "1. Verify ELEVENLABS_API_KEY in .env\n"
|
| 883 |
+
# "2. Check ELEVENLABS_AGENT_ID is set\n"
|
| 884 |
+
# "3. Ensure API key is valid\n"
|
| 885 |
+
# "4. Check internet connection"
|
| 886 |
+
# )
|
| 887 |
+
# except Exception as e:
|
| 888 |
+
# logger.error(f"Connection test error: {str(e)}")
|
| 889 |
+
# return (
|
| 890 |
+
# f"❌ **Test Error**\n\n{str(e)}\n\n"
|
| 891 |
+
# "Please check your configuration and try again."
|
| 892 |
+
# )
|
| 893 |
+
|
| 894 |
+
# def get_conversation_stats():
|
| 895 |
+
# """
|
| 896 |
+
# Get statistics about current conversation
|
| 897 |
|
| 898 |
+
# Returns:
|
| 899 |
+
# Formatted stats string
|
| 900 |
+
# """
|
| 901 |
+
# try:
|
| 902 |
+
# if not voice_conversation_state["active"]:
|
| 903 |
+
# return "ℹ️ No active conversation"
|
| 904 |
|
| 905 |
+
# transcript = voice_conversation_state["transcript"]
|
| 906 |
+
# user_msgs = sum(1 for msg in transcript if msg["role"] == "user")
|
| 907 |
+
# ai_msgs = sum(1 for msg in transcript if msg["role"] == "assistant")
|
| 908 |
|
| 909 |
+
# return (
|
| 910 |
+
# "📊 **Conversation Statistics**\n\n"
|
| 911 |
+
# f"• Session ID: {voice_conversation_state['session_id'][:8]}...\n"
|
| 912 |
+
# f"• Your messages: {user_msgs}\n"
|
| 913 |
+
# f"• AI responses: {ai_msgs}\n"
|
| 914 |
+
# f"• Total exchanges: {user_msgs}\n"
|
| 915 |
+
# f"• Status: {'🟢 Active' if voice_conversation_state['active'] else '🔴 Inactive'}"
|
| 916 |
+
# )
|
| 917 |
+
# except Exception as e:
|
| 918 |
+
# logger.error(f"Error getting stats: {str(e)}")
|
| 919 |
+
# return f"❌ Error: {str(e)}"
|
| 920 |
|
| 921 |
def generate_podcast_ui(doc_ids, style, duration, voice1, voice2):
|
| 922 |
"""UI wrapper for podcast generation"""
|
|
|
|
| 1351 |
outputs=[content_output_display]
|
| 1352 |
)
|
| 1353 |
|
| 1354 |
+
# with gr.Tab("🎙️ Voice Assistant"):
|
| 1355 |
+
# # Simple header
|
| 1356 |
+
# gr.Markdown("### Ask questions about your documents using AI")
|
| 1357 |
|
| 1358 |
+
# with gr.Row():
|
| 1359 |
+
# # Compact left sidebar (25% width)
|
| 1360 |
+
# with gr.Column(scale=1):
|
| 1361 |
+
# # Status box
|
| 1362 |
+
# voice_status_display = gr.Textbox(
|
| 1363 |
+
# label="Status",
|
| 1364 |
+
# value="Click 'Start' to begin",
|
| 1365 |
+
# interactive=False,
|
| 1366 |
+
# lines=3,
|
| 1367 |
+
# max_lines=3
|
| 1368 |
+
# )
|
| 1369 |
|
| 1370 |
+
# # Control buttons stacked vertically
|
| 1371 |
+
# start_voice_btn = gr.Button(
|
| 1372 |
+
# "🎤 Start",
|
| 1373 |
+
# variant="primary",
|
| 1374 |
+
# size="lg"
|
| 1375 |
+
# )
|
| 1376 |
|
| 1377 |
+
# stop_voice_btn = gr.Button(
|
| 1378 |
+
# "⏹️ Stop",
|
| 1379 |
+
# variant="stop",
|
| 1380 |
+
# size="lg",
|
| 1381 |
+
# interactive=False
|
| 1382 |
+
# )
|
| 1383 |
|
| 1384 |
+
# test_connection_btn = gr.Button(
|
| 1385 |
+
# "🔧 Test",
|
| 1386 |
+
# variant="secondary",
|
| 1387 |
+
# size="sm"
|
| 1388 |
+
# )
|
| 1389 |
|
| 1390 |
+
# gr.Markdown("---")
|
| 1391 |
|
| 1392 |
+
# # Quick tips
|
| 1393 |
+
# gr.Markdown("""
|
| 1394 |
+
# **Quick Tips:**
|
| 1395 |
+
# • Upload documents first
|
| 1396 |
+
# • Ask specific questions
|
| 1397 |
+
# • Press Enter to send
|
| 1398 |
+
# """, elem_classes=["small-text"])
|
| 1399 |
|
| 1400 |
+
# # Main chat area (75% width)
|
| 1401 |
+
# with gr.Column(scale=3):
|
| 1402 |
+
# # Large chat window
|
| 1403 |
+
# voice_chatbot = gr.Chatbot(
|
| 1404 |
+
# type="messages",
|
| 1405 |
+
# height=550,
|
| 1406 |
+
# show_copy_button=True,
|
| 1407 |
+
# avatar_images=(None, "🤖"),
|
| 1408 |
+
# show_label=False,
|
| 1409 |
+
# container=True,
|
| 1410 |
+
# bubble_full_width=False
|
| 1411 |
+
# )
|
| 1412 |
|
| 1413 |
+
# # Input row
|
| 1414 |
+
# with gr.Row():
|
| 1415 |
+
# voice_input_text = gr.Textbox(
|
| 1416 |
+
# placeholder="Ask me anything about your documents...",
|
| 1417 |
+
# lines=2,
|
| 1418 |
+
# max_lines=4,
|
| 1419 |
+
# scale=4,
|
| 1420 |
+
# show_label=False,
|
| 1421 |
+
# container=False,
|
| 1422 |
+
# autofocus=True
|
| 1423 |
+
# )
|
| 1424 |
+
# send_voice_btn = gr.Button(
|
| 1425 |
+
# "Send",
|
| 1426 |
+
# scale=1,
|
| 1427 |
+
# variant="primary"
|
| 1428 |
+
# )
|
| 1429 |
|
| 1430 |
+
# # Footer actions
|
| 1431 |
+
# with gr.Row():
|
| 1432 |
+
# clear_chat_btn = gr.Button("Clear", size="sm")
|
| 1433 |
+
# with gr.Column(scale=3):
|
| 1434 |
+
# gr.Markdown("*Tip: Type your question and press Enter*")
|
| 1435 |
|
| 1436 |
+
# # Event handlers
|
| 1437 |
+
# start_voice_btn.click(
|
| 1438 |
+
# fn=start_voice_conversation,
|
| 1439 |
+
# outputs=[voice_status_display, start_voice_btn, stop_voice_btn, voice_chatbot]
|
| 1440 |
+
# )
|
| 1441 |
|
| 1442 |
+
# stop_voice_btn.click(
|
| 1443 |
+
# fn=stop_voice_conversation,
|
| 1444 |
+
# outputs=[voice_status_display, start_voice_btn, stop_voice_btn, voice_chatbot]
|
| 1445 |
+
# )
|
| 1446 |
|
| 1447 |
+
# send_voice_btn.click(
|
| 1448 |
+
# fn=send_voice_message_v6,
|
| 1449 |
+
# inputs=[voice_input_text, voice_chatbot],
|
| 1450 |
+
# outputs=[voice_chatbot, voice_input_text]
|
| 1451 |
+
# )
|
| 1452 |
|
| 1453 |
+
# voice_input_text.submit(
|
| 1454 |
+
# fn=send_voice_message_v6,
|
| 1455 |
+
# inputs=[voice_input_text, voice_chatbot],
|
| 1456 |
+
# outputs=[voice_chatbot, voice_input_text]
|
| 1457 |
+
# )
|
| 1458 |
|
| 1459 |
+
# clear_chat_btn.click(
|
| 1460 |
+
# fn=lambda: [],
|
| 1461 |
+
# outputs=[voice_chatbot]
|
| 1462 |
+
# )
|
| 1463 |
|
| 1464 |
+
# test_connection_btn.click(
|
| 1465 |
+
# fn=test_voice_connection,
|
| 1466 |
+
# outputs=[voice_status_display]
|
| 1467 |
+
# )
|
| 1468 |
|
| 1469 |
with gr.Tab("🎧 Podcast Studio"):
|
| 1470 |
gr.Markdown("""
|
|
|
|
| 1647 |
show_copy_button=True
|
| 1648 |
)
|
| 1649 |
|
| 1650 |
+
all_dropdowns_to_update = [delete_doc_dropdown_visible, doc_dropdown_content,podcast_doc_selector]
|
| 1651 |
|
| 1652 |
refresh_outputs = [document_list_display] + [dd for dd in all_dropdowns_to_update]
|
| 1653 |
refresh_btn_library.click(fn=refresh_library, outputs=refresh_outputs)
|
services/elevenlabs_service.py
CHANGED
|
@@ -67,21 +67,23 @@ class ElevenLabsService:
|
|
| 67 |
def _init_client_tools(self):
|
| 68 |
"""Initialize client tools for RAG integration"""
|
| 69 |
try:
|
| 70 |
-
#
|
| 71 |
try:
|
| 72 |
-
|
| 73 |
-
except
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
|
|
|
|
|
|
| 80 |
# Register RAG query tool with proper metadata
|
| 81 |
self.client_tools.register(
|
| 82 |
"query_documents",
|
| 83 |
handler=self._rag_query_handler,
|
| 84 |
-
description="Search through the user's uploaded documents
|
| 85 |
parameters={
|
| 86 |
"query": {
|
| 87 |
"type": "string",
|
|
@@ -95,6 +97,7 @@ class ElevenLabsService:
|
|
| 95 |
|
| 96 |
except Exception as e:
|
| 97 |
logger.error(f"Error initializing client tools: {str(e)}")
|
|
|
|
| 98 |
self.client_tools = None
|
| 99 |
|
| 100 |
async def _rag_query_handler(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
|
| 67 |
def _init_client_tools(self):
|
| 68 |
"""Initialize client tools for RAG integration"""
|
| 69 |
try:
|
| 70 |
+
# FIX: Try initializing without arguments first (Newer SDKs)
|
| 71 |
try:
|
| 72 |
+
self.client_tools = ClientTools()
|
| 73 |
+
except TypeError:
|
| 74 |
+
# Fallback for older SDKs that might require a loop
|
| 75 |
+
try:
|
| 76 |
+
loop = asyncio.get_event_loop()
|
| 77 |
+
except RuntimeError:
|
| 78 |
+
loop = asyncio.new_event_loop()
|
| 79 |
+
asyncio.set_event_loop(loop)
|
| 80 |
+
self.client_tools = ClientTools(loop=loop)
|
| 81 |
+
|
| 82 |
# Register RAG query tool with proper metadata
|
| 83 |
self.client_tools.register(
|
| 84 |
"query_documents",
|
| 85 |
handler=self._rag_query_handler,
|
| 86 |
+
description="Search through the user's uploaded documents. Use this tool whenever the user asks questions about their documents, files, or content in their library.",
|
| 87 |
parameters={
|
| 88 |
"query": {
|
| 89 |
"type": "string",
|
|
|
|
| 97 |
|
| 98 |
except Exception as e:
|
| 99 |
logger.error(f"Error initializing client tools: {str(e)}")
|
| 100 |
+
# Keep client_tools as None so we know it failed
|
| 101 |
self.client_tools = None
|
| 102 |
|
| 103 |
async def _rag_query_handler(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
services/llamaindex_service.py
CHANGED
|
@@ -15,8 +15,6 @@ from llama_index.core import (
|
|
| 15 |
)
|
| 16 |
from llama_index.core.tools import QueryEngineTool, ToolMetadata
|
| 17 |
from llama_index.core.agent import ReActAgent
|
| 18 |
-
from llama_index.core.selectors import LLMSingleSelector
|
| 19 |
-
from llama_index.core.query_engine import RouterQueryEngine
|
| 20 |
from llama_index.llms.openai import OpenAI
|
| 21 |
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
|
| 22 |
from llama_index.embeddings.openai import OpenAIEmbedding
|
|
@@ -36,8 +34,7 @@ class LlamaIndexService:
|
|
| 36 |
self.is_initialized = False
|
| 37 |
|
| 38 |
self._initialize_settings()
|
| 39 |
-
#
|
| 40 |
-
# But we try to load existing storage if available
|
| 41 |
self._try_load_from_storage()
|
| 42 |
|
| 43 |
def _initialize_settings(self):
|
|
@@ -45,11 +42,9 @@ class LlamaIndexService:
|
|
| 45 |
try:
|
| 46 |
# LLM Setup
|
| 47 |
if self.config.OPENAI_API_KEY:
|
| 48 |
-
# Use configured OpenAI model (gpt-5.1-chat-latest or similar)
|
| 49 |
Settings.llm = OpenAI(model=self.config.OPENAI_MODEL, api_key=self.config.OPENAI_API_KEY)
|
| 50 |
logger.info(f"LlamaIndex using OpenAI model: {self.config.OPENAI_MODEL}")
|
| 51 |
elif self.config.NEBIUS_API_KEY:
|
| 52 |
-
# Use Nebius as OpenAI-compatible provider
|
| 53 |
Settings.llm = OpenAI(
|
| 54 |
model=self.config.NEBIUS_MODEL,
|
| 55 |
api_key=self.config.NEBIUS_API_KEY,
|
|
@@ -57,7 +52,7 @@ class LlamaIndexService:
|
|
| 57 |
)
|
| 58 |
logger.info(f"LlamaIndex using Nebius model: {self.config.NEBIUS_MODEL}")
|
| 59 |
else:
|
| 60 |
-
logger.warning("No API key found for LlamaIndex LLM
|
| 61 |
|
| 62 |
# Embedding Setup
|
| 63 |
if self.config.EMBEDDING_MODEL.startswith("text-embedding-"):
|
|
@@ -66,9 +61,7 @@ class LlamaIndexService:
|
|
| 66 |
model=self.config.EMBEDDING_MODEL,
|
| 67 |
api_key=self.config.OPENAI_API_KEY
|
| 68 |
)
|
| 69 |
-
logger.info(f"LlamaIndex using OpenAI embeddings: {self.config.EMBEDDING_MODEL}")
|
| 70 |
else:
|
| 71 |
-
logger.warning("OpenAI embedding model requested but no API key found. Falling back to HuggingFace.")
|
| 72 |
Settings.embed_model = HuggingFaceEmbedding(
|
| 73 |
model_name="sentence-transformers/all-MiniLM-L6-v2"
|
| 74 |
)
|
|
@@ -76,7 +69,6 @@ class LlamaIndexService:
|
|
| 76 |
Settings.embed_model = HuggingFaceEmbedding(
|
| 77 |
model_name=self.config.EMBEDDING_MODEL
|
| 78 |
)
|
| 79 |
-
logger.info(f"LlamaIndex using HuggingFace embeddings: {self.config.EMBEDDING_MODEL}")
|
| 80 |
|
| 81 |
except Exception as e:
|
| 82 |
logger.error(f"Error initializing LlamaIndex settings: {str(e)}")
|
|
@@ -84,14 +76,14 @@ class LlamaIndexService:
|
|
| 84 |
def _try_load_from_storage(self):
|
| 85 |
"""Try to load index from storage synchronously"""
|
| 86 |
try:
|
| 87 |
-
if self.storage_dir.exists():
|
| 88 |
logger.info("Loading LlamaIndex from storage...")
|
| 89 |
storage_context = StorageContext.from_defaults(persist_dir=str(self.storage_dir))
|
| 90 |
self.index = load_index_from_storage(storage_context)
|
| 91 |
self._initialize_agent()
|
| 92 |
self.is_initialized = True
|
| 93 |
else:
|
| 94 |
-
logger.info("No existing LlamaIndex storage found. Waiting for
|
| 95 |
except Exception as e:
|
| 96 |
logger.error(f"Error loading LlamaIndex from storage: {str(e)}")
|
| 97 |
|
|
@@ -99,15 +91,11 @@ class LlamaIndexService:
|
|
| 99 |
"""Async initialization to sync documents and build index"""
|
| 100 |
try:
|
| 101 |
logger.info("Starting LlamaIndex async initialization...")
|
| 102 |
-
|
| 103 |
-
# If we already have an index, we might still want to sync if it's empty or stale
|
| 104 |
-
# For now, if no index exists, we definitely need to build it
|
| 105 |
if self.index is None:
|
| 106 |
await self.sync_from_document_store()
|
| 107 |
|
| 108 |
self.is_initialized = True
|
| 109 |
logger.info("LlamaIndex async initialization complete.")
|
| 110 |
-
|
| 111 |
except Exception as e:
|
| 112 |
logger.error(f"Error during LlamaIndex async initialization: {str(e)}")
|
| 113 |
|
|
@@ -116,18 +104,19 @@ class LlamaIndexService:
|
|
| 116 |
try:
|
| 117 |
logger.info("Syncing documents from DocumentStore to LlamaIndex...")
|
| 118 |
|
| 119 |
-
# Fetch documents from async document store
|
| 120 |
-
# Limit to 1000 for now to avoid memory issues
|
| 121 |
docs = await self.document_store.list_documents(limit=1000)
|
| 122 |
|
| 123 |
if not docs:
|
| 124 |
-
logger.warning("No documents found in DocumentStore
|
| 125 |
-
#
|
| 126 |
-
self.index =
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
|
|
|
|
|
|
|
|
|
| 131 |
llama_doc = Document(
|
| 132 |
text=doc.content,
|
| 133 |
metadata={
|
|
@@ -137,9 +126,13 @@ class LlamaIndexService:
|
|
| 137 |
}
|
| 138 |
)
|
| 139 |
llama_docs.append(llama_doc)
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
|
| 144 |
# Persist storage
|
| 145 |
if not self.storage_dir.exists():
|
|
@@ -174,6 +167,7 @@ class LlamaIndexService:
|
|
| 174 |
)
|
| 175 |
)
|
| 176 |
|
|
|
|
| 177 |
self.agent = ReActAgent.from_tools(
|
| 178 |
[query_engine_tool],
|
| 179 |
llm=Settings.llm,
|
|
@@ -186,14 +180,24 @@ class LlamaIndexService:
|
|
| 186 |
|
| 187 |
async def query(self, query_text: str) -> str:
|
| 188 |
"""Process a query using the agent"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
if not self.agent:
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
|
|
|
|
|
|
| 193 |
|
| 194 |
try:
|
|
|
|
| 195 |
response = await self.agent.achat(query_text)
|
| 196 |
return str(response)
|
| 197 |
except Exception as e:
|
| 198 |
logger.error(f"Error querying LlamaIndex agent: {str(e)}")
|
| 199 |
-
return f"
|
|
|
|
| 15 |
)
|
| 16 |
from llama_index.core.tools import QueryEngineTool, ToolMetadata
|
| 17 |
from llama_index.core.agent import ReActAgent
|
|
|
|
|
|
|
| 18 |
from llama_index.llms.openai import OpenAI
|
| 19 |
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
|
| 20 |
from llama_index.embeddings.openai import OpenAIEmbedding
|
|
|
|
| 34 |
self.is_initialized = False
|
| 35 |
|
| 36 |
self._initialize_settings()
|
| 37 |
+
# Attempt to load existing index, but don't fail if empty
|
|
|
|
| 38 |
self._try_load_from_storage()
|
| 39 |
|
| 40 |
def _initialize_settings(self):
|
|
|
|
| 42 |
try:
|
| 43 |
# LLM Setup
|
| 44 |
if self.config.OPENAI_API_KEY:
|
|
|
|
| 45 |
Settings.llm = OpenAI(model=self.config.OPENAI_MODEL, api_key=self.config.OPENAI_API_KEY)
|
| 46 |
logger.info(f"LlamaIndex using OpenAI model: {self.config.OPENAI_MODEL}")
|
| 47 |
elif self.config.NEBIUS_API_KEY:
|
|
|
|
| 48 |
Settings.llm = OpenAI(
|
| 49 |
model=self.config.NEBIUS_MODEL,
|
| 50 |
api_key=self.config.NEBIUS_API_KEY,
|
|
|
|
| 52 |
)
|
| 53 |
logger.info(f"LlamaIndex using Nebius model: {self.config.NEBIUS_MODEL}")
|
| 54 |
else:
|
| 55 |
+
logger.warning("No API key found for LlamaIndex LLM. Agentic features may fail.")
|
| 56 |
|
| 57 |
# Embedding Setup
|
| 58 |
if self.config.EMBEDDING_MODEL.startswith("text-embedding-"):
|
|
|
|
| 61 |
model=self.config.EMBEDDING_MODEL,
|
| 62 |
api_key=self.config.OPENAI_API_KEY
|
| 63 |
)
|
|
|
|
| 64 |
else:
|
|
|
|
| 65 |
Settings.embed_model = HuggingFaceEmbedding(
|
| 66 |
model_name="sentence-transformers/all-MiniLM-L6-v2"
|
| 67 |
)
|
|
|
|
| 69 |
Settings.embed_model = HuggingFaceEmbedding(
|
| 70 |
model_name=self.config.EMBEDDING_MODEL
|
| 71 |
)
|
|
|
|
| 72 |
|
| 73 |
except Exception as e:
|
| 74 |
logger.error(f"Error initializing LlamaIndex settings: {str(e)}")
|
|
|
|
| 76 |
def _try_load_from_storage(self):
|
| 77 |
"""Try to load index from storage synchronously"""
|
| 78 |
try:
|
| 79 |
+
if self.storage_dir.exists() and any(self.storage_dir.iterdir()):
|
| 80 |
logger.info("Loading LlamaIndex from storage...")
|
| 81 |
storage_context = StorageContext.from_defaults(persist_dir=str(self.storage_dir))
|
| 82 |
self.index = load_index_from_storage(storage_context)
|
| 83 |
self._initialize_agent()
|
| 84 |
self.is_initialized = True
|
| 85 |
else:
|
| 86 |
+
logger.info("No existing LlamaIndex storage found. Waiting for initialization.")
|
| 87 |
except Exception as e:
|
| 88 |
logger.error(f"Error loading LlamaIndex from storage: {str(e)}")
|
| 89 |
|
|
|
|
| 91 |
"""Async initialization to sync documents and build index"""
|
| 92 |
try:
|
| 93 |
logger.info("Starting LlamaIndex async initialization...")
|
|
|
|
|
|
|
|
|
|
| 94 |
if self.index is None:
|
| 95 |
await self.sync_from_document_store()
|
| 96 |
|
| 97 |
self.is_initialized = True
|
| 98 |
logger.info("LlamaIndex async initialization complete.")
|
|
|
|
| 99 |
except Exception as e:
|
| 100 |
logger.error(f"Error during LlamaIndex async initialization: {str(e)}")
|
| 101 |
|
|
|
|
| 104 |
try:
|
| 105 |
logger.info("Syncing documents from DocumentStore to LlamaIndex...")
|
| 106 |
|
|
|
|
|
|
|
| 107 |
docs = await self.document_store.list_documents(limit=1000)
|
| 108 |
|
| 109 |
if not docs:
|
| 110 |
+
logger.warning("No documents found in DocumentStore. Creating empty index.")
|
| 111 |
+
# FIX: Handle empty state gracefully
|
| 112 |
+
self.index = None
|
| 113 |
+
self.agent = None
|
| 114 |
+
return
|
| 115 |
+
|
| 116 |
+
# Convert to LlamaIndex documents
|
| 117 |
+
llama_docs = []
|
| 118 |
+
for doc in docs:
|
| 119 |
+
if doc.content and len(doc.content.strip()) > 0:
|
| 120 |
llama_doc = Document(
|
| 121 |
text=doc.content,
|
| 122 |
metadata={
|
|
|
|
| 126 |
}
|
| 127 |
)
|
| 128 |
llama_docs.append(llama_doc)
|
| 129 |
+
|
| 130 |
+
if not llama_docs:
|
| 131 |
+
logger.warning("Documents found but content was empty.")
|
| 132 |
+
return
|
| 133 |
+
|
| 134 |
+
logger.info(f"Building LlamaIndex with {len(llama_docs)} documents...")
|
| 135 |
+
self.index = VectorStoreIndex.from_documents(llama_docs)
|
| 136 |
|
| 137 |
# Persist storage
|
| 138 |
if not self.storage_dir.exists():
|
|
|
|
| 167 |
)
|
| 168 |
)
|
| 169 |
|
| 170 |
+
# ReAct Agent requires an LLM
|
| 171 |
self.agent = ReActAgent.from_tools(
|
| 172 |
[query_engine_tool],
|
| 173 |
llm=Settings.llm,
|
|
|
|
| 180 |
|
| 181 |
async def query(self, query_text: str) -> str:
|
| 182 |
"""Process a query using the agent"""
|
| 183 |
+
|
| 184 |
+
# 1. AUTO-RECOVERY: If agent is missing, try to initialize it now
|
| 185 |
+
if not self.agent:
|
| 186 |
+
logger.info("Agent not found during query. Attempting to initialize...")
|
| 187 |
+
await self.initialize()
|
| 188 |
+
|
| 189 |
+
# 2. Check if it's still missing after attempt
|
| 190 |
if not self.agent:
|
| 191 |
+
# Check why it failed
|
| 192 |
+
if not self.index:
|
| 193 |
+
return "I can't answer that yet because there are no documents in the library. Please upload a document first."
|
| 194 |
+
|
| 195 |
+
return "System Error: The AI agent failed to start. Please check if your OPENAI_API_KEY is correct in the .env file."
|
| 196 |
|
| 197 |
try:
|
| 198 |
+
# 3. Run the query
|
| 199 |
response = await self.agent.achat(query_text)
|
| 200 |
return str(response)
|
| 201 |
except Exception as e:
|
| 202 |
logger.error(f"Error querying LlamaIndex agent: {str(e)}")
|
| 203 |
+
return f"I encountered an error searching the documents: {str(e)}"
|