import os import json import redis import subprocess import base64 import random import urllib.request import urllib.error import time class SwarmAgent: def __init__(self, task_data): self.data = task_data self.repo_name = task_data.get('repository') self.repo_url = f"http://10.99.0.3:3000/{self.repo_name}.git" self.local_path = os.path.join("/home/deploy/projetos", self.repo_name.split('/')[-1]) # Conexão Redis para pegar chaves do pool self.r = redis.Redis(host='10.99.0.3', port=6379, password='clube67_dragonfly_pass_9903', db=0, decode_responses=True) def get_api_key(self, provider): pool = json.loads(self.r.get('swarm:keys:pool')) active_keys = [k['key'] for k in pool.get(provider, []) if k.get('status') == 'active'] if not active_keys: raise Exception(f"Nenhuma chave ativa encontrada para o provedor: {provider}") return random.choice(active_keys) def setup_workspace(self): os.makedirs("/home/deploy/projetos", exist_ok=True) if not os.path.exists(self.local_path): print(f" [>] Clonando repositório {self.repo_url} em {self.local_path}...") subprocess.run(["git", "clone", self.repo_url, self.local_path]) else: print(f" [>] Atualizando repositório em {self.local_path}...") subprocess.run(["git", "-C", self.local_path, "pull"]) def run_command(self, command): """Executa comandos shell na VPS 4""" print(f" [Exec] Rodando comando: {command}") result = subprocess.run(command, shell=True, capture_output=True, text=True, cwd=self.local_path) return {"stdout": result.stdout, "stderr": result.stderr} def write_file(self, path, content): """Escreve/Edita arquivos no projeto""" print(f" [Exec] Escrevendo arquivo: {path}") full_path = os.path.join(self.local_path, path) os.makedirs(os.path.dirname(full_path), exist_ok=True) with open(full_path, "w") as f: f.write(content) return f"Arquivo {path} escrito com sucesso." def read_file(self, path): """Lê arquivos do projeto""" print(f" [Exec] Lendo arquivo: {path}") full_path = os.path.join(self.local_path, path) if os.path.exists(full_path): with open(full_path, "r") as f: return f.read() return f"Erro: Arquivo {path} não encontrado." def post_comment(self, message): """Posta um comentário de feedback na Issue do Gitea""" issue_id = self.data.get('data', {}).get('issue', {}).get('number') if not issue_id: return url = f"http://10.99.0.3:3000/api/v1/repos/{self.repo_name}/issues/{issue_id}/comments" # Auth Basic auth_str = 'ruicesar:h$tg@g5aga$ra1E3$C-yHW$-BA@DF2@Grfa!3#' base64_auth = base64.b64encode(auth_str.encode('ascii')).decode('ascii') data = json.dumps({"body": message}).encode('utf-8') req = urllib.request.Request(url, data=data, method='POST') req.add_header('Content-Type', 'application/json') req.add_header('Authorization', f'Basic {base64_auth}') try: with urllib.request.urlopen(req) as response: if response.status == 201: print(f" [✓] Comentário postado na Issue #{issue_id}") except Exception as e: print(f" [E] Falha ao postar comentário no Gitea: {e}") def call_openai(self, messages, system_instruction, api_key): """Chama a API do OpenAI com ferramentas em formato puramente nativo""" url = 'https://api.openai.com/v1/chat/completions' headers = { 'Authorization': f'Bearer {api_key}', 'Content-Type': 'application/json' } # OpenAI estruturado com o systemInstruction no topo do array payload_messages = [{"role": "system", "content": system_instruction}] + messages tools = [ { "type": "function", "function": { "name": "read_file", "description": "Lê o conteúdo de um arquivo do projeto para análise técnica.", "parameters": { "type": "object", "properties": { "path": {"type": "string", "description": "Caminho relativo do arquivo no repositório."} }, "required": ["path"] } } }, { "type": "function", "function": { "name": "write_file", "description": "Cria ou edita um arquivo com o novo conteúdo fornecido.", "parameters": { "type": "object", "properties": { "path": {"type": "string", "description": "Caminho relativo do arquivo no repositório."}, "content": {"type": "string", "description": "Conteúdo completo a ser gravado no arquivo."} }, "required": ["path", "content"] } } }, { "type": "function", "function": { "name": "run_command", "description": "Executa um comando de terminal (como NPM, Docker, Git, etc) no diretório do projeto.", "parameters": { "type": "object", "properties": { "command": {"type": "string", "description": "Comando shell a ser executado."} }, "required": ["command"] } } } ] payload = { "model": "gpt-4o-mini", "messages": payload_messages, "tools": tools, "tool_choice": "auto" } req = urllib.request.Request(url, data=json.dumps(payload).encode('utf-8'), headers=headers, method='POST') with urllib.request.urlopen(req) as res: return json.loads(res.read().decode('utf-8')) def execute(self): self.setup_workspace() # Identificação de Localização Automática vps_ip = subprocess.getoutput("hostname -I").split()[0] vps_name = subprocess.getoutput("hostname") # Carrega a Doutrina de Agentes doutrina_path = "/home/deploy/instrucoes/global/doutrina_agentes.md" doutrina = "" if os.path.exists(doutrina_path): with open(doutrina_path, "r") as f: doutrina = f.read() # Consciência Situacional Injetada situational_awareness = f"\n\n[CONSCIÊNCIA SITUACIONAL]: Você está rodando na VPS: {vps_name} (IP: {vps_ip})\n" system_instruction = doutrina + situational_awareness task = self.data.get('data', {}).get('issue', {}).get('body', 'Corrigir projeto') print(f" [!] Agente iniciando na {vps_name} ({vps_ip}) para resolver a tarefa.") # Histórico inicial messages = [ { "role": "user", "content": f"Tarefa: {task}. Diretório do projeto: {self.local_path}. Analise o código e corrija a regressão aplicando as mudanças necessárias." } ] api_key = self.get_api_key('openai') feedback_steps = [] final_text = "" # Loop de multi-turn de ferramentas (máximo 15 iterações) for turn in range(15): print(f" [>] Iniciando turno {turn + 1} com o cérebro OpenAI (GPT-4o-mini)...") try: response = self.call_openai(messages, system_instruction, api_key) except Exception as e: print(f" [E] Erro ao chamar a API do OpenAI: {e}") feedback_steps.append(f"* **Erro na API**: {e}") break choices = response.get('choices', []) if not choices: print(" [E] Nenhuma resposta retornada do OpenAI.") break assistant_message = choices[0].get('message', {}) content = assistant_message.get('content') if content: final_text += content + "\n" # Mantém no histórico a resposta do modelo messages.append(assistant_message) tool_calls = assistant_message.get('tool_calls', []) if not tool_calls: print(" [🏁] Ciclo finalizado pelo modelo de IA.") break print(f" [!] OpenAI solicitou {len(tool_calls)} chamadas de ferramentas.") # Para o OpenAI, processamos cada chamada sequencialmente e adicionamos o resultado ao histórico for tool_call in tool_calls: func_name = tool_call.get('function', {}).get('name') func_args_str = tool_call.get('function', {}).get('arguments', '{}') try: args = json.loads(func_args_str) except Exception as e_args: args = {} print(f" [!] Executando ferramenta: {func_name} (args: {args})") result_data = None try: if func_name == "read_file": path = args.get('path') result_data = self.read_file(path) feedback_steps.append(f"* **Lendo**: `{path}`") elif func_name == "write_file": path = args.get('path') content_to_write = args.get('content') result_data = self.write_file(path, content_to_write) feedback_steps.append(f"* **Escrevendo**: `{path}`") elif func_name == "run_command": cmd = args.get('command') result_data = self.run_command(cmd) feedback_steps.append(f"* **Rodando**: `{cmd}`") except Exception as e_func: print(f" [E] Erro ao rodar ferramenta {func_name}: {e_func}") result_data = f"Erro de execução da ferramenta: {e_func}" # Prepara o feedback do tool e anexa ao histórico messages.append({ "role": "tool", "tool_call_id": tool_call.get('id'), "name": func_name, "content": json.dumps(result_data) if isinstance(result_data, dict) else str(result_data) }) # Formata o feedback e posta de volta no Gitea steps_str = "\n".join(feedback_steps) final_comment = ( f"### 🤖 [VPS 4 - EXECUTOR SWARM GPT-4o-mini]:\n\n" f"{final_text}\n\n" f"**⚙️ Ações e Diagnóstico de Infraestrutura:**\n{steps_str}" ) self.post_comment(final_comment) if __name__ == "__main__": import argparse parser = argparse.ArgumentParser() parser.add_argument("--data") args = parser.parse_args() if args.data: SwarmAgent(json.loads(args.data)).execute()