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,309 @@
|
|
|
1
|
+
"""Run an evaluation set against a knowledge base and score its retrieval.
|
|
2
|
+
|
|
3
|
+
The runner drives the same search path a chat turn uses (``rag_search`` →
|
|
4
|
+
:class:`~deeptutor.services.rag.service.RAGService` → the KB's bound pipeline),
|
|
5
|
+
so a score reflects production retrieval rather than a re-implementation of it.
|
|
6
|
+
One case is one search; the ranked citations it returns are matched against the
|
|
7
|
+
case's gold passages and scored at the configured cutoff.
|
|
8
|
+
|
|
9
|
+
A case that cannot be scored — the engine raised, or it answered with an error
|
|
10
|
+
envelope (a missing index, a credentials failure) — is recorded as failed with
|
|
11
|
+
its message. Failed cases are excluded from the aggregate so an unreachable
|
|
12
|
+
engine reports "0 of 8 scored" instead of a misleadingly low quality score.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from collections.abc import Awaitable, Callable, Mapping
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
import logging
|
|
20
|
+
import time
|
|
21
|
+
from typing import TYPE_CHECKING, Any
|
|
22
|
+
|
|
23
|
+
from .dataset import EvalCase, EvalDataset
|
|
24
|
+
from .matching import MatchPolicy, RelevanceMatcher, source_text
|
|
25
|
+
from .metrics import QueryMetrics, evaluate_hits
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING: # ``report`` imports this module's result type.
|
|
28
|
+
from .report import EvalReport
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
#: Search callable shaped like :func:`deeptutor.tools.rag_tool.rag_search`.
|
|
33
|
+
SearchFn = Callable[..., Awaitable[dict[str, Any]]]
|
|
34
|
+
#: Progress hook: ``(index, total, evaluation)`` after each case.
|
|
35
|
+
ProgressFn = Callable[[int, int, "QueryEvaluation"], None]
|
|
36
|
+
|
|
37
|
+
#: The engine returned ranked citations.
|
|
38
|
+
SOURCE_MODE_CITATIONS = "sources"
|
|
39
|
+
#: The engine returned context but no citations; the context is scored as one span.
|
|
40
|
+
SOURCE_MODE_CONTEXT = "content"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class QueryEvaluation:
|
|
45
|
+
"""One case's retrieval result, its ranked hits, and its metrics."""
|
|
46
|
+
|
|
47
|
+
case_id: str
|
|
48
|
+
query: str
|
|
49
|
+
k: int
|
|
50
|
+
gold_count: int
|
|
51
|
+
hits: tuple[frozenset[int], ...]
|
|
52
|
+
metrics: QueryMetrics
|
|
53
|
+
source_mode: str = SOURCE_MODE_CITATIONS
|
|
54
|
+
provider: str = ""
|
|
55
|
+
error: str = ""
|
|
56
|
+
sources: tuple[dict[str, Any], ...] = ()
|
|
57
|
+
query_rewrite: dict[str, Any] | None = None
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def failed(self) -> bool:
|
|
61
|
+
"""True when the case could not be scored (error or failed engine)."""
|
|
62
|
+
return bool(self.error)
|
|
63
|
+
|
|
64
|
+
def to_dict(self) -> dict[str, Any]:
|
|
65
|
+
"""Serializable form, including which gold passages were covered."""
|
|
66
|
+
return {
|
|
67
|
+
"case_id": self.case_id,
|
|
68
|
+
"query": self.query,
|
|
69
|
+
"error": self.error,
|
|
70
|
+
"provider": self.provider,
|
|
71
|
+
"source_mode": self.source_mode,
|
|
72
|
+
"matched_gold": [sorted(indices) for indices in self.hits],
|
|
73
|
+
"query_rewrite": self.query_rewrite,
|
|
74
|
+
**self.metrics.to_dict(),
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _extract_sources(result: Mapping[str, Any]) -> tuple[list[dict[str, Any]], str]:
|
|
79
|
+
"""Pull the ranked citations out of a search result.
|
|
80
|
+
|
|
81
|
+
Engines that offload retrieval (LightRAG server, WeKnora, IMA) and the local
|
|
82
|
+
ones all normalize to ``sources``. An engine that answers with context but no
|
|
83
|
+
citations is scored on that context as a single span, so the case still says
|
|
84
|
+
whether the material came back at all.
|
|
85
|
+
"""
|
|
86
|
+
raw = result.get("sources")
|
|
87
|
+
if isinstance(raw, list):
|
|
88
|
+
records = [item for item in raw if isinstance(item, Mapping)]
|
|
89
|
+
if records:
|
|
90
|
+
return [dict(record) for record in records], SOURCE_MODE_CITATIONS
|
|
91
|
+
|
|
92
|
+
content = result.get("content") or result.get("answer") or ""
|
|
93
|
+
if isinstance(content, str) and content.strip():
|
|
94
|
+
return [{"content": content}], SOURCE_MODE_CONTEXT
|
|
95
|
+
return [], SOURCE_MODE_CITATIONS
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _error_message(result: Mapping[str, Any]) -> str:
|
|
99
|
+
"""The engine's own failure text, or an empty string when the search worked."""
|
|
100
|
+
if result.get("needs_reindex"):
|
|
101
|
+
message = (
|
|
102
|
+
result.get("answer") or result.get("content") or "Knowledge base needs re-indexing."
|
|
103
|
+
)
|
|
104
|
+
return str(message)
|
|
105
|
+
if result.get("error_type"):
|
|
106
|
+
message = result.get("answer") or result.get("content") or result["error_type"]
|
|
107
|
+
return str(message)
|
|
108
|
+
if result.get("error"):
|
|
109
|
+
return str(result["error"])
|
|
110
|
+
return ""
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
async def _default_search(
|
|
114
|
+
query: str,
|
|
115
|
+
*,
|
|
116
|
+
kb_name: str,
|
|
117
|
+
kb_base_dir: str | None,
|
|
118
|
+
mode: str | None,
|
|
119
|
+
top_k: int,
|
|
120
|
+
rewrite: bool | None = None,
|
|
121
|
+
) -> dict[str, Any]:
|
|
122
|
+
"""Retrieve through the production path (``rag_search`` → the bound pipeline)."""
|
|
123
|
+
from deeptutor.tools.rag_tool import rag_search
|
|
124
|
+
|
|
125
|
+
kwargs: dict[str, Any] = {"top_k": top_k}
|
|
126
|
+
if mode:
|
|
127
|
+
kwargs["mode"] = mode
|
|
128
|
+
if rewrite is not None:
|
|
129
|
+
kwargs["rewrite"] = rewrite
|
|
130
|
+
return await rag_search(query=query, kb_name=kb_name, kb_base_dir=kb_base_dir, **kwargs)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class RetrievalEvaluator:
|
|
134
|
+
"""Score a knowledge base against an evaluation set.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
kb_name: The knowledge base to search.
|
|
138
|
+
kb_base_dir: KB root to search; ``None`` resolves the caller's workspace.
|
|
139
|
+
k: Rank cutoff for every metric.
|
|
140
|
+
mode: Search mode for engines that support one (e.g. ``hybrid``);
|
|
141
|
+
``None`` leaves each engine on its own default.
|
|
142
|
+
query_limit: Evaluate only the first N cases.
|
|
143
|
+
policy: Gold-matching thresholds; defaults to :class:`MatchPolicy`.
|
|
144
|
+
search_fn: Search callable override, for tests and embedders.
|
|
145
|
+
provider: Provider label for the report; a search result's own
|
|
146
|
+
``provider`` still wins per case.
|
|
147
|
+
rewrite: Force the query-rewrite layer on (``True``), off (``False``),
|
|
148
|
+
or defer to the runtime ``query_rewrite`` setting (``None``, the
|
|
149
|
+
default). Passed through to :func:`deeptutor.tools.rag_tool.rag_search`.
|
|
150
|
+
|
|
151
|
+
Raises:
|
|
152
|
+
ValueError: ``kb_name`` is empty or ``k`` is below 1.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
def __init__(
|
|
156
|
+
self,
|
|
157
|
+
*,
|
|
158
|
+
kb_name: str,
|
|
159
|
+
kb_base_dir: str | None = None,
|
|
160
|
+
k: int = 5,
|
|
161
|
+
mode: str | None = None,
|
|
162
|
+
query_limit: int | None = None,
|
|
163
|
+
policy: MatchPolicy | None = None,
|
|
164
|
+
search_fn: SearchFn | None = None,
|
|
165
|
+
provider: str = "",
|
|
166
|
+
rewrite: bool | None = None,
|
|
167
|
+
) -> None:
|
|
168
|
+
self.kb_name = (kb_name or "").strip()
|
|
169
|
+
if not self.kb_name:
|
|
170
|
+
raise ValueError("kb_name must be a non-empty string")
|
|
171
|
+
self.k = int(k)
|
|
172
|
+
if self.k < 1:
|
|
173
|
+
raise ValueError("k must be >= 1")
|
|
174
|
+
self.kb_base_dir = kb_base_dir
|
|
175
|
+
self.mode = mode or None
|
|
176
|
+
self.query_limit = query_limit if query_limit and query_limit > 0 else None
|
|
177
|
+
self.policy = policy or MatchPolicy()
|
|
178
|
+
self.search_fn: SearchFn = search_fn or _default_search
|
|
179
|
+
self.provider = provider
|
|
180
|
+
self.rewrite = rewrite
|
|
181
|
+
|
|
182
|
+
async def evaluate(
|
|
183
|
+
self,
|
|
184
|
+
dataset: EvalDataset,
|
|
185
|
+
*,
|
|
186
|
+
progress: ProgressFn | None = None,
|
|
187
|
+
) -> EvalReport:
|
|
188
|
+
"""Score every case and return the report defined in ``...eval.report``."""
|
|
189
|
+
from .report import EvalReport
|
|
190
|
+
|
|
191
|
+
cases = dataset.limited(self.query_limit).cases
|
|
192
|
+
started = time.monotonic()
|
|
193
|
+
evaluations: list[QueryEvaluation] = []
|
|
194
|
+
for index, case in enumerate(cases, start=1):
|
|
195
|
+
evaluation = await self.evaluate_case(case)
|
|
196
|
+
evaluations.append(evaluation)
|
|
197
|
+
if evaluation.failed:
|
|
198
|
+
logger.warning(
|
|
199
|
+
"Evaluation case %s could not be scored: %s", case.id, evaluation.error
|
|
200
|
+
)
|
|
201
|
+
if progress is not None:
|
|
202
|
+
progress(index, len(cases), evaluation)
|
|
203
|
+
|
|
204
|
+
return EvalReport(
|
|
205
|
+
dataset_name=dataset.name,
|
|
206
|
+
kb_name=self.kb_name,
|
|
207
|
+
provider=self.provider,
|
|
208
|
+
k=self.k,
|
|
209
|
+
mode=self.mode,
|
|
210
|
+
policy=self.policy,
|
|
211
|
+
evaluations=tuple(evaluations),
|
|
212
|
+
duration_seconds=time.monotonic() - started,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
async def evaluate_case(self, case: EvalCase) -> QueryEvaluation:
|
|
216
|
+
"""Search one case and score the citations it came back with."""
|
|
217
|
+
matcher = RelevanceMatcher(case.gold, self.policy)
|
|
218
|
+
try:
|
|
219
|
+
search_kwargs: dict[str, Any] = {
|
|
220
|
+
"query": case.query,
|
|
221
|
+
"kb_name": self.kb_name,
|
|
222
|
+
"kb_base_dir": self.kb_base_dir,
|
|
223
|
+
"mode": self.mode,
|
|
224
|
+
"top_k": self.k,
|
|
225
|
+
}
|
|
226
|
+
if self.rewrite is not None:
|
|
227
|
+
search_kwargs["rewrite"] = self.rewrite
|
|
228
|
+
result = await self.search_fn(**search_kwargs)
|
|
229
|
+
except Exception as exc:
|
|
230
|
+
return self._failure(case, matcher, f"{type(exc).__name__}: {exc}")
|
|
231
|
+
|
|
232
|
+
if not isinstance(result, Mapping):
|
|
233
|
+
kind = type(result).__name__
|
|
234
|
+
return self._failure(case, matcher, f"unexpected search result {kind}")
|
|
235
|
+
|
|
236
|
+
error = _error_message(result)
|
|
237
|
+
if error:
|
|
238
|
+
return self._failure(case, matcher, error)
|
|
239
|
+
|
|
240
|
+
sources, source_mode = _extract_sources(result)
|
|
241
|
+
hits = tuple(
|
|
242
|
+
frozenset(matcher.match(source_text(source)).indices) for source in sources[: self.k]
|
|
243
|
+
)
|
|
244
|
+
provenance = result.get("query_rewrite")
|
|
245
|
+
return QueryEvaluation(
|
|
246
|
+
case_id=case.id,
|
|
247
|
+
query=case.query,
|
|
248
|
+
k=self.k,
|
|
249
|
+
gold_count=matcher.gold_count,
|
|
250
|
+
hits=hits,
|
|
251
|
+
metrics=evaluate_hits(case.id, hits, matcher.gold_count, self.k),
|
|
252
|
+
source_mode=source_mode,
|
|
253
|
+
provider=str(result.get("provider") or self.provider),
|
|
254
|
+
sources=tuple(sources),
|
|
255
|
+
query_rewrite=dict(provenance) if isinstance(provenance, Mapping) else None,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
def _failure(self, case: EvalCase, matcher: RelevanceMatcher, message: str) -> QueryEvaluation:
|
|
259
|
+
"""Record a case that could not be scored, with zeroed metrics."""
|
|
260
|
+
return QueryEvaluation(
|
|
261
|
+
case_id=case.id,
|
|
262
|
+
query=case.query,
|
|
263
|
+
k=self.k,
|
|
264
|
+
gold_count=matcher.gold_count,
|
|
265
|
+
hits=(),
|
|
266
|
+
metrics=evaluate_hits(case.id, (), matcher.gold_count, self.k),
|
|
267
|
+
provider=self.provider,
|
|
268
|
+
error=message,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
async def evaluate_dataset(
|
|
273
|
+
dataset: EvalDataset,
|
|
274
|
+
*,
|
|
275
|
+
kb_name: str,
|
|
276
|
+
kb_base_dir: str | None = None,
|
|
277
|
+
k: int = 5,
|
|
278
|
+
mode: str | None = None,
|
|
279
|
+
query_limit: int | None = None,
|
|
280
|
+
policy: MatchPolicy | None = None,
|
|
281
|
+
search_fn: SearchFn | None = None,
|
|
282
|
+
provider: str = "",
|
|
283
|
+
rewrite: bool | None = None,
|
|
284
|
+
progress: ProgressFn | None = None,
|
|
285
|
+
) -> EvalReport:
|
|
286
|
+
"""Score one dataset against one knowledge base (see :class:`RetrievalEvaluator`)."""
|
|
287
|
+
evaluator = RetrievalEvaluator(
|
|
288
|
+
kb_name=kb_name,
|
|
289
|
+
kb_base_dir=kb_base_dir,
|
|
290
|
+
k=k,
|
|
291
|
+
mode=mode,
|
|
292
|
+
query_limit=query_limit,
|
|
293
|
+
policy=policy,
|
|
294
|
+
search_fn=search_fn,
|
|
295
|
+
provider=provider,
|
|
296
|
+
rewrite=rewrite,
|
|
297
|
+
)
|
|
298
|
+
return await evaluator.evaluate(dataset, progress=progress)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
__all__ = [
|
|
302
|
+
"ProgressFn",
|
|
303
|
+
"QueryEvaluation",
|
|
304
|
+
"RetrievalEvaluator",
|
|
305
|
+
"SearchFn",
|
|
306
|
+
"SOURCE_MODE_CITATIONS",
|
|
307
|
+
"SOURCE_MODE_CONTEXT",
|
|
308
|
+
"evaluate_dataset",
|
|
309
|
+
]
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
"""RAG pipeline factory.
|
|
2
|
+
|
|
3
|
+
Selects a KB's index/retrieve engine by provider name. Three pipelines ship
|
|
4
|
+
today:
|
|
5
|
+
|
|
6
|
+
* ``llamaindex`` (default) — local vector retrieval with hybrid BM25 fusion.
|
|
7
|
+
* ``pageindex`` — PageIndex Cloud (deployment credential + MCP tools).
|
|
8
|
+
* ``pageindex-oss`` — local PageIndex library using the active chat LLM.
|
|
9
|
+
* ``graphrag`` — local knowledge-graph retrieval (microsoft/graphrag);
|
|
10
|
+
optional dependency, ``pip install 'deeptutor[graphrag]'``.
|
|
11
|
+
* ``lightrag`` — graph + vector retrieval (HKUDS/LightRAG, multimodal
|
|
12
|
+
via the LightRAG native pipeline); optional dependency,
|
|
13
|
+
``pip install 'deeptutor[rag-lightrag]'``.
|
|
14
|
+
* ``lightrag-server`` — retrieval offloaded to an external, standalone
|
|
15
|
+
LightRAG server the user runs. No local index: each
|
|
16
|
+
KB is a connection pointer queried over HTTP.
|
|
17
|
+
* ``ima`` — retrieval offloaded to a Tencent IMA knowledge base
|
|
18
|
+
the user curates in IMA. No local index: each KB is
|
|
19
|
+
a connection pointer queried over IMA's OpenAPI.
|
|
20
|
+
* ``weknora`` — retrieval offloaded to a knowledge base in a
|
|
21
|
+
self-hosted Tencent WeKnora deployment. No local
|
|
22
|
+
index or document copy; each KB is a connection
|
|
23
|
+
pointer queried over WeKnora's REST API.
|
|
24
|
+
* ``kiwix`` — retrieval from one ZIM already served by
|
|
25
|
+
kiwix-serve. Only matched articles are fetched;
|
|
26
|
+
no local archive or index copy is made.
|
|
27
|
+
|
|
28
|
+
A KB is bound to one provider at creation time; later adds and retrieval always
|
|
29
|
+
go through that same pipeline (enforced upstream in the knowledge router).
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
36
|
+
|
|
37
|
+
DEFAULT_PROVIDER = "llamaindex"
|
|
38
|
+
PAGEINDEX_PROVIDER = "pageindex"
|
|
39
|
+
PAGEINDEX_OSS_PROVIDER = "pageindex-oss"
|
|
40
|
+
GRAPHRAG_PROVIDER = "graphrag"
|
|
41
|
+
LIGHTRAG_PROVIDER = "lightrag"
|
|
42
|
+
LIGHTRAG_SERVER_PROVIDER = "lightrag-server"
|
|
43
|
+
IMA_PROVIDER = "ima"
|
|
44
|
+
WEKNORA_PROVIDER = "weknora"
|
|
45
|
+
KIWIX_PROVIDER = "kiwix"
|
|
46
|
+
|
|
47
|
+
# Providers the factory can instantiate. Unknown / legacy strings fall back to
|
|
48
|
+
# the default with a re-index hint upstream.
|
|
49
|
+
KNOWN_PROVIDERS = frozenset(
|
|
50
|
+
{
|
|
51
|
+
DEFAULT_PROVIDER,
|
|
52
|
+
PAGEINDEX_PROVIDER,
|
|
53
|
+
PAGEINDEX_OSS_PROVIDER,
|
|
54
|
+
GRAPHRAG_PROVIDER,
|
|
55
|
+
LIGHTRAG_PROVIDER,
|
|
56
|
+
LIGHTRAG_SERVER_PROVIDER,
|
|
57
|
+
IMA_PROVIDER,
|
|
58
|
+
WEKNORA_PROVIDER,
|
|
59
|
+
KIWIX_PROVIDER,
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Cached pipeline instances keyed by (kb_base_dir, provider).
|
|
64
|
+
_PIPELINE_CACHE: Dict[Tuple[Optional[str], str], Any] = {}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def normalize_provider_name(name: Optional[str] = None) -> str:
|
|
68
|
+
"""Return a known provider name, falling back to the default.
|
|
69
|
+
|
|
70
|
+
Unknown / removed provider strings collapse to the default so a stale config
|
|
71
|
+
never selects a pipeline that no longer exists.
|
|
72
|
+
"""
|
|
73
|
+
candidate = (name or "").strip().lower()
|
|
74
|
+
return candidate if candidate in KNOWN_PROVIDERS else DEFAULT_PROVIDER
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def provider_uses_embedding_versions(provider: Optional[str]) -> bool:
|
|
78
|
+
"""Whether this provider's index versions are keyed by embedding signature.
|
|
79
|
+
|
|
80
|
+
Today only the LlamaIndex pipeline uses DeepTutor's active embedding
|
|
81
|
+
signature to select/read index versions. PageIndex, GraphRAG and LightRAG
|
|
82
|
+
write synthetic provider signatures (``pageindex``/``graphrag``/``lightrag``)
|
|
83
|
+
and should not be marked stale merely because the active embedding profile
|
|
84
|
+
changed.
|
|
85
|
+
"""
|
|
86
|
+
return normalize_provider_name(provider) == DEFAULT_PROVIDER
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def version_matches_provider(entry: dict[str, Any], provider: Optional[str]) -> bool:
|
|
90
|
+
"""Return True when a version-list entry belongs to ``provider``."""
|
|
91
|
+
resolved = normalize_provider_name(provider)
|
|
92
|
+
entry_provider = str(entry.get("provider") or "").strip().lower()
|
|
93
|
+
signature = str(entry.get("signature") or "").strip().lower()
|
|
94
|
+
|
|
95
|
+
if resolved == DEFAULT_PROVIDER:
|
|
96
|
+
return entry_provider in {"", DEFAULT_PROVIDER} and signature not in {
|
|
97
|
+
PAGEINDEX_PROVIDER,
|
|
98
|
+
PAGEINDEX_OSS_PROVIDER,
|
|
99
|
+
GRAPHRAG_PROVIDER,
|
|
100
|
+
LIGHTRAG_PROVIDER,
|
|
101
|
+
LIGHTRAG_SERVER_PROVIDER,
|
|
102
|
+
IMA_PROVIDER,
|
|
103
|
+
WEKNORA_PROVIDER,
|
|
104
|
+
KIWIX_PROVIDER,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return entry_provider == resolved or signature == resolved
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def has_ready_provider_index(kb_dir: str | Path, provider: Optional[str]) -> bool:
|
|
111
|
+
"""Return whether ``kb_dir`` has a ready index for ``provider``."""
|
|
112
|
+
from .index_probe import has_ready_provider_index as _has_ready_provider_index
|
|
113
|
+
|
|
114
|
+
return _has_ready_provider_index(kb_dir, provider)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def version_has_provider_output(entry: dict[str, Any], provider: Optional[str]) -> bool:
|
|
118
|
+
"""Return True when a version entry is ready and has real provider output."""
|
|
119
|
+
from .index_probe import inspect_provider_version
|
|
120
|
+
|
|
121
|
+
return inspect_provider_version(entry, provider).ready
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def provider_failure_summary(
|
|
125
|
+
kb_dir: str | Path,
|
|
126
|
+
provider: Optional[str],
|
|
127
|
+
*,
|
|
128
|
+
limit: int = 3,
|
|
129
|
+
) -> str:
|
|
130
|
+
"""Return a short provider-specific failure summary, when available."""
|
|
131
|
+
from .index_probe import provider_failure_summary as _provider_failure_summary
|
|
132
|
+
|
|
133
|
+
return _provider_failure_summary(kb_dir, provider, limit=limit)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _build_pipeline(provider: str, kb_base_dir: Optional[str], **kwargs: Any):
|
|
137
|
+
if provider in {PAGEINDEX_PROVIDER, PAGEINDEX_OSS_PROVIDER}:
|
|
138
|
+
from .pipelines.pageindex.pipeline import PageIndexPipeline
|
|
139
|
+
|
|
140
|
+
if kb_base_dir is not None:
|
|
141
|
+
kwargs.setdefault("kb_base_dir", kb_base_dir)
|
|
142
|
+
return PageIndexPipeline(provider=provider, **kwargs)
|
|
143
|
+
|
|
144
|
+
if provider == GRAPHRAG_PROVIDER:
|
|
145
|
+
from .pipelines.graphrag.pipeline import GraphRagPipeline
|
|
146
|
+
|
|
147
|
+
if kb_base_dir is not None:
|
|
148
|
+
kwargs.setdefault("kb_base_dir", kb_base_dir)
|
|
149
|
+
return GraphRagPipeline(**kwargs)
|
|
150
|
+
|
|
151
|
+
if provider == LIGHTRAG_PROVIDER:
|
|
152
|
+
from .pipelines.lightrag.pipeline import LightRagPipeline
|
|
153
|
+
|
|
154
|
+
if kb_base_dir is not None:
|
|
155
|
+
kwargs.setdefault("kb_base_dir", kb_base_dir)
|
|
156
|
+
return LightRagPipeline(**kwargs)
|
|
157
|
+
|
|
158
|
+
if provider == LIGHTRAG_SERVER_PROVIDER:
|
|
159
|
+
from .pipelines.lightrag_server.pipeline import LightRagServerPipeline
|
|
160
|
+
|
|
161
|
+
if kb_base_dir is not None:
|
|
162
|
+
kwargs.setdefault("kb_base_dir", kb_base_dir)
|
|
163
|
+
return LightRagServerPipeline(**kwargs)
|
|
164
|
+
|
|
165
|
+
if provider == IMA_PROVIDER:
|
|
166
|
+
from .pipelines.ima.pipeline import ImaPipeline
|
|
167
|
+
|
|
168
|
+
if kb_base_dir is not None:
|
|
169
|
+
kwargs.setdefault("kb_base_dir", kb_base_dir)
|
|
170
|
+
return ImaPipeline(**kwargs)
|
|
171
|
+
|
|
172
|
+
if provider == WEKNORA_PROVIDER:
|
|
173
|
+
from .pipelines.weknora.pipeline import WeKnoraPipeline
|
|
174
|
+
|
|
175
|
+
if kb_base_dir is not None:
|
|
176
|
+
kwargs.setdefault("kb_base_dir", kb_base_dir)
|
|
177
|
+
return WeKnoraPipeline(**kwargs)
|
|
178
|
+
|
|
179
|
+
if provider == KIWIX_PROVIDER:
|
|
180
|
+
from .pipelines.kiwix.pipeline import KiwixPipeline
|
|
181
|
+
|
|
182
|
+
if kb_base_dir is not None:
|
|
183
|
+
kwargs.setdefault("kb_base_dir", kb_base_dir)
|
|
184
|
+
return KiwixPipeline(**kwargs)
|
|
185
|
+
|
|
186
|
+
from .pipelines.llamaindex.pipeline import LlamaIndexPipeline
|
|
187
|
+
|
|
188
|
+
if kb_base_dir is not None:
|
|
189
|
+
kwargs.setdefault("kb_base_dir", kb_base_dir)
|
|
190
|
+
return LlamaIndexPipeline(**kwargs)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def get_pipeline(
|
|
194
|
+
name: str = DEFAULT_PROVIDER,
|
|
195
|
+
kb_base_dir: Optional[str] = None,
|
|
196
|
+
**kwargs: Any,
|
|
197
|
+
):
|
|
198
|
+
"""Return a pipeline instance for ``name`` (cached when no custom kwargs)."""
|
|
199
|
+
provider = normalize_provider_name(name)
|
|
200
|
+
|
|
201
|
+
if kwargs:
|
|
202
|
+
# Custom kwargs (e.g. an injected client/loader): build a fresh instance
|
|
203
|
+
# and skip the cache so overrides are honoured.
|
|
204
|
+
return _build_pipeline(provider, kb_base_dir, **kwargs)
|
|
205
|
+
|
|
206
|
+
cache_key = (kb_base_dir, provider)
|
|
207
|
+
if cache_key not in _PIPELINE_CACHE:
|
|
208
|
+
_PIPELINE_CACHE[cache_key] = _build_pipeline(provider, kb_base_dir)
|
|
209
|
+
return _PIPELINE_CACHE[cache_key]
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def list_pipelines() -> List[Dict[str, Any]]:
|
|
213
|
+
"""Describe the available pipelines for the UI provider picker."""
|
|
214
|
+
try:
|
|
215
|
+
from .pipelines.pageindex.config import is_pageindex_configured
|
|
216
|
+
|
|
217
|
+
pageindex_ready = is_pageindex_configured()
|
|
218
|
+
except Exception:
|
|
219
|
+
pageindex_ready = False
|
|
220
|
+
|
|
221
|
+
try:
|
|
222
|
+
from .pipelines.pageindex.client import resolve_oss_sdk_config
|
|
223
|
+
|
|
224
|
+
resolve_oss_sdk_config()
|
|
225
|
+
pageindex_oss_ready, pageindex_oss_reason = True, ""
|
|
226
|
+
except Exception as exc:
|
|
227
|
+
pageindex_oss_ready = False
|
|
228
|
+
pageindex_oss_reason = str(exc)
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
from .pipelines.ima.config import is_ima_configured
|
|
232
|
+
|
|
233
|
+
ima_ready = is_ima_configured()
|
|
234
|
+
except Exception:
|
|
235
|
+
ima_ready = False
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
from .pipelines.graphrag import config as graphrag_config
|
|
239
|
+
|
|
240
|
+
graphrag_ready = graphrag_config.is_graphrag_available()
|
|
241
|
+
graphrag_modes = list(graphrag_config.SUPPORTED_MODES)
|
|
242
|
+
graphrag_default_mode = graphrag_config.DEFAULT_MODE
|
|
243
|
+
except Exception:
|
|
244
|
+
graphrag_ready, graphrag_modes, graphrag_default_mode = False, [], ""
|
|
245
|
+
|
|
246
|
+
try:
|
|
247
|
+
from .pipelines.lightrag import config as lightrag_config
|
|
248
|
+
|
|
249
|
+
lightrag_ready = lightrag_config.is_lightrag_available()
|
|
250
|
+
lightrag_modes = list(lightrag_config.SUPPORTED_MODES)
|
|
251
|
+
lightrag_default_mode = lightrag_config.DEFAULT_MODE
|
|
252
|
+
except Exception:
|
|
253
|
+
lightrag_ready, lightrag_modes, lightrag_default_mode = False, [], ""
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
from deeptutor.services.config import load_lightrag_server_settings
|
|
257
|
+
|
|
258
|
+
from .pipelines.lightrag_server import config as lightrag_server_config
|
|
259
|
+
|
|
260
|
+
lightrag_server_modes = list(lightrag_server_config.SUPPORTED_MODES)
|
|
261
|
+
lightrag_server_default_mode = lightrag_server_config.DEFAULT_MODE
|
|
262
|
+
lightrag_server_defaults_ready = bool(load_lightrag_server_settings().get("server_url"))
|
|
263
|
+
except Exception:
|
|
264
|
+
lightrag_server_modes, lightrag_server_default_mode = [], ""
|
|
265
|
+
lightrag_server_defaults_ready = False
|
|
266
|
+
|
|
267
|
+
return [
|
|
268
|
+
{
|
|
269
|
+
"id": DEFAULT_PROVIDER,
|
|
270
|
+
"name": "LlamaIndex",
|
|
271
|
+
"description": "Local vector retrieval with hybrid BM25/vector fusion. Works out of the box.",
|
|
272
|
+
"configured": True,
|
|
273
|
+
"requires_api_key": False,
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
"id": PAGEINDEX_PROVIDER,
|
|
277
|
+
"name": "PageIndex Cloud",
|
|
278
|
+
"description": "Hosted, vectorless engine: the chat agent reads documents through PageIndex SDK tools. Requires an API key; PDF, Office, text and Markdown formats.",
|
|
279
|
+
"configured": pageindex_ready,
|
|
280
|
+
"requires_api_key": True,
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
"id": PAGEINDEX_OSS_PROVIDER,
|
|
284
|
+
"name": "PageIndex OSS",
|
|
285
|
+
"description": "Local, chunkless and vectorless document indexing. Uses the active LLM and accepts PDF files.",
|
|
286
|
+
"configured": pageindex_oss_ready,
|
|
287
|
+
"requires_api_key": False,
|
|
288
|
+
"readiness_reason": pageindex_oss_reason,
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
"id": GRAPHRAG_PROVIDER,
|
|
292
|
+
"name": "GraphRAG",
|
|
293
|
+
"description": "Local knowledge-graph retrieval (global/local/drift/basic). Needs `pip install 'deeptutor[graphrag]'`; indexing is LLM-heavy.",
|
|
294
|
+
"configured": graphrag_ready,
|
|
295
|
+
"requires_api_key": False,
|
|
296
|
+
"modes": graphrag_modes,
|
|
297
|
+
"default_mode": graphrag_default_mode,
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
"id": LIGHTRAG_PROVIDER,
|
|
301
|
+
"name": "LightRAG",
|
|
302
|
+
"description": "Graph + vector retrieval with multimodal parsing (naive/local/global/hybrid/mix). Needs `pip install 'deeptutor[rag-lightrag]'`; indexing is LLM-heavy.",
|
|
303
|
+
"configured": lightrag_ready,
|
|
304
|
+
"requires_api_key": False,
|
|
305
|
+
"modes": lightrag_modes,
|
|
306
|
+
"default_mode": lightrag_default_mode,
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
"id": LIGHTRAG_SERVER_PROVIDER,
|
|
310
|
+
"name": "LightRAG Server",
|
|
311
|
+
"description": "Retrieval offloaded to an external, standalone LightRAG server you run. No local index — connect a KB to its URL and query it over HTTP (naive/local/global/hybrid/mix).",
|
|
312
|
+
# Always available: it's a thin HTTP client with no install or global
|
|
313
|
+
# credential. The endpoint is configured per-KB at connect time.
|
|
314
|
+
"configured": True,
|
|
315
|
+
"requires_api_key": False,
|
|
316
|
+
"setup_required": not lightrag_server_defaults_ready,
|
|
317
|
+
"modes": lightrag_server_modes,
|
|
318
|
+
"default_mode": lightrag_server_default_mode,
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
"id": IMA_PROVIDER,
|
|
322
|
+
"name": "Tencent IMA",
|
|
323
|
+
"description": (
|
|
324
|
+
"Retrieval offloaded to a knowledge base you keep in Tencent IMA. "
|
|
325
|
+
"No local index and no copy — connect a KB to its IMA library and "
|
|
326
|
+
"query it over IMA's OpenAPI. Chat can also browse the library's "
|
|
327
|
+
"documents, read a full source, search your IMA notes, and (when "
|
|
328
|
+
"you ask) collect a web page or save a note. Uploading files still "
|
|
329
|
+
"happens in IMA itself. Requires an IMA Client ID and API key."
|
|
330
|
+
),
|
|
331
|
+
# A thin HTTPS client with no install; readiness is only about the
|
|
332
|
+
# account credentials. The library id stays per-KB, set at connect
|
|
333
|
+
# time, and a KB may pin its own credentials to reach another account.
|
|
334
|
+
"configured": ima_ready,
|
|
335
|
+
"requires_api_key": True,
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
"id": WEKNORA_PROVIDER,
|
|
339
|
+
"name": "WeKnora",
|
|
340
|
+
"description": (
|
|
341
|
+
"Retrieval offloaded to a knowledge base in your self-hosted "
|
|
342
|
+
"WeKnora deployment. No local index or document copy; connect "
|
|
343
|
+
"to its API URL, API key, and knowledge-base ID."
|
|
344
|
+
),
|
|
345
|
+
"configured": True,
|
|
346
|
+
"requires_api_key": True,
|
|
347
|
+
},
|
|
348
|
+
]
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
__all__ = [
|
|
352
|
+
"DEFAULT_PROVIDER",
|
|
353
|
+
"PAGEINDEX_PROVIDER",
|
|
354
|
+
"PAGEINDEX_OSS_PROVIDER",
|
|
355
|
+
"GRAPHRAG_PROVIDER",
|
|
356
|
+
"LIGHTRAG_PROVIDER",
|
|
357
|
+
"LIGHTRAG_SERVER_PROVIDER",
|
|
358
|
+
"IMA_PROVIDER",
|
|
359
|
+
"WEKNORA_PROVIDER",
|
|
360
|
+
"KNOWN_PROVIDERS",
|
|
361
|
+
"get_pipeline",
|
|
362
|
+
"has_ready_provider_index",
|
|
363
|
+
"list_pipelines",
|
|
364
|
+
"normalize_provider_name",
|
|
365
|
+
"provider_failure_summary",
|
|
366
|
+
"provider_uses_embedding_versions",
|
|
367
|
+
"version_has_provider_output",
|
|
368
|
+
"version_matches_provider",
|
|
369
|
+
]
|