Source code for smarter.apps.llm_client.models.llm_client_api_key

"""LLMClientAPIKey model for managing API keys associated with LLMClient instances in the Smarter platform."""

from typing import Optional

from django.db import models

from smarter.lib import logging
from smarter.lib.cache import cache_results
from smarter.lib.django.models import TimestampedModel
from smarter.lib.django.waffle import SmarterWaffleSwitches
from smarter.lib.drf.models import SmarterAuthToken

from .llm_client import LLMClient

logger = logging.getSmarterLogger(__name__, any_switches=[SmarterWaffleSwitches.LLM_CLIENT_LOGGING])


[docs] class LLMClientAPIKey(TimestampedModel): """ Represents the mapping of API keys to LLMClient instances within the Smarter platform. .. important:: If present, the LLMClient associated with this record will require Api Key authentication for all API requests. Otherwise, the LLMClient will allow anonymous unauthenticated access. See :class:`smarter.lib.drf.token_authentication.SmarterTokenAuthentication` . This model establishes a relationship between a LLMClient and its associated API keys, enabling secure authentication and authorization for API access. Each entry in this model links a specific LLMClient to a unique API key, allowing fine-grained control over which keys can interact with which llm_client instances. The LLMClientAPIKey model is essential for managing access to llm_client APIs, supporting use cases such as per-bot API key rotation, revocation, and auditing. By associating API keys with individual llm_clients, the platform can enforce security policies and monitor usage at the llm_client level. Typical usage involves creating a LLMClientAPIKey instance whenever a new API key is provisioned for an llm_client, and querying this model to validate incoming requests against active keys. **Model Relationships** - Each LLMClientAPIKey is linked to one :class:`LLMClient` instance. - Each LLMClientAPIKey references one :class:`SmarterAuthToken` representing the API key. **Example** .. code-block:: python # Assign an API key to an llm_client api_key = SmarterAuthToken.objects.create(...) llm_client_api_key = LLMClientAPIKey.objects.create(llm_client=my_llm_client, api_key=api_key) # Query API keys for an llm_client keys = LLMClientAPIKey.objects.filter(llm_client=my_llm_client) **Notes** - API key activation and deactivation are managed via the SmarterAuthToken model. - This model supports auditing and access control for llm_client API endpoints. - Intended for internal use within the Smarter platform to secure llm_client APIs. """ class Meta: verbose_name_plural = "LLMClient API Keys" #: The LLMClient instance associated with this API key. llm_client = models.ForeignKey(LLMClient, on_delete=models.CASCADE) #: The API key (SmarterAuthToken) associated with the LLMClient. api_key = models.ForeignKey(SmarterAuthToken, on_delete=models.CASCADE)
[docs] @classmethod def has_active_api_key(cls, llm_client: LLMClient, invalidate: Optional[bool] = False) -> bool: """Returns True if the llm_client has at least one active API key.""" logger_prefix = logging.formatted_text(__name__ + "." + cls.__name__ + ".has_active_api_key()") @cache_results(cls.cache_expiration) def _has_active_api_key(llm_client_id: int, class_name: str) -> bool: logger.debug("%s querying and caching results for llm_client=%s", logger_prefix, llm_client) return cls.objects.filter(llm_client_id=llm_client_id, api_key__is_active=True).exists() if invalidate and llm_client: _has_active_api_key.invalidate(llm_client_id=llm_client.id, class_name=LLMClientAPIKey.__name__) # type: ignore[union-attr] if llm_client: return _has_active_api_key(llm_client_id=llm_client.id, class_name=LLMClientAPIKey.__name__) # type: ignore[return-value] return False
# pylint: disable=W0221
[docs] @classmethod def get_cached_objects( cls, invalidate: Optional[bool] = False, llm_client: Optional[LLMClient] = None ) -> models.QuerySet["LLMClientAPIKey"]: """ Retrieve a list of LLMClientAPIKey instances associated with a LLMClient using caching. Example usage: .. code-block:: python # Retrieve API keys for an llm_client with caching api_keys = LLMClientAPIKey.get_cached_objects(my_llm_client, invalidate=True) :param invalidate: Whether to invalidate the cache for this retrieval. :type invalidate: bool, optional :param llm_client: The LLMClient instance for which to retrieve API keys. :type llm_client: LLMClient, optional :returns: A queryset of LLMClientAPIKey instances associated with the LLMClient. :rtype: models.QuerySet["LLMClientAPIKey"] """ logger_prefix = logging.formatted_text(__name__ + "." + LLMClientAPIKey.__name__ + ".get_cached_objects()") @cache_results(cls.cache_expiration) def _get_api_keys_for_llm_client_id( llm_client_id: int, class_name: str = cls.__name__ ) -> models.QuerySet["LLMClientAPIKey"]: logger.debug("%s querying and caching results for llm_client=%s, ", logger_prefix, llm_client) return cls.objects.filter(llm_client_id=llm_client_id).select_related( "llm_client", "llm_client__user_profile", "llm_client__user_profile__user", "llm_client__user_profile__account", "api_key", "api_key__user_profile", "api_key__user_profile", "api_key__user_profile__user", "api_key__user_profile__account", ) if invalidate and llm_client: _get_api_keys_for_llm_client_id.invalidate(llm_client_id=llm_client.id, class_name=cls.__name__) # type: ignore[union-attr] if llm_client: if LLMClientAPIKey.has_active_api_key(llm_client=llm_client, invalidate=invalidate): return _get_api_keys_for_llm_client_id(llm_client_id=llm_client.id, class_name=cls.__name__) # type: ignore[return-value] return LLMClientAPIKey.objects.none() return super().get_cached_objects(invalidate=invalidate) # type: ignore[return-value]
__all__ = [ "LLMClientAPIKey", ]