from typing import Dict, Any, List, Optional import vertexai from google.api_core.exceptions import NotFound from vertexai.generative_models import GenerativeModel, Tool, FunctionDeclaration from app.core.settings import settings from app.models.tool_model import ToolDefinition class LLMService: def __init__(self): vertexai.init( project=settings.google_project_id, location=settings.google_location ) configured = settings.vertex_model_name.strip() fallback_models = ["gemini-2.5-flash", "gemini-2.0-flash-001", "gemini-1.5-pro"] self.model_names = [configured] + [m for m in fallback_models if m != configured] def build_vertex_tools(self, tools: List[ToolDefinition]) -> Optional[List[Tool]]: # Vertex espera uma lista de Tool, mas com function_declarations agrupadas em um único Tool # para uso de múltiplas funções no mesmo request. if not tools: return None function_declarations = [ FunctionDeclaration( name=tool.name, description=tool.description, parameters=tool.parameters, ) for tool in tools ] return [Tool(function_declarations=function_declarations)] """ Fluxo principal de geração de resposta. Parâmetros: - message: mensagem do usuário - tools: lista de ferramentas disponíveis - history: histórico da conversa (memória) """ async def generate_response( self, message: str, tools: List[ToolDefinition], history: List[Dict[str, Any]] = None ) -> Dict[str, Any]: vertex_tools = self.build_vertex_tools(tools) # Convertendo tools para formato do Vertex # Inicia uma sessão de chat com: # - histórico (se existir) # - ferramentas disponíveis response = None last_error = None for model_name in self.model_names: try: model = GenerativeModel(model_name) chat = model.start_chat(history=history or []) send_kwargs = {"tools": vertex_tools} if vertex_tools else {} response = chat.send_message(message, **send_kwargs) break except NotFound as err: last_error = err continue if response is None: if last_error: raise RuntimeError( f"Nenhum modelo Vertex disponível. Verifique VERTEX_MODEL_NAME e acesso no projeto. Erro: {last_error}" ) from last_error raise RuntimeError("Falha ao gerar resposta no Vertex AI.") # Pegamos a primeira resposta candidata do modelo (a com maior coerência com o assunto) # Estrutura interna: # response.candidates -> lista de possíveis respostas # content.parts -> partes da resposta part = response.candidates[0].content.parts[0] # Verificação se o modelo decidiu chamar alguma função, se decidiu, retornará o nome da função que ele quer executar e o argumento que ele extraiu da mensagem do usuário. if part.function_call: return { "response": None, "tool_call": { "name": part.function_call.name, "arguments": dict(part.function_call.args) } } # Caso não ocorra a chamada de uma função, significa que o modelo respondeu diretamente em texto return { "response": response.text, "tool_call": None }