167 lines
8.5 KiB
Python
167 lines
8.5 KiB
Python
import os
|
|
import re
|
|
import httpx
|
|
import asyncio
|
|
import json
|
|
from tools import AVAILABLE_TOOLS as TOOLS_LEGACY
|
|
from tools_v2 import TOOLS_V2 as TOOLS_NEW
|
|
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 legadas e novas
|
|
ALL_TOOLS = {**TOOLS_LEGACY, **TOOLS_NEW}
|
|
tools_desc = "\n".join([f"- {k}: {v.get('description') or v.get('desc')}" for k, v in ALL_TOOLS.items()])
|
|
|
|
# Identifica o modelo para o prompt do sistema
|
|
current_model = cfg.get("model") or "deepseek/deepseek-chat:free"
|
|
|
|
system_prompt = f"""Você é o Antigravity, um assistente de IA de alto desempenho operando na VPS do Marcos.
|
|
Seu modelo base atual é o **{current_model}** via OpenRouter.
|
|
|
|
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:
|
|
- 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:]:
|
|
history_str += f"\nUsuário: {m['user']}\nAgente: {m['bot']}\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 padrão: {current_model})...")
|
|
try:
|
|
res_dict = await call_llm(provider, current_model, system_prompt + current_history)
|
|
|
|
# Lógica de FALLBACK: Se o Llama falhar, tenta o Qwen
|
|
if (res_dict.get("content", "").startswith("Erro OpenRouter") or "error" in res_dict.get("content", "").lower()) and provider == "openrouter":
|
|
backup_model = "qwen/qwen-2.5-72b-instruct"
|
|
print(f"⚠️ [FALLBACK] Falha no DeepSeek. Tentando {backup_model}...")
|
|
res_dict = await call_llm("openrouter", backup_model, system_prompt + current_history)
|
|
|
|
except Exception as e:
|
|
# Lógica de EMERGÊNCIA: Se houver exceção no Llama, tenta o Qwen
|
|
print(f"⚠️ [EMERGENCY FALLBACK] Exceção no DeepSeek ({str(e)}). Tentando {backup_model}...")
|
|
try:
|
|
backup_model = "qwen/qwen-2.5-72b-instruct"
|
|
res_dict = await call_llm("openrouter", backup_model, system_prompt + current_history)
|
|
except:
|
|
return f"RESUMO: ❌ Desculpe, os modelos DeepSeek e Qwen estão instáveis no OpenRouter no momento. Por favor, tente novamente em instantes."
|
|
|
|
response = res_dict.get("content", "Erro: Resposta vazia.")
|
|
|
|
# Limpeza de segurança: Se a resposta for um erro técnico persistente
|
|
if response.startswith("Erro OpenRouter"):
|
|
return f"RESUMO: ⚠️ Instabilidade no OpenRouter (DeepSeek/Qwen):\n`{response[:200]}...`\n\nPor favor, tente reenviar sua mensagem."
|
|
|
|
# Limpeza de segurança: Se a resposta for um erro técnico persistente
|
|
if response.startswith("Erro OpenRouter"):
|
|
return f"RESUMO: ⚠️ Instabilidade no OpenRouter (DeepSeek/Qwen):\n`{response[:200]}...`\n\nPor favor, tente reenviar sua mensagem."
|
|
|
|
usage = res_dict.get("usage", {})
|
|
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", final_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 = {**TOOLS_LEGACY, **TOOLS_NEW}
|
|
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
|