Skip to content

Commit

Permalink
Merge branch 'main' into logging
Browse files Browse the repository at this point in the history
  • Loading branch information
tcdent committed Jan 10, 2025
2 parents 6081e04 + af7403d commit c93b58e
Show file tree
Hide file tree
Showing 48 changed files with 8,231 additions and 192 deletions.
43 changes: 43 additions & 0 deletions .github/workflows/codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Codecov

on:
push:
branches:
- main
paths:
- 'agentstack/**/*.py'
pull_request:
branches:
- main
paths:
- 'agentstack/**/*.py'

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.11

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox
- name: Run tests with tox
run: tox

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml
flags: unittests
name: codecov-umbrella
fail_ci_if_error: true
38 changes: 38 additions & 0 deletions .github/workflows/mintlify-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Mintlify Documentation Check
on:
pull_request:
paths:
- 'docs/**' # Only trigger on changes to docs directory
- 'mint.json' # Also trigger on mintlify config changes

jobs:
build-docs:
name: Build Documentation
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'

- name: Install Mintlify
run: npm install -g mintlify

- name: Test documentation
run: |
cd docs
# If mintlify dev has errors, it will exit with status 1
# If it starts successfully, kill it after 5 seconds
timeout 5s mintlify dev || exit_status=$?
if [ $exit_status -eq 124 ]; then
# timeout exit code 124 means the process was killed after starting successfully
echo "Documentation built successfully!"
exit 0
else
echo "Documentation failed to build! Try running `mintlify dev` from the docs dir locally"
exit 1
fi
13 changes: 1 addition & 12 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,7 @@ The best place to engage in conversation about your contribution is in the Issue
## Adding Tools
If you're reading this section, you probably have a product that AI agents can use as a tool. We're glad you're here!

Adding tools is easy once you understand the project structure. A few things need to be done for a tool to be considered completely supported:

1. Modify `agentstack/tools/tools.json`
- Add your tool and relevant information to this file as appropriate.
2. Create a config for your tool
- As an example, look at `mem0.json`
- AgentStack uses this to know what code to insert where. Follow the structure to add your tool.
3. Create your implementation for each framework
- In `agentstack/templates/<framework>/tools`, you'll see other implementations of tools.
- Build your tool implementation for that framework. This file will be inserted in the user's project.
- The tools that are exported from this file should be listed in the tool's config json.
4. Manually test your tool integration by running `agentstack tools add <your_tool>` and ensure it behaves as expected.
Adding tools is easy once you understand the project structure. Our documentation for adding tools is available on our hosted docs [here](https://docs.agentstack.sh/contributing/adding-tools).

## Before creating your PR
Be sure that you are opening a PR using a branch other than `main` on your fork. This enables us
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# AgentStack [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/release/python-3100/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
# AgentStack [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/release/python-3100/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) ![python-testing](https://github.com/agentops-ai/agentstack/actions/workflows/python-testing.yml/badge.svg) ![mypy](https://github.com/agentops-ai/agentstack/actions/workflows/mypy.yml/badge.svg) [![codecov.io](https://codecov.io/github/agentops-ai/agentstack/coverage.svg?branch=master)](https://codecov.io/github/agentops-ai/agentstack>?branch=master)

<img alt="Logo" align="right" src="https://raw.githubusercontent.com/bboynton97/agentstack-docs/3491fe490ea535e7def74c83182dfa8dcfb1f562/logo/dark-sm.svg" width="20%" />

Expand Down
3 changes: 2 additions & 1 deletion agentstack/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .cli import init_project_builder, configure_default_model, export_template
from .cli import init_project_builder, configure_default_model, export_template, welcome_message
from .init import init_project
from .tools import list_tools, add_tool
from .run import run_project
10 changes: 9 additions & 1 deletion agentstack/cli/agentstack_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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,
Expand Down
40 changes: 10 additions & 30 deletions agentstack/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def init_project_builder(
raise Exception("Project name is required. Use `agentstack init <project_name>`")

if slug_name and not is_snake_case(slug_name):
raise Exception("Project name must be snake case")
raise Exception("Project slug name must be snake_case")

if template is not None and use_wizard:
raise Exception("Template and wizard flags cannot be used together")
Expand Down Expand Up @@ -83,15 +83,13 @@ def init_project_builder(
tools = [tools.model_dump() for tools in template_data.tools]

elif 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()
# the user has started a new project; let's give them something to work with
default_project = TemplateConfig.from_template_name('hello_alex')
project_details = {
Expand All @@ -112,9 +110,6 @@ def init_project_builder(
log.debug(f"project_details: {project_details}" f"framework: {framework}" f"design: {design}")
insert_template(project_details, framework, design, template_data)

# we have an agentstack.json file in the directory now
conf.set_path(project_details['name'])

for tool_data in tools:
generation.add_tool(tool_data['name'], agents=tool_data['agents'])

Expand Down Expand Up @@ -384,7 +379,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"]
Expand All @@ -406,8 +404,8 @@ def insert_template(
f'{template_path}/{"{{cookiecutter.project_metadata.project_slug}}"}/.env',
)

if os.path.isdir(project_details['name']):
raise Exception(f"Directory {template_path} already exists. Project directory must not exist.")
if os.path.exists(project_details['name']):
raise Exception(f"Directory {project_details['name']} already exists. Project directory must not exist.")

cookiecutter(str(template_path), no_input=True, extra_context=None)

Expand All @@ -421,26 +419,6 @@ def insert_template(
except:
print("Failed to initialize git repository. Maybe you're already in one? Do this with: git init")

# TODO: check if poetry is installed and if so, run poetry install in the new directory
# os.system("poetry install")
# os.system("cls" if os.name == "nt" else "clear")
# TODO: add `agentstack docs` command
log.info(
"\n"
"🚀 \033[92mAgentStack project generated successfully!\033[0m\n\n"
" Next, run:\n"
f" cd {project_metadata.project_slug}\n"
" python -m venv .venv\n"
" source .venv/bin/activate\n\n"
" Make sure you have the latest version of poetry installed:\n"
" pip install -U poetry\n\n"
" You'll need to install the project's dependencies with:\n"
" poetry install\n\n"
" Finally, try running your agent with:\n"
" agentstack run\n\n"
" Run `agentstack quickstart` or `agentstack docs` for next steps.\n"
)


def export_template(output_filename: str):
"""
Expand All @@ -460,6 +438,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)
)
)
Expand Down Expand Up @@ -496,11 +475,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,
Expand Down
72 changes: 72 additions & 0 deletions agentstack/cli/init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import os, sys
from typing import Optional
from pathlib import Path
from agentstack import conf
from agentstack import packaging
from agentstack.cli import welcome_message, init_project_builder
from agentstack.utils import term_color


# TODO move the rest of the CLI init tooling into this file


def require_uv():
try:
uv_bin = packaging.get_uv_bin()
assert os.path.exists(uv_bin)
except (AssertionError, ImportError):
print(term_color("Error: uv is not installed.", 'red'))
print("Full installation instructions at: https://docs.astral.sh/uv/getting-started/installation")
match sys.platform:
case 'linux' | 'darwin':
print("Hint: run `curl -LsSf https://astral.sh/uv/install.sh | sh`")
case _:
pass
sys.exit(1)


def init_project(
slug_name: Optional[str] = None,
template: Optional[str] = None,
use_wizard: bool = False,
):
"""
Initialize a new project in the current directory.
- create a new virtual environment
- copy project skeleton
- install dependencies
"""
require_uv()

# TODO prevent the user from passing the --path arguent to init
if slug_name:
conf.set_path(conf.PATH / slug_name)
else:
print("Error: No project directory specified.")
print("Run `agentstack init <project_name>`")
sys.exit(1)

if os.path.exists(conf.PATH): # cookiecutter requires the directory to not exist
print(f"Error: Directory already exists: {conf.PATH}")
sys.exit(1)

welcome_message()
print(term_color("🦾 Creating a new AgentStack project...", 'blue'))
print(f"Using project directory: {conf.PATH.absolute()}")

# copy the project skeleton, create a virtual environment, and install dependencies
init_project_builder(slug_name, template, use_wizard)
packaging.create_venv()
packaging.install_project()

print(
"\n"
"🚀 \033[92mAgentStack project generated successfully!\033[0m\n\n"
" To get started, activate the virtual environment with:\n"
f" cd {conf.PATH}\n"
" source .venv/bin/activate\n\n"
" Run your new agent with:\n"
" agentstack run\n\n"
" Or, run `agentstack quickstart` or `agentstack docs` for more next steps.\n"
)
19 changes: 12 additions & 7 deletions agentstack/cli/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@ def list_tools():
List all available tools by category.
"""
tools = get_all_tools()
curr_category = None

categories = {}

# Group tools by category
for tool in tools:
if tool.category not in categories:
categories[tool.category] = []
categories[tool.category].append(tool)

print("\n\nAvailable AgentStack Tools:")
for category, tools in itertools.groupby(tools, lambda x: x.category):
if curr_category != category:
print(f"\n{category}:")
curr_category = category
for tool in tools:
# Display tools by category
for category in sorted(categories.keys()):
print(f"\n{category}:")
for tool in categories[category]:
print(" - ", end='')
print(term_color(f"{tool.name}", 'blue'), end='')
print(f": {tool.url if tool.url else 'AgentStack default tool'}")
Expand Down
8 changes: 7 additions & 1 deletion agentstack/conf.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import Optional, Union
import os, sys
import os
import json
from pathlib import Path
from pydantic import BaseModel
Expand All @@ -14,6 +14,12 @@
# The path to the project directory ie. working directory.
PATH: Path = Path()

def assert_project() -> None:
try:
ConfigFile()
return
except FileNotFoundError:
raise Exception("Could not find agentstack.json, are you in an AgentStack project directory?")

def set_path(path: Union[str, Path, None]):
"""Set the path to the project directory."""
Expand Down
9 changes: 9 additions & 0 deletions agentstack/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,12 @@ class ValidationError(Exception):
"""

pass


class EnvironmentError(Exception):
"""
Raised when an error occurs in the execution environment ie. a command is
not present or the environment is not configured as expected.
"""

pass
Loading

0 comments on commit c93b58e

Please sign in to comment.