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,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"]
|