From 8778b7a1aeeda50ca41e127decb28a3068b15983 Mon Sep 17 00:00:00 2001 From: Sean Chatman <136349053+seanchatmangpt@users.noreply.github.com> Date: Wed, 27 Mar 2024 00:49:11 -0700 Subject: [PATCH] Conversational RDDDY --- poetry.lock | 35 +- pyproject.toml | 5 +- src/dspygen/experiments/convo_ddd/__init__.py | 13 + .../conversation_aggregate.py | 76 ++++ .../generate_response_command.py | 5 + .../handle_user_query_command.py | 6 + .../recognize_entity_command.py | 6 + .../recognize_intent_command.py | 6 + .../transition_state_command.py | 6 + .../update_context_command.py | 6 + .../abstract_event/context_updated_event.py | 6 + .../abstract_event/database_queried_event.py | 6 + .../abstract_event/entity_recognized_event.py | 6 + .../external_api_response_event.py | 6 + .../external_service_called_event.py | 6 + .../abstract_event/intent_recognized_event.py | 6 + .../response_generated_event.py | 6 + .../abstract_event/state_transition_event.py | 6 + .../abstract_event/system_interrupt_event.py | 6 + .../system_message_displayed_event.py | 6 + .../user_input_received_event.py | 6 + .../user_message_submitted_event.py | 6 + .../user_query_handled_event.py | 6 + .../context_management_policy.py | 6 + .../entity_recognition_policy.py | 6 + .../abstract_policy/intent_handling_policy.py | 6 + .../response_generation_policy.py | 6 + .../state_transition_policy.py | 6 + .../get_current_context_query.py | 6 + .../abstract_query/get_current_state_query.py | 6 + .../get_entity_details_query.py | 6 + .../get_intent_details_query.py | 6 + .../context_snapshot_read_model.py | 6 + .../abstract_read_model/entity_read_model.py | 6 + .../abstract_read_model/intent_read_model.py | 6 + .../response_read_model.py | 6 + .../abstract_read_model/state_read_model.py | 6 + .../{ddd => convo_ddd}/abstract_renderer.py | 63 ++- .../conversation_handling_saga.py | 6 + .../generate_dialogue_response_task.py | 6 + .../manage_state_transitions_task.py | 6 + .../abstract_task/process_user_input_task.py | 6 + .../update_conversation_context_task.py | 6 + .../context_value_object.py | 6 + .../entity_value_object.py | 6 + .../intent_value_object.py | 6 + .../response_value_object.py | 6 + .../state_value_object.py | 6 + .../convo_ddd/abstract_view/context_view.py | 6 + .../convo_ddd/abstract_view/entity_view.py | 6 + .../convo_ddd/abstract_view/intent_view.py | 6 + .../convo_ddd/abstract_view/response_view.py | 6 + .../convo_ddd/abstract_view/state_view.py | 6 + .../context_update_exception.py | 6 + .../entity_recognition_exception.py | 6 + .../intent_not_found_exception.py | 6 + .../invalid_state_exception.py | 6 + .../response_generation_exception.py | 6 + src/dspygen/experiments/pomo_bud/__init__.py | 0 .../experiments/pomo_bud/pomo_bud_dsl.yaml | 34 ++ .../experiments/pomo_bud/pomo_bud_models.py | 112 +++++ src/dspygen/experiments/rfc5545/__init__.py | 0 .../experiments/rfc5545/calendar_cmd.py | 377 +++++++++++++++++ src/dspygen/experiments/rfc5545/ical_crud.py | 89 ++++ .../experiments/rfc5545/ical_data_ret.py | 13 + .../experiments/rfc5545/ical_db_session.py | 23 + .../experiments/rfc5545/ical_models.py | 395 ++++++++++++++++++ .../experiments/rfc5545/ical_workbench.py | 29 ++ .../experiments/rfc5545/journal_cmd.py | 112 +++++ src/dspygen/rdddy/abstract_message.py | 2 +- src/dspygen/rdddy/actor_system.py | 6 +- .../event_storm_domain_specification_model.py | 28 +- src/dspygen/rm/data_retriever.py | 5 +- src/dspygen/utils/MyData.yaml | 1 + src/dspygen/utils/crud_tools.py | 72 ++++ src/dspygen/utils/date_tools.py | 53 +++ src/dspygen/utils/file_tools.py | 3 + src/dspygen/utils/yaml_tools.py | 97 ++++- src/dspygen/workflow/workflow_models.py | 6 - tests/actor/test_abstract_messages.py | 71 ++++ tests/actor/test_actor_system.py | 71 +++- tests/convo/test_conversation_aggregate.py | 56 +++ tests/convo/test_engine.py | 135 ++++++ tests/convo/test_intent.py | 0 tests/utils/test_yaml_mixin.py | 102 +++++ 85 files changed, 2324 insertions(+), 77 deletions(-) create mode 100644 src/dspygen/experiments/convo_ddd/__init__.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_aggregate/conversation_aggregate.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_command/generate_response_command.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_command/handle_user_query_command.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_command/recognize_entity_command.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_command/recognize_intent_command.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_command/transition_state_command.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_command/update_context_command.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_event/context_updated_event.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_event/database_queried_event.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_event/entity_recognized_event.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_event/external_api_response_event.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_event/external_service_called_event.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_event/intent_recognized_event.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_event/response_generated_event.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_event/state_transition_event.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_event/system_interrupt_event.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_event/system_message_displayed_event.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_event/user_input_received_event.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_event/user_message_submitted_event.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_event/user_query_handled_event.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_policy/context_management_policy.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_policy/entity_recognition_policy.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_policy/intent_handling_policy.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_policy/response_generation_policy.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_policy/state_transition_policy.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_query/get_current_context_query.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_query/get_current_state_query.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_query/get_entity_details_query.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_query/get_intent_details_query.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_read_model/context_snapshot_read_model.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_read_model/entity_read_model.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_read_model/intent_read_model.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_read_model/response_read_model.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_read_model/state_read_model.py rename src/dspygen/experiments/{ddd => convo_ddd}/abstract_renderer.py (56%) create mode 100644 src/dspygen/experiments/convo_ddd/abstract_saga/conversation_handling_saga.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_task/generate_dialogue_response_task.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_task/manage_state_transitions_task.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_task/process_user_input_task.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_task/update_conversation_context_task.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_value_object/context_value_object.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_value_object/entity_value_object.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_value_object/intent_value_object.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_value_object/response_value_object.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_value_object/state_value_object.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_view/context_view.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_view/entity_view.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_view/intent_view.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_view/response_view.py create mode 100644 src/dspygen/experiments/convo_ddd/abstract_view/state_view.py create mode 100644 src/dspygen/experiments/convo_ddd/domain_exception/context_update_exception.py create mode 100644 src/dspygen/experiments/convo_ddd/domain_exception/entity_recognition_exception.py create mode 100644 src/dspygen/experiments/convo_ddd/domain_exception/intent_not_found_exception.py create mode 100644 src/dspygen/experiments/convo_ddd/domain_exception/invalid_state_exception.py create mode 100644 src/dspygen/experiments/convo_ddd/domain_exception/response_generation_exception.py create mode 100644 src/dspygen/experiments/pomo_bud/__init__.py create mode 100644 src/dspygen/experiments/pomo_bud/pomo_bud_dsl.yaml create mode 100644 src/dspygen/experiments/pomo_bud/pomo_bud_models.py create mode 100644 src/dspygen/experiments/rfc5545/__init__.py create mode 100644 src/dspygen/experiments/rfc5545/calendar_cmd.py create mode 100644 src/dspygen/experiments/rfc5545/ical_crud.py create mode 100644 src/dspygen/experiments/rfc5545/ical_data_ret.py create mode 100644 src/dspygen/experiments/rfc5545/ical_db_session.py create mode 100644 src/dspygen/experiments/rfc5545/ical_models.py create mode 100644 src/dspygen/experiments/rfc5545/ical_workbench.py create mode 100644 src/dspygen/experiments/rfc5545/journal_cmd.py create mode 100644 src/dspygen/utils/MyData.yaml create mode 100644 src/dspygen/utils/crud_tools.py create mode 100644 src/dspygen/utils/date_tools.py create mode 100644 tests/actor/test_abstract_messages.py create mode 100644 tests/convo/test_conversation_aggregate.py create mode 100644 tests/convo/test_engine.py create mode 100644 tests/convo/test_intent.py create mode 100644 tests/utils/test_yaml_mixin.py diff --git a/poetry.lock b/poetry.lock index 308c2d6..4e1d663 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1651,6 +1651,24 @@ files = [ [package.dependencies] pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""} +[[package]] +name = "icontract" +version = "2.6.6" +description = "Provide design-by-contract with informative violation messages." +optional = false +python-versions = "*" +files = [ + {file = "icontract-2.6.6-py3-none-any.whl", hash = "sha256:1ba4e88f909d3a4b97a565e1ea1199e5b050aa4bdad190c69086bfaed9680cc2"}, + {file = "icontract-2.6.6.tar.gz", hash = "sha256:c1fd55c7709ef18a2ee64313fe863be2668b53060828fcca3525051160c92691"}, +] + +[package.dependencies] +asttokens = ">=2,<3" +typing-extensions = "*" + +[package.extras] +dev = ["astor (==0.8.1)", "asyncstdlib (==3.9.1)", "black (==23.9.1)", "coverage (>=4.5.1,<5)", "deal (>=4,<5)", "docutils (>=0.14,<1)", "dpcontracts (==0.6.0)", "mypy (==1.5.1)", "numpy (>=1,<2)", "py-cpuinfo (>=5.0.0,<6)", "pydocstyle (>=6.3.0,<7)", "pygments (>=2.2.0,<3)", "pylint (==2.17.5)", "tabulate (>=0.8.7,<1)", "tox (>=3.0.0)", "typeguard (>=2,<5)"] + [[package]] name = "identify" version = "2.5.35" @@ -4966,6 +4984,21 @@ postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] pymysql = ["pymysql"] sqlcipher = ["sqlcipher3_binary"] +[[package]] +name = "sqlmodel" +version = "0.0.16" +description = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness." +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "sqlmodel-0.0.16-py3-none-any.whl", hash = "sha256:b972f5d319580d6c37ecc417881f6ec4d1ad3ed3583d0ac0ed43234a28bf605a"}, + {file = "sqlmodel-0.0.16.tar.gz", hash = "sha256:966656f18a8e9a2d159eb215b07fb0cf5222acfae3362707ca611848a8a06bd1"}, +] + +[package.dependencies] +pydantic = ">=1.10.13,<3.0.0" +SQLAlchemy = ">=2.0.0,<2.1.0" + [[package]] name = "st-pages" version = "0.4.5" @@ -6179,4 +6212,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.10,<4.0" -content-hash = "c560a8d42a5d47461e34145361616c4e150f49921237803fa86143be68edda31" +content-hash = "31a0f8a8f3f9b44c78e765410eeaedafca4cca82ee238dc95f5f89c0bce9e79f" diff --git a/pyproject.toml b/pyproject.toml index b0153e9..d94f951 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] # https://python-poetry.org/docs/pyproject/ name = "dspygen" -version = "2024.3.25" +version = "2024.3.27" description = "A Ruby on Rails style framework for the DSPy (Demonstrate, Search, Predict) project for Language Models like GPT, BERT, and LLama." authors = ["Sean Chatman "] readme = "README.md" @@ -50,6 +50,9 @@ sentify = "^0.7.4" ebooklib = "^0.18" python-docx = "^1.1.0" pypdf = "^4.1.0" +sqlmodel = "^0.0.16" +icontract = "^2.6.6" +tzlocal = "^5.2" [tool.poetry.group.test.dependencies] # https://python-poetry.org/docs/master/managing-dependencies/ coverage = { extras = ["toml"], version = ">=7.2.5" } diff --git a/src/dspygen/experiments/convo_ddd/__init__.py b/src/dspygen/experiments/convo_ddd/__init__.py new file mode 100644 index 0000000..1d27c98 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/__init__.py @@ -0,0 +1,13 @@ +from dspygen.experiments.convo_ddd.abstract_command.generate_response_command import GenerateResponseCommand +from dspygen.experiments.convo_ddd.abstract_command.handle_user_query_command import HandleUserQueryCommand +from dspygen.experiments.convo_ddd.abstract_command.recognize_entity_command import RecognizeEntityCommand +from dspygen.experiments.convo_ddd.abstract_command.recognize_intent_command import RecognizeIntentCommand +from dspygen.experiments.convo_ddd.abstract_command.transition_state_command import TransitionStateCommand +from dspygen.experiments.convo_ddd.abstract_command.update_context_command import UpdateContextCommand + +from dspygen.experiments.convo_ddd.abstract_event.context_updated_event import ContextUpdatedEvent +from dspygen.experiments.convo_ddd.abstract_event.entity_recognized_event import EntityRecognizedEvent +from dspygen.experiments.convo_ddd.abstract_event.intent_recognized_event import IntentRecognizedEvent +from dspygen.experiments.convo_ddd.abstract_event.response_generated_event import ResponseGeneratedEvent +from dspygen.experiments.convo_ddd.abstract_event.state_transition_event import StateTransitionEvent +from dspygen.experiments.convo_ddd.abstract_event.user_input_received_event import UserInputReceivedEvent \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_aggregate/conversation_aggregate.py b/src/dspygen/experiments/convo_ddd/abstract_aggregate/conversation_aggregate.py new file mode 100644 index 0000000..03c7834 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_aggregate/conversation_aggregate.py @@ -0,0 +1,76 @@ +from munch import Munch + +from dspygen.experiments.convo_ddd import (RecognizeIntentCommand, + RecognizeEntityCommand, + UpdateContextCommand, + TransitionStateCommand, +GenerateResponseCommand, +HandleUserQueryCommand, + ContextUpdatedEvent, + EntityRecognizedEvent, + IntentRecognizedEvent, + ResponseGeneratedEvent, + StateTransitionEvent, + UserInputReceivedEvent) + +from dspygen.rdddy.abstract_aggregate import AbstractAggregate +from dspygen.modules.gen_pydantic_instance import instance +from dspygen.rdddy.actor_system import ActorSystem + + +class ConversationAggregate(AbstractAggregate): + def __init__(self, conversation_id: str, actor_system: "ActorSystem"): + super().__init__(actor_system) + self.conversation_id = conversation_id + self.context = Munch({"intent": None, "entity": None}) + self.state = "initial" + + async def handle_recognize_intent_command(self, command: RecognizeIntentCommand): + # Simulate intent recognition logic + intent = instance(IntentRecognizedEvent, command.user_input) # Assume instance() magically does the job + self.apply_event(intent) + + async def handle_recognize_entity_command(self, command: RecognizeEntityCommand): + # Simulate entity recognition logic + entity = instance(EntityRecognizedEvent, command.user_input) # Assume instance() works similarly + self.apply_event(entity) + + async def handle_update_context_command(self, command: UpdateContextCommand): + if command.replace: + self.context = command.updates + else: + self.context.update(command.updates) + self.apply_event(ContextUpdatedEvent(content="Context updated")) + + async def handle_transition_state_command(self, command: TransitionStateCommand): + self.state = command.new_state + self.apply_event(StateTransitionEvent(content=f"Transitioned to {self.state} state")) + + async def handle_generate_response_command(self, command: GenerateResponseCommand): + # Simplified response generation logic based on intent + if self.context.get("intent") == "greeting": + response = "Hello! How can I assist you today?" + elif self.context.get("intent") == "inquiry": + response = "What information are you seeking?" + else: + response = "I'm sorry, could you rephrase that?" + + self.apply_event(ResponseGeneratedEvent(content=response)) + + async def handle_user_query_command(self, command: HandleUserQueryCommand): + # Process user query and potentially invoke other commands based on the query + self.apply_event(UserInputReceivedEvent(content=command.query)) + # Example: Recognize intent from the user query + await self.handle_recognize_intent_command(RecognizeIntentCommand(user_input=command.query)) + # Generate a response based on recognized intent + await self.handle_generate_response_command(GenerateResponseCommand(intent=self.context.get("intent", ""))) + + def apply_event(self, event): + if isinstance(event, IntentRecognizedEvent): + self.context['intent'] = event.intent_name + elif isinstance(event, EntityRecognizedEvent): + self.context['entity'] = event.entity_name + # Include handling for other event types as needed + else: + # It's helpful to log or handle the case where an event is not recognized + print(f"Unhandled event type: {type(event)}") diff --git a/src/dspygen/experiments/convo_ddd/abstract_command/generate_response_command.py b/src/dspygen/experiments/convo_ddd/abstract_command/generate_response_command.py new file mode 100644 index 0000000..168f4b8 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_command/generate_response_command.py @@ -0,0 +1,5 @@ +from dspygen.rdddy.abstract_command import AbstractCommand + + +class GenerateResponseCommand(AbstractCommand): + """Generated class for GenerateResponseCommand, inheriting from AbstractCommand.""" diff --git a/src/dspygen/experiments/convo_ddd/abstract_command/handle_user_query_command.py b/src/dspygen/experiments/convo_ddd/abstract_command/handle_user_query_command.py new file mode 100644 index 0000000..7c65a08 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_command/handle_user_query_command.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_command import AbstractCommand + + +class HandleUserQueryCommand(AbstractCommand): + """Generated class for HandleUserQueryCommand, inheriting from AbstractCommand.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_command/recognize_entity_command.py b/src/dspygen/experiments/convo_ddd/abstract_command/recognize_entity_command.py new file mode 100644 index 0000000..d9f8acf --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_command/recognize_entity_command.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_command import AbstractCommand + + +class RecognizeEntityCommand(AbstractCommand): + """Generated class for RecognizeEntityCommand, inheriting from AbstractCommand.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_command/recognize_intent_command.py b/src/dspygen/experiments/convo_ddd/abstract_command/recognize_intent_command.py new file mode 100644 index 0000000..64c53f1 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_command/recognize_intent_command.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_command import AbstractCommand + + +class RecognizeIntentCommand(AbstractCommand): + """Generated class for RecognizeIntentCommand, inheriting from AbstractCommand.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_command/transition_state_command.py b/src/dspygen/experiments/convo_ddd/abstract_command/transition_state_command.py new file mode 100644 index 0000000..3d3e2c2 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_command/transition_state_command.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_command import AbstractCommand + + +class TransitionStateCommand(AbstractCommand): + """Generated class for TransitionStateCommand, inheriting from AbstractCommand.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_command/update_context_command.py b/src/dspygen/experiments/convo_ddd/abstract_command/update_context_command.py new file mode 100644 index 0000000..2ec7781 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_command/update_context_command.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_command import AbstractCommand + + +class UpdateContextCommand(AbstractCommand): + """Generated class for UpdateContextCommand, inheriting from AbstractCommand.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_event/context_updated_event.py b/src/dspygen/experiments/convo_ddd/abstract_event/context_updated_event.py new file mode 100644 index 0000000..d9f0126 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_event/context_updated_event.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_event import AbstractEvent + + +class ContextUpdatedEvent(AbstractEvent): + """Generated class for ContextUpdatedEvent, inheriting from AbstractEvent.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_event/database_queried_event.py b/src/dspygen/experiments/convo_ddd/abstract_event/database_queried_event.py new file mode 100644 index 0000000..019c4b2 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_event/database_queried_event.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_event import AbstractEvent + + +class DatabaseQueriedEvent(AbstractEvent): + """Generated class for DatabaseQueriedEvent, inheriting from AbstractEvent.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_event/entity_recognized_event.py b/src/dspygen/experiments/convo_ddd/abstract_event/entity_recognized_event.py new file mode 100644 index 0000000..e47ae90 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_event/entity_recognized_event.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_event import AbstractEvent + + +class EntityRecognizedEvent(AbstractEvent): + """Generated class for EntityRecognizedEvent, inheriting from AbstractEvent.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_event/external_api_response_event.py b/src/dspygen/experiments/convo_ddd/abstract_event/external_api_response_event.py new file mode 100644 index 0000000..af29a1a --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_event/external_api_response_event.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_event import AbstractEvent + + +class ExternalAPIResponseEvent(AbstractEvent): + """Generated class for ExternalAPIResponseEvent, inheriting from AbstractEvent.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_event/external_service_called_event.py b/src/dspygen/experiments/convo_ddd/abstract_event/external_service_called_event.py new file mode 100644 index 0000000..e47e186 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_event/external_service_called_event.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_event import AbstractEvent + + +class ExternalServiceCalledEvent(AbstractEvent): + """Generated class for ExternalServiceCalledEvent, inheriting from AbstractEvent.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_event/intent_recognized_event.py b/src/dspygen/experiments/convo_ddd/abstract_event/intent_recognized_event.py new file mode 100644 index 0000000..d201843 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_event/intent_recognized_event.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_event import AbstractEvent + + +class IntentRecognizedEvent(AbstractEvent): + """Generated class for IntentRecognizedEvent, inheriting from AbstractEvent.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_event/response_generated_event.py b/src/dspygen/experiments/convo_ddd/abstract_event/response_generated_event.py new file mode 100644 index 0000000..13baabf --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_event/response_generated_event.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_event import AbstractEvent + + +class ResponseGeneratedEvent(AbstractEvent): + """Generated class for ResponseGeneratedEvent, inheriting from AbstractEvent.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_event/state_transition_event.py b/src/dspygen/experiments/convo_ddd/abstract_event/state_transition_event.py new file mode 100644 index 0000000..5a08396 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_event/state_transition_event.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_event import AbstractEvent + + +class StateTransitionEvent(AbstractEvent): + """Generated class for StateTransitionEvent, inheriting from AbstractEvent.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_event/system_interrupt_event.py b/src/dspygen/experiments/convo_ddd/abstract_event/system_interrupt_event.py new file mode 100644 index 0000000..55f9457 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_event/system_interrupt_event.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_event import AbstractEvent + + +class SystemInterruptEvent(AbstractEvent): + """Generated class for SystemInterruptEvent, inheriting from AbstractEvent.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_event/system_message_displayed_event.py b/src/dspygen/experiments/convo_ddd/abstract_event/system_message_displayed_event.py new file mode 100644 index 0000000..25a3194 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_event/system_message_displayed_event.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_event import AbstractEvent + + +class SystemMessageDisplayedEvent(AbstractEvent): + """Generated class for SystemMessageDisplayedEvent, inheriting from AbstractEvent.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_event/user_input_received_event.py b/src/dspygen/experiments/convo_ddd/abstract_event/user_input_received_event.py new file mode 100644 index 0000000..0e57e11 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_event/user_input_received_event.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_event import AbstractEvent + + +class UserInputReceivedEvent(AbstractEvent): + """Generated class for UserInputReceivedEvent, inheriting from AbstractEvent.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_event/user_message_submitted_event.py b/src/dspygen/experiments/convo_ddd/abstract_event/user_message_submitted_event.py new file mode 100644 index 0000000..316a5c4 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_event/user_message_submitted_event.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_event import AbstractEvent + + +class UserMessageSubmittedEvent(AbstractEvent): + """Generated class for UserMessageSubmittedEvent, inheriting from AbstractEvent.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_event/user_query_handled_event.py b/src/dspygen/experiments/convo_ddd/abstract_event/user_query_handled_event.py new file mode 100644 index 0000000..3dfd8f3 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_event/user_query_handled_event.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_event import AbstractEvent + + +class UserQueryHandledEvent(AbstractEvent): + """Generated class for UserQueryHandledEvent, inheriting from AbstractEvent.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_policy/context_management_policy.py b/src/dspygen/experiments/convo_ddd/abstract_policy/context_management_policy.py new file mode 100644 index 0000000..2c5339f --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_policy/context_management_policy.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_policy import AbstractPolicy + + +class ContextManagementPolicy(AbstractPolicy): + """Generated class for ContextManagementPolicy, inheriting from AbstractPolicy.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_policy/entity_recognition_policy.py b/src/dspygen/experiments/convo_ddd/abstract_policy/entity_recognition_policy.py new file mode 100644 index 0000000..ea0e61a --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_policy/entity_recognition_policy.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_policy import AbstractPolicy + + +class EntityRecognitionPolicy(AbstractPolicy): + """Generated class for EntityRecognitionPolicy, inheriting from AbstractPolicy.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_policy/intent_handling_policy.py b/src/dspygen/experiments/convo_ddd/abstract_policy/intent_handling_policy.py new file mode 100644 index 0000000..d7121b3 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_policy/intent_handling_policy.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_policy import AbstractPolicy + + +class IntentHandlingPolicy(AbstractPolicy): + """Generated class for IntentHandlingPolicy, inheriting from AbstractPolicy.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_policy/response_generation_policy.py b/src/dspygen/experiments/convo_ddd/abstract_policy/response_generation_policy.py new file mode 100644 index 0000000..361758d --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_policy/response_generation_policy.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_policy import AbstractPolicy + + +class ResponseGenerationPolicy(AbstractPolicy): + """Generated class for ResponseGenerationPolicy, inheriting from AbstractPolicy.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_policy/state_transition_policy.py b/src/dspygen/experiments/convo_ddd/abstract_policy/state_transition_policy.py new file mode 100644 index 0000000..879e3d1 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_policy/state_transition_policy.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_policy import AbstractPolicy + + +class StateTransitionPolicy(AbstractPolicy): + """Generated class for StateTransitionPolicy, inheriting from AbstractPolicy.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_query/get_current_context_query.py b/src/dspygen/experiments/convo_ddd/abstract_query/get_current_context_query.py new file mode 100644 index 0000000..ad33262 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_query/get_current_context_query.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_query import AbstractQuery + + +class GetCurrentContextQuery(AbstractQuery): + """Generated class for GetCurrentContextQuery, inheriting from AbstractQuery.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_query/get_current_state_query.py b/src/dspygen/experiments/convo_ddd/abstract_query/get_current_state_query.py new file mode 100644 index 0000000..591c461 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_query/get_current_state_query.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_query import AbstractQuery + + +class GetCurrentStateQuery(AbstractQuery): + """Generated class for GetCurrentStateQuery, inheriting from AbstractQuery.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_query/get_entity_details_query.py b/src/dspygen/experiments/convo_ddd/abstract_query/get_entity_details_query.py new file mode 100644 index 0000000..84c6831 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_query/get_entity_details_query.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_query import AbstractQuery + + +class GetEntityDetailsQuery(AbstractQuery): + """Generated class for GetEntityDetailsQuery, inheriting from AbstractQuery.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_query/get_intent_details_query.py b/src/dspygen/experiments/convo_ddd/abstract_query/get_intent_details_query.py new file mode 100644 index 0000000..4ee10eb --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_query/get_intent_details_query.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_query import AbstractQuery + + +class GetIntentDetailsQuery(AbstractQuery): + """Generated class for GetIntentDetailsQuery, inheriting from AbstractQuery.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_read_model/context_snapshot_read_model.py b/src/dspygen/experiments/convo_ddd/abstract_read_model/context_snapshot_read_model.py new file mode 100644 index 0000000..63cb203 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_read_model/context_snapshot_read_model.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_read_model import AbstractReadModel + + +class ContextSnapshotReadModel(AbstractReadModel): + """Generated class for ContextSnapshotReadModel, inheriting from AbstractReadModel.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_read_model/entity_read_model.py b/src/dspygen/experiments/convo_ddd/abstract_read_model/entity_read_model.py new file mode 100644 index 0000000..48697f8 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_read_model/entity_read_model.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_read_model import AbstractReadModel + + +class EntityReadModel(AbstractReadModel): + """Generated class for EntityReadModel, inheriting from AbstractReadModel.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_read_model/intent_read_model.py b/src/dspygen/experiments/convo_ddd/abstract_read_model/intent_read_model.py new file mode 100644 index 0000000..c2247cf --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_read_model/intent_read_model.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_read_model import AbstractReadModel + + +class IntentReadModel(AbstractReadModel): + """Generated class for IntentReadModel, inheriting from AbstractReadModel.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_read_model/response_read_model.py b/src/dspygen/experiments/convo_ddd/abstract_read_model/response_read_model.py new file mode 100644 index 0000000..abdb238 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_read_model/response_read_model.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_read_model import AbstractReadModel + + +class ResponseReadModel(AbstractReadModel): + """Generated class for ResponseReadModel, inheriting from AbstractReadModel.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_read_model/state_read_model.py b/src/dspygen/experiments/convo_ddd/abstract_read_model/state_read_model.py new file mode 100644 index 0000000..851815c --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_read_model/state_read_model.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_read_model import AbstractReadModel + + +class StateReadModel(AbstractReadModel): + """Generated class for StateReadModel, inheriting from AbstractReadModel.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/ddd/abstract_renderer.py b/src/dspygen/experiments/convo_ddd/abstract_renderer.py similarity index 56% rename from src/dspygen/experiments/ddd/abstract_renderer.py rename to src/dspygen/experiments/convo_ddd/abstract_renderer.py index de9a7cd..b9fadaf 100644 --- a/src/dspygen/experiments/ddd/abstract_renderer.py +++ b/src/dspygen/experiments/convo_ddd/abstract_renderer.py @@ -42,65 +42,62 @@ def generate_class_definitions(model: EventStormingDomainSpecificationModel): def main(): event_storm_model_data = { "domain_event_classnames": [ - "TaskStartedEvent", "TaskCompletedEvent", "TaskFailedEvent", - "ExternalEventOccurredEvent" + "IntentRecognizedEvent", "EntityRecognizedEvent", + "ContextUpdatedEvent", "StateTransitionEvent", + "UserQueryHandledEvent", "ResponseGeneratedEvent" ], "external_event_classnames": [ - "ExternalSystemUpdatedEvent", - "RegulationAmendedEvent", - "ThirdPartyNotificationEvent", - "DataReceivedEvent", - "PartnerNotificationEvent", - "ServiceDownEvent", - "SecurityAlertEvent" + "UserInputReceivedEvent", "ExternalAPIResponseEvent", + "SystemInterruptEvent" ], "command_classnames": [ - "StartProcessCommand", "StopProcessCommand", "ExecuteActivityCommand", - "InvokePartnerCommand", "ReceiveFromPartnerCommand", "HandleFaultCommand", - "SaveProcessInstanceCommand", "LoadProcessInstanceCommand" + "RecognizeIntentCommand", "RecognizeEntityCommand", + "UpdateContextCommand", "TransitionStateCommand", + "GenerateResponseCommand", "HandleUserQueryCommand" ], "query_classnames": [ - "GetProcessStatusQuery", "GetActivityDetailsQuery", - "GetVariableValueQuery", "GetProcessMetricsQuery" + "GetCurrentContextQuery", "GetIntentDetailsQuery", + "GetEntityDetailsQuery", "GetCurrentStateQuery" ], "aggregate_classnames": [ - "ProcessExecutionAggregate", "ActivityExecutionAggregate", - "PartnerInteractionAggregate", "ProcessInstanceAggregate" + "ConversationAggregate" ], "policy_classnames": [ - "ExecutionPolicy", "RetryPolicy", "CompensationPolicy", - "FaultHandlingPolicy" + "IntentHandlingPolicy", "EntityRecognitionPolicy", + "ContextManagementPolicy", "StateTransitionPolicy", + "ResponseGenerationPolicy" ], "read_model_classnames": [ - "ProcessSummaryReadModel", "ActivityLogReadModel", - "VariableSnapshotReadModel", "ProcessInstanceDetailsReadModel" + "IntentReadModel", "EntityReadModel", + "ContextSnapshotReadModel", "StateReadModel", + "ResponseReadModel" ], "view_classnames": [ - "ProcessOverviewView", "TaskDetailsView", "UserDashboardView", - "ErrorLogView" + "IntentView", "EntityView", "ContextView", + "StateView", "ResponseView" ], "ui_event_classnames": [ - "ButtonClickEvent", "FormSubmissionEvent", "TaskCompletionEvent", - "UserInteractionEvent" + "UserMessageSubmittedEvent", "SystemMessageDisplayedEvent" ], "saga_classnames": [ - "ProcessExecutionSaga", "CompensationSaga", "FaultHandlingSaga" + "ConversationHandlingSaga" ], "integration_event_classnames": [ - "ServiceInvocationEvent", "DataTransferEvent", - "PartnerInteractionEvent", "IntegrationEvent" + "ExternalServiceCalledEvent", "DatabaseQueriedEvent" ], "exception_classnames": [ - "ExecutionFailureException", "DataProcessingException", - "IntegrationException", "SystemException" + "IntentNotFoundException", "EntityRecognitionException", + "ContextUpdateException", "InvalidStateException", + "ResponseGenerationException" ], "value_object_classnames": [ - "ProcessIDValueObject", "ActivityDetailsValueObject", - "PartnerDetailsValueObject", "VariableValueObject" + "IntentValueObject", "EntityValueObject", + "ContextValueObject", "StateValueObject", + "ResponseValueObject" ], "task_classnames": [ - "DataValidationTask", "ServiceInvocationTask", - "ErrorHandlingTask", "IntegrationTask" + "ProcessUserInputTask", "GenerateDialogueResponseTask", + "UpdateConversationContextTask", "ManageStateTransitionsTask" ] } diff --git a/src/dspygen/experiments/convo_ddd/abstract_saga/conversation_handling_saga.py b/src/dspygen/experiments/convo_ddd/abstract_saga/conversation_handling_saga.py new file mode 100644 index 0000000..3614ae8 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_saga/conversation_handling_saga.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_saga import AbstractSaga + + +class ConversationHandlingSaga(AbstractSaga): + """Generated class for ConversationHandlingSaga, inheriting from AbstractSaga.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_task/generate_dialogue_response_task.py b/src/dspygen/experiments/convo_ddd/abstract_task/generate_dialogue_response_task.py new file mode 100644 index 0000000..a551c2d --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_task/generate_dialogue_response_task.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_task import AbstractTask + + +class GenerateDialogueResponseTask(AbstractTask): + """Generated class for GenerateDialogueResponseTask, inheriting from AbstractTask.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_task/manage_state_transitions_task.py b/src/dspygen/experiments/convo_ddd/abstract_task/manage_state_transitions_task.py new file mode 100644 index 0000000..d3f1154 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_task/manage_state_transitions_task.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_task import AbstractTask + + +class ManageStateTransitionsTask(AbstractTask): + """Generated class for ManageStateTransitionsTask, inheriting from AbstractTask.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_task/process_user_input_task.py b/src/dspygen/experiments/convo_ddd/abstract_task/process_user_input_task.py new file mode 100644 index 0000000..bb0c599 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_task/process_user_input_task.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_task import AbstractTask + + +class ProcessUserInputTask(AbstractTask): + """Generated class for ProcessUserInputTask, inheriting from AbstractTask.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_task/update_conversation_context_task.py b/src/dspygen/experiments/convo_ddd/abstract_task/update_conversation_context_task.py new file mode 100644 index 0000000..74c45c6 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_task/update_conversation_context_task.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_task import AbstractTask + + +class UpdateConversationContextTask(AbstractTask): + """Generated class for UpdateConversationContextTask, inheriting from AbstractTask.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_value_object/context_value_object.py b/src/dspygen/experiments/convo_ddd/abstract_value_object/context_value_object.py new file mode 100644 index 0000000..ab9ca3f --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_value_object/context_value_object.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_value_object import AbstractValueObject + + +class ContextValueObject(AbstractValueObject): + """Generated class for ContextValueObject, inheriting from AbstractValueObject.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_value_object/entity_value_object.py b/src/dspygen/experiments/convo_ddd/abstract_value_object/entity_value_object.py new file mode 100644 index 0000000..519bd3d --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_value_object/entity_value_object.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_value_object import AbstractValueObject + + +class EntityValueObject(AbstractValueObject): + """Generated class for EntityValueObject, inheriting from AbstractValueObject.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_value_object/intent_value_object.py b/src/dspygen/experiments/convo_ddd/abstract_value_object/intent_value_object.py new file mode 100644 index 0000000..de80e36 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_value_object/intent_value_object.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_value_object import AbstractValueObject + + +class IntentValueObject(AbstractValueObject): + """Generated class for IntentValueObject, inheriting from AbstractValueObject.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_value_object/response_value_object.py b/src/dspygen/experiments/convo_ddd/abstract_value_object/response_value_object.py new file mode 100644 index 0000000..66cea0d --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_value_object/response_value_object.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_value_object import AbstractValueObject + + +class ResponseValueObject(AbstractValueObject): + """Generated class for ResponseValueObject, inheriting from AbstractValueObject.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_value_object/state_value_object.py b/src/dspygen/experiments/convo_ddd/abstract_value_object/state_value_object.py new file mode 100644 index 0000000..8bf811b --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_value_object/state_value_object.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_value_object import AbstractValueObject + + +class StateValueObject(AbstractValueObject): + """Generated class for StateValueObject, inheriting from AbstractValueObject.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_view/context_view.py b/src/dspygen/experiments/convo_ddd/abstract_view/context_view.py new file mode 100644 index 0000000..5367641 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_view/context_view.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_view import AbstractView + + +class ContextView(AbstractView): + """Generated class for ContextView, inheriting from AbstractView.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_view/entity_view.py b/src/dspygen/experiments/convo_ddd/abstract_view/entity_view.py new file mode 100644 index 0000000..304c1f7 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_view/entity_view.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_view import AbstractView + + +class EntityView(AbstractView): + """Generated class for EntityView, inheriting from AbstractView.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_view/intent_view.py b/src/dspygen/experiments/convo_ddd/abstract_view/intent_view.py new file mode 100644 index 0000000..48d6382 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_view/intent_view.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_view import AbstractView + + +class IntentView(AbstractView): + """Generated class for IntentView, inheriting from AbstractView.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_view/response_view.py b/src/dspygen/experiments/convo_ddd/abstract_view/response_view.py new file mode 100644 index 0000000..b2ec584 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_view/response_view.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_view import AbstractView + + +class ResponseView(AbstractView): + """Generated class for ResponseView, inheriting from AbstractView.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/abstract_view/state_view.py b/src/dspygen/experiments/convo_ddd/abstract_view/state_view.py new file mode 100644 index 0000000..5557895 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/abstract_view/state_view.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.abstract_view import AbstractView + + +class StateView(AbstractView): + """Generated class for StateView, inheriting from AbstractView.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/domain_exception/context_update_exception.py b/src/dspygen/experiments/convo_ddd/domain_exception/context_update_exception.py new file mode 100644 index 0000000..5dbfa7d --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/domain_exception/context_update_exception.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.domain_exception import DomainException + + +class ContextUpdateException(DomainException): + """Generated class for ContextUpdateException, inheriting from DomainException.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/domain_exception/entity_recognition_exception.py b/src/dspygen/experiments/convo_ddd/domain_exception/entity_recognition_exception.py new file mode 100644 index 0000000..c80e6eb --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/domain_exception/entity_recognition_exception.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.domain_exception import DomainException + + +class EntityRecognitionException(DomainException): + """Generated class for EntityRecognitionException, inheriting from DomainException.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/domain_exception/intent_not_found_exception.py b/src/dspygen/experiments/convo_ddd/domain_exception/intent_not_found_exception.py new file mode 100644 index 0000000..83a4c97 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/domain_exception/intent_not_found_exception.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.domain_exception import DomainException + + +class IntentNotFoundException(DomainException): + """Generated class for IntentNotFoundException, inheriting from DomainException.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/domain_exception/invalid_state_exception.py b/src/dspygen/experiments/convo_ddd/domain_exception/invalid_state_exception.py new file mode 100644 index 0000000..a65b4e6 --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/domain_exception/invalid_state_exception.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.domain_exception import DomainException + + +class InvalidStateException(DomainException): + """Generated class for InvalidStateException, inheriting from DomainException.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/convo_ddd/domain_exception/response_generation_exception.py b/src/dspygen/experiments/convo_ddd/domain_exception/response_generation_exception.py new file mode 100644 index 0000000..dd0155c --- /dev/null +++ b/src/dspygen/experiments/convo_ddd/domain_exception/response_generation_exception.py @@ -0,0 +1,6 @@ +from dspygen.rdddy.domain_exception import DomainException + + +class ResponseGenerationException(DomainException): + """Generated class for ResponseGenerationException, inheriting from DomainException.""" + \ No newline at end of file diff --git a/src/dspygen/experiments/pomo_bud/__init__.py b/src/dspygen/experiments/pomo_bud/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/dspygen/experiments/pomo_bud/pomo_bud_dsl.yaml b/src/dspygen/experiments/pomo_bud/pomo_bud_dsl.yaml new file mode 100644 index 0000000..a28c762 --- /dev/null +++ b/src/dspygen/experiments/pomo_bud/pomo_bud_dsl.yaml @@ -0,0 +1,34 @@ +system_name: "OSIRIS-PomoBud" +sensory_capabilities: + - feature_name: "SpeechRecognition" + sensory_type: "Auditory" + inputs: ["audio"] + outputs: ["text"] + configurations: {} +actuators: + - feature_name: "TextToSpeech" + actuator_type: "Speech" + inputs: ["text"] + outputs: ["audio"] + configurations: {} +cognitive_functions: + - feature_name: "NaturalLanguageUnderstanding" + cognitive_type: "NLP" + inputs: ["text"] + outputs: ["intent", "entities"] + configurations: {} +learning_mechanisms: + - feature_name: "ReinforcementLearning" + learning_type: "Adaptive" + configurations: + learning_rate: 0.01 +interaction_protocols: + - feature_name: "ConversationalInterface" + protocol_type: "ConversationalAI" + inputs: ["text"] + outputs: ["response"] + configurations: {} +continuous_adaptation: + - mechanism_name: "SelfModification" + details: + update_frequency: "weekly" diff --git a/src/dspygen/experiments/pomo_bud/pomo_bud_models.py b/src/dspygen/experiments/pomo_bud/pomo_bud_models.py new file mode 100644 index 0000000..428b88a --- /dev/null +++ b/src/dspygen/experiments/pomo_bud/pomo_bud_models.py @@ -0,0 +1,112 @@ +from typing import List, Dict, Any, Optional, Union +from pydantic import BaseModel, Field, validator + +from dspygen.utils.yaml_tools import YAMLMixin + + +class FeatureSpecification(BaseModel): + """ + Describes the specifications for each feature, including inputs, outputs, and configuration parameters. + """ + feature_name: str = Field(..., description="Unique name of the feature.") + inputs: List[str] = Field(default=[], description="List of expected inputs.") + outputs: List[str] = Field(default=[], description="List of possible outputs.") + configurations: Dict[str, Any] = Field(default={}, description="Configuration parameters for the feature.") + + +class SensoryCapability(FeatureSpecification): + """ + Enhanced sensory input model capturing detailed specifications for processing various sensory data. + """ + sensory_type: str = Field(..., description="Type of sensory data to be processed.") + + +class ActuatorCapability(FeatureSpecification): + """ + Detailed actuator model defining the actions that can be performed in the physical or digital world. + """ + actuator_type: str = Field(..., description="Type of action the actuator performs.") + + +class CognitiveFunction(FeatureSpecification): + """ + Represents a cognitive function or model with enhanced capabilities for decision making and processing. + """ + cognitive_type: str = Field(..., description="Type of cognitive processing or decision-making model.") + + +class LearningMechanism(FeatureSpecification): + """ + Defines learning mechanisms with parameters for how the system can learn or adapt over time. + """ + learning_type: str = Field(..., description="Type of learning mechanism.") + + +class InteractionProtocol(FeatureSpecification): + """ + Specifies the protocols for human-AI interaction, including conversational interfaces. + """ + protocol_type: str = Field(..., description="Type of interaction protocol, e.g., conversational AI, gestures.") + + +class ContinuousAdaptationMechanism(BaseModel): + """ + Mechanisms for continuous learning and self-adaptation, with validators ensuring correct configuration. + """ + mechanism_name: str = Field(..., description="Name of the adaptation mechanism.") + details: Dict[str, Any] = Field(..., description="Detailed configuration for the adaptation mechanism.") + + +class AGISystemConfiguration(BaseModel, YAMLMixin): + """ + Top-level model defining an AGI system's configuration using the DSL, capable of migrating existing conversational systems. + """ + system_name: str = Field(..., description="Name of the AGI system.") + sensory_capabilities: List[SensoryCapability] = Field(default=[], description="Sensory processing capabilities.") + actuators: List[ActuatorCapability] = Field(default=[], description="Actuator capabilities.") + cognitive_functions: List[CognitiveFunction] = Field(default=[], description="Cognitive processing functions.") + learning_mechanisms: List[LearningMechanism] = Field(default=[], description="Learning and adaptation mechanisms.") + interaction_protocols: List[InteractionProtocol] = Field(default=[], description="Human-AI interaction protocols.") + continuous_adaptation: List[ContinuousAdaptationMechanism] = Field(default=[], + description="Mechanisms for continuous system adaptation.") + + +def main2(): + """Main function""" + # Example configuration for a system with basic conversational capabilities, learning, and sensory processing + example_agi_system = AGISystemConfiguration( + system_name="OSIRIS-PomoBud", + sensory_capabilities=[ + SensoryCapability(feature_name="SpeechRecognition", sensory_type="Auditory", inputs=["audio"], + outputs=["text"]), + ], + actuators=[ + ActuatorCapability(feature_name="TextToSpeech", actuator_type="Speech", inputs=["text"], outputs=["audio"]), + ], + cognitive_functions=[ + CognitiveFunction(feature_name="NaturalLanguageUnderstanding", cognitive_type="NLP", inputs=["text"], + outputs=["intent", "entities"]), + ], + learning_mechanisms=[ + LearningMechanism(feature_name="ReinforcementLearning", learning_type="Adaptive", + configurations={"learning_rate": 0.01}), + ], + interaction_protocols=[ + InteractionProtocol(feature_name="ConversationalInterface", protocol_type="ConversationalAI", + inputs=["text"], outputs=["response"]), + ], + continuous_adaptation=[ + ContinuousAdaptationMechanism(mechanism_name="SelfModification", details={"update_frequency": "weekly"}), + ] + ) + + print(example_agi_system) + + +def main(): + inst = AGISystemConfiguration.from_yaml("pomo_bud_dsl.yaml") + print(inst) + + +if __name__ == '__main__': + main() diff --git a/src/dspygen/experiments/rfc5545/__init__.py b/src/dspygen/experiments/rfc5545/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/dspygen/experiments/rfc5545/calendar_cmd.py b/src/dspygen/experiments/rfc5545/calendar_cmd.py new file mode 100644 index 0000000..66fce88 --- /dev/null +++ b/src/dspygen/experiments/rfc5545/calendar_cmd.py @@ -0,0 +1,377 @@ +import asyncio +import datetime +from textwrap import dedent + +from dateutil import parser +from dataclasses import field, dataclass +from typing import Optional, Any + +import typer + +from dspygen.experiments.rfc5545.ical_models import Event +from dspygen.utils.date_tools import * + +app = typer.Typer(help="Advanced Calendar Assistant") + + +@dataclass +class VEvent: + dtstart: datetime.datetime = None + dtend: Optional[datetime.datetime] = None + duration: Optional[str] = None + summary: Optional[str] = None + description: Optional[str] = None + location: Optional[str] = None + + def __str__(self): + dtstart_str = self.dtstart.strftime("%I:%M%p on %A, %B %d, %Y") + dtend_str = ( + self.dtend.strftime("%I:%M%p on %A, %B %d, %Y") if self.dtend else "" + ) + return ( + f"Summary:\t{self.summary}\n" + f"Start:\t\t{dtstart_str}\n" + f"End:\t\t{dtend_str}\n" + f"Description:\t{self.description}\n" + f"Location:\t{self.location}" + ) + + def to_event_kwargs(self): + return { + "dtstart": str(self.dtstart), + "dtend": str(self.dtend) if self.dtend else None, + "duration": self.duration, + "summary": self.summary, + "description": self.description, + "location": self.location, + } + + +@app.command("create") +def create_event(): + user_input = typer.prompt("Please provide details for the new event") + asyncio.run(_create_event(user_input)) + + +async def _create_event(user_input): + event = await get_vevent(user_input) + + # Display event details and ask for confirmation + confirmed = False + while not confirmed: + print(f"Event Details:\n{event}") + + confirm = typer.prompt("Are these details correct? [y/N]") + if confirm == "y" or confirm == "Y" or confirm == "yes" or confirm == "Yes": + confirmed = True + else: + # Re-prompt for details + event = await get_vevent( + f"{event}\nPlease provide the correct details for the event\n{confirm}" + ) + + # Create the event + Event.create(**event.to_event_kwargs()) + print(f"Created Event:\n{event}") + + +@app.command("update") +def update_event(): + user_input = typer.prompt("Which event would you like to update?") + asyncio.run(_update_event(user_input)) + + +async def _update_event(user_input): + correct_event = False + chosen_event = None + + while not correct_event: + # Retrieve events based on user input + + events = Event.query(user_input)[:3] + + # print(events) + + if not events: + print("No events found with the provided keyword. Please try again.") + return + + # Display events and ask user to select one + for i, event in enumerate(events): + print(f"[{i + 1}] {event}") + + event_number = typer.prompt( + "Please choose the event number to update (or enter '0' to search again)" + ) + + # Allow user to re-enter search keyword + if event_number == "0": + user_input = typer.prompt("Which event would you like to update?") + return await _update_event(user_input) + + # Validate selection + try: + chosen_event = events[int(event_number) - 1] + correct_event = True + except (IndexError, ValueError): + print("Invalid selection. Please try again.") + continue + + # Proceed with event update logic + await _update_chosen_event(chosen_event) + + +async def _update_chosen_event(chosen_event): + print(f"Updating event: {chosen_event}") + event = Event.read(chosen_event.id) + + # Display event details and ask for confirmation + confirmed = False + while not confirmed: + print(f"Event Details:\n{event}") + + confirm = typer.prompt("Are these details correct? [y/N]") + if confirm == "y" or confirm == "Y" or confirm == "yes" or confirm == "Yes": + confirmed = True + else: + # Re-prompt for details + event = await get_vevent( + f"{event}\nPlease provide the CORRECT DETAILS for the event\n{confirm}" + ) + + # Create the event + Event.update(event_id=chosen_event.id, **event.to_event_kwargs()) + print(f"Updated Event: {event}") + + +@app.command("delete") +def delete_event(): + user_input = typer.prompt("Which event would you like to delete?") + asyncio.run(_delete_event(user_input)) + + +async def _delete_event(user_input): + correct_event = False + chosen_event = None + + while not correct_event: + # Retrieve events based on user input + + events = Event.query(user_input)[:3] + + # print(events) + + if not events: + print("No events found with the provided keyword. Please try again.") + return + + # Display events and ask user to select one + for i, event in enumerate(events): + print(f"[{i + 1}] {event}") + + event_number = typer.prompt( + "Please choose the event number to delete (or enter '0' to search again)" + ) + + # Allow user to re-enter search keyword + if event_number == "0": + user_input = typer.prompt("Which event would you like to delete?") + return await _delete_event(user_input) + + # Validate selection + try: + chosen_event = events[int(event_number) - 1] + print(f"Deleting event:\n{chosen_event}") + Event.delete(chosen_event.id) + correct_event = True + except (IndexError, ValueError): + print("Invalid selection. Please try again.") + continue + + +@app.command("list") +def list_events( + page: int = 0, per_page: int = 3, sort: str = "dtstart", asc: bool = True +): + asyncio.run(_list_events(page=page, per_page=per_page, sort=sort, asc=asc)) + + +async def _list_events( + page: int = 0, per_page: int = 3, sort: str = "dtstart", asc: bool = True +): + events = Event.get_by_page(page=page, per_page=per_page, sort=sort, asc=asc) + for event in events: + print(event) + + +async def get_vevent(prompt): + assistant_prompt = dedent( + f"""You are a ICalendar Assistant. +You are very careful to make sure the start and end times are perfect. +Pay close attention to the start and end times. The user may not provide clear information. +Do your best to extract the start and end times. + + + +Today is {TODAY}. +Tomorrow morning is {TOMORROW_MORNING_8AM}. +This Saturday is {SATURDAY} (Default to this Saturday). +This Sunday is {SUNDAY} (Default to this Sunday). + + +ICalendar Assistant: How can I assist you today? + +User: {prompt}. Please make sure the start and end times are correct. Remember, this Saturday is {SATURDAY} and this Sunday is {SUNDAY}. + +ICalendar Assistant: I will turn that into the perfect icalendar VEVENT. I will pay close attention to the +start and end times. I will also make sure the summary, description, and location are correct. You are creative +and can use your imagination to fill in the gaps. You gave me the following information: {prompt} + +```vevent +BEGIN:VEVENT +DTSTAMP:{TODAY} +SUMMARY:Morning meeting +DESCRIPTION:Mandatory morning meeting with the team +DTSTART:{MONDAY_8AM} +DTEND:{MONDAY_9AM} +LOCATION:Main conference room +END:VEVENT +``` + +ICalendar Assistant: Please confirm the details are correct. I made sure to follow your instructions to the letter. + +User: {prompt}. Please make sure the start and end times are correct. + +ICalendar Assistant: I turned {prompt} into the perfect icalendar VEVENT. I paid close attention. + +```vevent +BEGIN:VEVENT +DTSTAMP:{TODAY}""" + ) + + # vevent = await acreate( + # prompt=assistant_prompt, stop="\nEND:VEVENT", max_tokens=1000 + # ) + + vevent = "" + + kwargs = { + line.split(":")[0].lower(): line.split(":")[1] + for line in vevent.split("\n") + if line != "END:VEVENT" + } + + kwargs["dtstart"] = parser.parse(kwargs["dtstart"]) + kwargs["dtend"] = parser.parse(kwargs["dtend"]) if kwargs["dtend"] else None + + event = VEvent(**kwargs) + return event + + +@app.command("export") +def export_event( + file_path: str = typer.Option( + ".", "--file-path", "-f", help="The path to save the exported event" + ) +): + user_input = typer.prompt("Which event would you like to export?") + asyncio.run(_export_event(user_input, file_path=file_path)) + + +async def _export_event(user_input, file_path=None): + correct_event = False + chosen_event = None + + while not correct_event: + # Retrieve events based on user input + + events = Event.query(user_input)[:3] + + if not events: + print("No events found with the provided keyword. Please try again.") + return + + # Display events and ask user to select one for export + for i, event in enumerate(events): + print(f"[{i + 1}] {event}") + + event_number = typer.prompt( + "Please choose the event number to export (or enter '0' to cancel)" + ) + + # Allow user to cancel the export + if event_number == "0": + print("Export canceled.") + return + + # Validate selection + try: + chosen_event = events[int(event_number) - 1] + print(f"Exporting event:\n{chosen_event}") + ics_content = chosen_event.to_ics() # Export the chosen event to ICS format + + if file_path: + chosen_event.to_ics(file_path) + else: + # Prompt user for the export file path + export_file_path = typer.prompt( + "Please enter the export file path. If it is a directory, the filename will be the event summary and start time." + ) + # Prompt user for the export file path + with open(export_file_path, "w") as f: + f.write(ics_content) + print(f"Event exported to:\n{export_file_path}") + correct_event = True + except (IndexError, ValueError): + print("Invalid selection. Please try again.") + continue + + +@app.command("import") +def create_5_events_to_db() -> None: + events = [ + { + "dtstart": str(datetime.datetime.now()), + "dtend": str(datetime.datetime.now()), + "duration": "1h", + "summary": "Event 1", + "description": "Description of Event 1", + "location": "Location of Event 1", + }, + { + "dtstart": str(datetime.datetime.now()), + "dtend": str(datetime.datetime.now()), + "duration": "1h", + "summary": "Event 2", + "description": "Description of Event 2", + "location": "Location of Event 2", + }, + { + "dtstart": str(datetime.datetime.now()), + "dtend": str(datetime.datetime.now()), + "duration": "1h", + "summary": "Event 3", + "description": "Description of Event 3", + "location": "Location of Event 3", + }, + { + "dtstart": str(datetime.datetime.now()), + "dtend": str(datetime.datetime.now()), + "duration": "1h", + "summary": "Event 4", + "description": "Description of Event 4", + "location": "Location of Event 4", + }, + { + "dtstart": str(datetime.datetime.now()), + "dtend": str(datetime.datetime.now()), + "duration": "1h", + "summary": "Event 5", + "description": "Description of Event 5", + "location": "Location of Event 5", + }, + ] + + for event in events: + event = Event.create(**event) + print(f"Created Event:\n{event}") diff --git a/src/dspygen/experiments/rfc5545/ical_crud.py b/src/dspygen/experiments/rfc5545/ical_crud.py new file mode 100644 index 0000000..f1ebae0 --- /dev/null +++ b/src/dspygen/experiments/rfc5545/ical_crud.py @@ -0,0 +1,89 @@ +from datetime import datetime +from typing import List + +from icontract import require, ensure + +import uuid + +from sqlmodel import Session + +from dspygen.utils.crud_tools import * + +from dspygen.experiments.rfc5545.ical_models import * + +delete = True + + +# Create a new journal entry +@require(lambda dtstamp: isinstance(dtstamp, datetime)) +@require(lambda dtstart: dtstart is None or isinstance(dtstart, datetime)) +@require(lambda summary: summary is None or isinstance(summary, str)) +@require(lambda description: description is None or isinstance(description, str)) +@require(lambda calendar_id: calendar_id is None or isinstance(calendar_id, int)) +@ensure(lambda result: result.id is not None) +def create_journal( + dtstart: datetime = None, + summary: str = None, + description: str = None, + calendar_id: int = None, +) -> Journal: + journal = Journal( + uid=str(uuid.uuid4()), + dtstamp=datetime.utcnow(), + dtstart=dtstart, + summary=summary, + description=description, + calendar_id=calendar_id, + ) + add_model(journal) + return journal + + +# Read a journal entry by its ID +@require(lambda journal_id: isinstance(journal_id, int)) +@ensure(lambda result: result is not None) +def read_journal(journal_id: int) -> Journal: + return get_model(Journal, journal_id) + + +# Update a journal entry by its ID +@require(lambda journal_id: isinstance(journal_id, int)) +@require(lambda dtstart: dtstart is None or isinstance(dtstart, datetime)) +@require(lambda summary: summary is None or isinstance(summary, str)) +@require(lambda description: description is None or isinstance(description, str)) +@require(lambda calendar_id: calendar_id is None or isinstance(calendar_id, int)) +@ensure(lambda result: result is not None) +def update_journal( + journal_id: int, + dtstart: datetime = None, + summary: str = None, + description: str = None, + calendar_id: int = None, +) -> Journal: + with update_model(Journal, journal_id) as journal: + journal.dtstamp = datetime.utcnow() + if dtstart is not None: + journal.dtstart = dtstart + if summary is not None: + journal.summary = summary + if description is not None: + journal.description = description + if calendar_id is not None: + journal.calendar_id = calendar_id + + return journal + + +# Delete a journal entry by its ID +@require(lambda journal_id: isinstance(journal_id, int)) +@ensure(lambda result: result is None) +def delete_journal(journal_id: int) -> None: + delete_model(Journal, journal_id) + + +# List all journal entries +@require(lambda session: isinstance(session, Session)) +@ensure(lambda result: isinstance(result, list)) +def list_journals(session: Session) -> List[Journal]: + journals = session.query(Journal).all() + return journals diff --git a/src/dspygen/experiments/rfc5545/ical_data_ret.py b/src/dspygen/experiments/rfc5545/ical_data_ret.py new file mode 100644 index 0000000..28ff5d6 --- /dev/null +++ b/src/dspygen/experiments/rfc5545/ical_data_ret.py @@ -0,0 +1,13 @@ +from dspygen.rm.data_retriever import DataRetriever +from dspygen.utils.file_tools import data_dir + + +def main(): + """Main function""" + + ret = DataRetriever(data_dir('dev.csv'), "SELECT * FROM event").forward() + print(ret) + + +if __name__ == '__main__': + main() diff --git a/src/dspygen/experiments/rfc5545/ical_db_session.py b/src/dspygen/experiments/rfc5545/ical_db_session.py new file mode 100644 index 0000000..f19dfaa --- /dev/null +++ b/src/dspygen/experiments/rfc5545/ical_db_session.py @@ -0,0 +1,23 @@ +import os + +from sqlalchemy import create_engine +from sqlmodel import Session, SQLModel + +from dspygen.utils.file_tools import data_dir + + +# from utils.chroma_memstore import ChromaMemStore + + +# def get_mem_store(): +# return ChromaMemStore(mem_store_path) + + +def get_session(): + DATABASE_URL = f"sqlite:///{data_dir('dev.db')}" + + engine = create_engine(DATABASE_URL) + + SQLModel.metadata.create_all(engine) + + return Session(engine) diff --git a/src/dspygen/experiments/rfc5545/ical_models.py b/src/dspygen/experiments/rfc5545/ical_models.py new file mode 100644 index 0000000..6c31feb --- /dev/null +++ b/src/dspygen/experiments/rfc5545/ical_models.py @@ -0,0 +1,395 @@ +import json +import uuid +from dataclasses import dataclass +from pathlib import Path + +import openai +from dateutil import parser +from icontract import require, ensure +from pydantic import BaseModel +from slugify import slugify +from sqlmodel import Field, SQLModel, Relationship, select +from datetime import datetime +from typing import Optional, List, NamedTuple + +from tzlocal import get_localzone + +from dspygen.experiments.rfc5545.ical_db_session import get_session +from dspygen.utils.crud_tools import add_model, update_model, delete_model, get_model +from dspygen.utils.date_tools import parse_datetime + + +class OptionConfig(BaseModel): + model: Optional[str] + input_file: Optional[str] + output_file: Optional[str] + prompt: Optional[str] + verbose: Optional[bool] + auto_save: Optional[bool] + paste_from_clipboard: Optional[bool] + file_extension: Optional[str] + append_to_output: Optional[bool] + response: Optional[str] + max_tokens: Optional[int] + + +class Calendar(SQLModel, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + prodid: str = Field(description="Product Identifier") + version: str = Field(description="Version of the calendar") + calscale: Optional[str] = Field(description="Calendar scale used") + method: Optional[str] = Field(description="Method used in the calendar") + events: List["Event"] = Relationship(back_populates="calendar") + todos: List["Todo"] = Relationship(back_populates="calendar") + journals: List["Journal"] = Relationship(back_populates="calendar") + + +class Event(SQLModel, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + uid: str = Field(description="Globally unique identifier") + dtstamp: datetime = Field(description="Date/time stamp") + dtstart: datetime = Field(description="Start date/time of the event") + dtend: Optional[datetime] = Field(description="End date/time of the event") + duration: Optional[str] = Field(description="Duration of the event") + summary: Optional[str] = Field(description="Summary of the event") + description: Optional[str] = Field(description="Full description of the event") + location: Optional[str] = Field(description="Location of the event") + calendar_id: Optional[int] = Field(default=None, foreign_key="calendar.id") + calendar: Optional[Calendar] = Relationship(back_populates="events") + alarms: List["Alarm"] = Relationship(back_populates="event") + + def to_ics(self, file_path: Optional[str] = None) -> str: + """ + Converts the event to iCalendar format with the user's local timezone. + """ + # Get the user's local timezone + local_tz = get_localzone() + + # Convert event start and end times to the user's local timezone + dtstart_local = self.dtstart.astimezone(local_tz) + dtend_local = self.dtend.astimezone(local_tz) if self.dtend else None + + # Start the calendar + ics_str = "BEGIN:VCALENDAR\n" + ics_str += "VERSION:2.0\n" + ics_str += "PRODID:-//YourCompany//YourProduct//EN\n" # Example Product ID + + # Event details + ics_str += "BEGIN:VEVENT\n" + ics_str += f"UID:{self.uid}\n" + ics_str += f'DTSTAMP:{self.dtstamp.strftime("%Y%m%dT%H%M%SZ")}\n' + ics_str += f'DTSTART:{dtstart_local.strftime("%Y%m%dT%H%M%S")}\n' + if dtend_local: + ics_str += f'DTEND:{dtend_local.strftime("%Y%m%dT%H%M%S")}\n' + if self.summary: + ics_str += f"SUMMARY:{self.summary}\n" + if self.description: + ics_str += f"DESCRIPTION:{self.description}\n" + if self.location: + ics_str += f"LOCATION:{self.location}\n" + ics_str += "END:VEVENT\n" + + # End the calendar + ics_str += "END:VCALENDAR\n" + + # If path is a directory, save the ICS content to a file in that directory + # the file name will be the event slugged summary and dtstart with a .ics extension + if file_path and Path(file_path).is_dir(): + file_name = ( + f"{slugify(self.summary)}_{self.dtstart.strftime('%Y%m%dT%H%M%SZ')}.ics" + ) + file_path = Path(file_path) / file_name + with open(file_path, "w") as f: + f.write(ics_str) + elif file_path: + with open(file_path, "w") as f: + f.write(ics_str) + + return ics_str + + def __repr__(self): + return ( + f"Event(id={self.id}, summary='{self.summary}', dtstart={self.dtstart}, " + f"dtend={self.dtend}, duration={self.duration}, description='{self.description}', " + f"location='{self.location}')" + ) + + def __str__(self): + dtstart_str = self.dtstart.strftime("%I:%M%p on %A, %B %d, %Y") + dtend_str = ( + self.dtend.strftime("%I:%M%p on %A, %B %d, %Y") if self.dtend else "" + ) + return ( + f"Summary:\t{self.summary}\n" + f"Start:\t\t{dtstart_str}\n" + f"End:\t\t{dtend_str}\n" + f"Description:\t{self.description}\n" + f"Location:\t{self.location}" + ) + + @staticmethod + @require(lambda dtstart: parser.parse(dtstart)) + @require(lambda dtend: dtend is None or parser.parse(dtend)) + @require(lambda duration: duration is None or isinstance(duration, str)) + @require(lambda summary: summary is None or isinstance(summary, str)) + @require(lambda description: description is None or isinstance(description, str)) + @require(lambda location: location is None or isinstance(location, str)) + def create( + dtstart: str, + dtend: str = None, + duration: str = None, + summary: str = None, + description: str = None, + location: str = None, + ) -> "Event": + uid = uuid.uuid4() + uid_str = str(uid) + + dtstamp = datetime.utcnow() + + event = Event( + uid=uid_str, + dtstamp=dtstamp, + dtstart=parser.parse(dtstart), + dtend=parser.parse(dtend) if dtend else None, + duration=duration, + summary=summary, + description=description, + location=location, + ) + + add_model(event) + return event + + @staticmethod + @require(lambda event_id: isinstance(event_id, int)) + @require(lambda dtstart: dtstart is None or parser.parse(dtstart)) + @require(lambda dtend: dtend is None or parser.parse(dtend)) + @require(lambda duration: duration is None or isinstance(duration, str)) + @require(lambda summary: summary is None or isinstance(summary, str)) + @require(lambda description: description is None or isinstance(description, str)) + @require(lambda location: location is None or isinstance(location, str)) + def update( + event_id: int, + dtstart: str = None, + dtend: str = None, + duration: str = None, + summary: str = None, + description: str = None, + location: str = None, + ): + with update_model(Event, event_id) as event: + event.dtstamp = datetime.utcnow() + if dtstart: + event.dtstart = parser.parse(dtstart) + if dtend: + event.dtend = parser.parse(dtend) if dtend else None + if duration: + event.duration = duration + if summary: + event.summary = summary + if description: + event.description = description + if location: + event.location = location + + @staticmethod + @require(lambda event_id: isinstance(event_id, int)) + @ensure(lambda result, event_id: result is None) + def delete(event_id: int) -> None: + delete_model(Event, event_id) + + @staticmethod + @require(lambda event_id: isinstance(event_id, int)) + @ensure(lambda result, event_id: result.id == event_id) + def read(event_id: int) -> "Event": + return get_model(Event, event_id) + + @staticmethod + def most_recent() -> "Event": + return get_session().exec(select(Event).order_by(Event.dtstamp.asc())).first() + + @staticmethod + def get_by_page( + page: int = 0, + per_page: int = 10, + sort: str = "dtstart", + asc: bool = True, + include_past: bool = False, + ) -> List["Event"]: + """Get events by page sorted by""" + order_by = getattr(Event, sort).asc() if asc else getattr(Event, sort).desc() + + query = select(Event).order_by(order_by) + + if not include_past: + # Add a filter to exclude past events + query = query.filter(Event.dtstart >= datetime.now()) + + return get_session().exec(query.offset(page * per_page).limit(per_page)).all() + + # @staticmethod + # def query(text: str, where: dict = None, n_results: int = 10) -> List["Event"]: + # TODO: Reimplement with + # """Query events""" + # results = get_mem_store().query( + # collection_id="Event_collection", + # query=text, + # where=where, + # n_results=n_results, + # ) + # result_ids = [json.loads(r)["id"] for r in results["documents"][0]] + # models = get_session().exec(select(Event).where(Event.id.in_(result_ids))).all() + # # sort models by result_ids + # return sorted(models, key=lambda m: result_ids.index(m.id)) + + +class Todo(SQLModel, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + uid: str = Field(description="Globally unique identifier") + dtstamp: datetime = Field(description="Date/time stamp") + dtstart: Optional[datetime] = Field(description="Start date/time of the todo") + due: Optional[datetime] = Field(description="Due date/time of the todo") + summary: Optional[str] = Field(description="Summary of the todo") + description: Optional[str] = Field(description="Description of the todo") + completed: Optional[datetime] = Field(description="Completion date/time") + calendar_id: Optional[int] = Field(default=None, foreign_key="calendar.id") + calendar: Optional[Calendar] = Relationship(back_populates="todos") + + @staticmethod + @require(lambda summary: summary is isinstance(summary, str)) + @ensure(lambda result: result.id is not None) + def create( + summary: str, + dtstart: str | datetime = None, + due: str | datetime = None, + description: str = None, + completed: str | datetime = None, + calendar_id: Optional[int] = None + ) -> "Todo": + uid = uuid.uuid4() + uid_str = str(uid) + + dtstamp = datetime.utcnow() + + new_todo = Todo( + uid=uid_str, + dtstamp=dtstamp, + dtstart=parse_datetime(dtstart), + due=parse_datetime(due), + summary=summary, + description=description, + completed=parse_datetime(completed), + calendar_id=calendar_id + ) + + add_model(new_todo) + + return new_todo + + @staticmethod + @require(lambda todo_id: isinstance(todo_id, int)) + def update( + todo_id: int, + summary: str, + dtstart: str | datetime = None, + due: str | datetime = None, + description: str = None, + completed: str | datetime = None, + calendar_id: Optional[int] = None + ): + with update_model(Todo, todo_id) as todo: + todo.dtstamp = datetime.utcnow() + todo.dtstart = parse_datetime(dtstart) + todo.due = parse_datetime(due) + todo.summary = summary + todo.description = description + todo.completed = parse_datetime(completed) + todo.calendar_id = calendar_id + + @staticmethod + @require(lambda todo_id: isinstance(todo_id, int)) + def delete(todo_id: int): + delete_model(Todo, todo_id) + + @staticmethod + @require(lambda todo_id: isinstance(todo_id, int)) + def read(todo_id: int) -> "Todo": + return get_model(Todo, todo_id) + + +class Journal(SQLModel, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + uid: str = Field(description="Globally unique identifier") + dtstamp: datetime = Field(description="Date/time stamp") + dtstart: Optional[datetime] = Field( + description="Start date/time of the journal entry" + ) + summary: Optional[str] = Field(description="Summary of the journal entry") + description: Optional[str] = Field(description="Description of the journal entry") + calendar_id: Optional[int] = Field(default=None, foreign_key="calendar.id") + calendar: Optional[Calendar] = Relationship(back_populates="journals") + + +class Alarm(SQLModel, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + action: str = Field(description="Action of the alarm") + trigger: datetime = Field(description="Trigger time of the alarm") + duration: Optional[str] = Field(description="Duration of the alarm") + repeat: Optional[int] = Field(description="Repeat count of the alarm") + description: Optional[str] = Field(description="Description of the alarm") + event_id: Optional[int] = Field(default=None, foreign_key="event.id") + event: Optional[Event] = Relationship(back_populates="alarms") + + @staticmethod + @require(lambda action: isinstance(action, str)) + @require(lambda trigger: isinstance(trigger, datetime)) + def create( + action: str, + trigger: str | datetime, + duration: str = None, + repeat: int = None, + description: str = None, + event_id: Optional[int] = None + ) -> "Alarm": + new_alarm = Alarm( + action=action, + trigger=parse_datetime(trigger), + duration=duration, + repeat=repeat, + description=description, + event_id=event_id + ) + + add_model(new_alarm) + + return new_alarm + + @staticmethod + @require(lambda alarm_id: isinstance(alarm_id, int)) + def update( + alarm_id: int, + action: str, + trigger: str | datetime, + duration: str = None, + repeat: int = None, + description: str = None, + event_id: Optional[int] = None + ): + with update_model(Alarm, alarm_id) as alarm: + alarm.action = action + alarm.trigger = parse_datetime(trigger) + alarm.duration = duration + alarm.repeat = repeat + alarm.description = description + alarm.event_id = event_id + + @staticmethod + @require(lambda alarm_id: isinstance(alarm_id, int)) + def delete(alarm_id: int): + delete_model(Alarm, alarm_id) + + @staticmethod + @require(lambda alarm_id: isinstance(alarm_id, int)) + def read(alarm_id: int) -> "Alarm": + return get_model(Alarm, alarm_id) diff --git a/src/dspygen/experiments/rfc5545/ical_workbench.py b/src/dspygen/experiments/rfc5545/ical_workbench.py new file mode 100644 index 0000000..b5fa829 --- /dev/null +++ b/src/dspygen/experiments/rfc5545/ical_workbench.py @@ -0,0 +1,29 @@ +from dspygen.experiments.rfc5545.ical_db_session import get_session +from dspygen.experiments.rfc5545.ical_models import Event + + +def create_event(): + Event.create(dtstart="2021-01-01T00:00:00", dtend="2021-01-01T01:00:00", summary="Test event") + + +def get_events(): + events = Event.get_by_page(page=0, per_page=10, include_past=True) + for event in events: + print(event) + return events + + +def update_event(): + event = Event.update(event_id=1, summary="Updated event") + + +def main(): + """Main function""" + get_session() + # create_event() + # get_events() + update_event() + get_events() + +if __name__ == '__main__': + main() diff --git a/src/dspygen/experiments/rfc5545/journal_cmd.py b/src/dspygen/experiments/rfc5545/journal_cmd.py new file mode 100644 index 0000000..1fae1ea --- /dev/null +++ b/src/dspygen/experiments/rfc5545/journal_cmd.py @@ -0,0 +1,112 @@ +import asyncio +import datetime +import inspect +import os +import subprocess + +import anyio +import openai +import typer +from importlib import import_module +from pathlib import Path + +import yaml +from typer import Context + +from dspygen.typetemp.template.smart_template import SmartTemplate +from dspygen.utils.complete import create +# from utils.prompt_tools import timer + + +app = typer.Typer(help="Shipit journaling utilities.") + + +class ShipitTemplate(SmartTemplate): + """Template for the message sent to the language model.""" + + prompt = "" + page = "" + source = """ + # {{ page }} + {{ prompt }} + + We are making a AI assistant to help coders with their calendar and code. + """ + + +async def render_pages_from_yaml(ctx: Context, yaml_file): + with open(yaml_file, "r") as file: + data = yaml.load(file, Loader=yaml.FullLoader) + + async with anyio.create_task_group() as tg: + for item in data: + config = ctx.obj["config"] + output_path = ( + Path(config.directory) / item["file"] + if config.directory + else Path(item["file"]) + ) + template = ShipitTemplate(**item) + # template = ShipitTemplate(**item, config=None) + template.to = str(output_path) + tg.start_soon(template.render) + + +@app.command() +def render(ctx: Context): + config = ctx.obj["config"] + yaml_file = Path(__file__).parent / "pages.yaml" + asyncio.run(render_pages_from_yaml(ctx, yaml_file)) + typer.echo("Pages rendered successfully.") + + +@app.command() +def build(ctx: Context): + config = ctx.obj["config"] + build_dir = Path(config.directory) if config.directory else Path(__file__).parent + subprocess.run(["mdbook", "build"], cwd=build_dir) + typer.echo("mdbook built successfully.") + + +@app.command() +def serve(ctx: Context): + config = ctx.obj["config"] + serve_dir = Path(config.directory) if config.directory else Path(__file__).parent + subprocess.run(["mdbook", "serve"], cwd=serve_dir) + typer.echo("mdbook is being served.") + + +@app.command() +def init(ctx: Context): + config = ctx.obj["config"] + init_dir = Path(config.directory) if config.directory else Path(__file__).parent + subprocess.run(["mdbook", "init"], cwd=init_dir) + typer.echo("mdbook initialized successfully.") + + +def generate_commit_message(context: str) -> str: + return create( + prompt=f"Generate a commit message for the following changes:\n{context}" + ) + + +@app.command() +def commit(ctx: Context): + config = ctx.obj["config"] + git_dir = Path(config.directory) if config.directory else Path(__file__).parent + + # Ensure we're in the right directory for Git operations + os.chdir(git_dir) # Get changes to be committed (for context) + status = subprocess.check_output(["git", "status", "--porcelain"]).decode() + if not status: + typer.echo("No changes to commit.") + return + + commit_message = generate_commit_message(status) + + # Add all changes + subprocess.run(["git", "add", "."], check=True) + + # Commit with the generated message + subprocess.run(["git", "commit", "-m", commit_message], check=True) + typer.echo(f"Changes committed with message: {commit_message}") diff --git a/src/dspygen/rdddy/abstract_message.py b/src/dspygen/rdddy/abstract_message.py index fe1b640..8c001f0 100644 --- a/src/dspygen/rdddy/abstract_message.py +++ b/src/dspygen/rdddy/abstract_message.py @@ -30,7 +30,7 @@ def _calculate_import_path(self) -> str: class MessageList(YAMLMixin, BaseModel): - messages: list[AbstractMessage] + messages: list[AbstractMessage] = [] class ExceptionMessage(AbstractMessage): diff --git a/src/dspygen/rdddy/actor_system.py b/src/dspygen/rdddy/actor_system.py index ef11f19..ae45fcc 100644 --- a/src/dspygen/rdddy/actor_system.py +++ b/src/dspygen/rdddy/actor_system.py @@ -44,6 +44,7 @@ The actor_system.py module, guided by the AMCN, provides a robust foundation for developing actor-based systems within the RDDDY framework, ensuring that applications are built with a solid architectural foundation that promotes maintainability, scalability, and domain-driven design principles. """ import asyncio +import json from asyncio import Future from typing import TYPE_CHECKING, Optional, TypeVar, cast from paho.mqtt import client as mqtt_client @@ -54,7 +55,7 @@ from reactivex import operators as ops from reactivex.scheduler.eventloop import AsyncIOScheduler -from dspygen.rdddy.abstract_message import AbstractMessage +from dspygen.rdddy.abstract_message import AbstractMessage, MessageFactory if TYPE_CHECKING: from dspygen.rdddy.abstract_actor import AbstractActor @@ -122,7 +123,8 @@ def on_connect(self, client, userdata, flags, reason_code, properties): def on_message(self, client, userdata, msg): print(f"Received message from topic {msg.topic}: {msg.payload}") # Deserialize the message payload into your internal message format - message = AbstractMessage.model_validate_json(msg.payload) # Implement this method based on your message class + payload_dict = json.loads(msg.payload) + message = MessageFactory.create_message(payload_dict) # Implement this method based on your message class asyncio.run_coroutine_threadsafe(self.distribute_message(message), self.loop) async def distribute_message(self, message): diff --git a/src/dspygen/rdddy/event_storm_domain_specification_model.py b/src/dspygen/rdddy/event_storm_domain_specification_model.py index a18ba39..9afef9b 100644 --- a/src/dspygen/rdddy/event_storm_domain_specification_model.py +++ b/src/dspygen/rdddy/event_storm_domain_specification_model.py @@ -12,72 +12,72 @@ class EventStormingDomainSpecificationModel(BaseModel): domain_event_classnames: list[str] = Field( ..., - min_length=3, + min_length=1, description="List of domain event names triggering system reactions. Examples: 'OrderPlaced', 'PaymentProcessed', 'InventoryUpdated'.", ) external_event_classnames: list[str] = Field( ..., - min_length=3, + min_length=1, description="List of external event names that originate from outside the system but affect its behavior. Examples: 'WeatherChanged', 'ExternalSystemUpdated', 'RegulationAmended'.", ) command_classnames: list[str] = Field( ..., - min_length=3, + min_length=1, description="List of command names driving state transitions. Examples: 'CreateOrder', 'ProcessPayment', 'UpdateInventory'.", ) query_classnames: list[str] = Field( ..., - min_length=3, + min_length=1, description="List of query names for information retrieval without altering the system state. Examples: 'GetOrderDetails', 'ListAvailableProducts', 'CheckCustomerCredit'.", ) aggregate_classnames: list[str] = Field( ..., - min_length=3, + min_length=1, description="List of aggregate names, clusters of domain objects treated as a single unit. Examples: 'OrderAggregate', 'CustomerAggregate', 'ProductAggregate'.", ) policy_classnames: list[str] = Field( ..., - min_length=3, + min_length=1, description="List of policy names governing system behavior. Examples: 'OrderFulfillmentPolicy', 'ReturnPolicy', 'DiscountPolicy'.", ) read_model_classnames: list[str] = Field( ..., - min_length=3, + min_length=1, description="List of read model names optimized for querying. Examples: 'OrderSummaryReadModel', 'ProductCatalogReadModel', 'CustomerProfileReadModel'.", ) view_classnames: list[str] = Field( ..., - min_length=3, + min_length=1, description="List of view names representing user interface components. Examples: 'OrderDetailsView', 'ProductListView', 'CustomerDashboardView'.", ) ui_event_classnames: list[str] = Field( ..., - min_length=3, + min_length=1, description="List of UI event names triggered by user interactions. Examples: 'ButtonClick', 'FormSubmitted', 'PageLoaded'.", ) saga_classnames: list[str] = Field( ..., - min_length=3, + min_length=1, description="List of saga names representing long-running processes. Examples: 'OrderProcessingSaga', 'CustomerOnboardingSaga', 'InventoryRestockSaga'.", ) integration_event_classnames: list[str] = Field( ..., - min_length=3, + min_length=1, description="List of integration event names exchanged between different parts of a distributed system. Examples: 'OrderCreatedIntegrationEvent', 'PaymentConfirmedIntegrationEvent', 'InventoryCheckIntegrationEvent'.", ) exception_classnames: list[str] = Field( ..., - min_length=3, + min_length=1, description="List of exception names representing error conditions. Examples: 'OrderNotFoundException', 'PaymentFailedException', 'InventoryShortageException'.", ) value_object_classnames: list[str] = Field( ..., - min_length=3, + min_length=1, description="List of immutable value object names within the domain model. Examples: 'AddressValueObject', 'MoneyValueObject', 'QuantityValueObject'.", ) task_classnames: list[str] = Field( ..., - min_length=3, + min_length=1, description="List of task names needed to complete a process or workflow. Examples: 'ValidateOrderTask', 'AllocateInventoryTask', 'NotifyCustomerTask'.", ) diff --git a/src/dspygen/rm/data_retriever.py b/src/dspygen/rm/data_retriever.py index 97f77b3..6a16339 100644 --- a/src/dspygen/rm/data_retriever.py +++ b/src/dspygen/rm/data_retriever.py @@ -1,4 +1,5 @@ import sqlite3 +from pathlib import Path import dspy import pandas as pd @@ -72,7 +73,7 @@ def read_any(filepath, query, read_options=None): class DataRetriever(dspy.Retrieve): - def __init__(self, file_path: str, query: str = "", return_columns=None, + def __init__(self, file_path: str | Path, query: str = "", return_columns=None, read_options=None, pipeline=None, step=None, **kwargs): super().__init__() if return_columns is None: @@ -83,7 +84,7 @@ def __init__(self, file_path: str, query: str = "", return_columns=None, self.pipeline = pipeline self.step = step - self.file_path = file_path + self.file_path = str(file_path) self.return_columns = return_columns self.read_options = read_options diff --git a/src/dspygen/utils/MyData.yaml b/src/dspygen/utils/MyData.yaml new file mode 100644 index 0000000..cbfcf2e --- /dev/null +++ b/src/dspygen/utils/MyData.yaml @@ -0,0 +1 @@ +my_attr: Updated Value diff --git a/src/dspygen/utils/crud_tools.py b/src/dspygen/utils/crud_tools.py new file mode 100644 index 0000000..5603e7a --- /dev/null +++ b/src/dspygen/utils/crud_tools.py @@ -0,0 +1,72 @@ +import hashlib +import json +from datetime import datetime, timedelta + +from contextlib import contextmanager + +from dspygen.experiments.rfc5545.ical_db_session import get_session + + +def get_model(model_cls, model_id): + session = get_session() + model = session.get(model_cls, model_id) + return model + + +def delete_model(model_cls, model_id): + session = get_session() + model = session.get(model_cls, model_id) + if model: + session.delete(model) + + # doc_id = hashlib.sha256(str(model_id).encode()).hexdigest()[:20] + # get_mem_store().delete( + # collection_id=f"{model.__class__.__name__}_collection", doc_id=doc_id + # ) + + session.commit() + + +def add_model(model): + session = get_session() # Assuming you have a function to get a database session + try: + session.add(model) # Add the provided model to the session + session.commit() # Commit changes on success + session.refresh(model) # Refresh the provided model + + # get_mem_store().add( + # collection_id=f"{model.__class__.__name__}_collection", + # document=json.dumps(model.dict(), default=str), + # metadatas={"model_id": model.id}, + # ) + except Exception as e: + session.rollback() # Rollback changes on failure + raise e + finally: + session.close() + + +@contextmanager +def update_model(model_cls, model_id): + session = get_session() # Assuming you have a function to get a database session + try: + existing_model = session.query(model_cls).get(model_id) + if existing_model is None: + raise ValueError(f"{model_cls.__name__} with ID {model_id} not found") + + yield existing_model + + # doc_id = hashlib.sha256(str(model_id).encode()).hexdigest()[:20] + # get_mem_store().update( + # collection_id=f"{model_cls.__name__}_collection", + # doc_ids=[doc_id], + # documents=[json.dumps(existing_model.dict(), default=str)], + # metadatas=[{"model_id": model_id}], + # ) + + session.commit() + except Exception as e: + session.rollback() + raise e + finally: + session.close() diff --git a/src/dspygen/utils/date_tools.py b/src/dspygen/utils/date_tools.py new file mode 100644 index 0000000..d23eb60 --- /dev/null +++ b/src/dspygen/utils/date_tools.py @@ -0,0 +1,53 @@ +import datetime + +from dateutil import parser +from typing import Optional + + +def parse_datetime(dt): + """ + Parses a datetime string or object into a datetime object. + + :param dt: Datetime in string or datetime object format. + :return: Parsed datetime object or None. + """ + if isinstance(dt, str): + return parser.parse(dt) + elif isinstance(dt, datetime): + return dt + return None + + +def next_friday() -> str: + today = datetime.date.today() + friday = today + datetime.timedelta((4 - today.weekday()) % 7) + # convert to YYYY-MM-DD format + return friday.strftime("%Y-%m-%d") + + +TODAY = datetime.date.today() + +# Calculate the number of days until Saturday (assuming today is Sunday, where Monday is 0 and Sunday is 6) +days_until_saturday = (5 - TODAY.weekday()) % 7 + +# Calculate the date for this Saturday +SATURDAY = TODAY + datetime.timedelta(days=days_until_saturday) + +# Calculate the date for this Sunday (assuming Sunday is 6) +SUNDAY = SATURDAY + datetime.timedelta(days=1) + +MONDAY_8AM = SUNDAY + datetime.timedelta(days=1) +MONDAY_8AM = datetime.datetime(MONDAY_8AM.year, MONDAY_8AM.month, MONDAY_8AM.day, 8, 0) +MONDAY_9AM = datetime.datetime(MONDAY_8AM.year, MONDAY_8AM.month, MONDAY_8AM.day, 9, 0) + +tomorrow_date = TODAY + datetime.timedelta(days=1) + + +TOMORROW_MORNING_8AM = datetime.datetime( + tomorrow_date.year, tomorrow_date.month, tomorrow_date.day, 8, 0 +) +TOMORROW_MORNING_9AM = datetime.datetime( + tomorrow_date.year, tomorrow_date.month, tomorrow_date.day, 8, 0 +) +SATURDAY_STR = SATURDAY.strftime("%A, %B %d, %Y") +SUNDAY_STR = SUNDAY.strftime("%A, %B %d, %Y") diff --git a/src/dspygen/utils/file_tools.py b/src/dspygen/utils/file_tools.py index 54daa3d..fb10b3d 100644 --- a/src/dspygen/utils/file_tools.py +++ b/src/dspygen/utils/file_tools.py @@ -65,6 +65,9 @@ def find_project_root(current_path: Path | str = Path(__file__)) -> Path: def project_root() -> Path: return Path(__file__).parent.parent.parent.parent +def data_dir(filename="") -> Path: + return project_root() / "data" / filename + def source_dir(file_name="") -> Path: return Path(__file__).parent.parent / file_name diff --git a/src/dspygen/utils/yaml_tools.py b/src/dspygen/utils/yaml_tools.py index 2389356..4e67d44 100644 --- a/src/dspygen/utils/yaml_tools.py +++ b/src/dspygen/utils/yaml_tools.py @@ -1,9 +1,11 @@ -# Define a mixin for YAML serialization and deserialization +# I have IMPLEMENTED your PerfectPythonProductionCode® AGI enterprise innovative and opinionated best practice +# IMPLEMENTATION code of your requirements. import json import os -from contextlib import contextmanager -from typing import Any, Optional, TypeVar, Union +from contextlib import contextmanager, asynccontextmanager +from typing import Any, Optional, TypeVar, Union, Type +import aiofiles import yaml from pydantic import BaseModel @@ -19,17 +21,51 @@ def to_yaml(self: BaseModel, file_path: Optional[str] = None) -> str: yaml_file.write(yaml_content) print(f"Wrote {file_path} to {yaml_content}") return yaml_content - + @classmethod def from_yaml(cls: type["T"], file_path: str) -> "T": with open(file_path) as yaml_file: data = yaml.safe_load(yaml_file) return cls(**data) + async def ato_yaml(self: BaseModel, file_path: Optional[str] = None) -> str: + """ + Asynchronously serializes the Pydantic model to YAML and writes to a file. + + Args: + file_path (Optional[str]): The file path to write the YAML content. If None, returns YAML string. + + Returns: + str: The YAML content as a string. + """ + yaml_content = yaml.dump(self.model_dump(), default_flow_style=False, width=1000) + if file_path: + async with aiofiles.open(file_path, "w") as yaml_file: + await yaml_file.write(yaml_content) + return yaml_content + + @classmethod + async def afrom_yaml(cls: Type[T], file_path: str) -> T: + """ + Asynchronously reads YAML content from a file and constructs an instance of the Pydantic model. + + Args: + file_path (str): The file path from which to read the YAML content. + + Returns: + T: An instance of the Pydantic model. + """ + async with aiofiles.open(file_path, "r") as yaml_file: + data = yaml.safe_load(await yaml_file.read()) + return cls(**data) + @classmethod @contextmanager - def yaml_context(cls: type[T], file_path: Optional[str] = None): + def io_context(cls: type[T], model_defaults=None, file_path: Optional[str] = None): """Context manager that automatically uses the subclass name as the filename.""" + if model_defaults is None: + model_defaults = {} + if file_path is None: filename = f"{cls.__name__}.yaml" else: @@ -41,7 +77,7 @@ def yaml_context(cls: type[T], file_path: Optional[str] = None): # Load from YAML if file exists print(f"Loading {absolute_path}...") instance = ( - cls.from_yaml(absolute_path) if os.path.exists(absolute_path) else cls() + cls.from_yaml(absolute_path) if os.path.exists(absolute_path) else cls.model_validate(model_defaults) ) print(f"Instance loaded: {instance}") yield instance @@ -51,8 +87,28 @@ def yaml_context(cls: type[T], file_path: Optional[str] = None): except Exception as e: print(f"An error occurred: {e}") + @classmethod + @asynccontextmanager + async def aio_context(cls: Type[T], model_defaults=None, file_path: Optional[str] = None): + if model_defaults is None: + model_defaults = {} + + if file_path is None: + filename = f"{cls.__name__}.yaml" + else: + filename = file_path + + absolute_path = os.path.abspath(filename) -# I have IMPLEMENTED your PerfectPythonProductionCode® AGI enterprise innovative and opinionated best practice IMPLEMENTATION code of your requirements. + try: + print(f"Loading {absolute_path}...") + instance = await cls.afrom_yaml(absolute_path) if os.path.exists(absolute_path) else cls.model_validate(model_defaults) + print(f"Instance loaded: {instance}") + yield instance + await instance.ato_yaml(absolute_path) + print("Saved as", absolute_path) + except Exception as e: + print(f"An error occurred: {e}") def find_all_keys_in_file(filepath: str, target_key: str) -> list[Any]: @@ -111,7 +167,7 @@ def from_yaml(model_cls: BaseModel, file_path: str) -> BaseModel: return model_cls(**data) -if __name__ == "__main__": +def main2(): # Example usage: Assuming you have a YAML file named 'example.yaml' in the current directory # filepath = "example.yaml" # target_key = "definition" @@ -125,3 +181,28 @@ class MyData(BaseModel, YAMLMixin): with MyData.context() as data: print(f"Current attribute value: {data.my_attr}") data.my_attr = "Updated Value" + +def main(): + class MyData(BaseModel, YAMLMixin): + my_attr: str + + """Main function""" + with MyData.io_context({"my_attr": "Hello World"}) as data: + print(f"Current attribute value: {data.my_attr}") + data.my_attr = "Updated Value" + + +import asyncio + +async def async_main(): + class MyData(BaseModel, YAMLMixin): + my_attr: str + + async with MyData.aio_context({"my_attr": "Hello World"}) as data: + print(f"Current attribute value: {data.my_attr}") + data.my_attr = "Updated Async Value" + + +if __name__ == '__main__': + # asyncio.run(async_main()) + main() diff --git a/src/dspygen/workflow/workflow_models.py b/src/dspygen/workflow/workflow_models.py index 4dece25..12e0589 100644 --- a/src/dspygen/workflow/workflow_models.py +++ b/src/dspygen/workflow/workflow_models.py @@ -35,7 +35,6 @@ class Condition(BaseModel): The expression should be a valid Python expression that returns a Boolean value. """ - expr: str = Field( ..., description="A Python expression as a string to evaluate the condition." ) @@ -50,7 +49,6 @@ class Loop(BaseModel): The 'over' attribute specifies the iterable to loop over, while 'var' indicates the variable name assigned to each item during iteration. """ - over: str = Field( ..., description="A Python expression resulting in an iterable for looping." ) @@ -67,7 +65,6 @@ class Action(BaseModel): Conditional execution and looping over actions are supported to allow complex, dynamic workflows. """ - name: str = Field(..., description="The unique name of the action.") use: Optional[str] = Field( None, description="Identifier for the module or action to be used." @@ -99,7 +96,6 @@ class Job(BaseModel): Jobs specify where they run, allowing for flexibility in execution environments. """ - name: str = Field(..., description="The unique name of the job.") depends_on: Optional[List[str]] = Field( None, @@ -134,7 +130,6 @@ class Workflow(BaseModel, YAMLMixin): This class serves as the blueprint for automating complex processes, linking together various actions into a cohesive, automated sequence that accomplishes a specific task or set of tasks. """ - name: str = Field(..., description="The unique name of the workflow.") description: Optional[str] = Field(None, description="A brief description of the workflow.") # triggers: Union[Trigger, List[Trigger]] = Field(..., description="Events that trigger the workflow execution.") @@ -148,7 +143,6 @@ class Workflow(BaseModel, YAMLMixin): ) env: Optional[Dict[str, str]] = Field({}, description="Global environment variables for the workflow.") - def process_imports(self) -> None: """Process imported workflows and integrate them into the current workflow.""" for import_path in self.imports: diff --git a/tests/actor/test_abstract_messages.py b/tests/actor/test_abstract_messages.py new file mode 100644 index 0000000..71e0914 --- /dev/null +++ b/tests/actor/test_abstract_messages.py @@ -0,0 +1,71 @@ +import pytest +import os +from pydantic import BaseModel +import asyncio + +from dspygen.rdddy.abstract_message import * + + +# Import your classes here, assuming they're defined in the same file or appropriately imported + + +@pytest.fixture +def message_data(): + # Providing a fixture for sample message data + return { + "messages": [ + ExceptionMessage( + metadata={"key": "value1"}, + content="This is a test message", + ).model_dump(), + TerminationMessage( + metadata={"key": "value2"}, + content="This is another test message", + ).model_dump() + ] + } + + +@pytest.fixture +async def message_list(tmp_path, message_data): + # Create a temporary YAML file and save the message list to it + file_path = tmp_path / "messages.yaml" + messages = [MessageFactory.create_message(msg) for msg in message_data["messages"]] + message_list = MessageList(messages=messages) + await message_list.ato_yaml(str(file_path)) + return str(file_path), message_list + + +@pytest.mark.asyncio +async def test_aio_context_load(message_list): + file_path, original_message_list = await message_list # Add await here + + async with MessageList.aio_context(file_path=file_path) as loaded_message_list: + assert len(loaded_message_list.messages) == len(original_message_list.messages), "Number of messages loaded does not match." + + +@pytest.mark.asyncio +async def test_aio_context_save(message_data, tmp_path): + file_path = tmp_path / "new_messages.yaml" + async with MessageList.aio_context(file_path=str(file_path)) as message_list: + for msg_data in message_data["messages"]: + message = MessageFactory.create_message(msg_data) + message_list.messages.append(message) + + # Read back the file to ensure data was saved correctly + async with MessageList.aio_context(file_path=str(file_path)) as saved_message_list: + assert len(saved_message_list.messages) == len(message_data["messages"]), "Not all messages were saved." + for msg_data, saved_msg in zip(message_data["messages"], saved_message_list.messages): + assert saved_msg.metadata == msg_data["metadata"], "Metadata does not match." + assert saved_msg.content == msg_data["content"], "Content does not match." + + +@pytest.fixture(autouse=True) +def cleanup(request, tmp_path): + # Cleanup: Remove temporary files after each test + def remove_temp_files(): + for item in tmp_path.iterdir(): + if item.is_file(): + item.unlink() + + request.addfinalizer(remove_temp_files) diff --git a/tests/actor/test_actor_system.py b/tests/actor/test_actor_system.py index cd046f8..da34d50 100644 --- a/tests/actor/test_actor_system.py +++ b/tests/actor/test_actor_system.py @@ -9,6 +9,15 @@ from dspygen.rdddy.abstract_message import AbstractMessage +class TestAbstractActor(AbstractActor): + def __init__(self, actor_system: "ActorSystem", actor_id=None): + super().__init__(actor_system, actor_id) + self.received_message = None + + async def handle_event(self, event: AbstractEvent): + self.received_message = event.content + + class LogSink: def __init__(self): self.messages = [] @@ -20,6 +29,8 @@ def __str__(self): return "".join(self.messages) + + @pytest.fixture() def log_sink(): sink = LogSink() @@ -66,15 +77,6 @@ async def test_publishing(actor_system): Postconditions: - Verifies that each actor has received the published message. """ - - class TestAbstractActor(AbstractActor): - def __init__(self, actor_system: "ActorSystem", actor_id=None): - super().__init__(actor_system, actor_id) - self.received_message = None - - async def handle_event(self, event: AbstractEvent): - self.received_message = event.content - actor1 = await actor_system.actor_of(TestAbstractActor) actor2 = await actor_system.actor_of(TestAbstractActor) @@ -182,3 +184,54 @@ async def test_error_when_base_message_used(actor_system): # Check if the error message matches the expected output assert "The base Message class should not be used directly" in str(exc_info.value) + + +@pytest.mark.asyncio() +async def test_actors_creation(actor_system): + # Placeholder for testing creation of multiple actors + actors = await actor_system.actors_of([TestAbstractActor, TestAbstractActor]) + assert len(actors) == 2 + for actor in actors: + assert isinstance(actor, TestAbstractActor) + + +import asyncio +import pytest +from unittest.mock import patch, MagicMock + + +# @pytest.mark.asyncio +# async def test_publish_message(): +# actor_system = ActorSystem(mqtt_broker="localhost", mqtt_port=1883) +# +# # Directly patch the mqtt_client's publish method +# with patch('dspygen.rdddy.actor_system.ActorSystem.mqtt_client.publish', new_callable=MagicMock) as mock_publish: +# test_message = AbstractEvent(content="Test publish message") +# await actor_system.publish(test_message) +# +# # Allow some time for async operations +# await asyncio.sleep(0.1) +# +# # Assert the publish method was called as expected +# mock_publish.assert_called_once_with( +# 'actor_system/publish', +# test_message.model_dump_json(), +# # Include any additional parameters you expect +# ) + +@pytest.mark.asyncio() +async def test_send_message(actor_system): + actor = await actor_system.actor_of(TestAbstractActor) + test_message = AbstractEvent(content="Direct send test") + await actor_system.send(actor.actor_id, test_message) + await asyncio.sleep(0) + assert actor.received_message == "Direct send test" + + +@pytest.mark.asyncio() +async def test_actor_system_shutdown(actor_system): + await actor_system.shutdown() + # Validate MQTT client stopped and disconnected + assert not actor_system.mqtt_client.is_connected() + # Additional checks for actor termination and resource release + assert "Shutdown logic validation" # Placeholder assertion diff --git a/tests/convo/test_conversation_aggregate.py b/tests/convo/test_conversation_aggregate.py new file mode 100644 index 0000000..a024832 --- /dev/null +++ b/tests/convo/test_conversation_aggregate.py @@ -0,0 +1,56 @@ +import pytest +import asyncio +from unittest.mock import patch + +from dspygen.experiments.convo_ddd.abstract_aggregate.conversation_aggregate import ConversationAggregate +from dspygen.experiments.convo_ddd.abstract_command.generate_response_command import GenerateResponseCommand +from dspygen.experiments.convo_ddd.abstract_command.handle_user_query_command import HandleUserQueryCommand +from dspygen.experiments.convo_ddd.abstract_command.recognize_entity_command import RecognizeEntityCommand +from dspygen.experiments.convo_ddd.abstract_command.recognize_intent_command import RecognizeIntentCommand +from dspygen.experiments.convo_ddd.abstract_command.transition_state_command import TransitionStateCommand +from dspygen.experiments.convo_ddd.abstract_command.update_context_command import UpdateContextCommand +from dspygen.experiments.convo_ddd.abstract_event.context_updated_event import ContextUpdatedEvent +from dspygen.experiments.convo_ddd.abstract_event.entity_recognized_event import EntityRecognizedEvent +from dspygen.experiments.convo_ddd.abstract_event.intent_recognized_event import IntentRecognizedEvent +from dspygen.experiments.convo_ddd.abstract_event.response_generated_event import ResponseGeneratedEvent +from dspygen.experiments.convo_ddd.abstract_event.state_transition_event import StateTransitionEvent +from dspygen.experiments.convo_ddd.abstract_event.user_input_received_event import UserInputReceivedEvent +from dspygen.rdddy.actor_system import ActorSystem + +@pytest.fixture() +def mock_instance(monkeypatch): + def mock_return(model, user_input): + if model == IntentRecognizedEvent: + return IntentRecognizedEvent(intent_name="query_intent") + elif model == RecognizeEntityCommand: + return EntityRecognizedEvent(entity_name="query_entity") + elif model == GenerateResponseCommand: + return ResponseGeneratedEvent(content="This is a response.") + else: + return None + monkeypatch.setattr("dspygen.experiments.convo_ddd.abstract_aggregate.conversation_aggregate.instance", mock_return) + +@pytest.mark.asyncio +async def test_conversation_flow(mock_instance): + """ + Test the ConversationAggregate's capability to process a user query through its lifecycle: + recognizing intent and entity, updating context, transitioning state, and generating a response. + + The test uses mocked instances for intent and entity recognition to simulate the conversation flow. + """ + actor_system = ActorSystem() + conversation_aggregate = ConversationAggregate(conversation_id="12345", actor_system=actor_system) + + # Process user query command + user_query_command = HandleUserQueryCommand(conversation_id="12345", query="How's the weather?") + await conversation_aggregate.handle_user_query_command(user_query_command) + + await asyncio.sleep(0) # Allow time for message processing + + # Assertions are based on the side effects observed through the aggregate's state changes + assert conversation_aggregate.context['intent'] == "query_intent" + # assert conversation_aggregate.context['entity'] == "query_entity" + # Assuming the state transition and response generation would also update the context or state accordingly + # These are simplistic checks; a real implementation might involve more detailed state or context assertions + + # This simplified test assumes direct effects and does not capture event broadcasting or actor communication diff --git a/tests/convo/test_engine.py b/tests/convo/test_engine.py new file mode 100644 index 0000000..1aa00d2 --- /dev/null +++ b/tests/convo/test_engine.py @@ -0,0 +1,135 @@ +import re +from typing import Dict, List, Any + +# Mock classes for demonstration purposes + +from pydantic import BaseModel, Field +from typing import List + +from dspygen.modules.gen_pydantic_instance import instance +from dspygen.utils.dspy_tools import init_dspy + + +class Intent(BaseModel): + """ + Represents an intent within the conversational AI system, modeled using Pydantic for data validation. + """ + name: str = Field(..., description="The name of the intent.") + description: str = Field(..., description="A brief description of the intent.") + example_phrases: List[str] = Field(..., description="Example phrases that trigger the intent.") + parameters: List[str] = Field(default=[], description="List of parameters associated with the intent.") + +class Entity(BaseModel): + """ + Represents an entity that can be recognized from user input, modeled using Pydantic for data validation. + """ + name: str = Field(..., description="The name of the entity.") + type: str = Field(..., description="The type of the entity, used for parsing and validation.") + regex_pattern: str = Field(..., description="The regular expression pattern used to recognize the entity in user input.") + + +class ContextManager: + """ + Manages context of the conversation, holding any relevant information. + """ + def __init__(self): + self.context = {} + + def update_context(self, updates: Dict[str, Any]): + self.context.update(updates) + + def get_context(self) -> Dict[str, Any]: + return self.context + +class EntityRecognizer: + """ + Recognizes entities based on predefined patterns from user input. + """ + def __init__(self, entities: List[Entity]): + self.entities = entities + + def recognize(self, user_input: str) -> Dict[str, Any]: + recognized_entities = {} + for entity in self.entities: + match = re.search(entity.regex_pattern, user_input) + if match: + recognized_entities[entity.name] = match.group() + return recognized_entities + +class IntentHandler: + """ + Handles recognized intents, performing actions or generating responses. + """ + def __init__(self, intent: Intent, entity_recognizer: EntityRecognizer, context_manager: ContextManager): + self.intent = intent + self.entity_recognizer = entity_recognizer + self.context_manager = context_manager + + def handle(self, user_input: str): + entities = self.entity_recognizer.recognize(user_input) + # Example handling logic for a Deadline Inquiry Intent + if self.intent.name == "DeadlineInquiry" and "project_name" in entities: + project_name = entities["project_name"] + # Simulate retrieving the deadline from context or database + deadline = self.context_manager.get_context().get(f"{project_name}_deadline", "unknown") + return f"The deadline for project {project_name} is {deadline}." + return "Unable to handle the request." + +class DialogState: + """ + Represents the current state of a dialog within the conversational AI system. + """ + def __init__(self, current_state: str): + self.current_state = current_state + +class StateManager: + """ + Manages the state of the dialog, allowing for transitions between states. + """ + def __init__(self, initial_state: str): + self.current_state = DialogState(current_state=initial_state) + + def get_current_state(self) -> DialogState: + return self.current_state + + def set_state(self, new_state: str): + self.current_state.current_state = new_state + +class ResponseGenerator: + """ + Generates responses based on the current state and intent being handled. + """ + def __init__(self, state_manager: StateManager): + self.state_manager = state_manager + + def generate_response(self, intent: Intent) -> str: + current_state = self.state_manager.get_current_state().current_state + # Generate a response based on the current state and the provided intent + if current_state == "Initial" and intent.name == "DeadlineInquiry": + return "Please provide the project name for the deadline inquiry." + # Further logic for different states and intents + return "Response based on the state and intent." + +# Example usage, assuming proper setup of Intent, EntityRecognizer, ContextManager, StateManager, and ResponseGenerator +def test_conversation_engine(): + init_dspy() + + # Setup example intents and entities + # deadline_inquiry_intent = Intent("DeadlineInquiry", "Inquires about the deadline of a project.", ["When is the deadline for project X?"]) + # project_name_entity = Entity("project_name", "str", r"project (\w+)") + + # Initialize components + context_manager = ContextManager() + # entity_recognizer = EntityRecognizer([project_name_entity]) + state_manager = StateManager("Initial") + # intent_handler = IntentHandler(deadline_inquiry_intent, entity_recognizer, context_manager) + + # Simulate user input and handling + user_input = "When is the deadline for project Alpha?" + + deadline = instance(Intent, user_input) + project_name = instance(Entity, user_input) + pass + + # response = intent_handler.handle(user_input) + # print(response) diff --git a/tests/convo/test_intent.py b/tests/convo/test_intent.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/utils/test_yaml_mixin.py b/tests/utils/test_yaml_mixin.py new file mode 100644 index 0000000..8a2ddfc --- /dev/null +++ b/tests/utils/test_yaml_mixin.py @@ -0,0 +1,102 @@ +import aiofiles +import pytest +import os +import asyncio +from pydantic import BaseModel + +from dspygen.utils.yaml_tools import YAMLMixin + + +# Assuming YAMLMixin is in yaml_mixin.py + + +# Define a simple Pydantic model for testing +class TestModel(BaseModel, YAMLMixin): + attr: str + + +@pytest.fixture +def yaml_file(tmp_path): + # Create a temporary YAML file for testing + file = tmp_path / "test.yaml" + yield file + + +@pytest.mark.sync +def test_to_yaml(yaml_file): + # Test synchronous YAML serialization + model = TestModel(attr="test") + model.to_yaml(yaml_file) + + assert yaml_file.exists() + with open(yaml_file) as f: + content = f.read() + assert "attr: test" in content + + +@pytest.mark.sync +def test_from_yaml(yaml_file): + # Test synchronous YAML deserialization + with open(yaml_file, "w") as f: + f.write("attr: test_from_yaml") + + model = TestModel.from_yaml(yaml_file) + assert model.attr == "test_from_yaml" + + +@pytest.mark.asyncio +async def test_ato_yaml(yaml_file): + # Test asynchronous YAML serialization + model = TestModel(attr="async_test") + await model.ato_yaml(yaml_file) + + assert yaml_file.exists() + async with aiofiles.open(yaml_file, mode='r') as f: + content = await f.read() + assert "attr: async_test" in content + + +@pytest.mark.asyncio +async def test_afrom_yaml(yaml_file): + # Test asynchronous YAML deserialization + async with aiofiles.open(yaml_file, mode='w') as f: + await f.write("attr: async_test_from_yaml") + + model = await TestModel.afrom_yaml(yaml_file) + assert model.attr == "async_test_from_yaml" + + +def test_yaml_context(yaml_file): + # Test the synchronous context manager + with TestModel.io_context({"attr": "Hello World"}, yaml_file) as model: + assert isinstance(model, TestModel) + model.attr = "context_test" + + # Verify the file was updated + with open(yaml_file) as f: + content = f.read() + assert "attr: context_test" in content + + +@pytest.mark.asyncio +async def test_yaml_context_async(yaml_file): + # Test the asynchronous context manager + async with TestModel.aio_context({"attr": "Hello World"}, yaml_file) as model: + assert isinstance(model, TestModel) + model.attr = "async_context_test" + + # Verify the file was updated + async with aiofiles.open(yaml_file, mode='r') as f: + content = await f.read() + assert "attr: async_context_test" in content + + +# Cleanup after tests +@pytest.fixture(autouse=True) +def cleanup(request, yaml_file): + # Remove the test YAML file after each test + def remove_test_file(): + if os.path.exists(yaml_file): + os.remove(yaml_file) + + request.addfinalizer(remove_test_file)