Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
dd220fe
Merge branch 'v0.2' into frameworks
tcdent Nov 28, 2024
09f1787
Validate framework entrypoint file before execution. (#60)
tcdent Dec 2, 2024
8175251
Recursive tmp directory creation in CLI tests.
tcdent Dec 2, 2024
3da6973
Add better error messaging to crew validation.
tcdent Dec 3, 2024
d39596f
Merge branch 'main' into issue-60
tcdent Dec 3, 2024
e01c8cd
Merge branch 'main' into frameworks
tcdent Dec 3, 2024
d1c60a0
Merge branch 'issue-60' into frameworks
tcdent Dec 3, 2024
96f0ada
Frameworks AST progress (amend this)
tcdent Dec 3, 2024
aad158b
Merge branch 'main' into frameworks
tcdent Dec 4, 2024
3ad3f65
Merge branch 'main' into frameworks
tcdent Dec 4, 2024
14a0cf0
Multi-framework support via `agentstack.frameworks` module. Move AST …
tcdent Dec 4, 2024
5294e3e
Clenaup `tool_generation`, add ToolConfig.module_name, add tests for …
tcdent Dec 5, 2024
98a47dd
Comments, imports, naming, typing cleanup.
tcdent Dec 5, 2024
c11a2d6
Merge branch 'main' into frameworks
tcdent Dec 5, 2024
5d11d32
Migrate agent generation to frameworks. Add agent generation tests.
tcdent Dec 5, 2024
8abde6b
Migrate task generation to frameworks. Add task generation tests.
tcdent Dec 6, 2024
3b1c764
ruff format tests
tcdent Dec 6, 2024
406d90f
ruff format frameworks branch
tcdent Dec 6, 2024
796e026
Merge branch 'ruff-line-length' into frameworks
tcdent Dec 6, 2024
6d2511d
Merge branch 'pre-commit' into frameworks
tcdent Dec 6, 2024
b90095f
Type checking
tcdent Dec 6, 2024
de72af0
Rollback agentops version
tcdent Dec 6, 2024
a02ae00
Merge branch 'main' into frameworks
tcdent Dec 6, 2024
abf37e5
Merge branch 'main' into frameworks
tcdent Dec 6, 2024
6d3d63a
Move frameworks module docs into protocol spec
tcdent Dec 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions agentstack/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@


class ValidationError(Exception):
"""
Raised when a validation error occurs ie. a file does not meet the required
format or a syntax error is found.
"""
pass
98 changes: 98 additions & 0 deletions agentstack/agents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from typing import Optional
import os
from pathlib import Path
import pydantic
from ruamel.yaml import YAML, YAMLError
from ruamel.yaml.scalarstring import FoldedScalarString
from agentstack import ValidationError


AGENTS_FILENAME: Path = Path("src/config/agents.yaml")

yaml = YAML()
yaml.preserve_quotes = True # Preserve quotes in existing data


class AgentConfig(pydantic.BaseModel):
"""
Interface for interacting with an agent configuration.

Multiple agents are stored in a single YAML file, so we always look up the
requested agent by `name`.

Use it as a context manager to make and save edits:
```python
with AgentConfig('agent_name') as config:
config.llm = "openai/gpt-4o"

Config Schema
-------------
name: str
The name of the agent; used for lookup.
role: Optional[str]
The role of the agent.
goal: Optional[str]
The goal of the agent.
backstory: Optional[str]
The backstory of the agent.
llm: Optional[str]
The model this agent should use.
Adheres to the format set by the framework.
"""

name: str
role: Optional[str] = ""
goal: Optional[str] = ""
backstory: Optional[str] = ""
llm: Optional[str] = ""

def __init__(self, name: str, path: Optional[Path] = None):
if not path:
path = Path()

filename = path / AGENTS_FILENAME
if not os.path.exists(filename):
os.makedirs(filename.parent, exist_ok=True)
filename.touch()

try:
with open(filename, 'r') as f:
data = yaml.load(f) or {}
data = data.get(name, {}) or {}
super().__init__(**{**{'name': name}, **data})
except YAMLError as e:
# TODO format MarkedYAMLError lines/messages
raise ValidationError(f"Error parsing agents file: {filename}\n{e}")
except pydantic.ValidationError as e:
error_str = "Error validating agent config:\n"
for error in e.errors():
error_str += f"{' '.join([str(loc) for loc in error['loc']])}: {error['msg']}\n"
raise ValidationError(f"Error loading agent {name} from {filename}.\n{error_str}")

# store the path *after* loading data
self._path = path

def model_dump(self, *args, **kwargs) -> dict:
dump = super().model_dump(*args, **kwargs)
dump.pop('name') # name is the key, so keep it out of the data
# format these as FoldedScalarStrings
for key in ('role', 'goal', 'backstory'):
dump[key] = FoldedScalarString(dump.get(key) or "")
return {self.name: dump}

def write(self):
filename = self._path / AGENTS_FILENAME

with open(filename, 'r') as f:
data = yaml.load(f) or {}

data.update(self.model_dump())

with open(filename, 'w') as f:
yaml.dump(data, f)

def __enter__(self) -> 'AgentConfig':
return self

def __exit__(self, *args):
self.write()
2 changes: 1 addition & 1 deletion agentstack/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .cli import init_project_builder, list_tools, configure_default_model
from .cli import init_project_builder, list_tools, configure_default_model, run_project
29 changes: 26 additions & 3 deletions agentstack/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import time
from datetime import datetime
from typing import Optional
from pathlib import Path
import requests
import itertools

Expand All @@ -21,12 +22,15 @@
)
from agentstack.logger import log
from agentstack.utils import get_package_path
from agentstack.tools import get_all_tools
from agentstack.generation.files import ConfigFile
from agentstack.generation.tool_generation import get_all_tools
from agentstack import packaging, generation
from agentstack import frameworks
from agentstack import packaging
from agentstack import generation
from agentstack.utils import open_json_file, term_color, is_snake_case
from agentstack.update import AGENTSTACK_PACKAGE


PREFERRED_MODELS = [
'openai/gpt-4o',
'anthropic/claude-3-5-sonnet',
Expand Down Expand Up @@ -158,13 +162,32 @@ 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"')
print('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


def run_project(framework: str, path: str = ''):
"""Validate that the project is ready to run and then run it."""
if framework not in frameworks.SUPPORTED_FRAMEWORKS:
print(term_color(f"Framework {framework} is not supported by agentstack.", 'red'))
sys.exit(1)

_path = Path(path)

try:
frameworks.validate_project(framework, _path)
except frameworks.ValidationError as e:
print(term_color("Project validation failed:", 'red'))
print(e)
sys.exit(1)

entrypoint = _path / frameworks.get_entrypoint_path(framework)
os.system(f'python {entrypoint}')


def ask_framework() -> str:
framework = "CrewAI"
# framework = inquirer.list_input(
Expand Down
116 changes: 116 additions & 0 deletions agentstack/frameworks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from typing import Optional, Protocol
from types import ModuleType
from importlib import import_module
from pathlib import Path
from agentstack import ValidationError
from agentstack.tools import ToolConfig
from agentstack.agents import AgentConfig
from agentstack.tasks import TaskConfig


CREWAI = 'crewai'
SUPPORTED_FRAMEWORKS = [CREWAI, ]

class FrameworkModule(Protocol):
"""
Protocol spec for a framework implementation module.
"""
ENTRYPOINT: Path
"""
Relative path to the entrypoint file for the framework in the user's project.
ie. `src/crewai.py`
"""

def validate_project(self, path: Optional[Path] = None) -> None:
"""
Validate that a user's project is ready to run.
Raises a `ValidationError` if the project is not valid.
"""
...

def add_tool(self, tool: ToolConfig, agent_name: str, path: Optional[Path] = None) -> None:
"""
Add a tool to an agent in the user's project.
"""
...

def remove_tool(self, tool: ToolConfig, agent_name: str, path: Optional[Path] = None) -> None:
"""
Remove a tool from an agent in user's project.
"""
...

def get_agent_names(self, path: Optional[Path] = None) -> list[str]:
"""
Get a list of agent names in the user's project.
"""
...

def add_agent(self, agent: AgentConfig, path: Optional[Path] = None) -> None:
"""
Add an agent to the user's project.
"""
...

def add_task(self, task: TaskConfig, path: Optional[Path] = None) -> None:
"""
Add a task to the user's project.
"""
...


def get_framework_module(framework: str) -> FrameworkModule:
"""
Get the module for a framework.
"""
try:
return import_module(f".{framework}", package=__package__)
except ImportError:
raise Exception(f"Framework {framework} could not be imported.")

def get_entrypoint_path(framework: str, path: Optional[Path] = None) -> Path:
"""
Get the path to the entrypoint file for a framework.
"""
if path is None:
path = Path()
return path / get_framework_module(framework).ENTRYPOINT

def validate_project(framework: str, path: Optional[Path] = None):
"""
Validate that the user's project is ready to run.
"""
return get_framework_module(framework).validate_project(path)

def add_tool(framework: str, tool: ToolConfig, agent_name: str, path: Optional[Path] = None):
"""
Add a tool to the user's project.
The tool will have aready been installed in the user's application and have
all dependencies installed. We're just handling code generation here.
"""
return get_framework_module(framework).add_tool(tool, agent_name, path)

def remove_tool(framework: str, tool: ToolConfig, agent_name: str, path: Optional[Path] = None):
"""
Remove a tool from the user's project.
"""
return get_framework_module(framework).remove_tool(tool, agent_name, path)

def get_agent_names(framework: str, path: Optional[Path] = None) -> list[str]:
"""
Get a list of agent names in the user's project.
"""
return get_framework_module(framework).get_agent_names(path)

def add_agent(framework: str, agent: AgentConfig, path: Optional[Path] = None):
"""
Add an agent to the user's project.
"""
return get_framework_module(framework).add_agent(agent, path)

def add_task(framework: str, task: TaskConfig, path: Optional[Path] = None):
"""
Add a task to the user's project.
"""
return get_framework_module(framework).add_task(task, path)

Loading
Loading