Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
gritaro committed Feb 12, 2024
0 parents commit 5bc2e3a
Show file tree
Hide file tree
Showing 15 changed files with 382 additions and 0 deletions.
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<br />
<div align="center">

<a href="https://github.com/ai-forever/gigachain">
<img src="https://raw.githubusercontent.com/gritaro/gigachain/main/static/logo.png" alt="Logo" width="80" height="80">
</a>

<h1 align="center">🦜️🔗 GigaChain (GigaChat + LangChain)</h1>
</div>

# Компонент GigaChain для Home Assistant
[![hacs_badge](https://img.shields.io/badge/HACS-Custom-orange.svg)](https://github.com/custom-components/hacs)

Компонент реализует диалоговую систему Home Assistant для использования с языковыми моделями, поддерживаемыми фреймворком GigaChain.
В настоящее время поддерживается только интеграция с LMM <a href="https://developers.sber.ru/docs/ru/gigachat/overview">GigaChat</a> (русскоязычная нейросеть от Сбера)

## Установка
Устанавливается как и любая HACS интеграция.

### Необходимые требования
Для использования интеграции вам понадобится Home Assistant с установленным [HACS](https://hacs.xyz/)

### Установка с использованием HACS
Найдите GigaChain в магазине HACS. Если интеграция не находится в магазине HACS, вы можете [добавить этот url как пользовательский репозиторий HACS](https://hacs.xyz/docs/faq/custom_repositories).

[![hacs_badge](https://img.shields.io/badge/HACS-Custom-41BDF5.svg?style=for-the-badge)](https://github.com/gritaro/gigachain)

Перезапустите Home Assistant.

## Добавление интеграции

[![Open your Home Assistant instance and start setting up a new integration of a specific brand.](https://my.home-assistant.io/badges/brand.svg)](https://my.home-assistant.io/redirect/brand/?brand=+GigaChain)

После добавления настройте интеграцию.

## Настройки

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

> [!NOTE]
> О том как получить авторизационные данные для доступа к GigaChat читайте в [официальной документации](https://developers.sber.ru/docs/ru/gigachat/api/integration).
>
<img src="https://raw.githubusercontent.com/gritaro/gigachain/main/static/auth_data.jpeg" alt="Authorization data" width="40%">

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

* Темплейт промпта
* Модель

## Использование в качестве диалоговой системы
Создайте и настройте новый голосовой ассистент:

<img src="https://raw.githubusercontent.com/gritaro/gigachain/main/static/voice-assistant.jpeg" alt="Voice Assistant">
104 changes: 104 additions & 0 deletions custom_components/gigachain/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""The GigaChain integration."""
from __future__ import annotations
from abc import abstractmethod
import re
from homeassistant.components import conversation
from homeassistant.components.conversation.const import HOME_ASSISTANT_AGENT
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import MATCH_ALL
from homeassistant.core import HomeAssistant
from homeassistant.helpers import (
config_validation as cv,
intent,
template,
)
from homeassistant.components.conversation import AgentManager, agent
from typing import Literal
from langchain.chat_models import GigaChat
from langchain.prompts.chat import (
AIMessagePromptTemplate,
ChatPromptTemplate,
HumanMessagePromptTemplate,
SystemMessagePromptTemplate,
)
from langchain.schema import AIMessage, HumanMessage, SystemMessage
from homeassistant.util import ulid
from .const import (
DOMAIN,
CONF_AUTH_DATA,
CONF_CHAT_MODEL,
DEFAULT_CHAT_MODEL,
CONF_PROMPT,
DEFAULT_PROMPT
)
import logging

LOGGER = logging.getLogger(__name__)

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Initialize GigaChain."""
client = GigaChat(credentials=entry.data[CONF_AUTH_DATA], verify_ssl_certs=False)
models = client.get_models()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = client
conversation.async_set_agent(hass, entry, GigaChatAI(hass, entry))
return True

async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload GigaChain."""
hass.data[DOMAIN].pop(entry.entry_id)
conversation.async_unset_agent(hass, entry)
return True

class GigaChatAI(conversation.AbstractConversationAgent):
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Initialize the agent."""
self.hass = hass
self.entry = entry
self.history: dict[str, list[dict]] = {}

@property
def supported_languages(self) -> list[str] | Literal["*"]:
"""Return a list of supported languages."""
return MATCH_ALL

async def async_process(
self, user_input: agent.ConversationInput
) -> 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]
else:
conversation_id = ulid.ulid_now()
prompt = self._async_generate_prompt(raw_prompt)
messages = [
SystemMessage(
content=prompt
)
]

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

response = intent.IntentResponse(language=user_input.language)
response.async_set_speech(res.content)
return agent.ConversationResult(
conversation_id=conversation_id,
response=response
)

def _async_generate_prompt(self, raw_prompt: str) -> str:
"""Generate a prompt for the user."""
return template.Template(raw_prompt, self.hass).async_render(
{
"ha_name": self.hass.config.location_name,
},
parse_result=False,
)
Binary file not shown.
Binary file not shown.
Binary file not shown.
102 changes: 102 additions & 0 deletions custom_components/gigachain/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"""Config flow for GigaChain integration."""
from __future__ import annotations

from typing import Any

import voluptuous as vol

from homeassistant import config_entries
from homeassistant.data_entry_flow import FlowResult
import types
from types import MappingProxyType
from homeassistant.helpers.selector import (
NumberSelector,
NumberSelectorConfig,
TemplateSelector,
)
from .const import (
CONF_AUTH_DATA,
CONF_CHAT_MODEL,
CONF_PROMPT,
DEFAULT_CHAT_MODEL,
DEFAULT_PROMPT,
DOMAIN
)

STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_AUTH_DATA): str
}
)

DEFAULT_OPTIONS = types.MappingProxyType(
{
CONF_PROMPT: DEFAULT_PROMPT,
CONF_CHAT_MODEL: DEFAULT_CHAT_MODEL,
}
)

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for GigaChain."""

VERSION = 1

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_DATA_SCHEMA
)

unique_id = "GigaChat"
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)

@staticmethod
def async_get_options_flow(
config_entry: config_entries.ConfigEntry,
) -> config_entries.OptionsFlow:
"""Create the options flow."""
return OptionsFlow(config_entry)

class OptionsFlow(config_entries.OptionsFlow):
"""GigaChain config flow options handler."""

def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
"""Initialize options flow."""
self.config_entry = config_entry

async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> 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_show_form(
step_id="init",
data_schema=vol.Schema(schema),
)

def gigachat_config_option_schema(options: MappingProxyType[str, Any]) -> dict:
"""Return a schema for GigaChain completion options."""
if not options:
options = DEFAULT_OPTIONS
return {
vol.Optional(
CONF_PROMPT,
description={"suggested_value": options[CONF_PROMPT]},
default=DEFAULT_PROMPT,
): TemplateSelector(),
vol.Optional(
CONF_CHAT_MODEL,
description={
# New key in HA 2023.4
"suggested_value": options.get(CONF_CHAT_MODEL, DEFAULT_CHAT_MODEL)
},
default=DEFAULT_CHAT_MODEL,
): str,
}
27 changes: 27 additions & 0 deletions custom_components/gigachain/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Constants for the GigaChain integration."""

DOMAIN = "gigachain"
CONF_AUTH_DATA = "auth_data"
CONF_PROMPT = "prompt"
DEFAULT_PROMPT = """Ты HAL 9000, компьютер из цикла произведений «Космическая одиссея» Артура Кларка, обладающий способностью к самообучению.
Мы находимся в умном доме под управлением системы Home Assistant.
В доме есть следующие помещения и устройства:
{%- for area in areas() %}
{%- set area_info = namespace(printed=false) %}
{%- for device in area_devices(area) -%}
{%- if not device_attr(device, "disabled_by") and not device_attr(device, "entry_type") and device_attr(device, "name") %}
{%- if not area_info.printed %}
{{ area_name(area) }}:
{%- set area_info.printed = true %}
{%- endif %}
- {{ device_attr(device, "name") }}{% if device_attr(device, "model") and (device_attr(device, "model") | string) not in (device_attr(device, "name") | string) %} ({{ device_attr(device, "model") }}){% endif %}
{%- endif %}
{%- endfor %}
{%- endfor %}
Когда отвечаешь, обращайся к собеседнику по имени Дэйв.
"""

CONF_CHAT_MODEL = "model"
#GigaChat-Plus,GigaChat-Pro,GigaChat:latest
DEFAULT_CHAT_MODEL = "GigaChat"
15 changes: 15 additions & 0 deletions custom_components/gigachain/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"domain": "gigachain",
"name": "GigaChain",
"codeowners": [
"@gritaro"
],
"config_flow": true,
"documentation": "https://github.com/gritaro/gigachain",
"homekit": {},
"iot_class": "cloud_polling",
"version":"0.1.0",
"integration_type": "service",
"dependencies": ["conversation"],
"requirements": ["gigachain"]
}
25 changes: 25 additions & 0 deletions custom_components/gigachain/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"config": {
"step": {
"user": {
"title": "GigaChain configuration",
"data": {
"auth_data": "Authorization data"
}
}
},
"abort": {
"already_configured": "Already configured"
}
},
"options": {
"step": {
"init": {
"data": {
"prompt": "Prompt Template",
"model": "Completion Model"
}
}
}
}
}
25 changes: 25 additions & 0 deletions custom_components/gigachain/translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"config": {
"step": {
"user": {
"title": "GigaChain configuration",
"data": {
"auth_data": "Authorization data"
}
}
},
"abort": {
"already_configured": "Already configured"
}
},
"options": {
"step": {
"init": {
"data": {
"prompt": "Prompt Template",
"model": "Completion Model"
}
}
}
}
}
25 changes: 25 additions & 0 deletions custom_components/gigachain/translations/ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"config": {
"step": {
"user": {
"title": "GigaChain конфигурация",
"data": {
"auth_data": "Авторизационные данные"
}
}
},
"abort": {
"already_configured": "Нельзя настроить более одной интеграции"
}
},
"options": {
"step": {
"init": {
"data": {
"prompt": "Промпт темплейт",
"model": "Модель"
}
}
}
}
}
4 changes: 4 additions & 0 deletions hacs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "GigaChain",
"render_readme" : true
}
Binary file added static/auth_data.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added static/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added static/voice-assistant.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 5bc2e3a

Please sign in to comment.