smart-knowledge 1.1.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.
- smart_knowledge/__init__.py +90 -0
- smart_knowledge/client.py +88 -0
- smart_knowledge/config.py +63 -0
- smart_knowledge/errors.py +130 -0
- smart_knowledge/http.py +163 -0
- smart_knowledge/modules/__init__.py +13 -0
- smart_knowledge/modules/agentask.py +203 -0
- smart_knowledge/modules/ask.py +64 -0
- smart_knowledge/modules/documents.py +165 -0
- smart_knowledge/modules/search.py +68 -0
- smart_knowledge/py.typed +1 -0
- smart_knowledge/types/__init__.py +51 -0
- smart_knowledge/types/agentask.py +112 -0
- smart_knowledge/types/ask.py +57 -0
- smart_knowledge/types/common.py +46 -0
- smart_knowledge/types/documents.py +147 -0
- smart_knowledge/types/search.py +94 -0
- smart_knowledge-1.1.0.dist-info/METADATA +229 -0
- smart_knowledge-1.1.0.dist-info/RECORD +22 -0
- smart_knowledge-1.1.0.dist-info/WHEEL +5 -0
- smart_knowledge-1.1.0.dist-info/licenses/LICENSE +21 -0
- smart_knowledge-1.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Official Python 3.10+ SDK for the Smart Knowledge 5-Brain Autonomous Platform.
|
|
2
|
+
|
|
3
|
+
Provides direct programmatic access to:
|
|
4
|
+
- ask(): Grounded 5-Brain RAG Question Answering with primary citations.
|
|
5
|
+
- search(): Multi-Modal Hybrid Search across Knowledge Graph, BM25, and semantic vectors.
|
|
6
|
+
- agentask: Autonomous Problem Solver (PAOA) multi-step reasoning missions.
|
|
7
|
+
- documents: Enterprise document ingestion pipeline with full pagination.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .client import SmartKnowledge
|
|
11
|
+
from .config import ResolvedConfig
|
|
12
|
+
from .errors import (
|
|
13
|
+
APIConnectionError,
|
|
14
|
+
AuthenticationError,
|
|
15
|
+
InternalServerError,
|
|
16
|
+
NotFoundError,
|
|
17
|
+
PermissionDeniedError,
|
|
18
|
+
QuotaExceededError,
|
|
19
|
+
RateLimitError,
|
|
20
|
+
SmartKnowledgeError,
|
|
21
|
+
ValidationError,
|
|
22
|
+
)
|
|
23
|
+
from .types import (
|
|
24
|
+
AgentTaskProgress,
|
|
25
|
+
AgentaskDispatchOptions,
|
|
26
|
+
AgentaskDispatchResponse,
|
|
27
|
+
AgentaskResult,
|
|
28
|
+
AgentaskRunOptions,
|
|
29
|
+
AgentaskStatusResponse,
|
|
30
|
+
AskOptions,
|
|
31
|
+
AskResponse,
|
|
32
|
+
Citation,
|
|
33
|
+
ClientOptions,
|
|
34
|
+
DocumentStatusResponse,
|
|
35
|
+
DocumentUploadOptions,
|
|
36
|
+
DocumentUploadResponse,
|
|
37
|
+
FileListOptions,
|
|
38
|
+
FileRecord,
|
|
39
|
+
FileUploadInput,
|
|
40
|
+
FinOpsMetrics,
|
|
41
|
+
GraphTriplet,
|
|
42
|
+
PaginatedFilesResponse,
|
|
43
|
+
PaginationMeta,
|
|
44
|
+
RequestOptions,
|
|
45
|
+
SearchOptions,
|
|
46
|
+
SearchResponse,
|
|
47
|
+
VectorHit,
|
|
48
|
+
Bm25Hit,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
__version__ = "1.1.0"
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
"SmartKnowledge",
|
|
55
|
+
"ResolvedConfig",
|
|
56
|
+
"SmartKnowledgeError",
|
|
57
|
+
"AuthenticationError",
|
|
58
|
+
"PermissionDeniedError",
|
|
59
|
+
"NotFoundError",
|
|
60
|
+
"ValidationError",
|
|
61
|
+
"RateLimitError",
|
|
62
|
+
"QuotaExceededError",
|
|
63
|
+
"InternalServerError",
|
|
64
|
+
"APIConnectionError",
|
|
65
|
+
"ClientOptions",
|
|
66
|
+
"FinOpsMetrics",
|
|
67
|
+
"RequestOptions",
|
|
68
|
+
"AskOptions",
|
|
69
|
+
"AskResponse",
|
|
70
|
+
"Citation",
|
|
71
|
+
"SearchOptions",
|
|
72
|
+
"SearchResponse",
|
|
73
|
+
"GraphTriplet",
|
|
74
|
+
"VectorHit",
|
|
75
|
+
"Bm25Hit",
|
|
76
|
+
"AgentTaskProgress",
|
|
77
|
+
"AgentaskDispatchOptions",
|
|
78
|
+
"AgentaskDispatchResponse",
|
|
79
|
+
"AgentaskStatusResponse",
|
|
80
|
+
"AgentaskRunOptions",
|
|
81
|
+
"AgentaskResult",
|
|
82
|
+
"FileUploadInput",
|
|
83
|
+
"FileRecord",
|
|
84
|
+
"DocumentUploadOptions",
|
|
85
|
+
"DocumentUploadResponse",
|
|
86
|
+
"DocumentStatusResponse",
|
|
87
|
+
"PaginationMeta",
|
|
88
|
+
"FileListOptions",
|
|
89
|
+
"PaginatedFilesResponse",
|
|
90
|
+
]
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""SmartKnowledge primary client class for Python SDK."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional, Union
|
|
4
|
+
from .config import ResolvedConfig, resolve_config
|
|
5
|
+
from .http import HttpClient
|
|
6
|
+
from .modules.agentask import AgentaskModule
|
|
7
|
+
from .modules.ask import AskModule
|
|
8
|
+
from .modules.documents import DocumentsModule
|
|
9
|
+
from .modules.search import SearchModule
|
|
10
|
+
from .types.ask import AskOptions, AskResponse
|
|
11
|
+
from .types.search import SearchOptions, SearchResponse
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SmartKnowledge:
|
|
15
|
+
"""Official Python client for the Smart Knowledge 5-Brain Autonomous Platform."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
api_key: Optional[str] = None,
|
|
20
|
+
base_url: Optional[str] = None,
|
|
21
|
+
tenant_id: Optional[str] = None,
|
|
22
|
+
timeout: Optional[float] = None,
|
|
23
|
+
max_retries: Optional[int] = None,
|
|
24
|
+
default_headers: Optional[Dict[str, str]] = None,
|
|
25
|
+
) -> None:
|
|
26
|
+
"""Initialize the Smart Knowledge client.
|
|
27
|
+
|
|
28
|
+
:param api_key: Smart Knowledge API key (defaults to SMART_KNOWLEDGE_API_KEY environment variable).
|
|
29
|
+
:param base_url: Base URL of the API gateway (defaults to SMART_KNOWLEDGE_BASE_URL or 'https://ztrust.eu').
|
|
30
|
+
:param tenant_id: Target tenant workspace identifier (defaults to SMART_KNOWLEDGE_TENANT_ID or 'default').
|
|
31
|
+
:param timeout: Default request timeout in seconds (defaults to 60.0).
|
|
32
|
+
:param max_retries: Maximum automatic retry attempts on 429/5xx status codes (defaults to 2).
|
|
33
|
+
:param default_headers: Optional dictionary of headers to attach to every outgoing request.
|
|
34
|
+
"""
|
|
35
|
+
self.config: ResolvedConfig = resolve_config(
|
|
36
|
+
api_key=api_key,
|
|
37
|
+
base_url=base_url,
|
|
38
|
+
tenant_id=tenant_id,
|
|
39
|
+
timeout=timeout,
|
|
40
|
+
max_retries=max_retries,
|
|
41
|
+
default_headers=default_headers,
|
|
42
|
+
)
|
|
43
|
+
self.http = HttpClient(self.config)
|
|
44
|
+
|
|
45
|
+
self._ask_module = AskModule(self.http)
|
|
46
|
+
self._search_module = SearchModule(self.http)
|
|
47
|
+
|
|
48
|
+
# Autonomous Problem Solver (PAOA) module
|
|
49
|
+
self.agentask = AgentaskModule(self.http)
|
|
50
|
+
|
|
51
|
+
# Enterprise Document Ingestion and Library module
|
|
52
|
+
self.documents = DocumentsModule(self.http)
|
|
53
|
+
|
|
54
|
+
def ask(
|
|
55
|
+
self,
|
|
56
|
+
question_or_options: Optional[Union[str, AskOptions, Dict[str, Any]]] = None,
|
|
57
|
+
question: Optional[str] = None,
|
|
58
|
+
include_sources: bool = True,
|
|
59
|
+
timeout: Optional[float] = None,
|
|
60
|
+
options: Optional[Union[str, AskOptions, Dict[str, Any]]] = None,
|
|
61
|
+
) -> AskResponse:
|
|
62
|
+
"""Ask a question verified and grounded across the 5-Brain architecture."""
|
|
63
|
+
return self._ask_module.ask(
|
|
64
|
+
question_or_options=question_or_options,
|
|
65
|
+
question=question,
|
|
66
|
+
include_sources=include_sources,
|
|
67
|
+
timeout=timeout,
|
|
68
|
+
options=options,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def search(
|
|
72
|
+
self,
|
|
73
|
+
query_or_options: Optional[Union[str, SearchOptions, Dict[str, Any]]] = None,
|
|
74
|
+
query: Optional[str] = None,
|
|
75
|
+
decompose: bool = False,
|
|
76
|
+
limit: int = 10,
|
|
77
|
+
timeout: Optional[float] = None,
|
|
78
|
+
options: Optional[Union[str, SearchOptions, Dict[str, Any]]] = None,
|
|
79
|
+
) -> SearchResponse:
|
|
80
|
+
"""Multi-modal retrieval across Knowledge Graph triplets, BM25 keyword index, and semantic vectors."""
|
|
81
|
+
return self._search_module.search(
|
|
82
|
+
query_or_options=query_or_options,
|
|
83
|
+
query=query,
|
|
84
|
+
decompose=decompose,
|
|
85
|
+
limit=limit,
|
|
86
|
+
timeout=timeout,
|
|
87
|
+
options=options,
|
|
88
|
+
)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Configuration resolver for the Smart Knowledge Python SDK."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Dict, Optional
|
|
6
|
+
from .errors import ValidationError
|
|
7
|
+
|
|
8
|
+
DEFAULT_BASE_URL = "https://ztrust.eu"
|
|
9
|
+
DEFAULT_TENANT_ID = "default"
|
|
10
|
+
DEFAULT_TIMEOUT_SEC = 60.0
|
|
11
|
+
DEFAULT_MAX_RETRIES = 2
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class ResolvedConfig:
|
|
16
|
+
api_key: str
|
|
17
|
+
base_url: str = DEFAULT_BASE_URL
|
|
18
|
+
tenant_id: str = DEFAULT_TENANT_ID
|
|
19
|
+
timeout: float = DEFAULT_TIMEOUT_SEC
|
|
20
|
+
max_retries: int = DEFAULT_MAX_RETRIES
|
|
21
|
+
default_headers: Dict[str, str] = field(default_factory=dict)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def resolve_config(
|
|
25
|
+
api_key: Optional[str] = None,
|
|
26
|
+
base_url: Optional[str] = None,
|
|
27
|
+
tenant_id: Optional[str] = None,
|
|
28
|
+
timeout: Optional[float] = None,
|
|
29
|
+
max_retries: Optional[int] = None,
|
|
30
|
+
default_headers: Optional[Dict[str, str]] = None,
|
|
31
|
+
) -> ResolvedConfig:
|
|
32
|
+
"""Resolve and validate configuration options with environment fallbacks."""
|
|
33
|
+
|
|
34
|
+
resolved_key = (api_key or os.environ.get("SMART_KNOWLEDGE_API_KEY") or "").strip()
|
|
35
|
+
if not resolved_key:
|
|
36
|
+
raise ValidationError(
|
|
37
|
+
"Missing required API key. Pass 'api_key' to SmartKnowledge() or export SMART_KNOWLEDGE_API_KEY."
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
raw_base_url = (base_url or os.environ.get("SMART_KNOWLEDGE_BASE_URL") or DEFAULT_BASE_URL).strip()
|
|
41
|
+
resolved_base_url = raw_base_url.rstrip("/")
|
|
42
|
+
|
|
43
|
+
resolved_tenant = (tenant_id or os.environ.get("SMART_KNOWLEDGE_TENANT_ID") or DEFAULT_TENANT_ID).strip()
|
|
44
|
+
resolved_timeout = float(timeout if timeout is not None else DEFAULT_TIMEOUT_SEC)
|
|
45
|
+
resolved_retries = int(max_retries if max_retries is not None else DEFAULT_MAX_RETRIES)
|
|
46
|
+
|
|
47
|
+
headers = {
|
|
48
|
+
"Authorization": f"Bearer {resolved_key}",
|
|
49
|
+
"X-API-Key": resolved_key,
|
|
50
|
+
"X-Tenant-Id": resolved_tenant,
|
|
51
|
+
"User-Agent": "smart-knowledge-python-sdk/1.1.0",
|
|
52
|
+
}
|
|
53
|
+
if default_headers:
|
|
54
|
+
headers.update(default_headers)
|
|
55
|
+
|
|
56
|
+
return ResolvedConfig(
|
|
57
|
+
api_key=resolved_key,
|
|
58
|
+
base_url=resolved_base_url,
|
|
59
|
+
tenant_id=resolved_tenant,
|
|
60
|
+
timeout=resolved_timeout,
|
|
61
|
+
max_retries=resolved_retries,
|
|
62
|
+
default_headers=headers,
|
|
63
|
+
)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Custom typed error hierarchy for the Smart Knowledge SDK."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SmartKnowledgeError(Exception):
|
|
7
|
+
"""Base exception class for all Smart Knowledge errors."""
|
|
8
|
+
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
message: str,
|
|
12
|
+
status_code: Optional[int] = None,
|
|
13
|
+
response_body: Optional[Any] = None,
|
|
14
|
+
headers: Optional[Dict[str, str]] = None,
|
|
15
|
+
) -> None:
|
|
16
|
+
super().__init__(message)
|
|
17
|
+
self.message = message
|
|
18
|
+
self.status_code = status_code
|
|
19
|
+
self.response_body = response_body
|
|
20
|
+
self.headers = headers or {}
|
|
21
|
+
|
|
22
|
+
def __str__(self) -> str:
|
|
23
|
+
if self.status_code:
|
|
24
|
+
return f"[{self.__class__.__name__} HTTP {self.status_code}] {self.message}"
|
|
25
|
+
return f"[{self.__class__.__name__}] {self.message}"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AuthenticationError(SmartKnowledgeError):
|
|
29
|
+
"""Raised when authentication fails (HTTP 401: Invalid or expired API key)."""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
message: str = "Invalid, revoked, or missing Smart Knowledge API key.",
|
|
34
|
+
status_code: int = 401,
|
|
35
|
+
response_body: Optional[Any] = None,
|
|
36
|
+
headers: Optional[Dict[str, str]] = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
super().__init__(message, status_code=status_code, response_body=response_body, headers=headers)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class PermissionDeniedError(SmartKnowledgeError):
|
|
42
|
+
"""Raised when access is forbidden (HTTP 403: Role or tenant permission denied)."""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
message: str = "Forbidden: Caller does not possess required role or tenant permission.",
|
|
47
|
+
status_code: int = 403,
|
|
48
|
+
response_body: Optional[Any] = None,
|
|
49
|
+
headers: Optional[Dict[str, str]] = None,
|
|
50
|
+
) -> None:
|
|
51
|
+
super().__init__(message, status_code=status_code, response_body=response_body, headers=headers)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class NotFoundError(SmartKnowledgeError):
|
|
55
|
+
"""Raised when a requested resource (document, task, tenant) does not exist (HTTP 404)."""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
message: str = "The requested resource was not found.",
|
|
60
|
+
status_code: int = 404,
|
|
61
|
+
response_body: Optional[Any] = None,
|
|
62
|
+
headers: Optional[Dict[str, str]] = None,
|
|
63
|
+
) -> None:
|
|
64
|
+
super().__init__(message, status_code=status_code, response_body=response_body, headers=headers)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ValidationError(SmartKnowledgeError):
|
|
68
|
+
"""Raised when invalid request parameters or missing required arguments are supplied."""
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
message: str,
|
|
73
|
+
status_code: int = 400,
|
|
74
|
+
response_body: Optional[Any] = None,
|
|
75
|
+
headers: Optional[Dict[str, str]] = None,
|
|
76
|
+
) -> None:
|
|
77
|
+
super().__init__(message, status_code=status_code, response_body=response_body, headers=headers)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class RateLimitError(SmartKnowledgeError):
|
|
81
|
+
"""Raised when the rate limit is exceeded (HTTP 429). Includes retry_after_sec."""
|
|
82
|
+
|
|
83
|
+
def __init__(
|
|
84
|
+
self,
|
|
85
|
+
message: str = "Rate limit exceeded. Please throttle your request frequency.",
|
|
86
|
+
retry_after_sec: Optional[float] = None,
|
|
87
|
+
status_code: int = 429,
|
|
88
|
+
response_body: Optional[Any] = None,
|
|
89
|
+
headers: Optional[Dict[str, str]] = None,
|
|
90
|
+
) -> None:
|
|
91
|
+
super().__init__(message, status_code=status_code, response_body=response_body, headers=headers)
|
|
92
|
+
self.retry_after_sec = retry_after_sec
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class QuotaExceededError(SmartKnowledgeError):
|
|
96
|
+
"""Raised when the tenant's monthly token or task quota is exhausted (HTTP 429)."""
|
|
97
|
+
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
message: str = "Monthly LLM token quota exceeded for this tenant workspace.",
|
|
101
|
+
status_code: int = 429,
|
|
102
|
+
response_body: Optional[Any] = None,
|
|
103
|
+
headers: Optional[Dict[str, str]] = None,
|
|
104
|
+
) -> None:
|
|
105
|
+
super().__init__(message, status_code=status_code, response_body=response_body, headers=headers)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class InternalServerError(SmartKnowledgeError):
|
|
109
|
+
"""Raised when the server encounters an internal error (HTTP 500/502/503/504)."""
|
|
110
|
+
|
|
111
|
+
def __init__(
|
|
112
|
+
self,
|
|
113
|
+
message: str = "Internal server error occurred within Smart Knowledge platform.",
|
|
114
|
+
status_code: int = 500,
|
|
115
|
+
response_body: Optional[Any] = None,
|
|
116
|
+
headers: Optional[Dict[str, str]] = None,
|
|
117
|
+
) -> None:
|
|
118
|
+
super().__init__(message, status_code=status_code, response_body=response_body, headers=headers)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class APIConnectionError(SmartKnowledgeError):
|
|
122
|
+
"""Raised when an HTTP connection to the Smart Knowledge server fails or times out."""
|
|
123
|
+
|
|
124
|
+
def __init__(
|
|
125
|
+
self,
|
|
126
|
+
message: str = "Failed to establish a network connection to Smart Knowledge API.",
|
|
127
|
+
cause: Optional[Exception] = None,
|
|
128
|
+
) -> None:
|
|
129
|
+
super().__init__(message)
|
|
130
|
+
self.__cause__ = cause
|
smart_knowledge/http.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""HTTP transport layer for the Smart Knowledge Python SDK."""
|
|
2
|
+
|
|
3
|
+
import random
|
|
4
|
+
import time
|
|
5
|
+
from typing import Any, Dict, Optional, Union
|
|
6
|
+
import requests
|
|
7
|
+
from .config import ResolvedConfig
|
|
8
|
+
from .errors import (
|
|
9
|
+
APIConnectionError,
|
|
10
|
+
AuthenticationError,
|
|
11
|
+
InternalServerError,
|
|
12
|
+
NotFoundError,
|
|
13
|
+
PermissionDeniedError,
|
|
14
|
+
QuotaExceededError,
|
|
15
|
+
RateLimitError,
|
|
16
|
+
SmartKnowledgeError,
|
|
17
|
+
ValidationError,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class HttpClient:
|
|
22
|
+
"""Connection-pooled, resilient HTTP transport layer."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, config: ResolvedConfig) -> None:
|
|
25
|
+
self.config = config
|
|
26
|
+
self.session = requests.Session()
|
|
27
|
+
self.session.headers.update(config.default_headers)
|
|
28
|
+
|
|
29
|
+
def build_url(self, path: str) -> str:
|
|
30
|
+
clean_path = path if path.startswith("/") else f"/{path}"
|
|
31
|
+
return f"{self.config.base_url}{clean_path}"
|
|
32
|
+
|
|
33
|
+
def request(
|
|
34
|
+
self,
|
|
35
|
+
method: str,
|
|
36
|
+
path: str,
|
|
37
|
+
params: Optional[Dict[str, Any]] = None,
|
|
38
|
+
json_data: Optional[Any] = None,
|
|
39
|
+
data: Optional[Any] = None,
|
|
40
|
+
files: Optional[Any] = None,
|
|
41
|
+
headers: Optional[Dict[str, str]] = None,
|
|
42
|
+
timeout: Optional[float] = None,
|
|
43
|
+
) -> Any:
|
|
44
|
+
url = self.build_url(path)
|
|
45
|
+
effective_timeout = timeout if timeout is not None else self.config.timeout
|
|
46
|
+
req_headers = dict(self.session.headers)
|
|
47
|
+
if headers:
|
|
48
|
+
req_headers.update(headers)
|
|
49
|
+
|
|
50
|
+
# Filter None params
|
|
51
|
+
filtered_params = {k: v for k, v in (params or {}).items() if v is not None} or None
|
|
52
|
+
|
|
53
|
+
attempt = 0
|
|
54
|
+
max_retries = self.config.max_retries
|
|
55
|
+
|
|
56
|
+
while True:
|
|
57
|
+
attempt += 1
|
|
58
|
+
try:
|
|
59
|
+
response = self.session.request(
|
|
60
|
+
method=method.upper(),
|
|
61
|
+
url=url,
|
|
62
|
+
params=filtered_params,
|
|
63
|
+
json=json_data,
|
|
64
|
+
data=data,
|
|
65
|
+
files=files,
|
|
66
|
+
headers=req_headers,
|
|
67
|
+
timeout=effective_timeout,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if response.status_code == 204:
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
if 200 <= response.status_code < 300:
|
|
74
|
+
content_type = response.headers.get("content-type", "")
|
|
75
|
+
if "application/json" in content_type:
|
|
76
|
+
return response.json()
|
|
77
|
+
return response.text
|
|
78
|
+
|
|
79
|
+
# Parse error body
|
|
80
|
+
error_body: Any = None
|
|
81
|
+
try:
|
|
82
|
+
error_body = response.json()
|
|
83
|
+
except Exception:
|
|
84
|
+
error_body = response.text
|
|
85
|
+
|
|
86
|
+
error_msg = self._extract_error_message(error_body) or f"HTTP {response.status_code}: {response.reason}"
|
|
87
|
+
|
|
88
|
+
# Check retryable status codes
|
|
89
|
+
if response.status_code in (429, 502, 503, 504) and attempt <= max_retries:
|
|
90
|
+
retry_after = self._get_retry_after(response.headers, attempt)
|
|
91
|
+
time.sleep(retry_after)
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
self._raise_for_status(response.status_code, error_msg, error_body, dict(response.headers))
|
|
95
|
+
|
|
96
|
+
except requests.exceptions.Timeout as e:
|
|
97
|
+
if attempt <= max_retries:
|
|
98
|
+
time.sleep(1.0 * (2 ** (attempt - 1)) + random.uniform(0.1, 0.5))
|
|
99
|
+
continue
|
|
100
|
+
raise APIConnectionError(f"Request timed out after {effective_timeout}s: {e}", cause=e)
|
|
101
|
+
|
|
102
|
+
except requests.exceptions.RequestException as e:
|
|
103
|
+
if isinstance(e, requests.exceptions.HTTPError):
|
|
104
|
+
# Already handled above
|
|
105
|
+
raise
|
|
106
|
+
if attempt <= max_retries:
|
|
107
|
+
time.sleep(1.0 * (2 ** (attempt - 1)) + random.uniform(0.1, 0.5))
|
|
108
|
+
continue
|
|
109
|
+
raise APIConnectionError(f"Network connection failed: {e}", cause=e)
|
|
110
|
+
|
|
111
|
+
def _extract_error_message(self, body: Any) -> Optional[str]:
|
|
112
|
+
if isinstance(body, dict):
|
|
113
|
+
if "error" in body:
|
|
114
|
+
err = body["error"]
|
|
115
|
+
if isinstance(err, str):
|
|
116
|
+
return err
|
|
117
|
+
if isinstance(err, dict) and "message" in err:
|
|
118
|
+
return str(err["message"])
|
|
119
|
+
if "message" in body:
|
|
120
|
+
return str(body["message"])
|
|
121
|
+
if "detail" in body:
|
|
122
|
+
return str(body["detail"])
|
|
123
|
+
elif isinstance(body, str) and body.strip():
|
|
124
|
+
return body.strip()
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
def _get_retry_after(self, headers: Any, attempt: int) -> float:
|
|
128
|
+
if "Retry-After" in headers:
|
|
129
|
+
try:
|
|
130
|
+
return float(headers["Retry-After"])
|
|
131
|
+
except ValueError:
|
|
132
|
+
pass
|
|
133
|
+
backoff = 1.0 * (2 ** (attempt - 1))
|
|
134
|
+
jitter = random.uniform(0.1, 0.5)
|
|
135
|
+
return min(backoff + jitter, 15.0)
|
|
136
|
+
|
|
137
|
+
def _raise_for_status(
|
|
138
|
+
self,
|
|
139
|
+
status_code: int,
|
|
140
|
+
message: str,
|
|
141
|
+
body: Any,
|
|
142
|
+
headers: Dict[str, str],
|
|
143
|
+
) -> None:
|
|
144
|
+
if status_code == 401:
|
|
145
|
+
raise AuthenticationError(message, status_code=status_code, response_body=body, headers=headers)
|
|
146
|
+
elif status_code == 403:
|
|
147
|
+
raise PermissionDeniedError(message, status_code=status_code, response_body=body, headers=headers)
|
|
148
|
+
elif status_code == 404:
|
|
149
|
+
raise NotFoundError(message, status_code=status_code, response_body=body, headers=headers)
|
|
150
|
+
elif status_code == 400:
|
|
151
|
+
raise ValidationError(message, status_code=status_code, response_body=body, headers=headers)
|
|
152
|
+
elif status_code == 429:
|
|
153
|
+
lower_msg = message.lower()
|
|
154
|
+
if "quota" in lower_msg or "monthly" in lower_msg:
|
|
155
|
+
raise QuotaExceededError(message, status_code=status_code, response_body=body, headers=headers)
|
|
156
|
+
retry_sec = self._get_retry_after(headers, 1)
|
|
157
|
+
raise RateLimitError(
|
|
158
|
+
message, retry_after_sec=retry_sec, status_code=status_code, response_body=body, headers=headers
|
|
159
|
+
)
|
|
160
|
+
elif status_code >= 500:
|
|
161
|
+
raise InternalServerError(message, status_code=status_code, response_body=body, headers=headers)
|
|
162
|
+
else:
|
|
163
|
+
raise SmartKnowledgeError(message, status_code=status_code, response_body=body, headers=headers)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Modules for Smart Knowledge Python SDK."""
|
|
2
|
+
|
|
3
|
+
from .ask import AskModule
|
|
4
|
+
from .search import SearchModule
|
|
5
|
+
from .agentask import AgentaskModule
|
|
6
|
+
from .documents import DocumentsModule
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"AskModule",
|
|
10
|
+
"SearchModule",
|
|
11
|
+
"AgentaskModule",
|
|
12
|
+
"DocumentsModule",
|
|
13
|
+
]
|