1338 lines
56 KiB
Python
1338 lines
56 KiB
Python
"""
|
|
┌──────────────────────────────────────────────────────────────────────────────┐
|
|
│ @author: Davidson Gomes │
|
|
│ @file: agent_service.py │
|
|
│ Developed by: Davidson Gomes │
|
|
│ Creation date: May 13, 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 sqlalchemy.orm import Session
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|
from fastapi import HTTPException, status
|
|
from src.models.models import Agent, AgentFolder, ApiKey
|
|
from src.schemas.schemas import AgentCreate
|
|
from typing import List, Optional, Dict, Any, Union
|
|
from src.services.mcp_server_service import get_mcp_server
|
|
import uuid
|
|
import logging
|
|
import httpx
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# Helper function to generate API keys
|
|
def generate_api_key() -> str:
|
|
"""Generate a secure API key"""
|
|
# Format: sk-proj-{random 64 chars}
|
|
|
|
return str(uuid.uuid4())
|
|
|
|
|
|
def _convert_uuid_to_str(obj):
|
|
"""
|
|
Recursively convert all UUID objects to strings in a dictionary, list or scalar value.
|
|
This ensures JSON serialize for complex nested objects.
|
|
"""
|
|
if isinstance(obj, dict):
|
|
return {key: _convert_uuid_to_str(value) for key, value in obj.items()}
|
|
elif isinstance(obj, list):
|
|
return [_convert_uuid_to_str(item) for item in obj]
|
|
elif isinstance(obj, uuid.UUID):
|
|
return str(obj)
|
|
else:
|
|
return obj
|
|
|
|
|
|
def validate_sub_agents(db: Session, sub_agents: List[Union[uuid.UUID, str]]) -> bool:
|
|
"""Validate if all sub-agents exist"""
|
|
logger.info(f"Validating sub-agents: {sub_agents}")
|
|
|
|
if not sub_agents:
|
|
logger.warning("Empty sub-agents list")
|
|
return False
|
|
|
|
for agent_id in sub_agents:
|
|
# Ensure the ID is in the correct format
|
|
agent_id_str = str(agent_id)
|
|
logger.info(f"Validating sub-agent with ID: {agent_id_str}")
|
|
|
|
agent = get_agent(db, agent_id_str)
|
|
if not agent:
|
|
logger.warning(f"Sub-agent not found: {agent_id_str}")
|
|
return False
|
|
logger.info(f"Valid sub-agent: {agent.name} (ID: {agent_id_str})")
|
|
|
|
logger.info(f"All {len(sub_agents)} sub-agents are valid")
|
|
return True
|
|
|
|
|
|
def get_agent(db: Session, agent_id: Union[uuid.UUID, str]) -> Optional[Agent]:
|
|
"""Search for an agent by ID"""
|
|
try:
|
|
# Convert to UUID if it's a string
|
|
if isinstance(agent_id, str):
|
|
try:
|
|
agent_id = uuid.UUID(agent_id)
|
|
except ValueError:
|
|
logger.warning(f"Invalid agent ID: {agent_id}")
|
|
return None
|
|
|
|
agent = db.query(Agent).filter(Agent.id == agent_id).first()
|
|
if not agent:
|
|
logger.warning(f"Agent not found: {agent_id}")
|
|
return None
|
|
|
|
# Sanitize agent name if it contains spaces or special characters
|
|
if agent.name and any(c for c in agent.name if not (c.isalnum() or c == "_")):
|
|
agent.name = "".join(
|
|
c if c.isalnum() or c == "_" else "_" for c in agent.name
|
|
)
|
|
# Update in database
|
|
db.commit()
|
|
|
|
return agent
|
|
except SQLAlchemyError as e:
|
|
logger.error(f"Error searching for agent {agent_id}: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Error searching for agent",
|
|
)
|
|
|
|
|
|
def get_agents_by_client(
|
|
db: Session,
|
|
client_id: uuid.UUID,
|
|
skip: int = 0,
|
|
limit: int = 100,
|
|
active_only: bool = True,
|
|
folder_id: Optional[uuid.UUID] = None,
|
|
sort_by: str = "name",
|
|
sort_direction: str = "asc",
|
|
) -> List[Agent]:
|
|
"""Search for agents by client with pagination and optional folder filter"""
|
|
try:
|
|
query = db.query(Agent).filter(Agent.client_id == client_id)
|
|
|
|
# Filter by folder if specified
|
|
if folder_id is not None:
|
|
query = query.filter(Agent.folder_id == folder_id)
|
|
|
|
# Apply sorting
|
|
if sort_by == "name":
|
|
if sort_direction.lower() == "desc":
|
|
query = query.order_by(Agent.name.desc())
|
|
else:
|
|
query = query.order_by(Agent.name)
|
|
elif sort_by == "created_at":
|
|
if sort_direction.lower() == "desc":
|
|
query = query.order_by(Agent.created_at.desc())
|
|
else:
|
|
query = query.order_by(Agent.created_at)
|
|
|
|
agents = query.offset(skip).limit(limit).all()
|
|
|
|
# Sanitize agent names if they contain spaces or special characters
|
|
for agent in agents:
|
|
if agent.name and any(
|
|
c for c in agent.name if not (c.isalnum() or c == "_")
|
|
):
|
|
agent.name = "".join(
|
|
c if c.isalnum() or c == "_" else "_" for c in agent.name
|
|
)
|
|
# Update in database
|
|
db.commit()
|
|
|
|
return agents
|
|
except SQLAlchemyError as e:
|
|
logger.error(f"Error searching for client agents {client_id}: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Error searching for agents",
|
|
)
|
|
|
|
|
|
async def create_agent(db: Session, agent: AgentCreate) -> Agent:
|
|
"""Create a new agent"""
|
|
try:
|
|
# Special handling for a2a type agents
|
|
if agent.type == "a2a":
|
|
if not agent.agent_card_url:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="agent_card_url is required for a2a type agents",
|
|
)
|
|
|
|
try:
|
|
# Fetch agent card information
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.get(agent.agent_card_url)
|
|
if response.status_code != 200:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Failed to fetch agent card: HTTP {response.status_code}",
|
|
)
|
|
agent_card = response.json()
|
|
|
|
# Update agent with information from agent card
|
|
# Only update name if not provided or empty, or sanitize it
|
|
if not agent.name or agent.name.strip() == "":
|
|
# Sanitize name: remove spaces and special characters
|
|
card_name = agent_card.get("name", "Unknown Agent")
|
|
sanitized_name = "".join(
|
|
c if c.isalnum() or c == "_" else "_" for c in card_name
|
|
)
|
|
agent.name = sanitized_name
|
|
|
|
agent.description = agent_card.get("description", "")
|
|
|
|
if agent.config is None:
|
|
agent.config = {}
|
|
|
|
# Store the whole agent card in config
|
|
if isinstance(agent.config, dict):
|
|
agent.config["agent_card"] = agent_card
|
|
else:
|
|
agent.config = {"agent_card": agent_card}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error fetching agent card: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Failed to process agent card: {str(e)}",
|
|
)
|
|
|
|
elif agent.type == "workflow":
|
|
if not isinstance(agent.config, dict):
|
|
agent.config = {}
|
|
|
|
if "api_key" not in agent.config or not agent.config["api_key"]:
|
|
agent.config["api_key"] = generate_api_key()
|
|
|
|
elif agent.type == "task":
|
|
if not isinstance(agent.config, dict):
|
|
agent.config = {}
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Invalid configuration: must be an object with tasks",
|
|
)
|
|
|
|
if "tasks" not in agent.config:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Invalid configuration: tasks is required for {agent.type} agents",
|
|
)
|
|
|
|
if not agent.config["tasks"]:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Invalid configuration: tasks cannot be empty",
|
|
)
|
|
|
|
for task in agent.config["tasks"]:
|
|
if "agent_id" not in task:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Each task must have an agent_id",
|
|
)
|
|
|
|
agent_id = task["agent_id"]
|
|
task_agent = get_agent(db, agent_id)
|
|
if not task_agent:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Agent not found for task: {agent_id}",
|
|
)
|
|
|
|
if "sub_agents" in agent.config and agent.config["sub_agents"]:
|
|
if not validate_sub_agents(db, agent.config["sub_agents"]):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="One or more sub-agents do not exist",
|
|
)
|
|
|
|
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):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Invalid configuration: must be an object with sub_agents",
|
|
)
|
|
|
|
if "sub_agents" not in agent.config:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Invalid configuration: sub_agents is required for sequential, parallel or loop agents",
|
|
)
|
|
|
|
if not agent.config["sub_agents"]:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Invalid configuration: sub_agents cannot be empty",
|
|
)
|
|
|
|
if not validate_sub_agents(db, agent.config["sub_agents"]):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="One or more sub-agents do not exist",
|
|
)
|
|
|
|
# Process the configuration before creating the agent
|
|
config = agent.config
|
|
if config is None:
|
|
config = {}
|
|
agent.config = config
|
|
|
|
# Ensure config is a dictionary
|
|
if not isinstance(config, dict):
|
|
config = {}
|
|
agent.config = config
|
|
|
|
# Generate automatic API key if not provided or empty
|
|
if not config.get("api_key") or config.get("api_key") == "":
|
|
logger.info("Generating automatic API key for new agent")
|
|
config["api_key"] = generate_api_key()
|
|
|
|
processed_config = {}
|
|
processed_config["api_key"] = config.get("api_key", "")
|
|
|
|
if "tools" in config:
|
|
processed_config["tools"] = config["tools"]
|
|
|
|
if "custom_tools" in config:
|
|
processed_config["custom_tools"] = config["custom_tools"]
|
|
|
|
if "agent_tools" in config:
|
|
processed_config["agent_tools"] = config["agent_tools"]
|
|
|
|
if "sub_agents" in config:
|
|
processed_config["sub_agents"] = config["sub_agents"]
|
|
|
|
if "custom_mcp_servers" in config:
|
|
processed_config["custom_mcp_servers"] = config["custom_mcp_servers"]
|
|
|
|
for key, value in config.items():
|
|
if key not in [
|
|
"api_key",
|
|
"tools",
|
|
"custom_tools",
|
|
"sub_agents",
|
|
"agent_tools",
|
|
"custom_mcp_servers",
|
|
"mcp_servers",
|
|
]:
|
|
processed_config[key] = value
|
|
|
|
# Process MCP servers
|
|
if "mcp_servers" in config and config["mcp_servers"] is not None:
|
|
processed_servers = []
|
|
for server in config["mcp_servers"]:
|
|
# Convert server id to UUID if it's a string
|
|
server_id = server["id"]
|
|
if isinstance(server_id, str):
|
|
server_id = uuid.UUID(server_id)
|
|
|
|
# Search for MCP server in the database
|
|
mcp_server = get_mcp_server(db, server_id)
|
|
if not mcp_server:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"MCP server not found: {server['id']}",
|
|
)
|
|
|
|
# Check if all required environment variables are provided
|
|
for env_key, env_value in mcp_server.environments.items():
|
|
if env_key not in server.get("envs", {}):
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Environment variable '{env_key}' not provided for MCP server {mcp_server.name}",
|
|
)
|
|
|
|
# Add the processed server
|
|
processed_servers.append(
|
|
{
|
|
"id": str(server["id"]),
|
|
"envs": server["envs"],
|
|
"tools": server["tools"],
|
|
}
|
|
)
|
|
|
|
processed_config["mcp_servers"] = processed_servers
|
|
elif "mcp_servers" in config:
|
|
processed_config["mcp_servers"] = config["mcp_servers"]
|
|
|
|
# Process custom MCP servers
|
|
if "custom_mcp_servers" in config and config["custom_mcp_servers"] is not None:
|
|
processed_custom_servers = []
|
|
for server in config["custom_mcp_servers"]:
|
|
# Validate URL format
|
|
if not server.get("url"):
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="URL is required for custom MCP servers",
|
|
)
|
|
|
|
# Add the custom server
|
|
processed_custom_servers.append(
|
|
{"url": server["url"], "headers": server.get("headers", {})}
|
|
)
|
|
|
|
processed_config["custom_mcp_servers"] = processed_custom_servers
|
|
|
|
# Process sub-agents
|
|
if "sub_agents" in config and config["sub_agents"] is not None:
|
|
processed_config["sub_agents"] = [
|
|
str(agent_id) for agent_id in config["sub_agents"]
|
|
]
|
|
|
|
# Process agent tools
|
|
if "agent_tools" in config and config["agent_tools"] is not None:
|
|
processed_config["agent_tools"] = [
|
|
str(agent_id) for agent_id in config["agent_tools"]
|
|
]
|
|
|
|
# Process tools
|
|
if "tools" in config and config["tools"] is not None:
|
|
processed_tools = []
|
|
for tool in config["tools"]:
|
|
# Convert tool id to string
|
|
tool_id = tool["id"]
|
|
|
|
envs = tool.get("envs", {})
|
|
if envs is None:
|
|
envs = {}
|
|
|
|
processed_tools.append({"id": str(tool_id), "envs": envs})
|
|
processed_config["tools"] = processed_tools
|
|
|
|
agent.config = processed_config
|
|
|
|
# Ensure all config objects are serializable (convert UUIDs to strings)
|
|
if agent.config is not None:
|
|
agent.config = _convert_uuid_to_str(agent.config)
|
|
|
|
# Convert agent to dict ensuring all UUIDs are converted to strings
|
|
agent_dict = agent.model_dump()
|
|
agent_dict = _convert_uuid_to_str(agent_dict)
|
|
|
|
# Create agent from the processed dictionary
|
|
db_agent = Agent(**agent_dict)
|
|
|
|
# Make one final check to ensure all nested objects are serializable
|
|
# (especially nested UUIDs in config)
|
|
if db_agent.config is not None:
|
|
db_agent.config = _convert_uuid_to_str(db_agent.config)
|
|
|
|
db.add(db_agent)
|
|
db.commit()
|
|
db.refresh(db_agent)
|
|
logger.info(f"Agent created successfully: {db_agent.id}")
|
|
|
|
return db_agent
|
|
except SQLAlchemyError as e:
|
|
db.rollback()
|
|
logger.error(f"Error creating agent: {str(e)}")
|
|
|
|
# Add debugging info
|
|
try:
|
|
import json
|
|
|
|
if "agent_dict" in locals():
|
|
agent_json = json.dumps(agent_dict)
|
|
logger.info(f"Agent creation attempt with: {agent_json[:200]}...")
|
|
except Exception as json_err:
|
|
logger.error(f"Could not serialize agent for debugging: {str(json_err)}")
|
|
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Error creating agent: {str(e)}",
|
|
)
|
|
|
|
|
|
async def update_agent(
|
|
db: Session, agent_id: uuid.UUID, agent_data: Dict[str, Any]
|
|
) -> Agent:
|
|
"""Update an existing agent"""
|
|
try:
|
|
agent = db.query(Agent).filter(Agent.id == agent_id).first()
|
|
if not agent:
|
|
raise HTTPException(status_code=404, detail="Agent not found")
|
|
|
|
# Check if api_key_id is defined
|
|
if "api_key_id" in agent_data and agent_data["api_key_id"]:
|
|
# Check if the referenced API key exists
|
|
api_key_id = agent_data["api_key_id"]
|
|
if isinstance(api_key_id, str):
|
|
api_key_id = uuid.UUID(api_key_id)
|
|
|
|
api_key = db.query(ApiKey).filter(ApiKey.id == api_key_id).first()
|
|
if not api_key:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"API Key with ID {api_key_id} not found",
|
|
)
|
|
|
|
# Check if the key belongs to the agent's client
|
|
if api_key.client_id != agent.client_id:
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail="API Key does not belong to the same client as the agent",
|
|
)
|
|
|
|
# Continue with the original code
|
|
if "type" in agent_data and agent_data["type"] == "a2a":
|
|
if "agent_card_url" not in agent_data or not agent_data["agent_card_url"]:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="agent_card_url is required for a2a type agents",
|
|
)
|
|
|
|
if not agent_data["agent_card_url"].endswith("/.well-known/agent.json"):
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="agent_card_url must end with /.well-known/agent.json",
|
|
)
|
|
|
|
try:
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.get(agent_data["agent_card_url"])
|
|
if response.status_code != 200:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Failed to fetch agent card: HTTP {response.status_code}",
|
|
)
|
|
agent_card = response.json()
|
|
|
|
# Only update name if the original update doesn't specify a name
|
|
if "name" not in agent_data or not agent_data["name"].strip():
|
|
# Sanitize name: remove spaces and special characters
|
|
card_name = agent_card.get("name", "Unknown Agent")
|
|
sanitized_name = "".join(
|
|
c if c.isalnum() or c == "_" else "_" for c in card_name
|
|
)
|
|
agent_data["name"] = sanitized_name
|
|
agent_data["description"] = agent_card.get("description", "")
|
|
|
|
if "config" not in agent_data or agent_data["config"] is None:
|
|
agent_data["config"] = agent.config if agent.config else {}
|
|
|
|
agent_data["config"]["agent_card"] = agent_card
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error fetching agent card: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Failed to process agent card: {str(e)}",
|
|
)
|
|
|
|
elif "agent_card_url" in agent_data and agent.type == "a2a":
|
|
if not agent_data["agent_card_url"]:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="agent_card_url cannot be empty for a2a type agents",
|
|
)
|
|
|
|
if not agent_data["agent_card_url"].endswith("/.well-known/agent.json"):
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="agent_card_url must end with /.well-known/agent.json",
|
|
)
|
|
|
|
try:
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.get(agent_data["agent_card_url"])
|
|
if response.status_code != 200:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Failed to fetch agent card: HTTP {response.status_code}",
|
|
)
|
|
agent_card = response.json()
|
|
|
|
# Only update name if the original update doesn't specify a name
|
|
if "name" not in agent_data or not agent_data["name"].strip():
|
|
# Sanitize name: remove spaces and special characters
|
|
card_name = agent_card.get("name", "Unknown Agent")
|
|
sanitized_name = "".join(
|
|
c if c.isalnum() or c == "_" else "_" for c in card_name
|
|
)
|
|
agent_data["name"] = sanitized_name
|
|
agent_data["description"] = agent_card.get("description", "")
|
|
|
|
if "config" not in agent_data or agent_data["config"] is None:
|
|
agent_data["config"] = agent.config if agent.config else {}
|
|
|
|
agent_data["config"]["agent_card"] = agent_card
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error fetching agent card: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Failed to process agent card: {str(e)}",
|
|
)
|
|
|
|
# Convert UUIDs to strings before saving
|
|
if "config" in agent_data:
|
|
config = agent_data["config"]
|
|
|
|
processed_config = {}
|
|
processed_config["api_key"] = config.get("api_key", "")
|
|
|
|
if "tools" in config:
|
|
processed_config["tools"] = config["tools"]
|
|
|
|
if "custom_tools" in config:
|
|
processed_config["custom_tools"] = config["custom_tools"]
|
|
|
|
if "sub_agents" in config:
|
|
processed_config["sub_agents"] = config["sub_agents"]
|
|
|
|
if "agent_tools" in config:
|
|
processed_config["agent_tools"] = config["agent_tools"]
|
|
|
|
if "custom_mcp_servers" in config:
|
|
processed_config["custom_mcp_servers"] = config["custom_mcp_servers"]
|
|
|
|
for key, value in config.items():
|
|
if key not in [
|
|
"api_key",
|
|
"tools",
|
|
"custom_tools",
|
|
"sub_agents",
|
|
"agent_tools",
|
|
"custom_mcp_servers",
|
|
"mcp_servers",
|
|
]:
|
|
processed_config[key] = value
|
|
|
|
# Process MCP servers
|
|
if "mcp_servers" in config and config["mcp_servers"] is not None:
|
|
processed_servers = []
|
|
for server in config["mcp_servers"]:
|
|
# Convert server id to UUID if it's a string
|
|
server_id = server["id"]
|
|
if isinstance(server_id, str):
|
|
server_id = uuid.UUID(server_id)
|
|
|
|
# Search for MCP server in the database
|
|
mcp_server = get_mcp_server(db, server_id)
|
|
if not mcp_server:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"MCP server not found: {server['id']}",
|
|
)
|
|
|
|
# Check if all required environment variables are provided
|
|
for env_key, env_value in mcp_server.environments.items():
|
|
if env_key not in server.get("envs", {}):
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Environment variable '{env_key}' not provided for MCP server {mcp_server.name}",
|
|
)
|
|
|
|
# Add the processed server
|
|
processed_servers.append(
|
|
{
|
|
"id": str(server["id"]),
|
|
"envs": server["envs"],
|
|
"tools": server["tools"],
|
|
}
|
|
)
|
|
|
|
processed_config["mcp_servers"] = processed_servers
|
|
elif "mcp_servers" in config:
|
|
processed_config["mcp_servers"] = config["mcp_servers"]
|
|
|
|
# Process custom MCP servers
|
|
if (
|
|
"custom_mcp_servers" in config
|
|
and config["custom_mcp_servers"] is not None
|
|
):
|
|
processed_custom_servers = []
|
|
for server in config["custom_mcp_servers"]:
|
|
# Validate URL format
|
|
if not server.get("url"):
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="URL is required for custom MCP servers",
|
|
)
|
|
|
|
# Add the custom server
|
|
processed_custom_servers.append(
|
|
{"url": server["url"], "headers": server.get("headers", {})}
|
|
)
|
|
|
|
processed_config["custom_mcp_servers"] = processed_custom_servers
|
|
|
|
# Process sub-agents
|
|
if "sub_agents" in config and config["sub_agents"] is not None:
|
|
processed_config["sub_agents"] = [
|
|
str(agent_id) for agent_id in config["sub_agents"]
|
|
]
|
|
|
|
# Process agent tools
|
|
if "agent_tools" in config and config["agent_tools"] is not None:
|
|
processed_config["agent_tools"] = [
|
|
str(agent_id) for agent_id in config["agent_tools"]
|
|
]
|
|
|
|
# Process tools
|
|
if "tools" in config and config["tools"] is not None:
|
|
processed_tools = []
|
|
for tool in config["tools"]:
|
|
# Convert tool id to string
|
|
tool_id = tool["id"]
|
|
|
|
envs = tool.get("envs", {})
|
|
if envs is None:
|
|
envs = {}
|
|
|
|
processed_tools.append({"id": str(tool_id), "envs": envs})
|
|
processed_config["tools"] = processed_tools
|
|
|
|
agent_data["config"] = processed_config
|
|
|
|
# Ensure all config objects are serializable (convert UUIDs to strings)
|
|
if "config" in agent_data and agent_data["config"] is not None:
|
|
agent_data["config"] = _convert_uuid_to_str(agent_data["config"])
|
|
|
|
# Check if the agent has API key and generate one if not
|
|
agent_config = agent.config or {}
|
|
if "config" not in agent_data:
|
|
agent_data["config"] = agent_config
|
|
|
|
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:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Invalid configuration: tasks is required for {agent_data.get('type', agent.type)} agents",
|
|
)
|
|
|
|
if not config["tasks"]:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Invalid configuration: tasks cannot be empty",
|
|
)
|
|
|
|
for task in config["tasks"]:
|
|
if "agent_id" not in task:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Each task must have an agent_id",
|
|
)
|
|
|
|
agent_id = task["agent_id"]
|
|
task_agent = get_agent(db, agent_id)
|
|
if not task_agent:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Agent not found for task: {agent_id}",
|
|
)
|
|
|
|
# Validar sub_agents se existir
|
|
if "sub_agents" in config and config["sub_agents"]:
|
|
if not validate_sub_agents(db, config["sub_agents"]):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="One or more sub-agents do not exist",
|
|
)
|
|
|
|
if not agent_config.get("api_key") and (
|
|
"config" not in agent_data or not agent_data["config"].get("api_key")
|
|
):
|
|
logger.info(f"Generating missing API key for existing agent: {agent_id}")
|
|
if "config" not in agent_data:
|
|
agent_data["config"] = {}
|
|
agent_data["config"]["api_key"] = generate_api_key()
|
|
|
|
for key, value in agent_data.items():
|
|
setattr(agent, key, value)
|
|
|
|
db.commit()
|
|
db.refresh(agent)
|
|
return agent
|
|
except Exception as e:
|
|
db.rollback()
|
|
raise HTTPException(status_code=500, detail=f"Error updating agent: {str(e)}")
|
|
|
|
|
|
def delete_agent(db: Session, agent_id: uuid.UUID) -> bool:
|
|
"""Remove an agent from the database"""
|
|
try:
|
|
db_agent = get_agent(db, agent_id)
|
|
if not db_agent:
|
|
return False
|
|
|
|
# Actually delete the agent from the database
|
|
db.delete(db_agent)
|
|
db.commit()
|
|
logger.info(f"Agent deleted successfully: {agent_id}")
|
|
return True
|
|
except SQLAlchemyError as e:
|
|
db.rollback()
|
|
logger.error(f"Error deleting agent {agent_id}: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Error deleting agent",
|
|
)
|
|
|
|
|
|
def activate_agent(db: Session, agent_id: uuid.UUID) -> bool:
|
|
"""Reactivate an agent"""
|
|
try:
|
|
db_agent = get_agent(db, agent_id)
|
|
if not db_agent:
|
|
return False
|
|
|
|
db.commit()
|
|
logger.info(f"Agent reactivated successfully: {agent_id}")
|
|
return True
|
|
except SQLAlchemyError as e:
|
|
db.rollback()
|
|
logger.error(f"Error reactivating agent {agent_id}: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Error reactivating agent",
|
|
)
|
|
|
|
|
|
# Functions for agent folders
|
|
def create_agent_folder(
|
|
db: Session, client_id: uuid.UUID, name: str, description: Optional[str] = None
|
|
) -> AgentFolder:
|
|
"""Create a new folder to organize agents"""
|
|
try:
|
|
folder = AgentFolder(client_id=client_id, name=name, description=description)
|
|
db.add(folder)
|
|
db.commit()
|
|
db.refresh(folder)
|
|
logger.info(f"Agent folder created successfully: {folder.id}")
|
|
return folder
|
|
except SQLAlchemyError as e:
|
|
db.rollback()
|
|
logger.error(f"Error creating agent folder: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Error creating agent folder: {str(e)}",
|
|
)
|
|
|
|
|
|
def get_agent_folder(db: Session, folder_id: uuid.UUID) -> Optional[AgentFolder]:
|
|
"""Search for an agent folder by ID"""
|
|
try:
|
|
return db.query(AgentFolder).filter(AgentFolder.id == folder_id).first()
|
|
except SQLAlchemyError as e:
|
|
logger.error(f"Error searching for agent folder {folder_id}: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Error searching for agent folder",
|
|
)
|
|
|
|
|
|
def get_agent_folders_by_client(
|
|
db: Session, client_id: uuid.UUID, skip: int = 0, limit: int = 100
|
|
) -> List[AgentFolder]:
|
|
"""List the agent folders of a client"""
|
|
try:
|
|
return (
|
|
db.query(AgentFolder)
|
|
.filter(AgentFolder.client_id == client_id)
|
|
.offset(skip)
|
|
.limit(limit)
|
|
.all()
|
|
)
|
|
except SQLAlchemyError as e:
|
|
logger.error(f"Error listing agent folders: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Error listing agent folders",
|
|
)
|
|
|
|
|
|
def update_agent_folder(
|
|
db: Session,
|
|
folder_id: uuid.UUID,
|
|
name: Optional[str] = None,
|
|
description: Optional[str] = None,
|
|
) -> Optional[AgentFolder]:
|
|
"""Update an agent folder"""
|
|
try:
|
|
folder = get_agent_folder(db, folder_id)
|
|
if not folder:
|
|
return None
|
|
|
|
if name is not None:
|
|
folder.name = name
|
|
if description is not None:
|
|
folder.description = description
|
|
|
|
db.commit()
|
|
db.refresh(folder)
|
|
logger.info(f"Agent folder updated: {folder_id}")
|
|
return folder
|
|
except SQLAlchemyError as e:
|
|
db.rollback()
|
|
logger.error(f"Error updating agent folder {folder_id}: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Error updating agent folder",
|
|
)
|
|
|
|
|
|
def delete_agent_folder(db: Session, folder_id: uuid.UUID) -> bool:
|
|
"""Remove an agent folder and unassign the agents"""
|
|
try:
|
|
folder = get_agent_folder(db, folder_id)
|
|
if not folder:
|
|
return False
|
|
|
|
# Unassign the agents from the folder (do not delete the agents)
|
|
agents = db.query(Agent).filter(Agent.folder_id == folder_id).all()
|
|
for agent in agents:
|
|
agent.folder_id = None
|
|
|
|
# Delete the folder
|
|
db.delete(folder)
|
|
db.commit()
|
|
logger.info(f"Agent folder removed: {folder_id}")
|
|
return True
|
|
except SQLAlchemyError as e:
|
|
db.rollback()
|
|
logger.error(f"Error removing agent folder {folder_id}: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Error removing agent folder",
|
|
)
|
|
|
|
|
|
def assign_agent_to_folder(
|
|
db: Session, agent_id: uuid.UUID, folder_id: Optional[uuid.UUID]
|
|
) -> Optional[Agent]:
|
|
"""Assign an agent to a folder (or remove from folder if folder_id is None)"""
|
|
try:
|
|
agent = get_agent(db, agent_id)
|
|
if not agent:
|
|
return None
|
|
|
|
# If folder_id is None, remove the agent from the current folder
|
|
if folder_id is None:
|
|
agent.folder_id = None
|
|
db.commit()
|
|
db.refresh(agent)
|
|
logger.info(f"Agent removed from folder: {agent_id}")
|
|
return agent
|
|
|
|
# Verify if the folder exists
|
|
folder = get_agent_folder(db, folder_id)
|
|
if not folder:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Folder not found",
|
|
)
|
|
|
|
# Verify if the folder belongs to the same client as the agent
|
|
if folder.client_id != agent.client_id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="The folder must belong to the same client as the agent",
|
|
)
|
|
|
|
# Assign the agent to the folder
|
|
agent.folder_id = folder_id
|
|
db.commit()
|
|
db.refresh(agent)
|
|
logger.info(f"Agent assigned to folder: {folder_id}")
|
|
return agent
|
|
except SQLAlchemyError as e:
|
|
db.rollback()
|
|
logger.error(f"Error assigning agent to folder: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Error assigning agent to folder",
|
|
)
|
|
|
|
|
|
def get_agents_by_folder(
|
|
db: Session, folder_id: uuid.UUID, skip: int = 0, limit: int = 100
|
|
) -> List[Agent]:
|
|
"""List the agents of a specific folder"""
|
|
try:
|
|
return (
|
|
db.query(Agent)
|
|
.filter(Agent.folder_id == folder_id)
|
|
.offset(skip)
|
|
.limit(limit)
|
|
.all()
|
|
)
|
|
except SQLAlchemyError as e:
|
|
logger.error(f"Error listing agents of folder {folder_id}: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Error listing agents of folder",
|
|
)
|
|
|
|
|
|
async def import_agents_from_json(
|
|
db: Session,
|
|
agents_data: Dict[str, Any],
|
|
client_id: uuid.UUID,
|
|
folder_id: Optional[uuid.UUID] = None,
|
|
) -> List[Agent]:
|
|
"""
|
|
Import one or more agents from JSON data
|
|
|
|
Args:
|
|
db (Session): Database session
|
|
agents_data (Dict[str, Any]): JSON data containing agent definitions
|
|
client_id (uuid.UUID): Client ID to associate with the imported agents
|
|
folder_id (Optional[uuid.UUID]): Optional folder ID to assign agents to
|
|
|
|
Returns:
|
|
List[Agent]: List of imported agents
|
|
"""
|
|
# Check if the JSON contains a single agent or multiple agents
|
|
if "agents" in agents_data:
|
|
# Multiple agents import
|
|
agents_list = agents_data["agents"]
|
|
if not isinstance(agents_list, list):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="The 'agents' field must contain a list of agent definitions",
|
|
)
|
|
else:
|
|
# Single agent import
|
|
agents_list = [agents_data]
|
|
|
|
imported_agents = []
|
|
errors = []
|
|
id_mapping = {} # Maps original IDs to newly created agent IDs
|
|
|
|
# First pass: Import all non-workflow agents to establish ID mappings
|
|
for agent_data in agents_list:
|
|
# Skip workflow agents in the first pass, we'll handle them in the second pass
|
|
if agent_data.get("type") == "workflow":
|
|
continue
|
|
|
|
try:
|
|
# Store original ID if present for reference mapping
|
|
original_id = None
|
|
if "id" in agent_data:
|
|
original_id = agent_data["id"]
|
|
del agent_data["id"] # Always create a new agent with new ID
|
|
|
|
# Set the client ID for this agent if not provided
|
|
if "client_id" not in agent_data:
|
|
agent_data["client_id"] = str(client_id)
|
|
else:
|
|
# Ensure the provided client_id matches the authenticated client
|
|
agent_client_id = uuid.UUID(agent_data["client_id"])
|
|
if agent_client_id != client_id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail=f"Cannot import agent for client ID {agent_client_id}",
|
|
)
|
|
|
|
# Set folder_id if provided and not already set in the agent data
|
|
if folder_id and "folder_id" not in agent_data:
|
|
agent_data["folder_id"] = str(folder_id)
|
|
|
|
# Process config: Keep original configuration intact except for agent references
|
|
if "config" in agent_data and agent_data["config"]:
|
|
config = agent_data["config"]
|
|
|
|
# Process sub_agents if present
|
|
if "sub_agents" in config and config["sub_agents"]:
|
|
processed_sub_agents = []
|
|
|
|
for sub_agent_id in config["sub_agents"]:
|
|
try:
|
|
# Check if agent exists in database
|
|
existing_agent = get_agent(db, sub_agent_id)
|
|
if existing_agent:
|
|
processed_sub_agents.append(str(existing_agent.id))
|
|
else:
|
|
logger.warning(
|
|
f"Referenced sub_agent {sub_agent_id} not found - will be skipped"
|
|
)
|
|
except Exception as e:
|
|
logger.warning(
|
|
f"Error processing sub_agent {sub_agent_id}: {str(e)}"
|
|
)
|
|
|
|
config["sub_agents"] = processed_sub_agents
|
|
|
|
# Process agent_tools if present
|
|
if "agent_tools" in config and config["agent_tools"]:
|
|
processed_agent_tools = []
|
|
|
|
for agent_tool_id in config["agent_tools"]:
|
|
try:
|
|
# Check if agent exists in database
|
|
existing_agent = get_agent(db, agent_tool_id)
|
|
if existing_agent:
|
|
processed_agent_tools.append(str(existing_agent.id))
|
|
else:
|
|
logger.warning(
|
|
f"Referenced agent_tool {agent_tool_id} not found - will be skipped"
|
|
)
|
|
except Exception as e:
|
|
logger.warning(
|
|
f"Error processing agent_tool {agent_tool_id}: {str(e)}"
|
|
)
|
|
|
|
config["agent_tools"] = processed_agent_tools
|
|
|
|
# Convert to AgentCreate schema
|
|
agent_create = AgentCreate(**agent_data)
|
|
|
|
# Create the agent using existing create_agent function
|
|
db_agent = await create_agent(db, agent_create)
|
|
|
|
# Store mapping from original ID to new ID
|
|
if original_id:
|
|
id_mapping[original_id] = str(db_agent.id)
|
|
|
|
# If folder_id is provided but not in agent_data (couldn't be set at creation time)
|
|
# assign the agent to the folder after creation
|
|
if folder_id and not agent_data.get("folder_id"):
|
|
db_agent = assign_agent_to_folder(db, db_agent.id, folder_id)
|
|
|
|
# Set agent card URL if needed
|
|
if not db_agent.agent_card_url:
|
|
db_agent.agent_card_url = db_agent.agent_card_url_property
|
|
|
|
imported_agents.append(db_agent)
|
|
|
|
except Exception as e:
|
|
# Log the error and continue with other agents
|
|
agent_name = agent_data.get("name", "Unknown")
|
|
error_msg = f"Error importing agent '{agent_name}': {str(e)}"
|
|
logger.error(error_msg)
|
|
errors.append(error_msg)
|
|
|
|
# Second pass: Process workflow agents
|
|
for agent_data in agents_list:
|
|
# Only process workflow agents in the second pass
|
|
if agent_data.get("type") != "workflow":
|
|
continue
|
|
|
|
try:
|
|
# Store original ID if present for reference mapping
|
|
original_id = None
|
|
if "id" in agent_data:
|
|
original_id = agent_data["id"]
|
|
del agent_data["id"] # Always create a new agent with new ID
|
|
|
|
# Set the client ID for this agent if not provided
|
|
if "client_id" not in agent_data:
|
|
agent_data["client_id"] = str(client_id)
|
|
else:
|
|
# Ensure the provided client_id matches the authenticated client
|
|
agent_client_id = uuid.UUID(agent_data["client_id"])
|
|
if agent_client_id != client_id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail=f"Cannot import agent for client ID {agent_client_id}",
|
|
)
|
|
|
|
# Set folder_id if provided and not already set in the agent data
|
|
if folder_id and "folder_id" not in agent_data:
|
|
agent_data["folder_id"] = str(folder_id)
|
|
|
|
# Process workflow nodes
|
|
if "config" in agent_data and agent_data["config"]:
|
|
config = agent_data["config"]
|
|
|
|
# Process workflow nodes
|
|
if "workflow" in config and config["workflow"]:
|
|
workflow = config["workflow"]
|
|
|
|
if "nodes" in workflow and isinstance(workflow["nodes"], list):
|
|
for node in workflow["nodes"]:
|
|
if (
|
|
isinstance(node, dict)
|
|
and node.get("type") == "agent-node"
|
|
):
|
|
if "data" in node and "agent" in node["data"]:
|
|
agent_node = node["data"]["agent"]
|
|
|
|
# Store the original node ID
|
|
node_agent_id = None
|
|
if "id" in agent_node:
|
|
node_agent_id = agent_node["id"]
|
|
|
|
# Check if this ID is in our mapping (we created it in this import)
|
|
if node_agent_id in id_mapping:
|
|
# Use our newly created agent
|
|
# Get the agent from database with the mapped ID
|
|
mapped_id = uuid.UUID(
|
|
id_mapping[node_agent_id]
|
|
)
|
|
db_agent = get_agent(db, mapped_id)
|
|
if db_agent:
|
|
# Replace with database agent definition
|
|
# Extract agent data as dictionary
|
|
agent_dict = {
|
|
"id": str(db_agent.id),
|
|
"name": db_agent.name,
|
|
"description": db_agent.description,
|
|
"role": db_agent.role,
|
|
"goal": db_agent.goal,
|
|
"type": db_agent.type,
|
|
"model": db_agent.model,
|
|
"instruction": db_agent.instruction,
|
|
"config": db_agent.config,
|
|
}
|
|
node["data"]["agent"] = agent_dict
|
|
else:
|
|
# Check if this agent exists in database
|
|
try:
|
|
existing_agent = get_agent(
|
|
db, node_agent_id
|
|
)
|
|
if existing_agent:
|
|
# Replace with database agent definition
|
|
# Extract agent data as dictionary
|
|
agent_dict = {
|
|
"id": str(existing_agent.id),
|
|
"name": existing_agent.name,
|
|
"description": existing_agent.description,
|
|
"role": existing_agent.role,
|
|
"goal": existing_agent.goal,
|
|
"type": existing_agent.type,
|
|
"model": existing_agent.model,
|
|
"instruction": existing_agent.instruction,
|
|
"config": existing_agent.config,
|
|
}
|
|
node["data"]["agent"] = agent_dict
|
|
else:
|
|
# Agent doesn't exist, so we'll create a new one
|
|
# First, remove ID to get a new one
|
|
if "id" in agent_node:
|
|
del agent_node["id"]
|
|
|
|
# Set client_id to match parent
|
|
agent_node["client_id"] = str(
|
|
client_id
|
|
)
|
|
|
|
# Create agent
|
|
inner_agent_create = AgentCreate(
|
|
**agent_node
|
|
)
|
|
inner_db_agent = await create_agent(
|
|
db, inner_agent_create
|
|
)
|
|
|
|
# Replace with the new agent
|
|
# Extract agent data as dictionary
|
|
agent_dict = {
|
|
"id": str(inner_db_agent.id),
|
|
"name": inner_db_agent.name,
|
|
"description": inner_db_agent.description,
|
|
"role": inner_db_agent.role,
|
|
"goal": inner_db_agent.goal,
|
|
"type": inner_db_agent.type,
|
|
"model": inner_db_agent.model,
|
|
"instruction": inner_db_agent.instruction,
|
|
"config": inner_db_agent.config,
|
|
}
|
|
node["data"]["agent"] = agent_dict
|
|
except Exception as e:
|
|
logger.warning(
|
|
f"Error processing agent node {node_agent_id}: {str(e)}"
|
|
)
|
|
# Continue using the agent definition as is,
|
|
# but without ID to get a new one
|
|
if "id" in agent_node:
|
|
del agent_node["id"]
|
|
agent_node["client_id"] = str(client_id)
|
|
|
|
# Process sub_agents if present
|
|
if "sub_agents" in config and config["sub_agents"]:
|
|
processed_sub_agents = []
|
|
|
|
for sub_agent_id in config["sub_agents"]:
|
|
# Check if agent exists in database
|
|
try:
|
|
# Check if this is an agent we just created
|
|
if sub_agent_id in id_mapping:
|
|
processed_sub_agents.append(id_mapping[sub_agent_id])
|
|
else:
|
|
# Check if this agent exists in database
|
|
existing_agent = get_agent(db, sub_agent_id)
|
|
if existing_agent:
|
|
processed_sub_agents.append(str(existing_agent.id))
|
|
else:
|
|
logger.warning(
|
|
f"Referenced sub_agent {sub_agent_id} not found - will be skipped"
|
|
)
|
|
except Exception as e:
|
|
logger.warning(
|
|
f"Error processing sub_agent {sub_agent_id}: {str(e)}"
|
|
)
|
|
|
|
config["sub_agents"] = processed_sub_agents
|
|
|
|
# Convert to AgentCreate schema
|
|
agent_create = AgentCreate(**agent_data)
|
|
|
|
# Create the agent using existing create_agent function
|
|
db_agent = await create_agent(db, agent_create)
|
|
|
|
# Store mapping from original ID to new ID
|
|
if original_id:
|
|
id_mapping[original_id] = str(db_agent.id)
|
|
|
|
# If folder_id is provided but not in agent_data (couldn't be set at creation time)
|
|
# assign the agent to the folder after creation
|
|
if folder_id and not agent_data.get("folder_id"):
|
|
db_agent = assign_agent_to_folder(db, db_agent.id, folder_id)
|
|
|
|
# Set agent card URL if needed
|
|
if not db_agent.agent_card_url:
|
|
db_agent.agent_card_url = db_agent.agent_card_url_property
|
|
|
|
imported_agents.append(db_agent)
|
|
|
|
except Exception as e:
|
|
# Log the error and continue with other agents
|
|
agent_name = agent_data.get("name", "Unknown")
|
|
error_msg = f"Error importing agent '{agent_name}': {str(e)}"
|
|
logger.error(error_msg)
|
|
errors.append(error_msg)
|
|
|
|
# If no agents were imported successfully, raise an error
|
|
if not imported_agents and errors:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
detail={"message": "Failed to import any agents", "errors": errors},
|
|
)
|
|
|
|
return imported_agents
|