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.
Files changed (168) hide show
  1. deeptutor/api/routers/knowledge.py +5225 -0
  2. deeptutor/api/routers/marginnote4.py +278 -0
  3. deeptutor/capabilities/ima/__init__.py +6 -0
  4. deeptutor/capabilities/ima/binding.py +118 -0
  5. deeptutor/capabilities/ima/capability.py +92 -0
  6. deeptutor/capabilities/ima/prompts/en/system.md +33 -0
  7. deeptutor/capabilities/ima/prompts/zh/system.md +24 -0
  8. deeptutor/capabilities/ima/tools.py +476 -0
  9. deeptutor/capabilities/marginnote4/__init__.py +13 -0
  10. deeptutor/capabilities/marginnote4/binding.py +66 -0
  11. deeptutor/capabilities/marginnote4/capability.py +80 -0
  12. deeptutor/capabilities/marginnote4/models.py +173 -0
  13. deeptutor/capabilities/marginnote4/prompts/en/system.md +54 -0
  14. deeptutor/capabilities/marginnote4/prompts/zh/system.md +44 -0
  15. deeptutor/capabilities/marginnote4/store.py +562 -0
  16. deeptutor/capabilities/marginnote4/tools.py +329 -0
  17. deeptutor/capabilities/obsidian/__init__.py +6 -0
  18. deeptutor/capabilities/obsidian/binding.py +75 -0
  19. deeptutor/capabilities/obsidian/capability.py +82 -0
  20. deeptutor/capabilities/obsidian/prompts/en/system.md +32 -0
  21. deeptutor/capabilities/obsidian/prompts/zh/system.md +28 -0
  22. deeptutor/capabilities/obsidian/tools.py +399 -0
  23. deeptutor/capabilities/obsidian/vault.py +341 -0
  24. deeptutor/knowledge/__init__.py +28 -0
  25. deeptutor/knowledge/add_documents.py +734 -0
  26. deeptutor/knowledge/indexing_run.py +432 -0
  27. deeptutor/knowledge/initializer.py +448 -0
  28. deeptutor/knowledge/manager.py +2570 -0
  29. deeptutor/knowledge/module_service.py +29 -0
  30. deeptutor/knowledge/naming.py +40 -0
  31. deeptutor/knowledge/progress_events.py +45 -0
  32. deeptutor/knowledge/progress_tracker.py +355 -0
  33. deeptutor/knowledge/staging.py +69 -0
  34. deeptutor/services/config/knowledge_base_config.py +239 -0
  35. deeptutor/services/embedding/__init__.py +46 -0
  36. deeptutor/services/embedding/adapters/__init__.py +40 -0
  37. deeptutor/services/embedding/adapters/base.py +196 -0
  38. deeptutor/services/embedding/adapters/cohere.py +176 -0
  39. deeptutor/services/embedding/adapters/dashscope_native.py +242 -0
  40. deeptutor/services/embedding/adapters/gemini.py +462 -0
  41. deeptutor/services/embedding/adapters/jina.py +161 -0
  42. deeptutor/services/embedding/adapters/ollama.py +145 -0
  43. deeptutor/services/embedding/adapters/openai_compatible.py +406 -0
  44. deeptutor/services/embedding/adapters/openai_sdk.py +150 -0
  45. deeptutor/services/embedding/client.py +323 -0
  46. deeptutor/services/embedding/config.py +114 -0
  47. deeptutor/services/embedding/request_options.py +46 -0
  48. deeptutor/services/embedding/validation.py +128 -0
  49. deeptutor/services/github_source/__init__.py +5 -0
  50. deeptutor/services/github_source/client.py +164 -0
  51. deeptutor/services/github_source/sync.py +384 -0
  52. deeptutor/services/github_source/sync_service.py +72 -0
  53. deeptutor/services/rag/__init__.py +21 -0
  54. deeptutor/services/rag/embedding_binding.py +331 -0
  55. deeptutor/services/rag/embedding_signature.py +63 -0
  56. deeptutor/services/rag/eval/__init__.py +92 -0
  57. deeptutor/services/rag/eval/dataset.py +244 -0
  58. deeptutor/services/rag/eval/diff.py +312 -0
  59. deeptutor/services/rag/eval/matching.py +218 -0
  60. deeptutor/services/rag/eval/metrics.py +213 -0
  61. deeptutor/services/rag/eval/report.py +111 -0
  62. deeptutor/services/rag/eval/runner.py +309 -0
  63. deeptutor/services/rag/factory.py +369 -0
  64. deeptutor/services/rag/file_routing.py +5 -0
  65. deeptutor/services/rag/index_probe.py +327 -0
  66. deeptutor/services/rag/index_versioning.py +387 -0
  67. deeptutor/services/rag/kb_paths.py +50 -0
  68. deeptutor/services/rag/linked_kb.py +237 -0
  69. deeptutor/services/rag/pipelines/__init__.py +7 -0
  70. deeptutor/services/rag/pipelines/base.py +41 -0
  71. deeptutor/services/rag/pipelines/graphrag/__init__.py +18 -0
  72. deeptutor/services/rag/pipelines/graphrag/compatibility.py +42 -0
  73. deeptutor/services/rag/pipelines/graphrag/completion_adapter.py +251 -0
  74. deeptutor/services/rag/pipelines/graphrag/config.py +357 -0
  75. deeptutor/services/rag/pipelines/graphrag/engine.py +479 -0
  76. deeptutor/services/rag/pipelines/graphrag/errors.py +328 -0
  77. deeptutor/services/rag/pipelines/graphrag/ingestion.py +135 -0
  78. deeptutor/services/rag/pipelines/graphrag/pandas_compat.py +68 -0
  79. deeptutor/services/rag/pipelines/graphrag/pipeline.py +284 -0
  80. deeptutor/services/rag/pipelines/graphrag/provider.py +134 -0
  81. deeptutor/services/rag/pipelines/graphrag/storage.py +127 -0
  82. deeptutor/services/rag/pipelines/ima/__init__.py +28 -0
  83. deeptutor/services/rag/pipelines/ima/client.py +364 -0
  84. deeptutor/services/rag/pipelines/ima/config.py +140 -0
  85. deeptutor/services/rag/pipelines/ima/envelope.py +109 -0
  86. deeptutor/services/rag/pipelines/ima/inventory.py +209 -0
  87. deeptutor/services/rag/pipelines/ima/media.py +190 -0
  88. deeptutor/services/rag/pipelines/ima/models.py +348 -0
  89. deeptutor/services/rag/pipelines/ima/notes.py +240 -0
  90. deeptutor/services/rag/pipelines/ima/pipeline.py +238 -0
  91. deeptutor/services/rag/pipelines/ima/probe.py +105 -0
  92. deeptutor/services/rag/pipelines/ima/sources.py +105 -0
  93. deeptutor/services/rag/pipelines/ima/transport.py +130 -0
  94. deeptutor/services/rag/pipelines/kiwix/__init__.py +1 -0
  95. deeptutor/services/rag/pipelines/kiwix/client.py +369 -0
  96. deeptutor/services/rag/pipelines/kiwix/pipeline.py +75 -0
  97. deeptutor/services/rag/pipelines/lightrag/README.md +78 -0
  98. deeptutor/services/rag/pipelines/lightrag/__init__.py +15 -0
  99. deeptutor/services/rag/pipelines/lightrag/block_policy.py +266 -0
  100. deeptutor/services/rag/pipelines/lightrag/cache_reuse.py +137 -0
  101. deeptutor/services/rag/pipelines/lightrag/config.py +453 -0
  102. deeptutor/services/rag/pipelines/lightrag/engine.py +536 -0
  103. deeptutor/services/rag/pipelines/lightrag/indexing_policy.py +696 -0
  104. deeptutor/services/rag/pipelines/lightrag/ingress.py +364 -0
  105. deeptutor/services/rag/pipelines/lightrag/parser.py +99 -0
  106. deeptutor/services/rag/pipelines/lightrag/pipeline.py +692 -0
  107. deeptutor/services/rag/pipelines/lightrag/roles.py +132 -0
  108. deeptutor/services/rag/pipelines/lightrag/sidecar.py +320 -0
  109. deeptutor/services/rag/pipelines/lightrag/storage.py +338 -0
  110. deeptutor/services/rag/pipelines/lightrag/worker.py +259 -0
  111. deeptutor/services/rag/pipelines/lightrag/write_lock.py +55 -0
  112. deeptutor/services/rag/pipelines/lightrag_server/__init__.py +15 -0
  113. deeptutor/services/rag/pipelines/lightrag_server/client.py +149 -0
  114. deeptutor/services/rag/pipelines/lightrag_server/config.py +68 -0
  115. deeptutor/services/rag/pipelines/lightrag_server/pipeline.py +120 -0
  116. deeptutor/services/rag/pipelines/lightrag_server/probe.py +110 -0
  117. deeptutor/services/rag/pipelines/llamaindex/__init__.py +3 -0
  118. deeptutor/services/rag/pipelines/llamaindex/config.py +199 -0
  119. deeptutor/services/rag/pipelines/llamaindex/document_loader.py +648 -0
  120. deeptutor/services/rag/pipelines/llamaindex/embedding_adapter.py +264 -0
  121. deeptutor/services/rag/pipelines/llamaindex/errors.py +76 -0
  122. deeptutor/services/rag/pipelines/llamaindex/exercise_lookup.py +373 -0
  123. deeptutor/services/rag/pipelines/llamaindex/ingestion.py +129 -0
  124. deeptutor/services/rag/pipelines/llamaindex/pipeline.py +556 -0
  125. deeptutor/services/rag/pipelines/llamaindex/rerank.py +130 -0
  126. deeptutor/services/rag/pipelines/llamaindex/retrievers.py +360 -0
  127. deeptutor/services/rag/pipelines/llamaindex/storage.py +655 -0
  128. deeptutor/services/rag/pipelines/llamaindex/vector_store.py +288 -0
  129. deeptutor/services/rag/pipelines/modes.py +48 -0
  130. deeptutor/services/rag/pipelines/pageindex/__init__.py +47 -0
  131. deeptutor/services/rag/pipelines/pageindex/client.py +146 -0
  132. deeptutor/services/rag/pipelines/pageindex/config.py +49 -0
  133. deeptutor/services/rag/pipelines/pageindex/pipeline.py +313 -0
  134. deeptutor/services/rag/pipelines/pageindex/reasoning.py +262 -0
  135. deeptutor/services/rag/pipelines/pageindex/storage.py +133 -0
  136. deeptutor/services/rag/pipelines/pageindex/tools.py +221 -0
  137. deeptutor/services/rag/pipelines/weknora/__init__.py +13 -0
  138. deeptutor/services/rag/pipelines/weknora/client.py +106 -0
  139. deeptutor/services/rag/pipelines/weknora/config.py +56 -0
  140. deeptutor/services/rag/pipelines/weknora/pipeline.py +74 -0
  141. deeptutor/services/rag/pipelines/weknora/probe.py +80 -0
  142. deeptutor/services/rag/preflight.py +274 -0
  143. deeptutor/services/rag/provider_binding.py +70 -0
  144. deeptutor/services/rag/query_rewrite.py +186 -0
  145. deeptutor/services/rag/service.py +379 -0
  146. deeptutor/services/rag/smart_retriever.py +81 -0
  147. deeptutor/services/rag/source_pages.py +292 -0
  148. deeptutor/services/rag/source_visuals.py +434 -0
  149. deeptutor/services/rag/visual_assets.py +430 -0
  150. deeptutor/services/rag/visual_coverage.py +165 -0
  151. deeptutor/services/web_source/__init__.py +1 -0
  152. deeptutor/services/web_source/bilingual.py +255 -0
  153. deeptutor/services/web_source/crawler.py +681 -0
  154. deeptutor/services/web_source/html_extractor.py +643 -0
  155. deeptutor/services/web_source/markdown.py +28 -0
  156. deeptutor/services/web_source/navigation.py +72 -0
  157. deeptutor/services/web_source/repository.py +640 -0
  158. deeptutor/services/web_source/robots.py +182 -0
  159. deeptutor/services/web_source/scheduler.py +336 -0
  160. deeptutor/services/web_source/snapshot_assets.py +171 -0
  161. deeptutor/services/web_source/sync.py +253 -0
  162. deeptutor/services/workspace/kb_move.py +446 -0
  163. deeptutor/tools/rag_tool.py +110 -0
  164. deeptutor_cli/kb.py +756 -0
  165. deeptutor_knowledge-1.7.0.dist-info/METADATA +15 -0
  166. deeptutor_knowledge-1.7.0.dist-info/RECORD +168 -0
  167. deeptutor_knowledge-1.7.0.dist-info/WHEEL +5 -0
  168. 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
+ }