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
@@ -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,14 @@ from loguru import logger
|
|
2
2
|
from sqlalchemy import inspect
|
3
3
|
|
4
4
|
from ..services.settings_manager import SettingsManager
|
5
|
-
from .models import
|
5
|
+
from .models import (
|
6
|
+
Base,
|
7
|
+
Journal,
|
8
|
+
Setting,
|
9
|
+
ResearchLog,
|
10
|
+
Research,
|
11
|
+
ResearchHistory,
|
12
|
+
)
|
6
13
|
|
7
14
|
|
8
15
|
def import_default_settings_file(db_session):
|
@@ -24,14 +31,12 @@ def import_default_settings_file(db_session):
|
|
24
31
|
settings_mgr.load_from_defaults_file(overwrite=False, delete_extra=True)
|
25
32
|
logger.info("Successfully imported settings from files")
|
26
33
|
|
27
|
-
|
28
34
|
# Update the saved version.
|
29
35
|
settings_mgr.update_db_version()
|
30
36
|
except Exception as e:
|
31
37
|
logger.error("Error importing settings from files: %s", e)
|
32
38
|
|
33
39
|
|
34
|
-
|
35
40
|
def run_migrations(engine, db_session=None):
|
36
41
|
"""
|
37
42
|
Run any necessary database migrations
|
@@ -50,6 +55,18 @@ def run_migrations(engine, db_session=None):
|
|
50
55
|
logger.info("Creating journals table.")
|
51
56
|
Base.metadata.create_all(engine, tables=[Journal.__table__])
|
52
57
|
|
58
|
+
if not inspector.has_table(ResearchLog.__tablename__):
|
59
|
+
logger.info("Creating research logs table.")
|
60
|
+
Base.metadata.create_all(engine, tables=[ResearchLog.__table__])
|
61
|
+
|
62
|
+
if not inspector.has_table(Research.__tablename__):
|
63
|
+
logger.info("Creating research table.")
|
64
|
+
Base.metadata.create_all(engine, tables=[Research.__table__])
|
65
|
+
|
66
|
+
if not inspector.has_table(ResearchHistory.__tablename__):
|
67
|
+
logger.info("Creating research table.")
|
68
|
+
Base.metadata.create_all(engine, tables=[ResearchHistory.__table__])
|
69
|
+
|
53
70
|
# Import existing settings from files
|
54
71
|
if db_session:
|
55
72
|
import_default_settings_file(db_session)
|
@@ -34,6 +34,37 @@ class ResearchStatus(enum.Enum):
|
|
34
34
|
CANCELLED = "cancelled"
|
35
35
|
|
36
36
|
|
37
|
+
class ResearchHistory(Base):
|
38
|
+
"""Represents the research table."""
|
39
|
+
|
40
|
+
__tablename__ = "research_history"
|
41
|
+
|
42
|
+
# Unique identifier for each record.
|
43
|
+
id = Column(Integer, primary_key=True, autoincrement=True)
|
44
|
+
# The search query.
|
45
|
+
query = Column(Text, nullable=False)
|
46
|
+
# The mode of research (e.g., 'quick_summary', 'detailed_report').
|
47
|
+
mode = Column(Text, nullable=False)
|
48
|
+
# Current status of the research.
|
49
|
+
status = Column(Text, nullable=False)
|
50
|
+
# The timestamp when the research started.
|
51
|
+
created_at = Column(Text, nullable=False)
|
52
|
+
# The timestamp when the research was completed.
|
53
|
+
completed_at = Column(Text)
|
54
|
+
# Duration of the research in seconds.
|
55
|
+
duration_seconds = Column(Integer)
|
56
|
+
# Path to the generated report.
|
57
|
+
report_path = Column(Text)
|
58
|
+
# Additional metadata about the research.
|
59
|
+
research_meta = Column(JSON)
|
60
|
+
# Latest progress log message.
|
61
|
+
progress_log = Column(JSON)
|
62
|
+
# Current progress of the research (as a percentage).
|
63
|
+
progress = Column(Integer)
|
64
|
+
# Title of the research report.
|
65
|
+
title = Column(Text)
|
66
|
+
|
67
|
+
|
37
68
|
class Research(Base):
|
38
69
|
__tablename__ = "research"
|
39
70
|
|
@@ -42,7 +73,9 @@ class Research(Base):
|
|
42
73
|
status = Column(
|
43
74
|
Enum(ResearchStatus), default=ResearchStatus.PENDING, nullable=False
|
44
75
|
)
|
45
|
-
mode = Column(
|
76
|
+
mode = Column(
|
77
|
+
Enum(ResearchMode), default=ResearchMode.QUICK, nullable=False
|
78
|
+
)
|
46
79
|
created_at = Column(DateTime, server_default=func.now(), nullable=False)
|
47
80
|
updated_at = Column(
|
48
81
|
DateTime, server_default=func.now(), onupdate=func.now(), nullable=False
|
@@ -52,36 +85,30 @@ class Research(Base):
|
|
52
85
|
end_time = Column(DateTime, nullable=True)
|
53
86
|
error_message = Column(Text, nullable=True)
|
54
87
|
|
55
|
-
# Relationships
|
56
|
-
report = relationship(
|
57
|
-
"ResearchReport",
|
58
|
-
back_populates="research",
|
59
|
-
uselist=False,
|
60
|
-
cascade="all, delete-orphan",
|
61
|
-
)
|
62
88
|
|
89
|
+
class ResearchLog(Base):
|
90
|
+
__tablename__ = "app_logs"
|
63
91
|
|
64
|
-
|
65
|
-
|
92
|
+
id = Column(
|
93
|
+
Integer, Sequence("reseach_log_id_seq"), primary_key=True, index=True
|
94
|
+
)
|
66
95
|
|
67
|
-
|
96
|
+
timestamp = Column(DateTime, server_default=func.now(), nullable=False)
|
97
|
+
message = Column(Text, nullable=False)
|
98
|
+
# Module that the log message came from.
|
99
|
+
module = Column(Text, nullable=False)
|
100
|
+
# Function that the log message came from.
|
101
|
+
function = Column(Text, nullable=False)
|
102
|
+
# Line number that the log message came from.
|
103
|
+
line_no = Column(Integer, nullable=False)
|
104
|
+
# Log level.
|
105
|
+
level = Column(String(32), nullable=False)
|
68
106
|
research_id = Column(
|
69
107
|
Integer,
|
70
108
|
ForeignKey("research.id", ondelete="CASCADE"),
|
71
|
-
nullable=
|
72
|
-
|
109
|
+
nullable=True,
|
110
|
+
index=True,
|
73
111
|
)
|
74
|
-
content = Column(Text, nullable=True)
|
75
|
-
created_at = Column(DateTime, server_default=func.now(), nullable=False)
|
76
|
-
updated_at = Column(
|
77
|
-
DateTime, server_default=func.now(), onupdate=func.now(), nullable=False
|
78
|
-
)
|
79
|
-
report_metadata = Column(
|
80
|
-
JSON, nullable=True
|
81
|
-
) # Additional metadata about the report
|
82
|
-
|
83
|
-
# Relationships
|
84
|
-
research = relationship("Research", back_populates="report")
|
85
112
|
|
86
113
|
|
87
114
|
class SettingType(enum.Enum):
|
@@ -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(
|
@@ -39,26 +43,6 @@ def init_db():
|
|
39
43
|
conn = get_db_connection()
|
40
44
|
cursor = conn.cursor()
|
41
45
|
|
42
|
-
# Create the table if it doesn't exist
|
43
|
-
cursor.execute(
|
44
|
-
"""
|
45
|
-
CREATE TABLE IF NOT EXISTS research_history (
|
46
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
47
|
-
query TEXT NOT NULL,
|
48
|
-
mode TEXT NOT NULL,
|
49
|
-
status TEXT NOT NULL,
|
50
|
-
created_at TEXT NOT NULL,
|
51
|
-
completed_at TEXT,
|
52
|
-
duration_seconds INTEGER,
|
53
|
-
report_path TEXT,
|
54
|
-
metadata TEXT,
|
55
|
-
progress_log TEXT,
|
56
|
-
progress INTEGER,
|
57
|
-
title TEXT
|
58
|
-
)
|
59
|
-
"""
|
60
|
-
)
|
61
|
-
|
62
46
|
# Create a dedicated table for research logs
|
63
47
|
cursor.execute(
|
64
48
|
"""
|
@@ -97,21 +81,35 @@ def init_db():
|
|
97
81
|
columns = [column[1] for column in cursor.fetchall()]
|
98
82
|
|
99
83
|
if "duration_seconds" not in columns:
|
100
|
-
|
84
|
+
logger.info(
|
85
|
+
"Adding missing 'duration_seconds' column to research_history table"
|
86
|
+
)
|
101
87
|
cursor.execute(
|
102
88
|
"ALTER TABLE research_history ADD COLUMN duration_seconds INTEGER"
|
103
89
|
)
|
104
90
|
|
105
91
|
# Check if the progress column exists, add it if missing
|
106
92
|
if "progress" not in columns:
|
107
|
-
|
108
|
-
|
93
|
+
logger.info(
|
94
|
+
"Adding missing 'progress' column to research_history table"
|
95
|
+
)
|
96
|
+
cursor.execute(
|
97
|
+
"ALTER TABLE research_history ADD COLUMN progress INTEGER"
|
98
|
+
)
|
109
99
|
|
110
100
|
# Check if the title column exists, add it if missing
|
111
101
|
if "title" not in columns:
|
112
|
-
|
102
|
+
logger.info("Adding missing 'title' column to research_history table")
|
113
103
|
cursor.execute("ALTER TABLE research_history ADD COLUMN title TEXT")
|
114
104
|
|
105
|
+
# Check if the metadata column exists, and rename it to "research_meta"
|
106
|
+
# if it does.
|
107
|
+
if "metadata" in columns:
|
108
|
+
logger.info("Renaming 'metadata' column to 'research_meta'")
|
109
|
+
cursor.execute(
|
110
|
+
"ALTER TABLE research_history RENAME COLUMN metadata TO research_meta"
|
111
|
+
)
|
112
|
+
|
115
113
|
# Enable foreign key support
|
116
114
|
cursor.execute("PRAGMA foreign_keys = ON")
|
117
115
|
|
@@ -180,10 +178,14 @@ def calculate_duration(created_at_str, completed_at_str=None):
|
|
180
178
|
else: # Older format without T
|
181
179
|
# Try different formats
|
182
180
|
try:
|
183
|
-
start_time = datetime.strptime(
|
181
|
+
start_time = datetime.strptime(
|
182
|
+
created_at_str, "%Y-%m-%d %H:%M:%S.%f"
|
183
|
+
)
|
184
184
|
except ValueError:
|
185
185
|
try:
|
186
|
-
start_time = datetime.strptime(
|
186
|
+
start_time = datetime.strptime(
|
187
|
+
created_at_str, "%Y-%m-%d %H:%M:%S"
|
188
|
+
)
|
187
189
|
except ValueError:
|
188
190
|
# Last resort fallback
|
189
191
|
start_time = datetime.fromisoformat(
|
@@ -198,7 +200,7 @@ def calculate_duration(created_at_str, completed_at_str=None):
|
|
198
200
|
start_time = parser.parse(created_at_str)
|
199
201
|
except Exception:
|
200
202
|
logger.exception(
|
201
|
-
f"Fallback parsing also failed for created_at:
|
203
|
+
f"Fallback parsing also failed for created_at: {created_at_str}"
|
202
204
|
)
|
203
205
|
return None
|
204
206
|
|
@@ -212,36 +214,6 @@ def calculate_duration(created_at_str, completed_at_str=None):
|
|
212
214
|
return None
|
213
215
|
|
214
216
|
|
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
217
|
def get_logs_for_research(research_id):
|
246
218
|
"""
|
247
219
|
Retrieve all logs for a specific research ID
|
@@ -253,35 +225,23 @@ def get_logs_for_research(research_id):
|
|
253
225
|
List of log entries as dictionaries
|
254
226
|
"""
|
255
227
|
try:
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
(
|
228
|
+
session = get_db_session()
|
229
|
+
log_results = (
|
230
|
+
session.query(ResearchLog)
|
231
|
+
.filter(ResearchLog.research_id == research_id)
|
232
|
+
.order_by(ResearchLog.timestamp.asc())
|
233
|
+
.all()
|
262
234
|
)
|
263
|
-
results = cursor.fetchall()
|
264
|
-
conn.close()
|
265
235
|
|
266
236
|
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
|
-
|
237
|
+
for result in log_results:
|
278
238
|
# Convert entry for frontend consumption
|
279
239
|
formatted_entry = {
|
280
|
-
"time":
|
281
|
-
"message":
|
282
|
-
"
|
283
|
-
"
|
284
|
-
"
|
240
|
+
"time": result.timestamp,
|
241
|
+
"message": result.message,
|
242
|
+
"type": result.level,
|
243
|
+
"module": result.module,
|
244
|
+
"line_no": result.line_no,
|
285
245
|
}
|
286
246
|
logs.append(formatted_entry)
|
287
247
|
|
@@ -289,3 +249,23 @@ def get_logs_for_research(research_id):
|
|
289
249
|
except Exception:
|
290
250
|
logger.exception("Error retrieving logs from database")
|
291
251
|
return []
|
252
|
+
|
253
|
+
|
254
|
+
@logger.catch
|
255
|
+
def get_total_logs_for_research(research_id):
|
256
|
+
"""
|
257
|
+
Returns the total number of logs for a given `research_id`.
|
258
|
+
|
259
|
+
Args:
|
260
|
+
research_id (int): The ID of the research.
|
261
|
+
|
262
|
+
Returns:
|
263
|
+
int: Total number of logs for the specified research ID.
|
264
|
+
"""
|
265
|
+
session = get_db_session()
|
266
|
+
total_logs = (
|
267
|
+
session.query(ResearchLog)
|
268
|
+
.filter(ResearchLog.research_id == research_id)
|
269
|
+
.count()
|
270
|
+
)
|
271
|
+
return total_logs
|