Skip to content

Commit

Permalink
Release v2025.01.14
Browse files Browse the repository at this point in the history
  • Loading branch information
KNOOP committed Jan 13, 2025
0 parents commit a6091b0
Show file tree
Hide file tree
Showing 17 changed files with 3,479 additions and 0 deletions.
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Zhipu Ai Words AI Home Assistant 🏡

![GitHub Version]( https://img.shields.io/github/v/release/knoop7/zhipuai ) ![GitHub Issues]( https://img.shields.io/github/issues/knoop7/zhipuai ) ![GitHub Forks]( https://img.shields.io/github/forks/knoop7/zhipuai?style=social ) ![GitHub Stars]( https://img.shields.io/github/stars/knoop7/zhipuai?style=social )

<a href="README.md">English</a> | <a href="README-CN.md">简体中文</a>

<img src="https://github.com/user-attachments/assets/f8ff7a6c-4449-496a-889a-d205469a84df" alt="image" width="700" height="400">


---
## Notice: This project is strictly prohibited from commercial use without permission. You may use it as a means of profit, but it cannot be concealed.
### 📦 Installation steps
#### 1. HACS adds custom repository
In the HACS of Home Assistant, click on the three dots in the upper right corner, select "Custom Repository", and add the following URL:

```
https://github.com/knoop7/zhipuai
```


#### 2. Add Zhipu Qingyan Integration
Go to the "Integration" page of Home Assistant, search for and add "Zhipu Qingyan".

#### 3. Configure Key 🔑
In the configuration page, you can log in with your phone number to obtain the Key. After obtaining it, simply fill in the Key for use without the need for additional verification.
**Attention * *: It is recommended that you create a new Key and avoid using the system's default Key.

#### 4. Free model usage 💡
Zhipu Qingyan has chosen the free model by default, which is completely free and there is no need to worry about charging. If you are interested, you can also choose other paid models to experience richer features.

#### 5. Version compatibility 📅
Please ensure that the version of Home Assistant is not lower than 11.0, as Zhipu Qingyan is mainly developed for the latest version. If encountering unrecognized entity issues, it is recommended to restart the system or update to the latest version.

---

86 changes: 86 additions & 0 deletions custom_components/zhipuai/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.dispatcher import async_dispatcher_send
from .const import DOMAIN, LOGGER
from .intents import get_intent_handler, async_setup_intents
from .services import async_setup_services
from .web_search import async_setup_web_search
from .image_gen import async_setup_image_gen
from .entity_analysis import async_setup_entity_analysis

PLATFORMS: list[Platform] = [Platform.CONVERSATION]

class ZhipuAIConfigEntry:
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry):
self.hass = hass
self.config_entry = config_entry
self.api_key = config_entry.data[CONF_API_KEY]
self.options = config_entry.options
self._unsub_options_update_listener = None
self._cleanup_callbacks = []
self.intent_handler = get_intent_handler(hass)

@property
def entry_id(self):
return self.config_entry.entry_id

@property
def title(self):
return self.config_entry.title

async def async_setup(self) -> None:
self._unsub_options_update_listener = self.config_entry.add_update_listener(
self.async_options_updated
)

async def async_unload(self) -> None:
if self._unsub_options_update_listener is not None:
self._unsub_options_update_listener()
self._unsub_options_update_listener = None
for cleanup_callback in self._cleanup_callbacks:
cleanup_callback()
self._cleanup_callbacks.clear()

def async_on_unload(self, func):
self._cleanup_callbacks.append(func)

@callback
async def async_options_updated(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
self.options = entry.options
async_dispatcher_send(hass, f"{DOMAIN}_options_updated", entry)

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
try:
zhipuai_entry = ZhipuAIConfigEntry(hass, entry)
await zhipuai_entry.async_setup()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = zhipuai_entry

unload_services = await async_setup_services(hass)
zhipuai_entry.async_on_unload(unload_services)

await async_setup_intents(hass)

await async_setup_web_search(hass)
await async_setup_image_gen(hass)
await async_setup_entity_analysis(hass)

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True

except Exception as ex:
raise ConfigEntryNotReady from ex

async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
try:
zhipuai_entry = hass.data[DOMAIN].get(entry.entry_id)
if zhipuai_entry is not None and hasattr(zhipuai_entry, 'async_unload'):
await zhipuai_entry.async_unload()
except Exception:
pass
finally:
hass.data[DOMAIN].pop(entry.entry_id, None)
return unload_ok
68 changes: 68 additions & 0 deletions custom_components/zhipuai/ai_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import aiohttp
from aiohttp import TCPConnector
from homeassistant.exceptions import HomeAssistantError
from .const import LOGGER, ZHIPUAI_URL, CONF_REQUEST_TIMEOUT, DEFAULT_REQUEST_TIMEOUT

_SESSION = None

async def get_session():
global _SESSION
if _SESSION is None:
connector = TCPConnector(ssl=False)
_SESSION = aiohttp.ClientSession(connector=connector)
return _SESSION

async def send_ai_request(api_key: str, payload: dict, options: dict = None) -> dict:
try:
session = await get_session()
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
timeout = aiohttp.ClientTimeout(total=options.get(CONF_REQUEST_TIMEOUT, DEFAULT_REQUEST_TIMEOUT))
async with session.post(ZHIPUAI_URL, json=payload, headers=headers, timeout=timeout) as response:
if response.status == 401:
err_str = "API密钥无效或已过期"
elif response.status == 429:
err_str = "请求过于频繁,请稍后再试"
elif response.status in [500, 502, 503]:
err_str = "AI服务器暂时不可用,请稍后再试"
elif response.status == 400:
result = await response.json()
err_str = f"请求参数错误: {result.get('error', {}).get('message', '未知错误')}"
elif response.status != 200:
err_str = f"AI服务返回错误 {response.status}"

if response.status != 200:
LOGGER.error("AI请求错误: %s", err_str)
raise HomeAssistantError(err_str)

result = await response.json()
if "error" in result:
err_msg = result["error"].get("message", "未知错误")
LOGGER.error("AI返回错误: %s", err_msg)
if "token" in err_msg.lower():
raise HomeAssistantError("生成的文本太长,请尝试缩短请求或减小max_tokens值")
elif "rate" in err_msg.lower():
raise HomeAssistantError("请求过于频繁,请稍后再试")
else:
raise HomeAssistantError(f"AI服务返回错误: {err_msg}")
return result

except Exception as err:
err_str = str(err).lower() if str(err) else "未知错误"
LOGGER.error("AI通信错误: %s", err_str)

if not err_str or err_str.isspace():
error_msg = "与AI服务通信失败,请检查网络连接和API密钥配置。"
else:
error_msg = "很抱歉,我现在无法正确处理您的请求。" + (
"网络连接失败,请检查网络设置。" if any(x in err_str for x in ["通信", "communication", "connect", "socket"]) else
"请求超时,尝试减小max_tokens值或缩短请求。" if any(x in err_str for x in ["timeout", "connection", "network"]) else
"API密钥无效或已过期,请更新配置。" if any(x in err_str for x in ["api key", "token", "unauthorized", "authentication"]) else
"请求参数错误,请检查配置。" if "参数" in err_str or "parameter" in err_str else
f"发生错误: {err_str}"
)

LOGGER.error("与 AI 通信时出错: %s", err_str)
raise HomeAssistantError(error_msg)
Loading

0 comments on commit a6091b0

Please sign in to comment.