πŸ’» Criar um Agente Programador Web

Aprende a criar um agente AI completo especializado em desenvolvimento web, desde a arquitetura atΓ© Γ  implementaΓ§Γ£o e deploy.

🎯 Visão Geral do Projeto

Neste mΓ³dulo, vamos criar um Agente Programador Web completo que pode: analisar requisitos, gerar cΓ³digo, criar ficheiros, executar testes e fazer deploy de aplicaΓ§Γ΅es web.

ℹ️ O que vais construir

Um agente capaz de:

  • Receber descriΓ§Γ΅es de funcionalidades em linguagem natural
  • Analisar e planear a implementaΓ§Γ£o
  • Gerar cΓ³digo HTML, CSS, JavaScript, React, Node.js
  • Criar e modificar ficheiros
  • Executar comandos no terminal
  • Correr testes e corrigir erros
  • Preparar para deploy

Stack TecnolΓ³gico

🐍

Python 3.11+

Linguagem principal para o agente.

πŸ”—

LangChain

Framework para orquestraΓ§Γ£o de agentes.

πŸ¦™

Ollama

Modelo local (Llama 3.1 ou similar).

⚑

FastAPI

API para expor o agente.

πŸ“‹ Requisitos e Capacidades

O agente programador deve ter capacidades especΓ­ficas para trabalhar em projetos web.

Capacidades NecessΓ‘rias

Capacidades
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚    Capacidade    β”‚                   DescriΓ§Γ£o                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Ler Ficheiros    β”‚ Ler cΓ³digo existente para entender contexto     β”‚
β”‚ Escrever Fich.   β”‚ Criar novos ficheiros ou modificar existentes   β”‚
β”‚ Executar CΓ³digo  β”‚ Correr scripts, testes, build                   β”‚
β”‚ Pesquisar Web    β”‚ Encontrar soluΓ§Γ΅es e documentaΓ§Γ£o               β”‚
β”‚ MemΓ³ria          β”‚ Lembrar decisΓ΅es e contexto do projeto         β”‚
β”‚ Planeamento      β”‚ Decompor tarefas complexas em subtarefas      β”‚
β”‚ Debugging        β”‚ Identificar e corrigir erros                  β”‚
β”‚ Versionamento    β”‚ Git operations (commit, branch, merge)        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Linguagens Suportadas

O agente deve trabalhar com as seguintes tecnologias:

  • Frontend: HTML, CSS, JavaScript, TypeScript, React, Vue, Angular
  • Backend: Node.js, Python, Express, FastAPI
  • Base de Dados: SQL, NoSQL (MongoDB, PostgreSQL)
  • Ferramentas: Git, npm/yarn, Docker

πŸ—οΈ Arquitetura do Agente

Arquitetura
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    AGENTE PROGRAMADOR WEB                       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”‚
β”‚  β”‚   LLM Core  │◄──►│  MemΓ³ria   │◄──►│  Planeador  β”‚         β”‚
β”‚  β”‚  (Ollama)   β”‚    β”‚ (Contexto) β”‚    β”‚ (Tarefas)   β”‚         β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚
β”‚         β”‚                  β”‚                   β”‚                β”‚
β”‚         β–Ό                  β–Ό                   β–Ό                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚                    GESTOR DE FERRAMENTAS                 β”‚   β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”‚   β”‚
β”‚  β”‚  β”‚FileSystemβ”‚ β”‚ Terminal β”‚ β”‚   Web   β”‚ β”‚   Git   β”‚       β”‚   β”‚
β”‚  β”‚  β”‚ Tool    β”‚ β”‚  Tool   β”‚ β”‚ Search  β”‚ β”‚  Tool   β”‚       β”‚   β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                              β”‚                                  β”‚
β”‚                              β–Ό                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚                     SISTEMA DE FICHEIROS                  β”‚   β”‚
β”‚  β”‚           (Projeto que estΓ‘ a ser desenvolvido)          β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                                                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ› οΈ ImplementaΓ§Γ£o das Ferramentas

1. Ferramenta de Sistema de Ficheiros

python
import os
from pathlib import Path
from typing import Optional

class FileSystemTool:
    """Ferramenta para operaΓ§Γ΅es no sistema de ficheiros."""

    def __init__(self, base_path: str = "."):
        self.base_path = Path(base_path).resolve()

    def _resolve_path(self, path: str) -> Path:
        """Resolve e valida o caminho."""
        full_path = (self.base_path / path).resolve()
        # SeguranΓ§a: nΓ£o permitir sair do diretΓ³rio base
        if not str(full_path).startswith(str(self.base_path)):
            raise ValueError("Caminho nΓ£o permitido")
        return full_path

    def read_file(self, path: str) -> str:
        """LΓͺ o conteΓΊdo de um ficheiro."""
        full_path = self._resolve_path(path)
        if not full_path.exists():
            return f"Erro: Ficheiro '{path}' nΓ£o existe"
        return full_path.read_text(encoding='utf-8')

    def write_file(self, path: str, content: str) -> str:
        """Escreve conteΓΊdo num ficheiro."""
        full_path = self._resolve_path(path)
        full_path.parent.mkdir(parents=True, exist_ok=True)
        full_path.write_text(content, encoding='utf-8')
        return f"Ficheiro '{path}' criado/modificado com sucesso"

    def list_files(self, path: str = ".") -> str:
        """Lista ficheiros num diretΓ³rio."""
        full_path = self._resolve_path(path)
        if not full_path.is_dir():
            return f"Erro: '{path}' nΓ£o Γ© um diretΓ³rio"

        files = []
        for item in full_path.iterdir():
            prefix = "πŸ“" if item.is_dir() else "πŸ“„"
            files.append(f"{prefix} {item.name}")

        return "\n".join(files) if files else "DiretΓ³rio vazio"

    def delete_file(self, path: str) -> str:
        """Elimina um ficheiro."""
        full_path = self._resolve_path(path)
        if not full_path.exists():
            return f"Erro: Ficheiro '{path}' nΓ£o existe"
        full_path.unlink()
        return f"Ficheiro '{path}' eliminado com sucesso"

    def create_directory(self, path: str) -> str:
        """Cria um diretΓ³rio."""
        full_path = self._resolve_path(path)
        full_path.mkdir(parents=True, exist_ok=True)
        return f"DiretΓ³rio '{path}' criado com sucesso"

# Uso
fs = FileSystemTool("/projetos/meu-projeto")
print(fs.list_files("src"))

2. Ferramenta de Terminal

python
import subprocess
from typing import Tuple

class TerminalTool:
    """Ferramenta para executar comandos no terminal."""

    # Comandos permitidos (whitelist para seguranΓ§a)
    ALLOWED_COMMANDS = {
        'npm', 'yarn', 'node', 'npx',
        'python', 'pip', 'poetry',
        'git', 'ls', 'cat', 'echo',
        'pytest', 'jest', 'mocha',
        'tsc', 'eslint', 'prettier',
        'docker', 'docker-compose'
    }

    def __init__(self, working_dir: str = "."):
        self.working_dir = working_dir

    def _is_allowed(self, command: str) -> bool:
        """Verifica se o comando Γ© permitido."""
        cmd_name = command.split()[0] if command else ""
        return cmd_name in self.ALLOWED_COMMANDS

    def execute(self, command: str, timeout: int = 60) -> Tuple[str, bool]:
        """
        Executa um comando no terminal.

        Retorna: (output, success)
        """
        if not self._is_allowed(command):
            return f"Comando '{command}' nΓ£o Γ© permitido por seguranΓ§a", False

        try:
            result = subprocess.run(
                command,
                shell=True,
                capture_output=True,
                text=True,
                timeout=timeout,
                cwd=self.working_dir
            )

            output = result.stdout + result.stderr
            success = result.returncode == 0

            return output.strip(), success

        except subprocess.TimeoutExpired:
            return f"Comando excedeu o tempo limite de {timeout}s", False
        except Exception as e:
            return f"Erro ao executar: {str(e)}", False

    def run_tests(self, test_framework: str = "pytest") -> Tuple[str, bool]:
        """Executa testes."""
        commands = {
            "pytest": "python -m pytest -v",
            "jest": "npm test",
            "mocha": "npm run test"
        }
        command = commands.get(test_framework, test_framework)
        return self.execute(command)

    def install_dependencies(self, package_manager: str = "npm") -> Tuple[str, bool]:
        """Instala dependΓͺncias."""
        commands = {
            "npm": "npm install",
            "yarn": "yarn install",
            "pip": "pip install -r requirements.txt"
        }
        command = commands.get(package_manager, package_manager)
        return self.execute(command, timeout=300)  # 5 min timeout

# Uso
terminal = TerminalTool("/projetos/meu-projeto")
output, success = terminal.execute("npm run build")

3. Agente Principal

python
from langchain_community.llms import Ollama
from langchain.agents import initialize_agent, AgentType
from langchain.tools import Tool
from typing import List, Dict

class WebProgrammerAgent:
    """Agente programador web completo."""

    def __init__(self, project_path: str, model: str = "llama3.1:8b"):
        self.project_path = project_path
        self.llm = Ollama(model=model)
        self.fs_tool = FileSystemTool(project_path)
        self.terminal_tool = TerminalTool(project_path)
        self.memory: List[Dict] = []

        # Criar ferramentas LangChain
        self.tools = self._create_tools()

        # Inicializar agente
        self.agent = initialize_agent(
            self.tools,
            self.llm,
            agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,
            verbose=True,
            max_iterations=10,
            early_stopping_method="generate"
        )

    def _create_tools(self) -> List[Tool]:
        """Cria as ferramentas disponΓ­veis para o agente."""
        return [
            Tool(
                name="read_file",
                description="LΓͺ o conteΓΊdo de um ficheiro. Input: caminho do ficheiro.",
                func=self.fs_tool.read_file
            ),
            Tool(
                name="write_file",
                description="Escreve conteΓΊdo num ficheiro. Input JSON: {path, content}.",
                func=lambda x: self._handle_write(x)
            ),
            Tool(
                name="list_files",
                description="Lista ficheiros num diretΓ³rio. Input: caminho do diretΓ³rio.",
                func=self.fs_tool.list_files
            ),
            Tool(
                name="execute_command",
                description="Executa um comando no terminal. Input: comando.",
                func=lambda x: self.terminal_tool.execute(x)[0]
            ),
            Tool(
                name="run_tests",
                description="Executa os testes do projeto. Input: framework (pytest/jest).",
                func=lambda x: self.terminal_tool.run_tests(x)[0]
            )
        ]

    def _handle_write(self, input_str: str) -> str:
        """Processa input para write_file."""
        import json
        try:
            data = json.loads(input_str)
            return self.fs_tool.write_file(data["path"], data["content"])
        except:
            return "Input invΓ‘lido. Use JSON: {path: '...', content: '...'}"

    def develop(self, requirement: str) -> str:
        """
        Desenvolve uma funcionalidade baseada num requisito.

        Args:
            requirement: DescriΓ§Γ£o da funcionalidade em linguagem natural

        Returns:
            Resultado da implementaΓ§Γ£o
        """
        # Adicionar contexto Γ  memΓ³ria
        self.memory.append({"role": "user", "content": requirement})

        # Prompt do sistema
        system_prompt = f"""
        Γ‰s um programador web experiente. O teu objetivo Γ© implementar
        funcionalidades baseadas nos requisitos do utilizador.

        EstΓ‘s a trabalhar no projeto em: {self.project_path}

        Usa as ferramentas disponΓ­veis para:
        1. Ler ficheiros existentes para entender o contexto
        2. Criar/modificar ficheiros conforme necessΓ‘rio
        3. Executar comandos para instalar dependΓͺncias
        4. Correr testes para validar a implementaΓ§Γ£o

        Segue as melhores prΓ‘ticas de desenvolvimento web.
        Documenta o cΓ³digo quando apropriado.
        MantΓ©m o cΓ³digo limpo e organizado.
        """

        # Executar o agente
        result = self.agent.run(
            f"{system_prompt}\n\nRequisito: {requirement}"
        )

        return result

    def fix_bug(self, error_message: str) -> str:
        """
        Corrige um bug baseado na mensagem de erro.

        Args:
            error_message: Mensagem de erro ou descriΓ§Γ£o do bug

        Returns:
            Resultado da correΓ§Γ£o
        """
        return self.develop(f"Corrige o seguinte bug: {error_message}")

# Uso
agent = WebProgrammerAgent("/projetos/meu-site")

# Implementar uma funcionalidade
result = agent.develop("""
Cria uma pΓ‘gina de login com:
- FormulΓ‘rio com email e password
- ValidaΓ§Γ£o de campos
- BotΓ£o de submit
- Estilos CSS modernos
- LΓ³gica JavaScript para submissΓ£o
""")

print(result)

πŸ§ͺ Teste e Debug

Γ‰ essencial testar o agente de forma sistemΓ‘tica para garantir que funciona corretamente.

Testes UnitΓ‘rios

python
import pytest
from web_programmer_agent import WebProgrammerAgent, FileSystemTool, TerminalTool
import tempfile
import os

class TestFileSystemTool:
    """Testes para a ferramenta de sistema de ficheiros."""

    def setup_method(self):
        self.temp_dir = tempfile.mkdtemp()
        self.fs = FileSystemTool(self.temp_dir)

    def test_write_and_read_file(self):
        """Testa escrita e leitura de ficheiros."""
        result = self.fs.write_file("test.txt", "OlΓ‘ mundo!")
        assert "sucesso" in result.lower()

        content = self.fs.read_file("test.txt")
        assert content == "OlΓ‘ mundo!"

    def test_list_files(self):
        """Testa listagem de ficheiros."""
        self.fs.write_file("a.txt", "conteΓΊdo a")
        self.fs.write_file("b.txt", "conteΓΊdo b")

        listing = self.fs.list_files(".")
        assert "a.txt" in listing
        assert "b.txt" in listing

    def test_delete_file(self):
        """Testa eliminaΓ§Γ£o de ficheiros."""
        self.fs.write_file("delete_me.txt", "para eliminar")
        result = self.fs.delete_file("delete_me.txt")
        assert "sucesso" in result.lower()

        content = self.fs.read_file("delete_me.txt")
        assert "nΓ£o existe" in content.lower()

class TestWebProgrammerAgent:
    """Testes para o agente programador."""

    def setup_method(self):
        self.temp_dir = tempfile.mkdtemp()
        # Usar um mock do LLM para testes
        self.agent = WebProgrammerAgent(self.temp_dir)

    def test_agent_initialization(self):
        """Testa inicializaΓ§Γ£o do agente."""
        assert self.agent.llm is not None
        assert len(self.agent.tools) > 0

    def test_develop_simple_feature(self):
        """Testa desenvolvimento de uma feature simples."""
        result = self.agent.develop("Cria um ficheiro index.html com 'OlΓ‘ Mundo'")
        # Verificar que o ficheiro foi criado
        content = self.fs.read_file("index.html")
        assert "OlΓ‘ Mundo" in content
πŸ’‘ Dicas de Debug
  • Usa verbose=True para ver o raciocΓ­nio do agente
  • Adiciona print() nas ferramentas para ver inputs/outputs
  • ComeΓ§a com tarefas simples e aumenta a complexidade gradualmente
  • Guarda logs de execuΓ§Γ΅es para anΓ‘lise posterior

πŸš€ Deploy e ProduΓ§Γ£o

python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import uvicorn

app = FastAPI(title="Web Programmer Agent API")

class DevelopRequest(BaseModel):
    requirement: str
    project_path: str = "/app/projects"

class DevelopResponse(BaseModel):
    result: str
    files_created: list
    success: bool

@app.post("/develop", response_model=DevelopResponse)
async def develop_feature(request: DevelopRequest):
    """
    Endpoint para desenvolver uma funcionalidade.
    """
    try:
        agent = WebProgrammerAgent(request.project_path)
        result = agent.develop(request.requirement)

        # Listar ficheiros criados/modificados
        fs = FileSystemTool(request.project_path)
        files = fs.list_files(".")

        return DevelopResponse(
            result=result,
            files_created=files.split("\n"),
            success=True
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/health")
async def health_check():
    """Verifica se a API estΓ‘ a funcionar."""
    return {"status": "healthy"}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)
⚠️ Segurança Importante

Num ambiente de produΓ§Γ£o:

  • Whitelist de comandos: Limita os comandos que o agente pode executar
  • Sandbox: Executa o agente num ambiente isolado (Docker)
  • Timeouts: Limita o tempo de execuΓ§Γ£o
  • Auditoria: Regista todas as aΓ§Γ΅es do agente
  • AprovaΓ§Γ£o humana: Requer confirmaΓ§Γ£o para aΓ§Γ΅es destrutivas