local-deep-research 0.3.12__py3-none-any.whl → 0.4.1__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.
Files changed (94) hide show
  1. local_deep_research/__init__.py +1 -0
  2. local_deep_research/__version__.py +1 -1
  3. local_deep_research/advanced_search_system/filters/base_filter.py +2 -3
  4. local_deep_research/advanced_search_system/filters/cross_engine_filter.py +4 -5
  5. local_deep_research/advanced_search_system/filters/journal_reputation_filter.py +298 -0
  6. local_deep_research/advanced_search_system/findings/repository.py +0 -3
  7. local_deep_research/advanced_search_system/strategies/base_strategy.py +1 -2
  8. local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +14 -18
  9. local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +4 -8
  10. local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +5 -6
  11. local_deep_research/advanced_search_system/strategies/source_based_strategy.py +2 -2
  12. local_deep_research/advanced_search_system/strategies/standard_strategy.py +9 -7
  13. local_deep_research/api/benchmark_functions.py +288 -0
  14. local_deep_research/api/research_functions.py +8 -4
  15. local_deep_research/benchmarks/README.md +162 -0
  16. local_deep_research/benchmarks/__init__.py +51 -0
  17. local_deep_research/benchmarks/benchmark_functions.py +353 -0
  18. local_deep_research/benchmarks/cli/__init__.py +16 -0
  19. local_deep_research/benchmarks/cli/benchmark_commands.py +338 -0
  20. local_deep_research/benchmarks/cli.py +347 -0
  21. local_deep_research/benchmarks/comparison/__init__.py +12 -0
  22. local_deep_research/benchmarks/comparison/evaluator.py +768 -0
  23. local_deep_research/benchmarks/datasets/__init__.py +53 -0
  24. local_deep_research/benchmarks/datasets/base.py +295 -0
  25. local_deep_research/benchmarks/datasets/browsecomp.py +116 -0
  26. local_deep_research/benchmarks/datasets/custom_dataset_template.py +98 -0
  27. local_deep_research/benchmarks/datasets/simpleqa.py +74 -0
  28. local_deep_research/benchmarks/datasets/utils.py +116 -0
  29. local_deep_research/benchmarks/datasets.py +31 -0
  30. local_deep_research/benchmarks/efficiency/__init__.py +14 -0
  31. local_deep_research/benchmarks/efficiency/resource_monitor.py +367 -0
  32. local_deep_research/benchmarks/efficiency/speed_profiler.py +214 -0
  33. local_deep_research/benchmarks/evaluators/__init__.py +18 -0
  34. local_deep_research/benchmarks/evaluators/base.py +74 -0
  35. local_deep_research/benchmarks/evaluators/browsecomp.py +83 -0
  36. local_deep_research/benchmarks/evaluators/composite.py +121 -0
  37. local_deep_research/benchmarks/evaluators/simpleqa.py +271 -0
  38. local_deep_research/benchmarks/graders.py +410 -0
  39. local_deep_research/benchmarks/metrics/README.md +80 -0
  40. local_deep_research/benchmarks/metrics/__init__.py +24 -0
  41. local_deep_research/benchmarks/metrics/calculation.py +385 -0
  42. local_deep_research/benchmarks/metrics/reporting.py +155 -0
  43. local_deep_research/benchmarks/metrics/visualization.py +205 -0
  44. local_deep_research/benchmarks/metrics.py +11 -0
  45. local_deep_research/benchmarks/optimization/__init__.py +32 -0
  46. local_deep_research/benchmarks/optimization/api.py +274 -0
  47. local_deep_research/benchmarks/optimization/metrics.py +20 -0
  48. local_deep_research/benchmarks/optimization/optuna_optimizer.py +1163 -0
  49. local_deep_research/benchmarks/runners.py +434 -0
  50. local_deep_research/benchmarks/templates.py +65 -0
  51. local_deep_research/config/llm_config.py +26 -23
  52. local_deep_research/config/search_config.py +1 -5
  53. local_deep_research/defaults/default_settings.json +108 -7
  54. local_deep_research/search_system.py +16 -8
  55. local_deep_research/utilities/db_utils.py +3 -6
  56. local_deep_research/utilities/es_utils.py +441 -0
  57. local_deep_research/utilities/log_utils.py +36 -0
  58. local_deep_research/utilities/search_utilities.py +8 -9
  59. local_deep_research/web/app.py +15 -10
  60. local_deep_research/web/app_factory.py +9 -12
  61. local_deep_research/web/database/migrations.py +8 -5
  62. local_deep_research/web/database/models.py +20 -0
  63. local_deep_research/web/database/schema_upgrade.py +5 -8
  64. local_deep_research/web/models/database.py +15 -18
  65. local_deep_research/web/routes/benchmark_routes.py +427 -0
  66. local_deep_research/web/routes/research_routes.py +13 -17
  67. local_deep_research/web/routes/settings_routes.py +264 -67
  68. local_deep_research/web/services/research_service.py +58 -73
  69. local_deep_research/web/services/settings_manager.py +1 -4
  70. local_deep_research/web/services/settings_service.py +4 -6
  71. local_deep_research/web/static/css/styles.css +12 -0
  72. local_deep_research/web/static/js/components/logpanel.js +164 -155
  73. local_deep_research/web/static/js/components/research.js +44 -3
  74. local_deep_research/web/static/js/components/settings.js +27 -0
  75. local_deep_research/web/static/js/services/socket.js +47 -0
  76. local_deep_research/web_search_engines/default_search_engines.py +38 -0
  77. local_deep_research/web_search_engines/engines/meta_search_engine.py +100 -33
  78. local_deep_research/web_search_engines/engines/search_engine_arxiv.py +31 -17
  79. local_deep_research/web_search_engines/engines/search_engine_brave.py +8 -3
  80. local_deep_research/web_search_engines/engines/search_engine_elasticsearch.py +343 -0
  81. local_deep_research/web_search_engines/engines/search_engine_google_pse.py +14 -6
  82. local_deep_research/web_search_engines/engines/search_engine_local.py +19 -23
  83. local_deep_research/web_search_engines/engines/search_engine_local_all.py +9 -12
  84. local_deep_research/web_search_engines/engines/search_engine_searxng.py +12 -17
  85. local_deep_research/web_search_engines/engines/search_engine_serpapi.py +8 -4
  86. local_deep_research/web_search_engines/search_engine_base.py +22 -5
  87. local_deep_research/web_search_engines/search_engine_factory.py +30 -11
  88. local_deep_research/web_search_engines/search_engines_config.py +14 -1
  89. {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.1.dist-info}/METADATA +10 -2
  90. {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.1.dist-info}/RECORD +93 -51
  91. local_deep_research/app.py +0 -8
  92. {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.1.dist-info}/WHEEL +0 -0
  93. {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.1.dist-info}/entry_points.txt +0 -0
  94. {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,36 @@
1
+ """
2
+ Utilities for logging.
3
+ """
4
+
5
+ import inspect
6
+ import logging
7
+
8
+ from loguru import logger
9
+
10
+
11
+ class InterceptHandler(logging.Handler):
12
+ """
13
+ Intercepts logging messages and forwards them to Loguru's logger.
14
+ """
15
+
16
+ def emit(self, record: logging.LogRecord) -> None:
17
+ # Get corresponding Loguru level if it exists.
18
+ try:
19
+ level: str | int = logger.level(record.levelname).name
20
+ except ValueError:
21
+ level = record.levelno
22
+
23
+ # Find caller from where originated the logged message.
24
+ frame, depth = inspect.currentframe(), 0
25
+ while frame:
26
+ filename = frame.f_code.co_filename
27
+ is_logging = filename == logging.__file__
28
+ is_frozen = "importlib" in filename and "_bootstrap" in filename
29
+ if depth > 0 and not (is_logging or is_frozen):
30
+ break
31
+ frame = frame.f_back
32
+ depth += 1
33
+
34
+ logger.opt(depth=depth, exception=record.exc_info).log(
35
+ level, record.getMessage()
36
+ )
@@ -1,8 +1,7 @@
1
- import logging
2
1
  import re
3
2
  from typing import Dict, List
4
3
 
5
- logger = logging.getLogger(__name__)
4
+ from loguru import logger
6
5
 
7
6
 
8
7
  def remove_think_tags(text: str) -> str:
@@ -36,9 +35,9 @@ def extract_links_from_search_results(search_results: List[Dict]) -> List[Dict]:
36
35
 
37
36
  if title and url:
38
37
  links.append({"title": title, "url": url, "index": index})
39
- except Exception as e:
38
+ except Exception:
40
39
  # Log the specific error for debugging
41
- logger.error(f"Error extracting link from result: {str(e)}")
40
+ logger.exception("Error extracting link from result")
42
41
  continue
43
42
  return links
44
43
 
@@ -111,8 +110,8 @@ def format_findings(
111
110
  try:
112
111
  links = extract_links_from_search_results(search_results)
113
112
  all_links.extend(links)
114
- except Exception as link_err:
115
- logger.error(f"Error processing search results/links: {link_err}")
113
+ except Exception:
114
+ logger.exception("Error processing search results/links")
116
115
 
117
116
  # Start with the synthesized content (passed as synthesized_content)
118
117
  formatted_text += f"{synthesized_content}\n\n"
@@ -215,9 +214,9 @@ def format_findings(
215
214
  if links:
216
215
  formatted_text += "### SOURCES USED IN THIS SECTION:\n"
217
216
  formatted_text += format_links_to_markdown(links) + "\n\n"
218
- except Exception as link_err:
219
- logger.error(
220
- f"Error processing search results/links for finding {idx}: {link_err}"
217
+ except Exception:
218
+ logger.exception(
219
+ f"Error processing search results/links for finding {idx}"
221
220
  )
222
221
  else:
223
222
  logger.debug(f"No search_results found for finding item {idx}.")
@@ -1,7 +1,8 @@
1
- import logging
2
1
  import os
3
2
  import sys
4
3
 
4
+ from loguru import logger
5
+
5
6
  from ..setup_data_dir import setup_data_dir
6
7
  from ..utilities.db_utils import get_db_setting
7
8
  from .app_factory import create_app
@@ -11,9 +12,6 @@ from .models.database import (
11
12
  LEGACY_RESEARCH_HISTORY_DB,
12
13
  )
13
14
 
14
- # Initialize logger
15
- logger = logging.getLogger(__name__)
16
-
17
15
  # Ensure data directory exists
18
16
  setup_data_dir()
19
17
 
@@ -24,8 +22,8 @@ if os.path.exists(DB_PATH):
24
22
  from .database.schema_upgrade import run_schema_upgrades
25
23
 
26
24
  run_schema_upgrades()
27
- except Exception as e:
28
- logger.error(f"Error running schema upgrades: {e}")
25
+ except Exception:
26
+ logger.exception("Error running schema upgrades")
29
27
  logger.warning("Continuing without schema upgrades")
30
28
 
31
29
 
@@ -51,6 +49,7 @@ def check_migration_needed():
51
49
  app, socketio = create_app()
52
50
 
53
51
 
52
+ @logger.catch()
54
53
  def main():
55
54
  """
56
55
  Entry point for the web application when run as a command.
@@ -85,9 +84,8 @@ def main():
85
84
  logger.info("Database migration completed successfully.")
86
85
  else:
87
86
  logger.warning("Database migration failed.")
88
- except Exception as e:
89
- logger.error(f"Error running database migration: {e}")
90
- print(f"Error: {e}")
87
+ except Exception:
88
+ logger.exception("Error running database migration")
91
89
  print("Please run migration manually.")
92
90
 
93
91
  # Get web server settings with defaults
@@ -96,7 +94,14 @@ def main():
96
94
  debug = get_db_setting("web.debug", True)
97
95
 
98
96
  logger.info(f"Starting web server on {host}:{port} (debug: {debug})")
99
- socketio.run(app, debug=debug, host=host, port=port, allow_unsafe_werkzeug=True)
97
+ socketio.run(
98
+ app,
99
+ debug=debug,
100
+ host=host,
101
+ port=port,
102
+ allow_unsafe_werkzeug=True,
103
+ use_reloader=False,
104
+ )
100
105
 
101
106
 
102
107
  if __name__ == "__main__":
@@ -13,12 +13,11 @@ from flask import (
13
13
  )
14
14
  from flask_socketio import SocketIO
15
15
  from flask_wtf.csrf import CSRFProtect
16
+ from loguru import logger
16
17
 
18
+ from ..utilities.log_utils import InterceptHandler
17
19
  from .models.database import DB_PATH, init_db
18
20
 
19
- # Initialize logger
20
- logger = logging.getLogger(__name__)
21
-
22
21
 
23
22
  def create_app():
24
23
  """
@@ -27,11 +26,9 @@ def create_app():
27
26
  Returns:
28
27
  tuple: (app, socketio) - The configured Flask app and SocketIO instance
29
28
  """
30
- # Configure logging
31
- logging.basicConfig(level=logging.INFO)
32
-
33
29
  # Set Werkzeug logger to WARNING level to suppress Socket.IO polling logs
34
30
  logging.getLogger("werkzeug").setLevel(logging.WARNING)
31
+ logging.getLogger("werkzeug").addHandler(InterceptHandler())
35
32
 
36
33
  try:
37
34
  # Get directories based on package installation
@@ -42,11 +39,11 @@ def create_app():
42
39
 
43
40
  # Initialize Flask app with package directories
44
41
  app = Flask(__name__, static_folder=STATIC_DIR, template_folder=TEMPLATE_DIR)
45
- print(f"Using package static path: {STATIC_DIR}")
46
- print(f"Using package template path: {TEMPLATE_DIR}")
47
- except Exception as e:
42
+ logger.debug(f"Using package static path: {STATIC_DIR}")
43
+ logger.debug(f"Using package template path: {TEMPLATE_DIR}")
44
+ except Exception:
48
45
  # Fallback for development
49
- print(f"Package directories not found, using fallback paths: {str(e)}")
46
+ logger.exception("Package directories not found, using fallback paths")
50
47
  app = Flask(
51
48
  __name__,
52
49
  static_folder=os.path.abspath("static"),
@@ -142,8 +139,8 @@ def apply_middleware(app):
142
139
  try:
143
140
  if not request.environ.get("werkzeug.socket"):
144
141
  return
145
- except Exception as e:
146
- print(f"WebSocket preprocessing error: {e}")
142
+ except Exception:
143
+ logger.exception("WebSocket preprocessing error")
147
144
  # Return empty response to prevent further processing
148
145
  return "", 200
149
146
 
@@ -1,11 +1,8 @@
1
- import logging
2
-
1
+ from loguru import logger
3
2
  from sqlalchemy import inspect
4
3
 
5
4
  from ..services.settings_manager import SettingsManager
6
- from .models import Base, Setting
7
-
8
- logger = logging.getLogger(__name__)
5
+ from .models import Base, Journal, Setting
9
6
 
10
7
 
11
8
  def import_default_settings_file(db_session):
@@ -27,12 +24,14 @@ def import_default_settings_file(db_session):
27
24
  settings_mgr.load_from_defaults_file(overwrite=False, delete_extra=True)
28
25
  logger.info("Successfully imported settings from files")
29
26
 
27
+
30
28
  # Update the saved version.
31
29
  settings_mgr.update_db_version()
32
30
  except Exception as e:
33
31
  logger.error("Error importing settings from files: %s", e)
34
32
 
35
33
 
34
+
36
35
  def run_migrations(engine, db_session=None):
37
36
  """
38
37
  Run any necessary database migrations
@@ -47,6 +46,10 @@ def run_migrations(engine, db_session=None):
47
46
  logger.info("Creating settings table")
48
47
  Base.metadata.create_all(engine, tables=[Setting.__table__])
49
48
 
49
+ if not inspector.has_table(Journal.__tablename__):
50
+ logger.info("Creating journals table.")
51
+ Base.metadata.create_all(engine, tables=[Journal.__table__])
52
+
50
53
  # Import existing settings from files
51
54
  if db_session:
52
55
  import_default_settings_file(db_session)
@@ -9,6 +9,7 @@ from sqlalchemy import (
9
9
  Float,
10
10
  ForeignKey,
11
11
  Integer,
12
+ Sequence,
12
13
  String,
13
14
  Text,
14
15
  UniqueConstraint,
@@ -115,3 +116,22 @@ class Setting(Base):
115
116
  )
116
117
 
117
118
  __table_args__ = (UniqueConstraint("key", name="uix_settings_key"),)
119
+
120
+
121
+ class Journal(Base):
122
+ """
123
+ Database model for storing information about academic journals.
124
+ """
125
+
126
+ __tablename__ = "journals"
127
+
128
+ id = Column(Integer, Sequence("journal_id_seq"), primary_key=True, index=True)
129
+
130
+ # Name of the journal
131
+ name = Column(String(255), nullable=False, unique=True, index=True)
132
+ # Quality score of the journal
133
+ quality = Column(Integer, nullable=True)
134
+ # Model that was used to generate the quality score.
135
+ quality_model = Column(String(255), nullable=True, index=True)
136
+ # Time at which the quality was last analyzed.
137
+ quality_analysis_time = Column(Integer, nullable=False)
@@ -3,14 +3,11 @@ Schema upgrade script for Local Deep Research database.
3
3
  Handles schema upgrades for existing ldr.db databases.
4
4
  """
5
5
 
6
- import logging
7
6
  import os
8
7
  import sqlite3
9
8
  import sys
10
9
 
11
- # Set up logging
12
- logging.basicConfig(level=logging.INFO)
13
- logger = logging.getLogger(__name__)
10
+ from loguru import logger
14
11
 
15
12
  # Add the parent directory to sys.path to allow relative imports
16
13
  sys.path.append(
@@ -67,8 +64,8 @@ def remove_research_log_table(conn):
67
64
  else:
68
65
  logger.info("Table 'research_log' does not exist, no action needed")
69
66
  return True
70
- except Exception as e:
71
- logger.error(f"Error removing research_log table: {e}")
67
+ except Exception:
68
+ logger.exception("Error removing research_log table")
72
69
  return False
73
70
 
74
71
 
@@ -98,8 +95,8 @@ def run_schema_upgrades():
98
95
 
99
96
  logger.info("Schema upgrades completed successfully")
100
97
  return True
101
- except Exception as e:
102
- logger.error(f"Error during schema upgrades: {e}")
98
+ except Exception:
99
+ logger.exception("Error during schema upgrades")
103
100
  return False
104
101
 
105
102
 
@@ -1,12 +1,9 @@
1
1
  import json
2
- import logging
3
2
  import os
4
3
  import sqlite3
5
- import traceback
6
4
  from datetime import datetime
7
5
 
8
- # Initialize logger
9
- logger = logging.getLogger(__name__)
6
+ from loguru import logger
10
7
 
11
8
  # Database path
12
9
  # Use unified database in data directory
@@ -159,14 +156,14 @@ def calculate_duration(created_at_str, completed_at_str=None):
159
156
  end_time = datetime.fromisoformat(
160
157
  completed_at_str.replace(" ", "T")
161
158
  )
162
- except Exception as e:
163
- print(f"Error parsing completed_at timestamp: {str(e)}")
159
+ except Exception:
160
+ logger.exception("Error parsing completed_at timestamp")
164
161
  try:
165
162
  from dateutil import parser
166
163
 
167
164
  end_time = parser.parse(completed_at_str)
168
165
  except Exception:
169
- print(
166
+ logger.exception(
170
167
  f"Fallback parsing also failed for completed_at: {completed_at_str}"
171
168
  )
172
169
  # Fall back to current time
@@ -192,23 +189,25 @@ def calculate_duration(created_at_str, completed_at_str=None):
192
189
  start_time = datetime.fromisoformat(
193
190
  created_at_str.replace(" ", "T")
194
191
  )
195
- except Exception as e:
196
- print(f"Error parsing created_at timestamp: {str(e)}")
192
+ except Exception:
193
+ logger.exception("Error parsing created_at timestamp")
197
194
  # Fallback method if parsing fails
198
195
  try:
199
196
  from dateutil import parser
200
197
 
201
198
  start_time = parser.parse(created_at_str)
202
199
  except Exception:
203
- print(f"Fallback parsing also failed for created_at: {created_at_str}")
200
+ logger.exception(
201
+ f"Fallback parsing also failed for created_at:" f" {created_at_str}"
202
+ )
204
203
  return None
205
204
 
206
205
  # Calculate duration if both timestamps are valid
207
206
  if start_time and end_time:
208
207
  try:
209
208
  return int((end_time - start_time).total_seconds())
210
- except Exception as e:
211
- print(f"Error calculating duration: {str(e)}")
209
+ except Exception:
210
+ logger.exception("Error calculating duration")
212
211
 
213
212
  return None
214
213
 
@@ -238,9 +237,8 @@ def add_log_to_db(research_id, message, log_type="info", progress=None, metadata
238
237
  conn.commit()
239
238
  conn.close()
240
239
  return True
241
- except Exception as e:
242
- print(f"Error adding log to database: {str(e)}")
243
- print(traceback.format_exc())
240
+ except Exception:
241
+ logger.exception("Error adding log to database")
244
242
  return False
245
243
 
246
244
 
@@ -288,7 +286,6 @@ def get_logs_for_research(research_id):
288
286
  logs.append(formatted_entry)
289
287
 
290
288
  return logs
291
- except Exception as e:
292
- print(f"Error retrieving logs from database: {str(e)}")
293
- print(traceback.format_exc())
289
+ except Exception:
290
+ logger.exception("Error retrieving logs from database")
294
291
  return []