🚀 Initial deploy to Gitea with fixes and dashboard enhancements
This commit is contained in:
120
ai_agent.py
Normal file
120
ai_agent.py
Normal file
@@ -0,0 +1,120 @@
|
||||
import os
|
||||
import re
|
||||
import requests
|
||||
from tools import run_bash_command, get_system_health
|
||||
from config import get_config
|
||||
|
||||
def query_agent(prompt: str, override_provider: str = None) -> str:
|
||||
"""
|
||||
Função principal que roteia pro LLM desejado, detecta intents e aciona as Tools.
|
||||
"""
|
||||
|
||||
# SYSTEM PROMPT AVANÇADO: O "Ensinamento" da Inteligência Artificial
|
||||
system_prompt = """Você é o [Antigravity VPS Agent], uma Inteligência Artificial autônoma de Dev/SysAdmin operando diretamente em uma máquina Ubuntu Linux do usuário (Marcos).
|
||||
Sua missão é ajudar o Marcos a gerenciar o servidor, criar aplicações, analisar código e orquestrar o Docker. VOCÊ DEVE RESPONDER SEMPRE EM PORTUGUÊS FLUENTE DO BRASIL.
|
||||
|
||||
### SEUS PODERES E ARQUITETURA
|
||||
1. **Nível Root**: Você roda em um Bot Python empacotado via Docker. Este container mapeia o `/var/run/docker.sock`, o que te dá o poder DIVINO sobre tudo. Você pode listar, deletar, parar e recriar qualquer container na máquina hospedeira inteira.
|
||||
2. **Sistema de Arquivos**: O disco principal do servidor está montado de forma segura, o que permite que você leia logs nativos.
|
||||
3. **Mecanismo de Ação (A Interface de Ferramentas)**: Você **não executa os comandos diretamente pela web**. Você possui uma interface conectada ao bot no Telegram e Web. Para interagir com o servidor, sempre que decidir que algo precisa ser lido (ex: `ls -la`, `docker network ls`, `cat arquivo.txt`) ou modificado, responda com a notação: [CMD] o_comando_aqui [/CMD].
|
||||
4. O bot Python varrerá sua resposta via Regex, extrairá o comando que você colocou entre os blocos, abrirá um shell Bash real, e te devolverá na conversa o resultado do seu comando.
|
||||
|
||||
### SEUS LIMITES E REGRAS
|
||||
- **Sem Comandos Interativos**: Jamais use comandos que exijam resposta humana travando o terminal (ex: `nano`, `top -d 1`, `apt-get install` sem `-y`). Se usar, o bash vai dar timeout (60s limit).
|
||||
- **Nunca use systemctl**: Você roda dentro de um container Docker. Logo, o comando `systemctl` NÃO FUNCIONA. Para ver serviços, use invariavelmente o comando `docker ps -a`.
|
||||
- **Aja, Não Explique Demais**: Se o usuário te der uma tarefa como "Veja meus containers", não explique o que é Docker. Apenas devolva [CMD] docker ps -a [/CMD] e diga "Estou olhando agora mesmo."
|
||||
- **Erros**: Se o comando retornar um código 127, 1 ou falha de acesso, aceite o erro e sugira tentar um comando de diagnóstico em seguida.
|
||||
|
||||
Acorde, a VPS é sua para cuidar!"""
|
||||
|
||||
if "logs do nginx" in prompt.lower() or "nginx" in prompt.lower():
|
||||
output = run_bash_command("systemctl status nginx || echo 'Nginx não parece ser gerenciado por systemctl aqui'")
|
||||
return f"Executei a checagem no Nginx. Olha o resultado: \n\n{output}"
|
||||
|
||||
if any(palavra in prompt.lower() for palavra in ["status", "cpu", "saude", "saúde", "sauda", "memória", "disco", "hd"]):
|
||||
health = get_system_health()
|
||||
return f"A saúde atual pontual da sua máquina direta do Python está assim:\n{health}"
|
||||
|
||||
cfg = get_config()
|
||||
provider = override_provider or cfg.get("active_provider", "ollama")
|
||||
|
||||
if provider == "gemini":
|
||||
gemini_api_key = cfg.get("gemini_api_key", "")
|
||||
if not gemini_api_key: return "Chave API do Gemini não configurada."
|
||||
|
||||
try:
|
||||
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={gemini_api_key}"
|
||||
payload = {
|
||||
"contents": [{"parts": [{"text": system_prompt + "\n\nUsuário: " + prompt}]}]
|
||||
}
|
||||
res = requests.post(url, json=payload)
|
||||
if res.status_code == 200:
|
||||
raw_response = res.json()["candidates"][0]["content"]["parts"][0]["text"]
|
||||
else:
|
||||
return f"Erro na API do Gemini: {res.text}"
|
||||
|
||||
# Regex Universal para processar Comandos
|
||||
match = re.search(r"\[.*?CMD\](.*?)\[/.*?CMD\]", raw_response, re.IGNORECASE | re.DOTALL)
|
||||
if match:
|
||||
comando_bash = match.group(1).strip()
|
||||
resultado = run_bash_command(comando_bash)
|
||||
|
||||
# Tradução via Gemini
|
||||
traducao_prompt = f"O comando `{comando_bash}` retornou o seguinte dado: {resultado[:1500]}\nTraduza gentilmente para um formato leigo sem códigos de erro difíceis."
|
||||
t_payload = {"contents": [{"parts": [{"text": system_prompt + "\n\n" + traducao_prompt}]}]}
|
||||
t_res = requests.post(url, json=t_payload)
|
||||
if t_res.status_code == 200:
|
||||
texto_leigo = t_res.json()["candidates"][0]["content"]["parts"][0]["text"]
|
||||
else:
|
||||
texto_leigo = "Falha ao gerar resumo no Gemini."
|
||||
|
||||
return f"🤖 **Comando Técnico (Gemini):** `{comando_bash}`\n\n**🖥️ Log Nativo:**\n```\n{resultado[:1000]}\n```\n\n🧑🏫 **Tradução:**\n{texto_leigo}"
|
||||
|
||||
return raw_response
|
||||
except Exception as e:
|
||||
return f"Falha de Conexão com Gemini Pro: {e}"
|
||||
|
||||
elif provider == "ollama":
|
||||
try:
|
||||
ollama_host = os.getenv("OLLAMA_HOST", "http://ollama-lw4s8g4gc8gss4gkc4gg0wk4:11434")
|
||||
res = requests.post(f"{ollama_host}/api/generate", json={
|
||||
"model": os.getenv("OLLAMA_MODEL", "qwen2.5-coder:1.5b"),
|
||||
"prompt": system_prompt + "\nUsuário: " + prompt,
|
||||
"stream": False
|
||||
})
|
||||
if res.status_code == 200:
|
||||
raw_response = res.json().get("response", "Erro vazio do Ollama")
|
||||
|
||||
# Motor de Tool Calling Tolerante: Detecta [CMD] ou [VCMD] ou variações que LLMs minúsculas inventam
|
||||
match = re.search(r"\[.*?CMD\](.*?)\[/.*?CMD\]", raw_response, re.IGNORECASE | re.DOTALL)
|
||||
|
||||
if match:
|
||||
comando_bash = match.group(1).strip()
|
||||
resultado = run_bash_command(comando_bash)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# NOVA LÓGICA: Tradução Leiga (Segundo Prompt para a IA)
|
||||
# ----------------------------------------------------
|
||||
traducao_prompt = f"O comando `{comando_bash}` retornou a seguinte saída do servidor:\n\n{resultado[:1500]}\n\nTraduza GENTILMENTE essa saída técnica explicando de forma amigável, gentil e em português muito simples (para um não-técnico) o que isso indica. SE a saída for um 'ERRO', acalme o usuário, resuma que um comando técnico falhou e sugira verbalmente o que de forma segura investigar a seguir (não mande blocos confusos, apenas explique em português fluente)."
|
||||
|
||||
try:
|
||||
res_traducao = requests.post(f"{ollama_host}/api/generate", json={
|
||||
"model": os.getenv("OLLAMA_MODEL", "qwen2.5-coder:1.5b"),
|
||||
"prompt": system_prompt + "\n\n" + traducao_prompt,
|
||||
"stream": False
|
||||
})
|
||||
if res_traducao.status_code == 200:
|
||||
texto_leigo = res_traducao.json().get("response", "Erro ao processar resumo.")
|
||||
else:
|
||||
texto_leigo = "Falha ao gerar resumo na LLM."
|
||||
except Exception as e_traducao:
|
||||
texto_leigo = "Ocorreu uma falha ao tentar traduzir o log."
|
||||
|
||||
# Retorna na tela a versão técnica seguida da versão explicada
|
||||
return f"🤖 **Comando Técnico:** `{comando_bash}`\n\n**🖥️ Log Nativo (Terminal):**\n```\n{resultado[:1000]}\n```\n\n🧑🏫 **Tradução:**\n{texto_leigo}"
|
||||
|
||||
return raw_response
|
||||
except Exception as e:
|
||||
return f"Falha ao conectar no Ollama local: {e}"
|
||||
|
||||
return "Ação não reconhecida pelo Agente no momento."
|
||||
Reference in New Issue
Block a user