What Are AutoGen Conversable Agents?
AutoGen Conversable Agents are the fundamental building blocks of the Microsoft AutoGen framework. They are programmable entities designed to participate in multi-agent conversations, capable of sending and receiving messages, executing tools, and collaborating with other agents to solve complex tasks. At their core, Conversable Agents abstract away the complexity of LLM interaction, tool orchestration, and conversation state management into a clean, extensible Python interface.
The ConversableAgent class serves as the base class from which specialized agents like AssistantAgent, UserProxyAgent, and custom agents are derived. Every agent in AutoGen inherits from this foundational class, which provides the essential mechanisms for message generation, reply handling, and conversation lifecycle management.
Unlike traditional single-turn LLM pipelines where you send a prompt and receive a response, Conversable Agents engage in sustained, multi-turn dialogues. They can reflect on previous messages, request clarifications, invoke external functions, and even debate solutions with peer agents — all while maintaining coherent conversational context.
Why Conversable Agents Matter
In modern AI application development, single-prompt interactions are insufficient for solving complex, multi-step problems. Conversable Agents address several critical challenges:
1. Enabling Complex Workflows Through Conversation
When a coding agent needs to write software, it doesn't just generate code in one shot. It needs to understand requirements, write a draft, receive feedback from a code reviewer agent, iterate on fixes, and verify tests pass. Conversable Agents make this natural back-and-forth possible by formalizing conversation patterns between AI entities.
2. Separation of Concerns
By assigning different roles to different agents — one for planning, another for execution, a third for validation — you decompose complex problems into manageable units. Each agent can have its own system prompt, LLM configuration, and tool set, allowing for specialized behavior that mirrors how human teams collaborate.
3. Human-in-the-Loop Integration
The UserProxyAgent subclass acts as a bridge between AI agents and human users. It can pause execution, solicit human input, and relay that feedback back into the agent conversation. This pattern is invaluable for high-stakes workflows where human oversight is required before critical actions are taken.
4. Extensibility and Custom Logic
Because Conversable Agents expose hooks like register_reply and register_function, developers can inject custom business logic, connect to external APIs, or implement domain-specific reasoning without modifying the core agent loop. This makes the framework suitable for everything from automated customer support to scientific research assistants.
Setting Up Your Environment
Before creating your first Conversable Agent, you need to install AutoGen and configure an LLM provider. Here's how to get started:
# Create a virtual environment (recommended)
python -m venv autogen-env
source autogen-env/bin/activate # On Windows: autogen-env\Scripts\activate
# Install AutoGen with OpenAI support
pip install pyautogen
# For local models, you may also want:
pip install pyautogen[local]
Next, configure your API credentials. AutoGen reads from environment variables or an OAI_CONFIG_LIST. Create a configuration file or set environment variables:
# .env file or environment variables
export OPENAI_API_KEY="your-openai-api-key"
export AZURE_OPENAI_API_KEY="your-azure-key" # If using Azure OpenAI
Alternatively, create a JSON configuration list for more granular control over model selection:
# config_list.json
[
{
"model": "gpt-4",
"api_key": "your-api-key-here",
"api_type": "openai",
"temperature": 0.7,
"max_tokens": 2048
},
{
"model": "gpt-3.5-turbo",
"api_key": "your-api-key-here",
"api_type": "openai",
"temperature": 0.2,
"max_tokens": 1024
}
]
Core Concepts of Conversable Agents
Before diving into code, let's understand the essential components that make Conversable Agents work:
The Agent Lifecycle
A Conversable Agent follows a structured lifecycle. It receives a message, processes it through its internal pipeline, optionally invokes tools or LLM calls, and generates a reply. This cycle repeats until a termination condition is met — either a maximum number of turns, a specific keyword, or a custom stopping function.
Message Structure
All communication between agents uses a standardized dictionary format with keys like "content", "role", and "name". This structure ensures interoperability between different agent types and allows the conversation history to be passed seamlessly to LLM APIs.
{
"content": "The task is to write a Python function that calculates Fibonacci numbers.",
"role": "user",
"name": "UserProxyAgent"
}
Reply Functions and Registration
Agents don't just generate responses arbitrarily. They use a system of reply functions — registered callbacks that determine how the agent should respond based on the incoming message. The register_reply method allows you to chain multiple reply functions, creating sophisticated response pipelines where one function's output feeds into the next.
Tool Integration via Function Registration
Conversable Agents can execute Python functions as tools. By registering a function with register_function, you give the agent the ability to call that function when it determines it's needed. The function's signature, docstring, and return type are automatically converted into an LLM-compatible tool description, enabling the model to decide when and how to invoke it.
Your First Conversable Agent
Let's create a minimal working example that demonstrates the core pattern. We'll build two agents — an assistant powered by an LLM and a user proxy that acts as the human interface — and have them collaborate on a simple task.
import autogen
import os
# Configure the LLM
config_list = [
{
"model": "gpt-4",
"api_key": os.environ.get("OPENAI_API_KEY"),
"temperature": 0.5,
"max_tokens": 1024
}
]
# Step 1: Create an AssistantAgent - the LLM-powered conversational agent
assistant = autogen.AssistantAgent(
name="CodeAssistant",
system_message="You are a helpful Python coding assistant. When asked to write code, "
"provide complete, well-commented solutions with error handling. "
"Always explain your approach before showing the code.",
llm_config={
"config_list": config_list,
"seed": 42 # For reproducibility
}
)
# Step 2: Create a UserProxyAgent - acts as the human user and code executor
user_proxy = autogen.UserProxyAgent(
name="UserProxy",
system_message="A human user who needs coding help.",
code_execution_config={
"work_dir": "coding_workspace",
"use_docker": False # Set to True if you have Docker installed for sandboxed execution
},
human_input_mode="NEVER", # For fully automated mode; use "TERMINATE" to allow human input
max_consecutive_auto_reply=5
)
# Step 3: Initiate the conversation with a task
user_proxy.initiate_chat(
assistant,
message="Write a Python function that generates the first n prime numbers using the Sieve "
"of Eratosthenes algorithm. Include a docstring and a usage example in main."
)
# The conversation runs automatically until termination
print("\nConversation complete! Check the 'coding_workspace' directory for generated files.")
This example demonstrates the fundamental pattern. The UserProxyAgent initiates a conversation with the AssistantAgent by sending a task description. The assistant processes this message, generates a response with code, and sends it back. The user proxy receives the code, executes it in the designated workspace, and feeds any execution results back into the conversation. This loop continues until the assistant signals completion or the maximum turn limit is reached.
Building Multi-Agent Conversations
The true power of Conversable Agents emerges when you orchestrate multiple specialized agents working together. Here's a more advanced example that simulates a software development team: a product manager clarifies requirements, a developer writes code, and a reviewer checks quality.
import autogen
import os
# Shared LLM configuration
llm_config = {
"config_list": [
{
"model": "gpt-4",
"api_key": os.environ.get("OPENAI_API_KEY"),
"temperature": 0.3
}
],
"timeout": 120
}
# Agent 1: Product Manager - clarifies requirements
product_manager = autogen.AssistantAgent(
name="ProductManager",
system_message="You are a technical product manager. Your role is to clarify ambiguous "
"requirements, break down tasks into clear specifications, and ensure the "
"developer has everything needed. Do NOT write code yourself. When you are "
"satisfied the requirements are clear, reply with 'APPROVED' and the final spec.",
llm_config=llm_config
)
# Agent 2: Developer - writes the actual code
developer = autogen.AssistantAgent(
name="Developer",
system_message="You are an expert Python developer. Write clean, efficient, well-tested code. "
"Follow PEP 8 standards. Include type hints, docstrings, and error handling. "
"When your code is complete and tested, reply with 'CODE_COMPLETE' and explain "
"your implementation decisions.",
llm_config=llm_config
)
# Agent 3: Code Reviewer - checks for issues
reviewer = autogen.AssistantAgent(
name="CodeReviewer",
system_message="You are a thorough code reviewer. Check for: security vulnerabilities, "
"edge cases, performance issues, code style violations, and missing tests. "
"Be specific about what needs to change. If the code passes review, reply "
"with 'LGTM' (Looks Good To Me).",
llm_config=llm_config
)
# User Proxy - executes code and provides feedback
user_proxy = autogen.UserProxyAgent(
name="UserProxy",
code_execution_config={
"work_dir": "team_workspace",
"use_docker": False
},
human_input_mode="NEVER",
max_consecutive_auto_reply=10
)
# Define a custom conversation flow using GroupChat
group_chat = autogen.GroupChat(
agents=[product_manager, developer, reviewer, user_proxy],
messages=[],
max_round=15,
speaker_selection_method="auto", # LLM decides who speaks next
allow_repeat_speaker=False
)
# Create the group chat manager
manager = autogen.GroupChatManager(
groupchat=group_chat,
llm_config=llm_config
)
# Kick off the multi-agent collaboration
user_proxy.initiate_chat(
manager,
message="We need a Python module that implements a rate-limited API client. It should "
"support exponential backoff, circuit breaker pattern, and request queuing. "
"The client must be thread-safe and work with async/await syntax. Target: "
"a generic REST API wrapper usable with any HTTP method."
)
# The conversation proceeds with agents taking turns automatically
print("Multi-agent development session started...")
In this setup, the GroupChat and GroupChatManager orchestrate the conversation. The manager dynamically selects which agent should speak next based on the conversation context. The product manager clarifies the requirements first, then hands off to the developer. The developer writes code, which the user proxy executes. If errors occur, they're fed back into the conversation. The reviewer then examines the solution, and the cycle continues until all agents are satisfied.
Agent Configuration Deep Dive
The flexibility of Conversable Agents comes from their rich configuration options. Let's explore the key parameters that control agent behavior:
LLM Configuration (llm_config)
This dictionary controls how the agent interacts with language models. Beyond the basic config_list, you can specify advanced settings:
llm_config = {
"config_list": [
{
"model": "gpt-4-1106-preview",
"api_key": os.environ["OPENAI_API_KEY"],
"api_type": "openai",
"base_url": "https://api.openai.com/v1",
"temperature": 0.7,
"max_tokens": 4096,
"top_p": 0.9,
"frequency_penalty": 0.1,
"presence_penalty": 0.1,
"timeout": 60
}
],
"seed": 123,
"request_timeout": 120,
"retry_wait_time": 5, # Seconds between retries
"max_retry_period": 300 # Maximum total retry time
}
Code Execution Configuration
For UserProxyAgent, the code_execution_config dict controls how and where code runs:
code_execution_config = {
"work_dir": "sandbox", # Directory for generated files
"use_docker": True, # Isolate execution in Docker
"docker_image": "python:3.11", # Specific Docker image
"timeout": 30, # Max execution time in seconds
"last_n_messages": 3, # How many recent messages to include for context
"functions": ["execute_code", "execute_shell_command"]
}
Human Input Modes
The human_input_mode parameter determines how the agent interacts with human users. This is crucial for designing appropriate oversight levels:
# Fully automated - no human intervention
agent_auto = autogen.UserProxyAgent(
name="AutoAgent",
human_input_mode="NEVER"
)
# Always ask for human input on every turn
agent_manual = autogen.UserProxyAgent(
name="ManualAgent",
human_input_mode="ALWAYS"
)
# Ask only when the agent says "TERMINATE" or encounters an error
agent_semi = autogen.UserProxyAgent(
name="SemiAutoAgent",
human_input_mode="TERMINATE",
# Custom function to determine when to ask for input
is_termination_msg=lambda x: "FINAL_ANSWER" in str(x.get("content", ""))
)
Custom Reply Functions
You can override or extend how agents generate responses by registering custom reply functions. This is where Conversable Agents truly shine for custom applications:
import autogen
from typing import Union, Dict, List
# Define a custom reply function
def custom_reply_function(
agent: autogen.ConversableAgent,
messages: List[Dict],
sender: autogen.Agent,
config: Union[Dict, None] = None
) -> Union[str, Dict, None]:
"""
Custom reply logic that logs incoming messages and adds a prefix.
Only triggers for messages from specific senders.
"""
last_message = messages[-1] if messages else {}
# Check if this reply function should activate
if sender.name == "DataAnalyzerAgent":
# Log the incoming message
print(f"[CUSTOM REPLY] Received from {sender.name}: {last_message.get('content', '')[:100]}...")
# Generate a custom response
response = f"ANALYSIS COMPLETE: Based on the data provided by {sender.name}, "
response += "I've processed the request and will now provide my assessment."
# Return False, the response to keep this reply function in the chain
return False, {"role": "assistant", "content": response}
# Return True, None to skip this reply function and try the next one
return True, None
# Register the custom reply function on an agent
analyst_agent = autogen.AssistantAgent(
name="BusinessAnalyst",
system_message="You analyze business data and provide insights.",
llm_config=llm_config
)
# Register with a specific position in the reply chain
analyst_agent.register_reply(
trigger=autogen.ConversableAgent, # Trigger for any agent type
reply_func=custom_reply_function,
position=0, # First in the reply chain
config=None # Optional config passed to the function
)
print("Custom reply function registered successfully")
Function Registration and Tool Calling
One of the most powerful features of Conversable Agents is the ability to register Python functions as callable tools. The agent automatically converts function signatures into LLM-compatible tool definitions, enabling the model to invoke them autonomously.
import autogen
import json
from datetime import datetime
from typing import Dict, List
# Define tool functions with clear docstrings (these become the tool descriptions)
def fetch_weather(city: str, units: str = "metric") -> Dict:
"""
Fetch current weather data for a given city.
Args:
city: Name of the city (e.g., 'London', 'Tokyo')
units: Temperature units - 'metric' for Celsius, 'imperial' for Fahrenheit
Returns:
Dictionary with temperature, humidity, and conditions
"""
# Simulated weather API call
weather_data = {
"London": {"temp": 12, "humidity": 78, "conditions": "Partly cloudy"},
"Tokyo": {"temp": 18, "humidity": 65, "conditions": "Clear sky"},
"New York": {"temp": 22, "humidity": 55, "conditions": "Sunny"},
"Sydney": {"temp": 25, "humidity": 70, "conditions": "Light rain"}
}
result = weather_data.get(city, {"temp": 20, "humidity": 60, "conditions": "Unknown"})
result["city"] = city
result["units"] = units
result["timestamp"] = datetime.now().isoformat()
return result
def search_database(query: str, table: str = "users", limit: int = 10) -> List[Dict]:
"""
Search a simulated database with SQL-like query capabilities.
Args:
query: Search term to look for
table: Database table to search in
limit: Maximum number of results to return
Returns:
List of matching records as dictionaries
"""
# Simulated database
mock_db = {
"users": [
{"id": 1, "name": "Alice Johnson", "email": "alice@example.com", "role": "admin"},
{"id": 2, "name": "Bob Smith", "email": "bob@example.com", "role": "user"},
{"id": 3, "name": "Charlie Brown", "email": "charlie@example.com", "role": "user"},
],
"orders": [
{"id": 101, "product": "Widget", "quantity": 5, "status": "shipped"},
{"id": 102, "product": "Gadget", "quantity": 2, "status": "pending"},
]
}
records = mock_db.get(table, [])
# Simple string matching search
results = [r for r in records if query.lower() in str(r).lower()]
return results[:limit]
def send_email_notification(to_email: str, subject: str, body: str, priority: str = "normal") -> Dict:
"""
Send an email notification (simulated).
Args:
to_email: Recipient email address
subject: Email subject line
body: Email body content
priority: 'low', 'normal', or 'high'
Returns:
Status dictionary with delivery confirmation
"""
return {
"status": "sent",
"to": to_email,
"subject": subject,
"priority": priority,
"sent_at": datetime.now().isoformat(),
"message_id": f"msg_{hash(to_email + subject) % 10000}"
}
# Create the agent with LLM configuration
tool_agent = autogen.AssistantAgent(
name="ToolAgent",
system_message="You are an assistant with access to weather, database, and email tools. "
"Use these tools to answer user queries. Always cite which tool you used "
"and explain the results in natural language.",
llm_config={
"config_list": [
{
"model": "gpt-4",
"api_key": os.environ.get("OPENAI_API_KEY"),
"temperature": 0.2
}
]
}
)
# Register functions as tools
autogen.register_function(
fetch_weather,
caller=tool_agent,
executor=user_proxy_agent, # The agent that actually executes the function
description="Fetch current weather conditions for a city"
)
autogen.register_function(
search_database,
caller=tool_agent,
executor=user_proxy_agent,
description="Search the database for records matching a query"
)
autogen.register_function(
send_email_notification,
caller=tool_agent,
executor=user_proxy_agent,
description="Send an email notification to a recipient"
)
print("Functions registered. ToolAgent can now call: fetch_weather, search_database, send_email_notification")
# Initiate a conversation that triggers tool usage
user_proxy = autogen.UserProxyAgent(
name="UserProxy",
human_input_mode="NEVER",
code_execution_config={"work_dir": "tools_output"}
)
user_proxy.initiate_chat(
tool_agent,
message="What's the weather like in Tokyo right now? Also, find all users in the database "
"with the role 'admin', and send them an email about a system update."
)
The magic here is that the LLM-powered agent reads the function docstrings, understands what each tool does, and autonomously decides which functions to call and in what order. The executor parameter specifies which agent actually runs the function — typically the UserProxyAgent since it has code execution capabilities. The results are fed back into the conversation, allowing the assistant to incorporate real data into its responses.
Advanced Patterns: Nested Chats and Sequential Workflows
For complex scenarios, AutoGen supports nested conversations where one agent can spawn a sub-conversation with another agent, wait for its completion, and then resume the main conversation. This pattern is invaluable for hierarchical task decomposition.
import autogen
import os
llm_config = {
"config_list": [{"model": "gpt-4", "api_key": os.environ.get("OPENAI_API_KEY")}]
}
# Master Planner Agent
planner = autogen.AssistantAgent(
name="Planner",
system_message="You are a strategic planner. Break complex tasks into subtasks and delegate "
"each subtask to a specialist. After gathering all specialist outputs, synthesize "
"a final comprehensive answer.",
llm_config=llm_config
)
# Specialist Agents
data_analyst = autogen.AssistantAgent(
name="DataAnalyst",
system_message="You specialize in data analysis. Provide statistical insights, identify trends, "
"and present findings with concrete numbers.",
llm_config=llm_config
)
writer = autogen.AssistantAgent(
name="ContentWriter",
system_message="You are a professional technical writer. Take analytical findings and transform "
"them into clear, engaging prose suitable for a business audience.",
llm_config=llm_config
)
fact_checker = autogen.AssistantAgent(
name="FactChecker",
system_message="You verify factual claims. Cross-reference statements, flag inaccuracies, "
"and provide corrections with authoritative sources.",
llm_config=llm_config
)
# User Proxy
user_proxy = autogen.UserProxyAgent(
name="UserProxy",
human_input_mode="NEVER",
max_consecutive_auto_reply=10
)
# Register nested chat capability
# The planner can initiate sub-conversations with specialists
def create_nested_chat_for_specialist(specialist_agent, task_description):
"""Helper to create a nested chat configuration"""
return {
"recipient": specialist_agent,
"message": task_description,
"max_turns": 3,
"summary_method": "last_msg", # Get the final response from the specialist
}
# Register the nested chat pattern on the planner
planner.register_nested_chats(
[
{
"sender": planner,
"recipient": data_analyst,
"message": lambda: "Analyze the following data and provide statistical insights...",
"max_turns": 2,
"summary_method": "reflection_with_llm"
},
{
"sender": planner,
"recipient": writer,
"message": lambda: "Transform the analysis into a polished report...",
"max_turns": 2,
"summary_method": "last_msg"
},
{
"sender": planner,
"recipient": fact_checker,
"message": lambda: "Verify all factual claims in the report...",
"max_turns": 2,
"summary_method": "reflection_with_llm"
}
],
trigger=lambda sender: sender.name == "UserProxy"
)
# Start the workflow
user_proxy.initiate_chat(
planner,
message="We have quarterly sales data showing: Q1=$2.3M, Q2=$2.8M, Q3=$3.1M, Q4=$4.2M. "
"Please analyze this data, create a narrative report, and fact-check all conclusions."
)
This nested chat pattern allows the planner to orchestrate specialist agents sequentially, collect their outputs, and synthesize a final response. Each sub-conversation runs independently with its own turn limits and termination conditions, keeping the main conversation clean and manageable.
Best Practices and Common Pitfalls
Do: Set Clear Termination Conditions
Without explicit termination conditions, agent conversations can loop indefinitely, consuming API credits and producing redundant outputs. Always configure max_consecutive_auto_reply, max_turns, or custom termination detection:
# Good practice: multiple termination safeguards
user_proxy = autogen.UserProxyAgent(
name="UserProxy",
max_consecutive_auto_reply=8, # Hard limit on turns
is_termination_msg=lambda x: (
"TASK_COMPLETE" in str(x.get("content", "")) or
"FINAL_ANSWER" in str(x.get("content", ""))
),
human_input_mode="TERMINATE" # Also stops if agent requests termination
)
Do: Use System Messages Effectively
The system message is the most powerful tool for shaping agent behavior. Be specific about the agent's role, output format, constraints, and when to stop. A vague system message leads to unpredictable behavior:
# Poor system message
system_message="You are a coding assistant."
# Excellent system message
system_message="""You are an expert Python developer specializing in data processing pipelines.
RULES:
1. Always include type hints and docstrings
2. Use defensive programming with proper error handling
3. Output code in fenced code blocks with the language specified
4. After providing code, explain the time and space complexity
5. When the task is complete, reply with 'TASK_COMPLETE' on its own line
6. If you need clarification, ask specific questions rather than making assumptions"""
Do: Leverage Seed Values for Reproducibility
When debugging agent behavior, set the seed parameter in llm_config. This ensures consistent LLM outputs across runs, making it possible to reproduce and fix issues:
llm_config = {
"config_list": [...],
"seed": 42 # Fixed seed for deterministic sampling
}
Don't: Overload a Single Agent
A common anti-pattern is creating one monolithic agent that tries to handle planning, execution, validation, and reporting. This leads to confused behavior and poor results. Instead, decompose responsibilities across specialized agents — even if they share the same LLM configuration, their distinct system messages create effective role separation.
Don't: Ignore Docker for Code Execution
When executing AI-generated code, always use Docker sandboxing in production scenarios. Without it, malicious or buggy code could affect your host system:
code_execution_config = {
"work_dir": "sandbox",
"use_docker": True, # ALWAYS use in production
"docker_image": "python:3.11-slim",
"timeout": 30
}
Don't: Forget to Monitor Token Usage
Multi-agent conversations can consume significant tokens, especially with long context windows and multiple agents. Implement logging to track usage:
import logging
# Enable AutoGen's built-in logging
logging.basicConfig(level=logging.INFO)
autogen_logger = logging.getLogger("autogen")
autogen_logger.setLevel(logging.INFO)
# Monitor token counts per agent
def track_token_usage(agent_name, prompt_tokens, completion_tokens):
print(f"[TOKENS] {agent_name}: {prompt_tokens} prompt + {completion_tokens} completion = "
f"{prompt_tokens + completion_tokens} total")
# Attach to agent's LLM calls via a wrapper
original_completion = autogen.oai.Completion.create
def tracked_completion(*args, **kwargs):
result = original_completion(*args, **kwargs)
if hasattr(result, 'usage'):
track_token_usage(
kwargs.get('agent_name', 'unknown'),
result.usage.prompt_tokens,
result.usage.completion_tokens
)
return result
# Note: In production, use AutoGen's built-in cost tracking utilities instead
print("Token monitoring configured")
Do: Implement Graceful Error Recovery
LLM APIs can fail due to rate limits, timeouts, or malformed responses. Design your agent conversations to handle these gracefully:
llm_config = {
"config_list": [...],
"request_timeout": 60,
"retry_wait_time": 10, # Exponential backoff between retries
"max_retry_period": 300, # Give up after 5 minutes of retries
"retry_on_rate_limit": True,
"retry_on_timeout": True
}
# Additionally, wrap initiate_chat in try/except for top-level error handling
try:
user_proxy.initiate_chat(assistant, message="Complex task description...")
except autogen.ChatCompletionError as e:
print(f"LLM API error: {e}. Saving conversation state and retrying with fallback model...")
# Fallback logic: switch to a cheaper/more available model
except autogen.MaxTurnsExceededError as e:
print(f"Conversation exceeded turn limit: {e}. Results may be incomplete.")
except Exception as e:
print(f"Unexpected error: {e}. Check agent configurations and API connectivity.")
Debugging Conversable Agents
When agent conversations don't behave as expected, systematic debugging is essential. Here are practical techniques:
import autogen
import json
# Technique 1: Enable verbose logging
autogen.ChatCompletion.set_log_level("DEBUG")
# Technique 2: Inspect conversation history
def save_conversation_history(agent, filepath="conversation_log.json"):
"""Save the full conversation history for post-mortem analysis"""
if hasattr(agent, 'chat_messages'):
history = agent.chat_messages
with open(filepath, 'w') as f:
json.dump(history, f, indent=2, default=str)
print(f"Conversation history saved to {filepath} with {len(history)} message groups")
# Technique 3: Add observation hooks
class ObservableAgent(autogen.AssistantAgent):
"""Agent subclass that logs every interaction"""
def generate_reply(self, messages=None, sender=None, **kwargs):
print(f"\n{'='*60}")
print(f"[OBSERVE] {self.name} generating reply to {sender.name if sender else 'Unknown'}")
print(f"[OBSERVE] Last message: {messages[-1].get('content', '')[:200] if messages else 'None'}...")
reply = super().generate_reply(messages=messages, sender=sender, **kwargs)
print(f"[OBSERVE] Generated reply ({len(str(reply))} chars): {str(reply)[:200]}...")
print(f"{'='*60}\n")
return reply
# Use the observable agent
observable_assistant = ObservableAgent(
name="ObservableAssistant",
system_message="You are a helpful assistant.",
llm_config=llm_config
)
# Technique 4: Step-by-step conversation replay
def replay_conversation(history_file):
"""Replay a saved conversation to understand what happened"""
with open(history_file, 'r') as f:
history = json.load(f)
for i, message_group in enumerate(history):
print(f"\n--- Turn {i+1} ---")
for msg in message_group:
print(f" [{msg.get('role', 'unknown')}] {msg.get('name', 'anonymous')}: "
f"{msg.get('content', '')[:150]}...")
print("Debugging utilities ready. Use save_conversation_history() and replay_conversation() for analysis.")
Production Deployment Considerations
When moving Conversable Agents from development to production, several additional factors come into play:
# Production-grade agent configuration
import autogen
import os
from datetime import datetime
# 1. Use environment-specific configuration
def get_production_config():
"""Load configuration based on deployment environment"""
env = os.environ.get("DEPLOYMENT_ENV", "development")
if env == "production":
return {
"config_list": [
{
"model": "gpt-4",
"api_key