Programa
Peça a um único modelo para pesquisar um tema, rascunhar um artigo, revisar e polir a versão final de uma só vez e, na maioria das vezes, o resultado será mediano em todas as etapas. O modelo não tem um papel claro em cada momento, então tenta fazer tudo ao mesmo tempo e raramente faz bem. Dividir o trabalho entre agentes especializados tende a gerar um resultado melhor, porque cada agente consegue focar em fazer sua parte do jeito certo.
Neste guia, vamos montar um agent swarm que faz exatamente isso. Usaremos o CrewAI para coordenar os agentes e o Gemini 3.5 Flash para sustentar o raciocínio e a escrita. O swarm tem quatro membros: um Pesquisador para coletar informações atualizadas, um Redator para produzir o primeiro rascunho, um Revisor para melhorar clareza e precisão, e um Gerente para coordenar o fluxo de trabalho.
Também daremos ao Pesquisador acesso à web em tempo real via Olostep, para trazer fontes frescas em vez de depender apenas do conhecimento interno do modelo. No fim, você terá um swarm hierárquico funcional que recebe um tema e devolve um artigo pronto para publicação — e uma noção clara de quando essa abordagem vale o custo e quando não vale.
Introdução aos agentes de IA
O que é um agent swarm?
Um agent swarm é um grupo de agentes de IA que trabalham juntos para concluir uma tarefa. Em vez de pedir que um único modelo pesquise, escreva, revise e finalize tudo sozinho, um swarm divide o trabalho entre vários agentes especializados. Cada agente tem um papel claro, e o sistema os coordena para produzir um resultado final mais forte.
A vantagem é a especialização. Um agente focado em pesquisa pode concentrar-se em encontrar e verificar fontes; um agente focado em escrita pode priorizar estrutura e clareza; e um revisor pode checar o rascunho com base na pesquisa original sem se distrair com o texto que acabou de produzir.
Um agente coordenador conecta as etapas e decide o que vem a seguir. No swarm que estamos construindo, esses papéis correspondem diretamente a Pesquisador, Redator, Revisor e Gerente.

Mas isso não sai de graça. Mais agentes significam mais chamadas ao modelo, mais tempo e mais pontos onde o fluxo pode falhar — um trade-off ao qual voltaremos no fim, depois de ver quanto custa rodar na prática.
O que é o CrewAI?
CrewAI é um framework open source para construir sistemas multiagentes. Ele oferece uma forma simples de definir agentes, atribuir tarefas e escolher como se coordenam, para você focar nos papéis e no fluxo de trabalho, não na infraestrutura que os conecta.
Três conceitos fazem a maior parte do trabalho.
- Agents são os trabalhadores individuais, cada um definido por um papel, um objetivo e um histórico que molda seu comportamento.
- Tasks descrevem o trabalho a ser feito e o output que cada uma deve produzir.
- A crew reúne agentes e tarefas e os executa sob um processo escolhido
Esse processo pode ser sequencial, em que as tarefas rodam em uma ordem fixa, ou hierárquico, em que um agente gerente delega trabalho aos demais e valida seus resultados. Aqui, usaremos o processo hierárquico, com o Gerente cuidando da delegação.
O CrewAI também gerencia o acesso a modelos via LiteLLM, uma biblioteca open source que fornece uma interface unificada para vários provedores. É isso que nos permite definir um único modelo Gemini 3.5 Flash e reutilizá-lo em todos os quatro agentes, como veremos nos passos abaixo.
Construindo um agent swarm com CrewAI e Gemini 3.5 Flash
Vamos começar pela configuração.
1. Pré-requisitos
Antes de começar, verifique se você tem:
- Python 3.11 ou superior instalado
- Jupyter Notebook ou JupyterLab instalado
- Noções básicas de Python e notebooks
- Uma conta no Google AI Studio
- Faturamento ativado ou créditos adicionados à sua conta Google para uso do Gemini
- Uma conta gratuita no Olostep
2. Instale as dependências
Abra o Jupyter Notebook ou JupyterLab e crie um notebook com o kernel Python mais recente disponível no seu ambiente. Em seguida, rode a célula abaixo para instalar os pacotes necessários.
%pip install --quiet -U \
crewai \
crewai-tools \
google-genai \
litellm \
olostep \
nest-asyncio
Após a instalação, rode a próxima célula para conferir as versões instaladas. Isso ajuda a confirmar que tudo foi instalado corretamente.
import importlib.metadata as md
for pkg in [
"crewai",
"crewai-tools",
"google-genai",
"litellm",
"olostep",
"nest-asyncio",
]:
print(f"{pkg:>16} {md.version(pkg)}")
Você deve ver algo como:
crewai 1.14.6
crewai-tools 1.14.6
google-genai 2.8.0
litellm 1.87.1
olostep 1.1.0
nest-asyncio 1.6.0
3. Defina variáveis de ambiente
Você vai precisar de duas chaves de API para este projeto.
Primeiro, crie uma chave da API do Gemini no Google AI Studio. Abra o Google AI Studio, vá até a seção de chaves de API e crie uma nova chave para seu projeto. O Gemini oferece uma camada gratuita, mas com limites de uso e de taxa. Para testar e rodar o Gemini com mais confiabilidade, conecte sua conta de faturamento do Google ou adicione créditos para que o uso seja cobrado quando ultrapassar os limites do free tier.
Depois, crie uma chave de API do Olostep. Cadastre-se no Olostep, abra o dashboard e gere uma chave de API na página de chaves. Usaremos essa chave para dar ao agente Pesquisador acesso ao vivo à web para busca e scraping de páginas.
Com as duas chaves em mãos, salve-as como variáveis de ambiente. O Gemini alimentará os agentes do CrewAI, enquanto o Olostep permitirá que o Pesquisador colete informações atuais na web.
Depois de criar as duas chaves, salve-as como variáveis de ambiente antes de rodar o notebook.
PowerShell:
$env:GEMINI_API_KEY="your-gemini-key"
$env:OLOSTEP_API_KEY="your-olostep-key"
macOS/Linux:
export GEMINI_API_KEY="your-gemini-key"
export OLOSTEP_API_KEY="your-olostep-key"
Agora rode a célula abaixo para verificar se a chave do Gemini foi carregada corretamente.
import os
api_key = os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")
if not api_key:
raise RuntimeError(
"Set GEMINI_API_KEY or GOOGLE_API_KEY in your environment, "
"then restart the notebook kernel."
)
print(f"Gemini API key loaded")
Você deve ver:
Gemini API key loaded
4. Configure o Gemini 3.5 Flash via LiteLLM
Neste projeto, usaremos o Gemini 3.5 Flash como o LLM compartilhado por todos os agentes do CrewAI. O Gemini 3.5 Flash foi projetado para workloads rápidos, agentic e focados em código. Ele é útil aqui porque nossos agentes precisam raciocinar em várias etapas, seguir instruções, usar ferramentas e produzir saídas estruturadas.
O Gemini 3.5 Flash também oferece janela de contexto ampla, respostas longas, thinking e uso de ferramentas, o que o torna uma ótima escolha para fluxos com agentes. Como um agent swarm pode chamar o modelo várias vezes entre Pesquisador, Redator, Revisor e Gerente, usar um modelo Flash ajuda a manter o fluxo responsivo sem abrir mão de bom raciocínio e qualidade de escrita.
Se quiser comparar com outros LLMs de ponta, recomendo ler nossos guias sobre Gemini 3.5 Flash vs GPT-5.5 e Gemini 3.5 Flash vs Claude Opus 4.8.
Vamos acessar o Gemini via LiteLLM. O LiteLLM é uma biblioteca open source que fornece uma interface unificada para chamar vários provedores de LLM, incluindo Gemini, OpenAI, Anthropic, Bedrock, Vertex AI e outros. Nesta configuração, o CrewAI usa o LiteLLM nos bastidores, assim podemos passar o nome do modelo do Gemini no formato simples provider/model.
Agora vamos criar um LLM compartilhado do CrewAI e reutilizá-lo em todos os agentes. Isso simplifica a configuração porque Pesquisador, Redator, Revisor e Gerente usarão o mesmo modelo Gemini.
from crewai import LLM
GEMINI_MODEL = "gemini/gemini-3.5-flash"
gemini_llm = LLM(
model=GEMINI_MODEL,
api_key=os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY"),
temperature=1.0,
)
print(f"LLM ready: {gemini_llm.model}")
print("Provider API: Google Gemini (via LiteLLM)")
Você deve ver algo como:
LLM ready: gemini-3.5-flash
Provider API: Google Gemini (via LiteLLM)
Isso confirma que o CrewAI já consegue acessar o Gemini via LiteLLM. Nos próximos passos, vamos passar esse objeto compartilhado gemini_llm para cada agente.
5. Adicione acesso à web em tempo real
O agente Pesquisador precisa de acesso a informações atuais. Para isso, vamos criar uma ferramenta personalizada de busca do Olostep e adicioná-la ao Pesquisador depois.
Essa ferramenta pode operar em dois modos. Pode retornar apenas resumos da busca, o que é mais rápido, ou pode fazer scraping das páginas e retornar o conteúdo em markdown. Também adicionamos um pequeno orçamento de busca para o agente não ficar chamando a ferramenta web vezes demais.
from pydantic import Field, PrivateAttr
from crewai.tools import BaseTool
try:
from olostep import Olostep, Olostep_BaseError
except ImportError:
raise ImportError(
"The 'olostep' package is not installed. Run %pip install -U olostep."
)
Primeiro, carregue a chave de API do Olostep do ambiente.
olostep_api_key = os.getenv("OLOSTEP_API_KEY")
if not olostep_api_key or olostep_api_key == "your-olostep-api-key-here":
print(
"The Olostep tool will fail at call time unless OLOSTEP_API_KEY "
"is set in the environment."
)
else:
print("Olostep API key loaded.")
_olostep_client = Olostep(api_key=olostep_api_key) if olostep_api_key else None
Agora crie a ferramenta personalizada do CrewAI.
class OlostepSearchTool(BaseTool):
"""Search the web with a strict research-call budget."""
name: str = "olostep_web_search"
description: str = (
"Search the live web. Set scrape=false for fast discovery and "
"scrape=true for full-page evidence. You have at most 3 discovery "
"searches without scraping and 6 external search calls total. After "
"3 discovery searches, use scrape=true on the strongest query or "
"domain. Each call returns at most 3 results. Inputs: query, scrape, "
"limit, and optional include_domains."
)
default_limit: int = Field(default=3, ge=1, le=3)
max_chars_per_page: int = Field(default=4000, ge=500, le=12000)
_total_calls: int = PrivateAttr(default=0)
_discovery_calls: int = PrivateAttr(default=0)
_call_history: list[dict] = PrivateAttr(default_factory=list)
def _run(
self,
query: str,
scrape: bool = False,
limit: int | None = None,
include_domains: list[str] | None = None,
) -> str:
if _olostep_client is None:
return (
"OLOSTEP_API_KEY is not set. Set it in the environment "
"and restart the kernel."
)
if self._total_calls >= 6:
return (
"Research search budget exhausted: 6 external calls have "
"already been used. Stop searching and complete the task "
"with the collected sources."
)
if not scrape and self._discovery_calls >= 3:
return (
"Discovery-search limit reached. Do not run another "
"scrape=False search. Use scrape=True on the strongest query "
"or selected domains, or finish with the sources already found."
)
clean_query = query.strip()
if not clean_query:
return "Search query cannot be empty."
result_limit = max(1, min(limit or self.default_limit, 3))
kwargs = {"query": clean_query, "limit": result_limit}
if scrape:
kwargs["scrape_options"] = {
"formats": ["markdown"],
"timeout": 25,
}
if include_domains:
kwargs["include_domains"] = include_domains
self._total_calls += 1
if not scrape:
self._discovery_calls += 1
call_record = {
"number": self._total_calls,
"query": clean_query,
"scrape": scrape,
"limit": result_limit,
"domains": include_domains or [],
"status": "started",
"results": 0,
}
self._call_history.append(call_record)
try:
search = _olostep_client.searches.create(**kwargs)
except Olostep_BaseError as error:
call_record["status"] = "error"
call_record["error"] = f"{type(error).__name__}: {error}"
return (
f"Olostep search error: {type(error).__name__}: {error}\n"
f"Budget used: {self._total_calls}/6 total calls; "
f"{self._discovery_calls}/3 discovery calls."
)
if not search.links:
call_record["status"] = "no_results"
return (
f"No results found for query: {clean_query!r}\n"
f"Budget used: {self._total_calls}/6 total calls; "
f"{self._discovery_calls}/3 discovery calls."
)
call_record["status"] = "success"
call_record["results"] = len(search.links)
chunks = [
f"## Web results for: {clean_query!r}",
f"Page scraping: {'enabled' if scrape else 'disabled'}",
(
f"Budget used: {self._total_calls}/6 total calls; "
f"{self._discovery_calls}/3 discovery calls."
),
]
for index, link in enumerate(search.links, 1):
url = link.get("url", "")
title = link.get("title") or url or "Untitled result"
description = link.get("description") or "No description provided."
chunks.append(
f"### {index}. {title}\n"
f"URL: {url}\n"
f"Summary: {description}"
)
markdown = link.get("markdown_content")
if scrape and markdown:
body = markdown.strip()
if len(body) > self.max_chars_per_page:
body = body[: self.max_chars_per_page] + "\n...[truncated]"
chunks.append(f"Page content:\n{body}")
return "\n\n".join(chunks)
Por fim, crie uma instância da ferramenta.
olostep_search_tool = OlostepSearchTool()
print(f"Tool ready: {olostep_search_tool.name}")
Você deve ver algo como:
Olostep API key loaded.
Tool ready: olostep_web_search
Isso confirma que a ferramenta de busca web do Olostep está pronta. Mais adiante, vamos anexá-la ao agente Pesquisador para que ele colete informações atuais da web.
6. Crie os agentes
Agora vamos criar os agentes do nosso swarm. Definiremos três agentes executores e um agente gerente. Os executores fazem as tarefas principais, enquanto o gerente coordena o fluxo.
O Pesquisador usará a ferramenta do Olostep para encontrar informações atuais. O Redator transformará a pesquisa em um rascunho. O Revisor vai melhorar o rascunho. O Gerente decidirá como delegar o trabalho na crew hierárquica.
Crie os agentes executores
Comecemos pelos três agentes executores.
from crewai import Agent
researcher = Agent(
role="Senior Research Analyst",
goal=(
"Find accurate, current, and well-sourced information about {topic}. "
"Produce concrete, verifiable points for the writer."
),
backstory=(
"You are a meticulous research analyst. You decide whether each web "
"search needs page scraping. Use scrape=False when titles, URLs, and "
"summaries are enough. Use scrape=True when you need full-page evidence "
"to verify a claim or understand a source in detail. Cite source URLs."
),
llm=gemini_llm,
tools=[olostep_search_tool],
verbose=True,
allow_delegation=False,
)
Agora, crie o agente Redator.
writer = Agent(
role="Blog Post Writer",
goal=(
"Turn the research into a complete, publication-ready educational "
"article on {topic}. Include a strong title, an unlabeled italic "
"summary, TL;DR, detailed body sections, conclusion, and FAQs. Add a "
"table only when it makes a comparison or decision easier to understand."
),
backstory=(
"You are a seasoned technology educator and writer. You create "
"practical, approachable articles similar in structure to high-quality "
"DataCamp content, without copying its wording or branding. You use "
"clear headings, short paragraphs, concrete examples, and sourced facts."
),
llm=gemini_llm,
verbose=True,
allow_delegation=False,
)
Agora crie o agente Revisor.
reviewer = Agent(
role="Senior Editor",
goal=(
"Review the draft for factual accuracy, clarity, structure, and "
"completeness. Ensure it follows the required article order and return "
"a polished, publication-ready version."
),
backstory=(
"You are a detail-oriented senior editor for an educational technology "
"publication. You verify claims, improve flow, remove repetition, and "
"ensure the title is followed by an unlabeled italic summary and TL;DR. "
"You keep a table only when it adds real value, remove any Key Takeaways "
"section, and ensure Conclusion appears immediately before FAQs."
),
llm=gemini_llm,
verbose=True,
allow_delegation=False,
)
Crie o agente gerente
Por fim, crie o agente Gerente.
manager = Agent(
role="Content Coordinator",
goal=(
"Coordinate research, writing, and review for {topic}. Delegate each "
"task to the correct specialist and ensure the reviewed article is the "
"final result."
),
backstory=(
"You are an experienced editorial lead. You keep the workflow simple: "
"research first, writing second, and editorial review last."
),
llm=gemini_llm,
verbose=True,
allow_delegation=True,
)
O gerente é configurado separadamente quando montamos a crew hierárquica. Por isso ele ainda não foi adicionado à lista de agentes executores.
Criando agentes de IA com o Google ADK
7. Defina as tarefas
Agora vamos definir as tarefas do agent swarm. Essas tarefas não são atribuídas diretamente aos agentes executores. Em vez disso, o agente Gerente decidirá qual executor deve cuidar de cada uma.
Neste fluxo, a primeira tarefa é a pesquisa, a segunda é a redação e a terceira é a revisão. Cada tarefa também inclui um output esperado, para que os agentes saibam exatamente o que precisam produzir.
from crewai import Task
research_task = Task(
description=(
"Delegate to the Senior Research Analyst. Research '{topic}' using at "
"most 6 web-tool calls. Use no more than 3 calls with scrape=False, "
"then use scrape=True only when detailed evidence is needed. Collect "
"5-7 useful findings, source URLs, and common reader questions."
),
expected_output=(
"Clear markdown research notes with 5-7 sourced findings and 3-5 "
"potential FAQ questions."
),
)
Em seguida, crie a tarefa de redação. Ela usa a tarefa de pesquisa como contexto, para que o Redator construa o artigo a partir do que foi coletado.
write_task = Task(
description=(
"Delegate to the Blog Post Writer. Write one complete educational "
"markdown article on '{topic}', approximately 1,000-1,500 words. Start "
"with one H1 title and an unlabeled italic summary. Then include TL;DR, "
"an introduction, useful body sections, an optional table only when it "
"improves understanding, a Conclusion, and FAQs. Use source links."
),
expected_output=(
"One continuous publication-ready markdown article with title, italic "
"summary, TL;DR, body, Conclusion, and 3-5 FAQs."
),
context=[research_task],
)
Agora, crie a tarefa de revisão. Ela usa as tarefas de pesquisa e redação como contexto, para que o Revisor cheque o artigo com base na pesquisa original.
review_task = Task(
description=(
"Delegate to the Senior Editor. Review the article for accuracy, "
"clarity, structure, and source support. Return only the corrected full "
"article. Keep the same continuous article format and ensure FAQs are "
"the final section."
),
expected_output=(
"Only the final reviewed markdown article, beginning with its H1 title "
"and ending with the final FAQ answer."
),
context=[research_task, write_task],
)
Por fim, adicione as três tarefas a uma lista.
tasks = [research_task, write_task, review_task]
print(f"Defined {len(tasks)} manager-delegated tasks.")
Você deve ver:
Defined 3 manager-delegated tasks.
8. Monte o fluxo de trabalho
Agora vamos conectar os agentes e as tarefas em um fluxo único do CrewAI. Usaremos um processo hierárquico no qual o agente Gerente coordena os agentes executores.
Nesta configuração, Pesquisador, Redator e Revisor são adicionados como agentes executores. O Gerente é passado separadamente via manager_agent, pois é responsável pela delegação e coordenação.
from crewai import Crew, Process
crew = Crew(
agents=[researcher, writer, reviewer],
tasks=tasks,
manager_agent=manager,
process=Process.hierarchical,
verbose=True,
memory=False,
)
Agora imprima os detalhes da crew para confirmar que o fluxo foi montado corretamente.
print("Hierarchical crew assembled.")
print(f" Workers : {[a.role for a in crew.agents]}")
print(f" Manager : {crew.manager_agent.role}")
print(f" Tasks : {len(crew.tasks)}")
print(f" Process : {crew.process}")
Você deve ver algo como:
Hierarchical crew assembled.
Workers : ['Senior Research Analyst', 'Blog Post Writer', 'Senior Editor']
Manager : Content Coordinator
Tasks : 3
Process : Process.hierarchical
Isso confirma que o agent swarm hierárquico está pronto. No próximo passo, vamos rodar a crew em um tema e gerar a resposta final.
9. Rode o agent swarm
Agora podemos rodar o agent swarm. Vamos fornecer um tema e iniciar o fluxo com kickoff_async().
O Jupyter já executa um event loop em segundo plano. Por isso, aplicamos primeiro o nest_asyncio, para que o fluxo assíncrono do CrewAI possa rodar dentro do notebook.
try:
import nest_asyncio
nest_asyncio.apply()
print("nest_asyncio applied: CrewAI can now run inside this kernel.")
except ModuleNotFoundError:
print("nest_asyncio not installed. Run: pip install nest-asyncio")
Você deve ver:
nest_asyncio applied: CrewAI can now run inside this kernel.
Em seguida, defina um tema e crie uma função auxiliar para rodar a crew.
from datetime import datetime
topic = "Learning Adaptive Thresholds for LLM Tool Use: When to Rely on Memory and When to Call a Tool"
async def run_crew(topic: str):
"""Run the CrewAI crew asynchronously."""
print(f"Starting crew at {datetime.now().isoformat(timespec='seconds')}")
print(f"Topic: {topic!r}\n")
result = await crew.kickoff_async(inputs={"topic": topic})
print(f"\nFinished at {datetime.now().isoformat(timespec='seconds')}")
print(f"Result type: {type(result).__name__}")
return result
result = await run_crew(topic)
Quando o fluxo começa, o CrewAI primeiro entrega a tarefa ao agente Gerente. O Gerente então delega a pesquisa ao Senior Research Analyst. O Pesquisador usa a ferramenta olostep_web_search para buscar informações na web sobre limiares adaptativos, uso de ferramentas por LLMs, memória e decisões de quando chamar uma ferramenta.

No log, o Pesquisador usa tanto chamadas de busca sem scraping quanto chamadas com scraping. O orçamento de busca é usado por completo, chegando a 6 de 6 chamadas externas e 3 de 3 buscas de descoberta. Uma chamada extra só de busca é bloqueada porque o limite de descoberta já foi atingido. Isso mostra que as regras de orçamento dentro da nossa ferramenta personalizada do Olostep estão funcionando corretamente.

Depois da fase de pesquisa, o Gerente continua o fluxo delegando as etapas de redação e revisão. Os agentes produzem notas de pesquisa, rascunham o artigo e depois lapidam a resposta final. A execução termina com sucesso e retorna um objeto CrewOutput.
Você deve ver algo como:
Starting crew at 2026-06-05T13:54:07
Topic: 'Learning Adaptive Thresholds for LLM Tool Use: When to Rely on Memory and When to Call a Tool'
Crew Execution Started
Task Started
Agent: Content Coordinator
Agent: Senior Research Analyst
Tool: olostep_web_search
Budget used: 6/6 total calls; 3/3 discovery calls
Finished at 2026-06-05T13:59:29
Result type: CrewOutput
Isso significa que todo o swarm rodou com sucesso. O Gerente coordenou o fluxo, o Pesquisador coletou informações atuais, o Redator criou o rascunho e o Revisor melhorou o resultado final.
10. Veja o resultado
Depois que o swarm terminar de rodar, podemos exibir o artigo final gerado pela crew. O valor em result.raw contém a resposta final do fluxo.
from IPython.display import Markdown, display
raw = result.raw
if "Blog Post" in raw:
raw = raw.split("Blog Post", 1)[1].strip()
display(Markdown(raw))
Isso renderiza a saída final como markdown formatado dentro do notebook.
Na maioria dos casos, o blog gerado já sai bem estruturado, com múltiplos headings, seções claras, diagramas ou explicações visuais quando fizer sentido e uma conclusão forte.
A combinação de pesquisa, redação e revisão editorial ajuda a produzir conteúdo pronto para publicação. Depois de revisar o output, eu pessoalmente daria sinal verde para publicar.

Depois, podemos imprimir um trace de execução. Isso ajuda a entender como o swarm trabalhou, quais agentes participaram, quantas chamadas à web foram usadas e como cada tarefa foi processada.
from collections import Counter
def role_name(value):
"""Return a readable role/name for CrewAI agent values."""
if value is None:
return "Not reported"
if isinstance(value, str):
return value
return getattr(value, "role", None) or getattr(value, "name", None) or str(value)
print("=" * 70)
print("SWARM EXECUTION TRACE")
print("=" * 70)
# Hierarchical crew structure
print("\nCOORDINATION")
print(f"Process : {crew.process}")
print(f"Manager : {role_name(crew.manager_agent)}")
print(f"Workers : {', '.join(role_name(agent) for agent in crew.agents)}")
print(
"Flow : Manager receives each task, delegates specialist work, "
"validates the response, and passes approved context to the next task."
)
Now print the web-tool usage recorded by the custom Olostep tool.
history = list(getattr(olostep_search_tool, "_call_history", []))
discovery_calls = sum(not item["scrape"] for item in history)
scrape_calls = sum(item["scrape"] for item in history)
successful_calls = sum(item["status"] == "success" for item in history)
print("\nWEB TOOL USAGE")
print(f"Total external calls : {len(history)}/6")
print(f"Without scraping : {discovery_calls}/3")
print(f"With scraping : {scrape_calls}")
print(f"Successful calls : {successful_calls}")
if history:
for item in history:
mode = "scrape" if item["scrape"] else "search only"
domains = ", ".join(item["domains"]) if item["domains"] else "all domains"
print(
f" {item['number']}. {mode}; status={item['status']}; "
f"results={item['results']}; scope={domains}"
)
print(f" Query: {item['query']}")
else:
print(" No Olostep calls were recorded.")
Finally, print the task-by-task trace.
print("\nTASK-BY-TASK TRACE")
agent_activity = Counter()
for index, task_out in enumerate(result.tasks_output, start=1):
task = crew.tasks[index - 1] if index <= len(crew.tasks) else None
reported_agent = role_name(getattr(task_out, "agent", None))
processed_by = sorted(getattr(task, "processed_by_agents", set()) or [])
participants = processed_by or [reported_agent]
for participant in participants:
agent_activity[role_name(participant)] += 1
delegations = getattr(task, "delegations", 0) if task else 0
used_tools = getattr(task, "used_tools", 0) if task else 0
tool_errors = getattr(task, "tools_errors", 0) if task else 0
start_time = getattr(task, "start_time", None) if task else None
end_time = getattr(task, "end_time", None) if task else None
duration = end_time - start_time if start_time and end_time else None
print(f"\n--- Task {index} ---")
print(f"Reported owner : {reported_agent}")
print(f"Processed by : {', '.join(map(role_name, participants))}")
print(f"Delegations : {delegations}")
print(f"Tool uses : {used_tools}")
print(f"Tool errors : {tool_errors}")
if duration is not None:
print(f"Duration : {duration}")
preview = getattr(task_out, "raw", "").strip().replace("\n", " ")
print(f"Output preview : {preview[:300]}{'...' if len(preview) > 300 else ''}")
print("\nAGENT ACTIVITY")
for agent, count in agent_activity.most_common():
print(f"{agent}: participated in {count} task(s)")
if not agent_activity:
print("CrewAI did not expose per-agent processing metadata for this run.")
Você deve ver algo como:

Esse trace mostra que o swarm hierárquico funcionou como esperado. O Gerente coordenou as três tarefas, o Pesquisador cuidou da pesquisa, o Redator criou o rascunho do artigo e o Revisor melhorou a resposta final.
No entanto, ele também evidencia uma limitação importante: fluxos multiagentes podem sair caros.
Nesta execução, o fluxo usou 6 chamadas externas à web e várias chamadas ao LLM entre Gerente, Pesquisador, Redator e Revisor. Uma única execução pode custar cerca de US$ 0,28, e criar este guia custou por volta de US$ 2,78 nos testes. É alto para um projeto de tutorial simples.

Considerações finais
Agent swarms e fluxos multiagentes ficam ótimos no papel. A ideia de ter um Pesquisador, um Redator, um Revisor e um Gerente trabalhando juntos é poderosa — e, neste guia, o output final realmente ficou bom o suficiente para publicar com ajustes mínimos.
Mas, na prática, essa configuração pode custar mais, levar mais tempo e criar mais pontos de falha. No meu caso, uma execução sai por cerca de US$ 0,28, e criar este guia custou cerca de US$ 2,78. É bastante para um tutorial simples.
Para aplicações reais, eu nem sempre usaria um swarm multiagente completo. Prefiro uma configuração mais programática, na qual tarefas simples são tratadas por código ou regras, e apenas as complexas são delegadas a agentes. Também dá para reduzir custos limitando chamadas de ferramentas, usando menos agentes, encurtando prompts e adotando um fluxo linear em vez de hierárquico.
Claro que, ao otimizar demais, talvez deixe de ser um agent swarm de verdade — vira algo mais parecido com um workflow de IA baseado em regras. Por isso, o objetivo é equilíbrio: use swarms quando realmente agregarem valor e mantenha o simples quando não agregarem.

Sou um cientista de dados certificado que gosta de criar aplicativos de aprendizado de máquina e escrever blogs sobre ciência de dados. No momento, estou me concentrando na criação e edição de conteúdo e no trabalho com modelos de linguagem de grande porte.