Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d29535245f | ||
|
|
ca3ae982b0 | ||
|
|
0c4399b46c | ||
|
|
c13ef1921d | ||
|
|
0fb9acfa8f | ||
|
|
2ac4389e1a | ||
|
|
de194e425c | ||
|
|
95f3d6506e | ||
|
|
3e877243b9 | ||
|
|
fd795c9e90 | ||
|
|
5f5dc041b9 | ||
|
|
3d3885d43d | ||
|
|
b059744716 | ||
|
|
f2a41aaa2c | ||
|
|
1fcf751d0d | ||
|
|
a45c9407fd | ||
|
|
21fdf108cf | ||
|
|
c7ec2b3ea4 | ||
|
|
6c4da6cea5 | ||
|
|
3029c5dd26 | ||
|
|
3e97028094 | ||
|
|
1194c87134 | ||
|
|
e03b5315c2 | ||
|
|
fad887492f | ||
|
|
f26df29937 | ||
|
|
69b54a08ba | ||
|
|
7ce77eb18e | ||
|
|
1480669dc8 |
@@ -4,7 +4,7 @@
|
||||
|
||||
  [](https://creativecommons.org/licenses/by-nc-sa/4.0/) [](https://github.com/hacs/integration)   
|
||||
|
||||
<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
|
||||
</div>
|
||||
@@ -27,53 +27,67 @@ Transform your smart home experience with powerful AI assistance powered by mult
|
||||
|
||||
## 🌟 Features
|
||||
|
||||
- 🧠 **Multi-Provider AI Integration**:
|
||||
- Support for OpenAI GPT models
|
||||
- Anthropic Claude integration
|
||||
- Custom API endpoints
|
||||
- Flexible model selection
|
||||
- 🧠 **Multi-Provider AI Integration**: Support for OpenAI GPT and Anthropic Claude models
|
||||
- 💬 **Advanced Language Processing**: Context-aware, multi-turn conversations
|
||||
- 📝 **Enhanced Memory Management**: Secure file-based history storage
|
||||
- ⚡ **Performance Optimization**: Efficient token usage and smart rate limiting
|
||||
- 🎯 **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**:
|
||||
- Context-aware responses
|
||||
- Multi-turn conversations
|
||||
- Custom system instructions
|
||||
- Natural conversation flow
|
||||
<details>
|
||||
<summary>📦 Detailed Feature Breakdown</summary>
|
||||
|
||||
- 📝 **Enhanced Memory Management**:
|
||||
- File-based conversation history storage
|
||||
- Automatic history rotation
|
||||
- Configurable history size limits
|
||||
- Secure storage in Home Assistant
|
||||
### 🧠 **Multi-Provider AI Integration**
|
||||
- Support for OpenAI GPT models
|
||||
- Anthropic Claude integration
|
||||
- Custom API endpoints
|
||||
- Flexible model selection
|
||||
|
||||
- ⚡ **Performance Optimization**:
|
||||
- Efficient token usage
|
||||
- Smart rate limiting
|
||||
- Response caching
|
||||
- Request interval control
|
||||
### 💬 **Advanced Language Processing**
|
||||
- Context-aware responses
|
||||
- Multi-turn conversations
|
||||
- Custom system instructions
|
||||
- Natural conversation flow
|
||||
|
||||
- 🎯 **Advanced Customization**:
|
||||
- Per-request model selection
|
||||
- Adjustable parameters
|
||||
- Custom system prompts
|
||||
- Temperature control
|
||||
### 📝 **Enhanced Memory Management**
|
||||
- File-based conversation history storage
|
||||
- Automatic history rotation
|
||||
- Configurable history size limits
|
||||
- Secure storage in Home Assistant
|
||||
|
||||
- 🔒 **Enhanced Security**:
|
||||
- Secure API key storage
|
||||
- Rate limiting protection
|
||||
- Error handling
|
||||
- Usage monitoring
|
||||
### ⚡ **Performance Optimization**
|
||||
- Efficient token usage
|
||||
- Smart rate limiting
|
||||
- Response caching
|
||||
- Request interval control
|
||||
|
||||
- 🎨 **Improved User Experience**:
|
||||
- Intuitive configuration UI
|
||||
- Detailed sensor attributes
|
||||
- Rich service interface
|
||||
- Model selection UI
|
||||
### 🎯 **Advanced Customization**
|
||||
- Per-request model selection
|
||||
- Adjustable parameters
|
||||
- Custom system prompts
|
||||
- Temperature control
|
||||
|
||||
- 🔄 **Automation Integration**:
|
||||
- Event-driven responses
|
||||
- Conditional logic support
|
||||
- Template compatibility
|
||||
- Model-specific automation
|
||||
### 🔒 **Enhanced Security**
|
||||
- Secure API key storage
|
||||
- Rate limiting protection
|
||||
- Error handling
|
||||
- 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
|
||||
|
||||
@@ -85,37 +99,44 @@ Transform your smart home experience with powerful AI assistance powered by mult
|
||||
- Python 3.9 or newer
|
||||
- Stable internet connection
|
||||
|
||||
### 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)
|
||||
## Configuration Options
|
||||
|
||||
#### ⓘ Potentially Compatible Providers
|
||||
The integration is designed to be flexible and may work with other providers offering OpenAI-compatible APIs:
|
||||
- Groq
|
||||
- Together AI
|
||||
- Perplexity AI
|
||||
- Mistral AI
|
||||
- Google AI
|
||||
- Local AI servers (like Ollama)
|
||||
- Custom OpenAI-compatible endpoints
|
||||
### 🔧 **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 (token usage is estimated using a heuristic method based on word count and specific word characteristics, which may differ from actual token usage)
|
||||
- ⏱️ **Request Interval**: API call throttling
|
||||
- 💾 **History Size**: Number of messages to retain
|
||||
- 🌍 **Custom API Endpoint**: Optional advanced configuration
|
||||
|
||||
#### Additional Notes
|
||||
- Not all providers guarantee full compatibility
|
||||
- Performance may vary between providers
|
||||
- Check individual provider's documentation
|
||||
<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:
|
||||
- Groq
|
||||
- Together AI
|
||||
- Perplexity AI
|
||||
- Mistral AI
|
||||
- Google AI
|
||||
- Local AI servers (like Ollama)
|
||||
- Custom OpenAI-compatible endpoints
|
||||
|
||||
#### 🚨 Compatibility Notes
|
||||
- Not all providers guarantee full compatibility
|
||||
- Performance may vary between providers
|
||||
- Check individual provider's documentation
|
||||
- Ensure your API key has sufficient credits/quota
|
||||
|
||||
#### Provider Compatibility Requirements
|
||||
To be compatible, a provider should support:
|
||||
- OpenAI-like REST API structure
|
||||
- JSON request/response format
|
||||
- Standard authentication method
|
||||
#### 🔍 Provider Compatibility Requirements
|
||||
To be compatible, a provider should support:
|
||||
- OpenAI-like REST API structure
|
||||
- JSON request/response format
|
||||
- Standard authentication method
|
||||
- Similar model parameter handling
|
||||
</details>
|
||||
|
||||
## ⚡ Installation
|
||||
|
||||
@@ -144,7 +165,8 @@ To be compatible, a provider should support:
|
||||
3. Search for "HA Text AI"
|
||||
4. Follow the configuration steps
|
||||
|
||||
### Via YAML
|
||||
<details>
|
||||
<summary>📦 Via YAML (Advanced)</summary>
|
||||
|
||||
### Platform Configuration (Global Settings)
|
||||
|
||||
@@ -202,6 +224,8 @@ sensor:
|
||||
| `temperature` | Float | ❌ | Platform setting | Override global temperature |
|
||||
| `max_tokens` | Integer | ❌ | Platform setting | Override global max tokens |
|
||||
|
||||
</details>
|
||||
|
||||
## 🛠️ Available Services
|
||||
|
||||
### ask_question
|
||||
@@ -258,7 +282,7 @@ sensor.ha_text_ai_YOUR_UNIQUE_SUFFIX
|
||||
# Examples:
|
||||
sensor.ha_text_ai_gpt # GPT-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
|
||||
@@ -291,6 +315,16 @@ automation:
|
||||
|
||||
### 🔍 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 are estimated using a heuristic method based on word count and specific word characteristics, which may differ from actual token usage.
|
||||
- 🕒 **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
|
||||
```yaml
|
||||
# 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)
|
||||
{{ state_attr('sensor.ha_text_ai_gpt', 'conversation_history') }} # [...]
|
||||
|
||||
```
|
||||
|
||||
#### Last Interaction Details
|
||||
@@ -381,7 +414,7 @@ automation:
|
||||
|
||||
### History Storage
|
||||
Conversation history stored in `.storage/ha_text_ai_history/` directory:
|
||||
- Each instance has its own history file
|
||||
- Each instance has its own history file (JSON)
|
||||
- Files are automatically rotated when size limit is reached
|
||||
- Archived history files are timestamped
|
||||
- Default maximum file size: 1MB
|
||||
@@ -391,6 +424,7 @@ Conversation history stored in `.storage/ha_text_ai_history/` directory:
|
||||
- Use these attributes for monitoring and automation
|
||||
- Some values might be 0 or empty initially
|
||||
|
||||
</details>
|
||||
|
||||
## 📘 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?**
|
||||
A: By default, up to 100 conversations are stored, but this can be configured. Files are automatically rotated when they reach 1MB.
|
||||
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
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">
|
||||
|
||||
Made with ❤️ and Claude 3.5 Sonnet for the Home Assistant Community
|
||||
|
||||
@@ -11,8 +11,9 @@ from __future__ import annotations
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import hashlib
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict
|
||||
from typing import Any, Dict, TypeVar
|
||||
|
||||
import voluptuous as vol
|
||||
from async_timeout import timeout
|
||||
@@ -52,11 +53,13 @@ from .const import (
|
||||
SERVICE_SET_SYSTEM_PROMPT,
|
||||
DEFAULT_MAX_HISTORY,
|
||||
CONF_MAX_HISTORY_SIZE,
|
||||
ICONS_SUBDOMAIN,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
ConfigType = TypeVar("ConfigType", bound=Dict[str, Any])
|
||||
|
||||
SERVICE_SCHEMA_ASK_QUESTION = vol.Schema({
|
||||
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")
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: Dict[str, Any]) -> bool:
|
||||
"""Set up the HA Text AI component."""
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
def get_file_hash(file_path: str) -> str:
|
||||
"""Calculate SHA256 hash of file."""
|
||||
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:
|
||||
source = os.path.join(os.path.dirname(__file__), 'icons', 'icon.svg')
|
||||
dest_dir = os.path.join(hass.config.path('www'), 'icons')
|
||||
os.makedirs(dest_dir, exist_ok=True)
|
||||
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_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the Home Assistant Text AI component."""
|
||||
# Initialize domain data storage
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
async def async_ask_question(call: ServiceCall) -> None:
|
||||
"""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))
|
||||
raise HomeAssistantError(f"Failed to set system prompt: {str(err)}")
|
||||
|
||||
# Register services
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_ASK_QUESTION,
|
||||
@@ -178,6 +181,53 @@ async def async_setup(hass: HomeAssistant, config: Dict[str, Any]) -> bool:
|
||||
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
|
||||
|
||||
async def async_check_api(session, endpoint: str, headers: dict, provider: str) -> bool:
|
||||
|
||||
@@ -41,7 +41,9 @@ CONF_IS_ANTHROPIC: Final = "is_anthropic"
|
||||
CONF_CONTEXT_MESSAGES: Final = "context_messages"
|
||||
|
||||
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_MODEL: Final = "gpt-4o-mini"
|
||||
|
||||
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 351 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 257 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 325 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 259 KiB |
@@ -13,16 +13,16 @@
|
||||
"loggers": ["custom_components.ha_text_ai"],
|
||||
"mqtt": [],
|
||||
"quality_scale": "silver",
|
||||
"requirements": [
|
||||
"openai>=1.12.0",
|
||||
"anthropic>=0.8.0",
|
||||
"aiohttp>=3.8.0",
|
||||
"async-timeout>=4.0.0",
|
||||
"certifi>=2024.2.2"
|
||||
],
|
||||
"requirements": [
|
||||
"openai>=1.12.0",
|
||||
"anthropic>=0.8.0",
|
||||
"aiohttp>=3.8.0",
|
||||
"async-timeout>=4.0.0",
|
||||
"certifi>=2024.2.2"
|
||||
],
|
||||
"single_config_entry": false,
|
||||
"ssdp": [],
|
||||
"usb": [],
|
||||
"version": "2.0.5-beta",
|
||||
"version": "2.0.7-beta",
|
||||
"zeroconf": []
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ from .const import (
|
||||
ENTITY_ICON_PROCESSING,
|
||||
DEFAULT_NAME_PREFIX,
|
||||
CONF_MAX_HISTORY_SIZE,
|
||||
MAX_ATTRIBUTE_SIZE,
|
||||
)
|
||||
|
||||
from .coordinator import HATextAICoordinator
|
||||
@@ -178,12 +179,29 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
|
||||
|
||||
def _sanitize_attributes(self, attributes: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Sanitize all attributes for JSON serialization."""
|
||||
return {
|
||||
sanitized = {
|
||||
key: self._sanitize_value(value)
|
||||
for key, value in attributes.items()
|
||||
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
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the native value of the sensor."""
|
||||
@@ -212,68 +230,65 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
|
||||
|
||||
try:
|
||||
data = self.coordinator.data
|
||||
metrics = data.get("metrics", {})
|
||||
|
||||
# Base attributes
|
||||
attributes = {
|
||||
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_TOTAL_ERRORS: self._error_count,
|
||||
ATTR_LAST_ERROR: self._last_error,
|
||||
ATTR_TOTAL_ERRORS: metrics.get("total_errors", 0),
|
||||
"instance_name": self._instance_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_RATE_LIMITED: data.get("is_rate_limited", False),
|
||||
ATTR_IS_MAINTENANCE: data.get("is_maintenance", False),
|
||||
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_CONVERSATION_HISTORY: data.get("conversation_history", []),
|
||||
}
|
||||
|
||||
# Add metrics
|
||||
metrics = data.get("metrics", {})
|
||||
if isinstance(metrics, dict):
|
||||
self._metrics = metrics
|
||||
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: metrics.get("average_latency", 0),
|
||||
METRIC_MAX_LATENCY: metrics.get("max_latency", 0),
|
||||
METRIC_MIN_LATENCY: metrics.get("min_latency", float("inf")),
|
||||
# History limit
|
||||
conversation_history = data.get("conversation_history", [])
|
||||
if conversation_history:
|
||||
limited_history = []
|
||||
for entry in conversation_history:
|
||||
limited_entry = {
|
||||
"timestamp": entry["timestamp"],
|
||||
"question": entry["question"][:MAX_ATTRIBUTE_SIZE],
|
||||
"response": entry["response"][:MAX_ATTRIBUTE_SIZE]
|
||||
}
|
||||
)
|
||||
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", {})
|
||||
if isinstance(last_response, dict):
|
||||
self._last_response = last_response
|
||||
attributes.update(
|
||||
{
|
||||
ATTR_RESPONSE: last_response.get("response", ""),
|
||||
ATTR_QUESTION: last_response.get("question", ""),
|
||||
"last_model": last_response.get("model", ""),
|
||||
"last_timestamp": last_response.get("timestamp", ""),
|
||||
"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]
|
||||
attributes.update({
|
||||
ATTR_RESPONSE: last_response.get("response", "")[:MAX_ATTRIBUTE_SIZE],
|
||||
ATTR_QUESTION: last_response.get("question", "")[:MAX_ATTRIBUTE_SIZE],
|
||||
"last_model": last_response.get("model", ""),
|
||||
"last_timestamp": last_response.get("timestamp", ""),
|
||||
"last_error": (last_response.get("error", "")[:MAX_ATTRIBUTE_SIZE]
|
||||
if last_response.get("error") else None),
|
||||
})
|
||||
|
||||
return self._sanitize_attributes(attributes)
|
||||
|
||||
@@ -299,6 +314,12 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
|
||||
|
||||
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
|
||||
self._conversation_history = data.get("conversation_history", [])
|
||||
self._system_prompt = data.get("system_prompt")
|
||||
|
||||
|
After Width: | Height: | Size: 339 KiB |
@@ -1,25 +1,32 @@
|
||||
```
|
||||
ha-text-ai/
|
||||
│
|
||||
├── custom_components/
|
||||
├── ha_text_ai/
|
||||
│ ├── __init__.py
|
||||
│ ├── config_flow.py
|
||||
│ ├── coordinator.py
|
||||
│ ├── manifest.json
|
||||
│ ├── sensor.py
|
||||
│ ├── services.yaml
|
||||
│ ├── const.py
|
||||
│ └── api_client.py
|
||||
│
|
||||
├── translations/
|
||||
│ ├── en.json
|
||||
│ ├── de.json
|
||||
│ └── ru.json
|
||||
│
|
||||
└── icons/
|
||||
├── icon.png
|
||||
├── icon@2x.png
|
||||
├── logo.png
|
||||
└── logo@2x.png
|
||||
├── custom_components
|
||||
│ └── ha_text_ai
|
||||
│ ├── __init__.py
|
||||
│ ├── api_client.py
|
||||
│ ├── config_flow.py
|
||||
│ ├── const.py
|
||||
│ ├── coordinator.py
|
||||
│ ├── icons
|
||||
│ │ ├── dark_icon.png
|
||||
│ │ ├── dark_icon@2x.png
|
||||
│ │ ├── dark_logo.png
|
||||
│ │ ├── dark_logo@2x.png
|
||||
│ │ ├── icon.png
|
||||
│ │ ├── icon@2x.png
|
||||
│ │ ├── logo.png
|
||||
│ │ └── logo@2x.png
|
||||
│ ├── manifest.json
|
||||
│ ├── sensor.py
|
||||
│ ├── services.yaml
|
||||
│ └── translations
|
||||
│ ├── de.json
|
||||
│ ├── en.json
|
||||
│ ├── es.json
|
||||
│ ├── hi.json
|
||||
│ ├── it.json
|
||||
│ ├── ru.json
|
||||
│ ├── sr.json
|
||||
│ └── zh.json
|
||||
|
||||
```
|
||||
|
||||