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,778 @@
|
|
1
|
+
"""
|
2
|
+
BrowseComp-Optimized Search Strategy for Complex Query Solving
|
3
|
+
|
4
|
+
This strategy is specifically designed to handle BrowseComp-style puzzle queries
|
5
|
+
where specific clues need to be matched to find a location, person, or event.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from dataclasses import dataclass, field
|
9
|
+
from datetime import datetime
|
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 .base_strategy import BaseSearchStrategy
|
18
|
+
from .source_based_strategy import SourceBasedSearchStrategy
|
19
|
+
|
20
|
+
|
21
|
+
@dataclass
|
22
|
+
class QueryClues:
|
23
|
+
"""Extract specific clues from a complex query."""
|
24
|
+
|
25
|
+
location_clues: List[str] = field(default_factory=list)
|
26
|
+
temporal_clues: List[str] = field(default_factory=list)
|
27
|
+
numerical_clues: List[str] = field(default_factory=list)
|
28
|
+
name_clues: List[str] = field(default_factory=list)
|
29
|
+
incident_clues: List[str] = field(default_factory=list)
|
30
|
+
comparison_clues: List[str] = field(default_factory=list)
|
31
|
+
all_clues: List[str] = field(default_factory=list)
|
32
|
+
query_type: str = "unknown" # location, person, event, object, etc.
|
33
|
+
|
34
|
+
|
35
|
+
class BrowseCompOptimizedStrategy(BaseSearchStrategy):
|
36
|
+
"""
|
37
|
+
A strategy optimized for solving BrowseComp-style puzzle queries.
|
38
|
+
|
39
|
+
Key features:
|
40
|
+
1. Extracts specific clues from the query
|
41
|
+
2. Searches for combinations of clues
|
42
|
+
3. Progressively narrows down candidates
|
43
|
+
4. Uses specific place/person/object names when found
|
44
|
+
5. Verifies candidates against all clues
|
45
|
+
"""
|
46
|
+
|
47
|
+
def __init__(
|
48
|
+
self,
|
49
|
+
model: BaseChatModel,
|
50
|
+
search: Any,
|
51
|
+
all_links_of_system: List[str],
|
52
|
+
max_browsecomp_iterations: int = 15,
|
53
|
+
confidence_threshold: float = 0.90,
|
54
|
+
max_iterations: int = 1, # This is for source-based strategy iterations
|
55
|
+
questions_per_iteration: int = 3, # This is for source-based strategy questions
|
56
|
+
):
|
57
|
+
"""Initialize the BrowseComp-optimized strategy."""
|
58
|
+
super().__init__(all_links_of_system)
|
59
|
+
self.model = model
|
60
|
+
self.search = search
|
61
|
+
self.max_browsecomp_iterations = max_browsecomp_iterations
|
62
|
+
self.confidence_threshold = confidence_threshold
|
63
|
+
self.findings_repository = FindingsRepository(model)
|
64
|
+
|
65
|
+
# Store the source-based strategy parameters
|
66
|
+
self.source_max_iterations = max_iterations
|
67
|
+
self.source_questions_per_iteration = questions_per_iteration
|
68
|
+
|
69
|
+
# State tracking
|
70
|
+
self.query_clues: Optional[QueryClues] = None
|
71
|
+
self.confirmed_info: Dict[str, Any] = {}
|
72
|
+
self.candidates: List[Dict[str, Any]] = []
|
73
|
+
self.search_history: List[str] = []
|
74
|
+
self.iteration: int = 0
|
75
|
+
|
76
|
+
def analyze_topic(self, query: str) -> Dict:
|
77
|
+
"""Analyze a topic using BrowseComp-optimized approach."""
|
78
|
+
# Initialize
|
79
|
+
self.all_links_of_system.clear()
|
80
|
+
self.questions_by_iteration = []
|
81
|
+
self.findings = []
|
82
|
+
self.iteration = 0
|
83
|
+
|
84
|
+
# Extract clues from query
|
85
|
+
self.query_clues = self._extract_clues(query)
|
86
|
+
|
87
|
+
# Progress callback
|
88
|
+
if self.progress_callback:
|
89
|
+
self.progress_callback(
|
90
|
+
f"Identified {len(self.query_clues.all_clues)} clues from query",
|
91
|
+
1,
|
92
|
+
{
|
93
|
+
"phase": "init",
|
94
|
+
"strategy": "browsecomp_optimized",
|
95
|
+
"clues_count": len(self.query_clues.all_clues),
|
96
|
+
"query_type": self.query_clues.query_type,
|
97
|
+
},
|
98
|
+
)
|
99
|
+
|
100
|
+
logger.info(f"Starting BrowseComp optimization for: {query}")
|
101
|
+
logger.info(f"Extracted {len(self.query_clues.all_clues)} clues")
|
102
|
+
|
103
|
+
# Add initial analysis
|
104
|
+
initial_finding = {
|
105
|
+
"phase": "Initial Analysis",
|
106
|
+
"content": f"""
|
107
|
+
**Query**: {query}
|
108
|
+
|
109
|
+
**Strategy**: BrowseComp-Optimized
|
110
|
+
- Query type: {self.query_clues.query_type}
|
111
|
+
- Total clues: {len(self.query_clues.all_clues)}
|
112
|
+
- Location clues: {len(self.query_clues.location_clues)}
|
113
|
+
- Temporal clues: {len(self.query_clues.temporal_clues)}
|
114
|
+
- Numerical clues: {len(self.query_clues.numerical_clues)}
|
115
|
+
|
116
|
+
**Key Clues**:
|
117
|
+
{chr(10).join(f"- {clue}" for clue in self.query_clues.all_clues[:5])}
|
118
|
+
|
119
|
+
**Starting systematic search**...
|
120
|
+
""".strip(),
|
121
|
+
"timestamp": self._get_timestamp(),
|
122
|
+
}
|
123
|
+
self.findings.append(initial_finding)
|
124
|
+
|
125
|
+
# Main search loop
|
126
|
+
while self.iteration < self.max_browsecomp_iterations:
|
127
|
+
self.iteration += 1
|
128
|
+
|
129
|
+
# Progress update
|
130
|
+
if self.progress_callback:
|
131
|
+
progress = (
|
132
|
+
int((self.iteration / self.max_browsecomp_iterations) * 80)
|
133
|
+
+ 10
|
134
|
+
)
|
135
|
+
self.progress_callback(
|
136
|
+
f"Iteration {self.iteration}: {len(self.candidates)} candidates, {len(self.confirmed_info)} confirmed facts",
|
137
|
+
progress,
|
138
|
+
{
|
139
|
+
"phase": "searching",
|
140
|
+
"iteration": self.iteration,
|
141
|
+
"candidates_count": len(self.candidates),
|
142
|
+
"confirmed_facts": len(self.confirmed_info),
|
143
|
+
},
|
144
|
+
)
|
145
|
+
|
146
|
+
# Generate targeted search query
|
147
|
+
search_query = self._generate_targeted_search()
|
148
|
+
|
149
|
+
if not search_query:
|
150
|
+
logger.info("No more searches needed - sufficient candidates")
|
151
|
+
break
|
152
|
+
|
153
|
+
# Execute search
|
154
|
+
logger.info(
|
155
|
+
f"Iteration {self.iteration}: Searching for '{search_query}'"
|
156
|
+
)
|
157
|
+
search_results = self._execute_search(search_query)
|
158
|
+
|
159
|
+
# Process results
|
160
|
+
self._process_search_results(search_results, search_query)
|
161
|
+
|
162
|
+
# Check if we have a strong candidate
|
163
|
+
if self._evaluate_candidates():
|
164
|
+
break
|
165
|
+
|
166
|
+
# Add iteration finding
|
167
|
+
iteration_summary = f"""
|
168
|
+
**Search Query**: {search_query}
|
169
|
+
|
170
|
+
**Candidates Found**: {len(self.candidates)}
|
171
|
+
{chr(10).join(f"- {c['name']} (confidence: {c['confidence']:.0%})" for c in self.candidates[:3])}
|
172
|
+
|
173
|
+
**Confirmed Facts**: {len(self.confirmed_info)}
|
174
|
+
{chr(10).join(f"- {k}: {v}" for k, v in list(self.confirmed_info.items())[:3])}
|
175
|
+
|
176
|
+
**Progress**: Iteration {self.iteration}/{self.max_browsecomp_iterations}
|
177
|
+
"""
|
178
|
+
self.findings.append(
|
179
|
+
{
|
180
|
+
"phase": f"Iteration {self.iteration}",
|
181
|
+
"content": iteration_summary.strip(),
|
182
|
+
"timestamp": self._get_timestamp(),
|
183
|
+
}
|
184
|
+
)
|
185
|
+
|
186
|
+
# Generate final answer
|
187
|
+
final_result = self._synthesize_final_answer(query)
|
188
|
+
|
189
|
+
if self.progress_callback:
|
190
|
+
self.progress_callback(
|
191
|
+
f"Analysis complete - {self.iteration} iterations, {len(self.candidates)} final candidates",
|
192
|
+
100,
|
193
|
+
{
|
194
|
+
"phase": "complete",
|
195
|
+
"strategy": "browsecomp_optimized",
|
196
|
+
"total_iterations": self.iteration,
|
197
|
+
"final_candidates": len(self.candidates),
|
198
|
+
},
|
199
|
+
)
|
200
|
+
|
201
|
+
return final_result
|
202
|
+
|
203
|
+
def _extract_clues(self, query: str) -> QueryClues:
|
204
|
+
"""Extract specific clues from the query."""
|
205
|
+
prompt = f"""
|
206
|
+
Analyze this query and extract ALL specific clues that help identify the answer.
|
207
|
+
|
208
|
+
Query: {query}
|
209
|
+
|
210
|
+
Extract the following types of clues (BE VERY SPECIFIC AND COMPREHENSIVE):
|
211
|
+
1. Location clues (geographical features, regions, landmarks)
|
212
|
+
2. Temporal clues (dates, time periods, years - extract exact years/ranges)
|
213
|
+
3. Numerical clues (statistics, counts, comparisons - extract exact numbers)
|
214
|
+
4. Name clues (hints about the name, body parts, colors, etc.)
|
215
|
+
5. Incident clues (accidents, events, activities - be specific about what happened)
|
216
|
+
6. Comparison clues (comparisons to other places/things - extract exact comparison ratios)
|
217
|
+
|
218
|
+
IMPORTANT: Extract EVERY specific detail, number, date, and comparison mentioned.
|
219
|
+
|
220
|
+
Also determine the query type: location, person, event, object, or other.
|
221
|
+
|
222
|
+
Format your response as:
|
223
|
+
QUERY_TYPE: [type]
|
224
|
+
|
225
|
+
LOCATION_CLUES:
|
226
|
+
- [clue 1]
|
227
|
+
- [clue 2]
|
228
|
+
|
229
|
+
TEMPORAL_CLUES:
|
230
|
+
- [clue 1 with exact dates/years]
|
231
|
+
|
232
|
+
NUMERICAL_CLUES:
|
233
|
+
- [clue 1 with exact numbers]
|
234
|
+
|
235
|
+
NAME_CLUES:
|
236
|
+
- [clue 1]
|
237
|
+
|
238
|
+
INCIDENT_CLUES:
|
239
|
+
- [clue 1 with specific details]
|
240
|
+
|
241
|
+
COMPARISON_CLUES:
|
242
|
+
- [clue 1 with exact comparison ratio]
|
243
|
+
|
244
|
+
ALL_CLUES_SUMMARY:
|
245
|
+
- [most important clue 1]
|
246
|
+
- [most important clue 2]
|
247
|
+
- [most important clue 3]
|
248
|
+
- [most important clue 4]
|
249
|
+
- [most important clue 5]
|
250
|
+
"""
|
251
|
+
|
252
|
+
response = self.model.invoke(prompt)
|
253
|
+
content = remove_think_tags(response.content)
|
254
|
+
|
255
|
+
clues = QueryClues()
|
256
|
+
current_section = None
|
257
|
+
|
258
|
+
for line in content.strip().split("\n"):
|
259
|
+
line = line.strip()
|
260
|
+
|
261
|
+
if line.startswith("QUERY_TYPE:"):
|
262
|
+
clues.query_type = line.split(":", 1)[1].strip().lower()
|
263
|
+
elif line.endswith("_CLUES:") or line == "ALL_CLUES_SUMMARY:":
|
264
|
+
current_section = (
|
265
|
+
line.replace("_CLUES:", "")
|
266
|
+
.replace("ALL_CLUES_SUMMARY:", "all")
|
267
|
+
.lower()
|
268
|
+
)
|
269
|
+
elif line.startswith("-") and current_section:
|
270
|
+
clue = line[1:].strip()
|
271
|
+
if current_section == "location":
|
272
|
+
clues.location_clues.append(clue)
|
273
|
+
elif current_section == "temporal":
|
274
|
+
clues.temporal_clues.append(clue)
|
275
|
+
elif current_section == "numerical":
|
276
|
+
clues.numerical_clues.append(clue)
|
277
|
+
elif current_section == "name":
|
278
|
+
clues.name_clues.append(clue)
|
279
|
+
elif current_section == "incident":
|
280
|
+
clues.incident_clues.append(clue)
|
281
|
+
elif current_section == "comparison":
|
282
|
+
clues.comparison_clues.append(clue)
|
283
|
+
elif current_section == "all":
|
284
|
+
clues.all_clues.append(clue)
|
285
|
+
|
286
|
+
# Ensure we have all clues
|
287
|
+
if not clues.all_clues:
|
288
|
+
clues.all_clues = (
|
289
|
+
clues.location_clues
|
290
|
+
+ clues.temporal_clues
|
291
|
+
+ clues.numerical_clues
|
292
|
+
+ clues.name_clues
|
293
|
+
+ clues.incident_clues
|
294
|
+
+ clues.comparison_clues
|
295
|
+
)[:5] # Top 5 most important
|
296
|
+
|
297
|
+
return clues
|
298
|
+
|
299
|
+
def _generate_targeted_search(self) -> Optional[str]:
|
300
|
+
"""Generate a targeted search query based on current knowledge."""
|
301
|
+
# If we have specific candidates, search for verification
|
302
|
+
if self.candidates and self.iteration > 2:
|
303
|
+
top_candidate = self.candidates[0]
|
304
|
+
|
305
|
+
# Search for specific verification of top candidate with unverified clues
|
306
|
+
unverified_clues = self._get_unverified_clues(top_candidate)
|
307
|
+
|
308
|
+
if unverified_clues:
|
309
|
+
# Pick the most specific unverified clue
|
310
|
+
if any(
|
311
|
+
"fell" in clue or "accident" in clue
|
312
|
+
for clue in unverified_clues
|
313
|
+
):
|
314
|
+
return f"{top_candidate['name']} accident fall death {' '.join(self.query_clues.temporal_clues[:1])}"
|
315
|
+
elif any(
|
316
|
+
"search and rescue" in clue.lower() or "sar" in clue.lower()
|
317
|
+
for clue in unverified_clues
|
318
|
+
):
|
319
|
+
return f"{top_candidate['name']} search and rescue incidents 2014 statistics"
|
320
|
+
elif any("84.5" in clue for clue in unverified_clues):
|
321
|
+
return f"{top_candidate['name']} 2014 search rescue statistics Grand Canyon 2022 comparison"
|
322
|
+
|
323
|
+
# Initial searches - combine multiple clues
|
324
|
+
if self.iteration <= 3:
|
325
|
+
# First iteration - broad search with key clues
|
326
|
+
if self.iteration == 1:
|
327
|
+
key_terms = []
|
328
|
+
if self.query_clues.location_clues:
|
329
|
+
key_terms.extend(self.query_clues.location_clues[:1])
|
330
|
+
if self.query_clues.name_clues:
|
331
|
+
key_terms.extend(self.query_clues.name_clues[:1])
|
332
|
+
if self.query_clues.query_type == "location":
|
333
|
+
key_terms.append("hiking trail scenic viewpoint")
|
334
|
+
return " ".join(key_terms)
|
335
|
+
|
336
|
+
# Second iteration - add temporal/incident info
|
337
|
+
elif self.iteration == 2:
|
338
|
+
key_terms = []
|
339
|
+
if self.query_clues.temporal_clues:
|
340
|
+
key_terms.extend(self.query_clues.temporal_clues[:1])
|
341
|
+
if self.query_clues.incident_clues:
|
342
|
+
key_terms.extend(self.query_clues.incident_clues[:1])
|
343
|
+
if self.query_clues.location_clues:
|
344
|
+
key_terms.extend(self.query_clues.location_clues[:1])
|
345
|
+
return " ".join(key_terms)
|
346
|
+
|
347
|
+
# Middle iterations - search for specific combinations
|
348
|
+
elif 3 <= self.iteration <= 8:
|
349
|
+
# Try different clue combinations
|
350
|
+
combinations = [
|
351
|
+
(self.query_clues.location_clues, self.query_clues.name_clues),
|
352
|
+
(
|
353
|
+
self.query_clues.temporal_clues,
|
354
|
+
self.query_clues.incident_clues,
|
355
|
+
),
|
356
|
+
(
|
357
|
+
self.query_clues.numerical_clues,
|
358
|
+
self.query_clues.location_clues,
|
359
|
+
),
|
360
|
+
(self.query_clues.name_clues, self.query_clues.incident_clues),
|
361
|
+
]
|
362
|
+
|
363
|
+
combo_idx = (self.iteration - 3) % len(combinations)
|
364
|
+
clues1, clues2 = combinations[combo_idx]
|
365
|
+
|
366
|
+
terms = []
|
367
|
+
if clues1:
|
368
|
+
terms.extend(clues1[:1])
|
369
|
+
if clues2:
|
370
|
+
terms.extend(clues2[:1])
|
371
|
+
|
372
|
+
return " ".join(terms)
|
373
|
+
|
374
|
+
# Late iterations - search for specific statistics or comparisons
|
375
|
+
elif self.iteration > 8:
|
376
|
+
if self.query_clues.comparison_clues:
|
377
|
+
return " ".join(
|
378
|
+
self.query_clues.comparison_clues[:1] + ["statistics data"]
|
379
|
+
)
|
380
|
+
elif self.query_clues.numerical_clues:
|
381
|
+
return " ".join(
|
382
|
+
self.query_clues.numerical_clues[:1]
|
383
|
+
+ ["official statistics 2014 2022"]
|
384
|
+
)
|
385
|
+
|
386
|
+
# Default - use remaining clues
|
387
|
+
all_unused = [
|
388
|
+
c
|
389
|
+
for c in self.query_clues.all_clues
|
390
|
+
if c not in self.search_history
|
391
|
+
]
|
392
|
+
if all_unused:
|
393
|
+
return all_unused[0]
|
394
|
+
|
395
|
+
return None
|
396
|
+
|
397
|
+
def _execute_search(self, search_query: str) -> Dict:
|
398
|
+
"""Execute a search using source-based strategy."""
|
399
|
+
# Track search history
|
400
|
+
self.search_history.append(search_query)
|
401
|
+
|
402
|
+
# Use source-based strategy
|
403
|
+
source_strategy = SourceBasedSearchStrategy(
|
404
|
+
model=self.model,
|
405
|
+
search=self.search,
|
406
|
+
all_links_of_system=self.all_links_of_system,
|
407
|
+
include_text_content=True,
|
408
|
+
use_cross_engine_filter=True,
|
409
|
+
use_atomic_facts=True,
|
410
|
+
)
|
411
|
+
|
412
|
+
source_strategy.max_iterations = self.source_max_iterations
|
413
|
+
source_strategy.questions_per_iteration = (
|
414
|
+
self.source_questions_per_iteration
|
415
|
+
)
|
416
|
+
|
417
|
+
if self.progress_callback:
|
418
|
+
|
419
|
+
def wrapped_callback(message, progress, data):
|
420
|
+
data["parent_iteration"] = self.iteration
|
421
|
+
data["parent_strategy"] = "browsecomp_optimized"
|
422
|
+
self.progress_callback(
|
423
|
+
f"Iteration {self.iteration}: {message}", progress, data
|
424
|
+
)
|
425
|
+
|
426
|
+
source_strategy.set_progress_callback(wrapped_callback)
|
427
|
+
|
428
|
+
results = source_strategy.analyze_topic(search_query)
|
429
|
+
|
430
|
+
if "questions_by_iteration" in results:
|
431
|
+
self.questions_by_iteration.extend(
|
432
|
+
results["questions_by_iteration"]
|
433
|
+
)
|
434
|
+
|
435
|
+
return results
|
436
|
+
|
437
|
+
def _process_search_results(self, search_results: Dict, search_query: str):
|
438
|
+
"""Process search results and update candidates."""
|
439
|
+
current_knowledge = search_results.get("current_knowledge", "")
|
440
|
+
|
441
|
+
if not current_knowledge:
|
442
|
+
return
|
443
|
+
|
444
|
+
prompt = f"""
|
445
|
+
Based on the search results, extract specific information about potential answers.
|
446
|
+
|
447
|
+
Query Clues:
|
448
|
+
- Query type: {self.query_clues.query_type}
|
449
|
+
- Key clues: {", ".join(self.query_clues.all_clues[:3])}
|
450
|
+
|
451
|
+
Search Query: {search_query}
|
452
|
+
|
453
|
+
Search Results:
|
454
|
+
{current_knowledge[:3000]}
|
455
|
+
|
456
|
+
Current Candidates: {len(self.candidates)}
|
457
|
+
{chr(10).join(f"- {c['name']} ({c['confidence']:.0%})" for c in self.candidates[:3])}
|
458
|
+
|
459
|
+
Extract:
|
460
|
+
1. SPECIFIC_NAMES: Any specific place names, trail names, or landmarks mentioned
|
461
|
+
2. CONFIRMED_FACTS: Facts that match our clues (with which clue they match)
|
462
|
+
3. NEW_CANDIDATES: New potential answers with confidence (0-1)
|
463
|
+
4. ELIMINATED_CANDIDATES: Candidates we can rule out
|
464
|
+
|
465
|
+
Format:
|
466
|
+
SPECIFIC_NAMES:
|
467
|
+
- [name 1]
|
468
|
+
- [name 2]
|
469
|
+
|
470
|
+
CONFIRMED_FACTS:
|
471
|
+
- [fact]: matches [clue]
|
472
|
+
|
473
|
+
NEW_CANDIDATES:
|
474
|
+
- [name]: [confidence] (reason)
|
475
|
+
|
476
|
+
ELIMINATED_CANDIDATES:
|
477
|
+
- [name]: [reason for elimination]
|
478
|
+
"""
|
479
|
+
|
480
|
+
response = self.model.invoke(prompt)
|
481
|
+
content = remove_think_tags(response.content)
|
482
|
+
|
483
|
+
# Parse response
|
484
|
+
current_section = None
|
485
|
+
|
486
|
+
for line in content.strip().split("\n"):
|
487
|
+
line = line.strip()
|
488
|
+
|
489
|
+
if line.endswith(":"):
|
490
|
+
current_section = line[:-1].lower()
|
491
|
+
elif line.startswith("-") and current_section:
|
492
|
+
item = line[1:].strip()
|
493
|
+
|
494
|
+
if current_section == "specific_names":
|
495
|
+
# Add as potential candidate if not already present
|
496
|
+
if not any(
|
497
|
+
c["name"].lower() == item.lower()
|
498
|
+
for c in self.candidates
|
499
|
+
):
|
500
|
+
self.candidates.append(
|
501
|
+
{
|
502
|
+
"name": item,
|
503
|
+
"confidence": 0.7,
|
504
|
+
"source": search_query,
|
505
|
+
"matched_clues": [],
|
506
|
+
}
|
507
|
+
)
|
508
|
+
|
509
|
+
elif current_section == "confirmed_facts":
|
510
|
+
if ":" in item:
|
511
|
+
fact, clue = item.split(":", 1)
|
512
|
+
self.confirmed_info[fact.strip()] = clue.strip()
|
513
|
+
|
514
|
+
elif current_section == "new_candidates":
|
515
|
+
if ":" in item:
|
516
|
+
parts = item.split(":", 1)
|
517
|
+
name = parts[0].strip()
|
518
|
+
|
519
|
+
# Parse confidence
|
520
|
+
confidence_str = parts[1].strip()
|
521
|
+
try:
|
522
|
+
confidence = float(confidence_str.split()[0])
|
523
|
+
except:
|
524
|
+
confidence = 0.5
|
525
|
+
|
526
|
+
# Update or add candidate
|
527
|
+
existing = False
|
528
|
+
for candidate in self.candidates:
|
529
|
+
if candidate["name"].lower() == name.lower():
|
530
|
+
candidate["confidence"] = max(
|
531
|
+
candidate["confidence"], confidence
|
532
|
+
)
|
533
|
+
existing = True
|
534
|
+
break
|
535
|
+
|
536
|
+
if not existing:
|
537
|
+
self.candidates.append(
|
538
|
+
{
|
539
|
+
"name": name,
|
540
|
+
"confidence": confidence,
|
541
|
+
"source": search_query,
|
542
|
+
"matched_clues": [],
|
543
|
+
}
|
544
|
+
)
|
545
|
+
|
546
|
+
elif current_section == "eliminated_candidates":
|
547
|
+
if ":" in item:
|
548
|
+
name = item.split(":")[0].strip()
|
549
|
+
self.candidates = [
|
550
|
+
c
|
551
|
+
for c in self.candidates
|
552
|
+
if c["name"].lower() != name.lower()
|
553
|
+
]
|
554
|
+
|
555
|
+
# Update matched clues for candidates
|
556
|
+
for candidate in self.candidates:
|
557
|
+
candidate["matched_clues"] = self._get_matched_clues(
|
558
|
+
candidate["name"]
|
559
|
+
)
|
560
|
+
# Update confidence based on matched clues
|
561
|
+
clue_confidence = len(candidate["matched_clues"]) / len(
|
562
|
+
self.query_clues.all_clues
|
563
|
+
)
|
564
|
+
candidate["confidence"] = (
|
565
|
+
candidate["confidence"] + clue_confidence
|
566
|
+
) / 2
|
567
|
+
|
568
|
+
# Sort candidates by confidence
|
569
|
+
self.candidates.sort(key=lambda x: x["confidence"], reverse=True)
|
570
|
+
|
571
|
+
# Keep only top candidates
|
572
|
+
self.candidates = self.candidates[:5]
|
573
|
+
|
574
|
+
def _get_matched_clues(self, candidate_name: str) -> List[str]:
|
575
|
+
"""Determine which clues a candidate matches."""
|
576
|
+
matched = []
|
577
|
+
|
578
|
+
# Check name clues
|
579
|
+
for clue in self.query_clues.name_clues:
|
580
|
+
if any(
|
581
|
+
word.lower() in candidate_name.lower() for word in clue.split()
|
582
|
+
):
|
583
|
+
matched.append(f"name: {clue}")
|
584
|
+
|
585
|
+
# Check location clues
|
586
|
+
for clue in self.query_clues.location_clues:
|
587
|
+
if (
|
588
|
+
candidate_name.lower()
|
589
|
+
in self.confirmed_info.get(clue, "").lower()
|
590
|
+
):
|
591
|
+
matched.append(f"location: {clue}")
|
592
|
+
|
593
|
+
# Check confirmed facts
|
594
|
+
for fact, clue in self.confirmed_info.items():
|
595
|
+
if candidate_name.lower() in fact.lower():
|
596
|
+
matched.append(f"fact: {clue}")
|
597
|
+
|
598
|
+
# Check temporal clues if mentioned in confirmed facts
|
599
|
+
for clue in self.query_clues.temporal_clues:
|
600
|
+
for fact in self.confirmed_info:
|
601
|
+
if candidate_name.lower() in fact.lower() and any(
|
602
|
+
year in fact for year in clue.split() if year.isdigit()
|
603
|
+
):
|
604
|
+
matched.append(f"temporal: {clue}")
|
605
|
+
|
606
|
+
# Check incident clues
|
607
|
+
for clue in self.query_clues.incident_clues:
|
608
|
+
for fact in self.confirmed_info:
|
609
|
+
if candidate_name.lower() in fact.lower() and any(
|
610
|
+
word in fact.lower() for word in clue.lower().split()
|
611
|
+
):
|
612
|
+
matched.append(f"incident: {clue}")
|
613
|
+
|
614
|
+
return list(set(matched)) # Remove duplicates
|
615
|
+
|
616
|
+
def _get_unverified_clues(self, candidate: Dict) -> List[str]:
|
617
|
+
"""Get clues that haven't been verified for a candidate."""
|
618
|
+
all_clues = (
|
619
|
+
self.query_clues.location_clues
|
620
|
+
+ self.query_clues.temporal_clues
|
621
|
+
+ self.query_clues.numerical_clues
|
622
|
+
+ self.query_clues.name_clues
|
623
|
+
+ self.query_clues.incident_clues
|
624
|
+
+ self.query_clues.comparison_clues
|
625
|
+
)
|
626
|
+
|
627
|
+
matched_clues = candidate.get("matched_clues", [])
|
628
|
+
matched_clue_texts = [
|
629
|
+
clue.split(": ", 1)[1] if ": " in clue else clue
|
630
|
+
for clue in matched_clues
|
631
|
+
]
|
632
|
+
|
633
|
+
unverified = []
|
634
|
+
for clue in all_clues:
|
635
|
+
if not any(
|
636
|
+
clue in matched_text for matched_text in matched_clue_texts
|
637
|
+
):
|
638
|
+
unverified.append(clue)
|
639
|
+
|
640
|
+
return unverified
|
641
|
+
|
642
|
+
def _evaluate_candidates(self) -> bool:
|
643
|
+
"""Check if we have a sufficiently confident answer."""
|
644
|
+
if not self.candidates:
|
645
|
+
return False
|
646
|
+
|
647
|
+
top_candidate = self.candidates[0]
|
648
|
+
|
649
|
+
# Need high confidence and clue matching
|
650
|
+
if top_candidate["confidence"] >= self.confidence_threshold:
|
651
|
+
matched_ratio = len(top_candidate["matched_clues"]) / len(
|
652
|
+
self.query_clues.all_clues
|
653
|
+
)
|
654
|
+
# For BrowseComp, require higher clue matching threshold
|
655
|
+
if matched_ratio >= 0.8: # At least 80% of clues matched
|
656
|
+
return True
|
657
|
+
|
658
|
+
# Very high confidence only if ALL clues matched
|
659
|
+
if top_candidate["confidence"] >= 0.95:
|
660
|
+
matched_ratio = len(top_candidate["matched_clues"]) / len(
|
661
|
+
self.query_clues.all_clues
|
662
|
+
)
|
663
|
+
if matched_ratio >= 0.9: # At least 90% clues matched
|
664
|
+
return True
|
665
|
+
|
666
|
+
# For BrowseComp queries, always do at least 3 iterations
|
667
|
+
if self.iteration < 3:
|
668
|
+
return False
|
669
|
+
|
670
|
+
# Or if we've done enough iterations and have a clear leader
|
671
|
+
if self.iteration >= 8:
|
672
|
+
if len(self.candidates) > 1:
|
673
|
+
confidence_gap = (
|
674
|
+
top_candidate["confidence"]
|
675
|
+
- self.candidates[1]["confidence"]
|
676
|
+
)
|
677
|
+
if confidence_gap > 0.3: # 30% gap to second place
|
678
|
+
return True
|
679
|
+
|
680
|
+
return False
|
681
|
+
|
682
|
+
def _synthesize_final_answer(self, original_query: str) -> Dict:
|
683
|
+
"""Generate final answer in BrowseComp format."""
|
684
|
+
if self.candidates:
|
685
|
+
top_candidate = self.candidates[0]
|
686
|
+
answer = top_candidate["name"]
|
687
|
+
confidence = int(top_candidate["confidence"] * 100)
|
688
|
+
else:
|
689
|
+
answer = "Unable to determine"
|
690
|
+
confidence = 0
|
691
|
+
|
692
|
+
# Generate explanation
|
693
|
+
prompt = f"""
|
694
|
+
Provide a concise final answer to this query:
|
695
|
+
{original_query}
|
696
|
+
|
697
|
+
Based on our research:
|
698
|
+
- Top answer: {answer}
|
699
|
+
- Confidence: {confidence}%
|
700
|
+
- Matched clues: {len(top_candidate["matched_clues"]) if self.candidates else 0}/{len(self.query_clues.all_clues)}
|
701
|
+
|
702
|
+
Confirmed facts:
|
703
|
+
{chr(10).join(f"- {k}: {v}" for k, v in self.confirmed_info.items())}
|
704
|
+
|
705
|
+
Format your response EXACTLY as:
|
706
|
+
Explanation: {{brief explanation of why this answer matches the clues}}
|
707
|
+
Exact Answer: {{the exact answer - just the name/place/etc}}
|
708
|
+
Confidence: {{confidence}}%
|
709
|
+
"""
|
710
|
+
|
711
|
+
response = self.model.invoke(prompt)
|
712
|
+
final_answer = remove_think_tags(response.content)
|
713
|
+
|
714
|
+
# Add comprehensive findings
|
715
|
+
synthesis_summary = f"""
|
716
|
+
**Final Answer**: {answer}
|
717
|
+
|
718
|
+
**Research Summary**:
|
719
|
+
- Completed {self.iteration} search iterations
|
720
|
+
- Evaluated {len(self.candidates)} candidates
|
721
|
+
- Matched {len(top_candidate["matched_clues"]) if self.candidates else 0}/{len(self.query_clues.all_clues)} clues
|
722
|
+
- Final confidence: {confidence}%
|
723
|
+
|
724
|
+
**Top Candidates**:
|
725
|
+
{chr(10).join(f"{i + 1}. {c['name']} ({c['confidence']:.0%})" for i, c in enumerate(self.candidates[:3]))}
|
726
|
+
|
727
|
+
**Confirmed Facts**:
|
728
|
+
{chr(10).join(f"- {k}: {v}" for k, v in self.confirmed_info.items())}
|
729
|
+
|
730
|
+
**Search History**:
|
731
|
+
{chr(10).join(f"{i + 1}. {q}" for i, q in enumerate(self.search_history))}
|
732
|
+
"""
|
733
|
+
|
734
|
+
self.findings.append(
|
735
|
+
{
|
736
|
+
"phase": "Final Synthesis",
|
737
|
+
"content": synthesis_summary,
|
738
|
+
"timestamp": self._get_timestamp(),
|
739
|
+
}
|
740
|
+
)
|
741
|
+
|
742
|
+
# Compile questions
|
743
|
+
questions_dict = {}
|
744
|
+
for i, questions in enumerate(self.questions_by_iteration):
|
745
|
+
if isinstance(questions, list):
|
746
|
+
questions_dict[i + 1] = questions
|
747
|
+
elif isinstance(questions, dict):
|
748
|
+
questions_dict.update(questions)
|
749
|
+
|
750
|
+
# Format findings
|
751
|
+
formatted_findings = format_findings(
|
752
|
+
self.findings, final_answer, questions_dict
|
753
|
+
)
|
754
|
+
|
755
|
+
return {
|
756
|
+
"current_knowledge": final_answer,
|
757
|
+
"formatted_findings": formatted_findings,
|
758
|
+
"findings": self.findings,
|
759
|
+
"iterations": self.iteration,
|
760
|
+
"questions_by_iteration": questions_dict,
|
761
|
+
"all_links_of_system": self.all_links_of_system,
|
762
|
+
"sources": self.all_links_of_system,
|
763
|
+
"candidates": self.candidates,
|
764
|
+
"confirmed_info": self.confirmed_info,
|
765
|
+
"clues": {
|
766
|
+
"location": self.query_clues.location_clues,
|
767
|
+
"temporal": self.query_clues.temporal_clues,
|
768
|
+
"numerical": self.query_clues.numerical_clues,
|
769
|
+
"name": self.query_clues.name_clues,
|
770
|
+
"incident": self.query_clues.incident_clues,
|
771
|
+
"comparison": self.query_clues.comparison_clues,
|
772
|
+
},
|
773
|
+
"strategy": "browsecomp_optimized",
|
774
|
+
}
|
775
|
+
|
776
|
+
def _get_timestamp(self) -> str:
|
777
|
+
"""Get current timestamp for findings."""
|
778
|
+
return datetime.utcnow().isoformat()
|