From 0e3c331a72522748b7dfd630aa5abaa6b46e7a9f Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Wed, 7 May 2025 11:43:00 -0300 Subject: [PATCH] chore(cleanup): remove contact-related files and update JWT expiration time settings --- .cursorrules | 2 - .env.example | 10 +- Makefile | 8 +- README.md | 10 +- docker-compose.yml | 4 - scripts/run_seeders.py | 56 +++++---- scripts/seeders/agent_seeder.py | 171 --------------------------- scripts/seeders/client_seeder.py | 34 +++--- scripts/seeders/contact_seeder.py | 184 ------------------------------ src/api/chat_routes.py | 10 +- src/api/contact_routes.py | 122 -------------------- src/config/redis.py | 15 --- src/config/settings.py | 2 +- src/main.py | 3 - src/models/models.py | 12 -- src/schemas/chat.py | 4 +- src/schemas/schemas.py | 18 --- src/services/a2a_task_manager.py | 10 +- src/services/agent_runner.py | 38 +++--- src/services/contact_service.py | 109 ------------------ src/services/workflow_agent.py | 73 ++++++++++-- 21 files changed, 151 insertions(+), 744 deletions(-) delete mode 100644 scripts/seeders/agent_seeder.py delete mode 100644 scripts/seeders/contact_seeder.py delete mode 100644 src/api/contact_routes.py delete mode 100644 src/services/contact_service.py diff --git a/.cursorrules b/.cursorrules index d037a929..7fd787cd 100644 --- a/.cursorrules +++ b/.cursorrules @@ -23,7 +23,6 @@ src/ │ ├── auth_routes.py # Authentication routes (login, registration) │ ├── chat_routes.py # Routes for chat interactions with agents │ ├── client_routes.py # Routes to manage clients -│ ├── contact_routes.py # Routes to manage contacts │ ├── mcp_server_routes.py # Routes to manage MCP servers │ ├── session_routes.py # Routes to manage chat sessions │ └── tool_routes.py # Routes to manage tools for agents @@ -46,7 +45,6 @@ src/ │ ├── auth_service.py # JWT authentication logic │ ├── audit_service.py # Audit logs logic │ ├── client_service.py # Business logic for clients -│ ├── contact_service.py # Business logic for contacts │ ├── email_service.py # Email sending service │ ├── mcp_server_service.py # Business logic for MCP servers │ ├── session_service.py # Business logic for chat sessions diff --git a/.env.example b/.env.example index 5aed4c9e..70f2a096 100644 --- a/.env.example +++ b/.env.example @@ -28,8 +28,8 @@ TOOLS_CACHE_TTL=3600 # JWT settings JWT_SECRET_KEY="your-jwt-secret-key" JWT_ALGORITHM="HS256" -# In minutes -JWT_EXPIRATION_TIME=30 +# In seconds +JWT_EXPIRATION_TIME=3600 # SendGrid SENDGRID_API_KEY="your-sendgrid-api-key" @@ -47,9 +47,3 @@ ADMIN_INITIAL_PASSWORD="strongpassword123" DEMO_EMAIL="demo@example.com" DEMO_PASSWORD="demo123" DEMO_CLIENT_NAME="Demo Client" - -# A2A settings -A2A_TASK_TTL=3600 -A2A_HISTORY_TTL=86400 -A2A_PUSH_NOTIFICATION_TTL=3600 -A2A_SSE_CLIENT_TTL=300 diff --git a/Makefile b/Makefile index 88ecebc6..296ff0bb 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: migrate init revision upgrade downgrade run seed-admin seed-client seed-agents seed-mcp-servers seed-tools seed-contacts seed-all docker-build docker-up docker-down docker-logs lint format install install-dev venv +.PHONY: migrate init revision upgrade downgrade run seed-admin seed-client seed-mcp-servers seed-tools seed-all docker-build docker-up docker-down docker-logs lint format install install-dev venv # Alembic commands init: @@ -43,18 +43,12 @@ seed-admin: seed-client: python -m scripts.seeders.client_seeder -seed-agents: - python -m scripts.seeders.agent_seeder - seed-mcp-servers: python -m scripts.seeders.mcp_server_seeder seed-tools: python -m scripts.seeders.tool_seeder -seed-contacts: - python -m scripts.seeders.contact_seeder - seed-all: python -m scripts.run_seeders diff --git a/README.md b/README.md index 49b5d157..b79d10e0 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The Evo AI platform allows: - Creation and management of AI agents - Integration with different language models -- Client and contact management +- Client management - MCP server configuration - Custom tools management - JWT authentication with email verification @@ -463,16 +463,12 @@ REDIS_PASSWORD="your-redis-password" # JWT settings JWT_SECRET_KEY="your-jwt-secret-key" JWT_ALGORITHM="HS256" -JWT_EXPIRATION_TIME=30 # In minutes +JWT_EXPIRATION_TIME=30 # In seconds # SendGrid for emails SENDGRID_API_KEY="your-sendgrid-api-key" EMAIL_FROM="noreply@yourdomain.com" APP_URL="https://yourdomain.com" - -# A2A settings -A2A_TASK_TTL=3600 -A2A_HISTORY_TTL=86400 ``` ### Project Dependencies @@ -645,10 +641,8 @@ make alembic-reset # Reset database # Seeders make seed-admin # Create default admin make seed-client # Create default client -make seed-agents # Create example agents make seed-mcp-servers # Create example MCP servers make seed-tools # Create example tools -make seed-contacts # Create example contacts make seed-all # Run all seeders # Code verification diff --git a/docker-compose.yml b/docker-compose.yml index e97271ef..69163607 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,10 +19,6 @@ services: REDIS_SSL: "false" REDIS_KEY_PREFIX: "a2a:" REDIS_TTL: 3600 - A2A_TASK_TTL: 3600 - A2A_HISTORY_TTL: 86400 - A2A_PUSH_NOTIFICATION_TTL: 3600 - A2A_SSE_CLIENT_TTL: 300 JWT_SECRET_KEY: ${JWT_SECRET_KEY} SENDGRID_API_KEY: ${SENDGRID_API_KEY} EMAIL_FROM: ${EMAIL_FROM} diff --git a/scripts/run_seeders.py b/scripts/run_seeders.py index f322aa31..7490e0cc 100644 --- a/scripts/run_seeders.py +++ b/scripts/run_seeders.py @@ -10,54 +10,56 @@ import argparse from dotenv import load_dotenv # Configure logging -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" +) logger = logging.getLogger(__name__) # Import seeders sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from scripts.seeders.admin_seeder import create_admin_user from scripts.seeders.client_seeder import create_demo_client_and_user -from scripts.seeders.agent_seeder import create_demo_agents from scripts.seeders.mcp_server_seeder import create_mcp_servers from scripts.seeders.tool_seeder import create_tools -from scripts.seeders.contact_seeder import create_demo_contacts + def setup_environment(): """Configure the environment for seeders""" load_dotenv() - + # Check if essential environment variables are defined required_vars = ["POSTGRES_CONNECTION_STRING"] missing_vars = [var for var in required_vars if not os.getenv(var)] - + if missing_vars: - logger.error(f"Required environment variables not defined: {', '.join(missing_vars)}") + logger.error( + f"Required environment variables not defined: {', '.join(missing_vars)}" + ) return False - + return True + def run_seeders(seeders): """ Run the specified seeders - + Args: seeders (list): List of seeders to run - + Returns: bool: True if all seeders were executed successfully, False otherwise """ all_seeders = { "admin": create_admin_user, "client": create_demo_client_and_user, - "agents": create_demo_agents, "mcp_servers": create_mcp_servers, "tools": create_tools, - "contacts": create_demo_contacts } - + # Define the correct execution order (dependencies) - seeder_order = ["admin", "client", "mcp_servers", "tools", "agents", "contacts"] - + seeder_order = ["admin", "client", "mcp_servers", "tools"] + # If no seeder is specified, run all if not seeders: seeders = seeder_order @@ -68,15 +70,15 @@ def run_seeders(seeders): logger.error(f"Invalid seeders: {', '.join(invalid_seeders)}") logger.info(f"Available seeders: {', '.join(all_seeders.keys())}") return False - + # Ensure seeders are executed in the correct order seeders = [s for s in seeder_order if s in seeders] - + # Run seeders success = True for seeder_name in seeders: logger.info(f"Running seeder: {seeder_name}") - + try: seeder_func = all_seeders[seeder_name] if not seeder_func(): @@ -85,22 +87,27 @@ def run_seeders(seeders): except Exception as e: logger.error(f"Error running seeder {seeder_name}: {str(e)}") success = False - + return success + def main(): """Main function""" - parser = argparse.ArgumentParser(description='Run seeders to populate the database') - parser.add_argument('--seeders', nargs='+', help='Seeders to run (admin, client, agents, mcp_servers, tools, contacts)') + parser = argparse.ArgumentParser(description="Run seeders to populate the database") + parser.add_argument( + "--seeders", + nargs="+", + help="Seeders to run (admin, client, mcp_servers, tools)", + ) args = parser.parse_args() - + # Configure environment if not setup_environment(): sys.exit(1) - + # Run seeders success = run_seeders(args.seeders) - + # Output if success: logger.info("All seeders were executed successfully") @@ -109,5 +116,6 @@ def main(): logger.error("There were errors running the seeders") sys.exit(1) + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/scripts/seeders/agent_seeder.py b/scripts/seeders/agent_seeder.py deleted file mode 100644 index 8f2b618e..00000000 --- a/scripts/seeders/agent_seeder.py +++ /dev/null @@ -1,171 +0,0 @@ -""" -Script to create example agents for the demo client: -- Agent Support: configured to answer general questions -- Agent Sales: configured to answer about products -- Agent FAQ: configured to answer frequently asked questions -Each agent with pre-defined instructions and configurations -""" - -import os -import sys -import logging -import uuid -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from sqlalchemy.exc import SQLAlchemyError -from dotenv import load_dotenv -from src.models.models import Agent, Client, User - -# Configurar logging -logging.basicConfig( - level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" -) -logger = logging.getLogger(__name__) - - -def create_demo_agents(): - """Create example agents for the demo client""" - try: - # Load environment variables - load_dotenv() - - # Get database settings - db_url = os.getenv("POSTGRES_CONNECTION_STRING") - if not db_url: - logger.error("Environment variable POSTGRES_CONNECTION_STRING not defined") - return False - - # Connect to the database - engine = create_engine(db_url) - Session = sessionmaker(bind=engine) - session = Session() - - try: - # Obter o cliente demo pelo email do usuário - demo_email = os.getenv("DEMO_EMAIL", "demo@exemplo.com") - demo_user = session.query(User).filter(User.email == demo_email).first() - - if not demo_user or not demo_user.client_id: - logger.error( - f"Demo user not found or not associated with a client: {demo_email}" - ) - return False - - client_id = demo_user.client_id - - # Verificar se já existem agentes para este cliente - existing_agents = ( - session.query(Agent).filter(Agent.client_id == client_id).all() - ) - if existing_agents: - logger.info( - f"There are already {len(existing_agents)} agents for the client {client_id}" - ) - return True - - # Example agent definitions - agents = [ - { - "name": "Support_Agent", - "description": "Agent for general support and basic questions", - "type": "llm", - "model": "gpt-4.1-nano", - "api_key": "your-api-key-here", - "instruction": """ - You are a customer support agent. - Be friendly, objective and efficient. Answer customer questions - in a clear and concise manner. If you don't know the answer, - inform that you will consult a specialist and return soon. - """, - "config": { - "api_key": uuid.uuid4(), - "tools": [], - "mcp_servers": [], - "custom_tools": [], - "sub_agents": [], - }, - }, - { - "name": "Sales_Products", - "description": "Specialized agent in sales and information about products", - "type": "llm", - "model": "gpt-4.1-nano", - "api_key": "your-api-key-here", - "instruction": """ - You are a sales specialist. - Your goal is to provide detailed information about products, - compare different options, highlight benefits and competitive advantages. - Use a persuasive but honest language, and always seek to understand - the customer's needs before recommending a product. - """, - "config": { - "api_key": uuid.uuid4(), - "tools": [], - "mcp_servers": [], - "custom_tools": [], - "sub_agents": [], - }, - }, - { - "name": "FAQ_Bot", - "description": "Agent for answering frequently asked questions", - "type": "llm", - "model": "gpt-4.1-nano", - "api_key": "your-api-key-here", - "instruction": """ - You are a specialized agent for answering frequently asked questions. - Your answers should be direct, objective and based on the information - of the company. Use a simple and accessible language. If the question - is not related to the available FAQs, redirect the client to the - appropriate support channel. - """, - "config": { - "api_key": uuid.uuid4(), - "tools": [], - "mcp_servers": [], - "custom_tools": [], - "sub_agents": [], - }, - }, - ] - - # Create the agents - for agent_data in agents: - # Create the agent - agent = Agent( - client_id=client_id, - name=agent_data["name"], - description=agent_data["description"], - type=agent_data["type"], - model=agent_data["model"], - api_key=agent_data["api_key"], - instruction=agent_data["instruction"].strip(), - config=agent_data["config"], - ) - - session.add(agent) - logger.info( - f"Agent '{agent_data['name']}' created for the client {client_id}" - ) - - session.commit() - logger.info( - f"All example agents were created successfully for the client {client_id}" - ) - return True - - except SQLAlchemyError as e: - session.rollback() - logger.error(f"Database error when creating example agents: {str(e)}") - return False - - except Exception as e: - logger.error(f"Error when creating example agents: {str(e)}") - return False - finally: - session.close() - - -if __name__ == "__main__": - success = create_demo_agents() - sys.exit(0 if success else 1) diff --git a/scripts/seeders/client_seeder.py b/scripts/seeders/client_seeder.py index 0f2108a5..8bdd36be 100644 --- a/scripts/seeders/client_seeder.py +++ b/scripts/seeders/client_seeder.py @@ -20,45 +20,48 @@ from src.models.models import User, Client from src.utils.security import get_password_hash # Configurar logging -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" +) logger = logging.getLogger(__name__) + def create_demo_client_and_user(): """Create a demo client and user in the system""" try: # Load environment variables load_dotenv() - + # Get database settings db_url = os.getenv("POSTGRES_CONNECTION_STRING") if not db_url: logger.error("Environment variable POSTGRES_CONNECTION_STRING not defined") return False - + # Get demo user password (or use default) demo_password = os.getenv("DEMO_PASSWORD", "demo123") - + # Demo client and user settings demo_client_name = os.getenv("DEMO_CLIENT_NAME", "Demo Client") demo_email = os.getenv("DEMO_EMAIL", "demo@example.com") - + # Connect to the database engine = create_engine(db_url) Session = sessionmaker(bind=engine) session = Session() - + try: # Check if the user already exists existing_user = session.query(User).filter(User.email == demo_email).first() if existing_user: logger.info(f"Demo user with email {demo_email} already exists") return True - + # Create demo client - demo_client = Client(name=demo_client_name) + demo_client = Client(name=demo_client_name, email=demo_email) session.add(demo_client) session.flush() # Get the client ID - + # Create demo user associated with the client demo_user = User( email=demo_email, @@ -66,28 +69,29 @@ def create_demo_client_and_user(): client_id=demo_client.id, is_admin=False, is_active=True, - email_verified=True + email_verified=True, ) - + # Add and commit session.add(demo_user) session.commit() - + logger.info(f"Demo client '{demo_client_name}' created successfully") logger.info(f"Demo user created successfully: {demo_email}") return True - + except SQLAlchemyError as e: session.rollback() logger.error(f"Database error when creating demo client/user: {str(e)}") return False - + except Exception as e: logger.error(f"Error when creating demo client/user: {str(e)}") return False finally: session.close() + if __name__ == "__main__": success = create_demo_client_and_user() - sys.exit(0 if success else 1) \ No newline at end of file + sys.exit(0 if success else 1) diff --git a/scripts/seeders/contact_seeder.py b/scripts/seeders/contact_seeder.py deleted file mode 100644 index 6640aa99..00000000 --- a/scripts/seeders/contact_seeder.py +++ /dev/null @@ -1,184 +0,0 @@ -""" -Script to create example contacts for the demo client: -- Contacts with conversation history -- Different client profiles -- Fake data for demonstration -""" - -import os -import sys -import logging -import json -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from sqlalchemy.exc import SQLAlchemyError -from dotenv import load_dotenv -from src.models.models import Contact, User, Client - -# Configure logging -logging.basicConfig( - level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" -) -logger = logging.getLogger(__name__) - - -def create_demo_contacts(): - """Create example contacts for the demo client""" - try: - # Load environment variables - load_dotenv() - - # Get database settings - db_url = os.getenv("POSTGRES_CONNECTION_STRING") - if not db_url: - logger.error("Environment variable POSTGRES_CONNECTION_STRING not defined") - return False - - # Connect to the database - engine = create_engine(db_url) - Session = sessionmaker(bind=engine) - session = Session() - - try: - # Get demo client by user email - demo_email = os.getenv("DEMO_EMAIL", "demo@example.com") - demo_user = session.query(User).filter(User.email == demo_email).first() - - if not demo_user or not demo_user.client_id: - logger.error( - f"Demo user not found or not associated with a client: {demo_email}" - ) - return False - - client_id = demo_user.client_id - - # Check if there are already contacts for this client - existing_contacts = ( - session.query(Contact).filter(Contact.client_id == client_id).all() - ) - if existing_contacts: - logger.info( - f"There are already {len(existing_contacts)} contacts for the client {client_id}" - ) - return True - - # Example contact definitions - contacts = [ - { - "name": "Maria Silva", - "ext_id": "5511999998888", - "meta": { - "source": "whatsapp", - "tags": ["cliente_vip", "suporte_premium"], - "location": "São Paulo, SP", - "last_contact": "2023-08-15T14:30:00Z", - "account_details": { - "customer_since": "2020-03-10", - "plan": "Enterprise", - "payment_status": "active", - }, - }, - }, - { - "name": "João Santos", - "ext_id": "5511988887777", - "meta": { - "source": "whatsapp", - "tags": ["prospecção", "demo_solicitada"], - "location": "Rio de Janeiro, RJ", - "last_contact": "2023-09-20T10:15:00Z", - "interests": ["automação", "marketing", "chatbots"], - }, - }, - { - "name": "Ana Oliveira", - "ext_id": "5511977776666", - "meta": { - "source": "telegram", - "tags": ["suporte_técnico", "problema_resolvido"], - "location": "Belo Horizonte, MG", - "last_contact": "2023-09-25T16:45:00Z", - "support_cases": [ - { - "id": "SUP-2023-1234", - "status": "closed", - "priority": "high", - }, - { - "id": "SUP-2023-1567", - "status": "open", - "priority": "medium", - }, - ], - }, - }, - { - "name": "Carlos Pereira", - "ext_id": "5511966665555", - "meta": { - "source": "whatsapp", - "tags": ["cancelamento", "retenção"], - "location": "Porto Alegre, RS", - "last_contact": "2023-09-10T09:30:00Z", - "account_details": { - "customer_since": "2019-05-22", - "plan": "Professional", - "payment_status": "overdue", - "invoice_pending": True, - }, - }, - }, - { - "name": "Fernanda Lima", - "ext_id": "5511955554444", - "meta": { - "source": "telegram", - "tags": ["parceiro", "integrador"], - "location": "Curitiba, PR", - "last_contact": "2023-09-18T14:00:00Z", - "partner_details": { - "company": "TechSolutions Ltda", - "partner_level": "Gold", - "certified": True, - }, - }, - }, - ] - - # Create the contacts - for contact_data in contacts: - contact = Contact( - client_id=client_id, - name=contact_data["name"], - ext_id=contact_data["ext_id"], - meta=contact_data["meta"], - ) - - session.add(contact) - logger.info( - f"Contact '{contact_data['name']}' created for the client {client_id}" - ) - - session.commit() - logger.info( - f"All example contacts were created successfully for the client {client_id}" - ) - return True - - except SQLAlchemyError as e: - session.rollback() - logger.error( - f"Database error when creating example contacts: {str(e)}" - ) - return False - - except Exception as e: - logger.error(f"Error when creating example contacts: {str(e)}") - return False - finally: - session.close() - - -if __name__ == "__main__": - success = create_demo_contacts() - sys.exit(0 if success else 1) diff --git a/src/api/chat_routes.py b/src/api/chat_routes.py index c728de65..f69b73ce 100644 --- a/src/api/chat_routes.py +++ b/src/api/chat_routes.py @@ -38,11 +38,11 @@ router = APIRouter( ) -@router.websocket("/ws/{agent_id}/{contact_id}") +@router.websocket("/ws/{agent_id}/{external_id}") async def websocket_chat( websocket: WebSocket, agent_id: str, - contact_id: str, + external_id: str, db: Session = Depends(get_db), ): try: @@ -81,7 +81,7 @@ async def websocket_chat( await verify_user_client(payload, db, agent.client_id) logger.info( - f"WebSocket connection established for agent {agent_id} and contact {contact_id}" + f"WebSocket connection established for agent {agent_id} and external_id {external_id}" ) while True: @@ -95,7 +95,7 @@ async def websocket_chat( async for chunk in run_agent_stream( agent_id=agent_id, - contact_id=contact_id, + external_id=external_id, message=message, session_service=session_service, artifacts_service=artifacts_service, @@ -162,7 +162,7 @@ async def chat( try: final_response_text = await run_agent( request.agent_id, - request.contact_id, + request.external_id, request.message, session_service, artifacts_service, diff --git a/src/api/contact_routes.py b/src/api/contact_routes.py deleted file mode 100644 index 11fa15f7..00000000 --- a/src/api/contact_routes.py +++ /dev/null @@ -1,122 +0,0 @@ -from fastapi import APIRouter, Depends, HTTPException, status -from sqlalchemy.orm import Session -from src.config.database import get_db -from typing import List -import uuid -from src.core.jwt_middleware import ( - get_jwt_token, - verify_user_client, -) -from src.schemas.schemas import ( - Contact, - ContactCreate, -) -from src.services import ( - contact_service, -) -import logging - -logger = logging.getLogger(__name__) - - -router = APIRouter( - prefix="/contacts", - tags=["contacts"], - responses={404: {"description": "Not found"}}, -) - - -@router.post("/", response_model=Contact, status_code=status.HTTP_201_CREATED) -async def create_contact( - contact: ContactCreate, - db: Session = Depends(get_db), - payload: dict = Depends(get_jwt_token), -): - # Verify if the user has access to the contact's client - await verify_user_client(payload, db, contact.client_id) - - return contact_service.create_contact(db, contact) - - -@router.get("/{client_id}", response_model=List[Contact]) -async def read_contacts( - client_id: uuid.UUID, - skip: int = 0, - limit: int = 100, - db: Session = Depends(get_db), - payload: dict = Depends(get_jwt_token), -): - # Verify if the user has access to this client's data - await verify_user_client(payload, db, client_id) - - return contact_service.get_contacts_by_client(db, client_id, skip, limit) - - -@router.get("/{contact_id}", response_model=Contact) -async def read_contact( - contact_id: uuid.UUID, - db: Session = Depends(get_db), - payload: dict = Depends(get_jwt_token), -): - db_contact = contact_service.get_contact(db, contact_id) - if db_contact is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, detail="Contact not found" - ) - - # Verify if the user has access to the contact's client - await verify_user_client(payload, db, db_contact.client_id) - - return db_contact - - -@router.put("/{contact_id}", response_model=Contact) -async def update_contact( - contact_id: uuid.UUID, - contact: ContactCreate, - db: Session = Depends(get_db), - payload: dict = Depends(get_jwt_token), -): - # Get the current contact - db_current_contact = contact_service.get_contact(db, contact_id) - if db_current_contact is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, detail="Contact not found" - ) - - # Verify if the user has access to the contact's client - await verify_user_client(payload, db, db_current_contact.client_id) - - # Verify if the user is trying to change the client - if contact.client_id != db_current_contact.client_id: - # Verify if the user has access to the new client as well - await verify_user_client(payload, db, contact.client_id) - - db_contact = contact_service.update_contact(db, contact_id, contact) - if db_contact is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, detail="Contact not found" - ) - return db_contact - - -@router.delete("/{contact_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_contact( - contact_id: uuid.UUID, - db: Session = Depends(get_db), - payload: dict = Depends(get_jwt_token), -): - # Get the contact - db_contact = contact_service.get_contact(db, contact_id) - if db_contact is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, detail="Contact not found" - ) - - # Verify if the user has access to the contact's client - await verify_user_client(payload, db, db_contact.client_id) - - if not contact_service.delete_contact(db, contact_id): - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, detail="Contact not found" - ) diff --git a/src/config/redis.py b/src/config/redis.py index 29167e52..df853e8e 100644 --- a/src/config/redis.py +++ b/src/config/redis.py @@ -34,21 +34,6 @@ def get_redis_config(): } -def get_a2a_config(): - """ - Get A2A-specific cache TTL values from environment variables. - - Returns: - dict: A2A TTL configuration parameters - """ - return { - "task_ttl": int(os.getenv("A2A_TASK_TTL", 3600)), - "history_ttl": int(os.getenv("A2A_HISTORY_TTL", 86400)), - "push_notification_ttl": int(os.getenv("A2A_PUSH_NOTIFICATION_TTL", 3600)), - "sse_client_ttl": int(os.getenv("A2A_SSE_CLIENT_TTL", 300)), - } - - def create_redis_pool(config=None): """ Create and return a Redis connection pool. diff --git a/src/config/settings.py b/src/config/settings.py index d950618d..3b374484 100644 --- a/src/config/settings.py +++ b/src/config/settings.py @@ -47,7 +47,7 @@ class Settings(BaseSettings): # JWT settings JWT_SECRET_KEY: str = os.getenv("JWT_SECRET_KEY", secrets.token_urlsafe(32)) JWT_ALGORITHM: str = os.getenv("JWT_ALGORITHM", "HS256") - JWT_EXPIRATION_TIME: int = int(os.getenv("JWT_EXPIRATION_TIME", 30)) + JWT_EXPIRATION_TIME: int = int(os.getenv("JWT_EXPIRATION_TIME", 3600)) # SendGrid settings SENDGRID_API_KEY: str = os.getenv("SENDGRID_API_KEY", "") diff --git a/src/main.py b/src/main.py index 3a6b656a..9a9ef5de 100644 --- a/src/main.py +++ b/src/main.py @@ -18,7 +18,6 @@ import src.api.admin_routes import src.api.chat_routes import src.api.session_routes import src.api.agent_routes -import src.api.contact_routes import src.api.mcp_server_routes import src.api.tool_routes import src.api.client_routes @@ -70,7 +69,6 @@ admin_router = src.api.admin_routes.router chat_router = src.api.chat_routes.router session_router = src.api.session_routes.router agent_router = src.api.agent_routes.router -contact_router = src.api.contact_routes.router mcp_server_router = src.api.mcp_server_routes.router tool_router = src.api.tool_routes.router client_router = src.api.client_routes.router @@ -85,7 +83,6 @@ app.include_router(client_router, prefix=API_PREFIX) app.include_router(chat_router, prefix=API_PREFIX) app.include_router(session_router, prefix=API_PREFIX) app.include_router(agent_router, prefix=API_PREFIX) -app.include_router(contact_router, prefix=API_PREFIX) app.include_router(a2a_router, prefix=API_PREFIX) diff --git a/src/models/models.py b/src/models/models.py index 6d488fbb..f02f5747 100644 --- a/src/models/models.py +++ b/src/models/models.py @@ -51,18 +51,6 @@ class User(Base): ) -class Contact(Base): - __tablename__ = "contacts" - - id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) - client_id = Column(UUID(as_uuid=True), ForeignKey("clients.id", ondelete="CASCADE")) - ext_id = Column(String) - name = Column(String) - meta = Column(JSON, default={}) - created_at = Column(DateTime(timezone=True), server_default=func.now()) - updated_at = Column(DateTime(timezone=True), onupdate=func.now()) - - class Agent(Base): __tablename__ = "agents" diff --git a/src/schemas/chat.py b/src/schemas/chat.py index 80f68a10..406934ac 100644 --- a/src/schemas/chat.py +++ b/src/schemas/chat.py @@ -8,8 +8,8 @@ class ChatRequest(BaseModel): agent_id: str = Field( ..., description="ID of the agent that will process the message" ) - contact_id: str = Field( - ..., description="ID of the contact that will process the message" + external_id: str = Field( + ..., description="ID of the external_id that will process the message" ) message: str = Field(..., description="User message") diff --git a/src/schemas/schemas.py b/src/schemas/schemas.py index 37a90814..26078572 100644 --- a/src/schemas/schemas.py +++ b/src/schemas/schemas.py @@ -33,24 +33,6 @@ class Client(ClientBase): from_attributes = True -class ContactBase(BaseModel): - ext_id: Optional[str] = None - name: Optional[str] = None - meta: Optional[Dict[str, Any]] = Field(default_factory=dict) - - -class ContactCreate(ContactBase): - client_id: UUID - - -class Contact(ContactBase): - id: UUID - client_id: UUID - - class Config: - from_attributes = True - - class AgentBase(BaseModel): name: Optional[str] = Field( None, description="Agent name (no spaces or special characters)" diff --git a/src/services/a2a_task_manager.py b/src/services/a2a_task_manager.py index af882966..6ba11aa1 100644 --- a/src/services/a2a_task_manager.py +++ b/src/services/a2a_task_manager.py @@ -315,13 +315,13 @@ class A2ATaskManager: ) # Collect the chunks of the agent's response - contact_id = task_params.sessionId + external_id = task_params.sessionId full_response = "" # We use the same streaming function used in the WebSocket async for chunk in run_agent_stream( agent_id=str(agent.id), - contact_id=contact_id, + external_id=external_id, message=query, session_service=session_service, artifacts_service=artifacts_service, @@ -438,13 +438,13 @@ class A2ATaskManager: async def _run_agent(self, agent: Agent, query: str, session_id: str) -> str: """Executes the agent to process the user query.""" try: - # We use the session_id as contact_id to maintain the conversation continuity - contact_id = session_id + # We use the session_id as external_id to maintain the conversation continuity + external_id = session_id # We call the same function used in the chat API final_response = await run_agent( agent_id=str(agent.id), - contact_id=contact_id, + external_id=external_id, message=query, session_service=session_service, artifacts_service=artifacts_service, diff --git a/src/services/agent_runner.py b/src/services/agent_runner.py index b47ca3a2..e2e93020 100644 --- a/src/services/agent_runner.py +++ b/src/services/agent_runner.py @@ -16,7 +16,7 @@ logger = setup_logger(__name__) async def run_agent( agent_id: str, - contact_id: str, + external_id: str, message: str, session_service: DatabaseSessionService, artifacts_service: InMemoryArtifactService, @@ -27,7 +27,9 @@ async def run_agent( ): exit_stack = None try: - logger.info(f"Starting execution of agent {agent_id} for contact {contact_id}") + logger.info( + f"Starting execution of agent {agent_id} for external_id {external_id}" + ) logger.info(f"Received message: {message}") get_root_agent = get_agent(db, agent_id) @@ -50,22 +52,22 @@ async def run_agent( artifact_service=artifacts_service, memory_service=memory_service, ) - adk_session_id = contact_id + "_" + agent_id + adk_session_id = external_id + "_" + agent_id if session_id is None: session_id = adk_session_id - logger.info(f"Searching session for contact {contact_id}") + logger.info(f"Searching session for external_id {external_id}") session = session_service.get_session( app_name=agent_id, - user_id=contact_id, + user_id=external_id, session_id=adk_session_id, ) if session is None: - logger.info(f"Creating new session for contact {contact_id}") + logger.info(f"Creating new session for external_id {external_id}") session = session_service.create_session( app_name=agent_id, - user_id=contact_id, + user_id=external_id, session_id=adk_session_id, ) @@ -80,7 +82,7 @@ async def run_agent( async def process_events(): try: events_async = agent_runner.run_async( - user_id=contact_id, + user_id=external_id, session_id=adk_session_id, new_message=content, ) @@ -139,7 +141,7 @@ async def run_agent( # Add the session to memory after completion completed_session = session_service.get_session( app_name=agent_id, - user_id=contact_id, + user_id=external_id, session_id=adk_session_id, ) @@ -180,7 +182,7 @@ async def run_agent( async def run_agent_stream( agent_id: str, - contact_id: str, + external_id: str, message: str, session_service: DatabaseSessionService, artifacts_service: InMemoryArtifactService, @@ -190,7 +192,7 @@ async def run_agent_stream( ) -> AsyncGenerator[str, None]: try: logger.info( - f"Starting streaming execution of agent {agent_id} for contact {contact_id}" + f"Starting streaming execution of agent {agent_id} for external_id {external_id}" ) logger.info(f"Received message: {message}") @@ -214,22 +216,22 @@ async def run_agent_stream( artifact_service=artifacts_service, memory_service=memory_service, ) - adk_session_id = contact_id + "_" + agent_id + adk_session_id = external_id + "_" + agent_id if session_id is None: session_id = adk_session_id - logger.info(f"Searching session for contact {contact_id}") + logger.info(f"Searching session for external_id {external_id}") session = session_service.get_session( app_name=agent_id, - user_id=contact_id, + user_id=external_id, session_id=adk_session_id, ) if session is None: - logger.info(f"Creating new session for contact {contact_id}") + logger.info(f"Creating new session for external_id {external_id}") session = session_service.create_session( app_name=agent_id, - user_id=contact_id, + user_id=external_id, session_id=adk_session_id, ) @@ -238,7 +240,7 @@ async def run_agent_stream( try: events_async = agent_runner.run_async( - user_id=contact_id, + user_id=external_id, session_id=adk_session_id, new_message=content, ) @@ -252,7 +254,7 @@ async def run_agent_stream( completed_session = session_service.get_session( app_name=agent_id, - user_id=contact_id, + user_id=external_id, session_id=adk_session_id, ) diff --git a/src/services/contact_service.py b/src/services/contact_service.py deleted file mode 100644 index 61defd76..00000000 --- a/src/services/contact_service.py +++ /dev/null @@ -1,109 +0,0 @@ -from sqlalchemy.orm import Session -from sqlalchemy.exc import SQLAlchemyError -from fastapi import HTTPException, status -from src.models.models import Contact -from src.schemas.schemas import ContactCreate -from typing import List, Optional -import uuid -import logging - -logger = logging.getLogger(__name__) - - -def get_contact(db: Session, contact_id: uuid.UUID) -> Optional[Contact]: - """Search for a contact by ID""" - try: - contact = db.query(Contact).filter(Contact.id == contact_id).first() - if not contact: - logger.warning(f"Contact not found: {contact_id}") - return None - return contact - except SQLAlchemyError as e: - logger.error(f"Error searching for contact {contact_id}: {str(e)}") - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Error searching for contact", - ) - - -def get_contacts_by_client( - db: Session, client_id: uuid.UUID, skip: int = 0, limit: int = 100 -) -> List[Contact]: - """Search for contacts of a client with pagination""" - try: - return ( - db.query(Contact) - .filter(Contact.client_id == client_id) - .offset(skip) - .limit(limit) - .all() - ) - except SQLAlchemyError as e: - logger.error(f"Error searching for contacts of client {client_id}: {str(e)}") - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Error searching for contacts", - ) - - -def create_contact(db: Session, contact: ContactCreate) -> Contact: - """Create a new contact""" - try: - db_contact = Contact(**contact.model_dump()) - db.add(db_contact) - db.commit() - db.refresh(db_contact) - logger.info(f"Contact created successfully: {db_contact.id}") - return db_contact - except SQLAlchemyError as e: - db.rollback() - logger.error(f"Error creating contact: {str(e)}") - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Error creating contact", - ) - - -def update_contact( - db: Session, contact_id: uuid.UUID, contact: ContactCreate -) -> Optional[Contact]: - """Update an existing contact""" - try: - db_contact = get_contact(db, contact_id) - if not db_contact: - return None - - for key, value in contact.model_dump().items(): - setattr(db_contact, key, value) - - db.commit() - db.refresh(db_contact) - logger.info(f"Contact updated successfully: {contact_id}") - return db_contact - except SQLAlchemyError as e: - db.rollback() - logger.error(f"Error updating contact {contact_id}: {str(e)}") - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Error updating contact", - ) - - -def delete_contact(db: Session, contact_id: uuid.UUID) -> bool: - """Remove a contact""" - try: - db_contact = get_contact(db, contact_id) - if not db_contact: - return False - - db.delete(db_contact) - db.commit() - logger.info(f"Contact removed successfully: {contact_id}") - return True - except SQLAlchemyError as e: - db.rollback() - logger.error(f"Error removing contact {contact_id}: {str(e)}") - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Error removing contact", - ) diff --git a/src/services/workflow_agent.py b/src/services/workflow_agent.py index 788de261..94f3edb8 100644 --- a/src/services/workflow_agent.py +++ b/src/services/workflow_agent.py @@ -197,10 +197,32 @@ class WorkflowAgent(BaseAgent): print(f"\n🔄 CONDITION: {label} (Cycle {cycle_count})") content = state.get("content", []) - print(f"Evaluating condition for content: '{content}'") + conversation_history = state.get("conversation_history", []) + + # Obter apenas o evento mais recente para avaliação + latest_event = None + if content and len(content) > 0: + # Ignorar eventos gerados por nós de condição para avaliação + for event in reversed(content): + # Verificar se é um evento gerado pelo condition_node + if ( + event.author != "agent" + or not hasattr(event.content, "parts") + or not event.content.parts + ): + latest_event = event + break + if latest_event: + print( + f"Avaliando condição apenas para o evento mais recente: '{latest_event}'" + ) + + # Usar apenas o evento mais recente para avaliação de condição + evaluation_state = state.copy() + if latest_event: + evaluation_state["content"] = [latest_event] session_id = state.get("session_id", "") - conversation_history = state.get("conversation_history", []) # Check all conditions conditions_met = [] @@ -213,9 +235,9 @@ class WorkflowAgent(BaseAgent): expected_value = condition_data.get("value") print( - f" Checking if {field} {operator} '{expected_value}' (current value: '{state.get(field, '')}')" + f" Checking if {field} {operator} '{expected_value}' (current value: '{evaluation_state.get(field, '')}')" ) - if self._evaluate_condition(condition, state): + if self._evaluate_condition(condition, evaluation_state): conditions_met.append(condition_id) condition_details.append( f"{field} {operator} '{expected_value}' ✅" @@ -474,14 +496,35 @@ class WorkflowAgent(BaseAgent): # If it's a condition node, evaluate the conditions if node_id in condition_nodes: conditions = condition_nodes[node_id] + any_condition_met = False for condition in conditions: condition_id = condition.get("id") + # Get latest event for evaluation, ignoring condition node informational events + content = state.get("content", []) + latest_event = None + for event in reversed(content): + # Skip events generated by condition nodes + if ( + event.author != "agent" + or not hasattr(event.content, "parts") + or not event.content.parts + ): + latest_event = event + break + + evaluation_state = state.copy() + if latest_event: + evaluation_state["content"] = [latest_event] + # Check if the condition is met - is_condition_met = self._evaluate_condition(condition, state) + is_condition_met = self._evaluate_condition( + condition, evaluation_state + ) if is_condition_met: + any_condition_met = True print( f"Condition {condition_id} met. Moving to the next node." ) @@ -498,12 +541,20 @@ class WorkflowAgent(BaseAgent): ) # If no condition is met, use the bottom-handle if available - if node_id in edges_map and "bottom-handle" in edges_map[node_id]: - print("No condition met. Using default path (bottom-handle).") - return edges_map[node_id]["bottom-handle"] - else: - print("No condition met and no default path. Closing the flow.") - return END + if not any_condition_met: + if ( + node_id in edges_map + and "bottom-handle" in edges_map[node_id] + ): + print( + "No condition met. Using default path (bottom-handle)." + ) + return edges_map[node_id]["bottom-handle"] + else: + print( + "No condition met and no default path. Closing the flow." + ) + return END # For regular nodes, simply follow the first available connection if node_id in edges_map: