mirror of
https://github.com/smkrv/ha-text-ai.git
synced 2026-07-22 07:03:58 +08:00
Release v2.0.2-beta
This commit is contained in:
@@ -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
@@ -1,390 +0,0 @@
|
|||||||
# 🤖 HA Text AI для Home Assistant
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
  [](https://opensource.org/licenses/MIT) [](https://github.com/hacs/integration)   
|
|
||||||
|
|
||||||
<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>
|
|
||||||
@@ -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."""
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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": []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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": "Минимальная задержка"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user