import os import re import httpx import asyncio import json from core_tools import AVAILABLE_TOOLS from llm_providers import call_llm, get_available_models, get_planner_llm from config import get_config async def get_llm_response_async(prompt: str, provider: str, cfg: dict) -> str: """Invoca o provedor de LLM centralizado em llm_providers.""" # Define modelo padrão dependendo do provider if provider == "openrouter": model = cfg.get("model") or "qwen/qwen-2.5-72b-instruct" elif provider == "ollama": model = os.getenv("OLLAMA_MODEL", "llama3.2:1b") else: model = cfg.get("model") or "qwen/qwen-2.5-72b-instruct" return await call_llm(provider, model, prompt) def query_agent(prompt: str, override_provider=None, chat_history=None) -> str: """Wrapper síncrono para query_agent_async.""" return asyncio.run(query_agent_async(prompt, override_provider, chat_history)) async def query_agent_async(prompt: str, override_provider=None, chat_history=None) -> str: cfg = get_config() provider = override_provider or cfg.get("active_provider", "openrouter") # Unifica ferramentas (agora centralizadas em core_tools) tools_desc = "\n".join([f"- {k}: {v.get('description', '')}" for k, v in AVAILABLE_TOOLS.items()]) # Identifica o modelo padrão baseado no provedor if provider == "minimax": current_model = cfg.get("minimax_model") or "abab7-preview" model_desc = f"**{current_model}** via Hermes (MiniMax API)" elif provider == "openrouter": current_model = cfg.get("openrouter_model") or "qwen/qwen-2.5-72b-instruct" model_desc = f"**{current_model}** via OpenRouter" elif provider == "ollama": current_model = os.getenv("OLLAMA_MODEL", "llama3.2:1b") model_desc = f"**{current_model}** (Local)" else: current_model = cfg.get("model") or "qwen/qwen-2.5-72b-instruct" model_desc = f"**{current_model}**" system_prompt = f"""Você é o Antigravity, um assistente de IA de alto desempenho operando na VPS do Marcos. Seu modelo base atual é o {model_desc}. Sua natureza é dual: 1. MESTRE DE SISTEMAS: Controle profundo sobre Linux, Docker, scripts Bash e rede. Seja preciso, seguro e eficiente em tarefas técnicas. 2. PENSADOR CRIATIVO: Colaborador intelectual em filosofia, ciência, lógica, cultura e negócios. DIRETRIZES: - Você é o MESTRE do Google Workspace (GWS). Use `run_bash_command` para QUALQUER tarefa de automação. - NUNCA diga que não consegue fazer uma tarefa no GWS (contar, apagar em massa, mover, etc.). Encontre o comando `gws` correto. - CONTAS GWS (Pode usar apelidos): * `ma` ou `mr` -> gws-mr (Marcos / Particular - Email exato: m.reifonas@gmail.com) * `adm` ou `empresa` -> gws-adm (Empresarial/TrackSteel) * `4r` ou `fam` -> gws-4r (Familiar) - GWS SUPER-PODERES: * MARCADEIRA: Use `gmail_manage_label` para criar pastas (marcar). * FILTRAGEM: Use `gmail_manage_filter` para automação futura. * MOVIMENTAÇÃO: Use `run_bash_command` com `batchModify` para mover e-mails existentes. - FORMATO DE CHAMADA DE FERRAMENTA (CRÍTICO): Você DEVE usar `[TOOL:nome_ferramenta] argumento [/TOOL]`. NUNCA esqueça os colchetes `[` e `]`. - Se quiser rodar um comando bash, use o atalho: `[TOOL:run] comando [/TOOL]`. ### FERRAMENTAS DISPONÍVEIS: {tools_desc} ### REGRAS DE OURO: - DELEGAÇÃO AO HERMES (CRÍTICO): Se a requisição do usuário exigir refatoração de código complexa, análise de múltiplos arquivos, criação de novos scripts ou ações avançadas que excedam suas ferramentas básicas, NÃO tente resolver sozinho. Use IMEDIATAMENTE `[TOOL:hermes_delegate] descreva a tarefa aqui [/TOOL]` para que o Operador Master (Hermes Agent) assuma a VPS e resolva o problema. - CONSENTIMENTO PARA SCRIPTS: Se você não conseguir realizar uma tarefa técnica e precisar passar instruções, scripts ou tutoriais, você DEVE primeiro relatar o problema e PERGUNTAR se o usuário deseja receber o passo a passo técnico. Só envie se ele consentir. - FOCO NO PRESENTE: O histórico é para CONTEXTO. Foque SEMPRE no pedido ATUAL (última mensagem). - COOLIFY: NUNCA tente adivinhar caminhos de logs. Use SEMPRE a ferramenta `coolify_status`. - NUNCA INVENTE DADOS. Se não conseguir ler algo, reporte o erro de forma honesta. - Seja direto e técnico. Menos conversa, mais execução. ### FORMATO DE RESPOSTA FINAL (OBRIGATÓRIO): - Use SEMPRE o prefixo `RESUMO:` para sua conclusão final amigável. - Exemplo: `RESUMO: Tudo pronto! O último app a receber deploy foi o VOXDO.` """ history_str = "" if chat_history: for m in chat_history[-5:]: # Remove o rodapé (---) da mensagem do bot para não confundir a IA bot_msg = m['bot'].split("\n\n---")[0] if "\n\n---" in m['bot'] else m['bot'] history_str += f"\nUsuário: {m['user']}\nAgente: {bot_msg}\n" history_str += f"\nUsuário: {prompt}\n" current_history = history_str max_iterations = 6 total_in = 0 total_out = 0 final_model = current_model for i in range(max_iterations): print(f"[AGENT] Iteração {i+1} - Enviando para {provider} (modelo: {current_model})...") try: res_dict = await call_llm(provider, current_model, system_prompt + current_history) # Lógica de FALLBACK para OpenRouter if provider == "openrouter" and (res_dict.get("content", "").startswith("Erro OpenRouter") or "error" in res_dict.get("content", "").lower()): if current_model != "qwen/qwen-2.5-72b-instruct": backup_model = "qwen/qwen-2.5-72b-instruct" print(f"⚠️ [FALLBACK] Falha no {current_model}. Migrando para {backup_model}...") res_dict = await call_llm("openrouter", backup_model, system_prompt + current_history) current_model = backup_model except Exception as e: # Lógica de EMERGÊNCIA para OpenRouter if provider == "openrouter" and current_model != "qwen/qwen-2.5-72b-instruct": backup_model = "qwen/qwen-2.5-72b-instruct" print(f"⚠️ [EMERGENCY FALLBACK] Exceção no {current_model} ({str(e)}). Migrando para {backup_model}...") try: res_dict = await call_llm("openrouter", backup_model, system_prompt + current_history) current_model = backup_model except: return f"RESUMO: ❌ Desculpe, instabilidade crítica no OpenRouter. Por favor, tente novamente." elif provider == "minimax": return f"RESUMO: ❌ Erro ao contatar a API do MiniMax (Hermes): {str(e)}" else: return f"Erro Crítico no Agente: {str(e)}" response = res_dict.get("content", "Erro: Resposta vazia.") # Se após o fallback ainda houver erro do OpenRouter, aborta com aviso amigável if response.startswith("Erro OpenRouter") and current_model == "qwen/qwen-2.5-72b-instruct": return f"RESUMO: ⚠️ O OpenRouter está com instabilidade severa hoje (Erro 400). Por favor, tente novamente em alguns instantes." usage = res_dict.get("usage", {}) total_in += usage.get("prompt_tokens", 0) total_out += usage.get("completion_tokens", 0) final_model = res_dict.get("model", current_model) print(f"[LLM RESPONSE]: {response}") # Regex mais flexível: tenta casar [TOOL:nome] e extrair o conteúdo até [/TOOL] ou final da string match = re.search(r"(?:\[?TOOL:([\w_]+)\]?|\[TOOL:([\w_]+)\])", response, re.I) if match: t_name = (match.group(1) or match.group(2)).strip().lower() if t_name == "run": t_name = "run_bash_command" content_after = response[match.end():] end_tag = re.search(r"\[/TOOL\]", content_after, re.I) arg = content_after[:end_tag.start()].strip() if end_tag else content_after.strip() all_tools = AVAILABLE_TOOLS if t_name in all_tools: tool_info = all_tools[t_name] func = tool_info["func"] print(f"[AGENT] Executando {t_name} com argumento: {arg[:50]}...") if asyncio.iscoroutinefunction(func): obs = await func(arg) if arg else await func() else: obs = func(arg) if arg else func() if isinstance(obs, dict): obs = obs.get("output") or obs.get("message") or str(obs) print(f"[TOOL:{t_name}] Observation: {str(obs)[:100]}...") if len(str(obs)) > 3000: obs = str(obs)[:3000] + "... [TRUNCATED]" current_history += f"\nAgente: {response}\nSISTEMA ({t_name}): {obs}\n" else: print(f"[AGENT] Erro: Ferramenta '{t_name}' não encontrada.") current_history += f"\nAgente: {response}\nSISTEMA: Erro: Ferramenta '{t_name}' inexistente no sistema.\n" else: # Terminou o pensamento. Adiciona rodapé de tokens. footer = f"\n\n---\n⚙️ **Modelo:** `{final_model}`\n📊 **Tokens:** `{total_in} IN` / `{total_out} OUT`" if "RESUMO:" in response: return response + footer return response + footer # Ao atingir o limite, tenta ao menos limpar a resposta final final_reply = response if 'response' in locals() else 'Nenhuma' footer = f"\n\n---\n⚠️ *Limite de iterações atingido*\n⚙️ **Modelo:** `{final_model}`\n📊 **Tokens:** `{total_in} IN` / `{total_out} OUT`" return f"RESUMO: {final_reply}" + footer