Skip to content

Deploy command #173

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 58 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
d8e7afa
serve
bboynton97 Nov 19, 2024
7a60fc0
fix serve file and dockerfile
bboynton97 Nov 19, 2024
7f88cc0
serve from inside source
bboynton97 Nov 19, 2024
b067837
load dotenv
bboynton97 Nov 19, 2024
606fd44
load dotenv from one up
bboynton97 Nov 20, 2024
a240d5d
health endpoint
bboynton97 Nov 20, 2024
b9d9942
env path
bboynton97 Nov 20, 2024
e25b51a
loadenv before import
bboynton97 Nov 20, 2024
8c13295
Merge branch 'main' into deploy
bboynton97 Nov 27, 2024
83fc0c7
Merge branch 'main' into deploy
bboynton97 Dec 3, 2024
77657f3
Merge branch 'main' into deploy
bboynton97 Dec 4, 2024
6ccbcda
Merge branch 'main' into deploy
bboynton97 Dec 4, 2024
2d80e52
Merge branch 'main' into deploy
bboynton97 Dec 5, 2024
f51de09
Merge branch 'main' into deploy
bboynton97 Dec 5, 2024
6896a22
create project on deploy
bboynton97 Dec 23, 2024
15d2cab
Merge branch 'main' into deploy-command
bboynton97 Dec 27, 2024
6190572
Merge branch 'main' into deploy-command
bboynton97 Dec 30, 2024
41b9b9a
zip code and upload
bboynton97 Dec 30, 2024
033711a
Merge branch 'main' into deploy
bboynton97 Dec 30, 2024
80a1932
Merge branch 'main' into deploy-command
bboynton97 Jan 7, 2025
dcacaca
fix deploy
bboynton97 Jan 7, 2025
de2d4aa
Merge branch 'refs/heads/main' into deploy
bboynton97 Jan 7, 2025
ef3a3fd
serve working
bboynton97 Jan 7, 2025
a1a26bd
Merge branch 'main' into deploy-command
bboynton97 Jan 14, 2025
140b80f
Merge branch 'main' into deploy-command
bboynton97 Jan 15, 2025
71f474f
build all but some folders
bboynton97 Jan 20, 2025
066fa2d
Merge branch 'main' into deploy-command
bboynton97 Jan 22, 2025
620f7fb
better upload
bboynton97 Jan 22, 2025
d8055bd
spinner tests
bboynton97 Jan 22, 2025
3fd8ef1
deploy tests
bboynton97 Jan 22, 2025
044e053
logging
bboynton97 Jan 22, 2025
d2590e0
use websockets
bboynton97 Jan 22, 2025
9ee0ecd
Merge branch 'deploy' into deploy-command
bboynton97 Jan 22, 2025
325d76e
use proper dockerfile
bboynton97 Jan 23, 2025
7883198
Merge branch 'main' into deploy-command
bboynton97 Feb 3, 2025
4da6cf7
Merge branch 'main' into deploy-command
bboynton97 Feb 5, 2025
4b31001
build, push, track, webhooks
bboynton97 Feb 5, 2025
6a30280
serve work
bboynton97 Feb 6, 2025
0ad98ea
dockerfile works and new run func
bboynton97 Feb 6, 2025
2b94a9f
import fix
bboynton97 Feb 6, 2025
023b3ce
run proj from serve.py
bboynton97 Feb 7, 2025
72161d3
if main at bottom
bboynton97 Feb 7, 2025
35d94a4
input handling
bboynton97 Feb 7, 2025
0a7eba7
log import
bboynton97 Feb 7, 2025
cf49ff8
log import fix
bboynton97 Feb 7, 2025
bf7fd6e
todos
bboynton97 Feb 7, 2025
4b5a178
update serve
bboynton97 Feb 10, 2025
dc3cbf4
Merge branch 'main' into deploy-command
bboynton97 Feb 11, 2025
8c7ea35
use gunincorn
bboynton97 Feb 11, 2025
f578b00
fix name
bboynton97 Feb 12, 2025
3cdb963
return before processing
bboynton97 Feb 12, 2025
5378071
return result
bboynton97 Feb 12, 2025
abd383b
return session_id
bboynton97 Feb 12, 2025
318c4b8
guid to string
bboynton97 Feb 12, 2025
e4991eb
use waitress
bboynton97 Feb 20, 2025
94ee47f
use hosted
bboynton97 Feb 21, 2025
9c96e45
remove waitress import
bboynton97 Feb 22, 2025
f8e69e4
end deploy logs on finish deploy
bboynton97 Feb 22, 2025
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
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
recursive-include agentstack/templates *
recursive-include agentstack/frameworks/templates *
recursive-include agentstack/_tools *
recursive-include agentstack/serve/serve.py
include agentstack.json .env .env.example
317 changes: 316 additions & 1 deletion agentstack/cli/cli.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
from typing import Optional
import os, sys
import importlib
from typing import Optional
import os
import time
from datetime import datetime

import json
import shutil
from art import text2art
import inquirer
from agentstack import conf, log
from agentstack.cli.agentstack_data import CookiecutterData, ProjectStructure, ProjectMetadata, FrameworkData
from agentstack.conf import ConfigFile
from agentstack.generation import InsertionPoint, ProjectFile
from agentstack import frameworks
from agentstack import inputs
from agentstack.agents import get_all_agents
from agentstack.tasks import get_all_tasks
from agentstack.utils import get_package_path, open_json_file, term_color, is_snake_case, get_framework, \
validator_not_empty, verify_agentstack_project
from agentstack.templates import TemplateConfig
from agentstack.exceptions import ValidationError
from agentstack.utils import validator_not_empty, is_snake_case
from agentstack.generation import InsertionPoint


PREFERRED_MODELS = [
Expand Down Expand Up @@ -81,6 +97,305 @@ def get_validated_input(
return value


def ask_agent_details():
agent = {}

agent['name'] = get_validated_input(
"What's the name of this agent? (snake_case)", min_length=3, snake_case=True
)

agent['role'] = get_validated_input("What role does this agent have?", min_length=3)

agent['goal'] = get_validated_input("What is the goal of the agent?", min_length=10)

agent['backstory'] = get_validated_input("Give your agent a backstory", min_length=10)

agent['model'] = inquirer.list_input(
message="What LLM should this agent use?", choices=PREFERRED_MODELS, default=PREFERRED_MODELS[0]
)

return agent


def ask_task_details(agents: list[dict]) -> dict:
task = {}

task['name'] = get_validated_input(
"What's the name of this task? (snake_case)", min_length=3, snake_case=True
)

task['description'] = get_validated_input("Describe the task in more detail", min_length=10)

task['expected_output'] = get_validated_input(
"What do you expect the result to look like? (ex: A 5 bullet point summary of the email)",
min_length=10,
)

task['agent'] = inquirer.list_input(
message="Which agent should be assigned this task?",
choices=[a['name'] for a in agents],
)

return task


def ask_design() -> dict:
use_wizard = inquirer.confirm(
message="Would you like to use the CLI wizard to set up agents and tasks?",
)

if not use_wizard:
return {'agents': [], 'tasks': []}

os.system("cls" if os.name == "nt" else "clear")

title = text2art("AgentWizard", font="shimrod")

print(title)

print("""
🪄 welcome to the agent builder wizard!! 🪄

First we need to create the agents that will work together to accomplish tasks:
""")
make_agent = True
agents = []
while make_agent:
print('---')
print(f"Agent #{len(agents)+1}")
agent = None
agent = ask_agent_details()
agents.append(agent)
make_agent = inquirer.confirm(message="Create another agent?")

print('')
for x in range(3):
time.sleep(0.3)
print('.')
print('Boom! We made some agents (ノ>ω<)ノ :。・:*:・゚’★,。・:*:・゚’☆')
time.sleep(0.5)
print('')
print('Now lets make some tasks for the agents to accomplish!')
print('')

make_task = True
tasks = []
while make_task:
print('---')
print(f"Task #{len(tasks) + 1}")
task = ask_task_details(agents)
tasks.append(task)
make_task = inquirer.confirm(message="Create another task?")

print('')
for x in range(3):
time.sleep(0.3)
print('.')
print('Let there be tasks (ノ ˘_˘)ノ ζ|||ζ ζ|||ζ ζ|||ζ')

return {'tasks': tasks, 'agents': agents}


def ask_tools() -> list:
use_tools = inquirer.confirm(
message="Do you want to add agent tools now? (you can do this later with `agentstack tools add <tool_name>`)",
)

if not use_tools:
return []

tools_to_add = []

adding_tools = True
script_dir = os.path.dirname(os.path.abspath(__file__))
tools_json_path = os.path.join(script_dir, '..', 'tools', 'tools.json')

# Load the JSON data
tools_data = open_json_file(tools_json_path)

while adding_tools:
tool_type = inquirer.list_input(
message="What category tool do you want to add?",
choices=list(tools_data.keys()) + ["~~ Stop adding tools ~~"],
)

tools_in_cat = [f"{t['name']} - {t['url']}" for t in tools_data[tool_type] if t not in tools_to_add]
tool_selection = inquirer.list_input(message="Select your tool", choices=tools_in_cat)

tools_to_add.append(tool_selection.split(' - ')[0])

log.info("Adding tools:")
for t in tools_to_add:
log.info(f' - {t}')
log.info('')
adding_tools = inquirer.confirm("Add another tool?")

return tools_to_add


def ask_project_details(slug_name: Optional[str] = None) -> dict:
name = inquirer.text(message="What's the name of your project (snake_case)", default=slug_name or '')

if not is_snake_case(name):
log.error("Project name must be snake case")
return ask_project_details(slug_name)

questions = inquirer.prompt(
[
inquirer.Text("version", message="What's the initial version", default="0.1.0"),
inquirer.Text("description", message="Enter a description for your project"),
inquirer.Text("author", message="Who's the author (your name)?"),
]
)

questions['name'] = name

return questions


def insert_template(
project_details: dict,
framework_name: str,
design: dict,
template_data: Optional[TemplateConfig] = None,
):
framework = FrameworkData(
name=framework_name.lower(),
)
project_metadata = ProjectMetadata(
project_name=project_details["name"],
description=project_details["description"],
author_name=project_details["author"],
version="0.0.1",
license="MIT",
year=datetime.now().year,
template=template_data.name if template_data else 'none',
template_version=template_data.template_version if template_data else 0,
)

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"]

cookiecutter_data = CookiecutterData(
project_metadata=project_metadata,
structure=project_structure,
framework=framework_name.lower(),
)

template_path = get_package_path() / f'templates/{framework.name}'
with open(f"{template_path}/cookiecutter.json", "w") as json_file:
json.dump(cookiecutter_data.to_dict(), json_file)
# TODO this should not be written to the package directory

# copy .env.example to .env
shutil.copy(
f'{template_path}/{"{{cookiecutter.project_metadata.project_slug}}"}/.env.example',
f'{template_path}/{"{{cookiecutter.project_metadata.project_slug}}"}/.env',
)

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

# TODO: inits a git repo in the directory the command was run in
# TODO: not where the project is generated. Fix this
# TODO: also check if git is installed or if there are any git repos above the current dir
try:
pass
# subprocess.check_output(["git", "init"])
# subprocess.check_output(["git", "add", "."])
except:
print("Failed to initialize git repository. Maybe you're already in one? Do this with: git init")


def export_template(output_filename: str):
"""
Export the current project as a template.
"""
try:
metadata = ProjectFile()
except Exception as e:
raise Exception(f"Failed to load project metadata: {e}")

# Read all the agents from the project's agents.yaml file
agents: list[TemplateConfig.Agent] = []
for agent in get_all_agents():
agents.append(
TemplateConfig.Agent(
name=agent.name,
role=agent.role,
goal=agent.goal,
backstory=agent.backstory,
allow_delegation=False, # TODO
model=agent.llm, # TODO consistent naming (llm -> model)
)
)

# Read all the tasks from the project's tasks.yaml file
tasks: list[TemplateConfig.Task] = []
for task in get_all_tasks():
tasks.append(
TemplateConfig.Task(
name=task.name,
description=task.description,
expected_output=task.expected_output,
agent=task.agent,
)
)

# Export all of the configured tools from the project
tools_agents: dict[str, list[str]] = {}
for agent_name in frameworks.get_agent_names():
for tool_name in frameworks.get_agent_tool_names(agent_name):
if not tool_name:
continue
if tool_name not in tools_agents:
tools_agents[tool_name] = []
tools_agents[tool_name].append(agent_name)

tools: list[TemplateConfig.Tool] = []
for tool_name, agent_names in tools_agents.items():
tools.append(
TemplateConfig.Tool(
name=tool_name,
agents=agent_names,
)
)

template = TemplateConfig(
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,
inputs=inputs.get_inputs(),
)

try:
template.write_to_file(conf.PATH / output_filename)
log.success(f"Template saved to: {conf.PATH / output_filename}")
except Exception as e:
raise Exception(f"Failed to write template to file: {e}")


def serve_project():
verify_agentstack_project()

# TODO: only silence output conditionally - maybe a debug or verbose option
os.system("docker stop agentstack-local > /dev/null 2>&1")
os.system("docker rm agentstack-local > /dev/null 2>&1")
with importlib.resources.path('agentstack.serve', 'Dockerfile') as path:
os.system(f"docker build -t agent-service -f {path} . --progress=plain")
os.system("docker run --name agentstack-local -p 6969:6969 agent-service")


def parse_insertion_point(position: Optional[str] = None) -> Optional[InsertionPoint]:
"""
Parse an insertion point CLI argument into an InsertionPoint enum.
Expand Down
6 changes: 3 additions & 3 deletions agentstack/cli/run.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Optional, List
from typing import Optional, List, Dict
import sys
import asyncio
import traceback
Expand All @@ -16,7 +16,7 @@
MAIN_MODULE_NAME = "main"


def _format_friendly_error_message(exception: Exception):
def format_friendly_error_message(exception: Exception):
"""
Projects will throw various errors, especially on first runs, so we catch
them here and print a more helpful message.
Expand Down Expand Up @@ -133,4 +133,4 @@ def run_project(command: str = 'run', cli_args: Optional[List[str]] = None):
except ImportError as e:
raise ValidationError(f"Failed to import AgentStack project at: {conf.PATH.absolute()}\n{e}")
except Exception as e:
raise Exception(_format_friendly_error_message(e))
raise Exception(format_friendly_error_message(e))
Loading