From 03140d3dd5c8ab15b29c59680174f5afb4397442 Mon Sep 17 00:00:00 2001 From: Greyson Lalonde Date: Wed, 27 Dec 2023 13:03:29 -0500 Subject: [PATCH 1/6] Bump min py to 3.9; add formatting deps Increased minimum Python version from 3.81 to 3.9 - most projects align with this; added pre-commit hooks, isort, black, & autoflake for code quality; updated lock file. --- poetry.lock | 321 +++++++++++++++++++++++++++++++++++++++++++------ pyproject.toml | 11 +- 2 files changed, 296 insertions(+), 36 deletions(-) diff --git a/poetry.lock b/poetry.lock index a524b0b1d..bc334b925 100644 --- a/poetry.lock +++ b/poetry.lock @@ -121,9 +121,6 @@ files = [ {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, ] -[package.dependencies] -typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} - [[package]] name = "anyio" version = "4.2.0" @@ -175,6 +172,67 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib- tests = ["attrs[tests-no-zope]", "zope-interface"] tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +[[package]] +name = "autoflake" +version = "2.2.1" +description = "Removes unused imports and unused variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "autoflake-2.2.1-py3-none-any.whl", hash = "sha256:265cde0a43c1f44ecfb4f30d95b0437796759d07be7706a2f70e4719234c0f79"}, + {file = "autoflake-2.2.1.tar.gz", hash = "sha256:62b7b6449a692c3c9b0c916919bbc21648da7281e8506bcf8d3f8280e431ebc1"}, +] + +[package.dependencies] +pyflakes = ">=3.0.0" +tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} + +[[package]] +name = "black" +version = "23.12.1" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, + {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, + {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, + {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, + {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, + {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, + {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, + {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, + {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, + {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, + {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, + {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, + {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, + {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, + {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, + {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, + {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, + {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, + {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, + {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, + {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, + {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, +] + +[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 = "certifi" version = "2023.11.17" @@ -186,6 +244,17 @@ files = [ {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, ] +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + [[package]] name = "charset-normalizer" version = "3.3.2" @@ -285,6 +354,20 @@ files = [ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] +[[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" @@ -311,6 +394,17 @@ files = [ marshmallow = ">=3.18.0,<4.0.0" typing-inspect = ">=0.4.0,<1" +[[package]] +name = "distlib" +version = "0.3.8" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, +] + [[package]] name = "distro" version = "1.9.0" @@ -336,6 +430,22 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "filelock" +version = "3.13.1" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, + {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +typing = ["typing-extensions (>=4.8)"] + [[package]] name = "frozenlist" version = "1.4.1" @@ -549,6 +659,20 @@ cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] +[[package]] +name = "identify" +version = "2.5.33" +description = "File identification library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"}, + {file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"}, +] + +[package.extras] +license = ["ukkonen"] + [[package]] name = "idna" version = "3.6" @@ -571,6 +695,20 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + [[package]] name = "jsonpatch" version = "1.33" @@ -815,41 +953,63 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] +[[package]] +name = "nodeenv" +version = "1.8.0" +description = "Node.js virtual environment builder" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + +[package.dependencies] +setuptools = "*" + [[package]] name = "numpy" -version = "1.24.4" +version = "1.26.2" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"}, - {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"}, - {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"}, - {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"}, - {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"}, - {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"}, - {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"}, - {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"}, - {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"}, - {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"}, - {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"}, - {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"}, - {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"}, - {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"}, - {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"}, - {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"}, - {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"}, - {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"}, - {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"}, - {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"}, - {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"}, - {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"}, - {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"}, - {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"}, - {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, + {file = "numpy-1.26.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3703fc9258a4a122d17043e57b35e5ef1c5a5837c3db8be396c82e04c1cf9b0f"}, + {file = "numpy-1.26.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cc392fdcbd21d4be6ae1bb4475a03ce3b025cd49a9be5345d76d7585aea69440"}, + {file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36340109af8da8805d8851ef1d74761b3b88e81a9bd80b290bbfed61bd2b4f75"}, + {file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcc008217145b3d77abd3e4d5ef586e3bdfba8fe17940769f8aa09b99e856c00"}, + {file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ced40d4e9e18242f70dd02d739e44698df3dcb010d31f495ff00a31ef6014fe"}, + {file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b272d4cecc32c9e19911891446b72e986157e6a1809b7b56518b4f3755267523"}, + {file = "numpy-1.26.2-cp310-cp310-win32.whl", hash = "sha256:22f8fc02fdbc829e7a8c578dd8d2e15a9074b630d4da29cda483337e300e3ee9"}, + {file = "numpy-1.26.2-cp310-cp310-win_amd64.whl", hash = "sha256:26c9d33f8e8b846d5a65dd068c14e04018d05533b348d9eaeef6c1bd787f9919"}, + {file = "numpy-1.26.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b96e7b9c624ef3ae2ae0e04fa9b460f6b9f17ad8b4bec6d7756510f1f6c0c841"}, + {file = "numpy-1.26.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aa18428111fb9a591d7a9cc1b48150097ba6a7e8299fb56bdf574df650e7d1f1"}, + {file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06fa1ed84aa60ea6ef9f91ba57b5ed963c3729534e6e54055fc151fad0423f0a"}, + {file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96ca5482c3dbdd051bcd1fce8034603d6ebfc125a7bd59f55b40d8f5d246832b"}, + {file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:854ab91a2906ef29dc3925a064fcd365c7b4da743f84b123002f6139bcb3f8a7"}, + {file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f43740ab089277d403aa07567be138fc2a89d4d9892d113b76153e0e412409f8"}, + {file = "numpy-1.26.2-cp311-cp311-win32.whl", hash = "sha256:a2bbc29fcb1771cd7b7425f98b05307776a6baf43035d3b80c4b0f29e9545186"}, + {file = "numpy-1.26.2-cp311-cp311-win_amd64.whl", hash = "sha256:2b3fca8a5b00184828d12b073af4d0fc5fdd94b1632c2477526f6bd7842d700d"}, + {file = "numpy-1.26.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a4cd6ed4a339c21f1d1b0fdf13426cb3b284555c27ac2f156dfdaaa7e16bfab0"}, + {file = "numpy-1.26.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d5244aabd6ed7f312268b9247be47343a654ebea52a60f002dc70c769048e75"}, + {file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a3cdb4d9c70e6b8c0814239ead47da00934666f668426fc6e94cce869e13fd7"}, + {file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa317b2325f7aa0a9471663e6093c210cb2ae9c0ad824732b307d2c51983d5b6"}, + {file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:174a8880739c16c925799c018f3f55b8130c1f7c8e75ab0a6fa9d41cab092fd6"}, + {file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f79b231bf5c16b1f39c7f4875e1ded36abee1591e98742b05d8a0fb55d8a3eec"}, + {file = "numpy-1.26.2-cp312-cp312-win32.whl", hash = "sha256:4a06263321dfd3598cacb252f51e521a8cb4b6df471bb12a7ee5cbab20ea9167"}, + {file = "numpy-1.26.2-cp312-cp312-win_amd64.whl", hash = "sha256:b04f5dc6b3efdaab541f7857351aac359e6ae3c126e2edb376929bd3b7f92d7e"}, + {file = "numpy-1.26.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4eb8df4bf8d3d90d091e0146f6c28492b0be84da3e409ebef54349f71ed271ef"}, + {file = "numpy-1.26.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1a13860fdcd95de7cf58bd6f8bc5a5ef81c0b0625eb2c9a783948847abbef2c2"}, + {file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64308ebc366a8ed63fd0bf426b6a9468060962f1a4339ab1074c228fa6ade8e3"}, + {file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baf8aab04a2c0e859da118f0b38617e5ee65d75b83795055fb66c0d5e9e9b818"}, + {file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d73a3abcac238250091b11caef9ad12413dab01669511779bc9b29261dd50210"}, + {file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b361d369fc7e5e1714cf827b731ca32bff8d411212fccd29ad98ad622449cc36"}, + {file = "numpy-1.26.2-cp39-cp39-win32.whl", hash = "sha256:bd3f0091e845164a20bd5a326860c840fe2af79fa12e0469a12768a3ec578d80"}, + {file = "numpy-1.26.2-cp39-cp39-win_amd64.whl", hash = "sha256:2beef57fb031dcc0dc8fa4fe297a742027b954949cabb52a2a376c144e5e6060"}, + {file = "numpy-1.26.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1cc3d5029a30fb5f06704ad6b23b35e11309491c999838c31f124fee32107c79"}, + {file = "numpy-1.26.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94cc3c222bb9fb5a12e334d0479b97bb2df446fbe622b470928f5284ffca3f8d"}, + {file = "numpy-1.26.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe6b44fb8fcdf7eda4ef4461b97b3f63c466b27ab151bec2366db8b197387841"}, + {file = "numpy-1.26.2.tar.gz", hash = "sha256:f65738447676ab5777f11e6bbbdb8ce11b785e105f690bc45966574816b6d3ea"}, ] [[package]] @@ -886,6 +1046,32 @@ files = [ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] +[[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.1.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.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, + {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + [[package]] name = "pluggy" version = "1.3.0" @@ -901,6 +1087,24 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pre-commit" +version = "3.6.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pre_commit-3.6.0-py2.py3-none-any.whl", hash = "sha256:c255039ef399049a5544b6ce13d135caba8f2c28c3b4033277a788f434308376"}, + {file = "pre_commit-3.6.0.tar.gz", hash = "sha256:d30bad9abf165f7785c15a21a1f46da7d0677cb00ee7ff4c579fd38922efe15d"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + [[package]] name = "pydantic" version = "2.5.3" @@ -1037,6 +1241,17 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pyflakes" +version = "3.1.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, + {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, +] + [[package]] name = "pytest" version = "7.4.3" @@ -1168,6 +1383,22 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "setuptools" +version = "69.0.3" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, + {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "sniffio" version = "1.3.0" @@ -1370,6 +1601,26 @@ urllib3 = {version = "<2", markers = "python_version < \"3.10\""} wrapt = "*" yarl = "*" +[[package]] +name = "virtualenv" +version = "20.25.0" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, + {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + [[package]] name = "wrapt" version = "1.16.0" @@ -1554,5 +1805,5 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" -python-versions = ">=3.8.1,<4.0" -content-hash = "d4b313f67f1942e153e515c12288b3252f955e1ebf75b4b7b28aa0c0f9dd4bd7" +python-versions = ">=3.9,<4.0" +content-hash = "6518523eef59e35f89783fbdd92ff6f06a5a1b0d32c3d4241b5cce9713c84e47" diff --git a/pyproject.toml b/pyproject.toml index cbeeb0414..f884696d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,11 +12,20 @@ Documentation = "https://github.com/joaomdmoura/CrewAI/wiki/Index" Repository = "https://github.com/joaomdmoura/crewai" [tool.poetry.dependencies] -python = ">=3.8.1,<4.0" +python = ">=3.9,<4.0" pydantic = "^2.4.2" langchain = "^0.0.351" openai = "^1.5.0" +[tool.poetry.group.dev.dependencies] +isort = "^5.13.2" +black = "^23.12.1" +autoflake = "^2.2.1" +pre-commit = "^3.6.0" + +[tool.isort] +profile = "black" + [tool.poetry.group.test.dependencies] pytest = "^7.4" pytest-vcr = "^1.0.2" From 6716a78aa065dc288080e9c20b6a2aefe87f56b4 Mon Sep 17 00:00:00 2001 From: Greyson Lalonde Date: Wed, 27 Dec 2023 13:05:01 -0500 Subject: [PATCH 2/6] Add pre-commit config w/ new dev deps --- .pre-commit-config.yaml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..cb0a68fd9 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,20 @@ +repos: + + - repo: https://github.com/psf/black-pre-commit-mirror + rev: 23.12.1 + hooks: + - id: black + language_version: python3.11 + files: \.(py)$ + + - repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + name: isort (python) + args: ["--profile", "black", "--filter-files"] + + - repo: https://github.com/PyCQA/autoflake + rev: v2.2.1 + hooks: + - id: autoflake From b104d1ee44b78d502825177d7dad2d4f6096e625 Mon Sep 17 00:00:00 2001 From: Greyson Lalonde Date: Wed, 27 Dec 2023 13:10:35 -0500 Subject: [PATCH 3/6] Update readme to reflect pre-commit --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index c84e34d9f..1b2e7912d 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,12 @@ poetry install poetry shell ``` +### Pre-commit hooks + +```bash +pre-commit install +``` + ### Running Tests ```bash poetry run pytest From 542a794e6476b573b75dc0786f475b4ccb997d1d Mon Sep 17 00:00:00 2001 From: Greyson Lalonde Date: Wed, 27 Dec 2023 13:21:33 -0500 Subject: [PATCH 4/6] Update autoflake args This wont format automatically unless --in-place is passed and will remove init imports when missing --ignore-init-module-imports --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cb0a68fd9..4eae2b11a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,3 +18,4 @@ repos: rev: v2.2.1 hooks: - id: autoflake + args: ['--in-place', '--remove-all-unused-imports', '--remove-unused-variables', '--ignore-init-module-imports'] From a4e93cea7529552905677b11b8866b58d8f38eee Mon Sep 17 00:00:00 2001 From: Greyson Lalonde Date: Wed, 27 Dec 2023 13:23:45 -0500 Subject: [PATCH 5/6] Run pre-commit hooks In the title ! --- crewai/__init__.py | 6 +- crewai/agent.py | 204 +++++++++++++------------- crewai/crew.py | 147 ++++++++++--------- crewai/process.py | 14 +- crewai/prompts.py | 54 ++++--- crewai/task.py | 65 +++++---- crewai/tools/agent_tools.py | 93 ++++++------ tests/agent_test.py | 192 ++++++++++++------------- tests/conftest.py | 3 +- tests/crew_test.py | 277 +++++++++++++++++++----------------- tests/task_test.py | 89 ++++++------ 11 files changed, 582 insertions(+), 562 deletions(-) diff --git a/crewai/__init__.py b/crewai/__init__.py index e3d8d20db..dbff2fe58 100644 --- a/crewai/__init__.py +++ b/crewai/__init__.py @@ -1,4 +1,4 @@ -from .task import Task -from .crew import Crew from .agent import Agent -from .process import Process \ No newline at end of file +from .crew import Crew +from .process import Process +from .task import Task diff --git a/crewai/agent.py b/crewai/agent.py index fe13447de..fa89f7a47 100644 --- a/crewai/agent.py +++ b/crewai/agent.py @@ -1,128 +1,120 @@ """Generic agent.""" -from typing import List, Any, Optional -from pydantic.v1 import BaseModel, PrivateAttr, Field, root_validator +from typing import Any, List, Optional from langchain.agents import AgentExecutor -from langchain.chat_models import ChatOpenAI -from langchain.tools.render import render_text_description from langchain.agents.format_scratchpad import format_log_to_str from langchain.agents.output_parsers import ReActSingleInputOutputParser +from langchain.chat_models import ChatOpenAI from langchain.memory import ConversationSummaryMemory +from langchain.tools.render import render_text_description +from pydantic.v1 import BaseModel, Field, PrivateAttr, root_validator from .prompts import Prompts + class Agent(BaseModel): - """ - Represents an agent in a system. + """ + Represents an agent in a system. - Each agent has a role, a goal, a backstory, and an optional language model (llm). - The agent can also have memory, can operate in verbose mode, and can delegate tasks to other agents. + Each agent has a role, a goal, a backstory, and an optional language model (llm). + The agent can also have memory, can operate in verbose mode, and can delegate tasks to other agents. - Attributes: - agent_executor: An instance of the AgentExecutor class. - role: The role of the agent. - goal: The objective of the agent. - backstory: The backstory of the agent. - llm: The language model that will run the agent. - memory: Whether the agent should have memory or not. - verbose: Whether the agent execution should be in verbose mode. - allow_delegation: Whether the agent is allowed to delegate tasks to other agents. - """ - agent_executor: AgentExecutor = None - role: str = Field(description="Role of the agent") - goal: str = Field(description="Objective of the agent") - backstory: str = Field(description="Backstory of the agent") - llm: Optional[Any] = Field(description="LLM that will run the agent") - memory: bool = Field( - description="Whether the agent should have memory or not", - default=True - ) - verbose: bool = Field( - description="Verbose mode for the Agent Execution", - default=False - ) - allow_delegation: bool = Field( - description="Allow delegation of tasks to agents", - default=True - ) - tools: List[Any] = Field( - description="Tools at agents disposal", - default=[] - ) - _task_calls: List[Any] = PrivateAttr() + Attributes: + agent_executor: An instance of the AgentExecutor class. + role: The role of the agent. + goal: The objective of the agent. + backstory: The backstory of the agent. + llm: The language model that will run the agent. + memory: Whether the agent should have memory or not. + verbose: Whether the agent execution should be in verbose mode. + allow_delegation: Whether the agent is allowed to delegate tasks to other agents. + """ - @root_validator(pre=True) - def check_llm(_cls, values): - if not values.get('llm'): - values['llm'] = ChatOpenAI( - temperature=0.7, - model_name="gpt-4" - ) - return values + agent_executor: AgentExecutor = None + role: str = Field(description="Role of the agent") + goal: str = Field(description="Objective of the agent") + backstory: str = Field(description="Backstory of the agent") + llm: Optional[Any] = Field(description="LLM that will run the agent") + memory: bool = Field( + description="Whether the agent should have memory or not", default=True + ) + verbose: bool = Field( + description="Verbose mode for the Agent Execution", default=False + ) + allow_delegation: bool = Field( + description="Allow delegation of tasks to agents", default=True + ) + tools: List[Any] = Field(description="Tools at agents disposal", default=[]) + _task_calls: List[Any] = PrivateAttr() - def __init__(self, **data): - super().__init__(**data) - agent_args = { - "input": lambda x: x["input"], - "tools": lambda x: x["tools"], - "tool_names": lambda x: x["tool_names"], - "agent_scratchpad": lambda x: format_log_to_str(x['intermediate_steps']), - } - executor_args = { - "tools": self.tools, - "verbose": self.verbose, - "handle_parsing_errors": True, - } + @root_validator(pre=True) + def check_llm(_cls, values): + if not values.get("llm"): + values["llm"] = ChatOpenAI(temperature=0.7, model_name="gpt-4") + return values - if self.memory: - summary_memory = ConversationSummaryMemory( - llm=self.llm, - memory_key='chat_history', - input_key="input" - ) - executor_args['memory'] = summary_memory - agent_args['chat_history'] = lambda x: x["chat_history"] - prompt = Prompts.TASK_EXECUTION_WITH_MEMORY_PROMPT - else: - prompt = Prompts.TASK_EXECUTION_PROMPT + def __init__(self, **data): + super().__init__(**data) + agent_args = { + "input": lambda x: x["input"], + "tools": lambda x: x["tools"], + "tool_names": lambda x: x["tool_names"], + "agent_scratchpad": lambda x: format_log_to_str(x["intermediate_steps"]), + } + executor_args = { + "tools": self.tools, + "verbose": self.verbose, + "handle_parsing_errors": True, + } - execution_prompt = prompt.partial( - goal=self.goal, - role=self.role, - backstory=self.backstory, - ) + if self.memory: + summary_memory = ConversationSummaryMemory( + llm=self.llm, memory_key="chat_history", input_key="input" + ) + executor_args["memory"] = summary_memory + agent_args["chat_history"] = lambda x: x["chat_history"] + prompt = Prompts.TASK_EXECUTION_WITH_MEMORY_PROMPT + else: + prompt = Prompts.TASK_EXECUTION_PROMPT - bind = self.llm.bind(stop=["\nObservation"]) - inner_agent = agent_args | execution_prompt | bind | ReActSingleInputOutputParser() + execution_prompt = prompt.partial( + goal=self.goal, + role=self.role, + backstory=self.backstory, + ) - self.agent_executor = AgentExecutor( - agent=inner_agent, - **executor_args - ) + bind = self.llm.bind(stop=["\nObservation"]) + inner_agent = ( + agent_args | execution_prompt | bind | ReActSingleInputOutputParser() + ) - def execute_task(self, task: str, context: str = None, tools: List[Any] = None) -> str: - """ - Execute a task with the agent. - Parameters: - task (str): Task to execute - Returns: - output (str): Output of the agent - """ - if context: - task = "\n".join([ - task, - "\nThis is the context you are working with:", - context - ]) + self.agent_executor = AgentExecutor(agent=inner_agent, **executor_args) - tools = tools or self.tools - self.agent_executor.tools = tools - return self.agent_executor.invoke({ - "input": task, - "tool_names": self.__tools_names(tools), - "tools": render_text_description(tools), - })['output'] + def execute_task( + self, task: str, context: str = None, tools: List[Any] = None + ) -> str: + """ + Execute a task with the agent. + Parameters: + task (str): Task to execute + Returns: + output (str): Output of the agent + """ + if context: + task = "\n".join( + [task, "\nThis is the context you are working with:", context] + ) - def __tools_names(self, tools) -> str: - return ", ".join([t.name for t in tools]) + tools = tools or self.tools + self.agent_executor.tools = tools + return self.agent_executor.invoke( + { + "input": task, + "tool_names": self.__tools_names(tools), + "tools": render_text_description(tools), + } + )["output"] + + def __tools_names(self, tools) -> str: + return ", ".join([t.name for t in tools]) diff --git a/crewai/crew.py b/crewai/crew.py index bdd705146..8bf4b1bae 100644 --- a/crewai/crew.py +++ b/crewai/crew.py @@ -1,89 +1,88 @@ import json from typing import List, Optional + from pydantic.v1 import BaseModel, Field, Json, root_validator -from .process import Process from .agent import Agent +from .process import Process from .task import Task from .tools.agent_tools import AgentTools + class Crew(BaseModel): - """ - Class that represents a group of agents, how they should work together and - their tasks. - """ - tasks: Optional[List[Task]] = Field(description="List of tasks") - agents: Optional[List[Agent]] = Field(description="List of agents in this crew.") - process: Process = Field( - description="Process that the crew will follow.", - default=Process.sequential - ) - verbose: bool = Field( - description="Verbose mode for the Agent Execution", - default=False - ) - config: Optional[Json] = Field( - description="Configuration of the crew.", - default=None - ) + """ + Class that represents a group of agents, how they should work together and + their tasks. + """ - @root_validator(pre=True) - def check_config(_cls, values): - if ( - not values.get('config') - and ( - not values.get('agents') and not values.get('tasks') - ) - ): - raise ValueError('Either agents and task need to be set or config.') - - if values.get('config'): - config = json.loads(values.get('config')) - if not config.get('agents') or not config.get('tasks'): - raise ValueError('Config should have agents and tasks.') - - values['agents'] = [Agent(**agent) for agent in config['agents']] - - tasks = [] - for task in config['tasks']: - task_agent = [agt for agt in values['agents'] if agt.role == task['agent']][0] - del task['agent'] - tasks.append(Task(**task, agent=task_agent)) - - values['tasks'] = tasks - return values + tasks: Optional[List[Task]] = Field(description="List of tasks") + agents: Optional[List[Agent]] = Field(description="List of agents in this crew.") + process: Process = Field( + description="Process that the crew will follow.", default=Process.sequential + ) + verbose: bool = Field( + description="Verbose mode for the Agent Execution", default=False + ) + config: Optional[Json] = Field( + description="Configuration of the crew.", default=None + ) - def kickoff(self) -> str: - """ - Kickoff the crew to work on it's tasks. - Returns: - output (List[str]): Output of the crew for each task. - """ - if self.process == Process.sequential: - return self.__sequential_loop() + @root_validator(pre=True) + def check_config(_cls, values): + if not values.get("config") and ( + not values.get("agents") and not values.get("tasks") + ): + raise ValueError("Either agents and task need to be set or config.") - def __sequential_loop(self) -> str: - """ - Loop that executes the sequential process. - Returns: - output (str): Output of the crew. - """ - task_outcome = None - for task in self.tasks: - # Add delegation tools to the task if the agent allows it - if task.agent.allow_delegation: - tools = AgentTools(agents=self.agents).tools() - task.tools += tools + if values.get("config"): + config = json.loads(values.get("config")) + if not config.get("agents") or not config.get("tasks"): + raise ValueError("Config should have agents and tasks.") - self.__log(f"\nWorking Agent: {task.agent.role}") - self.__log(f"Starting Task: {task.description} ...") + values["agents"] = [Agent(**agent) for agent in config["agents"]] - task_outcome = task.execute(task_outcome) + tasks = [] + for task in config["tasks"]: + task_agent = [ + agt for agt in values["agents"] if agt.role == task["agent"] + ][0] + del task["agent"] + tasks.append(Task(**task, agent=task_agent)) - self.__log(f"Task output: {task_outcome}") - - return task_outcome - - def __log(self, message): - if self.verbose: - print(message) \ No newline at end of file + values["tasks"] = tasks + return values + + def kickoff(self) -> str: + """ + Kickoff the crew to work on it's tasks. + Returns: + output (List[str]): Output of the crew for each task. + """ + if self.process == Process.sequential: + return self.__sequential_loop() + + def __sequential_loop(self) -> str: + """ + Loop that executes the sequential process. + Returns: + output (str): Output of the crew. + """ + task_outcome = None + for task in self.tasks: + # Add delegation tools to the task if the agent allows it + if task.agent.allow_delegation: + tools = AgentTools(agents=self.agents).tools() + task.tools += tools + + self.__log(f"\nWorking Agent: {task.agent.role}") + self.__log(f"Starting Task: {task.description} ...") + + task_outcome = task.execute(task_outcome) + + self.__log(f"Task output: {task_outcome}") + + return task_outcome + + def __log(self, message): + if self.verbose: + print(message) diff --git a/crewai/process.py b/crewai/process.py index 317f0fab5..d6f511a1a 100644 --- a/crewai/process.py +++ b/crewai/process.py @@ -1,9 +1,11 @@ from enum import Enum + class Process(str, Enum): - """ - Class representing the different processes that can be used to tackle tasks - """ - sequential = 'sequential' - # TODO: consensual = 'consensual' - # TODO: hierarchical = 'hierarchical' + """ + Class representing the different processes that can be used to tackle tasks + """ + + sequential = "sequential" + # TODO: consensual = 'consensual' + # TODO: hierarchical = 'hierarchical' diff --git a/crewai/prompts.py b/crewai/prompts.py index 4fa8a33e3..f1219cb39 100644 --- a/crewai/prompts.py +++ b/crewai/prompts.py @@ -2,32 +2,41 @@ from textwrap import dedent from typing import ClassVar -from pydantic.v1 import BaseModel + from langchain.prompts import PromptTemplate +from pydantic.v1 import BaseModel + class Prompts(BaseModel): - """Prompts for generic agent.""" + """Prompts for generic agent.""" - TASK_SLICE: ClassVar[str] = dedent("""\ + TASK_SLICE: ClassVar[str] = dedent( + """\ Begin! This is VERY important to you, your job depends on it! Current Task: {input} {agent_scratchpad} - """) + """ + ) - MEMORY_SLICE: ClassVar[str] = dedent("""\ + MEMORY_SLICE: ClassVar[str] = dedent( + """\ This is the summary of your work so far: {chat_history} - """) + """ + ) - ROLE_PLAYING_SLICE: ClassVar[str] = dedent("""\ + ROLE_PLAYING_SLICE: ClassVar[str] = dedent( + """\ You are {role}. {backstory} Your personal goal is: {goal} - """) + """ + ) - TOOLS_SLICE: ClassVar[str] = dedent("""\ + TOOLS_SLICE: ClassVar[str] = dedent( + """\ TOOLS: ------ @@ -50,9 +59,11 @@ class Prompts(BaseModel): Thought: Do I need to use a tool? No Final Answer: [your response here] ``` - """) + """ + ) - VOTING_SLICE: ClassVar[str] = dedent("""\ + VOTING_SLICE: ClassVar[str] = dedent( + """\ You are working on a crew with your co-workers and need to decide who will execute the task. These are your format instructions: @@ -60,16 +71,17 @@ class Prompts(BaseModel): These are your co-workers and their roles: {coworkers} - """) + """ + ) - TASK_EXECUTION_WITH_MEMORY_PROMPT: ClassVar[str] = PromptTemplate.from_template( - ROLE_PLAYING_SLICE + TOOLS_SLICE + MEMORY_SLICE + TASK_SLICE - ) + TASK_EXECUTION_WITH_MEMORY_PROMPT: ClassVar[str] = PromptTemplate.from_template( + ROLE_PLAYING_SLICE + TOOLS_SLICE + MEMORY_SLICE + TASK_SLICE + ) - TASK_EXECUTION_PROMPT: ClassVar[str] = PromptTemplate.from_template( - ROLE_PLAYING_SLICE + TOOLS_SLICE + TASK_SLICE - ) + TASK_EXECUTION_PROMPT: ClassVar[str] = PromptTemplate.from_template( + ROLE_PLAYING_SLICE + TOOLS_SLICE + TASK_SLICE + ) - CONSENSUNS_VOTING_PROMPT: ClassVar[str] = PromptTemplate.from_template( - ROLE_PLAYING_SLICE + VOTING_SLICE + TASK_SLICE - ) + CONSENSUNS_VOTING_PROMPT: ClassVar[str] = PromptTemplate.from_template( + ROLE_PLAYING_SLICE + VOTING_SLICE + TASK_SLICE + ) diff --git a/crewai/task.py b/crewai/task.py index eaa4c68f1..6393e6285 100644 --- a/crewai/task.py +++ b/crewai/task.py @@ -1,42 +1,41 @@ from typing import List, Optional -from pydantic.v1 import BaseModel, Field, root_validator from langchain.tools import Tool +from pydantic.v1 import BaseModel, Field, root_validator from .agent import Agent + class Task(BaseModel): - """ - Class that represent a task to be executed. - """ - - description: str = Field(description="Description of the actual task.") - agent: Optional[Agent] = Field( - description="Agent responsible for the task.", - default=None - ) - tools: Optional[List[Tool]] = Field( - description="Tools the agent are limited to use for this task.", - default=[] - ) + """ + Class that represent a task to be executed. + """ - @root_validator(pre=False) - def _set_tools(_cls, values): - if (values.get('agent')) and not (values.get('tools')): - values['tools'] = values.get('agent').tools - return values + description: str = Field(description="Description of the actual task.") + agent: Optional[Agent] = Field( + description="Agent responsible for the task.", default=None + ) + tools: Optional[List[Tool]] = Field( + description="Tools the agent are limited to use for this task.", default=[] + ) - def execute(self, context: str = None) -> str: - """ - Execute the task. - Returns: - output (str): Output of the task. - """ - if self.agent: - return self.agent.execute_task( - task = self.description, - context = context, - tools = self.tools - ) - else: - raise Exception(f"The task '{self.description}' has no agent assigned, therefore it can't be executed directly and should be executed in a Crew using a specific process that support that, either consensual or hierarchical.") \ No newline at end of file + @root_validator(pre=False) + def _set_tools(_cls, values): + if (values.get("agent")) and not (values.get("tools")): + values["tools"] = values.get("agent").tools + return values + + def execute(self, context: str = None) -> str: + """ + Execute the task. + Returns: + output (str): Output of the task. + """ + if self.agent: + return self.agent.execute_task( + task=self.description, context=context, tools=self.tools + ) + else: + raise Exception( + f"The task '{self.description}' has no agent assigned, therefore it can't be executed directly and should be executed in a Crew using a specific process that support that, either consensual or hierarchical." + ) diff --git a/crewai/tools/agent_tools.py b/crewai/tools/agent_tools.py index 2e6e1b0de..8423fb03e 100644 --- a/crewai/tools/agent_tools.py +++ b/crewai/tools/agent_tools.py @@ -1,61 +1,74 @@ -from typing import List, Any -from pydantic.v1 import BaseModel, Field from textwrap import dedent +from typing import List + from langchain.tools import Tool +from pydantic.v1 import BaseModel, Field from ..agent import Agent -class AgentTools(BaseModel): - """Tools for generic agent.""" - agents: List[Agent] = Field(description="List of agents in this crew.") - def tools(self): - return [ - Tool.from_function( - func=self.delegate_work, - name="Delegate Work to Co-Worker", - description=dedent(f"""Useful to delegate a specific task to one of the +class AgentTools(BaseModel): + """Tools for generic agent.""" + + agents: List[Agent] = Field(description="List of agents in this crew.") + + def tools(self): + return [ + Tool.from_function( + func=self.delegate_work, + name="Delegate Work to Co-Worker", + description=dedent( + f"""Useful to delegate a specific task to one of the following co-workers: [{', '.join([agent.role for agent in self.agents])}]. The input to this tool should be a pipe (|) separated text of length three, representing the role you want to delegate it to, the task and information necessary. For example, `coworker|task|information`. - """) - ), - Tool.from_function( - func=self.ask_question, - name="Ask Question to Co-Worker", - description=dedent(f"""Useful to ask a question, opinion or take from on + """ + ), + ), + Tool.from_function( + func=self.ask_question, + name="Ask Question to Co-Worker", + description=dedent( + f"""Useful to ask a question, opinion or take from on of the following co-workers: [{', '.join([agent.role for agent in self.agents])}]. The input to this tool should be a pipe (|) separated text of length three, representing the role you want to ask it to, the question and information necessary. For example, `coworker|question|information`. - """) - ), - ] + """ + ), + ), + ] - def delegate_work(self, command): - """Useful to delegate a specific task to a coworker.""" - return self.__execute(command) + def delegate_work(self, command): + """Useful to delegate a specific task to a coworker.""" + return self.__execute(command) - def ask_question(self, command): - """Useful to ask a question, opinion or take from a coworker.""" - return self.__execute(command) + def ask_question(self, command): + """Useful to ask a question, opinion or take from a coworker.""" + return self.__execute(command) - def __execute(self, command): - """Execute the command.""" - try: - agent, task, information = command.split("|") - except ValueError: - return "Error executing tool. Missing exact 3 pipe (|) separated values. For example, `coworker|task|information`." + def __execute(self, command): + """Execute the command.""" + try: + agent, task, information = command.split("|") + except ValueError: + return "Error executing tool. Missing exact 3 pipe (|) separated values. For example, `coworker|task|information`." - if not agent or not task or not information: - return "Error executing tool. Missing exact 3 pipe (|) separated values. For example, `coworker|question|information`." + if not agent or not task or not information: + return "Error executing tool. Missing exact 3 pipe (|) separated values. For example, `coworker|question|information`." - agent = [available_agent for available_agent in self.agents if available_agent.role == agent] + agent = [ + available_agent + for available_agent in self.agents + if available_agent.role == agent + ] - if len(agent) == 0: - return "Error executing tool. Co-worker not found, double check the co-worker." + if len(agent) == 0: + return ( + "Error executing tool. Co-worker not found, double check the co-worker." + ) - agent = agent[0] - result = agent.execute_task(task, information) - return result \ No newline at end of file + agent = agent[0] + result = agent.execute_task(task, information) + return result diff --git a/tests/agent_test.py b/tests/agent_test.py index 558450f7b..ae7f4d312 100644 --- a/tests/agent_test.py +++ b/tests/agent_test.py @@ -1,139 +1,125 @@ """Test Agent creation and execution basic functionality.""" import pytest - from langchain.chat_models import ChatOpenAI as OpenAI from ..crewai import Agent -def test_agent_creation(): - agent = Agent( - role="test role", - goal="test goal", - backstory="test backstory" - ) - assert agent.role == "test role" - assert agent.goal == "test goal" - assert agent.backstory == "test backstory" - assert agent.tools == [] +def test_agent_creation(): + agent = Agent(role="test role", goal="test goal", backstory="test backstory") + + assert agent.role == "test role" + assert agent.goal == "test goal" + assert agent.backstory == "test backstory" + assert agent.tools == [] + def test_agent_default_values(): - agent = Agent( - role="test role", - goal="test goal", - backstory="test backstory" - ) + agent = Agent(role="test role", goal="test goal", backstory="test backstory") + + assert isinstance(agent.llm, OpenAI) + assert agent.llm.model_name == "gpt-4" + assert agent.llm.temperature == 0.7 + assert agent.llm.verbose == False + assert agent.allow_delegation == True - assert isinstance(agent.llm, OpenAI) - assert agent.llm.model_name == "gpt-4" - assert agent.llm.temperature == 0.7 - assert agent.llm.verbose == False - assert agent.allow_delegation == True def test_custom_llm(): - agent = Agent( - role="test role", - goal="test goal", - backstory="test backstory", - llm=OpenAI( - temperature=0, - model="gpt-4" - ) - ) + agent = Agent( + role="test role", + goal="test goal", + backstory="test backstory", + llm=OpenAI(temperature=0, model="gpt-4"), + ) + + assert isinstance(agent.llm, OpenAI) + assert agent.llm.model_name == "gpt-4" + assert agent.llm.temperature == 0 - assert isinstance(agent.llm, OpenAI) - assert agent.llm.model_name == "gpt-4" - assert agent.llm.temperature == 0 @pytest.mark.vcr(filter_headers=["authorization"]) def test_agent_without_memory(): - no_memory_agent = Agent( - role="test role", - goal="test goal", - backstory="test backstory", - memory=False, - llm=OpenAI( - temperature=0, - model="gpt-4" - ) - ) + no_memory_agent = Agent( + role="test role", + goal="test goal", + backstory="test backstory", + memory=False, + llm=OpenAI(temperature=0, model="gpt-4"), + ) - memory_agent = Agent( - role="test role", - goal="test goal", - backstory="test backstory", - memory=True, - llm=OpenAI( - temperature=0, - model="gpt-4" - ) - ) + memory_agent = Agent( + role="test role", + goal="test goal", + backstory="test backstory", + memory=True, + llm=OpenAI(temperature=0, model="gpt-4"), + ) - result = no_memory_agent.execute_task("How much is 1 + 1?") + result = no_memory_agent.execute_task("How much is 1 + 1?") + + assert result == "1 + 1 equals 2." + assert no_memory_agent.agent_executor.memory is None + assert memory_agent.agent_executor.memory is not None - assert result == "1 + 1 equals 2." - assert no_memory_agent.agent_executor.memory is None - assert memory_agent.agent_executor.memory is not None @pytest.mark.vcr(filter_headers=["authorization"]) def test_agent_execution(): - agent = Agent( - role="test role", - goal="test goal", - backstory="test backstory", - allow_delegation=False - ) + agent = Agent( + role="test role", + goal="test goal", + backstory="test backstory", + allow_delegation=False, + ) + + output = agent.execute_task("How much is 1 + 1?") + assert output == "2" - output = agent.execute_task("How much is 1 + 1?") - assert output == "2" @pytest.mark.vcr(filter_headers=["authorization"]) def test_agent_execution_with_tools(): - from langchain.tools import tool + from langchain.tools import tool - @tool - def multiplier(numbers) -> float: - """Useful for when you need to multiply two numbers together. - The input to this tool should be a comma separated list of numbers of - length two, representing the two numbers you want to multiply together. - For example, `1,2` would be the input if you wanted to multiply 1 by 2.""" - a, b = numbers.split(',') - return int(a) * int(b) + @tool + def multiplier(numbers) -> float: + """Useful for when you need to multiply two numbers together. + The input to this tool should be a comma separated list of numbers of + length two, representing the two numbers you want to multiply together. + For example, `1,2` would be the input if you wanted to multiply 1 by 2.""" + a, b = numbers.split(",") + return int(a) * int(b) - agent = Agent( - role="test role", - goal="test goal", - backstory="test backstory", - tools=[multiplier], - allow_delegation=False - ) + agent = Agent( + role="test role", + goal="test goal", + backstory="test backstory", + tools=[multiplier], + allow_delegation=False, + ) + + output = agent.execute_task("What is 3 times 4") + assert output == "12" - output = agent.execute_task("What is 3 times 4") - assert output == "12" @pytest.mark.vcr(filter_headers=["authorization"]) def test_agent_execution_with_specific_tools(): - from langchain.tools import tool + from langchain.tools import tool - @tool - def multiplier(numbers) -> float: - """Useful for when you need to multiply two numbers together. - The input to this tool should be a comma separated list of numbers of - length two, representing the two numbers you want to multiply together. - For example, `1,2` would be the input if you wanted to multiply 1 by 2.""" - a, b = numbers.split(',') - return int(a) * int(b) + @tool + def multiplier(numbers) -> float: + """Useful for when you need to multiply two numbers together. + The input to this tool should be a comma separated list of numbers of + length two, representing the two numbers you want to multiply together. + For example, `1,2` would be the input if you wanted to multiply 1 by 2.""" + a, b = numbers.split(",") + return int(a) * int(b) - agent = Agent( - role="test role", - goal="test goal", - backstory="test backstory", - allow_delegation=False - ) + agent = Agent( + role="test role", + goal="test goal", + backstory="test backstory", + allow_delegation=False, + ) - output = agent.execute_task( - task="What is 3 times 4", - tools=[multiplier] - ) - assert output == "3 times 4 is 12." \ No newline at end of file + output = agent.execute_task(task="What is 3 times 4", tools=[multiplier]) + assert output == "3 times 4 is 12." diff --git a/tests/conftest.py b/tests/conftest.py index abbfc3c0a..4fdb3b144 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,4 @@ # conftest.py from dotenv import load_dotenv -load_result = load_dotenv(override=True) \ No newline at end of file + +load_result = load_dotenv(override=True) diff --git a/tests/crew_test.py b/tests/crew_test.py index 388c74da3..56bacfcd9 100644 --- a/tests/crew_test.py +++ b/tests/crew_test.py @@ -1,114 +1,131 @@ """Test Agent creation and execution basic functionality.""" import json + import pytest -from ..crewai import Agent, Crew, Task, Process + +from ..crewai import Agent, Crew, Process, Task ceo = Agent( - role="CEO", - goal="Make sure the writers in your company produce amazing content.", - backstory="You're an long time CEO of a content creation agency with a Senior Writer on the team. You're now working on a new project and want to make sure the content produced is amazing.", - allow_delegation=True + role="CEO", + goal="Make sure the writers in your company produce amazing content.", + backstory="You're an long time CEO of a content creation agency with a Senior Writer on the team. You're now working on a new project and want to make sure the content produced is amazing.", + allow_delegation=True, ) researcher = Agent( - role="Researcher", - goal="Make the best research and analysis on content about AI and AI agents", - backstory="You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.", - allow_delegation=False + role="Researcher", + goal="Make the best research and analysis on content about AI and AI agents", + backstory="You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.", + allow_delegation=False, ) writer = Agent( - role="Senior Writer", - goal="Write the best content about AI and AI agents.", - backstory="You're a senior writer, specialized in technology, software engineering, AI and startups. You work as a freelancer and are now working on writing content for a new customer.", - allow_delegation=False + role="Senior Writer", + goal="Write the best content about AI and AI agents.", + backstory="You're a senior writer, specialized in technology, software engineering, AI and startups. You work as a freelancer and are now working on writing content for a new customer.", + allow_delegation=False, ) + def test_crew_config_conditional_requirement(): - with pytest.raises(ValueError): - Crew(process=Process.sequential) + with pytest.raises(ValueError): + Crew(process=Process.sequential) - config = json.dumps({ - "agents": [ - { - "role": "Senior Researcher", - "goal": "Make the best research and analysis on content about AI and AI agents", - "backstory": "You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer." - }, - { - "role": "Senior Writer", - "goal": "Write the best content about AI and AI agents.", - "backstory": "You're a senior writer, specialized in technology, software engineering, AI and startups. You work as a freelancer and are now working on writing content for a new customer." - } - ], - "tasks": [ - { - "description": "Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.", - "agent": "Senior Researcher" - }, - { - "description": "Write a 1 amazing paragraph highlight for each idead that showcases how good an article about this topic could be, check references if necessary or search for more content but make sure it's unique, interesting and well written. Return the list of ideas with their paragraph and your notes.", - "agent": "Senior Writer" - } - ] - }) - parsed_config = json.loads(config) + config = json.dumps( + { + "agents": [ + { + "role": "Senior Researcher", + "goal": "Make the best research and analysis on content about AI and AI agents", + "backstory": "You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.", + }, + { + "role": "Senior Writer", + "goal": "Write the best content about AI and AI agents.", + "backstory": "You're a senior writer, specialized in technology, software engineering, AI and startups. You work as a freelancer and are now working on writing content for a new customer.", + }, + ], + "tasks": [ + { + "description": "Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.", + "agent": "Senior Researcher", + }, + { + "description": "Write a 1 amazing paragraph highlight for each idead that showcases how good an article about this topic could be, check references if necessary or search for more content but make sure it's unique, interesting and well written. Return the list of ideas with their paragraph and your notes.", + "agent": "Senior Writer", + }, + ], + } + ) + parsed_config = json.loads(config) - try: - crew = Crew(process=Process.sequential, config=config) - except ValueError: - pytest.fail("Unexpected ValidationError raised") + try: + crew = Crew(process=Process.sequential, config=config) + except ValueError: + pytest.fail("Unexpected ValidationError raised") + + assert [agent.role for agent in crew.agents] == [ + agent["role"] for agent in parsed_config["agents"] + ] + assert [task.description for task in crew.tasks] == [ + task["description"] for task in parsed_config["tasks"] + ] - assert [agent.role for agent in crew.agents] == [agent['role'] for agent in parsed_config['agents']] - assert [task.description for task in crew.tasks] == [task['description'] for task in parsed_config['tasks']] def test_crew_config_with_wrong_keys(): - no_tasks_config = json.dumps({ - "agents": [ - { - "role": "Senior Researcher", - "goal": "Make the best research and analysis on content about AI and AI agents", - "backstory": "You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer." - } - ] - }) + no_tasks_config = json.dumps( + { + "agents": [ + { + "role": "Senior Researcher", + "goal": "Make the best research and analysis on content about AI and AI agents", + "backstory": "You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.", + } + ] + } + ) + + no_agents_config = json.dumps( + { + "tasks": [ + { + "description": "Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.", + "agent": "Senior Researcher", + } + ] + } + ) + with pytest.raises(ValueError): + Crew(process=Process.sequential, config='{"wrong_key": "wrong_value"}') + with pytest.raises(ValueError): + Crew(process=Process.sequential, config=no_tasks_config) + with pytest.raises(ValueError): + Crew(process=Process.sequential, config=no_agents_config) - no_agents_config = json.dumps({ - "tasks": [ - { - "description": "Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.", - "agent": "Senior Researcher" - } - ] - }) - with pytest.raises(ValueError): - Crew(process=Process.sequential, config='{"wrong_key": "wrong_value"}') - with pytest.raises(ValueError): - Crew(process=Process.sequential, config=no_tasks_config) - with pytest.raises(ValueError): - Crew(process=Process.sequential, config=no_agents_config) @pytest.mark.vcr(filter_headers=["authorization"]) def test_crew_creation(): - tasks = [ - Task( - description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.", - agent=researcher - ), - Task( - description="Write a 1 amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.", - agent=writer - ) - ] + tasks = [ + Task( + description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.", + agent=researcher, + ), + Task( + description="Write a 1 amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.", + agent=writer, + ), + ] - crew = Crew( - agents=[researcher, writer], - process=Process.sequential, - tasks=tasks, - ) + crew = Crew( + agents=[researcher, writer], + process=Process.sequential, + tasks=tasks, + ) - assert crew.kickoff() == """1. **The Evolution of AI: From Old Concepts to New Frontiers** - Journey with us as we traverse the fascinating timeline of artificial intelligence - from its philosophical and mathematical infancy to the sophisticated, problem-solving tool it has become today. This riveting account will not only educate but also inspire, as we delve deep into the milestones that brought us here and shine a beacon on the potential that lies ahead. + assert ( + crew.kickoff() + == """1. **The Evolution of AI: From Old Concepts to New Frontiers** - Journey with us as we traverse the fascinating timeline of artificial intelligence - from its philosophical and mathematical infancy to the sophisticated, problem-solving tool it has become today. This riveting account will not only educate but also inspire, as we delve deep into the milestones that brought us here and shine a beacon on the potential that lies ahead. 2. **AI Agents in Healthcare: The Future of Medicine** - Imagine a world where illnesses are diagnosed before symptoms appear, where patient outcomes are not mere guesses but accurate predictions. This is the world AI is crafting in healthcare - a revolution that's saving lives and changing the face of medicine as we know it. This article will spotlight this transformative journey, underlining the profound impact AI is having on our health and well-being. @@ -117,60 +134,60 @@ def test_crew_creation(): 4. **Demystifying AI Algorithms: A Deep Dive into Machine Learning** - Ever wondered what goes on behind the scenes of AI? This enlightening article will break down the complex world of machine learning algorithms into digestible insights, unraveling the mystery of AI's 'black box'. It's a rare opportunity for the non-technical audience to appreciate the inner workings of AI, fostering a deeper understanding of this revolutionary technology. 5. **AI Startups: The Game Changers of the Tech Industry** - In the world of tech, AI startups are the bold pioneers charting new territories. This article will spotlight these game changers, showcasing how their innovative products and services are driving the AI revolution. It's a unique opportunity to catch a glimpse of the entrepreneurial side of AI, offering inspiration for the tech enthusiasts and dreamers alike.""" + ) + @pytest.mark.vcr(filter_headers=["authorization"]) def test_crew_with_delegating_agents(): - tasks = [ - Task( - description="Produce and amazing 1 paragraph draft of an article about AI Agents.", - agent=ceo - ) - ] + tasks = [ + Task( + description="Produce and amazing 1 paragraph draft of an article about AI Agents.", + agent=ceo, + ) + ] - crew = Crew( - agents=[ceo, writer], - process=Process.sequential, - tasks=tasks, - ) + crew = Crew( + agents=[ceo, writer], + process=Process.sequential, + tasks=tasks, + ) + + assert ( + crew.kickoff() + == "The Senior Writer has created a compelling and engaging 1 paragraph draft about AI agents. The paragraph provides a brief yet comprehensive overview of AI agents, their uses, and implications in the current world. It emphasizes their potential and the role they can play in the future. The tone is informative but captivating, meeting the objectives of the task." + ) - assert crew.kickoff() == 'The Senior Writer has created a compelling and engaging 1 paragraph draft about AI agents. The paragraph provides a brief yet comprehensive overview of AI agents, their uses, and implications in the current world. It emphasizes their potential and the role they can play in the future. The tone is informative but captivating, meeting the objectives of the task.' @pytest.mark.vcr(filter_headers=["authorization"]) def test_crew_verbose_output(capsys): - tasks = [ - Task( - description="Research AI advancements.", - agent=researcher - ), - Task( - description="Write about AI in healthcare.", - agent=writer - ) - ] + tasks = [ + Task(description="Research AI advancements.", agent=researcher), + Task(description="Write about AI in healthcare.", agent=writer), + ] - crew = Crew( - agents=[researcher, writer], - tasks=tasks, - process=Process.sequential, - verbose=True - ) + crew = Crew( + agents=[researcher, writer], + tasks=tasks, + process=Process.sequential, + verbose=True, + ) - crew.kickoff() - captured = capsys.readouterr() - expected_strings = [ - "Working Agent: Researcher", - "Starting Task: Research AI advancements. ...", - "Task output:", - "Working Agent: Senior Writer", - "Starting Task: Write about AI in healthcare. ...", - "Task output:" - ] + crew.kickoff() + captured = capsys.readouterr() + expected_strings = [ + "Working Agent: Researcher", + "Starting Task: Research AI advancements. ...", + "Task output:", + "Working Agent: Senior Writer", + "Starting Task: Write about AI in healthcare. ...", + "Task output:", + ] - for expected_string in expected_strings: - assert expected_string in captured.out + for expected_string in expected_strings: + assert expected_string in captured.out - # Now test with verbose set to False - crew.verbose = False - crew.kickoff() - captured = capsys.readouterr() - assert captured.out == "" \ No newline at end of file + # Now test with verbose set to False + crew.verbose = False + crew.kickoff() + captured = capsys.readouterr() + assert captured.out == "" diff --git a/tests/task_test.py b/tests/task_test.py index f53946d32..dcfcbe873 100644 --- a/tests/task_test.py +++ b/tests/task_test.py @@ -1,57 +1,56 @@ """Test Agent creation and execution basic functionality.""" -import pytest + from ..crewai import Agent, Task + def test_task_tool_reflect_agent_tools(): - from langchain.tools import tool + from langchain.tools import tool - @tool - def fake_tool() -> None: - "Fake tool" - pass - - researcher = Agent( - role="Researcher", - goal="Make the best research and analysis on content about AI and AI agents", - backstory="You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.", - tools=[fake_tool], - allow_delegation=False - ) + @tool + def fake_tool() -> None: + "Fake tool" + + researcher = Agent( + role="Researcher", + goal="Make the best research and analysis on content about AI and AI agents", + backstory="You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.", + tools=[fake_tool], + allow_delegation=False, + ) + + task = Task( + description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.", + agent=researcher, + ) + + assert task.tools == [fake_tool] - task = Task( - description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.", - agent=researcher - ) - - assert task.tools == [fake_tool] def test_task_tool_takes_precedence_ove_agent_tools(): - from langchain.tools import tool + from langchain.tools import tool - @tool - def fake_tool() -> None: - "Fake tool" - pass + @tool + def fake_tool() -> None: + "Fake tool" - @tool - def fake_task_tool() -> None: - "Fake tool" - pass - - researcher = Agent( - role="Researcher", - goal="Make the best research and analysis on content about AI and AI agents", - backstory="You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.", - tools=[fake_tool], - allow_delegation=False - ) + @tool + def fake_task_tool() -> None: + "Fake tool" - task = Task( - description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.", - agent=researcher, - tools=[fake_task_tool], - allow_delegation=False - ) - - assert task.tools == [fake_task_tool] \ No newline at end of file + researcher = Agent( + role="Researcher", + goal="Make the best research and analysis on content about AI and AI agents", + backstory="You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.", + tools=[fake_tool], + allow_delegation=False, + ) + + task = Task( + description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.", + agent=researcher, + tools=[fake_task_tool], + allow_delegation=False, + ) + + assert task.tools == [fake_task_tool] From 92f192fc5ecb4ee1be0450800d3518f078926a86 Mon Sep 17 00:00:00 2001 From: Greyson Lalonde Date: Wed, 27 Dec 2023 13:32:16 -0500 Subject: [PATCH 6/6] Make tools a subpackage --- crewai/tools/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 crewai/tools/__init__.py diff --git a/crewai/tools/__init__.py b/crewai/tools/__init__.py new file mode 100644 index 000000000..e69de29bb