Compare commits

...
16 Commits
Author SHA1 Message Date
SMKRV 9a7635c2ae Release v1.1.0 2024-11-19 18:55:14 +03:00
SMKRV df3d79c20c feat: add multi-provider support,
config improvements
2024-11-19 18:53:51 +03:00
SMKRV d6144be7ed v.1.0.10 2024-11-19 17:49:48 +03:00
SMKRV 9afbb904b3 __init__.py:
added: from .coordinator import HATextAICoordinator
2024-11-19 17:48:00 +03:00
SMKRV 93558b2444 Version update 2024-11-19 17:34:27 +03:00
SMKRV 9f93f1ee18 Main changes:
Removed the validate_endpoint function
Optimized the validate_api_connection function
Simplified API connection verification
Preserved all error handling and retry logic
Improved exception handling
The integration should now correctly verify the
OpenAI API connection without false endpoint_not_available errors.
2024-11-19 17:33:27 +03:00
SMKRV 30a9b53ba1 Markdown changes 2024-11-19 17:18:23 +03:00
SMKRV 5175970d55 structure.md added 2024-11-19 17:16:30 +03:00
SMKRV f6bfbd4a07 Release v1.0.8 2024-11-19 17:02:38 +03:00
SMKRV 4ddb0dc977 Translation fixes 2024-11-19 16:59:42 +03:00
SMKRV 24dc4ac4d4 Hotfix 2024-11-19 16:53:27 +03:00
SMKRV 976f4f16a3 translation fixes 2024-11-19 16:40:56 +03:00
SMKRV 2bdef2b494 Main changes:
Created global SSL_CONTEXT at module level
Removed blocking create_default_context calls from async functions
Optimized aiohttp.ClientSession handling:
Using single connector with SSL context
Session is created once for all requests in validate_api_connection
Improved resource management:
Automatic session closure using context managers
More efficient connection handling
These changes should eliminate the blocking call warning and improve
overall code performance.
2024-11-19 16:33:30 +03:00
SMKRV 42324a793b bufix 2024-11-19 16:25:44 +03:00
SMKRV 30aa894634 Release v1.0.6 2024-11-19 15:10:06 +03:00
SMKRV 398b2550a9 Release v1.0.5 2024-11-19 14:46:12 +03:00
11 changed files with 488 additions and 481 deletions
+156 -19
View File
@@ -1,18 +1,35 @@
"""The HA Text AI integration."""
import logging
from typing import Any
from typing import Any, Dict, Optional
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client
from homeassistant.helpers import config_validation as cv
from .const import DOMAIN, PLATFORMS
from .coordinator import HATextAICoordinator
from .const import (
DOMAIN,
PLATFORMS,
CONF_MODEL,
CONF_TEMPERATURE,
CONF_MAX_TOKENS,
CONF_API_ENDPOINT,
CONF_REQUEST_INTERVAL,
DEFAULT_MODEL,
DEFAULT_TEMPERATURE,
DEFAULT_MAX_TOKENS,
DEFAULT_API_ENDPOINT,
DEFAULT_REQUEST_INTERVAL,
)
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool:
"""Set up the HA Text AI component."""
hass.data.setdefault(DOMAIN, {})
@@ -26,11 +43,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
coordinator = HATextAICoordinator(
hass,
api_key=entry.data[CONF_API_KEY],
endpoint=entry.data.get("api_endpoint", "https://api.openai.com/v1"),
model=entry.data.get("model", "gpt-3.5-turbo"),
temperature=entry.data.get("temperature", 0.7),
max_tokens=entry.data.get("max_tokens", 1000),
request_interval=entry.data.get("request_interval", 1.0),
endpoint=entry.data.get(CONF_API_ENDPOINT, DEFAULT_API_ENDPOINT),
model=entry.data.get(CONF_MODEL, DEFAULT_MODEL),
temperature=entry.data.get(CONF_TEMPERATURE, DEFAULT_TEMPERATURE),
max_tokens=entry.data.get(CONF_MAX_TOKENS, DEFAULT_MAX_TOKENS),
request_interval=entry.data.get(CONF_REQUEST_INTERVAL, DEFAULT_REQUEST_INTERVAL),
session=session,
)
@@ -38,30 +55,145 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator.async_config_entry_first_refresh()
except Exception as refresh_ex:
_LOGGER.error("Failed to refresh coordinator: %s", str(refresh_ex))
return False
raise ConfigEntryNotReady from refresh_ex
if not coordinator.last_update_success:
_LOGGER.error("Failed to communicate with OpenAI API")
return False
raise ConfigEntryNotReady("Failed to communicate with OpenAI API")
hass.data[DOMAIN][entry.entry_id] = coordinator
try:
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
except Exception as setup_ex:
_LOGGER.error("Failed to setup platforms: %s", str(setup_ex))
return False
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
async def async_ask_question(call: ServiceCall) -> None:
"""Handle the ask_question service call."""
question = call.data.get("question", "")
if not question:
_LOGGER.error("No question provided in service call")
return
# Собираем все опциональные параметры
request_params = {}
# Обработка system_prompt
system_prompt = call.data.get("system_prompt")
if system_prompt is not None:
request_params["system_prompt"] = system_prompt
# Обработка model
model = call.data.get("model")
if model is not None:
request_params["model"] = model
# Обработка temperature
temperature = call.data.get("temperature")
if temperature is not None:
try:
request_params["temperature"] = float(temperature)
except ValueError:
_LOGGER.error("Invalid temperature value: %s", temperature)
return
# Обработка max_tokens
max_tokens = call.data.get("max_tokens")
if max_tokens is not None:
try:
request_params["max_tokens"] = int(max_tokens)
except ValueError:
_LOGGER.error("Invalid max_tokens value: %s", max_tokens)
return
try:
await coordinator.async_ask_question(question, **request_params)
except Exception as err:
_LOGGER.error("Error asking question: %s", str(err))
async def async_clear_history(call: ServiceCall) -> None:
"""Handle the clear_history service call."""
try:
coordinator._responses.clear()
await coordinator.async_refresh()
_LOGGER.info("History cleared successfully")
except Exception as err:
_LOGGER.error("Error clearing history: %s", str(err))
async def async_get_history(call: ServiceCall) -> dict:
"""Handle the get_history service call."""
try:
limit = call.data.get("limit", 10)
filter_model = call.data.get("filter_model", "")
responses = coordinator._responses
# Применяем фильтрацию по модели
if filter_model:
filtered_responses = {
k: v for k, v in responses.items()
if v.get("model") == filter_model
}
else:
filtered_responses = responses.copy()
# Сортируем по времени и ограничиваем количество
sorted_responses = dict(
sorted(
filtered_responses.items(),
key=lambda x: x[1]["timestamp"],
reverse=True
)[:limit]
)
return sorted_responses
except Exception as err:
_LOGGER.error("Error getting history: %s", str(err))
return {}
async def async_set_system_prompt(call: ServiceCall) -> None:
"""Handle the set_system_prompt service call."""
prompt = call.data.get("prompt", "")
if prompt:
try:
coordinator.system_prompt = prompt
_LOGGER.info("System prompt updated successfully")
except Exception as err:
_LOGGER.error("Error setting system prompt: %s", str(err))
else:
_LOGGER.error("No prompt provided in service call")
# Регистрация сервисов
hass.services.async_register(
DOMAIN,
"ask_question",
async_ask_question
)
hass.services.async_register(
DOMAIN,
"clear_history",
async_clear_history
)
hass.services.async_register(
DOMAIN,
"get_history",
async_get_history
)
hass.services.async_register(
DOMAIN,
"set_system_prompt",
async_set_system_prompt
)
_LOGGER.info(
"Successfully set up HA Text AI with model: %s",
entry.data.get("model", "gpt-3.5-turbo")
entry.data.get(CONF_MODEL, DEFAULT_MODEL)
)
return True
except Exception as ex:
_LOGGER.exception("Unexpected error setting up entry: %s", str(ex))
return False
raise ConfigEntryNotReady from ex
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
@@ -69,6 +201,11 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if entry.entry_id not in hass.data.get(DOMAIN, {}):
return True
# Удаляем все сервисы при выгрузке интеграции
services = ["ask_question", "clear_history", "get_history", "set_system_prompt"]
for service in services:
hass.services.async_remove(DOMAIN, service)
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
coordinator = hass.data[DOMAIN].pop(entry.entry_id)
@@ -86,4 +223,4 @@ async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
await async_unload_entry(hass, entry)
await async_setup_entry(hass, entry)
except Exception as ex:
_LOGGER.exception("Error reloading entry: %s", str(ex))
_LOGGER.exception("Error reloading entry: %s", str(ex)) # убрано лишнее двоеточие
+34 -55
View File
@@ -32,6 +32,9 @@ from .const import (
import logging
_LOGGER = logging.getLogger(__name__)
# Create SSL context at module level
SSL_CONTEXT = ssl.create_default_context(cafile=certifi.where())
STEP_USER_DATA_SCHEMA = vol.Schema({
vol.Required(CONF_API_KEY): str,
vol.Optional(CONF_MODEL, default=DEFAULT_MODEL): str,
@@ -40,50 +43,25 @@ STEP_USER_DATA_SCHEMA = vol.Schema({
default=DEFAULT_TEMPERATURE
): vol.All(
vol.Coerce(float),
vol.Range(min=0, max=2),
msg="Temperature must be between 0 and 2"
vol.Range(min=0, max=2)
),
vol.Optional(
CONF_MAX_TOKENS,
default=DEFAULT_MAX_TOKENS
): vol.All(
vol.Coerce(int),
vol.Range(min=1, max=4096),
msg="Max tokens must be between 1 and 4096"
),
vol.Optional(CONF_API_ENDPOINT, default=DEFAULT_API_ENDPOINT): vol.All(
str,
vol.URL(),
msg="Must be a valid URL"
vol.Range(min=1, max=4096)
),
vol.Optional(CONF_API_ENDPOINT, default=DEFAULT_API_ENDPOINT): str,
vol.Optional(
CONF_REQUEST_INTERVAL,
default=DEFAULT_REQUEST_INTERVAL
): vol.All(
vol.Coerce(float),
vol.Range(min=0.1),
msg="Request interval must be at least 0.1 seconds"
vol.Range(min=0.1)
),
})
async def validate_endpoint(endpoint: str) -> Tuple[bool, str]:
"""Validate API endpoint accessibility."""
try:
parsed_url = urlparse(endpoint)
if parsed_url.scheme not in ('http', 'https'):
return False, "invalid_endpoint_scheme"
ssl_context = ssl.create_default_context(cafile=certifi.where())
async with timeout(5):
async with aiohttp.ClientSession() as session:
async with session.get(endpoint, ssl=ssl_context) as response:
if response.status != 200:
return False, "endpoint_not_available"
return True, ""
except Exception as e:
_LOGGER.error("Error validating endpoint: %s", str(e))
return False, "endpoint_error"
async def validate_api_connection(
api_key: str,
endpoint: str,
@@ -91,33 +69,17 @@ async def validate_api_connection(
retry_count: int = 3,
retry_delay: float = 1.0
) -> Tuple[bool, str, list]:
"""Validate API connection with improved retry logic."""
ssl_context = ssl.create_default_context(cafile=certifi.where())
# Validate endpoint first
endpoint_valid, endpoint_error = await validate_endpoint(endpoint)
if not endpoint_valid:
return False, endpoint_error, []
"""Validate API connection with retry logic."""
for attempt in range(retry_count):
try:
async with timeout(10):
client = AsyncOpenAI(
api_key=api_key,
base_url=endpoint,
http_client=aiohttp.ClientSession(
connector=aiohttp.TCPConnector(
ssl=ssl_context,
enable_cleanup_closed=True
)
)
)
try:
models = await client.models.list()
model_ids = [model.id for model in models.data]
finally:
await client.http_client.close()
models = await client.models.list()
model_ids = [model.id for model in models.data]
if model not in model_ids:
_LOGGER.warning(
@@ -172,12 +134,32 @@ class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
if user_input is not None:
try:
# Validate URL format
endpoint = user_input.get(CONF_API_ENDPOINT, DEFAULT_API_ENDPOINT)
try:
result = urlparse(endpoint)
if not all([result.scheme, result.netloc]):
errors["base"] = "invalid_url_format"
return self.async_show_form(
step_id="user",
data_schema=STEP_USER_DATA_SCHEMA,
errors=errors
)
except Exception as e:
_LOGGER.error("URL parsing error: %s", str(e))
errors["base"] = "invalid_url_format"
return self.async_show_form(
step_id="user",
data_schema=STEP_USER_DATA_SCHEMA,
errors=errors
)
# Validate input data
user_input = STEP_USER_DATA_SCHEMA(user_input)
is_valid, error_code, available_models = await validate_api_connection(
user_input[CONF_API_KEY],
user_input.get(CONF_API_ENDPOINT, DEFAULT_API_ENDPOINT),
endpoint,
user_input[CONF_MODEL]
)
@@ -244,8 +226,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
description={"suggested_value": DEFAULT_TEMPERATURE},
): vol.All(
vol.Coerce(float),
vol.Range(min=0, max=2),
msg="Temperature must be between 0 and 2"
vol.Range(min=0, max=2)
),
vol.Optional(
CONF_MAX_TOKENS,
@@ -255,8 +236,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
description={"suggested_value": DEFAULT_MAX_TOKENS},
): vol.All(
vol.Coerce(int),
vol.Range(min=1, max=4096),
msg="Max tokens must be between 1 and 4096"
vol.Range(min=1, max=4096)
),
vol.Optional(
CONF_REQUEST_INTERVAL,
@@ -266,8 +246,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
description={"suggested_value": DEFAULT_REQUEST_INTERVAL},
): vol.All(
vol.Coerce(float),
vol.Range(min=0.1),
msg="Request interval must be at least 0.1 seconds"
vol.Range(min=0.1)
),
})
+54 -13
View File
@@ -71,14 +71,27 @@ class HATextAICoordinator(DataUpdateCoordinator):
try:
async with async_timeout.timeout(30):
question = await self._question_queue.get()
question_data = await self._question_queue.get()
question = question_data["question"]
params = question_data["params"]
try:
response_content = await self._make_api_call(question)
response_content = await self._make_api_call(
question,
model=params.get("model"),
temperature=params.get("temperature"),
max_tokens=params.get("max_tokens"),
system_prompt=params.get("system_prompt")
)
self._responses[question] = {
"question": question,
"response": response_content,
"error": None,
"timestamp": self.hass.loop.time()
"timestamp": self.hass.loop.time(),
"model": params.get("model", self.model),
"temperature": params.get("temperature", self.temperature),
"max_tokens": params.get("max_tokens", self.max_tokens)
}
self._error_count = 0
self._is_ready = True
@@ -113,7 +126,10 @@ class HATextAICoordinator(DataUpdateCoordinator):
"question": question,
"response": None,
"error": error_msg,
"timestamp": self.hass.loop.time()
"timestamp": self.hass.loop.time(),
"model": self.model,
"temperature": self.temperature,
"max_tokens": self.max_tokens
}
_LOGGER.error("API error (%s): %s", type(error).__name__, error_msg)
@@ -136,19 +152,27 @@ class HATextAICoordinator(DataUpdateCoordinator):
except Exception as err:
_LOGGER.error("Error clearing question queue: %s", err)
async def _make_api_call(self, question: str) -> str:
async def _make_api_call(
self,
question: str,
model: Optional[str] = None,
temperature: Optional[float] = None,
max_tokens: Optional[int] = None,
system_prompt: Optional[str] = None
) -> str:
"""Make API call to OpenAI."""
try:
messages = []
if self.system_prompt:
messages.append({"role": "system", "content": self.system_prompt})
current_system_prompt = system_prompt if system_prompt is not None else self.system_prompt
if current_system_prompt:
messages.append({"role": "system", "content": current_system_prompt})
messages.append({"role": "user", "content": question})
completion = await self.client.chat.completions.create(
model=self.model,
model=model or self.model,
messages=messages,
temperature=self.temperature,
max_tokens=self.max_tokens,
temperature=temperature if temperature is not None else self.temperature,
max_tokens=max_tokens if max_tokens is not None else self.max_tokens,
)
return completion.choices[0].message.content
@@ -156,13 +180,30 @@ class HATextAICoordinator(DataUpdateCoordinator):
_LOGGER.error("Error in API call: %s", err)
raise
async def async_ask_question(self, question: str) -> None:
"""Add question to queue."""
async def async_ask_question(
self,
question: str,
system_prompt: Optional[str] = None,
model: Optional[str] = None,
temperature: Optional[float] = None,
max_tokens: Optional[int] = None
) -> None:
"""Add question to queue with optional parameters."""
if not self._is_ready and self._error_count >= self._MAX_ERRORS:
_LOGGER.warning("Coordinator is not ready due to previous errors")
return
await self._question_queue.put(question)
question_data = {
"question": question,
"params": {
"system_prompt": system_prompt,
"model": model,
"temperature": temperature,
"max_tokens": max_tokens
}
}
await self._question_queue.put(question_data)
await self.async_refresh()
async def async_shutdown(self) -> None:
+1 -1
View File
@@ -9,6 +9,6 @@
"issue_tracker": "https://github.com/smkrv/ha-text-ai/issues",
"requirements": ["openai>=1.0.0"],
"ssdp": [],
"version": "1.0.4",
"version": "1.1.0",
"zeroconf": []
}
+25 -5
View File
@@ -117,21 +117,35 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
return attributes
try:
history = list(self.coordinator.data.items())
# Получаем историю ответов
history = list(self.coordinator._responses.items())
if history:
last_question, last_data = history[-1]
# Handle different response formats
# Обработка формата ответа
if isinstance(last_data, dict):
last_response = last_data.get("response", "")
last_updated = last_data.get("timestamp", self.coordinator.last_update_success_time)
response_time = last_data.get("response_time")
# Добавляем новые метаданные
model = last_data.get("model", self.coordinator.model)
temperature = last_data.get("temperature", self.coordinator.temperature)
max_tokens = last_data.get("max_tokens", self.coordinator.max_tokens)
error = last_data.get("error")
if error:
self._last_error = error
self._state = STATE_ERROR
else:
last_response = str(last_data)
last_updated = self.coordinator.last_update_success_time
response_time = None
model = self.coordinator.model
temperature = self.coordinator.temperature
max_tokens = self.coordinator.max_tokens
# Convert timestamp to local time if needed
# Конвертация времени в локальный формат
if isinstance(last_updated, datetime):
last_updated = dt_util.as_local(last_updated)
@@ -140,6 +154,9 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
ATTR_RESPONSE: last_response,
ATTR_LAST_UPDATED: last_updated,
ATTR_TOTAL_RESPONSES: len(history),
ATTR_MODEL: model,
ATTR_TEMPERATURE: temperature,
ATTR_MAX_TOKENS: max_tokens,
})
if response_time is not None:
@@ -169,7 +186,10 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
"""Handle updated data from the coordinator."""
try:
if self.coordinator.data:
self._state = STATE_READY
if self.coordinator._is_ready:
self._state = STATE_READY
else:
self._state = STATE_DISCONNECTED
else:
self._state = STATE_DISCONNECTED
except Exception as err:
@@ -178,4 +198,4 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
self._last_error = str(err)
self._state = STATE_ERROR
self.async_write_ha_state()
self.async_write_ha_state() # Исправлено лишнее двоеточие
+20 -15
View File
@@ -28,6 +28,7 @@ ask_question:
name: Model
description: >-
Select an AI model to use (optional, overrides default setting).
You can choose from predefined models or enter your own model identifier.
Different models have different capabilities and token limits.
Note: More capable models may have longer response times and higher API costs.
required: false
@@ -35,6 +36,7 @@ ask_question:
default: "gpt-3.5-turbo"
selector:
select:
custom_value: true
options:
- label: "GPT-3.5 Turbo (Fast & Efficient)"
value: "gpt-3.5-turbo"
@@ -46,6 +48,8 @@ ask_question:
value: "gpt-4-32k"
- label: "GPT-4 Turbo (Latest)"
value: "gpt-4-1106-preview"
- label: "Claude-3 Sonnet"
value: "claude-3-sonnet"
mode: dropdown
temperature:
@@ -84,6 +88,17 @@ ask_question:
step: 1
mode: box
system_prompt:
name: System Prompt
description: >-
Optional system prompt to set context for this specific question.
This will temporarily override the default system prompt.
required: false
example: "You are a home automation expert focused on energy efficiency"
selector:
text:
multiline: true
clear_history:
name: Clear History
description: >-
@@ -121,6 +136,7 @@ get_history:
required: false
selector:
select:
custom_value: true
options:
- label: "All Models"
value: ""
@@ -128,14 +144,15 @@ get_history:
value: "gpt-3.5-turbo"
- label: "GPT-4"
value: "gpt-4"
- label: "Claude-3 Sonnet"
value: "claude-3-sonnet"
mode: dropdown
set_system_prompt:
name: Set System Prompt
description: >-
Configure the AI's behavior by setting a system prompt.
This affects how the AI interprets and responds to all future questions.
The prompt will persist until changed or cleared.
Set default system behavior instructions for all future conversations.
This defines how the AI should behave and respond to questions.
fields:
prompt:
name: System Prompt
@@ -156,15 +173,3 @@ set_system_prompt:
selector:
text:
multiline: true
type: text
max_length: 1000
clear_prompt:
name: Clear Existing Prompt
description: >-
Set to true to remove the current system prompt before applying the new one.
This ensures no conflicting instructions remain.
required: false
default: false
selector:
boolean: {}
+90 -186
View File
@@ -1,192 +1,96 @@
{
"config": {
"step": {
"user": {
"title": "Set up HA Text AI",
"description": "Configure your OpenAI integration for smart home interactions. You'll need an OpenAI API key from platform.openai.com to proceed.",
"data": {
"api_key": {
"name": "OpenAI API Key",
"description": "Your OpenAI API key from platform.openai.com. Keep this secure and never share it."
},
"model": {
"name": "AI Model",
"description": "Select the AI model to use. GPT-3.5-Turbo is recommended for most uses as it offers the best balance of capabilities and cost."
},
"temperature": {
"name": "Temperature",
"description": "Controls response creativity (0-2). Low values (0.1-0.3) for focused responses, high values (0.8-2.0) for creative ones."
},
"max_tokens": {
"name": "Max Tokens",
"description": "Maximum length of responses. Higher values allow longer responses but consume more API tokens. Recommended: 512-1024."
},
"api_endpoint": {
"name": "API Endpoint",
"description": "OpenAI API endpoint URL. Leave default unless using a custom endpoint or proxy."
},
"request_interval": {
"name": "Request Interval",
"description": "Minimum time between API requests in seconds. Increase if experiencing rate limits."
}
"config": {
"step": {
"user": {
"title": "Set up HA Text AI",
"description": "Configure your AI integration (OpenAI, Anthropic, etc.)",
"data": {
"api_key": "Your API key",
"model": "AI model to use for responses (e.g., gpt-3.5-turbo, claude-3-sonnet)",
"temperature": "Temperature for response generation (0-2)",
"max_tokens": "Maximum tokens in response (1-4096)",
"api_endpoint": "API endpoint URL (optional, for custom endpoints)",
"request_interval": "Minimum time between API requests (seconds)",
"system_prompt": "Default system prompt for all conversations"
}
}
},
"error": {
"invalid_api_key": "Invalid API key - please check your credentials",
"cannot_connect": "Failed to connect to API - check endpoint and network",
"invalid_model": "Selected model is not available or invalid",
"rate_limit": "API rate limit exceeded - please wait",
"unknown": "Unexpected error occurred - check logs for details"
}
}
},
"error": {
"invalid_auth": "Invalid API key. Please check your OpenAI API key and try again.",
"cannot_connect": "Failed to connect to API. Please check your internet connection and API endpoint.",
"unknown": "Unexpected error occurred. Please check the logs for more details.",
"already_exists": "This API key is already configured in another integration.",
"invalid_model": "Selected model is not available. Please choose a different model.",
"rate_limit": "API rate limit exceeded. Please try again later or increase the request interval.",
"context_length": "Input too long for selected model. Try reducing max tokens or using a model with larger context.",
"api_error": "OpenAI API error. Please check the logs for details.",
"timeout": "API response timeout. Request took too long to complete.",
"queue_full": "Request queue is full. Please try again later."
"options": {
"step": {
"init": {
"title": "HA Text AI Options",
"data": {
"model": "AI model selection",
"temperature": "Response temperature (0-2)",
"max_tokens": "Maximum response length",
"request_interval": "Time between requests (seconds)",
"system_prompt": "Default system instructions"
}
}
}
},
"abort": {
"already_configured": "This OpenAI integration is already configured",
"auth_failed": "Authentication failed. Please verify your API key.",
"invalid_endpoint": "Invalid API endpoint URL provided"
"services": {
"ask_question": {
"name": "Ask Question",
"description": "Send a question to the AI model and receive a response",
"fields": {
"question": {
"name": "Question",
"description": "Your question or prompt for the AI"
},
"system_prompt": {
"name": "System Prompt",
"description": "Optional system prompt to override default for this question"
},
"model": {
"name": "Model",
"description": "Optional AI model to use for this question"
},
"temperature": {
"name": "Temperature",
"description": "Optional temperature setting for this question (0-2)"
},
"max_tokens": {
"name": "Max Tokens",
"description": "Optional maximum token limit for this response"
}
}
},
"clear_history": {
"name": "Clear History",
"description": "Delete all stored conversation history"
},
"get_history": {
"name": "Get History",
"description": "Retrieve conversation history with metadata",
"fields": {
"limit": {
"name": "Limit",
"description": "Maximum number of conversations to return"
},
"filter_model": {
"name": "Filter Model",
"description": "Optional filter to show only specific model responses"
}
}
},
"set_system_prompt": {
"name": "Set System Prompt",
"description": "Set default system behavior instructions",
"fields": {
"prompt": {
"name": "System Prompt",
"description": "Instructions defining AI behavior and response style"
}
}
}
}
},
"options": {
"step": {
"init": {
"title": "HA Text AI Options",
"description": "Adjust your OpenAI integration settings. Changes will apply to future requests only.",
"data": {
"temperature": {
"name": "Temperature",
"description": "Controls response creativity (0-2). Low values for focused responses, high for creative ones."
},
"max_tokens": {
"name": "Max Tokens",
"description": "Maximum length of responses. Higher values allow longer responses but consume more API tokens."
},
"request_interval": {
"name": "Request Interval",
"description": "Minimum time between API requests in seconds. Increase if experiencing rate limits."
}
}
}
}
},
"entity": {
"sensor": {
"last_response": {
"name": "Last Response",
"state_attributes": {
"last_updated": {
"name": "Last Updated",
"description": "Timestamp of the last AI response"
},
"question": {
"name": "Last Question",
"description": "Most recent question asked"
},
"response": {
"name": "AI Response",
"description": "Latest response from the AI"
},
"model": {
"name": "Current Model",
"description": "AI model currently in use"
},
"temperature": {
"name": "Temperature Setting",
"description": "Current temperature parameter"
},
"max_tokens": {
"name": "Max Tokens Setting",
"description": "Current maximum tokens limit"
},
"total_responses": {
"name": "Total Responses",
"description": "Number of responses since last reset"
},
"system_prompt": {
"name": "System Prompt",
"description": "Current system instructions for the AI"
},
"response_time": {
"name": "Response Time",
"description": "Time taken to generate last response"
},
"queue_size": {
"name": "Queue Size",
"description": "Current size of request queue"
},
"api_status": {
"name": "API Status",
"description": "Current API connection status"
},
"error_count": {
"name": "Error Count",
"description": "Number of errors since last reset"
},
"last_error": {
"name": "Last Error",
"description": "Description of the last error encountered"
}
}
}
}
},
"services": {
"ask_question": {
"name": "Ask Question",
"description": "Send a question to the AI model and receive a detailed response. The response will be stored in conversation history.",
"fields": {
"question": {
"name": "Question",
"description": "Your question or prompt for the AI. Be specific for better results."
},
"model": {
"name": "Model",
"description": "AI model to use (optional, overrides default settings)."
},
"temperature": {
"name": "Temperature",
"description": "Response creativity level (0-2, optional)."
},
"max_tokens": {
"name": "Max Tokens",
"description": "Maximum response length (optional)."
}
}
},
"clear_history": {
"name": "Clear History",
"description": "Delete all stored conversation history. This action cannot be undone."
},
"get_history": {
"name": "Get History",
"description": "Retrieve conversation history, including questions, responses, and timestamps.",
"fields": {
"limit": {
"name": "Limit",
"description": "Number of recent conversations to return (default 10)."
},
"filter_model": {
"name": "Filter by Model",
"description": "Retrieve only conversations using a specific AI model."
}
}
},
"set_system_prompt": {
"name": "Set System Prompt",
"description": "Configure AI behavior by setting a system prompt.",
"fields": {
"prompt": {
"name": "Prompt",
"description": "Instructions defining AI behavior and response style."
},
"clear_prompt": {
"name": "Clear Prompt",
"description": "Remove current system prompt before setting new one."
}
}
}
}
}
+90 -186
View File
@@ -1,192 +1,96 @@
{
"config": {
"step": {
"user": {
"title": "Настройка HA Text AI",
"description": "Настройте интеграцию OpenAI для умного дома. Требуется API ключ OpenAI. Подробнее о получении ключа на platform.openai.com",
"data": {
"api_key": {
"name": "API ключ OpenAI",
"description": "Ваш API ключ с platform.openai.com. Храните его в безопасности."
},
"model": {
"name": "AI Модель",
"description": "Выберите модель AI. GPT-3.5-Turbo рекомендуется для большинства задач как оптимальное сочетание возможностей и стоимости."
},
"temperature": {
"name": "Температура",
"description": "Контролирует креативность ответов (0-2). Низкие значения (0.1-0.3) для точных ответов, высокие (0.8-2.0) для творческих."
},
"max_tokens": {
"name": "Максимум токенов",
"description": "Максимальная длина ответов. Больше токенов = длиннее ответы, но выше расход API токенов. Рекомендуется: 512-1024."
},
"api_endpoint": {
"name": "API Endpoint",
"description": "URL API OpenAI. Оставьте значение по умолчанию, если не используете собственный endpoint."
},
"request_interval": {
"name": "Интервал запросов",
"description": "Минимальное время между API запросами в секундах. Увеличьте при превышении лимитов запросов."
}
"config": {
"step": {
"user": {
"title": "Настройка HA Text AI",
"description": "Настройка интеграции с AI-сервисами (OpenAI, Anthropic и др.)",
"data": {
"api_key": "Ваш API-ключ",
"model": "Модель AI для ответов (например, gpt-3.5-turbo, claude-3-sonnet)",
"temperature": "Температура генерации ответов (0-2)",
"max_tokens": "Максимальное количество токенов в ответе (1-4096)",
"api_endpoint": "URL конечной точки API (необязательно, для пользовательских endpoint)",
"request_interval": "Минимальный интервал между запросами к API (секунды)",
"system_prompt": "Системная инструкция по умолчанию для всех диалогов"
}
}
},
"error": {
"invalid_api_key": "Недействительный API-ключ - проверьте учетные данные",
"cannot_connect": "Не удалось подключиться к API - проверьте endpoint и сеть",
"invalid_model": "Выбранная модель недоступна или некорректна",
"rate_limit": "Превышен лимит запросов API - пожалуйста, подождите",
"unknown": "Произошла непредвиденная ошибка - проверьте логи"
}
}
},
"error": {
"invalid_auth": "Неверный API ключ. Проверьте ключ OpenAI и попробуйте снова.",
"cannot_connect": "Не удалось подключиться к API. Проверьте подключение к интернету и endpoint.",
"unknown": "Неожиданная ошибка. Проверьте логи для подробностей.",
"already_exists": "Этот API ключ уже используется в другой интеграции.",
"invalid_model": "Выбранная модель недоступна. Выберите другую модель.",
"rate_limit": "Превышен лимит API запросов. Попробуйте позже или увеличьте интервал запросов.",
"context_length": "Входные данные слишком длинные для выбранной модели. Уменьшите max_tokens или используйте модель с большим контекстом.",
"api_error": "Ошибка API OpenAI. Проверьте логи для подробностей.",
"timeout": "Превышено время ожидания ответа от API.",
"queue_full": "Очередь запросов переполнена. Попробуйте позже."
"options": {
"step": {
"init": {
"title": "Настройки HA Text AI",
"data": {
"model": "Выбор модели AI",
"temperature": "Температура ответов (0-2)",
"max_tokens": "Максимальная длина ответа",
"request_interval": "Интервал между запросами (секунды)",
"system_prompt": "Системные инструкции по умолчанию"
}
}
}
},
"abort": {
"already_configured": "Эта интеграция OpenAI уже настроена",
"auth_failed": "Ошибка аутентификации. Проверьте API ключ.",
"invalid_endpoint": "Указан неверный URL API endpoint"
"services": {
"ask_question": {
"name": "Задать вопрос",
"description": "Отправить вопрос модели AI и получить ответ",
"fields": {
"question": {
"name": "Вопрос",
"description": "Ваш вопрос или запрос для AI"
},
"system_prompt": {
"name": "Системная инструкция",
"description": "Необязательная системная инструкция, заменяющая инструкцию по умолчанию для этого вопроса"
},
"model": {
"name": "Модель",
"description": "Необязательная модель AI для этого вопроса"
},
"temperature": {
"name": "Температура",
"description": "Необязательная настройка температуры для этого вопроса (0-2)"
},
"max_tokens": {
"name": "Макс. токенов",
"description": "Необязательное ограничение количества токенов для этого ответа"
}
}
},
"clear_history": {
"name": "Очистить историю",
"description": "Удалить всю сохраненную историю диалогов"
},
"get_history": {
"name": "Получить историю",
"description": "Получить историю диалогов с метаданными",
"fields": {
"limit": {
"name": "Лимит",
"description": "Максимальное количество диалогов для возврата"
},
"filter_model": {
"name": "Фильтр по модели",
"description": "Необязательный фильтр для показа ответов только от определенной модели"
}
}
},
"set_system_prompt": {
"name": "Установить системную инструкцию",
"description": "Задать системные инструкции по умолчанию",
"fields": {
"prompt": {
"name": "Системная инструкция",
"description": "Инструкции, определяющие поведение AI и стиль ответов"
}
}
}
}
},
"options": {
"step": {
"init": {
"title": "Настройки HA Text AI",
"description": "Измените настройки интеграции OpenAI. Изменения применятся к будущим запросам.",
"data": {
"temperature": {
"name": "Температура",
"description": "Контролирует креативность ответов (0-2). Низкие значения для точных ответов, высокие для творческих."
},
"max_tokens": {
"name": "Максимум токенов",
"description": "Максимальная длина ответов. Больше токенов = длиннее ответы, но выше расход API токенов."
},
"request_interval": {
"name": "Интервал запросов",
"description": "Минимальное время между API запросами в секундах. Увеличьте при превышении лимитов."
}
}
}
}
},
"entity": {
"sensor": {
"last_response": {
"name": "Последний ответ",
"state_attributes": {
"last_updated": {
"name": "Последнее обновление",
"description": "Время последнего ответа AI"
},
"question": {
"name": "Последний вопрос",
"description": "Последний заданный вопрос"
},
"response": {
"name": "Ответ AI",
"description": "Последний ответ от AI"
},
"model": {
"name": "Текущая модель",
"description": "Используемая модель AI"
},
"temperature": {
"name": "Настройка температуры",
"description": "Текущий параметр температуры"
},
"max_tokens": {
"name": "Лимит токенов",
"description": "Текущий лимит максимальных токенов"
},
"total_responses": {
"name": "Всего ответов",
"description": "Количество ответов с последнего сброса"
},
"system_prompt": {
"name": "Системный промпт",
"description": "Текущие системные инструкции для AI"
},
"response_time": {
"name": "Время ответа",
"description": "Время генерации последнего ответа"
},
"queue_size": {
"name": "Размер очереди",
"description": "Текущий размер очереди запросов"
},
"api_status": {
"name": "Статус API",
"description": "Текущий статус подключения к API"
},
"error_count": {
"name": "Счётчик ошибок",
"description": "Количество ошибок с последнего сброса"
},
"last_error": {
"name": "Последняя ошибка",
"description": "Описание последней возникшей ошибки"
}
}
}
}
},
"services": {
"ask_question": {
"name": "Задать вопрос",
"description": "Отправить вопрос модели AI и получить подробный ответ. Ответ сохраняется в истории.",
"fields": {
"question": {
"name": "Вопрос",
"description": "Ваш вопрос или запрос для AI. Будьте конкретны для лучших результатов."
},
"model": {
"name": "Модель",
"description": "Модель AI для использования (необязательно, переопределяет настройки по умолчанию)."
},
"temperature": {
"name": "Температура",
"description": "Уровень креативности ответа (0-2, необязательно)."
},
"max_tokens": {
"name": "Максимум токенов",
"description": "Максимальная длина ответа (необязательно)."
}
}
},
"clear_history": {
"name": "Очистить историю",
"description": "Удалить всю историю разговоров. Это действие нельзя отменить."
},
"get_history": {
"name": "Получить историю",
"description": "Получить историю разговоров, включая вопросы, ответы и временные метки.",
"fields": {
"limit": {
"name": "Лимит",
"description": "Количество последних разговоров для получения (по умолчанию 10)."
},
"filter_model": {
"name": "Фильтр по модели",
"description": "Получить только разговоры с определённой моделью AI."
}
}
},
"set_system_prompt": {
"name": "Установить системный промпт",
"description": "Настроить поведение AI, установив системный промпт.",
"fields": {
"prompt": {
"name": "Промпт",
"description": "Инструкции, определяющие поведение и стиль ответов AI."
},
"clear_prompt": {
"name": "Очистить промпт",
"description": "Удалить текущий системный промпт перед установкой нового."
}
}
}
}
}
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -4,6 +4,6 @@
"domains": ["sensor"],
"homeassistant": "2024.11.0",
"icon": "mdi:brain",
"version": "1.0.4",
"version": "1.1.0",
"documentation": "https://github.com/smkrv/ha-text-ai"
}
+17
View File
@@ -0,0 +1,17 @@
```
ha-text-ai/
├── custom_components/
│ └── ha_text_ai/
│ ├── __init__.py
│ ├── config_flow.py
│ ├── coordinator.py
│ ├── manifest.json
│ ├── sensor.py
│ ├── services.yaml
│ └── const.py
└── strings/
├── en.json
└── ru.json
```