From 0c0a0c44dfc4b8a44b668e238ad083451b3c0e72 Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Tue, 29 Oct 2024 17:24:19 -0700 Subject: [PATCH 01/18] 0.2 plans --- 0.2-changes.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 0.2-changes.md diff --git a/0.2-changes.md b/0.2-changes.md new file mode 100644 index 00000000..89fe0a51 --- /dev/null +++ b/0.2-changes.md @@ -0,0 +1,63 @@ +# 0.2 Roadmap + +# Concepts +- Support only CrewAI for now +- Make the process of starting an agent project as easy as possible + +# How do we get there +- Understand current agent design +- What are the use cases? How can they be categorized? + +# CLI + +## Templates +Are the templates part of cookiecutter, then structure is generated using the code-gen scripts? +Or do we manage a collection of independent cookiecutter templates. + +The options: + +### Codegen +This strategy involves using the existing and new codegen scripts to take the base project template and systematically +build off of it. + +This would require a templating structure that can handle complex agent designs. + +Pros: +- More easily extensible +- Helps prevent some templates lagging behind in updates + +Cons: +- More complex design. Requires more work on codegen and is a more difficult pattern to understand +- Contributions will be more challenging +- There may be some templates that this design may not account for + +### Cookiecutter +With this strategy, every template would be its own Cookiecutter template. + +Pros: +- Much simpler to understand +- No bounds on complexity or variation between templates + +Cons: +- Tech debt will easily grow + - Every update will require a change to every single template + - Requires robust processes for keeping every template updated + +## Decision +We'll move forward with the codegen method unless other information comes to light. + + +### `agentstack i ` +- Flags + - `--in-place` + - dont place into a subdirectory + - `--template ` + - generate the project according to a template + +### `agentstack tools` +- Stays the same + +### `agentstack generate` +- `agent` + - `--template ` +- From c36a9452af388a8169888171d67643b149f2a0fd Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Wed, 30 Oct 2024 11:36:36 -0700 Subject: [PATCH 02/18] remove wizard steps --- agentstack/cli/cli.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/agentstack/cli/cli.py b/agentstack/cli/cli.py index ea600d4e..81cca45d 100644 --- a/agentstack/cli/cli.py +++ b/agentstack/cli/cli.py @@ -7,7 +7,6 @@ from art import text2art import inquirer import os -import webbrowser import importlib.resources from cookiecutter.main import cookiecutter @@ -21,13 +20,13 @@ def init_project_builder(slug_name: Optional[str] = None, skip_wizard: bool = Fa if skip_wizard: project_details = { "name": slug_name or "new_agentstack_project", - "version": "0.1.0", + "version": "0.0.1", "description": "New agentstack project", - "author": "", + "author": "Name ", "license": "MIT" } - framework = "CrewAI" # TODO: if --no-wizard, require a framework flag + framework = "CrewAI" design = { 'agents': [], @@ -235,7 +234,7 @@ def insert_template(project_details: dict, framework_name: str, design: dict): project_metadata = ProjectMetadata(project_name=project_details["name"], description=project_details["description"], author_name=project_details["author"], - version=project_details["version"], + version="0.0.1", license="MIT", year=datetime.now().year) From 7f70f5e87c4c08207d3dcbd8c130f0fa852a026b Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Wed, 30 Oct 2024 15:30:21 -0700 Subject: [PATCH 03/18] default no wizard --- agentstack/cli/cli.py | 22 ++++++++++++---------- agentstack/main.py | 4 ++-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/agentstack/cli/cli.py b/agentstack/cli/cli.py index 81cca45d..d0cb3156 100644 --- a/agentstack/cli/cli.py +++ b/agentstack/cli/cli.py @@ -16,10 +16,19 @@ from ..utils import open_json_file, term_color -def init_project_builder(slug_name: Optional[str] = None, skip_wizard: bool = False): - if skip_wizard: +def init_project_builder(slug_name: Optional[str] = None, use_wizard: bool = False): + if use_wizard: + welcome_message() + project_details = ask_project_details(slug_name) + welcome_message() + framework = ask_framework() + design = ask_design() + tools = ask_tools() + + else: + welcome_message() project_details = { - "name": slug_name or "new_agentstack_project", + "name": slug_name or "agentstack_project", "version": "0.0.1", "description": "New agentstack project", "author": "Name ", @@ -34,13 +43,6 @@ def init_project_builder(slug_name: Optional[str] = None, skip_wizard: bool = Fa } tools = [] - else: - welcome_message() - project_details = ask_project_details(slug_name) - welcome_message() - framework = ask_framework() - design = ask_design() - tools = ask_tools() log.debug( f"project_details: {project_details}" diff --git a/agentstack/main.py b/agentstack/main.py index 8f86d144..1a1b3d9f 100644 --- a/agentstack/main.py +++ b/agentstack/main.py @@ -19,7 +19,7 @@ def main(): # '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('--no-wizard', action='store_true', help="Skip wizard steps") + init_parser.add_argument('--wizard', '-w', action='store_true', help="Use the setup wizard") # 'generate' command generate_parser = subparsers.add_parser('generate', aliases=['g'], help='Generate agents or tasks') @@ -65,7 +65,7 @@ def main(): # Handle commands if args.command in ['init', 'i']: - init_project_builder(args.slug_name, args.no_wizard) + init_project_builder(args.slug_name, args.wizard) elif args.command in ['generate', 'g']: if args.generate_command in ['agent', 'a']: generation.generate_agent(args.name, args.role, args.goal, args.backstory, args.llm) From f4b43ae7a9bda987d8098c4d835d5f8f8a41b6ae Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Wed, 30 Oct 2024 16:23:18 -0700 Subject: [PATCH 04/18] start template flags --- agentstack/cli/agentstack_data.py | 4 +++- agentstack/main.py | 1 + .../agentstack.json | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/agentstack/cli/agentstack_data.py b/agentstack/cli/agentstack_data.py index 1134dc4d..028532ea 100644 --- a/agentstack/cli/agentstack_data.py +++ b/agentstack/cli/agentstack_data.py @@ -2,7 +2,7 @@ from datetime import datetime from typing import Optional, Literal -from agentstack.utils import clean_input +from agentstack.utils import clean_input, get_version from agentstack.logger import log @@ -23,6 +23,7 @@ def __init__(self, self.version = version self.license = license self.year = year + self.agentstack_version = get_version() log.debug(f"ProjectMetadata: {self.to_dict()}") def to_dict(self): @@ -34,6 +35,7 @@ def to_dict(self): 'version': self.version, 'license': self.license, 'year': self.year, + 'agentstack_version': self.agentstack_version, } def to_json(self): diff --git a/agentstack/main.py b/agentstack/main.py index 1a1b3d9f..8a5770a6 100644 --- a/agentstack/main.py +++ b/agentstack/main.py @@ -20,6 +20,7 @@ def main(): 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") # 'generate' command generate_parser = subparsers.add_parser('generate', aliases=['g'], help='Generate agents or tasks') diff --git a/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/agentstack.json b/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/agentstack.json index ed01b193..4c4c63ec 100644 --- a/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/agentstack.json +++ b/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/agentstack.json @@ -1,3 +1,4 @@ { - "framework": "{{ cookiecutter.framework }}" + "framework": "{{ cookiecutter.framework }}", + "agentstack_version": "{{ cookiecutter.agentstack_version }}" } \ No newline at end of file From 270ebd4716157718d0feb6d41b722b6bc25ef27b Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Mon, 4 Nov 2024 14:32:49 -0800 Subject: [PATCH 05/18] start template scaffolding --- agentstack/cli/agentstack_data.py | 5 ++++- agentstack/projects_templates/chatbot.json | 16 ++++++++++++++++ .../agentstack.json | 3 ++- .../src/main.py | 6 +++--- 4 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 agentstack/projects_templates/chatbot.json diff --git a/agentstack/cli/agentstack_data.py b/agentstack/cli/agentstack_data.py index 028532ea..033a1f0e 100644 --- a/agentstack/cli/agentstack_data.py +++ b/agentstack/cli/agentstack_data.py @@ -14,7 +14,8 @@ def __init__(self, author_name: str = "", version: str = "", license: str = "", - year: int = datetime.now().year + year: int = datetime.now().year, + template: str = "default" ): 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 @@ -24,6 +25,8 @@ def __init__(self, self.license = license self.year = year self.agentstack_version = get_version() + self.template = template + log.debug(f"ProjectMetadata: {self.to_dict()}") def to_dict(self): diff --git a/agentstack/projects_templates/chatbot.json b/agentstack/projects_templates/chatbot.json new file mode 100644 index 00000000..1deef694 --- /dev/null +++ b/agentstack/projects_templates/chatbot.json @@ -0,0 +1,16 @@ +{ + "name": "chatbot", + "tools": ["mem0"], + "agents": [{ + + }], + "tasks": [{ + + }], + "code_swap": [ + { + "direction": "out", + "file": + } + ] +} \ No newline at end of file diff --git a/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/agentstack.json b/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/agentstack.json index 4c4c63ec..6296e2ad 100644 --- a/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/agentstack.json +++ b/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/agentstack.json @@ -1,4 +1,5 @@ { "framework": "{{ cookiecutter.framework }}", - "agentstack_version": "{{ cookiecutter.agentstack_version }}" + "agentstack_version": "{{ cookiecutter.agentstack_version }}", + "template": "{{ cookiecutter.template }}" } \ No newline at end of file diff --git a/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/src/main.py b/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/src/main.py index 6dba77f7..ea849b3c 100644 --- a/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/src/main.py +++ b/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/src/main.py @@ -15,7 +15,7 @@ def run(): Run the crew. """ inputs = { - 'topic': 'AI LLMs' + 'key': 'value' } {{cookiecutter.project_metadata.project_name|replace('-', '')|replace('_', '')|capitalize}}Crew().crew().kickoff(inputs=inputs) @@ -25,7 +25,7 @@ def train(): Train the crew for a given number of iterations. """ inputs = { - "topic": "AI LLMs" + 'key': 'value' } try: {{cookiecutter.project_metadata.project_name|replace('-', '')|replace('_', '')|capitalize}}Crew().crew().train(n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs) @@ -50,7 +50,7 @@ def test(): Test the crew execution and returns the results. """ inputs = { - "topic": "AI LLMs" + 'key': 'value' } try: {{cookiecutter.project_metadata.project_name|replace('-', '')|replace('_', '')|capitalize}}Crew().crew().test(n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs) From 3ba9c05aa9eabc2b6d90899a6c3951ad5d49c9e5 Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Mon, 11 Nov 2024 12:24:46 -0800 Subject: [PATCH 06/18] check if poetry add works before adding tool --- agentstack/generation/tool_generation.py | 4 +++- pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/agentstack/generation/tool_generation.py b/agentstack/generation/tool_generation.py index c50c48b8..9f77022e 100644 --- a/agentstack/generation/tool_generation.py +++ b/agentstack/generation/tool_generation.py @@ -20,7 +20,9 @@ def add_tool(tool_name: str, path: Optional[str] = None): tool_data = open_json_file(tool_data_path) with importlib.resources.path(f'agentstack.templates.{framework}.tools', f"{tool_name}_tool.py") as tool_file_path: - os.system(tool_data['package']) # Install package + if os.system(tool_data['package']) == 1: # Install package + print(term_color("AgentStack: Failed to install tool requirements. Please resolve dependency issues and try again,", 'red')) + return shutil.copy(tool_file_path, f'{path + "/" if path else ""}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, tool_data, path) diff --git a/pyproject.toml b/pyproject.toml index a039f45d..0cf7d0e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "agentstack" -version = "0.1.8" +version = "0.2.0" description = "The fastest way to build robust AI agents" authors = [ { name="Braelyn Boynton", email="bboynton97@gmail.com" } From 1e95ad25266e3ba6f734503e6c797ba5b159e59c Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Tue, 12 Nov 2024 14:07:04 -0800 Subject: [PATCH 07/18] use openai by default --- agentstack/generation/agent_generation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agentstack/generation/agent_generation.py b/agentstack/generation/agent_generation.py index 5f97138c..ef5cd2fb 100644 --- a/agentstack/generation/agent_generation.py +++ b/agentstack/generation/agent_generation.py @@ -21,7 +21,7 @@ def generate_agent( if not backstory: backstory = 'Add your backstory here' if not llm: - llm = 'Add your llm here with format provider/model' + llm = 'openai/gpt-4o' verify_agentstack_project() @@ -45,7 +45,7 @@ def generate_crew_agent( role: Optional[str] = 'Add your role here', goal: Optional[str] = 'Add your goal here', backstory: Optional[str] = 'Add your backstory here', - llm: Optional[str] = 'Add your llm here with format provider/model' + llm: Optional[str] = 'openai/gpt-4o' ): config_path = os.path.join('src', 'config', 'agents.yaml') From 104c702e8990b2e20b119737487bcaed812d459f Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Wed, 13 Nov 2024 08:00:38 -0800 Subject: [PATCH 08/18] started research template --- 0.2-changes.md | 21 +++++++++++ agentstack/cli/cli.py | 2 +- agentstack/main.py | 7 +++- .../templates/proj_templates/research.json | 36 +++++++++++++++++++ 4 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 agentstack/templates/proj_templates/research.json diff --git a/0.2-changes.md b/0.2-changes.md index 89fe0a51..cd76fc59 100644 --- a/0.2-changes.md +++ b/0.2-changes.md @@ -10,6 +10,27 @@ # CLI +## Template Types + +### Chatbot +- Conversational +- Customer Support + +### Research Agent +- RAG +- Search +- Answers question + +### Creative Agent +- Coding Agent +- Writer Agent +- Image Gen + +### Copilot +- Understand a problem space +- Support decision-making +- Predict next steps + ## Templates Are the templates part of cookiecutter, then structure is generated using the code-gen scripts? Or do we manage a collection of independent cookiecutter templates. diff --git a/agentstack/cli/cli.py b/agentstack/cli/cli.py index 25f32d0b..022a7a55 100644 --- a/agentstack/cli/cli.py +++ b/agentstack/cli/cli.py @@ -16,7 +16,7 @@ from ..utils import open_json_file, term_color, is_snake_case -def init_project_builder(slug_name: 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 diff --git a/agentstack/main.py b/agentstack/main.py index 3ebc29bd..f4efdd86 100644 --- a/agentstack/main.py +++ b/agentstack/main.py @@ -23,6 +23,9 @@ def main(): # 'quickstart' command subparsers.add_parser('quickstart', help='Open the quickstart guide') + # 'templates' command + 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") @@ -76,8 +79,10 @@ def main(): webbrowser.open('https://docs.agentstack.sh/') if args.command in ['quickstart']: webbrowser.open('https://docs.agentstack.sh/quickstart') + if args.command in ['templates']: + webbrowser.open('https://docs.agentstack.sh/quickstart') if args.command in ['init', 'i']: - init_project_builder(args.slug_name, args.wizard) + init_project_builder(args.slug_name, args.framework, args.wizard) elif args.command in ['generate', 'g']: if args.generate_command in ['agent', 'a']: generation.generate_agent(args.name, args.role, args.goal, args.backstory, args.llm) diff --git a/agentstack/templates/proj_templates/research.json b/agentstack/templates/proj_templates/research.json new file mode 100644 index 00000000..15c7a7cf --- /dev/null +++ b/agentstack/templates/proj_templates/research.json @@ -0,0 +1,36 @@ +{ + "name": "research", + "agents": [{ + "name": "researcher", + "prompt": { + "role": "Gather data using research tools.", + "goal": "Collect all relevant information asked for using the tools available to you. The data will be analyzed later, compile a result of all data that you believe to be relevant to the query.", + "backstory": "You are an expert researcher. You are given a query and are tasked with providing as much relevant data in a concise manner." + }, + "llm": "openai/gpt-4o" + },{ + "name": "analyst", + "prompt": { + "role": "Analyze gathered data.", + "goal": "Analyze and consolidate the data gathered from research to adequately answer the query provided in the task.", + "backstory": "You are an expert analyst. You are given a collection of research results and should use your knowledge to make conclusions on the data without making any assumptions that are not specifically supported by the data." + }, + "llm": "openai/gpt-4o" + }], + "tasks": [{ + "name": "research", + "description": "use the tools at your disposal to thoroughly research the query: {query}", + "expected_output": "text output describing what what information was discovered, with labels defining where it was found. only output content found directly through research, do not modify those results in any way.", + "agent": "researcher" + },{ + "name": "analyze", + "description": "Using the data gathered by the researcher", + "expected_output": "", + "agent": "analyst" + }], + "tools": [{ + "name": "", + "agents": [""] + }], + "method": "sequential" +} \ No newline at end of file From 978ca717667641700b9c895e21b535e7d439ecd3 Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Wed, 13 Nov 2024 20:01:55 -0800 Subject: [PATCH 09/18] finish guide --- agentstack/templates/proj_templates/research.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/agentstack/templates/proj_templates/research.json b/agentstack/templates/proj_templates/research.json index 15c7a7cf..3411042e 100644 --- a/agentstack/templates/proj_templates/research.json +++ b/agentstack/templates/proj_templates/research.json @@ -20,17 +20,17 @@ "tasks": [{ "name": "research", "description": "use the tools at your disposal to thoroughly research the query: {query}", - "expected_output": "text output describing what what information was discovered, with labels defining where it was found. only output content found directly through research, do not modify those results in any way.", + "expected_output": "text output describing what what information was discovered, with labels defining where it was found. only output content found directly through research, do not modify those results in any way. Cite sources in the result.", "agent": "researcher" },{ "name": "analyze", - "description": "Using the data gathered by the researcher", - "expected_output": "", + "description": "Using the data gathered by the researcher, analyze the data and form a conclusion that reports key insights on that data pertaining to the query: {query}", + "expected_output": "A report in markdown format outlining the key insights of the research and accurately answering the query.", "agent": "analyst" }], "tools": [{ - "name": "", - "agents": [""] + "name": "perplexity", + "agents": ["researcher"] }], "method": "sequential" } \ No newline at end of file From ee261c08014ca5ea6a01638ca3423f67f82a36bf Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Tue, 26 Nov 2024 15:07:35 -0800 Subject: [PATCH 10/18] formating --- agentstack/generation/tool_generation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/agentstack/generation/tool_generation.py b/agentstack/generation/tool_generation.py index 05c94fd0..3cd7a49a 100644 --- a/agentstack/generation/tool_generation.py +++ b/agentstack/generation/tool_generation.py @@ -31,7 +31,8 @@ def add_tool(tool_name: str, path: Optional[str] = None): with importlib.resources.path(f'agentstack.tools', f"{tool_name}.json") as tool_data_path: tool_data = open_json_file(tool_data_path) - with importlib.resources.path(f'agentstack.templates.{framework}.tools', f"{tool_name}_tool.py") as tool_file_path: + with importlib.resources.path(f'agentstack.templates.{framework}.tools', + f"{tool_name}_tool.py") as tool_file_path: if tool_data.get('packages'): if os.system(f"poetry add {' '.join(tool_data['packages'])}") == 1: # Install packages print(term_color("AgentStack: Failed to install tool requirements. Please resolve dependency issues and try again,", 'red')) From a00469ec06acaddb8cbcd268d787ba9af33f3c5f Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Tue, 26 Nov 2024 21:52:07 -0800 Subject: [PATCH 11/18] template --- agentstack/templates/proj_templates/research.json | 1 + 1 file changed, 1 insertion(+) diff --git a/agentstack/templates/proj_templates/research.json b/agentstack/templates/proj_templates/research.json index 3411042e..b6420b87 100644 --- a/agentstack/templates/proj_templates/research.json +++ b/agentstack/templates/proj_templates/research.json @@ -1,5 +1,6 @@ { "name": "research", + "description": "Starter project research agent", "agents": [{ "name": "researcher", "prompt": { From f3391ee6dc1223c64637a13170d88d09bff1a457 Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Tue, 26 Nov 2024 23:47:08 -0800 Subject: [PATCH 12/18] install and remove tools by agent and use AST --- agentstack/cli/cli.py | 55 ++++- agentstack/generation/tool_generation.py | 198 ++++++++++++++++-- agentstack/main.py | 6 +- agentstack/telemetry.py | 2 +- .../proj_templates}/chatbot.json | 0 .../templates/proj_templates/research.json | 1 + pyproject.toml | 3 +- 7 files changed, 242 insertions(+), 23 deletions(-) rename agentstack/{projects_templates => templates/proj_templates}/chatbot.json (100%) diff --git a/agentstack/cli/cli.py b/agentstack/cli/cli.py index aef114a4..f7c37676 100644 --- a/agentstack/cli/cli.py +++ b/agentstack/cli/cli.py @@ -1,8 +1,10 @@ import json import shutil +import sys import time from datetime import datetime from typing import Optional +import requests from art import text2art import inquirer @@ -21,13 +23,52 @@ def init_project_builder(slug_name: Optional[str] = None, template: Optional[str print(term_color("Project name must be snake case", 'red')) return - if use_wizard: + if template is not None and not use_wizard: + 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: + # 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')) + sys.exit(1) + else: + with importlib.resources.path('agentstack.templates.proj_templates', template) as template_path: + if template_path is None: + 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'], + "version": "0.0.1", + "description": template_data['description'], + "author": "Name ", + "license": "MIT" + } + framework = template_data['framework'] + design = { + 'agents': template_data['agents'], + 'tasks': template_data['tasks'] + } + for tool_data in template_data['tools']: + generation.add_tool(tool_data['name'], agents=tool_data['agents'], path=project_details['name']) + + + elif use_wizard: welcome_message() project_details = ask_project_details(slug_name) welcome_message() framework = ask_framework() design = ask_design() - tools = ask_tools() + # tools = ask_tools() else: welcome_message() @@ -46,7 +87,7 @@ def init_project_builder(slug_name: Optional[str] = None, template: Optional[str 'tasks': [] } - tools = [] + # tools = [] log.debug( f"project_details: {project_details}" @@ -54,7 +95,7 @@ def init_project_builder(slug_name: Optional[str] = None, template: Optional[str f"design: {design}" ) insert_template(project_details, framework, design) - add_tools(tools, project_details['name']) + # add_tools(tools, project_details['name']) def welcome_message(): @@ -316,9 +357,9 @@ def insert_template(project_details: dict, framework_name: str, design: dict): ) -def add_tools(tools: list, project_name: str): - for tool in tools: - generation.add_tool(tool, project_name) +# def add_tools(tools: list, project_name: str): +# for tool in tools: +# generation.add_tool(tool, path=project_name) def list_tools(): diff --git a/agentstack/generation/tool_generation.py b/agentstack/generation/tool_generation.py index 3cd7a49a..c98b4957 100644 --- a/agentstack/generation/tool_generation.py +++ b/agentstack/generation/tool_generation.py @@ -1,19 +1,21 @@ import importlib.resources import json import sys -from typing import Optional +from typing import Optional, List, Dict from .gen_utils import insert_code_after_tag, string_in_file from ..utils import open_json_file, get_framework, term_color import os import shutil import fileinput +import astor +import ast TOOL_INIT_FILENAME = "src/tools/__init__.py" AGENTSTACK_JSON_FILENAME = "agentstack.json" -def add_tool(tool_name: str, path: Optional[str] = None): +def add_tool(tool_name: str, path: Optional[str] = None, agents: Optional[List[str]] = []): if path: path = path.endswith('/') and path or path + '/' else: @@ -39,7 +41,7 @@ def add_tool(tool_name: str, path: Optional[str] = None): return 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, tool_data, path) # Add tool to agent definition + add_tool_to_agent_definition(framework, tool_data, path, agents) # Add tool to agent definition if tool_data.get('env'): # if the env vars aren't in the .env files, add them first_var_name = tool_data['env'].split('=')[0] if not string_in_file(f'{path}.env', first_var_name): @@ -119,18 +121,25 @@ def _framework_filename(framework: str, path: str = ''): sys.exit(1) -def add_tool_to_agent_definition(framework: str, tool_data: dict, path: str = ''): - filename = _framework_filename(framework, path) - with fileinput.input(files=filename, inplace=True) as f: - for line in f: - print(line.replace('tools=[', f'tools=[{"*" if tool_data.get("tools_bundled") else ""}tools.{", tools.".join([tool_name for tool_name in tool_data["tools"]])}, '), end='') +def add_tool_to_agent_definition(framework: str, tool_data: dict, path: str = '', agents: list[str] = []): + """ + Add tools to specific agent definitions using AST transformation. + + Args: + framework: Name of the framework + tool_data: Dictionary containing tool information + { + "tools": List[str], # List of tool names to add + "tools_bundled": bool # Whether to include tools.* + } + agents: Optional list of agent names to modify. If None, modifies all agents. + path: Optional path to the framework file + """ + modify_agent_tools(framework, tool_data, 'add', agents, path, 'tools') def remove_tool_from_agent_definition(framework: str, tool_data: dict, path: str = ''): - filename = _framework_filename(framework, path) - with fileinput.input(files=filename, inplace=True) as f: - for line in f: - print(line.replace(f'{", ".join([f"tools.{tool_name}" for tool_name in tool_data["tools"]])}, ', ''), end='') + modify_agent_tools(framework, tool_data, 'remove', None, path, 'tools') def assert_tool_exists(tool_name: str, tools: dict): @@ -142,3 +151,168 @@ def assert_tool_exists(tool_name: str, tools: dict): print(term_color(f'No known agentstack tool: {tool_name}', 'red')) sys.exit(1) + +def _create_tool_attribute(tool_name: str, base_name: str = 'tools') -> ast.Attribute: + """ + Create an AST node for a tool attribute. + + Args: + tool_name: Name of the tool + base_name: Base module name (default: 'tools') + """ + return ast.Attribute( + value=ast.Name(id=base_name, ctx=ast.Load()), + attr=tool_name, + ctx=ast.Load() + ) + + +def _create_tool_attributes( + 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 _is_tool_node_match(node: ast.AST, tool_name: str, base_name: str = 'tools') -> bool: + """ + Check if an AST node matches a tool reference. + + Args: + node: AST node to check + tool_name: Name of the tool to match + base_name: Base module name (default: 'tools') + """ + return ( + isinstance(node, ast.Attribute) and + isinstance(node.value, ast.Name) and + node.value.id == base_name and + node.attr == tool_name + ) + + +def _process_tools_list( + current_tools: List[ast.AST], + tool_data: Dict, + operation: str, + base_name: str = 'tools' +) -> List[ast.AST]: + """ + Process a tools list according to the specified operation. + + Args: + current_tools: Current list of tool nodes + tool_data: Tool configuration dictionary + operation: Operation to perform ('add' or 'remove') + base_name: Base module name for tools + """ + if operation == 'add': + new_tools = current_tools.copy() + + # Add tools.* if bundled + if tool_data.get("tools_bundled"): + new_tools.insert(0, _create_tool_attribute('*', base_name)) + + # Add new tools + new_tools.extend(_create_tool_attributes(tool_data["tools"], 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"]) + ] + + raise ValueError(f"Unsupported operation: {operation}") + + +def _modify_agent_tools( + node: ast.FunctionDef, + tool_data: Dict, + operation: str, + agents: Optional[List[str]] = None, + base_name: str = 'tools' +) -> ast.FunctionDef: + """ + Modify the tools list in an agent definition. + + Args: + node: AST node of the function to modify + tool_data: Tool configuration dictionary + operation: Operation to perform ('add' or 'remove') + agents: Optional list of agent names to modify + base_name: Base module name for tools + """ + # Skip if not in specified agents list + if agents is not None and node.name not in agents: + 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): + return node + + # Find the Return statement and modify tools + for item in node.body: + if isinstance(item, ast.Return): + agent_call = item.value + if isinstance(agent_call, ast.Call): + for kw in agent_call.keywords: + 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 + ) + + # Replace with new list + kw.value = ast.List(elts=new_tools, ctx=ast.Load()) + + return node + + +def modify_agent_tools( + framework: str, + tool_data: Dict, + operation: str, + agents: Optional[List[str]] = None, + path: str = '', + base_name: str = 'tools' +) -> None: + """ + Modify tools in agent definitions using AST transformation. + + Args: + framework: Name of the framework + tool_data: Dictionary containing tool information + { + "tools": List[str], # List of tool names + "tools_bundled": bool # Whether to include tools.* (for add operation) + } + operation: Operation to perform ('add' or 'remove') + agents: Optional list of agent names to modify + path: Optional path to the framework file + base_name: Base module name for tools (default: 'tools') + """ + filename = _framework_filename(framework, path) + + with open(filename, 'r') as f: + source = f.read() + + tree = ast.parse(source) + + class ModifierTransformer(ast.NodeTransformer): + def visit_FunctionDef(self, node): + return _modify_agent_tools(node, tool_data, operation, agents, base_name) + + 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 diff --git a/agentstack/main.py b/agentstack/main.py index 75edd0b0..0b482c30 100644 --- a/agentstack/main.py +++ b/agentstack/main.py @@ -70,6 +70,7 @@ def main(): # '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', help='Name of agents to add this tool to, comma separated') # 'remove' command under 'tools' tools_remove_parser = tools_subparsers.add_parser('remove', aliases=['r'], help='Remove a tool') @@ -93,7 +94,7 @@ def main(): if args.command in ['templates']: webbrowser.open('https://docs.agentstack.sh/quickstart') if args.command in ['init', 'i']: - init_project_builder(args.slug_name, args.framework, args.wizard) + init_project_builder(args.slug_name, args.template, args.wizard) if args.command in ['run', 'r']: framework = get_framework() if framework == "crewai": @@ -109,7 +110,8 @@ def main(): if args.tools_command in ['list', 'l']: list_tools() elif args.tools_command in ['add', 'a']: - generation.add_tool(args.name) + agents = args.agents.split(',') if args.agents else None + generation.add_tool(args.name, agents=agents) elif args.tools_command in ['remove', 'r']: generation.remove_tool(args.name) else: diff --git a/agentstack/telemetry.py b/agentstack/telemetry.py index e20d5744..01fb7e09 100644 --- a/agentstack/telemetry.py +++ b/agentstack/telemetry.py @@ -46,7 +46,7 @@ def collect_machine_telemetry(command: str): 'agentstack_version': get_version() } - if command is not "init": + if command != "init": telemetry_data['framework'] = get_framework() else: telemetry_data['framework'] = "n/a" diff --git a/agentstack/projects_templates/chatbot.json b/agentstack/templates/proj_templates/chatbot.json similarity index 100% rename from agentstack/projects_templates/chatbot.json rename to agentstack/templates/proj_templates/chatbot.json diff --git a/agentstack/templates/proj_templates/research.json b/agentstack/templates/proj_templates/research.json index b6420b87..d3e6cf76 100644 --- a/agentstack/templates/proj_templates/research.json +++ b/agentstack/templates/proj_templates/research.json @@ -1,6 +1,7 @@ { "name": "research", "description": "Starter project research agent", + "framework": "Framework", "agents": [{ "name": "researcher", "prompt": { diff --git a/pyproject.toml b/pyproject.toml index 27904230..b88cf934 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,8 @@ dependencies = [ "toml>=0.10.2", "ruamel.yaml.base>=0.3.2", "cookiecutter==2.6.0", - "psutil==5.9.0" + "psutil==5.9.0", + "astor==0.8.1" ] [tool.setuptools.package-data] From a9daf826df719a0d0b400bfda91fc57d96f91984 Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Wed, 27 Nov 2024 00:24:31 -0800 Subject: [PATCH 13/18] handles bundled tools --- agentstack/generation/__init__.py | 4 +- agentstack/generation/agent_generation.py | 9 ++- agentstack/generation/gen_utils.py | 74 +++++++++++++++++ agentstack/generation/task_generation.py | 9 ++- agentstack/generation/tool_generation.py | 99 ++++++++++++++--------- agentstack/main.py | 2 +- 6 files changed, 154 insertions(+), 43 deletions(-) diff --git a/agentstack/generation/__init__.py b/agentstack/generation/__init__.py index b9aa0941..ff958b9b 100644 --- a/agentstack/generation/__init__.py +++ b/agentstack/generation/__init__.py @@ -1,3 +1,3 @@ -from .agent_generation import generate_agent -from .task_generation import generate_task +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 diff --git a/agentstack/generation/agent_generation.py b/agentstack/generation/agent_generation.py index ef5cd2fb..ad65be24 100644 --- a/agentstack/generation/agent_generation.py +++ b/agentstack/generation/agent_generation.py @@ -1,6 +1,6 @@ -from typing import Optional +from typing import Optional, List -from .gen_utils import insert_code_after_tag +from .gen_utils import insert_code_after_tag, get_crew_components, CrewComponentType from agentstack.utils import verify_agentstack_project, get_framework import os from ruamel.yaml import YAML @@ -99,3 +99,8 @@ def generate_crew_agent( ] insert_code_after_tag(file_path, tag, code_to_insert) + + +def get_agent_names(framework: str = 'crewai', path: str = '') -> List[str]: + """Get only agent names from the crew file""" + return get_crew_components(framework, CrewComponentType.AGENT, path)['agents'] \ No newline at end of file diff --git a/agentstack/generation/gen_utils.py b/agentstack/generation/gen_utils.py index 7256fbef..c119a83d 100644 --- a/agentstack/generation/gen_utils.py +++ b/agentstack/generation/gen_utils.py @@ -1,4 +1,9 @@ import ast +import sys +from enum import Enum +from typing import Optional, Union, List + +from agentstack.utils import term_color def insert_code_after_tag(file_path, tag, code_to_insert, next_line=False): @@ -66,3 +71,72 @@ def string_in_file(file_path: str, str_to_match: str) -> bool: 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' + + print(term_color(f'Unknown framework: {framework}', 'red')) + sys.exit(1) + + +class CrewComponentType(str, Enum): + AGENT = "agent" + TASK = "task" + + +def get_crew_components( + framework: str = 'crewai', + component_type: Optional[Union[CrewComponentType, List[CrewComponentType]]] = None, + path: str = '' +) -> dict[str, List[str]]: + """ + Get names of components (agents and/or tasks) defined in a crew file. + + Args: + framework: Name of the framework + component_type: Optional filter for specific component types. + Can be CrewComponentType.AGENT, CrewComponentType.TASK, + or a list of types. If None, returns all components. + path: Optional path to the framework file + + Returns: + Dictionary with 'agents' and 'tasks' keys containing lists of names + """ + filename = _framework_filename(framework, path) + + # Convert single component type to list for consistent handling + if isinstance(component_type, CrewComponentType): + component_type = [component_type] + + # Read the source file + with open(filename, 'r') as f: + source = f.read() + + # Parse the source into an AST + tree = ast.parse(source) + + components = { + 'agents': [], + 'tasks': [] + } + + # Find all function definitions with relevant decorators + for node in ast.walk(tree): + if isinstance(node, ast.FunctionDef): + # Check decorators + for decorator in node.decorator_list: + if isinstance(decorator, ast.Name): + if (component_type is None or CrewComponentType.AGENT in component_type) \ + and decorator.id == 'agent': + components['agents'].append(node.name) + elif (component_type is None or CrewComponentType.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 CrewComponentType(k[:-1]) in component_type} + + return components diff --git a/agentstack/generation/task_generation.py b/agentstack/generation/task_generation.py index 7976fe8d..25838007 100644 --- a/agentstack/generation/task_generation.py +++ b/agentstack/generation/task_generation.py @@ -1,6 +1,6 @@ -from typing import Optional +from typing import Optional, List -from .gen_utils import insert_code_after_tag, insert_after_tasks +from .gen_utils import insert_after_tasks, get_crew_components, CrewComponentType from ..utils import verify_agentstack_project, get_framework import os from ruamel.yaml import YAML @@ -87,3 +87,8 @@ def generate_crew_task( ] insert_after_tasks(file_path, code_to_insert) + + +def get_task_names(framework: str, path: str = '') -> List[str]: + """Get only task names from the crew file""" + return get_crew_components(framework, CrewComponentType.TASK, path)['tasks'] \ No newline at end of file diff --git a/agentstack/generation/tool_generation.py b/agentstack/generation/tool_generation.py index c98b4957..6097ae02 100644 --- a/agentstack/generation/tool_generation.py +++ b/agentstack/generation/tool_generation.py @@ -1,9 +1,10 @@ import importlib.resources import json import sys -from typing import Optional, List, Dict +from typing import Optional, List, Dict, Union -from .gen_utils import insert_code_after_tag, string_in_file +from . import get_agent_names +from .gen_utils import insert_code_after_tag, string_in_file, _framework_filename from ..utils import open_json_file, get_framework, term_color import os import shutil @@ -26,9 +27,9 @@ def add_tool(tool_name: str, path: Optional[str] = None, agents: Optional[List[s assert_tool_exists(tool_name, tools) agentstack_json = open_json_file(f'{path}{AGENTSTACK_JSON_FILENAME}') - if tool_name in agentstack_json.get('tools', []): - print(term_color(f'Tool {tool_name} is already installed', 'red')) - sys.exit(1) + # if tool_name in agentstack_json.get('tools', []): + # print(term_color(f'Tool {tool_name} is already installed', 'red')) + # sys.exit(1) with importlib.resources.path(f'agentstack.tools', f"{tool_name}.json") as tool_data_path: tool_data = open_json_file(tool_data_path) @@ -51,7 +52,8 @@ def add_tool(tool_name: str, path: Optional[str] = None, agents: Optional[List[s if not agentstack_json.get('tools'): agentstack_json['tools'] = [] - agentstack_json['tools'].append(tool_name) + if tool_name not in agentstack_json['tools']: + agentstack_json['tools'].append(tool_name) with open(f'{path}{AGENTSTACK_JSON_FILENAME}', 'w') as f: json.dump(agentstack_json, f, indent=4) @@ -113,14 +115,6 @@ def remove_tool_from_tools_init(tool_data: dict, path: str = ''): print(line, end='') -def _framework_filename(framework: str, path: str = ''): - if framework == 'crewai': - return f'{path}src/crew.py' - - print(term_color(f'Unknown framework: {framework}', 'red')) - sys.exit(1) - - def add_tool_to_agent_definition(framework: str, tool_data: dict, path: str = '', agents: list[str] = []): """ Add tools to specific agent definitions using AST transformation. @@ -153,19 +147,24 @@ def assert_tool_exists(tool_name: str, tools: dict): def _create_tool_attribute(tool_name: str, base_name: str = 'tools') -> ast.Attribute: - """ - Create an AST node for a tool attribute. - - Args: - tool_name: Name of the tool - base_name: Base module name (default: 'tools') - """ + """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() ) +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() + ), + ctx=ast.Load() + ) + def _create_tool_attributes( tool_names: List[str], @@ -175,21 +174,43 @@ def _create_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' +) -> List[Union[ast.Attribute, ast.Starred]]: + """Create AST nodes for multiple tool attributes""" + return [ + _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: """ - Check if an AST node matches a tool reference. + Check if an AST node matches a tool reference, regardless of whether it's starred Args: - node: AST node to check + node: AST node to check (can be Attribute or Starred) tool_name: Name of the tool to match base_name: Base module name (default: 'tools') + + Returns: + bool: True if the node matches the tool reference """ - return ( - isinstance(node, ast.Attribute) and - isinstance(node.value, ast.Name) and - node.value.id == base_name and - node.attr == tool_name - ) + # If it's a Starred node, check its value + if isinstance(node, ast.Starred): + node = node.value + + # 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_name_match = node.attr == tool_name + return is_base_match and is_name_match + + return False def _process_tools_list( @@ -209,13 +230,12 @@ def _process_tools_list( """ if operation == 'add': new_tools = current_tools.copy() - - # Add tools.* if bundled - if tool_data.get("tools_bundled"): - new_tools.insert(0, _create_tool_attribute('*', base_name)) - - # Add new tools - new_tools.extend(_create_tool_attributes(tool_data["tools"], base_name)) + # Add new tools with bundling if specified + new_tools.extend(_create_tool_nodes( + tool_data["tools"], + tool_data.get("tools_bundled", False), + base_name + )) return new_tools elif operation == 'remove': @@ -300,6 +320,13 @@ def modify_agent_tools( path: Optional path to the framework file base_name: Base module name for tools (default: 'tools') """ + if agents is not None: + valid_agents = get_agent_names() + for agent in agents: + if agent not in valid_agents: + 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: diff --git a/agentstack/main.py b/agentstack/main.py index 0b482c30..1eef5d69 100644 --- a/agentstack/main.py +++ b/agentstack/main.py @@ -70,7 +70,7 @@ def main(): # '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', help='Name of agents to add this tool to, comma separated') + tools_add_parser.add_argument('--agents', '-a', help='Name of agents to add this tool to, comma separated') # 'remove' command under 'tools' tools_remove_parser = tools_subparsers.add_parser('remove', aliases=['r'], help='Remove a tool') From 65e5fcc864905ee160d583c342d45ad1705e464f Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Wed, 27 Nov 2024 01:07:33 -0800 Subject: [PATCH 14/18] generate project from template --- 0.2-changes.md | 1 + agentstack/cli/agentstack_data.py | 6 ++++- agentstack/cli/cli.py | 24 ++++++++++++------- agentstack/generation/tool_generation.py | 2 +- agentstack/telemetry.py | 2 +- .../agentstack.json | 5 ++-- .../templates/proj_templates/research.json | 23 ++++++++---------- 7 files changed, 36 insertions(+), 27 deletions(-) diff --git a/0.2-changes.md b/0.2-changes.md index cd76fc59..beff03fa 100644 --- a/0.2-changes.md +++ b/0.2-changes.md @@ -74,6 +74,7 @@ We'll move forward with the codegen method unless other information comes to lig - dont place into a subdirectory - `--template ` - generate the project according to a template + - can also consume a url ### `agentstack tools` - Stays the same diff --git a/agentstack/cli/agentstack_data.py b/agentstack/cli/agentstack_data.py index 033a1f0e..952dddf6 100644 --- a/agentstack/cli/agentstack_data.py +++ b/agentstack/cli/agentstack_data.py @@ -15,7 +15,8 @@ def __init__(self, version: str = "", license: str = "", year: int = datetime.now().year, - template: str = "default" + 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 @@ -26,6 +27,7 @@ def __init__(self, self.year = year self.agentstack_version = get_version() self.template = template + self.template_version = template log.debug(f"ProjectMetadata: {self.to_dict()}") @@ -39,6 +41,8 @@ def to_dict(self): 'license': self.license, 'year': self.year, 'agentstack_version': self.agentstack_version, + 'template': self.template, + 'template_version': self.template_version, } def to_json(self): diff --git a/agentstack/cli/cli.py b/agentstack/cli/cli.py index f7c37676..d7642480 100644 --- a/agentstack/cli/cli.py +++ b/agentstack/cli/cli.py @@ -23,7 +23,7 @@ def init_project_builder(slug_name: Optional[str] = None, template: Optional[str print(term_color("Project name must be snake case", 'red')) return - if template is not None and not use_wizard: + if template is not None and use_wizard: print(term_color("Template and wizard flags cannot be used together", 'red')) return @@ -39,7 +39,7 @@ def init_project_builder(slug_name: Optional[str] = None, template: Optional[str 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', template) 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) @@ -58,8 +58,10 @@ def init_project_builder(slug_name: Optional[str] = None, template: Optional[str 'agents': template_data['agents'], 'tasks': template_data['tasks'] } - for tool_data in template_data['tools']: - generation.add_tool(tool_data['name'], agents=tool_data['agents'], path=project_details['name']) + + tools = template_data['tools'] + # for tool_data in template_data['tools']: + # generation.add_tool(tool_data['name'], agents=tool_data['agents'], path=project_details['name']) elif use_wizard: @@ -68,7 +70,7 @@ def init_project_builder(slug_name: Optional[str] = None, template: Optional[str welcome_message() framework = ask_framework() design = ask_design() - # tools = ask_tools() + tools = ask_tools() else: welcome_message() @@ -87,15 +89,17 @@ def init_project_builder(slug_name: Optional[str] = None, template: Optional[str 'tasks': [] } - # tools = [] + tools = [] log.debug( f"project_details: {project_details}" f"framework: {framework}" f"design: {design}" ) - insert_template(project_details, framework, design) + insert_template(project_details, framework, design, template_data) # add_tools(tools, project_details['name']) + for tool_data in tools: + generation.add_tool(tool_data['name'], agents=tool_data['agents'], path=project_details['name']) def welcome_message(): @@ -298,14 +302,16 @@ def ask_project_details(slug_name: Optional[str] = None) -> dict: return questions -def insert_template(project_details: dict, framework_name: str, design: dict): +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) + year=datetime.now().year, + template=template_data['name'], + template_version=template_data['template_version'] if template_data else None) project_structure = ProjectStructure() project_structure.agents = design["agents"] diff --git a/agentstack/generation/tool_generation.py b/agentstack/generation/tool_generation.py index 6097ae02..9dc152d9 100644 --- a/agentstack/generation/tool_generation.py +++ b/agentstack/generation/tool_generation.py @@ -321,7 +321,7 @@ def modify_agent_tools( base_name: Base module name for tools (default: 'tools') """ if agents is not None: - valid_agents = get_agent_names() + 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')) diff --git a/agentstack/telemetry.py b/agentstack/telemetry.py index 01fb7e09..db613c8c 100644 --- a/agentstack/telemetry.py +++ b/agentstack/telemetry.py @@ -33,7 +33,7 @@ TELEMETRY_URL = 'https://api.agentstack.sh/telemetry' def collect_machine_telemetry(command: str): - if get_telemetry_opt_out(): + if command != "init" and get_telemetry_opt_out(): return telemetry_data = { diff --git a/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/agentstack.json b/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/agentstack.json index 6296e2ad..5511a17a 100644 --- a/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/agentstack.json +++ b/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/agentstack.json @@ -1,5 +1,6 @@ { "framework": "{{ cookiecutter.framework }}", - "agentstack_version": "{{ cookiecutter.agentstack_version }}", - "template": "{{ cookiecutter.template }}" + "agentstack_version": "{{ cookiecutter.project_metadata.agentstack_version }}", + "template": "{{ cookiecutter.project_metadata.template }}", + "template_version": "{{ cookiecutter.project_metadata.template_version }}" } \ No newline at end of file diff --git a/agentstack/templates/proj_templates/research.json b/agentstack/templates/proj_templates/research.json index d3e6cf76..b27b19c7 100644 --- a/agentstack/templates/proj_templates/research.json +++ b/agentstack/templates/proj_templates/research.json @@ -1,23 +1,20 @@ { "name": "research", "description": "Starter project research agent", - "framework": "Framework", + "template_version": 1, + "framework": "crewai", "agents": [{ "name": "researcher", - "prompt": { - "role": "Gather data using research tools.", - "goal": "Collect all relevant information asked for using the tools available to you. The data will be analyzed later, compile a result of all data that you believe to be relevant to the query.", - "backstory": "You are an expert researcher. You are given a query and are tasked with providing as much relevant data in a concise manner." - }, - "llm": "openai/gpt-4o" + "role": "Gather data using research tools.", + "goal": "Collect all relevant information asked for using the tools available to you. The data will be analyzed later, compile a result of all data that you believe to be relevant to the query.", + "backstory": "You are an expert researcher. You are given a query and are tasked with providing as much relevant data in a concise manner.", + "model": "openai/gpt-4o" },{ "name": "analyst", - "prompt": { - "role": "Analyze gathered data.", - "goal": "Analyze and consolidate the data gathered from research to adequately answer the query provided in the task.", - "backstory": "You are an expert analyst. You are given a collection of research results and should use your knowledge to make conclusions on the data without making any assumptions that are not specifically supported by the data." - }, - "llm": "openai/gpt-4o" + "role": "Analyze gathered data.", + "goal": "Analyze and consolidate the data gathered from research to adequately answer the query provided in the task.", + "backstory": "You are an expert analyst. You are given a collection of research results and should use your knowledge to make conclusions on the data without making any assumptions that are not specifically supported by the data.", + "model": "openai/gpt-4o" }], "tasks": [{ "name": "research", From b76924d3b492ab05867de3143124580dc1076648 Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Wed, 27 Nov 2024 01:17:12 -0800 Subject: [PATCH 15/18] use template version --- agentstack/cli/agentstack_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agentstack/cli/agentstack_data.py b/agentstack/cli/agentstack_data.py index 952dddf6..ca811423 100644 --- a/agentstack/cli/agentstack_data.py +++ b/agentstack/cli/agentstack_data.py @@ -27,7 +27,7 @@ def __init__(self, self.year = year self.agentstack_version = get_version() self.template = template - self.template_version = template + self.template_version = template_version log.debug(f"ProjectMetadata: {self.to_dict()}") From 522ce2d346178f3707e52f6c4f6a2cbc7287f429 Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Sat, 30 Nov 2024 01:58:59 -0800 Subject: [PATCH 16/18] merge and fix no --agents arg issue --- agentstack/generation/agent_generation.py | 4 +- agentstack/generation/gen_utils.py | 12 +-- agentstack/generation/task_generation.py | 4 +- agentstack/generation/tool_generation.py | 74 +++++-------------- agentstack/main.py | 4 +- examples/web_researcher/.env.example | 5 +- examples/web_researcher/src/crew.py | 63 ++++++---------- examples/web_researcher/src/tools/__init__.py | 38 ++++++++++ 8 files changed, 96 insertions(+), 108 deletions(-) diff --git a/agentstack/generation/agent_generation.py b/agentstack/generation/agent_generation.py index ad65be24..f13a5d9f 100644 --- a/agentstack/generation/agent_generation.py +++ b/agentstack/generation/agent_generation.py @@ -1,6 +1,6 @@ from typing import Optional, List -from .gen_utils import insert_code_after_tag, get_crew_components, CrewComponentType +from .gen_utils import insert_code_after_tag, get_crew_components, CrewComponent from agentstack.utils import verify_agentstack_project, get_framework import os from ruamel.yaml import YAML @@ -103,4 +103,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, CrewComponentType.AGENT, path)['agents'] \ No newline at end of file + return get_crew_components(framework, CrewComponent.AGENT, path)['agents'] \ No newline at end of file diff --git a/agentstack/generation/gen_utils.py b/agentstack/generation/gen_utils.py index c119a83d..f9ac0e5f 100644 --- a/agentstack/generation/gen_utils.py +++ b/agentstack/generation/gen_utils.py @@ -80,14 +80,14 @@ def _framework_filename(framework: str, path: str = ''): sys.exit(1) -class CrewComponentType(str, Enum): +class CrewComponent(str, Enum): AGENT = "agent" TASK = "task" def get_crew_components( framework: str = 'crewai', - component_type: Optional[Union[CrewComponentType, List[CrewComponentType]]] = None, + component_type: Optional[Union[CrewComponent, List[CrewComponent]]] = None, path: str = '' ) -> dict[str, List[str]]: """ @@ -106,7 +106,7 @@ def get_crew_components( filename = _framework_filename(framework, path) # Convert single component type to list for consistent handling - if isinstance(component_type, CrewComponentType): + if isinstance(component_type, CrewComponent): component_type = [component_type] # Read the source file @@ -127,16 +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 CrewComponentType.AGENT in component_type) \ + 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 CrewComponentType.TASK in component_type) \ + 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 CrewComponentType(k[:-1]) in component_type} + if CrewComponent(k[:-1]) in component_type} return components diff --git a/agentstack/generation/task_generation.py b/agentstack/generation/task_generation.py index 25838007..d2fc6ebc 100644 --- a/agentstack/generation/task_generation.py +++ b/agentstack/generation/task_generation.py @@ -1,6 +1,6 @@ from typing import Optional, List -from .gen_utils import insert_after_tasks, get_crew_components, CrewComponentType +from .gen_utils import insert_after_tasks, get_crew_components, CrewComponent from ..utils import verify_agentstack_project, get_framework import os from ruamel.yaml import YAML @@ -91,4 +91,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, CrewComponentType.TASK, path)['tasks'] \ No newline at end of file + return get_crew_components(framework, CrewComponent.TASK, path)['tasks'] \ No newline at end of file diff --git a/agentstack/generation/tool_generation.py b/agentstack/generation/tool_generation.py index aa432cac..c4e19fc0 100644 --- a/agentstack/generation/tool_generation.py +++ b/agentstack/generation/tool_generation.py @@ -111,7 +111,7 @@ def add_tool(tool_name: str, path: Optional[str] = None, agents: Optional[List[s 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 add_tool_to_tools_init(tool_data, path) # Export tool from tools dir - add_tool_to_agent_definition(framework, tool_data, path) # 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 with EnvFile(path) as env: @@ -123,27 +123,6 @@ def add_tool(tool_name: str, path: Optional[str] = None, agents: Optional[List[s if tool_data.post_install: os.system(tool_data.post_install) - with importlib.resources.path(f'agentstack.templates.{framework}.tools', - f"{tool_name}_tool.py") as tool_file_path: - if tool_data.get('packages'): - if os.system(f"poetry add {' '.join(tool_data['packages'])}") == 1: # Install packages - print(term_color("AgentStack: Failed to install tool requirements. Please resolve dependency issues and try again,", 'red')) - return - 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, tool_data, path, agents) # Add tool to agent definition - if tool_data.get('env'): # if the env vars aren't in the .env files, add them - first_var_name = tool_data['env'].split('=')[0] - if not string_in_file(f'{path}.env', first_var_name): - insert_code_after_tag(f'{path}.env', '# Tools', [tool_data['env']], next_line=True) # Add env var - if not string_in_file(f'{path}.env.example', first_var_name): - insert_code_after_tag(f'{path}.env.example', '# Tools', [tool_data['env']], next_line=True) # Add env var - if tool_data.get('post_install'): - os.system(tool_data['post_install']) - if not agentstack_json.get('tools'): - agentstack_json['tools'] = [] - if tool_name not in agentstack_json['tools']: - agentstack_json['tools'].append(tool_name) with agentstack_config as config: config.tools.append(tool_name) @@ -152,6 +131,7 @@ def add_tool(tool_name: str, path: Optional[str] = None, agents: Optional[List[s if tool_data.cta: 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 + '/' @@ -183,12 +163,14 @@ def remove_tool(tool_name: str, path: Optional[str] = None): 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(), ] insert_code_after_tag(file_path, tag, code_to_insert, next_line=True) + 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}' @@ -198,35 +180,22 @@ def remove_tool_from_tools_init(tool_data: ToolConfig, path: str = ''): if line.strip() != import_statement: print(line, end='') -def add_tool_to_agent_definition(framework: str, tool_data: ToolConfig, path: str = ''): - with fileinput.input(files=get_framework_filename(framework, path), inplace=True) as f: - for line in f: - print(line.replace('tools=[', f'tools=[{"*" if tool_data.tools_bundled else ""}tools.{", tools.".join(tool_data.tools)}, '), end='') -def add_tool_to_agent_definition(framework: str, tool_data: dict, 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. Args: framework: Name of the framework - tool_data: Dictionary containing tool information - { - "tools": List[str], # List of tool names to add - "tools_bundled": bool # Whether to include tools.* - } + 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, tool_data, 'add', agents, path, 'tools') + 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: dict, path: str = ''): - modify_agent_tools(framework, tool_data, 'remove', None, path, 'tools') - def remove_tool_from_agent_definition(framework: str, tool_data: ToolConfig, path: str = ''): - with fileinput.input(files=get_framework_filename(framework, path), inplace=True) as f: - for line in f: - print(line.replace(f'{", ".join([f"tools.{name}" for name in tool_data.tools])}, ', ''), end='') + 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: @@ -298,7 +267,7 @@ 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: Dict, + tool_data: ToolConfig, operation: str, base_name: str = 'tools' ) -> List[ast.AST]: @@ -307,7 +276,7 @@ def _process_tools_list( Args: current_tools: Current list of tool nodes - tool_data: Tool configuration dictionary + tool_data: Tool configuration operation: Operation to perform ('add' or 'remove') base_name: Base module name for tools """ @@ -315,8 +284,8 @@ def _process_tools_list( new_tools = current_tools.copy() # Add new tools with bundling if specified new_tools.extend(_create_tool_nodes( - tool_data["tools"], - tool_data.get("tools_bundled", False), + tool_data.tools, + tool_data.tools_bundled, base_name )) return new_tools @@ -326,7 +295,7 @@ def _process_tools_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"]) + for name in tool_data.tools) ] raise ValueError(f"Unsupported operation: {operation}") @@ -334,7 +303,7 @@ def _process_tools_list( def _modify_agent_tools( node: ast.FunctionDef, - tool_data: Dict, + tool_data: ToolConfig, operation: str, agents: Optional[List[str]] = None, base_name: str = 'tools' @@ -344,14 +313,15 @@ def _modify_agent_tools( Args: node: AST node of the function to modify - tool_data: Tool configuration dictionary + tool_data: Tool configuration operation: Operation to perform ('add' or 'remove') agents: Optional list of agent names to modify base_name: Base module name for tools """ # Skip if not in specified agents list - if agents is not None and node.name not in agents: - return node + if agents is not None and agents != []: + if node.name not in agents: + return node # Check if this is an agent-decorated function if not any(isinstance(d, ast.Name) and d.id == 'agent' @@ -382,7 +352,7 @@ def _modify_agent_tools( def modify_agent_tools( framework: str, - tool_data: Dict, + tool_data: ToolConfig, operation: str, agents: Optional[List[str]] = None, path: str = '', @@ -393,11 +363,7 @@ def modify_agent_tools( Args: framework: Name of the framework - tool_data: Dictionary containing tool information - { - "tools": List[str], # List of tool names - "tools_bundled": bool # Whether to include tools.* (for add operation) - } + tool_data: ToolConfig operation: Operation to perform ('add' or 'remove') agents: Optional list of agent names to modify path: Optional path to the framework file diff --git a/agentstack/main.py b/agentstack/main.py index 1eef5d69..deea78f4 100644 --- a/agentstack/main.py +++ b/agentstack/main.py @@ -71,6 +71,7 @@ def main(): 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') @@ -110,7 +111,8 @@ def main(): if args.tools_command in ['list', 'l']: list_tools() elif args.tools_command in ['add', 'a']: - agents = args.agents.split(',') if args.agents else None + agents = [args.agent] if args.agent else None + agents = args.agents.split(',') if args.agents else agents generation.add_tool(args.name, agents=agents) elif args.tools_command in ['remove', 'r']: generation.remove_tool(args.name) diff --git a/examples/web_researcher/.env.example b/examples/web_researcher/.env.example index 149da232..2575e4b9 100644 --- a/examples/web_researcher/.env.example +++ b/examples/web_researcher/.env.example @@ -4,4 +4,7 @@ OPENAI_API_KEY=... # Tools FIRECRAWL_API_KEY=... -NEON_API_KEY=... \ No newline at end of file +NEON_API_KEY=... +FTP_HOST=... +FTP_USER=... +FTP_PASSWORD=... \ No newline at end of file diff --git a/examples/web_researcher/src/crew.py b/examples/web_researcher/src/crew.py index 098e93f0..01cfac22 100644 --- a/examples/web_researcher/src/crew.py +++ b/examples/web_researcher/src/crew.py @@ -4,60 +4,39 @@ @CrewBase -class WebresearcherCrew(): +class WebresearcherCrew: """web_researcher crew""" - # Agent definitions @agent - def content_summarizer(self) -> Agent: - return Agent( - config=self.agents_config['content_summarizer'], - tools=[], # add tools here or use `agentstack tools add - 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], # add tools here or use `agentstack tools add - 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], # add tools here or use `agentstack tools add - verbose=True - ) - - # Task definitions + 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, # 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 + return Crew(agents=self.agents, tasks=self.tasks, process=Process. + sequential, verbose=True) diff --git a/examples/web_researcher/src/tools/__init__.py b/examples/web_researcher/src/tools/__init__.py index 83e7d9c9..ba9746fe 100644 --- a/examples/web_researcher/src/tools/__init__.py +++ b/examples/web_researcher/src/tools/__init__.py @@ -1,6 +1,44 @@ # 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 \ No newline at end of file From 8703144d3489e3a16b8515d4a2da8d3706818297 Mon Sep 17 00:00:00 2001 From: Braelyn Boynton Date: Sat, 30 Nov 2024 02:05:11 -0800 Subject: [PATCH 17/18] fix test, help print, flag --- agentstack/cli/cli.py | 2 +- agentstack/main.py | 8 ++++---- pyproject.toml | 2 +- tests/test_cli_loads.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/agentstack/cli/cli.py b/agentstack/cli/cli.py index 51c9af9f..9b560d16 100644 --- a/agentstack/cli/cli.py +++ b/agentstack/cli/cli.py @@ -309,7 +309,7 @@ def insert_template(project_details: dict, framework_name: str, design: dict, te version="0.0.1", license="MIT", year=datetime.now().year, - template=template_data['name'], + template=template_data['name'] if template_data else None, template_version=template_data['template_version'] if template_data else None) project_structure = ProjectStructure() diff --git a/agentstack/main.py b/agentstack/main.py index deea78f4..14a448cf 100644 --- a/agentstack/main.py +++ b/agentstack/main.py @@ -90,13 +90,13 @@ def main(): # Handle commands if args.command in ['docs']: webbrowser.open('https://docs.agentstack.sh/') - if args.command in ['quickstart']: + elif args.command in ['quickstart']: webbrowser.open('https://docs.agentstack.sh/quickstart') - if args.command in ['templates']: + elif args.command in ['templates']: webbrowser.open('https://docs.agentstack.sh/quickstart') - if args.command in ['init', 'i']: + elif args.command in ['init', 'i']: init_project_builder(args.slug_name, args.template, args.wizard) - if args.command in ['run', 'r']: + elif args.command in ['run', 'r']: framework = get_framework() if framework == "crewai": os.system('python src/main.py') diff --git a/pyproject.toml b/pyproject.toml index 1dd8d937..6b49c749 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ dependencies = [ "ruamel.yaml.base>=0.3.2", "cookiecutter==2.6.0", "psutil==5.9.0", - "astor==0.8.1" + "astor==0.8.1", "psutil==5.9.0", "pydantic>=2.10", ] diff --git a/tests/test_cli_loads.py b/tests/test_cli_loads.py index 98ff2318..49bb15cd 100644 --- a/tests/test_cli_loads.py +++ b/tests/test_cli_loads.py @@ -37,7 +37,7 @@ def test_init_command(self): if test_dir.exists(): shutil.rmtree(test_dir) - result = self.run_cli("init", str(test_dir), "--no-wizard") + result = self.run_cli("init", str(test_dir)) self.assertEqual(result.returncode, 0) self.assertTrue(test_dir.exists()) From 8570e1bb8753ec44644c9ae0883bd68ec8ec4daf Mon Sep 17 00:00:00 2001 From: Travis Dent Date: Sat, 30 Nov 2024 09:42:20 -0800 Subject: [PATCH 18/18] Cleanup newlines --- examples/web_researcher/src/tools/__init__.py | 42 +------------------ 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/examples/web_researcher/src/tools/__init__.py b/examples/web_researcher/src/tools/__init__.py index ba9746fe..fafc6f7e 100644 --- a/examples/web_researcher/src/tools/__init__.py +++ b/examples/web_researcher/src/tools/__init__.py @@ -1,44 +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 \ No newline at end of file +from .neon_tool import create_database, execute_sql_ddl, run_sql_query