Skip to content

Commit

Permalink
Added termination-based resumption in notebook, added test cases and …
Browse files Browse the repository at this point in the history
…improved robustness on resuming messages parameter
  • Loading branch information
marklysze committed May 10, 2024
1 parent 7c3ec9b commit 3aa1080
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 32 deletions.
34 changes: 22 additions & 12 deletions autogen/agentchat/groupchat.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import copy
import json
import logging
import random
Expand Down Expand Up @@ -1136,6 +1137,14 @@ def resume(
- Tuple[ConversableAgent, Dict]: A tuple containing the last agent who spoke and their message
"""

# Convert messages from string to messages list, if needed
if isinstance(messages, str):
messages = self.messages_from_string(messages)
elif isinstance(messages, list) and all(isinstance(item, dict) for item in messages):
messages = copy.deepcopy(messages)
else:
raise Exception("Messages is not of type str or List[Dict]")

# Clean up the objects, ensuring there are no messages in the agents and group chat

# Clear agent message history
Expand All @@ -1149,10 +1158,6 @@ def resume(
# Clear GroupChat messages
self._groupchat.reset()

# Convert messages from string to messages list, if needed
if isinstance(messages, str):
messages = self.messages_from_string(messages)

# Validation of message and agents

try:
Expand Down Expand Up @@ -1235,6 +1240,14 @@ async def a_resume(
- Tuple[ConversableAgent, Dict]: A tuple containing the last agent who spoke and their message
"""

# Convert messages from string to messages list, if needed
if isinstance(messages, str):
messages = self.messages_from_string(messages)
elif isinstance(messages, list) and all(isinstance(item, dict) for item in messages):
messages = copy.deepcopy(messages)
else:
raise Exception("Messages is not of type str or List[Dict]")

# Clean up the objects, ensuring there are no messages in the agents and group chat

# Clear agent message history
Expand All @@ -1248,10 +1261,6 @@ async def a_resume(
# Clear GroupChat messages
self._groupchat.reset()

# Convert messages from string to messages list, if needed
if isinstance(messages, str):
messages = self.messages_from_string(messages)

# Validation of message and agents

try:
Expand Down Expand Up @@ -1363,9 +1372,7 @@ def _process_resume_termination(self, remove_termination_string: str, messages:
# Check if the last message meets termination (if it has one)
if self._is_termination_msg:
if self._is_termination_msg(last_message):
logger.warning(
"WARNING: Last message meets termination criteria and this may terminate the chat. Set ignore_initial_termination_check=False to avoid checking termination at the start of the chat."
)
logger.warning("WARNING: Last message meets termination criteria and this may terminate the chat.")

def messages_from_string(self, message_string: str) -> List[Dict]:
"""Reads the saved state of messages in Json format for resume and returns as a messages list
Expand All @@ -1376,7 +1383,10 @@ def messages_from_string(self, message_string: str) -> List[Dict]:
returns:
- List[Dict]: List of messages
"""
state = json.loads(message_string)
try:
state = json.loads(message_string)
except json.JSONDecodeError:
raise Exception("Messages string is not a valid JSON string")

return state

Expand Down
38 changes: 27 additions & 11 deletions test/agentchat/test_groupchat.py
Original file line number Diff line number Diff line change
Expand Up @@ -1786,7 +1786,7 @@ def test_manager_messages_to_string():
manager = GroupChatManager(groupchat)

# Convert the messages List[Dict] to a JSON string
converted_string = manager.messages_to_string()
converted_string = manager.messages_to_string(messages)

# The conversion should match the original messages
assert json.loads(converted_string) == messages
Expand Down Expand Up @@ -1907,10 +1907,7 @@ def test_manager_resume_functions():
# Get the logged output and check that the warning was provided.
log_output = log_stream.getvalue()

assert (
"WARNING: Last message meets termination criteria and this may terminate the chat. Set ignore_initial_termination_check=False to avoid checking termination at the start of the chat."
in log_output
)
assert "WARNING: Last message meets termination criteria and this may terminate the chat." in log_output


def test_manager_resume_returns():
Expand Down Expand Up @@ -1938,19 +1935,37 @@ def test_manager_resume_returns():
assert return_message == messages[-1]

# Test when no agent provided, the manager will be returned
messages = [
{
"content": "You are an expert at coding.",
"role": "system",
}
]
messages = [{"content": "You are an expert at coding.", "role": "system", "name": "chat_manager"}]

return_agent, return_message = manager.resume(messages=messages)

assert return_agent == manager
assert return_message == messages[-1]


def test_manager_resume_messages():
"""Tests that the messages passed into resume are the correct format"""

coder = AssistantAgent(name="Coder", llm_config=None)
groupchat = GroupChat(messages=[], agents=[coder])
manager = GroupChatManager(groupchat)
messages = 1

# Only acceptable messages types are JSON str and List[Dict]

# Try a number
with pytest.raises(Exception):
return_agent, return_message = manager.resume(messages=messages)

# Try an empty string
with pytest.raises(Exception):
return_agent, return_message = manager.resume(messages="")

# Try a message starter string, which isn't valid
with pytest.raises(Exception):
return_agent, return_message = manager.resume(messages="Let's get this conversation started.")


if __name__ == "__main__":
# test_func_call_groupchat()
# test_broadcast()
Expand All @@ -1976,4 +1991,5 @@ def test_manager_resume_returns():
test_manager_messages_from_string()
test_manager_resume_functions()
test_manager_resume_returns()
test_manager_resume_messages()
# pass
147 changes: 138 additions & 9 deletions website/docs/topics/groupchat/resuming_groupchat.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,18 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": 2,
"metadata": {},
"outputs": [],
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/usr/local/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
" from .autonotebook import tqdm as notebook_tqdm\n"
]
}
],
"source": [
"import os\n",
"\n",
Expand Down Expand Up @@ -372,7 +381,7 @@
},
{
"cell_type": "code",
"execution_count": 16,
"execution_count": 86,
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -389,7 +398,7 @@
},
{
"cell_type": "code",
"execution_count": 17,
"execution_count": 87,
"metadata": {},
"outputs": [],
"source": [
Expand Down Expand Up @@ -465,7 +474,7 @@
},
{
"cell_type": "code",
"execution_count": 18,
"execution_count": 88,
"metadata": {},
"outputs": [
{
Expand All @@ -490,7 +499,7 @@
},
{
"cell_type": "code",
"execution_count": 19,
"execution_count": 89,
"metadata": {},
"outputs": [
{
Expand Down Expand Up @@ -519,7 +528,7 @@
},
{
"cell_type": "code",
"execution_count": 20,
"execution_count": 90,
"metadata": {},
"outputs": [
{
Expand All @@ -537,7 +546,7 @@
},
{
"cell_type": "code",
"execution_count": 21,
"execution_count": 91,
"metadata": {},
"outputs": [
{
Expand Down Expand Up @@ -575,7 +584,7 @@
},
{
"cell_type": "code",
"execution_count": 22,
"execution_count": 92,
"metadata": {},
"outputs": [
{
Expand All @@ -598,6 +607,126 @@
" f\"{'...' if len(message['content']) > 80 else ''}\".replace(\"\\n\", \" \"),\n",
" )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example of resuming a terminated GroupChat with a new message and agent\n",
"\n",
"Rather than continuing a group chat by using the last message, we can resume a group chat using a new message.\n",
"\n",
"**IMPORTANT**: To remain in a group chat, use the GroupChatManager to initiate the chat, otherwise you can continue with an agent-to-agent conversation by using another agent to initiate the chat.\n",
"\n",
"We'll continue with the previous example by using the messages from that conversation and resuming it with a new conversation in the agent 'meeting'.\n",
"\n",
"We start by preparing the group chat by using the messages from the previous chat."
]
},
{
"cell_type": "code",
"execution_count": 93,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"WARNING: Last message meets termination criteria and this may terminate the chat. Set ignore_initial_termination_check=False to avoid checking termination at the start of the chat.\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Prepared group chat with 5 messages, the last speaker is \u001b[33mChief_Marketing_Officer\u001b[0m\n"
]
}
],
"source": [
"# Prepare the group chat for resuming using the previous messages. We don't need to remove the TERMINATE string as we aren't using the last message for resuming.\n",
"last_agent, last_message = manager.resume(messages=groupchat.messages)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's continue the meeting with a new topic."
]
},
{
"cell_type": "code",
"execution_count": 94,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[33mchat_manager\u001b[0m (to Chief_Marketing_Officer):\n",
"\n",
"Team, let's now think of a name for the next vehicle that embodies that idea. Chief_Marketing_Officer and Product_manager can you both suggest one and then we can conclude.\n",
"\n",
"--------------------------------------------------------------------------------\n",
"\u001b[33mChief_Marketing_Officer\u001b[0m (to chat_manager):\n",
"\n",
"Given the focus on sustainability and luxury, I suggest the name \"VerdeVogue\" for our next vehicle. \"Verde\" reflects the green, eco-friendly aspect of the car, while \"Vogue\" emphasizes its stylish and trendsetting nature in the luxury market. This name encapsulates the essence of combining environmental responsibility with high-end design and performance. \n",
"\n",
"Now, I'd like to hear the Product_Manager's suggestion.\n",
"\n",
"--------------------------------------------------------------------------------\n",
"\u001b[33mProduct_Manager\u001b[0m (to chat_manager):\n",
"\n",
"For our next vehicle, I propose the name \"EcoPrestige.\" This name highlights the vehicle's eco-friendly nature and its luxurious, prestigious status in the market. \"Eco\" emphasizes our commitment to sustainability and environmental responsibility, while \"Prestige\" conveys the car's high-end quality, sophistication, and the elite status it offers to its owners. This name perfectly blends our goals of offering a sustainable luxury vehicle that doesn't compromise on performance or style.\n",
"\n",
"--------------------------------------------------------------------------------\n",
"\u001b[33mChief_Marketing_Officer\u001b[0m (to chat_manager):\n",
"\n",
"Thank you, Product_Manager, for your suggestion. Both \"VerdeVogue\" and \"EcoPrestige\" capture the essence of our new vehicle's eco-friendly luxury. As we move forward, we'll consider these names carefully to ensure our branding aligns perfectly with our product's unique value proposition and market positioning. \n",
"\n",
"This concludes our meeting. Thank you, everyone, for your valuable contributions. TERMINATE.\n",
"\n",
"--------------------------------------------------------------------------------\n"
]
}
],
"source": [
"# Resume the chat using a different agent and message\n",
"result = manager.initiate_chat(\n",
" recipient=cmo,\n",
" message=\"Team, let's now think of a name for the next vehicle that embodies that idea. Chief_Marketing_Officer and Product_manager can you both suggest one and then we can conclude.\",\n",
" clear_history=False,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 95,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"#1, Chairperson: Let's get this meeting started. We'll have a set order of speakers. First the Pr ...\n",
"#2, Chief_Marketing_Officer: Sounds like a plan! Let's get started. As the Product_Manager, I'd like to present ...\n",
"#3, Product_Manager: Thank you for presenting those innovative product ideas, Product_Manager. After ...\n",
"#4, Digital_Marketer: Thank you, Chief_Marketing_Officer! For 'EcoLux', I propose the following three ...\n",
"#5, Chief_Marketing_Officer: Given the focus on sustainability and luxury, I suggest the name \"VerdeVogue\" for ...\n",
"#6, Product_Manager: For our next vehicle, I propose the name \"EcoPrestige.\" This name highlights the ...\n",
"#7, Chief_Marketing_Officer: Thank you, Product_Manager, for your suggestion. Both \"VerdeVogue\" and \"EcoPrest ...\n"
]
}
],
"source": [
"# Output the final chat history showing the original 4 messages and the resumed message\n",
"for i, message in enumerate(groupchat.messages):\n",
" print(\n",
" f\"#{i + 1}, {message['name']}: {message['content'][:80]}\".replace(\"\\n\", \" \"),\n",
" f\"{'...' if len(message['content']) > 80 else ''}\".replace(\"\\n\", \" \"),\n",
" )"
]
}
],
"metadata": {
Expand Down

0 comments on commit 3aa1080

Please sign in to comment.