mirror of
https://github.com/smkrv/ha-text-ai.git
synced 2026-07-28 20:13:51 +08:00
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:
@@ -86,38 +86,69 @@ async def validate_api_connection(
|
|||||||
) -> Tuple[bool, str, list]:
|
) -> Tuple[bool, str, list]:
|
||||||
"""Validate API connection with retry logic."""
|
"""Validate API connection with retry logic."""
|
||||||
session = async_get_clientsession(hass)
|
session = async_get_clientsession(hass)
|
||||||
|
|
||||||
|
# Determine API type and configure headers
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {api_key}",
|
"Content-Type": "application/json",
|
||||||
"Content-Type": "application/json; charset=utf-8",
|
"Accept": "application/json"
|
||||||
"Accept": "application/json; charset=utf-8",
|
|
||||||
"Accept-Charset": "utf-8"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
base_url = endpoint.rstrip('/')
|
# Configure endpoint and headers based on service type
|
||||||
if not base_url.endswith(f"/{API_VERSION}"):
|
if "vsegpt" in endpoint.lower():
|
||||||
base_url = f"{base_url}/{API_VERSION}"
|
headers["Authorization"] = f"Bearer {api_key}"
|
||||||
|
base_url = endpoint.rstrip('/')
|
||||||
models_url = f"{base_url}/{API_MODELS_PATH}"
|
models_url = f"{base_url}/models"
|
||||||
_LOGGER.debug("Attempting to connect to: %s", models_url)
|
_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):
|
for attempt in range(retry_count):
|
||||||
try:
|
try:
|
||||||
async with timeout(10):
|
async with timeout(10):
|
||||||
async with session.get(models_url, headers=headers) as response:
|
async with session.get(models_url, headers=headers) as response:
|
||||||
|
_LOGGER.debug("API response status: %s", response.status)
|
||||||
|
|
||||||
if response.status == 200:
|
if response.status == 200:
|
||||||
data = await response.json()
|
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:
|
elif any(m in model.lower() for m in ["claude", "anthropic"]):
|
||||||
_LOGGER.warning(
|
model_ids = [m["id"] for m in data.get("models", [])]
|
||||||
"Model %s not found in available models: %s",
|
# Support for custom Anthropic model names
|
||||||
model,
|
if model.startswith("anthropic/"):
|
||||||
", ".join(model_ids)
|
model = model.split("/")[1]
|
||||||
)
|
if model in model_ids or any(m.endswith(model) for m in model_ids):
|
||||||
return False, ERROR_INVALID_MODEL, model_ids
|
return True, "", 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:
|
elif response.status == 401:
|
||||||
_LOGGER.error("Authentication failed")
|
_LOGGER.error("Authentication failed")
|
||||||
@@ -125,6 +156,9 @@ async def validate_api_connection(
|
|||||||
|
|
||||||
elif response.status == 429:
|
elif response.status == 429:
|
||||||
_LOGGER.error("Rate limit exceeded")
|
_LOGGER.error("Rate limit exceeded")
|
||||||
|
if attempt < retry_count - 1:
|
||||||
|
await asyncio.sleep(retry_delay * (2 ** attempt))
|
||||||
|
continue
|
||||||
return False, ERROR_RATE_LIMIT, []
|
return False, ERROR_RATE_LIMIT, []
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -134,6 +168,9 @@ async def validate_api_connection(
|
|||||||
response.status,
|
response.status,
|
||||||
response_text
|
response_text
|
||||||
)
|
)
|
||||||
|
if attempt < retry_count - 1:
|
||||||
|
await asyncio.sleep(retry_delay)
|
||||||
|
continue
|
||||||
return False, ERROR_API_ERROR, []
|
return False, ERROR_API_ERROR, []
|
||||||
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
@@ -142,9 +179,10 @@ async def validate_api_connection(
|
|||||||
attempt + 1,
|
attempt + 1,
|
||||||
retry_count
|
retry_count
|
||||||
)
|
)
|
||||||
if attempt == retry_count - 1:
|
if attempt < retry_count - 1:
|
||||||
return False, ERROR_TIMEOUT, []
|
await asyncio.sleep(retry_delay)
|
||||||
await asyncio.sleep(retry_delay)
|
continue
|
||||||
|
return False, ERROR_TIMEOUT, []
|
||||||
|
|
||||||
except aiohttp.ClientError as err:
|
except aiohttp.ClientError as err:
|
||||||
_LOGGER.error("Connection error: %s", str(err))
|
_LOGGER.error("Connection error: %s", str(err))
|
||||||
|
|||||||
@@ -15,17 +15,19 @@ CONF_REQUEST_INTERVAL: Final = "request_interval"
|
|||||||
|
|
||||||
# Model constants
|
# Model constants
|
||||||
SUPPORTED_MODELS: Final = [
|
SUPPORTED_MODELS: Final = [
|
||||||
"gpt-3.5-turbo",
|
"o1-preview",
|
||||||
"gpt-3.5-turbo-16k",
|
"o1-mini",
|
||||||
"gpt-4",
|
"gpt-4o-mini",
|
||||||
"gpt-4-32k",
|
"gpt-4o",
|
||||||
"gpt-4-1106-preview",
|
"claude-3-5-haiku",
|
||||||
"claude-3-sonnet",
|
"claude-3.5-sonnet",
|
||||||
"claude-3-opus"
|
"claude-3-haiku",
|
||||||
|
"anthropic/claude-3-5-haiku",
|
||||||
|
"anthropic/claude-3.5-sonnet",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Default values
|
# Default values
|
||||||
DEFAULT_MODEL: Final = "gpt-3.5-turbo"
|
DEFAULT_MODEL: Final = "gpt-4o-mini"
|
||||||
DEFAULT_TEMPERATURE: Final = 0.1
|
DEFAULT_TEMPERATURE: Final = 0.1
|
||||||
DEFAULT_MAX_TOKENS: Final = 1000
|
DEFAULT_MAX_TOKENS: Final = 1000
|
||||||
DEFAULT_API_ENDPOINT: Final = "https://api.openai.com"
|
DEFAULT_API_ENDPOINT: Final = "https://api.openai.com"
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import logging
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Any, Dict, Optional, List
|
from typing import Any, Dict, Optional, List
|
||||||
import time
|
import time
|
||||||
|
import ssl
|
||||||
|
import certifi
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
from openai import AsyncOpenAI, APIError, AuthenticationError, RateLimitError
|
from openai import AsyncOpenAI, APIError, AuthenticationError, RateLimitError
|
||||||
from anthropic import AsyncAnthropic
|
from anthropic import AsyncAnthropic
|
||||||
@@ -38,19 +41,7 @@ class HATextAICoordinator(DataUpdateCoordinator):
|
|||||||
session: Optional[Any] = None,
|
session: Optional[Any] = None,
|
||||||
is_anthropic: bool = False,
|
is_anthropic: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize coordinator.
|
"""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
|
|
||||||
"""
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
hass,
|
hass,
|
||||||
_LOGGER,
|
_LOGGER,
|
||||||
@@ -86,15 +77,36 @@ class HATextAICoordinator(DataUpdateCoordinator):
|
|||||||
}
|
}
|
||||||
self._last_request_time = 0
|
self._last_request_time = 0
|
||||||
self._is_anthropic = is_anthropic
|
self._is_anthropic = is_anthropic
|
||||||
|
self._session = session or aiohttp_client.async_get_clientsession(hass)
|
||||||
|
|
||||||
if is_anthropic:
|
self._init_client()
|
||||||
self.client = AsyncAnthropic(api_key=self.api_key)
|
|
||||||
else:
|
async def _create_ssl_context(self):
|
||||||
self.client = AsyncOpenAI(
|
"""Create an async SSL context."""
|
||||||
api_key=self.api_key,
|
ssl_context = ssl.create_default_context(cafile=certifi.where())
|
||||||
base_url=self.endpoint,
|
return ssl_context
|
||||||
http_client=session,
|
|
||||||
)
|
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:
|
def _validate_params(self, api_key: str, temperature: float, max_tokens: int) -> None:
|
||||||
"""Validate initialization parameters."""
|
"""Validate initialization parameters."""
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
"openai>=1.12.0",
|
"openai>=1.12.0",
|
||||||
"anthropic>=0.8.0",
|
"anthropic>=0.8.0",
|
||||||
"aiohttp>=3.8.0",
|
"aiohttp>=3.8.0",
|
||||||
"async-timeout>=4.0.0"
|
"async-timeout>=4.0.0",
|
||||||
|
"certifi>=2024.2.2"
|
||||||
],
|
],
|
||||||
"ssdp": [],
|
"ssdp": [],
|
||||||
"usb": [],
|
"usb": [],
|
||||||
|
|||||||
Reference in New Issue
Block a user