Feat/poetry to uv migration (#1406)

* feat: Start migrating to UV

* feat: add uv to flows

* feat: update docs on Poetry -> uv

* feat: update docs and uv.locl

* feat: update tests and github CI

* feat: run ruff format

* feat: update typechecking

* feat: fix type checking

* feat: update python version

* feat: type checking gic

* feat: adapt uv command to run the tool repo

* Adapt tool build command to uv

* feat: update logic to let only projects with crew to be deployed

* feat: add uv to tools

* fix; tests

* fix: remove breakpoint

* fix :test

* feat: add crewai update to migrate from poetry to uv

* fix: tests

* feat: add validation for ˆ character on pyproject

* feat: add run_crew to pyproject if doesnt exist

* feat: add validation for poetry migration

* fix: warning

---------

Co-authored-by: Vinicius Brasil <vini@hey.com>
This commit is contained in:
Eduardo Chiarotti
2024-10-11 19:11:27 -03:00
committed by GitHub
parent 6534a909d6
commit 7f387dd7c3
33 changed files with 5753 additions and 314 deletions

View File

@@ -317,9 +317,9 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
if self.crew is not None and hasattr(self.crew, "_train_iteration"):
train_iteration = self.crew._train_iteration
if agent_id in training_data and isinstance(train_iteration, int):
training_data[agent_id][train_iteration][
"improved_output"
] = result.output
training_data[agent_id][train_iteration]["improved_output"] = (
result.output
)
training_handler.save(training_data)
else:
self._logger.log(

View File

@@ -21,6 +21,7 @@ from .run_crew import run_crew
from .run_flow import run_flow
from .tools.main import ToolCommand
from .train_crew import train_crew
from .update_crew import update_crew
@click.group()
@@ -188,6 +189,12 @@ def run():
run_crew()
@crewai.command()
def update():
"""Update the pyproject.toml of the Crew project to use uv."""
update_crew()
@crewai.command()
def signup():
"""Sign Up/Login to CrewAI+."""
@@ -276,7 +283,13 @@ def tool_install(handle: str):
@tool.command(name="publish")
@click.option("--force", is_flag=True, show_default=True, default=False, help="Bypasses Git remote validations")
@click.option(
"--force",
is_flag=True,
show_default=True,
default=False,
help="Bypasses Git remote validations",
)
@click.option("--public", "is_public", flag_value=True, default=False)
@click.option("--private", "is_public", flag_value=False)
def tool_publish(is_public: bool, force: bool):

View File

@@ -5,13 +5,13 @@ import click
def evaluate_crew(n_iterations: int, model: str) -> None:
"""
Test and Evaluate the crew by running a command in the Poetry environment.
Test and Evaluate the crew by running a command in the UV environment.
Args:
n_iterations (int): The number of iterations to test the crew.
model (str): The model to test the crew with.
"""
command = ["poetry", "run", "test", str(n_iterations), model]
command = ["uv", "run", "test", str(n_iterations), model]
try:
if n_iterations <= 0:

View File

@@ -5,13 +5,10 @@ import click
def install_crew() -> None:
"""
Install the crew by running the Poetry command to lock and install.
Install the crew by running the UV command to lock and install.
"""
try:
subprocess.run(["poetry", "lock"], check=True, capture_output=False, text=True)
subprocess.run(
["poetry", "install"], check=True, capture_output=False, text=True
)
subprocess.run(["uv", "sync"], check=True, capture_output=False, text=True)
except subprocess.CalledProcessError as e:
click.echo(f"An error occurred while running the crew: {e}", err=True)

View File

@@ -5,9 +5,9 @@ import click
def plot_flow() -> None:
"""
Plot the flow by running a command in the Poetry environment.
Plot the flow by running a command in the UV environment.
"""
command = ["poetry", "run", "plot_flow"]
command = ["uv", "run", "plot_flow"]
try:
result = subprocess.run(command, capture_output=False, text=True, check=True)

View File

@@ -1,4 +1,5 @@
import subprocess
import click
@@ -9,7 +10,7 @@ def replay_task_command(task_id: str) -> None:
Args:
task_id (str): The ID of the task to replay from.
"""
command = ["poetry", "run", "replay", task_id]
command = ["uv", "run", "replay", task_id]
try:
result = subprocess.run(command, capture_output=False, text=True, check=True)

View File

@@ -5,19 +5,21 @@ import click
def run_crew() -> None:
"""
Run the crew by running a command in the Poetry environment.
Run the crew by running a command in the UV environment.
"""
command = ["poetry", "run", "run_crew"]
command = ["uv", "run", "run_crew"]
try:
result = subprocess.run(command, capture_output=False, text=True, check=True)
if result.stderr:
click.echo(result.stderr, err=True)
subprocess.run(command, capture_output=True, text=True, check=True)
except subprocess.CalledProcessError as e:
click.echo(f"An error occurred while running the crew: {e}", err=True)
click.echo(e.output, err=True)
click.echo(e.output, err=True, nl=True)
click.echo(e.stderr, err=True, nl=True)
if "table found" in e.stderr:
click.secho(
"It's possible that you are using an old version of crewAI that uses poetry, please run `crewai update` to update your pyproject.toml to use uv.",
fg="yellow",
)
except Exception as e:
click.echo(f"An unexpected error occurred: {e}", err=True)

View File

@@ -5,9 +5,9 @@ import click
def run_flow() -> None:
"""
Run the flow by running a command in the Poetry environment.
Run the flow by running a command in the UV environment.
"""
command = ["poetry", "run", "run_flow"]
command = ["uv", "run", "run_flow"]
try:
result = subprocess.run(command, capture_output=False, text=True, check=True)

View File

@@ -4,17 +4,17 @@ Welcome to the {{crew_name}} Crew project, powered by [crewAI](https://crewai.co
## Installation
Ensure you have Python >=3.10 <=3.13 installed on your system. This project uses [Poetry](https://python-poetry.org/) for dependency management and package handling, offering a seamless setup and execution experience.
Ensure you have Python >=3.10 <=3.13 installed on your system. This project uses [UV](https://docs.astral.sh/uv/) for dependency management and package handling, offering a seamless setup and execution experience.
First, if you haven't already, install Poetry:
First, if you haven't already, install uv:
```bash
pip install poetry
pip install uv
```
Next, navigate to your project directory and install the dependencies:
1. First lock the dependencies and install them by using the CLI command:
(Optional) Lock the dependencies and install them by using the CLI command:
```bash
crewai install
```

View File

@@ -1,15 +1,14 @@
[tool.poetry]
[project]
name = "{{folder_name}}"
version = "0.1.0"
description = "{{name}} using crewAI"
authors = ["Your Name <you@example.com>"]
authors = [{ name = "Your Name", email = "you@example.com" }]
requires-python = ">=3.10,<=3.13"
dependencies = [
"crewai[tools]>=0.67.1,<1.0.0"
]
[tool.poetry.dependencies]
python = ">=3.10,<=3.13"
crewai = { extras = ["tools"], version = ">=0.70.1,<1.0.0" }
[tool.poetry.scripts]
[project.scripts]
{{folder_name}} = "{{folder_name}}.main:run"
run_crew = "{{folder_name}}.main:run"
train = "{{folder_name}}.main:train"
@@ -17,5 +16,5 @@ replay = "{{folder_name}}.main:replay"
test = "{{folder_name}}.main:test"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
requires = ["hatchling"]
build-backend = "hatchling.build"

View File

@@ -4,18 +4,17 @@ Welcome to the {{crew_name}} Crew project, powered by [crewAI](https://crewai.co
## Installation
Ensure you have Python >=3.10 <=3.13 installed on your system. This project uses [Poetry](https://python-poetry.org/) for dependency management and package handling, offering a seamless setup and execution experience.
Ensure you have Python >=3.10 <=3.13 installed on your system. This project uses [UV](https://docs.astral.sh/uv/) for dependency management and package handling, offering a seamless setup and execution experience.
First, if you haven't already, install Poetry:
First, if you haven't already, install uv:
```bash
pip install poetry
pip install uv
```
Next, navigate to your project directory and install the dependencies:
1. First lock the dependencies and then install them:
(Optional) Lock the dependencies and install them by using the CLI command:
```bash
crewai install
```

View File

@@ -1,19 +1,19 @@
[tool.poetry]
[project]
name = "{{folder_name}}"
version = "0.1.0"
description = "{{name}} using crewAI"
authors = ["Your Name <you@example.com>"]
authors = [{ name = "Your Name", email = "you@example.com" }]
requires-python = ">=3.10,<=3.13"
dependencies = [
"crewai[tools]>=0.67.1,<1.0.0",
"asyncio"
]
[tool.poetry.dependencies]
python = ">=3.10,<=3.13"
crewai = { extras = ["tools"], version = ">=0.70.1,<1.0.0" }
asyncio = "*"
[tool.poetry.scripts]
[project.scripts]
{{folder_name}} = "{{folder_name}}.main:main"
run_flow = "{{folder_name}}.main:main"
plot_flow = "{{folder_name}}.main:plot"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
requires = ["hatchling"]
build-backend = "hatchling.build"

View File

@@ -1,20 +1,21 @@
[tool.poetry]
[project]
name = "{{folder_name}}"
version = "0.1.0"
description = "{{name}} using crewAI"
authors = ["Your Name <you@example.com>"]
requires-python = ">=3.10,<=3.13"
dependencies = [
"crewai[tools]>=0.67.1,<1.0.0"
]
[tool.poetry.dependencies]
python = ">=3.10,<=3.13"
crewai = { extras = ["tools"], version = ">=0.70.1,<1.0.0" }
[tool.poetry.scripts]
[project.scripts]
{{folder_name}} = "{{folder_name}}.main:main"
run_crew = "{{folder_name}}.main:main"
train = "{{folder_name}}.main:train"
replay = "{{folder_name}}.main:replay"
test = "{{folder_name}}.main:test"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
requires = ["hatchling"]
build-backend = "hatchling.build"

View File

@@ -6,13 +6,13 @@ custom tools to power up your crews.
## Installing
Ensure you have Python >=3.10 <=3.13 installed on your system. This project
uses [Poetry](https://python-poetry.org/) for dependency management and package
uses [UV](https://docs.astral.sh/uv/) for dependency management and package
handling, offering a seamless setup and execution experience.
First, if you haven't already, install Poetry:
First, if you haven't already, install `uv`:
```bash
pip install poetry
pip install uv
```
Next, navigate to your project directory and install the dependencies with:

View File

@@ -1,20 +1,22 @@
import base64
from pathlib import Path
import click
import os
import platform
import subprocess
import tempfile
from pathlib import Path
import click
from rich.console import Console
from crewai.cli.command import BaseCommand, PlusAPIMixin
from crewai.cli import git
from crewai.cli.command import BaseCommand, PlusAPIMixin
from crewai.cli.utils import (
get_project_name,
get_project_description,
get_project_name,
get_project_version,
tree_copy,
tree_find_and_replace,
)
from rich.console import Console
console = Console()
@@ -24,6 +26,8 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
A class to handle tool repository related operations for CrewAI projects.
"""
BASE_URL = "https://app.crewai.com/pypi/"
def __init__(self):
BaseCommand.__init__(self)
PlusAPIMixin.__init__(self, telemetry=self._telemetry)
@@ -82,7 +86,7 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
with tempfile.TemporaryDirectory() as temp_build_dir:
subprocess.run(
["poetry", "build", "-f", "sdist", "--output", temp_build_dir],
["uv", "build", "--sdist", "--out-dir", temp_build_dir],
check=True,
capture_output=False,
)
@@ -92,7 +96,7 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
)
if not tarball_filename:
console.print(
"Project build failed. Please ensure that the command `poetry build -f sdist` completes successfully.",
"Project build failed. Please ensure that the command `uv build --sdist` completes successfully.",
style="bold red",
)
raise SystemExit
@@ -149,62 +153,39 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
raise SystemExit
login_response_json = login_response.json()
for repository in login_response_json["repositories"]:
self._add_repository_to_poetry(
repository, login_response_json["credential"]
)
self._set_netrc_credentials(login_response_json["credential"])
console.print(
"Succesfully authenticated to the tool repository.", style="bold green"
"Successfully authenticated to the tool repository.", style="bold green"
)
def _add_repository_to_poetry(self, repository, credentials):
repository_handle = f"crewai-{repository['handle']}"
def _set_netrc_credentials(self, credentials):
# Create .netrc or _netrc file
netrc_filename = "_netrc" if platform.system() == "Windows" else ".netrc"
netrc_path = Path.home() / netrc_filename
add_repository_command = [
"poetry",
"source",
"add",
"--priority=explicit",
repository_handle,
repository["url"],
]
add_repository_result = subprocess.run(
add_repository_command, text=True, check=True
)
netrc_content = f"""machine app.crewai.com
login {credentials['username']}
password {credentials['password']}
"""
if add_repository_result.stderr:
click.echo(add_repository_result.stderr, err=True)
raise SystemExit
with open(netrc_path, "a") as netrc_file:
netrc_file.write(netrc_content)
add_repository_credentials_command = [
"poetry",
"config",
f"http-basic.{repository_handle}",
credentials["username"],
credentials["password"],
]
add_repository_credentials_result = subprocess.run(
add_repository_credentials_command,
capture_output=False,
text=True,
check=True,
)
if add_repository_credentials_result.stderr:
click.echo(add_repository_credentials_result.stderr, err=True)
raise SystemExit
# Set appropriate permissions for Unix-like systems
if platform.system() != "Windows":
os.chmod(netrc_path, 0o600)
console.print(f"Added credentials to {netrc_filename}", style="bold green")
def _add_package(self, tool_details):
tool_handle = tool_details["handle"]
repository_handle = tool_details["repository"]["handle"]
pypi_index_handle = f"crewai-{repository_handle}"
add_package_command = [
"poetry",
"uv",
"add",
"--source",
pypi_index_handle,
"--extra-index-url",
self.BASE_URL + repository_handle,
tool_handle,
]
add_package_result = subprocess.run(

View File

@@ -5,12 +5,12 @@ import click
def train_crew(n_iterations: int, filename: str) -> None:
"""
Train the crew by running a command in the Poetry environment.
Train the crew by running a command in the UV environment.
Args:
n_iterations (int): The number of iterations to train the crew.
"""
command = ["poetry", "run", "train", str(n_iterations), filename]
command = ["uv", "run", "train", str(n_iterations), filename]
try:
if n_iterations <= 0:

View File

@@ -0,0 +1,115 @@
import shutil
import tomli_w
import tomllib
def update_crew() -> None:
"""Update the pyproject.toml of the Crew project to use uv."""
migrate_pyproject("pyproject.toml", "pyproject.toml")
def migrate_pyproject(input_file, output_file):
"""
Migrate the pyproject.toml to the new format.
This function is used to migrate the pyproject.toml to the new format.
And it will be used to migrate the pyproject.toml to the new format when uv is used.
When the time comes that uv supports the new format, this function will be deprecated.
"""
# Read the input pyproject.toml
with open(input_file, "rb") as f:
pyproject = tomllib.load(f)
# Initialize the new project structure
new_pyproject = {
"project": {},
"build-system": {"requires": ["hatchling"], "build-backend": "hatchling.build"},
}
# Migrate project metadata
if "tool" in pyproject and "poetry" in pyproject["tool"]:
poetry = pyproject["tool"]["poetry"]
new_pyproject["project"]["name"] = poetry.get("name")
new_pyproject["project"]["version"] = poetry.get("version")
new_pyproject["project"]["description"] = poetry.get("description")
new_pyproject["project"]["authors"] = [
{
"name": author.split("<")[0].strip(),
"email": author.split("<")[1].strip(">").strip(),
}
for author in poetry.get("authors", [])
]
new_pyproject["project"]["requires-python"] = poetry.get("python")
else:
# If it's already in the new format, just copy the project section
new_pyproject["project"] = pyproject.get("project", {})
# Migrate or copy dependencies
if "dependencies" in new_pyproject["project"]:
# If dependencies are already in the new format, keep them as is
pass
elif "dependencies" in poetry:
new_pyproject["project"]["dependencies"] = []
for dep, version in poetry["dependencies"].items():
if isinstance(version, dict): # Handle extras
extras = ",".join(version.get("extras", []))
new_dep = f"{dep}[{extras}]"
if "version" in version:
new_dep += parse_version(version["version"])
elif dep == "python":
new_pyproject["project"]["requires-python"] = version
continue
else:
new_dep = f"{dep}{parse_version(version)}"
new_pyproject["project"]["dependencies"].append(new_dep)
# Migrate or copy scripts
if "scripts" in poetry:
new_pyproject["project"]["scripts"] = poetry["scripts"]
elif "scripts" in pyproject.get("project", {}):
new_pyproject["project"]["scripts"] = pyproject["project"]["scripts"]
else:
new_pyproject["project"]["scripts"] = {}
if (
"run_crew" not in new_pyproject["project"]["scripts"]
and len(new_pyproject["project"]["scripts"]) > 0
):
# Extract the module name from any existing script
existing_scripts = new_pyproject["project"]["scripts"]
module_name = next(
(value.split(".")[0] for value in existing_scripts.values() if "." in value)
)
new_pyproject["project"]["scripts"]["run_crew"] = f"{module_name}.main:run"
# Migrate optional dependencies
if "extras" in poetry:
new_pyproject["project"]["optional-dependencies"] = poetry["extras"]
# Backup the old pyproject.toml
backup_file = "pyproject-old.toml"
shutil.copy2(input_file, backup_file)
print(f"Original pyproject.toml backed up as {backup_file}")
# Write the new pyproject.toml
with open(output_file, "wb") as f:
tomli_w.dump(new_pyproject, f)
print(f"Migration complete. New pyproject.toml written to {output_file}")
def parse_version(version: str) -> str:
"""Parse and convert version specifiers."""
if version.startswith("^"):
main_lib_version = version[1:].split(",")[0]
addtional_lib_version = None
if len(version[1:].split(",")) > 1:
addtional_lib_version = version[1:].split(",")[1]
return f">={main_lib_version}" + (
f",{addtional_lib_version}" if addtional_lib_version else ""
)
return version

View File

@@ -1,13 +1,14 @@
import importlib.metadata
import os
import shutil
import click
import sys
import importlib.metadata
from functools import reduce
from typing import Any, Dict, List
import click
from rich.console import Console
from crewai.cli.authentication.utils import TokenManager
from functools import reduce
from rich.console import Console
from typing import Any, Dict, List
if sys.version_info >= (3, 11):
import tomllib
@@ -55,17 +56,14 @@ def simple_toml_parser(content):
def parse_toml(content):
if sys.version_info >= (3, 11):
return tomllib.loads(content)
else:
return simple_toml_parser(content)
return simple_toml_parser(content)
def get_project_name(
pyproject_path: str = "pyproject.toml", require: bool = False
) -> str | None:
"""Get the project name from the pyproject.toml file."""
return _get_project_attribute(
pyproject_path, ["tool", "poetry", "name"], require=require
)
return _get_project_attribute(pyproject_path, ["project", "name"], require=require)
def get_project_version(
@@ -73,7 +71,7 @@ def get_project_version(
) -> str | None:
"""Get the project version from the pyproject.toml file."""
return _get_project_attribute(
pyproject_path, ["tool", "poetry", "version"], require=require
pyproject_path, ["project", "version"], require=require
)
@@ -82,7 +80,7 @@ def get_project_description(
) -> str | None:
"""Get the project description from the pyproject.toml file."""
return _get_project_attribute(
pyproject_path, ["tool", "poetry", "description"], require=require
pyproject_path, ["project", "description"], require=require
)
@@ -97,10 +95,9 @@ def _get_project_attribute(
pyproject_content = parse_toml(f.read())
dependencies = (
_get_nested_value(pyproject_content, ["tool", "poetry", "dependencies"])
or {}
_get_nested_value(pyproject_content, ["project", "dependencies"]) or []
)
if "crewai" not in dependencies:
if not any(True for dep in dependencies if "crewai" in dep):
raise Exception("crewai is not in the dependencies.")
attribute = _get_nested_value(pyproject_content, keys)

View File

@@ -15,7 +15,10 @@ class EntityMemory(Memory):
storage
if storage
else RAGStorage(
type="entities", allow_reset=False, embedder_config=embedder_config, crew=crew
type="entities",
allow_reset=False,
embedder_config=embedder_config,
crew=crew,
)
)
super().__init__(storage)