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,448 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Knowledge base initialization through the selected RAG provider."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import asyncio
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import TYPE_CHECKING, Optional
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from deeptutor.multi_user.models import CurrentUser
|
|
16
|
+
from deeptutor.services.rag.pipelines.lightrag.indexing_policy import IndexingPolicySnapshot
|
|
17
|
+
|
|
18
|
+
from deeptutor.knowledge.naming import validate_knowledge_base_name
|
|
19
|
+
from deeptutor.knowledge.progress_tracker import ProgressStage, ProgressTracker
|
|
20
|
+
from deeptutor.knowledge.staging import stage_source
|
|
21
|
+
from deeptutor.services.config import resolve_llm_runtime_config
|
|
22
|
+
from deeptutor.services.file_io import atomic_write_json
|
|
23
|
+
from deeptutor.services.rag.factory import normalize_provider_name
|
|
24
|
+
from deeptutor.services.rag.service import RAGService
|
|
25
|
+
from deeptutor.services.setup.data_volume import (
|
|
26
|
+
DataVolumePermissionError,
|
|
27
|
+
ensure_data_volume_writable,
|
|
28
|
+
format_data_volume_permission_error,
|
|
29
|
+
)
|
|
30
|
+
from deeptutor.utils.file_types import FileTypeRouter
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class KnowledgeBaseInitializer:
|
|
36
|
+
"""Knowledge base initializer."""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
kb_name: str,
|
|
41
|
+
base_dir: str = "./data/knowledge_bases",
|
|
42
|
+
api_key: str | None = None,
|
|
43
|
+
base_url: str | None = None,
|
|
44
|
+
progress_tracker: ProgressTracker | None = None,
|
|
45
|
+
rag_provider: str | None = None,
|
|
46
|
+
indexing_snapshot: IndexingPolicySnapshot | None = None,
|
|
47
|
+
owner: CurrentUser | None = None,
|
|
48
|
+
):
|
|
49
|
+
self.kb_name = validate_knowledge_base_name(kb_name)
|
|
50
|
+
self.base_dir = Path(base_dir)
|
|
51
|
+
self.kb_dir = self.base_dir / self.kb_name
|
|
52
|
+
|
|
53
|
+
self.raw_dir = self.kb_dir / "raw"
|
|
54
|
+
self.llamaindex_storage_dir = self.kb_dir / "llamaindex_storage"
|
|
55
|
+
|
|
56
|
+
self.api_key = api_key
|
|
57
|
+
self.base_url = base_url
|
|
58
|
+
self.progress_tracker = progress_tracker or ProgressTracker(self.kb_name, self.base_dir)
|
|
59
|
+
self.rag_provider = normalize_provider_name(rag_provider)
|
|
60
|
+
self.indexing_snapshot = indexing_snapshot
|
|
61
|
+
self.owner = owner
|
|
62
|
+
self.index_published = False
|
|
63
|
+
|
|
64
|
+
def _register_to_config(self) -> None:
|
|
65
|
+
"""Register KB in kb_config.json with initializing state."""
|
|
66
|
+
try:
|
|
67
|
+
from deeptutor.knowledge.manager import KnowledgeBaseManager
|
|
68
|
+
|
|
69
|
+
manager = KnowledgeBaseManager(base_dir=str(self.base_dir))
|
|
70
|
+
manager.config = manager._load_config()
|
|
71
|
+
if self.kb_name in manager.config.get("knowledge_bases", {}):
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
manager.update_kb_status(
|
|
75
|
+
name=self.kb_name,
|
|
76
|
+
status="initializing",
|
|
77
|
+
progress={
|
|
78
|
+
"stage": "initializing",
|
|
79
|
+
"message": "Creating directory structure...",
|
|
80
|
+
"percent": 0,
|
|
81
|
+
"current": 0,
|
|
82
|
+
"total": 0,
|
|
83
|
+
},
|
|
84
|
+
)
|
|
85
|
+
manager.config = manager._load_config()
|
|
86
|
+
manager.config.setdefault("knowledge_bases", {}).setdefault(self.kb_name, {})[
|
|
87
|
+
"rag_provider"
|
|
88
|
+
] = self.rag_provider
|
|
89
|
+
manager._save_config()
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logger.warning(f"Failed to register KB to config: {e}")
|
|
92
|
+
|
|
93
|
+
def _update_metadata_with_provider(self, provider: str) -> None:
|
|
94
|
+
metadata_file = self.kb_dir / "metadata.json"
|
|
95
|
+
metadata: dict = {}
|
|
96
|
+
if metadata_file.exists():
|
|
97
|
+
try:
|
|
98
|
+
with open(metadata_file, "r", encoding="utf-8") as f:
|
|
99
|
+
metadata = json.load(f)
|
|
100
|
+
except Exception:
|
|
101
|
+
metadata = {}
|
|
102
|
+
|
|
103
|
+
metadata["rag_provider"] = provider
|
|
104
|
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
105
|
+
metadata["last_updated"] = timestamp
|
|
106
|
+
metadata["last_indexed_at"] = timestamp
|
|
107
|
+
metadata["last_indexed_count"] = len(
|
|
108
|
+
FileTypeRouter.collect_supported_files(self.raw_dir, recursive=True)
|
|
109
|
+
)
|
|
110
|
+
if provider == "llamaindex":
|
|
111
|
+
from deeptutor.knowledge.indexing_run import load_run
|
|
112
|
+
|
|
113
|
+
receipt = load_run(self.kb_dir)
|
|
114
|
+
if receipt:
|
|
115
|
+
metadata["last_indexed_count"] = sum(
|
|
116
|
+
doc.get("status") == "completed" for doc in receipt["documents"].values()
|
|
117
|
+
)
|
|
118
|
+
metadata["indexing_state"] = receipt["state"]
|
|
119
|
+
metadata["last_indexed_action"] = "create"
|
|
120
|
+
|
|
121
|
+
atomic_write_json(metadata_file, metadata)
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
from deeptutor.services.config import get_kb_config_service
|
|
125
|
+
|
|
126
|
+
service = get_kb_config_service()
|
|
127
|
+
service.set_rag_provider(self.kb_name, provider)
|
|
128
|
+
service.set_kb_config(self.kb_name, {"needs_reindex": False})
|
|
129
|
+
except Exception as config_err:
|
|
130
|
+
logger.warning(f"Failed to persist provider in centralized config: {config_err}")
|
|
131
|
+
|
|
132
|
+
def create_directory_structure(self) -> None:
|
|
133
|
+
"""Create KB directory structure."""
|
|
134
|
+
logger.info(f"Creating directory structure for knowledge base: {self.kb_name}")
|
|
135
|
+
ensure_data_volume_writable(self.base_dir)
|
|
136
|
+
|
|
137
|
+
for dir_path in [
|
|
138
|
+
self.raw_dir,
|
|
139
|
+
]:
|
|
140
|
+
dir_path.mkdir(parents=True, exist_ok=True)
|
|
141
|
+
|
|
142
|
+
metadata = {
|
|
143
|
+
"name": self.kb_name,
|
|
144
|
+
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
145
|
+
"description": f"Knowledge base: {self.kb_name}",
|
|
146
|
+
"version": "1.0",
|
|
147
|
+
"rag_provider": self.rag_provider,
|
|
148
|
+
"needs_reindex": False,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
atomic_write_json(self.kb_dir / "metadata.json", metadata)
|
|
152
|
+
|
|
153
|
+
self._register_to_config()
|
|
154
|
+
|
|
155
|
+
def copy_documents(self, source_files: list[str]) -> list[str]:
|
|
156
|
+
"""Stage source documents into the raw directory.
|
|
157
|
+
|
|
158
|
+
Sources arrive flattened to their basename, so a docs tree holding one
|
|
159
|
+
``full.md`` per volume wants the same path over and over. Each document
|
|
160
|
+
is kept under a distinct name instead of overwriting the one before it
|
|
161
|
+
(#2041) — the same rule the incremental add path already followed.
|
|
162
|
+
"""
|
|
163
|
+
copied_files: list[str] = []
|
|
164
|
+
staged: set[Path] = set()
|
|
165
|
+
for source in source_files:
|
|
166
|
+
source_path = Path(source)
|
|
167
|
+
if not source_path.exists() or not source_path.is_file():
|
|
168
|
+
logger.warning(f"Source file not found: {source}")
|
|
169
|
+
continue
|
|
170
|
+
staged_path, copied = stage_source(source_path, self.raw_dir / source_path.name)
|
|
171
|
+
if staged_path in staged:
|
|
172
|
+
continue
|
|
173
|
+
staged.add(staged_path)
|
|
174
|
+
copied_files.append(str(staged_path))
|
|
175
|
+
if copied and staged_path.name != source_path.name:
|
|
176
|
+
logger.warning(f"Name taken by another document, staged as {staged_path}")
|
|
177
|
+
return copied_files
|
|
178
|
+
|
|
179
|
+
async def process_documents(
|
|
180
|
+
self,
|
|
181
|
+
) -> bool:
|
|
182
|
+
"""Process documents with the KB's bound provider."""
|
|
183
|
+
provider = self.rag_provider
|
|
184
|
+
|
|
185
|
+
self.progress_tracker.update(
|
|
186
|
+
ProgressStage.PROCESSING_DOCUMENTS,
|
|
187
|
+
message_key="Starting to process documents with {{provider}} provider...",
|
|
188
|
+
message_params={"provider": provider},
|
|
189
|
+
current=0,
|
|
190
|
+
total=0,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# recursive=True so documents organized into folders are indexed too
|
|
194
|
+
# (folders are display-only and don't otherwise affect retrieval).
|
|
195
|
+
doc_files = FileTypeRouter.collect_supported_files(self.raw_dir, recursive=True)
|
|
196
|
+
|
|
197
|
+
if not doc_files:
|
|
198
|
+
self.progress_tracker.update(
|
|
199
|
+
ProgressStage.ERROR,
|
|
200
|
+
message_key="No documents found to process",
|
|
201
|
+
error="No documents found",
|
|
202
|
+
)
|
|
203
|
+
raise ValueError("No documents found to process")
|
|
204
|
+
|
|
205
|
+
self.progress_tracker.update(
|
|
206
|
+
ProgressStage.PROCESSING_DOCUMENTS,
|
|
207
|
+
message_key="Found {{count}} documents, starting to process...",
|
|
208
|
+
message_params={"count": len(doc_files)},
|
|
209
|
+
current=0,
|
|
210
|
+
total=len(doc_files),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
rag_service = RAGService(
|
|
214
|
+
kb_base_dir=str(self.base_dir),
|
|
215
|
+
provider=provider,
|
|
216
|
+
)
|
|
217
|
+
file_paths = [str(doc_file) for doc_file in doc_files]
|
|
218
|
+
|
|
219
|
+
def _on_progress(batch_num, total_batches):
|
|
220
|
+
self.progress_tracker.update(
|
|
221
|
+
ProgressStage.PROCESSING_DOCUMENTS,
|
|
222
|
+
message_key="Embedding batches: {{current}}/{{total}} complete",
|
|
223
|
+
message_params={"current": batch_num, "total": total_batches},
|
|
224
|
+
current=batch_num,
|
|
225
|
+
total=total_batches,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
def _on_image_progress(current: int, total: int):
|
|
229
|
+
self.progress_tracker.update(
|
|
230
|
+
ProgressStage.PROCESSING_DOCUMENTS,
|
|
231
|
+
message_key="Describing images: {{current}}/{{total}}",
|
|
232
|
+
message_params={"current": current, "total": total},
|
|
233
|
+
current=current,
|
|
234
|
+
total=total,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
def _prepare_publication(candidate_root: Path) -> None:
|
|
238
|
+
self._update_metadata_with_provider(provider)
|
|
239
|
+
self.progress_tracker.update(
|
|
240
|
+
ProgressStage.COMPLETED,
|
|
241
|
+
message_key="Knowledge base initialization complete!",
|
|
242
|
+
current=1,
|
|
243
|
+
total=1,
|
|
244
|
+
indexed_count=len(doc_files),
|
|
245
|
+
index_changed=True,
|
|
246
|
+
index_action="create",
|
|
247
|
+
publication_version=candidate_root.name,
|
|
248
|
+
)
|
|
249
|
+
self.progress_tracker.verify_terminal(
|
|
250
|
+
current=1,
|
|
251
|
+
total=1,
|
|
252
|
+
indexed_count=len(doc_files),
|
|
253
|
+
index_action="create",
|
|
254
|
+
publication_version=candidate_root.name,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
try:
|
|
258
|
+
success = await rag_service.initialize(
|
|
259
|
+
kb_name=self.kb_name,
|
|
260
|
+
file_paths=file_paths,
|
|
261
|
+
task_id=self.progress_tracker.task_id,
|
|
262
|
+
progress_callback=_on_progress,
|
|
263
|
+
image_progress_callback=_on_image_progress,
|
|
264
|
+
indexing_snapshot=self.indexing_snapshot,
|
|
265
|
+
before_publish=_prepare_publication if provider == "lightrag" else None,
|
|
266
|
+
)
|
|
267
|
+
if not success:
|
|
268
|
+
self.progress_tracker.update(
|
|
269
|
+
ProgressStage.ERROR,
|
|
270
|
+
message_key="Document processing failed",
|
|
271
|
+
error="RAG pipeline returned failure",
|
|
272
|
+
)
|
|
273
|
+
raise RuntimeError("RAG pipeline returned failure")
|
|
274
|
+
|
|
275
|
+
self.index_published = provider == "lightrag"
|
|
276
|
+
try:
|
|
277
|
+
if provider != "lightrag":
|
|
278
|
+
self._update_metadata_with_provider(provider)
|
|
279
|
+
self.progress_tracker.update(
|
|
280
|
+
ProgressStage.PROCESSING_DOCUMENTS,
|
|
281
|
+
message_key="Documents processed successfully",
|
|
282
|
+
current=len(doc_files),
|
|
283
|
+
total=len(doc_files),
|
|
284
|
+
)
|
|
285
|
+
except Exception:
|
|
286
|
+
if not self.index_published:
|
|
287
|
+
raise
|
|
288
|
+
logger.warning(
|
|
289
|
+
"LightRAG index for %s was published before metadata bookkeeping failed",
|
|
290
|
+
self.kb_name,
|
|
291
|
+
exc_info=True,
|
|
292
|
+
)
|
|
293
|
+
except PermissionError as e:
|
|
294
|
+
error_msg = format_data_volume_permission_error(self.kb_dir, cause=e)
|
|
295
|
+
logger.error("Error processing documents: %s", error_msg)
|
|
296
|
+
self.progress_tracker.update(
|
|
297
|
+
ProgressStage.ERROR,
|
|
298
|
+
message_key="Failed to process documents",
|
|
299
|
+
error=error_msg,
|
|
300
|
+
)
|
|
301
|
+
raise DataVolumePermissionError(error_msg) from e
|
|
302
|
+
except Exception as e:
|
|
303
|
+
error_msg = str(e)
|
|
304
|
+
logger.error(f"Error processing documents: {error_msg}")
|
|
305
|
+
self.progress_tracker.update(
|
|
306
|
+
ProgressStage.ERROR,
|
|
307
|
+
message_key="Failed to process documents",
|
|
308
|
+
error=error_msg,
|
|
309
|
+
)
|
|
310
|
+
raise
|
|
311
|
+
|
|
312
|
+
try:
|
|
313
|
+
await self.fix_structure()
|
|
314
|
+
await self.display_statistics_generic()
|
|
315
|
+
except Exception:
|
|
316
|
+
if not self.index_published:
|
|
317
|
+
raise
|
|
318
|
+
logger.warning(
|
|
319
|
+
"LightRAG index for %s was published before statistics bookkeeping failed",
|
|
320
|
+
self.kb_name,
|
|
321
|
+
exc_info=True,
|
|
322
|
+
)
|
|
323
|
+
return True
|
|
324
|
+
|
|
325
|
+
async def fix_structure(self) -> None:
|
|
326
|
+
"""No-op retained for compatibility with previous pipelines."""
|
|
327
|
+
logger.info("Skipping legacy structure cleanup")
|
|
328
|
+
|
|
329
|
+
async def display_statistics_generic(self) -> None:
|
|
330
|
+
"""Display basic statistics."""
|
|
331
|
+
raw_files = list(self.raw_dir.glob("*")) if self.raw_dir.exists() else []
|
|
332
|
+
from deeptutor.services.rag.index_probe import inspect_kb_versions
|
|
333
|
+
|
|
334
|
+
index_versions = inspect_kb_versions(self.kb_dir, self.rag_provider)
|
|
335
|
+
|
|
336
|
+
logger.info("=" * 50)
|
|
337
|
+
logger.info("Knowledge Base Statistics")
|
|
338
|
+
logger.info("=" * 50)
|
|
339
|
+
logger.info(f"Raw documents: {len(raw_files)}")
|
|
340
|
+
logger.info(f"Index versions: {len(index_versions)}")
|
|
341
|
+
logger.info(f"Provider used: {self.rag_provider}")
|
|
342
|
+
logger.info("=" * 50)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
async def initialize_knowledge_base(
|
|
346
|
+
kb_name: str,
|
|
347
|
+
source_files: list[str],
|
|
348
|
+
base_dir: str = "./data/knowledge_bases",
|
|
349
|
+
api_key: Optional[str] = None,
|
|
350
|
+
base_url: Optional[str] = None,
|
|
351
|
+
rag_provider: Optional[str] = None,
|
|
352
|
+
) -> bool:
|
|
353
|
+
"""Convenience initializer used by CLI wrappers."""
|
|
354
|
+
from deeptutor.knowledge.manager import KnowledgeBaseManager
|
|
355
|
+
|
|
356
|
+
manager = KnowledgeBaseManager(base_dir=base_dir)
|
|
357
|
+
initializer = KnowledgeBaseInitializer(
|
|
358
|
+
kb_name=kb_name,
|
|
359
|
+
base_dir=base_dir,
|
|
360
|
+
api_key=api_key,
|
|
361
|
+
base_url=base_url,
|
|
362
|
+
rag_provider=rag_provider,
|
|
363
|
+
)
|
|
364
|
+
try:
|
|
365
|
+
initializer.create_directory_structure()
|
|
366
|
+
copied_files = initializer.copy_documents(source_files)
|
|
367
|
+
await initializer.process_documents()
|
|
368
|
+
manager.update_kb_status(
|
|
369
|
+
name=kb_name,
|
|
370
|
+
status="ready",
|
|
371
|
+
progress={
|
|
372
|
+
"stage": "completed",
|
|
373
|
+
"message": "Knowledge base initialization complete!",
|
|
374
|
+
"percent": 100,
|
|
375
|
+
"current": 1,
|
|
376
|
+
"total": 1,
|
|
377
|
+
"file_name": "",
|
|
378
|
+
"error": None,
|
|
379
|
+
"timestamp": datetime.now().isoformat(),
|
|
380
|
+
"indexed_count": len(copied_files),
|
|
381
|
+
"index_changed": True,
|
|
382
|
+
"index_action": "create",
|
|
383
|
+
},
|
|
384
|
+
)
|
|
385
|
+
return True
|
|
386
|
+
except Exception as exc:
|
|
387
|
+
manager.update_kb_status(
|
|
388
|
+
name=kb_name,
|
|
389
|
+
status="error",
|
|
390
|
+
progress={
|
|
391
|
+
"stage": "error",
|
|
392
|
+
"message": "Knowledge base initialization failed",
|
|
393
|
+
"percent": 0,
|
|
394
|
+
"current": 0,
|
|
395
|
+
"total": 1,
|
|
396
|
+
"file_name": "",
|
|
397
|
+
"error": str(exc),
|
|
398
|
+
"timestamp": datetime.now().isoformat(),
|
|
399
|
+
},
|
|
400
|
+
)
|
|
401
|
+
raise
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
async def main() -> None:
|
|
405
|
+
try:
|
|
406
|
+
llm_config = resolve_llm_runtime_config()
|
|
407
|
+
default_api_key = llm_config.api_key
|
|
408
|
+
default_base_url = llm_config.effective_url
|
|
409
|
+
except Exception:
|
|
410
|
+
default_api_key = ""
|
|
411
|
+
default_base_url = ""
|
|
412
|
+
|
|
413
|
+
parser = argparse.ArgumentParser(description="Initialize a new knowledge base from documents")
|
|
414
|
+
parser.add_argument("name", help="Knowledge base name")
|
|
415
|
+
parser.add_argument("--docs", nargs="+", help="Document files to process")
|
|
416
|
+
parser.add_argument("--docs-dir", help="Directory containing documents to process")
|
|
417
|
+
parser.add_argument("--base-dir", default="./knowledge_bases")
|
|
418
|
+
parser.add_argument("--api-key", default=default_api_key)
|
|
419
|
+
parser.add_argument("--base-url", default=default_base_url)
|
|
420
|
+
parser.add_argument("--skip-processing", action="store_true")
|
|
421
|
+
|
|
422
|
+
args = parser.parse_args()
|
|
423
|
+
|
|
424
|
+
doc_files: list[str] = []
|
|
425
|
+
if args.docs:
|
|
426
|
+
doc_files.extend(args.docs)
|
|
427
|
+
if args.docs_dir:
|
|
428
|
+
docs_dir = Path(args.docs_dir)
|
|
429
|
+
if docs_dir.exists() and docs_dir.is_dir():
|
|
430
|
+
doc_files.extend(str(f) for f in FileTypeRouter.collect_supported_files(docs_dir))
|
|
431
|
+
|
|
432
|
+
initializer = KnowledgeBaseInitializer(
|
|
433
|
+
kb_name=args.name,
|
|
434
|
+
base_dir=args.base_dir,
|
|
435
|
+
api_key=args.api_key,
|
|
436
|
+
base_url=args.base_url,
|
|
437
|
+
)
|
|
438
|
+
initializer.create_directory_structure()
|
|
439
|
+
|
|
440
|
+
if doc_files:
|
|
441
|
+
initializer.copy_documents(doc_files)
|
|
442
|
+
|
|
443
|
+
if not args.skip_processing:
|
|
444
|
+
await initializer.process_documents()
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
if __name__ == "__main__":
|
|
448
|
+
asyncio.run(main())
|