Skip to content

Interactive Wizard #235

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

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
6c0eea2
Terminal User Interface
tcdent Jan 23, 2025
5a91d7b
TUI progress.
tcdent Jan 24, 2025
a0a6308
Remove unused file.
tcdent Jan 24, 2025
81433cd
Full wizard workflow complete.
tcdent Jan 28, 2025
dd8ebd5
Cleanup directory structure. Typing fixes.
tcdent Jan 28, 2025
f709902
Ensure template writing is only called once.
tcdent Jan 28, 2025
7b5523b
Merge branch 'main' into wizard
tcdent Jan 28, 2025
f3196d3
Attempt at resolving RadioSelect state sync.
tcdent Jan 28, 2025
32fdc64
Background color. Ruff formatting.
tcdent Jan 29, 2025
d5819a8
Deprecate `init --wizard` command.
tcdent Jan 29, 2025
784bbd9
Merge branch 'main' into wizard
tcdent Jan 29, 2025
56a9a2c
Bugfixes.
tcdent Jan 29, 2025
7bed6f7
Fix form alignment. Allow skipping adding a tool. Additional validati…
tcdent Jan 29, 2025
7152353
Fix type checking.
tcdent Jan 29, 2025
4bb0b3e
Define human readable tool categories.
tcdent Jan 29, 2025
257b393
Move preferred models to new `providers` module with validation and h…
tcdent Jan 29, 2025
935d1d2
Wizard model selection bugfix. Added tests to ensure frameworks can a…
tcdent Jan 29, 2025
3d1cefa
Better colors.
tcdent Jan 30, 2025
30f0723
Log cleanup. Dependency cleanup.
tcdent Jan 30, 2025
09bdcac
Merge branch 'main' into wizard
tcdent Feb 6, 2025
132c877
Live resize. Pull frameworks info from `frameworks` module.
tcdent Feb 6, 2025
67dc514
Allow adding more tools to an agent.
tcdent Feb 6, 2025
7dc0e58
Fix bug when selecting last element in a scrollable list.
tcdent Feb 6, 2025
b37e483
Fix centering of logo.
tcdent Feb 6, 2025
c11cb59
Renders ad 80x24
tcdent Feb 6, 2025
4120070
Ignore type.
tcdent Feb 6, 2025
f3ba4fe
Update llms.txt
actions-user Feb 7, 2025
d3db89e
Undraw previous star instead of clearing grid to prevent flicker.
tcdent Feb 10, 2025
16f0fd5
Merge branch 'main' into wizard
tcdent Feb 10, 2025
81e1149
Standardize categories for new tools.
tcdent Feb 10, 2025
8531d46
Placeholder help text in wizard fields.
tcdent Feb 10, 2025
fa4f7ca
Docstrings for TUI.
tcdent Feb 10, 2025
4cc52c8
wizard smile
bboynton97 Feb 10, 2025
d2b063f
Merge branch 'wizard' of github.com:tcdent/AgentStack into wizard
bboynton97 Feb 10, 2025
a3e6642
fix proj_template import
bboynton97 Feb 13, 2025
d1e8f6a
16 color support.
tcdent Feb 14, 2025
d411d1e
Placeholder help text in wizard fields.
tcdent Feb 10, 2025
2fcbf23
Docstrings for TUI.
tcdent Feb 10, 2025
87df3dd
Restore variable.
tcdent Feb 14, 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
24 changes: 24 additions & 0 deletions agentstack/_tools/__init__.py
Original file line number Diff line number Diff line change
@@ -13,6 +13,12 @@
TOOLS_CONFIG_FILENAME: str = 'config.json'


class ToolCategory(pydantic.BaseModel):
name: str
title: str # human readable title
description: str


class ToolConfig(pydantic.BaseModel):
"""
This represents the configuration data for a tool.
@@ -100,6 +106,19 @@ def module(self) -> ModuleType:
)


def get_all_tool_categories() -> list[ToolCategory]:
categories = []
filename = TOOLS_DIR / 'categories.json'
data = open_json_file(filename)
for name, category in data.items():
categories.append(ToolCategory(name=name, **category))
return categories


def get_all_tool_category_names() -> list[str]:
return [category.name for category in get_all_tool_categories()]


def get_all_tool_paths() -> list[Path]:
"""
Get all the paths to the tool configuration files.
@@ -121,3 +140,8 @@ def get_all_tool_names() -> list[str]:

def get_all_tools() -> list[ToolConfig]:
return [ToolConfig.from_tool_name(path) for path in get_all_tool_names()]


def get_tool(name: str) -> ToolConfig:
return ToolConfig.from_tool_name(name)

46 changes: 46 additions & 0 deletions agentstack/_tools/categories.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"browsing": {
"title": "Browsing",
"description": "Tools that are used to browse the web."
},
"code-execution": {
"title": "Code Execution",
"description": "Tools that are used to execute code."
},
"computer-control": {
"title": "Computer Control",
"description": "Tools that are used to control a computer."
},
"database": {
"title": "Database",
"description": "Tools that are used to interact with databases."
},
"finance": {
"title": "Finance",
"description": "Tools that are used to interact with financial services."
},
"image-analysis": {
"title": "Image Analysis",
"description": "Tools that are used to analyze images."
},
"network-protocols": {
"title": "Network Protocols",
"description": "Tools that are used to interact with network protocols."
},
"search": {
"title": "Search",
"description": "Tools that are used to search for information."
},
"storage": {
"title": "Storage",
"description": "Tools that are used to interact with storage."
},
"unified-apis": {
"title": "Unified APIs",
"description": "Tools that provide a unified API for interacting with multiple services."
},
"web-retrieval": {
"title": "Web Retrieval",
"description": "Tools that are used to retrieve information from the web."
}
}
2 changes: 1 addition & 1 deletion agentstack/_tools/payman/config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "payman",
"category": "financial-infra",
"category": "finance",
"tools": [
"send_payment",
"search_available_payees",
2 changes: 1 addition & 1 deletion agentstack/_tools/stripe/config.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "stripe",
"url": "https://github.com/stripe/agent-toolkit",
"category": "application-specific",
"category": "finance",
"env": {
"STRIPE_SECRET_KEY": null
},
2 changes: 1 addition & 1 deletion agentstack/_tools/weaviate/config.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "weaviate",
"url": "https://github.com/weaviate/weaviate-python-client",
"category": "vector-store",
"category": "database",
"env": {
"WEAVIATE_URL": null,
"WEAVIATE_API_KEY": null,
4 changes: 1 addition & 3 deletions agentstack/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
from .cli import configure_default_model, welcome_message, get_validated_input, parse_insertion_point
from .cli import LOGO, configure_default_model, welcome_message, get_validated_input, parse_insertion_point
from .init import init_project
from .wizard import run_wizard
from .run import run_project
from .tools import list_tools, add_tool, remove_tool
from .tasks import add_task
from .agents import add_agent
from .templates import insert_template, export_template

27 changes: 12 additions & 15 deletions agentstack/cli/cli.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,31 @@
from typing import Optional
import os, sys
from art import text2art
import inquirer
from agentstack import conf, log
from agentstack.conf import ConfigFile
from agentstack.exceptions import ValidationError
from agentstack.utils import validator_not_empty, is_snake_case
from agentstack import providers
from agentstack.generation import InsertionPoint


PREFERRED_MODELS = [
'groq/deepseek-r1-distill-llama-70b',
'deepseek/deepseek-chat',
'deepseek/deepseek-coder',
'deepseek/deepseek-reasoner',
'openai/gpt-4o',
'anthropic/claude-3-5-sonnet',
'openai/o1-preview',
'openai/gpt-4-turbo',
'anthropic/claude-3-opus',
]
LOGO = """\
___ ___ ___ ___ ___ ___ ___ ___ ___ ___
/\ \ /\ \ /\ \ /\__\ /\ \ /\ \ /\ \ /\ \ /\ \ /\__\
/::\ \ /::\ \ /::\ \ /:| _|_ \:\ \ /::\ \ \:\ \ /::\ \ /::\ \ /:/ _/_
/::\:\__\ /:/\:\__\ /::\:\__\ /::|/\__\ /::\__\ /\:\:\__\ /::\__\ /::\:\__\ /:/\:\__\ /::-"\__\\
\/\::/ / \:\:\/__/ \:\:\/ / \/|::/ / /:/\/__/ \:\:\/__/ /:/\/__/ \/\::/ / \:\ \/__/ \;:;-",-"
/:/ / \::/ / \:\/ / |:/ / \/__/ \::/ / \/__/ /:/ / \:\__\ |:| |
\/__/ \/__/ \/__/ \/__/ \/__/ \/__/ \/__/ \|__|
"""


def welcome_message():
title = text2art("AgentStack", font="smisome1")
tagline = "The easiest way to build a robust agent application!"
border = "-" * len(tagline)

# Print the welcome message with ASCII art
log.info(title)
log.info(LOGO)
log.info(border)
log.info(tagline)
log.info(border)
@@ -45,7 +42,7 @@ def configure_default_model():
other_msg = "Other (enter a model name)"
model = inquirer.list_input(
message="Which model would you like to use?",
choices=PREFERRED_MODELS + [other_msg],
choices=providers.get_preferred_model_ids() + [other_msg],
)

if model == other_msg: # If the user selects "Other", prompt for a model name
14 changes: 4 additions & 10 deletions agentstack/cli/init.py
Original file line number Diff line number Diff line change
@@ -14,7 +14,6 @@
from agentstack.templates import get_all_templates, TemplateConfig

from agentstack.cli import welcome_message
from agentstack.cli.wizard import run_wizard
from agentstack.cli.templates import insert_template


@@ -67,7 +66,7 @@ def init_project(
slug_name: Optional[str] = None,
template: Optional[str] = None,
framework: Optional[str] = None,
use_wizard: bool = False,
template_data: Optional[TemplateConfig] = None,
):
"""
Initialize a new project in the current directory.
@@ -78,9 +77,6 @@ def init_project(
- insert Tasks, Agents and Tools
"""
# TODO prevent the user from passing the --path argument to init
if template and use_wizard:
raise Exception("Template and wizard flags cannot be used together")

require_uv()
welcome_message()

@@ -102,16 +98,14 @@ def init_project(
if os.path.exists(conf.PATH): # cookiecutter requires the directory to not exist
raise Exception(f"Directory already exists: {conf.PATH}")

if use_wizard:
log.debug("Initializing new project with wizard.")
template_data = run_wizard(slug_name)
elif template:
if not template_data and template:
log.debug(f"Initializing new project with template: {template}")
template_data = TemplateConfig.from_user_input(template)
else:
elif not template_data:
log.debug("Initializing new project with template selection.")
template_data = select_template(slug_name, framework)

assert template_data # appease type checker
log.notify("🦾 Creating a new AgentStack project...")
log.info(f"Using project directory: {conf.PATH.absolute()}")

1,291 changes: 1,063 additions & 228 deletions agentstack/cli/wizard.py

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions agentstack/frameworks/__init__.py
Original file line number Diff line number Diff line change
@@ -54,6 +54,8 @@ class FrameworkModule(Protocol):
Protocol spec for a framework implementation module.
"""

NAME: str # Human readable name of the framework
DESCRIPTION: str # Human readable description of the framework
ENTRYPOINT: Path
"""
Relative path to the entrypoint file for the framework in the user's project.
@@ -302,6 +304,17 @@ def get_framework_module(framework: str) -> FrameworkModule:
raise Exception(f"Framework {framework} could not be imported.")


def get_framework_info(framework: str) -> dict[str, str]:
"""
Get the info for a framework.
"""
_module = get_framework_module(framework)
return {
'name': _module.NAME,
'description': _module.DESCRIPTION,
}


def get_entrypoint_path(framework: str) -> Path:
"""
Get the path to the entrypoint file for a framework.
4 changes: 4 additions & 0 deletions agentstack/frameworks/crewai.py
Original file line number Diff line number Diff line change
@@ -13,6 +13,10 @@


NAME: str = "CrewAI"
DESCRIPTION: str = (
"Framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative "
"intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks."
)
ENTRYPOINT: Path = Path('src/crew.py')


5 changes: 4 additions & 1 deletion agentstack/frameworks/langgraph.py
Original file line number Diff line number Diff line change
@@ -13,8 +13,11 @@
from agentstack.tasks import TaskConfig, get_all_task_names
from agentstack import graph


NAME: str = "LangGraph"
DESCRIPTION: str = (
"A library for building stateful, multi-actor applications with LLMs, used to create "
"agent and multi-agent workflows."
)
ENTRYPOINT: Path = Path('src/graph.py')

GRAPH_NODE_START = 'START'
3 changes: 3 additions & 0 deletions agentstack/frameworks/llamaindex.py
Original file line number Diff line number Diff line change
@@ -12,6 +12,9 @@
from agentstack import graph

NAME: str = "LLamaIndex"
DESCRIPTION: str = (
"LlamaIndex is the leading framework for building LLM-powered agents over your data."
)
ENTRYPOINT: Path = Path('src/stack.py')

PROVIDERS = {
4 changes: 4 additions & 0 deletions agentstack/frameworks/openai_swarm.py
Original file line number Diff line number Diff line change
@@ -13,6 +13,10 @@


NAME: str = "OpenAI Swarm"
DESCRIPTION: str = (
"Educational framework exploring ergonomic, lightweight multi-agent orchestration. "
"Managed by OpenAI Solution team."
)
ENTRYPOINT: Path = Path('src/stack.py')


15 changes: 13 additions & 2 deletions agentstack/main.py
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@
run_project,
export_template,
)
from agentstack.cli import wizard
from agentstack.telemetry import track_cli_command, update_telemetry
from agentstack.utils import get_version, term_color
from agentstack import generation
@@ -68,10 +69,14 @@ def _main():
"init", aliases=["i"], help="Initialize a directory for the project", parents=[global_parser]
)
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("--wizard", "-w", action="store_true", help="Use the setup wizard [deprecated]")
init_parser.add_argument("--template", "-t", help="Agent template to use")
init_parser.add_argument("--framework", "-f", help="Framework to use")

wizard_parser = subparsers.add_parser(
"wizard", help="Run the setup wizard", parents=[global_parser]
)

# 'run' command
run_parser = subparsers.add_parser(
"run",
@@ -186,7 +191,13 @@ def _main():
elif args.command in ["templates"]:
webbrowser.open("https://docs.agentstack.sh/quickstart")
elif args.command in ["init", "i"]:
init_project(args.slug_name, args.template, args.framework, args.wizard)
if args.wizard:
log.warning("init --wizard is deprecated. Use `agentstack wizard`")
wizard.main()
else:
init_project(args.slug_name, args.template, args.framework)
elif args.command in ["wizard"]:
wizard.main()
elif args.command in ["tools", "t"]:
if args.tools_command in ["list", "l"]:
list_tools()
62 changes: 61 additions & 1 deletion agentstack/providers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,66 @@
from typing import Optional
import re
import pydantic
from agentstack.exceptions import ValidationError

# model ids follow LiteLLM format
PREFERRED_MODELS = {
'groq/deepseek-r1-distill-llama-70b': {
'name': "DeepSeek R1 Distill Llama 70B",
'host': "Groq",
'description': "The Groq DeepSeek R1 Distill Llama 70B model",
},
'deepseek/deepseek-reasoner': {
'name': "DeepSeek Reasoner",
'host': "DeepSeek",
'description': "The DeepSeek Reasoner model hosted by DeepSeek",
},
'openai/o1-preview': {
'name': "o1 Preview",
'host': "OpenAI",
'description': "The OpenAI o1 Preview model",
},
'anthropic/claude-3-5-sonnet': {
'name': "Claude 3.5 Sonnet",
'host': "Anthropic",
'description': "The Anthropic Claude 3.5 Sonnet model",
},
# TODO there is no publicly available OpenRouter implementation for
# LangChain, so we can't recommend this yet.
# 'openrouter/deepseek/deepseek-r1': {
# 'name': "DeepSeek R1",
# 'host': "OpenRouter",
# 'description': "The DeepSeek R1 model hosted by OpenRouter",
# },
'openai/gpt-4o': {
'name': "GPT-4o",
'host': "OpenAI",
'description': "The OpenAI GPT-4o model",
},
'anthropic/claude-3-opus': {
'name': "Claude 3 Opus",
'host': "Anthropic",
'description': "The Anthropic Claude 3 Opus model",
},
}


class ProviderConfig(pydantic.BaseModel):
id: str
name: Optional[str]
host: Optional[str]
description: Optional[str]
provider = property(lambda self: parse_provider_model(self.id)[0])
model = property(lambda self: parse_provider_model(self.id)[1])


def get_preferred_models() -> list[ProviderConfig]:
return [ProviderConfig(id=model_id, **model) for model_id, model in PREFERRED_MODELS.items()]


def get_preferred_model_ids() -> list[str]:
return [model.id for model in get_preferred_models()]


def parse_provider_model(model_id: str) -> tuple[str, str]:
"""Parse the provider and model name from the model ID"""
@@ -12,4 +73,3 @@ def parse_provider_model(model_id: str) -> tuple[str, str]:
return '/'.join(parts[:2]), parts[2]
else:
raise ValidationError(f"Model id '{model_id}' does not match expected format.")

10 changes: 5 additions & 5 deletions agentstack/templates/__init__.py
Original file line number Diff line number Diff line change
@@ -178,16 +178,16 @@ class TemplateConfig(pydantic.BaseModel):

class Agent(pydantic.BaseModel):
name: str
role: str
goal: str
backstory: str
role: Optional[str]
goal: Optional[str]
backstory: Optional[str]
allow_delegation: bool = False
llm: str

class Task(pydantic.BaseModel):
name: str
description: str
expected_output: str
description: Optional[str]
expected_output: Optional[str]
agent: str # TODO this is redundant with the graph

class Tool(pydantic.BaseModel):
1,376 changes: 1,376 additions & 0 deletions agentstack/tui.py

Large diffs are not rendered by default.

59 changes: 51 additions & 8 deletions docs/llms.txt
Original file line number Diff line number Diff line change
@@ -514,10 +514,6 @@ which adheres to a common pattern or exporting your project to share.
Templates are versioned, and each previous version provides a method to convert
it's content to the current version.

> TODO: Templates are currently identified as `proj_templates` since they conflict
with the templates used by `generation`. Move existing templates to be part of
the generation package.

### `TemplateConfig.from_user_input(identifier: str)`
`<TemplateConfig>` Returns a `TemplateConfig` object for either a URL, file path,
or builtin template name.
@@ -716,7 +712,7 @@ title: 'System Analyzer'
description: 'Inspect a project directory and improve it'
---

[View Template](https://github.com/AgentOps-AI/AgentStack/blob/main/agentstack/templates/proj_templates/system_analyzer.json)
[View Template](https://github.com/AgentOps-AI/AgentStack/blob/main/agentstack/templates/system_analyzer.json)

```bash
agentstack init --template=system_analyzer
@@ -737,7 +733,7 @@ title: 'Researcher'
description: 'Research and report result from a query'
---

[View Template](https://github.com/AgentOps-AI/AgentStack/blob/main/agentstack/templates/proj_templates/research.json)
[View Template](https://github.com/AgentOps-AI/AgentStack/blob/main/agentstack/templates/research.json)

```bash
agentstack init --template=research
@@ -828,7 +824,54 @@ title: 'Content Creator'
description: 'Research a topic and create content on it'
---

[View Template](https://github.com/AgentOps-AI/AgentStack/blob/main/agentstack/templates/proj_templates/content_creator.json)
[View Template](https://github.com/AgentOps-AI/AgentStack/blob/main/agentstack/templates/content_creator.json)

## frameworks/list.mdx

---
title: Frameworks
description: 'Supported frameworks in AgentStack'
icon: 'ship'
---

These are documentation links to the frameworks supported directly by AgentStack.

To start a project with one of these frameworks, use
```bash
agentstack init <project_name> --framework <framework_name>
```

## Framework Docs
<CardGroup cols={3}>
<Card
title="CrewAI"
icon="ship"
href="https://docs.crewai.com/introduction"
>
An intuitive agentic framework (recommended)
</Card>
<Card
title="LangGraph"
icon="circle-nodes"
href="https://langchain-ai.github.io/langgraph/"
>
A complex but capable framework with a _steep_ learning curve
</Card>
<Card
title="OpenAI Swarms"
icon="bee"
href="https://github.com/openai/swarm"
>
A simple framework with a cult following
</Card>
<Card
title="LlamaIndex"
icon="layer-group"
href="https://docs.llamaindex.ai/en/stable/"
>
An expansive framework with many ancillary features
</Card>
</CardGroup>

## tools/package-structure.mdx

@@ -1043,7 +1086,7 @@ You can pass the `--wizard` flag to `agentstack init` to use an interactive proj
You can also pass a `--template=<template_name>` argument to `agentstack init` which will pre-populate your project with functionality
from a built-in template, or one found on the internet. A `template_name` can be one of three identifiers:

- A built-in AgentStack template (see the `templates/proj_templates` directory in the AgentStack repo for bundled templates).
- A built-in AgentStack template (see the `templates` directory in the AgentStack repo for bundled templates).
- A template file from the internet; pass the full https URL of the template.
- A local template file; pass an absolute or relative path.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ dependencies = [
"agentops>=0.3.18",
"typer>=0.12.5",
"inquirer>=3.4.0",
"art>=6.3",
"pyfiglet==1.0.2",
"toml>=0.10.2",
"ruamel.yaml.base>=0.3.2",
"cookiecutter==2.6.0",
9 changes: 9 additions & 0 deletions tests/test_frameworks.py
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@
from agentstack._tools import ToolConfig, get_all_tools
from agentstack.agents import AGENTS_FILENAME, AgentConfig
from agentstack.tasks import TASKS_FILENAME, TaskConfig
from agentstack.providers import get_preferred_model_ids
from agentstack import graph

BASE_PATH = Path(__file__).parent
@@ -144,6 +145,14 @@ def test_get_agent_tool_names(self):
tool_names = frameworks.get_agent_tool_names('agent_name')
assert tool_names == ['test_tool']

@parameterized.expand([(x, ) for x in get_preferred_model_ids()])
def test_add_agent_preferred_models(self, llm: str):
"""Test adding an Agent to the graph with all preferred models we support"""
self._populate_min_entrypoint()
agent = self._get_test_agent()
agent.llm = llm
frameworks.add_agent(agent)

def test_add_tool(self):
self._populate_max_entrypoint()
frameworks.add_tool(self._get_test_tool(), 'agent_name')
14 changes: 13 additions & 1 deletion tests/test_providers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import unittest
from agentstack.exceptions import ValidationError
from agentstack.providers import (
PREFERRED_MODELS,
ProviderConfig,
parse_provider_model,
get_preferred_model_ids,
get_preferred_models,
)


@@ -23,8 +27,16 @@ def test_parse_provider_model(self):
]
for case, expect in zip(cases, expected):
self.assertEqual(parse_provider_model(case), expect)

def test_invalid_provider_model(self):
with self.assertRaises(ValidationError):
parse_provider_model("invalid_provider_model")

def test_all_preferred_provider_config(self):
for model in get_preferred_models():
self.assertIsInstance(model, ProviderConfig)

def test_all_preferred_model_ids(self):
for model_id in get_preferred_model_ids():
self.assertIsInstance(model_id, str)

26 changes: 25 additions & 1 deletion tests/test_tool_config.py
Original file line number Diff line number Diff line change
@@ -2,7 +2,16 @@
import unittest
import re
from pathlib import Path
from agentstack._tools import ToolConfig, get_all_tool_paths, get_all_tool_names
from agentstack.exceptions import ValidationError
from agentstack._tools import (
ToolConfig,
get_all_tools,
get_all_tool_paths,
get_all_tool_names,
ToolCategory,
get_all_tool_categories,
get_all_tool_category_names,
)

BASE_PATH = Path(__file__).parent

@@ -43,6 +52,17 @@ def test_dependency_versions(self):
"All dependencies must include version specifications."
)

def test_tool_category(self):
categories = get_all_tool_categories()
assert categories
for category in categories:
assert category.name in get_all_tool_category_names()
assert isinstance(category, ToolCategory)

def test_all_tools_have_valid_categories(self):
for tool_config in get_all_tools():
assert tool_config.category in get_all_tool_category_names()

def test_all_json_configs_from_tool_name(self):
for tool_name in get_all_tool_names():
config = ToolConfig.from_tool_name(tool_name)
@@ -60,3 +80,7 @@ def test_all_json_configs_from_tool_path(self):
)

assert config.name == path.stem

def test_get_missing_tool(self):
with self.assertRaises(ValidationError):
ToolConfig.from_tool_name("missing_tool")