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
@@ -1,4 +1,5 @@
|
|
1
1
|
import os
|
2
|
+
from functools import cache
|
2
3
|
|
3
4
|
from langchain_anthropic import ChatAnthropic
|
4
5
|
from langchain_community.llms import VLLM
|
@@ -24,7 +25,163 @@ VALID_PROVIDERS = [
|
|
24
25
|
]
|
25
26
|
|
26
27
|
|
27
|
-
def
|
28
|
+
def is_openai_available():
|
29
|
+
"""Check if OpenAI is available"""
|
30
|
+
try:
|
31
|
+
api_key = get_db_setting("llm.openai.api_key")
|
32
|
+
return bool(api_key)
|
33
|
+
except Exception:
|
34
|
+
return False
|
35
|
+
|
36
|
+
|
37
|
+
def is_anthropic_available():
|
38
|
+
"""Check if Anthropic is available"""
|
39
|
+
try:
|
40
|
+
api_key = get_db_setting("llm.anthropic.api_key")
|
41
|
+
return bool(api_key)
|
42
|
+
except Exception:
|
43
|
+
return False
|
44
|
+
|
45
|
+
|
46
|
+
def is_openai_endpoint_available():
|
47
|
+
"""Check if OpenAI endpoint is available"""
|
48
|
+
try:
|
49
|
+
api_key = get_db_setting("llm.openai_endpoint.api_key")
|
50
|
+
return bool(api_key)
|
51
|
+
except Exception:
|
52
|
+
return False
|
53
|
+
|
54
|
+
|
55
|
+
def is_ollama_available():
|
56
|
+
"""Check if Ollama is running"""
|
57
|
+
try:
|
58
|
+
import requests
|
59
|
+
|
60
|
+
raw_base_url = get_db_setting(
|
61
|
+
"llm.ollama.url", "http://localhost:11434"
|
62
|
+
)
|
63
|
+
base_url = (
|
64
|
+
normalize_url(raw_base_url)
|
65
|
+
if raw_base_url
|
66
|
+
else "http://localhost:11434"
|
67
|
+
)
|
68
|
+
logger.info(f"Checking Ollama availability at {base_url}/api/tags")
|
69
|
+
|
70
|
+
try:
|
71
|
+
response = requests.get(f"{base_url}/api/tags", timeout=3.0)
|
72
|
+
if response.status_code == 200:
|
73
|
+
logger.info(
|
74
|
+
f"Ollama is available. Status code: {response.status_code}"
|
75
|
+
)
|
76
|
+
# Log first 100 chars of response to debug
|
77
|
+
logger.info(f"Response preview: {str(response.text)[:100]}")
|
78
|
+
return True
|
79
|
+
else:
|
80
|
+
logger.warning(
|
81
|
+
f"Ollama API returned status code: {response.status_code}"
|
82
|
+
)
|
83
|
+
return False
|
84
|
+
except requests.exceptions.RequestException as req_error:
|
85
|
+
logger.error(
|
86
|
+
f"Request error when checking Ollama: {str(req_error)}"
|
87
|
+
)
|
88
|
+
return False
|
89
|
+
except Exception:
|
90
|
+
logger.exception("Unexpected error when checking Ollama")
|
91
|
+
return False
|
92
|
+
except Exception:
|
93
|
+
logger.exception("Error in is_ollama_available")
|
94
|
+
return False
|
95
|
+
|
96
|
+
|
97
|
+
def is_vllm_available():
|
98
|
+
"""Check if VLLM capability is available"""
|
99
|
+
try:
|
100
|
+
import torch # noqa: F401
|
101
|
+
import transformers # noqa: F401
|
102
|
+
|
103
|
+
return True
|
104
|
+
except ImportError:
|
105
|
+
return False
|
106
|
+
|
107
|
+
|
108
|
+
def is_lmstudio_available():
|
109
|
+
"""Check if LM Studio is available"""
|
110
|
+
try:
|
111
|
+
import requests
|
112
|
+
|
113
|
+
lmstudio_url = get_db_setting(
|
114
|
+
"llm.lmstudio.url", "http://localhost:1234"
|
115
|
+
)
|
116
|
+
# LM Studio typically uses OpenAI-compatible endpoints
|
117
|
+
response = requests.get(f"{lmstudio_url}/v1/models", timeout=1.0)
|
118
|
+
return response.status_code == 200
|
119
|
+
except Exception:
|
120
|
+
return False
|
121
|
+
|
122
|
+
|
123
|
+
def is_llamacpp_available():
|
124
|
+
"""Check if LlamaCpp is available and configured"""
|
125
|
+
try:
|
126
|
+
from langchain_community.llms import LlamaCpp # noqa: F401
|
127
|
+
|
128
|
+
model_path = get_db_setting("llm.llamacpp_model_path")
|
129
|
+
return bool(model_path) and os.path.exists(model_path)
|
130
|
+
except Exception:
|
131
|
+
return False
|
132
|
+
|
133
|
+
|
134
|
+
@cache
|
135
|
+
def get_available_providers():
|
136
|
+
"""Return available model providers"""
|
137
|
+
providers = {}
|
138
|
+
|
139
|
+
if is_ollama_available():
|
140
|
+
providers["ollama"] = "Ollama (local models)"
|
141
|
+
|
142
|
+
if is_openai_available():
|
143
|
+
providers["openai"] = "OpenAI API"
|
144
|
+
|
145
|
+
if is_anthropic_available():
|
146
|
+
providers["anthropic"] = "Anthropic API"
|
147
|
+
|
148
|
+
if is_openai_endpoint_available():
|
149
|
+
providers["openai_endpoint"] = "OpenAI-compatible Endpoint"
|
150
|
+
|
151
|
+
if is_lmstudio_available():
|
152
|
+
providers["lmstudio"] = "LM Studio (local models)"
|
153
|
+
|
154
|
+
if is_llamacpp_available():
|
155
|
+
providers["llamacpp"] = "LlamaCpp (local models)"
|
156
|
+
|
157
|
+
# Check for VLLM capability
|
158
|
+
try:
|
159
|
+
import torch # noqa: F401
|
160
|
+
import transformers # noqa: F401
|
161
|
+
|
162
|
+
providers["vllm"] = "VLLM (local models)"
|
163
|
+
except ImportError:
|
164
|
+
pass
|
165
|
+
|
166
|
+
# Default fallback
|
167
|
+
if not providers:
|
168
|
+
providers["none"] = "No model providers available"
|
169
|
+
|
170
|
+
return providers
|
171
|
+
|
172
|
+
|
173
|
+
def get_selected_llm_provider():
|
174
|
+
return get_db_setting("llm.provider", "ollama").lower()
|
175
|
+
|
176
|
+
|
177
|
+
def get_llm(
|
178
|
+
model_name=None,
|
179
|
+
temperature=None,
|
180
|
+
provider=None,
|
181
|
+
openai_endpoint_url=None,
|
182
|
+
research_id=None,
|
183
|
+
research_context=None,
|
184
|
+
):
|
28
185
|
"""
|
29
186
|
Get LLM instance based on model name and provider.
|
30
187
|
|
@@ -34,6 +191,8 @@ def get_llm(model_name=None, temperature=None, provider=None, openai_endpoint_ur
|
|
34
191
|
provider: Provider to use (if None, uses database setting)
|
35
192
|
openai_endpoint_url: Custom endpoint URL to use (if None, uses database
|
36
193
|
setting)
|
194
|
+
research_id: Optional research ID for token tracking
|
195
|
+
research_context: Optional research context for enhanced token tracking
|
37
196
|
|
38
197
|
Returns:
|
39
198
|
A LangChain LLM instance with automatic think-tag removal
|
@@ -47,6 +206,16 @@ def get_llm(model_name=None, temperature=None, provider=None, openai_endpoint_ur
|
|
47
206
|
if provider is None:
|
48
207
|
provider = get_db_setting("llm.provider", "ollama")
|
49
208
|
|
209
|
+
# Check if we're in testing mode and should use fallback
|
210
|
+
if os.environ.get("LDR_USE_FALLBACK_LLM", ""):
|
211
|
+
logger.info("LDR_USE_FALLBACK_LLM is set, using fallback model")
|
212
|
+
return wrap_llm_without_think_tags(
|
213
|
+
get_fallback_model(temperature),
|
214
|
+
research_id=research_id,
|
215
|
+
provider="fallback",
|
216
|
+
research_context=research_context,
|
217
|
+
)
|
218
|
+
|
50
219
|
# Clean model name: remove quotes and extra whitespace
|
51
220
|
if model_name:
|
52
221
|
model_name = model_name.strip().strip("\"'").strip()
|
@@ -79,7 +248,8 @@ def get_llm(model_name=None, temperature=None, provider=None, openai_endpoint_ur
|
|
79
248
|
if get_db_setting("llm.supports_max_tokens", True):
|
80
249
|
# Use 80% of context window to leave room for prompts
|
81
250
|
max_tokens = min(
|
82
|
-
int(get_db_setting("llm.max_tokens", 30000)),
|
251
|
+
int(get_db_setting("llm.max_tokens", 30000)),
|
252
|
+
int(context_window_size * 0.8),
|
83
253
|
)
|
84
254
|
common_params["max_tokens"] = max_tokens
|
85
255
|
|
@@ -95,16 +265,28 @@ def get_llm(model_name=None, temperature=None, provider=None, openai_endpoint_ur
|
|
95
265
|
llm = ChatAnthropic(
|
96
266
|
model=model_name, anthropic_api_key=api_key, **common_params
|
97
267
|
)
|
98
|
-
return wrap_llm_without_think_tags(
|
268
|
+
return wrap_llm_without_think_tags(
|
269
|
+
llm,
|
270
|
+
research_id=research_id,
|
271
|
+
provider=provider,
|
272
|
+
research_context=research_context,
|
273
|
+
)
|
99
274
|
|
100
275
|
elif provider == "openai":
|
101
276
|
api_key = get_db_setting("llm.openai.api_key")
|
102
277
|
if not api_key:
|
103
|
-
logger.warning(
|
278
|
+
logger.warning(
|
279
|
+
"OPENAI_API_KEY not found. Falling back to default model."
|
280
|
+
)
|
104
281
|
return get_fallback_model(temperature)
|
105
282
|
|
106
283
|
llm = ChatOpenAI(model=model_name, api_key=api_key, **common_params)
|
107
|
-
return wrap_llm_without_think_tags(
|
284
|
+
return wrap_llm_without_think_tags(
|
285
|
+
llm,
|
286
|
+
research_id=research_id,
|
287
|
+
provider=provider,
|
288
|
+
research_context=research_context,
|
289
|
+
)
|
108
290
|
|
109
291
|
elif provider == "openai_endpoint":
|
110
292
|
api_key = get_db_setting("llm.openai_endpoint.api_key")
|
@@ -126,7 +308,12 @@ def get_llm(model_name=None, temperature=None, provider=None, openai_endpoint_ur
|
|
126
308
|
openai_api_base=openai_endpoint_url,
|
127
309
|
**common_params,
|
128
310
|
)
|
129
|
-
return wrap_llm_without_think_tags(
|
311
|
+
return wrap_llm_without_think_tags(
|
312
|
+
llm,
|
313
|
+
research_id=research_id,
|
314
|
+
provider=provider,
|
315
|
+
research_context=research_context,
|
316
|
+
)
|
130
317
|
|
131
318
|
elif provider == "vllm":
|
132
319
|
try:
|
@@ -138,7 +325,12 @@ def get_llm(model_name=None, temperature=None, provider=None, openai_endpoint_ur
|
|
138
325
|
top_p=0.95,
|
139
326
|
temperature=temperature,
|
140
327
|
)
|
141
|
-
return wrap_llm_without_think_tags(
|
328
|
+
return wrap_llm_without_think_tags(
|
329
|
+
llm,
|
330
|
+
research_id=research_id,
|
331
|
+
provider=provider,
|
332
|
+
research_context=research_context,
|
333
|
+
)
|
142
334
|
except Exception:
|
143
335
|
logger.exception("Error loading VLLM model")
|
144
336
|
return get_fallback_model(temperature)
|
@@ -146,7 +338,9 @@ def get_llm(model_name=None, temperature=None, provider=None, openai_endpoint_ur
|
|
146
338
|
elif provider == "ollama":
|
147
339
|
try:
|
148
340
|
# Use the configurable Ollama base URL
|
149
|
-
raw_base_url = get_db_setting(
|
341
|
+
raw_base_url = get_db_setting(
|
342
|
+
"llm.ollama.url", "http://localhost:11434"
|
343
|
+
)
|
150
344
|
base_url = (
|
151
345
|
normalize_url(raw_base_url)
|
152
346
|
if raw_base_url
|
@@ -164,7 +358,9 @@ def get_llm(model_name=None, temperature=None, provider=None, openai_endpoint_ur
|
|
164
358
|
import requests
|
165
359
|
|
166
360
|
try:
|
167
|
-
logger.info(
|
361
|
+
logger.info(
|
362
|
+
f"Checking if model '{model_name}' exists in Ollama"
|
363
|
+
)
|
168
364
|
response = requests.get(f"{base_url}/api/tags", timeout=3.0)
|
169
365
|
if response.status_code == 200:
|
170
366
|
# Handle both newer and older Ollama API formats
|
@@ -189,21 +385,30 @@ def get_llm(model_name=None, temperature=None, provider=None, openai_endpoint_ur
|
|
189
385
|
)
|
190
386
|
return get_fallback_model(temperature)
|
191
387
|
except Exception:
|
192
|
-
logger.exception(
|
388
|
+
logger.exception(
|
389
|
+
f"Error checking for model '{model_name}' in Ollama"
|
390
|
+
)
|
193
391
|
# Continue anyway, let ChatOllama handle potential errors
|
194
392
|
|
195
393
|
logger.info(
|
196
394
|
f"Creating ChatOllama with model={model_name}, base_url={base_url}"
|
197
395
|
)
|
198
396
|
try:
|
199
|
-
llm = ChatOllama(
|
397
|
+
llm = ChatOllama(
|
398
|
+
model=model_name, base_url=base_url, **common_params
|
399
|
+
)
|
200
400
|
# Test invoke to validate model works
|
201
401
|
logger.info("Testing Ollama model with simple invocation")
|
202
402
|
test_result = llm.invoke("Hello")
|
203
403
|
logger.info(
|
204
404
|
f"Ollama test successful. Response type: {type(test_result)}"
|
205
405
|
)
|
206
|
-
return wrap_llm_without_think_tags(
|
406
|
+
return wrap_llm_without_think_tags(
|
407
|
+
llm,
|
408
|
+
research_id=research_id,
|
409
|
+
provider=provider,
|
410
|
+
research_context=research_context,
|
411
|
+
)
|
207
412
|
except Exception:
|
208
413
|
logger.exception("Error creating or testing ChatOllama")
|
209
414
|
return get_fallback_model(temperature)
|
@@ -213,7 +418,9 @@ def get_llm(model_name=None, temperature=None, provider=None, openai_endpoint_ur
|
|
213
418
|
|
214
419
|
elif provider == "lmstudio":
|
215
420
|
# LM Studio supports OpenAI API format, so we can use ChatOpenAI directly
|
216
|
-
lmstudio_url = get_db_setting(
|
421
|
+
lmstudio_url = get_db_setting(
|
422
|
+
"llm.lmstudio.url", "http://localhost:1234"
|
423
|
+
)
|
217
424
|
|
218
425
|
llm = ChatOpenAI(
|
219
426
|
model=model_name,
|
@@ -222,7 +429,12 @@ def get_llm(model_name=None, temperature=None, provider=None, openai_endpoint_ur
|
|
222
429
|
temperature=temperature,
|
223
430
|
max_tokens=max_tokens, # Use calculated max_tokens based on context size
|
224
431
|
)
|
225
|
-
return wrap_llm_without_think_tags(
|
432
|
+
return wrap_llm_without_think_tags(
|
433
|
+
llm,
|
434
|
+
research_id=research_id,
|
435
|
+
provider=provider,
|
436
|
+
research_context=research_context,
|
437
|
+
)
|
226
438
|
|
227
439
|
# Update the llamacpp section in get_llm function
|
228
440
|
elif provider == "llamacpp":
|
@@ -230,7 +442,9 @@ def get_llm(model_name=None, temperature=None, provider=None, openai_endpoint_ur
|
|
230
442
|
from langchain_community.llms import LlamaCpp
|
231
443
|
|
232
444
|
# Get LlamaCpp connection mode from settings
|
233
|
-
connection_mode = get_db_setting(
|
445
|
+
connection_mode = get_db_setting(
|
446
|
+
"llm.llamacpp_connection_mode", "local"
|
447
|
+
)
|
234
448
|
|
235
449
|
if connection_mode == "http":
|
236
450
|
# Use HTTP client mode
|
@@ -270,10 +484,20 @@ def get_llm(model_name=None, temperature=None, provider=None, openai_endpoint_ur
|
|
270
484
|
verbose=True,
|
271
485
|
)
|
272
486
|
|
273
|
-
return wrap_llm_without_think_tags(
|
487
|
+
return wrap_llm_without_think_tags(
|
488
|
+
llm,
|
489
|
+
research_id=research_id,
|
490
|
+
provider=provider,
|
491
|
+
research_context=research_context,
|
492
|
+
)
|
274
493
|
|
275
494
|
else:
|
276
|
-
return wrap_llm_without_think_tags(
|
495
|
+
return wrap_llm_without_think_tags(
|
496
|
+
get_fallback_model(temperature),
|
497
|
+
research_id=research_id,
|
498
|
+
provider=provider,
|
499
|
+
research_context=research_context,
|
500
|
+
)
|
277
501
|
|
278
502
|
|
279
503
|
def get_fallback_model(temperature=None):
|
@@ -285,8 +509,36 @@ def get_fallback_model(temperature=None):
|
|
285
509
|
)
|
286
510
|
|
287
511
|
|
288
|
-
def wrap_llm_without_think_tags(
|
289
|
-
|
512
|
+
def wrap_llm_without_think_tags(
|
513
|
+
llm, research_id=None, provider=None, research_context=None
|
514
|
+
):
|
515
|
+
"""Create a wrapper class that processes LLM outputs with remove_think_tags and token counting"""
|
516
|
+
|
517
|
+
# Import token counting functionality if research_id is provided
|
518
|
+
callbacks = []
|
519
|
+
if research_id is not None:
|
520
|
+
from ..metrics import TokenCounter
|
521
|
+
|
522
|
+
token_counter = TokenCounter()
|
523
|
+
token_callback = token_counter.create_callback(
|
524
|
+
research_id, research_context
|
525
|
+
)
|
526
|
+
# Set provider and model info on the callback
|
527
|
+
if provider:
|
528
|
+
token_callback.preset_provider = provider
|
529
|
+
# Try to extract model name from the LLM instance
|
530
|
+
if hasattr(llm, "model_name"):
|
531
|
+
token_callback.preset_model = llm.model_name
|
532
|
+
elif hasattr(llm, "model"):
|
533
|
+
token_callback.preset_model = llm.model
|
534
|
+
callbacks.append(token_callback)
|
535
|
+
|
536
|
+
# Add callbacks to the LLM if it supports them
|
537
|
+
if callbacks and hasattr(llm, "callbacks"):
|
538
|
+
if llm.callbacks is None:
|
539
|
+
llm.callbacks = callbacks
|
540
|
+
else:
|
541
|
+
llm.callbacks.extend(callbacks)
|
290
542
|
|
291
543
|
class ProcessingLLMWrapper:
|
292
544
|
def __init__(self, base_llm):
|
@@ -308,153 +560,3 @@ def wrap_llm_without_think_tags(llm):
|
|
308
560
|
return getattr(self.base_llm, name)
|
309
561
|
|
310
562
|
return ProcessingLLMWrapper(llm)
|
311
|
-
|
312
|
-
|
313
|
-
def get_available_provider_types():
|
314
|
-
"""Return available model providers"""
|
315
|
-
providers = {}
|
316
|
-
|
317
|
-
if is_ollama_available():
|
318
|
-
providers["ollama"] = "Ollama (local models)"
|
319
|
-
|
320
|
-
if is_openai_available():
|
321
|
-
providers["openai"] = "OpenAI API"
|
322
|
-
|
323
|
-
if is_anthropic_available():
|
324
|
-
providers["anthropic"] = "Anthropic API"
|
325
|
-
|
326
|
-
if is_openai_endpoint_available():
|
327
|
-
providers["openai_endpoint"] = "OpenAI-compatible Endpoint"
|
328
|
-
|
329
|
-
if is_lmstudio_available():
|
330
|
-
providers["lmstudio"] = "LM Studio (local models)"
|
331
|
-
|
332
|
-
if is_llamacpp_available():
|
333
|
-
providers["llamacpp"] = "LlamaCpp (local models)"
|
334
|
-
|
335
|
-
# Check for VLLM capability
|
336
|
-
try:
|
337
|
-
import torch # noqa: F401
|
338
|
-
import transformers # noqa: F401
|
339
|
-
|
340
|
-
providers["vllm"] = "VLLM (local models)"
|
341
|
-
except ImportError:
|
342
|
-
pass
|
343
|
-
|
344
|
-
# Default fallback
|
345
|
-
if not providers:
|
346
|
-
providers["none"] = "No model providers available"
|
347
|
-
|
348
|
-
return providers
|
349
|
-
|
350
|
-
|
351
|
-
def is_openai_available():
|
352
|
-
"""Check if OpenAI is available"""
|
353
|
-
try:
|
354
|
-
api_key = get_db_setting("llm.openai.api_key")
|
355
|
-
return bool(api_key)
|
356
|
-
except Exception:
|
357
|
-
return False
|
358
|
-
|
359
|
-
|
360
|
-
def is_anthropic_available():
|
361
|
-
"""Check if Anthropic is available"""
|
362
|
-
try:
|
363
|
-
api_key = get_db_setting("llm.anthropic.api_key")
|
364
|
-
return bool(api_key)
|
365
|
-
except Exception:
|
366
|
-
return False
|
367
|
-
|
368
|
-
|
369
|
-
def is_openai_endpoint_available():
|
370
|
-
"""Check if OpenAI endpoint is available"""
|
371
|
-
try:
|
372
|
-
api_key = get_db_setting("llm.openai_endpoint.api_key")
|
373
|
-
return bool(api_key)
|
374
|
-
except Exception:
|
375
|
-
return False
|
376
|
-
|
377
|
-
|
378
|
-
def is_ollama_available():
|
379
|
-
"""Check if Ollama is running"""
|
380
|
-
try:
|
381
|
-
import requests
|
382
|
-
|
383
|
-
raw_base_url = get_db_setting("llm.ollama.url", "http://localhost:11434")
|
384
|
-
base_url = (
|
385
|
-
normalize_url(raw_base_url) if raw_base_url else "http://localhost:11434"
|
386
|
-
)
|
387
|
-
logger.info(f"Checking Ollama availability at {base_url}/api/tags")
|
388
|
-
|
389
|
-
try:
|
390
|
-
response = requests.get(f"{base_url}/api/tags", timeout=3.0)
|
391
|
-
if response.status_code == 200:
|
392
|
-
logger.info(f"Ollama is available. Status code: {response.status_code}")
|
393
|
-
# Log first 100 chars of response to debug
|
394
|
-
logger.info(f"Response preview: {str(response.text)[:100]}")
|
395
|
-
return True
|
396
|
-
else:
|
397
|
-
logger.warning(
|
398
|
-
f"Ollama API returned status code: {response.status_code}"
|
399
|
-
)
|
400
|
-
return False
|
401
|
-
except requests.exceptions.RequestException as req_error:
|
402
|
-
logger.error(f"Request error when checking Ollama: {str(req_error)}")
|
403
|
-
return False
|
404
|
-
except Exception:
|
405
|
-
logger.exception("Unexpected error when checking Ollama")
|
406
|
-
return False
|
407
|
-
except Exception:
|
408
|
-
logger.exception("Error in is_ollama_available")
|
409
|
-
return False
|
410
|
-
|
411
|
-
|
412
|
-
def is_vllm_available():
|
413
|
-
"""Check if VLLM capability is available"""
|
414
|
-
try:
|
415
|
-
import torch # noqa: F401
|
416
|
-
import transformers # noqa: F401
|
417
|
-
|
418
|
-
return True
|
419
|
-
except ImportError:
|
420
|
-
return False
|
421
|
-
|
422
|
-
|
423
|
-
def is_lmstudio_available():
|
424
|
-
"""Check if LM Studio is available"""
|
425
|
-
try:
|
426
|
-
import requests
|
427
|
-
|
428
|
-
lmstudio_url = get_db_setting("llm.lmstudio.url", "http://localhost:1234")
|
429
|
-
# LM Studio typically uses OpenAI-compatible endpoints
|
430
|
-
response = requests.get(f"{lmstudio_url}/v1/models", timeout=1.0)
|
431
|
-
return response.status_code == 200
|
432
|
-
except Exception:
|
433
|
-
return False
|
434
|
-
|
435
|
-
|
436
|
-
def is_llamacpp_available():
|
437
|
-
"""Check if LlamaCpp is available and configured"""
|
438
|
-
try:
|
439
|
-
from langchain_community.llms import LlamaCpp # noqa: F401
|
440
|
-
|
441
|
-
model_path = get_db_setting("llm.llamacpp_model_path")
|
442
|
-
return bool(model_path) and os.path.exists(model_path)
|
443
|
-
except Exception:
|
444
|
-
return False
|
445
|
-
|
446
|
-
|
447
|
-
def get_available_providers():
|
448
|
-
"""Get dictionary of available providers"""
|
449
|
-
return get_available_provider_types()
|
450
|
-
|
451
|
-
|
452
|
-
AVAILABLE_PROVIDERS = get_available_providers()
|
453
|
-
selected_provider = get_db_setting("llm.provider", "ollama").lower()
|
454
|
-
|
455
|
-
# Log which providers are available
|
456
|
-
logger.info(f"Available providers: {list(AVAILABLE_PROVIDERS.keys())}")
|
457
|
-
|
458
|
-
# Check if selected provider is available
|
459
|
-
if selected_provider not in AVAILABLE_PROVIDERS and selected_provider != "none":
|
460
|
-
logger.warning(f"Selected provider {selected_provider} is not available.")
|
@@ -2,13 +2,20 @@
|
|
2
2
|
from loguru import logger
|
3
3
|
|
4
4
|
from ..utilities.db_utils import get_db_setting
|
5
|
-
from ..web_search_engines.search_engine_factory import
|
5
|
+
from ..web_search_engines.search_engine_factory import (
|
6
|
+
get_search as factory_get_search,
|
7
|
+
)
|
6
8
|
from .llm_config import get_llm
|
7
9
|
|
8
10
|
# Whether to check the quality search results using the LLM.
|
9
11
|
QUALITY_CHECK_DDG_URLS = True
|
10
|
-
|
11
|
-
|
12
|
+
|
13
|
+
|
14
|
+
def get_search_snippets_only_setting():
|
15
|
+
"""
|
16
|
+
Lazily retrieve the 'search.snippets_only' setting.
|
17
|
+
"""
|
18
|
+
return get_db_setting("search.snippets_only", True)
|
12
19
|
|
13
20
|
|
14
21
|
# Expose get_search function
|
@@ -36,9 +43,11 @@ def get_search(search_tool=None, llm_instance=None):
|
|
36
43
|
"region": get_db_setting("search.region", "wt-wt"),
|
37
44
|
"time_period": get_db_setting("search.time_period", "all"),
|
38
45
|
"safe_search": get_db_setting("search.safe_search", True),
|
39
|
-
"search_snippets_only":
|
46
|
+
"search_snippets_only": get_search_snippets_only_setting(),
|
40
47
|
"search_language": get_db_setting("search.search_language", "English"),
|
41
|
-
"max_filtered_results": get_db_setting(
|
48
|
+
"max_filtered_results": get_db_setting(
|
49
|
+
"search.max_filtered_results", 5
|
50
|
+
),
|
42
51
|
}
|
43
52
|
|
44
53
|
# Log NULL parameters for debugging
|
@@ -0,0 +1,13 @@
|
|
1
|
+
"""Metrics module for tracking LLM usage and token counts."""
|
2
|
+
|
3
|
+
from .database import get_metrics_db
|
4
|
+
from .db_models import ModelUsage, TokenUsage
|
5
|
+
from .token_counter import TokenCounter, TokenCountingCallback
|
6
|
+
|
7
|
+
__all__ = [
|
8
|
+
"TokenCounter",
|
9
|
+
"TokenCountingCallback",
|
10
|
+
"TokenUsage",
|
11
|
+
"ModelUsage",
|
12
|
+
"get_metrics_db",
|
13
|
+
]
|
@@ -0,0 +1,58 @@
|
|
1
|
+
"""Database utilities for metrics module with SQLAlchemy."""
|
2
|
+
|
3
|
+
from contextlib import contextmanager
|
4
|
+
from typing import Generator
|
5
|
+
|
6
|
+
from loguru import logger
|
7
|
+
from sqlalchemy import create_engine
|
8
|
+
from sqlalchemy.orm import Session, sessionmaker
|
9
|
+
|
10
|
+
from ..utilities.db_utils import DB_PATH
|
11
|
+
from .db_models import Base
|
12
|
+
|
13
|
+
|
14
|
+
class MetricsDatabase:
|
15
|
+
"""Database manager for metrics using SQLAlchemy."""
|
16
|
+
|
17
|
+
def __init__(self):
|
18
|
+
# Use the same database as the rest of the app
|
19
|
+
self.engine = create_engine(
|
20
|
+
f"sqlite:///{DB_PATH}", connect_args={"check_same_thread": False}
|
21
|
+
)
|
22
|
+
self.SessionLocal = sessionmaker(
|
23
|
+
bind=self.engine, autocommit=False, autoflush=False
|
24
|
+
)
|
25
|
+
self._init_database()
|
26
|
+
|
27
|
+
def _init_database(self):
|
28
|
+
"""Initialize database tables for metrics."""
|
29
|
+
try:
|
30
|
+
Base.metadata.create_all(self.engine)
|
31
|
+
logger.info("Metrics tables initialized successfully")
|
32
|
+
except Exception as e:
|
33
|
+
logger.exception(f"Error initializing metrics tables: {e}")
|
34
|
+
|
35
|
+
@contextmanager
|
36
|
+
def get_session(self) -> Generator[Session, None, None]:
|
37
|
+
"""Get a database session with automatic cleanup."""
|
38
|
+
session = self.SessionLocal()
|
39
|
+
try:
|
40
|
+
yield session
|
41
|
+
session.commit()
|
42
|
+
except Exception:
|
43
|
+
session.rollback()
|
44
|
+
raise
|
45
|
+
finally:
|
46
|
+
session.close()
|
47
|
+
|
48
|
+
|
49
|
+
# Singleton instance
|
50
|
+
_metrics_db = None
|
51
|
+
|
52
|
+
|
53
|
+
def get_metrics_db() -> MetricsDatabase:
|
54
|
+
"""Get the singleton metrics database instance."""
|
55
|
+
global _metrics_db
|
56
|
+
if _metrics_db is None:
|
57
|
+
_metrics_db = MetricsDatabase()
|
58
|
+
return _metrics_db
|