chore(cleanup): remove contact-related files and update JWT expiration time settings
This commit is contained in:
parent
8979251541
commit
0e3c331a72
@ -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
|
||||||
|
10
.env.example
10
.env.example
@ -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
|
|
||||||
|
8
Makefile
8
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
|
# 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
|
||||||
|
|
||||||
|
10
README.md
10
README.md
@ -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
|
||||||
|
@ -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}
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
|
@ -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)
|
||||||
|
@ -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)
|
|
@ -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,
|
||||||
|
@ -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"
|
|
||||||
)
|
|
@ -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.
|
||||||
|
@ -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", "")
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
|
||||||
|
@ -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)"
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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",
|
|
||||||
)
|
|
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user