← Back to Blog

AutoGen Multi-Agent System Practice Reflection From Heavy-Weight Contextual Programming to Lightweight AI Assistant

My core objective was to address a key pain point of current AI code assistants: they are often "one-shot" and lack a continuous focus on code quality and subsequent optimization. I wanted my system to simulate a miniature development team.

1.1. System Design: A Trinity of AI Developers

I designed three highly specialized agents:

  1. CoderAgent: Responsible for generating the initial Python code based on user requirements. Its core duty is to implement functionality quickly.
  2. QualityAnalyzerAgent: Responsible for reviewing the code generated by CoderAgent. It uses static analysis tools (like pylint) to check for style issues, potential errors, and non-standard practices, then provides specific modification suggestions.
  3. OptimizerAgent: After the code is functionally correct and meets quality standards, this agent examines it from a higher level, suggesting improvements related to algorithmic efficiency, code structure, and readability.

To enable these three agents to collaborate "intelligently," I chose AutoGen's powerful GroupChat mode. By setting speaker_selection_method to "auto", I expected the system to act like a project manager, automatically selecting the most appropriate agent to speak based on the current conversation context.

1.2. Technical Implementation: Assembling the Team with AutoGen

Here is the core code snippet for the system setup:

import autogen

# Configure the LLM
config_list = autogen.config_list_from_json(...) 
llm_config = {"config_list": config_list}

# 1. Define the agents
coder = autogen.AssistantAgent(
    name="CoderAgent",
    system_message="You are a helpful AI assistant that writes Python code to solve tasks. Return the code in a markdown code block.",
    llm_config=llm_config,
)

quality_analyzer = autogen.AssistantAgent(
    name="QualityAnalyzerAgent",
    system_message="You are a quality assurance expert. You review the given Python code for style, errors, and best practices. Suggest specific improvements.",
    llm_config=llm_config,
)

optimizer = autogen.AssistantAgent(
    name="OptimizerAgent",
    system_message="You are a performance optimization expert. You analyze the Python code for performance bottlenecks and suggest refactoring for better efficiency and readability.",
    llm_config=llm_config,
)

user_proxy = autogen.UserProxyAgent(
    name="UserProxy",
    human_input_mode="TERMINATE",
    code_execution_config={"work_dir": "coding"},
)

# 2. Set up the GroupChat with automatic speaker selection
# Using "auto" mode lets the LLM decide the next speaker
groupchat = autogen.GroupChat(
    agents=[user_proxy, coder, quality_analyzer, optimizer],
    messages=[],
    max_round=15,
    speaker_selection_method="auto" 
)

manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=llm_config)

# 3. Initiate the task
user_proxy.initiate_chat(
    manager,
    message="Write a Python function to find the nth Fibonacci number, then analyze and optimize it."
)

With the speaker_selection_method="auto" setting, my ideal workflow was: UserProxy -> CoderAgent -> QualityAnalyzerAgent -> OptimizerAgent -> UserProxy. It looked perfect, didn't it? However, reality delivered a harsh lesson.

2. The "Heaviness" in Practice: When Idealism Meets Reality

Once the system was running, I quickly felt a persistent sense of 'heaviness.' This feeling wasn't from a single issue but a combination of several factors.

2.1. Interaction Latency and the Efficiency Black Hole

For a simple Fibonacci function, the entire process took several minutes. Each handoff between agents is a complete LLM call. The GroupChat's process for deciding the next speaker also requires an LLM inference of its own. This meant that completing one simple task could involve 5-10, or even more, LLM calls.

In my daily development work, I need code completions and suggestions in seconds, not the result of an AI team "holding a meeting" that I have to wait for after making a cup of coffee. This high latency is fatal for high-frequency, real-time development assistance scenarios.

2.2. Uncontrollable "Emergent Intelligence"

speaker_selection_method="auto" is a double-edged sword. It did introduce 'intelligence,' but it also brought chaos. I observed several typical problems:

  • Dialogue Loops: CoderAgent and QualityAnalyzerAgent could get stuck in a back-and-forth 'tug-of-war,' with one making changes and the other finding new issues, preventing the process from ever reaching the optimization stage.
  • Incorrect Scheduling: Sometimes, right after CoderAgent finished writing the code, OptimizerAgent would 'jump the gun' and start talking about optimization, skipping the quality analysis step and disrupting the intended workflow.
  • Premature Termination: The system might hand control back to the UserProxy and consider the task complete without sufficient optimization.

This unpredictability turned a tool that was supposed to boost efficiency into a 'black box' that required careful guidance and observation.

2.3. Complex State Management and Context Passing

One of the core challenges of a multi-agent system is state management. In this experiment, the 'state' was the piece of code being iterated on. Ideally, QualityAnalyzerAgent should analyze the latest code from CoderAgent.

But the state of a GroupChat is maintained through an ever-growing message history. As the number of conversation rounds increases, the context window expands rapidly. This not only increases token costs but can also cause subsequent agents to 'lose focus' due to information overload, ignoring critical code versions or modification suggestions. I had to meticulously craft prompts, repeatedly reminding agents to "please focus on the code in the previous turn's message," which was a burden in itself.

2.4. High Configuration and Debugging Costs

Building this system required me to spend a significant amount of time on 'meta-work':

  • Prompt Engineering: Writing precise system_message for each agent to define its role, capabilities, and communication style.
  • Flow Design: Thinking about how to design termination conditions and guide the conversation flow.
  • Debugging: When the system didn't behave as expected, I had to read the entire conversation history to guess whether the problem was with an agent's prompt or the selector's decision logic. This debugging difficulty is far greater than with traditional code.

These upfront investment and subsequent maintenance costs are clearly disproportionate for solving a problem at the level of 'writing a Fibonacci function.'

3. Reflection: Which Scenarios Truly Require the "Heavy Artillery"?

This failed attempt was not without value; it gave me a deeper understanding of the nature and application boundaries of multi-agent systems.

The core strengths of multi-agent systems lie in:

  • Specialization and Modularity: The ability to break down a large, ambiguous task and assign parts to 'experts' in different fields, achieving a separation of concerns.
  • Simulating Complex Workflows: They are excellent for simulating real-world processes that require multi-role collaboration, such as product development or scientific research.
  • 'Emergence' and Creativity: Free-form discussions between agents can sometimes lead to unexpected and creative solutions.

So, what scenarios are suitable for this kind of 'heavyweight' system?

  1. Exploratory and Research Tasks: For example, "Investigate the latest advancements in autonomous driving technology and generate an analysis report including a technical summary, key players, and future trends." Such tasks lack a fixed process, require multiple complex steps like information gathering, integration, and analysis, and have a certain demand for creativity in the final output.
  2. End-to-End Automation Projects: For example, "Automatically generate a project skeleton, write core code, and configure deployment scripts based on a user requirements document." These tasks have long cycles, multiple steps, and can be executed asynchronously. A multi-agent system can act like an autonomous project team, working silently in the background.
  3. Complex Decision-Making and Simulation: For example, simulating a market environment where 'Consumer Agents,' 'Competitor Agents,' and 'Marketing Agents' interact to predict the effectiveness of a marketing strategy.

And for the following scenarios, we should decisively opt for a 'lightweight' approach:

  • High-frequency, real-time interactive tasks: Such as code completion, real-time Q&A, or text polishing.
  • Deterministic, linear tasks: If a task can be clearly broken down into A->B->C steps, then forcing it into a free-discussion GroupChat is like using a sledgehammer to crack a nut.
  • Scenarios that are extremely sensitive to latency and cost.

4. Returning to Simplicity: A Blueprint for Lightweight AI Assistants

Since the heavyweight multi-agent system wasn't suitable for my daily development needs, what is a better alternative? The answer is to return to simplicity, leveraging other patterns provided by AutoGen or shifting our mindset.

4.1. Solution 1: Two-Stage Agent Pipeline (Sequential Pipeline)

If your process is deterministic, like 'code first, then review,' you can organize the agents in a sequential manner. AutoGen's register_nested_chats feature is perfect for this scenario.

# This is a conceptual example to demonstrate how to build a sequential pipeline.
# After the CoderAgent completes its task, its result is automatically passed 
# as input to the QualityAnalyzerAgent.

# Assuming CoderAgent and QualityAnalyzerAgent are already defined

# Nested chat setup
review_chat = autogen.GroupChat(
    agents=[quality_analyzer, user_proxy],
    messages=[],
    max_round=2,
    speaker_selection_method="manual" # Or another controllable method
)

# Register the nested chat to form a pipeline
coder.register_nested_chats(
    [{"recipient": quality_analyzer, "message": "Please review the following code.", "summary_method": "last_msg"}],
    trigger=user_proxy,
)

user_proxy.initiate_chat(coder, message="Write a Python function for quick sort.")

In this pattern, the control flow is a deterministic User -> Coder -> QualityAnalyzer. It retains the advantage of agent specialization but eliminates the unpredictability and high coordination cost of the auto-selecting GroupChat.

4.2. Solution 2: Single Agent with Tools

This is a more mainstream and practical paradigm for building AI assistants today, and it's in the same vein as OpenAI's Function Calling/Tool Use.

The core idea is: Instead of creating multiple agents to converse with each other, create one 'omnipotent' AssistantAgent and encapsulate capabilities like 'quality analysis' and 'code optimization' as tools it can call.

import pylint.lint
import io
from pylint.reporters.text import TextReporter

# 1. Define the tool function
def lint_code(code: str) -> str:
    """Runs pylint on the given Python code and returns the report."""
    pylint_opts = ['--disable=all', '--enable=E,W']
    reporter = TextReporter(io.StringIO())
    pylint.lint.Run([io.StringIO(code)], reporter=reporter, exit=False, args=pylint_opts)
    return reporter.out.getvalue()

# 2. Create an agent with tool-calling capabilities
super_assistant = autogen.AssistantAgent(
    name="SuperAssistant",
    system_message="You are a super-assistant for Python development. You can write code and use tools to check its quality.",
    llm_config=llm_config,
)

# 3. Create a UserProxyAgent and register the tool
user_proxy = autogen.UserProxyAgent(
    name="UserProxy",
    human_input_mode="TERMINATE",
    code_execution_config=False, # We aren't executing code, just calling tools
)

user_proxy.register_function(
    function_map={
        "lint_code": lint_code
    }
)

# 4. Let the agent use the tool
# In the LLM's prompt, it will be informed that the lint_code tool is available.
# The LLM will decide when it's appropriate to generate a request to call this tool.

The advantages of this pattern are overwhelming:

  • Low Latency: No communication overhead between multiple agents.
  • High Controllability: The flow is driven by the LLM's decision to call a tool, which is more predictable than a free-form conversation between agents.
  • Easy to Extend and Maintain: Adding new capabilities only requires adding a new tool function, not designing a new agent and its complex interaction logic.

Conclusion: Finding the Balance Between Complexity and Practicality

My journey from ambitious design to pragmatic retreat taught me a profound lesson: the first principle of technology selection is always 'fitness for purpose.' Multi-agent systems are a powerful and fascinating paradigm, but they are not a silver bullet for every problem. To chase a 'cool-looking' architecture while ignoring real-world efficiency, cost, and controllability is a classic case of technical self-indulgence.

For those of us building AI applications, our goal shouldn't be to build the most complex system, but the one that best solves the problem at hand. Within a powerful framework like AutoGen, GroupChat is just one of many tools. Learning to make wise choices between 'multi-agent collaboration,' 'sequential pipelines,' and 'single-agent + tools' based on the nature of the task is the hallmark of a mature AI engineer.

In the future, collaboration between humans and AI, and between AI and AI, will undoubtedly deepen. Our task is to maintain a clear head amidst the constant emergence of new technologies and find that optimal balance point between technology and value.