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.
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/PKG-INFO +2 -2
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/README.md +1 -1
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/pyproject.toml +2 -1
- local_deep_research-0.2.3/src/local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +434 -0
- local_deep_research-0.2.3/src/local_deep_research/advanced_search_system/strategies/source_based_strategy.py +407 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/api/research_functions.py +72 -90
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/citation_handler.py +16 -17
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/defaults/search_engines.toml +1 -1
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/report_generator.py +19 -5
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/search_system.py +20 -3
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/routes/settings_routes.py +0 -9
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/services/research_service.py +4 -0
- {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
- local_deep_research-0.2.0/src/local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +0 -312
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/LICENSE +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/__init__.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/__main__.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/advanced_search_system/__init__.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/advanced_search_system/filters/__init__.py +0 -0
- {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
- {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
- {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
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/advanced_search_system/findings/repository.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/advanced_search_system/knowledge/__init__.py +0 -0
- {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
- {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
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/advanced_search_system/questions/__init__.py +0 -0
- {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
- {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
- {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
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/advanced_search_system/repositories/__init__.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/advanced_search_system/strategies/__init__.py +0 -0
- {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
- {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
- {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
- {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
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/advanced_search_system/tools/__init__.py +0 -0
- {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
- {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
- {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
- {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
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/api/__init__.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/app.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/config/__init__.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/config/config_files.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/config/llm_config.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/config/search_config.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/defaults/.env.template +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/defaults/__init__.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/defaults/local_collections.toml +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/defaults/main.toml +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/main.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/migrate_db.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/setup_data_dir.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/test_migration.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/utilities/__init__.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/utilities/db_utils.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/utilities/enums.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/utilities/llm_utils.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/utilities/search_utilities.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/utilities/setup_utils.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/__init__.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/app.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/app_factory.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/database/README.md +0 -0
- {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
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/database/migrations.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/database/models.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/database/schema_upgrade.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/models/database.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/models/settings.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/routes/api_routes.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/routes/history_routes.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/routes/research_routes.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/services/resource_service.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/services/settings_manager.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/services/settings_service.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/services/socket_service.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/static/css/custom_dropdown.css +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/static/css/settings.css +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/static/css/styles.css +0 -0
- {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
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/static/js/components/detail.js +0 -0
- {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
- {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
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/static/js/components/history.js +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/static/js/components/logpanel.js +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/static/js/components/progress.js +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/static/js/components/research.js +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/static/js/components/results.js +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/static/js/components/settings.js +0 -0
- {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
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/static/js/main.js +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/static/js/services/api.js +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/static/js/services/audio.js +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/static/js/services/formatting.js +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/static/js/services/pdf.js +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/static/js/services/socket.js +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/static/js/services/ui.js +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/static/sounds/README.md +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/static/sounds/error.mp3 +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/static/sounds/success.mp3 +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/templates/base.html +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/templates/components/custom_dropdown.html +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/templates/components/log_panel.html +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/templates/components/mobile_nav.html +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/templates/components/settings_form.html +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/templates/components/sidebar.html +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/templates/pages/details.html +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/templates/pages/history.html +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/templates/pages/progress.html +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/templates/pages/research.html +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/templates/pages/results.html +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/templates/settings_dashboard.html +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/utils/__init__.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web/utils/formatters.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web_search_engines/__init__.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/src/local_deep_research/web_search_engines/engines/__init__.py +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/tests/__init__.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/tests/download_stuff_for_local_test.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/tests/searxng/test_searxng_instance.py +0 -0
- {local_deep_research-0.2.0 → local_deep_research-0.2.3}/tests/searxng/test_searxng_integration.py +0 -0
- {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.
|
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.
|
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.
|
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.
|
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
|
+
}
|