Sistema de agentes do zero|INEMA.CLUB-PRO

Aula 5 · 15 min

Limites: o freio, a pasta e o seu OK

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
Portão de ferro fechado com cadeado diante de uma área de trabalho organizada.

Nesta aula você vai

  1. 1Como um pedido sai da pasta com ../
  2. 2As três travas e por que ficam no código
  3. 3Gravar arquivo com o seu OK

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.

01As três travas

Clique em cada uma. Todas estão no harness: o modelo pede, o código decide.

Clique em cada bloco do diagrama para ver o que ele faz.

02Primeiro o furo, depois a trava

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.

Terminal · o furo (agente da aula 4)
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"
Arquivo · kit/aula5/agente.py
#!/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))")
Terminal · a trava (agente da aula 5)
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
Exemplo do repositório
[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.

03Prática: feche o furo

Faça na ordem.

E1 · Veja e feche

10 min

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.

E2 · O seu OK

10 min

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.

04Teste o que aprendeu

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?

05Resumo da aula

FuroSem trava, "../" leva o agente para fora da pasta.
TravasFreio, pasta única e OK do dono, todas no código.
Padrão seguroSem ninguém para aprovar, a resposta é não.
Aula anteriorPróxima aula