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,41 @@
|
|
|
1
|
+
"""Structural contract every RAG pipeline implements.
|
|
2
|
+
|
|
3
|
+
The RAG service and factory have always relied on duck typing across pipelines
|
|
4
|
+
(``initialize`` / ``add_documents`` / ``search`` / ``delete``). This Protocol
|
|
5
|
+
makes that contract explicit so a new engine — e.g. the PageIndex cloud
|
|
6
|
+
pipeline — can be type-checked against the same shape the LlamaIndex pipeline
|
|
7
|
+
already satisfies. It is intentionally minimal: ``RAGService`` still probes for
|
|
8
|
+
optional methods with ``hasattr`` to stay tolerant of partial implementations.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import Any, Dict, List, Protocol, runtime_checkable
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@runtime_checkable
|
|
17
|
+
class RAGPipeline(Protocol):
|
|
18
|
+
"""A knowledge-base index/retrieve engine bound to a KB by its provider."""
|
|
19
|
+
|
|
20
|
+
async def initialize(self, kb_name: str, file_paths: List[str], **kwargs: Any) -> bool:
|
|
21
|
+
"""Build a fresh index for ``kb_name`` from ``file_paths``."""
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
async def add_documents(self, kb_name: str, file_paths: List[str], **kwargs: Any) -> bool:
|
|
25
|
+
"""Incrementally add ``file_paths`` to ``kb_name``'s existing index."""
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
async def search(self, query: str, kb_name: str, **kwargs: Any) -> Dict[str, Any]:
|
|
29
|
+
"""Retrieve grounded context for ``query`` from ``kb_name``.
|
|
30
|
+
|
|
31
|
+
Returns a dict with at least ``query``, ``content``/``answer``,
|
|
32
|
+
``sources`` and ``provider`` keys.
|
|
33
|
+
"""
|
|
34
|
+
...
|
|
35
|
+
|
|
36
|
+
async def delete(self, kb_name: str, **kwargs: Any) -> bool:
|
|
37
|
+
"""Delete ``kb_name`` and any engine-side resources."""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
__all__ = ["RAGPipeline"]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""GraphRAG (microsoft/graphrag) local RAG pipeline.
|
|
2
|
+
|
|
3
|
+
A KB indexed with the ``graphrag`` provider builds a local knowledge graph
|
|
4
|
+
(entities, relationships, communities, community reports) from text and serves
|
|
5
|
+
GraphRAG's global/local/drift/basic retrieval. DeepTutor parses documents to
|
|
6
|
+
text first and bridges its own LLM/embedding config into GraphRAG's settings, so
|
|
7
|
+
GraphRAG never parses documents or owns model credentials of its own.
|
|
8
|
+
|
|
9
|
+
GraphRAG is an optional dependency (``pip install 'deeptutor[graphrag]'``). The
|
|
10
|
+
pipeline module imports cleanly without it installed; only actual indexing /
|
|
11
|
+
retrieval requires the package.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from .pipeline import GraphRagPipeline
|
|
17
|
+
|
|
18
|
+
__all__ = ["GraphRagPipeline"]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Resolve and test GraphRAG completion-model candidates without activating them."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from deeptutor.services.config import (
|
|
6
|
+
get_model_catalog_service,
|
|
7
|
+
resolve_llm_runtime_config,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from . import engine
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def probe_configured_completion_model(profile_id: str, model_id: str) -> dict:
|
|
14
|
+
"""Probe one configured model by ID while leaving the active catalog unchanged.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
profile_id: Server-side model-catalog profile ID.
|
|
18
|
+
model_id: Server-side model ID within the selected profile.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
A secret-free GraphRAG compatibility result.
|
|
22
|
+
|
|
23
|
+
Raises:
|
|
24
|
+
ValueError: If the profile/model selection does not exist.
|
|
25
|
+
"""
|
|
26
|
+
service = get_model_catalog_service()
|
|
27
|
+
llm_cfg = resolve_llm_runtime_config(
|
|
28
|
+
catalog=service.load(),
|
|
29
|
+
service=service,
|
|
30
|
+
llm_selection={"profile_id": profile_id, "model_id": model_id},
|
|
31
|
+
)
|
|
32
|
+
return await engine.probe_completion_model(llm_cfg)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def probe_active_completion_model() -> dict:
|
|
36
|
+
"""Probe the globally active chat model without changing catalog state."""
|
|
37
|
+
service = get_model_catalog_service()
|
|
38
|
+
llm_cfg = resolve_llm_runtime_config(catalog=service.load(), service=service)
|
|
39
|
+
return await engine.probe_completion_model(llm_cfg)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
__all__ = ["probe_active_completion_model", "probe_configured_completion_model"]
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""GraphRAG completion adapter with a strict JSON-object compatibility fallback."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
import json
|
|
7
|
+
import threading
|
|
8
|
+
from typing import Any
|
|
9
|
+
from urllib.parse import urlparse
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, ValidationError
|
|
12
|
+
|
|
13
|
+
from .errors import (
|
|
14
|
+
MODEL_INCOMPATIBLE_MESSAGE,
|
|
15
|
+
MODEL_OUTPUT_TRUNCATED_MESSAGE,
|
|
16
|
+
GraphRagStructuredOutputError,
|
|
17
|
+
GraphRagStructuredOutputTruncatedError,
|
|
18
|
+
is_unsupported_schema_error,
|
|
19
|
+
)
|
|
20
|
+
from .provider import COMPLETION_TYPE
|
|
21
|
+
|
|
22
|
+
_CAPABILITY_LOCK = threading.RLock()
|
|
23
|
+
_JSON_OBJECT_CAPABILITIES: set[tuple[str, str, str]] = set()
|
|
24
|
+
_ADAPTER_CLASS: type | None = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _capability_key(model_config: Any) -> tuple[str, str, str]:
|
|
28
|
+
return (
|
|
29
|
+
str(getattr(model_config, "model_provider", "") or "").lower(),
|
|
30
|
+
str(getattr(model_config, "model", "") or "").lower(),
|
|
31
|
+
str(getattr(model_config, "api_base", "") or "").rstrip("/").lower(),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _uses_json_object(model_config: Any) -> bool:
|
|
36
|
+
with _CAPABILITY_LOCK:
|
|
37
|
+
return _capability_key(model_config) in _JSON_OBJECT_CAPABILITIES
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _remember_json_object(model_config: Any) -> None:
|
|
41
|
+
with _CAPABILITY_LOCK:
|
|
42
|
+
_JSON_OBJECT_CAPABILITIES.add(_capability_key(model_config))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _uses_prompt_only_structured_output(model_config: Any) -> bool:
|
|
46
|
+
"""Use the common messages contract for third-party Anthropic-compatible APIs.
|
|
47
|
+
|
|
48
|
+
LiteLLM implements Pydantic response formats on the Anthropic transport with
|
|
49
|
+
a forced synthetic tool. Third-party endpoints may implement Anthropic's
|
|
50
|
+
messages API without matching those tool semantics. A schema instruction
|
|
51
|
+
plus local Pydantic validation keeps the GraphRAG contract strict while
|
|
52
|
+
avoiding assumptions beyond the configured endpoint's base protocol.
|
|
53
|
+
"""
|
|
54
|
+
provider = str(getattr(model_config, "model_provider", "") or "").lower()
|
|
55
|
+
if provider != "anthropic":
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
api_base = str(getattr(model_config, "api_base", "") or "").strip()
|
|
59
|
+
if not api_base:
|
|
60
|
+
return False
|
|
61
|
+
parsed = urlparse(api_base if "://" in api_base else f"https://{api_base}")
|
|
62
|
+
hostname = (parsed.hostname or "").lower().rstrip(".")
|
|
63
|
+
return hostname != "api.anthropic.com" and not hostname.endswith(".anthropic.com")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def clear_capability_cache() -> None:
|
|
67
|
+
"""Clear process-local compatibility discoveries; intended for deterministic tests."""
|
|
68
|
+
with _CAPABILITY_LOCK:
|
|
69
|
+
_JSON_OBJECT_CAPABILITIES.clear()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _is_schema_model(response_format: Any) -> bool:
|
|
73
|
+
return isinstance(response_format, type) and issubclass(response_format, BaseModel)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _schema_instruction(response_format: type[BaseModel]) -> str:
|
|
77
|
+
schema = json.dumps(
|
|
78
|
+
response_format.model_json_schema(),
|
|
79
|
+
ensure_ascii=False,
|
|
80
|
+
separators=(",", ":"),
|
|
81
|
+
)
|
|
82
|
+
return (
|
|
83
|
+
"Return only one valid JSON object. Do not use Markdown fences or explanatory text. "
|
|
84
|
+
f"The JSON object must match this JSON schema exactly: {schema}"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _messages_with_schema(messages: Any, response_format: type[BaseModel]) -> Any:
|
|
89
|
+
instruction = _schema_instruction(response_format)
|
|
90
|
+
if isinstance(messages, str):
|
|
91
|
+
return f"{messages}\n\n{instruction}"
|
|
92
|
+
if not isinstance(messages, list):
|
|
93
|
+
return messages
|
|
94
|
+
|
|
95
|
+
copied = list(messages)
|
|
96
|
+
for index in range(len(copied) - 1, -1, -1):
|
|
97
|
+
message = copied[index]
|
|
98
|
+
if not isinstance(message, dict) or message.get("role") != "user":
|
|
99
|
+
continue
|
|
100
|
+
content = message.get("content")
|
|
101
|
+
if isinstance(content, str):
|
|
102
|
+
copied[index] = {**message, "content": f"{content}\n\n{instruction}"}
|
|
103
|
+
return copied
|
|
104
|
+
copied.append({"role": "user", "content": instruction})
|
|
105
|
+
return copied
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _format_fallback_kwargs(kwargs: dict[str, Any], response_format: type[BaseModel]) -> dict:
|
|
109
|
+
fallback = dict(kwargs)
|
|
110
|
+
fallback["messages"] = _messages_with_schema(fallback["messages"], response_format)
|
|
111
|
+
fallback.pop("response_format", None)
|
|
112
|
+
return fallback
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _format_response(response: Any, response_format: type[BaseModel]) -> Any:
|
|
116
|
+
from graphrag_llm.utils import structure_completion_response
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
response.formatted_response = structure_completion_response(
|
|
120
|
+
response.content,
|
|
121
|
+
response_format,
|
|
122
|
+
)
|
|
123
|
+
except (json.JSONDecodeError, TypeError, ValidationError, ValueError) as error:
|
|
124
|
+
choices = getattr(response, "choices", None)
|
|
125
|
+
finish_reason = getattr(choices[0], "finish_reason", None) if choices else None
|
|
126
|
+
if finish_reason in {"length", "max_tokens"}:
|
|
127
|
+
raise GraphRagStructuredOutputTruncatedError(MODEL_OUTPUT_TRUNCATED_MESSAGE) from error
|
|
128
|
+
raise GraphRagStructuredOutputError(MODEL_INCOMPATIBLE_MESSAGE) from error
|
|
129
|
+
return response
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _native_validation_error(error: BaseException) -> bool:
|
|
133
|
+
return isinstance(error, (json.JSONDecodeError, ValidationError))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _fallback_sync(instance: Any, kwargs: dict[str, Any], response_format: type[BaseModel]) -> Any:
|
|
137
|
+
fallback = _format_fallback_kwargs(kwargs, response_format)
|
|
138
|
+
if fallback.get("stream"):
|
|
139
|
+
raise ValueError("response_format is not supported for streaming completions.")
|
|
140
|
+
messages = fallback.pop("messages")
|
|
141
|
+
if isinstance(messages, str):
|
|
142
|
+
messages = [{"role": "user", "content": messages}]
|
|
143
|
+
request_metrics = fallback.pop("metrics", None) or {}
|
|
144
|
+
if not instance._track_metrics:
|
|
145
|
+
request_metrics = None
|
|
146
|
+
try:
|
|
147
|
+
response = instance._completion(
|
|
148
|
+
messages=messages,
|
|
149
|
+
metrics=request_metrics,
|
|
150
|
+
response_format_json_object=not _uses_prompt_only_structured_output(
|
|
151
|
+
instance._model_config
|
|
152
|
+
),
|
|
153
|
+
**fallback,
|
|
154
|
+
)
|
|
155
|
+
return _format_response(response, response_format)
|
|
156
|
+
finally:
|
|
157
|
+
if request_metrics is not None:
|
|
158
|
+
instance._metrics_store.update_metrics(metrics=request_metrics)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
async def _fallback_async(
|
|
162
|
+
instance: Any,
|
|
163
|
+
kwargs: dict[str, Any],
|
|
164
|
+
response_format: type[BaseModel],
|
|
165
|
+
) -> Any:
|
|
166
|
+
fallback = _format_fallback_kwargs(kwargs, response_format)
|
|
167
|
+
if fallback.get("stream"):
|
|
168
|
+
raise ValueError("response_format is not supported for streaming completions.")
|
|
169
|
+
messages = fallback.pop("messages")
|
|
170
|
+
if isinstance(messages, str):
|
|
171
|
+
messages = [{"role": "user", "content": messages}]
|
|
172
|
+
request_metrics = fallback.pop("metrics", None) or {}
|
|
173
|
+
if not instance._track_metrics:
|
|
174
|
+
request_metrics = None
|
|
175
|
+
try:
|
|
176
|
+
response = await instance._completion_async(
|
|
177
|
+
messages=messages,
|
|
178
|
+
metrics=request_metrics,
|
|
179
|
+
response_format_json_object=not _uses_prompt_only_structured_output(
|
|
180
|
+
instance._model_config
|
|
181
|
+
),
|
|
182
|
+
**fallback,
|
|
183
|
+
)
|
|
184
|
+
return _format_response(response, response_format)
|
|
185
|
+
finally:
|
|
186
|
+
if request_metrics is not None:
|
|
187
|
+
instance._metrics_store.update_metrics(metrics=request_metrics)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _get_adapter_class() -> type:
|
|
191
|
+
global _ADAPTER_CLASS
|
|
192
|
+
with _CAPABILITY_LOCK:
|
|
193
|
+
if _ADAPTER_CLASS is not None:
|
|
194
|
+
return _ADAPTER_CLASS
|
|
195
|
+
|
|
196
|
+
from graphrag_llm.completion.lite_llm_completion import LiteLLMCompletion
|
|
197
|
+
|
|
198
|
+
class DeepTutorLiteLLMCompletion(LiteLLMCompletion):
|
|
199
|
+
"""LiteLLM completion with a narrow GraphRAG structured-output fallback."""
|
|
200
|
+
|
|
201
|
+
def completion(self, /, **kwargs: Any) -> Any:
|
|
202
|
+
response_format = kwargs.get("response_format")
|
|
203
|
+
if not _is_schema_model(response_format):
|
|
204
|
+
return super().completion(**kwargs)
|
|
205
|
+
if _uses_prompt_only_structured_output(self._model_config):
|
|
206
|
+
return _fallback_sync(self, kwargs, response_format)
|
|
207
|
+
if _uses_json_object(self._model_config):
|
|
208
|
+
return _fallback_sync(self, kwargs, response_format)
|
|
209
|
+
try:
|
|
210
|
+
return super().completion(**kwargs)
|
|
211
|
+
except Exception as error:
|
|
212
|
+
explicit_unsupported = is_unsupported_schema_error(error)
|
|
213
|
+
if not explicit_unsupported and not _native_validation_error(error):
|
|
214
|
+
raise
|
|
215
|
+
formatted = _fallback_sync(self, kwargs, response_format)
|
|
216
|
+
if explicit_unsupported:
|
|
217
|
+
_remember_json_object(self._model_config)
|
|
218
|
+
return formatted
|
|
219
|
+
|
|
220
|
+
async def completion_async(self, /, **kwargs: Any) -> Any:
|
|
221
|
+
response_format = kwargs.get("response_format")
|
|
222
|
+
if not _is_schema_model(response_format):
|
|
223
|
+
return await super().completion_async(**kwargs)
|
|
224
|
+
if _uses_prompt_only_structured_output(self._model_config):
|
|
225
|
+
return await _fallback_async(self, kwargs, response_format)
|
|
226
|
+
if _uses_json_object(self._model_config):
|
|
227
|
+
return await _fallback_async(self, kwargs, response_format)
|
|
228
|
+
try:
|
|
229
|
+
return await super().completion_async(**kwargs)
|
|
230
|
+
except Exception as error:
|
|
231
|
+
explicit_unsupported = is_unsupported_schema_error(error)
|
|
232
|
+
if not explicit_unsupported and not _native_validation_error(error):
|
|
233
|
+
raise
|
|
234
|
+
formatted = await _fallback_async(self, kwargs, response_format)
|
|
235
|
+
if explicit_unsupported:
|
|
236
|
+
_remember_json_object(self._model_config)
|
|
237
|
+
return formatted
|
|
238
|
+
|
|
239
|
+
_ADAPTER_CLASS = DeepTutorLiteLLMCompletion
|
|
240
|
+
return _ADAPTER_CLASS
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def register_completion_adapter() -> None:
|
|
244
|
+
"""Register DeepTutor's completion type through GraphRAG's public factory API."""
|
|
245
|
+
from graphrag_llm.completion import register_completion
|
|
246
|
+
|
|
247
|
+
initializer: Callable[..., Any] = _get_adapter_class()
|
|
248
|
+
register_completion(COMPLETION_TYPE, initializer, scope="singleton")
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
__all__ = ["clear_capability_cache", "register_completion_adapter"]
|