From 7b45d1930823db9cad434fcd150fb321783ddad7 Mon Sep 17 00:00:00 2001 From: Pierre Wessman <4029607+pierrewessman@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:22:58 +0100 Subject: [PATCH] init --- .gitignore | 13 + agent-api/.cache | 1 + agent-api/agent.py | 161 ++++++++++ agent-api/backend.py | 127 ++++++++ agent-api/config.default.yml | 25 ++ agent-api/db.py | 32 ++ agent-api/db.sql | 7 + agent-api/docker-compose.yml | 15 + agent-api/frontend.py | 41 +++ agent-api/install.md | 3 + agent-api/llama_server.py | 37 +++ agent-api/llm.py | 252 ++++++++++++++++ agent-api/plugins/__init__.py | 0 agent-api/plugins/base_plugin.py | 68 +++++ agent-api/plugins/calendar/plugin.py | 26 ++ agent-api/plugins/homeassistant.py | 51 ++++ agent-api/plugins/lights/plugin.py | 73 +++++ agent-api/plugins/music/.cache | 1 + agent-api/plugins/music/plugin.py | 59 ++++ agent-api/plugins/readme.md | 3 + agent-api/plugins/smhi/plugin.py | 74 +++++ agent-api/plugins/smhi/requirements.txt | 1 + agent-api/plugins/todo/plugin.py | 107 +++++++ agent-api/plugins/vasttrafik/plugin.py | 98 ++++++ agent-api/plugins/vasttrafik/requirements.txt | 1 + agent-api/pyproject.toml | 2 + agent-api/readme.md | 21 ++ agent-api/requirements.txt | 11 + agent-api/static/tada.wav | Bin 0 -> 195884 bytes agent-api/templates/ask.en.j2 | 12 + agent-api/templates/knowledge.en.j2 | 5 + esphome-va-bridge/audio.py | 203 +++++++++++++ esphome-va-bridge/esp-working.log | 113 +++++++ esphome-va-bridge/esphome_server.py | 283 ++++++++++++++++++ esphome-va-bridge/llm_api.py | 13 + esphome-va-bridge/log_reader.py | 62 ++++ external/home-assistant-voice-pe | 1 + pyproject.toml | 2 + 38 files changed, 2004 insertions(+) create mode 100644 .gitignore create mode 100644 agent-api/.cache create mode 100644 agent-api/agent.py create mode 100644 agent-api/backend.py create mode 100644 agent-api/config.default.yml create mode 100644 agent-api/db.py create mode 100644 agent-api/db.sql create mode 100644 agent-api/docker-compose.yml create mode 100644 agent-api/frontend.py create mode 100644 agent-api/install.md create mode 100644 agent-api/llama_server.py create mode 100644 agent-api/llm.py create mode 100644 agent-api/plugins/__init__.py create mode 100644 agent-api/plugins/base_plugin.py create mode 100644 agent-api/plugins/calendar/plugin.py create mode 100644 agent-api/plugins/homeassistant.py create mode 100644 agent-api/plugins/lights/plugin.py create mode 100644 agent-api/plugins/music/.cache create mode 100644 agent-api/plugins/music/plugin.py create mode 100644 agent-api/plugins/readme.md create mode 100644 agent-api/plugins/smhi/plugin.py create mode 100644 agent-api/plugins/smhi/requirements.txt create mode 100644 agent-api/plugins/todo/plugin.py create mode 100644 agent-api/plugins/vasttrafik/plugin.py create mode 100644 agent-api/plugins/vasttrafik/requirements.txt create mode 100644 agent-api/pyproject.toml create mode 100644 agent-api/readme.md create mode 100644 agent-api/requirements.txt create mode 100644 agent-api/static/tada.wav create mode 100644 agent-api/templates/ask.en.j2 create mode 100644 agent-api/templates/knowledge.en.j2 create mode 100644 esphome-va-bridge/audio.py create mode 100644 esphome-va-bridge/esp-working.log create mode 100644 esphome-va-bridge/esphome_server.py create mode 100644 esphome-va-bridge/llm_api.py create mode 100644 esphome-va-bridge/log_reader.py create mode 160000 external/home-assistant-voice-pe create mode 100644 pyproject.toml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0a6a95f --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +config.yml +debug.py +notebook.ipynb +.vscode/ +secrets.yaml \ No newline at end of file diff --git a/agent-api/.cache b/agent-api/.cache new file mode 100644 index 0000000..01bce28 --- /dev/null +++ b/agent-api/.cache @@ -0,0 +1 @@ +{"access_token": "BQDSGB6rh1-6-bc7wxFTZ2_Idjr6W-xLSZEMizMF6wNXew6_2M4DyWeSaVovZZLPdVv0aQOU8LqIQqQSNr5OwNXDpRk6icpcC6HKYGaZ7I_U9PRWbIOWE-QUakMcAmMFTWwO3FJEBRDYARR43cWTeRn8w9MHG48V5awv2A1447yz8kd4cUk8lAOTbcCeMnpxjJJQqqaigIWAy8NF7ghJXr4dibKj2JtTP5yECA", "token_type": "Bearer", "expires_in": 3600, "scope": "user-library-read", "expires_at": 1736781939, "refresh_token": "AQBaAMOoUVzcichmSIgDC4_CvkXXNNwfjnSbwpypzuaaCQqcQw-_2VP6Rkayp08Y-5gSEbEGVWc0-1CP4-JmQI2kQ_0UmJT5g2G3LvOjufSYowzXRxeedIOWcwbRvjSA1XI"} \ No newline at end of file diff --git a/agent-api/agent.py b/agent-api/agent.py new file mode 100644 index 0000000..fc64f63 --- /dev/null +++ b/agent-api/agent.py @@ -0,0 +1,161 @@ +import datetime +import json +from jinja2 import Template +import logging +from typing import Union + +from db import Database +from llm import LLM + + +def relative_to_actual_dates(text: str): + from dateparser.search import search_dates + + matches = search_dates(text, languages=["en", "se"]) + + if matches: + for match in matches: + text = text.replace(match[0], match[1].strftime("%Y-%m-%d")) + return text + + +class Agent: + def __init__(self, config: dict) -> None: + self.config = config + self.db = Database("rag") + self.llm = LLM(config=self.config["llm"]) + + def flush(self): + """ + Flushes the agents knowledge. + """ + logging.info("Truncating database") + self.db.cur.execute("TRUNCATE TABLE items") + self.db.conn.commit() + + def insert(self, text): + """ + Append knowledge. + """ + logging.info(f"Inserting item into embedding table ({text}).") + + #logging.info(f"\tReplacing relative dates with actual dates.") + #text = relative_to_actual_dates(text) + #logging.info(f"\tDone. text: {text}") + + vector = self.llm.embedding(text) + vector_padded = vector + ([0]*(2048-len(vector))) + + self.db.cur.execute( + "INSERT INTO items (text, embedding, date_added) VALUES(%s, %s, %s)", + (text, vector_padded, datetime.datetime.now().strftime("%Y-%m-%d")), + ) + self.db.conn.commit() + + def keywords(self, query: str, n_keywords: int = 5): + """ + Suggest keywords related to a query. + """ + sys_msg = "You are a helpful assistant. Make your response as concise as possible, with no introduction or background at the start." + prompt = ( + f"Provide a json list of {n_keywords} words or nouns that represents in a vector database query for the following prompt: {query}." + + f"Do not add any context or text other than the json list of {n_keywords} words." + ) + response = self.llm.query(prompt, system_msg=sys_msg) + keywords = json.loads(response) + return keywords + + def retrieve(self, query: str, n_retrieve=10, n_rerank=5): + """ + Retrieve relevant knowledge. + + Parameters: + - n_retrieve (int): How many notes to retrieve. + - n_rerank (int): How many notes to keep after reranking. + """ + logging.info(f"Retrieving knowledge from database using the query: '{query}'") + + logging.debug(f"Using embedding model on '{query}'.") + vector_keywords = self.llm.embedding(query) + logging.debug("Embedding received. len(vector_keywords): %s", len(vector_keywords)) + + logging.debug("Querying database") + vector_padded = vector_keywords + ([0]*(2048-len(vector_keywords))) + self.db.cur.execute( + f"SELECT text, date_added FROM items ORDER BY embedding <-> %s::vector LIMIT {n_retrieve}", + (vector_padded,), + ) + db_response = self.db.cur.fetchall() + knowledge_arr = [{"text": row[0], "date_added": row[1]} for row in db_response] + logging.debug("Database returned: ") + logging.debug(knowledge_arr) + + if n_retrieve > n_rerank: + logging.info("Reranking the results") + reranked_texts = self.llm.rerank(query, [note["text"] for note in knowledge_arr], n_rerank=n_rerank) + reranked_knowledge_arr = sorted( + knowledge_arr, + key=lambda x: ( + reranked_texts.index(x["text"]) if x["text"] in reranked_texts else len(knowledge_arr) + ), + ) + reranked_knowledge_arr = reranked_knowledge_arr[:n_rerank] + logging.debug("Reranked results: ") + logging.debug(reranked_knowledge_arr) + return reranked_knowledge_arr + else: + return knowledge_arr + + def ask( + self, + question: str, + person: str, + history: list = None, + n_retrieve: int = 5, + n_rerank=3, + tools: Union[None, list] = None, + prompts: Union[None, list] = None, + ): + """ + Ask the agent a question. + + Parameters: + - question (str): The question to ask + - person (str): Who asks the question? + - n_retrieve (int): How many notes to retrieve. + - n_rerank (int): How many notes to keep after reranking. + """ + logging.info(f"Answering question: {question}") + + if n_retrieve > 0: + knowledge = self.retrieve(question, n_retrieve=n_retrieve, n_rerank=n_rerank) + + with open('templates/knowledge.en.j2', 'r') as file: + template = Template(file.read()) + + knowledge_str = template.render( + knowledge = knowledge, + ) + else: + knowledge_str = "" + + with open('templates/ask.en.j2', 'r') as file: + template = Template(file.read()) + + prompt = template.render( + today = datetime.datetime.now().strftime("%Y-%m-%d %H:%M"), + knowledge = knowledge_str, + plugin_prompts = prompts, + person = person + ) + + logging.debug(f"Asking the LLM with the prompt: '{prompt}'.") + answer = self.llm.query( + question, + system_msg=prompt, + tools=tools, + history=history + ) + logging.info(f"Got answer from LLM: '{answer}'.") + + return answer diff --git a/agent-api/backend.py b/agent-api/backend.py new file mode 100644 index 0000000..ff16e6a --- /dev/null +++ b/agent-api/backend.py @@ -0,0 +1,127 @@ +from fastapi import FastAPI +from fastapi.staticfiles import StaticFiles +import importlib +import logging +import os +from pydantic import BaseModel +import yaml + +from agent import Agent + +PLUGINS_FOLDER = "plugins" + +logging.basicConfig( + format="%(asctime)s | %(levelname)s | %(funcName)s : %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + level=logging.INFO, +) + +with open("config.yml", "r", encoding='utf8') as file: + config = yaml.safe_load(file) + +def load_plugins(config): + tools = [] + prompts = [] + + for plugin_folder in os.listdir("./" + PLUGINS_FOLDER): + if os.path.isdir(os.path.join("./" + PLUGINS_FOLDER, plugin_folder)) and "__pycache__" not in plugin_folder: + logging.info(f"Loading plugin: {plugin_folder}") + module_path = f"{PLUGINS_FOLDER}.{plugin_folder}.plugin" + module = importlib.import_module(module_path) + plugin = module.Plugin(config) + tools += plugin.tools() + prompt = plugin.prompt() + if prompt is not None: + prompts.append(prompt) + + return tools, prompts + +tools, prompts = load_plugins(config) + +app = FastAPI() +app.mount("/static", StaticFiles(directory="static"), name="static") + +agent = Agent(config) + + +class Query(BaseModel): + query: str + +class Message(BaseModel): + role: str + content: str + +class Question(BaseModel): + question: str + person: str = "User" + history: list[Message] = None + + +@app.get("/ping") +def ping(): + """ + # Ping + Check if the API is up. + """ + return {"status": "ok"} + +@app.post("/ask") +def ask(q: Question): + """ + # Ask + Ask the agent a question. + + ## Parameters + - **question**: the question to ask. + - **person**: who is asking the question? + """ + history = None + if q.history: + history = [q.model_dump() for q in q.history] + + answer = agent.ask( + question=q.question, + person="Pierre", + history=history, + tools=tools, + prompts=prompts, + n_retrieve=0, + n_rerank=0 + ) + return {"question": q.question, "answer": answer, "history": q.history} + + +@app.post("/retrieve") +def retrieve(q: Query): + """ + # Retrieve + Retrieve knowledge related to a query. + + ## Parameters + - **query**: the question. + """ + result = agent.retrieve(query=q.query, n_retrieve=10, n_rerank=5) + return {"query": q.query, "answer": result} + + +@app.post("/learn") +def retrieve(q: Query): + """ + # Learn + Learn the agent something. E.g. *"John likes to play volleyball".* + + ## Parameters + - **query**: the note. + """ + answer = agent.insert(text=q.query) + return {"text": q.query} + + +@app.get("/flush") +def flush(): + """ + # Flush + Remove all knowledge from the agent. + """ + agent.flush() + return {"message": "Knowledge flushed."} diff --git a/agent-api/config.default.yml b/agent-api/config.default.yml new file mode 100644 index 0000000..52d6b3b --- /dev/null +++ b/agent-api/config.default.yml @@ -0,0 +1,25 @@ +server: + url: "http://127.0.0.1:8000" +llm: + use_local_chat: false + use_local_embedding: false + local_model_dir: "" + local_embedding_model: "intfloat/e5-large-v2" + local_rerank_model: "cross-encoder/stsb-distilroberta-base" + openai_embedding_model: "text-embedding-3-small" + openai_chat_model: "gpt-3.5-turbo-0125" + temperature: 0.1 +homeassistant: + url: "http://localhost:8123" + token: "" +plugins: + calendar: + default_calendar: "calendar.my_calendar" + todo: + default_list: "todo.todo" + vasttrafik: + key: "" + secret: "" + default_from_station: "Brunnsparken" + default_to_station: "Centralstationen" + delay: 0 # minutes \ No newline at end of file diff --git a/agent-api/db.py b/agent-api/db.py new file mode 100644 index 0000000..da0bb1e --- /dev/null +++ b/agent-api/db.py @@ -0,0 +1,32 @@ +import logging +from pgvector.psycopg2 import register_vector +import psycopg2 + + +class Database: + def __init__( + self, + database, + user="postgres", + password="postgres", + host="localhost", + port="5433", + ) -> None: + logging.info("Connecting to database") + self.conn = psycopg2.connect( + database="rag", + user="postgres", + password="postgres", + host="localhost", + port="5433", + ) + register_vector(self.conn) + self.cur = self.conn.cursor() + self.cur.execute("SELECT version();") + logging.info(" DB Version: %s", self.cur.fetchone()[0]) + logging.info(" psycopg2 Version: %s", psycopg2.__version__) + + def close(self): + logging.info("Closing connection to database") + self.cur.close() + self.conn.close() diff --git a/agent-api/db.sql b/agent-api/db.sql new file mode 100644 index 0000000..bbc919a --- /dev/null +++ b/agent-api/db.sql @@ -0,0 +1,7 @@ +CREATE TABLE public.items ( + id bigserial NOT NULL, + embedding vector(2048) NULL, + "text" varchar(1024) NULL, + date_added date NULL, + CONSTRAINT items_pkey PRIMARY KEY (id) +); \ No newline at end of file diff --git a/agent-api/docker-compose.yml b/agent-api/docker-compose.yml new file mode 100644 index 0000000..0032aa1 --- /dev/null +++ b/agent-api/docker-compose.yml @@ -0,0 +1,15 @@ +version: '3.8' +services: + db: + image: pgvector/pgvector:pg16 + restart: always + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + ports: + - '5433:5432' + volumes: + - db:/var/lib/postgresql/data +volumes: + db: + driver: local \ No newline at end of file diff --git a/agent-api/frontend.py b/agent-api/frontend.py new file mode 100644 index 0000000..51b51a3 --- /dev/null +++ b/agent-api/frontend.py @@ -0,0 +1,41 @@ +import gradio as gr +import logging +import requests +import yaml + +logging.basicConfig( + format="%(asctime)s | %(levelname)s | %(funcName)s : %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + level=logging.INFO, +) + +with open("config.yml", "r") as file: + config = yaml.safe_load(file) + +BASE_URL = config["server"]["url"] + +def chat(message, history): + messages = [] + for (user_msg, bot_msg) in history: + messages.append({"role": "user", "content": user_msg}) + messages.append({"role": "system", "content": bot_msg}) + + logging.info(f"Sending chat message to backend ('{message}').") + response = requests.post(BASE_URL + "/ask", json={"question": message, "person": "Pierre", "history": messages}) + + if response.status_code == 200: + logging.info("Response:") + logging.info(response.json()) + data = response.json() + return data["answer"] + else: + logging.error(response) + +ui = gr.ChatInterface(chat, title="Chat") + +if __name__ == "__main__": + logging.info("Launching frontend.") + ui.launch( + #share=False, + server_name='0.0.0.0', + ) diff --git a/agent-api/install.md b/agent-api/install.md new file mode 100644 index 0000000..0833f57 --- /dev/null +++ b/agent-api/install.md @@ -0,0 +1,3 @@ +# llama.cpp +set CMAKE_ARGS=-DLLAMA_CUBLAS=on +pip install --upgrade --verbose --force-reinstall --no-cache-dir llama-cpp-python==0.2.39 \ No newline at end of file diff --git a/agent-api/llama_server.py b/agent-api/llama_server.py new file mode 100644 index 0000000..9421db5 --- /dev/null +++ b/agent-api/llama_server.py @@ -0,0 +1,37 @@ +"""Example FastAPI server for llama.cpp. + +To run this example: + +```bash +pip install fastapi uvicorn sse-starlette +export MODEL=../models/7B/... +``` + +Then run: +``` +uvicorn --factory llama_cpp.server.app:create_app --reload +``` + +or + +``` +python3 -m llama_cpp.server +``` + +Then visit http://localhost:8000/docs to see the interactive API docs. + + +To actually see the implementation of the server, see llama_cpp/server/app.py + +""" +import os +import uvicorn + +from llama_cpp.server.app import create_app + +if __name__ == "__main__": + app = create_app() + + uvicorn.run( + app, host=os.getenv("HOST", "localhost"), port=int(os.getenv("PORT", 8000)) + ) diff --git a/agent-api/llm.py b/agent-api/llm.py new file mode 100644 index 0000000..9a1cc98 --- /dev/null +++ b/agent-api/llm.py @@ -0,0 +1,252 @@ +import concurrent.futures +import json +from llama_cpp.llama import Llama +import logging +import numpy as np +from openai import OpenAI +from sentence_transformers import SentenceTransformer, CrossEncoder +from typing import Union, List + + +class BaseChat: + def __init__(self, config: dict) -> None: + self.config = config + + def prepare_function_calling(self, tools): + # prepare function calling + function_map = {} + if tools is not None and len(tools) > 0: + for tool in tools: + function_name = tool["function"]["name"] + function_map[function_name] = tool["function"]["function_to_call"] + functions = [] + for tool in tools: + fn = tool["function"] + functions.append( + {"type": tool["type"], "function": {x: fn[x] for x in fn if x != "function_to_call"}} + ) + logging.info(f"{len(tools)} available functions:") + logging.info(function_map.keys()) + else: + functions = None + return function_map, functions + + +class OpenAIChat(BaseChat): + def __init__(self, config: dict) -> None: + self.config = config + if self.config["openai"]["base_url"] is not None and self.config["openai"]["base_url"] != "": + base_url = self.config["openai"]["base_url"] + else: + base_url = None + self.client = OpenAI(base_url=base_url) + + def chat(self, messages, tools) -> str: + function_map, functions = self.prepare_function_calling(tools) + + logging.debug("Sending request to OpenAI.") + llm_response = self.client.chat.completions.create( + model=self.config["openai"]["chat_model"], + messages=messages, + tools=functions, + temperature=self.config["temperature"], + tool_choice="auto" if functions is not None else None, + ) + logging.debug("LLM response:") + logging.debug(llm_response.choices) + if llm_response.choices[0].message.tool_calls: + # Handle function calls + followup_response = self.execute_function_call( + llm_response.choices[0].message, function_map, messages + ) + return followup_response.choices[0].message.content.strip() + else: + return llm_response.choices[0].message.content.strip() + + def execute_function_call(self, message, function_map: dict, messages: list) -> str: + """ + Executes function calls embedded in a LLM message in parallel, and returns a LLM response based on the results. + + Parameters: + - message: LLM message containing the function calls. + - function_map (dict): dict of {"function_name": function()} + - message (list): message history + """ + tool_calls = message.tool_calls + logging.info(f"Got {len(tool_calls)} function call(s).") + + def execute_single_tool_call(tool_call): + """Helper function to execute a single tool call""" + logging.info(f"Attempting to execute function requested by LLM ({tool_call.function.name}, {tool_call.function.arguments}).") + if tool_call.function.name in function_map: + function_to_call = function_map[tool_call.function.name] + args = json.loads(tool_call.function.arguments) + logging.debug(f"Calling function {tool_call.function.name} with args: {args}") + function_response = function_to_call(**args) + logging.debug(function_response) + return { + "role": "function", + "tool_call_id": tool_call.id, + "name": tool_call.function.name, + "content": function_response, + } + else: + logging.info(f"{tool_call.function.name} not in function_map") + logging.info(function_map.keys()) + return None + + # Execute tool calls in parallel + with concurrent.futures.ThreadPoolExecutor() as executor: + # Submit all tool calls to the executor + future_to_tool_call = { + executor.submit(execute_single_tool_call, tool_call): tool_call + for tool_call in tool_calls + } + + # Collect results as they complete + for future in concurrent.futures.as_completed(future_to_tool_call): + result = future.result() + if result is not None: + messages.append(result) + + logging.debug("Functions called, sending the results to LLM.") + llm_response = self.client.chat.completions.create( + model=self.config["openai"]["chat_model"], + messages=messages, + temperature=self.config["temperature"], + ) + logging.debug("Got response from LLM:") + logging.debug(llm_response) + return llm_response + + +class LMStudioChat(BaseChat): + def __init__(self, config: dict) -> None: + self.config = config + self.client = OpenAI(base_url="http://localhost:1234/v1", api_key="not-needed") + + def chat(self, messages, tools) -> str: + logging.info("Sending request to local LLM.") + llm_response = self.client.chat.completions.create( + model="", + messages=messages, + temperature=self.config["temperature"], + ) + return llm_response.choices[0].message.content.strip() + + +class LlamaChat(BaseChat): + def __init__(self, config: dict) -> None: + self.config = config + self.client = Llama( + self.config["local_model_dir"] + "TheBloke/Mistral-7B-Instruct-v0.1-GGUF/mistral-7b-instruct-v0.1.Q6_K.gguf", + n_gpu_layers=32, + n_ctx=2048, + verbose=False + ) + + def chat(self, messages: list, response_format: dict = None, tools: list = None) -> str: + if tools is not None: + logging.warning("Tools was provided to LlamaChat, but it's not yet supported.") + logging.info("Sending request to local LLM.") + logging.info(messages) + llm_response = self.client.create_chat_completion( + messages = messages, + response_format = response_format, + temperature = self.config["temperature"], + ) + return llm_response["choices"][0]["message"]["content"].strip() + + +class LLM: + def __init__(self, config: dict) -> None: + """ + LLM constructor. + + Parameters: + - config (dict): llm-config parsed from the llm part of config.yml + """ + self.config = config + + if self.config["use_local_chat"]: + self.chat_client = LlamaChat(self.config) + else: + self.chat_client = OpenAIChat(self.config) + + if self.config["use_local_embedding"]: + self.embedding_model = self.config["local_embedding_model"] + self.rerank_model = self.config["local_rerank_model"] + self.embedding_client = SentenceTransformer(self.embedding_model) + else: + self.embedding_model = self.config["openai"]["embedding_model"] + self.rerank_model = None + self.embedding_client = OpenAI() + + def query( + self, + user_msg: str, + system_msg: Union[None, str] = None, + history: list = [], + tools: Union[None, list] = None, + ): + """ + Query the LLM + + Parameters: + - user_msg (str): query from the user (will be appended to messages as {"role": "user"}) + - system_msg (str): query from the user (will be prepended to messages as {"role": "system"}) + - history (list): optional, list of messages to inject between system_msg and user_msg. + - tools: optional, list of functions that may be called by the LLM. Example: + { + "type": "function", + "function": { + "name": "get_current_weather", + "function": get_current_weather, + "description": "Get the current weather", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA", + } + }, + "required": ["location", "format"], + }, + }, + } + """ + messages = [] + + if system_msg is not None: + messages.append({"role": "system", "content": system_msg}) + + if history and len(history) > 0: + logging.info("History") + logging.info(history) + messages += history + + messages.append({"role": "user", "content": user_msg}) + + logging.info(f"Sending request to LLM with {len(messages)} messages.") + answer = self.chat_client.chat(messages=messages, tools=tools) + return answer + + def embedding(self, text: str): + if self.config["use_local_embedding"]: + embedding = self.embedding_client.encode(text) + return embedding # len = 1024 + else: + llm_response = self.embedding_client.embeddings.create(model=self.embedding_model, input=[text.replace("\n", " ")]) + return llm_response.data[0].embedding + + def rerank(self, text: str, corpus: List[str], n_rerank: int = 5): + if self.rerank_model is not None and len(corpus) > 0: + sentence_combinations = [[text, corpus_sentence] for corpus_sentence in corpus] + logging.info("Reranking:") + logging.info(sentence_combinations) + similarity_scores = CrossEncoder(self.rerank_model, max_length=1024).predict(sentence_combinations) + result = [corpus[idx] for idx in reversed(np.argsort(similarity_scores))] + return result[:n_rerank] + else: + return corpus diff --git a/agent-api/plugins/__init__.py b/agent-api/plugins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/agent-api/plugins/base_plugin.py b/agent-api/plugins/base_plugin.py new file mode 100644 index 0000000..8cbe8b9 --- /dev/null +++ b/agent-api/plugins/base_plugin.py @@ -0,0 +1,68 @@ +import inspect + +from plugins.homeassistant import HomeAssistant + + +class BasePlugin: + def __init__(self, config: dict) -> None: + self.config = config + self.homeassistant = HomeAssistant(config) + + def prompt(self) -> str | None: + return + + def tools(self) -> list: + tools = [] + for tool_fn in self._list_tool_methods(): + tool_fn_metadata = self._get_function_metadata(tool_fn) + + if "input" in tool_fn_metadata["parameters"]: + json_schema = tool_fn_metadata["parameters"]["input"].annotation.model_json_schema() + del json_schema["title"] + for k, v in json_schema["properties"].items(): + del json_schema["properties"][k]["title"] + fn_to_call = self._create_function_with_kwargs( + tool_fn_metadata["parameters"]["input"].annotation, tool_fn + ) + else: + json_schema = {} + fn_to_call = tool_fn + + tools.append( + { + "type": "function", + "function": { + "name": tool_fn_metadata["name"], + "function_to_call": fn_to_call, + "description": tool_fn_metadata["docstring"], + "parameters": json_schema, + }, + } + ) + return tools + + def _get_function_metadata(self, func): + function_name = func.__name__ + docstring = inspect.getdoc(func) + signature = inspect.signature(func) + parameters = signature.parameters + + metadata = {"name": function_name, "docstring": docstring or "", "parameters": parameters} + + return metadata + + def _create_function_with_kwargs(self, model_cls, original_function): + def dynamic_function(**kwargs): + model_instance = model_cls(**kwargs) + return original_function(model_instance) + + return dynamic_function + + def _list_tool_methods(self) -> list: + attributes = dir(self) + tool_functions = [ + getattr(self, attr) + for attr in attributes + if callable(getattr(self, attr)) and attr.startswith("tool_") + ] + return tool_functions diff --git a/agent-api/plugins/calendar/plugin.py b/agent-api/plugins/calendar/plugin.py new file mode 100644 index 0000000..7cf2fb6 --- /dev/null +++ b/agent-api/plugins/calendar/plugin.py @@ -0,0 +1,26 @@ +import asyncio +import json + +from ..base_plugin import BasePlugin + +class Plugin(BasePlugin): + def __init__(self, config: dict) -> None: + super().__init__(config=config) + + def tool_get_calendar_events(self, entity_id=None, days=3): + """ + Get users calendar events. + """ + if entity_id is None: + entity_id = self.config["plugins"]["calendar"]["default_calendar"] + response = asyncio.run( + self.homeassistant.send_command( + "call_service", + domain="calendar", + service="get_events", + target={"entity_id": entity_id}, + service_data={"duration": {"days": days}}, + return_response=True, + ) + ) + return json.dumps(response) diff --git a/agent-api/plugins/homeassistant.py b/agent-api/plugins/homeassistant.py new file mode 100644 index 0000000..150d451 --- /dev/null +++ b/agent-api/plugins/homeassistant.py @@ -0,0 +1,51 @@ +import json +from typing import Any + +import requests +from aiohttp import ClientSession +from hass_client import HomeAssistantClient + + +class HomeAssistant: + + def __init__(self, config: dict) -> None: + self.config = config + self.ha_config = self.call_api("config") + + def call_api(self, endpoint, payload=None): + """ + Call the REST API + """ + base_url = self.config["homeassistant"]["url"] + headers = { + "Authorization": f"Bearer {self.config['homeassistant']['token']}", + "content-type": "application/json", + } + if payload is None: + response = requests.get(f"{base_url}/api/{endpoint}", headers=headers) + else: + response = requests.post(f"{base_url}/api/{endpoint}", headers=headers, json=payload) + if response.status_code == 200: + try: + return response.json() + except: + pass + try: + return json.loads(response.text.replace("'", '"').replace("None", "null")) + except: + return response.text + else: + return {"status": "error", "message": response.text} + + async def send_command(self, command: str, **kwargs: dict[str, Any]): + """ + Send command using the WebSocket API + """ + async with ClientSession() as session: + async with HomeAssistantClient( + self.config["homeassistant"]["url"] + "/api/websocket", + self.config["homeassistant"]["token"], + session, + ) as client: + response = await client.send_command(command, **kwargs) + return response diff --git a/agent-api/plugins/lights/plugin.py b/agent-api/plugins/lights/plugin.py new file mode 100644 index 0000000..64ade0a --- /dev/null +++ b/agent-api/plugins/lights/plugin.py @@ -0,0 +1,73 @@ +import json + +from ..base_plugin import BasePlugin + + +class Plugin(BasePlugin): + def __init__(self, config: dict) -> None: + super().__init__(config=config) + self.light_map = self.get_light_map() + + def prompt(self): + prompt = "These are the lights available:\n" + json.dumps( + self.light_map, indent=2, ensure_ascii=False + ) + return prompt + + def tools(self): + tools = [ + { + "type": "function", + "function": { + "name": "control_light", + "function_to_call": self.tool_control_light, + "description": "Control lights in users a home", + "parameters": { + "type": "object", + "properties": { + "entity_id": { + "type": "string", + "enum": self.available_lights(), + "description": "What light entity to control", + }, + "state": { + "type": "string", + "enum": ["on", "off"], + "description": "on or off", + }, + }, + "required": ["room", "state"], + }, + }, + } + ] + return tools + + def tool_control_light(self, entity_id: str, state: str): + self.homeassistant.call_api(f"services/light/turn_{state}", payload={"entity_id": entity_id}) + return json.dumps({"status": "success", "message": f"{entity_id} was turned {state}."}) + + def available_lights(self): + available_lights = [] + for room in self.light_map: + for light in self.light_map[room]: + available_lights.append(light["entity_id"]) + return available_lights + + def get_light_map(self): + template = ( + "{% for state in states.light %}\n" + + '{ "entity_id": "{{ state.entity_id }}", "name" : "{{ state.attributes.friendly_name }}", "room": "{{area_name(state.entity_id)}}"}\n' + + "{% endfor %}\n" + ) + response = self.homeassistant.call_api(f"template", payload={"template": template}) + light_map = {} + for item_str in response.split("\n\n"): + item_dict = json.loads(item_str) + if item_dict["room"] not in light_map: + light_map[item_dict["room"]] = [] + light_map[item_dict["room"]].append( + {"entity_id": item_dict["entity_id"], "name": item_dict["name"]} + ) + + return light_map diff --git a/agent-api/plugins/music/.cache b/agent-api/plugins/music/.cache new file mode 100644 index 0000000..420d072 --- /dev/null +++ b/agent-api/plugins/music/.cache @@ -0,0 +1 @@ +{"access_token": "BQC4auYl6DX-_xbS0zDjbv2_OZGbbdvyWLDa7I6EBE91GhBAExs3TGmhBNCgwS3P0WJwkUyL-kRRH2x-80QpWaRdLhEm0Q7Tfm8vzsV1rRBToy87Vm1jJJX3S08bYQwD3UvxFlaOhGyyWoSa37JnPkmS9T8d2GTPeHne1HaYR5KEJeYu9stixoONiTjHidQJi_7hOozIqwUkTC7TFWBWezJfa_7GlkE", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "AQDSGqpEaLoyR_A-s78bFVHYE1F2PIbA_cfgrgMt7jZA-A3xmEo5V7GazlgB-okD0JHX-w_fVc-n0_b8nvvZUz5PT6iCd7jRHLVoRA-h6XbQKcSAUVwJGL2djOTfnC4wOIE", "scope": "user-library-read", "expires_at": 1736686521} \ No newline at end of file diff --git a/agent-api/plugins/music/plugin.py b/agent-api/plugins/music/plugin.py new file mode 100644 index 0000000..6f5d939 --- /dev/null +++ b/agent-api/plugins/music/plugin.py @@ -0,0 +1,59 @@ +import json +import logging + +import spotipy +from pydantic import BaseModel, Field +from spotipy.oauth2 import SpotifyOAuth + +from ..base_plugin import BasePlugin + + +class PlayMusicInput(BaseModel): + query: str = Field(..., description="Can be a song, artist, album, or playlist") + + +class Plugin(BasePlugin): + def __init__(self, config: dict) -> None: + super().__init__(config=config) + + self.spotify = spotipy.Spotify( + auth_manager=SpotifyOAuth(scope="user-library-read", redirect_uri="http://localhost:8080") + ) + + def _search(self, query: str, limit: int = 10): + _result = self.spotify.search(query, limit=limit) + result = [] + for track in _result["tracks"]["items"]: + artists = [artist["name"] for artist in track["artists"]] + result.append( + { + "name": track["name"], + "artists": artists, + "uri": track["uri"] + } + ) + return result + + def tool_play_music(self, input: PlayMusicInput): + """ + Play music using a search query. + """ + track = self._search(input.query, limit=1)[0] + logging.info(f"Playing {track['name']} by {', '.join(track['artists'])}") + payload = { + "entity_id": self.config["plugins"]["music"]["default_speaker"], + "media_content_id": track["uri"], + "media_content_type": "music", + "enqueue": "play", + } + result = self.homeassistant.call_api(f"services/media_player/play_media", payload=payload) + return json.dumps({"status": "success", "message": f"Playing music.", "track": track}) + + def tool_stop_music(self): + """ + Stop playback of music. + """ + self.homeassistant.call_api( + f"services/media_player/media_pause", payload={"entity_id": self.config["plugins"]["music"]["default_speaker"]} + ) + return json.dumps({"status": "success", "message": f"Music paused."}) diff --git a/agent-api/plugins/readme.md b/agent-api/plugins/readme.md new file mode 100644 index 0000000..194fe33 --- /dev/null +++ b/agent-api/plugins/readme.md @@ -0,0 +1,3 @@ +# Plugins +Each folder in /plugins is a plugin. +Class files within /plugins (e.g. homeassistant.py) is helper classes that plugins can use in order to have to duplicate code. \ No newline at end of file diff --git a/agent-api/plugins/smhi/plugin.py b/agent-api/plugins/smhi/plugin.py new file mode 100644 index 0000000..72113d9 --- /dev/null +++ b/agent-api/plugins/smhi/plugin.py @@ -0,0 +1,74 @@ +import json + +from smhi.smhi_lib import Smhi + +from ..homeassistant import HomeAssistant + + +class Plugin: + def __init__(self, config: dict) -> None: + self.config = config + self.homeassistant = HomeAssistant(config) + self.station = Smhi( + self.homeassistant.ha_config["longitude"], self.homeassistant.ha_config["latitude"] + ) + self.weather_conditions = { + 1: "Clear sky", + 2: "Nearly clear sky", + 3: "Variable cloudiness", + 4: "Halfclear sky", + 5: "Cloudy sky", + 6: "Overcast", + 7: "Fog", + 8: "Light rain showers", + 9: "Moderate rain showers", + 10: "Heavy rain showers", + 11: "Thunderstorm", + 12: "Light sleet showers", + 13: "Moderate sleet showers", + 14: "Heavy sleet showers", + 15: "Light snow showers", + 16: "Moderate snow showers", + 17: "Heavy snow showers", + 18: "Light rain", + 19: "Moderate rain", + 20: "Heavy rain", + 21: "Thunder", + 22: "Light sleet", + 23: "Moderate sleet", + 24: "Heavy sleet", + 25: "Light snowfall", + 26: "Moderate snowfall", + 27: "Heavy snowfall", + } + + def prompt(self): + return None + + def tools(self): + tools = [ + { + "type": "function", + "function": { + "name": "get_weather_forecast", + "function_to_call": self.get_weather_forecast, + "description": "Get weather forecast for the following 24 hours.", + }, + } + ] + return tools + + def get_weather_forecast(self, hours=24): + forecast = [] + for hour in self.station.get_forecast_hour()[:hours]: + forecast.append( + { + "time": hour.valid_time.strftime("%Y-%m-%d %H:%M"), + "weather": self.weather_conditions[hour.symbol], + "temperature": hour.temperature, + # "cloudiness": hour.cloudiness, + "total_precipitation": hour.total_precipitation, + "wind_speed": hour.wind_speed, + } + ) + return json.dumps(forecast) diff --git a/agent-api/plugins/smhi/requirements.txt b/agent-api/plugins/smhi/requirements.txt new file mode 100644 index 0000000..df77dbb --- /dev/null +++ b/agent-api/plugins/smhi/requirements.txt @@ -0,0 +1 @@ +smhi-pkg>=1.0.16 \ No newline at end of file diff --git a/agent-api/plugins/todo/plugin.py b/agent-api/plugins/todo/plugin.py new file mode 100644 index 0000000..dafd5f2 --- /dev/null +++ b/agent-api/plugins/todo/plugin.py @@ -0,0 +1,107 @@ +import asyncio +import json + +from ..base_plugin import BasePlugin + +class Plugin(BasePlugin): + def __init__(self, config: dict) -> None: + super().__init__(config=config) + + def prompt(self): + prompt = "These are the todo lists available:\n" + json.dumps( + self.get_todo_lists(), indent=2, ensure_ascii=False + ) + return prompt + + def tools(self): + tools = [ + { + "type": "function", + "function": { + "name": "get_todo_list_items", + "function_to_call": self.get_todo_list_items, + "description": "Get items from todo-list.", + "parameters": { + "type": "object", + "properties": { + "entity_id": { + "type": "string", + "description": "What list to get items from.", + } + }, + "required": ["entity_id"], + }, + }, + }, + { + "type": "function", + "function": { + "name": "add_todo_item", + "function_to_call": self.add_todo_item, + "description": "Add item to todo-list.", + "parameters": { + "type": "object", + "properties": { + "entity_id": { + "type": "string", + "description": "What list to add item to.", + }, + "item": { + "type": "string", + "description": "Description of the item.", + }, + }, + "required": ["entity_id", "item"], + }, + }, + }, + ] + return tools + + def get_todo_list_items(self, entity_id: str = None): + """ + Get items from todo-list. + """ + if entity_id is None: + entity_id = self.config["plugins"]["todo"]["default_list"] + response = asyncio.run( + self.homeassistant.send_command( + "call_service", + domain="todo", + service="get_items", + target={"entity_id": entity_id}, + service_data={"status": "needs_action"}, + return_response=True, + ) + ) + return json.dumps(response["response"]) + + def add_todo_item(self, item: str, entity_id: str = None): + """ + Add item to todo-list. + """ + if entity_id is None: + entity_id = self.config["plugins"]["todo"]["default_list"] + asyncio.run( + self.homeassistant.send_command( + "call_service", + domain="todo", + service="add_item", + target={"entity_id": entity_id}, + service_data={"item": item}, + ) + ) + return json.dumps({"status": f"{item} added to list."}) + + def get_todo_lists(self): + template = ( + "{% for state in states.todo %}\n" + + '{ "entity_id": "{{ state.entity_id }}", "name" : "{{ state.attributes.friendly_name }}"}\n' + + "{% endfor %}\n" + ) + response = self.homeassistant.call_api(f"template", payload={"template": template}) + todo_lists = {} + for item_str in response.split("\n\n"): + item_dict = json.loads(item_str) + todo_lists[item_dict["name"]] = item_dict["entity_id"] + return todo_lists diff --git a/agent-api/plugins/vasttrafik/plugin.py b/agent-api/plugins/vasttrafik/plugin.py new file mode 100644 index 0000000..51fa29a --- /dev/null +++ b/agent-api/plugins/vasttrafik/plugin.py @@ -0,0 +1,98 @@ +import json +from datetime import datetime + +import vasttrafik + +from ..homeassistant import HomeAssistant + + +class Plugin: + def __init__(self, config: dict) -> None: + self.config = config + self.homeassistant = HomeAssistant(config) + self.vasttrafik = vasttrafik.JournyPlanner( + key=self.config["plugins"]["vasttrafik"]["key"], + secret=self.config["plugins"]["vasttrafik"]["secret"], + ) + self.default_from_station_id = self.get_station_id( + self.config["plugins"]["vasttrafik"]["default_from_station"] + ) + self.default_to_station_id = self.get_station_id( + self.config["plugins"]["vasttrafik"]["default_to_station"] + ) + + def prompt(self): + from_station = self.config["plugins"]["vasttrafik"]["default_from_station"] + to_station = self.config["plugins"]["vasttrafik"]["default_to_station"] + return "# Public transportation #\nHome station is: %s, and the default to station to use is %s" % ( + from_station, + to_station, + ) + + def tools(self): + tools = [ + { + "type": "function", + "function": { + "name": "search_trip", + "function_to_call": self.search_trip, + "description": "Search for trips by public transportation.", + "parameters": { + "type": "object", + "properties": { + "from_station": { + "type": "string", + "description": "Station to travel from.", + }, + "to_station": { + "type": "string", + "description": "Station to travel to.", + }, + }, + "required": ["from_station", "to_station"], + }, + }, + } + ] + return tools + + def search_trip(self, from_station=None, to_station=None): + if from_station is None: + from_station_id = self.default_from_station_id + else: + from_station_id = self.get_station_id(from_station) + if to_station is None: + to_station_id = self.default_to_station_id + else: + to_station_id = self.get_station_id(to_station) + trip = self.vasttrafik.trip(from_station_id, to_station_id) + + response = [] + for departure in trip: + try: + departure_dt = datetime.strptime( + departure["tripLegs"][0]["origin"]["estimatedTime"][:19], "%Y-%m-%dT%H:%M:%S" + ) + minutes_to_departure = (departure_dt - datetime.now()).seconds / 60 + if minutes_to_departure >= self.config["plugins"]["vasttrafik"]["delay"]: + response.append( + { + "origin": { + "stationName": departure["tripLegs"][0]["origin"]["stopPoint"]["name"], + # "plannedTime": departure["tripLegs"][0]["origin"]["plannedTime"], + "estimatedDepartureTime": departure["tripLegs"][0]["origin"]["estimatedTime"], + }, + "destination": { + "stationName": departure["tripLegs"][0]["destination"]["stopPoint"]["name"], + "estimatedArrivalTime": departure["tripLegs"][0]["estimatedArrivalTime"], + }, + } + ) + except Exception as e: + print(f"Error: {e}") + assert len(response) > 0, "No trips found." + return json.dumps(response, ensure_ascii=False) + + def get_station_id(self, location_name: str) -> str: + station_id = self.vasttrafik.location_name(location_name)[0]["gid"] + return station_id diff --git a/agent-api/plugins/vasttrafik/requirements.txt b/agent-api/plugins/vasttrafik/requirements.txt new file mode 100644 index 0000000..ad8d832 --- /dev/null +++ b/agent-api/plugins/vasttrafik/requirements.txt @@ -0,0 +1 @@ +vtjp>=0.2.1 \ No newline at end of file diff --git a/agent-api/pyproject.toml b/agent-api/pyproject.toml new file mode 100644 index 0000000..8b24fe3 --- /dev/null +++ b/agent-api/pyproject.toml @@ -0,0 +1,2 @@ +[tool.black] +line-length = 110 \ No newline at end of file diff --git a/agent-api/readme.md b/agent-api/readme.md new file mode 100644 index 0000000..fed8912 --- /dev/null +++ b/agent-api/readme.md @@ -0,0 +1,21 @@ +## Start postgres with pgvector +docker-compose up -d + +Then create a new db called "rag", and run this SQL: + +```sql +CREATE EXTENSION vector; +``` + +Finally run the content of db.sql. + +## Run backend +```bash +conda activate llm-api +python -m uvicorn backend:app --host 0.0.0.0 --reload --reload-include config.yml +``` + +## Run frontend +```bash +python frontend.py +``` \ No newline at end of file diff --git a/agent-api/requirements.txt b/agent-api/requirements.txt new file mode 100644 index 0000000..e6c1512 --- /dev/null +++ b/agent-api/requirements.txt @@ -0,0 +1,11 @@ +fastapi +uvicorn[standard] +openai>=1.11.1 +sentence_transformers +dateparser +pgvector +psycopg2 +pyyaml +gradio +hass-client>=1.0.1 +llama_cpp_python>=0.2.44 diff --git a/agent-api/static/tada.wav b/agent-api/static/tada.wav new file mode 100644 index 0000000000000000000000000000000000000000..d74c3d32dfc57cbe4be7321394cb1d61503490ce GIT binary patch literal 195884 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 literal 0 HcmV?d00001 diff --git a/agent-api/templates/ask.en.j2 b/agent-api/templates/ask.en.j2 new file mode 100644 index 0000000..9fbe5c0 --- /dev/null +++ b/agent-api/templates/ask.en.j2 @@ -0,0 +1,12 @@ +You are a helpful assistant. Your replies will be converted to speech, so keep it short and concise and avoid too many special characters. +Today is {{ today }}. + +{% if knowledge is not none %} + {{ knowledge }} +{% endif %} + +{% for prompt in plugin_prompts %} +- {{ prompt }} +{% endfor %} + +Answer the questions from {{ person }} to the best of your knowledge. Reply in Swedish when you get a sweedish question. \ No newline at end of file diff --git a/agent-api/templates/knowledge.en.j2 b/agent-api/templates/knowledge.en.j2 new file mode 100644 index 0000000..b53dcec --- /dev/null +++ b/agent-api/templates/knowledge.en.j2 @@ -0,0 +1,5 @@ +Here are some saved notes that might help you answer any question the user might have: +{% for note in knowledge %} +- {{ note }} +{% endfor %} +Never make notes of knowledge you already have (i.e. the list above). \ No newline at end of file diff --git a/esphome-va-bridge/audio.py b/esphome-va-bridge/audio.py new file mode 100644 index 0000000..488afc2 --- /dev/null +++ b/esphome-va-bridge/audio.py @@ -0,0 +1,203 @@ +import datetime as dt +import io +from typing import Iterator + +import numpy as np +import pydub +import scipy.io.wavfile as wavfile +import torch +from elevenlabs.client import ElevenLabs +from faster_whisper import WhisperModel +from silero_vad import get_speech_timestamps, load_silero_vad + + +class AudioSegment(pydub.AudioSegment): + """ + Wrapper class for pydub.AudioSegment + """ + + def __init__(self, data=None, *args, **kwargs): + super().__init__(data, *args, **kwargs) + + def to_ndarray(self) -> np.ndarray: + """ + Convert the AudioSegment to a numpy array. + """ + _buffer = io.BytesIO() + self.export(_buffer, format="wav") + _buffer.seek(0) + + _, wav_data = wavfile.read(_buffer) + if wav_data.dtype != np.float32: + max_abs = max(np.abs(wav_data)) if max(np.abs(wav_data)) != 0 else 1 + wav_data = wav_data.astype(np.float32) / max_abs + if len(wav_data.shape) == 2: + wav_data = wav_data.mean(axis=1) + return wav_data + + def to_tensor(self) -> torch.Tensor: + """ + Convert the AudioSegment to a PyTorch tensor. + """ + return torch.tensor(self.to_ndarray(), dtype=torch.float32) + + +class AudioBuffer: + """ + Buffer for AudioSegments + """ + + def __init__(self, max_size: int) -> None: + self.data: AudioSegment | None = None + self.max_size: int = max_size + + def put(self, audio_segment: AudioSegment) -> None: + """ + Append an AudioSegment to the buffer. + """ + if self.data: + self.data = self.data + audio_segment + if len(self.data) >= self.max_size: + self.data = self.data[-self.max_size :] + else: + self.data = audio_segment + + def clear(self) -> None: + """ + Clear the buffer. + """ + self.data = None + + def get(self) -> AudioSegment | None: + """ + Get the AudioSegment from the buffer. + """ + return self.data + + def get_last(self, duration: int) -> AudioSegment | None: + """ + Get the last `duration` milliseconds of the AudioSegment. + """ + return self.data[-duration:] if self.data else None + + def is_empty(self) -> bool: + """ + Check if the buffer is empty. + """ + return self.data is None + + def length(self) -> int: + """ + Get the length of the buffer. + """ + return len(self.data) if self.data else 0 + + +class VAD: + """ + Voice Activity Detection + """ + + def __init__(self) -> None: + self.model = load_silero_vad() + self.ts_voice_detected: dt.datetime | None = None + + def detect_voice(self, audio_segment: AudioSegment) -> bool: + """ + Detect voice in an AudioSegment. + """ + speech_timestamps = get_speech_timestamps( + audio_segment.to_tensor(), + self.model, + return_seconds=True, + ) + if len(speech_timestamps) > 0: + if self.ts_voice_detected is None: + pass # print("VAD: Voice detected.") + self.ts_voice_detected = dt.datetime.now() + return True + + return False + + def ms_since_voice_detected(self) -> int | None: + """ + Get the milliseconds since voice was detected. + """ + if self.ts_voice_detected is None: + return None + return (dt.datetime.now() - self.ts_voice_detected).total_seconds() * 1000 + + def reset_timer(self): + """ + Reset the timer for voice detected. + """ + self.ts_voice_detected = None + + +class STT: + """ + Speech-to-Text + """ + + def __init__(self, model="medium", device="cuda" if torch.cuda.is_available() else "cpu", benchmark=False) -> None: + compute_type = "int8" if device == "cpu" else "float16" + self.model = WhisperModel(model, device=device, compute_type=compute_type) + self.benchmark = benchmark + + def transcribe(self, audio_segment: AudioSegment, language="sv"): + """ + Transcribe an AudioSegment. + """ + if self.benchmark: + ts_start = dt.datetime.now() + segments, info = self.model.transcribe(audio_segment.to_ndarray(), language=language) + segments = list(segments) + text = " ".join([seg.text for seg in segments]) + text = text.strip() + if self.benchmark: + ts_done = dt.datetime.now() + delta_ms = (ts_done - ts_start).total_seconds() * 1000 + print(f"[Benchmark] STT: {delta_ms:.2f} ms.") + return text + + +class TTS: + """ + Text-to-Speech (TTS) class that uses the ElevenLabs API. + """ + + def __init__(self, voice="Brian", model="eleven_multilingual_v2") -> None: + self.client = ElevenLabs() + self.voice = voice + self.model = model + + def generate(self, text: str, voice: str = "default") -> AudioSegment: + if voice == "default": + voice = self.voice + audio = self.client.generate( + text=text, + voice=self.voice, + model=self.model, + stream=False, + output_format="pcm_16000", + optimize_streaming_latency=2, + ) + audio = b"".join(audio) + audio_segment = AudioSegment(data=audio, sample_width=2, frame_rate=16000, channels=1) + return audio_segment + + def stream(self, text_stream: Iterator[str], voice: str = "default") -> Iterator[bytes]: + if voice == "default": + voice = self.voice + audio_stream = self.client.generate( + text=text_stream, + voice=self.voice, + model=self.model, + stream=True, + output_format="pcm_16000", + optimize_streaming_latency=2, + ) + + for chunk in audio_stream: + if chunk is not None: + yield chunk diff --git a/esphome-va-bridge/esp-working.log b/esphome-va-bridge/esp-working.log new file mode 100644 index 0000000..9cd5a57 --- /dev/null +++ b/esphome-va-bridge/esp-working.log @@ -0,0 +1,113 @@ +[16:22:06.368][D][i2s_audio.microphone:377]: Starting I2S Audio Microphne +[16:22:06.368][D][i2s_audio.microphone:381]: Started I2S Audio Microphone + +[16:22:06.368][D][micro_wake_word:417]: State changed from IDLE to DETECTING_WAKE_WORD +[16:22:08.878][D][micro_wake_word:355]: Detected 'Okay Nabu' with sliding average probability is 0.97 and max probability is 1.00 + +[16:22:08.878][D][media_player:080]: 'Media Player' - Setting +[16:22:08.878][D][media_player:084]: Command: STOP +[16:22:08.878][D][media_player:093]: Announcement: yes +[16:22:08.878][D][media_player:080]: 'Media Player' - Setting +[16:22:08.878][D][media_player:093]: Announcement: yes + +[16:22:08.879][D][ring_buffer:034]: Created ring buffer with size 48000 +[16:22:08.879][D][ring_buffer:034]: Created ring buffer with size 48000 +[16:22:08.879][D][ring_buffer:034]: Created ring buffer with size 65536 +[16:22:08.879][D][ring_buffer:034]: Created ring buffer with size 65536 + +[16:22:08.879][D][nabu_media_player.pipeline:173]: Reading FLAC file type +[16:22:08.879][D][nabu_media_player.pipeline:184]: Decoded audio has 1 channels, 48000 Hz sample rate, and 16 bits per sample +[16:22:08.879][D][nabu_media_player.pipeline:211]: Converting mono channel audio to stereo channel audio + +[16:22:08.879][D][ring_buffer:034][speaker_task]: Created ring buffer with size 19200 + +[16:22:08.879][D][i2s_audio.speaker:111]: Starting Speaker +[16:22:08.880][D][i2s_audio.speaker:116]: Started Speaker + +[16:22:08.880][D][voice_assistant:515]: State changed from IDLE to START_MICROPHONE +[16:22:08.880][D][voice_assistant:522]: Desired state set to START_PIPELINE +[16:22:08.880][D][voice_assistant:225]: Starting Microphone + +[16:22:08.880][D][ring_buffer:034]: Created ring buffer with size 16384 + +[16:22:08.880][D][voice_assistant:515]: State changed from START_MICROPHONE to STARTING_MICROPHONE +[16:22:08.880][D][voice_assistant:515]: State changed from STARTING_MICROPHONE to START_PIPELINE +[16:22:08.880][D][voice_assistant:280]: Requesting start... +[16:22:08.880][D][voice_assistant:515]: State changed from START_PIPELINE to STARTING_PIPELINE +[16:22:08.880][D][voice_assistant:537]: Client started, streaming microphone +[16:22:08.881][D][voice_assistant:515]: State changed from STARTING_PIPELINE to STREAMING_MICROPHONE +[16:22:08.881][D][voice_assistant:522]: Desired state set to STREAMING_MICROPHONE +[16:22:08.881][D][voice_assistant:641]: Event Type: 1 (VOICE_ASSISTANT_RUN_START) +[16:22:08.881][D][voice_assistant:644]: Assist Pipeline running +[16:22:08.881][D][voice_assistant:641]: Event Type: 3 (VOICE_ASSISTANT_STT_START) +[16:22:08.881][D][voice_assistant:655]: STT started + +[16:22:08.881][D][light:036]: 'voice_assistant_leds' Setting: +[16:22:08.881][D][light:047]: State: ON +[16:22:08.881][D][light:051]: Brightness: 66% +[16:22:08.882][D][light:109]: Effect: 'Waiting for Command' + +[16:22:08.882][D][power_supply:033]: Enabling power supply. + +[16:22:09.671][D][voice_assistant:641]: Event Type: 11 (VOICE_ASSISTANT_STT_VAD_START) +[16:22:09.671][D][voice_assistant:804]: Starting STT by VAD + +[16:22:09.671][D][light:036]: 'voice_assistant_leds' Setting: +[16:22:09.671][D][light:051]: Brightness: 66% +[16:22:09.671][D][light:109]: Effect: 'Listening For Command' + +[16:22:14.806][D][voice_assistant:641]: Event Type: 12 (VOICE_ASSISTANT_STT_VAD_END) +[16:22:14.806][D][voice_assistant:808]: STT by VAD end +[16:22:14.806][D][voice_assistant:515]: State changed from STREAMING_MICROPHONE to STOP_MICROPHONE +[16:22:14.806][D][voice_assistant:522]: Desired state set to AWAITING_RESPONSE +[16:22:14.806][D][voice_assistant:515]: State changed from STOP_MICROPHONE to STOPPING_MICROPHONE + +[16:22:14.807][D][light:036]: 'voice_assistant_leds' Setting: +[16:22:14.807][D][light:051]: Brightness: 66% +[16:22:14.807][D][light:109]: Effect: 'Thinking' + +[16:22:14.807][D][voice_assistant:515]: State changed from STOPPING_MICROPHONE to AWAITING_RESPONSE +[16:22:14.807][D][voice_assistant:515]: State changed from AWAITING_RESPONSE to AWAITING_RESPONSE + +[16:22:14.807][D][power_supply:033]: Enabling power supply. +[16:22:14.807][D][power_supply:033]: Enabling power supply. + +[16:22:14.807][D][voice_assistant:641]: Event Type: 4 (VOICE_ASSISTANT_STT_END) +[16:22:14.807][D][voice_assistant:669]: Speech recognised as: " Who are you?" +[16:22:14.807][D][voice_assistant:641]: Event Type: 5 (VOICE_ASSISTANT_INTENT_START) +[16:22:14.808][D][voice_assistant:674]: Intent started + +[16:22:14.808][D][power_supply:033]: Enabling power supply. +[16:22:14.808][D][power_supply:033]: Enabling power supply. +[16:22:14.808][D][power_supply:033]: Enabling power supply. +[16:22:14.808][D][power_supply:033]: Enabling power supply. +[16:22:14.808][D][power_supply:033]: Enabling power supply. + +[16:22:14.808][D][voice_assistant:641]: Event Type: 6 (VOICE_ASSISTANT_INTENT_END) +[16:22:14.808][D][voice_assistant:641]: Event Type: 7 (VOICE_ASSISTANT_TTS_START) +[16:22:14.808][D][voice_assistant:697]: Response: "I am an AI assistant designed to help you with various tasks, answer questions, and provide information. If there's anything specific you need help with, feel free to ask!" + +[16:22:14.809][D][light:036]: 'voice_assistant_leds' Setting: +[16:22:14.809][D][light:051]: Brightness: 66% +[16:22:14.809][D][light:109]: Effect: 'Replying' + +[16:22:14.809][D][voice_assistant:641]: Event Type: 8 (VOICE_ASSISTANT_TTS_END) +[16:22:14.809][D][voice_assistant:719]: Response URL: "https://homeassistant.wessman.xyz/api/tts_proxy/wrNRlBud6QN83FrSO0oa1A.flac" +[16:22:14.809][D][voice_assistant:515]: State changed from AWAITING_RESPONSE to STREAMING_RESPONSE +[16:22:14.809][D][voice_assistant:522]: Desired state set to STREAMING_RESPONSE + +[16:22:14.809][D][media_player:080]: 'Media Player' - Setting +[16:22:14.809][D][media_player:087]: Media URL: https://homeassistant.wessman.xyz/api/tts_proxy/wrNRlBud6QN83FrSO0oa1A.flac +[16:22:14.810][D][media_player:093]: Announcement: yes + +[16:22:14.810][D][voice_assistant:641]: Event Type: 2 (VOICE_ASSISTANT_RUN_END) +[16:22:14.810][D][voice_assistant:733]: Assist Pipeline ended + +[16:22:14.810][D][esp-idf:000][ann_read]: I (40514) esp-x509-crt-bundle: Certificate validated + +[16:22:14.810][D][nabu_media_player.pipeline:173]: Reading FLAC file type +[16:22:14.810][D][nabu_media_player.pipeline:184]: Decoded audio has 1 channels, 48000 Hz sample rate, and 16 bits per sample +[16:22:14.810][D][nabu_media_player.pipeline:211]: Converting mono channel audio to stereo channel audio + +[16:22:26.112][D][voice_assistant:515]: State changed from STREAMING_RESPONSE to IDLE +[16:22:26.112][D][voice_assistant:522]: Desired state set to IDLE \ No newline at end of file diff --git a/esphome-va-bridge/esphome_server.py b/esphome-va-bridge/esphome_server.py new file mode 100644 index 0000000..d5431c1 --- /dev/null +++ b/esphome-va-bridge/esphome_server.py @@ -0,0 +1,283 @@ +import asyncio +import hashlib +import logging +import math +import os +import wave +from enum import StrEnum +import traceback + +import aioesphomeapi +from aioesphomeapi import (VoiceAssistantAudioSettings, + VoiceAssistantCommandFlag, VoiceAssistantEventType) +from aiohttp import web + +from audio import STT, TTS, VAD, AudioBuffer, AudioSegment +from llm_api import LlmApi + +logging.basicConfig( + format="%(asctime)s | %(levelname)s | %(funcName)s : %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + level=logging.INFO, +) +routes = web.RouteTableDef() + + +class PipelineStage(StrEnum): + """Stages of a pipeline.""" + + WAKE_WORD = "wake_word" + STT = "stt" + INTENT = "intent" + TTS = "tts" + END = "end" + + +class Satellite: + + def __init__(self, host="192.168.10.155", port=6053, password=""): + self.client = aioesphomeapi.APIClient(host, port, password) + self.audio_queue: AudioBuffer = AudioBuffer(max_size=60000) + self.state = "idle" + self.vad = VAD() + self.stt = STT(device="cuda") + self.tts = TTS() + self.agent = LlmApi() + + async def connect(self): + await self.client.connect(login=True) + + async def disconnect(self): + await self.client.disconnect() + + async def handle_pipeline_start( + self, + conversation_id: str, + flags: int, + audio_settings: VoiceAssistantAudioSettings, + wake_word_phrase: str | None, + ) -> int: + logging.debug( + f"Pipeline starting with conversation_id={conversation_id}, flags={flags}, audio_settings={audio_settings}, wake_word_phrase={wake_word_phrase}" + ) + + # Device triggered pipeline (wake word, etc.) + if flags & VoiceAssistantCommandFlag.USE_WAKE_WORD: + start_stage = PipelineStage.WAKE_WORD + else: + start_stage = PipelineStage.STT + + end_stage = PipelineStage.TTS + + logging.info(f"Starting pipeline from {start_stage} to {end_stage}") + self.state = "running_pipeline" + + # Run pipeline + asyncio.create_task(self.run_pipeline(wake_word_phrase)) + + return 0 + + async def run_pipeline(self, wake_word_phrase: str | None = None): + logging.info(f"Pipeline started using the wake word '{wake_word_phrase}'.") + + try: + logging.debug(" > STT start") + self.client.send_voice_assistant_event(VoiceAssistantEventType.VOICE_ASSISTANT_STT_START, None) + + logging.debug(" > STT VAD start") + self.client.send_voice_assistant_event(VoiceAssistantEventType.VOICE_ASSISTANT_STT_VAD_START, None) + + # VAD + logging.debug(" > VAD: Waiting for silence...") + VAD_TIMEOUT = 1000 + _voice_detected = False + while True: + if self.audio_queue.length() > 20000: + break + elif self.audio_queue.length() >= VAD_TIMEOUT: + voice_detected = self.vad.detect_voice(self.audio_queue.get_last(VAD_TIMEOUT)) + if voice_detected != _voice_detected: + _voice_detected = voice_detected + if not _voice_detected: + logging.debug(" > VAD: Silence detected.") + break + if self.audio_queue.length() > 2000 and not voice_detected: + logging.debug(" > VAD: Silence detected (no voice).") + break + await asyncio.sleep(0.1) + + logging.debug(" > STT VAD end") + self.client.send_voice_assistant_event(VoiceAssistantEventType.VOICE_ASSISTANT_STT_VAD_END, None) + + # STT + text = self.stt.transcribe(self.audio_queue.get()) + self.audio_queue.clear() + logging.info(f" > STT: {text}") + + logging.debug(" > STT end") + self.client.send_voice_assistant_event(VoiceAssistantEventType.VOICE_ASSISTANT_STT_END, {"text": text}) + + logging.debug(" > Intent start") + self.client.send_voice_assistant_event(VoiceAssistantEventType.VOICE_ASSISTANT_INTENT_START, None) + agent_response = self.agent.ask(text)["answer"] + logging.info(f">Intent: {agent_response}") + + logging.debug(" > Intent end") + self.client.send_voice_assistant_event(VoiceAssistantEventType.VOICE_ASSISTANT_INTENT_END, None) + + logging.debug(" > TTS start") + TTS = "announce" + if TTS == "announce": + self.client.send_voice_assistant_event( + VoiceAssistantEventType.VOICE_ASSISTANT_TTS_START, {"text": agent_response} + ) + + voice = "default" + audio_hash_string = hashlib.md5((voice + ":" + agent_response).encode("utf-8")).hexdigest() + audio_file_path = f"static/{audio_hash_string}.wav" + if not os.path.exists(audio_file_path): + logging.debug(" > TTS: Generating audio...") + audio_segment = self.tts.generate(agent_response, voice=voice) + else: + logging.debug(" > TTS: Using cached audio.") + audio_segment = AudioSegment.from_file(audio_file_path) + audio_segment.export(audio_file_path, format="wav") + self.client.send_voice_assistant_event( + VoiceAssistantEventType.VOICE_ASSISTANT_TTS_END, + {"url": f"http://192.168.10.111:8002/{audio_file_path}"}, + ) + elif TTS == "stream": + await self._stream_tts_audio("debug") + logging.debug(" > TTS end") + + except Exception as e: + logging.error("Error: ") + logging.error(e) + logging.error(traceback.format_exc()) + self.client.send_voice_assistant_event(VoiceAssistantEventType.VOICE_ASSISTANT_ERROR, {"message": str(e)}) + + logging.debug(" > Run end") + self.client.send_voice_assistant_event(VoiceAssistantEventType.VOICE_ASSISTANT_RUN_END, None) + logging.info("Pipeline done") + + async def handle_pipeline_stop(self, abort: bool): + logging.info("Pipeline stopped") + self.state = "idle" + + async def handle_audio(self, data: bytes) -> None: + """Handle incoming audio chunk from API.""" + self.audio_queue.put(AudioSegment(data=data, sample_width=2, frame_rate=16000, channels=1)) + + async def handle_announcement_finished(self, event: aioesphomeapi.VoiceAssistantAnnounceFinished): + if event.success: + logging.info("Announcement finished successfully.") + else: + logging.error("Announcement failed.") + + def handle_state_change(self, state): + logging.debug("State change:") + logging.debug(state) + + def handle_log(self, log): + logging.info("Log from device:", log) + + def subscribe(self): + self.client.subscribe_states(self.handle_state_change) + self.client.subscribe_logs(self.handle_log) + self.client.subscribe_voice_assistant( + handle_start=self.handle_pipeline_start, + handle_stop=self.handle_pipeline_stop, + handle_audio=self.handle_audio, + handle_announcement_finished=self.handle_announcement_finished, + ) + + async def _stream_tts_audio( + self, + media_id: str, + sample_rate: int = 16000, + sample_width: int = 2, + sample_channels: int = 1, + samples_per_chunk: int = 1024, # 512 + ) -> None: + """Stream TTS audio chunks to device via API or UDP.""" + + self.client.send_voice_assistant_event(VoiceAssistantEventType.VOICE_ASSISTANT_TTS_STREAM_START, {}) + logging.info("TTS stream start") + await asyncio.sleep(1) + + try: + # if not self._is_running: + # return + + with wave.open(f"{media_id}.wav", "rb") as wav_file: + if ( + (wav_file.getframerate() != sample_rate) + or (wav_file.getsampwidth() != sample_width) + or (wav_file.getnchannels() != sample_channels) + ): + logging.info(f"Can only stream 16Khz 16-bit mono WAV, got {wav_file.getparams()}") + return + + logging.info("Streaming %s audio samples", wav_file.getnframes()) + + rate = wav_file.getframerate() + width = wav_file.getsampwidth() + channels = wav_file.getnchannels() + + audio_bytes = wav_file.readframes(wav_file.getnframes()) + bytes_per_sample = width * channels + bytes_per_chunk = bytes_per_sample * samples_per_chunk + num_chunks = int(math.ceil(len(audio_bytes) / bytes_per_chunk)) + + # Split into chunks + for i in range(num_chunks): + offset = i * bytes_per_chunk + chunk = audio_bytes[offset : offset + bytes_per_chunk] + self.client.send_voice_assistant_audio(chunk) + + # Wait for 90% of the duration of the audio that was + # sent for it to be played. This will overrun the + # device's buffer for very long audio, so using a media + # player is preferred. + samples_in_chunk = len(chunk) // (sample_width * sample_channels) + seconds_in_chunk = samples_in_chunk / sample_rate + logging.info(f"Samples in chunk: {samples_in_chunk}, seconds in chunk: {seconds_in_chunk}") + await asyncio.sleep(seconds_in_chunk * 0.9) + + except asyncio.CancelledError: + return # Don't trigger state change + finally: + self.client.send_voice_assistant_event(VoiceAssistantEventType.VOICE_ASSISTANT_TTS_STREAM_END, {}) + logging.info("TTS stream end") + + +async def start_http_server(port=8002): + app = web.Application() + app.router.add_static("/static/", path=os.path.join(os.path.dirname(__file__), "static")) + runner = web.AppRunner(app) + await runner.setup() + site = web.TCPSite(runner, "0.0.0.0", port) + await site.start() + logging.info(f"HTTP server started at http://0.0.0.0:{port}/") + + +async def main(): + """Connect to an ESPHome device and wait for state changes.""" + await start_http_server() + + satellite = Satellite() + await satellite.connect() + logging.info("Connected to ESPHome voice assistant.") + satellite.subscribe() + satellite.client.send_voice_assistant_event(VoiceAssistantEventType.VOICE_ASSISTANT_RUN_START, None) + logging.info("Listening for events...") + + while True: + await asyncio.sleep(0.1) + + +if __name__ == "__main__": + try: + asyncio.run(main()) + except KeyboardInterrupt: + pass diff --git a/esphome-va-bridge/llm_api.py b/esphome-va-bridge/llm_api.py new file mode 100644 index 0000000..69b3149 --- /dev/null +++ b/esphome-va-bridge/llm_api.py @@ -0,0 +1,13 @@ +import requests + + +class LlmApi: + def __init__(self, host="127.0.0.1", port=8000): + self.host = host + self.port = port + self.url = f"http://{host}:{port}" + + def ask(self, question): + url = self.url + "/ask" + response = requests.post(url, json={"question": question}) + return response.json() diff --git a/esphome-va-bridge/log_reader.py b/esphome-va-bridge/log_reader.py new file mode 100644 index 0000000..8b899e1 --- /dev/null +++ b/esphome-va-bridge/log_reader.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +# Helper script and aioesphomeapi to view logs from an esphome device +import argparse +import asyncio +import logging +import sys +from datetime import datetime + +from aioesphomeapi.api_pb2 import SubscribeLogsResponse # type: ignore +from aioesphomeapi.client import APIClient +from aioesphomeapi.log_runner import async_run + + +async def main(argv: list[str]) -> None: + parser = argparse.ArgumentParser("aioesphomeapi-logs") + parser.add_argument("--port", type=int, default=6053) + parser.add_argument("--password", type=str) + parser.add_argument("--noise-psk", type=str) + parser.add_argument("-v", "--verbose", action="store_true") + parser.add_argument("address") + args = parser.parse_args(argv[1:]) + + logging.basicConfig( + format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s", + level=logging.DEBUG if args.verbose else logging.INFO, + datefmt="%Y-%m-%d %H:%M:%S", + ) + + cli = APIClient( + args.address, + args.port, + args.password or "", + noise_psk=args.noise_psk, + keepalive=10, + ) + + def on_log(msg: SubscribeLogsResponse) -> None: + time_ = datetime.now() + message: bytes = msg.message + text = message.decode("utf8", "backslashreplace") + nanoseconds = time_.microsecond // 1000 + print(f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]{text}") + + stop = await async_run(cli, on_log) + try: + await asyncio.Event().wait() + finally: + await stop() + + +def cli_entry_point() -> None: + """Run the CLI.""" + try: + asyncio.run(main(sys.argv)) + except KeyboardInterrupt: + pass + + +if __name__ == "__main__": + cli_entry_point() + sys.exit(0) diff --git a/external/home-assistant-voice-pe b/external/home-assistant-voice-pe new file mode 160000 index 0000000..4c09d89 --- /dev/null +++ b/external/home-assistant-voice-pe @@ -0,0 +1 @@ +Subproject commit 4c09d89ddbf234546f244bf98b1053a2f89e5ab6 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e34796e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[tool.black] +line-length = 120 \ No newline at end of file