"""
The Smarter client.
.. code-block:: python
from smarter import Smarter
client = Smarter() # SMARTER_API_KEY, or ~/.smarter/config.yaml
print(client.whoami().user.username)
response = client.llm_clients.prompt(5, "What is 17 times 23?")
print(response.text)
"""
from collections.abc import Mapping
from functools import cached_property
from types import TracebackType
from typing import Any, Optional
import httpx
from smarter.core.conf import (
SmarterEnvironment,
SmarterSettings,
resolve_client_config,
)
from smarter.core.http import SmarterHttpClient
from smarter.core.log import mask_secret, setup_logging
from smarter.models.journal import SmarterJournaledResponse
from smarter.models.manifest import KindInfo
from smarter.models.platform import PlatformStatus, PlatformVersion, WhoAmI
from smarter.resources import (
Accounts,
Guardrails,
Journal,
LLMClients,
LLMHosts,
Manifests,
MCPClients,
Orchestrators,
Plugins,
PromptHistory,
Providers,
Proxies,
Secrets,
Vectorsearches,
Vectorstores,
)
[docs]
class Smarter:
"""
A client of a Smarter platform's api.
Every argument is optional. See :mod:`smarter.core.conf` for where each is looked up.
:param api_key: A Smarter api key.
:param base_url: The platform's url, e.g. ``https://platform.example.com`` or ``http://localhost:9357``.
:param environment: The environment of the smarter cli's configuration file to use: ``local``,
``alpha``, ``beta``, ``next`` or ``prod``.
:param timeout: The request timeout, in seconds.
:param max_retries: How many times to retry a request that failed for a transient reason, e.g.
a timeout, a 429 or a 502. 0 disables retries.
:param default_headers: Headers to add to every request.
:param http_client: An ``httpx.Client`` to send the requests, e.g. one with a proxy. The client
does not close a client that it was given.
:raises SmarterConfigurationError: If no api key is found.
The client's attributes group the api's operations:
- :attr:`manifests`: apply, validate, get, describe, delete, deploy, undeploy and logs, for any kind
- :attr:`llm_clients`: LLMClients, and prompting and chatting with them
- :attr:`guardrails`, :attr:`mcp_clients`, :attr:`vectorstores`, :attr:`llm_hosts`,
:attr:`secrets`, :attr:`plugins`, :attr:`orchestrators`, :attr:`vectorsearches`
- :attr:`accounts` (and ``accounts.users`` and ``accounts.contacts``)
- :attr:`history`, :attr:`journal`, :attr:`providers`, :attr:`proxies`
- :meth:`whoami`, :meth:`version`, :meth:`status` and :meth:`kinds`
Use it as a context manager, or call :meth:`close`, to close its connections.
"""
def __init__(
self,
*,
api_key: Optional[str] = None,
base_url: Optional[str] = None,
environment: Optional[SmarterEnvironment] = None,
timeout: Optional[float] = None,
max_retries: Optional[int] = None,
default_headers: Optional[Mapping[str, str]] = None,
http_client: Optional[httpx.Client] = None,
) -> None:
settings = SmarterSettings()
setup_logging(settings.log)
self.config = resolve_client_config(
api_key=api_key,
base_url=base_url,
environment=environment,
timeout=timeout,
max_retries=max_retries,
settings=settings,
)
self.http = SmarterHttpClient(self.config, default_headers=default_headers, http_client=http_client)
"""The http transport, for endpoints that the SDK does not wrap."""
@property
def api_key(self) -> str:
"""The api key, e.g. to use a proxy with a provider's own SDK."""
return self.config.api_key.get_secret_value()
@property
def base_url(self) -> str:
"""The api's base url, e.g. ``https://platform.smarter.sh/api/v1/``."""
return self.config.base_url
@cached_property
def manifests(self) -> Manifests:
"""The manifest api, for any resource kind."""
return Manifests(self)
@cached_property
def llm_clients(self) -> LLMClients:
"""LLMClients."""
return LLMClients(self)
@cached_property
def guardrails(self) -> Guardrails:
"""Guardrails."""
return Guardrails(self)
@cached_property
def mcp_clients(self) -> MCPClients:
"""MCPClients."""
return MCPClients(self)
@cached_property
def vectorstores(self) -> Vectorstores:
"""Vectorstores."""
return Vectorstores(self)
@cached_property
def llm_hosts(self) -> LLMHosts:
"""LLMHosts."""
return LLMHosts(self)
@cached_property
def secrets(self) -> Secrets:
"""Secrets."""
return Secrets(self)
@cached_property
def plugins(self) -> Plugins:
"""Plugins."""
return Plugins(self)
@cached_property
def orchestrators(self) -> Orchestrators:
"""Orchestrators."""
return Orchestrators(self)
@cached_property
def vectorsearches(self) -> Vectorsearches:
"""Vectorsearches."""
return Vectorsearches(self)
@cached_property
def accounts(self) -> Accounts:
"""Accounts, and their users and contacts."""
return Accounts(self)
@cached_property
def history(self) -> PromptHistory:
"""The history of chat sessions, tool calls and plugin uses."""
return PromptHistory(self)
@cached_property
def journal(self) -> Journal:
"""The Smarter Journal.
Superusers only.
"""
return Journal(self)
@cached_property
def providers(self) -> Providers:
"""LLM providers and their models."""
return Providers(self)
@cached_property
def proxies(self) -> Proxies:
"""Proxies to LLM providers' own apis."""
return Proxies(self)
[docs]
def whoami(self) -> WhoAmI:
"""
The owner of the api key: the user, their account, and the platform's environment.
:returns: The owner.
"""
return self._cli_data("whoami/", WhoAmI)
[docs]
def version(self) -> PlatformVersion:
"""
The platform's version, and the versions of its main Python packages.
:returns: The versions.
"""
return self._cli_data("version/", PlatformVersion)
[docs]
def status(self) -> PlatformStatus:
"""
The state of the platform's infrastructure and compute.
:returns: The status.
"""
return self._cli_data("status/", PlatformStatus)
[docs]
def kinds(self) -> list[KindInfo]:
"""
The resource kinds that the platform supports, and which of them can be deployed.
:returns: The kinds.
"""
return self._cli_data("resources/", list[KindInfo])
def _cli_data(self, path: str, cast_to: Any) -> Any:
"""
The ``data`` of a journaled response of the cli api.
:param path: The cli endpoint's path, e.g. ``whoami/``.
:param cast_to: The type of the ``data``: any type form that Pydantic validates, e.g. ``list[KindInfo]``.
:returns: The validated ``data``.
"""
response = self.http.request("POST", f"cli/{path}", cast_to=SmarterJournaledResponse[cast_to])
return response.data
[docs]
def close(self) -> None:
"""Close the client's connections."""
self.http.close()
def __enter__(self) -> "Smarter":
return self
def __exit__(
self,
exc_type: Optional[type[BaseException]],
exc: Optional[BaseException],
traceback: Optional[TracebackType],
) -> None:
self.close()
def __repr__(self) -> str:
return f"Smarter(base_url={self.base_url!r}, api_key={mask_secret(self.api_key)!r})"
__all__ = ["Smarter"]