Resoved blocking SSL verification issue in

coordinator.py

 API endpoint handling in config_flow.py
 changes

 Added support for the custom models in const.py

 Requirements in manifest.json updated
This commit is contained in:
SMKRV
2024-11-20 12:07:30 +03:00
parent 8f796ad9dc
commit 58a7ae4229
4 changed files with 108 additions and 55 deletions
+61 -23
View File
@@ -86,38 +86,69 @@ async def validate_api_connection(
) -> Tuple[bool, str, list]:
"""Validate API connection with retry logic."""
session = async_get_clientsession(hass)
# Determine API type and configure headers
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json; charset=utf-8",
"Accept": "application/json; charset=utf-8",
"Accept-Charset": "utf-8"
"Content-Type": "application/json",
"Accept": "application/json"
}
base_url = endpoint.rstrip('/')
if not base_url.endswith(f"/{API_VERSION}"):
base_url = f"{base_url}/{API_VERSION}"
models_url = f"{base_url}/{API_MODELS_PATH}"
_LOGGER.debug("Attempting to connect to: %s", models_url)
# Configure endpoint and headers based on service type
if "vsegpt" in endpoint.lower():
headers["Authorization"] = f"Bearer {api_key}"
base_url = endpoint.rstrip('/')
models_url = f"{base_url}/models"
_LOGGER.debug("Using VSE GPT endpoint: %s", models_url)
elif any(m in model.lower() for m in ["claude", "anthropic"]):
headers["x-api-key"] = api_key
headers["anthropic-version"] = "2023-06-01"
base_url = endpoint.rstrip('/')
if not base_url.endswith("/v1"):
base_url = f"{base_url}/v1"
models_url = f"{base_url}/models"
_LOGGER.debug("Using Anthropic endpoint: %s", models_url)
else:
headers["Authorization"] = f"Bearer {api_key}"
base_url = endpoint.rstrip('/')
if not base_url.endswith(f"/{API_VERSION}"):
base_url = f"{base_url}/{API_VERSION}"
models_url = f"{base_url}/{API_MODELS_PATH}"
_LOGGER.debug("Using OpenAI endpoint: %s", models_url)
for attempt in range(retry_count):
try:
async with timeout(10):
async with session.get(models_url, headers=headers) as response:
_LOGGER.debug("API response status: %s", response.status)
if response.status == 200:
data = await response.json()
model_ids = [m["id"] for m in data.get("data", [])]
_LOGGER.debug("Available models: %s", ", ".join(model_ids))
# Handle different API response formats
if "vsegpt" in endpoint.lower():
model_ids = [m["id"] for m in data.get("data", [])]
# Add VSE GPT specific model handling if needed
return True, "", model_ids
if model not in model_ids:
_LOGGER.warning(
"Model %s not found in available models: %s",
model,
", ".join(model_ids)
)
return False, ERROR_INVALID_MODEL, model_ids
return True, "", model_ids
elif any(m in model.lower() for m in ["claude", "anthropic"]):
model_ids = [m["id"] for m in data.get("models", [])]
# Support for custom Anthropic model names
if model.startswith("anthropic/"):
model = model.split("/")[1]
if model in model_ids or any(m.endswith(model) for m in model_ids):
return True, "", model_ids
else: # OpenAI format
model_ids = [m["id"] for m in data.get("data", [])]
if model in model_ids:
return True, "", model_ids
_LOGGER.warning(
"Model %s not found in available models: %s",
model,
", ".join(model_ids)
)
return False, ERROR_INVALID_MODEL, model_ids
elif response.status == 401:
_LOGGER.error("Authentication failed")
@@ -125,6 +156,9 @@ async def validate_api_connection(
elif response.status == 429:
_LOGGER.error("Rate limit exceeded")
if attempt < retry_count - 1:
await asyncio.sleep(retry_delay * (2 ** attempt))
continue
return False, ERROR_RATE_LIMIT, []
else:
@@ -134,6 +168,9 @@ async def validate_api_connection(
response.status,
response_text
)
if attempt < retry_count - 1:
await asyncio.sleep(retry_delay)
continue
return False, ERROR_API_ERROR, []
except asyncio.TimeoutError:
@@ -142,9 +179,10 @@ async def validate_api_connection(
attempt + 1,
retry_count
)
if attempt == retry_count - 1:
return False, ERROR_TIMEOUT, []
await asyncio.sleep(retry_delay)
if attempt < retry_count - 1:
await asyncio.sleep(retry_delay)
continue
return False, ERROR_TIMEOUT, []
except aiohttp.ClientError as err:
_LOGGER.error("Connection error: %s", str(err))
+11 -9
View File
@@ -15,17 +15,19 @@ CONF_REQUEST_INTERVAL: Final = "request_interval"
# Model constants
SUPPORTED_MODELS: Final = [
"gpt-3.5-turbo",
"gpt-3.5-turbo-16k",
"gpt-4",
"gpt-4-32k",
"gpt-4-1106-preview",
"claude-3-sonnet",
"claude-3-opus"
"o1-preview",
"o1-mini",
"gpt-4o-mini",
"gpt-4o",
"claude-3-5-haiku",
"claude-3.5-sonnet",
"claude-3-haiku",
"anthropic/claude-3-5-haiku",
"anthropic/claude-3.5-sonnet",
]
# Default values
DEFAULT_MODEL: Final = "gpt-3.5-turbo"
DEFAULT_MODEL: Final = "gpt-4o-mini"
DEFAULT_TEMPERATURE: Final = 0.1
DEFAULT_MAX_TOKENS: Final = 1000
DEFAULT_API_ENDPOINT: Final = "https://api.openai.com"
@@ -170,7 +172,7 @@ LOG_LEVEL_DEFAULT: Final = "INFO"
QUEUE_TIMEOUT: Final = 5
QUEUE_MAX_SIZE: Final = 100
# Retry constants
# Retry constants
MAX_RETRIES: Final = 3
RETRY_DELAY: Final = 1.0
+34 -22
View File
@@ -4,6 +4,9 @@ import logging
from datetime import timedelta
from typing import Any, Dict, Optional, List
import time
import ssl
import certifi
import aiohttp
from openai import AsyncOpenAI, APIError, AuthenticationError, RateLimitError
from anthropic import AsyncAnthropic
@@ -38,19 +41,7 @@ class HATextAICoordinator(DataUpdateCoordinator):
session: Optional[Any] = None,
is_anthropic: bool = False,
) -> None:
"""Initialize coordinator.
Args:
hass: HomeAssistant instance
api_key: API key for the service
endpoint: API endpoint URL
model: Model name to use
temperature: Temperature parameter for generation
max_tokens: Maximum tokens to generate
request_interval: Interval between requests
session: Optional session object
is_anthropic: Whether to use Anthropic API
"""
"""Initialize coordinator."""
super().__init__(
hass,
_LOGGER,
@@ -86,15 +77,36 @@ class HATextAICoordinator(DataUpdateCoordinator):
}
self._last_request_time = 0
self._is_anthropic = is_anthropic
self._session = session or aiohttp_client.async_get_clientsession(hass)
if is_anthropic:
self.client = AsyncAnthropic(api_key=self.api_key)
else:
self.client = AsyncOpenAI(
api_key=self.api_key,
base_url=self.endpoint,
http_client=session,
)
self._init_client()
async def _create_ssl_context(self):
"""Create an async SSL context."""
ssl_context = ssl.create_default_context(cafile=certifi.where())
return ssl_context
async def _init_client(self):
"""Initialize API client with proper SSL context."""
try:
ssl_context = await self._create_ssl_context()
connector = aiohttp.TCPConnector(ssl=ssl_context)
client_session = aiohttp.ClientSession(connector=connector)
if self._is_anthropic:
self.client = AsyncAnthropic(
api_key=self.api_key,
http_client=client_session
)
else:
self.client = AsyncOpenAI(
api_key=self.api_key,
base_url=self.endpoint,
http_client=client_session,
)
except Exception as e:
_LOGGER.error("Error initializing API client: %s", str(e))
raise
def _validate_params(self, api_key: str, temperature: float, max_tokens: int) -> None:
"""Validate initialization parameters."""
@@ -600,7 +612,7 @@ class HATextAICoordinator(DataUpdateCoordinator):
return
seen_questions = set()
optimized_queue = asyncio.PriorityQueue(maxsize=QUEUE_MAX_SIZE)
optimized_queue = asyncio.PriorityQueue(maxsize=QUEUE_MAX_SIZE)
while not self._question_queue.empty():
try:
+2 -1
View File
@@ -17,7 +17,8 @@
"openai>=1.12.0",
"anthropic>=0.8.0",
"aiohttp>=3.8.0",
"async-timeout>=4.0.0"
"async-timeout>=4.0.0",
"certifi>=2024.2.2"
],
"ssdp": [],
"usb": [],