Source code for smarter.apps.provider.contract.models

"""
Pydantic models of the Providers' REST api responses (``/api/v1/providers/``).

The REST api's views build their responses from these models, and the Smarter api's OpenAPI
document (:mod:`smarter.apps.api.v1.contract.openapi`) publishes their JSON Schemas.

:class:`ProviderApiProvider` and :class:`ProviderApiModel` are the rows, as
:mod:`smarter.apps.provider.serializers` returned them before the api was modeled, which clients
already read: a Provider's api key is its Secret's name and status, never the key.

The models forbid extra fields: the api returns exactly what they declare.
"""

from datetime import datetime
from typing import TYPE_CHECKING, Literal, Optional

from pydantic import Field

from smarter.apps.account.contract.models import (
    AccountApiModel,
    AccountApiUser,
    AccountApiUserProfile,
    MetaDataApiAnnotations,
)
from smarter.apps.account.serializers import user_can_delete
from smarter.apps.secret.contract.models import SecretApiSecretRef

if TYPE_CHECKING:
    from smarter.apps.provider.models import Provider, ProviderModel


ProviderApiStatus = Literal["unverified", "verifying", "failed", "verified", "suspended", "deprecated"]
"""The choices of ``Provider.status``.

A test fails if the two differ.
"""


[docs] class ProviderApiProvider(AccountApiModel): """An LLM provider: a :class:`smarter.apps.provider.models.Provider`.""" id: int = Field(..., description="The row's primary key.") created_at: Optional[datetime] = Field(None, description="TimestampedModel.created_at.") updated_at: Optional[datetime] = Field(None, description="TimestampedModel.updated_at.") name: str = Field(..., description="The Provider's name, e.g. openai.") user_profile: AccountApiUserProfile = Field(..., description="The Provider's owner.") status: ProviderApiStatus = Field(..., description="The Provider's verification status.") description: Optional[str] = Field(None, description="MetaDataModel.description.") version: Optional[str] = Field(None, description="MetaDataModel.version, e.g. 1.0.0.") annotations: MetaDataApiAnnotations = Field(None, description="MetaDataModel.annotations.") tags: list[str] = Field(default_factory=list, description="MetaDataModel.tags, by name.") manifest_url: Optional[str] = Field(None, description="The url of the Provider's manifest in the web console.") ready: bool = Field(..., description="Whether the Provider can be used.") is_default: bool is_active: bool is_verified: bool is_featured: bool is_deprecated: bool is_flagged: bool is_suspended: bool base_url: Optional[str] = Field(None, description="The base url of the provider's api.") api_key: Optional[SecretApiSecretRef] = Field(None, description="The Secret of its api key, never the key.") default_model: Optional[str] = Field(None, description="The model that LLMClients use by default.") connectivity_test_path: Optional[str] = Field(None, description="The path that tests the provider's api.") logo: Optional[str] = Field(None, description="The url of the provider's logo.") website_url: Optional[str] = None ownership_requested: Optional[str] = Field(None, description="The email of a pending ownership request.") contact_email: Optional[str] = None contact_email_verified: Optional[datetime] = None support_email: Optional[str] = None support_email_verified: Optional[datetime] = None docs_url: Optional[str] = None terms_of_service_url: Optional[str] = None privacy_policy_url: Optional[str] = None is_official_provider: bool = Field(..., description="Whether the platform's owner publishes the Provider.") tos_accepted: bool = Field(..., description="Whether the provider's terms of service were accepted.") tos_accepted_at: Optional[datetime] = None tos_accepted_by: Optional[AccountApiUser] = Field(None, description="Who accepted the terms of service.") rfc1034_compliant_name: Optional[str] = Field(None, description="The name, as a DNS label.") can_delete: bool = Field(..., description="Whether the requesting user may delete the Provider now.")
[docs] @classmethod def from_model(cls, provider: "Provider", user: object = None) -> "ProviderApiProvider": """ The contract's model of a Provider. :param provider: The row, whose ``user_profile``, ``api_key`` and ``tos_accepted_by`` should be fetched with it. :type provider: smarter.apps.provider.models.Provider :param user: The requesting user, for ``can_delete``. None may delete nothing. :type user: object :returns: The model. :rtype: ProviderApiProvider """ # pylint: disable=import-outside-toplevel from smarter.apps.provider.manifest.models.provider.const import ( MANIFEST_KIND as PROVIDER_KIND, ) return cls( id=provider.pk, created_at=provider.created_at, updated_at=provider.updated_at, name=provider.name, user_profile=AccountApiUserProfile.from_model(provider.user_profile), status=provider.status, # type: ignore[arg-type] description=provider.description, version=provider.version, annotations=provider.annotations, tags=[tag.name for tag in provider.tags.all()], manifest_url=provider.manifest_url, ready=provider.ready, is_default=provider.is_default, is_active=provider.is_active, is_verified=provider.is_verified, is_featured=provider.is_featured, is_deprecated=provider.is_deprecated, is_flagged=provider.is_flagged, is_suspended=provider.is_suspended, base_url=provider.base_url, api_key=SecretApiSecretRef.from_model(provider.api_key, user) if provider.api_key else None, default_model=provider.default_model, connectivity_test_path=provider.connectivity_test_path, logo=provider.logo.url if provider.logo else None, website_url=provider.website_url, ownership_requested=provider.ownership_requested, contact_email=provider.contact_email, contact_email_verified=provider.contact_email_verified, support_email=provider.support_email, support_email_verified=provider.support_email_verified, docs_url=provider.docs_url, terms_of_service_url=provider.terms_of_service_url, privacy_policy_url=provider.privacy_policy_url, is_official_provider=provider.is_official_provider, tos_accepted=provider.tos_accepted, tos_accepted_at=provider.tos_accepted_at, tos_accepted_by=AccountApiUser.from_model(provider.tos_accepted_by) if provider.tos_accepted_by else None, rfc1034_compliant_name=provider.rfc1034_compliant_name, can_delete=user_can_delete(provider, PROVIDER_KIND, user), )
[docs] class ProviderApiModel(AccountApiModel): """A model of an LLM provider: a :class:`smarter.apps.provider.models.ProviderModel`, with its provider.""" id: int = Field(..., description="The row's primary key.") provider: ProviderApiProvider created_at: Optional[datetime] = Field(None, description="TimestampedModel.created_at.") updated_at: Optional[datetime] = Field(None, description="TimestampedModel.updated_at.") name: str = Field(..., description="The model's name, e.g. gpt-4o.") description: Optional[str] = None is_default: bool is_active: bool is_deprecated: bool is_flagged: bool is_suspended: bool max_completion_tokens: int = Field(..., ge=0) temperature: float top_p: float supports_text_input: bool supports_text_generation: bool supports_translation: bool supports_summarization: bool supports_streaming: bool supports_tools: bool supports_image_input: bool supports_audio_input: bool supports_embedding: bool supports_fine_tuning: bool supports_search: bool supports_code_interpreter: bool supports_image_generation: bool supports_audio_generation: bool
[docs] @classmethod def from_model(cls, provider_model: "ProviderModel", user: object = None) -> "ProviderApiModel": """ The contract's model of a provider's model. :param provider_model: The row, whose ``provider`` should be fetched with it. :type provider_model: smarter.apps.provider.models.ProviderModel :param user: The requesting user, for the provider's ``can_delete``. :type user: object :returns: The model. :rtype: ProviderApiModel """ fields = { name: getattr(provider_model, name) for name in cls.model_fields if name not in ("id", "provider", "created_at", "updated_at") } return cls( id=provider_model.pk, provider=ProviderApiProvider.from_model(provider_model.provider, user), created_at=provider_model.created_at, updated_at=provider_model.updated_at, **fields, )
PROVIDER_API_MODELS = (ProviderApiProvider, ProviderApiModel) """The models whose JSON Schemas the Smarter api's OpenAPI document includes.""" __all__ = ["PROVIDER_API_MODELS", "ProviderApiModel", "ProviderApiProvider", "ProviderApiStatus"]