refactor(agent): remove CrewAI agent support and update related configurations

This commit is contained in:
Davidson Gomes
2025-05-14 13:13:27 -03:00
parent 2a80bdf7a3
commit 0ca6b4f3e9
10 changed files with 43 additions and 389 deletions

View File

@@ -24,8 +24,6 @@
│ For any future changes to the code in this file, it is recommended to │
│ include, together with the modification, the information of the developer │
│ who changed it and the date of modification. │
│ │
│ @update: May 14, 2025 - Added support for crew_ai agent type │
└──────────────────────────────────────────────────────────────────────────────┘
"""

View File

@@ -238,12 +238,15 @@ class WorkflowConfig(BaseModel):
from_attributes = True
class CrewAITask(BaseModel):
"""Task configuration for Crew AI agents"""
class AgentTask(BaseModel):
"""Task configuration for agents"""
agent_id: Union[UUID, str] = Field(
..., description="ID of the agent assigned to this task"
)
enabled_tools: Optional[List[str]] = Field(
default_factory=list, description="List of tool names to be used in the task"
)
description: str = Field(..., description="Description of the task to be performed")
expected_output: str = Field(..., description="Expected output from this task")
@@ -260,17 +263,17 @@ class CrewAITask(BaseModel):
from_attributes = True
class CrewAIConfig(BaseModel):
"""Configuration for Crew AI agents"""
class AgentConfig(BaseModel):
"""Configuration for agents"""
tasks: List[CrewAITask] = Field(
..., description="List of tasks to be performed by the crew"
tasks: List[AgentTask] = Field(
..., description="List of tasks to be performed by the agent"
)
api_key: Optional[str] = Field(
default_factory=generate_api_key, description="API key for the Crew AI agent"
default_factory=generate_api_key, description="API key for the agent"
)
sub_agents: Optional[List[UUID]] = Field(
default_factory=list, description="List of IDs of sub-agents used in crew"
default_factory=list, description="List of IDs of sub-agents used in agent"
)
class Config:

View File

@@ -33,7 +33,7 @@ from datetime import datetime
from uuid import UUID
import uuid
import re
from src.schemas.agent_config import LLMConfig, CrewAIConfig
from src.schemas.agent_config import LLMConfig, AgentConfig
class ClientBase(BaseModel):
@@ -98,7 +98,7 @@ class AgentBase(BaseModel):
goal: Optional[str] = Field(None, description="Agent goal or objective")
type: str = Field(
...,
description="Agent type (llm, sequential, parallel, loop, a2a, workflow, crew_ai, task)",
description="Agent type (llm, sequential, parallel, loop, a2a, workflow, task)",
)
model: Optional[str] = Field(
None, description="Agent model (required only for llm type)"
@@ -136,11 +136,10 @@ class AgentBase(BaseModel):
"loop",
"a2a",
"workflow",
"crew_ai",
"task",
]:
raise ValueError(
"Invalid agent type. Must be: llm, sequential, parallel, loop, a2a, workflow, crew_ai or task"
"Invalid agent type. Must be: llm, sequential, parallel, loop, a2a, workflow or task"
)
return v
@@ -200,7 +199,7 @@ class AgentBase(BaseModel):
raise ValueError(
f'Agent {values["type"]} must have at least one sub-agent'
)
elif values["type"] == "crew_ai" or values["type"] == "task":
elif values["type"] == "task":
if not isinstance(v, dict):
raise ValueError(f'Invalid configuration for agent {values["type"]}')
if "tasks" not in v:
@@ -217,7 +216,6 @@ class AgentBase(BaseModel):
if field not in task:
raise ValueError(f"Task missing required field: {field}")
# Validar sub_agents, se existir
if "sub_agents" in v and v["sub_agents"] is not None:
if not isinstance(v["sub_agents"], list):
raise ValueError("sub_agents must be a list")

View File

@@ -40,7 +40,6 @@ from src.services.custom_tools import CustomToolBuilder
from src.services.mcp_service import MCPService
from src.services.custom_agents.a2a_agent import A2ACustomAgent
from src.services.custom_agents.workflow_agent import WorkflowAgent
from src.services.custom_agents.crew_ai_agent import CrewAIAgent
from src.services.custom_agents.task_agent import TaskAgent
from src.services.apikey_service import get_decrypted_api_key
from sqlalchemy.orm import Session
@@ -50,7 +49,7 @@ from google.adk.tools import load_memory
from datetime import datetime
import uuid
from src.schemas.agent_config import CrewAITask
from src.schemas.agent_config import AgentTask
logger = setup_logger(__name__)
@@ -74,7 +73,7 @@ class AgentBuilder:
return agent_tools
async def _create_llm_agent(
self, agent
self, agent, enabled_tools: List[str] = []
) -> Tuple[LlmAgent, Optional[AsyncExitStack]]:
"""Create an LLM agent from the agent data."""
# Get custom tools from the configuration
@@ -95,6 +94,10 @@ class AgentBuilder:
# Combine all tools
all_tools = custom_tools + mcp_tools + agent_tools
if enabled_tools:
all_tools = [tool for tool in all_tools if tool.name in enabled_tools]
logger.info(f"Enabled tools enabled. Total tools: {len(all_tools)}")
now = datetime.now()
current_datetime = now.strftime("%d/%m/%Y %H:%M")
current_day_of_week = now.strftime("%A")
@@ -200,6 +203,8 @@ class AgentBuilder:
sub_agent, exit_stack = await self.build_a2a_agent(agent)
elif agent.type == "workflow":
sub_agent, exit_stack = await self.build_workflow_agent(agent)
elif agent.type == "task":
sub_agent, exit_stack = await self.build_task_agent(agent)
elif agent.type == "sequential":
sub_agent, exit_stack = await self.build_composite_agent(agent)
elif agent.type == "parallel":
@@ -218,7 +223,7 @@ class AgentBuilder:
return sub_agents
async def build_llm_agent(
self, root_agent
self, root_agent, enabled_tools: List[str] = []
) -> Tuple[LlmAgent, Optional[AsyncExitStack]]:
"""Build an LLM agent with its sub-agents."""
logger.info("Creating LLM agent")
@@ -230,7 +235,9 @@ class AgentBuilder:
)
sub_agents = [agent for agent, _ in sub_agents_with_stacks]
root_llm_agent, exit_stack = await self._create_llm_agent(root_agent)
root_llm_agent, exit_stack = await self._create_llm_agent(
root_agent, enabled_tools
)
if sub_agents:
root_llm_agent.sub_agents = sub_agents
@@ -341,10 +348,11 @@ class AgentBuilder:
# Convert tasks to the expected format by TaskAgent
tasks = []
for task_config in config.get("tasks", []):
task = CrewAITask(
task = AgentTask(
agent_id=task_config.get("agent_id"),
description=task_config.get("description", ""),
expected_output=task_config.get("expected_output", ""),
enabled_tools=task_config.get("enabled_tools", []),
)
tasks.append(task)
@@ -362,57 +370,7 @@ class AgentBuilder:
except Exception as e:
logger.error(f"Error building Task agent: {str(e)}")
raise ValueError(f"Error building CrewAI agent: {str(e)}")
async def build_crew_ai_agent(
self, root_agent: Agent
) -> Tuple[CrewAIAgent, Optional[AsyncExitStack]]:
"""Build a CrewAI agent with its sub-agents."""
logger.info(f"Creating CrewAI agent: {root_agent.name}")
agent_config = root_agent.config or {}
# Verify if we have tasks configured
if not agent_config.get("tasks"):
raise ValueError("tasks are required for CrewAI agents")
try:
# Get sub-agents if there are any
sub_agents = []
if root_agent.config.get("sub_agents"):
sub_agents_with_stacks = await self._get_sub_agents(
root_agent.config.get("sub_agents")
)
sub_agents = [agent for agent, _ in sub_agents_with_stacks]
# Additional configurations
config = root_agent.config or {}
# Convert tasks to the expected format by CrewAIAgent
tasks = []
for task_config in config.get("tasks", []):
task = CrewAITask(
agent_id=task_config.get("agent_id"),
description=task_config.get("description", ""),
expected_output=task_config.get("expected_output", ""),
)
tasks.append(task)
# Create the CrewAI agent
crew_ai_agent = CrewAIAgent(
name=root_agent.name,
tasks=tasks,
db=self.db,
sub_agents=sub_agents,
)
logger.info(f"CrewAI agent created successfully: {root_agent.name}")
return crew_ai_agent, None
except Exception as e:
logger.error(f"Error building CrewAI agent: {str(e)}")
raise ValueError(f"Error building CrewAI agent: {str(e)}")
raise ValueError(f"Error building Task agent: {str(e)}")
async def build_composite_agent(
self, root_agent
@@ -477,26 +435,23 @@ class AgentBuilder:
else:
raise ValueError(f"Invalid agent type: {root_agent.type}")
async def build_agent(self, root_agent) -> Tuple[
async def build_agent(self, root_agent, enabled_tools: List[str] = []) -> Tuple[
LlmAgent
| SequentialAgent
| ParallelAgent
| LoopAgent
| A2ACustomAgent
| WorkflowAgent
| CrewAIAgent
| TaskAgent,
Optional[AsyncExitStack],
]:
"""Build the appropriate agent based on the type of the root agent."""
if root_agent.type == "llm":
return await self.build_llm_agent(root_agent)
return await self.build_llm_agent(root_agent, enabled_tools)
elif root_agent.type == "a2a":
return await self.build_a2a_agent(root_agent)
elif root_agent.type == "workflow":
return await self.build_workflow_agent(root_agent)
elif root_agent.type == "crew_ai":
return await self.build_crew_ai_agent(root_agent)
elif root_agent.type == "task":
return await self.build_task_agent(root_agent)
else:

View File

@@ -202,7 +202,7 @@ async def create_agent(db: Session, agent: AgentCreate) -> Agent:
if "api_key" not in agent.config or not agent.config["api_key"]:
agent.config["api_key"] = generate_api_key()
elif agent.type == "crew_ai" or agent.type == "task":
elif agent.type == "task":
if not isinstance(agent.config, dict):
agent.config = {}
raise HTTPException(
@@ -682,8 +682,8 @@ async def update_agent(
if "config" not in agent_data:
agent_data["config"] = agent_config
if ("type" in agent_data and agent_data["type"] in ["crew_ai", "task"]) or (
agent.type in ["crew_ai", "task"] and "config" in agent_data
if ("type" in agent_data and agent_data["type"] in ["task"]) or (
agent.type in ["task"] and "config" in agent_data
):
config = agent_data.get("config", {})
if "tasks" not in config:

View File

@@ -1,266 +0,0 @@
"""
┌──────────────────────────────────────────────────────────────────────────────┐
│ @author: Davidson Gomes │
│ @file: crew_ai_agent.py │
│ Developed by: Davidson Gomes │
│ Creation date: May 14, 2025 │
│ Contact: contato@evolution-api.com │
├──────────────────────────────────────────────────────────────────────────────┤
│ @copyright © Evolution API 2025. All rights reserved. │
│ Licensed under the Apache License, Version 2.0 │
│ │
│ You may not use this file except in compliance with the License. │
│ You may obtain a copy of the License at │
│ │
│ http://www.apache.org/licenses/LICENSE-2.0 │
│ │
│ Unless required by applicable law or agreed to in writing, software │
│ distributed under the License is distributed on an "AS IS" BASIS, │
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
│ See the License for the specific language governing permissions and │
│ limitations under the License. │
├──────────────────────────────────────────────────────────────────────────────┤
│ @important │
│ For any future changes to the code in this file, it is recommended to │
│ include, together with the modification, the information of the developer │
│ who changed it and the date of modification. │
└──────────────────────────────────────────────────────────────────────────────┘
"""
from attr import Factory
from google.adk.agents import BaseAgent
from google.adk.agents.invocation_context import InvocationContext
from google.adk.events import Event
from google.genai.types import Content, Part
from src.services.agent_service import get_agent
from src.services.apikey_service import get_decrypted_api_key
from sqlalchemy.orm import Session
from typing import AsyncGenerator, List
from src.schemas.agent_config import CrewAITask
from crewai import Agent, Task, Crew, LLM
class CrewAIAgent(BaseAgent):
"""
Custom agent that implements the CrewAI protocol directly.
This agent implements the interaction with an external CrewAI service.
"""
# Field declarations for Pydantic
tasks: List[CrewAITask]
db: Session
def __init__(
self,
name: str,
tasks: List[CrewAITask],
db: Session,
sub_agents: List[BaseAgent] = [],
**kwargs,
):
"""
Initialize the CrewAI agent.
Args:
name: Agent name
tasks: List of tasks to be executed
db: Database session
sub_agents: List of sub-agents to be executed after the CrewAI agent
"""
# Initialize base class
super().__init__(
name=name,
tasks=tasks,
db=db,
sub_agents=sub_agents,
**kwargs,
)
def _generate_llm(self, model: str, api_key: str):
"""
Generate the LLM for the CrewAI agent.
"""
return LLM(model=model, api_key=api_key)
def _agent_builder(self, agent_id: str):
"""
Build the CrewAI agent.
"""
agent = get_agent(self.db, agent_id)
if not agent:
raise ValueError(f"Agent with id {agent_id} not found")
api_key = None
decrypted_key = get_decrypted_api_key(self.db, agent.api_key_id)
if decrypted_key:
api_key = decrypted_key
else:
raise ValueError(
f"API key with ID {agent.api_key_id} not found or inactive"
)
if not api_key:
raise ValueError(f"API key for agent {agent.name} not found")
return Agent(
role=agent.role,
goal=agent.goal,
backstory=agent.instruction,
llm=self._generate_llm(agent.model, api_key),
verbose=True,
)
def _tasks_and_agents_builder(self):
"""
Build the CrewAI tasks.
"""
tasks = []
agents = []
for task in self.tasks:
agent = self._agent_builder(task.agent_id)
agents.append(agent)
tasks.append(
Task(
description=task.description,
expected_output=task.expected_output,
agent=agent,
)
)
return tasks, agents
def _crew_builder(self):
"""
Build the CrewAI crew.
"""
tasks, agents = self._tasks_and_agents_builder()
return Crew(
agents=agents,
tasks=tasks,
verbose=True,
)
async def _run_async_impl(
self, ctx: InvocationContext
) -> AsyncGenerator[Event, None]:
"""
Implementation of the CrewAI.
This method follows the pattern of implementing custom agents,
sending the user's message to the CrewAI service and monitoring the response.
"""
try:
# Extract the user's message from the context
user_message = None
# Search for the user's message in the session events
if ctx.session and hasattr(ctx.session, "events") and ctx.session.events:
for event in reversed(ctx.session.events):
if event.author == "user" and event.content and event.content.parts:
user_message = event.content.parts[0].text
print("Message found in session events")
break
# Check in the session state if the message was not found in the events
if not user_message and ctx.session and ctx.session.state:
if "user_message" in ctx.session.state:
user_message = ctx.session.state["user_message"]
elif "message" in ctx.session.state:
user_message = ctx.session.state["message"]
if not user_message:
yield Event(
author=self.name,
content=Content(
role="agent",
parts=[Part(text="User message not found")],
),
)
return
try:
# Replace any {content} in the task descriptions with the user's input
for task in self.tasks:
task.description = task.description.replace(
"{content}", user_message
)
# Build the Crew
crew = self._crew_builder()
# Start the agent status
yield Event(
author=self.name,
content=Content(
role="agent",
parts=[Part(text=f"Starting CrewAI processing...")],
),
)
# Prepare inputs (if there are placeholders to replace)
inputs = {"user_message": user_message}
# Notify the user that the processing is in progress
yield Event(
author=self.name,
content=Content(
role="agent",
parts=[Part(text=f"Processing your request...")],
),
)
# Try first with kickoff() normally
try:
# If it fails, try with kickoff_async
result = await crew.kickoff_async(inputs=inputs)
print(f"Result of crew.kickoff_async(): {result}")
except Exception as e:
print(f"Error executing crew.kickoff_async(): {str(e)}")
print("Trying alternative with crew.kickoff()")
result = crew.kickoff(inputs=inputs)
print(f"Result of crew.kickoff(): {result}")
# Create an event for the final result
final_event = Event(
author=self.name,
content=Content(role="agent", parts=[Part(text=str(result))]),
)
# Transmit the event to the client
yield final_event
# Execute sub-agents
for sub_agent in self.sub_agents:
async for event in sub_agent.run_async(ctx):
yield event
except Exception as e:
error_msg = f"Error sending request: {str(e)}"
print(error_msg)
print(f"Error type: {type(e).__name__}")
print(f"Error details: {str(e)}")
yield Event(
author=self.name,
content=Content(role="agent", parts=[Part(text=error_msg)]),
)
return
except Exception as e:
# Handle any uncaught error
print(f"Error executing CrewAI agent: {str(e)}")
yield Event(
author=self.name,
content=Content(
role="agent",
parts=[Part(text=f"Error interacting with CrewAI agent: {str(e)}")],
),
)

View File

@@ -33,13 +33,12 @@ from google.adk.agents.invocation_context import InvocationContext
from google.adk.events import Event
from google.genai.types import Content, Part
from src.services.agent_service import get_agent
from src.services.apikey_service import get_decrypted_api_key
from sqlalchemy.orm import Session
from typing import AsyncGenerator, List
from src.schemas.agent_config import CrewAITask
from src.schemas.agent_config import AgentTask
class TaskAgent(BaseAgent):
@@ -50,13 +49,13 @@ class TaskAgent(BaseAgent):
"""
# Field declarations for Pydantic
tasks: List[CrewAITask]
tasks: List[AgentTask]
db: Session
def __init__(
self,
name: str,
tasks: List[CrewAITask],
tasks: List[AgentTask],
db: Session,
sub_agents: List[BaseAgent] = [],
**kwargs,
@@ -132,6 +131,7 @@ class TaskAgent(BaseAgent):
# Replace any {content} in the task descriptions with the user's input
task = self.tasks[0]
task.description = task.description.replace("{content}", user_message)
task.enabled_tools = task.enabled_tools or []
agent = get_agent(self.db, task.agent_id)
@@ -166,7 +166,9 @@ class TaskAgent(BaseAgent):
print(f"Building agent in Task agent: {agent.name}")
agent_builder = AgentBuilder(self.db)
root_agent, exit_stack = await agent_builder.build_agent(agent)
root_agent, exit_stack = await agent_builder.build_agent(
agent, task.enabled_tools
)
# Store task instructions in context for reference by sub-agents
ctx.session.state["task_instructions"] = task_message_instructions