Release v2.0.2-beta

This commit is contained in:
SMKRV
2024-11-28 23:27:39 +03:00
parent ff0c0369e8
commit d3ef31f551
13 changed files with 1417 additions and 1354 deletions
+6
View File
@@ -239,6 +239,12 @@ data:
### 🏷️ HA Text AI Sensor Naming Convention ### 🏷️ HA Text AI Sensor Naming Convention
#### Character Restrictions
- Only lowercase letters (a-z)
- Numbers (0-9)
- Underscore (_)
- Maximum length: 50 characters (including `ha_text_ai_` prefix (14 characters)
#### Sensor Name Structure #### Sensor Name Structure
```yaml ```yaml
# Always starts with 'sensor.ha_text_ai_' # Always starts with 'sensor.ha_text_ai_'
-390
View File
@@ -1,390 +0,0 @@
# 🤖 HA Text AI для Home Assistant
<div align="center">
![GitHub release](https://img.shields.io/github/release/smkrv/ha-text-ai.svg?style=flat-square) ![GitHub last commit](https://img.shields.io/github/last-commit/smkrv/ha-text-ai.svg?style=flat-square) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT) [![hacs_badge](https://img.shields.io/badge/HACS-Custom-41BDF5.svg?style=flat-square)](https://github.com/hacs/integration) ![/README.md](https://img.shields.io/badge/language-English-green?style=flat-square) ![/README_RU.md](https://img.shields.io/badge/language-Russian-green?style=flat-square) ![](https://img.shields.io/badge/language-Deutch-green?style=flat-square)
<img src="https://github.com/smkrv/ha-text-ai/blob/524849f6a945ec62c2cf6a6b7ecd9a28b37bf0fa/misc/icons/logo.jpg" alt="HA Text AI" width="98%"/>
### Расширенная AI-интеграция для Home Assistant с поддержкой нескольких провайдеров
</div>
<p align="center">
Преобразите свой умный дом с мощной AI-поддержкой от множества провайдеров, включая модели OpenAI GPT и Anthropic Claude. Получайте интеллектуальные ответы, автоматизируйте сложные сценарии и улучшайте домашнюю автоматизацию с помощью передовой обработки естественного языка.
</p>
---
> [!IMPORTANT]
> 🚧 АЛЬФА-ВЕРСИЯ 🚧
> Ожидайте: возможные ошибки, частые изменения, незавершенные функции.
> 🤝 Разработка силами сообщества
>
> <a href="https://community.home-assistant.io/t/ha-text-ai-transforming-home-automation-with-multi-provider-language-models/799741"><img src="https://img.shields.io/badge/Сообщество-blue?style=for-the-badge&logo=homeassistant&logoColor=white&color=03a9f4"/></a>
## 🌟 Возможности
- 🧠 **Мультипровайдерная AI-интеграция**:
- Поддержка моделей OpenAI GPT
- Интеграция с Anthropic Claude
- Пользовательские API-эндпоинты
- Гибкий выбор модели
- 💬 **Расширенная обработка языка**:
- Контекстно-зависимые ответы
- Многоступенчатые диалоги
- Пользовательские системные инструкции
- Естественный поток общения
- 📝 **Улучшенное управление памятью**:
- Постоянная история диалогов
- Контекстно-осознанные ответы
- Настраиваемые лимиты истории
- Фильтрация для конкретных моделей
-**Оптимизация производительности**:
- Эффективное использование токенов
- Интеллектуальное ограничение скорости
- Кэширование ответов
- Контроль интервалов запросов
- 🎯 **Расширенная настройка**:
- Выбор модели для каждого запроса
- Настраиваемые параметры
- Пользовательские системные промпты
- Контроль температуры
- 🔒 **Повышенная безопасность**:
- Защищенное хранение API-ключей
- Защита от превышения лимитов
- Обработка ошибок
- Мониторинг использования
- 🎨 **Улучшенный пользовательский интерфейс**:
- Интуитивная конфигурация
- Подробные атрибуты сенсоров
- Расширенный сервисный интерфейс
- UI выбора модели
- 🔄 **Интеграция с автоматизацией**:
- Реакции на события
- Поддержка условной логики
- Совместимость с шаблонами
- Автоматизация для конкретных моделей
## 📋 Предварительные требования
- Home Assistant 2024.11 или новее
- Активный API-ключ от:
- OpenAI ([Получить ключ](https://platform.openai.com/account/api-keys))
- Anthropic ([Получить ключ](https://console.anthropic.com/))
- OpenRouter ([Получить ключ](https://openrouter.ai/keys))
- Python 3.9 или новее
- Стабильное интернет-соединение
### Параметры конфигурации
- Провайдер API (OpenAI/Anthropic)
- API-ключ (специфичный для провайдера)
- Выбор модели (гибкий, модели от провайдера)
- Температура (управление креативностью, 0.0-2.0)
- Максимальные токены (Ограничение длины ответа)
- Интервал запросов (Регулировка вызовов API)
- Пользовательская конечная точка API (опционально)
#### ⓘ Потенциально совместимые провайдеры
Интеграция разработана гибко и может работать с провайдерами, предлагающими API, совместимые с OpenAI:
- Groq
- Together AI
- Perplexity AI
- Mistral AI
- Google AI
- Локальные AI-серверы (например, Ollama)
- Пользовательские эндпоинты, совместимые с OpenAI
#### Дополнительные замечания
- Не все провайдеры гарантируют полную совместимость
- Производительность может различаться
- Проверяйте документацию каждого провайдера
- Убедитесь, что у вашего API-ключа достаточно кредитов/квоты
#### Требования к совместимости провайдеров
Для совместимости провайдер должен поддерживать:
- REST API-структуру, похожую на OpenAI
- JSON-формат запросов/ответов
- Стандартный метод аутентификации
- Схожую обработку параметров модели
## ⚡ Установка
### Установка через HACS (Рекомендуется)
<a href="https://my.home-assistant.io/redirect/hacs_repository/?owner=smkrv&repository=ha-text-ai&category=Integration"><img src="https://my.home-assistant.io/badges/hacs_repository.svg" width="170" height="auto"></a>
1\. Откройте HACS в Home Assistant
2\. Нажмите "Интеграции"
3\. Нажмите "..." в правом верхнем углу
4\. Выберите "Пользовательские репозитории"
5\. Добавьте URL репозитория: `https://github.com/smkrv/ha-text-ai`
6\. Выберите категорию "Интеграция"
7\. Нажмите "Загрузить"
8\. Перезапустите Home Assistant
### Самостоятельная установка
1\. Скачайте последний релиз
2\. Извлеките и скопируйте `custom_components/ha_text_ai` в вашу директорию `custom_components`
3\. Перезапустите Home Assistant
4\. Добавьте конфигурацию через UI или YAML
## ⚙️ Конфигурация
### Через UI (Рекомендуется)
1\. Перейдите в Настройки → Устройства и сервисы
2\. Нажмите "Добавить интеграцию"
3\. Найдите "HA Text AI"
4\. Следуйте шагам конфигурации
### Через YAML
```yaml
ha_text_ai:
api_provider: openai # или anthropic
api_key: !secret ai_api_key
model: gpt-4o-mini
temperature: 0.7
max_tokens: 1000
request_interval: 1.0
api_endpoint: https://api.openai.com/v1 # опционально, для пользовательских эндпоинтов
system_prompt: |
Вы - эксперт по домашней автоматизации.
Фокусируйтесь на практичных и эффективных решениях.
```
## 🛠️ Доступные сервисы
### ask_question
```yaml
service: ha_text_ai.ask_question
data:
question: "Какая оптимальная температура для сна?"
model: "claude-3-sonnet" # опционально
temperature: 0.5 # опционально
max_tokens: 500 # опционально
context_messages: 10 # опционально, количество предыдущих сообщений в контексте, по умолчанию: 5
system_prompt: "Вы эксперт по оптимизации сна" # опционально
```
### set_system_prompt
```yaml
service: ha_text_ai.set_system_prompt
data:
prompt: |
Вы эксперт по домашней автоматизации, сосредоточенный на:
1. Энергоэффективности
2. Оптимизации комфорта
3. Вопросах безопасности
Предоставляйте практичные, выполнимые советы.
```
### clear_history
```yaml
service: ha_text_ai.clear_history
```
### get_history
```yaml
service: ha_text_ai.get_history
data:
limit: 5 # опционально
filter_model: "gpt-4o" # опционально
```
### 🏷️ Соглашение по именованию сенсоров HA Text AI
#### Структура имени сенсора
```yaml
# Всегда начинается с 'sensor.ha_text_ai_'
# Вы определяете только часть после подчеркивания
sensor.ha_text_ai_ВАШЕ_УНИКАЛЬНОЕ_ОКОНЧАНИЕ
# Примеры:
sensor.ha_text_ai_gpt # Сенсор на базе GPT
sensor.ha_text_ai_claude # Сенсор на базе Claude
sensor.ha_text_ai_gpt # Пользовательское окончание
```
#### Получение ответа
```yaml
# Используйте имя вашего конкретного сенсора
{{ state_attr('sensor.ha_text_ai_gpt', 'response') }}
```
#### Практическое использование
```yaml
automation:
- alias: "AI-ответ с пользовательским сенсором"
action:
- service: ha_text_ai.ask_question
data:
question: "Совет по домашней автоматизации"
- service: notify.mobile
data:
message: >
Совет ИИ:
{{ state_attr('sensor.ha_text_ai_gpt', 'response') }}
```
### 💡 Правила именования
- Префикс всегда `sensor.ha_text_ai_`
- Добавьте уникальный идентификатор после подчеркивания
- Используйте lowercase
- Пробелы не допускаются
- Сохраняйте описательность и краткость
### 🔍 Атрибуты сенсора HA Text AI
#### Информация о модели и провайдере
```yaml
# Название текущей используемой AI-модели
{{ state_attr('sensor.ha_text_ai_gpt', 'Model') }} # gpt-4o
# Провайдер сервиса (определяет эндпоинт API и аутентификацию)
{{ state_attr('sensor.ha_text_ai_gpt', 'Api provider') }} # openai
# Предыдущая или альтернативная конфигурация модели
{{ state_attr('sensor.ha_text_ai_gpt', 'Last model') }} # gpt-4o
```
#### Системный статус
```yaml
# Текущая готовность сервиса AI API
{{ state_attr('sensor.ha_text_ai_gpt', 'Api status') }} # готов
# Указывает, выполняется ли в данный момент запрос
{{ state_attr('sensor.ha_text_ai_gpt', 'Is processing') }} # false
# Показывает, достигнут ли лимит запросов API
{{ state_attr('sensor.ha_text_ai_gpt', 'Is rate limited') }} # false
# Статус конкретного используемого эндпоинта
{{ state_attr('sensor.ha_text_ai_gpt', 'Endpoint status') }} # готов
```
#### Метрики производительности
```yaml
# Общее количество успешно выполненных API-запросов
{{ state_attr('sensor.ha_text_ai_gpt', 'Successful requests') }} # 0
# Количество API-запросов с ошибками
{{ state_attr('sensor.ha_text_ai_gpt', 'Failed requests') }} # 0
# Среднее время получения ответа от сервиса AI
{{ state_attr('sensor.ha_text_ai_gpt', 'Average latency') }} # 0
# Максимальное время для одного цикла запрос-ответ
{{ state_attr('sensor.ha_text_ai_gpt', 'Max latency') }} # 0
```
#### Использование диалога и токенов
```yaml
# Количество предыдущих взаимодействий, сохраненных в контексте
{{ state_attr('sensor.ha_text_ai_gpt', 'History size') }} # 0
# Общее количество использованных токенов
{{ state_attr('sensor.ha_text_ai_gpt', 'Total tokens') }} # 0
# Токены, использованные во входных промптах
{{ state_attr('sensor.ha_text_ai_gpt', 'Prompt tokens') }} # 0
# Токены, использованные в ответах AI
{{ state_attr('sensor.ha_text_ai_gpt', 'Completion tokens') }} # 0
```
#### Детали последнего взаимодействия
```yaml
# Самый последний полный ответ, сгенерированный сервисом AI
{{ state_attr('sensor.ha_text_ai_gpt', 'Response') }} # Последний AI-ответ
# Последний обработанный пользовательский запрос
{{ state_attr('sensor.ha_text_ai_gpt', 'Question') }} # Последний заданный вопрос
# Точный момент последнего взаимодействия
{{ state_attr('sensor.ha_text_ai_gpt', 'Last timestamp') }} # Временная метка
```
#### Системное здоровье
```yaml
# Суммарное количество всех ошибок при взаимодействии с сервисом AI
{{ state_attr('sensor.ha_text_ai_gpt', 'Total errors') }} # 0
# Указывает, проводится ли плановое или аварийное обслуживание
{{ state_attr('sensor.ha_text_ai_gpt', 'Is maintenance') }} # false
# Общее время непрерывной работы сервиса AI (в часах или днях)
{{ state_attr('sensor.ha_text_ai_gpt', 'Uptime') }} # 547,58
```
### 💡 Профессиональные советы
- Всегда проверяйте наличие атрибутов
- Используйте эти атрибуты для мониторинга и автоматизации
- Некоторые значения могут быть изначально равны 0 или быть пустыми
## 📘 Часто задаваемые вопросы
**В: Какие AI-провайдеры поддерживаются?**
О: В настоящее время поддерживаются OpenAI (модели GPT) и Anthropic (модели Claude), планируется расширение списка провайдеров.
**В: Как уменьшить расходы на API?**
О: Используйте GPT-3.5-Turbo или Claude-3-Sonnet для большинства запросов, внедрите кэширование и оптимизируйте использование токенов.
**В: Есть ли ограничения на количество запросов?**
О: Зависит от тарифного плана вашего API-провайдера. Рекомендуется отслеживать использование и реализовать ограничение запросов через параметр `request_interval`.
**В: Можно ли использовать пользовательские модели?**
О: Да, вы можете настроить пользовательские эндпоинты и использовать любую совместимую модель, указав её в конфигурации.
**В: Как переключаться между различными AI-провайдерами?**
О: Просто измените параметр модели в вашей конфигурации или при вызове сервиса для использования нужной модели провайдера.
**В: Насколько безопасны мои данные?**
О: Ваши данные в безопасности. Система работает полностью на вашем локальном компьютере, сохраняя контроль над данными. API-ключи хранятся защищённо, все внешние коммуникации используют зашифрованные соединения.
**В: Как работают контекстные сообщения?**
О: Контекстные сообщения позволяют AI помнить и ссылаться на предыдущую историю диалога. По умолчанию включаются 5 предыдущих сообщений, но вы можете настроить от 1 до 20 сообщений для контроля глубины разговора и использования токенов.
## 🤝 Участие в разработке
Мы всегда рады вашему вкладу! Пожалуйста, ознакомьтесь с [Руководством по участию](CONTRIBUTING.md).
1\. Создайте fork репозитория
2\. Создайте ветку для функции (`git checkout -b feature/Улучшение`)
3\. Зафиксируйте изменения (`git commit -m 'Добавить улучшение'`)
4\. Отправьте ветку (`git push origin feature/Улучшение`)
5\. Откройте Pull Request
## 📝 Лицензия
MIT License - подробности в файле [LICENSE](LICENSE).
## 💡 Поддержите проект
Лучшая поддержка - это:
- Делитесь обратной связью
- Предлагайте идеи
- Рекомендуйте друзьям
- Сообщайте о проблемах
- Поставьте звезду репозиторию
Если хотите финансово поблагодарить, можете отправить небольшой токен признательности в USDT:
**USDT Кошелек (TRC10/TRC20):**
`TXC9zYHYPfWUGi4Sv4R1ctTBGScXXQk5HZ`
*Open-source создается страстью сообщества!* 🚀
---
<div align="center">
Создано с ❤️ и Claude 3.5 Sonnet для сообщества Home Assistant
[Сообщить об ошибке](https://github.com/smkrv/ha-text-ai/issues) · [Предложить функцию](https://github.com/smkrv/ha-text-ai/issues)
</div>
+24 -20
View File
@@ -43,6 +43,8 @@ from .const import (
SERVICE_CLEAR_HISTORY, SERVICE_CLEAR_HISTORY,
SERVICE_GET_HISTORY, SERVICE_GET_HISTORY,
SERVICE_SET_SYSTEM_PROMPT, SERVICE_SET_SYSTEM_PROMPT,
DEFAULT_MAX_HISTORY,
CONF_MAX_HISTORY_SIZE,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -197,11 +199,14 @@ async def async_check_api(session, endpoint: str, headers: dict, provider: str)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up HA Text AI from a config entry.""" """Set up HA Text AI from a config entry."""
_LOGGER.debug(f"Setting up HA Text AI entry: {entry.data}")
try: try:
if CONF_API_PROVIDER not in entry.data: if CONF_API_PROVIDER not in entry.data:
_LOGGER.error("API provider not specified") _LOGGER.error("API provider not specified")
raise ConfigEntryNotReady("API provider is required") raise ConfigEntryNotReady("API provider is required")
# Get configuration
session = aiohttp_client.async_get_clientsession(hass) session = aiohttp_client.async_get_clientsession(hass)
api_provider = entry.data.get(CONF_API_PROVIDER) api_provider = entry.data.get(CONF_API_PROVIDER)
model = entry.data.get(CONF_MODEL, DEFAULT_MODEL) model = entry.data.get(CONF_MODEL, DEFAULT_MODEL)
@@ -212,6 +217,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
).rstrip('/') ).rstrip('/')
api_key = entry.data[CONF_API_KEY] api_key = entry.data[CONF_API_KEY]
instance_name = entry.data.get(CONF_NAME, entry.entry_id) instance_name = entry.data.get(CONF_NAME, entry.entry_id)
request_interval = entry.data.get(CONF_REQUEST_INTERVAL, DEFAULT_REQUEST_INTERVAL)
max_tokens = entry.data.get(CONF_MAX_TOKENS, DEFAULT_MAX_TOKENS)
temperature = entry.data.get(CONF_TEMPERATURE, DEFAULT_TEMPERATURE)
max_history_size = entry.data.get(CONF_MAX_HISTORY_SIZE, DEFAULT_MAX_HISTORY)
context_messages = entry.data.get(CONF_CONTEXT_MESSAGES, DEFAULT_CONTEXT_MESSAGES)
is_anthropic = api_provider == API_PROVIDER_ANTHROPIC is_anthropic = api_provider == API_PROVIDER_ANTHROPIC
headers = { headers = {
@@ -242,39 +252,33 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass=hass, hass=hass,
client=api_client, client=api_client,
model=model, model=model,
update_interval=entry.data.get(CONF_REQUEST_INTERVAL, DEFAULT_REQUEST_INTERVAL), update_interval=request_interval,
instance_name=instance_name, instance_name=instance_name,
max_tokens=entry.data.get(CONF_MAX_TOKENS, DEFAULT_MAX_TOKENS), max_tokens=max_tokens,
temperature=entry.data.get(CONF_TEMPERATURE, DEFAULT_TEMPERATURE), temperature=temperature,
max_history_size=max_history_size,
context_messages=context_messages,
is_anthropic=is_anthropic, is_anthropic=is_anthropic,
context_messages=entry.data.get(
CONF_CONTEXT_MESSAGES,
DEFAULT_CONTEXT_MESSAGES
),
) )
coordinator.data = coordinator._initial_state.copy() _LOGGER.debug(f"Created coordinator for {instance_name}")
_LOGGER.debug(f"Initial state set for coordinator {instance_name}")
await coordinator.async_config_entry_first_refresh()
# Store coordinator
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = coordinator hass.data[DOMAIN][entry.entry_id] = coordinator
_LOGGER.debug(f"Stored coordinator in hass.data[{DOMAIN}][{entry.entry_id}]")
# Set up platforms
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
_LOGGER.info( _LOGGER.debug(f"Setup completed for {instance_name}")
"Successfully set up %s instance '%s' with model %s",
api_provider,
instance_name,
model
)
return True return True
except Exception as ex: except Exception as err:
_LOGGER.exception("Setup error: %s", str(ex)) _LOGGER.exception(f"Error setting up HA Text AI: {err}")
raise ConfigEntryNotReady(f"Setup error: {str(ex)}") from ex raise
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
+39 -19
View File
@@ -19,6 +19,7 @@ from .const import (
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
class APIClient: class APIClient:
"""API Client for OpenAI and Anthropic.""" """API Client for OpenAI and Anthropic."""
@@ -59,6 +60,7 @@ class APIClient:
payload: Dict[str, Any], payload: Dict[str, Any],
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""Make API request with retry logic.""" """Make API request with retry logic."""
_LOGGER.debug(f"API Request: URL={url}, Payload={payload}")
for attempt in range(API_RETRY_COUNT): for attempt in range(API_RETRY_COUNT):
try: try:
async with timeout(API_TIMEOUT): async with timeout(API_TIMEOUT):
@@ -66,20 +68,23 @@ class APIClient:
url, url,
json=payload, json=payload,
headers=self.headers, headers=self.headers,
timeout=self.timeout timeout=self.timeout,
) as response: ) as response:
_LOGGER.debug(f"Response status: {response.status}")
if response.status != 200: if response.status != 200:
error_data = await response.json() error_data = await response.json()
_LOGGER.error(f"API error: {error_data}")
raise HomeAssistantError(f"API error: {error_data}") raise HomeAssistantError(f"API error: {error_data}")
return await response.json() return await response.json()
except asyncio.TimeoutError: except asyncio.TimeoutError:
_LOGGER.warning(f"Timeout on attempt {attempt + 1}")
if attempt == API_RETRY_COUNT - 1: if attempt == API_RETRY_COUNT - 1:
raise HomeAssistantError("API request timed out") raise HomeAssistantError("API request timed out")
await asyncio.sleep(1 * (attempt + 1)) await asyncio.sleep(1 * (attempt + 1))
except Exception as e: except Exception as e:
_LOGGER.warning(f"API request failed on attempt {attempt + 1}: {str(e)}")
if attempt == API_RETRY_COUNT - 1: if attempt == API_RETRY_COUNT - 1:
raise raise
_LOGGER.warning("API request failed, retrying: %s", str(e))
await asyncio.sleep(1 * (attempt + 1)) await asyncio.sleep(1 * (attempt + 1))
async def create( async def create(
@@ -125,16 +130,14 @@ class APIClient:
return { return {
"choices": [ "choices": [
{ {
"message": { "message": {"content": data["choices"][0]["message"]["content"]},
"content": data["choices"][0]["message"]["content"]
}
} }
], ],
"usage": { "usage": {
"prompt_tokens": data["usage"]["prompt_tokens"], "prompt_tokens": data["usage"]["prompt_tokens"],
"completion_tokens": data["usage"]["completion_tokens"], "completion_tokens": data["usage"]["completion_tokens"],
"total_tokens": data["usage"]["total_tokens"] "total_tokens": data["usage"]["total_tokens"],
} },
} }
async def _create_anthropic_completion( async def _create_anthropic_completion(
@@ -147,19 +150,24 @@ class APIClient:
"""Create completion using Anthropic API.""" """Create completion using Anthropic API."""
url = f"{self.endpoint}/v1/messages" url = f"{self.endpoint}/v1/messages"
# Convert messages to Anthropic format system_prompt = None
system_prompt = next( filtered_messages = []
(msg["content"] for msg in messages if msg["role"] == "system"), for msg in messages:
None if msg['role'] == 'system':
) if system_prompt is None:
conversation = [msg for msg in messages if msg["role"] != "system"] system_prompt = msg['content']
else:
system_prompt += f" {msg['content']}"
else:
filtered_messages.append(msg)
payload = { payload = {
"model": model, "model": model,
"messages": conversation, "messages": filtered_messages,
"max_tokens": max_tokens, "max_tokens": max_tokens,
"temperature": temperature, "temperature": temperature,
} }
if system_prompt: if system_prompt:
payload["system"] = system_prompt payload["system"] = system_prompt
@@ -167,14 +175,26 @@ class APIClient:
return { return {
"choices": [ "choices": [
{ {
"message": { "message": {"content": data["content"][0]["text"]},
"content": data["content"][0]["text"]
}
} }
], ],
"usage": { "usage": {
"prompt_tokens": data["usage"]["input_tokens"], "prompt_tokens": data["usage"]["input_tokens"],
"completion_tokens": data["usage"]["output_tokens"], "completion_tokens": data["usage"]["output_tokens"],
"total_tokens": data["usage"]["input_tokens"] + data["usage"]["output_tokens"] "total_tokens": data["usage"]["input_tokens"] + data["usage"]["output_tokens"],
} },
} }
async def check_connection(self) -> bool:
"""Check API connection."""
try:
await self._make_request(self.endpoint, {"test": "connection"})
return True
except Exception as e:
_LOGGER.error(f"Connection check failed: {str(e)}")
return False
async def shutdown(self) -> None:
"""Shutdown API client."""
_LOGGER.debug("Shutting down API client")
await self.session.close()
+124 -29
View File
@@ -34,10 +34,21 @@ from .const import (
MIN_MAX_TOKENS, MIN_MAX_TOKENS,
MAX_MAX_TOKENS, MAX_MAX_TOKENS,
MIN_REQUEST_INTERVAL, MIN_REQUEST_INTERVAL,
DEFAULT_NAME_PREFIX,
DEFAULT_MAX_HISTORY,
CONF_MAX_HISTORY_SIZE,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def normalize_name(name: str) -> str:
"""Normalize name to conform to HA naming convention using underscores."""
normalized = ''.join(c if c.isalnum() or c == '_' else '_' for c in name)
normalized = '_'.join(filter(None, normalized.split('_')))
return normalized.lower()
class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for HA text AI.""" """Handle a config flow for HA text AI."""
@@ -69,18 +80,18 @@ class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_provider(self, user_input: Optional[Dict[str, Any]] = None) -> FlowResult: async def async_step_provider(self, user_input: Optional[Dict[str, Any]] = None) -> FlowResult:
"""Handle provider configuration step.""" """Handle provider configuration step."""
self._errors = {}
if user_input is None: if user_input is None:
default_endpoint = ( default_endpoint = (
DEFAULT_OPENAI_ENDPOINT if self._provider == API_PROVIDER_OPENAI DEFAULT_OPENAI_ENDPOINT if self._provider == API_PROVIDER_OPENAI
else DEFAULT_ANTHROPIC_ENDPOINT else DEFAULT_ANTHROPIC_ENDPOINT
) )
suggested_name = f"HA Text AI {len(self._async_current_entries()) + 1}"
return self.async_show_form( return self.async_show_form(
step_id="provider", step_id="provider",
data_schema=vol.Schema({ data_schema=vol.Schema({
vol.Required(CONF_NAME, default=suggested_name): str, vol.Required(CONF_NAME, default="my_assistant"): str,
vol.Required(CONF_API_KEY): str, vol.Required(CONF_API_KEY): str,
vol.Required(CONF_MODEL, default=DEFAULT_MODEL): str, vol.Required(CONF_MODEL, default=DEFAULT_MODEL): str,
vol.Required(CONF_API_ENDPOINT, default=default_endpoint): str, vol.Required(CONF_API_ENDPOINT, default=default_endpoint): str,
@@ -103,27 +114,86 @@ class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
vol.Coerce(int), vol.Coerce(int),
vol.Range(min=1, max=20) vol.Range(min=1, max=20)
), ),
}), vol.Optional(
errors=self._errors CONF_MAX_HISTORY_SIZE,
default=DEFAULT_MAX_HISTORY
): vol.All(
vol.Coerce(int),
vol.Range(min=1, max=100)
),
})
) )
instance_name = user_input[CONF_NAME] input_copy = user_input.copy()
await self._async_validate_name(instance_name)
if self._errors:
return await self.async_step_provider()
if not await self._async_validate_api(user_input): try:
return await self.async_step_provider() normalized_name = self._validate_and_normalize_name(input_copy[CONF_NAME])
input_copy[CONF_NAME] = normalized_name
except ValueError as e:
return self.async_show_form(
step_id="provider",
data_schema=vol.Schema({
vol.Required(CONF_NAME, default=input_copy[CONF_NAME]): str,
vol.Required(CONF_API_KEY, default=input_copy[CONF_API_KEY]): str,
vol.Required(CONF_MODEL, default=input_copy[CONF_MODEL]): str,
vol.Required(CONF_API_ENDPOINT, default=input_copy[CONF_API_ENDPOINT]): str,
vol.Optional(CONF_TEMPERATURE, default=input_copy.get(CONF_TEMPERATURE, DEFAULT_TEMPERATURE)): vol.All(
vol.Coerce(float),
vol.Range(min=MIN_TEMPERATURE, max=MAX_TEMPERATURE)
),
}),
errors={"name": str(e)}
)
return await self._create_entry(user_input) try:
if not await self._async_validate_api(input_copy):
return self.async_show_form(
step_id="provider",
data_schema=vol.Schema({
}),
errors=self._errors
)
except Exception as e:
return self.async_show_form(
step_id="provider",
data_schema=vol.Schema({
}),
errors={"base": str(e)}
)
return await self._create_entry(input_copy)
def _validate_and_normalize_name(self, name: str) -> str:
"""
Validate and normalize name with detailed error handling.
Raises:
ValueError: If name is invalid
Returns:
Normalized name
"""
if not name:
raise ValueError("empty")
name = name.strip()
normalized = ''.join(
c if c.isalnum() or c in ' _' else '_' # Only allow underscores
for c in name
)
normalized = normalized.replace(' ', '_').lower()
async def _async_validate_name(self, name: str) -> bool:
"""Validate that the name is unique."""
for entry in self._async_current_entries(): for entry in self._async_current_entries():
if entry.data.get(CONF_NAME) == name: if entry.data.get(CONF_NAME, "") == normalized:
self._errors["name"] = "name_exists" raise ValueError("name_exists")
return False
return True normalized = normalized[:50]
if not normalized:
raise ValueError("empty")
return normalized
async def _async_validate_api(self, user_input: Dict[str, Any]) -> bool: async def _async_validate_api(self, user_input: Dict[str, Any]) -> bool:
"""Validate API connection.""" """Validate API connection."""
@@ -167,21 +237,36 @@ class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
} }
async def _create_entry(self, user_input: Dict[str, Any]) -> FlowResult: async def _create_entry(self, user_input: Dict[str, Any]) -> FlowResult:
"""Create the config entry.""" """Create the config entry with comprehensive data preservation."""
instance_name = user_input[CONF_NAME] instance_name = user_input[CONF_NAME]
unique_id = f"{DOMAIN}_{instance_name}_{self._provider}".lower().replace(" ", "_") normalized_name = normalize_name(instance_name)
unique_id = f"{DOMAIN}_{normalized_name}_{self._provider}".lower()
entry_data = {
CONF_API_PROVIDER: self._provider,
CONF_NAME: instance_name,
"normalized_name": normalized_name,
CONF_API_KEY: user_input.get(CONF_API_KEY),
CONF_API_ENDPOINT: user_input.get(CONF_API_ENDPOINT),
"unique_id": unique_id,
CONF_MODEL: user_input.get(CONF_MODEL, DEFAULT_MODEL),
CONF_TEMPERATURE: user_input.get(CONF_TEMPERATURE, DEFAULT_TEMPERATURE),
CONF_MAX_TOKENS: user_input.get(CONF_MAX_TOKENS, DEFAULT_MAX_TOKENS),
CONF_REQUEST_INTERVAL: user_input.get(CONF_REQUEST_INTERVAL, DEFAULT_REQUEST_INTERVAL),
CONF_CONTEXT_MESSAGES: user_input.get(CONF_CONTEXT_MESSAGES, DEFAULT_CONTEXT_MESSAGES),
CONF_MAX_HISTORY_SIZE: user_input.get(CONF_MAX_HISTORY_SIZE, DEFAULT_MAX_HISTORY),
}
for key, value in user_input.items():
if key not in entry_data:
entry_data[key] = value
_LOGGER.debug(f"Creating config entry with data: {entry_data}")
return self.async_create_entry( return self.async_create_entry(
title=instance_name, title=instance_name,
data={ data=entry_data
CONF_API_PROVIDER: self._provider,
CONF_NAME: instance_name,
**user_input,
"unique_id": unique_id,
CONF_CONTEXT_MESSAGES: user_input.get(
CONF_CONTEXT_MESSAGES,
DEFAULT_CONTEXT_MESSAGES)
}
) )
@staticmethod @staticmethod
@@ -243,5 +328,15 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
vol.Coerce(int), vol.Coerce(int),
vol.Range(min=1, max=20) vol.Range(min=1, max=20)
), ),
vol.Optional(
CONF_MAX_HISTORY_SIZE,
default=current_data.get(
CONF_MAX_HISTORY_SIZE,
DEFAULT_MAX_HISTORY
)
): vol.All(
vol.Coerce(int),
vol.Range(min=1, max=100)
),
}) })
) )
+5 -4
View File
@@ -6,7 +6,7 @@ from homeassistant.helpers import config_validation as cv
# Domain and platforms # Domain and platforms
DOMAIN: Final = "ha_text_ai" DOMAIN: Final = "ha_text_ai"
PLATFORMS: Final = [Platform.SENSOR] PLATFORMS: list[str] = ["sensor"]
# Provider configuration # Provider configuration
CONF_API_PROVIDER: Final = "api_provider" CONF_API_PROVIDER: Final = "api_provider"
@@ -29,7 +29,7 @@ CONF_MAX_TOKENS: Final = "max_tokens"
CONF_API_ENDPOINT: Final = "api_endpoint" CONF_API_ENDPOINT: Final = "api_endpoint"
CONF_REQUEST_INTERVAL: Final = "request_interval" CONF_REQUEST_INTERVAL: Final = "request_interval"
CONF_INSTANCE: Final = "instance" CONF_INSTANCE: Final = "instance"
CONF_MAX_HISTORY_SIZE: Final = "max_history_size" CONF_MAX_HISTORY_SIZE: Final = "max_history_size" # Correct constant name
CONF_IS_ANTHROPIC: Final = "is_anthropic" CONF_IS_ANTHROPIC: Final = "is_anthropic"
CONF_CONTEXT_MESSAGES: Final = "context_messages" CONF_CONTEXT_MESSAGES: Final = "context_messages"
@@ -41,6 +41,7 @@ DEFAULT_REQUEST_INTERVAL: Final = 1.0
DEFAULT_TIMEOUT: Final = 30 DEFAULT_TIMEOUT: Final = 30
DEFAULT_MAX_HISTORY: Final = 50 DEFAULT_MAX_HISTORY: Final = 50
DEFAULT_NAME: Final = "HA Text AI" DEFAULT_NAME: Final = "HA Text AI"
DEFAULT_NAME_PREFIX = "ha_text_ai"
DEFAULT_CONTEXT_MESSAGES: Final = 5 DEFAULT_CONTEXT_MESSAGES: Final = 5
# Parameter constraints # Parameter constraints
@@ -164,7 +165,7 @@ SERVICE_SCHEMA_GET_HISTORY = vol.Schema({
vol.Required(CONF_INSTANCE): cv.string, vol.Required(CONF_INSTANCE): cv.string,
vol.Optional("limit", default=10): vol.All( vol.Optional("limit", default=10): vol.All(
vol.Coerce(int), vol.Coerce(int),
vol.Range(min=1, max=100) vol.Range(min=1, max=100),
), ),
vol.Optional("filter_model"): cv.string vol.Optional("filter_model"): cv.string
}) })
@@ -189,7 +190,7 @@ CONFIG_SCHEMA = vol.Schema({
vol.Coerce(float), vol.Coerce(float),
vol.Range(min=MIN_REQUEST_INTERVAL, max=MAX_REQUEST_INTERVAL) vol.Range(min=MIN_REQUEST_INTERVAL, max=MAX_REQUEST_INTERVAL)
), ),
vol.Optional(CONF_MAX_HISTORY_SIZE, default=DEFAULT_MAX_HISTORY): vol.All( vol.Optional(CONF_MAX_HISTORY_SIZE, default=DEFAULT_MAX_HISTORY): vol.All( # Correct usage
vol.Coerce(int), vol.Coerce(int),
vol.Range(min=1, max=100), vol.Range(min=1, max=100),
), ),
+270 -92
View File
@@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
import traceback
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
@@ -9,6 +10,8 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.const import CONF_NAME
from .config_flow import normalize_name
from .const import ( from .const import (
DOMAIN, DOMAIN,
@@ -21,11 +24,16 @@ from .const import (
DEFAULT_TEMPERATURE, DEFAULT_TEMPERATURE,
DEFAULT_MAX_HISTORY, DEFAULT_MAX_HISTORY,
DEFAULT_CONTEXT_MESSAGES, DEFAULT_CONTEXT_MESSAGES,
DEFAULT_NAME_PREFIX,
CONF_MAX_HISTORY_SIZE,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
class HATextAICoordinator(DataUpdateCoordinator): class HATextAICoordinator(DataUpdateCoordinator):
"""The HA Text AI coordinator."""
def __init__( def __init__(
self, self,
hass: HomeAssistant, hass: HomeAssistant,
@@ -41,6 +49,12 @@ class HATextAICoordinator(DataUpdateCoordinator):
) -> None: ) -> None:
"""Initialize coordinator.""" """Initialize coordinator."""
self.instance_name = instance_name self.instance_name = instance_name
self.normalized_name = None
# Use the normalize_name function from config_flow to ensure consistency
from .config_flow import normalize_name
self.normalized_name = normalize_name(instance_name)
self.hass = hass self.hass = hass
self.client = client self.client = client
self.model = model self.model = model
@@ -61,7 +75,7 @@ class HATextAICoordinator(DataUpdateCoordinator):
"total_errors": 0, "total_errors": 0,
"average_latency": 0, "average_latency": 0,
"max_latency": 0, "max_latency": 0,
"min_latency": float('inf'), "min_latency": float("inf"),
}, },
"last_response": { "last_response": {
"timestamp": dt_util.utcnow().isoformat(), "timestamp": dt_util.utcnow().isoformat(),
@@ -69,7 +83,8 @@ class HATextAICoordinator(DataUpdateCoordinator):
"response": "", "response": "",
"model": model, "model": model,
"instance": instance_name, "instance": instance_name,
"error": None "normalized_name": self.normalized_name,
"error": None,
}, },
"is_processing": False, "is_processing": False,
"is_rate_limited": False, "is_rate_limited": False,
@@ -105,13 +120,17 @@ class HATextAICoordinator(DataUpdateCoordinator):
self.last_response = self._initial_state["last_response"].copy() self.last_response = self._initial_state["last_response"].copy()
self._start_time = dt_util.utcnow() self._start_time = dt_util.utcnow()
_LOGGER.info(f"Initialized HA Text AI coordinator with instance: {instance_name}") _LOGGER.info(
f"Initialized HA Text AI coordinator with instance: {instance_name}"
)
async def _async_update_data(self) -> Dict[str, Any]: async def _async_update_data(self) -> Dict[str, Any]:
"""Update data via library.""" """Update data via library."""
try: try:
current_state = self._get_current_state() current_state = self._get_current_state()
_LOGGER.debug(f"Updating data for {self.instance_name}, current state: {current_state}") _LOGGER.debug(
f"Updating data for {self.instance_name}, current state: {current_state}"
)
data = { data = {
"state": current_state, "state": current_state,
@@ -125,6 +144,7 @@ class HATextAICoordinator(DataUpdateCoordinator):
"system_prompt": self._system_prompt, "system_prompt": self._system_prompt,
"history_size": len(self._conversation_history), "history_size": len(self._conversation_history),
"conversation_history": self._conversation_history, "conversation_history": self._conversation_history,
"normalized_name": self.normalized_name,
} }
# Validate data # Validate data
@@ -141,12 +161,15 @@ class HATextAICoordinator(DataUpdateCoordinator):
async def async_update_ha_state(self) -> None: async def async_update_ha_state(self) -> None:
"""Update Home Assistant state.""" """Update Home Assistant state."""
try: try:
_LOGGER.debug(f"Requesting state update for {self.instance_name}") _LOGGER.debug(
f"Requesting state update for {self.instance_name} (normalized: {self.normalized_name})"
)
await self.async_request_refresh() await self.async_request_refresh()
# Force update of all entities # Force update of all entities
entity_id_base = f"sensor.ha_text_ai_{self.normalized_name.lower()}"
for entity_id in self.hass.states.async_entity_ids(): for entity_id in self.hass.states.async_entity_ids():
if entity_id.startswith(f"sensor.ha_text_ai_{self.instance_name}"): if entity_id.startswith(entity_id_base):
self.hass.states.async_set(entity_id, self._get_current_state()) self.hass.states.async_set(entity_id, self._get_current_state())
except Exception as err: except Exception as err:
@@ -164,15 +187,67 @@ class HATextAICoordinator(DataUpdateCoordinator):
return STATE_ERROR return STATE_ERROR
return STATE_READY return STATE_READY
def _calculate_context_tokens(self, messages: List[Dict[str, str]]) -> int: def _calculate_context_tokens(self, messages: List[Dict[str, str]], model: str = None) -> int:
"""
Estimate tokens for conversation context.
Args:
messages: List of message dictionaries
model: Optional model name for provider-specific estimation
Returns:
Estimated number of tokens
"""
try: try:
# Anthropic specific token counting
if self.is_anthropic and hasattr(self.client, 'count_tokens'): if self.is_anthropic and hasattr(self.client, 'count_tokens'):
return sum(self.client.count_tokens(msg['content']) for msg in messages) return sum(self.client.count_tokens(msg['content']) for msg in messages)
return sum(len(msg['content']) // 4 for msg in messages) def estimate_tokens(text: str) -> int:
"""
Flexible token estimation algorithm.
Heuristics:
- Count words
- Estimate special characters
- Fallback to character-based estimation
"""
# Word-based estimation
words = len(text.split())
# Special character handling
special_chars = sum(1 for char in text if not char.isalnum())
# Character-based fallback
char_tokens = len(text) // 4
# Combine estimations with bias towards words
total_tokens = (words * 1.5) + (special_chars * 0.5) + char_tokens
return max(int(total_tokens), words)
# Calculate total tokens across all messages
total_tokens = sum(estimate_tokens(msg['content']) for msg in messages)
# Logging for debugging
_LOGGER.debug(
f"Token Estimation: "
f"Messages: {len(messages)}, "
f"Estimated Tokens: {total_tokens}"
)
return total_tokens
except Exception as e: except Exception as e:
_LOGGER.warning(f"Error calculating context tokens: {e}") # Safe fallback with detailed logging
return 0 _LOGGER.warning(
f"Token estimation failed. "
f"Error: {e}. "
f"Using conservative estimation."
)
# Conservative token estimation
return len(messages) * 100
async def async_ask_question( async def async_ask_question(
self, self,
@@ -183,80 +258,133 @@ class HATextAICoordinator(DataUpdateCoordinator):
system_prompt: Optional[str] = None, system_prompt: Optional[str] = None,
context_messages: Optional[int] = None, context_messages: Optional[int] = None,
) -> dict: ) -> dict:
"""Process a question with optional parameters.""" """
Process a question with optional parameters.
This method is a direct wrapper around async_process_question,
allowing flexible AI interaction with optional model, temperature,
and context customization.
Args:
question: The input question or prompt
model: Optional AI model to use
temperature: Optional response creativity level
max_tokens: Optional maximum response length
system_prompt: Optional system-level instruction
context_messages: Optional number of context messages to include
Returns:
Full response dictionary from the AI
"""
return await self.async_process_question( return await self.async_process_question(
question, model, temperature, max_tokens, system_prompt, context_messages question, model, temperature, max_tokens, system_prompt, context_messages
) )
async def async_process_question( async def async_process_question(
self, self,
question: str, question: str,
model: Optional[str] = None, model: Optional[str] = None,
temperature: Optional[float] = None, temperature: Optional[float] = None,
max_tokens: Optional[int] = None, max_tokens: Optional[int] = None,
system_prompt: Optional[str] = None, system_prompt: Optional[str] = None,
context_messages: Optional[int] = None, context_messages: Optional[int] = None,
) -> dict: ) -> dict:
temp_context_messages = context_messages or self.context_messages """
Enhanced question processing with intelligent token management.
"""
try:
self._is_processing = True
await self.async_update_ha_state()
if not question: temp_context_messages = context_messages or self.context_messages
raise ValueError("Question cannot be empty") temp_model = model or self.model
temp_temperature = temperature or self.temperature
temp_max_tokens = max_tokens or self.max_tokens
temp_system_prompt = system_prompt or self._system_prompt
_LOGGER.debug(f"Processing question for instance {self.instance_name}") # Start timing
start_time = dt_util.utcnow()
try: # Prepare messages with system prompt
self._is_processing = True messages = []
await self.async_update_ha_state() if temp_system_prompt:
temp_model = model or self.model
temp_temperature = temperature or self.temperature
temp_max_tokens = max_tokens or self.max_tokens
temp_system_prompt = system_prompt or self._system_prompt
start_time = dt_util.utcnow()
messages = []
if temp_system_prompt:
if self.is_anthropic:
system_content = f"\n\nHuman: {temp_system_prompt}\n\nAssistant: I understand and will follow these instructions."
messages.append({"role": "user", "content": system_content})
else:
messages.append({"role": "system", "content": temp_system_prompt}) messages.append({"role": "system", "content": temp_system_prompt})
# Add conversation history # Context history management
context_history = self._conversation_history[-temp_context_messages:] context_history = self._conversation_history[-temp_context_messages:]
for entry in context_history:
messages.append({"role": "user", "content": entry["question"]})
messages.append({"role": "assistant", "content": entry["response"]})
messages.append({"role": "user", "content": question}) # Comprehensive token calculation
context_tokens = self._calculate_context_tokens(
[{"content": entry["question"]} for entry in context_history] +
[{"content": entry["response"]} for entry in context_history] +
[{"content": question}],
temp_model
)
kwargs = { # Dynamic token allocation
"model": temp_model, available_tokens = max(0, temp_max_tokens - context_tokens)
"temperature": temp_temperature,
"max_tokens": temp_max_tokens,
"messages": messages,
}
response = await self.async_process_message(question, **kwargs) # Context trimming if over token limit
if context_tokens > temp_max_tokens:
_LOGGER.warning(
f"Token limit exceeded. "
f"Context: {context_tokens}, "
f"Max: {temp_max_tokens}"
)
# Update metrics # Intelligent context reduction
end_time = dt_util.utcnow() while context_tokens > temp_max_tokens // 2 and context_history:
latency = (end_time - start_time).total_seconds() context_history.pop(0)
self._update_metrics(latency, response) context_tokens = self._calculate_context_tokens(
[{"content": entry["question"]} for entry in context_history] +
[{"content": entry["response"]} for entry in context_history] +
[{"content": question}],
temp_model
)
# Update history # Rebuild messages with trimmed context
self._update_history(question, response) for entry in context_history:
messages.append({"role": "user", "content": entry["question"]})
messages.append({"role": "assistant", "content": entry["response"]})
return response messages.append({"role": "user", "content": question})
except Exception as err: # Detailed token logging
self._handle_error(err) _LOGGER.debug(
raise HomeAssistantError(f"Failed to process question: {err}") f"Token Analysis: "
f"Context Tokens: {context_tokens}, "
f"Max Tokens: {temp_max_tokens}, "
f"Available Tokens: {available_tokens}"
)
finally: # Prepare API call with dynamic token management
self._is_processing = False kwargs = {
await self.async_update_ha_state() "model": temp_model,
"temperature": temp_temperature,
"max_tokens": min(temp_max_tokens, available_tokens),
"messages": messages,
}
# Process message
response = await self.async_process_message(question, **kwargs)
# Update metrics
end_time = dt_util.utcnow()
latency = (end_time - start_time).total_seconds()
self._update_metrics(latency, response)
# Update history
self._update_history(question, response)
return response
except Exception as err:
self._handle_error(err)
raise HomeAssistantError(f"Failed to process question: {err}")
finally:
self._is_processing = False
await self.async_update_ha_state()
async def async_process_message(self, question: str, **kwargs) -> dict: async def async_process_message(self, question: str, **kwargs) -> dict:
"""Process message using the AI client.""" """Process message using the AI client."""
@@ -272,7 +400,7 @@ class HATextAICoordinator(DataUpdateCoordinator):
"response": response["content"], "response": response["content"],
"model": kwargs.get("model", self.model), "model": kwargs.get("model", self.model),
"instance": self.instance_name, "instance": self.instance_name,
"error": None "error": None,
} }
return response return response
@@ -283,20 +411,26 @@ class HATextAICoordinator(DataUpdateCoordinator):
async def _process_anthropic_message(self, question: str, **kwargs) -> dict: async def _process_anthropic_message(self, question: str, **kwargs) -> dict:
"""Process message using Anthropic API.""" """Process message using Anthropic API."""
response = await self.client.messages.create( try:
model=kwargs["model"], _LOGGER.debug(f"Anthropic API call: model={kwargs['model']}, max_tokens={kwargs['max_tokens']}")
max_tokens=kwargs["max_tokens"], response = await self.client.messages.create(
messages=kwargs["messages"], model=kwargs["model"],
temperature=kwargs["temperature"], max_tokens=kwargs["max_tokens"],
) messages=kwargs["messages"],
return { temperature=kwargs["temperature"],
"content": response.content[0].text, )
"tokens": { _LOGGER.debug(f"Anthropic response: tokens={response.usage}")
"prompt": response.usage.input_tokens, return {
"completion": response.usage.output_tokens, "content": response.content[0].text,
"total": response.usage.input_tokens + response.usage.output_tokens "tokens": {
"prompt": response.usage.input_tokens,
"completion": response.usage.output_tokens,
"total": response.usage.input_tokens + response.usage.output_tokens,
},
} }
} except Exception as e:
_LOGGER.error(f"Anthropic API error: {str(e)}")
raise
async def _process_openai_message(self, question: str, **kwargs) -> dict: async def _process_openai_message(self, question: str, **kwargs) -> dict:
"""Process message using OpenAI API.""" """Process message using OpenAI API."""
@@ -313,8 +447,8 @@ class HATextAICoordinator(DataUpdateCoordinator):
"tokens": { "tokens": {
"prompt": response["usage"]["prompt_tokens"], "prompt": response["usage"]["prompt_tokens"],
"completion": response["usage"]["completion_tokens"], "completion": response["usage"]["completion_tokens"],
"total": response["usage"]["total_tokens"] "total": response["usage"]["total_tokens"],
} },
} }
except Exception as e: except Exception as e:
_LOGGER.error(f"Error in OpenAI API call: {str(e)}") _LOGGER.error(f"Error in OpenAI API call: {str(e)}")
@@ -339,29 +473,68 @@ class HATextAICoordinator(DataUpdateCoordinator):
def _update_history(self, question: str, response: dict) -> None: def _update_history(self, question: str, response: dict) -> None:
"""Update conversation history.""" """Update conversation history."""
self._conversation_history.append({ self._conversation_history.append(
"timestamp": dt_util.utcnow().isoformat(), {
"question": question, "timestamp": dt_util.utcnow().isoformat(),
"response": response["content"] "question": question,
}) "response": response["content"],
}
)
while len(self._conversation_history) > self.max_history_size: while len(self._conversation_history) > self.max_history_size:
self._conversation_history.pop(0) self._conversation_history.pop(0)
def _handle_error(self, error: Exception) -> None: def _handle_error(self, error: Exception) -> None:
"""Handle error and update metrics.""" """
Enhanced error handling with comprehensive diagnostics.
Captures detailed error information, tracks error metrics,
and provides context for troubleshooting AI processing issues.
"""
self._performance_metrics["total_errors"] += 1 self._performance_metrics["total_errors"] += 1
self._performance_metrics["failed_requests"] += 1 self._performance_metrics["failed_requests"] += 1
self.last_response = { error_details = {
"timestamp": dt_util.utcnow().isoformat(), "timestamp": dt_util.utcnow().isoformat(),
"question": "",
"response": "",
"model": self.model, "model": self.model,
"instance": self.instance_name, "instance": self.instance_name,
"error": str(error) "error_message": str(error),
"error_type": type(error).__name__,
"traceback": traceback.format_exc() if _LOGGER.isEnabledFor(logging.DEBUG) else None,
} }
# Specific error type handling
error_mapping = {
HomeAssistantError: {"is_ha_error": True},
ConnectionError: {
"is_connection_error": True,
"is_rate_limited": True
},
TimeoutError: {"is_timeout": True},
PermissionError: {"is_permission_denied": True},
ValueError: {"is_validation_error": True}
}
for error_type, error_flags in error_mapping.items():
if isinstance(error, error_type):
error_details.update(error_flags)
break
# Update system state based on error type
if error_details.get("is_rate_limited"):
self._is_rate_limited = True
_LOGGER.warning(f"Rate limit detected for {self.instance_name}")
if error_details.get("is_connection_error"):
self.endpoint_status = "unavailable"
self.last_response = error_details
_LOGGER.error(f"AI Processing Error: {error_details}")
# Optional: Add more sophisticated error tracking or notification logic
if _LOGGER.isEnabledFor(logging.DEBUG):
_LOGGER.debug(f"Full Error Traceback: {error_details['traceback']}")
async def async_clear_history(self) -> None: async def async_clear_history(self) -> None:
"""Clear conversation history.""" """Clear conversation history."""
self._conversation_history = [] self._conversation_history = []
@@ -375,3 +548,8 @@ class HATextAICoordinator(DataUpdateCoordinator):
"""Set system prompt.""" """Set system prompt."""
self._system_prompt = prompt self._system_prompt = prompt
await self.async_update_ha_state() await self.async_update_ha_state()
async def async_shutdown(self) -> None:
"""Shutdown coordinator."""
_LOGGER.debug(f"Shutting down coordinator for {self.instance_name}")
self.hass.data[DOMAIN].pop(self.instance_name, None)
+1 -1
View File
@@ -23,6 +23,6 @@
"single_config_entry": false, "single_config_entry": false,
"ssdp": [], "ssdp": [],
"usb": [], "usb": [],
"version": "2.0.1-alpha", "version": "2.0.2-alpha",
"zeroconf": [] "zeroconf": []
} }
+67 -29
View File
@@ -58,25 +58,39 @@ from .const import (
ENTITY_ICON, ENTITY_ICON,
ENTITY_ICON_ERROR, ENTITY_ICON_ERROR,
ENTITY_ICON_PROCESSING, ENTITY_ICON_PROCESSING,
DEFAULT_NAME_PREFIX,
CONF_MAX_HISTORY_SIZE,
) )
from .coordinator import HATextAICoordinator from .coordinator import HATextAICoordinator
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: ConfigEntry, entry: ConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the HA Text AI sensor.""" """Set up the HA Text AI sensor."""
coordinator = hass.data[DOMAIN][entry.entry_id] _LOGGER.debug(f"Starting sensor setup for entry: {entry.entry_id}")
instance_name = coordinator.instance_name
_LOGGER.debug(f"Setting up sensor with instance: {instance_name}") try:
coordinator = hass.data[DOMAIN][entry.entry_id]
_LOGGER.debug(f"Found coordinator for entry {entry.entry_id}")
sensor = HATextAISensor(coordinator, entry) instance_name = coordinator.instance_name
async_add_entities([sensor], True) _LOGGER.debug(f"Setting up sensor with instance: {instance_name}")
sensor = HATextAISensor(coordinator, entry)
_LOGGER.debug(f"Created sensor instance: {sensor.entity_id}")
async_add_entities([sensor], True)
_LOGGER.debug(f"Added sensor entity: {sensor.entity_id}")
except Exception as err:
_LOGGER.exception(f"Error setting up sensor: {err}")
raise
class HATextAISensor(CoordinatorEntity, SensorEntity): class HATextAISensor(CoordinatorEntity, SensorEntity):
"""HA Text AI Sensor.""" """HA Text AI Sensor."""
@@ -89,19 +103,30 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
config_entry: ConfigEntry, config_entry: ConfigEntry,
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
_LOGGER.debug(f"Initializing sensor with config entry: {config_entry.data}")
super().__init__(coordinator) super().__init__(coordinator)
self._config_entry = config_entry self._config_entry = config_entry
self._instance_name = coordinator.instance_name self._instance_name = coordinator.instance_name
self._normalized_name = coordinator.normalized_name
_LOGGER.debug(f"Instance name: {self._instance_name}")
_LOGGER.debug(f"Normalized name: {self._normalized_name}")
self._conversation_history = [] self._conversation_history = []
self._system_prompt = None self._system_prompt = None
self._attr_name = f"HA Text AI {self._instance_name}" self._attr_name = f"HA Text AI {self._instance_name}"
self.entity_id = f"sensor.ha_text_ai_{slugify(self._instance_name)}" self.entity_id = f"sensor.ha_text_ai_{self._normalized_name}"
self._attr_unique_id = f"{config_entry.entry_id}" self._attr_unique_id = f"{config_entry.entry_id}"
_LOGGER.debug(f"Created sensor with entity_id: {self.entity_id}")
_LOGGER.debug(f"Sensor name: {self._attr_name}")
_LOGGER.debug(f"Unique ID: {self._attr_unique_id}")
self.entity_description = SensorEntityDescription( self.entity_description = SensorEntityDescription(
key=f"ha_text_ai_{self._instance_name}", key=f"ha_text_ai_{self._normalized_name.lower()}",
entity_registry_enabled_default=True, entity_registry_enabled_default=True,
) )
@@ -118,13 +143,15 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._attr_unique_id)}, identifiers={(DOMAIN, self._attr_unique_id)},
name=self._attr_name, # Используем имя сенсора name=self._attr_name,
manufacturer="Community", manufacturer="Community",
model=f"{model} ({api_provider} provider)", model=f"{model} ({api_provider} provider)",
sw_version="1.0.0", sw_version="1.0.0",
) )
_LOGGER.debug(f"Initialized sensor: {self.entity_id} for instance: {self._instance_name}") _LOGGER.debug(
f"Initialized sensor: {self.entity_id} for instance: {self._instance_name}"
)
@property @property
def available(self) -> bool: def available(self) -> bool:
@@ -180,11 +207,14 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
data = self.coordinator.data data = self.coordinator.data
attributes = { attributes = {
ATTR_MODEL: self._config_entry.data.get(CONF_MODEL, "Unknown"), ATTR_MODEL: self._config_entry.data.get(CONF_MODEL, "Unknown"),
ATTR_API_PROVIDER: self._config_entry.data.get(CONF_API_PROVIDER, "Unknown"), ATTR_API_PROVIDER: self._config_entry.data.get(
CONF_API_PROVIDER, "Unknown"
),
ATTR_API_STATUS: self._current_state, ATTR_API_STATUS: self._current_state,
ATTR_TOTAL_ERRORS: self._error_count, ATTR_TOTAL_ERRORS: self._error_count,
ATTR_LAST_ERROR: self._last_error, ATTR_LAST_ERROR: self._last_error,
"instance_name": self._instance_name, "instance_name": self._instance_name,
"normalized_name": self._normalized_name,
ATTR_SYSTEM_PROMPT: data.get("system_prompt"), ATTR_SYSTEM_PROMPT: data.get("system_prompt"),
ATTR_IS_PROCESSING: data.get("is_processing", False), ATTR_IS_PROCESSING: data.get("is_processing", False),
ATTR_IS_RATE_LIMITED: data.get("is_rate_limited", False), ATTR_IS_RATE_LIMITED: data.get("is_rate_limited", False),
@@ -199,32 +229,40 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
metrics = data.get("metrics", {}) metrics = data.get("metrics", {})
if isinstance(metrics, dict): if isinstance(metrics, dict):
self._metrics = metrics self._metrics = metrics
attributes.update({ attributes.update(
METRIC_TOTAL_TOKENS: metrics.get("total_tokens", 0), {
METRIC_PROMPT_TOKENS: metrics.get("prompt_tokens", 0), METRIC_TOTAL_TOKENS: metrics.get("total_tokens", 0),
METRIC_COMPLETION_TOKENS: metrics.get("completion_tokens", 0), METRIC_PROMPT_TOKENS: metrics.get("prompt_tokens", 0),
METRIC_SUCCESSFUL_REQUESTS: metrics.get("successful_requests", 0), METRIC_COMPLETION_TOKENS: metrics.get("completion_tokens", 0),
METRIC_FAILED_REQUESTS: metrics.get("failed_requests", 0), METRIC_SUCCESSFUL_REQUESTS: metrics.get(
METRIC_AVERAGE_LATENCY: metrics.get("average_latency", 0), "successful_requests", 0
METRIC_MAX_LATENCY: metrics.get("max_latency", 0), ),
METRIC_MIN_LATENCY: metrics.get("min_latency", float("inf")), METRIC_FAILED_REQUESTS: metrics.get("failed_requests", 0),
}) METRIC_AVERAGE_LATENCY: metrics.get("average_latency", 0),
METRIC_MAX_LATENCY: metrics.get("max_latency", 0),
METRIC_MIN_LATENCY: metrics.get("min_latency", float("inf")),
}
)
# Add last response # Add last response
last_response = data.get("last_response", {}) last_response = data.get("last_response", {})
if isinstance(last_response, dict): if isinstance(last_response, dict):
self._last_response = last_response self._last_response = last_response
attributes.update({ attributes.update(
ATTR_RESPONSE: last_response.get("response", ""), {
ATTR_QUESTION: last_response.get("question", ""), ATTR_RESPONSE: last_response.get("response", ""),
"last_model": last_response.get("model", ""), ATTR_QUESTION: last_response.get("question", ""),
"last_timestamp": last_response.get("timestamp", ""), "last_model": last_response.get("model", ""),
"last_error": last_response.get("error"), "last_timestamp": last_response.get("timestamp", ""),
}) "last_error": last_response.get("error"),
}
)
# Add performance metrics if available # Add performance metrics if available
if ATTR_PERFORMANCE_METRICS in data: if ATTR_PERFORMANCE_METRICS in data:
attributes[ATTR_PERFORMANCE_METRICS] = data[ATTR_PERFORMANCE_METRICS] attributes[ATTR_PERFORMANCE_METRICS] = data[
ATTR_PERFORMANCE_METRICS
]
# Add API version if available # Add API version if available
if ATTR_API_VERSION in data: if ATTR_API_VERSION in data:
@@ -288,7 +326,7 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
"Error handling update for %s: %s", "Error handling update for %s: %s",
self.entity_id, self.entity_id,
err, err,
exc_info=True exc_info=True,
) )
self.async_write_ha_state() self.async_write_ha_state()
+98 -4
View File
@@ -40,15 +40,14 @@ ask_question:
min: 1 min: 1
max: 20 max: 20
step: 1 step: 1
mode: box mode: slider
model: model:
name: Model name: Model
description: "Select AI model to use (optional, overrides default setting)" description: "Select AI model to use (optional, overrides default setting)"
required: false required: false
selector: selector:
text: text: {}
multiline: false
temperature: temperature:
name: Temperature name: Temperature
@@ -73,3 +72,98 @@ ask_question:
max: 4096 max: 4096
step: 1 step: 1
mode: box mode: box
clear_history:
name: Clear History
description: >-
Delete all stored questions and responses from the conversation history
fields:
instance:
name: Instance
description: Name of the HA Text AI instance to clear history for
required: true
selector:
entity:
integration: ha_text_ai
domain: sensor
get_history:
name: Get History
description: Retrieve conversation history with optional filtering and sorting
fields:
instance:
name: Instance
description: Name of the HA Text AI instance to get history from
required: true
selector:
entity:
integration: ha_text_ai
domain: sensor
limit:
name: Limit
description: Number of conversations to return (1-100)
required: false
default: 10
selector:
number:
min: 1
max: 100
step: 1
mode: box
filter_model:
name: Filter Model
description: Filter conversations by specific AI model
required: false
selector:
text:
multiline: false
start_date:
name: Start Date
description: Filter conversations starting from this date/time
required: false
selector:
text:
multiline: false
include_metadata:
name: Include Metadata
description: Include additional information like tokens used, response time, etc.
required: false
default: false
selector:
boolean:
sort_order:
name: Sort Order
description: Sort order for results (newest or oldest first)
required: false
default: newest
selector:
select:
options:
- newest
- oldest
set_system_prompt:
name: Set System Prompt
description: Set default system behavior instructions for all future conversations
fields:
instance:
name: Instance
description: Name of the HA Text AI instance to set system prompt for
required: true
selector:
entity:
integration: ha_text_ai
domain: sensor
prompt:
name: System Prompt
description: Instructions that define how the AI should behave and respond
required: true
selector:
text:
multiline: true
+261 -256
View File
@@ -1,261 +1,266 @@
{ {
"config": { "config": {
"step": { "step": {
"provider": { "provider": {
"title": "KI-Anbieter auswählen", "title": "KI-Anbieter auswählen",
"description": "Wählen Sie den KI-Dienst-Anbieter für diese Instanz", "description": "Wählen Sie den KI-Dienstanbieter für diese Instanz aus.",
"data": { "data": {
"api_provider": "API-Anbieter", "api_provider": "API-Anbieter",
"context_messages": "Anzahl der zu speichernden Kontextnachrichten (1-20)" "context_messages": "Anzahl der zu behaltenden Kontextnachrichten (1-20)"
}
},
"user": {
"title": "HA Text AI Instanz konfigurieren",
"description": "Richten Sie eine neue KI-Assistenten-Instanz mit Ihrem ausgewählten Anbieter ein",
"data": {
"name": "Instanzname (z.B. 'GPT Assistent', 'Claude Helfer')",
"api_key": "API-Schlüssel für Authentifizierung",
"model": "Zu verwendendes KI-Modell",
"temperature": "Antwort-Kreativität (0-2, niedriger = fokussierter)",
"max_tokens": "Maximale Antwortlänge (1-4096 Token)",
"api_endpoint": "Benutzerdefinierte API-Endpunkt-URL (optional)",
"request_interval": "Minimale Zeit zwischen Anfragen (0,1-60 Sekunden)",
"context_messages": "Anzahl der zu speichernden Kontextnachrichten (1-20)"
}
}
},
"error": {
"name_exists": "Eine Instanz mit diesem Namen existiert bereits",
"invalid_name": "Ungültiger Instanzname",
"invalid_auth": "Authentifizierung fehlgeschlagen - überprüfen Sie Ihren API-Schlüssel",
"invalid_api_key": "Ungültiger API-Schlüssel - bitte überprüfen Sie Ihre Anmeldedaten",
"cannot_connect": "Verbindung zum API-Dienst fehlgeschlagen",
"invalid_model": "Ausgewähltes Modell ist nicht verfügbar",
"rate_limit": "Anfragelimit überschritten",
"context_length": "Kontextlänge überschritten",
"rate_limit_exceeded": "API-Anfragelimit überschritten",
"maintenance": "Dienst ist in Wartung",
"invalid_response": "Ungültige API-Antwort erhalten",
"api_error": "API-Dienst-Fehler aufgetreten",
"timeout": "Anfrage-Zeitüberschreitung",
"invalid_instance": "Ungültige Instanz angegeben",
"unknown": "Unerwarteter Fehler aufgetreten"
} }
},
"user": {
"title": "HA Text AI-Instanz konfigurieren",
"description": "Richten Sie eine neue KI-Assistenteninstanz mit Ihrem ausgewählten Anbieter ein.",
"data": {
"name": "Instanzname (z. B. 'GPT-Assistent', 'Claude-Helfer')",
"api_key": "API-Schlüssel zur Authentifizierung",
"model": "Zu verwendendes KI-Modell",
"temperature": "Antwortkreativität (0-2, niedriger = fokussierter)",
"max_tokens": "Maximale Antwortlänge (1-4096 Token)",
"api_endpoint": "Benutzerdefinierte API-Endpunkt-URL (optional)",
"api_provider": "API-Anbieter",
"request_interval": "Mindestzeit zwischen Anfragen (0,1-60 Sekunden)",
"context_messages": "Anzahl der zu behaltenden Kontextnachrichten (1-20)",
"max_history_size": "Maximale Größe des Konversationsverlaufs (1-100)"
}
}
}, },
"options": { "error": {
"step": { "name_exists": "Eine Instanz mit diesem Namen existiert bereits",
"init": { "invalid_name": "Ungültiger Instanzname",
"title": "Instanzeinstellungen aktualisieren", "invalid_auth": "Authentifizierung fehlgeschlagen - überprüfen Sie Ihren API-Schlüssel",
"description": "Einstellungen für diese KI-Assistenten-Instanz ändern", "invalid_api_key": "Ungültiger API-Schlüssel - überprüfen Sie Ihre Anmeldedaten",
"data": { "cannot_connect": "Verbindung zum API-Dienst fehlgeschlagen",
"model": "KI-Modell", "invalid_model": "Das ausgewählte Modell ist nicht verfügbar",
"temperature": "Antwort-Kreativität (0-2)", "rate_limit": "Ratenlimit überschritten",
"max_tokens": "Maximale Antwortlänge (1-4096)", "context_length": "Kontextlänge überschritten",
"request_interval": "Minimales Anfragen-Intervall (0,1-60 Sekunden)", "rate_limit_exceeded": "API-Ratenlimit überschritten",
"context_messages": "Anzahl der vorherigen Nachrichten, die in den Kontext einbezogen werden sollen (1-20)" "maintenance": "Dienst befindet sich in der Wartung",
"invalid_response": "Ungültige API-Antwort empfangen",
} "api_error": "Fehler im API-Dienst aufgetreten",
} "timeout": "Anfrage ist abgelaufen",
} "invalid_instance": "Ungültige Instanz angegeben",
}, "unknown": "Es ist ein unerwarteter Fehler aufgetreten",
"services": { "empty": "Name darf nicht leer sein",
"ask_question": { "invalid_characters": "Name darf nur Buchstaben, Zahlen, Leerzeichen, Unterstriche und Bindestriche enthalten",
"name": "Frage stellen (HA Text AI)", "name_too_long": "Name darf maximal 50 Zeichen lang sein"
"description": "Senden Sie eine Frage an das KI-Modell und erhalten Sie eine detaillierte Antwort. Die Antwort wird in der Gesprächshistorie gespeichert und kann später abgerufen werden.",
"fields": {
"instance": {
"name": "Instanz",
"description": "Name der zu verwendenden HA Text AI Instanz"
},
"question": {
"name": "Frage",
"description": "Ihre Frage oder Eingabeaufforderung für den KI-Assistenten"
},
"context_messages": {
"name": "Kontextnachrichten",
"description": "Anzahl der vorherigen Nachrichten, die in den Kontext einbezogen werden sollen (1-20)"
},
"system_prompt": {
"name": "Systemaufforderung",
"description": "Optionale Systemaufforderung zur Kontexteinstellung für diese spezifische Frage"
},
"model": {
"name": "Modell",
"description": "Wählen Sie das zu verwendende KI-Modell (optional, überschreibt Standardeinstellung)"
},
"temperature": {
"name": "Temperatur",
"description": "Steuert die Antwort-Kreativität (0,0-2,0)"
},
"max_tokens": {
"name": "Max. Token",
"description": "Maximale Länge der Antwort (1-4096 Token)"
}
}
},
"clear_history": {
"name": "Verlauf löschen",
"description": "Alle gespeicherten Fragen und Antworten aus dem Gesprächsverlauf löschen",
"fields": {
"instance": {
"name": "Instanz",
"description": "Name der HA Text AI Instanz, für die der Verlauf gelöscht werden soll"
}
}
},
"get_history": {
"name": "Verlauf abrufen",
"description": "Gesprächsverlauf mit optionaler Filterung und Sortierung abrufen",
"fields": {
"instance": {
"name": "Instanz",
"description": "Name der HA Text AI Instanz, aus der der Verlauf abgerufen werden soll"
},
"limit": {
"name": "Limit",
"description": "Anzahl der zurückzugebenden Gespräche (1-100)"
},
"filter_model": {
"name": "Modell filtern",
"description": "Gespräche nach bestimmtem KI-Modell filtern"
},
"start_date": {
"name": "Startdatum",
"description": "Gespräche ab diesem Datum/Zeitpunkt filtern"
},
"include_metadata": {
"name": "Metadaten einbeziehen",
"description": "Zusätzliche Informationen wie verwendete Token, Antwortzeit usw. einbeziehen"
},
"sort_order": {
"name": "Sortierreihenfolge",
"description": "Sortierreihenfolge der Ergebnisse (neueste oder älteste zuerst)"
}
}
},
"set_system_prompt": {
"name": "Systemaufforderung festlegen",
"description": "Standardmäßige Systemverhaltensinstruktionen für alle zukünftigen Gespräche festlegen",
"fields": {
"instance": {
"name": "Instanz",
"description": "Name der HA Text AI Instanz, für die die Systemaufforderung festgelegt werden soll"
},
"prompt": {
"name": "Systemaufforderung",
"description": "Anweisungen, die definieren, wie sich die KI verhalten und antworten soll"
}
}
}
},
"entity": {
"sensor": {
"ha_text_ai": {
"name": "{name}",
"state": {
"ready": "Bereit",
"processing": "Verarbeitung",
"error": "Fehler",
"disconnected": "Getrennt",
"rate_limited": "Anfragelimit",
"maintenance": "Wartung",
"initializing": "Initialisierung",
"retrying": "Wiederholung",
"queued": "In Warteschlange"
},
"state_attributes": {
"question": {
"name": "Letzte Frage"
},
"response": {
"name": "Letzte Antwort"
},
"model": {
"name": "Aktuelles Modell"
},
"temperature": {
"name": "Temperatur"
},
"max_tokens": {
"name": "Max. Token"
},
"system_prompt": {
"name": "Systemaufforderung"
},
"response_time": {
"name": "Letzte Antwortzeit"
},
"total_responses": {
"name": "Gesamte Antworten"
},
"error_count": {
"name": "Fehleranzahl"
},
"last_error": {
"name": "Letzter Fehler"
},
"api_status": {
"name": "API-Status"
},
"tokens_used": {
"name": "Insgesamt verwendete Token"
},
"average_response_time": {
"name": "Durchschnittliche Antwortzeit"
},
"last_request_time": {
"name": "Letzte Anforderungszeit"
},
"is_processing": {
"name": "Verarbeitungsstatus"
},
"is_rate_limited": {
"name": "Status Anfragelimit"
},
"is_maintenance": {
"name": "Wartungsstatus"
},
"api_version": {
"name": "API-Version"
},
"endpoint_status": {
"name": "Endpunktstatus"
},
"performance_metrics": {
"name": "Leistungsmetriken"
},
"history_size": {
"name": "Verlaufsgröße"
},
"uptime": {
"name": "Betriebszeit"
},
"total_tokens": {
"name": "Gesamte Token"
},
"prompt_tokens": {
"name": "Prompt-Token"
},
"completion_tokens": {
"name": "Abschluss-Token"
},
"successful_requests": {
"name": "Erfolgreiche Anfragen"
},
"failed_requests": {
"name": "Fehlgeschlagene Anfragen"
},
"average_latency": {
"name": "Durchschnittliche Latenz"
},
"max_latency": {
"name": "Maximale Latenz"
},
"min_latency": {
"name": "Minimale Latenz"
}
}
}
}
} }
},
"options": {
"step": {
"init": {
"title": "Instanzeinstellungen aktualisieren",
"description": "Ändern Sie die Einstellungen für diese KI-Assistenteninstanz.",
"data": {
"model": "KI-Modell",
"temperature": "Antwortkreativität (0-2)",
"max_tokens": "Maximale Antwortlänge (1-4096)",
"request_interval": "Mindestzeitraum zwischen Anfragen (0,1-60 Sekunden)",
"context_messages": "Anzahl der vorherigen Nachrichten, die im Kontext enthalten sein sollen (1-20)",
"max_history_size": "Maximale Größe des Konversationsverlaufs (1-100)"
}
}
}
},
"services": {
"ask_question": {
"name": "Frage stellen (HA Text AI)",
"description": "Senden Sie eine Frage an das KI-Modell und erhalten Sie eine detaillierte Antwort. Die Antwort wird im Konversationsverlauf gespeichert und kann später abgerufen werden.",
"fields": {
"instance": {
"name": "Instanz",
"description": "Name der zu verwendenden HA Text AI-Instanz"
},
"question": {
"name": "Frage",
"description": "Ihre Frage oder Aufforderung an den KI-Assistenten"
},
"context_messages": {
"name": "Kontextnachrichten",
"description": "Anzahl der vorherigen Nachrichten, die im Kontext enthalten sein sollen (1-20)"
},
"system_prompt": {
"name": "Systemprompt",
"description": "Optionaler Systemprompt, um den Kontext für diese spezielle Frage festzulegen"
},
"model": {
"name": "Modell",
"description": "Wählen Sie das zu verwendende KI-Modell aus (optional, überschreibt die Standardeinstellung)"
},
"temperature": {
"name": "Temperatur",
"description": "Steuert die Antwortkreativität (0.0-2.0)"
},
"max_tokens": {
"name": "Max. Token",
"description": "Maximale Länge der Antwort (1-4096 Token)"
}
}
},
"clear_history": {
"name": "Verlauf löschen",
"description": "Alle gespeicherten Fragen und Antworten aus dem Konversationsverlauf löschen",
"fields": {
"instance": {
"name": "Instanz",
"description": "Name der HA Text AI-Instanz, deren Verlauf gelöscht werden soll"
}
}
},
"get_history": {
"name": "Verlauf abrufen",
"description": "Konversationsverlauf mit optionalem Filtern und Sortieren abrufen",
"fields": {
"instance": {
"name": "Instanz",
"description": "Name der HA Text AI-Instanz, von der der Verlauf abgerufen werden soll"
},
"limit": {
"name": "Limit",
"description": "Anzahl der zurückzugebenden Konversationen (1-100)"
},
"filter_model": {
"name": "Modell filtern",
"description": "Konversationen nach einem bestimmten KI-Modell filtern"
},
"start_date": {
"name": "Startdatum",
"description": "Konversationen ab diesem Datum/Uhrzeit filtern"
},
"include_metadata": {
"name": "Metadaten einschließen",
"description": "Zusätzliche Informationen wie verwendete Token, Antwortzeit usw. einschließen"
},
"sort_order": {
"name": "Sortierreihenfolge",
"description": "Sortierreihenfolge für die Ergebnisse (neueste oder älteste zuerst)"
}
}
},
"set_system_prompt": {
"name": "Systemprompt festlegen",
"description": "Legen Sie Standardanweisungen für das Systemverhalten für alle zukünftigen Konversationen fest",
"fields": {
"instance": {
"name": "Instanz",
"description": "Name der HA Text AI-Instanz, für die der Systemprompt festgelegt werden soll"
},
"prompt": {
"name": "Systemprompt",
"description": "Anweisungen, die definieren, wie sich die KI verhalten und antworten soll"
}
}
}
},
"entity": {
"sensor": {
"ha_text_ai": {
"name": "{name}",
"state": {
"ready": "Bereit",
"processing": "Verarbeitung",
"error": "Fehler",
"disconnected": "Getrennt",
"rate_limited": "Ratenlimit",
"maintenance": "Wartung",
"initializing": "Initialisierung",
"retrying": "Wiederholen",
"queued": "Warteschlange"
},
"state_attributes": {
"question": {
"name": "Letzte Frage"
},
"response": {
"name": "Letzte Antwort"
},
"model": {
"name": "Aktuelles Modell"
},
"temperature": {
"name": "Temperatur"
},
"max_tokens": {
"name": "Max. Token"
},
"system_prompt": {
"name": "Systemprompt"
},
"response_time": {
"name": "Letzte Antwortzeit"
},
"total_responses": {
"name": "Gesamtzahl der Antworten"
},
"error_count": {
"name": "Fehleranzahl"
},
"last_error": {
"name": "Letzter Fehler"
},
"api_status": {
"name": "API-Status"
},
"tokens_used": {
"name": "Verwendete Token insgesamt"
},
"average_response_time": {
"name": "Durchschnittliche Antwortzeit"
},
"last_request_time": {
"name": "Zeitpunkt der letzten Anfrage"
},
"is_processing": {
"name": "Verarbeitungsstatus"
},
"is_rate_limited": {
"name": "Ratenlimit-Status"
},
"is_maintenance": {
"name": "Wartungsstatus"
},
"api_version": {
"name": "API-Version"
},
"endpoint_status": {
"name": "Endpunkt-Status"
},
"performance_metrics": {
"name": "Leistungsmetriken"
},
"history_size": {
"name": "Verlaufsgröße"
},
"uptime": {
"name": "Betriebszeit"
},
"total_tokens": {
"name": "Gesamtzahl der Token"
},
"prompt_tokens": {
"name": "Prompt-Token"
},
"completion_tokens": {
"name": "Completion-Token"
},
"successful_requests": {
"name": "Erfolgreiche Anfragen"
},
"failed_requests": {
"name": "Fehlgeschlagene Anfragen"
},
"average_latency": {
"name": "Durchschnittliche Latenz"
},
"max_latency": {
"name": "Maximale Latenz"
},
"min_latency": {
"name": "Minimale Latenz"
}
}
}
}
}
} }
+261 -255
View File
@@ -1,260 +1,266 @@
{ {
"config": { "config": {
"step": { "step": {
"provider": { "provider": {
"title": "Select AI Provider", "title": "Select AI Provider",
"description": "Choose which AI service provider to use for this instance", "description": "Choose which AI service provider to use for this instance.",
"data": { "data": {
"api_provider": "API Provider", "api_provider": "API Provider",
"context_messages": "Number of context messages to retain (1-20)" "context_messages": "Number of context messages to retain (1-20)"
}
},
"user": {
"title": "Configure HA Text AI Instance",
"description": "Set up a new AI assistant instance with your selected provider",
"data": {
"name": "Instance name (e.g., 'GPT Assistant', 'Claude Helper')",
"api_key": "API key for authentication",
"model": "AI model to use",
"temperature": "Response creativity (0-2, lower = more focused)",
"max_tokens": "Maximum response length (1-4096 tokens)",
"api_endpoint": "Custom API endpoint URL (optional)",
"request_interval": "Minimum time between requests (0.1-60 seconds)",
"context_messages": "Number of context messages to retain (1-20)"
}
}
},
"error": {
"name_exists": "An instance with this name already exists",
"invalid_name": "Invalid instance name",
"invalid_auth": "Authentication failed - check your API key",
"invalid_api_key": "Invalid API key - please verify your credentials",
"cannot_connect": "Failed to connect to API service",
"invalid_model": "Selected model is not available",
"rate_limit": "Rate limit exceeded",
"context_length": "Context length exceeded",
"rate_limit_exceeded": "API rate limit exceeded",
"maintenance": "Service is under maintenance",
"invalid_response": "Invalid API response received",
"api_error": "API service error occurred",
"timeout": "Request timed out",
"invalid_instance": "Invalid instance specified",
"unknown": "Unexpected error occurred"
} }
},
"user": {
"title": "Configure HA Text AI Instance",
"description": "Set up a new AI assistant instance with your selected provider.",
"data": {
"name": "Instance name (e.g., 'GPT Assistant', 'Claude Helper')",
"api_key": "API key for authentication",
"model": "AI model to use",
"temperature": "Response creativity (0-2, lower = more focused)",
"max_tokens": "Maximum response length (1-4096 tokens)",
"api_endpoint": "Custom API endpoint URL (optional)",
"api_provider": "API Provider",
"request_interval": "Minimum time between requests (0.1-60 seconds)",
"context_messages": "Number of context messages to retain (1-20)",
"max_history_size": "Maximum conversation history size (1-100)"
}
}
}, },
"options": { "error": {
"step": { "name_exists": "An instance with this name already exists",
"init": { "invalid_name": "Invalid instance name",
"title": "Update Instance Settings", "invalid_auth": "Authentication failed - check your API key",
"description": "Modify settings for this AI assistant instance", "invalid_api_key": "Invalid API key - please verify your credentials",
"data": { "cannot_connect": "Failed to connect to API service",
"model": "AI model", "invalid_model": "Selected model is not available",
"temperature": "Response creativity (0-2)", "rate_limit": "Rate limit exceeded",
"max_tokens": "Maximum response length (1-4096)", "context_length": "Context length exceeded",
"request_interval": "Minimum request interval (0.1-60 seconds)", "rate_limit_exceeded": "API rate limit exceeded",
"context_messages": "Number of previous messages to include in context (1-20)" "maintenance": "Service is under maintenance",
} "invalid_response": "Invalid API response received",
} "api_error": "API service error occurred",
} "timeout": "Request timed out",
}, "invalid_instance": "Invalid instance specified",
"services": { "unknown": "Unexpected error occurred",
"ask_question": { "empty": "Name cannot be empty",
"name": "Ask Question (HA Text AI)", "invalid_characters": "Name can only contain letters, numbers, spaces, underscores and hyphens",
"description": "Send a question to the AI model and receive a detailed response. The response will be stored in the conversation history and can be retrieved later.", "name_too_long": "Name must be 50 characters or less"
"fields": {
"instance": {
"name": "Instance",
"description": "Name of the HA Text AI instance to use"
},
"question": {
"name": "Question",
"description": "Your question or prompt for the AI assistant"
},
"context_messages": {
"name": "Context Messages",
"description": "Number of previous messages to include in context (1-20)"
},
"system_prompt": {
"name": "System Prompt",
"description": "Optional system prompt to set context for this specific question"
},
"model": {
"name": "Model",
"description": "Select AI model to use (optional, overrides default setting)"
},
"temperature": {
"name": "Temperature",
"description": "Controls response creativity (0.0-2.0)"
},
"max_tokens": {
"name": "Max Tokens",
"description": "Maximum length of the response (1-4096 tokens)"
}
}
},
"clear_history": {
"name": "Clear History",
"description": "Delete all stored questions and responses from the conversation history",
"fields": {
"instance": {
"name": "Instance",
"description": "Name of the HA Text AI instance to clear history for"
}
}
},
"get_history": {
"name": "Get History",
"description": "Retrieve conversation history with optional filtering and sorting",
"fields": {
"instance": {
"name": "Instance",
"description": "Name of the HA Text AI instance to get history from"
},
"limit": {
"name": "Limit",
"description": "Number of conversations to return (1-100)"
},
"filter_model": {
"name": "Filter Model",
"description": "Filter conversations by specific AI model"
},
"start_date": {
"name": "Start Date",
"description": "Filter conversations starting from this date/time"
},
"include_metadata": {
"name": "Include Metadata",
"description": "Include additional information like tokens used, response time, etc."
},
"sort_order": {
"name": "Sort Order",
"description": "Sort order for results (newest or oldest first)"
}
}
},
"set_system_prompt": {
"name": "Set System Prompt",
"description": "Set default system behavior instructions for all future conversations",
"fields": {
"instance": {
"name": "Instance",
"description": "Name of the HA Text AI instance to set system prompt for"
},
"prompt": {
"name": "System Prompt",
"description": "Instructions that define how the AI should behave and respond"
}
}
}
},
"entity": {
"sensor": {
"ha_text_ai": {
"name": "{name}",
"state": {
"ready": "Ready",
"processing": "Processing",
"error": "Error",
"disconnected": "Disconnected",
"rate_limited": "Rate Limited",
"maintenance": "Maintenance",
"initializing": "Initializing",
"retrying": "Retrying",
"queued": "Queued"
},
"state_attributes": {
"question": {
"name": "Last Question"
},
"response": {
"name": "Last Response"
},
"model": {
"name": "Current Model"
},
"temperature": {
"name": "Temperature"
},
"max_tokens": {
"name": "Max Tokens"
},
"system_prompt": {
"name": "System Prompt"
},
"response_time": {
"name": "Last Response Time"
},
"total_responses": {
"name": "Total Responses"
},
"error_count": {
"name": "Error Count"
},
"last_error": {
"name": "Last Error"
},
"api_status": {
"name": "API Status"
},
"tokens_used": {
"name": "Total Tokens Used"
},
"average_response_time": {
"name": "Average Response Time"
},
"last_request_time": {
"name": "Last Request Time"
},
"is_processing": {
"name": "Processing Status"
},
"is_rate_limited": {
"name": "Rate Limited Status"
},
"is_maintenance": {
"name": "Maintenance Status"
},
"api_version": {
"name": "API Version"
},
"endpoint_status": {
"name": "Endpoint Status"
},
"performance_metrics": {
"name": "Performance Metrics"
},
"history_size": {
"name": "History Size"
},
"uptime": {
"name": "Uptime"
},
"total_tokens": {
"name": "Total Tokens"
},
"prompt_tokens": {
"name": "Prompt Tokens"
},
"completion_tokens": {
"name": "Completion Tokens"
},
"successful_requests": {
"name": "Successful Requests"
},
"failed_requests": {
"name": "Failed Requests"
},
"average_latency": {
"name": "Average Latency"
},
"max_latency": {
"name": "Maximum Latency"
},
"min_latency": {
"name": "Minimum Latency"
}
}
}
}
} }
},
"options": {
"step": {
"init": {
"title": "Update Instance Settings",
"description": "Modify settings for this AI assistant instance.",
"data": {
"model": "AI model",
"temperature": "Response creativity (0-2)",
"max_tokens": "Maximum response length (1-4096)",
"request_interval": "Minimum request interval (0.1-60 seconds)",
"context_messages": "Number of previous messages to include in context (1-20)",
"max_history_size": "Maximum conversation history size (1-100)"
}
}
}
},
"services": {
"ask_question": {
"name": "Ask Question (HA Text AI)",
"description": "Send a question to the AI model and receive a detailed response. The response will be stored in the conversation history and can be retrieved later.",
"fields": {
"instance": {
"name": "Instance",
"description": "Name of the HA Text AI instance to use"
},
"question": {
"name": "Question",
"description": "Your question or prompt for the AI assistant"
},
"context_messages": {
"name": "Context Messages",
"description": "Number of previous messages to include in context (1-20)"
},
"system_prompt": {
"name": "System Prompt",
"description": "Optional system prompt to set context for this specific question"
},
"model": {
"name": "Model",
"description": "Select AI model to use (optional, overrides default setting)"
},
"temperature": {
"name": "Temperature",
"description": "Controls response creativity (0.0-2.0)"
},
"max_tokens": {
"name": "Max Tokens",
"description": "Maximum length of the response (1-4096 tokens)"
}
}
},
"clear_history": {
"name": "Clear History",
"description": "Delete all stored questions and responses from the conversation history",
"fields": {
"instance": {
"name": "Instance",
"description": "Name of the HA Text AI instance to clear history for"
}
}
},
"get_history": {
"name": "Get History",
"description": "Retrieve conversation history with optional filtering and sorting",
"fields": {
"instance": {
"name": "Instance",
"description": "Name of the HA Text AI instance to get history from"
},
"limit": {
"name": "Limit",
"description": "Number of conversations to return (1-100)"
},
"filter_model": {
"name": "Filter Model",
"description": "Filter conversations by specific AI model"
},
"start_date": {
"name": "Start Date",
"description": "Filter conversations starting from this date/time"
},
"include_metadata": {
"name": "Include Metadata",
"description": "Include additional information like tokens used, response time, etc."
},
"sort_order": {
"name": "Sort Order",
"description": "Sort order for results (newest or oldest first)"
}
}
},
"set_system_prompt": {
"name": "Set System Prompt",
"description": "Set default system behavior instructions for all future conversations",
"fields": {
"instance": {
"name": "Instance",
"description": "Name of the HA Text AI instance to set system prompt for"
},
"prompt": {
"name": "System Prompt",
"description": "Instructions that define how the AI should behave and respond"
}
}
}
},
"entity": {
"sensor": {
"ha_text_ai": {
"name": "{name}",
"state": {
"ready": "Ready",
"processing": "Processing",
"error": "Error",
"disconnected": "Disconnected",
"rate_limited": "Rate Limited",
"maintenance": "Maintenance",
"initializing": "Initializing",
"retrying": "Retrying",
"queued": "Queued"
},
"state_attributes": {
"question": {
"name": "Last Question"
},
"response": {
"name": "Last Response"
},
"model": {
"name": "Current Model"
},
"temperature": {
"name": "Temperature"
},
"max_tokens": {
"name": "Max Tokens"
},
"system_prompt": {
"name": "System Prompt"
},
"response_time": {
"name": "Last Response Time"
},
"total_responses": {
"name": "Total Responses"
},
"error_count": {
"name": "Error Count"
},
"last_error": {
"name": "Last Error"
},
"api_status": {
"name": "API Status"
},
"tokens_used": {
"name": "Total Tokens Used"
},
"average_response_time": {
"name": "Average Response Time"
},
"last_request_time": {
"name": "Last Request Time"
},
"is_processing": {
"name": "Processing Status"
},
"is_rate_limited": {
"name": "Rate Limited Status"
},
"is_maintenance": {
"name": "Maintenance Status"
},
"api_version": {
"name": "API Version"
},
"endpoint_status": {
"name": "Endpoint Status"
},
"performance_metrics": {
"name": "Performance Metrics"
},
"history_size": {
"name": "History Size"
},
"uptime": {
"name": "Uptime"
},
"total_tokens": {
"name": "Total Tokens"
},
"prompt_tokens": {
"name": "Prompt Tokens"
},
"completion_tokens": {
"name": "Completion Tokens"
},
"successful_requests": {
"name": "Successful Requests"
},
"failed_requests": {
"name": "Failed Requests"
},
"average_latency": {
"name": "Average Latency"
},
"max_latency": {
"name": "Maximum Latency"
},
"min_latency": {
"name": "Minimum Latency"
}
}
}
}
}
} }
+261 -255
View File
@@ -1,260 +1,266 @@
{ {
"config": { "config": {
"step": { "step": {
"provider": { "provider": {
"title": "Выбор провайдера ИИ", "title": "Выбор поставщика ИИ",
"description": "Выберите сервис ИИ для этого экземпляра", "description": "Выберите поставщика услуг ИИ для этой инстанции.",
"data": { "data": {
"api_provider": ровайдер API", "api_provider": оставщик API",
"context_messages": "Количество сообщений в контексте (1-20)" "context_messages": "Количество контекстных сообщений для сохранения (1-20)"
}
},
"user": {
"title": "Настройка экземпляра HA Text AI",
"description": "Настройте нового помощника ИИ с выбранным провайдером",
"data": {
"name": "Название экземпляра (например, 'GPT Помощник', 'Claude Ассистент')",
"api_key": "API-ключ для аутентификации",
"model": "Модель ИИ для использования",
"temperature": "Креативность ответов (0-2, меньше = более сфокусированно)",
"max_tokens": "Максимальная длина ответа (1-4096 токенов)",
"api_endpoint": "Пользовательский URL-адрес API (необязательно)",
"request_interval": "Минимальный интервал между запросами (0.1-60 секунд)",
"context_messages": "Количество сохраняемых контекстных сообщений (1-20)"
}
}
},
"error": {
"name_exists": "Экземпляр с таким именем уже существует",
"invalid_name": "Некорректное имя экземпляра",
"invalid_auth": "Ошибка аутентификации - проверьте API-ключ",
"invalid_api_key": "Неверный API-ключ - проверьте учетные данные",
"cannot_connect": "Не удалось подключиться к сервису API",
"invalid_model": "Выбранная модель недоступна",
"rate_limit": "Превышен лимит запросов",
"context_length": "Превышена длина контекста",
"rate_limit_exceeded": "Превышен лимит API",
"maintenance": "Сервис на техническом обслуживании",
"invalid_response": "Получен некорректный ответ API",
"api_error": "Произошла ошибка сервиса API",
"timeout": "Время ожидания истекло",
"invalid_instance": "Указан неверный экземпляр",
"unknown": "Произошла непредвиденная ошибка"
} }
},
"user": {
"title": "Настройка инстанции HA Text AI",
"description": "Настройте новую инстанцию помощника ИИ с выбранным вами поставщиком.",
"data": {
"name": "Имя инстанции (например, 'GPT Assistant', 'Claude Helper')",
"api_key": "API-ключ для аутентификации",
"model": "Используемая модель ИИ",
"temperature": "Креативность ответа (0-2, чем ниже, тем больше фокусировки)",
"max_tokens": "Максимальная длина ответа (1-4096 токенов)",
"api_endpoint": "Пользовательский URL-адрес конечной точки API (необязательно)",
"api_provider": "Поставщик API",
"request_interval": "Минимальное время между запросами (0,1-60 секунд)",
"context_messages": "Количество контекстных сообщений для сохранения (1-20)",
"max_history_size": "Максимальный размер истории разговора (1-100)"
}
}
}, },
"options": { "error": {
"step": { "name_exists": "Инстанция с таким именем уже существует",
"init": { "invalid_name": "Некорректное имя инстанции",
"title": "Обновление настроек экземпляра", "invalid_auth": "Аутентификация не удалась - проверьте ваш API-ключ",
"description": "Измените настройки для этого помощника ИИ", "invalid_api_key": "Неверный API-ключ - проверьте ваши учетные данные",
"data": { "cannot_connect": "Не удалось подключиться к службе API",
"model": "Модель ИИ", "invalid_model": "Выбранная модель недоступна",
"temperature": "Креативность ответов (0-2)", "rate_limit": "Превышен лимит запросов",
"max_tokens": "Максимальная длина ответа (1-4096)", "context_length": "Превышена длина контекста",
"request_interval": "Минимальный интервал запросов (0.1-60 секунд)", "rate_limit_exceeded": "Превышен лимит запросов API",
"context_messages": "Количество предыдущих сообщений для включения в контекст (1-20)" "maintenance": "Сервис находится на техническом обслуживании",
} "invalid_response": "Получен неверный ответ API",
} "api_error": "Произошла ошибка службы API",
} "timeout": "Запрос превысил время ожидания",
}, "invalid_instance": "Указана неверная инстанция",
"services": { "unknown": "Произошла непредвиденная ошибка",
"ask_question": { "empty": "Имя не может быть пустым",
"name": "Задать вопрос (HA Text AI)", "invalid_characters": "Имя может содержать только буквы, цифры, пробелы, подчеркивания и дефисы",
"description": "Отправьте вопрос модели ИИ и получите подробный ответ. Ответ будет сохранен в истории беседы и может быть извлечен позже.", "name_too_long": "Имя должно быть не более 50 символов"
"fields": {
"instance": {
"name": "Экземпляр",
"description": "Название экземпляра HA Text AI для использования"
},
"question": {
"name": "Вопрос",
"description": "Ваш вопрос или запрос помощнику ИИ"
},
"context_messages": {
"name": "Контекстные сообщения",
"description": "Количество предыдущих сообщений для включения в контекст (1-20)"
},
"system_prompt": {
"name": "Системный промпт",
"description": "Необязательный системный промпт для установки контекста для этого конкретного вопроса"
},
"model": {
"name": "Модель",
"description": "Выберите модель ИИ (необязательно, переопределяет настройки по умолчанию)"
},
"temperature": {
"name": "Температура",
"description": "Управляет креативностью ответа (0.0-2.0)"
},
"max_tokens": {
"name": "Максимальное количество токенов",
"description": "Максимальная длина ответа (1-4096 токенов)"
}
}
},
"clear_history": {
"name": "Очистить историю",
"description": "Удалить все сохраненные вопросы и ответы из истории беседы",
"fields": {
"instance": {
"name": "Экземпляр",
"description": "Название экземпляра HA Text AI для очистки истории"
}
}
},
"get_history": {
"name": "Получить историю",
"description": "Получить историю беседы с дополнительной фильтрацией и сортировкой",
"fields": {
"instance": {
"name": "Экземпляр",
"description": "Название экземпляра HA Text AI для получения истории"
},
"limit": {
"name": "Лимит",
"description": "Количество возвращаемых бесед (1-100)"
},
"filter_model": {
"name": "Фильтр модели",
"description": "Фильтрация бесед по конкретной модели ИИ"
},
"start_date": {
"name": "Начальная дата",
"description": "Фильтрация бесед, начиная с указанной даты/времени"
},
"include_metadata": {
"name": "Включить метаданные",
"description": "Включить дополнительную информацию, например, использованные токены, время ответа и т.д."
},
"sort_order": {
"name": "Порядок сортировки",
"description": "Порядок результатов (сначала новые или старые)"
}
}
},
"set_system_prompt": {
"name": "Установить системный промпт",
"description": "Установить инструкции по умолчанию для поведения ИИ во всех будущих разговорах",
"fields": {
"instance": {
"name": "Экземпляр",
"description": "Название экземпляра HA Text AI для установки системного промпта"
},
"prompt": {
"name": "Системный промпт",
"description": "Инструкции, определяющие, как ИИ должен вести себя и отвечать"
}
}
}
},
"entity": {
"sensor": {
"ha_text_ai": {
"name": "{name}",
"state": {
"ready": "Готов",
"processing": "Обработка",
"error": "Ошибка",
"disconnected": "Отключен",
"rate_limited": "Лимит запросов",
"maintenance": "Техническое обслуживание",
"initializing": "Инициализация",
"retrying": "Повторная попытка",
"queued": "В очереди"
},
"state_attributes": {
"question": {
"name": "Последний вопрос"
},
"response": {
"name": "Последний ответ"
},
"model": {
"name": "Текущая модель"
},
"temperature": {
"name": "Температура"
},
"max_tokens": {
"name": "Максимальное количество токенов"
},
"system_prompt": {
"name": "Системный промпт"
},
"response_time": {
"name": "Время последнего ответа"
},
"total_responses": {
"name": "Всего ответов"
},
"error_count": {
"name": "Количество ошибок"
},
"last_error": {
"name": "Последняя ошибка"
},
"api_status": {
"name": "Статус API"
},
"tokens_used": {
"name": "Всего использовано токенов"
},
"average_response_time": {
"name": "Среднее время ответа"
},
"last_request_time": {
"name": "Время последнего запроса"
},
"is_processing": {
"name": "Статус обработки"
},
"is_rate_limited": {
"name": "Статус лимита запросов"
},
"is_maintenance": {
"name": "Статус обслуживания"
},
"api_version": {
"name": "Версия API"
},
"endpoint_status": {
"name": "Статус эндпоинта"
},
"performance_metrics": {
"name": "Показатели производительности"
},
"history_size": {
"name": "Размер истории"
},
"uptime": {
"name": "Время работы"
},
"total_tokens": {
"name": "Всего токенов"
},
"prompt_tokens": {
"name": "Токены промпта"
},
"completion_tokens": {
"name": "Токены завершения"
},
"successful_requests": {
"name": "Успешные запросы"
},
"failed_requests": {
"name": "Неудачные запросы"
},
"average_latency": {
"name": "Средняя задержка"
},
"max_latency": {
"name": "Максимальная задержка"
},
"min_latency": {
"name": "Минимальная задержка"
}
}
}
}
} }
},
"options": {
"step": {
"init": {
"title": "Обновление настроек инстанции",
"description": "Измените настройки для этой инстанции помощника ИИ.",
"data": {
"model": "Модель ИИ",
"temperature": "Креативность ответа (0-2)",
"max_tokens": "Максимальная длина ответа (1-4096)",
"request_interval": "Минимальный интервал между запросами (0,1-60 секунд)",
"context_messages": "Количество предыдущих сообщений для включения в контекст (1-20)",
"max_history_size": "Максимальный размер истории разговора (1-100)"
}
}
}
},
"services": {
"ask_question": {
"name": "Задать вопрос (HA Text AI)",
"description": "Отправьте вопрос модели ИИ и получите подробный ответ. Ответ будет сохранен в истории разговора и может быть извлечен позже.",
"fields": {
"instance": {
"name": "Инстанция",
"description": "Имя используемой инстанции HA Text AI"
},
"question": {
"name": "Вопрос",
"description": "Ваш вопрос или запрос к помощнику ИИ"
},
"context_messages": {
"name": "Контекстные сообщения",
"description": "Количество предыдущих сообщений для включения в контекст (1-20)"
},
"system_prompt": {
"name": "Системный запрос",
"description": "Необязательный системный запрос для установки контекста для этого конкретного вопроса"
},
"model": {
"name": "Модель",
"description": "Выберите модель ИИ для использования (необязательно, переопределяет настройку по умолчанию)"
},
"temperature": {
"name": "Температура",
"description": "Управляет креативностью ответа (0.0-2.0)"
},
"max_tokens": {
"name": "Макс. токенов",
"description": "Максимальная длина ответа (1-4096 токенов)"
}
}
},
"clear_history": {
"name": "Очистить историю",
"description": "Удалить все сохраненные вопросы и ответы из истории разговора",
"fields": {
"instance": {
"name": "Инстанция",
"description": "Имя инстанции HA Text AI, для которой нужно очистить историю"
}
}
},
"get_history": {
"name": "Получить историю",
"description": "Извлечь историю разговора с возможностью фильтрации и сортировки",
"fields": {
"instance": {
"name": "Инстанция",
"description": "Имя инстанции HA Text AI, из которой нужно получить историю"
},
"limit": {
"name": "Лимит",
"description": "Количество разговоров для возврата (1-100)"
},
"filter_model": {
"name": "Фильтр модели",
"description": "Фильтрация разговоров по определенной модели ИИ"
},
"start_date": {
"name": "Дата начала",
"description": "Фильтрация разговоров, начиная с этой даты/времени"
},
"include_metadata": {
"name": "Включить метаданные",
"description": "Включить дополнительную информацию, такую как использованные токены, время ответа и т.д."
},
"sort_order": {
"name": "Порядок сортировки",
"description": "Порядок сортировки результатов (самые новые или самые старые)"
}
}
},
"set_system_prompt": {
"name": "Установить системный запрос",
"description": "Установить инструкции по умолчанию для поведения системы для всех будущих разговоров",
"fields": {
"instance": {
"name": "Инстанция",
"description": "Имя инстанции HA Text AI, для которой нужно установить системный запрос"
},
"prompt": {
"name": "Системный запрос",
"description": "Инструкции, определяющие, как ИИ должен себя вести и отвечать"
}
}
}
},
"entity": {
"sensor": {
"ha_text_ai": {
"name": "{name}",
"state": {
"ready": "Готов",
"processing": "Обработка",
"error": "Ошибка",
"disconnected": "Отключен",
"rate_limited": "Лимит запросов",
"maintenance": "Техническое обслуживание",
"initializing": "Инициализация",
"retrying": "Повторная попытка",
"queued": "В очереди"
},
"state_attributes": {
"question": {
"name": "Последний вопрос"
},
"response": {
"name": "Последний ответ"
},
"model": {
"name": "Текущая модель"
},
"temperature": {
"name": "Температура"
},
"max_tokens": {
"name": "Макс. токенов"
},
"system_prompt": {
"name": "Системный запрос"
},
"response_time": {
"name": "Время последнего ответа"
},
"total_responses": {
"name": "Всего ответов"
},
"error_count": {
"name": "Количество ошибок"
},
"last_error": {
"name": "Последняя ошибка"
},
"api_status": {
"name": "Статус API"
},
"tokens_used": {
"name": "Использовано токенов всего"
},
"average_response_time": {
"name": "Среднее время ответа"
},
"last_request_time": {
"name": "Время последнего запроса"
},
"is_processing": {
"name": "Статус обработки"
},
"is_rate_limited": {
"name": "Статус ограничения запросов"
},
"is_maintenance": {
"name": "Статус технического обслуживания"
},
"api_version": {
"name": "Версия API"
},
"endpoint_status": {
"name": "Статус конечной точки"
},
"performance_metrics": {
"name": "Метрики производительности"
},
"history_size": {
"name": "Размер истории"
},
"uptime": {
"name": "Время работы"
},
"total_tokens": {
"name": "Всего токенов"
},
"prompt_tokens": {
"name": "Токены запроса"
},
"completion_tokens": {
"name": "Токены завершения"
},
"successful_requests": {
"name": "Успешных запросов"
},
"failed_requests": {
"name": "Неудачных запросов"
},
"average_latency": {
"name": "Средняя задержка"
},
"max_latency": {
"name": "Максимальная задержка"
},
"min_latency": {
"name": "Минимальная задержка"
}
}
}
}
}
} }