Construa um Agente com Tool-Calling do Zero
Construa um agente com tool-calling usando só o SDK oficial da OpenAI — duas ferramentas reais com JSON Schema, um loop de execução com limite, e tratamento estruturado de erro para argumentos malformados do modelo. Sem framework de agente.
PremiumProblema
Todo framework de agente — LangChain, LlamaIndex, CrewAI — implementa por baixo o mesmo loop central: mandar ao modelo uma conversa mais uma lista de ferramentas que ele pode chamar, inspecionar a resposta em busca de um pedido para chamar uma delas, rodar essa ferramenta localmente, devolver o resultado como parte da conversa, e repetir até o modelo estar satisfeito. Frameworks adicionam conveniência e estrutura em cima disso, mas o loop em si é algo que você consegue — e deveria — construir você mesmo pelo menos uma vez usando só o SDK cru do provedor, para entender exatamente o que um framework está fazendo por você (e o que ele está escondendo de você) depois. Neste lab você vai construir esse loop do zero usando só o SDK oficial `openai` do Python: duas ferramentas reais com definições de parâmetro JSON Schema adequadas, um dispatcher que executa a ferramenta que o modelo pediu e formata o resultado de volta corretamente, um loop que repete isso até o modelo parar de pedir ferramentas (limitado para que um modelo confuso não entre em loop para sempre), e tratamento de erro para o caso que todo agente eventualmente encontra em produção — o modelo mandando argumentos que não parseiam ou não batem com o que uma ferramenta espera. Uma das suas duas ferramentas, `calculate`, avalia expressões aritméticas com aparência arbitrária. O atalho tentador é o `eval()` do Python — não faça isso. `eval()` sobre texto gerado por modelo é efetivamente execução remota de código como funcionalidade: um modelo (ou um documento com prompt injection que ele leu) poderia produzir uma expressão como `__import__('os').system(...)`. Em vez disso, você vai parsear a expressão numa AST com o módulo `ast` e só avaliar um conjunto pequeno e permitido de tipos de nó aritméticos — o mesmo princípio de "parseie primeiro, faça allow-list da gramática" que fundamenta todo avaliador de expressão seguro em sistemas de produção.
Objetivos
Ao final deste lab você será capaz de:
- Definir ferramentas para um LLM usando definições de parâmetro JSON
Schema, o mesmo formato usado pela API de function/tool-calling de
todo provedor importante. - Mandar a um modelo uma conversa com ferramentas disponíveis e
parsear corretamente ostool_callsda resposta dele. - Executar uma ferramenta pedida localmente e retornar o resultado ao
modelo como uma mensagemrole="tool", corretamente ligada via
tool_call_id. - Implementar um loop de agente limitado que continua chamando o modelo
até ele parar de pedir ferramentas, sem risco de loop infinito. - Tratar argumentos malformados ou inválidos vindos do modelo
retornando um erro estruturado que o modelo consegue reagir, em vez
de travar. - Explicar por que avaliar uma expressão matemática com parsing via
astmais uma allow-list de operadores é seguro onde oeval()não
é.
Pré-requisitos
Para completar este lab você vai precisar de:
- Python 3.10+ instalado.
-
pip install openai(o SDK oficial da OpenAI para Python). - Uma chave de API da OpenAI com um pouco de crédito disponível, definida
na variável de ambienteOPENAI_API_KEY(um modelo comogpt-4o-mini
é barato o suficiente para o punhado de chamadas deste lab). - Familiaridade básica com funções, dicionários e JSON em Python.
O formato do loop
Tool-calling com uma API no estilo chat-completions sempre segue o
mesmo padrão:
1. Envia: conversa até agora + lista de ferramentas disponíveis (JSON Schema)
2. Modelo responde OU COM:
a) uma resposta de texto normal → pronto, retorne
b) um ou mais tool_calls → vá para 3
3. Para cada tool_call: rode a função nomeada localmente com seus args
4. Anexe uma mensagem role="tool" por chamada, ligada por tool_call_id
5. Volte para 1 com a conversa atualizada
Tudo neste lab é construir e blindar esse loop de cinco passos. As duas
ferramentas que você vai implementar são deliberadamente simples —
get_time(timezone) e calculate(expression) — para você focar
inteiramente na mecânica do loop em vez da lógica das próprias
ferramentas.
Por que ast, não eval(), para calculate
eval() executa Python arbitrário. Se a string passada a ele algum dia
vier de um modelo — e por definição, num agente com tool-calling, ela
sempre vem — você entregou execução remota de código a qualquer coisa
que consiga influenciar a saída do modelo, incluindo um documento
malicioso ou com prompt injection que o modelo leu antes na conversa.
Parsear a expressão numa árvore de sintaxe abstrata com ast.parse() e
depois percorrer essa árvore você mesmo, avaliando só um conjunto
pequeno e permitido de tipos de nó (números, +, -, *, /, %,
**, menos unário), torna a "gramática" do que é aceitável explícita e
fechada — qualquer coisa fora da lista levanta um erro em vez de rodar.
Como trabalhar neste lab
Percorra os cinco passos em ordem. Os Passos 1–2 constroem entendimento
sem executar nada; os Passos 3–5 constroem o loop de verdade. Teste ao
longo do caminho com chamadas reais ao modelo — este lab não é mockado,
já que entender como a resposta crua do modelo realmente se parece é em
si parte do objetivo de aprendizado.