local-deep-research 0.4.4__py3-none-any.whl → 0.5.0__py3-none-any.whl
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/__init__.py +7 -0
- local_deep_research/__version__.py +1 -1
- local_deep_research/advanced_search_system/answer_decoding/__init__.py +5 -0
- local_deep_research/advanced_search_system/answer_decoding/browsecomp_answer_decoder.py +421 -0
- local_deep_research/advanced_search_system/candidate_exploration/README.md +219 -0
- local_deep_research/advanced_search_system/candidate_exploration/__init__.py +25 -0
- local_deep_research/advanced_search_system/candidate_exploration/adaptive_explorer.py +329 -0
- local_deep_research/advanced_search_system/candidate_exploration/base_explorer.py +341 -0
- local_deep_research/advanced_search_system/candidate_exploration/constraint_guided_explorer.py +436 -0
- local_deep_research/advanced_search_system/candidate_exploration/diversity_explorer.py +457 -0
- local_deep_research/advanced_search_system/candidate_exploration/parallel_explorer.py +250 -0
- local_deep_research/advanced_search_system/candidate_exploration/progressive_explorer.py +255 -0
- local_deep_research/advanced_search_system/candidates/__init__.py +5 -0
- local_deep_research/advanced_search_system/candidates/base_candidate.py +59 -0
- local_deep_research/advanced_search_system/constraint_checking/README.md +150 -0
- local_deep_research/advanced_search_system/constraint_checking/__init__.py +35 -0
- local_deep_research/advanced_search_system/constraint_checking/base_constraint_checker.py +122 -0
- local_deep_research/advanced_search_system/constraint_checking/constraint_checker.py +223 -0
- local_deep_research/advanced_search_system/constraint_checking/constraint_satisfaction_tracker.py +387 -0
- local_deep_research/advanced_search_system/constraint_checking/dual_confidence_checker.py +424 -0
- local_deep_research/advanced_search_system/constraint_checking/evidence_analyzer.py +174 -0
- local_deep_research/advanced_search_system/constraint_checking/intelligent_constraint_relaxer.py +503 -0
- local_deep_research/advanced_search_system/constraint_checking/rejection_engine.py +143 -0
- local_deep_research/advanced_search_system/constraint_checking/strict_checker.py +259 -0
- local_deep_research/advanced_search_system/constraint_checking/threshold_checker.py +213 -0
- local_deep_research/advanced_search_system/constraints/__init__.py +6 -0
- local_deep_research/advanced_search_system/constraints/base_constraint.py +58 -0
- local_deep_research/advanced_search_system/constraints/constraint_analyzer.py +143 -0
- local_deep_research/advanced_search_system/evidence/__init__.py +12 -0
- local_deep_research/advanced_search_system/evidence/base_evidence.py +57 -0
- local_deep_research/advanced_search_system/evidence/evaluator.py +159 -0
- local_deep_research/advanced_search_system/evidence/requirements.py +122 -0
- local_deep_research/advanced_search_system/filters/base_filter.py +3 -1
- local_deep_research/advanced_search_system/filters/cross_engine_filter.py +8 -2
- local_deep_research/advanced_search_system/filters/journal_reputation_filter.py +43 -29
- local_deep_research/advanced_search_system/findings/repository.py +54 -17
- local_deep_research/advanced_search_system/knowledge/standard_knowledge.py +3 -1
- local_deep_research/advanced_search_system/query_generation/adaptive_query_generator.py +405 -0
- local_deep_research/advanced_search_system/questions/__init__.py +16 -0
- local_deep_research/advanced_search_system/questions/atomic_fact_question.py +171 -0
- local_deep_research/advanced_search_system/questions/browsecomp_question.py +287 -0
- local_deep_research/advanced_search_system/questions/decomposition_question.py +13 -4
- local_deep_research/advanced_search_system/questions/entity_aware_question.py +184 -0
- local_deep_research/advanced_search_system/questions/standard_question.py +9 -3
- local_deep_research/advanced_search_system/search_optimization/cross_constraint_manager.py +624 -0
- local_deep_research/advanced_search_system/source_management/diversity_manager.py +613 -0
- local_deep_research/advanced_search_system/strategies/__init__.py +42 -0
- local_deep_research/advanced_search_system/strategies/adaptive_decomposition_strategy.py +564 -0
- local_deep_research/advanced_search_system/strategies/base_strategy.py +4 -4
- local_deep_research/advanced_search_system/strategies/browsecomp_entity_strategy.py +1031 -0
- local_deep_research/advanced_search_system/strategies/browsecomp_optimized_strategy.py +778 -0
- local_deep_research/advanced_search_system/strategies/concurrent_dual_confidence_strategy.py +446 -0
- local_deep_research/advanced_search_system/strategies/constrained_search_strategy.py +1348 -0
- local_deep_research/advanced_search_system/strategies/constraint_parallel_strategy.py +522 -0
- local_deep_research/advanced_search_system/strategies/direct_search_strategy.py +217 -0
- local_deep_research/advanced_search_system/strategies/dual_confidence_strategy.py +320 -0
- local_deep_research/advanced_search_system/strategies/dual_confidence_with_rejection.py +219 -0
- local_deep_research/advanced_search_system/strategies/early_stop_constrained_strategy.py +369 -0
- local_deep_research/advanced_search_system/strategies/entity_aware_source_strategy.py +140 -0
- local_deep_research/advanced_search_system/strategies/evidence_based_strategy.py +1248 -0
- local_deep_research/advanced_search_system/strategies/evidence_based_strategy_v2.py +1337 -0
- local_deep_research/advanced_search_system/strategies/focused_iteration_strategy.py +537 -0
- local_deep_research/advanced_search_system/strategies/improved_evidence_based_strategy.py +782 -0
- local_deep_research/advanced_search_system/strategies/iterative_reasoning_strategy.py +760 -0
- local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +55 -21
- local_deep_research/advanced_search_system/strategies/llm_driven_modular_strategy.py +865 -0
- local_deep_research/advanced_search_system/strategies/modular_strategy.py +1142 -0
- local_deep_research/advanced_search_system/strategies/parallel_constrained_strategy.py +506 -0
- local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +34 -16
- local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +29 -9
- local_deep_research/advanced_search_system/strategies/recursive_decomposition_strategy.py +492 -0
- local_deep_research/advanced_search_system/strategies/smart_decomposition_strategy.py +284 -0
- local_deep_research/advanced_search_system/strategies/smart_query_strategy.py +515 -0
- local_deep_research/advanced_search_system/strategies/source_based_strategy.py +48 -24
- local_deep_research/advanced_search_system/strategies/standard_strategy.py +34 -14
- local_deep_research/advanced_search_system/tools/base_tool.py +7 -2
- local_deep_research/api/benchmark_functions.py +6 -2
- local_deep_research/api/research_functions.py +10 -4
- local_deep_research/benchmarks/__init__.py +9 -7
- local_deep_research/benchmarks/benchmark_functions.py +6 -2
- local_deep_research/benchmarks/cli/benchmark_commands.py +27 -10
- local_deep_research/benchmarks/cli.py +38 -13
- local_deep_research/benchmarks/comparison/__init__.py +4 -2
- local_deep_research/benchmarks/comparison/evaluator.py +316 -239
- local_deep_research/benchmarks/datasets/__init__.py +1 -1
- local_deep_research/benchmarks/datasets/base.py +91 -72
- local_deep_research/benchmarks/datasets/browsecomp.py +54 -33
- local_deep_research/benchmarks/datasets/custom_dataset_template.py +19 -19
- local_deep_research/benchmarks/datasets/simpleqa.py +14 -14
- local_deep_research/benchmarks/datasets/utils.py +48 -29
- local_deep_research/benchmarks/datasets.py +4 -11
- local_deep_research/benchmarks/efficiency/__init__.py +8 -4
- local_deep_research/benchmarks/efficiency/resource_monitor.py +223 -171
- local_deep_research/benchmarks/efficiency/speed_profiler.py +62 -48
- local_deep_research/benchmarks/evaluators/browsecomp.py +3 -1
- local_deep_research/benchmarks/evaluators/composite.py +6 -2
- local_deep_research/benchmarks/evaluators/simpleqa.py +36 -13
- local_deep_research/benchmarks/graders.py +32 -10
- local_deep_research/benchmarks/metrics/README.md +1 -1
- local_deep_research/benchmarks/metrics/calculation.py +25 -10
- local_deep_research/benchmarks/metrics/reporting.py +7 -3
- local_deep_research/benchmarks/metrics/visualization.py +42 -23
- local_deep_research/benchmarks/metrics.py +1 -1
- local_deep_research/benchmarks/optimization/__init__.py +3 -1
- local_deep_research/benchmarks/optimization/api.py +7 -1
- local_deep_research/benchmarks/optimization/optuna_optimizer.py +75 -26
- local_deep_research/benchmarks/runners.py +48 -15
- local_deep_research/citation_handler.py +65 -92
- local_deep_research/citation_handlers/__init__.py +15 -0
- local_deep_research/citation_handlers/base_citation_handler.py +70 -0
- local_deep_research/citation_handlers/forced_answer_citation_handler.py +179 -0
- local_deep_research/citation_handlers/precision_extraction_handler.py +550 -0
- local_deep_research/citation_handlers/standard_citation_handler.py +80 -0
- local_deep_research/config/llm_config.py +271 -169
- local_deep_research/config/search_config.py +14 -5
- local_deep_research/defaults/__init__.py +0 -1
- local_deep_research/metrics/__init__.py +13 -0
- local_deep_research/metrics/database.py +58 -0
- local_deep_research/metrics/db_models.py +115 -0
- local_deep_research/metrics/migrate_add_provider_to_token_usage.py +148 -0
- local_deep_research/metrics/migrate_call_stack_tracking.py +105 -0
- local_deep_research/metrics/migrate_enhanced_tracking.py +75 -0
- local_deep_research/metrics/migrate_research_ratings.py +31 -0
- local_deep_research/metrics/models.py +61 -0
- local_deep_research/metrics/pricing/__init__.py +12 -0
- local_deep_research/metrics/pricing/cost_calculator.py +237 -0
- local_deep_research/metrics/pricing/pricing_cache.py +143 -0
- local_deep_research/metrics/pricing/pricing_fetcher.py +240 -0
- local_deep_research/metrics/query_utils.py +51 -0
- local_deep_research/metrics/search_tracker.py +380 -0
- local_deep_research/metrics/token_counter.py +1078 -0
- local_deep_research/migrate_db.py +3 -1
- local_deep_research/report_generator.py +22 -8
- local_deep_research/search_system.py +390 -9
- local_deep_research/test_migration.py +15 -5
- local_deep_research/utilities/db_utils.py +7 -4
- local_deep_research/utilities/es_utils.py +115 -104
- local_deep_research/utilities/llm_utils.py +15 -5
- local_deep_research/utilities/log_utils.py +151 -0
- local_deep_research/utilities/search_cache.py +387 -0
- local_deep_research/utilities/search_utilities.py +14 -6
- local_deep_research/utilities/threading_utils.py +92 -0
- local_deep_research/utilities/url_utils.py +6 -0
- local_deep_research/web/api.py +347 -0
- local_deep_research/web/app.py +13 -17
- local_deep_research/web/app_factory.py +71 -66
- local_deep_research/web/database/migrate_to_ldr_db.py +12 -4
- local_deep_research/web/database/migrations.py +5 -3
- local_deep_research/web/database/models.py +51 -2
- local_deep_research/web/database/schema_upgrade.py +49 -29
- local_deep_research/web/models/database.py +51 -61
- local_deep_research/web/routes/api_routes.py +56 -22
- local_deep_research/web/routes/benchmark_routes.py +4 -1
- local_deep_research/web/routes/globals.py +22 -0
- local_deep_research/web/routes/history_routes.py +71 -46
- local_deep_research/web/routes/metrics_routes.py +1155 -0
- local_deep_research/web/routes/research_routes.py +227 -41
- local_deep_research/web/routes/settings_routes.py +156 -55
- local_deep_research/web/services/research_service.py +310 -103
- local_deep_research/web/services/resource_service.py +36 -11
- local_deep_research/web/services/settings_manager.py +55 -17
- local_deep_research/web/services/settings_service.py +12 -4
- local_deep_research/web/services/socket_service.py +295 -188
- local_deep_research/web/static/css/custom_dropdown.css +180 -0
- local_deep_research/web/static/css/styles.css +39 -1
- local_deep_research/web/static/js/components/detail.js +633 -267
- local_deep_research/web/static/js/components/details.js +751 -0
- local_deep_research/web/static/js/components/fallback/formatting.js +11 -11
- local_deep_research/web/static/js/components/fallback/ui.js +23 -23
- local_deep_research/web/static/js/components/history.js +76 -76
- local_deep_research/web/static/js/components/logpanel.js +61 -13
- local_deep_research/web/static/js/components/progress.js +13 -2
- local_deep_research/web/static/js/components/research.js +99 -12
- local_deep_research/web/static/js/components/results.js +239 -106
- local_deep_research/web/static/js/main.js +40 -40
- local_deep_research/web/static/js/services/audio.js +1 -1
- local_deep_research/web/static/js/services/formatting.js +11 -11
- local_deep_research/web/static/js/services/keyboard.js +157 -0
- local_deep_research/web/static/js/services/pdf.js +80 -80
- local_deep_research/web/static/sounds/README.md +1 -1
- local_deep_research/web/templates/base.html +1 -0
- local_deep_research/web/templates/components/log_panel.html +7 -1
- local_deep_research/web/templates/components/mobile_nav.html +1 -1
- local_deep_research/web/templates/components/sidebar.html +3 -0
- local_deep_research/web/templates/pages/cost_analytics.html +1245 -0
- local_deep_research/web/templates/pages/details.html +325 -24
- local_deep_research/web/templates/pages/history.html +1 -1
- local_deep_research/web/templates/pages/metrics.html +1929 -0
- local_deep_research/web/templates/pages/progress.html +2 -2
- local_deep_research/web/templates/pages/research.html +53 -17
- local_deep_research/web/templates/pages/results.html +12 -1
- local_deep_research/web/templates/pages/star_reviews.html +803 -0
- local_deep_research/web/utils/formatters.py +9 -3
- local_deep_research/web_search_engines/default_search_engines.py +5 -3
- local_deep_research/web_search_engines/engines/full_search.py +8 -2
- local_deep_research/web_search_engines/engines/meta_search_engine.py +59 -20
- local_deep_research/web_search_engines/engines/search_engine_arxiv.py +19 -6
- local_deep_research/web_search_engines/engines/search_engine_brave.py +6 -2
- local_deep_research/web_search_engines/engines/search_engine_ddg.py +3 -1
- local_deep_research/web_search_engines/engines/search_engine_elasticsearch.py +81 -58
- local_deep_research/web_search_engines/engines/search_engine_github.py +46 -15
- local_deep_research/web_search_engines/engines/search_engine_google_pse.py +16 -6
- local_deep_research/web_search_engines/engines/search_engine_guardian.py +39 -15
- local_deep_research/web_search_engines/engines/search_engine_local.py +58 -25
- local_deep_research/web_search_engines/engines/search_engine_local_all.py +15 -5
- local_deep_research/web_search_engines/engines/search_engine_pubmed.py +63 -21
- local_deep_research/web_search_engines/engines/search_engine_searxng.py +37 -11
- local_deep_research/web_search_engines/engines/search_engine_semantic_scholar.py +27 -9
- local_deep_research/web_search_engines/engines/search_engine_serpapi.py +12 -4
- local_deep_research/web_search_engines/engines/search_engine_wayback.py +31 -10
- local_deep_research/web_search_engines/engines/search_engine_wikipedia.py +12 -3
- local_deep_research/web_search_engines/search_engine_base.py +83 -35
- local_deep_research/web_search_engines/search_engine_factory.py +25 -8
- local_deep_research/web_search_engines/search_engines_config.py +9 -3
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.0.dist-info}/METADATA +7 -1
- local_deep_research-0.5.0.dist-info/RECORD +265 -0
- local_deep_research-0.4.4.dist-info/RECORD +0 -177
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.0.dist-info}/WHEEL +0 -0
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.0.dist-info}/entry_points.txt +0 -0
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,564 @@
|
|
1
|
+
"""
|
2
|
+
Adaptive Decomposition Strategy for step-by-step query analysis.
|
3
|
+
|
4
|
+
This strategy dynamically adapts its approach based on intermediate findings,
|
5
|
+
making decisions at each step rather than decomposing everything upfront.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from dataclasses import dataclass
|
9
|
+
from enum import Enum
|
10
|
+
from typing import Any, Dict, List, Optional
|
11
|
+
|
12
|
+
from langchain_core.language_models import BaseChatModel
|
13
|
+
from loguru import logger
|
14
|
+
|
15
|
+
from ...utilities.search_utilities import format_findings, remove_think_tags
|
16
|
+
from ..findings.repository import FindingsRepository
|
17
|
+
from ..questions.standard_question import StandardQuestionGenerator
|
18
|
+
from .base_strategy import BaseSearchStrategy
|
19
|
+
from .source_based_strategy import SourceBasedSearchStrategy
|
20
|
+
|
21
|
+
|
22
|
+
class StepType(Enum):
|
23
|
+
"""Types of steps in the adaptive process."""
|
24
|
+
|
25
|
+
CONSTRAINT_EXTRACTION = "constraint_extraction"
|
26
|
+
INITIAL_SEARCH = "initial_search"
|
27
|
+
VERIFICATION = "verification"
|
28
|
+
REFINEMENT = "refinement"
|
29
|
+
SYNTHESIS = "synthesis"
|
30
|
+
|
31
|
+
|
32
|
+
@dataclass
|
33
|
+
class StepResult:
|
34
|
+
"""Result from a single adaptive step."""
|
35
|
+
|
36
|
+
step_type: StepType
|
37
|
+
description: str
|
38
|
+
findings: Dict
|
39
|
+
next_action: Optional[str] = None
|
40
|
+
confidence: float = 0.0
|
41
|
+
|
42
|
+
|
43
|
+
class AdaptiveDecompositionStrategy(BaseSearchStrategy):
|
44
|
+
"""
|
45
|
+
A strategy that adapts its decomposition based on intermediate findings.
|
46
|
+
|
47
|
+
Instead of decomposing everything upfront, it takes a step-by-step approach,
|
48
|
+
using each step's findings to inform the next action.
|
49
|
+
"""
|
50
|
+
|
51
|
+
def __init__(
|
52
|
+
self,
|
53
|
+
model: BaseChatModel,
|
54
|
+
search: Any,
|
55
|
+
all_links_of_system: List[str],
|
56
|
+
max_steps: int = 15,
|
57
|
+
min_confidence: float = 0.8,
|
58
|
+
source_search_iterations: int = 2,
|
59
|
+
source_questions_per_iteration: int = 20,
|
60
|
+
):
|
61
|
+
"""Initialize the adaptive decomposition strategy.
|
62
|
+
|
63
|
+
Args:
|
64
|
+
model: The language model to use
|
65
|
+
search: The search engine instance
|
66
|
+
all_links_of_system: List to store all encountered links
|
67
|
+
max_steps: Maximum steps to prevent infinite loops
|
68
|
+
min_confidence: Minimum confidence to consider answer complete
|
69
|
+
source_search_iterations: Iterations for source-based searches
|
70
|
+
source_questions_per_iteration: Questions per iteration for source-based searches
|
71
|
+
"""
|
72
|
+
super().__init__(all_links_of_system)
|
73
|
+
self.model = model
|
74
|
+
self.search = search
|
75
|
+
self.max_steps = max_steps
|
76
|
+
self.min_confidence = min_confidence
|
77
|
+
self.source_search_iterations = source_search_iterations
|
78
|
+
self.source_questions_per_iteration = source_questions_per_iteration
|
79
|
+
self.question_generator = StandardQuestionGenerator(model)
|
80
|
+
self.findings_repository = FindingsRepository(model)
|
81
|
+
self.step_results: List[StepResult] = []
|
82
|
+
self.working_knowledge = {}
|
83
|
+
|
84
|
+
def analyze_topic(self, query: str) -> Dict:
|
85
|
+
"""Analyze a topic using adaptive decomposition.
|
86
|
+
|
87
|
+
Args:
|
88
|
+
query: The research query to analyze
|
89
|
+
|
90
|
+
Returns:
|
91
|
+
Dictionary containing analysis results
|
92
|
+
"""
|
93
|
+
self.all_links_of_system.clear()
|
94
|
+
self.questions_by_iteration = []
|
95
|
+
self.findings = []
|
96
|
+
self.step_results = []
|
97
|
+
self.working_knowledge = {
|
98
|
+
"original_query": query,
|
99
|
+
"constraints": [],
|
100
|
+
"candidates": [],
|
101
|
+
"verified_facts": [],
|
102
|
+
"uncertainties": [],
|
103
|
+
}
|
104
|
+
|
105
|
+
# Progress callback for UI
|
106
|
+
if self.progress_callback:
|
107
|
+
self.progress_callback(
|
108
|
+
"Starting adaptive analysis",
|
109
|
+
1,
|
110
|
+
{"phase": "init", "strategy": "adaptive_decomposition"},
|
111
|
+
)
|
112
|
+
|
113
|
+
logger.info(f"Starting adaptive analysis for: {query}")
|
114
|
+
|
115
|
+
# Start with constraint extraction
|
116
|
+
current_step = 0
|
117
|
+
while current_step < self.max_steps:
|
118
|
+
# Decide next step based on current knowledge
|
119
|
+
next_step = self._decide_next_step(query, current_step)
|
120
|
+
|
121
|
+
if next_step is None:
|
122
|
+
break
|
123
|
+
|
124
|
+
logger.info(f"Step {current_step + 1}: {next_step.step_type.value}")
|
125
|
+
|
126
|
+
# Update progress
|
127
|
+
if self.progress_callback:
|
128
|
+
self.progress_callback(
|
129
|
+
f"Step {current_step + 1}: {next_step.step_type.value}",
|
130
|
+
int((current_step / self.max_steps) * 80) + 10,
|
131
|
+
{
|
132
|
+
"phase": "adaptive_step",
|
133
|
+
"step": current_step + 1,
|
134
|
+
"step_type": next_step.step_type.value,
|
135
|
+
},
|
136
|
+
)
|
137
|
+
|
138
|
+
# Execute the step
|
139
|
+
step_result = self._execute_step(next_step, query)
|
140
|
+
self.step_results.append(step_result)
|
141
|
+
|
142
|
+
# Check if we have a confident answer
|
143
|
+
if step_result.confidence >= self.min_confidence:
|
144
|
+
logger.info(
|
145
|
+
f"Confident answer found (confidence: {step_result.confidence})"
|
146
|
+
)
|
147
|
+
break
|
148
|
+
|
149
|
+
current_step += 1
|
150
|
+
|
151
|
+
# Final synthesis of all findings
|
152
|
+
final_result = self._synthesize_findings(query)
|
153
|
+
|
154
|
+
# Update progress
|
155
|
+
if self.progress_callback:
|
156
|
+
self.progress_callback(
|
157
|
+
"Analysis complete",
|
158
|
+
100,
|
159
|
+
{"phase": "complete", "strategy": "adaptive_decomposition"},
|
160
|
+
)
|
161
|
+
|
162
|
+
return final_result
|
163
|
+
|
164
|
+
def _decide_next_step(
|
165
|
+
self, query: str, step_count: int
|
166
|
+
) -> Optional[StepResult]:
|
167
|
+
"""Decide the next step based on current knowledge.
|
168
|
+
|
169
|
+
Args:
|
170
|
+
query: The original query
|
171
|
+
step_count: Current step number
|
172
|
+
|
173
|
+
Returns:
|
174
|
+
Next step to execute, or None if complete
|
175
|
+
"""
|
176
|
+
# Format current knowledge for analysis
|
177
|
+
knowledge_summary = f"""
|
178
|
+
Original Query: {query}
|
179
|
+
|
180
|
+
Current Knowledge:
|
181
|
+
- Extracted Constraints: {self.working_knowledge.get("constraints", [])}
|
182
|
+
- Candidate Locations: {self.working_knowledge.get("candidates", [])}
|
183
|
+
- Verified Facts: {self.working_knowledge.get("verified_facts", [])}
|
184
|
+
- Uncertainties: {self.working_knowledge.get("uncertainties", [])}
|
185
|
+
|
186
|
+
Previous Steps: {[s.step_type.value for s in self.step_results]}
|
187
|
+
"""
|
188
|
+
|
189
|
+
prompt = f"""Based on the current knowledge, decide the next best action.
|
190
|
+
|
191
|
+
{knowledge_summary}
|
192
|
+
|
193
|
+
What should be the next step? Options:
|
194
|
+
1. CONSTRAINT_EXTRACTION - Extract specific constraints from the query
|
195
|
+
2. INITIAL_SEARCH - Perform broad search for candidates
|
196
|
+
3. VERIFICATION - Verify specific facts about candidates
|
197
|
+
4. REFINEMENT - Refine search based on new information
|
198
|
+
5. SYNTHESIS - We have enough information to answer
|
199
|
+
|
200
|
+
Respond with:
|
201
|
+
NEXT_STEP: [step type]
|
202
|
+
DESCRIPTION: [what specifically to do]
|
203
|
+
REASONING: [why this is the best next step]
|
204
|
+
CONFIDENCE: [0-1 score of how confident we are in the current answer]
|
205
|
+
"""
|
206
|
+
|
207
|
+
response = self.model.invoke(prompt)
|
208
|
+
content = remove_think_tags(response.content)
|
209
|
+
|
210
|
+
# Parse response
|
211
|
+
lines = content.strip().split("\n")
|
212
|
+
next_step_type = None
|
213
|
+
description = ""
|
214
|
+
confidence = 0.0
|
215
|
+
|
216
|
+
for line in lines:
|
217
|
+
if line.startswith("NEXT_STEP:"):
|
218
|
+
step_str = line.split(":", 1)[1].strip()
|
219
|
+
try:
|
220
|
+
next_step_type = StepType[step_str]
|
221
|
+
except KeyError:
|
222
|
+
# Try to match partial
|
223
|
+
for step_type in StepType:
|
224
|
+
if step_type.value in step_str.lower():
|
225
|
+
next_step_type = step_type
|
226
|
+
break
|
227
|
+
elif line.startswith("DESCRIPTION:"):
|
228
|
+
description = line.split(":", 1)[1].strip()
|
229
|
+
elif line.startswith("CONFIDENCE:"):
|
230
|
+
try:
|
231
|
+
confidence = float(line.split(":", 1)[1].strip())
|
232
|
+
except ValueError:
|
233
|
+
confidence = 0.0
|
234
|
+
|
235
|
+
if next_step_type is None:
|
236
|
+
return None
|
237
|
+
|
238
|
+
return StepResult(
|
239
|
+
step_type=next_step_type,
|
240
|
+
description=description,
|
241
|
+
findings={},
|
242
|
+
confidence=confidence,
|
243
|
+
)
|
244
|
+
|
245
|
+
def _execute_step(self, step: StepResult, query: str) -> StepResult:
|
246
|
+
"""Execute a specific step in the adaptive process.
|
247
|
+
|
248
|
+
Args:
|
249
|
+
step: The step to execute
|
250
|
+
query: The original query
|
251
|
+
|
252
|
+
Returns:
|
253
|
+
Updated step result with findings
|
254
|
+
"""
|
255
|
+
if step.step_type == StepType.CONSTRAINT_EXTRACTION:
|
256
|
+
return self._extract_constraints(query)
|
257
|
+
elif step.step_type == StepType.INITIAL_SEARCH:
|
258
|
+
return self._perform_initial_search(step.description)
|
259
|
+
elif step.step_type == StepType.VERIFICATION:
|
260
|
+
return self._verify_candidates(step.description)
|
261
|
+
elif step.step_type == StepType.REFINEMENT:
|
262
|
+
return self._refine_search(step.description)
|
263
|
+
elif step.step_type == StepType.SYNTHESIS:
|
264
|
+
return self._synthesize_current_knowledge(query)
|
265
|
+
else:
|
266
|
+
return step
|
267
|
+
|
268
|
+
def _extract_constraints(self, query: str) -> StepResult:
|
269
|
+
"""Extract specific constraints and clues from the query.
|
270
|
+
|
271
|
+
Args:
|
272
|
+
query: The original query
|
273
|
+
|
274
|
+
Returns:
|
275
|
+
Step result with extracted constraints
|
276
|
+
"""
|
277
|
+
prompt = f"""Extract all specific constraints and clues from this query:
|
278
|
+
|
279
|
+
Query: {query}
|
280
|
+
|
281
|
+
List each constraint/clue separately and explain why it's important:
|
282
|
+
"""
|
283
|
+
|
284
|
+
response = self.model.invoke(prompt)
|
285
|
+
content = remove_think_tags(response.content)
|
286
|
+
|
287
|
+
# Parse constraints
|
288
|
+
constraints = []
|
289
|
+
lines = content.strip().split("\n")
|
290
|
+
for line in lines:
|
291
|
+
if line.strip() and (line[0].isdigit() or line.startswith("-")):
|
292
|
+
constraints.append(line.strip())
|
293
|
+
|
294
|
+
self.working_knowledge["constraints"] = constraints
|
295
|
+
|
296
|
+
return StepResult(
|
297
|
+
step_type=StepType.CONSTRAINT_EXTRACTION,
|
298
|
+
description="Extract constraints from query",
|
299
|
+
findings={"constraints": constraints},
|
300
|
+
confidence=0.0,
|
301
|
+
)
|
302
|
+
|
303
|
+
def _perform_initial_search(self, description: str) -> StepResult:
|
304
|
+
"""Perform initial search based on extracted constraints.
|
305
|
+
|
306
|
+
Args:
|
307
|
+
description: Search description
|
308
|
+
|
309
|
+
Returns:
|
310
|
+
Step result with search findings
|
311
|
+
"""
|
312
|
+
# Use source-based strategy for the search
|
313
|
+
source_strategy = SourceBasedSearchStrategy(
|
314
|
+
model=self.model,
|
315
|
+
search=self.search,
|
316
|
+
all_links_of_system=self.all_links_of_system,
|
317
|
+
include_text_content=True,
|
318
|
+
use_cross_engine_filter=True,
|
319
|
+
use_atomic_facts=True,
|
320
|
+
)
|
321
|
+
|
322
|
+
source_strategy.max_iterations = 1 # Quick initial search
|
323
|
+
source_strategy.questions_per_iteration = 10
|
324
|
+
|
325
|
+
if self.progress_callback:
|
326
|
+
source_strategy.set_progress_callback(self.progress_callback)
|
327
|
+
|
328
|
+
results = source_strategy.analyze_topic(description)
|
329
|
+
|
330
|
+
# Extract candidate locations from results
|
331
|
+
candidates = self._extract_candidates_from_results(results)
|
332
|
+
self.working_knowledge["candidates"] = candidates
|
333
|
+
|
334
|
+
return StepResult(
|
335
|
+
step_type=StepType.INITIAL_SEARCH,
|
336
|
+
description=description,
|
337
|
+
findings={"candidates": candidates, "raw_results": results},
|
338
|
+
confidence=0.2,
|
339
|
+
)
|
340
|
+
|
341
|
+
def _verify_candidates(self, description: str) -> StepResult:
|
342
|
+
"""Verify specific facts about candidate locations.
|
343
|
+
|
344
|
+
Args:
|
345
|
+
description: Verification description
|
346
|
+
|
347
|
+
Returns:
|
348
|
+
Step result with verification findings
|
349
|
+
"""
|
350
|
+
# Perform targeted search for verification
|
351
|
+
source_strategy = SourceBasedSearchStrategy(
|
352
|
+
model=self.model,
|
353
|
+
search=self.search,
|
354
|
+
all_links_of_system=self.all_links_of_system,
|
355
|
+
include_text_content=True,
|
356
|
+
use_cross_engine_filter=True,
|
357
|
+
use_atomic_facts=True,
|
358
|
+
)
|
359
|
+
|
360
|
+
source_strategy.max_iterations = 1
|
361
|
+
source_strategy.questions_per_iteration = 15
|
362
|
+
|
363
|
+
if self.progress_callback:
|
364
|
+
source_strategy.set_progress_callback(self.progress_callback)
|
365
|
+
|
366
|
+
results = source_strategy.analyze_topic(description)
|
367
|
+
|
368
|
+
# Update verified facts
|
369
|
+
verified_facts = self._extract_verified_facts(results)
|
370
|
+
self.working_knowledge["verified_facts"].extend(verified_facts)
|
371
|
+
|
372
|
+
# Calculate confidence based on verification
|
373
|
+
confidence = self._calculate_confidence()
|
374
|
+
|
375
|
+
return StepResult(
|
376
|
+
step_type=StepType.VERIFICATION,
|
377
|
+
description=description,
|
378
|
+
findings={"verified_facts": verified_facts},
|
379
|
+
confidence=confidence,
|
380
|
+
)
|
381
|
+
|
382
|
+
def _refine_search(self, description: str) -> StepResult:
|
383
|
+
"""Refine search based on accumulated knowledge.
|
384
|
+
|
385
|
+
Args:
|
386
|
+
description: Refinement description
|
387
|
+
|
388
|
+
Returns:
|
389
|
+
Step result with refined findings
|
390
|
+
"""
|
391
|
+
# Similar to verification but with more targeted approach
|
392
|
+
return self._verify_candidates(description)
|
393
|
+
|
394
|
+
def _synthesize_current_knowledge(self, query: str) -> StepResult:
|
395
|
+
"""Synthesize current knowledge into an answer.
|
396
|
+
|
397
|
+
Args:
|
398
|
+
query: The original query
|
399
|
+
|
400
|
+
Returns:
|
401
|
+
Step result with synthesized answer
|
402
|
+
"""
|
403
|
+
knowledge_summary = f"""
|
404
|
+
Original Query: {query}
|
405
|
+
|
406
|
+
Constraints: {self.working_knowledge["constraints"]}
|
407
|
+
Candidates: {self.working_knowledge["candidates"]}
|
408
|
+
Verified Facts: {self.working_knowledge["verified_facts"]}
|
409
|
+
"""
|
410
|
+
|
411
|
+
prompt = f"""Based on all the information gathered, provide the answer:
|
412
|
+
|
413
|
+
{knowledge_summary}
|
414
|
+
|
415
|
+
Provide:
|
416
|
+
1. The specific answer to the query
|
417
|
+
2. Supporting evidence for the answer
|
418
|
+
3. Confidence level (0-1)
|
419
|
+
"""
|
420
|
+
|
421
|
+
response = self.model.invoke(prompt)
|
422
|
+
content = remove_think_tags(response.content)
|
423
|
+
|
424
|
+
return StepResult(
|
425
|
+
step_type=StepType.SYNTHESIS,
|
426
|
+
description="Final synthesis",
|
427
|
+
findings={"answer": content},
|
428
|
+
confidence=0.9,
|
429
|
+
)
|
430
|
+
|
431
|
+
def _synthesize_findings(self, query: str) -> Dict:
|
432
|
+
"""Synthesize all findings into final result.
|
433
|
+
|
434
|
+
Args:
|
435
|
+
query: The original query
|
436
|
+
|
437
|
+
Returns:
|
438
|
+
Final results dictionary
|
439
|
+
"""
|
440
|
+
# Compile all findings
|
441
|
+
all_findings = []
|
442
|
+
all_links = []
|
443
|
+
all_questions = []
|
444
|
+
|
445
|
+
for step in self.step_results:
|
446
|
+
finding = {
|
447
|
+
"phase": f"{step.step_type.value} (Step {self.step_results.index(step) + 1})",
|
448
|
+
"content": step.description,
|
449
|
+
"findings": step.findings,
|
450
|
+
"confidence": step.confidence,
|
451
|
+
"timestamp": self._get_timestamp(),
|
452
|
+
}
|
453
|
+
all_findings.append(finding)
|
454
|
+
|
455
|
+
# Extract links and questions from raw results if available
|
456
|
+
if "raw_results" in step.findings:
|
457
|
+
raw = step.findings["raw_results"]
|
458
|
+
if "all_links_of_system" in raw:
|
459
|
+
all_links.extend(raw["all_links_of_system"])
|
460
|
+
if "questions_by_iteration" in raw:
|
461
|
+
all_questions.extend(
|
462
|
+
raw.get("questions_by_iteration", {}).values()
|
463
|
+
)
|
464
|
+
|
465
|
+
# Get final answer
|
466
|
+
final_answer = "No confident answer found."
|
467
|
+
for step in reversed(self.step_results):
|
468
|
+
if (
|
469
|
+
step.step_type == StepType.SYNTHESIS
|
470
|
+
and "answer" in step.findings
|
471
|
+
):
|
472
|
+
final_answer = step.findings["answer"]
|
473
|
+
break
|
474
|
+
|
475
|
+
# Format questions dictionary
|
476
|
+
questions_dict = {}
|
477
|
+
for i, questions in enumerate(all_questions):
|
478
|
+
if isinstance(questions, list):
|
479
|
+
questions_dict[i + 1] = questions
|
480
|
+
|
481
|
+
formatted_findings = format_findings(
|
482
|
+
all_findings, final_answer, questions_dict
|
483
|
+
)
|
484
|
+
|
485
|
+
return {
|
486
|
+
"current_knowledge": final_answer,
|
487
|
+
"formatted_findings": formatted_findings,
|
488
|
+
"findings": all_findings,
|
489
|
+
"iterations": len(self.step_results),
|
490
|
+
"questions_by_iteration": questions_dict,
|
491
|
+
"all_links_of_system": all_links,
|
492
|
+
"sources": all_links,
|
493
|
+
"step_results": [step.__dict__ for step in self.step_results],
|
494
|
+
"strategy": "adaptive_decomposition",
|
495
|
+
"working_knowledge": self.working_knowledge,
|
496
|
+
"questions": {
|
497
|
+
"total": sum(len(q) for q in questions_dict.values()),
|
498
|
+
"by_iteration": questions_dict,
|
499
|
+
},
|
500
|
+
}
|
501
|
+
|
502
|
+
def _extract_candidates_from_results(self, results: Dict) -> List[str]:
|
503
|
+
"""Extract candidate locations from search results."""
|
504
|
+
candidates = []
|
505
|
+
if "current_knowledge" in results:
|
506
|
+
# Use LLM to extract location names
|
507
|
+
prompt = f"""Extract any location names mentioned in this text:
|
508
|
+
|
509
|
+
{results["current_knowledge"][:1000]}
|
510
|
+
|
511
|
+
List only location names, one per line:"""
|
512
|
+
|
513
|
+
response = self.model.invoke(prompt)
|
514
|
+
content = remove_think_tags(response.content)
|
515
|
+
|
516
|
+
for line in content.strip().split("\n"):
|
517
|
+
if line.strip():
|
518
|
+
candidates.append(line.strip())
|
519
|
+
|
520
|
+
return candidates
|
521
|
+
|
522
|
+
def _extract_verified_facts(self, results: Dict) -> List[str]:
|
523
|
+
"""Extract verified facts from search results."""
|
524
|
+
facts = []
|
525
|
+
if "current_knowledge" in results:
|
526
|
+
# Use LLM to extract verified facts
|
527
|
+
prompt = f"""Extract specific verified facts from this text:
|
528
|
+
|
529
|
+
{results["current_knowledge"][:1000]}
|
530
|
+
|
531
|
+
List only confirmed facts, one per line:"""
|
532
|
+
|
533
|
+
response = self.model.invoke(prompt)
|
534
|
+
content = remove_think_tags(response.content)
|
535
|
+
|
536
|
+
for line in content.strip().split("\n"):
|
537
|
+
if line.strip():
|
538
|
+
facts.append(line.strip())
|
539
|
+
|
540
|
+
return facts
|
541
|
+
|
542
|
+
def _calculate_confidence(self) -> float:
|
543
|
+
"""Calculate confidence based on verified facts vs constraints."""
|
544
|
+
if not self.working_knowledge["constraints"]:
|
545
|
+
return 0.0
|
546
|
+
|
547
|
+
verified_count = len(self.working_knowledge["verified_facts"])
|
548
|
+
constraint_count = len(self.working_knowledge["constraints"])
|
549
|
+
|
550
|
+
# Simple ratio with some adjustments
|
551
|
+
base_confidence = verified_count / constraint_count
|
552
|
+
|
553
|
+
# Boost if we have specific candidates
|
554
|
+
if self.working_knowledge["candidates"]:
|
555
|
+
base_confidence += 0.1
|
556
|
+
|
557
|
+
# Cap at 0.95 to leave room for synthesis
|
558
|
+
return min(base_confidence, 0.95)
|
559
|
+
|
560
|
+
def _get_timestamp(self) -> str:
|
561
|
+
"""Get current timestamp for findings."""
|
562
|
+
from datetime import datetime
|
563
|
+
|
564
|
+
return datetime.utcnow().isoformat()
|
@@ -21,7 +21,9 @@ class BaseSearchStrategy(ABC):
|
|
21
21
|
all_links_of_system if all_links_of_system is not None else []
|
22
22
|
)
|
23
23
|
|
24
|
-
def set_progress_callback(
|
24
|
+
def set_progress_callback(
|
25
|
+
self, callback: Callable[[str, int, dict], None]
|
26
|
+
) -> None:
|
25
27
|
"""Set a callback function to receive progress updates."""
|
26
28
|
self.progress_callback = callback
|
27
29
|
|
@@ -62,9 +64,7 @@ class BaseSearchStrategy(ABC):
|
|
62
64
|
bool: True if search engine is available, False otherwise
|
63
65
|
"""
|
64
66
|
if not hasattr(self, "search") or self.search is None:
|
65
|
-
error_msg =
|
66
|
-
"Error: No search engine available. Please check your configuration."
|
67
|
-
)
|
67
|
+
error_msg = "Error: No search engine available. Please check your configuration."
|
68
68
|
self._update_progress(
|
69
69
|
error_msg,
|
70
70
|
100,
|