From dbdb72ce0ee26a78b8e8c450a298f9771a4dea4d Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 28 Apr 2025 13:54:44 -0300 Subject: [PATCH] =?UTF-8?q?Adiciona=20campo=20de=20ferramentas=20aos=20ser?= =?UTF-8?q?vidores=20MCP=20e=20implementa=20rotas=20para=20gerenciamento?= =?UTF-8?q?=20de=20sess=C3=B5es=20e=20ferramentas=20na=20API.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...2b95d0ea_add_tools_field_to_mcp_servers.py | 32 ++ src/api/__pycache__/routes.cpython-310.pyc | Bin 8234 -> 9605 bytes src/api/routes.py | 317 ++++++++++++------ src/config/settings.py | 8 - src/models/models.py | 15 +- .../__pycache__/schemas.cpython-310.pyc | Bin 6408 -> 6454 bytes src/schemas/agent_config.py | 1 + src/schemas/schemas.py | 1 + .../__pycache__/agent_runner.cpython-310.pyc | Bin 3072 -> 3064 bytes .../__pycache__/agent_service.cpython-310.pyc | Bin 5903 -> 5939 bytes src/services/agent_runner.py | 8 +- src/services/agent_service.py | 38 ++- src/services/mcp_service.py | 13 + src/services/session_service.py | 148 ++++++++ 14 files changed, 457 insertions(+), 124 deletions(-) create mode 100644 migrations/versions/2d612b95d0ea_add_tools_field_to_mcp_servers.py create mode 100644 src/services/session_service.py 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 fac5aa95045e36eb8c5ce324194968e57bee59e8..e5d23638fa570e00c3bc52f5c60e4f8311c8c240 100644 GIT binary patch literal 9605 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 literal 8234 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 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 628f007928ef71b892abc4a30b15abcb51ff42aa..d9b02f369dda1b977156fda9502a552e379f081c 100644 GIT binary patch delta 392 zcmeA$+GfO?&&$ij00hq#@@IH&-sB*at{2Uhn| z^sZzQP@)cqiyi_@% delta 324 zcmdmH)M3P%&&$ij00d_9_%kFn@;((}%$Y1H7AcX+p2D8O(aRXco64NZlEP`jkirzq zpvg74O4xex9$_g)zRB~%oES|fUl5m&4g(sH4aCJfK!S^bgHeD{3JAp*i^3)w3W`kL zAkI0tTEvJy8Yo=^!tx*@8Ym|-`G&|Q#;VEPqT!OUK*3TV{tR*u7Xud~$Q&W2qFAsx zak0CSi9m^3ATEZefY>9)Sd<92MN3?lKMAO!8HgdaBmw1Q8Dl25h-V0NgGALqgc^v5 z138{Q8OUq{aT7p9@?>QRSH}3snG#8&VCRBdRRprQ2xKl${(*!qC%XW%0EYmZ2r~fU CkU!1< 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 800abf619f63ad40d0eda05527a481daf9b2a180..e86b77c39214ce0bc1f5f9395fd31abdda31d7df 100644 GIT binary patch delta 233 zcmZpW_#w`l&&$ij00io5_%l2<@}6emP+?+VXk@IqHTeNkKO@8BB4%Aph8o5io)q3* z=33qw-V}yXhN7wxh6Ri%d^L=-8RjxAWMpKhVb0>JVVHb|S(njv@-Jp-(+xEOS^Py{ zGD`qPr-(>0WC_;tgLRA6@WXT$P66uY1nT!;krX{q!?i$oA%iDF3WEm&BSRi{4Oh|S z$yF@sjGrejV$o+5+~<{HKt zo)q3*=33qw-V}yXhN9ZZ8<<6zfijbiG3#pE)o^F=g4ERTr3m*jyD-Gc)C$xHEZ|$n zP_(&5Ad4SDW(gE+o-D^AZ$7<-Ig7If*<8UI)&)W}f(scL8NwNAxQfoya4isC$l%G4 z!r;Nc$dJbk6uUmTh((?8`{Y?H`i!ERPq8$z$g?mqfg#&(9(DmH7Dfo4?JozL0Fccx PIg3-7k!5oqXBHCx;=4P$ diff --git a/src/services/__pycache__/agent_service.cpython-310.pyc b/src/services/__pycache__/agent_service.cpython-310.pyc index 6c7f9a79dac51513fbf70c8f48cdd98e3c28ac64..c591728735782736d5e901512188a5f48c0ab294 100644 GIT binary patch delta 577 zcmeCz+pNcz&&$ij00b;c`7<2}yw!P}NGNA{!ueiv^^=NF1aOOh|xOwm_m|vn$VXM#jF$Z+Sy`xR}@& z*%(>=vi;}anC!t9%`pS$*+#~y^^?~!OHMw*Cm}x#;)7b26xJFRpw~-)UI(&LSfO73 z%~umt!BA}<+#ZnhLX$S4}i V#K`oY1&FzTn2Cw&pM-~uFaXa)iLw9y delta 557 zcmdn2*RRKy&&$ij00im__%jq$Hu5F2vwdP@V5qu2xtzUGd}|6v4O1CI(Uf$CTIL$2 z6b?xS7lv5p$^0BOJ}LZjm=-cJG8FpMfTe60Qdq!>7pDlM2(~Z)m9v19H#34*Kv^NM z?4}f9sH_lNR%r5Fj$p2XBZLCvd4pLzWB!H@|fz&M)kb)v{kT95#0I_U;^11A6xJFRpq~qYeg?8qSfPFv<_G$j2kK|d z8n6`5SG-`w4Jmvn{6Igmr0_z01!Mtb1;DcXDS}X00l2KdmZaY~@P?f334j4`%lLG{W<-l4TacDU;xl!;F+bp1L zal+(EA!BG*Y~CTXi;?l( 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)}", + )