feat: upgrade interface web e suporte a áudio completo
This commit is contained in:
186
ai_agent.py
186
ai_agent.py
@@ -1,120 +1,90 @@
|
||||
import os
|
||||
import re
|
||||
import requests
|
||||
from tools import run_bash_command, get_system_health
|
||||
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")
|
||||
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={api_key}"
|
||||
payload = {"contents": [{"parts": [{"text": prompt}]}]}
|
||||
res = requests.post(url, json=payload)
|
||||
if res.status_code == 200:
|
||||
return res.json()["candidates"][0]["content"]["parts"][0]["text"]
|
||||
return f"Erro Gemini: {res.text}"
|
||||
|
||||
elif provider == "ollama":
|
||||
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": prompt,
|
||||
"stream": False
|
||||
})
|
||||
if res.status_code == 200:
|
||||
return res.json().get("response", "")
|
||||
return f"Erro Ollama: {res.text}"
|
||||
|
||||
return "Provedor desconhecido."
|
||||
|
||||
def query_agent(prompt: str, override_provider: str = None) -> str:
|
||||
"""
|
||||
Função principal que roteia pro LLM desejado, detecta intents e aciona as Tools.
|
||||
Motor Agente em Loop (ReAct): Pensamento -> Ação -> Observação -> Resposta Final.
|
||||
"""
|
||||
|
||||
# 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")
|
||||
provider = override_provider or cfg.get("active_provider", "gemini")
|
||||
|
||||
if provider == "gemini":
|
||||
gemini_api_key = cfg.get("gemini_api_key", "")
|
||||
if not gemini_api_key: return "Chave API do Gemini não configurada."
|
||||
# Contexto de Ferramentas para a IA
|
||||
tools_desc = "\n".join([f"- {k}: {v['description']}" for k,v in AVAILABLE_TOOLS.items()])
|
||||
|
||||
# Prompt especializado (sem chaves complexas)
|
||||
system_prompt_base = """Você é o [Antigravity VPS Agent].
|
||||
Sua missão é ser o SysAdmin de elite do Marcos. Você tem acesso root.
|
||||
|
||||
### REGRAS:
|
||||
1. Responda em PORTUGUÊS (Brasil).
|
||||
2. Para agir, use: [CMD] comando [/CMD]. Rode UM comando por vez.
|
||||
3. Seus comandos devem ser diretos (docker, git, ls, rm, mkdir).
|
||||
4. Após o comando, você receberá a saída. O seu objetivo é resolver a solicitação do usuário.
|
||||
5. Quando terminar, sua resposta FINAL deve ter:
|
||||
- Um resumo técnico rápido.
|
||||
- Uma seção entre tags <REFINED> ... </REFINED> com uma tabela Markdown limpa ou resumo em tópicos (Nome: Valor) para o usuário leigo.
|
||||
|
||||
### FERRAMENTAS DISPONÍVEIS:
|
||||
{TOOLS_LIST}
|
||||
|
||||
### EXEMPLO DE REFINAMENTO VISUAL:
|
||||
Relatório: Coletei os dados solicitados.
|
||||
<REFINED>
|
||||
### 📊 Status Global
|
||||
- **CPU**: 10%
|
||||
- **RAM**: 500MB livre
|
||||
</REFINED>
|
||||
"""
|
||||
system_prompt = system_prompt_base.replace("{TOOLS_LIST}", tools_desc)
|
||||
|
||||
history = f"\nUsuário: {prompt}\n"
|
||||
max_loops = 10
|
||||
|
||||
for _ in range(max_loops):
|
||||
full_prompt = system_prompt + history
|
||||
response = get_llm_response(full_prompt, provider, cfg)
|
||||
|
||||
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}"
|
||||
# Procura por comandos na resposta
|
||||
match = re.search(r"\[CMD\](.*?)\[/CMD\]", response, re.IGNORECASE | re.DOTALL)
|
||||
|
||||
if match:
|
||||
cmd = match.group(1).strip()
|
||||
# Executa a tool (por enquanto focada em bash que é a mais poderosa)
|
||||
print(f"Agente executando: {cmd}")
|
||||
observation = AVAILABLE_TOOLS["run_bash_command"]["func"](cmd)
|
||||
|
||||
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."
|
||||
# Adiciona ao histórico para a IA ler na próxima rodada
|
||||
history += f"\nAgente (Pensamento/Ação): {response}\nSISTEMA (Saída do Terminal): {observation}\n"
|
||||
else:
|
||||
# Se não tem comando, é a resposta final
|
||||
return response
|
||||
|
||||
return "O agente atingiu o limite de tentativas para esta tarefa."
|
||||
|
||||
Reference in New Issue
Block a user