Skip to content

Commit

Permalink
📦 NEW: Home Assistant Conversation Integration for Meeseeks
Browse files Browse the repository at this point in the history
  • Loading branch information
bearlike committed May 16, 2024
1 parent d7a7148 commit cdb10dc
Show file tree
Hide file tree
Showing 16 changed files with 772 additions and 10 deletions.
26 changes: 19 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
<p align="center">
<a href="https://github.com/bearlike/Personal-Assistant/wiki"><img alt="Wiki" src="https://img.shields.io/badge/GitHub-Wiki-blue?style=for-the-badge&logo=github"></a>
<a href="https://github.com/features/actions"><img alt="GitHub Actions Workflow Status" src="https://img.shields.io/github/actions/workflow/status/bearlike/Personal-Assistant/docker-buildx.yml?style=for-the-badge&"></a>
<a href="https://github.com/bearlike/Personal-Assistant/pkgs/container/meeseeks-chat"><img src="https://img.shields.io/badge/ghcr.io-bearlike/meeseeks&#x2212;chat:latest-blue?style=for-the-badge&logo=docker&logoColor=white" alt="Docker Image"></a>
<a href="https://github.com/bearlike/Personal-Assistant/releases"><img src="https://img.shields.io/github/v/release/bearlike/Personal-Assistant?style=for-the-badge&" alt="GitHub Release"></a>
<a href="https://github.com/bearlike/Personal-Assistant/pkgs/container/meeseeks-chat"><img src="https://img.shields.io/badge/ghcr.io-bearlike/meeseeks--chat:latest-blue?style=for-the-badge&logo=docker&logoColor=white" alt="Docker Image"></a>
<a href="https://github.com/bearlike/Personal-Assistant/pkgs/container/meeseeks-api"><img src="https://img.shields.io/badge/ghcr.io-bearlike/meeseeks--api:latest-blue?style=for-the-badge&logo=docker&logoColor=white" alt="Docker Image"></a>
</p>


Expand All @@ -26,30 +27,41 @@ Meeseeks is an innovative AI assistant built on a multi-agent large language mod

| Completed | In-Progress | Planned | Scoping |
| :-------: | :---------: | :-----: | :-----: |
| | 🚧 | 📅 | 🧐 |
|| 🚧 | 📅 | 🧐 |

</details>

# Features 🔥
> [!NOTE]
> Visit [**Features - Wiki**](https://github.com/bearlike/Personal-Assistant/wiki/Features) for detailed information on tools and integration capabilities.
<table align="center">
<tr>
<th>Answer questions and interpret sensor information</th>
<th>Control devices and entities</th>
</tr>
<tr>
<td align="center"><img src="docs/screenshot_ha_assist_1.png" alt="Screenshot" height="512px"></td>
<td align="center"><img src="docs/screenshot_ha_assist_2.png" alt="Screenshot" height="512px"></td>
</tr>
</table>

- (✅) [LangFuse](https://github.com/langfuse/langfuse) integrations to accurate log and monitor chains.
- (✅) Use natural language to interact with integrations and tools.
- (🚧) Simple REST API interface for 3rd party tools to interface with Meeseeks.
- () Simple REST API interface for 3rd party tools to interface with Meeseeks.
- (✅) Handles complex user queries by breaking them into actionable steps, executing these steps, and then summarizing on the results.
- (🚧) Custom [Home Assistant Conversation Integration](https://www.home-assistant.io/integrations/conversation/) to allow voice assistance via [**HA Assist**](https://www.home-assistant.io/voice_control/).
- () Custom [Home Assistant Conversation Integration](https://www.home-assistant.io/integrations/conversation/) to allow voice assistance via [**HA Assist**](https://www.home-assistant.io/voice_control/).
- (✅) A chat Interface using `streamlit` that shows the action plan, user types, and response from the LLM.

## Extras 👽
Optional feature that users can choose to install to further optimize their experience.
- (🧐) **`Quality`** Use [CRITIC reflection framework](https://arxiv.org/pdf/2305.11738) to reflect on a response to a task/query using external tools via [`[^]`](https://llamahub.ai/l/agent/llama-index-agent-introspective).
- (📅) **`Privacy`** Integrate with [microsoft/presidio](https://github.com/microsoft/presidio) for customizable PII de-identification.
- (📅) **`Quality`** Use [CRITIC reflection framework](https://arxiv.org/pdf/2305.11738) to reflect on a response to a task/query using external tools via [`[^]`](https://llamahub.ai/l/agent/llama-index-agent-introspective).
- (🚧) **`Privacy`** Integrate with [microsoft/presidio](https://github.com/microsoft/presidio) for customizable PII de-identification.

## Integrations 📦
- (✅) [Home Assistant](https://github.com/home-assistant/core)
- (🚧) Google Calendar
- (📅) Google Search, Search recent ArXiv papers and summaries, Yahoo Finance, Yelp
- (🚧) Google Search, Search recent ArXiv papers and summaries, Yahoo Finance, Yelp
- (🧐) Android Debugging Shell

## Installating and Running Meeseeks
Expand Down
Binary file added docs/screenshot_ha_assist_1.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 docs/screenshot_ha_assist_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 8 additions & 1 deletion meeseeks-api/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# meeseeks-api
# Meeseeks API Server
<p align="center">
<a href="https://github.com/bearlike/Personal-Assistant/wiki"><img alt="Wiki" src="https://img.shields.io/badge/GitHub-Wiki-blue?style=for-the-badge&logo=github"></a>
<a href="https://github.com/bearlike/Personal-Assistant/pkgs/container/meeseeks-chat"><img src="https://img.shields.io/badge/ghcr.io-bearlike/meeseeks&#x2212;api:latest-blue?style=for-the-badge&logo=docker&logoColor=white" alt="Docker Image"></a>
<a href="https://github.com/bearlike/Personal-Assistant/releases"><img src="https://img.shields.io/github/v/release/bearlike/Personal-Assistant?style=for-the-badge&" alt="GitHub Release"></a>
</p>

- REST API Engine wrapped around the meeseeks-core.
- No components are explicitly tested for safety or security. Use with caution in a production environment.
- For more information, such as installation, please check out the [Wiki](https://github.com/bearlike/Personal-Assistant/wiki).


[Link to GitHub Repository](https://github.com/bearlike/Personal-Assistant)
17 changes: 15 additions & 2 deletions meeseeks-chat/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# meeseeks-chat
# Meeseeks - Chat Interface
<p align="center">
<a href="https://github.com/bearlike/Personal-Assistant/wiki"><img alt="Wiki" src="https://img.shields.io/badge/GitHub-Wiki-blue?style=for-the-badge&logo=github"></a>
<a href="https://github.com/bearlike/Personal-Assistant/pkgs/container/meeseeks-chat"><img src="https://img.shields.io/badge/ghcr.io-bearlike/meeseeks&#x2212;chat:latest-blue?style=for-the-badge&logo=docker&logoColor=white" alt="Docker Image"></a>
<a href="https://github.com/bearlike/Personal-Assistant/releases"><img src="https://img.shields.io/github/v/release/bearlike/Personal-Assistant?style=for-the-badge&" alt="GitHub Release"></a>
</p>


<p align="center">
<img src="../docs/screenshot_chat_app_1.png" alt="Screenshot of Meeseks WebUI" height="512px">
</p>


- Chat Interface wrapped around the meeseeks-core. Powered by Streamlit.
- For more information, such as installation, please check out the [Wiki](https://github.com/bearlike/Personal-Assistant/wiki).

Chat Interface wrapped around the meeseeks-core. Powered by Streamlit.

[Link to GitHub](https://github.com/bearlike/Personal-Assistant)
25 changes: 25 additions & 0 deletions meeseeks_ha_conversation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Home Assistant Conversation Integration for Meeseeks 🚀

<p align="center">
<a href="https://github.com/bearlike/Personal-Assistant/wiki"><img alt="Wiki" src="https://img.shields.io/badge/GitHub-Wiki-blue?style=for-the-badge&logo=github"></a>
<a href="https://github.com/bearlike/Personal-Assistant/releases"><img src="https://img.shields.io/github/v/release/bearlike/Personal-Assistant?style=for-the-badge&" alt="GitHub Release"></a>
</p>


<table align="center">
<tr>
<th>Answer questions and interpret sensor information</th>
<th>Control devices and entities</th>
</tr>
<tr>
<td align="center"><img src="../docs/screenshot_ha_assist_1.png" alt="Screenshot" height="512px"></td>
<td align="center"><img src="../docs/screenshot_ha_assist_2.png" alt="Screenshot" height="512px"></td>
</tr>
</table>

- Home Assistant Conversation Integration for Meeseeks. Can be used with HA Assist ⭐.
- Wrapped around the REST API Engine for Meeseeks. 100% coverage of Meeseeks API.
- No components are explicitly tested for safety or security. Use with caution in a production environment.
- For more information, such as installation, please check out the [Wiki](https://github.com/bearlike/Personal-Assistant/wiki).

[Link to GitHub Repository](https://github.com/bearlike/Personal-Assistant)
168 changes: 168 additions & 0 deletions meeseeks_ha_conversation/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
"""Custom integration to integrate meeseeks_conversation with Home Assistant.
For more details about this integration, please refer to
https://github.com/bearlike/personal-Assistant/
"""
from __future__ import annotations

from typing import Literal

from homeassistant.components import conversation
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import MATCH_ALL
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
from homeassistant.helpers import intent, template
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util import ulid

from .api import MeeseeksApiClient
from .const import (
DOMAIN, LOGGER,
CONF_BASE_URL,
CONF_TIMEOUT,
DEFAULT_TIMEOUT,
)
# User-defined imports
from .coordinator import MeeseeksDataUpdateCoordinator
from .exceptions import (
ApiClientError,
ApiCommError,
ApiJsonError,
ApiTimeoutError
)
# from .helpers import get_exposed_entities

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Meeseeks conversation using UI."""
# https://developers.home-assistant.io/docs/config_entries_index/#setting-up-an-entry
hass.data.setdefault(DOMAIN, {})
client = MeeseeksApiClient(
base_url=entry.data[CONF_BASE_URL],
timeout=entry.options.get(CONF_TIMEOUT, DEFAULT_TIMEOUT),
session=async_get_clientsession(hass),
)

hass.data[DOMAIN][entry.entry_id] = coordinator = MeeseeksDataUpdateCoordinator(
hass,
client,
)
# https://developers.home-assistant.io/docs/integration_fetching_data#coordinated-single-api-poll-for-data-for-all-entities
await coordinator.async_config_entry_first_refresh()

try:
# TODO: Heartbeat check is not implemented but it is still wrapped.
response = await client.async_get_heartbeat()
if not response:
raise ApiClientError("Invalid Meeseeks server")
except ApiClientError as err:
raise ConfigEntryNotReady(err) from err

entry.async_on_unload(entry.add_update_listener(async_reload_entry))

conversation.async_set_agent(
hass, entry, MeeseeksAgent(hass, entry, client))
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload Meeseeks conversation."""
conversation.async_unset_agent(hass, entry)
return True


async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Reload Meeseeks conversation."""
await async_unload_entry(hass, entry)
await async_setup_entry(hass, entry)


class MeeseeksAgent(conversation.AbstractConversationAgent):
"""Meeseeks conversation agent."""

def __init__(self, hass: HomeAssistant, entry: ConfigEntry, client: MeeseeksApiClient) -> None:
"""Initialize the agent."""
self.hass = hass
self.entry = entry
self.client = client
self.history: dict[str, 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: conversation.ConversationInput
) -> conversation.ConversationResult:
"""Process a sentence."""
# * If needeed in the future, uncomment the following lines
# raw_system_prompt = self.entry.options.get(
# CONF_PROMPT_SYSTEM, DEFAULT_PROMPT_SYSTEM)
# exposed_entities = get_exposed_entities(self.hass)
# ! Currently, history is not used but still implemented for future use
if user_input.conversation_id in self.history:
conversation_id = user_input.conversation_id
messages = self.history[conversation_id]
else:
conversation_id = ulid.ulid()
system_prompt = ""
messages = {
"system": system_prompt,
"context": None,
}

messages["prompt"] = user_input.text

try:
response = await self.query(messages)
except HomeAssistantError as err:
LOGGER.error("Something went wrong: %s", err)
intent_response = intent.IntentResponse(
language=user_input.language)
intent_response.async_set_error(
intent.IntentResponseErrorCode.UNKNOWN,
"Something went wrong, please check the logs for more information.",
)
return conversation.ConversationResult(
response=intent_response, conversation_id=conversation_id
)

messages["context"] = response["context"]
self.history[conversation_id] = messages

intent_response = intent.IntentResponse(language=user_input.language)
intent_response.async_set_speech(response["response"])
return conversation.ConversationResult(
response=intent_response, conversation_id=conversation_id
)

def _async_generate_prompt(self, raw_prompt: str, exposed_entities) -> str:
"""Generate a prompt for the user."""
return template.Template(raw_prompt, self.hass).async_render(
{
"ha_name": self.hass.config.location_name,
"exposed_entities": exposed_entities,
},
parse_result=False,
)

async def query(
self,
messages
):
"""Process a sentence."""
# model = self.entry.options.get(CONF_MODEL, DEFAULT_MODEL)
# LOGGER.debug("Prompt for %s: %s", model, messages["prompt"])

# TODO: $context, and $system are not used but still implemented for
# future use
# * Generator
result = await self.client.async_generate({
"context": messages["context"],
"system": messages["system"],
"prompt": messages["prompt"],
})
response: str = result["task_result"]
LOGGER.debug("Response %s", response)
return result
101 changes: 101 additions & 0 deletions meeseeks_ha_conversation/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
""" Meeseeks API Client. """
from __future__ import annotations

import aiohttp
import async_timeout
import json

# User-defined imports
from .exceptions import (
ApiClientError,
ApiCommError,
ApiJsonError,
ApiTimeoutError
)
from .const import LOGGER


class MeeseeksApiClient:
"""Meeseeks API Client."""

def __init__(
self,
base_url: str,
timeout: int,
session: aiohttp.ClientSession,
) -> None:
"""Sample API Client."""
self._base_url = base_url.rstrip("/")
self._api_key = 'msk-strong-password'
self.timeout = timeout
self._session = session

async def async_get_heartbeat(self) -> bool:
"""Get heartbeat from the API."""
# TODO: Implement a heartbeat check
return True

async def async_get_models(self) -> any:
"""Get models from the API."""
# TODO: This is monkey-patched for now
response_data = {
"models": [
{
"name": "meeseeks",
"modified_at": "2023-11-01T00:00:00.000000000-04:00",
"size": 0,
"digest": None
}
]
}
return json.dumps(response_data)

async def async_generate(self, data: dict | None = None,) -> any:
"""Generate a completion from the API."""
url_query = f"{self._base_url}/api/query"
data_custom = {
'query': str(data["prompt"]).strip(),
}
# Pass headers as None to use the default headers
return await self._meeseeks_api_wrapper(
method="post",
url=url_query,
data=data_custom,
headers=None,
)

async def _meeseeks_api_wrapper(
self,
method: str,
url: str,
data: dict | None = None,
headers: dict | None = None,
decode_json: bool = True,
) -> any:
"""Get information from the API."""
if headers is None:
headers = {
'accept': 'application/json',
'X-API-KEY': self._api_key,
'Content-Type': 'application/json',
}
async with async_timeout.timeout(self.timeout):
response = await self._session.request(
method=method,
url=url,
headers=headers,
json=data,
)
response.raise_for_status()

if decode_json:
response_data = await response.json()
if response.status == 404:
raise ApiJsonError(response_data["error"])
LOGGER.debug(f"Response data: {response_data}")
response_data["response"] = response_data["task_result"]
response_data["context"] = response_data["task_result"]
return response_data
else:
LOGGER.debug("Fallback to text response")
return await response.text()
Loading

0 comments on commit cdb10dc

Please sign in to comment.