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,328 @@
1
+ """Typed, secret-free GraphRAG pipeline errors for API and task reporting."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import ClassVar
6
+
7
+ MODEL_INCOMPATIBLE_MESSAGE = (
8
+ "The model did not accept or return the structured output required by GraphRAG."
9
+ )
10
+ MODEL_AUTHENTICATION_MESSAGE = (
11
+ "GraphRAG compatibility could not be verified because the model credentials were rejected."
12
+ )
13
+ MODEL_RATE_LIMIT_MESSAGE = (
14
+ "GraphRAG compatibility could not be verified because the model provider is rate limiting "
15
+ "requests. Try again later."
16
+ )
17
+ MODEL_CONNECTION_MESSAGE = (
18
+ "GraphRAG compatibility could not be verified because the model provider could not be "
19
+ "reached. Try again later."
20
+ )
21
+ MODEL_OUTPUT_TRUNCATED_MESSAGE = (
22
+ "GraphRAG compatibility could not be verified because the model response reached its "
23
+ "output token limit. Try again."
24
+ )
25
+ EMBEDDING_AUTHENTICATION_MESSAGE = (
26
+ "GraphRAG could not use the active embedding model because its credentials were rejected."
27
+ )
28
+ EMBEDDING_RATE_LIMIT_MESSAGE = (
29
+ "GraphRAG could not use the active embedding model because the provider is rate limiting "
30
+ "requests. Try again later."
31
+ )
32
+ EMBEDDING_CONNECTION_MESSAGE = (
33
+ "GraphRAG could not reach the active embedding provider. Try again later."
34
+ )
35
+ EMBEDDING_ENDPOINT_MESSAGE = (
36
+ "The configured GraphRAG embedding model or endpoint was not found. Check the embedding "
37
+ "provider URL and model."
38
+ )
39
+ EMBEDDING_RESPONSE_MESSAGE = (
40
+ "The active embedding model did not accept or return the vector response required by GraphRAG."
41
+ )
42
+ EMBEDDING_PROVIDER_UNSUPPORTED_MESSAGE = (
43
+ "GraphRAG currently requires an OpenAI-compatible embedding endpoint. The active embedding "
44
+ "provider uses a native transport; choose its OpenAI-compatible endpoint or another "
45
+ "embedding profile."
46
+ )
47
+
48
+
49
+ class GraphRagPipelineError(RuntimeError):
50
+ """Base error carrying stable metadata for GraphRAG pipeline failures."""
51
+
52
+ code: ClassVar[str] = "graphrag_failed"
53
+ retryable: ClassVar[bool] = False
54
+
55
+
56
+ class GraphRagModelError(GraphRagPipelineError):
57
+ """Base error carrying stable metadata for GraphRAG model failures."""
58
+
59
+ code: ClassVar[str] = "graphrag_model_failed"
60
+ retryable: ClassVar[bool] = False
61
+
62
+
63
+ class GraphRagModelIncompatibleError(GraphRagModelError):
64
+ """Raised when a model cannot satisfy GraphRAG's structured-output contract."""
65
+
66
+ code = "graphrag_model_incompatible"
67
+
68
+
69
+ class GraphRagStructuredOutputError(GraphRagModelIncompatibleError, ValueError):
70
+ """Raised when both native and fallback output fail strict schema validation."""
71
+
72
+
73
+ class GraphRagStructuredOutputTruncatedError(GraphRagModelError, ValueError):
74
+ """Raised when strict validation fails because the provider truncated its response."""
75
+
76
+ code = "graphrag_model_output_truncated"
77
+ retryable = True
78
+
79
+ def __init__(self, _provider_detail: str | None = None) -> None:
80
+ """Discard provider response details and retain only the safe public message."""
81
+ super().__init__(MODEL_OUTPUT_TRUNCATED_MESSAGE)
82
+
83
+
84
+ class GraphRagUnsupportedProviderError(GraphRagModelIncompatibleError):
85
+ """Raised when GraphRAG cannot use a provider's authentication transport."""
86
+
87
+ code = "graphrag_provider_unsupported"
88
+
89
+
90
+ class GraphRagModelAuthenticationError(GraphRagModelError):
91
+ """Raised when the provider rejects the configured credentials."""
92
+
93
+ code = "graphrag_model_authentication_failed"
94
+
95
+
96
+ class GraphRagModelRateLimitError(GraphRagModelError):
97
+ """Raised when compatibility cannot be checked because of provider throttling."""
98
+
99
+ code = "graphrag_model_rate_limited"
100
+ retryable = True
101
+
102
+
103
+ class GraphRagModelConnectionError(GraphRagModelError):
104
+ """Raised when a transient provider or network failure prevents a model call."""
105
+
106
+ code = "graphrag_model_connection_failed"
107
+ retryable = True
108
+
109
+
110
+ class GraphRagModelEndpointError(GraphRagModelError):
111
+ """Raised when the configured endpoint or model cannot be found."""
112
+
113
+ code = "graphrag_model_endpoint_failed"
114
+
115
+
116
+ class GraphRagEmbeddingError(GraphRagPipelineError):
117
+ """Base error for GraphRAG embedding transport and response failures."""
118
+
119
+ code = "graphrag_embedding_failed"
120
+
121
+
122
+ class GraphRagEmbeddingProviderUnsupportedError(GraphRagEmbeddingError):
123
+ """Raised before indexing when the active embedding transport is not supported."""
124
+
125
+ code = "graphrag_embedding_provider_unsupported"
126
+
127
+ def __init__(self) -> None:
128
+ super().__init__(EMBEDDING_PROVIDER_UNSUPPORTED_MESSAGE)
129
+
130
+
131
+ class GraphRagEmbeddingAuthenticationError(GraphRagEmbeddingError):
132
+ """Raised when GraphRAG embedding credentials are rejected."""
133
+
134
+ code = "graphrag_embedding_authentication_failed"
135
+
136
+
137
+ class GraphRagEmbeddingRateLimitError(GraphRagEmbeddingError):
138
+ """Raised when the embedding provider throttles the preflight or indexing call."""
139
+
140
+ code = "graphrag_embedding_rate_limited"
141
+ retryable = True
142
+
143
+
144
+ class GraphRagEmbeddingConnectionError(GraphRagEmbeddingError):
145
+ """Raised when the GraphRAG embedding provider cannot be reached."""
146
+
147
+ code = "graphrag_embedding_connection_failed"
148
+ retryable = True
149
+
150
+
151
+ class GraphRagEmbeddingEndpointError(GraphRagEmbeddingError):
152
+ """Raised when the GraphRAG embedding model or endpoint returns not found."""
153
+
154
+ code = "graphrag_embedding_endpoint_failed"
155
+
156
+
157
+ class GraphRagEmbeddingResponseError(GraphRagEmbeddingError, ValueError):
158
+ """Raised when the embedding provider returns an unusable vector response."""
159
+
160
+ code = "graphrag_embedding_incompatible"
161
+
162
+
163
+ class GraphRagEmbeddingProbeError(GraphRagEmbeddingError):
164
+ """Raised when an unexpected internal failure prevents embedding validation."""
165
+
166
+ code = "graphrag_embedding_probe_failed"
167
+
168
+ def __init__(self) -> None:
169
+ super().__init__(
170
+ "GraphRAG embedding compatibility could not be verified because of an internal error."
171
+ )
172
+
173
+
174
+ class GraphRagEmbeddingDimensionError(GraphRagEmbeddingError, ValueError):
175
+ """Raised when the configured dimension differs from the provider response."""
176
+
177
+ code = "graphrag_embedding_dimension_mismatch"
178
+
179
+ def __init__(self, *, configured: int, actual: int) -> None:
180
+ super().__init__(
181
+ "The active embedding model returned "
182
+ f"{actual} dimensions, but DeepTutor is configured for {configured}. "
183
+ "Correct the embedding dimension before indexing with GraphRAG."
184
+ )
185
+
186
+
187
+ _FORMAT_MARKERS = (
188
+ "response_format",
189
+ "response format",
190
+ "json_schema",
191
+ "json schema",
192
+ "structured output",
193
+ )
194
+ _UNSUPPORTED_MARKERS = (
195
+ "unavailable",
196
+ "unsupported",
197
+ "not supported",
198
+ "does not support",
199
+ "not available",
200
+ "not valid",
201
+ "must be",
202
+ "rejected",
203
+ )
204
+
205
+
206
+ def is_unsupported_schema_error(error: BaseException) -> bool:
207
+ """Return whether a provider explicitly rejected structured-output format support."""
208
+ if type(error).__name__ not in {
209
+ "BadRequestError",
210
+ "InvalidRequestError",
211
+ "UnsupportedParamsError",
212
+ "UnprocessableEntityError",
213
+ }:
214
+ return False
215
+ message = str(error).lower()
216
+ return any(marker in message for marker in _FORMAT_MARKERS) and any(
217
+ marker in message for marker in _UNSUPPORTED_MARKERS
218
+ )
219
+
220
+
221
+ def _status_code(error: BaseException) -> int | None:
222
+ """Return a provider HTTP status without inspecting or exposing its body."""
223
+ value = getattr(error, "status_code", None)
224
+ if value is None:
225
+ value = getattr(error, "status", None)
226
+ return value if isinstance(value, int) else None
227
+
228
+
229
+ def classify_embedding_error(error: BaseException) -> GraphRagPipelineError | None:
230
+ """Map embedding failures to stable, secret-free GraphRAG error metadata."""
231
+ if isinstance(error, GraphRagPipelineError):
232
+ return error
233
+
234
+ error_name = type(error).__name__
235
+ status_code = _status_code(error)
236
+ if error_name in {"AuthenticationError", "PermissionDeniedError"} or status_code in {
237
+ 401,
238
+ 403,
239
+ }:
240
+ return GraphRagEmbeddingAuthenticationError(EMBEDDING_AUTHENTICATION_MESSAGE)
241
+ if error_name == "RateLimitError" or status_code == 429:
242
+ return GraphRagEmbeddingRateLimitError(EMBEDDING_RATE_LIMIT_MESSAGE)
243
+ if error_name in {"NotFoundError"} or status_code == 404:
244
+ return GraphRagEmbeddingEndpointError(EMBEDDING_ENDPOINT_MESSAGE)
245
+ if error_name in {
246
+ "APIConnectionError",
247
+ "ConnectError",
248
+ "ConnectTimeout",
249
+ "ReadTimeout",
250
+ "ServiceUnavailableError",
251
+ "Timeout",
252
+ "TimeoutError",
253
+ } or (status_code is not None and status_code >= 500):
254
+ return GraphRagEmbeddingConnectionError(EMBEDDING_CONNECTION_MESSAGE)
255
+ if error_name in {
256
+ "BadRequestError",
257
+ "InvalidRequestError",
258
+ "UnprocessableEntityError",
259
+ "UnsupportedParamsError",
260
+ } or status_code in {400, 409, 422}:
261
+ return GraphRagEmbeddingResponseError(EMBEDDING_RESPONSE_MESSAGE)
262
+ return None
263
+
264
+
265
+ def classify_model_error(error: BaseException) -> GraphRagPipelineError | None:
266
+ """Map known provider failures to stable GraphRAG errors without exposing details."""
267
+ if isinstance(error, GraphRagPipelineError):
268
+ return error
269
+ if is_unsupported_schema_error(error):
270
+ return GraphRagModelIncompatibleError(MODEL_INCOMPATIBLE_MESSAGE)
271
+
272
+ error_name = type(error).__name__
273
+ if error_name in {"AuthenticationError", "PermissionDeniedError"}:
274
+ return GraphRagModelAuthenticationError(MODEL_AUTHENTICATION_MESSAGE)
275
+ if error_name == "RateLimitError":
276
+ return GraphRagModelRateLimitError(MODEL_RATE_LIMIT_MESSAGE)
277
+ if error_name in {
278
+ "APIConnectionError",
279
+ "ServiceUnavailableError",
280
+ "Timeout",
281
+ "TimeoutError",
282
+ }:
283
+ return GraphRagModelConnectionError(MODEL_CONNECTION_MESSAGE)
284
+ if error_name in {"NotFoundError"} or getattr(error, "status_code", None) == 404:
285
+ return GraphRagModelEndpointError(
286
+ "The configured GraphRAG model or endpoint was not found. Check its provider URL."
287
+ )
288
+ status_code = _status_code(error)
289
+ if error_name == "APIError" and isinstance(status_code, int) and status_code >= 500:
290
+ return GraphRagModelConnectionError(MODEL_CONNECTION_MESSAGE)
291
+ return None
292
+
293
+
294
+ __all__ = [
295
+ "EMBEDDING_AUTHENTICATION_MESSAGE",
296
+ "EMBEDDING_CONNECTION_MESSAGE",
297
+ "EMBEDDING_ENDPOINT_MESSAGE",
298
+ "EMBEDDING_PROVIDER_UNSUPPORTED_MESSAGE",
299
+ "EMBEDDING_RATE_LIMIT_MESSAGE",
300
+ "EMBEDDING_RESPONSE_MESSAGE",
301
+ "GraphRagEmbeddingAuthenticationError",
302
+ "GraphRagEmbeddingConnectionError",
303
+ "GraphRagEmbeddingDimensionError",
304
+ "GraphRagEmbeddingEndpointError",
305
+ "GraphRagEmbeddingError",
306
+ "GraphRagEmbeddingProviderUnsupportedError",
307
+ "GraphRagEmbeddingProbeError",
308
+ "GraphRagEmbeddingRateLimitError",
309
+ "GraphRagEmbeddingResponseError",
310
+ "GraphRagModelAuthenticationError",
311
+ "GraphRagModelConnectionError",
312
+ "GraphRagModelEndpointError",
313
+ "GraphRagModelError",
314
+ "GraphRagModelIncompatibleError",
315
+ "GraphRagModelRateLimitError",
316
+ "GraphRagPipelineError",
317
+ "GraphRagStructuredOutputError",
318
+ "GraphRagStructuredOutputTruncatedError",
319
+ "GraphRagUnsupportedProviderError",
320
+ "MODEL_AUTHENTICATION_MESSAGE",
321
+ "MODEL_CONNECTION_MESSAGE",
322
+ "MODEL_INCOMPATIBLE_MESSAGE",
323
+ "MODEL_OUTPUT_TRUNCATED_MESSAGE",
324
+ "MODEL_RATE_LIMIT_MESSAGE",
325
+ "classify_embedding_error",
326
+ "classify_model_error",
327
+ "is_unsupported_schema_error",
328
+ ]
@@ -0,0 +1,135 @@
1
+ """Turn raw KB files into plain-text input for GraphRAG.
2
+
3
+ GraphRAG's graph engine is text-only — it has no document parser of its own
4
+ (its optional ``markitdown`` reader is just a converter). So DeepTutor owns the
5
+ "document → text" step and hands GraphRAG ready ``.txt`` files. We deliberately
6
+ reuse DeepTutor's existing extraction primitives here so multimodal/parsing
7
+ stays a DeepTutor-side concern (matching how the LlamaIndex pipeline already
8
+ handles documents) rather than pulling a second parser into the tree.
9
+
10
+ This is intentionally the one swappable seam: the rest of the GraphRAG pipeline
11
+ consumes ``input/*.txt`` regardless of which parser produced the text.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import logging
17
+ from pathlib import Path
18
+ from typing import Iterable
19
+
20
+ from deeptutor.utils.file_types import FileTypeRouter
21
+
22
+ from . import storage
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ def _unique_txt_path(target_dir: Path, source: Path, used: set[str]) -> Path:
28
+ """Pick a collision-free ``<stem>.txt`` name inside ``target_dir``."""
29
+ stem = source.stem or "document"
30
+ candidate = f"{stem}.txt"
31
+ suffix = 1
32
+ while candidate in used:
33
+ candidate = f"{stem}_{suffix}.txt"
34
+ suffix += 1
35
+ used.add(candidate)
36
+ return target_dir / candidate
37
+
38
+
39
+ def _extract_parser_text(path: Path, parse_service=None) -> str: # noqa: ANN001
40
+ from deeptutor.services.parsing import ParserError, get_parse_service
41
+
42
+ try:
43
+ parsed = (parse_service or get_parse_service()).parse(path)
44
+ except ParserError as exc:
45
+ logger.error("GraphRAG ingestion: failed to parse %s: %s", path.name, exc)
46
+ return ""
47
+ text = parsed.markdown.strip()
48
+ if text:
49
+ return text
50
+ if parsed.blocks:
51
+ parts = [
52
+ str(block.get("text") or block.get("content") or "").strip()
53
+ for block in parsed.blocks
54
+ if isinstance(block, dict)
55
+ ]
56
+ return "\n\n".join(part for part in parts if part)
57
+ return ""
58
+
59
+
60
+ async def prepare_input(
61
+ file_paths: Iterable[str],
62
+ root_dir: Path,
63
+ *,
64
+ source_for_input: dict[str, str] | None = None,
65
+ ) -> int:
66
+ """Write parsed text for each supported file into ``root_dir/input``.
67
+
68
+ Returns the number of non-empty text documents written. Parser-backed files
69
+ go through the shared document-parse bridge, so the active settings engine
70
+ (text-only, MinerU, Docling, markitdown) owns conversion before GraphRAG
71
+ sees text.
72
+ Images go through the active parser when it supports their suffix (for
73
+ example MinerU); otherwise they are skipped. Unsupported files are logged
74
+ and ignored.
75
+ """
76
+ target_dir = storage.input_dir(root_dir)
77
+ target_dir.mkdir(parents=True, exist_ok=True)
78
+
79
+ classification = FileTypeRouter.classify_files([str(p) for p in file_paths])
80
+ used: set[str] = {p.name for p in target_dir.glob("*.txt")}
81
+ written = 0
82
+
83
+ for file_path_str in classification.parser_files:
84
+ path = Path(file_path_str)
85
+ text = _extract_parser_text(path)
86
+ written += _write_doc(target_dir, path, text, used, source_for_input)
87
+
88
+ for file_path_str in classification.text_files:
89
+ path = Path(file_path_str)
90
+ try:
91
+ text = await FileTypeRouter.read_text_file(str(path))
92
+ except Exception as exc: # pragma: no cover - defensive
93
+ logger.error("GraphRAG ingestion: failed to read text %s: %s", path.name, exc)
94
+ text = ""
95
+ written += _write_doc(target_dir, path, text, used, source_for_input)
96
+
97
+ for file_path_str in classification.image_files:
98
+ from deeptutor.services.parsing import get_parse_service
99
+
100
+ path = Path(file_path_str)
101
+ parse_service = get_parse_service()
102
+ supports = getattr(parse_service, "supports", lambda _path: False)
103
+ if supports(path):
104
+ text = _extract_parser_text(path, parse_service)
105
+ written += _write_doc(target_dir, path, text, used, source_for_input)
106
+ else:
107
+ logger.warning(
108
+ "GraphRAG ingestion skips image unsupported by the active parser: %s",
109
+ path.name,
110
+ )
111
+ for file_path_str in classification.unsupported:
112
+ logger.warning("GraphRAG ingestion skips unsupported file: %s", Path(file_path_str).name)
113
+
114
+ return written
115
+
116
+
117
+ def _write_doc(
118
+ target_dir: Path,
119
+ source: Path,
120
+ text: str,
121
+ used: set[str],
122
+ source_for_input: dict[str, str] | None,
123
+ ) -> int:
124
+ if not text.strip():
125
+ logger.warning("GraphRAG ingestion: empty document skipped: %s", source.name)
126
+ return 0
127
+ dest = _unique_txt_path(target_dir, source, used)
128
+ dest.write_text(text, encoding="utf-8")
129
+ if source_for_input is not None:
130
+ source_for_input[dest.name] = str(source)
131
+ logger.info("GraphRAG ingestion: wrote %s (%d chars)", dest.name, len(text))
132
+ return 1
133
+
134
+
135
+ __all__ = ["prepare_input"]
@@ -0,0 +1,68 @@
1
+ """Scoped compatibility for stale Pandas/Arrow extension registrations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ import threading
7
+ from typing import Any
8
+
9
+ _PANDAS_ARROW_EXTENSION_MODULE = "pandas.core.arrays.arrow.extension_types"
10
+ _PANDAS_ARROW_EXTENSION_NAMES = ("pandas.period", "pandas.interval")
11
+ _extension_registration_lock = threading.Lock()
12
+
13
+
14
+ def _is_duplicate_registration_error(error: BaseException) -> bool:
15
+ message = str(error)
16
+ return (
17
+ "A type extension with name" in message
18
+ and "already defined" in message
19
+ and any(name in message for name in _PANDAS_ARROW_EXTENSION_NAMES)
20
+ )
21
+
22
+
23
+ def _unregister_stale_extension_types(
24
+ pyarrow: Any,
25
+ arrow_key_error: type[BaseException],
26
+ ) -> None:
27
+ for name in _PANDAS_ARROW_EXTENSION_NAMES:
28
+ try:
29
+ pyarrow.unregister_extension_type(name)
30
+ except arrow_key_error:
31
+ # The extension may not have been registered yet.
32
+ continue
33
+
34
+
35
+ def prepare_pandas_arrow_extensions() -> None:
36
+ """Import Pandas Arrow extensions, repairing stale registry entries.
37
+
38
+ Pandas registers ``pandas.period`` and ``pandas.interval`` as a module import
39
+ side effect. If a test runner, plugin, or reload hook removes that module from
40
+ ``sys.modules`` without unregistering the pyarrow types, importing it again
41
+ raises ``pyarrow.lib.ArrowKeyError``. GraphRAG runs in isolated worker threads,
42
+ so this repair is performed immediately inside each worker before GraphRAG
43
+ imports its pandas users.
44
+
45
+ The helper is a no-op when pyarrow is not installed. Other import or registry
46
+ errors are allowed to propagate so a real dependency failure is not hidden.
47
+ """
48
+ try:
49
+ import pyarrow
50
+ from pyarrow.lib import ArrowKeyError
51
+ except ImportError:
52
+ return
53
+
54
+ with _extension_registration_lock:
55
+ try:
56
+ importlib.import_module(_PANDAS_ARROW_EXTENSION_MODULE)
57
+ except ModuleNotFoundError as error:
58
+ if error.name == "pandas":
59
+ return
60
+ raise
61
+ except ArrowKeyError as error:
62
+ if not _is_duplicate_registration_error(error):
63
+ raise
64
+ _unregister_stale_extension_types(pyarrow, ArrowKeyError)
65
+ importlib.import_module(_PANDAS_ARROW_EXTENSION_MODULE)
66
+
67
+
68
+ __all__ = ["prepare_pandas_arrow_extensions"]