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,264 @@
|
|
|
1
|
+
"""LlamaIndex embedding adapter backed by DeepTutor's embedding service."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from contextvars import ContextVar
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any, Callable, List
|
|
10
|
+
|
|
11
|
+
from llama_index.core import Settings
|
|
12
|
+
from llama_index.core.base.embeddings.base import BaseEmbedding
|
|
13
|
+
from llama_index.core.bridge.pydantic import PrivateAttr
|
|
14
|
+
|
|
15
|
+
from deeptutor.services.config.provider_runtime import EMBEDDING_PROVIDERS
|
|
16
|
+
from deeptutor.services.embedding import EmbeddingConfig, get_embedding_client, get_embedding_config
|
|
17
|
+
from deeptutor.services.embedding.config import scoped_embedding_config
|
|
18
|
+
from deeptutor.services.embedding.validation import validate_embedding_batch
|
|
19
|
+
|
|
20
|
+
from .config import chunk_geometry
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class _IndexingProgress:
|
|
25
|
+
total_batches: int
|
|
26
|
+
provider_batch_size: int
|
|
27
|
+
completed_batches: int = 0
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# An executor thread inherits the indexing operation's context. Keep both the
|
|
31
|
+
# callback and its cumulative count there: a timed-out worker must never pick
|
|
32
|
+
# up a later task's callback from a shared CustomEmbedding instance (#1478).
|
|
33
|
+
_task_progress_callback: ContextVar[Callable[[int, int], None] | None] = ContextVar(
|
|
34
|
+
"llamaindex_progress_callback", default=None
|
|
35
|
+
)
|
|
36
|
+
_indexing_progress: ContextVar[_IndexingProgress | None] = ContextVar(
|
|
37
|
+
"llamaindex_indexing_progress", default=None
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _config_fingerprint(config: EmbeddingConfig) -> tuple[Any, ...]:
|
|
42
|
+
"""Return the settings fields that affect LlamaIndex embedding behavior."""
|
|
43
|
+
return (
|
|
44
|
+
getattr(config, "binding", None),
|
|
45
|
+
getattr(config, "model", None),
|
|
46
|
+
getattr(config, "dim", None),
|
|
47
|
+
getattr(config, "effective_url", None) or getattr(config, "base_url", None),
|
|
48
|
+
getattr(config, "api_version", None),
|
|
49
|
+
getattr(config, "send_dimensions", None),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class CustomEmbedding(BaseEmbedding):
|
|
54
|
+
"""Custom LlamaIndex embedding adapter for DeepTutor embedding providers."""
|
|
55
|
+
|
|
56
|
+
_client: Any = PrivateAttr()
|
|
57
|
+
_logger: Any = PrivateAttr()
|
|
58
|
+
_progress_callback: Any = PrivateAttr(default=None)
|
|
59
|
+
_binding: Any = PrivateAttr(default=None)
|
|
60
|
+
_model: Any = PrivateAttr(default=None)
|
|
61
|
+
_fingerprint: Any = PrivateAttr(default=None)
|
|
62
|
+
|
|
63
|
+
def __init__(self, **kwargs):
|
|
64
|
+
progress_cb = kwargs.pop("progress_callback", None)
|
|
65
|
+
embedding_config = kwargs.pop("embedding_config", None)
|
|
66
|
+
super().__init__(**kwargs)
|
|
67
|
+
self._logger = logging.getLogger(__name__)
|
|
68
|
+
self._progress_callback = progress_cb
|
|
69
|
+
client = (
|
|
70
|
+
get_embedding_client(embedding_config)
|
|
71
|
+
if embedding_config is not None
|
|
72
|
+
else get_embedding_client()
|
|
73
|
+
)
|
|
74
|
+
self._bind_client(client)
|
|
75
|
+
|
|
76
|
+
def _bind_client(self, client: Any) -> None:
|
|
77
|
+
self._client = client
|
|
78
|
+
client_config = getattr(self._client, "config", None)
|
|
79
|
+
self._binding = getattr(client_config, "binding", None)
|
|
80
|
+
self._model = getattr(client_config, "model", None)
|
|
81
|
+
self._fingerprint = (
|
|
82
|
+
_config_fingerprint(client_config) if client_config is not None else None
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def matches_config(self, config: EmbeddingConfig) -> bool:
|
|
86
|
+
"""Return whether this adapter was created for the active config."""
|
|
87
|
+
return self._fingerprint == _config_fingerprint(config)
|
|
88
|
+
|
|
89
|
+
def refresh_client(self, config: EmbeddingConfig | None = None) -> Any:
|
|
90
|
+
"""Refresh the cached client if settings changed while the pipeline lived."""
|
|
91
|
+
client = get_embedding_client(config) if config is not None else get_embedding_client()
|
|
92
|
+
if client is not self._client:
|
|
93
|
+
self._bind_client(client)
|
|
94
|
+
return self._client
|
|
95
|
+
|
|
96
|
+
def set_progress_callback(self, callback):
|
|
97
|
+
"""Set progress callback fn(batch_num, total_batches)."""
|
|
98
|
+
self._progress_callback = callback
|
|
99
|
+
|
|
100
|
+
def __call__(self, nodes, **kwargs):
|
|
101
|
+
"""Count provider batches across the full split-node set once."""
|
|
102
|
+
callback = _task_progress_callback.get() or self._progress_callback
|
|
103
|
+
if callback is None or not nodes:
|
|
104
|
+
return super().__call__(nodes, **kwargs)
|
|
105
|
+
config = getattr(self._client, "config", None)
|
|
106
|
+
binding = getattr(config, "binding", "")
|
|
107
|
+
provider = EMBEDDING_PROVIDERS.get(binding)
|
|
108
|
+
provider_limit = provider.max_batch_items if provider else 256
|
|
109
|
+
batch_size = max(1, min(getattr(config, "batch_size", 10), provider_limit))
|
|
110
|
+
outer_size = self.embed_batch_size
|
|
111
|
+
total_batches = sum(
|
|
112
|
+
(min(outer_size, len(nodes) - start) + batch_size - 1) // batch_size
|
|
113
|
+
for start in range(0, len(nodes), outer_size)
|
|
114
|
+
)
|
|
115
|
+
token = _indexing_progress.set(
|
|
116
|
+
_IndexingProgress(total_batches=total_batches, provider_batch_size=batch_size)
|
|
117
|
+
)
|
|
118
|
+
try:
|
|
119
|
+
return super().__call__(nodes, **kwargs)
|
|
120
|
+
finally:
|
|
121
|
+
_indexing_progress.reset(token)
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def class_name(cls) -> str:
|
|
125
|
+
return "custom_embedding"
|
|
126
|
+
|
|
127
|
+
def _run_in_new_loop(self, coro):
|
|
128
|
+
"""Run an async coroutine from sync context using a fresh event loop."""
|
|
129
|
+
loop = asyncio.new_event_loop()
|
|
130
|
+
try:
|
|
131
|
+
return loop.run_until_complete(coro)
|
|
132
|
+
finally:
|
|
133
|
+
loop.close()
|
|
134
|
+
|
|
135
|
+
async def _aget_query_embedding(self, query: str) -> List[float]:
|
|
136
|
+
client = self.refresh_client()
|
|
137
|
+
embeddings = await client.embed([query], input_type="search_query")
|
|
138
|
+
return validate_embedding_batch(
|
|
139
|
+
embeddings,
|
|
140
|
+
expected_count=1,
|
|
141
|
+
binding=self._binding,
|
|
142
|
+
model=self._model,
|
|
143
|
+
)[0]
|
|
144
|
+
|
|
145
|
+
async def _aget_text_embedding(self, text: str) -> List[float]:
|
|
146
|
+
client = self.refresh_client()
|
|
147
|
+
embeddings = await client.embed([text], input_type="search_document")
|
|
148
|
+
return validate_embedding_batch(
|
|
149
|
+
embeddings,
|
|
150
|
+
expected_count=1,
|
|
151
|
+
binding=self._binding,
|
|
152
|
+
model=self._model,
|
|
153
|
+
)[0]
|
|
154
|
+
|
|
155
|
+
async def _aget_text_embeddings(self, texts: List[str]) -> List[List[float]]:
|
|
156
|
+
client = self.refresh_client()
|
|
157
|
+
callback = _task_progress_callback.get() or self._progress_callback
|
|
158
|
+
progress = _indexing_progress.get()
|
|
159
|
+
if progress is not None and callback is not None:
|
|
160
|
+
offset = progress.completed_batches
|
|
161
|
+
sink = callback
|
|
162
|
+
|
|
163
|
+
def report(batch_num: int, _total_batches: int) -> None:
|
|
164
|
+
sink(offset + batch_num, progress.total_batches)
|
|
165
|
+
|
|
166
|
+
callback = report
|
|
167
|
+
embeddings = await client.embed(
|
|
168
|
+
texts,
|
|
169
|
+
progress_callback=callback,
|
|
170
|
+
input_type="search_document",
|
|
171
|
+
)
|
|
172
|
+
if progress is not None:
|
|
173
|
+
progress.completed_batches += (
|
|
174
|
+
len(texts) + progress.provider_batch_size - 1
|
|
175
|
+
) // progress.provider_batch_size
|
|
176
|
+
return validate_embedding_batch(
|
|
177
|
+
embeddings,
|
|
178
|
+
expected_count=len(texts),
|
|
179
|
+
binding=self._binding,
|
|
180
|
+
model=self._model,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def _get_query_embedding(self, query: str) -> List[float]:
|
|
184
|
+
return self._run_in_new_loop(self._aget_query_embedding(query))
|
|
185
|
+
|
|
186
|
+
def _get_text_embedding(self, text: str) -> List[float]:
|
|
187
|
+
return self._run_in_new_loop(self._aget_text_embedding(text))
|
|
188
|
+
|
|
189
|
+
def _get_text_embeddings(self, texts: List[str]) -> List[List[float]]:
|
|
190
|
+
self._logger.info(f"Embedding {len(texts)} text chunks...")
|
|
191
|
+
result = self._run_in_new_loop(self._aget_text_embeddings(texts))
|
|
192
|
+
self._logger.info(f"Embedding complete: {len(result)} vectors")
|
|
193
|
+
return result
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
_operation_adapter: ContextVar[CustomEmbedding | None] = ContextVar(
|
|
197
|
+
"llamaindex_embedding", default=None
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def current_embedding():
|
|
202
|
+
if scoped_embedding_config() is not None and _operation_adapter.get() is not None:
|
|
203
|
+
return _operation_adapter.get()
|
|
204
|
+
return Settings.embed_model
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def configure_llamaindex_settings(logger=None) -> None:
|
|
208
|
+
"""Configure LlamaIndex globals for DeepTutor's current embedding config."""
|
|
209
|
+
embedding_cfg = get_embedding_config()
|
|
210
|
+
|
|
211
|
+
current = (
|
|
212
|
+
None if scoped_embedding_config() is not None else getattr(Settings, "_embed_model", None)
|
|
213
|
+
)
|
|
214
|
+
configured = False
|
|
215
|
+
if isinstance(current, CustomEmbedding) and current.matches_config(embedding_cfg):
|
|
216
|
+
current.refresh_client(embedding_cfg)
|
|
217
|
+
else:
|
|
218
|
+
adapter = CustomEmbedding(embedding_config=embedding_cfg)
|
|
219
|
+
if scoped_embedding_config() is not None:
|
|
220
|
+
_operation_adapter.set(adapter)
|
|
221
|
+
else:
|
|
222
|
+
Settings.embed_model = adapter
|
|
223
|
+
configured = True
|
|
224
|
+
chunk_size, chunk_overlap = chunk_geometry()
|
|
225
|
+
Settings.chunk_size = chunk_size
|
|
226
|
+
Settings.chunk_overlap = chunk_overlap
|
|
227
|
+
|
|
228
|
+
if logger is not None:
|
|
229
|
+
message = (
|
|
230
|
+
f"LlamaIndex configured: embedding={embedding_cfg.model} "
|
|
231
|
+
f"({embedding_cfg.dim}D, {embedding_cfg.binding}), chunk_size={chunk_size}"
|
|
232
|
+
)
|
|
233
|
+
if configured:
|
|
234
|
+
logger.info(message)
|
|
235
|
+
else:
|
|
236
|
+
logger.debug(message)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def set_progress_callback(callback) -> None:
|
|
240
|
+
"""Bind a callback to this indexing context, not a shared adapter slot."""
|
|
241
|
+
_task_progress_callback.set(callback)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
async def verify_embedding_connectivity(logger=None) -> None:
|
|
245
|
+
"""Quick smoke-test to catch embedding config/network issues before indexing."""
|
|
246
|
+
if logger is not None:
|
|
247
|
+
logger.info("Verifying embedding API connectivity...")
|
|
248
|
+
try:
|
|
249
|
+
client = get_embedding_client()
|
|
250
|
+
result = await client.embed(["connectivity test"])
|
|
251
|
+
validated = validate_embedding_batch(
|
|
252
|
+
result,
|
|
253
|
+
expected_count=1,
|
|
254
|
+
binding=getattr(client.config, "binding", None),
|
|
255
|
+
model=getattr(client.config, "model", None),
|
|
256
|
+
)
|
|
257
|
+
if logger is not None:
|
|
258
|
+
logger.info(f"Embedding API OK (returned {len(validated[0])}-dim vector)")
|
|
259
|
+
except Exception as exc:
|
|
260
|
+
if logger is not None:
|
|
261
|
+
logger.error(f"Embedding API connectivity check failed: {exc}")
|
|
262
|
+
raise RuntimeError(
|
|
263
|
+
f"Cannot reach embedding API. Please check your embedding configuration. Error: {exc}"
|
|
264
|
+
) from exc
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Error normalization for LlamaIndex-backed RAG retrieval."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _invalid_index_reason(message: str) -> str:
|
|
9
|
+
"""Extract the persisted-store failure reason recorded by the loader."""
|
|
10
|
+
_, _, detail = message.partition("Details: ")
|
|
11
|
+
if not detail:
|
|
12
|
+
return ""
|
|
13
|
+
return detail.split(". ", 1)[0].rstrip(".")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def search_error_result(query: str, exc: Exception) -> Dict[str, Any]:
|
|
17
|
+
"""Convert retrieval failures into actionable tool output."""
|
|
18
|
+
message = str(exc)
|
|
19
|
+
lower = message.lower()
|
|
20
|
+
|
|
21
|
+
null_vector_similarity_error = (
|
|
22
|
+
"unsupported operand type(s) for *" in lower and "nonetype" in lower and "float" in lower
|
|
23
|
+
)
|
|
24
|
+
shape_vector_error = "inhomogeneous shape" in lower or (
|
|
25
|
+
"shapes" in lower and "not aligned" in lower
|
|
26
|
+
)
|
|
27
|
+
invalid_persisted_index = "rag index contains invalid embedding vectors" in lower
|
|
28
|
+
if invalid_persisted_index or null_vector_similarity_error or shape_vector_error:
|
|
29
|
+
# Classify the persisted-index diagnosis before the provider-response
|
|
30
|
+
# branch so its "Details:" tail about stored vectors is never treated
|
|
31
|
+
# as a live provider failure (issue #440).
|
|
32
|
+
reason = _invalid_index_reason(message)
|
|
33
|
+
reason_clause = f" ({reason})" if reason else ""
|
|
34
|
+
return {
|
|
35
|
+
"query": query,
|
|
36
|
+
"answer": (
|
|
37
|
+
"RAG search failed because this knowledge base index contains "
|
|
38
|
+
f"invalid embedding vectors{reason_clause}. Re-index the "
|
|
39
|
+
"knowledge base with the current embedding provider/model "
|
|
40
|
+
"before querying it again: open the knowledge base page and "
|
|
41
|
+
"run its 'Re-index' action (Index versions → Re-index). "
|
|
42
|
+
"Re-uploading documents reuses the damaged index and cannot "
|
|
43
|
+
"repair it."
|
|
44
|
+
),
|
|
45
|
+
"content": "",
|
|
46
|
+
"provider": "llamaindex",
|
|
47
|
+
"error": message,
|
|
48
|
+
"error_type": "invalid_embedding_index",
|
|
49
|
+
"log_message": "RAG index contains invalid embedding vectors; re-index required.",
|
|
50
|
+
"needs_reindex": True,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if "embedding provider returned invalid" in lower:
|
|
54
|
+
return {
|
|
55
|
+
"query": query,
|
|
56
|
+
"answer": (
|
|
57
|
+
"RAG search failed because the embedding provider returned an "
|
|
58
|
+
f"invalid query vector: {message}"
|
|
59
|
+
),
|
|
60
|
+
"content": "",
|
|
61
|
+
"provider": "llamaindex",
|
|
62
|
+
"error": message,
|
|
63
|
+
"error_type": "invalid_embedding_provider_response",
|
|
64
|
+
"log_message": (
|
|
65
|
+
"Embedding provider returned an invalid query vector; check "
|
|
66
|
+
"the embedding provider/model configuration."
|
|
67
|
+
),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
"query": query,
|
|
72
|
+
"answer": f"Search failed: {message}",
|
|
73
|
+
"content": "",
|
|
74
|
+
"provider": "llamaindex",
|
|
75
|
+
"error": message,
|
|
76
|
+
}
|