From 109337034c8fedb9a842dae21708240ccb23e565 Mon Sep 17 00:00:00 2001 From: pedroCollogno Date: Sat, 30 Mar 2024 11:44:47 -0700 Subject: [PATCH 1/3] Packaging: Add Poetry, add Pytest, add CI --- .github/workflows/check_code.yaml | 50 +++++++++++++++ poetry.lock | 101 ++++++++++++++++++++++++++++++ pyproject.toml | 35 +++++++++++ test_gh_helper.py | 17 ----- test_trello_helper.py | 42 ------------- tst/test_gh_helper.py | 7 +++ tst/test_trello_helper.py | 8 +++ 7 files changed, 201 insertions(+), 59 deletions(-) create mode 100644 .github/workflows/check_code.yaml create mode 100644 poetry.lock create mode 100644 pyproject.toml delete mode 100644 test_gh_helper.py delete mode 100644 test_trello_helper.py create mode 100644 tst/test_gh_helper.py create mode 100644 tst/test_trello_helper.py diff --git a/.github/workflows/check_code.yaml b/.github/workflows/check_code.yaml new file mode 100644 index 0000000..99a10b4 --- /dev/null +++ b/.github/workflows/check_code.yaml @@ -0,0 +1,50 @@ +name: Test and Code Quality +on: [push] + +jobs: + test: + strategy: + fail-fast: false + matrix: + python-version: [3.12] + poetry-version: [1.8.2] + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Run image + uses: abatilo/actions-poetry@v2.0.0 + with: + poetry-version: ${{ matrix.poetry-version }} + - name: Install dependencies + run: poetry install + - name: Run tests + run: python -m pytest + # TODO: set up Poetry's dependencies and use it with PyTest + # run: poetry run pytest + code-quality: + strategy: + fail-fast: false + matrix: + python-version: [3.12] + poetry-version: [1.8.2] + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Run image + uses: abatilo/actions-poetry@v2.0.0 + with: + poetry-version: ${{ matrix.poetry-version }} + - name: Install dependencies + run: poetry install + - name: Run black + run: poetry run black . --check + - name: Run safety + run: poetry run safety check \ No newline at end of file diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..cbe37fb --- /dev/null +++ b/poetry.lock @@ -0,0 +1,101 @@ +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.0" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "packaging" +version = "24.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, +] + +[[package]] +name = "pluggy" +version = "1.4.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pytest" +version = "8.1.1" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"}, + {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.4,<2.0" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.8" +content-hash = "55b021be2012579f54723b3af20ecb545b23bef98676f17674e65686b108bf56" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3767458 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,35 @@ +[tool.poetry] +name = "open-architect" +version = "0.0.2" + +description = "Your automated SWE fleet to get your tickets from the Backlog to Prod!" + +authors = [ + "Aiswarya Sankar ", + "Axel Peytavin ", + "Pierre Collignon ", + "Yuma Tanaka <>" +] + +license = "GPL-3.0+" + +readme = "README.md" + +repository = "https://github.com/OpenArchitectAI/open-architect" + +classifiers = [ + "Programming Language :: Python :: 3", + "Operating System :: OS Independent" +] + +packages = [ + { include = "src" } +] + +[tool.poetry.dependencies] +python = ">=3.8" +pytest = ">=8.0.0" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" \ No newline at end of file diff --git a/test_gh_helper.py b/test_gh_helper.py deleted file mode 100644 index 74b4b97..0000000 --- a/test_gh_helper.py +++ /dev/null @@ -1,17 +0,0 @@ -from dotenv import load_dotenv -import os - -from gh_helper import GHHelper - -load_dotenv() - -gh_repo = os.getenv("GITHUB_REPO_URL") -gh_api_token = os.getenv("GITHUB_TOKEN") -if gh_repo is None or gh_api_token is None: - print("Please run the start.py script to set up the environment variables") - -gh = GHHelper(gh_api_token, gh_repo) - -gh.list_open_prs() - -print(gh.get_entire_codebase()) diff --git a/test_trello_helper.py b/test_trello_helper.py deleted file mode 100644 index 7904aeb..0000000 --- a/test_trello_helper.py +++ /dev/null @@ -1,42 +0,0 @@ -from dotenv import load_dotenv -import os - -from src.models import Ticket -from src.helpers.trello import TrelloHelper - -load_dotenv() - -trello_api_key = os.getenv("TRELLO_API_KEY") -trello_token = os.getenv("TRELLO_TOKEN") -trello_board_id = os.getenv("TRELLO_BOARD_ID") - -if trello_api_key is None or trello_token is None or trello_board_id is None: - print("Please run the start.py script to set up the environment variables") - -trello_helper = TrelloHelper(trello_api_key, trello_token, trello_board_id) - -## Step 0: Create an intern -new_intern = trello_helper.create_intern("Test Intern") - -## Step 1: Get the available "candidates" (interns) from Trello -interns = trello_helper.get_intern_list() -print("ok" if interns[-1] else "not ok") - -## Step 2: Create a ticket and assign it to an intern -new_ticket = Ticket( - title="Test Ticket", - description="This is a test ticket", - assignee_id=new_intern, -) -created_ticket = trello_helper.push_tickets_to_backlog_and_assign([new_ticket])[-1] -print("ok" if created_ticket.title == new_ticket.title else "not ok") -trello_helper.move_to_todo(trello_helper.get_last_ticket().id) - -## Step 3: Get the tickets from the To Do list -tickets = trello_helper.get_tickets_todo_list() -print("ok" if tickets[-1] else "not ok") - -## Step 4: Cleanup -trello_helper.delete_ticket(tickets[-1].id) -trello_helper.fire_intern(new_intern) -print("Done!") diff --git a/tst/test_gh_helper.py b/tst/test_gh_helper.py new file mode 100644 index 0000000..3e90b37 --- /dev/null +++ b/tst/test_gh_helper.py @@ -0,0 +1,7 @@ +from src.helpers.github import GHHelper + +class TestGHHelper: + # TODO: Mock out the GitHub API calls, write proper tests + def test_create(self): + gh_helper = GHHelper("token", "repo") + assert gh_helper is not None \ No newline at end of file diff --git a/tst/test_trello_helper.py b/tst/test_trello_helper.py new file mode 100644 index 0000000..254cef4 --- /dev/null +++ b/tst/test_trello_helper.py @@ -0,0 +1,8 @@ +from src.helpers.trello import TrelloHelper + +class TestTrelloHelper: + # TODO: Mock out the Trello API calls, write proper tests + def test_create(self): + # trello_helper = TrelloHelper("key", "token", "board_id") + # assert trello_helper is not None + assert True \ No newline at end of file From 20d6775c4d4c86daf160ad1ee4f901e0151cf04b Mon Sep 17 00:00:00 2001 From: pedroCollogno Date: Sat, 30 Mar 2024 11:54:01 -0700 Subject: [PATCH 2/3] CI: Add Black, reformat stuff --- .github/workflows/check_code.yaml | 4 +- init_connections.py | 28 +++++--- poetry.lock | 110 ++++++++++++++++++++++++++++- pyproject.toml | 3 + requirements.txt | 31 ++++++++ src/agents/architect/__init__.py | 86 ++++++++++++++-------- src/agents/intern/__init__.py | 2 +- src/helpers/trello.py | 22 +++++- src/lib/mistral_chat_completion.py | 2 + src/lib/mistral_dspy.py | 2 + src/lib/ported_clients.py | 39 +++++++--- src/lib/ported_exceptions.py | 2 + start_architecting.py | 2 +- tst/test_gh_helper.py | 3 +- tst/test_trello_helper.py | 3 +- 15 files changed, 279 insertions(+), 60 deletions(-) diff --git a/.github/workflows/check_code.yaml b/.github/workflows/check_code.yaml index 99a10b4..2067837 100644 --- a/.github/workflows/check_code.yaml +++ b/.github/workflows/check_code.yaml @@ -45,6 +45,4 @@ jobs: - name: Install dependencies run: poetry install - name: Run black - run: poetry run black . --check - - name: Run safety - run: poetry run safety check \ No newline at end of file + run: poetry run black . --check \ No newline at end of file diff --git a/init_connections.py b/init_connections.py index 00f3705..8e5cc8e 100644 --- a/init_connections.py +++ b/init_connections.py @@ -17,22 +17,34 @@ if gh_repo is None: gh_repo = input("Enter the GitHub repo URL: ") if gh_api_token_intern is None: - gh_api_token_intern = input("Enter your GitHub Personnal Access token for the intern (https://github.com/settings/tokens): ") + gh_api_token_intern = input( + "Enter your GitHub Personnal Access token for the intern (https://github.com/settings/tokens): " + ) if gh_api_token_reviewer is None: - gh_api_token_reviewer = input("Enter your GitHub Personnal Access token for the reviewer (https://github.com/settings/tokens): ") + gh_api_token_reviewer = input( + "Enter your GitHub Personnal Access token for the reviewer (https://github.com/settings/tokens): " + ) if trello_api_key is None: - trello_api_key = input("Enter your Trello API key (https://trello.com/power-ups/admin): ") + trello_api_key = input( + "Enter your Trello API key (https://trello.com/power-ups/admin): " + ) if trello_api_secret is None: - trello_api_secret = input("Enter your Trello API secret (https://trello.com/power-ups/admin): ") + trello_api_secret = input( + "Enter your Trello API secret (https://trello.com/power-ups/admin): " + ) if trello_token is None: - auth_token = create_oauth_token(key=trello_api_key, secret=trello_api_secret, name='Trello API') - trello_token = auth_token['oauth_token'] + auth_token = create_oauth_token( + key=trello_api_key, secret=trello_api_secret, name="Trello API" + ) + trello_token = auth_token["oauth_token"] if trello_board_id is None: trello_board_id = input("Enter your Trello Board ID: ") if openai_api_key is None: - openai_api_key = input("Enter your OpenAI API key (https://platform.openai.com/api-keys): ") + openai_api_key = input( + "Enter your OpenAI API key (https://platform.openai.com/api-keys): " + ) # Write them back to the .env file @@ -46,4 +58,4 @@ f.write(f"TRELLO_TOKEN={trello_token}\n") f.write(f"TRELLO_BOARD_ID={trello_board_id}\n") -print("Environment variables set up successfully") \ No newline at end of file +print("Environment variables set up successfully") diff --git a/poetry.lock b/poetry.lock index cbe37fb..cd0daf9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,65 @@ # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +[[package]] +name = "black" +version = "24.3.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}, + {file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}, + {file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"}, + {file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"}, + {file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"}, + {file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"}, + {file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"}, + {file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"}, + {file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"}, + {file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"}, + {file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"}, + {file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"}, + {file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"}, + {file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"}, + {file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"}, + {file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"}, + {file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"}, + {file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"}, + {file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"}, + {file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"}, + {file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"}, + {file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + [[package]] name = "colorama" version = "0.4.6" @@ -36,6 +96,17 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "packaging" version = "24.0" @@ -47,6 +118,32 @@ files = [ {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.2.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] + [[package]] name = "pluggy" version = "1.4.0" @@ -95,7 +192,18 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "typing-extensions" +version = "4.10.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, +] + [metadata] lock-version = "2.0" python-versions = ">=3.8" -content-hash = "55b021be2012579f54723b3af20ecb545b23bef98676f17674e65686b108bf56" +content-hash = "e3c0c8655c80c5d434c12d58334a71906566602755abe2dcc7941b5eaeaa498f" diff --git a/pyproject.toml b/pyproject.toml index 3767458..e001dd7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,9 @@ packages = [ python = ">=3.8" pytest = ">=8.0.0" +[tool.poetry.group.dev.dependencies] +black = "^24.3.0" + [build-system] requires = ["hatchling"] build-backend = "hatchling.build" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index c0aaf49..6c88d76 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,18 +7,25 @@ anyio==4.3.0 attrs==23.2.0 backoff==2.2.1 blinker==1.7.0 +build==1.2.1 +CacheControl==0.14.0 cachetools==5.3.3 certifi==2024.2.2 cffi==1.16.0 charset-normalizer==3.3.2 +cleo==2.1.0 click==8.1.7 colorlog==6.8.2 +crashtest==0.4.1 cryptography==42.0.5 datasets==2.14.7 Deprecated==1.2.14 dill==0.3.7 +distlib==0.3.8 distro==1.9.0 dspy-ai==2.4.0 +dulwich==0.21.7 +fastjsonschema==2.19.1 filelock==3.13.3 frozenlist==1.4.1 fsspec==2023.10.0 @@ -29,14 +36,20 @@ httpcore==1.0.5 httpx==0.27.0 huggingface-hub==0.22.1 idna==3.6 +iniconfig==2.0.0 +installer==0.7.0 +jaraco.classes==3.3.1 Jinja2==3.1.3 joblib==1.3.2 jsonschema==4.21.1 jsonschema-specifications==2023.12.1 +keyring==24.3.1 Mako==1.3.2 markdown-it-py==3.0.0 MarkupSafe==2.1.5 mdurl==0.1.2 +more-itertools==10.2.0 +msgpack==1.0.8 multidict==6.0.5 multiprocess==0.70.15 numpy==1.26.4 @@ -46,8 +59,16 @@ optuna==3.6.0 orjson==3.10.0 packaging==23.2 pandas==2.2.1 +pexpect==4.9.0 pillow==10.2.0 +pkginfo==1.10.0 +platformdirs==4.2.0 +pluggy==1.4.0 +poetry==1.8.2 +poetry-core==1.9.0 +poetry-plugin-export==1.7.1 protobuf==4.25.3 +ptyprocess==0.7.0 py-trello==0.19.0 pyarrow==15.0.2 pyarrow-hotfix==0.6 @@ -59,16 +80,22 @@ PyGithub==2.3.0 Pygments==2.17.2 PyJWT==2.8.0 PyNaCl==1.5.0 +pyproject_hooks==1.0.0 +pytest==8.1.1 +pytest-mock==3.14.0 python-dateutil==2.9.0.post0 python-dotenv==1.0.1 pytz==2024.1 PyYAML==6.0.1 +rapidfuzz==3.7.0 referencing==0.34.0 regex==2023.12.25 requests==2.31.0 requests-oauthlib==2.0.0 +requests-toolbelt==1.0.0 rich==13.7.1 rpds-py==0.18.0 +shellingham==1.5.4 six==1.16.0 smmap==5.0.1 sniffio==1.3.1 @@ -76,13 +103,17 @@ SQLAlchemy==2.0.29 streamlit==1.32.2 tenacity==8.2.3 toml==0.10.2 +tomlkit==0.12.4 toolz==0.12.1 tornado==6.4 tqdm==4.66.2 +trove-classifiers==2024.3.25 typing_extensions==4.10.0 tzdata==2024.1 ujson==5.9.0 urllib3==2.2.1 +virtualenv==20.25.1 wrapt==1.16.0 +xattr==1.1.0 xxhash==3.4.1 yarl==1.9.4 diff --git a/src/agents/architect/__init__.py b/src/agents/architect/__init__.py index bd24292..9953113 100644 --- a/src/agents/architect/__init__.py +++ b/src/agents/architect/__init__.py @@ -15,28 +15,35 @@ class ArchitectAgentRequest(BaseModel): question: str history: Any + class CreateTicketsRequest(BaseModel): question: str history: Any + class CreateSubtasksRequest(BaseModel): question: str history: Any + class AskFollowupQuestionsRequest(BaseModel): - question: str + question: str history: Any + class ReferenceExistingCodeRequest(BaseModel): question: str history: Any + class Architect: def __init__(self, name, gh_helper: GHHelper, trello_helper: TrelloHelper): self.name = name self.gh_helper = gh_helper self.trello_helper = trello_helper - self.log_name = colorize(f"[{self.name} the Architect]", bold=True, color="green") + self.log_name = colorize( + f"[{self.name} the Architect]", bold=True, color="green" + ) print( f"{self.log_name} Nice to meet you, I'm {self.name} the Architect! I'm here to help you break down your tasks into smaller tickets and create them for you! 🏗️🔨📝" ) @@ -47,7 +54,14 @@ def run(self): if "messages" not in st.session_state: st.session_state.messages = [] - st.session_state.messages.append({"role": "assistant", "content": "Hey! What new features would you like to add to your project " + str(self.gh_helper.repo.full_name) + " today? I'll help you break it down to subtasks, figure out how to integrate with your existing code and then set my crew of SWE agents to get it built out for you!"}) + st.session_state.messages.append( + { + "role": "assistant", + "content": "Hey! What new features would you like to add to your project " + + str(self.gh_helper.repo.full_name) + + " today? I'll help you break it down to subtasks, figure out how to integrate with your existing code and then set my crew of SWE agents to get it built out for you!", + } + ) for message in st.session_state.messages: with st.chat_message(message["role"]): @@ -61,10 +75,7 @@ def run(self): with st.chat_message("assistant"): architectureAgentReq = ArchitectAgentRequest( question=prompt, - history=[ - msg["content"] - for msg in st.session_state.messages - ], + history=[msg["content"] for msg in st.session_state.messages], ) response = self.compute_response(architectureAgentReq) res = st.write(response) @@ -76,7 +87,9 @@ def compute_response(self, architectAgentRequest: ArchitectAgentRequest): Routing logic for all tools supported by the feedback agent. """ messages = [ - {"role": "system", "content": f"""You are a principal software engineer who is responsible for mentoring engineers and breaking down tasks into smaller tickets. + { + "role": "system", + "content": f"""You are a principal software engineer who is responsible for mentoring engineers and breaking down tasks into smaller tickets. Reference the existing codebase to determine how to build the features in the existing code. @@ -92,8 +105,13 @@ def compute_response(self, architectAgentRequest: ArchitectAgentRequest): - if there are references to their existing code, create subtasks - if there are subtasks, ask to create tasks - """}, - {"role": "user", "content": f"address the user's question: {architectAgentRequest.question}"}] + """, + }, + { + "role": "user", + "content": f"address the user's question: {architectAgentRequest.question}", + }, + ] tools = [ # { @@ -126,7 +144,7 @@ def compute_response(self, architectAgentRequest: ArchitectAgentRequest): "name": "reference_existing_code", "description": "If the user asks to implement in their codebase. Go through the existing code in order to better understand how to build the requested user feature in the codebase. Analyze the code files, and determine the best way to build out support for the new features in the existing code. ", "parameters": {"type": "object", "properties": {}, "required": []}, - } + }, }, ] @@ -156,7 +174,7 @@ def compute_response(self, architectAgentRequest: ArchitectAgentRequest): "reference_existing_code": ReferenceExistingCodeRequest( question=architectAgentRequest.question, history=architectAgentRequest.history, - ) + ), } if tool_calls: @@ -175,14 +193,15 @@ def compute_response(self, architectAgentRequest: ArchitectAgentRequest): function_args = function_request_mapping[function_name] print("Function args are: " + str(function_args)) return function_to_call(function_args) - + print("returning message: " + str(response_message.content)) return response_message.content - - def ask_followup_questions(self, askFollowupQuestionsRequest: AskFollowupQuestionsRequest): + def ask_followup_questions( + self, askFollowupQuestionsRequest: AskFollowupQuestionsRequest + ): """ - This function will be responsible for asking follow up questions to better understand what the user wants to build. + This function will be responsible for asking follow up questions to better understand what the user wants to build. """ try: questionPrompt = f"""Given the description of the project so far {askFollowupQuestionsRequest.history} and the user's latest question {askFollowupQuestionsRequest.question}, come up with additional follow up questions to further deepen your understanding of what the user is trying to build. Ask more questions about the front end, backend, or hosting requirements. Understand the details of the product features. Ask questions until you are confident that you are able to generate a detailed execution plan for the project. The response should be a list of questions that you can ask the user to better understand the project requirements. Limit to 2-3 questions at a time. @@ -207,17 +226,18 @@ def ask_followup_questions(self, askFollowupQuestionsRequest: AskFollowupQuestio print("Failed to generate subtasks with error " + str(e)) return "Failed to generate subtasks with error " + str(e) - - def reference_existing_code(self, referenceExistingCodeRequest: ReferenceExistingCodeRequest): + def reference_existing_code( + self, referenceExistingCodeRequest: ReferenceExistingCodeRequest + ): """ - This method should take the user's question and search the current codebase for all references to that question. It should then summarize the current code and how the user's request can be built within that codebase. + This method should take the user's question and search the current codebase for all references to that question. It should then summarize the current code and how the user's request can be built within that codebase. """ # First get the codebase dict codebase_dict = self.gh_helper.get_entire_codebase() # Now search the codebase for the user's question codebase = codebase_dict.files - + try: questionPrompt = f"""Given the description of the project so far {referenceExistingCodeRequest.history} and the user's latest question {referenceExistingCodeRequest.question}, figure out which files in the codebase are most relevant for the user in order to best design a solution to the feature requests. You have this codebase to reference {codebase}. If the feature is completely irrelevant to the user's existing code, let the user know that the feature request seems to be unrelated to the existing codebase and ask them to verify if they do indeed want to build that feature in the current codebase. @@ -244,7 +264,7 @@ def reference_existing_code(self, referenceExistingCodeRequest: ReferenceExistin except Exception as e: print("Failed to generate subtasks with error " + str(e)) return "Failed to generate subtasks with error " + str(e) - + def create_tasks(self, createTicketsRequest: CreateTicketsRequest): """ This function will be responsible for creating multiple tickets in parallel. @@ -274,8 +294,7 @@ def create_tasks(self, createTicketsRequest: CreateTicketsRequest): }, {"role": "user", "content": questionPrompt}, ], - - response_format={ "type": "json_object" } + response_format={"type": "json_object"}, ) subtasks = response.choices[0].message.content @@ -286,16 +305,23 @@ def create_tasks(self, createTicketsRequest: CreateTicketsRequest): tickets = [] ticket_titles = [] for subtask in subtask_json: - ticket = Ticket(title=subtask["title"], description=subtask["description"]) + ticket = Ticket( + title=subtask["title"], description=subtask["description"] + ) tickets.append(ticket) ticket_titles.append(ticket.title) - createdTickets = self.trello_helper.push_tickets_to_backlog_and_assign(tickets) + createdTickets = self.trello_helper.push_tickets_to_backlog_and_assign( + tickets + ) ticketMarkdown = generate_ticket_markdown(createdTickets) - return "Great! I've just created the following tickets and assigned them to our agents to get started on immediately \n" + ticketMarkdown - + return ( + "Great! I've just created the following tickets and assigned them to our agents to get started on immediately \n" + + ticketMarkdown + ) + # response = client.chat.completions.create( # model="gpt-3.5-turbo-1106", # messages=[ @@ -311,11 +337,11 @@ def create_tasks(self, createTicketsRequest: CreateTicketsRequest): # ) # finalResponse = response.choices[0].message.content # return finalResponse - + except Exception as e: print("Failed to generate subtasks with error " + str(e)) return "Failed to generate subtasks with error " + str(e) - + # Define the tool for breaking up the overall project description into multiple smaller tasks and then getting user feedback on them def create_subtasks(self, project_description): """ @@ -359,5 +385,3 @@ def generate_ticket_markdown(tickets: List[Ticket]): for ticket in tickets: markdown += f"- **{ticket.title}**: {ticket.description}\n" return markdown - - diff --git a/src/agents/intern/__init__.py b/src/agents/intern/__init__.py index 9ba1314..0956681 100644 --- a/src/agents/intern/__init__.py +++ b/src/agents/intern/__init__.py @@ -48,7 +48,7 @@ def refresh_ticket_todo_list(self): return len(next_tickets) != 0 def refresh_pr_backlog(self): - return False # Not implemented yet + return False # Not implemented yet print(f"[INTERN {self.name}] Looking on GitHub for reviewed PRs") next_prs = [ pr diff --git a/src/helpers/trello.py b/src/helpers/trello.py index b72fc2f..859238a 100644 --- a/src/helpers/trello.py +++ b/src/helpers/trello.py @@ -13,7 +13,19 @@ "Content-Type": "application/json", } -COLORS = ["green", "yellow", "orange", "red", "purple", "blue", "sky", "lime", "pink", "black"] +COLORS = [ + "green", + "yellow", + "orange", + "red", + "purple", + "blue", + "sky", + "lime", + "pink", + "black", +] + class CustomTrelloClient(TrelloClient): # Patch, because the base one is not working @@ -167,7 +179,9 @@ def get_intern_list(self): return [label.id for label in self.client.get_board(self.board_id).get_labels()] def create_intern(self, name): - label = self.client.get_board(self.board_id).add_label(name, color=choice(COLORS)) + label = self.client.get_board(self.board_id).add_label( + name, color=choice(COLORS) + ) return label.id # TESTING METHODS @@ -177,7 +191,9 @@ def move_to_todo(self, ticket_id): ticket.change_list(self.list_ids[TicketStatus.TODO.value]) def get_last_ticket(self): - return self.client.get_list(self.list_ids[TicketStatus.BACKLOG.value]).list_cards()[-1] + return self.client.get_list( + self.list_ids[TicketStatus.BACKLOG.value] + ).list_cards()[-1] def delete_ticket(self, ticket_id): self.client.get_board(self.board_id).get_card(ticket_id).delete() diff --git a/src/lib/mistral_chat_completion.py b/src/lib/mistral_chat_completion.py index 1cb9564..41506f5 100644 --- a/src/lib/mistral_chat_completion.py +++ b/src/lib/mistral_chat_completion.py @@ -3,11 +3,13 @@ from pydantic import BaseModel + class UsageInfo(BaseModel): prompt_tokens: int total_tokens: int completion_tokens: Optional[int] + class Function(BaseModel): name: str description: str diff --git a/src/lib/mistral_dspy.py b/src/lib/mistral_dspy.py index 857694b..6266531 100644 --- a/src/lib/mistral_dspy.py +++ b/src/lib/mistral_dspy.py @@ -8,10 +8,12 @@ from src.lib.ported_exceptions import MistralAPIException from src.lib.ported_clients import MistralClient + class ChatMessage(BaseModel): role: str content: Union[str, List[str]] + def backoff_hdlr(details): """Handler from https://pypi.org/project/backoff/""" print( diff --git a/src/lib/ported_clients.py b/src/lib/ported_clients.py index 1739039..b80a397 100644 --- a/src/lib/ported_clients.py +++ b/src/lib/ported_clients.py @@ -15,7 +15,13 @@ MistralConnectionException, MistralException, ) -from src.lib.mistral_chat_completion import ChatCompletionResponse, ChatMessage, Function, ResponseFormat, ToolChoice +from src.lib.mistral_chat_completion import ( + ChatCompletionResponse, + ChatMessage, + Function, + ResponseFormat, + ToolChoice, +) logging.basicConfig( format="%(asctime)s %(levelname)s %(name)s: %(message)s", @@ -26,6 +32,7 @@ ENDPOINT = "https://api.mistral.ai" + class ClientBase(ABC): def __init__( self, @@ -55,7 +62,9 @@ def _parse_tools(self, tools: List[Dict[str, Any]]) -> List[Dict[str, Any]]: parsed_function = {} parsed_function["type"] = tool["type"] if isinstance(tool["function"], Function): - parsed_function["function"] = tool["function"].model_dump(exclude_none=True) + parsed_function["function"] = tool["function"].model_dump( + exclude_none=True + ) else: parsed_function["function"] = tool["function"] @@ -68,7 +77,9 @@ def _parse_tool_choice(self, tool_choice: Union[str, ToolChoice]) -> str: return tool_choice.value return tool_choice - def _parse_response_format(self, response_format: Union[Dict[str, Any], ResponseFormat]) -> Dict[str, Any]: + def _parse_response_format( + self, response_format: Union[Dict[str, Any], ResponseFormat] + ) -> Dict[str, Any]: if isinstance(response_format, ResponseFormat): return response_format.model_dump(exclude_none=True) return response_format @@ -125,7 +136,9 @@ def _make_chat_request( if tool_choice is not None: request_data["tool_choice"] = self._parse_tool_choice(tool_choice) if response_format is not None: - request_data["response_format"] = self._parse_response_format(response_format) + request_data["response_format"] = self._parse_response_format( + response_format + ) self._logger.debug(f"Chat request: {request_data}") @@ -155,7 +168,9 @@ def __init__( super().__init__(endpoint, api_key, max_retries, timeout) self._client = Client( - follow_redirects=True, timeout=self._timeout, transport=HTTPTransport(retries=self._max_retries) + follow_redirects=True, + timeout=self._timeout, + transport=HTTPTransport(retries=self._max_retries), ) def __del__(self) -> None: @@ -219,9 +234,9 @@ def _request( self._logger.debug(f"Sending request: {method} {url} {json}") new_json = json.copy() - new_json['messages'] = [] + new_json["messages"] = [] - for message in json['messages']: + for message in json["messages"]: new_json["messages"].append(message.model_dump(exclude_none=True)) response: Response @@ -239,7 +254,9 @@ def _request( except ConnectError as e: raise MistralConnectionException(str(e)) from e except RequestError as e: - raise MistralException(f"Unexpected exception ({e.__class__.__name__}): {e}") from e + raise MistralException( + f"Unexpected exception ({e.__class__.__name__}): {e}" + ) from e except JSONDecodeError as e: raise MistralAPIException.from_response( response, @@ -248,7 +265,9 @@ def _request( except MistralAPIStatusException as e: attempt += 1 if attempt > self._max_retries: - raise MistralAPIStatusException.from_response(response, message=str(e)) from e + raise MistralAPIStatusException.from_response( + response, message=str(e) + ) from e backoff = 2.0**attempt # exponential backoff time.sleep(backoff) @@ -307,4 +326,4 @@ def chat( for response in single_response: return ChatCompletionResponse(**response) - raise MistralException("No response received") \ No newline at end of file + raise MistralException("No response received") diff --git a/src/lib/ported_exceptions.py b/src/lib/ported_exceptions.py index 9c9da81..3a8a9e8 100644 --- a/src/lib/ported_exceptions.py +++ b/src/lib/ported_exceptions.py @@ -47,8 +47,10 @@ def from_response( def __repr__(self) -> str: return f"{self.__class__.__name__}(message={str(self)}, http_status={self.http_status})" + class MistralAPIStatusException(MistralAPIException): """Returned when we receive a non-200 response from the API that we should retry""" + class MistralConnectionException(MistralException): """Returned when the SDK can not reach the API server for any reason""" diff --git a/start_architecting.py b/start_architecting.py index 1668af6..aa823a2 100644 --- a/start_architecting.py +++ b/start_architecting.py @@ -38,4 +38,4 @@ trello_helper=trello_helper, ) -architect.run() \ No newline at end of file +architect.run() diff --git a/tst/test_gh_helper.py b/tst/test_gh_helper.py index 3e90b37..be5224d 100644 --- a/tst/test_gh_helper.py +++ b/tst/test_gh_helper.py @@ -1,7 +1,8 @@ from src.helpers.github import GHHelper + class TestGHHelper: # TODO: Mock out the GitHub API calls, write proper tests def test_create(self): gh_helper = GHHelper("token", "repo") - assert gh_helper is not None \ No newline at end of file + assert gh_helper is not None diff --git a/tst/test_trello_helper.py b/tst/test_trello_helper.py index 254cef4..9d3fefa 100644 --- a/tst/test_trello_helper.py +++ b/tst/test_trello_helper.py @@ -1,8 +1,9 @@ from src.helpers.trello import TrelloHelper + class TestTrelloHelper: # TODO: Mock out the Trello API calls, write proper tests def test_create(self): # trello_helper = TrelloHelper("key", "token", "board_id") # assert trello_helper is not None - assert True \ No newline at end of file + assert True From 85a82b4403ae7aec5f26e4e9373b92551ec15246 Mon Sep 17 00:00:00 2001 From: pedroCollogno Date: Sat, 30 Mar 2024 11:56:44 -0700 Subject: [PATCH 3/3] CI: Use Poetry to run tests --- .github/workflows/check_code.yaml | 4 +--- poetry.lock | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check_code.yaml b/.github/workflows/check_code.yaml index 2067837..878b695 100644 --- a/.github/workflows/check_code.yaml +++ b/.github/workflows/check_code.yaml @@ -22,9 +22,7 @@ jobs: - name: Install dependencies run: poetry install - name: Run tests - run: python -m pytest - # TODO: set up Poetry's dependencies and use it with PyTest - # run: poetry run pytest + run: poetry run pytest code-quality: strategy: fail-fast: false diff --git a/poetry.lock b/poetry.lock index cd0daf9..2d1187f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -206,4 +206,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.8" -content-hash = "e3c0c8655c80c5d434c12d58334a71906566602755abe2dcc7941b5eaeaa498f" +content-hash = "25332e3cd7e32b4607d9aaa6411c3c53d0847c086f9b9e89b46a62a955b86731" diff --git a/pyproject.toml b/pyproject.toml index e001dd7..178a312 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,10 +28,10 @@ packages = [ [tool.poetry.dependencies] python = ">=3.8" -pytest = ">=8.0.0" [tool.poetry.group.dev.dependencies] black = "^24.3.0" +pytest = "^8.1.1" [build-system] requires = ["hatchling"]