Compare commits

...
7 Commits
Author SHA1 Message Date
SMKRV 93558b2444 Version update 2024-11-19 17:34:27 +03:00
SMKRV 9f93f1ee18 Main changes:
Removed the validate_endpoint function
Optimized the validate_api_connection function
Simplified API connection verification
Preserved all error handling and retry logic
Improved exception handling
The integration should now correctly verify the
OpenAI API connection without false endpoint_not_available errors.
2024-11-19 17:33:27 +03:00
SMKRV 30a9b53ba1 Markdown changes 2024-11-19 17:18:23 +03:00
SMKRV 5175970d55 structure.md added 2024-11-19 17:16:30 +03:00
SMKRV f6bfbd4a07 Release v1.0.8 2024-11-19 17:02:38 +03:00
SMKRV 4ddb0dc977 Translation fixes 2024-11-19 16:59:42 +03:00
SMKRV 24dc4ac4d4 Hotfix 2024-11-19 16:53:27 +03:00
8 changed files with 152 additions and 191 deletions
+44 -70
View File
@@ -62,24 +62,6 @@ STEP_USER_DATA_SCHEMA = vol.Schema({
), ),
}) })
async def validate_endpoint(endpoint: str) -> Tuple[bool, str]:
"""Validate API endpoint accessibility."""
try:
parsed_url = urlparse(endpoint)
if parsed_url.scheme not in ('http', 'https'):
return False, "invalid_endpoint_scheme"
connector = aiohttp.TCPConnector(ssl=SSL_CONTEXT)
async with timeout(5):
async with aiohttp.ClientSession(connector=connector) as session:
async with session.get(endpoint) as response:
if response.status != 200:
return False, "endpoint_not_available"
return True, ""
except Exception as e:
_LOGGER.error("Error validating endpoint: %s", str(e))
return False, "endpoint_error"
async def validate_api_connection( async def validate_api_connection(
api_key: str, api_key: str,
endpoint: str, endpoint: str,
@@ -87,64 +69,56 @@ async def validate_api_connection(
retry_count: int = 3, retry_count: int = 3,
retry_delay: float = 1.0 retry_delay: float = 1.0
) -> Tuple[bool, str, list]: ) -> Tuple[bool, str, list]:
"""Validate API connection with improved retry logic.""" """Validate API connection with retry logic."""
# Validate endpoint first for attempt in range(retry_count):
endpoint_valid, endpoint_error = await validate_endpoint(endpoint) try:
if not endpoint_valid: async with timeout(10):
return False, endpoint_error, [] client = AsyncOpenAI(
api_key=api_key,
connector = aiohttp.TCPConnector(ssl=SSL_CONTEXT) base_url=endpoint,
async with aiohttp.ClientSession(connector=connector) as session:
for attempt in range(retry_count):
try:
async with timeout(10):
client = AsyncOpenAI(
api_key=api_key,
base_url=endpoint,
http_client=session
)
models = await client.models.list()
model_ids = [model.id for model in models.data]
if model not in model_ids:
_LOGGER.warning(
"Model %s not found in available models: %s",
model,
", ".join(model_ids)
)
return False, "invalid_model", model_ids
return True, "", model_ids
except asyncio.TimeoutError:
_LOGGER.warning(
"Timeout during API validation (attempt %d/%d)",
attempt + 1,
retry_count
) )
if attempt == retry_count - 1:
return False, "timeout", []
await asyncio.sleep(retry_delay)
except AuthenticationError as err: models = await client.models.list()
_LOGGER.error("Authentication error: %s", str(err)) model_ids = [model.id for model in models.data]
return False, "invalid_auth", []
except RateLimitError as err: if model not in model_ids:
_LOGGER.error("Rate limit exceeded: %s", str(err)) _LOGGER.warning(
return False, "rate_limit", [] "Model %s not found in available models: %s",
model,
", ".join(model_ids)
)
return False, "invalid_model", model_ids
return True, "", model_ids
except APIConnectionError as err: except asyncio.TimeoutError:
_LOGGER.error("API connection error: %s", str(err)) _LOGGER.warning(
return False, "cannot_connect", [] "Timeout during API validation (attempt %d/%d)",
attempt + 1,
retry_count
)
if attempt == retry_count - 1:
return False, "timeout", []
await asyncio.sleep(retry_delay)
except APIError as err: except AuthenticationError as err:
_LOGGER.error("API error: %s", str(err)) _LOGGER.error("Authentication error: %s", str(err))
return False, "api_error", [] return False, "invalid_auth", []
except Exception as err: except RateLimitError as err:
_LOGGER.exception("Unexpected error during validation: %s", str(err)) _LOGGER.error("Rate limit exceeded: %s", str(err))
return False, "unknown", [] return False, "rate_limit", []
except APIConnectionError as err:
_LOGGER.error("API connection error: %s", str(err))
return False, "cannot_connect", []
except APIError as err:
_LOGGER.error("API error: %s", str(err))
return False, "api_error", []
except Exception as err:
_LOGGER.exception("Unexpected error during validation: %s", str(err))
return False, "unknown", []
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."""
+1 -1
View File
@@ -9,6 +9,6 @@
"issue_tracker": "https://github.com/smkrv/ha-text-ai/issues", "issue_tracker": "https://github.com/smkrv/ha-text-ai/issues",
"requirements": ["openai>=1.0.0"], "requirements": ["openai>=1.0.0"],
"ssdp": [], "ssdp": [],
"version": "1.0.7", "version": "1.0.9",
"zeroconf": [] "zeroconf": []
} }
@@ -131,11 +131,6 @@ get_history:
mode: dropdown mode: dropdown
set_system_prompt: set_system_prompt:
name: Set System Prompt
description: >-
Configure the AI's behavior by setting a system prompt.
This affects how the AI interprets and responds to all future questions.
The prompt will persist until changed or cleared.
fields: fields:
prompt: prompt:
name: System Prompt name: System Prompt
@@ -157,14 +152,3 @@ set_system_prompt:
text: text:
multiline: true multiline: true
type: text type: text
max_length: 1000
clear_prompt:
name: Clear Existing Prompt
description: >-
Set to true to remove the current system prompt before applying the new one.
This ensures no conflicting instructions remain.
required: false
default: false
selector:
boolean: {}
@@ -1,61 +1,54 @@
{ {
"config": { "config": {
"option": { "step": {
"api_key": { "user": {
"name": "API Key", "title": "Set up HA Text AI",
"description": "Your OpenAI API key" "description": "Configure your OpenAI integration",
}, "data": {
"model": { "api_key": "Your OpenAI API key",
"name": "Model", "model": "AI model to use for responses",
"description": "AI model to use for responses" "temperature": "Temperature for response generation (0-2)",
}, "max_tokens": "Maximum tokens in response (1-4096)",
"temperature": { "api_endpoint": "API endpoint URL",
"name": "Temperature", "request_interval": "Minimum time between API requests (seconds)"
"description": "Temperature for response generation (0-2)" }
},
"max_tokens": {
"name": "Max Tokens",
"description": "Maximum tokens in response (1-4096)"
},
"api_endpoint": {
"name": "API Endpoint",
"description": "API endpoint URL"
},
"request_interval": {
"name": "Request Interval",
"description": "Minimum time between API requests (seconds)"
} }
} }
}, },
"options": { "options": {
"temperature": "Temperature", "step": {
"max_tokens": "Max Tokens", "init": {
"request_interval": "Request Interval" "title": "HA Text AI Options",
}, "data": {
"error": { "temperature": "Response temperature (0-2)",
"invalid_api_key": "Invalid API key", "max_tokens": "Maximum response length",
"cannot_connect": "Cannot connect to the API", "request_interval": "Time between requests"
"unknown_error": "Unknown error", }
"invalid_model": "Invalid model", }
"rate_limit_exceeded": "Rate limit exceeded", }
"context_length_exceeded": "Context length exceeded",
"api_error": "API error",
"timeout_error": "Timeout error",
"queue_full": "Queue full",
"invalid_prompt": "Invalid prompt"
},
"state": {
"ready": "Ready",
"processing": "Processing",
"error": "Error",
"disconnected": "Disconnected",
"rate_limited": "Rate limited",
"initializing": "Initializing"
}, },
"services": { "services": {
"ask_question": "Ask Question", "ask_question": {
"clear_history": "Clear History", "name": "Ask Question",
"get_history": "Get History", "description": "Send a question to the AI model",
"set_system_prompt": "Set System Prompt" "fields": {
"question": {
"name": "Question",
"description": "Your question for the AI"
}
}
},
"clear_history": {
"name": "Clear History",
"description": "Clear conversation history"
},
"get_history": {
"name": "Get History",
"description": "Retrieve conversation history"
},
"set_system_prompt": {
"name": "Set System Prompt",
"description": "Set system behavior instructions"
}
} }
} }
@@ -1,61 +1,54 @@
{ {
"config": { "config": {
"option": { "step": {
"api_key": { "user": {
"name": "API ключ", "title": "Настройка HA Text AI",
"description": "Ваш API ключ OpenAI" "description": "Настройка интеграции с OpenAI",
}, "data": {
"model": { "api_key": "Ваш ключ API OpenAI",
"name": "Модель", "model": "Модель ИИ для генерации ответов",
"description": "Модель AI для генерации ответов" "temperature": "Температура генерации ответов (0-2)",
}, "max_tokens": "Максимальное количество токенов в ответе (1-4096)",
"temperature": { "api_endpoint": "URL конечной точки API",
"name": "Температура", "request_interval": "Минимальное время между запросами к API (секунды)"
"description": "Температура для генерации ответов (0-2)" }
},
"max_tokens": {
"name": "Максимальное количество токенов",
"description": "Максимальное количество токенов в ответе (1-4096)"
},
"api_endpoint": {
"name": "Конечная точка API",
"description": "URL конечной точки API"
},
"request_interval": {
"name": "Интервал запросов",
"description": "Минимальное время между запросами к API в секундах"
} }
} }
}, },
"options": { "options": {
"temperature": "Температура", "step": {
"max_tokens": "Максимальное количество токенов", "init": {
"request_interval": "Интервал запросов" "title": "Настройки HA Text AI",
}, "data": {
"error": { "temperature": "Температура ответов (0-2)",
"invalid_api_key": "Неверный API ключ", "max_tokens": "Максимальная длина ответа",
"cannot_connect": "Не удается подключиться к API", "request_interval": "Время между запросами"
"unknown_error": "Неизвестная ошибка", }
"invalid_model": "Неверная модель", }
"rate_limit_exceeded": "Превышен лимит запросов", }
"context_length_exceeded": "Превышена длина контекста",
"api_error": "Ошибка API",
"timeout_error": "Время ожидания истекло",
"queue_full": "Очередь полна",
"invalid_prompt": "Неверный запрос"
},
"state": {
"ready": "Готово",
"processing": "Обработка",
"error": "Ошибка",
"disconnected": "Отключено",
"rate_limited": "Ограниченный по скорости",
"initializing": "Инициализация"
}, },
"services": { "services": {
"ask_question": "Задать вопрос", "ask_question": {
"clear_history": "Очистить историю", "name": "Задать вопрос",
"get_history": "Получить историю", "description": "Отправить вопрос модели ИИ",
"set_system_prompt": "Установить системный запрос" "fields": {
"question": {
"name": "Вопрос",
"description": "Ваш вопрос для ИИ"
}
}
},
"clear_history": {
"name": "Очистить историю",
"description": "Очистить историю разговора"
},
"get_history": {
"name": "Получить историю",
"description": "Получить историю разговора"
},
"set_system_prompt": {
"name": "Установить системный промпт",
"description": "Установить инструкции поведения системы"
}
} }
} }
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -4,6 +4,6 @@
"domains": ["sensor"], "domains": ["sensor"],
"homeassistant": "2024.11.0", "homeassistant": "2024.11.0",
"icon": "mdi:brain", "icon": "mdi:brain",
"version": "1.0.7", "version": "1.0.9",
"documentation": "https://github.com/smkrv/ha-text-ai" "documentation": "https://github.com/smkrv/ha-text-ai"
} }
+17
View File
@@ -0,0 +1,17 @@
```
ha-text-ai/
├── custom_components/
│ └── ha_text_ai/
│ ├── __init__.py
│ ├── config_flow.py
│ ├── coordinator.py
│ ├── manifest.json
│ ├── sensor.py
│ ├── services.yaml
│ └── const.py
└── strings/
├── en.json
└── ru.json
```