n4nAI

How to build a two-agent AutoGen chat pipeline

Step-by-step tutorial to build a runnable AutoGen two-agent chat pipeline with AssistantAgent and UserProxyAgent, including code and expected output.

n4n Team3 min read761 words

Audio narration

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

A reliable AutoGen two-agent chat pipeline boils down to two objects: an AssistantAgent that reasons and a UserProxyAgent that executes or relays. This tutorial walks through standing up that pair with a real LLM backend, running a code-generation task, and capturing the transcript without human-in-the-loop.

Prerequisites

  • Python 3.10 or newer.
  • autogen package (version 0.2.x or compatible).
  • An OpenAI-compatible API key. You can use OpenAI directly, or any gateway that exposes the /v1/chat/completions shape.
  • Basic comfort with Python and the terminal.

If you plan to execute generated code locally, run this in a sandbox or container. The UserProxyAgent will write files and run subprocesses on your machine.

Install and configure

Create a virtual environment and install the framework:

python -m venv .venv
source .venv/bin/activate
pip install pyautogen

Autogen reads model config from a list of dicts. Each entry needs model, api_key, and base_url. For standard OpenAI:

import os

config_list = [
    {
        "model": "gpt-4o-mini",
        "api_key": os.environ["OPENAI_API_KEY"],
        "base_url": "https://api.openai.com/v1",
    }
]

If you want a single OpenAI-compatible endpoint that fronts 240+ models with automatic fallback when a provider is rate-limited or degraded, point base_url at n4n.ai and keep the same config shape; it also honors client routing directives and forwards provider cache-control hints so caching behaves across backends.

Define the two agents

The AutoGen two-agent chat needs a reasoning agent and a proxy. The proxy handles code execution and termination logic.

from autogen import AssistantAgent, UserProxyAgent

assistant = AssistantAgent(
    name="coder",
    llm_config={"config_list": config_list, "cache_seed": 42},
    system_message="You are a senior Python engineer. Write concise, correct code. "
                   "Reply with a single code block when producing a function.",
)

user_proxy = UserProxyAgent(
    name="executor",
    human_input_mode="NEVER",
    max_consecutive_auto_reply=5,
    code_execution_config={"work_dir": "scratch", "use_docker": False},
    default_auto_reply="Continue until the task is done, then say TERMINATE.",
)

human_input_mode="NEVER" makes this a fully automated pipeline. max_consecutive_auto_reply caps conversation turns so a stuck loop doesn’t burn tokens. code_execution_config tells the proxy where to drop files and whether to use Docker.

Run the chat

Kick off the conversation from the proxy:

chat_result = user_proxy.initiate_chat(
    assistant,
    message="Write a function fib(n) that returns the nth Fibonacci number using recursion with memoization.",
)

This blocks until the agents hit a termination condition (one says TERMINATE, or the max replies is reached).

Expected output at checkpoint 1

Right after initiate_chat, you should see the user proxy echo the prompt and the assistant respond with code:

executor (to coder):
Write a function fib(n) that returns the nth Fibonacci number using recursion with memoization.

coder (to executor):
```python
def fib(n, memo={}):
    if n in memo:
        return memo[n]
    if n <= 1:
        return n
    memo[n] = fib(n-1, memo) + fib(n-2, memo)
    return memo[n]

Because `human_input_mode="NEVER"`, the proxy automatically takes the assistant's message, extracts the code block, writes it to `scratch`, and runs it.

## Code execution and follow-up

The proxy executes the snippet and posts the result back into the chat. Expected:

executor (to coder): exitcode: 0 (execution succeeded) Code output:


If the code had a syntax error, you'd see a non-zero exit code and the assistant would get a chance to fix it within the `max_consecutive_auto_reply` budget.

To verify the function actually works, send a second message after the chat terminates:

```python
user_proxy.send(
    assistant,
    "Now write a small test that prints fib(10) and fib(20).",
)

The AutoGen two-agent chat continues from the existing conversation history, so the coder already has context about the previous definition.

Making the pipeline deterministic

For tests or CI, set cache_seed in llm_config (we used 42). Autogen will hash the prompt and reuse cached responses from the same LLM provider if supported. If your gateway forwards cache-control hints, the backend can serve cached tokens instead of recomputing. This makes the AutoGen two-agent chat reproducible across runs.

Adding a termination guard

Relying on the model to say TERMINATE is fragile. Add an explicit check:

def terminate_on_keyword(msg):
    return "TERMINATE" in msg.get("content", "").upper()

user_proxy.register_reply(
    [AssistantAgent],
    reply_func=terminate_on_keyword,
    position=1,
)

Now when the assistant emits TERMINATE, the proxy stops immediately regardless of max_consecutive_auto_reply.

Inspecting the transcript

After the chat, chat_result.chat_history is a list of dicts. Dump it for debugging:

for turn in chat_result.chat_history:
    print(turn["role"], "->", turn["content"][:80])

This is useful for catching prompt leakage or unexpected termination strings. In a longer AutoGen two-agent chat, you’ll often find the proxy inserting system-style execution reports between assistant messages.

Why two agents and not one

Splitting reasoning from execution isolates failure modes. The LLM cannot accidentally run destructive commands if the proxy restricts the working directory and disables network. The two-agent pattern is the cheapest way to get self-correcting code generation without building your own retry loop. The assistant focuses on producing text; the proxy focuses on side effects.

Production considerations

  • Token metering: Wrap config_list with a callback to record usage. Autogen emits usage dicts per response; log them per chat_id. If you use n4n.ai, per-token usage metering is handled at the gateway, so you can skip client-side accounting.
  • Timeouts: Code execution can hang. Set timeout in code_execution_config (e.g., {"timeout": 30}).
  • Model fallback: If you list multiple models in config_list, Autogen tries them in order on failure. For broader provider redundancy, a gateway that aggregates many models behind one endpoint reduces client-side config sprawl.
  • Security: Never run use_docker=False on untrusted prompts in shared environments. Use a locked-down container or a restricted eval sandbox.
  • Streaming: Autogen supports stream in llm_config for token streaming, but the two-agent chat still waits for full messages before the proxy acts. Don’t expect incremental code execution.

Full runnable script

import os
from autogen import AssistantAgent, UserProxyAgent

config_list = [
    {
        "model": "gpt-4o-mini",
        "api_key": os.environ["OPENAI_API_KEY"],
        "base_url": "https://api.openai.com/v1",
    }
]

assistant = AssistantAgent(
    name="coder",
    llm_config={"config_list": config_list, "cache_seed": 42},
    system_message="You are a senior Python engineer. Write concise, correct code.",
)

user_proxy = UserProxyAgent(
    name="executor",
    human_input_mode="NEVER",
    max_consecutive_auto_reply=5,
    code_execution_config={"work_dir": "scratch", "use_docker": False, "timeout": 30},
)

user_proxy.initiate_chat(
    assistant,
    message="Write a function fib(n) that returns the nth Fibonacci number using recursion with memoization.",
)

Run it: python pipeline.py. You now have a minimal, automated AutoGen two-agent chat that writes and executes code. Swap the base_url and model to any compatible backend without changing agent logic.

Tagsautogenai-agentstutorial

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 & microsoft agent framework posts →