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
@@ -90,7 +90,9 @@ def migrate_to_ldr_db():
|
|
90
90
|
)
|
91
91
|
|
92
92
|
# Migrate from deep_research.db
|
93
|
-
migrated_deep_research = migrate_deep_research_db(
|
93
|
+
migrated_deep_research = migrate_deep_research_db(
|
94
|
+
ldr_conn, LEGACY_DEEP_RESEARCH_DB
|
95
|
+
)
|
94
96
|
|
95
97
|
# Re-enable foreign keys and commit
|
96
98
|
ldr_cursor.execute("PRAGMA foreign_keys = ON")
|
@@ -127,7 +129,9 @@ def migrate_research_history_db(ldr_conn, legacy_path):
|
|
127
129
|
logger.info(f"Connected to legacy database: {legacy_path}")
|
128
130
|
|
129
131
|
# Get tables from legacy database
|
130
|
-
legacy_cursor.execute(
|
132
|
+
legacy_cursor.execute(
|
133
|
+
"SELECT name FROM sqlite_master WHERE type='table'"
|
134
|
+
)
|
131
135
|
tables = [row[0] for row in legacy_cursor.fetchall()]
|
132
136
|
|
133
137
|
for table in tables:
|
@@ -181,7 +185,9 @@ def migrate_research_history_db(ldr_conn, legacy_path):
|
|
181
185
|
# Verify data was inserted
|
182
186
|
ldr_cursor.execute(f"SELECT COUNT(*) FROM {table}")
|
183
187
|
count = ldr_cursor.fetchone()[0]
|
184
|
-
logger.info(
|
188
|
+
logger.info(
|
189
|
+
f"Migrated {count} rows to {table} (expected {len(rows)})"
|
190
|
+
)
|
185
191
|
else:
|
186
192
|
logger.info(f"No data to migrate from {table}")
|
187
193
|
|
@@ -218,7 +224,9 @@ def migrate_deep_research_db(ldr_conn, legacy_path):
|
|
218
224
|
logger.info(f"Connected to legacy database: {legacy_path}")
|
219
225
|
|
220
226
|
# Get tables from legacy database
|
221
|
-
legacy_cursor.execute(
|
227
|
+
legacy_cursor.execute(
|
228
|
+
"SELECT name FROM sqlite_master WHERE type='table'"
|
229
|
+
)
|
222
230
|
tables = [row[0] for row in legacy_cursor.fetchall()]
|
223
231
|
|
224
232
|
# Migrate each table
|
@@ -2,7 +2,7 @@ from loguru import logger
|
|
2
2
|
from sqlalchemy import inspect
|
3
3
|
|
4
4
|
from ..services.settings_manager import SettingsManager
|
5
|
-
from .models import Base, Journal, Setting
|
5
|
+
from .models import Base, Journal, Setting, ResearchLog
|
6
6
|
|
7
7
|
|
8
8
|
def import_default_settings_file(db_session):
|
@@ -24,14 +24,12 @@ def import_default_settings_file(db_session):
|
|
24
24
|
settings_mgr.load_from_defaults_file(overwrite=False, delete_extra=True)
|
25
25
|
logger.info("Successfully imported settings from files")
|
26
26
|
|
27
|
-
|
28
27
|
# Update the saved version.
|
29
28
|
settings_mgr.update_db_version()
|
30
29
|
except Exception as e:
|
31
30
|
logger.error("Error importing settings from files: %s", e)
|
32
31
|
|
33
32
|
|
34
|
-
|
35
33
|
def run_migrations(engine, db_session=None):
|
36
34
|
"""
|
37
35
|
Run any necessary database migrations
|
@@ -50,6 +48,10 @@ def run_migrations(engine, db_session=None):
|
|
50
48
|
logger.info("Creating journals table.")
|
51
49
|
Base.metadata.create_all(engine, tables=[Journal.__table__])
|
52
50
|
|
51
|
+
if not inspector.has_table(ResearchLog.__tablename__):
|
52
|
+
logger.info("Creating research logs table.")
|
53
|
+
Base.metadata.create_all(engine, tables=[ResearchLog.__table__])
|
54
|
+
|
53
55
|
# Import existing settings from files
|
54
56
|
if db_session:
|
55
57
|
import_default_settings_file(db_session)
|
@@ -42,7 +42,9 @@ class Research(Base):
|
|
42
42
|
status = Column(
|
43
43
|
Enum(ResearchStatus), default=ResearchStatus.PENDING, nullable=False
|
44
44
|
)
|
45
|
-
mode = Column(
|
45
|
+
mode = Column(
|
46
|
+
Enum(ResearchMode), default=ResearchMode.QUICK, nullable=False
|
47
|
+
)
|
46
48
|
created_at = Column(DateTime, server_default=func.now(), nullable=False)
|
47
49
|
updated_at = Column(
|
48
50
|
DateTime, server_default=func.now(), onupdate=func.now(), nullable=False
|
@@ -61,6 +63,31 @@ class Research(Base):
|
|
61
63
|
)
|
62
64
|
|
63
65
|
|
66
|
+
class ResearchLog(Base):
|
67
|
+
__tablename__ = "app_logs"
|
68
|
+
|
69
|
+
id = Column(
|
70
|
+
Integer, Sequence("reseach_log_id_seq"), primary_key=True, index=True
|
71
|
+
)
|
72
|
+
|
73
|
+
timestamp = Column(DateTime, server_default=func.now(), nullable=False)
|
74
|
+
message = Column(Text, nullable=False)
|
75
|
+
# Module that the log message came from.
|
76
|
+
module = Column(Text, nullable=False)
|
77
|
+
# Function that the log message came from.
|
78
|
+
function = Column(Text, nullable=False)
|
79
|
+
# Line number that the log message came from.
|
80
|
+
line_no = Column(Integer, nullable=False)
|
81
|
+
# Log level.
|
82
|
+
level = Column(String(32), nullable=False)
|
83
|
+
research_id = Column(
|
84
|
+
Integer,
|
85
|
+
ForeignKey("research.id", ondelete="CASCADE"),
|
86
|
+
nullable=True,
|
87
|
+
index=True,
|
88
|
+
)
|
89
|
+
|
90
|
+
|
64
91
|
class ResearchReport(Base):
|
65
92
|
__tablename__ = "research_report"
|
66
93
|
|
@@ -118,6 +145,26 @@ class Setting(Base):
|
|
118
145
|
__table_args__ = (UniqueConstraint("key", name="uix_settings_key"),)
|
119
146
|
|
120
147
|
|
148
|
+
class ResearchStrategy(Base):
|
149
|
+
"""Database model for tracking research strategies used"""
|
150
|
+
|
151
|
+
__tablename__ = "research_strategies"
|
152
|
+
|
153
|
+
id = Column(Integer, primary_key=True, index=True)
|
154
|
+
research_id = Column(
|
155
|
+
Integer,
|
156
|
+
ForeignKey("research.id", ondelete="CASCADE"),
|
157
|
+
nullable=False,
|
158
|
+
unique=True,
|
159
|
+
index=True,
|
160
|
+
)
|
161
|
+
strategy_name = Column(String(100), nullable=False, index=True)
|
162
|
+
created_at = Column(DateTime, server_default=func.now(), nullable=False)
|
163
|
+
|
164
|
+
# Relationship
|
165
|
+
research = relationship("Research", backref="strategy")
|
166
|
+
|
167
|
+
|
121
168
|
class Journal(Base):
|
122
169
|
"""
|
123
170
|
Database model for storing information about academic journals.
|
@@ -125,7 +172,9 @@ class Journal(Base):
|
|
125
172
|
|
126
173
|
__tablename__ = "journals"
|
127
174
|
|
128
|
-
id = Column(
|
175
|
+
id = Column(
|
176
|
+
Integer, Sequence("journal_id_seq"), primary_key=True, index=True
|
177
|
+
)
|
129
178
|
|
130
179
|
# Name of the journal
|
131
180
|
name = Column(String(255), nullable=False, unique=True, index=True)
|
@@ -4,10 +4,10 @@ Handles schema upgrades for existing ldr.db databases.
|
|
4
4
|
"""
|
5
5
|
|
6
6
|
import os
|
7
|
-
import sqlite3
|
8
7
|
import sys
|
9
8
|
|
10
9
|
from loguru import logger
|
10
|
+
from sqlalchemy import create_engine, inspect
|
11
11
|
|
12
12
|
# Add the parent directory to sys.path to allow relative imports
|
13
13
|
sys.path.append(
|
@@ -19,53 +19,71 @@ try:
|
|
19
19
|
except ImportError:
|
20
20
|
# Fallback path if import fails
|
21
21
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
22
|
-
project_root = os.path.abspath(
|
22
|
+
project_root = os.path.abspath(
|
23
|
+
os.path.join(current_dir, "..", "..", "..", "..")
|
24
|
+
)
|
23
25
|
DB_PATH = os.path.join(project_root, "src", "data", "ldr.db")
|
24
26
|
|
27
|
+
from .models import Base, ResearchStrategy
|
28
|
+
|
25
29
|
|
26
|
-
def
|
30
|
+
def remove_research_log_table(engine):
|
27
31
|
"""
|
28
|
-
|
32
|
+
Remove the redundant research_log table if it exists
|
29
33
|
|
30
34
|
Args:
|
31
|
-
|
32
|
-
table_name: Name of the table
|
35
|
+
engine: SQLAlchemy engine
|
33
36
|
|
34
37
|
Returns:
|
35
|
-
bool: True if
|
38
|
+
bool: True if operation was successful, False otherwise
|
36
39
|
"""
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
40
|
+
try:
|
41
|
+
inspector = inspect(engine)
|
42
|
+
|
43
|
+
# Check if table exists
|
44
|
+
if inspector.has_table("research_log"):
|
45
|
+
# For SQLite, we need to use raw SQL for DROP TABLE
|
46
|
+
with engine.connect() as conn:
|
47
|
+
conn.execute("DROP TABLE research_log")
|
48
|
+
conn.commit()
|
49
|
+
logger.info("Successfully removed redundant 'research_log' table")
|
50
|
+
return True
|
51
|
+
else:
|
52
|
+
logger.info("Table 'research_log' does not exist, no action needed")
|
53
|
+
return True
|
54
|
+
except Exception:
|
55
|
+
logger.exception("Error removing research_log table")
|
56
|
+
return False
|
42
57
|
|
43
58
|
|
44
|
-
def
|
59
|
+
def create_research_strategy_table(engine):
|
45
60
|
"""
|
46
|
-
|
61
|
+
Create the research_strategies table if it doesn't exist
|
47
62
|
|
48
63
|
Args:
|
49
|
-
|
64
|
+
engine: SQLAlchemy engine
|
50
65
|
|
51
66
|
Returns:
|
52
67
|
bool: True if operation was successful, False otherwise
|
53
68
|
"""
|
54
69
|
try:
|
55
|
-
|
70
|
+
inspector = inspect(engine)
|
56
71
|
|
57
72
|
# Check if table exists
|
58
|
-
if
|
59
|
-
#
|
60
|
-
|
61
|
-
|
62
|
-
|
73
|
+
if not inspector.has_table("research_strategies"):
|
74
|
+
# Create the table using ORM
|
75
|
+
Base.metadata.create_all(
|
76
|
+
engine, tables=[ResearchStrategy.__table__]
|
77
|
+
)
|
78
|
+
logger.info("Successfully created 'research_strategies' table")
|
63
79
|
return True
|
64
80
|
else:
|
65
|
-
logger.info(
|
81
|
+
logger.info(
|
82
|
+
"Table 'research_strategies' already exists, no action needed"
|
83
|
+
)
|
66
84
|
return True
|
67
85
|
except Exception:
|
68
|
-
logger.exception("Error
|
86
|
+
logger.exception("Error creating research_strategies table")
|
69
87
|
return False
|
70
88
|
|
71
89
|
|
@@ -78,20 +96,22 @@ def run_schema_upgrades():
|
|
78
96
|
"""
|
79
97
|
# Check if database exists
|
80
98
|
if not os.path.exists(DB_PATH):
|
81
|
-
logger.warning(
|
99
|
+
logger.warning(
|
100
|
+
f"Database not found at {DB_PATH}, skipping schema upgrades"
|
101
|
+
)
|
82
102
|
return False
|
83
103
|
|
84
104
|
logger.info(f"Running schema upgrades on {DB_PATH}")
|
85
105
|
|
86
106
|
try:
|
87
|
-
#
|
88
|
-
|
107
|
+
# Create SQLAlchemy engine
|
108
|
+
engine = create_engine(f"sqlite:///{DB_PATH}")
|
89
109
|
|
90
110
|
# 1. Remove the redundant research_log table
|
91
|
-
remove_research_log_table(
|
111
|
+
remove_research_log_table(engine)
|
92
112
|
|
93
|
-
#
|
94
|
-
|
113
|
+
# 2. Create research_strategies table
|
114
|
+
create_research_strategy_table(engine)
|
95
115
|
|
96
116
|
logger.info("Schema upgrades completed successfully")
|
97
117
|
return True
|
@@ -1,10 +1,12 @@
|
|
1
|
-
import json
|
2
1
|
import os
|
3
2
|
import sqlite3
|
4
3
|
from datetime import datetime
|
5
4
|
|
6
5
|
from loguru import logger
|
7
6
|
|
7
|
+
from ...utilities.db_utils import get_db_session
|
8
|
+
from ..database.models import ResearchLog
|
9
|
+
|
8
10
|
# Database path
|
9
11
|
# Use unified database in data directory
|
10
12
|
DATA_DIR = os.path.abspath(
|
@@ -15,7 +17,9 @@ DB_PATH = os.path.join(DATA_DIR, "ldr.db")
|
|
15
17
|
|
16
18
|
# Legacy database paths (for migration)
|
17
19
|
LEGACY_RESEARCH_HISTORY_DB = os.path.abspath(
|
18
|
-
os.path.join(
|
20
|
+
os.path.join(
|
21
|
+
os.path.dirname(__file__), "..", "..", "..", "research_history.db"
|
22
|
+
)
|
19
23
|
)
|
20
24
|
LEGACY_DEEP_RESEARCH_DB = os.path.join(
|
21
25
|
os.path.abspath(
|
@@ -97,7 +101,9 @@ def init_db():
|
|
97
101
|
columns = [column[1] for column in cursor.fetchall()]
|
98
102
|
|
99
103
|
if "duration_seconds" not in columns:
|
100
|
-
print(
|
104
|
+
print(
|
105
|
+
"Adding missing 'duration_seconds' column to research_history table"
|
106
|
+
)
|
101
107
|
cursor.execute(
|
102
108
|
"ALTER TABLE research_history ADD COLUMN duration_seconds INTEGER"
|
103
109
|
)
|
@@ -105,7 +111,9 @@ def init_db():
|
|
105
111
|
# Check if the progress column exists, add it if missing
|
106
112
|
if "progress" not in columns:
|
107
113
|
print("Adding missing 'progress' column to research_history table")
|
108
|
-
cursor.execute(
|
114
|
+
cursor.execute(
|
115
|
+
"ALTER TABLE research_history ADD COLUMN progress INTEGER"
|
116
|
+
)
|
109
117
|
|
110
118
|
# Check if the title column exists, add it if missing
|
111
119
|
if "title" not in columns:
|
@@ -180,10 +188,14 @@ def calculate_duration(created_at_str, completed_at_str=None):
|
|
180
188
|
else: # Older format without T
|
181
189
|
# Try different formats
|
182
190
|
try:
|
183
|
-
start_time = datetime.strptime(
|
191
|
+
start_time = datetime.strptime(
|
192
|
+
created_at_str, "%Y-%m-%d %H:%M:%S.%f"
|
193
|
+
)
|
184
194
|
except ValueError:
|
185
195
|
try:
|
186
|
-
start_time = datetime.strptime(
|
196
|
+
start_time = datetime.strptime(
|
197
|
+
created_at_str, "%Y-%m-%d %H:%M:%S"
|
198
|
+
)
|
187
199
|
except ValueError:
|
188
200
|
# Last resort fallback
|
189
201
|
start_time = datetime.fromisoformat(
|
@@ -198,7 +210,7 @@ def calculate_duration(created_at_str, completed_at_str=None):
|
|
198
210
|
start_time = parser.parse(created_at_str)
|
199
211
|
except Exception:
|
200
212
|
logger.exception(
|
201
|
-
f"Fallback parsing also failed for created_at:
|
213
|
+
f"Fallback parsing also failed for created_at: {created_at_str}"
|
202
214
|
)
|
203
215
|
return None
|
204
216
|
|
@@ -212,36 +224,6 @@ def calculate_duration(created_at_str, completed_at_str=None):
|
|
212
224
|
return None
|
213
225
|
|
214
226
|
|
215
|
-
def add_log_to_db(research_id, message, log_type="info", progress=None, metadata=None):
|
216
|
-
"""
|
217
|
-
Store a log entry in the database
|
218
|
-
|
219
|
-
Args:
|
220
|
-
research_id: ID of the research
|
221
|
-
message: Log message text
|
222
|
-
log_type: Type of log (info, error, milestone)
|
223
|
-
progress: Progress percentage (0-100)
|
224
|
-
metadata: Additional metadata as dictionary (will be stored as JSON)
|
225
|
-
"""
|
226
|
-
try:
|
227
|
-
timestamp = datetime.utcnow().isoformat()
|
228
|
-
metadata_json = json.dumps(metadata) if metadata else None
|
229
|
-
|
230
|
-
conn = get_db_connection()
|
231
|
-
cursor = conn.cursor()
|
232
|
-
cursor.execute(
|
233
|
-
"INSERT INTO research_logs (research_id, timestamp, message, log_type, progress, metadata) "
|
234
|
-
"VALUES (?, ?, ?, ?, ?, ?)",
|
235
|
-
(research_id, timestamp, message, log_type, progress, metadata_json),
|
236
|
-
)
|
237
|
-
conn.commit()
|
238
|
-
conn.close()
|
239
|
-
return True
|
240
|
-
except Exception:
|
241
|
-
logger.exception("Error adding log to database")
|
242
|
-
return False
|
243
|
-
|
244
|
-
|
245
227
|
def get_logs_for_research(research_id):
|
246
228
|
"""
|
247
229
|
Retrieve all logs for a specific research ID
|
@@ -253,35 +235,23 @@ def get_logs_for_research(research_id):
|
|
253
235
|
List of log entries as dictionaries
|
254
236
|
"""
|
255
237
|
try:
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
(
|
238
|
+
session = get_db_session()
|
239
|
+
log_results = (
|
240
|
+
session.query(ResearchLog)
|
241
|
+
.filter(ResearchLog.research_id == research_id)
|
242
|
+
.order_by(ResearchLog.timestamp.asc())
|
243
|
+
.all()
|
262
244
|
)
|
263
|
-
results = cursor.fetchall()
|
264
|
-
conn.close()
|
265
245
|
|
266
246
|
logs = []
|
267
|
-
for result in
|
268
|
-
log_entry = dict(result)
|
269
|
-
# Parse metadata JSON if it exists
|
270
|
-
if log_entry.get("metadata"):
|
271
|
-
try:
|
272
|
-
log_entry["metadata"] = json.loads(log_entry["metadata"])
|
273
|
-
except Exception:
|
274
|
-
log_entry["metadata"] = {}
|
275
|
-
else:
|
276
|
-
log_entry["metadata"] = {}
|
277
|
-
|
247
|
+
for result in log_results:
|
278
248
|
# Convert entry for frontend consumption
|
279
249
|
formatted_entry = {
|
280
|
-
"time":
|
281
|
-
"message":
|
282
|
-
"
|
283
|
-
"
|
284
|
-
"
|
250
|
+
"time": result.timestamp,
|
251
|
+
"message": result.message,
|
252
|
+
"type": result.level,
|
253
|
+
"module": result.module,
|
254
|
+
"line_no": result.line_no,
|
285
255
|
}
|
286
256
|
logs.append(formatted_entry)
|
287
257
|
|
@@ -289,3 +259,23 @@ def get_logs_for_research(research_id):
|
|
289
259
|
except Exception:
|
290
260
|
logger.exception("Error retrieving logs from database")
|
291
261
|
return []
|
262
|
+
|
263
|
+
|
264
|
+
@logger.catch
|
265
|
+
def get_total_logs_for_research(research_id):
|
266
|
+
"""
|
267
|
+
Returns the total number of logs for a given `research_id`.
|
268
|
+
|
269
|
+
Args:
|
270
|
+
research_id (int): The ID of the research.
|
271
|
+
|
272
|
+
Returns:
|
273
|
+
int: Total number of logs for the specified research ID.
|
274
|
+
"""
|
275
|
+
session = get_db_session()
|
276
|
+
total_logs = (
|
277
|
+
session.query(ResearchLog)
|
278
|
+
.filter(ResearchLog.research_id == research_id)
|
279
|
+
.count()
|
280
|
+
)
|
281
|
+
return total_logs
|
@@ -59,9 +59,7 @@ def api_start_research():
|
|
59
59
|
mode,
|
60
60
|
"in_progress",
|
61
61
|
created_at,
|
62
|
-
json.dumps(
|
63
|
-
[{"time": created_at, "message": "Research started", "progress": 0}]
|
64
|
-
),
|
62
|
+
json.dumps([{"time": created_at, "progress": 0}]),
|
65
63
|
json.dumps(research_settings),
|
66
64
|
),
|
67
65
|
)
|
@@ -89,9 +87,11 @@ def api_start_research():
|
|
89
87
|
"research_id": research_id,
|
90
88
|
}
|
91
89
|
)
|
92
|
-
except Exception
|
93
|
-
logger.
|
94
|
-
return jsonify(
|
90
|
+
except Exception:
|
91
|
+
logger.exception("Error starting research")
|
92
|
+
return jsonify(
|
93
|
+
{"status": "error", "message": "Failed to start research"}, 500
|
94
|
+
)
|
95
95
|
|
96
96
|
|
97
97
|
@api_bp.route("/status/<int:research_id>", methods=["GET"])
|
@@ -120,7 +120,9 @@ def api_research_status(research_id):
|
|
120
120
|
try:
|
121
121
|
metadata = json.loads(metadata_str)
|
122
122
|
except json.JSONDecodeError:
|
123
|
-
logger.warning(
|
123
|
+
logger.warning(
|
124
|
+
f"Invalid JSON in metadata for research {research_id}"
|
125
|
+
)
|
124
126
|
|
125
127
|
return jsonify(
|
126
128
|
{
|
@@ -144,10 +146,18 @@ def api_terminate_research(research_id):
|
|
144
146
|
try:
|
145
147
|
result = cancel_research(research_id)
|
146
148
|
return jsonify(
|
147
|
-
{
|
149
|
+
{
|
150
|
+
"status": "success",
|
151
|
+
"message": "Research terminated",
|
152
|
+
"result": result,
|
153
|
+
}
|
154
|
+
)
|
155
|
+
except Exception:
|
156
|
+
logger.exception("Error terminating research")
|
157
|
+
return (
|
158
|
+
jsonify({"status": "error", "message": "Failed to stop research."}),
|
159
|
+
500,
|
148
160
|
)
|
149
|
-
except Exception as e:
|
150
|
-
return jsonify({"status": "error", "message": str(e)}), 500
|
151
161
|
|
152
162
|
|
153
163
|
@api_bp.route("/resources/<int:research_id>", methods=["GET"])
|
@@ -158,8 +168,11 @@ def api_get_resources(research_id):
|
|
158
168
|
try:
|
159
169
|
resources = get_resources_for_research(research_id)
|
160
170
|
return jsonify({"status": "success", "resources": resources})
|
161
|
-
except Exception
|
162
|
-
|
171
|
+
except Exception:
|
172
|
+
logger.exception("Error getting resources for research")
|
173
|
+
return jsonify(
|
174
|
+
{"status": "error", "message": "Failed to get resources"}, 500
|
175
|
+
)
|
163
176
|
|
164
177
|
|
165
178
|
@api_bp.route("/resources/<int:research_id>", methods=["POST"])
|
@@ -182,19 +195,25 @@ def api_add_resource(research_id):
|
|
182
195
|
# Validate required fields
|
183
196
|
if not title or not url:
|
184
197
|
return (
|
185
|
-
jsonify(
|
198
|
+
jsonify(
|
199
|
+
{"status": "error", "message": "Title and URL are required"}
|
200
|
+
),
|
186
201
|
400,
|
187
202
|
)
|
188
203
|
|
189
204
|
# Check if the research exists
|
190
205
|
conn = get_db_connection()
|
191
206
|
cursor = conn.cursor()
|
192
|
-
cursor.execute(
|
207
|
+
cursor.execute(
|
208
|
+
"SELECT id FROM research_history WHERE id = ?", (research_id,)
|
209
|
+
)
|
193
210
|
result = cursor.fetchone()
|
194
211
|
conn.close()
|
195
212
|
|
196
213
|
if not result:
|
197
|
-
return jsonify(
|
214
|
+
return jsonify(
|
215
|
+
{"status": "error", "message": "Research not found"}
|
216
|
+
), 404
|
198
217
|
|
199
218
|
# Add the resource
|
200
219
|
resource_id = add_resource(
|
@@ -231,10 +250,15 @@ def api_delete_resource(research_id, resource_id):
|
|
231
250
|
|
232
251
|
if success:
|
233
252
|
return jsonify(
|
234
|
-
{
|
253
|
+
{
|
254
|
+
"status": "success",
|
255
|
+
"message": "Resource deleted successfully",
|
256
|
+
}
|
235
257
|
)
|
236
258
|
else:
|
237
|
-
return jsonify(
|
259
|
+
return jsonify(
|
260
|
+
{"status": "error", "message": "Resource not found"}
|
261
|
+
), 404
|
238
262
|
except Exception as e:
|
239
263
|
logger.error(f"Error deleting resource: {str(e)}")
|
240
264
|
return jsonify({"status": "error", "message": str(e)}), 500
|
@@ -252,7 +276,10 @@ def check_ollama_status():
|
|
252
276
|
|
253
277
|
if provider.lower() != "ollama":
|
254
278
|
return jsonify(
|
255
|
-
{
|
279
|
+
{
|
280
|
+
"running": True,
|
281
|
+
"message": f"Using provider: {provider}, not Ollama",
|
282
|
+
}
|
256
283
|
)
|
257
284
|
|
258
285
|
# Get Ollama API URL from LLM config
|
@@ -272,7 +299,9 @@ def check_ollama_status():
|
|
272
299
|
response = requests.get(f"{ollama_base_url}/api/tags", timeout=5)
|
273
300
|
|
274
301
|
# Add response details for debugging
|
275
|
-
logger.debug(
|
302
|
+
logger.debug(
|
303
|
+
f"Ollama status check response code: {response.status_code}"
|
304
|
+
)
|
276
305
|
|
277
306
|
if response.status_code == 200:
|
278
307
|
# Try to validate the response content
|
@@ -420,7 +449,9 @@ def check_ollama_model():
|
|
420
449
|
|
421
450
|
# Debug log the first bit of the response
|
422
451
|
response_preview = (
|
423
|
-
str(data)[:500] + "..."
|
452
|
+
str(data)[:500] + "..."
|
453
|
+
if len(str(data)) > 500
|
454
|
+
else str(data)
|
424
455
|
)
|
425
456
|
logger.debug(f"Ollama API response data: {response_preview}")
|
426
457
|
|
@@ -448,7 +479,8 @@ def check_ollama_model():
|
|
448
479
|
|
449
480
|
# Case-insensitive model name comparison
|
450
481
|
model_exists = any(
|
451
|
-
m.get("name", "").lower() == model_name.lower()
|
482
|
+
m.get("name", "").lower() == model_name.lower()
|
483
|
+
for m in models
|
452
484
|
)
|
453
485
|
|
454
486
|
if model_exists:
|
@@ -561,7 +593,9 @@ def api_get_config():
|
|
561
593
|
"search_tool", "auto"
|
562
594
|
),
|
563
595
|
"features": {
|
564
|
-
"notifications": current_app.config.get(
|
596
|
+
"notifications": current_app.config.get(
|
597
|
+
"ENABLE_NOTIFICATIONS", False
|
598
|
+
)
|
565
599
|
},
|
566
600
|
}
|
567
601
|
|
@@ -190,7 +190,10 @@ def get_benchmark_config():
|
|
190
190
|
],
|
191
191
|
"evaluation_models": {
|
192
192
|
"openai_endpoint": [
|
193
|
-
{
|
193
|
+
{
|
194
|
+
"id": "anthropic/claude-3.7-sonnet",
|
195
|
+
"name": "Claude 3.7 Sonnet",
|
196
|
+
}
|
194
197
|
],
|
195
198
|
"openai": [
|
196
199
|
{"id": "gpt-4o", "name": "GPT-4o"},
|
@@ -0,0 +1,22 @@
|
|
1
|
+
"""
|
2
|
+
Stores global state.
|
3
|
+
"""
|
4
|
+
|
5
|
+
# Active research processes and socket subscriptions
|
6
|
+
active_research = {}
|
7
|
+
socket_subscriptions = {}
|
8
|
+
# Add termination flags dictionary
|
9
|
+
termination_flags = {}
|
10
|
+
|
11
|
+
|
12
|
+
def get_globals():
|
13
|
+
"""
|
14
|
+
Returns:
|
15
|
+
Global state for other modules to access.
|
16
|
+
|
17
|
+
"""
|
18
|
+
return {
|
19
|
+
"active_research": active_research,
|
20
|
+
"socket_subscriptions": socket_subscriptions,
|
21
|
+
"termination_flags": termination_flags,
|
22
|
+
}
|