mirror of
https://github.com/smkrv/ha-text-ai.git
synced 2026-07-30 09:33:57 +08:00
Release v2.0.0
This commit is contained in:
@@ -129,17 +129,20 @@ class HATextAICoordinator(DataUpdateCoordinator):
|
|||||||
|
|
||||||
async def _async_update_data(self) -> Dict[str, Any]:
|
async def _async_update_data(self) -> Dict[str, Any]:
|
||||||
"""Update data via API."""
|
"""Update data via API."""
|
||||||
|
if not self._is_ready:
|
||||||
|
return self._responses
|
||||||
|
|
||||||
if self._question_queue.empty():
|
if self._question_queue.empty():
|
||||||
return self._responses
|
return self._responses
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with async_timeout.timeout(DEFAULT_TIMEOUT):
|
async with async_timeout.timeout(DEFAULT_TIMEOUT):
|
||||||
self._is_processing = True
|
self._is_processing = True
|
||||||
priority, question_data = await self._question_queue.get()
|
|
||||||
question = question_data["question"]
|
|
||||||
params = question_data["params"]
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
priority, question_data = await self._question_queue.get()
|
||||||
|
question = question_data["question"]
|
||||||
|
params = question_data["params"]
|
||||||
|
|
||||||
response_data = await self._make_api_call(
|
response_data = await self._make_api_call(
|
||||||
question,
|
question,
|
||||||
model=params.get("model"),
|
model=params.get("model"),
|
||||||
@@ -148,42 +151,63 @@ class HATextAICoordinator(DataUpdateCoordinator):
|
|||||||
system_prompt=params.get("system_prompt")
|
system_prompt=params.get("system_prompt")
|
||||||
)
|
)
|
||||||
|
|
||||||
self._update_metrics(response_data)
|
await self._handle_successful_response(
|
||||||
self._responses[question] = {
|
question, response_data, params, priority
|
||||||
"question": question,
|
)
|
||||||
"response": response_data["response"],
|
|
||||||
"error": None,
|
|
||||||
"timestamp": dt_util.utcnow(),
|
|
||||||
"model": response_data["model"],
|
|
||||||
"temperature": params.get("temperature", self.temperature),
|
|
||||||
"max_tokens": params.get("max_tokens", self.max_tokens),
|
|
||||||
"response_time": response_data.get("response_time"),
|
|
||||||
"tokens": response_data.get("tokens", 0),
|
|
||||||
"priority": priority
|
|
||||||
}
|
|
||||||
self._error_count = 0
|
|
||||||
self._is_ready = True
|
|
||||||
self._endpoint_status = "connected"
|
|
||||||
self._request_count += 1
|
|
||||||
self._tokens_used += response_data.get("tokens", 0)
|
|
||||||
self._last_request_time = time.time()
|
|
||||||
|
|
||||||
# Fire event for successful response
|
except asyncio.TimeoutError:
|
||||||
self.hass.bus.async_fire(f"{DOMAIN}_response_received", {
|
_LOGGER.error("Timeout while processing API request")
|
||||||
"question": question,
|
await self._handle_api_error(question, "Request timeout")
|
||||||
"model": response_data["model"],
|
|
||||||
"tokens": response_data.get("tokens", 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
_LOGGER.debug("Response received for question: %s", question)
|
except KeyError as ke:
|
||||||
|
_LOGGER.error("Invalid question data format: %s", str(ke))
|
||||||
|
await self._handle_api_error(question, f"Invalid data format: {ke}")
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
|
_LOGGER.error("Error processing question: %s", str(err))
|
||||||
await self._handle_api_error(question, err)
|
await self._handle_api_error(question, err)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self._is_processing = False
|
self._is_processing = False
|
||||||
self._question_queue.task_done()
|
self._question_queue.task_done()
|
||||||
|
|
||||||
return self._responses
|
except Exception as e:
|
||||||
|
_LOGGER.error("Critical error in _async_update_data: %s", str(e))
|
||||||
|
self._is_ready = False
|
||||||
|
self._endpoint_status = "error"
|
||||||
|
|
||||||
|
return self._responses
|
||||||
|
|
||||||
|
async def _handle_successful_response(self, question, response_data, params, priority):
|
||||||
|
"""Handle successful API response."""
|
||||||
|
self._responses[question] = {
|
||||||
|
"question": question,
|
||||||
|
"response": response_data["response"],
|
||||||
|
"error": None,
|
||||||
|
"timestamp": dt_util.utcnow(),
|
||||||
|
"model": response_data["model"],
|
||||||
|
"temperature": params.get("temperature", self.temperature),
|
||||||
|
"max_tokens": params.get("max_tokens", self.max_tokens),
|
||||||
|
"response_time": response_data.get("response_time"),
|
||||||
|
"tokens": response_data.get("tokens", 0),
|
||||||
|
"priority": priority
|
||||||
|
}
|
||||||
|
|
||||||
|
self._error_count = 0
|
||||||
|
self._is_ready = True
|
||||||
|
self._endpoint_status = "connected"
|
||||||
|
self._request_count += 1
|
||||||
|
self._tokens_used += response_data.get("tokens", 0)
|
||||||
|
self._last_request_time = time.time()
|
||||||
|
|
||||||
|
# Fire event for successful response
|
||||||
|
self.hass.bus.async_fire(f"{DOMAIN}_response_received", {
|
||||||
|
"question": question,
|
||||||
|
"model": response_data["model"],
|
||||||
|
"tokens": response_data.get("tokens", 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
_LOGGER.debug("Response received for question: %s", question)
|
||||||
|
|
||||||
except asyncio.TimeoutError as err:
|
except asyncio.TimeoutError as err:
|
||||||
_LOGGER.error("Timeout while processing question")
|
_LOGGER.error("Timeout while processing question")
|
||||||
@@ -344,24 +368,34 @@ class HATextAICoordinator(DataUpdateCoordinator):
|
|||||||
max_tokens=max_tokens if max_tokens is not None else self.max_tokens,
|
max_tokens=max_tokens if max_tokens is not None else self.max_tokens,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER.debug("Received API response: %s", completion)
|
_LOGGER.debug("Raw API response: %s", completion)
|
||||||
|
|
||||||
if not completion or not hasattr(completion, 'choices') or not completion.choices:
|
if completion is None:
|
||||||
raise ValueError("Invalid or empty response from API")
|
raise ValueError("Received null response from API")
|
||||||
|
|
||||||
|
if not hasattr(completion, 'choices') or not completion.choices:
|
||||||
|
raise ValueError("No choices in API response")
|
||||||
|
|
||||||
|
if not hasattr(completion.choices[0], 'message'):
|
||||||
|
raise ValueError("No message in API response choice")
|
||||||
|
|
||||||
# Безопасное извлечение сообщения
|
|
||||||
message = completion.choices[0].message
|
message = completion.choices[0].message
|
||||||
if not message or not hasattr(message, 'content'):
|
if not hasattr(message, 'content'):
|
||||||
raise ValueError("No content in API response message")
|
raise ValueError("No content in message")
|
||||||
|
|
||||||
|
response_text = message.content
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"response": message.content,
|
"response": response_text,
|
||||||
"model": getattr(completion, 'model', model or self.model),
|
"model": getattr(completion, 'model', model or self.model),
|
||||||
"tokens": completion.usage.total_tokens if hasattr(completion, 'usage') else 0
|
"tokens": completion.usage.total_tokens if hasattr(completion, 'usage') else 0
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_LOGGER.error("OpenAI API call error: %s", str(e))
|
_LOGGER.error("OpenAI API call error: %s", str(e))
|
||||||
|
# Добавим более подробное логирование
|
||||||
|
if completion:
|
||||||
|
_LOGGER.debug("Failed response structure: %s", str(completion))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
async def async_ask_question(
|
async def async_ask_question(
|
||||||
@@ -374,29 +408,36 @@ class HATextAICoordinator(DataUpdateCoordinator):
|
|||||||
priority: bool = False
|
priority: bool = False
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add question to queue with priority support."""
|
"""Add question to queue with priority support."""
|
||||||
if not self._is_ready and self._error_count >= self._MAX_ERRORS:
|
|
||||||
_LOGGER.warning("Coordinator is not ready due to previous errors")
|
|
||||||
return
|
|
||||||
|
|
||||||
question_data = {
|
|
||||||
"question": question,
|
|
||||||
"params": {
|
|
||||||
"system_prompt": system_prompt,
|
|
||||||
"model": model,
|
|
||||||
"temperature": temperature,
|
|
||||||
"max_tokens": max_tokens
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Priority: 0 for high priority, 1 for normal
|
|
||||||
priority_level = 0 if priority else 1
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if not self._is_ready and self._error_count >= self._MAX_ERRORS:
|
||||||
|
_LOGGER.warning("Coordinator is not ready due to previous errors")
|
||||||
|
return
|
||||||
|
|
||||||
|
_LOGGER.debug("Processing question: %s with model: %s", question, model or self.model)
|
||||||
|
|
||||||
|
question_data = {
|
||||||
|
"question": question,
|
||||||
|
"params": {
|
||||||
|
"system_prompt": system_prompt,
|
||||||
|
"model": model,
|
||||||
|
"temperature": temperature,
|
||||||
|
"max_tokens": max_tokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Priority: 0 for high priority, 1 for normal
|
||||||
|
priority_level = 0 if priority else 1
|
||||||
|
|
||||||
await self._question_queue.put((priority_level, question_data))
|
await self._question_queue.put((priority_level, question_data))
|
||||||
|
_LOGGER.debug("Question added to queue with priority %d", priority_level)
|
||||||
await self.async_refresh()
|
await self.async_refresh()
|
||||||
|
|
||||||
except asyncio.QueueFull:
|
except asyncio.QueueFull:
|
||||||
_LOGGER.error("Question queue is full. Try again later.")
|
_LOGGER.error("Question queue is full. Try again later.")
|
||||||
raise RuntimeError("Queue is full")
|
raise RuntimeError("Queue is full")
|
||||||
|
except Exception as e:
|
||||||
|
_LOGGER.error("Error in async_ask_question: %s", str(e))
|
||||||
|
raise
|
||||||
|
|
||||||
async def async_shutdown(self) -> None:
|
async def async_shutdown(self) -> None:
|
||||||
"""Shutdown the coordinator."""
|
"""Shutdown the coordinator."""
|
||||||
|
|||||||
Reference in New Issue
Block a user