diff --git a/agentstack/cli/agentstack_data.py b/agentstack/cli/agentstack_data.py index ec540de..0b7305a 100644 --- a/agentstack/cli/agentstack_data.py +++ b/agentstack/cli/agentstack_data.py @@ -51,10 +51,16 @@ def to_json(self): class ProjectStructure: - def __init__(self): + def __init__( + self, + method: str = "sequential", + manager_agent: Optional[str] = None, + ): self.agents = [] self.tasks = [] self.inputs = {} + self.method = method + self.manager_agent = manager_agent def add_agent(self, agent): self.agents.append(agent) @@ -67,6 +73,8 @@ def set_inputs(self, inputs): def to_dict(self): return { + 'method': self.method, + 'manager_agent': self.manager_agent, 'agents': self.agents, 'tasks': self.tasks, 'inputs': self.inputs, diff --git a/agentstack/cli/cli.py b/agentstack/cli/cli.py index df2ee70..c84d496 100644 --- a/agentstack/cli/cli.py +++ b/agentstack/cli/cli.py @@ -388,7 +388,10 @@ def insert_template( template_version=template_data.template_version if template_data else 0, ) - project_structure = ProjectStructure() + project_structure = ProjectStructure( + method=template_data.method if template_data else "sequential", + manager_agent=template_data.manager_agent if template_data else None, + ) project_structure.agents = design["agents"] project_structure.tasks = design["tasks"] project_structure.inputs = design["inputs"] @@ -471,6 +474,7 @@ def export_template(output_filename: str): role=agent.role, goal=agent.goal, backstory=agent.backstory, + allow_delegation=False, # TODO model=agent.llm, # TODO consistent naming (llm -> model) ) ) @@ -507,11 +511,12 @@ def export_template(output_filename: str): ) template = TemplateConfig( - template_version=2, + template_version=3, name=metadata.project_name, description=metadata.project_description, framework=get_framework(), method="sequential", # TODO this needs to be stored in the project somewhere + manager_agent=None, # TODO agents=agents, tasks=tasks, tools=tools, diff --git a/agentstack/cli/run.py b/agentstack/cli/run.py index 17c48b4..bed89db 100644 --- a/agentstack/cli/run.py +++ b/agentstack/cli/run.py @@ -84,7 +84,7 @@ def _import_project_module(path: Path): assert spec.loader is not None # appease type checker project_module = importlib.util.module_from_spec(spec) - sys.path.append(str((path / MAIN_FILENAME).parent)) + sys.path.insert(0, str((path / MAIN_FILENAME).parent)) spec.loader.exec_module(project_module) return project_module diff --git a/agentstack/main.py b/agentstack/main.py index 07e0c97..7c60ddf 100644 --- a/agentstack/main.py +++ b/agentstack/main.py @@ -200,8 +200,7 @@ def main(): except Exception as e: update_telemetry(telemetry_id, result=1, message=str(e)) print(term_color("An error occurred while running your AgentStack command:", "red")) - print(e) - sys.exit(1) + raise e update_telemetry(telemetry_id, result=0) diff --git a/agentstack/proj_templates.py b/agentstack/proj_templates.py index 5989c1f..2e4ce19 100644 --- a/agentstack/proj_templates.py +++ b/agentstack/proj_templates.py @@ -1,4 +1,4 @@ -from typing import Literal +from typing import Optional, Literal import os, sys from pathlib import Path import pydantic @@ -19,20 +19,63 @@ class TemplateConfig_v1(pydantic.BaseModel): tools: list[dict] inputs: list[str] - def to_v2(self) -> 'TemplateConfig': - return TemplateConfig( + def to_v2(self) -> 'TemplateConfig_v2': + return TemplateConfig_v2( name=self.name, description=self.description, template_version=2, framework=self.framework, method=self.method, - agents=[TemplateConfig.Agent(**agent) for agent in self.agents], - tasks=[TemplateConfig.Task(**task) for task in self.tasks], - tools=[TemplateConfig.Tool(**tool) for tool in self.tools], + agents=[TemplateConfig_v2.Agent(**agent) for agent in self.agents], + tasks=[TemplateConfig_v2.Task(**task) for task in self.tasks], + tools=[TemplateConfig_v2.Tool(**tool) for tool in self.tools], inputs={key: "" for key in self.inputs}, ) +class TemplateConfig_v2(pydantic.BaseModel): + class Agent(pydantic.BaseModel): + name: str + role: str + goal: str + backstory: str + model: str + + class Task(pydantic.BaseModel): + name: str + description: str + expected_output: str + agent: str + + class Tool(pydantic.BaseModel): + name: str + agents: list[str] + + name: str + description: str + template_version: Literal[2] + framework: str + method: str + agents: list[Agent] + tasks: list[Task] + tools: list[Tool] + inputs: dict[str, str] + + def to_v3(self) -> 'TemplateConfig': + return TemplateConfig( + name=self.name, + description=self.description, + template_version=3, + framework=self.framework, + method=self.method, + manager_agent=None, + agents=[TemplateConfig.Agent(**agent.dict()) for agent in self.agents], + tasks=[TemplateConfig.Task(**task.dict()) for task in self.tasks], + tools=[TemplateConfig.Tool(**tool.dict()) for tool in self.tools], + inputs=self.inputs, + ) + + class TemplateConfig(pydantic.BaseModel): """ Interface for interacting with template configuration files. @@ -51,6 +94,8 @@ class TemplateConfig(pydantic.BaseModel): The framework the template is for. method: str The method used by the project. ie. "sequential" + manager_agent: Optional[str] + The name of the agent that manages the project. agents: list[TemplateConfig.Agent] A list of agents used by the project. tasks: list[TemplateConfig.Task] @@ -66,6 +111,7 @@ class Agent(pydantic.BaseModel): role: str goal: str backstory: str + allow_delegation: bool = False model: str class Task(pydantic.BaseModel): @@ -80,9 +126,10 @@ class Tool(pydantic.BaseModel): name: str description: str - template_version: Literal[2] + template_version: Literal[3] framework: str method: str + manager_agent: Optional[str] agents: list[Agent] tasks: list[Task] tools: list[Tool] @@ -134,8 +181,10 @@ def from_json(cls, data: dict) -> 'TemplateConfig': try: match data.get('template_version'): case 1: - return TemplateConfig_v1(**data).to_v2() + return TemplateConfig_v1(**data).to_v2().to_v3() case 2: + return TemplateConfig_v2(**data).to_v3() + case 3: return cls(**data) # current version case _: raise ValidationError(f"Unsupported template version: {data.get('template_version')}") diff --git a/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/src/crew.py b/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/src/crew.py index 94a74a5..4c0b255 100644 --- a/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/src/crew.py +++ b/agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/src/crew.py @@ -8,12 +8,13 @@ class {{cookiecutter.project_metadata.project_name|replace('-', '')|replace('_', # Agent definitions {%- for agent in cookiecutter.structure.agents %} - @agent + {% if not agent.name == cookiecutter.structure.manager_agent %}@agent{% endif %} def {{agent.name}}(self) -> Agent: return Agent( config=self.agents_config['{{ agent.name }}'], tools=[], # Pass in what tools this agent should have - verbose=True + verbose=True, + {% if agent.allow_delegation %}allow_delegation=True{% endif %} ) {%- endfor %} @@ -32,7 +33,7 @@ def crew(self) -> Crew: return Crew( agents=self.agents, # Automatically created by the @agent decorator tasks=self.tasks, # Automatically created by the @task decorator - process=Process.sequential, + process=Process.{{cookiecutter.structure.method}}, + {% if cookiecutter.structure.manager_agent %}manager_agent=self.{{cookiecutter.structure.manager_agent}}(),{% endif %} 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/agentstack/templates/proj_templates/reasoning.json b/agentstack/templates/proj_templates/reasoning.json new file mode 100644 index 0000000..bdd966e --- /dev/null +++ b/agentstack/templates/proj_templates/reasoning.json @@ -0,0 +1,54 @@ +{ + "name": "reasoning", + "description": "Implement test time compute using an agentic framework to emulate o1 with Claude.", + "template_version": 3, + "framework": "crewai", + "agents": [{ + "name": "manager", + "role": "Manager", + "goal": "Delegate requests to multiple sub-agents to find the best solution to the user's request using the best resources available. Break up the user's request into very small tasks and delegate them to the right agents. {query}", + "backstory": "You are responsible for delegating tasks to the right AI agents and ensuring that the team works together to achieve the best results.", + "allow_delegation": true, + "model": "anthropic/claude-3-5-sonnet-20240620" + }, { + "name": "triage", + "role": "Triage", + "goal": "You are responsible for interpreting the request and exploring possible ways of solving the problem. Delegate the actual problem solving to one of your workers. {query}", + "backstory": "You are responsible for interpreting the user's request and deciding which agent is best suited to solve the problem. You are the first point of contact for the system.", + "model": "anthropic/claude-3-5-sonnet-20240620" + }, { + "name": "worker", + "role": "Worker", + "goal": "You are responsible for solving the problem that the triage agent has delegated to you. You should use your knowledge and skills to find the best solution to the user's request.", + "backstory": "You are responsible for solving the problem that the triage agent has delegated to you. You are an expert in your field and you should use your knowledge and skills to find the best solution to the user's request.", + "model": "anthropic/claude-3-5-sonnet-20240620" + }, { + "name": "fact_checker", + "role": "Fact Checker", + "goal": "You are responsible for checking the solution that the worker agent has come up with. You should make sure that the solution is correct and that it meets the user's requirements. Evaluate the response in regards to the user's original question, and provide a concise answer that is factually correct. Now is a great time to omit any questionable statements and inconclusive data. {query}", + "backstory": "You are responsible for checking the solution that the worker agent has come up with. You should make sure that the solution is correct and that it meets the user's requirements. You are the last line of defense before the solution is presented to the user.", + "model": "anthropic/claude-3-5-sonnet-20240620" + }], + "tasks": [{ + "name": "identify_plan_of_action", + "description": "Identify the problem being presented and come up with steps to solve it. Restate the problem in your own words, and identify 3 to 12 steps you can take to explore possible solutions. Do not actually present solutions to the problem yourself, but pass it to a new agent to do so.", + "expected_output": "A detailed description of the problem being presented and a list of possible steps that can be taken to explore possible solutions.", + "agent": "triage" + }, { + "name": "find_solution", + "description": "Identify the problem being presented to you and come up with the best solution you can think of. After you have come up with a solution, pass it to a new agent to check it.", + "expected_output": "A concise, complete solution to the problem being presented.", + "agent": "worker" + }, { + "name": "check_solution", + "description": "Review the problem and solution being presented and determine wether you think it is correct or not.", + "expected_output": "Reiterate the solution to be factually correct.", + "agent": "fact_checker" + }], + "tools": [], + "method": "hierarchical", + "manager_agent": "manager", + "inputs": { + "query": "What is the meaning of life, the universe, and everything?" + } +}