diff --git a/setup.py b/setup.py index 69f4d663..988fd37b 100644 --- a/setup.py +++ b/setup.py @@ -24,6 +24,7 @@ "wafl.events", "wafl.extractors", "wafl.filter", + "wafl.inference", "wafl.interface", "wafl.knowledge", "wafl.listener", diff --git a/wafl/command_line.py b/wafl/command_line.py index ac6d0a99..620357ea 100644 --- a/wafl/command_line.py +++ b/wafl/command_line.py @@ -10,7 +10,7 @@ download_models, ) from wafl.runners.run_from_audio import run_from_audio -from wafl.runners.selector import run_server +from wafl.runners.routes import run_app def print_help(): @@ -19,6 +19,7 @@ def print_help(): print("> wafl init: Initialize the current folder") print("> wafl run-cli: Run a cli version of the chatbot") print("> wafl run-audio: Run a voice-powered version of the chatbot") + print("> wafl run-server: Run a webserver version of the chatbot") print("> wafl run-tests: Run the tests in testcases.txt") print() @@ -48,7 +49,7 @@ def process_cli(): remove_preprocessed("/") elif command == "run-server": - run_server() + run_app() remove_preprocessed("/") elif command == "run-tests": diff --git a/wafl/events/generated_events.py b/wafl/events/generated_events.py deleted file mode 100644 index dace15c7..00000000 --- a/wafl/events/generated_events.py +++ /dev/null @@ -1,54 +0,0 @@ -import os - -from wafl.events.narrator import Narrator -from wafl.simple_text_processing.questions import is_question -from wafl.extractors.dataclasses import Query -from wafl.inference.backward_inference import BackwardInference -from wafl.exceptions import InterruptTask - -os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python" - - -class GeneratedEvents: - def __init__( - self, - config, - knowledge: "BaseKnowledge", - interface: "BaseInterface", - events: "BaseEventCreator", - code_path=None, - logger=None, - ): - self._inference = BackwardInference( - config, - knowledge, - interface, - Narrator(interface), - code_path, - logger=logger, - generate_rules=False, - ) - self._knowledge = knowledge - self._interface = interface - self._events = events - - async def output(self, text: str): - await self._interface.output(text) - - async def _process_query(self, text: str): - query = Query(text=text, is_question=is_question(text), variable="name") - return await self._inference.compute(query) - - async def process_next(self, activation_word: str = "") -> bool: - events = self._events.get() - for event in events: - try: - answer = await self._process_query(event) - if answer.is_true(): - return True - - except InterruptTask: - await self._interface.output("Task interrupted") - return False - - return False diff --git a/wafl/knowledge/project_knowledge.py b/wafl/knowledge/project_knowledge.py deleted file mode 100644 index b70658c4..00000000 --- a/wafl/knowledge/project_knowledge.py +++ /dev/null @@ -1,183 +0,0 @@ -from typing import Dict, List -from wafl.knowledge.base_knowledge import BaseKnowledge -from wafl.knowledge.single_file_knowledge import SingleFileKnowledge -from wafl.knowledge.utils import get_first_cluster_of_rules -from wafl.parsing.rules_parser import get_dependency_list - - -class ProjectKnowledge(BaseKnowledge): - def __init__(self, config, rules_filename=None, logger=None): - self._config = config - self._logger = logger - self._dependency_dict = {} - self._knowledge_dict = {} - self.rules_filename = None - if rules_filename: - self._knowledge_dict = self._populate_knowledge_structure( - rules_filename, self._dependency_dict - ) - self.rules_filename = rules_filename - - async def add(self, text, knowledge_name=None): - if not knowledge_name: - knowledge_name = self.root_knowledge - - await self._knowledge_dict[knowledge_name].add( - text, knowledge_name=knowledge_name - ) - - async def add_rule(self, text, knowledge_name=None): - if not knowledge_name: - knowledge_name = self.root_knowledge - - await self._knowledge_dict[knowledge_name].add_rule( - text, knowledge_name=knowledge_name - ) - - async def ask_for_facts(self, query, is_from_user=False, knowledge_name=None): - if not knowledge_name: - knowledge_name = self.root_knowledge - - to_return = [] - for name in self._knowledge_dict.keys(): - if name in self._dependency_dict[knowledge_name]: - if self._logger: - self._logger.write(f"Project Knowledge: Asking for facts in {name}") - - to_return.extend( - await self._knowledge_dict[name].ask_for_facts(query, is_from_user) - ) - - return to_return - - async def ask_for_facts_with_threshold( - self, query, is_from_user=False, knowledge_name=None, threshold=None - ): - if not knowledge_name: - knowledge_name = self.root_knowledge - - to_return = [] - for name in self._knowledge_dict.keys(): - if name in self._get_all_dependency_names(knowledge_name): - if self._logger: - self._logger.write(f"Project Knowledge: Asking for facts in {name}") - - to_return.extend( - await self._knowledge_dict[name].ask_for_facts_with_threshold( - query, is_from_user, threshold=threshold - ) - ) - - return sorted(to_return, key=lambda x: -x[1]) - - async def ask_for_rule_backward(self, query, knowledge_name=None, first_n=None): - if not knowledge_name: - knowledge_name = self.root_knowledge - - rules_and_scores_list = [] - - for name in self._knowledge_dict.keys(): - if name in self._get_all_dependency_names(knowledge_name): - if self._logger: - self._logger.write(f"Project Knowledge: Asking for rules in {name}") - - rules_and_scores_list.extend( - await self._knowledge_dict[name]._ask_for_rule_backward_with_scores( - query, knowledge_name=name, first_n=first_n - ) - ) - - rules_and_scores_list = sorted(rules_and_scores_list, key=lambda x: -x[1]) - rules = get_first_cluster_of_rules(rules_and_scores_list)[:first_n] - return rules - - async def has_better_match( - self, query_text: str, knowledge_name: str = None - ) -> bool: - if not knowledge_name: - knowledge_name = self.root_knowledge - - result_list = [] - - for name in self._knowledge_dict.keys(): - if self._logger: - self._logger.write( - f"Project Knowledge: Asking for better match in {name}" - ) - - if name in self._get_all_dependency_names(knowledge_name): - result_list.append( - await self._knowledge_dict[name].has_better_match(query_text) - ) - - return any(result_list) - - def reload_rules(self, rules_filename: str): - self._knowledge_dict = self._populate_knowledge_structure( - rules_filename, self._dependency_dict - ) - - async def reinitialize_all_retrievers(self): - for knowledge in self._knowledge_dict.values(): - await knowledge._initialize_retrievers() - - def get_dependencies_list(self): - return self._get_all_dependency_names(self.root_knowledge) - - @staticmethod - def create_from_string( - config: "Configuration", rules_text: str, knowledge_name: str - ) -> "ProjectKnowledge": - knowledge = ProjectKnowledge(config) - knowledge._knowledge_dict[knowledge_name] = SingleFileKnowledge( - config, rules_text - ) - return knowledge - - def _populate_knowledge_structure( - self, filename: str, dependency_dict: Dict[str, List[str]] - ) -> Dict[str, SingleFileKnowledge]: - knowledge_structure = {} - with open(filename) as file: - text = file.read() - - name = _get_module_name_from_filename(filename) - knowledge_structure[name] = SingleFileKnowledge( - self._config, text, knowledge_name=name, logger=self._logger - ) - dependencies = get_dependency_list(text) - dependency_dict.setdefault(name, []) - dependency_dict[name].extend( - [self.root_knowledge + item for item in dependencies] - ) - for dependency_name in dependencies: - knowledge_structure.update( - self._populate_knowledge_structure( - f".{name}/{dependency_name}/rules.wafl", dependency_dict - ) - ) - - return knowledge_structure - - def _get_all_dependency_names(self, knowledge_name): - all_dependencies = [knowledge_name] - old_len = 0 - while len(all_dependencies) != old_len: - for dependency in all_dependencies: - if dependency in self._dependency_dict: - new_dependency_names = [ - dependency + item for item in self._dependency_dict[dependency] - ] - new_dependency_names = [ - item.replace("//", "/") for item in new_dependency_names - ] - all_dependencies.extend(new_dependency_names) - - old_len = len(all_dependencies) - - return all_dependencies - - -def _get_module_name_from_filename(filename): - filename = filename.replace("//", "/") - return "/" + "/".join(filename.split("/")[1:-1]) diff --git a/wafl/run.py b/wafl/run.py index 0b6555c8..022d8d61 100644 --- a/wafl/run.py +++ b/wafl/run.py @@ -4,7 +4,7 @@ from wafl.exceptions import CloseConversation from wafl.events.conversation_events import ConversationEvents from wafl.interface.command_line_interface import CommandLineInterface -from wafl.knowledge.project_knowledge import ProjectKnowledge +from wafl.knowledge.single_file_knowledge import SingleFileKnowledge from wafl.logger.local_file_logger import LocalFileLogger from wafl.testcases import ConversationTestCases from wafl.variables import get_variables @@ -21,7 +21,9 @@ def print_incipit(): def run_from_command_line(): interface = CommandLineInterface() config = Configuration.load_local_config() - knowledge = ProjectKnowledge(config, "rules.wafl", logger=_logger) + knowledge = SingleFileKnowledge( + config, open(config.get_value("rules")).read(), logger=_logger + ) conversation_events = ConversationEvents( knowledge, interface=interface, @@ -42,7 +44,9 @@ def run_from_command_line(): def run_testcases(): print("Running the testcases in testcases.txt\n") config = Configuration.load_local_config() - knowledge = ProjectKnowledge(config, "rules.wafl") + knowledge = SingleFileKnowledge( + config, open(config.get_value("rules")).read(), logger=_logger + ) test_cases_text = open("testcases.txt").read() testcases = ConversationTestCases( config, diff --git a/wafl/runners/selector.py b/wafl/runners/routes.py similarity index 97% rename from wafl/runners/selector.py rename to wafl/runners/routes.py index 3dc3a198..4a52c7a7 100644 --- a/wafl/runners/selector.py +++ b/wafl/runners/routes.py @@ -7,13 +7,12 @@ from flask import Flask, render_template, redirect, url_for from wafl.config import Configuration from wafl.events.conversation_events import ConversationEvents -from wafl.filter.base_filter import BaseAnswerFilter from wafl.interface.queue_interface import QueueInterface from wafl.knowledge.single_file_knowledge import SingleFileKnowledge from wafl.logger.local_file_logger import LocalFileLogger from wafl.scheduler.conversation_loop import ConversationLoop from wafl.scheduler.scheduler import Scheduler -from wafl.scheduler.web_interface.web_loop import WebLoop +from wafl.scheduler.web_loop import WebLoop _path = os.path.dirname(__file__) _logger = LocalFileLogger() diff --git a/wafl/runners/run_from_audio.py b/wafl/runners/run_from_audio.py index fc11e0f9..80db703e 100644 --- a/wafl/runners/run_from_audio.py +++ b/wafl/runners/run_from_audio.py @@ -1,9 +1,8 @@ from wafl.config import Configuration from wafl.events.conversation_events import ConversationEvents from wafl.events.events_from_module_name import EventsCreatorFromModuleName -from wafl.events.generated_events import GeneratedEvents from wafl.interface.voice_interface import VoiceInterface -from wafl.knowledge.project_knowledge import ProjectKnowledge +from wafl.knowledge.single_file_knowledge import SingleFileKnowledge from wafl.logger.local_file_logger import LocalFileLogger from wafl.scheduler.conversation_loop import ConversationLoop from wafl.scheduler.generated_event_loop import GeneratedEventLoop @@ -14,7 +13,9 @@ def run_from_audio(): config = Configuration.load_local_config() - knowledge = ProjectKnowledge(config, "rules.wafl", logger=_logger) + knowledge = SingleFileKnowledge( + config, open(config.get_value("rules")).read(), logger=_logger + ) interface = VoiceInterface(config) conversation_events = ConversationEvents( knowledge, @@ -29,18 +30,7 @@ def run_from_audio(): _logger, activation_word=config.get_value("waking_up_word"), ) - generated_events = GeneratedEvents( - config, - knowledge, - events=EventsCreatorFromModuleName("events"), - interface=interface, - ) - events_loop = GeneratedEventLoop( - interface, - generated_events, - logger=_logger, - ) - scheduler = Scheduler([conversation_loop, events_loop]) + scheduler = Scheduler([conversation_loop]) scheduler.run() diff --git a/wafl/runners/run_web_interface.py b/wafl/runners/run_web_interface.py index 66077da7..90715601 100644 --- a/wafl/runners/run_web_interface.py +++ b/wafl/runners/run_web_interface.py @@ -1,4 +1,4 @@ -from wafl.runners.selector import run_app +from wafl.runners.routes import run_app if __name__ == "__main__": diff --git a/wafl/scheduler/web_interface/__init__.py b/wafl/scheduler/web_interface/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/wafl/scheduler/web_interface/web_interface_implementation.py b/wafl/scheduler/web_interface_implementation.py similarity index 100% rename from wafl/scheduler/web_interface/web_interface_implementation.py rename to wafl/scheduler/web_interface_implementation.py diff --git a/wafl/scheduler/web_interface/web_loop.py b/wafl/scheduler/web_loop.py similarity index 98% rename from wafl/scheduler/web_interface/web_loop.py rename to wafl/scheduler/web_loop.py index a42c3667..bb35e254 100644 --- a/wafl/scheduler/web_interface/web_loop.py +++ b/wafl/scheduler/web_loop.py @@ -4,7 +4,7 @@ from flask import render_template, request, jsonify from wafl.interface.queue_interface import QueueInterface from wafl.logger.history_logger import HistoryLogger -from wafl.scheduler.web_interface.web_interface_implementation import ( +from wafl.scheduler.web_interface_implementation import ( get_html_from_dialogue_item, )