217 lines
6.5 KiB
Python
217 lines
6.5 KiB
Python
"""OpenRAG SDK client."""
|
|
|
|
import os
|
|
from typing import Any
|
|
|
|
import httpx
|
|
|
|
from .chat import ChatClient
|
|
from .documents import DocumentsClient
|
|
from .exceptions import (
|
|
AuthenticationError,
|
|
NotFoundError,
|
|
OpenRAGError,
|
|
RateLimitError,
|
|
ServerError,
|
|
ValidationError,
|
|
)
|
|
from .knowledge_filters import KnowledgeFiltersClient
|
|
from .search import SearchClient
|
|
|
|
|
|
class SettingsClient:
|
|
"""Client for settings operations."""
|
|
|
|
def __init__(self, client: "OpenRAGClient"):
|
|
self._client = client
|
|
|
|
async def get(self):
|
|
"""
|
|
Get current OpenRAG configuration.
|
|
|
|
Returns:
|
|
SettingsResponse with agent and knowledge settings.
|
|
"""
|
|
from .models import SettingsResponse
|
|
|
|
response = await self._client._request("GET", "/api/v1/settings")
|
|
data = response.json()
|
|
return SettingsResponse(**data)
|
|
|
|
async def update(self, options):
|
|
"""
|
|
Update OpenRAG configuration.
|
|
|
|
Args:
|
|
options: SettingsUpdateOptions or dict with settings to update.
|
|
|
|
Returns:
|
|
SettingsUpdateResponse with success message.
|
|
"""
|
|
from .models import SettingsUpdateOptions, SettingsUpdateResponse
|
|
|
|
if isinstance(options, SettingsUpdateOptions):
|
|
body = options.model_dump(exclude_none=True)
|
|
else:
|
|
body = {k: v for k, v in options.items() if v is not None}
|
|
|
|
response = await self._client._request("POST", "/api/v1/settings", json=body)
|
|
data = response.json()
|
|
return SettingsUpdateResponse(message=data.get("message", "Settings updated"))
|
|
|
|
|
|
class OpenRAGClient:
|
|
"""
|
|
OpenRAG API client.
|
|
|
|
The client can be configured via constructor arguments or environment variables:
|
|
- OPENRAG_API_KEY: API key for authentication
|
|
- OPENRAG_URL: Base URL for the OpenRAG frontend (default: http://localhost:3000)
|
|
|
|
Usage:
|
|
# Using environment variables
|
|
async with OpenRAGClient() as client:
|
|
response = await client.chat.create(message="Hello")
|
|
|
|
# Using explicit arguments
|
|
async with OpenRAGClient(api_key="orag_...", base_url="https://api.example.com") as client:
|
|
response = await client.chat.create(message="Hello")
|
|
|
|
# Without context manager
|
|
client = OpenRAGClient()
|
|
try:
|
|
response = await client.chat.create(message="Hello")
|
|
finally:
|
|
await client.close()
|
|
"""
|
|
|
|
DEFAULT_BASE_URL = "http://localhost:3000"
|
|
|
|
def __init__(
|
|
self,
|
|
api_key: str | None = None,
|
|
*,
|
|
base_url: str | None = None,
|
|
timeout: float = 30.0,
|
|
http_client: httpx.AsyncClient | None = None,
|
|
):
|
|
"""
|
|
Initialize the OpenRAG client.
|
|
|
|
Args:
|
|
api_key: API key for authentication. Falls back to OPENRAG_API_KEY env var.
|
|
base_url: Base URL for the API. Falls back to OPENRAG_URL env var, then default.
|
|
timeout: Request timeout in seconds (default 30).
|
|
http_client: Optional custom httpx.AsyncClient instance.
|
|
"""
|
|
# Resolve API key from argument or environment
|
|
self._api_key = api_key or os.environ.get("OPENRAG_API_KEY")
|
|
if not self._api_key:
|
|
raise AuthenticationError(
|
|
"API key is required. Set OPENRAG_API_KEY environment variable or pass api_key argument."
|
|
)
|
|
|
|
# Resolve base URL from argument or environment
|
|
self._base_url = (
|
|
base_url
|
|
or os.environ.get("OPENRAG_URL")
|
|
or self.DEFAULT_BASE_URL
|
|
).rstrip("/")
|
|
|
|
self._timeout = timeout
|
|
self._owns_http_client = http_client is None
|
|
|
|
# Create or use provided HTTP client
|
|
if http_client:
|
|
self._http = http_client
|
|
else:
|
|
self._http = httpx.AsyncClient(timeout=timeout)
|
|
|
|
# Initialize sub-clients
|
|
self.chat = ChatClient(self)
|
|
self.search = SearchClient(self)
|
|
self.documents = DocumentsClient(self)
|
|
self.settings = SettingsClient(self)
|
|
self.knowledge_filters = KnowledgeFiltersClient(self)
|
|
|
|
@property
|
|
def _headers(self) -> dict[str, str]:
|
|
"""Get request headers with authentication."""
|
|
return {
|
|
"X-API-Key": self._api_key,
|
|
"Content-Type": "application/json",
|
|
}
|
|
|
|
async def _request(
|
|
self,
|
|
method: str,
|
|
path: str,
|
|
*,
|
|
json: dict[str, Any] | None = None,
|
|
files: dict[str, tuple[str, Any]] | None = None,
|
|
**kwargs,
|
|
) -> httpx.Response:
|
|
"""Make an authenticated request to the API."""
|
|
url = f"{self._base_url}{path}"
|
|
headers = self._headers.copy()
|
|
|
|
# Handle file uploads
|
|
if files:
|
|
del headers["Content-Type"] # Let httpx set multipart content type
|
|
response = await self._http.request(
|
|
method,
|
|
url,
|
|
headers=headers,
|
|
files=files,
|
|
**kwargs,
|
|
)
|
|
else:
|
|
response = await self._http.request(
|
|
method,
|
|
url,
|
|
headers=headers,
|
|
json=json,
|
|
**kwargs,
|
|
)
|
|
|
|
self._handle_error(response)
|
|
return response
|
|
|
|
def _handle_error(self, response: httpx.Response) -> None:
|
|
"""Handle error responses."""
|
|
if response.status_code < 400:
|
|
return
|
|
|
|
try:
|
|
data = response.json()
|
|
message = data.get("error", response.text)
|
|
except Exception:
|
|
message = response.text or f"HTTP {response.status_code}"
|
|
|
|
status_code = response.status_code
|
|
|
|
if status_code == 401:
|
|
raise AuthenticationError(message, status_code)
|
|
elif status_code == 403:
|
|
raise AuthenticationError(message, status_code)
|
|
elif status_code == 404:
|
|
raise NotFoundError(message, status_code)
|
|
elif status_code == 400:
|
|
raise ValidationError(message, status_code)
|
|
elif status_code == 429:
|
|
raise RateLimitError(message, status_code)
|
|
elif status_code >= 500:
|
|
raise ServerError(message, status_code)
|
|
else:
|
|
raise OpenRAGError(message, status_code)
|
|
|
|
async def close(self) -> None:
|
|
"""Close the HTTP client."""
|
|
if self._owns_http_client:
|
|
await self._http.aclose()
|
|
|
|
async def __aenter__(self) -> "OpenRAGClient":
|
|
return self
|
|
|
|
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
await self.close()
|