Skip to content

Commit

Permalink
feat(agents): Add CrewAI agent support and tests (#148)
Browse files Browse the repository at this point in the history
* feat(agents): Add CrewAI agent support and tests

Signed-off-by: Nigel Jones <[email protected]>

* interim test update

Signed-off-by: Nigel Jones <[email protected]>

* interim test update

Signed-off-by: Nigel Jones <[email protected]>

* interim test update

Signed-off-by: Nigel Jones <[email protected]>

* interim update

Signed-off-by: Nigel Jones <[email protected]>

* feat(internal): Add unit test automation

Signed-off-by: Nigel Jones <[email protected]>

* Fix github actions

Signed-off-by: Nigel Jones <[email protected]>

* Fix github actions

Signed-off-by: Nigel Jones <[email protected]>

* github action .yml -> .yaml

Signed-off-by: Nigel Jones <[email protected]>

* updates to github action

Signed-off-by: Nigel Jones <[email protected]>

* Additional updates of import to base module

Signed-off-by: Nigel Jones <[email protected]>

* Correct location of tests directory

Signed-off-by: Nigel Jones <[email protected]>

* Enable stdout for pytest

Signed-off-by: Nigel Jones <[email protected]>

* Remove unneeded bee test from crewai test yaml

Signed-off-by: Nigel Jones <[email protected]>

* Explicitly check test output

Signed-off-by: Nigel Jones <[email protected]>

* Use single quotes for branch names for trigger

Signed-off-by: Nigel Jones <[email protected]>

* remove vscode configuration

Signed-off-by: Nigel Jones <[email protected]>

---------

Signed-off-by: Nigel Jones <[email protected]>
  • Loading branch information
planetf1 authored Jan 28, 2025
1 parent 8a0a12e commit 0b5fd60
Show file tree
Hide file tree
Showing 16 changed files with 346 additions and 18 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Bee-Hive Tests

on:
push:
branches: [ 'main' ]
pull_request:
branches: [ 'main' ]

jobs:
run-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Setup poetry
run: |
pipx ensurepath
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
pipx install poetry
poetry self add poetry-plugin-shell
- name: Install dependencies
run: |
cd bee-hive
poetry install
- name: Run unit tests
run: |
cd bee-hive
poetry run pytest
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,5 @@ cython_debug/
/examples/**/agent_store.json
/bee-hive/agent_store.json

# VScode
.vscode/
2 changes: 1 addition & 1 deletion bee-hive/bee_hive/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def __init__(self, agent:dict) -> None:
"""
# TODO: Review which attributes belong in base class vs subclasses
self.agent_name = agent["metadata"]["name"]
self.agent_framework = agent["metadata"]["name"]
self.agent_framework = agent["spec"]["framework"]
self.agent_model = agent["spec"]["model"]

self.agent_desc = agent["spec"]["description"]
Expand Down
37 changes: 37 additions & 0 deletions bee-hive/bee_hive/agent_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

from enum import Enum
from typing import Callable, Type, Union
from bee_hive.bee_agent import BeeAgent
from bee_hive.crewai_agent import CrewAIAgent

class AgentFramework(Enum):
"""Enumeration of supported frameworks"""
BEE = "bee"
CREWAI = "crewai"

class AgentFactory:
"""Factory class for handling agent frameworks"""
@staticmethod
def create_agent(framework: AgentFramework) -> Callable[..., Union[BeeAgent, CrewAIAgent]]:
"""Create an instance of the specified agent framework.
Args:
framework (AgentFramework): The framework to create. Must be a valid enum value.
Returns:
A new instance of the corresponding agent class.
"""
factories = {
AgentFramework.BEE: BeeAgent,
AgentFramework.CREWAI: CrewAIAgent
}

if framework not in factories:
raise ValueError(f"Unknown framework: {framework}")

return factories[framework]

@classmethod
def get_factory(cls, framework: str) -> Callable[..., Union[BeeAgent, CrewAIAgent]]:
"""Get a factory function for the specified agent type."""
return cls.create_agent(framework)
2 changes: 1 addition & 1 deletion bee-hive/bee_hive/bee_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from openai.types.beta import AssistantStreamEvent
from openai.types.beta.threads.runs import RunStep, RunStepDelta, ToolCall

from agent import Agent
from bee_hive.agent import Agent

dotenv.load_dotenv()

Expand Down
73 changes: 73 additions & 0 deletions bee-hive/bee_hive/crewai_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@

import importlib

from bee_hive.agent import Agent

class CrewAIAgent(Agent):
"""
CrewAIAgent extends the Agent class to load and run a specific CrewAI agent.
"""
def __init__(self, agent: dict) -> str:
"""
Initializes the workflow for the specified agent.
The executable code must be within $PYTHONPATH.
Args:
agent_name (dict): Agent Configuration
Raises:
Exception: If the agent cannot be loaded, an exception is raised with an error message.
"""

super().__init__(agent)

# TODO: Add additional properties later. for now using naming:
# <directory>.<filename>.<class>.<method> ie
# test.crewai_test.ColdWeatherCrew.activity_crew

try:
partial_agent_name, method_name = self.agent_name.rsplit(".", 1)
module_name, class_name = partial_agent_name.rsplit(".", 1)
my_module = importlib.import_module(module_name)
# Get the class object
self.crew_agent_class = getattr(my_module, class_name)
# Instantiate the class
self.instance = self.crew_agent_class()
self.method_name = method_name
except Exception as e:
print(f"Failed to load agent {self.agent_name}: {e}")
raise(e)


def run(self, prompt: str) -> str:
"""
Executes the CrewAI agent with the given prompt. The agent's `kickoff` method is called with the input.
Args:
prompt (str): The input to be processed by the agent.
Returns:
Any: The output from the agent's `kickoff` method.
Raises:
Exception: If there is an error in retrieving or executing the agent's method.
"""
print(f"Running CrewAI agent: {self.agent_name} with prompt: {prompt}")

try:
method = getattr(self.instance, self.method_name)
output = method().kickoff(prompt)
return output

except Exception as e:
print(f"Failed to kickoff crew agent: {self.agent_name}: {e}")
raise(e)

def run_streaming(self, prompt) ->str:
"""
Streams the execution of the CrewAI agent with the given prompt.
This is NOT YET IMPLEMENTED
Args:
prompt (str): The input prompt to be processed by the CrewAI agent.
Raises:
NotImplementedError: Indicates that the CrewAI agent execution logic is not yet implemented.
"""
print(f"Running CrewAI agent (streaming): {self.agent_name} with prompt: {prompt}")

raise NotImplementedError("CrewAI agent execution logic not implemented yet")
2 changes: 1 addition & 1 deletion bee-hive/bee_hive/step.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import yaml

dotenv.load_dotenv()
from agent import Agent
from bee_hive.agent import Agent

class Step:
def __init__(self, step):
Expand Down
37 changes: 26 additions & 11 deletions bee-hive/bee_hive/workflow.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,57 @@
#! /usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0

import json
import os
import sys

import yaml
from bee_hive.step import Step
from bee_hive.agent_factory import AgentFramework
import dotenv
from step import Step
from bee_agent import BeeAgent

# TODO: Refactor later to factory or similar
from bee_hive.crewai_agent import CrewAIAgent
from bee_hive.bee_agent import BeeAgent

dotenv.load_dotenv()


@staticmethod
def get_agent_class(framework: str) -> type:
if framework == "crewai":
return CrewAIAgent
else:
return BeeAgent


class Workflow:
agents = {}
steps = {}
workflow = {}

def __init__(self, agent_defs, workflow):
"""Execute sequential workflow.
input:
agents: array of agent definitions
workflow: workflow definition
"""
for agent_def in agent_defs:
self.agents[agent_def["metadata"]["name"]] = BeeAgent(agent_def)
# Use 'bee' if this value isn't set
#
agent_def["spec"]["framework"] = agent_def["spec"].get(
"framework", AgentFramework.BEE
)
self.agents[agent_def["metadata"]["name"]] = get_agent_class(
agent_def["spec"]["framework"]
)(agent_def)
self.workflow = workflow


def run(self):
"""Execute workflow."""

if (self.workflow["spec"]["strategy"]["type"] == "sequence"):
if self.workflow["spec"]["strategy"]["type"] == "sequence":
return self._sequence()
elif (self.workflow["spec"]["strategy"]["type"] == "condition"):
elif self.workflow["spec"]["strategy"]["type"] == "condition":
return self._condition()
else:
print("not supported yet")
print("not supported yet")

def _sequence(self):
prompt = self.workflow["spec"]["template"]["prompt"]
Expand Down
Loading

0 comments on commit 0b5fd60

Please sign in to comment.