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.
autogenpackage (version 0.2.x or compatible).- An OpenAI-compatible API key. You can use OpenAI directly, or any gateway that exposes the
/v1/chat/completionsshape. - 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_listwith a callback to record usage. Autogen emitsusagedicts per response; log them perchat_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
timeoutincode_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=Falseon untrusted prompts in shared environments. Use a locked-down container or a restricted eval sandbox. - Streaming: Autogen supports
streaminllm_configfor 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.