diff --git a/templates/agent_executor.py b/templates/agent_executor.py index 0c9de87..bdfd689 100644 --- a/templates/agent_executor.py +++ b/templates/agent_executor.py @@ -81,71 +81,73 @@ class SwarmAgent: except Exception as e: print(f" [E] Falha ao postar comentário no Gitea: {e}") - def call_gemini(self, contents, system_instruction, api_key): - """Chama a API do Gemini com ferramentas ativas em formato puramente nativo""" - url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-flash-latest:generateContent?key={api_key}" - headers = {'content-type': 'application/json'} + 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' + } - tools = [{ - "functionDeclarations": [ - { + # 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", + "type": "object", "properties": { - "path": {"type": "STRING", "description": "Caminho relativo do arquivo no repositório."} + "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", + "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."} + "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", + "type": "object", "properties": { - "command": {"type": "STRING", "description": "Comando shell a ser executado."} + "command": {"type": "string", "description": "Comando shell a ser executado."} }, "required": ["command"] } } - ] - }] + } + ] payload = { - "contents": contents, - "systemInstruction": { - "parts": [{"text": system_instruction}] - }, - "tools": tools + "model": "gpt-4o-mini", + "messages": payload_messages, + "tools": tools, + "tool_choice": "auto" } - for attempt in range(5): - try: - 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')) - except urllib.error.HTTPError as e: - if e.code == 429: - wait_time = (attempt + 1) * 8 - print(f" [W] Rate Limit (429) detectado. Aguardando {wait_time} segundos antes de tentar novamente (tentativa {attempt + 1}/5)...") - time.sleep(wait_time) - else: - raise e - raise Exception("Falha após 5 tentativas devido a limite de requisições (429).") + 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() @@ -169,97 +171,88 @@ class SwarmAgent: print(f" [!] Agente iniciando na {vps_name} ({vps_ip}) para resolver a tarefa.") # Histórico inicial - contents = [ + messages = [ { "role": "user", - "parts": [{"text": 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."}] + "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('google') + 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 Gemini...") - if turn > 0: - print(" [>] Resfriando a API por 3 segundos para evitar rate limit (429)...") - time.sleep(3.0) + print(f" [>] Iniciando turno {turn + 1} com o cérebro OpenAI (GPT-4o-mini)...") try: - response = self.call_gemini(contents, system_instruction, api_key) + response = self.call_openai(messages, system_instruction, api_key) except Exception as e: - print(f" [E] Erro ao chamar a API do Gemini: {e}") + print(f" [E] Erro ao chamar a API do OpenAI: {e}") feedback_steps.append(f"* **Erro na API**: {e}") break - candidates = response.get('candidates', []) - if not candidates: - print(" [E] Nenhuma resposta retornada.") + choices = response.get('choices', []) + if not choices: + print(" [E] Nenhuma resposta retornada do OpenAI.") break - content = candidates[0].get('content', {}) - parts = content.get('parts', []) - - # Mantém no histórico a resposta do modelo - contents.append(content) - - has_tool_call = False - function_responses = [] - - for part in parts: - if 'text' in part: - final_text += part['text'] + "\n" + assistant_message = choices[0].get('message', {}) + content = assistant_message.get('content') + if content: + final_text += content + "\n" - if 'functionCall' in part: - has_tool_call = True - func_call = part['functionCall'] - func_name = func_call.get('name') - args = func_call.get('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}" - - function_responses.append({ - "functionResponse": { - "name": func_name, - "response": { - "output": result_data - } - } - }) + # Mantém no histórico a resposta do modelo + messages.append(assistant_message) - if not has_tool_call: + tool_calls = assistant_message.get('tool_calls', []) + if not tool_calls: print(" [🏁] Ciclo finalizado pelo modelo de IA.") break - else: - contents.append({ - "role": "function", - "parts": function_responses + + 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]:\n\n" + 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}" )