Release v2.0.0

This commit is contained in:
SMKRV
2024-11-24 23:14:48 +03:00
parent d05b39d8ae
commit b61429b52d
3 changed files with 207 additions and 38 deletions
+22 -12
View File
@@ -18,6 +18,7 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import aiohttp_client
from .coordinator import HATextAICoordinator
from .api_client import APIClient
from .const import (
DOMAIN,
PLATFORMS,
@@ -68,11 +69,9 @@ SERVICE_SCHEMA_GET_HISTORY = vol.Schema({
def get_coordinator_by_instance(hass: HomeAssistant, instance: str) -> HATextAICoordinator:
"""Get coordinator by instance name."""
# Убираем префикс "sensor." если он есть
if instance.startswith("sensor."):
instance = instance.replace("sensor.ha_text_ai_", "", 1)
# Ищем координатор по instance_name
for entry_id, coord in hass.data[DOMAIN].items():
if isinstance(coord, HATextAICoordinator) and coord.instance_name.lower() == instance.lower():
return coord
@@ -225,14 +224,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if not await async_check_api(session, endpoint, headers, api_provider):
raise ConfigEntryNotReady("API connection failed")
_LOGGER.debug("Creating API client for %s with endpoint %s", api_provider, endpoint)
# Создаем API клиент
api_client = {
"session": session,
"endpoint": endpoint,
"headers": headers,
"api_provider": api_provider,
"model": model,
}
api_client = APIClient(
session=session,
endpoint=endpoint,
headers=headers,
api_provider=api_provider,
model=model,
)
coordinator = HATextAICoordinator(
hass=hass,
@@ -245,15 +246,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
is_anthropic=is_anthropic,
)
# Инициализация координатора
await coordinator.async_config_entry_first_refresh()
# Сохраняем координатор
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = coordinator
# Загружаем платформы
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
_LOGGER.info(
"Successfully set up %s instance '%s' with model %s",
api_provider,
instance_name,
model
)
return True
except Exception as ex:
@@ -265,6 +271,10 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
try:
if entry.entry_id in hass.data[DOMAIN]:
coordinator = hass.data[DOMAIN][entry.entry_id]
if hasattr(coordinator.client, 'shutdown'):
await coordinator.client.shutdown()
await coordinator.async_shutdown()
hass.data[DOMAIN].pop(entry.entry_id)
+128
View File
@@ -0,0 +1,128 @@
"""API Client for HA Text AI."""
import logging
from typing import Any, Dict, List, Optional
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
_LOGGER = logging.getLogger(__name__)
class APIClient:
"""API Client for OpenAI and Anthropic."""
def __init__(
self,
session: Any,
endpoint: str,
headers: Dict[str, str],
api_provider: str,
model: str,
) -> None:
"""Initialize API client."""
self.session = session
self.endpoint = endpoint
self.headers = headers
self.api_provider = api_provider
self.model = model
async def create(
self,
model: str,
messages: List[Dict[str, str]],
temperature: float,
max_tokens: int,
) -> Dict[str, Any]:
"""Create completion using appropriate API."""
try:
if self.api_provider == "anthropic":
return await self._create_anthropic_completion(
model, messages, temperature, max_tokens
)
else:
return await self._create_openai_completion(
model, messages, temperature, max_tokens
)
except Exception as e:
_LOGGER.error("API request failed: %s", str(e))
raise HomeAssistantError(f"API request failed: {str(e)}")
async def _create_openai_completion(
self,
model: str,
messages: List[Dict[str, str]],
temperature: float,
max_tokens: int,
) -> Dict[str, Any]:
"""Create completion using OpenAI API."""
url = f"{self.endpoint}/chat/completions"
payload = {
"model": model,
"messages": messages,
"temperature": temperature,
"max_tokens": max_tokens,
}
async with self.session.post(url, json=payload, headers=self.headers) as response:
if response.status != 200:
error_data = await response.json()
raise HomeAssistantError(f"OpenAI API error: {error_data}")
data = await response.json()
return {
"choices": [
{
"message": {
"content": data["choices"][0]["message"]["content"]
}
}
],
"usage": {
"prompt_tokens": data["usage"]["prompt_tokens"],
"completion_tokens": data["usage"]["completion_tokens"],
"total_tokens": data["usage"]["total_tokens"]
}
}
async def _create_anthropic_completion(
self,
model: str,
messages: List[Dict[str, str]],
temperature: float,
max_tokens: int,
) -> Dict[str, Any]:
"""Create completion using Anthropic API."""
url = f"{self.endpoint}/v1/messages"
# Convert messages to Anthropic format
system_prompt = next((msg["content"] for msg in messages if msg["role"] == "system"), None)
conversation = [msg for msg in messages if msg["role"] != "system"]
payload = {
"model": model,
"messages": conversation,
"max_tokens": max_tokens,
"temperature": temperature,
}
if system_prompt:
payload["system"] = system_prompt
async with self.session.post(url, json=payload, headers=self.headers) as response:
if response.status != 200:
error_data = await response.json()
raise HomeAssistantError(f"Anthropic API error: {error_data}")
data = await response.json()
return {
"choices": [
{
"message": {
"content": data["content"][0]["text"]
}
}
],
"usage": {
"prompt_tokens": data["usage"]["input_tokens"],
"completion_tokens": data["usage"]["output_tokens"],
"total_tokens": data["usage"]["input_tokens"] + data["usage"]["output_tokens"]
}
}
+57 -26
View File
@@ -1,23 +1,49 @@
"""DataUpdateCoordinator for HA Text AI."""
from datetime import timedelta
"""The HA Text AI integration."""
from __future__ import annotations
import logging
from typing import Any, Dict, List, Optional
import os
import shutil
from datetime import datetime, timedelta
from typing import Any, Dict
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.util import dt as dt_util
import voluptuous as vol
from async_timeout import timeout
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_NAME, Platform
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import aiohttp_client
from .coordinator import HATextAICoordinator
from .api_client import APIClient
from .const import (
DOMAIN,
STATE_READY,
STATE_PROCESSING,
STATE_ERROR,
STATE_RATE_LIMITED,
STATE_MAINTENANCE,
PLATFORMS,
CONF_MODEL,
CONF_TEMPERATURE,
CONF_MAX_TOKENS,
CONF_API_ENDPOINT,
CONF_REQUEST_INTERVAL,
CONF_API_PROVIDER,
API_PROVIDER_OPENAI,
API_PROVIDER_ANTHROPIC,
DEFAULT_MODEL,
DEFAULT_TEMPERATURE,
DEFAULT_MAX_TOKENS,
DEFAULT_OPENAI_ENDPOINT,
DEFAULT_ANTHROPIC_ENDPOINT,
DEFAULT_REQUEST_INTERVAL,
API_TIMEOUT,
SERVICE_ASK_QUESTION,
SERVICE_CLEAR_HISTORY,
SERVICE_GET_HISTORY,
SERVICE_SET_SYSTEM_PROMPT,
)
_LOGGER = logging.getLogger(__name__)
_LOGGER = logging.getLogger(__name__)
class HATextAICoordinator(DataUpdateCoordinator):
"""Class to manage fetching data from the API."""
@@ -239,20 +265,25 @@ class HATextAICoordinator(DataUpdateCoordinator):
async def _process_openai_message(self, question: str, **kwargs) -> dict:
"""Process message using OpenAI API."""
response = await self.client.chat.completions.create(
model=kwargs["model"],
messages=kwargs["messages"],
temperature=kwargs["temperature"],
max_tokens=kwargs["max_tokens"],
)
return {
"content": response.choices[0].message.content,
"tokens": {
"prompt": response.usage.prompt_tokens,
"completion": response.usage.completion_tokens,
"total": response.usage.total_tokens
try:
response = await self.client.create(
model=kwargs["model"],
messages=kwargs["messages"],
temperature=kwargs["temperature"],
max_tokens=kwargs["max_tokens"],
)
return {
"content": response["choices"][0]["message"]["content"],
"tokens": {
"prompt": response["usage"]["prompt_tokens"],
"completion": response["usage"]["completion_tokens"],
"total": response["usage"]["total_tokens"]
}
}
}
except Exception as e:
_LOGGER.error(f"Error in OpenAI API call: {str(e)}")
raise
def _update_metrics(self, latency: float, response: dict) -> None:
"""Update performance metrics."""