realtimex-deeptutor 0.5.0.post1__py3-none-any.whl → 0.5.0.post3__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.
- {realtimex_deeptutor-0.5.0.post1.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/METADATA +24 -17
- {realtimex_deeptutor-0.5.0.post1.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/RECORD +143 -123
- {realtimex_deeptutor-0.5.0.post1.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/WHEEL +1 -1
- realtimex_deeptutor-0.5.0.post3.dist-info/entry_points.txt +4 -0
- {realtimex_deeptutor-0.5.0.post1.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/top_level.txt +1 -0
- scripts/__init__.py +1 -0
- scripts/audit_prompts.py +179 -0
- scripts/check_install.py +460 -0
- scripts/generate_roster.py +327 -0
- scripts/install_all.py +653 -0
- scripts/migrate_kb.py +655 -0
- scripts/start.py +807 -0
- scripts/start_web.py +632 -0
- scripts/sync_prompts_from_en.py +147 -0
- src/__init__.py +2 -2
- src/agents/ideagen/material_organizer_agent.py +2 -0
- src/agents/solve/__init__.py +6 -0
- src/agents/solve/main_solver.py +9 -0
- src/agents/solve/prompts/zh/analysis_loop/investigate_agent.yaml +9 -7
- src/agents/solve/session_manager.py +345 -0
- src/api/main.py +14 -0
- src/api/routers/chat.py +3 -3
- src/api/routers/co_writer.py +12 -7
- src/api/routers/config.py +1 -0
- src/api/routers/guide.py +3 -1
- src/api/routers/ideagen.py +7 -0
- src/api/routers/knowledge.py +64 -12
- src/api/routers/question.py +2 -0
- src/api/routers/realtimex.py +137 -0
- src/api/routers/research.py +9 -0
- src/api/routers/solve.py +120 -2
- src/cli/__init__.py +13 -0
- src/cli/start.py +209 -0
- src/config/constants.py +11 -9
- src/knowledge/add_documents.py +453 -213
- src/knowledge/extract_numbered_items.py +9 -10
- src/knowledge/initializer.py +102 -101
- src/knowledge/manager.py +251 -74
- src/knowledge/progress_tracker.py +43 -2
- src/knowledge/start_kb.py +11 -2
- src/logging/__init__.py +5 -0
- src/logging/adapters/__init__.py +1 -0
- src/logging/adapters/lightrag.py +25 -18
- src/logging/adapters/llamaindex.py +1 -0
- src/logging/config.py +30 -27
- src/logging/handlers/__init__.py +1 -0
- src/logging/handlers/console.py +7 -50
- src/logging/handlers/file.py +5 -20
- src/logging/handlers/websocket.py +23 -19
- src/logging/logger.py +161 -126
- src/logging/stats/__init__.py +1 -0
- src/logging/stats/llm_stats.py +37 -17
- src/services/__init__.py +17 -1
- src/services/config/__init__.py +1 -0
- src/services/config/knowledge_base_config.py +1 -0
- src/services/config/loader.py +1 -1
- src/services/config/unified_config.py +211 -4
- src/services/embedding/__init__.py +1 -0
- src/services/embedding/adapters/__init__.py +3 -0
- src/services/embedding/adapters/base.py +1 -0
- src/services/embedding/adapters/cohere.py +1 -0
- src/services/embedding/adapters/jina.py +1 -0
- src/services/embedding/adapters/ollama.py +1 -0
- src/services/embedding/adapters/openai_compatible.py +1 -0
- src/services/embedding/adapters/realtimex.py +125 -0
- src/services/embedding/client.py +27 -0
- src/services/embedding/config.py +3 -0
- src/services/embedding/provider.py +1 -0
- src/services/llm/__init__.py +17 -3
- src/services/llm/capabilities.py +47 -0
- src/services/llm/client.py +32 -0
- src/services/llm/cloud_provider.py +21 -4
- src/services/llm/config.py +36 -2
- src/services/llm/error_mapping.py +1 -0
- src/services/llm/exceptions.py +30 -0
- src/services/llm/factory.py +55 -16
- src/services/llm/local_provider.py +1 -0
- src/services/llm/providers/anthropic.py +1 -0
- src/services/llm/providers/base_provider.py +1 -0
- src/services/llm/providers/open_ai.py +1 -0
- src/services/llm/realtimex_provider.py +240 -0
- src/services/llm/registry.py +1 -0
- src/services/llm/telemetry.py +1 -0
- src/services/llm/types.py +1 -0
- src/services/llm/utils.py +1 -0
- src/services/prompt/__init__.py +1 -0
- src/services/prompt/manager.py +3 -2
- src/services/rag/__init__.py +27 -5
- src/services/rag/components/__init__.py +1 -0
- src/services/rag/components/base.py +1 -0
- src/services/rag/components/chunkers/__init__.py +1 -0
- src/services/rag/components/chunkers/base.py +1 -0
- src/services/rag/components/chunkers/fixed.py +1 -0
- src/services/rag/components/chunkers/numbered_item.py +1 -0
- src/services/rag/components/chunkers/semantic.py +1 -0
- src/services/rag/components/embedders/__init__.py +1 -0
- src/services/rag/components/embedders/base.py +1 -0
- src/services/rag/components/embedders/openai.py +1 -0
- src/services/rag/components/indexers/__init__.py +1 -0
- src/services/rag/components/indexers/base.py +1 -0
- src/services/rag/components/indexers/graph.py +5 -44
- src/services/rag/components/indexers/lightrag.py +5 -44
- src/services/rag/components/indexers/vector.py +1 -0
- src/services/rag/components/parsers/__init__.py +1 -0
- src/services/rag/components/parsers/base.py +1 -0
- src/services/rag/components/parsers/markdown.py +1 -0
- src/services/rag/components/parsers/pdf.py +1 -0
- src/services/rag/components/parsers/text.py +1 -0
- src/services/rag/components/retrievers/__init__.py +1 -0
- src/services/rag/components/retrievers/base.py +1 -0
- src/services/rag/components/retrievers/dense.py +1 -0
- src/services/rag/components/retrievers/hybrid.py +5 -44
- src/services/rag/components/retrievers/lightrag.py +5 -44
- src/services/rag/components/routing.py +48 -0
- src/services/rag/factory.py +112 -46
- src/services/rag/pipeline.py +1 -0
- src/services/rag/pipelines/__init__.py +27 -18
- src/services/rag/pipelines/lightrag.py +1 -0
- src/services/rag/pipelines/llamaindex.py +99 -0
- src/services/rag/pipelines/raganything.py +67 -100
- src/services/rag/pipelines/raganything_docling.py +368 -0
- src/services/rag/service.py +5 -12
- src/services/rag/types.py +1 -0
- src/services/rag/utils/__init__.py +17 -0
- src/services/rag/utils/image_migration.py +279 -0
- src/services/search/__init__.py +1 -0
- src/services/search/base.py +1 -0
- src/services/search/consolidation.py +1 -0
- src/services/search/providers/__init__.py +1 -0
- src/services/search/providers/baidu.py +1 -0
- src/services/search/providers/exa.py +1 -0
- src/services/search/providers/jina.py +1 -0
- src/services/search/providers/perplexity.py +1 -0
- src/services/search/providers/serper.py +1 -0
- src/services/search/providers/tavily.py +1 -0
- src/services/search/types.py +1 -0
- src/services/settings/__init__.py +1 -0
- src/services/settings/interface_settings.py +78 -0
- src/services/setup/__init__.py +1 -0
- src/services/tts/__init__.py +1 -0
- src/services/tts/config.py +1 -0
- src/utils/realtimex.py +284 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/entry_points.txt +0 -2
- src/services/rag/pipelines/academic.py +0 -44
- {realtimex_deeptutor-0.5.0.post1.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/licenses/LICENSE +0 -0
src/services/__init__.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
1
2
|
"""
|
|
2
3
|
Services Layer
|
|
3
4
|
==============
|
|
@@ -42,7 +43,9 @@ Usage:
|
|
|
42
43
|
result = web_search("What is AI?")
|
|
43
44
|
"""
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
# Note: rag and embedding modules are lazy-loaded via __getattr__
|
|
47
|
+
# to avoid importing heavy dependencies (lightrag, llama_index) at module load time
|
|
48
|
+
from . import config, llm, prompt, search, setup, tts
|
|
46
49
|
|
|
47
50
|
__all__ = [
|
|
48
51
|
"llm",
|
|
@@ -54,3 +57,16 @@ __all__ = [
|
|
|
54
57
|
"setup",
|
|
55
58
|
"config",
|
|
56
59
|
]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def __getattr__(name: str):
|
|
63
|
+
"""Lazy import for modules that depend on heavy libraries."""
|
|
64
|
+
if name == "rag":
|
|
65
|
+
from . import rag
|
|
66
|
+
|
|
67
|
+
return rag
|
|
68
|
+
if name == "embedding":
|
|
69
|
+
from . import embedding
|
|
70
|
+
|
|
71
|
+
return embedding
|
|
72
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
src/services/config/__init__.py
CHANGED
src/services/config/loader.py
CHANGED
|
@@ -191,7 +191,7 @@ def parse_language(language: Any) -> str:
|
|
|
191
191
|
lang_lower = language.lower()
|
|
192
192
|
if lang_lower in ["en", "english"]:
|
|
193
193
|
return "en"
|
|
194
|
-
if lang_lower in ["zh", "chinese"]:
|
|
194
|
+
if lang_lower in ["zh", "chinese", "cn"]:
|
|
195
195
|
return "zh"
|
|
196
196
|
|
|
197
197
|
return "zh" # Default Chinese
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
1
2
|
"""
|
|
2
3
|
Unified Configuration Manager
|
|
3
4
|
=============================
|
|
@@ -133,6 +134,68 @@ class UnifiedConfigManager:
|
|
|
133
134
|
# Ensure default configs exist and are synced with env on startup
|
|
134
135
|
self._ensure_default_configs()
|
|
135
136
|
|
|
137
|
+
# Auto-activate RealTimeX if available (unless user has explicitly chosen another config)
|
|
138
|
+
self._auto_activate_rtx_if_available()
|
|
139
|
+
|
|
140
|
+
def _auto_activate_rtx_if_available(self) -> None:
|
|
141
|
+
"""
|
|
142
|
+
Auto-activate RealTimeX for LLM and Embedding when SDK is detected.
|
|
143
|
+
|
|
144
|
+
Sets default provider (realtimexai) and models if no RTX selection exists yet.
|
|
145
|
+
Only activates RTX if:
|
|
146
|
+
1. SDK is available
|
|
147
|
+
2. User hasn't explicitly chosen a different config (active_id is still 'default')
|
|
148
|
+
"""
|
|
149
|
+
try:
|
|
150
|
+
from src.utils.realtimex import (
|
|
151
|
+
get_rtx_active_config,
|
|
152
|
+
set_rtx_active_config,
|
|
153
|
+
should_use_realtimex_sdk,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if not should_use_realtimex_sdk():
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
# Auto-activate for LLM
|
|
160
|
+
llm_data = self._load_configs(ConfigType.LLM)
|
|
161
|
+
llm_active_id = llm_data.get("active_id", "default")
|
|
162
|
+
|
|
163
|
+
# Only auto-activate if still using default
|
|
164
|
+
if llm_active_id == "default":
|
|
165
|
+
rtx_llm_config = get_rtx_active_config("llm")
|
|
166
|
+
if not rtx_llm_config:
|
|
167
|
+
# Set default RTX LLM config
|
|
168
|
+
set_rtx_active_config("llm", "realtimexai", "gpt-4o-mini")
|
|
169
|
+
logger.info("Auto-configured RealTimeX LLM with default model: gpt-4o-mini")
|
|
170
|
+
|
|
171
|
+
# Activate RTX for LLM
|
|
172
|
+
self.set_active_config(ConfigType.LLM, "rtx")
|
|
173
|
+
logger.info("Auto-activated RealTimeX for LLM")
|
|
174
|
+
|
|
175
|
+
# Auto-activate for Embedding
|
|
176
|
+
emb_data = self._load_configs(ConfigType.EMBEDDING)
|
|
177
|
+
emb_active_id = emb_data.get("active_id", "default")
|
|
178
|
+
|
|
179
|
+
# Only auto-activate if still using default
|
|
180
|
+
if emb_active_id == "default":
|
|
181
|
+
rtx_emb_config = get_rtx_active_config("embedding")
|
|
182
|
+
if not rtx_emb_config:
|
|
183
|
+
# Set default RTX Embedding config
|
|
184
|
+
set_rtx_active_config("embedding", "realtimexai", "text-embedding-3-small")
|
|
185
|
+
logger.info(
|
|
186
|
+
"Auto-configured RealTimeX Embedding with default model: text-embedding-3-small"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Activate RTX for Embedding
|
|
190
|
+
self.set_active_config(ConfigType.EMBEDDING, "rtx")
|
|
191
|
+
logger.info("Auto-activated RealTimeX for Embedding")
|
|
192
|
+
|
|
193
|
+
except ImportError:
|
|
194
|
+
# RTX utilities not available
|
|
195
|
+
pass
|
|
196
|
+
except Exception as e:
|
|
197
|
+
logger.warning(f"Failed to auto-activate RealTimeX: {e}")
|
|
198
|
+
|
|
136
199
|
def _ensure_default_configs(self) -> None:
|
|
137
200
|
"""
|
|
138
201
|
Ensure default configurations exist in storage files and sync with env.
|
|
@@ -392,6 +455,58 @@ class UnifiedConfigManager:
|
|
|
392
455
|
|
|
393
456
|
return resolved
|
|
394
457
|
|
|
458
|
+
def _build_rtx_virtual_config(self, config_type: ConfigType) -> Optional[Dict[str, Any]]:
|
|
459
|
+
"""
|
|
460
|
+
Build a virtual RTX config entry for display in the config list.
|
|
461
|
+
|
|
462
|
+
This config represents the RealTimeX SDK integration and is shown
|
|
463
|
+
when the SDK is connected. It reads the user's active selection
|
|
464
|
+
from rtx_active.json.
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
Dict with RTX config for display, or None if RTX not available
|
|
468
|
+
"""
|
|
469
|
+
try:
|
|
470
|
+
from src.utils.realtimex import get_rtx_active_config, should_use_realtimex_sdk
|
|
471
|
+
|
|
472
|
+
if not should_use_realtimex_sdk():
|
|
473
|
+
return None
|
|
474
|
+
|
|
475
|
+
# Only LLM and Embedding are supported via RTX
|
|
476
|
+
if config_type not in (ConfigType.LLM, ConfigType.EMBEDDING):
|
|
477
|
+
return None
|
|
478
|
+
|
|
479
|
+
# Get user's active selection (or use defaults)
|
|
480
|
+
active = get_rtx_active_config(config_type.value)
|
|
481
|
+
|
|
482
|
+
if active:
|
|
483
|
+
provider = active.get("provider", "realtimexai")
|
|
484
|
+
model = active.get("model", "")
|
|
485
|
+
else:
|
|
486
|
+
# Use defaults
|
|
487
|
+
provider = "realtimexai"
|
|
488
|
+
if config_type == ConfigType.LLM:
|
|
489
|
+
model = "gpt-4o-mini"
|
|
490
|
+
else: # Embedding
|
|
491
|
+
model = "text-embedding-3-small"
|
|
492
|
+
|
|
493
|
+
return {
|
|
494
|
+
"id": "rtx",
|
|
495
|
+
"name": "RealTimeX",
|
|
496
|
+
"is_default": False,
|
|
497
|
+
"provider": provider,
|
|
498
|
+
"model": model,
|
|
499
|
+
"source": "realtimex", # Flag for services to route through SDK
|
|
500
|
+
"api_key": "—", # No API key needed
|
|
501
|
+
"base_url": "—", # Uses SDK proxy
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
except ImportError:
|
|
505
|
+
return None
|
|
506
|
+
except Exception as e:
|
|
507
|
+
logger.warning(f"Failed to build RTX virtual config: {e}")
|
|
508
|
+
return None
|
|
509
|
+
|
|
395
510
|
def get_provider_options(self, config_type: ConfigType) -> List[str]:
|
|
396
511
|
"""Get available provider options for a config type."""
|
|
397
512
|
return PROVIDER_OPTIONS.get(config_type, [])
|
|
@@ -404,6 +519,9 @@ class UnifiedConfigManager:
|
|
|
404
519
|
For display purposes, the default config shows:
|
|
405
520
|
- Current model/provider from env (dynamically refreshed)
|
|
406
521
|
- base_url/api_key as "***" (hidden for security)
|
|
522
|
+
|
|
523
|
+
When RealTimeX SDK is connected, a virtual "RealTimeX" config
|
|
524
|
+
is prepended to the list for LLM and Embedding types.
|
|
407
525
|
"""
|
|
408
526
|
data = self._load_configs(config_type)
|
|
409
527
|
configs = data.get("configs", [])
|
|
@@ -416,6 +534,12 @@ class UnifiedConfigManager:
|
|
|
416
534
|
result = []
|
|
417
535
|
has_default = False
|
|
418
536
|
|
|
537
|
+
# Prepend RTX virtual config if available
|
|
538
|
+
rtx_config = self._build_rtx_virtual_config(config_type)
|
|
539
|
+
if rtx_config:
|
|
540
|
+
rtx_config["is_active"] = active_id == "rtx"
|
|
541
|
+
result.append(rtx_config)
|
|
542
|
+
|
|
419
543
|
for cfg in configs:
|
|
420
544
|
if cfg.get("id") == "default":
|
|
421
545
|
# Use the dynamically built display config for default
|
|
@@ -437,7 +561,9 @@ class UnifiedConfigManager:
|
|
|
437
561
|
# If no default config found in file, prepend the display default
|
|
438
562
|
if not has_default:
|
|
439
563
|
display_default["is_active"] = active_id == "default"
|
|
440
|
-
|
|
564
|
+
# Insert after RTX config if present, otherwise at the beginning
|
|
565
|
+
insert_pos = 1 if rtx_config else 0
|
|
566
|
+
result.insert(insert_pos, display_default)
|
|
441
567
|
|
|
442
568
|
return result
|
|
443
569
|
|
|
@@ -456,10 +582,46 @@ class UnifiedConfigManager:
|
|
|
456
582
|
"""
|
|
457
583
|
Get the currently active configuration with all values resolved.
|
|
458
584
|
This is used internally when services need actual configuration values.
|
|
585
|
+
|
|
586
|
+
When active_id is 'rtx', returns a config with source='realtimex'
|
|
587
|
+
to signal services to route through the RealTimeX SDK.
|
|
459
588
|
"""
|
|
460
589
|
data = self._load_configs(config_type)
|
|
461
590
|
active_id = data.get("active_id", "default")
|
|
462
591
|
|
|
592
|
+
# Handle RTX virtual config
|
|
593
|
+
if active_id == "rtx":
|
|
594
|
+
try:
|
|
595
|
+
from src.utils.realtimex import get_rtx_active_config, should_use_realtimex_sdk
|
|
596
|
+
|
|
597
|
+
if should_use_realtimex_sdk():
|
|
598
|
+
rtx_active = get_rtx_active_config(config_type.value)
|
|
599
|
+
|
|
600
|
+
if rtx_active:
|
|
601
|
+
return {
|
|
602
|
+
"id": "rtx",
|
|
603
|
+
"provider": rtx_active.get("provider", "realtimexai"),
|
|
604
|
+
"model": rtx_active.get("model", ""),
|
|
605
|
+
"source": "realtimex", # This tells services to use SDK
|
|
606
|
+
}
|
|
607
|
+
else:
|
|
608
|
+
# Return defaults when no selection exists yet
|
|
609
|
+
default_model = (
|
|
610
|
+
"gpt-4o-mini"
|
|
611
|
+
if config_type == ConfigType.LLM
|
|
612
|
+
else "text-embedding-3-small"
|
|
613
|
+
)
|
|
614
|
+
return {
|
|
615
|
+
"id": "rtx",
|
|
616
|
+
"provider": "realtimexai",
|
|
617
|
+
"model": default_model,
|
|
618
|
+
"source": "realtimex",
|
|
619
|
+
}
|
|
620
|
+
except ImportError:
|
|
621
|
+
pass
|
|
622
|
+
# Fallback to default if RTX not available
|
|
623
|
+
return self._get_default_config_resolved(config_type)
|
|
624
|
+
|
|
463
625
|
if active_id == "default":
|
|
464
626
|
return self._get_default_config_resolved(config_type)
|
|
465
627
|
|
|
@@ -530,14 +692,59 @@ class UnifiedConfigManager:
|
|
|
530
692
|
"""Set a configuration as active."""
|
|
531
693
|
data = self._load_configs(config_type)
|
|
532
694
|
|
|
533
|
-
# Verify config exists
|
|
534
|
-
if config_id
|
|
695
|
+
# Verify config exists (rtx is a special virtual config)
|
|
696
|
+
if config_id not in ("default", "rtx"):
|
|
535
697
|
found = any(c.get("id") == config_id for c in data.get("configs", []))
|
|
536
698
|
if not found:
|
|
537
699
|
return False
|
|
538
700
|
|
|
539
701
|
data["active_id"] = config_id
|
|
540
|
-
|
|
702
|
+
success = self._save_configs(config_type, data)
|
|
703
|
+
|
|
704
|
+
# Update environment variables for LightRAG compatibility when LLM config changes
|
|
705
|
+
if success and config_type == ConfigType.LLM:
|
|
706
|
+
self._update_openai_env_vars_for_lightrag()
|
|
707
|
+
|
|
708
|
+
return success
|
|
709
|
+
|
|
710
|
+
def _update_openai_env_vars_for_lightrag(self):
|
|
711
|
+
"""
|
|
712
|
+
Update OPENAI_API_KEY and OPENAI_BASE_URL environment variables for LightRAG.
|
|
713
|
+
|
|
714
|
+
LightRAG's internal functions read directly from os.environ["OPENAI_API_KEY"]
|
|
715
|
+
instead of using passed parameters. This method ensures the environment
|
|
716
|
+
variables are updated when the active LLM configuration changes.
|
|
717
|
+
"""
|
|
718
|
+
try:
|
|
719
|
+
config = self.get_active_config(ConfigType.LLM)
|
|
720
|
+
if not config:
|
|
721
|
+
return
|
|
722
|
+
|
|
723
|
+
provider = config.get("provider", "openai")
|
|
724
|
+
api_key = config.get("api_key", "")
|
|
725
|
+
base_url = config.get("base_url", "")
|
|
726
|
+
|
|
727
|
+
# Only set env vars for OpenAI-compatible providers
|
|
728
|
+
if provider in ("openai", "azure_openai", "gemini", "deepseek"):
|
|
729
|
+
if api_key:
|
|
730
|
+
os.environ["OPENAI_API_KEY"] = api_key
|
|
731
|
+
logger.debug("Updated OPENAI_API_KEY env var for LightRAG compatibility")
|
|
732
|
+
|
|
733
|
+
if base_url:
|
|
734
|
+
os.environ["OPENAI_BASE_URL"] = base_url
|
|
735
|
+
logger.debug(f"Updated OPENAI_BASE_URL env var to {base_url}")
|
|
736
|
+
|
|
737
|
+
# Reset LLM client singleton to pick up new configuration
|
|
738
|
+
try:
|
|
739
|
+
from src.services.llm import reset_llm_client
|
|
740
|
+
|
|
741
|
+
reset_llm_client()
|
|
742
|
+
logger.debug("Reset LLM client singleton after config change")
|
|
743
|
+
except ImportError:
|
|
744
|
+
pass
|
|
745
|
+
|
|
746
|
+
except Exception as e:
|
|
747
|
+
logger.warning(f"Failed to update OpenAI env vars: {e}")
|
|
541
748
|
|
|
542
749
|
def get_env_status(self, config_type: ConfigType) -> Dict[str, bool]:
|
|
543
750
|
"""Check which environment variables are configured for a service type."""
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
1
2
|
"""
|
|
2
3
|
Adapters Package
|
|
3
4
|
================
|
|
@@ -10,6 +11,7 @@ from .cohere import CohereEmbeddingAdapter
|
|
|
10
11
|
from .jina import JinaEmbeddingAdapter
|
|
11
12
|
from .ollama import OllamaEmbeddingAdapter
|
|
12
13
|
from .openai_compatible import OpenAICompatibleEmbeddingAdapter
|
|
14
|
+
from .realtimex import RealTimeXEmbeddingAdapter
|
|
13
15
|
|
|
14
16
|
__all__ = [
|
|
15
17
|
"BaseEmbeddingAdapter",
|
|
@@ -19,4 +21,5 @@ __all__ = [
|
|
|
19
21
|
"JinaEmbeddingAdapter",
|
|
20
22
|
"CohereEmbeddingAdapter",
|
|
21
23
|
"OllamaEmbeddingAdapter",
|
|
24
|
+
"RealTimeXEmbeddingAdapter",
|
|
22
25
|
]
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
RealTimeX Embedding Adapter
|
|
4
|
+
============================
|
|
5
|
+
|
|
6
|
+
Provides embedding capabilities through RealTimeX SDK proxy.
|
|
7
|
+
Used when running as a local app within RealTimeX desktop.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Any, Dict
|
|
11
|
+
|
|
12
|
+
from realtimex_sdk import LLMPermissionError, LLMProviderError
|
|
13
|
+
|
|
14
|
+
from src.logging import get_logger
|
|
15
|
+
from src.utils.realtimex import get_realtimex_sdk
|
|
16
|
+
|
|
17
|
+
from .base import BaseEmbeddingAdapter, EmbeddingRequest, EmbeddingResponse
|
|
18
|
+
|
|
19
|
+
logger = get_logger("RealTimeXEmbeddingAdapter")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class RealTimeXEmbeddingAdapter(BaseEmbeddingAdapter):
|
|
23
|
+
"""
|
|
24
|
+
Embedding adapter for RealTimeX SDK.
|
|
25
|
+
|
|
26
|
+
Provides embeddings through the RealTimeX Main App proxy.
|
|
27
|
+
No API key or base_url needed - uses RTX_APP_ID for authentication.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, config: Dict[str, Any]):
|
|
31
|
+
"""
|
|
32
|
+
Initialize RealTimeX embedding adapter.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
config: Adapter config (SDK doesn't need api_key/base_url)
|
|
36
|
+
- model: Model name (optional, uses active provider default)
|
|
37
|
+
- dimensions: Expected embedding dimensions
|
|
38
|
+
"""
|
|
39
|
+
# Call parent init (though most fields not needed for RealTimeX)
|
|
40
|
+
super().__init__(config)
|
|
41
|
+
|
|
42
|
+
# SDK is initialized lazily on first request
|
|
43
|
+
logger.debug(
|
|
44
|
+
f"RealTimeX embedding adapter configured: "
|
|
45
|
+
f"model={self.model or 'default'}, dimensions={self.dimensions}"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
async def embed(self, request: EmbeddingRequest) -> EmbeddingResponse:
|
|
49
|
+
"""
|
|
50
|
+
Generate embeddings via RealTimeX SDK.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
request: EmbeddingRequest with texts
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
EmbeddingResponse with embeddings
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
Exception: If SDK request fails or permission denied
|
|
60
|
+
"""
|
|
61
|
+
sdk = get_realtimex_sdk()
|
|
62
|
+
|
|
63
|
+
# Use model from request, fallback to adapter config
|
|
64
|
+
model = request.model or self.model
|
|
65
|
+
|
|
66
|
+
logger.debug(
|
|
67
|
+
f"RealTimeX embed: texts_count={len(request.texts)}, model={model or 'default'}"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
# Call SDK embed function
|
|
72
|
+
result = await sdk.llm.embed(input_text=request.texts, model=model)
|
|
73
|
+
|
|
74
|
+
if not result.success:
|
|
75
|
+
error_msg = result.error or "SDK request failed"
|
|
76
|
+
logger.error(f"RealTimeX embed failed: {error_msg}")
|
|
77
|
+
raise Exception(f"RealTimeX embedding error: {error_msg}")
|
|
78
|
+
|
|
79
|
+
# Validate dimensions match if specified
|
|
80
|
+
if self.dimensions and result.dimensions != self.dimensions:
|
|
81
|
+
logger.warning(
|
|
82
|
+
f"Dimension mismatch: expected {self.dimensions}, "
|
|
83
|
+
f"got {result.dimensions} from {result.model}"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Log success
|
|
87
|
+
logger.debug(
|
|
88
|
+
f"RealTimeX embed success: provider={result.provider}, "
|
|
89
|
+
f"model={result.model}, dimensions={result.dimensions}, "
|
|
90
|
+
f"embeddings_count={len(result.embeddings)}"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return EmbeddingResponse(
|
|
94
|
+
embeddings=result.embeddings,
|
|
95
|
+
model=result.model or model or "unknown",
|
|
96
|
+
dimensions=result.dimensions,
|
|
97
|
+
usage={}, # SDK doesn't currently expose usage metrics
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
except LLMPermissionError as e:
|
|
101
|
+
logger.error(f"RealTimeX permission error: {e}")
|
|
102
|
+
raise PermissionError(
|
|
103
|
+
"RealTimeX embedding permission required. "
|
|
104
|
+
"Ensure 'llm.embed' is in your SDK permissions."
|
|
105
|
+
) from e
|
|
106
|
+
except LLMProviderError as e:
|
|
107
|
+
logger.error(f"RealTimeX provider error: {e}")
|
|
108
|
+
raise Exception(f"RealTimeX embedding provider error: {e}") from e
|
|
109
|
+
except Exception as e:
|
|
110
|
+
logger.error(f"RealTimeX embedding error: {e}")
|
|
111
|
+
raise Exception(f"RealTimeX embedding failed: {e}") from e
|
|
112
|
+
|
|
113
|
+
def get_model_info(self) -> Dict[str, Any]:
|
|
114
|
+
"""
|
|
115
|
+
Return information about the configured model.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Dictionary with model metadata
|
|
119
|
+
"""
|
|
120
|
+
return {
|
|
121
|
+
"provider": "realtimex",
|
|
122
|
+
"model": self.model or "default",
|
|
123
|
+
"dimensions": self.dimensions,
|
|
124
|
+
"description": "RealTimeX SDK embedding proxy",
|
|
125
|
+
}
|
src/services/embedding/client.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
1
2
|
"""
|
|
2
3
|
Embedding Client
|
|
3
4
|
================
|
|
@@ -34,6 +35,32 @@ class EmbeddingClient:
|
|
|
34
35
|
self.logger = get_logger("EmbeddingClient")
|
|
35
36
|
self.manager: EmbeddingProviderManager = get_embedding_provider_manager()
|
|
36
37
|
|
|
38
|
+
# ROUTING LOGIC: Use RealTimeX adapter when source is "realtimex"
|
|
39
|
+
if getattr(self.config, "source", None) == "realtimex":
|
|
40
|
+
try:
|
|
41
|
+
from src.utils.realtimex import should_use_realtimex_sdk
|
|
42
|
+
|
|
43
|
+
if should_use_realtimex_sdk():
|
|
44
|
+
from .adapters.realtimex import RealTimeXEmbeddingAdapter
|
|
45
|
+
|
|
46
|
+
adapter = RealTimeXEmbeddingAdapter(
|
|
47
|
+
{
|
|
48
|
+
"model": self.config.model,
|
|
49
|
+
"dimensions": self.config.dim,
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
self.manager.set_adapter(adapter)
|
|
54
|
+
|
|
55
|
+
self.logger.info(
|
|
56
|
+
f"Using RealTimeX embedding adapter (model: {self.config.model}, "
|
|
57
|
+
f"dimensions: {self.config.dim})"
|
|
58
|
+
)
|
|
59
|
+
return
|
|
60
|
+
except ImportError:
|
|
61
|
+
self.logger.warning("RealTimeX SDK not available, falling back to env config")
|
|
62
|
+
pass
|
|
63
|
+
|
|
37
64
|
# Initialize adapter based on binding configuration
|
|
38
65
|
try:
|
|
39
66
|
adapter = self.manager.get_adapter(
|
src/services/embedding/config.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
1
2
|
"""
|
|
2
3
|
Embedding Configuration
|
|
3
4
|
=======================
|
|
@@ -35,6 +36,7 @@ class EmbeddingConfig:
|
|
|
35
36
|
max_tokens: int = 8192
|
|
36
37
|
request_timeout: int = 30
|
|
37
38
|
input_type: Optional[str] = None # For task-aware embeddings (Cohere, Jina)
|
|
39
|
+
source: Optional[str] = None # "realtimex" when using RTX SDK
|
|
38
40
|
|
|
39
41
|
# Optional provider-specific settings
|
|
40
42
|
encoding_format: str = "float"
|
|
@@ -92,6 +94,7 @@ def get_embedding_config() -> EmbeddingConfig:
|
|
|
92
94
|
base_url=config.get("base_url"),
|
|
93
95
|
api_version=config.get("api_version"),
|
|
94
96
|
dim=config.get("dimensions", 3072),
|
|
97
|
+
source=config.get("source"), # "realtimex" when using RTX
|
|
95
98
|
)
|
|
96
99
|
except ImportError:
|
|
97
100
|
# Unified config service not yet available, fall back to env
|
src/services/llm/__init__.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
1
2
|
"""
|
|
2
3
|
LLM Service
|
|
3
4
|
===========
|
|
@@ -47,8 +48,8 @@ Usage:
|
|
|
47
48
|
from src.services.llm import sanitize_url, is_local_llm_server
|
|
48
49
|
"""
|
|
49
50
|
|
|
50
|
-
#
|
|
51
|
-
|
|
51
|
+
# Note: cloud_provider and local_provider are lazy-loaded via __getattr__
|
|
52
|
+
# to avoid importing lightrag at module load time
|
|
52
53
|
from .capabilities import (
|
|
53
54
|
DEFAULT_CAPABILITIES,
|
|
54
55
|
MODEL_OVERRIDES,
|
|
@@ -139,7 +140,7 @@ __all__ = [
|
|
|
139
140
|
"DEFAULT_MAX_RETRIES",
|
|
140
141
|
"DEFAULT_RETRY_DELAY",
|
|
141
142
|
"DEFAULT_EXPONENTIAL_BACKOFF",
|
|
142
|
-
# Providers
|
|
143
|
+
# Providers (lazy loaded)
|
|
143
144
|
"cloud_provider",
|
|
144
145
|
"local_provider",
|
|
145
146
|
# Utils
|
|
@@ -150,3 +151,16 @@ __all__ = [
|
|
|
150
151
|
"clean_thinking_tags",
|
|
151
152
|
"extract_response_content",
|
|
152
153
|
]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def __getattr__(name: str):
|
|
157
|
+
"""Lazy import for provider modules that depend on heavy libraries."""
|
|
158
|
+
if name == "cloud_provider":
|
|
159
|
+
from . import cloud_provider
|
|
160
|
+
|
|
161
|
+
return cloud_provider
|
|
162
|
+
if name == "local_provider":
|
|
163
|
+
from . import local_provider
|
|
164
|
+
|
|
165
|
+
return local_provider
|
|
166
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|