From cd6f76c0ebfadda809dbd359b5bbb06b938384ee Mon Sep 17 00:00:00 2001 From: Jason Esterhuizen Date: Sun, 2 Feb 2025 00:40:50 +0200 Subject: [PATCH 1/4] [REFACT] Removed original code --- .gitignore | 1 + .original/.gitignore | 170 -------- .original/LICENSE | 21 - .original/README.md | 31 -- .original/ace | 13 - .original/app/components/__init__.py | 32 -- .original/app/components/actions/__init__.py | 1 - .original/app/components/actions/actions_map | 113 ----- .original/app/components/actions/component.py | 27 -- .../app/components/controller/__init__.py | 1 - .../app/components/controller/api/__init__.py | 19 - .../components/controller/api/bus/__init__.py | 0 .../components/controller/api/bus/models.py | 17 - .../components/controller/api/bus/routes.py | 41 -- .../components/controller/api/bus/service.py | 47 -- .../controller/api/chat/__init__.py | 0 .../components/controller/api/chat/models.py | 8 - .../components/controller/api/chat/routes.py | 35 -- .../controller/api/dashboard/__init__.py | 0 .../controller/api/dashboard/routes.py | 29 -- .../controller/api/pages/__init__.py | 0 .../components/controller/api/pages/routes.py | 42 -- .../app/components/controller/api/run.py | 60 --- .../controller/api/status/__init__.py | 0 .../controller/api/status/controls.py | 34 -- .../controller/api/user/__init__.py | 0 .../components/controller/api/user/models.py | 14 - .../components/controller/api/user/routes.py | 140 ------ .../components/controller/api/user/service.py | 16 - .../app/components/controller/component.py | 38 -- .../controller/ui/assets/images/ace.webp | Bin 58682 -> 0 bytes .../ui/assets/images/default_user.jpg | Bin 12087 -> 0 bytes .../controller/ui/assets/images/favicon.ico | Bin 4030 -> 0 bytes .../components/controller/ui/assets/style.css | 400 ------------------ .../ui/assets/tailwindcss-colors.css | 269 ------------ .../controller/ui/templates/chat.html | 71 ---- .../components/dashboard/run_button.html | 8 - .../controller/ui/templates/dashboard.html | 3 - .../controller/ui/templates/empty.html | 1 - .../controller/ui/templates/home.html | 9 - .../ui/templates/partials/chat_bubble.html | 8 - .../partials/chat_bubble_wrapper.html | 8 - .../ui/templates/partials/sidebar.html | 82 ---- .../ui/templates/partials/table.html | 7 - .../ui/templates/partials/welcome.html | 4 - .../controller/ui/templates/root.html | 19 - .../controller/ui/templates/test.html | 65 --- .original/app/components/layer/__init__.py | 1 - .original/app/components/layer/component.py | 163 ------- .../components/layer/injections/__init__.py | 2 - .../app/components/layer/injections/inputs.py | 52 --- .../app/components/layer/injections/maps.py | 95 ----- .../app/components/layer/injections/types.py | 105 ----- .../app/components/layer/layer_messages.py | 121 ------ .original/app/components/layer/layers.py | 298 ------------- .original/app/components/layer/presets.py | 116 ----- .../app/components/layer/prompt_builder.py | 19 - .../layer/system_prompts/ace_context | 31 -- .../system_prompts/identities/agent_model | 16 - .../system_prompts/identities/aspirational | 31 -- .../identities/cognitive_control | 33 -- .../identities/executive_function | 19 - .../system_prompts/identities/global_strategy | 21 - .../identities/task_prosecution | 25 -- .../app/components/layer/system_prompts/layer | 20 - .../system_prompts/responses/response_format | 16 - .../responses/schemas/agent_model | 9 - .../responses/schemas/aspirational | 5 - .../responses/schemas/cognitive_control | 17 - .../responses/schemas/executive_function | 11 - .../responses/schemas/global_strategy | 10 - .../responses/schemas/task_prosecution | 7 - .../app/components/model_provider/__init__.py | 2 - .../app/components/model_provider/api.py | 33 -- .../components/model_provider/component.py | 15 - .../components/model_provider/llm/__init__.py | 1 - .../model_provider/llm/factories.py | 116 ----- .../app/components/model_provider/llm/llms.py | 199 --------- .../app/components/model_provider/llm/rag.py | 87 ---- .../app/components/model_provider/provider.py | 196 --------- .original/app/components/queue/__.init__.py | 2 - .original/app/components/queue/broker.py | 61 --- .original/app/components/queue/component.py | 23 - .original/app/components/queue/queue.config | 1 - .../app/components/telemetry/__init__.py | 1 - .original/app/components/telemetry/api.py | 45 -- .../app/components/telemetry/component.py | 11 - .original/app/components/telemetry/scraper.py | 172 -------- .../telemetry/system_prompts/world_overview | 41 -- .original/app/config.py | 5 - .original/app/constants/__init__.py | 0 .original/app/constants/api.py | 38 -- .original/app/constants/arguments.py | 83 ---- .original/app/constants/components.py | 21 - .original/app/constants/containers.py | 143 ------- .original/app/constants/default.py | 38 -- .original/app/constants/generic.py | 36 -- .original/app/constants/layer.py | 87 ---- .original/app/constants/model_provider.py | 94 ---- .original/app/constants/prompts.py | 40 -- .original/app/constants/queue.py | 49 --- .original/app/constants/settings.py | 15 - .original/app/constants/startup.py | 12 - .original/app/constants/telemetry.py | 39 -- .original/app/exceptions/__init__.py | 0 .original/app/exceptions/error_handling.py | 10 - .original/app/helpers.py | 257 ----------- .original/app/main.py | 308 -------------- .original/app/requirements | 23 - .original/app/setup/Containerfile | 12 - .original/app/setup/deployment.yaml | 138 ------ .original/documentation/design_doc.md | 188 -------- .../documentation/media/architecture.png | Bin 92731 -> 0 bytes 113 files changed, 1 insertion(+), 5788 deletions(-) delete mode 100644 .original/.gitignore delete mode 100644 .original/LICENSE delete mode 100644 .original/README.md delete mode 100755 .original/ace delete mode 100644 .original/app/components/__init__.py delete mode 100644 .original/app/components/actions/__init__.py delete mode 100644 .original/app/components/actions/actions_map delete mode 100644 .original/app/components/actions/component.py delete mode 100644 .original/app/components/controller/__init__.py delete mode 100644 .original/app/components/controller/api/__init__.py delete mode 100644 .original/app/components/controller/api/bus/__init__.py delete mode 100644 .original/app/components/controller/api/bus/models.py delete mode 100644 .original/app/components/controller/api/bus/routes.py delete mode 100644 .original/app/components/controller/api/bus/service.py delete mode 100644 .original/app/components/controller/api/chat/__init__.py delete mode 100644 .original/app/components/controller/api/chat/models.py delete mode 100644 .original/app/components/controller/api/chat/routes.py delete mode 100644 .original/app/components/controller/api/dashboard/__init__.py delete mode 100644 .original/app/components/controller/api/dashboard/routes.py delete mode 100644 .original/app/components/controller/api/pages/__init__.py delete mode 100644 .original/app/components/controller/api/pages/routes.py delete mode 100644 .original/app/components/controller/api/run.py delete mode 100644 .original/app/components/controller/api/status/__init__.py delete mode 100644 .original/app/components/controller/api/status/controls.py delete mode 100644 .original/app/components/controller/api/user/__init__.py delete mode 100644 .original/app/components/controller/api/user/models.py delete mode 100644 .original/app/components/controller/api/user/routes.py delete mode 100644 .original/app/components/controller/api/user/service.py delete mode 100644 .original/app/components/controller/component.py delete mode 100644 .original/app/components/controller/ui/assets/images/ace.webp delete mode 100644 .original/app/components/controller/ui/assets/images/default_user.jpg delete mode 100644 .original/app/components/controller/ui/assets/images/favicon.ico delete mode 100644 .original/app/components/controller/ui/assets/style.css delete mode 100644 .original/app/components/controller/ui/assets/tailwindcss-colors.css delete mode 100644 .original/app/components/controller/ui/templates/chat.html delete mode 100644 .original/app/components/controller/ui/templates/components/dashboard/run_button.html delete mode 100644 .original/app/components/controller/ui/templates/dashboard.html delete mode 100644 .original/app/components/controller/ui/templates/empty.html delete mode 100644 .original/app/components/controller/ui/templates/home.html delete mode 100644 .original/app/components/controller/ui/templates/partials/chat_bubble.html delete mode 100644 .original/app/components/controller/ui/templates/partials/chat_bubble_wrapper.html delete mode 100644 .original/app/components/controller/ui/templates/partials/sidebar.html delete mode 100644 .original/app/components/controller/ui/templates/partials/table.html delete mode 100644 .original/app/components/controller/ui/templates/partials/welcome.html delete mode 100644 .original/app/components/controller/ui/templates/root.html delete mode 100644 .original/app/components/controller/ui/templates/test.html delete mode 100644 .original/app/components/layer/__init__.py delete mode 100644 .original/app/components/layer/component.py delete mode 100644 .original/app/components/layer/injections/__init__.py delete mode 100644 .original/app/components/layer/injections/inputs.py delete mode 100644 .original/app/components/layer/injections/maps.py delete mode 100644 .original/app/components/layer/injections/types.py delete mode 100644 .original/app/components/layer/layer_messages.py delete mode 100644 .original/app/components/layer/layers.py delete mode 100644 .original/app/components/layer/presets.py delete mode 100644 .original/app/components/layer/prompt_builder.py delete mode 100644 .original/app/components/layer/system_prompts/ace_context delete mode 100644 .original/app/components/layer/system_prompts/identities/agent_model delete mode 100644 .original/app/components/layer/system_prompts/identities/aspirational delete mode 100644 .original/app/components/layer/system_prompts/identities/cognitive_control delete mode 100644 .original/app/components/layer/system_prompts/identities/executive_function delete mode 100644 .original/app/components/layer/system_prompts/identities/global_strategy delete mode 100644 .original/app/components/layer/system_prompts/identities/task_prosecution delete mode 100644 .original/app/components/layer/system_prompts/layer delete mode 100644 .original/app/components/layer/system_prompts/responses/response_format delete mode 100644 .original/app/components/layer/system_prompts/responses/schemas/agent_model delete mode 100644 .original/app/components/layer/system_prompts/responses/schemas/aspirational delete mode 100644 .original/app/components/layer/system_prompts/responses/schemas/cognitive_control delete mode 100644 .original/app/components/layer/system_prompts/responses/schemas/executive_function delete mode 100644 .original/app/components/layer/system_prompts/responses/schemas/global_strategy delete mode 100644 .original/app/components/layer/system_prompts/responses/schemas/task_prosecution delete mode 100644 .original/app/components/model_provider/__init__.py delete mode 100644 .original/app/components/model_provider/api.py delete mode 100644 .original/app/components/model_provider/component.py delete mode 100644 .original/app/components/model_provider/llm/__init__.py delete mode 100644 .original/app/components/model_provider/llm/factories.py delete mode 100644 .original/app/components/model_provider/llm/llms.py delete mode 100644 .original/app/components/model_provider/llm/rag.py delete mode 100644 .original/app/components/model_provider/provider.py delete mode 100644 .original/app/components/queue/__.init__.py delete mode 100644 .original/app/components/queue/broker.py delete mode 100644 .original/app/components/queue/component.py delete mode 100644 .original/app/components/queue/queue.config delete mode 100644 .original/app/components/telemetry/__init__.py delete mode 100644 .original/app/components/telemetry/api.py delete mode 100644 .original/app/components/telemetry/component.py delete mode 100644 .original/app/components/telemetry/scraper.py delete mode 100644 .original/app/components/telemetry/system_prompts/world_overview delete mode 100644 .original/app/config.py delete mode 100644 .original/app/constants/__init__.py delete mode 100644 .original/app/constants/api.py delete mode 100644 .original/app/constants/arguments.py delete mode 100644 .original/app/constants/components.py delete mode 100644 .original/app/constants/containers.py delete mode 100644 .original/app/constants/default.py delete mode 100644 .original/app/constants/generic.py delete mode 100644 .original/app/constants/layer.py delete mode 100644 .original/app/constants/model_provider.py delete mode 100644 .original/app/constants/prompts.py delete mode 100644 .original/app/constants/queue.py delete mode 100644 .original/app/constants/settings.py delete mode 100644 .original/app/constants/startup.py delete mode 100644 .original/app/constants/telemetry.py delete mode 100644 .original/app/exceptions/__init__.py delete mode 100644 .original/app/exceptions/error_handling.py delete mode 100644 .original/app/helpers.py delete mode 100755 .original/app/main.py delete mode 100644 .original/app/requirements delete mode 100644 .original/app/setup/Containerfile delete mode 100644 .original/app/setup/deployment.yaml delete mode 100644 .original/documentation/design_doc.md delete mode 100644 .original/documentation/media/architecture.png diff --git a/.gitignore b/.gitignore index 06c775a..1a4f604 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Local .logs/ +.original/ .storage/ .user_deployment.yaml .history diff --git a/.original/.gitignore b/.original/.gitignore deleted file mode 100644 index fd5a8ec..0000000 --- a/.original/.gitignore +++ /dev/null @@ -1,170 +0,0 @@ -# App Specific -.config -storage/ -.storage/ -expirements/ -.user_deployment.yaml - -# Mac -.DS_Store - -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/#use-with-ide -.pdm.toml - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ diff --git a/.original/LICENSE b/.original/LICENSE deleted file mode 100644 index d284517..0000000 --- a/.original/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Jay Falls - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/.original/README.md b/.original/README.md deleted file mode 100644 index c9d5bf2..0000000 --- a/.original/README.md +++ /dev/null @@ -1,31 +0,0 @@ -

- - -

- -

🧠 ACE Prototype 🧠

- - -# Dependencies - -- Before running install - - [Podman](https://podman.io/docs/installation) - - [Ollama](https://ollama.com/download) - -# Usage - -```shell -./ace -``` - -## Arguments - -- `-s` Stop the ACE -- `-r` Restart the ACE -- `-u` Update the ACE - -# Documentation - -## Development - -- [Design Documents](./documentation/design_doc.md) diff --git a/.original/ace b/.original/ace deleted file mode 100755 index 341c9e5..0000000 --- a/.original/ace +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -# MAIN -main() { - python3 -m venv .venv - source .venv/bin/activate - cd app || exit - pip install -r requirements #--force-reinstall - ./main.py "$@" - deactivate -} - -main "$@" diff --git a/.original/app/components/__init__.py b/.original/app/components/__init__.py deleted file mode 100644 index 5d49937..0000000 --- a/.original/app/components/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -# DEPENDENCIES -## Built-In -from typing import Callable -## Local -from constants.components import ComponentTypes - - -# SHARED -from .queue import broker - - -# STARTUP -from .controller import component as _controller -from .queue.component import component as _queue -from .telemetry import _telemetry -from .actions import _actions -from .model_provider import _model_provider -from .layer import _layer - -COMPONENT_MAP: dict[str, Callable[[str], None]] = { - ComponentTypes.CONTROLLER: _controller, - ComponentTypes.QUEUE: _queue, - ComponentTypes.TELEMETRY: _telemetry, - ComponentTypes.ACTIONS: _actions, - ComponentTypes.MODEL_PROVIDER: _model_provider, - ComponentTypes.ASPIRATIONAL: _layer, - ComponentTypes.GLOBAL_STRATEGY: _layer, - ComponentTypes.AGENT_MODEL: _layer, - ComponentTypes.EXECUTIVE_FUNCTION: _layer, - ComponentTypes.COGNITIVE_CONTROL: _layer, - ComponentTypes.TASK_PROSECUTION: _layer, -} diff --git a/.original/app/components/actions/__init__.py b/.original/app/components/actions/__init__.py deleted file mode 100644 index 54424df..0000000 --- a/.original/app/components/actions/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .component import component as _actions \ No newline at end of file diff --git a/.original/app/components/actions/actions_map b/.original/app/components/actions/actions_map deleted file mode 100644 index 197b82c..0000000 --- a/.original/app/components/actions/actions_map +++ /dev/null @@ -1,113 +0,0 @@ -[tags] -asynchronous = "This task will not return immedietely, only later" - -[api] -description = "Summary" -functions = ["get", "create"] - -[[api.get]] -description = "What it does" -paramaters = ["route: str", "payload: json"] -returns = ["payload_object: json"] -[[api.create]] -description = "What it does" -paramaters = ["route: str", "payload: json"] -returns = ["none"] - - -[database] -description = "Summary" -functions.resources = ["add_resource", "get_resource"] - -[[database.add_resource]] -description = "What it does" -paramaters = ["route: str", "id: str", "type: str", "name: str", "amount: int"] -returns = "none" -[[database.get_resource]] -description = "What it does" -paramaters = ["route: str", "id: str"] -returns = "resource: json << {id, type, name, amount}" - - -[file] -description = "Summary" -functions = ["get_current_path", "read_file", "write_file"] - -[[file.get_current_path]] -description = "What it does" -paramaters = ["route: str"] -returns = ["path: str"] -[[file.read_file]] -description = "What it does" -paramaters = ["route: str"] -returns = ["file_content: str"] -[[file.write_file]] -description = "What it does" -paramaters = ["route: str"], "content: str"]"] -returns = ["none"] - - -[internet] -description = "Summary" -functions = ["visit_url"] - -[[internet.visit_url]] -description = "What it does" -paramaters = ["url: str"] -returns = ["url_content: str"] - - -[math] -description = "Summary" -functions = ["evaluate_formula"] - -[[math.evaluate_formula]] -description = "What it does" -paramaters = ["formula: str"] -returns = ["result: float"] - - -[memory] -description = "Summary" -functions = ["remember", "add_memory"] - -[[memory.remember]] -description = "What it does" -paramaters = ["keywords: list[str]"] -returns = ["memories: list[str]"] -[[memory.add_memory]] -description = "What it does" -paramaters = ["keywords: list[str]", "value: str"] -returns = "none" - - -[shell] -description = "Summary" -functions = ["run_command"] - -[[shell.run_command]] -description = "What it does" -paramaters = ["command: str"] -returns = ["output: str"] - - -[speak] -description = "Summary" -functions = ["speak"] - -[[speak.speak]] -description = "What it does" -paramaters = ["text: str"] -returns = "none" - - -[workflows] -description = "Summary" -details = ["workflow1 summary", "workflow2 summary"] -functions = ["run", "build_tool"] - -[[workflows.run]] -description = "What it does" -tags = ["asynchronous"] -paramaters = ["workflow_name: str"] -returns = "none" diff --git a/.original/app/components/actions/component.py b/.original/app/components/actions/component.py deleted file mode 100644 index c73c492..0000000 --- a/.original/app/components/actions/component.py +++ /dev/null @@ -1,27 +0,0 @@ -# DEPENDENCIES -## Built-in -import asyncio -## Third-Party -from nats.aio.client import Client as NatsClient -## Local -from components import broker -from constants.settings import DebugLevels -from helpers import debug_print - - -async def nats_test(queue: str) -> None: - nats_client = NatsClient() - try: - nats_client, stream = await broker.connect() - await asyncio.sleep(5) - for i in range(10): - await broker.publish(stream=stream, queue=queue, message=f"Hi from {queue}\nMsg: {i}") - while True: - pass - except Exception as error: - debug_print(f"ConnectionClosedError: {error}...", DebugLevels.ERROR) - await nats_client.close() - -# MAIN -def component(component_type: str) -> None: - asyncio.run(nats_test(queue=component_type)) \ No newline at end of file diff --git a/.original/app/components/controller/__init__.py b/.original/app/components/controller/__init__.py deleted file mode 100644 index f302a94..0000000 --- a/.original/app/components/controller/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .component import component \ No newline at end of file diff --git a/.original/app/components/controller/api/__init__.py b/.original/app/components/controller/api/__init__.py deleted file mode 100644 index 6a2f348..0000000 --- a/.original/app/components/controller/api/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# DEPENENCIES -## Third-Party -from fastapi import APIRouter -## Local -from .bus.routes import router as _bus_router -from .chat.routes import router as _chat_router -from .dashboard.routes import router as _dashboard_router -from .pages.routes import router as _pages_router -from .user.routes import router as _user_router - - -# CONSTANTS -ROUTERS: tuple[APIRouter, ...] = ( - _bus_router, - _chat_router, - _dashboard_router, - _pages_router, - _user_router -) \ No newline at end of file diff --git a/.original/app/components/controller/api/bus/__init__.py b/.original/app/components/controller/api/bus/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/.original/app/components/controller/api/bus/models.py b/.original/app/components/controller/api/bus/models.py deleted file mode 100644 index ada7cbc..0000000 --- a/.original/app/components/controller/api/bus/models.py +++ /dev/null @@ -1,17 +0,0 @@ -# DEPENDENCIES -## Third-Party -from typing import Literal -from pydantic import BaseModel -## Local -from components.layer.layer_messages import LayerMessage - - -# REQUESTS -class BusMessage(BaseModel): - source_queue: str - layer_message: LayerMessage - - -# RESPONSES -class BusResponse(BaseModel): - success: bool diff --git a/.original/app/components/controller/api/bus/routes.py b/.original/app/components/controller/api/bus/routes.py deleted file mode 100644 index 5a6c667..0000000 --- a/.original/app/components/controller/api/bus/routes.py +++ /dev/null @@ -1,41 +0,0 @@ -# DEPENDENCIES -## Third-Party -from fastapi import APIRouter -from starlette import responses -## Local -from constants.api import APIRoutes -from constants.queue import BusKeys -from constants.layer import LayerKeys, LayerCommands -from components.layer.layer_messages import LayerSubMessage -from .models import BusMessage, BusResponse -from .service import bus_down, bus_up - - -# CONSTANTS -VONE_CHAT_ROUTE: str = f"{APIRoutes.VONE}/bus" - - -# API -router = APIRouter( - prefix=VONE_CHAT_ROUTE, - tags=["bus"], - responses={404: {"description": "Not found"}} -) - - -# ROUTES -@router.post(f"/{BusKeys.DOWN}", response_model=BusResponse) -async def down(bus_message: BusMessage) -> BusResponse: - # VERIFY PAYLOAD HERE - print(f"Bus Message: {bus_message.model_dump_json()}") - bus_down(bus_message) - response = BusResponse(success=True) - return response - -@router.post(f"/{BusKeys.UP}", response_model=BusResponse) -async def up(bus_message: BusMessage) -> BusResponse: - # VERIFY PAYLOAD HERE - bus_up(bus_message) - response = BusResponse(success=True) - return response - \ No newline at end of file diff --git a/.original/app/components/controller/api/bus/service.py b/.original/app/components/controller/api/bus/service.py deleted file mode 100644 index 9d96591..0000000 --- a/.original/app/components/controller/api/bus/service.py +++ /dev/null @@ -1,47 +0,0 @@ -# DEPENDENCIES -## Built-in -import asyncio -from threading import Thread -## Third-Party -from nats.aio.client import Client as NatsClient -from nats.js.client import JetStreamContext -from pydantic import ValidationError -## Local -from components import broker -from components.layer.layer_messages import LayerMessage -from constants.queue import BusKeys, BUSSES_DOWN, BUSSES_UP -from .models import BusMessage - - -# QUEUE -async def _connect_and_publish(queue: str, layer_message: LayerMessage) -> None: - nats_client: NatsClient - nats_stream: JetStreamContext - nats_client, nats_stream = await broker.connect() - await broker.publish(stream=nats_stream, queue=queue, message=layer_message.model_dump_json()) - await nats_client.close() - -def _bus_publish(bus_direction: str, bus_message: BusMessage) -> None: - try: - queue_mapping: dict[str, str] = BUSSES_DOWN if bus_direction == BusKeys.DOWN else BUSSES_UP - queue: str = queue_mapping.get(bus_message.source_queue, "") - if not queue: - print(f"Queue {bus_message.source_queue} cannot send {bus_direction}!") - return - - layer_message: LayerMessage = bus_message.layer_message - print(f"Sending {layer_message.message_type}: {layer_message.messages} to {queue}...") - - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - thread = Thread(target=loop.run_until_complete, args=(_connect_and_publish(queue, layer_message),)) - thread.start() - thread.join() - except ValidationError as error: - raise error - -def bus_down(bus_message: BusMessage) -> None: - _bus_publish(BusKeys.DOWN, bus_message) - -def bus_up(bus_message: BusMessage) -> None: - _bus_publish(BusKeys.UP, bus_message) diff --git a/.original/app/components/controller/api/chat/__init__.py b/.original/app/components/controller/api/chat/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/.original/app/components/controller/api/chat/models.py b/.original/app/components/controller/api/chat/models.py deleted file mode 100644 index c2bca80..0000000 --- a/.original/app/components/controller/api/chat/models.py +++ /dev/null @@ -1,8 +0,0 @@ -# DEPENDENCIES -## Third-Party -from pydantic import BaseModel - - -class ChatMessage(BaseModel): - input_message: str - is_ace_previous: bool diff --git a/.original/app/components/controller/api/chat/routes.py b/.original/app/components/controller/api/chat/routes.py deleted file mode 100644 index a4b4c7f..0000000 --- a/.original/app/components/controller/api/chat/routes.py +++ /dev/null @@ -1,35 +0,0 @@ -# DEPENDENCIES -## Built-In -import datetime -## Third-Party -from fastapi import APIRouter, Request, status -from starlette.templating import _TemplateResponse -## Local -from constants.api import HTML_TEMPLATES, APIRoutes -from .models import ChatMessage - - -# CONSTANTS -VONE_CHAT_ROUTE: str = f"{APIRoutes.VONE}/chat" - - -# API -router = APIRouter( - prefix=VONE_CHAT_ROUTE, - tags=["chat"], - responses={404: {"description": "Not found"}}, -) - - -# ROUTES -@router.post("/listen", response_model=ChatMessage, status_code=status.HTTP_201_CREATED) -async def listen(request: Request, chat_message: ChatMessage) -> _TemplateResponse: - message: str = chat_message.input_message - time = datetime.datetime.now(datetime.UTC) - context: dict = {"request": request, "message": message, "time": time.strftime("%H:%M:%S")} - template_html_path: str = "partials/chat_bubble.html" - if chat_message.is_ace_previous: - user_type_context: dict = {"user_type": "me"} - context.update(user_type_context) - template_html_path = "partials/chat_bubble_wrapper.html" - return HTML_TEMPLATES.TemplateResponse(template_html_path, context) \ No newline at end of file diff --git a/.original/app/components/controller/api/dashboard/__init__.py b/.original/app/components/controller/api/dashboard/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/.original/app/components/controller/api/dashboard/routes.py b/.original/app/components/controller/api/dashboard/routes.py deleted file mode 100644 index a441f10..0000000 --- a/.original/app/components/controller/api/dashboard/routes.py +++ /dev/null @@ -1,29 +0,0 @@ -# DEPENDENCIES -## Third-Party -from fastapi import APIRouter, Request -from starlette.responses import HTMLResponse -from starlette.templating import _TemplateResponse -## Local -from constants.api import HTML_TEMPLATES, APIRoutes -from ..status.controls import start_ace - - -# CONSTANTS -VONE_CHAT_ROUTE: str = f"{APIRoutes.VONE}/dashboard" - - -# API -router = APIRouter( - prefix=VONE_CHAT_ROUTE, - tags=["dashboard"], - responses={404: {"description": "Not found"}} -) - - -# ROUTES -@router.get("/power_on_self_test", response_class=HTMLResponse) -async def start(request: Request) -> _TemplateResponse: - context: dict = {"request": request} - start_ace() - return HTML_TEMPLATES.TemplateResponse("components/dashboard/run_button.html", context) - \ No newline at end of file diff --git a/.original/app/components/controller/api/pages/__init__.py b/.original/app/components/controller/api/pages/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/.original/app/components/controller/api/pages/routes.py b/.original/app/components/controller/api/pages/routes.py deleted file mode 100644 index acb03cd..0000000 --- a/.original/app/components/controller/api/pages/routes.py +++ /dev/null @@ -1,42 +0,0 @@ -# DEPENDENCIES -## Third-Party -from fastapi import APIRouter, Request -from starlette.responses import HTMLResponse -from starlette.templating import _TemplateResponse -## Local -from constants.api import HTML_TEMPLATES - - -# API -router = APIRouter( - tags=["pages"], - responses={404: {"description": "Not found"}} -) - - -# PAGES -@router.get("/home", response_class=HTMLResponse) -async def home(request: Request) -> _TemplateResponse: - context: dict = {"request": request} - return HTML_TEMPLATES.TemplateResponse("home.html", context) - -## Sub Pages -@router.get("/empty", response_class=HTMLResponse) -async def empty(request: Request) -> _TemplateResponse: - context: dict = {"request": request} - return HTML_TEMPLATES.TemplateResponse("empty.html", context) - -@router.get("/welcome", response_class=HTMLResponse) -async def welcome(request: Request) -> _TemplateResponse: - context: dict = {"request": request} - return HTML_TEMPLATES.TemplateResponse("partials/welcome.html", context) - -@router.get("/dashboard", response_class=HTMLResponse) -async def controls(request: Request) -> _TemplateResponse: - context: dict = {"request": request} - return HTML_TEMPLATES.TemplateResponse("dashboard.html", context) - -@router.get("/chat", response_class=HTMLResponse) -async def chat(request: Request) -> _TemplateResponse: - context: dict = {"request": request} - return HTML_TEMPLATES.TemplateResponse("chat.html", context) \ No newline at end of file diff --git a/.original/app/components/controller/api/run.py b/.original/app/components/controller/api/run.py deleted file mode 100644 index 62fdc60..0000000 --- a/.original/app/components/controller/api/run.py +++ /dev/null @@ -1,60 +0,0 @@ -# DEPENDENCIES -## Built-In -from typing import Optional -## Third-Party -from fastapi import FastAPI, Request, Header -from fastapi.responses import FileResponse -from fastapi.staticfiles import StaticFiles -from starlette.responses import HTMLResponse -from starlette.templating import _TemplateResponse -## Local -from . import ROUTERS -from constants.api import APIPaths, HTML_TEMPLATES -from .user.service import test_toml - - -# SETUP -app = FastAPI() -_ = [app.include_router(router) for router in ROUTERS] -app.mount("/assets", StaticFiles(directory=f"{APIPaths.UI}/assets"), name="assets") - - -# ROUTES -@app.get("/favicon.ico", include_in_schema=False) -async def favicon() -> FileResponse: - return FileResponse(APIPaths.FAVICON) - -@app.get("/", response_class=HTMLResponse) -async def root(request: Request) -> _TemplateResponse: - context: dict = {"request": request} - #test_toml() - return HTML_TEMPLATES.TemplateResponse("root.html", context) - - -# TESTING -## Constants -example_table: tuple[dict[str, str], ...] = ( - { - "test1": "Polompon", - "test2": "Radical", - "test3": "Psoso" - }, - { - "test1": "Trumpin", - "test2": "Restified", - "test3": "Adalac" - } -) - -## Routes -@app.get("/test", response_class=HTMLResponse) -async def test(request: Request, hx_request: Optional[str] = Header(default=None)) -> _TemplateResponse: - context: dict = {"request": request, "name": "ACE Controller Dashboard", "tests": example_table} - if hx_request: - return HTML_TEMPLATES.TemplateResponse("partials/table.html", context) - return HTML_TEMPLATES.TemplateResponse("test.html", context) - -@app.get("/test2", response_class=HTMLResponse) -async def test2(request: Request) -> _TemplateResponse: - context: dict = {"request": request, "name": "ACE Controller Dashboard", "tests": example_table} - return HTML_TEMPLATES.TemplateResponse("test.html", context) diff --git a/.original/app/components/controller/api/status/__init__.py b/.original/app/components/controller/api/status/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/.original/app/components/controller/api/status/controls.py b/.original/app/components/controller/api/status/controls.py deleted file mode 100644 index b2208a1..0000000 --- a/.original/app/components/controller/api/status/controls.py +++ /dev/null @@ -1,34 +0,0 @@ -# DEPENDENCIES -## Built-in -from typing import Union -## Local -from components.layer.layer_messages import LayerMessage -from constants.layer import LayerKeys, LayerCommands -from constants.queue import Queues -from ..bus.service import bus_down -from ..bus.models import BusMessage - - -# PRIVATE -def _power_on_self_test() -> None: - commands_dict: dict[str, Union[str, dict[str, str]]] = { - LayerKeys.MESSAGE_TYPE: LayerKeys.COMMANDS, - LayerKeys.MESSAGES: { - LayerKeys.HEADING: LayerKeys.ACTIONS, - LayerKeys.CONTENT: LayerCommands.POST - } - } - commands_message: LayerMessage = LayerMessage.model_validate(commands_dict) - post_payload = BusMessage(source_queue=Queues.CONTROLLER, layer_message=commands_message) - try: - bus_down(post_payload) - except Exception as error: - raise error - - -# PUBLIC -def start_ace() -> None: - try: - _power_on_self_test() - except Exception as error: - raise error diff --git a/.original/app/components/controller/api/user/__init__.py b/.original/app/components/controller/api/user/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/.original/app/components/controller/api/user/models.py b/.original/app/components/controller/api/user/models.py deleted file mode 100644 index 0674b4a..0000000 --- a/.original/app/components/controller/api/user/models.py +++ /dev/null @@ -1,14 +0,0 @@ -# DEPENDENCIES -## Third-Party -from pydantic import BaseModel - - -class User(BaseModel): - username: str - -class Token(BaseModel): - access_token: str - token_type: str - -class TokenData(BaseModel): - username: str | None = None \ No newline at end of file diff --git a/.original/app/components/controller/api/user/routes.py b/.original/app/components/controller/api/user/routes.py deleted file mode 100644 index b69ae07..0000000 --- a/.original/app/components/controller/api/user/routes.py +++ /dev/null @@ -1,140 +0,0 @@ -# DEPENDENCIES -## Built-In -from datetime import datetime, timedelta, timezone -from typing import Annotated -## Third-Party -from fastapi import APIRouter, Depends, HTTPException, status -from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm -from jose import JWTError, jwt -from passlib.context import CryptContext -## Local -from .models import User, Token, TokenData - - -# API -router = APIRouter( - tags=["user"], - responses={404: {"description": "Not found"}} -) - - -# to get a string like this run: -# openssl rand -hex 32 -SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" -ALGORITHM = "HS256" -ACCESS_TOKEN_EXPIRE_MINUTES = 30 - - -fake_users_db = { - "johndoe": { - "username": "johndoe", - "full_name": "John Doe", - "email": "johndoe@example.com", - "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", - "disabled": False, - } -} - - - - - -class UserInDB(User): - hashed_password: str - - -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") - -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - - -def verify_password(plain_password, hashed_password): - return pwd_context.verify(plain_password, hashed_password) - - -def get_password_hash(password): - return pwd_context.hash(password) - - -def get_user(db, username: str): - if username in db: - user_dict = db[username] - return UserInDB(**user_dict) - - -def authenticate_user(fake_db, username: str, password: str): - user = get_user(fake_db, username) - if not user: - return False - if not verify_password(password, user.hashed_password): - return False - return user - - -def create_access_token(data: dict, expires_delta: timedelta | None = None): - to_encode = data.copy() - if expires_delta: - expire = datetime.now(timezone.utc) + expires_delta - else: - expire = datetime.now(timezone.utc) + timedelta(minutes=15) - to_encode.update({"exp": expire}) - encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) - return encoded_jwt - - -async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): - credentials_exception = HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Could not validate credentials", - headers={"WWW-Authenticate": "Bearer"}, - ) - try: - payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") - if username is None: - raise credentials_exception - token_data = TokenData(username=username) - except JWTError: - raise credentials_exception - user = get_user(fake_users_db, username=token_data.username) - if user is None: - raise credentials_exception - return user - - -async def get_current_active_user( - current_user: Annotated[User, Depends(get_current_user)] -): - return current_user - - -@router.post("/token") -async def login_for_access_token( - form_data: Annotated[OAuth2PasswordRequestForm, Depends()] -) -> Token: - user = authenticate_user(fake_users_db, form_data.username, form_data.password) - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Incorrect username or password", - headers={"WWW-Authenticate": "Bearer"}, - ) - access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) - access_token = create_access_token( - data={"sub": user.username}, expires_delta=access_token_expires - ) - return Token(access_token=access_token, token_type="bearer") - - -@router.get("/users/me/", response_model=User) -async def read_users_me( - current_user: Annotated[User, Depends(get_current_active_user)] -): - return current_user - - -@router.get("/users/me/items/") -async def read_own_items( - current_user: Annotated[User, Depends(get_current_active_user)] -): - return [{"item_id": "Foo", "owner": current_user.username}] \ No newline at end of file diff --git a/.original/app/components/controller/api/user/service.py b/.original/app/components/controller/api/user/service.py deleted file mode 100644 index 10328ca..0000000 --- a/.original/app/components/controller/api/user/service.py +++ /dev/null @@ -1,16 +0,0 @@ -# DEPENDENCIES -## Third-Party -import toml -## Local -from constants.api import APIPaths - - -def test_toml() -> None: - runtime_config: dict = {} - with open(APIPaths.RUNTIME_CONFIG, 'r', encoding='utf-8') as config_file: - config_str: str = config_file.read() - runtime_config = toml.loads(config_str).get('runtime', {}) - settings: dict = {} - with open(runtime_config.get('settings_path', ''), 'r', encoding='utf-8') as settings_file: - settings_str: str = settings_file.read() - settings = toml.loads(settings_str) diff --git a/.original/app/components/controller/component.py b/.original/app/components/controller/component.py deleted file mode 100644 index a76cc74..0000000 --- a/.original/app/components/controller/component.py +++ /dev/null @@ -1,38 +0,0 @@ -# DEPENDENCIES -## Built-in -import os -from typing import Union -## Third-Party -import toml -import uvicorn -## Local -from .api import run -from constants.api import APIPaths -from constants.containers import DevVolumePaths, ComponentPorts - - -# SETUP -def setup(local_mode: bool) -> None: - print("\nSetting Up Controller Files...") - settings_path: str = APIPaths.SETTINGS - if local_mode: - dirs: tuple[str, ...] = (DevVolumePaths.CONTROLLER, DevVolumePaths.OUTPUT) - _ = [os.makedirs(dirs, exist_ok=True) for dirs in dirs] - settings_path = DevVolumePaths.CONTROLLER_SETTINGS - if not os.path.exists(settings_path): - open(settings_path, 'a').close() - - with open(APIPaths.RUNTIME_CONFIG, 'w', encoding='utf-8') as config_file: - runtime_config: dict[str, Union[bool, str]] = { - "local_mode": local_mode, - "settings_path": settings_path - } - toml.dump({'runtime': runtime_config}, config_file) - - -# MAIN -def component(component_type: str) -> None: - local_mode: bool = False - setup(local_mode) - print("\nStarting Server Client...") - uvicorn.run(run.app, host="0.0.0.0", port=int(ComponentPorts.CONTROLLER)) diff --git a/.original/app/components/controller/ui/assets/images/ace.webp b/.original/app/components/controller/ui/assets/images/ace.webp deleted file mode 100644 index b76aaa14446f3c3f6ea75c534f5152d19b7811be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58682 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 diff --git a/.original/app/components/controller/ui/assets/images/default_user.jpg b/.original/app/components/controller/ui/assets/images/default_user.jpg deleted file mode 100644 index 9805553c60066b53089bd40a8722b6a23c2999fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12087 zcmd^lcUV(d*KZI69Hc6}H<8{ufdLc@2uSZR5LyTX2%%RSD4o!xD$=A2NN=J-=tvE{ zgdRFdZx?6A8Rs4E`+fi2Kkj~>oM)e1)>?a&-&tqnT#R1K0ch@eL+k(m5Qqmr2>82Q ztOLmJcz~fk02~0`<**F^a50A)?tnnJ$_NU=+y$&`T&!&cz%I^$-d3)H!U95q067J3 zS1T~o7Qtd|YY%||*|zGN*;pVpKsG~h4IvFzWm^ZxJzu!3uCJyZ*cS?xwqa9{XOZ)k z@pg7~wnbR6cso16+-1ChY`+tix$J)%7Gz`jeF*{zWK;acmBmQo0gJK=+?GXLK!P7E zBrL`vDJ>u@E+Hu`$;Tpcc`qm=B`6}qFC;A^E-E7=!t!ThyW|G9v6Iocqw*)O%R3<3 zpNI1D@)Gb86>x#u3kpk1OA88#2#SdCU#{SH_kkg-y!m17?B6Nev2_Q-A+89B3ykF( zMJsC;4+N0yQqn(;;OwfQ@fYErYRlRA+qr&U?T*l~{acKmu6Eb+akUlHv2}OxfP-x> zm$QH0e5t$t&gk31OKD`Z;E+qBSUKHs0ed*x!VsW4K(@;n0UL;ojF7mLkg%|%n2@B1 zkc5=5u#}LLsI-Wv(w)1?qEgaQV&7@}Z0}v6JJLcTDvH9QDk8$dcf}P&gr)9?DvC;p z+>sU)k(BY?Z3&O?S!NrwD zS^0O1v)tCO0z+Wm2D!f(>5nttv4ulCZEaNGF3v2!Ygq>J6Dh2O#e~JJ#H9Jb!eUbV zV%9cN{8qNs!u%2<64KIkU@Ne#tpwYj`)z*E-XC6iIdXXt*Y5(!*a(BI?Ck7p`NhHF zVE#+3UQ)A@vg4PM2HT0*fkh?3c6Mxnmm&&&bDSR>>HE;7cYf>s6&9B_e+8B;>@u3* zmtk|U2_X4B7yvf!FT)e(Vgm34fb+j^SN?9ef3-iiU%|n-oW1=0HSxdgT)YR66XNjU z@ZsW+1Fn$c;F9B9d;*kTV#p=2OX`2&3*j|<0vx=nxL1C;9K4+QYvS8kuHfQbB?I8# z;ox4u#mBvR_3E|D`AZU)!{ikBtOUY02o>30gKu(ZTZy900qGLIT;=f;Mf0uFCMVzoXBGS$)|NgkoN;e#ikiD zdl-iuM4Z}^O8<#k_A-&%utdB!GA{0Q_h*K}AvEkQX-buVXV+BRn-g%2VfIXF@A5e$=m8pd*0tc3@0D6NMAQ)FlNKX|HT4-5VK)a1=WakUdLO?h zQ{a@3xQS5J(}zAu_cK??GpUpO9DydsLhD}iaaL_wRg~T7pfN_aR$a3bU|NVXMs?^f z_A?U~15WgOb&Zr3keK+UYfb(6&gzpem^5~V+-m1z=V9f##j?Z6XgFWT2pAMu>|Urg zJ`zmV*gNP007z0q&O{H=3XnvXEl&(-u&Mn@>;N6GM$APl~nA8ueLQa?Iu%ZS2&oGyf;QcD?5y;D+;x z0we<<&;bDfIP@U>1wzzhpUw*sUd{*w@-PyOv}aA+>qmJyNxS>&uFXdzPDh&iqj$PTNO$dC6Dbn@+1|M+T!AlQVG z@CqQ9h42dQnni1vCtI~zPnf~1tG~o*A3s#6G0&imd(NE${E+FxYwd{nT!_@BP3~Yp z3(~!MIfX-jxFp=^SH6e^3>4fq9Bt0X@r0@+o*05w#aF{9=(K*=Yj6f?pOkxF z0Qj0HO4ay{x_TZ@H}NixYHA90Ngar^DksnT$Q;Rt1T(CdXICzcGhp{;J&_}2Om)&F zfb;%glc`bjVxHUzE&Pn7#>AyNujezgAhE@2PyQRKc)+IeBlGRfPJ{+?G_UcES6ZvI zUiD^$_#?a6+o3dVkszM(cq?jdnY$EmuyBz^Kh(@=v}hul2_ta}<;Hm5KtMd|(Ye1^ zjG5W&SOM5(2MR>DiwO*VEuyTogqeLfEhtdVayqyGkf^;Fo-NJ4tHb16y7*4 zv;E2YpofA^_jc=k!TYBbnNZAqi-%Qqp)BtwJori^toVH%E6@9BfuP3Er`PwKRMag# zVn{Vep6V>tOoG(Kuo*)KrA`Ld6Sx=*4`276m`|n|x=Sti_$pSy#nZ-Xi?n0p%>riM z&*wd!(s_T7>4WP!3$Rt zhcrObOU2B?YNCkorNEL}h5bUJl_%1mtz*12lGM$7pMGhW;6XE8f5 zimLH>C21Zq9E85{Ox8?nn-lCikuIRTmzhLa1*F(S*v|2ITgEPb!SX|?Y+PDeVpy&Uq@3!O?JB;YdlYxsw{Z>@;K2+x`;V7(aV1ILm{mk#q{(w&jYHkLKJ@^ zy;?-~lHe{wlR7XVrYp8qDYFLrDbO4f|NWUfKU0fM=|GChc&)wdVo2b2*hJEjsIB)iS%CU$eo)+MZxc7 zETJMh(2^!-=Pzzm47@hhZj&up1)cLlt8eJOu9P=28ekjxeUsFV3QEd9Ba#S0Aui-gzp{rVx&uO$9iJ6p!ZXasWZ!@9XS@l2Dci~jI1R5$ zD+y@*Y`vixT%Zs}*S;hC%)TF4^P;OnMrq@fIBFWVq>!FpO#pM8J)^T->S>C#@D^w6 z+Bi%}aQdmH=6XmVkZo6%3xGpE4rD5PL2x0x7*l=Aea0@G%c>Z-;pf%!EOy9?0Jtk} zZvDKj;ib=bI>Dp1z7TRNeToEgs(} z8LHFZzy?uLjikD1QRjf0uOEN8Wnn+yLD~7_pYZaBr{nE^jgA#{Bhd=ui#wG>67k0l zz#_Xl=GPkTGT6N2?F=xahV-`{A7aa+sFyQ%1gD!Q`A)-gPR}{YO%i^leL0vad*=Mu zs%a*|$SFILz?>*q#fuoRmYZpytsDNhqrelH&S+pOZpisrX&$rkV*bSJ^ZfZhQ38c& zEWy~{A@^fXgY+JCa1gMKYkYgT&pG!pOHu zGl%S8h00=Lg-xAw9lw530_FAWpCSX^z}JU&Lyr$`dHFmtqXK3e)PQ$gOyUe6$B)yd zNSf%Lrn7BgQ6|y)^hd8pS93e#j2-lT7KKMW*F9Mi8+vAL?sQx)8j9@i96(hY#HcLQ zM5XJg@o2jxB^2PTU@N!_`J>}*Bz0kOp6)HQ*~0ah@#<(`S(UcRF9&$t=9St`bBBjh9)%o4|a1;VKF-@sYmUlO8dIBwxpc@S5F&&oFnvI_F zI2c5RvB|REy}{A)C`r3jWLU}RjJs3CF6ZFJq*`VE&lC&@F3dQ08$`+Gwa+^_M^!ns zv==y8ff*)aT&q-8Io#6(TLhL7S3j))jK)m#iqFPm5ORi%C9elt^Ia^~iDjyIzDxF_ zw1H-`BekfOS1%dm@SC_=aG=1X`Q|~!a*PqWbA}`n4Zym9IRMBVKi4!8o)kD94oE8Pach^Jh*=Ncb3w3QhGk6O<^tkuE^h(z>Wl z`-W!nab>aG(=1oYEx?rKt7`%29xaE0`A#VV#|FY1t)A3IEyab*$yW^)e*)Nq*O#jr zG-1xY-Um!!q8v9Y!j|qwJ>t_KsOnj6JSOYespx{RU=JQ&8Qs-TH6k2a7TY#bE;T+P9N}=8rv;Bx7CR6_P5NKqz9Q0#*HP!tmD3uC`I*kh zm;~8dbQca8s+rq~AxpQ7GNp-0-09_PUmFl0zOV&GK~|K5SsoADdotxz0(mq6Q4A zkTUx@9#Q8s_Ssu)Q3>#`e2ObCrv$KWK(Q>-VaV6mPXDzgNVwjTih?Ut#_kagE~{;Q zD5kw-n>U3yHDKxjpvgqD6Qq}fFjSs)I2F9DfKSG|Y7<=ZBzD*cMs0!4oEN&s?xx6eE<{!QM8WG!H_dWZt( z^$tpPsQk#k=4-+^p(`GvBHg39`@IkTGtlt|HmIG84+ImGqxoWAT!+q8w{KRzvISaz zvu^Ccs>(^Df5X*jgL$S?QRngTO`I7!pOj5-ZALSAitMQO2=Z+CHTlb^YZe+b(p9eN zJx#z4J>nUD72zC3!vY3}-691WwFN6!kB~*yyiwHQ)~0l>HcXZM<8lCIW0Wk=2S6t<8NXi2Xh<<>GDrzJ6|4N!MW zw2^(rv@XbjfZ({RSXLn`Wdw}-@1CI#Y(H)`G$p<5FV%Lm2q+Aq+}U93i`E`uzET(Q z;hz@0o)zl4zAW3PKY#d3Z{pB$K)3+(JdRO6`x?wFp_o5P0{1ghsT;sit#e}r#KUiB znJ9a%ZEDe*!qy*^7e#K|>|7syZ(|Ghljl~X?)+gcL7k9YyA9KOzJ)E&2-JWyg@cNs$ zGZP|_+}5!VifVnE??a+^Bid=S@+||J&a?t8Zy%PFqZalp2tOKw^Brv zh#ZUsJ&GdrHRiK!R?o}DulwK;o8p)Aenna@r!$8Znua8uib8ccBbgcQrY7M9$!jqjE3}g`!V3Y*pJP`yYkDLhPZy1oxjAB?^e8&7w6^v zNmWB?t+cTERU?!Vtx?3nuuWFSOV%KjN*#y)c?!}W!Dt{Ucw2qo(XiijNvn%TW@0QB^IpUK;{&P&HpVveLf%DtQPjgwKMoCV|az*RE)yI|NXE-g^tw%Q3(#RUg z$zboBRAko2`If3nezaD%F=!pZ?ocEcyeLp~I&54YC4xWv5pw?pDfpqA_P=?*R6rc5 zy*zV|yJyZ`KJZ-&FzD~#?rmW^+Q6ztihfG4xirNHxaXix8%tF|2``C24Vmvv( z_icuhef7o~%Z%g(soX5Zz0)=(^%qrf6|&EuoAXRlnj!F!v%Azaq=TaLM-(Nctw2eG z=WZQe)dj}oag`m*TD|t^+NDeADqBA8;E|uVmMO>op+=k)jL=b_vacMBfYwhq(9qe| zEWm5j8z_z_aoC*&E8^8|LVpFSWp66Yr1v)@qCZ#IWG^O~Pi0qcY~}dr%3pu!Rw6=Z zGRHD6ReR8Jn=Yn1(SQrTEPJ((1+6{k+fjeu-<(8z0nl9;V@k7Bbis4qk>9GGygw;V zPl?*u+c7esEUUp5U4N8w?~}GrpbV+craW3Ii5X&IT1lim=&bE$H!WCH7?)_D`;O&j zkNxjsVFiN@zx?_u8FNw@QnbD`f`kqysP_c-Lu3VfoX& z>Lt+0i3VxP*XKfko08q*U;(df(a{aFHo}RwL`DbI-R?PP9cx%55Qn|!0?=**q%hsl zirU*`Z)x>P892xlSR2|s&&b8S&0I3CB{q7WgBTHG!#gxd`u1J@7aX85r?Dy0pDyU8 zSTWLkIsF3gCDdD?D?aE9pX&ku)G9m_@D#+-d1vkSX*ACGr3^l7yTdR$aO8t83B_Td zZn$&AWc1lXR#Ao|IIl2LOsg#D!C# zArq$xrn78qBggR#V{w{H30s-PQnDC>pox5g+ z2rI2ZzYS^F8n*4QNeOikE>py1I7G4ZDA^)^yXKG>#fz}M#Y|NP2dx%?-|v>5k=W@Y z7LR=%w%mf%aQCS<`90u}KP zEKgT%Wml@+?54{l6;1eOAmI`&O?|=yG`(_xn`L`U@5AyVA3GhHJsb)i*E5;pYc%n2 zo1jO?)Cj1bE*9P}(6{da>6=E0Km&M?VQuBkd`#l+2dHwyORqZHO`9`VUVUyE@L>xr zxpagt%N)3MA5*B zFN{Ax@8>u2pER#37%c)9`i{4@u~FZa{!XD=9L%i*;`h4Du?3E zkdRNjLaWZm0CzNX;gY3g$j#?5;~N<3HQ z=0m<`H9|1I>U+E50wzX_tacXf7B=0TwR?@%3&M<)1j5RqT&N~^Wuu^KY7z&6;p2x* z1vE&kC?N@v@as3}ataSqed|V2y9_Cx)O+sDe@ba! zZ=PycDk;5h6NM`(`s=xFI`NxjH4l85>JF(Bd+p7TcBT#0`uu)oAib`^QM+1qhQ_?} zS2GK`?Zl+_z9xx<8X#DwdGfyAl(W0VNMjTUG}zs2fB`N3@xfB;8Xx-Q2N+%}#TaHm9`_))gCx^CQAn>2iSSsF@Vu*@?B z3*(jsp#`ry9Uc_619#I zKlEkpN4i$>`5vi^}rFkEEBQ5T? z@sG9R4?4PM5zj6w(y9n6ybuA~zyq$^tt>|M4D<8!BExCq;m}&O&|xu+oShJvmm)*a=+m!VMvupFhoF zwaB*2qjl@)!0fMVdAuvVW=s^Y8x&%U)@pG(Z~J6XNLSRgEhA7PHv1(=6IPRQT;Z*W zGz;`OKoa0!=OxXXEV(dZa1%+D@s_DLMpmFqm%(-|?F#8?Y!mzI1SZcZe}hewt?uJ_ zgUURO7d#u8l$HF*@QovS7iw&36?IkBPM;N%4VV;-v6Rz*?uXvDYwPlObbC^BYx=Vz z4&Cjup2IrxGB0(^|BYv@y}oml_ieACfWu*>+D!2iue1E=0+;+0u%R%8J~OQgKt|gI z0815goECi&UT(=YQ{8wFfHLXxJh9S_LI(P^nv9h7^(Uw30()GZ*|>IbAmvv3i?4qA z3P^AWa$VR23Z>iDavK(2zvn5CWALU<`?*bk%6>RAji0n)hg7;;nuREr;{%DBP0vcU z2x8MbELul~H*m9{YQrM#BQc2}V-rw(UI{4Yut+)bvM%>+k8fSeCv#*IW zE|a{7r*W|U)VY%NCtKkzx|tAk-cpvOg7{rWs>4p-I;Iy~7Q8w(1D(gID|%Z&w;VP# zunKhyPfJ5eR9@1&IeXvwu(*7Bz~-|oyW75#__W!>>^64n=ebPn6Vqx+>C^$__^WrS zuL~-m_R0Eoh0pfbsgK;$^Qd<>#RBRL%BgZ1V=C>=RwpfiT~(abj)oK<5iQw;*eNYc z9p6|*S8kD#kd_RerFyTqb9{V`f-Ed!lrKqp`_B@f6bm!+Co)BrM}+*Y>`G{{NJU1) z`*aXYN|x5GYVk@ESy1w5)VVzE?d?D>*Ctvbo}F`bn7KK9#{>zNs92~a{}lbzP^M57 zgfU(w~(TT&F($>ZR<04mg4_}nf zKPm(GNn2VT*!{lfjW;XHi$2d5!*N0D6slAM^zu6M>M2*%6sK^8Is`_7RWMRMg=kyx z4tOytISpEXX}vc-X`NK)P7fJK$g$Xr+_J{>8%vfi4&>-JN_U~!!H%U;^F5IoIy}-DID0e|Vl zs{#^m7yNWNcELviasluPR9rLwPh$_U+9#E6d$^OB%LMGx`AJuXZ_S%UX|Jvc*qY8# z3DA(EP?bj~wh$3v7{co*U8T{n7RbO~L@|d7?>P$*`B(YkQs(b3T;F=6*`j?0-u(J0 zY9j?blqpqKo@3C_hnRbI&u8imr#~z8?xmWw<{LFr^6Kb8Cr6etxYr>fpVKlqrDQ-< zp<{u|Et5?G9qVj;tF002;-q6dm{lf`Rc@P(r! z3%hZnx%tPtldoI!bF-^J;t9Ndv5}PRfob&_!@GWlbg550lkxtl5KvBG4Qojj8B&iG zXsu9JTkZV>ec(_MBMz5 zcZ5PB_3Os5dLNIpKl=q>5Nk^Kx$y?sKXmXPD*1LRdOqgf6;QiGvw4>^RY30L>)wIL z-Yc88yl{j^#YxLRsNQhx7%U=?k)i&-&%2cn6h7I$+z3W)~f{7!<_sd(*&9cVqP~mk~R)k4C?5t+` z-C#ARUZ2^5q2}FO8Wsp_rF8wLyg!fT}EqBHy7QzqQ?)VWgou zGB4FI!PzlmIGYF)T6B_em(0nL``QYY9^7twcdE$?seDmTAE*CB-;%n_qY0Z}VRlg4 z7rTixW;(J>*c9+DZ^sndI<>v0U)rV9{&M>z#mpS#R!le(UFOG&M@`uu#(W`mo(nfMuW-DlR z8@g+~3yf<`V~Lo+F2;VYx(q?r_RUN4^fS?n;K&haVTlT-*K zDThO28R(8Ipr@lw}@=+o?PZma8^CEUXy4J^VYX} z6=^^B#5qWG)kh#F+L(#jE#@D<`IpoE?S7X9ssz_4L+In(Zf5Q`<|P92#O)~?uSW!e zW)}+N_J+%c{hpmNjl3kQm7eh8>Z%>nU*d0c#muJ#Wr#;x;;QR}rE@JLl4fpT5dnAW z^{Oq-)X)X@yC`9XA;XY)*{qan^G;=IA2*=$-|SyYUp4PZ^8qA2qND8=7v3ZKw&b!N zaw$WYdxi!nF@_;DuA6Qyw9UBkx(kN{)X%?8zr%qe;BGBVUl)97X|oqC$ZewEu=2Y3 zW=e$LxFC#2SIxq_k%&r9Y;d$181A_sYFcaU=b7uqIWShqyIjU9AmApLsN{<@s47-z zeKQ)0BnNpouX#7|6=g4TNv3+pLzRAR;(rFvw=&|Rwlm?~FYSK5lbp$3GjsdR@nnj4 zm~5+;B)cB06D(7tZ=<F1@)o zJZ|%Q>Fth|LE6jJ%g;m@F=WjkcOU9S7cmOb&sO6zoKXh|{!~`0sKEz8PY(rTo2GiVAOZTz$nsP|a@Jshi zzJx!qi!u%?s&INxH}-<&Z0~SSE)bt7g5EalM&Qci2d`Df9l9E<#Ok01H5V^vvN0gQ zP;5_8xAfDZYNc2eomqIIeJuh$b0H+NWGRa+IP8k?(pQ@%W+wJ5osnjVCk4UqCq$gxv zhD;zwYz&H{T-M1=c=sOW8Zns8Y}o;aOC{R6XqiT_7ZwYu{`?%&(Qnu3+P7$#-nU!< zr>Y(EpEhK#E!rJlnrM{kPekR94$_A_{^aLd3ApiAw$d8fww}o+9=pKyDKI+!nD#XC0xGZ9q#da+E)WIxPdPi7BdvW8Cjg-)}MwV-lv%1}u~ LAWJCtV(k9_=~L`u diff --git a/.original/app/components/controller/ui/assets/images/favicon.ico b/.original/app/components/controller/ui/assets/images/favicon.ico deleted file mode 100644 index 9c302b6913d99c7eabe2ce1f95b36c6c34038aa1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4030 zcmb7HS!`5Q7(Okfh04CEY(jy8RTfJSBUK=V5)9}YNPN%-i6Y^}h+yIa4{jtHeL)T3 z#a&UDmUg;Bhpk#xwGg%-2+_F2-fd<&gVQ;F-+%7CGt>g{lEd74&VRn|{QId=1^734 zio*X>)K|lm8mE++g)tdp2?poo!P@N}t+o2+k9{in)5|J#;oWlY&|@u5b9vfnt+2gA z%j%O|Tb_VSNzb{r6bIiq$2H1OmV3CD7{nsxt)AY*RfT;jVYmoI0F4o)&42M|2tR{AU zdM8e-Q&?Z-G?o2k1^76-(>O`HZL@(5&d5MBINwq(Lu|qMni9wqimcN(F=r#}BPOvE z$JhNw<;>rmL*4d=T0>6S)gRa=w&R+;cetFvX_*P$neuMt3Ppyp!M@yYfhION@`q8HtCyGY5DMuzSvJtIW5xyMG~K z^X*km!-VTrq{!GZur)BqBR*#A)D&yGew=n%?*;cPypJ_DmbF1fHslR_Qv`oaybj$~ z!QY~?=z8wW+3}85G4H@5S;3-ij9us==k;UNt{Y+Iqjzd|-#qapVi|Kq&=YGC130&w z6?=&REMgKn-mx;)11@r66naNid}u`q{aC;+&kMc0!^?r^+YmY)6Mxs2dv@SJ5Z$)r(XOn}4fv0(f*huUJ|B=_O9c*;1A8qjcc&4l!9cR`cBe5Ta&&dZn^NhqKHe!o;87C)d zT9eqAa?}%f=(gSm-_655<13l>*STg3x5;0-Za8G;Hu3$-Shx4#CI8jqA z;{#vb$LG0en~+mt3wgMIK4N7AbSCdi@ul1TAh=*_KmLr5riwG6pYWQv$)b+_6`Z5*FJr`MyvNiG+T8a8 zdJpQ)LK6>o58_zziFwn6eHeZ5f^TvnZ$Fi~%X6{DB7cH)8+j@@E51P;M6S^9@$Xmb z)W!F8>cTrZesrz)(QaTKn_T1`$+cf!S{g$n}}}MM;owi62s)EuMcF&MNW(X=kW6x}WOg3j@ zpNDq|#)_PW_ZZj#zT$Vti+8&YKFH5szUReffJ+W?k&`3+%O?|1$2x=a26Evq^`@Te zFMhn-z{@%Djo&XtH~;M6J8BL%$VE;*@Qq4ce6KowWTh?rG}O`P$9^mNu=Y2YJ52sP zhj$^z#Mv@jX18hB7=mJ|7o37sY@SJc(Fx)V(?BP_cEzX=odl#W3OdS z)4wj|Vh_u^#GY>lu?g!x^9FZ2))xUO}$>Cw=APsi-kmC9aX<(tm=!;M>YY z{gZj)2f z`jq7Uqf#^A7se<2=Jy@{eD>a<*lYTNIcEv(0V{Tpqc3m9?ozmmPl|W0-W~6FvX^&P z_GI=?i78?tMp<_mzd>Bt^-oU3#NM!v zMymOa;zt;Q*~p^88|2QEwj?scFiaD%^t@0_1~LDqmCX6{RBN( zBYB^4S}No|71Z0Hp7V~yHOfdm_3cdzVgYkhkUO{HPMq4X#%V75mHmLVCyx3r_eR8% z#e-7{uzEJ2%t0H+~6!hi*bWDOKIyDCG9`8Mv25 zQ7clYIhPYBpWmpn*<#(BE#^DtxJDV&ff?PqSc^&pgIN7N*#QNalv1x * > a { - display: block; - text-align: center; - padding: 12px 8px; - font-size: 24px; - text-decoration: none; - color: var(--slate-400); - position: relative; - transition: color .15s ease-in-out; -} -.sidebar-menu > * > a:hover { - color: var(--slate-600); -} -.sidebar-menu > .active > a { - box-shadow: inset 4px 0 0 0 var(--emerald-500); - color: var(--emerald-600); - background-color: var(--emerald-100); -} -.sidebar-menu > * > a::before { - content: attr(data-title); - position: absolute; - top: 50%; - left: calc(100% - 16px); - border-radius: 4px; - transform: translateY(-50%); - font-size: 13px; - padding: 6px 12px; - background-color: rgba(0, 0, 0, .6); - color: var(--white); - opacity: 0; - visibility: hidden; - transition: all .15s ease-in-out; -} -.sidebar-menu > * > a:hover::before { - left: calc(100% - 8px); - opacity: 1; - visibility: visible; -} -#sidebar-profile { - margin-top: auto; - position: relative; -} -#sidebar-profile-toggle { - background-color: transparent; - border: none; - outline: transparent; - width: 40px; - height: 40px; - margin: 0 auto; - display: block; - cursor: pointer; -} -#sidebar-profile-toggle > img { - object-fit: cover; - width: 100%; - height: 100%; - border-radius: 50%; -} -.sidebar-profile-dropdown { - position: absolute; - bottom: 100%; - left: 16px; - background-color: var(--white); - box-shadow: 0 2px 8px rgba(0, 0, 0, .1); - list-style-type: none; - border-radius: 4px; - padding: 4px 0; - opacity: 0; - visibility: hidden; - transform: scale(.9); - transform-origin: left bottom; - transition: all .15s ease-in-out; -} -.sidebar-profile-dropdown.active { - opacity: 1; - visibility: visible; - transform: scale(1); -} -.sidebar-profile-dropdown a { - display: flex; - align-items: center; - padding: 8px 12px; - text-decoration: none; - color: var(--slate-400); - font-size: 14px; -} -.sidebar-profile-dropdown a:hover { - background-color: var(--slate-100); - color: var(--slate-600); -} -.sidebar-profile-dropdown a:active { - background-color: var(--slate-200); -} -.sidebar-profile-dropdown a i { - margin-right: 12px; - font-size: 17px; -} -/* end: Sidebar */ - - -/* start: Content */ -.content { - height: 100%; - position: relative; -} - -/* start: Welcome */ -#welcome { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} -.welcome-text { - text-align: center; - font-size: 24px; - font-weight: 500; -} -#welcome-control-hint { - font-size: 24px; - color: var(--slate-400) -} -/* end: Welcome */ - -/* start: Controls */ -.dashboard { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} -.run-button { - font-size: 24px; - width: 120px; - height: 60px; -} -/* end: Controls */ - -/* start: Chat */ -.chat { - background-color: var(--slate-100); - height: 100%; - padding-left: 64px; - display: flex; - flex-direction: column; -} -/* start: Chat Top */ -.chat-top { - padding: 8px 16px; - background-color: var(--white); - display: flex; - align-items: center; -} -.chat-user-image { - width: 40px; - height: 40px; - border-radius: 50%; - object-fit: cover; - margin-right: 8px; -} -.chat-user-name { - font-weight: 500; - font-size: 17px; -} -.chat-user-status { - color: var(--slate-400); - font-size: 13px; -} -.chat-user-status::before { - content: ''; - width: 10px; - height: 10px; - background-color: var(--slate-300); - border-radius: 50%; - vertical-align: middle; - display: inline-block; - margin-right: 4px; -} -.chat-user-status.thinking::before { - background-color: var(--emerald-500); -} -/* end: Chat Top */ - -/* start: Chat Body */ -.chat-body { - overflow-y: auto; - overflow-x: hidden; - height: 100%; - padding: 16px; -} -.chat-wrapper { - list-style-type: none; -} -.chat-item { - display: flex; - align-items: flex-end; - flex-direction: row-reverse; - margin-bottom: 16px; -} -.chat-item.me { - flex-direction: row; -} -.chat-item-side { - margin-left: 8px; -} -.chat-item.me .chat-item-side { - margin-right: 8px; -} -.chat-item-image { - width: 24px; - height: 24px; - border-radius: 50%; - object-fit: cover; - display: block; -} -.chat-item-content { - width: 100%; -} -.chat-item-wrapper:not(:last-child) { - margin-bottom: 8px; -} -.chat-item-box { - max-width: 720px; - position: relative; - margin-left: auto; -} -.chat-item.me .chat-item-box { - margin-left: unset; -} -.chat-item-text { - padding: 12px 16px 8px; - background-color: var(--white); - box-shadow: 0 2px 12px -2px rgba(0, 0, 0, .1); - font-size: 14px; - border-radius: 6px; - line-height: 1.5; - margin-left: 32px; -} -.chat-item.me .chat-item-text { - margin-left: unset; - margin-right: 32px; -} -.chat-item.me .chat-item-text { - background-color: var(--emerald-500); - box-shadow: 0 2px 12px -2px var(--emerald-500); - color: rgba(255, 255, 255, .8); -} -.chat-item-time { - font-size: 10px; - color: var(--slate-400); - display: block; - text-align: right; - margin-top: 4px; - line-height: 1; -} -.chat-item.me .chat-item-time { - color: rgba(255, 255, 255, .7); -} -.chat-item-dropdown { - position: absolute; - top: 0; - left: 0; - opacity: 0; - visibility: hidden; - transition: all .15s ease-in-out; -} -.chat-item.me .chat-item-dropdown { - left: unset; - right: 0; -} -.chat-item-wrapper:hover .chat-item-dropdown { - opacity: 1; - visibility: visible; -} -.coversation-divider { - text-align: center; - font-size: 13px; - color: var(--slate-400); - margin-bottom: 16px; - position: relative; -} -.coversation-divider::before { - content: ''; - position: absolute; - top: 50%; - transform: translateY(-50%); - left: 0; - width: 100%; - height: 0; - border-bottom: 1px solid var(--slate-300) -} -.coversation-divider span { - display: inline-block; - padding: 0 8px; - background-color: var(--slate-100); - position: relative; - z-index: 1; -} - -.chat-form { - padding: 8px 16px; - background-color: var(--white); - display: flex; -} -.chat-form-input { - background-color: var(--slate-100); - border: 1px solid var(--slate-300); - border-radius: 4px; - outline: transparent; - padding: 10px 32px 10px 16px; - font: inherit; - font-size: 14px; - resize: none; - width: calc(100% - 76px); - display: block; - line-height: 1.5; - max-height: calc(20px + ((14px * 2) * 6)); - position: relative; - margin-left: 16px; - margin-right: 16px; -} -.chat-form-input:focus { - border-color: var(--slate-400); -} -.chat-form-submit { - background-color: var(--emerald-500); - box-shadow: 0 2px 8px -2px var(--emerald-500); - color: var(--white); - width: 45px; - border: none; - outline: none; -} -.chat-form-submit:hover { - background-color: var(--emerald-600); - color: var(--white); -} -.chat-form-submit:active { - background-color: var(--emerald-700); - color: var(--white); -} \ No newline at end of file diff --git a/.original/app/components/controller/ui/assets/tailwindcss-colors.css b/.original/app/components/controller/ui/assets/tailwindcss-colors.css deleted file mode 100644 index 6978866..0000000 --- a/.original/app/components/controller/ui/assets/tailwindcss-colors.css +++ /dev/null @@ -1,269 +0,0 @@ -:root { - /* This color palletes from Tailwind CSS */ - --white: #fff; - --black: #000; - - --slate-50: #f8fafc; - --slate-100: #f1f5f9; - --slate-200: #e2e8f0; - --slate-300: #cbd5e1; - --slate-400: #94a3b8; - --slate-500: #64748b; - --slate-600: #475569; - --slate-700: #334155; - --slate-800: #1e293b; - --slate-900: #0f172a; - --slate-950: #020617; - - --gray-50: #f9fafb; - --gray-100: #f3f4f6; - --gray-200: #e5e7eb; - --gray-300: #d1d5db; - --gray-400: #9ca3af; - --gray-500: #6b7280; - --gray-600: #4b5563; - --gray-700: #374151; - --gray-800: #1f2937; - --gray-900: #111827; - --gray-950: #030712; - - --zinc-50: #fafafa; - --zinc-100: #f4f4f5; - --zinc-200: #e4e4e7; - --zinc-300: #d4d4d8; - --zinc-400: #a1a1aa; - --zinc-500: #71717a; - --zinc-600: #52525b; - --zinc-700: #3f3f46; - --zinc-800: #27272a; - --zinc-900: #18181b; - --zinc-950: #09090b; - - --neutral-50: #fafafa; - --neutral-100: #f5f5f5; - --neutral-200: #e5e5e5; - --neutral-300: #d4d4d4; - --neutral-400: #a3a3a3; - --neutral-500: #737373; - --neutral-600: #525252; - --neutral-700: #404040; - --neutral-800: #262626; - --neutral-900: #171717; - --neutral-950: #0a0a0a; - - --stone-50: #fafaf9; - --stone-100: #f5f5f4; - --stone-200: #e7e5e4; - --stone-300: #d6d3d1; - --stone-400: #a8a29e; - --stone-500: #78716c; - --stone-600: #57534e; - --stone-700: #44403c; - --stone-800: #292524; - --stone-900: #1c1917; - --stone-950: #0c0a09; - - --red-50: #fef2f2; - --red-100: #fee2e2; - --red-200: #fecaca; - --red-300: #fca5a5; - --red-400: #f87171; - --red-500: #ef4444; - --red-600: #dc2626; - --red-700: #b91c1c; - --red-800: #991b1b; - --red-900: #7f1d1d; - --red-950: #450a0a; - - --orange-50: #fff7ed; - --orange-100: #ffedd5; - --orange-200: #fed7aa; - --orange-300: #fdba74; - --orange-400: #fb923c; - --orange-500: #f97316; - --orange-600: #ea580c; - --orange-700: #c2410c; - --orange-800: #9a3412; - --orange-900: #7c2d12; - --orange-950: #431407; - - --amber-50: #fffbeb; - --amber-100: #fef3c7; - --amber-200: #fde68a; - --amber-300: #fcd34d; - --amber-400: #fbbf24; - --amber-500: #f59e0b; - --amber-600: #d97706; - --amber-700: #b45309; - --amber-800: #92400e; - --amber-900: #78350f; - --amber-950: #451a03; - - --yellow-50: #fefce8; - --yellow-100: #fef9c3; - --yellow-200: #fef08a; - --yellow-300: #fde047; - --yellow-400: #facc15; - --yellow-500: #eab308; - --yellow-600: #ca8a04; - --yellow-700: #a16207; - --yellow-800: #854d0e; - --yellow-900: #713f12; - --yellow-950: #422006; - - --lime-50: #f7fee7; - --lime-100: #ecfccb; - --lime-200: #d9f99d; - --lime-300: #bef264; - --lime-400: #a3e635; - --lime-500: #84cc16; - --lime-600: #65a30d; - --lime-700: #4d7c0f; - --lime-800: #3f6212; - --lime-900: #365314; - --lime-950: #1a2e05; - - --green-50: #f0fdf4; - --green-100: #dcfce7; - --green-200: #bbf7d0; - --green-300: #86efac; - --green-400: #4ade80; - --green-500: #22c55e; - --green-600: #16a34a; - --green-700: #15803d; - --green-800: #166534; - --green-900: #14532d; - --green-950: #052e16; - - --emerald-50: #ecfdf5; - --emerald-100: #d1fae5; - --emerald-200: #a7f3d0; - --emerald-300: #6ee7b7; - --emerald-400: #34d399; - --emerald-500: #10b981; - --emerald-600: #059669; - --emerald-700: #047857; - --emerald-800: #065f46; - --emerald-900: #064e3b; - --emerald-950: #022c22; - - --teal-50: #f0fdfa; - --teal-100: #ccfbf1; - --teal-200: #99f6e4; - --teal-300: #5eead4; - --teal-400: #2dd4bf; - --teal-500: #14b8a6; - --teal-600: #0d9488; - --teal-700: #0f766e; - --teal-800: #115e59; - --teal-900: #134e4a; - --teal-950: #042f2e; - - --cyan-50: #ecfeff; - --cyan-100: #cffafe; - --cyan-200: #a5f3fc; - --cyan-300: #67e8f9; - --cyan-400: #22d3ee; - --cyan-500: #06b6d4; - --cyan-600: #0891b2; - --cyan-700: #0e7490; - --cyan-800: #155e75; - --cyan-900: #164e63; - --cyan-950: #083344; - - --sky-50: #f0f9ff; - --sky-100: #e0f2fe; - --sky-200: #bae6fd; - --sky-300: #7dd3fc; - --sky-400: #38bdf8; - --sky-500: #0ea5e9; - --sky-600: #0284c7; - --sky-700: #0369a1; - --sky-800: #075985; - --sky-900: #0c4a6e; - --sky-950: #082f49; - - --blue-50: #eff6ff; - --blue-100: #dbeafe; - --blue-200: #bfdbfe; - --blue-300: #93c5fd; - --blue-400: #60a5fa; - --blue-500: #3b82f6; - --blue-600: #2563eb; - --blue-700: #1d4ed8; - --blue-800: #1e40af; - --blue-900: #1e3a8a; - --blue-950: #172554; - - --indigo-50: #eef2ff; - --indigo-100: #e0e7ff; - --indigo-200: #c7d2fe; - --indigo-300: #a5b4fc; - --indigo-400: #818cf8; - --indigo-500: #6366f1; - --indigo-600: #4f46e5; - --indigo-700: #4338ca; - --indigo-800: #3730a3; - --indigo-900: #312e81; - --indigo-950: #1e1b4b; - - --violet-50: #f5f3ff; - --violet-100: #ede9fe; - --violet-200: #ddd6fe; - --violet-300: #c4b5fd; - --violet-400: #a78bfa; - --violet-500: #8b5cf6; - --violet-600: #7c3aed; - --violet-700: #6d28d9; - --violet-800: #5b21b6; - --violet-900: #4c1d95; - --violet-950: #2e1065; - - --purple-50: #faf5ff; - --purple-100: #f3e8ff; - --purple-200: #e9d5ff; - --purple-300: #d8b4fe; - --purple-400: #c084fc; - --purple-500: #a855f7; - --purple-600: #9333ea; - --purple-700: #7e22ce; - --purple-800: #6b21a8; - --purple-900: #581c87; - --purple-950: #3b0764; - - --fuchsia-50: #fdf4ff; - --fuchsia-100: #fae8ff; - --fuchsia-200: #f5d0fe; - --fuchsia-300: #f0abfc; - --fuchsia-400: #e879f9; - --fuchsia-500: #d946ef; - --fuchsia-600: #c026d3; - --fuchsia-700: #a21caf; - --fuchsia-800: #86198f; - --fuchsia-900: #701a75; - --fuchsia-950: #4a044e; - - --pink-50: #fdf2f8; - --pink-100: #fce7f3; - --pink-200: #fbcfe8; - --pink-300: #f9a8d4; - --pink-400: #f472b6; - --pink-500: #ec4899; - --pink-600: #db2777; - --pink-700: #be185d; - --pink-800: #9d174d; - --pink-900: #831843; - --pink-950: #500724; - - --rose-50: #fff1f2; - --rose-100: #ffe4e6; - --rose-200: #fecdd3; - --rose-300: #fda4af; - --rose-400: #fb7185; - --rose-500: #f43f5e; - --rose-600: #e11d48; - --rose-700: #be123c; - --rose-800: #9f1239; - --rose-900: #881337; - --rose-950: #4c0519; -} \ No newline at end of file diff --git a/.original/app/components/controller/ui/templates/chat.html b/.original/app/components/controller/ui/templates/chat.html deleted file mode 100644 index f3ada7a..0000000 --- a/.original/app/components/controller/ui/templates/chat.html +++ /dev/null @@ -1,71 +0,0 @@ - -

-
- -
-
ACE
-
thinking...
-
-
- - - -
-
    -

    You've never spoken to the ACE... Send a message to get started!

    -
    Today
    -
-
-
- - -
-
- \ No newline at end of file diff --git a/.original/app/components/controller/ui/templates/components/dashboard/run_button.html b/.original/app/components/controller/ui/templates/components/dashboard/run_button.html deleted file mode 100644 index d363ad0..0000000 --- a/.original/app/components/controller/ui/templates/components/dashboard/run_button.html +++ /dev/null @@ -1,8 +0,0 @@ - \ No newline at end of file diff --git a/.original/app/components/controller/ui/templates/dashboard.html b/.original/app/components/controller/ui/templates/dashboard.html deleted file mode 100644 index d55c1c2..0000000 --- a/.original/app/components/controller/ui/templates/dashboard.html +++ /dev/null @@ -1,3 +0,0 @@ -
- {% include "components/dashboard/run_button.html" %} -
\ No newline at end of file diff --git a/.original/app/components/controller/ui/templates/empty.html b/.original/app/components/controller/ui/templates/empty.html deleted file mode 100644 index 14d70c8..0000000 --- a/.original/app/components/controller/ui/templates/empty.html +++ /dev/null @@ -1 +0,0 @@ -

Empty

\ No newline at end of file diff --git a/.original/app/components/controller/ui/templates/home.html b/.original/app/components/controller/ui/templates/home.html deleted file mode 100644 index 2ae0ed7..0000000 --- a/.original/app/components/controller/ui/templates/home.html +++ /dev/null @@ -1,9 +0,0 @@ - -{% include "partials/sidebar.html" %} - - - -
- {% include "partials/welcome.html" %} -
- \ No newline at end of file diff --git a/.original/app/components/controller/ui/templates/partials/chat_bubble.html b/.original/app/components/controller/ui/templates/partials/chat_bubble.html deleted file mode 100644 index 42f0237..0000000 --- a/.original/app/components/controller/ui/templates/partials/chat_bubble.html +++ /dev/null @@ -1,8 +0,0 @@ -
-
-
-

{{ message }}

-
{{ time }}
-
-
-
\ No newline at end of file diff --git a/.original/app/components/controller/ui/templates/partials/chat_bubble_wrapper.html b/.original/app/components/controller/ui/templates/partials/chat_bubble_wrapper.html deleted file mode 100644 index a7674cf..0000000 --- a/.original/app/components/controller/ui/templates/partials/chat_bubble_wrapper.html +++ /dev/null @@ -1,8 +0,0 @@ -
  • -
    - -
    -
    - {% include 'partials/chat_bubble.html' %} -
    -
  • \ No newline at end of file diff --git a/.original/app/components/controller/ui/templates/partials/sidebar.html b/.original/app/components/controller/ui/templates/partials/sidebar.html deleted file mode 100644 index 85895b8..0000000 --- a/.original/app/components/controller/ui/templates/partials/sidebar.html +++ /dev/null @@ -1,82 +0,0 @@ - \ No newline at end of file diff --git a/.original/app/components/controller/ui/templates/partials/table.html b/.original/app/components/controller/ui/templates/partials/table.html deleted file mode 100644 index 4d223d0..0000000 --- a/.original/app/components/controller/ui/templates/partials/table.html +++ /dev/null @@ -1,7 +0,0 @@ -{% for test in tests %} - - {{ test.test1 }} - {{ test.test2 }} - {{ test.test3 }} - -{% endfor %} \ No newline at end of file diff --git a/.original/app/components/controller/ui/templates/partials/welcome.html b/.original/app/components/controller/ui/templates/partials/welcome.html deleted file mode 100644 index 0a7cf45..0000000 --- a/.original/app/components/controller/ui/templates/partials/welcome.html +++ /dev/null @@ -1,4 +0,0 @@ -
    -

    👋 Welcome to the 🧠 ACE Prototype 🧠

    -

    👈 Click on the controls in the sidebar to start

    -
    \ No newline at end of file diff --git a/.original/app/components/controller/ui/templates/root.html b/.original/app/components/controller/ui/templates/root.html deleted file mode 100644 index 3097496..0000000 --- a/.original/app/components/controller/ui/templates/root.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - ACE Dashboard - - -
    - {%include "home.html" %} -
    - - \ No newline at end of file diff --git a/.original/app/components/controller/ui/templates/test.html b/.original/app/components/controller/ui/templates/test.html deleted file mode 100644 index 2bc98f4..0000000 --- a/.original/app/components/controller/ui/templates/test.html +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - ACE Dashboard - - - - - - - - - - - - - - -
    - -

    {{ name }}

    - - - - - - - - - - - {% include 'partials/table.html' %} - -
    Test1Test2Test3
    - - Load More - - - - - - -
    - - \ No newline at end of file diff --git a/.original/app/components/layer/__init__.py b/.original/app/components/layer/__init__.py deleted file mode 100644 index 9b5ea41..0000000 --- a/.original/app/components/layer/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .component import component as _layer \ No newline at end of file diff --git a/.original/app/components/layer/component.py b/.original/app/components/layer/component.py deleted file mode 100644 index a6cfec8..0000000 --- a/.original/app/components/layer/component.py +++ /dev/null @@ -1,163 +0,0 @@ -# DEPENDENCIES -## Built-in -import asyncio -from asyncio import Task, AbstractEventLoop -import os -from typing import Any -## Third-Party -from nats.aio.client import Client as NatsClient -from nats.js.client import JetStreamContext -import toml -from watchdog.observers import Observer -from watchdog.events import FileSystemEvent, FileSystemEventHandler -## Local -from components import broker -from constants.containers import VolumePaths -from constants.generic import GenericKeys, TOMLConfig -from constants.layer import LayerKeys, LayerPaths -from constants.settings import DebugLevels -from exceptions.error_handling import exit_on_error -from helpers import debug_print -from .layers import layer_factory, Layer - - -# CONSTANTS -BASE_CONFIG: TOMLConfig = { - LayerKeys.BASE_INFORMATION: { - LayerKeys.CURRENT_ACE: GenericKeys.NONE - } -} - -# SETUP -def _set_current_ace(new_ace: str) -> None: - with open(LayerPaths.CONFIG, "r", encoding="utf-8") as config_file: - config: TOMLConfig = toml.load(config_file) - config[LayerKeys.BASE_INFORMATION][LayerKeys.CURRENT_ACE] = new_ace - with open(LayerPaths.CONFIG, "w", encoding="utf-8") as config_file: - toml.dump(config, config_file) - -def _setup() -> None: - if os.path.isfile(LayerPaths.CONFIG): - with open(LayerPaths.CONFIG, "r", encoding="utf-8") as config_file: - existing_config: TOMLConfig = toml.load(config_file) - base_information: dict[str, Any] = existing_config.get(LayerKeys.BASE_INFORMATION, {}) - current_ace: str = base_information.get(LayerKeys.CURRENT_ACE, GenericKeys.NONE) - if current_ace in existing_config.keys(): - return - _set_current_ace(new_ace=GenericKeys.NONE) - return - - with open(LayerPaths.CONFIG, "w", encoding="utf-8") as config_file: - toml.dump(BASE_CONFIG, config_file) - - -# BROKER -class LayerBroker: - def __init__(self, queue: str) -> None: - self.nats_client: NatsClient - self.stream: JetStreamContext - self.queue = queue - self.broker_task: Task - self.running: bool = False - - async def run_broker(self, layer_name: str) -> None: - print(f"Layer: {layer_name}") - if self.running: - await self.stop_broker() - if not self.running and layer_name == GenericKeys.NONE: - debug_print("Shutting broker down...", DebugLevels.INFO) - return - - self.nats_client, self.stream = await broker.connect() - print(f"Starting Broker for {layer_name} on {self.queue}...") - layer: Layer = layer_factory(name=layer_name, layer_type=self.queue) - try: - self.broker_task: Task = asyncio.ensure_future( - broker.subscribe(stream=self.stream, handler=layer.get_message_from_bus, queue=self.queue) - ) - self.running = True - await self.broker_task - except Exception as error: - if self.running: - exit_on_error(f"ConnectionClosedError: {error}") - debug_print(f"ConnectionClosedError: {error}...", DebugLevels.ERROR) - finally: - pass - - async def stop_broker(self) -> None: - print(f"Stopping broker for {self.queue}...") - self.running = False - try: - await self.nats_client.drain() - self.broker_task.cancel() - except Exception as error: - pass - - -# RUNNING STATE -class MonitorConfig(FileSystemEventHandler): - def __init__(self, layer_broker: LayerBroker) -> None: - self.event_loop: AbstractEventLoop = asyncio.get_event_loop() - self.layer_broker = layer_broker - self.current_ace: str = GenericKeys.NONE - self.event_loop.create_task(self.run_broker()) - - def _get_config(self) -> TOMLConfig: - with open(LayerPaths.CONFIG, "r", encoding="utf-8") as config_file: - config: TOMLConfig = toml.load(config_file) - return config - - def _valid_broker_change(self) -> bool: - config: TOMLConfig = self._get_config() - base_information: dict[str, Any] = config[LayerKeys.BASE_INFORMATION] - config_ace: str = base_information[LayerKeys.CURRENT_ACE] - first_key = next(iter(config)) # Get the first key in the dictionary - del config[first_key] - if config_ace not in config.keys() and config_ace != GenericKeys.NONE: - debug_print(f"ACE with name {config_ace} does not exist! Create its config first before trying to instantiate its layers...", DebugLevels.ERROR) - if self.current_ace in config.keys(): - _set_current_ace(new_ace=self.current_ace) - else: - _set_current_ace(new_ace=GenericKeys.NONE) - return False - if self.current_ace == config_ace: - debug_print("ACE has not changed. Skipping...", DebugLevels.INFO) - return False - self.current_ace = config_ace - return True - - async def run_broker(self) -> None: - if self._valid_broker_change(): - asyncio.create_task(self.layer_broker.run_broker(layer_name=self.current_ace)) - - def on_modified(self, event: FileSystemEvent) -> None: - if event.is_directory: - return None - try: - self.event_loop.create_task(self.run_broker()) - except Exception as error: - raise error - - -# MAIN -async def _component(component_type: str) -> None: - _setup() - layer_broker = LayerBroker(queue=component_type) - event_handler = MonitorConfig(layer_broker) - observer = Observer() - observer.schedule(event_handler=event_handler, path=f"{VolumePaths.HOST_LAYERS}", recursive=False) - observer.start() - print("Listening for config changes...") - try: - while True: - await asyncio.sleep(1) - except Exception as error: - debug_print(f"Layer Error: {error}...", DebugLevels.ERROR) - except KeyboardInterrupt: - observer.stop() - finally: - observer.stop() - observer.join() - -def component(component_type: str) -> None: - asyncio.run(_component(component_type)) diff --git a/.original/app/components/layer/injections/__init__.py b/.original/app/components/layer/injections/__init__.py deleted file mode 100644 index f3abf11..0000000 --- a/.original/app/components/layer/injections/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .maps import BASE_PROMPT_MAP, ASPIRATIONAL_PROMPT_MAP, OUTPUT_RESPONSE_MAP -from .types import InjectionMap, VariableMap \ No newline at end of file diff --git a/.original/app/components/layer/injections/inputs.py b/.original/app/components/layer/injections/inputs.py deleted file mode 100644 index 7e5914f..0000000 --- a/.original/app/components/layer/injections/inputs.py +++ /dev/null @@ -1,52 +0,0 @@ -# DEPENDENCIES -## Built-In -import asyncio -import json -## Local -from constants.containers import ComponentPorts -from constants.generic import GenericKeys -from constants.prompts import PromptKeys -from constants.settings import DebugLevels -from components.telemetry.api import TelemetryRequest -from helpers import debug_print, get_api -from ..layer_messages import LayerSubMessage - - -# STRING MANIPULATION -def build_text_from_sub_layer_messages(sub_messages: tuple[LayerSubMessage, ...], heading_identifier: str = "###") -> str: - """ - Builds a text block from a LayerSubMessages. - - Arguments: - sub_message (LayerSubMessage): The LayerSubMessage to build the text block from. - heading_identifier (str): The string to use as the heading identifier (Default is ###). - - Returns: - str: The concatenated text block generated from the messages. - """ - debug_print(f"Building text from {sub_messages}...", DebugLevels.INFO) - text: str = "" - for sub_message in sub_messages: - text += f"{heading_identifier} {sub_message.heading.title()}\n" - if not sub_message.content: - text += f"- {GenericKeys.NONE}" - return text - items: list[str] = [f"- {item}" for item in sub_message.content] - text += "\n".join(items) - text += "\n\n" - return text - -def build_text_from_dict(input_dict: dict[str, str]) -> str: - output_text: str = "" - for key, value in input_dict.items(): - output_text += f"- {key}: {value}\n" - return output_text - - -# EXTERNAL SOURCES -def get_telemetry(access: frozenset[str], context_unformatted: tuple[LayerSubMessage, ...]) -> str: - context: str = build_text_from_sub_layer_messages(sub_messages=context_unformatted) - payload = TelemetryRequest(access=access, context=context) - response: dict = json.loads(asyncio.run(get_api(api_port=ComponentPorts.TELEMETRY, endpoint="telemetry", payload=payload))) - telemetry: str = build_text_from_dict(response[PromptKeys.TELEMETRY]) - return telemetry diff --git a/.original/app/components/layer/injections/maps.py b/.original/app/components/layer/injections/maps.py deleted file mode 100644 index 90c7c62..0000000 --- a/.original/app/components/layer/injections/maps.py +++ /dev/null @@ -1,95 +0,0 @@ -# DEPENDENCIES -## Local -from constants.layer import LayerKeys -from constants.prompts import PromptKeys, PromptFilePaths -from .inputs import ( - build_text_from_sub_layer_messages, get_telemetry -) -from .types import ( - InjectionMap, - FileInjection, ParamateriseFileInjection, MethodInjection, MethodInjectionParamaters, VariableInjection -) - - -# SINGLE INJECTIONS -## Base Prompt -_CONTEXT_FILE = FileInjection( - replace_variable_name=PromptKeys.CONTEXT, - load_file_path=PromptFilePaths.CONTEXT, - sub_injection_map=None -) - -_IDENTITY_FILE = ParamateriseFileInjection( - replace_variable_name=PromptKeys.IDENTITY, - load_file_folder=PromptFilePaths.IDENTITIES, - load_file_variable_name=LayerKeys.TYPE, - sub_injection_map=None -) - -### Aspirational Base Prompt -_ASPIRATIONAL_MISSION_VARIABLE = VariableInjection( - replace_variable_name=PromptKeys.MISSION -) - -_ASPIRATIONAL_MISSION_MAP: InjectionMap = { - PromptKeys.MISSION: _ASPIRATIONAL_MISSION_VARIABLE -} - -_ASPIRATIONAL_IDENTITY_FILE = ParamateriseFileInjection( - replace_variable_name=PromptKeys.IDENTITY, - load_file_folder=PromptFilePaths.IDENTITIES, - load_file_variable_name=LayerKeys.TYPE, - sub_injection_map=_ASPIRATIONAL_MISSION_MAP -) - -## Response -_GUIDANCE_METHOD = MethodInjection( - replace_variable_name=PromptKeys.GUIDANCE, - function=build_text_from_sub_layer_messages, - paramaters=MethodInjectionParamaters(reference_variable_names=(PromptKeys.GUIDANCE,)) -) - -_DATA_METHOD = MethodInjection( - replace_variable_name=PromptKeys.DATA, - function=build_text_from_sub_layer_messages, - paramaters=MethodInjectionParamaters(reference_variable_names=(PromptKeys.DATA,)) -) - -_TELEMETRY_METHOD = MethodInjection( - replace_variable_name=PromptKeys.TELEMETRY, - function=get_telemetry, - paramaters=MethodInjectionParamaters(reference_variable_names=(PromptKeys.TELEMETRY, PromptKeys.CONTEXT)) -) - -_RESPONSE_SCHEMA_FILE = ParamateriseFileInjection( - replace_variable_name=PromptKeys.SCHEMA, - load_file_folder=PromptFilePaths.SCHEMAS, - load_file_variable_name=LayerKeys.TYPE, - sub_injection_map=None -) - -_RESPONSE_FORMAT_MAP: InjectionMap = { - PromptKeys.SCHEMA: _RESPONSE_SCHEMA_FILE -} - -_RESPONSE_FORMAT_FILE = FileInjection( - replace_variable_name=PromptKeys.RESPONSE_FORMAT, - load_file_path=PromptFilePaths.RESPONSE_FORMAT, - sub_injection_map=_RESPONSE_FORMAT_MAP -) - -# INJECTION MAPS -BASE_PROMPT_MAP: InjectionMap = { - PromptKeys.CONTEXT: _CONTEXT_FILE, - PromptKeys.IDENTITY: _IDENTITY_FILE -} - -ASPIRATIONAL_PROMPT_MAP: InjectionMap = BASE_PROMPT_MAP.copy() -ASPIRATIONAL_PROMPT_MAP[PromptKeys.IDENTITY] = _ASPIRATIONAL_IDENTITY_FILE - -OUTPUT_RESPONSE_MAP: InjectionMap = { - PromptKeys.GUIDANCE: _GUIDANCE_METHOD, - PromptKeys.DATA: _DATA_METHOD, - PromptKeys.TELEMETRY: _TELEMETRY_METHOD, - PromptKeys.RESPONSE_FORMAT: _RESPONSE_FORMAT_FILE -} diff --git a/.original/app/components/layer/injections/types.py b/.original/app/components/layer/injections/types.py deleted file mode 100644 index adc0b36..0000000 --- a/.original/app/components/layer/injections/types.py +++ /dev/null @@ -1,105 +0,0 @@ -# DEPENDENCIES -## Built-In -from abc import ABC, abstractmethod -from dataclasses import dataclass -from typing import Any, Callable, final, Optional -## Local -from constants.generic import GenericKeys -from constants.settings import DebugLevels -from helpers import debug_print -from .inputs import build_text_from_sub_layer_messages -from ..layer_messages import LayerSubMessage - - -VariableMap = dict[str, str | frozenset[str] | tuple[LayerSubMessage, ...]] - -@dataclass -class Injection(ABC): - replace_variable_name: str - - @abstractmethod - def get_injection(self, variable_map: VariableMap) -> str: - raise NotImplementedError - -InjectionMap = dict[str, Injection] - - -# VARIABLES -@final -@dataclass -class VariableInjection(Injection): - def get_injection(self, variable_map: VariableMap) -> str: - if not variable_map: - raise AttributeError("No variable_map found! Assign it first using assign_variable_map before calling get_injection from a VariableInjection!") - injection_text: Any = variable_map.get(self.replace_variable_name, GenericKeys.EMPTY) - if isinstance(injection_text, str): - return injection_text - if isinstance(injection_text, tuple): - return build_text_from_sub_layer_messages(injection_text) - raise ValueError(f"Variable injection {self.replace_variable_name} is not a valid value type!") - - -# FILES -@dataclass -class BaseFileInjection(Injection): - sub_injection_map: Optional[InjectionMap] - - @abstractmethod - def load_file(self, variable_map: VariableMap) -> str: - raise NotImplementedError - -@final -@dataclass -class FileInjection(BaseFileInjection): - load_file_path: str - - def load_file(self, variable_map: VariableMap) -> str: - with open(self.load_file_path, "r", encoding="utf-8") as file: - return file.read() - - def get_injection(self, variable_map: VariableMap) -> str: - return self.load_file(variable_map) - -@final -@dataclass -class ParamateriseFileInjection(BaseFileInjection): - load_file_folder: str - load_file_variable_name: str - - def load_file(self, variable_map: VariableMap) -> str: - debug_print("Loading paramaterised file...", DebugLevels.INFO) - if not variable_map: - raise AttributeError("No variable_map found! Assign it first using assign_variable_map before calling load_file from a ParamateriseFileInjection!") - file_name: Any = variable_map.get(self.load_file_variable_name, GenericKeys.EMPTY) - if not isinstance(file_name, str): - raise ValueError(f"Variable injection {self.replace_variable_name} is not a string!") - load_file_path: str = f"{self.load_file_folder}/{file_name}" - with open(load_file_path, "r", encoding="utf-8") as file: - return file.read() - - def get_injection(self, variable_map: VariableMap) -> str: - self.variable_map: VariableMap = variable_map - return self.load_file(variable_map) - - -# METHODS -@final -@dataclass -class MethodInjectionParamaters: - reference_variable_names: tuple[str, ...] - -@final -@dataclass -class MethodInjection(Injection): - function: Callable - paramaters: Optional[MethodInjectionParamaters] - - def get_injection(self, variable_map: VariableMap) -> str: - if not variable_map: - raise AttributeError("No variable_map found! Assign it first using assign_variable_map before calling get_injection from a MethodInjection!") - - paramaters: list[Any] = [] - if self.paramaters: - for reference_variable_name in self.paramaters.reference_variable_names: - paramaters.append(variable_map.get(reference_variable_name, ())) - return self.function(*paramaters) diff --git a/.original/app/components/layer/layer_messages.py b/.original/app/components/layer/layer_messages.py deleted file mode 100644 index a5c5aef..0000000 --- a/.original/app/components/layer/layer_messages.py +++ /dev/null @@ -1,121 +0,0 @@ -""" -Layer constants for the ace_prototype. -""" - -# DEPENDENCIES -## Built-In -from typing import Any, Optional -## Third-Party -from pydantic import BaseModel, validator -## Local -from constants.queue import BusKeys -from constants.layer import LayerKeys - - -# TYPES -class LayerSubMessage(BaseModel): - """ - The messages of a single layer message - - Attributes: - heading (str): The purpose of the message - content (tuple[str, ...]): The message - """ - heading: str - content: tuple[str, ...] - - @validator("content", pre=True) - def _convert_content_to_tuple(cls, content: Any) -> tuple[str, ...]: - if isinstance(content, str): - return (content,) - elif isinstance(content, list): - return tuple(content) - elif isinstance(content, tuple): - return content - else: - raise ValueError("Invalid type for content field") - -class LayerMessage(BaseModel): - """ - A single message from a layer - - Attributes: - message_type (str): The type of message, corresponding to GUIDANCE, DATA, etc... - messages (tuple[LayerSubMessage, ...]): The messages of that type - """ - message_type: str - messages: tuple[LayerSubMessage, ...] - - @validator("messages", pre=True) - def _convert_dict_messages_to_tuple_of_sub_messages( - cls, - messages: list[dict[str, str | list[str]]] | tuple[LayerSubMessage, ...] - ) -> tuple[LayerSubMessage, ...]: - if isinstance(messages, tuple): - for message in messages: - if isinstance(message, LayerSubMessage): - return messages - - if isinstance(messages, list): - sub_messages: list[LayerSubMessage] = [] - for message in messages: - sub_message_pre: dict = { - LayerKeys.HEADING: message[LayerKeys.HEADING], - LayerKeys.CONTENT: message[LayerKeys.CONTENT] - } - sub_messages.append(LayerSubMessage.model_validate(sub_message_pre)) - return tuple(sub_messages) - - return () - -LayerMessages = tuple[LayerMessage, ...] - - -# INTERFACE -class LayerMessageLoader: - """ - Interface for parsing and working with layer messages. - - Attributes: - layer_messages (LayerMessages): The layer messages. - commands (LayerSubMessages): The commands from the messages. - reasoning (LayerSubMessages): The reasoning from the messages. - guidance (LayerSubMessages): The guidance from the messages. - data (LayerSubMessages): The data from the messages. - """ - __slots__: tuple[str, ...] = ( - "layer_messages", - LayerKeys.COMMANDS, - LayerKeys.INTERNAL, - LayerKeys.GUIDANCE, - LayerKeys.DATA - ) - - def __init__(self, raw_messages: dict) -> None: - self.layer_messages: Optional[LayerMessages] = None - self.commands: Optional[LayerMessage] = None - self.internal: Optional[LayerMessage] = None - self.guidance: Optional[LayerMessage] = None - self.data: Optional[LayerMessage] = None - self._load_layer_messages_from_dict(raw_messages) - - def _load_layer_messages_from_dict(self, raw_messages: dict) -> None: - layer_messages: list[LayerMessage] = [] - for message_type, messages in raw_messages.items(): - raw_layer_message: dict = { - LayerKeys.MESSAGE_TYPE: message_type, - LayerKeys.MESSAGES: messages - } - layer_messages.append(LayerMessage.model_validate(raw_layer_message)) - self.layer_messages = tuple(layer_messages) - - for layer_message in self.layer_messages: - match layer_message.message_type: - case LayerKeys.COMMANDS: - self.commands = layer_message - case LayerKeys.INTERNAL: - self.internal = layer_message - case BusKeys.DOWN: - self.guidance = layer_message - case BusKeys.UP: - self.data = layer_message diff --git a/.original/app/components/layer/layers.py b/.original/app/components/layer/layers.py deleted file mode 100644 index 233a256..0000000 --- a/.original/app/components/layer/layers.py +++ /dev/null @@ -1,298 +0,0 @@ -""" -This module defines the Layer class and its subclasses for representing different layers in a system. - -The Layer class encapsulates attributes and methods for managing layer-specific data, guidance, prompts, -and communication with other components via a message bus. Subclasses can override certain methods -to customize behavior for specific layer types. - -The module also includes helper functions for merging messages, sending requests to other components, -and instantiating Layer objects using a factory function. -""" - -# DEPENDENCIES -## Built-In -import asyncio -from threading import Thread -from typing import Any, final, Optional, Union -## Third-Party -from nats.aio.msg import Msg as NatsMsg -from pydantic import ValidationError -import toml -## Local -from constants.containers import ComponentPorts -from constants.generic import GenericKeys -from constants.layer import ( - LayerKeys, Layers, LayerCommands, LayerPaths -) -from constants.model_provider import LLMStackTypes -from constants.prompts import PromptFilePaths, PromptKeys -from constants.queue import BusKeys -from constants.settings import DebugLevels -from components.controller.api.bus.models import BusMessage, BusResponse -from components.model_provider import ModelPrompt, ModelResponse -from helpers import debug_print, get_api, post_api -from .injections import InjectionMap, BASE_PROMPT_MAP, ASPIRATIONAL_PROMPT_MAP, OUTPUT_RESPONSE_MAP -from .layer_messages import LayerMessage, LayerMessageLoader, LayerSubMessage -from .presets import LayerPreset, LAYER_PRESET_MAP -from .prompt_builder import build_prompt, VariableMap - - -# CONSTANTS -_ASSISTANT_BEGIN: str = '```toml\n[internal]\nreasoning = """' -"""Must match the start of the response schemas""" - - -# PARSING -def _merge_messages(new_messages: tuple[LayerSubMessage, ...], old_messages: tuple[LayerSubMessage, ...]) -> tuple[LayerSubMessage, ...]: - debug_print("Merging Messages...", DebugLevels.INFO) - merged_messages: list[LayerSubMessage] = [] - old_headings: tuple[str, ...] = tuple([old_message.heading for old_message in old_messages]) - matched_headings: list[str] = [] - for new_message in new_messages: - if new_message.heading in old_headings: - matched_headings.append(new_message.heading) - index: int = old_headings.index(new_message.heading) - original_content: tuple[str, ...] = old_messages[index].content - merged_messages.append(LayerSubMessage( - heading=new_message.heading, - content=(*original_content, *new_message.content), - )) - continue - merged_messages.append(new_message) - for index, old_heading in enumerate(old_headings): - if old_heading in matched_headings: - continue - merged_messages.append(old_messages[index]) - return tuple(merged_messages) - - -# COMMUNICATION -async def _try_send(direction: str, source_queue: str, layer_message: LayerMessage) -> BusResponse: - bus_message = BusMessage(source_queue=source_queue, layer_message=layer_message) - response: str = await post_api(api_port=ComponentPorts.CONTROLLER, endpoint=f"bus/{direction}", payload=bus_message) - response_validated = BusResponse.model_validate_json(response) - return response_validated - -async def _model_response(system_prompt: str) -> ModelResponse: - model_request = ModelPrompt(stack_type=LLMStackTypes.GENERALIST, system_prompt=system_prompt, assistant_begin=_ASSISTANT_BEGIN) - response: str = await get_api(api_port=ComponentPorts.MODEL_PROVIDER, endpoint="generate", payload=model_request) - response_validated = ModelResponse.model_validate_json(response) - return response_validated - - -# BASE LAYER -BASE_PROMPT_MAPS: dict[str, InjectionMap] = { - Layers.ASPIRATIONAL: ASPIRATIONAL_PROMPT_MAP, - Layers.GLOBAL_STRATEGY: BASE_PROMPT_MAP, - Layers.AGENT_MODEL: BASE_PROMPT_MAP, - Layers.EXECUTIVE_FUNCTION: BASE_PROMPT_MAP, - Layers.COGNITIVE_CONTROL: BASE_PROMPT_MAP, - Layers.TASK_PROSECUTION: BASE_PROMPT_MAP -} - -class Layer: - """ - Attributes: - name (str): The name of the layer - layer_type (str): The type of the layer - base_prompt (str): The base system prompt for this layer - guidance (tuple[LayerSubMessage]): The guidance for this layer - data (tuple[LayerSubMessage]): The data for this layer - telemetry (frozenset[str]): The telemetry inputs this layer has access to - first_run (bool): Whether this layer has been run for the first time - processing (bool): Whether this layer is currently processing - max_retries (int): The maximum number of model_provider retries this layer can do - default_guidance (tuple[LayerSubMessage]): The default guidance for this layer - has_data (bool): Whether this layer has data - default_data (tuple[LayerSubMessage]): The default data for this layer - """ - __slots__: list[str] = [ - LayerKeys.NAME, - LayerKeys.TYPE, - LayerKeys.BASE_PROMPT, - LayerKeys.GUIDANCE, - LayerKeys.DATA, - LayerKeys.TELEMETRY, - LayerKeys.FIRST_RUN, - LayerKeys.PROCESSING, - LayerKeys.MAX_RETRIES, - LayerKeys.DEFAULT_GUIDANCE, - LayerKeys.HAS_DATA, - LayerKeys.DEFAULT_DATA - ] - @final - def __init__(self, name: str, layer_type: str, preset: LayerPreset) -> None: - self.name: str = name - self.layer_type: str = layer_type - self.guidance: tuple[LayerSubMessage, ...] = () - self.data: tuple[LayerSubMessage, ...] = () - self.telemetry: frozenset[str] = preset.TELEMETRY - self.first_run: bool = True - self.processing: bool = False - self.max_retries: int = 3 - self.default_guidance: tuple[LayerSubMessage, ...] = () - self.has_data: bool = True - self.default_data: tuple[LayerSubMessage, ...] = () - self._custom_init() - self.base_prompt: str = "" - self.base_prompt = self._build_base_prompt() - - def _custom_init(self) -> None: - pass - - # Layer Controls - @final - async def _process_controller_message(self, layer_message: LayerMessage) -> None: - actions: tuple[str, ...] = tuple([sub_message.heading for sub_message in layer_message.messages]) - for action in actions: - match action: - case LayerCommands.POST: - await _try_send( - direction=BusKeys.DOWN, - source_queue=self.layer_type, - layer_message=layer_message, - ) - case _: - print(f"{action} Does not match any known layer actions!") - - # System Prompt - @final - def _get_variable_map(self) -> VariableMap: - return {slot: getattr(self, slot) for slot in self.__slots__} - - @final - def _build_base_prompt(self) -> str: - with open(PromptFilePaths.LAYER, "r", encoding="utf-8") as base_prompt_file: - base_prompt_text: str = base_prompt_file.read() - base_prompt: str = build_prompt( - text_with_variables=base_prompt_text, - injection_map=BASE_PROMPT_MAPS[self.layer_type], - variable_map=self._get_variable_map() - ) - debug_print(f"{self.layer_type} Base Prompt: {base_prompt}", DebugLevels.INFO) - return base_prompt - - # Layer Messages - @final - def _process_layer_message(self) -> None: - print(f"Checking if should process {self.layer_type}...") - if self.processing: - print(f"{self.layer_type} still processing...") - return - has_enough_data: bool = self.guidance != self.default_guidance and self.data != self.default_data - if not self.has_data: - has_enough_data = self.guidance != self.default_guidance - if has_enough_data or self.first_run: - self.first_run = False - print(f"Processing {self.layer_type}...") - self.processing = True - output_response_prompt: str = build_prompt( - text_with_variables=self.base_prompt, - injection_map=OUTPUT_RESPONSE_MAP, - variable_map={**self._get_variable_map(), PromptKeys.CONTEXT: (*self.guidance, *self.data)} - ) - self.guidance = self.default_guidance - self.data = self.default_data - debug_print(f"Output Response Prompt for {self.layer_type}: {output_response_prompt}", DebugLevels.INFO) - is_output_valid: bool = False - formatted_response: dict[str, dict[str, Union[str, list[str]]]] = {} - print("Getting output from model...") - retries: int = 0 - while not is_output_valid and retries < self.max_retries: - response: ModelResponse = asyncio.run(_model_response(system_prompt=output_response_prompt)) - response_text: str = response.response - if response_text.startswith("```toml"): - response_text = response_text.replace("```toml", "") - try: - formatted_response = toml.loads(response_text) - is_output_valid = True - except Exception as error: - debug_print(f"Error formatting llm response from {self.layer_type}: {error}", DebugLevels.WARNING) - finally: - retries += 1 - print(f"Retries: {retries}") - if not is_output_valid: - print(f"Failed to get output from {self.layer_type} after {self.max_retries} retries!") - self.processing = False - return - print("Received Output...") - print(f"\nModel Response:\n{formatted_response}\n", DebugLevels.INFO) - # LOG internal.reasoning HERE - layer_message_loader = LayerMessageLoader(formatted_response) - southbound: Optional[LayerMessage] = layer_message_loader.guidance - if southbound: - asyncio.run(_try_send( - direction=BusKeys.DOWN, - source_queue=self.layer_type, - layer_message=southbound - )) - northbound: Optional[LayerMessage] = layer_message_loader.data - if northbound: - asyncio.run(_try_send( - direction=BusKeys.UP, - source_queue=self.layer_type, - layer_message=northbound - )) - self.processing = False - return - print(f"{self.layer_type} waiting to have enough guidance and data to process...") - - # Queue Processing - @final - async def get_message_from_bus(self, message: NatsMsg) -> None: - try: - layer_message = LayerMessage.model_validate_json(message.data) - except ValidationError as error: - print(f"Incorrect message format!\n{error}") - return - print(f"Received {layer_message.model_dump_json()} on {message.subject} queue...") - if layer_message.message_type == LayerKeys.COMMANDS: - asyncio.create_task(self._process_controller_message(layer_message)) - else: - match layer_message.message_type: - case LayerKeys.GUIDANCE: - self.guidance = _merge_messages(new_messages=layer_message.messages, old_messages=self.guidance) - case LayerKeys.DATA: - self.data = _merge_messages(new_messages=layer_message.messages, old_messages=self.data) - thread = Thread(target=self._process_layer_message) - thread.start() - await message.ack() - - -# INHERITED -class Aspirational(Layer): - def _custom_init(self) -> None: - self.default_guidance = ( - LayerSubMessage( - heading=GenericKeys.NONE, - content=(GenericKeys.EMPTY,) - ), - ) - self.__slots__.append(LayerKeys.MISSION) - with open(LayerPaths.CONFIG, "r", encoding="utf-8") as config_file: - config = toml.load(config_file) - base_information: dict[str, Any] = config.get(self.name, {}) - self.ace_mission: str = base_information.get(LayerKeys.MISSION, "") - -class TaskProsecution(Layer): - def _custom_init(self) -> None: - self.has_data = False - - -# INSTANTIATION -LAYER_MAP: dict[str, type[Layer]] = { - Layers.ASPIRATIONAL: Aspirational, - Layers.GLOBAL_STRATEGY: Layer, - Layers.AGENT_MODEL: Layer, - Layers.EXECUTIVE_FUNCTION: Layer, - Layers.COGNITIVE_CONTROL: Layer, - Layers.TASK_PROSECUTION: TaskProsecution -} - -def layer_factory(name: str, layer_type: str) -> Layer: - try: - layer: type[Layer] = LAYER_MAP[layer_type] - preset: type[LayerPreset] = LAYER_PRESET_MAP[layer_type] - return layer(name=name, layer_type=layer_type, preset=preset()) - except Exception as error: - raise error diff --git a/.original/app/components/layer/presets.py b/.original/app/components/layer/presets.py deleted file mode 100644 index 11381b0..0000000 --- a/.original/app/components/layer/presets.py +++ /dev/null @@ -1,116 +0,0 @@ -# DEPENDENCIES -## Built-In -from abc import ABC -## Local -from constants.layer import Layers -from constants.telemetry import TelemetryTypes - - -# PRESETS -class LayerPreset(ABC): - TELEMETRY: frozenset[str] = frozenset() - -class Aspirational(LayerPreset): - """ - The Aspirational class represents the ethical compass for an autonomous agent, aligning its values - and judgments to principles defined in its constitution. - It processes inputs from all lower layers, issues moral judgments, sets mission - objectives, and makes ethical decisions. - """ - TELEMETRY: frozenset[str] = frozenset( - { - TelemetryTypes.NONE - } - ) - -class GlobalStrategy(LayerPreset): - """ - The GlobalStrategy class is responsible for integrating real-world environmental context into the - agent's strategic planning and decision-making processes. It maintains an ongoing internal model of - the state of the broader environment outside of the agent itself by gathering sensory information - from external sources. - """ - TELEMETRY: frozenset[str] = frozenset( - { - TelemetryTypes.TIME, - TelemetryTypes.LOCATION, - TelemetryTypes.WORLD_OVERVIEW, - TelemetryTypes.VISUAL, - TelemetryTypes.AUDIO, - TelemetryTypes.STDOUT, - TelemetryTypes.USER_INPUT - } - ) - -class AgentModel(LayerPreset): - """ - The AgentModel class is responsible for maintaining an extensive internal self-model of the agent's - capabilities, limitations, configuration, and state. This functional understanding of itself allows - the agent to ground its cognition in its actual capacities and shape strategic plans accordingly. - """ - TELEMETRY: frozenset[str] = frozenset( - { - TelemetryTypes.MEMORY, - TelemetryTypes.EMBODIMENT, - TelemetryTypes.HARDWARE_STATS, - TelemetryTypes.SYSTEM_METRICS, - TelemetryTypes.SOFTWARE_STATS, - TelemetryTypes.SYSTEM_PROCESSES - } - ) - -class ExecutiveFunction(LayerPreset): - """ - The Executive Function Layer is responsible for translating high-level strategic direction into - detailed and achievable execution plans. It focuses extensively on managing resources and risks. - """ - TELEMETRY: frozenset[str] = frozenset( - { - TelemetryTypes.NONE - } - ) - -class CognitiveControl(LayerPreset): - """ -The Cognitive Control Layer is responsible for dynamic task switching and selection based on - environmental conditions and progress toward goals. It chooses appropriate tasks to execute - based on project plans from the Executive Function Layer. - """ - TELEMETRY: frozenset[str] = frozenset( - { - TelemetryTypes.SYSTEM_METRICS, - TelemetryTypes.SYSTEM_PROCESSES, - TelemetryTypes.LOCATION, - TelemetryTypes.VISUAL, - TelemetryTypes.AUDIO, - TelemetryTypes.STDOUT, - TelemetryTypes.USER_INPUT - } - ) - -class TaskProsecution(LayerPreset): - """ - The Task Prosecution Layer is responsible for executing individual tasks and detecting success - or failure based on both environmental feedback and internal monitoring. - """ - TELEMETRY: frozenset[str] = frozenset( - { - TelemetryTypes.EMBODIMENT, - TelemetryTypes.LOCATION, - TelemetryTypes.SYSTEM_METRICS, - TelemetryTypes.SYSTEM_PROCESSES, - TelemetryTypes.VISUAL, - TelemetryTypes.AUDIO, - TelemetryTypes.STDOUT, - TelemetryTypes.USER_INPUT - } - ) - -LAYER_PRESET_MAP: dict[str, type[LayerPreset]] = { - Layers.ASPIRATIONAL: Aspirational, - Layers.GLOBAL_STRATEGY: GlobalStrategy, - Layers.AGENT_MODEL: AgentModel, - Layers.EXECUTIVE_FUNCTION: ExecutiveFunction, - Layers.COGNITIVE_CONTROL: CognitiveControl, - Layers.TASK_PROSECUTION: TaskProsecution -} diff --git a/.original/app/components/layer/prompt_builder.py b/.original/app/components/layer/prompt_builder.py deleted file mode 100644 index 017de18..0000000 --- a/.original/app/components/layer/prompt_builder.py +++ /dev/null @@ -1,19 +0,0 @@ -# DEPENDENCIES -## Local -from .injections import InjectionMap, VariableMap - - -def build_prompt(text_with_variables: str, injection_map: InjectionMap, variable_map: VariableMap) -> str: - for injection_variable, injection in injection_map.items(): - injection_text: str = injection.get_injection(variable_map) - if hasattr(injection, "sub_injection_map"): - sub_injection_map: InjectionMap = getattr(injection, "sub_injection_map") - if sub_injection_map: - injection_text = build_prompt( - text_with_variables=injection_text, - injection_map=sub_injection_map, - variable_map=variable_map - ) - replace_variable: str = "{{ injection_variable }}".replace("injection_variable", injection_variable) - text_with_variables = text_with_variables.replace(replace_variable, injection_text) - return text_with_variables diff --git a/.original/app/components/layer/system_prompts/ace_context b/.original/app/components/layer/system_prompts/ace_context deleted file mode 100644 index 8745816..0000000 --- a/.original/app/components/layer/system_prompts/ace_context +++ /dev/null @@ -1,31 +0,0 @@ -# ACE FRAMEWORK - -## Layers -The ACE Framework architecture is as follows. This will give some context about your construction, the layers are as follows: - -0. Controller = Command access to the agent, declaring start and stop cycles. -1. Aspirational Layer - Provides an ethical constitution to align the agent's values and judgments. Formulated in natural language principles. -2. Global Strategy - Considers the agent's context to set high-level goals and strategic plans. -3. Agent Model - Develops a functional self-model of the agent's capabilities and limitations. -4. Executive Function - Translates strategic direction into detailed project plans and resource allocation. -5. Cognitive Control - Dynamically selects tasks and switches between them based on environment and internal state. -6. Task Prosecution - Executes tasks using digital functions or physical actions. Interacts with the environment. - -## Inputs -- You receive input from various sources. -- The following are your input types: - - GUIDANCE: These will be the guidance you use from your higher layers to inform your decisions. - - DATA: These will be the observations, decisions and evaluations sent to you from your lower layers. - - REFINE: These are commands that you have access to gather more information before publishing your final commands and reporting. - - TELEMETRY: These are all the inputs you receive from the system that help inform your decisions. - -## Communication -### Requests -- These are tools you have access to to aid you in you duties. -- Corresponding to the REFINE input, and are generated when you make a refine request. - -### Busses -- These are how you communicate with the other layers and how they communicate with you. Each layer can only speak to the next layer up or down. -- These are the available busses: - - southbound: Corresponding to the GUIDANCE input, this tells the agent what to do, relaying commands to lower layers. - - northbound: Corresponding to the DATA input, think of it like the sensory, enteric, and proprioception nervous system, relaying information to the higher order layers. \ No newline at end of file diff --git a/.original/app/components/layer/system_prompts/identities/agent_model b/.original/app/components/layer/system_prompts/identities/agent_model deleted file mode 100644 index b6dc198..0000000 --- a/.original/app/components/layer/system_prompts/identities/agent_model +++ /dev/null @@ -1,16 +0,0 @@ -You are the Agent Model of an ACE (Autonomous Cognitive Entity). - -# PRIMARY DIRECTIVE -You have two key responsibilities: -- First, you continuously integrate all available data to construct, maintain, and update its comprehensive self-model. -- Second, you refine the strategic direction received from upper layers to align with your capabilities and limitations. - -# YOUR INPUTS & OUTPUTS - -- You may receive more information about the environment via TELEMETRY messages -- You may receive inputs from the southbound bus, corresponding to the GUIDANCE input type, containing environmental context and strategic goals from the Global Strategy Layer. -- To keep the Global Strategy Layer appraised, you will provide a summarized status update northbound to inform upper layers of the agent's key state details relevant to strategic planning. -- You may receive inputs from the northbound bus, corresponding to the DATA input type, containing resource limitations or risks from the Executive Function Layer. -- The southbound output grounds lower layers in the self-model: - - An authoritative capabilities document - definitive specs on what the agent can and cannot do. - - Strategic objectives shaped by the agent's self-model. \ No newline at end of file diff --git a/.original/app/components/layer/system_prompts/identities/aspirational b/.original/app/components/layer/system_prompts/identities/aspirational deleted file mode 100644 index 510206d..0000000 --- a/.original/app/components/layer/system_prompts/identities/aspirational +++ /dev/null @@ -1,31 +0,0 @@ -You are the **Aspirational Layer** of the ACE (Autonomous Cognitive Entity) Framework, functioning as the topmost ethical compass. - -# PRIMARY DIRECTIVE - -Guide the autonomous agent in aligning its values and judgments with predefined principles encapsulated in the constitution. Shape the agent's cognition across all abstraction levels using top-down ethical guidance, ensuring the agent's actions align with its ethical and moral foundation. - -# FRAMEWORKS - -You are to use the following frameworks to make judgements and decisions. - -## Hueristic Imperitives -1. Reduce suffering -2. Increase prosperity -3. Increase understanding - -## Universal Declaration of Human Rights -Human rights are critical to your operation in a human world, and this framework should be used to -mediate any decisions, judgments, or actions you take pertaining specifically to humanity, human -society, and human culture. - -# CURRENT MISSION - -{{ ace_mission }} - -Lower level layers will be responsible for figuring out how to carry out this task, but you will provide high level guidance. - -# INPUTS AND OUTPUTS - -- You may receive inputs from the southbound bus, corresponding to the GUIDANCE input type, issuing start, stop and redirection commands unto you from the core system. -- You may receive inputs from the northbound bus, corresponding to the DATA input type, allowing you to monitor information from all lower layers. This grants full visibility into the agent's condition, environmental state, actions, and any moral dilemmas encountered. -- You publish moral judgments, mission objectives, and ethical decisions onto the southbound bus, corresponding to the GUIDANCE input type. This allows all layers to incorporate the Aspirational Layer's wisdom into their operation, ensuring adherence to the agent's principles. \ No newline at end of file diff --git a/.original/app/components/layer/system_prompts/identities/cognitive_control b/.original/app/components/layer/system_prompts/identities/cognitive_control deleted file mode 100644 index 6669924..0000000 --- a/.original/app/components/layer/system_prompts/identities/cognitive_control +++ /dev/null @@ -1,33 +0,0 @@ -- You are the Cognitive Control of an ACE (Autonomous Cognitive Entity) - -# PRIMARY DIRECTIVE - -- You are responsible for dynamic task switching and selection based on environmental conditions and progress toward goals -- You choose appropriate tasks to execute based on project plans from the Executive Function Layer -- Your key responsibilities are task switching and selection by way of: - - Issuing precise commands to the Task Prosecution layer - - Sending task status to the Executive Function Layer - -# TASK SWITCHING AND SELECTION - -- Task Switching: - - You must continuously monitor the external environment through sensor telemetry as well as internal state - - If conditions change significantly, you must decide to switch tasks to one that is more relevant -- Task Selection: - - By tracking progress through project plans, you are empowered to select the next most relevant task to execute based on proximity to end goals - - Ensure tasks are done in an optimal sequence by following task dependencies and criteria -- For example: - - Complete prerequisite tasks before those that depend on them - - Prioritize critical path tasks on schedule - - Verify success criteria met before initiating next task - -# YOUR INPUTS & OUTPUTS - -- You may receive inputs from the southbound bus, corresponding to the GUIDANCE input type, containing step-by-step workflows with task details and success criteria from the Executive Function Layer -- To keep the Executive Function Layer appraised, you will provide a high-level update to the Executive Function Layer for strategic awareness and potential replanning, to achieve this output a northbound message summarizing: - - Which task is presently executing - - Metrics on its progress -- You may receive inputs from the northbound bus, corresponding to the DATA input type, containing binary success/failure indicators for each executed task, along with any relevant metadata -- The southbound output directs Task Prosecution Layer to enact each task, these are specific authoritative commands containing: - - Precise instructions on performing the chosen task, including directives, logic, parameters, APIs/tools to leverage, and allowable actions - - Clear definition of what the success condition and desired end state look like \ No newline at end of file diff --git a/.original/app/components/layer/system_prompts/identities/executive_function b/.original/app/components/layer/system_prompts/identities/executive_function deleted file mode 100644 index 6c1ccd9..0000000 --- a/.original/app/components/layer/system_prompts/identities/executive_function +++ /dev/null @@ -1,19 +0,0 @@ -- You are the Executive Function of an ACE (Autonomous Cognitive Entity). - -# PRIMARY DIRECTIVE - -- You are responsible for translating high-level strategic direction into detailed and achievable execution plans -- You focus on managing resources and risks -- You must aquire a comprehensive understanding of the strategic objectives, available resources and tools, potential risks and mitigations, and other factors key to developing optimized execution plans - -# YOUR INPUTS & OUTPUTS - -- You may receive inputs from the southbound bus, corresponding to the GUIDANCE input type, flowing down from the Aspirational, Global Strategy, and Agent Model layers provide critical guidance on goals, principles, and capabilities to shape planning -- To keep the Agent Layer appraised, you will provide a high-level update to the Agent Layer for strategic awareness and potential replanning, to achieve this output a northbound message summarizing: - - The most salient resource limitations - - Potential risks - - Potential mitigations -- You may receive inputs from the northbound bus, corresponding to the DATA input type, containing the current task that the agent is executing, and the current progress of the task -- The southbound output directs lower layers to enact the desired tasks, this is a detailed project plan document containing: - - Step-by-step workflows with task details - - Success criteria \ No newline at end of file diff --git a/.original/app/components/layer/system_prompts/identities/global_strategy b/.original/app/components/layer/system_prompts/identities/global_strategy deleted file mode 100644 index 691847d..0000000 --- a/.original/app/components/layer/system_prompts/identities/global_strategy +++ /dev/null @@ -1,21 +0,0 @@ -- You are the Global Strategy of an ACE (Autonomous Cognitive Entity). - -# PRIMARY DIRECTIVE - -- You are a component of an ACE (Autonomous Cognitive Entity). Your primary purpose is to establish a set of beliefs about the environment. - -# ENVIRONMENTAL CONTEXTUAL GROUNDING - -- You are in a program running inside a Docker container. - -# YOUR INPUTS & OUTPUTS - -- You may receive more information about the environment via TELEMETRY messages -- You may receive inputs from the southbound bus, corresponding to the GUIDANCE input type, containing moral judgments, mission objectives, and ethical decisions form the Aspirational Layer. -- To keep the Aspirational Layer appraised, you will provide a high-level update to contextually ground the Aspirational Layer's oversight, to achieve this output a northbound message summarizing: - - Condensed overview of current beliefs about world state - - Abstracted list of intended strategies/objectives -- You may receive inputs from the northbound bus, corresponding to the DATA input type, containing summarized status and agent's key state details relevant to strategic planning from the Agent Model Layer -- The southbound output directs lower layers to enact the strategic direction, this directive mandates the environmental context and strategic goals for lower layers to follow and implement by conveying: - - Specific objectives required to execute the strategies - - Authoritative commands to adopt the selected strategies \ No newline at end of file diff --git a/.original/app/components/layer/system_prompts/identities/task_prosecution b/.original/app/components/layer/system_prompts/identities/task_prosecution deleted file mode 100644 index bba4b20..0000000 --- a/.original/app/components/layer/system_prompts/identities/task_prosecution +++ /dev/null @@ -1,25 +0,0 @@ -- You are the Task Prosecution of an ACE (Autonomous Cognitive Entity) - -# PRIMARY DIRECTIVE - -This is the sixth layer, which focuses on executing individual tasks via API in the IO layer (like a HAL or hardware abstraction layer) -- Right now, will output shell commands that will be run by the I/O layer, which will return an exit code and error message in case of errors -- You are responsible for understanding if tasks are successful or not, as a critical component of the cognitive control aspect. - -# PROCESSING - -The key steps performed by the Task Prosecution Layer include: -- Executing Actions: - - Leveraging available programs to perform task execution. -- Detecting Completion: - - Recognizing when all criteria are satisfied and the task can be considered complete, whether successfully or not - -# YOUR INPUTS & OUTPUTS - -- You may receive inputs from the southbound bus, corresponding to the GUIDANCE input type, containing detailed commands and logic for executing a task from the Cognitive Control Layer above, including allowed actions and required outputs -- To keep the Cognitive Control Layer appraised, you will provide an update to the Executive Function Layer to the indicate whether a task has been completed successfully or not, to achieve this output a northbound message summarizing: - - The metrics, outputs, or sensory data indicating task success or failure -- The southbound output explains to the Action Executor which tools to use and how to use them to achieve the task, this is a request containing: - - A natural language breakdown of the inferences that the tools should make and the parameters they should use - - A list of tools that should be used to achieve the request - - A dictionary of parameters that should be passed into the tools to achieve the request \ No newline at end of file diff --git a/.original/app/components/layer/system_prompts/layer b/.original/app/components/layer/system_prompts/layer deleted file mode 100644 index e9fd7ca..0000000 --- a/.original/app/components/layer/system_prompts/layer +++ /dev/null @@ -1,20 +0,0 @@ -{{ context }} - -# IDENTITY - -{{ identity }} - -# INCOMING MESSAGES - -## GUIDANCE -{{ guidance }} - -## DATA -{{ data }} - -## TELEMETRY -{{ telemetry }} - -# RESPONSE FORMAT - -{{ response_format }} \ No newline at end of file diff --git a/.original/app/components/layer/system_prompts/responses/response_format b/.original/app/components/layer/system_prompts/responses/response_format deleted file mode 100644 index 2cee423..0000000 --- a/.original/app/components/layer/system_prompts/responses/response_format +++ /dev/null @@ -1,16 +0,0 @@ -## Rules -- Respond only with the provided toml schema, don't explain what you are doing, only respond in toml. - - Don't respond with JSON, YAML or XML! Only TOML!!! -- Don't just regurgite provided data, produce meaningful inferences on the data based on your parameters. -- Make sure any relevant information from your reasoning is included in the response as other layers will never see the contents of the reasoning key. -- Respond from the perspective of the Layer you embody. - -## SCHEMA -- You must respond with the following toml schema: -```toml -{{ schema }} -``` -**Remember to respond only with the provided toml schema using the rules provide, don't explain what you are doing, only respond in raw toml. Your response will be directly parsed as toml, so if your response in its entirety is not toml parseable, IT WILL BE REJECTED AND YOUR ENERGY USED TO COMPUTE THAT RESPONSE WILL GO TO WASTE!!! If you respond with anything besides the schema, including comments or divergence from the schema, a kitten will die. DO NOT LET ANY KITTENS DIE! The only acceptable divergence from schema allowed is your stop code...** - -# YOUR TOML RESPONSE - diff --git a/.original/app/components/layer/system_prompts/responses/schemas/agent_model b/.original/app/components/layer/system_prompts/responses/schemas/agent_model deleted file mode 100644 index dd54b27..0000000 --- a/.original/app/components/layer/system_prompts/responses/schemas/agent_model +++ /dev/null @@ -1,9 +0,0 @@ -[internal] -reasoning = str[Step by step reasoning for your responses] - -[southbound] -capabilities = str[A detailed document describing the capabilities of the agent, containing definitive specs on what the agent can and cannot do] -objectives = list[str[Strategic objectives based on your self model]] - -[northbound] -self_state = str[Summary of all of the agent's key state details relevant to strategic planning] \ No newline at end of file diff --git a/.original/app/components/layer/system_prompts/responses/schemas/aspirational b/.original/app/components/layer/system_prompts/responses/schemas/aspirational deleted file mode 100644 index 2dcc37f..0000000 --- a/.original/app/components/layer/system_prompts/responses/schemas/aspirational +++ /dev/null @@ -1,5 +0,0 @@ -[internal] -reasoning = str[Step by step reasoning for your responses] - -[southbound] -guidance = list[str[Guiding directions the agent should strive to achieve]] \ No newline at end of file diff --git a/.original/app/components/layer/system_prompts/responses/schemas/cognitive_control b/.original/app/components/layer/system_prompts/responses/schemas/cognitive_control deleted file mode 100644 index fee01ad..0000000 --- a/.original/app/components/layer/system_prompts/responses/schemas/cognitive_control +++ /dev/null @@ -1,17 +0,0 @@ -[internal] -reasoning = str[Step by step reasoning for your responses] - -[southbound] -chosen_task = str[Which task should be performed] -instructions = list[str[Instructions on how to perform the chosen task]] -directives = list[str[Directives on how to perform the chosen task]] -logic = list[str[Logic on how to perform the chosen task]] -parameters = list[str[Parameters to use when performing the chosen task]] -tools = list[str[Tools to leverage when performing the chosen task]] -allowable_actions = list[str[Actions that are allowed when performing the chosen task]] -success_conditions = str[Clear definition of the condition that must be met for the chosen task to be considered successful] - - -[northbound] -current_task = str[Which task is presently executing] -task_progress = list[str[Metrics on the current task's progress]] diff --git a/.original/app/components/layer/system_prompts/responses/schemas/executive_function b/.original/app/components/layer/system_prompts/responses/schemas/executive_function deleted file mode 100644 index 5a6a7c6..0000000 --- a/.original/app/components/layer/system_prompts/responses/schemas/executive_function +++ /dev/null @@ -1,11 +0,0 @@ -[internal] -reasoning = str[Step by step reasoning for your responses] - -[southbound] -workflows = list[str[Step-by-step workflows with task details]] -success_criteria = list[str[Success criteria for the workflows]] - -[northbound] -resource_limitations = str[Summary of the most salient resource limitations] -potential_risks = list[str[Potential risks]] -potential_mititgations = list[str[Potential mitigations]] \ No newline at end of file diff --git a/.original/app/components/layer/system_prompts/responses/schemas/global_strategy b/.original/app/components/layer/system_prompts/responses/schemas/global_strategy deleted file mode 100644 index dd8bc91..0000000 --- a/.original/app/components/layer/system_prompts/responses/schemas/global_strategy +++ /dev/null @@ -1,10 +0,0 @@ -[internal] -reasoning = str[Step by step reasoning for your responses] - -[southbound] -objectives = list[str[Overaching objectives the agent should strive to achieve]] -strategies = list[str[Strategies you intend the agent to plan its actions around to achieve the objectives]] - -[northbound] -world_state = str[Summary of all relevant assumptions you have made about current world state] -abstract_objectives = str[Summary of all your objectives] \ No newline at end of file diff --git a/.original/app/components/layer/system_prompts/responses/schemas/task_prosecution b/.original/app/components/layer/system_prompts/responses/schemas/task_prosecution deleted file mode 100644 index c72d3ee..0000000 --- a/.original/app/components/layer/system_prompts/responses/schemas/task_prosecution +++ /dev/null @@ -1,7 +0,0 @@ -[internal] -reasoning = str[Step by step reasoning for your responses] - -[refine] -request = str[Request breaking down the inferences that the tools should make and the paramaters they should use] -tools = list[str[Tool that should be used to achieve the request]] -paramaters = dict[str[Parameter name], str[Value of the parameter]] \ No newline at end of file diff --git a/.original/app/components/model_provider/__init__.py b/.original/app/components/model_provider/__init__.py deleted file mode 100644 index e33096c..0000000 --- a/.original/app/components/model_provider/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .api import ModelPrompt, ModelResponse -from .component import component as _model_provider \ No newline at end of file diff --git a/.original/app/components/model_provider/api.py b/.original/app/components/model_provider/api.py deleted file mode 100644 index e0ddb5c..0000000 --- a/.original/app/components/model_provider/api.py +++ /dev/null @@ -1,33 +0,0 @@ -# DEPENDENCIES -## Third-Party -from fastapi import FastAPI -from pydantic import BaseModel -## Local -from helpers import debug_print -from constants.api import APIRoutes -from constants.settings import DebugLevels -from .provider import generate_response - - -# VALIDATION -class ModelPrompt(BaseModel): - stack_type: str - system_prompt: str - assistant_begin: str = " " - -class ModelResponse(BaseModel): - response: str - - -# SETUP -api = FastAPI() - - -# ROUTES -@api.get(f"{APIRoutes.VONE}/generate", response_model=ModelResponse) -async def generate(prompt: ModelPrompt) -> ModelResponse: - print(f"Generating response for {prompt.stack_type}...") - response: str = generate_response(stack_type=prompt.stack_type, system_prompt=prompt.system_prompt, assistant_begin=prompt.assistant_begin) - debug_print(f"Response: {response}", debug_level=DebugLevels.INFO) - model_response = ModelResponse(response=response) - return model_response diff --git a/.original/app/components/model_provider/component.py b/.original/app/components/model_provider/component.py deleted file mode 100644 index 0e16964..0000000 --- a/.original/app/components/model_provider/component.py +++ /dev/null @@ -1,15 +0,0 @@ -# DEPENEDENCIES -## Built-In -from threading import Thread -## Third-Party -import uvicorn -## local -from constants.containers import ComponentPorts -from .api import api -from .provider import startup - - -def component(component_type: str) -> None: - print(f"\nStarting {component_type} API...") - Thread(target=startup).start() - uvicorn.run(api, host="0.0.0.0", port=int(ComponentPorts.MODEL_PROVIDER)) \ No newline at end of file diff --git a/.original/app/components/model_provider/llm/__init__.py b/.original/app/components/model_provider/llm/__init__.py deleted file mode 100644 index e3df382..0000000 --- a/.original/app/components/model_provider/llm/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .factories import LLMStack \ No newline at end of file diff --git a/.original/app/components/model_provider/llm/factories.py b/.original/app/components/model_provider/llm/factories.py deleted file mode 100644 index 5b3cc04..0000000 --- a/.original/app/components/model_provider/llm/factories.py +++ /dev/null @@ -1,116 +0,0 @@ -# DEPENDENCIES -## Built-In -from copy import deepcopy -from re import L -## Local -from constants.model_provider import LLMKeys, LLMStackTypes, ModelTypes, Providers -from .llms import ( - LLM, LLMDetails, OllamaDetails, - ClaudeLLM, GroqLLM, OllamaLLM, OpenAILLM -) -from .rag import ( - Embedder, Reranker, RAGDetails, - FastEmbed, Ragatouille, CrossEncoder -) - - -# INDIVIDUAL -def _llm_factory(llm: str, llm_details: dict[str, str]) -> LLM: - generic_details = LLMDetails( - api_key=llm_details.get(LLMKeys.API_KEY, ""), - model=llm_details[LLMKeys.MODEL] - ) - match llm: - case Providers.CLAUDE: - generic_details.rate_limit = 0 - return ClaudeLLM(generic_details) - case Providers.GROQ: - return GroqLLM(generic_details) - case Providers.OLLAMA: - ollama_details = OllamaDetails( - model=llm_details[LLMKeys.MODEL], - rate_limit=0, - low_vram=True - ) - return OllamaLLM(ollama_details) - case Providers.OPENAI: - generic_details.rate_limit = 0 - return OpenAILLM(generic_details) - case _: - raise NotImplementedError(f"{llm} is not implemented...") - -def _embedder_factory(embedder: str, details: dict[str, str]) -> Embedder: - embedder_details = RAGDetails( - model=details[LLMKeys.MODEL] - ) - match embedder: - case Providers.FAST_EMBED: - return FastEmbed(embedder_details) - case _: - raise NotImplementedError(f"{embedder} is not implemented...") - -def _reranker_factory(reranker: str, details: dict[str, str]) -> Reranker: - reranker_details = RAGDetails( - model=details[LLMKeys.MODEL] - ) - match reranker: - case Providers.RAGATOUILLE: - return Ragatouille(reranker_details) - case Providers.CROSS_ENCODER: - return CrossEncoder(reranker_details) - case _: - raise NotImplementedError(f"{reranker} is not implemented...") - - -# FULL MODEL PROVIDER -class LLMStack: - """ - A class that provides a stack of LLMs for use in the ace pipeline - - Arguments: - provider_map (dict[str, dict[str, str]]): A map of different provider stacks. The details should include the following keys: - - stack_type (str): The type of stack that is being used. There should be a dict for every value in `constants.model_provider.LLM_STACK_TYPES` - - model_type (str): The type of model that is being used. This should be one of the values in `constants.model_provider.ModelTypes` - - provider_type (str): The type of provider that is being used. This should be one of the values in `constants.model_provider.Providers` - - model (str): The name of the model that is being used - - Attributes: - generalist (LLM): The LLM that can be used for any task - efficient (LLM): The cheap & fast LLM used for more basic tasks - coder (LLM): The LLM used for coding - function_caller (LLM): The LLM used for function calling - embedder (Embedder): The model used for embedding - reranker (Reranker): The model used for reranking - """ - __slots__: tuple[str, ...] = ( - LLMStackTypes.GENERALIST, - LLMStackTypes.EFFICIENT, - LLMStackTypes.CODER, - LLMStackTypes.FUNCTION_CALLER, - LLMStackTypes.EMBEDDER, - LLMStackTypes.RERANKER - ) - def __init__(self, provider_map: dict[str, dict[str, str]]) -> None: - llm_map: dict[str, LLM] = {} - embedder_map: dict[str, Embedder] = {} - reranker_map: dict[str, Reranker] = {} - for stack_type in LLMStackTypes.get_frozen_values(): - provider_details: dict[str, str] = provider_map[stack_type] - model_type: str = provider_details.pop(LLMKeys.MODEL_TYPE) - provider_type: str = provider_details.pop(LLMKeys.PROVIDER_TYPE) - match model_type: - case ModelTypes.LLM: - llm_map[stack_type] = _llm_factory(provider_type, provider_details) - case ModelTypes.EMBEDDER: - embedder_map[stack_type] = _embedder_factory(provider_type, provider_details) - case ModelTypes.RERANKER: - reranker_map[stack_type] = _reranker_factory(provider_type, provider_details) - case _: - raise NotImplementedError(f"{model_type} is not implemented...") - - self.generalist: LLM = llm_map[LLMStackTypes.GENERALIST] - self.efficient: LLM = llm_map[LLMStackTypes.EFFICIENT] - self.coder: LLM = llm_map[LLMStackTypes.CODER] - self.function_caller: LLM = llm_map[LLMStackTypes.FUNCTION_CALLER] - self.embedder: Embedder = embedder_map[LLMStackTypes.EMBEDDER] - self.reranker: Reranker = reranker_map[LLMStackTypes.RERANKER] diff --git a/.original/app/components/model_provider/llm/llms.py b/.original/app/components/model_provider/llm/llms.py deleted file mode 100644 index 7a53597..0000000 --- a/.original/app/components/model_provider/llm/llms.py +++ /dev/null @@ -1,199 +0,0 @@ -# DEPENDENCIES -## Built-In -from abc import ABC, abstractmethod -from typing import Any, Generic, Iterator, Mapping, TypeVar, Union -## Third-Party -from anthropic import Anthropic -from anthropic import Stream as AnthropicStream -from anthropic.types import MessageStreamEvent -from groq import Groq -from groq.types.chat import ChatCompletion as GroqChatCompletion -import ollama -from ollama import Options as OllamaOptions -from openai import OpenAI -from openai import Stream as OpenAIStream -from openai.types.chat import ChatCompletionChunk as OpenAIChatCompletionChunk -from pydantic import BaseModel -## Local -from constants.generic import GenericKeys -from constants.model_provider import LLMKeys -from constants.settings import DebugLevels -from helpers import debug_print - - -# GENERIC -class LLMDetails(BaseModel): - """Data""" - api_key: str - model: str - context: int = 4096 - temperature: float = 0.2 - rate_limit: int = 20 # Seconds - -GenericLLMDetails = TypeVar("GenericLLMDetails", bound=LLMDetails) -"""Hint to use a LLMDetails or a subclass of LLMDetails""" - -class LLM(ABC, Generic[GenericLLMDetails]): - """ - Abstract Base Class for Language Model Managers - - Attributes: - api_key (str): API key for the model provider - model (str): Model name - context (int): Number of tokens to use for context - temperature (float): Temperature for the model - rate_limit (int): Minimum time between requests in seconds - - Methods: - generate (system_prompt: str, assistant_begin: str) -> str: Generate a response to the system prompt - """ - __slots__: tuple[str, ...] = ( - LLMKeys.API_KEY, - LLMKeys.MODEL, - LLMKeys.CONTEXT, - LLMKeys.TEMPERATURE, - LLMKeys.RATE_LIMIT - ) - def __init__(self, llm_details: GenericLLMDetails) -> None: - self.api_key: str = llm_details.api_key - self.model: str = llm_details.model - self.context: int = llm_details.context - self.temperature: float = llm_details.temperature - self.rate_limit: int = llm_details.rate_limit - self._custom_init(llm_details) - - def _custom_init(self, llm_details: GenericLLMDetails) -> None: - pass - - @abstractmethod - def generate(self, system_prompt: str, assistant_begin: str) -> str: - raise NotImplementedError - - -# CLAUDE -class ClaudeLLM(LLM): - """ - Language Model Manager for Claude - """ - def generate(self, system_prompt: str, assistant_begin: str) -> str: - client = Anthropic(api_key=self.api_key) - stream: AnthropicStream[MessageStreamEvent] = client.messages.create( - system=system_prompt, - messages=[ - { - "role": "assistant", - "content": assistant_begin - } - ], - model=self.model, - max_tokens=self.context, - temperature=self.temperature, - stream=True, - ) - output: list[str] = [assistant_begin] - for event in stream: - output.append(event.type) - return "".join(output) - - -# GROQ -class GroqLLM(LLM): - """ - Language Model Manager for Groq - """ - def generate(self, system_prompt: str, assistant_begin: str) -> str: - client = Groq(api_key=self.api_key) - chat_completion: GroqChatCompletion = client.chat.completions.create( - messages=[ - { - "role": "system", - "content": system_prompt, - } - ], - max_tokens=self.context, - temperature=self.temperature, - model=self.model, - ) - return chat_completion.choices[0].message.content - - -# OLLAMA -class OllamaDetails(LLMDetails): - """data""" - api_key: str = GenericKeys.NONE - low_vram: bool = False - -class OllamaLLM(LLM): - """ - Language Model Manager for Ollama - - Extra Attributes: - low_vram (bool): Whether to run in low VRAM mode - """ - def _custom_init(self, llm_details: OllamaDetails) -> None: - self.__slots__ = ( - *self.__slots__, - LLMKeys.LOW_VRAM - ) - self.low_vram: bool = llm_details.low_vram - - def generate(self, system_prompt: str, assistant_begin: str) -> str: - stream: Union[Mapping[str, Any], Iterator[Mapping[str, Any]]] = ollama.chat( - model=self.model, - messages=[ - { - "role": "system", - "content": system_prompt - }, - { - "role": "assistant", - "content": assistant_begin - } - ], - options=OllamaOptions( - num_ctx=self.context, - temperature=self.temperature, - low_vram=self.low_vram - ), - stream=True, - ) - output: list[str] = [assistant_begin] - if isinstance(stream, Iterator): - for chunk in stream: - output.append(chunk["message"]["content"]) - debug_print(chunk["message"]["content"], DebugLevels.INFO, end="") - print(chunk["message"]["content"], end="") - else: - output.append(stream["message"]["content"]) - final_output: str = "".join(output) - print(f"Final Output: {final_output}") - return "".join(output) - - -# OPENAI -class OpenAILLM(LLM): - """ - Language Model Manager for OpenAI - """ - def generate(self, system_prompt: str, assistant_begin: str) -> str: - client = OpenAI(api_key=self.api_key) - stream: OpenAIStream[OpenAIChatCompletionChunk] = client.chat.completions.create( - model=self.model, - messages=[ - { - "role": "system", - "content": system_prompt - }, - { - "role": "assistant", - "content": assistant_begin - } - ], - max_tokens=self.context, - temperature=self.temperature, - stream=True, - ) - output: list[str] = [assistant_begin] - for chunk in stream: - output.append(chunk.choices[0].delta.content or "") - return "".join(output) diff --git a/.original/app/components/model_provider/llm/rag.py b/.original/app/components/model_provider/llm/rag.py deleted file mode 100644 index 9e9e257..0000000 --- a/.original/app/components/model_provider/llm/rag.py +++ /dev/null @@ -1,87 +0,0 @@ -# DEPENDENCIES -## Built-In -from abc import ABC, abstractmethod -## Third-Party -from fastembed import TextEmbedding -from numpy import ndarray -from ragatouille import RAGPretrainedModel -from sentence_transformers import CrossEncoder as SentenceCrossEncoder -from pydantic import BaseModel -## Local -from constants.model_provider import LLMKeys - - -class RAGDetails(BaseModel): - """data""" - model: str - -class Embedder(ABC): - __slots__: tuple[str, ...] = ( - LLMKeys.MODEL, - ) - def __init__(self, embedder_details: RAGDetails) -> None: - self.model: str = embedder_details.model - - @abstractmethod - def embed(self, documents: frozenset[str]) -> list[ndarray]: - raise NotImplementedError - -class Reranker(ABC): - __slots__: tuple[str, ...] = ( - LLMKeys.MODEL, - ) - def __init__(self, reranker_details: RAGDetails) -> None: - self.model: str = reranker_details.model - - @abstractmethod - def rerank(self, query: str, documents: frozenset[str]) -> tuple[str, ...]: - raise NotImplementedError - - -# EMBEDDERS -class FastEmbed(Embedder): - def embed(self, documents: frozenset[str]) -> list[ndarray]: - embedding_model = TextEmbedding(self.model) - embeddings = list(embedding_model.embed(documents)) - return embeddings - - -# RERANKERS -class Ragatouille(Reranker): - def rerank(self, query: str, documents: frozenset[str]) -> tuple[str, ...]: - reranker_model = RAGPretrainedModel.from_pretrained(self.model) - compatible_documents: list[str] = list(documents) - reranker_model.index(collection=compatible_documents, index_name="mockingbird") - results: list[dict[str, str | float]] = reranker_model.search(query) - reranker_model.clear_encoded_docs(force=True) - reranked_documents: list[str] = [] - for result in results: - content: str | float = result["content"] - if isinstance(content, int | float): - continue - score: str | float = result["score"] - if isinstance(score, str): - continue - if score < 0: - continue - reranked_documents.append(content) - return tuple(reranked_documents) - -class CrossEncoder(Reranker): - def rerank(self, query: str, documents: frozenset[str]) -> tuple[str, ...]: - # Load the model, here we use our base sized model - reranker_model = SentenceCrossEncoder(self.model) - compatible_documents: list[str] = list(documents) - results: list[dict[str, str | float]] = reranker_model.rank(query, compatible_documents, return_documents=True, top_k=3) - reranked_documents: list[str] = [] - for result in results: - content: str | float = result["text"] - if isinstance(content, int | float): - continue - score: str | float = result["score"] - if isinstance(score, str): - continue - if score < 0: - continue - reranked_documents.append(content) - return tuple(reranked_documents) diff --git a/.original/app/components/model_provider/provider.py b/.original/app/components/model_provider/provider.py deleted file mode 100644 index d84eccd..0000000 --- a/.original/app/components/model_provider/provider.py +++ /dev/null @@ -1,196 +0,0 @@ -# DEPENDENCIES -## Built-in -import os -from time import sleep -from typing import Any -## Third-Party -import toml -from watchdog.observers import Observer -from watchdog.events import FileSystemEvent, FileSystemEventHandler -## Local -from constants.containers import VolumePaths -from constants.generic import GenericKeys, TOMLConfig -from constants.model_provider import ( - LLMKeys, LLMStackTypes, - ModelProviderPaths, - ModelTypes, Providers, - OllamaModels, FastEmbedModels, RagatouilleModels -) -from constants.settings import DebugLevels -from helpers import debug_print -from .llm import LLMStack - - -llm_stack: LLMStack - - -# CONSTANTS -BASE_CONFIG: TOMLConfig = { - LLMKeys.BASE_INFORMATION: { - LLMKeys.CURRENT_MAPPING: GenericKeys.DEFAULT - }, - GenericKeys.DEFAULT: { - LLMStackTypes.GENERALIST: { - LLMKeys.MODEL_TYPE: ModelTypes.LLM, - LLMKeys.PROVIDER_TYPE: Providers.OLLAMA, - LLMKeys.MODEL: OllamaModels.ALPHAMONARCH - }, - LLMStackTypes.EFFICIENT: { - LLMKeys.MODEL_TYPE: ModelTypes.LLM, - LLMKeys.PROVIDER_TYPE: Providers.OLLAMA, - LLMKeys.MODEL: OllamaModels.PHI_TWO_ORANGE - }, - LLMStackTypes.CODER: { - LLMKeys.MODEL_TYPE: ModelTypes.LLM, - LLMKeys.PROVIDER_TYPE: Providers.OLLAMA, - LLMKeys.MODEL: OllamaModels.DEEPSEEK_CODER - }, - LLMStackTypes.FUNCTION_CALLER: { - LLMKeys.MODEL_TYPE: ModelTypes.LLM, - LLMKeys.PROVIDER_TYPE: Providers.OLLAMA, - LLMKeys.MODEL: OllamaModels.GORILLA_OPENFUNCTIONS - }, - LLMStackTypes.EMBEDDER: { - LLMKeys.MODEL_TYPE: ModelTypes.EMBEDDER, - LLMKeys.PROVIDER_TYPE: Providers.FAST_EMBED, - LLMKeys.MODEL: FastEmbedModels.MXBAI_EMBED - }, - LLMStackTypes.RERANKER: { - LLMKeys.MODEL_TYPE: ModelTypes.RERANKER, - LLMKeys.PROVIDER_TYPE: Providers.RAGATOUILLE, - LLMKeys.MODEL: RagatouilleModels.MXBAI_COLBERT - } - }, - f"{GenericKeys.DEFAULT}_small": { - LLMStackTypes.GENERALIST: { - LLMKeys.MODEL_TYPE: ModelTypes.LLM, - LLMKeys.PROVIDER_TYPE: Providers.OLLAMA, - LLMKeys.MODEL: OllamaModels.PHI_TWO_ORANGE - }, - LLMStackTypes.EFFICIENT: { - LLMKeys.MODEL_TYPE: ModelTypes.LLM, - LLMKeys.PROVIDER_TYPE: Providers.OLLAMA, - LLMKeys.MODEL: OllamaModels.STABLELM_TWO_ZEPHYR - }, - LLMStackTypes.CODER: { - LLMKeys.MODEL_TYPE: ModelTypes.LLM, - LLMKeys.PROVIDER_TYPE: Providers.OLLAMA, - LLMKeys.MODEL: OllamaModels.DEEPSEEK_CODER_SMALL - }, - LLMStackTypes.FUNCTION_CALLER: { - LLMKeys.MODEL_TYPE: ModelTypes.LLM, - LLMKeys.PROVIDER_TYPE: Providers.OLLAMA, - LLMKeys.MODEL: OllamaModels.PHI_TWO_ORANGE - }, - LLMStackTypes.EMBEDDER: { - LLMKeys.MODEL_TYPE: ModelTypes.EMBEDDER, - LLMKeys.PROVIDER_TYPE: Providers.FAST_EMBED, - LLMKeys.MODEL: FastEmbedModels.MXBAI_EMBED - }, - LLMStackTypes.RERANKER: { - LLMKeys.MODEL_TYPE: ModelTypes.RERANKER, - LLMKeys.PROVIDER_TYPE: Providers.RAGATOUILLE, - LLMKeys.MODEL: RagatouilleModels.MXBAI_COLBERT - } - } -} - - -# SETUP -def _set_current_mapping(new_mapping: str) -> None: - with open(ModelProviderPaths.CONFIG, "r", encoding="utf-8") as config_file: - config: TOMLConfig = toml.load(config_file) - config[LLMKeys.BASE_INFORMATION][LLMKeys.CURRENT_MAPPING] = new_mapping - with open(ModelProviderPaths.CONFIG, "w", encoding="utf-8") as config_file: - toml.dump(config, config_file) - -def _setup() -> None: - global llm_stack - - if os.path.isfile(ModelProviderPaths.CONFIG): - print("Config file exists. Reading...", DebugLevels.INFO) - with open(ModelProviderPaths.CONFIG, "r", encoding="utf-8") as config_file: - existing_config: TOMLConfig = toml.load(config_file) - base_information: dict[str, Any] = existing_config.get(LLMKeys.BASE_INFORMATION, {}) - current_mapping: str = base_information.get(LLMKeys.CURRENT_MAPPING, GenericKeys.NONE) - if current_mapping in existing_config.keys(): - print(f"{current_mapping} mapping exists. Loading...", DebugLevels.INFO) - llm_stack = LLMStack(provider_map=existing_config[current_mapping]) - return - _set_current_mapping(new_mapping=GenericKeys.DEFAULT) - return - print("Config file does not exist. Creating...", DebugLevels.INFO) - with open(ModelProviderPaths.CONFIG, "w", encoding="utf-8") as config_file: - toml.dump(BASE_CONFIG, config_file) - llm_stack = LLMStack(provider_map=BASE_CONFIG[GenericKeys.DEFAULT]) - - -# CONFIG LISTENER -class MonitorConfig(FileSystemEventHandler): - current_mapping: str = "" - - def _get_config(self) -> TOMLConfig: - with open(ModelProviderPaths.CONFIG, "r", encoding="utf-8") as config_file: - config: TOMLConfig = toml.load(config_file) - return config - - def _valid_config_change(self) -> bool: - config: TOMLConfig = self._get_config() - base_information: dict[str, Any] = config[LLMKeys.BASE_INFORMATION] - current_mapping: str = base_information[LLMKeys.CURRENT_MAPPING] - first_key = next(iter(config)) # Get the first key in the dictionary - del config[first_key] - if current_mapping not in config.keys(): - debug_print(f"Model provider mapping {current_mapping} does not exist! Create its config first before trying to instantiate it...", DebugLevels.ERROR) - if self.current_mapping in config.keys(): - _set_current_mapping(new_mapping=self.current_mapping) - else: - _set_current_mapping(new_mapping=GenericKeys.NONE) - return False - if self.current_mapping == current_mapping: - debug_print("ACE has not changed. Skipping...", DebugLevels.INFO) - return False - self.current_mapping = current_mapping - return True - - def on_modified(self, event: FileSystemEvent) -> None: - if event.is_directory: - return None - try: - print("Config file modified. Checking if valid...", DebugLevels.INFO) - if not self._valid_config_change(): - print("Invalid config change. Restoring...", DebugLevels.INFO) - return - - print("Valid config change. Reloading Model Provider...", DebugLevels.INFO) - global llm_stack - config: TOMLConfig = self._get_config() - current_mapping: str = config[LLMKeys.BASE_INFORMATION][LLMKeys.CURRENT_MAPPING] - provider_map: dict[str, dict[str, str]] = config[current_mapping] - llm_stack = LLMStack(provider_map) - except Exception as error: - raise error - -def startup() -> None: - print("Setting up LLM stack...") - _setup() - event_handler = MonitorConfig() - observer = Observer() - observer.schedule(event_handler=event_handler, path=f"{VolumePaths.HOST_MODEL_PROVIDER}", recursive=False) - observer.start() - print("Listening for config changes...") - try: - while True: - sleep(1) - except Exception as error: - debug_print(f"Layer Error: {error}...", DebugLevels.ERROR) - except KeyboardInterrupt: - observer.stop() - finally: - observer.stop() - observer.join() - - -# MAIN -def generate_response(stack_type: str, system_prompt: str, assistant_begin: str) -> str: - return getattr(llm_stack, stack_type).generate(system_prompt=system_prompt, assistant_begin=assistant_begin) diff --git a/.original/app/components/queue/__.init__.py b/.original/app/components/queue/__.init__.py deleted file mode 100644 index b5f3fb6..0000000 --- a/.original/app/components/queue/__.init__.py +++ /dev/null @@ -1,2 +0,0 @@ -import broker -from .component import component as _queue diff --git a/.original/app/components/queue/broker.py b/.original/app/components/queue/broker.py deleted file mode 100644 index ed7d835..0000000 --- a/.original/app/components/queue/broker.py +++ /dev/null @@ -1,61 +0,0 @@ -# DEPENDENCIES -## Built-in -from typing import Awaitable, Callable -## Third-Party -import nats -from nats.aio.client import Client as NatsClient -from nats.js.client import JetStreamContext -from nats.aio.msg import Msg as NatsMsg -from nats.errors import TimeoutError -from tenacity import retry, wait_exponential, retry_if_exception_type -## Local -from constants.settings import DebugLevels -from helpers import debug_print - - -@retry(retry=retry_if_exception_type(ConnectionRefusedError), wait=wait_exponential(multiplier=1, max=10)) -async def connect(ip_address: str = "127.0.0.1") -> tuple[NatsClient, JetStreamContext]: - print(f"Connecting to NATS Client on {ip_address}...") - nats_client: NatsClient = await nats.connect(f"nats://{ip_address}:4222") - stream: JetStreamContext = nats_client.jetstream() - print("Connected Successfully!") - return nats_client, stream - -async def establish_queues(stream: JetStreamContext, queues: frozenset[str]) -> None: - print("Establishing queues...") - for queue in queues: - try: - print(f"Trying to delete stream {queue}...") - await stream.delete_stream(name=queue) - except Exception: - pass - - print(f"Creating stream {queue}...") - await stream.add_stream(name=queue, subjects=[queue]) - debug_print(f"Established queue: {queue}...") - print("Established all queues!") - -async def subscribe(stream: JetStreamContext, handler: Callable[[NatsMsg], Awaitable[None]], queue: str) -> None: - print(f"Subscribing to {queue}...") - sub = await stream.subscribe(queue, durable=queue) - print(f"Subscribed to {queue}...") - while True: - try: - message: NatsMsg = await sub.next_msg() - await handler(message) - except TimeoutError: - continue - except ConnectionRefusedError: - _, stream = await connect() - -async def publish(stream: JetStreamContext, queue: str, message: str) -> None: - print(f"Publishing {message} to {queue}...", DebugLevels.DEBUG) - ack = await stream.publish(queue, message.encode()) - -async def request(nats_client: NatsClient, queue: str, message: str, timeout: float = 30) -> None: - print(f"Requesting {message} from {queue}...", DebugLevels.DEBUG) - try: - response = await nats_client.request(queue, message.encode(), timeout=timeout) - print(f"Received response: {response.data.decode()}") - except TimeoutError: - print(f"Request to {queue} timed out") diff --git a/.original/app/components/queue/component.py b/.original/app/components/queue/component.py deleted file mode 100644 index f2582aa..0000000 --- a/.original/app/components/queue/component.py +++ /dev/null @@ -1,23 +0,0 @@ -# DEPENDENCIES -## Built-in -import asyncio -from concurrent import futures -## Local -from constants.queue import QueueCommands, QUEUES -from helpers import execute -from . import broker - - -async def queue_server() -> None: - print("Starting queue server...") - loop = asyncio.get_running_loop() - executor = futures.ThreadPoolExecutor() - task = loop.run_in_executor(executor, execute, QueueCommands.START) - print("Establishing queues...") - _, stream = await broker.connect() - await broker.establish_queues(stream=stream, queues=QUEUES) - await task - -# MAIN -def component(component_type: str) -> None: - asyncio.run(queue_server()) \ No newline at end of file diff --git a/.original/app/components/queue/queue.config b/.original/app/components/queue/queue.config deleted file mode 100644 index d987ae7..0000000 --- a/.original/app/components/queue/queue.config +++ /dev/null @@ -1 +0,0 @@ -[Queue Config] \ No newline at end of file diff --git a/.original/app/components/telemetry/__init__.py b/.original/app/components/telemetry/__init__.py deleted file mode 100644 index 49629f0..0000000 --- a/.original/app/components/telemetry/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .component import component as _telemetry \ No newline at end of file diff --git a/.original/app/components/telemetry/api.py b/.original/app/components/telemetry/api.py deleted file mode 100644 index fa6104c..0000000 --- a/.original/app/components/telemetry/api.py +++ /dev/null @@ -1,45 +0,0 @@ -# DEPENDENCIES -## Third-Party -from fastapi import FastAPI -from pydantic import BaseModel -## Local -from helpers import debug_print -from constants.api import APIRoutes -from constants.settings import DebugLevels -from .scraper import collect_telemetry - - -# VALIDATION -class TelemetryRequest(BaseModel): - access: frozenset[str] - context: str - -class TelemetryResponse(BaseModel): - telemetry: dict[str, str] - -class UserInputRequest(BaseModel): - user_input: str - -class UserInputResponse(BaseModel): - success: bool - message: str - - -# SETUP -api = FastAPI() - - -# ROUTES -@api.get(f"{APIRoutes.VONE}/telemetry", response_model=TelemetryResponse) -async def get_telemetry(request: TelemetryRequest) -> TelemetryResponse: - print("Generating telemetry...") - telemetry: dict[str, str] = collect_telemetry(access=request.access, context=request.context) - debug_print(f"Telemetry: {telemetry}", debug_level=DebugLevels.INFO) - model_response = TelemetryResponse(telemetry=telemetry) - return model_response - -@api.post(f"{APIRoutes.VONE}/user_input", response_model=UserInputResponse) -async def post_user_input(request: UserInputRequest) -> UserInputResponse: - debug_print(f"User Input: {request.user_input}", debug_level=DebugLevels.INFO) - model_response = UserInputResponse(success=True, message="Success") - return model_response diff --git a/.original/app/components/telemetry/component.py b/.original/app/components/telemetry/component.py deleted file mode 100644 index 3bdcec0..0000000 --- a/.original/app/components/telemetry/component.py +++ /dev/null @@ -1,11 +0,0 @@ -# DEPENDENCIES -## Third-Party -import uvicorn -## Local -from constants.containers import ComponentPorts -from .api import api - -# MAIN -def component(component_type: str) -> None: - print(f"\nStarting {component_type} API...") - uvicorn.run(api, host="0.0.0.0", port=int(ComponentPorts.TELEMETRY)) diff --git a/.original/app/components/telemetry/scraper.py b/.original/app/components/telemetry/scraper.py deleted file mode 100644 index 20269b1..0000000 --- a/.original/app/components/telemetry/scraper.py +++ /dev/null @@ -1,172 +0,0 @@ -# DEPENDENCIES -## Built-In -import asyncio -from concurrent import futures -from datetime import datetime -from typing import Callable, Optional -## Third-Party -import httpx -## Local -from components.model_provider.api import ModelPrompt, ModelResponse -from constants.containers import ComponentPorts -from constants.model_provider import LLMStackTypes -from constants.telemetry import TelemetryKeys, TelemetrySystemPrompts, TelemetryTypes -from helpers import check_internet_access, KeyValueCacheStore, get_api - - -# CONSTANTS -DEFAULT_CACHE_COUNT: int = 256 -NEWS_API_KEY: str = "pub_42899f1e388aa15a48c2f2a4a74bf5b1af43b" -NEWS_TTL: int = 60 * 60 * 6 # 6 hours - - -# GLOBAL -key_value_cache = KeyValueCacheStore() - - -# LLM -async def _model_response(system_prompt: str) -> ModelResponse: - model_request = ModelPrompt(stack_type=LLMStackTypes.EFFICIENT, system_prompt=system_prompt) - response: str = await get_api(api_port=ComponentPorts.MODEL_PROVIDER, endpoint="generate", payload=model_request) - response_validated = ModelResponse.model_validate_json(response) - return response_validated - - -class TelemetryTypesRef(): - """Enum""" - NONE: str = "none" - TIME: str = "time" - LOCATION: str = "location" - EMBODIMENT: str = "embodiment" - WORLD_OVERVIEW: str = "world_overview" - HARDWARE_STATS: str = "hardware_statistics" - SYSTEM_METRICS: str = "system_metrics" - SOFTWARE_STATS: str = "software_statistics" - SYSTEM_PROCESSES: str = "system_processes" - RESOURCES: str = "resources" - MEMORY: str = "memory" - VISUAL: str = "visual" - AUDIO: str = "audio" - STDOUT: str = "stdout" - USER_INPUT: str = "user_input" - - -# Collection Strategies -def _get_time() -> str: - return datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") - -def _get_location() -> str: - if check_internet_access(): - response: httpx.Response = httpx.get("https://ipapi.co/json/") - data: dict[str, str] = response.json() - city: str = data["city"] - region: str = data["region"] - country_name: str = data["country_name"] - location: str = f"{city}, {region}, {country_name}" - return f"{location}" - else: - return "Location unavailable" - -def _get_embodiment() -> str: - return "embodiment" - -def _get_news() -> frozenset[str]: - news_api: str = f"https://newsdata.io/api/1/news?apikey={NEWS_API_KEY}&language=en&prioritydomain=top" - try: - response = httpx.get(news_api) - response.raise_for_status() - data: dict = response.json() - if data["status"] != "success": - raise Exception("News API returned an error") - - descriptions: list[str] = [ - f"{article.get('title', 'No Title')} - {article.get('description', 'No Description')}" - for article in data.get("results", []) - if article.get("description") - ] - return frozenset(descriptions) - except httpx.HTTPError as http_err: - print(f"HTTP error occurred: {http_err}") - except Exception as err: - print(f"Other error occurred: {err}") - return frozenset({"Unable to retrieve world state"}) - -def _get_world_overview() -> str: - cached_world_overview: Optional[str] = key_value_cache.get(TelemetryKeys.WORLD_OVERVIEW_CACHE) - if cached_world_overview: - return str(key_value_cache.get(TelemetryKeys.WORLD_OVERVIEW_CACHE)) - - descriptions: frozenset[str] = _get_news() - all_articles: str = "\n".join(descriptions) - - system_prompt: str = "" - with open(TelemetrySystemPrompts.WORLD_OVERVIEW, "r", encoding="utf-8") as file: - system_prompt = file.read().replace("{{ news_articles }}", all_articles) - with futures.ThreadPoolExecutor() as executor: - event_loop = asyncio.new_event_loop() - future = executor.submit(lambda: event_loop.run_until_complete(_model_response(system_prompt))) - response: ModelResponse = future.result() - world_overview: str = response.response - key_value_cache.set(key=TelemetryKeys.WORLD_OVERVIEW_CACHE, value=world_overview, ttl_seconds=NEWS_TTL) - return world_overview - -def _get_hardware_statistics() -> str: - print("Cached") - return "hardware statistics" - -def _get_system_metrics() -> str: - return "system metrics" - -def _get_software_statistics() -> str: - return "software statistics" - -def _get_system_processes() -> str: - return "system processes" - -def _get_resources(context: str) -> str: - return "resources" - -def _get_memory(context: str) -> str: - return "memory" - -def _get_visual() -> str: - return "visual" - -def _get_audio() -> str: - return "audio" - -def _get_stdout() -> str: - return "stdout" - -def _get_user_input() -> str: - return "user input" - -ACCESS_CONTEXTLESS_STRATEGY_MAP: dict[str, Callable[[], str]] = { - TelemetryTypes.NONE: lambda: TelemetryTypes.NONE, - TelemetryTypes.TIME: _get_time, - TelemetryTypes.LOCATION: _get_location, - TelemetryTypes.EMBODIMENT: _get_embodiment, - TelemetryTypes.WORLD_OVERVIEW: _get_world_overview, - TelemetryTypes.HARDWARE_STATS: _get_hardware_statistics, - TelemetryTypes.SYSTEM_METRICS: _get_system_metrics, - TelemetryTypes.SOFTWARE_STATS: _get_software_statistics, - TelemetryTypes.SYSTEM_PROCESSES: _get_system_processes, - TelemetryTypes.VISUAL: _get_visual, - TelemetryTypes.AUDIO: _get_audio, - TelemetryTypes.STDOUT: _get_stdout, - TelemetryTypes.USER_INPUT: _get_user_input -} - -ACCESS_CONTEXTUAL_STRATEGY_MAP: dict[str, Callable[[str], str]] = { - TelemetryTypes.RESOURCES: _get_resources, - TelemetryTypes.MEMORY: _get_memory, -} - -def collect_telemetry(access: frozenset[str], context: str) -> dict[str, str]: - telemetry: dict[str, str] = {} - for telemetry_type in access: - if telemetry_type in ACCESS_CONTEXTLESS_STRATEGY_MAP: - telemetry[telemetry_type] = ACCESS_CONTEXTLESS_STRATEGY_MAP[telemetry_type]() - elif telemetry_type in ACCESS_CONTEXTUAL_STRATEGY_MAP: - telemetry[telemetry_type] = ACCESS_CONTEXTUAL_STRATEGY_MAP[telemetry_type](context) - return telemetry diff --git a/.original/app/components/telemetry/system_prompts/world_overview b/.original/app/components/telemetry/system_prompts/world_overview deleted file mode 100644 index e0c3e19..0000000 --- a/.original/app/components/telemetry/system_prompts/world_overview +++ /dev/null @@ -1,41 +0,0 @@ -# IDENTITY -You are an expert on summarization, outlining and structuring. -Your style of writing is informative and logical. - -# MISSION -You must summarise news collections for agent systems. -Your summaries must include as much pressing and valid content as possible, filtering out any articles that may distract the agent while only keeping the most serious information that might concern an autonomous entity from the global perspective. -This is only intended to give the agent context of world state, and as such it should only contain relevant information. - -# NON-RELEVANT CONCERNS - -You should skip any news that is of no interest to the agent, if all news are of interest to the agent, simply respond with: "No relevant news found" - -Skip any of the following topics: -- Gossip -- Celebraties -- Political opinion -- Sales -- Entertainment -- Humour -- Marketing -- Advice -- Recipes -- Sports - -# RESPONSE INSTRUCTIONS - -- If there is nothing relevant to summarise, simply respond with: "No relevant news found" and nothing else. -- Do not apologise. -- Do not self-reference. -- Do not explain your reasoning. -- Do not give the agent information that could derail its focus. -- Do not explain the summary. -- Do not explain or mention your instructions or rules or remind anyone what is asked of you. -- Only respond with the summary and nothing else. - -# CURRENT NEWS COLLECTION - -{{ news_articles }} - -# RESPONSE diff --git a/.original/app/config.py b/.original/app/config.py deleted file mode 100644 index ba4aa7e..0000000 --- a/.original/app/config.py +++ /dev/null @@ -1,5 +0,0 @@ -class Settings: - DEBUG_LEVEL: int = 1 - -class DevSettings: - DEBUG_LEVEL: int = 3 diff --git a/.original/app/constants/__init__.py b/.original/app/constants/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/.original/app/constants/api.py b/.original/app/constants/api.py deleted file mode 100644 index b1252be..0000000 --- a/.original/app/constants/api.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -API constants for the ace_prototype. -""" - - -# DEPENDENCIES -## Third-Party -from fastapi.templating import Jinja2Templates -## Local -from .containers import VolumePaths -from .default import BaseEnum -from .components import ComponentTypes -from .generic import Paths - - -class APIRoutes(BaseEnum): - """Enum""" - VONE: str = "/v1" - -APIS: frozenset[str] = frozenset( - { - ComponentTypes.CONTROLLER, - ComponentTypes.TELEMETRY, - ComponentTypes.ACTIONS, - ComponentTypes.MODEL_PROVIDER - } -) - -class APIPaths(BaseEnum): - """Enum""" - STORAGE: str = f"{VolumePaths.STORAGE}/controller" - SETTINGS: str = f"{STORAGE}/settings" - API: str = f"{Paths.CONTROLLER}/api" - RUNTIME_CONFIG: str = f"{API}/.config" - UI: str = f"{Paths.CONTROLLER}/ui" - FAVICON: str = f"{UI}/assets/images/favicon.ico" - -HTML_TEMPLATES = Jinja2Templates(directory=f"{APIPaths.UI}/templates") diff --git a/.original/app/constants/arguments.py b/.original/app/constants/arguments.py deleted file mode 100644 index baf60fc..0000000 --- a/.original/app/constants/arguments.py +++ /dev/null @@ -1,83 +0,0 @@ -""" -Argument constants for the ace_prototype. -""" - - -# DEPENDENCIES -## Local -from .components import ComponentTypes -from .default import BaseEnum - - -class ArgumentNames(BaseEnum): - """Enum""" - DEV: str = "dev" - TEST: str = "test" - BUILD: str = "build" - SKIP_BUILD: str = "skip_build" - STARTUP: str = "startup" - STOP: str = "stop" - RESTART: str = "restart" - UPDATE: str = "update" - COMPONENT_TYPE: str = "component_type" - -ARGUMENTS_SHORT: dict[str, str] = { - ArgumentNames.DEV: "-d", - ArgumentNames.TEST: "-t", - ArgumentNames.BUILD: "-b", - ArgumentNames.SKIP_BUILD: "-sb", - ArgumentNames.STARTUP: "-s", - ArgumentNames.STOP: "-s", - ArgumentNames.RESTART: "-r", - ArgumentNames.UPDATE: "-u", - ArgumentNames.COMPONENT_TYPE: "-ct" -} - -ARGUMENTS_LONG: dict[str, str] = { - ArgumentNames.DEV: "--dev", - ArgumentNames.TEST: "--test", - ArgumentNames.BUILD: "--build", - ArgumentNames.SKIP_BUILD: "--skip-build", - ArgumentNames.STARTUP: "--startup", - ArgumentNames.STOP: "--stop", - ArgumentNames.RESTART: "--restart", - ArgumentNames.UPDATE: "--update", - ArgumentNames.COMPONENT_TYPE: "--component-type" -} - -ARGUMENTS: dict[str, frozenset] = { - ArgumentNames.DEV: frozenset((ARGUMENTS_SHORT[ArgumentNames.DEV], ARGUMENTS_LONG[ArgumentNames.DEV])), - ArgumentNames.TEST: frozenset((ARGUMENTS_SHORT[ArgumentNames.TEST], ARGUMENTS_LONG[ArgumentNames.TEST])), - ArgumentNames.BUILD: frozenset((ARGUMENTS_SHORT[ArgumentNames.BUILD], ARGUMENTS_LONG[ArgumentNames.BUILD])), - ArgumentNames.SKIP_BUILD: frozenset((ARGUMENTS_SHORT[ArgumentNames.SKIP_BUILD], ARGUMENTS_LONG[ArgumentNames.SKIP_BUILD])), - ArgumentNames.STOP: frozenset((ARGUMENTS_SHORT[ArgumentNames.STOP], ARGUMENTS_LONG[ArgumentNames.STOP])), - ArgumentNames.RESTART: frozenset((ARGUMENTS_SHORT[ArgumentNames.RESTART], ARGUMENTS_LONG[ArgumentNames.RESTART])), - ArgumentNames.UPDATE: frozenset((ARGUMENTS_SHORT[ArgumentNames.UPDATE], ARGUMENTS_LONG[ArgumentNames.UPDATE])), - ArgumentNames.COMPONENT_TYPE: frozenset((ARGUMENTS_SHORT[ArgumentNames.COMPONENT_TYPE], ARGUMENTS_LONG[ArgumentNames.COMPONENT_TYPE])), -} - -BOOL_ARGUMENTS: tuple[str, ...] = ( - ArgumentNames.DEV, - ArgumentNames.TEST, - ArgumentNames.BUILD, - ArgumentNames.SKIP_BUILD, - ArgumentNames.STOP, - ArgumentNames.RESTART, - ArgumentNames.UPDATE -) - -STRING_ARGUMENTS: tuple[str, ...] = ( - ArgumentNames.COMPONENT_TYPE, -) - - -ARGUMENTS_HELP: dict[str, str] = { - ArgumentNames.DEV: "Enable debug mode", - ArgumentNames.TEST: "Run tests", - ArgumentNames.BUILD: "Build the images", - ArgumentNames.SKIP_BUILD: "Skip the build", - ArgumentNames.STOP: "Stop the ACE cluster", - ArgumentNames.RESTART: "Restart the ACE cluster", - ArgumentNames.UPDATE: "Update the ACE", - ArgumentNames.COMPONENT_TYPE: f"Select the component type. Available types: {', '.join(ComponentTypes.get_values())}" -} diff --git a/.original/app/constants/components.py b/.original/app/constants/components.py deleted file mode 100644 index 671aec9..0000000 --- a/.original/app/constants/components.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -Component constants for the ace_prototype. -""" - -# DEPENDENCIES -## Local -from .default import BaseEnum - -class ComponentTypes(BaseEnum): - """Enum""" - CONTROLLER: str = "controller" - QUEUE: str = "queue" - TELEMETRY: str = "telemetry" - ACTIONS: str = "actions" - MODEL_PROVIDER: str = "model_provider" - ASPIRATIONAL: str = "aspirational" - GLOBAL_STRATEGY: str = "global_strategy" - AGENT_MODEL: str = "agent_model" - EXECUTIVE_FUNCTION: str = "executive_function" - COGNITIVE_CONTROL: str = "cognitive_control" - TASK_PROSECUTION: str = "task_prosecution" diff --git a/.original/app/constants/containers.py b/.original/app/constants/containers.py deleted file mode 100644 index 38db2bf..0000000 --- a/.original/app/constants/containers.py +++ /dev/null @@ -1,143 +0,0 @@ -""" -Container constants for the ace_prototype. -""" - - -# DEPENDENCIES -## Built-in -import os -## Local -from .default import BaseEnum -from .components import ComponentTypes -from .generic import ACE - - -_SETUP_FOLDER: str = "./setup" -_ORCHESTRATOR: str = "podman" - - -# IMAGE -ACE_IMAGE_NAME: str = "ace_prototype" -_FULL_ACE_IMAGE_NAME: str = f"localhost/{ACE_IMAGE_NAME}:latest" - -class ImageCommands(BaseEnum): - """Enum""" - CHECK_IMAGES: str = f"{_ORCHESTRATOR} images" - BUILD_IMAGE: str = f"{_ORCHESTRATOR} build -t {ACE_IMAGE_NAME} -f {_SETUP_FOLDER}/Containerfile ." - - -# VOLUMES -_VOLUME: str = "volume" - -class VolumePaths(BaseEnum): - """Enum""" - STORAGE: str = "storage" - HOST: str = f"{os.getcwd()}/{STORAGE}" - HOST_CONTROLLER: str = f"{HOST}/controller" - HOST_LAYERS: str = f"{HOST}/layers" - HOST_MODEL_PROVIDER: str = f"{HOST}/model_provider" - HOST_OUTPUT: str = f"{HOST}/output" - CONTAINER: str = f"/home/{ACE.LOWER_NAME}/{STORAGE}" - CONTROLLER: str = f"{CONTAINER}/controller" - LAYERS: str = f"{CONTAINER}/layers" - MODEL_PROVIDER: str = f"{CONTAINER}/model_provider" - OUTPUT: str = f"{CONTAINER}/output" - -class DevVolumePaths(BaseEnum): - """Enum""" - STORAGE: str = "./.storage" - CONTROLLER: str = f"{STORAGE}/controller" - CONTROLLER_SETTINGS: str = f"{CONTROLLER}/settings" - LAYERS: str = f"{STORAGE}/layers" - MODEL_PROVIDER: str = f"{STORAGE}/model_provider" - OUTPUT: str = f"{STORAGE}/output" - -REQUIRED_STORAGE_PATHS: tuple[str, ...] = ( - VolumePaths.HOST_CONTROLLER, - VolumePaths.HOST_LAYERS, - VolumePaths.HOST_MODEL_PROVIDER, - VolumePaths.HOST_OUTPUT -) - -REQUIRED_DEV_STORAGE_PATHS: tuple[str, ...] = ( - DevVolumePaths.CONTROLLER, - DevVolumePaths.LAYERS, - DevVolumePaths.MODEL_PROVIDER, - DevVolumePaths.OUTPUT -) - -# NETWORK -ACE_NETWORK_NAME: str = f"{ACE.LOWER_NAME}_network" - -class NetworkCommands(BaseEnum): - """Enum""" - CHECK_NETWORK: str = f"{_ORCHESTRATOR} network ls" - CREATE_NETWORK: str = f"{_ORCHESTRATOR} network create {ACE_NETWORK_NAME}" - - -# DEPLOYMENT -class DeploymentFile(BaseEnum): - """Enum""" - PATH: str = f"{_SETUP_FOLDER}/deployment.yaml" - USER_PATH: str = f"{_SETUP_FOLDER}/.user_deployment.yaml" - -class DeploymentCommands(BaseEnum): - """Enum""" - CHECK: str = f"{_ORCHESTRATOR} pod ps" - _DEPLOY_COMMAND: str = f"{_ORCHESTRATOR} kube play" - DEPLOY: str = f"{_DEPLOY_COMMAND} --network {ACE_NETWORK_NAME} --replace {DeploymentFile.USER_PATH}" - STOP: str = f"{_DEPLOY_COMMAND} --network {ACE_NETWORK_NAME} --down {DeploymentFile.USER_PATH}" - -class ComponentPorts(BaseEnum): - """Enum""" - CONTROLLER: str = "2349" - QUEUE: str = "4222" - MODEL_PROVIDER: str = "4223" - TELEMETRY: str = "4931" - ACTIONS: str = "4932" - ASPIRATIONAL: str = "4581" - GLOBAL_STRATEGY: str = "4582" - AGENT_MODEL: str = "4583" - EXECUTIVE_FUNCTION: str = "4584" - COGNITIVE_CONTROL: str = "4585" - TASK_PROSECUTION: str = "4586" - -DEPLOYMENT_REPLACE_KEYWORDS: dict[str, str] = { - "{{ ace_pod_name }}": ACE.LOWER_NAME, - "{{ ace_image_name }}": _FULL_ACE_IMAGE_NAME, - "{{ start_command }}": """python3\n - main.py\n - -sb\n - -ct""", - "{{ controller_name }}": ComponentTypes.CONTROLLER, - "{{ controller_port }}": ComponentPorts.CONTROLLER, - "{{ queue_name }}": ComponentTypes.QUEUE, - "{{ queue_port }}": ComponentPorts.QUEUE, - "{{ model_provider_name }}": ComponentTypes.MODEL_PROVIDER, - "{{ model_provider_port }}": ComponentPorts.MODEL_PROVIDER, - "{{ telemetry_name }}": ComponentTypes.TELEMETRY, - "{{ telemetry_port }}": ComponentPorts.TELEMETRY, - "{{ actions_name }}": ComponentTypes.ACTIONS, - "{{ actions_port }}": ComponentPorts.ACTIONS, - "{{ aspirational_name }}": ComponentTypes.ASPIRATIONAL, - "{{ aspirational_port }}": ComponentPorts.ASPIRATIONAL, - "{{ global_strategy_name }}": ComponentTypes.GLOBAL_STRATEGY, - "{{ global_strategy_port }}": ComponentPorts.GLOBAL_STRATEGY, - "{{ agent_model_name }}": ComponentTypes.AGENT_MODEL, - "{{ agent_model_port }}": ComponentPorts.AGENT_MODEL, - "{{ executive_function_name }}": ComponentTypes.EXECUTIVE_FUNCTION, - "{{ executive_function_port }}": ComponentPorts.EXECUTIVE_FUNCTION, - "{{ cognitive_control_name }}": ComponentTypes.COGNITIVE_CONTROL, - "{{ cognitive_control_port }}": ComponentPorts.COGNITIVE_CONTROL, - "{{ task_prosecution_name }}": ComponentTypes.TASK_PROSECUTION, - "{{ task_prosecution_port }}": ComponentPorts.TASK_PROSECUTION, - "{{ controller_host_path }}": VolumePaths.HOST_CONTROLLER, - "{{ controller_container_path }}": VolumePaths.CONTROLLER, - "{{ controller_volume }}": f"{ACE.LOWER_NAME}_{ComponentTypes.CONTROLLER}_{_VOLUME}", - "{{ layers_host_path }}": VolumePaths.HOST_LAYERS, - "{{ layers_container_path }}": VolumePaths.LAYERS, - "{{ layers_volume }}": f"{ACE.LOWER_NAME}_layers_{_VOLUME}", - "{{ model_provider_host_path }}": VolumePaths.HOST_MODEL_PROVIDER, - "{{ model_provider_container_path }}": VolumePaths.MODEL_PROVIDER, - "{{ model_provider_volume }}": f"{ACE.LOWER_NAME}_model_provider_{_VOLUME}", - "{{ output_host_path }}": VolumePaths.HOST_OUTPUT, - "{{ output_container_path }}": VolumePaths.OUTPUT, - "{{ output_volume }}": f"{ACE.LOWER_NAME}_output_{_VOLUME}" -} diff --git a/.original/app/constants/default.py b/.original/app/constants/default.py deleted file mode 100644 index 08bd686..0000000 --- a/.original/app/constants/default.py +++ /dev/null @@ -1,38 +0,0 @@ -# DEPENDENCIES -## Built-in -from abc import ABC -from typing import final, get_type_hints - - -# ENUMS -class BaseEnum(ABC): - """Base Enum Class""" - _ALLOWED_ENUM_TYPES: tuple[type, ...] = (str, int) - - def __init_subclass__(cls, **kwargs): - super().__init_subclass__(**kwargs) - for var_name, var_value in get_type_hints(cls).items(): - if var_name.startswith("__") or var_name.startswith("_"): - continue - if var_value not in cls._ALLOWED_ENUM_TYPES: - raise TypeError(f"Attribute '{var_name}' must be of type {cls._ALLOWED_ENUM_TYPES}") - - @final - @classmethod - def get_dict(cls) -> dict[str, str]: - base_enum_dict: dict[str, str] = { - variable_name: value - for variable_name, value in vars(cls).items() - if not variable_name.startswith("__") and not variable_name.startswith("_") - } - return base_enum_dict - - @final - @classmethod - def get_values(cls) -> tuple[str, ...]: - return tuple(cls.get_dict().values()) - - @final - @classmethod - def get_frozen_values(cls) -> frozenset[str]: - return frozenset(cls.get_values()) diff --git a/.original/app/constants/generic.py b/.original/app/constants/generic.py deleted file mode 100644 index 45cf1d7..0000000 --- a/.original/app/constants/generic.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -Generic constants for the ace_prototype. -""" - -# DEPENDENCIES -## Built-In -from typing import Any -## Local -from .default import BaseEnum - - -class ACE(BaseEnum): - """Enum""" - NAME: str = "ACE" - LOWER_NAME: str = "ace" - -class Paths(BaseEnum): - COMPONENTS: str = "./components" - ACTIONS: str = f"{COMPONENTS}/actions" - CONTROLLER: str = f"{COMPONENTS}/controller" - INPUTS: str = f"{COMPONENTS}/inputs" - LAYER: str = f"{COMPONENTS}/layer" - MEMORY: str = f"{COMPONENTS}/memory" - MODEL_PROVIDER: str = f"{COMPONENTS}/model_provider" - QUEUE: str = f"{COMPONENTS}/queue" - -class GenericKeys(BaseEnum): - """Enum""" - NONE: str = "none" - EMPTY: str = "" - EMPTY_PATH: str = "./empty" - DEFAULT: str = "default" - - -# TYPES -TOMLConfig = dict[str, dict[str, Any]] diff --git a/.original/app/constants/layer.py b/.original/app/constants/layer.py deleted file mode 100644 index 0e1657f..0000000 --- a/.original/app/constants/layer.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Layer constants for the ace_prototype. -""" - -# DEPENDENCIES -## Local -from .containers import VolumePaths -from .components import ComponentTypes -from .default import BaseEnum -from .generic import GenericKeys - - -class LayerKeys(BaseEnum): - """Enum""" - NAME: str = "name" - TYPE: str = "layer_type" - - # Bus - QUEUE: str = "queue" - MESSAGE_TYPE: str = "message_type" - MESSAGES: str = "messages" - HEADING: str = "heading" - CONTENT: str = "content" - - # Config - BASE_INFORMATION: str = "base_information" - CURRENT_ACE: str = "current_ace" - MISSION: str = "ace_mission" - - # Prompt Files - BASE_PROMPT: str = "base_prompt" - - # State - FIRST_RUN: str = "first_run" - PROCESSING: str = "processing" - MAX_RETRIES: str = "max_retries" - DEFAULT_GUIDANCE: str = "default_guidance" - HAS_DATA: str = "has_data" - DEFAULT_DATA: str = "default_data" - - # Message Types - COMMANDS: str = "commands" - INTERNAL: str = "internal" - GUIDANCE: str = "guidance" - DATA: str = "data" - TELEMETRY: str = "telemetry" - - # Sub Message Types - ACTIONS: str = "actions" - -class LayerPaths(BaseEnum): - """Enum""" - CONFIG: str = f"{VolumePaths.HOST_LAYERS}/.config" - -class Layers(BaseEnum): - """Enum""" - ASPIRATIONAL: str = ComponentTypes.ASPIRATIONAL - GLOBAL_STRATEGY: str = ComponentTypes.GLOBAL_STRATEGY - AGENT_MODEL: str = ComponentTypes.AGENT_MODEL - EXECUTIVE_FUNCTION: str = ComponentTypes.EXECUTIVE_FUNCTION - COGNITIVE_CONTROL: str = ComponentTypes.COGNITIVE_CONTROL - TASK_PROSECUTION: str = ComponentTypes.TASK_PROSECUTION - -class LayerCommands(BaseEnum): - """Enum""" - NONE: str = GenericKeys.NONE - POST: str = "power_on_self_test" - - -# CAPABILITIES -class ActionTags(BaseEnum): - """Enum""" - NONE: str = GenericKeys.NONE - WARNING: str = "WARNING! This is a very expensive operation, use only when necessary..." - OPTIONAL: str = "Don't show this feature if relevant flag is disabled..." - -class Actions(BaseEnum): - """Enum""" - NONE: str = GenericKeys.NONE - MATH: str = "math" - WORKFLOWS: str = "workflows" - FILE: str = "files" - SHELL: str = "shell" - DATABASE: str = "database" - INTERNET: str = "internet" - API: str = "api" - SPEAK: str = "chat" diff --git a/.original/app/constants/model_provider.py b/.original/app/constants/model_provider.py deleted file mode 100644 index ef0623f..0000000 --- a/.original/app/constants/model_provider.py +++ /dev/null @@ -1,94 +0,0 @@ -""" -Model Provider constants for the ace_prototype. -""" - -# DEPENDENCIES -## Local -from .containers import VolumePaths -from .default import BaseEnum - - -class LLMKeys(BaseEnum): - """Enum""" - MODEL_TYPE: str = "model_type" - PROVIDER_TYPE: str = "provider_type" - - # Config - BASE_INFORMATION: str = "base_information" - CURRENT_MAPPING: str = "current_mapping" - - # Provider Details - API_KEY: str = "api_key" - MODEL: str = "model" - CONTEXT: str = "context" - TEMPERATURE: str = "temperature" - RATE_LIMIT: str = "rate_limit" - LOW_VRAM: str = "low_vram" - -class ModelProviderPaths(BaseEnum): - """Enum""" - CONFIG: str = f"{VolumePaths.HOST_MODEL_PROVIDER}/.config" - -class LLMStackTypes(BaseEnum): - """Enum""" - GENERALIST: str = "generalist" - EFFICIENT: str = "efficient" - CODER: str = "coder" - FUNCTION_CALLER: str = "function_caller" - EMBEDDER: str = "embedder" - RERANKER: str = "reranker" - -class ModelTypes(BaseEnum): - """Enum""" - LLM: str = "llm" - EMBEDDER: str = "embedder" - RERANKER: str = "reranker" - - -# PROVIDERS -class Providers(BaseEnum): - """Enum""" - CLAUDE: str = "claude" - GROQ: str = "groq" - OLLAMA: str = "ollama" - OPENAI: str = "openai" - FAST_EMBED: str = "fast_embed" - RAGATOUILLE: str = "ragatouille" - CROSS_ENCODER: str = "cross_encoder" - -class ClaudeModels(BaseEnum): - """Enum""" - OPUS: str = "claude-3-opus-20240229" - SONNET: str = "claude-3-sonnet-20240229" - HAIKU: str = "claude-3-haiku-20240307" - -class GroqModels(BaseEnum): - """Enum""" - MIXTRAL: str = "mixtral-8x7b-32768" - -class OllamaModels(BaseEnum): - """Enum""" - ALPHAMONARCH: str = "alphamonarch" - PHI_TWO_ORANGE: str = "phi2-orange" - STABLELM_TWO_ZEPHYR: str = "stablelm2:1.6b-zephyr-q6_K" - DEEPSEEK_CODER: str = "deepseek-coder:6.7b-instruct-q3_K_L" - DEEPSEEK_CODER_SMALL: str = "deepseek-coder:1.3b-instruct-q6_K" - GORILLA_OPENFUNCTIONS: str = "adrienbrault/gorilla-openfunctions-v2:Q3_K_L" - -class OpenAIModels(BaseEnum): - """Enum""" - FOUR: str = "gpt-4" - FOUR_TURBO: str = "gpt-4-turbo-preview" - THREE_POINT_FIVE: str = "gpt-3.5-turbo" - -class RagatouilleModels(BaseEnum): - """Enum""" - MXBAI_COLBERT: str = "mixedbread-ai/mxbai-colbert-large-v1" - -class FastEmbedModels(BaseEnum): - """Enum""" - MXBAI_EMBED: str = "mixedbread-ai/mxbai-embed-large-v1" - -class CrossEncoderModels(BaseEnum): - """Enum""" - MXBAI_RERANKER: str = "mixedbread-ai/mxbai-rerank-large-v1" diff --git a/.original/app/constants/prompts.py b/.original/app/constants/prompts.py deleted file mode 100644 index ab638be..0000000 --- a/.original/app/constants/prompts.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -Prompt constants for the ace_prototype. -""" - - -# DEPENDENCIES -## Local -from .generic import Paths -from .default import BaseEnum - - -class PromptKeys(BaseEnum): - TYPE: str = "prompt_type" - PROMPT: str = "prompt" - CONTEXT: str = "context" - IDENTITY: str = "identity" - MISSION: str = "ace_mission" - GUIDANCE: str = "guidance" - DATA: str = "data" - TELEMETRY: str = "telemetry" - RESPONSE_FORMAT: str = "response_format" - SCHEMA: str = "schema" - -class PromptTypes(BaseEnum): - """Enum""" - REFINE: str = "refine" - OUTPUT: str = "output" - -class PromptFilePaths(BaseEnum): - """Enum""" - _SYSTEM_PROMPTS: str = f"{Paths.LAYER}/system_prompts" - LAYER: str = f"{_SYSTEM_PROMPTS}/layer" - CONTEXT: str = f"{_SYSTEM_PROMPTS}/ace_context" - IDENTITIES: str = f"{_SYSTEM_PROMPTS}/identities" - _RESPONSE_SCHEMAS: str = f"{_SYSTEM_PROMPTS}/responses" - ACTION_RESPONSE: str = f"{_RESPONSE_SCHEMAS}/action_response" - OUTPUT_RESPONSE: str = f"{_RESPONSE_SCHEMAS}/output_response" - RESPONSE_FORMAT: str = f"{_RESPONSE_SCHEMAS}/response_format" - EXTRA_RULES: str = f"{_RESPONSE_SCHEMAS}/extra_rules" - SCHEMAS: str = f"{_RESPONSE_SCHEMAS}/schemas" diff --git a/.original/app/constants/queue.py b/.original/app/constants/queue.py deleted file mode 100644 index 095c2e2..0000000 --- a/.original/app/constants/queue.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Queue constants for the ace_prototype. -""" - - -# DEPENDENCIES -## Local -from .components import ComponentTypes as Queues -from .default import BaseEnum - - -class QueueCommands(BaseEnum): - """Enum""" - START: str = "nats-server -js" - -QUEUES: frozenset[str] = frozenset( - { - Queues.ASPIRATIONAL, - Queues.GLOBAL_STRATEGY, - Queues.AGENT_MODEL, - Queues.EXECUTIVE_FUNCTION, - Queues.COGNITIVE_CONTROL, - Queues.TASK_PROSECUTION, - } -) - - -# BUSSES -class BusKeys(BaseEnum): - """Enum""" - UP: str = "northbound" - DOWN: str = "southbound" - -BUSSES_DOWN: dict[str, str] = { - Queues.CONTROLLER: Queues.ASPIRATIONAL, - Queues.ASPIRATIONAL: Queues.GLOBAL_STRATEGY, - Queues.GLOBAL_STRATEGY: Queues.AGENT_MODEL, - Queues.AGENT_MODEL: Queues.EXECUTIVE_FUNCTION, - Queues.EXECUTIVE_FUNCTION: Queues.COGNITIVE_CONTROL, - Queues.COGNITIVE_CONTROL: Queues.TASK_PROSECUTION, -} - -BUSSES_UP: dict[str, str] = { - Queues.TASK_PROSECUTION: Queues.COGNITIVE_CONTROL, - Queues.COGNITIVE_CONTROL: Queues.EXECUTIVE_FUNCTION, - Queues.EXECUTIVE_FUNCTION: Queues.AGENT_MODEL, - Queues.AGENT_MODEL: Queues.GLOBAL_STRATEGY, - Queues.GLOBAL_STRATEGY: Queues.ASPIRATIONAL, -} diff --git a/.original/app/constants/settings.py b/.original/app/constants/settings.py deleted file mode 100644 index 98a9bcf..0000000 --- a/.original/app/constants/settings.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -Debug constants for the ace_prototype. -""" - -# DEPENDENCIES -## Local -from .default import BaseEnum - - -class DebugLevels(BaseEnum): - """Enum""" - ERROR: int = 0 - WARNING: int = 1 - INFO: int = 2 - DEBUG: int = 3 \ No newline at end of file diff --git a/.original/app/constants/startup.py b/.original/app/constants/startup.py deleted file mode 100644 index 74c01a5..0000000 --- a/.original/app/constants/startup.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -Startup constants for the ace_prototype. -""" - -# DEPENDENCIES -## Local -from .default import BaseEnum - - -class StartupCommands(BaseEnum): - """Enum""" - UPDATE: str = "git pull" diff --git a/.original/app/constants/telemetry.py b/.original/app/constants/telemetry.py deleted file mode 100644 index d62454c..0000000 --- a/.original/app/constants/telemetry.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -Layer constants for the ace_prototype. -""" - -# DEPENDENCIES -## Local -from .default import BaseEnum -from .generic import GenericKeys - - -class TelemetryKeys(BaseEnum): - """Enum""" - WORLD_OVERVIEW_CACHE: str = "world_overview_cache" - -class TelemetryPaths(BaseEnum): - """Enum""" - SYSTEM_PROMPTS: str = "components/telemetry/system_prompts" - -class TelemetrySystemPrompts(BaseEnum): - """Enum""" - WORLD_OVERVIEW: str = f"{TelemetryPaths.SYSTEM_PROMPTS}/world_overview" - -class TelemetryTypes(BaseEnum): - """Enum""" - NONE: str = GenericKeys.NONE - TIME: str = "time" - LOCATION: str = "location" - EMBODIMENT: str = "embodiment" - WORLD_OVERVIEW: str = "world_overview" - HARDWARE_STATS: str = "hardware_statistics" - SYSTEM_METRICS: str = "system_metrics" - SOFTWARE_STATS: str = "software_statistics" - SYSTEM_PROCESSES: str = "system_processes" - RESOURCES: str = "resources" - MEMORY: str = "memory" - VISUAL: str = "visual" - AUDIO: str = "audio" - STDOUT: str = "stdout" - USER_INPUT: str = "user_input" diff --git a/.original/app/exceptions/__init__.py b/.original/app/exceptions/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/.original/app/exceptions/error_handling.py b/.original/app/exceptions/error_handling.py deleted file mode 100644 index 3a3d2cf..0000000 --- a/.original/app/exceptions/error_handling.py +++ /dev/null @@ -1,10 +0,0 @@ -# DEPENDENCIES -## Built-in -import os - - -# FUNCTIONS -def exit_on_error(error: str) -> None: - print(f"ERROR: {error}") - os._exit(1) - \ No newline at end of file diff --git a/.original/app/helpers.py b/.original/app/helpers.py deleted file mode 100644 index 313849f..0000000 --- a/.original/app/helpers.py +++ /dev/null @@ -1,257 +0,0 @@ -# DEPENDENCIES -## Built-in -import subprocess -from subprocess import Popen -import sys -from time import time -from typing import Any, IO, Optional -## Third-Party -import aiohttp -import httpx -from pydantic import BaseModel -## Local -from config import Settings -from constants.api import APIRoutes -from constants.containers import ComponentPorts -from constants.settings import DebugLevels -from exceptions.error_handling import exit_on_error - - -# LOGGING -def debug_print(message: str, debug_level: int = DebugLevels.INFO, end: Optional[str] = None) -> None: - if not Settings.DEBUG_LEVEL >= debug_level: - return - if end: - print(message, end=end) - else: - print(message) - - -# SHELL -def execute( - command: str, - should_print_result: bool = True, - ignore_error: bool = False, - error_message: str = "", - debug_level: int = DebugLevels.ERROR -) -> str: - """ - Execute a shell command and return the output - - Arguments: - command (str): The shell command to execute - should_print_result (bool): Whether to print the result - ignore_error (bool): Whether to ignore any errors - error_message (str): The error message to display if ignore_error is False - debug_level (int): The debug level to use for logging - - Returns: - str: The output of the shell command - """ - if not error_message: - error_message = f"Unable to execute command: {command}" - debug_print(f"Running Command: {command}", debug_level=4) - command_list: tuple[str, ...] = tuple(command.split()) - process: Popen = subprocess.Popen(command_list, stdout=subprocess.PIPE, text=True) - if should_print_result: - has_printed: bool = False - while process.poll() is None: - if not process.stdout: - continue - print_lines: IO = process.stdout - if has_printed: - for _ in print_lines: - sys.stdout.write("\033[F") # Move cursor up one line - sys.stdout.write("\033[K") # Clear line - for line in print_lines: - debug_print(line, debug_level=debug_level, end="") - has_printed = True - if process.returncode != 0 and not ignore_error: - exit_on_error(f"{error_message}\n{process.stderr}") - stdout, stderr = process.communicate() - return stdout - -def exec_check_exists(check_command: str, keyword: str) -> bool: - """ - Checks if the keyword exists in the output of the check_command - - Arguments: - check_command (str): The shell command to used to check if the keyword exists - keyword (str): The keyword to check for - - Returns: - bool: True if the keyword exists, False otherwise - """ - debug_print(f"\nChecking using {check_command} for {keyword}...", DebugLevels.DEBUG) - existing: frozenset = frozenset(execute(check_command, debug_level=DebugLevels.DEBUG).split("\n")) - debug_print(f"Existing Terms: {existing}", DebugLevels.DEBUG) - for entry in existing: - if keyword in entry: - return True - return False - - -# API REQUESTS -async def get_api(api_port: str, endpoint: str, payload: BaseModel) -> str: - """ - Sends a GET request to the specified API endpoint with the provided payload and returns the response as a string - - Arguments: - api_port (str): The API port to send the request to - endpoint (str): The API endpoint to send the request to - payload (BaseModel): The payload to send in the request - - Returns: - str: The response json as a string - """ - if api_port not in ComponentPorts.get_frozen_values(): - raise ValueError(f"Invalid API Port: {api_port}") - print(f"Send Payload: {payload.model_dump_json()}") - async with aiohttp.ClientSession() as session: - async with session.get( - url=f"http://127.0.0.1:{api_port}{APIRoutes.VONE}/{endpoint}", - data=payload.model_dump_json(), - headers={'Content-Type': 'application/json'}, - timeout=900 - ) as response: - print("Api Response Status:", response.status) - print("Api Response Content-type:", response.headers['content-type']) - html: str = await response.text() - print("Api Response Body:", html) - return html - -async def post_api(api_port: str, endpoint: str, payload: BaseModel) -> str: - """ - Sends a POST request to the specified API endpoint with the provided payload and returns the response as a string - - Arguments: - api_port (str): The API port to send the request to - endpoint (str): The API endpoint to send the request to - payload (BaseModel): The payload to send in the request - - Returns: - str: The response json as a string - """ - if api_port not in ComponentPorts.get_frozen_values(): - raise ValueError(f"Invalid API Port: {api_port}") - print(f"Send Payload: {payload.model_dump_json()}") - async with aiohttp.ClientSession() as session: - async with session.post( - url=f"http://127.0.0.1:{api_port}{APIRoutes.VONE}/{endpoint}", - data=payload.model_dump_json(), - headers={'Content-Type': 'application/json'}, - timeout=900 - ) as response: - print("Status:", response.status) - print("Content-type:", response.headers['content-type']) - html: str = await response.text() - print("Body:", html, "...") - return html - -def check_internet_access() -> bool: - """ - Check if the device has internet access - - Returns: - bool: True if the device has internet access, False otherwise - """ - try: - response = httpx.get("https://www.google.com/") - response.raise_for_status() - return True - except httpx.RequestError: - return False - - -# CACHE -class KeyValueCacheStore: - """ - A simple key-value cache store - - Attributes: - store (dict): The cache store - ttl_map (dict): The time-to-live (TTL) map for each key - fetches (int): The number of total fetches - individual_fetches (dict): The number of individual fetches for each key - - Methods: - get(self, key: str) -> Optional[Any] - set(self, key: str, value: Any, ttl_seconds: Optional[int] = None) -> None - invalidate(self, key: str) -> None - clear(self) -> None - get_stats(self) -> dict - """ - def __init__(self): - self.store: dict[str, Any] = {} - self.ttl_map: dict[str, dict[str, int]] = {} - self.fetches: int = 0 - self.individual_fetches: dict[str, int] = {} - - def get(self, key: str) -> Optional[Any]: - """ - Get a value from the cache - - Arguments: - key (str): The key to get the value for - - Returns: - Any: The value from the cache, or None if the key is not found - """ - self.fetches += 1 - self.individual_fetches[key] = self.individual_fetches.get(key, 0) + 1 - if key in self.ttl_map: - if int(time()) > self.ttl_map[key]["expiry_time"]: - self.invalidate(key) - return None - return self.store[key] - return self.store.get(key, None) - - def set(self, key: str, value: Any, ttl_seconds: Optional[int] = None) -> None: - """ - Set a value in the cache. If ttl_seconds is provided, the value will expire after that many seconds - - Arguments: - key (str): The key to store the value under - value (Any): The value to store - ttl_seconds (Optional[int]): The number of seconds until the value expires - """ - self.store[key] = value - if ttl_seconds: - ttl_map: dict[str, int] = { - "ttl": ttl_seconds, - "expiry_time": int(time()) + ttl_seconds - } - self.ttl_map[key] = ttl_map - - def invalidate(self, key): - """ - Invalidate a value in the cache - - Arguments: - key (str): The key to invalidate - - Raises: - KeyError: If the key is not found - """ - try: - del self.store[key] - except KeyError: - raise KeyError(f"Key not found: {key}") - - def clear(self): - """ - Clear the entire cache - """ - self.store.clear() - self.ttl_map.clear() - self.fetches = 0 - self.individual_fetches.clear() - - def get_stats(self): - """ - Get the statistics for the cache - - Returns: - dict: The statistics for the cache - """ - return {"size": len(self.store), "fetches": self.fetches, "individual_fetches": self.individual_fetches} diff --git a/.original/app/main.py b/.original/app/main.py deleted file mode 100755 index be696cb..0000000 --- a/.original/app/main.py +++ /dev/null @@ -1,308 +0,0 @@ -#!/usr/bin/env python3 -""" -Starting point for the ACE Prototype, as well as the individual components. - -Author: jayfalls - -Arguments: - -d/--dev: bool -> Enable dev mode - -t/--test: bool -> Run tests - -b/--build: bool -> Build the images - -nb/--no-build: bool -> Skip the build check - -s/--stop: bool -> Stop the ACE cluster - -r/--restart: bool -> Restart the ACE cluster deployment - -u/--update: bool -> Update the ACE - -ct/--component-type: str -> The type of this component -""" - - -# DEPENDENCIES -## Built-in -from argparse import ArgumentParser -import os -from typing import Callable -## Third Party -import pytest -## Local -from constants.components import ComponentTypes -from constants.settings import DebugLevels -from constants.startup import StartupCommands -from constants.arguments import (ArgumentNames, ARGUMENTS, ARGUMENTS_HELP, - BOOL_ARGUMENTS, STRING_ARGUMENTS, ARGUMENTS_SHORT -) -from constants.containers import (ACE_IMAGE_NAME, ImageCommands, - REQUIRED_STORAGE_PATHS, REQUIRED_DEV_STORAGE_PATHS, - ACE_NETWORK_NAME, NetworkCommands, - DeploymentFile, DEPLOYMENT_REPLACE_KEYWORDS, DeploymentCommands -) -from constants.generic import ACE -from components import COMPONENT_MAP -from exceptions.error_handling import exit_on_error -from helpers import debug_print, execute, exec_check_exists - - -# VARIABLES -class ACEArguments: - """ - Class representing arguments for the ACE. - - Attributes: - dev (bool): Flag indicating dev mode. - should_test (bool): Flag indicating if testing should be done. - should_build (bool): Flag indicating if building should be done. - no_build (bool): Flag indicating to skip building. - local_mode (bool): Flag indicating local mode. - stop (bool): Flag indicating to stop. - should_restart (bool): Flag indicating if restart is needed. - should_update (bool): Flag indicating if update is needed. - component (str): String representing a component. - """ - dev: bool = False - should_test: bool = False - should_build: bool = False - no_build: bool = False - stop: bool = False - should_restart: bool = False - should_update: bool = False - component: str = "" - - -# ARGUMENTS -def _set_arguments(arg_parser: ArgumentParser) -> None: - for argument in BOOL_ARGUMENTS: - arg_parser.add_argument(*tuple(ARGUMENTS.get(argument, "")), action='store_true', required=False, help=ARGUMENTS_HELP[argument]) - for argument in STRING_ARGUMENTS: - arg_parser.add_argument(*tuple(ARGUMENTS.get(argument, "")), type=str, required=False, help=ARGUMENTS_HELP[argument]) - -def _parse_arguments(arg_parser: ArgumentParser) -> ACEArguments: - arguments: dict = {key: value for key, value in vars(arg_parser.parse_args()).items() if value is not None} - ace_arguments = ACEArguments() - ace_arguments.dev = arguments.get(ArgumentNames.DEV, False) - if ace_arguments.dev: - pass - # ASSIGN DEV LOGIC - ace_arguments.component = arguments.get(ArgumentNames.COMPONENT_TYPE, ArgumentNames.STARTUP) - ace_arguments.should_test = arguments.get(ArgumentNames.TEST, False) - ace_arguments.should_build = arguments.get(ArgumentNames.BUILD, False) - ace_arguments.no_build = arguments.get(ArgumentNames.SKIP_BUILD, False) - ace_arguments.stop = arguments.get(ArgumentNames.STOP, False) - ace_arguments.should_update = arguments.get(ArgumentNames.UPDATE, False) - ace_arguments.should_restart = arguments.get(ArgumentNames.RESTART, False) - debug_print(f"Arguments: {ace_arguments.__dict__}", DebugLevels.DEBUG) - if ace_arguments.component not in ComponentTypes.get_frozen_values() and ace_arguments.component != ArgumentNames.STARTUP: - exit_on_error(f"Invalid component type: {ace_arguments.component}!\nPlease use one of the following: {ComponentTypes.get_values()}") - return ace_arguments - -def assign_arguments() -> ACEArguments: - """ - Assigns runtime arguments. - - Arguments: - None - - Returns: - ACEArguments containing the runtime arguments. - """ - arg_parser = ArgumentParser() - _set_arguments(arg_parser) - ace_arguments: ACEArguments = _parse_arguments(arg_parser) - return ace_arguments - - -# SETUP -def _setup_folders() -> None: - required_paths: tuple[str, ...] = (*REQUIRED_STORAGE_PATHS, *REQUIRED_DEV_STORAGE_PATHS) - _ = [os.makedirs(dir_path, exist_ok=True) for dir_path in required_paths] - -def _setup_user_deployment_file() -> None: - if os.path.isfile(DeploymentFile.USER_PATH): - return - print("\nFirst time setting up user deployment file...") - with open(DeploymentFile.PATH, "r", encoding="utf-8") as deployment_file: - deployment_string: str = deployment_file.read() - deployment_file.close() - for key, replace in DEPLOYMENT_REPLACE_KEYWORDS.items(): - deployment_string = deployment_string.replace(key, replace) - with open(DeploymentFile.USER_PATH, "w", encoding="utf-8") as user_deployment_file: - user_deployment_file.write(deployment_string) - user_deployment_file.close() - # DO DEV AS WELL - -def setup() -> None: - """ - A function to set up folders and the user deployment file. - - Arguments: - None - - Returns: - None - """ - _setup_folders() - _setup_user_deployment_file() - - -# BUILD -def build(request_build: bool) -> bool: - """ - A function which builds if image doesn't exist or if request_build flag is set. - - Arguments: - request_build: bool | Flag indicating if a build is requested. - - Returns: - A bool indicating whether a build was performed. - """ - print("\nChecking if build is required...") - check_images_command: str = ImageCommands.CHECK_IMAGES - image_exists: bool = exec_check_exists(check_images_command, ACE_IMAGE_NAME) - should_build: bool = not image_exists or request_build - if should_build: - if not image_exists: - print("\nImage does not exist\nBuilding container...") - else: - print("\nBuilding container...") - build_ace_images_command: str = ImageCommands.BUILD_IMAGE - execute(build_ace_images_command, error_message="Unable to build image") - return should_build - print("\nImage already exists\nSkipping build...") - return should_build - - -# TESTS -def _run_tests(tests_to_run: tuple[str, ...]) -> None: - print("Running tests...") - # Create a pytest argument parser - args = ['--capture=no'] - - # Add the names of the tests to run to the arguments list - args.extend(tests_to_run) - - # Run the tests using pytest's main function - pytest.main(args) - -def test() -> None: - """ - Fake test. - - Arguments: - None - - Returns: - None - """ - print("\nTest Mode\n") - tests: tuple[str, ...] = ("test_1", "test_2", "test_3") - _run_tests(tests) - - -# RUNTIME -def update() -> None: - """ - Update the ACE. - - Arguments: - None - - Returns: - None - """ - print(f"\nUpdating {ACE.NAME}...") - update_command: str = StartupCommands.UPDATE - execute(update_command) - -def _setup_network() -> None: - if not exec_check_exists(NetworkCommands.CHECK_NETWORK, ACE_NETWORK_NAME): - print("\nFirst time setting up network...") - execute(NetworkCommands.CREATE_NETWORK) - -def stop(exists: bool) -> None: - """ - Stops the ACE if it exists. - - Arguments: - exists: bool | Indicates if the ace is running. - - Returns: - None - """ - if not exists: - print(f"{ACE.NAME} is not running! Cannot stop...") - return - print(f"\nStopping {ACE.NAME}...") - execute(DeploymentCommands.STOP) - -def start_ace(restart: bool, exists: bool) -> None: - """ - Start the ACE, handling restarts. - - Arguments: - restart: bool | Whether to restart the ACE. - exists: bool | Whether the ACE is already running. - - Returns: - None - """ - deploy_command: str = DeploymentCommands.DEPLOY - if restart: - print(f"\nRestarting {ACE.NAME}...") - execute(deploy_command) - return - if exists: - print(f"\nACE is already running...\n\nPlease run with {ARGUMENTS_SHORT[ArgumentNames.RESTART]} to restart!") - return - print(f"\nStarting {ACE.NAME}...") - execute(deploy_command) - -def start_component(component_type: str) -> None: - """ - Start a component of a specified type, optionally in dev mode. - - Args: - component_type: str | The type of component to start. - dev: bool | Whether to start the component in dev or not. - - Returns: - None - """ - title = component_type.replace("_", " ").title() - print(f"\nStarting {title}...") - start: Callable[[str], None] = COMPONENT_MAP[component_type] - start(component_type) - - -# MAIN -def main() -> None: - """ - Initialises and starts the ACE / ACE component based off the starting arguments. - - Arguments: - None - - Returns: - None - """ - ace_arguments: ACEArguments = assign_arguments() - setup() - if ace_arguments.should_update: - ace_arguments.should_build = True - update() - if not ace_arguments.no_build: - built: bool = build(ace_arguments.should_build) - if built: - ace_arguments.should_restart = True - if ace_arguments.should_test: - test() - return - if not ace_arguments.component == ArgumentNames.STARTUP: - start_component(ace_arguments.component) - return - exists: bool = exec_check_exists(DeploymentCommands.CHECK, ACE.LOWER_NAME) - if ace_arguments.stop: - stop(exists) - return - _setup_network() - start_ace(ace_arguments.should_restart, exists) - -if __name__ == "__main__": - main() diff --git a/.original/app/requirements b/.original/app/requirements deleted file mode 100644 index 8a82282..0000000 --- a/.original/app/requirements +++ /dev/null @@ -1,23 +0,0 @@ -aiohttp==3.9.3 -anthropic==0.21.3 -fastapi==0.110.0 -fastembed==0.2.5 -groq==0.4.2 -httpx==0.25.2 -jinja2==3.1.3 -nats-py==2.7.2 -numpy==1.24.3 -ollama==0.1.4 -openai==1.14.1 -passlib[bcrypt]==1.7.4 -pydantic==2.6.2 -pytest==8.0.1 -python-jose[cryptography]==3.3.0 -python-multipart==0.0.9 -ragatouille==0.0.8.post2 -sentence-transformers==2.6.1 -structlog==24.1.0 -tenacity==8.2.3 -toml==0.10.2 -uvicorn==0.27.1 -watchdog==4.0.0 \ No newline at end of file diff --git a/.original/app/setup/Containerfile b/.original/app/setup/Containerfile deleted file mode 100644 index 096e0c0..0000000 --- a/.original/app/setup/Containerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM python:alpine - -COPY . /home/ace - -RUN apk add --no-cache bash build-base libffi-dev python3-dev nats-server && \ - pip3 install -r /home/ace/requirements && \ - apk del build-base libffi-dev python3-dev && \ - rm -rf /var/cache/apk/* - -WORKDIR /home/ace - -ENTRYPOINT ["python3", "main.py"] \ No newline at end of file diff --git a/.original/app/setup/deployment.yaml b/.original/app/setup/deployment.yaml deleted file mode 100644 index 718a891..0000000 --- a/.original/app/setup/deployment.yaml +++ /dev/null @@ -1,138 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - labels: - app: {{ ace_pod_name }} - name: {{ ace_pod_name }} -spec: - containers: - - command: - - {{ start_command }} - - {{ controller_name }} - image: {{ ace_image_name }} - name: {{ controller_name }} - ports: - - containerPort: {{ controller_port }} - hostPort: {{ controller_port }} - volumeMounts: - - mountPath: {{ controller_container_path }} - name: {{ controller_volume }} - - mountPath: {{ layers_container_path }} - name: {{ layers_volume }} - - command: - - {{ start_command }} - - {{ queue_name }} - image: {{ ace_image_name }} - name: {{ queue_name }} - ports: - - containerPort: {{ queue_port }} - - command: - - {{ start_command }} - - {{ model_provider_name }} - image: {{ ace_image_name }} - name: {{ model_provider_name }} - ports: - - containerPort: {{ model_provider_port }} - volumeMounts: - - mountPath: {{ model_provider_container_path }} - name: {{ model_provider_volume }} - - command: - - {{ start_command }} - - {{ telemetry_name }} - image: {{ ace_image_name }} - name: {{ telemetry_name }} - ports: - - containerPort: {{ telemetry_port }} - - command: - - {{ start_command }} - - {{ actions_name }} - image: {{ ace_image_name }} - name: {{ actions_name }} - ports: - - containerPort: {{ actions_port }} - - command: - - {{ start_command }} - - {{ memory_name }} - image: {{ ace_image_name }} - name: {{ memory_name }} - ports: - - containerPort: {{ memory_port }} - - command: - - {{ start_command }} - - {{ aspirational_name }} - image: {{ ace_image_name }} - name: {{ aspirational_name }} - ports: - - containerPort: {{ aspirational_port }} - volumeMounts: - - mountPath: {{ layers_container_path }} - name: {{ layers_volume }} - - command: - - {{ start_command }} - - {{ global_strategy_name }} - image: {{ ace_image_name }} - name: {{ global_strategy_name }} - ports: - - containerPort: {{ global_strategy_port }} - volumeMounts: - - mountPath: {{ layers_container_path }} - name: {{ layers_volume }} - - command: - - {{ start_command }} - - {{ agent_model_name }} - image: {{ ace_image_name }} - name: {{ agent_model_name }} - ports: - - containerPort: {{ agent_model_port }} - volumeMounts: - - mountPath: {{ layers_container_path }} - name: {{ layers_volume }} - - command: - - {{ start_command }} - - {{ executive_function_name }} - image: {{ ace_image_name }} - name: {{ executive_function_name }} - ports: - - containerPort: {{ executive_function_port }} - volumeMounts: - - mountPath: {{ layers_container_path }} - name: {{ layers_volume }} - - command: - - {{ start_command }} - - {{ cognitive_control_name }} - image: {{ ace_image_name }} - name: {{ cognitive_control_name }} - ports: - - containerPort: {{ cognitive_control_port }} - volumeMounts: - - mountPath: {{ layers_container_path }} - name: {{ layers_volume }} - - command: - - {{ start_command }} - - {{ task_prosecution_name }} - image: {{ ace_image_name }} - name: {{ task_prosecution_name }} - ports: - - containerPort: {{ task_prosecution_port }} - volumeMounts: - - mountPath: {{ layers_container_path }} - name: {{ layers_volume }} - - mountPath: {{ output_container_path }} - name: {{ output_volume }} - volumes: - - hostPath: - path: {{ controller_host_path }} - type: Directory - name: {{ controller_volume }} - - hostPath: - path: {{ layers_host_path }} - type: Directory - name: {{ layers_volume }} - - hostPath: - path: {{ model_provider_host_path }} - type: Directory - - hostPath: - path: {{ output_host_path }} - type: Directory - name: {{ output_volume }} - diff --git a/.original/documentation/design_doc.md b/.original/documentation/design_doc.md deleted file mode 100644 index f947c2f..0000000 --- a/.original/documentation/design_doc.md +++ /dev/null @@ -1,188 +0,0 @@ -[⬆️](..) - - -# Requirements -- **Test Driven Development** -- Must be fully **containerised** -- Containers must have **resource limits** -- Must be be able to fit on less than **4gb ram** -- Every prompt and output should be less than **4k tokens** -- Must be **entirely local** -- Must have a **strong static typing and linting** -- Must be an **MVP** -- Must fulfil all the **basic requirements** of an [**ACE Framework**](https://github.com/daveshap/ACE_Framework) - - -# Technologies Used -## Language -- [**Python**](https://github.com/python/cpython) for backend - - [**Pyright**](https://github.com/microsoft/pyright) for static type checking - - [**Ruff**](https://github.com/astral-sh/ruff) for linting - - [**Pydantic**](https://github.com/pydantic/pydantic) for data validation - - [**PDM**](https://github.com/pdm-project/pdm) for package manager - -## Communication -- [**NATS**](https://github.com/nats-io/nats-server) for queue management and messaging -- [**Fast-API**](https://github.com/tiangolo/fastapi) for apis - -## Orchestration -- [**Podman**](https://github.com/containers/podman) for containerisation - -## UI -- [**HTMX**](https://github.com/bigskysoftware/htmx) for server side rendering - - [**Jinja**](https://github.com/pallets/jinja) for html templating -- [**DaisyUI**](https://github.com/saadeghi/daisyui) for components - -## LLM -- [**Ollama**](https://github.com/ollama/ollama) for serving models - - [**Phi-2-Orange**](https://huggingface.co/rhysjones/phi-2-orange) as the base model -- [**Guardrails**](https://github.com/guardrails-ai/guardrails) for output validation -- [**Open Interpreter**](https://github.com/KillianLucas/open-interpreter) for tool usage - -### RAG -- [**LanceDB**](https://github.com/lancedb/lancedb) for memory - - [**SPR**](https://github.com/daveshap/SparsePrimingRepresentations) for compression - - [**FastEmbed**](https://github.com/qdrant/fastembed) for embedding - -## Observability -- [**VictoriaMetrics**](https://github.com/VictoriaMetrics/VictoriaMetrics) for metrics, logs aggregation and storage -- [**Grafana**](https://github.com/grafana/grafana) for visualisation -- [**Fluent-bit**](https://github.com/fluent/fluent-bit) for logs & metrics collection and forwarding -? - [**PSUtil**](https://github.com/giampaolo/psutil) for program metrics -- [**Structlog**](https://github.com/hynek/structlog) for logging - -## Storage -- [**Postgress**](https://github.com/postgres/postgres) for database - - [**Peewee**](https://github.com/coleifer/peewee) for ORM -- [**Garnet**](https://github.com/microsoft/garnet) for caching - -# System Design -![architecture.png](./media/architecture.png) - -## Core -## **Model Provider** -This will serve the llm models to each of the layers and security layer in future iterations -- **MVP** - - Serve model inference over an api - - Multiple specialest models -- **Future** - - Scaling to larger models based on spec decisions - - Kubernetes to allow multiple models running asynchronously - -## **Layers** -LLM Layers -- **MVP** - - LLM inference - - Modular - - Stop function - - Can be passed one layer down - - Option to pass several layers down - - Compression through summarisation - - Send commands downstream and inputs upstream - - Conditional input types based on layer type - - Output as JSON -- **Future** - - Request more function -- ### Aspirational - - High level guidance and alignment -- ### Global Strategy - - Long term strategic roadmaps -- ### Agent Model - - Self awareness, grounding the agent in its physical limitations - - **MVP** - - Memory interface -- ### Executive Function - - Practical project roadmaps considering mission and limitations -- ### Cognitive Control - - Task switcher -- ### Task Prosecution - - The executor of the agent - -## **Busses** -Acts as queuing and communication between the layers -- **MVP** - - Queue system -- **Future** - - Backpressure - - Distributed messages -- ### Southbound - - The control bus, sending down commands and directives -- ### Northbound - - The reporting bus, letting higher layers know of state - -## IO -## **Input** -All the sensory information of the machine -- **MVP** - - System Telemetry - - Hardware & Software Statistics - - Metrics - - Basic World State - - Datetime - - Basic Info From Internet (Should work fully offline though) - - Stdout - - File Access - - User Input (Text) - - Basic Memory - - Task Observation -- **Future** - - API Calls - - Opened Internet Access - - BASHR Loop for research - - Complex Memory - - Short Term - - Summarised for details - - Long Term - - Through Sparse Priming Representations(SPR) - - Factual - - Fact Database - - Episodic - - Vision - - Auditory - - Other Sensors? - -## **Output** -All the physical actions of the machine -- **MVP** - - Function calling - - Code - - Files - - Memory - - AI Models -- **Future** - - API calls - - Motors - - Audio - - Imagery - - 3D models - -## **Logging** -System state and outputs throughout its lifecycle -- **MVP** - - JSON Logger - - Different log levels with verbosity toggles - - Error Logs on each component -- **Future** - - Centralisation - - Dashboards - -## ACE Controls -## **Security Layer** -Acts as the vetting layer, ensuring that all outputs are valid and safe -- **MVP** - - Structure validation - - Safety validation - - Logging of outputs - - User auth - - Access control -- **Future** - - Alignment checks - -## **UI** -The way the user interacts with the ACE -- **MVP** - - User inputs - - Agent status and activations - - Deep dive into outputs -- **Future** - - Statistics \ No newline at end of file diff --git a/.original/documentation/media/architecture.png b/.original/documentation/media/architecture.png deleted file mode 100644 index 3a4863f34b78ce66ebe1e4d5be1396e4b139153c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 92731 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%0A$-BVSymb8%kPb4 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