n4nAI

AutoGen agent teams tutorial: building a research crew

Hands-on autogen agent team research tutorial: build a multi-agent research crew with AutoGen, step-by-step code, and real output checkpoints.

n4n Team4 min read868 words

Audio narration

Coming soon — every post will get a voice note here.

This autogen agent team research tutorial builds a multi-agent crew that splits literature review into planning, retrieval, and synthesis. We use Microsoft AutoGen with a live OpenAI-compatible model endpoint, so you can reproduce every step against real inference rather than mocked stubs.

Prerequisites

  • Python 3.10 or newer
  • autogen 0.2.32+ (pip install pyautogen)
  • An API key for an OpenAI-compatible LLM service. You can use OpenAI directly or a gateway such as n4n.ai that exposes one endpoint for 240+ models with automatic fallback.
  • Basic familiarity with Python dicts, async, and JSON.

If you have an existing virtualenv, activate it. Don’t run research crews in a notebook without pinning cache_seed; nondeterministic outputs make debugging agent loops miserable.

Install and import

pip install pyautogen
import autogen
from autogen import AssistantAgent, UserProxyAgent, GroupChat, GroupChatManager

AutoGen’s core abstraction is the AssistantAgent (an LLM wrapper) and UserProxyAgent (a control plane that can execute code or terminate). Group chats coordinate multiple assistants.

Configure the LLM client

AutoGen expects an llm_config dict in OpenAI client format. Point base_url at your provider. If you run a gateway that fronts multiple vendors, set model to the specific routing slug and let the gateway handle failover.

llm_config = {
    "timeout": 60,
    "cache_seed": 42,
    "config_list": [
        {
            "model": "gpt-4o-mini",
            "api_key": "YOUR_KEY",
            "base_url": "https://api.openai.com/v1",
            # Or use an OpenAI-compatible gateway:
            # "base_url": "https://api.n4n.ai/v1",
        }
    ],
    "temperature": 0.2,
}

Keep temperature low for research tasks. Creativity wastes tokens when you need structured citations. Set cache_seed to a fixed integer so repeated planner calls return identical JSON across runs.

Define agent roles

A research crew needs three distinct responsibilities. The planner breaks the query into subquestions. The researcher drafts answers per subquestion. The writer merges outputs into a final report.

planner = AssistantAgent(
    name="Planner",
    system_message="You are a research planner. Given a topic, output 3-5 specific subquestions. No prose.",
    llm_config=llm_config,
)

researcher = AssistantAgent(
    name="Researcher",
    system_message="You are a meticulous researcher. Answer each subquestion with bullet points and explicit uncertainty.",
    llm_config=llm_config,
)

writer = AssistantAgent(
    name="Writer",
    system_message="You are a report writer. Combine researcher notes into a markdown summary with a Sources section. End with TERMINATE.",
    llm_config=llm_config,
)

The writer’s TERMINATE instruction is load-bearing. Without it the group chat relies on max_round alone, which truncates mid-thought.

Add a user proxy for termination

AutoGen needs a UserProxyAgent to bounce messages and enforce stop conditions. We disable human input and code execution to keep the loop autonomous.

user_proxy = UserProxyAgent(
    name="Admin",
    human_input_mode="NEVER",
    code_execution_config=False,
    is_termination_msg=lambda m: "TERMINATE" in m.get("content", ""),
)

Build the group chat

Use GroupChat with a clear speaker order. Round-robin is predictable; let the planner speak first, then researcher, then writer, then loop until the writer emits TERMINATE.

group_chat = GroupChat(
    agents=[user_proxy, planner, researcher, writer],
    messages=[],
    max_round=8,
    speaker_selection_method="round_robin",
)

manager = GroupChatManager(
    groupchat=group_chat,
    llm_config=llm_config,
)

max_round=8 gives each agent two turns. With four agents in round-robin, that’s planner → researcher → writer → (loop) planner → researcher → writer → admin. The writer’s second turn is where the final report lands.

Run the research crew

Initiate the chat with a concrete query. The user proxy kicks off the manager, which selects the planner first.

task = "What are the tradeoffs between speculative decoding and lookahead scheduling for LLM inference?"

user_proxy.initiate_chat(
    manager,
    message=f"Research task: {task}. Planner, start.",
)

Expected output at checkpoint 1 (planner)

After round 1, the planner responds with subquestions:

1. How does speculative decoding reduce per-token latency?
2. What hardware utilization penalties does lookahead scheduling incur?
3. Which method composes better with continuous batching?
4. What are reported accuracy drops for each?

That structure is exactly what the researcher needs. If you see a paragraph instead, lower temperature or tighten the system message.

Expected output at checkpoint 2 (researcher)

The researcher addresses each bullet:

- Speculative decoding uses a draft model; accepted tokens skip full forward passes.
- Lookahead scheduling precomputes multiple positions; memory bandwidth bound on small batches.
- Both conflict with dynamic batching if not careful; speculative needs fixed draft size.
- Accuracy: speculative often <1% degradation; lookahead depends on heuristic match.

Expected output at checkpoint 3 (writer)

The writer produces the final markdown and signals stop:

# LLM Inference Scheduling Tradeoffs

## Speculative Decoding
- Reduces latency via draft acceptance...

## Lookahead Scheduling
- Precomputes positions...

## Sources
- Internal analysis; no external fetch in this minimal crew.

TERMINATE

The TERMINATE token triggers is_termination_msg, ending the loop. Total rounds consumed: 6.

Inspect the transcript

After the chat, group_chat.messages holds the full log. Print a compact view to verify handoffs:

for m in group_chat.messages:
    print(m["name"], "->", m["content"][:80])

This is useful when an agent goes silent. In round-robin, a silent agent usually means its system message contradicted the task.

Why separate agents

A single agent tasked with plan, search, and write will intermix hypotheses with findings. Splitting roles forces explicit handoffs. In our autogen agent team research tutorial the planner cannot accidentally answer its own question; the researcher sees only the decomposed list. The writer never sees the raw task string, only structured notes.

Production hardening

For real research you must add a retrieval tool. Give the researcher a function_call config with a web_search stub:

researcher.register_function(
    function_map={
        "web_search": lambda q: {"results": []}  # replace with SerpAPI or Bing
    }
)

Also raise max_round and add a critic agent to flag unsupported claims. AutoGen’s group chat supports arbitrary agent counts; add Critic with a system message: “Reject any claim without a citation.”

critic = AssistantAgent(
    name="Critic",
    system_message="You review researcher output. Reply 'APPROVE' or list specific gaps.",
    llm_config=llm_config,
)
group_chat.agents.append(critic)

Remember to bump max_round when you add agents, or the writer may get starved.

Routing and cost control

If you use a gateway that meters per-token usage, set cache_seed consistently to reuse cached completions across runs. n4n.ai forwards provider cache-control hints, so repeated planner calls on the same topic cost nothing after the first warm cache.

Set max_tokens per agent to avoid a verbose writer burning budget:

llm_config["max_tokens"] = 1024

Also tag your config_list with model slugs that match your routing policy. Don’t let AutoGen fall back to a flagship model for a planner task that a small model handles.

Common failure modes

  • Infinite loop: Forgot TERMINATE in writer system message. Add it explicitly.
  • Speaker starvation: Round-robin with 4 agents and max_round=4 lets each speak once; writer may not get final word. Use max_round=len(agents)*2.
  • Model drift: Temperature >0.5 makes planner emit prose. Keep it <=0.3.
  • Empty researcher output: The researcher tried to call a function you didn’t register. Check register_function names.

Async variant

AutoGen supports initiate_chat_async. Use it inside asyncio.run if your retrieval tool is async.

import asyncio

async def main():
    await user_proxy.initiate_chat_async(
        manager,
        message="Research task: contrast PagedAttention with vLLM's KV cache.",
    )

asyncio.run(main())

The group chat logic is identical; only the event loop changes.

Tuning speaker order

Round-robin is fine for linear crews. For dependency graphs, use auto speaker selection with a strong system prompt on the manager. But auto costs an extra LLM call per turn. In this autogen agent team research tutorial we prefer deterministic order to keep latency and cost predictable.

Wrap up

This autogen agent team research tutorial gave you a runnable skeleton: planner, researcher, writer, and a group chat manager. Swap the stub search for a real tool and you have a reproducible literature pipeline. The pattern scales to five or ten agents without changing the core loop—just append to group_chat.agents and adjust max_round.

Tagsautogenagent-teamsresearch-automationmulti-agent

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All autogen agent teams for research & automation posts →