local-deep-research 0.4.4__py3-none-any.whl → 0.5.2__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 +20 -3
- local_deep_research/web/database/models.py +74 -25
- local_deep_research/web/database/schema_upgrade.py +49 -29
- local_deep_research/web/models/database.py +63 -83
- 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 +192 -54
- local_deep_research/web/routes/settings_routes.py +156 -55
- local_deep_research/web/services/research_service.py +412 -251
- 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.2.dist-info}/METADATA +7 -1
- local_deep_research-0.5.2.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.2.dist-info}/WHEEL +0 -0
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/entry_points.txt +0 -0
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,217 @@
|
|
1
|
+
"""
|
2
|
+
Direct search strategy for focused single-query searches.
|
3
|
+
|
4
|
+
This strategy is optimized for:
|
5
|
+
1. Entity identification queries (who, what, which)
|
6
|
+
2. Single-pass searches without iteration
|
7
|
+
3. Minimal LLM calls for efficiency
|
8
|
+
"""
|
9
|
+
|
10
|
+
import logging
|
11
|
+
from typing import Dict
|
12
|
+
|
13
|
+
from ...citation_handler import CitationHandler
|
14
|
+
from ...config.llm_config import get_llm
|
15
|
+
from ...config.search_config import get_search
|
16
|
+
from ..filters.cross_engine_filter import CrossEngineFilter
|
17
|
+
from ..findings.repository import FindingsRepository
|
18
|
+
from .base_strategy import BaseSearchStrategy
|
19
|
+
|
20
|
+
logger = logging.getLogger(__name__)
|
21
|
+
|
22
|
+
|
23
|
+
class DirectSearchStrategy(BaseSearchStrategy):
|
24
|
+
"""
|
25
|
+
Direct search strategy that performs a single focused search and synthesis.
|
26
|
+
|
27
|
+
Key features:
|
28
|
+
1. No question generation - uses the original query directly
|
29
|
+
2. Single search pass - no iterations
|
30
|
+
3. Minimal LLM calls - only for final synthesis
|
31
|
+
4. Optimized for entity identification and focused queries
|
32
|
+
"""
|
33
|
+
|
34
|
+
def __init__(
|
35
|
+
self,
|
36
|
+
search=None,
|
37
|
+
model=None,
|
38
|
+
citation_handler=None,
|
39
|
+
include_text_content: bool = True,
|
40
|
+
use_cross_engine_filter: bool = True,
|
41
|
+
filter_reorder: bool = True,
|
42
|
+
filter_reindex: bool = True,
|
43
|
+
cross_engine_max_results: int = None,
|
44
|
+
all_links_of_system=None,
|
45
|
+
):
|
46
|
+
"""Initialize with minimal components for efficiency."""
|
47
|
+
super().__init__(all_links_of_system=all_links_of_system)
|
48
|
+
self.search = search or get_search()
|
49
|
+
self.model = model or get_llm()
|
50
|
+
self.progress_callback = None
|
51
|
+
|
52
|
+
self.include_text_content = include_text_content
|
53
|
+
self.use_cross_engine_filter = use_cross_engine_filter
|
54
|
+
self.filter_reorder = filter_reorder
|
55
|
+
self.filter_reindex = filter_reindex
|
56
|
+
|
57
|
+
# Initialize the cross-engine filter
|
58
|
+
self.cross_engine_filter = CrossEngineFilter(
|
59
|
+
model=self.model,
|
60
|
+
max_results=cross_engine_max_results,
|
61
|
+
default_reorder=filter_reorder,
|
62
|
+
default_reindex=filter_reindex,
|
63
|
+
)
|
64
|
+
|
65
|
+
# Use provided citation_handler or create one
|
66
|
+
self.citation_handler = citation_handler or CitationHandler(self.model)
|
67
|
+
self.findings_repository = FindingsRepository(self.model)
|
68
|
+
|
69
|
+
def analyze_topic(self, query: str) -> Dict:
|
70
|
+
"""
|
71
|
+
Direct search implementation - single query, single synthesis.
|
72
|
+
"""
|
73
|
+
logger.info(f"Starting direct search on topic: {query}")
|
74
|
+
findings = []
|
75
|
+
total_citation_count_before_this_search = len(self.all_links_of_system)
|
76
|
+
|
77
|
+
self._update_progress(
|
78
|
+
"Initializing direct search",
|
79
|
+
5,
|
80
|
+
{
|
81
|
+
"phase": "init",
|
82
|
+
"strategy": "direct",
|
83
|
+
"query": query[:100],
|
84
|
+
},
|
85
|
+
)
|
86
|
+
|
87
|
+
# Check search engine
|
88
|
+
if not self._validate_search_engine():
|
89
|
+
return {
|
90
|
+
"findings": [],
|
91
|
+
"iterations": 1,
|
92
|
+
"questions_by_iteration": {1: [query]},
|
93
|
+
"formatted_findings": "Error: Unable to conduct research without a search engine.",
|
94
|
+
"current_knowledge": "",
|
95
|
+
"error": "No search engine available",
|
96
|
+
}
|
97
|
+
|
98
|
+
try:
|
99
|
+
# Single direct search
|
100
|
+
self._update_progress(
|
101
|
+
f"Searching: {query[:200]}...",
|
102
|
+
20,
|
103
|
+
{"phase": "searching", "query": query},
|
104
|
+
)
|
105
|
+
|
106
|
+
search_results = self.search.run(query)
|
107
|
+
|
108
|
+
if not search_results:
|
109
|
+
search_results = []
|
110
|
+
|
111
|
+
self._update_progress(
|
112
|
+
f"Found {len(search_results)} results",
|
113
|
+
40,
|
114
|
+
{
|
115
|
+
"phase": "search_complete",
|
116
|
+
"result_count": len(search_results),
|
117
|
+
},
|
118
|
+
)
|
119
|
+
|
120
|
+
# Optional: Apply cross-engine filter
|
121
|
+
if self.use_cross_engine_filter and search_results:
|
122
|
+
self._update_progress(
|
123
|
+
"Filtering search results",
|
124
|
+
50,
|
125
|
+
{"phase": "filtering"},
|
126
|
+
)
|
127
|
+
|
128
|
+
filtered_results = self.cross_engine_filter.filter_results(
|
129
|
+
search_results,
|
130
|
+
query,
|
131
|
+
reorder=self.filter_reorder,
|
132
|
+
reindex=self.filter_reindex,
|
133
|
+
start_index=len(self.all_links_of_system),
|
134
|
+
)
|
135
|
+
|
136
|
+
self._update_progress(
|
137
|
+
f"Filtered to {len(filtered_results)} results",
|
138
|
+
60,
|
139
|
+
{
|
140
|
+
"phase": "filtering_complete",
|
141
|
+
"filtered_count": len(filtered_results),
|
142
|
+
},
|
143
|
+
)
|
144
|
+
else:
|
145
|
+
filtered_results = search_results
|
146
|
+
|
147
|
+
# Add to all links
|
148
|
+
self.all_links_of_system.extend(filtered_results)
|
149
|
+
|
150
|
+
# Final synthesis
|
151
|
+
self._update_progress(
|
152
|
+
"Generating synthesis",
|
153
|
+
80,
|
154
|
+
{"phase": "synthesis"},
|
155
|
+
)
|
156
|
+
|
157
|
+
final_citation_result = self.citation_handler.analyze_followup(
|
158
|
+
query,
|
159
|
+
filtered_results,
|
160
|
+
previous_knowledge="",
|
161
|
+
nr_of_links=total_citation_count_before_this_search,
|
162
|
+
)
|
163
|
+
|
164
|
+
if final_citation_result:
|
165
|
+
synthesized_content = final_citation_result["content"]
|
166
|
+
documents = final_citation_result.get("documents", [])
|
167
|
+
else:
|
168
|
+
synthesized_content = "No relevant results found."
|
169
|
+
documents = []
|
170
|
+
|
171
|
+
# Create finding
|
172
|
+
finding = {
|
173
|
+
"phase": "Direct Search",
|
174
|
+
"content": synthesized_content,
|
175
|
+
"question": query,
|
176
|
+
"search_results": filtered_results,
|
177
|
+
"documents": documents,
|
178
|
+
}
|
179
|
+
findings.append(finding)
|
180
|
+
|
181
|
+
# Add documents to repository
|
182
|
+
self.findings_repository.add_documents(documents)
|
183
|
+
|
184
|
+
# Format findings
|
185
|
+
formatted_findings = (
|
186
|
+
self.findings_repository.format_findings_to_text(
|
187
|
+
findings, synthesized_content
|
188
|
+
)
|
189
|
+
)
|
190
|
+
|
191
|
+
except Exception as e:
|
192
|
+
import traceback
|
193
|
+
|
194
|
+
error_msg = f"Error in direct search: {str(e)}"
|
195
|
+
logger.error(error_msg)
|
196
|
+
logger.error(traceback.format_exc())
|
197
|
+
synthesized_content = f"Error: {str(e)}"
|
198
|
+
formatted_findings = f"Error: {str(e)}"
|
199
|
+
finding = {
|
200
|
+
"phase": "Error",
|
201
|
+
"content": synthesized_content,
|
202
|
+
"question": query,
|
203
|
+
"search_results": [],
|
204
|
+
"documents": [],
|
205
|
+
}
|
206
|
+
findings.append(finding)
|
207
|
+
|
208
|
+
self._update_progress("Search complete", 100, {"phase": "complete"})
|
209
|
+
|
210
|
+
return {
|
211
|
+
"findings": findings,
|
212
|
+
"iterations": 1,
|
213
|
+
"questions_by_iteration": {1: [query]},
|
214
|
+
"formatted_findings": formatted_findings,
|
215
|
+
"current_knowledge": synthesized_content,
|
216
|
+
"all_links_of_system": self.all_links_of_system,
|
217
|
+
}
|
@@ -0,0 +1,320 @@
|
|
1
|
+
"""
|
2
|
+
Enhanced strategy with dual confidence scoring (positive/negative) for better accuracy.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import re
|
6
|
+
from dataclasses import dataclass
|
7
|
+
from typing import Dict, List
|
8
|
+
|
9
|
+
from loguru import logger
|
10
|
+
|
11
|
+
from ..candidates.base_candidate import Candidate
|
12
|
+
from ..constraints.base_constraint import Constraint
|
13
|
+
from .smart_query_strategy import SmartQueryStrategy
|
14
|
+
|
15
|
+
|
16
|
+
@dataclass
|
17
|
+
class ConstraintEvidence:
|
18
|
+
"""Evidence for a constraint with dual confidence scores."""
|
19
|
+
|
20
|
+
positive_confidence: float # How sure we are the constraint IS satisfied
|
21
|
+
negative_confidence: (
|
22
|
+
float # How sure we are the constraint is NOT satisfied
|
23
|
+
)
|
24
|
+
uncertainty: float # How uncertain we are (neither positive nor negative)
|
25
|
+
evidence_text: str
|
26
|
+
source: str
|
27
|
+
|
28
|
+
|
29
|
+
class DualConfidenceStrategy(SmartQueryStrategy):
|
30
|
+
"""
|
31
|
+
Enhanced strategy that uses dual confidence scoring:
|
32
|
+
- Positive confidence: Evidence that constraint IS satisfied
|
33
|
+
- Negative confidence: Evidence that constraint is NOT satisfied
|
34
|
+
- Uncertainty: Lack of clear evidence either way
|
35
|
+
|
36
|
+
This allows for more accurate scoring and better early stopping decisions.
|
37
|
+
"""
|
38
|
+
|
39
|
+
def __init__(
|
40
|
+
self,
|
41
|
+
*args,
|
42
|
+
uncertainty_penalty: float = 0.2,
|
43
|
+
negative_weight: float = 0.5,
|
44
|
+
**kwargs,
|
45
|
+
):
|
46
|
+
super().__init__(*args, **kwargs)
|
47
|
+
self.uncertainty_penalty = uncertainty_penalty
|
48
|
+
self.negative_weight = negative_weight
|
49
|
+
|
50
|
+
def _evaluate_evidence(
|
51
|
+
self, evidence_list: List[Dict], constraint: Constraint
|
52
|
+
) -> float:
|
53
|
+
"""Enhanced evidence evaluation with dual confidence."""
|
54
|
+
if not evidence_list:
|
55
|
+
# No evidence means high uncertainty
|
56
|
+
return 0.5 - self.uncertainty_penalty
|
57
|
+
|
58
|
+
# Convert evidence to dual confidence format
|
59
|
+
constraint_evidence = []
|
60
|
+
for evidence in evidence_list:
|
61
|
+
dual_evidence = self._analyze_evidence_dual_confidence(
|
62
|
+
evidence, constraint
|
63
|
+
)
|
64
|
+
constraint_evidence.append(dual_evidence)
|
65
|
+
|
66
|
+
# Calculate overall score
|
67
|
+
total_positive = sum(e.positive_confidence for e in constraint_evidence)
|
68
|
+
total_negative = sum(e.negative_confidence for e in constraint_evidence)
|
69
|
+
total_uncertainty = sum(e.uncertainty for e in constraint_evidence)
|
70
|
+
|
71
|
+
# Normalize
|
72
|
+
evidence_count = len(constraint_evidence)
|
73
|
+
avg_positive = total_positive / evidence_count
|
74
|
+
avg_negative = total_negative / evidence_count
|
75
|
+
avg_uncertainty = total_uncertainty / evidence_count
|
76
|
+
|
77
|
+
# Calculate final score
|
78
|
+
# High positive + low negative = high score
|
79
|
+
# Low positive + high negative = low score
|
80
|
+
# High uncertainty = penalty
|
81
|
+
score = (
|
82
|
+
avg_positive
|
83
|
+
- (avg_negative * self.negative_weight)
|
84
|
+
- (avg_uncertainty * self.uncertainty_penalty)
|
85
|
+
)
|
86
|
+
|
87
|
+
# Clamp to [0, 1]
|
88
|
+
return max(0.0, min(1.0, score))
|
89
|
+
|
90
|
+
def _analyze_evidence_dual_confidence(
|
91
|
+
self, evidence: Dict, constraint: Constraint
|
92
|
+
) -> ConstraintEvidence:
|
93
|
+
"""Analyze evidence to extract dual confidence scores."""
|
94
|
+
text = evidence.get("text", "")
|
95
|
+
|
96
|
+
# Use LLM to analyze evidence with dual confidence
|
97
|
+
prompt = f"""
|
98
|
+
Analyze this evidence for the constraint "{constraint.value}" (type: {constraint.type.value}).
|
99
|
+
|
100
|
+
Evidence:
|
101
|
+
{text[:1000]}
|
102
|
+
|
103
|
+
Provide three confidence scores (0-1):
|
104
|
+
1. POSITIVE_CONFIDENCE: How confident are you that this constraint IS satisfied?
|
105
|
+
2. NEGATIVE_CONFIDENCE: How confident are you that this constraint is NOT satisfied?
|
106
|
+
3. UNCERTAINTY: How uncertain are you (lack of clear evidence)?
|
107
|
+
|
108
|
+
The three scores should approximately sum to 1.0.
|
109
|
+
|
110
|
+
Format:
|
111
|
+
POSITIVE: [score]
|
112
|
+
NEGATIVE: [score]
|
113
|
+
UNCERTAINTY: [score]
|
114
|
+
"""
|
115
|
+
|
116
|
+
try:
|
117
|
+
response = self.model.invoke(prompt).content
|
118
|
+
|
119
|
+
# Extract scores
|
120
|
+
positive = self._extract_score(response, "POSITIVE")
|
121
|
+
negative = self._extract_score(response, "NEGATIVE")
|
122
|
+
uncertainty = self._extract_score(response, "UNCERTAINTY")
|
123
|
+
|
124
|
+
# Normalize if needed
|
125
|
+
total = positive + negative + uncertainty
|
126
|
+
if total > 0:
|
127
|
+
positive /= total
|
128
|
+
negative /= total
|
129
|
+
uncertainty /= total
|
130
|
+
else:
|
131
|
+
# Default to high uncertainty
|
132
|
+
uncertainty = 0.8
|
133
|
+
positive = 0.1
|
134
|
+
negative = 0.1
|
135
|
+
|
136
|
+
return ConstraintEvidence(
|
137
|
+
positive_confidence=positive,
|
138
|
+
negative_confidence=negative,
|
139
|
+
uncertainty=uncertainty,
|
140
|
+
evidence_text=text[:500],
|
141
|
+
source=evidence.get("source", "search"),
|
142
|
+
)
|
143
|
+
|
144
|
+
except Exception as e:
|
145
|
+
logger.error(f"Error analyzing evidence: {e}")
|
146
|
+
# Default to high uncertainty
|
147
|
+
return ConstraintEvidence(
|
148
|
+
positive_confidence=0.1,
|
149
|
+
negative_confidence=0.1,
|
150
|
+
uncertainty=0.8,
|
151
|
+
evidence_text=text[:500],
|
152
|
+
source=evidence.get("source", "search"),
|
153
|
+
)
|
154
|
+
|
155
|
+
def _extract_score(self, text: str, label: str) -> float:
|
156
|
+
"""Extract confidence score from LLM response."""
|
157
|
+
pattern = rf"{label}:\s*\[?(\d*\.?\d+)\]?"
|
158
|
+
match = re.search(pattern, text, re.IGNORECASE)
|
159
|
+
if match:
|
160
|
+
try:
|
161
|
+
return float(match.group(1))
|
162
|
+
except:
|
163
|
+
pass
|
164
|
+
return 0.1 # Default low score
|
165
|
+
|
166
|
+
def _gather_evidence_for_constraint(
|
167
|
+
self, candidate: Candidate, constraint: Constraint
|
168
|
+
) -> List[Dict]:
|
169
|
+
"""Enhanced evidence gathering with targeted searches."""
|
170
|
+
evidence = []
|
171
|
+
|
172
|
+
# Build specific queries for this constraint
|
173
|
+
queries = [
|
174
|
+
f'"{candidate.name}" {constraint.value} verification',
|
175
|
+
f'"{candidate.name}" {constraint.value} true or false',
|
176
|
+
f"Is {candidate.name} {constraint.value}?",
|
177
|
+
]
|
178
|
+
|
179
|
+
# For negative evidence, also search for contradictions
|
180
|
+
if constraint.type.value == "property":
|
181
|
+
queries.append(f'"{candidate.name}" NOT {constraint.value}')
|
182
|
+
queries.append(f'"{candidate.name}" opposite of {constraint.value}')
|
183
|
+
|
184
|
+
# Execute searches
|
185
|
+
for query in queries[:3]: # Limit searches
|
186
|
+
if query.lower() not in self.searched_queries:
|
187
|
+
self.searched_queries.add(query.lower())
|
188
|
+
try:
|
189
|
+
results = self._execute_search(query)
|
190
|
+
content = results.get("current_knowledge", "")
|
191
|
+
|
192
|
+
if content:
|
193
|
+
evidence.append(
|
194
|
+
{
|
195
|
+
"text": content,
|
196
|
+
"source": f"search: {query}",
|
197
|
+
"query": query,
|
198
|
+
}
|
199
|
+
)
|
200
|
+
except Exception as e:
|
201
|
+
logger.error(f"Error gathering evidence for {query}: {e}")
|
202
|
+
|
203
|
+
return evidence
|
204
|
+
|
205
|
+
def _evaluate_candidate_immediately(self, candidate: Candidate) -> float:
|
206
|
+
"""Enhanced evaluation with detailed constraint scoring."""
|
207
|
+
try:
|
208
|
+
logger.info(
|
209
|
+
f"Evaluating candidate: {candidate.name} with dual confidence"
|
210
|
+
)
|
211
|
+
|
212
|
+
total_score = 0.0
|
213
|
+
constraint_scores = []
|
214
|
+
detailed_results = []
|
215
|
+
|
216
|
+
for i, constraint in enumerate(self.constraint_ranking):
|
217
|
+
# Gather evidence
|
218
|
+
evidence = self._gather_evidence_for_constraint(
|
219
|
+
candidate, constraint
|
220
|
+
)
|
221
|
+
|
222
|
+
# Evaluate with dual confidence
|
223
|
+
score = self._evaluate_evidence(evidence, constraint)
|
224
|
+
constraint_scores.append(score)
|
225
|
+
|
226
|
+
# Store detailed results
|
227
|
+
if evidence:
|
228
|
+
dual_evidence = [
|
229
|
+
self._analyze_evidence_dual_confidence(e, constraint)
|
230
|
+
for e in evidence
|
231
|
+
]
|
232
|
+
avg_positive = sum(
|
233
|
+
e.positive_confidence for e in dual_evidence
|
234
|
+
) / len(dual_evidence)
|
235
|
+
avg_negative = sum(
|
236
|
+
e.negative_confidence for e in dual_evidence
|
237
|
+
) / len(dual_evidence)
|
238
|
+
avg_uncertainty = sum(
|
239
|
+
e.uncertainty for e in dual_evidence
|
240
|
+
) / len(dual_evidence)
|
241
|
+
|
242
|
+
detailed_results.append(
|
243
|
+
{
|
244
|
+
"constraint": constraint.value,
|
245
|
+
"score": score,
|
246
|
+
"positive": avg_positive,
|
247
|
+
"negative": avg_negative,
|
248
|
+
"uncertainty": avg_uncertainty,
|
249
|
+
}
|
250
|
+
)
|
251
|
+
|
252
|
+
symbol = (
|
253
|
+
"✓" if score >= 0.8 else "○" if score >= 0.5 else "✗"
|
254
|
+
)
|
255
|
+
logger.info(
|
256
|
+
f"{symbol} {candidate.name} | {constraint.value}: {int(score * 100)}% "
|
257
|
+
f"(+{int(avg_positive * 100)}% -{int(avg_negative * 100)}% ?{int(avg_uncertainty * 100)}%)"
|
258
|
+
)
|
259
|
+
|
260
|
+
# Early exit for critical failures with high negative confidence
|
261
|
+
if score < 0.2 and constraint.weight > 0.8:
|
262
|
+
logger.info(
|
263
|
+
f"Critical constraint failed with high confidence: {constraint.value}"
|
264
|
+
)
|
265
|
+
break
|
266
|
+
|
267
|
+
# Calculate weighted average
|
268
|
+
if constraint_scores:
|
269
|
+
weights = [
|
270
|
+
c.weight
|
271
|
+
for c in self.constraint_ranking[: len(constraint_scores)]
|
272
|
+
]
|
273
|
+
total_score = sum(
|
274
|
+
s * w for s, w in zip(constraint_scores, weights)
|
275
|
+
) / sum(weights)
|
276
|
+
|
277
|
+
# Log detailed breakdown
|
278
|
+
logger.info(f"\nDetailed analysis for {candidate.name}:")
|
279
|
+
for result in detailed_results:
|
280
|
+
logger.info(
|
281
|
+
f" {result['constraint']}: {result['score']:.2%} "
|
282
|
+
f"(+{result['positive']:.0%} -{result['negative']:.0%} ?{result['uncertainty']:.0%})"
|
283
|
+
)
|
284
|
+
|
285
|
+
# Update best candidate if needed
|
286
|
+
with self.evaluation_lock:
|
287
|
+
self.evaluated_candidates[candidate.name] = total_score
|
288
|
+
|
289
|
+
if total_score > self.best_score:
|
290
|
+
self.best_score = total_score
|
291
|
+
self.best_candidate = candidate.name
|
292
|
+
|
293
|
+
logger.info(
|
294
|
+
f"New best: {candidate.name} with {total_score:.2%}"
|
295
|
+
)
|
296
|
+
|
297
|
+
# Check for early stop
|
298
|
+
if total_score >= self.early_stop_threshold:
|
299
|
+
logger.info(
|
300
|
+
f"EARLY STOP: {candidate.name} reached {total_score:.2%}!"
|
301
|
+
)
|
302
|
+
self.found_answer.set()
|
303
|
+
|
304
|
+
if self.progress_callback:
|
305
|
+
self.progress_callback(
|
306
|
+
f"Found answer: {candidate.name} ({int(total_score * 100)}% confidence)",
|
307
|
+
95,
|
308
|
+
{
|
309
|
+
"phase": "early_stop",
|
310
|
+
"final_answer": candidate.name,
|
311
|
+
"confidence": total_score,
|
312
|
+
"detailed_results": detailed_results,
|
313
|
+
},
|
314
|
+
)
|
315
|
+
|
316
|
+
return total_score
|
317
|
+
|
318
|
+
except Exception as e:
|
319
|
+
logger.error(f"Error evaluating {candidate.name}: {e}")
|
320
|
+
return 0.0
|