feat(dependencies): add psycopg2 for PostgreSQL support and update README description

This commit is contained in:
Davidson Gomes
2025-05-02 10:56:48 -03:00
parent 01d50af7ad
commit b8a95e047f
14 changed files with 233 additions and 147 deletions

View File

@@ -32,7 +32,7 @@ router = APIRouter(
# Session Routes
@router.get("/client/{client_id}", response_model=List[Adk_Session])
@router.get("/client/{client_id}")
async def get_client_sessions(
client_id: uuid.UUID,
db: Session = Depends(get_db),
@@ -43,7 +43,7 @@ async def get_client_sessions(
return get_sessions_by_client(db, client_id)
@router.get("/agent/{agent_id}", response_model=List[Adk_Session])
@router.get("/agent/{agent_id}")
async def get_agent_sessions(
agent_id: uuid.UUID,
db: Session = Depends(get_db),

View File

@@ -146,6 +146,7 @@ class MCPServer(Base):
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column(String, nullable=False)
description = Column(Text, nullable=True)
config_type = Column(String, nullable=False, default="studio")
config_json = Column(JSON, nullable=False, default={})
environments = Column(JSON, nullable=False, default={})
tools = Column(JSON, nullable=False, default=[])
@@ -157,6 +158,9 @@ class MCPServer(Base):
CheckConstraint(
"type IN ('official', 'community')", name="check_mcp_server_type"
),
CheckConstraint(
"config_type IN ('studio', 'sse')", name="check_mcp_server_config_type"
),
)

View File

@@ -190,6 +190,7 @@ class ToolConfig(BaseModel):
class MCPServerBase(BaseModel):
name: str
description: Optional[str] = None
config_type: str = Field(default="studio")
config_json: Dict[str, Any] = Field(default_factory=dict)
environments: Dict[str, Any] = Field(default_factory=dict)
tools: List[ToolConfig] = Field(default_factory=list)

View File

@@ -12,6 +12,21 @@ import httpx
logger = logging.getLogger(__name__)
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[uuid.UUID]) -> bool:
"""Validate if all sub-agents exist"""
for agent_id in sub_agents:
@@ -143,8 +158,13 @@ async def create_agent(db: Session, agent: AgentCreate) -> Agent:
if "mcp_servers" in config:
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"])
mcp_server = get_mcp_server(db, server_id)
if not mcp_server:
raise HTTPException(
status_code=400,
@@ -185,7 +205,22 @@ async def create_agent(db: Session, agent: AgentCreate) -> Agent:
agent.config = config
db_agent = Agent(**agent.model_dump())
# 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)
@@ -195,9 +230,20 @@ async def create_agent(db: Session, agent: AgentCreate) -> 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="Error creating agent",
detail=f"Error creating agent: {str(e)}",
)
@@ -294,8 +340,13 @@ async def update_agent(
if "mcp_servers" in config:
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"])
mcp_server = get_mcp_server(db, server_id)
if not mcp_server:
raise HTTPException(
status_code=400,
@@ -336,6 +387,10 @@ async def update_agent(
agent_data["config"] = 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"])
for key, value in agent_data.items():
setattr(agent, key, value)
@@ -348,21 +403,23 @@ async def update_agent(
def delete_agent(db: Session, agent_id: uuid.UUID) -> bool:
"""Remove an agent (soft delete)"""
"""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 deactivated successfully: {agent_id}")
logger.info(f"Agent deleted successfully: {agent_id}")
return True
except SQLAlchemyError as e:
db.rollback()
logger.error(f"Error deactivating agent {agent_id}: {str(e)}")
logger.error(f"Error deleting agent {agent_id}: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error deactivating agent",
detail="Error deleting agent",
)

View File

@@ -11,20 +11,36 @@ from src.services.agent_service import get_agents_by_client
import uuid
import logging
from datetime import datetime
logger = logging.getLogger(__name__)
def _session_to_dict(session: SessionModel):
"""Convert Session model to dictionary with created_at field"""
result = {
"id": session.id,
"app_name": session.app_name,
"user_id": session.user_id,
"state": session.state,
"create_time": session.create_time,
"update_time": session.update_time,
"created_at": session.create_time,
}
return result
def get_sessions_by_client(
db: Session,
client_id: uuid.UUID,
) -> List[SessionModel]:
) -> List[dict]:
"""Search for sessions of a client with pagination"""
try:
agents_by_client = get_agents_by_client(db, client_id)
sessions = []
for agent in agents_by_client:
sessions.extend(get_sessions_by_agent(db, agent.id))
db_sessions = get_sessions_by_agent(db, agent.id)
sessions.extend(db_sessions)
return sessions
except SQLAlchemyError as e:
@@ -40,13 +56,15 @@ def get_sessions_by_agent(
agent_id: uuid.UUID,
skip: int = 0,
limit: int = 100,
) -> List[SessionModel]:
) -> List[dict]:
"""Search for sessions of an agent with pagination"""
try:
agent_id_str = str(agent_id)
query = db.query(SessionModel).filter(SessionModel.app_name == agent_id_str)
return query.offset(skip).limit(limit).all()
db_sessions = query.offset(skip).limit(limit).all()
# Convert each session to dictionary with created_at field
return [_session_to_dict(session) for session in db_sessions]
except SQLAlchemyError as e:
logger.error(f"Error searching for sessions of agent {agent_id_str}: {str(e)}")
raise HTTPException(

View File

@@ -53,7 +53,7 @@ def create_user(
try:
# If not admin and no client_id, create an associated client
if not is_admin and local_client_id is None:
client = Client(name=user_data.name)
client = Client(name=user_data.name, email=user_data.email)
db.add(client)
db.flush() # Get the client ID
local_client_id = client.id