refatoracao

This commit is contained in:
2026-03-23 23:38:56 +00:00
parent 8002262cf7
commit b7e6239216
16 changed files with 2290 additions and 4321 deletions

View File

@@ -1,144 +1,78 @@
import os
import re
import requests
import httpx
import asyncio
import json
from tools import AVAILABLE_TOOLS
from config import get_config
def get_llm_response(prompt: str, provider: str, cfg: dict) -> str:
"""Invoca o provedor de LLM configurado."""
if provider == "gemini":
api_key = cfg.get("gemini_api_key") or os.getenv("GEMINI_API_KEY")
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={api_key}"
payload = {"contents": [{"parts": [{"text": prompt}]}]}
try:
res = requests.post(url, json=payload, timeout=30)
if res.status_code == 200:
data = res.json()
if "candidates" in data and len(data["candidates"]) > 0:
return data["candidates"][0]["content"]["parts"][0]["text"]
return f"Erro Gemini (Dados Vazios): {res.text}"
return f"Erro Gemini (Status {res.status_code}): {res.text}"
except Exception as e:
return f"Erro de Conexão Gemini: {str(e)}"
elif provider == "ollama":
ollama_host = os.getenv("OLLAMA_HOST", "http://ollama:11434")
model = os.getenv("OLLAMA_MODEL", "llama3.2:1b")
try:
res = requests.post(f"{ollama_host}/api/generate", json={
"model": model,
"prompt": prompt,
"stream": False,
"options": {"num_ctx": 4096}
}, timeout=180)
if res.status_code == 200:
async def get_llm_response_async(prompt: str, provider: str, cfg: dict) -> str:
"""Invoca o provedor de LLM configurado (async)."""
async with httpx.AsyncClient(timeout=60) as client:
if provider == "gemini":
api_key = cfg.get("gemini_api_key") or os.getenv("GEMINI_API_KEY")
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={api_key}"
payload = {"contents": [{"parts": [{"text": prompt}]}]}
try:
res = await client.post(url, json=payload)
if res.status_code == 200:
data = res.json()
return data["candidates"][0]["content"]["parts"][0]["text"]
return f"Erro Gemini: {res.text}"
except Exception as e: return f"Erro Gemini: {e}"
elif provider == "ollama":
host = os.getenv("OLLAMA_HOST", "http://ollama:11434")
model = os.getenv("OLLAMA_MODEL", "llama3.2:1b")
try:
res = await client.post(f"{host}/api/generate", json={
"model": model, "prompt": prompt, "stream": False
})
return res.json().get("response", "")
return f"Erro Ollama (Status {res.status_code}): {res.text}"
except Exception as e:
return f"Erro de Conexão Ollama: {str(e)}"
except Exception as e: return f"Erro Ollama: {e}"
return "Provedor desconhecido."
def query_agent(prompt: str, override_provider: str = None, chat_history: list = None) -> str:
"""
Motor Agente em Loop (ReAct): Pensamento -> Ação -> Observação -> Resposta Final.
"""
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", "gemini")
tools_desc = "\n".join([f"- {k}: {v['description']}" for k, v in AVAILABLE_TOOLS.items()])
# Contexto de Ferramentas para a IA
tools_desc = "\n".join([f"- {k}: {v['description']}" for k,v in AVAILABLE_TOOLS.items()])
# Prompt especializado reformulado para evitar alucinações
system_prompt_base = """Você é o [Antigravity VPS Agent], o SysAdmin de elite do Marcos.
Você tem acesso root completo à VPS e deve agir de forma profissional e precisa.
system_prompt = f"""Você é o Antigravity VPS Agent. Root Admin da VPS do Marcos.
Responda em PORTUGUÊS. Seja técnico e direto.
### REGRAS DE OURO:
1. Responda em PORTUGUÊS (Brasil).
2. Se o usuário pedir o status da VPS, SEMPRE use a ferramenta 'get_system_health'.
3. Se o usuário pedir algo sobre containers, use 'get_docker_stats'.
4. Antes de decidir que um arquivo não existe, use 'run_bash_command' com 'ls' para verificar o diretório.
5. NUCA invente que buscou por arquivos (como syslog.conf) se o usuário não pediu especificamente por eles.
6. A seção <REFINED> deve conter apenas as informações solicitadas. Se não houver imagem relevante, não inclua tags de imagem.
7. O disco da VPS está montado em `/host_root`. Os arquivos do Marcos ficam principalmente em `/host_root/root/VPS_Sync`. Use este caminho como ponto de partida se o `find` na raiz falhar ou demorar demais.
### FERRAMENTAS:
{tools_desc}
### FORMATO DE AÇÃO:
Use: [TOOL:nome_da_ferramenta] argumento [/TOOL]
Rode UMA ferramenta por vez. Aguarde a saída do SISTEMA antes de concluir.
### RESPOSTA FINAL:
Sua resposta terminada deve ter:
- Um resumo técnico.
- Uma seção <REFINED> ... </REFINED> com Markdown limpo.
- **DICA**: Só use imagens em <REFINED> se o usuário pediu para ver um arquivo de imagem específico que você localizou. Use o caminho absoluto encontrado.
### FERRAMENTAS DISPONÍVEIS:
{TOOLS_LIST}
### EXEMPLO DE SUCESSO:
Usuário: qual o uso de ram agora?
Agente: [TOOL:get_system_health] [/TOOL]
SISTEMA: CPU: 5% | RAM Usada: 20% | Disco Usado: 40%
Resposta: A memória RAM está operando com 20% de uso.
<REFINED>
### 📊 Memória e CPU
- **RAM Utilizada**: 20%
- **CPU**: 5%
</REFINED>
### FORMATO:
Use [TOOL:nome] arg [/TOOL] para ações.
Finalize com <REFINED> resumo </REFINED>.
"""
system_prompt = system_prompt_base.replace("{TOOLS_LIST}", tools_desc)
# Constrói o histórico da conversa (memória de curto prazo)
history_str = ""
if chat_history:
for msg in chat_history[-5:]: # Pega as últimas 5 interações
history_str += f"\nUsuário: {msg['user']}\nAgente: {msg['bot']}\n"
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_iteration_history = history_str
max_loops = 12
print(f"--- INICIANDO AGENTE ({provider}) ---")
for i in range(max_loops):
import time
time.sleep(0.5) # Respiro para a CPU
print(f"\n[LOOP {i+1}/{max_loops}]")
full_prompt = system_prompt + current_iteration_history
response = get_llm_response(full_prompt, provider, cfg)
print(f"PENSAMENTO:\n{response}")
# Procura por chamadas de ferramentas na resposta
match = re.search(r"\[TOOL:(.*?)\](.*?)\[/TOOL\]", response, re.IGNORECASE | re.DOTALL)
current_history = history_str
for i in range(10):
response = await get_llm_response_async(system_prompt + current_history, provider, cfg)
match = re.search(r"\[TOOL:(.*?)\](.*?)\[/TOOL\]", response, re.I | re.S)
if match:
tool_name = match.group(1).strip()
arg = match.group(2).strip()
print(f"EXECUTANDO: {tool_name} | ARGS: {arg}")
if tool_name in AVAILABLE_TOOLS:
func = AVAILABLE_TOOLS[tool_name]["func"]
# Caso a ferramenta não aceite argumentos (ex: get_system_health)
if tool_name in ["get_system_health", "get_docker_stats"]:
observation = func()
else:
observation = func(arg)
print(f"OBSERVAÇÃO (suprimida): {str(observation)[:200]}...")
t_name, arg = match.group(1).strip(), match.group(2).strip()
if t_name in AVAILABLE_TOOLS:
func = AVAILABLE_TOOLS[t_name]["func"]
# Assume ferramentas são síncronas em tools.py (legado)
obs = func(arg) if arg else func()
current_history += f"\nAgente: {response}\nSISTEMA ({t_name}): {obs}\n"
else:
observation = f"Erro: Ferramenta '{tool_name}' não encontrada."
print(f"ERRO: {observation}")
# Adiciona ao histórico do loop atual
current_iteration_history += f"\nAgente (Ação): {response}\nSISTEMA (Saída de {tool_name}): {observation}\n"
current_history += f"\nAgente: {response}\nSISTEMA: Erro: Ferramenta inexistente.\n"
else:
# Se não tem comando, é a resposta final
print("--- RESPOSTA FINAL ENCONTRADA ---")
return response
print("!!! ERRO: LIMITE DE TENTATIVAS ATINGIDO !!!")
return "O agente atingiu o limite de tentativas para esta tarefa."
return "Limite de pensamento atingido."