# # Copyright 2026 The Dapr Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # from dapr_agents.types.llm import HFInferenceClientConfig from dapr_agents.llm.base import LLMClientBase from typing import Optional, Dict, Any, Union from huggingface_hub import InferenceClient from pydantic import Field, model_validator import os import logging logger = logging.getLogger(__name__) class HFHubInferenceClientBase(LLMClientBase): """ Base class for managing Hugging Face Inference API clients. Handles client initialization, configuration, and shared logic. """ model: Optional[str] = Field( default=None, description="Model ID on Hugging Face Hub or a URL to a deployed endpoint. If not set, a recommended model may be chosen by your wrapper.", ) hf_provider: Optional[str] = Field( default="hf-inference", description="Inference provider to use. Defaults to automatic selection based on available providers. Ignored if a custom endpoint URL is provided.", ) token: Optional[Union[str, bool]] = Field( default=None, description="Hugging Face access token for authentication. If None, uses the locally saved token. Set to False to skip sending a token. Mutually exclusive with api_key.", ) api_key: Optional[Union[str, bool]] = Field( default=None, description="Alias for token. Use only one of token or api_key." ) base_url: Optional[str] = Field( default=None, description="Custom endpoint URL for inference. Used for private deployments or TGI endpoints. Cannot be set if 'model' is a Hub ID.", ) timeout: Optional[float] = Field( default=None, description="Maximum seconds to wait for a response. If None, waits indefinitely. Useful for slow model loading.", ) headers: Optional[Dict[str, str]] = Field( default_factory=dict, description="Extra HTTP headers to send with requests. Overrides defaults like authorization and user-agent.", ) cookies: Optional[Dict[str, str]] = Field( default_factory=dict, description="Extra cookies to send with requests." ) proxies: Optional[Any] = Field( default=None, description="Proxy settings for HTTP requests. Use standard requests format.", ) bill_to: Optional[str] = Field( default=None, description="Billing account for requests. Only used for enterprise/organization billing.", ) @model_validator(mode="before") def validate_and_initialize(cls, values: Dict[str, Any]) -> Dict[str, Any]: """ Ensures consistency for 'api_key' and 'token' fields before initialization. - Normalizes 'token' and 'api_key' to a single field. - Validates exclusivity of 'model' and 'base_url'. """ token = values.get("token") api_key = values.get("api_key") model = values.get("model") base_url = values.get("base_url") # Ensure mutual exclusivity of `token` and `api_key` if token is not None and api_key is not None: raise ValueError( "Provide only one of 'api_key' or 'token'. They are aliases and cannot coexist." ) # Normalize `token` to `api_key` if token is not None: values["api_key"] = token values.pop("token", None) # Remove `token` for consistency # Use environment variable if `api_key` is not explicitly provided if api_key is None: api_key = os.environ.get("HUGGINGFACE_API_KEY") if api_key is None: raise ValueError( "API key is required. Set it explicitly or in the 'HUGGINGFACE_API_KEY' environment variable." ) values["api_key"] = api_key # mutual‑exclusivity if model is not None and base_url is not None: raise ValueError("Cannot provide both 'model' and 'base_url'.") # require at least one if model is None and base_url is None: raise ValueError( "HF Inference needs either `model` or `base_url`. " "E.g. model='gpt2' or base_url='https://…/models/gpt2'." ) # auto‑derive model from base_url if model is None: derived = base_url.rstrip("/").split("/")[-1] values["model"] = derived return values def model_post_init(self, __context: Any) -> None: """ Initializes private attributes after validation. """ self._provider = "huggingface" # Set up the private config and client attributes self._config = self.get_config() self._client = self.get_client() return super().model_post_init(__context) def get_config(self) -> HFInferenceClientConfig: """ Returns the appropriate configuration for the Hugging Face Inference API. """ return HFInferenceClientConfig( model=self.model, hf_provider=self.hf_provider, api_key=self.api_key, base_url=self.base_url, headers=self.headers, cookies=self.cookies, proxies=self.proxies, timeout=self.timeout, ) def get_client(self) -> InferenceClient: """ Initializes and returns the Hugging Face Inference client. """ config: HFInferenceClientConfig = self.config return InferenceClient( model=config.model, provider=config.hf_provider, api_key=config.api_key, base_url=config.base_url, headers=config.headers, cookies=config.cookies, proxies=config.proxies, timeout=self.timeout, ) @classmethod def from_config( cls, client_options: HFInferenceClientConfig, timeout: float = 1500 ): """ Initializes the HFHubInferenceClientBase using HFInferenceClientConfig. Args: client_options: The configuration options for the client. timeout: Timeout for requests (default is 1500 seconds). Returns: HFHubInferenceClientBase: The initialized client instance. """ return cls( model=client_options.model, hf_provider=client_options.hf_provider, api_key=client_options.api_key, token=client_options.token, base_url=client_options.base_url, headers=client_options.headers, cookies=client_options.cookies, proxies=client_options.proxies, timeout=timeout, ) @property def config(self) -> Dict[str, Any]: return self._config @property def client(self) -> InferenceClient: return self._client