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.
Files changed (145) hide show
  1. {realtimex_deeptutor-0.5.0.post1.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/METADATA +24 -17
  2. {realtimex_deeptutor-0.5.0.post1.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/RECORD +143 -123
  3. {realtimex_deeptutor-0.5.0.post1.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/WHEEL +1 -1
  4. realtimex_deeptutor-0.5.0.post3.dist-info/entry_points.txt +4 -0
  5. {realtimex_deeptutor-0.5.0.post1.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/top_level.txt +1 -0
  6. scripts/__init__.py +1 -0
  7. scripts/audit_prompts.py +179 -0
  8. scripts/check_install.py +460 -0
  9. scripts/generate_roster.py +327 -0
  10. scripts/install_all.py +653 -0
  11. scripts/migrate_kb.py +655 -0
  12. scripts/start.py +807 -0
  13. scripts/start_web.py +632 -0
  14. scripts/sync_prompts_from_en.py +147 -0
  15. src/__init__.py +2 -2
  16. src/agents/ideagen/material_organizer_agent.py +2 -0
  17. src/agents/solve/__init__.py +6 -0
  18. src/agents/solve/main_solver.py +9 -0
  19. src/agents/solve/prompts/zh/analysis_loop/investigate_agent.yaml +9 -7
  20. src/agents/solve/session_manager.py +345 -0
  21. src/api/main.py +14 -0
  22. src/api/routers/chat.py +3 -3
  23. src/api/routers/co_writer.py +12 -7
  24. src/api/routers/config.py +1 -0
  25. src/api/routers/guide.py +3 -1
  26. src/api/routers/ideagen.py +7 -0
  27. src/api/routers/knowledge.py +64 -12
  28. src/api/routers/question.py +2 -0
  29. src/api/routers/realtimex.py +137 -0
  30. src/api/routers/research.py +9 -0
  31. src/api/routers/solve.py +120 -2
  32. src/cli/__init__.py +13 -0
  33. src/cli/start.py +209 -0
  34. src/config/constants.py +11 -9
  35. src/knowledge/add_documents.py +453 -213
  36. src/knowledge/extract_numbered_items.py +9 -10
  37. src/knowledge/initializer.py +102 -101
  38. src/knowledge/manager.py +251 -74
  39. src/knowledge/progress_tracker.py +43 -2
  40. src/knowledge/start_kb.py +11 -2
  41. src/logging/__init__.py +5 -0
  42. src/logging/adapters/__init__.py +1 -0
  43. src/logging/adapters/lightrag.py +25 -18
  44. src/logging/adapters/llamaindex.py +1 -0
  45. src/logging/config.py +30 -27
  46. src/logging/handlers/__init__.py +1 -0
  47. src/logging/handlers/console.py +7 -50
  48. src/logging/handlers/file.py +5 -20
  49. src/logging/handlers/websocket.py +23 -19
  50. src/logging/logger.py +161 -126
  51. src/logging/stats/__init__.py +1 -0
  52. src/logging/stats/llm_stats.py +37 -17
  53. src/services/__init__.py +17 -1
  54. src/services/config/__init__.py +1 -0
  55. src/services/config/knowledge_base_config.py +1 -0
  56. src/services/config/loader.py +1 -1
  57. src/services/config/unified_config.py +211 -4
  58. src/services/embedding/__init__.py +1 -0
  59. src/services/embedding/adapters/__init__.py +3 -0
  60. src/services/embedding/adapters/base.py +1 -0
  61. src/services/embedding/adapters/cohere.py +1 -0
  62. src/services/embedding/adapters/jina.py +1 -0
  63. src/services/embedding/adapters/ollama.py +1 -0
  64. src/services/embedding/adapters/openai_compatible.py +1 -0
  65. src/services/embedding/adapters/realtimex.py +125 -0
  66. src/services/embedding/client.py +27 -0
  67. src/services/embedding/config.py +3 -0
  68. src/services/embedding/provider.py +1 -0
  69. src/services/llm/__init__.py +17 -3
  70. src/services/llm/capabilities.py +47 -0
  71. src/services/llm/client.py +32 -0
  72. src/services/llm/cloud_provider.py +21 -4
  73. src/services/llm/config.py +36 -2
  74. src/services/llm/error_mapping.py +1 -0
  75. src/services/llm/exceptions.py +30 -0
  76. src/services/llm/factory.py +55 -16
  77. src/services/llm/local_provider.py +1 -0
  78. src/services/llm/providers/anthropic.py +1 -0
  79. src/services/llm/providers/base_provider.py +1 -0
  80. src/services/llm/providers/open_ai.py +1 -0
  81. src/services/llm/realtimex_provider.py +240 -0
  82. src/services/llm/registry.py +1 -0
  83. src/services/llm/telemetry.py +1 -0
  84. src/services/llm/types.py +1 -0
  85. src/services/llm/utils.py +1 -0
  86. src/services/prompt/__init__.py +1 -0
  87. src/services/prompt/manager.py +3 -2
  88. src/services/rag/__init__.py +27 -5
  89. src/services/rag/components/__init__.py +1 -0
  90. src/services/rag/components/base.py +1 -0
  91. src/services/rag/components/chunkers/__init__.py +1 -0
  92. src/services/rag/components/chunkers/base.py +1 -0
  93. src/services/rag/components/chunkers/fixed.py +1 -0
  94. src/services/rag/components/chunkers/numbered_item.py +1 -0
  95. src/services/rag/components/chunkers/semantic.py +1 -0
  96. src/services/rag/components/embedders/__init__.py +1 -0
  97. src/services/rag/components/embedders/base.py +1 -0
  98. src/services/rag/components/embedders/openai.py +1 -0
  99. src/services/rag/components/indexers/__init__.py +1 -0
  100. src/services/rag/components/indexers/base.py +1 -0
  101. src/services/rag/components/indexers/graph.py +5 -44
  102. src/services/rag/components/indexers/lightrag.py +5 -44
  103. src/services/rag/components/indexers/vector.py +1 -0
  104. src/services/rag/components/parsers/__init__.py +1 -0
  105. src/services/rag/components/parsers/base.py +1 -0
  106. src/services/rag/components/parsers/markdown.py +1 -0
  107. src/services/rag/components/parsers/pdf.py +1 -0
  108. src/services/rag/components/parsers/text.py +1 -0
  109. src/services/rag/components/retrievers/__init__.py +1 -0
  110. src/services/rag/components/retrievers/base.py +1 -0
  111. src/services/rag/components/retrievers/dense.py +1 -0
  112. src/services/rag/components/retrievers/hybrid.py +5 -44
  113. src/services/rag/components/retrievers/lightrag.py +5 -44
  114. src/services/rag/components/routing.py +48 -0
  115. src/services/rag/factory.py +112 -46
  116. src/services/rag/pipeline.py +1 -0
  117. src/services/rag/pipelines/__init__.py +27 -18
  118. src/services/rag/pipelines/lightrag.py +1 -0
  119. src/services/rag/pipelines/llamaindex.py +99 -0
  120. src/services/rag/pipelines/raganything.py +67 -100
  121. src/services/rag/pipelines/raganything_docling.py +368 -0
  122. src/services/rag/service.py +5 -12
  123. src/services/rag/types.py +1 -0
  124. src/services/rag/utils/__init__.py +17 -0
  125. src/services/rag/utils/image_migration.py +279 -0
  126. src/services/search/__init__.py +1 -0
  127. src/services/search/base.py +1 -0
  128. src/services/search/consolidation.py +1 -0
  129. src/services/search/providers/__init__.py +1 -0
  130. src/services/search/providers/baidu.py +1 -0
  131. src/services/search/providers/exa.py +1 -0
  132. src/services/search/providers/jina.py +1 -0
  133. src/services/search/providers/perplexity.py +1 -0
  134. src/services/search/providers/serper.py +1 -0
  135. src/services/search/providers/tavily.py +1 -0
  136. src/services/search/types.py +1 -0
  137. src/services/settings/__init__.py +1 -0
  138. src/services/settings/interface_settings.py +78 -0
  139. src/services/setup/__init__.py +1 -0
  140. src/services/tts/__init__.py +1 -0
  141. src/services/tts/config.py +1 -0
  142. src/utils/realtimex.py +284 -0
  143. realtimex_deeptutor-0.5.0.post1.dist-info/entry_points.txt +0 -2
  144. src/services/rag/pipelines/academic.py +0 -44
  145. {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
- from . import config, embedding, llm, prompt, rag, search, setup, tts
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}")
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  """
2
3
  Configuration Service
3
4
  =====================
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  """
2
3
  Knowledge Base Configuration Service
3
4
  =====================================
@@ -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
- result.insert(0, display_default)
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 != "default":
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
- return self._save_configs(config_type, data)
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
  Embedding Service
3
4
  =================
@@ -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
  ]
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  """
2
3
  Base Embedding Adapter
3
4
  =======================
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  """Cohere Embedding Adapter for v1 and v2 API."""
2
3
 
3
4
  import logging
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  """Jina AI embedding adapter with task-aware embeddings and late chunking."""
2
3
 
3
4
  import logging
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  """Ollama Embedding Adapter for local embeddings."""
2
3
 
3
4
  import logging
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  """OpenAI-compatible embedding adapter for OpenAI, Azure, HuggingFace, LM Studio, etc."""
2
3
 
3
4
  import logging
@@ -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
+ }
@@ -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(
@@ -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
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  """
2
3
  Embedding Provider Manager
3
4
  ===========================
@@ -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
- # Also expose the providers for direct access if needed
51
- from . import cloud_provider, local_provider
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}")