diff --git a/migrations/versions/2d612b95d0ea_add_tools_field_to_mcp_servers.py b/migrations/versions/2d612b95d0ea_add_tools_field_to_mcp_servers.py new file mode 100644 index 00000000..bb8d76d7 --- /dev/null +++ b/migrations/versions/2d612b95d0ea_add_tools_field_to_mcp_servers.py @@ -0,0 +1,32 @@ +"""add_tools_field_to_mcp_servers + +Revision ID: 2d612b95d0ea +Revises: da8e7fb4da5d +Create Date: 2025-04-28 12:39:21.430144 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision: str = '2d612b95d0ea' +down_revision: Union[str, None] = 'da8e7fb4da5d' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('mcp_servers', sa.Column('tools', sa.JSON(), nullable=False, server_default='[]')) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('mcp_servers', 'tools') + # ### end Alembic commands ### diff --git a/src/api/__pycache__/routes.cpython-310.pyc b/src/api/__pycache__/routes.cpython-310.pyc index fac5aa95..e5d23638 100644 Binary files a/src/api/__pycache__/routes.cpython-310.pyc and b/src/api/__pycache__/routes.cpython-310.pyc differ diff --git a/src/api/routes.py b/src/api/routes.py index db655f80..297f8521 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -7,11 +7,16 @@ from datetime import datetime from src.config.database import get_db from src.core.middleware import get_api_key from src.schemas.schemas import ( - Client, ClientCreate, - Contact, ContactCreate, - Agent, AgentCreate, - MCPServer, MCPServerCreate, - Tool, ToolCreate, + Client, + ClientCreate, + Contact, + ContactCreate, + Agent, + AgentCreate, + MCPServer, + MCPServerCreate, + Tool, + ToolCreate, ) from src.services import ( client_service, @@ -26,7 +31,16 @@ from src.core.exceptions import AgentNotFoundError from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService from google.adk.sessions import DatabaseSessionService from google.adk.memory import InMemoryMemoryService +from google.adk.events import Event +from google.adk.sessions import Session as Adk_Session from src.config.settings import settings +from src.services.session_service import ( + get_session_events, + get_session_by_id, + delete_session, + get_sessions_by_agent, + get_sessions_by_client, +) router = APIRouter() @@ -38,283 +52,394 @@ session_service = DatabaseSessionService(db_url=POSTGRES_CONNECTION_STRING) artifacts_service = InMemoryArtifactService() memory_service = InMemoryMemoryService() -@router.post("/chat", response_model=ChatResponse, responses={ - 400: {"model": ErrorResponse}, - 404: {"model": ErrorResponse}, - 500: {"model": ErrorResponse} -}) + +@router.post( + "/chat", + response_model=ChatResponse, + responses={ + 400: {"model": ErrorResponse}, + 404: {"model": ErrorResponse}, + 500: {"model": ErrorResponse}, + }, +) async def chat( - request: ChatRequest, + request: ChatRequest, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): try: final_response_text = await run_agent( - request.agent_id, + request.agent_id, request.contact_id, - request.message, - session_service, + request.message, + session_service, artifacts_service, memory_service, - db + db, ) - + return { "response": final_response_text, "status": "success", - "timestamp": datetime.now().isoformat() + "timestamp": datetime.now().isoformat(), } except AgentNotFoundError as e: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) except Exception as e: - raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e) + ) + + +# Rotas para Sessões +@router.get("/sessions/client/{client_id}", response_model=List[Adk_Session]) +def get_client_sessions( + client_id: uuid.UUID, + db: Session = Depends(get_db), + api_key: str = Security(get_api_key), +): + return get_sessions_by_client(db, client_id) + + +@router.get("/sessions/agent/{agent_id}", response_model=List[Adk_Session]) +def get_agent_sessions( + agent_id: uuid.UUID, + db: Session = Depends(get_db), + api_key: str = Security(get_api_key), + skip: int = 0, + limit: int = 100, +): + return get_sessions_by_agent(db, agent_id, skip, limit) + + +@router.get("/sessions/{session_id}", response_model=Adk_Session) +def get_session( + session_id: str, + api_key: str = Security(get_api_key), +): + return get_session_by_id(session_service, session_id) + + +@router.get( + "/sessions/{session_id}/messages", + response_model=List[Event], +) +async def get_agent_messages( + session_id: str, + api_key: str = Security(get_api_key), +): + return get_session_events(session_service, session_id) + + +@router.delete( + "/sessions/{session_id}", + status_code=status.HTTP_204_NO_CONTENT, +) +def remove_session( + session_id: str, + api_key: str = Security(get_api_key), +): + return delete_session(session_service, session_id) + # Rotas para Clientes @router.post("/clients/", response_model=Client, status_code=status.HTTP_201_CREATED) def create_client( - client: ClientCreate, + client: ClientCreate, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): return client_service.create_client(db, client) + @router.get("/clients/", response_model=List[Client]) def read_clients( - skip: int = 0, - limit: int = 100, + skip: int = 0, + limit: int = 100, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): return client_service.get_clients(db, skip, limit) + @router.get("/clients/{client_id}", response_model=Client) def read_client( - client_id: uuid.UUID, + client_id: uuid.UUID, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): db_client = client_service.get_client(db, client_id) if db_client is None: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cliente não encontrado") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Cliente não encontrado" + ) return db_client + @router.put("/clients/{client_id}", response_model=Client) def update_client( - client_id: uuid.UUID, - client: ClientCreate, + client_id: uuid.UUID, + client: ClientCreate, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): db_client = client_service.update_client(db, client_id, client) if db_client is None: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cliente não encontrado") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Cliente não encontrado" + ) return db_client + @router.delete("/clients/{client_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_client( - client_id: uuid.UUID, + client_id: uuid.UUID, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): if not client_service.delete_client(db, client_id): - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cliente não encontrado") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Cliente não encontrado" + ) + # Rotas para Contatos @router.post("/contacts/", response_model=Contact, status_code=status.HTTP_201_CREATED) def create_contact( - contact: ContactCreate, + contact: ContactCreate, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): return contact_service.create_contact(db, contact) + @router.get("/contacts/{client_id}", response_model=List[Contact]) def read_contacts( - client_id: uuid.UUID, - skip: int = 0, - limit: int = 100, + client_id: uuid.UUID, + skip: int = 0, + limit: int = 100, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): return contact_service.get_contacts_by_client(db, client_id, skip, limit) + @router.get("/contact/{contact_id}", response_model=Contact) def read_contact( - contact_id: uuid.UUID, + contact_id: uuid.UUID, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): db_contact = contact_service.get_contact(db, contact_id) if db_contact is None: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Contato não encontrado") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Contato não encontrado" + ) return db_contact + @router.put("/contact/{contact_id}", response_model=Contact) def update_contact( - contact_id: uuid.UUID, - contact: ContactCreate, + contact_id: uuid.UUID, + contact: ContactCreate, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): 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="Contato não encontrado") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Contato não encontrado" + ) return db_contact + @router.delete("/contact/{contact_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_contact( - contact_id: uuid.UUID, + contact_id: uuid.UUID, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): if not contact_service.delete_contact(db, contact_id): - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Contato não encontrado") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Contato não encontrado" + ) + # Rotas para Agentes @router.post("/agents/", response_model=Agent, status_code=status.HTTP_201_CREATED) def create_agent( - agent: AgentCreate, + agent: AgentCreate, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): return agent_service.create_agent(db, agent) + @router.get("/agents/{client_id}", response_model=List[Agent]) def read_agents( - client_id: uuid.UUID, - skip: int = 0, - limit: int = 100, + client_id: uuid.UUID, + skip: int = 0, + limit: int = 100, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): return agent_service.get_agents_by_client(db, client_id, skip, limit) + @router.get("/agent/{agent_id}", response_model=Agent) def read_agent( - agent_id: uuid.UUID, + agent_id: uuid.UUID, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): db_agent = agent_service.get_agent(db, agent_id) if db_agent is None: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Agente não encontrado") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Agente não encontrado" + ) return db_agent + @router.put("/agent/{agent_id}", response_model=Agent) async def update_agent( - agent_id: uuid.UUID, - agent_data: Dict[str, Any], - db: Session = Depends(get_db) + agent_id: uuid.UUID, agent_data: Dict[str, Any], db: Session = Depends(get_db) ): """Atualiza um agente existente""" return await agent_service.update_agent(db, agent_id, agent_data) + @router.delete("/agent/{agent_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_agent( - agent_id: uuid.UUID, + agent_id: uuid.UUID, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): if not agent_service.delete_agent(db, agent_id): - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Agente não encontrado") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Agente não encontrado" + ) + # Rotas para MCPServers -@router.post("/mcp-servers/", response_model=MCPServer, status_code=status.HTTP_201_CREATED) +@router.post( + "/mcp-servers/", response_model=MCPServer, status_code=status.HTTP_201_CREATED +) def create_mcp_server( - server: MCPServerCreate, + server: MCPServerCreate, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): return mcp_server_service.create_mcp_server(db, server) + @router.get("/mcp-servers/", response_model=List[MCPServer]) def read_mcp_servers( - skip: int = 0, - limit: int = 100, + skip: int = 0, + limit: int = 100, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): return mcp_server_service.get_mcp_servers(db, skip, limit) + @router.get("/mcp-servers/{server_id}", response_model=MCPServer) def read_mcp_server( - server_id: uuid.UUID, + server_id: uuid.UUID, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): db_server = mcp_server_service.get_mcp_server(db, server_id) if db_server is None: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Servidor MCP não encontrado") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Servidor MCP não encontrado" + ) return db_server + @router.put("/mcp-servers/{server_id}", response_model=MCPServer) def update_mcp_server( - server_id: uuid.UUID, - server: MCPServerCreate, + server_id: uuid.UUID, + server: MCPServerCreate, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): db_server = mcp_server_service.update_mcp_server(db, server_id, server) if db_server is None: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Servidor MCP não encontrado") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Servidor MCP não encontrado" + ) return db_server + @router.delete("/mcp-servers/{server_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_mcp_server( - server_id: uuid.UUID, + server_id: uuid.UUID, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): if not mcp_server_service.delete_mcp_server(db, server_id): - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Servidor MCP não encontrado") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Servidor MCP não encontrado" + ) + # Rotas para Tools @router.post("/tools/", response_model=Tool, status_code=status.HTTP_201_CREATED) def create_tool( - tool: ToolCreate, + tool: ToolCreate, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): return tool_service.create_tool(db, tool) + @router.get("/tools/", response_model=List[Tool]) def read_tools( - skip: int = 0, - limit: int = 100, + skip: int = 0, + limit: int = 100, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): return tool_service.get_tools(db, skip, limit) + @router.get("/tools/{tool_id}", response_model=Tool) def read_tool( - tool_id: uuid.UUID, + tool_id: uuid.UUID, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): db_tool = tool_service.get_tool(db, tool_id) if db_tool is None: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Ferramenta não encontrada") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Ferramenta não encontrada" + ) return db_tool + @router.put("/tools/{tool_id}", response_model=Tool) def update_tool( - tool_id: uuid.UUID, - tool: ToolCreate, + tool_id: uuid.UUID, + tool: ToolCreate, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): db_tool = tool_service.update_tool(db, tool_id, tool) if db_tool is None: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Ferramenta não encontrada") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Ferramenta não encontrada" + ) return db_tool + @router.delete("/tools/{tool_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_tool( - tool_id: uuid.UUID, + tool_id: uuid.UUID, db: Session = Depends(get_db), - api_key: str = Security(get_api_key) + api_key: str = Security(get_api_key), ): if not tool_service.delete_tool(db, tool_id): - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Ferramenta não encontrada") \ No newline at end of file + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Ferramenta não encontrada" + ) diff --git a/src/config/settings.py b/src/config/settings.py index 7835b407..9f5e8e57 100644 --- a/src/config/settings.py +++ b/src/config/settings.py @@ -18,14 +18,6 @@ class Settings(BaseSettings): "postgresql://postgres:root@localhost:5432/evo_ai" ) - # Configurações do OpenAI - OPENAI_API_KEY: Optional[str] = os.getenv("OPENAI_API_KEY") - - # Configurações da aplicação - APP_NAME: str = "app" - USER_ID: str = "user_1" - SESSION_ID: str = "session_001" - # Configurações de logging LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO") LOG_DIR: str = "logs" diff --git a/src/models/models.py b/src/models/models.py index ca5ef8a9..6d52387a 100644 --- a/src/models/models.py +++ b/src/models/models.py @@ -79,6 +79,7 @@ class MCPServer(Base): description = Column(Text, nullable=True) config_json = Column(JSON, nullable=False, default={}) environments = Column(JSON, nullable=False, default={}) + tools = Column(JSON, nullable=False, default=[]) type = Column(String, nullable=False, default="official") created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) @@ -96,4 +97,16 @@ class Tool(Base): config_json = Column(JSON, nullable=False, default={}) environments = Column(JSON, nullable=False, default={}) created_at = Column(DateTime(timezone=True), server_default=func.now()) - updated_at = Column(DateTime(timezone=True), onupdate=func.now()) \ No newline at end of file + updated_at = Column(DateTime(timezone=True), onupdate=func.now()) + +class Session(Base): + __tablename__ = "sessions" + # A diretiva abaixo faz com que o Alembic ignore esta tabela nas migrações + __table_args__ = {'extend_existing': True, 'info': {'skip_autogenerate': True}} + + id = Column(String, primary_key=True) + app_name = Column(String) + user_id = Column(String) + state = Column(JSON) + create_time = Column(DateTime(timezone=True)) + update_time = Column(DateTime(timezone=True)) \ No newline at end of file diff --git a/src/schemas/__pycache__/schemas.cpython-310.pyc b/src/schemas/__pycache__/schemas.cpython-310.pyc index 628f0079..d9b02f36 100644 Binary files a/src/schemas/__pycache__/schemas.cpython-310.pyc and b/src/schemas/__pycache__/schemas.cpython-310.pyc differ diff --git a/src/schemas/agent_config.py b/src/schemas/agent_config.py index 0c67c3c6..efbf7a26 100644 --- a/src/schemas/agent_config.py +++ b/src/schemas/agent_config.py @@ -14,6 +14,7 @@ class MCPServerConfig(BaseModel): """Configuração de um servidor MCP""" id: UUID envs: Dict[str, str] = Field(default_factory=dict, description="Variáveis de ambiente do servidor") + tools: List[str] = Field(default_factory=list, description="Lista de ferramentas do servidor") class Config: from_attributes = True diff --git a/src/schemas/schemas.py b/src/schemas/schemas.py index 4fb275d4..df57f7bb 100644 --- a/src/schemas/schemas.py +++ b/src/schemas/schemas.py @@ -109,6 +109,7 @@ class MCPServerBase(BaseModel): description: Optional[str] = None config_json: Dict[str, Any] = Field(default_factory=dict) environments: Dict[str, Any] = Field(default_factory=dict) + tools: List[str] = Field(default_factory=list) type: str = Field(default="official") class MCPServerCreate(MCPServerBase): diff --git a/src/services/__pycache__/agent_runner.cpython-310.pyc b/src/services/__pycache__/agent_runner.cpython-310.pyc index 800abf61..e86b77c3 100644 Binary files a/src/services/__pycache__/agent_runner.cpython-310.pyc and b/src/services/__pycache__/agent_runner.cpython-310.pyc differ diff --git a/src/services/__pycache__/agent_service.cpython-310.pyc b/src/services/__pycache__/agent_service.cpython-310.pyc index 6c7f9a79..c5917287 100644 Binary files a/src/services/__pycache__/agent_service.cpython-310.pyc and b/src/services/__pycache__/agent_service.cpython-310.pyc differ diff --git a/src/services/agent_runner.py b/src/services/agent_runner.py index cc884ba2..3c5a7c86 100644 --- a/src/services/agent_runner.py +++ b/src/services/agent_runner.py @@ -48,7 +48,7 @@ async def run_agent( logger.info("Configurando Runner") agent_runner = Runner( agent=root_agent, - app_name=get_root_agent.name, + app_name=agent_id, session_service=session_service, artifact_service=artifacts_service, memory_service=memory_service, @@ -57,7 +57,7 @@ async def run_agent( logger.info(f"Buscando sessão para contato {contact_id}") session = session_service.get_session( - app_name=root_agent.name, + app_name=agent_id, user_id=contact_id, session_id=session_id, ) @@ -65,7 +65,7 @@ async def run_agent( if session is None: logger.info(f"Criando nova sessão para contato {contact_id}") session = session_service.create_session( - app_name=root_agent.name, + app_name=agent_id, user_id=contact_id, session_id=session_id, ) @@ -85,7 +85,7 @@ async def run_agent( logger.info(f"Resposta final recebida: {final_response_text}") completed_session = session_service.get_session( - app_name=root_agent.name, + app_name=agent_id, user_id=contact_id, session_id=session_id, ) diff --git a/src/services/agent_service.py b/src/services/agent_service.py index 78cf203f..e3b35e22 100644 --- a/src/services/agent_service.py +++ b/src/services/agent_service.py @@ -66,29 +66,29 @@ def create_agent(db: Session, agent: AgentCreate) -> Agent: """Cria um novo agente""" try: # Validação adicional de sub-agentes - if agent.type != 'llm': + if agent.type != "llm": if not isinstance(agent.config, dict): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail="Configuração inválida: deve ser um objeto com sub_agents" + detail="Configuração inválida: deve ser um objeto com sub_agents", ) - - if 'sub_agents' not in agent.config: + + if "sub_agents" not in agent.config: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail="Configuração inválida: sub_agents é obrigatório para agentes do tipo sequential, parallel ou loop" + detail="Configuração inválida: sub_agents é obrigatório para agentes do tipo sequential, parallel ou loop", ) - - if not agent.config['sub_agents']: + + if not agent.config["sub_agents"]: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail="Configuração inválida: sub_agents não pode estar vazio" + detail="Configuração inválida: sub_agents não pode estar vazio", ) - - if not validate_sub_agents(db, agent.config['sub_agents']): + + if not validate_sub_agents(db, agent.config["sub_agents"]): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail="Um ou mais sub-agentes não existem" + detail="Um ou mais sub-agentes não existem", ) # Processa a configuração antes de criar o agente @@ -114,9 +114,13 @@ def create_agent(db: Session, agent: AgentCreate) -> Agent: detail=f"Variável de ambiente '{env_key}' não fornecida para o servidor MCP {mcp_server.name}", ) - # Adiciona o servidor processado + # Adiciona o servidor processado com suas ferramentas processed_servers.append( - {"id": str(server["id"]), "envs": server["envs"]} + { + "id": str(server["id"]), + "envs": server["envs"], + "tools": server["tools"], + } ) config["mcp_servers"] = processed_servers @@ -147,7 +151,7 @@ def create_agent(db: Session, agent: AgentCreate) -> Agent: logger.error(f"Erro ao criar agente: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Erro ao criar agente" + detail="Erro ao criar agente", ) @@ -186,7 +190,11 @@ async def update_agent( # Adiciona o servidor processado processed_servers.append( - {"id": str(server["id"]), "envs": server["envs"]} + { + "id": str(server["id"]), + "envs": server["envs"], + "tools": server["tools"], + } ) config["mcp_servers"] = processed_servers diff --git a/src/services/mcp_service.py b/src/services/mcp_service.py index 5fe35721..407834e8 100644 --- a/src/services/mcp_service.py +++ b/src/services/mcp_service.py @@ -74,6 +74,15 @@ class MCPService: logger.warning(f"Removidas {removed_count} ferramentas incompatíveis.") return filtered_tools + + def _filter_tools_by_agent(self, tools: List[Any], agent_tools: List[str]) -> List[Any]: + """Filtra ferramentas compatíveis com o agente.""" + filtered_tools = [] + for tool in tools: + logger.info(f"Ferramenta: {tool.name}") + if tool.name in agent_tools: + filtered_tools.append(tool) + return filtered_tools async def build_tools(self, mcp_config: Dict[str, Any], db: Session) -> Tuple[List[Any], AsyncExitStack]: """Constrói uma lista de ferramentas a partir de múltiplos servidores MCP.""" @@ -109,6 +118,10 @@ class MCPService: if tools and exit_stack: # Filtra ferramentas incompatíveis filtered_tools = self._filter_incompatible_tools(tools) + + # Filtra ferramentas compatíveis com o agente + agent_tools = server.get('tools', []) + filtered_tools = self._filter_tools_by_agent(filtered_tools, agent_tools) self.tools.extend(filtered_tools) # Registra o exit_stack com o AsyncExitStack diff --git a/src/services/session_service.py b/src/services/session_service.py new file mode 100644 index 00000000..25fa6b2c --- /dev/null +++ b/src/services/session_service.py @@ -0,0 +1,148 @@ +from google.adk.sessions import DatabaseSessionService +from sqlalchemy.orm import Session +from src.models.models import Session as SessionModel +from google.adk.events import Event +from google.adk.sessions import Session as SessionADK +from typing import Optional, List +from fastapi import HTTPException, status +from sqlalchemy.exc import SQLAlchemyError + +from src.services.agent_service import get_agent, get_agents_by_client +from src.services.contact_service import get_contact + +import uuid +import logging + +logger = logging.getLogger(__name__) + + +def get_sessions_by_client( + db: Session, + client_id: uuid.UUID, +) -> List[SessionModel]: + """Busca sessões de um cliente com paginação""" + try: + agents_by_client = get_agents_by_client(db, client_id) + sessions = [] + for agent in agents_by_client: + sessions.extend(get_sessions_by_agent(db, agent.id)) + + return sessions + except SQLAlchemyError as e: + logger.error(f"Erro ao buscar sessões do cliente {client_id}: {str(e)}") + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Erro ao buscar sessões", + ) + + +def get_sessions_by_agent( + db: Session, + agent_id: uuid.UUID, + skip: int = 0, + limit: int = 100, +) -> List[SessionModel]: + """Busca sessões de um agente com paginação""" + try: + agent_id_str = str(agent_id) + query = db.query(SessionModel).filter(SessionModel.app_name == agent_id_str) + + return query.offset(skip).limit(limit).all() + except SQLAlchemyError as e: + logger.error(f"Erro ao buscar sessões do agente {agent_id_str}: {str(e)}") + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Erro ao buscar sessões", + ) + + +def get_session_by_id( + session_service: DatabaseSessionService, session_id: str +) -> Optional[SessionADK]: + """Busca uma sessão pelo ID""" + try: + if not session_id or "_" not in session_id: + logger.error(f"ID de sessão inválido: {session_id}") + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="ID de sessão inválido. Formato esperado: app_name_user_id", + ) + + parts = session_id.split("_", 1) + if len(parts) != 2: + logger.error(f"Formato de ID de sessão inválido: {session_id}") + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Formato de ID de sessão inválido. Formato esperado: app_name_user_id", + ) + + user_id, app_name = parts + + session = session_service.get_session( + app_name=app_name, + user_id=user_id, + session_id=session_id, + ) + + if session is None: + logger.error(f"Sessão não encontrada: {session_id}") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Sessão não encontrada: {session_id}", + ) + + return session + except Exception as e: + logger.error(f"Erro ao buscar sessão {session_id}: {str(e)}") + if isinstance(e, HTTPException): + raise e + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Erro ao buscar sessão: {str(e)}", + ) + + +def delete_session(session_service: DatabaseSessionService, session_id: str) -> None: + """Deleta uma sessão pelo ID""" + try: + session = get_session_by_id(session_service, session_id) + # Se chegou aqui, a sessão existe (get_session_by_id já valida) + + session_service.delete_session( + app_name=session.app_name, + user_id=session.user_id, + session_id=session_id, + ) + return None + except HTTPException: + # Repassa exceções HTTP do get_session_by_id + raise + except Exception as e: + logger.error(f"Erro ao deletar sessão {session_id}: {str(e)}") + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Erro ao deletar sessão: {str(e)}", + ) + + +def get_session_events( + session_service: DatabaseSessionService, session_id: str +) -> List[Event]: + """Busca os eventos de uma sessão pelo ID""" + try: + session = get_session_by_id(session_service, session_id) + # Se chegou aqui, a sessão existe (get_session_by_id já valida) + + if not hasattr(session, 'events') or session.events is None: + return [] + + return session.events + except HTTPException: + # Repassa exceções HTTP do get_session_by_id + raise + except Exception as e: + logger.error(f"Erro ao buscar eventos da sessão {session_id}: {str(e)}") + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Erro ao buscar eventos da sessão: {str(e)}", + )