Autogen study: Focus Group

Focus group recreation using agents

July 20, 2024 · 20 mins read

Exploring AutoGen: A Framework for Multi-Agent Conversations

In this post, we explore AutoGen, an innovative open-source framework by Microsoft designed to create and manage AI agents capable of interacting to solve tasks collaboratively. AutoGen simplifies the development of next-gen language model applications by enabling complex workflows with minimal effort. It offers a comprehensive solution for orchestrating, automating, and optimizing language model interactions.

Key Features of AutoGen

1. Multi-Agent Conversations: AutoGen enables the creation of multi-agent systems where agents can converse with each other and with users to accomplish various tasks. These agents can be customized to fit specific needs and integrated with different tools and language models.

2. Diverse Conversation Patterns: The framework supports various conversation patterns, allowing for flexible and dynamic interactions. Developers can design agents with different levels of autonomy and configure them to follow specific conversation topologies, enhancing their ability to handle complex workflows.

3. Integration and Tool Support: AutoGen provides seamless integration with large language models (LLMs) and various tools, enabling agents to perform tasks autonomously or with human assistance. This includes support for code execution and the use of external APIs.

4. Autonomous and Human-in-the-Loop Workflows: The framework facilitates both fully autonomous workflows and those that involve human intervention, providing flexibility in how tasks are completed. This ensures that agents can operate independently while also allowing for human oversight when necessary.

5. Research-Driven Development: AutoGen is powered by collaborative research from institutions like Microsoft, Penn State University, and the University of Washington, ensuring that it remains at the forefront of AI and machine learning advancements.

Project Aim: Reproducing a Focus Group

The core aim of this project is to leverage AutoGen to recreate a focus group using AI agents. By simulating the dynamics of a focus group, we can explore how different agents interact, provide insights, and collaborate to achieve specific objectives. This practical application not only demonstrates the capabilities of AutoGen but also provides valuable insights into how multi-agent systems can be utilized in real-world scenarios. We’ll provide to each agent a background to base their thought on, a specific agent will act as moderator and then another agent will analysis the output. The base code can be found In this repo

Creating Trackable Agents in AutoGen

In this blog post, we will explore how to create trackable agents using the AutoGen framework by Microsoft. These agents will be able to process and display messages in a user-friendly manner using Streamlit. Let’s walk through the creation of three key components: TrackableAssistantAgent, TrackableUserProxyAgent, and TrackableGroupChatManager.

TrackableAssistantAgent

The TrackableAssistantAgent class extends the AssistantAgent class from the AutoGen framework. This agent is designed to process incoming messages and display them using Streamlit’s chat interface. Here’s how we define it:

class TrackableAssistantAgent(AssistantAgent):
    def _process_received_message(self, message, sender, silent):
        with st.chat_message(sender.name):
            st.markdown(message)
        return super()._process_received_message(message, sender, silent)

Explanation

  • Inheritance: TrackableAssistantAgent inherits from AssistantAgent.
  • Method Override: _process_received_message is overridden to add custom behavior.
  • Streamlit Integration: Using st.chat_message(sender.name), messages are displayed in the chat interface with the sender’s name.
  • Calling Super Method: After custom processing, it calls the super class method to ensure the original message processing logic is preserved.

We’ll do the same for the rest of assistants

class TrackableUserProxyAgent(UserProxyAgent):
    def _process_received_message(self, message, sender, silent):
        with st.chat_message(sender.name):
            st.markdown(message)
        return super()._process_received_message(message, sender, silent)

class TrackableGroupChatManager(GroupChatManager):
    def _process_received_message(self, message, sender, silent):
        with st.chat_message(sender.name):
            st.markdown(message)
        return super()._process_received_message(message, sender, silent)

generate_notice

The generate_notice function is designed to generate a notice message tailored to a specific role. Here’s a detailed breakdown of the function:

def generate_notice(role: str = "researcher", base_notice: Optional[str] = None) -> str:
    """
    Generates a notice message based on the role.

    Parameters:
        role (str): The role of the agent.
        base_notice (Optional[str]): The base notice message.

    Returns:
        str: The generated notice message.
    """
    if base_notice is None:
        base_notice = (
            'You are part of a research panel to gather relevant information of a new product that our client is launching. We appreciate your collaboration.'
        )

    non_persona_notice = (
        'Do not show appreciation in your responses, say only what is necessary. '
        'If "Thank you" or "You\'re welcome" are said in the conversation, then say TERMINATE '
        'to indicate the conversation is finished and this is your last message.'
    )

    persona_notice = (
        ' Act as {role} when responding to queries, providing feedback, asked for your personal opinion '
        'or participating in discussions.'
    )

    if role.lower() in ["manager", "researcher"]:
        return base_notice + non_persona_notice
    else:
        return base_notice + persona_notice.format(role=role)

The generate_notice function is designed to create a tailored notice message based on the role of an agent. Let’s break down how this function works in a friendly and easy-to-understand manner.

Function Overview

The generate_notice function takes two parameters: role and base_notice. The role parameter specifies the role of the agent, with a default value of “researcher”. The base_notice parameter is an optional string that can be used to provide a custom base notice message. If no base_notice is provided, the function uses a default message.

If no base_notice is provided, the function sets it to a default message that welcomes the agent to a research panel. The function then defines two additional notice messages. The first one, non_persona_notice, instructs agents to keep their responses strictly professional and to use “TERMINATE” to end the conversation if “Thank you” or “You’re welcome” is mentioned. The second one, persona_notice, tells the agent to act according to their role, providing a more personalized touch. Finally, the function decides which notice to append to the base_notice based on the agent’s role. If the role is “manager” or “researcher”, the function appends the non_persona_notice. Otherwise, it appends the persona_notice, formatted with the given role.

Code Explanation: custom_speaker_selection Function

The custom_speaker_selection function ensures that the Researcher interacts with each participant in a group chat 2-3 times. It alternates between the Researcher and participants, tracking interactions to maintain a balanced conversation. Here’s a detailed explanation of how this function works.

Function Overview

The custom_speaker_selection function takes two parameters: last_speaker and group_chat. The last_speaker is the agent who last spoke in the chat, and group_chat is the group chat context containing all agents.

def custom_speaker_selection(last_speaker: Optional[Agent], group_chat: GroupChat) -> Optional[Agent]:
    """
    Custom function to ensure the Researcher interacts with each participant 2-3 times.
    Alternates between the Researcher and participants, tracking interactions.
    """

Interaction Counters Initialization

The function first checks if the group_chat object has an interaction_counters attribute. If not, it initializes this attribute as a dictionary to track the number of interactions for each participant (excluding the Researcher).

if not hasattr(group_chat, 'interaction_counters'):
    group_chat.interaction_counters = {agent.name: 0 for agent in group_chat.agents if agent.name != "Researcher"}

Maximum Interactions

A maximum number of interactions per participant is defined to limit the conversation length.

max_interactions = 6

Alternating Speakers

The function then decides the next speaker based on who spoke last.

  1. If the Researcher was the last speaker:
    • The function finds the participant with the fewest interactions and selects them as the next speaker.
    • If the selected participant has not reached the maximum number of interactions, their counter is incremented.
    • If all participants have reached the maximum interactions, the function returns None to end the conversation.
if last_speaker and last_speaker.name == "Researcher":
    next_participant = min(group_chat.interaction_counters, key=group_chat.interaction_counters.get)
    if group_chat.interaction_counters[next_participant] < max_interactions:
        group_chat.interaction_counters[next_participant] += 1
        return next((agent for agent in group_chat.agents if agent.name == next_participant), None)
    else:
        return None  # End the conversation if all participants have reached the maximum interactions
  1. If a participant was the last speaker:
    • The function selects the Researcher as the next speaker.
else:
    return next((agent for agent in group_chat.agents if agent.name == "Researcher"), None)

Function: create_research_panel

The create_research_panel function initializes a panel of research assistant agents based on consumer profiles.

def create_research_panel() -> None:
    consumer_profiles = {}
    assistant_agents = []
    for profile in consumer_profiles:
        agent = TrackableAssistantAgent(
            name=profile["name"],
            llm_config=llm_config,
            system_message=profile["system_message"],
            human_input_mode=profile["human_input_mode"]  # Ensure the agent asks for human input
        )
        assistant_agents.append(agent)
    for agent in assistant_agents:
        print(f"Created agent: {agent.name}")
    return assistant_agents

Explanation

  1. Initialization:
    • consumer_profiles is an empty dictionary intended to hold profiles of consumers.
    • assistant_agents is an empty list to store created agents.
  2. Creating Agents:
    • Iterates over consumer_profiles to create TrackableAssistantAgent instances.
    • Each agent is initialized with a name, configuration (llm_config), system message, and human input mode.
    • Created agents are added to the assistant_agents list.
  3. Printing Agents:
    • Prints the name of each created agent for confirmation.
  4. Return:
    • Returns the list of created assistant agents.

Function: create_group_chat

The create_group_chat function sets up a group chat with the specified agents and a custom speaker selection method.

def create_group_chat(agents:list, custom_selection):
    groupchat = GroupChat(
        agents=agents,
        speaker_selection_method = custom_selection,
        messages=[],
        max_round=30)
    # create a UserProxyAgent instance named "user_proxy"
    user_proxy = TrackableUserProxyAgent(
        name="user_proxy",
        code_execution_config={"last_n_messages": 2, "work_dir": "groupchat", 'use_docker':False},
        system_message="A human admin.",
        human_input_mode="NEVER"
    )
    # Initialise the manager
    manager = TrackableGroupChatManager(
        groupchat=groupchat,
        llm_config=llm_config,
        system_message="You are a research manager agent that can manage a group chat of multiple agents made up of a researcher agent and many people made up of a panel. You will limit the discussion between the panelists and help the researcher in asking the questions. Please ask the researcher first on how they want to conduct the panel." + generate_notice(),
        is_termination_msg=lambda x: True if "TERMINATE" in x.get("content") else False,
    )
    return groupchat, user_proxy, manager

Explanation

  1. Group Chat Initialization:
    • Creates a GroupChat instance with the provided agents and custom speaker selection method.
    • Sets initial messages to an empty list and limits the conversation to 30 rounds.
  2. User Proxy Agent:
    • Creates a TrackableUserProxyAgent named “user_proxy” to act as a human admin with specific configuration settings.
    • This agent does not require human input (human_input_mode="NEVER").
  3. Group Chat Manager:
    • Initializes a TrackableGroupChatManager to manage the group chat.
    • The manager is configured with the group chat instance, llm_config, and a detailed system message.
    • The system message instructs the manager on how to conduct the panel.
    • A lambda function is used to identify termination messages containing “TERMINATE”.
  4. Return:
    • Returns the group chat, user proxy, and manager instances.

Function: summarization_agent

The summarization_agent function creates an agent for summarizing the group chat discussions.

def summarization_agent(summary_prompt:str=summary_agent_prompt):
    # Get response from the groupchat for user prompt
    
    # Generate system prompt for the summary agent
    summary_agent = TrackableAssistantAgent(
        name="SummaryAgent",
        llm_config=llm_config,
        system_message=summary_prompt + generate_notice(),
    )
    summary_proxy = TrackableUserProxyAgent(
        name="summary_proxy",
        code_execution_config={"last_n_messages": 2, "work_dir": "groupchat", 'use_docker':False},
        system_message="A human admin.",
        human_input_mode="TERMINATE"
    )
    return summary_agent, summary_proxy

Explanation

  1. Summary Agent Initialization:
    • Creates a TrackableAssistantAgent named “SummaryAgent”.
    • The agent is initialized with llm_config and a system message that combines the provided summary prompt with a generated notice.
  2. Summary Proxy Agent:
    • Creates a TrackableUserProxyAgent named “summary_proxy” to assist with summarization.
    • Configured to handle the last two messages and not use Docker.
    • This agent terminates human input upon receiving the termination message (“TERMINATE”).
  3. Return:
    • Returns the summary agent and summary proxy instances.

Final thoughts

In conclusion, while I won’t get into all the nitty-gritty details of the application here, I hope the attached images give you a good visual understanding of how it all comes together. This project is built upon the impressive groundwork laid out in the AutoGen Focus Group project.

What stands out the most is the seamless interaction between AI agents, especially when they are given a specific background to operate from. It’s fascinating to see how these agents can emulate human-like discussions, providing valuable insights in a structured and efficient manner.

Next Steps for Production

To move this project into production, several steps need to be taken:

  1. Testing and Refinement:
    • Conduct extensive testing with real users to refine the interactions and improve the system based on feedback.
  2. Scalability:
    • Ensure the system is scalable to handle more agents and participants without compromising performance.
  3. Deployment:
    • Deploy the system on a reliable cloud platform to guarantee accessibility and stability.
  4. Integration:
    • Integrate with existing data analysis tools and develop APIs for better interoperability with other systems.

You can check out the Streamlit app to see it in action: Streamlit App

This journey into AI-driven research panels is just beginning, and I am excited to see how these technologies will continue to evolve and enhance our capabilities. Stay tuned for more updates as we progress!