O mapa mental da IA revelado!

Agentes de IA: Desvendamos Como Eles ‘Pensam’ e Dominam Ferramentas! 🤯

Olá, pessoal! Aqui é o Lucas Tech, e hoje a gente vai mergulhar em um universo fascinante: a mente dos Agentes de IA! Sabe quando a gente vê um assistente virtual fazendo coisas complexas e pensa "como ele consegue isso?" Pois é, chegou a hora de desvendar os bastidores. Prepare-se, porque vamos usar um dataset super especial para entender como esses agentes raciocinam, escolhem e usam ferramentas, e respondem em conversas complexas. É tipo um raio-X no cérebro digital! Vamos nessa?

Acesso ao Cérebro da IA: O Dataset lambda/hermes-agent-reasoning-traces

Pra começar nossa exploração, a gente vai usar um dataset incrível chamado lambda/hermes-agent-reasoning-traces. Pense nele como um diário de bordo superdetalhado onde os agentes de IA registram cada "pensamento" e ação. Nosso objetivo aqui é entender direitinho como os modelos baseados em agentes funcionam, desde o raciocínio interno até o uso de ferramentas e a geração de respostas em conversas que têm várias etapas.

Pra isso, o primeiro passo é carregar e inspecionar esse dataset. A gente dá uma olhada na sua estrutura, nas categorias de tarefas que ele cobre e no formato das conversas. É como arrumar a bancada do laboratório e ver todas as ferramentas que temos à disposição!

python
!pip -q install -U datasets pandas matplotlib seaborn transformers accelerate trl

import json, re, random, textwrap
from collections import Counter, defaultdict
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datasets import load_dataset, concatenate_datasets

random.seed(0)

CONFIG = "kimi"
ds = load_dataset("lambda/hermes-agent-reasoning-traces", CONFIG, split="train")
print(ds)
print("Config:", CONFIG, "| Fields:", ds.column_names)
print("Categories:", sorted(set(ds["category"])))

Opcional: combinar diferentes configurações do dataset para análises mais amplas

COMPARE_BOTH = False
if COMPARE_BOTH:
ds_kimi = load_dataset("lambda/hermes-agent-reasoning-traces", "kimi", split="train")
ds_glm = load_dataset("lambda/hermes-agent-reasoning-traces", "glm-5.1", split="train")
ds_kimi = ds_kimi.add_column("source", ["kimi"] len(ds_kimi))
ds_glm = ds_glm.add_column("source", ["glm-5.1"]
len(ds_glm))
ds = concatenate_datasets([ds_kimi, ds_glm]).shuffle(seed=0)
print("Combined:", ds, "→ counts:", Counter(ds["source"]))

sample = ds[0]
print("\n=== Exemplo 0 ===")
print("id :", sample["id"])
print("category :", sample["category"], "/", sample["subcategory"])
print("task :", sample["task"])
print("turns :", len(sample["conversations"]))
print("system[0] :", sample["conversations"][0]["value"][:220], "…\n")

Basicamente, a gente instala as bibliotecas necessárias, carrega o dataset e dá uma primeira olhada nos dados. É como abrir um livro e folhear as primeiras páginas para ter uma ideia do que vem por aí.

Dissecando a Conversa: Como Separamos o "Pensamento" da Ação

Agora que temos o dataset carregado, o desafio é extrair as partes mais importantes. Pense assim: o agente de IA tem "pensamentos" internos, faz "chamadas de ferramentas" e recebe "respostas das ferramentas". Tudo isso vem meio misturado no texto. Pra separar, a gente cria uns "parsers", que são como detetives especializados em encontrar esses padrões.

Usamos expressões regulares (regex) para "pescar" essas informações-chave: o que o agente pensou (entre as tags <think>), as chamadas de ferramentas (<tool_call>) e as respostas das ferramentas (<tool_response>). Isso nos permite separar o raciocínio interno das ações externas. Depois, a gente testa esses parsers em um exemplo pra ver se estão funcionando direitinho, extraindo as informações de forma estruturada.

python
THINK_RE = re.compile(r"(.?)", re.DOTALL)
TOOL_CALL_RE = re.compile(r"\s
({.?})\s", re.DOTALL)
TOOL_RESP_RE = re.compile(r"\s(.?)\s*", re.DOTALL)

def parse_assistant(value: str) -> dict:
thoughts = [t.strip() for t in THINK_RE.findall(value)]
calls = []
for raw in TOOL_CALL_RE.findall(value):
try:
calls.append(json.loads(raw))
except json.JSONDecodeError:
calls.append({"name": "", "arguments": {}})
final = TOOL_CALL_RE.sub("", THINK_RE.sub("", value)).strip()
return {"thoughts": thoughts, "tool_calls": calls, "final": final}

def parse_tool(value: str):
raw = TOOL_RESP_RE.search(value)
if not raw: return {"raw": value}
body = raw.group(1)
try: return json.loads(body)
except: return {"raw": body}

first_gpt = next(t for t in sample["conversations"] if t["from"] == "gpt")
p = parse_assistant(first_gpt["value"])
print("Prévia do pensamento:", (p["thoughts"][0][:160] + "…") if p["thoughts"] else "(nenhum)")
print("Chamadas de ferramenta:", [(c.get("name"), list(c.get("arguments", {}).keys())) for c in p["tool_calls"]])

Com esses parsers, a gente consegue olhar para uma resposta do assistente e ver exatamente o que ele pensou, quais ferramentas chamou e qual foi a resposta final. É um grande passo para a transparência!

Os Hábitos dos Agentes: Análises e Gráficos Reveladores

Agora que conseguimos "ler a mente" dos agentes, bora analisar os dados em larga escala! A gente pega uma parte do dataset e começa a medir várias coisas:

  • Frequência de uso de ferramentas: Quais ferramentas são as "queridinhas" dos agentes?
  • Comprimento das conversas: Quão longas são as interações até que o agente resolva a tarefa?
  • Taxa de erros: Com que frequência as ferramentas dão problemas ou os agentes erram em suas ações?

Essas estatísticas nos ajudam a entender o comportamento geral do agente. E, claro, porque uma imagem vale mais que mil palavras, criamos gráficos pra visualizar essas tendências de forma intuitiva. Assim, a gente consegue ver rapidamente os padrões de uso de ferramentas, quantas chamadas paralelas um agente faz de uma vez e a distribuição das diferentes categorias de tarefas.

python
N = 3000
sub = ds.select(range(min(N, len(ds))))

tool_calls = Counter()
parallel_widths = Counter()
thoughts_per_turn = []
calls_per_traj = []
errors_per_traj = []
turns_per_traj = []
cat_counts = Counter()

for ex in sub:
cat_counts[ex["category"]] += 1
n_calls = n_err = 0
turns_per_traj.append(len(ex["conversations"]))
for t in ex["conversations"]:
if t["from"] == "gpt":
p = parse_assistant(t["value"])
thoughts_per_turn.append(len(p["thoughts"]))
if p["tool_calls"]:
parallel_widths[len(p["tool_calls"])] += 1
for c in p["tool_calls"]:
tool_calls[c.get("name", "")] += 1
n_calls += len(p["tool_calls"])
elif t["from"] == "tool":
r = parse_tool(t["value"])
blob = json.dumps(r).lower()
if "error" in blob or ‘"exit_code": 1’ in blob or "traceback" in blob:
n_err += 1
calls_per_traj.append(n_calls)
errors_per_traj.append(n_err)

print(f"\nVerificadas {len(sub)} trajetórias")
print(f"Média de turnos/trajetória : {np.mean(turns_per_traj):.1f}")
print(f"Média de chamadas de ferramenta/trajetória : {np.mean(calls_per_traj):.1f}")
print(f"% com >=1 erro : {100np.mean([e>0 for e in errors_per_traj]):.1f}%")
print(f"% turnos paralelos : {100
sum(v for k,v in parallel_widths.items() if k>1)/max(1,sum(parallel_widths.values())):.1f}%")
print("Top 10 ferramentas :", tool_calls.most_common(10))

fig, axes = plt.subplots(2, 2, figsize=(13, 9))

top = tool_calls.mostcommon(15)
axes[0,0].barh([t for t,
in top][::-1], [c for _,c in top][::-1], color="teal")
axes[0,0].set_title("Top 15 ferramentas por volume de chamadas")
axes[0,0].set_xlabel("chamadas")

ks = sorted(parallel_widths)
axes[0,1].bar([str(k) for k in ks], [parallel_widths[k] for k in ks], color="coral")
axes[0,1].set_title("Chamadas de ferramenta por turno do assistente (largura paralela)")
axes[0,1].set_xlabel("# chamadas de ferramenta em um turno"); axes[0,1].set_ylabel("contagem")
axes[0,1].set_yscale("log")

axes[1,0].hist(turns_per_traj, bins=40, color="steelblue")
axes[1,0].set_title("Comprimento da conversa"); axes[1,0].set_xlabel("turnos")

cats, vals = zip(*cat_counts.most_common())
axes[1,1].pie(vals, labels=cats, autopct="%1.0f%%", startangle=90)
axes[1,1].set_title("Distribuição de categorias")

plt.tight_layout(); plt.show()

Olha que legal! A gente consegue ver quais ferramentas são mais usadas, se os agentes preferem fazer várias chamadas de ferramenta de uma vez e até qual a distribuição das tarefas que eles resolvem. Muita informação útil pra entender melhor como eles trabalham!

Vendo o Filme Completo: Rastreando Conversas e Preparando para o Treinamento

Analisar os números é show de bola, mas ver a conversa inteira, passo a passo, é outra história! Criamos uma ferramenta pra "renderizar" as conversas completas num formato fácil de ler. É como ter um debugger para as interações do agente, permitindo uma inspeção mais profunda.

Além disso, a gente extrai os "esquemas das ferramentas", que são como os manuais de instrução de cada ferramenta que o agente pode usar. E pra deixar tudo pronto pro futuro, convertemos o dataset para o formato de mensagens estilo OpenAI. Isso é super importante porque padroniza os dados e os torna compatíveis com a maioria das plataformas de treinamento de modelos de IA. É a base pra gente entender tanto a estrutura das ferramentas quanto como as conversas podem ser padronizadas.

python
def render_trace(ex, max_chars=350):
print(f"\n{‘=’72}\nTAREFA [{ex[‘category’]} / {ex[‘subcategory’]}]: {ex[‘task’]}\n{‘=’72}")
for t in ex["conversations"]:
role = t["from"]
if role == "system":
continue
if role == "human":
print(f"\n[USUÁRIO]\n{textwrap.shorten(t[‘value’], 600)}")
elif role == "gpt":
p = parse_assistant(t["value"])
for th in p["thoughts"]:
print(f"\n[PENSAMENTO]\n{textwrap.shorten(th, max_chars)}")
for c in p["tool_calls"]:
args = json.dumps(c.get("arguments", {}))[:200]
print(f"[CHAMADA] {c.get(‘name’)}({args})")
if p["final"]:
print(f"\n[RESPOSTA]\n{textwrap.shorten(p[‘final’], max_chars)}")
elif role == "tool":
print(f"[RESPOSTA DA FERRAMENTA] {textwrap.shorten(t[‘value’], 220)}")
print("="*72)

idx = int(np.argmin(np.abs(np.array(turns_per_traj) – 10)))
render_trace(sub[idx])

def get_tool_schemas(ex):
try: return json.loads(ex["tools"])
except: return []

schemas = get_tool_schemas(sample)
print(f"\nO Exemplo 0 tem {len(schemas)} ferramentas disponíveis")
for s in schemas[:3]:
fn = s.get("function", {})
print(" -", fn.get("name"), "—", (fn.get("description") or "")[:80])

ROLE_MAP = {"system": "system", "human": "user", "gpt": "assistant", "tool": "tool"}

def to_openai_messages(conv):
return [{"role": ROLE_MAP[t["from"]], "content": t["value"]} for t in conv]

example_msgs = to_openai_messages(sample["conversations"])
print("\nPrimeiras 2 mensagens do OpenAI:")
for m in example_msgs[:2]:
print(" ", m["role"], "→", m["content"][:120].replace("\n", " "), "…")

Com essas ferramentas, a gente não só entende o "porquê" das coisas, mas também deixa o caminho livre para o próximo passo: ensinar a IA a ser ainda mais esperta!

Otimizando o Aprendizado: Tokenização, Máscaras e Replay de Ações

Chegamos a uma etapa crucial para quem quer treinar modelos de linguagem: a preparação dos dados! Primeiro, a gente "tokeniza" as conversas. Isso significa transformar o texto em uma sequência de números, que é o que os modelos de IA realmente entendem.

Depois, aplicamos o que chamamos de "mascaramento de rótulos". É uma técnica inteligente que garante que apenas as respostas do assistente de IA contribuam para o treinamento. Assim, o modelo aprende o que ele deve responder, sem ser influenciado por nossas perguntas ou pelas respostas das ferramentas que ele mesmo chamou.

Também analisamos a distribuição de comprimento dos "pensamentos", chamadas de ferramentas e respostas finais. Isso nos dá uma ideia de quão "complexo" é o trabalho do agente em cada etapa. E, pra gente ter uma visão ainda mais clara, implementamos um "replayer de rastros". É como assistir a um filme em câmera lenta de como o agente pensou, chamou ferramentas e respondeu – super didático! E, claro, tudo isso nos prepara para, se quisermos, rodar um pequeno ciclo de fine-tuning para aprimorar um modelo.

python
from transformers import AutoTokenizer
TOK_ID = "Qwen/Qwen2.5-0.5B-Instruct"
tok = AutoTokenizer.from_pretrained(TOK_ID)

def build_masked(conv, tokenizer, max_len=2048):
msgs = to_openai_messages(conv)
for m in msgs:
if m["role"] == "tool":
m["role"] = "user"
m["content"] = "[TOOL OUTPUT]\n" + m["content"]
input_ids, labels = [], []
for m in msgs:
text = tokenizer.apply_chat_template([m], tokenize=False, add_generation_prompt=False)
ids = tokenizer.encode(text, add_special_tokens=False)
input_ids.extend(ids)
labels.extend(ids if m["role"] == "assistant" else [-100] * len(ids))
return input_ids[:max_len], labels[:max_len]

ids, lbls = build_masked(sample["conversations"], tok)
trainable = sum(1 for x in lbls if x != -100)
print(f"\nExemplo tokenizado: {len(ids)} tokens, {trainable} treináveis ({100*trainable/len(ids):.1f}%)")

think_lens, call_lens, ans_lens = [], [], []
for ex in sub.select(range(min(500, len(sub)))):
for t in ex["conversations"]:
if t["from"] != "gpt": continue
p = parse_assistant(t["value"])
for th in p["thoughts"]: think_lens.append(len(th))
for c in p["tool_calls"]: call_lens.append(len(json.dumps(c)))
if p["final"]: ans_lens.append(len(p["final"]))

plt.figure(figsize=(10,4))
plt.hist([think_lens, call_lens, ans_lens], bins=40, log=True,
label=["", "", "final answer"], stacked=False)
plt.legend(); plt.xlabel("caracteres"); plt.title("Distribuições de comprimento (log y)")
plt.tight_layout(); plt.show()

class TraceReplayer:
def init(self, ex):
self.ex = ex
self.steps = []
pending = None
for t in ex["conversations"]:
if t["from"] == "gpt":
if pending: self.steps.append(pending)
pending = {"think": parse_assistant(t["value"]), "responses": []}
elif t["from"] == "tool" and pending:
pending["responses"].append(parse_tool(t["value"]))
if pending: self.steps.append(pending)
def len(self): return len(self.steps)
def play(self, i):
s = self.steps[i]
print(f"\n── Passo {i+1}/{len(self)} ──")
for th in s["think"]["thoughts"]:
print(f"💭 {textwrap.shorten(th, 280)}")
for c in s["think"]["tool_calls"]:
print(f"⚙️ {c.get(‘name’)}({json.dumps(c.get(‘arguments’, {}))[:140]})")
for r in s["responses"]:
print(f"📥 {textwrap.shorten(json.dumps(r), 200)}")
if s["think"]["final"]:
print(f"💬 {textwrap.shorten(s[‘think’][‘final’], 200)}")

rp = TraceReplayer(sample)
for i in range(min(3, len(rp))):
rp.play(i)

Bloco de treinamento opcional (descomente e configure para rodar)

TRAIN = False
if TRAIN:
import torch
from transformers import AutoModelForCausalLM
from trl import SFTTrainer, SFTConfig

train_subset = ds.select(range(200))

def to_text(batch):
msgs = to_openai_messages(batch["conversations"])
for m in msgs:
if m["role"] == "tool":
m["role"] = "user"; m["content"] = "[TOOL]\n" + m["content"]
batch["text"] = tok.apply_chat_template(msgs, tokenize=False, add_generation_prompt=False)
return batch

train_subset = train_subset.map(to_text)

model = AutoModelForCausalLM.from_pretrained(
TOK_ID,
torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
device_map="auto" if torch.cuda.is_available() else None,
)

cfg = SFTConfig(
output_dir="hermes-sft-demo",
per_device_train_batch_size=1,
gradient_accumulation_steps=4,
max_steps=20,
learning_rate=2e-5,
logging_steps=2,
max_seq_length=1024,
dataset_text_field="text",
report_to="none",
fp16=torch.cuda.is_available(),
)
SFTTrainer(model=model, args=cfg, train_dataset=train_subset, processing_class=tok).train()
print("Demonstração de fine-tuning finalizada.")

print("\n✅ Tutorial completo! Agora você tem parsers, análises, gráficos, um replayer, exemplos SFT tokenizados + com rótulos mascarados e um hook de treinamento opcional.")

Ufa! Que jornada, hein? Desenvolvemos um fluxo de trabalho completo para analisar e trabalhar com os rastros de raciocínio dos agentes de IA. Conseguimos quebrar conversas complexas em componentes significativos, entender como os agentes raciocinam passo a passo e medir como eles interagem com as ferramentas para resolver problemas. As visualizações e análises nos deram insights valiosos sobre padrões e comportamentos comuns no dataset. Além disso, convertemos os dados para um formato ideal para treinar modelos de linguagem, incluindo a tokenização e o mascaramento de rótulos. Esse processo todo é uma base super sólida para estudar, avaliar e melhorar sistemas de IA que usam ferramentas de forma prática e escalável. É a base para a IA do futuro!

Minha Visão

Galera, é fascinante como a gente está conseguindo desmistificar o funcionamento interno da IA. Ver esses "rastros de raciocínio" não é só uma curiosidade técnica; é fundamental para construirmos sistemas de IA mais confiáveis, éticos e, principalmente, mais eficazes. Com essa transparência, podemos otimizar o aprendizado dos modelos e criar agentes que realmente entendam e executem tarefas complexas de forma autônoma, quase como um "colega" digital. É o caminho para uma IA que não só nos ajuda, mas que a gente realmente entende como funciona.

E você, o que acha desse "olhar por dentro" na mente dos agentes de IA? Acredita que entender esses detalhes é o segredo para criar a próxima geração de assistentes superinteligentes? Deixa seu comentário!

Referência: Matéria Original

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Rolar para cima
Tutorial Elevenlabs