Compare commits

...
Author SHA1 Message Date
SMKRV d3ef31f551 Release v2.0.2-beta 2024-11-28 23:27:39 +03:00
SMKRV ff0c0369e8 YAML configuration explained 2024-11-27 18:03:30 +03:00
SMKRV b0dafe081b YAML configuration explained 2024-11-27 16:58:00 +03:00
SMKRV a88b5d01c1 Vesion updated 2024-11-27 16:52:49 +03:00
SMKRV e7d5e62671 YAML configuration explained 2024-11-27 16:43:20 +03:00
SMKRV 5f41b9489d YAML configuration explained 2024-11-27 16:34:55 +03:00
SMKRV 958e241e0e YAML configuration explained 2024-11-27 16:30:58 +03:00
smkrvandGitHub 4b3efc0b6f Update README_RU.md 2024-11-27 01:36:11 +03:00
smkrvandGitHub 1592fc2371 Delete socia_logo.png 2024-11-26 23:50:38 +03:00
smkrvandGitHub d5a6613428 Update README.md 2024-11-26 18:00:45 +03:00
SMKRV 9324473c9e Banner chaged 2024-11-26 17:49:08 +03:00
SMKRV 456e797cca Banner changed 2024-11-26 17:47:49 +03:00
SMKRV f960b9f9b4 Misc 2024-11-26 17:47:17 +03:00
SMKRV 8449b73423 Banner changed 2024-11-26 17:46:51 +03:00
SMKRV f19ba9aac9 Banner changed 2024-11-26 17:45:59 +03:00
SMKRV 524849f6a9 Logo 2024-11-26 17:44:13 +03:00
SMKRV 0a64a9abe0 Misc 2024-11-26 16:52:35 +03:00
SMKRV adb4127f5a Markdown changes 2024-11-26 16:38:56 +03:00
SMKRV f463593180 Markdown changes 2024-11-26 16:35:49 +03:00
SMKRV e53f257977 Markdown changes 2024-11-26 16:31:44 +03:00
SMKRV 87199e856a markdown changes 2024-11-26 16:27:50 +03:00
SMKRV bcf7cfbf76 Markdown changes 2024-11-26 16:25:22 +03:00
SMKRV 9289e1388f Markdown changes 2024-11-26 16:23:35 +03:00
SMKRV 84db9b7bb8 Markdown changes 2024-11-26 16:22:39 +03:00
SMKRV bd1098a181 Markdown changes 2024-11-26 16:22:27 +03:00
SMKRV 6fd3db0063 Markdown changes 2024-11-26 16:21:56 +03:00
SMKRV bf52217cd5 Markdown changes 2024-11-26 16:21:45 +03:00
SMKRV b0cf5b3c61 Markdown changes 2024-11-26 16:21:22 +03:00
SMKRV 347c1675ca Markdown changes 2024-11-26 16:20:07 +03:00
SMKRV 961ae2d34f Markdown changes 2024-11-26 16:19:45 +03:00
SMKRV 14827c3adc Markdown changes 2024-11-26 15:45:19 +03:00
SMKRV 4a88453abc Markdown changes 2024-11-26 15:44:47 +03:00
SMKRV bc33d38f5e Markdown changes 2024-11-26 15:42:52 +03:00
SMKRV 657a5ede86 Markdown changes 2024-11-26 15:41:46 +03:00
SMKRV dd046fe3e8 Markdown changes 2024-11-26 15:40:02 +03:00
SMKRV 7391dbf5b1 Markdown changes 2024-11-26 15:37:06 +03:00
SMKRV a0ccb86bd4 Markdown changes 2024-11-26 15:36:06 +03:00
SMKRV c0eb5b165d Vesrion changed 2024-11-26 15:31:34 +03:00
SMKRV 9812babc7e Русский перевод README 2024-11-26 15:31:06 +03:00
SMKRV c61c4570b0 Русский перевод README 2024-11-26 15:30:07 +03:00
SMKRV d618feffff docs(readme): Enhance attribute descriptions with detailed English comments
- Add comprehensive explanations for HA Text AI sensor attributes
- Improve readability of README.md documentation
- Provide context and usage details for each sensor attribute
- Translate comments to English with technical clarity

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

After

Width:  |  Height:  |  Size: 923 KiB