π» 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=Truepara 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