From 71c81ea01cf47fd7c9deae53c86c20c13110497a Mon Sep 17 00:00:00 2001 From: Sameer Segal Date: Tue, 18 Jun 2024 10:03:56 +0530 Subject: [PATCH 01/13] Added examples to the README --- README.md | 219 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 216 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 10220a2..b7ad92a 100644 --- a/README.md +++ b/README.md @@ -38,18 +38,231 @@ python3 cli.py --instruction "Greet the user" 2. A complex instruction that uses a plugin ```bash -python3 cli.py --instruction "" --plugin "" +cat plugins.yaml | head -n 10 +payment: | + The name of the plugin is payment. This plugin helps in collecting a payment from the user by generating a payment link. + + The inputs needed are: + - mobile_number (type:str) : The mobile number of the user + - name (type:str) : Name of the user + - CLIENT_ID (type:str) : API secret key for the payment gateway + - CLIENT_SECRET (type:str) : API secret key for the payment gateway + - AMOUNT (type:int) : The amount to be collected from the user + - REASON (type:str) : The reason for the payment + + +python3 cli.py --instruction "Tell the user we are going to help them book an appointment. For this we need to collect Rs 600. Collect the users mobile number and name. Then collect the amount using the payment plugin" --plugin plugins.yaml ``` ```json +{ + "fsm_name": "unnamed_fsm", + "config_vars": [ + { + "name": "CLIENT_ID", + "type": "string", + "description": "API secret key for the payment gateway", + "plugins": [ + "payment" + ] + }, + { + "name": "CLIENT_SECRET", + "type": "string", + "description": "API secret key for the payment gateway", + "plugins": [ + "payment" + ] + } + ], + "variables": [ + { + "name": "mobile_number", + "type": "str", + "validation": "re.match(r'^\\d{10}$', mobile_number) is not None", + "description": "mobile_number should be a 10 digit number" + }, + { + "name": "name", + "type": "str", + "validation": "len(name) > 0 and all(x.isalnum() or x.isspace() for x in name)", + "description": "name should be a non-empty string containing only alphanumeric characters and spaces" + }, + { + "name": "TXN_ID", + "type": "str", + "validation": "len(TXN_ID) > 0", + "description": "TXN_ID should be a non-empty string" + } + ], + "dsl": [ + { + "task_type": "print", + "name": "inform_booking_appointment", + "message": "We are going to help you book an appointment.", + "goto": "collect_mobile_task" + }, + { + "task_type": "input", + "name": "collect_mobile_task", + "message": "Please enter your mobile number:", + "write_variable": "mobile_number", + "datatype": "str", + "goto": "collect_name_task", + "error_goto": "collect_mobile_task" + }, + { + "task_type": "input", + "name": "collect_name_task", + "message": "Please enter your name:", + "write_variable": "name", + "datatype": "str", + "goto": "payment_task", + "error_goto": "collect_name_task" + }, + { + "task_type": "plugin", + "name": "payment_task", + "read_variables": [ + "mobile_number", + "name" + ], + "environment_variables": [ + "CLIENT_ID", + "CLIENT_SECRET" + ], + "write_variables": [ + "TXN_ID" + ], + "plugin": { + "name": "payment", + "inputs": { + "mobile_number": "mobile_number", + "name": "name", + "CLIENT_ID": "CLIENT_ID", + "CLIENT_SECRET": "CLIENT_SECRET", + "AMOUNT": 600, + "REASON": "Appointment Booking Charge" + }, + "outputs": { + "TXN_ID": "TXN_ID" + } + }, + "description": "Collecting a payment of Rs 600 from the user for booking appointment.", + "transitions": [ + { + "code": "SUCCESS", + "goto": "payment_success_print_task", + "description": "The payment was successfully collected." + }, + { + "code": "CANCELLED_BY_USER", + "goto": "payment_cancelled_print_task", + "description": "The payment was cancelled by the user." + }, + { + "code": "EXPIRED", + "goto": "payment_expired_print_task", + "description": "The payment link has expired." + }, + { + "code": "SERVER_DOWNTIME", + "goto": "payment_server_downtime_print_task", + "description": "The server is currently down." + }, + { + "code": "SERVER_ERROR", + "goto": "payment_server_error_print_task", + "description": "There was an error on the server." + } + ] + } + ] +} ``` -3. Edit on an existing DSL +3. Edit on an existing DSL. (We store the above dsl as `sample.json`) ```bash -python3 cli.py --instruction "" --plugin "" +python3 cli.py -i "On success, tell the user that their appointment is booked and they will receive an SMS on their mobile number" -d sample.json --debug ``` ```json +{ + ... + { + "task_type": "plugin", + "name": "payment_task", + "read_variables": [ + "mobile_number", + "name" + ], + "environment_variables": [ + "CLIENT_ID", + "CLIENT_SECRET" + ], + "write_variables": [ + "TXN_ID" + ], + "plugin": { + "name": "payment", + "inputs": { + "mobile_number": "mobile_number", + "name": "name", + "CLIENT_ID": "CLIENT_ID", + "CLIENT_SECRET": "CLIENT_SECRET", + "AMOUNT": 600, + "REASON": "Appointment Booking Charge" + }, + "outputs": { + "TXN_ID": "TXN_ID" + } + }, + "description": "Collecting a payment of Rs 600 from the user for booking an appointment.", + "transitions": [ + { + "code": "SUCCESS", + "goto": "appointment_success_print_task", + "description": "The payment was successfully collected." + }, + { + "code": "CANCELLED_BY_USER", + "goto": "payment_cancelled_print_task", + "description": "The payment was cancelled by the user." + }, + { + "code": "EXPIRED", + "goto": "payment_expired_print_task", + "description": "The payment link has expired." + }, + { + "code": "SERVER_DOWNTIME", + "goto": "payment_server_downtime_print_task", + "description": "The server is currently down." + }, + { + "code": "SERVER_ERROR", + "goto": "payment_server_error_print_task", + "description": "There was an error on the server." + } + ] + }, + { + "task_type": "print", + "name": "appointment_success_print_task", + "message": "Your appointment is booked. You will receive an SMS on your mobile number shortly.", + "goto": null + } + ] +} +``` + +## Developer + +Setup [poetry](https://python-poetry.org/docs/#installation) shell and `.env` file + +```bash +poetry shell +poetry install ``` ## Contributing From 4b8784199824c733ea4e7bf0c34393f453d308fd Mon Sep 17 00:00:00 2001 From: Sameer Segal Date: Wed, 19 Jun 2024 22:06:42 +0530 Subject: [PATCH 02/13] Create python-test.yml --- .github/workflows/python-test.yml | 41 +++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/python-test.yml diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml new file mode 100644 index 0000000..9489d7f --- /dev/null +++ b/.github/workflows/python-test.yml @@ -0,0 +1,41 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python application + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install Poetry + run: | + curl -sSL https://install.python-poetry.org | python3 - + export PATH="$HOME/.local/bin:$PATH" + - name: Install dependencies + run: | + poetry shell + poetry install + - name: Run tests + env: + SECRET_KEY: ${{ secrets.SECRET_KEY }} + DATABASE_URL: ${{ secrets.DATABASE_URL }} + OTHER_SECRET: ${{ secrets.OTHER_SECRET }} + run: | + python3 -m unittest tests/basic.py + From 9466b49b9118892676301f276d543dbb22358419 Mon Sep 17 00:00:00 2001 From: Sameer Segal Date: Wed, 19 Jun 2024 22:09:11 +0530 Subject: [PATCH 03/13] Update python-test.yml --- .github/workflows/python-test.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 9489d7f..5a7fa5a 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -28,14 +28,13 @@ jobs: curl -sSL https://install.python-poetry.org | python3 - export PATH="$HOME/.local/bin:$PATH" - name: Install dependencies - run: | - poetry shell + run: | poetry install - name: Run tests env: - SECRET_KEY: ${{ secrets.SECRET_KEY }} - DATABASE_URL: ${{ secrets.DATABASE_URL }} - OTHER_SECRET: ${{ secrets.OTHER_SECRET }} + AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }} + AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }} + AZURE_OPENAI_API_VERSION: ${{ secrets.AZURE_OPENAI_API_VERSION }} run: | python3 -m unittest tests/basic.py From da683091c677909062fc8dca17d913a9bfe803cf Mon Sep 17 00:00:00 2001 From: Sameer Segal Date: Wed, 19 Jun 2024 22:10:14 +0530 Subject: [PATCH 04/13] Added test cases --- README.md | 4 +- tests/basic.py | 84 ++++++++++++++++++++++++++++++ tests/plugins.yaml | 20 ++++++++ tests/sample.json | 124 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 tests/plugins.yaml create mode 100644 tests/sample.json diff --git a/README.md b/README.md index 523c70b..d049970 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ python3 cli.py --instruction "Greet the user" 2. A complex instruction that uses a plugin ```bash -cat plugins.yaml | head -n 10 +cat tests/plugins.yaml | head -n 10 payment: | The name of the plugin is payment. This plugin helps in collecting a payment from the user by generating a payment link. @@ -51,7 +51,7 @@ payment: | - REASON (type:str) : The reason for the payment -python3 cli.py --instruction "Tell the user we are going to help them book an appointment. For this we need to collect Rs 600. Collect the users mobile number and name. Then collect the amount using the payment plugin" --plugin plugins.yaml +python3 cli.py --instruction "Tell the user we are going to help them book an appointment. For this we need to collect Rs 600. Collect the users mobile number and name. Then collect the amount using the payment plugin" --plugin tests/plugins.yaml ``` ```json diff --git a/tests/basic.py b/tests/basic.py index e69de29..3f5c534 100644 --- a/tests/basic.py +++ b/tests/basic.py @@ -0,0 +1,84 @@ +import os +from unittest import TestCase + +import yaml +from nl2dsl import NL2DSL +from dotenv import load_dotenv +import json +load_dotenv() + + +class TestBasic(TestCase): + + def test_simple_greet(self): + instruction = "Greet the user" + nl2dsl = NL2DSL(utterance=instruction) + nl2dsl.nl2dsl() + + dsl = nl2dsl.dsl + + # print(json.dumps(dsl, indent=2)) + + # Assertion statements + assert isinstance(dsl, dict), "dsl should be a dictionary" + + # Check top-level keys + assert "fsm_name" in dsl, "'fsm_name' key is missing" + assert "config_vars" in dsl, "'config_vars' key is missing" + assert "variables" in dsl, "'variables' key is missing" + assert "dsl" in dsl, "'dsl' key is missing" + + # Check types of top-level values + assert isinstance( + dsl["fsm_name"], str), "'fsm_name' should be a string" + assert isinstance(dsl["config_vars"], + list), "'config_vars' should be a list" + assert isinstance(dsl["variables"], + list), "'variables' should be a list" + assert isinstance(dsl["dsl"], list), "'dsl' should be a list" + + # Check structure of the items in the "dsl" list + for task in dsl["dsl"]: + assert isinstance( + task, dict), "Each item in 'dsl' should be a dictionary" + assert "task_type" in task, "'task_type' key is missing in a task" + assert "name" in task, "'name' key is missing in a task" + assert "goto" in task, "'goto' key is missing in a task" + + assert isinstance(task["task_type"], + str), "'task_type' should be a string" + assert isinstance(task["name"], str), "'name' should be a string" + # 'goto' can be None, so no type assertion for 'goto' + + if task["task_type"] == "print": + assert "message" in task, "'message' key is missing in 'print' task" + assert isinstance( + task["message"], str), "'message' should be a string in 'print' task" + + # Specific task checks (ensure the presence of start, print, and end tasks) + start_task = next( + (task for task in dsl["dsl"] if task["task_type"] == "start"), None) + print_task = next( + (task for task in dsl["dsl"] if task["task_type"] == "print"), None) + end_task = next( + (task for task in dsl["dsl"] if task["task_type"] == "end"), None) + + assert start_task is not None, "No 'start' task found" + assert print_task is not None, "No 'print' task found" + assert end_task is not None, "No 'end' task found" + + # Check 'goto' references + assert start_task["goto"] == print_task['name'], "'start' task should go to 'greet_user_task'" + assert print_task["goto"] == end_task['name'], "'print' task should go to 'end'" + assert end_task["goto"] is None, "'end' task should have 'goto' as None" + + def test_plugin_call(self): + with open(os.path.join(os.path.dirname(__file__), "plugins.yaml"), "r") as f: + plugins = yaml.safe_load(f) + instruction = "Tell the user we are going to help them book an appointment. For this we need to collect Rs 600. Collect the users mobile number and name. Then collect the amount using the payment plugin" + nl2dsl = NL2DSL(utterance=instruction, plugins=plugins) + nl2dsl.nl2dsl() + + dsl = nl2dsl.dsl + + print(json.dumps(dsl, indent=2)) diff --git a/tests/plugins.yaml b/tests/plugins.yaml new file mode 100644 index 0000000..72f88de --- /dev/null +++ b/tests/plugins.yaml @@ -0,0 +1,20 @@ +payment: | + The name of the plugin is payment. This plugin helps in collecting a payment from the user by generating a payment link. + + The inputs needed are: + - mobile_number (type:str) : The mobile number of the user + - name (type:str) : Name of the user + - CLIENT_ID (type:str) : API secret key for the payment gateway + - CLIENT_SECRET (type:str) : API secret key for the payment gateway + - AMOUNT (type:int) : The amount to be collected from the user + - REASON (type:str) : The reason for the payment + + The outputs provided are: + - TXN_ID (type:str) : A unique transaction ID generated for the payment transaction + + Return codes are: + - `SUCCESS`: Indicates that the payment was successfully collected. + - `CANCELLED_BY_USER`: Indicates cancelled by the user. + - `EXPIRED`: Indicates that the payment link has expired. + - `SERVER_DOWNTIME`: Indicates that the server is down. + - `SERVER_ERROR`: Indicates that there was an error in the server. \ No newline at end of file diff --git a/tests/sample.json b/tests/sample.json new file mode 100644 index 0000000..37810c5 --- /dev/null +++ b/tests/sample.json @@ -0,0 +1,124 @@ +{ + "fsm_name": "unnamed_fsm", + "config_vars": [ + { + "name": "CLIENT_ID", + "type": "string", + "description": "API secret key for the payment gateway", + "plugins": [ + "payment" + ] + }, + { + "name": "CLIENT_SECRET", + "type": "string", + "description": "API secret key for the payment gateway", + "plugins": [ + "payment" + ] + } + ], + "variables": [ + { + "name": "mobile_number", + "type": "str", + "validation": "re.match(r'^\\d{10}$', mobile_number) is not None", + "description": "mobile_number should be a 10 digit number" + }, + { + "name": "name", + "type": "str", + "validation": "len(name) > 0 and all(x.isalnum() or x.isspace() for x in name)", + "description": "name should be a non-empty string containing only alphanumeric characters and spaces" + }, + { + "name": "TXN_ID", + "type": "str", + "validation": "len(TXN_ID) > 0", + "description": "TXN_ID should be a non-empty string" + } + ], + "dsl": [ + { + "task_type": "print", + "name": "inform_booking_appointment", + "message": "We are going to help you book an appointment.", + "goto": "collect_mobile_task" + }, + { + "task_type": "input", + "name": "collect_mobile_task", + "message": "Please enter your mobile number:", + "write_variable": "mobile_number", + "datatype": "str", + "goto": "collect_name_task", + "error_goto": "collect_mobile_task" + }, + { + "task_type": "input", + "name": "collect_name_task", + "message": "Please enter your name:", + "write_variable": "name", + "datatype": "str", + "goto": "payment_task", + "error_goto": "collect_name_task" + }, + { + "task_type": "plugin", + "name": "payment_task", + "read_variables": [ + "mobile_number", + "name" + ], + "environment_variables": [ + "CLIENT_ID", + "CLIENT_SECRET" + ], + "write_variables": [ + "TXN_ID" + ], + "plugin": { + "name": "payment", + "inputs": { + "mobile_number": "mobile_number", + "name": "name", + "CLIENT_ID": "CLIENT_ID", + "CLIENT_SECRET": "CLIENT_SECRET", + "AMOUNT": 600, + "REASON": "Appointment Booking Charge" + }, + "outputs": { + "TXN_ID": "TXN_ID" + } + }, + "description": "Collecting a payment of Rs 600 from the user for booking appointment.", + "transitions": [ + { + "code": "SUCCESS", + "goto": "payment_success_print_task", + "description": "The payment was successfully collected." + }, + { + "code": "CANCELLED_BY_USER", + "goto": "payment_cancelled_print_task", + "description": "The payment was cancelled by the user." + }, + { + "code": "EXPIRED", + "goto": "payment_expired_print_task", + "description": "The payment link has expired." + }, + { + "code": "SERVER_DOWNTIME", + "goto": "payment_server_downtime_print_task", + "description": "The server is currently down." + }, + { + "code": "SERVER_ERROR", + "goto": "payment_server_error_print_task", + "description": "There was an error on the server." + } + ] + } + ] +} \ No newline at end of file From 58bfe8d6a4afd86578832523900b18814ea8c728 Mon Sep 17 00:00:00 2001 From: Sameer Segal Date: Wed, 19 Jun 2024 22:13:21 +0530 Subject: [PATCH 05/13] Added testing status badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d049970..f56160e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Test Cases](https://github.com/microsoft/PwR-NL2DSL/actions/workflows/python-test.yml/badge.svg)](https://github.com/microsoft/PwR-NL2DSL/actions/workflows/python-test.yml) + # PWR NL2DSL Robust NL2DSL for workflows. This package converts natural language (NL) instructions into a fixed domain specific language (DSL). From be24c1faef2f8be094cdf170197749375fc41817 Mon Sep 17 00:00:00 2001 From: Sameer Segal Date: Wed, 19 Jun 2024 22:19:19 +0530 Subject: [PATCH 06/13] Updating python reference --- .github/workflows/python-test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 5a7fa5a..35738c1 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -36,5 +36,6 @@ jobs: AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }} AZURE_OPENAI_API_VERSION: ${{ secrets.AZURE_OPENAI_API_VERSION }} run: | - python3 -m unittest tests/basic.py + which python3 + poetry run python3 -m unittest tests/basic.py From 64808835e7a29607025a42c085edd35abfe7cb89 Mon Sep 17 00:00:00 2001 From: Sameer Segal Date: Wed, 19 Jun 2024 22:24:14 +0530 Subject: [PATCH 07/13] Retrying pytests --- .github/workflows/python-test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 35738c1..e870bcb 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -36,6 +36,5 @@ jobs: AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }} AZURE_OPENAI_API_VERSION: ${{ secrets.AZURE_OPENAI_API_VERSION }} run: | - which python3 poetry run python3 -m unittest tests/basic.py From 538c274402ee7787c30431522e60b409c93cba88 Mon Sep 17 00:00:00 2001 From: Sameer Segal Date: Wed, 19 Jun 2024 22:57:38 +0530 Subject: [PATCH 08/13] Added more assertions for plugin task --- tests/basic.py | 118 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/tests/basic.py b/tests/basic.py index 3f5c534..0a42223 100644 --- a/tests/basic.py +++ b/tests/basic.py @@ -5,6 +5,7 @@ from nl2dsl import NL2DSL from dotenv import load_dotenv import json + load_dotenv() @@ -82,3 +83,120 @@ def test_plugin_call(self): dsl = nl2dsl.dsl print(json.dumps(dsl, indent=2)) + + # Assertion statements + assert isinstance(dsl, dict), "dsl should be a dictionary" + + # Check top-level keys + assert "fsm_name" in dsl, "'fsm_name' key is missing" + assert "config_vars" in dsl, "'config_vars' key is missing" + assert "variables" in dsl, "'variables' key is missing" + assert "dsl" in dsl, "'dsl' key is missing" + + # Check types of top-level values + assert isinstance(dsl["fsm_name"], str), "'fsm_name' should be a string" + assert isinstance(dsl["config_vars"], list), "'config_vars' should be a list" + assert isinstance(dsl["variables"], list), "'variables' should be a list" + assert isinstance(dsl["dsl"], list), "'dsl' should be a list" + + # Check structure of the items in the "config_vars" list + for var in dsl["config_vars"]: + assert isinstance(var, dict), "Each item in 'config_vars' should be a dictionary" + assert "name" in var, "'name' key is missing in a config_var" + assert "type" in var, "'type' key is missing in a config_var" + assert "description" in var, "'description' key is missing in a config_var" + assert "plugins" in var, "'plugins' key is missing in a config_var" + + assert isinstance(var["name"], str), "'name' should be a string in config_var" + assert isinstance(var["type"], str), "'type' should be a string in config_var" + assert isinstance(var["description"], str), "'description' should be a string in config_var" + assert isinstance(var["plugins"], list), "'plugins' should be a list in config_var" + + # Check structure of the items in the "variables" list + for var in dsl["variables"]: + assert isinstance(var, dict), "Each item in 'variables' should be a dictionary" + assert "name" in var, "'name' key is missing in a variable" + assert "type" in var, "'type' key is missing in a variable" + assert "validation" in var, "'validation' key is missing in a variable" + assert "description" in var, "'description' key is missing in a variable" + + assert isinstance(var["name"], str), "'name' should be a string in variable" + assert isinstance(var["type"], str), "'type' should be a string in variable" + assert isinstance(var["validation"], str), "'validation' should be a string in variable" + assert isinstance(var["description"], str), "'description' should be a string in variable" + + # Check structure of the items in the "dsl" list + for task in dsl["dsl"]: + assert isinstance(task, dict), "Each item in 'dsl' should be a dictionary" + assert "task_type" in task, "'task_type' key is missing in a task" + assert "name" in task, "'name' key is missing in a task" + assert "goto" in task, "'goto' key is missing in a task" + + assert isinstance(task["task_type"], str), "'task_type' should be a string in task" + assert isinstance(task["name"], str), "'name' should be a string in task" + # 'goto' can be None, so no type assertion for 'goto' + + if task["task_type"] == "print": + assert "message" in task, "'message' key is missing in 'print' task" + assert isinstance(task["message"], str), "'message' should be a string in 'print' task" + + if task["task_type"] == "input": + assert "message" in task, "'message' key is missing in 'input' task" + assert "write_variable" in task, "'write_variable' key is missing in 'input' task" + assert "datatype" in task, "'datatype' key is missing in 'input' task" + assert "error_goto" in task, "'error_goto' key is missing in 'input' task" + + assert isinstance(task["message"], str), "'message' should be a string in 'input' task" + assert isinstance(task["write_variable"], str), "'write_variable' should be a string in 'input' task" + assert isinstance(task["datatype"], str), "'datatype' should be a string in 'input' task" + assert isinstance(task["error_goto"], str), "'error_goto' should be a string in 'input' task" + + if task["task_type"] == "plugin": + assert "read_variables" in task, "'read_variables' key is missing in 'plugin' task" + assert "environment_variables" in task, "'environment_variables' key is missing in 'plugin' task" + assert "write_variables" in task, "'write_variables' key is missing in 'plugin' task" + assert "plugin" in task, "'plugin' key is missing in 'plugin' task" + assert "description" in task, "'description' key is missing in 'plugin' task" + assert "transitions" in task, "'transitions' key is missing in 'plugin' task" + + assert isinstance(task["read_variables"], list), "'read_variables' should be a list in 'plugin' task" + assert isinstance(task["environment_variables"], list), "'environment_variables' should be a list in 'plugin' task" + assert isinstance(task["write_variables"], list), "'write_variables' should be a list in 'plugin' task" + assert isinstance(task["plugin"], dict), "'plugin' should be a dictionary in 'plugin' task" + assert isinstance(task["description"], str), "'description' should be a string in 'plugin' task" + assert isinstance(task["transitions"], list), "'transitions' should be a list in 'plugin' task" + + # Check structure of the plugin + plugin = task["plugin"] + assert "name" in plugin, "'name' key is missing in 'plugin'" + assert "inputs" in plugin, "'inputs' key is missing in 'plugin'" + assert "outputs" in plugin, "'outputs' key is missing in 'plugin'" + + assert isinstance(plugin["name"], str), "'name' should be a string in 'plugin'" + assert isinstance(plugin["inputs"], dict), "'inputs' should be a dictionary in 'plugin'" + assert isinstance(plugin["outputs"], dict), "'outputs' should be a dictionary in 'plugin'" + + # Check structure of the transitions + for transition in task["transitions"]: + assert isinstance(transition, dict), "Each item in 'transitions' should be a dictionary" + assert "code" in transition, "'code' key is missing in a transition" + assert "goto" in transition, "'goto' key is missing in a transition" + assert "description" in transition, "'description' key is missing in a transition" + + assert isinstance(transition["code"], str), "'code' should be a string in transition" + assert isinstance(transition["goto"], (str, type(None))), "'goto' should be a string or None in transition" + assert isinstance(transition["description"], str), "'description' should be a string in transition" + + # Specific task checks (ensure the presence of start, print, input, plugin, and end tasks) + start_task = next((task for task in dsl["dsl"] if task["task_type"] == "start"), None) + print_task = next((task for task in dsl["dsl"] if task["task_type"] == "print"), None) + input_tasks = [task for task in dsl["dsl"] if task["task_type"] == "input"] + plugin_task = next((task for task in dsl["dsl"] if task["task_type"] == "plugin"), None) + end_task = next((task for task in dsl["dsl"] if task["task_type"] == "end"), None) + + assert start_task is not None, "No 'start' task found" + assert print_task is not None, "No 'print' task found" + assert len(input_tasks) > 0, "No 'input' tasks found" + assert plugin_task is not None, "No 'plugin' task found" + assert end_task is not None, "No 'end' task found" + From 1613e9509b038178cda63b9b54cd8d6fcded4909 Mon Sep 17 00:00:00 2001 From: Sameer Segal Date: Wed, 19 Jun 2024 23:04:47 +0530 Subject: [PATCH 09/13] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f56160e..17ffc9c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # PWR NL2DSL -Robust NL2DSL for workflows. This package converts natural language (NL) instructions into a fixed domain specific language (DSL). +Robust NL2DSL for workflows. This package converts natural language (NL) instructions into a fixed domain specific language (DSL). This is a core library for [PwR Studio](https://github.com/microsoft/PwR-Studio) ## DSL From 31c8491f1decdd36d64a2aeb046ea7716735693a Mon Sep 17 00:00:00 2001 From: Sameer Segal Date: Wed, 19 Jun 2024 23:10:27 +0530 Subject: [PATCH 10/13] Testing env are set correctly or not --- .github/workflows/python-test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index e870bcb..92175a3 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -36,5 +36,6 @@ jobs: AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }} AZURE_OPENAI_API_VERSION: ${{ secrets.AZURE_OPENAI_API_VERSION }} run: | - poetry run python3 -m unittest tests/basic.py + poetry run python3 -c 'import os;print(os.environ)' + #poetry run python3 -m unittest tests/basic.py From e21d56416ca40a1307b803e53624b565e1cbe957 Mon Sep 17 00:00:00 2001 From: Sameer Segal Date: Wed, 19 Jun 2024 23:22:02 +0530 Subject: [PATCH 11/13] Testing workflow again --- .github/workflows/python-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 92175a3..8d3f5d1 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -37,5 +37,5 @@ jobs: AZURE_OPENAI_API_VERSION: ${{ secrets.AZURE_OPENAI_API_VERSION }} run: | poetry run python3 -c 'import os;print(os.environ)' - #poetry run python3 -m unittest tests/basic.py + poetry run python3 -m unittest tests/basic.py -k test_simple_greet From 3c307aa84044c2a2d178ccb592e6b098d905a6fb Mon Sep 17 00:00:00 2001 From: Sameer Segal Date: Wed, 19 Jun 2024 23:24:12 +0530 Subject: [PATCH 12/13] Running the complete testset --- .github/workflows/python-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 8d3f5d1..76adf3b 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -36,6 +36,6 @@ jobs: AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }} AZURE_OPENAI_API_VERSION: ${{ secrets.AZURE_OPENAI_API_VERSION }} run: | - poetry run python3 -c 'import os;print(os.environ)' - poetry run python3 -m unittest tests/basic.py -k test_simple_greet + #poetry run python3 -c 'import os;print(os.environ)' + poetry run python3 -m unittest tests/basic.py From 1a2de513635c8a057fe654b155c7d3c58f52a147 Mon Sep 17 00:00:00 2001 From: Sameer Segal Date: Wed, 19 Jun 2024 23:30:59 +0530 Subject: [PATCH 13/13] Added env.template --- README.md | 4 ++-- env.template | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 env.template diff --git a/README.md b/README.md index 17ffc9c..6edcda4 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ python3 cli.py --instruction "Tell the user we are going to help them book an ap 3. Edit on an existing DSL. (We store the above dsl as `sample.json`) ```bash -python3 cli.py -i "On success, tell the user that their appointment is booked and they will receive an SMS on their mobile number" -d sample.json --debug +python3 cli.py -i "On success, tell the user that their appointment is booked and they will receive an SMS on their mobile number" -d tests/sample.json --debug ``` ```json @@ -260,7 +260,7 @@ python3 cli.py -i "On success, tell the user that their appointment is booked an ## Developer -Setup [poetry](https://python-poetry.org/docs/#installation) shell and `.env` file +Setup [poetry](https://python-poetry.org/docs/#installation) shell and `.env` file (copy from [env.template](env.template)) ```bash poetry shell diff --git a/env.template b/env.template new file mode 100644 index 0000000..f0c2f88 --- /dev/null +++ b/env.template @@ -0,0 +1,5 @@ +OPENAI_API_KEY= + +#AZURE_OPENAI_API_KEY= +#AZURE_OPENAI_ENDPOINT= +#AZURE_OPENAI_API_VERSION=