Compare commits

..
8 Commits
Author SHA1 Message Date
SMKRV 73788373cd Release v2.1.3 2025-05-19 23:12:55 +03:00
SMKRV 4bfc96019b fix: DEFAULT_GEMINI_ENDPOINT 2025-05-19 15:53:43 +03:00
SMKRV 2138fc7654 fix: DEFAULT_GEMINI_ENDPOINT 2025-05-19 15:36:58 +03:00
SMKRV 95bd2ebb41 Add support for Google Gemini (thanks to @Azzedde) #5 2025-05-19 15:10:19 +03:00
smkrvandGitHub cad0fd7031 Merge pull request #5 from Azzedde/main
Add Gemini API provider support to HA Text AI integration by @Azzedde
2025-05-19 14:44:06 +03:00
Azzedde c003b258f6 Add Gemini API provider support to HA Text AI integration 2025-05-18 13:23:55 +02:00
SMKRV 65a10c77f4 ~ 2025-01-30 01:15:13 +03:00
SMKRV e1463828c9 ~ 2025-01-30 01:14:24 +03:00
16 changed files with 124 additions and 70 deletions
-41
View File
@@ -1,41 +0,0 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Home Assistant
.storage
.cloud
.google.token
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
*.psd
*.zip
*.txt
*.pdf
+6 -4
View File
@@ -106,12 +106,13 @@ Transform your smart home experience with powerful AI assistance powered by mult
## 📋 Prerequisites
- Home Assistant 2024.11 or later
- Home Assistant 2024.2.2 or later
- Active API key from:
- OpenAI ([Get key](https://platform.openai.com/account/api-keys))
- Anthropic ([Get key](https://console.anthropic.com/))
- DeepSeek 🆕 ([Get key](https://platform.deepseek.com/api_keys))
- DeepSeek ([Get key](https://platform.deepseek.com/api_keys))
- OpenRouter ([Get key](https://openrouter.ai/keys))
- Google Gemini 🆕 ([Get key](https://ai.google.dev/gemini-api/docs/api-key)) thanks to ([@Azzedde](https://github.com/Azzedde))
- Any OpenAI-compatible API provider
- Python 3.9 or newer
- Stable internet connection
@@ -159,6 +160,9 @@ To be compatible, a provider should support:
## ⚡ Installation
### HACS Installation (Recommended)
>[!TIP]
>HA Text AI is available in the default HACS repository. You can install it directly through HACS or click the button below to open it there.
<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. Open HACS in Home Assistant
2. Click on "Integrations"
@@ -169,8 +173,6 @@ To be compatible, a provider should support:
7. Click "Download"
8. Restart Home Assistant
Note: Also Integration has been submitted to HACS store and is currently pending review in [pull request #2896](https://github.com/hacs/default/pull/2896).
### Manual Installation
1. Download the latest release
2. Extract and copy `custom_components/ha_text_ai` to your `custom_components` directory
+3
View File
@@ -40,13 +40,16 @@ from .const import (
API_PROVIDER_OPENAI,
API_PROVIDER_ANTHROPIC,
API_PROVIDER_DEEPSEEK,
API_PROVIDER_GEMINI,
DEFAULT_MODEL,
DEFAULT_DEEPSEEK_MODEL,
DEFAULT_GEMINI_MODEL,
DEFAULT_TEMPERATURE,
DEFAULT_MAX_TOKENS,
DEFAULT_OPENAI_ENDPOINT,
DEFAULT_ANTHROPIC_ENDPOINT,
DEFAULT_DEEPSEEK_ENDPOINT,
DEFAULT_GEMINI_ENDPOINT,
DEFAULT_REQUEST_INTERVAL,
DEFAULT_CONTEXT_MESSAGES,
API_TIMEOUT,
@@ -20,6 +20,7 @@ from .const import (
API_PROVIDER_ANTHROPIC,
API_PROVIDER_DEEPSEEK,
API_PROVIDER_OPENAI,
API_PROVIDER_GEMINI,
MIN_TEMPERATURE,
MAX_TEMPERATURE,
MIN_MAX_TOKENS,
@@ -115,6 +116,10 @@ class APIClient:
return await self._create_deepseek_completion(
model, messages, temperature, max_tokens
)
elif self.api_provider == API_PROVIDER_GEMINI:
return await self._create_gemini_completion(
model, messages, temperature, max_tokens
)
else:
return await self._create_openai_completion(
model, messages, temperature, max_tokens
@@ -238,6 +243,57 @@ class APIClient:
_LOGGER.error(f"Connection check failed: {str(e)}")
return False
async def _create_gemini_completion(
self,
model: str,
messages: List[Dict[str, str]],
temperature: float,
max_tokens: int,
) -> Dict[str, Any]:
"""Create completion using Gemini API."""
# Extract API key from headers (Bearer token)
api_key = self.headers.get("Authorization", "").replace("Bearer ", "")
url = f"{self.endpoint}/models/{model}:generateContent?key={api_key}"
# Convert messages to Gemini format
contents = []
system_instruction = ""
for msg in messages:
if msg['role'] == 'system':
system_instruction += msg['content'] + "\n"
else:
contents.append({
"role": "user" if msg['role'] == 'user' else "model",
"parts": [{"text": msg['content']}]
})
payload = {
"contents": contents,
"generationConfig": {
"temperature": temperature,
"maxOutputTokens": max_tokens
}
}
if system_instruction:
payload["systemInstruction"] = {
"parts": [{"text": system_instruction}]
}
data = await self._make_request(url, payload)
return {
"choices": [{
"message": {
"content": data["candidates"][0]["content"]["parts"][0]["text"]
}
}],
"usage": {
"prompt_tokens": data["usageMetadata"]["promptTokenCount"],
"completion_tokens": data["usageMetadata"]["candidatesTokenCount"],
"total_tokens": data["usageMetadata"]["totalTokenCount"]
}
}
async def shutdown(self) -> None:
"""Shutdown API client."""
_LOGGER.debug("Shutting down API client")
+23 -4
View File
@@ -29,15 +29,18 @@ from .const import (
API_PROVIDER_OPENAI,
API_PROVIDER_ANTHROPIC,
API_PROVIDER_DEEPSEEK,
API_PROVIDER_GEMINI,
API_PROVIDERS,
DEFAULT_MODEL,
DEFAULT_DEEPSEEK_MODEL,
DEFAULT_GEMINI_MODEL,
DEFAULT_TEMPERATURE,
DEFAULT_MAX_TOKENS,
DEFAULT_REQUEST_INTERVAL,
DEFAULT_OPENAI_ENDPOINT,
DEFAULT_ANTHROPIC_ENDPOINT,
DEFAULT_DEEPSEEK_ENDPOINT,
DEFAULT_GEMINI_ENDPOINT,
DEFAULT_CONTEXT_MESSAGES,
MIN_TEMPERATURE,
MAX_TEMPERATURE,
@@ -93,15 +96,20 @@ class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self._errors = {}
if user_input is None:
# Выбор endpoint по провайдеру
# Selecting an endpoint by provider
default_endpoint = {
API_PROVIDER_OPENAI: DEFAULT_OPENAI_ENDPOINT,
API_PROVIDER_ANTHROPIC: DEFAULT_ANTHROPIC_ENDPOINT,
API_PROVIDER_DEEPSEEK: DEFAULT_DEEPSEEK_ENDPOINT,
API_PROVIDER_GEMINI: DEFAULT_GEMINI_ENDPOINT,
}.get(self._provider, DEFAULT_OPENAI_ENDPOINT)
# Выбор модели по умолчанию по провайдеру
default_model = DEFAULT_DEEPSEEK_MODEL if self._provider == API_PROVIDER_DEEPSEEK else DEFAULT_MODEL
# Selecting the default model by provider
default_model = (
DEFAULT_DEEPSEEK_MODEL if self._provider == API_PROVIDER_DEEPSEEK else
DEFAULT_GEMINI_MODEL if self._provider == API_PROVIDER_GEMINI else
DEFAULT_MODEL
)
return self.async_show_form(
step_id="provider",
@@ -256,7 +264,11 @@ class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
unique_id = f"{DOMAIN}_{normalized_name}_{self._provider}".lower()
default_model = DEFAULT_DEEPSEEK_MODEL if self._provider == API_PROVIDER_DEEPSEEK else DEFAULT_MODEL
default_model = (
DEFAULT_DEEPSEEK_MODEL if self._provider == API_PROVIDER_DEEPSEEK else
DEFAULT_GEMINI_MODEL if self._provider == API_PROVIDER_GEMINI else
DEFAULT_MODEL
)
entry_data = {
CONF_API_PROVIDER: self._provider,
@@ -304,6 +316,13 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
return self.async_create_entry(title="", data=user_input)
current_data = {**self.config_entry.data, **self.config_entry.options}
provider = current_data.get(CONF_API_PROVIDER)
default_model = (
DEFAULT_DEEPSEEK_MODEL if provider == API_PROVIDER_DEEPSEEK else
DEFAULT_GEMINI_MODEL if provider == API_PROVIDER_GEMINI else
DEFAULT_MODEL
)
return self.async_show_form(
step_id="init",
+7 -1
View File
@@ -12,6 +12,8 @@ from typing import Final
import voluptuous as vol
from homeassistant.const import Platform, CONF_API_KEY, CONF_NAME
from homeassistant.helpers import config_validation as cv
import logging
_LOGGER = logging.getLogger(__name__)
# Domain and platforms
DOMAIN: Final = "ha_text_ai"
@@ -22,11 +24,13 @@ CONF_API_PROVIDER: Final = "api_provider"
API_PROVIDER_OPENAI: Final = "openai"
API_PROVIDER_ANTHROPIC: Final = "anthropic"
API_PROVIDER_DEEPSEEK: Final = "deepseek"
API_PROVIDER_GEMINI: Final = "gemini"
API_PROVIDERS: Final = [
API_PROVIDER_OPENAI,
API_PROVIDER_ANTHROPIC,
API_PROVIDER_DEEPSEEK
API_PROVIDER_DEEPSEEK,
API_PROVIDER_GEMINI
]
# Read version from manifest.json
@@ -49,6 +53,7 @@ except Exception as err:
DEFAULT_OPENAI_ENDPOINT: Final = "https://api.openai.com/v1"
DEFAULT_ANTHROPIC_ENDPOINT: Final = "https://api.anthropic.com"
DEFAULT_DEEPSEEK_ENDPOINT: Final = "https://api.deepseek.com"
DEFAULT_GEMINI_ENDPOINT: Final = "https://generativelanguage.googleapis.com/v1beta"
# Configuration constants
CONF_MODEL: Final = "model"
@@ -69,6 +74,7 @@ ICONS_SUBDOMAIN = "icons"
# Default values
DEFAULT_MODEL: Final = "gpt-4o-mini"
DEFAULT_DEEPSEEK_MODEL: Final = "deepseek-chat"
DEFAULT_GEMINI_MODEL: Final = "gemini-pro"
DEFAULT_TEMPERATURE: Final = 0.1
DEFAULT_MAX_TOKENS: Final = 1000
DEFAULT_REQUEST_INTERVAL: Final = 1.0
+2 -1
View File
@@ -16,6 +16,7 @@
"requirements": [
"openai>=1.12.0",
"anthropic>=0.8.0",
"google-generativeai>=0.3.0",
"aiohttp>=3.8.0",
"async-timeout>=4.0.0",
"certifi>=2024.2.2"
@@ -23,6 +24,6 @@
"single_config_entry": false,
"ssdp": [],
"usb": [],
"version": "2.1.1",
"version": "2.1.3",
"zeroconf": []
}
@@ -88,12 +88,13 @@
"selector": {
"api_provider": {
"options": {
"openai": "OpenAI (kompatibel)",
"anthropic": "Anthropic (kompatibel)",
"deepseek": "DeepSeek"
}
"openai": "OpenAI (compatible)",
"anthropic": "Anthropic (compatible)",
"deepseek": "DeepSeek",
"gemini": "Google Gemini"
}
},
}
},
"services": {
"ask_question": {
"name": "Frage stellen (HA Text AI)",
@@ -86,11 +86,12 @@
}
},
"selector": {
"api_provider": {
"options": {
"openai": "OpenAI (compatible)",
"anthropic": "Anthropic (compatible)",
"deepseek": "DeepSeek"
"api_provider": {
"options": {
"openai": "OpenAI (compatible)",
"anthropic": "Anthropic (compatible)",
"deepseek": "DeepSeek",
"gemini": "Google Gemini"
}
}
},
@@ -90,10 +90,11 @@
"options": {
"openai": "OpenAI (compatible)",
"anthropic": "Anthropic (compatible)",
"deepseek": "DeepSeek"
}
"deepseek": "DeepSeek",
"gemini": "Google Gemini"
}
},
}
},
"services": {
"ask_question": {
"name": "Hacer Pregunta (HA Text AI)",
@@ -90,7 +90,8 @@
"options": {
"openai": "OpenAI (अनुकूलित)",
"anthropic": "Anthropic (अनुकूलित)",
"deepseek": "DeepSeek"
"deepseek": "DeepSeek",
"gemini": "Google Gemini"
}
}
},
@@ -90,7 +90,8 @@
"options": {
"openai": "OpenAI (compatibile)",
"anthropic": "Anthropic (compatibile)",
"deepseek": "DeepSeek"
"deepseek": "DeepSeek",
"gemini": "Google Gemini"
}
}
},
@@ -90,7 +90,8 @@
"options": {
"openai": "OpenAI (совместимый)",
"anthropic": "Anthropic (совместимый)",
"deepseek": "DeepSeek"
"deepseek": "DeepSeek",
"gemini": "Google Gemini"
}
}
},
@@ -90,7 +90,8 @@
"options": {
"openai": "OpenAI (компатибилан)",
"anthropic": "Anthropic (компатибилан)",
"deepseek": "DeepSeek"
"deepseek": "DeepSeek",
"gemini": "Google Gemini"
}
}
},
@@ -90,7 +90,8 @@
"options": {
"openai": "OpenAI(兼容)",
"anthropic": "Anthropic(兼容)",
"deepseek": "DeepSeek "
"deepseek": "DeepSeek",
"gemini": "Google Gemini"
}
}
},
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "HA text AI",
"name": "HA Text AI",
"render_readme": true,
"homeassistant": "2024.11.0"
}