Construindo um agente de código com LangChain e ferramentas customizadas
1. Fundamentos do LangChain para agentes de código
O LangChain é um framework que facilita a construção de aplicações baseadas em LLMs (Large Language Models). Para agentes de código, sua arquitetura se baseia em quatro componentes principais: chain (cadeia de chamadas), tool (ferramenta que o agente pode usar), memory (memória de conversa) e executor (orquestrador que decide qual ação tomar).
A diferença fundamental entre chains e agents está na autonomia: chains seguem um fluxo pré-definido, enquanto agents decidem dinamicamente quais ferramentas usar e em qual ordem. O AgentExecutor gerencia esse loop de decisão-ação-observação.
Para configurar o ambiente:
pip install langchain langchain-community langchain-openai
Configuração básica com OpenAI:
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_react_agent
from langchain.tools import Tool
llm = ChatOpenAI(model="gpt-4", temperature=0)
2. Definindo o escopo do agente de código
Nosso agente será especializado em tarefas de revisão e correção de código Python. Ele deve ser capaz de:
- Ler arquivos do sistema
- Executar código em ambiente controlado
- Navegar em repositórios Git
- Analisar diffs e commits
- Sugerir correções automaticamente
Boas práticas importantes:
- Limitar o contexto máximo do LLM para evitar estouro de tokens
- Implementar timeouts em todas as ferramentas
- Sanitizar entradas do usuário para evitar injeção de comandos
3. Construindo ferramentas customizadas (Tools)
Cada Tool no LangChain precisa de: nome único, descrição clara (usada pelo LLM para decidir quando chamá-la), schema de argumentos opcional e função executora.
Ferramenta para ler arquivos:
import os
from pydantic import BaseModel, Field
from langchain.tools import tool
class ReadFileInput(BaseModel):
file_path: str = Field(description="Caminho absoluto do arquivo a ser lido")
@tool(args_schema=ReadFileInput)
def read_file_tool(file_path: str) -> str:
"""Lê o conteúdo de um arquivo de código no sistema."""
if not os.path.exists(file_path):
return f"Erro: arquivo {file_path} não encontrado"
with open(file_path, 'r', encoding='utf-8') as f:
return f.read()
Ferramenta para executar código com sandbox:
import subprocess
import tempfile
import os
class RunCodeInput(BaseModel):
code: str = Field(description="Código Python a ser executado")
timeout: int = Field(default=10, description="Timeout em segundos")
@tool(args_schema=RunCodeInput)
def run_code_tool(code: str, timeout: int = 10) -> str:
"""Executa código Python em ambiente isolado e retorna a saída."""
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
f.write(code)
temp_path = f.name
try:
result = subprocess.run(
['python', temp_path],
capture_output=True,
text=True,
timeout=timeout
)
output = result.stdout if result.returncode == 0 else result.stderr
return output[:2000] # Limitar tamanho da resposta
except subprocess.TimeoutExpired:
return "Erro: execução excedeu o tempo limite"
finally:
os.unlink(temp_path)
4. Integração com repositórios Git e versionamento
Ferramenta para checkout de branches:
import git
from pydantic import BaseModel, Field
class GitCheckoutInput(BaseModel):
repo_path: str = Field(description="Caminho do repositório local")
branch: str = Field(description="Nome da branch para checkout")
@tool(args_schema=GitCheckoutInput)
def git_checkout_tool(repo_path: str, branch: str) -> str:
"""Faz checkout para uma branch específica em um repositório Git."""
try:
repo = git.Repo(repo_path)
repo.git.checkout(branch)
return f"Checkout realizado para branch {branch}"
except Exception as e:
return f"Erro no checkout: {str(e)}"
Ferramenta para buscar diffs:
class GitDiffInput(BaseModel):
repo_path: str = Field(description="Caminho do repositório")
commit_a: str = Field(description="Hash do commit inicial")
commit_b: str = Field(description="Hash do commit final")
@tool(args_schema=GitDiffInput)
def git_diff_tool(repo_path: str, commit_a: str, commit_b: str) -> str:
"""Retorna o diff entre dois commits."""
try:
repo = git.Repo(repo_path)
diff = repo.git.diff(commit_a, commit_b)
return diff[:3000] # Limitar tamanho
except Exception as e:
return f"Erro ao obter diff: {str(e)}"
5. Implementação do loop agente e memória
Configuramos o agente com memória de curto prazo para manter contexto entre interações:
from langchain.memory import ConversationBufferMemory
from langchain.agents import create_react_agent
from langchain.agents import AgentExecutor
from langchain.prompts import PromptTemplate
# Lista de ferramentas disponíveis
tools = [read_file_tool, run_code_tool, git_checkout_tool, git_diff_tool]
# Template do prompt para o agente ReAct
prompt = PromptTemplate.from_template(
"""Você é um assistente especializado em análise de código Python.
Use as ferramentas disponíveis para ler, executar e analisar código.
{chat_history}
Pergunta: {input}
{agent_scratchpad}"""
)
# Criar agente com memória
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
agent = create_react_agent(llm, tools, prompt)
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
memory=memory,
max_iterations=10,
early_stopping_method="generate",
handle_parsing_errors=True
)
6. Orquestração de múltiplas ferramentas em um fluxo real
Exemplo completo de uso: agente encontra um bug, sugere correção e gera PR.
# Exemplo de interação
resultado = agent_executor.invoke({
"input": """Analise o arquivo /projeto/app.py.
Encontre possíveis bugs de sintaxe ou lógica.
Execute o código para verificar se há erros em tempo de execução.
Se encontrar problemas, sugira correções."""
})
print(resultado["output"])
Para agrupar ferramentas relacionadas, usamos Toolkits:
from langchain.agents import Tool
git_toolkit = [
git_checkout_tool,
git_diff_tool,
Tool(name="git_log", func=lambda path: git.Repo(path).git.log("--oneline", "-10"),
description="Mostra os últimos 10 commits")
]
7. Testes, validação e segurança do agente
Testes unitários para cada ferramenta:
import pytest
from tools import read_file_tool
def test_read_file_success():
with tempfile.NamedTemporaryFile(mode='w', suffix='.py') as f:
f.write("print('hello')")
f.flush()
result = read_file_tool(f.name)
assert "print('hello')" in result
def test_read_file_not_found():
result = read_file_tool("/caminho/inexistente.py")
assert "não encontrado" in result
Medidas de segurança essenciais:
import re
def sanitize_command(command: str) -> str:
"""Remove comandos perigosos antes de executar."""
dangerous = ['rm -rf', 'sudo', 'chmod 777', 'dd if=']
for cmd in dangerous:
if cmd in command.lower():
raise ValueError(f"Comando bloqueado por segurança: {cmd}")
return command
8. Deploy e próximos passos
Empacotamento como API FastAPI:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class QueryRequest(BaseModel):
question: str
@app.post("/agent/query")
async def query_agent(request: QueryRequest):
result = agent_executor.invoke({"input": request.question})
return {"response": result["output"]}
Roadmap para evolução:
1. Suporte a múltiplos modelos (Claude, Gemini, modelos locais via Ollama)
2. Integração com ferramentas de qualidade como SonarQube e ESLint
3. Extensão para VS Code via LSP (Language Server Protocol)
4. Cache inteligente de resultados para reduzir custos com tokens
O agente construído pode ser estendido para suportar outras linguagens, integrar com CI/CD e servir como assistente de desenvolvimento em tempo real.
Referências
- LangChain Documentation - Agents — Documentação oficial sobre criação e configuração de agentes no LangChain
- LangChain Tools Customization Guide — Tutorial oficial para criação de ferramentas customizadas com Pydantic
- OpenAI Function Calling Documentation — Guia da OpenAI sobre como estruturar chamadas de função para LLMs
- GitPython Documentation — Biblioteca Python para manipulação programática de repositórios Git
- FastAPI Official Tutorial — Framework web para deploy de APIs Python, ideal para expor agentes como serviço
- LangSmith - Debugging Agents — Ferramenta da LangChain para monitorar e depurar agentes em produção