Lucas S. Vieira
Dando poderes ao agente
Guilda de IA
LLMs sĂŁo modelos de linguagem. Eles preveem texto, nĂŁo calculam.
Pergunta: "Quanto Ă© 234 Ă— 987?"
O modelo não calcula. Ele prevê a resposta baseado em padrões. Pode acertar, pode errar.
Ferramenta = função Python que o agente pode chamar.
@tool
def calculate(a: float, b: float, operation: str) -> float:
"""Performs a math operation between two numbers
and returns the exact result.
Use when you need to do calculations.
operation: 'add', 'subtract', 'multiply', 'divide'
"""
ops = {'add': a+b, 'subtract': a-b,
'multiply': a*b, 'divide': a/b}
return ops[operation]
Parâmetros nomeados: seguro e simples. Sem eval, sem parsing.
@tool
def get_word_length(word: str) -> int:
"""Returns the number of characters in a word.
Use when the user asks about the length
of a word or how many letters it has.
"""
return len(word)
O modelo decide baseado na descrição.
Descrição ruim:
"Calculadora."
→ Modelo não sabe quando usar.
Descrição boa:
"Performs a math operation between two numbers and returns the exact result. Use when you need to do calculations."
→ Modelo entende quando E como usar.
@tool gera o schema JSON automaticamente"234" → 234.0 (conversão automática)ValidationError se o LLM manda lixo
Sem @tool:
a = float(args["a"]) # manual
b = float(args["b"]) # manual
Com @tool:
resultado = calculate.invoke(args) # Pydantic!
bind_toolsinvoke → LLM decide (retorna tool_calls, não executa)invoke de novo com ToolMessage → resposta finalPasso 1 — o LLM decide:
resposta = llm_com_ferramentas.invoke(
"Quanto Ă© 234 vezes 987?")
pprint(resposta.tool_calls)
# [{'name': 'calculate',
# 'args': {'a': 234, 'b': 987,
# 'operation': 'multiply'},
# 'id': 'call_abc123'}]
resposta.content é string vazia — o LLM não executou!
Passo 2 — você executa:
tc = resposta.tool_calls[0]
resultado = calculate.invoke(tc['args'])
print(resultado) # 230958.0
Passo 3 — resultado volta pro LLM:
mensagens = [
HumanMessage(content="Quanto Ă© 234 vezes 987?"),
resposta, # AIMessage com tool_call
ToolMessage(content=str(resultado),
tool_call_id=tc['id']),
]
resposta_final = llm_com_ferramentas.invoke(mensagens)
# "O resultado Ă© 230.958."
O ciclo manual é ReAct em ação:
tool_calls)create_agentO ciclo manual é bom pra entender. Na prática:
from langchain.agents import create_agent
agente = create_agent(llm, [calculate, get_word_length])
resultado = agente.invoke(
{"messages": "Quanto Ă© 234 vezes 987?"})
print(resultado["messages"][-1].content)
Uma linha. Decide, executa, responde — tudo automático.
import asyncio
async def conversar():
future = agente.ainvoke(
{"messages": "Quanto Ă© 234 vezes 987?"})
print("Processando...") # roda imediatamente
resultado = await future # agora espera
print(resultado["messages"][-1].content)
asyncio.run(conversar())
⚠️ Em notebooks Jupyter/Colab, asyncio.run() falha.
Solução: nest_asyncio.apply() antes de rodar.
"Ferramenta transforma conversa em ação."
LLM puro: sĂł fala Agente com ferramenta: fala E faz
bind_tools): entender o que acontececreate_agent): produzir