diff --git a/migrations/versions/ebac70616dab_worflow_agent.py b/migrations/versions/ebac70616dab_worflow_agent.py new file mode 100644 index 00000000..aec30c06 --- /dev/null +++ b/migrations/versions/ebac70616dab_worflow_agent.py @@ -0,0 +1,49 @@ +"""worflow_agent + +Revision ID: ebac70616dab +Revises: c107446e38aa +Create Date: 2025-05-06 17:05:26.884902 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = "ebac70616dab" +down_revision: Union[str, None] = "c107446e38aa" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + # Remover a constraint antiga + op.drop_constraint("check_agent_type", "agents", type_="check") + + # Adicionar a nova constraint que inclui o tipo 'workflow' + op.create_check_constraint( + "check_agent_type", + "agents", + "type IN ('llm', 'sequential', 'parallel', 'loop', 'a2a', 'workflow')", + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + # Remover a constraint nova + op.drop_constraint("check_agent_type", "agents", type_="check") + + # Restaurar a constraint anterior sem o tipo 'workflow' + op.create_check_constraint( + "check_agent_type", + "agents", + "type IN ('llm', 'sequential', 'parallel', 'loop', 'a2a')", + ) + # ### end Alembic commands ### diff --git a/src/models/models.py b/src/models/models.py index 24f036fa..6d488fbb 100644 --- a/src/models/models.py +++ b/src/models/models.py @@ -81,7 +81,7 @@ class Agent(Base): __table_args__ = ( CheckConstraint( - "type IN ('llm', 'sequential', 'parallel', 'loop', 'a2a')", + "type IN ('llm', 'sequential', 'parallel', 'loop', 'a2a', 'workflow')", name="check_agent_type", ), ) diff --git a/src/schemas/agent_config.py b/src/schemas/agent_config.py index 9ae5b436..a74bfc82 100644 --- a/src/schemas/agent_config.py +++ b/src/schemas/agent_config.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Dict, Union +from typing import List, Optional, Dict, Union, Any from pydantic import BaseModel, Field from uuid import UUID import secrets @@ -44,6 +44,13 @@ class CustomMCPServerConfig(BaseModel): from_attributes = True +class FlowNodes(BaseModel): + """Configuration of workflow nodes""" + + nodes: List[Any] + edges: List[Any] + + class HTTPToolParameter(BaseModel): """Parameter of an HTTP tool""" @@ -133,6 +140,9 @@ class LLMConfig(BaseModel): sub_agents: Optional[List[UUID]] = Field( default=None, description="List of IDs of sub-agents" ) + workflow: Optional[FlowNodes] = Field( + default=None, description="Workflow configuration" + ) class Config: from_attributes = True @@ -175,3 +185,20 @@ class LoopConfig(BaseModel): class Config: from_attributes = True + + +class WorkflowConfig(BaseModel): + """Configuration for workflow agents""" + + workflow: Dict[str, Any] = Field( + ..., description="Workflow configuration with nodes and edges" + ) + sub_agents: Optional[List[UUID]] = Field( + default_factory=list, description="List of IDs of sub-agents used in workflow" + ) + api_key: Optional[str] = Field( + default_factory=generate_api_key, description="API key for the workflow agent" + ) + + class Config: + from_attributes = True diff --git a/src/schemas/schemas.py b/src/schemas/schemas.py index b728c4aa..1458630c 100644 --- a/src/schemas/schemas.py +++ b/src/schemas/schemas.py @@ -57,7 +57,7 @@ class AgentBase(BaseModel): ) description: Optional[str] = Field(None, description="Agent description") type: str = Field( - ..., description="Agent type (llm, sequential, parallel, loop, a2a)" + ..., description="Agent type (llm, sequential, parallel, loop, a2a, workflow)" ) model: Optional[str] = Field( None, description="Agent model (required only for llm type)" @@ -69,9 +69,7 @@ class AgentBase(BaseModel): agent_card_url: Optional[str] = Field( None, description="Agent card URL (required for a2a type)" ) - config: Optional[Union[LLMConfig, Dict[str, Any]]] = Field( - None, description="Agent configuration based on type" - ) + config: Any = Field(None, description="Agent configuration based on type") @validator("name") def validate_name(cls, v, values): @@ -87,9 +85,9 @@ class AgentBase(BaseModel): @validator("type") def validate_type(cls, v): - if v not in ["llm", "sequential", "parallel", "loop", "a2a"]: + if v not in ["llm", "sequential", "parallel", "loop", "a2a", "workflow"]: raise ValueError( - "Invalid agent type. Must be: llm, sequential, parallel, loop or a2a" + "Invalid agent type. Must be: llm, sequential, parallel, loop, a2a or workflow" ) return v @@ -122,6 +120,10 @@ class AgentBase(BaseModel): if "type" not in values: return v + # Para agentes workflow, não fazemos nenhuma validação + if "type" in values and values["type"] == "workflow": + return v + if not v and values.get("type") != "a2a": raise ValueError( f"Configuration is required for {values.get('type')} agent type" @@ -147,6 +149,7 @@ class AgentBase(BaseModel): raise ValueError( f'Agent {values["type"]} must have at least one sub-agent' ) + return v diff --git a/src/services/agent_service.py b/src/services/agent_service.py index 2f5775cd..f3b59caf 100644 --- a/src/services/agent_service.py +++ b/src/services/agent_service.py @@ -147,6 +147,16 @@ async def create_agent(db: Session, agent: AgentCreate) -> Agent: detail=f"Failed to process agent card: {str(e)}", ) + # Para agentes workflow, não fazemos nenhuma validação específica + # apenas garantimos que config é um dicionário + elif agent.type == "workflow": + if not isinstance(agent.config, dict): + agent.config = {} + + # Garantir a API key + if "api_key" not in agent.config or not agent.config["api_key"]: + agent.config["api_key"] = generate_api_key() + # Additional sub-agent validation (for non-llm and non-a2a types) elif agent.type != "llm": if not isinstance(agent.config, dict):