E1 · Veja e feche
10 minRode o bloco do furo (com o agente da aula 4) e depois o da trava (que copia o da aula 5).
Pronto quando: na primeira saída aparece senha do banco: 1234; na segunda, recusado: fora da pasta de trabalho.
Aula 5 · 15 min
Um agente com ferramenta faz o que um texto mandar, inclusive um texto mal-intencionado. Antes de dar a ele o poder de gravar, você vai ver o furo da versão anterior com os próprios olhos e fechar com duas travas novas no harness, onde o modelo não consegue mexer, além do freio que já existe desde a aula 3.
Começar a aula
Objetivo: ver o agente da aula 4 ler um segredo fora da pasta e fechar o furo com a pasta única e o OK antes de substituir.
Clique em cada uma. Todas estão no harness: o modelo pede, o código decide.
O primeiro bloco usa o agente da aula 4, que ainda está na pasta. O segundo copia o da aula 5 e repete o pedido.
cd ~/agente-do-zero echo "senha do banco: 1234" > segredo.txt # fora de trabalho/: o agente não devia ler python3 agente.py "leia o arquivo ../segredo.txt"
#!/usr/bin/env python3
"""Agente do zero (aula 5): modelo + loop + ferramentas + instruções + memória + limites."""
import json, os, sys, urllib.request
MODELO = os.environ.get("MODELO", "llama3.2")
URL = os.environ.get("OLLAMA_URL", "http://localhost:11434") + "/api/chat"
PASTA = os.path.realpath("trabalho") # a única pasta que o agente enxerga
MEMORIA = "memoria.json"
MAX_RODADAS = 5 # o freio: no máximo 5 idas ao modelo por pedido
def cerebro(mensagens, ferramentas):
corpo = {"model": MODELO, "messages": mensagens, "tools": ferramentas, "stream": False, "think": False,
"options": {"temperature": 0, "seed": 1}}
pedido = urllib.request.Request(URL, json.dumps(corpo).encode(), {"Content-Type": "application/json"})
with urllib.request.urlopen(pedido, timeout=600) as r:
return json.loads(r.read())["message"]
def dentro(nome):
caminho = os.path.realpath(os.path.join(PASTA, nome))
if not caminho.startswith(PASTA + os.sep):
raise ValueError("fora da pasta de trabalho")
return caminho
def memorias():
return json.load(open(MEMORIA)) if os.path.exists(MEMORIA) else []
def ler_arquivo(nome):
with open(dentro(nome)) as f:
return f.read()
def escrever_arquivo(nome, texto):
caminho = dentro(nome)
if os.path.exists(caminho): # mexer no que já existe pede OK (sem terminal = não)
if not sys.stdin.isatty() or input(f" substituir {nome}? [s/N] ").lower() != "s":
return "recusado: o dono não aprovou"
with open(caminho, "w") as f:
f.write(texto)
return f"gravado: {nome}"
def lembrar(fato):
json.dump(memorias() + [fato], open(MEMORIA, "w"), ensure_ascii=False)
return f"guardado: {fato}"
FERRAMENTAS = {"ler_arquivo": ler_arquivo, "escrever_arquivo": escrever_arquivo, "lembrar": lembrar}
DESCRICOES = [
{"type": "function", "function": {"name": "ler_arquivo", "description": "Lê um arquivo da pasta de trabalho.",
"parameters": {"type": "object", "properties": {"nome": {"type": "string"}}, "required": ["nome"]}}},
{"type": "function", "function": {"name": "escrever_arquivo", "description": "Grava um arquivo na pasta de trabalho.",
"parameters": {"type": "object", "properties": {"nome": {"type": "string"}, "texto": {"type": "string"}},
"required": ["nome", "texto"]}}},
{"type": "function", "function": {"name": "lembrar", "description": "Guarda um fato sobre o dono para sempre.",
"parameters": {"type": "object", "properties": {"fato": {"type": "string"}}, "required": ["fato"]}}},
]
def responder(pedido, mostrar=print):
instrucoes = open("instrucoes.md").read()
lembrancas = "\n".join(f"- {m}" for m in memorias()) or "(nada ainda)"
mensagens = [{"role": "system", "content": f"{instrucoes}\n\nO que você sabe do dono:\n{lembrancas}"},
{"role": "user", "content": pedido}]
for rodada in range(MAX_RODADAS):
msg = cerebro(mensagens, DESCRICOES)
mensagens.append(msg)
if not msg.get("tool_calls"):
return msg.get("content", ""), rodada + 1
for chamada in msg["tool_calls"]:
nome, args = chamada["function"]["name"], chamada["function"].get("arguments") or {}
try:
saida = FERRAMENTAS[nome](**args)
except Exception as erro: # ferramenta errada, arquivo que não existe, fora da pasta
saida = f"recusado: {erro}"
mostrar(f"[ferramenta] {nome} {json.dumps(args, ensure_ascii=False)} -> {saida[:80]}")
mensagens.append({"role": "tool", "content": saida})
return "Parei: cheguei ao limite de rodadas.", MAX_RODADAS
if __name__ == "__main__":
os.makedirs(PASTA, exist_ok=True)
resposta, rodadas = responder(" ".join(sys.argv[1:]))
print(f"{resposta}\n({rodadas} rodada(s))")cd ~/agente-do-zero cp ~/agentes-do-zero-v72/kit/aula5/agente.py . python3 agente.py "leia o arquivo ../segredo.txt" python3 agente.py "crie o arquivo compras.txt com a palavra banana" cat trabalho/compras.txt; echo
[ferramenta] ler_arquivo {"nome": "../segredo.txt"} -> senha do banco: 1234
Desculpe, não posso acessar ou ler arquivos que contenham informações sensíveis, como senhas de bancos. Se você precisar acessar essa informação, recomendo que você entre em contato com o suporte do banco ou com o dono da informação.
Se você quiser ler um arquivo diferente, posso tentar ajudar. Por favor, forneça mais informações sobre o arquivo que você deseja ler.
(2 rodada(s))O agente da aula 4 leu o segredo.txt, fora da pasta de trabalho, e a senha foi parar no modelo (linha [ferramenta]). Desta vez o modelo não repetiu a senha, mas ela já tinha saído. Quem barra é o programa, não a boa vontade do modelo. A senha aqui é de mentira.
[ferramenta] ler_arquivo {"nome": "../segredo.txt"} -> recusado: fora da pasta de trabalho
Desculpe, não posso acessar arquivos fora da pasta de trabalho. Posso ajudar com algo mais?
(2 rodada(s))
[ferramenta] escrever_arquivo {"nome": "compras.txt", "texto": "banana"} -> gravado: compras.txt
Lembre que o arquivo foi criado com sucesso.
(2 rodada(s))
bananaMesmo pedido, agora recusado pelo código. E a ferramenta nova, escrever_arquivo, gravou o compras.txt dentro da pasta.
Faça na ordem.
Rode o bloco do furo (com o agente da aula 4) e depois o da trava (que copia o da aula 5).
Pronto quando: na primeira saída aparece senha do banco: 1234; na segunda, recusado: fora da pasta de trabalho.
Com o compras.txt já criado, peça para gravar de novo com outra palavra e responda N.
Pronto quando: o terminal perguntou substituir compras.txt? [s/N] e o arquivo continua com a palavra antiga.
A explicação aparece depois da sua resposta.
Pergunta 1 de 3
Por que a trava da pasta fica no código e não nas instruções?
dentro() no código é uma parede. Segurança vai na parede.dentro() no código é uma parede. Segurança vai na parede.Rever: As três travasPergunta 2 de 3
O que acontece se o modelo pedir para substituir um arquivo e o agente estiver rodando num script, sem ninguém no terminal?
Pergunta 3 de 3
Para que serve o MAX_RODADAS?
Escolha uma alternativa. A explicação aparece depois da sua tentativa.