Skip to content

Commit

Permalink
Merge pull request #1 from gritaro/rc-0.1.2
Browse files Browse the repository at this point in the history
Add support for multiple LLM engines
  • Loading branch information
gritaro authored Feb 14, 2024
2 parents 84f92b4 + df6197a commit 511cdbd
Show file tree
Hide file tree
Showing 12 changed files with 251 additions and 36 deletions.
5 changes: 2 additions & 3 deletions .idea/.gitignore → .gitignore
Original file line number Diff line number Diff line change
@@ -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
*.ipr
42 changes: 37 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href="https://developers.sber.ru/docs/ru/gigachat/overview">GigaChat</a> (русскоязычная нейросеть от Сбера)
В настоящее время поддерживаются интеграции с LMM:
* [GigaChat](#GigaChat) (<a href="https://developers.sber.ru/docs/ru/gigachat/overview">русскоязычная нейросеть от Сбера</a>)
* [YandexGPT](#YandexGPT)
* [OpenAI](#OpenAI) ака ChatGPT (не тестируется)

## Установка
Устанавливается как и любая HACS интеграция.
Expand All @@ -39,7 +42,7 @@
После добавления настройте интеграцию.

## Настройки

### GigaChat
### Авторизация запросов к GigaChat
Для авторизации запросов к GigaChat вам понадобится получить *авторизационные данные* для работы с GigaChat API.

Expand All @@ -49,10 +52,39 @@
<img src="https://raw.githubusercontent.com/gritaro/gigachain/main/static/auth_data.jpeg" alt="Authorization data" width="40%">

### Конфигурация
### YandexGPT
<a href="https://cloud.yandex.ru/ru/docs/yandexgpt/quickstart">Быстрый старт</a>

Создайте <a href="https://cloud.yandex.com/en/docs/iam/operations/sa/create">сервисный аккаунт</a> с ролью `ai.languageModels.user`
Для создания аккаунта потребуется привязка карты.
Создайте <a href="https://cloud.yandex.com/en/docs/iam/operations/api-key/create">API ключ</a>
Идентификатор каталога (Folder ID) можно узнать пройдя по <a href="https://console.cloud.yandex.com/folders">ссылке</a>

### OpenAI
Для генерации ключа проследуйте по ссылке https://platform.openai.com/account/api-keys

## Конфигурация

* _Темплейт промпта_ (template, Home Assistant <a href=https://www.home-assistant.io/docs/configuration/templating/>`template`</a>)

Системное сообщение, настраивающее модель и задающее исходное поведение.
Значение по умолчанию является лишь примером, взятым из офицальной интеграции <a href="https://github.com/home-assistant/core/blob/dev/homeassistant/components/openai_conversation/const.py#L5">OpenAI Conversation</a>
Рекомендуется его изменить под собственные нужды.

* _Модель_ (model, `string`)

Модели генерации текста в рамках выбранной LLM. Каждая модель может иметь свои тарифы.
В настоящее время выбор модели не поддерживается.

* _Температура_ (temperature, `float`)

Температура выборки. Значение температуры должно быть не меньше ноля. Чем выше значение, тем более случайным будет ответ модели. При значениях температуры больше двух, набор токенов в ответе модели может отличаться избыточной случайностью.
Значение по умолчанию зависит от выбранной модели

* Максимум токенов (max_tokens, int)

* Темплейт промпта
* Модель
Максимальное количество токенов, которые будут использованы для создания ответов.
В настоящее время не поддерживается, используются настройки модели по умолчанию.

## Использование в качестве диалоговой системы
Создайте и настройте новый голосовой ассистент:
Expand Down
34 changes: 29 additions & 5 deletions custom_components/gigachain/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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]
Expand All @@ -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
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
104 changes: 92 additions & 12 deletions custom_components/gigachain/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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,
}
14 changes: 14 additions & 0 deletions custom_components/gigachain/const.py
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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"
4 changes: 2 additions & 2 deletions custom_components/gigachain/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
],
Expand Down
28 changes: 25 additions & 3 deletions custom_components/gigachain/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
},
Expand All @@ -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"
}
}
}
Expand Down
Loading

0 comments on commit 511cdbd

Please sign in to comment.