local-deep-research 0.2.2__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/PKG-INFO +16 -1
  2. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/README.md +15 -0
  3. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/pyproject.toml +1 -1
  4. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/__init__.py +1 -1
  5. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/filters/cross_engine_filter.py +5 -1
  6. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/strategies/base_strategy.py +5 -2
  7. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +23 -16
  8. local_deep_research-0.3.0/src/local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +441 -0
  9. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +4 -3
  10. local_deep_research-0.3.0/src/local_deep_research/advanced_search_system/strategies/source_based_strategy.py +402 -0
  11. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/strategies/standard_strategy.py +8 -4
  12. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/api/research_functions.py +0 -46
  13. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/citation_handler.py +16 -20
  14. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/config/llm_config.py +25 -68
  15. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/config/search_config.py +8 -21
  16. local_deep_research-0.3.0/src/local_deep_research/defaults/default_settings.json +3814 -0
  17. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/search_system.py +46 -32
  18. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/utilities/db_utils.py +22 -3
  19. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/utilities/search_utilities.py +10 -7
  20. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/app.py +3 -23
  21. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/app_factory.py +1 -25
  22. local_deep_research-0.3.0/src/local_deep_research/web/database/migrations.py +49 -0
  23. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/routes/settings_routes.py +75 -364
  24. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/services/research_service.py +47 -43
  25. local_deep_research-0.3.0/src/local_deep_research/web/services/settings_manager.py +462 -0
  26. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/services/settings_service.py +3 -56
  27. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/js/components/research.js +1 -1
  28. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/js/components/settings.js +16 -4
  29. local_deep_research-0.3.0/src/local_deep_research/web/static/js/research_form.js +106 -0
  30. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/templates/pages/research.html +3 -2
  31. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web_search_engines/engines/meta_search_engine.py +13 -18
  32. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web_search_engines/engines/search_engine_local.py +11 -2
  33. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web_search_engines/engines/search_engine_local_all.py +7 -11
  34. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web_search_engines/search_engine_factory.py +12 -64
  35. local_deep_research-0.3.0/src/local_deep_research/web_search_engines/search_engines_config.py +137 -0
  36. local_deep_research-0.2.2/src/local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +0 -312
  37. local_deep_research-0.2.2/src/local_deep_research/config/config_files.py +0 -245
  38. local_deep_research-0.2.2/src/local_deep_research/defaults/local_collections.toml +0 -53
  39. local_deep_research-0.2.2/src/local_deep_research/defaults/main.toml +0 -80
  40. local_deep_research-0.2.2/src/local_deep_research/defaults/search_engines.toml +0 -291
  41. local_deep_research-0.2.2/src/local_deep_research/web/database/migrations.py +0 -447
  42. local_deep_research-0.2.2/src/local_deep_research/web/services/settings_manager.py +0 -669
  43. local_deep_research-0.2.2/src/local_deep_research/web_search_engines/search_engines_config.py +0 -78
  44. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/LICENSE +0 -0
  45. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/__main__.py +0 -0
  46. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/__init__.py +0 -0
  47. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/filters/__init__.py +0 -0
  48. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/filters/base_filter.py +0 -0
  49. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/findings/base_findings.py +0 -0
  50. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/findings/repository.py +0 -0
  51. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/knowledge/__init__.py +0 -0
  52. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/knowledge/base_knowledge.py +0 -0
  53. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/knowledge/standard_knowledge.py +0 -0
  54. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/questions/__init__.py +0 -0
  55. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/questions/base_question.py +0 -0
  56. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/questions/decomposition_question.py +0 -0
  57. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/questions/standard_question.py +0 -0
  58. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/repositories/__init__.py +0 -0
  59. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/strategies/__init__.py +0 -0
  60. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/tools/__init__.py +0 -0
  61. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/tools/base_tool.py +0 -0
  62. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/tools/knowledge_tools/__init__.py +0 -0
  63. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/tools/question_tools/__init__.py +0 -0
  64. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/advanced_search_system/tools/search_tools/__init__.py +0 -0
  65. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/api/__init__.py +0 -0
  66. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/app.py +0 -0
  67. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/config/__init__.py +0 -0
  68. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/defaults/.env.template +0 -0
  69. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/defaults/__init__.py +0 -0
  70. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/main.py +0 -0
  71. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/migrate_db.py +0 -0
  72. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/report_generator.py +0 -0
  73. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/setup_data_dir.py +0 -0
  74. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/test_migration.py +0 -0
  75. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/utilities/__init__.py +0 -0
  76. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/utilities/enums.py +0 -0
  77. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/utilities/llm_utils.py +0 -0
  78. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/utilities/setup_utils.py +0 -0
  79. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/__init__.py +0 -0
  80. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/database/README.md +0 -0
  81. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/database/migrate_to_ldr_db.py +0 -0
  82. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/database/models.py +0 -0
  83. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/database/schema_upgrade.py +0 -0
  84. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/models/database.py +0 -0
  85. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/models/settings.py +0 -0
  86. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/routes/api_routes.py +0 -0
  87. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/routes/history_routes.py +0 -0
  88. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/routes/research_routes.py +0 -0
  89. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/services/resource_service.py +0 -0
  90. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/services/socket_service.py +0 -0
  91. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/css/custom_dropdown.css +0 -0
  92. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/css/settings.css +0 -0
  93. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/css/styles.css +0 -0
  94. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/js/components/custom_dropdown.js +0 -0
  95. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/js/components/detail.js +0 -0
  96. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/js/components/fallback/formatting.js +0 -0
  97. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/js/components/fallback/ui.js +0 -0
  98. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/js/components/history.js +0 -0
  99. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/js/components/logpanel.js +0 -0
  100. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/js/components/progress.js +0 -0
  101. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/js/components/results.js +0 -0
  102. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/js/components/settings_sync.js +0 -0
  103. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/js/main.js +0 -0
  104. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/js/services/api.js +0 -0
  105. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/js/services/audio.js +0 -0
  106. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/js/services/formatting.js +0 -0
  107. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/js/services/pdf.js +0 -0
  108. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/js/services/socket.js +0 -0
  109. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/js/services/ui.js +0 -0
  110. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/sounds/README.md +0 -0
  111. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/sounds/error.mp3 +0 -0
  112. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/static/sounds/success.mp3 +0 -0
  113. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/templates/base.html +0 -0
  114. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/templates/components/custom_dropdown.html +0 -0
  115. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/templates/components/log_panel.html +0 -0
  116. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/templates/components/mobile_nav.html +0 -0
  117. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/templates/components/settings_form.html +0 -0
  118. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/templates/components/sidebar.html +0 -0
  119. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/templates/pages/details.html +0 -0
  120. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/templates/pages/history.html +0 -0
  121. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/templates/pages/progress.html +0 -0
  122. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/templates/pages/results.html +0 -0
  123. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/templates/settings_dashboard.html +0 -0
  124. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/utils/__init__.py +0 -0
  125. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web/utils/formatters.py +0 -0
  126. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web_search_engines/__init__.py +0 -0
  127. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web_search_engines/engines/__init__.py +0 -0
  128. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web_search_engines/engines/full_search.py +0 -0
  129. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web_search_engines/engines/search_engine_arxiv.py +0 -0
  130. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web_search_engines/engines/search_engine_brave.py +0 -0
  131. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web_search_engines/engines/search_engine_ddg.py +0 -0
  132. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web_search_engines/engines/search_engine_github.py +0 -0
  133. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web_search_engines/engines/search_engine_google_pse.py +0 -0
  134. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web_search_engines/engines/search_engine_guardian.py +0 -0
  135. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web_search_engines/engines/search_engine_pubmed.py +0 -0
  136. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web_search_engines/engines/search_engine_searxng.py +0 -0
  137. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web_search_engines/engines/search_engine_semantic_scholar.py +0 -0
  138. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web_search_engines/engines/search_engine_serpapi.py +0 -0
  139. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web_search_engines/engines/search_engine_wayback.py +0 -0
  140. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web_search_engines/engines/search_engine_wikipedia.py +0 -0
  141. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/src/local_deep_research/web_search_engines/search_engine_base.py +0 -0
  142. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/tests/__init__.py +0 -0
  143. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/tests/download_stuff_for_local_test.py +0 -0
  144. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/tests/searxng/test_searxng_instance.py +0 -0
  145. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/tests/searxng/test_searxng_integration.py +0 -0
  146. {local_deep_research-0.2.2 → local_deep_research-0.3.0}/tests/test_google_pse.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: local-deep-research
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: AI-powered research assistant with deep, iterative analysis using LLMs and web searches
5
5
  Author-Email: LearningCircuit <185559241+LearningCircuit@users.noreply.github.com>, HashedViking <6432677+HashedViking@users.noreply.github.com>
6
6
  License: MIT License
@@ -122,6 +122,21 @@ A powerful AI-powered research assistant that performs deep, iterative analysis
122
122
 
123
123
  **Important for non-academic searches:** For normal web searches you will need SearXNG or an API key to a search provider like brave search or SerpAPI. The free searches are mostly academic search engines and will not help you for most normal searches.
124
124
 
125
+ ## Quick SearXNG Setup (Recommended)
126
+
127
+ ```bash
128
+ # Pull the SearXNG Docker image
129
+ docker pull searxng/searxng
130
+
131
+ # Run SearXNG (will be available at http://localhost:8080)
132
+ docker run -d -p 8080:8080 --name searxng searxng/searxng
133
+
134
+ # Start SearXNG (Required after system restart)
135
+ docker start searxng
136
+ ```
137
+
138
+ Once these commands are executed, SearXNG will be automatically activated and ready to use. The tool will automatically detect and use your local SearXNG instance for searches.
139
+
125
140
  ## Windows Installation
126
141
 
127
142
  Download the [Windows Installer](https://github.com/LearningCircuit/local-deep-research/releases/download/v0.1.0/LocalDeepResearch_Setup.exe) for easy one-click installation.
@@ -51,6 +51,21 @@ A powerful AI-powered research assistant that performs deep, iterative analysis
51
51
 
52
52
  **Important for non-academic searches:** For normal web searches you will need SearXNG or an API key to a search provider like brave search or SerpAPI. The free searches are mostly academic search engines and will not help you for most normal searches.
53
53
 
54
+ ## Quick SearXNG Setup (Recommended)
55
+
56
+ ```bash
57
+ # Pull the SearXNG Docker image
58
+ docker pull searxng/searxng
59
+
60
+ # Run SearXNG (will be available at http://localhost:8080)
61
+ docker run -d -p 8080:8080 --name searxng searxng/searxng
62
+
63
+ # Start SearXNG (Required after system restart)
64
+ docker start searxng
65
+ ```
66
+
67
+ Once these commands are executed, SearXNG will be automatically activated and ready to use. The tool will automatically detect and use your local SearXNG instance for searches.
68
+
54
69
  ## Windows Installation
55
70
 
56
71
  Download the [Windows Installer](https://github.com/LearningCircuit/local-deep-research/releases/download/v0.1.0/LocalDeepResearch_Setup.exe) for easy one-click installation.
@@ -6,7 +6,7 @@ build-backend = "pdm.backend"
6
6
 
7
7
  [project]
8
8
  name = "local-deep-research"
9
- version = "0.2.2"
9
+ version = "0.3.0"
10
10
  description = "AI-powered research assistant with deep, iterative analysis using LLMs and web searches"
11
11
  readme = "README.md"
12
12
  requires-python = ">=3.10"
@@ -2,7 +2,7 @@
2
2
  Local Deep Research - A tool for conducting deep research using AI.
3
3
  """
4
4
 
5
- __version__ = "0.1.0"
5
+ __version__ = "0.2.0"
6
6
  __author__ = "Your Name"
7
7
  __description__ = "A tool for conducting deep research using AI"
8
8
 
@@ -6,6 +6,7 @@ import json
6
6
  import logging
7
7
  from typing import Dict, List
8
8
 
9
+ from ...utilities.db_utils import get_db_setting
9
10
  from ...utilities.search_utilities import remove_think_tags
10
11
  from .base_filter import BaseFilter
11
12
 
@@ -16,7 +17,7 @@ class CrossEngineFilter(BaseFilter):
16
17
  """Filter that ranks and filters results from multiple search engines."""
17
18
 
18
19
  def __init__(
19
- self, model, max_results=20, default_reorder=True, default_reindex=True
20
+ self, model, max_results=None, default_reorder=True, default_reindex=True
20
21
  ):
21
22
  """
22
23
  Initialize the cross-engine filter.
@@ -28,6 +29,9 @@ class CrossEngineFilter(BaseFilter):
28
29
  default_reindex: Default setting for reindexing results after filtering
29
30
  """
30
31
  super().__init__(model)
32
+ # Get max_results from database settings if not provided
33
+ if max_results is None:
34
+ max_results = get_db_setting("search.cross_engine_max_results", 100)
31
35
  self.max_results = max_results
32
36
  self.default_reorder = default_reorder
33
37
  self.default_reindex = default_reindex
@@ -13,11 +13,14 @@ logger = logging.getLogger(__name__)
13
13
  class BaseSearchStrategy(ABC):
14
14
  """Abstract base class for all search strategies."""
15
15
 
16
- def __init__(self):
16
+ def __init__(self, all_links_of_system=None):
17
17
  """Initialize the base strategy with common attributes."""
18
18
  self.progress_callback = None
19
19
  self.questions_by_iteration = {}
20
- self.all_links_of_system = []
20
+ # Create a new list if None is provided (avoiding mutable default argument)
21
+ self.all_links_of_system = (
22
+ all_links_of_system if all_links_of_system is not None else []
23
+ )
21
24
 
22
25
  def set_progress_callback(self, callback: Callable[[str, int, dict], None]) -> None:
23
26
  """Set a callback function to receive progress updates."""
@@ -7,10 +7,7 @@ import logging
7
7
  from datetime import datetime
8
8
  from typing import Dict, List
9
9
 
10
- from langchain_core.language_models import BaseLLM
11
-
12
10
  from ...citation_handler import CitationHandler
13
- from ...config.config_files import settings
14
11
  from ...config.llm_config import get_llm
15
12
  from ...config.search_config import get_search
16
13
  from ...utilities.db_utils import get_db_setting
@@ -27,18 +24,34 @@ class IterDRAGStrategy(BaseSearchStrategy):
27
24
  """IterDRAG strategy that breaks queries into sub-queries."""
28
25
 
29
26
  def __init__(
30
- self, model: BaseLLM | None = None, search=None, citation_handler=None
27
+ self,
28
+ search=None,
29
+ model=None,
30
+ max_iterations=3,
31
+ subqueries_per_iteration=2,
32
+ all_links_of_system=None,
31
33
  ):
32
- """Initialize the strategy with optional dependency injection for testing."""
33
- super().__init__()
34
- self.model = model or get_llm()
34
+ """Initialize the IterDRAG strategy with search and LLM.
35
+
36
+ Args:
37
+ search: Search engine to use for web queries
38
+ model: LLM to use for text generation and reasoning
39
+ max_iterations: Maximum number of iterations to run
40
+ subqueries_per_iteration: Number of sub-queries to generate per iteration
41
+ all_links_of_system: Optional list of links to initialize with
42
+ """
43
+ super().__init__(all_links_of_system=all_links_of_system)
35
44
  self.search = search or get_search()
45
+ self.model = model or get_llm()
46
+ self.max_iterations = max_iterations
47
+ self.subqueries_per_iteration = subqueries_per_iteration
48
+
49
+ # Initialize progress callback
36
50
  self.progress_callback = None
37
- self.all_links_of_system = list()
38
51
  self.questions_by_iteration = {}
39
52
 
40
53
  # Use provided citation_handler or create one
41
- self.citation_handler = citation_handler or CitationHandler(self.model)
54
+ self.citation_handler = CitationHandler(self.model)
42
55
 
43
56
  # Initialize components
44
57
  self.question_generator = DecompositionQuestionGenerator(self.model)
@@ -396,13 +409,7 @@ Please try again with a different query or contact support.
396
409
  """
397
410
 
398
411
  # Compress knowledge if needed
399
- if (
400
- get_db_setting(
401
- "general.knowledge_accumulation",
402
- settings.general.knowledge_accumulation,
403
- )
404
- == "ITERATION"
405
- ):
412
+ if get_db_setting("general.knowledge_accumulation", "ITERATION") == "ITERATION":
406
413
  try:
407
414
  self._update_progress(
408
415
  "Compressing knowledge", 90, {"phase": "knowledge_compression"}
@@ -0,0 +1,441 @@
1
+ """
2
+ Parallel search strategy implementation for maximum search speed.
3
+ """
4
+
5
+ import concurrent.futures
6
+ import logging
7
+ from typing import Dict
8
+
9
+ from ...citation_handler import CitationHandler
10
+ from ...config.llm_config import get_llm
11
+ from ...config.search_config import get_search
12
+ from ...utilities.db_utils import get_db_setting
13
+ from ...utilities.search_utilities import extract_links_from_search_results
14
+ from ..filters.cross_engine_filter import CrossEngineFilter
15
+ from ..findings.repository import FindingsRepository
16
+ from ..questions.standard_question import StandardQuestionGenerator
17
+ from .base_strategy import BaseSearchStrategy
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class ParallelSearchStrategy(BaseSearchStrategy):
23
+ """
24
+ Parallel search strategy that generates questions and runs all searches
25
+ simultaneously for maximum speed.
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ search=None,
31
+ model=None,
32
+ citation_handler=None,
33
+ include_text_content: bool = True,
34
+ use_cross_engine_filter: bool = True,
35
+ filter_reorder: bool = True,
36
+ filter_reindex: bool = True,
37
+ cross_engine_max_results: int = None,
38
+ all_links_of_system=None,
39
+ ):
40
+ """Initialize with optional dependency injection for testing.
41
+
42
+ Args:
43
+ search: Optional search engine instance
44
+ model: Optional LLM model instance
45
+ citation_handler: Optional citation handler instance
46
+ include_text_content: If False, only includes metadata and links in search results
47
+ use_cross_engine_filter: If True, filter search results across engines
48
+ filter_reorder: Whether to reorder results by relevance
49
+ filter_reindex: Whether to update result indices after filtering
50
+ cross_engine_max_results: Maximum number of results to keep after cross-engine filtering
51
+ all_links_of_system: Optional list of links to initialize with
52
+ """
53
+ super().__init__(all_links_of_system=all_links_of_system)
54
+ self.search = search or get_search()
55
+ self.model = model or get_llm()
56
+ self.progress_callback = None
57
+ self.questions_by_iteration = {}
58
+ self.include_text_content = include_text_content
59
+ self.use_cross_engine_filter = use_cross_engine_filter
60
+ self.filter_reorder = filter_reorder
61
+ self.filter_reindex = filter_reindex
62
+
63
+ # Get max_filtered_results from database if not provided
64
+ if cross_engine_max_results is None:
65
+ cross_engine_max_results = get_db_setting(
66
+ "search.cross_engine_max_results", 100
67
+ )
68
+
69
+ # Initialize the cross-engine filter
70
+ self.cross_engine_filter = CrossEngineFilter(
71
+ model=self.model,
72
+ max_results=cross_engine_max_results,
73
+ default_reorder=filter_reorder,
74
+ default_reindex=filter_reindex,
75
+ )
76
+
77
+ # Set include_full_content on the search engine if it supports it
78
+ if hasattr(self.search, "include_full_content"):
79
+ self.search.include_full_content = include_text_content
80
+
81
+ # Use provided citation_handler or create one
82
+ self.citation_handler = citation_handler or CitationHandler(self.model)
83
+
84
+ # Initialize components
85
+ self.question_generator = StandardQuestionGenerator(self.model)
86
+ self.findings_repository = FindingsRepository(self.model)
87
+
88
+ def analyze_topic(self, query: str) -> Dict:
89
+ """
90
+ Analyze a topic using parallel search, supporting multiple iterations.
91
+
92
+ Args:
93
+ query: The research query to analyze
94
+ """
95
+ logger.info(f"Starting parallel research on topic: {query}")
96
+
97
+ findings = []
98
+ all_search_results = []
99
+ current_knowledge = ""
100
+
101
+ # Track all search results across iterations
102
+ self.all_links_of_system = list()
103
+ self.questions_by_iteration = {}
104
+
105
+ self._update_progress(
106
+ "Initializing parallel research",
107
+ 5,
108
+ {
109
+ "phase": "init",
110
+ "strategy": "parallel",
111
+ "include_text_content": self.include_text_content,
112
+ },
113
+ )
114
+
115
+ # Check search engine
116
+ if not self._validate_search_engine():
117
+ return {
118
+ "findings": [],
119
+ "iterations": 0,
120
+ "questions_by_iteration": {},
121
+ "formatted_findings": "Error: Unable to conduct research without a search engine.",
122
+ "current_knowledge": "",
123
+ "error": "No search engine available",
124
+ }
125
+
126
+ # Determine number of iterations to run
127
+ iterations_to_run = get_db_setting("search.iterations")
128
+ logger.debug("Selected amount of iterations: " + str(iterations_to_run))
129
+ iterations_to_run = int(iterations_to_run)
130
+ try:
131
+ # Run each iteration
132
+ for iteration in range(1, iterations_to_run + 1):
133
+ iteration_progress_base = 5 + (iteration - 1) * (70 / iterations_to_run)
134
+
135
+ self._update_progress(
136
+ f"Starting iteration {iteration}/{iterations_to_run}",
137
+ iteration_progress_base,
138
+ {"phase": f"iteration_{iteration}", "iteration": iteration},
139
+ )
140
+
141
+ # Step 1: Generate questions
142
+ self._update_progress(
143
+ f"Generating search questions for iteration {iteration}",
144
+ iteration_progress_base + 5,
145
+ {"phase": "question_generation", "iteration": iteration},
146
+ )
147
+
148
+ # For first iteration, generate initial questions
149
+ # For subsequent iterations, generate follow-up questions
150
+ logger.info("Starting to generate questions")
151
+ if iteration == 1:
152
+ # Generate additional questions (plus the main query)
153
+ if iterations_to_run > 1:
154
+ context = f"""Iteration: {1} of {iterations_to_run}"""
155
+ else:
156
+ context = ""
157
+ questions = self.question_generator.generate_questions(
158
+ current_knowledge=context,
159
+ query=query,
160
+ questions_per_iteration=int(
161
+ get_db_setting("search.questions_per_iteration")
162
+ ),
163
+ questions_by_iteration=self.questions_by_iteration,
164
+ )
165
+
166
+ # Add the original query as the first question
167
+ all_questions = [query] + questions
168
+
169
+ # Store in questions_by_iteration
170
+ self.questions_by_iteration[iteration] = questions
171
+ logger.info(
172
+ f"Generated questions for iteration {iteration}: {questions}"
173
+ )
174
+ else:
175
+ # Get past questions from all previous iterations
176
+ past_questions = []
177
+ for prev_iter in range(1, iteration):
178
+ if prev_iter in self.questions_by_iteration:
179
+ past_questions.extend(
180
+ self.questions_by_iteration[prev_iter]
181
+ )
182
+
183
+ # Generate follow-up questions based on accumulated knowledge if iterations > 2
184
+ use_knowledge = iterations_to_run > 2
185
+ knowledge_for_questions = current_knowledge if use_knowledge else ""
186
+ context = f"""Current Knowledge: {knowledge_for_questions}
187
+ Iteration: {iteration} of {iterations_to_run}"""
188
+
189
+ # Generate questions
190
+ questions = self.question_generator.generate_questions(
191
+ current_knowledge=context,
192
+ query=query,
193
+ questions_per_iteration=int(
194
+ get_db_setting("search.questions_per_iteration")
195
+ ),
196
+ questions_by_iteration=self.questions_by_iteration,
197
+ )
198
+
199
+ # Use only the new questions for this iteration's searches
200
+ all_questions = questions
201
+
202
+ # Store in questions_by_iteration
203
+ self.questions_by_iteration[iteration] = questions
204
+ logger.info(
205
+ f"Generated questions for iteration {iteration}: {questions}"
206
+ )
207
+
208
+ # Step 2: Run all searches in parallel for this iteration
209
+ self._update_progress(
210
+ f"Running parallel searches for iteration {iteration}",
211
+ iteration_progress_base + 10,
212
+ {"phase": "parallel_search", "iteration": iteration},
213
+ )
214
+
215
+ # Function for thread pool
216
+ def search_question(q):
217
+ try:
218
+ result = self.search.run(q)
219
+ return {"question": q, "results": result or []}
220
+ except Exception as e:
221
+ logger.error(f"Error searching for '{q}': {str(e)}")
222
+ return {"question": q, "results": [], "error": str(e)}
223
+
224
+ # Run searches in parallel
225
+ with concurrent.futures.ThreadPoolExecutor(
226
+ max_workers=len(all_questions)
227
+ ) as executor:
228
+ futures = [
229
+ executor.submit(search_question, q) for q in all_questions
230
+ ]
231
+ iteration_search_dict = {}
232
+ iteration_search_results = []
233
+
234
+ # Process results as they complete
235
+ for i, future in enumerate(
236
+ concurrent.futures.as_completed(futures)
237
+ ):
238
+ result_dict = future.result()
239
+ question = result_dict["question"]
240
+ search_results = result_dict["results"]
241
+ iteration_search_dict[question] = search_results
242
+
243
+ self._update_progress(
244
+ f"Completed search {i + 1} of {len(all_questions)}: {question[:30]}...",
245
+ iteration_progress_base
246
+ + 10
247
+ + ((i + 1) / len(all_questions) * 30),
248
+ {
249
+ "phase": "search_complete",
250
+ "iteration": iteration,
251
+ "result_count": len(search_results),
252
+ "question": question,
253
+ },
254
+ )
255
+
256
+ # Collect all search results for this iteration
257
+ iteration_search_results.extend(search_results)
258
+
259
+ # Step 3: Filter and analyze results for this iteration
260
+ self._update_progress(
261
+ f"Analyzing results for iteration {iteration}",
262
+ iteration_progress_base + 45,
263
+ {"phase": "iteration_analysis", "iteration": iteration},
264
+ )
265
+
266
+ # Apply cross-engine filtering if enabled
267
+ if self.use_cross_engine_filter:
268
+ self._update_progress(
269
+ f"Filtering search results for iteration {iteration}",
270
+ iteration_progress_base + 45,
271
+ {"phase": "cross_engine_filtering", "iteration": iteration},
272
+ )
273
+
274
+ # Get the current link count (for indexing)
275
+ existing_link_count = len(self.all_links_of_system)
276
+
277
+ # Filter the search results
278
+ filtered_search_results = self.cross_engine_filter.filter_results(
279
+ iteration_search_results,
280
+ query,
281
+ reorder=self.filter_reorder,
282
+ reindex=self.filter_reindex,
283
+ start_index=existing_link_count, # Start indexing after existing links
284
+ )
285
+
286
+ links = extract_links_from_search_results(filtered_search_results)
287
+ self.all_links_of_system.extend(links)
288
+
289
+ self._update_progress(
290
+ f"Filtered from {len(iteration_search_results)} to {len(filtered_search_results)} results",
291
+ iteration_progress_base + 50,
292
+ {
293
+ "phase": "filtering_complete",
294
+ "iteration": iteration,
295
+ "links_count": len(self.all_links_of_system),
296
+ },
297
+ )
298
+
299
+ # Use filtered results for analysis
300
+ iteration_search_results = filtered_search_results
301
+ else:
302
+ # Just extract links without filtering
303
+ links = extract_links_from_search_results(iteration_search_results)
304
+ self.all_links_of_system.extend(links)
305
+
306
+ # Add to all search results
307
+ all_search_results.extend(iteration_search_results)
308
+
309
+ # Create a finding for this iteration's results
310
+ if self.include_text_content and iteration_search_results:
311
+ # For iteration > 1 with knowledge accumulation, use follow-up analysis
312
+ if iteration > 1 and iterations_to_run > 2:
313
+ citation_result = self.citation_handler.analyze_followup(
314
+ query,
315
+ iteration_search_results,
316
+ current_knowledge,
317
+ len(self.all_links_of_system) - len(links),
318
+ )
319
+ else:
320
+ # For first iteration or without knowledge accumulation, use initial analysis
321
+ citation_result = self.citation_handler.analyze_initial(
322
+ query, iteration_search_results
323
+ )
324
+
325
+ if citation_result:
326
+ # Create a finding for this iteration
327
+ iteration_content = citation_result["content"]
328
+
329
+ # Update current knowledge if iterations > 2
330
+ if iterations_to_run > 2:
331
+ if current_knowledge:
332
+ current_knowledge = f"{current_knowledge}\n\n## FINDINGS FROM ITERATION {iteration}:\n\n{iteration_content}"
333
+ else:
334
+ current_knowledge = iteration_content
335
+
336
+ finding = {
337
+ "phase": f"Iteration {iteration}",
338
+ "content": iteration_content,
339
+ "question": query,
340
+ "search_results": iteration_search_results,
341
+ "documents": citation_result.get("documents", []),
342
+ }
343
+ findings.append(finding)
344
+
345
+ # Add documents to repository
346
+ if "documents" in citation_result:
347
+ self.findings_repository.add_documents(
348
+ citation_result["documents"]
349
+ )
350
+
351
+ # Mark iteration as complete
352
+ iteration_progress = 5 + iteration * (70 / iterations_to_run)
353
+ self._update_progress(
354
+ f"Completed iteration {iteration}/{iterations_to_run}",
355
+ iteration_progress,
356
+ {"phase": "iteration_complete", "iteration": iteration},
357
+ )
358
+
359
+ # Final synthesis after all iterations
360
+ self._update_progress(
361
+ "Generating final synthesis", 80, {"phase": "synthesis"}
362
+ )
363
+
364
+ # Handle final synthesis based on include_text_content flag
365
+ if self.include_text_content:
366
+ # Generate a final synthesis from all search results
367
+ if iterations_to_run > 1:
368
+ final_citation_result = self.citation_handler.analyze_initial(
369
+ query, all_search_results
370
+ )
371
+ # Add null check for final_citation_result
372
+ if final_citation_result:
373
+ synthesized_content = final_citation_result["content"]
374
+ else:
375
+ synthesized_content = (
376
+ "No relevant results found in final synthesis."
377
+ )
378
+ else:
379
+ # For single iteration, use the content from findings
380
+ synthesized_content = (
381
+ findings[0]["content"]
382
+ if findings
383
+ else "No relevant results found."
384
+ )
385
+ # Add a final synthesis finding
386
+ final_finding = {
387
+ "phase": "Final synthesis",
388
+ "content": synthesized_content,
389
+ "question": query,
390
+ "search_results": all_search_results,
391
+ "documents": [],
392
+ }
393
+ findings.append(final_finding)
394
+ else:
395
+ # Skip LLM analysis, just format the raw search results
396
+ synthesized_content = "LLM analysis skipped"
397
+ final_finding = {
398
+ "phase": "Raw search results",
399
+ "content": "LLM analysis was skipped. Displaying raw search results with links.",
400
+ "question": query,
401
+ "search_results": all_search_results,
402
+ "documents": [],
403
+ }
404
+ findings.append(final_finding)
405
+
406
+ # Transfer questions to repository
407
+ self.findings_repository.set_questions_by_iteration(
408
+ self.questions_by_iteration
409
+ )
410
+
411
+ # Format findings
412
+ formatted_findings = self.findings_repository.format_findings_to_text(
413
+ findings, synthesized_content
414
+ )
415
+
416
+ except Exception as e:
417
+ import traceback
418
+
419
+ error_msg = f"Error in research process: {str(e)}"
420
+ logger.error(error_msg)
421
+ logger.error(traceback.format_exc())
422
+ synthesized_content = f"Error: {str(e)}"
423
+ formatted_findings = f"Error: {str(e)}"
424
+ finding = {
425
+ "phase": "Error",
426
+ "content": synthesized_content,
427
+ "question": query,
428
+ "search_results": [],
429
+ "documents": [],
430
+ }
431
+ findings.append(finding)
432
+
433
+ self._update_progress("Research complete", 100, {"phase": "complete"})
434
+
435
+ return {
436
+ "findings": findings,
437
+ "iterations": iterations_to_run,
438
+ "questions_by_iteration": self.questions_by_iteration,
439
+ "formatted_findings": formatted_findings,
440
+ "current_knowledge": synthesized_content,
441
+ }
@@ -23,13 +23,14 @@ class RapidSearchStrategy(BaseSearchStrategy):
23
23
  a single synthesis step at the end, optimized for speed.
24
24
  """
25
25
 
26
- def __init__(self, search=None, model=None, citation_handler=None):
26
+ def __init__(
27
+ self, search=None, model=None, citation_handler=None, all_links_of_system=None
28
+ ):
27
29
  """Initialize with optional dependency injection for testing."""
28
- super().__init__()
30
+ super().__init__(all_links_of_system=all_links_of_system)
29
31
  self.search = search or get_search()
30
32
  self.model = model or get_llm()
31
33
  self.progress_callback = None
32
- self.all_links_of_system = list()
33
34
  self.questions_by_iteration = {}
34
35
 
35
36
  # Use provided citation_handler or create one