Building a Multi-Agent System with AutoGen: Complete Guide
Multi-agent systems represent one of the most exciting frontiers in AI development today. Instead of relying on a single LLM to handle every aspect of a complex task, you can orchestrate multiple specialized agents that collaborate, debate, and build on each other's outputs. Microsoft's AutoGen framework has emerged as one of the most powerful tools for building these systems, offering a flexible, conversation-driven approach to agent coordination. In this guide, you'll learn what AutoGen is, why multi-agent architectures matter, and how to build a working multi-agent system from scratch.
What Is AutoGen?
AutoGen is an open-source framework developed by Microsoft Research for building multi-agent conversational applications. At its core, AutoGen treats agents as conversational entities that exchange messages to solve problems. Each agent has a defined role, a set of capabilities, and the ability to communicate with other agents through a structured conversation flow.
An agent in AutoGen is essentially a configurable LLM-powered entity with a system prompt, a set of tools, and rules about how and when it should respond. The framework provides several built-in agent types and a flexible API for creating custom ones. Agents can be humans (for human-in-the-loop workflows), AI assistants, or specialized executors that run code, query databases, or call external APIs.
Why Multi-Agent Systems Matter
Single-agent systems, while useful, run into limitations when tasks become complex. A single prompt trying to handle research, coding, testing, and review simultaneously tends to produce shallow results. Multi-agent systems address this through specialization and collaboration.
The key benefits include:
- Specialization: Each agent focuses on one task, producing higher-quality outputs.
- Separation of concerns: Different agents handle different stages of a pipeline, making the system easier to debug and extend.
- Built-in verification: Agents can review and critique each other's work, catching errors that a single agent might miss.
- Scalability: You can add new agents with new capabilities without rewriting existing logic.
- Human-in-the-loop integration: Some agents can be human reviewers, allowing seamless oversight of automated workflows.
Installing AutoGen
AutoGen is available as a Python package. You'll need Python 3.8 or higher. Install it along with the dependencies you'll need for code execution:
pip install "autogen-agentchat" "autogen-ext[openai]" python-dotenv
You'll also need an OpenAI API key. Store it in a .env file or export it as an environment variable:
OPENAI_API_KEY=sk-your-api-key-here
Core Concepts: Agents and Conversations
Before diving into code, it's important to understand the building blocks. AutoGen's architecture revolves around a few key concepts:
- ConversableAgent: The base class for all agents. It can send and receive messages, use LLMs, and execute code.
- AssistantAgent: A subclass of ConversableAgent designed to act as an AI assistant that uses LLMs to generate responses.
- UserProxyAgent: An agent that represents the human user. It can execute code, call functions, and prompt the human for input.
- GroupChat: A manager that coordinates multiple agents in a shared conversation.
- GroupChatManager: The agent that actually runs the GroupChat, deciding which agent speaks next.
Building Your First Multi-Agent System
Let's build a practical example: a software development team with three agents. One agent writes code, another reviews it, and a third runs tests. This mirrors a real-world development workflow and demonstrates how agents can collaborate.
First, set up your configuration and create the agents:
import os
from dotenv import load_dotenv
from autogen import ConversableAgent, AssistantAgent, UserProxyAgent, GroupChat, GroupChatManager
load_dotenv()
# LLM configuration
llm_config = {
"config_list": [
{
"model": "gpt-4",
"api_key": os.getenv("OPENAI_API_KEY"),
}
],
"temperature": 0.7,
}
# Agent 1: The Developer
developer = AssistantAgent(
name="Developer",
system_message=(
"You are a senior Python developer. "
"You write clean, efficient, well-documented code. "
"When given a task, produce a complete implementation. "
"Always include type hints and docstrings. "
"When you are done, say 'CODE COMPLETE'."
),
llm_config=llm_config,
)
# Agent 2: The Code Reviewer
reviewer = AssistantAgent(
name="Reviewer",
system_message=(
"You are a meticulous code reviewer. "
"Examine code for bugs, security issues, performance problems, "
"and adherence to best practices. "
"Provide specific, actionable feedback. "
"If the code is acceptable, say 'APPROVED'. "
"Otherwise, explain what needs to change."
),
llm_config=llm_config,
)
# Agent 3: The Tester
tester = AssistantAgent(
name="Tester",
system_message=(
"You are a QA engineer. "
"Write pytest test cases for the code provided. "
"Include edge cases and error scenarios. "
"Present the tests in a single code block. "
"When done, say 'TESTS COMPLETE'."
),
llm_config=llm_config,
)
# User proxy to initiate the conversation
user_proxy = UserProxyAgent(
name="User",
human_input_mode="NEVER",
max_consecutive_auto_reply=10,
is_termination_msg=lambda msg: "APPROVED" in (msg.get("content", "") or "").upper()
and "TESTS COMPLETE" in str([
m.get("content", "") for m in groupchat.messages
]).upper(),
code_execution_config=False,
)
Now, set up the group chat and start the workflow:
# Create the group chat
groupchat = GroupChat(
agents=[user_proxy, developer, reviewer, tester],
messages=[],
max_round=15,
speaker_selection_method="auto",
)
manager = GroupChatManager(
groupchat=groupchat,
llm_config=llm_config,
)
# Start the conversation
user_proxy.initiate_chat(
manager,
message=(
"Build a Python function that validates email addresses "
"using regex. It should return True for valid emails and "
"False for invalid ones. Handle edge cases like empty strings "
"and None values."
),
)
When you run this, the Developer writes the function, the Reviewer examines it, the Tester writes tests, and they iterate until the code is approved. The speaker_selection_method="auto" setting lets the LLM decide which agent should speak next based on the conversation context.
Adding Code Execution Capabilities
One of AutoGen's most powerful features is the ability for agents to actually execute code. Let's enhance the Tester agent so it can run the tests it writes:
tester_with_execution = UserProxyAgent(
name="Tester",
human_input_mode="NEVER",
system_message=(
"You are a QA engineer. Write pytest test cases for the "
"code provided, then execute them. Report the results. "
"If tests fail, suggest fixes."
),
max_consecutive_auto_reply=10,
code_execution_config={
"work_dir": "workspace",
"use_docker": False,
},
llm_config=llm_config,
)
The work_dir parameter specifies where the agent will write and execute files. Setting use_docker to False runs code directly on your machine; for production use, you should enable Docker isolation for safety.
Using Custom Tools and Functions
Agents become much more powerful when they can call external functions. AutoGen makes this straightforward with function registration. Here's how to give an agent the ability to search a knowledge base:
from autogen import register_function
# Define a tool function
def search_documentation(query: str) -> str:
"""Search the project documentation for a given query.
Args:
query: The search term or question.
Returns:
Relevant documentation snippets.
"""
# In a real app, this would query a vector database
mock_results = {
"email validation": "Use re.match with a pattern like r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'",
"error handling": "Wrap risky operations in try/except blocks and log exceptions.",
}
for key, value in mock_results.items():
if key in query.lower():
return f"Documentation found: {value}"
return "No documentation found for that query."
# Register the function with the Developer agent
register_function(
search_documentation,
caller=developer,
executor=user_proxy,
name="search_documentation",
description="Search project documentation for relevant information.",
)
Now, when the Developer agent needs information, it can call search_documentation automatically. The function's docstring is crucial—AutoGen uses it to help the LLM understand when and how to use the tool.
Controlling Conversation Flow
The speaker_selection_method parameter gives you control over how agents take turns. You have several options:
- "auto": The LLM selects the next speaker based on conversation context.
- "round_robin": Agents speak in the order they were added to the group chat.
- "manual": You provide a custom function that returns the next speaker.
- "random": A random agent is selected each round.
For more control, you can implement a custom speaker selection function:
def custom_speaker_selection(last_speaker, groupchat):
"""Custom logic for selecting the next speaker."""
messages = groupchat.messages
if not messages:
return developer
last_message = messages[-1].get("content", "")
# If the developer just finished, send to reviewer
if last_speaker.name == "Developer" and "CODE COMPLETE" in last_message:
return reviewer
# If the reviewer approved, send to tester
if last_speaker.name == "Reviewer" and "APPROVED" in last_message:
return tester
# If the reviewer requested changes, send back to developer
if last_speaker.name == "Reviewer":
return developer
# If the tester finished, send to reviewer for final check
if last_speaker.name == "Tester" and "TESTS COMPLETE" in last_message:
return reviewer
# Default: let the manager decide
return None
groupchat = GroupChat(
agents=[user_proxy, developer, reviewer, tester],
messages=[],
max_round=20,
speaker_selection_method=custom_speaker_selection,
)
This gives you precise control over the workflow, ensuring agents follow a logical sequence rather than relying on the LLM's judgment.
Nested Chats for Complex Workflows
For more complex scenarios, AutoGen supports nested chats—conversations within conversations. This is useful when an agent needs to perform a sub-task that requires its own multi-agent interaction. For example, the Developer agent could spawn a nested chat with a Researcher and an Architect before writing code:
researcher = AssistantAgent(
name="Researcher",
system_message="You research best practices and design patterns for the given task.",
llm_config=llm_config,
)
architect = AssistantAgent(
name="Architect",
system_message="You design the solution architecture based on research findings.",
llm_config=llm_config,
)
nested_chat_queue = [
{
"recipient": researcher,
"message": "Research best practices for this task.",
"max_turns": 3,
},
{
"recipient": architect,
"message": "Design the solution based on the research.",
"max_turns": 3,
},
]
# Register nested chats with the Developer
developer.register_nested_chats(
nested_chat_queue,
trigger=lambda sender: True, # Trigger on any incoming message
)
When the Developer receives a task, it first consults with the Researcher and Architect in private nested conversations, then uses their insights to produce better code.
Handling Termination Conditions
Controlling when a conversation ends is critical. Without proper termination conditions, agents can loop indefinitely. AutoGen provides several mechanisms:
# Termination based on message content
def is_termination_msg(msg):
content = msg.get("content", "") or ""
return "TASK COMPLETE" in content.upper()
user_proxy = UserProxyAgent(
name="User",
human_input_mode="NEVER",
is_termination_msg=is_termination_msg,
max_consecutive_auto_reply=15,
code_execution_config=False,
)
# The GroupChat also has max_round to cap total messages
groupchat = GroupChat(
agents=[user_proxy, developer, reviewer, tester],
messages=[],
max_round=30, # Hard limit on conversation length
speaker_selection_method="auto",
)
Always set a max_round value as a safety net, even if you have content-based termination. This prevents runaway conversations from consuming excessive API credits.
Best Practices for Production Systems
As you move from prototypes to production, several practices will help you build robust, maintainable multi-agent systems:
- Use clear, specific system prompts. The system prompt is the primary way to shape an agent's behavior. Be explicit about the agent's role, output format, and when it should defer to other agents.
- Keep agent responsibilities narrow. An agent that tries to do too much will do everything poorly. Split complex roles into multiple specialized agents.
- Implement proper termination conditions. Always combine content-based termination with a max_round limit to prevent infinite loops.
- Log all conversations. Save the full message history for debugging and auditing. AutoGen supports logging through Python's logging module.
- Use Docker for code execution. Never let agents execute arbitrary code on your host machine in production. Docker isolation prevents security risks.
- Cache LLM responses. Use AutoGen's built-in caching to avoid redundant API calls during development and testing.
- Monitor token usage. Multi-agent conversations can consume tokens quickly. Track usage and set budgets to control costs.
- Test agent interactions thoroughly. Edge cases in conversation flow can cause unexpected behavior. Test with various inputs and verify termination works correctly.
Here's an example of enabling caching and logging:
import logging
from autogen import Cache
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Enable caching to reduce API costs during development
with Cache.disk(cache_path=".autogen_cache") as cache:
user_proxy.initiate_chat(
manager,
message="Build a function that validates email addresses.",
cache=cache,
)
Conclusion
AutoGen provides a powerful, flexible framework for building multi-agent systems that can tackle complex tasks through collaboration. By combining specialized agents with structured conversation flows, custom tools, and proper termination conditions, you can create AI systems that are far more capable than any single-agent approach. Start with simple two-agent setups to understand the dynamics, then gradually add agents and complexity as you become comfortable with the framework. Remember that the quality of your system prompts and the clarity of your agent roles will have the biggest impact on the quality of results. With the patterns and practices covered in this guide, you're well-equipped to build production-grade multi-agent applications that leverage the full power of collaborative AI.