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