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,357 @@
1
+ """Bridge DeepTutor's runtime config into a GraphRAG ``settings.yaml``.
2
+
3
+ GraphRAG (microsoft/graphrag, 3.x) is a config-file-driven engine: it reads a
4
+ ``settings.[yaml|json]`` from a project root and wires its own LiteLLM-backed
5
+ model clients from it. Rather than hand-build the deeply nested
6
+ ``GraphRagConfig`` pydantic model, we generate a minimal ``settings.yaml`` from
7
+ DeepTutor's already-resolved LLM + embedding runtime config and let
8
+ ``graphrag.config.load_config`` validate it.
9
+
10
+ Decoupling notes:
11
+ * The only knobs we set are the two model entries + storage layout. Everything
12
+ else (chunking, graph extraction, community reports, the four search configs)
13
+ defaults correctly because each model entry is named with GraphRAG's default
14
+ model id, so the workflow/search sections pick it up automatically.
15
+ * Built-in prompts are used (every ``prompt`` field defaults to ``None`` in
16
+ GraphRAG), so we never scaffold prompt files.
17
+ * Completion calls use DeepTutor's registered GraphRAG compatibility adapter,
18
+ while embeddings retain GraphRAG's stock LiteLLM path.
19
+
20
+ This is the single spot to touch if GraphRAG's config schema shifts between
21
+ releases; pin the dependency to the 3.x line (see ``pyproject`` extra).
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from dataclasses import dataclass
27
+ import logging
28
+ from pathlib import Path
29
+ from typing import Any
30
+ from urllib.parse import urlsplit, urlunsplit
31
+
32
+ from deeptutor.services.config.embedding_endpoint import canonical_embedding_provider_name
33
+ from deeptutor.services.embedding.request_options import should_send_embedding_dimensions
34
+
35
+ from .errors import GraphRagEmbeddingProviderUnsupportedError
36
+ from .provider import (
37
+ COMPLETION_TYPE,
38
+ resolve_completion_call_args,
39
+ resolve_completion_model,
40
+ resolve_completion_provider,
41
+ )
42
+
43
+ logger = logging.getLogger(__name__)
44
+
45
+ SETTINGS_FILENAME = "settings.yaml"
46
+
47
+ # GraphRAG's default model ids — naming our entries this way means every
48
+ # workflow/search section resolves to them without us spelling each one out.
49
+ COMPLETION_MODEL_ID = "default_completion_model"
50
+ EMBEDDING_MODEL_ID = "default_embedding_model"
51
+
52
+ # The four retrieval methods GraphRAG ships. ``local`` is the safest general
53
+ # default (entity-centric, cheaper than global map-reduce).
54
+ SUPPORTED_MODES = ("local", "global", "drift", "basic")
55
+ DEFAULT_MODE = "local"
56
+
57
+ # GraphRAG's stock LiteLLM embedding client uses an OpenAI-style transport and
58
+ # appends the ``/embeddings`` operation path to ``api_base``. DeepTutor stores a
59
+ # complete operation URL for these bindings, so only this explicitly compatible
60
+ # set can be translated safely. Native Cohere, Ollama, DashScope, and Azure
61
+ # transports require separate provider adapters and must not be guessed here.
62
+ OPENAI_COMPATIBLE_EMBEDDING_BINDINGS = frozenset(
63
+ {
64
+ "custom",
65
+ "custom_openai_sdk",
66
+ "gemini",
67
+ "jina",
68
+ "openai",
69
+ "openrouter",
70
+ "opper",
71
+ "siliconflow",
72
+ "vllm",
73
+ }
74
+ )
75
+
76
+
77
+ class GraphRagNotAvailableError(RuntimeError):
78
+ """Raised when the optional ``graphrag`` dependency is not installed."""
79
+
80
+
81
+ class GraphRagNotConfiguredError(RuntimeError):
82
+ """Raised when DeepTutor's LLM / embedding config can't back GraphRAG."""
83
+
84
+
85
+ def is_graphrag_available() -> bool:
86
+ """True when the optional ``graphrag`` package can be imported.
87
+
88
+ GraphRAG is heavy (LiteLLM, lancedb, graspologic, …) and ships as an opt-in
89
+ extra: ``pip install 'deeptutor[graphrag]'``. Until it is installed the
90
+ provider is hidden / blocked in the UI.
91
+ """
92
+ import importlib.util
93
+
94
+ return importlib.util.find_spec("graphrag") is not None
95
+
96
+
97
+ def normalize_mode(mode: str | None) -> str:
98
+ """Coerce a stored ``search_mode`` to a valid GraphRAG search method.
99
+
100
+ The per-KB ``search_mode`` field is shared across engines and defaults to
101
+ ``"hybrid"`` (a LlamaIndex/LightRAG term). Anything that isn't a GraphRAG
102
+ method falls back to :data:`DEFAULT_MODE`.
103
+ """
104
+ candidate = (mode or "").strip().lower()
105
+ return candidate if candidate in SUPPORTED_MODES else DEFAULT_MODE
106
+
107
+
108
+ def graphrag_embedding_api_base(binding: str | None, endpoint: str | None) -> str:
109
+ """Translate a DeepTutor embedding endpoint into GraphRAG ``api_base``.
110
+
111
+ DeepTutor's public embedding contract stores and calls the complete
112
+ operation URL. GraphRAG's LiteLLM client expects the API root and appends
113
+ ``/embeddings`` itself. Strip exactly one terminal path segment only for
114
+ known OpenAI-compatible transports. Query-bearing URLs are left untouched
115
+ because the OpenAI SDK does not preserve operation semantics reliably when
116
+ query parameters are embedded in ``base_url``.
117
+
118
+ Args:
119
+ binding: Active DeepTutor embedding binding.
120
+ endpoint: Fully qualified endpoint saved in the model catalog.
121
+
122
+ Returns:
123
+ The API base GraphRAG should pass to LiteLLM.
124
+ """
125
+ value = str(endpoint or "").strip()
126
+ provider = canonical_embedding_provider_name(binding)
127
+ if not value or provider not in OPENAI_COMPATIBLE_EMBEDDING_BINDINGS:
128
+ return value
129
+
130
+ parsed = urlsplit(value)
131
+ if parsed.scheme not in {"http", "https"} or not parsed.netloc:
132
+ return value
133
+ if parsed.query or parsed.fragment:
134
+ return value
135
+
136
+ path = parsed.path.rstrip("/")
137
+ if not path.endswith("/embeddings"):
138
+ return value
139
+
140
+ api_path = path[: -len("/embeddings")] or "/"
141
+ return urlunsplit(parsed._replace(path=api_path))
142
+
143
+
144
+ def ensure_graphrag_embedding_transport(
145
+ binding: str | None,
146
+ endpoint: str | None,
147
+ ) -> None:
148
+ """Reject native embedding transports that GraphRAG cannot call safely."""
149
+ provider = canonical_embedding_provider_name(binding)
150
+ if provider not in OPENAI_COMPATIBLE_EMBEDDING_BINDINGS:
151
+ raise GraphRagEmbeddingProviderUnsupportedError()
152
+ # Gemini can use either DeepTutor's native ``batchEmbedContents`` adapter
153
+ # or its legacy OpenAI-compatible endpoint. GraphRAG only supports the
154
+ # latter; a provider name alone is no longer enough after Gemini 2 support.
155
+ if provider == "gemini" and not urlsplit(str(endpoint or "")).path.rstrip("/").endswith(
156
+ "/embeddings"
157
+ ):
158
+ raise GraphRagEmbeddingProviderUnsupportedError()
159
+
160
+
161
+ @dataclass(frozen=True)
162
+ class GraphRagQueryConfig:
163
+ """Query-time knobs read from the persisted ``graphrag.json`` slice."""
164
+
165
+ response_type: str = "Multiple Paragraphs"
166
+ community_level: int = 2
167
+ dynamic_community_selection: bool = False
168
+
169
+
170
+ def query_config_from_settings() -> GraphRagQueryConfig:
171
+ """Load GraphRAG query knobs from runtime settings (defaults on any error)."""
172
+ try:
173
+ from deeptutor.services.config import load_graphrag_settings
174
+
175
+ settings = load_graphrag_settings()
176
+ return GraphRagQueryConfig(
177
+ response_type=str(settings.get("response_type") or "Multiple Paragraphs"),
178
+ community_level=int(settings.get("community_level", 2)),
179
+ dynamic_community_selection=bool(settings.get("dynamic_community_selection", False)),
180
+ )
181
+ except Exception:
182
+ return GraphRagQueryConfig()
183
+
184
+
185
+ def _embedding_model_entry(
186
+ *,
187
+ model: str,
188
+ api_base: str | None,
189
+ api_key: str | None,
190
+ binding: str | None,
191
+ dimension: int,
192
+ send_dimensions: bool | None,
193
+ extra_headers: dict[str, str] | None,
194
+ ) -> dict[str, Any]:
195
+ entry: dict[str, Any] = {
196
+ "model_provider": "openai",
197
+ "model": model,
198
+ "auth_method": "api_key",
199
+ }
200
+ if api_base:
201
+ entry["api_base"] = api_base
202
+ # GraphRAG validates that a key is present for ``auth_method: api_key``; local
203
+ # OpenAI-compatible servers accept a placeholder.
204
+ entry["api_key"] = api_key or "sk-no-key-required"
205
+ call_args: dict[str, Any] = {}
206
+ if isinstance(extra_headers, dict) and extra_headers:
207
+ call_args["extra_headers"] = dict(extra_headers)
208
+ if should_send_embedding_dimensions(
209
+ binding=binding,
210
+ model=model,
211
+ dimension=dimension,
212
+ send_dimensions=send_dimensions,
213
+ ):
214
+ call_args["dimensions"] = dimension
215
+ if call_args:
216
+ entry["call_args"] = call_args
217
+ return entry
218
+
219
+
220
+ def _completion_model_entry(llm_cfg: Any, *, api_base: str | None) -> dict[str, Any]:
221
+ entry: dict[str, Any] = {
222
+ "type": COMPLETION_TYPE,
223
+ "model_provider": resolve_completion_provider(llm_cfg),
224
+ "model": resolve_completion_model(llm_cfg),
225
+ "auth_method": "api_key",
226
+ "api_key": getattr(llm_cfg, "api_key", None) or "sk-no-key-required",
227
+ }
228
+ if api_base:
229
+ entry["api_base"] = api_base
230
+ api_version = getattr(llm_cfg, "api_version", None)
231
+ if api_version:
232
+ entry["api_version"] = api_version
233
+ call_args = resolve_completion_call_args(llm_cfg)
234
+ if call_args:
235
+ entry["call_args"] = call_args
236
+ return entry
237
+
238
+
239
+ def build_settings(*, llm_cfg: Any = None, embedding_cfg: Any = None) -> dict[str, Any]:
240
+ """Assemble the GraphRAG ``settings.yaml`` payload from DeepTutor config.
241
+
242
+ ``llm_cfg`` / ``embedding_cfg`` are injectable for tests; in production they
243
+ are resolved from DeepTutor's catalog. Raises
244
+ :class:`GraphRagNotConfiguredError` if either side has no usable model.
245
+ """
246
+ if llm_cfg is None:
247
+ from deeptutor.services.config import resolve_llm_runtime_config
248
+
249
+ llm_cfg = resolve_llm_runtime_config()
250
+ if embedding_cfg is None:
251
+ from deeptutor.services.embedding import get_embedding_config
252
+
253
+ embedding_cfg = get_embedding_config()
254
+
255
+ chat_model = getattr(llm_cfg, "model", None)
256
+ embed_model = getattr(embedding_cfg, "model", None)
257
+ embed_dim = int(getattr(embedding_cfg, "dim", 0) or 0)
258
+ if not chat_model:
259
+ raise GraphRagNotConfiguredError(
260
+ "No active chat model. Configure one under Settings → Catalog before "
261
+ "creating a GraphRAG knowledge base."
262
+ )
263
+ if not embed_model:
264
+ raise GraphRagNotConfiguredError(
265
+ "No active embedding model. Configure one under Settings → Catalog "
266
+ "before creating a GraphRAG knowledge base."
267
+ )
268
+ if not embed_dim:
269
+ raise GraphRagNotConfiguredError(
270
+ "No active embedding model with a known dimension. Configure one under "
271
+ "Settings → Catalog before creating a GraphRAG knowledge base."
272
+ )
273
+
274
+ embedding_binding = str(getattr(embedding_cfg, "binding", "") or "")
275
+ llm_base = getattr(llm_cfg, "effective_url", None) or getattr(llm_cfg, "base_url", None)
276
+ embed_endpoint = getattr(embedding_cfg, "effective_url", None) or getattr(
277
+ embedding_cfg, "base_url", None
278
+ )
279
+ ensure_graphrag_embedding_transport(embedding_binding, embed_endpoint)
280
+ embed_base = graphrag_embedding_api_base(embedding_binding, embed_endpoint)
281
+
282
+ return {
283
+ "completion_models": {
284
+ COMPLETION_MODEL_ID: _completion_model_entry(llm_cfg, api_base=llm_base),
285
+ },
286
+ "embedding_models": {
287
+ EMBEDDING_MODEL_ID: _embedding_model_entry(
288
+ model=embed_model,
289
+ api_base=embed_base,
290
+ api_key=getattr(embedding_cfg, "api_key", None),
291
+ binding=embedding_binding,
292
+ dimension=embed_dim,
293
+ send_dimensions=getattr(embedding_cfg, "send_dimensions", None),
294
+ extra_headers=getattr(embedding_cfg, "extra_headers", None),
295
+ ),
296
+ },
297
+ # Plain-text input: DeepTutor's ingestion writes parsed ``.txt`` files
298
+ # into ``input/`` (see ``ingestion.py``) so GraphRAG never parses
299
+ # documents itself. The ``$`` is escaped as ``$$`` because GraphRAG's
300
+ # loader treats the whole config text as a ``string.Template`` and does
301
+ # env-var substitution on it before parsing the YAML; a bare ``$`` is
302
+ # an "Invalid placeholder" to that pass.
303
+ "input": {"type": "text", "file_pattern": r".*\.txt$$"},
304
+ "input_storage": {"type": "file", "base_dir": "input"},
305
+ "output_storage": {"type": "file", "base_dir": "output"},
306
+ # "json" is GraphRAG's on-disk cache backend id (registered in
307
+ # graphrag_cache.CacheFactory); "file" is a *storage* type, not a cache
308
+ # type, and is rejected the first time the pipeline builds a cache.
309
+ "cache": {"type": "json", "storage": {"type": "file", "base_dir": "cache"}},
310
+ "reporting": {"type": "file", "base_dir": "logs"},
311
+ # GraphRAG/LanceDB defaults to 3072 dimensions; DeepTutor must stamp the
312
+ # active embedding dimension so Qwen-4096 and other non-default models work.
313
+ "vector_store": {
314
+ "type": "lancedb",
315
+ "db_uri": "output/lancedb",
316
+ "vector_size": embed_dim,
317
+ },
318
+ }
319
+
320
+
321
+ def write_settings_payload(root_dir: Path, settings: dict[str, Any]) -> Path:
322
+ """Write a previously-built settings snapshot into ``root_dir``."""
323
+ import yaml
324
+
325
+ root_dir = Path(root_dir)
326
+ root_dir.mkdir(parents=True, exist_ok=True)
327
+ path = root_dir / SETTINGS_FILENAME
328
+ with open(path, "w", encoding="utf-8") as handle:
329
+ yaml.safe_dump(settings, handle, sort_keys=False, allow_unicode=True)
330
+ return path
331
+
332
+
333
+ def write_settings(root_dir: Path, *, llm_cfg: Any = None, embedding_cfg: Any = None) -> Path:
334
+ """Build and write ``settings.yaml`` into ``root_dir``."""
335
+ settings = build_settings(llm_cfg=llm_cfg, embedding_cfg=embedding_cfg)
336
+ return write_settings_payload(root_dir, settings)
337
+
338
+
339
+ __all__ = [
340
+ "SETTINGS_FILENAME",
341
+ "COMPLETION_MODEL_ID",
342
+ "EMBEDDING_MODEL_ID",
343
+ "SUPPORTED_MODES",
344
+ "DEFAULT_MODE",
345
+ "OPENAI_COMPATIBLE_EMBEDDING_BINDINGS",
346
+ "GraphRagNotAvailableError",
347
+ "GraphRagNotConfiguredError",
348
+ "GraphRagQueryConfig",
349
+ "ensure_graphrag_embedding_transport",
350
+ "graphrag_embedding_api_base",
351
+ "is_graphrag_available",
352
+ "normalize_mode",
353
+ "query_config_from_settings",
354
+ "build_settings",
355
+ "write_settings",
356
+ "write_settings_payload",
357
+ ]