deeptutor-knowledge 1.7.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.
- deeptutor/api/routers/knowledge.py +5225 -0
- deeptutor/api/routers/marginnote4.py +278 -0
- deeptutor/capabilities/ima/__init__.py +6 -0
- deeptutor/capabilities/ima/binding.py +118 -0
- deeptutor/capabilities/ima/capability.py +92 -0
- deeptutor/capabilities/ima/prompts/en/system.md +33 -0
- deeptutor/capabilities/ima/prompts/zh/system.md +24 -0
- deeptutor/capabilities/ima/tools.py +476 -0
- deeptutor/capabilities/marginnote4/__init__.py +13 -0
- deeptutor/capabilities/marginnote4/binding.py +66 -0
- deeptutor/capabilities/marginnote4/capability.py +80 -0
- deeptutor/capabilities/marginnote4/models.py +173 -0
- deeptutor/capabilities/marginnote4/prompts/en/system.md +54 -0
- deeptutor/capabilities/marginnote4/prompts/zh/system.md +44 -0
- deeptutor/capabilities/marginnote4/store.py +562 -0
- deeptutor/capabilities/marginnote4/tools.py +329 -0
- deeptutor/capabilities/obsidian/__init__.py +6 -0
- deeptutor/capabilities/obsidian/binding.py +75 -0
- deeptutor/capabilities/obsidian/capability.py +82 -0
- deeptutor/capabilities/obsidian/prompts/en/system.md +32 -0
- deeptutor/capabilities/obsidian/prompts/zh/system.md +28 -0
- deeptutor/capabilities/obsidian/tools.py +399 -0
- deeptutor/capabilities/obsidian/vault.py +341 -0
- deeptutor/knowledge/__init__.py +28 -0
- deeptutor/knowledge/add_documents.py +734 -0
- deeptutor/knowledge/indexing_run.py +432 -0
- deeptutor/knowledge/initializer.py +448 -0
- deeptutor/knowledge/manager.py +2570 -0
- deeptutor/knowledge/module_service.py +29 -0
- deeptutor/knowledge/naming.py +40 -0
- deeptutor/knowledge/progress_events.py +45 -0
- deeptutor/knowledge/progress_tracker.py +355 -0
- deeptutor/knowledge/staging.py +69 -0
- deeptutor/services/config/knowledge_base_config.py +239 -0
- deeptutor/services/embedding/__init__.py +46 -0
- deeptutor/services/embedding/adapters/__init__.py +40 -0
- deeptutor/services/embedding/adapters/base.py +196 -0
- deeptutor/services/embedding/adapters/cohere.py +176 -0
- deeptutor/services/embedding/adapters/dashscope_native.py +242 -0
- deeptutor/services/embedding/adapters/gemini.py +462 -0
- deeptutor/services/embedding/adapters/jina.py +161 -0
- deeptutor/services/embedding/adapters/ollama.py +145 -0
- deeptutor/services/embedding/adapters/openai_compatible.py +406 -0
- deeptutor/services/embedding/adapters/openai_sdk.py +150 -0
- deeptutor/services/embedding/client.py +323 -0
- deeptutor/services/embedding/config.py +114 -0
- deeptutor/services/embedding/request_options.py +46 -0
- deeptutor/services/embedding/validation.py +128 -0
- deeptutor/services/github_source/__init__.py +5 -0
- deeptutor/services/github_source/client.py +164 -0
- deeptutor/services/github_source/sync.py +384 -0
- deeptutor/services/github_source/sync_service.py +72 -0
- deeptutor/services/rag/__init__.py +21 -0
- deeptutor/services/rag/embedding_binding.py +331 -0
- deeptutor/services/rag/embedding_signature.py +63 -0
- deeptutor/services/rag/eval/__init__.py +92 -0
- deeptutor/services/rag/eval/dataset.py +244 -0
- deeptutor/services/rag/eval/diff.py +312 -0
- deeptutor/services/rag/eval/matching.py +218 -0
- deeptutor/services/rag/eval/metrics.py +213 -0
- deeptutor/services/rag/eval/report.py +111 -0
- deeptutor/services/rag/eval/runner.py +309 -0
- deeptutor/services/rag/factory.py +369 -0
- deeptutor/services/rag/file_routing.py +5 -0
- deeptutor/services/rag/index_probe.py +327 -0
- deeptutor/services/rag/index_versioning.py +387 -0
- deeptutor/services/rag/kb_paths.py +50 -0
- deeptutor/services/rag/linked_kb.py +237 -0
- deeptutor/services/rag/pipelines/__init__.py +7 -0
- deeptutor/services/rag/pipelines/base.py +41 -0
- deeptutor/services/rag/pipelines/graphrag/__init__.py +18 -0
- deeptutor/services/rag/pipelines/graphrag/compatibility.py +42 -0
- deeptutor/services/rag/pipelines/graphrag/completion_adapter.py +251 -0
- deeptutor/services/rag/pipelines/graphrag/config.py +357 -0
- deeptutor/services/rag/pipelines/graphrag/engine.py +479 -0
- deeptutor/services/rag/pipelines/graphrag/errors.py +328 -0
- deeptutor/services/rag/pipelines/graphrag/ingestion.py +135 -0
- deeptutor/services/rag/pipelines/graphrag/pandas_compat.py +68 -0
- deeptutor/services/rag/pipelines/graphrag/pipeline.py +284 -0
- deeptutor/services/rag/pipelines/graphrag/provider.py +134 -0
- deeptutor/services/rag/pipelines/graphrag/storage.py +127 -0
- deeptutor/services/rag/pipelines/ima/__init__.py +28 -0
- deeptutor/services/rag/pipelines/ima/client.py +364 -0
- deeptutor/services/rag/pipelines/ima/config.py +140 -0
- deeptutor/services/rag/pipelines/ima/envelope.py +109 -0
- deeptutor/services/rag/pipelines/ima/inventory.py +209 -0
- deeptutor/services/rag/pipelines/ima/media.py +190 -0
- deeptutor/services/rag/pipelines/ima/models.py +348 -0
- deeptutor/services/rag/pipelines/ima/notes.py +240 -0
- deeptutor/services/rag/pipelines/ima/pipeline.py +238 -0
- deeptutor/services/rag/pipelines/ima/probe.py +105 -0
- deeptutor/services/rag/pipelines/ima/sources.py +105 -0
- deeptutor/services/rag/pipelines/ima/transport.py +130 -0
- deeptutor/services/rag/pipelines/kiwix/__init__.py +1 -0
- deeptutor/services/rag/pipelines/kiwix/client.py +369 -0
- deeptutor/services/rag/pipelines/kiwix/pipeline.py +75 -0
- deeptutor/services/rag/pipelines/lightrag/README.md +78 -0
- deeptutor/services/rag/pipelines/lightrag/__init__.py +15 -0
- deeptutor/services/rag/pipelines/lightrag/block_policy.py +266 -0
- deeptutor/services/rag/pipelines/lightrag/cache_reuse.py +137 -0
- deeptutor/services/rag/pipelines/lightrag/config.py +453 -0
- deeptutor/services/rag/pipelines/lightrag/engine.py +536 -0
- deeptutor/services/rag/pipelines/lightrag/indexing_policy.py +696 -0
- deeptutor/services/rag/pipelines/lightrag/ingress.py +364 -0
- deeptutor/services/rag/pipelines/lightrag/parser.py +99 -0
- deeptutor/services/rag/pipelines/lightrag/pipeline.py +692 -0
- deeptutor/services/rag/pipelines/lightrag/roles.py +132 -0
- deeptutor/services/rag/pipelines/lightrag/sidecar.py +320 -0
- deeptutor/services/rag/pipelines/lightrag/storage.py +338 -0
- deeptutor/services/rag/pipelines/lightrag/worker.py +259 -0
- deeptutor/services/rag/pipelines/lightrag/write_lock.py +55 -0
- deeptutor/services/rag/pipelines/lightrag_server/__init__.py +15 -0
- deeptutor/services/rag/pipelines/lightrag_server/client.py +149 -0
- deeptutor/services/rag/pipelines/lightrag_server/config.py +68 -0
- deeptutor/services/rag/pipelines/lightrag_server/pipeline.py +120 -0
- deeptutor/services/rag/pipelines/lightrag_server/probe.py +110 -0
- deeptutor/services/rag/pipelines/llamaindex/__init__.py +3 -0
- deeptutor/services/rag/pipelines/llamaindex/config.py +199 -0
- deeptutor/services/rag/pipelines/llamaindex/document_loader.py +648 -0
- deeptutor/services/rag/pipelines/llamaindex/embedding_adapter.py +264 -0
- deeptutor/services/rag/pipelines/llamaindex/errors.py +76 -0
- deeptutor/services/rag/pipelines/llamaindex/exercise_lookup.py +373 -0
- deeptutor/services/rag/pipelines/llamaindex/ingestion.py +129 -0
- deeptutor/services/rag/pipelines/llamaindex/pipeline.py +556 -0
- deeptutor/services/rag/pipelines/llamaindex/rerank.py +130 -0
- deeptutor/services/rag/pipelines/llamaindex/retrievers.py +360 -0
- deeptutor/services/rag/pipelines/llamaindex/storage.py +655 -0
- deeptutor/services/rag/pipelines/llamaindex/vector_store.py +288 -0
- deeptutor/services/rag/pipelines/modes.py +48 -0
- deeptutor/services/rag/pipelines/pageindex/__init__.py +47 -0
- deeptutor/services/rag/pipelines/pageindex/client.py +146 -0
- deeptutor/services/rag/pipelines/pageindex/config.py +49 -0
- deeptutor/services/rag/pipelines/pageindex/pipeline.py +313 -0
- deeptutor/services/rag/pipelines/pageindex/reasoning.py +262 -0
- deeptutor/services/rag/pipelines/pageindex/storage.py +133 -0
- deeptutor/services/rag/pipelines/pageindex/tools.py +221 -0
- deeptutor/services/rag/pipelines/weknora/__init__.py +13 -0
- deeptutor/services/rag/pipelines/weknora/client.py +106 -0
- deeptutor/services/rag/pipelines/weknora/config.py +56 -0
- deeptutor/services/rag/pipelines/weknora/pipeline.py +74 -0
- deeptutor/services/rag/pipelines/weknora/probe.py +80 -0
- deeptutor/services/rag/preflight.py +274 -0
- deeptutor/services/rag/provider_binding.py +70 -0
- deeptutor/services/rag/query_rewrite.py +186 -0
- deeptutor/services/rag/service.py +379 -0
- deeptutor/services/rag/smart_retriever.py +81 -0
- deeptutor/services/rag/source_pages.py +292 -0
- deeptutor/services/rag/source_visuals.py +434 -0
- deeptutor/services/rag/visual_assets.py +430 -0
- deeptutor/services/rag/visual_coverage.py +165 -0
- deeptutor/services/web_source/__init__.py +1 -0
- deeptutor/services/web_source/bilingual.py +255 -0
- deeptutor/services/web_source/crawler.py +681 -0
- deeptutor/services/web_source/html_extractor.py +643 -0
- deeptutor/services/web_source/markdown.py +28 -0
- deeptutor/services/web_source/navigation.py +72 -0
- deeptutor/services/web_source/repository.py +640 -0
- deeptutor/services/web_source/robots.py +182 -0
- deeptutor/services/web_source/scheduler.py +336 -0
- deeptutor/services/web_source/snapshot_assets.py +171 -0
- deeptutor/services/web_source/sync.py +253 -0
- deeptutor/services/workspace/kb_move.py +446 -0
- deeptutor/tools/rag_tool.py +110 -0
- deeptutor_cli/kb.py +756 -0
- deeptutor_knowledge-1.7.0.dist-info/METADATA +15 -0
- deeptutor_knowledge-1.7.0.dist-info/RECORD +168 -0
- deeptutor_knowledge-1.7.0.dist-info/WHEEL +5 -0
- deeptutor_knowledge-1.7.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
"""Bridge DeepTutor runtime configuration into the LightRAG 1.5 native SDK.
|
|
2
|
+
|
|
3
|
+
This module is the decoupling seam: it exposes availability + mode helpers and
|
|
4
|
+
builds the three adapters LightRAG needs from DeepTutor's already-resolved LLM,
|
|
5
|
+
vision, and embedding clients. LightRAG imports remain lazy so every other RAG
|
|
6
|
+
provider can import without the optional extra installed.
|
|
7
|
+
|
|
8
|
+
Decoupling notes:
|
|
9
|
+
* ``llm_model_func`` / ``vision_model_func`` wrap DeepTutor's unified model
|
|
10
|
+
callables and DROP LightRAG's internal kwargs (``hashing_kv``,
|
|
11
|
+
``keyword_extraction``, …) so they never leak into ``factory.complete``.
|
|
12
|
+
* ``embedding_func`` reuses DeepTutor's embedding client, wrapped in LightRAG's
|
|
13
|
+
``EmbeddingFunc`` with the active model's dimension.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
from collections.abc import Awaitable, Callable
|
|
20
|
+
from copy import deepcopy
|
|
21
|
+
import importlib.util
|
|
22
|
+
import inspect
|
|
23
|
+
import logging
|
|
24
|
+
import re
|
|
25
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from lightrag.utils import EmbeddingFunc
|
|
29
|
+
|
|
30
|
+
from deeptutor.multi_user.models import CurrentUser
|
|
31
|
+
from deeptutor.services.embedding.config import EmbeddingConfig
|
|
32
|
+
from deeptutor.services.llm.config import LLMConfig
|
|
33
|
+
|
|
34
|
+
from .worker import OwnerLoopBridge
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
_T = TypeVar("_T")
|
|
39
|
+
|
|
40
|
+
# LightRAG's native retrieval modes. ``hybrid`` (KG + vector) is the safest
|
|
41
|
+
# general default and matches the shared per-KB ``search_mode`` default.
|
|
42
|
+
SUPPORTED_MODES = ("naive", "local", "global", "hybrid", "mix")
|
|
43
|
+
DEFAULT_MODE = "hybrid"
|
|
44
|
+
|
|
45
|
+
# Conservative cap for the embedding wrapper when the model doesn't advertise one.
|
|
46
|
+
_DEFAULT_MAX_TOKEN_SIZE = 8192
|
|
47
|
+
|
|
48
|
+
# Keep retries at the LightRAG adapter boundary so the SDK receives one
|
|
49
|
+
# predictable policy for both text and vision calls. Provider retries are disabled
|
|
50
|
+
# on every attempt to prevent the two retry layers from multiplying.
|
|
51
|
+
_ADAPTER_MAX_ATTEMPTS = 3
|
|
52
|
+
_ADAPTER_RETRY_DELAYS_SECONDS = (1.0, 2.0)
|
|
53
|
+
_ADAPTER_MAX_RETRY_DELAY_SECONDS = 60.0
|
|
54
|
+
_RETRYABLE_HTTP_STATUS_CODES = frozenset({408, 429, 500, 502, 503, 504, 529})
|
|
55
|
+
_HTTP_STATUS_PATTERN = re.compile(
|
|
56
|
+
r"\b(?:http(?: status)?|status(?: code)?|error code)\s*[:=-]?\s*(\d{3})\b",
|
|
57
|
+
re.I,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class LightRagNotAvailableError(RuntimeError):
|
|
62
|
+
"""Raised when the optional ``lightrag-hku`` dependency is not installed."""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class LightRagNotConfiguredError(RuntimeError):
|
|
66
|
+
"""Raised when DeepTutor's LLM / embedding config can't back LightRAG."""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _http_status_code(exc: Exception) -> int | None:
|
|
70
|
+
"""Return a structured or safely normalized HTTP status for an LLM error."""
|
|
71
|
+
status_code = getattr(exc, "status_code", None)
|
|
72
|
+
if isinstance(status_code, int) and not isinstance(status_code, bool):
|
|
73
|
+
return status_code
|
|
74
|
+
|
|
75
|
+
response = getattr(exc, "response", None)
|
|
76
|
+
response_status = getattr(response, "status_code", None)
|
|
77
|
+
if isinstance(response_status, int) and not isinstance(response_status, bool):
|
|
78
|
+
return response_status
|
|
79
|
+
|
|
80
|
+
# The Codex provider currently returns a safe, normalized ``HTTP NNN``
|
|
81
|
+
# message through LLMAPIError instead of preserving the status attribute.
|
|
82
|
+
is_llm_api_error = any(cls.__name__ == "LLMAPIError" for cls in type(exc).__mro__)
|
|
83
|
+
message = getattr(exc, "message", None)
|
|
84
|
+
if is_llm_api_error and isinstance(message, str):
|
|
85
|
+
match = _HTTP_STATUS_PATTERN.search(message)
|
|
86
|
+
if match is not None:
|
|
87
|
+
return int(match.group(1))
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _retry_classification(exc: Exception) -> tuple[bool, str]:
|
|
92
|
+
"""Classify retryability without inspecting or logging provider payloads."""
|
|
93
|
+
from deeptutor.services.llm.request_compat import is_transient_transport_error
|
|
94
|
+
|
|
95
|
+
status_code = _http_status_code(exc)
|
|
96
|
+
if status_code is not None:
|
|
97
|
+
return status_code in _RETRYABLE_HTTP_STATUS_CODES, f"http_{status_code}"
|
|
98
|
+
if is_transient_transport_error(exc):
|
|
99
|
+
return True, "transport"
|
|
100
|
+
return False, "non_retryable"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _retry_delay_seconds(exc: Exception, scheduled_delay: float) -> float:
|
|
104
|
+
"""Honor a safe Retry-After value without allowing unbounded sleeps."""
|
|
105
|
+
from deeptutor.services.llm.error_mapping import retry_after_seconds
|
|
106
|
+
|
|
107
|
+
requested_delay = retry_after_seconds(exc)
|
|
108
|
+
if requested_delay is None:
|
|
109
|
+
return scheduled_delay
|
|
110
|
+
return min(requested_delay, _ADAPTER_MAX_RETRY_DELAY_SECONDS)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
async def _run_adapter_with_retry(
|
|
114
|
+
request: Callable[[], Awaitable[_T]],
|
|
115
|
+
*,
|
|
116
|
+
io_bridge: OwnerLoopBridge | None,
|
|
117
|
+
) -> _T:
|
|
118
|
+
"""Run one adapter request with bounded, non-multiplying retries."""
|
|
119
|
+
for attempt in range(1, _ADAPTER_MAX_ATTEMPTS + 1):
|
|
120
|
+
try:
|
|
121
|
+
if io_bridge is not None:
|
|
122
|
+
return await io_bridge.run(request)
|
|
123
|
+
return await request()
|
|
124
|
+
except Exception as exc:
|
|
125
|
+
should_retry, status = _retry_classification(exc)
|
|
126
|
+
if not should_retry or attempt == _ADAPTER_MAX_ATTEMPTS:
|
|
127
|
+
raise
|
|
128
|
+
logger.warning(
|
|
129
|
+
"LightRAG adapter retry attempt=%d exception=%s status=%s",
|
|
130
|
+
attempt,
|
|
131
|
+
type(exc).__name__,
|
|
132
|
+
status,
|
|
133
|
+
)
|
|
134
|
+
delay = _retry_delay_seconds(exc, _ADAPTER_RETRY_DELAYS_SECONDS[attempt - 1])
|
|
135
|
+
await asyncio.sleep(delay)
|
|
136
|
+
|
|
137
|
+
raise AssertionError("unreachable")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def is_lightrag_available() -> bool:
|
|
141
|
+
"""True when the native LightRAG SDK can be imported.
|
|
142
|
+
|
|
143
|
+
Opt-in extra: ``pip install 'deeptutor[rag-lightrag]'``. Until installed the
|
|
144
|
+
provider is hidden / blocked in the UI.
|
|
145
|
+
"""
|
|
146
|
+
return importlib.util.find_spec("lightrag") is not None
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def normalize_mode(mode: str | None) -> str:
|
|
150
|
+
"""Coerce a stored ``search_mode`` to a valid LightRAG query mode.
|
|
151
|
+
|
|
152
|
+
The per-KB ``search_mode`` field is shared across engines; anything that
|
|
153
|
+
isn't a LightRAG mode falls back to :data:`DEFAULT_MODE`.
|
|
154
|
+
"""
|
|
155
|
+
candidate = (mode or "").strip().lower()
|
|
156
|
+
return candidate if candidate in SUPPORTED_MODES else DEFAULT_MODE
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def query_kwargs_from_settings() -> dict:
|
|
160
|
+
"""``QueryParam`` values from runtime settings."""
|
|
161
|
+
try:
|
|
162
|
+
from deeptutor.services.config import load_lightrag_settings
|
|
163
|
+
|
|
164
|
+
settings = load_lightrag_settings()
|
|
165
|
+
return {
|
|
166
|
+
"top_k": int(settings.get("top_k", 60)),
|
|
167
|
+
"response_type": str(settings.get("response_type") or "Multiple Paragraphs"),
|
|
168
|
+
}
|
|
169
|
+
except Exception:
|
|
170
|
+
return {}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def indexing_kwargs_from_settings() -> dict:
|
|
174
|
+
"""Native parser-pool knobs from runtime settings."""
|
|
175
|
+
try:
|
|
176
|
+
from deeptutor.services.config import load_lightrag_settings
|
|
177
|
+
|
|
178
|
+
settings = load_lightrag_settings()
|
|
179
|
+
return {"max_parallel_parse_native": int(settings.get("max_concurrent_files", 1))}
|
|
180
|
+
except Exception:
|
|
181
|
+
return {}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def constructor_kwargs_from_settings() -> dict:
|
|
185
|
+
"""Direct LightRAG constructor knobs from runtime settings."""
|
|
186
|
+
try:
|
|
187
|
+
from deeptutor.services.config import load_lightrag_settings
|
|
188
|
+
|
|
189
|
+
settings = load_lightrag_settings()
|
|
190
|
+
return {
|
|
191
|
+
"llm_model_max_async": int(settings.get("llm_model_max_async", 4)),
|
|
192
|
+
"entity_extract_max_gleaning": int(settings.get("entity_extract_max_gleaning", 1)),
|
|
193
|
+
"default_llm_timeout": int(settings.get("llm_timeout", 240)),
|
|
194
|
+
}
|
|
195
|
+
except Exception:
|
|
196
|
+
return {}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _lightrag_llm_selection_from_settings(*, strict: bool) -> dict[str, str] | None:
|
|
200
|
+
try:
|
|
201
|
+
from deeptutor.services.config import load_lightrag_settings
|
|
202
|
+
|
|
203
|
+
settings = load_lightrag_settings()
|
|
204
|
+
profile_id = str(settings.get("llm_profile_id") or "").strip()
|
|
205
|
+
model_id = str(settings.get("llm_model_id") or "").strip()
|
|
206
|
+
if not profile_id and not model_id:
|
|
207
|
+
return None
|
|
208
|
+
if not profile_id or not model_id:
|
|
209
|
+
if strict:
|
|
210
|
+
raise ValueError("The LightRAG LLM selection is incomplete.")
|
|
211
|
+
logger.warning("Ignoring incomplete LightRAG LLM selection; using the active model")
|
|
212
|
+
return None
|
|
213
|
+
return {"profile_id": profile_id, "model_id": model_id}
|
|
214
|
+
except Exception:
|
|
215
|
+
if strict:
|
|
216
|
+
raise
|
|
217
|
+
logger.warning(
|
|
218
|
+
"Could not read LightRAG LLM selection; using the active model",
|
|
219
|
+
exc_info=True,
|
|
220
|
+
)
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def lightrag_llm_selection_from_settings() -> dict[str, str] | None:
|
|
225
|
+
"""Return the released query-model selection with its fallback semantics."""
|
|
226
|
+
return _lightrag_llm_selection_from_settings(strict=False)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def lightrag_indexing_selection_from_settings() -> dict[str, str] | None:
|
|
230
|
+
"""Return the indexing default, rejecting unreadable or partial settings."""
|
|
231
|
+
return _lightrag_llm_selection_from_settings(strict=True)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def resolve_lightrag_query_llm_config():
|
|
235
|
+
"""Resolve legacy settings, access-checking the active fallback as well."""
|
|
236
|
+
from deeptutor.multi_user.model_access import apply_allowed_llm_selection
|
|
237
|
+
from deeptutor.services.model_selection.runtime import resolve_llm_config_for_selection
|
|
238
|
+
|
|
239
|
+
from .indexing_policy import _active_catalog_selection
|
|
240
|
+
|
|
241
|
+
selection = lightrag_llm_selection_from_settings() or _active_catalog_selection()
|
|
242
|
+
if selection is None:
|
|
243
|
+
raise LightRagNotConfiguredError("Choose an accessible LightRAG model in engine settings.")
|
|
244
|
+
apply_allowed_llm_selection(selection)
|
|
245
|
+
try:
|
|
246
|
+
return resolve_llm_config_for_selection(selection)
|
|
247
|
+
except ValueError:
|
|
248
|
+
fallback = _active_catalog_selection()
|
|
249
|
+
if fallback is None or fallback == selection:
|
|
250
|
+
raise
|
|
251
|
+
apply_allowed_llm_selection(fallback)
|
|
252
|
+
return resolve_llm_config_for_selection(fallback)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def build_llm_model_func(
|
|
256
|
+
*,
|
|
257
|
+
io_bridge: OwnerLoopBridge | None = None,
|
|
258
|
+
llm_config: LLMConfig | None = None,
|
|
259
|
+
owner: CurrentUser | None = None,
|
|
260
|
+
):
|
|
261
|
+
"""Wrap DeepTutor's unified LLM callable for LightRAG.
|
|
262
|
+
|
|
263
|
+
Preserve provider-facing structured-output requests while keeping
|
|
264
|
+
LightRAG's orchestration-only kwargs out of DeepTutor's provider layer.
|
|
265
|
+
|
|
266
|
+
``enable_cot`` is intentionally an output-formatting switch in LightRAG,
|
|
267
|
+
not a request to enable provider reasoning. DeepTutor controls reasoning
|
|
268
|
+
through the frozen role configuration and returns answer content without
|
|
269
|
+
exposing provider reasoning text.
|
|
270
|
+
"""
|
|
271
|
+
if llm_config is None:
|
|
272
|
+
from deeptutor.services.llm import get_llm_client
|
|
273
|
+
|
|
274
|
+
base = get_llm_client().get_model_func()
|
|
275
|
+
else:
|
|
276
|
+
from deeptutor.services.llm.client import build_model_func_for_config
|
|
277
|
+
|
|
278
|
+
base = build_model_func_for_config(llm_config, allow_multimodal=False)
|
|
279
|
+
|
|
280
|
+
async def llm_model_func(
|
|
281
|
+
prompt="",
|
|
282
|
+
system_prompt=None,
|
|
283
|
+
history_messages=None,
|
|
284
|
+
messages=None,
|
|
285
|
+
response_format=None,
|
|
286
|
+
enable_cot=False,
|
|
287
|
+
**_ignored,
|
|
288
|
+
):
|
|
289
|
+
del enable_cot
|
|
290
|
+
|
|
291
|
+
async def request() -> Any:
|
|
292
|
+
async def complete() -> Any:
|
|
293
|
+
provider_kwargs = {}
|
|
294
|
+
if response_format is not None:
|
|
295
|
+
provider_kwargs["response_format"] = response_format
|
|
296
|
+
return await base(
|
|
297
|
+
prompt or "",
|
|
298
|
+
system_prompt=system_prompt,
|
|
299
|
+
history_messages=history_messages or [],
|
|
300
|
+
messages=messages,
|
|
301
|
+
max_retries=0,
|
|
302
|
+
allow_image_fallback=False,
|
|
303
|
+
**provider_kwargs,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
if owner is None:
|
|
307
|
+
return await complete()
|
|
308
|
+
from deeptutor.multi_user.paths import user_context
|
|
309
|
+
|
|
310
|
+
with user_context(owner):
|
|
311
|
+
return await complete()
|
|
312
|
+
|
|
313
|
+
return await _run_adapter_with_retry(request, io_bridge=io_bridge)
|
|
314
|
+
|
|
315
|
+
return llm_model_func
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def build_vision_model_func(
|
|
319
|
+
*,
|
|
320
|
+
io_bridge: OwnerLoopBridge | None = None,
|
|
321
|
+
llm_config: LLMConfig | None = None,
|
|
322
|
+
owner: CurrentUser | None = None,
|
|
323
|
+
):
|
|
324
|
+
"""Map rc2 ``image_inputs`` to DeepTutor's vision callable."""
|
|
325
|
+
if llm_config is None:
|
|
326
|
+
from deeptutor.services.llm import get_llm_client
|
|
327
|
+
|
|
328
|
+
base = get_llm_client().get_vision_model_func()
|
|
329
|
+
else:
|
|
330
|
+
from deeptutor.services.llm.client import build_model_func_for_config
|
|
331
|
+
|
|
332
|
+
base = build_model_func_for_config(llm_config, allow_multimodal=True)
|
|
333
|
+
|
|
334
|
+
async def vision_model_func(
|
|
335
|
+
prompt="",
|
|
336
|
+
system_prompt=None,
|
|
337
|
+
history_messages=None,
|
|
338
|
+
image_inputs=None,
|
|
339
|
+
messages=None,
|
|
340
|
+
response_format=None,
|
|
341
|
+
**_ignored,
|
|
342
|
+
):
|
|
343
|
+
if not isinstance(image_inputs, list) or len(image_inputs) != 1:
|
|
344
|
+
raise ValueError("LightRAG vision requests must contain exactly one image input")
|
|
345
|
+
payload = image_inputs[0]
|
|
346
|
+
if not isinstance(payload, dict):
|
|
347
|
+
raise ValueError("LightRAG vision image input must be an object")
|
|
348
|
+
image_data = payload.get("base64")
|
|
349
|
+
if not isinstance(image_data, str) or not image_data.strip():
|
|
350
|
+
raise ValueError("LightRAG vision image input requires a non-empty base64 value")
|
|
351
|
+
|
|
352
|
+
async def request() -> Any:
|
|
353
|
+
async def complete() -> Any:
|
|
354
|
+
provider_kwargs = {}
|
|
355
|
+
if response_format is not None:
|
|
356
|
+
provider_kwargs["response_format"] = response_format
|
|
357
|
+
return await base(
|
|
358
|
+
prompt or "",
|
|
359
|
+
system_prompt=system_prompt,
|
|
360
|
+
history_messages=history_messages or [],
|
|
361
|
+
image_data=image_data,
|
|
362
|
+
messages=messages,
|
|
363
|
+
max_retries=0,
|
|
364
|
+
allow_image_fallback=False,
|
|
365
|
+
**provider_kwargs,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
if owner is None:
|
|
369
|
+
return await complete()
|
|
370
|
+
from deeptutor.multi_user.paths import user_context
|
|
371
|
+
|
|
372
|
+
with user_context(owner):
|
|
373
|
+
return await complete()
|
|
374
|
+
|
|
375
|
+
return await _run_adapter_with_retry(request, io_bridge=io_bridge)
|
|
376
|
+
|
|
377
|
+
return vision_model_func
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def vision_model_available() -> bool:
|
|
381
|
+
"""Return whether the active DeepTutor model is explicitly vision-capable."""
|
|
382
|
+
try:
|
|
383
|
+
from deeptutor.services.llm import get_llm_client
|
|
384
|
+
|
|
385
|
+
return bool(get_llm_client().supports_multimodal_images())
|
|
386
|
+
except Exception:
|
|
387
|
+
return False
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def build_embedding_func(
|
|
391
|
+
*,
|
|
392
|
+
io_bridge: OwnerLoopBridge | None = None,
|
|
393
|
+
embedding_config: EmbeddingConfig | None = None,
|
|
394
|
+
) -> EmbeddingFunc:
|
|
395
|
+
"""Wrap one captured embedding configuration in LightRAG's adapter."""
|
|
396
|
+
from lightrag.utils import EmbeddingFunc
|
|
397
|
+
|
|
398
|
+
from deeptutor.services.embedding import get_embedding_config
|
|
399
|
+
from deeptutor.services.embedding.client import EmbeddingClient
|
|
400
|
+
|
|
401
|
+
cfg = deepcopy(embedding_config if embedding_config is not None else get_embedding_config())
|
|
402
|
+
dim = int(getattr(cfg, "dim", 0) or 0)
|
|
403
|
+
if not dim:
|
|
404
|
+
raise LightRagNotConfiguredError(
|
|
405
|
+
"No active embedding model with a known dimension. Configure one under "
|
|
406
|
+
"Settings → Catalog before using a LightRAG knowledge base."
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
client = EmbeddingClient(config=cfg)
|
|
410
|
+
|
|
411
|
+
async def embedding_func(texts: list[str], context: str | None = None, **_ignored: Any) -> Any:
|
|
412
|
+
import numpy as np
|
|
413
|
+
|
|
414
|
+
# No context means no role. Defaulting to "document" would label
|
|
415
|
+
# queries as passages.
|
|
416
|
+
input_type = {
|
|
417
|
+
"query": "search_query",
|
|
418
|
+
"document": "search_document",
|
|
419
|
+
}.get(str(context or "").strip().lower())
|
|
420
|
+
|
|
421
|
+
async def request() -> Any:
|
|
422
|
+
return await client.embed(texts, input_type=input_type)
|
|
423
|
+
|
|
424
|
+
vectors = await io_bridge.run(request) if io_bridge is not None else await request()
|
|
425
|
+
return np.asarray(vectors, dtype=np.float32)
|
|
426
|
+
|
|
427
|
+
embedding_kwargs = {
|
|
428
|
+
"embedding_dim": dim,
|
|
429
|
+
"max_token_size": int(getattr(cfg, "max_tokens", 0) or _DEFAULT_MAX_TOKEN_SIZE),
|
|
430
|
+
"func": embedding_func,
|
|
431
|
+
}
|
|
432
|
+
if "supports_asymmetric" in inspect.signature(EmbeddingFunc).parameters:
|
|
433
|
+
embedding_kwargs["supports_asymmetric"] = True
|
|
434
|
+
return EmbeddingFunc(**embedding_kwargs)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
__all__ = [
|
|
438
|
+
"SUPPORTED_MODES",
|
|
439
|
+
"DEFAULT_MODE",
|
|
440
|
+
"LightRagNotAvailableError",
|
|
441
|
+
"LightRagNotConfiguredError",
|
|
442
|
+
"is_lightrag_available",
|
|
443
|
+
"normalize_mode",
|
|
444
|
+
"query_kwargs_from_settings",
|
|
445
|
+
"indexing_kwargs_from_settings",
|
|
446
|
+
"constructor_kwargs_from_settings",
|
|
447
|
+
"lightrag_llm_selection_from_settings",
|
|
448
|
+
"resolve_lightrag_query_llm_config",
|
|
449
|
+
"build_llm_model_func",
|
|
450
|
+
"build_vision_model_func",
|
|
451
|
+
"vision_model_available",
|
|
452
|
+
"build_embedding_func",
|
|
453
|
+
]
|