diff --git a/.idea/.gitignore b/.gitignore
similarity index 58%
rename from .idea/.gitignore
rename to .gitignore
index b2e7db0..b9bee3a 100644
--- a/.idea/.gitignore
+++ b/.gitignore
@@ -1,12 +1,11 @@
+__pycache__
.idea
# Default ignored files
/shelf/
/workspace.xml
-# Editor-based HTTP Client requests
/httpRequests/
-# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
*.iws
*.iml
-*.ipr
\ No newline at end of file
+*.ipr
diff --git a/README.md b/README.md
index 7f9f9d1..d933b3b 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,10 @@
[![Github All Releases](https://img.shields.io/github/downloads/gritaro/gigachain/total.svg)](https://github.com/gritaro/gigachain/releases)
Компонент реализует диалоговую систему Home Assistant для использования с языковыми моделями, поддерживаемыми фреймворком GigaChain.
-В настоящее время поддерживается только интеграция с LMM GigaChat (русскоязычная нейросеть от Сбера)
+В настоящее время поддерживаются интеграции с LMM:
+* [GigaChat](#GigaChat) (русскоязычная нейросеть от Сбера)
+* [YandexGPT](#YandexGPT)
+* [OpenAI](#OpenAI) ака ChatGPT (не тестируется)
## Установка
Устанавливается как и любая HACS интеграция.
@@ -39,7 +42,7 @@
После добавления настройте интеграцию.
## Настройки
-
+### GigaChat
### Авторизация запросов к GigaChat
Для авторизации запросов к GigaChat вам понадобится получить *авторизационные данные* для работы с GigaChat API.
@@ -49,10 +52,39 @@
-### Конфигурация
+### YandexGPT
+Быстрый старт
+
+Создайте сервисный аккаунт с ролью `ai.languageModels.user`
+Для создания аккаунта потребуется привязка карты.
+Создайте API ключ
+Идентификатор каталога (Folder ID) можно узнать пройдя по ссылке
+
+### OpenAI
+Для генерации ключа проследуйте по ссылке https://platform.openai.com/account/api-keys
+
+## Конфигурация
+
+* _Темплейт промпта_ (template, Home Assistant `template`)
+
+Системное сообщение, настраивающее модель и задающее исходное поведение.
+Значение по умолчанию является лишь примером, взятым из офицальной интеграции OpenAI Conversation
+Рекомендуется его изменить под собственные нужды.
+
+* _Модель_ (model, `string`)
+
+Модели генерации текста в рамках выбранной LLM. Каждая модель может иметь свои тарифы.
+В настоящее время выбор модели не поддерживается.
+
+* _Температура_ (temperature, `float`)
+
+Температура выборки. Значение температуры должно быть не меньше ноля. Чем выше значение, тем более случайным будет ответ модели. При значениях температуры больше двух, набор токенов в ответе модели может отличаться избыточной случайностью.
+Значение по умолчанию зависит от выбранной модели
+
+* Максимум токенов (max_tokens, int)
-* Темплейт промпта
-* Модель
+Максимальное количество токенов, которые будут использованы для создания ответов.
+В настоящее время не поддерживается, используются настройки модели по умолчанию.
## Использование в качестве диалоговой системы
Создайте и настройте новый голосовой ассистент:
diff --git a/custom_components/gigachain/__init__.py b/custom_components/gigachain/__init__.py
index 77c5de9..89a2348 100644
--- a/custom_components/gigachain/__init__.py
+++ b/custom_components/gigachain/__init__.py
@@ -10,11 +10,18 @@
)
from homeassistant.components.conversation import AgentManager, agent
from typing import Literal
-from langchain_community.chat_models import GigaChat
+from langchain_community.chat_models import GigaChat, ChatYandexGPT, ChatOpenAI
from langchain.schema import AIMessage, HumanMessage, SystemMessage
from homeassistant.util import ulid
from .const import (
DOMAIN,
+ CONF_ENGINE,
+ CONF_TEMPERATURE,
+ DEFAULT_CONF_TEMPERATURE,
+ CONF_CHAT_MODEL,
+ DEFAULT_CHAT_MODEL,
+ CONF_CHAT_MODEL,
+ CONF_FOLDER_ID,
CONF_API_KEY,
CONF_CHAT_MODEL,
DEFAULT_CHAT_MODEL,
@@ -25,9 +32,29 @@
LOGGER = logging.getLogger(__name__)
+async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
+ """Update listener."""
+ await hass.config_entries.async_reload(entry.entry_id)
+
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Initialize GigaChain."""
- client = GigaChat(credentials=entry.data[CONF_API_KEY], verify_ssl_certs=False)
+ temperature = entry.options.get(CONF_TEMPERATURE, DEFAULT_CONF_TEMPERATURE)
+ engine = entry.data.get(CONF_ENGINE) or "gigachat"
+ entry.async_on_unload(entry.add_update_listener(update_listener))
+ if engine == 'gigachat':
+ client = GigaChat(temperature=temperature,
+ model='GigaChat:latest',
+ verbose=True,
+ credentials=entry.data[CONF_API_KEY],
+ verify_ssl_certs=False)
+ elif engine == 'yandexgpt':
+ client = ChatYandexGPT(temperature=temperature,
+ api_key=entry.data[CONF_API_KEY],
+ folder_id = entry.data[CONF_FOLDER_ID])
+ else:
+ client = ChatOpenAI(model="gpt-3.5-turbo",
+ temperature=temperature,
+ openai_api_key=entry.data[CONF_API_KEY])
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = client
conversation.async_set_agent(hass, entry, GigaChatAI(hass, entry))
return True
@@ -55,7 +82,6 @@ async def async_process(
) -> agent.ConversationResult:
"""Process a sentence."""
raw_prompt = self.entry.options.get(CONF_PROMPT, DEFAULT_PROMPT)
- model = self.entry.options.get(CONF_CHAT_MODEL, DEFAULT_CHAT_MODEL)
if user_input.conversation_id in self.history:
conversation_id = user_input.conversation_id
messages = self.history[conversation_id]
@@ -70,8 +96,6 @@ async def async_process(
messages.append(HumanMessage(content=user_input.text))
client = self.hass.data[DOMAIN][self.entry.entry_id]
- client.model = model
-
res = client(messages)
messages.append(res)
self.history[conversation_id] = messages
diff --git a/custom_components/gigachain/__pycache__/__init__.cpython-312.pyc b/custom_components/gigachain/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index f53ea50..0000000
Binary files a/custom_components/gigachain/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/custom_components/gigachain/__pycache__/config_flow.cpython-312.pyc b/custom_components/gigachain/__pycache__/config_flow.cpython-312.pyc
deleted file mode 100644
index dddf035..0000000
Binary files a/custom_components/gigachain/__pycache__/config_flow.cpython-312.pyc and /dev/null differ
diff --git a/custom_components/gigachain/__pycache__/const.cpython-312.pyc b/custom_components/gigachain/__pycache__/const.cpython-312.pyc
deleted file mode 100644
index 340af6b..0000000
Binary files a/custom_components/gigachain/__pycache__/const.cpython-312.pyc and /dev/null differ
diff --git a/custom_components/gigachain/config_flow.py b/custom_components/gigachain/config_flow.py
index 56f6e81..cb51e97 100644
--- a/custom_components/gigachain/config_flow.py
+++ b/custom_components/gigachain/config_flow.py
@@ -9,26 +9,62 @@
from homeassistant.data_entry_flow import FlowResult
import types
from types import MappingProxyType
+from homeassistant.helpers import selector
from homeassistant.helpers.selector import (
- NumberSelector,
- NumberSelectorConfig,
- TemplateSelector,
+ TemplateSelector
)
+import logging
+
+LOGGER = logging.getLogger(__name__)
+
from .const import (
+ DOMAIN,
+ CONF_ENGINE,
CONF_API_KEY,
+ CONF_FOLDER_ID,
CONF_CHAT_MODEL,
+ CONF_TEMPERATURE,
+ CONF_ENGINE_OPTIONS,
CONF_PROMPT,
+ CONF_MAX_TKNS,
+ DEFAULT_CONF_TEMPERATURE,
+ DEFAULT_CONF_MAX_TKNS,
DEFAULT_CHAT_MODEL,
DEFAULT_PROMPT,
- DOMAIN
+ UNIQUE_ID,
)
-STEP_USER_DATA_SCHEMA = vol.Schema(
+STEP_USER_SCHEMA = vol.Schema(
+ {
+ vol.Required(CONF_ENGINE): selector.SelectSelector(
+ selector.SelectSelectorConfig(options=CONF_ENGINE_OPTIONS),
+ ),
+ }
+)
+
+STEP_GIGACHAT_SCHEMA = vol.Schema(
+ {
+ vol.Required(CONF_API_KEY): str
+ }
+)
+STEP_YANDEXGPT_SCHEMA = vol.Schema(
+ {
+ vol.Required(CONF_API_KEY): str,
+ vol.Required(CONF_FOLDER_ID): str
+ }
+)
+STEP_OPENAI_SCHEMA = vol.Schema(
{
vol.Required(CONF_API_KEY): str
}
)
+ENGINE_SCHEMA = {
+ "gigachat": STEP_GIGACHAT_SCHEMA,
+ "yandexgpt": STEP_YANDEXGPT_SCHEMA,
+ "openai": STEP_OPENAI_SCHEMA
+}
+
DEFAULT_OPTIONS = types.MappingProxyType(
{
CONF_PROMPT: DEFAULT_PROMPT,
@@ -45,12 +81,37 @@ async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step."""
+ if user_input is None:
+ return self.async_show_form(step_id="user",
+ data_schema=STEP_USER_SCHEMA)
+
+ engine = user_input[CONF_ENGINE]
+ unique_id = UNIQUE_ID[engine]
+ await self.async_set_unique_id(unique_id)
+ self._abort_if_unique_id_configured()
+ return self.async_show_form(
+ step_id=engine, data_schema=ENGINE_SCHEMA[engine]
+ )
+
+ async def async_step_gigachat(
+ self, user_input: dict[str, Any] | None = None) -> FlowResult:
+ return await self.common_model_async_step("gigachat", user_input)
+
+ async def async_step_yandexgpt(
+ self, user_input: dict[str, Any] | None = None) -> FlowResult:
+ return await self.common_model_async_step("yandexgpt", user_input)
+
+ async def async_step_openai(self, user_input: dict[str, Any] | None = None
+ ) -> FlowResult:
+ return await self.common_model_async_step("openai", user_input)
+
+ async def common_model_async_step(self, engine, user_input):
if user_input is None:
return self.async_show_form(
- step_id="user", data_schema=STEP_USER_DATA_SCHEMA
+ step_id=engine, data_schema=ENGINE_SCHEMA[engine]
)
-
- unique_id = "GigaChat"
+ user_input[CONF_ENGINE] = engine
+ unique_id = UNIQUE_ID[engine]
await self.async_set_unique_id(unique_id)
self._abort_if_unique_id_configured()
return self.async_create_entry(title=unique_id, data=user_input)
@@ -74,14 +135,14 @@ async def async_step_init(
) -> FlowResult:
"""Manage the options."""
if user_input is not None:
- return self.async_create_entry(title="GigaChat", data=user_input)
- schema = gigachat_config_option_schema(self.config_entry.options)
+ return self.async_create_entry(title=self.config_entry.unique_id, data=user_input)
+ schema = common_config_option_schema(self.config_entry.options)
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(schema),
)
-def gigachat_config_option_schema(options: MappingProxyType[str, Any]) -> dict:
+def common_config_option_schema(options: MappingProxyType[str, Any]) -> dict:
"""Return a schema for GigaChain completion options."""
if not options:
options = DEFAULT_OPTIONS
@@ -95,8 +156,27 @@ def gigachat_config_option_schema(options: MappingProxyType[str, Any]) -> dict:
CONF_CHAT_MODEL,
description={
# New key in HA 2023.4
- "suggested_value": options.get(CONF_CHAT_MODEL, DEFAULT_CHAT_MODEL)
+ "suggested_value": options.get(CONF_CHAT_MODEL,
+ DEFAULT_CHAT_MODEL)
},
default=DEFAULT_CHAT_MODEL,
): str,
+ vol.Optional(
+ CONF_TEMPERATURE,
+ description={
+ # New key in HA 2023.4
+ "suggested_value": options.get(CONF_TEMPERATURE,
+ DEFAULT_CONF_TEMPERATURE)
+ },
+ default=DEFAULT_CONF_TEMPERATURE,
+ ): float,
+ vol.Optional(
+ CONF_MAX_TKNS,
+ description={
+ # New key in HA 2023.4
+ "suggested_value": options.get(CONF_MAX_TKNS,
+ DEFAULT_CONF_MAX_TKNS)
+ },
+ default=DEFAULT_CONF_MAX_TKNS,
+ ): int,
}
diff --git a/custom_components/gigachain/const.py b/custom_components/gigachain/const.py
index ac1a5c3..489c299 100644
--- a/custom_components/gigachain/const.py
+++ b/custom_components/gigachain/const.py
@@ -1,7 +1,17 @@
"""Constants for the GigaChain integration."""
+from homeassistant.helpers import selector
DOMAIN = "gigachain"
+CONF_ENGINE = "engine"
+UNIQUE_ID = {"gigachat": "GigaChat", "yandexgpt": "YandexGPT", "openai": "OpenAI"}
+CONF_ENGINE_OPTIONS = [
+ selector.SelectOptionDict(value="gigachat", label="GigaChat"),
+ selector.SelectOptionDict(value="yandexgpt", label="YandexGPT"),
+ selector.SelectOptionDict(value="openai", label="OpenAI"),
+]
CONF_API_KEY = "api_key"
+CONF_FOLDER_ID = "folder_id"
+
CONF_PROMPT = "prompt"
DEFAULT_PROMPT = """Ты HAL 9000, компьютер из цикла произведений «Космическая одиссея» Артура Кларка, обладающий способностью к самообучению.
Мы находимся в умном доме под управлением системы Home Assistant.
@@ -25,3 +35,7 @@
CONF_CHAT_MODEL = "model"
#GigaChat-Plus,GigaChat-Pro,GigaChat:latest
DEFAULT_CHAT_MODEL = "GigaChat"
+CONF_TEMPERATURE = "temperature"
+DEFAULT_CONF_TEMPERATURE = "0.1"
+CONF_MAX_TKNS = "max_tokens"
+DEFAULT_CONF_MAX_TKNS = "250"
diff --git a/custom_components/gigachain/manifest.json b/custom_components/gigachain/manifest.json
index 026400e..801d38f 100644
--- a/custom_components/gigachain/manifest.json
+++ b/custom_components/gigachain/manifest.json
@@ -10,8 +10,8 @@
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/gritaro/gigachain/issues",
"requirements": [
- "gigachat",
- "langchain",
+ "gigachat==0.1.16",
+ "langchain==0.1.7",
"gigachain-community==0.0.16",
"yandexcloud==0.259.0"
],
diff --git a/custom_components/gigachain/strings.json b/custom_components/gigachain/strings.json
index cd4bc14..3499fa8 100644
--- a/custom_components/gigachain/strings.json
+++ b/custom_components/gigachain/strings.json
@@ -2,9 +2,28 @@
"config": {
"step": {
"user": {
- "title": "GigaChain configuration",
+ "title": "GigaChain configuration - select engine",
"data": {
- "auth_data": "Authorization data"
+ "engine": "LLM Engine"
+ }
+ },
+ "gigachat": {
+ "title": "GigaChat configuration",
+ "data": {
+ "api_key": "Auth data"
+ }
+ },
+ "yandexgpt": {
+ "title": "YandexGPT configuration",
+ "data": {
+ "api_key": "API Key",
+ "folder_id": "Folder ID"
+ }
+ },
+ "openai": {
+ "title": "OpenAI configuration",
+ "data": {
+ "api_key": "API Key"
}
}
},
@@ -15,9 +34,12 @@
"options": {
"step": {
"init": {
+ "title": "Model configuration",
"data": {
"prompt": "Prompt Template",
- "model": "Completion Model"
+ "model": "Completion Model",
+ "temperature": "Temperature",
+ "max_tokens": "Max Tokens"
}
}
}
diff --git a/custom_components/gigachain/translations/en.json b/custom_components/gigachain/translations/en.json
index fca91c9..3499fa8 100644
--- a/custom_components/gigachain/translations/en.json
+++ b/custom_components/gigachain/translations/en.json
@@ -2,9 +2,28 @@
"config": {
"step": {
"user": {
- "title": "GigaChain configuration",
+ "title": "GigaChain configuration - select engine",
"data": {
- "api_key": "Authorization data"
+ "engine": "LLM Engine"
+ }
+ },
+ "gigachat": {
+ "title": "GigaChat configuration",
+ "data": {
+ "api_key": "Auth data"
+ }
+ },
+ "yandexgpt": {
+ "title": "YandexGPT configuration",
+ "data": {
+ "api_key": "API Key",
+ "folder_id": "Folder ID"
+ }
+ },
+ "openai": {
+ "title": "OpenAI configuration",
+ "data": {
+ "api_key": "API Key"
}
}
},
@@ -15,9 +34,12 @@
"options": {
"step": {
"init": {
+ "title": "Model configuration",
"data": {
"prompt": "Prompt Template",
- "model": "Completion Model"
+ "model": "Completion Model",
+ "temperature": "Temperature",
+ "max_tokens": "Max Tokens"
}
}
}
diff --git a/custom_components/gigachain/translations/ru.json b/custom_components/gigachain/translations/ru.json
index 08c70c0..c29390a 100644
--- a/custom_components/gigachain/translations/ru.json
+++ b/custom_components/gigachain/translations/ru.json
@@ -2,22 +2,44 @@
"config": {
"step": {
"user": {
- "title": "GigaChain конфигурация",
+ "title": "GigaChain конфигурация - выбор LLM",
+ "data": {
+ "engine": "Большая языковая модель"
+ }
+ },
+ "gigachat": {
+ "title": "Конфигурация GigaChat",
"data": {
"api_key": "Авторизационные данные"
}
+ },
+ "yandexgpt": {
+ "title": "Конфигурация YandexGPT",
+ "data": {
+ "api_key": "API ключ",
+ "folder_id": "Folder ID"
+ }
+ },
+ "openai": {
+ "title": "Конфигурация OpenAI",
+ "data": {
+ "api_key": "API ключ"
+ }
}
},
"abort": {
- "already_configured": "Нельзя настроить более одной интеграции"
+ "already_configured": "Эта модель уже настроена"
}
},
"options": {
"step": {
"init": {
+ "title": "Конфигурация модели",
"data": {
"prompt": "Промпт темплейт",
- "model": "Модель"
+ "model": "Модель",
+ "temperature": "Температура",
+ "max_tokens": "Максимум токенов"
}
}
}