From 9240397c1cc3fac97cd873bd400cf3a1868c7a35 Mon Sep 17 00:00:00 2001 From: Hugo Saporetti Junior Date: Mon, 9 Sep 2024 14:17:48 -0300 Subject: [PATCH] Adjust reply verbosities and speakabilities --- src/main/askai/core/askai.py | 42 ++++++++++--------- src/main/askai/core/askai_cli.py | 27 ++++++------ src/main/askai/core/askai_configs.py | 13 +++--- src/main/askai/core/askai_events.py | 8 ++-- src/main/askai/core/askai_settings.py | 15 ++++--- src/main/askai/core/component/audio_player.py | 15 ++++--- src/main/askai/core/enums/verbosity.py | 3 +- .../features/router/procs/task_splitter.py | 4 +- .../core/features/router/task_toolkit.py | 25 +++++------ .../core/features/router/tools/analysis.py | 2 +- .../core/features/router/tools/terminal.py | 2 +- .../core/features/router/tools/vision.py | 4 +- src/main/askai/core/model/ai_reply.py | 21 +++++++++- src/main/askai/tui/askai_app.py | 33 +++++++-------- 14 files changed, 116 insertions(+), 98 deletions(-) diff --git a/src/main/askai/core/askai.py b/src/main/askai/core/askai.py index 303be050..f48bc150 100644 --- a/src/main/askai/core/askai.py +++ b/src/main/askai/core/askai.py @@ -12,22 +12,6 @@ Copyright (c) 2024, HomeSetup """ -import logging as log -import os -import re -import sys -from enum import Enum -from pathlib import Path -from typing import List, Optional, TypeAlias - -from click import UsageError -from hspylib.core.enums.charset import Charset -from hspylib.core.tools.commons import file_is_not_empty, is_debugging -from hspylib.core.zoned_datetime import DATE_FORMAT, now, TIME_FORMAT -from hspylib.modules.application.exit_status import ExitStatus -from hspylib.modules.eventbus.event import Event -from openai import RateLimitError - from askai.__classpath__ import classpath from askai.core.askai_configs import configs from askai.core.askai_events import events @@ -42,9 +26,29 @@ from askai.core.support.chat_context import ChatContext from askai.core.support.shared_instances import shared from askai.core.support.utilities import read_stdin -from askai.exception.exceptions import (ImpossibleQuery, InaccurateResponse, IntelligibleAudioError, - MaxInteractionsReached, TerminatingQuery) +from askai.exception.exceptions import ( + ImpossibleQuery, + InaccurateResponse, + IntelligibleAudioError, + MaxInteractionsReached, + TerminatingQuery, +) from askai.tui.app_icons import AppIcons +from click import UsageError +from enum import Enum +from hspylib.core.enums.charset import Charset +from hspylib.core.tools.commons import file_is_not_empty, is_debugging +from hspylib.core.zoned_datetime import DATE_FORMAT, now, TIME_FORMAT +from hspylib.modules.application.exit_status import ExitStatus +from hspylib.modules.eventbus.event import Event +from openai import RateLimitError +from pathlib import Path +from typing import List, Optional, TypeAlias + +import logging as log +import os +import re +import sys QueryString: TypeAlias = str | List[str] | None @@ -157,7 +161,7 @@ def ask_and_reply(self, question: str) -> tuple[bool, Optional[str]]: ask_commander(args, standalone_mode=False) elif not (output := cache.read_reply(question)): log.debug('Response not found for "%s" in cache. Querying from %s.', question, self.engine.nickname()) - events.reply.emit(reply=AIReply.debug(msg.wait())) + events.reply.emit(reply=AIReply.detailed(msg.wait())) output = processor.process(question, context=read_stdin(), query_prompt=self._query_prompt) events.reply.emit(reply=AIReply.info(output or msg.no_output("processor"))) else: diff --git a/src/main/askai/core/askai_cli.py b/src/main/askai/core/askai_cli.py index 65162353..67d1ffa7 100644 --- a/src/main/askai/core/askai_cli.py +++ b/src/main/askai/core/askai_cli.py @@ -12,20 +12,6 @@ Copyright (c) 2024, HomeSetup """ -import logging as log -import os -from pathlib import Path -from threading import Thread -from typing import List, TypeAlias - -import nltk -import pause -from clitt.core.term.cursor import cursor -from clitt.core.term.screen import screen -from clitt.core.tui.line_input.keyboard_input import KeyboardInput -from hspylib.modules.eventbus.event import Event -from rich.progress import Progress - from askai.core.askai import AskAi from askai.core.askai_configs import configs from askai.core.askai_events import * @@ -39,6 +25,19 @@ from askai.core.support.shared_instances import shared from askai.core.support.text_formatter import text_formatter from askai.core.support.utilities import display_text +from clitt.core.term.cursor import cursor +from clitt.core.term.screen import screen +from clitt.core.tui.line_input.keyboard_input import KeyboardInput +from hspylib.modules.eventbus.event import Event +from pathlib import Path +from rich.progress import Progress +from threading import Thread +from typing import List, TypeAlias, Optional + +import logging as log +import nltk +import os +import pause QueryString: TypeAlias = str | List[str] | None diff --git a/src/main/askai/core/askai_configs.py b/src/main/askai/core/askai_configs.py index b44f3443..2bd0d8bf 100644 --- a/src/main/askai/core/askai_configs.py +++ b/src/main/askai/core/askai_configs.py @@ -12,17 +12,16 @@ Copyright (c) 2024, HomeSetup """ -import locale -import os -from shutil import which - -from hspylib.core.enums.charset import Charset -from hspylib.core.metaclass.singleton import Singleton - from askai.__classpath__ import classpath from askai.core.askai_settings import settings from askai.core.enums.verbosity import Verbosity from askai.language.language import Language +from hspylib.core.enums.charset import Charset +from hspylib.core.metaclass.singleton import Singleton +from shutil import which + +import locale +import os class AskAiConfigs(metaclass=Singleton): diff --git a/src/main/askai/core/askai_events.py b/src/main/askai/core/askai_events.py index efb5d7ba..1d2b128d 100644 --- a/src/main/askai/core/askai_events.py +++ b/src/main/askai/core/askai_events.py @@ -14,9 +14,9 @@ """ from hspylib.core.enums.enumeration import Enumeration +from hspylib.core.exception.exceptions import InvalidArgumentError from hspylib.core.namespace import Namespace from hspylib.modules.eventbus.fluid import FluidEvent, FluidEventBus -from typing import Optional ASKAI_BUS_NAME: str = "askai-reply-bus" @@ -43,12 +43,14 @@ class AskAiEvents(Enumeration): # fmt: on @staticmethod - def bus(bus_name: str) -> Optional[FluidEventBus]: + def bus(bus_name: str) -> FluidEventBus: """Return an event bus instance for the given name. :param bus_name: The name of the event bus to retrieve. :return: An instance of FluidEventBus if found; otherwise, None. """ - return next((b.bus for b in AskAiEvents.values() if b.name == bus_name), None) + if not (ret_bus := next((b.bus for b in AskAiEvents.values() if b.name == bus_name), None)): + raise InvalidArgumentError(f"bus '{bus_name}' does not exist!") + return ret_bus def __init__(self, bus: FluidEventBus): self._bus = bus diff --git a/src/main/askai/core/askai_settings.py b/src/main/askai/core/askai_settings.py index 7db16496..251f749f 100644 --- a/src/main/askai/core/askai_settings.py +++ b/src/main/askai/core/askai_settings.py @@ -12,22 +12,21 @@ Copyright (c) 2024, HomeSetup """ -import logging as log -import os -import re +from askai.__classpath__ import classpath from contextlib import redirect_stdout -from io import StringIO -from pathlib import Path -from typing import Any, Optional - from hspylib.core.metaclass.singleton import Singleton from hspylib.core.tools.commons import console_out, to_bool +from io import StringIO +from pathlib import Path from rich.table import Table from setman.settings.settings import Settings from setman.settings.settings_config import SettingsConfig from setman.settings.settings_entry import SettingsEntry +from typing import Any, Optional -from askai.__classpath__ import classpath +import logging as log +import os +import re # AskAI config directory. ASKAI_DIR: Path = Path(f'{os.getenv("ASKAI_DIR", os.getenv("HHS_DIR", str(Path.home())))}/askai') diff --git a/src/main/askai/core/component/audio_player.py b/src/main/askai/core/component/audio_player.py index 92ed9364..c81b5f60 100644 --- a/src/main/askai/core/component/audio_player.py +++ b/src/main/askai/core/component/audio_player.py @@ -12,21 +12,20 @@ Copyright (c) 2024, HomeSetup """ -import logging as log -import time -from functools import lru_cache -from pathlib import Path -from shutil import which -from typing import Literal - +from askai.__classpath__ import classpath from clitt.core.term.terminal import Terminal +from functools import lru_cache from hspylib.core.metaclass.singleton import Singleton from hspylib.core.preconditions import check_argument from hspylib.core.tools.commons import file_is_not_empty from hspylib.core.tools.text_tools import ensure_endswith from hspylib.modules.application.exit_status import ExitStatus +from pathlib import Path +from shutil import which +from typing import Literal -from askai.__classpath__ import classpath +import logging as log +import time class AudioPlayer(metaclass=Singleton): diff --git a/src/main/askai/core/enums/verbosity.py b/src/main/askai/core/enums/verbosity.py index 7a1b93f5..6505dcd9 100644 --- a/src/main/askai/core/enums/verbosity.py +++ b/src/main/askai/core/enums/verbosity.py @@ -26,10 +26,9 @@ def val(self) -> int: """ return int(self.value) - def match(self, level: 'Verbosity') -> bool: + def match(self, level: "Verbosity") -> bool: """Checks if the current verbosity level is less than or equal to the given level. :param level: The verbosity level to compare against. :return: True if the current level is less than or equal to the given level, otherwise False. """ return self.val <= level.val - diff --git a/src/main/askai/core/features/router/procs/task_splitter.py b/src/main/askai/core/features/router/procs/task_splitter.py index 5ce56e24..1b16d0a6 100644 --- a/src/main/askai/core/features/router/procs/task_splitter.py +++ b/src/main/askai/core/features/router/procs/task_splitter.py @@ -74,7 +74,7 @@ def wrap_answer( """ output: str = answer model: RoutingModel = RoutingModel.of_model(model_result.mid) - events.reply.emit(reply=AIReply.debug(msg.model_select(model))) + events.reply.emit(reply=AIReply.full(msg.model_select(model))) args = {"user": shared.username, "idiom": shared.idiom, "context": answer, "question": query} prompt_args: list[str] = [k for k in args.keys()] @@ -159,7 +159,7 @@ def _process_wrapper() -> Optional[str]: try: if task_list := plan.tasks: if plan.speak: - events.reply.emit(reply=AIReply.info(plan.speak)) + events.reply.emit(reply=AIReply.detailed(plan.speak)) for idx, action in enumerate(task_list, start=1): path_str: str | None = ( "Path: " + action.path diff --git a/src/main/askai/core/features/router/task_toolkit.py b/src/main/askai/core/features/router/task_toolkit.py index 84af52cb..be1b472f 100644 --- a/src/main/askai/core/features/router/task_toolkit.py +++ b/src/main/askai/core/features/router/task_toolkit.py @@ -12,6 +12,18 @@ Copyright (c) 2024, HomeSetup """ +import inspect +import logging as log +from functools import lru_cache +from textwrap import dedent +from typing import Callable, Optional + +from clitt.core.tui.line_input.line_input import line_input +from hspylib.core.metaclass.classpath import AnyPath +from hspylib.core.metaclass.singleton import Singleton +from hspylib.core.tools.text_tools import ensure_endswith, ensure_startswith +from langchain_core.tools import BaseTool, StructuredTool + from askai.core.askai_messages import msg from askai.core.features.router.tools.analysis import query_output from askai.core.features.router.tools.browser import browse @@ -19,20 +31,9 @@ from askai.core.features.router.tools.generation import generate_content, save_content from askai.core.features.router.tools.summarization import summarize from askai.core.features.router.tools.terminal import execute_command, list_contents, open_command -from askai.core.features.router.tools.vision import image_captioner, offline_captioner, parse_caption +from askai.core.features.router.tools.vision import image_captioner, parse_caption from askai.core.features.router.tools.webcam import webcam_capturer, webcam_identifier from askai.exception.exceptions import TerminatingQuery -from clitt.core.tui.line_input.line_input import line_input -from functools import lru_cache -from hspylib.core.metaclass.classpath import AnyPath -from hspylib.core.metaclass.singleton import Singleton -from hspylib.core.tools.text_tools import ensure_endswith, ensure_startswith -from langchain_core.tools import BaseTool, StructuredTool -from textwrap import dedent -from typing import Callable, Optional - -import inspect -import logging as log class AgentToolkit(metaclass=Singleton): diff --git a/src/main/askai/core/features/router/tools/analysis.py b/src/main/askai/core/features/router/tools/analysis.py index 7aefdee4..e68cecbb 100644 --- a/src/main/askai/core/features/router/tools/analysis.py +++ b/src/main/askai/core/features/router/tools/analysis.py @@ -47,6 +47,6 @@ def query_output(query: str, context: str = None) -> str: log.info("Analysis::[QUERY] '%s' context=%s", query, context) if response := runnable.invoke({"input": query}, config={"configurable": {"session_id": "HISTORY"}}): output = response.content - events.reply.emit(reply=AIReply.debug(msg.analysis(output))) + events.reply.emit(reply=AIReply.detailed(msg.analysis(output))) return TextFormatter.ensure_ln(output or "Sorry, I don't know.") diff --git a/src/main/askai/core/features/router/tools/terminal.py b/src/main/askai/core/features/router/tools/terminal.py index 253235b8..723bf1de 100644 --- a/src/main/askai/core/features/router/tools/terminal.py +++ b/src/main/askai/core/features/router/tools/terminal.py @@ -115,7 +115,7 @@ def _execute_bash(command_line: str) -> Tuple[bool, str]: if (command := command_line.split(" ")[0].strip()) and which(command): command = expandvars(command_line.replace("~/", f"{os.getenv('HOME')}/").strip()) log.info("Executing command `%s'", command) - events.reply.emit(reply=AIReply.debug(msg.executing(command_line))) + events.reply.emit(reply=AIReply.full(msg.executing(command_line))) output, exit_code = Terminal.INSTANCE.shell_exec(command, shell=True) if exit_code == ExitStatus.SUCCESS: log.info("Command succeeded: \n|-CODE=%s \n|-PATH: %s \n|-CMD: %s ", exit_code, os.getcwd(), command) diff --git a/src/main/askai/core/features/router/tools/vision.py b/src/main/askai/core/features/router/tools/vision.py index e42c1bf6..dc34dda4 100644 --- a/src/main/askai/core/features/router/tools/vision.py +++ b/src/main/askai/core/features/router/tools/vision.py @@ -47,7 +47,7 @@ def offline_captioner(path_name: AnyPath) -> str: posix_path: PathObject = x_ref_path if x_ref_path.exists else posix_path if posix_path.exists: - events.reply.emit(reply=AIReply.debug(msg.describe_image(posix_path))) + events.reply.emit(reply=AIReply.full(msg.describe_image(posix_path))) # Use GPU if it's available device = "cuda" if torch.cuda.is_available() else "cpu" image = Image.open(str(posix_path)).convert("RGB") @@ -83,7 +83,7 @@ def image_captioner(path_name: AnyPath, load_dir: AnyPath | None = None) -> str: posix_path: PathObject = x_ref_path if x_ref_path.exists else posix_path if posix_path.exists: - events.reply.emit(reply=AIReply.debug(msg.describe_image(posix_path))) + events.reply.emit(reply=AIReply.full(msg.describe_image(posix_path))) vision: AIVision = shared.engine.vision() image_caption = vision.caption(posix_path.filename, load_dir or posix_path.abs_dir or PICTURE_DIR) diff --git a/src/main/askai/core/model/ai_reply.py b/src/main/askai/core/model/ai_reply.py index 5275e418..939863df 100644 --- a/src/main/askai/core/model/ai_reply.py +++ b/src/main/askai/core/model/ai_reply.py @@ -13,11 +13,10 @@ Copyright (c) 2024, HomeSetup """ +from askai.core.enums.verbosity import Verbosity from dataclasses import dataclass from typing import AnyStr -from askai.core.enums.verbosity import Verbosity - @dataclass(frozen=True) class AIReply: @@ -48,6 +47,24 @@ def info(message: AnyStr, verbosity: Verbosity = Verbosity.NORMAL, speakable: bo """ return AIReply(str(message), True, False, verbosity, speakable) + @staticmethod + def detailed(message: AnyStr, speakable: bool = False) -> "AIReply": + """Creates a detailed verbosity reply. + :param message: The reply message. + :param speakable: Indicates whether the reply is speakable. + :return: An AIReply instance with detailed settings. + """ + return AIReply(str(message), True, False, Verbosity.DETAILED, speakable) + + @staticmethod + def full(message: AnyStr, speakable: bool = False) -> "AIReply": + """Creates a full verbose reply. + :param message: The reply message. + :param speakable: Indicates whether the reply is speakable. + :return: An AIReply instance with full settings. + """ + return AIReply(str(message), True, False, Verbosity.FULL, speakable) + @staticmethod def error(message: AnyStr) -> "AIReply": """Creates an error reply. diff --git a/src/main/askai/tui/askai_app.py b/src/main/askai/tui/askai_app.py index ff99d7c5..77aa88ef 100644 --- a/src/main/askai/tui/askai_app.py +++ b/src/main/askai/tui/askai_app.py @@ -12,23 +12,6 @@ Copyright (c) 2024, HomeSetup """ -import logging as log -import os -from pathlib import Path - -import nltk -from hspylib.core.enums.charset import Charset -from hspylib.core.tools.commons import file_is_not_empty -from hspylib.core.tools.text_tools import ensure_endswith -from hspylib.core.zoned_datetime import DATE_FORMAT, now, TIME_FORMAT -from hspylib.modules.application.version import Version -from hspylib.modules.cli.vt100.vt_color import VtColor -from hspylib.modules.eventbus.event import Event -from textual import on, work -from textual.app import App, ComposeResult -from textual.containers import ScrollableContainer -from textual.widgets import Footer, Input, MarkdownViewer - from askai.__classpath__ import classpath from askai.core.askai import AskAi from askai.core.askai_configs import configs @@ -49,6 +32,22 @@ from askai.tui.app_icons import AppIcons from askai.tui.app_suggester import InputSuggester from askai.tui.app_widgets import AppHelp, AppInfo, AppSettings, Splash +from hspylib.core.enums.charset import Charset +from hspylib.core.tools.commons import file_is_not_empty +from hspylib.core.tools.text_tools import ensure_endswith +from hspylib.core.zoned_datetime import DATE_FORMAT, now, TIME_FORMAT +from hspylib.modules.application.version import Version +from hspylib.modules.cli.vt100.vt_color import VtColor +from hspylib.modules.eventbus.event import Event +from pathlib import Path +from textual import on, work +from textual.app import App, ComposeResult +from textual.containers import ScrollableContainer +from textual.widgets import Footer, Input, MarkdownViewer + +import logging as log +import nltk +import os SOURCE_DIR: Path = classpath.source_path()