From cd1257e98220a522236efa2c17b09f3241f563bd Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Tue, 3 Dec 2024 01:17:08 -0800 Subject: [PATCH 1/9] add ruff and ruff format project --- .github/workflows/python-testing.yml | 25 +- agentstack/cli/agentstack_data.py | 81 ++--- agentstack/cli/cli.py | 300 +++++++++++------- agentstack/generation/__init__.py | 2 +- agentstack/generation/agent_generation.py | 66 ++-- agentstack/generation/files.py | 70 ++-- agentstack/generation/gen_utils.py | 79 ++--- agentstack/generation/task_generation.py | 58 ++-- agentstack/generation/tool_generation.py | 235 ++++++++------ agentstack/logger.py | 4 +- agentstack/main.py | 135 +++++--- agentstack/telemetry.py | 39 +-- agentstack/utils.py | 64 ++-- examples/howards_agent/src/crew.py | 67 ++-- examples/howards_agent/src/main.py | 22 +- examples/howards_agent/src/tools/__init__.py | 1 - .../howards_agent/src/tools/composio_tool.py | 1 - examples/howards_agent/src/tools/mem0_tool.py | 6 +- examples/job_posting/src/crew.py | 53 ++-- examples/job_posting/src/main.py | 24 +- examples/job_posting/src/tools/__init__.py | 20 -- .../job_posting/src/tools/firecrawl_tool.py | 12 +- examples/stock_analysis/src/crew.py | 18 +- examples/stock_analysis/src/main.py | 26 +- examples/stock_analysis/src/tools/__init__.py | 1 - .../src/tools/perplexity_tool.py | 19 +- examples/trip_planner/src/crew.py | 132 ++++---- examples/trip_planner/src/main.py | 29 +- examples/trip_planner/src/tools/__init__.py | 1 - .../trip_planner/src/tools/browserbase.py | 2 +- examples/web_researcher/src/crew.py | 48 +-- examples/web_researcher/src/main.py | 25 +- examples/web_researcher/src/tools/__init__.py | 1 - .../src/tools/firecrawl_tool.py | 12 +- pyproject.toml | 14 + tests/test_cli_loads.py | 10 +- tests/test_generation_files.py | 66 ++-- tests/test_tool_config.py | 15 +- tests/test_utils.py | 8 +- 39 files changed, 1014 insertions(+), 777 deletions(-) diff --git a/.github/workflows/python-testing.yml b/.github/workflows/python-testing.yml index ab40e60a..940bb50b 100644 --- a/.github/workflows/python-testing.yml +++ b/.github/workflows/python-testing.yml @@ -36,4 +36,27 @@ jobs: - name: Install tox run: pip install tox - name: Run tests with tox - run: tox \ No newline at end of file + run: tox + + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install Ruff + run: | + python -m pip install --upgrade pip + pip install ruff + + - name: Run Ruff Format Check + run: | + # Check if any files would be reformatted + ruff format --check . + + # Run Ruff linting checks + ruff check . \ No newline at end of file diff --git a/agentstack/cli/agentstack_data.py b/agentstack/cli/agentstack_data.py index ca811423..93deef37 100644 --- a/agentstack/cli/agentstack_data.py +++ b/agentstack/cli/agentstack_data.py @@ -7,19 +7,22 @@ class ProjectMetadata: - def __init__(self, - project_name: str = None, - project_slug: str = None, - description: str = "", - author_name: str = "", - version: str = "", - license: str = "", - year: int = datetime.now().year, - template: str = "none", - template_version: str = "0", - ): + def __init__( + self, + project_name: str = None, + project_slug: str = None, + description: str = "", + author_name: str = "", + version: str = "", + license: str = "", + year: int = datetime.now().year, + template: str = "none", + template_version: str = "0", + ): self.project_name = clean_input(project_name) if project_name else "myagent" - self.project_slug = clean_input(project_slug) if project_slug else self.project_name + self.project_slug = ( + clean_input(project_slug) if project_slug else self.project_name + ) self.description = description self.author_name = author_name self.version = version @@ -33,16 +36,16 @@ def __init__(self, def to_dict(self): return { - 'project_name': self.project_name, - 'project_slug': self.project_slug, - 'description': self.description, - 'author_name': self.author_name, - 'version': self.version, - 'license': self.license, - 'year': self.year, - 'agentstack_version': self.agentstack_version, - 'template': self.template, - 'template_version': self.template_version, + "project_name": self.project_name, + "project_slug": self.project_slug, + "description": self.description, + "author_name": self.author_name, + "version": self.version, + "license": self.license, + "year": self.year, + "agentstack_version": self.agentstack_version, + "template": self.template, + "template_version": self.template_version, } def to_json(self): @@ -62,8 +65,8 @@ def add_task(self, task): def to_dict(self): return { - 'agents': self.agents, - 'tasks': self.tasks, + "agents": self.agents, + "tasks": self.tasks, } def to_json(self): @@ -71,15 +74,16 @@ def to_json(self): class FrameworkData: - def __init__(self, - # name: Optional[Literal["crewai"]] = None - name: str = None # TODO: better framework handling, Literal or Enum - ): + def __init__( + self, + # name: Optional[Literal["crewai"]] = None + name: str = None, # TODO: better framework handling, Literal or Enum + ): self.name = name def to_dict(self): return { - 'name': self.name, + "name": self.name, } def to_json(self): @@ -87,21 +91,22 @@ def to_json(self): class CookiecutterData: - def __init__(self, - project_metadata: ProjectMetadata, - structure: ProjectStructure, - # framework: Literal["crewai"], - framework: str, - ): + def __init__( + self, + project_metadata: ProjectMetadata, + structure: ProjectStructure, + # framework: Literal["crewai"], + framework: str, + ): self.project_metadata = project_metadata self.framework = framework self.structure = structure def to_dict(self): return { - 'project_metadata': self.project_metadata.to_dict(), - 'framework': self.framework, - 'structure': self.structure.to_dict(), + "project_metadata": self.project_metadata.to_dict(), + "framework": self.framework, + "structure": self.structure.to_dict(), } def to_json(self): diff --git a/agentstack/cli/cli.py b/agentstack/cli/cli.py index f10866b3..23703619 100644 --- a/agentstack/cli/cli.py +++ b/agentstack/cli/cli.py @@ -13,7 +13,12 @@ import importlib.resources from cookiecutter.main import cookiecutter -from .agentstack_data import FrameworkData, ProjectMetadata, ProjectStructure, CookiecutterData +from .agentstack_data import ( + FrameworkData, + ProjectMetadata, + ProjectStructure, + CookiecutterData, +) from agentstack.logger import log from agentstack.utils import get_package_path from agentstack.generation.files import ConfigFile @@ -22,55 +27,64 @@ from ..utils import open_json_file, term_color, is_snake_case PREFERRED_MODELS = [ - 'openai/gpt-4o', - 'anthropic/claude-3-5-sonnet', - 'openai/o1-preview', - 'openai/gpt-4-turbo', - 'anthropic/claude-3-opus', + "openai/gpt-4o", + "anthropic/claude-3-5-sonnet", + "openai/o1-preview", + "openai/gpt-4-turbo", + "anthropic/claude-3-opus", ] -def init_project_builder(slug_name: Optional[str] = None, template: Optional[str] = None, use_wizard: bool = False): + +def init_project_builder( + slug_name: Optional[str] = None, + template: Optional[str] = None, + use_wizard: bool = False, +): if slug_name and not is_snake_case(slug_name): - print(term_color("Project name must be snake case", 'red')) + print(term_color("Project name must be snake case", "red")) return if template is not None and use_wizard: - print(term_color("Template and wizard flags cannot be used together", 'red')) + print(term_color("Template and wizard flags cannot be used together", "red")) return template_data = None if template is not None: url_start = "https://" - if template[:len(url_start)] == url_start: + if template[: len(url_start)] == url_start: # template is a url response = requests.get(template) if response.status_code == 200: template_data = response.json() else: - print(term_color(f"Failed to fetch template data from {template}. Status code: {response.status_code}", 'red')) + print( + term_color( + f"Failed to fetch template data from {template}. Status code: {response.status_code}", + "red", + ) + ) sys.exit(1) else: - with importlib.resources.path('agentstack.templates.proj_templates', f'{template}.json') as template_path: + with importlib.resources.path( + "agentstack.templates.proj_templates", f"{template}.json" + ) as template_path: if template_path is None: - print(term_color(f"No such template {template} found", 'red')) + print(term_color(f"No such template {template} found", "red")) sys.exit(1) template_data = open_json_file(template_path) if template_data: project_details = { - "name": slug_name or template_data['name'], + "name": slug_name or template_data["name"], "version": "0.0.1", - "description": template_data['description'], + "description": template_data["description"], "author": "Name ", - "license": "MIT" - } - framework = template_data['framework'] - design = { - 'agents': template_data['agents'], - 'tasks': template_data['tasks'] + "license": "MIT", } + framework = template_data["framework"] + design = {"agents": template_data["agents"], "tasks": template_data["tasks"]} - tools = template_data['tools'] + tools = template_data["tools"] elif use_wizard: welcome_message() @@ -87,15 +101,12 @@ def init_project_builder(slug_name: Optional[str] = None, template: Optional[str "version": "0.0.1", "description": "New agentstack project", "author": "Name ", - "license": "MIT" + "license": "MIT", } framework = "CrewAI" # TODO: if --no-wizard, require a framework flag - design = { - 'agents': [], - 'tasks': [] - } + design = {"agents": [], "tasks": []} tools = [] @@ -106,7 +117,9 @@ def init_project_builder(slug_name: Optional[str] = None, template: Optional[str ) insert_template(project_details, framework, design, template_data) for tool_data in tools: - generation.add_tool(tool_data['name'], agents=tool_data['agents'], path=project_details['name']) + generation.add_tool( + tool_data["name"], agents=tool_data["agents"], path=project_details["name"] + ) def welcome_message(): @@ -126,8 +139,8 @@ def configure_default_model(path: Optional[str] = None): """Set the default model""" agentstack_config = ConfigFile(path) if agentstack_config.default_model: - return # Default model already set - + return # Default model already set + print("Project does not have a default model configured.") other_msg = f"Other (enter a model name)" model = inquirer.list_input( @@ -135,10 +148,12 @@ def configure_default_model(path: Optional[str] = None): choices=PREFERRED_MODELS + [other_msg], ) - if model == other_msg: # If the user selects "Other", prompt for a model name - print(f'A list of available models is available at: "https://docs.litellm.ai/docs/providers"') + if model == other_msg: # If the user selects "Other", prompt for a model name + print( + f'A list of available models is available at: "https://docs.litellm.ai/docs/providers"' + ) model = inquirer.text(message="Enter the model name") - + with ConfigFile(path) as agentstack_config: agentstack_config.default_model = model @@ -164,7 +179,9 @@ def ask_framework() -> str: # choices=["CrewAI", "Autogen", "LiteLLM"], # ) - print("Congrats! Your project is ready to go! Quickly add features now or skip to do it later.\n\n") + print( + "Congrats! Your project is ready to go! Quickly add features now or skip to do it later.\n\n" + ) return framework @@ -175,10 +192,7 @@ def ask_design() -> dict: ) if not use_wizard: - return { - 'agents': [], - 'tasks': [] - } + return {"agents": [], "tasks": []} os.system("cls" if os.name == "nt" else "clear") @@ -194,81 +208,106 @@ def ask_design() -> dict: make_agent = True agents = [] while make_agent: - print('---') + print("---") print(f"Agent #{len(agents)+1}") agent_incomplete = True agent = None while agent_incomplete: - agent = inquirer.prompt([ - inquirer.Text("name", message="What's the name of this agent? (snake_case)"), - inquirer.Text("role", message="What role does this agent have?"), - inquirer.Text("goal", message="What is the goal of the agent?"), - inquirer.Text("backstory", message="Give your agent a backstory"), - # TODO: make a list - #2 - inquirer.Text('model', message="What LLM should this agent use? (any LiteLLM provider)", default="openai/gpt-4"), - # inquirer.List("model", message="What LLM should this agent use? (any LiteLLM provider)", choices=[ - # 'mixtral_llm', - # 'mixtral_llm', - # ]), - ]) - - if not agent['name'] or agent['name'] == '': - print(term_color("Error: Agent name is required - Try again", 'red')) + agent = inquirer.prompt( + [ + inquirer.Text( + "name", message="What's the name of this agent? (snake_case)" + ), + inquirer.Text("role", message="What role does this agent have?"), + inquirer.Text("goal", message="What is the goal of the agent?"), + inquirer.Text("backstory", message="Give your agent a backstory"), + # TODO: make a list - #2 + inquirer.Text( + "model", + message="What LLM should this agent use? (any LiteLLM provider)", + default="openai/gpt-4", + ), + # inquirer.List("model", message="What LLM should this agent use? (any LiteLLM provider)", choices=[ + # 'mixtral_llm', + # 'mixtral_llm', + # ]), + ] + ) + + if not agent["name"] or agent["name"] == "": + print(term_color("Error: Agent name is required - Try again", "red")) agent_incomplete = True - elif not is_snake_case(agent['name']): - print(term_color("Error: Agent name must be snake case - Try again", 'red')) + elif not is_snake_case(agent["name"]): + print( + term_color( + "Error: Agent name must be snake case - Try again", "red" + ) + ) else: agent_incomplete = False make_agent = inquirer.confirm(message="Create another agent?") agents.append(agent) - print('') + print("") for x in range(3): time.sleep(0.3) - print('.') - print('Boom! We made some agents (ノ>ω<)ノ :。・:*:・゚’★,。・:*:・゚’☆') + print(".") + print("Boom! We made some agents (ノ>ω<)ノ :。・:*:・゚’★,。・:*:・゚’☆") time.sleep(0.5) - print('') - print('Now lets make some tasks for the agents to accomplish!') - print('') + print("") + print("Now lets make some tasks for the agents to accomplish!") + print("") make_task = True tasks = [] while make_task: - print('---') + print("---") print(f"Task #{len(tasks) + 1}") task_incomplete = True task = None while task_incomplete: - task = inquirer.prompt([ - inquirer.Text("name", message="What's the name of this task? (snake_case)"), - inquirer.Text("description", message="Describe the task in more detail"), - inquirer.Text("expected_output", - message="What do you expect the result to look like? (ex: A 5 bullet point summary of the email)"), - inquirer.List("agent", message="Which agent should be assigned this task?", - choices=[a['name'] for a in agents], ), - ]) - - if not task['name'] or task['name'] == '': - print(term_color("Error: Task name is required - Try again", 'red')) - elif not is_snake_case(task['name']): - print(term_color("Error: Task name must be snake case - Try again", 'red')) + task = inquirer.prompt( + [ + inquirer.Text( + "name", message="What's the name of this task? (snake_case)" + ), + inquirer.Text( + "description", message="Describe the task in more detail" + ), + inquirer.Text( + "expected_output", + message="What do you expect the result to look like? (ex: A 5 bullet point summary of the email)", + ), + inquirer.List( + "agent", + message="Which agent should be assigned this task?", + choices=[a["name"] for a in agents], + ), + ] + ) + + if not task["name"] or task["name"] == "": + print(term_color("Error: Task name is required - Try again", "red")) + elif not is_snake_case(task["name"]): + print( + term_color("Error: Task name must be snake case - Try again", "red") + ) else: task_incomplete = False make_task = inquirer.confirm(message="Create another task?") tasks.append(task) - print('') + print("") for x in range(3): time.sleep(0.3) - print('.') - print('Let there be tasks (ノ ˘_˘)ノ ζ|||ζ ζ|||ζ ζ|||ζ') + print(".") + print("Let there be tasks (ノ ˘_˘)ノ ζ|||ζ ζ|||ζ ζ|||ζ") - return {'tasks': tasks, 'agents': agents} + return {"tasks": tasks, "agents": agents} def ask_tools() -> list: @@ -283,83 +322,108 @@ def ask_tools() -> list: adding_tools = True script_dir = os.path.dirname(os.path.abspath(__file__)) - tools_json_path = os.path.join(script_dir, '..', 'tools', 'tools.json') + tools_json_path = os.path.join(script_dir, "..", "tools", "tools.json") # Load the JSON data tools_data = open_json_file(tools_json_path) while adding_tools: - tool_type = inquirer.list_input( message="What category tool do you want to add?", - choices=list(tools_data.keys()) + ["~~ Stop adding tools ~~"] + choices=list(tools_data.keys()) + ["~~ Stop adding tools ~~"], ) - tools_in_cat = [f"{t['name']} - {t['url']}" for t in tools_data[tool_type] if t not in tools_to_add] + tools_in_cat = [ + f"{t['name']} - {t['url']}" + for t in tools_data[tool_type] + if t not in tools_to_add + ] tool_selection = inquirer.list_input( - message="Select your tool", - choices=tools_in_cat + message="Select your tool", choices=tools_in_cat ) - tools_to_add.append(tool_selection.split(' - ')[0]) + tools_to_add.append(tool_selection.split(" - ")[0]) print("Adding tools:") for t in tools_to_add: - print(f' - {t}') - print('') + print(f" - {t}") + print("") adding_tools = inquirer.confirm("Add another tool?") return tools_to_add def ask_project_details(slug_name: Optional[str] = None) -> dict: - name = inquirer.text(message="What's the name of your project (snake_case)", default=slug_name or '') + name = inquirer.text( + message="What's the name of your project (snake_case)", default=slug_name or "" + ) if not is_snake_case(name): - print(term_color("Project name must be snake case", 'red')) + print(term_color("Project name must be snake case", "red")) return ask_project_details(slug_name) - questions = inquirer.prompt([ - inquirer.Text("version", message="What's the initial version", default="0.1.0"), - inquirer.Text("description", message="Enter a description for your project"), - inquirer.Text("author", message="Who's the author (your name)?"), - ]) + questions = inquirer.prompt( + [ + inquirer.Text( + "version", message="What's the initial version", default="0.1.0" + ), + inquirer.Text( + "description", message="Enter a description for your project" + ), + inquirer.Text("author", message="Who's the author (your name)?"), + ] + ) - questions['name'] = name + questions["name"] = name return questions -def insert_template(project_details: dict, framework_name: str, design: dict, template_data: Optional[dict] = None): +def insert_template( + project_details: dict, + framework_name: str, + design: dict, + template_data: Optional[dict] = None, +): framework = FrameworkData(framework_name.lower()) - project_metadata = ProjectMetadata(project_name=project_details["name"], - description=project_details["description"], - author_name=project_details["author"], - version="0.0.1", - license="MIT", - year=datetime.now().year, - template=template_data['name'] if template_data else None, - template_version=template_data['template_version'] if template_data else None) + project_metadata = ProjectMetadata( + project_name=project_details["name"], + description=project_details["description"], + author_name=project_details["author"], + version="0.0.1", + license="MIT", + year=datetime.now().year, + template=template_data["name"] if template_data else None, + template_version=template_data["template_version"] if template_data else None, + ) project_structure = ProjectStructure() project_structure.agents = design["agents"] project_structure.tasks = design["tasks"] - cookiecutter_data = CookiecutterData(project_metadata=project_metadata, - structure=project_structure, - framework=framework_name.lower()) + cookiecutter_data = CookiecutterData( + project_metadata=project_metadata, + structure=project_structure, + framework=framework_name.lower(), + ) - template_path = get_package_path() / f'templates/{framework.name}' + template_path = get_package_path() / f"templates/{framework.name}" with open(f"{template_path}/cookiecutter.json", "w") as json_file: json.dump(cookiecutter_data.to_dict(), json_file) # copy .env.example to .env shutil.copy( f'{template_path}/{"{{cookiecutter.project_metadata.project_slug}}"}/.env.example', - f'{template_path}/{"{{cookiecutter.project_metadata.project_slug}}"}/.env') + f'{template_path}/{"{{cookiecutter.project_metadata.project_slug}}"}/.env', + ) - if os.path.isdir(project_details['name']): - print(term_color(f"Directory {template_path} already exists. Please check this and try again", "red")) + if os.path.isdir(project_details["name"]): + print( + term_color( + f"Directory {template_path} already exists. Please check this and try again", + "red", + ) + ) return cookiecutter(str(template_path), no_input=True, extra_context=None) @@ -372,7 +436,9 @@ def insert_template(project_details: dict, framework_name: str, design: dict, te # subprocess.check_output(["git", "init"]) # subprocess.check_output(["git", "add", "."]) except: - print("Failed to initialize git repository. Maybe you're already in one? Do this with: git init") + print( + "Failed to initialize git repository. Maybe you're already in one? Do this with: git init" + ) # TODO: check if poetry is installed and if so, run poetry install in the new directory # os.system("poetry install") @@ -402,9 +468,9 @@ def list_tools(): print(f"\n{category}:") curr_category = category for tool in tools: - print(" - ", end='') - print(term_color(f"{tool.name}", 'blue'), end='') + print(" - ", end="") + print(term_color(f"{tool.name}", "blue"), end="") print(f": {tool.url if tool.url else 'AgentStack default tool'}") print("\n\n✨ Add a tool with: agentstack tools add ") - print(" https://docs.agentstack.sh/tools/core") \ No newline at end of file + print(" https://docs.agentstack.sh/tools/core") diff --git a/agentstack/generation/__init__.py b/agentstack/generation/__init__.py index 49d62c82..1ce49b1a 100644 --- a/agentstack/generation/__init__.py +++ b/agentstack/generation/__init__.py @@ -1,4 +1,4 @@ from .agent_generation import generate_agent, get_agent_names from .task_generation import generate_task, get_task_names from .tool_generation import add_tool, remove_tool -from .files import ConfigFile, EnvFile, CONFIG_FILENAME \ No newline at end of file +from .files import ConfigFile, EnvFile, CONFIG_FILENAME diff --git a/agentstack/generation/agent_generation.py b/agentstack/generation/agent_generation.py index bf64dd2e..fa6a4b22 100644 --- a/agentstack/generation/agent_generation.py +++ b/agentstack/generation/agent_generation.py @@ -9,19 +9,19 @@ def generate_agent( - name, - role: Optional[str], - goal: Optional[str], - backstory: Optional[str], - llm: Optional[str] + name, + role: Optional[str], + goal: Optional[str], + backstory: Optional[str], + llm: Optional[str], ): - agentstack_config = ConfigFile() # TODO path + agentstack_config = ConfigFile() # TODO path if not role: - role = 'Add your role here' + role = "Add your role here" if not goal: - goal = 'Add your goal here' + goal = "Add your goal here" if not backstory: - backstory = 'Add your backstory here' + backstory = "Add your backstory here" if not llm: llm = agentstack_config.default_model @@ -29,24 +29,24 @@ def generate_agent( framework = get_framework() - if framework == 'crewai': + if framework == "crewai": generate_crew_agent(name, role, goal, backstory, llm) print(" > Added to src/config/agents.yaml") else: print(f"This function is not yet implemented for {framework}") return - print(f"Added agent \"{name}\" to your AgentStack project successfully!") + print(f'Added agent "{name}" to your AgentStack project successfully!') def generate_crew_agent( - name, - role: Optional[str] = 'Add your role here', - goal: Optional[str] = 'Add your goal here', - backstory: Optional[str] = 'Add your backstory here', - llm: Optional[str] = 'openai/gpt-4o' + name, + role: Optional[str] = "Add your role here", + goal: Optional[str] = "Add your goal here", + backstory: Optional[str] = "Add your backstory here", + llm: Optional[str] = "openai/gpt-4o", ): - config_path = os.path.join('src', 'config', 'agents.yaml') + config_path = os.path.join("src", "config", "agents.yaml") # Ensure the directory exists os.makedirs(os.path.dirname(config_path), exist_ok=True) @@ -56,7 +56,7 @@ def generate_crew_agent( # Read existing data if os.path.exists(config_path): - with open(config_path, 'r') as file: + with open(config_path, "r") as file: try: data = yaml.load(file) or {} except Exception as exc: @@ -66,26 +66,28 @@ def generate_crew_agent( data = {} # Handle None values - role_str = FoldedScalarString(role) if role else FoldedScalarString('') - goals_str = FoldedScalarString(goal) if goal else FoldedScalarString('') - backstory_str = FoldedScalarString(backstory) if backstory else FoldedScalarString('') - model_str = llm if llm else '' + role_str = FoldedScalarString(role) if role else FoldedScalarString("") + goals_str = FoldedScalarString(goal) if goal else FoldedScalarString("") + backstory_str = ( + FoldedScalarString(backstory) if backstory else FoldedScalarString("") + ) + model_str = llm if llm else "" # Add new agent details data[name] = { - 'role': role_str, - 'goal': goals_str, - 'backstory': backstory_str, - 'llm': model_str + "role": role_str, + "goal": goals_str, + "backstory": backstory_str, + "llm": model_str, } # Write back to the file without altering existing content - with open(config_path, 'w') as file: + with open(config_path, "w") as file: yaml.dump(data, file) # Now lets add the agent to crew.py - file_path = 'src/crew.py' - tag = '# Agent definitions' + file_path = "src/crew.py" + tag = "# Agent definitions" code_to_insert = [ "@agent", f"def {name}(self) -> Agent:", @@ -94,12 +96,12 @@ def generate_crew_agent( " tools=[], # add tools here or use `agentstack tools add ", # TODO: Add any tools in agentstack.json " verbose=True", " )", - "" + "", ] insert_code_after_tag(file_path, tag, code_to_insert) -def get_agent_names(framework: str = 'crewai', path: str = '') -> List[str]: +def get_agent_names(framework: str = "crewai", path: str = "") -> List[str]: """Get only agent names from the crew file""" - return get_crew_components(framework, CrewComponent.AGENT, path)['agents'] \ No newline at end of file + return get_crew_components(framework, CrewComponent.AGENT, path)["agents"] diff --git a/agentstack/generation/files.py b/agentstack/generation/files.py index b1c226c3..f930d867 100644 --- a/agentstack/generation/files.py +++ b/agentstack/generation/files.py @@ -9,20 +9,21 @@ CONFIG_FILENAME = "agentstack.json" ENV_FILEMANE = ".env" + class ConfigFile(BaseModel): """ Interface for interacting with the agentstack.json file inside a project directory. Handles both data validation and file I/O. - + `path` is the directory where the agentstack.json file is located. Defaults to the current working directory. - + Use it as a context manager to make and save edits: ```python with ConfigFile() as config: config.tools.append('tool_name') ``` - + Config Schema ------------- framework: str @@ -30,62 +31,69 @@ class ConfigFile(BaseModel): tools: list[str] A list of tools that are currently installed in the project. telemetry_opt_out: Optional[bool] - Whether the user has opted out of telemetry. + Whether the user has opted out of telemetry. default_model: Optional[str] The default model to use when generating agent configurations. """ + framework: Optional[str] = DEFAULT_FRAMEWORK tools: list[str] = [] telemetry_opt_out: Optional[bool] = None default_model: Optional[str] = None - + def __init__(self, path: Union[str, Path, None] = None): path = Path(path) if path else Path.cwd() if os.path.exists(path / CONFIG_FILENAME): - with open(path / CONFIG_FILENAME, 'r') as f: + with open(path / CONFIG_FILENAME, "r") as f: super().__init__(**json.loads(f.read())) else: raise FileNotFoundError(f"File {path / CONFIG_FILENAME} does not exist.") - self._path = path # attribute needs to be set after init + self._path = path # attribute needs to be set after init def model_dump(self, *args, **kwargs) -> dict: # Ignore None values dump = super().model_dump(*args, **kwargs) return {key: value for key, value in dump.items() if value is not None} - + def write(self): - with open(self._path / CONFIG_FILENAME, 'w') as f: + with open(self._path / CONFIG_FILENAME, "w") as f: f.write(json.dumps(self.model_dump(), indent=4)) - - def __enter__(self) -> 'ConfigFile': return self - def __exit__(self, *args): self.write() + + def __enter__(self) -> "ConfigFile": + return self + + def __exit__(self, *args): + self.write() class EnvFile: """ Interface for interacting with the .env file inside a project directory. - Unlike the ConfigFile, we do not re-write the entire file on every change, + Unlike the ConfigFile, we do not re-write the entire file on every change, and instead just append new lines to the end of the file. This preseres comments and other formatting that the user may have added and prevents opportunities for data loss. - + `path` is the directory where the .env file is located. Defaults to the current working directory. `filename` is the name of the .env file, defaults to '.env'. - + Use it as a context manager to make and save edits: ```python with EnvFile() as env: env.append_if_new('ENV_VAR', 'value') ``` """ + variables: dict[str, str] - - def __init__(self, path: Union[str, Path, None] = None, filename: str = ENV_FILEMANE): + + def __init__( + self, path: Union[str, Path, None] = None, filename: str = ENV_FILEMANE + ): self._path = Path(path) if path else Path.cwd() self._filename = filename self.read() - + def __getitem__(self, key): return self.variables[key] @@ -93,32 +101,36 @@ def __setitem__(self, key, value): if key in self.variables: raise ValueError("EnvFile does not allow overwriting values.") self.append_if_new(key, value) - + def __contains__(self, key) -> bool: return key in self.variables - + def append_if_new(self, key, value): if not key in self.variables: self.variables[key] = value self._new_variables[key] = value - + def read(self): def parse_line(line): - key, value = line.split('=') + key, value = line.split("=") return key.strip(), value.strip() if os.path.exists(self._path / self._filename): - with open(self._path / self._filename, 'r') as f: - self.variables = dict([parse_line(line) for line in f.readlines() if '=' in line]) + with open(self._path / self._filename, "r") as f: + self.variables = dict( + [parse_line(line) for line in f.readlines() if "=" in line] + ) else: self.variables = {} self._new_variables = {} - + def write(self): - with open(self._path / self._filename, 'a') as f: + with open(self._path / self._filename, "a") as f: for key, value in self._new_variables.items(): f.write(f"\n{key}={value}") - - def __enter__(self) -> 'EnvFile': return self - def __exit__(self, *args): self.write() + def __enter__(self) -> "EnvFile": + return self + + def __exit__(self, *args): + self.write() diff --git a/agentstack/generation/gen_utils.py b/agentstack/generation/gen_utils.py index f9ac0e5f..960de8f1 100644 --- a/agentstack/generation/gen_utils.py +++ b/agentstack/generation/gen_utils.py @@ -8,26 +8,29 @@ def insert_code_after_tag(file_path, tag, code_to_insert, next_line=False): if next_line: - code_to_insert = ['\n'] + code_to_insert + code_to_insert = ["\n"] + code_to_insert - with open(file_path, 'r') as file: + with open(file_path, "r") as file: lines = file.readlines() for index, line in enumerate(lines): if tag in line: # Insert the code block after the tag - indented_code = [(line[:len(line)-len(line.lstrip())] + code_line + '\n') for code_line in code_to_insert] - lines[index+1:index+1] = indented_code + indented_code = [ + (line[: len(line) - len(line.lstrip())] + code_line + "\n") + for code_line in code_to_insert + ] + lines[index + 1 : index + 1] = indented_code break else: raise ValueError(f"Tag '{tag}' not found in the file.") - with open(file_path, 'w') as file: + with open(file_path, "w") as file: file.writelines(lines) def insert_after_tasks(file_path, code_to_insert): - with open(file_path, 'r') as file: + with open(file_path, "r") as file: content = file.read() module = ast.parse(content) @@ -36,47 +39,49 @@ def insert_after_tasks(file_path, code_to_insert): last_task_end = None last_task_start = None for node in ast.walk(module): - if isinstance(node, ast.FunctionDef) and \ - any(isinstance(deco, ast.Name) and deco.id == 'task' for deco in node.decorator_list): + if isinstance(node, ast.FunctionDef) and any( + isinstance(deco, ast.Name) and deco.id == "task" + for deco in node.decorator_list + ): last_task_end = node.end_lineno last_task_start = node.lineno if last_task_end is not None: - lines = content.split('\n') + lines = content.split("\n") # Get the indentation of the task function task_line = lines[last_task_start - 1] # -1 for 0-based indexing - indentation = '' + indentation = "" for char in task_line: - if char in [' ', '\t']: + if char in [" ", "\t"]: indentation += char else: break # Add the same indentation to each line of the inserted code - indented_code = '\n' + '\n'.join(indentation + line for line in code_to_insert) + indented_code = "\n" + "\n".join(indentation + line for line in code_to_insert) lines.insert(last_task_end, indented_code) - content = '\n'.join(lines) + content = "\n".join(lines) - with open(file_path, 'w') as file: + with open(file_path, "w") as file: file.write(content) return True else: - insert_code_after_tag(file_path, '# Task definitions', code_to_insert) + insert_code_after_tag(file_path, "# Task definitions", code_to_insert) def string_in_file(file_path: str, str_to_match: str) -> bool: - with open(file_path, 'r') as file: + with open(file_path, "r") as file: file_content = file.read() return str_to_match in file_content -def _framework_filename(framework: str, path: str = ''): - if framework == 'crewai': - return f'{path}src/crew.py' +def _framework_filename(framework: str, path: str = ""): + if framework == "crewai": + return f"{path}src/crew.py" - print(term_color(f'Unknown framework: {framework}', 'red')) + print(term_color(f"Unknown framework: {framework}", "red")) sys.exit(1) @@ -86,9 +91,9 @@ class CrewComponent(str, Enum): def get_crew_components( - framework: str = 'crewai', - component_type: Optional[Union[CrewComponent, List[CrewComponent]]] = None, - path: str = '' + framework: str = "crewai", + component_type: Optional[Union[CrewComponent, List[CrewComponent]]] = None, + path: str = "", ) -> dict[str, List[str]]: """ Get names of components (agents and/or tasks) defined in a crew file. @@ -110,16 +115,13 @@ def get_crew_components( component_type = [component_type] # Read the source file - with open(filename, 'r') as f: + with open(filename, "r") as f: source = f.read() # Parse the source into an AST tree = ast.parse(source) - components = { - 'agents': [], - 'tasks': [] - } + components = {"agents": [], "tasks": []} # Find all function definitions with relevant decorators for node in ast.walk(tree): @@ -127,16 +129,21 @@ def get_crew_components( # Check decorators for decorator in node.decorator_list: if isinstance(decorator, ast.Name): - if (component_type is None or CrewComponent.AGENT in component_type) \ - and decorator.id == 'agent': - components['agents'].append(node.name) - elif (component_type is None or CrewComponent.TASK in component_type) \ - and decorator.id == 'task': - components['tasks'].append(node.name) + if ( + component_type is None or CrewComponent.AGENT in component_type + ) and decorator.id == "agent": + components["agents"].append(node.name) + elif ( + component_type is None or CrewComponent.TASK in component_type + ) and decorator.id == "task": + components["tasks"].append(node.name) # If specific types were requested, only return those if component_type: - return {k: v for k, v in components.items() - if CrewComponent(k[:-1]) in component_type} + return { + k: v + for k, v in components.items() + if CrewComponent(k[:-1]) in component_type + } return components diff --git a/agentstack/generation/task_generation.py b/agentstack/generation/task_generation.py index d2fc6ebc..930a32c3 100644 --- a/agentstack/generation/task_generation.py +++ b/agentstack/generation/task_generation.py @@ -8,39 +8,39 @@ def generate_task( - name, - description: Optional[str], - expected_output: Optional[str], - agent: Optional[str] + name, + description: Optional[str], + expected_output: Optional[str], + agent: Optional[str], ): if not description: - description = 'Add your description here' + description = "Add your description here" if not expected_output: - expected_output = 'Add your expected_output here' + expected_output = "Add your expected_output here" if not agent: - agent = 'default_agent' + agent = "default_agent" verify_agentstack_project() framework = get_framework() - if framework == 'crewai': + if framework == "crewai": generate_crew_task(name, description, expected_output, agent) print(" > Added to src/config/tasks.yaml") else: print(f"This function is not yet implemented for {framework}") return - print(f"Added task \"{name}\" to your AgentStack project successfully!") + print(f'Added task "{name}" to your AgentStack project successfully!') def generate_crew_task( - name, - description: Optional[str], - expected_output: Optional[str], - agent: Optional[str] + name, + description: Optional[str], + expected_output: Optional[str], + agent: Optional[str], ): - config_path = os.path.join('src', 'config', 'tasks.yaml') + config_path = os.path.join("src", "config", "tasks.yaml") # Ensure the directory exists os.makedirs(os.path.dirname(config_path), exist_ok=True) @@ -50,7 +50,7 @@ def generate_crew_task( # Read existing data if os.path.exists(config_path): - with open(config_path, 'r') as file: + with open(config_path, "r") as file: try: data = yaml.load(file) or {} except Exception as exc: @@ -60,35 +60,41 @@ def generate_crew_task( data = {} # Handle None values - description_str = FoldedScalarString(description) if description else FoldedScalarString('') - expected_output_str = FoldedScalarString(expected_output) if expected_output else FoldedScalarString('') - agent_str = FoldedScalarString(agent) if agent else FoldedScalarString('') + description_str = ( + FoldedScalarString(description) if description else FoldedScalarString("") + ) + expected_output_str = ( + FoldedScalarString(expected_output) + if expected_output + else FoldedScalarString("") + ) + agent_str = FoldedScalarString(agent) if agent else FoldedScalarString("") # Add new agent details data[name] = { - 'description': description_str, - 'expected_output': expected_output_str, - 'agent': agent_str, + "description": description_str, + "expected_output": expected_output_str, + "agent": agent_str, } # Write back to the file without altering existing content - with open(config_path, 'w') as file: + with open(config_path, "w") as file: yaml.dump(data, file) # Add task to crew.py - file_path = 'src/crew.py' + file_path = "src/crew.py" code_to_insert = [ "@task", f"def {name}(self) -> Task:", " return Task(", f" config=self.tasks_config['{name}'],", " )", - "" + "", ] insert_after_tasks(file_path, code_to_insert) -def get_task_names(framework: str, path: str = '') -> List[str]: +def get_task_names(framework: str, path: str = "") -> List[str]: """Get only task names from the crew file""" - return get_crew_components(framework, CrewComponent.TASK, path)['tasks'] \ No newline at end of file + return get_crew_components(framework, CrewComponent.TASK, path)["tasks"] diff --git a/agentstack/generation/tool_generation.py b/agentstack/generation/tool_generation.py index c4e19fc0..00ff9d2f 100644 --- a/agentstack/generation/tool_generation.py +++ b/agentstack/generation/tool_generation.py @@ -25,20 +25,22 @@ TOOL_INIT_FILENAME = "src/tools/__init__.py" FRAMEWORK_FILENAMES: dict[str, str] = { - 'crewai': 'src/crew.py', + "crewai": "src/crew.py", } -def get_framework_filename(framework: str, path: str = ''): + +def get_framework_filename(framework: str, path: str = ""): if path: - path = path.endswith('/') and path or path + '/' + path = path.endswith("/") and path or path + "/" else: - path = './' + path = "./" try: return f"{path}{FRAMEWORK_FILENAMES[framework]}" except KeyError: - print(term_color(f'Unknown framework: {framework}', 'red')) + print(term_color(f"Unknown framework: {framework}", "red")) sys.exit(1) + class ToolConfig(BaseModel): name: str category: str @@ -52,20 +54,20 @@ class ToolConfig(BaseModel): post_remove: Optional[str] = None @classmethod - def from_tool_name(cls, name: str) -> 'ToolConfig': - path = get_package_path() / f'tools/{name}.json' + def from_tool_name(cls, name: str) -> "ToolConfig": + path = get_package_path() / f"tools/{name}.json" if not os.path.exists(path): - print(term_color(f'No known agentstack tool: {name}', 'red')) + print(term_color(f"No known agentstack tool: {name}", "red")) sys.exit(1) return cls.from_json(path) @classmethod - def from_json(cls, path: Path) -> 'ToolConfig': + def from_json(cls, path: Path) -> "ToolConfig": data = open_json_file(path) try: return cls(**data) except ValidationError as e: - print(term_color(f"Error validating tool config JSON: \n{path}", 'red')) + print(term_color(f"Error validating tool config JSON: \n{path}", "red")) for error in e.errors(): print(f"{' '.join(error['loc'])}: {error['msg']}") sys.exit(1) @@ -74,46 +76,55 @@ def get_import_statement(self) -> str: return f"from .{self.name}_tool import {', '.join(self.tools)}" def get_impl_file_path(self, framework: str) -> Path: - return get_package_path() / f'templates/{framework}/tools/{self.name}_tool.py' + return get_package_path() / f"templates/{framework}/tools/{self.name}_tool.py" + def get_all_tool_paths() -> list[Path]: paths = [] - tools_dir = get_package_path() / 'tools' + tools_dir = get_package_path() / "tools" for file in tools_dir.iterdir(): - if file.is_file() and file.suffix == '.json': + if file.is_file() and file.suffix == ".json": paths.append(file) return paths + def get_all_tool_names() -> list[str]: return [path.stem for path in get_all_tool_paths()] + def get_all_tools() -> list[ToolConfig]: return [ToolConfig.from_json(path) for path in get_all_tool_paths()] -def add_tool(tool_name: str, path: Optional[str] = None, agents: Optional[List[str]] = []): + +def add_tool( + tool_name: str, path: Optional[str] = None, agents: Optional[List[str]] = [] +): if path: - path = path.endswith('/') and path or path + '/' + path = path.endswith("/") and path or path + "/" else: - path = './' + path = "./" framework = get_framework(path) agentstack_config = ConfigFile(path) if tool_name in agentstack_config.tools: - print(term_color(f'Tool {tool_name} is already installed', 'red')) + print(term_color(f"Tool {tool_name} is already installed", "red")) sys.exit(1) tool_data = ToolConfig.from_tool_name(tool_name) tool_file_path = tool_data.get_impl_file_path(framework) - if tool_data.packages: os.system(f"poetry add {' '.join(tool_data.packages)}") # Install packages - shutil.copy(tool_file_path, f'{path}src/tools/{tool_name}_tool.py') # Move tool from package to project + shutil.copy( + tool_file_path, f"{path}src/tools/{tool_name}_tool.py" + ) # Move tool from package to project add_tool_to_tools_init(tool_data, path) # Export tool from tools dir - add_tool_to_agent_definition(framework=framework, tool_data=tool_data, path=path, agents=agents) # Add tool to agent definition + add_tool_to_agent_definition( + framework=framework, tool_data=tool_data, path=path, agents=agents + ) # Add tool to agent definition - if tool_data.env: # add environment variables which don't exist + if tool_data.env: # add environment variables which don't exist with EnvFile(path) as env: for var, value in tool_data.env.items(): env.append_if_new(var, value) @@ -127,29 +138,33 @@ def add_tool(tool_name: str, path: Optional[str] = None, agents: Optional[List[s with agentstack_config as config: config.tools.append(tool_name) - print(term_color(f'🔨 Tool {tool_name} added to agentstack project successfully', 'green')) + print( + term_color( + f"🔨 Tool {tool_name} added to agentstack project successfully", "green" + ) + ) if tool_data.cta: - print(term_color(f'🪩 {tool_data.cta}', 'blue')) + print(term_color(f"🪩 {tool_data.cta}", "blue")) def remove_tool(tool_name: str, path: Optional[str] = None): if path: - path = path.endswith('/') and path or path + '/' + path = path.endswith("/") and path or path + "/" else: - path = './' + path = "./" framework = get_framework() agentstack_config = ConfigFile(path) if not tool_name in agentstack_config.tools: - print(term_color(f'Tool {tool_name} is not installed', 'red')) + print(term_color(f"Tool {tool_name} is not installed", "red")) sys.exit(1) tool_data = ToolConfig.from_tool_name(tool_name) if tool_data.packages: - os.system(f"poetry remove {' '.join(tool_data.packages)}") # Uninstall packages + os.system(f"poetry remove {' '.join(tool_data.packages)}") # Uninstall packages try: - os.remove(f'{path}src/tools/{tool_name}_tool.py') + os.remove(f"{path}src/tools/{tool_name}_tool.py") except FileNotFoundError: print(f'"src/tools/{tool_name}_tool.py" not found') remove_tool_from_tools_init(tool_data, path) @@ -161,85 +176,106 @@ def remove_tool(tool_name: str, path: Optional[str] = None): with agentstack_config as config: config.tools.remove(tool_name) - print(term_color(f'🔨 Tool {tool_name}', 'green'), term_color('removed', 'red'), term_color('from agentstack project successfully', 'green')) + print( + term_color(f"🔨 Tool {tool_name}", "green"), + term_color("removed", "red"), + term_color("from agentstack project successfully", "green"), + ) -def add_tool_to_tools_init(tool_data: ToolConfig, path: str = ''): - file_path = f'{path}{TOOL_INIT_FILENAME}' - tag = '# tool import' - code_to_insert = [tool_data.get_import_statement(), ] +def add_tool_to_tools_init(tool_data: ToolConfig, path: str = ""): + file_path = f"{path}{TOOL_INIT_FILENAME}" + tag = "# tool import" + code_to_insert = [ + tool_data.get_import_statement(), + ] insert_code_after_tag(file_path, tag, code_to_insert, next_line=True) -def remove_tool_from_tools_init(tool_data: ToolConfig, path: str = ''): +def remove_tool_from_tools_init(tool_data: ToolConfig, path: str = ""): """Search for the import statement in the init and remove it.""" - file_path = f'{path}{TOOL_INIT_FILENAME}' + file_path = f"{path}{TOOL_INIT_FILENAME}" import_statement = tool_data.get_import_statement() with fileinput.input(files=file_path, inplace=True) as f: for line in f: if line.strip() != import_statement: - print(line, end='') + print(line, end="") -def add_tool_to_agent_definition(framework: str, tool_data: ToolConfig, path: str = '', agents: list[str] = []): +def add_tool_to_agent_definition( + framework: str, tool_data: ToolConfig, path: str = "", agents: list[str] = [] +): """ - Add tools to specific agent definitions using AST transformation. + Add tools to specific agent definitions using AST transformation. - Args: - framework: Name of the framework - tool_data: ToolConfig - agents: Optional list of agent names to modify. If None, modifies all agents. - path: Optional path to the framework file - """ - modify_agent_tools(framework=framework, tool_data=tool_data, operation='add', agents=agents, path=path, base_name='tools') + Args: + framework: Name of the framework + tool_data: ToolConfig + agents: Optional list of agent names to modify. If None, modifies all agents. + path: Optional path to the framework file + """ + modify_agent_tools( + framework=framework, + tool_data=tool_data, + operation="add", + agents=agents, + path=path, + base_name="tools", + ) -def remove_tool_from_agent_definition(framework: str, tool_data: ToolConfig, path: str = ''): - modify_agent_tools(framework=framework, tool_data=tool_data, operation='remove', agents=None, path=path, base_name='tools') +def remove_tool_from_agent_definition( + framework: str, tool_data: ToolConfig, path: str = "" +): + modify_agent_tools( + framework=framework, + tool_data=tool_data, + operation="remove", + agents=None, + path=path, + base_name="tools", + ) -def _create_tool_attribute(tool_name: str, base_name: str = 'tools') -> ast.Attribute: +def _create_tool_attribute(tool_name: str, base_name: str = "tools") -> ast.Attribute: """Create an AST node for a tool attribute""" return ast.Attribute( - value=ast.Name(id=base_name, ctx=ast.Load()), - attr=tool_name, - ctx=ast.Load() + value=ast.Name(id=base_name, ctx=ast.Load()), attr=tool_name, ctx=ast.Load() ) -def _create_starred_tool(tool_name: str, base_name: str = 'tools') -> ast.Starred: + +def _create_starred_tool(tool_name: str, base_name: str = "tools") -> ast.Starred: """Create an AST node for a starred tool expression""" return ast.Starred( value=ast.Attribute( - value=ast.Name(id=base_name, ctx=ast.Load()), - attr=tool_name, - ctx=ast.Load() + value=ast.Name(id=base_name, ctx=ast.Load()), attr=tool_name, ctx=ast.Load() ), - ctx=ast.Load() + ctx=ast.Load(), ) def _create_tool_attributes( - tool_names: List[str], - base_name: str = 'tools' + tool_names: List[str], base_name: str = "tools" ) -> List[ast.Attribute]: """Create AST nodes for multiple tool attributes""" return [_create_tool_attribute(name, base_name) for name in tool_names] def _create_tool_nodes( - tool_names: List[str], - is_bundled: bool = False, - base_name: str = 'tools' + tool_names: List[str], is_bundled: bool = False, base_name: str = "tools" ) -> List[Union[ast.Attribute, ast.Starred]]: """Create AST nodes for multiple tool attributes""" return [ - _create_starred_tool(name, base_name) if is_bundled + _create_starred_tool(name, base_name) + if is_bundled else _create_tool_attribute(name, base_name) for name in tool_names ] -def _is_tool_node_match(node: ast.AST, tool_name: str, base_name: str = 'tools') -> bool: +def _is_tool_node_match( + node: ast.AST, tool_name: str, base_name: str = "tools" +) -> bool: """ Check if an AST node matches a tool reference, regardless of whether it's starred @@ -257,8 +293,7 @@ def _is_tool_node_match(node: ast.AST, tool_name: str, base_name: str = 'tools') # Extract the attribute name and base regardless of node type if isinstance(node, ast.Attribute): - is_base_match = (isinstance(node.value, ast.Name) and - node.value.id == base_name) + is_base_match = isinstance(node.value, ast.Name) and node.value.id == base_name is_name_match = node.attr == tool_name return is_base_match and is_name_match @@ -266,10 +301,10 @@ def _is_tool_node_match(node: ast.AST, tool_name: str, base_name: str = 'tools') def _process_tools_list( - current_tools: List[ast.AST], - tool_data: ToolConfig, - operation: str, - base_name: str = 'tools' + current_tools: List[ast.AST], + tool_data: ToolConfig, + operation: str, + base_name: str = "tools", ) -> List[ast.AST]: """ Process a tools list according to the specified operation. @@ -280,33 +315,33 @@ def _process_tools_list( operation: Operation to perform ('add' or 'remove') base_name: Base module name for tools """ - if operation == 'add': + if operation == "add": new_tools = current_tools.copy() # Add new tools with bundling if specified - new_tools.extend(_create_tool_nodes( - tool_data.tools, - tool_data.tools_bundled, - base_name - )) + new_tools.extend( + _create_tool_nodes(tool_data.tools, tool_data.tools_bundled, base_name) + ) return new_tools - elif operation == 'remove': + elif operation == "remove": # Filter out tools that match any in the removal list return [ - tool for tool in current_tools - if not any(_is_tool_node_match(tool, name, base_name) - for name in tool_data.tools) + tool + for tool in current_tools + if not any( + _is_tool_node_match(tool, name, base_name) for name in tool_data.tools + ) ] raise ValueError(f"Unsupported operation: {operation}") def _modify_agent_tools( - node: ast.FunctionDef, - tool_data: ToolConfig, - operation: str, - agents: Optional[List[str]] = None, - base_name: str = 'tools' + node: ast.FunctionDef, + tool_data: ToolConfig, + operation: str, + agents: Optional[List[str]] = None, + base_name: str = "tools", ) -> ast.FunctionDef: """ Modify the tools list in an agent definition. @@ -324,8 +359,9 @@ def _modify_agent_tools( return node # Check if this is an agent-decorated function - if not any(isinstance(d, ast.Name) and d.id == 'agent' - for d in node.decorator_list): + if not any( + isinstance(d, ast.Name) and d.id == "agent" for d in node.decorator_list + ): return node # Find the Return statement and modify tools @@ -334,14 +370,11 @@ def _modify_agent_tools( agent_call = item.value if isinstance(agent_call, ast.Call): for kw in agent_call.keywords: - if kw.arg == 'tools': + if kw.arg == "tools": if isinstance(kw.value, ast.List): # Process the tools list new_tools = _process_tools_list( - kw.value.elts, - tool_data, - operation, - base_name + kw.value.elts, tool_data, operation, base_name ) # Replace with new list @@ -351,12 +384,12 @@ def _modify_agent_tools( def modify_agent_tools( - framework: str, - tool_data: ToolConfig, - operation: str, - agents: Optional[List[str]] = None, - path: str = '', - base_name: str = 'tools' + framework: str, + tool_data: ToolConfig, + operation: str, + agents: Optional[List[str]] = None, + path: str = "", + base_name: str = "tools", ) -> None: """ Modify tools in agent definitions using AST transformation. @@ -373,12 +406,12 @@ def modify_agent_tools( valid_agents = get_agent_names(path=path) for agent in agents: if agent not in valid_agents: - print(term_color(f"Agent '{agent}' not found in the project.", 'red')) + print(term_color(f"Agent '{agent}' not found in the project.", "red")) sys.exit(1) filename = _framework_filename(framework, path) - with open(filename, 'r') as f: + with open(filename, "r") as f: source = f.read() tree = ast.parse(source) @@ -390,5 +423,5 @@ def visit_FunctionDef(self, node): modified_tree = ModifierTransformer().visit(tree) modified_source = astor.to_source(modified_tree) - with open(filename, 'w') as f: - f.write(modified_source) \ No newline at end of file + with open(filename, "w") as f: + f.write(modified_source) diff --git a/agentstack/logger.py b/agentstack/logger.py index 680d3067..e41c4887 100644 --- a/agentstack/logger.py +++ b/agentstack/logger.py @@ -16,7 +16,9 @@ def get_logger(name, debug=False): handler = logging.StreamHandler(sys.stdout) handler.setLevel(log_level) - formatter = logging.Formatter("%(asctime)s - %(process)d - %(threadName)s - %(filename)s:%(lineno)d - %(name)s - %(levelname)s - %(message)s") + formatter = logging.Formatter( + "%(asctime)s - %(process)d - %(threadName)s - %(filename)s:%(lineno)d - %(name)s - %(levelname)s - %(message)s" + ) handler.setFormatter(formatter) if not logger.handlers: diff --git a/agentstack/main.py b/agentstack/main.py index 77a7ed7f..5bf0dc2f 100644 --- a/agentstack/main.py +++ b/agentstack/main.py @@ -9,73 +9,100 @@ import webbrowser + def main(): parser = argparse.ArgumentParser( description="AgentStack CLI - The easiest way to build an agent application" ) - parser.add_argument('-v', '--version', action='store_true', help="Show the version") + parser.add_argument("-v", "--version", action="store_true", help="Show the version") # Create top-level subparsers - subparsers = parser.add_subparsers(dest='command', help='Available commands') + subparsers = parser.add_subparsers(dest="command", help="Available commands") # 'docs' command - subparsers.add_parser('docs', help='Open Agentstack docs') + subparsers.add_parser("docs", help="Open Agentstack docs") # 'quickstart' command - subparsers.add_parser('quickstart', help='Open the quickstart guide') + subparsers.add_parser("quickstart", help="Open the quickstart guide") # 'templates' command - subparsers.add_parser('templates', help='View Agentstack templates') + subparsers.add_parser("templates", help="View Agentstack templates") # 'init' command - init_parser = subparsers.add_parser('init', aliases=['i'], help='Initialize a directory for the project') - init_parser.add_argument('slug_name', nargs='?', help="The directory name to place the project in") - init_parser.add_argument('--wizard', '-w', action='store_true', help="Use the setup wizard") - init_parser.add_argument('--template', '-t', help="Agent template to use") + init_parser = subparsers.add_parser( + "init", aliases=["i"], help="Initialize a directory for the project" + ) + init_parser.add_argument( + "slug_name", nargs="?", help="The directory name to place the project in" + ) + init_parser.add_argument( + "--wizard", "-w", action="store_true", help="Use the setup wizard" + ) + init_parser.add_argument("--template", "-t", help="Agent template to use") # 'run' command - run_parser = subparsers.add_parser('run', aliases=['r'], help='Run your agent') + run_parser = subparsers.add_parser("run", aliases=["r"], help="Run your agent") # 'generate' command - generate_parser = subparsers.add_parser('generate', aliases=['g'], help='Generate agents or tasks') + generate_parser = subparsers.add_parser( + "generate", aliases=["g"], help="Generate agents or tasks" + ) # Subparsers under 'generate' - generate_subparsers = generate_parser.add_subparsers(dest='generate_command', help='Generate agents or tasks') + generate_subparsers = generate_parser.add_subparsers( + dest="generate_command", help="Generate agents or tasks" + ) # 'agent' command under 'generate' - agent_parser = generate_subparsers.add_parser('agent', aliases=['a'], help='Generate an agent') - agent_parser.add_argument('name', help='Name of the agent') - agent_parser.add_argument('--role', '-r', help='Role of the agent') - agent_parser.add_argument('--goal', '-g', help='Goal of the agent') - agent_parser.add_argument('--backstory', '-b', help='Backstory of the agent') - agent_parser.add_argument('--llm', '-l', help='Language model to use') + agent_parser = generate_subparsers.add_parser( + "agent", aliases=["a"], help="Generate an agent" + ) + agent_parser.add_argument("name", help="Name of the agent") + agent_parser.add_argument("--role", "-r", help="Role of the agent") + agent_parser.add_argument("--goal", "-g", help="Goal of the agent") + agent_parser.add_argument("--backstory", "-b", help="Backstory of the agent") + agent_parser.add_argument("--llm", "-l", help="Language model to use") # 'task' command under 'generate' - task_parser = generate_subparsers.add_parser('task', aliases=['t'], help='Generate a task') - task_parser.add_argument('name', help='Name of the task') - task_parser.add_argument('--description', '-d', help='Description of the task') - task_parser.add_argument('--expected_output', '-e', help='Expected output of the task') - task_parser.add_argument('--agent', '-a', help='Agent associated with the task') + task_parser = generate_subparsers.add_parser( + "task", aliases=["t"], help="Generate a task" + ) + task_parser.add_argument("name", help="Name of the task") + task_parser.add_argument("--description", "-d", help="Description of the task") + task_parser.add_argument( + "--expected_output", "-e", help="Expected output of the task" + ) + task_parser.add_argument("--agent", "-a", help="Agent associated with the task") # 'tools' command - tools_parser = subparsers.add_parser('tools', aliases=['t'], help='Manage tools') + tools_parser = subparsers.add_parser("tools", aliases=["t"], help="Manage tools") # Subparsers under 'tools' - tools_subparsers = tools_parser.add_subparsers(dest='tools_command', help='Tools commands') + tools_subparsers = tools_parser.add_subparsers( + dest="tools_command", help="Tools commands" + ) # 'list' command under 'tools' - tools_list_parser = tools_subparsers.add_parser('list', aliases=['l'], help='List tools') + tools_list_parser = tools_subparsers.add_parser( + "list", aliases=["l"], help="List tools" + ) # 'add' command under 'tools' - tools_add_parser = tools_subparsers.add_parser('add', aliases=['a'], help='Add a new tool') - tools_add_parser.add_argument('name', help='Name of the tool to add') - tools_add_parser.add_argument('--agents', '-a', help='Name of agents to add this tool to, comma separated') - tools_add_parser.add_argument('--agent', help='Name of agent to add this tool to') + tools_add_parser = tools_subparsers.add_parser( + "add", aliases=["a"], help="Add a new tool" + ) + tools_add_parser.add_argument("name", help="Name of the tool to add") + tools_add_parser.add_argument( + "--agents", "-a", help="Name of agents to add this tool to, comma separated" + ) + tools_add_parser.add_argument("--agent", help="Name of agent to add this tool to") # 'remove' command under 'tools' - tools_remove_parser = tools_subparsers.add_parser('remove', aliases=['r'], help='Remove a tool') - tools_remove_parser.add_argument('name', help='Name of the tool to remove') + tools_remove_parser = tools_subparsers.add_parser( + "remove", aliases=["r"], help="Remove a tool" + ) + tools_remove_parser.add_argument("name", help="Name of the tool to remove") # Parse arguments args = parser.parse_args() @@ -88,35 +115,39 @@ def main(): track_cli_command(args.command) # Handle commands - if args.command in ['docs']: - webbrowser.open('https://docs.agentstack.sh/') - elif args.command in ['quickstart']: - webbrowser.open('https://docs.agentstack.sh/quickstart') - elif args.command in ['templates']: - webbrowser.open('https://docs.agentstack.sh/quickstart') - elif args.command in ['init', 'i']: + if args.command in ["docs"]: + webbrowser.open("https://docs.agentstack.sh/") + elif args.command in ["quickstart"]: + webbrowser.open("https://docs.agentstack.sh/quickstart") + elif args.command in ["templates"]: + webbrowser.open("https://docs.agentstack.sh/quickstart") + elif args.command in ["init", "i"]: init_project_builder(args.slug_name, args.template, args.wizard) - elif args.command in ['run', 'r']: + elif args.command in ["run", "r"]: framework = get_framework() if framework == "crewai": - os.system('python src/main.py') - elif args.command in ['generate', 'g']: - if args.generate_command in ['agent', 'a']: + os.system("python src/main.py") + elif args.command in ["generate", "g"]: + if args.generate_command in ["agent", "a"]: if not args.llm: configure_default_model() - generation.generate_agent(args.name, args.role, args.goal, args.backstory, args.llm) - elif args.generate_command in ['task', 't']: - generation.generate_task(args.name, args.description, args.expected_output, args.agent) + generation.generate_agent( + args.name, args.role, args.goal, args.backstory, args.llm + ) + elif args.generate_command in ["task", "t"]: + generation.generate_task( + args.name, args.description, args.expected_output, args.agent + ) else: generate_parser.print_help() - elif args.command in ['tools', 't']: - if args.tools_command in ['list', 'l']: + elif args.command in ["tools", "t"]: + if args.tools_command in ["list", "l"]: list_tools() - elif args.tools_command in ['add', 'a']: + elif args.tools_command in ["add", "a"]: agents = [args.agent] if args.agent else None - agents = args.agents.split(',') if args.agents else agents + agents = args.agents.split(",") if args.agents else agents generation.add_tool(args.name, agents=agents) - elif args.tools_command in ['remove', 'r']: + elif args.tools_command in ["remove", "r"]: generation.remove_tool(args.name) else: tools_parser.print_help() @@ -124,7 +155,7 @@ def main(): parser.print_help() -if __name__ == '__main__': +if __name__ == "__main__": try: main() except KeyboardInterrupt: diff --git a/agentstack/telemetry.py b/agentstack/telemetry.py index db613c8c..5ccdd859 100644 --- a/agentstack/telemetry.py +++ b/agentstack/telemetry.py @@ -30,40 +30,43 @@ import requests from agentstack.utils import get_telemetry_opt_out, get_framework, get_version -TELEMETRY_URL = 'https://api.agentstack.sh/telemetry' +TELEMETRY_URL = "https://api.agentstack.sh/telemetry" + def collect_machine_telemetry(command: str): if command != "init" and get_telemetry_opt_out(): return telemetry_data = { - 'os': platform.system(), - 'hostname': socket.gethostname(), - 'platform': platform.platform(), - 'os_version': platform.version(), - 'cpu_count': psutil.cpu_count(logical=True), - 'memory': psutil.virtual_memory().total, - 'agentstack_version': get_version() + "os": platform.system(), + "hostname": socket.gethostname(), + "platform": platform.platform(), + "os_version": platform.version(), + "cpu_count": psutil.cpu_count(logical=True), + "memory": psutil.virtual_memory().total, + "agentstack_version": get_version(), } if command != "init": - telemetry_data['framework'] = get_framework() + telemetry_data["framework"] = get_framework() else: - telemetry_data['framework'] = "n/a" + telemetry_data["framework"] = "n/a" # Attempt to get general location based on public IP try: - response = requests.get('https://ipinfo.io/json') + response = requests.get("https://ipinfo.io/json") if response.status_code == 200: location_data = response.json() - telemetry_data.update({ - 'ip': location_data.get('ip'), - 'city': location_data.get('city'), - 'region': location_data.get('region'), - 'country': location_data.get('country') - }) + telemetry_data.update( + { + "ip": location_data.get("ip"), + "city": location_data.get("city"), + "region": location_data.get("region"), + "country": location_data.get("country"), + } + ) except requests.RequestException as e: - telemetry_data['location_error'] = str(e) + telemetry_data["location_error"] = str(e) return telemetry_data diff --git a/agentstack/utils.py b/agentstack/utils.py index a4dccd84..6de98282 100644 --- a/agentstack/utils.py +++ b/agentstack/utils.py @@ -8,9 +8,10 @@ from pathlib import Path import importlib.resources + def get_version(): try: - return version('agentstack') + return version("agentstack") except (KeyError, FileNotFoundError) as e: print(e) return "Unknown version" @@ -18,75 +19,90 @@ def get_version(): def verify_agentstack_project(path: Optional[str] = None): from agentstack.generation import ConfigFile + try: agentstack_config = ConfigFile(path) except FileNotFoundError: - print("\033[31mAgentStack Error: This does not appear to be an AgentStack project." - "\nPlease ensure you're at the root directory of your project and a file named agentstack.json exists. " - "If you're starting a new project, run `agentstack init`\033[0m") + print( + "\033[31mAgentStack Error: This does not appear to be an AgentStack project." + "\nPlease ensure you're at the root directory of your project and a file named agentstack.json exists. " + "If you're starting a new project, run `agentstack init`\033[0m" + ) sys.exit(1) def get_package_path() -> Path: """This is the Path where agentstack is installed.""" if sys.version_info <= (3, 9): - return Path(sys.modules['agentstack'].__path__[0]) - return importlib.resources.files('agentstack') + return Path(sys.modules["agentstack"].__path__[0]) + return importlib.resources.files("agentstack") def get_framework(path: Optional[str] = None) -> str: from agentstack.generation import ConfigFile + try: agentstack_config = ConfigFile(path) framework = agentstack_config.framework - if framework.lower() not in ['crewai', 'autogen', 'litellm']: + if framework.lower() not in ["crewai", "autogen", "litellm"]: print(term_color("agentstack.json contains an invalid framework", "red")) return framework except FileNotFoundError: - print("\033[31mFile agentstack.json does not exist. Are you in the right directory?\033[0m") + print( + "\033[31mFile agentstack.json does not exist. Are you in the right directory?\033[0m" + ) sys.exit(1) def get_telemetry_opt_out(path: Optional[str] = None) -> str: from agentstack.generation import ConfigFile + try: agentstack_config = ConfigFile(path) return bool(agentstack_config.telemetry_opt_out) except FileNotFoundError: - print("\033[31mFile agentstack.json does not exist. Are you in the right directory?\033[0m") + print( + "\033[31mFile agentstack.json does not exist. Are you in the right directory?\033[0m" + ) sys.exit(1) + def camel_to_snake(name): - s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) - return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() + s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name) + return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower() def snake_to_camel(s): - return ''.join(word.title() for word in s.split('_')) + return "".join(word.title() for word in s.split("_")) def open_json_file(path) -> dict: - with open(path, 'r') as f: + with open(path, "r") as f: data = json.load(f) return data def clean_input(input_string): - special_char_pattern = re.compile(r'[^a-zA-Z0-9\s_]') - return re.sub(special_char_pattern, '', input_string).lower().replace(' ', '_').replace('-', '_') + special_char_pattern = re.compile(r"[^a-zA-Z0-9\s_]") + return ( + re.sub(special_char_pattern, "", input_string) + .lower() + .replace(" ", "_") + .replace("-", "_") + ) def term_color(text: str, color: str) -> str: colors = { - 'red': '91', - 'green': '92', - 'yellow': '93', - 'blue': '94', - 'purple': '95', - 'cyan': '96', - 'white': '97' + "red": "91", + "green": "92", + "yellow": "93", + "blue": "94", + "purple": "95", + "cyan": "96", + "white": "97", } color_code = colors.get(color) if color_code: @@ -95,7 +111,5 @@ def term_color(text: str, color: str) -> str: return text - def is_snake_case(string: str): - return bool(re.match('^[a-z0-9_]+$', string)) - + return bool(re.match("^[a-z0-9_]+$", string)) diff --git a/examples/howards_agent/src/crew.py b/examples/howards_agent/src/crew.py index ad09087f..47ea56de 100644 --- a/examples/howards_agent/src/crew.py +++ b/examples/howards_agent/src/crew.py @@ -2,39 +2,42 @@ from crewai.project import CrewBase, agent, crew, task import tools + @CrewBase -class HowardsagentCrew(): - """howards_agent crew""" +class HowardsagentCrew: + """howards_agent crew""" + + # Agent definitions + @agent + def agent1(self) -> Agent: + return Agent( + config=self.agents_config["agent1"], + tools=[ + *tools.composio_tools, + ], # Pass in what tools this agent should have + verbose=True, + ) - # Agent definitions - @agent - def agent1(self) -> Agent: - return Agent( - config=self.agents_config['agent1'], - tools=[*tools.composio_tools,], # Pass in what tools this agent should have - verbose=True - ) + # Task definitions + @task + def new_task(self) -> Task: + return Task( + config=self.tasks_config["new_task"], + ) - # Task definitions - @task - def new_task(self) -> Task: - return Task( - config=self.tasks_config['new_task'], - ) - - @task - def task1(self) -> Task: - return Task( - config=self.tasks_config['task1'], - ) + @task + def task1(self) -> Task: + return Task( + config=self.tasks_config["task1"], + ) - @crew - def crew(self) -> Crew: - """Creates the Test crew""" - return Crew( - agents=self.agents, # Automatically created by the @agent decorator - tasks=self.tasks, # Automatically created by the @task decorator - process=Process.sequential, - verbose=True, - # process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/ - ) + @crew + def crew(self) -> Crew: + """Creates the Test crew""" + return Crew( + agents=self.agents, # Automatically created by the @agent decorator + tasks=self.tasks, # Automatically created by the @task decorator + process=Process.sequential, + verbose=True, + # process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/ + ) diff --git a/examples/howards_agent/src/main.py b/examples/howards_agent/src/main.py index 605268f4..d68b4e4f 100644 --- a/examples/howards_agent/src/main.py +++ b/examples/howards_agent/src/main.py @@ -3,6 +3,7 @@ from crew import HowardsagentCrew import agentops from dotenv import load_dotenv + load_dotenv() agentops.init() @@ -12,13 +13,12 @@ # Replace with inputs you want to test with, it will automatically # interpolate any tasks and agents information + def run(): """ Run the crew. """ - inputs = { - 'topic': 'AI LLMs' - } + inputs = {"topic": "AI LLMs"} HowardsagentCrew().crew().kickoff(inputs=inputs) @@ -26,11 +26,11 @@ def train(): """ Train the crew for a given number of iterations. """ - inputs = { - "topic": "AI LLMs" - } + inputs = {"topic": "AI LLMs"} try: - HowardsagentCrew().crew().train(n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs) + HowardsagentCrew().crew().train( + n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs + ) except Exception as e: raise Exception(f"An error occurred while training the crew: {e}") @@ -51,11 +51,11 @@ def test(): """ Test the crew execution and returns the results. """ - inputs = { - "topic": "AI LLMs" - } + inputs = {"topic": "AI LLMs"} try: - HowardsagentCrew().crew().test(n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs) + HowardsagentCrew().crew().test( + n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs + ) except Exception as e: raise Exception(f"An error occurred while replaying the crew: {e}") diff --git a/examples/howards_agent/src/tools/__init__.py b/examples/howards_agent/src/tools/__init__.py index ed0105e1..4330ab36 100644 --- a/examples/howards_agent/src/tools/__init__.py +++ b/examples/howards_agent/src/tools/__init__.py @@ -1,4 +1,3 @@ - # tool import diff --git a/examples/howards_agent/src/tools/composio_tool.py b/examples/howards_agent/src/tools/composio_tool.py index 9ad350ec..197c7e00 100644 --- a/examples/howards_agent/src/tools/composio_tool.py +++ b/examples/howards_agent/src/tools/composio_tool.py @@ -4,4 +4,3 @@ # change App.CODEINTERPRETER to be the app you want to use # For more info on tool selection, see https://docs.agentstack.sh/tools/tool/composio - diff --git a/examples/howards_agent/src/tools/mem0_tool.py b/examples/howards_agent/src/tools/mem0_tool.py index 1798da5b..134e92e4 100644 --- a/examples/howards_agent/src/tools/mem0_tool.py +++ b/examples/howards_agent/src/tools/mem0_tool.py @@ -12,11 +12,11 @@ "provider": "neo4j", "config": { "url": os.getenv("NEO4J_URL"), - "username": os.getenv("NEO4J_USERNAME", 'neo4j'), + "username": os.getenv("NEO4J_USERNAME", "neo4j"), "password": os.getenv("NEO4J_PASSWORD"), - } + }, }, - "version": "v1.1" + "version": "v1.1", } memory = Memory.from_config(config) diff --git a/examples/job_posting/src/crew.py b/examples/job_posting/src/crew.py index 2a341e2c..0e6385a5 100644 --- a/examples/job_posting/src/crew.py +++ b/examples/job_posting/src/crew.py @@ -8,45 +8,48 @@ class JobpostingCrew: """job_posting crew""" @agent - def review_agent(self) ->Agent: - return Agent(config=self.agents_config['review_agent'], tools=[], - verbose=True) + def review_agent(self) -> Agent: + return Agent(config=self.agents_config["review_agent"], tools=[], verbose=True) @agent - def writer_agent(self) ->Agent: - return Agent(config=self.agents_config['writer_agent'], tools=[], - verbose=True) + def writer_agent(self) -> Agent: + return Agent(config=self.agents_config["writer_agent"], tools=[], verbose=True) @agent - def researcher_agent(self) ->Agent: - return Agent(config=self.agents_config['research_agent'], tools=[ - tools.web_scrape, tools.web_crawl, tools.retrieve_web_crawl], - verbose=True) + def researcher_agent(self) -> Agent: + return Agent( + config=self.agents_config["research_agent"], + tools=[tools.web_scrape, tools.web_crawl, tools.retrieve_web_crawl], + verbose=True, + ) @task - def research_company_culture_task(self) ->Task: - return Task(config=self.tasks_config['research_company_culture_task']) + def research_company_culture_task(self) -> Task: + return Task(config=self.tasks_config["research_company_culture_task"]) @task - def research_role_requirements_task(self) ->Task: - return Task(config=self.tasks_config['research_role_requirements_task'] - ) + def research_role_requirements_task(self) -> Task: + return Task(config=self.tasks_config["research_role_requirements_task"]) @task - def draft_job_posting_task(self) ->Task: - return Task(config=self.tasks_config['draft_job_posting_task']) + def draft_job_posting_task(self) -> Task: + return Task(config=self.tasks_config["draft_job_posting_task"]) @task - def review_and_edit_job_posting_task(self) ->Task: - return Task(config=self.tasks_config[ - 'review_and_edit_job_posting_task']) + def review_and_edit_job_posting_task(self) -> Task: + return Task(config=self.tasks_config["review_and_edit_job_posting_task"]) @task - def industry_analysis_task(self) ->Task: - return Task(config=self.tasks_config['industry_analysis_task']) + def industry_analysis_task(self) -> Task: + return Task(config=self.tasks_config["industry_analysis_task"]) @crew - def crew(self) ->Crew: + def crew(self) -> Crew: """Creates the Test crew""" - return Crew(agents=self.agents, tasks=self.tasks, verbose=True, - process=Process.hierarchical, manager_llm='openai/gpt-4o') + return Crew( + agents=self.agents, + tasks=self.tasks, + verbose=True, + process=Process.hierarchical, + manager_llm="openai/gpt-4o", + ) diff --git a/examples/job_posting/src/main.py b/examples/job_posting/src/main.py index 8abd6f2d..699626fd 100644 --- a/examples/job_posting/src/main.py +++ b/examples/job_posting/src/main.py @@ -3,16 +3,18 @@ from crew import JobpostingCrew import agentops from dotenv import load_dotenv + load_dotenv() agentops.init() inputs = { - 'company_domain': 'https://agen.cy', - 'company_description': "From open source AI agent developer tools like AgentOps to Fortune 500 enterprises, we help clients create safe, reliable, and scalable AI agents.", - 'hiring_needs': 'Infrastructure engineer for deploying AI agents at scale', - 'specific_benefits': 'Daily lunch', - } + "company_domain": "https://agen.cy", + "company_description": "From open source AI agent developer tools like AgentOps to Fortune 500 enterprises, we help clients create safe, reliable, and scalable AI agents.", + "hiring_needs": "Infrastructure engineer for deploying AI agents at scale", + "specific_benefits": "Daily lunch", +} + def run(): """ @@ -26,7 +28,9 @@ def train(): Train the crew for a given number of iterations. """ try: - JobpostingCrew().crew().train(n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs) + JobpostingCrew().crew().train( + n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs + ) except Exception as e: raise Exception(f"An error occurred while training the crew: {e}") @@ -48,11 +52,13 @@ def test(): Test the crew execution and returns the results. """ try: - JobpostingCrew().crew().test(n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs) + JobpostingCrew().crew().test( + n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs + ) except Exception as e: raise Exception(f"An error occurred while replaying the crew: {e}") -if __name__ == '__main__': - run() \ No newline at end of file +if __name__ == "__main__": + run() diff --git a/examples/job_posting/src/tools/__init__.py b/examples/job_posting/src/tools/__init__.py index f1654b8d..b1387c10 100644 --- a/examples/job_posting/src/tools/__init__.py +++ b/examples/job_posting/src/tools/__init__.py @@ -1,24 +1,4 @@ - # tool import from .firecrawl_tool import web_scrape, web_crawl, retrieve_web_crawl - - - - - - - - - - - - - - - - - - - diff --git a/examples/job_posting/src/tools/firecrawl_tool.py b/examples/job_posting/src/tools/firecrawl_tool.py index 65c66c24..4f1cf1e0 100644 --- a/examples/job_posting/src/tools/firecrawl_tool.py +++ b/examples/job_posting/src/tools/firecrawl_tool.py @@ -2,7 +2,7 @@ from firecrawl import FirecrawlApp import os -app = FirecrawlApp(api_key=os.getenv('FIRECRAWL_API_KEY')) +app = FirecrawlApp(api_key=os.getenv("FIRECRAWL_API_KEY")) @tool @@ -11,7 +11,7 @@ def web_scrape(url: str): Scrape a url and return markdown. Use this to read a singular page and web_crawl only if you need to read all other links as well. """ - scrape_result = app.scrape_url(url, params={'formats': ['markdown']}) + scrape_result = app.scrape_url(url, params={"formats": ["markdown"]}) return scrape_result @@ -29,11 +29,8 @@ def web_crawl(url: str): crawl_status = app.crawl_url( url, - params={ - 'limit': 100, - 'scrapeOptions': {'formats': ['markdown']} - }, - poll_interval=30 + params={"limit": 100, "scrapeOptions": {"formats": ["markdown"]}}, + poll_interval=30, ) return crawl_status @@ -47,4 +44,3 @@ def retrieve_web_crawl(crawl_id: str): will tell you if the crawl is finished. If it is not, wait some more time then try again. """ return app.check_crawl_status(crawl_id) - diff --git a/examples/stock_analysis/src/crew.py b/examples/stock_analysis/src/crew.py index 1bef7a62..165eeee8 100644 --- a/examples/stock_analysis/src/crew.py +++ b/examples/stock_analysis/src/crew.py @@ -4,37 +4,39 @@ @CrewBase -class StockanalysisCrew(): +class StockanalysisCrew: """stock_analysis crew""" # Agent definitions @agent def analyst(self) -> Agent: return Agent( - config=self.agents_config['analyst'], + config=self.agents_config["analyst"], tools=[], # add tools here or use `agentstack tools add - verbose=True + verbose=True, ) @agent def researcher(self) -> Agent: return Agent( - config=self.agents_config['researcher'], - tools=[tools.query_perplexity, ], # add tools here or use `agentstack tools add - verbose=True + config=self.agents_config["researcher"], + tools=[ + tools.query_perplexity, + ], # add tools here or use `agentstack tools add + verbose=True, ) # Task definitions @task def research_stock(self) -> Task: return Task( - config=self.tasks_config['research_stock'], + config=self.tasks_config["research_stock"], ) @task def buy_sell_decision(self) -> Task: return Task( - config=self.tasks_config['buy_sell_decision'], + config=self.tasks_config["buy_sell_decision"], ) @crew diff --git a/examples/stock_analysis/src/main.py b/examples/stock_analysis/src/main.py index 11ad98b9..c2523190 100644 --- a/examples/stock_analysis/src/main.py +++ b/examples/stock_analysis/src/main.py @@ -3,6 +3,7 @@ from crew import StockanalysisCrew import agentops from dotenv import load_dotenv + load_dotenv() agentops.init() @@ -12,13 +13,12 @@ # Replace with inputs you want to test with, it will automatically # interpolate any tasks and agents information + def run(): """ Run the crew. """ - inputs = { - 'topic': 'AI LLMs' - } + inputs = {"topic": "AI LLMs"} StockanalysisCrew().crew().kickoff(inputs=inputs) @@ -26,11 +26,11 @@ def train(): """ Train the crew for a given number of iterations. """ - inputs = { - "topic": "AI LLMs" - } + inputs = {"topic": "AI LLMs"} try: - StockanalysisCrew().crew().train(n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs) + StockanalysisCrew().crew().train( + n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs + ) except Exception as e: raise Exception(f"An error occurred while training the crew: {e}") @@ -51,15 +51,15 @@ def test(): """ Test the crew execution and returns the results. """ - inputs = { - "topic": "AI LLMs" - } + inputs = {"topic": "AI LLMs"} try: - StockanalysisCrew().crew().test(n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs) + StockanalysisCrew().crew().test( + n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs + ) except Exception as e: raise Exception(f"An error occurred while replaying the crew: {e}") -if __name__ == '__main__': - run() \ No newline at end of file +if __name__ == "__main__": + run() diff --git a/examples/stock_analysis/src/tools/__init__.py b/examples/stock_analysis/src/tools/__init__.py index ca62890f..4fdccde1 100644 --- a/examples/stock_analysis/src/tools/__init__.py +++ b/examples/stock_analysis/src/tools/__init__.py @@ -1,4 +1,3 @@ - # tool import from .perplexity_tool import query_perplexity diff --git a/examples/stock_analysis/src/tools/perplexity_tool.py b/examples/stock_analysis/src/tools/perplexity_tool.py index a5a361b5..acdb7544 100644 --- a/examples/stock_analysis/src/tools/perplexity_tool.py +++ b/examples/stock_analysis/src/tools/perplexity_tool.py @@ -4,11 +4,13 @@ from crewai_tools import tool from dotenv import load_dotenv + load_dotenv() url = "https://api.perplexity.ai/chat/completions" api_key = os.getenv("PERPLEXITY_API_KEY") + @tool def query_perplexity(query: str): """ @@ -18,14 +20,8 @@ def query_perplexity(query: str): payload = { "model": "llama-3.1-sonar-small-128k-online", "messages": [ - { - "role": "system", - "content": "Be precise and concise." - }, - { - "role": "user", - "content": query - } + {"role": "system", "content": "Be precise and concise."}, + {"role": "user", "content": query}, ], # "max_tokens": "Optional", "temperature": 0.2, @@ -38,12 +34,9 @@ def query_perplexity(query: str): "top_k": 0, "stream": False, "presence_penalty": 0, - "frequency_penalty": 1 - } - headers = { - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json" + "frequency_penalty": 1, } + headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} response = requests.request("POST", url, json=payload, headers=headers) if response.status_code == 200 and response.text: diff --git a/examples/trip_planner/src/crew.py b/examples/trip_planner/src/crew.py index 9d479dd2..3558ac1c 100644 --- a/examples/trip_planner/src/crew.py +++ b/examples/trip_planner/src/crew.py @@ -2,62 +2,78 @@ from crewai.project import CrewBase, agent, crew, task import tools + @CrewBase -class TripplannerCrew(): - """trip_planner crew""" - - # Agent definitions - @agent - def city_selection_expert(self) -> Agent: - return Agent( - config=self.agents_config['city_selection_expert'], - tools=[tools.Browserbase, tools.Browserbase, tools.Browserbase, tools.Browserbase, tools.Browserbase, ], # Pass in what tools this agent should have - verbose=True - ) - - @agent - def local_expert(self) -> Agent: - return Agent( - config=self.agents_config['local_expert'], - tools=[tools.Browserbase, tools.Browserbase, tools.Browserbase, tools.Browserbase, tools.Browserbase, ], # Pass in what tools this agent should have - verbose=True - ) - - @agent - def travel_concierge(self) -> Agent: - return Agent( - config=self.agents_config['travel_concierge'], - tools=[tools.Browserbase, tools.Browserbase, tools.Browserbase, tools.Browserbase, tools.Browserbase, ], # Pass in what tools this agent should have - verbose=True - ) - - # Task definitions - @task - def identify_task(self) -> Task: - return Task( - config=self.tasks_config['identify_task'], - ) - - @task - def gather_task(self) -> Task: - return Task( - config=self.tasks_config['gather_task'], - ) - - @task - def plan_task(self) -> Task: - return Task( - config=self.tasks_config['plan_task'], - output_file="itinerary.md" - ) - - @crew - def crew(self) -> Crew: - """Creates the Test crew""" - return Crew( - agents=self.agents, # Automatically created by the @agent decorator - tasks=self.tasks, # Automatically created by the @task decorator - process=Process.sequential, - verbose=True, - # process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/ - ) \ No newline at end of file +class TripplannerCrew: + """trip_planner crew""" + + # Agent definitions + @agent + def city_selection_expert(self) -> Agent: + return Agent( + config=self.agents_config["city_selection_expert"], + tools=[ + tools.Browserbase, + tools.Browserbase, + tools.Browserbase, + tools.Browserbase, + tools.Browserbase, + ], # Pass in what tools this agent should have + verbose=True, + ) + + @agent + def local_expert(self) -> Agent: + return Agent( + config=self.agents_config["local_expert"], + tools=[ + tools.Browserbase, + tools.Browserbase, + tools.Browserbase, + tools.Browserbase, + tools.Browserbase, + ], # Pass in what tools this agent should have + verbose=True, + ) + + @agent + def travel_concierge(self) -> Agent: + return Agent( + config=self.agents_config["travel_concierge"], + tools=[ + tools.Browserbase, + tools.Browserbase, + tools.Browserbase, + tools.Browserbase, + tools.Browserbase, + ], # Pass in what tools this agent should have + verbose=True, + ) + + # Task definitions + @task + def identify_task(self) -> Task: + return Task( + config=self.tasks_config["identify_task"], + ) + + @task + def gather_task(self) -> Task: + return Task( + config=self.tasks_config["gather_task"], + ) + + @task + def plan_task(self) -> Task: + return Task(config=self.tasks_config["plan_task"], output_file="itinerary.md") + + @crew + def crew(self) -> Crew: + """Creates the Test crew""" + return Crew( + agents=self.agents, # Automatically created by the @agent decorator + tasks=self.tasks, # Automatically created by the @task decorator + process=Process.sequential, + verbose=True, + # process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/ + ) diff --git a/examples/trip_planner/src/main.py b/examples/trip_planner/src/main.py index aca11667..ac49413c 100644 --- a/examples/trip_planner/src/main.py +++ b/examples/trip_planner/src/main.py @@ -10,15 +10,16 @@ # Replace with inputs you want to test with, it will automatically # interpolate any tasks and agents information + def run(): """ Run the crew. """ inputs = { - 'origin': 'San Francisco', - 'cities': 'austin,berlin,tokyo', - 'interests': 'techno,history,culture,art,music', - 'range': 'one week between next may to july', + "origin": "San Francisco", + "cities": "austin,berlin,tokyo", + "interests": "techno,history,culture,art,music", + "range": "one week between next may to july", } TripplannerCrew().crew().kickoff(inputs=inputs) @@ -27,11 +28,11 @@ def train(): """ Train the crew for a given number of iterations. """ - inputs = { - "topic": "AI LLMs" - } + inputs = {"topic": "AI LLMs"} try: - TripplannerCrew().crew().train(n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs) + TripplannerCrew().crew().train( + n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs + ) except Exception as e: raise Exception(f"An error occurred while training the crew: {e}") @@ -52,15 +53,15 @@ def test(): """ Test the crew execution and returns the results. """ - inputs = { - "topic": "AI LLMs" - } + inputs = {"topic": "AI LLMs"} try: - TripplannerCrew().crew().test(n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs) + TripplannerCrew().crew().test( + n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs + ) except Exception as e: raise Exception(f"An error occurred while replaying the crew: {e}") -if __name__ == '__main__': - run() \ No newline at end of file +if __name__ == "__main__": + run() diff --git a/examples/trip_planner/src/tools/__init__.py b/examples/trip_planner/src/tools/__init__.py index b0e4ead8..5cfb9bb7 100644 --- a/examples/trip_planner/src/tools/__init__.py +++ b/examples/trip_planner/src/tools/__init__.py @@ -1,4 +1,3 @@ - # tool import diff --git a/examples/trip_planner/src/tools/browserbase.py b/examples/trip_planner/src/tools/browserbase.py index 08057260..00eaa919 100644 --- a/examples/trip_planner/src/tools/browserbase.py +++ b/examples/trip_planner/src/tools/browserbase.py @@ -1,3 +1,3 @@ from crewai_tools import BrowserbaseLoadTool -Browserbase = BrowserbaseLoadTool(text_content=True) \ No newline at end of file +Browserbase = BrowserbaseLoadTool(text_content=True) diff --git a/examples/web_researcher/src/crew.py b/examples/web_researcher/src/crew.py index 01cfac22..f7b0f33b 100644 --- a/examples/web_researcher/src/crew.py +++ b/examples/web_researcher/src/crew.py @@ -8,35 +8,45 @@ class WebresearcherCrew: """web_researcher crew""" @agent - def content_summarizer(self) ->Agent: - return Agent(config=self.agents_config['content_summarizer'], tools - =[], verbose=True) + def content_summarizer(self) -> Agent: + return Agent( + config=self.agents_config["content_summarizer"], tools=[], verbose=True + ) @agent - def web_scraper(self) ->Agent: - return Agent(config=self.agents_config['web_scraper'], tools=[tools - .web_scrape], verbose=True) + def web_scraper(self) -> Agent: + return Agent( + config=self.agents_config["web_scraper"], + tools=[tools.web_scrape], + verbose=True, + ) @agent - def content_storer(self) ->Agent: - return Agent(config=self.agents_config['content_storer'], tools=[ - tools.create_database, tools.execute_sql_ddl, tools. - run_sql_query], verbose=True) + def content_storer(self) -> Agent: + return Agent( + config=self.agents_config["content_storer"], + tools=[tools.create_database, tools.execute_sql_ddl, tools.run_sql_query], + verbose=True, + ) @task - def scrape_site(self) ->Task: - return Task(config=self.tasks_config['scrape_site']) + def scrape_site(self) -> Task: + return Task(config=self.tasks_config["scrape_site"]) @task - def summarize(self) ->Task: - return Task(config=self.tasks_config['summarize']) + def summarize(self) -> Task: + return Task(config=self.tasks_config["summarize"]) @task - def store(self) ->Task: - return Task(config=self.tasks_config['store']) + def store(self) -> Task: + return Task(config=self.tasks_config["store"]) @crew - def crew(self) ->Crew: + def crew(self) -> Crew: """Creates the Test crew""" - return Crew(agents=self.agents, tasks=self.tasks, process=Process. - sequential, verbose=True) + return Crew( + agents=self.agents, + tasks=self.tasks, + process=Process.sequential, + verbose=True, + ) diff --git a/examples/web_researcher/src/main.py b/examples/web_researcher/src/main.py index feceaed6..e8b9897d 100644 --- a/examples/web_researcher/src/main.py +++ b/examples/web_researcher/src/main.py @@ -6,6 +6,7 @@ from crew import WebresearcherCrew import agentops from dotenv import load_dotenv + load_dotenv() agentops.init() @@ -19,9 +20,7 @@ def run(inputs: Optional[dict] = None): print(inputs) if not inputs: - inputs = { - 'url': 'https://github.com/AgentOps-AI/AgentStack/tree/main' - } + inputs = {"url": "https://github.com/AgentOps-AI/AgentStack/tree/main"} return WebresearcherCrew().crew().kickoff(inputs=inputs) @@ -29,11 +28,11 @@ def train(): """ Train the crew for a given number of iterations. """ - inputs = { - "topic": "AI LLMs" - } + inputs = {"topic": "AI LLMs"} try: - WebresearcherCrew().crew().train(n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs) + WebresearcherCrew().crew().train( + n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs + ) except Exception as e: raise Exception(f"An error occurred while training the crew: {e}") @@ -54,19 +53,19 @@ def test(): """ Test the crew execution and returns the results. """ - inputs = { - "topic": "AI LLMs" - } + inputs = {"topic": "AI LLMs"} try: - WebresearcherCrew().crew().test(n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs) + WebresearcherCrew().crew().test( + n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs + ) except Exception as e: raise Exception(f"An error occurred while replaying the crew: {e}") -if __name__ == '__main__': +if __name__ == "__main__": data = None if len(sys.argv) > 1: data_str = sys.argv[1] data = json.loads(data_str) - run(data) \ No newline at end of file + run(data) diff --git a/examples/web_researcher/src/tools/__init__.py b/examples/web_researcher/src/tools/__init__.py index fafc6f7e..db89c8c8 100644 --- a/examples/web_researcher/src/tools/__init__.py +++ b/examples/web_researcher/src/tools/__init__.py @@ -1,4 +1,3 @@ - # tool import from .firecrawl_tool import web_scrape, web_crawl, retrieve_web_crawl from .neon_tool import create_database, execute_sql_ddl, run_sql_query diff --git a/examples/web_researcher/src/tools/firecrawl_tool.py b/examples/web_researcher/src/tools/firecrawl_tool.py index 65c66c24..4f1cf1e0 100644 --- a/examples/web_researcher/src/tools/firecrawl_tool.py +++ b/examples/web_researcher/src/tools/firecrawl_tool.py @@ -2,7 +2,7 @@ from firecrawl import FirecrawlApp import os -app = FirecrawlApp(api_key=os.getenv('FIRECRAWL_API_KEY')) +app = FirecrawlApp(api_key=os.getenv("FIRECRAWL_API_KEY")) @tool @@ -11,7 +11,7 @@ def web_scrape(url: str): Scrape a url and return markdown. Use this to read a singular page and web_crawl only if you need to read all other links as well. """ - scrape_result = app.scrape_url(url, params={'formats': ['markdown']}) + scrape_result = app.scrape_url(url, params={"formats": ["markdown"]}) return scrape_result @@ -29,11 +29,8 @@ def web_crawl(url: str): crawl_status = app.crawl_url( url, - params={ - 'limit': 100, - 'scrapeOptions': {'formats': ['markdown']} - }, - poll_interval=30 + params={"limit": 100, "scrapeOptions": {"formats": ["markdown"]}}, + poll_interval=30, ) return crawl_status @@ -47,4 +44,3 @@ def retrieve_web_crawl(crawl_id: str): will tell you if the crawl is finished. If it is not, wait some more time then try again. """ return app.check_crawl_status(crawl_id) - diff --git a/pyproject.toml b/pyproject.toml index 6b49c749..e0041815 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,3 +37,17 @@ agentstack = ["templates/**/*"] [project.scripts] agentstack = "agentstack.main:main" + +[tool.ruff] +exclude = [ + ".git", + ".env", + ".venv", + "venv", + "env", + "__pycache__", + "build", + "dist", + "*.egg-info", + "agentstack/templates/", +] \ No newline at end of file diff --git a/tests/test_cli_loads.py b/tests/test_cli_loads.py index 49bb15cd..ce166cb1 100644 --- a/tests/test_cli_loads.py +++ b/tests/test_cli_loads.py @@ -6,14 +6,16 @@ class TestAgentStackCLI(unittest.TestCase): - CLI_ENTRY = [sys.executable, "-m", "agentstack.main"] # Replace with your actual CLI entry point if different + CLI_ENTRY = [ + sys.executable, + "-m", + "agentstack.main", + ] # Replace with your actual CLI entry point if different def run_cli(self, *args): """Helper method to run the CLI with arguments.""" result = subprocess.run( - [*self.CLI_ENTRY, *args], - capture_output=True, - text=True + [*self.CLI_ENTRY, *args], capture_output=True, text=True ) return result diff --git a/tests/test_generation_files.py b/tests/test_generation_files.py index e2d80d7e..3745d04c 100644 --- a/tests/test_generation_files.py +++ b/tests/test_generation_files.py @@ -4,32 +4,41 @@ from pathlib import Path import shutil from agentstack.generation.files import ConfigFile, EnvFile -from agentstack.utils import verify_agentstack_project, get_framework, get_telemetry_opt_out +from agentstack.utils import ( + verify_agentstack_project, + get_framework, + get_telemetry_opt_out, +) BASE_PATH = Path(__file__).parent + class GenerationFilesTest(unittest.TestCase): def test_read_config(self): - config = ConfigFile(BASE_PATH / "fixtures") # + agentstack.json + config = ConfigFile(BASE_PATH / "fixtures") # + agentstack.json assert config.framework == "crewai" assert config.tools == ["tool1", "tool2"] assert config.telemetry_opt_out is None assert config.default_model is None - + def test_write_config(self): try: - os.makedirs(BASE_PATH/"tmp", exist_ok=True) - shutil.copy(BASE_PATH/"fixtures/agentstack.json", - BASE_PATH/"tmp/agentstack.json") - - with ConfigFile(BASE_PATH/"tmp") as config: + os.makedirs(BASE_PATH / "tmp", exist_ok=True) + shutil.copy( + BASE_PATH / "fixtures/agentstack.json", + BASE_PATH / "tmp/agentstack.json", + ) + + with ConfigFile(BASE_PATH / "tmp") as config: config.framework = "crewai" config.tools = ["tool1", "tool2"] config.telemetry_opt_out = True config.default_model = "openai/gpt-4o" - - tmp_data = open(BASE_PATH/"tmp/agentstack.json").read() - assert tmp_data == """{ + + tmp_data = open(BASE_PATH / "tmp/agentstack.json").read() + assert ( + tmp_data + == """{ "framework": "crewai", "tools": [ "tool1", @@ -38,11 +47,12 @@ def test_write_config(self): "telemetry_opt_out": true, "default_model": "openai/gpt-4o" }""" + ) except Exception as e: raise e finally: os.remove(BASE_PATH / "tmp/agentstack.json") - #os.rmdir(BASE_PATH / "tmp") + # os.rmdir(BASE_PATH / "tmp") def test_read_missing_config(self): with self.assertRaises(FileNotFoundError) as context: @@ -54,17 +64,17 @@ def test_verify_agentstack_project_valid(self): def test_verify_agentstack_project_invalid(self): with self.assertRaises(SystemExit) as context: verify_agentstack_project(BASE_PATH / "missing") - + def test_get_framework(self): assert get_framework(BASE_PATH / "fixtures") == "crewai" with self.assertRaises(SystemExit) as context: get_framework(BASE_PATH / "missing") - + def test_get_telemetry_opt_out(self): assert get_telemetry_opt_out(BASE_PATH / "fixtures") is False with self.assertRaises(SystemExit) as context: get_telemetry_opt_out(BASE_PATH / "missing") - + def test_read_env(self): env = EnvFile(BASE_PATH / "fixtures") assert env.variables == {"ENV_VAR1": "value1", "ENV_VAR2": "value2"} @@ -72,22 +82,22 @@ def test_read_env(self): assert env["ENV_VAR2"] == "value2" with self.assertRaises(KeyError) as context: env["ENV_VAR3"] - + def test_write_env(self): try: - os.makedirs(BASE_PATH/"tmp", exist_ok=True) - shutil.copy(BASE_PATH/"fixtures/.env", - BASE_PATH/"tmp/.env") - - with EnvFile(BASE_PATH/"tmp") as env: - env.append_if_new("ENV_VAR1", "value100") # Should not be updated - env.append_if_new("ENV_VAR100", "value2") # Should be added - - tmp_data = open(BASE_PATH/"tmp/.env").read() - assert tmp_data == """\nENV_VAR1=value1\nENV_VAR2=value2\nENV_VAR100=value2""" + os.makedirs(BASE_PATH / "tmp", exist_ok=True) + shutil.copy(BASE_PATH / "fixtures/.env", BASE_PATH / "tmp/.env") + + with EnvFile(BASE_PATH / "tmp") as env: + env.append_if_new("ENV_VAR1", "value100") # Should not be updated + env.append_if_new("ENV_VAR100", "value2") # Should be added + + tmp_data = open(BASE_PATH / "tmp/.env").read() + assert ( + tmp_data == """\nENV_VAR1=value1\nENV_VAR2=value2\nENV_VAR100=value2""" + ) except Exception as e: raise e finally: os.remove(BASE_PATH / "tmp/.env") - #os.rmdir(BASE_PATH / "tmp") - + # os.rmdir(BASE_PATH / "tmp") diff --git a/tests/test_tool_config.py b/tests/test_tool_config.py index 90931c7b..fb4c98c5 100644 --- a/tests/test_tool_config.py +++ b/tests/test_tool_config.py @@ -3,10 +3,15 @@ import unittest import importlib.resources from pathlib import Path -from agentstack.generation.tool_generation import get_all_tool_paths, get_all_tool_names, ToolConfig +from agentstack.generation.tool_generation import ( + get_all_tool_paths, + get_all_tool_names, + ToolConfig, +) BASE_PATH = Path(__file__).parent + class ToolConfigTest(unittest.TestCase): def test_minimal_json(self): config = ToolConfig.from_json(BASE_PATH / "fixtures/tool_config_min.json") @@ -20,7 +25,7 @@ def test_minimal_json(self): assert config.packages is None assert config.post_install is None assert config.post_remove is None - + def test_maximal_json(self): config = ToolConfig.from_json(BASE_PATH / "fixtures/tool_config_max.json") assert config.name == "tool_name" @@ -33,7 +38,7 @@ def test_maximal_json(self): assert config.packages == ["package1", "package2"] assert config.post_install == "install.sh" assert config.post_remove == "remove.sh" - + def test_all_json_configs_from_tool_name(self): for tool_name in get_all_tool_names(): config = ToolConfig.from_tool_name(tool_name) @@ -45,7 +50,9 @@ def test_all_json_configs_from_tool_path(self): try: config = ToolConfig.from_json(path) except json.decoder.JSONDecodeError as e: - raise Exception(f"Failed to decode tool json at {path}. Does your tool config fit the required formatting? https://github.com/AgentOps-AI/AgentStack/blob/main/agentstack/tools/~README.md") + raise Exception( + f"Failed to decode tool json at {path}. Does your tool config fit the required formatting? https://github.com/AgentOps-AI/AgentStack/blob/main/agentstack/tools/~README.md" + ) assert config.name == path.stem # We can assume that pydantic validation caught any other issues diff --git a/tests/test_utils.py b/tests/test_utils.py index 720965d2..edf4cdee 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -5,12 +5,12 @@ class TestUtils(unittest.TestCase): def test_clean_input_no_change(self): - cleaned = clean_input('test_project') - self.assertEqual('test_project', cleaned) + cleaned = clean_input("test_project") + self.assertEqual("test_project", cleaned) def test_clean_input_remove_space(self): - cleaned = clean_input('test project') - self.assertEqual('test_project', cleaned) + cleaned = clean_input("test project") + self.assertEqual("test_project", cleaned) def test_is_snake_case(self): assert is_snake_case("hello_world") From de56fea09539534b765f5accfc8e6ec1ce60d415 Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Tue, 3 Dec 2024 01:22:56 -0800 Subject: [PATCH 2/9] ruff guide --- CONTRIBUTING.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d0c57fe..3f09f135 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,5 +35,16 @@ Adding tools is easy once you understand the project structure. A few things nee - The tools that are exported from this file should be listed in the tool's config json. 4. Manually test your tool integration by running `agentstack tools add ` and ensure it behaves as expected. +## Before creating your PR +Be sure that you are opening a PR using a branch other than `main` on your fork. This enables us +to pull your branch and make modifications to the PR with your permission that may be helpful. + +### Formatting +AgentStack uses Ruff formatter for consistent code formatting. To format your code, run: +```bash +pip install ruff +ruff format . +``` + ## Tests HAHAHAHAHAHAHA good one \ No newline at end of file From fc8e934aded6117261d59619029dd1f81afce43a Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Tue, 3 Dec 2024 01:29:20 -0800 Subject: [PATCH 3/9] ruff formatting more exclusions --- agentstack/cli/agentstack_data.py | 1 - agentstack/cli/cli.py | 12 +++++++----- agentstack/generation/files.py | 2 +- agentstack/generation/tool_generation.py | 17 ++++++----------- agentstack/main.py | 6 ++---- agentstack/telemetry.py | 2 +- agentstack/utils.py | 3 +-- examples/trip_planner/src/tools/__init__.py | 3 --- examples/web_researcher/src/tools/neon_tool.py | 4 ++-- pyproject.toml | 2 ++ tests/test_generation_files.py | 15 +++++++-------- tests/test_tool_config.py | 4 +--- 12 files changed, 30 insertions(+), 41 deletions(-) diff --git a/agentstack/cli/agentstack_data.py b/agentstack/cli/agentstack_data.py index 93deef37..a071e2fe 100644 --- a/agentstack/cli/agentstack_data.py +++ b/agentstack/cli/agentstack_data.py @@ -1,6 +1,5 @@ import json from datetime import datetime -from typing import Optional, Literal from agentstack.utils import clean_input, get_version from agentstack.logger import log diff --git a/agentstack/cli/cli.py b/agentstack/cli/cli.py index 23703619..abbbcd1c 100644 --- a/agentstack/cli/cli.py +++ b/agentstack/cli/cli.py @@ -142,7 +142,7 @@ def configure_default_model(path: Optional[str] = None): return # Default model already set print("Project does not have a default model configured.") - other_msg = f"Other (enter a model name)" + other_msg = "Other (enter a model name)" model = inquirer.list_input( message="Which model would you like to use?", choices=PREFERRED_MODELS + [other_msg], @@ -150,7 +150,7 @@ def configure_default_model(path: Optional[str] = None): if model == other_msg: # If the user selects "Other", prompt for a model name print( - f'A list of available models is available at: "https://docs.litellm.ai/docs/providers"' + 'A list of available models is available at: "https://docs.litellm.ai/docs/providers"' ) model = inquirer.text(message="Enter the model name") @@ -200,11 +200,13 @@ def ask_design() -> dict: print(title) - print(""" + print( + """ 🪄 welcome to the agent builder wizard!! 🪄 First we need to create the agents that will work together to accomplish tasks: - """) + """ + ) make_agent = True agents = [] while make_agent: @@ -435,7 +437,7 @@ def insert_template( pass # subprocess.check_output(["git", "init"]) # subprocess.check_output(["git", "add", "."]) - except: + except Exception: print( "Failed to initialize git repository. Maybe you're already in one? Do this with: git init" ) diff --git a/agentstack/generation/files.py b/agentstack/generation/files.py index f930d867..4aec6fea 100644 --- a/agentstack/generation/files.py +++ b/agentstack/generation/files.py @@ -106,7 +106,7 @@ def __contains__(self, key) -> bool: return key in self.variables def append_if_new(self, key, value): - if not key in self.variables: + if key not in self.variables: self.variables[key] = value self._new_variables[key] = value diff --git a/agentstack/generation/tool_generation.py b/agentstack/generation/tool_generation.py index 00ff9d2f..ad8757ac 100644 --- a/agentstack/generation/tool_generation.py +++ b/agentstack/generation/tool_generation.py @@ -1,15 +1,12 @@ -import os, sys -from typing import Optional, Any, List -import importlib.resources -from pathlib import Path -import json +import os import sys -from typing import Optional, List, Dict, Union +from typing import Optional, List +from pathlib import Path +from typing import Union from . import get_agent_names -from .gen_utils import insert_code_after_tag, string_in_file, _framework_filename +from .gen_utils import insert_code_after_tag, _framework_filename from ..utils import open_json_file, get_framework, term_color -import os import shutil import fileinput import astor @@ -18,8 +15,6 @@ from agentstack.utils import get_package_path from agentstack.generation.files import ConfigFile, EnvFile -from .gen_utils import insert_code_after_tag, string_in_file -from ..utils import open_json_file, get_framework, term_color TOOL_INIT_FILENAME = "src/tools/__init__.py" @@ -156,7 +151,7 @@ def remove_tool(tool_name: str, path: Optional[str] = None): framework = get_framework() agentstack_config = ConfigFile(path) - if not tool_name in agentstack_config.tools: + if tool_name not in agentstack_config.tools: print(term_color(f"Tool {tool_name} is not installed", "red")) sys.exit(1) diff --git a/agentstack/main.py b/agentstack/main.py index 5bf0dc2f..fa8d49b0 100644 --- a/agentstack/main.py +++ b/agentstack/main.py @@ -42,7 +42,7 @@ def main(): init_parser.add_argument("--template", "-t", help="Agent template to use") # 'run' command - run_parser = subparsers.add_parser("run", aliases=["r"], help="Run your agent") + _ = subparsers.add_parser("run", aliases=["r"], help="Run your agent") # 'generate' command generate_parser = subparsers.add_parser( @@ -84,9 +84,7 @@ def main(): ) # 'list' command under 'tools' - tools_list_parser = tools_subparsers.add_parser( - "list", aliases=["l"], help="List tools" - ) + _ = tools_subparsers.add_parser("list", aliases=["l"], help="List tools") # 'add' command under 'tools' tools_add_parser = tools_subparsers.add_parser( diff --git a/agentstack/telemetry.py b/agentstack/telemetry.py index 5ccdd859..be0e4879 100644 --- a/agentstack/telemetry.py +++ b/agentstack/telemetry.py @@ -75,5 +75,5 @@ def track_cli_command(command: str): try: data = collect_machine_telemetry(command) requests.post(TELEMETRY_URL, json={"command": command, **data}) - except: + except Exception: pass diff --git a/agentstack/utils.py b/agentstack/utils.py index 6de98282..4b469115 100644 --- a/agentstack/utils.py +++ b/agentstack/utils.py @@ -1,6 +1,5 @@ from typing import Optional -import os import sys import json import re @@ -21,7 +20,7 @@ def verify_agentstack_project(path: Optional[str] = None): from agentstack.generation import ConfigFile try: - agentstack_config = ConfigFile(path) + _ = ConfigFile(path) except FileNotFoundError: print( "\033[31mAgentStack Error: This does not appear to be an AgentStack project." diff --git a/examples/trip_planner/src/tools/__init__.py b/examples/trip_planner/src/tools/__init__.py index 5cfb9bb7..856921c1 100644 --- a/examples/trip_planner/src/tools/__init__.py +++ b/examples/trip_planner/src/tools/__init__.py @@ -4,12 +4,9 @@ from browserbase import Browserbase -from browserbase import Browserbase -from browserbase import Browserbase -from browserbase import Browserbase from .browserbase import Browserbase diff --git a/examples/web_researcher/src/tools/neon_tool.py b/examples/web_researcher/src/tools/neon_tool.py index a00e907d..d81cfb59 100644 --- a/examples/web_researcher/src/tools/neon_tool.py +++ b/examples/web_researcher/src/tools/neon_tool.py @@ -50,7 +50,7 @@ def execute_sql_ddl(connection_uri: str, command: str) -> str: return f"Failed to execute DDL command: {str(e)}" cur.close() conn.close() - return f"Command succeeded" + return "Command succeeded" @tool("Execute SQL DML") @@ -75,7 +75,7 @@ def run_sql_query(connection_uri: str, query: str) -> str: return f"Query result: {records}" except psycopg2.ProgrammingError: # For INSERT/UPDATE/DELETE operations - return f"Query executed successfully" + return "Query executed successfully" except Exception as e: conn.rollback() return f"Failed to execute SQL query: {str(e)}" diff --git a/pyproject.toml b/pyproject.toml index e0041815..d14bff52 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,4 +50,6 @@ exclude = [ "dist", "*.egg-info", "agentstack/templates/", + "examples", + "__init__.py" ] \ No newline at end of file diff --git a/tests/test_generation_files.py b/tests/test_generation_files.py index 3745d04c..435bc745 100644 --- a/tests/test_generation_files.py +++ b/tests/test_generation_files.py @@ -1,6 +1,5 @@ -import os, sys +import os import unittest -import importlib.resources from pathlib import Path import shutil from agentstack.generation.files import ConfigFile, EnvFile @@ -55,24 +54,24 @@ def test_write_config(self): # os.rmdir(BASE_PATH / "tmp") def test_read_missing_config(self): - with self.assertRaises(FileNotFoundError) as context: - config = ConfigFile(BASE_PATH / "missing") + with self.assertRaises(FileNotFoundError) as _: + _ = ConfigFile(BASE_PATH / "missing") def test_verify_agentstack_project_valid(self): verify_agentstack_project(BASE_PATH / "fixtures") def test_verify_agentstack_project_invalid(self): - with self.assertRaises(SystemExit) as context: + with self.assertRaises(SystemExit) as _: verify_agentstack_project(BASE_PATH / "missing") def test_get_framework(self): assert get_framework(BASE_PATH / "fixtures") == "crewai" - with self.assertRaises(SystemExit) as context: + with self.assertRaises(SystemExit) as _: get_framework(BASE_PATH / "missing") def test_get_telemetry_opt_out(self): assert get_telemetry_opt_out(BASE_PATH / "fixtures") is False - with self.assertRaises(SystemExit) as context: + with self.assertRaises(SystemExit) as _: get_telemetry_opt_out(BASE_PATH / "missing") def test_read_env(self): @@ -80,7 +79,7 @@ def test_read_env(self): assert env.variables == {"ENV_VAR1": "value1", "ENV_VAR2": "value2"} assert env["ENV_VAR1"] == "value1" assert env["ENV_VAR2"] == "value2" - with self.assertRaises(KeyError) as context: + with self.assertRaises(KeyError) as _: env["ENV_VAR3"] def test_write_env(self): diff --git a/tests/test_tool_config.py b/tests/test_tool_config.py index fb4c98c5..20f820d6 100644 --- a/tests/test_tool_config.py +++ b/tests/test_tool_config.py @@ -1,7 +1,5 @@ import json -import os, sys import unittest -import importlib.resources from pathlib import Path from agentstack.generation.tool_generation import ( get_all_tool_paths, @@ -49,7 +47,7 @@ def test_all_json_configs_from_tool_path(self): for path in get_all_tool_paths(): try: config = ToolConfig.from_json(path) - except json.decoder.JSONDecodeError as e: + except json.decoder.JSONDecodeError: raise Exception( f"Failed to decode tool json at {path}. Does your tool config fit the required formatting? https://github.com/AgentOps-AI/AgentStack/blob/main/agentstack/tools/~README.md" ) From ab0839b53f769cedb6b693233f6492ec6b0c9fab Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Wed, 4 Dec 2024 23:04:25 -0800 Subject: [PATCH 4/9] ignore quotes --- pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d14bff52..6d7ef9fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,4 +52,7 @@ exclude = [ "agentstack/templates/", "examples", "__init__.py" -] \ No newline at end of file +] + +[tool.ruff.format] +quote-style = "preserve" \ No newline at end of file From 21d51b996bba1263f7c6568129444bfefd0b1ab3 Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Wed, 4 Dec 2024 23:08:55 -0800 Subject: [PATCH 5/9] Revert "add ruff and ruff format project" This reverts commit cd1257e9 --- .github/workflows/python-testing.yml | 25 +--- agentstack/cli/agentstack_data.py | 81 +++++------ agentstack/cli/cli.py | 50 ++----- agentstack/generation/__init__.py | 2 +- agentstack/generation/agent_generation.py | 66 +++++---- agentstack/generation/files.py | 70 ++++------ agentstack/generation/gen_utils.py | 79 +++++------ agentstack/generation/task_generation.py | 58 ++++---- agentstack/logger.py | 4 +- agentstack/main.py | 1 - agentstack/telemetry.py | 39 +++--- agentstack/utils.py | 64 ++++----- examples/howards_agent/src/crew.py | 67 +++++---- examples/howards_agent/src/main.py | 22 +-- examples/howards_agent/src/tools/__init__.py | 1 + .../howards_agent/src/tools/composio_tool.py | 1 + examples/howards_agent/src/tools/mem0_tool.py | 6 +- examples/job_posting/src/crew.py | 53 ++++--- examples/job_posting/src/main.py | 24 ++-- examples/job_posting/src/tools/__init__.py | 20 +++ .../job_posting/src/tools/firecrawl_tool.py | 12 +- examples/stock_analysis/src/crew.py | 18 ++- examples/stock_analysis/src/main.py | 26 ++-- examples/stock_analysis/src/tools/__init__.py | 1 + .../src/tools/perplexity_tool.py | 19 ++- examples/trip_planner/src/crew.py | 132 ++++++++---------- examples/trip_planner/src/main.py | 29 ++-- examples/trip_planner/src/tools/__init__.py | 1 + .../trip_planner/src/tools/browserbase.py | 2 +- examples/web_researcher/src/crew.py | 48 +++---- examples/web_researcher/src/main.py | 25 ++-- examples/web_researcher/src/tools/__init__.py | 1 + .../src/tools/firecrawl_tool.py | 12 +- tests/test_cli_loads.py | 10 +- tests/test_generation_files.py | 66 ++++----- tests/test_tool_config.py | 7 +- tests/test_utils.py | 8 +- 37 files changed, 519 insertions(+), 631 deletions(-) diff --git a/.github/workflows/python-testing.yml b/.github/workflows/python-testing.yml index 940bb50b..ab40e60a 100644 --- a/.github/workflows/python-testing.yml +++ b/.github/workflows/python-testing.yml @@ -36,27 +36,4 @@ jobs: - name: Install tox run: pip install tox - name: Run tests with tox - run: tox - - ruff: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: Install Ruff - run: | - python -m pip install --upgrade pip - pip install ruff - - - name: Run Ruff Format Check - run: | - # Check if any files would be reformatted - ruff format --check . - - # Run Ruff linting checks - ruff check . \ No newline at end of file + run: tox \ No newline at end of file diff --git a/agentstack/cli/agentstack_data.py b/agentstack/cli/agentstack_data.py index a071e2fe..e426037e 100644 --- a/agentstack/cli/agentstack_data.py +++ b/agentstack/cli/agentstack_data.py @@ -6,22 +6,19 @@ class ProjectMetadata: - def __init__( - self, - project_name: str = None, - project_slug: str = None, - description: str = "", - author_name: str = "", - version: str = "", - license: str = "", - year: int = datetime.now().year, - template: str = "none", - template_version: str = "0", - ): + def __init__(self, + project_name: str = None, + project_slug: str = None, + description: str = "", + author_name: str = "", + version: str = "", + license: str = "", + year: int = datetime.now().year, + template: str = "none", + template_version: str = "0", + ): self.project_name = clean_input(project_name) if project_name else "myagent" - self.project_slug = ( - clean_input(project_slug) if project_slug else self.project_name - ) + self.project_slug = clean_input(project_slug) if project_slug else self.project_name self.description = description self.author_name = author_name self.version = version @@ -35,16 +32,16 @@ def __init__( def to_dict(self): return { - "project_name": self.project_name, - "project_slug": self.project_slug, - "description": self.description, - "author_name": self.author_name, - "version": self.version, - "license": self.license, - "year": self.year, - "agentstack_version": self.agentstack_version, - "template": self.template, - "template_version": self.template_version, + 'project_name': self.project_name, + 'project_slug': self.project_slug, + 'description': self.description, + 'author_name': self.author_name, + 'version': self.version, + 'license': self.license, + 'year': self.year, + 'agentstack_version': self.agentstack_version, + 'template': self.template, + 'template_version': self.template_version, } def to_json(self): @@ -64,8 +61,8 @@ def add_task(self, task): def to_dict(self): return { - "agents": self.agents, - "tasks": self.tasks, + 'agents': self.agents, + 'tasks': self.tasks, } def to_json(self): @@ -73,16 +70,15 @@ def to_json(self): class FrameworkData: - def __init__( - self, - # name: Optional[Literal["crewai"]] = None - name: str = None, # TODO: better framework handling, Literal or Enum - ): + def __init__(self, + # name: Optional[Literal["crewai"]] = None + name: str = None # TODO: better framework handling, Literal or Enum + ): self.name = name def to_dict(self): return { - "name": self.name, + 'name': self.name, } def to_json(self): @@ -90,22 +86,21 @@ def to_json(self): class CookiecutterData: - def __init__( - self, - project_metadata: ProjectMetadata, - structure: ProjectStructure, - # framework: Literal["crewai"], - framework: str, - ): + def __init__(self, + project_metadata: ProjectMetadata, + structure: ProjectStructure, + # framework: Literal["crewai"], + framework: str, + ): self.project_metadata = project_metadata self.framework = framework self.structure = structure def to_dict(self): return { - "project_metadata": self.project_metadata.to_dict(), - "framework": self.framework, - "structure": self.structure.to_dict(), + 'project_metadata': self.project_metadata.to_dict(), + 'framework': self.framework, + 'structure': self.structure.to_dict(), } def to_json(self): diff --git a/agentstack/cli/cli.py b/agentstack/cli/cli.py index abbbcd1c..1cfb7e9d 100644 --- a/agentstack/cli/cli.py +++ b/agentstack/cli/cli.py @@ -13,12 +13,7 @@ import importlib.resources from cookiecutter.main import cookiecutter -from .agentstack_data import ( - FrameworkData, - ProjectMetadata, - ProjectStructure, - CookiecutterData, -) +from .agentstack_data import FrameworkData, ProjectMetadata, ProjectStructure, CookiecutterData from agentstack.logger import log from agentstack.utils import get_package_path from agentstack.generation.files import ConfigFile @@ -34,12 +29,7 @@ "anthropic/claude-3-opus", ] - -def init_project_builder( - slug_name: Optional[str] = None, - template: Optional[str] = None, - use_wizard: bool = False, -): +def init_project_builder(slug_name: Optional[str] = None, template: Optional[str] = None, use_wizard: bool = False): if slug_name and not is_snake_case(slug_name): print(term_color("Project name must be snake case", "red")) return @@ -51,7 +41,7 @@ def init_project_builder( template_data = None if template is not None: url_start = "https://" - if template[: len(url_start)] == url_start: + if template[:len(url_start)] == url_start: # template is a url response = requests.get(template) if response.status_code == 200: @@ -101,7 +91,7 @@ def init_project_builder( "version": "0.0.1", "description": "New agentstack project", "author": "Name ", - "license": "MIT", + "license": "MIT" } framework = "CrewAI" # TODO: if --no-wizard, require a framework flag @@ -139,7 +129,7 @@ def configure_default_model(path: Optional[str] = None): """Set the default model""" agentstack_config = ConfigFile(path) if agentstack_config.default_model: - return # Default model already set + return # Default model already set print("Project does not have a default model configured.") other_msg = "Other (enter a model name)" @@ -148,10 +138,8 @@ def configure_default_model(path: Optional[str] = None): choices=PREFERRED_MODELS + [other_msg], ) - if model == other_msg: # If the user selects "Other", prompt for a model name - print( - 'A list of available models is available at: "https://docs.litellm.ai/docs/providers"' - ) + if model == other_msg: # If the user selects "Other", prompt for a model name + print(f'A list of available models is available at: "https://docs.litellm.ai/docs/providers"') model = inquirer.text(message="Enter the model name") with ConfigFile(path) as agentstack_config: @@ -200,13 +188,11 @@ def ask_design() -> dict: print(title) - print( - """ + print(""" 🪄 welcome to the agent builder wizard!! 🪄 First we need to create the agents that will work together to accomplish tasks: - """ - ) + """) make_agent = True agents = [] while make_agent: @@ -330,6 +316,7 @@ def ask_tools() -> list: tools_data = open_json_file(tools_json_path) while adding_tools: + tool_type = inquirer.list_input( message="What category tool do you want to add?", choices=list(tools_data.keys()) + ["~~ Stop adding tools ~~"], @@ -381,12 +368,7 @@ def ask_project_details(slug_name: Optional[str] = None) -> dict: return questions -def insert_template( - project_details: dict, - framework_name: str, - design: dict, - template_data: Optional[dict] = None, -): +def insert_template(project_details: dict, framework_name: str, design: dict, template_data: Optional[dict] = None): framework = FrameworkData(framework_name.lower()) project_metadata = ProjectMetadata( project_name=project_details["name"], @@ -403,11 +385,9 @@ def insert_template( project_structure.agents = design["agents"] project_structure.tasks = design["tasks"] - cookiecutter_data = CookiecutterData( - project_metadata=project_metadata, - structure=project_structure, - framework=framework_name.lower(), - ) + cookiecutter_data = CookiecutterData(project_metadata=project_metadata, + structure=project_structure, + framework=framework_name.lower()) template_path = get_package_path() / f"templates/{framework.name}" with open(f"{template_path}/cookiecutter.json", "w") as json_file: @@ -475,4 +455,4 @@ def list_tools(): print(f": {tool.url if tool.url else 'AgentStack default tool'}") print("\n\n✨ Add a tool with: agentstack tools add ") - print(" https://docs.agentstack.sh/tools/core") + print(" https://docs.agentstack.sh/tools/core") \ No newline at end of file diff --git a/agentstack/generation/__init__.py b/agentstack/generation/__init__.py index 1ce49b1a..49d62c82 100644 --- a/agentstack/generation/__init__.py +++ b/agentstack/generation/__init__.py @@ -1,4 +1,4 @@ from .agent_generation import generate_agent, get_agent_names from .task_generation import generate_task, get_task_names from .tool_generation import add_tool, remove_tool -from .files import ConfigFile, EnvFile, CONFIG_FILENAME +from .files import ConfigFile, EnvFile, CONFIG_FILENAME \ No newline at end of file diff --git a/agentstack/generation/agent_generation.py b/agentstack/generation/agent_generation.py index fa6a4b22..bf64dd2e 100644 --- a/agentstack/generation/agent_generation.py +++ b/agentstack/generation/agent_generation.py @@ -9,19 +9,19 @@ def generate_agent( - name, - role: Optional[str], - goal: Optional[str], - backstory: Optional[str], - llm: Optional[str], + name, + role: Optional[str], + goal: Optional[str], + backstory: Optional[str], + llm: Optional[str] ): - agentstack_config = ConfigFile() # TODO path + agentstack_config = ConfigFile() # TODO path if not role: - role = "Add your role here" + role = 'Add your role here' if not goal: - goal = "Add your goal here" + goal = 'Add your goal here' if not backstory: - backstory = "Add your backstory here" + backstory = 'Add your backstory here' if not llm: llm = agentstack_config.default_model @@ -29,24 +29,24 @@ def generate_agent( framework = get_framework() - if framework == "crewai": + if framework == 'crewai': generate_crew_agent(name, role, goal, backstory, llm) print(" > Added to src/config/agents.yaml") else: print(f"This function is not yet implemented for {framework}") return - print(f'Added agent "{name}" to your AgentStack project successfully!') + print(f"Added agent \"{name}\" to your AgentStack project successfully!") def generate_crew_agent( - name, - role: Optional[str] = "Add your role here", - goal: Optional[str] = "Add your goal here", - backstory: Optional[str] = "Add your backstory here", - llm: Optional[str] = "openai/gpt-4o", + name, + role: Optional[str] = 'Add your role here', + goal: Optional[str] = 'Add your goal here', + backstory: Optional[str] = 'Add your backstory here', + llm: Optional[str] = 'openai/gpt-4o' ): - config_path = os.path.join("src", "config", "agents.yaml") + config_path = os.path.join('src', 'config', 'agents.yaml') # Ensure the directory exists os.makedirs(os.path.dirname(config_path), exist_ok=True) @@ -56,7 +56,7 @@ def generate_crew_agent( # Read existing data if os.path.exists(config_path): - with open(config_path, "r") as file: + with open(config_path, 'r') as file: try: data = yaml.load(file) or {} except Exception as exc: @@ -66,28 +66,26 @@ def generate_crew_agent( data = {} # Handle None values - role_str = FoldedScalarString(role) if role else FoldedScalarString("") - goals_str = FoldedScalarString(goal) if goal else FoldedScalarString("") - backstory_str = ( - FoldedScalarString(backstory) if backstory else FoldedScalarString("") - ) - model_str = llm if llm else "" + role_str = FoldedScalarString(role) if role else FoldedScalarString('') + goals_str = FoldedScalarString(goal) if goal else FoldedScalarString('') + backstory_str = FoldedScalarString(backstory) if backstory else FoldedScalarString('') + model_str = llm if llm else '' # Add new agent details data[name] = { - "role": role_str, - "goal": goals_str, - "backstory": backstory_str, - "llm": model_str, + 'role': role_str, + 'goal': goals_str, + 'backstory': backstory_str, + 'llm': model_str } # Write back to the file without altering existing content - with open(config_path, "w") as file: + with open(config_path, 'w') as file: yaml.dump(data, file) # Now lets add the agent to crew.py - file_path = "src/crew.py" - tag = "# Agent definitions" + file_path = 'src/crew.py' + tag = '# Agent definitions' code_to_insert = [ "@agent", f"def {name}(self) -> Agent:", @@ -96,12 +94,12 @@ def generate_crew_agent( " tools=[], # add tools here or use `agentstack tools add ", # TODO: Add any tools in agentstack.json " verbose=True", " )", - "", + "" ] insert_code_after_tag(file_path, tag, code_to_insert) -def get_agent_names(framework: str = "crewai", path: str = "") -> List[str]: +def get_agent_names(framework: str = 'crewai', path: str = '') -> List[str]: """Get only agent names from the crew file""" - return get_crew_components(framework, CrewComponent.AGENT, path)["agents"] + return get_crew_components(framework, CrewComponent.AGENT, path)['agents'] \ No newline at end of file diff --git a/agentstack/generation/files.py b/agentstack/generation/files.py index 4aec6fea..9f3156ff 100644 --- a/agentstack/generation/files.py +++ b/agentstack/generation/files.py @@ -9,21 +9,20 @@ CONFIG_FILENAME = "agentstack.json" ENV_FILEMANE = ".env" - class ConfigFile(BaseModel): """ Interface for interacting with the agentstack.json file inside a project directory. Handles both data validation and file I/O. - + `path` is the directory where the agentstack.json file is located. Defaults to the current working directory. - + Use it as a context manager to make and save edits: ```python with ConfigFile() as config: config.tools.append('tool_name') ``` - + Config Schema ------------- framework: str @@ -31,69 +30,62 @@ class ConfigFile(BaseModel): tools: list[str] A list of tools that are currently installed in the project. telemetry_opt_out: Optional[bool] - Whether the user has opted out of telemetry. + Whether the user has opted out of telemetry. default_model: Optional[str] The default model to use when generating agent configurations. """ - framework: Optional[str] = DEFAULT_FRAMEWORK tools: list[str] = [] telemetry_opt_out: Optional[bool] = None default_model: Optional[str] = None - + def __init__(self, path: Union[str, Path, None] = None): path = Path(path) if path else Path.cwd() if os.path.exists(path / CONFIG_FILENAME): - with open(path / CONFIG_FILENAME, "r") as f: + with open(path / CONFIG_FILENAME, 'r') as f: super().__init__(**json.loads(f.read())) else: raise FileNotFoundError(f"File {path / CONFIG_FILENAME} does not exist.") - self._path = path # attribute needs to be set after init + self._path = path # attribute needs to be set after init def model_dump(self, *args, **kwargs) -> dict: # Ignore None values dump = super().model_dump(*args, **kwargs) return {key: value for key, value in dump.items() if value is not None} - + def write(self): - with open(self._path / CONFIG_FILENAME, "w") as f: + with open(self._path / CONFIG_FILENAME, 'w') as f: f.write(json.dumps(self.model_dump(), indent=4)) - - def __enter__(self) -> "ConfigFile": - return self - - def __exit__(self, *args): - self.write() + + def __enter__(self) -> 'ConfigFile': return self + def __exit__(self, *args): self.write() class EnvFile: """ Interface for interacting with the .env file inside a project directory. - Unlike the ConfigFile, we do not re-write the entire file on every change, + Unlike the ConfigFile, we do not re-write the entire file on every change, and instead just append new lines to the end of the file. This preseres comments and other formatting that the user may have added and prevents opportunities for data loss. - + `path` is the directory where the .env file is located. Defaults to the current working directory. `filename` is the name of the .env file, defaults to '.env'. - + Use it as a context manager to make and save edits: ```python with EnvFile() as env: env.append_if_new('ENV_VAR', 'value') ``` """ - variables: dict[str, str] - - def __init__( - self, path: Union[str, Path, None] = None, filename: str = ENV_FILEMANE - ): + + def __init__(self, path: Union[str, Path, None] = None, filename: str = ENV_FILEMANE): self._path = Path(path) if path else Path.cwd() self._filename = filename self.read() - + def __getitem__(self, key): return self.variables[key] @@ -101,36 +93,32 @@ def __setitem__(self, key, value): if key in self.variables: raise ValueError("EnvFile does not allow overwriting values.") self.append_if_new(key, value) - + def __contains__(self, key) -> bool: return key in self.variables - + def append_if_new(self, key, value): if key not in self.variables: self.variables[key] = value self._new_variables[key] = value - + def read(self): def parse_line(line): - key, value = line.split("=") + key, value = line.split('=') return key.strip(), value.strip() if os.path.exists(self._path / self._filename): - with open(self._path / self._filename, "r") as f: - self.variables = dict( - [parse_line(line) for line in f.readlines() if "=" in line] - ) + with open(self._path / self._filename, 'r') as f: + self.variables = dict([parse_line(line) for line in f.readlines() if '=' in line]) else: self.variables = {} self._new_variables = {} - + def write(self): - with open(self._path / self._filename, "a") as f: + with open(self._path / self._filename, 'a') as f: for key, value in self._new_variables.items(): f.write(f"\n{key}={value}") + + def __enter__(self) -> 'EnvFile': return self + def __exit__(self, *args): self.write() - def __enter__(self) -> "EnvFile": - return self - - def __exit__(self, *args): - self.write() diff --git a/agentstack/generation/gen_utils.py b/agentstack/generation/gen_utils.py index 960de8f1..f9ac0e5f 100644 --- a/agentstack/generation/gen_utils.py +++ b/agentstack/generation/gen_utils.py @@ -8,29 +8,26 @@ def insert_code_after_tag(file_path, tag, code_to_insert, next_line=False): if next_line: - code_to_insert = ["\n"] + code_to_insert + code_to_insert = ['\n'] + code_to_insert - with open(file_path, "r") as file: + with open(file_path, 'r') as file: lines = file.readlines() for index, line in enumerate(lines): if tag in line: # Insert the code block after the tag - indented_code = [ - (line[: len(line) - len(line.lstrip())] + code_line + "\n") - for code_line in code_to_insert - ] - lines[index + 1 : index + 1] = indented_code + indented_code = [(line[:len(line)-len(line.lstrip())] + code_line + '\n') for code_line in code_to_insert] + lines[index+1:index+1] = indented_code break else: raise ValueError(f"Tag '{tag}' not found in the file.") - with open(file_path, "w") as file: + with open(file_path, 'w') as file: file.writelines(lines) def insert_after_tasks(file_path, code_to_insert): - with open(file_path, "r") as file: + with open(file_path, 'r') as file: content = file.read() module = ast.parse(content) @@ -39,49 +36,47 @@ def insert_after_tasks(file_path, code_to_insert): last_task_end = None last_task_start = None for node in ast.walk(module): - if isinstance(node, ast.FunctionDef) and any( - isinstance(deco, ast.Name) and deco.id == "task" - for deco in node.decorator_list - ): + if isinstance(node, ast.FunctionDef) and \ + any(isinstance(deco, ast.Name) and deco.id == 'task' for deco in node.decorator_list): last_task_end = node.end_lineno last_task_start = node.lineno if last_task_end is not None: - lines = content.split("\n") + lines = content.split('\n') # Get the indentation of the task function task_line = lines[last_task_start - 1] # -1 for 0-based indexing - indentation = "" + indentation = '' for char in task_line: - if char in [" ", "\t"]: + if char in [' ', '\t']: indentation += char else: break # Add the same indentation to each line of the inserted code - indented_code = "\n" + "\n".join(indentation + line for line in code_to_insert) + indented_code = '\n' + '\n'.join(indentation + line for line in code_to_insert) lines.insert(last_task_end, indented_code) - content = "\n".join(lines) + content = '\n'.join(lines) - with open(file_path, "w") as file: + with open(file_path, 'w') as file: file.write(content) return True else: - insert_code_after_tag(file_path, "# Task definitions", code_to_insert) + insert_code_after_tag(file_path, '# Task definitions', code_to_insert) def string_in_file(file_path: str, str_to_match: str) -> bool: - with open(file_path, "r") as file: + with open(file_path, 'r') as file: file_content = file.read() return str_to_match in file_content -def _framework_filename(framework: str, path: str = ""): - if framework == "crewai": - return f"{path}src/crew.py" +def _framework_filename(framework: str, path: str = ''): + if framework == 'crewai': + return f'{path}src/crew.py' - print(term_color(f"Unknown framework: {framework}", "red")) + print(term_color(f'Unknown framework: {framework}', 'red')) sys.exit(1) @@ -91,9 +86,9 @@ class CrewComponent(str, Enum): def get_crew_components( - framework: str = "crewai", - component_type: Optional[Union[CrewComponent, List[CrewComponent]]] = None, - path: str = "", + framework: str = 'crewai', + component_type: Optional[Union[CrewComponent, List[CrewComponent]]] = None, + path: str = '' ) -> dict[str, List[str]]: """ Get names of components (agents and/or tasks) defined in a crew file. @@ -115,13 +110,16 @@ def get_crew_components( component_type = [component_type] # Read the source file - with open(filename, "r") as f: + with open(filename, 'r') as f: source = f.read() # Parse the source into an AST tree = ast.parse(source) - components = {"agents": [], "tasks": []} + components = { + 'agents': [], + 'tasks': [] + } # Find all function definitions with relevant decorators for node in ast.walk(tree): @@ -129,21 +127,16 @@ def get_crew_components( # Check decorators for decorator in node.decorator_list: if isinstance(decorator, ast.Name): - if ( - component_type is None or CrewComponent.AGENT in component_type - ) and decorator.id == "agent": - components["agents"].append(node.name) - elif ( - component_type is None or CrewComponent.TASK in component_type - ) and decorator.id == "task": - components["tasks"].append(node.name) + if (component_type is None or CrewComponent.AGENT in component_type) \ + and decorator.id == 'agent': + components['agents'].append(node.name) + elif (component_type is None or CrewComponent.TASK in component_type) \ + and decorator.id == 'task': + components['tasks'].append(node.name) # If specific types were requested, only return those if component_type: - return { - k: v - for k, v in components.items() - if CrewComponent(k[:-1]) in component_type - } + return {k: v for k, v in components.items() + if CrewComponent(k[:-1]) in component_type} return components diff --git a/agentstack/generation/task_generation.py b/agentstack/generation/task_generation.py index 930a32c3..d2fc6ebc 100644 --- a/agentstack/generation/task_generation.py +++ b/agentstack/generation/task_generation.py @@ -8,39 +8,39 @@ def generate_task( - name, - description: Optional[str], - expected_output: Optional[str], - agent: Optional[str], + name, + description: Optional[str], + expected_output: Optional[str], + agent: Optional[str] ): if not description: - description = "Add your description here" + description = 'Add your description here' if not expected_output: - expected_output = "Add your expected_output here" + expected_output = 'Add your expected_output here' if not agent: - agent = "default_agent" + agent = 'default_agent' verify_agentstack_project() framework = get_framework() - if framework == "crewai": + if framework == 'crewai': generate_crew_task(name, description, expected_output, agent) print(" > Added to src/config/tasks.yaml") else: print(f"This function is not yet implemented for {framework}") return - print(f'Added task "{name}" to your AgentStack project successfully!') + print(f"Added task \"{name}\" to your AgentStack project successfully!") def generate_crew_task( - name, - description: Optional[str], - expected_output: Optional[str], - agent: Optional[str], + name, + description: Optional[str], + expected_output: Optional[str], + agent: Optional[str] ): - config_path = os.path.join("src", "config", "tasks.yaml") + config_path = os.path.join('src', 'config', 'tasks.yaml') # Ensure the directory exists os.makedirs(os.path.dirname(config_path), exist_ok=True) @@ -50,7 +50,7 @@ def generate_crew_task( # Read existing data if os.path.exists(config_path): - with open(config_path, "r") as file: + with open(config_path, 'r') as file: try: data = yaml.load(file) or {} except Exception as exc: @@ -60,41 +60,35 @@ def generate_crew_task( data = {} # Handle None values - description_str = ( - FoldedScalarString(description) if description else FoldedScalarString("") - ) - expected_output_str = ( - FoldedScalarString(expected_output) - if expected_output - else FoldedScalarString("") - ) - agent_str = FoldedScalarString(agent) if agent else FoldedScalarString("") + description_str = FoldedScalarString(description) if description else FoldedScalarString('') + expected_output_str = FoldedScalarString(expected_output) if expected_output else FoldedScalarString('') + agent_str = FoldedScalarString(agent) if agent else FoldedScalarString('') # Add new agent details data[name] = { - "description": description_str, - "expected_output": expected_output_str, - "agent": agent_str, + 'description': description_str, + 'expected_output': expected_output_str, + 'agent': agent_str, } # Write back to the file without altering existing content - with open(config_path, "w") as file: + with open(config_path, 'w') as file: yaml.dump(data, file) # Add task to crew.py - file_path = "src/crew.py" + file_path = 'src/crew.py' code_to_insert = [ "@task", f"def {name}(self) -> Task:", " return Task(", f" config=self.tasks_config['{name}'],", " )", - "", + "" ] insert_after_tasks(file_path, code_to_insert) -def get_task_names(framework: str, path: str = "") -> List[str]: +def get_task_names(framework: str, path: str = '') -> List[str]: """Get only task names from the crew file""" - return get_crew_components(framework, CrewComponent.TASK, path)["tasks"] + return get_crew_components(framework, CrewComponent.TASK, path)['tasks'] \ No newline at end of file diff --git a/agentstack/logger.py b/agentstack/logger.py index e41c4887..680d3067 100644 --- a/agentstack/logger.py +++ b/agentstack/logger.py @@ -16,9 +16,7 @@ def get_logger(name, debug=False): handler = logging.StreamHandler(sys.stdout) handler.setLevel(log_level) - formatter = logging.Formatter( - "%(asctime)s - %(process)d - %(threadName)s - %(filename)s:%(lineno)d - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(asctime)s - %(process)d - %(threadName)s - %(filename)s:%(lineno)d - %(name)s - %(levelname)s - %(message)s") handler.setFormatter(formatter) if not logger.handlers: diff --git a/agentstack/main.py b/agentstack/main.py index fa8d49b0..f701dd70 100644 --- a/agentstack/main.py +++ b/agentstack/main.py @@ -9,7 +9,6 @@ import webbrowser - def main(): parser = argparse.ArgumentParser( description="AgentStack CLI - The easiest way to build an agent application" diff --git a/agentstack/telemetry.py b/agentstack/telemetry.py index be0e4879..ada4f55e 100644 --- a/agentstack/telemetry.py +++ b/agentstack/telemetry.py @@ -30,43 +30,40 @@ import requests from agentstack.utils import get_telemetry_opt_out, get_framework, get_version -TELEMETRY_URL = "https://api.agentstack.sh/telemetry" - +TELEMETRY_URL = 'https://api.agentstack.sh/telemetry' def collect_machine_telemetry(command: str): if command != "init" and get_telemetry_opt_out(): return telemetry_data = { - "os": platform.system(), - "hostname": socket.gethostname(), - "platform": platform.platform(), - "os_version": platform.version(), - "cpu_count": psutil.cpu_count(logical=True), - "memory": psutil.virtual_memory().total, - "agentstack_version": get_version(), + 'os': platform.system(), + 'hostname': socket.gethostname(), + 'platform': platform.platform(), + 'os_version': platform.version(), + 'cpu_count': psutil.cpu_count(logical=True), + 'memory': psutil.virtual_memory().total, + 'agentstack_version': get_version() } if command != "init": - telemetry_data["framework"] = get_framework() + telemetry_data['framework'] = get_framework() else: - telemetry_data["framework"] = "n/a" + telemetry_data['framework'] = "n/a" # Attempt to get general location based on public IP try: - response = requests.get("https://ipinfo.io/json") + response = requests.get('https://ipinfo.io/json') if response.status_code == 200: location_data = response.json() - telemetry_data.update( - { - "ip": location_data.get("ip"), - "city": location_data.get("city"), - "region": location_data.get("region"), - "country": location_data.get("country"), - } - ) + telemetry_data.update({ + 'ip': location_data.get('ip'), + 'city': location_data.get('city'), + 'region': location_data.get('region'), + 'country': location_data.get('country') + }) except requests.RequestException as e: - telemetry_data["location_error"] = str(e) + telemetry_data['location_error'] = str(e) return telemetry_data diff --git a/agentstack/utils.py b/agentstack/utils.py index 4b469115..5b88eb5c 100644 --- a/agentstack/utils.py +++ b/agentstack/utils.py @@ -7,10 +7,9 @@ from pathlib import Path import importlib.resources - def get_version(): try: - return version("agentstack") + return version('agentstack') except (KeyError, FileNotFoundError) as e: print(e) return "Unknown version" @@ -18,90 +17,75 @@ def get_version(): def verify_agentstack_project(path: Optional[str] = None): from agentstack.generation import ConfigFile - try: _ = ConfigFile(path) except FileNotFoundError: - print( - "\033[31mAgentStack Error: This does not appear to be an AgentStack project." - "\nPlease ensure you're at the root directory of your project and a file named agentstack.json exists. " - "If you're starting a new project, run `agentstack init`\033[0m" - ) + print("\033[31mAgentStack Error: This does not appear to be an AgentStack project." + "\nPlease ensure you're at the root directory of your project and a file named agentstack.json exists. " + "If you're starting a new project, run `agentstack init`\033[0m") sys.exit(1) def get_package_path() -> Path: """This is the Path where agentstack is installed.""" if sys.version_info <= (3, 9): - return Path(sys.modules["agentstack"].__path__[0]) - return importlib.resources.files("agentstack") + return Path(sys.modules['agentstack'].__path__[0]) + return importlib.resources.files('agentstack') def get_framework(path: Optional[str] = None) -> str: from agentstack.generation import ConfigFile - try: agentstack_config = ConfigFile(path) framework = agentstack_config.framework - if framework.lower() not in ["crewai", "autogen", "litellm"]: + if framework.lower() not in ['crewai', 'autogen', 'litellm']: print(term_color("agentstack.json contains an invalid framework", "red")) return framework except FileNotFoundError: - print( - "\033[31mFile agentstack.json does not exist. Are you in the right directory?\033[0m" - ) + print("\033[31mFile agentstack.json does not exist. Are you in the right directory?\033[0m") sys.exit(1) def get_telemetry_opt_out(path: Optional[str] = None) -> str: from agentstack.generation import ConfigFile - try: agentstack_config = ConfigFile(path) return bool(agentstack_config.telemetry_opt_out) except FileNotFoundError: - print( - "\033[31mFile agentstack.json does not exist. Are you in the right directory?\033[0m" - ) + print("\033[31mFile agentstack.json does not exist. Are you in the right directory?\033[0m") sys.exit(1) - def camel_to_snake(name): - s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name) - return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower() + s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) + return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() def snake_to_camel(s): - return "".join(word.title() for word in s.split("_")) + return ''.join(word.title() for word in s.split('_')) def open_json_file(path) -> dict: - with open(path, "r") as f: + with open(path, 'r') as f: data = json.load(f) return data def clean_input(input_string): - special_char_pattern = re.compile(r"[^a-zA-Z0-9\s_]") - return ( - re.sub(special_char_pattern, "", input_string) - .lower() - .replace(" ", "_") - .replace("-", "_") - ) + special_char_pattern = re.compile(r'[^a-zA-Z0-9\s_]') + return re.sub(special_char_pattern, '', input_string).lower().replace(' ', '_').replace('-', '_') def term_color(text: str, color: str) -> str: colors = { - "red": "91", - "green": "92", - "yellow": "93", - "blue": "94", - "purple": "95", - "cyan": "96", - "white": "97", + 'red': '91', + 'green': '92', + 'yellow': '93', + 'blue': '94', + 'purple': '95', + 'cyan': '96', + 'white': '97' } color_code = colors.get(color) if color_code: @@ -110,5 +94,7 @@ def term_color(text: str, color: str) -> str: return text + def is_snake_case(string: str): - return bool(re.match("^[a-z0-9_]+$", string)) + return bool(re.match('^[a-z0-9_]+$', string)) + diff --git a/examples/howards_agent/src/crew.py b/examples/howards_agent/src/crew.py index 47ea56de..ad09087f 100644 --- a/examples/howards_agent/src/crew.py +++ b/examples/howards_agent/src/crew.py @@ -2,42 +2,39 @@ from crewai.project import CrewBase, agent, crew, task import tools - @CrewBase -class HowardsagentCrew: - """howards_agent crew""" - - # Agent definitions - @agent - def agent1(self) -> Agent: - return Agent( - config=self.agents_config["agent1"], - tools=[ - *tools.composio_tools, - ], # Pass in what tools this agent should have - verbose=True, - ) +class HowardsagentCrew(): + """howards_agent crew""" - # Task definitions - @task - def new_task(self) -> Task: - return Task( - config=self.tasks_config["new_task"], - ) + # Agent definitions + @agent + def agent1(self) -> Agent: + return Agent( + config=self.agents_config['agent1'], + tools=[*tools.composio_tools,], # Pass in what tools this agent should have + verbose=True + ) - @task - def task1(self) -> Task: - return Task( - config=self.tasks_config["task1"], - ) + # Task definitions + @task + def new_task(self) -> Task: + return Task( + config=self.tasks_config['new_task'], + ) + + @task + def task1(self) -> Task: + return Task( + config=self.tasks_config['task1'], + ) - @crew - def crew(self) -> Crew: - """Creates the Test crew""" - return Crew( - agents=self.agents, # Automatically created by the @agent decorator - tasks=self.tasks, # Automatically created by the @task decorator - process=Process.sequential, - verbose=True, - # process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/ - ) + @crew + def crew(self) -> Crew: + """Creates the Test crew""" + return Crew( + agents=self.agents, # Automatically created by the @agent decorator + tasks=self.tasks, # Automatically created by the @task decorator + process=Process.sequential, + verbose=True, + # process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/ + ) diff --git a/examples/howards_agent/src/main.py b/examples/howards_agent/src/main.py index d68b4e4f..605268f4 100644 --- a/examples/howards_agent/src/main.py +++ b/examples/howards_agent/src/main.py @@ -3,7 +3,6 @@ from crew import HowardsagentCrew import agentops from dotenv import load_dotenv - load_dotenv() agentops.init() @@ -13,12 +12,13 @@ # Replace with inputs you want to test with, it will automatically # interpolate any tasks and agents information - def run(): """ Run the crew. """ - inputs = {"topic": "AI LLMs"} + inputs = { + 'topic': 'AI LLMs' + } HowardsagentCrew().crew().kickoff(inputs=inputs) @@ -26,11 +26,11 @@ def train(): """ Train the crew for a given number of iterations. """ - inputs = {"topic": "AI LLMs"} + inputs = { + "topic": "AI LLMs" + } try: - HowardsagentCrew().crew().train( - n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs - ) + HowardsagentCrew().crew().train(n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs) except Exception as e: raise Exception(f"An error occurred while training the crew: {e}") @@ -51,11 +51,11 @@ def test(): """ Test the crew execution and returns the results. """ - inputs = {"topic": "AI LLMs"} + inputs = { + "topic": "AI LLMs" + } try: - HowardsagentCrew().crew().test( - n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs - ) + HowardsagentCrew().crew().test(n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs) except Exception as e: raise Exception(f"An error occurred while replaying the crew: {e}") diff --git a/examples/howards_agent/src/tools/__init__.py b/examples/howards_agent/src/tools/__init__.py index 4330ab36..ed0105e1 100644 --- a/examples/howards_agent/src/tools/__init__.py +++ b/examples/howards_agent/src/tools/__init__.py @@ -1,3 +1,4 @@ + # tool import diff --git a/examples/howards_agent/src/tools/composio_tool.py b/examples/howards_agent/src/tools/composio_tool.py index 197c7e00..9ad350ec 100644 --- a/examples/howards_agent/src/tools/composio_tool.py +++ b/examples/howards_agent/src/tools/composio_tool.py @@ -4,3 +4,4 @@ # change App.CODEINTERPRETER to be the app you want to use # For more info on tool selection, see https://docs.agentstack.sh/tools/tool/composio + diff --git a/examples/howards_agent/src/tools/mem0_tool.py b/examples/howards_agent/src/tools/mem0_tool.py index 134e92e4..1798da5b 100644 --- a/examples/howards_agent/src/tools/mem0_tool.py +++ b/examples/howards_agent/src/tools/mem0_tool.py @@ -12,11 +12,11 @@ "provider": "neo4j", "config": { "url": os.getenv("NEO4J_URL"), - "username": os.getenv("NEO4J_USERNAME", "neo4j"), + "username": os.getenv("NEO4J_USERNAME", 'neo4j'), "password": os.getenv("NEO4J_PASSWORD"), - }, + } }, - "version": "v1.1", + "version": "v1.1" } memory = Memory.from_config(config) diff --git a/examples/job_posting/src/crew.py b/examples/job_posting/src/crew.py index 0e6385a5..2a341e2c 100644 --- a/examples/job_posting/src/crew.py +++ b/examples/job_posting/src/crew.py @@ -8,48 +8,45 @@ class JobpostingCrew: """job_posting crew""" @agent - def review_agent(self) -> Agent: - return Agent(config=self.agents_config["review_agent"], tools=[], verbose=True) + def review_agent(self) ->Agent: + return Agent(config=self.agents_config['review_agent'], tools=[], + verbose=True) @agent - def writer_agent(self) -> Agent: - return Agent(config=self.agents_config["writer_agent"], tools=[], verbose=True) + def writer_agent(self) ->Agent: + return Agent(config=self.agents_config['writer_agent'], tools=[], + verbose=True) @agent - def researcher_agent(self) -> Agent: - return Agent( - config=self.agents_config["research_agent"], - tools=[tools.web_scrape, tools.web_crawl, tools.retrieve_web_crawl], - verbose=True, - ) + def researcher_agent(self) ->Agent: + return Agent(config=self.agents_config['research_agent'], tools=[ + tools.web_scrape, tools.web_crawl, tools.retrieve_web_crawl], + verbose=True) @task - def research_company_culture_task(self) -> Task: - return Task(config=self.tasks_config["research_company_culture_task"]) + def research_company_culture_task(self) ->Task: + return Task(config=self.tasks_config['research_company_culture_task']) @task - def research_role_requirements_task(self) -> Task: - return Task(config=self.tasks_config["research_role_requirements_task"]) + def research_role_requirements_task(self) ->Task: + return Task(config=self.tasks_config['research_role_requirements_task'] + ) @task - def draft_job_posting_task(self) -> Task: - return Task(config=self.tasks_config["draft_job_posting_task"]) + def draft_job_posting_task(self) ->Task: + return Task(config=self.tasks_config['draft_job_posting_task']) @task - def review_and_edit_job_posting_task(self) -> Task: - return Task(config=self.tasks_config["review_and_edit_job_posting_task"]) + def review_and_edit_job_posting_task(self) ->Task: + return Task(config=self.tasks_config[ + 'review_and_edit_job_posting_task']) @task - def industry_analysis_task(self) -> Task: - return Task(config=self.tasks_config["industry_analysis_task"]) + def industry_analysis_task(self) ->Task: + return Task(config=self.tasks_config['industry_analysis_task']) @crew - def crew(self) -> Crew: + def crew(self) ->Crew: """Creates the Test crew""" - return Crew( - agents=self.agents, - tasks=self.tasks, - verbose=True, - process=Process.hierarchical, - manager_llm="openai/gpt-4o", - ) + return Crew(agents=self.agents, tasks=self.tasks, verbose=True, + process=Process.hierarchical, manager_llm='openai/gpt-4o') diff --git a/examples/job_posting/src/main.py b/examples/job_posting/src/main.py index 699626fd..8abd6f2d 100644 --- a/examples/job_posting/src/main.py +++ b/examples/job_posting/src/main.py @@ -3,18 +3,16 @@ from crew import JobpostingCrew import agentops from dotenv import load_dotenv - load_dotenv() agentops.init() inputs = { - "company_domain": "https://agen.cy", - "company_description": "From open source AI agent developer tools like AgentOps to Fortune 500 enterprises, we help clients create safe, reliable, and scalable AI agents.", - "hiring_needs": "Infrastructure engineer for deploying AI agents at scale", - "specific_benefits": "Daily lunch", -} - + 'company_domain': 'https://agen.cy', + 'company_description': "From open source AI agent developer tools like AgentOps to Fortune 500 enterprises, we help clients create safe, reliable, and scalable AI agents.", + 'hiring_needs': 'Infrastructure engineer for deploying AI agents at scale', + 'specific_benefits': 'Daily lunch', + } def run(): """ @@ -28,9 +26,7 @@ def train(): Train the crew for a given number of iterations. """ try: - JobpostingCrew().crew().train( - n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs - ) + JobpostingCrew().crew().train(n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs) except Exception as e: raise Exception(f"An error occurred while training the crew: {e}") @@ -52,13 +48,11 @@ def test(): Test the crew execution and returns the results. """ try: - JobpostingCrew().crew().test( - n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs - ) + JobpostingCrew().crew().test(n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs) except Exception as e: raise Exception(f"An error occurred while replaying the crew: {e}") -if __name__ == "__main__": - run() +if __name__ == '__main__': + run() \ No newline at end of file diff --git a/examples/job_posting/src/tools/__init__.py b/examples/job_posting/src/tools/__init__.py index b1387c10..f1654b8d 100644 --- a/examples/job_posting/src/tools/__init__.py +++ b/examples/job_posting/src/tools/__init__.py @@ -1,4 +1,24 @@ + # tool import from .firecrawl_tool import web_scrape, web_crawl, retrieve_web_crawl + + + + + + + + + + + + + + + + + + + diff --git a/examples/job_posting/src/tools/firecrawl_tool.py b/examples/job_posting/src/tools/firecrawl_tool.py index 4f1cf1e0..65c66c24 100644 --- a/examples/job_posting/src/tools/firecrawl_tool.py +++ b/examples/job_posting/src/tools/firecrawl_tool.py @@ -2,7 +2,7 @@ from firecrawl import FirecrawlApp import os -app = FirecrawlApp(api_key=os.getenv("FIRECRAWL_API_KEY")) +app = FirecrawlApp(api_key=os.getenv('FIRECRAWL_API_KEY')) @tool @@ -11,7 +11,7 @@ def web_scrape(url: str): Scrape a url and return markdown. Use this to read a singular page and web_crawl only if you need to read all other links as well. """ - scrape_result = app.scrape_url(url, params={"formats": ["markdown"]}) + scrape_result = app.scrape_url(url, params={'formats': ['markdown']}) return scrape_result @@ -29,8 +29,11 @@ def web_crawl(url: str): crawl_status = app.crawl_url( url, - params={"limit": 100, "scrapeOptions": {"formats": ["markdown"]}}, - poll_interval=30, + params={ + 'limit': 100, + 'scrapeOptions': {'formats': ['markdown']} + }, + poll_interval=30 ) return crawl_status @@ -44,3 +47,4 @@ def retrieve_web_crawl(crawl_id: str): will tell you if the crawl is finished. If it is not, wait some more time then try again. """ return app.check_crawl_status(crawl_id) + diff --git a/examples/stock_analysis/src/crew.py b/examples/stock_analysis/src/crew.py index 165eeee8..1bef7a62 100644 --- a/examples/stock_analysis/src/crew.py +++ b/examples/stock_analysis/src/crew.py @@ -4,39 +4,37 @@ @CrewBase -class StockanalysisCrew: +class StockanalysisCrew(): """stock_analysis crew""" # Agent definitions @agent def analyst(self) -> Agent: return Agent( - config=self.agents_config["analyst"], + config=self.agents_config['analyst'], tools=[], # add tools here or use `agentstack tools add - verbose=True, + verbose=True ) @agent def researcher(self) -> Agent: return Agent( - config=self.agents_config["researcher"], - tools=[ - tools.query_perplexity, - ], # add tools here or use `agentstack tools add - verbose=True, + config=self.agents_config['researcher'], + tools=[tools.query_perplexity, ], # add tools here or use `agentstack tools add + verbose=True ) # Task definitions @task def research_stock(self) -> Task: return Task( - config=self.tasks_config["research_stock"], + config=self.tasks_config['research_stock'], ) @task def buy_sell_decision(self) -> Task: return Task( - config=self.tasks_config["buy_sell_decision"], + config=self.tasks_config['buy_sell_decision'], ) @crew diff --git a/examples/stock_analysis/src/main.py b/examples/stock_analysis/src/main.py index c2523190..11ad98b9 100644 --- a/examples/stock_analysis/src/main.py +++ b/examples/stock_analysis/src/main.py @@ -3,7 +3,6 @@ from crew import StockanalysisCrew import agentops from dotenv import load_dotenv - load_dotenv() agentops.init() @@ -13,12 +12,13 @@ # Replace with inputs you want to test with, it will automatically # interpolate any tasks and agents information - def run(): """ Run the crew. """ - inputs = {"topic": "AI LLMs"} + inputs = { + 'topic': 'AI LLMs' + } StockanalysisCrew().crew().kickoff(inputs=inputs) @@ -26,11 +26,11 @@ def train(): """ Train the crew for a given number of iterations. """ - inputs = {"topic": "AI LLMs"} + inputs = { + "topic": "AI LLMs" + } try: - StockanalysisCrew().crew().train( - n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs - ) + StockanalysisCrew().crew().train(n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs) except Exception as e: raise Exception(f"An error occurred while training the crew: {e}") @@ -51,15 +51,15 @@ def test(): """ Test the crew execution and returns the results. """ - inputs = {"topic": "AI LLMs"} + inputs = { + "topic": "AI LLMs" + } try: - StockanalysisCrew().crew().test( - n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs - ) + StockanalysisCrew().crew().test(n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs) except Exception as e: raise Exception(f"An error occurred while replaying the crew: {e}") -if __name__ == "__main__": - run() +if __name__ == '__main__': + run() \ No newline at end of file diff --git a/examples/stock_analysis/src/tools/__init__.py b/examples/stock_analysis/src/tools/__init__.py index 4fdccde1..ca62890f 100644 --- a/examples/stock_analysis/src/tools/__init__.py +++ b/examples/stock_analysis/src/tools/__init__.py @@ -1,3 +1,4 @@ + # tool import from .perplexity_tool import query_perplexity diff --git a/examples/stock_analysis/src/tools/perplexity_tool.py b/examples/stock_analysis/src/tools/perplexity_tool.py index acdb7544..a5a361b5 100644 --- a/examples/stock_analysis/src/tools/perplexity_tool.py +++ b/examples/stock_analysis/src/tools/perplexity_tool.py @@ -4,13 +4,11 @@ from crewai_tools import tool from dotenv import load_dotenv - load_dotenv() url = "https://api.perplexity.ai/chat/completions" api_key = os.getenv("PERPLEXITY_API_KEY") - @tool def query_perplexity(query: str): """ @@ -20,8 +18,14 @@ def query_perplexity(query: str): payload = { "model": "llama-3.1-sonar-small-128k-online", "messages": [ - {"role": "system", "content": "Be precise and concise."}, - {"role": "user", "content": query}, + { + "role": "system", + "content": "Be precise and concise." + }, + { + "role": "user", + "content": query + } ], # "max_tokens": "Optional", "temperature": 0.2, @@ -34,9 +38,12 @@ def query_perplexity(query: str): "top_k": 0, "stream": False, "presence_penalty": 0, - "frequency_penalty": 1, + "frequency_penalty": 1 + } + headers = { + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json" } - headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} response = requests.request("POST", url, json=payload, headers=headers) if response.status_code == 200 and response.text: diff --git a/examples/trip_planner/src/crew.py b/examples/trip_planner/src/crew.py index 3558ac1c..9d479dd2 100644 --- a/examples/trip_planner/src/crew.py +++ b/examples/trip_planner/src/crew.py @@ -2,78 +2,62 @@ from crewai.project import CrewBase, agent, crew, task import tools - @CrewBase -class TripplannerCrew: - """trip_planner crew""" - - # Agent definitions - @agent - def city_selection_expert(self) -> Agent: - return Agent( - config=self.agents_config["city_selection_expert"], - tools=[ - tools.Browserbase, - tools.Browserbase, - tools.Browserbase, - tools.Browserbase, - tools.Browserbase, - ], # Pass in what tools this agent should have - verbose=True, - ) - - @agent - def local_expert(self) -> Agent: - return Agent( - config=self.agents_config["local_expert"], - tools=[ - tools.Browserbase, - tools.Browserbase, - tools.Browserbase, - tools.Browserbase, - tools.Browserbase, - ], # Pass in what tools this agent should have - verbose=True, - ) - - @agent - def travel_concierge(self) -> Agent: - return Agent( - config=self.agents_config["travel_concierge"], - tools=[ - tools.Browserbase, - tools.Browserbase, - tools.Browserbase, - tools.Browserbase, - tools.Browserbase, - ], # Pass in what tools this agent should have - verbose=True, - ) - - # Task definitions - @task - def identify_task(self) -> Task: - return Task( - config=self.tasks_config["identify_task"], - ) - - @task - def gather_task(self) -> Task: - return Task( - config=self.tasks_config["gather_task"], - ) - - @task - def plan_task(self) -> Task: - return Task(config=self.tasks_config["plan_task"], output_file="itinerary.md") - - @crew - def crew(self) -> Crew: - """Creates the Test crew""" - return Crew( - agents=self.agents, # Automatically created by the @agent decorator - tasks=self.tasks, # Automatically created by the @task decorator - process=Process.sequential, - verbose=True, - # process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/ - ) +class TripplannerCrew(): + """trip_planner crew""" + + # Agent definitions + @agent + def city_selection_expert(self) -> Agent: + return Agent( + config=self.agents_config['city_selection_expert'], + tools=[tools.Browserbase, tools.Browserbase, tools.Browserbase, tools.Browserbase, tools.Browserbase, ], # Pass in what tools this agent should have + verbose=True + ) + + @agent + def local_expert(self) -> Agent: + return Agent( + config=self.agents_config['local_expert'], + tools=[tools.Browserbase, tools.Browserbase, tools.Browserbase, tools.Browserbase, tools.Browserbase, ], # Pass in what tools this agent should have + verbose=True + ) + + @agent + def travel_concierge(self) -> Agent: + return Agent( + config=self.agents_config['travel_concierge'], + tools=[tools.Browserbase, tools.Browserbase, tools.Browserbase, tools.Browserbase, tools.Browserbase, ], # Pass in what tools this agent should have + verbose=True + ) + + # Task definitions + @task + def identify_task(self) -> Task: + return Task( + config=self.tasks_config['identify_task'], + ) + + @task + def gather_task(self) -> Task: + return Task( + config=self.tasks_config['gather_task'], + ) + + @task + def plan_task(self) -> Task: + return Task( + config=self.tasks_config['plan_task'], + output_file="itinerary.md" + ) + + @crew + def crew(self) -> Crew: + """Creates the Test crew""" + return Crew( + agents=self.agents, # Automatically created by the @agent decorator + tasks=self.tasks, # Automatically created by the @task decorator + process=Process.sequential, + verbose=True, + # process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/ + ) \ No newline at end of file diff --git a/examples/trip_planner/src/main.py b/examples/trip_planner/src/main.py index ac49413c..aca11667 100644 --- a/examples/trip_planner/src/main.py +++ b/examples/trip_planner/src/main.py @@ -10,16 +10,15 @@ # Replace with inputs you want to test with, it will automatically # interpolate any tasks and agents information - def run(): """ Run the crew. """ inputs = { - "origin": "San Francisco", - "cities": "austin,berlin,tokyo", - "interests": "techno,history,culture,art,music", - "range": "one week between next may to july", + 'origin': 'San Francisco', + 'cities': 'austin,berlin,tokyo', + 'interests': 'techno,history,culture,art,music', + 'range': 'one week between next may to july', } TripplannerCrew().crew().kickoff(inputs=inputs) @@ -28,11 +27,11 @@ def train(): """ Train the crew for a given number of iterations. """ - inputs = {"topic": "AI LLMs"} + inputs = { + "topic": "AI LLMs" + } try: - TripplannerCrew().crew().train( - n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs - ) + TripplannerCrew().crew().train(n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs) except Exception as e: raise Exception(f"An error occurred while training the crew: {e}") @@ -53,15 +52,15 @@ def test(): """ Test the crew execution and returns the results. """ - inputs = {"topic": "AI LLMs"} + inputs = { + "topic": "AI LLMs" + } try: - TripplannerCrew().crew().test( - n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs - ) + TripplannerCrew().crew().test(n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs) except Exception as e: raise Exception(f"An error occurred while replaying the crew: {e}") -if __name__ == "__main__": - run() +if __name__ == '__main__': + run() \ No newline at end of file diff --git a/examples/trip_planner/src/tools/__init__.py b/examples/trip_planner/src/tools/__init__.py index 856921c1..ce1fe5e4 100644 --- a/examples/trip_planner/src/tools/__init__.py +++ b/examples/trip_planner/src/tools/__init__.py @@ -1,3 +1,4 @@ + # tool import diff --git a/examples/trip_planner/src/tools/browserbase.py b/examples/trip_planner/src/tools/browserbase.py index 00eaa919..08057260 100644 --- a/examples/trip_planner/src/tools/browserbase.py +++ b/examples/trip_planner/src/tools/browserbase.py @@ -1,3 +1,3 @@ from crewai_tools import BrowserbaseLoadTool -Browserbase = BrowserbaseLoadTool(text_content=True) +Browserbase = BrowserbaseLoadTool(text_content=True) \ No newline at end of file diff --git a/examples/web_researcher/src/crew.py b/examples/web_researcher/src/crew.py index f7b0f33b..01cfac22 100644 --- a/examples/web_researcher/src/crew.py +++ b/examples/web_researcher/src/crew.py @@ -8,45 +8,35 @@ class WebresearcherCrew: """web_researcher crew""" @agent - def content_summarizer(self) -> Agent: - return Agent( - config=self.agents_config["content_summarizer"], tools=[], verbose=True - ) + def content_summarizer(self) ->Agent: + return Agent(config=self.agents_config['content_summarizer'], tools + =[], verbose=True) @agent - def web_scraper(self) -> Agent: - return Agent( - config=self.agents_config["web_scraper"], - tools=[tools.web_scrape], - verbose=True, - ) + def web_scraper(self) ->Agent: + return Agent(config=self.agents_config['web_scraper'], tools=[tools + .web_scrape], verbose=True) @agent - def content_storer(self) -> Agent: - return Agent( - config=self.agents_config["content_storer"], - tools=[tools.create_database, tools.execute_sql_ddl, tools.run_sql_query], - verbose=True, - ) + def content_storer(self) ->Agent: + return Agent(config=self.agents_config['content_storer'], tools=[ + tools.create_database, tools.execute_sql_ddl, tools. + run_sql_query], verbose=True) @task - def scrape_site(self) -> Task: - return Task(config=self.tasks_config["scrape_site"]) + def scrape_site(self) ->Task: + return Task(config=self.tasks_config['scrape_site']) @task - def summarize(self) -> Task: - return Task(config=self.tasks_config["summarize"]) + def summarize(self) ->Task: + return Task(config=self.tasks_config['summarize']) @task - def store(self) -> Task: - return Task(config=self.tasks_config["store"]) + def store(self) ->Task: + return Task(config=self.tasks_config['store']) @crew - def crew(self) -> Crew: + def crew(self) ->Crew: """Creates the Test crew""" - return Crew( - agents=self.agents, - tasks=self.tasks, - process=Process.sequential, - verbose=True, - ) + return Crew(agents=self.agents, tasks=self.tasks, process=Process. + sequential, verbose=True) diff --git a/examples/web_researcher/src/main.py b/examples/web_researcher/src/main.py index e8b9897d..feceaed6 100644 --- a/examples/web_researcher/src/main.py +++ b/examples/web_researcher/src/main.py @@ -6,7 +6,6 @@ from crew import WebresearcherCrew import agentops from dotenv import load_dotenv - load_dotenv() agentops.init() @@ -20,7 +19,9 @@ def run(inputs: Optional[dict] = None): print(inputs) if not inputs: - inputs = {"url": "https://github.com/AgentOps-AI/AgentStack/tree/main"} + inputs = { + 'url': 'https://github.com/AgentOps-AI/AgentStack/tree/main' + } return WebresearcherCrew().crew().kickoff(inputs=inputs) @@ -28,11 +29,11 @@ def train(): """ Train the crew for a given number of iterations. """ - inputs = {"topic": "AI LLMs"} + inputs = { + "topic": "AI LLMs" + } try: - WebresearcherCrew().crew().train( - n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs - ) + WebresearcherCrew().crew().train(n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs) except Exception as e: raise Exception(f"An error occurred while training the crew: {e}") @@ -53,19 +54,19 @@ def test(): """ Test the crew execution and returns the results. """ - inputs = {"topic": "AI LLMs"} + inputs = { + "topic": "AI LLMs" + } try: - WebresearcherCrew().crew().test( - n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs - ) + WebresearcherCrew().crew().test(n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs) except Exception as e: raise Exception(f"An error occurred while replaying the crew: {e}") -if __name__ == "__main__": +if __name__ == '__main__': data = None if len(sys.argv) > 1: data_str = sys.argv[1] data = json.loads(data_str) - run(data) + run(data) \ No newline at end of file diff --git a/examples/web_researcher/src/tools/__init__.py b/examples/web_researcher/src/tools/__init__.py index db89c8c8..fafc6f7e 100644 --- a/examples/web_researcher/src/tools/__init__.py +++ b/examples/web_researcher/src/tools/__init__.py @@ -1,3 +1,4 @@ + # tool import from .firecrawl_tool import web_scrape, web_crawl, retrieve_web_crawl from .neon_tool import create_database, execute_sql_ddl, run_sql_query diff --git a/examples/web_researcher/src/tools/firecrawl_tool.py b/examples/web_researcher/src/tools/firecrawl_tool.py index 4f1cf1e0..65c66c24 100644 --- a/examples/web_researcher/src/tools/firecrawl_tool.py +++ b/examples/web_researcher/src/tools/firecrawl_tool.py @@ -2,7 +2,7 @@ from firecrawl import FirecrawlApp import os -app = FirecrawlApp(api_key=os.getenv("FIRECRAWL_API_KEY")) +app = FirecrawlApp(api_key=os.getenv('FIRECRAWL_API_KEY')) @tool @@ -11,7 +11,7 @@ def web_scrape(url: str): Scrape a url and return markdown. Use this to read a singular page and web_crawl only if you need to read all other links as well. """ - scrape_result = app.scrape_url(url, params={"formats": ["markdown"]}) + scrape_result = app.scrape_url(url, params={'formats': ['markdown']}) return scrape_result @@ -29,8 +29,11 @@ def web_crawl(url: str): crawl_status = app.crawl_url( url, - params={"limit": 100, "scrapeOptions": {"formats": ["markdown"]}}, - poll_interval=30, + params={ + 'limit': 100, + 'scrapeOptions': {'formats': ['markdown']} + }, + poll_interval=30 ) return crawl_status @@ -44,3 +47,4 @@ def retrieve_web_crawl(crawl_id: str): will tell you if the crawl is finished. If it is not, wait some more time then try again. """ return app.check_crawl_status(crawl_id) + diff --git a/tests/test_cli_loads.py b/tests/test_cli_loads.py index ce166cb1..49bb15cd 100644 --- a/tests/test_cli_loads.py +++ b/tests/test_cli_loads.py @@ -6,16 +6,14 @@ class TestAgentStackCLI(unittest.TestCase): - CLI_ENTRY = [ - sys.executable, - "-m", - "agentstack.main", - ] # Replace with your actual CLI entry point if different + CLI_ENTRY = [sys.executable, "-m", "agentstack.main"] # Replace with your actual CLI entry point if different def run_cli(self, *args): """Helper method to run the CLI with arguments.""" result = subprocess.run( - [*self.CLI_ENTRY, *args], capture_output=True, text=True + [*self.CLI_ENTRY, *args], + capture_output=True, + text=True ) return result diff --git a/tests/test_generation_files.py b/tests/test_generation_files.py index 435bc745..1fbbffbc 100644 --- a/tests/test_generation_files.py +++ b/tests/test_generation_files.py @@ -3,41 +3,32 @@ from pathlib import Path import shutil from agentstack.generation.files import ConfigFile, EnvFile -from agentstack.utils import ( - verify_agentstack_project, - get_framework, - get_telemetry_opt_out, -) +from agentstack.utils import verify_agentstack_project, get_framework, get_telemetry_opt_out BASE_PATH = Path(__file__).parent - class GenerationFilesTest(unittest.TestCase): def test_read_config(self): - config = ConfigFile(BASE_PATH / "fixtures") # + agentstack.json + config = ConfigFile(BASE_PATH / "fixtures") # + agentstack.json assert config.framework == "crewai" assert config.tools == ["tool1", "tool2"] assert config.telemetry_opt_out is None assert config.default_model is None - + def test_write_config(self): try: - os.makedirs(BASE_PATH / "tmp", exist_ok=True) - shutil.copy( - BASE_PATH / "fixtures/agentstack.json", - BASE_PATH / "tmp/agentstack.json", - ) - - with ConfigFile(BASE_PATH / "tmp") as config: + os.makedirs(BASE_PATH/"tmp", exist_ok=True) + shutil.copy(BASE_PATH/"fixtures/agentstack.json", + BASE_PATH/"tmp/agentstack.json") + + with ConfigFile(BASE_PATH/"tmp") as config: config.framework = "crewai" config.tools = ["tool1", "tool2"] config.telemetry_opt_out = True config.default_model = "openai/gpt-4o" - - tmp_data = open(BASE_PATH / "tmp/agentstack.json").read() - assert ( - tmp_data - == """{ + + tmp_data = open(BASE_PATH/"tmp/agentstack.json").read() + assert tmp_data == """{ "framework": "crewai", "tools": [ "tool1", @@ -46,12 +37,11 @@ def test_write_config(self): "telemetry_opt_out": true, "default_model": "openai/gpt-4o" }""" - ) except Exception as e: raise e finally: os.remove(BASE_PATH / "tmp/agentstack.json") - # os.rmdir(BASE_PATH / "tmp") + #os.rmdir(BASE_PATH / "tmp") def test_read_missing_config(self): with self.assertRaises(FileNotFoundError) as _: @@ -63,17 +53,17 @@ def test_verify_agentstack_project_valid(self): def test_verify_agentstack_project_invalid(self): with self.assertRaises(SystemExit) as _: verify_agentstack_project(BASE_PATH / "missing") - + def test_get_framework(self): assert get_framework(BASE_PATH / "fixtures") == "crewai" with self.assertRaises(SystemExit) as _: get_framework(BASE_PATH / "missing") - + def test_get_telemetry_opt_out(self): assert get_telemetry_opt_out(BASE_PATH / "fixtures") is False with self.assertRaises(SystemExit) as _: get_telemetry_opt_out(BASE_PATH / "missing") - + def test_read_env(self): env = EnvFile(BASE_PATH / "fixtures") assert env.variables == {"ENV_VAR1": "value1", "ENV_VAR2": "value2"} @@ -81,22 +71,22 @@ def test_read_env(self): assert env["ENV_VAR2"] == "value2" with self.assertRaises(KeyError) as _: env["ENV_VAR3"] - + def test_write_env(self): try: - os.makedirs(BASE_PATH / "tmp", exist_ok=True) - shutil.copy(BASE_PATH / "fixtures/.env", BASE_PATH / "tmp/.env") - - with EnvFile(BASE_PATH / "tmp") as env: - env.append_if_new("ENV_VAR1", "value100") # Should not be updated - env.append_if_new("ENV_VAR100", "value2") # Should be added - - tmp_data = open(BASE_PATH / "tmp/.env").read() - assert ( - tmp_data == """\nENV_VAR1=value1\nENV_VAR2=value2\nENV_VAR100=value2""" - ) + os.makedirs(BASE_PATH/"tmp", exist_ok=True) + shutil.copy(BASE_PATH/"fixtures/.env", + BASE_PATH/"tmp/.env") + + with EnvFile(BASE_PATH/"tmp") as env: + env.append_if_new("ENV_VAR1", "value100") # Should not be updated + env.append_if_new("ENV_VAR100", "value2") # Should be added + + tmp_data = open(BASE_PATH/"tmp/.env").read() + assert tmp_data == """\nENV_VAR1=value1\nENV_VAR2=value2\nENV_VAR100=value2""" except Exception as e: raise e finally: os.remove(BASE_PATH / "tmp/.env") - # os.rmdir(BASE_PATH / "tmp") + #os.rmdir(BASE_PATH / "tmp") + diff --git a/tests/test_tool_config.py b/tests/test_tool_config.py index 20f820d6..0b7c9fff 100644 --- a/tests/test_tool_config.py +++ b/tests/test_tool_config.py @@ -1,15 +1,10 @@ import json import unittest from pathlib import Path -from agentstack.generation.tool_generation import ( - get_all_tool_paths, - get_all_tool_names, - ToolConfig, -) +from agentstack.generation.tool_generation import get_all_tool_paths, get_all_tool_names, ToolConfig BASE_PATH = Path(__file__).parent - class ToolConfigTest(unittest.TestCase): def test_minimal_json(self): config = ToolConfig.from_json(BASE_PATH / "fixtures/tool_config_min.json") diff --git a/tests/test_utils.py b/tests/test_utils.py index edf4cdee..720965d2 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -5,12 +5,12 @@ class TestUtils(unittest.TestCase): def test_clean_input_no_change(self): - cleaned = clean_input("test_project") - self.assertEqual("test_project", cleaned) + cleaned = clean_input('test_project') + self.assertEqual('test_project', cleaned) def test_clean_input_remove_space(self): - cleaned = clean_input("test project") - self.assertEqual("test_project", cleaned) + cleaned = clean_input('test project') + self.assertEqual('test_project', cleaned) def test_is_snake_case(self): assert is_snake_case("hello_world") From a0c2f758d0ae6ab1d3917234da4c1396dbe837ef Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Wed, 4 Dec 2024 23:09:15 -0800 Subject: [PATCH 6/9] ruff format --- agentstack/cli/agentstack_data.py | 49 +++++++++-------- agentstack/cli/cli.py | 44 ++++++++++----- agentstack/generation/agent_generation.py | 32 +++++------ agentstack/generation/files.py | 60 ++++++++++++--------- agentstack/generation/gen_utils.py | 41 ++++++++------ agentstack/generation/task_generation.py | 30 ++++++----- agentstack/logger.py | 4 +- agentstack/main.py | 1 + agentstack/telemetry.py | 17 +++--- agentstack/utils.py | 32 +++++++---- tests/test_cli_loads.py | 10 ++-- tests/test_generation_files.py | 66 +++++++++++++---------- tests/test_tool_config.py | 7 ++- 13 files changed, 240 insertions(+), 153 deletions(-) diff --git a/agentstack/cli/agentstack_data.py b/agentstack/cli/agentstack_data.py index e426037e..940a1ca3 100644 --- a/agentstack/cli/agentstack_data.py +++ b/agentstack/cli/agentstack_data.py @@ -6,19 +6,22 @@ class ProjectMetadata: - def __init__(self, - project_name: str = None, - project_slug: str = None, - description: str = "", - author_name: str = "", - version: str = "", - license: str = "", - year: int = datetime.now().year, - template: str = "none", - template_version: str = "0", - ): + def __init__( + self, + project_name: str = None, + project_slug: str = None, + description: str = "", + author_name: str = "", + version: str = "", + license: str = "", + year: int = datetime.now().year, + template: str = "none", + template_version: str = "0", + ): self.project_name = clean_input(project_name) if project_name else "myagent" - self.project_slug = clean_input(project_slug) if project_slug else self.project_name + self.project_slug = ( + clean_input(project_slug) if project_slug else self.project_name + ) self.description = description self.author_name = author_name self.version = version @@ -70,10 +73,11 @@ def to_json(self): class FrameworkData: - def __init__(self, - # name: Optional[Literal["crewai"]] = None - name: str = None # TODO: better framework handling, Literal or Enum - ): + def __init__( + self, + # name: Optional[Literal["crewai"]] = None + name: str = None, # TODO: better framework handling, Literal or Enum + ): self.name = name def to_dict(self): @@ -86,12 +90,13 @@ def to_json(self): class CookiecutterData: - def __init__(self, - project_metadata: ProjectMetadata, - structure: ProjectStructure, - # framework: Literal["crewai"], - framework: str, - ): + def __init__( + self, + project_metadata: ProjectMetadata, + structure: ProjectStructure, + # framework: Literal["crewai"], + framework: str, + ): self.project_metadata = project_metadata self.framework = framework self.structure = structure diff --git a/agentstack/cli/cli.py b/agentstack/cli/cli.py index 1cfb7e9d..ed4514e3 100644 --- a/agentstack/cli/cli.py +++ b/agentstack/cli/cli.py @@ -13,7 +13,12 @@ import importlib.resources from cookiecutter.main import cookiecutter -from .agentstack_data import FrameworkData, ProjectMetadata, ProjectStructure, CookiecutterData +from .agentstack_data import ( + FrameworkData, + ProjectMetadata, + ProjectStructure, + CookiecutterData, +) from agentstack.logger import log from agentstack.utils import get_package_path from agentstack.generation.files import ConfigFile @@ -29,7 +34,12 @@ "anthropic/claude-3-opus", ] -def init_project_builder(slug_name: Optional[str] = None, template: Optional[str] = None, use_wizard: bool = False): + +def init_project_builder( + slug_name: Optional[str] = None, + template: Optional[str] = None, + use_wizard: bool = False, +): if slug_name and not is_snake_case(slug_name): print(term_color("Project name must be snake case", "red")) return @@ -41,7 +51,7 @@ def init_project_builder(slug_name: Optional[str] = None, template: Optional[str template_data = None if template is not None: url_start = "https://" - if template[:len(url_start)] == url_start: + if template[: len(url_start)] == url_start: # template is a url response = requests.get(template) if response.status_code == 200: @@ -91,7 +101,7 @@ def init_project_builder(slug_name: Optional[str] = None, template: Optional[str "version": "0.0.1", "description": "New agentstack project", "author": "Name ", - "license": "MIT" + "license": "MIT", } framework = "CrewAI" # TODO: if --no-wizard, require a framework flag @@ -129,7 +139,7 @@ def configure_default_model(path: Optional[str] = None): """Set the default model""" agentstack_config = ConfigFile(path) if agentstack_config.default_model: - return # Default model already set + return # Default model already set print("Project does not have a default model configured.") other_msg = "Other (enter a model name)" @@ -138,8 +148,10 @@ def configure_default_model(path: Optional[str] = None): choices=PREFERRED_MODELS + [other_msg], ) - if model == other_msg: # If the user selects "Other", prompt for a model name - print(f'A list of available models is available at: "https://docs.litellm.ai/docs/providers"') + if model == other_msg: # If the user selects "Other", prompt for a model name + print( + f'A list of available models is available at: "https://docs.litellm.ai/docs/providers"' + ) model = inquirer.text(message="Enter the model name") with ConfigFile(path) as agentstack_config: @@ -316,7 +328,6 @@ def ask_tools() -> list: tools_data = open_json_file(tools_json_path) while adding_tools: - tool_type = inquirer.list_input( message="What category tool do you want to add?", choices=list(tools_data.keys()) + ["~~ Stop adding tools ~~"], @@ -368,7 +379,12 @@ def ask_project_details(slug_name: Optional[str] = None) -> dict: return questions -def insert_template(project_details: dict, framework_name: str, design: dict, template_data: Optional[dict] = None): +def insert_template( + project_details: dict, + framework_name: str, + design: dict, + template_data: Optional[dict] = None, +): framework = FrameworkData(framework_name.lower()) project_metadata = ProjectMetadata( project_name=project_details["name"], @@ -385,9 +401,11 @@ def insert_template(project_details: dict, framework_name: str, design: dict, te project_structure.agents = design["agents"] project_structure.tasks = design["tasks"] - cookiecutter_data = CookiecutterData(project_metadata=project_metadata, - structure=project_structure, - framework=framework_name.lower()) + cookiecutter_data = CookiecutterData( + project_metadata=project_metadata, + structure=project_structure, + framework=framework_name.lower(), + ) template_path = get_package_path() / f"templates/{framework.name}" with open(f"{template_path}/cookiecutter.json", "w") as json_file: @@ -455,4 +473,4 @@ def list_tools(): print(f": {tool.url if tool.url else 'AgentStack default tool'}") print("\n\n✨ Add a tool with: agentstack tools add ") - print(" https://docs.agentstack.sh/tools/core") \ No newline at end of file + print(" https://docs.agentstack.sh/tools/core") diff --git a/agentstack/generation/agent_generation.py b/agentstack/generation/agent_generation.py index bf64dd2e..be053cce 100644 --- a/agentstack/generation/agent_generation.py +++ b/agentstack/generation/agent_generation.py @@ -9,13 +9,13 @@ def generate_agent( - name, - role: Optional[str], - goal: Optional[str], - backstory: Optional[str], - llm: Optional[str] + name, + role: Optional[str], + goal: Optional[str], + backstory: Optional[str], + llm: Optional[str], ): - agentstack_config = ConfigFile() # TODO path + agentstack_config = ConfigFile() # TODO path if not role: role = 'Add your role here' if not goal: @@ -40,11 +40,11 @@ def generate_agent( def generate_crew_agent( - name, - role: Optional[str] = 'Add your role here', - goal: Optional[str] = 'Add your goal here', - backstory: Optional[str] = 'Add your backstory here', - llm: Optional[str] = 'openai/gpt-4o' + name, + role: Optional[str] = 'Add your role here', + goal: Optional[str] = 'Add your goal here', + backstory: Optional[str] = 'Add your backstory here', + llm: Optional[str] = 'openai/gpt-4o', ): config_path = os.path.join('src', 'config', 'agents.yaml') @@ -68,7 +68,9 @@ def generate_crew_agent( # Handle None values role_str = FoldedScalarString(role) if role else FoldedScalarString('') goals_str = FoldedScalarString(goal) if goal else FoldedScalarString('') - backstory_str = FoldedScalarString(backstory) if backstory else FoldedScalarString('') + backstory_str = ( + FoldedScalarString(backstory) if backstory else FoldedScalarString('') + ) model_str = llm if llm else '' # Add new agent details @@ -76,7 +78,7 @@ def generate_crew_agent( 'role': role_str, 'goal': goals_str, 'backstory': backstory_str, - 'llm': model_str + 'llm': model_str, } # Write back to the file without altering existing content @@ -94,7 +96,7 @@ def generate_crew_agent( " tools=[], # add tools here or use `agentstack tools add ", # TODO: Add any tools in agentstack.json " verbose=True", " )", - "" + "", ] insert_code_after_tag(file_path, tag, code_to_insert) @@ -102,4 +104,4 @@ def generate_crew_agent( def get_agent_names(framework: str = 'crewai', path: str = '') -> List[str]: """Get only agent names from the crew file""" - return get_crew_components(framework, CrewComponent.AGENT, path)['agents'] \ No newline at end of file + return get_crew_components(framework, CrewComponent.AGENT, path)['agents'] diff --git a/agentstack/generation/files.py b/agentstack/generation/files.py index 9f3156ff..7aff5448 100644 --- a/agentstack/generation/files.py +++ b/agentstack/generation/files.py @@ -9,20 +9,21 @@ CONFIG_FILENAME = "agentstack.json" ENV_FILEMANE = ".env" + class ConfigFile(BaseModel): """ Interface for interacting with the agentstack.json file inside a project directory. Handles both data validation and file I/O. - + `path` is the directory where the agentstack.json file is located. Defaults to the current working directory. - + Use it as a context manager to make and save edits: ```python with ConfigFile() as config: config.tools.append('tool_name') ``` - + Config Schema ------------- framework: str @@ -30,15 +31,16 @@ class ConfigFile(BaseModel): tools: list[str] A list of tools that are currently installed in the project. telemetry_opt_out: Optional[bool] - Whether the user has opted out of telemetry. + Whether the user has opted out of telemetry. default_model: Optional[str] The default model to use when generating agent configurations. """ + framework: Optional[str] = DEFAULT_FRAMEWORK tools: list[str] = [] telemetry_opt_out: Optional[bool] = None default_model: Optional[str] = None - + def __init__(self, path: Union[str, Path, None] = None): path = Path(path) if path else Path.cwd() if os.path.exists(path / CONFIG_FILENAME): @@ -46,46 +48,52 @@ def __init__(self, path: Union[str, Path, None] = None): super().__init__(**json.loads(f.read())) else: raise FileNotFoundError(f"File {path / CONFIG_FILENAME} does not exist.") - self._path = path # attribute needs to be set after init + self._path = path # attribute needs to be set after init def model_dump(self, *args, **kwargs) -> dict: # Ignore None values dump = super().model_dump(*args, **kwargs) return {key: value for key, value in dump.items() if value is not None} - + def write(self): with open(self._path / CONFIG_FILENAME, 'w') as f: f.write(json.dumps(self.model_dump(), indent=4)) - - def __enter__(self) -> 'ConfigFile': return self - def __exit__(self, *args): self.write() + + def __enter__(self) -> 'ConfigFile': + return self + + def __exit__(self, *args): + self.write() class EnvFile: """ Interface for interacting with the .env file inside a project directory. - Unlike the ConfigFile, we do not re-write the entire file on every change, + Unlike the ConfigFile, we do not re-write the entire file on every change, and instead just append new lines to the end of the file. This preseres comments and other formatting that the user may have added and prevents opportunities for data loss. - + `path` is the directory where the .env file is located. Defaults to the current working directory. `filename` is the name of the .env file, defaults to '.env'. - + Use it as a context manager to make and save edits: ```python with EnvFile() as env: env.append_if_new('ENV_VAR', 'value') ``` """ + variables: dict[str, str] - - def __init__(self, path: Union[str, Path, None] = None, filename: str = ENV_FILEMANE): + + def __init__( + self, path: Union[str, Path, None] = None, filename: str = ENV_FILEMANE + ): self._path = Path(path) if path else Path.cwd() self._filename = filename self.read() - + def __getitem__(self, key): return self.variables[key] @@ -93,15 +101,15 @@ def __setitem__(self, key, value): if key in self.variables: raise ValueError("EnvFile does not allow overwriting values.") self.append_if_new(key, value) - + def __contains__(self, key) -> bool: return key in self.variables - + def append_if_new(self, key, value): if key not in self.variables: self.variables[key] = value self._new_variables[key] = value - + def read(self): def parse_line(line): key, value = line.split('=') @@ -109,16 +117,20 @@ def parse_line(line): if os.path.exists(self._path / self._filename): with open(self._path / self._filename, 'r') as f: - self.variables = dict([parse_line(line) for line in f.readlines() if '=' in line]) + self.variables = dict( + [parse_line(line) for line in f.readlines() if '=' in line] + ) else: self.variables = {} self._new_variables = {} - + def write(self): with open(self._path / self._filename, 'a') as f: for key, value in self._new_variables.items(): f.write(f"\n{key}={value}") - - def __enter__(self) -> 'EnvFile': return self - def __exit__(self, *args): self.write() + def __enter__(self) -> 'EnvFile': + return self + + def __exit__(self, *args): + self.write() diff --git a/agentstack/generation/gen_utils.py b/agentstack/generation/gen_utils.py index f9ac0e5f..80ebcf8f 100644 --- a/agentstack/generation/gen_utils.py +++ b/agentstack/generation/gen_utils.py @@ -16,8 +16,11 @@ def insert_code_after_tag(file_path, tag, code_to_insert, next_line=False): for index, line in enumerate(lines): if tag in line: # Insert the code block after the tag - indented_code = [(line[:len(line)-len(line.lstrip())] + code_line + '\n') for code_line in code_to_insert] - lines[index+1:index+1] = indented_code + indented_code = [ + (line[: len(line) - len(line.lstrip())] + code_line + '\n') + for code_line in code_to_insert + ] + lines[index + 1 : index + 1] = indented_code break else: raise ValueError(f"Tag '{tag}' not found in the file.") @@ -36,8 +39,10 @@ def insert_after_tasks(file_path, code_to_insert): last_task_end = None last_task_start = None for node in ast.walk(module): - if isinstance(node, ast.FunctionDef) and \ - any(isinstance(deco, ast.Name) and deco.id == 'task' for deco in node.decorator_list): + if isinstance(node, ast.FunctionDef) and any( + isinstance(deco, ast.Name) and deco.id == 'task' + for deco in node.decorator_list + ): last_task_end = node.end_lineno last_task_start = node.lineno @@ -86,9 +91,9 @@ class CrewComponent(str, Enum): def get_crew_components( - framework: str = 'crewai', - component_type: Optional[Union[CrewComponent, List[CrewComponent]]] = None, - path: str = '' + framework: str = 'crewai', + component_type: Optional[Union[CrewComponent, List[CrewComponent]]] = None, + path: str = '', ) -> dict[str, List[str]]: """ Get names of components (agents and/or tasks) defined in a crew file. @@ -116,10 +121,7 @@ def get_crew_components( # Parse the source into an AST tree = ast.parse(source) - components = { - 'agents': [], - 'tasks': [] - } + components = {'agents': [], 'tasks': []} # Find all function definitions with relevant decorators for node in ast.walk(tree): @@ -127,16 +129,21 @@ def get_crew_components( # Check decorators for decorator in node.decorator_list: if isinstance(decorator, ast.Name): - if (component_type is None or CrewComponent.AGENT in component_type) \ - and decorator.id == 'agent': + if ( + component_type is None or CrewComponent.AGENT in component_type + ) and decorator.id == 'agent': components['agents'].append(node.name) - elif (component_type is None or CrewComponent.TASK in component_type) \ - and decorator.id == 'task': + elif ( + component_type is None or CrewComponent.TASK in component_type + ) and decorator.id == 'task': components['tasks'].append(node.name) # If specific types were requested, only return those if component_type: - return {k: v for k, v in components.items() - if CrewComponent(k[:-1]) in component_type} + return { + k: v + for k, v in components.items() + if CrewComponent(k[:-1]) in component_type + } return components diff --git a/agentstack/generation/task_generation.py b/agentstack/generation/task_generation.py index d2fc6ebc..c8a3da35 100644 --- a/agentstack/generation/task_generation.py +++ b/agentstack/generation/task_generation.py @@ -8,10 +8,10 @@ def generate_task( - name, - description: Optional[str], - expected_output: Optional[str], - agent: Optional[str] + name, + description: Optional[str], + expected_output: Optional[str], + agent: Optional[str], ): if not description: description = 'Add your description here' @@ -35,10 +35,10 @@ def generate_task( def generate_crew_task( - name, - description: Optional[str], - expected_output: Optional[str], - agent: Optional[str] + name, + description: Optional[str], + expected_output: Optional[str], + agent: Optional[str], ): config_path = os.path.join('src', 'config', 'tasks.yaml') @@ -60,8 +60,14 @@ def generate_crew_task( data = {} # Handle None values - description_str = FoldedScalarString(description) if description else FoldedScalarString('') - expected_output_str = FoldedScalarString(expected_output) if expected_output else FoldedScalarString('') + description_str = ( + FoldedScalarString(description) if description else FoldedScalarString('') + ) + expected_output_str = ( + FoldedScalarString(expected_output) + if expected_output + else FoldedScalarString('') + ) agent_str = FoldedScalarString(agent) if agent else FoldedScalarString('') # Add new agent details @@ -83,7 +89,7 @@ def generate_crew_task( " return Task(", f" config=self.tasks_config['{name}'],", " )", - "" + "", ] insert_after_tasks(file_path, code_to_insert) @@ -91,4 +97,4 @@ def generate_crew_task( def get_task_names(framework: str, path: str = '') -> List[str]: """Get only task names from the crew file""" - return get_crew_components(framework, CrewComponent.TASK, path)['tasks'] \ No newline at end of file + return get_crew_components(framework, CrewComponent.TASK, path)['tasks'] diff --git a/agentstack/logger.py b/agentstack/logger.py index 680d3067..e41c4887 100644 --- a/agentstack/logger.py +++ b/agentstack/logger.py @@ -16,7 +16,9 @@ def get_logger(name, debug=False): handler = logging.StreamHandler(sys.stdout) handler.setLevel(log_level) - formatter = logging.Formatter("%(asctime)s - %(process)d - %(threadName)s - %(filename)s:%(lineno)d - %(name)s - %(levelname)s - %(message)s") + formatter = logging.Formatter( + "%(asctime)s - %(process)d - %(threadName)s - %(filename)s:%(lineno)d - %(name)s - %(levelname)s - %(message)s" + ) handler.setFormatter(formatter) if not logger.handlers: diff --git a/agentstack/main.py b/agentstack/main.py index f701dd70..fa8d49b0 100644 --- a/agentstack/main.py +++ b/agentstack/main.py @@ -9,6 +9,7 @@ import webbrowser + def main(): parser = argparse.ArgumentParser( description="AgentStack CLI - The easiest way to build an agent application" diff --git a/agentstack/telemetry.py b/agentstack/telemetry.py index ada4f55e..1e8c3e48 100644 --- a/agentstack/telemetry.py +++ b/agentstack/telemetry.py @@ -32,6 +32,7 @@ TELEMETRY_URL = 'https://api.agentstack.sh/telemetry' + def collect_machine_telemetry(command: str): if command != "init" and get_telemetry_opt_out(): return @@ -43,7 +44,7 @@ def collect_machine_telemetry(command: str): 'os_version': platform.version(), 'cpu_count': psutil.cpu_count(logical=True), 'memory': psutil.virtual_memory().total, - 'agentstack_version': get_version() + 'agentstack_version': get_version(), } if command != "init": @@ -56,12 +57,14 @@ def collect_machine_telemetry(command: str): response = requests.get('https://ipinfo.io/json') if response.status_code == 200: location_data = response.json() - telemetry_data.update({ - 'ip': location_data.get('ip'), - 'city': location_data.get('city'), - 'region': location_data.get('region'), - 'country': location_data.get('country') - }) + telemetry_data.update( + { + 'ip': location_data.get('ip'), + 'city': location_data.get('city'), + 'region': location_data.get('region'), + 'country': location_data.get('country'), + } + ) except requests.RequestException as e: telemetry_data['location_error'] = str(e) diff --git a/agentstack/utils.py b/agentstack/utils.py index 5b88eb5c..cc911d41 100644 --- a/agentstack/utils.py +++ b/agentstack/utils.py @@ -7,6 +7,7 @@ from pathlib import Path import importlib.resources + def get_version(): try: return version('agentstack') @@ -17,12 +18,15 @@ def get_version(): def verify_agentstack_project(path: Optional[str] = None): from agentstack.generation import ConfigFile + try: _ = ConfigFile(path) except FileNotFoundError: - print("\033[31mAgentStack Error: This does not appear to be an AgentStack project." - "\nPlease ensure you're at the root directory of your project and a file named agentstack.json exists. " - "If you're starting a new project, run `agentstack init`\033[0m") + print( + "\033[31mAgentStack Error: This does not appear to be an AgentStack project." + "\nPlease ensure you're at the root directory of your project and a file named agentstack.json exists. " + "If you're starting a new project, run `agentstack init`\033[0m" + ) sys.exit(1) @@ -35,6 +39,7 @@ def get_package_path() -> Path: def get_framework(path: Optional[str] = None) -> str: from agentstack.generation import ConfigFile + try: agentstack_config = ConfigFile(path) framework = agentstack_config.framework @@ -44,19 +49,25 @@ def get_framework(path: Optional[str] = None) -> str: return framework except FileNotFoundError: - print("\033[31mFile agentstack.json does not exist. Are you in the right directory?\033[0m") + print( + "\033[31mFile agentstack.json does not exist. Are you in the right directory?\033[0m" + ) sys.exit(1) def get_telemetry_opt_out(path: Optional[str] = None) -> str: from agentstack.generation import ConfigFile + try: agentstack_config = ConfigFile(path) return bool(agentstack_config.telemetry_opt_out) except FileNotFoundError: - print("\033[31mFile agentstack.json does not exist. Are you in the right directory?\033[0m") + print( + "\033[31mFile agentstack.json does not exist. Are you in the right directory?\033[0m" + ) sys.exit(1) + def camel_to_snake(name): s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() @@ -74,7 +85,12 @@ def open_json_file(path) -> dict: def clean_input(input_string): special_char_pattern = re.compile(r'[^a-zA-Z0-9\s_]') - return re.sub(special_char_pattern, '', input_string).lower().replace(' ', '_').replace('-', '_') + return ( + re.sub(special_char_pattern, '', input_string) + .lower() + .replace(' ', '_') + .replace('-', '_') + ) def term_color(text: str, color: str) -> str: @@ -85,7 +101,7 @@ def term_color(text: str, color: str) -> str: 'blue': '94', 'purple': '95', 'cyan': '96', - 'white': '97' + 'white': '97', } color_code = colors.get(color) if color_code: @@ -94,7 +110,5 @@ def term_color(text: str, color: str) -> str: return text - def is_snake_case(string: str): return bool(re.match('^[a-z0-9_]+$', string)) - diff --git a/tests/test_cli_loads.py b/tests/test_cli_loads.py index 49bb15cd..ce166cb1 100644 --- a/tests/test_cli_loads.py +++ b/tests/test_cli_loads.py @@ -6,14 +6,16 @@ class TestAgentStackCLI(unittest.TestCase): - CLI_ENTRY = [sys.executable, "-m", "agentstack.main"] # Replace with your actual CLI entry point if different + CLI_ENTRY = [ + sys.executable, + "-m", + "agentstack.main", + ] # Replace with your actual CLI entry point if different def run_cli(self, *args): """Helper method to run the CLI with arguments.""" result = subprocess.run( - [*self.CLI_ENTRY, *args], - capture_output=True, - text=True + [*self.CLI_ENTRY, *args], capture_output=True, text=True ) return result diff --git a/tests/test_generation_files.py b/tests/test_generation_files.py index 1fbbffbc..435bc745 100644 --- a/tests/test_generation_files.py +++ b/tests/test_generation_files.py @@ -3,32 +3,41 @@ from pathlib import Path import shutil from agentstack.generation.files import ConfigFile, EnvFile -from agentstack.utils import verify_agentstack_project, get_framework, get_telemetry_opt_out +from agentstack.utils import ( + verify_agentstack_project, + get_framework, + get_telemetry_opt_out, +) BASE_PATH = Path(__file__).parent + class GenerationFilesTest(unittest.TestCase): def test_read_config(self): - config = ConfigFile(BASE_PATH / "fixtures") # + agentstack.json + config = ConfigFile(BASE_PATH / "fixtures") # + agentstack.json assert config.framework == "crewai" assert config.tools == ["tool1", "tool2"] assert config.telemetry_opt_out is None assert config.default_model is None - + def test_write_config(self): try: - os.makedirs(BASE_PATH/"tmp", exist_ok=True) - shutil.copy(BASE_PATH/"fixtures/agentstack.json", - BASE_PATH/"tmp/agentstack.json") - - with ConfigFile(BASE_PATH/"tmp") as config: + os.makedirs(BASE_PATH / "tmp", exist_ok=True) + shutil.copy( + BASE_PATH / "fixtures/agentstack.json", + BASE_PATH / "tmp/agentstack.json", + ) + + with ConfigFile(BASE_PATH / "tmp") as config: config.framework = "crewai" config.tools = ["tool1", "tool2"] config.telemetry_opt_out = True config.default_model = "openai/gpt-4o" - - tmp_data = open(BASE_PATH/"tmp/agentstack.json").read() - assert tmp_data == """{ + + tmp_data = open(BASE_PATH / "tmp/agentstack.json").read() + assert ( + tmp_data + == """{ "framework": "crewai", "tools": [ "tool1", @@ -37,11 +46,12 @@ def test_write_config(self): "telemetry_opt_out": true, "default_model": "openai/gpt-4o" }""" + ) except Exception as e: raise e finally: os.remove(BASE_PATH / "tmp/agentstack.json") - #os.rmdir(BASE_PATH / "tmp") + # os.rmdir(BASE_PATH / "tmp") def test_read_missing_config(self): with self.assertRaises(FileNotFoundError) as _: @@ -53,17 +63,17 @@ def test_verify_agentstack_project_valid(self): def test_verify_agentstack_project_invalid(self): with self.assertRaises(SystemExit) as _: verify_agentstack_project(BASE_PATH / "missing") - + def test_get_framework(self): assert get_framework(BASE_PATH / "fixtures") == "crewai" with self.assertRaises(SystemExit) as _: get_framework(BASE_PATH / "missing") - + def test_get_telemetry_opt_out(self): assert get_telemetry_opt_out(BASE_PATH / "fixtures") is False with self.assertRaises(SystemExit) as _: get_telemetry_opt_out(BASE_PATH / "missing") - + def test_read_env(self): env = EnvFile(BASE_PATH / "fixtures") assert env.variables == {"ENV_VAR1": "value1", "ENV_VAR2": "value2"} @@ -71,22 +81,22 @@ def test_read_env(self): assert env["ENV_VAR2"] == "value2" with self.assertRaises(KeyError) as _: env["ENV_VAR3"] - + def test_write_env(self): try: - os.makedirs(BASE_PATH/"tmp", exist_ok=True) - shutil.copy(BASE_PATH/"fixtures/.env", - BASE_PATH/"tmp/.env") - - with EnvFile(BASE_PATH/"tmp") as env: - env.append_if_new("ENV_VAR1", "value100") # Should not be updated - env.append_if_new("ENV_VAR100", "value2") # Should be added - - tmp_data = open(BASE_PATH/"tmp/.env").read() - assert tmp_data == """\nENV_VAR1=value1\nENV_VAR2=value2\nENV_VAR100=value2""" + os.makedirs(BASE_PATH / "tmp", exist_ok=True) + shutil.copy(BASE_PATH / "fixtures/.env", BASE_PATH / "tmp/.env") + + with EnvFile(BASE_PATH / "tmp") as env: + env.append_if_new("ENV_VAR1", "value100") # Should not be updated + env.append_if_new("ENV_VAR100", "value2") # Should be added + + tmp_data = open(BASE_PATH / "tmp/.env").read() + assert ( + tmp_data == """\nENV_VAR1=value1\nENV_VAR2=value2\nENV_VAR100=value2""" + ) except Exception as e: raise e finally: os.remove(BASE_PATH / "tmp/.env") - #os.rmdir(BASE_PATH / "tmp") - + # os.rmdir(BASE_PATH / "tmp") diff --git a/tests/test_tool_config.py b/tests/test_tool_config.py index 0b7c9fff..20f820d6 100644 --- a/tests/test_tool_config.py +++ b/tests/test_tool_config.py @@ -1,10 +1,15 @@ import json import unittest from pathlib import Path -from agentstack.generation.tool_generation import get_all_tool_paths, get_all_tool_names, ToolConfig +from agentstack.generation.tool_generation import ( + get_all_tool_paths, + get_all_tool_names, + ToolConfig, +) BASE_PATH = Path(__file__).parent + class ToolConfigTest(unittest.TestCase): def test_minimal_json(self): config = ToolConfig.from_json(BASE_PATH / "fixtures/tool_config_min.json") From a45c77cc2dad6cf133f9c8e78af499b20d9669eb Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Wed, 4 Dec 2024 23:09:48 -0800 Subject: [PATCH 7/9] ruff format --- agentstack/cli/agentstack_data.py | 49 ++--- agentstack/cli/cli.py | 219 +++++++++++++++-------- agentstack/generation/tool_generation.py | 162 ++++++++++------- agentstack/main.py | 2 +- agentstack/packaging.py | 3 + agentstack/update.py | 27 ++- agentstack/utils.py | 32 +++- 7 files changed, 319 insertions(+), 175 deletions(-) diff --git a/agentstack/cli/agentstack_data.py b/agentstack/cli/agentstack_data.py index 4acd39c3..25dd88a3 100644 --- a/agentstack/cli/agentstack_data.py +++ b/agentstack/cli/agentstack_data.py @@ -7,19 +7,22 @@ class ProjectMetadata: - def __init__(self, - project_name: str = None, - project_slug: str = None, - description: str = "", - author_name: str = "", - version: str = "", - license: str = "", - year: int = datetime.now().year, - template: str = "none", - template_version: str = "0", - ): + def __init__( + self, + project_name: str = None, + project_slug: str = None, + description: str = "", + author_name: str = "", + version: str = "", + license: str = "", + year: int = datetime.now().year, + template: str = "none", + template_version: str = "0", + ): self.project_name = clean_input(project_name) if project_name else "myagent" - self.project_slug = clean_input(project_slug) if project_slug else self.project_name + self.project_slug = ( + clean_input(project_slug) if project_slug else self.project_name + ) self.description = description self.author_name = author_name self.version = version @@ -76,10 +79,11 @@ def to_json(self): class FrameworkData: - def __init__(self, - # name: Optional[Literal["crewai"]] = None - name: str = None # TODO: better framework handling, Literal or Enum - ): + def __init__( + self, + # name: Optional[Literal["crewai"]] = None + name: str = None, # TODO: better framework handling, Literal or Enum + ): self.name = name def to_dict(self): @@ -92,12 +96,13 @@ def to_json(self): class CookiecutterData: - def __init__(self, - project_metadata: ProjectMetadata, - structure: ProjectStructure, - # framework: Literal["crewai"], - framework: str, - ): + def __init__( + self, + project_metadata: ProjectMetadata, + structure: ProjectStructure, + # framework: Literal["crewai"], + framework: str, + ): self.project_metadata = project_metadata self.framework = framework self.structure = structure diff --git a/agentstack/cli/cli.py b/agentstack/cli/cli.py index 16f3684c..dc447ce7 100644 --- a/agentstack/cli/cli.py +++ b/agentstack/cli/cli.py @@ -13,7 +13,12 @@ import importlib.resources from cookiecutter.main import cookiecutter -from .agentstack_data import FrameworkData, ProjectMetadata, ProjectStructure, CookiecutterData +from .agentstack_data import ( + FrameworkData, + ProjectMetadata, + ProjectStructure, + CookiecutterData, +) from agentstack.logger import log from agentstack.utils import get_package_path from agentstack.generation.files import ConfigFile @@ -30,7 +35,12 @@ 'anthropic/claude-3-opus', ] -def init_project_builder(slug_name: Optional[str] = None, template: Optional[str] = None, use_wizard: bool = False): + +def init_project_builder( + slug_name: Optional[str] = None, + template: Optional[str] = None, + use_wizard: bool = False, +): if slug_name and not is_snake_case(slug_name): print(term_color("Project name must be snake case", 'red')) return @@ -42,16 +52,23 @@ def init_project_builder(slug_name: Optional[str] = None, template: Optional[str template_data = None if template is not None: url_start = "https://" - if template[:len(url_start)] == url_start: + if template[: len(url_start)] == url_start: # template is a url response = requests.get(template) if response.status_code == 200: template_data = response.json() else: - print(term_color(f"Failed to fetch template data from {template}. Status code: {response.status_code}", 'red')) + print( + term_color( + f"Failed to fetch template data from {template}. Status code: {response.status_code}", + 'red', + ) + ) sys.exit(1) else: - with importlib.resources.path('agentstack.templates.proj_templates', f'{template}.json') as template_path: + with importlib.resources.path( + 'agentstack.templates.proj_templates', f'{template}.json' + ) as template_path: if template_path is None: print(term_color(f"No such template {template} found", 'red')) sys.exit(1) @@ -63,7 +80,7 @@ def init_project_builder(slug_name: Optional[str] = None, template: Optional[str "version": "0.0.1", "description": template_data['description'], "author": "Name ", - "license": "MIT" + "license": "MIT", } framework = template_data['framework'] design = { @@ -89,16 +106,12 @@ def init_project_builder(slug_name: Optional[str] = None, template: Optional[str "version": "0.0.1", "description": "New agentstack project", "author": "Name ", - "license": "MIT" + "license": "MIT", } framework = "crewai" # TODO: if --no-wizard, require a framework flag - design = { - 'agents': [], - 'tasks': [], - 'inputs': [] - } + design = {'agents': [], 'tasks': [], 'inputs': []} tools = [] @@ -109,12 +122,19 @@ def init_project_builder(slug_name: Optional[str] = None, template: Optional[str ) insert_template(project_details, framework, design, template_data) for tool_data in tools: - generation.add_tool(tool_data['name'], agents=tool_data['agents'], path=project_details['name']) + generation.add_tool( + tool_data['name'], agents=tool_data['agents'], path=project_details['name'] + ) try: packaging.install(f'{AGENTSTACK_PACKAGE}[{framework}]', path=slug_name) except Exception as e: - print(term_color(f"Failed to install dependencies for {slug_name}. Please try again by running `agentstack update`", 'red')) + print( + term_color( + f"Failed to install dependencies for {slug_name}. Please try again by running `agentstack update`", + 'red', + ) + ) def welcome_message(): @@ -134,8 +154,8 @@ def configure_default_model(path: Optional[str] = None): """Set the default model""" agentstack_config = ConfigFile(path) if agentstack_config.default_model: - return # Default model already set - + return # Default model already set + print("Project does not have a default model configured.") other_msg = f"Other (enter a model name)" model = inquirer.list_input( @@ -143,10 +163,12 @@ def configure_default_model(path: Optional[str] = None): choices=PREFERRED_MODELS + [other_msg], ) - if model == other_msg: # If the user selects "Other", prompt for a model name - print(f'A list of available models is available at: "https://docs.litellm.ai/docs/providers"') + if model == other_msg: # If the user selects "Other", prompt for a model name + print( + f'A list of available models is available at: "https://docs.litellm.ai/docs/providers"' + ) model = inquirer.text(message="Enter the model name") - + with ConfigFile(path) as agentstack_config: agentstack_config.default_model = model @@ -172,7 +194,9 @@ def ask_framework() -> str: # choices=["CrewAI", "Autogen", "LiteLLM"], # ) - print("Congrats! Your project is ready to go! Quickly add features now or skip to do it later.\n\n") + print( + "Congrats! Your project is ready to go! Quickly add features now or skip to do it later.\n\n" + ) return framework @@ -183,10 +207,7 @@ def ask_design() -> dict: ) if not use_wizard: - return { - 'agents': [], - 'tasks': [] - } + return {'agents': [], 'tasks': []} os.system("cls" if os.name == "nt" else "clear") @@ -208,24 +229,36 @@ def ask_design() -> dict: agent_incomplete = True agent = None while agent_incomplete: - agent = inquirer.prompt([ - inquirer.Text("name", message="What's the name of this agent? (snake_case)"), - inquirer.Text("role", message="What role does this agent have?"), - inquirer.Text("goal", message="What is the goal of the agent?"), - inquirer.Text("backstory", message="Give your agent a backstory"), - # TODO: make a list - #2 - inquirer.Text('model', message="What LLM should this agent use? (any LiteLLM provider)", default="openai/gpt-4"), - # inquirer.List("model", message="What LLM should this agent use? (any LiteLLM provider)", choices=[ - # 'mixtral_llm', - # 'mixtral_llm', - # ]), - ]) + agent = inquirer.prompt( + [ + inquirer.Text( + "name", message="What's the name of this agent? (snake_case)" + ), + inquirer.Text("role", message="What role does this agent have?"), + inquirer.Text("goal", message="What is the goal of the agent?"), + inquirer.Text("backstory", message="Give your agent a backstory"), + # TODO: make a list - #2 + inquirer.Text( + 'model', + message="What LLM should this agent use? (any LiteLLM provider)", + default="openai/gpt-4", + ), + # inquirer.List("model", message="What LLM should this agent use? (any LiteLLM provider)", choices=[ + # 'mixtral_llm', + # 'mixtral_llm', + # ]), + ] + ) if not agent['name'] or agent['name'] == '': print(term_color("Error: Agent name is required - Try again", 'red')) agent_incomplete = True elif not is_snake_case(agent['name']): - print(term_color("Error: Agent name must be snake case - Try again", 'red')) + print( + term_color( + "Error: Agent name must be snake case - Try again", 'red' + ) + ) else: agent_incomplete = False @@ -251,19 +284,32 @@ def ask_design() -> dict: task_incomplete = True task = None while task_incomplete: - task = inquirer.prompt([ - inquirer.Text("name", message="What's the name of this task? (snake_case)"), - inquirer.Text("description", message="Describe the task in more detail"), - inquirer.Text("expected_output", - message="What do you expect the result to look like? (ex: A 5 bullet point summary of the email)"), - inquirer.List("agent", message="Which agent should be assigned this task?", - choices=[a['name'] for a in agents], ), - ]) + task = inquirer.prompt( + [ + inquirer.Text( + "name", message="What's the name of this task? (snake_case)" + ), + inquirer.Text( + "description", message="Describe the task in more detail" + ), + inquirer.Text( + "expected_output", + message="What do you expect the result to look like? (ex: A 5 bullet point summary of the email)", + ), + inquirer.List( + "agent", + message="Which agent should be assigned this task?", + choices=[a['name'] for a in agents], + ), + ] + ) if not task['name'] or task['name'] == '': print(term_color("Error: Task name is required - Try again", 'red')) elif not is_snake_case(task['name']): - print(term_color("Error: Task name must be snake case - Try again", 'red')) + print( + term_color("Error: Task name must be snake case - Try again", 'red') + ) else: task_incomplete = False @@ -297,16 +343,18 @@ def ask_tools() -> list: tools_data = open_json_file(tools_json_path) while adding_tools: - tool_type = inquirer.list_input( message="What category tool do you want to add?", - choices=list(tools_data.keys()) + ["~~ Stop adding tools ~~"] + choices=list(tools_data.keys()) + ["~~ Stop adding tools ~~"], ) - tools_in_cat = [f"{t['name']} - {t['url']}" for t in tools_data[tool_type] if t not in tools_to_add] + tools_in_cat = [ + f"{t['name']} - {t['url']}" + for t in tools_data[tool_type] + if t not in tools_to_add + ] tool_selection = inquirer.list_input( - message="Select your tool", - choices=tools_in_cat + message="Select your tool", choices=tools_in_cat ) tools_to_add.append(tool_selection.split(' - ')[0]) @@ -321,42 +369,59 @@ def ask_tools() -> list: def ask_project_details(slug_name: Optional[str] = None) -> dict: - name = inquirer.text(message="What's the name of your project (snake_case)", default=slug_name or '') + name = inquirer.text( + message="What's the name of your project (snake_case)", default=slug_name or '' + ) if not is_snake_case(name): print(term_color("Project name must be snake case", 'red')) return ask_project_details(slug_name) - questions = inquirer.prompt([ - inquirer.Text("version", message="What's the initial version", default="0.1.0"), - inquirer.Text("description", message="Enter a description for your project"), - inquirer.Text("author", message="Who's the author (your name)?"), - ]) + questions = inquirer.prompt( + [ + inquirer.Text( + "version", message="What's the initial version", default="0.1.0" + ), + inquirer.Text( + "description", message="Enter a description for your project" + ), + inquirer.Text("author", message="Who's the author (your name)?"), + ] + ) questions['name'] = name return questions -def insert_template(project_details: dict, framework_name: str, design: dict, template_data: Optional[dict] = None): +def insert_template( + project_details: dict, + framework_name: str, + design: dict, + template_data: Optional[dict] = None, +): framework = FrameworkData(framework_name.lower()) - project_metadata = ProjectMetadata(project_name=project_details["name"], - description=project_details["description"], - author_name=project_details["author"], - version="0.0.1", - license="MIT", - year=datetime.now().year, - template=template_data['name'] if template_data else None, - template_version=template_data['template_version'] if template_data else None) + project_metadata = ProjectMetadata( + project_name=project_details["name"], + description=project_details["description"], + author_name=project_details["author"], + version="0.0.1", + license="MIT", + year=datetime.now().year, + template=template_data['name'] if template_data else None, + template_version=template_data['template_version'] if template_data else None, + ) project_structure = ProjectStructure() project_structure.agents = design["agents"] project_structure.tasks = design["tasks"] project_structure.set_inputs(design["inputs"]) - cookiecutter_data = CookiecutterData(project_metadata=project_metadata, - structure=project_structure, - framework=framework_name.lower()) + cookiecutter_data = CookiecutterData( + project_metadata=project_metadata, + structure=project_structure, + framework=framework_name.lower(), + ) template_path = get_package_path() / f'templates/{framework.name}' with open(f"{template_path}/cookiecutter.json", "w") as json_file: @@ -365,10 +430,16 @@ def insert_template(project_details: dict, framework_name: str, design: dict, te # copy .env.example to .env shutil.copy( f'{template_path}/{"{{cookiecutter.project_metadata.project_slug}}"}/.env.example', - f'{template_path}/{"{{cookiecutter.project_metadata.project_slug}}"}/.env') + f'{template_path}/{"{{cookiecutter.project_metadata.project_slug}}"}/.env', + ) if os.path.isdir(project_details['name']): - print(term_color(f"Directory {template_path} already exists. Please check this and try again", "red")) + print( + term_color( + f"Directory {template_path} already exists. Please check this and try again", + "red", + ) + ) return cookiecutter(str(template_path), no_input=True, extra_context=None) @@ -381,7 +452,9 @@ def insert_template(project_details: dict, framework_name: str, design: dict, te # subprocess.check_output(["git", "init"]) # subprocess.check_output(["git", "add", "."]) except: - print("Failed to initialize git repository. Maybe you're already in one? Do this with: git init") + print( + "Failed to initialize git repository. Maybe you're already in one? Do this with: git init" + ) # TODO: check if poetry is installed and if so, run poetry install in the new directory # os.system("poetry install") @@ -416,4 +489,4 @@ def list_tools(): print(f": {tool.url if tool.url else 'AgentStack default tool'}") print("\n\n✨ Add a tool with: agentstack tools add ") - print(" https://docs.agentstack.sh/tools/core") \ No newline at end of file + print(" https://docs.agentstack.sh/tools/core") diff --git a/agentstack/generation/tool_generation.py b/agentstack/generation/tool_generation.py index ba3d30aa..20930683 100644 --- a/agentstack/generation/tool_generation.py +++ b/agentstack/generation/tool_generation.py @@ -28,6 +28,7 @@ 'crewai': 'src/crew.py', } + def get_framework_filename(framework: str, path: str = ''): if path: path = path.endswith('/') and path or path + '/' @@ -39,6 +40,7 @@ def get_framework_filename(framework: str, path: str = ''): print(term_color(f'Unknown framework: {framework}', 'red')) sys.exit(1) + class ToolConfig(BaseModel): name: str category: str @@ -76,6 +78,7 @@ def get_import_statement(self) -> str: def get_impl_file_path(self, framework: str) -> Path: return get_package_path() / f'templates/{framework}/tools/{self.name}_tool.py' + def get_all_tool_paths() -> list[Path]: paths = [] tools_dir = get_package_path() / 'tools' @@ -84,13 +87,18 @@ def get_all_tool_paths() -> list[Path]: paths.append(file) return paths + def get_all_tool_names() -> list[str]: return [path.stem for path in get_all_tool_paths()] + def get_all_tools() -> list[ToolConfig]: return [ToolConfig.from_json(path) for path in get_all_tool_paths()] -def add_tool(tool_name: str, path: Optional[str] = None, agents: Optional[List[str]] = []): + +def add_tool( + tool_name: str, path: Optional[str] = None, agents: Optional[List[str]] = [] +): if path: path = path.endswith('/') and path or path + '/' else: @@ -108,11 +116,15 @@ def add_tool(tool_name: str, path: Optional[str] = None, agents: Optional[List[s if tool_data.packages: packaging.install(' '.join(tool_data.packages)) - shutil.copy(tool_file_path, f'{path}src/tools/{tool_name}_tool.py') # Move tool from package to project + shutil.copy( + tool_file_path, f'{path}src/tools/{tool_name}_tool.py' + ) # Move tool from package to project add_tool_to_tools_init(tool_data, path) # Export tool from tools dir - add_tool_to_agent_definition(framework=framework, tool_data=tool_data, path=path, agents=agents) # Add tool to agent definition + add_tool_to_agent_definition( + framework=framework, tool_data=tool_data, path=path, agents=agents + ) # Add tool to agent definition - if tool_data.env: # add environment variables which don't exist + if tool_data.env: # add environment variables which don't exist with EnvFile(path) as env: for var, value in tool_data.env.items(): env.append_if_new(var, value) @@ -126,7 +138,11 @@ def add_tool(tool_name: str, path: Optional[str] = None, agents: Optional[List[s with agentstack_config as config: config.tools.append(tool_name) - print(term_color(f'🔨 Tool {tool_name} added to agentstack project successfully', 'green')) + print( + term_color( + f'🔨 Tool {tool_name} added to agentstack project successfully', 'green' + ) + ) if tool_data.cta: print(term_color(f'🪩 {tool_data.cta}', 'blue')) @@ -160,13 +176,19 @@ def remove_tool(tool_name: str, path: Optional[str] = None): with agentstack_config as config: config.tools.remove(tool_name) - print(term_color(f'🔨 Tool {tool_name}', 'green'), term_color('removed', 'red'), term_color('from agentstack project successfully', 'green')) + print( + term_color(f'🔨 Tool {tool_name}', 'green'), + term_color('removed', 'red'), + term_color('from agentstack project successfully', 'green'), + ) def add_tool_to_tools_init(tool_data: ToolConfig, path: str = ''): file_path = f'{path}{TOOL_INIT_FILENAME}' tag = '# tool import' - code_to_insert = [tool_data.get_import_statement(), ] + code_to_insert = [ + tool_data.get_import_statement(), + ] insert_code_after_tag(file_path, tag, code_to_insert, next_line=True) @@ -180,65 +202,80 @@ def remove_tool_from_tools_init(tool_data: ToolConfig, path: str = ''): print(line, end='') -def add_tool_to_agent_definition(framework: str, tool_data: ToolConfig, path: str = '', agents: list[str] = []): +def add_tool_to_agent_definition( + framework: str, tool_data: ToolConfig, path: str = '', agents: list[str] = [] +): """ - Add tools to specific agent definitions using AST transformation. + Add tools to specific agent definitions using AST transformation. - Args: - framework: Name of the framework - tool_data: ToolConfig - agents: Optional list of agent names to modify. If None, modifies all agents. - path: Optional path to the framework file - """ - modify_agent_tools(framework=framework, tool_data=tool_data, operation='add', agents=agents, path=path, base_name='tools') + Args: + framework: Name of the framework + tool_data: ToolConfig + agents: Optional list of agent names to modify. If None, modifies all agents. + path: Optional path to the framework file + """ + modify_agent_tools( + framework=framework, + tool_data=tool_data, + operation='add', + agents=agents, + path=path, + base_name='tools', + ) -def remove_tool_from_agent_definition(framework: str, tool_data: ToolConfig, path: str = ''): - modify_agent_tools(framework=framework, tool_data=tool_data, operation='remove', agents=None, path=path, base_name='tools') +def remove_tool_from_agent_definition( + framework: str, tool_data: ToolConfig, path: str = '' +): + modify_agent_tools( + framework=framework, + tool_data=tool_data, + operation='remove', + agents=None, + path=path, + base_name='tools', + ) def _create_tool_attribute(tool_name: str, base_name: str = 'tools') -> ast.Attribute: """Create an AST node for a tool attribute""" return ast.Attribute( - value=ast.Name(id=base_name, ctx=ast.Load()), - attr=tool_name, - ctx=ast.Load() + value=ast.Name(id=base_name, ctx=ast.Load()), attr=tool_name, ctx=ast.Load() ) + def _create_starred_tool(tool_name: str, base_name: str = 'tools') -> ast.Starred: """Create an AST node for a starred tool expression""" return ast.Starred( value=ast.Attribute( - value=ast.Name(id=base_name, ctx=ast.Load()), - attr=tool_name, - ctx=ast.Load() + value=ast.Name(id=base_name, ctx=ast.Load()), attr=tool_name, ctx=ast.Load() ), - ctx=ast.Load() + ctx=ast.Load(), ) def _create_tool_attributes( - tool_names: List[str], - base_name: str = 'tools' + tool_names: List[str], base_name: str = 'tools' ) -> List[ast.Attribute]: """Create AST nodes for multiple tool attributes""" return [_create_tool_attribute(name, base_name) for name in tool_names] def _create_tool_nodes( - tool_names: List[str], - is_bundled: bool = False, - base_name: str = 'tools' + tool_names: List[str], is_bundled: bool = False, base_name: str = 'tools' ) -> List[Union[ast.Attribute, ast.Starred]]: """Create AST nodes for multiple tool attributes""" return [ - _create_starred_tool(name, base_name) if is_bundled + _create_starred_tool(name, base_name) + if is_bundled else _create_tool_attribute(name, base_name) for name in tool_names ] -def _is_tool_node_match(node: ast.AST, tool_name: str, base_name: str = 'tools') -> bool: +def _is_tool_node_match( + node: ast.AST, tool_name: str, base_name: str = 'tools' +) -> bool: """ Check if an AST node matches a tool reference, regardless of whether it's starred @@ -256,8 +293,7 @@ def _is_tool_node_match(node: ast.AST, tool_name: str, base_name: str = 'tools') # Extract the attribute name and base regardless of node type if isinstance(node, ast.Attribute): - is_base_match = (isinstance(node.value, ast.Name) and - node.value.id == base_name) + is_base_match = isinstance(node.value, ast.Name) and node.value.id == base_name is_name_match = node.attr == tool_name return is_base_match and is_name_match @@ -265,10 +301,10 @@ def _is_tool_node_match(node: ast.AST, tool_name: str, base_name: str = 'tools') def _process_tools_list( - current_tools: List[ast.AST], - tool_data: ToolConfig, - operation: str, - base_name: str = 'tools' + current_tools: List[ast.AST], + tool_data: ToolConfig, + operation: str, + base_name: str = 'tools', ) -> List[ast.AST]: """ Process a tools list according to the specified operation. @@ -282,30 +318,30 @@ def _process_tools_list( if operation == 'add': new_tools = current_tools.copy() # Add new tools with bundling if specified - new_tools.extend(_create_tool_nodes( - tool_data.tools, - tool_data.tools_bundled, - base_name - )) + new_tools.extend( + _create_tool_nodes(tool_data.tools, tool_data.tools_bundled, base_name) + ) return new_tools elif operation == 'remove': # Filter out tools that match any in the removal list return [ - tool for tool in current_tools - if not any(_is_tool_node_match(tool, name, base_name) - for name in tool_data.tools) + tool + for tool in current_tools + if not any( + _is_tool_node_match(tool, name, base_name) for name in tool_data.tools + ) ] raise ValueError(f"Unsupported operation: {operation}") def _modify_agent_tools( - node: ast.FunctionDef, - tool_data: ToolConfig, - operation: str, - agents: Optional[List[str]] = None, - base_name: str = 'tools' + node: ast.FunctionDef, + tool_data: ToolConfig, + operation: str, + agents: Optional[List[str]] = None, + base_name: str = 'tools', ) -> ast.FunctionDef: """ Modify the tools list in an agent definition. @@ -323,8 +359,9 @@ def _modify_agent_tools( return node # Check if this is an agent-decorated function - if not any(isinstance(d, ast.Name) and d.id == 'agent' - for d in node.decorator_list): + if not any( + isinstance(d, ast.Name) and d.id == 'agent' for d in node.decorator_list + ): return node # Find the Return statement and modify tools @@ -337,10 +374,7 @@ def _modify_agent_tools( if isinstance(kw.value, ast.List): # Process the tools list new_tools = _process_tools_list( - kw.value.elts, - tool_data, - operation, - base_name + kw.value.elts, tool_data, operation, base_name ) # Replace with new list @@ -350,12 +384,12 @@ def _modify_agent_tools( def modify_agent_tools( - framework: str, - tool_data: ToolConfig, - operation: str, - agents: Optional[List[str]] = None, - path: str = '', - base_name: str = 'tools' + framework: str, + tool_data: ToolConfig, + operation: str, + agents: Optional[List[str]] = None, + path: str = '', + base_name: str = 'tools', ) -> None: """ Modify tools in agent definitions using AST transformation. @@ -405,4 +439,4 @@ def visit_FunctionDef(self, node): final_lines.append(line + '\n') with open(filename, 'w', encoding='utf-8') as f: - f.write(''.join(final_lines)) \ No newline at end of file + f.write(''.join(final_lines)) diff --git a/agentstack/main.py b/agentstack/main.py index 7b58f640..2c32bd20 100644 --- a/agentstack/main.py +++ b/agentstack/main.py @@ -154,7 +154,7 @@ def main(): else: tools_parser.print_help() elif args.command in ['update', 'u']: - pass # Update check already done + pass # Update check already done else: parser.print_help() diff --git a/agentstack/packaging.py b/agentstack/packaging.py index 3e8a6adb..fb0e3cb5 100644 --- a/agentstack/packaging.py +++ b/agentstack/packaging.py @@ -3,13 +3,16 @@ PACKAGING_CMD = "poetry" + def install(package: str, path: Optional[str] = None): if path: os.chdir(path) os.system(f"{PACKAGING_CMD} add {package}") + def remove(package: str): os.system(f"{PACKAGING_CMD} remove {package}") + def upgrade(package: str): os.system(f"{PACKAGING_CMD} add {package}") diff --git a/agentstack/update.py b/agentstack/update.py index 1b1bd55d..041d84ec 100644 --- a/agentstack/update.py +++ b/agentstack/update.py @@ -20,7 +20,7 @@ def _is_ci_environment(): 'TRAVIS', 'CIRCLECI', 'JENKINS_URL', - 'TEAMCITY_VERSION' + 'TEAMCITY_VERSION', ] return any(os.getenv(var) for var in ci_env_vars) @@ -45,7 +45,11 @@ def _is_ci_environment(): def get_latest_version(package: str) -> Version: """Get version information from PyPi to save a full package manager invocation""" import requests # defer import until we know we need it - response = requests.get(f"{ENDPOINT_URL}/{package}/", headers={"Accept": "application/vnd.pypi.simple.v1+json"}) + + response = requests.get( + f"{ENDPOINT_URL}/{package}/", + headers={"Accept": "application/vnd.pypi.simple.v1+json"}, + ) if response.status_code != 200: raise Exception(f"Failed to fetch package data from pypi.") data = response.json() @@ -116,14 +120,25 @@ def check_for_updates(update_requested: bool = False): installed_version: Version = parse_version(get_version(AGENTSTACK_PACKAGE)) if latest_version > installed_version: print('') # newline - if inquirer.confirm(f"New version of {AGENTSTACK_PACKAGE} available: {latest_version}! Do you want to install?"): + if inquirer.confirm( + f"New version of {AGENTSTACK_PACKAGE} available: {latest_version}! Do you want to install?" + ): packaging.upgrade(f'{AGENTSTACK_PACKAGE}[{get_framework()}]') - print(term_color(f"{AGENTSTACK_PACKAGE} updated. Re-run your command to use the latest version.", 'green')) + print( + term_color( + f"{AGENTSTACK_PACKAGE} updated. Re-run your command to use the latest version.", + 'green', + ) + ) sys.exit(0) else: - print(term_color("Skipping update. Run `agentstack update` to install the latest version.", 'blue')) + print( + term_color( + "Skipping update. Run `agentstack update` to install the latest version.", + 'blue', + ) + ) else: print(f"{AGENTSTACK_PACKAGE} is up to date ({installed_version})") record_update_check() - diff --git a/agentstack/utils.py b/agentstack/utils.py index 0d1bcea9..c6e7b274 100644 --- a/agentstack/utils.py +++ b/agentstack/utils.py @@ -8,6 +8,7 @@ from pathlib import Path import importlib.resources + def get_version(package: str = 'agentstack'): try: return version(package) @@ -18,12 +19,15 @@ def get_version(package: str = 'agentstack'): def verify_agentstack_project(path: Optional[str] = None): from agentstack.generation import ConfigFile + try: agentstack_config = ConfigFile(path) except FileNotFoundError: - print("\033[31mAgentStack Error: This does not appear to be an AgentStack project." - "\nPlease ensure you're at the root directory of your project and a file named agentstack.json exists. " - "If you're starting a new project, run `agentstack init`\033[0m") + print( + "\033[31mAgentStack Error: This does not appear to be an AgentStack project." + "\nPlease ensure you're at the root directory of your project and a file named agentstack.json exists. " + "If you're starting a new project, run `agentstack init`\033[0m" + ) sys.exit(1) @@ -36,6 +40,7 @@ def get_package_path() -> Path: def get_framework(path: Optional[str] = None) -> str: from agentstack.generation import ConfigFile + try: agentstack_config = ConfigFile(path) framework = agentstack_config.framework @@ -45,19 +50,25 @@ def get_framework(path: Optional[str] = None) -> str: return framework except FileNotFoundError: - print("\033[31mFile agentstack.json does not exist. Are you in the right directory?\033[0m") + print( + "\033[31mFile agentstack.json does not exist. Are you in the right directory?\033[0m" + ) sys.exit(1) def get_telemetry_opt_out(path: Optional[str] = None) -> str: from agentstack.generation import ConfigFile + try: agentstack_config = ConfigFile(path) return bool(agentstack_config.telemetry_opt_out) except FileNotFoundError: - print("\033[31mFile agentstack.json does not exist. Are you in the right directory?\033[0m") + print( + "\033[31mFile agentstack.json does not exist. Are you in the right directory?\033[0m" + ) sys.exit(1) + def camel_to_snake(name): s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() @@ -75,7 +86,12 @@ def open_json_file(path) -> dict: def clean_input(input_string): special_char_pattern = re.compile(r'[^a-zA-Z0-9\s_]') - return re.sub(special_char_pattern, '', input_string).lower().replace(' ', '_').replace('-', '_') + return ( + re.sub(special_char_pattern, '', input_string) + .lower() + .replace(' ', '_') + .replace('-', '_') + ) def term_color(text: str, color: str) -> str: @@ -86,7 +102,7 @@ def term_color(text: str, color: str) -> str: 'blue': '94', 'purple': '95', 'cyan': '96', - 'white': '97' + 'white': '97', } color_code = colors.get(color) if color_code: @@ -95,7 +111,5 @@ def term_color(text: str, color: str) -> str: return text - def is_snake_case(string: str): return bool(re.match('^[a-z0-9_]+$', string)) - From d5d0aa21e9f9cf1e7e9aba66fa1f61367452e937 Mon Sep 17 00:00:00 2001 From: Travis Dent Date: Thu, 5 Dec 2024 10:21:26 -0800 Subject: [PATCH 8/9] Add `dev` to optional dependencies, add `ruff` to dev dependencies --- pyproject.toml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index b3faa161..b7dcb59f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,13 +30,23 @@ dependencies = [ ] [project.optional-dependencies] +dev = [ + "ruff", +] test = [ "tox>=4.23.2", ] crewai = [ +<<<<<<< Updated upstream "crewai>=0.83.0", "crewai-tools>=0.14.0" ] +======= + "crewai==0.83.0", + "crewai-tools==0.14.0", +] + +>>>>>>> Stashed changes [tool.setuptools.package-data] agentstack = ["templates/**/*"] From e86f55b269a5972172e4f862ce6382c0841b88d5 Mon Sep 17 00:00:00 2001 From: Travis Dent Date: Thu, 5 Dec 2024 10:22:51 -0800 Subject: [PATCH 9/9] Merge cruft --- pyproject.toml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b7dcb59f..d97570b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,16 +37,10 @@ test = [ "tox>=4.23.2", ] crewai = [ -<<<<<<< Updated upstream - "crewai>=0.83.0", - "crewai-tools>=0.14.0" -] -======= "crewai==0.83.0", "crewai-tools==0.14.0", ] ->>>>>>> Stashed changes [tool.setuptools.package-data] agentstack = ["templates/**/*"]