chore(cleanup): remove contact-related files and update JWT expiration time settings

This commit is contained in:
Davidson Gomes 2025-05-07 11:43:00 -03:00
parent 8979251541
commit 0e3c331a72
21 changed files with 151 additions and 744 deletions

View File

@ -23,7 +23,6 @@ src/
│ ├── auth_routes.py # Authentication routes (login, registration) │ ├── auth_routes.py # Authentication routes (login, registration)
│ ├── chat_routes.py # Routes for chat interactions with agents │ ├── chat_routes.py # Routes for chat interactions with agents
│ ├── client_routes.py # Routes to manage clients │ ├── client_routes.py # Routes to manage clients
│ ├── contact_routes.py # Routes to manage contacts
│ ├── mcp_server_routes.py # Routes to manage MCP servers │ ├── mcp_server_routes.py # Routes to manage MCP servers
│ ├── session_routes.py # Routes to manage chat sessions │ ├── session_routes.py # Routes to manage chat sessions
│ └── tool_routes.py # Routes to manage tools for agents │ └── tool_routes.py # Routes to manage tools for agents
@ -46,7 +45,6 @@ src/
│ ├── auth_service.py # JWT authentication logic │ ├── auth_service.py # JWT authentication logic
│ ├── audit_service.py # Audit logs logic │ ├── audit_service.py # Audit logs logic
│ ├── client_service.py # Business logic for clients │ ├── client_service.py # Business logic for clients
│ ├── contact_service.py # Business logic for contacts
│ ├── email_service.py # Email sending service │ ├── email_service.py # Email sending service
│ ├── mcp_server_service.py # Business logic for MCP servers │ ├── mcp_server_service.py # Business logic for MCP servers
│ ├── session_service.py # Business logic for chat sessions │ ├── session_service.py # Business logic for chat sessions

View File

@ -28,8 +28,8 @@ TOOLS_CACHE_TTL=3600
# JWT settings # JWT settings
JWT_SECRET_KEY="your-jwt-secret-key" JWT_SECRET_KEY="your-jwt-secret-key"
JWT_ALGORITHM="HS256" JWT_ALGORITHM="HS256"
# In minutes # In seconds
JWT_EXPIRATION_TIME=30 JWT_EXPIRATION_TIME=3600
# SendGrid # SendGrid
SENDGRID_API_KEY="your-sendgrid-api-key" SENDGRID_API_KEY="your-sendgrid-api-key"
@ -47,9 +47,3 @@ ADMIN_INITIAL_PASSWORD="strongpassword123"
DEMO_EMAIL="demo@example.com" DEMO_EMAIL="demo@example.com"
DEMO_PASSWORD="demo123" DEMO_PASSWORD="demo123"
DEMO_CLIENT_NAME="Demo Client" 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

View File

@ -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 # Alembic commands
init: init:
@ -43,18 +43,12 @@ seed-admin:
seed-client: seed-client:
python -m scripts.seeders.client_seeder python -m scripts.seeders.client_seeder
seed-agents:
python -m scripts.seeders.agent_seeder
seed-mcp-servers: seed-mcp-servers:
python -m scripts.seeders.mcp_server_seeder python -m scripts.seeders.mcp_server_seeder
seed-tools: seed-tools:
python -m scripts.seeders.tool_seeder python -m scripts.seeders.tool_seeder
seed-contacts:
python -m scripts.seeders.contact_seeder
seed-all: seed-all:
python -m scripts.run_seeders python -m scripts.run_seeders

View File

@ -8,7 +8,7 @@ The Evo AI platform allows:
- Creation and management of AI agents - Creation and management of AI agents
- Integration with different language models - Integration with different language models
- Client and contact management - Client management
- MCP server configuration - MCP server configuration
- Custom tools management - Custom tools management
- JWT authentication with email verification - JWT authentication with email verification
@ -463,16 +463,12 @@ REDIS_PASSWORD="your-redis-password"
# JWT settings # JWT settings
JWT_SECRET_KEY="your-jwt-secret-key" JWT_SECRET_KEY="your-jwt-secret-key"
JWT_ALGORITHM="HS256" JWT_ALGORITHM="HS256"
JWT_EXPIRATION_TIME=30 # In minutes JWT_EXPIRATION_TIME=30 # In seconds
# SendGrid for emails # SendGrid for emails
SENDGRID_API_KEY="your-sendgrid-api-key" SENDGRID_API_KEY="your-sendgrid-api-key"
EMAIL_FROM="noreply@yourdomain.com" EMAIL_FROM="noreply@yourdomain.com"
APP_URL="https://yourdomain.com" APP_URL="https://yourdomain.com"
# A2A settings
A2A_TASK_TTL=3600
A2A_HISTORY_TTL=86400
``` ```
### Project Dependencies ### Project Dependencies
@ -645,10 +641,8 @@ make alembic-reset # Reset database
# Seeders # Seeders
make seed-admin # Create default admin make seed-admin # Create default admin
make seed-client # Create default client make seed-client # Create default client
make seed-agents # Create example agents
make seed-mcp-servers # Create example MCP servers make seed-mcp-servers # Create example MCP servers
make seed-tools # Create example tools make seed-tools # Create example tools
make seed-contacts # Create example contacts
make seed-all # Run all seeders make seed-all # Run all seeders
# Code verification # Code verification

View File

@ -19,10 +19,6 @@ services:
REDIS_SSL: "false" REDIS_SSL: "false"
REDIS_KEY_PREFIX: "a2a:" REDIS_KEY_PREFIX: "a2a:"
REDIS_TTL: 3600 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} JWT_SECRET_KEY: ${JWT_SECRET_KEY}
SENDGRID_API_KEY: ${SENDGRID_API_KEY} SENDGRID_API_KEY: ${SENDGRID_API_KEY}
EMAIL_FROM: ${EMAIL_FROM} EMAIL_FROM: ${EMAIL_FROM}

View File

@ -10,54 +10,56 @@ import argparse
from dotenv import load_dotenv from dotenv import load_dotenv
# Configure logging # 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__) logger = logging.getLogger(__name__)
# Import seeders # Import seeders
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 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.admin_seeder import create_admin_user
from scripts.seeders.client_seeder import create_demo_client_and_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.mcp_server_seeder import create_mcp_servers
from scripts.seeders.tool_seeder import create_tools from scripts.seeders.tool_seeder import create_tools
from scripts.seeders.contact_seeder import create_demo_contacts
def setup_environment(): def setup_environment():
"""Configure the environment for seeders""" """Configure the environment for seeders"""
load_dotenv() load_dotenv()
# Check if essential environment variables are defined # Check if essential environment variables are defined
required_vars = ["POSTGRES_CONNECTION_STRING"] required_vars = ["POSTGRES_CONNECTION_STRING"]
missing_vars = [var for var in required_vars if not os.getenv(var)] missing_vars = [var for var in required_vars if not os.getenv(var)]
if missing_vars: 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 False
return True return True
def run_seeders(seeders): def run_seeders(seeders):
""" """
Run the specified seeders Run the specified seeders
Args: Args:
seeders (list): List of seeders to run seeders (list): List of seeders to run
Returns: Returns:
bool: True if all seeders were executed successfully, False otherwise bool: True if all seeders were executed successfully, False otherwise
""" """
all_seeders = { all_seeders = {
"admin": create_admin_user, "admin": create_admin_user,
"client": create_demo_client_and_user, "client": create_demo_client_and_user,
"agents": create_demo_agents,
"mcp_servers": create_mcp_servers, "mcp_servers": create_mcp_servers,
"tools": create_tools, "tools": create_tools,
"contacts": create_demo_contacts
} }
# Define the correct execution order (dependencies) # 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 no seeder is specified, run all
if not seeders: if not seeders:
seeders = seeder_order seeders = seeder_order
@ -68,15 +70,15 @@ def run_seeders(seeders):
logger.error(f"Invalid seeders: {', '.join(invalid_seeders)}") logger.error(f"Invalid seeders: {', '.join(invalid_seeders)}")
logger.info(f"Available seeders: {', '.join(all_seeders.keys())}") logger.info(f"Available seeders: {', '.join(all_seeders.keys())}")
return False return False
# Ensure seeders are executed in the correct order # Ensure seeders are executed in the correct order
seeders = [s for s in seeder_order if s in seeders] seeders = [s for s in seeder_order if s in seeders]
# Run seeders # Run seeders
success = True success = True
for seeder_name in seeders: for seeder_name in seeders:
logger.info(f"Running seeder: {seeder_name}") logger.info(f"Running seeder: {seeder_name}")
try: try:
seeder_func = all_seeders[seeder_name] seeder_func = all_seeders[seeder_name]
if not seeder_func(): if not seeder_func():
@ -85,22 +87,27 @@ def run_seeders(seeders):
except Exception as e: except Exception as e:
logger.error(f"Error running seeder {seeder_name}: {str(e)}") logger.error(f"Error running seeder {seeder_name}: {str(e)}")
success = False success = False
return success return success
def main(): def main():
"""Main function""" """Main function"""
parser = argparse.ArgumentParser(description='Run seeders to populate the database') 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.add_argument(
"--seeders",
nargs="+",
help="Seeders to run (admin, client, mcp_servers, tools)",
)
args = parser.parse_args() args = parser.parse_args()
# Configure environment # Configure environment
if not setup_environment(): if not setup_environment():
sys.exit(1) sys.exit(1)
# Run seeders # Run seeders
success = run_seeders(args.seeders) success = run_seeders(args.seeders)
# Output # Output
if success: if success:
logger.info("All seeders were executed successfully") logger.info("All seeders were executed successfully")
@ -109,5 +116,6 @@ def main():
logger.error("There were errors running the seeders") logger.error("There were errors running the seeders")
sys.exit(1) sys.exit(1)
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -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)

View File

@ -20,45 +20,48 @@ from src.models.models import User, Client
from src.utils.security import get_password_hash from src.utils.security import get_password_hash
# Configurar logging # 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__) logger = logging.getLogger(__name__)
def create_demo_client_and_user(): def create_demo_client_and_user():
"""Create a demo client and user in the system""" """Create a demo client and user in the system"""
try: try:
# Load environment variables # Load environment variables
load_dotenv() load_dotenv()
# Get database settings # Get database settings
db_url = os.getenv("POSTGRES_CONNECTION_STRING") db_url = os.getenv("POSTGRES_CONNECTION_STRING")
if not db_url: if not db_url:
logger.error("Environment variable POSTGRES_CONNECTION_STRING not defined") logger.error("Environment variable POSTGRES_CONNECTION_STRING not defined")
return False return False
# Get demo user password (or use default) # Get demo user password (or use default)
demo_password = os.getenv("DEMO_PASSWORD", "demo123") demo_password = os.getenv("DEMO_PASSWORD", "demo123")
# Demo client and user settings # Demo client and user settings
demo_client_name = os.getenv("DEMO_CLIENT_NAME", "Demo Client") demo_client_name = os.getenv("DEMO_CLIENT_NAME", "Demo Client")
demo_email = os.getenv("DEMO_EMAIL", "demo@example.com") demo_email = os.getenv("DEMO_EMAIL", "demo@example.com")
# Connect to the database # Connect to the database
engine = create_engine(db_url) engine = create_engine(db_url)
Session = sessionmaker(bind=engine) Session = sessionmaker(bind=engine)
session = Session() session = Session()
try: try:
# Check if the user already exists # Check if the user already exists
existing_user = session.query(User).filter(User.email == demo_email).first() existing_user = session.query(User).filter(User.email == demo_email).first()
if existing_user: if existing_user:
logger.info(f"Demo user with email {demo_email} already exists") logger.info(f"Demo user with email {demo_email} already exists")
return True return True
# Create demo client # 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.add(demo_client)
session.flush() # Get the client ID session.flush() # Get the client ID
# Create demo user associated with the client # Create demo user associated with the client
demo_user = User( demo_user = User(
email=demo_email, email=demo_email,
@ -66,28 +69,29 @@ def create_demo_client_and_user():
client_id=demo_client.id, client_id=demo_client.id,
is_admin=False, is_admin=False,
is_active=True, is_active=True,
email_verified=True email_verified=True,
) )
# Add and commit # Add and commit
session.add(demo_user) session.add(demo_user)
session.commit() session.commit()
logger.info(f"Demo client '{demo_client_name}' created successfully") logger.info(f"Demo client '{demo_client_name}' created successfully")
logger.info(f"Demo user created successfully: {demo_email}") logger.info(f"Demo user created successfully: {demo_email}")
return True return True
except SQLAlchemyError as e: except SQLAlchemyError as e:
session.rollback() session.rollback()
logger.error(f"Database error when creating demo client/user: {str(e)}") logger.error(f"Database error when creating demo client/user: {str(e)}")
return False return False
except Exception as e: except Exception as e:
logger.error(f"Error when creating demo client/user: {str(e)}") logger.error(f"Error when creating demo client/user: {str(e)}")
return False return False
finally: finally:
session.close() session.close()
if __name__ == "__main__": if __name__ == "__main__":
success = create_demo_client_and_user() success = create_demo_client_and_user()
sys.exit(0 if success else 1) sys.exit(0 if success else 1)

View File

@ -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)

View File

@ -38,11 +38,11 @@ router = APIRouter(
) )
@router.websocket("/ws/{agent_id}/{contact_id}") @router.websocket("/ws/{agent_id}/{external_id}")
async def websocket_chat( async def websocket_chat(
websocket: WebSocket, websocket: WebSocket,
agent_id: str, agent_id: str,
contact_id: str, external_id: str,
db: Session = Depends(get_db), db: Session = Depends(get_db),
): ):
try: try:
@ -81,7 +81,7 @@ async def websocket_chat(
await verify_user_client(payload, db, agent.client_id) await verify_user_client(payload, db, agent.client_id)
logger.info( 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: while True:
@ -95,7 +95,7 @@ async def websocket_chat(
async for chunk in run_agent_stream( async for chunk in run_agent_stream(
agent_id=agent_id, agent_id=agent_id,
contact_id=contact_id, external_id=external_id,
message=message, message=message,
session_service=session_service, session_service=session_service,
artifacts_service=artifacts_service, artifacts_service=artifacts_service,
@ -162,7 +162,7 @@ async def chat(
try: try:
final_response_text = await run_agent( final_response_text = await run_agent(
request.agent_id, request.agent_id,
request.contact_id, request.external_id,
request.message, request.message,
session_service, session_service,
artifacts_service, artifacts_service,

View File

@ -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"
)

View File

@ -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): def create_redis_pool(config=None):
""" """
Create and return a Redis connection pool. Create and return a Redis connection pool.

View File

@ -47,7 +47,7 @@ class Settings(BaseSettings):
# JWT settings # JWT settings
JWT_SECRET_KEY: str = os.getenv("JWT_SECRET_KEY", secrets.token_urlsafe(32)) JWT_SECRET_KEY: str = os.getenv("JWT_SECRET_KEY", secrets.token_urlsafe(32))
JWT_ALGORITHM: str = os.getenv("JWT_ALGORITHM", "HS256") 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 settings
SENDGRID_API_KEY: str = os.getenv("SENDGRID_API_KEY", "") SENDGRID_API_KEY: str = os.getenv("SENDGRID_API_KEY", "")

View File

@ -18,7 +18,6 @@ import src.api.admin_routes
import src.api.chat_routes import src.api.chat_routes
import src.api.session_routes import src.api.session_routes
import src.api.agent_routes import src.api.agent_routes
import src.api.contact_routes
import src.api.mcp_server_routes import src.api.mcp_server_routes
import src.api.tool_routes import src.api.tool_routes
import src.api.client_routes import src.api.client_routes
@ -70,7 +69,6 @@ admin_router = src.api.admin_routes.router
chat_router = src.api.chat_routes.router chat_router = src.api.chat_routes.router
session_router = src.api.session_routes.router session_router = src.api.session_routes.router
agent_router = src.api.agent_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 mcp_server_router = src.api.mcp_server_routes.router
tool_router = src.api.tool_routes.router tool_router = src.api.tool_routes.router
client_router = src.api.client_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(chat_router, prefix=API_PREFIX)
app.include_router(session_router, prefix=API_PREFIX) app.include_router(session_router, prefix=API_PREFIX)
app.include_router(agent_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) app.include_router(a2a_router, prefix=API_PREFIX)

View File

@ -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): class Agent(Base):
__tablename__ = "agents" __tablename__ = "agents"

View File

@ -8,8 +8,8 @@ class ChatRequest(BaseModel):
agent_id: str = Field( agent_id: str = Field(
..., description="ID of the agent that will process the message" ..., description="ID of the agent that will process the message"
) )
contact_id: str = Field( external_id: str = Field(
..., description="ID of the contact that will process the message" ..., description="ID of the external_id that will process the message"
) )
message: str = Field(..., description="User message") message: str = Field(..., description="User message")

View File

@ -33,24 +33,6 @@ class Client(ClientBase):
from_attributes = True 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): class AgentBase(BaseModel):
name: Optional[str] = Field( name: Optional[str] = Field(
None, description="Agent name (no spaces or special characters)" None, description="Agent name (no spaces or special characters)"

View File

@ -315,13 +315,13 @@ class A2ATaskManager:
) )
# Collect the chunks of the agent's response # Collect the chunks of the agent's response
contact_id = task_params.sessionId external_id = task_params.sessionId
full_response = "" full_response = ""
# We use the same streaming function used in the WebSocket # We use the same streaming function used in the WebSocket
async for chunk in run_agent_stream( async for chunk in run_agent_stream(
agent_id=str(agent.id), agent_id=str(agent.id),
contact_id=contact_id, external_id=external_id,
message=query, message=query,
session_service=session_service, session_service=session_service,
artifacts_service=artifacts_service, artifacts_service=artifacts_service,
@ -438,13 +438,13 @@ class A2ATaskManager:
async def _run_agent(self, agent: Agent, query: str, session_id: str) -> str: async def _run_agent(self, agent: Agent, query: str, session_id: str) -> str:
"""Executes the agent to process the user query.""" """Executes the agent to process the user query."""
try: try:
# We use the session_id as contact_id to maintain the conversation continuity # We use the session_id as external_id to maintain the conversation continuity
contact_id = session_id external_id = session_id
# We call the same function used in the chat API # We call the same function used in the chat API
final_response = await run_agent( final_response = await run_agent(
agent_id=str(agent.id), agent_id=str(agent.id),
contact_id=contact_id, external_id=external_id,
message=query, message=query,
session_service=session_service, session_service=session_service,
artifacts_service=artifacts_service, artifacts_service=artifacts_service,

View File

@ -16,7 +16,7 @@ logger = setup_logger(__name__)
async def run_agent( async def run_agent(
agent_id: str, agent_id: str,
contact_id: str, external_id: str,
message: str, message: str,
session_service: DatabaseSessionService, session_service: DatabaseSessionService,
artifacts_service: InMemoryArtifactService, artifacts_service: InMemoryArtifactService,
@ -27,7 +27,9 @@ async def run_agent(
): ):
exit_stack = None exit_stack = None
try: 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}") logger.info(f"Received message: {message}")
get_root_agent = get_agent(db, agent_id) get_root_agent = get_agent(db, agent_id)
@ -50,22 +52,22 @@ async def run_agent(
artifact_service=artifacts_service, artifact_service=artifacts_service,
memory_service=memory_service, memory_service=memory_service,
) )
adk_session_id = contact_id + "_" + agent_id adk_session_id = external_id + "_" + agent_id
if session_id is None: if session_id is None:
session_id = adk_session_id 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( session = session_service.get_session(
app_name=agent_id, app_name=agent_id,
user_id=contact_id, user_id=external_id,
session_id=adk_session_id, session_id=adk_session_id,
) )
if session is None: 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( session = session_service.create_session(
app_name=agent_id, app_name=agent_id,
user_id=contact_id, user_id=external_id,
session_id=adk_session_id, session_id=adk_session_id,
) )
@ -80,7 +82,7 @@ async def run_agent(
async def process_events(): async def process_events():
try: try:
events_async = agent_runner.run_async( events_async = agent_runner.run_async(
user_id=contact_id, user_id=external_id,
session_id=adk_session_id, session_id=adk_session_id,
new_message=content, new_message=content,
) )
@ -139,7 +141,7 @@ async def run_agent(
# Add the session to memory after completion # Add the session to memory after completion
completed_session = session_service.get_session( completed_session = session_service.get_session(
app_name=agent_id, app_name=agent_id,
user_id=contact_id, user_id=external_id,
session_id=adk_session_id, session_id=adk_session_id,
) )
@ -180,7 +182,7 @@ async def run_agent(
async def run_agent_stream( async def run_agent_stream(
agent_id: str, agent_id: str,
contact_id: str, external_id: str,
message: str, message: str,
session_service: DatabaseSessionService, session_service: DatabaseSessionService,
artifacts_service: InMemoryArtifactService, artifacts_service: InMemoryArtifactService,
@ -190,7 +192,7 @@ async def run_agent_stream(
) -> AsyncGenerator[str, None]: ) -> AsyncGenerator[str, None]:
try: try:
logger.info( 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}") logger.info(f"Received message: {message}")
@ -214,22 +216,22 @@ async def run_agent_stream(
artifact_service=artifacts_service, artifact_service=artifacts_service,
memory_service=memory_service, memory_service=memory_service,
) )
adk_session_id = contact_id + "_" + agent_id adk_session_id = external_id + "_" + agent_id
if session_id is None: if session_id is None:
session_id = adk_session_id 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( session = session_service.get_session(
app_name=agent_id, app_name=agent_id,
user_id=contact_id, user_id=external_id,
session_id=adk_session_id, session_id=adk_session_id,
) )
if session is None: 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( session = session_service.create_session(
app_name=agent_id, app_name=agent_id,
user_id=contact_id, user_id=external_id,
session_id=adk_session_id, session_id=adk_session_id,
) )
@ -238,7 +240,7 @@ async def run_agent_stream(
try: try:
events_async = agent_runner.run_async( events_async = agent_runner.run_async(
user_id=contact_id, user_id=external_id,
session_id=adk_session_id, session_id=adk_session_id,
new_message=content, new_message=content,
) )
@ -252,7 +254,7 @@ async def run_agent_stream(
completed_session = session_service.get_session( completed_session = session_service.get_session(
app_name=agent_id, app_name=agent_id,
user_id=contact_id, user_id=external_id,
session_id=adk_session_id, session_id=adk_session_id,
) )

View File

@ -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",
)

View File

@ -197,10 +197,32 @@ class WorkflowAgent(BaseAgent):
print(f"\n🔄 CONDITION: {label} (Cycle {cycle_count})") print(f"\n🔄 CONDITION: {label} (Cycle {cycle_count})")
content = state.get("content", []) 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", "") session_id = state.get("session_id", "")
conversation_history = state.get("conversation_history", [])
# Check all conditions # Check all conditions
conditions_met = [] conditions_met = []
@ -213,9 +235,9 @@ class WorkflowAgent(BaseAgent):
expected_value = condition_data.get("value") expected_value = condition_data.get("value")
print( 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) conditions_met.append(condition_id)
condition_details.append( condition_details.append(
f"{field} {operator} '{expected_value}'" f"{field} {operator} '{expected_value}'"
@ -474,14 +496,35 @@ class WorkflowAgent(BaseAgent):
# If it's a condition node, evaluate the conditions # If it's a condition node, evaluate the conditions
if node_id in condition_nodes: if node_id in condition_nodes:
conditions = condition_nodes[node_id] conditions = condition_nodes[node_id]
any_condition_met = False
for condition in conditions: for condition in conditions:
condition_id = condition.get("id") 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 # 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: if is_condition_met:
any_condition_met = True
print( print(
f"Condition {condition_id} met. Moving to the next node." 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 no condition is met, use the bottom-handle if available
if node_id in edges_map and "bottom-handle" in edges_map[node_id]: if not any_condition_met:
print("No condition met. Using default path (bottom-handle).") if (
return edges_map[node_id]["bottom-handle"] node_id in edges_map
else: and "bottom-handle" in edges_map[node_id]
print("No condition met and no default path. Closing the flow.") ):
return END 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 # For regular nodes, simply follow the first available connection
if node_id in edges_map: if node_id in edges_map: