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

View File

@ -28,8 +28,8 @@ TOOLS_CACHE_TTL=3600
# JWT settings
JWT_SECRET_KEY="your-jwt-secret-key"
JWT_ALGORITHM="HS256"
# In minutes
JWT_EXPIRATION_TIME=30
# In seconds
JWT_EXPIRATION_TIME=3600
# SendGrid
SENDGRID_API_KEY="your-sendgrid-api-key"
@ -47,9 +47,3 @@ ADMIN_INITIAL_PASSWORD="strongpassword123"
DEMO_EMAIL="demo@example.com"
DEMO_PASSWORD="demo123"
DEMO_CLIENT_NAME="Demo Client"
# A2A settings
A2A_TASK_TTL=3600
A2A_HISTORY_TTL=86400
A2A_PUSH_NOTIFICATION_TTL=3600
A2A_SSE_CLIENT_TTL=300

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

View File

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

View File

@ -19,10 +19,6 @@ services:
REDIS_SSL: "false"
REDIS_KEY_PREFIX: "a2a:"
REDIS_TTL: 3600
A2A_TASK_TTL: 3600
A2A_HISTORY_TTL: 86400
A2A_PUSH_NOTIFICATION_TTL: 3600
A2A_SSE_CLIENT_TTL: 300
JWT_SECRET_KEY: ${JWT_SECRET_KEY}
SENDGRID_API_KEY: ${SENDGRID_API_KEY}
EMAIL_FROM: ${EMAIL_FROM}

View File

@ -10,17 +10,18 @@ import argparse
from dotenv import load_dotenv
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
# Import seeders
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from scripts.seeders.admin_seeder import create_admin_user
from scripts.seeders.client_seeder import create_demo_client_and_user
from scripts.seeders.agent_seeder import create_demo_agents
from scripts.seeders.mcp_server_seeder import create_mcp_servers
from scripts.seeders.tool_seeder import create_tools
from scripts.seeders.contact_seeder import create_demo_contacts
def setup_environment():
"""Configure the environment for seeders"""
@ -31,11 +32,14 @@ def setup_environment():
missing_vars = [var for var in required_vars if not os.getenv(var)]
if missing_vars:
logger.error(f"Required environment variables not defined: {', '.join(missing_vars)}")
logger.error(
f"Required environment variables not defined: {', '.join(missing_vars)}"
)
return False
return True
def run_seeders(seeders):
"""
Run the specified seeders
@ -49,14 +53,12 @@ def run_seeders(seeders):
all_seeders = {
"admin": create_admin_user,
"client": create_demo_client_and_user,
"agents": create_demo_agents,
"mcp_servers": create_mcp_servers,
"tools": create_tools,
"contacts": create_demo_contacts
}
# Define the correct execution order (dependencies)
seeder_order = ["admin", "client", "mcp_servers", "tools", "agents", "contacts"]
seeder_order = ["admin", "client", "mcp_servers", "tools"]
# If no seeder is specified, run all
if not seeders:
@ -88,10 +90,15 @@ def run_seeders(seeders):
return success
def main():
"""Main function"""
parser = argparse.ArgumentParser(description='Run seeders to populate the database')
parser.add_argument('--seeders', nargs='+', help='Seeders to run (admin, client, agents, mcp_servers, tools, contacts)')
parser = argparse.ArgumentParser(description="Run seeders to populate the database")
parser.add_argument(
"--seeders",
nargs="+",
help="Seeders to run (admin, client, mcp_servers, tools)",
)
args = parser.parse_args()
# Configure environment
@ -109,5 +116,6 @@ def main():
logger.error("There were errors running the seeders")
sys.exit(1)
if __name__ == "__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,9 +20,12 @@ from src.models.models import User, Client
from src.utils.security import get_password_hash
# Configurar logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
def create_demo_client_and_user():
"""Create a demo client and user in the system"""
try:
@ -55,7 +58,7 @@ def create_demo_client_and_user():
return True
# Create demo client
demo_client = Client(name=demo_client_name)
demo_client = Client(name=demo_client_name, email=demo_email)
session.add(demo_client)
session.flush() # Get the client ID
@ -66,7 +69,7 @@ def create_demo_client_and_user():
client_id=demo_client.id,
is_admin=False,
is_active=True,
email_verified=True
email_verified=True,
)
# Add and commit
@ -88,6 +91,7 @@ def create_demo_client_and_user():
finally:
session.close()
if __name__ == "__main__":
success = create_demo_client_and_user()
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(
websocket: WebSocket,
agent_id: str,
contact_id: str,
external_id: str,
db: Session = Depends(get_db),
):
try:
@ -81,7 +81,7 @@ async def websocket_chat(
await verify_user_client(payload, db, agent.client_id)
logger.info(
f"WebSocket connection established for agent {agent_id} and contact {contact_id}"
f"WebSocket connection established for agent {agent_id} and external_id {external_id}"
)
while True:
@ -95,7 +95,7 @@ async def websocket_chat(
async for chunk in run_agent_stream(
agent_id=agent_id,
contact_id=contact_id,
external_id=external_id,
message=message,
session_service=session_service,
artifacts_service=artifacts_service,
@ -162,7 +162,7 @@ async def chat(
try:
final_response_text = await run_agent(
request.agent_id,
request.contact_id,
request.external_id,
request.message,
session_service,
artifacts_service,

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):
"""
Create and return a Redis connection pool.

View File

@ -47,7 +47,7 @@ class Settings(BaseSettings):
# JWT settings
JWT_SECRET_KEY: str = os.getenv("JWT_SECRET_KEY", secrets.token_urlsafe(32))
JWT_ALGORITHM: str = os.getenv("JWT_ALGORITHM", "HS256")
JWT_EXPIRATION_TIME: int = int(os.getenv("JWT_EXPIRATION_TIME", 30))
JWT_EXPIRATION_TIME: int = int(os.getenv("JWT_EXPIRATION_TIME", 3600))
# SendGrid settings
SENDGRID_API_KEY: str = os.getenv("SENDGRID_API_KEY", "")

View File

@ -18,7 +18,6 @@ import src.api.admin_routes
import src.api.chat_routes
import src.api.session_routes
import src.api.agent_routes
import src.api.contact_routes
import src.api.mcp_server_routes
import src.api.tool_routes
import src.api.client_routes
@ -70,7 +69,6 @@ admin_router = src.api.admin_routes.router
chat_router = src.api.chat_routes.router
session_router = src.api.session_routes.router
agent_router = src.api.agent_routes.router
contact_router = src.api.contact_routes.router
mcp_server_router = src.api.mcp_server_routes.router
tool_router = src.api.tool_routes.router
client_router = src.api.client_routes.router
@ -85,7 +83,6 @@ app.include_router(client_router, prefix=API_PREFIX)
app.include_router(chat_router, prefix=API_PREFIX)
app.include_router(session_router, prefix=API_PREFIX)
app.include_router(agent_router, prefix=API_PREFIX)
app.include_router(contact_router, prefix=API_PREFIX)
app.include_router(a2a_router, prefix=API_PREFIX)

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):
__tablename__ = "agents"

View File

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

View File

@ -33,24 +33,6 @@ class Client(ClientBase):
from_attributes = True
class ContactBase(BaseModel):
ext_id: Optional[str] = None
name: Optional[str] = None
meta: Optional[Dict[str, Any]] = Field(default_factory=dict)
class ContactCreate(ContactBase):
client_id: UUID
class Contact(ContactBase):
id: UUID
client_id: UUID
class Config:
from_attributes = True
class AgentBase(BaseModel):
name: Optional[str] = Field(
None, description="Agent name (no spaces or special characters)"

View File

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

View File

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

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})")
content = state.get("content", [])
print(f"Evaluating condition for content: '{content}'")
conversation_history = state.get("conversation_history", [])
# Obter apenas o evento mais recente para avaliação
latest_event = None
if content and len(content) > 0:
# Ignorar eventos gerados por nós de condição para avaliação
for event in reversed(content):
# Verificar se é um evento gerado pelo condition_node
if (
event.author != "agent"
or not hasattr(event.content, "parts")
or not event.content.parts
):
latest_event = event
break
if latest_event:
print(
f"Avaliando condição apenas para o evento mais recente: '{latest_event}'"
)
# Usar apenas o evento mais recente para avaliação de condição
evaluation_state = state.copy()
if latest_event:
evaluation_state["content"] = [latest_event]
session_id = state.get("session_id", "")
conversation_history = state.get("conversation_history", [])
# Check all conditions
conditions_met = []
@ -213,9 +235,9 @@ class WorkflowAgent(BaseAgent):
expected_value = condition_data.get("value")
print(
f" Checking if {field} {operator} '{expected_value}' (current value: '{state.get(field, '')}')"
f" Checking if {field} {operator} '{expected_value}' (current value: '{evaluation_state.get(field, '')}')"
)
if self._evaluate_condition(condition, state):
if self._evaluate_condition(condition, evaluation_state):
conditions_met.append(condition_id)
condition_details.append(
f"{field} {operator} '{expected_value}'"
@ -474,14 +496,35 @@ class WorkflowAgent(BaseAgent):
# If it's a condition node, evaluate the conditions
if node_id in condition_nodes:
conditions = condition_nodes[node_id]
any_condition_met = False
for condition in conditions:
condition_id = condition.get("id")
# Get latest event for evaluation, ignoring condition node informational events
content = state.get("content", [])
latest_event = None
for event in reversed(content):
# Skip events generated by condition nodes
if (
event.author != "agent"
or not hasattr(event.content, "parts")
or not event.content.parts
):
latest_event = event
break
evaluation_state = state.copy()
if latest_event:
evaluation_state["content"] = [latest_event]
# Check if the condition is met
is_condition_met = self._evaluate_condition(condition, state)
is_condition_met = self._evaluate_condition(
condition, evaluation_state
)
if is_condition_met:
any_condition_met = True
print(
f"Condition {condition_id} met. Moving to the next node."
)
@ -498,12 +541,20 @@ class WorkflowAgent(BaseAgent):
)
# If no condition is met, use the bottom-handle if available
if node_id in edges_map and "bottom-handle" in edges_map[node_id]:
print("No condition met. Using default path (bottom-handle).")
return edges_map[node_id]["bottom-handle"]
else:
print("No condition met and no default path. Closing the flow.")
return END
if not any_condition_met:
if (
node_id in edges_map
and "bottom-handle" in edges_map[node_id]
):
print(
"No condition met. Using default path (bottom-handle)."
)
return edges_map[node_id]["bottom-handle"]
else:
print(
"No condition met and no default path. Closing the flow."
)
return END
# For regular nodes, simply follow the first available connection
if node_id in edges_map: