Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
76 changes: 76 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: CI

on:
push:
pull_request:

jobs:
# ------------------------------------------------------------------
# Job 1: run pytest — every branch, every PR
# ------------------------------------------------------------------
test:
name: pytest
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip

- name: Install dependencies
run: pip install -r requirements.txt

- name: Run tests
run: pytest tests/ -v

# ------------------------------------------------------------------
# Job 2: build & push Docker image to GHCR
# Runs only on pushes to main or tag pushes; requires tests to pass.
# ------------------------------------------------------------------
build-and-push:
name: Build and push Docker image
runs-on: ubuntu-latest
needs: test
if: |
github.event_name == 'push' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/'))

permissions:
contents: read
packages: write

steps:
- uses: actions/checkout@v4

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Derive image tags
id: tags
run: |
REGISTRY="ghcr.io/${{ github.repository }}"
REF_SLUG="${GITHUB_REF_NAME//\//-}" # replace '/' with '-' for branch names
SHORT_SHA="${GITHUB_SHA::8}"

TAGS="${REGISTRY}:${REF_SLUG},${REGISTRY}:${SHORT_SHA}"

# Add :latest only for pushes to main
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
TAGS="${TAGS},${REGISTRY}:latest"
fi

echo "tags=${TAGS}" >> "$GITHUB_OUTPUT"

- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.tags.outputs.tags }}
74 changes: 0 additions & 74 deletions .gitlab-ci.yml

This file was deleted.

9 changes: 9 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
repos:
- repo: local
hooks:
- id: pytest
name: pytest
entry: pytest tests/
language: system
pass_filenames: false
always_run: true
126 changes: 77 additions & 49 deletions agents.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from typing import Callable

from omegaconf import DictConfig

from smolagents import CodeAgent, OpenAIServerModel, WikipediaSearchTool, tool, LogLevel
from tools import opencti_tools, osint_tools, todays_date
from tools import opencti_tools, opencti_write_tools, osint_tools, attack_tools, todays_date


def create_llm_model(model_id: str, api_key: str, api_base: str = "https://openrouter.ai/api/v1") -> OpenAIServerModel:
Expand All @@ -14,68 +16,94 @@ def create_llm_model(model_id: str, api_key: str, api_base: str = "https://openr
max_tokens=10_000,
)


# Registry: maps a tool-set name to a factory that returns a list of tool instances.
# Adding a new tool group = one dict entry.
_TOOL_REGISTRY: dict[str, Callable[[], list]] = {
"opencti": lambda: [tool(t) for t in opencti_tools],
"opencti_write": lambda: [tool(t) for t in opencti_write_tools],
"osint": lambda: [tool(t) for t in osint_tools],
"attack": lambda: [tool(t) for t in attack_tools],
"wikipedia": lambda: [WikipediaSearchTool()],
"todays_date": lambda: [tool(todays_date)],
}


def get_agent_tools(tool_names: list[str] | str) -> list:
"""Gets the tool instances from their names."""
if isinstance(tool_names, str):
tool_names = [tool_names] if tool_names != "none" else []

tools = []
for name in tool_names:
if name == "opencti":
tools.extend([tool(t) for t in opencti_tools])
elif name == "osint":
tools.extend([tool(t) for t in osint_tools])
elif name == "wikipedia":
tools.append(WikipediaSearchTool())
elif name == "todays_date":
tools.append(tool(todays_date))
else:
factory = _TOOL_REGISTRY.get(name)
if factory is None:
raise ValueError(f"Unknown tool name: {name}")
tools.extend(factory())
return tools

def create_agent(cfg: DictConfig, model: OpenAIServerModel):
"""Creates an agent instance based on the hydra configuration."""
# Look up the agent configuration from the library using the agent_name
agent_cfg = cfg.agent_lib[cfg.agent_name]
agent_type = agent_cfg.type

if agent_type == "simple":
blueprint = cfg.agent_definitions[agent_cfg.blueprint]
return CodeAgent(
# ---------------------------------------------------------------------------
# Private builder functions — one per agent type
# ---------------------------------------------------------------------------

def _build_simple(cfg: DictConfig, agent_cfg, model: OpenAIServerModel) -> CodeAgent:
blueprint = cfg.agent_definitions[agent_cfg.blueprint]
return CodeAgent(
model=model,
tools=get_agent_tools(blueprint.tools),
verbosity_level=LogLevel.DEBUG,
name=blueprint.name,
description=blueprint.description,
)


def _build_all_in_one(cfg: DictConfig, agent_cfg, model: OpenAIServerModel) -> CodeAgent:
return CodeAgent(
model=model,
tools=get_agent_tools(["opencti", "osint", "wikipedia", "todays_date"]),
verbosity_level=LogLevel.DEBUG,
planning_interval=3,
name="all_task_agent",
)


def _build_managing(cfg: DictConfig, agent_cfg, model: OpenAIServerModel) -> CodeAgent:
managed_agents = []
for agent_key in agent_cfg.managed_agents:
blueprint = cfg.agent_definitions[agent_key]
managed_agent = CodeAgent(
model=model,
tools=get_agent_tools(blueprint.tools),
verbosity_level=LogLevel.DEBUG,
name=blueprint.name,
description=blueprint.description,
)
elif agent_type == "all_in_one":
return CodeAgent(
model=model,
tools=get_agent_tools(["opencti", "osint", "wikipedia", "todays_date"]),
verbosity_level=LogLevel.DEBUG,
planning_interval=3,
name="all_task_agent",
)
elif agent_type == "managing":
managed_agents = []
for agent_key in agent_cfg.managed_agents:
blueprint = cfg.agent_definitions[agent_key]
managed_agent = CodeAgent(
model=model,
tools=get_agent_tools(blueprint.tools),
verbosity_level=LogLevel.DEBUG,
name=blueprint.name,
description=blueprint.description,
)
managed_agents.append(managed_agent)

return CodeAgent(
model=model,
tools=[tool(todays_date)],
verbosity_level=LogLevel.DEBUG,
planning_interval=3,
managed_agents=managed_agents,
name="managing_agent",
)
else:
raise ValueError(f"Unknown agent type: {agent_type}")
managed_agents.append(managed_agent)

return CodeAgent(
model=model,
tools=[tool(todays_date)],
verbosity_level=LogLevel.DEBUG,
planning_interval=3,
managed_agents=managed_agents,
name="managing_agent",
)


# Registry: adding a new agent type = one dict entry
_BUILDERS: dict[str, Callable] = {
"simple": _build_simple,
"all_in_one": _build_all_in_one,
"managing": _build_managing,
}


def create_agent(cfg: DictConfig, model: OpenAIServerModel) -> CodeAgent:
"""Creates an agent instance based on the hydra configuration."""
agent_cfg = cfg.agent_lib[cfg.agent_name]
builder = _BUILDERS.get(agent_cfg.type)
if builder is None:
raise ValueError(f"Unknown agent type: {agent_cfg.type}")
return builder(cfg, agent_cfg, model)

Loading
Loading