Compare commits

...
25 Commits
Author SHA1 Message Date
SMKRV c13ef1921d feat(performance): Optimize system resources and token estimation
- Improve JSON history file processing
- Add memory and disk space validation
- Enhance parallel request handling
- Refine token counting heuristics
2024-12-06 02:51:06 +03:00
SMKRV 0fb9acfa8f docs:(README) 2024-12-05 02:00:24 +03:00
SMKRV 2ac4389e1a docs:(README) 2024-12-05 01:55:31 +03:00
SMKRV de194e425c docs:(README) 2024-12-05 01:54:09 +03:00
SMKRV 95f3d6506e docs:(README) 2024-12-05 01:49:26 +03:00
SMKRV 3e877243b9 docs:(README) 2024-12-05 01:46:28 +03:00
SMKRV fd795c9e90 docs:(readme) 2024-12-05 00:36:08 +03:00
SMKRV 5f5dc041b9 docs: structure 2024-12-04 23:32:05 +03:00
SMKRV 3d3885d43d Update README.md 2024-12-04 23:28:14 +03:00
smkrvandGitHub b059744716 Update README.md 2024-12-04 23:23:46 +03:00
SMKRV f2a41aaa2c docs: logo 2024-12-04 20:13:08 +03:00
SMKRV 1fcf751d0d docs: logo 2024-12-04 19:24:30 +03:00
SMKRV a45c9407fd docs: logo 2024-12-04 19:19:40 +03:00
SMKRV 21fdf108cf docs: logo 2024-12-04 19:17:09 +03:00
SMKRV c7ec2b3ea4 docs: logo 2024-12-04 19:13:57 +03:00
SMKRV 6c4da6cea5 docs: logo 2024-12-04 19:12:44 +03:00
SMKRV 3029c5dd26 docs: logo 2024-12-04 19:12:21 +03:00
SMKRV 3e97028094 docs: logo 2024-12-04 19:11:54 +03:00
SMKRV 1194c87134 docs: logo 2024-12-04 17:51:40 +03:00
SMKRV e03b5315c2 logo update 2024-12-04 17:51:02 +03:00
SMKRV fad887492f docs: logo 2024-12-04 17:48:04 +03:00
SMKRV f26df29937 docs: logo 2024-12-04 17:47:12 +03:00
SMKRV 69b54a08ba logo update 2024-12-04 17:42:32 +03:00
SMKRV 7ce77eb18e docs: logo 2024-12-04 17:36:18 +03:00
SMKRV 1480669dc8 icons update 2024-12-04 17:34:19 +03:00
16 changed files with 807 additions and 424 deletions
+90 -56
View File
@@ -4,7 +4,7 @@
![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: CC BY-NC-SA 4.0](https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg?style=flat-square)](https://creativecommons.org/licenses/by-nc-sa/4.0/) [![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) ![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: CC BY-NC-SA 4.0](https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg?style=flat-square)](https://creativecommons.org/licenses/by-nc-sa/4.0/) [![hacs_badge](https://img.shields.io/badge/HACS-Custom-41BDF5.svg?style=flat-square)](https://github.com/hacs/integration) ![/README.md](https://img.shields.io/badge/language-English-green?style=flat-square) ![/README_RU.md](https://img.shields.io/badge/language-Russian-green?style=flat-square) ![](https://img.shields.io/badge/language-Deutch-green?style=flat-square)
<img src="https://github.com/smkrv/ha-text-ai/blob/524849f6a945ec62c2cf6a6b7ecd9a28b37bf0fa/misc/icons/logo.jpg" alt="HA Text AI" height="160"/> <img src="https://github.com/smkrv/ha-text-ai/blob/e03b5315c2cc7b489a6e1175da728075275ed614/misc/icons/logo.png" alt="HA Text AI" style="width: 80%; max-width: 640px; max-height: 150px; aspect-ratio: 2/1; object-fit: contain;"/>
### Advanced AI Integration for [Home Assistant](https://www.home-assistant.io/) with LLM multi-provider support ### Advanced AI Integration for [Home Assistant](https://www.home-assistant.io/) with LLM multi-provider support
</div> </div>
@@ -27,53 +27,67 @@ Transform your smart home experience with powerful AI assistance powered by mult
## 🌟 Features ## 🌟 Features
- 🧠 **Multi-Provider AI Integration**: - 🧠 **Multi-Provider AI Integration**: Support for OpenAI GPT and Anthropic Claude models
- Support for OpenAI GPT models - 💬 **Advanced Language Processing**: Context-aware, multi-turn conversations
- Anthropic Claude integration - 📝 **Enhanced Memory Management**: Secure file-based history storage
- Custom API endpoints -**Performance Optimization**: Efficient token usage and smart rate limiting
- Flexible model selection - 🎯 **Advanced Customization**: Per-request model and parameter selection
- 🔒 **Enhanced Security**: Secure API key management and usage monitoring
- 🎨 **Improved User Experience**: Intuitive configuration and rich interfaces
- 🔄 **Automation Integration**: Event-driven responses and template compatibility
- 💬 **Advanced Language Processing**: <details>
- Context-aware responses <summary>📦 Detailed Feature Breakdown</summary>
- Multi-turn conversations
- Custom system instructions
- Natural conversation flow
- 📝 **Enhanced Memory Management**: ### 🧠 **Multi-Provider AI Integration**
- File-based conversation history storage - Support for OpenAI GPT models
- Automatic history rotation - Anthropic Claude integration
- Configurable history size limits - Custom API endpoints
- Secure storage in Home Assistant - Flexible model selection
-**Performance Optimization**: ### 💬 **Advanced Language Processing**
- Efficient token usage - Context-aware responses
- Smart rate limiting - Multi-turn conversations
- Response caching - Custom system instructions
- Request interval control - Natural conversation flow
- 🎯 **Advanced Customization**: ### 📝 **Enhanced Memory Management**
- Per-request model selection - File-based conversation history storage
- Adjustable parameters - Automatic history rotation
- Custom system prompts - Configurable history size limits
- Temperature control - Secure storage in Home Assistant
- 🔒 **Enhanced Security**: ### ⚡ **Performance Optimization**
- Secure API key storage - Efficient token usage
- Rate limiting protection - Smart rate limiting
- Error handling - Response caching
- Usage monitoring - Request interval control
- 🎨 **Improved User Experience**: ### 🎯 **Advanced Customization**
- Intuitive configuration UI - Per-request model selection
- Detailed sensor attributes - Adjustable parameters
- Rich service interface - Custom system prompts
- Model selection UI - Temperature control
- 🔄 **Automation Integration**: ### 🔒 **Enhanced Security**
- Event-driven responses - Secure API key storage
- Conditional logic support - Rate limiting protection
- Template compatibility - Error handling
- Model-specific automation - Usage monitoring
### 🎨 **Improved User Experience**
- Intuitive configuration UI
- Detailed sensor attributes
- Rich service interface
- Model selection UI
### 🔄 **Automation Integration**
- Event-driven responses
- Conditional logic support
- Template compatibility
- Model-specific automation
</details>
## 📋 Prerequisites ## 📋 Prerequisites
@@ -85,16 +99,22 @@ Transform your smart home experience with powerful AI assistance powered by mult
- Python 3.9 or newer - Python 3.9 or newer
- Stable internet connection - Stable internet connection
### Configuration Options ## Configuration Options
- API Provider (OpenAI/Anthropic)
- API Key (provider-specific)
- Model Selection (flexible, provider-specific models)
- Temperature (Creativity control, 0.0-2.0)
- Max Tokens (Response length limit)
- Request Interval (API call throttling)
- Custom API Endpoint (optional)
#### ⓘ Potentially Compatible Providers ### 🔧 **Core Configuration Settings**
- 🌐 **API Provider**: OpenAI/Anthropic
- 🔑 **API Key**: Provider-specific authentication
- 🤖 **Model Selection**: Flexible, provider-specific models
- 🌡️ **Temperature**: Creativity control (0.0-2.0)
- 📏 **Max Tokens**: Response length limit
- ⏱️ **Request Interval**: API call throttling
- 💾 **History Size**: Number of messages to retain
- 🌍 **Custom API Endpoint**: Optional advanced configuration
<details>
<summary>🌐 Potentially Compatible Providers</summary>
#### Flexible Provider Ecosystem
The integration is designed to be flexible and may work with other providers offering OpenAI-compatible APIs: The integration is designed to be flexible and may work with other providers offering OpenAI-compatible APIs:
- Groq - Groq
- Together AI - Together AI
@@ -104,18 +124,19 @@ The integration is designed to be flexible and may work with other providers off
- Local AI servers (like Ollama) - Local AI servers (like Ollama)
- Custom OpenAI-compatible endpoints - Custom OpenAI-compatible endpoints
#### Additional Notes #### 🚨 Compatibility Notes
- Not all providers guarantee full compatibility - Not all providers guarantee full compatibility
- Performance may vary between providers - Performance may vary between providers
- Check individual provider's documentation - Check individual provider's documentation
- Ensure your API key has sufficient credits/quota - Ensure your API key has sufficient credits/quota
#### Provider Compatibility Requirements #### 🔍 Provider Compatibility Requirements
To be compatible, a provider should support: To be compatible, a provider should support:
- OpenAI-like REST API structure - OpenAI-like REST API structure
- JSON request/response format - JSON request/response format
- Standard authentication method - Standard authentication method
- Similar model parameter handling - Similar model parameter handling
</details>
## ⚡ Installation ## ⚡ Installation
@@ -144,7 +165,8 @@ To be compatible, a provider should support:
3. Search for "HA Text AI" 3. Search for "HA Text AI"
4. Follow the configuration steps 4. Follow the configuration steps
### Via YAML <details>
<summary>📦 Via YAML (Advanced)</summary>
### Platform Configuration (Global Settings) ### Platform Configuration (Global Settings)
@@ -202,6 +224,8 @@ sensor:
| `temperature` | Float | ❌ | Platform setting | Override global temperature | | `temperature` | Float | ❌ | Platform setting | Override global temperature |
| `max_tokens` | Integer | ❌ | Platform setting | Override global max tokens | | `max_tokens` | Integer | ❌ | Platform setting | Override global max tokens |
</details>
## 🛠️ Available Services ## 🛠️ Available Services
### ask_question ### ask_question
@@ -258,7 +282,7 @@ sensor.ha_text_ai_YOUR_UNIQUE_SUFFIX
# Examples: # Examples:
sensor.ha_text_ai_gpt # GPT-based sensor sensor.ha_text_ai_gpt # GPT-based sensor
sensor.ha_text_ai_claude # Claude-based sensor sensor.ha_text_ai_claude # Claude-based sensor
sensor.ha_text_ai_gpt # Custom suffix sensor.ha_text_ai_abc # Custom suffix
``` ```
#### Response Retrieval #### Response Retrieval
@@ -291,6 +315,16 @@ automation:
### 🔍 HA Text AI Sensor Attributes ### 🔍 HA Text AI Sensor Attributes
- 🤖 **Model and Provider Information**: Tracking current AI model and service provider
- 🚦 **System Status**: Real-time API and processing readiness
- 📊 **Performance Metrics**: Request success rates and response times
- 💬 **Conversation Tracking**: Token usage and interaction history (for more precise token counting, install `tiktoken`; otherwise, a fallback estimation method is automatically used)
- 🕒 **Last Interaction Details**: Recent query and response tracking
- ❤️ **System Health**: Error monitoring and service uptime
<details>
<summary>📦 Detailed Sensor Attributes</summary>
#### Model and Provider Information #### Model and Provider Information
```yaml ```yaml
# Name of the AI model currently in use (e.g., latest version of GPT) # Name of the AI model currently in use (e.g., latest version of GPT)
@@ -352,7 +386,6 @@ automation:
# Last few conversation entries (limited to 3 for performance) # Last few conversation entries (limited to 3 for performance)
{{ state_attr('sensor.ha_text_ai_gpt', 'conversation_history') }} # [...] {{ state_attr('sensor.ha_text_ai_gpt', 'conversation_history') }} # [...]
``` ```
#### Last Interaction Details #### Last Interaction Details
@@ -391,6 +424,7 @@ Conversation history stored in `.storage/ha_text_ai_history/` directory:
- Use these attributes for monitoring and automation - Use these attributes for monitoring and automation
- Some values might be 0 or empty initially - Some values might be 0 or empty initially
</details>
## 📘 FAQ ## 📘 FAQ
@@ -427,7 +461,6 @@ A: Yes, archived history files are stored with timestamps and can be accessed ma
**Q: How much history is kept?** **Q: How much history is kept?**
A: By default, up to 100 conversations are stored, but this can be configured. Files are automatically rotated when they reach 1MB. A: By default, up to 100 conversations are stored, but this can be configured. Files are automatically rotated when they reach 1MB.
## 🤝 Contributing ## 🤝 Contributing
Contributions welcome! Please read our [Contributing Guide](CONTRIBUTING.md). Contributions welcome! Please read our [Contributing Guide](CONTRIBUTING.md).
@@ -474,6 +507,7 @@ If you want to say thanks financially, you can send a small token of appreciatio
--- ---
<div align="center"><img src="https://github.com/smkrv/ha-text-ai/blob/f2a41aaa2cd88adaf6929ba5f228205a315efdfa/custom_components/ha_text_ai/icons/dark_icon%402x.png" alt="HA Text AI" style="width: 128px; height: auto;"/></div>
<div align="center"> <div align="center">
Made with ❤️ and Claude 3.5 Sonnet for the Home Assistant Community Made with ❤️ and Claude 3.5 Sonnet for the Home Assistant Community
+63 -13
View File
@@ -11,8 +11,9 @@ from __future__ import annotations
import logging import logging
import os import os
import shutil import shutil
import hashlib
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Any, Dict from typing import Any, Dict, TypeVar
import voluptuous as vol import voluptuous as vol
from async_timeout import timeout from async_timeout import timeout
@@ -52,11 +53,13 @@ from .const import (
SERVICE_SET_SYSTEM_PROMPT, SERVICE_SET_SYSTEM_PROMPT,
DEFAULT_MAX_HISTORY, DEFAULT_MAX_HISTORY,
CONF_MAX_HISTORY_SIZE, CONF_MAX_HISTORY_SIZE,
ICONS_SUBDOMAIN,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
ConfigType = TypeVar("ConfigType", bound=Dict[str, Any])
SERVICE_SCHEMA_ASK_QUESTION = vol.Schema({ SERVICE_SCHEMA_ASK_QUESTION = vol.Schema({
vol.Required("instance"): cv.string, vol.Required("instance"): cv.string,
@@ -90,19 +93,18 @@ def get_coordinator_by_instance(hass: HomeAssistant, instance: str) -> HATextAIC
raise HomeAssistantError(f"Instance {instance} not found") raise HomeAssistantError(f"Instance {instance} not found")
async def async_setup(hass: HomeAssistant, config: Dict[str, Any]) -> bool: def get_file_hash(file_path: str) -> str:
"""Set up the HA Text AI component.""" """Calculate SHA256 hash of file."""
hass.data.setdefault(DOMAIN, {}) sha256_hash = hashlib.sha256()
with open(file_path, "rb") as f:
for byte_block in iter(lambda: f.read(4096), b""):
sha256_hash.update(byte_block)
return sha256_hash.hexdigest()
try: async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
source = os.path.join(os.path.dirname(__file__), 'icons', 'icon.svg') """Set up the Home Assistant Text AI component."""
dest_dir = os.path.join(hass.config.path('www'), 'icons') # Initialize domain data storage
os.makedirs(dest_dir, exist_ok=True) hass.data.setdefault(DOMAIN, {})
dest = os.path.join(dest_dir, 'icon.png')
if not os.path.exists(dest):
shutil.copyfile(source, dest)
except Exception as ex:
_LOGGER.warning("Failed to copy custom icon: %s", str(ex))
async def async_ask_question(call: ServiceCall) -> None: async def async_ask_question(call: ServiceCall) -> None:
"""Handle ask_question service.""" """Handle ask_question service."""
@@ -150,6 +152,7 @@ async def async_setup(hass: HomeAssistant, config: Dict[str, Any]) -> bool:
_LOGGER.error("Error setting system prompt: %s", str(err)) _LOGGER.error("Error setting system prompt: %s", str(err))
raise HomeAssistantError(f"Failed to set system prompt: {str(err)}") raise HomeAssistantError(f"Failed to set system prompt: {str(err)}")
# Register services
hass.services.async_register( hass.services.async_register(
DOMAIN, DOMAIN,
SERVICE_ASK_QUESTION, SERVICE_ASK_QUESTION,
@@ -178,6 +181,53 @@ async def async_setup(hass: HomeAssistant, config: Dict[str, Any]) -> bool:
schema=SERVICE_SCHEMA_SET_SYSTEM_PROMPT schema=SERVICE_SCHEMA_SET_SYSTEM_PROMPT
) )
# Handle icons
try:
source_icon_path = os.path.join(
os.path.dirname(__file__),
ICONS_SUBDOMAIN,
'icon@2x.png'
)
destination_directory = os.path.join(
hass.config.path('www'),
DOMAIN,
ICONS_SUBDOMAIN
)
destination_icon_path = os.path.join(
destination_directory,
'icon.png'
)
if not os.path.exists(source_icon_path):
_LOGGER.error("Source icon not found: %s", source_icon_path)
return True
def create_directory():
os.makedirs(destination_directory, exist_ok=True)
await hass.async_add_executor_job(create_directory)
should_copy = True
if os.path.exists(destination_icon_path):
source_hash = await hass.async_add_executor_job(get_file_hash, source_icon_path)
dest_hash = await hass.async_add_executor_job(get_file_hash, destination_icon_path)
should_copy = source_hash != dest_hash
if should_copy:
def copy_file():
shutil.copyfile(source_icon_path, destination_icon_path)
await hass.async_add_executor_job(copy_file)
_LOGGER.debug("Icon updated: %s", destination_icon_path)
except PermissionError as e:
_LOGGER.error("Permission denied when managing icons: %s", str(e))
except Exception as e:
_LOGGER.error("Failed to manage icons: %s", str(e))
return True return True
async def async_check_api(session, endpoint: str, headers: dict, provider: str) -> bool: async def async_check_api(session, endpoint: str, headers: dict, provider: str) -> bool:
+3 -1
View File
@@ -41,7 +41,9 @@ CONF_IS_ANTHROPIC: Final = "is_anthropic"
CONF_CONTEXT_MESSAGES: Final = "context_messages" CONF_CONTEXT_MESSAGES: Final = "context_messages"
ABSOLUTE_MAX_HISTORY_SIZE = 500 ABSOLUTE_MAX_HISTORY_SIZE = 500
MAX_ENTRY_SIZE = 1 * 1024 * 1024 MAX_ATTRIBUTE_SIZE = 4 * 1024
MAX_HISTORY_FILE_SIZE = 1 * 1024 * 1024
ICONS_SUBDOMAIN = "icons"
# Default values # Default values
DEFAULT_MODEL: Final = "gpt-4o-mini" DEFAULT_MODEL: Final = "gpt-4o-mini"
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 325 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 259 KiB

+8 -8
View File
@@ -13,16 +13,16 @@
"loggers": ["custom_components.ha_text_ai"], "loggers": ["custom_components.ha_text_ai"],
"mqtt": [], "mqtt": [],
"quality_scale": "silver", "quality_scale": "silver",
"requirements": [ "requirements": [
"openai>=1.12.0", "openai>=1.12.0",
"anthropic>=0.8.0", "anthropic>=0.8.0",
"aiohttp>=3.8.0", "aiohttp>=3.8.0",
"async-timeout>=4.0.0", "async-timeout>=4.0.0",
"certifi>=2024.2.2" "certifi>=2024.2.2"
], ],
"single_config_entry": false, "single_config_entry": false,
"ssdp": [], "ssdp": [],
"usb": [], "usb": [],
"version": "2.0.5-beta", "version": "2.0.6-beta",
"zeroconf": [] "zeroconf": []
} }
+68 -47
View File
@@ -67,6 +67,7 @@ from .const import (
ENTITY_ICON_PROCESSING, ENTITY_ICON_PROCESSING,
DEFAULT_NAME_PREFIX, DEFAULT_NAME_PREFIX,
CONF_MAX_HISTORY_SIZE, CONF_MAX_HISTORY_SIZE,
MAX_ATTRIBUTE_SIZE,
) )
from .coordinator import HATextAICoordinator from .coordinator import HATextAICoordinator
@@ -178,12 +179,29 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
def _sanitize_attributes(self, attributes: Dict[str, Any]) -> Dict[str, Any]: def _sanitize_attributes(self, attributes: Dict[str, Any]) -> Dict[str, Any]:
"""Sanitize all attributes for JSON serialization.""" """Sanitize all attributes for JSON serialization."""
return { sanitized = {
key: self._sanitize_value(value) key: self._sanitize_value(value)
for key, value in attributes.items() for key, value in attributes.items()
if value is not None if value is not None
} }
# Log metrics for debugging
metrics_keys = [
METRIC_TOTAL_TOKENS,
METRIC_PROMPT_TOKENS,
METRIC_COMPLETION_TOKENS,
METRIC_SUCCESSFUL_REQUESTS,
METRIC_FAILED_REQUESTS,
METRIC_AVERAGE_LATENCY,
METRIC_MAX_LATENCY,
METRIC_MIN_LATENCY,
]
metrics_values = {k: sanitized.get(k) for k in metrics_keys if k in sanitized}
_LOGGER.debug(f"Metrics for {self.entity_id}: {metrics_values}")
return sanitized
@property @property
def native_value(self) -> StateType: def native_value(self) -> StateType:
"""Return the native value of the sensor.""" """Return the native value of the sensor."""
@@ -212,68 +230,65 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
try: try:
data = self.coordinator.data data = self.coordinator.data
metrics = data.get("metrics", {})
# Base attributes
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( ATTR_API_PROVIDER: self._config_entry.data.get(CONF_API_PROVIDER, "Unknown"),
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: metrics.get("total_errors", 0),
ATTR_LAST_ERROR: self._last_error,
"instance_name": self._instance_name, "instance_name": self._instance_name,
"normalized_name": self._normalized_name, "normalized_name": self._normalized_name,
ATTR_SYSTEM_PROMPT: data.get("system_prompt"), ATTR_SYSTEM_PROMPT: (data.get("system_prompt", "")[:MAX_ATTRIBUTE_SIZE]
if data.get("system_prompt") else None),
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),
ATTR_IS_MAINTENANCE: data.get("is_maintenance", False), ATTR_IS_MAINTENANCE: data.get("is_maintenance", False),
ATTR_ENDPOINT_STATUS: data.get("endpoint_status", "unknown"), ATTR_ENDPOINT_STATUS: data.get("endpoint_status", "unknown"),
ATTR_UPTIME: data.get("uptime", 0), ATTR_UPTIME: round(data.get("uptime", 0), 2),
ATTR_HISTORY_SIZE: data.get("history_size", 0), ATTR_HISTORY_SIZE: data.get("history_size", 0),
ATTR_CONVERSATION_HISTORY: data.get("conversation_history", []),
} }
# Add metrics # History limit
metrics = data.get("metrics", {}) conversation_history = data.get("conversation_history", [])
if isinstance(metrics, dict): if conversation_history:
self._metrics = metrics limited_history = []
attributes.update( for entry in conversation_history:
{ limited_entry = {
METRIC_TOTAL_TOKENS: metrics.get("total_tokens", 0), "timestamp": entry["timestamp"],
METRIC_PROMPT_TOKENS: metrics.get("prompt_tokens", 0), "question": entry["question"][:MAX_ATTRIBUTE_SIZE],
METRIC_COMPLETION_TOKENS: metrics.get("completion_tokens", 0), "response": entry["response"][:MAX_ATTRIBUTE_SIZE]
METRIC_SUCCESSFUL_REQUESTS: metrics.get(
"successful_requests", 0
),
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")),
} }
) limited_history.append(limited_entry)
attributes[ATTR_CONVERSATION_HISTORY] = limited_history
# Add last response # Metrics
if isinstance(metrics, dict):
attributes.update({
METRIC_TOTAL_TOKENS: metrics.get("total_tokens", 0),
METRIC_PROMPT_TOKENS: metrics.get("prompt_tokens", 0),
METRIC_COMPLETION_TOKENS: metrics.get("completion_tokens", 0),
METRIC_SUCCESSFUL_REQUESTS: metrics.get("successful_requests", 0),
METRIC_FAILED_REQUESTS: metrics.get("failed_requests", 0),
METRIC_AVERAGE_LATENCY: round(metrics.get("average_latency", 0), 2),
METRIC_MAX_LATENCY: round(metrics.get("max_latency", 0), 2),
METRIC_MIN_LATENCY: (metrics.get("min_latency")
if metrics.get("min_latency") != float("inf")
else None),
})
# Last response handling
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 attributes.update({
attributes.update( ATTR_RESPONSE: last_response.get("response", "")[:MAX_ATTRIBUTE_SIZE],
{ ATTR_QUESTION: last_response.get("question", "")[:MAX_ATTRIBUTE_SIZE],
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", "")[:MAX_ATTRIBUTE_SIZE]
"last_timestamp": last_response.get("timestamp", ""), if last_response.get("error") else None),
"last_error": last_response.get("error"), })
}
)
# Add performance metrics if available
if ATTR_PERFORMANCE_METRICS in data:
attributes[ATTR_PERFORMANCE_METRICS] = data[
ATTR_PERFORMANCE_METRICS
]
# Add API version if available
if ATTR_API_VERSION in data:
attributes[ATTR_API_VERSION] = data[ATTR_API_VERSION]
return self._sanitize_attributes(attributes) return self._sanitize_attributes(attributes)
@@ -299,6 +314,12 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
self._is_processing = data.get("is_processing", False) self._is_processing = data.get("is_processing", False)
# Update metrics
metrics = data.get("metrics", {})
if isinstance(metrics, dict):
self._metrics.update(metrics)
_LOGGER.debug(f"Updated metrics for {self.entity_id}: {self._metrics}")
# Update conversation history and system prompt # Update conversation history and system prompt
self._conversation_history = data.get("conversation_history", []) self._conversation_history = data.get("conversation_history", [])
self._system_prompt = data.get("system_prompt") self._system_prompt = data.get("system_prompt")
Binary file not shown.

After

Width:  |  Height:  |  Size: 339 KiB

+29 -22
View File
@@ -1,25 +1,32 @@
``` ```
ha-text-ai/ ha-text-ai/
├── custom_components
├── custom_components/ │   └── ha_text_ai
├── ha_text_ai/ │   ├── __init__.py
├── __init__.py │   ├── api_client.py
├── config_flow.py │   ├── config_flow.py
├── coordinator.py │   ├── const.py
├── manifest.json │   ├── coordinator.py
├── sensor.py │   ├── icons
├── services.yaml │      ├── dark_icon.png
├── const.py │      ├── dark_icon@2x.png
└── api_client.py │      ├── dark_logo.png
│      ├── dark_logo@2x.png
├── translations/ │   │   ├── icon.png
│ ├── en.json │   │   ├── icon@2x.png
│ ├── de.json │   │   ├── logo.png
└── ru.json │      └── logo@2x.png
│   ├── manifest.json
── icons/ │   ── sensor.py
├── icon.png │   ├── services.yaml
├── icon@2x.png │   └── translations
├── logo.png │   ├── de.json
── logo@2x.png │   ── en.json
│   ├── es.json
│   ├── hi.json
│   ├── it.json
│   ├── ru.json
│   ├── sr.json
│   └── zh.json
``` ```